/* ============================================================================
   terminaldeck.dev — one stylesheet for the whole site.

   THE PAGE SHELL
   --------------
   Every page is a hand-written static HTML file. No framework, no build step,
   no external request of any kind (fonts are self-hosted next to this file).
   Copy this skeleton exactly; the only things that change per page are the
   title, the description, the `aria-current` on the nav, and — for pages in a
   subfolder — the `../` in front of every href.

     <!doctype html>
     <html lang="en">
       <head>
         <meta charset="utf-8" />
         <meta name="viewport" content="width=device-width, initial-scale=1" />
         <title>Page name — Terminal Deck</title>
         <meta name="description" content="One sentence." />
         <link rel="icon" href="assets/favicon.svg" type="image/svg+xml" />
         <link rel="stylesheet" href="assets/site.css" />
         <script> …the theme script, copied from index.html verbatim… </script>
       </head>
       <body>
         <a class="skip-link" href="#main">Skip to content</a>
         <header class="site-head"> …copy from index.html verbatim… </header>
         <main id="main"> …page content… </main>
         <footer class="site-foot"> …copy from index.html verbatim… </footer>
       </body>
     </html>

   TWO HEADERS, ONE OF WHICH IS DRAWN. The second one, the script in the <head>
   that chooses between them, and the account slot inside each of them are NOT
   copied by hand: `scripts/build-header.mjs` writes all of it into every page,
   and `npm run check` fails on a page that lost it or hand-edited it. Signed
   out, the first header is what shows and the site is a website. Signed in, the
   second one shows and the site is the platform. See "the product header"
   further down this file, and the README section that goes with it.

   The theme script is not optional and is not a separate file. It has to run
   before the first paint or a visitor who chose dark gets a white flash on
   every page, which rules out a `defer`red script; and it is what sets
   `data-theme-choice`, which is the attribute the header's theme button is
   hidden behind. A page that omits it renders correctly and silently loses the
   control, which is the kind of omission nobody notices for months.

   Paths are RELATIVE on purpose: the site has to open by double-clicking
   index.html from the filesystem AND deploy unchanged to GitHub Pages or
   Vercel. A leading slash breaks the first case, so never use one.
   A page one level down (docs/index.html) uses `../assets/site.css`,
   `../index.html`, `../download.html`, `../changelog.html`. That resolves the
   same whether the page is served as /docs/index.html or /docs/.

   THE BLOCKS THIS STYLESHEET PROVIDES
   -----------------------------------
     .wrap                page-width container (64rem), used inside every band
     .prose               reading column (44rem) for long text
     .site-head / .nav    sticky header, hairline underneath
     .hero                first band on a page: h1 + .lede + .cta-row
     .lede                one-sentence intro, larger than body text
     .cta-row             row of buttons; wraps on narrow screens
     .btn                 base button; add .btn-primary or .btn-quiet
     .band                a vertical section; add .band-alt for a tinted one
     .band-head           h2 + optional .band-blurb
     .feature             one feature: h3 + prose + optional .detail list
     .cards / .card       equal grid of small blocks
     .detail              key/value list (dt/dd) for concrete specifics
     .steps               numbered <ol> for install steps
     .note                callout; add .note-warn for the amber one
     .keys / kbd          shortcut table and key caps
     .code                <pre> block; scrolls sideways, never the page
     .legend / .dot       status-dot legend, using the app's status colours
     .table-scroll        wrap any wide table in this
     .stack               vertical rhythm helper (gap between children)
     .site-foot           footer

   COLOUR
   ------
   True-neutral grey and one blue — Terminal Deck's own, #1a66c4 on paper and
   #3b8fee in the dark, lifted from the application icon. The same values
   assets/app.css draws the signed-in half of this site in, so that signing out
   does not look like arriving at a different company. Both files say so at
   their palette block, and neither is free to drift: if you change a value in
   one, change it in the other in the same commit.

   `site-only` marks the two tokens this file has and the workspace does not:
   --text-quiet, the secondary prose ink, and --link. Both take values straight
   out of the same four-ink scale.

   Every grey is r = g = b. Check that before adding one. The palette this
   replaced was warm graphite on a lighter blue, which is one AI vendor's brand
   — untrue of a store that lists Claude Code, Codex and Gemini as equals, and
   the reason the change was made.

   Radii, both fonts and the status dots are unchanged. Run the contrast check
   again if you touch any of it.
   ============================================================================ */

/* --------------------------------------------------------------- fonts -- */

@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/hanken-grotesk.woff2') format('woff2');
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url('fonts/jetbrains-mono.woff2') format('woff2');
}

/* -------------------------------------------------------------- tokens -- */

:root {
  /* Tells the browser to theme scrollbars and form controls both ways. */
  color-scheme: light dark;

  /* ---- OUR OWN COLOURS ------------------------------------------------
     True-neutral grey and one blue, taken verbatim from the approved
     workspace reference and identical to the values assets/app.css draws the
     signed-in half of this site in. The two stylesheets exist because a
     marketing page and a workspace are two shapes; they agree on the accent,
     the greys and the three theme states, because signing out must not look
     like arriving at a different company.

     Every grey here is r = g = b. Check that before adding one: a warm grey
     is invisible in a swatch and unmissable across a 1440px screen, and it
     drags the blue toward violet. What this replaced was warm graphite and a
     lighter blue — one AI vendor's palette, on a store whose whole claim is
     that it lists Claude Code, Codex and Gemini as equals.

     Contrast on --bg-primary #ffffff, computed:
       --text-primary 16.9:1 · --text-quiet 9.5:1 · --text-secondary 7.5:1
       --text-muted 5.7:1 · --accent 5.6:1 · white on --accent 5.6:1
     All AA, including the small grey text — the old --text-muted (#9b9a97,
     2.8:1 on white) never was. */
  --bg-primary: #ffffff;
  --bg-secondary: #fafafa;
  --bg-tertiary: #f0f0f0;
  --bg-hover: rgba(28, 28, 28, 0.06);
  --bg-active: rgba(28, 28, 28, 0.09);

  --text-primary: #1c1c1c;
  --text-secondary: #454545;
  --text-muted: #666666;
  /* site-only: AA-safe secondary ink for prose. */
  --text-quiet: #545454;

  --accent: #1a66c4;
  --accent-dim: #155bb0;
  --accent-soft: rgba(26, 102, 196, 0.09);
  --accent-fg: #ffffff;
  /* site-only: link colour. The accent itself clears 4.5:1 on white, so a
     link and a filled button wear the same blue rather than two. */
  --link: var(--accent);

  --border: rgba(28, 28, 28, 0.11);
  --border-subtle: rgba(28, 28, 28, 0.06);
  --border-strong: rgba(28, 28, 28, 0.22);

  --status-working: #1a66c4;
  --status-waiting: #cb912f;
  --status-input: #d9730d;
  --status-completed: #448361;
  --status-idle: #9b9a97;

  --color-warning: #cb912f;
  --color-critical: #d44c47;

  /* One shadow, and dark takes it away rather than darkening it — see
     --shadow-1 in the platform block, which this is now the site's name for.
     A drop shadow on a #191919 ground is a smudge; the raised surfaces get a
     1px top highlight (--card-top) there instead. */
  --shadow-sm: var(--shadow-1);

  /* The one filled button, in the accent — the same button assets/app.css
     draws in the workspace. It was neutral ink here so that blue could mean
     "link", which was right when the two halves of this site had two
     different blues. They have one now. */
  --btn-bg: var(--accent);
  --btn-fg: var(--accent-fg);

  --font-ui: 'Hanken Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', 'SF Mono', Menlo, monospace;

  /* RADII COME FROM THE PLATFORM SCALE, and the three names above are what
     this sheet already called them. The approved design is drawn on 6/10/14
     with a 20 for the largest panes, not the 4/6/10 this file was written
     with; pointing the old names at the new scale is what makes 47 existing
     rules — every card, button, field, chip, note and code block on the site
     — wear it, instead of 47 edits that would each have to be found. Both
     scales are declared on :root, so the reference below resolves even though
     --r-sm is written further down this file. */
  --radius-sm: var(--r-sm);
  --radius-md: var(--r-md);
  --radius-lg: var(--r-lg);

  --wrap: 64rem;
  --prose: 44rem;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    /* Dark is its own design, not an inversion. The accent goes UP rather than
       down — #3b8fee is the application icon's own top stop — and the filled
       button flips to near-black ink, because no one blue is both readable on
       #191919 and readable under white. The same values assets/app.css uses.
       Contrast on --bg-primary #191919:
         --text-primary 13.4:1 · --text-quiet 8.6:1 · --text-secondary 7.2:1
         --text-muted 5.6:1 · --accent 5.5:1 */
    --bg-primary: #191919;
    --bg-secondary: #202020;
    --bg-tertiary: #262626;
    --bg-hover: rgba(255, 255, 255, 0.055);
    --bg-active: rgba(255, 255, 255, 0.085);

    --text-primary: #ededed;
    --text-secondary: #b8b8b8;
    --text-muted: #949494;
    --text-quiet: #a8a8a8; /* 7.4:1 on #191919 */

    --accent: #3b8fee;
    --accent-dim: #5ba2f2;
    --accent-soft: rgba(59, 143, 238, 0.16);
    --accent-fg: #0f1114;
    --link: var(--accent);

    --border: rgba(255, 255, 255, 0.10);
    --border-subtle: rgba(255, 255, 255, 0.055);
    --border-strong: rgba(255, 255, 255, 0.22);

    --status-working: #3b8fee;
    --status-waiting: #c9a227;
    --status-input: #d9730d;
    --status-completed: #4dab9a;
    --status-idle: #6f6f6f;

    --color-warning: #c9a227;
    --color-critical: #ff7369;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);

    --btn-bg: var(--accent);
    --btn-fg: var(--accent-fg);
  }
}

/* The same values again, for a visitor who picked dark while the system is
   light. CSS has no way to name one declaration list from two places, so this
   is a copy and it has to be edited in step with the block above. The media
   query up there is guarded rather than plain so the opposite choice — light
   on a dark system — needs no third copy: it simply stops matching. */
:root[data-theme='dark'] {
  color-scheme: dark;
  /* Dark is its own design, not an inversion. The accent goes UP rather than
     down — #3b8fee is the application icon's own top stop — and the filled
     button flips to near-black ink, because no one blue is both readable on
     #191919 and readable under white. The same values assets/app.css uses.
     Contrast on --bg-primary #191919:
       --text-primary 13.4:1 · --text-quiet 8.6:1 · --text-secondary 7.2:1
       --text-muted 5.6:1 · --accent 5.5:1 */
  --bg-primary: #191919;
  --bg-secondary: #202020;
  --bg-tertiary: #262626;
  --bg-hover: rgba(255, 255, 255, 0.055);
  --bg-active: rgba(255, 255, 255, 0.085);

  --text-primary: #ededed;
  --text-secondary: #b8b8b8;
  --text-muted: #949494;
  --text-quiet: #a8a8a8; /* 7.4:1 on #191919 */

  --accent: #3b8fee;
  --accent-dim: #5ba2f2;
  --accent-soft: rgba(59, 143, 238, 0.16);
  --accent-fg: #0f1114;
  --link: var(--accent);

  --border: rgba(255, 255, 255, 0.10);
  --border-subtle: rgba(255, 255, 255, 0.055);
  --border-strong: rgba(255, 255, 255, 0.22);

  --status-working: #3b8fee;
  --status-waiting: #c9a227;
  --status-input: #d9730d;
  --status-completed: #4dab9a;
  --status-idle: #6f6f6f;

  --color-warning: #c9a227;
  --color-critical: #ff7369;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);

  --btn-bg: var(--accent);
  --btn-fg: var(--accent-fg);
}

:root[data-theme='light'] {
  color-scheme: light;
}

/* ==========================================================================
   THE PLATFORM DESIGN SYSTEM — tokens

   terminaldeck.dev is two experiences on one domain. Signed out it is a
   website. Signed in it is the product's community platform, and it is drawn
   from the approved "workspace" concept: a 4px spacing rhythm, one type
   scale, true-neutral greys and one blue that is Terminal Deck's own
   (#1a66c4 light, #3b8fee dark, both lifted from the application icon).

   The blue matters and is not a taste decision. The store lists Claude Code,
   Codex and Gemini as equals. A site wearing one vendor's brand colour on
   one vendor's warm graphite reads as that vendor's endorsement, which would
   be untrue and would quietly contradict the one claim the store makes. Our
   own colour is the only version of this that can say "all three, equally"
   and be believed. Every grey below is r = g = b — check that before adding
   one, because a warm grey is invisible in a swatch and unmissable across a
   1440px screen, and it drags the blue toward violet.

   HALF OF THIS SECTION IS AN ALIAS. The site already had a name for the same
   value — --text-primary is the platform's --ink, --border is its --line —
   and two names holding two copies of one colour is how a palette drifts. So
   the names that agree in BOTH themes are declared once, as var() of the name
   that was already here, and dark needs no second copy of them: custom
   properties resolve on the element they are declared on, and both blocks
   are declared on <html>.

   Only the tokens whose light and dark values do not simply follow the
   site's own — the platform's tinted ground, the recessed rail, the raised
   surfaces, and the shadows dark mode replaces with a top highlight — are
   written out three times, the same way this file already handles dark.
   ========================================================================== */

:root {
  /* spacing — 4px base, and nothing between the steps */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 20px;
  --s-6: 24px;
  --s-8: 32px;
  --s-10: 40px;
  --s-12: 48px;
  --s-16: 64px;
  --s-20: 80px;
  --s-24: 96px;

  /* type scale */
  --t-2xs: 0.6875rem;
  --t-xs: 0.75rem;
  --t-sm: 0.8125rem;
  --t-md: 0.875rem;
  --t-base: 1rem;
  --t-lg: 1.125rem;
  --t-xl: 1.375rem;
  --t-2xl: 1.75rem;
  --t-3xl: 2.25rem;

  --lh-tight: 1.15;
  --lh-snug: 1.3;
  --lh-body: 1.55;

  --r-sm: 6px;
  --r-md: 10px;
  --r-lg: 14px;
  --r-xl: 20px;
  --r-pill: 999px;

  --rail-w: 236px;
  --topbar-h: 56px;
  /* the signed-in reading measure, applied to the content INSIDE the column
     rather than to a wrapper around it, so a wide monitor widens the column
     without ever floating the rail off the left edge of the screen */
  --page-max: 1400px;

  --dur: 150ms;
  --ease: cubic-bezier(0.2, 0, 0, 1);

  /* the same value under the platform's name — see the note above */
  --ink: var(--text-primary);
  --accent-hi: var(--accent-dim);
  --ink-2: var(--text-secondary);
  --ink-3: var(--text-quiet);
  --ink-4: var(--text-muted);
  --line: var(--border);
  --line-soft: var(--border-subtle);
  --line-strong: var(--border-strong);
  --surface-3: var(--bg-tertiary);

  /* light: a tinted ground with white panes floating on it */
  --bg: #f4f4f4;
  --rail-bg: #ededed;
  --surface: #ffffff;
  --surface-2: #fafafa;
  --accent-line: rgba(26, 102, 196, 0.28);
  --ok: #1f6b46;
  --warn: #7a5a10;
  /* neutral black. A warm shadow tints every edge on the page at once. */
  --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.05), 0 1px 1px rgba(0, 0, 0, 0.04);
  --shadow-2: 0 2px 4px rgba(0, 0, 0, 0.05), 0 8px 20px -8px rgba(0, 0, 0, 0.14);
  --shadow-3: 0 24px 60px -24px rgba(0, 0, 0, 0.3), 0 2px 8px rgba(0, 0, 0, 0.08);
  --card-top: transparent;
}

/* Dark is its own design, not an inversion: the rail is recessed rather than
   raised, drop shadows are removed entirely, and a 1px top highlight stands in
   for them on anything that should read as lifted off the ground. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --bg: #191919;
    --rail-bg: #121212;
    --surface: #202020;
    --surface-2: #232323;
    --accent-line: rgba(59, 143, 238, 0.4);
    --ok: #62c48c;
    --warn: #d9ad4f;
    --shadow-1: none;
    --shadow-2: none;
    --shadow-3: 0 32px 80px -28px rgba(0, 0, 0, 0.85);
    --card-top: rgba(255, 255, 255, 0.06);
  }
}

/* The copy for a visitor who picked dark on a light system, exactly as the
   block above this section. Edit the two together. */
:root[data-theme='dark'] {
  --bg: #191919;
  --rail-bg: #121212;
  --surface: #202020;
  --surface-2: #232323;
  --accent-line: rgba(59, 143, 238, 0.4);
  --ok: #62c48c;
  --warn: #d9ad4f;
  --shadow-1: none;
  --shadow-2: none;
  --shadow-3: 0 32px 80px -28px rgba(0, 0, 0, 0.85);
  --card-top: rgba(255, 255, 255, 0.06);
}

/* --------------------------------------------------------------- reset -- */

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

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

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  * {
    transition-duration: 0.01ms !important;
  }
}

body {
  margin: 0;
  /* The tinted ground of the approved design, not white paper. Everything
     that should read as an object — a card, a button, a code block, the
     schematic — sits on --surface above it. */
  background: var(--bg);
  color: var(--text-primary);
  font-family: var(--font-ui);
  font-size: 1rem;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* A bare URL or a long path in inline code is one unbreakable word. At 380px
     that is enough to widen the page and give the whole site a sideways
     scrollbar, which is how `github.com/asadev/…/advisories/new` used to do it.
     `.code` blocks scroll themselves and are exempt below. */
  overflow-wrap: break-word;
}

a,
code,
kbd,
samp {
  overflow-wrap: anywhere;
}

/* Two places a break is wrong. A <pre> would silently rewrite the command
   someone is about to paste. And a path in a table cell — `~/.claude/settings
   .json` snapped across three lines — is harder to read than the sideways
   scroll `.table-scroll` already provides. Both scroll instead of breaking. */
pre,
pre code {
  overflow-wrap: normal;
}

.table-scroll td code,
.table-scroll th code {
  overflow-wrap: normal;
  white-space: nowrap;
}

img,
svg {
  max-width: 100%;
  height: auto;
}

h1,
h2,
h3,
h4 {
  margin: 0;
  line-height: 1.25;
  font-weight: 600;
  letter-spacing: -0.011em;
  text-wrap: balance;
}

p,
ul,
ol,
dl,
pre,
table,
figure {
  margin: 0;
}

a {
  color: var(--link);
  text-decoration: underline;
  text-underline-offset: 0.18em;
  text-decoration-thickness: 1px;
}

a:hover {
  text-decoration-thickness: 2px;
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

code,
kbd,
pre,
samp {
  font-family: var(--font-mono);
  font-size: 0.875em;
}

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

/* ------------------------------------------------------------- helpers -- */

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin: 0 auto;
  padding: 0 1.5rem;
}

.prose {
  max-width: var(--prose);
}

.prose p + p,
.prose p + ul,
.prose ul + p {
  margin-top: 0.9rem;
}

.stack > * + * {
  margin-top: 1rem;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 20;
  background: var(--surface);
  color: var(--text-primary);
  padding: var(--s-2) var(--s-4);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  text-decoration: none;
}

.skip-link:focus {
  left: 1rem;
  top: 1rem;
}

.small {
  font-size: 0.875rem;
  color: var(--text-quiet);
}

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

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

.site-head {
  position: sticky;
  top: 0;
  z-index: 10;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: saturate(1.4) blur(8px);
  border-bottom: 1px solid var(--line-soft);
}

@supports not (backdrop-filter: blur(1px)) {
  .site-head {
    background: var(--bg);
  }
}

/*
  ONE BAR, AND IT NEVER GROWS A SECOND ROW.

  This header used to wrap. On a 390px phone that meant the name took one line
  and five links took two more — 240 of 844 pixels spent on chrome before a
  word of the page — and the two things a visitor most often wants from a
  header were not even in it: the release number sat in a footnote under the
  hero, and there was no way to choose a theme at all.

  Everything the header carries now sits on one 48px line: the name, the
  navigation, the release, and the theme. `nowrap` is the rule that keeps it
  that way. What will not fit is dropped by width rather than folded onto
  another line, in a deliberate order — the changelog and GitHub links go
  first, because the version pill beside them opens the same page; then the
  wordmark, because the mark to its left already goes home; then, on a phone,
  the docs link and the pill itself, which is what made room for Community
  when the store grew one. Nothing dropped becomes unreachable: the footer
  lists every page on every page.
*/
.site-head .wrap {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 0.75rem;
  min-height: 3rem;
  padding-top: 0.35rem;
  padding-bottom: 0.35rem;
}

.brand {
  display: inline-flex;
  align-items: center;
  flex: none;
  gap: 0.5rem;
  margin-right: auto;
  font-weight: 600;
  font-size: 0.95rem;
  letter-spacing: -0.01em;
  white-space: nowrap;
  color: var(--text-primary);
  text-decoration: none;
}

.brand:hover {
  text-decoration: none;
}

.brand-mark {
  display: block;
  flex: none;
  color: var(--text-quiet);
}

.nav {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 0.15rem;
  font-size: 0.9rem;
}

.nav a {
  display: inline-block;
  padding: 0.3rem 0.5rem;
  border-radius: var(--radius-sm);
  white-space: nowrap;
  color: var(--text-quiet);
  text-decoration: none;
}

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

.nav a[aria-current='page'] {
  color: var(--text-primary);
  background: var(--bg-active);
}

/* A MENU IN THE MARKETING BAR.

   The home page used to be the site map: every section of the product written
   out on it, one after another, because there was nowhere else for them to go.
   They have pages now, and this is how the bar reaches them without becoming a
   row of eleven links.

   <details> and <summary>, not a button and a script. A menu that only opens
   when JavaScript has loaded is a dead control until it does, and this one sits
   in the header of every page — including the pages a crawler reads with no
   scripts at all. A summary opens with the keyboard, opens with no JavaScript,
   and needs nothing added to any page's script budget.

   The panel is taken out of flow, so opening one moves nothing else on the bar.
   Everything in it is also in the footer of every page, which is what makes it
   safe for the whole control to disappear at phone width. */
.nav-menu {
  position: relative;
  flex: none;
}

.nav-menu > summary {
  display: inline-flex;
  align-items: center;
  gap: 0.2rem;
  padding: 0.3rem 0.5rem;
  border-radius: var(--radius-sm);
  white-space: nowrap;
  color: var(--text-quiet);
  cursor: pointer;
  list-style: none;
}

/* Safari draws its own triangle otherwise, beside the one we drew. */
.nav-menu > summary::-webkit-details-marker {
  display: none;
}

.nav-menu > summary:hover,
.nav-menu[open] > summary {
  background: var(--bg-hover);
  color: var(--text-primary);
}

.nav-menu > summary svg {
  width: 11px;
  height: 11px;
  transition: transform var(--dur) var(--ease);
}

.nav-menu[open] > summary svg {
  transform: rotate(180deg);
}

.nav-menu-panel {
  position: absolute;
  top: calc(100% + 0.4rem);
  left: 0;
  z-index: 20;
  display: grid;
  min-width: 12rem;
  padding: 0.25rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  background: var(--bg-primary);
  box-shadow: var(--shadow-sm);
}

/* Same specificity as `.nav a` and written after it, which is what lets a link
   in the panel be a menu row rather than a bar item. */
.nav-menu-panel a {
  display: flex;
  align-items: center;
  min-height: 2.125rem;
  padding: 0 0.6rem;
  border-radius: var(--radius-sm);
  white-space: nowrap;
  color: var(--text-secondary);
  text-decoration: none;
}

/* The release, as a link to the entry that describes it. A pill of mono digits
   rather than the words "Version 0.0.0", because this has to survive beside a
   nav on a 390px screen. scripts/sync-release.mjs rewrites the number. */
.head-version {
  flex: none;
  padding: 0.15rem 0.4rem;
  border-radius: var(--radius-sm);
  background: var(--surface);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  line-height: 1.5;
  white-space: nowrap;
  color: var(--text-quiet);
  text-decoration: none;
}

.head-version:hover {
  background: var(--bg-active);
  color: var(--text-primary);
  text-decoration: none;
}

/* One button cycling three states: follow the system, light, dark. It is
   hidden until the script in every page's <head> announces itself by setting
   data-theme-choice, because without that script the button cannot change
   anything, and a control that does nothing is worse than no control. */
.head-theme {
  display: none;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 1.85rem;
  height: 1.85rem;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-quiet);
  cursor: pointer;
}

:root[data-theme-choice] .head-theme {
  display: inline-flex;
}

.head-theme:hover {
  background: var(--bg-hover);
  color: var(--text-primary);
}

.head-theme svg {
  display: none;
}

:root[data-theme-choice='system'] .head-theme svg[data-icon='system'],
:root[data-theme-choice='light'] .head-theme svg[data-icon='light'],
:root[data-theme-choice='dark'] .head-theme svg[data-icon='dark'] {
  display: block;
}

/* ------------------------------------------------------- the account control -- */

/* Written by assets/site-account.js into the empty slot every page carries, and
   drawn only when there is something to press: signed out with no provider
   switched on, nothing appears at all. So none of this is ever seen by somebody
   who cannot use it.

   It is the last thing on the bar and the only thing on the bar that is about
   the person rather than the product, which is where a header has put an account
   for twenty years. Everything is sized against the theme button beside it so
   the bar stays one 48px line — the rule the whole header is built on. */
.head-account {
  position: relative;
  display: flex;
  align-items: center;
  flex: none;
}

/* Signed out. Two controls: the quiet word for people who have been here, and
   the filled one for people who have not.

   They are deliberately unequal. A stranger is being offered one thing, and two
   identical words either side of a gap make them read both to find out which is
   theirs. So Sign up carries the fill and Sign in is a plain word — the same
   relationship the hero's two buttons have, at header scale.

   Both are one line tall and neither wraps: the bar is 48px and stays 48px. The
   room for the second one came from "Download" leaving the nav in the same
   change; below 37.5rem the first collapses to its mark, which is why a phone
   sees a mark and one short word rather than two words. */
.head-signin {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.3rem 0.5rem;
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
  font-weight: 500;
  white-space: nowrap;
  color: var(--text-primary);
  text-decoration: none;
}

.head-signin:hover {
  background: var(--bg-hover);
  text-decoration: none;
}

.head-signin-mark {
  display: none;
}

/* The filled half of the pair. Sized against the theme button beside it, like
   everything else on this bar, so adding it did not make the row taller. */
.head-signup {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-left: 0.15rem;
  padding: 0.3rem 0.6rem;
  border-radius: var(--radius-sm);
  background: var(--btn-bg);
  font-size: 0.9rem;
  font-weight: 500;
  line-height: 1.35;
  white-space: nowrap;
  color: var(--btn-fg);
  text-decoration: none;
}

.head-signup:hover {
  opacity: 0.88;
  color: var(--btn-fg);
  text-decoration: none;
}

/* Signed in. The picture is the control. */
.head-account-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  cursor: pointer;
}

.head-account-btn:hover,
.head-account.is-open .head-account-btn {
  box-shadow: 0 0 0 3px var(--bg-active);
}

/* .avatar is 64px on a profile page. In a 48px bar it is the size of the theme
   button beside it, and square-ish rather than round so it sits with the version
   pill rather than against it. */
.head-account .avatar {
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--radius-sm);
  font-size: 0.7rem;
}

/* Signed in, publisher name not chosen yet. One dot, because the menu behind it
   says the sentence — a header is no place for a paragraph. */
.head-account-dot {
  position: absolute;
  top: 0.15rem;
  right: 0.15rem;
  width: 0.45rem;
  height: 0.45rem;
  border: 2px solid var(--bg);
  border-radius: 50%;
  background: var(--status-input);
}

.head-menu {
  position: absolute;
  top: calc(100% + 0.5rem);
  right: 0;
  z-index: 1;
  min-width: 12.5rem;
  max-width: min(16rem, calc(100vw - 2rem));
  padding: var(--s-1);
  /* An overlay is the one place dark mode keeps a drop shadow: --shadow-1
     and --shadow-2 are `none` there, so a menu with either would float on a
     hairline alone. --line-strong and --shadow-3, exactly as the approved
     design's own menu, and as the workspace's own menu in shell.css. */
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  background: var(--surface);
  box-shadow: var(--shadow-3);
}

.head-menu-who {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  margin: 0 0 0.35rem;
  padding: 0.5rem 0.6rem 0.6rem;
  border-bottom: 1px solid var(--border-subtle);
}

.head-menu-name {
  font-size: 0.9rem;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.head-menu-handle {
  font-size: 0.8rem;
  color: var(--text-quiet);
  overflow-wrap: anywhere;
}

.head-menu a,
.head-menu button {
  display: block;
  width: 100%;
  padding: 0.4rem 0.6rem;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  font: inherit;
  font-size: 0.9rem;
  text-align: left;
  color: var(--text-primary);
  text-decoration: none;
  cursor: pointer;
}

.head-menu a:hover,
.head-menu button:hover {
  background: var(--bg-hover);
  text-decoration: none;
}

/* Signing out is the one item that ends something, so it is set apart. */
.head-menu-out {
  margin-top: 0.35rem;
  padding-top: 0.35rem;
  border-top: 1px solid var(--border-subtle) !important;
  border-radius: 0 0 var(--radius-md) var(--radius-md) !important;
  color: var(--text-quiet) !important;
}

/* The widths at which something has to give, and what gives at each.

   MEASURED, EVERY TIME. Milestone 2 put two more things on this bar — the
   Community link and the account control — and they arrived from two lanes
   that could not see each other. Each was measured on its own branch and each
   was right there; together the bar hung off the screen between 545 and 635
   and again between 740 and 835. These numbers are what the bar actually needs
   with everything on it, signed in and signed out, and moving anything here
   means sweeping 300-1440 again.

   SWEPT AGAIN 29 Aug 2026, when "Download" came off the nav and "Sign up" went
   on beside "Sign in". Every number below moved, and not because of the swap:
   the bar was already hanging off the screen in five narrow bands — 300-312,
   338-356, 370-386, 642-666 and 850-868 — and had been since the account
   control landed, because each rule dropped its element a rem or so LATER than
   the width at which that element stopped fitting, so the bar overflowed in the
   band just above every breakpoint. Measured with a real headless Chrome at
   every even width from 300 to 1440, both headers, signed in and signed out:
   50 overflowing widths before, none after. The two numbers this block shares
   with the inlined critical stylesheet — 54rem and 41rem — were moved in
   features.html as well, or the first paint would drop things at one width and
   the loaded stylesheet at another. */
@media (max-width: 54rem) {
  /* `.nav a` above is more specific than a bare `.nav-wide`, so this has to
     name both or the link stays and the bar overflows the screen. */
  .nav a.nav-wide {
    display: none;
  }
  /* The second menu goes with them, and for the same reason: the bar at phone
     width holds three things without wrapping, and every address inside this
     menu is in the footer of every page. The first menu — the product itself —
     never drops, because it is the only way the bar reaches the pages the home
     page used to be. */
  .nav .nav-menu.nav-wide {
    display: none;
  }
}

/* The release pill goes before the wordmark does. It is the last thing in the
   drop order this header was built around: its destination — the changelog —
   has already gone at 54rem, so below that the pill is a number with nowhere
   to click, and the words beside it are places to go. Every page's footer
   carries the changelog. */
@media (max-width: 41rem) {
  .head-version {
    display: none;
  }
}

/* Measured, not guessed: the word "Sign in" is the widest thing on the bar and
   it stops fitting at about 600px — a whole breakpoint EARLIER than the one
   where the wordmark goes. Below this the mark stands in for it, at exactly the
   width the avatar takes once somebody is signed in. */
@media (max-width: 37.5rem) {
  /* The wordmark is still on the bar at this width, so the pixels come out of
     the gaps and the link padding instead — measured at 545px, where losing
     both the wordmark and the shaving at once used to leave 21px hanging off
     the right edge of the screen. */
  .site-head .wrap {
    gap: 0.5rem;
  }
  .nav a {
    padding: 0.3rem 0.35rem;
  }
  .head-signin {
    width: 1.85rem;
    height: 1.85rem;
    padding: 0;
    color: var(--text-quiet);
  }
  .head-signin-mark {
    display: block;
  }
  .head-signin-word {
    display: none;
  }
  /* Sign up keeps its word here and the one beside it gives its up. Two marks
     would be two circles nobody can tell apart, and dropping the filled one
     would take away the control a stranger is actually being offered. */
  .head-signup {
    margin-left: 0;
    padding: 0.3rem 0.45rem;
    font-size: 0.85rem;
  }
}

@media (max-width: 34rem) {
  .site-head .wrap {
    gap: 0.35rem;
    padding-left: 1rem;
    padding-right: 1rem;
  }
  .brand-name {
    display: none;
  }
  .nav {
    font-size: 0.85rem;
  }
  .nav a {
    padding: 0.3rem 0.2rem;
  }
  /* The bar is 390px wide, holds five things, and will not become two lines.
     Nothing else is dropped: every element gives back a few pixels instead, and
     the one that gives back the most is the sign-in, whose word becomes the
     mark it shares a footprint with once somebody is signed in. (The release
     pill is not shaved here — it is already gone at 40rem.) */
  .head-account .avatar {
    width: 1.6rem;
    height: 1.6rem;
  }
  .head-signup {
    padding: 0.3rem 0.4rem;
    font-size: 0.8rem;
  }
}

/* Under about 370px — a 360px Android, a 320px SE — the bar was already over
   the edge before it carried an account at all: five nav links and a release
   pill do not fit in 320 pixels, and the overflow was simply never looked at.
   The pill itself has already gone at 40rem. */
@media (max-width: 23.5rem) {
  .site-head .wrap {
    gap: 0.3rem;
  }
  .nav {
    font-size: 0.78rem;
  }
  .nav a {
    padding: 0.3rem 0.15rem;
  }
  .head-signup {
    padding: 0.3rem 0.35rem;
    font-size: 0.75rem;
  }
}

/* The last shave, at 336px and below — a 320px SE with a signed-out "Sign in"
   mark on the bar, which is the widest this header ever is at its narrowest.
   Everything gives a little and nothing is dropped: the four remaining links
   are the four places to go, and losing one of them to save eight pixels would
   be trading a destination for a gap. Measured, not guessed: without this the
   bar hung 32px off a 300px screen. */
@media (max-width: 22rem) {
  .site-head .wrap {
    gap: 0.2rem;
    padding-left: 0.4rem;
    padding-right: 0.4rem;
  }
  .nav {
    font-size: 0.7rem;
  }
  .nav a {
    padding: 0.3rem 0.1rem;
  }
  .head-theme,
  .head-signin {
    width: 1.55rem;
    height: 1.55rem;
  }
  .head-account .avatar {
    width: 1.5rem;
    height: 1.5rem;
  }
  .head-signup {
    padding: 0.28rem 0.28rem;
    font-size: 0.7rem;
  }
}

/* ------------------------------------------------- the signed-in shell -- */

/*
  IT IS NOT HERE. It is in assets/shell.css, and every page inside the product
  loads that file after this one.

  What used to be here was a second HEADER — a bar that replaced the marketing
  one when somebody signed in, with the four places on it, a search box and an
  "About the app" menu holding Features, Docs, Changelog, Roadmap, About and
  GitHub. Below it, ported but never rendered, was a left panel.

  Both are gone, for one reason each.

  The bar, because /home drew a panel instead and the two were different
  shapes: "If I click on community, it takes me back to the top header view. If
  I click on home, I goes back to side panel. … There is no consistency of type
  of view." One shell now, in one file, drawn identically on /home, the store,
  a listing, the conversations, a discussion, publishing, a profile and the
  account page.

  The menu, because those six pages are the site that SELLS the app: "like
  marketing page side, things should stay at marketing page". They are reached
  from the marketing header — which every one of those pages still has, signed
  in or not — and from one quiet link at the foot of the panel. Nothing became
  unreachable; it stopped being carried around inside the product.

  The MARKETING header above is untouched, and a product page with nobody
  signed in still draws it: /store and a listing are the shop window, and a
  stranger reading one is on the marketing site.
*/

/* --------------------------------------------- the workspace pane -- */

/*
  One workspace unit: a raised surface, a hairline head, a body. It is content
  rather than frame — the store's own pages build with it — so it stays in this
  stylesheet while the panel and the bar do not.
*/

/* One workspace unit: a raised surface, a hairline head, a body. */
.pane {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow-1);
}

/* Dark mode has no drop shadow at all — see --shadow-1 — so a 1px highlight
   along the top edge is what says "this is lifted off the ground" instead. */
.pane::before {
  content: '';
  position: absolute;
  inset: 0 0 auto;
  height: 1px;
  background: var(--card-top);
  pointer-events: none;
}

.pane-head {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  min-height: 48px;
  padding: var(--s-4) var(--s-5);
  border-bottom: 1px solid var(--line-soft);
}

.pane-head h2,
.pane-head h3 {
  font-size: var(--t-sm);
  font-weight: 600;
  letter-spacing: 0.01em;
}

.pane-head .spacer {
  margin-left: auto;
}

.pane-body {
  padding: var(--s-5);
}

.pane-body.flush {
  padding: 0;
}

/* --------------------------------------------------------------- bands -- */

/* Sections are separated by space first and a hairline second — the approved
   design has 80px between sections and no rules at all. The line stays here
   because this site's bands are much longer than that page's, and a reader
   scrolling a 2,000-word features page needs to see where one ends; it is
   the soft line rather than the full-strength one. */
.band {
  padding: var(--s-16) 0;
  border-top: 1px solid var(--border-subtle);
}

/* A tinted band on a tinted ground: --surface-2 is a step up from --bg in
   both themes, where --bg-secondary was a step up from white and therefore
   invisible once the ground stopped being white. */
.band-alt {
  background: var(--surface-2);
}

.band-tight {
  padding: var(--s-10) 0;
}

.band-head {
  max-width: var(--prose);
  margin-bottom: var(--s-8);
}

.band-head h2 {
  font-size: clamp(var(--t-xl), 1.05rem + 1vw, var(--t-2xl));
  letter-spacing: -0.025em;
}

.band-blurb {
  margin-top: var(--s-2);
  color: var(--text-quiet);
}

.eyebrow {
  font-size: var(--t-2xs);
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--s-3);
}

/* The two full-bleed scenes on the home page (assets/hero.css and
   assets/community-scene.css) run to both edges of the window, outside the
   padded column every other block sits in. Two things the bands around them
   need to know:

   A band rule drawn directly under a picture reads as a seam in the picture,
   so the row of links under the community scene is separated by space alone.
   And the scene is a jump target — the header is sticky, so an anchor landing
   on it has to stop clear of the bar. */
.cs-after {
  border-top: 0;
  padding-top: var(--s-8);
}

.h-stage,
.cs-stage {
  scroll-margin-top: 4rem;
}

/* ---------------------------------------------------------------- hero -- */

/* The header above is one 48px line now, so the hero no longer needs to push
   past a stack of chrome to look like the start of the page. */
.hero {
  padding: var(--s-12) 0;
  border-top: 0;
}

.hero h1 {
  font-size: clamp(2.1rem, 1.5rem + 2.6vw, 3.25rem);
  letter-spacing: -0.025em;
  max-width: 20ch;
}

.lede {
  margin-top: var(--s-5);
  max-width: 58ch;
  font-size: var(--t-lg);
  line-height: 1.5;
  color: var(--text-quiet);
}

.hero-who {
  margin-top: var(--s-4);
  max-width: 58ch;
  color: var(--text-quiet);
}

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

.cta-note {
  margin-top: var(--s-4);
  font-size: var(--t-md);
  color: var(--text-muted);
}

/* ------------------------------------------------------------- buttons -- */

/* The approved button: a raised surface with a hairline, not an outline drawn
   on the page. It is the same object in the workspace half of this site, and
   the same object the hero draws — one shape, three places. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: 36px;
  padding: 0 var(--s-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--text-primary);
  font: inherit;
  font-size: var(--t-md);
  font-weight: 500;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition:
    background var(--dur) var(--ease),
    border-color var(--dur) var(--ease),
    color var(--dur) var(--ease),
    transform var(--dur) var(--ease);
}

.btn svg {
  width: 16px;
  height: 16px;
}

.btn:hover {
  background: var(--surface-2);
  border-color: var(--border-strong);
  text-decoration: none;
}

.btn:active {
  transform: translateY(1px);
}

/* 44px and the body size — the one button a first screen is built around.
   Used by the hero on the home page. */
.btn-lg {
  min-height: 44px;
  padding: 0 var(--s-5);
  font-size: var(--t-base);
}

.btn-primary {
  background: var(--btn-bg);
  border-color: transparent;
  color: var(--btn-fg);
  font-weight: 540;
}

/* Not `opacity: 0.88`, which washed the fill toward the ground and took the
   white label with it. The accent has its own hover value in both themes. */
.btn-primary:hover {
  background: var(--accent-hi);
  border-color: transparent;
}

/* Quiet, not absent.
   With a transparent border and the quiet ink this rendered as grey caption
   text sitting beside a real button — "Read the discussions" on the landing
   page and "Load a file instead" on the publish page both read as disabled
   labels, and on a phone there is no hover to discover them with. It keeps the
   transparent fill; it gets back a hairline and readable ink. */
.btn-quiet {
  border-color: var(--border-subtle);
  background: transparent;
  box-shadow: none;
  color: var(--text-primary);
}

.btn-quiet:hover {
  background: var(--bg-hover);
  border-color: transparent;
  color: var(--text-primary);
}

/* ------------------------------------------------------------ features -- */

.features {
  display: grid;
  gap: 3rem;
}

.feature {
  display: grid;
  /* `start`, not the default. A grid item stretches to its row, and a grid with
     auto rows then shares that extra height out between them — so beside a
     taller neighbour, a two-paragraph feature was drawn with a hole under its
     heading. Only visible once two columns stopped having the same shape. */
  align-content: start;
  gap: var(--s-3);
  max-width: var(--prose);
}

.feature h3 {
  font-size: var(--t-lg);
}

.feature p {
  color: var(--text-primary);
}

.feature p + p {
  margin-top: var(--s-3);
}

@media (min-width: 60rem) {
  .features-2 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 3rem 3.5rem;
  }
  .features-2 .feature {
    max-width: none;
  }
}

/* --------------------------------------------------------------- cards -- */

.cards {
  display: grid;
  gap: var(--s-4);
  /* Written as a <ul> on the features page and as a <div> everywhere else, and
     nothing here ever reset the list. So every card grid on that one page was
     drawn indented by the UA's 40px padding with a bullet hanging outside each
     card — a marker on a box that already has a border and a radius. */
  list-style: none;
  padding: 0;
  /* 17.5rem lands on three columns at the page width, which is what the nine
     panel cards want. Narrower and the last row is one lonely card.
     The `min()` is what stops it below 328px, where a 17.5rem floor is wider
     than the column it sits in and pushed the whole page sideways. */
  /* auto-FILL, not auto-fit. auto-fit collapses the tracks nothing landed in,
     so one surviving card — "Skill 1", "Driving a browser 1" — was stretched
     across the whole three-column width and stopped looking like a card at all.
     auto-fill keeps the empty tracks and the card keeps its size. */
  grid-template-columns: repeat(auto-fill, minmax(min(17.5rem, 100%), 1fr));
}

/* A card floats on the ground: white paper in light, a raised surface with a
   1px top highlight in dark, where the palette carries no drop shadows. */
.card {
  position: relative;
  padding: var(--s-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}

.card::before {
  content: '';
  position: absolute;
  inset: 0 0 auto;
  height: 1px;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  background: var(--card-top);
  pointer-events: none;
}

/* A card that takes the whole row. An odd number of cards in a two-column grid
   leaves the last one stranded beside a hole; where the odd one out is also the
   one that behaves differently — the publish page's "installs nothing" kind —
   giving it the row says that as well as closing the gap. Below the grid's
   breakpoint there is one column and this changes nothing. */
.card-wide {
  grid-column: 1 / -1;
}

.card h3 {
  font-size: var(--t-base);
}

.card p {
  margin-top: var(--s-2);
  font-size: var(--t-md);
  color: var(--text-quiet);
}

/* ------------------------------------------------------- detail lists -- */

.detail {
  display: grid;
  grid-template-columns: minmax(8rem, max-content) 1fr;
  gap: var(--s-1) var(--s-5);
  font-size: var(--t-md);
  border-top: 1px solid var(--border-subtle);
  padding-top: var(--s-4);
}

.detail dt {
  color: var(--text-quiet);
}

.detail dd {
  margin: 0;
}

@media (max-width: 34rem) {
  .detail {
    grid-template-columns: 1fr;
    gap: 0.1rem;
  }
  .detail dd {
    margin-bottom: 0.6rem;
  }
}

/* --------------------------------------------------------------- lists -- */

.bullets {
  padding-left: var(--s-5);
}

.bullets li + li {
  margin-top: var(--s-2);
}

.bullets li::marker {
  color: var(--text-muted);
}

/* ----------------------------------------------------------- checklist -- */

/* Done/not-done lists on the roadmap and the features page. The marker is a
   glyph in the ::before rather than a list-style, because the two states have
   to be distinguishable without colour — a green tick and a grey circle read
   the same to a monochrome or colour-blind reader, so the shapes differ. */

.checklist {
  list-style: none;
  padding: 0;
  margin: 0;
  max-width: var(--prose);
}

.checklist li {
  position: relative;
  padding-left: var(--s-6);
}

.checklist li + li {
  margin-top: var(--s-2);
}

.checklist li::before {
  position: absolute;
  left: 0;
  font-family: var(--font-mono);
  font-size: 0.9em;
}

.checklist .done::before {
  content: '✓';
  color: var(--status-completed);
}

.checklist .todo {
  color: var(--text-quiet);
}

.checklist .todo::before {
  content: '○';
  color: var(--text-muted);
}

/* ---------------------------------------------------------- breadcrumb -- */

.breadcrumb {
  font-size: var(--t-md);
  color: var(--text-quiet);
}

.breadcrumb ol {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0;
  padding: 0;
}

.breadcrumb li + li::before {
  content: '/';
  margin-right: 0.4rem;
  color: var(--text-muted);
}

.breadcrumb a {
  color: var(--text-quiet);
}

/* ------------------------------------------------------- the download list -- */

/*
  THE CHOOSER. One row per artifact that actually exists.

  The download page used to open with two buttons and a paragraph, and the
  paragraph was doing the work: what the file is called, how big it is, that
  there is a portable one too, that the phones are somewhere else entirely.
  Nobody reads a paragraph to find out which of two buttons is theirs. So every
  build is a row now — what it is on the left, how to get it on the right — and
  the question the page answers is "which machine are you on", which is the
  question people actually arrive with.

  A row is not a card grid. These are read in order and compared against each
  other ("is the portable one the same build?"), and three columns of boxes at
  1440px turns a comparison into a scavenger hunt. One column, wide, aligned.

  ## The two rows with no button

  iPhone has nothing to download and says so in the row rather than being left
  off the page — somebody with an iPhone needs an answer, and silence is not
  one. House rule 2 is why it has no button rather than a disabled one: a
  control either works or it is not drawn. `.dl-none` marks it so its right-hand
  column can hold a sentence where a button would be, and gives the row the
  quieter ground that says "read this, do not press it".
*/
.dl-list {
  display: grid;
  gap: 1rem;
  list-style: none;
  padding: 0;
  margin: 0;
}

.dl {
  display: grid;
  /* The button column is fixed rather than fractional: every row's control
     should start at the same x, or the eye has to re-find it six times. */
  grid-template-columns: minmax(0, 1fr) 17rem;
  gap: 1.25rem 2rem;
  align-items: start;
  padding: 1.35rem 1.5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--bg-primary);
}

.dl-none {
  background: var(--bg-secondary);
}

/* One row whose "button" is a line of shell. It gets the full width, because
   the command is longer than the 17rem control column and a `curl … | sh` that
   breaks in the middle of its own URL is a command people squint at before they
   run it. The install line, in full, on one line. */
.dl-full {
  grid-template-columns: minmax(0, 1fr);
}

.dl-full .dl-get {
  justify-items: stretch;
}

.dl h3 {
  font-size: 1.05rem;
  letter-spacing: -0.01em;
}

.dl-who {
  margin-top: 0.4rem;
  max-width: 42rem;
  color: var(--text-muted);
}

.dl-note {
  margin-top: 0.65rem;
  max-width: 42rem;
  font-size: 0.9rem;
  color: var(--text-quiet);
}

.dl-get {
  display: grid;
  gap: 0.6rem;
  justify-items: start;
}

.dl-get .btn {
  width: 100%;
  justify-content: center;
}

.dl-meta {
  font-size: 0.82rem;
  line-height: 1.55;
  color: var(--text-quiet);
}

.dl-meta code {
  font-size: 0.78rem;
  /* These filenames are long and the column is 17rem. Wrapping them anywhere is
     better than a name that runs out of its own row. */
  overflow-wrap: anywhere;
}

.dl-instead {
  font-size: 0.9rem;
  color: var(--text-quiet);
}

/* The install line is the control in that row, so it sits where the buttons
   above and below it sit.

   It WRAPS rather than scrolling. The column is 17rem and the command is longer
   than that, so scrolling meant the line ended at "https://terminaldeck." with
   the rest of the address off the right — a command you cannot read is a
   command you cannot check before you pipe it into a shell. `pre-wrap` breaks
   the display and not the text: a copy still yields the one line that was
   written, because a soft wrap is not a newline. */
.dl-code {
  width: 100%;
  margin: 0;
  padding: 0.7rem 0.8rem;
  overflow-x: visible;
  font-size: 0.8rem;
}

/* `.code code` sets `white-space: pre`, which is right for a four-line block of
   shell and wrong for one long line in a narrow column. The tag is in the
   selector because that rule is 300 lines FURTHER DOWN this file and carries
   the same specificity — two classes each — so without it the later one wins
   and the command stays cut off at "https://terminaldeck." */
pre.dl-code code {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.dl-foot {
  margin-top: 1.5rem;
  max-width: var(--prose);
}

/* One column. The button column stops paying for itself the moment the text
   beside it is narrower than the button — measured at 52rem, which is where
   `.dl-who` starts breaking to four lines against a 17rem control. */
@media (max-width: 52rem) {
  .dl {
    grid-template-columns: minmax(0, 1fr);
  }
  .dl-get .btn {
    width: auto;
  }
}

.steps {
  counter-reset: step;
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: var(--s-6);
  max-width: var(--prose);
}

.steps > li {
  counter-increment: step;
  position: relative;
  padding-left: var(--s-10);
}

.steps > li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: 0.1rem;
  width: 1.65rem;
  height: 1.65rem;
  display: grid;
  place-items: center;
  border: 1px solid var(--border-strong);
  border-radius: 50%;
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  color: var(--text-quiet);
}

.steps h3 {
  font-size: var(--t-base);
  margin-bottom: var(--s-1);
}

.steps p + p {
  margin-top: var(--s-2);
}

/* --------------------------------------------------------------- notes -- */

.note {
  border: 1px solid var(--border);
  border-left: 3px solid var(--border-strong);
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
  background: var(--surface);
  padding: var(--s-4) var(--s-5);
  font-size: var(--t-md);
  max-width: var(--prose);
  box-shadow: var(--shadow-sm);
}

.note p + p {
  margin-top: var(--s-2);
}

.note-warn {
  border-left-color: var(--color-warning);
}

.note strong {
  font-weight: 600;
}

/* ---------------------------------------------------------------- code -- */

.code {
  overflow-x: auto;
  padding: var(--s-4) var(--s-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
  font-size: var(--t-sm);
  line-height: 1.7;
  max-width: var(--prose);
  box-shadow: var(--shadow-sm);
  -webkit-overflow-scrolling: touch;
}

.code code {
  font-size: inherit;
  white-space: pre;
}

/* A code block whose long lines wrap instead of scrolling sideways.

   `.code` is for commands, where a line break in the middle of one is a lie
   about what to type, so it scrolls. The publish page's example manifest is the
   other case: it is JSON to read rather than to retype, one line of it is a
   120-character summary, and a reader on a phone met that line clipped with no
   sign there was more of it. Wrapping is the honest shape for prose-shaped
   code — nothing is hidden and there is no sideways scroll on a narrow screen. */
.code-wrap code {
  display: block;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  /* A hanging indent, so a line that wrapped is visibly a continuation of the
     one above rather than a new field starting at the left margin. */
  padding-left: 2ch;
  text-indent: -2ch;
}

p code,
li code,
dd code,
td code {
  background: var(--surface-3);
  border-radius: var(--radius-sm);
  padding: 0.1em 0.35em;
}

/* -------------------------------------------------------------- tables -- */

.table-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

table {
  border-collapse: collapse;
  width: 100%;
  font-size: var(--t-md);
}

th,
td {
  text-align: left;
  padding: var(--s-2) var(--s-4) var(--s-2) 0;
  border-bottom: 1px solid var(--border-subtle);
  vertical-align: top;
}

th {
  font-weight: 600;
  color: var(--text-muted);
  font-size: var(--t-xs);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  border-bottom-color: var(--border);
  white-space: nowrap;
}

.keys td:first-child {
  white-space: nowrap;
  width: 1%;
}

kbd {
  display: inline-block;
  min-width: 1.5rem;
  padding: 0.15rem 0.4rem;
  border: 1px solid var(--border-strong);
  border-bottom-width: 2px;
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text-primary);
  font-size: var(--t-sm);
  line-height: 1.35;
  text-align: center;
}

/* -------------------------------------------------------- status dots -- */

.legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  padding: 0;
  margin: 0;
  list-style: none;
  font-size: 0.875rem;
}

.legend li {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
}

.dot {
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--status-idle);
  transform: translateY(-0.05rem);
}

.dot-working {
  background: var(--status-working);
}
.dot-waiting {
  background: var(--status-waiting);
}
.dot-input {
  background: var(--status-input);
}
.dot-done {
  background: var(--status-completed);
}
.dot-idle {
  background: var(--status-idle);
}

/* ------------------------------------------------------ schematic frame -- */
/* A drawn diagram of the window chrome — deliberately not a screenshot. */

.frame {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  max-width: 42rem;
}

.frame-bar {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-3);
  border-bottom: 1px solid var(--border-subtle);
  background: var(--surface-3);
  overflow-x: auto;
}

.frame-tab {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  flex: none;
  padding: var(--s-1) var(--s-3);
  border-radius: var(--radius-sm);
  font-size: var(--t-sm);
  color: var(--text-quiet);
  white-space: nowrap;
}

.frame-tab-on {
  background: var(--surface);
  color: var(--text-primary);
  box-shadow: var(--shadow-sm);
}

.frame-body {
  padding: var(--s-4) var(--s-5);
  font-size: var(--t-md);
  color: var(--text-quiet);
  background: var(--surface);
}

.frame-body p + p {
  margin-top: var(--s-2);
}

.frame-caption {
  margin-top: var(--s-3);
  font-size: var(--t-sm);
  color: var(--text-muted);
  max-width: 42rem;
}

/* ---------------------------------------------------------- docs layout -- */

.docs {
  display: grid;
  /* minmax(0,…) not 1fr: a grid item's automatic minimum size is its
     min-content, and one nowrap table cell would otherwise stretch the whole
     column past the viewport and scroll the page sideways. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-10);
  padding: var(--s-10) 0 var(--s-16);
}

.docs > * {
  min-width: 0;
}

@media (min-width: 60rem) {
  .docs {
    grid-template-columns: 15rem minmax(0, 1fr);
    gap: 3.5rem;
    align-items: start;
  }
  .docs-toc {
    position: sticky;
    top: 4.5rem;
    max-height: calc(100vh - 6rem);
    overflow-y: auto;
  }
}

.docs-toc h2 {
  font-size: var(--t-2xs);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--s-2);
}

.docs-toc ol {
  list-style: none;
  padding: 0;
  margin: 0;
  font-size: var(--t-md);
  border-left: 1px solid var(--border);
}

.docs-toc a {
  display: block;
  padding: 0.22rem 0.75rem;
  color: var(--text-quiet);
  text-decoration: none;
  border-left: 2px solid transparent;
  margin-left: -1px;
}

.docs-toc a:hover {
  color: var(--text-primary);
  border-left-color: var(--border-strong);
}

.docs-main > section {
  padding-top: var(--s-10);
  margin-top: var(--s-10);
  border-top: 1px solid var(--border-subtle);
  scroll-margin-top: 5rem;
}

.docs-main > section:first-child {
  padding-top: 0;
  margin-top: 0;
  border-top: 0;
}

/* The sticky header wraps as the window narrows — one row to 590px, two to
   440px, three below that — and `scroll-margin-top` has to clear whichever it
   is. At 5rem a jump from the contents list on a phone parked the heading
   exactly on the header's bottom edge, with the section's own rule and padding
   hidden behind it. These match the measured header heights plus the same
   1rem of air the wide layout gets. */
@media (max-width: 589px) {
  .docs-main > section {
    scroll-margin-top: 7.25rem;
  }
}

@media (max-width: 439px) {
  .docs-main > section {
    scroll-margin-top: 9.5rem;
  }
}

.docs-main h2 {
  font-size: 1.375rem;
  margin-bottom: 0.85rem;
}

.docs-main h3 {
  font-size: 1rem;
  margin-top: 1.75rem;
  margin-bottom: 0.5rem;
}

.docs-main p,
.docs-main ul,
.docs-main ol,
.docs-main .code,
.docs-main .note,
.docs-main .table-scroll,
.docs-main dl {
  max-width: var(--prose);
}

.docs-main p + p,
.docs-main p + ul,
.docs-main ul + p,
.docs-main p + .code,
.docs-main .code + p,
.docs-main p + .note,
.docs-main .note + p,
.docs-main p + .table-scroll,
.docs-main .table-scroll + p,
.docs-main p + dl {
  margin-top: 0.9rem;
}

/* ---------------------------------------------------------- flow page -- */

/* A plain long-form page — h2/h3 and prose straight down the column, no bands.
   The reset zeroes every margin, so a page that opts out of `.band` has to be
   given its rhythm back explicitly or it renders as one wall of text. Used by
   the features and roadmap pages, which are lists rather than a pitch. */

/* padding-top/bottom, never the shorthand: `.page` is worn alongside `.wrap`,
   whose `padding: 0 1.5rem` is the only thing keeping text off the left edge on
   a phone. A shorthand here wins on source order and zeroes it. */
.page {
  padding-top: var(--s-10);
  padding-bottom: var(--s-16);
}

.page > .breadcrumb {
  margin-bottom: var(--s-5);
}

.page h1 {
  font-size: clamp(1.9rem, 1.4rem + 1.8vw, 2.5rem);
  letter-spacing: -0.03em;
  margin-bottom: var(--s-3);
}

.page > .lede {
  margin: 0 0 var(--s-8);
  max-width: 58ch;
  font-size: var(--t-lg);
  line-height: 1.5;
  color: var(--text-quiet);
}

.page h2 {
  font-size: var(--t-xl);
  margin: var(--s-10) 0 var(--s-4);
  padding-top: var(--s-8);
  border-top: 1px solid var(--border-subtle);
}

.page h2:first-of-type {
  margin-top: var(--s-8);
}

/* A card's own heading is not a section rule across the page. The features page
   is an index of six cards, each headed by an h2 because each one IS a section
   of the page; without this they each drew the section separator inside the
   card, which is a hairline and eight rems of nothing above every title. */
.page .card h2 {
  font-size: var(--t-base);
  margin: 0;
  padding-top: 0;
  border-top: 0;
}

.page h3 {
  font-size: var(--t-base);
  margin: var(--s-6) 0 var(--s-2);
}

.page p,
.page ul,
.page ol,
.page dl,
.page .note,
.page .table-scroll {
  max-width: var(--prose);
}

.page p + p,
.page p + ul,
.page p + ol,
.page ul + p,
.page ol + p,
.page p + .note,
.page .note + p,
.page .cards + p,
.page p + .cards,
.page .legend + p,
.page p + .legend,
/* The ⓘ disclosure is in the prose flow on an item page, between the one line
   it hides behind and whatever follows it. Without these two it butts straight
   up against both. */
.page p + .more,
.page .more + p {
  margin-top: var(--s-4);
}

.page .cards,
.page .note {
  margin-top: var(--s-5);
}

.page .cards {
  max-width: none;
}

/* ----------------------------------------------------------- changelog -- */

.release {
  padding-top: var(--s-10);
  margin-top: var(--s-10);
  border-top: 1px solid var(--border-subtle);
}

.release:first-of-type {
  padding-top: 0;
  margin-top: 0;
  border-top: 0;
}

.release-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.75rem;
  margin-bottom: 0.35rem;
}

.release-head h2 {
  font-size: 1.375rem;
}

.tag {
  display: inline-block;
  padding: 0.1rem 0.5rem;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  font-size: 0.75rem;
  color: var(--text-quiet);
  white-space: nowrap;
}

.change-group {
  margin-top: 2rem;
  max-width: var(--prose);
}

.change-group h3 {
  font-size: 0.95rem;
  margin-bottom: 0.6rem;
}

.changes {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: 0.7rem;
}

/* MEASURED on /changelog.html: 74,936px tall, with sentences set one or two
   words per line and words broken mid-word ("managem / ent").

   The two columns exist for the commit hash. Twenty-one of the entries have no
   hash, so their SENTENCE became the first grid item and was laid out in the
   4.5rem column meant for a sha. The two-column rule therefore belongs only to
   the entries that actually have one; everything else is a paragraph. */
.changes li {
  display: block;
  font-size: 0.95rem;
}

.changes li:has(.sha) {
  display: grid;
  grid-template-columns: 4.5rem minmax(0, 1fr);
  gap: 0 0.9rem;
}

.changes .sha {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--text-quiet);
  padding-top: 0.28rem;
}

@media (max-width: 34rem) {
  .changes li {
    grid-template-columns: 1fr;
    gap: 0.1rem;
  }
  .changes .sha {
    padding-top: 0;
  }
}

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

/* THE FOOTER IS THE SITE MAP.

   It was one wrapped line of fifteen links, which is a list you scan rather
   than read: nothing said which of them was a place to learn something and
   which was a legal page, and adding the six pages the home page shed would
   have made it twenty-one items long in one grey row.

   Four named columns instead. It is the same idea as the header's menus, held
   open: everything the site has, grouped, on every page — which is what pays
   for the header dropping links at phone width and for the home page carrying
   almost none. */
.site-foot {
  border-top: 1px solid var(--border-subtle);
  padding: var(--s-12) 0 var(--s-12);
  font-size: var(--t-md);
  color: var(--text-quiet);
}

.foot-cols {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s-8) var(--s-6);
}

.foot-col {
  display: grid;
  align-content: start;
  gap: var(--s-2);
}

.foot-col h2 {
  margin: 0 0 var(--s-1);
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.foot-col a {
  color: var(--text-quiet);
  text-decoration: none;
}

.foot-col a:hover {
  color: var(--text-primary);
  text-decoration: underline;
}

.foot-col a[aria-current='page'] {
  color: var(--text-primary);
}

.foot-line {
  margin: var(--s-10) 0 0;
  padding-top: var(--s-5);
  border-top: 1px solid var(--border-subtle);
  font-size: var(--t-sm);
  color: var(--text-muted);
}

/* Two columns before the fourth one is a single word per line. Measured on the
   longest label there is — "Usage and context" — which is what sets the floor. */
@media (max-width: 52rem) {
  .foot-cols {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
/* ========================================================== the store ==== */

/* Everything below this line is new for terminaldeck.dev/store. It is the first
   search box, the first chip row, the first avatar and the first price this
   stylesheet has ever needed — before the store there was not one <form>,
   <input> or <select> anywhere on the site.

   No new colours. Every value here is one of the tokens at the top of this file,
   plus the four --bind-* fills below, which are lifted from the app's
   tokens.css:405 (light) and :648 (dark) so a monogram a person saw in the app
   is the same letter on the same colour here.

   Radii and the primary action are no longer this section's to decide. Both
   now come from the platform design system at the top of this file: the radius
   names point at the 6/10/14 scale the approved design is drawn on, and the
   one filled button wears the accent, because the two halves of this site
   agreed on one blue. What this paragraph used to say — 4/6/10, neutral ink —
   was true when the store was the only thing here. */

:root {
  /* Four fills a monogram chooses between. Each clears 4.5:1 with --bind-fg ink
     on every surface of its theme; the derivation is written out in the app's
     tokens.css beside the light values. */
  --bind-1: #725d9d;
  --bind-2: #327270;
  --bind-3: #955183;
  --bind-4: #626e2e;
  --bind-fg: #ffffff;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --bind-1: #a98ae8;
    --bind-2: #4fb3b0;
    --bind-3: #e07ac4;
    --bind-4: #9fb24a;
    /* Near-black on a bright fill: white on these four runs 2.4–2.8:1, which no
       chip survives. 6.7:1 this way. */
    --bind-fg: #0f1114;
  }
}

:root[data-theme='dark'] {
  --bind-1: #a98ae8;
  --bind-2: #4fb3b0;
  --bind-3: #e07ac4;
  --bind-4: #9fb24a;
  --bind-fg: #0f1114;
}

/* ------------------------------------------------------- the filter bar -- */

.storebar {
  display: grid;
  gap: 1rem;
  margin: 2rem 0 1.75rem;
  padding-bottom: 1.25rem;
  border-bottom: 1px solid var(--border);
}

.field {
  display: grid;
  gap: 0.35rem;
  max-width: 40rem;
}

.field > label {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--text-quiet);
}

.input,
.select,
.textarea {
  display: block;
  width: 100%;
  padding: 0.55rem 0.75rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--text-primary);
  font: inherit;
  font-size: 0.95rem;
  line-height: 1.5;
}

.input::placeholder {
  color: var(--text-muted);
}

.input:focus-visible,
.select:focus-visible,
.textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}

.select {
  width: auto;
  min-width: 12rem;
}

/* One facet: its label, then its chips. A grid rather than a flex row, so the
   labels line up in a column instead of each one starting wherever the row above
   happened to end — with flex, a facet whose chips wrap onto two lines pushed its
   own label onto a line of its own and the bar stopped reading as a list. */
.chipset {
  display: grid;
  grid-template-columns: 7rem minmax(0, 1fr);
  align-items: baseline;
  gap: 0.4rem 0.6rem;
}

.chipset + .chipset {
  margin-top: 0.5rem;
}

/*
 * THE SHELF, NOT THE FILTERS, ON A PHONE.
 *
 * MEASURED at 390px: the search box and three filter groups stack into
 * fourteen chips over four rows, and the first listing starts below the fold —
 * the whole first screen of the Store is controls for a list you cannot see.
 *
 * Each group becomes one line that scrolls sideways instead of wrapping, which
 * is the same trick the account tab strip uses two hundred lines down. Three
 * short rows instead of four tall ones puts the first card on the first screen,
 * and nothing is removed: every chip is still reachable, on its own row.
 *
 * The chips also grow to a real tap target here — they were 29px in a 44px
 * world.
 */
@media (max-width: 40rem) {
  .chipset {
    grid-template-columns: minmax(0, 1fr);
    gap: 0.25rem;
  }

  .chipset .chips {
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    gap: 0.5rem;
    padding-bottom: 0.15rem;
  }

  .chipset .chips::-webkit-scrollbar {
    display: none;
  }

  .chipset .chip {
    flex: 0 0 auto;
    padding: 0.55rem var(--s-3);
  }
}

.chipset-label {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--text-quiet);
}

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.chip {
  display: inline-flex;
  align-items: baseline;
  gap: var(--s-1);
  padding: var(--s-1) var(--s-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--text-primary);
  /* The store's chips are <button> and the community's are <a>, so the same
     control wore an underline on one page and not on the other. The chip is
     the affordance; the underline belonged to a link. */
  text-decoration: none;
  font: inherit;
  font-size: 0.85rem;
  line-height: 1.3;
  cursor: pointer;
}

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

/* A pressed chip changes its border as well as its fill. Colour alone is not a
   state: this row has to read on a monochrome screen and to somebody who cannot
   separate the two greys. */
.chip[aria-pressed='true'] {
  background: var(--bg-active);
  border-color: var(--border-strong);
  font-weight: 600;
}

.chip:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.chip-count {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--text-muted);
}

.storebar-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem 1.5rem;
}

.result-count {
  font-size: 0.85rem;
  color: var(--text-quiet);
}

.result-count strong {
  font-family: var(--font-mono);
  font-weight: 500;
  color: var(--text-primary);
}

@media (max-width: 34rem) {
  .chipset {
    grid-template-columns: minmax(0, 1fr);
    gap: 0.25rem;
  }
}

/* --------------------------------------------------------- the item card -- */

/* A card is an object with a button at the bottom of it, and every card in a
   row has that button on the same line. That is most of what makes a shelf read
   as a shelf rather than as nine paragraphs of different lengths: the eye finds
   the controls once and then only reads the ones it needs.

   `align-content` used to be `start`, which collapsed the single row to the
   height of its own content and left the spare height below it — so the text
   column could not be told to fill the card and nothing could be pinned to its
   foot. Stretch gives the column the card's full height; the 40px logo beside
   it has an explicit height and does not grow. */
.itemcard {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr);
  gap: 0.9rem;
  align-content: stretch;
}

.itemcard-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.itemcard h3 {
  font-size: 0.95rem;
}

.itemcard h3 a {
  color: inherit;
  text-decoration: none;
}

.itemcard h3 a:hover {
  text-decoration: underline;
}

.itemcard-by {
  margin-top: 0.15rem;
  font-size: 0.8rem;
  color: var(--text-muted);
}

.itemcard p {
  overflow-wrap: anywhere;
}

/* The store's cards sit inside <main class="page">, whose type rules were
   written for a reading column: 1.75rem above every h3 and 0.9rem between
   consecutive paragraphs. Inside a card that is a hole under the heading and a
   gap between a name and the line naming its author. Scoped back here, and only
   for these cards, so no existing page moves. */
.page .itemcard h3 {
  margin: 0;
}

.page .itemcard p {
  max-width: none;
}

.page .itemcard .itemcard-by {
  margin-top: 0.15rem;
}

.page .itemcard .itemcard-by + p {
  margin-top: 0.5rem;
}

.page .itemcard .meta {
  margin-top: 0.5rem;
}

/* ---------------------------------------------- a form page's own crumbs -- */

/* The four pages people reach with a purpose — join, publish, write a manifest,
   your account — had no breadcrumb and no way back to the store except the
   header. Every other page in the section has one, so these have one now, in
   the same shape and the same place. `.form-page` sets its own top padding, so
   the crumb takes it and the h1 keeps its own spacing off the crumb. */
.form-page > .breadcrumb {
  margin-bottom: 1.25rem;
}

/* One short line and two links, drawn once for somebody who has just chosen a
   name. assets/site-account.js draws it; it is on every page of the site for
   the reason written there. */
.firstrun {
  margin: 0 0 1.5rem;
  padding: 0.7rem 1rem;
  border: 1px solid var(--read-line);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-lg);
  background: var(--read-surface);
  font-size: 0.95rem;
  max-width: none;
}

/* --------------------------------------------------- links under a title -- */

/* Where a page sends you next, sitting right under the one line that says what
   the page is. Every one of these replaced a paragraph whose only real content
   was the link inside it, which is house rule 1: one short line, and the
   explaining goes behind the ⓘ. */
.page-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1.25rem;
  margin-top: 0.75rem;
  max-width: none;
  font-size: 0.9rem;
}

/* ------------------------------------------------ scanning a shelf of them -- */

/* Two questions decide whether a row is worth reading: what IS it, and does it
   need anything from me? Both used to be words inside one dot-separated run of
   six facts, at the same size and colour as the licence and the date. A run
   like that is read left to right or not at all, and nobody reads thirty of
   them. So the kind comes out as a chip above the name, the requirement comes
   out as its own line under the summary, and what is left in the meta run is
   genuinely secondary. */

.itemcard-kind {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem;
  margin: 0 0 0.2rem;
  line-height: 1;
}

.kindchip {
  display: inline-block;
  padding: 0.15rem 0.45rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--read-line);
  background: var(--read-surface);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--read-ink-3);
  white-space: nowrap;
}

.page .itemcard .itemcard-sum {
  margin-top: 0.5rem;
  color: var(--read-ink-2);
  /* Three lines is the longest summary the grammar allows at this width; past
     that a card would set the height of its whole row for one verbose
     publisher. */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.page .itemcard .itemcard-needs {
  margin-top: 0.5rem;
  font-size: 0.8rem;
  color: var(--read-ink-2);
  max-width: none;
}

/* "Nothing to set up" is the good answer, so it is not shouted — it is the one
   that lets the eye move on. Quieter than a requirement, never green: a colour
   here would make every other row look like a warning. */
.page .itemcard .itemcard-needs.is-clear {
  color: var(--read-ink-4);
}

/* The price, beside the kind. Sentence case rather than the kind chip's small
   caps: "Free, needs an account" is four words the eye has to read, and small
   caps are for a label the eye recognises without reading. Same box, so the two
   sit as a pair, and no colour of their own — a price is a fact, not a warning
   and not an offer. */
.pricechip {
  display: inline-block;
  padding: 0.15rem 0.45rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--read-line);
  background: var(--read-surface);
  font-size: 0.7rem;
  font-weight: 500;
  line-height: 1.4;
  color: var(--read-ink-2);
}

/* ----------------------------------------------------- what a card can do -- */

/* `margin-top: auto` inside the flex column is the whole trick: whatever the
   summary and the requirement line did above, the buttons fall to the bottom of
   the card, so a row of cards has one line of controls across it. */
.itemcard-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  margin-top: auto;
  padding-top: 0.9rem;
}

/* `.page p + p` sets a 16px top margin on every consecutive paragraph in the
   reading column, and it is more specific than the rule above — so the `auto`
   that does the pinning has to be restated here or the buttons sit wherever the
   text left them. That was the whole bug: cards the same height with their
   controls on three different lines. */
.page .itemcard .itemcard-actions {
  margin-top: auto;
  max-width: none;
}

.itemcard-actions .btn {
  min-height: 32px;
  padding: 0 0.7rem;
  font-size: 0.82rem;
}

/* Nowhere to send you is a sentence, never a greyed-out button. A disabled
   control is a promise that it would work if something changed, and nothing
   here is going to change until the publisher adds an address. */
.itemcard-nolink {
  font-size: 0.8rem;
  color: var(--read-ink-4);
}

/* -------------------------------------------------------- the logo slot -- */

.logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-lg);
  overflow: hidden;
  flex: none;
}

.logo-lg {
  width: 56px;
  height: 56px;
  font-size: 1.5rem;
}

/* A letter on a colour, not a grey square with a broken-image glyph. A
   placeholder pretending to be a logo is the card version of a button that does
   nothing; a letter is honestly this site's own drawing and reads as one. */
.logo-mono {
  color: var(--bind-fg);
  font-weight: 600;
  font-size: 1.1rem;
  line-height: 1;
  user-select: none;
}

.logo-fill-1 { background: var(--bind-1); }
.logo-fill-2 { background: var(--bind-2); }
.logo-fill-3 { background: var(--bind-3); }
.logo-fill-4 { background: var(--bind-4); }

/* ---------------------------------------------------------- the meta row -- */

/* Monospace, because DESIGN-BRIEF.md reserves it for what is exact and
   countable — a version, a count, a hash. The words beside them are not mono. */
.meta {
  margin-top: 0.5rem;
  font-size: 0.75rem;
  color: var(--text-muted);
  display: flex;
  flex-wrap: wrap;
  gap: 0.2rem 0.45rem;
}

.page .meta {
  margin-top: 0.5rem;
}

/* ------------------------------------------------------------- the price -- */

.price {
  font-weight: 600;
}

.price-claim {
  font-weight: 500;
  color: var(--text-quiet);
}

.rating-none {
  color: var(--text-quiet);
  font-size: 0.9rem;
}

/* --------------------------------------------------------- an item page -- */

.item-head {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-top: 0.5rem;
}

.item-head h1 {
  margin: 0;
}

.item-by {
  margin-top: 0.35rem;
  font-size: 0.9rem;
  color: var(--text-quiet);
}

.page .item-by .mono {
  font-size: 0.85rem;
  color: var(--text-muted);
}

.tagrow {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.page .tagrow {
  margin-top: 1.5rem;
  max-width: none;
}

.page .price {
  margin-top: 1.25rem;
}

.tag-link {
  display: inline-block;
  padding: 0.15rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: 0.8rem;
  color: var(--text-quiet);
  text-decoration: none;
}

.tag-link:hover {
  background: var(--bg-hover);
  color: var(--text-primary);
  text-decoration: none;
}

/* A 64-hex digest has no spaces in it, so nothing can break the line and the
   whole page goes sideways on a phone without this. */
.fingerprint {
  overflow-wrap: anywhere;
  font-size: 0.8rem;
}

/* ------------------------------------------------- reading a listing -- */

/* A listing is the one page on this site a stranger lands on cold, and until
   now it read as eight headings with a fact under each. These rules are the
   reading half of that page: a measure text actually sits inside, one small
   table of the five things people came for, and real weight on the section that
   tells them how to install the thing.

   ## The token aliases

   Every colour below goes through one of the six --read-* names. Each of them
   names the new design system's token FIRST and falls back to this sheet's
   existing one, so this section is correct both before and after the workspace
   palette lands — one lane porting tokens cannot leave the other lane's page
   colourless, and neither of us has to guess when the other merges. */
:root {
  --read-ink: var(--ink, var(--text-primary));
  --read-ink-2: var(--ink-2, var(--text-secondary));
  --read-ink-3: var(--ink-3, var(--text-quiet));
  --read-ink-4: var(--ink-4, var(--text-muted));
  --read-line: var(--line, var(--border));
  --read-surface: var(--surface-2, var(--bg-secondary));

  /* 68 characters. The rule of thumb is 65–75; below 60 the eye jumps lines
     too often and above 80 it loses the start of the next one. `ch` is the
     width of a zero in the page's own face, so this tracks the font rather
     than a pixel count taken on one machine. */
  --prose-read: 68ch;
}

.page .lede,
.page .said-by p,
.page .said-by li,
.page .steps li,
.page .asks li {
  max-width: var(--prose-read);
}

/* --------------------------------------------------------- at a glance -- */

/* Five facts, one line each, above everything else on the page: what it is,
   what it needs, what it will do to this machine, who wrote it, what it costs.
   A <dl> and not a paragraph — a person reads down the labels for the one they
   came for, and a sentence would make them read all five to find it. */
.glance {
  margin: 1.5rem 0 0;
  border: 1px solid var(--read-line);
  border-radius: var(--radius-lg);
  background: var(--read-surface);
  overflow: hidden;
  max-width: none;
}

.glance-row {
  display: grid;
  grid-template-columns: 10.5rem minmax(0, 1fr);
  gap: 0 1rem;
  padding: 0.7rem 1rem;
}

.glance-row + .glance-row {
  border-top: 1px solid var(--read-line);
}

.page .glance dt {
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--read-ink-4);
  margin: 0;
  padding-top: 0.05rem;
}

.page .glance dd {
  margin: 0;
  font-size: 0.95rem;
  color: var(--read-ink);
  min-width: 0;
  overflow-wrap: anywhere;
}

/* The one-line explanation of a kind, the hint under an input's name, the
   aside beside a handle. Quiet, and never the thing being read. */
.glance-note {
  color: var(--read-ink-4);
  font-weight: 400;
}

.glance-handle {
  font-size: 0.85rem;
  color: var(--read-ink-4);
}

.glance-price {
  color: var(--read-ink-2);
}

/* Each requirement as its own object, because "Needs Node.js Needs a key you
   supply Needs an account" run together in one sentence is three facts nobody
   separates at a glance. */
.need {
  display: inline-block;
  padding: 0.05rem 0.45rem;
  margin: 0.1rem 0.25rem 0.1rem 0;
  border: 1px solid var(--read-line);
  border-radius: var(--radius-sm);
  font-size: 0.8rem;
  color: var(--read-ink-2);
  background: var(--bg-primary, transparent);
}

@media (max-width: 30rem) {
  .glance-row {
    grid-template-columns: minmax(0, 1fr);
    gap: 0.15rem;
  }
}

/* ------------------------------------------------------ how to install it -- */

/* The numbered circles are this site's own `.steps`, already used on the
   download and docs pages — house rule 6, and the reason there is no second
   step component here. The one thing that differs is the air between them:
   `.steps` was drawn for steps that are a heading and a paragraph each, and at
   1.5rem apart two one-line steps read as two separate sections. */
.steps-tight {
  gap: 0.6rem;
}

.page .steps {
  margin-top: 1rem;
  max-width: var(--prose-read);
}

.page .asks {
  max-width: var(--prose-read);
  margin-top: 0.6rem;
  padding-left: 1.1rem;
  list-style: disc;
}

.page .asks li {
  margin-top: 0.3rem;
}

/* A command is meant to be copied, so it gets the box that says so rather than
   a run of monospace inside a sentence. */
.cmd {
  margin-top: 0.75rem;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--read-line);
  border-radius: var(--radius-lg);
  background: var(--read-surface);
  overflow-x: auto;
  font-size: 0.85rem;
  max-width: none;
}

.cmd code {
  font-family: var(--font-mono, ui-monospace, monospace);
  color: var(--read-ink);
  white-space: pre;
  background: none;
  border: 0;
  padding: 0;
}

/* The command and the button that copies it, side by side on a wide screen and
   stacked on a phone. The button is drawn hidden and unhidden by
   assets/store-copy.js once there is a clipboard to write to — see that file. */
.cmdblock {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  margin-top: 0.75rem;
}

.cmdblock .cmd {
  flex: 1 1 auto;
  min-width: 0;
  margin-top: 0;
}

.cmdblock .copy-cmd {
  flex: none;
  min-height: 36px;
  /* Wide enough that "Copy" does not become "Copied" and shove the block
     sideways on every press. The refusal message is longer and does move it,
     once, on a browser that would not write to the clipboard at all. */
  min-width: 5.5rem;
}

.cmdblock .copy-cmd[hidden] {
  display: none;
}

.copy-cmd.is-copied {
  color: var(--ok);
  border-color: var(--ok);
}

@media (max-width: 34rem) {
  .cmdblock {
    flex-direction: column;
    align-items: stretch;
  }
}

/* -------------------------------------------- what the publisher wrote -- */

/* Their file, rendered for a person instead of served only as a .txt. It gets
   a left rule and a measure so it reads as a quotation from somebody else and
   not as more of the site's own copy — which matters here more than it usually
   would, because this store lists things it has not endorsed. */
.said-by {
  margin-top: 1rem;
  padding-left: 1.1rem;
  border-left: 2px solid var(--read-line);
  max-width: none;
}

.page .said-by p {
  margin-top: 0.9rem;
  color: var(--read-ink-2);
}

.page .said-by h3 {
  margin: 1.6rem 0 0.1rem;
  font-size: 0.95rem;
  color: var(--read-ink);
}

.page .said-by > :first-child {
  margin-top: 0;
}

.page .said-by ul,
.page .said-by ol {
  margin-top: 0.7rem;
  padding-left: 1.2rem;
  max-width: var(--prose-read);
  list-style: disc;
  color: var(--read-ink-2);
}

.page .said-by ol {
  list-style: decimal;
}

.page .said-by li {
  margin-top: 0.3rem;
}

.page .said-by pre {
  margin-top: 0.8rem;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--read-line);
  border-radius: var(--radius-lg);
  background: var(--read-surface);
  overflow-x: auto;
  font-size: 0.85rem;
}

.page .said-by pre code {
  white-space: pre;
  border: 0;
  padding: 0;
  background: none;
}

.page .said-by code {
  font-size: 0.85em;
}

/* Where a listing sends you next, once the page is read. Two links on one
   line, quiet, at the very bottom — the shape every page in this store ends
   with so a person never reaches a dead end. */
.page .onward {
  margin-top: 2.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--read-line);
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.5rem;
  max-width: none;
  font-size: 0.9rem;
}

/* --------------------------------------------------------- a profile page -- */

.profile-head {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-top: 0.5rem;
}

.profile-head h1 {
  margin: 0;
}

.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  border-radius: var(--radius-lg);
  object-fit: cover;
  flex: none;
}

.avatar-mono {
  /* --bind-1 is the fill when no .logo-fill-N is set beside it: /account draws
     a monogram from one name and has no hash to choose a fill with, and white
     ink on no background is an invisible avatar. */
  background: var(--bind-1);
  color: var(--bind-fg);
  font-weight: 600;
  font-size: 1.75rem;
  line-height: 1;
  text-transform: uppercase;
  user-select: none;
}

.page .profile-handle {
  margin-top: 0.2rem;
  font-size: 0.9rem;
  color: var(--text-muted);
}

.page .profile-note {
  margin-top: 1rem;
  font-size: 0.9rem;
  color: var(--text-quiet);
}

.page .profile-bio {
  max-width: var(--prose);
}

.page .profile-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 0.5rem;
  font-size: 0.9rem;
}

/* -------------------------------------------------- a list of conversations -- */

/* Worn on a publisher page ("recent discussion") and twice on /account ("your
   discussions", "what has happened since you looked"). Deliberately NOT a card:
   a conversation is a line and a place it happened, and giving it a card would
   give it the same weight on the page as a listing somebody published.

   One rule holds it together — the title is the only thing on the line that is a
   link. _threadlink.js decides where it goes, borrowing the scheme from
   _discussion.js so the store only ever has one of them; this only draws it. */
.threadlist {
  list-style: none;
  padding: 0;
  margin: 0;
  max-width: var(--prose);
}

.threadlist > li {
  padding: 0.7rem 0;
  border-top: 1px solid var(--border-subtle);
}

.threadlist > li:first-child {
  border-top: 0;
}

.threadlist-title {
  font-weight: 500;
  color: var(--text-primary);
}

a.threadlist-title {
  color: var(--link);
}

.page .threadlist .meta,
.threadlist .meta {
  margin-top: 0.15rem;
}

/* The count of answers that arrived since this person last looked. There is no
   mailer on the box yet, so this number IS the notification — it gets the accent
   and nothing else on these pages does. */
.unread {
  display: inline-block;
  padding: 0.05rem 0.4rem;
  border-radius: var(--radius-sm);
  background: var(--accent-soft);
  color: var(--link);
  font-size: 0.75rem;
  font-weight: 600;
}

/* ---------------------------------------------------- something is wrong -- */

/* Lifted out of review.html:91, where it was written inline for one page. The
   store needs it on four, and a fifth copy of the same eleven lines is how two
   of them end up different. review.html keeps its own copy: this batch does not
   touch that page. */
.problem {
  margin: 1.5rem 0;
  padding: 0.9rem 1.1rem;
  border-left: 3px solid var(--color-critical);
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
  background: var(--surface);
  font-size: 0.9rem;
}

.problem p + p {
  margin-top: 0.6rem;
}
/* ==========================================================================
   THE STORE — the first form controls this stylesheet has ever needed.

   Verified before this block was written: there was not one <form>, <input>,
   <select> or <textarea> anywhere in the twelve pages of this site. Everything
   below is new and every value in it comes from the tokens at the top of this
   file — no new colours, no new radii, no second type scale.

   Two rules from that header apply directly and are worth restating where the
   next person will be tempted to break them:

     * the primary action stays neutral ink (--btn-bg), never blue. The one blue
       in this palette means "link or focus", and a filled blue button spends it.
     * a state is never colour alone. A field in error gains a left border and a
       sentence under it, not a red outline on its own.

   MERGED, 29 August. The browse lane above owns the one definition of .field,
   .input/.select/.textarea, their focus ring, .problem, .avatar and
   .avatar-mono; the duplicates that stood here were removed rather than left to
   fight over the cascade. Everything below is what only the forms need.
   ========================================================================== */

/* ------------------------------------------------------------- the fields -- */

.field + .field,
.field + .form-actions,
.fieldset > * + * {
  margin-top: 1.15rem;
}

.field > .label {
  display: block;
  font-size: 0.9rem;
  font-weight: 500;
}

/* The one place mono is right on an input: a repository address, a handle and a
   commit are all things that are read character by character. */
.input-mono {
  font-family: var(--font-mono);
  font-size: 0.875rem;
}

.textarea {
  min-height: 5.5rem;
  resize: vertical;
}

.input:disabled,
.textarea:disabled {
  background: var(--surface-3);
  color: var(--text-quiet);
  cursor: not-allowed;
}

/* One short line under a field. Anything longer belongs behind the ⓘ. */
.field-hint {
  margin-top: 0.35rem;
  font-size: 0.85rem;
  color: var(--text-quiet);
}

.field-hint.hint-tight {
  margin-top: 0;
}

.field-hint.is-bad {
  color: var(--color-critical);
}

.field-hint.is-good {
  color: var(--status-completed);
}

/* A handle is shown beside its prefix so the address is the thing being chosen,
   not the word. */
.field-prefix {
  display: flex;
  align-items: stretch;
  max-width: var(--prose);
}

.field-prefix > .prefix {
  display: flex;
  align-items: center;
  padding: 0 0.6rem;
  border: 1px solid var(--border-strong);
  border-right: 0;
  border-radius: var(--radius-md) 0 0 var(--radius-md);
  background: var(--surface-3);
  font-family: var(--font-mono);
  font-size: 0.875rem;
  color: var(--text-quiet);
  white-space: nowrap;
}

.field-prefix > .input {
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
}

.form-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  margin-top: 1.5rem;
}

.form-actions .btn[disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}

/* --------------------------------------------------------------- messages -- */

.said {
  max-width: var(--prose);
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--border);
  border-left-width: 3px;
  border-radius: var(--radius-md);
  background: var(--surface);
  font-size: 0.9rem;
}

.said-bad {
  border-left-color: var(--color-critical);
}

.said-good {
  border-left-color: var(--status-completed);
}

/* A message above the first section, which `.first-shown` has deliberately
   stripped the top margin from. Without this the sentence and the heading under
   it touch — seen on /store/publish the moment the page had anything to say
   before its form. */
.said:not([hidden]) + .section.first-shown {
  margin-top: var(--s-6);
}

/* --------------------------------------------------------------- avatars -- */

.avatar-lg {
  width: 72px;
  height: 72px;
}

.avatar-lg.avatar-mono {
  font-size: 2rem;
}

.avatar-row {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}

.avatar-row .form-actions,
.form-actions.avatar-actions {
  margin-top: 0;
}

.after-avatar {
  margin-top: 1.75rem;
}

/* ---------------------------------------------------------- the sign-in row -- */

.signin {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
}

.signin .btn svg {
  flex: none;
}

/* ------------------------------------------------------------- the preview -- */

/* What a listing will look like, drawn before it exists. Deliberately plain:
   this is a receipt, not a shelf. */
.preview {
  max-width: var(--prose);
  padding: 1.15rem 1.25rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
}

.preview h3 {
  font-size: 1.05rem;
  margin: 0;
}

.preview .summary {
  margin-top: 0.3rem;
  color: var(--text-quiet);
  font-size: 0.9rem;
}

/* Mono for what is exact and countable, and nothing else. */
.mono-row {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  color: var(--text-quiet);
  overflow-wrap: anywhere;
}

.tierline {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-top: 0.9rem;
  padding-top: 0.85rem;
  border-top: 1px solid var(--border-subtle);
  font-size: 0.9rem;
}

.tierline .tier {
  font-weight: 600;
}

/* Tier 3 is the one that runs a program. It gets weight, not a colour, so it
   still reads as the loudest line in a monochrome screenshot. */
.tierline[data-tier='3'] .tier {
  color: var(--color-warning);
}

.pill {
  display: inline-block;
  padding: 0.1rem 0.45rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  font-size: 0.8rem;
  color: var(--text-quiet);
}

.pill + .pill {
  margin-left: 0.3rem;
}

/* ------------------------------------------------- what an AI will be handed --

   A publisher who cannot see the file they are giving to every assistant that
   reads their listing is publishing blind, so the publish form prints the first
   fourteen lines of it back. Mono, because this is the file byte for byte and
   not a rendering of it — it is served as text/plain and never as markup, and
   the type has to say so. It scrolls sideways rather than wrapping for the same
   reason .code does: a wrapped line looks like two lines the publisher did not
   write. */
.ai-preview {
  margin-top: 0.9rem;
  padding: 0.85rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  line-height: 1.55;
  white-space: pre;
  overflow-x: auto;
  color: var(--text-primary);
}

.ai-more {
  margin-top: 0.4rem;
  font-size: 0.8rem;
  color: var(--text-quiet);
}

/* ------------------------------------------------------------ page furniture -- */

/* These pages have no hero band, so the page itself carries the rhythm the
   .hero would otherwise have given it. */
.form-page {
  max-width: var(--prose);
  padding: 3.25rem 0 5rem;
}

.form-page h1 {
  font-size: 1.7rem;
  line-height: 1.2;
  margin: 0 0 0.6rem;
}

.form-page > .lede {
  margin-top: 0;
  margin-bottom: 2rem;
}

.section + .section {
  margin-top: 2.75rem;
  padding-top: 2.25rem;
  border-top: 1px solid var(--border-subtle);
}

/* Set by TD.rules() on the first section that is actually shown. A rule above
   the top of the page is a rule under the heading, which is not what it means. */
.section.first-shown {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

.section > h2 {
  font-size: 1.15rem;
  margin: 0 0 1rem;
}

/* One short line, and the long version behind a disclosure. House rule 1 in a
   component: nothing on a page is a paragraph of explanation. */
.more {
  max-width: var(--prose);
  font-size: 0.875rem;
}

.more > summary {
  cursor: pointer;
  color: var(--link);
  list-style: none;
}

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

.more > summary::before {
  content: 'ⓘ ';
}

.more > div {
  margin-top: 0.5rem;
  color: var(--text-quiet);
  line-height: 1.6;
}

/* What is behind the ⓘ is prose, and prose that is three paragraphs long has to
   read as three. Without this they run together into a wall, which is the exact
   thing house rule 1 puts them behind a disclosure to avoid. */
.more > div > p + p {
  margin-top: 0.75rem;
}

/* A disclosure directly under a form field, explaining that field. */
.field + .more {
  margin-top: 1rem;
}

/* Off screen, still focusable, still announced. A file input styled to look like
   a button stops being keyboard-reachable and stops telling a screen reader what
   it is; a <label class="btn"> in front of a hidden real input keeps both. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The label is the control, so it has to show focus when the input behind it has it. */
.visually-hidden:focus-visible + .btn,
.btn:has(+ .visually-hidden:focus-visible),
.visually-hidden:focus-visible ~ .btn {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* A row of tick boxes — the agents, the platforms, what an item needs. Wraps on
   a phone rather than running off the side. */
.ticks {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.1rem;
  margin-top: 0.5rem;
  font-size: 0.9rem;
}

.ticks label {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}

/* Several inputs that are one field between them: the four profile links. */
.inputs > * + * {
  margin-top: 0.5rem;
}

/* Rows a person owns: one listing and where it has got to. */
.rows {
  list-style: none;
  padding: 0;
  margin: 0;
  max-width: var(--prose);
}

.rows > li {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  flex-wrap: wrap;
  padding: 0.7rem 0;
  border-top: 1px solid var(--border-subtle);
}

.rows > li:first-child {
  border-top: 0;
}

.rows .said {
  flex: 1 1 100%;
}

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

/* What to do next about one listing. Sits under the row it belongs to, quiet,
   because it is an instruction and not a verdict. */
.rows .next {
  flex: 1 1 100%;
  margin-top: 0.15rem;
  font-size: 0.85rem;
  color: var(--text-quiet);
}

/* ==========================================================================
   THE COMMUNITY — conversations, on a listing and on their own page.

   No new colours and no new radii: every value here is a token from the top of
   this file, and the badges reuse the four --bind-* fills the monograms already
   use. Two things are worth saying out loud, because they are decisions rather
   than styling:

     * A post is a block of text on the page ground, separated by a rule, not a
       card in a box. Threading a conversation through nested boxes on a 390px
       phone spends thirty of its pixels per level on border and padding; a rule
       and an indent say the same thing and cost four.
     * Every control under a post is a `.linkbtn` — a button that looks like the
       quiet text it is. A row of five filled buttons under every post would
       shout louder than the words they belong to, and the words are the page.
   ========================================================================== */

/* ------------------------------------------------------------- a small face -- */

.avatar-sm {
  width: 28px;
  height: 28px;
  border-radius: var(--radius-md);
}

.avatar-sm.avatar-mono {
  font-size: 0.85rem;
}

/* ----------------------------------------------------------------- the posts -- */

.discussion {
  margin-top: 1.25rem;
  max-width: var(--prose);
}

.threads,
.replies {
  list-style: none;
  padding: 0;
  margin: 0;
}

.thread + .thread {
  margin-top: 1.75rem;
  padding-top: 1.75rem;
  border-top: 1px solid var(--border);
}

/*
  The way in, and the first thing said.

  `.threads` has no margin of its own and `.thread + .thread` only separates
  posts from each other, so whatever stands above the list — the two provider
  buttons a stranger is offered, or the "Ask about this listing" button a
  member gets — sat directly on the first byline with nothing between them.
  Same gap the posts give each other, and the same rule, so the invitation
  reads as its own thing rather than as part of the first post.
*/
/* `~`, not `+`: a <noscript> sits between them in the markup. */
.start-slot:not([hidden]) ~ .threads {
  margin-top: 1.75rem;
  padding-top: 1.75rem;
  border-top: 1px solid var(--border);
}

.thread-title {
  font-size: 1.05rem;
  margin-bottom: 0.6rem;
}

.thread-title a {
  color: var(--text-primary);
  text-decoration: none;
}

.thread-title a:hover {
  text-decoration: underline;
}

/* The answers, indented by a rule rather than a box. */
.replies {
  margin: 0.75rem 0 0 0;
  padding-left: 1rem;
  border-left: 2px solid var(--border);
}

.replies > li + li {
  margin-top: 1rem;
}

.post {
  padding: 0.35rem 0;
}

.post-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem 0.55rem;
  font-size: 0.85rem;
  color: var(--text-quiet);
}

/* A button that lives inside a line of text — "Read and approved. [Put it on the
   shelf]". Same button, one size down, so the sentence stays the thing being
   read and the control stays a control. */
.btn-sm {
  padding: 0.15rem 0.55rem;
  font-size: 0.85rem;
}

/* ------------------------------------------------------- closing an account -- */

/* The one destructive control on this site. It is drawn as a plain section with
   a red-edged box around the consequences — not a red button somebody can press
   by reflex, and not a modal that hides what it is about to do behind a word
   like "confirm". The list of what happens is ON the page, and the button is
   disabled until the person types their own handle, because that is the one
   thing they cannot type by accident. */
.danger {
  margin-top: 1rem;
  padding: 1rem 1.1rem;
  border-left: 3px solid var(--color-critical);
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
  background: var(--surface);
  max-width: var(--prose);
}

.danger > p + p {
  margin-top: 0.7rem;
}

.danger form {
  margin-top: 1.1rem;
}

.danger ul {
  margin: 0.6rem 0 0;
  padding-left: 1.1rem;
  font-size: 0.9rem;
  color: var(--text-quiet);
}

.post-who {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  color: var(--text-primary);
  text-decoration: none;
  font-weight: 500;
}

.post-who:hover .post-name {
  text-decoration: underline;
}

.post-when,
.post-edited {
  font-size: 0.8rem;
  color: var(--text-muted);
}

.page .post-body {
  margin-top: 0.5rem;
}

.page .post-body p + p {
  margin-top: 0.7rem;
}

.post-body code {
  padding: 0.05em 0.3em;
  border-radius: var(--radius-sm);
  background: var(--surface-3);
}

/* ---------------------------------------------------------------- the badges -- */

/* A word on a tint, never a colour on its own: the badge says what it is in
   letters, so somebody who cannot tell the fills apart loses nothing. */
.post-badge {
  padding: 0.05rem 0.4rem;
  border-radius: var(--radius-sm);
  background: var(--surface-3);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--text-quiet);
  white-space: nowrap;
}

.post-badge-publisher {
  background: var(--accent-soft);
  color: var(--link);
}

.post-badge-answer,
.post-accepted > .post-answer {
  background: color-mix(in srgb, var(--status-completed) 14%, transparent);
  color: var(--status-completed);
}

.post-badge-quiet {
  background: transparent;
  border: 1px solid var(--border);
}

/* The one post on a page that is the answer. A left edge and a line above it,
   because a page somebody landed on from a search has to say which of fourteen
   paragraphs is the one that worked. */
.post-accepted {
  border-left: 3px solid var(--status-completed);
  padding-left: 0.85rem;
  margin-left: -1rem;
}

.post-answer {
  display: inline-block;
  margin-bottom: 0.4rem;
  padding: 0.1rem 0.45rem;
  border-radius: var(--radius-sm);
  font-size: 0.75rem;
  font-weight: 600;
}

/* A post of yours that a moderator took down. Amber, not green and not red:
   nothing is broken and nothing succeeded — somebody decided something, and the
   words are still here to read. */
.post-down {
  border-left: 3px solid var(--color-warning);
  padding-left: 0.85rem;
  margin-left: -1rem;
  color: var(--text-quiet);
}

.post-down-said {
  display: inline-block;
  margin-bottom: 0.4rem;
  padding: 0.1rem 0.45rem;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--color-warning) 16%, transparent);
  color: var(--color-warning);
  font-size: 0.75rem;
  font-weight: 600;
}

/* The header is sticky, so an anchor that lands on a post has to leave room for
   it. Every card on /community links to `#p-<id>` and without this the post it
   names arrives underneath the bar. */
.post,
.thread,
h2[id] {
  scroll-margin-top: 4.5rem;
}

/* ---------------------------------------------------------------- the tools -- */

.post-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem 0.9rem;
  margin-top: 0.55rem;
  font-size: 0.8rem;
  color: var(--text-muted);
}

.post-helpful-said {
  color: var(--text-quiet);
}

.post-tools {
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem 0.35rem;
}

/* A button that reads as the quiet word it is. */
/* Like and Report are buttons and have to look like buttons at rest. With no
   border and the quiet ink they read as a caption sitting beside the underlined
   "1 reply" link — and on a phone there is no hover to discover them with. */
.linkbtn {
  padding: 0.15rem 0.5rem;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-secondary);
  font: inherit;
  font-size: 0.8rem;
  cursor: pointer;
}

.linkbtn:hover {
  background: var(--bg-hover);
  color: var(--text-primary);
}

.linkbtn[disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}

.linkbtn.is-on {
  color: var(--status-completed);
  background: color-mix(in srgb, var(--status-completed) 10%, transparent);
}

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

.post-said {
  flex: 1 1 100%;
  margin-top: 0.4rem;
}

/* ------------------------------------------------------------- writing one -- */

.compose,
.report-form {
  margin-top: 0.75rem;
  padding: 0.9rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
  /* The same measure as the conversation it joins. On /community this box sits
     outside `.discussion`, so without its own cap it stretched the full 64rem
     and a person typed one sentence across a metre of screen. */
  max-width: var(--prose);
}

.report-form {
  flex: 1 1 100%;
}

.compose > * + *,
.report-form > * + * {
  margin-top: 0.5rem;
}

.compose .form-actions,
.report-form .form-actions {
  margin-top: 0.9rem;
}

.reply-slot,
.start-slot {
  margin-top: 0.85rem;
}

.community-actions {
  margin-top: 1.5rem;
}

/* ----------------------------------------------------------- the card list -- */

.communitybar {
  margin-top: 1.5rem;
}

.searchrow {
  display: flex;
  gap: 0.5rem;
}

.searchrow .input {
  flex: 1 1 auto;
  min-width: 0;
}

.searchrow .btn {
  flex: none;
}

/* A conversation card is one column of text: there is no logo to put beside it
   and a blank 40px slot would be a placeholder pretending to be a mark. */
/* One column, and a measure a person can read. A conversation card is
   paragraphs; two of them side by side on a wide screen would be two columns of
   prose to scan at once, and 96 characters of excerpt across a laptop is a line
   the eye loses its place on. */
.threadcards {
  grid-template-columns: 1fr;
  max-width: 52rem;
}

.threadcard {
  display: block;
}

.threadcard-where {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.75rem;
}

.page .threadcard h3 {
  margin-top: 0.35rem;
  font-size: 1rem;
}

.threadcard h3 a {
  color: var(--text-primary);
  text-decoration: none;
}

.threadcard h3 a:hover {
  text-decoration: underline;
}

.page .threadcard p {
  margin-top: 0.35rem;
  font-size: 0.9rem;
  color: var(--text-quiet);
}

.danger li + li {
  margin-top: 0.3rem;
}

.btn-danger {
  border-color: var(--color-critical);
  color: var(--color-critical);
}

.btn-danger:hover:not([disabled]) {
  background: var(--color-critical);
  color: var(--bg-primary);
}

.page .threadcard .meta {
  margin-top: 0.5rem;
}

.threadcard-marks {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin-top: 0.5rem;
}

/* A chip that is a link rather than a toggle still has to show which one is on. */
/* The chip you are on. `aria-pressed` chips already carry the weight — see the
   note beside that rule — and these did not, so in dark, where the fill and the
   border move least, the Community's six filters all looked the same. */
.chip[aria-current='page'] {
  background: var(--bg-active);
  border-color: var(--border-strong);
  color: var(--text-primary);
  font-weight: 600;
}

.paging {
  margin-top: 1.75rem;
  font-size: 0.9rem;
}

.thread-topic {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
  margin-top: 0.75rem;
}

/* -------------------------------------------------- what people think of it -- */

/* The likes and the rating, on a card and on a listing page.
 *
 * These are FACTS, drawn for everybody, and they are styled as facts: the same
 * quiet weight as the licence and the date beside them, not a badge and not a
 * colour. A number a store makes loud is a number a store is selling with, and
 * this one is only worth anything while it reads as a report.
 *
 * The stars are the single exception, and they carry the accent rather than a
 * gold, because this site has one accent and inventing a second colour for one
 * glyph is how a palette stops being a palette. */
/* A card is narrow at every width — the grid floors a column at 17.5rem and the
   40px mark eats into that — so its two facts are two lines and it carries no
   separator at all. `short` in W.ratingWords is what keeps the second of them to
   two words. */
.cardscore {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.15rem;
  margin-top: 0.45rem;
  font-size: 0.8rem;
  color: var(--text-muted);
}

.cardscore .score-sep {
  display: none;
}

.scorerow {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
  margin-top: 0.6rem;
  font-size: 0.95rem;
  color: var(--text-secondary);
}

/* The dot between two facts, and the reason it is an element rather than a
   character in the sentence: a wrapped flex row leaves the separator stranded at
   the end of the first line, so "3 likes ·" reads as a typo. Below the width
   where both facts fit on one line the row becomes two lines and the dot goes.
   CSS cannot see a wrap, so this is decided on the viewport, and the breakpoint
   is above the longest of these strings rather than at a device size. */
.score-sep {
  padding: 0 0.35rem;
}

@media (max-width: 44rem) {
  .scorerow {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.15rem;
  }

  .scorerow .score-sep {
    display: none;
  }
}


.score-likes {
  font-weight: 600;
  color: var(--text-primary);
}

.stars {
  color: var(--accent);
  letter-spacing: 0.06em;
}

.stars-lg {
  font-size: 1.05rem;
}

/* The two controls under the score row. Hidden until the script fills them,
   because a like button that cannot be pressed is a picture of a button. */
.reaction {
  margin-top: 0.9rem;
}

.reaction-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

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

.reaction-invite .field-hint {
  margin-bottom: 0.5rem;
}

/* A pressed Like: the accent's own soft tint, so "I have done this" is visible
   at a glance without the button changing size and moving the row. */
.btn-on {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
}

.ratebox {
  margin-top: 0.85rem;
}

.ratebox-label {
  font-size: 0.85rem;
  color: var(--text-quiet);
}

.ratestrip {
  display: flex;
  gap: 0.1rem;
  margin-top: 0.15rem;
}

/* A star is a button first. It gets a real hit area — 2.2rem is above the 44px
   floor on a phone at the base size — because five controls 16px wide in a row
   is a control only a mouse can use. */
.starbtn {
  width: 2.2rem;
  height: 2.2rem;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
}

.starbtn:hover:not([disabled]) {
  background: var(--bg-hover);
  color: var(--accent);
}

.starbtn.is-on {
  color: var(--accent);
}

.starbtn[disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}

/* The count beside a heading — "Questions and comments (4)" without the
   brackets, which read as an aside rather than as a number. */
.count-badge {
  display: inline-block;
  margin-left: 0.4rem;
  padding: 0.1rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--bg-tertiary);
  color: var(--text-secondary);
  font-size: 0.8rem;
  font-weight: 600;
  vertical-align: middle;
}

/* The publisher's own totals, at the top of their shelf. Set at the weight of a
   sentence rather than a scoreboard: it is a report to one person, not a badge
   for everybody else to compare. */
.profile-score {
  margin-top: 0.5rem;
  font-size: 0.95rem;
  color: var(--text-secondary);
}

/* "replying to Sam Helper", on an answer. Quiet: it is orientation, not news. */
.post-replying {
  color: var(--text-muted);
}

/* ------------------------------------------------------- the community feed -- */

/* The heading and the way in, on one line at a laptop's width and stacked on a
   phone. The button used to sit in a block of its own under a details panel,
   which put three things between the title and the first thing anybody had
   written. */
.community-top {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.75rem 1.5rem;
}

.community-say {
  flex: 1 1 22rem;
  min-width: 0;
}

.community-top .start-slot {
  flex: 0 1 auto;
  margin-top: 0.35rem;
}

/* One row: the filters on the left, the search on the right, wrapping to two on
   a phone. Held to the same measure as the feed under it, so the rule at its
   foot ends where the posts end rather than running past them. */
.communitybar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem 1rem;
  margin-top: 1.25rem;
  padding-bottom: 0.9rem;
  border-bottom: 1px solid var(--border-subtle);
  max-width: 52rem;
}

.community-find {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.community-find .input {
  width: 11rem;
  max-width: 100%;
}

/* Moved under the posts. Nothing was deleted — it is the same panel, and it is
   now where somebody who has read a few posts and has a question about the place
   would go looking for it. */
.community-how {
  margin-top: 2rem;
}

/* One column, and a measure a person can read. Posts are paragraphs; two of them
   side by side on a wide screen is two columns of prose to scan at once. */
.feed {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.85rem;
  margin-top: 1.25rem;
  max-width: 52rem;
  /* MEASURED on /community: the cards started at x=390 while the h1, the topic
     chips and the search box all started at 350. This is a <ul>, and a grid
     display does not clear the list padding a browser puts on one. */
  padding: 0;
  list-style: none;
}

.postcard {
  display: block;
}

.postcard-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem 0.55rem;
  font-size: 0.85rem;
  color: var(--text-quiet);
}

.page .postcard h3 {
  margin-top: 0.6rem;
  font-size: 1.05rem;
}

.postcard h3 a {
  color: var(--text-primary);
  text-decoration: none;
}

.postcard h3 a:hover {
  text-decoration: underline;
}

.page .postcard .postcard-body {
  margin-top: 0.4rem;
  font-size: 0.95rem;
  line-height: 1.6;
  color: var(--text-secondary);
}

.postcard-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem 0.85rem;
  margin-top: 0.85rem;
  padding-top: 0.7rem;
  border-top: 1px solid var(--border-subtle);
  font-size: 0.8rem;
  color: var(--text-muted);
}

.postcard-tools {
  display: flex;
  gap: 0.25rem;
}

/* The like button on a card sits at the left of the foot, so the row reads
   "Like · 4 likes · 2 replies" — the control, then what it is a control over. */
.postcard-tools .linkbtn {
  font-size: 0.85rem;
}

.postcard-likes {
  color: var(--text-quiet);
}

.postcard-replies {
  color: var(--text-quiet);
}

a.postcard-replies:hover {
  color: var(--text-primary);
}

.postcard-note {
  color: var(--text-muted);
}

/* ------------------------------------------------------------- the queue -- */

.review-list {
  list-style: none;
  padding: 0;
  margin: 1.5rem 0 0;
  max-width: var(--prose);
}

.review-row + .review-row {
  margin-top: 2rem;
  padding-top: 2rem;
  border-top: 1px solid var(--border);
}

.review-head {
  font-size: 0.9rem;
  color: var(--text-quiet);
}

.review-note {
  margin-top: 0.5rem;
  padding-left: 0.85rem;
  border-left: 3px solid var(--color-warning);
  font-size: 0.9rem;
}

.review-subject {
  margin-top: 0.9rem;
  padding: 0.9rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
}

.review-what {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
}

.review-tools {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.9rem;
}

.review-done {
  opacity: 0.55;
}

.review + p,
.discussion + p {
  margin-top: 2rem;
}

.review-row .said {
  margin-top: 0.75rem;
}

/* On a phone the answers keep their rule but give back their indent. */
@media (max-width: 34rem) {
  .replies {
    padding-left: 0.7rem;
  }
  .post-accepted {
    margin-left: 0;
  }
}

/* --------------------------------------------------------- the help page -- */

/* faq.html borrows the docs page's two columns — a sticky list of topics beside
   a single reading column — and adds three things a help page needs and a
   reference page does not: a search that filters as you type, one address per
   question, and an empty state that sends somebody somewhere real.

   The search control is written into the page carrying `hidden`, and
   assets/faq.js takes it off. With no JavaScript there is no box offering a
   search that cannot happen; the page is a plain list of headings and
   paragraphs, every one of them still its own link. */

.faq-top {
  padding: 2.5rem 0 0;
  max-width: var(--prose);
}

.faq-top h1 {
  font-size: clamp(1.9rem, 1.4rem + 1.8vw, 2.5rem);
  letter-spacing: -0.025em;
  margin-bottom: 0.75rem;
}

.faq-top .lede {
  font-size: 1.125rem;
  color: var(--text-quiet);
}

/* ------------------------------------------------------------ the search -- */

.faq-find {
  margin-top: 1.5rem;
}

/* The magnifier sits inside the field rather than beside it, so the control
   reads as one object at every width. The input is `flex: 1` inside it and
   carries no border of its own — the wrapper owns the border and the focus
   ring, which is what keeps the outline around the whole box instead of around
   the text alone. */
.faq-form {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.55rem 0.75rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--bg-primary);
  color: var(--text-muted);
}

.faq-form:focus-within {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}

.faq-form svg {
  flex: none;
}

.faq-form input {
  flex: 1;
  min-width: 0;
  border: 0;
  padding: 0;
  background: none;
  color: var(--text-primary);
  font: inherit;
  font-size: 0.95rem;
  line-height: 1.5;
}

.faq-form input:focus {
  outline: none;
}

.faq-form input::placeholder {
  color: var(--text-muted);
}

/* Safari draws its own clear button inside a type=search; ours says what it
   does and works the same in every browser. */
.faq-form input::-webkit-search-cancel-button {
  display: none;
}

.faq-clear {
  flex: none;
  border: 0;
  border-radius: var(--radius-sm);
  padding: 0.2rem 0.5rem;
  background: var(--bg-tertiary);
  color: var(--text-quiet);
  font: inherit;
  font-size: 0.8rem;
  cursor: pointer;
}

.faq-clear:hover {
  background: var(--bg-active);
  color: var(--text-primary);
}

.faq-tally-line {
  margin-top: 0.5rem;
  min-height: 1.25rem;
  font-size: 0.85rem;
  color: var(--text-quiet);
}

/* ------------------------------------------------------------ the topics -- */

/* The topic rows only. The "still stuck?" line under the list holds a link too,
   and a flex rule that caught it put the full stop on a line of its own. */
.faq-toc ol a {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.34rem 0.75rem;
}

.faq-toc ol a[aria-current='true'] {
  color: var(--text-primary);
  border-left-color: var(--accent);
}

.faq-tally {
  flex: none;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* A topic with nothing left under it after a search stays in the list, greyed,
   rather than vanishing — a contents list that reflows while somebody types is
   a contents list they lose their place in. */
.faq-toc li[data-faq-topic].is-empty a {
  opacity: 0.4;
}

.faq-toc-foot {
  margin-top: 1rem;
  padding-left: 0.75rem;
  font-size: 0.85rem;
  color: var(--text-quiet);
}

/* The docs contents list makes every link a block, which is right for a topic
   row and wrong for a link inside a sentence — it put the full stop on a line
   of its own under "Ask in the community". */
.faq-toc-foot a {
  display: inline;
  padding: 0;
  margin-left: 0;
  border-left: 0;
  color: var(--link);
}

/* ---------------------------------------------------------- the answers -- */

/* The first topic opens the column, so it does not wear the rule that separates
   it from the one above. :first-of-type rather than :first-child, because the
   empty-state block is the first child of the column and it is a div. */
.faq-main > section:first-of-type {
  padding-top: 0;
  margin-top: 0;
  border-top: 0;
}

/* The search box already put air above the columns. */
.docs.faq {
  padding-top: 1.75rem;
}

.faq-topic-line {
  margin-top: -0.35rem;
  margin-bottom: 1.5rem;
  color: var(--text-quiet);
}

.faq-item {
  padding: 1.15rem 1.25rem;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  background: var(--bg-secondary);
  /* Clears the sticky header when a #link lands on one. The docs page sets the
     same offsets on its sections; here every question is a landing place. */
  scroll-margin-top: 5rem;
}

.faq-item + .faq-item {
  margin-top: 0.75rem;
}

.faq-item h3 {
  font-size: 1rem;
  margin: 0 0 0.5rem;
}

.faq-item p {
  max-width: var(--prose);
}

.faq-item p + p {
  margin-top: 0.7rem;
}

/* The whole heading is the permalink, so the target is a line rather than a
   glyph somebody has to aim at. The # is a hint that appears on hover and on
   keyboard focus; it is never the only way to get the address. */
.faq-permalink {
  color: inherit;
  text-decoration: none;
}

.faq-permalink:hover {
  color: var(--link);
  text-decoration: none;
}

/* Drawn, never written. As a <span> it was real text, and llms-full.txt — which
   flattens this page by stripping tags — read every heading as "What does it
   cost?#". Nothing a machine is handed should carry a mouse's decoration. */
.faq-permalink::after {
  content: '#';
  margin-left: 0.4rem;
  color: var(--text-muted);
  font-weight: 400;
  opacity: 0;
}

.faq-permalink:hover::after,
.faq-permalink:focus-visible::after {
  opacity: 1;
}

/* The one landed on holds a mark for a moment, so a deep link says "this one"
   rather than dropping somebody in the middle of a page of identical cards. */
.faq-item:target,
.faq-item.is-landed {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.faq-note {
  margin-top: 0.7rem;
  padding-left: 0.75rem;
  border-left: 2px solid var(--border-strong);
  font-size: 0.9rem;
  color: var(--text-quiet);
}

/* ------------------------------------------------------- nothing matched -- */

.faq-none {
  padding: 1.25rem 1.35rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--bg-secondary);
}

.faq-none p + p {
  margin-top: 0.6rem;
  color: var(--text-quiet);
}

/* A section whose questions were all filtered out leaves the page rather than
   leaving a heading standing over nothing. */
.faq-main > section[hidden],
.faq-item[hidden] {
  display: none;
}

.faq-more ul {
  list-style: none;
  padding: 0;
  max-width: var(--prose);
}

.faq-more li + li {
  margin-top: 0.5rem;
}

/* On a phone the cards give back their padding rather than their content. */
@media (max-width: 34rem) {
  .faq-item {
    padding: 1rem;
  }
  .faq-top {
    padding-top: 2rem;
  }
}

@media (max-width: 589px) {
  .faq-item {
    scroll-margin-top: 7.25rem;
  }
}

@media (max-width: 439px) {
  .faq-item {
    scroll-margin-top: 9.5rem;
  }
}

/* Somebody who asked not to be animated gets the mark without the fade. */
@media (prefers-reduced-motion: no-preference) {
  .faq-item.is-landed {
    transition: box-shadow 400ms ease, border-color 400ms ease;
  }
}


/* ==========================================================================
   YOUR ACCOUNT — the four pages a publisher runs their own shop from.

   What was here before was one narrow column with every section stacked in it:
   a profile form, a listings list, discussions, notifications, a Sign out, and
   a red box at the bottom for deleting the account. His words, on the
   recording: "very ugly, cheap kind of thing, not a proper platform" — and for
   the same column at /account#listings, "very cheap page".

   Three things are different here and each is a rule rather than a decoration:

     * The account is FOUR pages, not four anchors on one. Profile, Listings,
       Activity and Settings each have an address, so a link can name one and
       the browser's back button means something.
     * The column is `--wrap`, not `--prose`. Prose is 44rem because that is how
       wide a paragraph should be; a table of listings and a row of numbers are
       not paragraphs, and squeezing them into a reading measure is most of why
       the old page felt like a form somebody left lying about.
     * Everything is a panel floating on the tinted ground, which is what the
       rest of this design does and what the old account page was the one place
       not doing.
   ========================================================================== */

.acct {
  max-width: var(--wrap);
  padding: var(--s-10) 0 var(--s-20);
}

.acct-title {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--s-3);
}

.acct h1 {
  font-size: var(--t-2xl);
  line-height: 1.2;
  margin: 0;
}

.acct > .lede {
  margin: var(--s-2) 0 0;
  color: var(--ink-3);
  max-width: var(--prose);
}

/* --------------------------------------------------- who you are, up top -- */

/* The one strip that is the same on all four pages: your picture, your name,
   your address in the store, and the way to look at what a stranger sees. It is
   the answer to "whose account is this" that the old page made you infer from a
   form field. */
.acct-who {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  flex-wrap: wrap;
  margin-top: var(--s-6);
  padding: var(--s-4) var(--s-5);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  background: var(--surface);
  box-shadow: var(--shadow-1);
}

.acct-who .avatar {
  flex: none;
}

.acct-who-text {
  min-width: 0;
  flex: 1 1 12rem;
}

.acct-who-name {
  margin: 0;
  font-size: var(--t-lg);
  font-weight: 600;
  line-height: 1.3;
}

.acct-who-handle {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--t-md);
  color: var(--ink-4);
  overflow-wrap: anywhere;
}

.acct-who .btn {
  flex: none;
}

/* ------------------------------------------------------------ the four tabs -- */

.acct-nav {
  margin-top: var(--s-6);
  border-bottom: 1px solid var(--border);
  /* Four words plus a phone is four words that do not fit. They scroll rather
     than wrap into two rows that push the page around. */
  overflow-x: auto;
  scrollbar-width: none;
}

.acct-nav::-webkit-scrollbar {
  display: none;
}

.acct-nav ul {
  display: flex;
  gap: var(--s-1);
  margin: 0;
  padding: 0;
  list-style: none;
  white-space: nowrap;
}

.acct-nav a {
  display: block;
  padding: var(--s-3) var(--s-4);
  color: var(--ink-3);
  text-decoration: none;
  font-size: var(--t-md);
  font-weight: 500;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}

.acct-nav a:hover {
  color: var(--ink);
  background: var(--bg-hover);
  border-radius: var(--r-sm) var(--r-sm) 0 0;
}

.acct-nav a[aria-current='page'] {
  color: var(--ink);
  border-bottom-color: var(--accent);
}

/* ---------------------------------------------------------------- panels -- */

/* One object per subject. `.section`'s rule-between-siblings is right for a
   form that reads top to bottom; a page of separate things wants separate
   things. */
.panel {
  margin-top: var(--s-6);
  padding: var(--s-5) var(--s-6);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  background: var(--surface);
  box-shadow: var(--shadow-1);
}

.panel > h2 {
  font-size: var(--t-lg);
  margin: 0;
}

.panel > h2 + .panel-why {
  margin-top: var(--s-1);
}

.panel-why {
  margin: 0;
  font-size: var(--t-md);
  color: var(--ink-4);
  max-width: var(--prose);
}

.panel > * + *:not(.panel-why) {
  margin-top: var(--s-4);
}

.panel-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-4);
  flex-wrap: wrap;
}

.panel-head > div {
  min-width: 0;
}

.panel .field {
  max-width: var(--prose);
}

.panel-empty {
  margin: 0;
  font-size: var(--t-md);
  color: var(--ink-4);
}

/* ------------------------------------------------------- connected accounts -- */

.conns {
  list-style: none;
  margin: 0;
  padding: 0;
}

.conns > li + li {
  margin-top: var(--s-2);
}

.conn {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-lg);
  background: var(--surface-2);
  flex-wrap: wrap;
}

.conn-mark {
  display: flex;
  flex: none;
  color: var(--ink-2);
}

.conn-text {
  flex: 1 1 10rem;
  min-width: 0;
}

.conn-name {
  display: block;
  font-weight: 500;
  font-size: var(--t-base);
}

.conn-as {
  display: block;
  font-size: var(--t-md);
  color: var(--ink-4);
  overflow-wrap: anywhere;
}

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

/* A word on a tint, never a colour on its own — the same rule the post badges
   were written to. "Connected" is legible with the fill removed. */
.conn-state {
  flex: none;
  padding: 0.1rem 0.5rem;
  border-radius: var(--r-pill);
  font-size: var(--t-xs);
  font-weight: 500;
  background: var(--bg-tertiary);
  color: var(--ink-3);
}

.conn-state.is-on {
  background: var(--accent-soft);
  color: var(--accent-dim);
}

.conn .btn {
  flex: none;
}

/* ----------------------------------------------------- your own repositories -- */

/* Nothing here publishes anything. Each row is a way INTO the publish page with
   the address already filled in, which is the only step of publishing that is
   pure typing. Every check the store makes still happens there, and the second
   press is still the person's. */
.repos {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 27rem;
  overflow-y: auto;
}

.repos > li + li {
  margin-top: var(--s-2);
}

.repo {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-lg);
  background: var(--surface-2);
  flex-wrap: wrap;
}

.repo-text {
  flex: 1 1 14rem;
  min-width: 0;
}

.repo-name {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--t-md);
  font-weight: 500;
  overflow-wrap: anywhere;
}

.repo-sum {
  display: block;
  font-size: var(--t-md);
  color: var(--ink-4);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.repo-meta {
  display: flex;
  gap: var(--s-3);
  flex-wrap: wrap;
  font-size: var(--t-xs);
  color: var(--ink-4);
  margin-top: var(--s-1);
}

.repo .btn {
  flex: none;
}

.repo-search {
  max-width: 22rem;
}

/* ------------------------------------------------------------ your listings -- */

/* A real table, which is what a list of things with a state and a number is.
   It scrolls inside its own box rather than making the page scroll sideways. */
.ltable-wrap {
  overflow-x: auto;
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-lg);
}

.ltable {
  width: 100%;
  min-width: 40rem;
  border-collapse: collapse;
  font-size: var(--t-md);
}

.ltable th,
.ltable td {
  text-align: left;
  padding: var(--s-3) var(--s-4);
  vertical-align: top;
}

.ltable thead th {
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-4);
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}

.ltable tbody tr + tr td {
  border-top: 1px solid var(--border-subtle);
}

.ltable tbody tr:hover td {
  background: var(--bg-hover);
}

.ltable-name {
  font-weight: 500;
}

.ltable-name a {
  color: inherit;
  text-decoration: none;
}

.ltable-name a:hover {
  text-decoration: underline;
}

.ltable-kind {
  display: block;
  font-size: var(--t-xs);
  color: var(--ink-4);
}

.ltable-num {
  font-family: var(--font-mono);
  white-space: nowrap;
}

/* What to do about this row, in one short sentence, in the last column. A queue
   position or a moderator's note is the only reason a publisher opens this
   page, so it is a column and not a thing to hover for. */
.ltable-next {
  color: var(--ink-3);
  min-width: 12rem;
}

.ltable-next .ltable-line {
  margin: 0;
}

.ltable-next .btn,
.ltable-next .said {
  margin-top: var(--s-2);
}

.ltable-next .said {
  font-size: var(--t-md);
}

/* The state, as a word. Same rule as .conn-state: the fill is a help, never the
   message. */
.state {
  display: inline-block;
  padding: 0.1rem 0.5rem;
  border-radius: var(--r-pill);
  font-size: var(--t-xs);
  font-weight: 500;
  white-space: nowrap;
  background: var(--bg-tertiary);
  color: var(--ink-3);
}

.state-listed {
  background: rgba(68, 131, 97, 0.14);
  color: var(--status-completed);
}

.state-waiting {
  background: rgba(203, 145, 47, 0.16);
  color: var(--status-waiting);
}

.state-stopped {
  background: rgba(212, 76, 71, 0.14);
  color: var(--color-critical);
}

/* A table is the right shape for six columns on a screen and the wrong one on a
   phone, where the column a publisher opens this page FOR — what to do next
   about a listing — is the one that scrolls off the side. Below this width each
   row becomes a card and every cell prints its own column name, which the script
   puts on it as data-label. */
@media (max-width: 699px) {
  .ltable-wrap {
    overflow-x: visible;
    border: 0;
    border-radius: 0;
  }

  .ltable {
    display: block;
    min-width: 0;
  }

  .ltable thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .ltable tbody,
  .ltable tr,
  .ltable td {
    display: block;
    width: auto;
  }

  .ltable tbody tr {
    padding: var(--s-3) var(--s-4);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background: var(--surface-2);
  }

  .ltable tbody tr + tr {
    margin-top: var(--s-3);
  }

  .ltable tbody tr + tr td,
  .ltable tbody tr:hover td {
    border-top: 0;
    background: none;
  }

  .ltable td {
    padding: var(--s-1) 0;
  }

  .ltable td[data-label]:not(.ltable-name):not(.ltable-next)::before {
    content: attr(data-label) ' ';
    margin-right: var(--s-2);
    font-family: var(--font-ui);
    font-size: var(--t-xs);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--ink-4);
  }

  .ltable-next {
    padding-top: var(--s-3);
  }
}

/* ------------------------------------------------------------- the numbers -- */

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: var(--s-3);
  list-style: none;
  margin: 0;
  padding: 0;
}

.stat {
  padding: var(--s-4);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-lg);
  background: var(--surface-2);
}

.stat-n {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--t-2xl);
  line-height: 1.1;
}

.stat-l {
  display: block;
  font-size: var(--t-md);
  color: var(--ink-4);
  margin-top: var(--s-1);
}

/* ------------------------------------------------------------ the settings -- */

.setting {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-5);
  flex-wrap: wrap;
  padding: var(--s-4) 0;
}

.setting + .setting {
  border-top: 1px solid var(--border-subtle);
}

.setting-text {
  flex: 1 1 18rem;
  min-width: 0;
}

.setting-name {
  display: block;
  font-weight: 500;
}

.setting-why {
  display: block;
  font-size: var(--t-md);
  color: var(--ink-4);
  max-width: var(--prose);
}

.setting-do {
  flex: none;
}

/* Three buttons that are one choice. A radio group would be right if it looked
   like one; this is the same control the header already uses, spelled out. */
.choice {
  display: inline-flex;
  padding: 2px;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface-2);
}

.choice button {
  appearance: none;
  border: 0;
  background: none;
  font: inherit;
  font-size: var(--t-md);
  color: var(--ink-3);
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-md);
  cursor: pointer;
}

.choice button:hover {
  color: var(--ink);
}

/* The chosen segment, in BOTH themes.
   Dark sets --shadow-1 to none — the palette carries no drop shadows there —
   and --surface sits below --surface-2, so the pressed segment lost its lift
   AND went darker than the track it sits in: on the Settings page in dark,
   nothing at all read as selected. An outline survives a palette with no
   shadows, and the weight carries it on a monochrome screen. */
.choice button[aria-pressed='true'] {
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow-1);
  outline: 1px solid var(--border-strong);
  outline-offset: -1px;
  font-weight: 600;
}

/* ------------------------------------------------ closing it, out of the way -- */

/* His words: "Why do we have this big widget just to delete our account? If we
   click on delete, then it can come up."

   So the page's body is things a person does, and closing the account is one
   quiet line at the bottom of Settings that opens the same consequences and the
   same type-your-own-handle gate that were always there. Nothing about the
   deletion itself is easier to reach than it was: it is one press FURTHER away,
   the five sentences still have to be read past, close_account() and its guards
   are untouched, and the button is still disabled until the handle is typed. */
.closing {
  margin-top: var(--s-6);
  max-width: var(--prose);
}

.closing > summary {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  cursor: pointer;
  list-style: none;
  font-size: var(--t-md);
  color: var(--ink-4);
  padding: var(--s-2) 0;
}

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

.closing > summary:hover {
  color: var(--color-critical);
}

.closing > summary::before {
  content: '›';
  display: inline-block;
  font-size: var(--t-lg);
  line-height: 1;
  transition: transform 120ms ease;
}

.closing[open] > summary::before {
  transform: rotate(90deg);
}

@media (prefers-reduced-motion: reduce) {
  .closing > summary::before {
    transition: none;
  }
}

/* ------------------------------------------------------------- a publisher -- */

/* The public page — what a stranger sees, and the reason any of this is worth
   filling in. It reads as a shop window: who this is, then what they publish,
   then what people thought of it. */
.pro {
  display: flex;
  gap: var(--s-6);
  align-items: flex-start;
  flex-wrap: wrap;
  padding: var(--s-6);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  background: var(--surface);
  box-shadow: var(--shadow-1);
}

.pro-face {
  flex: none;
}

.pro-face .avatar {
  width: 88px;
  height: 88px;
}

.pro-face .avatar-mono {
  font-size: 2.4rem;
}

.pro-text {
  flex: 1 1 20rem;
  min-width: 0;
}

.page .pro-text h1 {
  margin: 0;
  font-size: var(--t-2xl);
  line-height: 1.15;
}

.page .pro-handle {
  margin: var(--s-1) 0 0;
  font-family: var(--font-mono);
  font-size: var(--t-md);
  color: var(--ink-4);
}

.page .pro-bio {
  margin: var(--s-4) 0 0;
  max-width: var(--prose);
}

.pro-facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-4);
  margin: var(--s-4) 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--t-md);
  color: var(--ink-4);
}

.pro-facts li {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
}

.pro-facts a {
  color: var(--link);
}

.pro-note {
  margin: var(--s-4) 0 0;
  padding: var(--s-3) var(--s-4);
  border-left: 3px solid var(--border-strong);
  border-radius: 0 var(--r-md) var(--r-md) 0;
  background: var(--surface-2);
  font-size: var(--t-md);
  color: var(--ink-3);
  max-width: var(--prose);
}

/* Your own page, when it is yours. Hidden in the markup and unhidden by
   assets/site-account.js for the one person it belongs to — see the comment
   beside it in api/store/_routes/profile.js. */
.pro-yours {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3) var(--s-4);
  margin: var(--s-5) 0 0;
  padding: var(--s-4);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-lg);
  background: var(--surface-2);
}

.pro-yours-line {
  margin: 0;
  font-size: var(--t-md);
  color: var(--ink-3);
}

.pro-yours-do {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}

/* The numbers, beside the person rather than under the fold. Three facts a
   stranger actually wants: how much they publish, how many people took it, and
   what those people said. */
.pro-nums {
  display: flex;
  gap: var(--s-6);
  flex-wrap: wrap;
  margin: 0;
  padding: 0;
}

.pro-nums > div {
  min-width: 4.5rem;
}

.pro-nums dt {
  font-size: var(--t-xs);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-4);
}

.pro-nums dd {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--t-xl);
  line-height: 1.2;
}

@media (max-width: 599px) {
  .pro {
    padding: var(--s-5);
    gap: var(--s-4);
  }

  .pro-face .avatar {
    width: 64px;
    height: 64px;
  }

  .pro-face .avatar-mono {
    font-size: 1.8rem;
  }

  .acct {
    padding-top: var(--s-6);
  }

  .panel {
    padding: var(--s-4);
  }
}

[hidden] {
  display: none !important;
}

/* ------------------------------------------------------------- diagrams -- */

/*
  A drawing, on a page that would otherwise be prose.

  Seven pages on this site described the app in words and showed nothing —
  right after Asad held up claude.com and said "look how they have done this,
  it is very beautiful". Some of what those pages say is a picture and was
  being read as a paragraph: which colour a session's dot goes, the three steps
  of pairing a phone, what actually sits on your machine.

  Inline SVG rather than an image file: it takes the page's own colour tokens,
  so it is correct in light and dark with no second asset to keep in step, it
  costs no request, and it stays sharp at any size. Every one of them is
  labelled for a screen reader and the text inside is real text.
*/
.figure {
  margin: var(--s-6) 0;
}

.figure svg {
  display: block;
  width: 100%;
  height: auto;
  max-width: 44rem;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  background: var(--surface);
}

.figure figcaption {
  max-width: 44rem;
  margin-top: var(--s-3);
  font-size: var(--t-md);
  color: var(--text-quiet);
}

/*
 * A DIAGRAM ON A PHONE.
 *
 * MEASURED: the drawings are drawn in a 700-unit viewBox with 13px and 11.5px
 * type. On a 390px phone the figure gets about 350px, a scale of 0.5, and the
 * labels render at 5.6px — smaller than anything else on the site by half, on
 * the pages that exist BECAUSE some of what the app does is easier to see than
 * to read.
 *
 * Scaling the type up inside the drawing would push it out of the boxes it
 * labels. So the drawing keeps its size and the figure scrolls inside its own
 * box — rule 5.3, wide content scrolls in its container and never the page. The
 * caption is stuck to the left edge so it stays put while the drawing moves.
 */
@media (max-width: 40rem) {
  .figure {
    overflow-x: auto;
    overscroll-behavior-x: contain;
  }

  .figure svg {
    min-width: 34rem;
  }

  .figure figcaption {
    position: sticky;
    left: 0;
    max-width: calc(100vw - 3rem);
  }
}

/* The parts every diagram is built from. Named for what they are, so a new
   drawing does not invent a fourth grey. */
.dg-panel { fill: var(--bg-secondary); stroke: var(--border); }
.dg-card { fill: var(--surface); stroke: var(--border); }
.dg-line { stroke: var(--border-strong); fill: none; }
.dg-flow { stroke: var(--accent); fill: none; }
.dg-t { fill: var(--text-primary); font-family: var(--font-ui); font-size: 13px; }
.dg-t-sm { fill: var(--text-quiet); font-family: var(--font-ui); font-size: 11.5px; }
.dg-t-mono { fill: var(--text-primary); font-family: var(--font-mono); font-size: 12px; }
.dg-t-num { fill: var(--accent-fg); font-family: var(--font-ui); font-size: 12px; font-weight: 600; }
.dg-eyebrow {
  fill: var(--text-muted);
  font-family: var(--font-ui);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.dg-mark { fill: var(--accent); }
.dg-working { fill: var(--status-working); }
.dg-waiting { fill: var(--status-waiting); }
.dg-input { fill: var(--status-input); }
.dg-idle { fill: var(--status-idle); }
.dg-done { fill: var(--status-completed); }
