/* ===========================================================================
   THE STYLESHEET. Clean SaaS, chosen 2026-09-30 from two rendered directions.

   This IS site.css — the one stylesheet every page links, by a name carrying a
   stamp of these bytes (Assets::url). It began as theme-saas.css beside a
   theme-industrial.css; the choice made one of them the site and the other a
   file in the history (commit dae69a9), because two copies of a thousand lines
   that must not drift is a cost with nothing to show for it.

   WHAT IT IS. Off-white ground, white cards with a soft edge and a soft shadow,
   one deep blue accent, and air: bigger headings, wider gutters, rounder
   corners. It is meant to read like something a small business is glad to have
   on the invoice — a product rather than a tool — which is the strapline
   Words::catchphrase() spells out, said in shapes.

   LIGHT IS THE IDENTITY, so :root is light and stays light on a machine set to
   light. A machine set to DARK gets the same language after dark — deep navy
   slate surfaces, the same blue lifted to stay readable, the same air, the same
   rounding. `data-theme="light"` / `data-theme="dark"` on <html> wins over both,
   so a switch added later needs no change here.

   No framework, no build step, no external font and no external anything: the
   Content-Security-Policy is `default-src 'none'; style-src 'self'` (Http::
   CSP_DEFAULT), so there are no @imports, no url() to another host, and no
   inline styles in any view.
   =========================================================================== */

:root {
    /* ---- ink and ground (light: the identity) ---- */
    --bg: #f6f8fb;          /* the page */
    --surface: #ffffff;     /* a card, a panel, the header bar */
    --raised: #eef2f7;      /* a well: a table head, an empty thumbnail */
    --line: #dde5ee;        /* every hairline */
    --line-soft: #eaeff5;
    --ink: #0f1a2b;
    --ink-strong: #06101e;
    --quiet: #5a6a7e;

    /* ---- the one accent ---- */
    --accent: #1b4b9c;      /* deep blue: links, primary buttons, marks */
    --accent-hot: #143d84;  /* hover: deeper, not brighter */
    --accent-ink: #ffffff;  /* text ON the blue */
    --accent-soft: #e8eefa; /* a tint behind text */

    --bad: #b02a24;
    --bad-soft: #fdeceb;
    --good: #12704a;
    --good-soft: #e4f4ec;
    --warn: #8a5a00;

    /* ---- type ---- */
    --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
    --t-xs: 0.78rem;
    --t-sm: 0.9rem;
    --t-base: 1rem;
    --t-md: 1.1rem;
    --t-lg: 1.3rem;
    --t-xl: 1.75rem;
    --t-2xl: 2.4rem;

    /* ---- space: one step larger than the industrial direction throughout ---- */
    --s1: 0.3rem;
    --s2: 0.5rem;
    --s3: 0.8rem;
    --s4: 1.2rem;
    --s5: 1.8rem;
    --s6: 2.75rem;
    --s7: 4.5rem;

    /* ---- shape ---- */
    --r-sm: 6px;
    --r: 10px;
    --r-lg: 16px;
    --column: 48rem;
    --touch: 3rem;          /* 48 dp. Nothing a thumb presses is smaller. */
    --shadow: 0 1px 2px rgba(15, 26, 43, 0.06), 0 1px 3px rgba(15, 26, 43, 0.05);
    --shadow-lg: 0 4px 16px rgba(15, 26, 43, 0.08);
    --shadow-up: 0 -4px 20px rgba(15, 26, 43, 0.12);
    color-scheme: light;
}

/* AFTER DARK. The same direction with the lights off: navy slate instead of
   paper, the blue lifted so it still reads, the air and the rounding unchanged.
   Guarded so an explicit `data-theme="light"` still wins. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --bg: #0d1420;
        --surface: #141d2b;
        --raised: #1c2736;
        --line: #26333f;
        --line-soft: #1f2a38;
        --ink: #e8eef6;
        --ink-strong: #ffffff;
        --quiet: #94a4b8;
        --accent: #7ba7f5;
        --accent-hot: #a3c2fb;
        --accent-ink: #0b1220;
        --accent-soft: #1b2a45;
        --bad: #f28b82;
        --bad-soft: #35201e;
        --good: #6ede9f;
        --good-soft: #14301f;
        --warn: #e0a33a;
        --shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
        --shadow-lg: 0 4px 20px rgba(0, 0, 0, 0.5);
        --shadow-up: 0 -4px 20px rgba(0, 0, 0, 0.55);
        color-scheme: dark;
    }
}

/* An explicit choice, either way, over the setting. */
:root[data-theme="dark"] {
    --bg: #0d1420;
    --surface: #141d2b;
    --raised: #1c2736;
    --line: #26333f;
    --line-soft: #1f2a38;
    --ink: #e8eef6;
    --ink-strong: #ffffff;
    --quiet: #94a4b8;
    --accent: #7ba7f5;
    --accent-hot: #a3c2fb;
    --accent-ink: #0b1220;
    --accent-soft: #1b2a45;
    --bad: #f28b82;
    --bad-soft: #35201e;
    --good: #6ede9f;
    --good-soft: #14301f;
    --warn: #e0a33a;
    --shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
    --shadow-lg: 0 4px 20px rgba(0, 0, 0, 0.5);
    --shadow-up: 0 -4px 20px rgba(0, 0, 0, 0.55);
    color-scheme: dark;
}

:root[data-theme="light"] {
    --bg: #f6f8fb;
    --surface: #ffffff;
    --raised: #eef2f7;
    --line: #dde5ee;
    --line-soft: #eaeff5;
    --ink: #0f1a2b;
    --ink-strong: #06101e;
    --quiet: #5a6a7e;
    --accent: #1b4b9c;
    --accent-hot: #143d84;
    --accent-ink: #ffffff;
    --accent-soft: #e8eefa;
    --bad: #b02a24;
    --bad-soft: #fdeceb;
    --good: #12704a;
    --good-soft: #e4f4ec;
    --warn: #8a5a00;
    --shadow: 0 1px 2px rgba(15, 26, 43, 0.06), 0 1px 3px rgba(15, 26, 43, 0.05);
    --shadow-lg: 0 4px 16px rgba(15, 26, 43, 0.08);
    --shadow-up: 0 -4px 20px rgba(15, 26, 43, 0.12);
    color-scheme: light;
}

* { box-sizing: border-box; }

body {
    margin: 0;
    font: 16px/1.6 var(--sans);
    color: var(--ink);
    background: var(--bg);
    -webkit-text-size-adjust: 100%;
}

/* Keyboard focus, once, for everything. */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ------------------------------------------------------------ the header bar

   White, with one hairline under it and the wordmark at the left: the shape
   every product this is sold beside already has, and the one shape a reader
   does not have to learn. */
header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s4);
    padding: 0.85rem 1.5rem;
    background: var(--surface);
    border-bottom: 1px solid var(--line);
}

.brand {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-weight: 600;
    text-decoration: none;
    color: var(--ink);
}
.brand:hover { color: var(--accent); }

/* THE WORDMARK (assets/wordmark.svg, inlined so it cannot be the subresource
   the host's bot challenge answers with an HTML page — the reason the map
   carries its own JavaScript inline, Assets.php). Sized in rem so it grows with
   the reader's font size, and coloured through its four classes. */
.wordmark { display: block; height: 1.4rem; width: auto; }
.wm-mark { fill: var(--accent); }
.wm-hole { fill: var(--surface); }
.wm-word { fill: var(--ink-strong); }
.wm-accent { fill: var(--accent); }

nav {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.3rem 1.25rem;
    margin-left: auto;
    font-size: var(--t-sm);
}
nav form { margin: 0; }
nav a {
    padding: 0.35rem 0;
    color: var(--quiet);
    text-decoration: none;
    border-radius: var(--r-sm);
}
nav a:hover { color: var(--accent); }

main {
    max-width: var(--column);
    margin: 0 auto;
    padding: var(--s5) 1.5rem var(--s6);
}

footer {
    max-width: var(--column);
    margin: 0 auto;
    padding: var(--s4) 1.5rem var(--s5);
    color: var(--quiet);
    font-size: var(--t-sm);
    border-top: 1px solid var(--line);
}

h1 {
    margin: 0 0 var(--s4);
    font-size: var(--t-xl);
    font-weight: 700;
    line-height: 1.2;
    letter-spacing: -0.02em;
    color: var(--ink-strong);
}
h2 {
    font-size: var(--t-md);
    font-weight: 600;
    margin: var(--s5) 0 var(--s2);
    color: var(--ink-strong);
}
h3 { font-weight: 600; }

a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { color: var(--accent-hot); }

.stack { display: flex; flex-direction: column; gap: var(--s3); max-width: 23rem; }

/* ------------------------------------------------------------------- controls

   Rounder, roomier, and never under 48 dp high where a thumb is the pointer. */
input, select, button, textarea {
    font: inherit;
    padding: 0.65rem 0.8rem;
    color: var(--ink);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
}
input:hover, select:hover, textarea:hover { border-color: var(--quiet); }
input:focus, select:focus, textarea:focus { border-color: var(--accent); }
input::placeholder, textarea::placeholder { color: var(--quiet); }

button {
    min-height: var(--touch);
    padding: 0.6rem 1.25rem;
    background: var(--accent);
    color: var(--accent-ink);
    border-color: var(--accent);
    border-radius: var(--r-sm);
    font-weight: 600;
    cursor: pointer;
    box-shadow: var(--shadow);
}
button:hover { background: var(--accent-hot); border-color: var(--accent-hot); }
button:disabled { opacity: 0.45; cursor: default; box-shadow: none; }

select { min-height: var(--touch); }
input[type="text"], input[type="search"], input[type="email"],
input[type="password"], input[type="number"] { min-height: var(--touch); }

/* The nav's Sign out is a link that happens to be a form. */
nav button {
    min-height: 0;
    padding: 0.35rem 0;
    background: none;
    color: var(--quiet);
    border: none;
    box-shadow: none;
    font-weight: 400;
}
nav button:hover { background: none; color: var(--accent); }

.error { color: var(--bad); }

/* The other half of .error: what happened when it worked. */
.notice { color: var(--good); }

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

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

   A table is a panel: white, rounded, with a tinted head. */
table {
    border-collapse: separate;
    border-spacing: 0;
    width: 100%;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r);
    overflow: hidden;
    box-shadow: var(--shadow);
    font-variant-numeric: tabular-nums;
}
th, td {
    text-align: left;
    padding: 0.7rem 0.9rem;
    border-bottom: 1px solid var(--line-soft);
}
tr:last-child td { border-bottom: 0; }
th {
    font-size: var(--t-xs);
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--quiet);
    background: var(--raised);
    border-bottom: 1px solid var(--line);
}

/* The pairing code: the one string on this site a person reads out loud. */
.code {
    font: 700 1.7rem/1.2 var(--mono);
    letter-spacing: 0.18em;
    padding: 1rem 1.4rem;
    color: var(--accent);
    background: var(--accent-soft);
    border: 1px solid var(--line);
    border-radius: var(--r);
    display: inline-block;
}

.search { display: flex; flex-wrap: wrap; gap: var(--s3); margin: var(--s4) 0; }
.search input { flex: 1 1 auto; min-width: 0; }

/* THE LIST'S ONE FORM (2026-10-14). The things list asks its whole question
   with one form and one button, so its narrowings live INSIDE the search form
   rather than in a second one beside it -- the row of chips that replaced its
   pickers the same day included. They keep their own card and their own line
   -- the two rows the page always had -- by taking the full width and wrapping
   under the box. The card's own outer margin would double the form's, so it is
   dropped. */
.search .filters { flex: 1 1 100%; margin: 0; }

/* The item and place lists: one line a person can hit with a thumb, and a
   quieter second line under it. No table, because a table cannot wrap.

   Here each row is a small card in a stack, not a ruled line: it is the one
   change that makes the densest page on the site read like a product. */
.things { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s3); }
.things li {
    padding: var(--s3) var(--s4);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r);
    box-shadow: var(--shadow);
    overflow: hidden;
}
.things li:hover { border-color: var(--accent); }
.thing {
    font-size: var(--t-md);
    font-weight: 600;
    color: var(--ink-strong);
    text-decoration: none;
}
.thing:hover { color: var(--accent); }
.things li > .quiet {
    font-size: var(--t-sm);
    /* A BLOCK FORMATTING CONTEXT, so the second line never tucks itself under
       the thumbnail. `.thumb` is floated (the markup has no wrapper round the
       text, so a flex row is not available), and a plain block child flows
       AROUND a float: at 390 dp "seen 1 week ago" wrapped and the words after
       the break started under the picture, reading as an orphan. `overflow`
       makes this box avoid the float instead of wrapping under it. */
    overflow: hidden;
}

/* ------------------------------------------- the whole card opens the thing
   ON THE THINGS PAGE ONLY (/, "Your things"): the box that encircles the
   words AND the photograph is the target, not just the words. Asked for on
   2026-10-13 -- having to hit four characters of "No name yet" with a thumb
   is a miss waiting to happen.

   THE STRETCHED LINK, and not a wrapping anchor and not a click handler. The
   row is `position: relative` and the TITLE's own `::after` is blown up to
   cover it, so there is still exactly one link in the row and it is still the
   title: keyboard focus lands on it, middle-click and ctrl-click open it in a
   tab, the context menu copies its address, and a screen reader reads one
   link rather than two. With this stylesheet missing the row degrades to what
   it was before -- a clickable title and a picture beside it.

   WHAT IT COSTS, said out loud rather than hidden: an overlay across the card
   means the row's own text cannot be selected by DRAGGING ACROSS IT. A drag
   that starts outside the card still sweeps the text as it passes through,
   and the lifted pieces below (the map link, the "Looks like" links) select
   in their own right; "copy link" on the row gets the thing's address. This
   is the known price of the technique and it was taken knowingly.

   `min-height` is belt to the thumbnail's braces: every row already stands 48
   px on its 48 px picture (or on the empty slot that stands in for one), and
   this says so in the one place a reader would look for it. */
.whole-row { position: relative; min-height: var(--touch); }
.whole-row > .thing::after { content: ""; position: absolute; inset: 0; z-index: 0; }

/* The card now reads as the button it is. `:focus-within` is the keyboard's
   half of that, and the focus RING is untouched by any of this -- an outline
   follows the anchor's own box, not its stretched pseudo-element, so tabbing
   still draws a ring around the title and the row's padding is far wider than
   the 2 px offset `:focus-visible` asks for. */
.whole-row:hover, .whole-row:focus-within {
    background: var(--raised);
    border-color: var(--accent);
}

/* EVERYTHING ELSE A ROW CAN CARRY, lifted back above that overlay -- one
   rule, and every selector in it is something a person presses:
     `.on-top`   the map of where an untagged thing was photographed
     `.alike a`  the links on the "Looks like" line, lifted by naming the
                 LINE so the anchors' own markup stays as it was
   The PHOTOGRAPH is deliberately absent: it is in no anchor of its own, and a
   tap on it is meant to be a tap on the thing. So is `.mark`, which is one
   character of ink and nothing to press. There is no form and no button
   inside a row on this page -- the drop zone sits above the list and a merge
   question is asked on the review page -- and if one is ever added it belongs
   in this rule on the day it arrives. */
.whole-row .on-top, .whole-row .alike a { position: relative; z-index: 1; }

.pages { display: flex; align-items: center; gap: var(--s4); margin: var(--s5) 0; }

.hex {
    font: 0.95rem/1.5 var(--mono);
    letter-spacing: 0.05em;
    word-break: break-all;
    color: var(--quiet);
    padding: var(--s3);
    background: var(--raised);
    border-radius: var(--r-sm);
}

/* THE ONE FOLD AT THE FOOT OF A THING'S PAGE, in the same hand as the drop
   zone's summary and the review page's: a disclosure a person can see is one.
   The blocks behind it keep every rule they had -- this names the lid only. */
.thing-details { margin: var(--s5) 0; }
.thing-details summary { cursor: pointer; color: var(--accent); }

/* For a label that a screen reader needs and a sighted reader does not. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}

/* ------------------------------------------------------------- the pictures
   A thumbnail at the head of a row, and the item page's gallery, which wraps
   rather than scrolling sideways: a warehouse phone in landscape is 640x360 and
   a row of tiles would run off the edge. */
.thumb {
    float: left;
    width: 48px;
    height: 48px;
    margin: 0 var(--s3) 0 0;
    border-radius: var(--r-sm);
    object-fit: cover;
    background: var(--raised);
}
/* A row with no picture yet: a slot, not a hole. */
.thumb-none { display: block; background: var(--raised); border: 1px solid var(--line); }

.gallery {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s3);
    list-style: none;
    margin: var(--s3) 0;
    padding: 0;
}
.gallery li { max-width: 100%; }
.gallery img {
    width: 160px;
    height: 160px;
    object-fit: cover;
    border-radius: var(--r);
    background: var(--raised);
    box-shadow: var(--shadow);
}
.gallery video { max-width: 100%; width: 320px; border-radius: var(--r); background: #000; }
.gallery audio { max-width: 100%; }
.note { min-width: 260px; }

/* THE SAME GALLERY AT THE TOP OF A THING'S PAGE, drawn small (2026-11-02).
   For an account with no processing station the photographs lead the page, and
   eight 160px tiles above the name line is that door pushed off a 360dp phone.
   96px is the size `.strip` already draws a row of evidence pictures at, so no
   new number enters this file: three tiles to a phone row instead of two, and
   they still WRAP rather than scrolling sideways, for the reason above. A film
   keeps its own width -- a 96px player is not a player. The leading heading has
   its top margin taken off and the block carries the gap instead, the way
   `.suggestion h2` and `.row-what h2` do. */
.pictures-first { margin: var(--s4) 0 var(--s5); }
.pictures-first h2 { margin-top: 0; }
.pictures-first .gallery { margin-bottom: 0; }
.pictures-first .gallery img { width: 96px; height: 96px; border-radius: var(--r-sm); }

/* The map. An explicit height, because a map in a box with no height is a blank
   rectangle and Leaflet cannot tell you why. min-height is the floor under a
   small root font size; map.js sets a height of its own if THIS FILE is the
   thing that failed to arrive. */
.map {
    height: 26rem;
    min-height: 320px;
    max-height: 70vh;
    margin: var(--s4) 0;
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow);
}
/* What the box says when the library, the data or this stylesheet went missing.
   A blank rectangle answers no questions; a sentence does. */
.map-failed { margin: 0; padding: var(--s4); color: var(--quiet); }
.legend { list-style: none; margin: var(--s2) 0; padding: 0; }
.legend li { display: inline-block; margin: 0 var(--s4) 0.2rem 0; }
.legend .dot {
    display: inline-block;
    width: 0.7rem;
    height: 0.7rem;
    margin-right: 0.35rem;
    border-radius: 50%;
    vertical-align: middle;
}
.legend .good { background: var(--good); }
.legend .fair { background: var(--warn); }
.legend .poor { background: var(--bad); }
.legend .stale { background: var(--surface); border: 2px dashed var(--quiet); }

/* ------------------------------------------------------------- the questions
   One section per thing, one card per suggestion, and the evidence quiet under
   each: the value being suggested is the one thing on the card that has to be
   readable at a glance, because it is the thing the owner is saying yes or no
   to. */
.suggestion {
    margin: var(--s5) 0;
    padding-bottom: var(--s5);
    border-bottom: 1px solid var(--line);
}
.suggestion h2 { margin-top: 0; }
.suggestion img {
    width: 160px;
    height: 160px;
    object-fit: cover;
    border-radius: var(--r);
    background: var(--raised);
}

.card {
    margin: var(--s3) 0;
    padding: var(--s4);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r);
    box-shadow: var(--shadow);
}
.card h3 { margin: 0 0 var(--s2); font-size: var(--t-base); }
.card ul { margin: 0.25rem 0 var(--s2) 1.2rem; padding: 0; }
.proposed {
    margin: 0 0 var(--s3);
    font-size: var(--t-lg);
    font-weight: 600;
    color: var(--ink-strong);
}

/* THE STRIP OVER THE QUEUE: what is left, and what today has answered. Three
   cells on one line, the number big enough to read across a desk and its word
   quiet beside it. It wraps rather than scrolling, for the reason every row on
   this site does: a phone in landscape is 640x360. */
.tally {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s3) var(--s6);
    list-style: none;
    margin: var(--s3) 0 var(--s5);
    padding: var(--s4);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r);
    box-shadow: var(--shadow);
    color: var(--quiet);
    font-size: var(--t-sm);
}
.tally strong {
    display: block;
    font-size: var(--t-xl);
    font-weight: 700;
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
    color: var(--ink-strong);
    margin-right: 0.3rem;
}

/* The count beside "Suggestions" in the nav. Small, round and quiet: it is a
   nudge and not an alarm. */
.badge {
    display: inline-block;
    min-width: 1.5em;
    padding: 0.05em 0.4em;
    border-radius: 1em;
    background: var(--accent);
    color: var(--accent-ink);
    font-size: var(--t-xs);
    font-weight: 700;
    text-align: center;
}

/* What was read off a thing's pictures, on its own page. A list of lines with
   no decoration: they are what the label says, not a claim about it. */
.readings { list-style: none; margin: var(--s2) 0; padding: 0; }
.readings li { padding: 0.2rem 0; }

/* The manual door's paste box. A file and a box, because the same JSON reaches
   a browser both ways: picked off the disk, or pasted out of a terminal. */
textarea { font-family: var(--mono); resize: vertical; }

/* One card holding several candidates for one thing, with radio buttons. Three
   suggestions about one drill are one question asked three ways. */
.candidates { list-style: none; margin: 0.25rem 0 var(--s3); padding: 0; }
.candidates li { padding: 0.35rem 0; }
.candidates label { display: block; cursor: pointer; min-height: 2rem; }
.candidates input { width: auto; min-height: 0; margin-right: 0.5rem; accent-color: var(--accent); }

/* Every picture of the thing on the card that asks about it. The evidence first
   and marked. It wraps rather than scrolling sideways, for the same reason the
   item page's gallery does: a warehouse phone in landscape is 640x360. */
.strip {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s3);
    list-style: none;
    margin: var(--s3) 0;
    padding: 0;
}
.strip li { max-width: 7rem; }
.strip a { position: relative; display: block; line-height: 0; }
.strip img {
    width: 96px;
    height: 96px;
    object-fit: cover;
    border-radius: var(--r-sm);
    background: var(--raised);
}
.strip .is-evidence img { outline: 2px solid var(--accent); outline-offset: 2px; }
.strip .quiet { display: block; font-size: var(--t-xs); line-height: 1.3; margin-top: 0.25rem; }
/* A film is its still with a play mark on it: an .mp4 in an image tag draws
   nothing, and /m/{sha}/thumb already serves the still the phone sent. */
.strip .play {
    position: absolute;
    left: 0.4rem;
    bottom: 0.4rem;
    padding: 0 0.35rem;
    border-radius: var(--r-sm);
    background: rgba(6, 16, 30, 0.72);
    color: #fff;
    font-size: var(--t-sm);
    line-height: 1.5;
}
.strip .more { display: flex; align-items: center; }

/* The review page's own controls, all of them drawn by suggestions.js: a page
   with no script offers none of them, because a tick box with nothing behind it
   is worse than no tick box. */
.pick, .pick-all { display: block; cursor: pointer; }
.pick { margin: 0 0 var(--s2); font-size: var(--t-sm); color: var(--quiet); }
.pick input, .pick-all input {
    width: auto;
    min-height: 0;
    margin-right: 0.5rem;
    accent-color: var(--accent);
}
.pick-all { margin: var(--s4) 0; font-weight: 600; }

/* What a card says once it has been decided, in place, with no page load. The
   card is dimmed rather than removed: a card that vanished under the pointer is
   how a person loses their place in a queue. */
.card[data-state] { opacity: 0.6; border-style: dashed; box-shadow: none; }
.card .state { margin: var(--s2) 0 0; color: var(--good); }
.card[data-state="gone"] .state,
.card[data-state="failed"] .state { color: var(--quiet); }

/* The bar that says what the ticked boxes would do. It sits over the page
   rather than at the end of it: a queue is worked through by scrolling, and a
   button at the bottom of a long list is a button nobody reaches. */
.bulk {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    display: flex;
    /* Three answers now, and on a phone they do not fit on one line: the bar
       wraps rather than pushing the third one off the edge. */
    flex-wrap: wrap;
    gap: var(--s3);
    justify-content: center;
    padding: var(--s3) var(--s4);
    background: var(--surface);
    border-top: 1px solid var(--line);
    box-shadow: var(--shadow-up);
}
.bulk .plain { background: none; color: var(--accent); border-color: var(--line); box-shadow: none; }

/* Narrowing the queue. A plain GET form, so it survives a reload and keeps
   working with no script; it wraps rather than scrolling, because a phone in
   landscape is 640x360 and a row of three controls would run off the edge. */
.filters {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s3);
    margin: var(--s4) 0;
    padding: var(--s3) var(--s4);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r);
}
.filters label { display: flex; align-items: center; gap: 0.35rem; }
.filters input[type="checkbox"] { width: auto; min-height: 0; accent-color: var(--accent); }

/* A link that has to read as a button, because it sits in a row of them: the
   review screen's Skip, and the list page's Review. Never a form: skipping
   decides nothing, and a POST that decides nothing is a lie about what the
   button does. */
.button-ish {
    display: inline-block;
    min-height: var(--touch);
    padding: 0.7rem 1.1rem;
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    color: var(--accent);
    background: var(--surface);
    font-weight: 600;
    text-decoration: none;
    text-align: center;
}
.button-ish:hover { border-color: var(--accent); background: var(--accent-soft); text-decoration: none; }

/* ---------------------------------------------------------- the list's rows

   ONE ROW PER THING, closed. Two hundred and thirty-seven cards drawn in full
   is a page nobody reads to the end of; the row carries the picture, the name,
   the best idea and the two things a person does about it, and the rest is one
   click away. It wraps rather than scrolling: a laptop gets three columns and a
   phone gets three stacked blocks. */
.row {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: var(--s4);
}
.row-what { flex: 1 1 14rem; min-width: 0; }
.row-what h2 { margin: 0 0 0.25rem; font-size: var(--t-md); }
.row-what p { margin: 0.15rem 0; }
.row-where { font-size: var(--t-sm); }
.row-best { font-size: var(--t-base); overflow-wrap: anywhere; }
.row-do {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s3);
    margin-left: auto;
}
.row-do form { margin: 0; }

/* THE ROW'S OWN FOUR DECISIONS, side by side and visible without opening
   anything. ONE form holds three of them -- they share the token, the tag and
   the idea -- so it is the form that is the flex row rather than `.row-do`.
   `.plain` is the review screen's own quiet button, borrowed so that Accept is
   the one thing on a row that reads as the ordinary answer. */
.row-decide {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s2);
}
.row-decide .plain {
    background: var(--surface);
    color: var(--accent);
    border-color: var(--line);
    box-shadow: none;
}
.row-decide .plain:hover { border-color: var(--accent); background: var(--accent-soft); }
.strip-lead { margin: 0; flex: 0 0 auto; }
.row-card > details { margin-top: var(--s3); }
.row-card summary, .others summary { cursor: pointer; color: var(--accent); }
.others { margin: 0 0 var(--s4); }
.others .card { margin-top: var(--s3); }

/* ------------------------------------------------------ one thing per screen

   The review screen. One column, big controls, and the picture first: a person
   naming a thing is looking at the thing. */
.review { margin-top: var(--s4); }
.review .progress { margin: 0 0 var(--s2); }
.review h2.called { margin: var(--s4) 0 0.15rem; font-size: var(--t-lg); }
.review .where { margin: 0 0 var(--s4); }

/* THE PICTURE, as big as the column allows and no bigger. `max-height` is in
   viewport units so the buttons under it stay on screen on a laptop: a picture
   that pushes Accept below the fold is a picture that costs a scroll a time. */
.shot { margin: var(--s3) 0; }
.shot a { display: block; line-height: 0; }
.shot img {
    width: 100%;
    max-height: 52vh;
    object-fit: contain;
    border-radius: var(--r-lg);
    background: var(--raised);
    box-shadow: var(--shadow);
}
.review .strip li.is-on img { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The box the answer is typed in. It is the suggestion, already filled in, so
   Edit is just typing and there is no button to find first. */
.decide { margin: var(--s3) 0 0; }
.decide label { display: block; margin-bottom: 0.25rem; font-size: var(--t-sm); color: var(--quiet); }
.answer {
    width: 100%;
    min-height: var(--touch);
    font-size: 1.2rem;
    padding: 0.7rem 0.8rem;
}
.review .why { margin: var(--s2) 0 var(--s4); }
.review .also { display: block; margin: var(--s3) 0 0.25rem; cursor: pointer; }
.review .also input { width: auto; min-height: 0; margin-right: 0.5rem; accent-color: var(--accent); }
.review .also-says { margin: 0 0 var(--s3) 1.5rem; }
.review .keys { margin-top: var(--s2); font-size: var(--t-xs); }

/* THE WORDS PRINTED ON THE THING, under their own heading and never in the box
   above: that box renames the thing, and a description put in it was one press
   away from the wrong column. Its own block, its own verb. */
.review .described { margin: var(--s5) 0 0; }
.review .described h2 { font-size: var(--t-md); margin: 0 0 0.3rem; }
.described-answer {
    /* Not the name box's monospace, and no 255-character cap: a description is
       TEXT in both tables and a silent truncation is worse than a refusal.

       NOT SCOPED TO `.review`, because the list page's "or type your own
       description" is the same box asking the same thing. */
    font-family: var(--sans);
    min-height: 4.5rem;
}
.review .described-says { margin: 0 0 0.3rem; }

/* The other ideas about the same thing. Chips, because they are a choice and
   not a list: pressing one puts its words in the box above. */
.chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s2);
    list-style: none;
    margin: 0.25rem 0 var(--s3);
    padding: 0;
}
.chips a {
    display: inline-block;
    max-width: 100%;
    padding: 0.45rem 0.9rem;
    color: var(--ink);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 999px;
    text-decoration: none;
    overflow-wrap: anywhere;
}
.chips a:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }

/* Four big buttons, thumb-sized on a phone and side by side on a laptop. */
.buttons {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s3);
    margin: var(--s3) 0 var(--s2);
}
.buttons > * {
    flex: 1 1 8rem;
    min-height: var(--touch);
    font-size: var(--t-base);
}
.buttons .button-ish { line-height: 2rem; }
.buttons .plain { background: var(--surface); color: var(--accent); border-color: var(--line); box-shadow: none; }
.buttons .plain:hover { border-color: var(--accent); background: var(--accent-soft); }
.review .evidence summary { cursor: pointer; color: var(--accent); }

/* A NAME A PERSON STANDS BY. The mark is a small pill rather than a filled
   badge: `.badge` is the nav's count, which is a nudge about work waiting, and
   this is a quiet statement of fact about a thing that is already finished. In
   the good colour and never colour alone -- it carries the word "confirmed"
   itself, so a screen reader and a colour-blind reader lose nothing. */
.mark {
    display: inline-block;
    padding: 0.1em 0.6em;
    border: 1px solid var(--good);
    border-radius: 999px;
    background: var(--good-soft);
    color: var(--good);
    font-size: var(--t-xs);
    font-weight: 600;
    text-transform: lowercase;
    white-space: nowrap;
    vertical-align: middle;
}

/* WHAT KIND OF THING IT IS, AND WHAT IT IS IN (docs/categories.md section 6.1).
   One row of chips under a thing's name, each a LINK that narrows the list to
   itself, and each carrying its own word -- there is no colour here that means
   anything on its own, which is `.mark`'s rule read once more. It wraps rather
   than scrolling, because a category name is as long as the owner typed it.

   `.kind` borrows `.mark`'s shape and not its colour: a confirmed name is good
   news and a category is a fact, so this one is the ordinary ink on the raised
   surface. `.kind-type` takes the accent, because the type is the one of the two
   that is a partition the business agreed on.

   AND IT IS `.kind` AND NOT `.chip`, because `.chips` two blocks up is the
   review screen's own control -- a thumb-sized choice that puts words in a box
   -- and two classes one letter apart doing two different jobs is a stylesheet
   nobody can read. */
.kinds {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s2);
    margin: 0.3rem 0 0;
    /* A BLOCK FORMATTING CONTEXT, `.things li > .quiet`'s own reason: the
       thumbnail is floated and a plain block child would flow round it, so the
       chips of a short row would tuck themselves under the picture. */
    overflow: hidden;
}
.kind {
    display: inline-block;
    padding: 0.1em 0.6em;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--raised);
    color: var(--ink);
    font-size: var(--t-xs);
    text-decoration: none;
    white-space: nowrap;
}
.kind:hover { border-color: var(--accent); color: var(--accent); }
.kind-type { border-color: var(--accent); color: var(--accent); font-weight: 600; }

/* A one-control form that has to sit ON a line of prose rather than under it:
   the Confirm button beside the name, and the same button on the review screen.
   A `form` is a block by default and would take a line of its own. */
.inline { display: inline; }
.inline button { min-height: 0; padding: 0.2rem 0.7rem; font-size: var(--t-sm); box-shadow: none; }

/* The line under an item's name. It is often empty of everything but the
   button, so it keeps its own small gap rather than borrowing a paragraph's. */
.confirmed-line {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s2);
    align-items: baseline;
    margin: 0.25rem 0 var(--s2);
}
.confirmed-line strong { color: var(--good); }

/* On the review screen it sits between the buttons and the evidence, so it
   wants air above it and none below. */
.confirm-name { display: block; margin: var(--s3) 0 0; }

/* A NAME THIS WEBSITE CHOSE BY ITSELF. Nearly the opposite of `.mark`, so it
   takes the warning colour rather than the good one. */
.mark-auto { border-color: var(--bad); background: var(--bad-soft); color: var(--bad); }

/* A NAME THE OWNER SAID IS WRONG -- and the thing is still WEARING that name
   until a replacement arrives, so the warning colour is the honest one. Never
   colour alone: the mark carries its own words. */
.mark-wrong { border-color: var(--bad); background: var(--bad-soft); color: var(--bad); }
.confirmed-line strong[data-name-wrong] { color: var(--bad); }
.confirmed-line strong[data-looked-again] { color: var(--bad); }

/* A RELABEL IS NOT A WARNING. The system answered the question the owner asked,
   so the line reports work DONE and reads in the ordinary ink. */
.mark-relabelled { border-color: var(--line); background: var(--raised); color: var(--quiet); }

/* ------------------- the things nobody has tagged
   The drop zone at the top of the Things page, and the merge cards that ask
   whether a new tag is a thing this website already has photographs of. Both
   are plain blocks: the zone is a <details> and the card is an <article>, so
   neither needs a script to be usable. */
.dropzone {
    margin: 0 0 var(--s4);
    padding: var(--s3) var(--s4);
    background: var(--surface);
    border: 1px dashed var(--line);
    border-radius: var(--r);
}
.dropzone summary { cursor: pointer; font-weight: 600; }
.dropzone form { margin-top: var(--s3); }
.dropzone input[type="file"] { max-width: 100%; }
/* While a drag is over it. The colour change is the whole of the feedback, and
   it is only ever reached with a script running -- without one nothing drags. */
.dropzone form.over { outline: 2px solid var(--accent); outline-offset: 4px; }

/* THE CAMERA, which is the same form's second door and the one a phone uses.
   It is a <label> wrapping a file input that carries `capture`, so it has to
   LOOK like the button it behaves as -- and be the size of a thumb, because it
   is pressed once per photograph with one hand while the other holds the thing
   being photographed. Full width for the same reason: on a phone there is
   nothing to aim at. */
.camera {
    margin-top: var(--s3);
    padding-top: var(--s3);
    border-top: 1px solid var(--line);
}
.camera .take {
    display: block;
    min-height: var(--touch);
    padding: 1rem;
    border-radius: var(--r);
    background: var(--accent);
    color: var(--accent-ink);
    font-size: 1.1rem;
    font-weight: 600;
    text-align: center;
    cursor: pointer;
    box-shadow: var(--shadow);
}
/* The input inside it is off-screen rather than display:none, so it still takes
   focus; this is where that focus becomes visible. */
.camera .take:focus-within { outline: 2px solid var(--ink); outline-offset: 2px; }
.camera p { margin: var(--s2) 0 0; }
.camera .wifi label { cursor: pointer; }
/* The camera app is the SECOND door now and is drawn as one: the same shape as
   the button above it, in outline rather than filled. */
.camera .take.app {
    margin-top: var(--s3);
    background: var(--surface);
    color: var(--accent);
    border: 1px solid var(--line);
    box-shadow: none;
    font-size: var(--t-base);
}
.camera button.take { width: 100%; border: none; font-family: inherit; }

/* THE VIEWFINDER. A live picture with one big round button under it, held to a
   height that leaves the button on the screen: a shutter a thumb cannot reach
   without scrolling is a shutter pressed twice by accident. */
[data-viewfinder] { margin-top: var(--s3); }
[data-viewfinder] video {
    width: 100%;
    max-height: 60vh;
    background: #000;
    border-radius: var(--r);
    display: block;
}
[data-viewfinder] .shots { text-align: center; }
.camera .shutter {
    width: 72px;
    height: 72px;
    min-width: 72px;
    min-height: 72px;
    padding: 0;
    border: 4px solid #fff;
    border-radius: 50%;
    background: var(--accent);
    color: var(--accent-ink);
    font: 600 0.8rem/1.1 inherit;
    cursor: pointer;
    box-shadow: 0 0 0 2px var(--accent);
}
.camera .shutter:disabled { opacity: 0.5; cursor: default; }
/* WHAT A SHUTTER SOUND IS FOR, on a page that may not make one: the frame is
   already grabbed by the time this shows, so it reports rather than promises. */
[data-viewfinder].flashing video { filter: brightness(2.5); }
/* The thumbnail on the left and the way out on the right, and the way out stays
   on the right before there is a thumbnail to sit beside. */
[data-viewfinder] .after { display: flex; align-items: center; gap: var(--s4); }
[data-viewfinder] .last-shot {
    width: 96px;
    height: 96px;
    object-fit: cover;
    border: 1px solid var(--line);
    border-radius: var(--r);
}
[data-viewfinder] .done {
    margin-left: auto;
    min-height: var(--touch);
    padding: 0.7rem 1.4rem;
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    background: var(--surface);
    color: var(--accent);
    font: inherit;
    cursor: pointer;
}

/* "PLACES TO NAME" -- the clusters the station proposed, above the places list.
   A card like the merge question below, because it is the same kind of thing:
   something to answer rather than something to read. */
.proposals { margin: var(--s5) 0; }
.proposal {
    margin: 0 0 var(--s4);
    padding: var(--s4);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r);
    box-shadow: var(--shadow);
}
.proposal h3 { margin: 0 0 0.3rem; font-size: var(--t-base); }
.proposal label { display: block; margin: var(--s3) 0 0.25rem; }
.proposal input[type="text"] { width: 100%; max-width: 22rem; }
.proposal button { margin: var(--s3) var(--s2) 0 0; }

.merges { margin: var(--s5) 0; }
.merge {
    margin: 0 0 var(--s4);
    padding: var(--s4);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r);
    box-shadow: var(--shadow);
}
.merge h3 { margin: 0 0 0.3rem; font-size: var(--t-base); }
/* TWO GALLERIES SIDE BY SIDE, which is the whole of the card: it asks a
   question a person answers with his eyes. They fall under one another on a
   narrow screen rather than shrinking, because a thumbnail too small to
   recognise answers nothing. */
.merge-sides { display: flex; flex-wrap: wrap; gap: var(--s4); }
.merge-sides > div { flex: 1 1 16rem; min-width: 0; }

/* ======================================================= THE LANDING PAGE

   views/landing.php: what somebody sees who is not signed in, and since
   2026-11-02 the page that sells the thing. Still white, wide and mostly air,
   and still earned with type, space and this file alone -- no pictures, no
   script, no second request. One hero, then a stack of bands each holding one
   argument, then a tinted band with the ask in it, then the page's own foot. */

/* main holds a 48rem reading column; the landing page wants the width. */
main:has(> .landing) { max-width: 74rem; padding-left: 0; padding-right: 0; }

.landing { display: block; }
.landing-inner { max-width: 62rem; margin: 0 auto; padding: 0 1.5rem; }

.hero { padding: var(--s7) 0 var(--s6); text-align: center; }
.hero-mark { display: block; margin: 0 auto var(--s5); }
.hero-mark .wordmark { height: 2.2rem; margin: 0 auto; }

/* THE CATCHPHRASE IS THE LINE OVER THE HEADLINE: small, spaced, in the accent,
   in Nick's own lower case. It used to be the headline itself; the headline is
   now the claim, and the line sits above it the way a strapline sits over a
   masthead. */
.catchphrase {
    margin: 0 auto var(--s3);
    font-size: var(--t-sm);
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--accent);
}

.headline {
    margin: 0 auto var(--s4);
    max-width: 22ch;
    font-size: clamp(2rem, 6.2vw, 3.4rem);
    font-weight: 700;
    line-height: 1.1;
    letter-spacing: -0.03em;
    color: var(--ink-strong);
}

.what {
    margin: 0 auto var(--s5);
    max-width: 44ch;
    font-size: var(--t-lg);
    line-height: 1.55;
    color: var(--quiet);
}

/* TWO BUTTONS, AND THE FILLED ONE IS THE ASK. A stranger's job on this page is
   to write; signing in is the way in for somebody already set up, so it is
   drawn in outline beside it. With no address configured there is no ask, and
   the one button left takes the fill, so the hero never ends in an outline
   standing alone. */
.cta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: var(--s3);
    margin: 0;
}
.ask, .sign-in {
    display: inline-block;
    min-height: var(--touch);
    padding: 0.95rem 2.25rem;
    border-radius: var(--r);
    font-size: var(--t-md);
    font-weight: 600;
    text-decoration: none;
}
.ask, .cta .sign-in:only-child {
    background: var(--accent);
    color: var(--accent-ink);
    border: 1px solid var(--accent);
    box-shadow: var(--shadow-lg);
}
.ask:hover, .cta .sign-in:only-child:hover { background: var(--accent-hot); color: var(--accent-ink); }
.sign-in {
    background: var(--surface);
    color: var(--accent);
    border: 1px solid var(--line);
}
.sign-in:hover { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-hot); }

/* ONE BAND PER ARGUMENT. A hairline between bands and air inside them; the
   title is the one big thing in each, and it is left-aligned because a band is
   read, where the hero is looked at. */
.band { padding: var(--s6) 1.5rem; border-top: 1px solid var(--line-soft); }
.band-title {
    margin: 0 0 var(--s5);
    font-size: var(--t-xl);
    font-weight: 700;
    line-height: 1.2;
    letter-spacing: -0.02em;
    color: var(--ink-strong);
}
.band h3 { margin: 0 0 var(--s2); font-size: var(--t-md); font-weight: 600; color: var(--ink-strong); }
.band p { margin: 0; }

/* A PICTURE ON THE FRONT PAGE (View::picture): an <img> cropped to the box's
   shape, or -- where the file is missing -- a dashed box of the same shape
   with the picture's words inside it, so a gap is visible and says what
   belongs in it. `aspect-ratio` is the same on both, so nothing moves between
   the two. */
img.pic {
    display: block;
    width: 100%;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    margin: 0 0 var(--s4);
    border-radius: var(--r-lg);
    background: var(--raised);
    box-shadow: var(--shadow);
}
figure.pic {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--s2);
    margin: 0 0 var(--s4);
    padding: var(--s4);
    aspect-ratio: 4 / 3;
    background: var(--raised);
    border: 2px dashed var(--line);
    border-radius: var(--r-lg);
    text-align: center;
}
.pic-label {
    font-size: var(--t-xs);
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--accent);
}
.pic-prompt { font-size: var(--t-sm); line-height: 1.45; color: var(--quiet); max-width: 40ch; }

/* TAG, TRACK, FIND: three cards, a picture over each, the word big. */
.steps3 {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
    gap: var(--s4);
    list-style: none;
    margin: 0;
    padding: 0;
}
.step3 {
    padding: var(--s4);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow);
}
.step3 .pic { margin-bottom: var(--s3); }
.step3 h2 {
    margin: 0 0 var(--s2);
    font-size: var(--t-xl);
    font-weight: 700;
    letter-spacing: -0.02em;
    color: var(--accent);
}
.step3 p { color: var(--quiet); }

/* WHO IT IS FOR: three cards with the accent down the left edge, which is the
   one thing that tells them apart from the three above -- these are people. */
.customers {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
    gap: var(--s4);
    list-style: none;
    margin: 0;
    padding: 0;
}
.customer {
    padding: var(--s5) var(--s4);
    background: var(--surface);
    border: 1px solid var(--line);
    border-left: 4px solid var(--accent);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow);
}
.customer p { color: var(--quiet); }

/* WHAT NO ONE ELSE HAS: prose, in a reading measure, and the first paragraph
   set large and dark because it is the sentence the page is built on. */
.unclaimed { max-width: 62ch; }
.unclaimed p { margin: 0 0 var(--s4); color: var(--quiet); }
.unclaimed p:first-child {
    font-size: var(--t-xl);
    line-height: 1.3;
    font-weight: 700;
    letter-spacing: -0.02em;
    color: var(--ink-strong);
}
.unclaimed p:last-child { margin-bottom: 0; }

/* THREE FACTS IN A STRIP: a hairline between them on a laptop, stacked on a
   phone. No cards -- they are asides, not arguments. */
.facts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
    gap: var(--s4) var(--s5);
    list-style: none;
    margin: 0;
    padding: 0;
}
.fact { padding-left: var(--s4); border-left: 1px solid var(--line); }
.fact p { color: var(--quiet); font-size: var(--t-sm); }

/* THE AFTERNOON: the five steps and the box's picture side by side. */
.afternoon {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
    gap: var(--s5) var(--s6);
    align-items: start;
}
.afternoon .pic { margin: 0; }

/* THE AFTERNOON: five numbered steps wearing the same round marker as the
   cards, drawn by a counter so the number is never typed into the words. */
.steps {
    list-style: none;
    counter-reset: step;
    margin: 0 0 var(--s4);
    padding: 0;
}
.steps li {
    position: relative;
    counter-increment: step;
    padding: 0.45rem 0 0.45rem 3.25rem;
    font-size: var(--t-md);
    color: var(--ink);
}
.steps li::before {
    content: counter(step);
    position: absolute;
    left: 0;
    top: 0.35rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: 999px;
    background: var(--accent-soft);
    color: var(--accent);
    font-size: var(--t-sm);
    font-weight: 700;
}
.note { font-size: var(--t-base); color: var(--quiet); }

/* WHAT IT COSTS AND WHAT IS NOT IN IT YET, side by side on a laptop and one
   under the other on a phone. Smaller titles than a band's: these are two
   answers, not two arguments. */
.twin {
    display: grid;
    /* 20rem and not the cards' 16rem: with two children and a 16rem floor the
       62rem column fits a THIRD track, which auto-fit collapses to nothing but
       whose gap stays, and the pair sits off-centre by one gutter. */
    grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
    gap: var(--s5) var(--s6);
}
.twin h2 { margin: 0 0 var(--s2); font-size: var(--t-lg); }
.twin p { color: var(--quiet); }

/* THE ASK: a white band across the whole width, centred like the hero it
   echoes, with the same filled button. */
.ask-band {
    padding: var(--s6) 0;
    text-align: center;
    background: var(--surface);
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
}
.ask-band h2 { margin: 0 0 var(--s3); font-size: var(--t-xl); letter-spacing: -0.02em; }
.ask-band p { margin: 0 auto var(--s4); max-width: 56ch; color: var(--quiet); }
.ask-band .cta { margin: 0; }

.landing-foot {
    max-width: 62rem;
    margin: 0 auto;
    padding: var(--s4) 1.5rem var(--s6);
    color: var(--quiet);
    font-size: var(--t-sm);
    /* Centred, because the hero above it is: a left-aligned footer under a
       centred page reads as a second page that got attached by mistake. */
    text-align: center;
}
.landing-foot p { margin: 0.25rem 0; }

/* ------------------------------------------------------------------ narrow */

@media (max-width: 48rem) {
    header { gap: var(--s2) var(--s4); padding: 0.85rem 1rem; }
    nav { width: 100%; margin-left: 0; }
    main { padding-left: 1rem; padding-right: 1rem; }
    footer { padding-left: 1rem; padding-right: 1rem; }
    .landing-inner, .landing-foot { padding-left: 1rem; padding-right: 1rem; }
    .hero { padding: var(--s6) 0 var(--s5); }
    .band { padding-top: var(--s5); padding-bottom: var(--s5); }
    .ask-band { padding: var(--s5) 0; }
}

@media (max-width: 30rem) {
    /* A phone: one thing under another, and nothing shoulder to shoulder. */
    .row-do { margin-left: 0; width: 100%; }
    .row-do > * { flex: 1 1 8rem; }
    /* The three decisions take a row of their own and share it evenly: at
       360 dp they do not fit beside the Review link, and a button squeezed to
       its own ellipsis is a button nobody presses on purpose. */
    .row-decide { flex: 1 1 100%; }
    .row-decide > button { flex: 1 1 6rem; }
    .buttons > * { flex: 1 1 100%; }
    /* The two buttons one under the other, each the width of a thumb's reach. */
    .ask, .sign-in { display: block; width: 100%; text-align: center; }
    .cta { gap: var(--s3); }
    /* The paragraph under the headline is four sentences; at 360 dp and
       --t-lg it ran to six lines and pushed the buttons under the fold. */
    .what { font-size: var(--t-md); }
    .steps li { font-size: var(--t-base); }
    .fact { padding-left: var(--s3); }
    .code { font-size: 1.35rem; letter-spacing: 0.12em; }
    .tally { gap: var(--s3) var(--s5); }
}

/* ------------------------------------------- the row of narrowings

   THE QUEUE NARROWS ONE WAY AT A TIME, and the row says which way it is
   narrowed now. Nick, 2026-10-14: "dont use tick boxes, use selection
   buttons" -- so the three doors and the four tick boxes are one row of
   buttons inside the filter form, and the pressed one is filled in.

   IT TAKES A LINE OF ITS OWN inside `.filters` (`flex: 1 1 100%`) rather
   than sitting shoulder to shoulder with the two pickers: five labels and
   two selects on one line is a row that wraps into a muddle, and the
   question the row answers -- WHICH of these am I looking at -- is a
   different question from "and narrow it further by word".

   A PILL AND NOT A BLOCK, borrowing `.kind`'s shape from the item rows so a
   set of choices looks like a set of choices everywhere on this site; it
   keeps the full `--touch` height a button has, because a thumb presses
   these. */
.narrowings { display: flex; flex-wrap: wrap; gap: var(--s2); flex: 1 1 100%; }
.narrowing {
    padding: 0.4rem 1rem;
    background: var(--surface);
    color: var(--accent);
    border: 1px solid var(--line);
    border-radius: 999px;
    box-shadow: none;
    line-height: 1.25;
}
.narrowing:hover { background: var(--accent-soft); border-color: var(--accent); }
/* THE PRESSED ONE IS FILLED IN, and that is the whole of the state: a row
   where the chosen one is only outlined is a row nobody can read at a
   glance, which is the fault the tick boxes had. */
.narrowing[aria-pressed="true"] {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-ink);
}
.narrowing[aria-pressed="true"]:hover { background: var(--accent-hot); border-color: var(--accent-hot); }
/* Which end to start from, under the label it belongs to. */
.narrowing .quiet { display: block; font-size: var(--t-xs); font-weight: 400; }
.narrowing[aria-pressed="true"] .quiet { color: inherit; opacity: 0.8; }

/* AND THE SAME ROW OVER THE LIST (Nick, 2026-10-14: "replace drop down boxes
   with something more fluid"; scope, his own, "Just the list"). One row, drawn
   to look like the queue's to the pixel, so a person who has read one has read
   the other -- which is why this block APPENDS to the one above rather than
   starting a second set of chip rules.

   LINKS AND NOT SUBMIT BUTTONS, and that is the whole difference. On the queue
   the narrowings are exclusive, so a GET button that sends itself and nothing
   else is exactly right. On the list they COMBINE -- what to show, AND what
   kind, AND which category, AND the word in the box -- and a button would send
   itself and quietly drop the other three. Each chip is therefore an `<a>` the
   view builds through `$link()`, which keeps every narrowing the reader is
   already under.

   SO THE STATE IS `aria-current` AND NOT `aria-pressed`: a link is not a
   control that stays down, and "this is the one you are looking at" is what
   `aria-current` says about a set of links. The filled-in look answers to
   both, because it is the same look.

   `More…` IS A `<summary>` AND TAKES THE SAME PILL. A chip that opens the rest
   of the row is still a chip, and the marker is dropped so it cannot be the
   one chip with a triangle in it. */
a.narrowing, summary.narrowing, span.narrowing {
    /* A BUTTON'S OWN HEIGHT AND A BUTTON'S OWN WEIGHT, because a thumb presses
       these too: `inline-flex` and not `inline-block`, or `min-height` would
       leave the word sitting at the top of a tall pill. The size and the family
       come down from the page, which is where `font: inherit` puts the queue's
       buttons, so the two rows cannot drift apart by a tenth of a rem. */
    display: inline-flex;
    align-items: center;
    min-height: var(--touch);
    font-weight: 600;
    text-decoration: none;
    list-style: none;
}
a.narrowing, summary.narrowing { cursor: pointer; }
/* THE ONE CHIP THAT IS NOT A LINK: the place the list is narrowed to. Its way
   out is `Anywhere` standing beside it, so this one is a statement of where
   the reader is and never a control -- no pointer, and no hover. */
span.narrowing { cursor: default; }
summary.narrowing::-webkit-details-marker { display: none; }
a.narrowing:hover, summary.narrowing:hover { background: var(--accent-soft); border-color: var(--accent); }
.narrowing[aria-current="true"] {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-ink);
}
a.narrowing[aria-current="true"]:hover { background: var(--accent-hot); border-color: var(--accent-hot); }

/* WHAT THE `More…` CHIP OPENS, under the row and not over it. The fold sits in
   the row as one more chip while it is shut, so the row is the row; opened, its
   rest hangs below that chip rather than pushing the four in front of it
   anywhere. The chips inside it are the same chips -- `.kind`'s shape, the
   pressed one filled in -- because they are the same question, further down the
   same list. `flex-start` so a fold standing open does not stretch every chip
   beside it to its own height -- and on `.search .narrowings` ALONE, because the
   queue's buttons wear a second line each and stretching them to one height is
   right there. */
.search .narrowings { align-items: flex-start; }
.search .narrowings > details { max-width: 100%; }
.more-narrowings {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s2);
    margin-top: var(--s2);
}

/* THE PROVISIONING SQUARE (views/devices.php), WHICH NOTHING MAY SHRINK. A QR
   code read off a screen by a handheld's camera is the one picture on this site
   whose size is a function and not a taste: `Qr::svg` sets width and height
   ATTRIBUTES -- 565 px for the version-22 provisioning payload -- and these two
   lines exist so that a future `img, svg { max-width: 100% }` reset cannot
   quietly take that away, and so a window narrower than the square scrolls THIS
   BOX rather than pushing the whole page sideways (the worry the `.hex` line
   below it already answered for the long token). The rule lives here and not in
   a style attribute because the policy is `style-src 'self'` (Http::
   CSP_DEFAULT) and an inline style would simply be dropped. */
.square { overflow-x: auto; margin: 0 0 var(--s4); }
.square svg { max-width: none; }

/* THE TITLE THAT RENAMES THE THING (assets/rename.js, 2026-11-09). The heading
   is a <label>, so it must LOOK pressable without looking like a button: the
   underline is the whole affordance, and it is dotted rather than solid so it is
   not read as a link to somewhere else. `cursor: text` says what clicking does
   -- it puts a cursor in a name -- where `pointer` would promise navigation. */
.title-edit label {
    cursor: text;
    text-decoration: underline dotted var(--line, currentColor);
    text-decoration-thickness: 1px;
    text-underline-offset: 4px;
}
.title-edit label:hover { text-decoration-style: solid; }

/* AND THE BOX IT BECOMES, which stands where the heading stood: the field wears
   the heading's own size so the name does not jump as it turns into something
   typable, and the two sit on one row with the button, wrapping on a narrow
   phone rather than squeezing the field to nothing. */
.rename-here {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s2);
    align-items: center;
    margin: 0 0 var(--s4);
}
.rename-here input[type="text"] {
    flex: 1 1 12rem;
    min-width: 0;
    font-size: inherit;
}
