@font-face {
  font-family: 'TapeOSD';
  src: url('/tape-osd.ttf') format('truetype');
  font-display: swap;
}
/* THE WORLD'S TWO FACES, SELF-HOSTED, SUBSET TO LATIN. Fetched once from the
   Google Fonts repository (SIL OFL 1.1, licences beside the files), instanced
   and subset with fontTools, and served from assets/fonts/ under the same
   font-src self as the tape's own face. No network font, ever: the CSP does
   not change and a page that has been handed a photograph makes no third-party
   request. Anton is the display face and is never set below 18px. */
@font-face {
  font-family: 'Anton';
  src: url('/fonts/anton.woff2') format('woff2'), url('/fonts/anton.ttf') format('truetype');
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-400.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-600.woff2') format('woff2');
  font-weight: 600;
  font-display: swap;
}

:root {
  /* THE WORLD: A LIME POSTER ON A DARK GROUND (DESIGN.md, 2026-09-06). One
     ground, one accent, one light. Every ratio is measured with the WCAG
     formula and recomputed by test/web-static.test.js from the values below,
     so the table in DESIGN.md is an output and cannot drift. */
  --ground: #161618;       /* the page, everywhere                               */
  --card: #1F1F22;         /* an outlined card on the ground        1.10:1 surface */
  --lime: #D9FF00;         /* chosen, go, display    15.71:1 ground, 14.29:1 card  */
  --lime-hover: #E9FF5C;   /* the same lime pressed, on hover                     */
  --ink: #F2F2F0;          /* body prose             16.12:1 ground, 14.67:1 card  */
  --ink-soft: #A9A9A6;     /* labels, hints, fine     7.67:1 ground,  6.98:1 card  */
  --on-lime: #141414;      /* headings, buttons, body on lime      16.01:1 on lime */
  --on-lime-soft: #3A3A3A; /* fine print on lime                    9.89:1 on lime */
  /* THE RECORD LIGHT, and nothing else is red. The spec's floor value clears
     4.5:1 on the ground and not on a card (4.15:1), and the status page's
     phase rows are cards; this value clears both -- 5.01:1 and 4.55:1. */
  --rec: #E85545;
  --line: rgba(242, 242, 240, 0.14);  /* the 1px outline on cards and fields  */

  /* TEXT ON A PHOTOGRAPH IS STILL TEXT ON A PHOTOGRAPH. Measured in the paper
     world against a pure white picture under the caption scrim; the picture
     is the same picture, so these did not move. Struck on an image is lime. */
  --on-image: #FAF7F2;
  --on-image-soft: #CFC7BC;
  --on-image-accent: var(--lime);

  /* THE ALIASES THAT STAY. Several hundred rules and the generated per-catalog
     block read --accent and --faint, and --alarm names the one decision that
     there is no alarm red; they are re-pointed here once and never rewritten.
     Seven other names were retired on 2026-09-07 -- lift, ink-strong, frost,
     frost-lit, muted, hairline, hairline-firm -- because each resolved to one
     token every rule could name directly, and a name that maps to one value
     is a second place to decide a colour. A test fails if any of them
     reappears. The paper and oxide names are GONE, not aliased. */
  --accent: var(--lime);
  --accent-bright: var(--lime-hover);
  --accent-deep: var(--lime);
  --faint: var(--ink-soft);
  --alarm: var(--ink);          /* there is no alarm red: weight and words */

  /* SHAPE. Cards and fields 10-12px, buttons 6-8px (DESIGN.md). */
  --r: 12px;
  --r-sm: 10px;
  --r-btn: 7px;

  /* THE GHOST FLOOR ON THIS GROUND. --ink at .5 over --ground measures
     4.83:1 and over --card 4.70:1; .48 is the least that clears the card and
     .5 is the round number above it. The rule is the paper world's: a ghost
     sits at the floor and no lower, and nothing inside a ghosted control is
     written in the soft tier. */
  --ghost: 0.5;
  --ghost-hover: 0.82;

  /* THE SPACING SCALE. Before this existed the signed-in page used 34 distinct
     rem values for margin, padding and gap -- 18 of them inside a 14.4px span,
     and seven stepping by 0.8px, which is neither perceptible nor a decision.
     Structural rhythm now comes from these eight steps and nothing else.
     A 4px base, not 8: the useful middle steps (12, 24) are exactly the ones an
     8-only scale misses, and this page needs both a tight interval and a
     generous one to tell a group boundary from a within-group one. */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 24px;
  --s-6: 32px;
  --s-7: 48px;
  --s-8: 64px;

  /* THE TYPE SCALE, and the reason it did not exist until 2026-08-31.
     The spacing scale above was built because the page used 34 distinct rem
     values and seven of them stepped by 0.8px. Type had exactly the same
     disease and nobody measured it: the sheet carried clamp(29,4.4vw,40),
     clamp(44,7.4vw,104), clamp(20,2.4vw,29), clamp(26,3.6vw,44), then bare
     15px, 25px and 16px. No ratio, no named steps, and no relationship between
     any two of them. Ordered spacing on unordered type is why the pages read
     as tidy rather than as composed.

     A MINOR THIRD, 1.2, ON A 16px BASE. The restrained ratio on purpose: a
     major third (1.25) compounds to 31px by the third step and starts shouting
     where this world whispers. 1.2 gives more usable middle steps, which is
     what an editorial page actually needs -- the same argument the 4px spacing
     base won on.

     THE BODY MOVES 15px -> 16px AND THAT IS A FIX, NOT A PREFERENCE. §6c of
     CLAUDE.md found it, named the consequence, and deliberately left it:
     "changing it reflows every page in the product. Typography, not layout --
     not done unasked." This is the pass where typography IS the subject. It
     also removes iOS input auto-zoom, which fires on any input below 16px and
     silently breaks every form on this site.

     Small steps are fixed and large steps are fluid, because only the large
     ones have anywhere to go on a phone. */
  /* ONE SIZE FOR EVERY LABEL, AND THIS IS THE WORST OF THE FOUR FINDINGS.
     The uppercase tracked label -- the eyebrow, the step key, the flag, the
     unit, the tick, the "not yet" -- is ONE role, and the sheet was setting it
     at 9px, 10px, 11px and 12px depending on which component you landed in.
     Four values for one job, none of them a decision, and the 9px one
     (.stepno-k) was the smallest type in the product. Uppercase and tracked
     reads larger than its px size, which is why nobody noticed and why the
     answer is one honest value rather than a bigger one. */
  --t-label: 12px;                      /* every uppercase tracked label      */
  --t-1: 13px;                          /* fine print, legal, captions        */
  --t-2: 16px;                          /* body prose -- the base             */
  --t-3: 19px;                          /* lede                               */
  --t-4: 23px;                          /* h3                                 */
  --t-5: clamp(23px, 2.2vw, 28px);      /* h2                                 */
  --t-6: clamp(28px, 3.2vw, 33px);      /* h1, interior pages                 */
  --t-7: clamp(33px, 4.4vw, 40px);      /* h1, the app page                   */
  --t-8: clamp(40px, 6vw, 48px);        /* display, sub-hero                  */
  --t-hero: clamp(48px, 8vw, 96px);     /* the landing hero, ONCE per site    */

  /* SIZED OFF ITS OWN COLUMN, NOT THE VIEWPORT. 18vw read the landing's wide
     column correctly and clipped on every page whose content sits narrower
     than the glass -- the giant word cut mid-glyph on the pricing page, whose
     column tops out well short of the viewport at a laptop width. .foot is a
     query container (see its rule below) and cqw is 1% of THAT column, so
     the word can never outgrow the box that clips it.

     60px, not 64px: measured against the actual glyphs -- the narrowest
     column this site renders (the 320px width, on every page) is under 284px
     wide, and TIMESTAMP. at 64px alone is already wider than that. 60px is
     the largest floor that still leaves room. 21cqw was picked by rendering
     the word at every width on both the landing and the pricing page and
     reading its painted range back; the ceiling still lands the landing's
     widest column on exactly 240px, unchanged. */
  --t-mark: clamp(60px, 21cqw, 240px);  /* the footer's giant word, once per site */

  /* THE DISPLAY LADDER IS SEPARATE, AND IT HAS TO BE. Anton is a heavy
     condensed face: at the same pixel size it carries far more weight and far
     less width than Inter beside it, so one shared ladder would either make
     every heading shout or leave every readout timid. Same 1.2 ratio, shifted
     up a step. Uppercase, line-height 0.9-0.92, and NO tracking -- the face is
     drawn tight and letter-spacing on it opens holes in a word. Never below
     18px, which is why --d-1 is the one rung this ladder does not set in
     Anton. */
  --d-1: 15px;                          /* OSD labels, step numerals, hints   */
  --d-2: 18px;                          /* readouts in prose                  */
  --d-3: clamp(22px, 2.4vw, 26px);      /* card titles                        */
  --d-4: clamp(26px, 3.6vw, 32px);      /* section headings                   */

  /* THE THREE FACES. Anton for display, never below 18px; Inter for
     everything read; VT323 only where the interface depicts the tape. */
  --sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  --display: 'Anton', Impact, 'Arial Narrow', sans-serif;
  --osd: 'TapeOSD', ui-monospace, 'Courier New', monospace;
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  min-height: 100dvh;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--t-2);
  line-height: 1.6;
  padding: 0 1.15rem 5rem;
}

/* --- the full-bleed background ----------------------------------------- */

/* Before a place is picked this is the warm near-black on its own. Each place
   layer sits on top at opacity 0 and is faded in by its own ":checked" rule, so
   the cross-fade is a CSS transition between two layers rather than a swap. */
.bgs { position: fixed; inset: 0; z-index: -2; background: var(--ground); overflow: hidden; }

.bg {
  position: absolute;
  inset: -6%;
  opacity: 0;
  background-size: cover;
  background-position: center;
  filter: blur(26px) saturate(0.72);
  transform: scale(1.08);
  transition: opacity 900ms ease;
  animation: drift 46s ease-in-out infinite alternate;
}

@keyframes drift {
  from { transform: scale(1.08) translate3d(-1.2%, -0.8%, 0); }
  to   { transform: scale(1.13) translate3d(1.2%, 0.9%, 0); }
}

/* THE SAME PICTURE, MOVING. One element for eight places -- the script swaps
   its source -- and it sits over the still it was cut from, so a loop that
   never arrives is invisible rather than a hole.

   IT IS BARELY BLURRED, AND THAT IS THE POINT OF THE WHOLE EXERCISE. The still
   underneath carries blur(26px), which is correct for a colour wash and is
   exactly what stops anybody recognising where they are. 3px is enough to keep
   text off the detail edges while the place stays a place. It does NOT drift:
   the loop already drifts, in ffmpeg, on a sine that returns to its origin, and
   a CSS animation on top would beat against it. */
.bgv {
  position: absolute;
  inset: -6%;
  width: 112%;
  height: 112%;
  object-fit: cover;
  opacity: 0;
  filter: blur(3px) saturate(0.86);
  transition: opacity 1200ms ease;
}
.bgs.is-showing .bgv { opacity: 1; }

/* The scrim. Heavy, and heavier at the top where the wordmark sits. */
.scrim {
  position: fixed; inset: 0; z-index: -1; pointer-events: none;
  /* Matched to the video's own fade, so the ground and the picture over it
     arrive together instead of the scrim snapping off first. */
  transition: opacity 1200ms ease;
  /* Painted from SCRIM_PAINT, the constants the solver models, so the stops
     cannot have two homes again. */
  background:
    linear-gradient(180deg, rgba(11,10,9,0.92) 0%, rgba(11,10,9,0.74) 34%, rgba(11,10,9,0.86) 100%),
    radial-gradient(120% 70% at 50% 0%, rgba(11,10,9,0.20) 0%, rgba(11,10,9,0.80) 100%);
}

/* The radios live at the top of <body> so that ":checked ~ .bgs" and
   ":checked ~ .wrap" can reach the background and the cards. They are still
   part of the form, via the "form" attribute on each input. Hidden without
   "display:none", which would take them out of the tab order entirely.

   POSITION IS "fixed" AND THAT IS THE WHOLE BUG FIX, so do not "tidy" it back
   to "absolute". Clicking a <label for> focuses its input, and a browser
   scrolls a newly focused element into view. With "absolute" these inputs sit
   at the top of the DOCUMENT -- offset -1 -- so choosing 720p at step 4 threw
   the page back up to step 1, measured at 1641px, every single time. "fixed"
   positions them against the VIEWPORT instead, so they are always already in
   view and there is nothing to scroll to. Verified by measuring scrollY across
   a real click; see test/web-static.test.js.

   pointer-events:none keeps the 1px boxes from swallowing a click at the very
   top-left corner of the page. It does not affect keyboard focus, so the tab
   order this block exists to preserve is untouched. */
.statehook {
  position: fixed;
  top: 0; left: 0;
  pointer-events: none;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
  .bg { animation: none; transition: none; transform: scale(1.08); }
}

.wrap { max-width: 44rem; margin: 0 auto; position: relative; z-index: 1; }
.wrap--narrow { max-width: 25rem; }

/* --- masthead ---------------------------------------------------------- */

.masthead {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
  padding: 2rem 0 2.25rem;
}

/* THE WORDMARK IS THE WORD, SET IN THE DISPLAY FACE. It lays out the word and
   the record light beside it on one baseline; the dot is sized in em so it
   tracks the word rather than needing a number of its own. */
.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  font-family: var(--display);
  font-size: var(--t-4);
  line-height: 1;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  color: var(--ink);
  text-decoration: none;
}
.wordmark .rec { display: inline-block; width: 0.32em; height: 0.32em; border-radius: 50%; background: var(--rec); }

/* THE MONOGRAM'S TWO RULES LIVED HERE AND WENT WITH IT ON 2026-08-28.
   One lifted it 3.37px so the two marks shared a baseline rather than a tear,
   and held it to 60% opacity; the other dropped that to 45% over a photograph,
   because at 60% the mark out-shouted the record light on seven of the eight
   place loops. Both were measurements rather than taste, and both are only
   worth re-deriving if the mark ever comes back.
   THE RULE THEY PROTECTED OUTLIVES THEM AND IS NOT ABOUT THE MONOGRAM: the
   record light is the one thing in this chrome wearing red, and it is a few
   pixels across. Anything larger painted in the same value stops being a
   record light and becomes a second accent. See DESIGN.md. */

/* THE RECORD LIGHT BESIDE THE WORD IS STILL (the owner's call, 2026-09-07).
   It blinked on every page from 2026-08-20 until the lime world, where the
   near-black ground and the poster face made the pulse read as a glitch, or
   as the web's own idiom of a red dot beside a name meaning "live" -- to the
   person who built the product, which is the evidence. The dot stays: it is
   the palette's one light and the one trace of the camcorder in the chrome.
   The blink now lives ONLY on the status page's phase row (.reclight below),
   where recording is actually happening, and it means something there again
   because the masthead no longer competes with it. The pulse this rule used
   to carry bottomed at .45 rather than .12, because at .12 a dot inside a
   lockup reads as a rendering fault for half of every cycle; worth knowing
   only if the blink ever comes back. Guarded in web-static and browser-smoke. */

/* min-width: 0 IS NEEDED ON BOTH LEVELS OR IT IS NEEDED ON NEITHER. The nav is
   itself a flex item inside .masthead, and a flex item defaults to
   min-width: auto -- so without the 0 here the nav is handed its full content
   width, .who is never squeezed, and the ellipsis it was given below never
   fires. Measured with the fix on .who alone: still 44px of page overflow at
   375px. The chain has to give way at every link. */
/* IT WRAPS, BECAUSE AT 375px THERE IS NOTHING LEFT TO SHRINK (2026-08-31).
   Adding "My videos" put the row 61px over a 375px viewport, and the measured
   parts say why no amount of tightening fixes it: with a 58-character address
   the email is ALREADY at 0px -- it is the only item allowed to give way and it
   has given way completely -- leaving credits 75.4 + My videos 76.4 + Plans
   44.2 + Account 66 + Sign out 67.1 plus five 17.6px gaps, which is about 417px
   of controls. Six items do not fit on one phone line at any sane type size.
   Wrapping is the one answer that loses nothing: no control is hidden, no type
   shrinks, and §36B's guarantee -- that Sign out is never carried off the right
   edge -- is kept by taking a second line rather than by pushing something out
   of the frame. min-width: 0 stays: it is what lets the email ellipsise before
   the row is forced to wrap at all, so wider screens still get one line. */
.nav { display: flex; align-items: center; gap: 1.1rem; min-width: 0; }

/* AND IT WRAPS ONLY ON A PHONE. flex-wrap cannot be the default here: wrapping
   is decided BEFORE shrinking, so a wrapping row lets the 58-character address
   take its full natural width and pushes the links onto a second line at 1440px
   as readily as at 375px -- measured, two rows on a laptop, which is not a fix,
   it is a different bug. Below 30rem there is genuinely no single-line answer
   (see the arithmetic above), and above it the ellipsis has always been enough.
   30rem sits between two of the six widths this layout is tested at -- 414 and
   768 -- so no tested width lands on the boundary. */
@media (max-width: 30rem) {
  .nav { flex-wrap: wrap; gap: 0.5rem 1.1rem; }
}
.nav a, .nav button {
  background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; font-size: var(--t-label); font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--faint); text-decoration: none; flex: none;
}
.nav a:hover, .nav button:hover { color: var(--ink); }
/* A FLEX ITEM THAT WRAPS A CONTROL MUST NOT BRING ITS OWN STRUT. Sign out is a
   <button> in a <form> because signing out is a POST, so the item this row
   lays out is the FORM and not the button. 'display: inline' here was
   blockified by the flex container into 'display: block' -- a block box whose
   line box carries the inherited 16px strut while the button inside it is
   12px. Measured on /pricing at 1440px: the links were 19.19px tall, this form
   25.59px, and Sign out sat 1.8px BELOW Plans and Account. Small, and plainly
   visible on 12px uppercase type at 0.14em; the owner reported it on
   2026-08-31. As a flex container the form has no strut of its own and
   collapses to the button's height, so .nav's align-items centres a box that
   is finally the same size as its neighbours. Re-measured after: 0.00px of
   spread across all three controls. A browser test asserts it, because this
   is invisible to every markup test in the suite. */
.nav-form { display: flex; align-items: center; margin: 0; flex: none; }
/* THE EMAIL IS THE ONLY ITEM IN THIS ROW WHOSE WIDTH THE CUSTOMER CHOOSES, so
   it is the only one allowed to give way. A flex item defaults to
   min-width: auto and refuses to shrink below its text, so without the 0 here
   a long address widens the whole nav past the viewport and carries Sign out
   off the right-hand edge -- measured at 375px, an ordinary 33-character
   address put the only way out of the account outside the frame. Everything
   else in the row is a control and holds its size (flex: none above). */
.nav .who {
  color: var(--ink); text-transform: none; letter-spacing: 0; font-size: var(--t-1);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* --- the credit meter -------------------------------------------------- */
/* A ring that empties as credits are spent. The FRACTION is carried on the
   <circle> as stroke-dasharray, a presentation attribute rather than an inline
   style, because style-src 'self' has no 'unsafe-inline' and must not gain
   one for a progress ring. See creditMeter() in views.mjs.

   Only the COLOURS live here; nothing in this block knows the percentage. */
.creds {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  text-transform: none;
  letter-spacing: 0;
  font-size: var(--t-1);
  color: var(--ink);
}
.ring { width: 20px; height: 20px; flex: none; overflow: visible; }
.ring-track {
  fill: none;
  stroke: var(--line);
  stroke-width: 2.5;
}
.ring-fill {
  fill: none;
  stroke: var(--faint);
  stroke-width: 2.5;
  stroke-linecap: round;
  /* Start the arc at twelve o'clock instead of three, so it drains the way a
     dial does. Rotating the geometry rather than the <svg> box keeps the
     element's layout square and its focus ring where it belongs. */
  transform: rotate(-90deg);
  transform-origin: 50% 50%;
  transition: stroke-dasharray 420ms cubic-bezier(0.4, 0, 0.2, 1);
}
.creds-n { font-variant-numeric: tabular-nums; color: var(--ink); }
.creds-u { color: var(--faint); font-size: var(--t-label); }

/* Two of the cheapest tape or fewer: worth noticing, not yet a problem. It
   takes plain ink rather than the accent, because low credits are a fact and
   the accent in this world means chosen or go. */
.creds--low .ring-fill { stroke: var(--ink); }
.creds--low .creds-n { color: var(--ink); }

/* Cannot afford anything at all. A different fact from "low", and it gets a
   different colour, because a thin arc reads as "probably enough" and finding
   out otherwise costs a click and a refusal. */
.creds--spent .ring-fill { stroke: var(--alarm); }
.creds--spent .creds-n { color: var(--alarm); }
.creds--spent .ring-track { stroke: color-mix(in srgb, var(--alarm) 25%, transparent); }

@media (prefers-reduced-motion: reduce) {
  .ring-fill { transition: none; }
}

/* --- type -------------------------------------------------------------- */

/* The label role: the only uppercase body text in the world, and the one
   size every label takes. Inter 600, tracked, in the soft tier. */
.eyebrow {
  font-size: var(--t-label);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.22em;
  color: var(--faint);
  margin: 0 0 var(--s-2);
}

/* Headings are the display face, uppercase, tight, untracked. */
.headline { font-family: var(--display); font-size: var(--t-5); line-height: 0.92; letter-spacing: 0; text-transform: uppercase; font-weight: 400; margin: 0 0 var(--s-3); }
.title { font-family: var(--display); font-size: var(--t-4); line-height: 0.95; letter-spacing: 0; text-transform: uppercase; font-weight: 400; margin: 0 0 var(--s-2); }
/* MEASURED AT 82ch ON /privacy BEFORE THIS CAP, on the longest prose in the
   product. §6c adopted the 65-75ch band from the UX guideline set and moved the
   landing lede from 62ch to 66ch to satisfy it; the rule never reached ordinary
   body copy, so the legal pages -- the one place somebody actually reads several
   hundred words in a row -- were the worst offender. A cap only ever narrows,
   so this is safe on the pages where .sub already sits in a column. */
.sub { color: var(--ink); margin: 0 0 0.75rem; max-width: 66ch; }

/* THE LEGAL PAGES ARE THE ONLY LONG-FORM DOCUMENTS IN THIS PRODUCT, AND THEY
   WERE STRUCTURED LIKE A MARKETING PANEL. Their section headings were
   <p class="eyebrow"> -- 12px uppercase tracked labels -- so /privacy shipped
   SEVEN headings that were not headings and a document outline consisting of
   one h1. For a page whose whole job is to be read and understood, that is an
   accessibility defect before it is a design one: a screen reader got no
   structure for a privacy policy.
   They are h2 now, and they stop borrowing the eyebrow's voice: a label
   announces a field, a heading opens a section, and at 23px in sentence case
   this reads as the second one. The three page kickers that sit ABOVE the h1
   stay <p class="eyebrow"> -- an h2 before the h1 would be worse structure
   than none. */
.legal-h {
  font-family: var(--display);
  font-size: var(--t-4);
  text-transform: uppercase;
  letter-spacing: 0;
  font-weight: 400;
  color: var(--ink);
  margin: var(--s-7) 0 var(--s-3);
}

/* THE LEGAL PAGES ARE DOCUMENTS, NOT CARDS (2026-09-07, spec §6: "Dark ground,
   Anton heading, Inter body at reading measure, no lime panel"). Several
   hundred words read top to bottom have no edges to box; they sit on the
   ground at the 66ch measure .sub already imposes on their paragraphs, under
   a title at the page size. The error trio -- one sentence and a button -- is
   exactly what a card is for and keeps .panel. */
.legal { max-width: 66ch; }
.page-legal .headline { font-size: var(--t-7); text-wrap: balance; }
.hint { color: var(--faint); font-size: var(--t-1); margin: 0 0 0.7rem; }
.lede { color: var(--ink); margin: 0 0 2rem; }

.stamp {
  font-family: var(--osd);
  color: var(--ink-soft);
  letter-spacing: 0.16em;
  font-size: var(--t-1);
  margin: 0 0 1rem;
}

/* THE TWO BANNERS LOST THEIR 2px BARS, and that is the one rule of this world
   catching up with two elements that had been quietly breaking it.
 *
 * A 'border-left: 2px solid' is a rule. It is the exact device DESIGN.md
 * forbids -- a line drawn to separate the banner from the page -- and it
 * survived every previous pass because the border sweep can only see a LITERAL
 * colour in a border declaration, and these two were written against tokens.
 * On the near-black ground they were also dim enough to overlook. On paper an
 * oxide bar down the side of a banner is the loudest thing on the page.
 *
 * WHAT CARRIES THE DISTINCTION NOW THAT NEITHER A LINE NOR A SECOND HUE CAN.
 * There is no alarm red in this world -- red is the record light and nothing
 * else -- so an error is carried by WEIGHT and by WORDS: the alert is an
 * outlined card whose text is set in the label weight, and the notice is the
 * same card in ordinary prose. That survives greyscale, which two similar
 * reds never did. */
.alert {
  background: var(--card);
  color: var(--ink);
  font-weight: 600;
  border: 1px solid var(--line);
  padding: 0.75rem 0.95rem;
  margin: 0 0 1.5rem;
  font-size: var(--t-1);
  border-radius: var(--r-sm);
}

.notice {
  background: var(--card);
  color: var(--ink);
  border: 1px solid var(--line);
  padding: 0.75rem 0.95rem;
  margin: 0 0 1.5rem;
  font-size: var(--t-1);
  border-radius: var(--r-sm);
}

/* --- the signed-in page's subject -------------------------------------- */

/* The page used to open on a paragraph. See the comment in views.mjs where the
   h1 is emitted for why that was a missing subject rather than a missing style.
   The measure is bounded and the wrap is balanced as a progressive heuristic --
   text-wrap:balance is ignored by browsers that do not have it and the natural
   wrap is fine, which is the only reason it is safe to use on shipped copy. */
.app-head { margin: 0 0 2.1rem; }

.app-h1 {
  font-family: var(--display);
  font-size: var(--t-7);
  line-height: 0.92;
  letter-spacing: 0;
  text-transform: uppercase;
  font-weight: 400;
  max-inline-size: 18ch;
  text-wrap: balance;
  margin: 0 0 var(--s-3);
}

/* 65-75 characters is the readable measure. The lede used to run the full
   44rem of the wrap. */
.app-head .lede { max-width: 66ch; margin: 0; }

/* --- the step cards ---------------------------------------------------- */

/* WHAT EACH MODIFIER IS FOR. Every panel used to be the same object -- same
   surface, same 1px line, same radius, same padding, same width -- five slabs
   down the page with nothing to say which mattered. Each one states its job:

     --anchor   step 01, the photograph. The identity anchor; nothing on the
                page works without it. On a wide screen it is a NARROWER
                column that stays in view while the choices scroll past it,
                which is the #tape grid's doing rather than a surface of its
                own: it is a card like the commit.
     --choice   steps 02 and 03. Menus of options, and the light half of the
                arc -- the same outline with nothing behind it, so they read
                as a continuous flow of choosing rather than as two more
                filled cards competing with the anchor. See below.
     --commit   step 04. Where credits are actually spent. A filled card
                again, with more room, because the last panel before money
                leaves should not look like the two browsing panels above it.
     --archive  outside the form, and the full width of the wrap, so the
                boundary between "making one" and "the ones you made" is a
                change of shape and not just more vertical space. */
/* SEPARATION IS margin-bottom ONLY, never margin-top. Below 64rem the panels
   are block siblings and their margins COLLAPSE; at 64rem they become grid
   items and grid margins do not collapse. With margins on both sides the same
   declarations produced 25.6px of separation on a phone and 32px on a laptop,
   and 35.2 / 53.6 at the archive -- a rhythm change nobody chose. One
   direction makes the two cases arithmetically identical. */
.panel {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: var(--s-5);
  margin: 0 0 var(--s-6);
}

/* THE WEIGHT ARC, IN THIS WORLD'S VOCABULARY (2026-09-07). The 2026-08-22
   arc -- anchor firm, the two choices lightest, commit firm -- was carried by
   a frost tier, and the lime tokens collapsed every frost value to one plane,
   so the four steps read as four identical boxes (CLAUDE.md §70E). The arc is
   the FILL now: the photo and the tape are cards, the two menus between them
   are the same outline with nothing behind it. Heavy, light, light, heavy.
   Both kinds keep the 1px, so the four step numerals sit on one line. */
.panel--choice { background: transparent; }

/* The commit earns more room because it holds six decisions and the money.
   The archive break is carried HERE, on the bottom edge, for the reason every
   other separation is: a margin-top on the archive collapsed to 48px between
   block siblings and summed to 80px between grid items. Measured both ways. */
.panel--commit {
  padding: var(--s-6) var(--s-5);
  margin-bottom: var(--s-7);
}

/* The archive is not a fifth step, and the air that says so is on the commit's
   bottom edge -- see above. Nothing here. */
.panel--archive { margin-top: 0; }

/* --- the step header --------------------------------------------------- */

/* Two columns: the number in a gutter, everything said about the step in the
   other. The gutter is what lines the four steps up as a sequence.

   A MINIMUM, NOT A FIXED WIDTH (2026-09-04). The archive header borrows this
   grid with the word ARCHIVE where a step has STEP over a numeral, and the
   numeral is exactly 2.9rem wide while ARCHIVE at the label size and tracking
   is about 4rem -- so for four days it overflowed the gutter and printed
   through "Your tapes", and only the owner's eye caught it. max-content lets
   a header's gutter grow to its own label; the four steps stay at 2.9rem
   because nothing in them is wider than the numeral, so the sequence still
   lines up. test/browser-smoke.test.js measures the glyphs, not the box. */
.step-head {
  display: grid;
  grid-template-columns: minmax(2.9rem, max-content) minmax(0, 1fr);
  column-gap: 1rem;
  align-items: start;
  margin: 0 0 var(--s-5);
}

.step-say { min-width: 0; }

.stepno { margin: 0; text-align: right; }

.stepno-k {
  display: block;
  font-size: var(--t-label);
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: var(--faint);
  margin: 0 0 0.15rem;
}

/* The step numeral is display type in plain ink. It is a numeral, not a
   choice, so it does not take the accent: the accent means chosen or go, and
   five glowing numerals down the page would spend it on nothing. */
.stepno-n {
  display: block;
  font-family: var(--display);
  font-size: var(--t-8);
  line-height: 0.9;
  color: var(--ink);
}

.stepno-n--mark { font-size: var(--t-4); line-height: 1.4; color: var(--ink); }

/* The subtitle is prose and takes the readable measure with it. */
.step-say .sub { max-width: 56ch; }

/* --- the signed-in page's layout --------------------------------------- */

/* WHY TWO COLUMNS, AND WHY ONLY HERE.
   The four panels were not merely styled alike, they were the same WIDTH --
   one 44rem column in a 1280px viewport, which is a phone layout centred on a
   desktop and is most of why the page read as templated. Above 64rem the form
   becomes a grid: the photograph on the left in a narrow sticky column, the
   three panels that follow it stacked in a wider one.

   Sticky is the point rather than a flourish. The photograph is the identity
   anchor for every choice made to the right of it, and it is the one input a
   visitor must supply; keeping it in view while they scroll the look and the
   place is the layout saying so.

   No JavaScript is involved in any of this, and none is added: it is CSS grid
   plus position:sticky. Below 64rem every panel is full width and the order in
   the DOM is already the order to read them in, so the whole block simply does
   not apply and nothing needs unwinding.

   64rem IS 1024px AND THAT IS THE WHOLE REASON IT IS 64 AND NOT 62. The first
   version broke at 62rem/992px, which is on nobody's device list and on no
   standard breakpoint scale -- it was picked because it was a bit wider than
   the content needed, which is how a codebase ends up with five breakpoints
   that each disagree with the others by twenty pixels. 1024 is a real tablet
   landscape width and a real testing stop. Verified at 320 / 375 / 414 / 768 /
   1024 / 1440: no horizontal overflow at any of them, single column below the
   break, two columns at and above it. If this needs to move, move it to
   another number on that list, not to whatever the content happens to want. */
@media (min-width: 64rem) {
  .page-home .wrap { max-width: 62rem; }

  .page-home #tape {
    display: grid;
    grid-template-columns: 20rem minmax(0, 1fr);
    column-gap: 2rem;
    align-items: start;
  }

  .page-home .panel--anchor {
    grid-column: 1;
    grid-row: 1 / span 3;
    position: sticky;
    top: 1.25rem;
  }

  .page-home .panel--choice,
  .page-home .panel--commit { grid-column: 2; }

  /* NOTE, so nobody "fixes" this later: step 02 keeps its top hairline, and it
     is load-bearing. It lands on exactly the same y as the anchor panel's top
     border, and because both boxes carry the same 1.5rem of padding under it,
     "Your photo" and "The look" sit on the same baseline across the gutter.
     That shared line is what makes two columns of different widths read as one
     layout. An earlier version of this block cancelled that border and padding
     on a ".panel--choice:first-of-type" selector, which never matched anything
     -- the first section of its type inside the form is the ANCHOR, not a
     choice -- and had it matched, it would have broken the very alignment it
     was written to create. */

  /* The dropzone no longer needs to be 15rem of empty box: in a 20rem column
     it is already the tallest single thing on the page. */
  .page-home .panel--anchor .drop { min-height: 11rem; }
}

/* A sticky element inside a scroll container taller than the viewport is fine,
   but if the anchor ever grows past the viewport height it must be able to
   scroll itself rather than trapping its own bottom edge off-screen. */
@media (min-width: 64rem) and (max-height: 40rem) {
  .page-home .panel--anchor { position: static; }
}

/* --- step 01, the dropzone --------------------------------------------- */

.drop {
  position: relative;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 0.5rem;
  min-height: 15rem;
  border: 1px dashed var(--line);
  border-radius: var(--r-sm);
  /* DEPTH AND A DASHED OUTLINE, because each says a different half of it. The
     well behind the control says "this is a recess in the card"; the dash is
     what spec §6 names, and it is what says "drop here" rather than "this box
     is a heading". On the open step-3 panel there is no card plane to recess
     from at all, so the recess alone read as centred prose with no affordance. */
  background: var(--ground);
  text-align: center;
  padding: 1.5rem;
  cursor: pointer;
  transition: border-color 140ms;
}
/* THE SECOND UPLOAD IN A STEP IS NOT THE SUBJECT OF ONE. Step 1's dropzone is
   15rem because the face IS that step; the place photograph is one of two ways
   to answer step 3, sitting above a text field that answers it equally well.
   Same recess, same behaviour, a third of the height -- so it reads as a
   sibling of the field below it rather than as a competing hero. */
.drop--slim { min-height: 5.5rem; gap: var(--s-2); background: var(--card); }
.drop--slim .plus { font-size: var(--t-2); }
/* Hover brightens the outline rather than lifting the fill: the fill is what
   says "well", the dash is what says "drop here", and only the second should
   answer the pointer. */
.drop:hover { border-color: var(--ink-soft); }

/* THE CHOSEN PHOTO, SHOWN BACK. Step 1 named the file and showed nothing, so a
   wrong photo was invisible until the finished tape came back -- on the step
   that commits 21 credits.

   The [hidden] rule IS DECLARED EXPLICITLY AND THAT IS NOT BELT-AND-BRACES:
   the display:flex below beats a bare hidden attribute, and an empty box with
   a broken-image icon is what ships if this rule is missing. There is a test
   that reads this stylesheet for it. */
.picked[hidden] { display: none; }
.picked {
  display: flex; align-items: center; gap: var(--s-2);
  margin: var(--s-2) 0 0;
}
.picked .pickthumb {
  width: 4.5rem; height: 4.5rem;
  object-fit: cover; border-radius: var(--r-sm);
  background: var(--ground);
}
.picked .pickname {
  flex: 1 1 auto; min-width: 0;
  color: var(--faint); font-size: var(--t-1);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* A real <button>, stripped rather than styled: it must be a button for the
   keyboard and for a screen reader, and it must not draw a box, because
   DESIGN.md permits exactly two borders and this is neither of them. */
.picked #photo-clear {
  flex: 0 0 auto;
  background: none; border: 0; padding: 0.35rem 0;
  font: inherit; font-size: var(--t-1); cursor: pointer;
}
.drop input[type="file"] { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
.drop .plus { font-size: var(--t-3); color: var(--ink); letter-spacing: 0.04em; }
.drop .say { color: var(--faint); font-size: var(--t-1); max-width: 22rem; }
.drop .chosen-name { color: var(--ink); font-size: var(--t-1); }

/* --- step 02, the look grid -------------------------------------------- */

.looks { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.7rem; }

.lookcard {
  position: relative;
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: transparent;
  cursor: pointer;
  /* VALUES SNAP. The outline brightens on hover; the fill does not ease. */
  transition: border-color 140ms;
}
.lookcard:hover { border-color: var(--ink-soft); }
.lookcard .name { display: block; font-size: var(--t-2); color: var(--ink); }
/* HIERARCHY INSIDE A CARD IS COLOUR AGAIN. The rule that forbade the soft tier
   inside these cards was about GHOSTS -- --ink-soft under a 0.5 opacity is
   2.45:1 -- and the card is not a ghost any more. Unchosen it is an outline on
   the ground with page ink inside it; chosen it fills lime and the generated
   rule re-inks name and detail for the lime. */
.lookcard .detail { display: block; font-size: var(--t-1); color: var(--ink-soft); margin-top: 0.15rem; }
/* The state marks carry NO TEXT IN THE MARKUP. They are hidden by opacity, and
   with the stylesheet switched off an opacity rule does nothing -- so a badge
   that spelled out "Selected" would appear on all nine cards at once and the
   page would read as though everything were chosen. The checked radio is what
   actually conveys the state to assistive technology; these two are decoration,
   so their text belongs in the decoration. */
/* THE MARK IS ON THE LEFT, BEFORE THE NAME, IN EVERY ROW (2026-09-04). The
   frame card always drew its dot in the flow ahead of the shape; this card
   pinned its own to the top-right corner, and the owner saw the two rows
   disagree. A two-column grid gives the dot a gutter of its own, the way the
   frame card's in-flow tick does: unlit it is invisible and the gutter is
   empty, chosen it sits on the name's baseline. Everything that is not the
   tick lives in the second column, so the names align across the row. */
.lookcard { display: grid; grid-template-columns: 0.9rem minmax(0, 1fr); column-gap: 0.5rem; align-items: baseline; }
.lookcard > :not(.tick) { grid-column: 2; }
.lookcard .tick {
  grid-column: 1; grid-row: 1;
  color: var(--on-lime); font-size: var(--t-label);
  opacity: 0; transition: opacity 140ms;
}
.lookcard .tick::before { content: "●"; }

@media (max-width: 30rem) { .looks { grid-template-columns: 1fr; } }

/* --- step 03, the place rail ------------------------------------------- */

.rail {
  display: flex; gap: var(--s-3);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding: var(--s-1) var(--s-5) var(--s-4);
  margin: 0 calc(-1 * var(--s-5));
  scrollbar-width: thin;
}

/* LANDSCAPE, AND THE REASON IS THAT ONE FILE DOES TWO JOBS.
   "assets/places/<id>.jpg" is BOTH this card's thumbnail AND the full-bleed
   ".bg--<id>" layer behind the whole page, and both use "cover". The card was
   11x14rem -- portrait, roughly 4:5 -- against 16:9 source photographs, so
   "cover" threw away about two thirds of the width and centred on whatever
   happened to be in the middle. Measured on the real images: the Autobahn card
   lost its striped kiosk entirely and kept an empty picnic table, and the
   balcony lost the washing line that is the whole subject.
   16:9 cards show the photograph the place actually is, and the same file then
   fills a 16:9-ish viewport as a background with no crop fighting either. If
   these ever become portrait again, the images have to be re-shot to match --
   do not just change the numbers back. */
.placecard {
  position: relative;
  flex: 0 0 auto;
  width: 17rem; height: 9.5rem;
  scroll-snap-align: center;
  border: 0;
  border-radius: var(--r-sm);
  overflow: hidden;
  cursor: pointer;
  transition: transform 220ms cubic-bezier(.2,.9,.3,1);
}

/* THE GHOST IS ON THE PHOTOGRAPH, NOT ON THE CARD, and this is the one place
 * where the ghosting rule had to be applied to a different element rather than
 * a different number.
 *
 * A place card carries its name and date on the image, over a scrim solved for
 * FULL opacity. Ghosting the whole card multiplies that scrim as well: measured
 * against the worst ground the gradient can produce, the name lands at 4.36:1
 * and the date at 3.32:1 even at the floor -- so the unlit half of the menu
 * would be the half nobody can read, on the control where reading the label IS
 * the choice.
 *
 * The world decides which PICTURE is lit. It does not dim the menu. So the
 * opacity moves to '.thumb' and the caption stays at full strength, where it
 * measures 13.94:1 and 8.90:1. The card still reads as unlit, because the
 * photograph is the thing anybody is looking at.
 *
 * The photograph itself stays untinted -- this world does not colour the
 * subject, it only decides which one is lit. */
.thumb {
  position: absolute; inset: 0;
  background-size: cover; background-position: center;
  opacity: var(--ghost);
  transition: opacity 160ms linear;
}
.placecard:hover .thumb { opacity: var(--ghost-hover); }
/* The "your own photo" card has no photograph to show, so it draws a hatch
   where one would be. The two stops are the card plane and the ground, which
   keeps it the faintest thing in a row where every other card is a picture. */
.placecard--own .thumb { background: repeating-linear-gradient(135deg, var(--card) 0 8px, var(--ground) 8px 16px); }

/* THE DEFAULT STATE OF THIS PAIR IS THE PRESET STATE, and that is the safe way
   round rather than an arbitrary one. Base CSS shows own-pick and hides
   own-add; the generated '#pl-own:checked' rules swap them. So a browser that
   somehow never applies the generated block still shows a WORKING card -- the
   label for the radio, which is what the rail had before today -- rather than
   an upload card sitting next to a preset it contradicts. The failure mode of
   the fallback is the old behaviour, not a new one. */
.placecard--own-add { display: none; }

.placecard .cap {
  position: absolute; inset: auto 0 0 0;
  padding: 2.5rem 0.75rem 0.7rem;
  background: linear-gradient(180deg, rgba(11,10,9,0) 0%, rgba(11,10,9,0.88) 62%);
}
.placecard .cap .name { display: block; font-size: var(--t-1); color: var(--on-image); line-height: 1.25; }
.placecard .cap .when { display: block; font-size: var(--t-label); letter-spacing: 0.14em; text-transform: uppercase; color: var(--on-image-soft); margin-top: 0.25rem; }

/* CHOSEN, ON THE IMAGE: a lime pill, the same object as the pricing page's
   Recommended mark, sitting on the picture it marks. Filled rather than
   text-with-a-shadow because a fill survives any photograph without a halo,
   and a halo on a photograph reads as a filter. Top-left, where the
   selection mark sits in every other row (§60G). */
.placecard .badge {
  position: absolute; top: 0.6rem; left: 0.6rem;
  font-size: var(--t-label); font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--on-lime);
  background: var(--lime);
  border: 0; border-radius: 999px;
  padding: 0.15rem 0.55rem;
  opacity: 0;
  transition: opacity 160ms;
}
.placecard .badge::before { content: "Selected"; }

.dots { display: flex; gap: 0.35rem; justify-content: center; margin: 0.2rem 0 0; }
.dot { width: 5px; height: 5px; border-radius: 50%; background: var(--line); }

/* THE OWN-PLACE BLOCK LEADS STEP 3 (2026-08-30), and it is shown by the same
   rule it has always been shown by -- the pl-own radio is simply checked when
   the page arrives now, so the condition is true on load. Nothing here reveals
   it; do not add a second mechanism.
   It still hides when a preset card is clicked, and a hidden file input STILL
   SUBMITS -- which is why the server refuses a preset card posted together with
   a place photograph rather than silently picking one. See PLACE_CONFLICT. */
.ownplace { display: none; margin-top: 1rem; }
/* A file input, then this line, then the text box. .hint carries a bottom
   margin only, so without this the prose sits flush against the control above
   it. */
.ownplace .or-describe { margin-top: 0.9rem; }

/* The signpost to the two escape hatches. See the comment in views.mjs: both
   the upload and the free-text box already existed and neither could be found,
   because one was behind a card at the far end of a scrolling rail and the
   other was inside a collapsed <details>.

   .linky is a <label for="pl-own"> dressed as a link. It has to LOOK
   clickable, because the whole failure being fixed here is a control nobody
   could tell was a control -- the same mistake the PAL chips made in the other
   direction, where something unclickable looked clickable. Underlined, in the
   accent colour, with a pointer cursor: three signals, because one is what the
   place cards had and it was not enough. */
.escape { margin: 0.9rem 0 0; text-align: center; }
.linky {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.linky:hover { color: var(--ink-soft); }
/* Keyboard parity: the radio is what actually receives focus, so the visible
   ring has to be drawn on the label that stands in for it. */
#pl-own:focus-visible ~ .wrap .linky { outline: 2px solid var(--ink); outline-offset: 2px; }

/* --- the free-text escape hatch ---------------------------------------- */

/* NO SUMMARY RULES HERE ANY MORE (2026-09-05). The outfit box was the last
   disclosure on the page and it is a plain block now, so the three
   'aside summary' rules that used to sit here matched nothing at all. Deleted
   rather than left: a rule that matches nothing is how dead markup survives a
   review, which is the reasoning section 30 used when it took the 4:3 veil's
   markup and its rule out together. */
.aside { margin-top: 1.1rem; }

input[type="text"], input[type="email"], input[type="password"], select {
  width: 100%;
  background: var(--ground);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  color: var(--ink);
  font: inherit;
  padding: 0.7rem 0.85rem;
}
/* THE PLACEHOLDER IS TEXT, SO IT CLEARS THE FLOOR. A placeholder is the kind
   of value a theme gets away with dimming because nobody measures a hint; it
   takes the soft tier, which is the same colour every other hint uses and is
   measured against both surfaces by the palette test. */
/* THE OTHER HALF OF THE SURFACES RULE. DESIGN.md: 'A field inside a card
   recesses to the ground; a field on the ground lifts to the card.' Steps 2 and
   3 are the weight arc's light panels -- transparent, so their surface is the
   page ground -- and each carries a free-text field. Recessed there it is
   ground on ground, and the only thing left saying 'this is a field' is a 1px
   line at 0.14 alpha. Its sibling the slim dropzone already lifts, with the
   same reasoning written above it. */
.panel--choice input[type="text"] { background: var(--card); }
input::placeholder { color: var(--ink-soft); }
select { width: auto; min-width: 6rem; }

input[type="file"] { color: var(--ink); font-size: var(--t-1); }
input[type="file"]::file-selector-button {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--ink);
  font: inherit; font-size: var(--t-1);
  padding: 0.4rem 0.85rem;
  margin-right: 0.8rem;
  cursor: pointer;
}

/* The ring is ink everywhere by default. A positive outline-offset draws the
   ring OUTSIDE the border box, so on a lime BUTTON it lands on the dark
   ground around it, not on the lime -- ink is the right ring there too. Only
   a control genuinely sitting ON a lime panel (a chosen card, not a button
   surrounded by dark) needs the ink-of-the-lime ring, or it would vanish
   against the lime beneath it. */
:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.lime :focus-visible { outline-color: var(--on-lime); }

.field { margin: 0 0 var(--s-5); }
.field label { display: block; font-size: var(--t-1); letter-spacing: 0; color: var(--ink); margin-bottom: var(--s-2); }

/* --- the settings panel ------------------------------------------------ */

.pills { display: flex; flex-wrap: wrap; gap: 0.45rem; margin: 0 0 0.6rem; }

/* FACTS, NOT CHOICES. Styled like the active state of a control on purpose --
   these are what the tape is -- but they are <span>s, not buttons, so there is
   nothing to click and nothing to change. A camcorder tape IS 4:3; offering
   16:9 would make this a filter instead of a tape, and the 375-frame contract
   is asserted by roughly two hundred tests. */
.pill {
  font-size: var(--t-label); letter-spacing: 0.14em;
  /* A FACT IS NOT A CHOICE, so it does not wear the accent: it is ink on the
     card plane inside the world's own outline, which is what every other
     small chip on the site is. */
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 0.3rem 0.8rem;
}

/* --- the frame row -------------------------------------------------------
   Three shapes, and the aspect ratio is drawn rather than only named: a little
   outline in the real proportion says "tall" or "wide" faster than the numbers
   do, and it costs one empty span. The 4:3 box is the reference the other two
   are read against, so all three are scaled to the same HEIGHT budget and only
   the width varies -- which is also, not coincidentally, exactly what the
   renderer does. */

.frames { display: flex; flex-wrap: wrap; gap: var(--s-2); margin: 0 0 var(--s-2); }

.framecard {
  position: relative; display: flex; align-items: center; gap: 0.6rem;
  padding: var(--s-2) var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: transparent;
  cursor: pointer;
  transition: border-color 140ms;
}
.framecard:hover { border-color: var(--ink-soft); }
.framecard .ratio { font-size: var(--t-2); font-weight: 600; letter-spacing: 0.08em; color: var(--ink); }
.framecard .detail { font-size: var(--t-label); color: var(--ink-soft); }
.framecard .tick { color: var(--on-lime); font-size: var(--t-label); opacity: 0; transition: opacity 140ms; }
.framecard .tick::before { content: "●"; }

/* The drawn shape. Height is fixed at 18px and the width carries the ratio.
   IT IS DRAWN IN INK, NOT IN THE ACCENT. Lime means chosen, and a glyph in lime
   on every card says every shape is chosen -- which is exactly the thing colour
   is reserved to answer. The generated rule redraws it in the on-lime ink on
   the card that is actually filled. */
.framecard .shape { display: block; border: 1px solid var(--ink); height: 18px; flex: none; }
.framecard--a-4x3 .shape { width: 24px; }
.framecard--a-16x9 .shape { width: 32px; }
.framecard--a-9x16 .shape { width: 10px; }
/* A deferred shape still DRAWS its glyph -- the exception DESIGN.md grants is
   for the rectangle that depicts an aspect ratio, and one drawn in a
   transparent colour depicts nothing. It was invisible on the dark ground for
   the same reason; ghosting it is what "unavailable" should have looked like
   all along.

   PAGE INK, NOT THE SOFT TIER, FOR THE REASON THE DEFERRED QUALITY CARD'S
   DETAIL LINE TAKES IT: the card is ghosted, and an opacity multiplies colour
   away. The soft tier under --ghost composites to 2.85:1, under the 3:1 that
   1.4.11 asks of a glyph carrying meaning; page ink under the same ghost is
   about 4.8:1. */
.framecard--soon .shape { border-color: var(--ink); }

/* Same rule as the deferred quality card: a <span>, no radio behind it, so
   there is nothing to select and nothing that can be posted. */
/* A DEFERRED SHAPE SITS AT THE SAME FLOOR AS EVERY OTHER UNLIT ONE. It was at
   .26, which is --ink at 1.70:1 -- text nobody can read, on the element whose
   only job is to say "not yet". Opacity cannot carry this distinction below the
   floor, so the FLAG carries it, in words, which is also the only version a
   screen reader ever had. */
.framecard--soon { cursor: default; opacity: var(--ghost); }
.framecard--soon:hover { opacity: var(--ghost); }
.framecard--soon .flag { font-size: var(--t-label); letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink); }

/* --- the quality row: a real choice, and it must not look like the pills --- */

.quality { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: var(--s-2); margin: 0 0 var(--s-2); }

.qualitycard {
  position: relative; display: block;
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: transparent;
  cursor: pointer;
  transition: border-color 140ms;
}
.qualitycard:hover { border-color: var(--ink-soft); }
.qualitycard .name { display: block; font-family: var(--display); font-size: var(--t-3); letter-spacing: 0; text-transform: uppercase; line-height: 1; color: var(--ink); }
/* One price per shape, hidden until the frame row says which shape. Painting
   them all at once would list three numbers on one card; painting the un-shaped
   one quotes the 4:3 price for a shape charged 4/3 of it. The deferred tier is
   the exception and says so in its own class -- it has no per-shape quote to
   switch to, because creditCost refuses it outright. */
.qualitycard .cr { display: none; font-size: var(--t-label); letter-spacing: 0.14em; color: var(--ink); margin-top: 0.1rem; }
.qualitycard .cr--soon { display: block; }
.qualitycard .detail { display: block; font-size: var(--t-1); color: var(--ink-soft); margin-top: 0.35rem; }
.qualitycard .flag { display: inline-block; font-size: var(--t-label); letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink); margin-top: 0.4rem; }
/* The same gutter as .lookcard, for the same reason: the mark on the left,
   before the name, in every row. The deferred card has no tick, and its
   children take the second column too, so its name lines up with the rest. */
.qualitycard { display: grid; grid-template-columns: 0.9rem minmax(0, 1fr); column-gap: 0.5rem; align-items: baseline; }
.qualitycard > :not(.tick) { grid-column: 2; }
.qualitycard .tick { grid-column: 1; grid-row: 1; color: var(--on-lime); font-size: var(--t-label); opacity: 0; transition: opacity 140ms; }
.qualitycard .tick::before { content: "●"; }

/* Unavailable options are a <span>, not a <label>: there is no radio behind
   them, so there is nothing to select and nothing that can be posted. */
/* A REFUSED VALUE IS OUTLINED LIKE ITS SIBLINGS AND GHOSTED AS WELL. This world
   has lines now, so an unlit card is still a card -- "not yet" is a value in the
   row, not an absence from it -- and the ghost is what says it cannot be had.
   AT THE FLOOR AND NOT BELOW IT -- .26 put --ink at 1.70:1. The flag says
   "coming soon" in words, which is the only version that ever reached a screen
   reader anyway, and words do not have a contrast ratio to fail. */
.qualitycard--soon { cursor: default; opacity: var(--ghost); }
.qualitycard--soon:hover { opacity: var(--ghost); }
/* THE ONE CARD STILL GHOSTED KEEPS PAGE INK INSIDE IT. Every other card took
   the soft tier for its detail line above, which is right on a card at full
   strength and wrong here: --ink-soft under the ghost measures 2.83:1 on this
   ground, and DESIGN.md's rule is that nothing inside a ghosted control is
   written in the soft tier, because colour is what an opacity multiplies away. */
.qualitycard--soon .detail { color: var(--ink); }

/* One cost per resolution, all hidden until the matching radio is checked. */
.cost { display: none; }
.why { display: none; color: var(--alarm); }

/* The group boundary the ladder was missing entirely. */
.panel--commit .hint { margin-bottom: var(--s-5); }

.facts { display: grid; grid-template-columns: 1fr auto; gap: var(--s-1) var(--s-4); margin: var(--s-5) 0; }
.facts dt { font-size: var(--t-label); text-transform: uppercase; letter-spacing: 0.22em; color: var(--faint); }
.facts dd { margin: 0; font-size: var(--t-2); font-weight: 600; letter-spacing: 0.12em; color: var(--ink); text-align: right; }

/* THE PRICE BESIDE THE BUTTON. Two columns, the facts filling and the button
   sized to its own label, aligned on the bottom edge so the button stands
   level with the credits line. Below 30rem -- the width where the nav wraps
   too -- the row stacks and the button takes the full width again. */
.commit-foot { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: var(--s-5); align-items: end; margin: var(--s-5) 0 0; }
.commit-foot .facts { margin: 0; }
.commit-foot .record { width: auto; margin-top: 0; padding: 0.85rem 1.6rem; }
@media (max-width: 30rem) {
  .commit-foot { grid-template-columns: 1fr; row-gap: var(--s-4); }
  .commit-foot .record { width: 100%; }
}

.record {
  display: block; width: 100%;
  margin-top: var(--s-5);
  background: var(--accent);
  border: 0; border-radius: var(--r-btn);
  /* The label sits ON the accent, so it takes the ink solved for lime rather
     than the page's ink. The palette test measures that pair on lime and on
     its hover value, because a hovered button is still a button. */
  color: var(--on-lime);
  font-family: var(--display); font-size: var(--d-2);
  text-transform: uppercase; font-weight: 400;
  letter-spacing: 0.02em;
  padding: 0.85rem 1rem;
  cursor: pointer;
  text-align: center; text-decoration: none;
}
.record:hover { background: var(--accent-bright); }
.record:disabled {
  background: var(--card);
  color: var(--ink-soft);
  border: 1px solid var(--line);
  cursor: not-allowed;
}

/* THE ONE-WAY DOOR IS NOT GO. '.record--danger' was in the markup from
   2026-08-29 and styled by nothing, so it rendered as the lime Record button.
   There is no alarm red in this world; danger is weight and words -- the
   sentence above the form says there is no undo -- and the control itself is
   the same outlined card every secondary control is, in ink. Hover brightens
   the outline and never the fill. */
.record--danger { background: var(--card); color: var(--ink); border: 1px solid var(--line); }
.record--danger:hover { background: var(--card); border-color: var(--ink); }

.reason { text-align: center; color: var(--faint); font-size: var(--t-1); margin: var(--s-3) 0 0; }

/* --- the shelf --------------------------------------------------------- */

/* THE SHELF IS THE REFERENCE'S GRID, and DESIGN.md § "The reference" says why
   this one and not a nicer-looking one: artifactuprising.com solves OUR problem,
   not an adjacent one -- somebody's photographs go in and a keepsake comes out.
   Image, then name, then caption, sitting directly on the paper. No box around
   the image, no border, no shadow, nothing between one tile and the next except
   space. It is a grid that obeys this file's one rule already.

   THE GAP GREW FROM 0.75rem TO --s-5. Space is the only thing separating two
   tiles now, so it has to be legible as separation rather than as a seam. */
.shelf { display: grid; grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr)); gap: var(--s-5); }

.tape {
  display: block;
  text-decoration: none;
  color: inherit;
}

/* HALF OF EVERY POSTER IS LETTERBOX, AND THE CROP IS WHAT REMOVES IT.
 *
 * The tape is delivered 9:16 with the 4:3 camcorder picture matted inside it, so
 * the poster is the picture plus two bars of the surround colour. Measured on a
 * 16-row sample of a real render, rows 1-4 and 13-16 are luma 0: the content is
 * EXACTLY the middle half.
 *
 * On a near-black page ground those bars were invisible, which is why this went
 * unnoticed for as long as it did. Uncropped they are 50% of every tile and are
 * what makes a shelf read as a wall of slabs rather than as photographs.
 *
 * 9/8 is that middle half. With the 'object-fit: cover' the tile already had,
 * one declaration crops to the picture and nothing else changes -- no new
 * markup, no reprocessing, and the burnt-in date stamp survives because it sits
 * inside the content band, not on the bar below it. */
.tape .frame {
  display: block;
  aspect-ratio: 9 / 8;
  overflow: hidden;
  position: relative;
  /* AN UNFINISHED TAPE HAS NO POSTER, AND THE TILE MUST STILL BE SOMETHING.
     Once it was a 50% near-black wash, so a rendering job read as a dark plate
     with its status on it; dropping the wash with the rest of the near-black
     left a 151px hole in the shelf with a status pill floating in the middle
     of nothing. The plate is the card plane now, outlined like every other
     card in this world, and the poster covers it completely the moment there
     is one. */
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
}
.tape img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* THE CROP FOLLOWS THE TAPE'S OWN SHAPE (2026-08-31), and until today it did
   not. The 9/8 above is MEASURED and correct -- for a 4:3 tape, which is
   delivered 1080x1920 with the picture matted inside it, so 9/8 removes the
   letterbox and nothing else. Then the frame menu opened (§34D) and nothing
   taught this rule: a 9:16 order is delivered FULL-BLEED at 1080x1920, so the
   same crop threw away more than half of a real picture, and a 16:9 tape lost
   its sides. Invisible so far only because every finished tape on this machine
   is 4:3.
   The default stays 9/8 rather than 4/3 because the file really does carry a
   little surround above and below the picture; the two wide shapes carry none,
   so they take their own ratio exactly. Rows of different heights are the
   honest result -- these ARE different-shaped tapes, and pretending otherwise
   is what loses the picture. */
.tape .frame--a-16x9 { aspect-ratio: 16 / 9; }
.tape .frame--a-9x16 { aspect-ratio: 9 / 16; }

/* The player on /videos. 'object-fit: cover' matches the poster it replaces, so
   the tile does not resize the instant somebody presses play. Black behind it
   because a video element with nothing decoded yet is transparent, and a tile
   showing whatever is behind it reads as a hole rather than as a picture that
   has not started. Black, not the tile's own surface: this is the tape's matte
   and it must not follow the interface. */
.tape--play { display: block; }
.vplay { width: 100%; height: 100%; object-fit: cover; display: block; background: #000; }

/* The download sits under the caption rather than over the picture: it is an
   action on the tape, not part of it, and DESIGN.md keeps text off a photograph
   unless the photograph is the ground. */
.dl {
  display: inline-block; margin-top: 0.35rem;
  font-size: var(--t-label); letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink); text-decoration: none;
}
.dl:hover { color: var(--ink-soft); }

/* THE CAPTION CAME OUT OF THE PICTURE AND ONTO THE PAGE, which is the other
   half of what the reference does. It used to sit inside the tile under a 90%
   near-black gradient -- a scrim that exists to make text survive over an
   unknown photograph, and a device this world does not need once the text has
   paper to sit on. Deleting it also deletes the only dark rectangle left in
   the tile after the crop. */
.tape .cap { display: block; padding-top: var(--s-3); font-size: var(--t-label); }
.tape .what { display: block; font-size: var(--t-1); color: var(--ink); line-height: 1.3; overflow-wrap: anywhere; }
.tape .when { display: block; font-size: var(--t-label); font-weight: 600; text-transform: uppercase; letter-spacing: 0.1em; color: var(--faint); }

/* Unfinished tapes still say so -- AND THE BADGE IS NEVER ON AN IMAGE, which is
   the detail that decided its colours. 'shelfTile' only emits a poster when the
   tape is done, so a tile carrying this badge is always the bare card plate,
   never a photograph. The on-image tier is therefore exactly wrong here: it
   measured 1.67:1 over that plate, the one contrast failure the sweep found on
   this page. It is ink on the ground -- a small label on a card. */
.tape .state {
  position: absolute; top: 0.5rem; left: 0.5rem;
  font-size: var(--t-label); letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--ink); background: var(--ground);
  border-radius: 999px; padding: 0.15rem 0.5rem;
}

.empty {
  /* An outlined card, like every other card in this world. */
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--card);
  padding: 2.5rem 1.5rem;
  text-align: center;
  color: var(--faint);
}
.empty .title { color: var(--ink); margin-bottom: 0.3rem; }

/* --- progress ---------------------------------------------------------- */

/* THE PHASES ARE ROWS, NOT A BAR (2026-09-04, from the design prototype). A
   bar can only say how far; a row per phase says what is happening and what
   is still to come, in words, with the record light on the one being filmed.
   The counter above keeps the "2 of 3" a bar used to imply. */
.status { max-width: 44rem; }
.status .headline { font-size: var(--t-7); text-wrap: balance; }
.counter { font-weight: 600; color: var(--faint); letter-spacing: 0.12em; text-transform: uppercase; font-size: var(--d-1); margin: var(--s-6) 0 var(--s-3); }
.phases { list-style: none; padding: 0; margin: 0 0 var(--s-6); display: grid; gap: var(--s-3); }
/* EACH PHASE IS A CARD (2026-09-07, spec §6). The row grid is unchanged
   inside it: state, body, number. The card is what makes three rows read as
   three things a render does rather than three lines of a list. */
.phase {
  display: grid; grid-template-columns: 5rem minmax(0, 1fr) auto; column-gap: var(--s-4); align-items: baseline;
  padding: var(--s-4) var(--s-5);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
}
.phase-state { display: flex; align-items: center; gap: 0.5rem; font-weight: 600; font-size: var(--t-1); letter-spacing: 0.18em; text-transform: uppercase; color: var(--faint); white-space: nowrap; }
/* A STATE IS A WORD, NOT A COLOUR (2026-09-07). 'Done' was painted lime and so
   was its dot, three rows above an order form where lime is the answer to
   'what have I chosen?'. Lime is never a label (DESIGN.md, Lime means chosen),
   so the states are told apart the way the rest of this world tells things
   apart: done and stopped are ink words each carrying a dot, a phase still to
   come is ghosted, and the one being filmed is red and blinks -- which is what
   makes the record light mean something.

   The base dot carries no fill: it is display:none until a state class shows
   it, and every state that does sets its own, so a colour here was a
   declaration nothing could ever paint. */
.phase-state .dot { display: none; width: 9px; height: 9px; border-radius: 50%; }
.phase-done .phase-state { color: var(--ink); }
.phase-done .phase-state .dot { display: inline-block; background: var(--ink); }
.phase-stopped .phase-state { color: var(--alarm); }
.phase-stopped .phase-state .dot { display: inline-block; background: var(--alarm); }
.phase-title { display: block; font-family: var(--display); font-size: var(--d-3); line-height: 0.92; letter-spacing: 0; text-transform: uppercase; font-weight: 400; color: var(--ink); }
.phase-note { display: block; font-size: var(--t-1); color: var(--faint); margin-top: var(--s-1); max-width: 48ch; }
/* A phase still to come is a ghost, at the floor and no lower. Hierarchy in
   the row is otherwise carried by size, which survives the opacity. */
.phase-pending .phase-title { opacity: var(--ghost); }
.phase-n { font-weight: 600; font-size: var(--d-1); letter-spacing: 0.12em; color: var(--faint); }
@media (max-width: 27rem) {
  .phase { grid-template-columns: minmax(0, 1fr) auto; row-gap: var(--s-1); }
  .phase-state { grid-column: 1 / -1; }
}

/* THE RECORD LIGHT. It sits on the phase being filmed and nowhere else -- see
   statusPage -- so it never blinks over a tape that has already stopped.

   NOT '.rec': that class is already the dot inside the wordmark SVG, and
   sharing it would have made the brand mark and this indicator style each
   other. Caught by a test before it shipped.

   The blink is steps() rather than a fade, because a tally light is a lamp
   being switched, not something that breathes.

   THE RECORD LIGHT IS RED. It took --accent when the accent was cathode
   orange and kept the name through two palettes, so in the lime world the one
   element the palette reserves red for was the one painted in the colour
   that means "chosen". A lime REC light is not a REC light (spec §2.1). */
.reclight { color: var(--rec); }
.reclight .dot { display: inline-block; background: var(--rec); animation: tally 1.6s steps(1, end) infinite; }
@keyframes tally { 0%, 55% { opacity: 1; } 56%, 100% { opacity: 0.25; } }
@media (prefers-reduced-motion: reduce) { .reclight .dot { animation: none; } }

/* THE ELEVEN STEPS, ONE LINE AWAY. Native <details>, so no script -- the CSP
   names the shipped inline scripts by hash and a fourth would be dead in the
   browser. The marker is left alone rather than restyled: the disclosure
   triangle is the affordance somebody already knows, and DESIGN.md's borders
   rule means there is nothing to draw around it anyway. */
.stepdetail { margin: 0 0 1.2rem; }
.stepdetail > summary {
  cursor: pointer; color: var(--ink); opacity: var(--ghost);
  font-size: var(--t-1); padding: 0.2rem 0;
}
.stepdetail > summary:hover { opacity: 1; }
/* The one ring, at the one weight and the one offset: spec §2.4. This summary
   was the only focus indicator on a dark surface drawn in the accent, and a
   ring that says 'chosen' to somebody who has chosen nothing is the same
   confusion the state colours above were. */
.stepdetail > summary:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; opacity: 1; }

.steps { list-style: none; padding: 0; margin: 0 0 1.75rem; }
.step {
  display: grid;
  grid-template-columns: 1rem 1fr;
  grid-template-areas: 'mark name' '. note';
  gap: 0 0.75rem;
  /* Was a 1px top border. DESIGN.md forbids rules and dividers anywhere, and a
     literal colour is a line no token can turn transparent -- so the steps are
     separated by space instead. Tight within the group, generous between. */
  padding: 0.6rem 0;
  color: var(--faint);
}
.step-mark { grid-area: mark; width: 6px; height: 6px; margin-top: 0.6rem; border-radius: 50%; background: var(--line); }
.step-name { grid-area: name; font-size: var(--t-2); }
.step-note { grid-area: note; font-size: var(--t-1); color: var(--faint); }
/* THE ELEVEN MARKS ARE THE SAME ARGUMENT ONE LINE DOWN. Done, skipped and
   current were lime, so the disclosure a customer opens to read the detail of
   a render answered 'what happened here?' in the colour that answers 'what did
   I pick?'. Weight carries it instead: the step being worked is page ink and a
   filled mark, the ones behind it are the soft tier, and skipped is the same
   soft tier hollowed out. */
.step-done { color: var(--ink); }
.step-done .step-mark { background: var(--ink-soft); }
.step-skipped .step-mark { box-shadow: inset 0 0 0 1px var(--ink-soft); background: transparent; }
.step-failed .step-mark { background: var(--alarm); }
.step-current { color: var(--ink); }
.step-current .step-mark { background: var(--ink); }
.step-current .step-note { color: var(--ink); }

/* The order, as a definition list: where, wearing, frame. */
.inputs { display: grid; grid-template-columns: 5rem minmax(0, 1fr); gap: var(--s-1) var(--s-4); margin: 0 0 var(--s-6); color: var(--ink); }
.inputs dt { font-size: var(--t-label); text-transform: uppercase; letter-spacing: 0.22em; color: var(--faint); line-height: 1.6; }
.inputs dd { margin: 0; overflow-wrap: anywhere; }

/* --- contact sheet ----------------------------------------------------- */

.sheet { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: var(--s-5); }
.still {
  position: relative; display: block; padding: 0;
  background: transparent;
  border: 0;
  border-radius: var(--r-sm);
  cursor: pointer; overflow: hidden; line-height: 0;
  /* STRUCK, NOT RINGED. Selection here was two border-colour rules -- a line
     drawn round the chosen thing, which is the device DESIGN.md's one rule
     exists to forbid, and which the border sweep could not see because both
     named tokens. It is the world's own grammar instead: every option present
     as an unlit ghost, one struck forward. A ghost sits at the floor and no
     lower; on this ground the ghost sits at the floor, and '--ghost' is where
     the ground names it. */
  opacity: var(--ghost);
  transition: opacity 160ms linear;
}
.still img { width: 100%; height: auto; display: block; }
.still:hover { opacity: var(--ghost-hover); }
.still.chosen { opacity: 1; }
.still-n {
  position: absolute; left: 0.45rem; bottom: 0.35rem;
  font-weight: 600; color: var(--on-image-soft); font-size: var(--t-2); line-height: 1;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
}
/* The index of the struck one goes to the accent-on-an-image. It is the one
   place on this grid where colour answers "which have I chosen?". */
.still.chosen .still-n { color: var(--on-image-accent); }

/* --- the video --------------------------------------------------------- */

/* THE PLAYER KEEPS THE TAPE'S OWN MATTE AND DOES NOT FOLLOW THE GROUND. It used
   to name the interface's own ground token, which was right only while the page
   and the surround happened to be the same near-black -- and the day the page
   moved, a letterboxed video sat in bars of whatever the interface had become.
   The delivered file is matted on '#0B0A09' and the player must not argue with
   it, so this is PALETTE.ground, the colour the renderer actually mattes onto.
   The outline is the card's, so the tape sits in the same frame as everything
   else on this page; the colour behind the picture is the tape's own, and
   stays that colour however the interface is repainted. */
.player {
  background: #0B0A09;
  border: 1px solid var(--line);
  border-radius: var(--r);
  overflow: hidden; line-height: 0;
}
.player video { width: 100%; height: auto; display: block; background: #0B0A09; }
.meta { font-weight: 600; color: var(--faint); letter-spacing: 0.12em; font-size: var(--t-1); margin: 0.8rem 0 1.5rem; }

/* THE TAPE LABEL -- the payoff page's one signature moment.

   AN OUTLINED CARD, like every other card in this world -- the fill is the
   card plane and the outline is the one token every outline in the sheet
   names, so this introduces no colour the palette did not have.

   The date takes the accent because on this page the date IS the product --
   it is the one place the single accent means "this is the thing you chose",
   the same job it does on a struck option everywhere else. */
.label {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  column-gap: 1rem;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 0.75rem 0.9rem 0.8rem;
  margin: 0.6rem 0 0;
}
.label .lname { font-weight: 600; letter-spacing: -0.01em; }
.label .lsub {
  grid-column: 1; grid-row: 2;
  color: var(--faint); font-size: var(--t-label);
}
/* THE READOUT FACE IS WHAT DEPICTS THE TAPE, NOT THE COLOUR. The date was the
   one numeral on the site painted lime, and lime is never a numeral. VT323 on
   its own already says 'camcorder' -- it is the only place in the interface
   that uses the OSD face at all. */
.label .ldate {
  grid-column: 2; grid-row: 1 / span 2;
  font-family: var(--osd); font-size: var(--t-2);
  color: var(--ink); letter-spacing: 0.06em; white-space: nowrap;
}

/* ON A PHONE THE DATE TAKES ITS OWN LINE. The date column is fixed-width and
   never wraps, so on a narrow screen it squeezes the place name into a ragged
   two-line wrap beside it -- measured at 375px, the commonest phone width and
   one of the six this project tests: 'The garden, in / summer'. Stacked, the
   date reads as the top line of the label, which is where a real cassette
   label carries it. The DOM order is unchanged, so a screen reader still hears
   the place first; only the painting order moves. */
@media (max-width: 27rem) {
  .label { grid-template-columns: 1fr; }
  .label .ldate { grid-column: 1; grid-row: 1; }
  .label .lname { grid-column: 1; grid-row: 2; }
  .label .lsub { grid-column: 1; grid-row: 3; }
}

/* --- the result: the tape beside the words (2026-09-04) ---------------- */

/* Two columns: the tape at a fixed 20rem with its label beneath, the words
   taking the rest. On a phone they stack and the tape still comes first. */
.result-grid { display: grid; grid-template-columns: minmax(0, 20rem) minmax(0, 1fr); gap: var(--s-7); align-items: start; margin: 0 0 var(--s-8); }
@media (max-width: 48rem) { .result-grid { grid-template-columns: 1fr; gap: var(--s-6); } }
.result-words { padding-top: var(--s-2); }
.result-words .headline { font-size: var(--t-7); text-wrap: balance; max-inline-size: 18ch; }
.result-words .sub { max-width: 44ch; }
/* A label in the readout face, for a section whose content is a readout --
   "The file", "Earlier tapes". The same size and tracking as the status
   page's counter. */
.eyebrow--osd { font-weight: 600; font-size: var(--d-1); letter-spacing: 0.12em; text-transform: uppercase; color: var(--faint); margin: 0 0 var(--s-2); }
/* THE SPEC LINE IS A LABELLED READOUT ON THE PAYOFF PAGE, under "The file",
   rather than a caption on the picture: a frame count and a raster are what
   the tape physically is, and beneath their own label they read as a fact
   about the file rather than as a description of a memory. It is a
   stylesheet change rather than a second class because the tests read
   'class="meta"' exactly -- and because where a fact sits in the hierarchy is
   a design decision, not a markup one. */
.page-result .meta { font-weight: 600; font-size: var(--d-2); letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink); margin: 0 0 var(--s-5); }
.earlier .shelf { margin-top: var(--s-4); }

/* --- the shelf page and the account page (2026-09-04) ------------------ */

/* The shelf is the page: label, heading at page-title size, two sentences,
   tiles on the paper. The tiles keep the prototype's grid rather than the
   home strip's, because here they are the content and not an aside. */
.videos .headline { font-size: var(--t-7); max-inline-size: 18ch; margin: 0 0 var(--s-3); }
.videos .sub { margin: 0 0 var(--s-2); }
.videos > .hint { margin: 0 0 var(--s-7); }
.videos .shelf { grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr)); gap: var(--s-6) var(--s-5); }

/* The account: the address as the heading (it can be long, so it may break
   anywhere), the sections under readout labels, the one-way door in a narrow
   column so the field and the button read as one control. */
.account { max-width: 44rem; }
/* THE ADDRESS IS DATA, NOT A HEADING. Every other .headline is the display
   face, uppercase; an email address uppercased is a different string and in a
   condensed poster face it shouts. It stays the page's h1 -- the markup and the
   accessible outline are unchanged -- and takes the body face at 600. The
   display-metrics test skips a rule that names --sans for exactly this. */
.account .headline { font-family: var(--sans); font-weight: 600; font-size: var(--t-5); line-height: 1.2; letter-spacing: 0; text-transform: none; overflow-wrap: anywhere; }
.subhead--osd { font-weight: 600; font-size: var(--d-1); letter-spacing: 0.12em; text-transform: uppercase; color: var(--faint); margin: var(--s-7) 0 var(--s-2); }
.account-danger { max-width: 22rem; }
.account-danger .record { margin-top: var(--s-4); }

/* --- buttons and links ------------------------------------------------- */

.go {
  display: inline-block;
  background: var(--accent); border: 0; border-radius: var(--r-btn);
  color: var(--on-lime);
  font-family: var(--display); font-size: var(--d-2);
  text-transform: uppercase; font-weight: 400; letter-spacing: 0.02em;
  padding: 0.7rem 1.4rem; cursor: pointer; text-align: center; text-decoration: none;
}
.go:hover { background: var(--accent-bright); }

.actions { margin-top: 1.75rem; display: flex; gap: 1.25rem; align-items: center; flex-wrap: wrap; }

.quiet {
  background: none; border: 0; color: var(--faint);
  font: inherit; font-size: var(--t-1); padding: 0; cursor: pointer;
  text-decoration: underline; text-underline-offset: 3px;
  text-decoration-color: var(--line);
}
.quiet:hover { color: var(--ink); }

/* --- consent ----------------------------------------------------------- */

/* 24x24 IS A FLOOR, NOT A PREFERENCE. WCAG 2.2 AA (target size, minimum) asks
   for 24x24 CSS px on a pointer target, and this box was 16x16 -- the smallest
   hit area in the product, on the one control that gates both signing up and
   spending credits, and the only element on the page that failed an AA
   criterion. The two ".linky" labels nearby are smaller still and are fine:
   they sit inline inside a sentence, which the criterion exempts. This does
   not, so it does not get the exemption.

   "align-items: flex-start" on the row plus a near-zero top margin is what
   keeps the taller box optically level with the first line of 13px consent
   text; the old 0.35rem was compensating for a box 8px shorter than this one,
   so it has to come down as the box goes up, or the tick floats below the
   sentence it belongs to. */
.check { display: flex; gap: 0.8rem; align-items: flex-start; cursor: pointer; }
.check input {
  margin-top: 0;
  accent-color: var(--accent);
  flex: 0 0 auto;
  width: 1.5rem;
  height: 1.5rem;
}
.consent-text span { display: block; color: var(--ink); font-size: var(--t-1); }
.consent-text span + span { margin-top: 0.5rem; }
/* The immediate-supply acknowledgement, which sits between the plan and its Buy
   button rather than at the end of a long form the way the consent gate does.
   It needs air on both sides: flush against the button it reads as part of the
   button, and flush against the price it reads as part of the price. */
/* THE ACKNOWLEDGEMENT IS LAW, SO IT STAYS -- BUT IT WAS SHOUTING. §45D recorded
   it and let it go: the sentence renders once per PAID rung, so it appears
   twice on /pricing, and in a narrow column it wrapped to about eight ragged
   lines each. Two identical eight-line legal blobs were the heaviest thing on
   the page, above the price and above the button.
   It cannot be hoisted into one shared line -- that needs one form to own a
   field the other posts, which the checkout guard forbids by design. So it is
   made QUIET instead of shared: label size, tighter leading, and a measure wide
   enough that it sets as a short paragraph rather than a ragged column. The
   words are unchanged; consent copy is not something to trim for looks. */
.check--buy { margin: var(--s-4) 0 var(--s-5); text-align: left; gap: var(--s-2); }
.check--buy .lbl,
.check--buy span { font-size: var(--t-label); line-height: 1.45; color: var(--ink-soft); }

/* --- the sign-in dialog ------------------------------------------------- */

/* THE DIALOG IS A PIECE OF THE APPLICATION, not a piece of the landing: it is
   the same two doors the /login page renders, so it is a card on the ground
   like every other card, and a person meets one sign-in whichever door they
   came through. There is one world now, so it restates no tokens of its own --
   the block that used to sit here existed only because a second ground did.
   A browser test reads the cascade for the typed text, the caret and the two
   foot links, which is the layer that caught them painted for the wrong
   surface once already. */
.signin { border: 0; padding: 0; background: transparent; max-width: min(26rem, calc(100vw - 2rem)); }
.signin::backdrop { background: color-mix(in srgb, var(--ground) 72%, transparent); }
.signin-box {
  position: relative;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: var(--s-8) var(--s-6) var(--s-6);
  text-align: center;
}
/* The close control is a real 44px target rather than a decorative glyph --
   SC 2.5.8 asks 24x24 and §6b already had to fix this product's consent box
   for exactly that reason. */
.signin-x {
  position: absolute; top: var(--s-3); right: var(--s-3);
  width: 44px; height: 44px; line-height: 1;
  border: 0; background: transparent; cursor: pointer;
  font-size: var(--t-4); color: var(--ink-soft);
}
.signin-x:hover { color: var(--ink); }
.signin-t { font-family: var(--display); font-size: var(--d-4); text-transform: uppercase; letter-spacing: 0; line-height: 0.92; color: var(--ink); margin: 0 0 var(--s-2); font-weight: 400; }
.signin-sub { color: var(--ink-soft); font-size: var(--t-1); margin: 0 0 var(--s-6); }
/* One door per row, full width, in the order the reference sets: the provider
   first, then the password. Stacked rather than side by side so neither reads
   as the lesser option. */
.signin-way, .signin-go {
  display: block; width: 100%;
  font: inherit; font-size: var(--t-2);
  padding: var(--s-3) var(--s-4);
  border: 0; border-radius: var(--r-sm);
  cursor: pointer;
}
.signin-way { background: var(--ground); color: var(--ink); border: 1px solid var(--line); border-radius: var(--r-btn); }
.signin-way:hover { background: var(--card); }
.signin-go { background: var(--lime); color: var(--on-lime); border-radius: var(--r-btn); margin-top: var(--s-4); }
.signin-go:hover { background: var(--lime-hover); }
/* The rule through the "or" is a 1px fill rather than a border, so it takes
   the outline token like every other line in the sheet. */
.signin-or {
  margin: var(--s-5) 0;
  font-size: var(--t-label); letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--ink-soft);
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: var(--s-3);
}
.signin-or::before, .signin-or::after {
  content: ''; height: 1px;
  background: var(--line);
}
.signin-form { text-align: left; margin: 0; }
.signin-l { display: block; font-size: var(--t-label); letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-soft); margin: var(--s-4) 0 var(--s-1); }
.signin-i {
  display: block; width: 100%; box-sizing: border-box;
  font: inherit; font-size: var(--t-2);
  padding: var(--s-3);
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--ground); color: var(--ink);
}
.signin-alt { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--s-4); margin: var(--s-6) 0 0; font-size: var(--t-label); }

/* --- pricing (2026-09-06): the lime band, three cards, the comparison ---- */

.pricing-hero { padding: var(--s-7) var(--s-6); text-align: center; margin: 0 0 var(--s-7); }
.pricing-t { font-family: var(--display); text-transform: uppercase; font-size: var(--t-8); line-height: 0.9; letter-spacing: 0; font-weight: 400; margin: 0 0 var(--s-4); color: var(--on-lime); }
.pricing-hero .lede { color: var(--on-lime-soft); max-width: 46ch; margin: 0 auto; }
.pricing-hero .balance { font-weight: 600; color: var(--on-lime); margin: var(--s-4) 0 0; }
.pricing-hero .notice { margin: var(--s-4) auto 0; max-width: 40ch; }

/* THREE EQUAL COLUMNS: a pricing comparison is the one place parallel things
   at parallel size is the honest layout. Two when the Free card is absent. */
.tiers { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s-5); align-items: start; margin: 0 0 var(--s-8); }
.tiers--two { grid-template-columns: repeat(2, minmax(0, 1fr)); max-width: 52rem; margin-inline: auto; }
.tier { padding: var(--s-6) var(--s-5); position: relative; }
.tier--lime { transform: translateY(-0.75rem); }
/* THE FLAG WRAPS RATHER THAN BEING CUT (2026-09-06). A flex item will not
   shrink below its content, so in the three-column grid the name and a
   126px chip needed 223px of a 219px card and the chip ran past the edge,
   where the lime panel's own clipping sliced it in half. Wrapping is the
   honest answer: the row genuinely does not fit, and a flag on its own line
   still reads as the flag. A browser test measures it, because no markup
   test can see an overflow. */
.tier-name { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: var(--s-2) var(--s-3); font-size: var(--t-label); font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; margin: 0 0 var(--s-4); }
.tier .price { font-family: var(--display); text-transform: uppercase; font-size: var(--t-8); line-height: 0.9; letter-spacing: 0; margin: 0 0 var(--s-1); }
.tier .per { font-size: var(--t-1); margin: 0 0 var(--s-5); color: var(--ink-soft); }
.tier--lime .per, .tier--lime .checks, .tier--lime .check--buy span, .tier--lime .hint { color: var(--on-lime-soft); }
.checks { list-style: none; padding: 0; margin: 0 0 var(--s-5); font-size: var(--t-1); color: var(--ink-soft); }
.checks li { position: relative; padding: 0.35rem 0 0.35rem 1.4rem; }
.checks li::before { content: '✓'; position: absolute; left: 0; font-weight: 600; }
.tier .mark { font-size: var(--t-label); font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; background: var(--on-lime); color: var(--lime); border-radius: 999px; padding: 0.15rem 0.6rem; }
.plan .mark { position: absolute; top: -0.65rem; left: 1.4rem; background: var(--card); color: var(--ink); border: 1px solid var(--line); }
/* The button on the lime card inverts: black on lime, the way the hero's does. */
.tier--lime .record { background: var(--on-lime); color: var(--lime); }
.tier--lime .record:hover { color: var(--lime-hover); }
.tier--lime .check--buy input { accent-color: var(--on-lime); }
.tier .record { margin-top: 0; }
.tier > .hint { margin: var(--s-3) 0 0; text-align: center; }
/* The plainer pack takes the outlined button, so the solid one appears once in
   the row, on the card the page recommends. */
.record--way { background: var(--card); color: var(--ink); border: 1px solid var(--line); }
.record--way:hover { background: var(--card); color: var(--ink); }
/* Priced plans (fixtures only today) keep the struck/ghost grammar. */
.tiers:has(.plan--current) .plan { opacity: var(--ghost); }
.tiers:has(.plan--current) .plan--current { opacity: 1; }
@media (max-width: 48rem) { .tiers, .tiers--two { grid-template-columns: 1fr; } .tier--lime { transform: none; } }

/* THE COMPARISON. An outlined table, the recommended column lit. */
.compare { max-width: 52rem; margin: 0 auto var(--s-8); }
.compare-t { font-family: var(--display); text-transform: uppercase; font-size: var(--d-4); line-height: 0.92; letter-spacing: 0; font-weight: 400; margin: 0 0 var(--s-5); }
.compare-scroll { overflow-x: auto; }
.compare table { width: 100%; border-collapse: separate; border-spacing: 0; border: 1px solid var(--line); border-radius: var(--r); overflow: hidden; font-size: var(--t-1); }
.compare th, .compare td { padding: var(--s-3) var(--s-4); text-align: left; vertical-align: top; }
.compare thead th { font-size: var(--t-label); font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-soft); }
.compare tbody th { font-weight: 600; color: var(--ink); }
.compare td { color: var(--ink-soft); }
.compare .lit { background: var(--lime); color: var(--on-lime); }
.compare tbody tr + tr th, .compare tbody tr + tr td { border-top: 1px solid var(--line); }
.pricing-foot { max-width: 44rem; margin: 0 auto; }

/* --- the world's primitives (2026-09-06) --------------------------------- */

.vh-svg { position: absolute; width: 0; height: 0; overflow: hidden; }

/* A LIME PANEL. The speckle is a white layer under multiply, so it darkens the
   lime by a hair where the noise is dark and leaves it alone elsewhere: ink on
   paper, not video noise. Children sit above the layer. */
.lime { position: relative; overflow: hidden; background: var(--lime); color: var(--on-lime); border-radius: var(--r); }
.lime > * { position: relative; z-index: 1; }
.lime::before { content: ''; position: absolute; inset: 0; z-index: 0; pointer-events: none; background: #FFFFFF; filter: url(#speckle); mix-blend-mode: multiply; opacity: 0.32; }

/* AN OUTLINED CARD ON THE GROUND. Flat: no texture, ever. */
.card { background: var(--card); border: 1px solid var(--line); border-radius: var(--r); }

/* THE FACT CARDS: crumpled paper, and only here. */
.fact { position: relative; overflow: hidden; margin: 0; border-radius: var(--r); padding: var(--s-6) var(--s-5); min-height: 14rem; }
.fact > * { position: relative; z-index: 1; }
.fact::before { content: ''; position: absolute; inset: 0; z-index: 0; pointer-events: none; background: #FFFFFF; filter: url(#paper); mix-blend-mode: multiply; opacity: 0.55; }
.fact--lime { background: var(--lime); color: var(--on-lime); }
.fact--white { background: #FFFFFF; color: var(--on-lime); }
.fact blockquote { margin: 0; }
.fact blockquote p { margin: 0; font-family: var(--display); text-transform: uppercase; font-size: var(--d-4); line-height: 0.92; letter-spacing: 0; }
.fact figcaption { margin: var(--s-4) 0 0; font-size: var(--t-1); color: var(--on-lime-soft); }

/* THE FAQ: native rows, outlined, the glyph switched by [open]. */
.faq { max-width: 52rem; margin: 0 auto var(--s-8); }
.faq-t { font-family: var(--display); text-transform: uppercase; font-size: var(--d-4); line-height: 0.92; letter-spacing: 0; font-weight: 400; margin: 0 0 var(--s-5); }
.faq-row { border: 1px solid var(--line); border-radius: var(--r-sm); margin: 0 0 var(--s-3); padding: 0 var(--s-5); }
.faq-row > summary { display: flex; justify-content: space-between; align-items: center; gap: var(--s-4); cursor: pointer; list-style: none; padding: var(--s-4) 0; font-weight: 600; }
.faq-row > summary::-webkit-details-marker { display: none; }
.faq-glyph::before { content: '+'; font-family: var(--display); font-size: var(--t-4); line-height: 1; color: var(--ink-soft); }
details[open] > summary .faq-glyph::before { content: '−'; }
.faq-row > p { margin: 0 0 var(--s-5); color: var(--ink-soft); max-width: 66ch; }

/* THE FOOTER: two columns, the giant word, the fine print. */
.foot-cols { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-6); max-width: 32rem; margin: 0 0 var(--s-7); }
.foot-h { font-size: var(--t-label); font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-soft); margin: 0 0 var(--s-3); }
.foot-col ul { list-style: none; margin: 0; padding: 0; }
.foot-col li { margin: 0 0 var(--s-2); }
.foot-col .quiet { text-decoration: none; color: var(--ink); font-size: var(--t-2); }
.foot-col .quiet:hover { color: var(--lime); }
.foot-mark { font-family: var(--display); text-transform: uppercase; font-size: var(--t-mark); line-height: 0.9; letter-spacing: 0; color: var(--lime); margin: 0 0 var(--s-5); white-space: nowrap; overflow: hidden; }
@media (max-width: 30rem) { .foot-cols { grid-template-columns: 1fr; } }

/* --- foot -------------------------------------------------------------- */

/* --- the landing (2026-09-06): the lime poster, the tape first ------------ */

/* THE PRIMARY BUTTON NEEDS NO RESTATEMENT HERE ANY MORE. It used to, because
   the landing had a ground of its own and the button's label had been solved
   against a different accent. One world, one accent, one answer: the label is
   the ink solved for lime, wherever the button is drawn.

   DESIGN.md owns this world. Borders are ALLOWED now and are always the
   outline token; what is still forbidden is a rule drawn BETWEEN two things,
   and any texture on the ground or on a dark card. */

/* TEXTURE BELONGS TO THE TAPE AND TO NOTHING ELSE, and as of 2026-08-24 that is
   structural rather than remembered. This used to be a "display: none" naming
   the two converted pages, so login, signup, pricing, status and result all
   kept wearing the page-wide grain plate -- precisely the "AI tool with a
   filter over it" impression the product exists to avoid. The plate is deleted,
   not switched off: a suppression rule is one tidy-up away from being undone,
   and a list of exceptions is a list somebody forgets to add to. */

/* The page owns its own gutters: the hero is a panel with a margin, the band
   runs edge to edge, and everything else sits in a 76rem column. No 100vw
   anywhere -- a viewport unit includes the scrollbar and is how a full-bleed
   band buys a horizontal scrollbar. */
body.page-landing { padding: 0 0 var(--s-8); }
.page-landing .wrap { max-width: none; }
.page-landing .inner { max-width: 76rem; margin: 0 auto; padding: 0 1.15rem; }
.page-landing .foot { max-width: 76rem; margin: var(--s-8) auto 0; padding: 0 1.15rem; }

/* THE ANODE GAUZE IS DELETED, and it resolved a contradiction DESIGN.md had
   been carrying rather than merely retiring a texture.
 *
 * The Struck palette listed '--gauze' as "the anode mesh, 1px on 4px, fixed,
 * over everything". Four sections earlier the same file says the interface
 * carries "no grain, scanlines, noise or vignette" and that "every trace of
 * texture on any page exists inside a tape frame". A 1px-on-4px repeating
 * gradient across the whole viewport is scanlines; the two rules could not both
 * be followed, and the mesh was only ever invisible enough to hide that.
 *
 * The texture rule is the stronger of the two -- it is the one that keeps the
 * chrome from competing with the artifact, which is the product's whole thesis
 * -- so the mesh goes and the rule stands. Deleted rather than suppressed, for
 * the reason the grain plate was: a 'display: none' naming today's pages is one
 * tidy-up away from being switched back on.
 *
 * THE BLOOM WENT WITH IT ON 2026-09-06. It was a soft radial glow tinted with
 * the accent of a world that no longer exists, laid over the whole viewport --
 * which is a texture on the ground by any other name, and this world's ground
 * and cards are flat. */

/* the hoisted landing state, same technique as the signed-in page: fixed, so
   focusing one can never scroll the document. */
.lstate { position: fixed; top: 0; left: 0; width: 1px; height: 1px; opacity: 0; margin: 0; pointer-events: none; }

/* THE HERO. A lime panel with room at the foot for the tape to break out of. */
.hero { margin: var(--s-4) var(--s-4) 0; padding: 0 0 12rem; }
.hero-nav { display: flex; align-items: center; justify-content: space-between; gap: var(--s-4); flex-wrap: wrap; padding: var(--s-5) var(--s-6) 0; }
.hero-nav .wordmark { color: var(--on-lime); }
.hero-links { display: flex; gap: var(--s-5); }
.hero-links a { font-size: var(--t-label); font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--on-lime); text-decoration: none; }
.hero-links a:hover { text-decoration: underline; text-underline-offset: 4px; }
.navpill, .hero-cta { display: inline-block; font-family: var(--display); text-transform: uppercase; letter-spacing: 0.02em; background: var(--on-lime); color: var(--lime); border-radius: var(--r-btn); text-decoration: none; }
.navpill { font-size: var(--d-2); padding: 0.5rem 0.9rem; }
.hero-cta { font-size: var(--d-2); padding: 0.8rem 1.6rem; }
.navpill:hover, .hero-cta:hover { color: var(--lime-hover); }
.hero-body { text-align: center; max-width: 48rem; margin: 0 auto; padding: var(--s-8) var(--s-5) var(--s-6); }
/* THE HERO IN THE DISPLAY FACE. It is the largest heading in the product and
   the one place --t-hero is used; uppercase, untracked, set tight at 0.9 so
   two lines read as one block, and held to twelve characters a line. The one
   thing that moved on 2026-09-06 is its colour: the hero sits on lime now. */
.hero-line { font-family: var(--display); font-size: var(--t-hero); line-height: 0.9; letter-spacing: 0; text-transform: uppercase; font-weight: 400; color: var(--on-lime); margin: 0 auto var(--s-5); max-inline-size: 12ch; text-wrap: balance; }
.hero-sub { color: var(--on-lime-soft); font-size: var(--t-3); line-height: 1.5; max-width: 46ch; margin: 0 auto var(--s-5); }
.hero-fine { font-size: var(--t-1); color: var(--on-lime-soft); margin: var(--s-3) 0 0; }
/* THE COUNTER RULER: tick marks along the panel's foot, the readout in the
   label role. A repeating gradient is a fill, not a border. */
.ruler { display: flex; justify-content: space-between; align-items: flex-end; height: 1.75rem; margin-top: var(--s-6); padding: 0 var(--s-6); font-size: var(--t-label); font-weight: 600; letter-spacing: 0.08em; color: var(--on-lime-soft); background-image: repeating-linear-gradient(to right, var(--on-lime) 0 1px, transparent 1px 12px); background-size: 100% 8px; background-repeat: no-repeat; background-position: 0 100%; }
.ruler span { background: var(--lime); padding: 0 3px; }
/* THE TAPE BREAKS OUT OF THE PANEL onto the dark ground -- the reference's
   move, and what makes the tape the largest thing on the first screen. */
.hero-tape { position: relative; z-index: 2; width: 82%; max-width: 76rem; margin: -10rem auto 0; }
.tape-media { display: block; width: 100%; aspect-ratio: 16 / 9; background: var(--card); border-radius: 8px; object-fit: cover; }
.tape-media--tall { aspect-ratio: 9 / 16; }
.tape-media--four { aspect-ratio: 4 / 3; }
.tape-cap { font-size: var(--t-label); font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--ink-soft); margin: var(--s-3) 0 0; }
.hero-tape .tape-cap, .demo-tape .tape-cap { text-align: left; }
@media (max-width: 48rem) {
  .hero { margin: var(--s-3) var(--s-3) 0; padding-bottom: 5rem; }
  .hero-nav { padding: var(--s-4) var(--s-4) 0; }
  .hero-links { order: 3; width: 100%; }
  .hero-body { padding: var(--s-7) var(--s-3) var(--s-5); }
  .hero-tape { width: calc(100% - 2.3rem); margin-top: -3.5rem; }
}

/* THE MANIFESTO: the giant sentence, lime and ink alternating by phrase, the
   stickers inline and tilted. */
.manifesto { padding: var(--s-8) 1.15rem; text-align: center; }
.manifesto-line { font-family: var(--display); text-transform: uppercase; font-size: var(--t-8); line-height: 1.05; letter-spacing: 0; margin: 0; color: var(--ink); text-wrap: balance; }
.manifesto-line .lit { color: var(--lime); }
.sticker { display: inline-block; height: 1em; width: auto; vertical-align: -0.15em; border-radius: 4px; margin: 0 0.1em; transform: rotate(-4deg); }
.sticker--2 { transform: rotate(3deg); }
.sticker--3 { transform: rotate(-2deg); }
.sticker--4 { transform: rotate(5deg); }

/* THE TWO CARDS. */
.how2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-5); padding-bottom: var(--s-8); }
.how-card { padding: var(--s-5); }
.card-t { font-family: var(--display); text-transform: uppercase; font-size: var(--d-3); line-height: 0.92; letter-spacing: 0; font-weight: 400; margin: 0 0 var(--s-4); color: var(--ink); }
.card-d { color: var(--ink-soft); margin: var(--s-4) 0 0; max-width: 46ch; }
.own-pair { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-2); }
.own-pair img { display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: 6px; }
@media (max-width: 48rem) { .how2 { grid-template-columns: 1fr; } }

/* the ghost stack: every place present at once, one struck */
/* THE OPTIONS ARE STILL A LIST IN THE MARKUP AND A RAIL ONLY HERE. They are a
   set of choices and a screen reader should meet them as one; horizontal is a
   presentation of that, not a different thing. Same scroll-snap mechanic as the
   place cards on the signed-in page, so the two screens answer a swipe the same
   way rather than inventing a second pattern.

   The gutters are negative margins against the menu's own padding, so the rail
   bleeds to the plate's edges: a row that stops short of them looks clipped,
   and a row that reaches them reads as continuing past the frame -- which it
   does. */
.lrail {
  list-style: none;
  display: flex; gap: var(--s-5);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  margin: 0 calc(-1 * var(--s-5)) var(--s-4);
  padding: var(--s-1) var(--s-5) var(--s-3);
  scrollbar-width: thin;
  overscroll-behavior-x: contain;
  /* The bleed above is reaching for "this continues past the frame". Measured
     on the landing at 1280 it was not arriving: scrollWidth 3066 against a 736
     client, so SIX of the eight places sit outside the frame and the first
     thing outside it is a word cut in half. A guillotined word reads as broken
     rather than as scrollable, and the place list is the whole appeal -- a
     visitor who sees one and a half of them concludes there are two.
     The mask dissolves that edge instead of cutting it, which is the signal
     the bleed already wanted. It is not a border and not a divider: it is the
     absence of paint, which is the thing DESIGN.md's no-rules rule protects.
     Kept narrow so the last place is still legible once it is scrolled to. */
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - var(--s-6)), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - var(--s-6)), transparent);
}
.lrail li { margin: 0; flex: 0 0 auto; scroll-snap-align: center; }
.lrail .lopt { white-space: nowrap; }
.lopt {
  display: block; cursor: pointer;
  font-family: var(--display); font-size: var(--d-3); line-height: 1;
  text-transform: uppercase; letter-spacing: 0;
  color: var(--ink); padding: var(--s-1) 0;
}
/* NOT A GHOST, SINCE 2026-09-07. The rail's unchosen options sat at --ghost,
   a floor solved for --ink over the FLAT ground (DESIGN.md, Ghosts) and applied
   over a photograph, where the same 0.5 measured 2.1-4.4:1 beside the strokes.
   The precedent is §63B's footer: on the band's picture a word is full
   opacity plus its shadow, and the unchosen/chosen distinction is carried by
   colour -- --on-image against lime -- which is what DESIGN.md already says of
   a text option card. Hover is an underline for the same reason: an opacity
   step is exactly the thing that cannot be afforded over a picture. */
/* THE INDEX TAKES THE OPTION'S OWN COLOUR and is distinguished by size, at
   0.5em: the soft tier is not painted over the photograph at all (it sits
   outside the scrim solve), and size is the step that survives whatever the
   picture behind it does. */
.lopt .lidx { font-size: 0.5em; letter-spacing: 0.22em; color: var(--ink); margin-right: var(--s-3); vertical-align: 0.3em; }
.lopt:hover { text-decoration: underline; text-underline-offset: 6px; }

/* THE BAND: the place photograph across the whole width, the rail over it.
   The ground and the scrim are positioned inside the band rather than fixed
   to the viewport, which is the only change to a mechanism that is otherwise
   the 2026-08-27 landing's: the same hoisted radios, the same generated
   layers, the same script swapping the loop, the same per-place scrim. */
.band { position: relative; overflow: hidden; padding: var(--s-8) 0; color: var(--on-image); }
.band .bgs { position: absolute; inset: 0; z-index: 0; }
.band .bg { position: absolute; inset: -6%; filter: blur(10px) saturate(0.8); }
/* The opacity here is the scrim for a place with NO measurement -- solved as
   though its loop were a white photograph, mean and highlight both, which is
   §31's rule for text on a picture nobody has measured. Every shipped place
   overrides it with its own solve, keyed on its radio. It used to be a typed
   0.5, which for the brightest place sat under that place's own number. */
.band .scrim { position: absolute; inset: 0; z-index: 1; opacity: 0.87; }
.band-in { position: relative; z-index: 2; }
.band-t { font-family: var(--display); text-transform: uppercase; font-size: var(--d-4); line-height: 0.92; letter-spacing: 0; font-weight: 400; margin: 0 0 var(--s-5); color: var(--on-image); text-shadow: 0 1px 14px rgba(22, 22, 24, 0.7); }
.band .lopt { color: var(--on-image); text-shadow: 0 1px 14px rgba(22, 22, 24, 0.7); }
.band .lopt .lidx { color: var(--on-image); }
/* THE HINT IS THE INK THE SCRIM IS SOLVED FOR, demoted by size. It painted
   --on-image-soft, a tier outside the solve, and the pixels said what that
   costs: 1.57:1 over the Tokyo neon (2026-09-07). Over a photograph hierarchy
   is size, which survives whatever the picture does; a tier does not. */
.band-hint { font-size: var(--t-1); color: var(--on-image); margin: var(--s-3) 0 0; text-shadow: 0 1px 10px rgba(22, 22, 24, 0.85); }

/* THE BEFORE/AFTER WIPE.
   One custom property drives everything: the clip on the top half, where the
   divider sits, and nothing else. WIPE_SCRIPT sets it from a range input; the
   inline style on the figure is the no-script value, so the default state is a
   real 50/50 composite rather than an empty frame.

   NO BORDER ANYWHERE IN HERE. The divider is a filled 2px element, not a rule
   on a box, and the grip is a filled disc -- an outline round either would be
   a line drawn between two halves of one picture. */
  /* The no-script value, and it lives HERE rather than on a style attribute:
     style-src self refuses an inline style attribute outright, and a hash
     cannot rescue one -- that needs unsafe-hashes. Caught by the browser test,
     invisible to every markup assertion. */
.wipe { --wipe: 50%; }
.wipe {
  position: relative; margin: 0; aspect-ratio: 16 / 9; overflow: hidden;
  border-radius: 2px; background: var(--ground);
}
.wipe img { display: block; width: 100%; height: 100%; object-fit: cover; }
.wipe-under { position: absolute; inset: 0; }
/* The clipped half. 'clip-path' does not affect layout, which is why the
   browser test reads the resolved clip rather than a bounding rect. */
.wipe-clip {
  position: absolute; inset: 0;
  clip-path: polygon(0 0, var(--wipe) 0, var(--wipe) 100%, 0 100%);
}
.wipe-line {
  position: absolute; top: 0; bottom: 0; left: var(--wipe);
  width: 2px; margin-left: -1px; background: var(--ink);
  box-shadow: 0 0 12px rgba(22, 22, 24, 0.55);
}
/* THE GRIP IS DRAWN ONLY WHERE IT CAN BE DRAGGED. WIPE_SCRIPT adds
   'wipe--live'; with no script the figure is an honest static split, and a
   round handle sitting on that split would be a control that looks draggable
   and is not -- section 49D's dead own-place card, which passed every markup
   test it had because presence was never the thing that was wrong. */
.wipe-grip { display: none; }
.wipe--live .wipe-grip {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 2.75rem; height: 2.75rem; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--ink); color: var(--ground);
  box-shadow: 0 2px 18px rgba(22, 22, 24, 0.5);
}
.wipe-cap {
  position: absolute; left: 0; right: 0; bottom: 0;
  display: flex; justify-content: space-between;
  padding: var(--s-3) var(--s-4);
  font-size: var(--d-1); font-weight: 600; letter-spacing: 0.24em;
  text-transform: uppercase; color: var(--on-image); pointer-events: none;
  text-shadow: 0 1px 10px rgba(22, 22, 24, 0.85);
}
/* The control: a real range stretched over the picture, invisible because the
   divider and grip above ARE its thumb as far as a viewer is concerned.
   Pointer-down anywhere on the picture jumps it and begins a drag. */
.wipe-range {
  position: absolute; inset: 0; width: 100%; height: 100%;
  margin: 0; padding: 0; opacity: 0; cursor: ew-resize;
  -webkit-appearance: none; appearance: none; background: transparent;
}
.wipe-range::-webkit-slider-thumb { -webkit-appearance: none; width: 3rem; height: 100%; }
.wipe-range::-moz-range-thumb { width: 3rem; height: 100%; border: 0; opacity: 0; }
/* The ring has to be drawn on something visible, and the range itself is not.
   §16's ruling: the indicator belongs on the control a person can actually see.
   The combinator is ':has()' and not '~' on purpose: WIPE_SCRIPT appends the
   range LAST, so the divider it needs to reach is an EARLIER sibling and a
   general-sibling selector would match nothing at all -- silently, which is how
   a focus indicator goes missing for a year.
   The grip is drawn in ink, so its ring cannot be: it takes the go colour,
   which is the one thing on the page guaranteed to read against it. */
.wipe--live:has(.wipe-range:focus-visible) .wipe-grip {
  outline: 3px solid var(--lime); outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) { .wipe-grip { transition: none; } }

/* THE FACTS: three cards; Task 3 owns .fact. */
.facts3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s-5); padding: var(--s-8) 0; }
@media (max-width: 48rem) { .facts3 { grid-template-columns: 1fr; } }

/* THE DEMO: the two other shapes standing on the ground, the button, and the
   flip counter in the tape's own readout face -- one of the three places it
   is allowed in the chrome. Static digits; nothing animates. */
.demo { text-align: center; padding: 0 1.15rem var(--s-8); }
.demo-t { font-family: var(--display); text-transform: uppercase; font-size: var(--t-8); line-height: 0.92; letter-spacing: 0; font-weight: 400; margin: 0 0 var(--s-6); color: var(--ink); }
.demo-tapes { display: grid; grid-template-columns: minmax(0, 9fr) minmax(0, 16fr); gap: var(--s-5); align-items: end; max-width: 56rem; margin: 0 auto var(--s-6); text-align: left; }
.demo-tape { margin: 0; }
/* BLOCK, OR THE AUTO MARGINS ARE A DECLARATION THAT DOES NOTHING. The shared
   button rule makes this inline-block, and auto side margins on an inline box
   centres nothing -- so the button and the counter below it stayed on ONE
   line, the button hard against the counter's first digit. Measured on the
   running page: the button at 179-321 and the counter starting at 325, twelve
   pixels apart vertically. Block-level with a max-content width keeps the pill
   hugging its label and puts the counter on its own line, where the section's
   own text-align centres it.

   AND IT NEEDS ITS OWN SURFACE. The shared rule paints this pill's background
   dark, which is right for a pill sitting ON the lime hero and wrong here,
   where the page behind it is dark too -- the fill and the ground read as one
   colour and the pill disappears, label and all. Lime means go, so this one
   inverts: a lime surface with a dark label, and the hover lifts the surface
   rather than recolouring the label to something that would fail against it. */
.demo-cta { display: block; width: max-content; margin: 0 auto; background: var(--lime); color: var(--on-lime); }
.demo-cta:hover { color: var(--on-lime); filter: brightness(1.06); }
.flip { display: inline-flex; gap: 0.3em; font-family: var(--osd); font-size: var(--t-8); color: var(--ink); margin: var(--s-6) auto 0; letter-spacing: 0.04em; }
.flip span { display: inline-block; background: var(--card); border: 1px solid var(--line); border-radius: 6px; padding: 0.1em 0.3em; min-width: 1.1em; line-height: 1.1; }
.flip .gap { background: none; border: 0; min-width: 0.3em; padding: 0; }
/* THE COUNTER IS TEN FIXED BOXES IN A ROW THAT CANNOT WRAP, so its width is
   arithmetic rather than taste: eight digits at 1.1em plus eleven gaps of
   0.3em is 12.1em, whatever the em happens to be. At the display size that is
   484px, and the smallest width this product tests at is 320. Measured on the
   running page before this rule existed: 182px of horizontal scroll, and it
   was the ONLY thing on the landing escaping its clip. Wrapping was the other
   way out and is worse -- a date broken across two lines reads as damage --
   so the em comes down instead.

   AND IT COMES DOWN TWO STEPS RATHER THAN ONE, which is the measurement worth
   keeping. One step lands the row at 283px inside 283px of column: it fits,
   and it fits with nothing to spare, so the moment the readout face is not
   the one paint uses -- the swap before a self-hosted font arrives, or a
   machine that refuses it -- the digits widen and the page scrolls sideways
   again. A margin of zero is not a margin. Two steps leaves about a fifth of
   the column free, which is enough to absorb the fallback metrics. */
@media (max-width: 48rem) {
  .demo-tapes { grid-template-columns: 1fr; }
  .flip { font-size: var(--t-3); }
}

/* A QUERY CONTAINER FOR ITS OWN WIDTH, so the giant word below can size off
   the column it actually sits in instead of the viewport -- see --t-mark. */
.foot { container-type: inline-size; margin-top: var(--s-8); padding-top: 0; border-top: 0; color: var(--faint); font-size: var(--t-1); }
.foot p { margin: 0 0 0.4rem; }
.fine { color: var(--faint); }

/* --- when there is no CSS at all --------------------------------------- */
/* (nothing to do here; the markup is ordered so the page reads top to bottom
   as headings, labels and controls with no CSS applied at all) */

/* --- generated from the catalog --- */
.thumb--pl-amalfi-afternoon{background-image:url('/places/amalfi-afternoon.jpg'), linear-gradient(158deg, hsl(187 57% 28%) 0%, hsl(175 49% 14%) 100%);}
.bg--pl-amalfi-afternoon{background-image:url('/places/amalfi-afternoon.jpg'), linear-gradient(158deg, hsl(187 57% 28%) 0%, hsl(175 49% 14%) 100%);}
#pl-amalfi-afternoon:checked~.wrap .bgs .bg--pl-amalfi-afternoon{opacity:1;}
#pl-amalfi-afternoon:checked~.wrap .scrim{opacity:0.69;}
#pl-amalfi-afternoon:checked~.wrap .lopt--pl-amalfi-afternoon{color:var(--lime);}
#pl-amalfi-afternoon:checked~.wrap .lopt--pl-amalfi-afternoon .lidx{color:var(--lime);}
#pl-amalfi-afternoon:focus-visible~.wrap .lopt--pl-amalfi-afternoon{text-decoration:underline;text-underline-offset:6px;text-decoration-color:var(--lime);}
#pl-amalfi-afternoon:focus-visible~.wrap .placecard--pl-amalfi-afternoon{opacity:1;outline:2px solid var(--ink);outline-offset:2px;}#pl-amalfi-afternoon:focus-visible~.wrap .placecard--pl-amalfi-afternoon .thumb{opacity:1;}
#pl-amalfi-afternoon:checked~.wrap .placecard--pl-amalfi-afternoon{transform:scale(1.03);box-shadow:0 0 0 2px var(--lime);}
#pl-amalfi-afternoon:checked~.wrap .placecard--pl-amalfi-afternoon .thumb{opacity:1;}
#pl-amalfi-afternoon:checked~.wrap .placecard--pl-amalfi-afternoon .badge{opacity:1;}
#pl-amalfi-afternoon:checked~.wrap .dot--pl-amalfi-afternoon{background:var(--accent);}
.thumb--pl-kuechentisch-fruehstueck{background-image:url('/places/kuechentisch-fruehstueck.jpg'), linear-gradient(158deg, hsl(47 48% 29%) 0%, hsl(35 40% 15%) 100%);}
.bg--pl-kuechentisch-fruehstueck{background-image:url('/places/kuechentisch-fruehstueck.jpg'), linear-gradient(158deg, hsl(47 48% 29%) 0%, hsl(35 40% 15%) 100%);}
#pl-kuechentisch-fruehstueck:checked~.wrap .bgs .bg--pl-kuechentisch-fruehstueck{opacity:1;}
#pl-kuechentisch-fruehstueck:checked~.wrap .scrim{opacity:0.62;}
#pl-kuechentisch-fruehstueck:checked~.wrap .lopt--pl-kuechentisch-fruehstueck{color:var(--lime);}
#pl-kuechentisch-fruehstueck:checked~.wrap .lopt--pl-kuechentisch-fruehstueck .lidx{color:var(--lime);}
#pl-kuechentisch-fruehstueck:focus-visible~.wrap .lopt--pl-kuechentisch-fruehstueck{text-decoration:underline;text-underline-offset:6px;text-decoration-color:var(--lime);}
#pl-kuechentisch-fruehstueck:focus-visible~.wrap .placecard--pl-kuechentisch-fruehstueck{opacity:1;outline:2px solid var(--ink);outline-offset:2px;}#pl-kuechentisch-fruehstueck:focus-visible~.wrap .placecard--pl-kuechentisch-fruehstueck .thumb{opacity:1;}
#pl-kuechentisch-fruehstueck:checked~.wrap .placecard--pl-kuechentisch-fruehstueck{transform:scale(1.03);box-shadow:0 0 0 2px var(--lime);}
#pl-kuechentisch-fruehstueck:checked~.wrap .placecard--pl-kuechentisch-fruehstueck .thumb{opacity:1;}
#pl-kuechentisch-fruehstueck:checked~.wrap .placecard--pl-kuechentisch-fruehstueck .badge{opacity:1;}
#pl-kuechentisch-fruehstueck:checked~.wrap .dot--pl-kuechentisch-fruehstueck{background:var(--accent);}
.thumb--pl-new-york-times-square{background-image:url('/places/new-york-times-square.jpg'), linear-gradient(158deg, hsl(51 58% 34%) 0%, hsl(39 50% 14%) 100%);}
.bg--pl-new-york-times-square{background-image:url('/places/new-york-times-square.jpg'), linear-gradient(158deg, hsl(51 58% 34%) 0%, hsl(39 50% 14%) 100%);}
#pl-new-york-times-square:checked~.wrap .bgs .bg--pl-new-york-times-square{opacity:1;}
#pl-new-york-times-square:checked~.wrap .scrim{opacity:0.65;}
#pl-new-york-times-square:checked~.wrap .lopt--pl-new-york-times-square{color:var(--lime);}
#pl-new-york-times-square:checked~.wrap .lopt--pl-new-york-times-square .lidx{color:var(--lime);}
#pl-new-york-times-square:focus-visible~.wrap .lopt--pl-new-york-times-square{text-decoration:underline;text-underline-offset:6px;text-decoration-color:var(--lime);}
#pl-new-york-times-square:focus-visible~.wrap .placecard--pl-new-york-times-square{opacity:1;outline:2px solid var(--ink);outline-offset:2px;}#pl-new-york-times-square:focus-visible~.wrap .placecard--pl-new-york-times-square .thumb{opacity:1;}
#pl-new-york-times-square:checked~.wrap .placecard--pl-new-york-times-square{transform:scale(1.03);box-shadow:0 0 0 2px var(--lime);}
#pl-new-york-times-square:checked~.wrap .placecard--pl-new-york-times-square .thumb{opacity:1;}
#pl-new-york-times-square:checked~.wrap .placecard--pl-new-york-times-square .badge{opacity:1;}
#pl-new-york-times-square:checked~.wrap .dot--pl-new-york-times-square{background:var(--accent);}
.thumb--pl-schrebergarten-august{background-image:url('/places/schrebergarten-august.jpg'), linear-gradient(158deg, hsl(82 48% 34%) 0%, hsl(70 40% 13%) 100%);}
.bg--pl-schrebergarten-august{background-image:url('/places/schrebergarten-august.jpg'), linear-gradient(158deg, hsl(82 48% 34%) 0%, hsl(70 40% 13%) 100%);}
#pl-schrebergarten-august:checked~.wrap .bgs .bg--pl-schrebergarten-august{opacity:1;}
#pl-schrebergarten-august:checked~.wrap .scrim{opacity:0.61;}
#pl-schrebergarten-august:checked~.wrap .lopt--pl-schrebergarten-august{color:var(--lime);}
#pl-schrebergarten-august:checked~.wrap .lopt--pl-schrebergarten-august .lidx{color:var(--lime);}
#pl-schrebergarten-august:focus-visible~.wrap .lopt--pl-schrebergarten-august{text-decoration:underline;text-underline-offset:6px;text-decoration-color:var(--lime);}
#pl-schrebergarten-august:focus-visible~.wrap .placecard--pl-schrebergarten-august{opacity:1;outline:2px solid var(--ink);outline-offset:2px;}#pl-schrebergarten-august:focus-visible~.wrap .placecard--pl-schrebergarten-august .thumb{opacity:1;}
#pl-schrebergarten-august:checked~.wrap .placecard--pl-schrebergarten-august{transform:scale(1.03);box-shadow:0 0 0 2px var(--lime);}
#pl-schrebergarten-august:checked~.wrap .placecard--pl-schrebergarten-august .thumb{opacity:1;}
#pl-schrebergarten-august:checked~.wrap .placecard--pl-schrebergarten-august .badge{opacity:1;}
#pl-schrebergarten-august:checked~.wrap .dot--pl-schrebergarten-august{background:var(--accent);}
.thumb--pl-space-centre{background-image:url('/places/space-centre.jpg'), linear-gradient(158deg, hsl(226 59% 32%) 0%, hsl(214 51% 12%) 100%);}
.bg--pl-space-centre{background-image:url('/places/space-centre.jpg'), linear-gradient(158deg, hsl(226 59% 32%) 0%, hsl(214 51% 12%) 100%);}
#pl-space-centre:checked~.wrap .bgs .bg--pl-space-centre{opacity:1;}
#pl-space-centre:checked~.wrap .scrim{opacity:0.66;}
#pl-space-centre:checked~.wrap .lopt--pl-space-centre{color:var(--lime);}
#pl-space-centre:checked~.wrap .lopt--pl-space-centre .lidx{color:var(--lime);}
#pl-space-centre:focus-visible~.wrap .lopt--pl-space-centre{text-decoration:underline;text-underline-offset:6px;text-decoration-color:var(--lime);}
#pl-space-centre:focus-visible~.wrap .placecard--pl-space-centre{opacity:1;outline:2px solid var(--ink);outline-offset:2px;}#pl-space-centre:focus-visible~.wrap .placecard--pl-space-centre .thumb{opacity:1;}
#pl-space-centre:checked~.wrap .placecard--pl-space-centre{transform:scale(1.03);box-shadow:0 0 0 2px var(--lime);}
#pl-space-centre:checked~.wrap .placecard--pl-space-centre .thumb{opacity:1;}
#pl-space-centre:checked~.wrap .placecard--pl-space-centre .badge{opacity:1;}
#pl-space-centre:checked~.wrap .dot--pl-space-centre{background:var(--accent);}
.thumb--pl-tokyo-night{background-image:url('/places/tokyo-night.jpg'), linear-gradient(158deg, hsl(343 47% 24%) 0%, hsl(331 39% 10%) 100%);}
.bg--pl-tokyo-night{background-image:url('/places/tokyo-night.jpg'), linear-gradient(158deg, hsl(343 47% 24%) 0%, hsl(331 39% 10%) 100%);}
#pl-tokyo-night:checked~.wrap .bgs .bg--pl-tokyo-night{opacity:1;}
#pl-tokyo-night:checked~.wrap .scrim{opacity:0.64;}
#pl-tokyo-night:checked~.wrap .lopt--pl-tokyo-night{color:var(--lime);}
#pl-tokyo-night:checked~.wrap .lopt--pl-tokyo-night .lidx{color:var(--lime);}
#pl-tokyo-night:focus-visible~.wrap .lopt--pl-tokyo-night{text-decoration:underline;text-underline-offset:6px;text-decoration-color:var(--lime);}
#pl-tokyo-night:focus-visible~.wrap .placecard--pl-tokyo-night{opacity:1;outline:2px solid var(--ink);outline-offset:2px;}#pl-tokyo-night:focus-visible~.wrap .placecard--pl-tokyo-night .thumb{opacity:1;}
#pl-tokyo-night:checked~.wrap .placecard--pl-tokyo-night{transform:scale(1.03);box-shadow:0 0 0 2px var(--lime);}
#pl-tokyo-night:checked~.wrap .placecard--pl-tokyo-night .thumb{opacity:1;}
#pl-tokyo-night:checked~.wrap .placecard--pl-tokyo-night .badge{opacity:1;}
#pl-tokyo-night:checked~.wrap .dot--pl-tokyo-night{background:var(--accent);}
.thumb--pl-wohnzimmer-abend{background-image:url('/places/wohnzimmer-abend.jpg'), linear-gradient(158deg, hsl(294 46% 20%) 0%, hsl(282 38% 9%) 100%);}
.bg--pl-wohnzimmer-abend{background-image:url('/places/wohnzimmer-abend.jpg'), linear-gradient(158deg, hsl(294 46% 20%) 0%, hsl(282 38% 9%) 100%);}
#pl-wohnzimmer-abend:checked~.wrap .bgs .bg--pl-wohnzimmer-abend{opacity:1;}
#pl-wohnzimmer-abend:checked~.wrap .scrim{opacity:0.6;}
#pl-wohnzimmer-abend:checked~.wrap .lopt--pl-wohnzimmer-abend{color:var(--lime);}
#pl-wohnzimmer-abend:checked~.wrap .lopt--pl-wohnzimmer-abend .lidx{color:var(--lime);}
#pl-wohnzimmer-abend:focus-visible~.wrap .lopt--pl-wohnzimmer-abend{text-decoration:underline;text-underline-offset:6px;text-decoration-color:var(--lime);}
#pl-wohnzimmer-abend:focus-visible~.wrap .placecard--pl-wohnzimmer-abend{opacity:1;outline:2px solid var(--ink);outline-offset:2px;}#pl-wohnzimmer-abend:focus-visible~.wrap .placecard--pl-wohnzimmer-abend .thumb{opacity:1;}
#pl-wohnzimmer-abend:checked~.wrap .placecard--pl-wohnzimmer-abend{transform:scale(1.03);box-shadow:0 0 0 2px var(--lime);}
#pl-wohnzimmer-abend:checked~.wrap .placecard--pl-wohnzimmer-abend .thumb{opacity:1;}
#pl-wohnzimmer-abend:checked~.wrap .placecard--pl-wohnzimmer-abend .badge{opacity:1;}
#pl-wohnzimmer-abend:checked~.wrap .dot--pl-wohnzimmer-abend{background:var(--accent);}
#of-fleecepulli:checked~.wrap .lookcard--of-fleecepulli{background:var(--lime);border-color:var(--lime);}
#of-fleecepulli:checked~.wrap .lookcard--of-fleecepulli .name{color:var(--on-lime);}
#of-fleecepulli:checked~.wrap .lookcard--of-fleecepulli .detail{color:var(--on-lime-soft);}
#of-fleecepulli:checked~.wrap .lookcard--of-fleecepulli .tick{opacity:1;}
#of-fleecepulli:focus-visible~.wrap .lookcard--of-fleecepulli{opacity:1;outline:2px solid var(--ink);outline-offset:2px;}
#of-hemd-jeans:checked~.wrap .lookcard--of-hemd-jeans{background:var(--lime);border-color:var(--lime);}
#of-hemd-jeans:checked~.wrap .lookcard--of-hemd-jeans .name{color:var(--on-lime);}
#of-hemd-jeans:checked~.wrap .lookcard--of-hemd-jeans .detail{color:var(--on-lime-soft);}
#of-hemd-jeans:checked~.wrap .lookcard--of-hemd-jeans .tick{opacity:1;}
#of-hemd-jeans:focus-visible~.wrap .lookcard--of-hemd-jeans{opacity:1;outline:2px solid var(--ink);outline-offset:2px;}
#of-trainingsjacke:checked~.wrap .lookcard--of-trainingsjacke{background:var(--lime);border-color:var(--lime);}
#of-trainingsjacke:checked~.wrap .lookcard--of-trainingsjacke .name{color:var(--on-lime);}
#of-trainingsjacke:checked~.wrap .lookcard--of-trainingsjacke .detail{color:var(--on-lime-soft);}
#of-trainingsjacke:checked~.wrap .lookcard--of-trainingsjacke .tick{opacity:1;}
#of-trainingsjacke:focus-visible~.wrap .lookcard--of-trainingsjacke{opacity:1;outline:2px solid var(--ink);outline-offset:2px;}
#of-tshirt-jeans:checked~.wrap .lookcard--of-tshirt-jeans{background:var(--lime);border-color:var(--lime);}
#of-tshirt-jeans:checked~.wrap .lookcard--of-tshirt-jeans .name{color:var(--on-lime);}
#of-tshirt-jeans:checked~.wrap .lookcard--of-tshirt-jeans .detail{color:var(--on-lime-soft);}
#of-tshirt-jeans:checked~.wrap .lookcard--of-tshirt-jeans .tick{opacity:1;}
#of-tshirt-jeans:focus-visible~.wrap .lookcard--of-tshirt-jeans{opacity:1;outline:2px solid var(--ink);outline-offset:2px;}
#of-winterjacke:checked~.wrap .lookcard--of-winterjacke{background:var(--lime);border-color:var(--lime);}
#of-winterjacke:checked~.wrap .lookcard--of-winterjacke .name{color:var(--on-lime);}
#of-winterjacke:checked~.wrap .lookcard--of-winterjacke .detail{color:var(--on-lime-soft);}
#of-winterjacke:checked~.wrap .lookcard--of-winterjacke .tick{opacity:1;}
#of-winterjacke:focus-visible~.wrap .lookcard--of-winterjacke{opacity:1;outline:2px solid var(--ink);outline-offset:2px;}
#q-480p:checked~.wrap .qualitycard--q-480p{background:var(--lime);border-color:var(--lime);}
#q-480p:checked~.wrap .qualitycard--q-480p .name,#q-480p:checked~.wrap .qualitycard--q-480p .cr,#q-480p:checked~.wrap .qualitycard--q-480p .flag{color:var(--on-lime);}
#q-480p:checked~.wrap .qualitycard--q-480p .detail{color:var(--on-lime-soft);}
#q-480p:checked~.wrap .qualitycard--q-480p .tick{opacity:1;}
#q-480p:focus-visible~.wrap .qualitycard--q-480p{opacity:1;outline:2px solid var(--ink);outline-offset:2px;}
#q-480p:checked~#a-4x3:checked~.wrap .cost--q-480p-a-4x3{display:inline;}
#q-480p:checked~#a-4x3:checked~.wrap .why--q-480p-a-4x3{display:block;}
#q-480p:checked~#a-16x9:checked~.wrap .cost--q-480p-a-16x9{display:inline;}
#q-480p:checked~#a-16x9:checked~.wrap .why--q-480p-a-16x9{display:block;}
#q-480p:checked~#a-9x16:checked~.wrap .cost--q-480p-a-9x16{display:inline;}
#q-480p:checked~#a-9x16:checked~.wrap .why--q-480p-a-9x16{display:block;}
#q-720p:checked~.wrap .qualitycard--q-720p{background:var(--lime);border-color:var(--lime);}
#q-720p:checked~.wrap .qualitycard--q-720p .name,#q-720p:checked~.wrap .qualitycard--q-720p .cr,#q-720p:checked~.wrap .qualitycard--q-720p .flag{color:var(--on-lime);}
#q-720p:checked~.wrap .qualitycard--q-720p .detail{color:var(--on-lime-soft);}
#q-720p:checked~.wrap .qualitycard--q-720p .tick{opacity:1;}
#q-720p:focus-visible~.wrap .qualitycard--q-720p{opacity:1;outline:2px solid var(--ink);outline-offset:2px;}
#q-720p:checked~#a-4x3:checked~.wrap .cost--q-720p-a-4x3{display:inline;}
#q-720p:checked~#a-4x3:checked~.wrap .why--q-720p-a-4x3{display:block;}
#q-720p:checked~#a-16x9:checked~.wrap .cost--q-720p-a-16x9{display:inline;}
#q-720p:checked~#a-16x9:checked~.wrap .why--q-720p-a-16x9{display:block;}
#q-720p:checked~#a-9x16:checked~.wrap .cost--q-720p-a-9x16{display:inline;}
#q-720p:checked~#a-9x16:checked~.wrap .why--q-720p-a-9x16{display:block;}
#a-4x3:checked~.wrap .framecard--a-4x3{background:var(--lime);border-color:var(--lime);}
#a-4x3:checked~.wrap .framecard--a-4x3 .ratio,#a-4x3:checked~.wrap .framecard--a-4x3 .detail{color:var(--on-lime);}
#a-4x3:checked~.wrap .framecard--a-4x3 .shape{border-color:var(--on-lime);}
#a-4x3:checked~.wrap .framecard--a-4x3 .tick{opacity:1;}
#a-4x3:checked~.wrap .qualitycard .cr--a-4x3{display:block;}
#a-4x3:focus-visible~.wrap .framecard--a-4x3{opacity:1;outline:2px solid var(--ink);outline-offset:2px;}
#a-16x9:checked~.wrap .framecard--a-16x9{background:var(--lime);border-color:var(--lime);}
#a-16x9:checked~.wrap .framecard--a-16x9 .ratio,#a-16x9:checked~.wrap .framecard--a-16x9 .detail{color:var(--on-lime);}
#a-16x9:checked~.wrap .framecard--a-16x9 .shape{border-color:var(--on-lime);}
#a-16x9:checked~.wrap .framecard--a-16x9 .tick{opacity:1;}
#a-16x9:checked~.wrap .qualitycard .cr--a-16x9{display:block;}
#a-16x9:focus-visible~.wrap .framecard--a-16x9{opacity:1;outline:2px solid var(--ink);outline-offset:2px;}
#a-9x16:checked~.wrap .framecard--a-9x16{background:var(--lime);border-color:var(--lime);}
#a-9x16:checked~.wrap .framecard--a-9x16 .ratio,#a-9x16:checked~.wrap .framecard--a-9x16 .detail{color:var(--on-lime);}
#a-9x16:checked~.wrap .framecard--a-9x16 .shape{border-color:var(--on-lime);}
#a-9x16:checked~.wrap .framecard--a-9x16 .tick{opacity:1;}
#a-9x16:checked~.wrap .qualitycard .cr--a-9x16{display:block;}
#a-9x16:focus-visible~.wrap .framecard--a-9x16{opacity:1;outline:2px solid var(--ink);outline-offset:2px;}
#pl-own:checked~.wrap .placecard--own-pick{display:none;}
#pl-own:checked~.wrap .placecard--own-add{display:block;}
#pl-own:checked~.wrap .placecard--own{transform:scale(1.03);box-shadow:0 0 0 2px var(--lime);}
#pl-own:checked~.wrap .placecard--own .thumb{opacity:1;}
#pl-own:checked~.wrap .placecard--own .badge{opacity:1;}
#pl-own:checked~.wrap .ownplace{display:block;}
#pl-own:checked~.wrap .dot--own{background:var(--accent);}
