/* ==========================================================================
   KONSPEC — design foundation v3 (shadcn/ui)
   Built on shadcn's token vocabulary and component metrics: neutral OKLCH
   scale, one radius scale, hairline borders, xs shadows, 3px focus rings.
   One brand red survives, reserved for the keyline. Fonts vendored — no CDNs.
   ========================================================================== */

/* == vendored fonts (Inter + IBM Plex Mono, OFL) ==
   Inter is shadcn's reference sans; the variable file covers 100–900 in one
   request. Mono is static and stays IBM Plex (codes, secrets, snippets).
   Paths are relative to this sheet inside static/css/. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/inter-var.260c81a4759b.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/ibm-plex-mono-400.79936b18df9f.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/ibm-plex-mono-600.5a2ce7798732.woff2") format("woff2");
}

/* == design tokens ==
   shadcn/ui's light theme with its role-named pairs (`--x` surface,
   `--x-foreground` ink on it), tuned a few degrees cool so the neutrals carry
   a faint blue rather than reading dead grey. Two departures from stock
   shadcn, both deliberate:

   - `--background` is a hair off white (#f8f9fa) while `--card` stays pure
     white. The launchpad is a field of cards; if the page were also white the
     tiles would rely on their border alone to exist.
   - Controls that shadcn puts on `bg-background` (outline buttons, inputs) sit
     on `--card` here, for the same reason.

   Extended only where an IdP needs colours shadcn has no name for — the brand
   red and the info/success/warning message tones. */
:root {
  --background:           oklch(0.972 0.003 248);
  --foreground:           oklch(0.154 0.005 264);
  --card:                 oklch(1 0 0);
  --card-foreground:      oklch(0.154 0.005 264);
  --popover:              oklch(1 0 0);
  --popover-foreground:   oklch(0.154 0.005 264);
  --primary:              oklch(0.210 0.006 286);
  --primary-foreground:   oklch(0.985 0 0);
  --secondary:            oklch(0.961 0.003 265);
  --secondary-foreground: oklch(0.210 0.006 286);
  --muted:                oklch(0.961 0.003 265);
  --muted-foreground:     oklch(0.565 0.015 262);
  --accent:               oklch(0.961 0.003 265);
  --accent-foreground:    oklch(0.210 0.006 286);
  --destructive:          oklch(0.577 0.245 27.325);
  --border:               oklch(0.916 0.007 269);
  --input:                oklch(0.916 0.007 269);
  --ring:                 oklch(0.705 0.020 266);

  /* shadcn writes this as `text-muted-foreground/70`; naming it keeps the
     second-tier greys (tile counts, placeholders) from drifting apart. */
  --muted-foreground-subtle: oklch(0.705 0.020 266);
  /* the lighter hairline: icon holders and section rules, which sit inside a
     card and would look drawn-on at full --border weight */
  --border-subtle:        oklch(0.937 0.006 275);
  /* the table header band. Not --muted: shadcn's muted is drawn to sit on a
     white card, and it lands 0.011 from --background — so a table placed
     directly on the page (sessions, the analytics figures) had a header the
     same tone as the canvas behind it, and stopped reading as a surface. */
  --table-head:           oklch(0.941 0.005 268);

  /* == extensions ==
     KONSPEC red. Not `--primary`: shadcn's primary is the neutral CTA ink, and
     a red button reads as destructive. This is brand furniture only. */
  --brand:                #d51c24;
  --brand-deep:           #a31218;

  /* message tones — shadcn ships only --destructive, so the rest match its
     shape: a foreground and the tinted surface it sits on. */
  --destructive-bg:       oklch(0.971 0.021 25);
  --info:                 oklch(0.47 0.075 240);
  --info-bg:              oklch(0.971 0.014 240);
  --success:              oklch(0.52 0.12 152);
  --success-bg:           oklch(0.971 0.03 152);
  --warning:              oklch(0.55 0.1 75);
  --warning-bg:           oklch(0.973 0.035 85);

  /* == shape ==
     shadcn's radius scale, set to its zero preset: square corners throughout.
     The four steps are kept (rather than deleting every border-radius) so the
     whole system still turns on one number — raise --radius and the derived
     steps come back in proportion. They are literals, not calc() off --radius,
     because calc(0px - 4px) is a negative radius. */
  --radius:               0;
  --radius-sm:            0;
  --radius-md:            0;
  --radius-lg:            0;
  --radius-xl:            0;

  /* == depth == Tailwind's shadow scale; shadcn leans on xs and sm. */
  --shadow-xs:            0 1px 2px 0 rgb(0 0 0 / 0.05);
  --shadow-sm:            0 1px 3px 0 rgb(0 0 0 / 0.1),
                          0 1px 2px -1px rgb(0 0 0 / 0.1);
  --shadow-md:            0 4px 6px -1px rgb(0 0 0 / 0.1),
                          0 2px 4px -2px rgb(0 0 0 / 0.1);
  --shadow-lg:            0 10px 15px -3px rgb(0 0 0 / 0.1),
                          0 4px 6px -4px rgb(0 0 0 / 0.1);

  /* == focus ==
     shadcn's ring is one recipe everywhere: 3px of --ring at 50%, paired with
     border-color: var(--ring) on the control itself. Kept as a token so no
     control can invent its own. */
  --ring-shadow:          0 0 0 3px color-mix(in oklch, var(--ring) 50%, transparent);
  --ring-shadow-destructive:
                          0 0 0 3px color-mix(in oklch, var(--destructive) 20%, transparent);

  /* type */
  --font-sans: "Inter", ui-sans-serif, system-ui, -apple-system,
               "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", "Cascadia Mono",
               Menlo, Consolas, monospace;
}

/* == reset == */
*,
*::before,
*::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

/* visually hidden but available to assistive tech */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* skip link: off-screen until focused, then pinned top-left over the page.
   Shaped as a default shadcn button. */
.skip-link {
  position: absolute;
  left: 8px;
  top: -56px;
  z-index: 100;
  height: 36px;
  display: inline-flex;
  align-items: center;
  padding: 0 16px;
  background: var(--primary);
  color: var(--primary-foreground);
  border-radius: var(--radius-md);
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
  transition: top .15s ease;
}
.skip-link:focus { top: 8px; box-shadow: var(--ring-shadow); }

/* any anchor-jumped element (skip-link target, toc deep-links into the prose
   guide headings) clears the sticky .topbar instead of hiding beneath it */
:target { scroll-margin-top: 76px; }

body {
  margin: 0;
  /* dvh, not vh: on mobile 100vh is the viewport with the URL bar *retracted*,
     so a 100vh box always overflows the visible area while the bar is showing
     and leaves the page scrollable by a bar's height even when it fits. */
  min-height: 100vh;
  min-height: 100dvh;
  font-family: var(--font-sans);
  color: var(--foreground);
  /* shadcn's base is text-sm/1.25rem — the whole component scale is drawn
     against 14px, so anything larger throws every height and gap off. */
  font-size: 14px;
  line-height: 1.4286;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  background: var(--background);
}

/* == layout shell (auth pages) == */
.shell {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 48px 20px 32px;
}

.stack {
  width: 100%;
  max-width: 400px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

/* == brand == */
.brand {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* the KONSPEC logo is a wide horizontal lockup (~3.4:1, trimmed to the ink) —
   size by height, width auto-scales. It sits above the auth card; compact
   inside the top bar. */
.brand__logo {
  display: block;
  height: 58px;
  width: auto;
  max-width: 100%;
}

.brand--bar { justify-content: flex-start; }
.brand--bar .brand__logo { height: 28px; }

/* logo doubles as the "home" link in the top bar */
.brand__home {
  display: inline-flex;
  align-items: center;
  border-radius: var(--radius-sm);
  outline: none;
}
.brand__home:focus-visible { box-shadow: var(--ring-shadow); }

/* == messages == */
/* shadcn Alert: rounded-lg, hairline border, px-4 py-3, a dot where the
   component puts its icon slot. The tint stays on the surface, the copy stays
   foreground — only the mark carries the tone. */
.messages {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.msg {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 12px 16px;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  background: var(--card);
  font-size: 14px;
  line-height: 1.4286;
}

.msg::before {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  margin-top: 7px;
  border-radius: 50%;
  background: currentColor;
}

.msg--info    { color: var(--info);        background: var(--info-bg);        border-color: color-mix(in oklch, var(--info) 20%, transparent); }
.msg--success { color: var(--success);     background: var(--success-bg);     border-color: color-mix(in oklch, var(--success) 20%, transparent); }
.msg--error   { color: var(--destructive); background: var(--destructive-bg); border-color: color-mix(in oklch, var(--destructive) 20%, transparent); }
.msg--warning { color: var(--warning);     background: var(--warning-bg);     border-color: color-mix(in oklch, var(--warning) 22%, transparent); }

.msg span { color: var(--foreground); }

/* == auth card == */
/* shadcn Card: bg-card, rounded-xl, one hairline, shadow-sm. */
.card {
  background: var(--card);
  color: var(--card-foreground);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-sm);
  padding: 24px;
}

/* == headings + copy == */
/* CardTitle / CardDescription: semibold, tight tracking, none of the optical
   sizes the old scale used — shadcn's headings are plain weight steps. */
.card__title {
  margin: 0 0 6px;
  font-size: 18px;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.015em;
  color: var(--foreground);
}

.card__lede {
  margin: 0 0 24px;
  color: var(--muted-foreground);
  font-size: 14px;
}
.card__lede strong { color: var(--foreground); font-weight: 500; }

/* == forms == */
.field { margin-bottom: 16px; }

/* shadcn Label: text-sm, medium, no colour of its own */
.field__label {
  display: block;
  margin-bottom: 8px;
  font-size: 14px;
  font-weight: 500;
  line-height: 1;
  color: var(--foreground);
}

/* Base field styling applies to the .input class (set by the form widgets)
   and to any text-like input Django renders inside the card form.

   The card branch is wrapped in :where() so it contributes no specificity: it's
   a default, and the variants below (the code field, the error state, the file
   input) are meant to override it by being more specific in intent — not by
   out-weighing a four-:not() chain, which none of them did. */
/* shadcn Input: h-9, transparent fill, border-input, rounded-md, shadow-xs.
   The fill is transparent rather than a tinted surface — the border alone
   carries the field, which is what keeps a shadcn form quiet. */
.input,
:where(.card form input:not([type="checkbox"]):not([type="radio"]):not([type="submit"]):not([type="button"])) {
  width: 100%;
  height: 36px;
  padding: 4px 12px;
  font-family: inherit;
  font-size: 14px;
  line-height: 1.4286;
  color: var(--foreground);
  background: transparent;
  border: 1px solid var(--input);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-xs);
  transition: color .15s ease, border-color .15s ease, box-shadow .15s ease;
}
.input::placeholder,
.card form input::placeholder { color: var(--muted-foreground); }

/* focus is the shadcn ring: the border goes to --ring and 3px of it at 50%
   sits outside. No colour change, no fill change. */
.input:focus,
:where(.card form input:not([type="checkbox"]):not([type="radio"])):focus {
  outline: none;
  border-color: var(--ring);
  box-shadow: var(--ring-shadow);
}

textarea.input { height: auto; resize: vertical; min-height: 64px; padding: 8px 12px; }

/* Error state is driven by the aria-invalid="true" Django renders on a field
   with errors — so the visual border and the programmatic state never drift.
   Covers both .input widgets and the card's element-selector-styled inputs. */
.input[aria-invalid="true"],
.card form input[aria-invalid="true"],
.card form textarea[aria-invalid="true"] { border-color: var(--destructive); }
.input[aria-invalid="true"]:focus,
.card form input[aria-invalid="true"]:focus,
.card form textarea[aria-invalid="true"]:focus { box-shadow: var(--ring-shadow-destructive); }

/* plain one-time-code field: large, spaced, monospace. Targeted by class and by
   the autocomplete attr the OTP widget sets — but not when otp.js has turned the
   field into boxes, since the first box carries that attr too.
   height:auto releases the 36px the base Input sets — a code field is display
   type, not a control in the component scale. */
.input--code,
.card form input[autocomplete="one-time-code"]:not(.otp__box) {
  height: auto;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 28px;
  letter-spacing: 0.4em;
  padding: 13px 12px;
  padding-left: calc(12px + 0.4em); /* optically recenter tracked text */
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--foreground);
}

/* Segmented code entry: otp.js hides the single input and lays one box per
   digit in this wrapper. Without JS the wrapper holds just the plain input. */
.otp { display: flex; gap: 8px; }

.otp__box {
  flex: 1 1 0;
  width: auto;
  min-width: 0;
  height: auto;
  padding: 12px 0;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 24px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--foreground);
}

/* field-level errors */
.errorlist {
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
  color: var(--destructive);
  font-size: 13px;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.help {
  margin: 8px 0 0;
  font-size: 13px;
  color: var(--muted-foreground);
  line-height: 1.4;
}
.help--center { text-align: center; }

/* == buttons ==
   shadcn Button: h-9 / px-4 / text-sm / font-medium / rounded-md / gap-2, with
   16px icons. No lift on press and no transform anywhere — shadcn buttons
   change colour and nothing else. */
.btn {
  width: 100%;
  height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 0 16px;
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  line-height: 1.4286;
  white-space: nowrap;
  text-decoration: none; /* .btn is used on <a> as well as <button> */
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  cursor: pointer;
  transition: background-color .15s ease, border-color .15s ease,
              color .15s ease, box-shadow .15s ease;
}
.btn:focus-visible { outline: none; border-color: var(--ring); box-shadow: var(--ring-shadow); }
/* icons ship as bare viewBox SVGs, so the size has to come from here */
.btn svg { width: 16px; height: 16px; flex: none; }

/* default variant: --primary ink on --primary-foreground, shadow-xs */
.btn--primary {
  color: var(--primary-foreground);
  background: var(--primary);
  box-shadow: var(--shadow-xs);
}
.btn--primary:hover { background: color-mix(in oklch, var(--primary) 90%, transparent); }

/* outline variant: border-input, hover to --accent. On --card rather than
   shadcn's --background, which is tinted here (see the token block). */
.btn--ghost,
.btn--google {
  color: var(--foreground);
  background: var(--card);
  border-color: var(--input);
  box-shadow: var(--shadow-xs);
}
.btn--ghost:hover,
.btn--google:hover { background: var(--accent); color: var(--accent-foreground); }

/* stacked alternative sign-in methods below the "or" divider */
.alts {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* shadcn disables pointer events outright, so :hover can't fire on a
   disabled control and the opacity is the whole signal */
.btn:disabled {
  pointer-events: none;
  cursor: not-allowed;
  opacity: 0.5;
}

/* inline button: the base .btn is full-width (it lives in the narrow auth
   card); in a wide head/toolbar it hugs its label instead. */
.btn--inline { width: auto; }

/* size="sm": h-8 / px-3 / gap-1.5 */
.btn--sm {
  width: auto;
  height: 32px;
  padding: 0 12px;
  gap: 6px;
  font-size: 14px;
}

/* cautious "Disable" — outlined in the destructive tone, never a solid red
   CTA: this app's real destructive actions are rare and reversible */
.btn--danger {
  color: var(--destructive);
  background: var(--card);
  border-color: color-mix(in oklch, var(--destructive) 32%, transparent);
  box-shadow: var(--shadow-xs);
}
.btn--danger:hover { background: var(--destructive-bg); border-color: var(--destructive); }
.btn--danger:focus-visible {
  border-color: var(--destructive);
  box-shadow: var(--ring-shadow-destructive);
}

/* == consent (authorize) card == */
/* the scope list reads as an itemised grant: boxed on the quiet inset surface */
.consent {
  margin: 4px 0 20px;
  padding: 16px;
  background: var(--muted);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}

.consent__label {
  margin: 0 0 10px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted-foreground);
}

.consent__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.consent__list li {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  font-size: 14px;
  line-height: 1.4286;
  color: var(--foreground);
}
.consent__list svg {
  flex: none;
  width: 16px;
  height: 16px;
  margin-top: 2px;
  color: var(--success);
}

.consent-actions {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* == divider ==
   shadcn Separator with a label: the rule runs behind, the caption is
   text-xs muted, sentence case — shadcn doesn't shout in small caps. */
.divider {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 20px 0;
  color: var(--muted-foreground);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.divider::before,
.divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

/* == card actions / links == */
.actions {
  margin-top: 20px;
  text-align: center;
  font-size: 14px;
  color: var(--muted-foreground);
}

/* shadcn's inline link: medium weight, underline with a 4px offset */
.link {
  color: var(--foreground);
  font-weight: 500;
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-color: color-mix(in oklch, var(--foreground) 30%, transparent);
  transition: text-decoration-color .15s ease;
}
.link:hover { text-decoration-color: currentColor; }
/* a <button> wearing .link — for actions that must POST, where an <a> would
   make the state change reachable by link/prefetch */
button.link {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  cursor: pointer;
}
/* mirrors .btn:disabled, for a link-button held on a cooldown */
button.link:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  text-decoration: none;
}
.link:focus-visible {
  outline: none;
  box-shadow: var(--ring-shadow);
  border-radius: var(--radius-sm);
}

/* == page footer == */
.foot {
  margin-top: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  color: var(--muted-foreground);
  font-size: 12px;
  font-weight: 400;
}
.foot svg { width: 13px; height: 13px; flex: none; }
/* Same ink as the line above it: only the smaller size and the tabular figures
   set it apart, so it reads as the config value it is rather than a second
   footer message. */
.foot--ip {
  margin-top: 0;
  gap: 5px;
  font-size: 11px;
  font-weight: 400;
  font-variant-numeric: tabular-nums;
}
.foot--ip svg { width: 14px; height: 14px; }

/* both footer lines are one block: the .stack gap applies to the group, and the
   lines sit close enough to read as a pair rather than two stray notes */
.foots {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}

/* == entrance motion (respect reduced motion) == */
@media (prefers-reduced-motion: no-preference) {
  .stack > * {
    animation: rise .45s cubic-bezier(.2,.7,.2,1) both;
  }
  .stack > .card { animation-delay: .04s; }
  .stack > .foots { animation-delay: .10s; }

  @keyframes rise {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: translateY(0); }
  }
}

/* == small screens == */
/* short phones (SE-class, 667px tall): the stack is ~607px, so the shell's
   roomy desktop padding is what tips it past the fold. Tighten the frame rather
   than the card, so nothing inside the card has to shrink. */
@media (max-height: 720px) {
  .shell { padding: 24px 20px 20px; }
  .stack { gap: 16px; }
  .brand__logo { height: 48px; }
  .divider { margin: 14px 0; }
}

@media (max-width: 440px) {
  .card { padding: 24px 20px; }
  .input--code,
  .card form input[autocomplete="one-time-code"]:not(.otp__box) {
    font-size: 24px; letter-spacing: 0.32em; padding-left: calc(12px + 0.32em);
  }
  .otp { gap: 6px; }
  .otp__box { font-size: 21px; }
}

/* ==========================================================================
   launchpad dashboard — wide, logged-in layout
   Built from shadcn primitives: a sticky bordered header carrying the search,
   Card tiles laid out icon-beside-text, muted section eyebrows with a rule,
   and one Card for the empty state.
   ========================================================================== */

.app {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

/* == top bar ==
   One wrapping flex row on --card, full-bleed, with a single bottom hairline.
   Desktop: brand · nav · search (flexes) · account. Below the breakpoint the
   nav and account drop out for the overflow menu and the search — ordered
   last — takes a full-width second row inside the same sticky bar. */
.topbar {
  position: sticky;
  top: 0;
  z-index: 10;
  background: color-mix(in oklch, var(--card) 92%, transparent);
  -webkit-backdrop-filter: saturate(180%) blur(8px); /* Safari/iOS */
  backdrop-filter: saturate(180%) blur(8px);
  border-bottom: 1px solid var(--border);
}

.topbar__inner {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 12px;
  min-height: 60px;
  padding: 10px 24px;
}

.topbar__brand {
  display: inline-flex;
  align-items: center;
  flex: none;
  border-radius: var(--radius-sm);
  outline: none;
}
.topbar__brand:focus-visible { box-shadow: var(--ring-shadow); }

/* the KONSPEC lockup is a wide horizontal mark (~3.4:1, trimmed to the ink) —
   size by height, width auto-scales */
.topbar__logo {
  display: block;
  height: 26px;
  width: auto;
}

/* Separator, orientation="vertical" */
.topbar__sep {
  flex: none;
  width: 1px;
  height: 20px;
  background: var(--border);
}

.topbar__nav {
  display: flex;
  align-items: center;
  gap: 2px;
  flex: none;
}

/* Button variant="ghost" size="sm": no border, no fill until hover */
.navlink {
  display: inline-flex;
  align-items: center;
  height: 32px;
  padding: 0 10px;
  font-size: 14px;
  font-weight: 400;
  color: var(--muted-foreground);
  text-decoration: none;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  white-space: nowrap;
  transition: background-color .15s ease, color .15s ease,
              border-color .15s ease, box-shadow .15s ease;
}
.navlink:hover { background: var(--accent); color: var(--accent-foreground); }
.navlink[aria-current="page"] { color: var(--foreground); font-weight: 500; }
.navlink:focus-visible {
  outline: none;
  border-color: var(--ring);
  box-shadow: var(--ring-shadow);
}

/* == top-bar search ==
   shadcn Input with a leading icon and a trailing kbd. Takes the slack in the
   bar on desktop, a full row of its own once the bar wraps. */
.topsearch {
  position: relative;
  flex: 1 1 200px;
  min-width: 0;
  max-width: 560px;
  margin: 0 4px;
}
.topsearch[hidden] { display: none; }

.topsearch__input {
  width: 100%;
  height: 34px;
  padding: 0 36px 0 34px;
  font-family: inherit;
  font-size: 14px;
  color: var(--foreground);
  background: var(--card);
  border: 1px solid var(--input);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-xs);
  transition: border-color .15s ease, box-shadow .15s ease;
}
.topsearch__input::placeholder { color: var(--muted-foreground-subtle); }
.topsearch__input:focus {
  outline: none;
  border-color: var(--ring);
  box-shadow: var(--ring-shadow);
}
/* the UA's own clear affordance duplicates the Escape-to-clear the script
   already provides, and it can't be styled to match */
.topsearch__input::-webkit-search-cancel-button { -webkit-appearance: none; }

.topsearch__icon {
  position: absolute;
  left: 11px;
  top: 50%;
  width: 15px;
  height: 15px;
  transform: translateY(-50%);
  color: var(--muted-foreground-subtle);
  pointer-events: none;
}

/* shadcn kbd: muted fill, no border. It stops being useful once the field has
   focus, and it's noise on a phone where there is no "/" key to press. */
.topsearch__key {
  position: absolute;
  right: 7px;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 5px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--muted-foreground-subtle);
  background: var(--muted);
  border-radius: var(--radius-sm);
  pointer-events: none;
  transition: opacity .15s ease;
}
.topsearch:focus-within .topsearch__key { opacity: 0; }

/* == signed-in account == */
.account {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: none;
  min-width: 0;
  margin-left: auto;
}

/* Avatar with an initials fallback */
.avatar {
  flex: none;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--muted-foreground);
  background: var(--muted);
  border: 1px solid var(--border-subtle);
}
.avatar--lg { width: 36px; height: 36px; font-size: 13px; }

/* the identity block is a link to your own sessions — styled as the block it
   already was, so it reads as your name and not as navigation until hovered */
.account__me {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  padding: 4px 6px;
  margin: -4px -6px;
  border-radius: var(--radius-sm);
  text-decoration: none;
  color: inherit;
}
.account__me:hover { background: var(--accent); }
.account__me:focus-visible { outline: none; box-shadow: var(--ring-shadow); }

.account__id {
  display: flex;
  flex-direction: column;
  line-height: 1.25;
  min-width: 0;
}

.account__name {
  font-size: 13px;
  font-weight: 600;
  color: var(--foreground);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 28ch;
}

/* the employee code, set in mono: it's an identifier to be read off and
   quoted, not prose */
.account__code {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: -0.01em;
  color: var(--muted-foreground);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 28ch;
}

.account__signout { margin: 0; }

/* Button variant="ghost" size="sm" */
.btn-signout {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 32px;
  padding: 0 10px;
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  white-space: nowrap;
  color: var(--foreground);
  background: none;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: background-color .15s ease, border-color .15s ease,
              color .15s ease, box-shadow .15s ease;
}
.btn-signout svg { width: 16px; height: 16px; flex: none; }
.btn-signout:hover { background: var(--accent); color: var(--accent-foreground); }
.btn-signout:focus-visible {
  outline: none;
  border-color: var(--ring);
  box-shadow: var(--ring-shadow);
}

/* == page body == */
.page {
  width: 100%;
  max-width: 1152px;
  margin: 0 auto;
  padding: 40px 24px 72px;
  flex: 1;
}

.page .messages { margin-bottom: 24px; }

.page__head { margin-bottom: 28px; }

/* text-3xl / bold / tracking-tight — the greeting is the page's one loud thing */
.page__title {
  margin: 0 0 6px;
  font-size: 28px;
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.028em;
  color: var(--foreground);
}

.page__lede {
  margin: 0;
  /* the page is 1152px wide for the tiles and tables; prose set to that width
     is a 150-character measure nobody tracks back from */
  max-width: 72ch;
  color: var(--muted-foreground);
  font-size: 14px;
}

/* title row with a right-aligned action; the lede sits below it at full
   content width so the head always matches the body width */
.page__head__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 6px;
}
.page__head__row .page__title { margin: 0; }

/* == launchpad sections ==
   An eyebrow, its live count, and a rule running out to the content edge. */
.section + .section { margin-top: 28px; }

.section__title {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 12px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--muted-foreground);
}
.section__count {
  font-weight: 400;
  letter-spacing: 0.02em;
  color: var(--muted-foreground-subtle);
}
.section__title::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border-subtle);
}

/* == tile grid ==
   Wide columns on purpose: the tiles are what the page is for, so they get the
   width, and a blurb lands on one or two lines instead of a four-line column. */
.tiles {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(304px, 1fr));
  gap: 16px;
}

/* the star is a sibling of the tile link (a form can't nest in an anchor), so
   the item is the positioning context it hangs off */
.tiles__item { position: relative; }

/* shadcn Card as a launcher: icon holder beside the text, rounded-lg, one
   hairline, shadow-xs. Hover moves the border and the shadow only — no lift,
   which is the one thing a shadcn card never does.

   min-height gives a tile without a blurb the same mass as one with, so a row
   of them reads as a row of destinations rather than boxes of assorted size. */
.tile {
  position: relative;
  height: 100%;
  min-height: 108px;
  display: flex;
  align-items: flex-start;
  gap: 14px;
  /* right padding clears the star pinned in the corner */
  padding: 20px 52px 20px 20px;
  background: var(--card);
  color: var(--card-foreground);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  text-decoration: none;
  transition: border-color .15s ease, box-shadow .15s ease,
              background-color .15s ease;
}

/* nothing under the label to fill the height, so centre the row instead of
   leaving the icon stranded at the top of a stretched card */
.tile:not(:has(.tile__desc)):not(:has(.tile__tag)) { align-items: center; }

a.tile:hover {
  border-color: color-mix(in oklch, var(--foreground) 28%, transparent);
  box-shadow: var(--shadow-md);
}
a.tile:focus-visible {
  outline: none;
  border-color: var(--ring);
  box-shadow: var(--ring-shadow);
}

/* logo / monogram holder */
.tile__logo {
  flex: none;
  width: 48px;
  height: 48px;
  border-radius: var(--radius-md);
  overflow: hidden;
  display: grid;
  place-items: center;
  background: var(--background);
}
/* the hairline frames a logo on a white plate; a monogram is its own block of
   colour and a border round it just reads as a stray outline */
.tile__logo:has(img) { border: 1px solid var(--border-subtle); }
.tile__logo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* One neutral plate for every tile: a wall of assorted hues made the grid
   look like a colour chart, and the letter is an identifier, not a category.
   The apps that want to be recognisable at a glance upload a logo. */
.tile__mono {
  width: 100%;
  height: 100%;
  display: grid;
  place-items: center;
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--foreground);
  background: var(--secondary);
  border: 1px solid var(--border-subtle);
}

.tile__body {
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding-top: 1px;
}

/* CardTitle */
.tile__label {
  font-size: 16px;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.012em;
  color: var(--foreground);
}

/* CardDescription — clamped so a long blurb can't set the row's height */
.tile__desc {
  margin-top: 5px;
  font-size: 13px;
  line-height: 1.45;
  color: var(--muted-foreground);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* == launch affordance ==
   Faint at rest, it anchors the bottom-right corner so a card with no blurb
   still has a shape; on hover it steps toward the corner it points at. */
.tile__go {
  position: absolute;
  right: 18px;
  bottom: 16px;
  display: block;
  width: 16px;
  height: 16px;
  color: var(--muted-foreground-subtle);
  opacity: 0.7;
  transition: opacity .15s ease, color .15s ease, transform .15s ease;
}
.tile__go svg { width: 100%; height: 100%; display: block; fill: none; }

a.tile:hover .tile__go,
a.tile:focus-visible .tile__go {
  opacity: 1;
  color: var(--foreground);
  transform: translateX(2px);
}
/* the external mark points up-and-out, so it should leave in that direction */
a.tile:hover .tile__go--out,
a.tile:focus-visible .tile__go--out {
  transform: translate(1px, -1px);
}

/* unavailable tile: visibly inert, not a link */
.tile--disabled {
  background: transparent;
  border-style: dashed;
  box-shadow: none;
  cursor: default;
  padding-right: 20px;
}
.tile--disabled .tile__logo {
  opacity: 0.55;
  filter: saturate(0.35);
}
.tile--disabled .tile__label { color: var(--muted-foreground); }

/* shadcn Badge variant="secondary" */
.tile__tag {
  align-self: flex-start;
  margin-top: 8px;
  display: inline-flex;
  align-items: center;
  padding: 1px 8px;
  font-size: 11px;
  font-weight: 500;
  line-height: 1.5;
  white-space: nowrap;
  color: var(--muted-foreground);
  background: var(--secondary);
  border-radius: var(--radius-sm);
}

/* == favourite star ==
   A ghost icon-button pinned in the corner, above the tile link. Always
   present but muted rather than appearing only on hover: a control nobody can
   see is a control nobody uses, and hover never fires on touch. Starred, it
   stays solid. */
.fav {
  position: absolute;
  top: 12px;
  right: 12px;
  margin: 0;
  line-height: 0;
}

.fav__btn {
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  background: none;
  color: var(--muted-foreground);
  cursor: pointer;
  transition: color .15s ease, background-color .15s ease,
              border-color .15s ease, box-shadow .15s ease;
}
.fav__btn svg { width: 16px; height: 16px; fill: none; }

.fav__btn:hover {
  color: var(--accent-foreground);
  background: var(--accent);
}
.fav__btn:focus-visible {
  outline: none;
  border-color: var(--ring);
  box-shadow: var(--ring-shadow);
}

.fav__btn[aria-pressed="true"] { color: oklch(0.72 0.14 78); }
.fav__btn[aria-pressed="true"] svg { fill: currentColor; }

/* == no search results == */
.search__none {
  margin: 4px 0 0;
  color: var(--muted-foreground);
  font-size: 14px;
}
.search__none strong { color: var(--foreground); font-weight: 500; }

/* == empty state ==
   shadcn has no empty-state primitive — this is a Card with its header stack
   centred. */
.empty {
  max-width: 448px;
  margin: 16px auto 0;
  text-align: center;
  padding: 40px 32px;
  background: var(--card);
  color: var(--card-foreground);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-xs);
}

.empty__mark {
  display: inline-grid;
  place-items: center;
  width: 40px;
  height: 40px;
  margin-bottom: 16px;
  border-radius: var(--radius-md);
  color: var(--muted-foreground);
  background: var(--background);
  border: 1px solid var(--border-subtle);
}
.empty__mark svg { width: 20px; height: 20px; }

.empty__title {
  margin: 0 0 6px;
  font-size: 16px;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--foreground);
}

.empty__text {
  margin: 0 auto;
  max-width: 38ch;
  color: var(--muted-foreground);
  font-size: 14px;
  line-height: 1.4286;
}
.empty .btn { margin-top: 20px; }

/* == dashboard entrance (respect reduced motion) == */
@media (prefers-reduced-motion: no-preference) {
  .page__head,
  .tiles > li,
  .empty {
    animation: rise .45s cubic-bezier(.2,.7,.2,1) both;
  }
  .page__head { animation-delay: .02s; }
  .tiles > li:nth-child(1) { animation-delay: .06s; }
  .tiles > li:nth-child(2) { animation-delay: .10s; }
  .tiles > li:nth-child(3) { animation-delay: .14s; }
  .tiles > li:nth-child(4) { animation-delay: .18s; }
  .tiles > li:nth-child(n+5) { animation-delay: .22s; }
  .empty { animation-delay: .06s; }
}

/* == mobile overflow menu (hamburger + dropdown) ========================== */
/* On desktop the inline nav + account are used and .menu is hidden. Below the
   breakpoint this menu takes over and carries both. */
.menu {
  display: none;
  position: relative;
  align-items: center;
  flex: none;
  margin-left: auto;
}

/* Button variant="ghost" size="icon" — 36px square, a comfortable tap target */
.menu__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  padding: 0;
  color: var(--foreground);
  background: none;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: background-color .15s ease, border-color .15s ease,
              color .15s ease, box-shadow .15s ease;
}
.menu__btn svg { width: 20px; height: 20px; }
.menu__btn:hover { background: var(--accent); color: var(--accent-foreground); }
.menu__btn:focus-visible {
  outline: none;
  border-color: var(--ring);
  box-shadow: var(--ring-shadow);
}
.menu__btn[aria-expanded="true"] {
  background: var(--accent);
  color: var(--accent-foreground);
}

/* DropdownMenuContent: popover surface, rounded-md, p-1, shadow-md */
.menu__panel {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 20;
  width: 264px;
  max-width: calc(100vw - 24px);
  padding: 4px;
  background: var(--popover);
  color: var(--popover-foreground);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}
.menu__panel[hidden] { display: none; }

/* DropdownMenuLabel: avatar + the two identity lines */
.menu__account {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px;
}
.menu__who {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.menu__name {
  font-size: 14px;
  font-weight: 600;
  color: var(--foreground);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.menu__code {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: -0.01em;
  color: var(--muted-foreground);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* DropdownMenuSeparator: -mx-1 so the rule spans the panel's full width */
.menu__sep {
  height: 1px;
  margin: 4px -4px;
  background: var(--border);
}

/* DropdownMenuItem — rounded-sm, px-2 py-1.5, held at a 44px touch target
   since this menu only ever renders on a phone */
.menu__item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 44px;
  padding: 8px;
  font-family: inherit;
  font-size: 14px;
  font-weight: 400;
  text-align: left;
  color: var(--foreground);
  text-decoration: none;
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background-color .15s ease, color .15s ease;
}
.menu__item svg { flex: none; width: 16px; height: 16px; color: var(--muted-foreground); }
.menu__item:hover { background: var(--accent); color: var(--accent-foreground); }
.menu__item:hover svg { color: var(--accent-foreground); }
.menu__item:focus-visible {
  outline: none;
  background: var(--accent);
  color: var(--accent-foreground);
  box-shadow: var(--ring-shadow);
}
.menu__item:focus-visible svg { color: var(--accent-foreground); }
.menu__item[aria-current="page"] {
  color: var(--accent-foreground);
  background: var(--accent);
  font-weight: 500;
}
.menu__item[aria-current="page"] svg { color: var(--accent-foreground); }

.menu__signout { margin: 0; }

/* == dashboard responsive ==================================================
   Three steps down from the desktop bar. The search is ordered last so that
   the moment the bar wraps it lands on its own full-width row rather than
   squeezing the brand. */

/* tablet: the account's name/email block is the first thing to go — the
   avatar still identifies who's signed in */
@media (max-width: 1023.98px) {
  .account__id { display: none; }
  .topsearch { max-width: none; }
}

/* phone: the inline nav and account give way to the overflow menu, and the
   search takes a row of its own under the brand */
@media (max-width: 767.98px) {
  .topbar__inner { padding: 6px 16px; }
  .topbar__nav,
  .topbar__sep,
  .account { display: none; }
  .menu { display: flex; }

  .topsearch {
    order: 3;
    flex: 1 0 100%;
    max-width: none;
    margin: 0 0 4px;
  }
  .topsearch__input { height: 38px; }
  /* no "/" key to press on a touch keyboard */
  .topsearch__key { display: none; }

  .page { padding: 28px 16px 56px; }
  .page__title { font-size: 24px; }
}

@media (max-width: 480px) {
  .tiles { grid-template-columns: 1fr; }
  .page__title { font-size: 22px; }
}

/* ==========================================================================
   guide pieces (the integration guides, on the panel shell)
   ========================================================================== */

/* inline code chip used within prose */
.code-inline {
  font-family: var(--font-mono);
  font-size: 0.86em;
  padding: 1px 6px;
  border-radius: var(--radius-sm);
  background: var(--muted);
  border: 1px solid var(--border);
  color: var(--foreground);
  white-space: nowrap;
}

/* small inline "required" pill */
.pill {
  display: inline-block;
  padding: 2px 9px;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  border-radius: var(--radius-sm);
  color: var(--foreground);
  background: var(--muted);
  border: 1px solid var(--border);
}
.pill--muted {
  color: var(--muted-foreground);
  background: var(--card);
  border-color: var(--border);
}

/* status pills — carry a small state dot so "configured / not configured"
   reads as a deliberate status indicator */
.pill--success,
.pill--warning {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.pill--success::before,
.pill--warning::before {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}
.pill--success {
  color: var(--success);
  background: var(--success-bg);
  border-color: rgba(27, 122, 75, 0.22);
}
.pill--warning {
  color: var(--warning);
  background: var(--warning-bg);
  border-color: rgba(143, 94, 20, 0.24);
}

/* status row: a separated footer to the "before you start" step */
.status {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 18px 0 0;
  padding-top: 15px;
  border-top: 1px solid var(--border);
}
.status .pill { padding: 4px 11px; font-size: 12px; }

/* highlighted note / important callout inside a step */
.note {
  display: flex;
  gap: 11px;
  margin: 14px 0 0;
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  background: var(--warning-bg);
  border: 1px solid rgba(143, 94, 20, 0.20);
  color: var(--foreground);
  font-size: 13.5px;
  line-height: 1.55;
}
.note svg { flex: none; width: 17px; height: 17px; margin-top: 1px; color: var(--warning); }
.note strong { color: var(--foreground); }

/* featured value block (discovery URL, credentials) — quiet inset panel,
   not a tinted callout */
.feature {
  margin: 4px 0 14px;
  padding: 14px 16px;
  border-radius: var(--radius-sm);
  background: var(--muted);
  border: 1px solid var(--border);
}
.feature__label {
  display: flex;
  align-items: center;
  gap: 7px;
  margin: 0 0 9px;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted-foreground);
}
.feature__label svg { width: 14px; height: 14px; }

/* copy-row: monospace value + copy button */
.copyrow {
  display: flex;
  align-items: stretch;
  gap: 8px;
}
.copyrow__val {
  flex: 1;
  min-width: 0;
  overflow-x: auto;
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--foreground);
  padding: 9px 12px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

.copy {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0 12px;
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--foreground);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease, color .15s ease;
}
.copy svg { width: 14px; height: 14px; flex: none; }
.copy:hover { border-color: var(--border); background: var(--muted); }
.copy:focus-visible { outline: none; box-shadow: var(--ring-shadow); }
.copy.is-copied { color: var(--success); border-color: rgba(27, 122, 75, 0.35); }
.copy .copy__done { display: none; }
.copy.is-copied .copy__idle { display: none; }
.copy.is-copied .copy__done { display: inline-flex; align-items: center; gap: 6px; }

/* compact icon-only copy used inline in tables */
.copy-inline { display: inline-flex; align-items: center; gap: 8px; }
.copy--icon { padding: 6px; }
.copy--icon .copy__idle { display: inline-flex; }
.copy--icon .copy__done { display: none; }
.copy--icon.is-copied .copy__idle { display: none; }
.copy--icon.is-copied .copy__done { display: inline-flex; }

/* responsive data tables */
.table-wrap {
  position: relative; /* keep absolutely-positioned .sr-only cells inside the
                         scroll clip — otherwise they widen the document */
  overflow-x: auto;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-sm);
  -webkit-overflow-scrolling: touch;
}

.doc-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13.5px;
}
.doc-table caption { display: none; }
.doc-table th,
.doc-table td {
  padding: 10px 14px;
  text-align: left;
  vertical-align: top;
  /* the lighter hairline, as everywhere else inside a surface — .table-wrap's
     own --border already draws the table's edge */
  border-bottom: 1px solid var(--border-subtle);
}
.doc-table thead th {
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted-foreground);
  background: var(--table-head);
  /* the band terminates on the full-weight line, not the row hairline */
  border-bottom-color: var(--border);
  white-space: nowrap;
}
.doc-table tbody tr:last-child td,
.doc-table tbody tr:last-child th { border-bottom: 0; }
.doc-table tbody tr:hover td,
.doc-table tbody tr:hover th { background: var(--muted); }
.doc-table .mono {
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: var(--foreground);
  white-space: nowrap;
}
.doc-table .desc { color: var(--muted-foreground); }

/* highlighted row (the discovery endpoint, your own device) — a quiet ink
   keyline, no tint wash: the wash this used to carry was --muted, which now
   reads as a second header band rather than a marked row. */
.doc-table tr.is-primary th:first-child { box-shadow: inset 3px 0 0 var(--brand); }
.doc-table .row-label {
  font-weight: 650;
  color: var(--foreground);
  white-space: nowrap;
}

/* scope definition list */
.scopes {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.scope {
  display: flex;
  gap: 12px;
  align-items: baseline;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
}
.scope:last-child { padding-bottom: 0; border-bottom: 0; }
.scope__name {
  flex: none;
  min-width: 92px;
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 600;
  color: var(--foreground);
}
.scope__desc { margin: 0; color: var(--muted-foreground); font-size: 13.5px; line-height: 1.5; }

/* code block (illustrative config) */
.codeblock {
  margin: 4px 0 0;
  padding: 16px 18px;
  overflow-x: auto;
  background: #16181d;
  border: 1px solid #262a31;
  border-radius: var(--radius-sm);
  -webkit-overflow-scrolling: touch;
}
.codeblock code {
  display: block;
  font-family: var(--font-mono);
  font-size: 13px;
  line-height: 1.7;
  color: #e6e9ed;
  white-space: pre;
  tab-size: 2;
}
.codeblock .c-key { color: #84cbbb; }
.codeblock .c-str { color: #d8b88e; }
.codeblock .c-com { color: #6e7681; }

/* codeblock with a copy affordance pinned to its top-right corner */
.codeblock-wrap { position: relative; }
.codeblock-wrap .copy {
  position: absolute;
  top: 10px;
  right: 10px;
}

/* copy button recolored for the dark codeblock surface */
.copy--dark {
  color: #aeb5bd;
  background: rgba(255, 255, 255, 0.06);
  border-color: #2f343c;
}
.copy--dark:hover {
  color: #e6e9ed;
  background: rgba(255, 255, 255, 0.10);
  border-color: #3c424b;
}
.copy--dark.is-copied { color: #7fd1a8; border-color: rgba(127, 209, 168, 0.45); }

/* GET-able endpoint URLs double as links — same mono ink, quiet underline */
.mono-link {
  color: inherit;
  text-decoration: underline;
  text-decoration-style: dotted;
  text-decoration-color: var(--muted-foreground);
  text-underline-offset: 3px;
}
.mono-link:hover {
  text-decoration-style: solid;
  text-decoration-color: currentColor;
}
.mono-link:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* hover anchor on section headings (injected by the docs page script) */
.anchor {
  display: inline-block;
  margin-left: 9px;
  vertical-align: middle;
  color: var(--muted-foreground);
  opacity: 0;
  transition: opacity .15s ease, color .15s ease;
}
.anchor svg { width: 15px; height: 15px; display: block; }
.anchor:hover { color: var(--foreground); }
.anchor:focus-visible {
  opacity: 1;
  outline: 2px solid var(--ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
.panel:hover .anchor { opacity: 1; }

@media (max-width: 560px) {
  .scope { flex-direction: column; gap: 4px; }
  .scope__name { min-width: 0; }
  .copyrow { flex-direction: column; }
  .copy { padding: 9px 12px; justify-content: center; }

  /* long URLs/identifiers in prose must wrap rather than push the page wide */
  .code-inline { white-space: normal; overflow-wrap: anywhere; }
}

/* == tabbed guides ====================================================== */
/* quiet underline tabs on a hairline rule; the active tab carries an ink
   underline and full-ink text */

.tabs__bar {
  display: flex;
  margin: 0 0 24px;
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.tabs__bar::-webkit-scrollbar { display: none; }

.tabs__list {
  display: inline-flex;
  gap: 22px;
}

.tab {
  flex: none;
  appearance: none;
  margin: 0 0 -1px;
  padding: 10px 2px 11px;
  font-family: inherit;
  font-size: 14px;
  font-weight: 550;
  color: var(--muted-foreground);
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  cursor: pointer;
  white-space: nowrap;
  transition: color .15s ease, border-color .15s ease;
}
.tab:hover { color: var(--foreground); }
.tab[aria-selected="true"] {
  color: var(--foreground);
  font-weight: 600;
  border-bottom-color: var(--foreground);
}
.tab:focus-visible {
  outline: none;
  box-shadow: var(--ring-shadow);
  border-radius: var(--radius-sm);
}

.tabs__panel { outline: none; }
.tabs__panel[hidden] { display: none; }

@media (max-width: 480px) {
  .tabs__list { gap: 16px; }
  .tab { font-size: 13.5px; }
}

/* ==========================================================================
   shared form-page pieces
   ========================================================================== */

/* a wrapping row of links/buttons closing a form page */
.doc-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
}
.doc-actions .btn { width: auto; }

/* File input (logo upload). It wears the Input's exact geometry — same 36px
   height, border and shadow — so it lines up with the text field beside it in
   a row instead of sitting a few pixels taller in a box of its own colour. The
   picker becomes a segment at the leading edge, divided by a hairline, rather
   than a bordered button floating inside a bordered field. */
.field input[type="file"] {
  width: 100%;
  height: 36px;
  padding: 0;
  font-family: inherit;
  font-size: 13.5px;
  line-height: 34px; /* the content box, so "no file chosen" sits centred */
  color: var(--muted-foreground);
  background: transparent;
  border: 1px solid var(--input);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-xs);
  cursor: pointer;
  overflow: hidden;
}
.field input[type="file"]:focus-visible {
  outline: none;
  border-color: var(--ring);
  box-shadow: var(--ring-shadow);
}
.field input[type="file"]::file-selector-button {
  height: 34px;
  margin: 0 12px 0 0;
  padding: 0 12px;
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 500;
  color: var(--foreground);
  background: var(--secondary);
  border: 0;
  border-right: 1px solid var(--input);
  border-radius: 0;
  cursor: pointer;
  transition: background-color .15s ease;
}
.field input[type="file"]::file-selector-button:hover { background: var(--accent); }

/* ==========================================================================
   manage apps — superuser console listing every registered client
   ========================================================================== */

/* The console is the launchpad's grid with a different card in it: same
   sections, same rule-and-count heads, same search. Wider columns, because an
   admin card carries a client id and its callbacks rather than a one-line
   blurb. */
.tiles--admin { grid-template-columns: repeat(auto-fill, minmax(348px, 1fr)); }

/* below the card's own minimum, the track has to give or the grid overflows —
   this has to sit after the rule above, which the launchpad's own phone
   override can't reach (same specificity, earlier in the file) */
@media (max-width: 560px) {
  .tiles--admin { grid-template-columns: 1fr; }
}

/* shadcn Card with a real header/content/footer split — the three parts an
   admin reads in order: what it is, how it's configured, what to do about it. */
.appcard {
  height: 100%;
  display: flex;
  flex-direction: column;
  background: var(--card);
  color: var(--card-foreground);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}

/* A disabled app keeps the card surface — tinting it to --background sank it
   into the page and it stopped reading as a card at all. The state is carried
   by a keyline in the warning tone down the leading edge (paired with the
   pill), and by holding the identity and configuration back a step. The footer
   keeps full contrast: this page is the only place it can be re-enabled. */
.appcard--off {
  box-shadow: inset 3px 0 0 color-mix(in oklch, var(--warning) 60%, transparent),
              var(--shadow-sm);
}
.appcard--off .appcard__head,
.appcard--off .appcard__meta { opacity: 0.72; }

/* == card header: identity == */
.appcard__head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 16px 16px 14px;
}

/* the launchpad's logo holder, one step down: this card leads with the text */
.appcard .tile__logo { width: 40px; height: 40px; }
.appcard .tile__mono { font-size: 17px; }

.appcard__id {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
/* CardTitle, and a link to the same edit page as the footer button — quiet
   until hover, so it reads as a title first and an affordance second */
.appcard__label {
  align-self: flex-start;
  max-width: 100%;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--foreground);
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.appcard__label:hover {
  text-decoration: underline;
  text-underline-offset: 4px;
}
.appcard__label:focus-visible {
  outline: none;
  border-radius: var(--radius-sm);
  box-shadow: var(--ring-shadow);
}
/* the OAuth client name, which is an identifier and not always the label */
.appcard__name {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--muted-foreground);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.appcard__head .pill { flex: none; }

/* == card content: the configuration, as labelled rows == */
.appcard__meta {
  flex: 1;
  margin: 0;
  padding: 0 16px 16px;
  display: flex;
  flex-direction: column;
  gap: 11px;
}
.appcard__row {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.appcard__row dt {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--muted-foreground-subtle);
}
.appcard__row dd {
  margin: 0;
  min-width: 0;
  font-size: 13px;
  line-height: 1.45;
  color: var(--foreground);
}

/* a client id is 40-odd characters in a ~350px column — it wraps rather than
   pushes the copy button off the card */
.appcard .copy-inline { align-items: flex-start; gap: 6px; }
.appcard .mono {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--foreground);
  word-break: break-all;
}

/* callbacks can be several — quiet, monospace, wrapping */
.appcard__uris {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--muted-foreground);
  word-break: break-all;
}
.appcard__none,
.appcard__where { color: var(--muted-foreground); }
.appcard__dot { margin: 0 2px; color: var(--muted-foreground-subtle); }

/* == card footer: the actions, on their own hairline == */
.appcard__foot {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: auto;
  padding: 12px 16px;
  border-top: 1px solid var(--border-subtle);
}
/* the toggle is the consequential one, so it sits apart from Edit */
.appcard__foot form { margin: 0 0 0 auto; }

/* == edit app: one form, split into panels =============================== */

/* A form page is bounded by how wide a field can be before it stops being
   comfortable to read, not by the launchpad's column count. */
.page--form { max-width: 960px; }

.page__meta {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  font-size: 13px;
}
.page__meta__label { color: var(--muted-foreground); }
.page__meta .mono {
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: var(--foreground);
  overflow-wrap: anywhere;
}

/* shadcn Card as a section of a longer page: a titled header, a body, and an
   optional footer for the action that commits it. */
.panel {
  background: var(--card);
  color: var(--card-foreground);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
/* panels stack: siblings inside the form, and the access panel after it */
.panel + .panel,
form + .panel { margin-top: 20px; }

.panel__head { padding: 18px 20px 0; }
/* CardTitle */
.panel__title {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.012em;
  color: var(--foreground);
}
/* CardDescription — one line, or it's back to being a document */
.panel__hint {
  margin: 4px 0 0;
  font-size: 13px;
  line-height: 1.45;
  color: var(--muted-foreground);
}
.panel__body { padding: 18px 20px 20px; }
/* blocks that carry their own rhythm (a .feature stack, a codeblock) must not
   add it against the padding that already sets the panel's edges */
.panel__body > :first-child { margin-top: 0; }
.panel__body > :last-child { margin-bottom: 0; }

/* a guide panel holds prose rather than fields, so its body needs the reading
   typography a .fields grid doesn't */
.panel__body--prose { font-size: 14.5px; color: var(--foreground); }
.panel__body--prose p { margin: 0 0 12px; line-height: 1.6; }
.panel__body--prose strong { font-weight: 650; color: var(--foreground); }
.panel__body--prose code { font-family: var(--font-mono); }
.panel__foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
  padding: 14px 20px;
  border-top: 1px solid var(--border-subtle);
}

/* Two columns, so a ten-field form is five rows deep instead of ten. Fields
   that hold a list or a sentence (the URI textareas, the description) claim
   both with wide=True; the rest pair up. */
.fields {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 18px 20px;
  align-items: start;
}
.fields .field { margin-bottom: 0; min-width: 0; }
.field--wide { grid-column: 1 / -1; }

@media (max-width: 720px) {
  .fields { grid-template-columns: 1fr; }
}

/* the closing row sits under the last panel, not inside it */
.page .doc-actions { margin-top: 20px; }

/* == edit app: per-user access management ================================ */

/* Select shares the Input's geometry (native arrow kept). The old 10px padding
   on a 36px box left no room for the text's descenders, which clipped the
   chosen option; height and padding now come from the same scale as every
   other control. The filled surface stays: a select is a control you open, and
   the fill is what tells you so. */
select.input {
  width: 100%;
  height: 36px;
  padding: 0 12px;
  font-family: inherit;
  font-size: 14px;
  color: var(--foreground);
  background: var(--secondary);
  border: 1px solid var(--input);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-xs);
  cursor: pointer;
  transition: border-color .15s ease, box-shadow .15s ease, background .15s ease;
}
select.input:hover { background: var(--accent); }
select.input:focus {
  outline: none;
  border-color: var(--ring);
  box-shadow: var(--ring-shadow);
}

/* == people picker ==
   One control, used twice: filter, tick, submit once. Granting and revoking
   both used to be a click per person — a dropdown for one, a button per row
   for the other — which made onboarding or offboarding a team a chore. */
.picker { margin: 0 0 20px; }

.picker__bar {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}
.picker__bar .btn { flex: none; }

/* the search icon sits inside the field, as in the top bar */
.picker__search {
  position: relative;
  flex: 1;
  min-width: 0;
}
.picker__search svg {
  position: absolute;
  left: 11px;
  top: 50%;
  width: 15px;
  height: 15px;
  margin-top: -7.5px;
  color: var(--muted-foreground);
  pointer-events: none;
}
.picker__search .input { padding-left: 34px; }
.picker__search .input::-webkit-search-cancel-button { display: none; }

.picker__box {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--card);
}

/* Bounded and scrolling, both lists alike: a few hundred staff must not push
   the rest of the page out of reach, and the two lists have to stay the same
   size as each other or the panel reads as one list with an appendix. Capped
   rather than fixed, so a short list doesn't sit in a half-empty box. */
.picker__list {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 264px;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.picker__item + .picker__item { border-top: 1px solid var(--border-subtle); }

.picker__row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  cursor: pointer;
}
.picker__row:hover { background: var(--accent); }
.picker__row input[type="checkbox"] {
  flex: none;
  width: 15px;
  height: 15px;
  accent-color: var(--foreground);
  cursor: pointer;
}

/* the select-all row is the list's header, not one of its options */
.picker__row--all {
  border-bottom: 1px solid var(--border);
  background: var(--muted);
}
.picker__all {
  font-size: 13px;
  font-weight: 500;
  color: var(--foreground);
}
.picker__count {
  margin-left: auto;
  font-size: 12px;
  color: var(--muted-foreground);
  font-variant-numeric: tabular-nums;
}

.picker__who { flex: 1; min-width: 0; }
.picker__name {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13.5px;
  color: var(--foreground);
}
.picker__email {
  display: block;
  font-size: 12px;
  color: var(--muted-foreground);
  overflow-wrap: anywhere;
}

/* the trailing note on a row that has one (when access was granted) */
.picker__meta {
  flex: none;
  font-size: 12px;
  color: var(--muted-foreground);
  white-space: nowrap;
}

.picker__none {
  margin: 0;
  padding: 14px 12px;
  font-size: 13px;
  color: var(--muted-foreground);
}

@media (max-width: 560px) {
  .picker__bar { flex-direction: column; align-items: stretch; }
  .picker__bar .btn { width: 100%; }
  /* the row keeps its shape; the date moves under the name rather than
     squeezing it to nothing */
  .picker__row { flex-wrap: wrap; }
  .picker__meta { flex: 1 0 100%; padding-left: 35px; }
}

/* current logo preview + remove control on the edit form */
.logo-current {
  display: flex;
  align-items: center;
  gap: 14px;
}
.logo-current img {
  width: 48px;
  height: 48px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border);
  object-fit: cover;
  background: var(--muted);
}
.logo-current__remove {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 13.5px;
  color: var(--foreground);
  cursor: pointer;
}
.logo-current__remove input {
  width: 15px;
  height: 15px;
  accent-color: var(--foreground);
  cursor: pointer;
}

/* the eyebrow that separates who has access from the picker above it */
.access__label {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 10px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--muted-foreground);
}
.access__count {
  font-weight: 400;
  letter-spacing: 0.02em;
  color: var(--muted-foreground-subtle);
}

/* superusers reach every app without a grant, so they're a closing line rather
   than a second list of rows with nothing to revoke */
.grants__note {
  margin: 12px 0 0;
  font-size: 13px;
  line-height: 1.45;
  color: var(--muted-foreground);
}

/* == mobile form ergonomics == */
@media (max-width: 560px) {
  /* full-width actions are easier to hit than a hugging button */
  .doc-actions .btn,
  .page__head__row .btn { width: 100%; }
}

/* ==========================================================================
   login analytics — stat tiles, bars, per-day columns
   ========================================================================== */

/* == data-viz palette ==
   Categorical slots 1–3, validated as a set against the white card surface:
   adjacent CVD ΔE 9.2 (deutan), normal-vision ΔE 24.0, both clear of their
   floors. The order is the safety mechanism — a method is bound to a slot by
   its declaration order in LoginMethod and never by rank, so a colour always
   means the same method.

   Slot 3 measures 2.82:1 against the card, under the 3:1 bar, so every bar
   carries a visible count and share and the failures table repeats the whole
   dataset — colour never has to be read on its own.

   Failure marks use the fixed status-critical red, deliberately outside the
   categorical set so a failure can't be mistaken for a fourth method. Red on
   its own is never the message: the failure plot and bars are labelled, and
   green/red was rejected outright for success-vs-failure — that pair measures
   ΔE 4.1 under deuteranopia, i.e. one colour to the readers who most need the
   distinction.

   One mode only, matching the rest of the product: base.html pins
   color-scheme: light, so there is no dark surface to select steps against. */
:root {
  --viz-1: #2a78d6;  /* email code */
  --viz-2: #eb6834;  /* google */
  --viz-3: #1baf7a;  /* password */
  --viz-failure: #d03b3b;
  /* App launches — a different measure entirely, so it takes a slot outside the
     method set. Violet clears both floors against every colour on the page
     (ΔE 16.3 vs slot 1, 37.6 vs slot 2). */
  --viz-launch: #4a3aa7;
  --viz-grid: var(--border-subtle);
}

[data-method="otp"]      { --viz: var(--viz-1); }
[data-method="google"]   { --viz: var(--viz-2); }
[data-method="password"] { --viz: var(--viz-3); }

/* Line-chart series reuse the same four slots rather than introducing new hues.
   The separations above were measured for this set; a fifth colour chosen to
   look different would have to be measured against all of them again, and no
   figure on the console draws more than four series at once. */
[data-series="daily"]   { --viz: var(--viz-launch); }
[data-series="weekly"]  { --viz: var(--viz-1); }
[data-series="monthly"] { --viz: var(--viz-2); }
[data-series="prior"]   { --viz: var(--muted-foreground-subtle); }

/* == window filter == */
.segmented {
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card);
  overflow: hidden;
}
.segmented__opt {
  padding: 7px 14px;
  font-size: 13px;
  font-weight: 500;
  color: var(--muted-foreground);
  text-decoration: none;
  white-space: nowrap;
}
.segmented__opt + .segmented__opt { border-left: 1px solid var(--border); }
.segmented__opt:hover { background: var(--muted); color: var(--foreground); }
.segmented__opt[aria-current="true"] {
  background: var(--primary);
  color: var(--primary-foreground);
}
.segmented__opt:focus-visible {
  outline: none;
  box-shadow: var(--ring-shadow);
  position: relative;
}

/* == KPI row == */
.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 12px;
  margin-bottom: 20px;
}
.stat {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 16px 18px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-xs);
}
.stat__label {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--muted-foreground);
}
/* Proportional figures, not tabular: these are large standalone numbers, and
   tabular digits read loose at this size. The table below is where digits
   need to line up. */
.stat__value {
  font-size: 30px;
  font-weight: 650;
  line-height: 1.1;
  color: var(--foreground);
}
.stat__note { font-size: 12.5px; color: var(--muted-foreground-subtle); }

/* == horizontal bars ==
   The svg is 100 viewBox units wide and scales non-uniformly to the track, so
   a rect's width attribute is literally its percentage. These bars never touch
   one another, so nothing depends on a gap surviving the scale. */
.bars { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.bar {
  display: grid;
  grid-template-columns: minmax(120px, 190px) 1fr auto auto;
  align-items: center;
  gap: 12px;
  font-size: 13.5px;
}
.bar__key {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--foreground);
}
.bar__swatch {
  width: 10px;
  height: 10px;
  flex: none;
  background: var(--viz);
}
/* Why a row is where it is when the bar can't say — "disabled", "deregistered".
   Muted and small: it qualifies the label, it isn't a second label. */
.bar__tag {
  flex: none;
  padding: 1px 6px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--muted-foreground);
  background: var(--muted);
  border-radius: var(--radius-sm);
}
.bar__track { display: block; min-width: 0; }
.bar__svg { display: block; width: 100%; height: 10px; }
.bar__fill--failure { fill: var(--viz-failure); }
.bar__fill--launch { fill: var(--viz-launch); }

/* One rule for every method-coloured mark on the page — the bars here and the
   stacked column segments below both inherit --viz from their data-method. */
rect[data-method] { fill: var(--viz); }
/* Values wear text tokens, never the series colour. */
.bar__value {
  font-family: var(--font-mono);
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
  color: var(--foreground);
}
.bar__share {
  min-width: 48px;
  text-align: right;
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
  color: var(--muted-foreground);
}

/* == legend == */
.legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  margin-bottom: 14px;
  font-size: 12.5px;
  color: var(--muted-foreground);
}
.legend__item { display: inline-flex; align-items: center; gap: 7px; }
.legend__swatch { width: 10px; height: 10px; background: var(--viz); }

/* == per-day columns ==
   Fixed-pixel canvas (see charts.py): the 2px strips separating stacked
   segments must stay 2px, which a container-scaled viewBox can't promise. The
   frame scrolls when the canvas is wider than the panel. */
.plot { display: flex; gap: 10px; align-items: stretch; }
.plot__scale {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  flex: none;
  /* Clears the axis strip (14px) and the baseline rule, so the "0" sits on the
     line it labels. Every chart is its own figure now, so this is the only
     thing ever drawn below a baseline. */
  padding-bottom: 15px;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--muted-foreground-subtle);
}
/* The peak label clears the value-label band reserved at the top of the canvas
   (charts.LABEL_BAND), so it marks the plot's ceiling and not the text above it. */
.plot__peak { padding-top: 16px; }
.plot__frame { overflow-x: auto; min-width: 0; -webkit-overflow-scrolling: touch; }

/* Every plot sits on its own baseline, drawn under it. Refusals used to be a
   strip hung beneath the sign-in plot sharing that plot's rule, which made bars
   growing up from the bottom of their box read as negative values below a
   shared zero. They are a separate figure with a separate zero now, and each
   chart owning its own hairline is what keeps that unambiguous. */
.plot__svg {
  display: block;
  border-bottom: 1px solid var(--viz-grid);
}
.plot__axis .plot__svg { border-bottom: 0; }

/* Value on a column's cap. Text tokens, never the series colour — the mark
   below it carries identity. Tabular figures so a row of caps aligns. */
.plot__value {
  fill: var(--muted-foreground);
  font-size: 10px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.plot__failure { fill: var(--viz-failure); }
.plot__launch { fill: var(--viz-launch); }
.plot__tick { fill: var(--muted-foreground-subtle); font-size: 10px; }

/* == diverging columns ==
   Direction carries the meaning here; the hues only reinforce it. Which is why
   this may use the failure red for "down" where a success/failure pair may not
   — a column below a zero line is already unambiguous without colour. */
/* The zero line is drawn inside the canvas, so this chart drops the baseline
   rule every other plot hangs under itself — two rules would read as two
   different zeros. */
.plot__svg--diverging { border-bottom: 0; }
.plot__zeroline { stroke: var(--border); stroke-width: 1; }
.plot__up { fill: var(--viz-launch); }
.plot__down { fill: var(--viz-failure); }
.legend__swatch--up { background: var(--viz-launch); }
.legend__swatch--down { background: var(--viz-failure); }

/* == lines ==
   Stroked, not filled, and drawn in a scaled viewBox — so the stroke is set in
   px with vector-effect rather than in user units, which the non-uniform scale
   would stretch into a different thickness horizontally than vertically. */
.lines__svg { display: block; width: 100%; height: 150px; }
.lines__path {
  stroke: var(--viz);
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

/* == ring ==
   One share of one whole: the number is the figure, the arc qualifies it. */
.ring { display: flex; align-items: center; gap: 20px; }
.ring__svg { width: 110px; height: 110px; flex: none; }
.ring__track { stroke: var(--muted); }
.ring__arc { stroke: var(--viz-launch); }
.ring__figure { margin: 0; }
.ring__value {
  display: block;
  font-size: 30px;
  font-weight: 650;
  line-height: 1.1;
  color: var(--foreground);
}
.ring__caption { font-size: 13px; color: var(--muted-foreground); }

/* == heatmap ==
   Opacity carries the count, so one fill and one class cover every cell — a
   colour ramp would need a class per bucket, since the CSP forbids the inline
   style a computed colour would otherwise ride on. */
.heat { display: flex; gap: 8px; }
.heat__svg { display: block; }
.heat__cell { fill: var(--viz-launch); }
.heat__rowlabels {
  display: flex;
  flex-direction: column;
  flex: none;
  text-align: right;
}
.heat__rowlabel {
  height: 24px; /* charts.grid CELL + GAP, so a label sits on its own row */
  font-size: 11px;
  line-height: 22px;
  color: var(--muted-foreground-subtle);
}
.heat__collabels { display: flex; }
.heat__collabel {
  width: 24px;
  font-size: 10px;
  text-align: center;
  color: var(--muted-foreground-subtle);
}
.plot__foot { margin: 12px 0 0; font-size: 12.5px; color: var(--muted-foreground); }

.stats--inline { margin-bottom: 18px; }
.panel__subtitle {
  margin: 22px 0 12px;
  font-size: 13px;
  font-weight: 650;
  color: var(--foreground);
}

.panel__empty { margin: 0; font-size: 13.5px; color: var(--muted-foreground); }

/* second line in a table cell: the account a failed attempt resolved to */
.doc-table__sub {
  display: block;
  font-size: 12px;
  color: var(--muted-foreground-subtle);
}

@media (max-width: 640px) {
  .bar { grid-template-columns: 1fr auto; }
  .bar__track { grid-column: 1 / -1; order: 3; }
}

/* == analytics console: a tab of questions, each opening onto its answer ==
   The console is a long list of closed questions, so the closed state is what
   it mostly looks like: a stack of rules, quiet enough to scan by reading. The
   weight goes on the question text, not on chrome around it. */

/* Clock and window filter travel together at the trailing edge, so the head
   row stays a two-part layout (title | tools) rather than spreading three
   items across it. */
.page__head__tools {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}

/* The server's clock. Deliberately quiet — it is a footnote to the hour-based
   figures, not a feature of the page. Tabular figures so the seconds ticking
   don't shift the label beside it. */
.clock {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  margin: 0;
  font-size: 12px;
  color: var(--muted-foreground-subtle);
  white-space: nowrap;
}
.clock__label { font-size: 11px; }
.clock__time {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--muted-foreground);
}

.tabs__lede {
  max-width: 68ch;
  margin: -6px 0 22px;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--muted-foreground);
}

.figures {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}
.figure + .figure { border-top: 1px solid var(--border); }

.figure__q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 15px 20px;
  font-size: 14.5px;
  font-weight: 550;
  color: var(--foreground);
  cursor: pointer;
  list-style: none; /* the default triangle; the chevron below replaces it */
}
.figure__q::-webkit-details-marker { display: none; }
.figure__q:hover { background: var(--muted); }
.figure__q:focus-visible {
  outline: none;
  box-shadow: var(--ring-shadow);
  border-radius: var(--radius-sm);
}
.figure[open] > .figure__q {
  border-bottom: 1px solid var(--border);
  background: var(--muted);
}

.figure__chevron {
  width: 16px;
  height: 16px;
  flex: none;
  color: var(--muted-foreground);
  transition: transform .18s ease;
}
.figure[open] > .figure__q .figure__chevron { transform: rotate(180deg); }

.figure__body { padding: 18px 20px 22px; }
.figure__lede {
  max-width: 72ch;
  margin: 0 0 16px;
  font-size: 13px;
  line-height: 1.6;
  color: var(--muted-foreground);
}
.figure__pending { margin: 0; font-size: 13.5px; color: var(--muted-foreground); }

/* The one number a figure leads with, above whatever draws it. Larger than a
   .stat__value because it stands alone rather than in a row of four. */
.figure__headline {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 10px;
  margin: 0 0 16px;
}
.figure__number {
  font-size: 34px;
  font-weight: 650;
  line-height: 1;
  color: var(--foreground);
}
.figure__unit { font-size: 13.5px; color: var(--muted-foreground); }

/* The closing line of a figure: what the number means, or what to do about it.
   Set apart by a rule rather than a tint so it reads as a note on the figure
   and not as a second figure. */
.figure__note {
  max-width: 72ch;
  margin: 18px 0 0;
  padding-top: 14px;
  border-top: 1px solid var(--border);
  font-size: 13px;
  line-height: 1.6;
  color: var(--muted-foreground);
}

@media (max-width: 560px) {
  .figure__q { padding: 14px 15px; font-size: 14px; }
  .figure__body { padding: 16px 15px 20px; }
}

/* ==========================================================================
   sessions — where you're signed in, and the buttons that end it
   ========================================================================== */

/* The action column: hugs its button and never wraps the label. Centred
   against the row rather than top-aligned like the text columns — a button
   sitting on the first text baseline reads as belonging to that line, not to
   the row. Scoped to .doc-table so vertical-align outranks the th/td default. */
.doc-table .cell--act {
  width: 1%;
  text-align: right;
  white-space: nowrap;
  vertical-align: middle;
}
.cell--act form { margin: 0; }

/* server-side search above the admin console's list */
.filter {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 24px;
}
.filter .input { width: min(360px, 100%); }

/* the per-account "sign out everywhere", under that account's table */
.section__act { margin-top: 12px; }

/* the account's address beside their name in a section eyebrow — the identifier
   under the display name, set in mono like the employee code in the top bar */
.section__sub {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--muted-foreground-subtle);
}

/* Destructive zone: a card in the destructive tone, kept clear of the table so
   the difference between "end this one" and "end everything" is spatial and not
   just wording. The tone is the outlined one used everywhere else in this app —
   no solid red — because the action is drastic but recoverable: you sign in
   again. */
.danger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  margin-top: 32px;
  padding: 20px 22px;
  background: var(--card);
  border: 1px solid color-mix(in oklch, var(--destructive) 28%, transparent);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-sm);
}
.danger__text { flex: 1 1 380px; }
.danger__title {
  margin: 0 0 4px;
  font-size: 15px;
  font-weight: 650;
  letter-spacing: -0.01em;
  color: var(--destructive);
}
.danger__body {
  margin: 0;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--muted-foreground);
}

@media (max-width: 640px) {
  .danger { align-items: stretch; }
  .danger .btn { width: 100%; }
}

/* ==========================================================================
   user console — one account's record (accounts/manage_user.html)
   ========================================================================== */

/* The identity panel: short labelled facts, laid out in as many columns as fit
   rather than as a two-column table. Each fact is a couple of words, so a table
   would leave most of the row empty and force the eye across it. */
.facts {
  margin: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 16px 24px;
}
.fact {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
/* the same eyebrow as an app card's rows — this is the same kind of read */
.fact dt {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--muted-foreground-subtle);
}
.fact dd {
  margin: 0;
  min-width: 0;
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--foreground);
  overflow-wrap: anywhere;
}
.fact .mono {
  font-family: var(--font-mono);
  font-size: 12.5px;
}

/* ==========================================================================
   impersonation banner (templates/hijack/notification.html)
   ========================================================================== */
/* Injected as the last thing in <body> on every page of a session that is
   impersonating somebody. Sticky rather than fixed: it pins to the bottom of
   the viewport while the page scrolls, but still occupies its own space at the
   end of the document, so it can't permanently cover the last rows of a table.
   The warning tone, not the destructive one — nothing is being destroyed, but
   the state is abnormal and must not blend into the page. */
.impbar {
  position: sticky;
  bottom: 0;
  z-index: 60; /* above the sticky top bar's stacking context */
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px 24px;
  flex-wrap: wrap;
  padding: 12px 20px;
  background: var(--warning-bg);
  border-top: 1px solid color-mix(in oklch, var(--warning) 35%, transparent);
  box-shadow: 0 -1px 3px 0 rgb(0 0 0 / 0.06);
}
.impbar__msg {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--warning);
}
.impbar__msg svg { width: 18px; height: 18px; flex: none; }
.impbar__msg strong { font-weight: 650; }

/* ==========================================================================
   print — the guides are documents admins print/PDF
   ========================================================================== */
@media print {
  body { background: none; }
  body::before { display: none; }
  .topbar,
  .menu,
  .messages,
  .impbar,
  .tabs__bar,
  .copy,
  .anchor { display: none !important; }
  .page--form { max-width: none; padding: 0; }
  .panel,
  .card { box-shadow: none; }
  .panel { border-color: #bbb; break-inside: avoid; }
  .codeblock { background: #fff; border-color: #bbb; }
  .codeblock code,
  .codeblock .c-key,
  .codeblock .c-str { color: var(--foreground); }
  .codeblock .c-com { color: var(--muted-foreground); }
}
