/*
 * The web shell's hand-written stylesheet -- no framework, no build step
 * (Propshaft serves this as-is). Palette and posture follow the qunei
 * brand's terracotta system (typeui.sh/design-skills/terracotta, the
 * reference the logo kit was drawn against): warm cream page, ink-brown
 * serif headings, one clay accent, thin rules instead of card chrome,
 * reading-first spacing. skills/qunei-reports/dashboard-template.html
 * mirrors these palette tokens so agent-generated dashboards match the
 * web shell -- a palette change here must be synced there in the same
 * change.
 *
 * Two accent tokens, not one, and it matters: white on raw clay #C56A3C
 * is 3.82:1 -- an AA failure for button labels -- and clay on cream is
 * 3.17:1, a failure for link text. So --accent is a deepened clay that
 * carries text (links, button fills; 4.83:1 on cream, 5.3:1 under white),
 * while --clay is the pure brand hue and only ever marks non-text
 * accents (the logo wedge, quote rules, focus rings) where WCAG's 3:1
 * graphic minimum applies. Every pair below was computed, not eyeballed:
 *   light: fg/bg 7.08 · heading/bg 12.05 · muted/bg 5.37 · accent/bg 4.83 · border-strong/bg 3.86
 *   dark:  fg/bg 9.08 · heading/bg 14.67 · muted/bg 5.97 · accent/bg 6.77 · border-strong/bg 4.27
 *
 * Typography (spec 2026-09-06, section 2): three self-hosted latin-subset
 * faces (app/assets/fonts, OFL licences alongside) so the zero-external-
 * request invariant holds. DM Serif Display carries headings, titles and
 * the one editorial aside; DM Sans (one variable file, wght 400 to 700)
 * carries body, UI, buttons, labels, inputs and navigation; JetBrains
 * Mono carries the journal excerpts and metadata. --font-body is an alias
 * of --font-sans so one line reverts the site to a serif body. Role sizes
 * (--size-*) are set mobile-first and stepped at 640 and 1024.
 *
 * New-surface contrast (same method as above, computed not eyeballed):
 *   light on --paper-soft #F8F0E4: fg 7.54 · muted 5.72 · accent 5.14 · border-strong 4.11
 *   dark  on --paper-soft #2D1B14: fg 8.45 · muted 5.56 · accent 6.3  · on control fill #3A241C: fg 7.46 · border-strong 3.51
 *   buttons: white on --accent 5.81, on --accent-strong 7.29; dark label on --accent 6.61, on --accent-strong 5.23
 */

@font-face {
  font-family: "DM Serif Display";
  src: url("/assets/dm-serif-display-latin-400-dd23de7b.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "DM Serif Display";
  src: url("/assets/dm-serif-display-latin-400italic-d7c29d8b.woff2") format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("/assets/jetbrains-mono-latin-400-849c32ea.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("/assets/jetbrains-mono-latin-500-9590a15c.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("/assets/jetbrains-mono-latin-700-e3f3f7e0.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* DM Sans is the body and UI face (spec 2026-09-06, section 2). One
   variable latin-subset file carries weights 400 to 700; Google Fonts no
   longer distributes static cuts. OFL text sits beside it. */
@font-face {
  font-family: "DM Sans";
  src: url("/assets/dm-sans-latin-variable-fe3ac7ee.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}

:root {
  color-scheme: light dark;

  /* Surfaces (brand guidelines settle these) */
  --bg: #F3E9D8;
  --paper-soft: #F8F0E4;
  --card-bg: #FFFFFF;

  /* Inks. --fg is Terracotta's warm body ink (7.08:1 on canvas); the
     heading ink and muted ink are the brand's own (12.05 and 5.37). */
  --heading: #3A241C;
  --fg: #5C4938;
  --muted: #6F5A4E;

  /* Accent pair: --accent carries text and fills (4.83 on canvas, white
     on it 5.81); --accent-strong is the gradient's deep stop, hover and
     pressed fill (white on it 7.29); --clay is the pure brand hue for
     non-text marks and the focus ring (3.17 on canvas, 3.82 on white). */
  --accent: #A04E26;
  --accent-strong: #8A4220;
  --accent-contrast: #FFFFFF;
  --clay: #C56A3C;
  --clay-soft: rgba(197, 106, 60, 0.14);

  /* Rules are decorative hairlines; control boundaries must clear 3:1
     against canvas, panel and control fill (3.86 / 4.11 / 4.64). */
  --border: rgba(58, 36, 28, 0.22);
  --border-strong: #8B6F5B;

  /* Status (4.77 on canvas, 5.73 on white; text on the tint 4.84) */
  --danger: #B83A1F;
  --danger-soft: #FBE8E0;
  --notice-fg: var(--accent-strong);   /* accent text on the clay tint measures 4.18; the deep stop 5.24 */
  --disabled: #EADDC6;

  /* Depth: warm brown shadow base and the button glint (Terracotta) */
  --shadow-xs: 0 1px 2px 0 rgb(43 29 20 / 0.05);
  --shadow-sm: 0 1px 3px 0 rgb(43 29 20 / 0.08), 0 1px 2px -1px rgb(43 29 20 / 0.06);
  --shadow-md: 0 4px 8px -2px rgb(43 29 20 / 0.08), 0 2px 4px -2px rgb(43 29 20 / 0.06);
  --glint-hi: rgba(255, 244, 231, 0.30);
  --glint-lo: rgba(43, 29, 20, 0.18);

  /* Radius scale (spec section 4): 16 on primary surfaces, 8 on chips,
     4 on blocks nested inside padded panels, pills full. */
  --radius-xs: 4px;
  --radius-sm: 8px;
  --radius-md: 16px;
  --radius-lg: 16px;
  --radius-pill: 9999px;

  /* Spacing scale: multiples of 4 only */
  --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;
  --space-24: 96px;

  /* Columns (spec section 5) */
  --content-max: 1152px;
  --reading-max: 720px;
  --auth-max: 400px;
  --form-max: 32rem;

  --text-xs: 0.875rem;
  --text-sm: 1rem;
  --text-md: 1.125rem;
  --text-lg: 1.5rem;
  --text-xl: 2rem;
  --text-2xl: 2.5rem;

  --font-sans: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  --font-display: "DM Serif Display", "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  /* The body face is an alias so one line reverts the whole site to a
     serif body (spec section 9): set it to var(--font-display). */
  --font-body: var(--font-sans);
  --font-serif: var(--font-display);
  --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

  /* Type roles (spec section 2): phone values here, tablet and desktop
     overrides in the responsive layer. Sized in rem so 200% zoom reflows. */
  --size-hero: 2.25rem;    /* 36px */
  --size-title: 1.875rem;  /* 30px */
  --size-doc: 1.5rem;      /* 24px */
  --size-sub: 1.375rem;    /* 22px */
  --size-card: 1.125rem;   /* 18px */
  --size-panel: 1.75rem;   /* 28px */
  --size-faq: 1.125rem;    /* 18px */
  --size-lede: 1.125rem;   /* 18px */
  --size-body: 1.0625rem;  /* 17px */
  --size-ui: 1rem;         /* 16px */
  --size-small: 0.875rem;  /* 14px */
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #241511;
    --paper-soft: #2D1B14;
    --card-bg: #3A241C;

    /* Body 9.08 on canvas, 8.45 on panel, 7.46 on control fill; muted
       5.97 and 5.56; heading 14.67. Softer than pure cream to avoid
       halation. Fallback if it reads dim on a real screen: #D6C9B4. */
    --heading: #F3E9D8;
    --fg: #C8B89E;
    --muted: #A89378;

    /* Inverted button pairing: dark label on light accent (6.61; 5.23 on
       the deep stop). Terracotta's white-on-dark-brand fails at 2.76. */
    --accent: #D98F63;
    --accent-strong: #C97A4C;
    --accent-contrast: #2A160D;
    --clay: #C56A3C;
    --clay-soft: rgba(197, 106, 60, 0.22);

    --border: rgba(243, 233, 216, 0.16);
    --border-strong: #8A7A6C;   /* 4.27 on canvas, 3.51 on control fill */

    --danger: #E5704F;          /* 5.68 on canvas; 5.19 on its tint */
    --danger-soft: #3D140A;
    --notice-fg: var(--accent);          /* 5.08 on the dark tint */
    --disabled: #2B1D14;

    --shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.35);
    --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.45), 0 1px 2px -1px rgb(0 0 0 / 0.35);
    --shadow-md: 0 4px 8px -2px rgb(0 0 0 / 0.45), 0 2px 4px -2px rgb(0 0 0 / 0.35);
    --glint-hi: rgba(251, 244, 231, 0.10);
    --glint-lo: rgba(0, 0, 0, 0.30);
  }
}

* { box-sizing: border-box; }

html { background: var(--bg); }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: var(--size-ui);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 400;
  line-height: 1.2;
  color: var(--heading);
  text-wrap: balance;
}

p { margin: 0 0 1em; text-wrap: pretty; }

/* DM Sans has a real 700; the display face does not, so emphasis inside
   headings and the serif aside stays the italic cut. */
strong { font-weight: 700; }
h1 strong, h2 strong, h3 strong, .name-note strong { font-weight: 400; font-style: italic; }

::selection { background: rgba(197, 106, 60, 0.3); }

a {
  color: var(--accent);
  text-decoration: none;
}
a:hover { text-decoration: underline; }
/* Inline links in prose carry an underline at rest (spec section 6): the
   accent alone is 1.47:1 against body ink, so colour cannot be the cue.
   Nav, footer and button links set their own decoration later. Link-buttons
   inside prose are excluded: buttons are never underlined. */
.page p a:not(.button), .page li a:not(.button), .auth p a:not(.button), .beta-note a, .landing p a:not(.button) { text-decoration: underline; text-underline-offset: .15em; }
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible {
  outline: 3px solid var(--clay);
  outline-offset: 3px;
}

code { font-family: var(--font-mono); font-size: .9em; }

/* Skip link (spec section 6): off-canvas until keyboard focus reaches it,
   then a small ink pill at the top-left. The wrapper it lands on takes
   programmatic focus without painting a ring around the whole page. */
.skip-link {
  position: absolute;
  top: var(--space-4);
  left: var(--space-4);
  z-index: 10;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  background: var(--heading);
  color: var(--bg);
  font-family: var(--font-sans);
  font-weight: 500;
  text-decoration: none;
  transform: translateY(-200%);
}
.skip-link:focus-visible { transform: none; }
/* Fallback for engines that drop :focus-visible in a selector list. */
.skip-link:focus { transform: none; }
#main:focus { outline: none; }

/* ---- Header (every page) ---- */

.site-header { border-bottom: 1px solid var(--border); }

/* The bar is full-bleed; its content sits on the same column as the
   sections, so the wordmark lands on the hero's left edge and the CTA on
   its right (guardrails, landing-page navbar width). 72px tall. */
.site-header__inner {
  max-width: var(--content-max);
  margin-inline: auto;
  padding: 14px var(--space-6);
  display: flex;
  align-items: center;
  justify-content: space-between;
}

/* The wordmark link wraps the inline SVG partial
   (app/views/shared/_wordmark.html.erb). Lettering is currentColor so it
   follows --heading through both schemes; the wedge stays literal clay. */
.wordmark {
  display: inline-flex;
  align-items: center;
  color: var(--heading);
}
.wordmark svg { height: 1.75rem; width: auto; display: block; }
.wordmark:hover { opacity: .8; }

/* The nav grew from one link to three plus the CTA (front door, 2026-09):
   flex + a generous gap replaces the run-on whitespace spacing, and per
   the terracotta nav rules the base links are quiet ink with terracotta
   reserved for hover; the filled CTA button keeps its own colors (scoped
   with :not so this rule cannot override .button's contrast pair). */
.site-header__nav {
  display: flex;
  align-items: center;
  gap: var(--space-8);
}
/* Nav links are quiet ink at UI size with no underline at rest (position
   is the affordance); hover and focus underline; the current page keeps
   the accent and a 2px rule (aria-current from ApplicationHelper#nav_link),
   so the active state is more than colour. Vertical padding makes every
   link a 44px target. */
.site-header__nav a { font-weight: 500; font-size: var(--size-ui); line-height: 1; }
.site-header__nav a:not(.button) {
  display: inline-block;
  padding: 14px 0;
  color: var(--heading);
  text-decoration: none;
}
.site-header__nav a:not(.button):hover,
.site-header__nav a:not(.button):focus-visible {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: .45em;
}
/* The current page keeps its 2px rule while hovered or focused: the
   hover rule's text-decoration shorthand would otherwise reset the
   thickness to the browser default. */
.site-header__nav a[aria-current="page"]:hover,
.site-header__nav a[aria-current="page"]:focus-visible { text-decoration-thickness: 2px; }
.site-header__nav a[aria-current="page"] {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: .45em;
}

/* ---- Page containers ---- */

main {
  max-width: 860px;
  margin-inline: auto;
  padding: 3rem 1.5rem 5rem;
}

/* ---- Landing ---- */

.landing {
  max-width: 720px;
  font-family: var(--font-body);
  font-size: var(--size-body);
  line-height: 1.7;
}

.landing h1 {
  margin: 0 0 var(--space-8);
  font-size: var(--size-hero);
  line-height: 1.05;
  letter-spacing: -0.015em;
  font-weight: 400;
}

.landing .lede,
.page .lede {
  font-family: var(--font-sans);
  font-size: var(--size-lede);
  line-height: 1.6;
  max-width: 70ch;
}

.cta-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin-top: var(--space-8);
}

.beta-note {
  font-family: var(--font-sans);
  font-size: var(--size-ui);
  color: var(--muted);
}

/* Sections sit 96px apart with the hairline mid-gap: 48 above the rule,
   48 below it (spec section 5). */
.landing-section {
  margin-top: var(--space-12);
  padding-top: var(--space-12);
  border-top: 1px solid var(--border);
}
/* Sibling margins collapse with the heading's 32px margin-bottom, so the grid carries the full 48px gap itself. */
.landing-section h2 + .beats,
.landing-section h2 + .today-grid,
.record-band h2 + .record-band__grid { margin-top: var(--space-12); }

.landing-section h2 {
  margin: 0 0 var(--space-8);
  font-size: var(--size-title);
  line-height: 1.05;
}

/* Numbered beats: an editorial numeral column, thin-rule rhythm --
   no icon grid, no cards. */
.beats {
  list-style: none;
  margin: 0;
  padding: 0;
}
/* Direct children only: .phrases nests its own li inside a beat, and a
   bare ".beats li" would drag those into this grid's 3rem numeral track. */
.beats > li {
  display: grid;
  grid-template-columns: 3rem 1fr;
  gap: 0 .5rem;
}
.beats > li + li { margin-top: var(--space-10); }
.beats .num {
  font-family: var(--font-mono);
  font-size: var(--size-ui);
  color: var(--accent);
  padding-top: .45rem;
}
.beats h3 {
  margin: 0 0 var(--space-2);
  font-size: var(--size-sub);
  line-height: 1.15;
}
.beats p { margin: 0 0 .75rem; max-width: 58ch; }
.beats p:last-child { margin-bottom: 0; }

/* The phrases you'd actually say -- set as quoted mono lines with a clay
   quote-rule, the pull-quote pattern in miniature. Not a mock chat UI. */
.phrases {
  list-style: none;
  margin: .35rem 0 1.1rem;
  padding: 0;
  display: grid;
  gap: .8rem;
}
.phrases li {
  font-family: var(--font-mono);
  font-size: var(--size-small);
  line-height: 1.5;
  border-left: 2px solid var(--clay);
  padding-left: 1rem;
}

.reasons h3 {
  margin: 2rem 0 .5rem;
  font-size: 1.3rem;
}
.reasons h3:first-child { margin-top: 0; }
.reasons p { max-width: 60ch; }

/* The name story -- one quiet editorial aside, not a manifesto. */
/* The one deliberate serif-body moment (spec section 2): the name aside
   keeps the display face in italic at lede size. */
.name-note {
  margin-top: var(--space-16);
  padding-top: 0;
  border-left: 3px solid var(--clay);
  padding-left: 1.5rem;
  font-family: var(--font-serif);
  font-style: italic;
  font-size: var(--size-lede);
  line-height: 1.6;
  color: var(--muted);
  max-width: 58ch;
}
.name-note p { margin: 0; }

/* ---- Landing v2 (2026-09-05 front door): the six moments. The page
   keeps the editorial register; the artifact panels are the only
   "cards", set on --paper-soft with the radius-lg role. Wide hero, but
   text measures stay capped. ---- */

.landing--v2 {
  max-width: var(--content-max);
  padding: var(--space-16) var(--space-6) var(--space-16);
  font-family: var(--font-body);
}

.hero {
  display: grid;
  grid-template-columns: minmax(0, 6fr) minmax(0, 6fr);
  gap: var(--space-12);
  align-items: center;
}
.hero__copy .lede { font-size: var(--size-lede); }
.hero__artifact { max-width: 560px; justify-self: end; width: 100%; }

.artifact {
  margin: 0;
  background: var(--paper-soft);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  box-shadow: var(--shadow-xs);
}
.artifact__chat p {
  margin: 0 0 var(--space-3);
  font-size: var(--size-ui);
  line-height: 1.55;
}
.artifact__you { color: var(--heading); }
.artifact__agent { color: var(--muted); }
/* Chip text is heading ink, not accent: accent on clay-soft-over-paper-soft composites to 4.42:1, under the 4.5 floor. */
.artifact__approve {
  display: inline-block;
  margin-left: var(--space-2);
  padding: 0 var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--clay-soft);
  color: var(--heading);
  font-family: var(--font-mono);
  font-size: var(--size-small);
  text-transform: uppercase;
  letter-spacing: .06em;
}
.artifact__journal {
  margin: var(--space-4) 0 0;
  padding: var(--space-4);
  border-top: 2px solid var(--clay);
  background: var(--bg);
  border-radius: var(--radius-xs);
  overflow-x: auto;
  font-family: var(--font-mono);
  font-size: var(--size-small);
  line-height: 1.6;
  color: var(--fg);
}
.artifact__journal code { font-size: inherit; }
.artifact__caption {
  margin-top: var(--space-3);
  font-family: var(--font-sans);
  font-size: var(--size-small);
  color: var(--muted);
}

.record-band {
  background: var(--paper-soft);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-8);
  box-shadow: var(--shadow-xs);
}
.record-band__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-12);
  align-items: start;
}
.proofs h3 { margin: var(--space-6) 0 var(--space-2); font-size: var(--size-sub); line-height: 1.15; }
.proofs h3:first-child { margin-top: 0; }
.proofs p { margin: 0; max-width: 48ch; }
.record-band__journal { margin-top: 0; }

.today-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-8);
}
.today-card {
  background: var(--paper-soft);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  box-shadow: var(--shadow-xs);
}
.today-card h3 { margin: 0 0 var(--space-2); font-size: var(--size-card); line-height: 1.25; }
.today-card p { margin: 0; }

/* Your AI, not theirs (owner ruling 2026-09-26): a contrast pair, not a
   feature grid. The category card sits on the canvas behind a hairline;
   Qunei's card takes --paper-soft and a clay rule along its top edge, a
   non-text mark, so the 3:1 graphic minimum applies (clay on cream
   3.17). Text uses the header's computed pairs on canvas and on
   --paper-soft. The pair stacks below 900px, like the today grid. */
.section-lede {
  max-width: 58ch;
  margin: 0 0 var(--space-8);
  font-size: var(--size-lede);
}
.contrast-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-8);
}
.contrast-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
}
.contrast-card h3 { margin: 0 0 var(--space-3); font-size: var(--size-card); line-height: 1.25; }
.contrast-card ul { margin: 0; padding-left: 1.2rem; }
.contrast-card li + li { margin-top: var(--space-2); }
.contrast-card--qunei {
  background: var(--paper-soft);
  border-top: 3px solid var(--clay);
  box-shadow: var(--shadow-xs);
}
.contrast-note { margin: var(--space-6) 0 0; max-width: 62ch; }

.trust-strip {
  margin-top: var(--space-6);
  font-family: var(--font-mono);
  font-size: var(--size-small);
  color: var(--muted);
}

.closing-band { text-align: center; }
.closing-band h2 { margin-bottom: var(--space-8); }
.closing-band p { max-width: 52ch; margin-inline: auto; }
.cta-row--center { justify-content: center; }

.field-error {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--size-small);
  color: var(--danger);
}
.invite-privacy { font-family: var(--font-sans); font-size: var(--size-small); }

.invite-form { max-width: var(--form-max); }

/* Section reveal: script ADDS reveal--pending (so no JS means fully
   visible), then removes it as sections enter the viewport. */
.reveal { transition: opacity .5s ease, transform .5s cubic-bezier(.16, 1, .3, 1); }
.reveal--pending { opacity: 0; transform: translateY(12px); }

/* The landing's link to the features page, under What it does today. */
.today-more { margin: var(--space-6) 0 0; }

/* ---- Features page (2026-10-12): what Qunei does today and what is on
   the way. It stands on the landing's container, hero, artifact panel,
   cards (.today-card) and closing band, and adds the announcement pill
   above the title, the status chip, the three-across feature grid, the
   bank feeds band with its route of country stops, the Inland Revenue
   before-and-after, and the grid of what else is coming. Text uses the
   computed pairs in the header: heading ink on --clay-soft (the artifact
   chip's pair), heading, muted and accent on canvas and on --paper-soft.
   The route, its stops and the clay rules are non-text marks (3:1). The
   breakpoints sit with the landing's in the responsive layer. ---- */

.announce {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  min-height: 44px;
  margin: 0 0 var(--space-8);
  padding: var(--space-2) var(--space-4) var(--space-2) var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--paper-soft);
  box-shadow: var(--shadow-xs);
  color: var(--heading);
  font-size: var(--size-small);
  font-weight: 500;
  line-height: 1.4;
  text-decoration: none;
}
.announce:hover, .announce:focus-visible { border-color: var(--clay); text-decoration: none; }
.announce:hover .announce__text, .announce:focus-visible .announce__text { text-decoration: underline; text-underline-offset: .15em; }
.announce__label {
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--clay-soft);
  font-family: var(--font-mono);
  font-size: .75rem;
  letter-spacing: .06em;
  text-transform: uppercase;
}
.announce__arrow { color: var(--accent); }

.kicker {
  margin: 0 0 var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--size-small);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--accent);
}

.say-panel__title {
  margin: 0 0 var(--space-4);
  font-family: var(--font-display);
  font-size: var(--size-card);
  line-height: 1.25;
  color: var(--heading);
}
.say-panel .phrases { margin: 0; }

.feature-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-6);
}

/* A roadmap item's place, in words: Coming soon (Now) on the clay tint,
   Up next (Next) outlined, Later in muted ink. The word carries the
   meaning; the fill only echoes it. */
.chip {
  display: inline-block;
  margin: 0 0 var(--space-3);
  padding: 2px var(--space-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  color: var(--heading);
  font-family: var(--font-mono);
  font-size: .75rem;
  line-height: 1.6;
  letter-spacing: .06em;
  text-transform: uppercase;
  white-space: nowrap;
}
.chip--soon { background: var(--clay-soft); border-color: var(--clay); }
.chip--later { color: var(--muted); border-color: var(--border); }

/* Bank feeds: a route of country stops, solid for the one coming soon
   and dashed for those further along. Below 900px it turns to run down
   the left edge (responsive layer). */
.feed-band {
  padding: var(--space-8);
  background: var(--paper-soft);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs);
}
.feed-band__lede {
  max-width: 40ch;
  margin: 0 0 var(--space-10);
  font-family: var(--font-display);
  font-size: var(--size-sub);
  line-height: 1.25;
  color: var(--heading);
}
.feed-track {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-8);
}
.feed-stop {
  position: relative;
  padding-top: var(--space-8);
  border-top: 2px solid var(--clay);
}
.feed-stop--ahead { border-top-style: dashed; }
.feed-stop::before {
  content: "";
  position: absolute;
  top: -7px;
  left: 0;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--clay);
}
.feed-stop--ahead::before { background: var(--paper-soft); border: 2px solid var(--clay); }
.feed-stop__code {
  display: block;
  margin-bottom: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--size-lede);
  font-weight: 500;
  letter-spacing: .04em;
  color: var(--accent);
}
.feed-stop h3 { margin: 0 0 var(--space-2); font-size: var(--size-card); line-height: 1.25; }
.feed-stop p { margin: 0; }
.feed-band__note { margin: var(--space-8) 0 0; font-size: var(--size-small); color: var(--muted); }

/* Filing with Inland Revenue: the item beside today's step and the next
   one, the next on --paper-soft under a clay rule, as Qunei's card is in
   the landing's contrast pair. */
.ird-band {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: var(--space-8);
  align-items: start;
  margin-top: var(--space-8);
  padding: var(--space-8);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.ird-band__copy h3 { margin: 0 0 var(--space-2); font-size: var(--size-sub); line-height: 1.15; }
.ird-band__copy p { margin: 0; max-width: 46ch; }
.ird-compare {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-4);
}
.ird-compare__step {
  padding: var(--space-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}
.ird-compare__step p { margin: 0; }
.ird-compare__step--next {
  background: var(--paper-soft);
  border-top: 3px solid var(--clay);
  box-shadow: var(--shadow-xs);
}
.ird-compare__step .ird-compare__label {
  margin-bottom: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--size-small);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted);
}
.ird-compare__step--next .ird-compare__label { color: var(--accent); }

.soon-kicker { margin-top: var(--space-12); }
.soon-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-6);
  margin-top: var(--space-4);
}
.soon-card {
  padding: var(--space-6);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.soon-card h3 { margin: 0 0 var(--space-2); font-size: var(--size-card); line-height: 1.25; }
.soon-card p { margin: 0; }
.soon-more { margin: var(--space-6) 0 0; max-width: 62ch; }

/* See it working: the two videos side by side (stacked below 900px), each
   under its title, with the player the connect card uses (.connect-video)
   and its narration written out in a disclosure. Each paragraph opens on
   its time, a link that plays the video from there; the cards' Watch it
   links do the same. Text pairs are the canvas's (accent 4.83, muted
   5.37). */
.video-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-8);
  align-items: start;
}
.video-card h3 { margin: 0 0 var(--space-4); font-size: var(--size-sub); line-height: 1.15; }
.video-card .connect-video { margin: 0; }
.video-card .connect-video video { border-radius: var(--radius-md); box-shadow: var(--shadow-sm); }
.narration { margin-top: var(--space-3); }
.narration summary {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  cursor: pointer;
  font-weight: 500;
  color: var(--accent);
}
.narration p { margin: 0 0 var(--space-3); max-width: 62ch; font-size: var(--size-ui); }
.narration__at {
  margin-right: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--size-small);
}
.today-card .watch-it { margin-top: var(--space-3); font-size: var(--size-ui); }

/* The closing band's onward links are standalone targets: 44px, like the
   footer's. */
.closing-links { margin-top: var(--space-6); }
.closing-links a { display: inline-flex; align-items: center; min-height: 44px; }

/* ---- Landing footer: two blocks on a grid (polish 2026-09-05). The
   brand block sets the boilerplate on a reading measure under the
   wordmark; the meta block (links, origin, contact) sits beside it at
   its own width. The flex-wrap row this replaces left the wordmark
   orphaned on a line of its own with the boilerplate running the full
   812px beneath it, and broke the small lines unevenly around 700px. ---- */

.landing-footer {
  border-top: 1px solid var(--border);
  font-family: var(--font-sans);
  font-size: var(--size-ui);
  color: var(--muted);
}
.landing-footer__inner {
  max-width: var(--content-max);
  margin-inline: auto;
  padding: var(--space-12) var(--space-6) var(--space-16);
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--space-8) var(--space-16);
  align-items: start;
}
.landing-footer .wordmark svg { height: 1.15rem; }
.landing-footer p { margin: 0; }
.landing-footer__brand .landing-footer__boilerplate { margin-top: .9rem; max-width: 46ch; }
/* Flush right beside the brand block, echoing the header's nav; left
   again once the blocks stack. */
.landing-footer__meta { text-align: right; }
.landing-footer__meta > * + * { margin-top: var(--space-2); }
/* Links row at UI size; the origin and contact lines are support text. */
.landing-footer__meta > p { font-size: var(--size-small); }
.landing-footer__meta > p a { font-size: var(--size-ui); }
/* Footer links: accent, no underline at rest, underline on hover and
   focus. Targets reach 44px through a minimum height, not padding
   (guardrails: underlines are for inline links). */
.landing-footer a,
.site-footer a,
.legal-nav a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--accent);
  text-decoration: none;
}
.landing-footer a:hover, .landing-footer a:focus-visible,
.site-footer a:hover, .site-footer a:focus-visible,
.legal-nav a:hover, .legal-nav a:focus-visible { text-decoration: underline; }
.landing-footer .wordmark { min-height: 0; }

/* ---- Shared site footer (every layout page; the landing opts out --
   its .landing-footer above carries the same links). Final review B1. ---- */

.site-footer {
  border-top: 1px solid var(--border);
  margin-top: var(--space-16);
  font-size: var(--size-ui);
  color: var(--muted);
}
.site-footer__inner {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: var(--space-6);
}

/* ---- Public prose pages: pricing / privacy / terms / support (SP3b D1).
   The landing's editorial serif register narrowed to a reading measure;
   section breaks are thin rules, no card chrome. .owner-input marks the
   draft [OWNER INPUT: ...] slots awaiting owner facts -- deliberately
   loud (mono + dashed clay box) so an unfilled slot cannot ship
   unnoticed. ---- */

.page {
  max-width: var(--reading-max);
  padding-top: var(--space-16);
  padding-bottom: var(--space-16);
  font-family: var(--font-body);
  font-size: var(--size-body);
  line-height: 1.7;
}
.page h1 {
  margin: 0 0 var(--space-8);
  font-size: var(--size-title);
  line-height: 1.05;
  letter-spacing: -0.01em;
}
.page h2 {
  margin: var(--space-12) 0 var(--space-5);
  padding-top: var(--space-6);
  border-top: 1px solid var(--border);
  font-size: var(--size-doc);
  line-height: 1.15;
}
.page .updated { font-family: var(--font-sans); font-size: var(--size-small); color: var(--muted); }
.page p { max-width: 62ch; }
.page ul { margin: 0 0 1.1rem; padding-left: 1.2rem; max-width: 60ch; }
.page li { margin-bottom: .6rem; }
/* A sub-heading inside a prose section (the connect guide's Team plan
   note) takes the FAQ question's rhythm. */
.page h3 { margin: var(--space-8) 0 var(--space-2); font-size: var(--size-faq); line-height: 1.3; }

/* Pricing FAQ: flat editorial Q&A, no accordion chrome; questions get
   their own rhythm inside the .page h2 section. */
.faq h3 { margin: var(--space-8) 0 var(--space-2); font-size: var(--size-faq); line-height: 1.3; }
.faq h3:first-of-type { margin-top: var(--space-4); }

.price-line {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: .5rem .75rem;
  margin: 1.5rem 0 1rem;
}
.price-figure {
  font-family: var(--font-display);
  font-size: var(--size-title);
  font-weight: 400;
  color: var(--heading);
  line-height: 1;
}
.price-terms { font-size: var(--size-lede); color: var(--heading); }

.owner-input {
  font-family: var(--font-mono);
  font-size: .8em;
  background: rgba(197, 106, 60, 0.14);
  border: 1px dashed var(--clay);
  border-radius: var(--radius-xs);
  padding: 0 .35em;
}

/* ---- Compare page (2026-09-26): the one prose page with a wide table.
   The page widens to 960px so five products sit side by side on a
   laptop, while its prose keeps the 62ch measure above. Narrower than
   the table, the table scrolls inside its own labelled region (tabindex
   0, so a keyboard can scroll it too) instead of widening the page: the
   copy-row lesson. Row labels stay pinned while the products scroll, on
   an opaque canvas so scrolled cells pass beneath them. Qunei's column
   sits on --paper-soft, whose pairs the header already computes (fg 7.54
   light, 8.45 dark). ---- */

.page--wide { max-width: 960px; }
.compare-scroll {
  overflow-x: auto;
  margin: 0 0 var(--space-8);
}
.compare-scroll:focus-visible { outline: 3px solid var(--clay); outline-offset: 3px; }
.compare-table {
  min-width: 760px;
  margin: 0;
  font-family: var(--font-sans);
}
.compare-table caption {
  caption-side: top;
  padding: 0 0 var(--space-3);
  text-align: left;
  font-size: var(--size-small);
  color: var(--muted);
}
.compare-table th,
.compare-table td {
  padding: var(--space-3);
  vertical-align: top;
  font-size: var(--size-small);
  line-height: 1.5;
}
.compare-table thead th {
  font-family: var(--font-display);
  font-size: var(--size-faq);
  font-weight: 400;
  color: var(--heading);
  text-transform: none;
  letter-spacing: 0;
}
.compare-table tbody th {
  font-weight: 600;
  color: var(--heading);
  text-transform: none;
  letter-spacing: 0;
}
.compare-table tr > :first-child {
  position: sticky;
  left: 0;
  z-index: 1;
  width: 9.5rem;
  background: var(--bg);
}
.compare-table .compare-table__qunei { background: var(--paper-soft); }

.legal-nav {
  margin-top: var(--space-16);
  padding-top: var(--space-6);
  border-top: 1px solid var(--border);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-6);
  font-family: var(--font-sans);
  font-size: var(--size-ui);
}

/* ---- Auth pages (sign in / password reset) ---- */

.auth {
  max-width: var(--auth-max);
  padding-top: var(--space-16);
}
/* Panel title (spec section 2): the smallest heading in the serif family. */
.auth h1 { font-size: var(--size-panel); line-height: 1.1; margin: 0 0 var(--space-6); }
.auth .muted-links { margin-top: var(--space-6); font-size: var(--size-ui); }

/* Auth-family cross-links are standalone targets, not inline prose: 44px. */
.muted-links a { display: inline-block; padding: 10px 0; }

/* ---- Buttons (spec 2026-09-06, section 4). Every button ships default,
   hover, pressed, focus-visible and disabled. The primary fill is
   Terracotta's sun-warmed gradient with a top-edge glint; both stops keep
   the label above 4.5:1 (header math). Labels never wrap and buttons
   never shrink. The focus ring is the site-wide rule near the top of the
   file. ---- */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 44px;
  padding: 0 22px;
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  background: linear-gradient(to bottom right, var(--accent), var(--accent-strong));
  color: var(--accent-contrast);
  font-family: var(--font-sans);
  font-size: var(--size-ui);
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
  flex-shrink: 0;
  text-decoration: none;
  cursor: pointer;
  box-shadow: var(--shadow-xs), inset var(--glint-hi) 0 6px 0 -5px, var(--glint-lo) 0 4px 10px -5px;
  transition: background 150ms ease-out, box-shadow 150ms ease-out, transform 100ms ease-out;
}
.button:hover {
  background: var(--accent-strong);
  color: var(--accent-contrast);
  text-decoration: none;
  box-shadow: var(--shadow-sm), inset var(--glint-hi) 0 6px 0 -5px, var(--glint-lo) 0 4px 10px -5px;
}
.button:active {
  background: var(--accent-strong);
  box-shadow: none;
  transform: translateY(1px);
}

.button--lg {
  min-height: 47px;
  padding: 0 26px;
  font-size: 1.0625rem;
}

.button--secondary {
  background: transparent;
  color: var(--heading);
  border-color: var(--border-strong);
  box-shadow: none;
}
.button--secondary:hover {
  background: var(--clay-soft);
  color: var(--heading);
  border-color: var(--accent);
  box-shadow: none;
}
.button--secondary:active { background: rgba(197, 106, 60, 0.20); transform: translateY(1px); }

.button--danger {
  background: transparent;
  color: var(--danger);
  border-color: var(--danger);
  box-shadow: none;
}
.button--danger:hover {
  background: var(--danger-soft);
  color: var(--danger);
  border-color: var(--danger);
  box-shadow: none;
}
.button--danger:active { background: var(--danger-soft); box-shadow: none; transform: translateY(1px); }

/* App-only compact size: still a 36px target with 8px between neighbours. */
.button--small { min-height: 36px; padding: 0 14px; font-size: var(--size-small); }

/* This sits last on purpose, to outrank every variant's hover and active rules on the specificity tie. */
.button:disabled,
.button[aria-disabled="true"],
.button[aria-busy="true"] {
  background: var(--disabled);
  color: var(--muted);
  border-color: var(--border-strong);
  box-shadow: none;
  transform: none;
  cursor: not-allowed;
}

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

.card {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--card-bg);
  box-shadow: var(--shadow-xs);
  padding: 1.5rem;
  margin-bottom: 2rem;
}
.card h2 {
  margin: 0 0 1rem;
  padding-bottom: .5rem;
  border-bottom: 1px solid var(--border);
  font-size: 1.1rem;
}
.card h3 { margin: 1.5rem 0 .5rem; font-size: .9rem; }
.card h3:first-of-type { margin-top: 0; }

.connect-block + .connect-block { margin-top: 1.5rem; }

/* The connect video (shared/_connect_video): the card's width, in its own
   proportions, showing its poster until it plays. */
.connect-video { margin: 0 0 1.5rem; }
.connect-video video {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: #EDE6DA;
}
.connect-video figcaption { margin-top: .5rem; font-size: .875rem; }

/* ---- Forms (spec 2026-09-06, section 4). Labels bind to their field at
   8px and fields sit 24px apart. Controls are sans at 16px on the control
   fill with a boundary that clears 3:1 at rest in both schemes; hover
   darkens the boundary, focus adds the clay ring and turns the border
   clay, an invalid field gets a 2px danger edge without moving anything
   (border plus a 1px ring). ---- */

.form .field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-6);
}
.form label { font-size: var(--size-ui); font-weight: 500; color: var(--heading); }
/* A tick box sits on the line of its label, and a hint beneath takes the
   full width. In the column above, the box stretched and sat centred on a
   line of its own (live run of the Akahu review, step 10). */
.form .field--check {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
}
.form .field--check > input[type="checkbox"] { width: 18px; height: 18px; margin: 0; accent-color: var(--clay); }
.form .field--check > p { flex-basis: 100%; }

input[type="text"],
input[type="email"],
input[type="password"],
select {
  font-family: var(--font-sans);
  font-size: var(--size-ui);
  line-height: 1.5;
  min-height: 48px;
  padding: 11px 14px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--card-bg);
  color: var(--fg);
  box-shadow: var(--shadow-xs);
  transition: border-color 150ms ease-out, box-shadow 150ms ease-out;
}
input::placeholder { color: var(--muted); opacity: 1; }
input[type="text"]:hover,
input[type="email"]:hover,
input[type="password"]:hover,
select:hover { border-color: var(--fg); }
input[type="text"]:focus-visible,
input[type="email"]:focus-visible,
input[type="password"]:focus-visible,
select:focus-visible { border-color: var(--clay); }
/* A field that failed validation is marked on the control itself
   (ApplicationHelper#invalid_field_attrs) with its message beneath, so
   "check the highlighted field" is true and colour is not the only cue. */
input[aria-invalid="true"], input[aria-invalid="true"]:hover, input[aria-invalid="true"]:focus-visible { border-color: var(--danger); box-shadow: 0 0 0 1px var(--danger); }
input[type="text"]:disabled,
input[type="email"]:disabled,
input[type="password"]:disabled,
select:disabled { background-color: var(--disabled); color: var(--muted); cursor: not-allowed; }

/* Native selects lose the browser arrow for a chevron with a balanced
   12px inset; trailing padding reserves room for it (guardrails). The
   stroke colours are literal copies of --fg for each scheme and must
   move with it. */
select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 40px;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><path d='M4 6l4 4 4-4' fill='none' stroke='%235C4938' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 16px 16px;
}
@media (prefers-color-scheme: dark) {
  select {
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><path d='M4 6l4 4 4-4' fill='none' stroke='%23C8B89E' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  }
}

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

table { width: 100%; border-collapse: collapse; margin-bottom: 1rem; }
th, td {
  text-align: left;
  padding: .55rem .6rem;
  border-bottom: 1px solid var(--border);
  font-size: var(--size-ui);
}
th { color: var(--muted); font-weight: 600; font-size: var(--size-small); text-transform: uppercase; letter-spacing: .03em; }

/* Cell fit: a short value (an access level, a date) stays on one line,
   a long one (an email) may break anywhere so the table fits its card,
   and an action cell hugs its button at the row's right edge. Without
   these the account page's connections table ran its Disconnect
   buttons past the card's edge (owner screenshot, 12 October 2026). */
td.cell-nowrap { white-space: nowrap; }
td.cell-break { overflow-wrap: anywhere; }
td.cell-action { width: 1%; text-align: right; white-space: nowrap; }

/* Stacked tables: a list table too wide for a narrow screen stacks below
   900px, one block per row, each cell under its own label. The label is
   the cell's data-label, which repeats its column header (an action
   cell under an empty header carries none). The admin dashboard's
   ruling first (a browser check found its seven-column table overflows
   or cramps below 900px); the account page's ledgers, connections and
   tokens tables joined on 1 October 2026, when a 375px phone showed
   that page 784px wide. Stacked, a long value such as an email may
   break anywhere, so it cannot push a phone's page wide; a full-width
   table keeps its words whole. */
@media (max-width: 899px) {
  .stack-table thead { display: none; }
  .stack-table, .stack-table tbody, .stack-table tr, .stack-table td { display: block; width: 100%; }
  .stack-table tr { padding: var(--space-3) 0; border-bottom: 1px solid var(--border); }
  .stack-table td { padding: var(--space-1) 0; border-bottom: 0; overflow-wrap: anywhere; }
  .stack-table td.cell-action { width: 100%; text-align: left; }
  .stack-table td::before {
    content: attr(data-label);
    display: block;
    color: var(--muted);
    font-size: var(--size-small);
    font-weight: 600;
    letter-spacing: .03em;
    text-transform: uppercase;
  }
}

/* ---- Badges ---- */

.badge {
  display: inline-block;
  padding: .15rem .55rem;
  border-radius: 99px;
  border: 1px solid;
  font-size: .75rem;
  font-weight: 600;
}
.badge--active { color: var(--accent); border-color: var(--accent); }
.badge--revoked { color: var(--muted); border-color: var(--border); }
/* SP3b D2: the ledgers card's archived tag + greyed row -- archived books
   stay listed for the owner (append-only ethos, the bill went down) but
   read as past tense. */
.badge--archived { color: var(--muted); border-color: var(--border); }
tr.ledger-row--archived td { color: var(--muted); }

/* SP3b D2: the billing card's grace warning -- danger-toned text inside
   the card, matching .flash--alert's color voice without its box. */
.billing-warning { color: var(--danger); }

/* ---- Code / connect blocks ---- */

.code-line {
  display: block;
  overflow-x: auto;
  white-space: pre;
  padding: .6rem .75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg);
  font-size: var(--size-small);
}

.copy-row { display: flex; gap: .5rem; align-items: flex-start; }
.copy-row .code-line { flex: 1; }

/* Connect guide (2026-09-24): Claude's numbered connector steps, the one
   list the public guide, the connect page and the dashboard card share.
   The address sits inside its step, just under the words. The skill
   steps (the connect page's Claude Free door and the guide's skills
   section) share the look. */
.connect-steps, .skill-steps { margin: 0 0 1.1rem; padding-left: 1.4rem; max-width: 60ch; }
.connect-steps li, .skill-steps li { margin-bottom: .6rem; }
.connect-steps .copy-row { margin-top: .5rem; }

/* ---- Flash / callouts ---- */

/* Flashes take Terracotta's alert shape: soft tint, subtle border, strong
   text. Text is the second channel beside colour. */
.flash {
  margin-bottom: var(--space-6);
  padding: var(--space-4);
  border: 1px solid;
  border-radius: var(--radius-md);
  font-family: var(--font-sans);
  font-size: var(--size-ui);
}
.flash--notice { color: var(--notice-fg); background: var(--clay-soft); border-color: var(--clay); border-color: color-mix(in srgb, var(--clay) 40%, transparent); }
.flash--alert { color: var(--danger); background: var(--danger-soft); border-color: var(--danger); border-color: color-mix(in srgb, var(--danger) 40%, transparent); }

.callout--token {
  margin-bottom: 2rem;
  padding: 1rem 1.25rem;
  border: 1px solid var(--accent);
  border-radius: var(--radius-md);
}
.callout--token p { margin: 0 0 .6rem; }
.callout--token p:last-child { margin-bottom: 0; }

.muted { color: var(--muted); }

/* The skills' downloads, in the connect page's Claude Free door. The upload
   steps follow straight after, so the list keeps the steps' own gap. */
.skills-list { list-style: none; margin: 0 0 1.1rem; padding: 0; display: grid; gap: .5rem; }

/* The invite form's honeypot: visually gone, still in the DOM for bots.
   display:none is deliberate -- screen readers must skip it too (the
   wrapper is aria-hidden as well). */
.hp { display: none; }

/* Words for a screen reader only: off the page for the eye, still in the
   accessibility tree, unlike .hp's display:none and visibility:hidden,
   which take them away from a screen reader too. The dashboard's F and U
   carry their word this way (Dash M9: an abbr's title is not reliably
   spoken). Taken out of flow at one pixel and clipped, so it neither
   moves the layout nor shows. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---- Responsive ---- */

/* Role sizes step up at tablet and desktop (spec section 2). */
@media (min-width: 640px) {
  :root {
    --size-hero: 2.75rem;    /* 44px */
    --size-title: 2.25rem;   /* 36px */
    --size-doc: 1.625rem;    /* 26px */
    --size-sub: 1.625rem;    /* 26px */
    --size-card: 1.25rem;    /* 20px */
    --size-panel: 2rem;      /* 32px */
    --size-lede: 1.25rem;    /* 20px */
  }
}

@media (min-width: 1024px) {
  :root {
    --size-hero: 3.75rem;    /* 60px */
    --size-title: 2.75rem;   /* 44px */
    --size-doc: 1.75rem;     /* 28px */
    --size-sub: 1.75rem;     /* 28px */
  }
}

/* Rhythm steps down on tablet and phone; tiers keep their ratios. */
@media (max-width: 1023px) {
  .landing--v2 { padding-top: var(--space-12); padding-bottom: var(--space-12); }
  .page { padding-top: var(--space-12); padding-bottom: var(--space-12); }
  .landing-section { margin-top: var(--space-8); padding-top: var(--space-8); }
  .landing-section h2 + .beats,
  .landing-section h2 + .today-grid,
  .record-band h2 + .record-band__grid { margin-top: var(--space-10); }
  .beats > li + li { margin-top: var(--space-8); }
  .page h2 { margin-top: var(--space-10); margin-bottom: var(--space-4); }
  .landing-footer__inner { padding: var(--space-10) var(--space-6) var(--space-12); }
  .feature-grid, .soon-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* The journal lines are byte-fixed (serializer format) and hold 14px at
   every width (spec section 2). Beside the hero copy they fit only from
   1120px up (the column holds 448px of text against a 422px longest
   line at 1152), so the hero and record band stack below that. */
@media (max-width: 1119px) {
  .hero { grid-template-columns: minmax(0, 1fr); gap: var(--space-8); }
  .hero__artifact { justify-self: stretch; max-width: none; }
  .record-band__grid { grid-template-columns: minmax(0, 1fr); gap: var(--space-8); }
}

@media (max-width: 899px) {
  .today-grid { grid-template-columns: minmax(0, 1fr); }
  .contrast-grid { grid-template-columns: minmax(0, 1fr); gap: var(--space-6); }
  /* The feeds route runs down the left edge, one stop under another. */
  .feed-track { grid-template-columns: minmax(0, 1fr); }
  .feed-stop { padding: 0 0 0 var(--space-6); border-top: 0; border-left: 2px solid var(--clay); }
  .feed-stop--ahead { border-left-style: dashed; }
  .feed-stop::before { top: 0; left: -7px; }
  .ird-band { grid-template-columns: minmax(0, 1fr); }
  .video-grid { grid-template-columns: minmax(0, 1fr); gap: var(--space-10); }
}

@media (max-width: 719px) {
  .landing-footer__inner { grid-template-columns: minmax(0, 1fr); gap: 1.75rem; }
  .landing-footer__meta { text-align: left; }
  /* The header's Features link yields below 720px: beside the CTA it
     needs 660px to hold one line, and on a phone the nav already fills
     the line (measured 2026-10-12). Same specificity as the nav's link
     rule above, and later, so it wins. */
  .site-header__nav a.site-header__roomy { display: none; }
}

@media (max-width: 639px) {
  main { padding: 2rem var(--space-5) 3rem; }
  .landing--v2 { padding: var(--space-8) var(--space-5) var(--space-10); }
  .page { padding: var(--space-10) var(--space-5); }
  .landing h1, .landing-section h2, .page h1 { margin-bottom: var(--space-6); }
  .landing-section h2 + .beats,
  .landing-section h2 + .today-grid,
  .record-band h2 + .record-band__grid { margin-top: var(--space-8); }
  .landing-section { margin-top: var(--space-6); padding-top: var(--space-6); }
  .hero { gap: var(--space-6); }
  .record-band__grid { gap: var(--space-6); }
  .beats > li { grid-template-columns: 2.25rem 1fr; }
  /* Stacked, the code line takes the row's width and scrolls inside it:
     with flex-start alignment a long unbreakable line (the Claude Code
     command) sized the box to itself and pushed the whole page wider
     than a phone. The button keeps its own width. */
  .copy-row { flex-direction: column; align-items: stretch; }
  .copy-row .button { align-self: flex-start; }
  /* The pinned row labels narrow so a label, Qunei's column and the
     start of the next product share a phone's width. */
  .compare-table tr > :first-child { width: 6.5rem; }
  .compare-table th, .compare-table td { padding: var(--space-2); }
  .record-band { padding: var(--space-6); }
  .today-grid { gap: var(--space-6); }
  .feature-grid, .soon-grid, .ird-compare { grid-template-columns: minmax(0, 1fr); }
  .feed-band, .ird-band { padding: var(--space-6); }
  .feed-band__lede { margin-bottom: var(--space-8); }
  .page h2 { margin-top: var(--space-8); }
  .landing-footer__inner { padding: var(--space-8) var(--space-5) var(--space-10); }
  .site-header__inner { padding-inline: var(--space-5); }
  .site-header__nav { gap: var(--space-6); }
  /* The header CTA yields on phones: the hero and pricing CTAs carry the
     intent, and the nav must hold one line. */
  .site-header__nav .button { display: none; }
}

/* This sits last so its transition and transform resets outrank every
   earlier state rule the cascade would otherwise let win. */
@media (prefers-reduced-motion: reduce) {
  .reveal { transition: none; }
  .button, input, select { transition: none; }
  .button:active, .button--secondary:active, .button--danger:active { transform: none; }
}

/* SP5 Task 9: the file input the upload form's .field needs -- the
   shared input rule list above (:897-912) covers text/email/password/
   select only, and a file input takes none of that styling natively, so
   it gets its own small rule pair here rather than joining that list. */
.form input[type="file"] { min-height: 48px; padding: 11px 14px; border: 1px solid var(--border-strong); border-radius: var(--radius-md); font: inherit; background: var(--card-bg); }
.form input[type="file"]:focus-visible { border-color: var(--clay); }

/* ---- Admin dashboard (spec 2026-09-26, section 10): the owner's
   read-only activity page. A verdict badge always carries its word, at
   the 14px floor; Warning keeps its word in ink because --clay is a
   non-text token (plan R2). List tables stack below 900px, each cell
   under its own label: a controller ruling after a browser check found
   that below that width the seven-column table overflows or cramps.
   That stacking comes from spec 10 ("Phone") plus the ruling; plan R8
   is only the 16px card padding below 640px. The admin page takes the
   site's content width, not the 860px reading width. The stacking
   itself is .stack-table, under Tables, which the account page has
   shared since 1 October 2026. ---- */

main.admin { max-width: var(--content-max); }

.admin-verdict {
  display: inline-block;
  margin-right: var(--space-2);
  padding: .1rem .55rem;
  border: 1px solid currentColor;
  border-radius: var(--radius-pill);
  font-size: var(--size-small);
  font-weight: 600;
  line-height: 1.5;
}
.admin-verdict--ok { color: var(--accent); }
.admin-verdict--warning { color: var(--heading); border-color: var(--clay); background: var(--clay-soft); }
.admin-verdict--problem { color: var(--danger); background: var(--danger-soft); }
.admin-verdict--not-set-up { color: var(--muted); border-color: var(--border); }

.admin-health { margin: 0; }
.admin-health__row { padding: var(--space-3) 0; border-bottom: 1px solid var(--border); }
.admin-health__row:last-child { border-bottom: 0; }
.admin-health dt { margin-bottom: var(--space-1); color: var(--heading); font-weight: 600; }
.admin-health dd { margin: 0; }
.admin-health__detail { display: block; margin-top: var(--space-1); }
.admin-failed { margin: var(--space-2) 0 0; padding-left: 1.25rem; }

.admin-numbers thead th { text-align: right; }
.admin-numbers tbody th { color: var(--fg); font-size: var(--size-ui); font-weight: 500; letter-spacing: 0; text-transform: none; }
.admin-numbers td { text-align: right; font-variant-numeric: tabular-nums; }
/* The tagged-link card's ?ref= stays on one line on a phone. */
#tagged-links code { white-space: nowrap; }

.admin-progress { margin: 0; padding: 0; list-style: none; }
.admin-list th, .admin-list td { vertical-align: top; }
.admin-list td { overflow-wrap: anywhere; }

.admin .form + .button_to { margin-top: var(--space-4); }

@media (max-width: 639px) {
  .admin .card { padding: var(--space-4); }
  .admin-numbers th, .admin-numbers td { padding: .45rem .3rem; }
}

/* ROADMAP-FORWARD B4: the drafts page (app/views/app/drafts). One
   article per waiting draft, its lines in the shared table style, and
   its Approve and Discard side by side (button_to and form_with each
   render their own form, so both forms sit inline). */
.draft { padding-block: var(--space-4); border-top: 1px solid var(--border); }
.draft:first-of-type { border-top: 0; }
.draft h3 { margin-bottom: var(--space-2); }
.draft .actions { display: flex; gap: var(--space-3); align-items: center; margin-top: var(--space-3); }
.draft .actions form { margin: 0; }
#approve-all { margin-bottom: var(--space-4); }

/* Expense claims: the claims page (app/views/app/claims). Each claim is a
   .draft article; its Decline carries a reason box, so the actions wrap on
   a phone. The form's date input takes the file input's look. */
.claim .actions { flex-wrap: wrap; }
.claim-decline { display: flex; gap: var(--space-2); align-items: center; flex-wrap: wrap; }
.claim-decline input[type="text"] { max-width: 100%; }
.form input[type="date"] { min-height: 48px; padding: 11px 14px; border: 1px solid var(--border-strong); border-radius: var(--radius-md); font: inherit; background: var(--card-bg); color: var(--fg); }
.form input[type="date"]:focus-visible { border-color: var(--clay); }

/* The Qunei plugin (2026-09-26): the plugin steps share the connector
   steps' look, and the connect surfaces' Advanced blocks (personal access
   tokens) open on a click. */
.plugin-steps { margin: 0 0 1.1rem; padding-left: 1.4rem; max-width: 60ch; }
.plugin-steps li { margin-bottom: .6rem; }
.plugin-steps .copy-row { margin-top: .5rem; }
.dashboard details > summary { cursor: pointer; font-weight: 600; }
.dashboard details[open] > summary { margin-bottom: .75rem; }

/* Plan R30: the words after a copy box stand clear of it (a box inside a numbered step ends its step, so those keep their look). */
.copy-row + p, .card .copy-row + h3 { margin-top: 1rem; }

/* Live dashboards (docs/specs/2026-10-02-fixed-reports-live-dashboards-
   design.md): one card per tile, as many across as fit, each a small
   table whose figures sit right-aligned in tabular numerals, the way a
   statement prints them; the total row is set heavier with a rule above. */
.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr));
  gap: var(--space-4);
  align-items: start;
}
.tiles .tile { margin-bottom: 0; }
/* The budget tile's table (account, actual, budget, variance) doesn't fit
   in half the row once amounts reach six figures, and figures never wrap,
   so it takes the whole row. */
.tiles .tile--budget { grid-column: 1 / -1; }
.tile-table { margin-bottom: 0; }
.tile-table th.tile-figure, .tile-table td.tile-figure {
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums lining-nums;
}
.tile-table td.tile-figure { font-family: var(--font-mono); }
.tile-table tr.tile-total td { font-weight: 600; border-top: 1px solid var(--border-strong); }
.tile-note { margin: var(--space-2) 0 0; font-size: var(--size-small); }
/* Dash M10: a tile's table scrolls inside its own labelled box (tabindex
   0, so a keyboard can scroll it too, as on the compare page) instead of
   widening the page: the budget tile's four columns of six-figure amounts
   do not fit a phone, and figures never wrap. The box is positioned so it
   is the containing block of the table's .visually-hidden words: left to
   the page, those sit where the table's far edge is and widen the page on
   their own. */
.tile-scroll {
  position: relative;
  overflow-x: auto;
}
.tile-scroll:focus-visible { outline: 3px solid var(--clay); outline-offset: 3px; }

/* A ledger's own brand on its live dashboard (the owner's ruling on server
   rule 5, final review Minor 6). Once the ledger's brand kit is set, the
   page's head carries the brand block every rendered report carries
   (BrandKit.css: the --brand-* properties, the kit's values over the
   reports' own defaults), and the dashboard reads the kit from it, as a
   report does. The site's tokens are re-pointed inside the dashboard only,
   so every rule that reads them there (headings, tiles, tables, links, the
   choice button, a notice, the focus ring) takes the kit's colour or face,
   while the header, the footer and every other page keep Qunei's look.
   Headings take the primary colour, as qunei-reports maps a dashboard's.
   Links take it too rather than the accent: a report prints its accent
   only as a rule, so a kit's accent need not read as text, and a notice is
   set in ink on a tint of it for the same reason. The sheet is the kit's
   paper in either scheme, as a report is. */
.ledger-dashboard--branded {
  --card-bg: var(--brand-paper);
  --heading: var(--brand-primary);
  --fg: var(--brand-ink);
  --muted: var(--brand-muted);
  --accent: var(--brand-primary);
  --accent-strong: var(--brand-primary);
  --accent-contrast: var(--brand-paper);
  --clay: var(--brand-accent);
  --clay-soft: color-mix(in srgb, var(--brand-accent) 14%, var(--brand-paper));
  --notice-fg: var(--brand-ink);
  --border: var(--brand-rule);
  --border-strong: var(--brand-ink);
  --font-display: var(--brand-font-heading);
  --font-sans: var(--brand-font-body);
  --font-mono: var(--brand-font-numeric);
  /* Centred like any page, and inset from a phone's edges, where the
     sheet would otherwise run its border along the screen's. */
  margin: var(--space-8) max(var(--space-3), calc((100% - 860px) / 2));
  border: 1px solid var(--brand-rule);
  border-radius: var(--radius-md);
  background: var(--brand-paper);
  color: var(--brand-ink);
  font-family: var(--brand-font-body);
}
/* The masthead, a report's: the logo, then the name in the heading face
   and the primary colour, closed by a 2px rule in the primary colour. */
.ledger-brand {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3) var(--space-4);
  margin: 0 0 var(--space-6);
  padding: 0 0 var(--space-4);
  border-bottom: 2px solid var(--brand-primary);
}
.ledger-brand .brand-logo { display: block; width: auto; height: auto; max-width: min(100%, 240px); max-height: 56px; }
.ledger-brand .brand-name {
  margin: 0;
  font-family: var(--brand-font-heading);
  font-size: var(--size-sub);
  line-height: 1.2;
  color: var(--brand-primary);
  overflow-wrap: anywhere;
}
/* The one-line branding choice, for a person who may change the dashboard. */
.dashboard-brand-choice { margin: 0 0 var(--space-6); }

/* The Akahu logo beside the consumer information on Bank feeds
   (app/views/app/bank_feeds). A class, not a style attribute: the
   Content-Security-Policy's style-src 'self' drops inline styles. */
.akahu-logo { vertical-align: middle; margin-right: .5rem; }
