/* == 01-tokens.css == */
/* ----------------------------------------------------------------------
   Design tokens. Ported verbatim from dashboard/app/globals.css, which
   was already token-driven: Tailwind's theme mapped `bg-surface`,
   `text-muted` and friends onto these variables rather than owning any
   color of its own. That is why dropping Tailwind costs no color
   fidelity — the palette never lived in it.

   Unified with the marketing site (cool-neutral ink, indigo / clay /
   moss). Success states use moss directly; amber stays a distinct
   warning tier and red a distinct failure tier.

   Dropped in the port: --haiku-*, --sonnet-*, --opus-*, --fable-* and
   --deepseek-*. Those were the model badges of the coding agent, retired
   2026-07-25; nothing has referenced them since.
---------------------------------------------------------------------- */

:root {
  /* Surfaces */
  --bg: #f4f5f7;
  --paper: #fbfcfd;
  --surface: #ffffff;
  --surface2: #eaecef;
  --surface-alt: #f1f3f6;

  /* Rules / borders */
  --border: #d8dadf;
  --border-soft: #e5e6ea;
  --rule: #0c0e14;
  --rule-soft: rgba(12, 14, 20, 0.16);
  --rule-faint: rgba(12, 14, 20, 0.08);

  /* Ink (text) */
  --text: #0c0e14;
  --text-mid: #252830;
  --text-muted: #555864;
  /* Raised from #8a8d99, which measured 3.0:1 on --bg and 3.3:1 on
     --surface: below the 4.5:1 that small text needs, and this is the
     tier that carries stat notes, timestamps and issue references. Now
     4.5:1 on the page ground and 4.9:1 on a card. */
  --text-faint: #6e707a;
  --ink: #0c0e14;
  --ink-mid: #252830;
  --ink-muted: #555864;
  --ink-faint: #6e707a;

  /* Indigo — primary action */
  --indigo: #4f46e5;
  --indigo-dk: #3730a3;
  --indigo-deep: #3730a3;
  --indigo-lt: #eef0fc;
  --indigo-soft: rgba(79, 70, 229, 0.08);
  /* In-flight wash — the home page's lead card. Paired with --moss-soft on
     BackerImpactHero: moss for money that landed, indigo for work still
     moving. Kept separate from --indigo-soft because a full-card wash needs
     more lift than a chip fill, especially over the dark surface. */
  --wash-inflight: rgba(79, 70, 229, 0.1);
  --indigo-edge: rgba(79, 70, 229, 0.24);
  --indigo-ring: rgba(79, 70, 229, 0.22);
  --indigo-chip-border: #c7d2fe;

  /* Clay — human-attention / approvals / pauses */
  --clay: #b85c3a;
  --clay-deep: #8f4528;
  --clay-soft: #f6e8e1;
  --clay-edge: #e3c0b0;

  /* Moss — success / merged / healthy */
  --moss: #4d6b3c;
  --moss-deep: #344a28;
  --moss-soft: #eef2e8;
  --moss-edge: #c4d2b4;

  /* Seal — failure / serious */
  --seal-bg: #1c1f26;
  --seal-fg: #faf9f6;

  /* The secondary button: the same silhouette as the primary, one step
     down in weight. A tint of the accent rather than an outline, so it
     still reads as a button when it sits alone (the Connect payouts
     component mirrors these three through its appearance API). */
  --btn-secondary-bg: #eef0fc;
  --btn-secondary-border: #c7d2fe;
  --btn-secondary-fg: #3730a3;

  --amber: #b67d2c;
  --amber-lt: #f6ecd9;
  --amber-bd: #e6cf94;
  --amber-fg: #6f4a14;

  --red: #b3322a;
  --red-lt: #fef2f2;
  --red-bd: #fecaca;
  --red-fg: #7f1d1d;

  /* Shadows */
  --shadow-sm:
    0 1px 2px rgba(12, 14, 20, 0.05), 0 1px 1px rgba(12, 14, 20, 0.04);
  --shadow-md:
    0 4px 10px rgba(12, 14, 20, 0.06), 0 2px 4px rgba(12, 14, 20, 0.04);
  --shadow-save: 0 -4px 12px rgba(12, 14, 20, 0.06);
  --sidebar-w: 240px;
  /* Behind the mobile drawer. Ink rather than pure black, and the same
     value in both schemes: the page under it is what changes. */
  --scrim: rgba(12, 14, 20, 0.45);


  --select-bg: #f9f9fb;
  --select-caret: #6b7280;

  /* ------------------------------------------------------------------
     Chart marks (internal/web/charts.go, the operator dashboard).

     Named apart from the semantic tokens above rather than reusing
     --moss / --indigo directly at the call site, because a chart series
     is an ENTITY, not a state: "money that left the platform" keeps its
     color when it is good news and when it is bad, while --clay is free
     to change if the palette's idea of "needs attention" ever does.

     The assignments were validated, not eyeballed, against the
     colorblind-separation checks at each surface. Two facts drove them:
     moss against clay is nearly identical under protanopia (ΔE 2.0), so
     those two never appear in the same chart; indigo against clay is the
     classic cool/warm pair and separates cleanly (ΔE 28.4), so it is the
     diverging chart's. Moss reads a little gray for its size — it is a
     deliberately desaturated olive — which is why every chart that uses
     it also carries a legend, a 2px surface gap between segments, and a
     table of its own numbers underneath.
  ------------------------------------------------------------------ */
  --chart-in: var(--indigo);
  --chart-out: var(--clay);
  --chart-payout: var(--moss);
  --chart-fee: var(--indigo);
  --chart-compute: var(--clay);
  --chart-line: var(--indigo);
  /* One step off the surface: a grid has to be findable and nothing
     more. Solid, never dashed — dashing reads as a threshold. */
  --chart-grid: var(--rule-faint);
  --chart-axis: var(--rule-soft);
  /* The funnel's ordinal ramp: one hue, deepening with depth, because
     the four stages are one population measured four times rather than
     four different things. Monotone in lightness with visible steps; the
     light end clears 3:1 against the surface. */
  --funnel-1: #818cf8;
  --funnel-2: #6366f1;
  --funnel-3: #4f46e5;
  --funnel-4: #3730a3;
  --funnel-track: var(--surface2);
}

@media (prefers-color-scheme: dark) {
  :root {
    /* Surfaces */
    --bg: #0d0f14;
    --paper: #14171f;
    --surface: #1c1f29;
    --surface2: #232633;
    --surface-alt: #232633;

    /* Rules / borders */
    --border: #353945;
    --border-soft: #262934;
    --rule: #e6e8ee;
    --rule-soft: rgba(230, 232, 238, 0.22);
    --rule-faint: rgba(230, 232, 238, 0.12);

    /* Ink */
    --text: #e6e8ee;
    --text-mid: #c2c5cf;
    --text-muted: #8a8d99;
    /* Same fix in the dark scheme: #5e6270 measured 2.7:1 on a card. */
    --text-faint: #808699;
    --ink: #e6e8ee;
    --ink-mid: #c2c5cf;
    --ink-muted: #8a8d99;
    --ink-faint: #808699;

    /* Indigo */
    --indigo: #a5b4fc;
    --indigo-dk: #818cf8;
    --indigo-deep: #6366f1;
    --indigo-lt: #1e2040;
    --indigo-soft: rgba(165, 180, 252, 0.1);
    --wash-inflight: rgba(129, 140, 248, 0.17);
    --indigo-edge: rgba(165, 180, 252, 0.32);
    --indigo-ring: rgba(165, 180, 252, 0.28);
    --indigo-chip-border: #3730a3;

    /* Clay */
    --clay: #e89572;
    --clay-deep: #f4b393;
    --clay-soft: #3a2118;
    --clay-edge: #6b3826;

    /* Moss */
    --moss: #94b07a;
    --moss-deep: #b6cc9d;
    --moss-soft: #1d2a17;
    --moss-edge: #354a28;

    /* Seal */
    --seal-bg: #ece9df;
    --seal-fg: #0c0d12;

    --btn-secondary-bg: #262a4d;
    --btn-secondary-border: #3b3f6b;
    --btn-secondary-fg: #c3cbfd;

    --amber: #d7a85a;
    --amber-lt: #2a1f0a;
    --amber-bd: #5c4218;
    --amber-fg: #f3d99a;

    --red: #e07065;
    --red-lt: #2a1212;
    --red-bd: #5c1f1c;
    --red-fg: #f3b5b0;

    /* Shadows */
    --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3), 0 1px 2px rgba(0, 0, 0, 0.2);
    --shadow-md: 0 4px 10px rgba(0, 0, 0, 0.28), 0 2px 4px rgba(0, 0, 0, 0.2);
    --shadow-save: 0 -4px 12px rgba(0, 0, 0, 0.32);


    --select-bg: #21252f;
    --select-caret: #7c8494;

    /* Chart marks, re-stepped for the dark surface rather than flipped.
       The dark accents above are pastels chosen to sit on a dark ground,
       and a fill wants more body than a text accent: indigo drops to its
       deep step so it holds its own against clay (ΔE 28.3 protan) and
       against moss (ΔE 29.8). */
    --chart-in: var(--indigo-deep);
    --chart-out: var(--clay);
    --chart-payout: var(--moss);
    --chart-fee: var(--indigo-deep);
    --chart-compute: var(--clay);
    --chart-line: var(--indigo-dk);
    --chart-grid: var(--rule-faint);
    --chart-axis: var(--rule-soft);
    /* The ramp runs the other way on a dark ground: deeper in the funnel
       is brighter, because brightness is what reads as "more" here. */
    --funnel-1: #4f46e5;
    --funnel-2: #6366f1;
    --funnel-3: #818cf8;
    --funnel-4: #a5b4fc;
    --funnel-track: var(--surface2);
  }
}

/* ----------------------------------------------------------------------
   Type and space scale. These lived in tailwind.config.ts rather than in
   globals.css, so unlike the palette above they are a genuine port.

   The size steps preserve the exact pixel values the config defined,
   including `smd` (13px) — the intentional extra step, kept because 13px
   had too many real uses to fold into 12 or 14. Values are bare pixels
   with no paired line-height, matching the config; leading stays a
   separate decision.

   The space scale is new. Tailwind's spacing was implicit (p-3 = 12px);
   naming it makes the rhythm visible and stops the hand-written CSS from
   drifting into arbitrary pixel values.
---------------------------------------------------------------------- */

:root {
  --text-2xs: 10px;
  --text-xs: 11px;
  --text-sm: 12px;
  --text-smd: 13px;
  --text-base: 14px;
  --text-md: 15px;
  --text-lg: 18px;
  --text-xl: 20px;
  --text-2xl: 22px;
  --text-3xl: 28px;
  --text-4xl: 34px;

  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-10: 40px;
  --space-12: 48px;
  --space-16: 64px;

  --radius: 10px;
  --radius-sm: 6px;
  --radius-lg: 14px;
  --radius-pill: 999px;

  /* Breakpoint reference (Tailwind's sm/md/lg), for the media queries
     below. CSS cannot use a variable in a media query, so these are
     documentation, not indirection: change one and change its queries. */
  --bp-sm: 640px;
  --bp-md: 768px;
  --bp-lg: 1024px;
}

/* == 02-base.css == */
/* ----------------------------------------------------------------------
   Fonts. next/font/google downloaded Geist, Geist Mono and Newsreader at
   build time and self-hosted them; without a build step we ship the
   faces ourselves.

   These are Google Fonts' own woff2 subsets, fetched once from
   fonts.gstatic.com and committed. To refresh them, ask the CSS API with
   a current browser User-Agent (an old one is answered with ttf) and
   pull the files it names:

     https://fonts.googleapis.com/css2
       ?family=Geist:wght@400;600
       &family=Geist+Mono:wght@400
       &family=Newsreader:ital,wght@0,600;1,600
       &display=swap

   That is the whole "toolchain": the compression these files needed had
   already been done by the people who publish the fonts, so self-hosting
   woff2 costs a download, not a build step. The blocks below are
   Google's CSS with the URLs pointed at /static/fonts; the
   unicode-range on each is what makes the split pay,
   since a browser fetches only the subsets the page's text actually
   needs (latin alone, for almost every page).

   Geist and Geist Mono ship one variable file per subset, shared by both
   weight declarations, so 22 faces are 17 files.

   The one remaining regression from next/font: two weights per family
   instead of five. Requests for 500 resolve to 400 and 700 to 600 under
   normal CSS font matching; because a 600 face is present, nothing is
   synthetically emboldened, which is the artifact that would actually
   look wrong.

   Newsreader is the display face and ships in a single weight, which is
   a rule the design already had: never pair it with bold, or the browser
   synthesizes and the hairlines smear.

   The OG card renderer does not read these. It needs full TTFs (see
   ogimage.go), which are embedded in the binary but never served.
---------------------------------------------------------------------- */

/* Geist 400: latin */
@font-face {
  font-family: "Geist";
  src: url("/static/fonts/Geist-latin.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Geist 400: latin-ext */
@font-face {
  font-family: "Geist";
  src: url("/static/fonts/Geist-latin-ext.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Geist 400: vietnamese */
@font-face {
  font-family: "Geist";
  src: url("/static/fonts/Geist-vietnamese.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

/* Geist 400: cyrillic */
@font-face {
  font-family: "Geist";
  src: url("/static/fonts/Geist-cyrillic.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

/* Geist 400: cyrillic-ext */
@font-face {
  font-family: "Geist";
  src: url("/static/fonts/Geist-cyrillic-ext.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}

/* Geist 600: latin */
@font-face {
  font-family: "Geist";
  src: url("/static/fonts/Geist-latin.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Geist 600: latin-ext */
@font-face {
  font-family: "Geist";
  src: url("/static/fonts/Geist-latin-ext.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Geist 600: vietnamese */
@font-face {
  font-family: "Geist";
  src: url("/static/fonts/Geist-vietnamese.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

/* Geist 600: cyrillic */
@font-face {
  font-family: "Geist";
  src: url("/static/fonts/Geist-cyrillic.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

/* Geist 600: cyrillic-ext */
@font-face {
  font-family: "Geist";
  src: url("/static/fonts/Geist-cyrillic-ext.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}

/* Geist Mono 400: latin */
@font-face {
  font-family: "Geist Mono";
  src: url("/static/fonts/GeistMono-latin.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Geist Mono 400: latin-ext */
@font-face {
  font-family: "Geist Mono";
  src: url("/static/fonts/GeistMono-latin-ext.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Geist Mono 400: vietnamese */
@font-face {
  font-family: "Geist Mono";
  src: url("/static/fonts/GeistMono-vietnamese.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

/* Geist Mono 400: cyrillic */
@font-face {
  font-family: "Geist Mono";
  src: url("/static/fonts/GeistMono-cyrillic.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

/* Geist Mono 400: cyrillic-ext */
@font-face {
  font-family: "Geist Mono";
  src: url("/static/fonts/GeistMono-cyrillic-ext.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}

/* Geist Mono 400: symbols2 */
@font-face {
  font-family: "Geist Mono";
  src: url("/static/fonts/GeistMono-symbols2.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+2000-2001, U+2004-2008, U+200A, U+23B8-23BD, U+2500-259F;
}

/* Newsreader 600: latin */
@font-face {
  font-family: "Newsreader";
  src: url("/static/fonts/Newsreader-latin.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Newsreader 600: latin-ext */
@font-face {
  font-family: "Newsreader";
  src: url("/static/fonts/Newsreader-latin-ext.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Newsreader 600: vietnamese */
@font-face {
  font-family: "Newsreader";
  src: url("/static/fonts/Newsreader-vietnamese.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

/* Newsreader 600 italic: latin */
@font-face {
  font-family: "Newsreader";
  src: url("/static/fonts/Newsreader-Italic-latin.woff2") format("woff2");
  font-weight: 600;
  font-style: italic;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Newsreader 600 italic: latin-ext */
@font-face {
  font-family: "Newsreader";
  src: url("/static/fonts/Newsreader-Italic-latin-ext.woff2") format("woff2");
  font-weight: 600;
  font-style: italic;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Newsreader 600 italic: vietnamese */
@font-face {
  font-family: "Newsreader";
  src: url("/static/fonts/Newsreader-Italic-vietnamese.woff2") format("woff2");
  font-weight: 600;
  font-style: italic;
  font-display: swap;
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

:root {
  --font-sans: "Geist", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  --font-display: "Newsreader", Georgia, serif;
}

/* ----------------------------------------------------------------------
   Base. Tailwind's preflight is gone with Tailwind, so the handful of
   resets that actually mattered are spelled out. This is deliberately
   not a full normalize: unstyled defaults are mostly fine, and the ones
   that are not are listed here.
---------------------------------------------------------------------- */

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

/* The browser's own [hidden] rule lives in the user-agent stylesheet,
   which any author rule outranks regardless of specificity -- so an
   element carrying both `hidden` and a class that sets `display` (e.g.
   `.banner`) stays visible, silently. Toggling `el.hidden` in JS is the
   right pattern; this is what makes it actually work everywhere. */
[hidden] {
  display: none !important;
}

html,
body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--text-md);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-feature-settings: "ss01", "kern";
}

body {
  margin: 0;
  min-height: 100vh;
}

a {
  color: inherit;
  text-decoration: none;
}

a:hover {
  text-decoration: none;
}

/* Headings arrive with browser margins and a bold weight that fights the
   display face. Sizing is per-context, so only the shared reset lives here. */
h1,
h2,
h3,
h4,
h5,
h6 {
  margin: 0;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.2;
}

p {
  margin: 0;
}

ul,
ol {
  margin: 0;
  padding: 0;
  /* Lists are layout by default (.stack, .row); prose re-adds real bullets. */
  list-style: none;
}

figure,
blockquote {
  margin: 0;
}

img,
svg {
  display: block;
  max-width: 100%;
}

table {
  border-collapse: collapse;
  width: 100%;
}

/* Form controls do not inherit type by default, which is the single most
   common source of a stray 13px Times input. */
button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
  margin: 0;
}

button {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}

hr {
  border: none;
  border-top: 1px solid var(--border-soft);
  margin: 0;
}

::selection {
  background: var(--indigo);
  color: var(--paper);
}

/* Every interactive element gets the same visible focus ring. In a
   keyboard-and-form application this is not a nicety: without a client
   bundle, tabbing between controls IS the interaction model. */
:focus-visible {
  outline: 2px solid var(--indigo);
  outline-offset: 2px;
  border-radius: 3px;
}

/* iOS Safari auto-zooms when a focused control's font-size is under 16px,
   and the zoom sticks after blur, leaving the layout wider than the
   viewport. Our type scale sits below that, so force 16px on phones. */
@media (max-width: 639px) {
  input,
  select,
  textarea {
    font-size: 16px;
  }
}

/* Skip link — keyboard users should not tab the whole sidebar to reach
   the page. Visually hidden until focused. */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: var(--space-2) var(--space-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: var(--text-smd);
  font-weight: 600;
}

.skip-link:focus {
  left: var(--space-3);
  top: var(--space-3);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* A page must never scroll sideways on a phone.
   
   This is the backstop, not the fix: the fix is naming grid minimums
   explicitly (see .split in 03-layout.css). But a single long word — a repo
   name, a URL in an issue title, a Stripe id — can still overflow its box,
   and a horizontally scrolling page is a worse outcome than a wrapped word.
   Wide content that legitimately needs to scroll does so inside its own
   container (.table-scroll, .prose pre). */
html,
body {
  overflow-x: hidden;
}

/* Long unbroken strings wrap rather than overflow. */
body {
  overflow-wrap: break-word;
}

/* == 03-layout.css == */
/* ----------------------------------------------------------------------
   Layout primitives.

   Tailwind expressed layout as strings of utilities on every element
   (`flex items-center gap-2.5 px-2.5 py-[7px]`). Hand-written CSS could
   reproduce that class-for-class, but the result would be Tailwind with
   worse tooling. Instead the recurring shapes are named once here —
   stack, cluster, grid, shell — and pages compose them.

   The rule of thumb the port followed: if a shape appears three or more
   times it earns a name; if it appears once it belongs in that page's
   own rules, not in a utility.
---------------------------------------------------------------------- */

/* --- The application shell ------------------------------------------- */

.shell {
  display: flex;
  min-height: 100vh;
}

/* The sidebar is fixed on desktop and a slide-over on phones. In React it
   was a client component for two reasons — active-route highlighting and
   the open/closed state of the mobile drawer. The server has always known
   the route, and the drawer is a <details> element, so neither reason
   survives. */
.sidebar {
  position: fixed;
  left: 0;
  top: 0;
  bottom: 0;
  z-index: 50;
  display: flex;
  flex-direction: column;
  width: var(--sidebar-w);
  background: var(--surface);
  border-right: 1px solid var(--border);
  box-shadow: var(--shadow-sm);
}

.shell-main {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
  min-height: 100vh;
}

.shell-content {
  flex: 1;
}

@media (min-width: 768px) {
  .shell-main {
    margin-left: var(--sidebar-w);
  }
}

/* --- Containers ------------------------------------------------------- */

.container {
  width: 100%;
  max-width: 1100px;
  margin: 0 auto;
  padding: var(--space-6) var(--space-4);
}

.container-narrow {
  max-width: 720px;
}

.container-wide {
  max-width: 1320px;
}

.container-prose {
  max-width: 46rem;
}

@media (min-width: 640px) {
  .container {
    padding: var(--space-8) var(--space-6);
  }
}

/* --- Page furniture --------------------------------------------------- */

.page-header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-6);
}

.page-title {
  font-family: var(--font-display);
  font-size: var(--text-3xl);
  font-weight: 600;
  letter-spacing: -0.022em;
}

.page-lede {
  margin-top: var(--space-2);
  max-width: 46rem;
  color: var(--text-muted);
  font-size: var(--text-base);
}

/* The enrolled-repo row under the org name on Home. Links, not prose: the
   sidebar's repo list is behind the hamburger on a phone. */
.repo-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.repo-link {
  display: inline-flex;
  align-items: center;
  padding: 2px 10px;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text);
  font-size: var(--text-sm);
  font-weight: 500;
  transition: border-color 120ms ease, color 120ms ease;
}

.repo-link:hover {
  border-color: var(--indigo);
  color: var(--indigo);
}

.repo-link-add {
  border-style: dashed;
  color: var(--text-faint);
  font-weight: 400;
}

.section {
  margin-top: var(--space-8);
}

.section-title {
  font-size: var(--text-lg);
  font-weight: 600;
  margin-bottom: var(--space-3);
}

/* A subtitle under a .section-title. Pulled up against the title it
   belongs to, since the title carries its own bottom margin for the
   sections that have no subtitle. */
.section-lede {
  margin-top: calc(-1 * var(--space-2));
  margin-bottom: var(--space-3);
  color: var(--text-muted);
  font-size: var(--text-sm);
}

/* An all-caps mono label. Used to head a group of rows without spending a
   heading level on it. */
.eyebrow {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--text-faint);
}

/* The eyebrow in its other role: labelling a whole section rather than
   kicking the title directly under it. A kicker can be dim and tight
   because the big line beneath it carries the pairing; a label sitting
   over a row of cards has nothing to pair with, so it needs its own gap
   and enough contrast to read as type rather than as disabled text. */
.section > .eyebrow {
  margin-bottom: var(--space-3);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

/* --- Composition primitives ------------------------------------------- */

/* A vertical rhythm. Margin on the children rather than gap so it works
   inside a <details>, a <form>, or any element whose display we do not
   control. */
.stack > * + * {
  margin-top: var(--space-4);
}

.stack-sm > * + * {
  margin-top: var(--space-2);
}

.stack-lg > * + * {
  margin-top: var(--space-6);
}

/* A horizontal group that wraps rather than overflowing — the phone-width
   failure mode of a plain flex row. */
.cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

.cluster-sm {
  gap: var(--space-1);
}

.cluster-lg {
  gap: var(--space-4);
}

/* Push everything after it to the far edge of a cluster or row. */
.push {
  margin-left: auto;
}

.row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.row-between {
  justify-content: space-between;
}

.row-start {
  align-items: flex-start;
}

/* Auto-fitting card grid. One column on a phone, as many as fit above it,
   with no breakpoint bookkeeping. */
.grid-cards {
  display: grid;
  /* min(260px, 100%) so the track's floor collapses on a narrow screen
     instead of forcing the grid wider than the viewport. */
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: var(--space-4);
}

.grid-cards-sm {
  grid-template-columns: repeat(auto-fit, minmax(min(190px, 100%), 1fr));
}

/* A two-column split that collapses to one on narrow screens.
   
   minmax(0, 1fr) rather than 1fr, everywhere. A bare `1fr` track is
   minmax(AUTO, 1fr), and that auto minimum is min-content — so one long
   word, or a wide heading, pushes the track past the viewport and the whole
   page scrolls sideways on a phone. This is the single most common way a
   CSS grid breaks at 390px, and it is why every track here names its
   minimum explicitly. */
.split {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-6);
}

@media (min-width: 900px) {
  .split {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--space-8);
  }

  .split-aside {
    grid-template-columns: minmax(0, 1fr) 320px;
  }
}

/* Truncation needs min-width:0 on the flex parent as well as the child;
   forgetting the parent is the classic reason text overflows instead. */
.truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.grow {
  flex: 1;
  min-width: 0;
}

.shrink-0 {
  flex-shrink: 0;
}

/* The prose pages (about, terms, privacy, refunds, contact, GitHub access):
   header, one measured column, footer. */
.legal-page {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  background: var(--bg);
}

.legal-page .container {
  flex: 1;
  min-width: 0;
  padding-top: var(--space-12);
}

.legal-endnote {
  padding-top: var(--space-6);
  border-top: 1px solid var(--border);
  color: var(--text-faint);
  font-size: var(--text-sm);
}

/* == 04-components.css == */
/* ----------------------------------------------------------------------
   Components.

   The first half of this file is lifted from dashboard/app/globals.css
   with no changes. That is the quiet lesson of the port: the button
   system, the water-level bar, the commitment bar, the stage shell and
   the login page were already hand-written CSS classes. Tailwind was
   never doing the interesting work — it was doing the padding.

   The second half is new, and it is the actual cost of the rewrite: the
   recurring shapes that used to be spelled out as utility strings inline
   on every element (cards, chips, tables, form controls, nav rows) now
   need names and definitions.
---------------------------------------------------------------------- */

/* ----------------------------------------------------------------------
   Motion. Ported selectively — the save-status spinner and check-draw
   animations went with FieldSaveStatus, whose whole job was narrating an
   in-flight background write. Config changes are explicit saves now, so
   there is no in-flight state to narrate.
---------------------------------------------------------------------- */

@keyframes slide-in {
  from {
    opacity: 0;
    transform: translateY(8px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.animate-slide-in {
  animation: slide-in 0.25s ease both;
}

/* Merge pulse — the incoming commit on a pull request waiting on a human
   to merge it. Shallower and slower than an alert blink: this marks a
   pending state, not a problem. */
@keyframes merge-pulse {
  0%,
  100% {
    opacity: 1;
  }

  50% {
    opacity: 0.45;
  }
}

.animate-merge-pulse {
  animation: merge-pulse 2.6s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
  .animate-slide-in,
  .animate-merge-pulse {
    animation: none;
  }
}

.btn-primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 6px 12px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.005em;
  line-height: 1.2;
  border-radius: 6px;
  background: var(--indigo);
  color: var(--seal-fg);
  border: 1px solid transparent;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.12),
    0 1px 2px rgba(79, 70, 229, 0.25);
  transition:
    background 120ms ease,
    box-shadow 120ms ease,
    transform 80ms ease;
  white-space: nowrap;
  cursor: pointer;
}

.btn-primary:hover {
  background: var(--indigo-dk);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.14),
    0 2px 8px rgba(79, 70, 229, 0.35);
}

.btn-primary:active {
  transform: translateY(0.5px);
  box-shadow:
    inset 0 1px 1px rgba(0, 0, 0, 0.12),
    0 1px 2px rgba(79, 70, 229, 0.2);
}

.btn-primary:focus-visible {
  outline: none;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.12),
    0 0 0 3px var(--indigo-ring);
}

.btn-primary[disabled],
.btn-primary:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.btn-primary--lg {
  padding: 8px 14px;
  font-size: 14px;
}

.btn-primary--block {
  display: flex;
  width: 100%;
}

.btn-primary .btn-plus {
  font-weight: 400;
  font-size: 1.05em;
  margin-right: -1px;
  opacity: 0.85;
}

/* One step down from .btn-primary, same silhouette: a tint of the accent
   rather than an outline, so a lone secondary action still reads as a
   button. This is the definition the Connect payouts component mirrors
   through its appearance API — keep the two in step. */
.btn-secondary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 6px 12px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.005em;
  line-height: 1.2;
  border-radius: 6px;
  background: var(--btn-secondary-bg);
  color: var(--btn-secondary-fg);
  border: 1px solid var(--btn-secondary-border);
  transition:
    background 120ms ease,
    border-color 120ms ease,
    transform 80ms ease;
  white-space: nowrap;
  cursor: pointer;
}

.btn-secondary:hover {
  border-color: var(--indigo);
}

.btn-secondary:active {
  transform: translateY(0.5px);
}

.btn-secondary:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--indigo-ring);
}

.btn-secondary[disabled],
.btn-secondary:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.btn-dark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 6px 12px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.005em;
  line-height: 1.2;
  border-radius: 6px;
  background: var(--seal-bg, #1c1f26);
  color: var(--seal-fg, #faf9f6);
  border: 1px solid transparent;
  transition:
    background 120ms ease,
    box-shadow 120ms ease,
    transform 80ms ease;
  white-space: nowrap;
  cursor: pointer;
}

.btn-dark:hover {
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
}

.btn-dark:active {
  transform: translateY(0.5px);
}

.btn-dark[disabled],
.btn-dark:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.btn-ghost {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 6px 12px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.005em;
  line-height: 1.2;
  border-radius: 6px;
  background: transparent;
  color: var(--text-muted);
  border: 1px solid var(--border);
  transition:
    background 120ms ease,
    color 120ms ease;
  white-space: nowrap;
  cursor: pointer;
}

.btn-ghost:hover {
  background: var(--bg);
  color: var(--text);
}

.btn-ghost[disabled],
.btn-ghost:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ----------------------------------------------------------------------
   Stage shell — the editorial treatment shared with the marketing site
   (soft radial washes + masked dot grid). Reserved for "stage" pages in
   the arrival journey: login, onboarding, day-one heroes. Working pages
   stay calm — never apply this to dense content.
---------------------------------------------------------------------- */

.stage-shell {
  position: relative;
  overflow: hidden;
  background: var(--bg);
}

.stage-shell::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    radial-gradient(circle at 12% 0%, var(--indigo-soft) 0%, transparent 42%),
    radial-gradient(circle at 100% 18%, var(--clay-soft) 0%, transparent 50%);
  pointer-events: none;
  z-index: 0;
}

.stage-shell::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: radial-gradient(var(--rule-faint) 1px, transparent 1px);
  background-size: 22px 22px;
  background-position: 0 0;
  opacity: 0.55;
  mask-image: radial-gradient(ellipse at 50% 30%, black 40%, transparent 75%);
  -webkit-mask-image: radial-gradient(
    ellipse at 50% 30%,
    black 40%,
    transparent 75%
  );
  pointer-events: none;
  z-index: 0;
}

.stage-shell > * {
  position: relative;
  z-index: 1;
}

/* Featured card — a thin indigo hairline along the top edge. Reserve for
   the single most important card on a page (login card, day-one hero). */
.featured-card {
  position: relative;
}

.featured-card::before {
  content: "";
  position: absolute;
  left: 14px;
  right: 14px;
  top: 0;
  height: 1px;
  background: linear-gradient(
    90deg,
    transparent 0%,
    var(--indigo) 20%,
    var(--indigo) 80%,
    transparent 100%
  );
  opacity: 0.65;
  border-radius: 1px;
  z-index: 1;
}

/* ----------------------------------------------------------------------
   Login page — editorial sign-in, side-by-side on desktop, stacked on mobile.
---------------------------------------------------------------------- */

.login-pitch {
  max-width: 30rem;
}

/* Shared text measure: headline, lede, and bullets all wrap on the same
   left-aligned edge so the column reads as one tidy block, not three
   ragged ones. */
.login-headline,
.login-lede,
.login-bullets {
  max-width: 27rem;
}

.login-headline {
  font-size: clamp(34px, 6vw, 50px);
  font-weight: 600;
  letter-spacing: -0.025em;
  line-height: 1.06;
}

/* Break the italic indigo clause onto its own line — a black statement
   followed by its payoff — instead of starting mid-line. */
.login-headline-accent {
  display: block;
  margin-top: 0.06em;
  color: var(--indigo);
  font-style: italic;
  font-weight: 500;
  letter-spacing: -0.028em;
}

.login-bullets {
  list-style: none;
  padding: 0;
  flex-direction: column;
  gap: 11px;
}

.login-bullets li {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  font-size: 14px;
  color: var(--text-mid);
  line-height: 1.5;
}

.login-bullets strong {
  color: var(--text);
  font-weight: 600;
}

/* A small, calm indigo dot aligned to the first line's cap height. The halo
   ring was fussy against the clean type, so it's gone — the bullets read as
   one tidy list, not three decorated fragments. */
.bullet-dot {
  display: inline-block;
  width: 5px;
  height: 5px;
  margin-top: 7px;
  border-radius: 999px;
  background: var(--indigo);
  flex-shrink: 0;
}

.login-card {
  position: relative;
}

.login-card-inner {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 28px 24px;
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.04) inset,
    0 30px 60px -30px rgba(12, 14, 20, 0.35),
    0 12px 24px -16px rgba(12, 14, 20, 0.25);
}

@media (min-width: 640px) {
  .login-card-inner {
    padding: 32px 32px;
  }
}

.login-github-btn {
  display: inline-flex;
  width: 100%;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 12px 16px;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.005em;
  border-radius: 10px;
  background: var(--seal-bg);
  color: var(--seal-fg);
  border: 1px solid transparent;
  cursor: pointer;
  transition:
    transform 80ms ease,
    box-shadow 160ms ease,
    background 160ms ease;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.08),
    0 1px 2px rgba(12, 14, 20, 0.25),
    0 8px 18px -10px rgba(12, 14, 20, 0.45);
}

.login-github-btn:hover {
  transform: translateY(-1px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.1),
    0 2px 6px rgba(12, 14, 20, 0.3),
    0 14px 26px -12px rgba(12, 14, 20, 0.55);
}

.login-github-btn:active {
  transform: translateY(0);
}

.login-github-btn:focus-visible {
  outline: none;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.08),
    0 0 0 3px var(--indigo-ring);
}

.login-divider {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 18px 0 14px;
}

.login-divider::before,
.login-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border-soft);
}

.login-divider span {
  font-family: var(--font-mono), ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 10px;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.login-scopes {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.login-scope {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-size: 14px;
  line-height: 1.45;
  color: var(--text-mid);
}

.login-scope strong {
  color: var(--text);
  font-weight: 600;
}

.login-scope-mark {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-top: 1px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
}

.login-scope-yes .login-scope-mark {
  background: var(--moss-soft);
  color: var(--moss-deep);
  box-shadow: inset 0 0 0 1px var(--moss-edge);
}

.login-scope-no .login-scope-mark {
  background: var(--red-lt);
  color: var(--red-fg);
  box-shadow: inset 0 0 0 1px var(--red-bd);
}

.login-stale {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: 18px;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius);
  font-size: var(--text-smd);
  line-height: 1.4;
  background: var(--amber-lt);
  border: 1px solid var(--amber-bd);
  color: var(--amber-fg);
}

.login-stale-dot {
  width: 8px;
  height: 8px;
  border-radius: 999px;
  background: var(--amber);
  flex-shrink: 0;
  box-shadow: 0 0 0 3px rgba(182, 125, 44, 0.18);
}

/* Custom scrollbar in dark mode */
@media (prefers-color-scheme: dark) {
  ::-webkit-scrollbar {
    width: 10px;
    height: 10px;
  }

  ::-webkit-scrollbar-track {
    background: var(--bg);
  }

  ::-webkit-scrollbar-thumb {
    background: var(--border);
    border-radius: 6px;
  }

  /* In dark mode --indigo is the light lavender used for chip text, which
     looks washed out as a filled CTA against the deep canvas. Drop to the
     saturated indigo-deep so the button reads in the same family as the
     sidebar-active treatment. */
  .btn-primary {
    background: var(--indigo-deep);
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.08),
      0 1px 2px rgba(0, 0, 0, 0.35);
  }

  .btn-primary:hover {
    background: var(--indigo-dk);
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.12),
      0 2px 8px rgba(0, 0, 0, 0.45);
  }

  .btn-primary:active {
    box-shadow:
      inset 0 1px 1px rgba(0, 0, 0, 0.4),
      0 1px 2px rgba(0, 0, 0, 0.3);
  }
}

/* ======================================================================
   New in the port: the shapes that used to be inline utility strings.
   ====================================================================== */

/* --- Cards ------------------------------------------------------------ */

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-4);
}

.card-lg {
  border-radius: var(--radius-lg);
  padding: var(--space-6);
}

.card-quiet {
  background: var(--paper);
}

.card-raised {
  box-shadow: var(--shadow-sm);
}

.card-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}

.card-title {
  font-size: var(--text-base);
  font-weight: 600;
}

/* A card whose rows are the content: the list treatment used for
   backings, repos, ledger entries. Rows rule against each other rather
   than each carrying its own border. */
.card-rows {
  padding: 0;
  overflow: hidden;
}

.card-rows > * + * {
  border-top: 1px solid var(--border-soft);
}

.card-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
}

.card-row-link:hover {
  background: var(--bg);
}

/* Home's accrued balance: a full-width row in the maintaining band rather
   than a card beside the org name. Baseline-aligned so the figure, the
   sentence that qualifies it and the destination all sit on one line; the
   note wraps under the figure at phone width. */
.accrued-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
  margin-bottom: var(--space-4);
  padding: var(--space-3) var(--space-4);
  transition: border-color 120ms ease;
}

.accrued-row:hover {
  border-color: var(--indigo);
}

.accrued-row:hover .accrued-go {
  color: var(--indigo);
}

/* --- Stats ------------------------------------------------------------ */

.stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.stat-label {
  font-size: var(--text-sm);
  color: var(--text-muted);
}

.stat-value {
  font-size: var(--text-2xl);
  font-weight: 600;
  letter-spacing: -0.02em;
}

.stat-value-lg {
  font-size: var(--text-4xl);
}

.stat-note {
  font-size: var(--text-sm);
  color: var(--text-faint);
}

/* --- Chips and status pills ------------------------------------------- */

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 2px var(--space-2);
  border-radius: var(--radius-pill);
  border: 1px solid var(--border);
  background: var(--surface-alt);
  color: var(--text-mid);
  font-size: var(--text-xs);
  font-weight: 600;
  line-height: 1.6;
  white-space: nowrap;
}

.chip-indigo {
  background: var(--indigo-lt);
  border-color: var(--indigo-chip-border);
  color: var(--indigo-dk);
}

.chip-moss {
  background: var(--moss-soft);
  border-color: var(--moss-edge);
  color: var(--moss-deep);
}

.chip-clay {
  background: var(--clay-soft);
  border-color: var(--clay-edge);
  color: var(--clay-deep);
}

.chip-amber {
  background: var(--amber-lt);
  border-color: var(--amber-bd);
  color: var(--amber-fg);
}

.chip-red {
  background: var(--red-lt);
  border-color: var(--red-bd);
  color: var(--red-fg);
}

/* --- Banners and flashes ---------------------------------------------- */

/* The flash is how a POST tells the user what happened. In the React
   dashboard the mutation and its result rendered in the same component,
   so nothing had to survive a navigation; here every write is
   POST-then-redirect, and this is what carries the outcome across. */
.flash {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid transparent;
  font-size: var(--text-base);
  font-weight: 500;
}

/* Clear the sidebar, but only where there IS one. Unscoped, this indented
   the flash by a sidebar width on the public pages, which have none: a
   failed backing redirects to the issue's public page, so that is exactly
   where the error messages land. */
@media (min-width: 768px) {
  .has-shell .flash {
    margin-left: var(--sidebar-w);
  }
}

.flash-ok {
  background: var(--moss-soft);
  border-color: var(--moss-edge);
  color: var(--moss-deep);
}

.flash-error {
  background: var(--red-lt);
  border-color: var(--red-bd);
  color: var(--red-fg);
}

.banner {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-alt);
  font-size: var(--text-smd);
  line-height: 1.5;
}

.banner-amber {
  background: var(--amber-lt);
  border-color: var(--amber-bd);
  color: var(--amber-fg);
}

.banner-red {
  background: var(--red-lt);
  border-color: var(--red-bd);
  color: var(--red-fg);
}

.banner-indigo {
  background: var(--indigo-lt);
  border-color: var(--indigo-chip-border);
  color: var(--indigo-dk);
}

/* --- Forms ------------------------------------------------------------ */

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.field-label {
  font-size: var(--text-smd);
  font-weight: 600;
  color: var(--text-mid);
}

.field-hint {
  font-size: var(--text-sm);
  color: var(--text-faint);
  line-height: 1.45;
}

.field-error {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--red-fg);
}

.input,
.select,
.textarea {
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
  font-size: var(--text-base);
  line-height: 1.4;
}

.input:focus-visible,
.select:focus-visible,
.textarea:focus-visible {
  outline: none;
  border-color: var(--indigo);
  box-shadow: 0 0 0 3px var(--indigo-ring);
}

.input[aria-invalid="true"] {
  border-color: var(--red-bd);
}

.textarea {
  min-height: 96px;
  resize: vertical;
}

/* StyledSelect was a client component in React. It was a styled <select>;
   the caret and background are the whole of what it did, and both are
   CSS. */
.select {
  appearance: none;
  background-color: var(--select-bg);
  background-image: linear-gradient(45deg, transparent 50%, var(--select-caret) 50%),
    linear-gradient(135deg, var(--select-caret) 50%, transparent 50%);
  background-position:
    calc(100% - 16px) calc(50% + 1px),
    calc(100% - 11px) calc(50% + 1px);
  background-size:
    5px 5px,
    5px 5px;
  background-repeat: no-repeat;
  padding-right: var(--space-8);
}

/* A money input: the currency sign sits inside the control so the number
   is what the user types. */
.input-money {
  display: flex;
  align-items: center;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  padding-left: var(--space-3);
}

.input-money:focus-within {
  border-color: var(--indigo);
  box-shadow: 0 0 0 3px var(--indigo-ring);
}

.input-money span {
  color: var(--text-faint);
  font-size: var(--text-base);
}

.input-money input {
  flex: 1;
  min-width: 0;
  border: none;
  background: none;
  padding: var(--space-2) var(--space-3) var(--space-2) var(--space-1);
  font-size: var(--text-base);
}

.input-money input:focus-visible {
  outline: none;
}

/* A checkbox or radio and its label on one line, with the control's
   hit area covering the text. */
.check {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  font-size: var(--text-smd);
  line-height: 1.45;
  cursor: pointer;
}

.check input {
  margin-top: 3px;
  flex-shrink: 0;
  accent-color: var(--indigo);
}

/* The sticky bar that carries a form's primary action on long pages. */
.form-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-5);
}

/* --- Tables ----------------------------------------------------------- */

/* A wide table must scroll inside its own box, never push the page into a
   horizontal scroll. */
.table-scroll {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}

.table {
  font-size: var(--text-smd);
}

.table th {
  text-align: left;
  padding: var(--space-2) var(--space-4);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-faint);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}

.table td {
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border-soft);
  vertical-align: top;
}

.table tr:last-child td {
  border-bottom: none;
}

/* A table that stops being a table on a phone.
   
   Four columns do not fit 390px, and the one that falls off the edge of
   .table-scroll is the last: on My backings that is the only control in
   the row. Below the fold of a table's usefulness, each row becomes a
   block and each cell states its own column name, so nothing needs a
   sideways scroll to be found. The markup is the same table either way. */
@media (max-width: 639px) {
  .table-stack thead {
    /* Not display:none — the header is still the accessible name for
       each cell, and hiding it outright takes that away too. */
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
  }

  .table-stack,
  .table-stack tbody,
  .table-stack tr,
  .table-stack td {
    display: block;
    width: 100%;
  }

  .table-stack tr {
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--border-soft);
  }

  .table-stack tr:last-child {
    border-bottom: none;
  }

  .table-stack td {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-1) 0;
    border-bottom: none;
    text-align: left;
  }

  .table-stack td::before {
    content: attr(data-label);
    flex-shrink: 0;
    font-size: var(--text-xs);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-faint);
  }

  /* The first cell is the row's subject, so it reads as the heading of
     the block rather than as one more labelled field. */
  .table-stack td:first-child {
    display: block;
    padding-bottom: var(--space-2);
  }

  .table-stack td:first-child::before {
    content: none;
  }

  /* An empty action cell would otherwise draw its own label and a gap. */
  .table-stack td:empty {
    display: none;
  }
}

.table-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* --- Navigation ------------------------------------------------------- */

.nav {
  padding: var(--space-2);
  overflow-y: auto;
}

.nav-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 10px;
  border-radius: var(--radius-sm);
  font-size: var(--text-smd);
  font-weight: 500;
  color: var(--text-muted);
  transition: background 120ms ease, color 120ms ease;
}

.nav-item:hover {
  background: var(--bg);
  color: var(--text-mid);
}

/* Active state is computed on the server from the request path. This is
   the entire reason Sidebar was a client component. */
.nav-item[aria-current="page"] {
  color: var(--indigo-dk);
  background: var(--indigo-lt);
}

.nav-item[aria-current="page"]::before {
  content: "";
  position: absolute;
  left: 0;
  top: 4px;
  bottom: 4px;
  width: 3px;
  border-radius: 0 2px 2px 0;
  background: var(--indigo);
}

.nav-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  opacity: 0.7;
}

.nav-item[aria-current="page"] .nav-icon {
  opacity: 1;
}

/* The maintainer panel: an inset card that reads as "your installation". */
.nav-panel {
  margin-top: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--paper);
  overflow: hidden;
}

.nav-repo {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 6px var(--space-3);
  font-size: var(--text-smd);
  color: var(--text-muted);
}

.nav-repo:hover {
  background: var(--bg);
}

.nav-repo[aria-current="page"] {
  color: var(--text);
  background: var(--indigo-lt);
  font-weight: 600;
}

.nav-repo-dot {
  width: 6px;
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--border);
  flex-shrink: 0;
}

.nav-repo[aria-current="page"] .nav-repo-dot {
  background: var(--indigo);
}

.sidebar-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: var(--space-5) 18px var(--space-4);
  border-bottom: 1px solid var(--border-soft);
}

.sidebar-foot {
  margin-top: auto;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: var(--space-3) 14px;
  border-top: 1px solid var(--border-soft);
}

/* --- The mobile drawer, with no state of our own ----------------------- */

/* MobileNav was a React context provider, a button, and a piece of state.
   The :target pseudo-class is all three: the open button is a link to the
   sidebar's id, the browser owns "is it open", and the close button is a
   link back to the page without the fragment.

   No JavaScript, and it survives a page load — which the React version,
   holding its state in memory, did not. */

.drawer-open,
.drawer-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  flex-shrink: 0;
}

/* The X and the wordmark share a row, so they have to share a center, and
   a pixel nudge cannot give them one: how far a line box sits above the
   glyphs inside it is the engine's call, and Chrome and Safari answer it
   differently (a -3px that centered the X in Chrome still read ~3px low on
   iOS). Trim the wordmark's box down to the cap-height block the eye
   actually reads instead. Then "center it in the row" and "center it on
   the wordmark" are the same instruction in every engine, and neither the
   X nor the crow glyph needs an offset of its own.

   Engines without text-box (Safari < 18.2, Chrome < 133) fall back to the
   untrimmed box, which is where this header has always been. */
.sidebar-head .logomark-word {
  text-box: trim-both cap alphabetic;
}

.sidebar-head .logomark-glyph {
  transform: none;
}

/* The logomark is an inline-flex, so inside the link it sits in a LINE box:
   baseline-aligned, with the body font's leading padding the link out
   taller than the mark itself. That extra space is what pushed the X off
   center. Making the link a flex container drops the line box, and the row
   then aligns three boxes and no text metrics. */
.sidebar-head > a.grow {
  display: flex;
  align-items: center;
}

.drawer-open:hover,
.drawer-close:hover {
  background: var(--bg);
  color: var(--text);
}

/* The scrim behind the open drawer. Inert until the drawer is open, so
   it can never swallow a click on the page it is covering. */
.drawer-scrim {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: var(--scrim);
  opacity: 0;
  pointer-events: none;
  transition: opacity 200ms ease-in-out;
}

@media (max-width: 767px) {
  /* Off-canvas until the sidebar is the URL's target. */
  .sidebar {
    transform: translateX(-100%);
    transition: transform 200ms ease-in-out;
  }

  .sidebar:target {
    transform: translateX(0);
  }

  .sidebar:target ~ .drawer-scrim {
    opacity: 1;
    pointer-events: auto;
  }
}

@media (prefers-reduced-motion: reduce) {
  .sidebar,
  .drawer-scrim {
    transition: none;
  }
}

/* --- The installation switcher ---------------------------------------- */

/* Also a <details>: the summary names the active org, and opening it
   lists the others. Each row is its own one-field form rather than a
   <select>, because a select needs script to submit on change, and a
   visible "Switch" button beside a dropdown is two controls for one
   decision. */

.switcher-summary {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border-soft);
  cursor: pointer;
  list-style: none;
}

.switcher-summary::-webkit-details-marker {
  display: none;
}

/* A single installation is not a choice, so it does not look like one. */
.switcher[data-single="true"] .switcher-summary {
  cursor: default;
}

.switcher-summary:hover {
  background: var(--bg);
}

.switcher-summary .t-xs {
  display: block;
}

.switcher-caret {
  color: var(--text-faint);
  font-size: var(--text-sm);
  transition: transform 120ms ease;
}

.switcher[open] .switcher-caret {
  transform: rotate(180deg);
}

.switcher-list {
  border-bottom: 1px solid var(--border-soft);
  background: var(--surface-alt);
}

.switcher-option {
  display: block;
  width: 100%;
  text-align: left;
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-smd);
  color: var(--text-muted);
}

.switcher-option:hover {
  background: var(--bg);
  color: var(--text);
}

.nav-panel-repos {
  padding: var(--space-1) 0;
}

.nav-panel-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0 var(--space-2) 0 22px;
}

.nav-panel-actions a {
  padding: 6px var(--space-1);
  border-radius: var(--radius-sm);
}

.nav-panel-actions a:hover {
  background: var(--bg);
  color: var(--text-mid);
}

.nav-payouts {
  border-top: 1px solid var(--border-soft);
  border-radius: 0;
}

/* --- The header inside the shell -------------------------------------- */

.shell-header {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--surface);
  border-bottom: 1px solid var(--border-soft);
}

@media (min-width: 640px) {
  .shell-header {
    padding: var(--space-3) var(--space-6);
  }
}

/* The wallet balance, always one click from anywhere. */
.wallet-chip {
  display: flex;
  align-items: center;
  line-height: 1.2;
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--paper);
}

.wallet-chip:hover {
  border-color: var(--indigo-edge);
}

/* A bare icon button: sign out, close, and friends. */
.icon-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--radius-sm);
  color: var(--text-faint);
}

.icon-button:hover {
  background: var(--bg);
  color: var(--text);
}

/* --- Avatars ---------------------------------------------------------- */

.avatar {
  width: 30px;
  height: 30px;
  border-radius: var(--radius-pill);
  object-fit: cover;
  border: 1px solid var(--border-soft);
  flex-shrink: 0;
}

.avatar-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-xs);
  font-weight: 700;
  background: var(--clay-soft);
  color: var(--clay-deep);
  border-color: var(--clay-edge);
}

.avatar-sm {
  width: 20px;
  height: 20px;
}

/* --- Links ------------------------------------------------------------ */

.link {
  color: var(--indigo-dk);
  font-weight: 500;
}

.link:hover {
  text-decoration: underline;
}

.link-quiet {
  color: var(--text-muted);
}

.link-quiet:hover {
  color: var(--text);
}

/* The external-link arrow that ArrowLink drew. A pseudo-element, so it
   cannot be forgotten on one instance and present on another. */
.link-external::after {
  content: " ↗";
  font-size: 0.9em;
  opacity: 0.7;
}

/* --- Empty states ----------------------------------------------------- */

.empty {
  padding: var(--space-10) var(--space-6);
  text-align: center;
  color: var(--text-muted);
  font-size: var(--text-base);
}

.empty-title {
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--text);
  margin-bottom: var(--space-2);
}

/* --- Footer ----------------------------------------------------------- */

.footer {
  border-top: 1px solid var(--border-soft);
  padding: var(--space-6) var(--space-4);
  color: var(--text-faint);
  font-size: var(--text-sm);
}

.footer a:hover {
  color: var(--text-mid);
}

.dashboard-footer {
  /* Height is matched to the sidebar's user row (a 30px avatar with
     var(--space-3) above and below) so the two footers, which meet at the
     sidebar's edge, sit on the same line. */
  display: flex;
  align-items: center;
  min-height: calc(30px + var(--space-3) * 2);
  padding: var(--space-3) var(--space-4);
}

.dashboard-footer > .cluster {
  flex: 1;
}

@media (max-width: 639px) {
  /* Narrow, the tagline has no room to be pushed anywhere: dropping the
     push alone left it sitting against "Contact" like a sixth link. Give
     it the whole line instead, so it reads as the footer's sign-off. */
  .dashboard-footer .push {
    flex-basis: 100%;
    margin-left: 0;
    margin-top: var(--space-2);
  }
}

/* --- Prose ------------------------------------------------------------ */

/* Rendered markdown (issue bodies) and the legal pages. Scoped tightly so
   these element selectors cannot leak into application chrome. */
.prose {
  font-size: var(--text-base);
  line-height: 1.65;
  color: var(--text-mid);
}

/* Flow spacing. The legal pages wrap their content in <section>s, so the
   rhythm has to reach one level in as well as across the top level. */
.prose > * + *,
.prose section > * + * {
  margin-top: var(--space-4);
}

.prose h1,
.prose h2,
.prose h3 {
  color: var(--text);
  margin-top: var(--space-8);
}

.prose h1 {
  font-family: var(--font-display);
  font-size: var(--text-2xl);
}

.prose h2 {
  font-size: var(--text-lg);
}

.prose h3 {
  font-size: var(--text-md);
}

.prose a {
  color: var(--indigo-dk);
  text-decoration: underline;
}

/* Bullets and their indent belong to plain prose lists, never to a
   .stack used for layout. */
.prose ul:not(.stack):not(.stack-sm):not(.stack-lg) {
  padding-left: var(--space-6);
  list-style: disc;
}

.prose ol:not(.stack):not(.stack-sm):not(.stack-lg) {
  padding-left: var(--space-6);
  list-style: decimal;
}

/* Layout lists carry their own rhythm (.stack); only prose bullets get the
   tighter list spacing. */
.prose ul:not(.stack):not(.stack-sm):not(.stack-lg) > li + li,
.prose ol:not(.stack):not(.stack-sm):not(.stack-lg) > li + li {
  margin-top: var(--space-1);
}

.prose code {
  font-family: var(--font-mono);
  font-size: 0.92em;
  padding: 1px 5px;
  border-radius: 4px;
  background: var(--surface2);
}

.prose pre {
  overflow-x: auto;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  background: var(--surface2);
  border: 1px solid var(--border-soft);
}

.prose pre code {
  padding: 0;
  background: none;
}

.prose blockquote {
  padding-left: var(--space-4);
  border-left: 3px solid var(--border);
  color: var(--text-muted);
}

.prose table {
  display: block;
  overflow-x: auto;
  font-size: var(--text-smd);
}

.prose img {
  border-radius: var(--radius-sm);
}

/* --- Logomark --------------------------------------------------------- */

/* Identity, not decoration. The glyph and wordmark are never redrawn. */
.logomark {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 17px;
  font-weight: 500;
  letter-spacing: -0.01em;
  line-height: 1;
}

.logomark-glyph {
  display: inline-flex;
  color: var(--indigo);
  /* Optical, not geometric: "CleverCrow" has no descenders, so centering the
     glyph on the text's em box drops it ~2px below the cap-height block the
     eye actually reads. The offset is a constant because the wordmark size
     is (--text-2xl everywhere); only the glyph box varies. */
  transform: translateY(-2px);
}

.logomark-word {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--text-2xl);
  letter-spacing: -0.005em;
}

.logomark-word-accent {
  color: var(--indigo);
  font-weight: 500;
}

/* --- Site footer ------------------------------------------------------ */

.site-footer {
  margin-top: var(--space-16);
  padding: 0;
}

.site-footer-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-8);
  padding-top: var(--space-10);
}

@media (min-width: 640px) {
  .site-footer-cols {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 768px) {
  .site-footer-cols {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

.site-footer-rule {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
  margin-top: var(--space-12);
  padding: var(--space-6) 0 var(--space-8);
  border-top: 1px solid var(--border-soft);
}

@media (min-width: 640px) {
  .site-footer-rule {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
  }
}

/* --- Login page ------------------------------------------------------- */

.login-stage {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

.login-main {
  flex: 1;
  padding: var(--space-10) var(--space-5);
}

@media (min-width: 640px) {
  .login-main {
    padding: 56px var(--space-8);
  }
}

.login-grid {
  display: grid;
  /* See the note on .split: a bare 1fr floors at min-content and the long
     display headline here would push the page sideways on a phone. */
  grid-template-columns: minmax(0, 1fr);
  align-items: center;
  gap: var(--space-10);
  width: 100%;
  max-width: 1024px;
  margin: 0 auto;
}

.login-grid > * {
  min-width: 0;
}

@media (min-width: 1024px) {
  .login-main {
    padding-top: 80px;
    padding-bottom: 80px;
  }

  .login-grid {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
    gap: 64px;
  }
}

/* The card enters a beat after the pitch. An inline style would have to
   carry this on the element; a class keeps the stylesheet the only place
   that knows about timing. */
.login-card-delayed {
  animation-delay: 60ms;
}

/* --- Sparkline -------------------------------------------------------- */

/* WalletBalanceChart was already a server-rendered SVG in React, because a
   chart of values the server holds gains nothing from a plotting library in
   the browser. It arrives here unchanged in spirit. */
.sparkline {
  width: 100%;
  height: 64px;
  overflow: visible;
}

.sparkline-line {
  fill: none;
  stroke: var(--indigo);
  stroke-width: 1.5;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.sparkline-area {
  fill: var(--indigo-soft);
  stroke: none;
}

/* --- Site header ------------------------------------------------------ */

.site-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border-soft);
  background: var(--surface);
}

/* --- Copy fields ------------------------------------------------------ */

/* The clipboard surface (study §5). The field is readonly and selectable
   with or without scripting, so the button is an enhancement rather than
   the mechanism. */
.copy-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.copy-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
}

.copy-row .input,
.copy-row .textarea {
  flex: 1;
  min-width: 0;
}

.copy-button {
  flex-shrink: 0;
}


/* == 04b-charts.css == */
/* ----------------------------------------------------------------------
   Charts — the operator dashboard's plots (internal/web/charts.go).

   Its own file rather than another 1,500 lines in 04-components.css, and
   numbered to land between components and utilities so a utility class can
   still override anything here. buildStylesheet sorts by filename, so the
   name IS the cascade position.

   The governing decision is in charts.go: the <svg> holds marks and
   gridlines only, and every label around it is HTML. That is what these
   rules are mostly doing — standing a grid up around a plot so the axis
   text stays at the page's own type scale however wide the card gets,
   instead of being scaled down with the drawing.
---------------------------------------------------------------------- */

.chart {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.chart-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-4);
}

.chart-title {
  font-size: var(--text-smd);
  font-weight: 600;
  color: var(--text);
}

.chart-sub {
  font-size: var(--text-sm);
  color: var(--text-muted);
}

/* The legend is present whenever a chart has two or more series: identity
   must never rest on color alone. A single-series chart has none — the
   heading already names what is plotted, and a one-swatch box would just
   say it twice. */
.chart-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  margin-left: auto;
}

.chart-legend-item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  /* Text wears text tokens, never the series color. The swatch beside it
     carries the identity; a light hue set as type is unreadable. */
  color: var(--text-muted);
}

.chart-swatch {
  width: 10px;
  height: 10px;
  border-radius: 3px;
  flex: none;
}

.chart-swatch-in { background: var(--chart-in); }
.chart-swatch-out { background: var(--chart-out); }
.chart-swatch-payout { background: var(--chart-payout); }
.chart-swatch-fee { background: var(--chart-fee); }
.chart-swatch-compute { background: var(--chart-compute); }

/* The plot and its y axis. The axis column stretches to the plot's height,
   which is what keeps its three ticks on the three gridlines without either
   one knowing the other's pixel height. */
.chart-body {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-2) var(--space-3);
  align-items: stretch;
}

/* The x labels are a second row of the SAME grid, in the plot's column.
   Sharing the grid is what aligns them with the plot exactly; a margin
   guessed at the width of the y-axis column drifts the moment a tick
   label gets a digit longer. */
.chart-plot,
.chart-xaxis {
  grid-column: 2;
  min-width: 0;
}

.chart-yaxis {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: flex-end;
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  color: var(--text-faint);
  line-height: 1;
}

/* space-between puts the first tick's TOP on the top gridline and the last
   tick's BOTTOM on the baseline; half a line of pull centres each on its
   own rule. */
.chart-yaxis > :first-child { margin-top: -0.5em; }
.chart-yaxis > :last-child { margin-bottom: -0.5em; }

.chart-svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

/* Hairline, solid, one step off the surface. Never dashed: a dashed rule
   reads as a threshold or a projection when it is only a grid. */
.chart-grid-line {
  stroke: var(--chart-grid);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.chart-baseline {
  stroke: var(--chart-axis);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.mark-in { fill: var(--chart-in); }
.mark-out { fill: var(--chart-out); }
.mark-payout { fill: var(--chart-payout); }
.mark-fee { fill: var(--chart-fee); }
.mark-compute { fill: var(--chart-compute); }

/* A 2px hit target is unusable, so the mark's pointer area is widened
   without widening the mark: the bar keeps its drawn width and the cursor
   gets something to land on for the native <title> tooltip. */
.chart-svg path[class^="mark-"] {
  stroke: transparent;
  stroke-width: 10;
  paint-order: stroke fill;
}

.chart-line {
  fill: none;
  stroke: var(--chart-line);
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

/* A wash, never a saturated block — the line is the data, the fill is
   only there to say which side of it is "so far". */
.chart-area {
  fill: var(--chart-line);
  opacity: 0.1;
  stroke: none;
}

/* The end marker carries a surface-colored ring so it stays legible where
   it crosses its own line or the top gridline. */
.chart-end-dot {
  fill: var(--chart-line);
  stroke: var(--surface);
  stroke-width: 2;
  vector-effect: non-scaling-stroke;
}

.chart-xaxis {
  display: flex;
  font-size: var(--text-xs);
  color: var(--text-faint);
}

/* One cell per column, so a label sits under the column it names.
   Overflow is VISIBLE on purpose: on a narrow card a cell is thinner than
   "Aug 10", and cropping it to "Aug 1" is a worse answer than letting it
   spill. The axis is thinned to every other column (see labelEvery), so
   what it spills into is its empty neighbour. */
.chart-xaxis > * {
  flex: 1 1 0;
  min-width: 0;
  text-align: center;
  white-space: nowrap;
  overflow: visible;
}

/* The one direct label each chart carries: the extreme, named in prose.
   Charts label selectively on purpose — a number on every column is chaos
   and goes unread, and the table below carries the rest. */
.chart-note {
  font-size: var(--text-sm);
  color: var(--text-muted);
}

.chart-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 140px;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  background: var(--surface-alt);
  font-size: var(--text-sm);
  color: var(--text-faint);
}

/* The table-view twin. Every chart has one, and it is what lets the plot
   stay quiet: a tooltip may enhance a value but must never be the only way
   to reach it, and a native <title> is not reachable by keyboard at all. */
.chart-table > summary {
  cursor: pointer;
  font-size: var(--text-sm);
  color: var(--text-faint);
  padding: var(--space-1) 0;
}

.chart-table > summary:hover {
  color: var(--text-muted);
}

.chart-table[open] > summary {
  margin-bottom: var(--space-2);
}

/* ----------------------------------------------------------------------
   The funnel.

   HTML bars rather than SVG: each stage has to line up with its own label
   and count, and a percentage width does that at every viewport without a
   coordinate system to keep in sync.
---------------------------------------------------------------------- */

.funnel {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.funnel-row {
  display: grid;
  grid-template-columns: 8.5rem minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-3);
}

@media (max-width: 640px) {
  .funnel-row {
    grid-template-columns: minmax(0, 1fr) auto;
  }
  .funnel-label {
    grid-column: 1 / -1;
  }
}

.funnel-label {
  font-size: var(--text-smd);
  color: var(--text-mid);
}

/* The unfilled track is a step of the surface ramp, not a hole: the row
   has to read as a proportion even where the bar is short. */
.funnel-track {
  background: var(--funnel-track);
  border-radius: var(--radius-sm);
  height: 20px;
  overflow: hidden;
}

.funnel-bar {
  height: 100%;
  border-radius: var(--radius-sm);
  min-width: 2px;
}

.funnel-bar-1 { background: var(--funnel-1); }
.funnel-bar-2 { background: var(--funnel-2); }
.funnel-bar-3 { background: var(--funnel-3); }
.funnel-bar-4 { background: var(--funnel-4); }

.funnel-count {
  font-size: var(--text-smd);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  text-align: right;
  min-width: 3.5rem;
}

.funnel-share {
  font-size: var(--text-xs);
  color: var(--text-faint);
}

/* == 05-utilities.css == */
/* ----------------------------------------------------------------------
   Utilities.

   Deliberately small. Reproducing Tailwind's full utility set by hand
   would be all of the cost of Tailwind with none of its tooling — no
   purge, no IDE completion, no guarantee a class exists. What is here is
   the handful that genuinely reads better inline than as a named
   component: type scale, ink shades, weight, alignment, and a short
   spacing scale for one-off gaps.

   If you find yourself wanting a fifth utility on one element, that
   element wants a class in 04-components.css instead.
---------------------------------------------------------------------- */

/* --- Type ------------------------------------------------------------- */

.t-2xs {
  font-size: var(--text-2xs);
}
.t-xs {
  font-size: var(--text-xs);
}
.t-sm {
  font-size: var(--text-sm);
}
.t-smd {
  font-size: var(--text-smd);
}
.t-base {
  font-size: var(--text-base);
}
.t-md {
  font-size: var(--text-md);
}
.t-lg {
  font-size: var(--text-lg);
}
.t-xl {
  font-size: var(--text-xl);
}
.t-2xl {
  font-size: var(--text-2xl);
}
.t-3xl {
  font-size: var(--text-3xl);
}
.t-4xl {
  font-size: var(--text-4xl);
}

.t-medium {
  font-weight: 500;
}
.t-semibold {
  font-weight: 600;
}

/* The display face is single-weight by design: pairing it with bold makes
   the browser synthesize, and the hairlines smear. */
.t-display {
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: -0.02em;
}

.t-mono {
  font-family: var(--font-mono);
}

/* Money and counts in a column must line up digit for digit. */
.t-nums {
  font-variant-numeric: tabular-nums;
}

.t-center {
  text-align: center;
}
.t-right {
  text-align: right;
}

/* --- Ink -------------------------------------------------------------- */

.c-text {
  color: var(--text);
}
.c-mid {
  color: var(--text-mid);
}
.c-muted {
  color: var(--text-muted);
}
.c-faint {
  color: var(--text-faint);
}
.c-indigo {
  color: var(--indigo-dk);
}
.c-moss {
  color: var(--moss);
}
.c-clay {
  color: var(--clay);
}
.c-red {
  color: var(--red-fg);
}

/* --- Space ------------------------------------------------------------ */

.mt-0 {
  margin-top: 0;
}
.mt-1 {
  margin-top: var(--space-1);
}
.mt-2 {
  margin-top: var(--space-2);
}
.mt-3 {
  margin-top: var(--space-3);
}
.mt-4 {
  margin-top: var(--space-4);
}
.mt-6 {
  margin-top: var(--space-6);
}
.mt-8 {
  margin-top: var(--space-8);
}

/* --- Lists ------------------------------------------------------------ */

/* Real bullets outside a .prose page. A typed "·" inside the <li> puts the
   mark in the text flow, so a requirement that wraps on a phone starts its
   second line flush under the bullet instead of under the words. */
.bullets {
  padding-left: var(--space-5);
  list-style: disc;
}

/* --- Display ---------------------------------------------------------- */

.hidden {
  display: none;
}

@media (max-width: 767px) {
  .hide-phone {
    display: none;
  }
}

@media (min-width: 768px) {
  .hide-desktop {
    display: none;
  }
}

