/* ============================================================================
   CatchLoop design system — "Cobalt". Single source of truth (served by the API,
   imported by the dashboard). Atomic utilities + a few components.
   Self-hosted Archivo (GDPR: no Google CDN). Body = system font, display = Archivo.
   ========================================================================== */

/* Self-hosted Archivo (variable, 700–900). Relative url resolves both when the
   API serves /public/catchloop.css and when Vite bundles it into the dashboard. */
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 700 900;
  font-display: swap;
  src: url("fonts/archivo-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 700 900;
  font-display: swap;
  src: url("fonts/archivo-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02AF, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF;
}

/* 1 · TOKENS ---------------------------------------------------------------
   Direction A "Linear" (design/rebrand/tokens-a.css): neutral surfaces,
   near-black titles, grey metadata, cobalt for the primary action, the
   selection and the focus ring. Same property names as before, new values.
   The portal restates its own copy under body.portal further down. */
:root {
  --ink: #202124;
  --ink-2: #303238;
  --muted: #60646c;
  /* Was #a5a5ae (2.44:1). Every grey that carries text is now AA on white. */
  --faint: #60646c;
  --cobalt: #2743d6;
  --cobalt-dark: #1d33ac;
  --cobalt-hover: #1d33ac;
  --cobalt-alt: #2743d6;
  /* Text tints, legible on --canvas. Only the dark bands use them, and under A
     they are neutral: the one coloured word on a dark band is the hero's
     emphasis, which keeps its own value (see .hero-h1-mark). */
  --cobalt-tint: #c5c7cd;
  --ink-tint: #c5c7cd;
  --vote-tint: #b7d4c5;
  --canvas: #151618;
  --paper: #ffffff;
  --white: #ffffff;
  --tint: #f7f7f8;
  --tint-2: #ecedef;
  --tint-selected: #eef1ff;
  --nav-bg: #f7f7f8;
  /* --line: surface edges and every secondary control. --line-2: fields only,
     dark enough that an empty input reads as one on white. --line-3: rows. */
  --line: #e4e5e7;
  --line-2: #8b8f97;
  --line-3: #ecedef;
  --vote: #28624f;
  --vote-bg: #f0f6f3;
  --vote-border: #adc8bb;
  --vote-arrow: #28624f;
  /* Solid green: the filled "you voted" state, and the VoteRow hover border. */
  --vote-solid: #28624f;
  --vote-sep: #d8e5dd;
  /* Status signs, one per `tone` of the status table in @catchloop/shared.
     The text and fill pairs live with the pills that use them. */
  --st-new: #677281;
  --st-planned: #4f6d8e;
  --st-in-progress: #97733a;
  --st-shipped: #4d805a;
  --st-not-planned: #72757b;
  --status-bg: #f3f3f4;
  --surface-2: #fafafb;
  --disabled: #c5c7cc;
  --anon-border: #a2a6ae;
  --anon-ink: #60646c;
  --avatar-bg: #ecedef;
  --avatar-text: #4e525a;
  --team-bg: #f0f6f3;
  --team-border: #d8e5dd;
  --danger: #b3261e;
  --danger-border: #e6b4b0;

  /* Items, buttons and fields 6; cards, surfaces and modals 8. */
  --r: 6px;
  --r-md: 6px;
  --r-lg: 8px;
  --r-xl: 8px;
  --r-full: 99px;

  --wrap: 1200px;

  /* One vertical rhythm for the whole site. Section padding and the gaps inside
     a section both come from this scale, so nothing is spaced by feel. */
  --sp-1: 8px;
  --sp-2: 16px;
  --sp-3: 24px;
  --sp-4: 32px;
  --sp-5: 48px;
  --sp-6: 64px;
  --sp-7: 96px;

  /* Docs shell columns. */
  --doc-nav: 250px;
  --doc-toc: 220px;

  /* One control height for every button / input / select (homogeneity). */
  --control-h: 34px;
  --control-h-sm: 28px;
  --control-h-lg: 44px;

  --display: "Archivo", system-ui, sans-serif;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, "SF Mono", Menlo, monospace;
}

/* 2 · BASE ----------------------------------------------------------------- */
* { box-sizing: border-box; }
/* L'attribut `hidden` du navigateur ne pose qu'un `display: none` de feuille de
   style utilisateur-agent : n'importe quelle règle qui déclare un `display` le
   bat, et `.m-btn` en déclare un. Sans ceci, masquer un élément en JS ne fait
   rien du tout, en silence — ce qui est précisément ce qui vient d'arriver à la
   barre marketing, où les deux états s'affichaient côte à côte. */
[hidden] { display: none !important; }
html, body { min-height: 100%; }
body { margin: 0; background: var(--paper); color: var(--ink); font-family: var(--sans); font-size: 15px; line-height: 1.6; -webkit-font-smoothing: antialiased; }
/* Portal sticky-footer column (scoped — the dashboard shares this stylesheet).
   #root holds nav + content + footer; .wrap grows so the badge sits at the bottom. */
/* The seal is pushed down by `margin-top: auto` inside .screen, which only has
   somewhere to push it if the column is at least a screen tall. Without this
   the powered-by badge rode up under the content on every short page (the 404,
   the suggest confirmation, an empty board). */
body.portal { display: flex; flex-direction: column; min-height: 100vh; }
/* Phones whose toolbars grow and shrink: 100vh is the largest viewport there,
   so it hangs the footer below the fold until you scroll. dvh is the live one. */
@supports (min-height: 100dvh) { body.portal { min-height: 100dvh; } }
body.portal #root { flex: 1; display: flex; flex-direction: column; }
a { color: inherit; text-decoration: none; }
a:hover { color: var(--cobalt-dark); }
::selection { background: var(--cobalt); color: var(--white); }
/* Direction A: one focus ring everywhere, cobalt, 2px, 3px clear of the edge. */
:focus-visible { outline: 2px solid var(--cobalt); outline-offset: 3px; }
/* Skip link: off-screen until focused, then the first stop Tab reaches. Reaching
   the first request used to cost ~18 tabs. */
.skip { position: absolute; left: -9999px; top: 0; z-index: 10; }
.skip:focus { left: 12px; top: 12px; padding: 9px 14px; border-radius: 8px;
              background: #fff; color: var(--ink); font-size: 13px; font-weight: 650; }
@media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } }
/* Archivo on headings is a marketing exception to direction A, kept on purpose:
   the site's display type is the brand's voice. The portal opts out below
   (body.portal), because a customer's board is a product surface, not ours. */
h1, h2, h3, h4 { font-family: var(--display); font-weight: 800; letter-spacing: -0.025em; line-height: 1.15; margin: 0; }
h1 { font-size: 32px; }
h2 { font-size: 22px; }
h3 { font-size: 17px; }

/* 3 · ATOMIC UTILITIES ----------------------------------------------------- */
.mt-6 { margin-top: 44px; }
.text-faint { color: var(--faint); }

/* 4 · COMPONENTS ----------------------------------------------------------- */
/* Layout shell */
.wrap { max-width: var(--wrap); margin: 0 auto; padding: 0 32px; flex: 1; width: 100%; box-sizing: border-box; }
/* One side margin per breakpoint for the whole marketing site: 32px, then 20px
   on a phone. The bar, the bands and the footer below follow the same step, so
   every left edge on a page lines up. */
@media (max-width: 800px) {
  .wrap { padding: 0 20px; }
}
main { padding: 44px 0 56px; }
/* Pages built from full-bleed bands: each band carries its own padding, so main
   must not add any or a strip of --paper shows above the first band. */
main.bands { padding: 0; }

/* Buttons — same height as inputs/selects via --control-h. */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; height: var(--control-h); padding: 0 14px; font: inherit; font-family: var(--sans); font-weight: 600; font-size: 13px; line-height: 1; border: 1px solid transparent; border-radius: var(--r); cursor: pointer; background: none; color: var(--ink); white-space: nowrap; }

/* Header / nav */
/* Full-bleed bar, content aligned to the body column (--wrap). The side padding
   grows so inner content lines up with .wrap; background/border still span edge to edge. */
.site-header { display: flex; align-items: center; justify-content: space-between; min-height: 64px; padding: 0 max(32px, calc((100% - var(--wrap)) / 2 + 32px)); border-bottom: 1px solid var(--line-3); background: var(--white); gap: 20px; }

/* Marketing only: the portal shares .site-header and must not become sticky. */
.site-header--sticky { position: sticky; top: 0; z-index: 10; }
.logo { width: 24px; height: 24px; border-radius: var(--r); background: var(--cobalt); color: var(--white); display: inline-flex; align-items: center; justify-content: center; font-family: var(--display); font-size: 12px; font-weight: 800; }
.logo-img { object-fit: cover; background: none; }
.brand { font-weight: 600; font-size: 14px; }
.site-brand { display: inline-flex; align-items: center; gap: 9px; flex: none; color: var(--ink); }
.site-brand .logo { width: 26px; height: 26px; }
.site-brand .brand { font-size: 15px; font-weight: 700; }
.nav { display: flex; gap: 4px; align-items: center; font-size: 13px; font-weight: 500; flex-wrap: wrap; }
.nav--desktop { flex-wrap: nowrap; }
.nav--desktop > .m-btn { margin-left: 8px; }
.nav-link { color: var(--ink-2); padding: 7px 12px; border-radius: var(--r); }
.nav-link:hover { background: var(--tint); color: var(--ink); }
/* Direction A selection: a light cobalt wash with cobalt text. */
.nav-link.active { background: var(--tint-selected); color: var(--cobalt); }

/* Resources menu. A bare <details>: it opens without JavaScript, which is the
   rule the public portal already lives under and costs nothing to keep here.
   The panel is absolute so opening it never reflows the sticky bar. */
.nav-menu { position: relative; }
.nav-menu > summary { list-style: none; cursor: pointer; user-select: none; }
.nav-menu > summary::-webkit-details-marker { display: none; }
.nav-menu > summary::after { content: "\25BE"; margin-left: 5px; font-size: 10px; color: var(--muted); }
.nav-menu > summary:focus-visible { outline: 2px solid var(--cobalt); outline-offset: 3px; }
.nav-menu-panel { position: absolute; top: calc(100% + 6px); left: 0; z-index: 20; min-width: 176px; display: flex; flex-direction: column; gap: 2px; padding: 8px; background: var(--white); border: 1px solid var(--line); border-radius: var(--r-lg); box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); }
.nav-menu-panel a { padding: 8px 12px; border-radius: var(--r); color: var(--ink-2); font-size: 13px; }
.nav-menu-panel a:hover { background: var(--tint); color: var(--ink); }
.nav-menu-panel a[aria-current="page"] { background: var(--tint-selected); color: var(--cobalt); font-weight: 500; }

/* Phone menu. A <details> like Resources, so it opens with JavaScript off; the
   panel floats over the page from the right edge of the bar. Hidden above the
   breakpoint, where the desktop links fit on one line. */
.nav-mobile { display: none; position: relative; }
.nav-mobile > summary { list-style: none; user-select: none; }
.nav-mobile > summary::-webkit-details-marker { display: none; }
.nav-mobile-icon { fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; }
.nav-mobile[open] > .nav-mobile-btn { background: var(--tint); border-color: var(--line-2); }
.nav-mobile-panel { position: absolute; top: calc(100% + 8px); right: 0; z-index: 20; width: 260px; max-width: calc(100vw - 40px); max-height: calc(100vh - 88px); overflow-y: auto; display: flex; flex-direction: column; gap: 2px; padding: 8px; background: var(--white); border: 1px solid var(--line); border-radius: var(--r-lg); box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); }
.nav-mobile-panel > a:not(.m-btn) { display: flex; align-items: center; min-height: 44px; padding: 0 12px; border-radius: var(--r); font-size: 15px; color: var(--ink-2); }
.nav-mobile-panel > a:not(.m-btn):hover { background: var(--tint); color: var(--ink); }
.nav-mobile-panel > a[aria-current="page"] { background: var(--tint-selected); color: var(--cobalt); }
.nav-mobile-panel > .m-btn { margin-top: 8px; width: 100%; }
@media (max-width: 800px) {
  .site-header { min-height: 60px; padding: 0 20px; }
  .nav--desktop { display: none; }
  .nav-mobile { display: block; }
}

/* Cards. Direction A: a hairline edge and no shadow; only what floats casts one. */
.card { background: var(--white); border: 1px solid var(--line); border-radius: var(--r-lg); }
.card-pad { padding: 24px; }

/* Comments */
.comment { padding: 16px 0; border-bottom: 1px solid var(--line-3); }

/* Forms: one homogeneous style for every field, by element, so an unclassed
   input, textarea or select (custom chevron) looks like any other. */
input[type="text"], input[type="email"], input[type="search"], input[type="password"], textarea, select {
  font: inherit; font-family: var(--sans); font-size: 14px; width: 100%; height: var(--control-h); padding: 0 12px;
  border: 1px solid var(--line-2); border-radius: var(--r-md); background: var(--white); color: var(--ink);
}
/* Textarea is multi-line: no fixed height. */
textarea { height: auto; min-height: 88px; padding: 9px 12px; resize: vertical; }
input:focus, textarea:focus, select:focus {
  border-color: var(--cobalt); outline: 2px solid var(--cobalt); outline-offset: 3px;
}
input::placeholder, textarea::placeholder { color: var(--faint); }
textarea { min-height: 88px; resize: vertical; }
select {
  appearance: none; -webkit-appearance: none; padding-right: 34px; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%2367648b' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='m6 9l6 6l6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 10px center;
}

/* Pricing — decision page. Free and Pro get the same box size on purpose: the
   free plan is uncapped, not a crippled teaser. Each card is three bands, head
   / list / action, so the price, what you get and what it costs to say yes each
   sit on the same line across both cards however long the lists are. */
.plans { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); align-items: stretch; max-width: 920px; margin: 0 auto; }
.plan { background: var(--white); border: 1px solid var(--line); border-radius: var(--r-lg); display: flex; flex-direction: column; overflow: hidden; }
/* The paid card is marked by its edge and its tag, the same cobalt as a
   selection, and by nothing louder: no shadow, no tinted band. */
.plan--featured { border-color: var(--cobalt); }
.plan-top { padding: 26px 30px 22px; border-bottom: 1px solid var(--line-3); }
/* A fixed head height, whatever sits on its right: the Pro tag is taller than
   the Free label alone, and it pushed the Pro price 6px below the Free one. */
.plan-head { display: flex; justify-content: space-between; align-items: center; min-height: 24px; }
.plan-label { font-family: var(--sans); font-size: 12px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.price-row { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; margin: 12px 0 8px; }
.price { font-family: var(--display); font-size: 46px; font-weight: 700; line-height: 1; letter-spacing: -0.03em; font-variant-numeric: tabular-nums; }
.price-period { font-size: 13.5px; font-weight: 600; color: var(--muted); }
.plan-sub { font-size: 13.5px; line-height: 1.55; color: var(--ink-2); margin: 0; }
.plan-body { padding: 20px 30px 24px; display: flex; flex-direction: column; gap: 11px; flex: 1; }
.plan-incl { font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--faint); }
.plan ul { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 11px; }
.plan li { position: relative; padding-left: 22px; font-size: 13.5px; line-height: 1.5; color: var(--ink-2); }
/* Green, not cobalt: a tick is a thing you already have, and the brand colour on
   this page belongs to the action you have not taken yet. */
.plan li::before { content: "\2713"; position: absolute; left: 0; top: 0; color: var(--vote-arrow); font-weight: 700; }
.plan-foot { padding: 0 30px 26px; }
.plan-foot .m-btn { width: 100%; }
.plan-uncapped, .plan li strong { font-weight: 700; color: var(--ink); }
/* The terms that decide a click belong under the button that gets clicked, not
   in one shared line below both cards where neither plan owns them. */
.plan-fine { text-align: center; font-size: 12px; color: var(--faint); margin: var(--sp-1) 0 0; }
/* The Pro waitlist: one field and the button, stacked like the sign-in card. */
.waitlist-form { display: flex; flex-direction: column; gap: var(--sp-1); }
.waitlist-form .m-input { width: 100%; font-size: 14px; }
.plan-thanks { margin: 0; text-align: center; font-size: 14px; font-weight: 600; color: var(--ink); }
/* Once joined there is no form to line up with the Free card's button: the card
   keeps its own height rather than stretching around an empty band. */
.plan--joined { align-self: start; }
/* A honeypot, not a hidden field: display:none is skipped by the bots it exists
   for. Off screen, it is still in the form a bot reads and out of a person's way. */
.hp { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }
.pricing-fine { text-align: center; font-size: 13px; color: var(--muted); margin: var(--sp-3) 0 0; }
/* The one thing that outranks a pricing claim is the product running in public,
   so the link to it sits between the cards and the FAQ. */
.pricing-live { display: flex; align-items: center; justify-content: center; gap: 10px; flex-wrap: wrap; max-width: 920px; background: var(--tint); border-radius: var(--r-lg); padding: 14px 18px; margin: var(--sp-3) auto 0; font-size: 13px; color: var(--ink-2); text-align: center; }
.pricing-live-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--st-shipped); flex: none; }
@media (max-width: 760px) {
  .plans { grid-template-columns: 1fr; }
}
@media (max-width: 600px) {
  /* The dot stays beside the sentence it marks instead of wrapping above it. */
  .pricing-live { flex-wrap: nowrap; align-items: flex-start; justify-content: flex-start; text-align: left; }
  .pricing-live-dot { margin-top: 6px; }
}

/* Homepage. Light from the top: no dark band anywhere on the page. The start
   is given by the bar's hairline, the board, and the loop's own rule after it.
   Every section below the hero is one template (eyebrow, title, 96px rhythm, a
   hairline on top), and grey appears once, under the one deep section, so a
   change of ground means "slow down here". The selectors are the homepage's
   own: the portal loads this stylesheet too. */
.home-hero { padding: 80px 0 var(--sp-7); }
.hero-copy { max-width: 820px; margin: 0 auto; text-align: center; }
.hero-h1 { margin: 0; font-size: 66px; font-weight: 700; line-height: 1.05; letter-spacing: -0.045em; color: var(--ink); text-wrap: balance; }
/* Marketing exception, approved: the hero's one coloured emphasis ("real
   customers"). Cobalt on white, 7.3:1. */
.hero-h1-mark { color: var(--cobalt); }
.hero-lead { max-width: 600px; margin: var(--sp-3) auto 0; font-size: 18px; line-height: 1.6; color: var(--muted); text-wrap: pretty; }
.hero-ctas { display: flex; justify-content: center; flex-wrap: wrap; gap: 12px; margin-top: var(--sp-4); }
.hero-arrow { font-size: 12px; opacity: 0.8; }
.hero-note { margin: var(--sp-2) 0 0; font-size: 13px; color: var(--muted); }

/* The demo board. The portal ledger's vocabulary (ruled rows, one vote count,
   stage pills that differ by sign) under its own selectors, so the portal can
   move without moving the homepage. The votes column is headed in ink. The one
   surface of the site allowed a drop shadow: it is a window onto the product. */
.demo-wrap { margin-top: var(--sp-6); }
/* A picture of the product, not part of the page: no text selection, no dragging, like an image. */
.demo { -webkit-user-select: none; user-select: none; cursor: default; }
.demo img { -webkit-user-drag: none; pointer-events: none; }
.demo { margin: 0; overflow: hidden; background: var(--white); color: var(--ink); border: 1px solid var(--line); border-radius: var(--r-lg); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04), 0 8px 24px rgba(0, 0, 0, 0.06); }
.demo-bar { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); padding: 12px 20px; border-bottom: 1px solid var(--line-3); font-size: 13px; line-height: 1.6; }
.demo-ws { display: flex; align-items: center; gap: var(--sp-1); margin: 0; font-weight: 600; white-space: nowrap; }
.demo-ws img { display: block; flex: none; }
.demo-ws span { color: var(--muted); font-weight: 400; }
.demo-ws .demo-mark { display: grid; place-items: center; flex: none; width: 18px; height: 18px; border-radius: 5px; background: var(--ink); color: #fff; font-size: 11px; font-weight: 700; }
.demo-tabs { display: flex; gap: 2px; margin: 0; }
.demo-tabs span { padding: 5px 10px; border-radius: var(--r); color: var(--muted); }
.demo-tabs .is-on { background: var(--tint-selected); color: var(--cobalt); font-weight: 500; }
/* A picture of the product's button, not a control: no hover, no pointer. */
.demo-btn { pointer-events: none; }
.demo-body { padding: 24px 24px 8px; }
.demo-title { margin: 0; font-size: 20px; font-weight: 700; line-height: 1.6; letter-spacing: -0.02em; }
.demo-sum { margin: 4px 0 0; font-size: 13px; color: var(--muted); }
.demo-filters { display: flex; flex-wrap: wrap; gap: 6px; margin: 18px 0 0; font-size: 12.5px; }
.demo-filters span { padding: 4px 10px; border-radius: var(--r-full); color: var(--muted); }
.demo-filters .is-on { background: var(--tint-selected); color: var(--cobalt); font-weight: 500; }
.demo-table { width: 100%; border-collapse: collapse; margin-top: var(--sp-2); font-size: 13.5px; }
.demo-table th { padding: 10px 12px; text-align: left; font-size: 11px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); background: var(--tint); border-bottom: 1px solid var(--line); }
.demo-table td { height: 50px; padding: 0 12px; border-bottom: 1px solid var(--line-3); vertical-align: middle; color: var(--muted); }
.demo-table tr:last-child td { border-bottom: 0; }
.demo-table td.demo-c-req { font-weight: 500; color: var(--ink); }
.demo-table th.demo-c-v { color: var(--ink); }
.demo-c-v { width: 108px; white-space: nowrap; }
.demo-c-date { width: 96px; }
.demo-c-stage { width: 128px; }
/* The one vote count, solid and green. */
.demo-v { display: inline-flex; align-items: center; gap: 6px; box-sizing: border-box; min-width: 58px; height: 26px; padding: 0 9px; border-radius: var(--r); font-size: 13px; line-height: 1; white-space: nowrap; font-variant-numeric: tabular-nums; }
.demo-v { background: var(--vote-bg); border: 1px solid var(--vote-border); color: var(--vote); font-weight: 600; }
.demo-g { font-size: 10px; }
.demo-vh { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.demo-stage { display: inline-flex; align-items: center; gap: 6px; padding: 3px 9px; border-radius: var(--r-full); font-size: 12px; font-weight: 500; line-height: 1.6; white-space: nowrap; }
.demo-stage i { width: 6px; height: 6px; border-radius: 50%; flex: none; }
.demo-stage--planned { background: #f0f4f8; color: #3f5d7d; }
.demo-stage--planned i { background: var(--st-planned); }
.demo-stage--in-progress { background: #faf4ea; color: #805718; }
.demo-stage--in-progress i { background: var(--st-in-progress); }
.demo-stage--shipped, .ha-mail-pill { background: #eff6f0; color: #31653e; }
/* Shipped is a tick, not a dot: the stages differ by sign as well as hue. */
.demo-stage--shipped i, .ha-mail-pill::before {
  width: 10px; height: 10px; border-radius: 0; background: var(--st-shipped);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 6.4 5 8.8l4.6-5.6' fill='none' stroke='%23000' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 6.4 5 8.8l4.6-5.6' fill='none' stroke='%23000' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat; }
.demo-cap { margin: 20px 0 0; text-align: center; font-size: 13.5px; color: var(--muted); }
.demo-cap b { color: var(--ink); font-weight: 500; }

/* The receipt: what a voter gets when their request ships. Square and flat, on
   the sign-in page beside the form it argues for. */
.ha-mail { margin: 0; padding: 24px; background: var(--surface-2); color: var(--ink); border: 1px solid var(--line); border-radius: var(--r-lg); }
.ha-mail-pill { display: inline-flex; align-items: center; gap: 6px; padding: 3px 8px; border-radius: var(--r-full); font-size: 12px; font-weight: 500; line-height: 1.4; }
.ha-mail-pill::before { content: ""; flex: none; }
.ha-mail blockquote { margin: 12px 0 8px; font-size: 22px; font-weight: 600; line-height: 1.3; letter-spacing: -0.025em; color: var(--ink); }
.ha-mail figcaption { font-size: 13px; line-height: 1.5; color: var(--muted); }

/* The section template. */
.home-section { padding: var(--sp-7) 0; border-top: 1px solid var(--line-3); }
.home-head { max-width: 640px; margin-bottom: 56px; }
.home-h2 { margin: 0; font-size: 38px; font-weight: 700; line-height: 1.15; letter-spacing: -0.03em; color: var(--ink); text-wrap: balance; }
.home-h2 .mark { color: var(--cobalt); }
.home-lead { margin: 18px 0 0; font-size: 17px; line-height: 1.6; color: var(--muted); text-wrap: pretty; }

/* The loop, told once, as one surface. A rule over each step darkens toward
   Close, the step the cycle is built for; each step ends on a fragment of the
   real interface, in the board's vocabulary. Plain 1fr, not minmax(0, 1fr):
   the roadmap fragment's three pills never wrap, so Ship takes the width they
   need instead of letting them spill into each other. */
.loop-grid { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(4, 1fr); background: var(--white); border: 1px solid var(--line); border-radius: var(--r-lg); }
.loop-step { position: relative; display: flex; flex-direction: column; padding: 36px 28px 32px; border-left: 1px solid var(--line-3); }
.loop-step:first-child { border-left: 0; }
.loop-step::before, .loop-step::after { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 2px; }
.loop-step::before { background: var(--line-3); }
.loop-step::after { background: var(--cobalt); opacity: 0.18; }
.loop-step:nth-child(2)::after { opacity: 0.36; }
.loop-step:nth-child(3)::after { opacity: 0.6; }
.loop-step:nth-child(4)::after { opacity: 1; }
.loop-step-num { display: block; margin-bottom: 10px; font-size: 12px; font-weight: 600; letter-spacing: 0.08em; color: var(--muted); font-variant-numeric: tabular-nums; }
.loop-step-title { margin: 0; font-size: 22px; font-weight: 700; line-height: 1.6; letter-spacing: -0.02em; color: var(--ink); }
.loop-step-body { margin: 10px 0 22px; font-size: 14.5px; line-height: 1.55; color: var(--muted); }
.loop-frag { margin-top: auto; overflow: hidden; background: var(--white); border: 1px solid var(--line); border-radius: var(--r-lg); font-size: 12.5px; color: var(--ink-2); }
.loop-frag-row { padding: 10px 12px; }
.loop-frag-row + .loop-frag-row { border-top: 1px solid var(--line-3); }
.loop-frag-k { display: block; margin-bottom: 4px; font-size: 10.5px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }
.loop-frag-input { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-1); height: 32px; padding: 0 10px; border: 1px solid var(--line-2); border-radius: var(--r); color: var(--ink); white-space: nowrap; overflow: hidden; }
.loop-frag-input svg { flex: none; width: 14px; height: 14px; fill: none; stroke: var(--muted); stroke-width: 1.5; stroke-linecap: round; }
.loop-frag-match { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 4px 10px; }
.loop-frag-match b { color: var(--cobalt); font-weight: 500; white-space: nowrap; }
.loop-frag-title { font-weight: 500; color: var(--ink); }
.loop-frag-counters { display: flex; align-items: center; gap: 6px; margin-top: 6px; }
.loop-frag-cols { display: grid; grid-template-columns: repeat(3, 1fr); }
.loop-frag-col { min-height: 86px; padding: 10px 10px 12px; border-left: 1px solid var(--line-3); }
.loop-frag-col:first-child { border-left: 0; }
.loop-frag-col .demo-stage { padding: 2px 7px; font-size: 10.5px; }
.loop-frag-card { margin-top: 8px; padding: 6px 8px; border: 1px solid var(--line); border-radius: 4px; font-size: 11.5px; font-weight: 500; line-height: 1.3; color: var(--ink); }
.loop-frag-card--ghost { border-style: dashed; color: var(--muted); font-weight: 400; }
.loop-frag-mail { padding: 12px 12px 14px; }
.loop-frag-mail blockquote { margin: 8px 0 4px; font-family: var(--display); font-size: 16px; font-weight: 700; letter-spacing: -0.02em; color: var(--ink); }
.loop-frag-to { font-size: 12px; color: var(--muted); }

/* Four capabilities, one line each, ruled rather than carded. */
.home-caps { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); border-top: 1px solid var(--line); }
.home-cap { padding: 32px 28px 8px 0; }
.home-cap + .home-cap { padding-left: 28px; border-left: 1px solid var(--line-3); }
.home-cap svg { display: block; width: 22px; height: 22px; fill: none; stroke: var(--ink); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.home-cap-title { margin: 18px 0 0; font-family: var(--sans); font-size: 15.5px; font-weight: 600; line-height: 1.6; letter-spacing: 0; color: var(--ink); }
.home-cap-body { margin: 8px 0 0; font-size: 14px; line-height: 1.55; color: var(--muted); }

/* Pricing on the homepage: the same two plans as /pricing, at section size. */
.home-plans { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-2); }
.home-plan { position: relative; display: flex; flex-direction: column; padding: 32px; background: var(--white); border: 1px solid var(--line); border-radius: var(--r-lg); }
.home-plan-name { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 0; font-family: var(--sans); font-size: 14px; font-weight: 600; line-height: 1.6; letter-spacing: 0; color: var(--ink); }
.home-plan-pill { padding: 3px 9px; border-radius: var(--r-full); background: var(--tint); color: var(--muted); font-size: 11.5px; font-weight: 500; }
.home-plan-price { display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px; margin: 18px 0 0; }
.home-plan-amt { font-family: var(--display); font-size: 44px; font-weight: 700; line-height: 1; letter-spacing: -0.03em; color: var(--ink); font-variant-numeric: tabular-nums; }
.home-plan-per { font-size: 14px; color: var(--muted); }
.home-plan-sub { margin: 10px 0 0; font-size: 14.5px; color: var(--muted); }
.home-plan-list { list-style: none; flex: 1; display: grid; gap: 10px; margin: 24px 0 0; padding: 24px 0 0; border-top: 1px solid var(--line-3); font-size: 14px; color: var(--ink-2); }
.home-plan-list li { display: flex; align-items: flex-start; gap: 10px; }
/* Green, not cobalt: a tick is a thing you already have. */
.home-plan-list svg { flex: none; width: 16px; height: 16px; margin-top: 3px; fill: none; stroke: var(--vote); stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }
.home-plan-list b { font-weight: 600; color: var(--ink); }
.home-plan-list .home-plan-muted { color: var(--muted); }
.home-plan > .m-btn { align-self: flex-start; margin-top: 28px; }
.home-plan-art { position: absolute; top: 24px; right: 28px; width: 72px; height: 72px; }

/* Final call. Jump target of the hero CTA, clear of the sticky bar. */
#signup { scroll-margin-top: 64px; }
.home-cta { display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); gap: var(--sp-6); align-items: center; }
.home-cta .home-head { margin-bottom: 0; }
.signup-form { display: flex; gap: 10px; }
/* width:auto undoes the base field rule's width:100%, so the field shares the
   row with the button instead of claiming it. */
.signup-form .m-input { flex: 1; width: auto; min-width: 0; font-size: 14px; }
.signup-note { margin: 12px 0 0; font-size: 13px; color: var(--muted); }
.signup-note + .signup-note { margin-top: 4px; }
.signup-note a { color: var(--cobalt); }
/* A compound that must not break at its hyphen ("sign-in"). */
.nobr { white-space: nowrap; }

@media (max-width: 1040px) {
  .loop-grid { grid-template-columns: repeat(2, 1fr); }
  .loop-step:nth-child(3) { border-left: 0; }
  .loop-step:nth-child(n + 3) { border-top: 1px solid var(--line-3); }
  .home-caps { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .home-cap:nth-child(3) { padding-left: 0; border-left: 0; }
  .home-cap:nth-child(n + 3) { border-top: 1px solid var(--line-3); }
  .home-cta { grid-template-columns: minmax(0, 1fr); gap: 40px; }
}
@media (max-width: 800px) {
  .home-hero { padding: 56px 0; }
  .hero-h1 { font-size: 40px; line-height: 1.08; letter-spacing: -0.035em; }
  .hero-lead { margin-top: 18px; font-size: 16px; }
  .hero-ctas { flex-direction: column; align-items: stretch; }
  .demo-wrap { margin-top: 40px; }
  .demo-bar { padding: 10px 16px; }
  .demo-tabs { display: none; }
  .demo-body { padding: 16px 16px 4px; }
  /* Each row becomes a card: the title, then the two counters and the stage.
     Nothing shrinks to keep a line. */
  .demo-table, .demo-table tbody { display: block; }
  .demo-table thead { display: none; }
  .demo-table tr { display: grid; grid-template-columns: auto auto minmax(0, 1fr); grid-template-areas: "req req req" "v e stage"; gap: 8px 6px; padding: 12px 0; border-bottom: 1px solid var(--line-3); }
  .demo-table tr:last-child { border-bottom: 0; }
  .demo-table td { display: block; width: auto; height: auto; padding: 0; border: 0; }
  .demo-table .demo-c-req { grid-area: req; }
  .demo-table .demo-c-v { grid-area: v; }
  .demo-table .demo-c-stage { grid-area: stage; justify-self: end; }
  .demo-table .demo-c-date { display: none; }
  .home-section { padding: var(--sp-6) 0; }
  .home-head { margin-bottom: 36px; }
  .home-h2 { font-size: 30px; }
  .loop-grid { grid-template-columns: minmax(0, 1fr); }
  .loop-step { padding: 28px 20px; border-left: 0; }
  .loop-step:nth-child(n + 2) { border-top: 1px solid var(--line-3); }
  .home-caps, .home-plans { grid-template-columns: minmax(0, 1fr); }
  .home-cap, .home-cap + .home-cap, .home-cap:nth-child(3) { padding: 24px 0 8px; border-left: 0; }
  .home-cap + .home-cap { border-top: 1px solid var(--line-3); }
  .home-plan { padding: 24px 20px; }
  .home-plan-art { display: none; }
  .signup-form { flex-direction: column; }
  .signup-form .m-input { flex: none; width: 100%; }
}
/* The narrowest phones: the board's path goes before its button is cut. */
@media (max-width: 360px) {
  .demo-ws span { display: none; }
}

/* Calm eyebrows: grey, small, spaced. Cobalt belongs to the action. Restated
   inside the prose containers, whose own `p` rule outranks a bare class. */
.eyebrow, .doc .eyebrow, .panel-tint .eyebrow { font-size: 12px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); margin: 0 0 12px; }
.feature-row { display: grid; grid-template-columns: 48px 300px 1fr; gap: var(--sp-4); padding: var(--sp-4) 0; border-top: 1px solid var(--line-3); }
.feature-num { font-size: 13px; font-weight: 500; color: var(--muted); font-variant-numeric: tabular-nums; }
.feature-h { font-family: var(--display); font-size: 21px; font-weight: 700; letter-spacing: -0.02em; }
.feature-kicker { font-size: 11.5px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); margin-top: 6px; }
.feature-p { font-size: 15px; line-height: 1.6; color: var(--ink-2); max-width: 520px; margin: 0; }

.feature-list { display: flex; flex-direction: column; gap: var(--sp-3); margin-top: var(--sp-4); }
.feature-list .feature-h { font-family: var(--sans); font-size: 16px; font-weight: 600; letter-spacing: -0.01em; line-height: 1.4; }
.feature-list .feature-p { margin-top: 6px; font-size: 14px; color: var(--muted); }
/* The qualifier rides on the title line: it is what the feature is, not a
   second sentence about it. */
.feature-tag { font-family: var(--sans); font-size: 10.5px; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--muted); margin-left: 8px; white-space: nowrap; }
/* Screenshots on a light surface: a hairline edge, no drop shadow. */
.shot { display: block; width: 100%; height: auto; border: 1px solid var(--line); border-radius: var(--r-lg); }

/* A thin strip mid-page. The page is long and the decision does not have to
   wait for the end of it. */
.cta-strip { background: var(--tint); border-top: 1px solid var(--line-3); border-bottom: 1px solid var(--line-3); padding: 20px max(32px, calc((100% - var(--wrap)) / 2 + 32px)); display: flex; align-items: center; justify-content: center; gap: 12px var(--sp-2); flex-wrap: wrap; }
.cta-strip-copy { font-size: 14px; color: var(--ink-2); }
.cta-strip-link { font-size: 13px; font-weight: 500; color: var(--ink-2); }
.cta-strip-link:hover { color: var(--cobalt); }

/* Two audiences side by side: whoever wires it up, and whoever runs it. */
.duo { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-5); padding: 80px 0; border-top: 1px solid var(--line-3); }
.duo .support-title { font-size: 26px; }

@media (max-width: 900px) {
  .duo { grid-template-columns: 1fr; gap: var(--sp-4); }
}
@media (max-width: 800px) {
  .duo { padding: 56px 0; }
  .cta-strip { padding: 20px; }
}

/* Footer */
footer { border-top: 1px solid var(--line-3); padding: 24px max(32px, calc((100% - var(--wrap)) / 2 + 32px)); display: flex; justify-content: space-between; align-items: flex-start; flex-wrap: wrap; gap: 12px 28px; font-size: 12.5px; color: var(--muted); }
.site-footer-brand { display: flex; align-items: center; gap: 8px; }
.site-footer-brand strong { color: var(--ink); font-weight: 700; }
.site-footer-nav { flex: 1 1 0; min-width: 0; display: flex; flex-wrap: wrap; gap: 8px 18px; justify-content: flex-end; }
.site-footer-nav a { color: var(--muted); }
.site-footer-nav a:hover { color: var(--ink); }
@media (max-width: 800px) {
  footer { flex-direction: column; gap: 16px; padding: 24px 20px; }
  .site-footer-nav { flex: none; justify-content: flex-start; }
}

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

/* ============================================================================
   5 · SITE SYSTEM: the page-level vocabulary of the marketing site and docs,
   direction A. No dark band: every section sits on paper or on the support
   grey, titled, 80px apart (56px on a phone), inside
   the one .wrap column. Cards and screenshots get a hairline edge and no
   shadow. Everything here is marketing/docs only; the portal and dashboard
   keep the components above untouched.
   ========================================================================== */

/* Marketing button. The direction A family (flat, 6px corners, 500 weight) at a
   44px marketing size; --sm is the 34px control height of the product.
   Variants: primary (cobalt, the one action), secondary (white on a hairline),
   ghost (transparent on a hairline). */
.m-btn { display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; gap: 6px; height: var(--control-h-lg); padding: 0 20px; border: 1px solid transparent; border-radius: var(--r-md); font: inherit; font-family: var(--sans); font-size: 14px; font-weight: 500; line-height: 1; white-space: nowrap; cursor: pointer; background: none; color: var(--ink); text-decoration: none; transition: background-color .14s ease, border-color .14s ease, color .14s ease; }
.m-btn--primary { background: var(--cobalt); border-color: var(--cobalt); color: var(--white); }
.m-btn--primary:hover { background: var(--cobalt-dark); border-color: var(--cobalt-dark); color: var(--white); }
.m-btn--secondary { background: var(--white); color: var(--ink); border-color: var(--line); }
.m-btn--secondary:hover { background: var(--tint); color: var(--ink); border-color: var(--line-2); }
.m-btn--ghost { background: transparent; border-color: var(--line); color: var(--ink); }
.m-btn--ghost:hover { background: var(--tint); color: var(--ink); border-color: var(--line-2); }
.m-btn--sm { height: var(--control-h); padding: 0 12px; font-size: 13px; border-radius: var(--r); }
.m-btn:focus-visible { outline: 2px solid var(--cobalt); outline-offset: 3px; }

/* Marketing field. Same --control-h-lg and --r-md as .m-btn, so a field and the
   button beside it line up without anyone nudging a pixel. */
/* Qualified with `input` on purpose: the base field rule matches
   input[type="email"], which outranks a bare class. Same specificity, later in
   the file, so this one wins without an !important. */
input.m-input, .m-input { box-sizing: border-box; height: var(--control-h-lg); padding: 0 14px; border: 1px solid var(--line-2); border-radius: var(--r-md); background: var(--white); color: var(--ink); font: inherit; font-family: var(--sans); font-size: 15px; }
input.m-input::placeholder, .m-input::placeholder { color: var(--faint); }
input.m-input:focus, .m-input:focus { border-color: var(--cobalt); outline: 2px solid var(--cobalt); outline-offset: 3px; }

/* Sections (T2). A section is an eyebrow, a display title, then its content. */
.section-support { padding: 80px 0; }
.section-support.ruled { border-top: 1px solid var(--line-3); }
.support-title { font-size: 34px; font-weight: 700; letter-spacing: -0.035em; line-height: 1.2; max-width: 720px; margin: 0; text-wrap: balance; }
/* Marketing exception, approved: one coloured emphasis in a heading. */
.support-title .mark { color: var(--cobalt); }
.cards { display: grid; gap: var(--sp-2); margin-top: 36px; }
.cards-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
/* Half-width cards carry half the prose, so they get their own padding rather
   than the full card's with the text rattling around inside it. */
.card-mini { padding: 16px 18px; }
.card-mini .card-title { font-size: 13.5px; }
.card-mini .card-body { font-size: 12.5px; line-height: 1.6; }
.card-title { font-family: var(--sans); font-size: 15px; font-weight: 600; letter-spacing: -0.01em; line-height: 1.4; color: var(--ink); }
.card-body { font-size: 14px; line-height: 1.65; color: var(--muted); margin: var(--sp-1) 0 0; }
/* Spot illustration over a card's title, flush with its left edge. The drawing
   keeps its own margin inside the frame, so no padding is added around it. One
   size per row: 96 on a full card, 64 on a mini card. */
.card-art { display: block; width: 96px; height: auto; margin: 0 0 var(--sp-1); }
.card-mini .card-art { width: 64px; }

/* Page opener for argument and decision pages: one step under the homepage. */
.page-hero { padding: 80px 0 56px; }
.page-hero--center { text-align: center; }
.page-h1 { font-size: 44px; font-weight: 700; line-height: 1.1; letter-spacing: -0.035em; max-width: 20ch; margin: 0; text-wrap: balance; }
.page-hero--center .page-h1 { margin: 0 auto; }
.page-h1 .mark { color: var(--cobalt); }
.page-lead { font-size: 17px; line-height: 1.65; color: var(--ink-2); max-width: 640px; margin: 20px 0 0; text-wrap: pretty; }
.page-hero--center .page-lead { margin-left: auto; margin-right: auto; }
/* An opener with art becomes two columns: the copy keeps its own measure, the
   illustration sits against the right edge of the wrap, centred on the copy. */
.page-hero--art { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--sp-5); }
.page-hero-art { display: block; width: clamp(260px, 30vw, 340px); height: auto; }
.fact-line { display: flex; gap: 12px; flex-wrap: wrap; margin-top: var(--sp-3); font-size: 13px; font-weight: 500; color: var(--muted); }
.fact-line span[aria-hidden] { color: var(--faint); }

/* One tint panel per page, maximum. */
.panel-tint { background: var(--tint); border-radius: var(--r-lg); padding: 40px; margin: var(--sp-6) 0; }
.panel-tint .support-title { font-size: 26px; }
.panel-tint p { font-size: 15px; line-height: 1.65; color: var(--ink-2); max-width: 640px; margin: var(--sp-2) 0 0; }
/* Same two columns inside the panel. A fixed height, not a width, so a 4:3
   drawing and a square one stand at the same size from page to page. The
   drawing carries its own air, so it reaches into the panel's padding rather
   than making the panel taller than its copy needs. */
.panel-tint--art { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--sp-5); }
.panel-art { display: block; width: auto; height: 220px; margin: calc(-1 * var(--sp-2)) 0; }

.faq-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4) var(--sp-5); margin-top: 36px; }
.faq-q { font-size: 15px; font-weight: 600; font-family: var(--sans); letter-spacing: -0.01em; line-height: 1.4; }
.faq-a { font-size: 14px; line-height: 1.65; color: var(--muted); margin: 6px 0 0; }
.faq-a a { color: var(--cobalt); }

/* MCP page: connecting in three steps, borrowing the homepage loop's surface,
   then the client configs and the four tools. Later in the file than the loop's
   own breakpoints, so three columns hold down to the phone layout. */
.loop-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); margin-top: 36px; }
.loop-grid--3 .m-btn { align-self: flex-start; margin-top: auto; }
.loop-step-body code, .tool-name { font-family: var(--mono); font-size: 13px; color: var(--ink); }
.tool-name { font-size: 15px; font-weight: 600; }
.mcp-configs { max-width: 720px; margin-top: var(--sp-4); }
.section-more { margin: var(--sp-4) 0 0; font-size: 15px; line-height: 1.6; color: var(--ink-2); }
.section-more a { color: var(--cobalt); }
@media (min-width: 801px) and (max-width: 1040px) {
  .loop-grid--3 .loop-step:nth-child(3) { border-left: 1px solid var(--line-3); border-top: 0; }
}
@media (max-width: 800px) {
  .loop-grid--3 { grid-template-columns: minmax(0, 1fr); }
}

/* Closing section: how an argument or decision page ends, on the support grey. */
.cta-band { background: var(--tint); border-top: 1px solid var(--line-3); padding: 80px max(32px, calc((100% - var(--wrap)) / 2 + 32px)); text-align: center; }
.cta-title { font-size: 34px; font-weight: 700; line-height: 1.2; letter-spacing: -0.035em; color: var(--ink); max-width: 680px; margin: 0 auto; text-wrap: balance; }
.cta-sub { font-size: 15px; line-height: 1.65; color: var(--muted); max-width: 56ch; margin: 20px auto 0; text-wrap: pretty; }
.cta-actions { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; margin-top: 28px; }

/* Action shell: signup and message. One job, no navigation temptation. */
body.action { display: flex; flex-direction: column; min-height: 100vh; }
.action-band { background: var(--tint); padding: 80px max(32px, calc((100% - var(--wrap)) / 2 + 32px)); flex: 1; display: flex; align-items: center; justify-content: center; }
.action-card { background: var(--white); border: 1px solid var(--line); border-radius: var(--r-lg); padding: var(--sp-5) var(--sp-4); max-width: 520px; width: 100%; text-align: center; }
/* Optional spot illustration over the title, centred like the rest of the card.
   Lifted into the top padding by the air its frame already carries, so the card
   reads as tall above the drawing as below the last button. */
.action-art { display: block; width: 96px; height: auto; margin: calc(-1 * var(--sp-1)) auto var(--sp-2); }
.action-title { font-size: 26px; font-weight: 700; letter-spacing: -0.03em; line-height: 1.2; }
.action-body { font-size: 15px; line-height: 1.6; color: var(--muted); margin: 12px 0 0; text-wrap: pretty; }
.action-form { display: flex; gap: var(--sp-1); flex-wrap: wrap; justify-content: center; margin: var(--sp-3) 0 0; }
.action-form .m-input { flex: 1; width: auto; min-width: 180px; }
.action-fine { font-size: 12.5px; color: var(--muted); margin: var(--sp-2) 0 0; }
.action-fine a { color: var(--cobalt); }
.action-link { display: inline-flex; margin-top: var(--sp-3); }
/* "Continue as <address>" carries a whole work address, which ran past a
   phone's edge on one line. It wraps inside the card instead. */
.action-card form .action-link { max-width: 100%; height: auto; min-height: var(--control-h-lg); padding-top: 10px; padding-bottom: 10px;
                                 line-height: 1.35; white-space: normal; overflow-wrap: anywhere; }

/* Sign-in: the one page in the action shell that has to argue before it
   collects, so it keeps the paper surface and puts the case beside the field
   rather than a band around it. `message` keeps the centred shell above. */
.signin-band { background: var(--paper); padding: 80px max(32px, calc((100% - var(--wrap)) / 2 + 32px)); flex: 1; display: flex; align-items: center; }
.signin-grid { display: grid; grid-template-columns: minmax(0, 1fr) 440px; gap: 96px; align-items: center; width: 100%; max-width: 1060px; margin: 0 auto; }
.signin-h1 { font-size: 34px; font-weight: 700; line-height: 1.2; letter-spacing: -0.035em; margin: 0; }
.signin-points { list-style: none; display: flex; flex-direction: column; gap: 12px; margin: var(--sp-3) 0 0; padding: 0; }
/* The tick is decorative: each item reads as a sentence without it. */
.signin-points li { position: relative; padding-left: 26px; font-size: 14px; line-height: 1.6; color: var(--muted); }
.signin-points li::before { content: "✓"; position: absolute; left: 0; color: var(--vote-arrow); font-weight: 700; }
.signin-points strong { color: var(--ink); font-weight: 600; }
/* Same receipt as the homepage, on paper. */
.signin-mail { margin: var(--sp-4) 0 0; max-width: 440px; background: var(--white); }
.signin-card { background: var(--white); border: 1px solid var(--line); border-radius: var(--r-lg); padding: var(--sp-5) var(--sp-4); text-align: center; }
.signin-form { display: flex; flex-direction: column; gap: var(--sp-1); margin: var(--sp-3) 0 0; }
.signin-form .m-input { width: 100%; text-align: left; }
@media (max-width: 900px) {
  .signin-band { padding-top: var(--sp-5); padding-bottom: var(--sp-5); }
  .signin-grid { grid-template-columns: minmax(0, 1fr); gap: var(--sp-5); max-width: 560px; }
  .signin-h1 { font-size: 28px; }
}
@media (max-width: 800px) {
  .section-support { padding: 56px 0; }
  .support-title, .cta-title { font-size: 30px; }
  .cta-band { padding: 56px 20px; }
  .action-band, .signin-band { padding-left: 20px; padding-right: 20px; }
}
/* Document pages: legal prose and docs share one measure. */
.doc { max-width: 720px; padding: 80px 0; }
.doc-h1 { font-size: 44px; font-weight: 700; line-height: 1.1; letter-spacing: -0.035em; margin: 0; }
.doc .doc-updated, .doc-updated { font-size: 13px; color: var(--muted); margin: 12px 0 0; }
.doc h2 { font-size: 22px; font-weight: 700; letter-spacing: -0.025em; line-height: 1.3; margin: var(--sp-5) 0 var(--sp-2); }
.doc h3 { font-family: var(--sans); font-size: 15px; font-weight: 600; letter-spacing: -0.01em; margin: var(--sp-3) 0 var(--sp-1); }
.doc p, .doc li { font-size: 15px; line-height: 1.7; margin: 0 0 12px; }
.doc ul { padding-left: 20px; margin: 0 0 14px; }
.doc li { margin-bottom: 6px; }
.doc a { color: var(--cobalt); }
.doc a:hover { color: var(--cobalt-dark); text-decoration: underline; }
/* A checklist is read by jumping between headings, so on a reference page each
   section gets a rule to land on. Legal prose reads straight through and does
   not opt in. */
.doc .doc-lead, .doc-lead { font-size: 17px; line-height: 1.65; color: var(--ink-2); margin: 20px 0 0; }
.doc--ruled h2 { border-top: 1px solid var(--line-3); padding-top: var(--sp-4); }
.doc .doc-related, .doc-related { margin-top: var(--sp-5); font-size: 14px; }
/* A reference page with art: the 720px column stays exactly where it was and
   the illustration takes the empty side of the wrap, level with the title.
   Under 1100px that side is too narrow to hold it, so it goes. */
.doc-shell { display: grid; grid-template-columns: minmax(0, 720px) minmax(0, 1fr); column-gap: var(--sp-5); align-items: start; }
.doc-art { display: block; justify-self: end; width: 260px; max-width: 100%; height: auto; margin-top: 80px; }
@media (max-width: 1100px) {
  .doc-shell { display: block; }
  .doc-art { display: none; }
}
@media (max-width: 800px) {
  .doc { padding: 56px 0; }
}

/* ---------------------------------------------------------------------------
   Blog. Index, article, and the styles a post is written with. The article body
   reuses the docs vocabulary (.code, .callout, .params) so an aside looks the
   same whichever surface a reader met it on first. */

/* Category chips are links to their own URL, so they are styled as links that
   look selected, not as buttons that hold state.
   Prefixed because this stylesheet is shared with the portal and the dashboard:
   a bare `.chips` here once landed on the board's vote counters and gave every
   row of the request table 24px of margin it never asked for. */
.blog-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: var(--sp-3); }
/* Direction A filters: flat text chips; the selected one takes the selection
   wash, cobalt on a light cobalt ground. */
.blog-chip { display: inline-flex; align-items: center; min-height: 32px; padding: 0 10px; border-radius: var(--r); color: var(--muted); font-size: 13px; font-weight: 500; }
.blog-chip:hover { background: var(--tint); color: var(--ink); }
.blog-chip--on { background: var(--tint-selected); color: var(--cobalt); }
.blog-chip--on:hover { background: var(--tint-selected); color: var(--cobalt); }
/* A category on a post: a label, not a control. */
.blog-chip--tag { min-height: 0; height: 20px; padding: 0 6px; border-radius: 3px; background: var(--tint); color: var(--muted); font-size: 10.5px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; }

.post-lead { display: grid; grid-template-columns: 1.2fr 1fr; align-items: stretch; background: var(--white); border: 1px solid var(--line); border-radius: var(--r-lg); overflow: hidden; margin-top: var(--sp-2); }
/* Fixed height, cropped from the top-left: a screenshot's first rows are the
   part worth showing, and letting the image set the row height made the lead
   card taller than the three cards under it put together. */
.post-lead-shot { display: block; width: 100%; height: 340px; object-fit: cover; object-position: left top; border-right: 1px solid var(--line); }
.post-lead-copy { padding: 40px; display: flex; flex-direction: column; justify-content: center; }
.post-lead-title { font-size: 28px; font-weight: 700; line-height: 1.2; letter-spacing: -0.03em; margin: 12px 0 0; }
.post-meta { display: flex; align-items: center; gap: var(--sp-1); margin: 0; font-size: 12px; }

.post-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-3); margin-top: var(--sp-3); }
.post-grid--related { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.post-card { display: flex; flex-direction: column; overflow: hidden; }
.post-card-shot { display: block; width: 100%; height: 170px; object-fit: cover; object-position: left top; border-bottom: 1px solid var(--line); }
.post-card-body { padding: 20px 22px 22px; }
.post-card-title { font-size: 18px; font-weight: 700; line-height: 1.3; letter-spacing: -0.02em; margin: 12px 0 0; }
.post-card-title a, .post-lead-title a { color: var(--ink); }
.post-card-title a:hover, .post-lead-title a:hover { color: var(--cobalt); }
.post-excerpt { font-size: 14px; line-height: 1.6; color: var(--muted); margin: var(--sp-1) 0 0; }
.post-byline { font-size: 12px; color: var(--muted); margin: var(--sp-2) 0 0; }
.post-empty { font-size: 15px; color: var(--muted); margin: var(--sp-4) 0; }
.post-empty a { color: var(--cobalt); }

.pagination { display: flex; align-items: center; justify-content: center; gap: var(--sp-3); margin-top: var(--sp-5); }
.pagination-count { font-size: 13px; color: var(--muted); }

/* The article measure is the docs measure. A post that is wider than the page
   it links to would read as a different site. */
.article { max-width: 720px; margin: 0 auto; padding: 64px 0 80px; }
.article-crumb { font-size: 13px; color: var(--muted); margin: 0 0 var(--sp-2); }
.article-crumb a { color: var(--muted); }
.article-crumb a:hover { color: var(--ink); }
.article-h1 { font-size: 40px; font-weight: 700; line-height: 1.15; letter-spacing: -0.035em; margin: 14px 0 0; text-wrap: balance; }
.article-standfirst { font-size: 17px; line-height: 1.65; color: var(--ink-2); margin: var(--sp-2) 0 0; }
.article-byline { font-size: 13px; color: var(--muted); margin: var(--sp-3) 0 var(--sp-4); padding-bottom: var(--sp-3); border-bottom: 1px solid var(--line-3); }
.article-figure { margin: 0 0 var(--sp-5); }

.article-body p, .article-body li { font-size: 16px; line-height: 1.75; color: var(--ink-2); margin: 0 0 20px; }
.article-body h2 { font-size: 26px; font-weight: 700; letter-spacing: -0.03em; line-height: 1.25; margin: var(--sp-5) 0 14px; }
.article-body h3 { font-size: 20px; font-weight: 700; letter-spacing: -0.02em; line-height: 1.3; margin: var(--sp-4) 0 12px; }
.article-body strong { color: var(--ink); }
/* :not(.m-btn) or the link colour repaints every button in a post, which meant
   cobalt text on a cobalt fill. */
.article-body a:not(.m-btn) { color: var(--cobalt); }
.article-body a:not(.m-btn):hover { color: var(--cobalt-dark); text-decoration: underline; }
.article-body ul { padding-left: 22px; margin: 0 0 28px; }
.article-body li { margin-bottom: 6px; }
.article-body code { font-family: var(--mono); font-size: 13.5px; background: var(--tint-2); border: 1px solid var(--line); border-radius: 4px; padding: 1px 5px; color: var(--ink); }
.article-body .code code { border: 0; background: none; padding: 0; }
/* A pull quote earns display type because it is the one line of the piece we
   would want quoted back at us. A neutral rule beside it: cobalt is for action. */
.article-body blockquote { border-left: 2px solid var(--ink); margin: 0 0 28px; padding: 4px 0 4px 24px; }
.article-body blockquote p { font-family: var(--display); font-size: 21px; font-weight: 700; line-height: 1.4; letter-spacing: -0.02em; color: var(--ink); margin: 0; }
/* One per post, two thirds of the way down. A support-grey panel on a hairline,
   not a dark slab in the middle of the prose. */
.article-cta { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); flex-wrap: wrap; background: var(--tint); border: 1px solid var(--line-3); border-radius: var(--r-lg); padding: 24px 28px; margin: 0 0 var(--sp-5); }
/* Qualified by .article-body: the body's own paragraph rule is a class plus a
   type and would otherwise outrank a bare class here. */
.article-body .article-cta-title { font-family: var(--display); font-size: 19px; font-weight: 700; line-height: 1.3; letter-spacing: -0.02em; color: var(--ink); margin: 0; }
.article-body .article-cta-sub { font-size: 13.5px; line-height: 1.5; color: var(--muted); margin: 4px 0 0; }

@media (max-width: 900px) {
  .post-lead { grid-template-columns: minmax(0, 1fr); }
  .post-lead-shot { height: 220px; border-right: 0; border-bottom: 1px solid var(--line); }
  .post-lead-copy { padding: var(--sp-4); }
  .post-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .article-h1 { font-size: 32px; }
}
@media (max-width: 800px) {
  .article { padding: 48px 0 56px; }
}
@media (max-width: 620px) {
  .post-grid, .post-grid--related { grid-template-columns: minmax(0, 1fr); }
  .post-lead-copy { padding: var(--sp-3); }
  .post-lead-title { font-size: 24px; }
  .article-cta { padding: 20px; }
}

/* Docs template. Left nav and table of contents are plain links: no JS, so
   every page and every heading stays reachable with scripting off. */
.skip-link { position: absolute; left: -9999px; top: 0; background: var(--white); color: var(--cobalt); font-weight: 600; padding: 10px 16px; border-radius: var(--r-md); z-index: 100; }
.skip-link:focus-visible { left: 16px; top: 12px; }
.docs-layout { display: grid; grid-template-columns: var(--doc-nav) minmax(0, 1fr) var(--doc-toc); gap: var(--sp-6); align-items: start; padding: 64px 0 80px; }
.docs-nav { position: sticky; top: var(--sp-3); font-size: 13px; }
/* Item rhythm is the dashboard sidebar's, to the pixel: .cl-side is 220px with
   14px/10px padding and a 2px gap, .cl-nav items are 7px/8px, section heads
   12px/8px/4px. Same object, same spacing, whichever app you are in. */
.docs-nav-head { font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); margin: 0; padding: 26px 10px 10px; }
/* Only the first group, not every group. Each one is its own <section>, so its
   label is always :first-child there, and the rule was cancelling the 26px
   above every heading: the groups ran straight into the list above them. */
.docs-nav section:first-child .docs-nav-head { padding-top: 0; }
.docs-nav ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
/* Items darker than the labels above them. Both were the same grey, so a
   group heading read as one more entry in the list. */
.docs-nav a { display: block; color: var(--ink-2); padding: 7px 10px; border-radius: var(--r); }
.docs-nav a:hover { background: var(--tint); color: var(--ink); }
.docs-nav a[aria-current="page"] { background: var(--tint-selected); color: var(--cobalt); font-weight: 500; }
.docs-toc { position: sticky; top: var(--sp-3); font-size: 13px; }
.docs-toc-head { font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); margin: 0 0 12px; padding: 0; }
.docs-toc ul { list-style: none; margin: 0; padding: 0 0 0 14px; border-left: 1px solid var(--line); display: flex; flex-direction: column; gap: 8px; }
.docs-toc a { display: block; color: var(--ink-2); }
.docs-toc a:hover { color: var(--cobalt-dark); }
.docs-toc .toc-sub a { padding-left: 14px; }
.docs-article { max-width: 660px; min-width: 0; }
.docs-article h1 { font-size: 34px; font-weight: 700; letter-spacing: -0.035em; line-height: 1.2; margin: var(--sp-1) 0 12px; }
/* The paragraph under the title introduces the page rather than continuing
   it: one step down in size, and a clear gap before the first section. */
.docs-article h1 + p { font-size: 14.5px; line-height: 1.7; color: var(--ink-2); margin-bottom: var(--sp-4); }
.docs-article h2 { font-size: 21px; font-weight: 700; letter-spacing: -0.025em; line-height: 1.3; margin: var(--sp-5) 0 var(--sp-2); scroll-margin-top: var(--sp-2); }
.docs-article h3 { font-family: var(--sans); font-size: 15px; font-weight: 700; letter-spacing: 0; margin: var(--sp-3) 0 var(--sp-1); scroll-margin-top: var(--sp-2); }
.docs-article h4 { font-family: var(--sans); font-size: 13px; font-weight: 700; letter-spacing: 0.02em; text-transform: uppercase; color: var(--ink-2); margin: var(--sp-3) 0 var(--sp-1); }
.docs-article p, .docs-article li { font-size: 15px; line-height: 1.7; margin: 0 0 12px; }
.docs-article ul { padding-left: 20px; }
.docs-article a { color: var(--cobalt); }
.docs-article a:hover { color: var(--cobalt-dark); text-decoration: underline; }
.docs-article .docs-kicker, .docs-kicker { font-size: 12px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); margin: 0 0 var(--sp-1); }

/* Code. No highlighter ships, so the block has to read as plain monospace on a
   surface. Long lines scroll inside the block and never widen the page. */
.docs-article code, .doc code { font-family: var(--mono); font-size: 13px; background: var(--tint-2); border: 1px solid var(--line); border-radius: 4px; padding: 1px 5px; color: var(--ink); }
.code { margin: var(--sp-3) 0; border-radius: var(--r-lg); background: var(--canvas); overflow: hidden; }
.code-head { display: flex; justify-content: space-between; align-items: center; gap: var(--sp-2); padding: 8px 16px; background: rgba(255, 255, 255, 0.045); }
.code-file { font-family: var(--mono); font-size: 12px; color: var(--white); }
.code-lang { font-size: 10.5px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--cobalt-tint); }
.code pre { margin: 0; padding: var(--sp-2) 20px; overflow-x: auto; white-space: pre-wrap; overflow-wrap: anywhere; font-family: var(--mono); font-size: 13px; line-height: 1.7; color: var(--ink-tint); }
.code pre code { all: unset; font: inherit; color: inherit; }

.callout { border-left: 3px solid var(--disabled); border-radius: 0 var(--r-md) var(--r-md) 0; padding: 14px 18px; margin: var(--sp-3) 0; font-size: 14px; line-height: 1.6; }
.callout p { margin: 0; font-size: 14px; }
.callout-label { display: block; font-size: 10.5px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; margin-bottom: 5px; }
.callout--note { background: var(--tint); color: var(--ink-2); }
.callout--note .callout-label { color: var(--muted); }
.callout--warn { background: #faf4ea; border-left-color: #dac5a0; color: var(--ink-2); }
.callout--warn .callout-label { color: #805718; }
/* The security asides say "this key stays on your server", which is guidance to
   follow, not an alarm to survive. A red box around every one of them spent the
   page's loudest treatment on something the reader has not done wrong yet. */
.callout--security { background: var(--tint-selected); border-left-color: var(--cobalt); color: var(--ink-2); }
.callout--security .callout-label { color: var(--cobalt-dark); }
.callout--security strong { color: var(--ink); }

/* A wide table scrolls inside its own surface, same rule as a code block. */
.params-wrap { overflow-x: auto; margin: var(--sp-3) 0; border: 1px solid var(--line); border-radius: var(--r-lg); background: var(--white); }
.params { border-collapse: collapse; width: 100%; min-width: 560px; font-size: 13.5px; }
.params th { text-align: left; font-size: 11.5px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); padding: 10px 16px; background: var(--surface-2); border-bottom: 1px solid var(--line); }
.params td { padding: 10px 16px; border-bottom: 1px solid var(--line-3); vertical-align: top; line-height: 1.55; }
.params tr:last-child td { border-bottom: 0; }
.params .req { font-size: 10.5px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--vote); }
.params .opt { font-size: 10.5px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--faint); }

.docs-pager { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-2); margin-top: var(--sp-6); }
.pager-link { display: block; border: 1px solid var(--line); border-radius: var(--r-lg); padding: 14px 18px; background: var(--white); transition: border-color .14s ease; }
.pager-link:hover { border-color: var(--line-2); color: inherit; }
.pager-link--next { text-align: right; grid-column: 2; }
.pager-cap { display: block; font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.pager-title { display: block; font-size: 14px; font-weight: 600; color: var(--cobalt); margin-top: 3px; }

/* Docs-only structural breakpoint: the three columns need ~1100px before the
   article drops under a readable measure. Content styles keep the 900/600 steps. */
@media (max-width: 1100px) {
  .docs-layout { grid-template-columns: 1fr; gap: var(--sp-4); }
  .docs-nav { position: static; order: 1; display: flex; gap: var(--sp-4); flex-wrap: wrap; border-bottom: 1px solid var(--line-3); padding-bottom: var(--sp-3); }
  .docs-nav section { min-width: 150px; }
  .docs-toc { position: static; order: 2; border-left: 0; border: 1px solid var(--line); border-radius: var(--r-lg); padding: 14px 18px; background: var(--white); }
  .docs-article { order: 3; }
}
@media (max-width: 900px) {
  .page-hero { padding: 56px 0 40px; }
  /* Stacked, the art would only push the copy down: it is decoration, so it
     leaves instead. Card icons stay, they mark the card they sit on. */
  .page-hero--art, .panel-tint--art { display: block; }
  .page-hero-art, .panel-art { display: none; }
  .page-h1, .doc-h1 { font-size: 36px; }
  .plans { grid-template-columns: 1fr; }
  .feature-row { grid-template-columns: 1fr; gap: var(--sp-1); }
  .feature-p { margin-top: var(--sp-1); }
  .faq-grid { grid-template-columns: 1fr; }
}
@media (max-width: 600px) {
  .page-h1, .doc-h1 { font-size: 32px; }
  .cards-2 { grid-template-columns: minmax(0, 1fr); }
  .panel-tint { padding: 24px 20px; }
  .plan-top, .plan-body, .plan-foot { padding-left: 20px; padding-right: 20px; }
  .cta-actions { flex-direction: column; align-items: stretch; }
  .cta-actions .m-btn { width: 100%; }
  .action-card { padding: var(--sp-4) 20px; }
  /* A column of flex: 1 items has no height to share out: the field collapsed
     to a sliver. It keeps its own height instead. */
  .action-form { flex-direction: column; }
  .action-form .m-input, .action-form .m-btn { flex: none; width: 100%; min-width: 0; }
  .docs-article h1 { font-size: 26px; }
  .code pre { padding: 14px 16px; }
  .docs-pager { grid-template-columns: 1fr; }
  .pager-link--next { grid-column: auto; }
}

/* ============================================================================
   PORTAL — DIRECTION B, "THE LEDGER"
   Ported from the approved mockup (scratchpad/direction-b.html). A public
   record, not a forum: ruled rows, a rail that shows how far each request has
   travelled, and a receipt at the end.

   Scoped to body.portal on purpose — the dashboard shares this stylesheet and
   must not inherit these token values.

   Two deliberate departures from the mockup, both on Morgan's call:
   - the board's vote control became read-only chips (axe 01), and the
     request page casts the vote from its rail;
   - no dark mode, so only the light :root block is ported.
   ========================================================================== */
body.portal {
  /* Direction A, same values as :root: a white working surface, near-black
     titles, grey metadata. Restated here so the portal reads whole in one
     place; only --portal-accent and its derivatives are the portal's own. */
  --bg:      #ffffff;
  --paper:   #ffffff;
  --paper-2: #fafafb;
  --ink:     #202124;
  --ink-2:   #303238;
  --muted:   #60646c;
  /* 5.9:1 on paper. The old #a5a2b8 measured 2.48:1 and carried six real
     strings, including the only place ▲ and △ are ever explained. Under A the
     faint and muted greys are one value: nothing that carries text sits under
     AA, so there is no lighter step left to take. */
  --faint:   #60646c;
  /* --line: surfaces and secondary controls. --line-2: fields only. */
  --line:    #e4e5e7;
  --line-2:  #8b8f97;
  --line-3:  #ecedef;

  /* The client's accent. Document.tsx overrides --portal-accent from the
     workspace colour; everything else on the portal derives from it. The
     dashboard cobalt is a presentation token and never reaches this page, so
     a workspace that set no colour falls back to the portal's own ink. */
  --portal-accent: var(--ink);
  --accent:      var(--portal-accent);
  --accent-ink:  color-mix(in srgb, var(--portal-accent) 85%, #000);
  --accent-soft: color-mix(in srgb, var(--portal-accent) 9%, #fff);

  /* Verified (solid, green): never used for anything but verified votes. */
  --ver:        #28624f;
  --ver-text:   #28624f;
  --ver-bg:     #f0f6f3;
  --ver-border: #adc8bb;
  /* Anonymous (dashed, grey): never green, never merged. */
  --anon:        #60646c;
  --anon-border: #a2a6ae;

  /* Items, buttons and fields 6; cards and surfaces 8. */
  --r:    6px;
  --r-md: 6px;
  --r-lg: 8px;
  --r-xl: 8px;

  background: var(--bg);
  color: var(--ink);
}
body.portal :focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 3px; }
/* Product type, not marketing type: the portal's headings are the system stack
   (direction A). :where() keeps this at one element's weight, so every class
   rule below that sizes or weights a heading still wins. */
:where(body.portal) :is(h1, h2, h3, h4) { font-family: var(--sans); letter-spacing: -0.015em; }
/* The mockup framed each page in a .screen card floating on --bg; live, the
   page IS the screen, so it grows to fill the viewport and keeps the seal at
   the bottom. Content sits in a centred column, bands stay full-bleed. */
body.portal .screen { flex: 1; display: flex; flex-direction: column; background: var(--paper); }
body.portal .screen > .seal { margin-top: auto; }
/* THE container. One 1040px column, header to footer (direction A): the bar
   used to run full-bleed on its own 32px edge, so the brand and the title under
   it started at two different x. Every band pads to the column, every wrap
   margins to it, the bar included; only backgrounds and rules stay full-bleed.
   `100%` resolves against .screen, which is every one of these elements'
   containing block. 40px gutters, 20px on a phone. */
body.portal { --gutter: max(40px, calc((100% - 1040px) / 2)); }
@media (max-width: 800px) { body.portal { --gutter: 20px; } }
/* Bands: full-bleed background, content padded in to the column. */
body.portal .mast-bar,
body.portal .mast-body,
body.portal .bd-filters,
body.portal .bd-foot,
body.portal .bd-suggest,
body.portal .si-wrap,
body.portal .my-wrap,
body.portal .nm-wrap,
body.portal .nf-wrap,
body.portal .seal { padding-left: var(--gutter); padding-right: var(--gutter); }
/* Wraps and cards: the box itself stops at the column, so its own padding is
   the inside of a card and must not double as the gutter. */
body.portal .pd-wrap,
body.portal .rm-wrap,
body.portal .cg-wrap { margin-left: var(--gutter); margin-right: var(--gutter);
                       padding-left: 0; padding-right: 0; max-width: none; }
/* The permalink keeps a reading column inside the container, like the request
   page's document side; its own max-width is the point, so it is not cleared. */
body.portal .ep-wrap { margin-left: var(--gutter); margin-right: var(--gutter);
                       padding-left: 0; padding-right: 0; }
body.portal .bd-table,
body.portal .bd-empty { margin-left: var(--gutter); margin-right: var(--gutter); }
/* Inside a band that already pads to the column. */
body.portal .my-wrap .bd-empty { margin-left: 0; margin-right: 0; }

/* ---- THE header partial (identical on every page) -------------------------
   Two bands and nothing else: a 72px bar on the content column, then the page
   header. What used to be here (a 54px cover title repeating the active tab, a
   lead repeating the rail, a dark band that was a dashboard token) cost two
   thirds of a screen before the first row of data. */
.mast-bar { min-height: 72px; display: flex; align-items: center; gap: 20px;
            background: var(--paper); border-bottom: 1px solid var(--line-3); }
/* Workspace / board, as a path: the workspace is the brand, the board a quieter
   label after the slash. */
.mast-id { display: flex; align-items: center; min-width: 0; }
.mast-brand { display: flex; align-items: center; gap: 9px; flex: none; font-size: 15px; font-weight: 700; color: var(--ink); }
.mast-brand:hover { color: var(--ink); }
.mast-mark { width: 27px; height: 27px; flex: none; border-radius: 7px; background: var(--portal-accent);
             color: var(--accent-contrast, #fff); display: inline-flex; align-items: center; justify-content: center;
             font-family: var(--sans); font-weight: 700; font-size: 13px; letter-spacing: -.02em; }
.mast-mark.logo-img { object-fit: cover; }
.mast-sep { margin: 0 14px; font-size: 15px; color: var(--muted); }
.mast-board { min-width: 0; font-size: 13px; font-weight: 500; color: var(--ink);
              white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mast-board:hover { color: var(--accent-ink); }
/* Brand left, links and CTA right. The current link takes the selection
   treatment: a wash of the workspace accent, with the accent as text. */
.mast-nav { display: flex; align-items: center; margin-left: auto; }
.mast-nav a { font-size: 13px; font-weight: 500; color: var(--ink-2);
              padding: 7px 10px; border-radius: var(--r); white-space: nowrap; }
.mast-nav a:hover { background: var(--tint); color: var(--ink); }
.mast-nav a[aria-current] { background: var(--accent-soft); color: var(--accent-ink); }
/* Persistent chrome, not the page's action: direction A's one secondary
   button. The fill belongs to whatever the page is for (the vote on a request
   page); the client's colour stays in the mark and the current link. */
.mast-nav .mast-cta { margin-left: 12px; display: inline-flex; align-items: center; min-height: 34px;
                      padding: 6px 12px; border: 1px solid var(--line); background: var(--paper); color: var(--ink); }
.mast-nav .mast-cta:hover { background: var(--tint); border-color: var(--line-2); color: var(--ink); }

/* Phones: the same links in a native disclosure, so it opens with JavaScript
   off (§9). Hidden here, shown under 800px in place of the inline links. */
.mast-menu { display: none; position: relative; margin-left: auto; }
.mast-menu-btn { display: inline-flex; align-items: center; gap: 7px; min-height: 36px; padding: 6px 12px;
                 border: 1px solid var(--line); border-radius: var(--r); background: var(--paper);
                 color: var(--ink); font-size: 13px; font-weight: 500; cursor: pointer;
                 list-style: none; user-select: none; }
.mast-menu-btn::-webkit-details-marker { display: none; }
.mast-menu-btn svg { fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; }
.mast-menu-btn:hover, .mast-menu[open] .mast-menu-btn { background: var(--tint); }
.mast-menu-panel { position: absolute; right: 0; top: calc(100% + 8px); z-index: 20; width: 240px;
                   max-width: calc(100vw - 40px); padding: 8px; display: flex; flex-direction: column; gap: 2px;
                   background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-lg);
                   box-shadow: 0 4px 20px rgba(0, 0, 0, .08); }
.mast-menu-board { margin: 0 0 4px; padding: 6px 10px 10px; border-bottom: 1px solid var(--line-3);
                   font-size: 12px; color: var(--muted); }
.mast-menu-panel a { display: flex; align-items: center; min-height: 44px; padding: 0 10px;
                     border-radius: var(--r); font-size: 14px; color: var(--ink); }
.mast-menu-panel a:hover { background: var(--tint); color: var(--ink); }
.mast-menu-panel a[aria-current] { background: var(--accent-soft); color: var(--accent-ink); font-weight: 500; }
.mast-menu-panel .mast-cta { justify-content: center; margin-top: 6px; border: 1px solid var(--line); font-weight: 500; }

/* The page header: the title, and the only numbers the nav does not already
   state. Detail pages put the breadcrumb here instead. */
.mast-body { display: flex; align-items: baseline; justify-content: space-between;
             gap: 8px 24px; flex-wrap: wrap; padding-top: 40px; }
.mast-body:has(.mast-back) { padding-top: 28px; }
.mast-h1 { font-size: 26px; font-weight: 700; letter-spacing: -.025em; line-height: 1.25;
           margin: 0; color: var(--ink); }
/* The board's own description under its name, when the team wrote one. */
.mast-titles { min-width: 0; }
.mast-lede { font-size: 15px; line-height: 1.5; color: var(--muted); margin: 6px 0 0; max-width: 60ch; }
.mast-stats { font-size: 13px; line-height: 1.5; color: var(--muted); margin: 0; }
.mast-stats b { color: var(--ink); font-weight: 600; font-variant-numeric: tabular-nums; }
.mast-stats a { color: var(--accent-ink); font-weight: 500; }
.mast-stats a:hover { color: var(--ink); }
.mast-back { font-size: 13px; color: var(--muted); }
.mast-back:hover { color: var(--accent-ink); }
/* On a dead end (a missing page, a sign-in step) there are no tabs to show. */
.mast-note { margin-left: auto; font-size: 13px; color: var(--muted); }

/* The account corner, last in the bar. Signed out, one quiet link; signed in,
   the account in a native disclosure (no JavaScript, §9) holding My requests
   and Sign out. */
.mast-nav .mast-signin { margin-left: 4px; }
.mast-acct { position: relative; margin-left: 10px; }
.mast-acct-btn { display: inline-flex; align-items: center; gap: 8px; min-height: 34px; max-width: 220px;
                 padding: 4px 12px 4px 4px; border: 1px solid var(--line); border-radius: 99px;
                 background: var(--paper); color: var(--ink); font-size: 13px; font-weight: 500;
                 cursor: pointer; list-style: none; user-select: none; }
.mast-acct-btn::-webkit-details-marker { display: none; }
.mast-acct-btn:hover, .mast-acct[open] .mast-acct-btn { background: var(--tint); }
.mast-acct-av { width: 24px; height: 24px; flex: none; border-radius: 50%; display: inline-flex;
                align-items: center; justify-content: center; background: var(--accent-soft);
                color: var(--accent-ink); font-size: 11px; font-weight: 700; }
.mast-acct-l { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mast-acct-panel { position: absolute; right: 0; top: calc(100% + 8px); z-index: 20; min-width: 200px;
                   padding: 6px; display: flex; flex-direction: column; gap: 2px;
                   background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-lg);
                   box-shadow: 0 4px 20px rgba(0, 0, 0, .08); }
.mast-acct-panel form { margin: 0; }
.mast-acct-panel a, .mast-acct-panel button, .mast-menu-acct button {
  display: flex; align-items: center; width: 100%; min-height: 36px; padding: 0 10px; border: 0;
  border-radius: var(--r); background: none; font: inherit; font-size: 13px; font-weight: 500;
  color: var(--ink); text-align: left; cursor: pointer; }
.mast-acct-panel a:hover, .mast-acct-panel button:hover, .mast-menu-acct button:hover { background: var(--tint); color: var(--ink); }
/* The same account, inside the phone menu. */
.mast-menu-acct { display: flex; flex-direction: column; gap: 2px; margin-top: 6px; padding-top: 6px;
                  border-top: 1px solid var(--line-3); }
.mast-menu-acct form { margin: 0; }
.mast-menu-acct button { min-height: 44px; font-size: 14px; font-weight: 400; }
.mast-menu-me { margin: 0; padding: 6px 10px; font-size: 12px; color: var(--muted); overflow-wrap: anywhere; }
.mast-menu-panel .mast-signin { justify-content: center; margin-top: 6px; border: 1px solid var(--line); font-weight: 500; }


/* ---- the one-time name step ----------------------------------------------- */
.nm-wrap { padding: 48px 0 48px; }
.nm-wrap > * { max-width: 44ch; }
.nm-k { font-size: 11px; font-weight: 500; letter-spacing: .05em; text-transform: uppercase;
        color: var(--muted); margin: 0; }
.nm-h { font-size: 24px; font-weight: 700; letter-spacing: -.025em; line-height: 1.25; margin: 8px 0 0; }
.nm-p { font-size: 14px; line-height: 1.6; color: var(--muted); margin: 8px 0 0; }
.nm-form { display: flex; gap: 8px; flex-wrap: wrap; margin: 20px 0 0; max-width: 44ch; }
.nm-skip { font-size: 13px; margin: 12px 0 0; }
.nm-skip a { color: var(--muted); }
.nm-skip a:hover { color: var(--accent-ink); }

/* ---- a dead end inside a portal (axe 12) ----------------------------------
   The 404, an expired link, the "check your email" step. */
.nf-wrap { padding: 64px 0 48px; text-align: center; }
.nf-k { font-size: 11px; font-weight: 500; letter-spacing: .05em; text-transform: uppercase;
        color: var(--muted); margin: 0; }
.nf-h { font-size: 24px; font-weight: 700; letter-spacing: -.025em; line-height: 1.25; margin: 8px 0 0; }
.nf-p { font-size: 14px; line-height: 1.6; color: var(--muted); margin: 8px 0 0; }
/* Development only: the mailed link, long and unbroken, so it may wrap anywhere. */
.nf-dev { font-size: 12px; line-height: 1.6; margin: 12px 0 0; word-break: break-all; }
.nf-dev a { color: var(--muted); text-decoration: underline; text-underline-offset: 2px; }
.nf-do { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; margin: 24px 0 0; }
/* Last, on purpose: every rule above sets `margin` as a shorthand, which resets
   left and right to 0 and drops the column out of the centre. */
.nf-wrap > * { max-width: 44ch; margin-inline: auto; }

/* ---- the rail: five stages, and a ring that closes ------------------------ */

/* ---- board ---------------------------------------------------------------- */
/* Two rows: the stages first, then sort on the left and areas on the right.
   Every row wraps instead of scrolling sideways, so no filter is ever cut at
   the right edge and nothing on the page scrolls horizontally. */
.bd-filters { display: flex; flex-direction: column; gap: 16px; padding-top: 28px; padding-bottom: 16px; }
/* Direction A filters: flat text chips, no outline. The selected one takes the
   selection treatment, a light wash with the accent as text, the same move the
   dashboard makes in cobalt. */
.bd-stages { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.bd-stages > a[aria-current] { background: var(--accent-soft); color: var(--accent-ink); font-weight: 500; }
.bd-pill, .bd-stages > a { display: inline-flex; align-items: center; gap: 6px;
                           font-size: 13px; font-weight: 400; color: var(--muted);
                           background: transparent; border: 1px solid transparent;
                           border-radius: var(--r); padding: 5px 10px; white-space: nowrap; }
.bd-stages > a:hover { color: var(--ink); background: var(--tint); }
.bd-stages > a[aria-current]:hover { background: var(--accent-soft); color: var(--accent-ink); }
/* The active stage carries its own removal, so one axis can be dropped without
   throwing away the other: "clear everything" is the exit that destroys the
   most deliberate choice along with the accidental one. */
.bd-pill--on { background: var(--accent-soft); color: var(--accent-ink); font-weight: 500; padding: 5px 4px 5px 10px; }
.bd-pill--on a { display: inline-flex; align-items: center; justify-content: center;
                 width: 20px; height: 20px; border-radius: var(--r); font-size: 10px; line-height: 1; color: var(--accent-ink); }
.bd-pill--on a:hover { background: color-mix(in srgb, var(--portal-accent) 14%, transparent); color: var(--accent-ink); }

.bd-axes { display: flex; align-items: center; justify-content: space-between; gap: 12px 24px; flex-wrap: wrap;
           font-size: 12px; color: var(--muted); }
.bd-ax { display: flex; align-items: center; gap: 4px 14px; flex-wrap: wrap; }
.bd-ax-lab { font-size: 10px; font-weight: 500; letter-spacing: .08em;
             text-transform: uppercase; color: var(--muted); }
.bd-ax a { font-size: 12px; font-weight: 400; color: var(--ink-2); }
.bd-ax a:hover { color: var(--accent-ink); }
.bd-ax a[aria-current] { color: var(--accent-ink); font-weight: 600; }

/* The ledger itself. The description and the chip row moved to the request
   page, which says both better; what is left is what a reader scans on:
   support, title, age, stage.
   Direction A: a ruled table, not a card. No outer frame and no shadow; the
   head sits on the support grey and 1px rules separate the lines. Each cell
   carries its own 16px inset, so the columns line up with the head's labels. */
.bd-table { background: var(--paper); }
.bd-head, .bd-row { display: grid; grid-template-columns: 150px minmax(0, 1fr) 95px 135px; align-items: center; }
.bd-head > span, .bd-row > span { min-width: 0; padding-inline: 16px; }
.bd-head { padding-block: 12px; border-block: 1px solid var(--line-3); background: var(--tint);
           font-size: 11px; letter-spacing: .05em; text-transform: uppercase;
           font-weight: 500; color: var(--muted); }
/* Left, not right: the stage column is scanned vertically, and right alignment
   gave every chip a different starting x, so the eye had to re-find the front of
   each one down the whole ledger. */
.bd-head .h-r, .bd-stage-cell { text-align: left; }

.bd-rows { list-style: none; margin: 0; padding: 0; }
.bd-row { min-height: 55px; padding-block: 12px; border-bottom: 1px solid var(--line-3); }
.bd-row:hover { background: var(--paper-2); }

.bd-t { display: flex; align-items: baseline; gap: 8px; }
/* Titles wrap rather than truncate: a request is read by its title, and an
   ellipsis hid the half that told two similar requests apart. */
.bd-title { font-size: 14px; font-weight: 500; line-height: 1.45; letter-spacing: 0; color: var(--ink);
            overflow-wrap: anywhere; }
.bd-row:hover .bd-title { color: var(--accent-ink); }
.bd-posted { font-size: 13px; color: var(--muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
/* "Caught:" is the column head on a desktop, where it stays for screen readers
   only (the head is aria-hidden); a phone card shows it. */
.bd-lab { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* One vote figure (hard rule #4), and on the board and the request page it is
   the vote itself: a native form button, ▲ before a vote and ✓ after, in the
   workspace's accent once it is yours. The ✓ is this session's, which is why
   the page cannot be cached without a session key. Read-only (a span) on the
   roadmap and the changelog. */
.vb-form { display: inline-flex; margin: 0; }
.vb { display: inline-flex; align-items: center; justify-content: center; gap: 5px; min-width: 52px;
      height: 32px; padding: 0 10px; border: 1px solid var(--line-2); border-radius: var(--r-md);
      background: var(--paper); color: var(--ink); font: inherit; font-size: 13px; font-weight: 600;
      line-height: 1; font-variant-numeric: tabular-nums; white-space: nowrap; }
button.vb { cursor: pointer; }
button.vb:hover { border-color: var(--accent); }
.vb-g { font-size: 10px; color: var(--muted); }
.vb--on { background: var(--accent-soft); border-color: var(--accent); color: var(--accent-ink); }
.vb--on .vb-g { color: var(--accent-ink); }
/* Roadmap, changelog and My requests register. */
.vb--sm { min-width: 0; height: 22px; padding: 0 7px; gap: 4px; border-radius: 4px; font-size: 11.5px; }
.vb--sm .vb-g { font-size: 8px; }
/* The request page's rail: the verb spelled out, the figure at the end. The
   accent fill on that page belongs to the vote, which is what it is for. */
.vb-form:has(.vb--lg) { display: flex; }
.vb--lg { width: 100%; height: 44px; justify-content: flex-start; gap: 8px; padding: 0 14px; font-size: 14px; }
.vb--lg .vb-n { margin-left: auto; font-size: 17px; font-weight: 700; }
button.vb--lg:not(.vb--on) { background: var(--accent); border-color: transparent; color: var(--accent-contrast, #fff); }
button.vb--lg:not(.vb--on) .vb-g { color: inherit; }
button.vb--lg:not(.vb--on):hover { background: var(--accent-ink); color: #fff; }
span.vb--lg { background: var(--tint); }

/* Status as a compact badge, one modifier per `tone` of the status table in
   @catchloop/shared. The colours are the status's, not the client's: they mean
   the same thing on every portal, so they are not brand.

   Values are `packages/ui/src/tokens.css` §status, which is the canonical status
   palette: the dashboard already renders from it, so the same request reads the
   same colour to the admin and to their customer. Two things this fixed and
   must not regress: Shipped never borrows the vote chip's green, and New is
   never byte-identical to Not planned, which are the two statuses a reader most
   needs to tell apart. */
.bd-stage { display: inline-flex; align-items: center; gap: 6px; padding: 3px 8px;
            border-radius: 99px; font-size: 12px; font-weight: 500; line-height: 1.4; white-space: nowrap; }
.bd-stage i { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.bd-stage b { font-size: 10px; font-weight: 700; }
/* Direction A: each status differs by label, sign and hue, never by hue alone.
   A dot until it ships, a tick once shipped, a cross when not planned. */
.bd-stage--new { background: #f1f3f6; color: #4e5968; }
.bd-stage--new i { background: var(--st-new); }
.bd-stage--planned { background: #f0f4f8; color: #3f5d7d; }
.bd-stage--planned i { background: var(--st-planned); }
.bd-stage--in-progress { background: #faf4ea; color: #805718; }
.bd-stage--in-progress i { background: var(--st-in-progress); }
.bd-stage--shipped { background: #eff6f0; color: #31653e; }
/* The tick is a mask so it takes the stage colour like the dots do. Shared with
   the changelog's Shipped pill and the release permalink's (::before). */
.bd-stage--shipped i, .ep-pill::before, .cg-row-pill::before, .rm-dot--shipped {
  width: 10px; height: 10px; border-radius: 0; background: var(--st-shipped);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 6.4 5 8.8l4.6-5.6' fill='none' stroke='%23000' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 6.4 5 8.8l4.6-5.6' fill='none' stroke='%23000' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat; }
.bd-stage--not-planned { background: var(--status-bg); color: var(--muted); }

/* What the list holds, and the glyph key, read once under the data. */
.bd-foot { display: flex; align-items: center; justify-content: space-between; gap: 6px 20px;
           flex-wrap: wrap; padding-top: 12px; margin: 0; font-size: 12px; color: var(--muted); }

/* suggest block: a section under a rule, not a card. The pitch on the left,
   the form on the right, on the same 1:2 split as the mockup. */
.bd-suggest { padding-top: 40px; padding-bottom: 48px; scroll-margin-top: 16px; }
.bd-sg-card { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); gap: 48px;
              align-items: start; padding-top: 32px; border-top: 1px solid var(--line); }
/* Arriving from "Suggest a request": the page has just scrolled somewhere the
   visitor did not choose to look, so the form says which part of it is the
   answer. The field carries the signal, since that is where the cursor lands.
   :target, so it works with JS off and clears itself on the next link (the
   client adds .is-arriving to replay it). The motion carries the arrival and
   then gets out of the way: a ring that stays is a permanent mark on a form
   nobody asked to decorate. */
@media (prefers-reduced-motion: no-preference) {
  /* Linear on purpose: an eased curve would stretch the second beat and the two
     would stop reading as a pair. The decay is carried by the amplitudes, not
     by the timing. */
  .bd-suggest:target .bd-sg-form textarea,
  .bd-sg-card.is-arriving .bd-sg-form textarea { animation: sg-arrive 1.6s linear; }
  /* Two knocks, not a fade. Each one starts tight and saturated and is thrown
     wide, because a ring that fades from wide reads as something leaving the
     screen, which is the opposite of what just happened. The wash rides the
     first beat only: twice would tint the field rather than flash it. */
  @keyframes sg-arrive {
    0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--portal-accent) 50%, transparent);
           border-color: var(--portal-accent);
           background: color-mix(in srgb, var(--portal-accent) 8%, var(--paper)); }
    14%  { box-shadow: 0 0 0 10px color-mix(in srgb, var(--portal-accent) 24%, transparent);
           border-color: var(--portal-accent);
           background: color-mix(in srgb, var(--portal-accent) 5%, var(--paper)); }
    32%  { box-shadow: 0 0 0 0 transparent; border-color: var(--line-2); background: var(--paper); }
    46%  { box-shadow: 0 0 0 0 color-mix(in srgb, var(--portal-accent) 42%, transparent);
           border-color: var(--portal-accent); }
    60%  { box-shadow: 0 0 0 8px color-mix(in srgb, var(--portal-accent) 18%, transparent);
           border-color: var(--portal-accent); }
    100% { box-shadow: 0 0 0 0 transparent; border-color: var(--line-2); background: var(--paper); }
  }
}
/* No motion to carry it, so the marking has to stay or the arrival is not
   communicated at all. This is the one case where the ring outlives the flash. */
@media (prefers-reduced-motion: reduce) {
  .bd-suggest:target .bd-sg-form textarea { border-color: var(--portal-accent);
                                            box-shadow: 0 0 0 4px var(--accent-soft); }
}
.bd-suggest h2 { font-size: 20px; font-weight: 700; letter-spacing: -.025em; line-height: 1.25; margin: 0; }
.bd-suggest p { font-size: 13px; line-height: 1.6; color: var(--muted); margin: 8px 0 0; }
.bd-sg-form textarea { display: block; width: 100%; height: auto; min-height: 90px; padding: 9px 12px;
                       line-height: 1.5; resize: vertical; box-sizing: border-box; }
.bd-sg-go { display: flex; align-items: center; justify-content: space-between; gap: 12px 20px;
            flex-wrap: wrap; margin-top: 12px; }
.bd-sg-go span { font-size: 13px; line-height: 1.6; color: var(--muted); }

/* ---- sign in: the one email step ------------------------------------------ */
.si-wrap { padding: 48px 0 48px; }
.si-wrap > * { max-width: 52ch; }
.si-k { font-size: 11px; font-weight: 500; letter-spacing: .05em; text-transform: uppercase;
        color: var(--muted); margin: 0; }
.si-h { font-size: 24px; font-weight: 700; letter-spacing: -.025em; line-height: 1.25; margin: 8px 0 0; overflow-wrap: anywhere; }
.si-p { font-size: 14px; line-height: 1.6; color: var(--muted); margin: 8px 0 0; }
.si-p b { color: var(--ink); font-weight: 600; overflow-wrap: anywhere; }
/* The request typed before this step, so the visitor sees what they confirm. */
.si-quote { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
           background: var(--tint); border: 1px solid var(--line-3); border-radius: var(--r-md);
           padding: 10px 12px; margin: 16px 0 0; font-size: 14px; color: var(--ink-2); overflow-wrap: anywhere; }
.si-quote b { font-size: 11px; font-weight: 500; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); }
.si-form { display: flex; gap: 8px; flex-wrap: wrap; margin: 20px 0 0; }
.si-err { font-size: 13px; color: #b3261e; margin: 8px 0 0; }
.si-note { font-size: 12.5px; line-height: 1.6; color: var(--muted); margin: 16px 0 0; }
.si-note a { color: var(--muted); text-decoration: underline; text-underline-offset: 2px; }
.si-note a:hover { color: var(--accent-ink); }
.si-dev { font-size: 12px; line-height: 1.6; margin: 12px 0 0; word-break: break-all; }
.si-dev a { color: var(--muted); text-decoration: underline; text-underline-offset: 2px; }
.si-skip { font-size: 13px; margin: 16px 0 0; }
.si-skip a { color: var(--muted); }
.si-skip a:hover { color: var(--accent-ink); }

/* ---- My requests ----------------------------------------------------------- */
.my-wrap { padding-top: 28px; padding-bottom: 48px; display: flex; flex-direction: column; gap: 24px; }
.my-saved, .pd-posted { background: var(--team-bg); border: 1px solid var(--team-border); border-radius: var(--r-md);
                        padding: 10px 12px; margin: 0; font-size: 13px; font-weight: 600; color: var(--ver-text); }
.pd-posted { margin-bottom: 16px; }
.my-list { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--line-3); }
.my-row { display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto; gap: 8px 16px; align-items: center;
          padding: 14px 0; border-bottom: 1px solid var(--line-3); }
.my-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.my-title { font-size: 14px; font-weight: 500; line-height: 1.45; color: var(--ink); overflow-wrap: anywhere; }
.my-title:hover { color: var(--accent-ink); }
.my-why { font-size: 12px; color: var(--muted); }
.my-row--muted .my-title { color: var(--muted); }
.my-mute { margin: 0; }
.my-card { background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-xl); padding: 18px 20px;
           display: flex; flex-direction: column; align-items: flex-start; gap: 12px; }
.my-card form { margin: 0; }
.my-h { font-size: 15px; font-weight: 700; letter-spacing: -.01em; margin: 0; }
.my-p { font-size: 13.5px; line-height: 1.6; color: var(--muted); margin: 0; }
.my-p b { color: var(--ink); font-weight: 600; overflow-wrap: anywhere; }

/* empty state: the exit that keeps the intent */
.bd-empty-do { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; margin-top: 20px; }

/* One control height for the portal's fields and buttons, so a field and its
   button always share a line; 44px on a phone (see the 600px block).
   Qualified with `input` like .m-input: the base field rule matches
   input[type="email"], which outranks a bare class and set the field 2px
   shorter than the button beside it. */
input.inp, .inp { flex: 1; min-width: 210px; height: 36px; padding: 0 12px; font: inherit; font-size: 14px;
       border: 1px solid var(--line-2); border-radius: var(--r-md);
       background: var(--paper); color: var(--ink); }
.inp::placeholder { color: var(--muted); opacity: 1; }
/* The fill is the tenant's exact hex and is never darkened to win contrast: the
   label moves instead. --accent-contrast is chosen from the accent's luminance
   in Document.tsx; #fff is the fallback for a workspace that set no colour, and
   the default accent is --ink, on which white is correct. */
.portal .btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; height: 36px;
       padding: 0 14px; border: 1px solid transparent; border-radius: var(--r-md); font: inherit;
       font-size: 13px; font-weight: 600; line-height: 1; white-space: nowrap; cursor: pointer;
       background: var(--accent); color: var(--accent-contrast, #fff); }
.portal .btn:hover { background: var(--accent-ink); color: #fff; }
/* Same selector weight as .portal .btn, so it has to come after it or the
   secondary action renders in the accent, i.e. as a second primary. */
.portal .btn--2 { background: var(--paper); color: var(--ink); border-color: var(--line); font-weight: 500; }
.portal .btn--2:hover { background: var(--tint); color: var(--ink); border-color: var(--line-2); }

.seal { display: flex; align-items: center; justify-content: space-between; gap: 14px;
        flex-wrap: wrap; padding-top: 24px; padding-bottom: 24px; border-top: 1px solid var(--line-3);
        font-size: 12px; line-height: 1.5; color: var(--muted); }
/* Le sceau porte la note à gauche, le badge à droite. Le compte est passé dans
   l'en-tête, à côté du lien Sign in qu'il remplace. */
.seal-left { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; min-width: 0; }
/* A chip, not a bare line of text: on a free board this is the one piece of our
   own branding on someone else's page, and it has to read as a mark rather than
   as a caption that ran on. Dofollow on purpose (PRD §4) — it is the channel. */
.seal-badge { display: inline-flex; align-items: center; gap: 7px; font-size: 11px;
              font-weight: 650; letter-spacing: .04em; color: var(--muted);
              padding: 5px 12px; border: 1px solid var(--line); border-radius: var(--r-full);
              background: var(--white); transition: border-color .14s ease, color .14s ease; }
.seal-badge img { display: block; border-radius: 3px; }
.seal-badge:hover { border-color: var(--disabled); color: var(--ink); }

/* ---- post detail (axe 04) -------------------------------------------------
   Two natures, side by side instead of stacked: a document read once on the
   left, the state come back to on the right. Stacked, the state sat in third
   position and the vote went under the thread. */
/* The rail is fixed and the document takes the rest: 720px at the full column,
   narrower before the one-column switch rather than overflowing it. */
.pd-wrap { padding: 20px 0 48px; display: grid; grid-template-columns: minmax(0, 1fr) 280px;
           gap: 40px; align-items: start; }
.pd-doc { min-width: 0; }
.pd-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.pd-h1 { font-size: 26px; font-weight: 700; letter-spacing: -.025em; line-height: 1.25;
         margin: 0; text-wrap: balance; }
.pd-sub { font-size: 13px; color: var(--muted); margin: 8px 0 0; }

/* What happened, in order, with three node states. One state for all five was
   a diagram that said nothing: a shipped request drew five identical dots, and
   a building one drew no "not yet" at all. Geometry is the axe 03 mockup's:
   14px nodes, a 3px connector, labels 10.5 caps and dates 11.5. */
.pd-track { list-style: none; display: flex; align-items: flex-start; gap: 0;
            margin: 26px 0 0; padding: 0; }
.pd-track li { position: relative; flex: 1; padding-top: 22px; padding-right: 8px; min-width: 0; }
.pd-track li::before { content: ""; position: absolute; left: 7px; right: 0; top: 5px; height: 3px;
                       background: var(--disabled); }
.pd-track li:last-child::before { display: none; }
.pd-track li::after { content: ""; position: absolute; left: 0; top: 0; width: 14px; height: 14px;
                      border-radius: 50%; background: var(--paper); box-sizing: border-box;
                      box-shadow: inset 0 0 0 2.5px var(--disabled); }
.pd-t-name { display: block; font-size: 10.5px; font-weight: 700; letter-spacing: .08em;
             text-transform: uppercase; color: var(--faint); }
.pd-t-date { display: block; font-size: 11.5px; color: var(--faint); margin-top: 2px;
             font-variant-numeric: tabular-nums; }

/* Passed: filled, and so is the segment that leads out of it. Only `is-done`
   paints its connector: a stage's ::before is the run to the NEXT node, and the
   one leaving the current stage has not been travelled yet. */
.pd-track li.is-done::after { background: var(--portal-accent); box-shadow: none; }
.pd-track li.is-done::before { background: var(--portal-accent); }
.pd-track li.is-done .pd-t-name { color: var(--ink-2); }
.pd-track li.is-done .pd-t-date { color: var(--muted); }
/* Where it sits now: ringed, not filled, so "reached" and "here" never look
   alike. The halo is what carries the eye to it on a five-node line. */
.pd-track li.is-now::after { background: var(--paper);
                             box-shadow: inset 0 0 0 3.5px var(--portal-accent),
                                         0 0 0 4px color-mix(in srgb, var(--portal-accent) 20%, transparent); }
.pd-track li.is-now .pd-t-name { color: var(--accent-ink); }
.pd-track li.is-now .pd-t-date { color: var(--ink-2); font-weight: 600; }
/* Still ahead: hollow, and its own date says so.
   `.is-next::before` is left at --disabled by the base rule. */

/* A refusal is muted end to end: a loop that never closed is not brand
   coloured, and the stages it will never reach are not drawn at all (axe 03).
   Matched on `li.is-done` rather than `li`, or the accent rules above outrank
   these and a refused request comes out in the customer's colour. */
.pd-track--off li::after,
.pd-track--off li.is-done::after { background: var(--muted); box-shadow: none; }
.pd-track--off li::before,
.pd-track--off li.is-done::before { background: var(--muted); }
.pd-track--off .pd-t-name { color: var(--ink-2); }
/* The terminus. Round like every other node, so the line reads as one object,
   and bigger with a ✕ inside so the end of the walk is unmistakable. It is
   22px against the others' 14px, so it needs lifting by half that difference
   or its centre hangs below the connector it sits on. */
.pd-t-end { flex: 0 0 auto; padding-right: 0; }
.pd-track li.pd-t-end::after { top: -4px; width: 22px; height: 22px; border-radius: 50%;
                   background: var(--ink-2); content: "\2715"; color: #fff;
                   font-size: 11px; font-weight: 800; line-height: 1;
                   display: flex; align-items: center; justify-content: center; box-shadow: none; }
.pd-track li.pd-t-end .pd-t-name { color: var(--ink); }

.pd-body { white-space: pre-line; font-size: 15px; line-height: 1.7; color: var(--ink-2); margin: 26px 0 0;
           max-width: 60ch; text-wrap: pretty; }

/* Why it is not planned: the one piece of content that justifies a refused request
   having a page at all. */
.pd-why { background: var(--tint); border: 1px solid var(--line); border-radius: var(--r-lg);
          padding: 18px 20px; margin-top: 22px; }
.pd-why-h { display: flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 700;
            color: var(--ink-2); margin: 0; }
.pd-why-mark, .pd-c-mark { width: 18px; height: 18px; flex: none; border-radius: 5px;
                           background: var(--portal-accent); color: #fff; display: inline-flex;
                           align-items: center; justify-content: center; font-size: 10px; font-weight: 800; }
.pd-why-b { white-space: pre-line; font-size: 14px; line-height: 1.7; color: var(--ink-2); margin: 10px 0 0; }

/* Phones only: the rail folded into one sticky line (axe 10). */
.pd-strip { display: none; }

.pd-comments { margin: 32px 0 0; }
.pd-c-head { font-size: 11px; font-weight: 500; letter-spacing: .05em; text-transform: uppercase;
             color: var(--muted); margin: 0; padding-bottom: 12px; border-bottom: 1px solid var(--line-3); }
.pd-c { padding: 16px 0; border-bottom: 1px solid var(--line-3); }
.pd-c-who { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
            font-size: 13px; font-weight: 600; margin: 0; }
.av { width: 20px; height: 20px; flex: none; border-radius: 50%; background: var(--avatar-bg);
      color: var(--avatar-text); display: inline-flex; align-items: center; justify-content: center;
      font-size: 10px; font-weight: 700; }
/* A commenter with no name is Anonymous, with a "?", never a stand-in name. */
.av--anon { background: var(--paper); border: 1px dashed var(--anon-border); color: var(--muted); }
.pd-c-name--anon { color: var(--muted); font-weight: 400; }
.pd-c-when { font-size: 13px; color: var(--muted); font-weight: 400; }
.pd-c-body { white-space: pre-line; font-size: 13.5px; line-height: 1.6; color: var(--ink-2); margin: 6px 0 0 28px; }
.pd-c--team { background: var(--team-bg); border: 1px solid var(--team-border); border-radius: var(--r-lg);
              padding: 14px 18px; margin: 16px 0; }
.pd-c--team .pd-c-who { color: var(--ver-text); font-size: 12px; font-weight: 700; }
.pd-c--team .pd-c-body { margin-left: 0; color: var(--ink-2); }
.pd-form { margin: 20px 0 0; }
.pd-form textarea { display: block; width: 100%; min-height: 76px; padding: 9px 12px; font: inherit; font-size: 14px;
                    line-height: 1.5; border: 1px solid var(--line-2); border-radius: var(--r-md);
                    background: var(--paper); color: var(--ink); resize: vertical; box-sizing: border-box; }
.pd-form textarea::placeholder { color: var(--muted); opacity: 1; }
.pd-form-go { display: flex; align-items: center; gap: 8px 12px; flex-wrap: wrap; margin-top: 12px; }
.pd-form-go span { font-size: 13px; color: var(--muted); }
.pd-form-note { font-size: 13px; color: var(--muted); margin: 8px 0 0; }
/* Signed out: the way into the comment box, not the box itself. */
.pd-form--out { display: flex; align-items: center; gap: 8px 12px; flex-wrap: wrap; }
.pd-form--out .pd-form-note { margin: 0; }
/* The visitor's own name, and the way to change it, wherever it appears. */
.me-edit { color: var(--accent-ink); font-weight: 500; }

/* ---- the state rail ------------------------------------------------------- */
/* Sticky needs every ancestor to keep its overflow visible; .screen, #root and
   .pd-wrap all do, and the board's overflow:hidden is on a card, not here. */
.pd-rail { position: sticky; top: 24px; display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.pd-card { background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-xl);
           padding: 16px 18px; }
.pd-card-h { font-size: 11px; font-weight: 500; letter-spacing: .05em; text-transform: uppercase;
             color: var(--muted); margin: 0 0 10px; }
.pd-card-p { font-size: 13px; line-height: 1.6; color: var(--muted); margin: 10px 0 0; }
.pd-card-p a { color: var(--accent-ink); font-weight: 500; }
.pd-card-n { font-size: 12px; line-height: 1.6; color: var(--muted); margin: 8px 0 0; text-align: center; text-wrap: balance; }
.pd-card-a { display: inline-block; font-size: 13px; font-weight: 500;
             color: var(--accent-ink); margin-top: 12px; }
.pd-jump { font-size: 13px; font-weight: 500; color: var(--ink-2); padding: 12px 18px; }
.pd-jump span { color: var(--accent-ink); }


/* ---- roadmap -------------------------------------------------------------- */
.rm-wrap { padding: 28px 0 48px; }

/* Three columns on a tinted bed, the count in a pill. Refusals are NOT a
   fourth column: a refusal is not work in progress. */
.rm-cols { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; align-items: start; }
.rm-col { background: var(--tint); border-radius: var(--r-xl); padding: 12px; }
.rm-col-head { display: flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 600;
               margin: 2px 2px 12px; color: var(--ink); }
.rm-dot { width: 8px; height: 8px; flex: none; border-radius: 50%; }
/* Same canonical status dots as .bd-stage i. Keep them in step. */
.rm-dot--planned { background: var(--st-planned); }
.rm-dot--in-progress { background: var(--st-in-progress); }
/* Shipped is a tick, as on the board's status pill (masked above), so the
   Planned and Shipped heads differ by sign as well as by hue. */
.rm-dot--shipped { background: var(--st-shipped); width: 10px; height: 10px; border-radius: 0; }
.rm-col-n { margin-left: auto; background: var(--paper); border: 1px solid var(--line);
            border-radius: 99px; font-size: 11px; font-weight: 500; color: var(--muted); padding: 1px 8px;
            font-variant-numeric: tabular-nums; }

/* Direction A: no shadow in the portal; the edge is a 1px line. */
.rm-card { background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-lg); padding: 12px 14px; }
.rm-card + .rm-card { margin-top: 8px; }
.rm-card-t { display: block; font-size: 14px; font-weight: 500; line-height: 1.45; color: var(--ink); }
.rm-card-t:hover { color: var(--accent-ink); }
.rm-when { font-size: 12px; color: var(--muted); margin: 4px 0 0; }
.rm-when a { color: var(--accent-ink); font-weight: 500; }
/* The roadmap and the changelog row render <VoteButton size="sm">, read-only,
   so the styling is .vb--sm above. */
.rm-counts { display: flex; align-items: center; gap: 6px; margin: 10px 0 0; }
.rm-empty { font-size: 12px; line-height: 1.6; color: var(--faint); margin: 0; padding: 2px 2px 4px; }

/* Where the refusals go. Native <details>, folded by default, and the summary
   holds no link: a control inside a control is a coin toss for the reader. */
.rm-off { margin-top: 16px; background: var(--paper); border: 1px solid var(--line);
          border-radius: var(--r-lg); padding: 14px 18px; }
.rm-off summary { display: flex; align-items: center; gap: 8px; cursor: pointer;
                  font-size: 13px; font-weight: 500; color: var(--ink-2); list-style: none; }
.rm-off summary::-webkit-details-marker { display: none; }
.rm-off summary::before { content: "\25B8"; color: var(--faint); font-size: 11px; }
.rm-off[open] summary::before { content: "\25BE"; }
.rm-off-list { list-style: none; margin: 12px 0 0 20px; padding: 0; border-top: 1px solid var(--line-3); }
.rm-off-list li { display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
                  padding: 10px 0; border-bottom: 1px solid var(--line-3); font-size: 13px; }
.rm-off-list li:last-child { border-bottom: 0; }
.rm-off-t { font-weight: 500; }
.rm-off-d { color: var(--muted); }
.rm-off-list a { margin-left: auto; color: var(--accent-ink); font-weight: 500; }

/* ---- changelog ------------------------------------------------------------ */
.cg-wrap { padding: 28px 0 48px; }

/* The page's whole reason for existing, said once at the top. Neutral: the
   verified green belongs to the verified counter alone, and this is a promise,
   not a vote. The shipped dot carries the meaning. */
.cg-promise { display: inline-flex; align-items: center; gap: 8px; margin: 0 0 32px;
              background: var(--tint); border: 1px solid var(--line-3); border-radius: var(--r-full);
              padding: 5px 12px; font-size: 13px; font-weight: 500; color: var(--ink-2); }
/* The shipped dot: --st-shipped, the same muted green as the stage pill. It
   used to be a separate hex while the global token still held the old teal. */
.cg-promise-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--st-shipped); flex: none; }

/* A dated timeline. The month pins itself beside its releases while they
   scroll, so the period a reader is in never leaves the screen; the line and
   the node are the only ornament, and they carry the one thing the cards
   cannot: that these are consecutive. */
.cg-month { display: grid; grid-template-columns: 110px minmax(0, 1fr); }
.cg-month + .cg-month { margin-top: 32px; }
.cg-month-sticky { position: sticky; top: 16px; padding-top: 4px; }
.cg-month-h { font-size: 11px; font-weight: 500; letter-spacing: .05em; text-transform: uppercase;
              color: var(--muted); margin: 0; }
/* The current month is the one a returning reader is looking for. */
.cg-month--current .cg-month-h { color: var(--accent-ink); font-weight: 600; }
.cg-month-n { font-size: 12px; color: var(--muted); margin: 2px 0 0; }

.cg-track { position: relative; padding-left: 32px; display: flex; flex-direction: column; gap: 28px; }
.cg-track::before { content: ""; position: absolute; left: 0; top: 6px; bottom: -28px; width: 2px; background: var(--line); }
/* Under the oldest group the line fades instead of stopping dead: there is
   history before the first entry we published. */
.cg-track--last::before { bottom: 0; background: linear-gradient(to bottom, var(--line) 0%, var(--line) 55%, transparent 100%); }

.cg-entry { position: relative; }
.cg-entry::before { content: ""; position: absolute; left: -25px; top: 32px; width: 25px; height: 2px; background: var(--line); }
.cg-node { position: absolute; left: -35px; top: 28px; width: 9px; height: 9px; border-radius: 50%;
           background: var(--white); border: 3px solid var(--disabled); box-shadow: 0 0 0 4px var(--paper); }
.cg-month--current .cg-node { border-color: var(--portal-accent); }

.cg-card { background: var(--white); border: 1px solid var(--line); border-radius: var(--r-xl); padding: 24px;
           transition: border-color .15s ease, box-shadow .15s ease; }
/* A tint of the workspace's own colour, not a fixed blue: this border has to
   work on whatever accent the client picked. */
.cg-card:hover { border-color: color-mix(in srgb, var(--portal-accent) 35%, var(--line)); }
.cg-meta { display: flex; align-items: center; gap: 10px; margin: 0 0 8px; }
/* The date is metadata, so it reads as metadata: grey, tabular, no chip. */
.cg-date { font-size: 13px; font-weight: 500; color: var(--muted); font-variant-numeric: tabular-nums; }
.cg-latest { display: inline-flex; align-items: center; background: var(--accent-soft); color: var(--accent-ink);
             border-radius: var(--r-full); padding: 2px 8px; font-size: 12px; font-weight: 500; line-height: 1.4; }
.cg-h { font-size: 17px; font-weight: 600; letter-spacing: -.015em; line-height: 1.35; margin: 0 0 6px; }
.cg-h a { color: var(--ink); }
.cg-h a:hover { color: var(--accent-ink); }
.cg-p { font-size: 14px; line-height: 1.6; color: var(--ink-2); margin: 0; max-width: 68ch; }
.cg-p a { color: var(--accent-ink); font-weight: 500; white-space: nowrap; }

/* One closed request, one line, under a caption that counts them. */
.cg-closed { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line-3); }
.cg-closed-k { font-size: 11px; font-weight: 500; letter-spacing: .05em; text-transform: uppercase;
               color: var(--muted); margin: 0 0 8px; }
.cg-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 4px 0; font-size: 13px; }
/* The same Shipped pill as the board's stage column, tick included: it sits
   eight pixels from a verified chip, and the two must never read as one
   object, so it wears the canonical shipped green, never --ver-bg. */
.cg-row-pill { display: inline-flex; align-items: center; gap: 6px; background: #eff6f0; color: #31653e;
               border-radius: var(--r-full); padding: 3px 8px; font-size: 12px; font-weight: 500; line-height: 1.4; }
/* The title takes the slack and wraps inside its own box, so on a phone a long
   one breaks onto a second line instead of pushing the pill and the counters
   onto lines of their own. The counters line up at the right edge. */
.cg-row-t { flex: 1 1 0; min-width: 120px; font-size: 14px; font-weight: 500; color: var(--ink); }
.cg-row:hover .cg-row-t { color: var(--accent-ink); }

@media (max-width: 700px) {
  /* The rail costs a third of the width on a phone; the month becomes a heading
     over its releases instead. */
  .cg-month { grid-template-columns: 1fr; }
  .cg-month-sticky { position: static; margin-bottom: 12px; }
  .cg-track { padding-left: 0; }
  .cg-track::before, .cg-entry::before, .cg-node { display: none; }
}

/* ---- one release, on its permalink (axe 08) -------------------------------
   One flow. Three section headings over three lines of content each is
   scaffolding heavier than what it holds, so there are none: the closed list
   follows the lead, the "who was told" line is that list's caption, and the
   invitation is one line at the foot. */
.ep-wrap { padding: 20px 0 48px; max-width: 720px; }
.ep-crumb { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
            font-size: 13px; color: var(--muted); margin: 0; }
.ep-crumb a { color: var(--muted); }
.ep-crumb a:hover { color: var(--accent-ink); }
.ep-pill { display: inline-flex; align-items: center; gap: 6px; padding: 3px 8px; border-radius: 99px;
           font-size: 12px; font-weight: 500; line-height: 1.4; background: #eff6f0; color: #31653e; }
.ep-pill::before, .cg-row-pill::before { content: ""; flex: none; }
.ep-h1 { font-size: 26px; font-weight: 700; letter-spacing: -.025em; line-height: 1.25;
         margin: 12px 0 0; text-wrap: balance; }
.ep-body { white-space: pre-line; font-size: 15px; line-height: 1.65; color: var(--ink-2); margin: 10px 0 0;
           max-width: 68ch; text-wrap: pretty; }
.ep-closed { margin-top: 20px; border-top: 1px solid var(--line-3); }
.ep-closed .cg-row { padding: 12px 0; border-bottom: 1px solid var(--line-3); font-size: 14px; }
.ep-closed .cg-row:last-child { border-bottom: 0; }
/* The counters sit right on this list, the title takes the slack. */
.ep-closed .cg-row-t { flex: 1; min-width: 160px; }
.ep-told { font-size: 13px; line-height: 1.6; color: var(--muted); margin: 10px 0 0; }
.ep-foot { display: flex; align-items: center; justify-content: space-between; gap: 14px;
           flex-wrap: wrap; margin: 20px 0 0; padding-top: 16px; border-top: 1px solid var(--line-3);
           font-size: 13px; color: var(--ink-2); }

.ep-pager { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 32px 0 0; }
.ep-pg { border: 1px solid var(--line); border-radius: var(--r-lg); padding: 14px 16px; background: var(--paper); }
.ep-pg:hover { border-color: color-mix(in srgb, var(--portal-accent) 45%, var(--line)); }
.ep-pg--next { text-align: right; }
.ep-pg-k { display: block; font-size: 11px; font-weight: 500; letter-spacing: .05em;
           text-transform: uppercase; color: var(--muted); }
.ep-pg-t { display: block; font-size: 14px; font-weight: 500; letter-spacing: -.01em;
           color: var(--ink); margin-top: 4px; }
.ep-pg:hover .ep-pg-t { color: var(--accent-ink); }
.ep-pg-d { display: block; font-size: 12px; color: var(--muted); margin-top: 2px; font-variant-numeric: tabular-nums; }

/* Empty states invite instead of reporting, and their way out keeps the filter
   the visitor chose most deliberately (axe 05). */
.bd-empty { background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-lg);
            padding: 48px 40px; text-align: center; }
.bd-empty b { display: block; font-size: 16px; font-weight: 600; letter-spacing: -.01em;
              color: var(--ink); margin-bottom: 8px; }
/* `margin: 0 auto`, not `margin: 0`: the shorthand reset the inline margins,
   which pushed the message and its buttons off the centre of the card. */
.bd-empty p { font-size: 14px; line-height: 1.6; color: var(--muted); margin: 0 auto; }
.bd-empty .bd-empty-do { margin-top: 20px; }
.bd-empty > * { max-width: 52ch; margin-inline: auto; }

/* ---- responsive ----------------------------------------------------------- */
@media (max-width: 800px) {
  /* The bar keeps the brand and one Menu button; the links, the CTA and the
     board name move into the panel. The bar used to wrap instead, and a wrap
     depends on how long the workspace and board names are. */
  .mast-bar { min-height: 64px; }
  .mast-nav, .mast-sep, .mast-board { display: none; }
  .mast-menu { display: block; }
  /* Still a table here, with narrower fixed columns: the title keeps the room. */
  .bd-head, .bd-row { grid-template-columns: 116px minmax(0, 1fr) 75px 110px; }
  .bd-head > span, .bd-row > span { padding-inline: 8px; }

  /* Axe 10: one column, and the rail's headline folds into a sticky strip
     under the title so the stage, the counters and the vote survive forty
     comments. The rail itself follows the document rather than vanishing —
     the ship note, the refusal copy and the echo confirmation all live in it,
     and none of them fit on one line. */
  .pd-wrap { grid-template-columns: minmax(0, 1fr); gap: 24px; padding-top: 16px; }
  .pd-h1, .ep-h1 { font-size: 24px; }
  .pd-strip { position: sticky; top: 0; z-index: 5; display: flex; align-items: center; gap: 8px;
              flex-wrap: wrap; margin: 12px calc(-1 * var(--gutter)) 0; padding: 10px var(--gutter);
              background: var(--paper); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
  .pd-strip .vb-form { margin-left: auto; }
  .pd-strip .vb--lg { width: auto; height: 38px; font-size: 13px; }
  .pd-strip .vb--lg .vb-n { margin-left: 4px; font-size: 14px; }
  .pd-rail { position: static; }
  /* The strip already carries it; two identical vote forms on one screen is
     one too many. The thread anchor goes too, the rail now sits after it. */
  .pd-rail #vote:has(.vb-form), .pd-jump { display: none; }

  /* Axe 10: the three columns stack, each keeping its head and its count, and
     the refusals stay last. Side-scrolling three columns on a phone hides two
     of them behind a gesture nothing announces. */
  .rm-cols { grid-template-columns: minmax(0, 1fr); gap: 12px; }

  .ep-wrap { max-width: none; }
  .ep-foot { align-items: flex-start; }

  .pd-track { flex-direction: column; gap: 0; }
  .pd-track li { flex: none; padding: 0 0 16px 26px; }
  .pd-track li::before { left: 6px; top: 6px; bottom: -6px; right: auto; width: 3px; height: auto; }
  .pd-track li:last-child::before { display: none; }
  .pd-track li::after { top: 1px; }
  /* Vertical here, so the terminus centres on the rail's x instead of its y. */
  .pd-track li.pd-t-end::after { left: -4px; top: -3px; }
  /* Vertical, each stage reads as one line: name, then its date, on a 16px
     line so the text centres on the 14px node beside it. */
  .pd-track li { font-size: 11px; line-height: 16px; }
  .pd-track .pd-t-name { display: inline; }
  .pd-t-date { display: inline-block; margin: 0 0 0 10px; }
}
@media (max-width: 600px) {
  /* Touch: every portal control is 44px tall on a phone. */
  .portal .btn, .inp, .mast-menu-btn { height: 44px; min-height: 44px; }

  .mast-body { padding-top: 28px; }
  .mast-body:has(.mast-back) { padding-top: 20px; }
  /* Filters wrap onto as many rows as they need: no horizontal scroll trap and
     no stage hidden past the edge. Chips and links get thumb-sized boxes. */
  .bd-filters { padding-top: 24px; }
  .bd-pill, .bd-stages > a { min-height: 38px; }
  .bd-ax a { padding-block: 8px; }

  /* Axe 10: each request becomes a card with everything the row held: title,
     then the two counters and the stage, then the date, labelled since the
     column head is gone. */
  .bd-head { display: none; }
  .bd-rows { display: flex; flex-direction: column; gap: 12px; }
  .bd-row { grid-template-columns: minmax(0, 1fr) auto; gap: 10px 12px; min-height: 0; padding: 16px;
            border: 1px solid var(--line); border-radius: var(--r-lg); }
  .bd-head > span, .bd-row > span { padding-inline: 0; }
  .bd-t { grid-column: 1 / -1; grid-row: 1; }
  .bd-title { font-size: 15px; }
  .bd-votes { grid-column: 1; grid-row: 2; }
  .bd-stage-cell { grid-column: 2; grid-row: 2; justify-self: end; }
  .bd-posted { grid-column: 1; grid-row: 3; font-size: 12px; }
  .bd-lab { position: static; width: auto; height: auto; overflow: visible; clip-path: none; }
  .bd-foot { flex-direction: column; align-items: flex-start; gap: 4px; font-size: 11px; line-height: 1.6; }

  /* The suggest block stacks: pitch, field, then a full-width button. */
  .bd-suggest { padding-top: 32px; }
  .bd-sg-card { grid-template-columns: minmax(0, 1fr); gap: 20px; padding-top: 24px; }
  .bd-sg-form textarea { min-height: 112px; }
  .bd-sg-go { flex-direction: column; align-items: stretch; gap: 16px; }

  /* A closed request keeps one row: the title absorbs the squeeze and wraps,
     the pill and the counters stay beside it. */
  .cg-closed .cg-row { flex-wrap: nowrap; align-items: flex-start; }
  .cg-closed .cg-row-t { min-width: 0; }

  .nf-wrap { padding-top: 48px; }
  .si-wrap { padding-top: 32px; }
  /* A followed request stacks: its figure, the title over its stage, the mute. */
  .my-row { grid-template-columns: auto minmax(0, 1fr) auto; }
  .my-row .bd-stage { grid-column: 2; grid-row: 2; justify-self: start; }
  .my-mute { grid-column: 3; grid-row: 1 / span 2; }
  .bd-empty { padding: 28px 20px; }
  .ep-pager { grid-template-columns: 1fr; }
  .ep-pg--next { text-align: left; }
  .seal { line-height: 1.8; }
}
@media (prefers-reduced-motion: no-preference) {
  .bd-title, .rm-card, .mast-cta, .portal .btn { transition: color .14s ease, background-color .14s ease, border-color .14s ease; }
}
