/* ==========================================================================
   Fabric Studio Manager for GCP -- design tokens
   Palette: Fortinet-red accent on a light, clinical "security console" base.
   Type: Space Grotesk (display/nav) + Inter (body/UI) + JetBrains Mono (data)
   Signature motif: pulsing status dots + hairline "circuit trace" dividers,
   echoing network/security monitoring consoles.
   ========================================================================== */

:root {
  --flb-red: #DA1A32;
  --flb-red-dark: #A8101F;
  --flb-red-tint: #FDEBEC;
  --flb-ink: #0F2337;
  --flb-ink-soft: #1B3A54;
  --flb-text-primary: #0F2337;
  --flb-text-secondary: #1B3A54;
  --flb-slate: #55697A;
  --flb-slate-light: #8797A5;
  --flb-bg: #F2F5F7;
  --flb-card: #FFFFFF;
  --flb-border: #E2E8ED;
  --flb-border-strong: #CBD5DD;
  --flb-green: #1B9C6E;
  --flb-green-tint: #E7F7F0;
  --flb-amber: #DC8B1E;
  --flb-amber-tint: #FDF3E4;
  --flb-gray: #94A3AF;
  --flb-gray-tint: #EEF1F3;
  --flb-blue: #1E6FD9;
  --flb-blue-tint: #E8F0FD;
  /* API debug log method colors only (Logs > API debug) -- aqua for
     PATCH, yellow for SSH. Not reused elsewhere, so these exist purely
     to keep that one table's color-coding theme-aware in both palettes
     rather than hardcoding a color that'd look wrong in dark mode. */
  --flb-aqua: #2FA894;
  --flb-aqua-tint: #E3F6F2;
  --flb-yellow: #B08900;
  --flb-yellow-tint: #FBF3D8;
  /* Scheduler calendar's Saturday/Sunday column tint -- deliberately its
     own variable rather than reusing --flb-gray-tint/.cal-cell--out's
     color: those mark days OUTSIDE the viewed month (a muted, receded
     look), while this marks weekEND days WITHIN it (a "notice me"
     highlight) -- reusing either would make the two mean the same thing
     visually when they don't. */
  --cal-weekend-bg: #EEF4FC;
  /* Public-holiday cell tint (Scheduler's "Show holidays" menu) -- its
     own color (a soft lavender, not used anywhere else in this app) so
     a holiday reads as its own distinct thing rather than blending into
     the weekend tint above or looking like a status color borrowed from
     somewhere else. */
  --cal-holiday-bg: #F1EDFB;

  --font-display: 'Space Grotesk', 'Segoe UI', system-ui, sans-serif;
  --font-body: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif;
  --font-mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;

  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 16px;
  --shadow-card: 0 1px 2px rgba(15,35,55,.04), 0 6px 20px rgba(15,35,55,.06);
  --shadow-pop: 0 10px 40px rgba(15,35,55,.18);
}

/* Dark ("Night") theme -- applied via a data-theme attribute on <html>,
   set by JS reading a long-lived cookie so it's remembered across
   logins, not just within one session. The login screen never gets this
   attribute, so it always stays on the light palette regardless of what
   a user picked after signing in. --flb-ink/--flb-ink-soft are left
   alone here since they're used as intentionally-dark BACKGROUNDS
   (sidebar, primary buttons, toast) that already look right in both
   themes -- only the surface colors (bg/card/border) and the dedicated
   text-color variables actually flip. */
html[data-theme="dark"] {
  --flb-red: #FF5A70;
  --flb-red-dark: #E23A50;
  --flb-red-tint: #3A1620;
  --flb-text-primary: #E8EDF2;
  --flb-text-secondary: #B7C4CF;
  --flb-slate: #93A3B0;
  --flb-slate-light: #6B7C8A;
  --flb-bg: #0C141C;
  --flb-card: #16212C;
  --flb-border: #2A3A48;
  --flb-border-strong: #3C4F5E;
  --flb-green: #3DD9A0;
  --flb-green-tint: #113328;
  --flb-amber: #F2A93C;
  --flb-amber-tint: #3A2A10;
  --flb-gray: #8394A2;
  --flb-gray-tint: #1E2B37;
  --flb-blue: #6FA8FF;
  --flb-blue-tint: #16283C;
  --flb-aqua: #5FD6C0;
  --flb-aqua-tint: #123B33;
  --flb-yellow: #E8C547;
  --flb-yellow-tint: #3A3010;
  /* Noticeably lighter/bluer than .cal-cell--out's dark-mode background
     (#101B24) on purpose -- a weekend should read as highlighted, not as
     receded the way an out-of-month day does. */
  --cal-weekend-bg: #17293D;
  /* Same reasoning as --cal-weekend-bg above -- a lighter, more purple-
     leaning tint than the base card color, distinct from both it and
     the weekend tint, so a holiday still reads as its own thing here. */
  --cal-holiday-bg: #241B36;
  --shadow-card: 0 1px 2px rgba(0,0,0,.35), 0 6px 20px rgba(0,0,0,.4);
  --shadow-pop: 0 10px 40px rgba(0,0,0,.55);
}
html[data-theme="dark"] body { background: var(--flb-bg); }
html[data-theme="dark"] .cal-cell--out { background: #101B24; }
html[data-theme="dark"] thead th { background: #101B24; }
html[data-theme="dark"] .btn-outline { background: transparent; }
html[data-theme="dark"] input, html[data-theme="dark"] select, html[data-theme="dark"] textarea {
  background: #101B24; border-color: var(--flb-border);
}
html[data-theme="dark"] .field input:focus, html[data-theme="dark"] .field select:focus, html[data-theme="dark"] .field textarea:focus { background: #101B24; }
html[data-theme="dark"] .topbar { background: var(--flb-card); }
html[data-theme="dark"] .modal, html[data-theme="dark"] .modal-backdrop .modal { background: var(--flb-card); }
html[data-theme="dark"] .btn-danger { background: transparent; }
html[data-theme="dark"] .topbar-dropdown { background: var(--flb-card); }
html[data-theme="dark"] .toast-stack .toast { background: var(--flb-card); border: 1px solid var(--flb-border); }
/* .section-note's background already flips via --flb-amber-tint, but its
   text/border colors were hardcoded for the light (pale amber bg / dark
   brown text) combination -- against the dark theme's much darker amber
   tint, that same brown text has poor contrast and reads as barely
   legible, which matters here since this is also how error messages are
   shown throughout the app. */
html[data-theme="dark"] .section-note { color: var(--flb-amber); border-color: #5A4420; }

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  font-family: var(--font-body);
  background: var(--flb-bg);
  color: var(--flb-text-primary);
  -webkit-font-smoothing: antialiased;
}
h1, h2, h3, .display { font-family: var(--font-display); letter-spacing: -0.01em; }
code, .mono { font-family: var(--font-mono); }
a { color: inherit; }
button { font-family: inherit; }
:focus-visible {
  outline: 2px solid var(--flb-red);
  outline-offset: 2px;
  border-radius: 4px;
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 9px 16px; border-radius: var(--radius-sm); border: 1px solid transparent;
  font-weight: 600; font-size: 13.5px; cursor: pointer; transition: transform .12s ease, background .15s ease, box-shadow .15s ease;
  white-space: nowrap;
}
/* Inline icon markup (e.g. ICONS.upload) has no intrinsic size of its
   own -- without this, an <svg> dropped into a .btn (as opposed to
   .icon-btn, which already sizes its svg below) falls back to the
   browser's default replaced-element size and renders huge. */
.btn svg { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 2; flex-shrink: 0; }
.btn:active { transform: translateY(1px); }
.btn-primary { background: var(--flb-red); color: #fff; box-shadow: 0 2px 8px rgba(218,26,50,.28); }
.btn-primary:hover { background: var(--flb-red-dark); }
.btn-secondary { background: var(--flb-ink); color: #fff; }
.btn-secondary:hover { background: var(--flb-ink-soft); }
.btn-outline { background: #fff; color: var(--flb-text-primary); border-color: var(--flb-border-strong); }
.btn-outline:hover { border-color: var(--flb-red); color: var(--flb-red); }
.btn-danger { background: #fff; color: var(--flb-red); border-color: var(--flb-red-tint); }
.btn-danger:hover { background: var(--flb-red-tint); }
.btn-sm { padding: 6px 12px; font-size: 12.5px; }
.btn-block { width: 100%; }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.icon-btn { padding: 6px; border-radius: var(--radius-sm); background: transparent; border: 1px solid var(--flb-border); cursor: pointer; color: var(--flb-slate); }
.icon-btn:hover { border-color: var(--flb-red); color: var(--flb-red); }
.icon-btn:disabled { opacity: .4; cursor: not-allowed; pointer-events: none; }
/* A smaller variant for an icon-btn that needs to sit inline with
   regular body text rather than at the default ~30px (padding + border
   + a 16px svg) size -- same idea as .ip-copy-row's own icon-btn
   override, just as a reusable class instead of scoped to one parent. */
.icon-btn-sm { width: 20px; height: 20px; padding: 0; }
.icon-btn-sm svg { width: 12px; height: 12px; }
.icon-btn svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 2; }

/* ---------- Auth pages ---------- */
.auth-body { background: var(--flb-ink); }
.auth-shell {
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  position: relative; overflow: hidden; padding: 24px;
  background:
    radial-gradient(1200px 600px at 15% -10%, rgba(218,26,50,.25), transparent 60%),
    radial-gradient(900px 500px at 110% 110%, rgba(255,255,255,.06), transparent 60%),
    var(--flb-ink);
}
.auth-trace {
  position: absolute; inset: 0; opacity: .35; pointer-events: none;
  background-image:
    linear-gradient(rgba(255,255,255,.05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,255,255,.05) 1px, transparent 1px);
  background-size: 42px 42px;
  mask-image: radial-gradient(700px 500px at 30% 30%, #000, transparent 70%);
}
.auth-card {
  position: relative; width: 100%; max-width: 400px; background: #fff;
  border-radius: var(--radius-lg); padding: 36px 34px 30px; box-shadow: var(--shadow-pop);
}
.brand-mark { display: flex; align-items: center; gap: 12px; margin-bottom: 22px; }
.brand-logo-img { height: 72px; width: auto; flex: 0 0 auto; display: block; }
.brand-name { font-family: var(--font-display); font-weight: 700; font-size: 19px; color: var(--flb-text-primary); line-height: 1.1; }
.brand-sub { font-size: 11.5px; color: var(--flb-slate); letter-spacing: .03em; text-transform: uppercase; }
.brand-version { font-family: var(--font-mono); font-size: 10px; color: var(--flb-slate-light); letter-spacing: .02em; margin-top: 3px; }
.auth-card h1 { font-size: 22px; margin: 0 0 6px; }
.auth-copy { color: var(--flb-slate); font-size: 13.5px; margin: 0 0 20px; }
.auth-form { display: flex; flex-direction: column; gap: 14px; }
.field { display: flex; flex-direction: column; gap: 6px; font-size: 12.5px; font-weight: 600; color: var(--flb-text-secondary); min-width: 0; }
.field input, .field select, .field textarea {
  font: inherit; font-size: 14px; padding: 10px 12px; border-radius: var(--radius-sm);
  border: 1px solid var(--flb-border-strong); background: #fbfcfd; color: var(--flb-text-primary);
  width: 100%; max-width: 100%;
}
.field select { text-overflow: ellipsis; overflow: hidden; white-space: nowrap; }
.field input:focus, .field select:focus, .field textarea:focus { border-color: var(--flb-red); background: #fff; }
/* input[type=file] inherits the same 10px/12px padding as every other
   field above, but a native file input renders that padding around its
   whole control INCLUDING the browser's own "Choose file" button, which
   inflates the whole thing to a much bigger control than any other field
   on the same form. Overridden down to a compact size, with the native
   button itself restyled via ::file-selector-button to match this app's
   own small outline buttons instead of the browser default. */
.field input[type="file"] { padding: 5px 8px; font-size: 12.5px; }
.field input[type="file"]::file-selector-button {
  font: inherit; font-size: 12px; padding: 5px 10px; margin-right: 10px;
  border-radius: var(--radius-sm); border: 1px solid var(--flb-border-strong);
  background: var(--flb-bg); color: var(--flb-text-primary); cursor: pointer;
}
.field input[type="file"]::file-selector-button:hover { background: var(--flb-gray-tint); }
.password-field { position: relative; display: flex; }
.password-field input { padding-right: 38px; }
.password-toggle-btn {
  position: absolute; right: 0; top: 0; bottom: 0; width: 36px; padding: 0; background: none; border: none;
  color: var(--flb-slate); cursor: pointer; display: flex; align-items: center; justify-content: center;
}
.password-toggle-btn:hover { color: var(--flb-red); }
.password-toggle-btn svg { width: 17px; height: 17px; stroke: currentColor; fill: none; stroke-width: 1.7; }
.password-toggle-btn .icon-eye-off { display: none; }
.password-toggle-btn.is-revealed .icon-eye { display: none; }
.password-toggle-btn.is-revealed .icon-eye-off { display: block; }
/* A copy icon inside a .password-field wrapper -- sits at the far right
   (right: 0) when it's the only button (e.g. FortiFlex API ID, a plain
   text field with nothing to reveal/hide), or shifts one slot left
   (.field-copy-btn--with-toggle, right: 36px) to sit beside the
   show/hide eye button on an actual password field without the two
   overlapping -- see passwordFieldHtml's own copyable option. */
.field-copy-btn {
  position: absolute; right: 0; top: 0; bottom: 0; width: 36px; padding: 0; background: none; border: none;
  color: var(--flb-slate); cursor: pointer; display: flex; align-items: center; justify-content: center;
}
.field-copy-btn:hover { color: var(--flb-red); }
.field-copy-btn svg { width: 15px; height: 15px; }
.field-copy-btn.field-copy-btn--with-toggle { right: 36px; }
.password-field.has-copy input { padding-right: 72px; }
.auth-foot { margin-top: 18px; font-size: 12px; color: var(--flb-slate-light); text-align: center; }
.alert { padding: 10px 12px; border-radius: var(--radius-sm); font-size: 13px; margin-bottom: 14px; }
.alert-error { background: var(--flb-red-tint); color: var(--flb-red-dark); border: 1px solid #f6c8cc; }

/* ---------- Global in-flight progress bar (top of viewport) ---------- */
.top-progress {
  position: fixed; top: 0; left: 0; right: 0; height: 3px; z-index: 100;
  overflow: hidden; opacity: 0; transition: opacity .25s ease .1s; pointer-events: none;
}
.top-progress.active { opacity: 1; transition-delay: 0s; }
.top-progress .bar {
  position: absolute; top: 0; height: 100%; width: 34%; left: -34%;
  background: linear-gradient(90deg, var(--flb-red), var(--flb-red-dark));
  box-shadow: 0 0 10px rgba(218,26,50,.55);
  border-radius: 0 4px 4px 0;
}
.top-progress.active .bar { animation: topProgressSlide 1.15s cubic-bezier(.4,0,.2,1) infinite; }
@keyframes topProgressSlide {
  0%   { left: -34%; width: 28%; }
  55%  { left: 45%;  width: 38%; }
  100% { left: 100%; width: 28%; }
}

/* ---------- App shell ---------- */
.app-body { overflow: hidden; }
.app-shell { display: flex; height: 100vh; }
.sidebar {
  width: 232px; flex: 0 0 232px; background: var(--flb-ink); color: #fff;
  display: flex; flex-direction: column; padding: 18px 14px;
}
.brand-mark--sidebar { padding: 4px 6px 18px; border-bottom: 1px solid rgba(255,255,255,.08); margin-bottom: 12px; cursor: pointer; }
.brand-mark--sidebar .brand-logo-img { height: 58px; }
.brand-logo-circle {
  width: 70px; height: 70px; border-radius: 50%; background: #fff;
  border: 1px solid var(--flb-red); flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
}
.brand-logo-circle .brand-logo-img { height: 64px; }
/* Centered within #content (the main content viewport, to the right of
   the sidebar and below the topbar) and sized in JS on every hover --
   75% of whichever of #content's current width/height is smaller, so it
   always fits fully on screen regardless of window size. left/top/
   width/height are all set inline by wireLogoHoverAndAbout(); only the
   fade/scale-in look lives here. */
.logo-hover-preview {
  position: fixed; z-index: 400; pointer-events: none; opacity: 0;
  transform: scale(.92); transition: opacity .12s ease, transform .12s ease;
  filter: drop-shadow(0 10px 28px rgba(0,0,0,.35));
}
.logo-hover-preview.visible { opacity: 1; transform: scale(1); }
.logo-hover-preview img { display: block; width: 100%; height: 100%; object-fit: contain; }
/* The About popup opened by clicking the sidebar logo -- logo enlarged
   on the left (almost the modal's own height), identity/version/credit/
   disclaimer stacked on the right. */
.about-modal { display: flex; gap: 28px; align-items: center; }
.about-modal-logo { width: 300px; height: 300px; object-fit: contain; flex: 0 0 auto; }
.about-modal-info { min-width: 0; flex: 1 1 auto; }
.about-modal-kicker { font-size: 11px; text-transform: uppercase; letter-spacing: .08em; color: var(--flb-slate); font-weight: 700; }
.about-modal-name { margin-top: 10px; font-family: var(--font-display); font-weight: 700; font-size: 21px; color: var(--flb-text-primary); line-height: 1.2; }
/* Two spans, plain inline by default so narrow modals just wrap the whole
   phrase across as many lines as they need. Once the modal has room to
   breathe (its own max-width fully kicks in around 688px viewport
   width), force the two-line "Fabric Studio Manager" / "for GCP" split. */
@media (min-width: 688px) {
  .about-modal-name span { display: block; }
}
.about-modal-version { font-family: var(--font-mono); font-size: 12px; color: var(--flb-slate); margin-top: 2px; }
.about-modal-credit { margin-top: 16px; font-size: 13px; color: var(--flb-text-secondary); }
.about-modal-credit a { color: var(--flb-red); font-weight: 600; }
/* Narrower than the info column it sits in -- a compact warning box
   rather than one stretched edge to edge. The "Provided as is. Expect
   bugs." sentence is forced onto its own line with a literal <br> in
   the markup (app.js), not just wide-enough-to-wrap-there text flow,
   since a width-dependent wrap can't guarantee it "always" lands on
   line 2 the way an explicit break can. */
.about-modal-disclaimer { margin: 16px 0 0; max-width: 300px; }
.about-modal-disclaimer strong { text-decoration: underline; text-underline-offset: 2px; }
/* "Warning" needs to stand out beyond the existing <strong> emphasis
   already used for "as is"/"bugs" mid-sentence -- uppercase + letter-
   spacing on top of the inherited bold gives it a distinct label look
   instead of reading as just another bolded word in the sentence. */
.about-modal-disclaimer-label { text-transform: uppercase; letter-spacing: .03em; text-decoration: none; }
/* Wider than the default .modal max-width (640px) specifically for the
   About dialog -- at 640px the info column next to the 300px logo is
   too narrow for the credit line ("Hacked together by tvangramberen
   and (robot emoji)") to fit on one line. Scoped to just this modal via
   :has() rather than raising every modal's width, since nothing else
   needs the extra room. Smaller than an earlier version of this rule
   (760px) -- now that the disclaimer box above has its own fixed,
   narrower max-width instead of stretching to fill the column, the
   whole dialog no longer needs to be that wide. Still capped by the
   viewport on small screens, same as the default. */
.modal:has(.about-modal) { max-width: min(700px, calc(100vw - 48px)); }
/* Below this, the logo/text side-by-side layout no longer has room to
   breathe -- stack the (shrunk) logo above the text instead, centered,
   so the dialog scales down cleanly instead of cramming a 300px logo
   and a text column into whatever's left of a phone-width viewport. */
@media (max-width: 560px) {
  .about-modal { flex-direction: column; text-align: center; gap: 16px; }
  .about-modal-logo { width: 160px; height: 160px; }
  .about-modal-info { width: 100%; }
  .about-modal-disclaimer { max-width: 100%; margin-left: auto; margin-right: auto; }
}
.brand-name--sidebar { color: #fff; }
.brand-sub--sidebar { color: rgba(255,255,255,.55); }
.sidebar-version {
  font-family: var(--font-mono); font-size: 10px; color: rgba(255,255,255,.3); letter-spacing: .02em;
  padding: 10px 12px 2px; text-align: center;
}
.nav { display: flex; flex-direction: column; gap: 2px; flex: 1; overflow-y: auto; }
#mainNav {
  min-height: 0; /* the flexbox fix: without this, a flex:1 child won't actually
    shrink below its content's natural size, so overflow-y:auto never kicks in
    and the whole sidebar grows past the viewport instead of scrolling here */
  scrollbar-width: thin;
  scrollbar-color: rgba(255,255,255,.22) transparent;
}
#mainNav::-webkit-scrollbar { width: 5px; }
#mainNav::-webkit-scrollbar-track { background: transparent; }
#mainNav::-webkit-scrollbar-thumb { background: rgba(255,255,255,.22); border-radius: 999px; }
#mainNav::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,.35); }
/* Same thin/rounded/transparent-track recipe as #mainNav above, but
   using theme-aware variables instead of a fixed white/rgba -- #mainNav
   always sits on the permanently-dark sidebar, while these sit on
   whatever the light/dark theme's own background happens to be. Applied
   to the main content column's own scrollbar and every horizontally-
   scrolling table wrapper, in place of each browser's chunky default. */
.content, .table-card {
  scrollbar-width: thin;
  scrollbar-color: var(--flb-border-strong) transparent;
}
.content::-webkit-scrollbar, .table-card::-webkit-scrollbar { width: 7px; height: 7px; }
.content::-webkit-scrollbar-track, .table-card::-webkit-scrollbar-track { background: transparent; }
.content::-webkit-scrollbar-thumb, .table-card::-webkit-scrollbar-thumb { background: var(--flb-border-strong); border-radius: 999px; }
.content::-webkit-scrollbar-thumb:hover, .table-card::-webkit-scrollbar-thumb:hover { background: var(--flb-slate-light); }
.nav-item {
  display: flex; align-items: center; gap: 11px; padding: 7px 12px; border-radius: var(--radius-sm);
  background: transparent; border: none; color: rgba(255,255,255,.68); font-size: 13.5px; font-weight: 500;
  cursor: pointer; text-align: left; position: relative;
}
.nav-item .ic { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 1.8; flex: 0 0 18px; }
.nav-item:hover { background: rgba(255,255,255,.06); color: #fff; }
.nav-item.active { background: rgba(218,26,50,.16); color: #fff; }
.nav-item.active::before {
  content: ""; position: absolute; left: -14px; top: 8px; bottom: 8px; width: 3px;
  background: var(--flb-red); border-radius: 0 3px 3px 0;
}
.nav-divider { height: 1px; background: rgba(255,255,255,.08); margin: 5px 6px; flex: 0 0 auto; }
.nav-group-label {
  font-size: 10.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: rgba(255,255,255,.35); padding: 5px 12px 3px; margin-top: 8px;
  border-top: 1px solid rgba(255,255,255,.08);
}
.nav-item--soon { opacity: .45; cursor: default; }
.nav-item--soon:hover { background: transparent; color: rgba(255,255,255,.68); }
.nav-item--soon:disabled { opacity: .45; }
.soon-badge {
  margin-left: auto; flex: 0 0 auto; font-size: 9px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  background: rgba(255,255,255,.12); color: rgba(255,255,255,.6); padding: 2px 6px; border-radius: 999px;
}
.user-avatar {
  width: 32px; height: 32px; border-radius: 8px; background: var(--flb-red); color: #fff;
  display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 14px; font-family: var(--font-display);
}
.user-name { font-size: 13px; font-weight: 600; color: #fff; }
.user-role { font-size: 11px; color: rgba(255,255,255,.55); }

.main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.topbar {
  height: 62px; flex: 0 0 62px; background: #fff; border-bottom: 1px solid var(--flb-border);
  display: flex; align-items: center; justify-content: space-between; padding: 0 26px; position: relative;
}
.topbar-trace {
  position: absolute; right: 0; top: 0; bottom: 0; width: 320px; opacity: .5; pointer-events: none;
  background-image: radial-gradient(circle, var(--flb-red-tint) 1.5px, transparent 1.5px);
  background-size: 14px 14px;
  mask-image: linear-gradient(to left, #000, transparent);
}
.topbar-title { font-family: var(--font-display); font-weight: 600; font-size: 18px; position: relative; }
.topbar-status { font-size: 12px; color: var(--flb-slate); position: relative; display: flex; align-items: center; gap: 8px; }
.topbar-actions { display: flex; align-items: center; gap: 6px; position: relative; }
.topbar-icon-btn {
  width: 34px; height: 34px; padding: 0; border-radius: var(--radius-sm); background: transparent; border: none;
  color: var(--flb-slate); cursor: pointer; display: flex; align-items: center; justify-content: center;
}
.topbar-icon-btn:hover { background: var(--flb-bg); color: var(--flb-red); }
.topbar-icon-btn svg { width: 19px; height: 19px; stroke: currentColor; fill: none; stroke-width: 1.8; }
.topbar-tenant-btn { width: auto; padding: 0 10px; gap: 7px; font-size: 13px; font-weight: 600; color: var(--flb-text-secondary); }
.topbar-divider { width: 1px; height: 22px; background: var(--flb-border); margin: 0 4px; }
.topbar-user-menu { position: relative; }
.topbar-dropdown {
  display: none; position: absolute; top: calc(100% + 8px); right: 0; min-width: 210px; z-index: 40;
  background: var(--flb-card); border: 1px solid var(--flb-border); border-radius: var(--radius-md); box-shadow: var(--shadow-card);
  padding: 6px; flex-direction: column;
}
.topbar-dropdown.open { display: flex; }
.topbar-dropdown-head { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding: 8px 8px 10px; margin-bottom: 4px; border-bottom: 1px solid var(--flb-border); }
.topbar-dropdown-head .user-name { color: var(--flb-text-primary); }
.topbar-dropdown-head .user-role { color: var(--flb-slate); }
/* flex-basis: 100% forces this onto its own line below the avatar/name
   row (rather than a third column beside them), and because that new
   line starts fresh at the flex container's own left edge -- the same
   edge .user-avatar itself starts at -- the email lines up flush left
   with the avatar, not indented under the name/role text next to it. */
.topbar-dropdown-email { flex-basis: 100%; font-size: 11.5px; color: var(--flb-slate); word-break: break-all; }
.topbar-dropdown-item {
  display: block; padding: 8px 8px; border-radius: var(--radius-sm); color: var(--flb-text-secondary); font-size: 13px;
  text-decoration: none; cursor: pointer;
}
.topbar-dropdown-item:hover { background: var(--flb-bg); color: var(--flb-red); }
/* A plain info line in the user dropdown (session timeout remaining) --
   same padding/size as .topbar-dropdown-item, but not a link: no
   pointer cursor, no hover highlight, since there's nothing to click. */
.topbar-dropdown-info { padding: 8px 8px; font-size: 12.5px; color: var(--flb-slate); }
.content { flex: 1; overflow-y: auto; padding: 26px 30px 60px; }
.tab-panel { display: none; }
.tab-panel.active { display: block; animation: fadein .18s ease; }
@keyframes fadein { from { opacity: 0; transform: translateY(4px);} to { opacity: 1; transform: translateY(0);} }

/* ---------- Building blocks ---------- */
.panel-head { display: flex; align-items: flex-end; justify-content: space-between; margin-bottom: 18px; gap: 16px; flex-wrap: wrap; }
.panel-head h2 { margin: 0 0 4px; font-size: 20px; }
.panel-head p { margin: 0; color: var(--flb-slate); font-size: 13px; }
.actions-row { display: flex; gap: 10px; flex-wrap: wrap; }
/* A small, left-aligned dropdown for an actions-row button (Scheduler's
   "Show holidays") -- deliberately not .topbar-dropdown/wireDropdown,
   which are built for the topbar's own once-ever, right-aligned menus;
   this one opens below-left of its own button instead, and is wired
   directly in renderScheduler (see the comment above that function)
   since it's rebuilt fresh on every render rather than created once. */
.action-dropdown { position: relative; display: inline-block; }
.action-dropdown-menu {
  display: none; position: absolute; top: calc(100% + 8px); left: 0; min-width: 170px; z-index: 40;
  background: var(--flb-card); border: 1px solid var(--flb-border); border-radius: var(--radius-md); box-shadow: var(--shadow-card);
  padding: 6px; flex-direction: column;
}
.action-dropdown-menu.open { display: flex; }
.action-dropdown-item {
  display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%;
  padding: 8px 8px; border-radius: var(--radius-sm); color: var(--flb-text-secondary); font-size: 13px;
  background: none; border: none; text-align: left; font-family: inherit; cursor: pointer;
}
.action-dropdown-item:hover { background: var(--flb-bg); color: var(--flb-red); }
.action-dropdown-item.active { color: var(--flb-red); font-weight: 600; }
.action-dropdown-item svg { width: 15px; height: 15px; flex: none; }
/* Logs' own scope description -- deliberately full-width, underneath
   panel-head's title/actions-row rather than squeezed inside it as a
   flex sibling, so it always ends up below the scope toggle and its
   buttons no matter how narrow the window gets, instead of the two
   competing for the same row's space. */
.panel-sub-note { margin: -10px 0 18px; color: var(--flb-slate); font-size: 13px; }

.card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; margin-bottom: 22px; }
.stat-card {
  background: var(--flb-card); border: 1px solid var(--flb-border); border-radius: var(--radius-md);
  padding: 16px 18px; box-shadow: var(--shadow-card); position: relative; overflow: hidden;
}
.stat-card .stat-label { font-size: 11.5px; text-transform: uppercase; letter-spacing: .05em; color: var(--flb-slate); font-weight: 600; }
.stat-card .stat-value { font-family: var(--font-display); font-size: 28px; font-weight: 700; margin-top: 6px; }
.stat-card .stat-sub { font-size: 12px; color: var(--flb-slate-light); margin-top: 4px; }
.stat-card::after {
  content: ""; position: absolute; right: -10px; bottom: -10px; width: 70px; height: 70px; border-radius: 50%;
  background: var(--flb-red-tint); opacity: .5;
}

.chart-card {
  background: var(--flb-card); border: 1px solid var(--flb-border); border-radius: var(--radius-md);
  padding: 18px 20px; box-shadow: var(--shadow-card); margin-bottom: 22px;
}
.chart-card h3 { margin: 0 0 14px; font-size: 14.5px; }
.chart-canvas-wrap { position: relative; height: 230px; }
/* Horizontal mini bar charts (Scheduler) only need room for two thin
   category rows, not the taller box a vertical bar chart needed. */
.mini-chart-wrap { position: relative; height: 48px; margin-top: 10px; }
/* Scheduler's widget row: always exactly 3 equal columns (2 rows of 3
   for its 6 cards), rather than .card-grid's usual auto-fit -- and
   every card in it shares one fixed height regardless of which of the
   6 happen to have a chart, a sub-line, or a conditional button. */
.card-grid--sched3 { grid-template-columns: repeat(3, 1fr); }
.stat-card--sched { min-height: 152px; display: flex; flex-direction: column; }

.clickable-card { cursor: pointer; transition: box-shadow .15s ease, border-color .15s ease; }
.clickable-card:hover { border-color: var(--flb-red); box-shadow: 0 4px 14px rgba(0,0,0,.08); }

.row-clickable { cursor: pointer; }

/* One line per disk on Instance Settings' GCP settings "Storage" row. */
.disk-row { margin: 4px 0; }
.disk-row:first-child { margin-top: 0; }
.disk-row:last-child { margin-bottom: 0; }

/* Small green checkmark next to a FS settings row's title (Registration
   status, Remote License Server, FortiFlex API) -- shown only once
   that row's own value confirms it's actually configured/active, so
   it's a signal to notice, not decoration on every row regardless. */
.row-status-ok { display: inline-flex; vertical-align: middle; margin-left: 6px; color: var(--flb-green); }
.row-status-ok svg { width: 14px; height: 14px; }
/* Same idea as .row-status-ok above, for the FS License Server deploy
   progress list's own done/failed step icons (fsLicStepIconHtml) --
   ICONS.check/ICONS.close are bare SVGs with no size of their own. */
.deploy-step-icon { display: inline-flex; }
.deploy-step-icon svg { width: 15px; height: 15px; }

/* Registration status row's Mode/Token/Account lines -- a plain
   2-column grid (not separate inline spans) is what actually lines up
   every value at the same horizontal position regardless of its own
   label's length ("Token:" vs "Account:"), rather than each line
   drifting based on its own label width. */
.reg-status-grid { display: grid; grid-template-columns: auto 1fr; column-gap: 8px; row-gap: 2px; }

/* FortiFlex configurations row: one .flex-config-group per productType,
   a small toggle switch (checkbox, visually restyled as an iOS-style
   slider -- red rather than green when on, matching this app's own
   accent color instead of the "success" green used for status dots
   elsewhere) showing/hiding a list of per-configuration radio buttons
   underneath it, indented to where the toggle's own label text starts
   (26px switch + 8px gap = 34px), not the row's left edge. Groups and
   their records are sorted A-Z in app.js before this ever renders. */
.flex-config-group { margin-bottom: 10px; }
.flex-config-group:last-child { margin-bottom: 0; }
.flex-toggle-row { display: flex; align-items: center; gap: 8px; cursor: pointer; }
.flex-toggle-switch { position: relative; display: inline-block; width: 26px; height: 14px; flex: 0 0 auto; }
.flex-toggle-switch input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.flex-toggle-switch .slider { position: absolute; inset: 0; background: var(--flb-border-strong); border-radius: 999px; transition: background .15s ease; }
.flex-toggle-switch .slider::before { content: ""; position: absolute; width: 10px; height: 10px; left: 2px; top: 2px; background: #fff; border-radius: 50%; transition: transform .15s ease; }
.flex-toggle-switch input:checked + .slider { background: var(--flb-red); }
.flex-toggle-switch input:checked + .slider::before { transform: translateX(12px); }
.flex-radio-list { margin-left: 34px; margin-top: 4px; display: flex; flex-direction: column; gap: 4px; }
.flex-radio-list label { display: flex; align-items: center; gap: 6px; font-weight: 400; cursor: pointer; }

/* ------------------------------- Documentation page ----------------------- */
.docs-toc { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 22px; }
.docs-toc a { font-size: 12.5px; padding: 6px 12px; border-radius: 999px; background: var(--flb-gray-tint); color: var(--flb-text-primary); text-decoration: none; font-weight: 600; }
.docs-toc a:hover { background: var(--flb-red-tint); color: var(--flb-red); }
.docs-section { background: var(--flb-card); border: 1px solid var(--flb-border); border-radius: var(--radius-md); box-shadow: var(--shadow-card); padding: 22px 26px; margin-bottom: 16px; scroll-margin-top: 20px; }
.docs-section h3 { font-family: var(--font-display); font-size: 16px; margin: 0 0 4px; color: var(--flb-text-primary); display: flex; align-items: center; gap: 8px; }
.docs-badge { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; background: var(--flb-gray-tint); color: var(--flb-slate); padding: 2px 8px; border-radius: 999px; }
.docs-badge--admin { background: var(--flb-red-tint); color: var(--flb-red); }
.docs-badge--root { background: var(--flb-blue-tint); color: var(--flb-blue); }
.docs-section p { font-size: 13.5px; line-height: 1.65; color: var(--flb-text-secondary); margin: 8px 0; }
.docs-section ul { margin: 8px 0; padding-left: 20px; font-size: 13.5px; line-height: 1.75; color: var(--flb-text-secondary); }
.docs-section li { margin-bottom: 4px; }
.docs-section code { font-size: 12.5px; }
.docs-back-to-top-row { text-align: right; margin-top: 14px; }
.docs-back-to-top {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 5px 10px; border-radius: var(--radius-sm); border: 1px solid var(--flb-border);
  background: transparent; color: var(--flb-slate); font-size: 12px; font-weight: 600; cursor: pointer;
}
.docs-back-to-top svg { width: 13px; height: 13px; stroke: currentColor; fill: none; stroke-width: 2; }
.docs-back-to-top:hover { border-color: var(--flb-red); color: var(--flb-red); }

/* ------------------------------- Scheduler calendar ---------------------- */
.cal-wrap { background: var(--flb-card); border: 1px solid var(--flb-border); border-radius: var(--radius-md); box-shadow: var(--shadow-card); overflow: hidden; }
.cal-header { display: flex; align-items: center; justify-content: space-between; padding: 14px 18px; border-bottom: 1px solid var(--flb-border); flex-wrap: wrap; gap: 10px; }
.cal-title { font-family: var(--font-display); font-weight: 700; font-size: 16px; min-width: 160px; }
.cal-nav { display: flex; gap: 6px; align-items: center; }
.cal-legend { display: flex; gap: 14px; flex-wrap: wrap; font-size: 11.5px; color: var(--flb-slate); align-items: center; }
.cal-legend-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 5px; vertical-align: middle; }
/* A round 8px dot works fine for the Past-running/Needs-a-look legend
   entries (--flb-red/--flb-amber -- both fully saturated, so they stay
   visible even that small), but --cal-holiday-bg is a deliberately pale
   tint (so a whole calendar cell filled with it doesn't overpower the
   day number/events inside it) -- at legend-dot size that same pale
   color reads as barely-there/white rather than as its own visible
   swatch. This is the same var(--cal-holiday-bg) as the day cells
   themselves (so it's genuinely the same color, not a different one),
   just shown in a bigger, rectangular swatch closer to a mini calendar
   cell's own shape so the tint actually registers. */
.cal-legend-swatch { display: inline-block; width: 16px; height: 11px; border-radius: 3px; margin-right: 5px; vertical-align: middle; border: 1px solid var(--flb-border-strong); }
.cal-grid-header { display: grid; grid-template-columns: repeat(7, 1fr); border-bottom: 1px solid var(--flb-border); }
.cal-grid-header > div { padding: 8px; text-align: center; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--flb-slate); }
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); }
.cal-cell { min-height: 108px; border-right: 1px solid var(--flb-border); border-bottom: 1px solid var(--flb-border); padding: 6px; display: flex; flex-direction: column; gap: 3px; }
.cal-cell:nth-child(7n) { border-right: none; }
.cal-cell--out { background: #FAFBFC; }
.cal-cell--out .cal-daynum { color: var(--flb-slate-light); }
.cal-cell--today { background: var(--flb-red-tint); }
/* Public holidays (Scheduler's "Show holidays" menu, default None) --
   never set on an out-of-month cell in the first place (holidays are
   only looked up for in-month ISO dates), but still excluded from
   "today" explicitly so today's own red tint always wins if the two
   ever land on the same day. */
.cal-cell--holiday:not(.cal-cell--today) { background: var(--cal-holiday-bg); }
/* Saturday/Sunday columns (grid is 7-wide, Mon first -- position 6 and 7
   of every row of 7) get their own subtle tint so weekends stand out at
   a glance -- but only for days that actually belong to the viewed month
   and aren't already the "today" or a holiday cell, so it never fights
   with any of those three's own, more important, backgrounds. */
.cal-cell:not(.cal-cell--out):not(.cal-cell--today):not(.cal-cell--holiday):nth-child(7n+6),
.cal-cell:not(.cal-cell--out):not(.cal-cell--today):not(.cal-cell--holiday):nth-child(7n) { background: var(--cal-weekend-bg); }
.cal-daynum { font-size: 12px; font-weight: 700; color: var(--flb-text-primary); margin-bottom: 2px; }
/* Plain, non-interactive label -- never a <button>, never wired to a
   click handler (see renderScheduler) -- so it can't be mistaken for a
   real, clickable/deletable event pill like .cal-event below. */
.cal-holiday-label {
  font-size: 10.5px; font-weight: 600; font-style: italic; color: var(--flb-slate);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: default;
}
.cal-holiday-label svg { width: 10px; height: 10px; vertical-align: -1px; margin-right: 1px; }
.cal-events { display: flex; flex-direction: column; gap: 3px; overflow-y: auto; max-height: 82px; }
.cal-event {
  font-size: 10.5px; line-height: 1.3; padding: 3px 6px; border-radius: 4px; cursor: pointer; border: none; text-align: left;
  background: var(--flb-gray-tint); color: var(--flb-text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-family: inherit;
}
.cal-event:hover { filter: brightness(0.96); }
.cal-event--future_missing, .cal-event--future_mismatch { background: var(--flb-amber-tint); color: #8a5a0a; border-left: 3px solid var(--flb-amber); padding-left: 5px; font-weight: 600; }
.cal-event--past_running { background: var(--flb-red-tint); color: var(--flb-red); border-left: 3px solid var(--flb-red); padding-left: 5px; font-weight: 600; }
.cal-event-user { opacity: .7; font-weight: 600; }
.event-detail-flag { padding: 10px 12px; border-radius: var(--radius-sm); font-size: 12.5px; margin-bottom: 14px; }
.event-detail-flag--past_running { background: var(--flb-red-tint); color: var(--flb-red); border-left: 4px solid var(--flb-red); }
.event-detail-flag--future_missing, .event-detail-flag--future_mismatch { background: var(--flb-amber-tint); color: #8a5a0a; border-left: 4px solid var(--flb-amber); }
.event-readonly-row { display: flex; justify-content: space-between; padding: 7px 0; border-bottom: 1px solid var(--flb-border); font-size: 13px; }
.event-readonly-row span:first-child { color: var(--flb-slate); }

.chart-row { display: grid; grid-template-columns: 1.4fr 1fr; gap: 18px; }
@media (max-width: 980px) { .chart-row { grid-template-columns: 1fr; } }

/* Instance Settings' GCP settings / FS settings tables -- stacked full
   width (FS on top, GCP below), not side by side. Two narrow columns
   used to force a horizontal scrollbar on nearly every row (each
   table's min-width: max-content -- see the plain `table` rule below
   -- fought for room in half the content width); full width plus
   .instance-settings-table's fixed column widths (further down) fixes
   both the cramped width and the two tables' fields lining up. */
.settings-table-row { display: flex; flex-direction: column; gap: 18px; }

.table-card { background: var(--flb-card); border: 1px solid var(--flb-border); border-radius: var(--radius-md); box-shadow: var(--shadow-card); overflow-x: auto; overflow-y: hidden; }
table { width: 100%; min-width: max-content; border-collapse: collapse; font-size: 13px; }
/* Overrides the plain `table` rule's min-width: max-content above --
   that's what let a single wide row (a long DNS zone list, the Guest
   user / FortiFlex fields) force the *whole table* wider, well past
   its .table-card container, triggering a horizontal scrollbar even
   though there was plenty of room to just wrap the content instead.
   table-layout: fixed plus explicit Field/Actions widths (identical
   across the GCP settings and FS settings tables, so their columns
   line up when stacked) makes the Value column wrap within whatever
   width is left, instead of dictating the table's own width. */
.instance-settings-table { table-layout: fixed; width: 100%; min-width: 0; }
.instance-settings-table th:nth-child(1), .instance-settings-table td:nth-child(1) { width: 235px; }
/* Wide enough for the worst case: two icon buttons (Edit + Sync, ~62px)
   plus a Changed badge (~74px) plus the gaps between them -- every FS
   settings row's Changed badge lives here now, rightmost in
   .row-actions, not in the Value column. */
.instance-settings-table th:nth-child(3), .instance-settings-table td:nth-child(3) { width: 150px; }

/* Logs > API debug table -- same table-layout: fixed technique as
   .instance-settings-table above, so Date/Time, Method, and the Show
   content button all get a fixed width and the Remote call column
   takes whatever's left, rather than the table's own width growing to
   fit a long URL and forcing .table-card's shared horizontal scrollbar
   to appear (which this table deliberately never shows -- see
   .table-card-no-hscroll below). */
.api-debug-table { table-layout: fixed; width: 100%; min-width: 0; }
/* Columns are Date/time, Source, App User, Method, Remote call,
   view-icon (6 total, in that order) -- only Remote call is left
   unsized on purpose, so table-layout:fixed hands it whatever's left
   over after the other five, rather than every column splitting the
   row evenly. */
.api-debug-table th:nth-child(1), .api-debug-table td:nth-child(1) { width: 150px; }
.api-debug-table th:nth-child(2), .api-debug-table td:nth-child(2) { width: 90px; }
.api-debug-table th:nth-child(3), .api-debug-table td:nth-child(3) { width: 110px; }
.api-debug-table th:nth-child(4), .api-debug-table td:nth-child(4) { width: 70px; }
/* 44px wasn't actually enough room here: tbody td's own 16px/side
   padding (border-box) left only ~12px of content box for a ~30px
   .icon-btn (padding + border + a 16px svg) to sit in, so it overflowed
   the cell -- with nothing to its right to contain that overflow (this
   is the last column), it visually poked out past the table's own
   right edge instead of stopping at a comfortable distance from it.
   56px gives the button room to sit inside its own padding without
   overflowing at all. */
.api-debug-table th:nth-child(6), .api-debug-table td:nth-child(6) { width: 56px; text-align: right; }
.table-card-no-hscroll { overflow-x: hidden; }
/* The Remote call cell itself: clipped to a single line with an
   ellipsis (never wraps, never scrolls) -- hovering it shows the full
   value in a separate fixed-position tooltip (#remoteCallHoverTip,
   wired in app.js) instead of expanding the cell in place, since this
   table's own .table-card container clips overflow on both axes the
   moment either one isn't "visible" (that's what stops the shared
   horizontal scrollbar from appearing at all here), which would also
   clip an in-place popover before it ever got wide enough to be useful
   for a long URL. */
.remote-call-cell { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: default; }
.remote-call-hover-tip {
  position: fixed; display: none; z-index: 600; background: var(--flb-card);
  border: 1px solid var(--flb-border); border-radius: 8px; box-shadow: var(--shadow-pop);
  padding: 8px 12px; font-family: var(--font-mono); font-size: 12px;
  white-space: pre-wrap; word-break: break-all; max-width: calc(100vw - 40px); pointer-events: none;
}
thead th {
  text-align: left; padding: 11px 16px; background: #FAFBFC; color: var(--flb-slate); font-size: 11px;
  text-transform: uppercase; letter-spacing: .04em; font-weight: 700; border-bottom: 1px solid var(--flb-border);
}
th.sortable-th { cursor: pointer; user-select: none; white-space: nowrap; }
th.sortable-th:hover { color: var(--flb-red); }
th.sortable-th::after { content: ""; display: inline-block; width: 0; height: 0; margin-left: 5px; vertical-align: middle; border-left: 3px solid transparent; border-right: 3px solid transparent; opacity: .35; }
th.sortable-th.sort-asc::after { border-bottom: 4px solid currentColor; opacity: 1; }
th.sortable-th.sort-desc::after { border-top: 4px solid currentColor; opacity: 1; }
.table-toolbar { display: flex; justify-content: flex-end; margin-bottom: 10px; }
.table-filter-input {
  font: inherit; font-size: 13px; padding: 8px 12px 8px 32px; border-radius: var(--radius-sm);
  border: 1px solid var(--flb-border-strong); background: #fff url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="%2394A3AF" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>') no-repeat 9px center;
  background-size: 14px; width: 220px; max-width: 100%;
}
.table-filter-input:focus { border-color: var(--flb-red); }
tbody td { padding: 12px 16px; border-bottom: 1px solid var(--flb-border); vertical-align: middle; }
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover { background: var(--flb-gray-tint); }
.cell-name { font-family: var(--font-mono); font-size: 12.5px; color: var(--flb-text-primary); }
.cell-muted { color: var(--flb-slate-light); }
.cell-stack { display: flex; flex-direction: column; gap: 2px; line-height: 1.3; }
.cell-stack .cell-stack-sub { font-size: 11px; color: var(--flb-slate-light); }
.ip-copy-row { display: inline-flex; align-items: center; gap: 4px; }
.ip-copy-row .icon-btn { width: 20px; height: 20px; padding: 0; }
.ip-copy-row .icon-btn svg { width: 12px; height: 12px; }
/* align-items: center -- without it, flex's default (stretch) makes
   any shorter sibling (the fs-changed-badge tag-pill, ~20px tall) grow
   to match a taller one (.icon-btn, ~30px with its padding/border/svg)
   whenever both share a row, so the badge looked one height in rows
   with an icon button and another, smaller height in rows without one
   (e.g. Guest user). Centering each item at its own natural height
   keeps the badge the same (smaller) size everywhere. */
.row-actions { display: flex; align-items: center; gap: 6px; justify-content: flex-end; }
.empty-state { padding: 40px 20px; text-align: center; color: var(--flb-slate-light); font-size: 13.5px; }
.empty-state svg { width: 34px; height: 34px; stroke: var(--flb-border-strong); fill: none; stroke-width: 1.6; margin-bottom: 10px; }

/* status dots -- signature motif */
.status { display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px; font-weight: 600; }
.dot { width: 8px; height: 8px; border-radius: 50%; position: relative; flex: 0 0 8px; }
.dot.running { background: var(--flb-green); }
.dot.running::after { content:""; position:absolute; inset:-4px; border-radius:50%; border:1.5px solid var(--flb-green); animation: pulse 1.8s ease-out infinite; }
.dot.stopped { background: var(--flb-amber); }
.dot.pending { background: var(--flb-slate-light); }
.dot.pending::after { content:""; position:absolute; inset:-4px; border-radius:50%; border:1.5px solid var(--flb-slate-light); animation: pulse 1.8s ease-out infinite; }
.dot.unknown { background: var(--flb-gray); }
@keyframes pulse { 0% { opacity:.7; transform:scale(.6);} 100% { opacity:0; transform:scale(1.6);} }

.badge { display: inline-flex; align-items: center; padding: 3px 9px; border-radius: 999px; font-size: 11px; font-weight: 700; letter-spacing: .01em; }
.badge-admin { background: var(--flb-red-tint); color: var(--flb-red-dark); }
.badge-user { background: var(--flb-gray-tint); color: var(--flb-slate); }
.badge-role { background: #EAF2FA; color: #1B4F7A; }

.tag-pill { display: inline-flex; align-items: center; gap: 4px; background: var(--flb-gray-tint); color: var(--flb-text-secondary); border-radius: 6px; padding: 3px 8px; font-size: 11.5px; font-family: var(--font-mono); }
/* Toggled via visibility (in app.js), not display -- an appearing/
   disappearing Changed badge must not change the row's (and so the
   whole table's) width. visibility: hidden still reserves this box's
   layout space, unlike display: none; min-width/justify-content make
   that reserved space constant regardless of which row's badge is
   currently showing "Changed" and which one isn't. */
/* Lives in .row-actions now, rightmost after any icon buttons in the
   same row -- toggled via display (in app.js), not visibility, so an
   appearing badge pushes those icons left within the Actions column's
   own fixed width, instead of always reserving its space regardless of
   whether it's actually showing. */
.fs-changed-badge { background: var(--flb-amber-tint); color: #8a5a0a; margin-left: 8px; min-width: 74px; justify-content: center; flex: 0 0 auto; }

/* ---------- Forms ---------- */
.form-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px 16px; }
.form-grid.single { grid-template-columns: 1fr; }
.form-grid .span-2 { grid-column: 1 / -1; }
.settings-columns { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 0 30px; }
.settings-col-title { font-size: 12.5px; font-weight: 700; color: var(--flb-text-secondary); margin: 0 0 14px; padding-bottom: 8px; border-bottom: 1px solid var(--flb-border); }
.field input[type="range"] { width: 100%; accent-color: var(--flb-red); padding: 0; background: none; border: none; height: 20px; }
.seg-toggle { display: inline-flex; border: 1px solid var(--flb-border); border-radius: var(--radius-sm); overflow: hidden; }
.seg-toggle-btn { padding: 7px 12px; font-size: 12.5px; font-weight: 600; background: var(--flb-card); color: var(--flb-slate); border: none; border-right: 1px solid var(--flb-border); cursor: pointer; }
.seg-toggle-btn:last-child { border-right: none; }
.seg-toggle-btn.active { background: var(--flb-ink); color: #fff; }
.link-btn { background: none; border: none; padding: 0; font: inherit; color: inherit; cursor: pointer; text-decoration: underline dotted; text-underline-offset: 3px; }
.link-btn:hover { color: var(--flb-red); }
.theme-toggle { display: inline-flex; border: 1px solid var(--flb-border); border-radius: var(--radius-sm); overflow: hidden; }
.theme-toggle-btn { width: 30px; height: 30px; padding: 0; background: transparent; border: none; color: var(--flb-slate); cursor: pointer; display: flex; align-items: center; justify-content: center; }
.theme-toggle-btn + .theme-toggle-btn { border-left: 1px solid var(--flb-border); }
.theme-toggle-btn svg { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 1.8; }
.theme-toggle-btn.active { background: var(--flb-ink); color: #fff; }
.theme-toggle-btn:hover:not(.active) { background: var(--flb-bg); color: var(--flb-red); }
.hint { font-size: 11.5px; color: var(--flb-slate-light); font-weight: 400; margin-top: 2px; }

/* ---------- Modal ---------- */
.modal-backdrop {
  display: none; position: fixed; inset: 0; background: rgba(15,35,55,.5); backdrop-filter: blur(2px);
  align-items: center; justify-content: center; z-index: 60; padding: 20px;
}
.modal-backdrop.open { display: flex; }
.modal {
  position: relative;
  background: #fff; border-radius: var(--radius-lg); padding: 26px 28px;
  width: 100%; max-width: min(640px, calc(100vw - 48px));
  max-height: 88vh; overflow-y: auto; overflow-x: hidden; box-shadow: var(--shadow-pop);
}
.modal h3 { margin: 0 0 4px; font-size: 18px; }
.modal .modal-sub { color: var(--flb-slate); font-size: 12.5px; margin: 0 0 18px; }
.modal-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 22px; }
.modal-close-x {
  position: absolute; top: 10px; right: 10px; width: 28px; height: 28px;
  display: flex; align-items: center; justify-content: center;
  border: none; background: transparent; cursor: pointer;
  font-size: 22px; line-height: 1; color: var(--flb-slate); border-radius: 50%;
}
.modal-close-x:hover { background: var(--flb-bg); color: var(--flb-red); }
/* Same scrollbar treatment as .table-card/.content -- .modal already
   scrolls (max-height: 88vh, overflow-y: auto above) once its content
   is taller than that, this just makes the browser's own default
   scrollbar match the rest of the app instead of standing out. Applies
   to every modal, not just the API debug "Show content" overlay that
   asked for it, since there was no reason for this app's modals to be
   the one place still using an unstyled default. */
.modal::-webkit-scrollbar { width: 7px; }
.modal::-webkit-scrollbar-track { background: transparent; }
.modal::-webkit-scrollbar-thumb { background: var(--flb-border-strong); border-radius: 999px; }
.modal::-webkit-scrollbar-thumb:hover { background: var(--flb-slate-light); }

/* API debug "Show content" overlay -- 6 stacked rows (Date/Time, App
   User, Method + Remote call, Request content, Response content). */
.api-debug-detail-row { margin-bottom: 16px; }
.api-debug-detail-row:last-child { margin-bottom: 0; }
.api-debug-detail-label { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; font-weight: 700; color: var(--flb-slate); margin-bottom: 6px; }
.api-debug-detail-line { font-family: var(--font-mono); font-size: 12.5px; word-break: break-all; }
/* Date/Time and App User show their value right next to the title, on
   the same line, rather than stacked underneath it like every other
   row above -- .api-debug-detail-label's own margin-bottom is unwanted
   here, so it's zeroed out just for these two. */
.api-debug-detail-row-inline { display: flex; align-items: baseline; gap: 10px; }
.api-debug-detail-row-inline .api-debug-detail-label { margin-bottom: 0; flex: 0 0 auto; }
.app-user-admin-icon { display: inline-flex; vertical-align: middle; margin-left: 6px; color: var(--flb-blue); }
.app-user-admin-icon svg { width: 13px; height: 13px; }

/* User Sessions' "By user" view -- one bordered card per username, its
   own entries indented ("slide indent") underneath a header row that
   carries the admin/unknown marker icons and the reset-secret button. */
.user-session-group { border: 1px solid var(--flb-border); border-radius: 8px; margin-bottom: 10px; overflow: hidden; }
.user-session-group:last-child { margin-bottom: 0; }
.user-session-group-head { display: flex; align-items: center; gap: 8px; padding: 10px 14px; background: var(--flb-bg); }
.user-session-group-name { font-weight: 600; font-size: 13.5px; }
.user-session-group-count { margin-left: auto; font-size: 12px; }
.user-session-unknown-icon { display: inline-flex; vertical-align: middle; color: var(--flb-amber); }
.user-session-unknown-icon svg { width: 13px; height: 13px; }
.user-session-group-entries { padding: 4px 14px 4px 36px; }
.user-session-entry-row { display: flex; align-items: center; gap: 18px; padding: 7px 0; font-size: 12.5px; border-top: 1px solid var(--flb-border); }
.user-session-entry-row:first-child { border-top: none; }
.user-session-entry-row > span:nth-child(1) { flex: 0 0 150px; }
.user-session-entry-row > span:nth-child(2) { flex: 0 0 130px; }
.user-session-entry-row > span:nth-child(3) { flex: 1 1 auto; }
.api-debug-detail-box {
  font-family: var(--font-mono); font-size: 12px; white-space: pre-wrap; word-break: break-word;
  background: var(--flb-bg); border: 1px solid var(--flb-border); border-radius: 6px; padding: 10px 12px;
}
/* JSON syntax highlighting inside .api-debug-detail-box (see
   highlightJson in app.js) -- reuses existing theme tokens rather than
   introducing JSON-specific colors, so it stays consistent with the
   rest of the app's palette in both light and dark mode. */
.json-key { color: var(--flb-blue); font-weight: 600; }
.json-string { color: var(--flb-green); }
.json-number { color: var(--flb-amber); }
.json-boolean { color: var(--flb-aqua); font-weight: 600; }
.json-null { color: var(--flb-slate-light); font-style: italic; }

/* ---------- Toasts ---------- */
.toast-stack { position: fixed; right: 22px; bottom: 22px; display: flex; flex-direction: column; gap: 10px; z-index: 80; }
.toast {
  background: var(--flb-ink); color: #fff; padding: 12px 16px; border-radius: var(--radius-sm); font-size: 13px;
  box-shadow: var(--shadow-pop); display: flex; align-items: center; gap: 10px; min-width: 220px; animation: toastin .2s ease;
  border-left: 3px solid var(--flb-green);
}
.toast.error { border-left-color: var(--flb-red); }
@keyframes toastin { from { opacity:0; transform: translateY(8px);} to { opacity:1; transform: translateY(0);} }

/* ---------- Misc ---------- */
.section-note { background: var(--flb-amber-tint); border: 1px solid #f2ddb8; color: #7a5615; padding: 10px 14px; border-radius: var(--radius-sm); font-size: 12.5px; margin-bottom: 18px; }
.spinner { display: inline-block; width: 16px; height: 16px; border: 2px solid var(--flb-border-strong); border-top-color: var(--flb-red); border-radius: 50%; animation: spin .7s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
.progress-track { height: 6px; background: var(--flb-gray-tint); border-radius: 4px; overflow: hidden; }
.progress-fill { height: 100%; background: linear-gradient(90deg, var(--flb-red), var(--flb-red-dark)); border-radius: 4px; }

.admin-only { display: none; }
body.is-admin .admin-only { display: flex; }

@media (max-width: 860px) {
  .sidebar { position: fixed; z-index: 40; height: 100vh; transform: translateX(-100%); transition: transform .2s ease; }
  .sidebar.open { transform: translateX(0); }
  .form-grid { grid-template-columns: 1fr; }
}

/* Easter egg overlay (5 clicks on "GCP connected", see wireEasterEgg in
   app.js) -- deliberately its own fixed dark/green palette rather than
   this app's own theme tokens, since the whole point is to look like
   an old-school terminal, in either light or dark mode. */
#easterEggOverlay {
  position: fixed; inset: 0; z-index: 500; background: #000; color: #33ff33;
  font-family: var(--font-mono); font-size: 14px; line-height: 1.6;
  padding: 30px 34px; overflow-y: auto;
  scrollbar-width: thin; scrollbar-color: #33ff33 transparent;
}
#easterEggOverlay::-webkit-scrollbar { width: 10px; }
#easterEggOverlay::-webkit-scrollbar-track { background: transparent; }
#easterEggOverlay::-webkit-scrollbar-thumb { background: #33ff33; border-radius: 999px; }
#easterEggOverlay::-webkit-scrollbar-thumb:hover { background: #7fff7f; }
#easterEggScreen { white-space: pre-wrap; word-break: break-word; }
/* Every typed/scripted "line" is its own div (see newLine() in app.js)
   -- a completely empty one (used for the deliberate blank-line gaps
   in the script) has no inline content at all, so without this it
   never generates a line box and collapses to zero height instead of
   showing as a blank line. min-height (in em, so it tracks font-size)
   guarantees at least one line's worth of height regardless. */
#easterEggScreen > div { min-height: 1.6em; }
/* Fixed, not absolute -- #easterEggOverlay is itself the scrolling
   element (overflow-y: auto), so an absolutely positioned child scrolls
   away with the content instead of staying put. Anchored to the
   viewport instead keeps Close reachable no matter how far scrolled. */
#btnCloseEasterEgg {
  position: fixed; top: 16px; right: 20px; width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  background: transparent; border: 1px solid #1a5c1a; border-radius: 50%;
  color: #33ff33; font-size: 20px; line-height: 1; cursor: pointer;
  z-index: 501;
}
#btnCloseEasterEgg:hover { background: rgba(51,255,51,.15); color: #7fff7f; }
.ee-ok { color: #39ff14; font-weight: 700; }
.ee-fail { color: #1b5e20; font-weight: 700; }
.ee-quote { color: #39ff14; }
/* A fixed-size block, not a scaled-up "_" character -- the earlier
   version used font-size: 2em to make the glyph big, but an inline
   element's own line-height scales with its own font-size, so that
   2em span inflated the whole prompt line's height well past every
   other line's. The extra space landed mostly above the visible text
   (read as a phantom blank line before the prompt) and vanished the
   instant the cursor was removed on Enter, yanking everything below
   up by that same amount. A fixed em-sized block keeps the line's
   height identical to every other line, so nothing shifts, and
   vertical-align: text-bottom plus its own height (shorter than the
   line box) sits it low against the text instead of centered in it. */
.ee-cursor {
  display: inline-block; width: 0.55em; height: 1.1em; margin-left: 1px;
  background: #39ff14; vertical-align: text-bottom;
  animation: ee-blink 1s step-end infinite;
}
/* Applied in app.js, not baked into .ee-ok/.ee-fail directly -- the
   FAIL status blinks for a fixed 2s then stops (settling on a solid
   .ee-fail), while "All Moose go!" gets this same class but keeps it
   forever, so the two need to be independent toggles, not tied to
   whichever color class happens to be present. */
.ee-blink-anim { animation: ee-blink 1s step-end infinite; }
@keyframes ee-blink { 50% { opacity: 0; } }
