/* ============================================================================
   Walking Reset — the assessment wizard.
   Layout: editorial split. A sequenced rail on the left carries the step
   number and its framing copy; the working surface sits on the right.
   ========================================================================== */

.shell {
  width: min(100% - (var(--gutter) * 2), 1120px);
  margin-inline: auto;
}

/* Set by app.js from the banner's measured height while it is up, removed on
   dismiss. Zero by default so a page without the banner is unaffected. */
body { padding-bottom: var(--banner-pad, 0); }

/* --- Arrival ------------------------------------------------------------ */

/* Uncovers itself. The animation carries its own delay and fill-mode, so the page
   is revealed with no JavaScript at all; app.js only removes the delay once fonts
   and hero imagery have settled. A loader that needs a script to get out of the
   way is a blank page waiting for a script error. */
.pageload {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  place-items: center;
  background: var(--paper);
  /* Never intercepts. It has nothing to click and the page beneath is already
     interactive, so a cover that swallowed the first tap would be a bug wearing
     a loading animation. */
  pointer-events: none;
  animation: pageload-lift var(--dur-enter) var(--ease-expo) 420ms forwards;
}
.pageload.is-done { animation-delay: 0s; animation-duration: var(--dur-move); }
.pageload__wick {
  position: relative;
  width: min(8rem, 38vw);
  height: 2px;
  overflow: hidden;
  background: var(--hairline-2);
}
/* One line drawing itself left to right: the page is being read, not spun at. */
.pageload__wick::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--sage);
  transform: scaleX(0);
  transform-origin: left center;
  animation: pageload-draw 1s var(--ease-fluid) forwards;
}
@keyframes pageload-draw { to { transform: scaleX(1); } }
@keyframes pageload-lift { to { opacity: 0; visibility: hidden; } }

/* No cover at all when motion is unwelcome — a fade someone did not ask for is
   still motion, and the page underneath is already complete. */
@media (prefers-reduced-motion: reduce) {
  .pageload { display: none; }
}

.skiplink {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 300;
  padding: 0.75rem 1.1rem;
  border-radius: 0 0 var(--r-ctl) 0;
  background: var(--ink);
  color: var(--paper);
  font-size: var(--step--1);
  font-weight: 600;
  text-decoration: none;
  transform: translateY(-110%);
  transition: transform var(--dur-ui) var(--ease-out);
}
.skiplink:focus-visible { transform: translateY(0); }

/* Destructive, and on --card-quiet rather than --ink. Quiet by default because it
   throws away the only credential that exists; it should not invite a stray tap. */
.secret__clear {
  margin-top: 0.9rem;
  padding: 0.4rem 0.9rem;
  border: 1px solid var(--hairline-2);
  border-radius: 999px;
  background: transparent;
  color: var(--ink-2);
  font: inherit;
  font-size: var(--step--1);
  font-weight: 600;
  cursor: pointer;
  transition: color var(--dur-ui) var(--ease-out), border-color var(--dur-ui) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .secret__clear:hover { color: var(--rose); border-color: var(--rose-line); }
}

/* --- Section rail ------------------------------------------------------- */

/* Anchors to real sections, so it works before any script runs. The dot is drawn
   by the link itself and the label is held back until hover or focus, because a
   permanent stack of words down the right edge competes with the page it indexes. */
.railnav {
  position: fixed;
  top: 50%;
  right: clamp(0.5rem, 1.2vw, 1.25rem);
  z-index: 90;
  transform: translateY(-50%);
}
.railnav ul { display: grid; gap: 0.7rem; margin: 0; padding: 0; list-style: none; }
.railnav a {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.55rem;
  min-height: 44px;
  padding-inline: 0.4rem;
  color: var(--ink-3);
  font-size: var(--step--1);
  text-decoration: none;
}
.railnav a::after {
  content: "";
  flex: none;
  width: 9px;
  height: 9px;
  border: 1px solid currentColor;
  border-radius: 50%;
  transition: background var(--dur-ui) var(--ease-out),
              transform var(--dur-ui) var(--ease-out);
}
.railnav a[aria-current="true"] { color: var(--sage-ink); }
.railnav a[aria-current="true"]::after { background: currentColor; transform: scale(1.3); }
.railnav span {
  opacity: 0;
  transform: translateX(5px);
  white-space: nowrap;
  pointer-events: none;
  transition: opacity var(--dur-ui) var(--ease-out),
              transform var(--dur-ui) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .railnav a:hover { color: var(--ink); }
  .railnav a:hover span { opacity: 1; transform: none; }
}
.railnav a:focus-visible span { opacity: 1; transform: none; }

/* Below the guide breakpoint the page is one column and the rail would sit on top
   of the content it indexes. The document is short and linear there; scrolling is
   the navigation. */
@media (max-width: 899px) {
  .railnav { display: none; }
}

/* --- Language switch: a small floating pill, detached from the top edge --- */

/* Wraps, because there are three languages now and there were two when this was
   written. A non-wrapping flex row pushed the pill past the viewport on a phone and
   widened the whole document with it — which is why every line of the page looked
   clipped, not just this bar. */
.langbar {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
  gap: 0.4rem 0.75rem;
  padding-top: 1.5rem;
}

/* The 44px floor, in one place. PRODUCT.md records WCAG AA as binding and the
   audience is described as older and on a phone, but this query used to cover the
   language bar alone: measured on a real device, "Choose" came out 36px across
   eleven instances, "See example" 30px across seven, and the summary guarding the
   only credential in the product 21px. Whether a button cleared 44 was decided by
   whether it happened to carry a decorative arrow glyph.

   These stay deliberately small for a mouse. Only a finger gets the bigger box. */
@media (pointer: coarse) {
  .langbar__opt,
  .langbar__account,
  .themebar__opt,
  .upload__pick,
  .upload__example,
  .secret .secret__copy,
  .secret__clear,
  .sheet__close,
  .btn,
  .railnav a { display: inline-flex; align-items: center; justify-content: center; min-height: 44px; }

  /* Square, because a 44px-tall target 30px wide is still a miss waiting to
     happen and these are the smallest controls on the page. */
  .themebar__opt { min-width: 44px; }

  /* Full-width rows: the label is the target, so it grows rather than the text. */
  .check,
  .pick select { min-height: 44px; }
  .check { display: flex; align-items: center; }

  /* A disclosure summary is a control even though it does not look like one. */
  .secret-reveal > summary { display: flex; align-items: center; min-height: 44px; }
}

@media (max-width: 480px) {
  .langbar__opt { padding-inline: 0.6rem; }
}

.langbar__account {
  padding: 0.34rem 0.5rem;
  color: var(--ink-3);
  font-size: var(--step--1);
  font-weight: 600;
  text-decoration: none;
}

@media (hover: hover) and (pointer: fine) {
  .langbar__account:hover { color: var(--ink); }
}

.langbar__pill {
  display: inline-flex;
  /* Wraps too, and can actually shrink to do it. Making only the bar wrap was not
     enough: the pill is one flex item holding every language, and a flex item
     defaults to min-width:auto, so it refused to go narrower than its contents and
     ran off a phone screen with the document behind it. */
  flex-wrap: wrap;
  min-width: 0;
  justify-content: flex-end;
  align-items: center;
  gap: 0.15rem;
  padding: 0.25rem;
  border-radius: 999px;
  /* Mixed from the page colour rather than pinned to it, so the pill stays a
     frosted sheet of the surface behind it in either theme. */
  background: color-mix(in srgb, var(--paper) 72%, transparent);
  border: 1px solid var(--hairline);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}

.langbar__opt {
  position: relative;
  display: inline-flex;
  align-items: center;
  padding: 0.34rem 0.85rem;
  border-radius: 999px;
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--ink-3);
  text-decoration: none;
  transition: color var(--dur-ui) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .langbar__opt:not([aria-current="true"]):hover { color: var(--ink); }
}

/* The indicator behind the active label is --ink, so the label takes --paper in
   both themes: near-white on dark ink by day, near-black on light ink at night. */
.langbar__opt[aria-current="true"] { color: var(--paper); }

.langbar__txt { position: relative; z-index: 1; }

/* Sits behind the label rather than being the label's own background, so the
   chip can travel between options while the two words stay put. */
.langbar__ind {
  position: absolute;
  inset: 0;
  border-radius: 999px;
  background: var(--ink);
  view-transition-name: lang-indicator;
}

/* --- Appearance --------------------------------------------------------- */

/* The language pill's smaller sibling, and deliberately quieter than it: the
   language you are reading in is a fact about the page, the appearance is a
   preference. Same pill, same frosted surface, no travelling indicator — there
   is no navigation here to carry one across, and three glyphs a few pixels apart
   have nothing to travel. The pressed one is drawn with the sage instead, which
   is the accent's own job: "this is the product's own voice".

   No view-transition-name either. The language chip has one because /en/ and
   /ru/ are two documents; this changes nothing but custom properties. */
.themebar {
  display: inline-flex;
  align-items: center;
  gap: 0.1rem;
  padding: 0.25rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--paper) 72%, transparent);
  border: 1px solid var(--hairline);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}

.themebar__opt {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.95rem;
  height: 1.95rem;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--ink-3);
  cursor: pointer;
  transition:
    color var(--dur-ui) var(--ease-out),
    background-color var(--dur-ui) var(--ease-out);
}

.themebar__opt svg { width: 1rem; height: 1rem; display: block; }

@media (hover: hover) and (pointer: fine) {
  .themebar__opt:hover { color: var(--ink); }
}

/* aria-pressed is the source of truth, so the mark cannot drift from what a
   screen reader is told — and until theme.js runs, all three read false and
   none of them claims to be on. */
.themebar__opt[aria-pressed="true"] {
  color: var(--sage-ink);
  background: var(--sage-soft);
}

/* --- Hero and filming narrative ---------------------------------------- */

.hero { padding-top: clamp(2.5rem, 1rem + 6vw, 7rem); }
.hero__opening {
  min-height: min(76vh, 48rem);
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(18rem, 0.72fr);
  grid-template-areas: "brand lede" "title lede" "cue chips";
  align-content: center;
  align-items: end;
  gap: clamp(1.4rem, 1rem + 3vw, 4.5rem);
  padding-bottom: clamp(4rem, 2rem + 7vw, 8rem);
}
.hero__brand { grid-area: brand; align-self: end; padding-bottom: 0.9rem; }
/* The sequence and the claims share this area but not a list. Separating them is
   what stopped five dot-joined items wrapping into a ragged column. */
.hero__facts {
  grid-area: chips;
  align-self: end;
  display: grid;
  gap: 0.85rem;
  max-width: 40ch;
}
.hero__steps {
  display: grid;
  gap: 0.4rem;
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: step;
  color: var(--ink-2);
  font-size: var(--step--1);
}
/* Indent-and-absolute, not grid. A grid item here would have split the label from
   the sentence after it — a bare text node becomes its own grid item, so every
   line broke after one word. Normal inline flow keeps them one paragraph. */
.hero__steps li { position: relative; padding-left: 1.5rem; }
/* The numeral carries the order, so the words do not have to. */
.hero__steps li::before {
  counter-increment: step;
  content: counter(step);
  position: absolute;
  left: 0;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.hero__steps strong { color: var(--ink); font-weight: 650; }
.hero__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 0.75rem;
  padding: 0;
  margin: 0;
  list-style: none;
  color: var(--ink-3);
  font-size: var(--step--1);
}
/* Decorative separators only — the list already carries the structure. Trailing
   rather than leading: on a wrap, a dot that ends the line reads as "continues",
   while one that starts the next line reads as a typo. */
.hero__chips li:not(:last-child)::after { content: "·"; margin-left: 0.4rem; }
.hero__chips strong { color: var(--ink); font-weight: 650; }
.hero h1 {
  grid-area: title;
  /* Six words, not two: the old 9ch was cut for the wordmark and would break
     this headline into five lines. */
  max-width: 15ch;
  font-size: clamp(2.8rem, 1.9rem + 5vw, 4.7rem);
  letter-spacing: -0.036em;
  font-weight: 500;
  line-height: 0.95;
  font-variation-settings: "opsz" 72;
}
.hero__lede {
  grid-area: lede;
  align-self: end;
  max-width: 38ch;
  padding-bottom: 0.35rem;
  font-size: var(--step-1);
  line-height: 1.55;
  color: var(--ink-2);
}
.hero__cue { grid-area: cue; justify-self: start; }
.hero-guide {
  position: relative;
  display: grid;
  grid-template-columns: minmax(13rem, 0.52fr) minmax(0, 1.48fr);
  gap: clamp(2rem, 1rem + 4vw, 5rem);
  padding: clamp(3.2rem, 2rem + 5vw, 7rem) 0;
  border-top: 1px solid var(--hairline);
}
.hero-guide__rail { align-self: start; }
.hero-guide__title { max-width: 8ch; font-size: var(--step-3); font-weight: 500; }
.hero-guide__note { max-width: 31ch; margin-top: 1rem; color: var(--ink-3); font-size: var(--step--1); }
.hero-guide__story { min-width: 0; }
.hero-plate { transform-origin: 50% 50%; }
.hero-mistakes { padding-top: clamp(2rem, 1rem + 4vw, 4rem); }
.is-enhanced .hero__opening > *,
.is-enhanced .hero-plate,
.is-enhanced .hero-mistakes { will-change: transform, opacity, clip-path; }
@media (min-width: 900px) and (prefers-reduced-motion: no-preference) {
  .hero-guide__rail { position: sticky; top: clamp(2rem, 8vh, 5rem); }
}

/* --- Proof of value ---------------------------------------------------- */

.proof { padding: clamp(3.5rem, 2rem + 7vw, 8rem) 0; border-top: 1px solid var(--hairline); }
.proof__intro { max-width: 48rem; margin-bottom: clamp(2rem, 1rem + 3vw, 4rem); }
.proof__intro h2 { max-width: 18ch; margin: 0.45rem 0 1rem; font-size: var(--step-3); font-weight: 500; }
.proof__intro > p:last-child { max-width: 64ch; color: var(--ink-2); }
/* The two-column score layout lives on the body, because the page itself is now a
   head / body / foot column and a grid on the <li> would fight the bands. */
.book__page--score .book__page-body {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(18rem, 1.1fr);
  gap: clamp(1.25rem, 1rem + 2vw, 2.5rem);
  align-items: center;
}
.proof__visual { margin: 0; overflow: hidden; border-radius: var(--r-sm); background: var(--mat); }
/* contain, not cover: the frame is a portrait photograph and cover cropped its
   edges — which is where the overlay and its caption live. */
.proof__visual img { display: block; width: 100%; height: auto; object-fit: contain; }
.proof__visual figcaption {
  padding: 0.6rem 0.9rem;
  color: var(--mat-ink);
  background: var(--mat);
  font-size: var(--step--1);
}
.proof__score { display: grid; gap: 1.1rem; align-content: center; }
.proof__score p { margin: 0; }
.proof__score-value { display: flex; align-items: baseline; font-variant-numeric: tabular-nums; }
.proof__score-value span { font-family: var(--serif); font-size: clamp(3rem, 2rem + 4vw, 5.5rem); line-height: 0.85; }
.proof__score-value small { color: var(--ink-3); }
/* Specificity rather than !important: `.proof__score p` out-weighs a lone class,
   so these were forcing their way past it. Qualifying them wins honestly. */
.proof__score .proof__score-title { margin-bottom: 0.4rem; font-weight: 650; }
.proof__score .proof__repeatability { margin-top: 0.75rem; color: var(--ink-3); font-size: var(--step--1); }

@media (max-width: 780px) {
  .book__page--score .book__page-body { grid-template-columns: minmax(0, 1fr); }
}

/* One column means the plate gets the card's full width, and the plate is a
   576x1024 portrait: measured on a 390px phone it came out 309x550 inside a card
   1116px tall, against 495-693 for its six siblings. One card twice the height
   of the rest is not a deck, and the first thing in the deck being a photograph
   two thirds of a screen tall is a page you scroll past rather than read.

   Capped by height rather than width so the frame keeps its proportions, and
   centred so the cap does not leave it hanging off one edge. The overlay it
   carries is legible at this size — it is a full-body skeleton on a plain
   background, not fine print — and the sheet is a click away for anyone who
   wants it bigger. */
@media (max-width: 560px) {
  .proof__visual { max-width: 15rem; margin-inline: auto; }
}

/* --- The report as a book ----------------------------------------------- */

/* Default state is a readable list. Everything below only takes effect once the
   module has actually built the scene and set .book--3d, so a failed or blocked
   module leaves five plain cards in document order. */
.book { position: relative; }
.book__pages { margin: 0; padding: 0; list-style: none; display: grid; gap: 1.25rem; }

/* A page of the report, not a card about it. The furniture — a running head, a
   numbered foot, findings set as the report sets them — applies here, in the plain
   stack and on phones. The scene only decides how pages are arranged in space; it
   never decides what a page is. */
.book__page {
  display: flex;
  flex-direction: column;
  padding: 0 clamp(1.25rem, 0.9rem + 1.6vw, 2.25rem);
  background: var(--card);
  border-radius: var(--r-ctl);
  box-shadow: var(--shadow-lift);
}
.book__page-head,
.book__page-foot {
  display: flex;
  flex: none;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  color: var(--ink-3);
  font-size: var(--step--1);
}
.book__page-head {
  min-height: 2.75rem;
  border-bottom: 1px solid var(--hairline);
  font-family: var(--sans);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-size: 0.66rem;
}
.book__page-foot {
  min-height: 3rem;
  align-items: end;
  padding-bottom: 0.85rem;
  border-top: 1px solid var(--hairline);
}
.book__folio { font-family: var(--serif); font-variant-numeric: tabular-nums lining-nums; }
.book__page-body {
  flex: 1 1 auto;
  min-height: 0;
  padding-block: 1.4rem;
  /* Written by book-scene.js, and only when a page would otherwise overrun its
     box. Scaling the measure and the type by the same factor leaves the line
     breaks alone, so the height scales by exactly this and one pass is exact. */
  transform: scale(var(--fit, 1));
  transform-origin: top center;
}
/* The report's own section heading, to the value — .doc__section > h2. The optical
   size is what makes Literata read as a document heading rather than a card title. */
.book__page-title {
  margin: 0 0 0.6rem;
  font-size: var(--step-2);
  font-weight: 500;
  font-variation-settings: "opsz" 32;
}
.book__stage { display: none; }

/* One plateau per page turn, where the page holds still and is read. The pinned
   run is what carries them, so it is sized per turn rather than picked: at 280vh a
   turn was 405px and never stopped. Seven pages is six turns at roughly 55vh each,
   plus the viewport the stage occupies while pinned. */
.book--3d { height: 430vh; }
.book--3d .book__pages { display: none; }
.book--3d .book__stage {
  display: block;
  position: sticky;
  top: 0;
  height: 100vh;
  /* Clips the stack. Pages sit behind and below the focused one and the deepest
     of them reached past the viewport, which widened the document and gave the
     whole page a horizontal scrollbar.
     No perspective here: CSS3DRenderer writes its own perspective() from the
     camera onto its container, and a second one on an ancestor fights it. */
  overflow: hidden;
}
/* The pages move into the renderer's own transformed containers, so their box is
   set here rather than by the grid they came from — and it is one box for all
   five. Letting height follow content is what made the score page 1370px against
   344px siblings: one slab and four coasters, not a book. JS measures the stage
   and writes the two values, because the right page size is a function of the
   room available, which CSS cannot see from here. */
.book--3d .book__page {
  width: var(--page-w, 30rem);
  height: var(--page-h, 42rem);
  max-width: none;
  min-height: 0;
  backface-visibility: hidden;
  /* No transition on opacity: it is authored every frame from scroll position,
     and a 220ms ease makes the whole stack lag the wheel. */
}
/* flow-root rather than block: a block box does not contain its floats, so the
   plate below did not count towards scrollHeight and the fit measurement in
   book-scene.js came back clean while the page overflowed by 78px in German. */
.book--3d .book__page--score .book__page-body { display: flow-root; }
/* The plate is floated into the prose rather than given a column of its own. The
   frame is 576x1024 with "front" burned into its corner and the skeleton running
   its full height, so it can be neither cropped nor given a column wide enough to
   read at page size. Floated, the text runs beside it and then under it — which is
   how a printed report sets a plate, and which absorbs Russian and German by
   spending the full-width space below rather than stretching a column. */
.book--3d .proof__visual {
  float: inline-start;
  width: 10.5rem;
  margin: 0.2rem 1.15rem 0.7rem 0;
}
.book--3d .proof__score { display: block; }
.book--3d .proof__score-value { margin-bottom: 0.3rem; }
.book--3d .proof__score-value span { font-size: var(--step-4); }
.book--3d .proof__score > div > p { font-size: var(--step--1); }

.book__loader {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  transition: opacity 0.5s var(--ease-out);
}
.book__loader span {
  width: 8rem; height: 2px;
  background: linear-gradient(90deg, transparent, var(--ink-3), transparent);
  background-size: 200% 100%;
}
@media (prefers-reduced-motion: no-preference) {
  .book__loader span { animation: book-sweep 1.1s linear infinite; }
}
@keyframes book-sweep { from { background-position: 200% 0; } to { background-position: -200% 0; } }
.book__loader.is-done { opacity: 0; pointer-events: none; }

/* Never a book under reduced motion: the module returns early, and this makes sure
   no half-applied 3D rule can survive a preference change mid-session. */
@media (prefers-reduced-motion: reduce) {
  .book--3d { height: auto; }
  .book--3d .book__pages { display: grid; }
  .book--3d .book__stage { display: none; }
}

/* --- The split ---------------------------------------------------------- */

.step {
  display: grid;
  grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
  gap: clamp(1.5rem, 0.5rem + 3vw, 4rem);
  padding-block: clamp(2.25rem, 1.5rem + 3vw, 4.5rem);
  border-top: 1px solid var(--hairline);
}

.step[hidden] { display: none; }

.step__rail { position: relative; }

@media (min-width: 900px) {
  .step__rail {
    position: sticky;
    top: 2.5rem;
    align-self: start;
  }
}

.step__num {
  display: block;
  font-family: var(--serif);
  font-size: clamp(2.6rem, 1.8rem + 3vw, 4rem);
  font-weight: 300;
  line-height: 0.9;
  letter-spacing: -0.04em;
  color: var(--ink);
  font-variant-numeric: tabular-nums lining-nums;
  font-variation-settings: "opsz" 60;
}

.step__title {
  margin-top: 0.9rem;
  font-size: var(--step-2);
  font-weight: 500;
  font-variation-settings: "opsz" 32;
}

.step__note {
  margin-top: 0.8rem;
  font-size: var(--step--1);
  line-height: 1.6;
  color: var(--ink-3);
  max-width: 34ch;
}

.step[data-state="done"] .step__num { color: var(--ink-3); }

/* --- Working surface ---------------------------------------------------- */

fieldset.field { border: 0; padding: 0; margin: 0; min-width: 0; }

.field + .field { margin-top: 1.6rem; }

.cta-row { margin-top: 1.6rem; }

.step__body > .bezel > .core > .prose + .cta-row { margin-top: 1.35rem; }
.privacy-section { padding-block: clamp(3.5rem, 2rem + 5vw, 7rem); border-top: 1px solid var(--hairline); }
.privacy-section h2 { font-size: var(--step-3); font-weight: 500; }
.privacy-section p { max-width: 72ch; margin-top: 1.2rem; color: var(--ink-2); }

.field__legend {
  font-family: var(--sans);
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--ink);
  padding: 0;
  margin-bottom: 0.15rem;
}

.field__hint {
  font-size: var(--step--1);
  color: var(--ink-3);
  margin-bottom: 0.85rem;
  max-width: 58ch;
}

/* Checkboxes ------------------------------------------------------------- */

.check {
  display: grid;
  grid-template-columns: 1.15rem 1fr;
  gap: 0.75rem;
  align-items: start;
  padding: 0.55rem 0;
  cursor: pointer;
  font-size: var(--step--1);
  line-height: 1.55;
  color: var(--ink-2);
  transition: color var(--dur-ui) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .check:hover { color: var(--ink); }
}
.check + .check { border-top: 1px solid var(--hairline); }

.check input {
  appearance: none;
  -webkit-appearance: none;
  width: 1.15rem;
  height: 1.15rem;
  margin: 0.16rem 0 0;
  border: 1px solid var(--hairline-2);
  border-radius: var(--r-xs);
  background: var(--card);
  cursor: pointer;
  display: grid;
  place-content: center;
  transition:
    background-color var(--dur-ui) var(--ease-out),
    border-color var(--dur-ui) var(--ease-out);
}

.check input::before {
  content: "";
  width: 0.62rem;
  height: 0.62rem;
  transform: scale(0);
  transform-origin: center;
  transition: transform var(--dur-ui) var(--ease-expo);
  box-shadow: inset 1rem 1rem var(--paper);
  clip-path: polygon(14% 44%, 0 65%, 40% 100%, 100% 16%, 84% 0%, 39% 67%);
}

.check input:checked { background: var(--sage); border-color: var(--sage); }
.check input:checked::before { transform: scale(1); }

.check--consent {
  border-top: 1px solid var(--hairline);
  margin-top: 1.1rem;
  padding-top: 1.1rem;
}

/* Select rows ------------------------------------------------------------ */

/* Same row grammar as the checkboxes directly above them — question on the
   left, control on the right, hairline between — because these are the same
   kind of thing and answering fourteen of them in a row should feel like one
   list, not a second form. The controls share a column so the eye can run down
   the answers without re-finding them on each line. */

.pick {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 15rem;
  align-items: center;
  gap: 1rem;
  padding: 0.5rem 0;
}

.pick + .pick { border-top: 1px solid var(--hairline); }

.pick__q {
  font-size: var(--step--1);
  line-height: 1.5;
  color: var(--ink-2);
  transition: color var(--dur-ui) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .pick:hover .pick__q { color: var(--ink); }
}

/* The native control carries system chrome that has nothing to do with this
   page, so it is rebuilt on the same tokens as every other control here. The
   chevron is a background image rather than a pseudo-element because a select
   cannot host one. */
.pick select {
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  min-height: 2.6rem;
  padding: 0.5rem 2.2rem 0.5rem 0.85rem;
  border: 1px solid var(--hairline-2);
  border-radius: var(--r-ctl);
  background-color: var(--card);
  background-image: var(--select-chevron);
  background-repeat: no-repeat;
  background-position: right 0.85rem center;
  color: var(--ink);
  font: inherit;
  font-family: var(--sans);
  font-size: var(--step--1);
  line-height: 1.4;
  cursor: pointer;
  transition:
    border-color var(--dur-ui) var(--ease-out),
    background-color var(--dur-ui) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .pick select:hover { border-color: var(--ink-3); background-color: var(--card-quiet); }
}

.pick select:focus-visible { border-color: var(--sage); }

/* ============================================================================
   The filming guide (step 01).
   Diagrams carry no type of their own — an img element is out of reach of
   the page's webfonts and of its two languages — so every word here is HTML
   sitting beside a picture, and the numbered keys are the join between them.
   ========================================================================== */

.room { margin: 0; }

.room__img { display: block; width: 100%; height: auto; }

.room__legend {
  counter-reset: key;
  list-style: none;
  margin: 1.35rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.95rem 2.25rem;
}

.room__legend li {
  counter-increment: key;
  display: grid;
  grid-template-columns: 1.55rem minmax(0, 1fr);
  gap: 0.7rem;
  font-size: var(--step--1);
  line-height: 1.55;
  color: var(--ink-3);
}

/* The same disc as the one printed in the drawing, so the eye can travel from
   a numbered point in the room to the sentence that explains it. */
.room__legend li::before {
  content: counter(key);
  display: grid;
  place-items: center;
  width: 1.55rem;
  height: 1.55rem;
  border-radius: 999px;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--serif);
  font-size: 0.8rem;
  font-variant-numeric: lining-nums;
  line-height: 1;
}

.room__legend b,
.fix b { color: var(--ink); font-weight: 600; }

.fix {
  display: grid;
  grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
  align-items: center;
  gap: 1.6rem;
  margin: 1.85rem 0 0;
  padding-top: 1.6rem;
  border-top: 1px solid var(--hairline);
}

.fix__img { display: block; width: 100%; height: auto; }

.fix figcaption {
  font-size: var(--step--1);
  line-height: 1.6;
  color: var(--ink-3);
  max-width: 46ch;
}

.fix b { display: block; margin-bottom: 0.2rem; }

.guide__sub {
  margin: 1.95rem 0 0.9rem;
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--ink);
}

.dont {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.9rem;
}

.dont li { min-width: 0; }
.dont img { display: block; width: 100%; height: auto; }

.dont span {
  display: block;
  margin-top: 0.15rem;
  font-size: var(--step--1);
  line-height: 1.45;
  color: var(--ink-3);
}

/* Upload rows ------------------------------------------------------------ */

.uploads { display: grid; gap: 0.5rem; }

.upload {
  --shot: 96px;
  display: grid;
  grid-template-columns: var(--shot) minmax(0, 1fr) auto;
  align-items: center;
  gap: 1rem;
  padding: 0.7rem 1rem 0.7rem 0.7rem;
  border-radius: var(--r-ctl);
  background: var(--card-quiet);
  border: 1px solid transparent;
  transition:
    background-color var(--dur-ui) var(--ease-out),
    border-color var(--dur-ui) var(--ease-out);
}

/* The diagram of the shot this slot wants. Ten labels that differ by one word
   are hard to tell apart at a glance; the viewfinder is what separates them. */
.upload__shot {
  display: block;
  width: var(--shot);
  height: var(--shot);
  transition: opacity var(--dur-ui) var(--ease-out);
}

/* Once the file is in, the instruction has done its job and stops competing
   with the row that still needs one. */
.upload[data-status="done"] .upload__shot { opacity: 0.4; }

.upload:has(input:focus-visible) { border-color: var(--sage-line); }
.upload[data-status="done"] { background: var(--sage-soft); }
.upload[data-status="error"] { background: var(--rose-soft); }

.upload__label { font-size: var(--step--1); font-weight: 600; }

.upload__hint {
  display: block;
  font-weight: 400;
  color: var(--ink-3);
  margin-top: 0.1rem;
}

.upload__tag {
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-left: 0.5rem;
}

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

.upload__status {
  font-size: var(--step--1);
  color: var(--ink-3);
  min-width: 5.5rem;
  text-align: right;
}
.upload[data-status="done"]  .upload__status { color: var(--sage-ink); font-weight: 600; }
.upload[data-status="error"] .upload__status { color: var(--rose); font-weight: 600; }

/* The native control is replaced by its label, which stays keyboard-reachable
   because the input keeps its own focus ring under :focus-visible. */
.upload input[type="file"] {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.upload__pick {
  display: inline-flex;
  align-items: center;
  padding: 0.42rem 0.95rem;
  border-radius: 999px;
  border: 1px solid var(--hairline-2);
  background: var(--card);
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--ink-2);
  cursor: pointer;
  white-space: nowrap;
  transition:
    background-color var(--dur-ui) var(--ease-out),
    color var(--dur-ui) var(--ease-out),
    transform var(--dur-press) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .upload__pick:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); }
}
.upload__pick:active { transform: scale(0.97); }
.upload input[type="file"]:focus-visible + .upload__pick {
  outline: 2px solid var(--sage);
  outline-offset: 3px;
}

#intake-outcome:not(:empty) { margin-top: 1.5rem; }
#secret-box:not([hidden]) { margin-top: 1.6rem; }

/* Secret key ------------------------------------------------------------- */

.secret {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 1rem;
  padding: 0.85rem 0.85rem 0.85rem 1.1rem;
  border-radius: var(--r-ctl);
  background: var(--ink);
  color: var(--paper);
}

.secret__key {
  font-family: var(--mono);
  font-size: var(--step--1);
  letter-spacing: 0.02em;
  word-break: break-all;
}

/* Scoped to .secret on purpose: these colours are for the --ink surface, and the
   clear button wearing this class outside that div rendered paper-on-paper —
   1.03:1 in daylight, 1.05:1 after dark, on the only control that deletes a
   stored access key. */
.secret .secret__copy {
  flex: none;
  padding: 0.4rem 0.9rem;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--paper) 24%, transparent);
  background: color-mix(in srgb, var(--paper) 10%, transparent);
  color: var(--paper);
  font: inherit;
  font-size: var(--step--1);
  font-weight: 600;
  cursor: pointer;
  transition: background-color var(--dur-ui) var(--ease-out), transform var(--dur-press) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .secret .secret__copy:hover { background: color-mix(in srgb, var(--paper) 20%, transparent); }
}
.secret__copy:active { transform: scale(0.97); }

/* Progress --------------------------------------------------------------- */

.progress { display: flex; align-items: center; gap: 0.9rem; }

.progress__dot {
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 999px;
  background: var(--sage);
  flex: none;
  animation: pulse 1.9s var(--ease-fluid) infinite;
}

@keyframes pulse {
  0%, 100% { opacity: 1;   transform: scale(1); }
  50%      { opacity: 0.35; transform: scale(0.82); }
}

.progress[data-done="true"] .progress__dot { animation: none; }
.progress[data-failed="true"] .progress__dot { background: var(--rose); animation: none; }

.progress__label { font-size: var(--step--1); color: var(--ink-2); }

/* Report frame ----------------------------------------------------------- */

.visuals__state { margin: 0; color: var(--ink-3); }
.visuals__state:empty { display: none; }
.visuals__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px;
  overflow: hidden;
  border-radius: var(--r-inner);
  background: var(--hairline);
}
.visual {
  margin: 0;
  min-width: 0;
  background: var(--paper);
}
.visual--clip { grid-column: 1 / -1; }
.visual img,
.visual video {
  display: block;
  width: 100%;
  max-height: 34rem;
  object-fit: contain;
  /* The same plate as the sample report's, and for the same reason: contain
     letterboxes a portrait frame, and what fills the bars has to be dark in both
     themes rather than following --ink into near-white after dark. */
  background: var(--mat);
}
.visual figcaption {
  padding: 0.65rem 0.8rem;
  font-size: var(--step--1);
  color: var(--ink-3);
}
.visuals__legend {
  margin: 1rem 0 0;
  font-size: var(--step--1);
  color: var(--ink-3);
}

.reportbar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-bottom: 1.25rem;
}

/* Same trap as .databanner: this is rendered with the hidden attribute, and
   `display: block` below would outrank the UA rule that hides it. Latent until
   now only because its step is hidden too. */
#report-frame[hidden] { display: none; }

#report-frame {
  width: 100%;
  border: 0;
  border-radius: var(--r-inner);
  background: var(--paper);
  display: block;
  min-height: 24rem;
}

.share-out {
  margin-top: 0.9rem;
  font-size: var(--step--1);
  color: var(--ink-3);
  word-break: break-all;
}

/* Colophon --------------------------------------------------------------- */

.colophon {
  border-top: 1px solid var(--hairline);
  margin-top: 1rem;
  padding: 2rem 0 4.5rem;
  font-size: var(--step--1);
  line-height: 1.65;
  color: var(--ink-3);
  max-width: 62ch;
}

/* ============================================================================
   The one authored motion moment: a step unlocking. Steps are visible by
   default and the class is added by script, so nothing depends on JS to appear.
   ========================================================================== */

.step--reveal .step__rail,
.step--reveal .bezel {
  animation: rise var(--dur-enter) var(--ease-expo) backwards;
}
.step--reveal .bezel { animation-delay: 80ms; }

/* The shift is a variable so reduced-motion can zero the travel and keep the
   fade, rather than losing the cue that a new step arrived. */
@keyframes rise {
  from { opacity: 0; transform: translate3d(0, var(--reveal-shift), 0); }
  to   { opacity: 1; transform: none; }
}

/* Eight upload slots landing at once reads as a dump; a short stagger makes it
   read as a list being laid out. Delays stay inside the card's own entrance. */
.step--reveal .upload {
  animation: rise 380ms var(--ease-out) backwards;
}
.step--reveal .upload:nth-child(1) { animation-delay: 200ms; }
.step--reveal .upload:nth-child(2) { animation-delay: 240ms; }
.step--reveal .upload:nth-child(3) { animation-delay: 280ms; }
.step--reveal .upload:nth-child(4) { animation-delay: 320ms; }
.step--reveal .upload:nth-child(5) { animation-delay: 360ms; }
.step--reveal .upload:nth-child(6) { animation-delay: 400ms; }
.step--reveal .upload:nth-child(7) { animation-delay: 440ms; }
.step--reveal .upload:nth-child(8) { animation-delay: 480ms; }

@media (max-width: 899px) {
  .step { grid-template-columns: minmax(0, 1fr); }
  .step__note { max-width: none; }
  .hero__opening {
    min-height: auto;
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "brand" "title" "lede" "chips" "cue";
    align-items: start;
  }
  .hero__brand { padding-bottom: 0.2rem; }
  .hero__facts { max-width: 44ch; }
  /* `ch` measures the "0" glyph, which under-measures Cyrillic caps badly:
     «видели, как вы ходите.» overran the viewport at 18ch. Narrow column plus a
     gentler ramp, so the headline wraps to four short lines and cannot reach
     the edge in either language. */
  .hero h1 { max-width: 13ch; font-size: clamp(2.1rem, 1.3rem + 3.6vw, 3.4rem); }
  .hero__lede { max-width: 44ch; padding-bottom: 0; }
  .hero-guide { grid-template-columns: minmax(0, 1fr); gap: 2rem; }
  .hero-guide__title { max-width: none; }
  .hero-guide__note { max-width: 48ch; }
}

/* The room drawing is 900 units wide and holds detail the whole way across, so
   it gets the card to itself well before the layout runs out of room. */
@media (max-width: 760px) {
  .room__legend { grid-template-columns: minmax(0, 1fr); gap: 0.8rem; }
  .fix { grid-template-columns: minmax(0, 1fr); gap: 1rem; }
  .fix__img { max-width: 20rem; }
  .fix figcaption { max-width: none; }
  .dont { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.8rem 1rem; }
}

/* The upload row runs out of horizontal room long before the viewport does:
   it sits inside a bezel inside the shell. Stack it early. */
@media (max-width: 640px) {
  .visuals__grid { grid-template-columns: minmax(0, 1fr); }
  .visual--clip { grid-column: auto; }
  /* 15rem of control plus a question does not fit a phone, so the answer moves
     under its question. The hairlines stay: they are what keeps fourteen
     stacked pairs from reading as twenty-eight loose lines. */
  .pick { grid-template-columns: minmax(0, 1fr); gap: 0.45rem; padding: 0.7rem 0; }

  /* The diagram stays on the label's line; only the control drops, so the row
     never becomes three stacked strips. */
  .upload { --shot: 72px; grid-template-columns: var(--shot) minmax(0, 1fr); gap: 0.7rem 0.85rem; }
  .upload__ctl { grid-column: 1 / -1; justify-content: space-between; }
  .upload__status { min-width: 0; text-align: left; }
  .upload__label { display: block; }
  .upload__tag { margin-left: 0; display: block; margin-top: 0.15rem; }
}

/* --- Access key, revealed on request ------------------------------------- */
/* The key is a credential. Printing it unprompted puts it on whatever screen
   happens to be visible; behind a disclosure it stays available without being
   broadcast. <details> gives the toggle, keyboard support and the open/closed
   semantics for free. */

.secret-reveal {
  margin-top: 1.15rem;
  padding: 0.55rem 0.9rem;
  border: 1px solid var(--hairline);
  border-radius: var(--r-ctl);
  background: var(--card-quiet);
}

.secret-reveal > summary {
  cursor: pointer;
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--ink-2);
  list-style: none;
}
.secret-reveal > summary::-webkit-details-marker { display: none; }

.secret-reveal > summary::after {
  content: "＋";
  float: right;
  color: var(--ink-3);
  transition: transform var(--dur-ui) var(--ease-out);
}
.secret-reveal[open] > summary::after { content: "－"; }

.secret-reveal__note {
  margin: 0.6rem 0 0;
  font-size: var(--step--1);
  line-height: 1.5;
  color: var(--ink-3);
}

/* --- Data notice ---------------------------------------------------------- */
/* A notice, not a gate: it never covers the page and never blocks interaction,
   because nothing here requires consent. Fixed to the bottom so it is visible
   without displacing the hero, and it yields on small screens. */

/* The hidden attribute only works through the UA rule `[hidden] { display: none }`,
   which ANY author display declaration outranks. Without this, setting
   banner.hidden = true changed the attribute and nothing else: the notice stayed
   on screen and its dismiss button looked broken. */
.databanner[hidden] { display: none; }
#wizard-signin-prompt[hidden] { display: none; }
.signin-prompt { position: fixed; right: var(--gutter); bottom: var(--gutter); z-index: 110; width: min(28rem, calc(100% - (var(--gutter) * 2))); padding: 1rem; border: 1px solid var(--hairline-2); border-radius: var(--r-sm); background: var(--card); box-shadow: 0 .8rem 2.2rem rgba(22, 19, 15, .12); }
.signin-prompt div { display: flex; flex-wrap: wrap; gap: .55rem; margin-top: .85rem; }

/* On --mat, not --ink. Both are near-black in daylight, so this looks the same
   as it always has; the difference is after dark, where --ink is a near-white and
   this rule was inverting a strip 161px tall across the bottom of a 390px phone
   into the brightest surface on the page — measured rgb(245, 240, 232), on a page
   whose dark palette exists precisely so that does not happen. --mat is the
   darkest surface in whichever palette is on, so the bar stays a recessed tray in
   both and the reader keeps their night vision. */
.databanner {
  position: fixed;
  inset: auto 0 0 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem 1.25rem;
  flex-wrap: wrap;
  padding: 0.85rem clamp(1rem, 4vw, 2.5rem);
  background: var(--mat);
  color: var(--mat-ink);
  box-shadow: 0 -1px 0 color-mix(in srgb, var(--mat-ink) 22%, transparent);
}

.databanner__text {
  margin: 0;
  max-width: 68ch;
  font-size: var(--step--1);
  line-height: 1.5;
}

.databanner .databanner__close {
  flex: none;
  background: var(--mat-ink);
  color: var(--mat);
  border-color: transparent;
}

@media print { .databanner { display: none; } }

/* --- Example sheet --------------------------------------------------------- */
/* One real assessment, shown beside the diagrams. It is a dialog rather than a
   page because it is consulted mid-task — someone about to photograph a sole
   wants to see a sole and then go straight back to the file picker, not lose
   their place in a form they have half filled in. */

/* Same trap as .databanner: rendered with the hidden attribute, so the display
   rule below has to be cancelled explicitly or hiding it does nothing. */
.sheet[hidden] { display: none; }

.sheet {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: grid;
  place-items: end center;
}

/* --scrim, which is black and gets heavier after dark — see base.css for why a
   warm near-black could not do this job in both themes. */
.sheet__scrim {
  position: absolute;
  inset: 0;
  background: var(--scrim);
}

.sheet__panel {
  position: relative;
  width: min(64rem, 100%);
  max-height: 92vh;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 1.4rem clamp(1rem, 4vw, 2.4rem) 2.4rem;
  /* --r-outer. This asked for --r-card, which has never existed in this system —
     the six radii are 6, 10, 12, 21, 28 and the pill — so the whole declaration
     was invalid and the sheet has been shipping with square corners against the
     bottom of the phone this entire time. 28px is the bezel's outer radius, which
     is what this is: the largest surface on the page. */
  border-radius: var(--r-outer) var(--r-outer) 0 0;
  background: var(--paper);
  box-shadow: 0 -8px 40px color-mix(in srgb, var(--mat) 24%, transparent);
}

@media (prefers-reduced-motion: no-preference) {
  .sheet:not([hidden]) .sheet__panel { animation: sheet-rise 320ms var(--ease-out) both; }
}
@keyframes sheet-rise { from { transform: translateY(3%); opacity: 0 } to { transform: none; opacity: 1 } }

.sheet__head {
  position: sticky;
  top: -1.4rem;
  z-index: 1;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin: -1.4rem 0 0;
  padding: 1.4rem 0 0.7rem;
  background: var(--paper);
}

.sheet__title { margin: 0; font-size: var(--step-1); }

.sheet__close {
  flex: none;
  width: 2.2rem;
  height: 2.2rem;
  border-radius: 999px;
  border: 1px solid var(--hairline-2);
  background: var(--card);
  font-size: 1.3rem;
  line-height: 1;
  color: var(--ink-2);
  cursor: pointer;
}
/* Gated like every other hover here. Ungated, the tap that opens the sheet on a
   phone leaves the close button stuck in its pressed-looking inverted state for
   as long as the sheet is open — on the one control whose job is to be obviously
   available. */
@media (hover: hover) and (pointer: fine) {
  .sheet__close:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); }
}

.sheet__note,
.sheet__foot {
  margin: 0 0 1.4rem;
  max-width: 62ch;
  font-size: var(--step--1);
  line-height: 1.5;
  color: var(--ink-3);
}
.sheet__foot { margin: 1.4rem 0 0; }

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

.sample {
  display: grid;
  gap: 0.9rem;
  padding: 0.9rem;
  border-radius: var(--r-ctl);
  background: var(--card-quiet);
}
.sample:focus-visible { outline: 2px solid var(--sage); outline-offset: 3px; }

.sample__pair {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 0.9rem;
  /* Bottom-aligned: a square diagram and a portrait photograph are never the
     same height, and aligning their tops leaves the two captions on different
     lines, which reads as a layout accident. */
  align-items: end;
}

.sample__shot { margin: 0; }
.sample__shot img,
.sample__shot video {
  display: block;
  width: 100%;
  height: auto;
  max-height: 46vh;
  object-fit: contain;
  border-radius: var(--r-sm);
  background: var(--paper-sunk);
}
.sample__shot figcaption {
  margin-top: 0.4rem;
  font-size: var(--step--1);
  color: var(--ink-3);
}

.sample__text h3 { margin: 0 0 0.2rem; font-size: var(--step-0); }
.sample__text p { margin: 0; font-size: var(--step--1); line-height: 1.5; color: var(--ink-3); }

.upload__example {
  flex: none;
  padding: 0.42rem 0.8rem;
  border-radius: 999px;
  border: 1px solid transparent;
  background: none;
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--sage-ink);
  cursor: pointer;
  white-space: nowrap;
}
@media (hover: hover) and (pointer: fine) {
  .upload__example:hover { border-color: var(--hairline-2); background: var(--card); }
}

.hero-guide__cta { margin: 0; }

@media (max-width: 640px) {
  .upload__status { min-width: 0; }
  .sheet__panel { max-height: 96vh; }

  /* Two equal columns is right at desk width and wrong on a phone: measured at
     390px they came out 157px each, and 157px is fine for a line drawing and far
     too small for the photograph it is being compared against — a whole standing
     body, which is the one thing the reader is here to see the framing of. They
     stay side by side, because the comparison IS the content and stacking them
     puts a screen's worth of scrolling between the two halves of one sentence.
     The photograph simply takes the larger share: measured, 199px against 124px,
     which brings the standing shot from 157x210 up to 199x266. */
  .sample__pair { grid-template-columns: minmax(0, 0.62fr) minmax(0, 1fr); gap: 0.7rem; }
  .sample { padding: 0.75rem; }
}

@media print { .sheet { display: none; } }
