/* ─────────────────────────────────────────────────────────────
   PhotoWrite — shared stylesheet for photowrite.co.

   The design language is ReadWrite's (readwrite/style.css): the same
   system font stack, the same 12px radius, the same soft two-layer
   shadow, the same card/badge/button idioms. What differs is the
   PALETTE. These are siblings, not clones — ReadWrite is a cool blue
   on cool greys; PhotoWrite is a warm amber on warm greys, because
   this one is about photographs and the light in them.

   No webfonts, no JavaScript, no external request of any kind.
   ───────────────────────────────────────────────────────────── */

:root {
  --bg: #ffffff;
  --surface: #faf8f4;
  --border: #e4ddd2;
  --text: #211d17;
  --text-muted: #6f6557;
  --accent: #b45309;
  --accent-hover: #92400e;
  --accent-light: #fdf3e3;
  --on-accent: #ffffff;
  --success: #4d7c2f;
  --success-bg: #edf5e3;
  --success-fg: #3f6626;
  /* A field left blank is the tool working, not an error — so this is a
     quiet neutral, never a warning colour. */
  --pending-bg: #f0ece5;
  --pending-fg: #6f6557;
  --radius: 12px;
  --shadow: 0 1px 3px rgba(33,29,23,0.07), 0 4px 12px rgba(33,29,23,0.06);
  --shadow-lg: 0 8px 40px rgba(33,29,23,0.10), 0 2px 8px rgba(33,29,23,0.06);
  --mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #14110d;
    --surface: #1f1b15;
    --border: #39322a;
    --text: #f4f0e9;
    --text-muted: #a99e8e;
    --accent: #f0a629;
    --accent-hover: #fbc465;
    --accent-light: #38270f;
    --on-accent: #14110d;
    --success: #8fbf6a;
    --success-bg: #22301a;
    --success-fg: #9ccb77;
    --pending-bg: #2a251e;
    --pending-fg: #a99e8e;
    --shadow: 0 1px 3px rgba(0,0,0,0.4), 0 4px 12px rgba(0,0,0,0.3);
    --shadow-lg: 0 8px 40px rgba(0,0,0,0.5), 0 2px 8px rgba(0,0,0,0.35);
  }
}
:root[data-theme="dark"] {
  --bg: #14110d;
  --surface: #1f1b15;
  --border: #39322a;
  --text: #f4f0e9;
  --text-muted: #a99e8e;
  --accent: #f0a629;
  --accent-hover: #fbc465;
  --accent-light: #38270f;
  --on-accent: #14110d;
  --success: #8fbf6a;
  --success-bg: #22301a;
  --success-fg: #9ccb77;
  --pending-bg: #2a251e;
  --pending-fg: #a99e8e;
  --shadow: 0 1px 3px rgba(0,0,0,0.4), 0 4px 12px rgba(0,0,0,0.3);
  --shadow-lg: 0 8px 40px rgba(0,0,0,0.5), 0 2px 8px rgba(0,0,0,0.35);
}

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

/* `hidden` MEANS HIDDEN, and it took an author rule beating it to put this
   here. `[hidden]` is a UA rule, so ANY author selector that sets `display`
   on the same element wins -- and `.nav-links .menu-panel a { display: block }`
   did exactly that, so the phone menu's signed-in-only "My account" link was
   visible to signed-OUT visitors with the attribute correctly on it. Found by
   driving the real script in Chromium, not by reading either file: the markup
   was right, the script was right, and the computed style said `block`.

   Site-wide and `!important` for the reason `/app`'s `.hide` carries one --
   a rule whose entire job is to override a component's display is what the
   keyword is for, and ordering the file so this comes last would work today
   and fail for whoever adds the next display rule below it. This is the
   `.hide`/`.runbar` fault one stylesheet along, and it now cannot recur for
   any element on the site rather than being patched at the one that showed
   it. */
[hidden] { display: none !important; }

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  background: var(--bg);
  color: var(--text);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); }
a:hover { color: var(--accent-hover); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }

.icon {
  width: 16px; height: 16px; stroke: currentColor; fill: none;
  stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; flex-shrink: 0;
}

/* ── Nav ── */
.nav {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 16px 32px; border-bottom: 1px solid var(--border);
  position: sticky; top: 0; z-index: 100; background: var(--bg);
}
.logo {
  font-size: 1.5rem; font-weight: 700; color: var(--accent);
  text-decoration: none; letter-spacing: -0.5px; white-space: nowrap;
}
.logo span { color: var(--text); }
.nav-links { display: flex; align-items: center; gap: 22px; }
.nav-links a { font-size: 0.9rem; color: var(--text-muted); text-decoration: none; }
.nav-links a:hover { color: var(--accent); }
.nav-links a[aria-current="page"] { color: var(--text); font-weight: 600; }
.nav-links a.nav-cta {
  padding: 9px 20px; background: var(--accent); color: var(--on-accent); font-weight: 600;
  border-radius: var(--radius); transition: background 0.15s;
}
.nav-links a.nav-cta:hover { background: var(--accent-hover); color: var(--on-accent); }

/* ── The phone menu ──
   NO JAVASCRIPT, and `<details>`/`<summary>` rather than the checkbox hack
   because the disclosure semantics come free: the browser gives `summary` a
   button role, keyboard operation and `aria-expanded` without an author
   writing any of it, and Escape closes an open one. A checkbox styled to look
   like a button gives none of that and has to be told what it is.

   WHY THERE IS A MENU AT ALL, measured at 390px before it was built: the nav
   is sticky, so the logo is always one tap from home and no page is a dead
   end -- but on /story and /writes, the two pages the nav itself points at,
   the first in-page route to anywhere is 12.5 and 12.8 SCREENS down, and
   /privacy carries none at all, footer only. So the escape hatch was a
   WORDMARK, with nothing on a phone saying it was one. Showing the links
   instead is not an option and was checked rather than assumed: all four plus
   the CTA need ~459px of bar against 390 available, and only stop overflowing
   at 430.

   THE PANEL IS A SECOND COPY OF THE SAME THREE LINKS, deliberately, and it is
   guarded rather than derived. The no-duplication version puts one list
   inside `<details>` and force-shows it at desktop width by overriding what
   the UA stylesheet does to a closed `details`' own children -- which works in
   Chromium and is exactly the kind of cross-engine behaviour this sandbox
   cannot verify and this repo has a rule about. Two lists that cannot drift
   beat one list that might render two ways on an engine nobody here can open.
   `test_the_phone_menu_offers_the_same_links_as_the_nav` compares them in both
   directions.

   IT OVERLAYS RATHER THAN PUSHING. The nav is `position: sticky`, so a panel
   in flow would grow the bar and shove the page down under it every time
   somebody opened it. */
.menu { display: none; position: relative; }
.menu > summary {
  list-style: none; cursor: pointer; display: flex; align-items: center;
  padding: 6px; margin: -6px; border-radius: 8px; color: var(--text-muted);
}
/* Safari draws its own disclosure triangle through a pseudo-element that
   `list-style: none` alone does not reach. */
.menu > summary::-webkit-details-marker { display: none; }
.menu > summary:hover { color: var(--accent); }
.menu > summary svg { width: 22px; height: 22px; stroke: currentColor; stroke-width: 2; }
.menu[open] > summary { color: var(--accent); }
.menu-panel {
  position: absolute; right: 0; top: calc(100% + 12px); z-index: 101;
  display: flex; flex-direction: column; gap: 2px; min-width: 12rem;
  padding: 8px; background: var(--bg);
  border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
}
/* A ROW RATHER THAN A LINE OF TEXT: the tap target is the whole width of the
   panel, not the few characters of the label. */
.nav-links .menu-panel a { display: block; padding: 10px 12px; border-radius: 8px; font-size: 0.95rem; }
.nav-links .menu-panel a:hover { background: var(--surface); }

@media (max-width: 780px) {
  .nav { padding: 14px 18px; }
  .nav-links { gap: 14px; }
  .nav-links a.opt { display: none; }
  .menu { display: block; }
}

/* ── Shell ── */
.wrap { max-width: 960px; margin: 0 auto; padding: 0 24px; }
.wrap-narrow { max-width: 680px; margin: 0 auto; padding: 0 24px; }
/* SECTIONS ARE SEPARATED BY SPACE ALONE, and there used to be a rule as well.
   `section + section { border-top }` drew a full-bleed line at every boundary,
   and the exception under it — `.page-head + section` — was the whole argument
   in miniature: a section opening with `padding-top:0` says "this continues
   what is above", while a border says "this is a new section", and both cannot
   be true. That contradiction was fixed once, for the page head, and left
   standing everywhere else — so on /writes the line landed with 72px of white
   above it and the cards hard underneath, a rule belonging to neither side of
   its own boundary.

   MEASURED at 1280 before it went: ink-to-ink gaps of 73, 112, 145 and 201px
   down one page, with a 1px line sitting in the 73. Space at that scale
   already separates; the line was a second answer to a question the padding
   had answered, and a worse one.

   The padding comes down with it (144px between sections at desktop, now 96)
   because the two were sized against each other: generous air was what kept a
   full-width rule from reading as a cut, and with no rule the same air reads
   as a gap nobody meant.

   IT CAME DOWN TWICE. 72px a side read as a gap nobody meant; 48 still did,
   on the same boundary, reported the same way. 36 is where it stopped, with
   `.page-head`'s own bottom following it from 40 to 28 — that pair is the
   head-to-first-section join, and leaving it behind would have made the one
   boundary every page opens with the loosest on the site. Ink to ink on
   /writes across the three passes: 112/73/145/201, then 88/48/96/152, now
   64/36/72/128. The clamp's LOW end moves least, so a phone is close to
   unchanged. */
section { padding: clamp(24px, 3.4vw, 36px) 0; }

/* The setup steps on /install. An ordered list rather than the code block it
   was: that block styled INSTRUCTIONS as shell comments and then broke its own
   pattern for the two lines that are genuinely values to copy, so the one part
   a reader has to type looked like commentary. The values keep a `pre`; the
   prose does not. */
ol.steps { padding-left: 1.5em; }
ol.steps > li { margin-top: 1.1em; padding-left: 0.3em; }
ol.steps > li::marker { color: var(--accent); font-weight: 700; }
ol.steps pre { margin-top: 0.7em; }
ol.steps code { font-size: 0.92em; }

/* THE BUTTON IS NOT PART OF STEP 4. `ol.steps` carries no margin-bottom and
   `.actions` carries no margin-top of its own, so on /install the closing
   sign-in button sat flush against the last step's own text -- it read as
   part of "Run it" rather than as the page's own call to action. Every other
   `.actions` block on the site gets its spacing for free, from a surrounding
   `.section-sub` or `.card`; this is the one place a numbered list hands off
   to a button with nothing between them, so it is the one place that needs
   naming rather than a blanket rule on `.actions` itself. Centred to match
   the closing CTA idiom used elsewhere (index.html's hero and final
   section), which is also what marks it as a page-level action rather than
   a continuation of the left-aligned list above it. */
ol.steps + .actions { margin-top: 34px; }
.center { text-align: center; }

h2 {
  font-size: clamp(1.6rem, 3.4vw, 2.15rem); font-weight: 800;
  line-height: 1.2; letter-spacing: -0.6px;
  /* A HEADING BELONGS TO WHAT FOLLOWS IT, so the space above must be the
     larger of the two. This carried a margin-BOTTOM alone, and the reset at
     the top of this file zeroes everything else — so on any page with real
     prose in it (the legal pages, most obviously) a heading sat flush against
     the paragraph it had nothing to do with and 14px from the text it
     introduced. It read as a caption on the section above rather than a title
     for the section below.

     `em` rather than `px` so the gap tracks the heading's own clamped size:
     the same rule has to hold at 1.6rem on a phone and 2.15rem on a desktop. */
  margin-top: 2.1em; margin-bottom: 0.42em;
}
h3 { margin-top: 1.7em; margin-bottom: 0.4em; }
/* ...except where a component already owns its spacing — a section's padding,
   a card's, a callout's. Anything that OPENS its container needs no gap above
   it, and adding one pushes the whole block down instead. */
h2:first-child, h3:first-child,
.card h2, .card h3, .callout h2, .callout h3 { margin-top: 0; }

/* LONG-FORM PROSE: bare markup inside a narrow column. The marketing pages
   compose from designed components (.prose, .reqs, .rules, cards) which carry
   their own spacing; the legal pages are ordinary headings, paragraphs, lists
   and a table, and the reset left every one of them touching its neighbour.
   Scoped to DIRECT children so no component's internals are reached. */
.wrap-narrow > p,
.wrap-narrow > ul,
.wrap-narrow > ol,
.wrap-narrow > table { margin-top: 0.9em; }
.wrap-narrow > ul,
.wrap-narrow > ol { padding-left: 1.4em; }
.wrap-narrow > ul > li + li,
.wrap-narrow > ol > li + li { margin-top: 0.5em; }
.wrap-narrow > table { margin-bottom: 0.4em; }
/* Whatever opens the column sets its own top edge — but ONLY the bare prose
   this block gave margins to. `> :first-child` was too wide: it also stripped
   the margin a COMPONENT owns, so a `.callout` opening a section lost its 22px
   and sat flush against the rule above it. A reset must undo its own rule and
   nothing else. */
.wrap-narrow > h2:first-child,
.wrap-narrow > h3:first-child,
.wrap-narrow > p:first-child,
.wrap-narrow > ul:first-child,
.wrap-narrow > ol:first-child,
.wrap-narrow > table:first-child { margin-top: 0; }

/* THE PAGE HEAD SHARES ITS BODY'S LEFT EDGE. It sat in `.wrap-narrow` (680px)
   above sections in `.wrap` (960px), and because both centre, their left edges
   were ~140px apart: the heading column floated inward from the content it
   introduced, which is the "not quite right" that is hard to name and
   impossible to unsee. Same container now, with the PROSE capped instead — a
   960px measure is too long to read, but the fix for that is the text's width,
   not the column's.

   The narrow pages (account, privacy, terms) keep `.wrap-narrow` throughout and
   already agree with themselves, so nothing here reaches them. */
.page-head p { max-width: 42rem; }
h2 em, h1 em { font-style: normal; color: var(--accent); }
.section-sub { font-size: 1.05rem; color: var(--text-muted); margin-bottom: 30px; }
.center .section-sub { max-width: 46rem; margin-left: auto; margin-right: auto; }
/* ...ONLY WHERE IT REALLY ENDS THE COLUMN. This was a bare
   `.section-sub:last-child`, written to stop a standfirst that closes a section
   adding 30px on top of the section's own padding. But two pages wrap their
   heading and standfirst in a nested `.center` div and put the content --- a
   table, a callout, a grid --- BESIDE it rather than inside it. The standfirst
   is then the last child of that wrapper with a whole table underneath, so the
   gap it needed was the gap this rule took away, and the table sat jammed under
   the prose. Naming the column itself is what separates "nothing follows" from
   "nothing follows INSIDE THIS DIV". */
.wrap > .section-sub:last-child,
.wrap-narrow > .section-sub:last-child { margin-bottom: 0; }

/* CENTRED PROSE IS FOR ONE OR TWO LINES. Every line of centred text starts at a
   different x, so the eye has to hunt for each one -- fine across a hero's two
   lines, tiring across a standfirst's four, and visibly odd directly above a
   table whose every row starts at the same place. The BLOCK stays centred on
   the page and keeps its 46rem measure; only the lines inside it line up.
   Justification is the other candidate and is worse: with no reliable
   hyphenation it opens rivers of white space down a narrow column. */
.center .section-sub { text-align: left; }
.card-label {
  font-size: 0.8rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--accent); margin-bottom: 10px;
}

/* ── Page header (inner pages) ── */
.page-head { padding: clamp(44px, 7vw, 72px) 0 clamp(20px, 3vw, 28px); }
.page-head h1 {
  /* 2.4rem rather than 2.8 at the cap, because 680px of measure at 2.8 gave a
     page head only ~22 characters a line and /story's headline ran to THREE.
     Measured across all nine heads at 1280: at 2.8 the longest is 3 lines, at
     2.5 still 3, at 2.4 it is 2 with room to spare. Only the CAP moves — below
     ~873px the 4.4vw term governs and nothing on a phone changes. Note the
     headline that forced it is a live `site_text` override rather than the
     wording in `story.html`, so a future edit can spill to three lines again;
     this buys headroom, not a guarantee. */
  font-size: clamp(1.9rem, 4.4vw, 2.4rem); font-weight: 800;
  line-height: 1.15; letter-spacing: -0.8px; margin-bottom: 16px;
}
.page-head p { font-size: 1.1rem; color: var(--text-muted); max-width: 40rem; }

/* ── Hero (home) ── */
.hero { max-width: 880px; margin: 0 auto; padding: clamp(52px, 8vw, 84px) 24px clamp(36px, 5vw, 52px); text-align: center; }
.hero-badge {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--accent-light); color: var(--accent);
  font-size: 0.8rem; font-weight: 600; padding: 5px 12px;
  border-radius: 20px; margin-bottom: 24px;
}
.hero-badge svg { width: 13px; height: 13px; }
/* The availability note under the hero's buttons. `.field-note` carries
   `margin-top: 2px`, which is right beneath a form field and far too tight
   beneath a row of buttons -- it read as attached to the button rather than
   as a line of its own. Scoped to the hero so no other field-note moves. */
.hero .field-note { margin-top: 16px; }
.hero h1 {
  font-size: clamp(2rem, 5vw, 3.15rem); font-weight: 800;
  line-height: 1.15; letter-spacing: -1px; margin-bottom: 20px; text-wrap: balance;
}
/* The promise, one tier below the statement. It lives outside the h1 because
   it is a whole second sentence: inside one, the balancer either strands
   "to tell it." on a line of its own or, once constrained, breaks the short
   first sentence up to avoid it. Two elements size independently.

   No max-width of its own: a 30rem cap was narrower than the sentence needs,
   so it broke into two centred lines on an ordinary desktop width for no
   reason but the cap itself -- the sentence fits `.hero`'s own 880px column
   with room spare. `text-wrap: balance` still evens out a break forced by an
   actually narrow (mobile) viewport; it does not itself force one. */
.hero-line {
  font-size: clamp(1.2rem, 2.7vw, 1.65rem); font-weight: 600;
  line-height: 1.35; letter-spacing: -0.3px; color: var(--text);
  margin: 0 auto 18px; text-wrap: balance;
}
.hero-line em { font-style: normal; color: var(--accent); }

/* The BLOCK stays centred on the page (margin: auto within .hero's centred
   column); the TEXT inside it does not. Now four-plus lines of real prose,
   .hero-sub is the same shape as `.center .section-sub` below it on other
   pages, and got the same fix for the same reason: centred body text is
   ragged on BOTH edges, so the eye has to hunt for the start of every line.
   Left-aligned reads as normal prose while the paragraph as a whole still
   sits centred under the (short, headline-style) h1 and .hero-line above
   it -- those stay centred deliberately, since a one-line statement has no
   raggedness to fix. */
.hero-sub {
  font-size: 1.15rem; color: var(--text-muted); text-align: left;
  max-width: 570px; margin: 0 auto 36px; line-height: 1.7;
}
/* BUTTONS CENTRE, AND IT IS THE DEFAULT RATHER THAN AN OPT-IN. It was the
   other way round -- left-justified unless a surrounding `.center` said
   otherwise -- and MEASURED at 1200px that left exactly the two pages which
   never opted in sitting differently from the rest of the site: `/start`'s
   "See what is involved" at L=23 R=405 inside its card, and `/account`'s
   "Try again" at L=22 R=486. Every other page reached the same look through
   `.center`, so the idiom was already universal and the default was simply
   pointing the wrong way; a row of buttons under some prose is a page-level
   action wherever it appears.

   `.center` STAYS, because it is a TEXT rule as well and five pages use it for
   their closing sections; what goes is the line that made centring a thing an
   author had to remember. */
.actions { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; }

.btn-primary {
  padding: 14px 28px; background: var(--accent); color: var(--on-accent); border: none;
  border-radius: var(--radius); font-size: 1rem; font-weight: 600;
  text-decoration: none; display: inline-flex; align-items: center; gap: 8px;
  transition: background 0.15s, transform 0.1s;
}
.btn-primary:hover { background: var(--accent-hover); color: var(--on-accent); transform: translateY(-1px); }
.btn-secondary {
  padding: 14px 28px; background: transparent; color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius);
  font-size: 1rem; font-weight: 500; text-decoration: none;
  display: inline-flex; align-items: center; gap: 8px;
  transition: border-color 0.15s, color 0.15s;
}
.btn-secondary:hover { border-color: var(--accent); color: var(--accent); }
@media (prefers-reduced-motion: reduce) { .btn-primary:hover { transform: none; } }

/* ── The metadata panel ── */
.preview {
  max-width: 560px; margin: 48px auto 0; background: var(--surface);
  border: 1px solid var(--border); border-radius: 16px;
  box-shadow: var(--shadow-lg); overflow: hidden; text-align: left;
}
.preview-bar { display: flex; align-items: center; gap: 10px; padding: 10px 16px; background: var(--border); }
.preview-dot { width: 10px; height: 10px; border-radius: 50%; flex: none; }
.preview-dot:nth-child(1) { background: #ff5f57; }
.preview-dot:nth-child(2) { background: #febc2e; }
.preview-dot:nth-child(3) { background: #28c840; }
.preview-file { margin-left: auto; font-family: var(--mono); font-size: 0.72rem; color: var(--text-muted); }
.preview-head {
  padding: 12px 20px 10px; border-bottom: 1px solid var(--border);
  font-size: 0.8rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--text-muted);
}
.fields { padding: 6px 0; }
.field {
  display: grid; grid-template-columns: 9.5rem minmax(0, 1fr);
  gap: 0 14px; align-items: baseline; padding: 8px 20px;
}
.field + .field { border-top: 1px solid var(--border); }
.field dt { font-size: 0.82rem; color: var(--text-muted); }
.field dd { font-size: 0.9rem; overflow-wrap: anywhere; }
.field dd .native { color: var(--text-muted); }
.field-blank dd { color: var(--text-muted); }
.field-note { display: block; font-size: 0.78rem; color: var(--text-muted); margin-top: 2px; }

/* THE LINE UNDER A COMPARISON CARD, and it was five identical inline styles.
   `margin:-6px auto 34px` typed by hand on every card: hand-kept duplication
   this codebase keeps paying for, and INVISIBLE to the sweep for undefined
   classes, because an inline style is not a class name.

   Measured in Chromium at 1280px, all five read the same three faults. The
   -6px was a NEGATIVE gap, so the line overlapped its own card's bottom edge
   rather than sitting under it. 34px then separated one card-and-its-caption
   from the next, which is too little to group them. And `auto` centred a
   680px line under an 880px card, indenting it 100px from the thing it
   describes -- so a caption belonging to the card above read as floating
   between two of them.

   A caption shares its card's LEFT EDGE, which is this stylesheet's own rule
   about a head and its body, one component along. The reading measure stays:
   680px is right for a sentence and 880 is not. */
.compare-note {
  /* CENTRED UNDER THE CARD, on the owner's call, and that is a DECISION rather
     than a defect being fixed -- worth saying because the measurement went the
     other way. This site has exactly one other caption under a picture, the
     home page's figure caption, and it renders at indent 0 sharing the
     image's left edge; matching it is what the previous version did. (Named
     in words rather than as a selector ON PURPOSE: the guard for that rule
     splits this stylesheet on its selector, so quoting it here aimed that
     check at this comment and failed it -- the comment-quoting trap, third
     time in one session and the first to break another rule's guard.)

     What centring answers is the thing that reads wrong under a FULL-WIDTH
     photograph: a 680px line hard against the left of an 880px card leaves
     200px of white on one side and none on the other, which looks lopsided in
     a way the same line does not under a screenshot that fills its column.

     THE SPACING IS THE PART THAT WAS A DEFECT. `-6px` was a NEGATIVE top
     margin, so the line overlapped its own card's bottom edge; 34px below was
     too little to group a card with its caption against the next one.

     THE BOTTOM HALF SERVES TWO DIFFERENT BOUNDARIES, and one value cannot do
     both -- which a first pass at closing this up got wrong and a measurement
     caught. A caption is either the LAST thing in its section (/writes, /) or
     it sits BETWEEN two cards in one (/story stacks five). Against the next
     SECTION the gap is this margin plus 72px of section padding; against the
     next CARD it is this margin and nothing else. Cutting it to 24 to close
     the section join measured 14/24 on /story -- 1.7 to 1, near enough to
     ambiguous that the caption read as a lead-in to the card BELOW it, which
     is the `34px` defect returning by another route.

     So 40 holds the card-to-card case (14/40), and `:last-child` takes the
     section boundary to zero, where the section's own 72px is already the
     rhythm every other join on the page uses. 56 was not wrong when it was
     set: it was sized against 72px sections PLUS a full-bleed rule, both of
     which are gone, and it had quietly become 56+36+36 = 128px, the loosest
     join on the page. One rule, so every caption on the site moves together.
     Its guard asserts the RELATIONSHIP at both boundaries rather than an
     absolute, which is a constant that outlives its reason. */
  max-width: 680px;
  margin: 14px auto 40px;
  /* ITS OWN ALIGNMENT, because an ancestor was deciding it. `/writes` wraps its
     sections in `.center`, which sets `text-align: center` and INHERITS -- so
     the same caption rendered left on /story and /how and centred there, three
     ragged-both-sides paragraphs of four lines each. Nothing about the
     component said which it was; where it sat did. Stating it here is the
     `.hide`/`.runbar` lesson one component along: a block whose rendering
     depends on its container is a block that renders two ways. */
  text-align: left;
}
/* THE LAST CAPTION IN A SECTION NEEDS NO MARGIN OF ITS OWN: the section's 36px
   is already below it and the next section's 36 above, which is the join every
   other pair on the page uses. Without this the one boundary that also carries
   a caption was 40px looser than all of them, and shrinking the margin to fix
   it broke the card-to-card case that margin exists for. */
.compare-note:last-child { margin-bottom: 0; }
@media (max-width: 520px) { .field { grid-template-columns: minmax(0, 1fr); gap: 2px; } }

.preview-foot { display: flex; flex-wrap: wrap; gap: 8px; padding: 12px 20px; border-top: 1px solid var(--border); }
.badge { font-size: 0.75rem; font-weight: 700; padding: 2px 10px; border-radius: 12px; }
.badge.pass { background: var(--success-bg); color: var(--success-fg); }
.badge.pending { background: var(--pending-bg); color: var(--pending-fg); }
.badge.info { background: var(--accent-light); color: var(--accent); }

/* ── The before / after diptych (home) ──
   One photograph, described two ways. The illustration keeps its own fixed
   colours in both themes: it stands in for a photograph, and a photograph
   does not invert when the page does. It is deliberately drawn rather than
   photographic — nothing here is a real frame, and it should not pretend to
   be one. */
.compare {
  max-width: 880px; margin: 8px auto 0; background: var(--surface);
  border: 1px solid var(--border); border-radius: 16px;
  box-shadow: var(--shadow-lg); overflow: hidden; text-align: left;
}

.compare-grid { display: grid; }
@media (min-width: 720px) { .compare-grid { grid-template-columns: 1fr 1fr; } }
.col { padding: 20px 22px 22px; }
.col + .col { border-top: 1px solid var(--border); }
@media (min-width: 720px) {
  .col + .col { border-top: 0; border-left: 1px solid var(--border); }
}
.col-head {
  display: flex; align-items: center; gap: 7px; margin-bottom: 14px;
  font-size: 0.8rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;
}
.col-before .col-head { color: var(--text-muted); }
.col-after .col-head { color: var(--accent); }

.chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 18px; }
.chip {
  font-family: var(--mono); font-size: 0.74rem; padding: 3px 10px;
  border-radius: 14px; background: var(--pending-bg); color: var(--pending-fg);
}

.said dt {
  font-size: 0.72rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: 3px;
}
.said dd { font-size: 0.93rem; margin-bottom: 15px; overflow-wrap: anywhere; }
.said dd:last-child { margin-bottom: 0; }
.said dd strong { font-weight: 600; }
.said dd .native { color: var(--text-muted); }
/* An em dash where a field was never filled — the absence IS the argument, so
   it is drawn quietly rather than as a warning. */
.said dd.empty { color: var(--text-muted); }
.said-note { display: block; font-size: 0.78rem; color: var(--text-muted); margin-top: 3px; }
.said dd.rungs { color: var(--text-muted); }
.said dd .sep { opacity: 0.5; }

/* Supporting examples, ONE PER ROW. They sat side by side at half width, which
   forced each card's own before/after to stack -- so a card carrying a
   photograph got a frame about 430px wide above a single narrow column, and a
   second example could only ever be a second half-width card beside it. Full
   width gives the frame room and lets the before/after split as it does
   everywhere else; a further example goes BELOW rather than alongside. The
   `.sm` variant went with the change: nothing used it once both cards came out
   of the two-column grid. */
.compare-pair { display: grid; gap: 22px; max-width: 880px; margin: 26px auto 0; }

/* A REAL PHOTOGRAPH AT THE HEAD OF A CARD, full-bleed inside the frame, SHOWN
   WHOLE. The browser client draws the frame it is about; the home page's
   before/after does the same, so the reader sees the picture the nine fields
   describe rather than being asked to imagine one. `.compare` already clips to
   its own radius, so no corner rounding is needed here.

   THERE WAS A SECOND CLASS FOR THIS AND IT CROPPED. `.cardshot` capped at 300px
   and covered, on the reasoning that a 4:3 frame at 880px is 660px tall and
   pushes the two columns -- the thing the card is actually FOR -- off the
   bottom of the screen. That is a true cost and it is the wrong trade: a card
   whose whole argument is "here is the photograph, and here is what was
   written about it" cannot show a band through the middle of the photograph.
   It also left the home page with two picture cards cropping differently, one
   above the other, which is how the crop got noticed at all.

   IT SCALES TO FIT RATHER THAN LETTERBOXING, and the first version did the
   other thing. `object-fit: contain` inside a full-width box capped at 460px
   put a 4:3 frame on screen at 613px wide inside an 880px card -- smaller than
   its own source file, with bands down both sides. Letting the box take the
   image's own aspect ratio uses the width that is there and leaves no bands to
   colour. The cap is `vh` because the thing it guards against is a PORTRAIT
   frame taller than the screen, which is a fact about the viewport rather than
   about the card. */
.framephoto {
  display: block; margin-inline: auto;
  /* `width: auto` IS LOAD-BEARING and looks redundant beside `max-width`. Every
     one of these images carries HTML `width`/`height` attributes, which is right
     -- they reserve the space and stop the card jumping as it loads -- but the
     `width` attribute is a presentational hint on the CSS `width` property, so
     the element's width is NOT auto. `max-height` then clamps the height of a
     box whose width is already fixed, and the frame is SQUASHED rather than
     scaled: measured at a 900px-tall viewport, a 4:3 frame rendered 878x648,
     which is 1.355. Author CSS beats a presentational hint, so restoring `auto`
     puts both dimensions back under the constraint rules that preserve the
     ratio. Only a frame tall enough to hit the cap shows it, which is why a
     16:9 one never did. */
  max-width: 100%; width: auto; height: auto; max-height: 72vh;
  border-bottom: 1px solid var(--border);
}

/* One frame described several ways: the same card, but a single column, so the
   two-column split does not leave a lone column at half width. */
.compare.solo { text-align: left; }
.compare.solo .compare-grid { grid-template-columns: 1fr; }
.said dd code { font-family: var(--mono); font-size: 0.92em; overflow-wrap: anywhere; }

/* ── The prompt-note mock (/writes) ──
   A DRAWN CONTROL, and it is allowed to be drawn where a photograph is not:
   the rule `test_no_page_draws_a_photograph_it_did_not_take` holds is about
   FRAMES, and this is a form field, the same class of thing as the
   before/after columns — a comparison of metadata rather than a picture of
   one. What is inside it is not illustrative at all: it is the note that
   run was actually given, the same string the caption under the card
   quotes, so the two halves of the section cannot disagree about what a
   person typed.

   THE WRAPPER TAKES THE CARD'S 880 AND THE FIELD TAKES 560, which is the
   only arrangement of those two numbers that was measured rather than
   guessed. Centring a 560px box under a centred heading puts its left edge
   at 360 while the card below starts at 200, so the one thing on the page
   that stands for an INPUT agreed with nothing around it and floated. Left
   aligning it inside the card's own measure makes the box and the card read
   as one thing -- what was typed, then what came back -- which is what the
   section is about. The field stays 560 because a text field as wide as the
   evidence below it reads as a second panel rather than as the line that
   preceded it.

   `text-align: left` for .callout's own reason: it sits inside `.wrap
   center` and is a self-contained component, not prose belonging to the
   section around it. */
.notebox { max-width: 880px; margin: 26px auto 4px; text-align: left; }
.notebox-field { max-width: 560px; }
.notebox-label { font-size: 0.82rem; font-weight: 600; color: var(--text-muted); margin-bottom: 7px; }
.notebox-field {
  display: flex; align-items: flex-start; gap: 10px;
  background: var(--bg); border: 1px solid var(--border);
  border-radius: 10px; padding: 12px 14px;
  font-size: 0.95rem; line-height: 1.5; color: var(--text);
}
.notebox-field .icon { width: 16px; height: 16px; flex: none; color: var(--accent); margin-top: 3px; }

/* ── Cards ── */
/* ONE GAP BETWEEN BOXES, AND THERE WERE TWO. MEASURED at 1200px: a card sat
   16px from the next card in a grid and 22px from a callout beside it, so
   /pricing showed both numbers in one column and the difference read as an
   accident rather than as a distinction. 22px is what the rest of the
   stylesheet already uses between blocks -- `.callout`'s own margins and
   `.compare-pair`'s grid -- so the odd one out was this. */
.grid-2, .grid-3 { display: grid; gap: 22px; }
@media (min-width: 760px) { .grid-3 { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 860px) { .grid-2 { grid-template-columns: 1fr 1fr; align-items: start; } }
.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 22px; box-shadow: var(--shadow);
}
.card { text-align: left; }
.card h3 { font-size: 1.08rem; font-weight: 700; margin-bottom: 8px; }
.card p { font-size: 0.95rem; color: var(--text-muted); }
.card p + p { margin-top: 10px; }
/* TWO CARDS STACKED IN THE FLOW HAVE NOTHING BETWEEN THEM. `.callout` carries
   its own `margin-top: 22px` and `.card` never did, so cards sitting as
   siblings touched -- fine everywhere it had happened before, because every
   other stack of them is a `.grid-*` or `#account`, both of which space with
   `gap`. Scoped to a DIRECT child of a wrap so it can never add to a grid's
   gap, which is what an unconditional `.card + .card` would do. */
/* 22px, MATCHING EVERY OTHER GAP BETWEEN BOXES. It was 16, so /pricing put
   both numbers in one column -- 16 between two cards and 22 to the callout
   below them -- and two gaps that differ by six pixels read as a mistake
   rather than as a distinction. */
.wrap > .card + .card,
.wrap-narrow > .card + .card { margin-top: 22px; }
.step-n {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--accent-light); color: var(--accent);
  font-size: 0.8rem; font-weight: 700; margin-bottom: 12px;
}
a.card { text-decoration: none; color: inherit; display: block; transition: border-color .15s, transform .1s; }
a.card:hover { border-color: var(--accent); transform: translateY(-2px); }
a.card .more { display: inline-flex; align-items: center; gap: 6px; margin-top: 12px; color: var(--accent); font-size: 0.9rem; font-weight: 600; }
@media (prefers-reduced-motion: reduce) { a.card:hover { transform: none; } }

/* ── Prose ── */
.prose { max-width: 40rem; }
.prose p { color: var(--text-muted); }
.prose p + p { margin-top: 16px; }
.prose strong { color: var(--text); font-weight: 600; }
.pull {
  font-size: clamp(1.3rem, 2.8vw, 1.8rem); font-weight: 700;
  line-height: 1.35; letter-spacing: -0.4px; color: var(--text); margin-bottom: 22px;
}
/* These two select TAGS rather than classes because the line is editable and a
   class cannot survive the sanitiser (see the comment in story.html).
   `font-weight: inherit` is the load-bearing half and looks redundant beside a
   parent that is already 700: `strong` defaults to `bolder`, which is RELATIVE,
   so against 700 it resolves to 900 and the accent word came out heavier than
   the line it sits in — measured in Chromium, not guessed. The span it replaced
   inherited, so this is what keeps the rendering identical. */
.pull code { font-family: var(--mono); font-size: 0.7em; font-weight: 600; color: var(--text-muted); white-space: nowrap; }
.pull strong { color: var(--accent); font-weight: inherit; }

/* ── Rule lists ── */
/* THE ACCOUNT PAGE'S PANELS. Bare siblings in a div have no gap, so the cards
   here touched edge to edge -- and a card is drawn as a box precisely to say
   where one thing ends and the next begins. A grid gap rather than a margin on
   .card, because .card is used in grids on four other pages that already own
   their spacing, and only this page stacks them by hand. */
#account { display: grid; gap: 14px; justify-items: start; }
/* THE REAL CAUSE of "loading and the account panel both visible at once",
   found live from a screenshot -- and it explains why an earlier JS fix to
   render()'s own reset logic (adding show("account", false) at the top)
   never actually stopped it. CSS CASCADE ORIGIN RANKS BEFORE SPECIFICITY:
   `[hidden] { display: none }` is a BROWSER DEFAULT, and ANY author rule
   with a `display` property -- regardless of its own specificity -- beats a
   UA rule outright. `#account`'s own `display: grid` above is exactly such
   a rule, so setting the `hidden` ATTRIBUTE via JS (`account.hidden = true`)
   was always doing nothing visually: the element stayed `display: grid`
   forever, once anything had ever populated it. Every OTHER toggled panel on
   this page (#loading, #broken, #not-entitled, #signed-out) has no author
   `display` rule of its own, so `hidden` worked correctly on them by
   accident -- #account was the one exception, silently broken since the day
   this rule was written. `#account[hidden]` restores it: a compound
   selector beats the bare `#account` rule on ordinary specificity grounds
   (both are author-origin, so specificity decides here), which is what
   actually hides it now. */
#account[hidden] { display: none; }
#account > .card { justify-self: stretch; }
.whoami { font-size: 0.92rem; color: var(--text-muted); }
.whoami strong { color: var(--text); font-weight: 600; }
/* The id sits UNDER the "Signed in as" line and is quieter than it: a person
   reads their own email to know they are in the right account, and reads this
   only when somebody asks them for it. `user-select: all` makes it one click
   to copy -- it is a 32-character opaque string whose whole purpose is being
   pasted somewhere else, and a partial selection of one is worse than none. */
.whoami-id { margin: -0.55rem 0 0; font-size: 0.8rem; color: var(--text-muted); }
.whoami-id code { user-select: all; font-size: 0.95em; }

.panel-title { display: flex; align-items: center; gap: 8px; font-size: 1.08rem; font-weight: 700; margin-bottom: 16px; }
.panel-title .icon { width: 18px; height: 18px; }
.writes .panel-title { color: var(--success); }
.rules { list-style: none; display: grid; gap: 14px; }
.rules li { display: grid; grid-template-columns: 20px minmax(0, 1fr); gap: 2px 8px; }
.rules .icon { margin-top: 5px; }
.writes .rules .icon { color: var(--success); }
.refuses .rules .icon { color: var(--text-muted); }
.rules b { font-size: 0.95rem; font-weight: 600; }
.rules span { grid-column: 2; font-size: 0.92rem; color: var(--text-muted); }

/* ── Table ── */
.table-wrap { overflow-x: auto; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); box-shadow: var(--shadow); }
table { border-collapse: collapse; width: 100%; min-width: 30rem; }
/* THE 30rem FLOOR IS FOR PROSE TABLES and is wrong for a short numeric one.
   `/how`'s columns hold sentences, so 480px is what keeps them readable and
   `.table-wrap` scrolls when the screen cannot take it. A price ladder is
   three short columns -- `1,000`, `$45`, `4.5c` -- and the same floor makes a
   phone SCROLL to reach the per-scan figure, which is the number somebody came
   to the page for. Measured at 390px: the table is 480px inside a 342px card.
   `min-width: 0` lets it fit; the wrapper stays, so a table that ever does
   outgrow the screen is still reachable rather than cut. */
.pricetable { min-width: 0; }
/* THE SAME ESCAPE, FOR A TABLE OF PROPERTY NAMES. The global `min-width: 30rem`
   is sized for prose columns, and `Iptc4xmpCore:ExtDescrAccessibility` pushed
   this one to 685px inside a 342px card -- MEASURED at 390px. `.table-wrap`
   kept it reachable rather than cut, which is the right backstop and the wrong
   answer here: the property names ARE what somebody opens that page for, so
   reaching them should not need a sideways scroll. The long tokens wrap at any
   character, which is `.said dd code`'s own idiom three rules along. */
.iptctable { min-width: 0; }
.iptctable code { overflow-wrap: anywhere; }
/* AND THE HEADERS WRAP TOO. `thead th { white-space: nowrap }` is right for a
   price ladder's short labels and wrong for three prose headings: with the
   long tokens already wrapping it was still 488px in a 342px card, and the
   nowrap was the whole of the remainder. */
.iptctable thead th { white-space: normal; }
thead th {
  font-size: 0.8rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--text-muted); text-align: left; padding: 12px 20px;
  border-bottom: 1px solid var(--border); white-space: nowrap;
}
tbody td { padding: 12px 20px; border-bottom: 1px solid var(--border); vertical-align: top; font-size: 0.93rem; }
tbody tr:last-child td { border-bottom: 0; }
tbody td:first-child { font-weight: 600; white-space: nowrap; }
tbody td:last-child { color: var(--text-muted); }

/* ── Callout ── */
/* text-align: left for the same reason .card carries its own: a callout is a
   self-contained component, not prose that belongs to whatever section it
   sits in. `.wrap center` on /writes stays open around a whole callout
   (both its paragraphs), so without this it inherited ragged-both-edges
   centred text -- exactly the .hero-sub / .center .section-sub defect, one
   component this sweep had not reached. Fixed at the COMPONENT, as .card
   is, so no future centred section can reintroduce it by accident. */
/* A CALLOUT IS SPACED ON BOTH SIDES, and it had a top margin and nothing
   underneath -- so it sat FLUSH against whatever followed. MEASURED at 1200px
   on /pricing: card -> callout 22px, callout -> card 0px, which is two boxes
   of different colours sharing an edge and reading as one. Symmetric now, and
   `:last-child` takes it back off so a callout closing a card does not push
   the card's own padding out -- the idiom `.compare-note`, `.said dd` and
   `.section-sub` already use here. */
.callout { margin-top: 22px; margin-bottom: 22px; padding: 20px 22px; background: var(--accent-light); border-radius: var(--radius); text-align: left; }
.callout:last-child { margin-bottom: 0; }
.callout h3 { display: flex; align-items: center; gap: 8px; font-size: 0.98rem; font-weight: 700; color: var(--accent); margin-bottom: 8px; }
.callout h3 svg { width: 18px; height: 18px; flex: none; }
.callout p { font-size: 0.93rem; color: var(--text); }
/* A BUTTON INSIDE A CALLOUT NEEDS AIR ABOVE IT. Every paragraph in a callout
   carries margin 0, so an `.actions` row landed flush against the prose --
   measured at a 0px gap, against 20px of padding below it, which is what made
   the account page's "Sign in" read as crammed into the bottom-left corner.
   At the COMPONENT, like .card and .callout's own text-align, so all seven
   account panels (sign in, not approved, create a key, sign out...) gain it
   together rather than the one that was noticed. */
.callout .actions { margin-top: 16px; }

/* ── Install ── */
.reqs { list-style: none; display: grid; gap: 12px; margin-bottom: 26px; }
.reqs li { display: flex; gap: 10px; font-size: 0.98rem; color: var(--text-muted); }
.reqs .icon { color: var(--accent); margin-top: 5px; }
pre {
  font-family: var(--mono); font-size: 0.82rem; line-height: 1.8;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 18px 20px; overflow-x: auto;
  margin-bottom: 26px; color: var(--text);
}
pre .c { color: var(--text-muted); }

/* ── Next-page link ── */
.next { display: flex; justify-content: space-between; align-items: center; gap: 16px; flex-wrap: wrap; }
.next-label { font-size: 0.85rem; color: var(--text-muted); }
.next a { font-size: 1.15rem; font-weight: 700; text-decoration: none; display: inline-flex; align-items: center; gap: 8px; }
.next a:hover { text-decoration: underline; }

/* ── Footer ── */
footer { border-top: 1px solid var(--border); background: var(--surface); padding: 44px 0 40px; font-size: 0.9rem; color: var(--text-muted); }
.foot-grid { display: grid; gap: 28px; }
/* Three columns: brand, Learn, Legal. The Elsewhere pair went in 0.11.2 --
   a private repo nobody invited can open, and a health endpoint written for
   whoever is debugging a scan rather than for a customer. */
@media (min-width: 700px) { .foot-grid { grid-template-columns: 1.6fr 1fr 1fr; } }
footer h4 { font-size: 0.8rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: 12px; }
footer .logo { font-size: 1.25rem; display: inline-block; margin-bottom: 8px; }
footer ul { list-style: none; display: grid; gap: 8px; }
footer a { text-decoration: none; }
footer a:hover { text-decoration: underline; }
.colophon {
  margin-top: 32px; padding-top: 20px; border-top: 1px solid var(--border);
  display: flex; flex-wrap: wrap; gap: 6px 20px; font-size: 0.82rem;
}

/* ---- A real result, shown as a screenshot -------------------------------
   The drawn frames elsewhere are ILLUSTRATIONS and say so by being drawn.
   This one is a photograph of a real catalogue after a real run, so it is
   the honest form here -- and the figcaption has to keep saying which is
   which, or the two read as the same kind of claim. */
/* A TOP MARGIN, because the paragraph above sits in a SEPARATE `.wrap` and
   nothing else was going to put air between them. `margin: 0` here is a
   figure reset -- browsers give `<figure>` a 1em inline margin that would
   have indented the screenshot out of line with the text above it -- and
   zeroing all four sides took the gap with it, so the last line of the lead
   sat directly on the window chrome. The inline zero stays; only the top
   moves. */
.shot { margin: 32px 0 0; }
.shot img {
  display: block; width: 100%; height: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  background: #1c1c1c;          /* the screenshot's own chrome, so no flash of page */
}
/* LEFT-ALIGNED, and inheriting `.center` would have made it ragged on both
   edges -- the same defect already fixed at the component for `.hero-sub`,
   `.center .section-sub` and `.callout`. This one sits outside that sweep
   because the sweep looks for a <p>, and a <figcaption> is not one. */
.shot figcaption {
  margin: 14px 0 0;
  font-size: 0.92rem;
  line-height: 1.55;
  color: var(--text-muted);
  max-width: 62ch;
  text-align: left;
}

/* ---- A SET, shown as a set ----------------------------------------------
   Ten frames is the argument: one account composed from ten scans is not a
   claim a single photograph can make. So every frame the narrative was
   written from is shown, rather than a chosen few -- a selection would put a
   different number on the page from the number the account was composed over.

   They are prepared at 600px against a slot of roughly 220px, which is 2x on
   the widest row. The first pass used 900 and cost 981 KB for one section;
   these are content-hashed, so the weight is bought once rather than on every
   visit, but a third of it was still paying for pixels nothing displays. */
.strip {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 10px;
  margin: 0 auto 22px;
  max-width: 880px;
}
@media (max-width: 760px) { .strip { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 420px) { .strip { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.stripshot {
  display: block; width: 100%; height: auto;
  border: 1px solid var(--border);
  border-radius: 8px;
}

/* An `Example` label sits where a filename used to. The site's illustrations
   were once obviously illustrations because they were DRAWN; with the drawings
   gone, and a real screenshot now on the home page, only this word tells a
   reader which kind of thing they are looking at. So it is deliberately not
   styled to look like a filename. */
.preview-file.is-example {
  font-family: inherit;
  font-style: italic;
  letter-spacing: 0.02em;
  opacity: 0.85;
}

/* ── The holding page ──────────────────────────────────────────────────────
   The front door while the beta is closed. One card, no nav, no footer, no
   button — nothing clickable anywhere on the page, because a "coming soon"
   with a call to action on it is not a coming soon.

   It centres in the VIEWPORT rather than sitting under a top margin, and that
   follows from the footer going: with a footer below it the card sat in the
   upper third and the page read as a document that had been cut short. With
   nothing below it, anything other than centred reads as a page that failed
   to finish loading — the same failure the empty `.plate` windows were.

   The HEADING and the one-line lead are centred; the two PROSE paragraphs are
   not, and that is this site's own rule rather than a preference. Centred
   multi-line prose is ragged at both edges and the eye has to hunt for the
   start of every line — `.hero-sub` and `.center .section-sub` are both left
   aligned for exactly this reason, and a holding page is not the place to
   make an exception nobody would have argued for on any other page. */
.holding {
  display: flex; flex-direction: column; align-items: center;
  justify-content: center;
  padding: 8vh 24px; min-height: 100vh;
}
.holding-mark {
  font-size: 1.85rem; font-weight: 700; color: var(--accent);
  letter-spacing: -0.02em; margin-bottom: 8vh;
}
.holding-mark span { color: var(--text); }
.holding-card {
  width: 100%; max-width: 40rem; padding: 44px 40px 40px;
  box-shadow: var(--shadow-lg);
}
.holding-card h1 {
  text-align: center; font-size: clamp(1.7rem, 4.5vw, 2.3rem);
  line-height: 1.2; margin-bottom: 10px;
}
.holding-card .card-label.center { text-align: center; }
.holding-lead {
  text-align: center; font-size: 1.15rem; color: var(--text);
  margin-bottom: 26px;
}
.holding-lead em { font-style: normal; color: var(--accent); }
.holding-card p + p { margin-top: 14px; }
@media (max-width: 560px) {
  .holding { padding: 8vh 18px 7vh; }
  .holding-mark { margin-bottom: 5vh; }
  .holding-card { padding: 32px 24px 28px; }
}
/* The closing invitation. Separated by a rule rather than by space alone: the
   two paragraphs above are ABOUT the product and this one asks the reader to
   do something, and a reader skimming a short card should be able to find the
   one actionable line without reading back up. Left-aligned like the prose it
   follows, for the same reason. */
.holding-write {
  margin-top: 20px; padding-top: 18px; border-top: 1px solid var(--border);
}

/* ------------------------------------------------------------------------
   THE SITE EDITOR. Every rule here is inert for every visitor: `site-edit.js`
   returns before touching the DOM unless the browser holds an editing token,
   so nothing below is ever matched by a page as served.

   It lives in the shared stylesheet rather than in the script because a
   stylesheet injected by JavaScript arrives after the first paint, and the
   one thing worse than no pencil is a pencil that lands as a full-size
   unstyled button in the middle of a sentence.
   ------------------------------------------------------------------------ */

/* The affordance. Sized to the text it sits in (`em`, not `px`) so it reads as
   punctuation beside a heading and beside body copy alike, and dimmed until
   the section is hovered -- an editor still has to be able to READ the page
   they are editing, and nine bright pencils down a column is a page about its
   own controls. */
.pw-ed-pencil {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.15em; height: 1.15em; margin-left: .35em; padding: 0;
  vertical-align: baseline;
  border: 0; border-radius: 4px; background: none; cursor: pointer;
  color: var(--accent); opacity: .38; transition: opacity .12s ease;
}
.pw-ed-pencil svg {
  width: 100%; height: 100%;
  fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}
.pw-ed-on:hover > .pw-ed-pencil,
.pw-ed-pencil:focus-visible { opacity: 1; }
.pw-ed-pencil:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* WHICH PARAGRAPHS ARE NOT WHAT THE REPOSITORY SAYS. A left rule rather than a
   background, because a tint would change how the prose itself reads and the
   whole point of editing in place is judging the words on the page. */
.pw-ed-dirty {
  box-shadow: -10px 0 0 -8px var(--accent);
}

/* The editor, in the space the text was in, so the surrounding page keeps its
   shape and a heading is still judged against the paragraph under it. */
.pw-ed-box { display: block; }
/* `font: inherit` so the words are edited at something like the size and
   weight they will be read at -- but CLAMPED, because a page-head `h1` is
   2.6rem and a card label is uppercase and letter-spaced, and neither is a
   usable typing surface. `text-transform` is reset outright: a label's
   uppercase would apply to the textarea and to the slot id beside it, and an
   id shown in the wrong case is one that does not work when it is pasted. */
.pw-ed-area {
  display: block; width: 100%; box-sizing: border-box;
  font: inherit; font-size: clamp(.95rem, 1em, 1.35rem);
  text-transform: none; letter-spacing: normal; line-height: 1.5;
  padding: 10px 12px; border: 1px solid var(--accent); border-radius: 8px;
  background: var(--bg); color: var(--text); resize: vertical;
}
.pw-ed-actions {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin-top: 8px;
}
.pw-ed-actions button {
  font: inherit; font-size: .82rem; font-weight: 600;
  padding: 5px 12px; border-radius: 7px; cursor: pointer;
  border: 1px solid var(--border); background: var(--bg); color: var(--text);
}
.pw-ed-save { border-color: var(--accent) !important;
              background: var(--accent) !important; color: var(--on-accent) !important; }
.pw-ed-revert { color: var(--text-muted) !important; }
.pw-ed-actions button[disabled] { opacity: .5; cursor: default; }
/* The slot id, until something goes wrong and it becomes the reason. It is on
   screen at all times rather than only on failure, because the id is what the
   export prints and what a person needs when folding an edit back into a
   file -- and a label that appears only when something breaks is one nobody
   learns to read. */
.pw-ed-msg {
  font-size: .74rem; font-weight: 400; letter-spacing: .01em;
  text-transform: none;
  color: var(--text-muted); font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.pw-ed-bad { color: var(--accent) !important; font-family: inherit !important;
             font-size: .8rem !important; }

/* The one thing on screen saying this page is not in its ordinary state.
   Fixed, so it survives a scroll down a long page -- an editor who cannot find
   the way out is one who clears their own storage to get it. */
.pw-ed-bar {
  position: fixed; z-index: 60; right: 16px; bottom: 16px;
  display: flex; align-items: center; gap: 12px;
  padding: 9px 12px 9px 14px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--bg);
  box-shadow: 0 1px 2px rgba(33,29,23,.06), 0 8px 24px rgba(33,29,23,.14);
}
.pw-ed-note { font-size: .78rem; color: var(--text-muted); }
.pw-ed-stop {
  font: inherit; font-size: .78rem; font-weight: 600;
  padding: 4px 11px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--border); background: none; color: var(--text);
}
@media (max-width: 560px) {
  .pw-ed-bar { left: 16px; right: 16px; justify-content: space-between; }
}

/* THE BUY COLUMN ON /pricing. `.buycell` is EMPTY until the catalogue answers
   -- a deploy that sells nothing draws no buttons at all -- so it is sized to
   collapse rather than to reserve a gap nothing will fill. `.buybtn` shrinks
   the shared secondary button to a table row's height; it is the site's own
   button rather than /app's `.mini`, which is not defined here. */
.pricetable .buyhead,
.pricetable .buycell { text-align: right; white-space: nowrap; width: 1%; }
.pricetable .buybtn { padding: .3rem .75rem; font-size: .85rem; }

/* MEASURED, NOT GUESSED. With the Buy column the table came to 342px inside a
   294px card at 390px, so `.table-wrap` put the page's primary action behind a
   horizontal scroll. The `Each` column is NOT the thing to drop -- it is the
   number somebody came to the page for, and `.pricetable { min-width: 0 }`
   exists because of it -- so the padding gives way instead. */
@media (max-width: 560px) {
  .pricetable th, .pricetable td { padding-left: .4rem; padding-right: .4rem; }
  .pricetable .buybtn { padding: .3rem .5rem; }
}
