/* ===================================================================
   Zilhouette — mobile-first stylesheet, world B
   White ground, #F5F5F7 plates, #111 ink, platform system stack. 12px
   tiles, 16px heroes and sheets, pill controls, soft offset shadows.
   One stroke-SVG icon family; no emoji chrome; no webfont at all.
   Structure first; the design tokens (:root) live near the end, and
   zilhouette/DESIGN.md is the written contract for them.
   4/8/12/16/20/24 spacing grid, ≥44px touch targets.
   =================================================================== */

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--frame-bg);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 15px;
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

button { font-family: inherit; color: inherit; }

/* Focus — visible, consistent, token-driven */
:focus { outline: none; }
:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

/* ---------- Phone frame on desktop, full-bleed on phone ---------- */
.device {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}
@media (min-width: 720px) {
  .device { padding: 28px; }
}
.app {
  position: relative;
  width: 100%;
  max-width: 430px;
  /* A BOUNDED height, not min-height: the layout is a flex column whose
     .screens child scrolls internally and whose .tabbar is pinned to the
     bottom. With min-height the frame grew with the content instead, so
     .screens never scrolled and the tab bar was pushed below the fold —
     on the Twin form it sat ~850px down and could only be reached by
     scrolling the whole page. dvh, not vh, so iOS Safari's collapsing
     toolbar doesn't clip it. */
  height: 100vh;
  height: 100dvh;
  background: var(--ground);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
@media (min-width: 720px) {
  .app {
    min-height: auto;
    height: 880px;
    max-height: 92vh;
    border-radius: 40px;
    box-shadow: 0 24px 64px rgba(0, 0, 0, 0.22);
    border: 10px solid var(--ink);
  }
}

:root {
  --page-pad: 20px;   /* .screens gutter — edge-bleeding strips cancel it */
  --tab-h: 64px;
  --top-h: 56px;
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
}

/* ---------- Top bar ----------
   B's bar is the screen's own title, nothing else: no wordmark, no rule
   under it. The title comes from the active screen's h1 (see .screen-title). */
.topbar {
  height: calc(var(--top-h) + var(--safe-top));
  padding-top: var(--safe-top);
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-left: var(--sp-5);
  padding-right: var(--sp-4);
  background: var(--ground);
  z-index: 5;
}
/* The one h1 the app exposes. go() copies the active screen's heading text
   in; that heading stays in the screen markup as the source of the copy and
   is display:none there, so nothing is announced twice. */
.screen-title {
  margin: 0;
  font-family: var(--font-ui);
  font-size: var(--fs-title);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.2;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The wordmark stopped being bar furniture: it introduces the product at
   onboarding step 1 and heads the help sheet, and appears nowhere else. */
.wordmark {
  font-family: var(--font-ui);
  font-weight: var(--wordmark-weight);
  font-size: 13px;
  letter-spacing: var(--wordmark-tracking);
  text-transform: var(--wordmark-case);
  color: var(--ink);
}
.ghost-btn {
  width: 36px; height: 36px;
  flex: 0 0 auto;
  border-radius: 50%;
  border: none;
  background: var(--plate);
  color: var(--ink);
  font-size: var(--fs-btn); font-weight: 600;
  cursor: pointer;
  transition: background 0.15s;
}
.ghost-btn:hover { background: #ECEDF0; }
.ghost-btn:active { background: var(--hairline); }

/* ---------- Screens ---------- */
.screens {
  flex: 1 1 auto;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  /* 4px, not 20: the screen title lives in the bar now, so content starts
     directly under it the way the B artboards set it. */
  padding: var(--sp-1) var(--page-pad) calc(var(--tab-h) + var(--safe-bottom) + 88px);
}
/* No entrance animation on .screen: it replayed a 320ms full-screen fade and
   slide on every one of the five tab presses, so a third of a second of washed
   -out content sat between the tap and the content. Switching tabs is
   navigation, not an authored moment. */
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }

/* The screen heading is the SOURCE of the top-bar title, not a second one:
   go() reads its text into .screen-title and it is never drawn here.
   display:none also keeps it out of the accessibility tree, so the title is
   announced once. Later sprints change this copy here, in the screen. */
.screen-head h1 { display: none; }
.screen-head .sub {
  margin: 0 0 var(--sp-4);
  max-width: 36ch;
  color: var(--secondary);
  font-size: var(--fs-body);
  line-height: 1.4;
}

/* ---------- Fields ---------- */
.field { display: block; margin-bottom: var(--sp-5); }
.field > span {
  display: block;
  font-family: var(--label-font);
  font-style: var(--label-style);
  font-size: var(--label-size);
  font-weight: var(--label-weight);
  letter-spacing: var(--label-tracking);
  text-transform: var(--label-case);
  /* The field label names the answer: it is ink, not a quiet grey rubric. */
  color: var(--ink);
  margin-bottom: var(--sp-2);
}
.field > span em {
  font-family: var(--font-ui);
  font-weight: 400;
  color: var(--secondary);
  font-style: normal;
  text-transform: none;
  letter-spacing: 0;
  font-size: var(--fs-meta);
}
/* On the twin form and in the wizard the parenthetical is a full sentence, so
   run in it wrapped under its own label and the two read as one broken line.
   It gets its own line there: label, then what the answer is for. The short
   inline form still holds everywhere else (the add sheet, Worth It?). */
#screen-twin .field > span em:not(#heightVal),
#onboardModal .field > span em:not(#obHeightVal) { display: block; margin-top: 2px; line-height: 1.35; }
/* The height em is not a hint, it is the slider's live value — it stays on the
   label's line and reads as a number. */
#heightVal, #obHeightVal { color: var(--ink); font-weight: 600; font-variant-numeric: tabular-nums; }
/* Inputs: a 12px plate with no hairline. The border is the fill — an outline
   AND a fill would be two edges for one control. */
input[type="text"], input[type="number"] {
  width: 100%;
  min-height: 48px;
  padding: 13px 14px;
  border: none;
  border-radius: var(--radius-field);
  background: var(--plate);
  font-size: var(--fs-btn);
  font-family: inherit;
  color: var(--ink);
  transition: box-shadow 0.15s, background 0.15s;
}
input[type="text"]::placeholder, input[type="number"]::placeholder { color: var(--secondary-plate); }
input[type="text"]:focus, input[type="number"]:focus {
  outline: none;
  background: var(--ground);
  box-shadow: inset 0 0 0 1.5px var(--ink);
}

/* Range — custom track + thumb */
input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 44px;
  background: transparent;
  margin: 0;
}
input[type="range"]::-webkit-slider-runnable-track {
  height: 2px;
  background: var(--hairline);
  border-radius: 1px;
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 26px; height: 26px;
  border-radius: 50%;
  background: var(--thumb-bg);
  border: 1.5px solid var(--ink);
  margin-top: -12px;
  transition: transform 0.15s;
}
input[type="range"]:active::-webkit-slider-thumb { transform: scale(1.12); }
input[type="range"]::-moz-range-track { height: 2px; background: var(--hairline); border-radius: 1px; }
input[type="range"]::-moz-range-thumb {
  width: 24px; height: 24px; border-radius: 50%;
  background: var(--thumb-bg); border: 1.5px solid var(--ink);
}

/* ---------- Chips ---------- */
.chip-row { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 2px; scrollbar-width: none; }
.chip-row::-webkit-scrollbar { display: none; }
/* A chip that fades out reads as "more this way", where one sliced flat at the
   container edge reads as broken. Not on .wrap rows, which have no overflow.
   The fade is on the edge that actually HAS hidden content, and only that one.
   A fixed trailing-only mask got this wrong in both directions: it dimmed the
   SELECTED chip whenever the selection sat at the right edge, and it sliced
   the leading chip flat the moment the row had been scrolled at all. `data-edge`
   is written by markEdges() in app.js on scroll, resize and re-render (CSS
   scroll-timelines would do it without JS, but Safari does not ship them and
   this app is mobile-first). No attribute yet = the old trailing fade, so the
   row is never worse than it was before the script runs.
   scroll-padding-inline keeps a chip scrolled into view clear of the fade. */
.chip-row:not(.wrap), .cat-filter, .style-shots { scroll-padding-inline: 24px; }
.chip-row:not(.wrap):not([data-edge]), .cat-filter:not([data-edge]),
.chip-row:not(.wrap)[data-edge="end"], .cat-filter[data-edge="end"], .style-shots[data-edge="end"] {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
}
.chip-row:not(.wrap)[data-edge="start"], .cat-filter[data-edge="start"], .style-shots[data-edge="start"] {
  -webkit-mask-image: linear-gradient(to right, transparent, #000 24px);
  mask-image: linear-gradient(to right, transparent, #000 24px);
}
.chip-row:not(.wrap)[data-edge="both"], .cat-filter[data-edge="both"], .style-shots[data-edge="both"] {
  -webkit-mask-image: linear-gradient(to right, transparent, #000 24px, #000 calc(100% - 24px), transparent);
  mask-image: linear-gradient(to right, transparent, #000 24px, #000 calc(100% - 24px), transparent);
}
.chip-row:not(.wrap)[data-edge="none"], .cat-filter[data-edge="none"], .style-shots[data-edge="none"] {
  -webkit-mask-image: none;
  mask-image: none;
}
.chip-row.wrap { flex-wrap: wrap; overflow: visible; }
/* Three options whose third label is much longer than the other two, so a
   plain wrap dropped it onto its own line looking like a mistake. Spanning it
   deliberately makes the same 2 + 1 shape read as the intended layout.
   Scoped to this row: the other .chip-row.wrap holds style tags, where an
   ordinary wrap is right. */
#obGenderChips {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}
#obGenderChips > :last-child { grid-column: 1 / -1; }
/* A chip is a pill: plate fill, ink label, no border. Pressed fills with ink
   and the label turns white — the state is the fill, never an outline. */
.chip {
  flex: 0 0 auto;
  min-height: 44px;
  padding: 11px 16px;
  border-radius: var(--radius-pill);
  border: none;
  background: var(--plate);
  color: var(--ink);
  font-size: var(--fs-body);
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s, color 0.15s, transform 0.12s;
}
.chip:hover { background: #ECEDF0; }
.chip:active { transform: scale(0.96); }
.chip.active {
  background: var(--chip-active-bg);
  color: var(--chip-active-ink);
  font-weight: 600;
}
.chip:disabled { opacity: 0.45; cursor: default; }
.chip.sm { min-height: 38px; padding: 8px 14px; font-size: var(--fs-meta); }
.chip.big { min-height: 48px; padding: 13px 20px; font-size: var(--fs-btn); }

/* ---------- Segmented control ----------
   Two or three exclusive views of the same thing: a plate track, and the
   chosen one lifted onto a white thumb. The app's only segmented control:
   the Twin's fit pair (R3) and the Style screen's mode pair (R4) both came
   onto it from hand-drawn copies, and nothing may draw a fifth. */
.seg {
  display: flex;
  gap: 2px;
  padding: 2px;
  background: var(--plate);
  border-radius: var(--radius-seg);
}
.seg-btn {
  flex: 1;
  min-height: 44px;
  padding: 10px 12px;
  border: none;
  background: transparent;
  border-radius: var(--radius-seg-thumb);
  font-family: var(--font-ui);
  font-size: var(--fs-body);
  font-weight: 500;
  color: var(--secondary-plate);
  cursor: pointer;
  transition: background 0.15s, color 0.15s, box-shadow 0.15s;
}
.seg-btn.active {
  background: var(--ground);
  color: var(--ink);
  font-weight: 600;
  box-shadow: var(--shadow-thumb);
}
/* A segment may carry one line of supporting detail under its label ("30 sec",
   "like a fitting"). It is the same control, one line taller — never a third
   line, and never a different weight. */
.seg-btn.two-line {
  min-height: 52px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 1px; padding: var(--sp-2);
}
.seg-btn em { display: block; font-style: normal; font-size: var(--fs-tab); font-weight: 400; color: inherit; }
.seg-btn.active em { color: var(--secondary); font-weight: 400; }

/* ---------- Tone + colour swatches ---------- */
.color-row { display: flex; gap: 10px; flex-wrap: wrap; }
/* A colour is a circle: it names a hue, not a garment, and nothing about it is
   rectangular. Pressed is the same ink ring the rest of the app uses. */
.swatch {
  width: 40px; height: 40px;
  border-radius: 50%;
  border: 1px solid rgba(0, 0, 0, 0.08);
  cursor: pointer;
  position: relative;
  transition: transform 0.12s;
}
/* 40px drawn, 48px to the thumb */
.swatch::after { content: ''; position: absolute; inset: -4px; border-radius: 50%; }
.swatch:active { transform: scale(0.92); }
.swatch.active { box-shadow: 0 0 0 2px var(--ground), 0 0 0 3.5px var(--ink); }

/* Skin tone is a continuous scale, so it is drawn as one: nine cells of a
   printed colour bar. As nine free-floating dots they wrapped 6 + 3, which
   read as an accident rather than a decision — and a ramp also lets you
   judge your tone against its neighbours, which is how you actually pick. */
.tone-row {
  display: grid;
  grid-template-columns: repeat(9, 1fr);
  border: none;
  border-radius: var(--radius-tile);
  overflow: hidden;
}
.tone {
  height: 46px;           /* the row is the touch target, full-height per cell */
  border: none;
  border-radius: 0;
  cursor: pointer;
  position: relative;
  transition: transform 0.12s;
}
.tone:active { transform: scaleY(1.08); }
/* Inset, because an outer ring on a contiguous bar would be clipped. */
.tone.active {
  box-shadow: inset 0 0 0 2px var(--ground), inset 0 0 0 4px var(--ink);
  z-index: 1;
}

/* ---------- Twin figure ---------- */
.twin-stage {
  position: relative;
  display: flex;
  justify-content: center;
  background: var(--stage-bg);
  border: 1px solid var(--stage-line);
  border-radius: var(--radius-hero);
  /* the figure now stands to its full height, so the caption needs a floor of
     its own rather than the feet it used to sit on */
  padding: 22px 0 58px;
  margin-bottom: var(--sp-4);
}
/* Spec mode draws a value column beside the figure, so the box is wider than
   the body it holds — the empty right half IS the layout. */
.twin-figure {
  width: min(250px, 100%); height: 300px;
  position: relative;
}
.twin-figure svg { width: 100%; height: 100%; display: block; }

/* The tape, as a drawing convention: a dimension line crosses the body exactly
   where the tape would sit, and its value is set out to the right. Styled here
   rather than in the SVG so the palette stays in one file. */
.fx-rule { stroke: var(--hairline-strong); stroke-width: 1; }
.fx-rule.on { stroke: var(--ink); stroke-width: 1.6; }
.fx-rule.dash { stroke-dasharray: 5 4; stroke-linecap: round; }
/* The spec sheet's own type. The composition is untouched — same rules, same
   uppercase labels, same numbers in the same places (owner's ruling); only the
   colour and the face are B's. The labels sit on the white stage now, so they
   take --secondary rather than the plate value. */
.fx-lbl {
  font-family: var(--font-ui);
  font-size: 7px; letter-spacing: 0.14em;
  fill: var(--secondary);
}
.fx-val { font-family: var(--font-ui); font-size: 13px; font-weight: 600; fill: var(--ink); }
.fx-lbl.on, .fx-val.on { fill: var(--ink); }
/* A reading of the figure above it, printed at the foot of the sheet: a plate
   pill on the white stage. It sits on paper, not on a photograph, so it takes
   the plate fill instead of the white-circle shadow. */
.silhouette-badge {
  position: absolute;
  bottom: 12px; left: 50%;
  transform: translateX(-50%);
  background: var(--plate);
  border: none;
  border-radius: var(--radius-pill);
  padding: 6px 14px;
  font-size: var(--fs-meta); font-weight: 500;
  letter-spacing: 0;
  color: var(--ink);
  white-space: nowrap;
}

/* ---------- Twin detail meter ---------- */
/* On the ground, not in a card: it is a reading of the twin above it, and a
   plate around it would make it a second object. */
.acc-meter-wrap {
  background: transparent;
  border: none;
  border-radius: 0;
  padding: 0;
  margin: 0 0 var(--sp-6);
}
.acc-meter-head {
  display: flex; justify-content: space-between; align-items: baseline;
  font-size: var(--fs-body); font-weight: 600; color: var(--ink);
}
.acc-meter-head em { font-style: normal; color: var(--ink); font-weight: 600; font-variant-numeric: tabular-nums; }
.acc-meter {
  height: 4px; border-radius: var(--radius-pill);
  background: var(--plate);
  margin: var(--sp-3) 0 var(--sp-2); overflow: hidden;
}
.acc-fill {
  height: 100%; border-radius: var(--radius-pill);
  /* ink: the meter reports a fact about the twin, it is not a warning and
     not the AI accent, which belongs to the render moment alone. */
  background: var(--ink);
  transition: width 0.4s cubic-bezier(.3,.9,.3,1);
}
.acc-hint { margin: 0; font-size: var(--fs-meta); color: var(--secondary); }

/* Quick fit / Tailor fit is the plain .seg above — R3 retired the hand-drawn
   copy of it. It is the one segment that carries a second line, so the track
   sets its own rhythm on the form. */
#fitTabs { margin: var(--sp-1) 0 var(--sp-5); }

/* ---------- Photo drop ----------
   A plate that wants a photo. Dashed hairline, because "put something here"
   is worth one drawn convention. */
.photo-drop {
  display: block;
  border: 1px dashed var(--drop-line);
  border-radius: var(--radius-tile);
  background: var(--plate);
  cursor: pointer;
  overflow: hidden;
  transition: border-color 0.15s, background 0.15s;
}
.photo-drop:hover { border-color: var(--secondary); }
.photo-drop:active { border-color: var(--ink); }
.photo-drop.big { min-height: 150px; }
.photo-drop-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: var(--sp-6);
  color: var(--ink);
  font-size: var(--fs-body);
  font-weight: 500;
  min-height: 96px;
  text-align: center;
}
.photo-drop-inner .plus { display: flex; color: var(--ink); }
.photo-drop-inner .plus svg { width: 26px; height: 26px; }
/* Once there is a photo it IS the drop zone: the prompt's padding would only
   frame the user's own picture in a dashed box. */
.photo-drop-inner.has-photo { padding: 0; gap: 0; }
.photo-drop-inner.has-photo .garment-progress { padding: var(--sp-3) var(--sp-4) var(--sp-4); }
.photo-drop img.preview {
  width: 100%;
  height: 200px;
  object-fit: cover;
  display: block;
}
/* A selfie is a portrait, and the well is wider than it is tall: a full-bleed
   200px cover crops the face to a band. It shows at the 4:5 the camera framed
   it in, centred on the plate — nothing of the face is cut. */
#selfieDrop img.preview {
  width: auto; height: 260px;
  aspect-ratio: 4 / 5;
  margin: var(--sp-3) auto;
  border-radius: var(--radius-tile);
}
/* Dashed means "a photograph goes here". Once one does, the prompt goes —
   the same rule the full-body slots follow. */
#selfieDrop.has-photo { border-color: transparent; }
.drop-hint { font-size: var(--fs-meta); color: var(--secondary-plate); margin-top: 0; font-weight: 400; }

/* ---------- Buttons ----------
   Two, both pills. Primary is an ink fill; secondary is a 1.5px ink outline
   on the ground. Nothing else is a button. */
.btn-primary, .btn-secondary {
  border: none;
  border-radius: var(--radius-pill);
  min-height: 48px;
  padding: 13px 22px;
  font-family: var(--font-ui);
  font-size: var(--fs-btn);
  font-weight: 600;
  letter-spacing: 0;
  line-height: 1.2;
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  transition: transform 0.12s, opacity 0.15s, background 0.15s, color 0.15s;
}
.btn-primary:active, .btn-secondary:active { transform: scale(0.98); }
.btn-primary { background: var(--btn-bg); color: var(--btn-ink); }
.btn-primary:hover { background: #000000; }
/* Disabled is a plate with plate-safe secondary text — still 5.31:1, so a
   disabled label stays readable rather than fading to a rumour. */
.btn-primary:disabled, .btn-primary[disabled] {
  background: var(--plate); color: var(--secondary-plate); cursor: default; transform: none;
}
.btn-secondary {
  background: var(--ground); color: var(--ink);
  box-shadow: inset 0 0 0 1.5px var(--btn2-line);
}
.btn-secondary:hover { background: var(--plate); }
.btn-secondary:disabled, .btn-secondary[disabled] {
  box-shadow: inset 0 0 0 1.5px var(--hairline); color: var(--secondary); cursor: default; transform: none;
}
.btn-primary svg, .btn-secondary svg { width: 18px; height: 18px; flex: 0 0 auto; }
.btn-primary.full, .btn-secondary.full { width: 100%; }
/* Compact size, same component — for buttons that sit inline with body text
   (the garment notice's OK). Still a 44px target. */
.btn-primary.sm, .btn-secondary.sm { min-height: 44px; padding: 10px 18px; font-size: var(--fs-body); }
/* The one control that is not a pill: a labelled action that must NOT compete
   with the pill beside it — a sheet's Cancel, or "Try another mix" next to
   "Save look". No fill, no ring — the label alone, at button weight, secondary
   ink on the ground (AA there), ink on the hover plate. Still a 44px target.
   Never the one action on a screen. */
.btn-text {
  border: none;
  background: none;
  min-height: 44px;
  padding: 10px 14px;
  border-radius: var(--radius-pill);
  font-family: var(--font-ui);
  font-size: var(--fs-btn);
  font-weight: 600;
  line-height: 1.2;
  color: var(--secondary);
  cursor: pointer;
  transition: background 0.15s, color 0.15s, transform 0.12s;
}
/* --secondary is AA on the ground but not on a plate, so the hover plate takes
   the label to ink rather than leaving it at 4.44:1. */
.btn-text:hover { background: var(--plate); color: var(--ink); }
.btn-text:active { transform: scale(0.98); }
.btn-text:disabled, .btn-text[disabled] { color: var(--secondary-plate); background: none; cursor: default; transform: none; }

/* ---------- Garment pack-shot conversion (PRD §14) ---------- */
/* Progress lives inside the drop zone, under the original photo: the user's own
   photo is what they recognise, so it stays on screen while the pack shot is
   made. Text + a bar, no spinner glyph — the icon family is stroke SVG only. */
.garment-progress {
  align-self: stretch; /* the drop zone centres its children; this spans it */
  display: flex;
  flex-direction: column;
  gap: 6px;
  text-align: left;
}
.garment-progress .lbl { font-size: var(--fs-meta); font-weight: 500; color: var(--secondary-plate); }
.garment-progress .track {
  height: 3px;
  border-radius: var(--radius-pill);
  background: var(--ai-soft);
  overflow: hidden;
}
/* The one coloured thing in the shell. The AI accent marks the moment a
   machine is working on the user's photo and appears nowhere else. */
.garment-progress .track i {
  display: block;
  height: 100%;
  width: 38%;
  border-radius: var(--radius-pill);
  background: var(--ai);
  animation: garmentBar 1.15s cubic-bezier(.65,0,.35,1) infinite;
}
/* Indeterminate: the job has no percentage to report, so the bar says "working",
   not "this far". Reduced-motion users get the segment parked at the start. */
@keyframes garmentBar {
  from { transform: translateX(-100%); }
  to   { transform: translateX(263%); }
}
/* A line in the sheet, not a panel inside it: the sheet is already the card,
   and a plate around one sentence made a card within a card. */
.garment-notice {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  margin: var(--sp-3) 0 var(--sp-5);
  font-size: var(--fs-meta);
  line-height: 1.4;
  color: var(--secondary);
}
#garmentChoice { margin: var(--sp-3) 0 var(--sp-5); }
/* The add sheet's title carries the batch counter ("Add to wardrobe · 2 more
   photos queued"), which at the 22/700 sheet step ran to three lines and read
   as a headline rather than a state. 17/600 is the section step. Scoped here
   on purpose: every other sheet keeps the 22/700 title until the owner rules
   on one sheet title size for the whole app. */
#addModal .sheet h2 { font-size: var(--fs-section); font-weight: 600; letter-spacing: 0; }

/* ---------- Wardrobe ---------- */
.cat-filter { display: flex; gap: 8px; overflow-x: auto; margin-bottom: 16px; padding-bottom: 2px; scrollbar-width: none; }
.cat-filter::-webkit-scrollbar { display: none; }
/* Two columns, the way the B artboard lays the closet out: at 375px that is a
   161.5px tile, big enough to recognise a garment in. 16px between rows, 12px
   between columns — the extra row gap gives the name + meta block under each
   photo room to read as part of its own tile. The FAB is cleared by .screens'
   own bottom padding; a second clearance here only added dead space. */
.wardrobe-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-4) var(--sp-3);
}
/* ---------- Tile ----------
   No plate, no border: the photo IS the tile. 12px corners on the image,
   name 14/600 and meta 12 secondary sitting on the ground beneath it. */
.item-card {
  background: transparent;
  border: none;
  border-radius: 0;
  overflow: visible;
  position: relative;
  /* Column + .cw-row's auto top margin bottom-aligns the wear count across a
     row whatever the meta above it holds — a brand line, one name line or two.
     Grid stretch already makes the cards themselves equal height. */
  display: flex;
  flex-direction: column;
}
/* No entrance animation: renderWardrobe() repaints the whole grid on every
   wear tap, filter and delete, so an animated tile replayed the same flourish
   across every photo on screen for actions that changed one number. */
.item-card:active { transform: none; }
.item-card .thumb {
  width: 100%; aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: var(--radius-tile);
  display: block;
  background: var(--shot-bg);
}
.item-card .meta { padding: var(--sp-2) 2px 0; flex: 1; display: flex; flex-direction: column; }
/* Brand over name, the way a shop tile reads it (PRD §14 step 4). One line,
   truncated; an item with no brand gets no element at all. */
.item-card .meta .brand {
  font-size: var(--fs-meta); font-weight: 500; line-height: 1.3;
  color: var(--secondary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  margin-bottom: 1px;
}
/* Quiet text, not a badge: the pack shot is on its way, nothing to act on. */
.item-card .meta .converting {
  font-size: var(--fs-meta); line-height: 1.35; color: var(--secondary);
  margin-top: 2px;
}
/* Capped at two lines. No reserved second line: at two columns most names fit
   on one, and holding the space open left a blank band between every name and
   its wear count. .cw-row's auto top margin still bottom-aligns the counts
   across a row when one name does wrap. */
.item-card .meta .nm {
  font-size: var(--fs-body); font-weight: 600; line-height: 1.25;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.item-card .cw-row {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 6px; margin-top: auto; padding-top: 2px;
}
/* "4 wears · $12/wear" on one line — at two columns it fits, and the count and
   what it cost to get there are one fact, not two. */
.item-card .meta .cw {
  font-size: var(--fs-meta); line-height: 1.35; color: var(--secondary);
  font-variant-numeric: tabular-nums;
}
.item-card .wear-add {
  flex: none; position: relative;
  width: 24px; height: 24px; margin: -4px -2px -2px 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--plate); border: none; border-radius: 50%;
  color: var(--ink); cursor: pointer;
}
.item-card .wear-add svg { width: 13px; height: 13px; }
.item-card .wear-add:hover { background: var(--hairline); }
/* 22px and 28px controls, sized up to a thumb-sized target without moving the art */
.item-card .wear-add::after { content: ''; position: absolute; inset: -11px; }
/* A white disc with an ink cross, the app's own ink on the app's own ground
   rather than a grey wash over the photograph. --shadow-thumb keeps its edge
   where the pack shot behind it is white too. */
.item-card .del {
  position: absolute; top: var(--sp-2); right: var(--sp-2);
  width: 28px; height: 28px; border-radius: 50%;
  background: var(--ground); color: var(--ink);
  box-shadow: var(--shadow-thumb);
  border: none; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.item-card .del:hover { background: var(--plate); }
.item-card .del svg { width: 14px; height: 14px; }
.item-card .del::after { content: ''; position: absolute; inset: -8px; }

/* ---------- "No photo yet" ----------
   The plate that stands exactly where a photograph belongs, on tiles, look
   pieces, saved-look collages and Worth It? pairings. Never a drawing of a
   garment: a closet is photographs, and the cartoon standing in for one is
   what the client saw as slop (REDESIGN-B-PLAN, 2026-09-18). Same plate,
   same 12px corners, one stroke hanger from the icon family, and the words
   for the state so it reads as "not yet", not as a broken image. */
.no-photo {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 100%;
  aspect-ratio: 4 / 5;
  border-radius: var(--radius-tile);
  background: var(--plate);
  color: var(--secondary-plate);
  font-size: 11px;
  font-weight: 500;
  line-height: 1.2;
  text-align: center;
  padding: 6px;
  box-sizing: border-box;
}
.no-photo svg { width: 26px; height: 26px; flex: 0 0 auto; }
.no-photo .np-lbl { display: block; }
/* A collage cell is ~60px across — no room for the words, so the hanger
   carries it alone and the cell fills its grid slot. */
.no-photo.fill {
  aspect-ratio: auto;
  height: 100%;
  border-radius: 0;
  gap: 0;
}
.no-photo.fill .np-lbl { display: none; }
.no-photo.fill svg { width: 22px; height: 22px; }

/* ---------- Empty states ---------- */
.empty-state { text-align: center; padding: 48px 24px; }
.empty-emoji { display: flex; justify-content: center; margin-bottom: var(--sp-4); color: var(--secondary); font-size: 34px; }
.empty-emoji svg { width: 44px; height: 44px; }
/* The wardrobe's empty state is photo-led: a tile-shaped plate standing where
   the first photograph will go, not an icon floating on the ground. */
.empty-plate {
  width: 120px;
  aspect-ratio: 4 / 5;
  margin: 0 auto var(--sp-4);
  border-radius: var(--radius-tile);
  background: var(--plate);
  color: var(--secondary-plate);
  display: flex; align-items: center; justify-content: center;
}
.empty-plate svg { width: 36px; height: 36px; }
.empty-state h2 { margin: var(--sp-2) 0 var(--sp-1); font-size: var(--fs-section); font-weight: 600; }
.empty-state p { color: var(--secondary); margin: var(--sp-1) 0 var(--sp-5); font-size: var(--fs-body); }

/* ---------- Style studio ---------- */
/* Who composes the look: the plain .seg above, the last hand-drawn copy of the
   control retired (R4). It switches the whole top half of the screen, so it
   leads the screen and takes the space a section heading would. */
#styleModeTabs { margin: var(--sp-1) 0 var(--sp-5); }

.pick-panel { margin-bottom: 18px; }
.pick-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
}
/* The wardrobe tile, made tappable: same plate, same 4:5 art, no wear counter. */
.pick-card {
  display: block;
  appearance: none; -webkit-appearance: none;
  width: 100%; padding: 0; margin: 0;
  font: inherit; color: inherit; text-align: left;
  cursor: pointer;
  /* A selection repaints the pressed state in place; the entrance animation
     .item-card carries would replay across the whole grid on every tap. */
  animation: none;
}
.pick-card .meta { display: block; }
/* Selected: a 2px ink ring around the photo and nothing else — no fill, no
   check mark. The garment stays the brightest thing in the tile either way. */
.pick-card.active .thumb, .pick-card.active .no-photo {
  box-shadow: 0 0 0 2px var(--ground), 0 0 0 4px var(--ink);
}
.pick-card.active .nm { font-weight: 700; }
.pick-hint {
  margin: var(--sp-3) 0;
  font-size: var(--fs-meta);
  color: var(--secondary);
}

.vibe-row { display: block; margin-bottom: 16px; }
.vibe-group-lbl {
  font-family: var(--label-font);
  font-style: var(--label-style);
  font-size: var(--label-size);
  font-weight: var(--label-weight);
  letter-spacing: var(--label-tracking);
  text-transform: var(--label-case);
  /* A section heading, 15/600 ink — not a tracked-out grey rubric. */
  color: var(--ink);
  margin: var(--sp-6) 2px var(--sp-2);
}
/* Inside the vibe row the label is not a section heading but the name of the
   group of pills under it, so it steps down to meta weight and secondary ink:
   seventeen styles in five groups, and five 15/600 headings would read as five
   sections. "Saved looks" and "Recent try-ons" keep the 15/600 heading. */
#vibeRow .vibe-group-lbl {
  font-size: var(--fs-meta); font-weight: 500;
  color: var(--secondary);
  margin-top: var(--sp-4);
}
#vibeRow .vibe-group-lbl:first-child { margin-top: var(--sp-2); }
/* The strip WRAPS; it does not scroll (R5).
   It used to bleed through the page padding and fade its trailing edge, which
   is the right treatment for a scroller — but the critique measured what the
   scroller cost: of fourteen styles, seven were fully visible at rest
   ("Everyday" showed 2 of 6, scrollWidth 881 against a 375 client; "Character"
   2 of 5). Every one of those fourteen carries a researched card with
   photographs, sources and buying advice, and half the catalogue sat behind a
   gesture whose only hint was a 24px fade. Choosing a style is this screen's
   primary decision, so the catalogue is now all on screen at once.
   Neither B artboard shows a style picker, so nothing in the comp is being
   contradicted here. The bleed, the mask, the scroll padding and the hidden
   scrollbar all left with the scrolling — they only ever made sense on a
   scroller, and a wrapped strip that bled past the page padding would push its
   pills to the physical screen edge. */
.vibe-strip {
  display: flex;
  flex-wrap: wrap;
  /* 20px between cells, 8px inside one (.style-cell). A chip and its ⓘ are a
     pair; at one 8px gap everywhere the disc sat equidistant between its own
     chip and the next one and had no visible owner. The row gap stays 8. */
  column-gap: 20px;
  row-gap: 8px;
}
/* Same pill as .chip, its own class because the strip scrolls. */
.vibe {
  flex: 0 0 auto;
  min-height: 44px;
  padding: 11px 16px;
  border-radius: var(--radius-pill);
  border: none;
  background: var(--plate);
  color: var(--ink);
  cursor: pointer;
  text-align: center;
  transition: background 0.15s, color 0.15s, transform 0.12s;
}
.vibe:hover { background: #ECEDF0; }
.vibe:active { transform: scale(0.96); }
.vibe .emoji { display: none; }
.vibe .lbl { font-size: var(--fs-body); font-weight: 500; display: block; white-space: nowrap; }
.vibe.active { background: var(--chip-active-bg); color: var(--chip-active-ink); }
.vibe.active .lbl { font-weight: 600; }

/* The look sits on the ground, not in a card: the pieces and the hero are
   the composition, and a plate around them would be a container around a
   container (R2 rebuilds the composition itself). */
.outfit-viewport {
  background: transparent;
  border: none;
  border-radius: 0;
  padding: 0;
  min-height: 340px;
}
.outfit-placeholder {
  text-align: center; color: var(--secondary-plate);
  background: var(--plate); border-radius: var(--radius-hero);
  padding: 72px var(--sp-5); font-size: var(--fs-body);
}

.outfit-card { animation: fade 0.3s ease; }
/* The picture comes first, then the look's name and the reason for it — the
   order the B artboard reads in, and the reverse of what shipped, where five
   full-width tiles stood between the screen title and the hero. */
.outfit-caption {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--sp-3);
  margin-top: var(--sp-4);
}
.outfit-caption .title { font-family: var(--font-ui); font-weight: 600; letter-spacing: 0; font-size: var(--fs-section); }
/* The match reading, in words. A percentage claimed a precision the stylist
   has no source for, and a green pill gave a ranking the weight of a verdict:
   this is a quiet qualifier beside the look's name. */
.outfit-caption .score {
  flex: 0 0 auto;
  background: none; padding: 0; border-radius: 0;
  color: var(--secondary);
  font-size: var(--fs-meta); font-weight: 500;
}
/* The pieces are the caption to the picture, not the picture: three to a row,
   small enough that a five-piece look still reads as one block. */
.outfit-pieces-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-3) 10px;
  margin-top: var(--sp-4);
}
/* The piece sits directly on the outfit plate: it used to carry its own
   surface and border inside .outfit-viewport's, around an image well with a
   third — three nested containers for one garment. */
.piece {
  background: transparent;
  border: 0;
  overflow: hidden;
  padding-bottom: 10px;
}
.piece .p-img {
  aspect-ratio: 4 / 5;
  background: var(--shot-bg);
  border-radius: var(--radius-tile);
  overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  margin-bottom: var(--sp-2);
}
.piece .p-img img {
  width: 100%; height: 100%;
  object-fit: cover;
  padding: 0;
  box-sizing: border-box;
}
.piece .p-nm {
  font-size: 13px; font-weight: 600; line-height: 1.3; padding: 0 2px;
  /* three to a row is ~105px: two lines of a garment name, then an ellipsis,
     so one long name cannot push its row out of line with the next */
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden;
}
.piece .p-cat { font-size: var(--fs-meta); color: var(--secondary); padding: 0 2px; margin-top: 2px; }

/* The hero: the plate holds the picture and nothing else, so a render can fill
   it edge to edge at its own aspect. Caption, votes and Try-On sit underneath
   on the ground — anything inside the plate would either crop the photograph
   or letterbox it. */
.outfit-hero { position: relative; }
.hero-plate {
  position: relative;
  background: var(--plate);
  border-radius: var(--radius-hero);
  overflow: hidden;
  display: flex; flex-direction: column; align-items: center;
}
/* The mannequin only — NOT the sparkle in the Try-On pill, the expand glyph or
   the vote icons, which a descendant selector used to reach as well. */
.hero-plate > svg { height: 340px; width: auto; margin: var(--sp-4) 0; }
/* Caption, votes and the Try-On pill on one line under the picture. It wraps
   rather than crushes: "Regenerate (29 left)" beside two vote circles is wider
   than 375px allows, and a dropped line reads better than a clipped one. */
.hero-foot {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--sp-2) var(--sp-3);
  margin-top: var(--sp-3);
}
.hero-cap {
  font-family: var(--font-ui);
  font-style: normal;
  font-size: var(--fs-meta); font-weight: 500;
  color: var(--secondary);
  margin: 0 auto 0 2px;
  min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* What the two allowances are, under the button that spends them. Meta
   weight: it is a fact to check, not a thing to act on. */
.hero-quota {
  font-family: var(--font-ui);
  font-size: var(--fs-meta);
  font-weight: 400;
  color: var(--secondary);
  margin: var(--sp-2) 2px 0;
}
/* The stylist speaks in body copy, not in a quoted callout: a coloured rule
   down one side is the thing the craft floor calls out by name. */
.stylist-note {
  margin-top: var(--sp-4);
  background: transparent;
  border: none;
  border-radius: 0;
  padding: 0;
  font-size: var(--fs-body);
  line-height: 1.5;
  color: var(--ink);
}
.stylist-note b { color: var(--ink); font-weight: 600; }

/* What you do with the look once you have it. Try-On is not here — it belongs
   to the picture and sits on the hero plate. */
.studio-actions { display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-5); }
.studio-actions .btn-secondary, .studio-actions .btn-primary { flex: 1; }
.studio-actions .btn-secondary { display: flex; align-items: center; justify-content: center; gap: 8px; }
.studio-actions .btn-secondary svg { width: 18px; height: 18px; }

.impact-cta { margin-top: var(--sp-4); }

/* The same plate the placeholder uses: whatever the reason the hero is empty,
   the shape of the empty hero is one shape. */
.studio-warn {
  text-align: center;
  background: var(--plate); border-radius: var(--radius-hero);
  padding: 48px var(--sp-5);
  color: var(--secondary-plate); font-size: var(--fs-body);
}
.studio-warn .empty-emoji { color: var(--secondary-plate); }
.studio-warn p { margin: 0 0 var(--sp-4); }
.studio-warn .btn-secondary { background: var(--ground); }

/* ---------- Pack shot ---------- */
/* The screen's primary action, so it is the ink pill — and it carries the one
   glyph in the app that means "a machine makes this". */
.packshot-btn {
  flex: 0 0 auto;
  margin: 0; min-height: 44px; padding: 10px 18px; font-size: var(--fs-body);
  display: flex; align-items: center; gap: 7px;
  border-radius: var(--radius-pill);
  background: var(--btn-bg); color: var(--btn-ink); border: none; box-shadow: none;
}
.packshot-btn svg { width: 17px; height: 17px; flex: 0 0 auto; }
.packshot-btn:disabled, .packshot-btn[disabled] {
  background: var(--plate); color: var(--secondary-plate); border: none; box-shadow: none; cursor: default;
}
/* The render at ITS OWN aspect. The provider returns 1024×1536 (2:3), the stub
   400×500 (4:5), and a future one something else again — so the width is the
   plate's and the height follows the file. No crop, no letterbox: the plate is
   exactly as tall as the picture it holds. */
.hero-plate > img.hero-shot {
  display: block;
  width: 100%; height: auto;
  border-radius: var(--radius-hero);
  animation: fade 0.3s ease;
}
/* Before a render: the look's own pieces, photographed. 2×2 from four pieces
   up, a row below that. 4:5 so the plate keeps its footprint when the render
   lands in its place. */
.hero-collage {
  display: grid;
  gap: 2px;
  width: 100%; aspect-ratio: 4 / 5;
  background: var(--ground);
}
.hero-collage.c4 { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }
.hero-collage.c3 { grid-template-columns: repeat(3, 1fr); }
.hero-collage.c2 { grid-template-columns: 1fr 1fr; }
.hero-collage.c1 { grid-template-columns: 1fr; }
.hero-collage > * { background: var(--plate); min-width: 0; min-height: 0; }
.hero-collage img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}
/* Rendering: a white veil over the plate carrying the AI accent's progress bar
   and its label — the same bar as the add sheet's conversion, and the only
   place the accent appears on this screen. */
.hero-rendering {
  position: absolute; inset: 0;
  display: none; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--sp-3);
  background: var(--rendering-veil);
  font-size: var(--fs-meta); font-weight: 600;
  letter-spacing: 0; color: var(--ai);
  pointer-events: none;
}
.outfit-hero.rendering .hero-rendering { display: flex; }
.hero-rendering .track {
  width: min(160px, 56%); height: 4px;
  border-radius: var(--radius-pill);
  background: var(--ai-soft);
  overflow: hidden;
}
.hero-rendering .track i {
  display: block; width: 45%; height: 100%;
  border-radius: var(--radius-pill);
  background: var(--ai);
  animation: heroTrack 1.15s ease-in-out infinite;
}
.hero-rendering .lbl { animation: renderPulse 1.4s ease-in-out infinite; }
@keyframes renderPulse { 0%, 100% { opacity: 0.5; } 50% { opacity: 1; } }
@keyframes heroTrack { 0% { transform: translateX(-100%); } 100% { transform: translateX(222%); } }
@media (prefers-reduced-motion: reduce) {
  .hero-rendering .track i { animation: none; width: 100%; }
  .hero-rendering .lbl { animation: none; opacity: 1; }
}
.consent-copy { margin: 0 0 var(--sp-5); font-size: var(--fs-body); line-height: 1.55; color: var(--secondary); }

/* ---------- Like / dislike (vote row, in the hero foot) ---------- */
/* Small white circles beside the caption, not a second button row: the verdict
   on a picture belongs next to the picture, and the screen already has an ink
   pill. 36px drawn, 44px tappable. */
.vote-row { display: flex; gap: 6px; margin: 0; }
.vote-btn {
  position: relative;
  width: 36px; height: 36px;
  display: flex; align-items: center; justify-content: center;
  border: none;
  border-radius: 50%;
  background: var(--ground);
  box-shadow: var(--shadow-thumb);
  color: var(--ink);
  cursor: pointer;
  transition: background 0.15s, color 0.15s, transform 0.12s;
}
.vote-btn::after { content: ''; position: absolute; inset: -4px; }
.vote-btn svg { width: 18px; height: 18px; }
.vote-btn:hover { background: var(--plate); }
.vote-btn:active { transform: scale(0.94); }
.vote-btn.active { background: var(--ink); color: var(--on-ink); }

/* ---------- Lightbox (full-size pack shot, issue #12) ---------- */
.hero-plate img.hero-shot { cursor: zoom-in; }
/* A white circle with an ink glyph: it sits on the user's own photograph, so
   it is the same white-on-plate control the rest of world B uses, lifted off
   the picture by the one small shadow rather than by a dark scrim. */
.hero-expand {
  position: absolute; top: 10px; right: 10px;
  width: 32px; height: 32px;
  display: flex; align-items: center; justify-content: center;
  padding: 0; border: none; border-radius: 50%;
  background: var(--ground);
  box-shadow: var(--shadow-thumb);
  color: var(--ink);
  cursor: zoom-in;
}
.hero-expand::after { content: ''; position: absolute; inset: -6px; }
.hero-expand:hover { background: var(--plate); }
.hero-expand svg { width: 16px; height: 16px; }
.lightbox {
  position: fixed; inset: 0;
  z-index: 70;
  background: rgba(17, 17, 17, 0.94);
  display: flex; align-items: center; justify-content: center;
  animation: fade 0.2s ease;
}
/* The picture keeps the 16px corner it had in the hero plate — squared off
   against the dark ground it reads as a different object from the one that
   was tapped — and it clears the edges so the corner is visible. */
.lightbox img {
  max-width: calc(100% - var(--sp-6) * 2);
  max-height: calc(100% - 96px);
  object-fit: contain;
  border-radius: var(--radius-sheet);
  transition: transform 0.18s ease;
}
/* The same white circle as the expand control it was opened from. */
.lightbox-close {
  position: absolute;
  top: calc(12px + env(safe-area-inset-top, 0px)); right: 14px;
  width: 44px; height: 44px;
  display: flex; align-items: center; justify-content: center;
  padding: 0; border: none; border-radius: 50%;
  background: var(--ground);
  box-shadow: var(--shadow-thumb);
  color: var(--ink);
  font-size: 18px; line-height: 1;
  cursor: pointer;
}
.lightbox-close svg { width: 20px; height: 20px; }
.lightbox-close:hover { background: var(--plate); }

/* ---------- My Looks — saved-looks gallery (client step 9, FR-6) ---------- */
.looks-section { margin-top: 20px; }
.looks-filter {
  display: flex; gap: 8px; overflow-x: auto;
  margin-bottom: 12px; padding-bottom: 2px;
  scrollbar-width: none;
}
.looks-filter::-webkit-scrollbar { display: none; }
.looks-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14px 12px;
}
/* A plain positioning box, not a second plate: the card below IS the plate,
   and the close control and caption hang off this. */
.look-cell { position: relative; }
.look-card {
  display: block;
  appearance: none; -webkit-appearance: none;
  width: 100%; padding: 0; margin: 0;
  aspect-ratio: 4 / 5;
  border: none;
  border-radius: var(--radius-tile);
  background: var(--shot-bg);
  overflow: hidden;
  cursor: pointer;
  transition: box-shadow 0.15s, transform 0.12s;
}
.look-card:active { transform: scale(0.97); }
.look-card:hover { box-shadow: 0 0 0 2px var(--hairline); }
.look-shot { width: 100%; height: 100%; object-fit: cover; display: block; }
/* No worn shot yet: the pieces themselves, four to a plate. The hairline grid
   says "four garments", where one bled-together image would read as a photo. */
.look-collage {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  gap: 2px;
  width: 100%; height: 100%;
  background: var(--ground);
}
.look-collage > * { background: var(--plate); min-width: 0; min-height: 0; }
.look-collage img {
  width: 100%; height: 100%;
  object-fit: cover;
  padding: 0;
  box-sizing: border-box;
  display: block;
}
/* A white circle with an ink ✕, the same control as the hero's expand badge —
   28px drawn, 44px tappable through the ::after. */
.look-del {
  position: absolute; top: 6px; right: 6px;
  width: 28px; height: 28px; border-radius: 50%;
  padding: 0;
  background: var(--ground); color: var(--ink);
  box-shadow: var(--shadow-thumb);
  border: none; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.look-del:hover { background: var(--plate); }
.look-del svg { width: 14px; height: 14px; }
.look-del::after { content: ''; position: absolute; inset: -8px; }
/* Stacked and left-aligned, not a justified row: at two columns a right-aligned
   date ends up nearer the NEXT card's label than its own. */
.look-meta { margin-top: 7px; }
.look-vibe, .look-date {
  display: block;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.look-vibe { font-size: 13px; font-weight: 600; line-height: 1.3; }
.look-date {
  font-size: var(--fs-meta); line-height: 1.3; color: var(--secondary);
  font-variant-numeric: tabular-nums;
}

/* ---------- Render history strip (FR-6) ---------- */
.history-section { margin-top: 8px; }
.history-strip {
  display: flex; gap: 10px;
  overflow-x: auto;
  padding-bottom: 4px;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.history-strip::-webkit-scrollbar { display: none; }
.history-thumb {
  flex: 0 0 auto;
  width: 76px; aspect-ratio: 4 / 5;
  padding: 0; margin: 0;
  border: none;
  border-radius: var(--radius-tile);
  background: var(--shot-bg);
  overflow: hidden;
  cursor: pointer;
  transition: transform 0.12s, box-shadow 0.15s;
}
.history-thumb:active { transform: scale(0.94); }
.history-thumb:hover { box-shadow: 0 0 0 2px var(--hairline); }
.history-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ---------- Privacy actions (help modal, FR-1) ----------
   Three destructive actions read as a list, not as three outlined pills: a
   pill gives each of them the billing of the sheet's own "Got it", and three
   of them stacked made the help sheet look like a menu of things to press.
   Rules above and below the group, one between each row, label left. The
   second tap is the commitment, so the confirming row turns ink 600 —
   confirmTap already sets data-confirming, and no new colour is spent. */
.privacy-section { margin-top: var(--sp-2); margin-bottom: var(--sp-5); }
.privacy-actions {
  display: flex; flex-direction: column;
  margin-top: var(--sp-2);
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
}
.priv-row {
  display: block; width: 100%;
  min-height: 48px;
  padding: var(--sp-3) 2px;
  border: none; background: none;
  text-align: left;
  font-family: var(--font-ui);
  font-size: var(--fs-body); font-weight: 500;
  color: var(--ink);
  cursor: pointer;
  transition: color 0.15s, background 0.15s;
}
.priv-row + .priv-row { border-top: 1px solid var(--hairline); }
.priv-row:hover { color: var(--ink); background: var(--plate); }
.priv-row[data-confirming] { font-weight: 600; background: var(--plate); }

/* ---------- Worth It ---------- */
.worth-intake { display: flex; flex-direction: column; gap: var(--sp-4); }
.worth-meta { display: flex; flex-direction: column; gap: var(--sp-1); }
.worth-result { animation: fade 0.3s ease; margin-top: var(--sp-6); }

/* ---------- The verdict ----------
   One recessed plate holding the whole answer: the word, the score that
   produced it, the sentence, and the reasons. It used to be a saturated
   green / amber / red field under white text, which gave a rule-engine
   estimate the authority of a test result and put the app's loudest surface
   on its least certain number (R4). The hue survives where it means
   something — on the verdict word itself, all three ≥ 4.5:1 on the plate. */
.verdict-card {
  background: var(--plate);
  border-radius: var(--radius-hero);
  padding: var(--sp-5);
  margin-bottom: var(--sp-5);
}
.verdict-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--sp-3);
}
.verdict-label {
  font-size: var(--fs-title); font-weight: 700; letter-spacing: -0.01em;
  line-height: 1.2;
}
.verdict-card.buy   .verdict-label { color: var(--verdict-buy); }   /* 4.61:1 */
.verdict-card.maybe .verdict-label { color: var(--verdict-maybe); } /* 4.61:1 */
.verdict-card.skip  .verdict-label { color: var(--verdict-skip); }  /* 5.94:1 */
/* The score is a reading, not a headline: an estimate from an on-device rule
   engine shown at 44px read as a measurement. It keeps its scale so the
   number is legible as one. */
.verdict-score {
  flex: 0 0 auto;
  font-size: var(--fs-meta); font-weight: 500;
  color: var(--secondary-plate);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.verdict-sub {
  margin: var(--sp-2) 0 0;
  font-size: var(--fs-body); line-height: 1.45;
  color: var(--ink);
}
/* The reasons are the argument behind the word, so they sit in the same plate
   rather than as a loose list further down the scroller. */
.verdict-card .reason-list { margin-top: var(--sp-3); }
.verdict-card .reason-list li:last-child { border-bottom: none; padding-bottom: 0; }

.worth-pairings h3 { font-size: var(--fs-section); font-weight: 600; margin: 0 0 var(--sp-3); }
.pairing-row {
  display: flex; gap: var(--sp-3); overflow-x: auto;
  padding-bottom: var(--sp-1);
  scrollbar-width: none;
  margin-inline: calc(var(--page-pad) * -1);
  padding-inline: var(--page-pad);
}
.pairing-row::-webkit-scrollbar { display: none; }
/* The same tile the Wardrobe and the look pieces use: a 4:5 photograph at
   12px with its name under it. It was a square thumb on its own plate, which
   made one piece of the closet look like a different kind of object here. */
.pairing {
  flex: 0 0 104px;
  background: transparent;
  border: none;
  border-radius: 0;
  padding: 0;
  text-align: left;
}
.pairing img, .pairing .no-photo {
  width: 100%; aspect-ratio: 4 / 5;
  border-radius: var(--radius-tile);
  object-fit: cover;
  background: var(--plate);
  display: block;
}
.pairing .pn {
  font-size: var(--fs-body); font-weight: 600;
  margin-top: var(--sp-2); line-height: 1.25;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden;
}
.pair-none { color: var(--secondary); font-size: var(--fs-body); margin: 0; line-height: 1.45; }
#worthReset { margin-top: var(--sp-5); }
.reason-list { margin: var(--sp-4) 0 0; padding: 0; list-style: none; }
.reason-list li {
  display: flex; gap: 10px; align-items: flex-start;
  font-size: var(--fs-body); padding: var(--sp-3) 0; border-bottom: 1px solid var(--hairline);
  color: var(--ink); line-height: 1.45;
}
.reason-list li .ic { flex: 0 0 auto; display: flex; margin-top: 2px; color: var(--secondary-plate); }
.reason-list li .ic svg { width: 16px; height: 16px; }
.reason-list li .ic.ok { color: var(--ok); }
.reason-list li .ic.warn { color: var(--warn); }

/* ---------- Impact ---------- */
/* R5, on the finish reviewer's first material fix. This was a 2×2 of identical
   plate cards, each a big number over a small label, plus two more full-width
   plates — the hero-metric template AND the same-size-card scaffold the craft
   floor refuses by name, and the one place in the app that spent a colour on
   decoration. Same four numbers, same words, no plates: the footprint is the
   screen's one figure and leads, the four stats are a hairline-ruled list on
   the ground. The `—` for "no wears yet" stops reading as a broken headline
   once it is a row's value rather than a hero metric.
   `order` moves the lead VISUALLY only. The DOM order is untouched, because
   four stats then their summary is a sound reading sequence and because two
   regression harnesses address the looks-styled card as `.impact-card[3]`. */
.impact-cards { display: flex; flex-direction: column; margin-bottom: var(--sp-5); }
.impact-card {
  background: none;
  border: none;
  border-radius: 0;
  padding: var(--sp-3) 2px;
  /* row-reverse puts the label left and the figure right without touching the
     markup: the children are emitted figure-then-label. */
  display: flex; flex-direction: row-reverse;
  align-items: baseline; justify-content: space-between; gap: var(--sp-4);
  border-bottom: 1px solid var(--hairline);
}
/* The lead figure: the one display-sized thing on the screen. */
.impact-card.wide {
  order: -1;
  display: block;
  border-bottom: none;
  padding: var(--sp-1) 2px var(--sp-5);
}
/* A figure, at the top of the app's own type scale — 28px was a step that
   exists nowhere else, and it was carrying a whole sentence in the zero
   state. Only a number is display-sized here; see .impact-zero. */
.impact-card .big {
  font-size: var(--fs-title); font-weight: 700; letter-spacing: -0.01em;
  color: var(--ink); line-height: 1.2;
  font-variant-numeric: tabular-nums;
}
/* --secondary, not --secondary-plate: these labels sit on the ground now. */
.impact-card .cap { font-size: var(--fs-meta); color: var(--secondary); margin-top: 0; font-weight: 500; line-height: 1.35; }
.impact-card.wide .cap { margin-top: var(--sp-1); }
/* The green is gone. OWN-WORLD allows exactly one accent, on the render
   moment, and says of the semantic pair "never a second brand colour": --ok
   belongs to the Worth It? verdict, where it means a judgement, not to two
   Impact figures where it only meant "this is the eco bit". */
.impact-card.green .big { color: var(--ink); }
/* No wears logged yet means there is no average, and "$—" at 700 read as a
   broken number. The mark for "nothing to report" is quiet. */
.impact-card .big.none { color: var(--secondary-plate); font-weight: 500; }
/* The zero state has no number to show, so it has no figure: a lead line at
   body weight and the explanation under it, both body copy. */
.impact-zero { font-size: var(--fs-body); font-weight: 600; color: var(--ink); line-height: 1.4; }
.impact-zero + .cap { font-size: var(--fs-body); font-weight: 400; line-height: 1.45; }
.impact-note {
  background: var(--plate);
  border-radius: var(--radius-tile);
  padding: var(--sp-4);
  font-size: var(--fs-body);
  line-height: 1.5;
  color: var(--secondary-plate);
}
.impact-note strong { color: var(--ink); font-weight: 600; }

/* ---------- FAB ----------
   A labelled black pill, centred, not a bare circle: a "+" alone makes the
   user guess, and B gives the action its words. */
.fab {
  position: absolute;
  left: 50%;
  right: auto;
  transform: translateX(-50%);
  bottom: calc(var(--tab-h) + var(--safe-bottom) + var(--sp-4));
  height: 48px;
  padding: 0 22px 0 18px;
  border-radius: var(--radius-pill);
  background: var(--btn-bg);
  color: var(--btn-ink);
  border: none;
  font-family: var(--font-ui);
  font-size: var(--fs-btn); font-weight: 600; line-height: 1;
  white-space: nowrap;
  box-shadow: var(--shadow-float);
  cursor: pointer;
  z-index: 20;
  display: flex; align-items: center; justify-content: center; gap: var(--sp-2);
  transition: transform 0.12s, background 0.15s;
}
.fab svg { width: 20px; height: 20px; flex: 0 0 auto; }
.fab:hover { background: #000000; }
.fab:active { transform: translateX(-50%) scale(0.96); }

/* ---------- Tab bar ---------- */
.tabbar {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: calc(var(--tab-h) + var(--safe-bottom));
  padding-bottom: var(--safe-bottom);
  /* Opaque white over a hairline. The blur was a glass effect standing in
     for a surface; B's bar is a surface. */
  background: var(--tabbar-bg);
  border-top: 1px solid var(--hairline);
  display: flex;
  z-index: 15;
}
.tab {
  flex: 1;
  background: none; border: none; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--sp-1);
  color: var(--secondary);
  font-family: var(--font-ui);
  /* 11px floor: these five labels are the app's primary navigation, and 10px
     functional text degrades on small viewports. */
  font-size: var(--fs-tab); font-weight: 500; letter-spacing: 0;
  padding-top: 6px;
  transition: color 0.18s, transform 0.12s;
}
.tab:active { transform: scale(0.94); }
.tab .ic { display: flex; }
.tab .ic svg { width: 24px; height: 24px; }
.tab.active { color: var(--tab-active); font-weight: 600; }

/* ---------- Modals / sheets ---------- */
.modal-scrim {
  position: fixed; inset: 0;
  background: var(--scrim);
  display: flex; align-items: flex-end; justify-content: center;
  z-index: 50;
  animation: fade 0.2s ease;
}
@media (min-width: 720px) { .modal-scrim { align-items: center; } }
.sheet {
  width: 100%;
  max-width: 430px;
  background: var(--sheet-bg);
  border-radius: var(--radius-sheet) var(--radius-sheet) 0 0;
  padding: 8px 20px calc(20px + var(--safe-bottom));
  max-height: 90vh;
  overflow-y: auto;
  animation: slideup 0.34s cubic-bezier(.2,.9,.25,1);
  box-shadow: var(--shadow-sheet);
}
@media (min-width: 720px) { .sheet { border-radius: var(--radius-sheet); } }
@keyframes slideup { from { transform: translateY(100%); } to { transform: none; } }
/* The grabber: the one thing that says "this sheet drags". */
.sheet-handle {
  width: 36px; height: 5px; border-radius: var(--radius-pill);
  background: var(--hairline);
  margin: 6px auto var(--sp-4);
}
.sheet h2 {
  margin: 0 0 var(--sp-4);
  font-family: var(--font-ui);
  font-size: var(--fs-title);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.2;
}
.sheet-actions { display: flex; align-items: center; gap: 10px; margin-top: var(--sp-3); }
.sheet-actions .btn-secondary, .sheet-actions .btn-primary { flex: 1; }
/* A text Cancel takes only its label's width; the action keeps the rest. */
.sheet-actions .btn-text { flex: 0 0 auto; margin-left: -14px; }
/* The wordmark introduces the product; it heads the help sheet and
   onboarding step 1, and lives nowhere else. */
.sheet-brand {
  display: block;
  text-align: center;
  margin: 0 0 var(--sp-2);
}

/* The five steps are what the app is FOR, so they are read at full strength:
   ink at the body step, with only the numeral in secondary. Set in grey they
   read as small print under the heading that promises to explain the app. */
.help-steps { margin: 0 0 var(--sp-5); padding-left: 18px; }
.help-steps li { margin-bottom: var(--sp-3); font-size: var(--fs-body); line-height: 1.5; color: var(--ink); }
.help-steps li::marker { color: var(--secondary); }
.help-steps b { font-weight: 600; }

/* ---------- Toast ---------- */
/* Browser surfaces the design never claimed: selection, caret and the form
   control accent all shipped as platform blue against a bone ground. */
::selection { background: var(--ink); color: var(--on-ink); }
input, textarea { caret-color: var(--ink); }
input[type="range"], input[type="checkbox"], input[type="radio"] { accent-color: var(--ink); }

/* Undo sits beside the message, never inside its wrap: "Removed White cotton
   tee" breaks over two lines at 375px and the action was ending up mid-
   sentence. The ::after grows the target to 44px without moving the label. */
.toast-action {
  position: relative;
  flex: 0 0 auto;
  margin-left: var(--sp-4); padding: 0;
  background: none; border: none;
  color: var(--on-ink); font: inherit; font-weight: 600;
  text-decoration: underline; text-underline-offset: 3px;
  cursor: pointer;
}
.toast-action::after { content: ''; position: absolute; inset: -12px -8px; }
/* A dark pill floating over the app — the one dark surface in world B. */
.toast {
  position: fixed;
  left: 50%; bottom: calc(var(--tab-h) + var(--safe-bottom) + var(--sp-5));
  transform: translateX(-50%);
  /* left:50% leaves a fixed, shrink-to-fit box only the right HALF of the
     viewport to lay out in (measured 187.5px at 375), so a two-word message
     with Undo beside it broke over four lines. max-content sizes it to the
     line, and the cap is measured against the viewport, not that half. */
  width: max-content;
  display: flex; align-items: center; justify-content: center;
  background: var(--ink);
  color: var(--on-ink);
  padding: var(--sp-3) var(--sp-5);
  border-radius: var(--radius-pill);
  font-size: var(--fs-body); font-weight: 500;
  line-height: 1.35;
  z-index: 60;
  box-shadow: var(--shadow-float);
  animation: toastIn 0.25s cubic-bezier(.2,.9,.25,1);
  max-width: calc(min(100vw, 430px) - 48px);
  text-align: center;
}
@keyframes toastIn { from { opacity: 0; transform: translate(-50%, 8px); } to { opacity: 1; transform: translate(-50%, 0); } }


/* ---------- Tailor fitting ---------- */
.tailor-intro {
  font-size: var(--fs-body); color: var(--secondary-plate);
  background: var(--plate);
  border-radius: var(--radius-tile);
  padding: var(--sp-3) var(--sp-4);
  margin: 0 0 var(--sp-3);
  line-height: 1.5;
}
.measure-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  background: var(--plate);
  border: none;
  border-radius: var(--radius-tile);
  padding: var(--sp-3) var(--sp-3) var(--sp-3) var(--sp-4);
  margin-bottom: var(--sp-2);
  cursor: pointer;
  transition: box-shadow 0.15s, background 0.15s;
}
.measure-row.focus { box-shadow: inset 0 0 0 1.5px var(--ink); background: var(--ground); }
.measure-row.confirmed { background: var(--plate); }
.m-lbl { font-size: var(--fs-body); font-weight: 600; }
.m-check { color: var(--ok); font-size: var(--fs-tab); font-weight: 600; margin-left: var(--sp-1); white-space: nowrap; }
/* The tick is a drawn icon, not a ✓ character — the icon family is stroke SVG. */
.m-check svg { width: 11px; height: 11px; vertical-align: -1px; }
.m-est { color: var(--secondary-plate); font-size: 11px; font-weight: 500; margin-left: var(--sp-1); }
.m-hint { font-size: 11px; color: var(--secondary-plate); margin-top: 2px; }
.stepper { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; }
.step-btn {
  width: 40px; height: 40px;
  border-radius: 50%;
  border: none;
  background: var(--ground);
  font-size: var(--fs-section); font-weight: 500; color: var(--ink);
  cursor: pointer; line-height: 1;
  transition: background 0.12s, transform 0.12s;
}
.step-btn:hover { background: var(--hairline); }
.step-btn:active { background: var(--hairline); transform: scale(0.92); }
.m-val { font-size: var(--fs-btn); font-weight: 600; min-width: 56px; text-align: center; font-variant-numeric: tabular-nums; }
.m-val small { font-size: var(--fs-tab); color: var(--secondary-plate); font-weight: 500; margin-left: 1px; }

.silhouette-card {
  background: var(--plate);
  border-radius: var(--radius-tile);
  padding: 14px var(--sp-4);
  margin: var(--sp-4) 0 var(--sp-5);
}
.sil-head { font-size: var(--fs-body); }
.sil-head b { font-weight: 600; }
/* The provenance line sits under the reading, not beside it: run in, it wrapped
   mid-phrase ("read from your / measurements") against the reading's own wrap. */
.sil-head em {
  display: block;
  font-style: normal; font-size: var(--fs-tab);
  color: var(--secondary-plate);
  margin: 2px 0 0;
}
.sil-tip { font-size: var(--fs-body); color: var(--secondary-plate); margin-top: var(--sp-2); line-height: 1.5; }

/* ---------- Ask the stylist ---------- */
.ask-box {
  display: flex; gap: 8px;
  margin: 4px 0 12px;
}
.ask-box input {
  flex: 1;
  min-height: 48px;
  border: none;
  border-radius: var(--radius-field);
  background: var(--plate);
  padding: 13px 18px;
  font-size: var(--fs-body);
  font-family: inherit;
  color: var(--ink);
  outline: none;
  transition: box-shadow 0.15s, background 0.15s;
}
.ask-box input::placeholder { color: var(--secondary-plate); }
.ask-box input:focus { background: var(--ground); box-shadow: inset 0 0 0 1.5px var(--ink); }
/* A round send, the way a chat field sends. */
.ask-btn {
  width: 48px; height: 48px;
  border-radius: var(--radius-pill);
  border: none;
  background: var(--btn-bg);
  color: var(--btn-ink);
  cursor: pointer;
  flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  transition: transform 0.12s, background 0.15s;
}
.ask-btn:hover { background: #000000; }
.ask-btn:active { transform: scale(0.94); }
.ask-btn svg { width: 20px; height: 20px; }
.ask-reply {
  display: flex; gap: 10px; align-items: flex-start;
  background: var(--plate);
  border: none;
  border-radius: var(--radius-tile);
  padding: var(--sp-3) 15px;
  font-size: var(--fs-body);
  line-height: 1.5;
  color: var(--ink);
  margin-bottom: var(--sp-3);
  animation: fade 0.25s ease;
}
.ask-ic { flex: 0 0 auto; display: flex; color: var(--secondary-plate); margin-top: 2px; }
.ask-ic svg { width: 17px; height: 17px; }

/* ---------- Onboarding ---------- */
.ob-sheet { text-align: left; }
/* The monogram that opens the app: the brand's own letter on an ink plate,
   with the wordmark beneath it. This and the help sheet are the only two
   places the wordmark appears. */
.ob-mark {
  display: flex; align-items: center; justify-content: center;
  width: 56px; height: 56px;
  /* Mark and wordmark are one lockup: tight to each other, with the heading's
     own space opening beneath them so the wordmark reads as the brand's
     signature and not as a label stuck on top of the heading. */
  margin: var(--sp-2) auto var(--sp-2);
  border-radius: var(--radius-tile);
  background: var(--ink);
  color: var(--on-ink);
  font-family: var(--font-ui);
  font-size: 30px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.02em;
  user-select: none;
}
.ob-sheet h2 { text-align: center; }
#obStep1 .sheet-brand { margin-bottom: 0; }
#obStep1 h2 { margin-top: var(--sp-6); }
.ob-sub {
  font-size: var(--fs-body); color: var(--secondary);
  text-align: center;
  margin: var(--sp-1) 0 var(--sp-5);
  line-height: 1.5;
}
.ob-figure {
  height: 230px;
  display: flex; justify-content: center;
  margin: 6px 0;
}
.ob-figure svg { height: 100%; }
.ob-figure.sm { height: 165px; margin: 0 0 12px; }
.ob-acc {
  text-align: center;
  font-size: var(--fs-body); color: var(--secondary-plate);
  background: var(--plate);
  border-radius: var(--radius-tile);
  padding: var(--sp-3) 14px;
  margin-bottom: var(--sp-4);
}
.ob-acc b { color: var(--ink); font-weight: 600; }
.ob-dots { display: flex; gap: 6px; justify-content: center; margin: 0 0 14px; }
.ob-dots i {
  width: 6px; height: 6px; border-radius: var(--radius-pill);
  background: var(--hairline);
  transition: background 0.2s, width 0.2s;
}
.ob-dots i.on { background: var(--ink); width: 18px; }
.ob-meter {
  background: var(--plate);
  border: none;
  border-radius: var(--radius-tile);
  padding: var(--sp-3) 14px;
  margin: 2px 0 var(--sp-4);
}
.ob-meter .acc-meter { margin-bottom: 0; background: var(--ground); }
/* The upload fallback is a .btn-text (R1's label-only button); this only puts
   it on its own centred line under the action it is an alternative to. */
.ob-upload {
  display: block;
  margin: var(--sp-3) auto 0;
}
.ob-selfie { padding: var(--sp-1) 0 14px; }
.selfie-done { display: flex; align-items: center; gap: 14px; justify-content: center; }
.selfie-done img {
  width: 84px; height: 105px;
  object-fit: cover;
  border-radius: var(--radius-tile);
}
.selfie-done b { font-size: var(--fs-body); }
/* .btn-text carries 14px of side padding; pulled back so "Retake" starts on
   the same line as the sentence above it (the trick .sheet-actions uses). */
.selfie-done .ob-upload { margin: 6px 0 0 -14px; }

/* ---------- Body shape picker ---------- */
.shape-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}
.shape-btn {
  border: none;
  background: var(--plate);
  border-radius: var(--radius-tile);
  padding: var(--sp-3) var(--sp-1) var(--sp-3);
  cursor: pointer;
  color: var(--shape-fig);
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  transition: background 0.15s, box-shadow 0.15s, transform 0.12s;
}
.shape-btn:hover { background: #ECEDF0; }
.shape-btn:active { transform: scale(0.96); }
.shape-btn svg { width: 30px; height: 56px; }
.shape-btn .sh-lbl { font-size: 12px; font-weight: 600; color: var(--ink); }
.shape-btn .sh-hint { font-size: var(--fs-tab); color: var(--secondary-plate); }
.shape-btn.active {
  background: var(--ground);
  color: var(--ink);
  box-shadow: inset 0 0 0 2px var(--ink);
}
.shape-btn.active .sh-hint { color: var(--secondary); }

/* ---------- Guided selfie ---------- */
.face-stage {
  position: relative;
  width: min(100%, 320px);
  aspect-ratio: 4 / 5;
  margin: 0 auto;
  border-radius: var(--radius-hero);
  overflow: hidden;
  background: var(--ink);
}
.face-stage video, .face-stage img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}
.face-stage video { transform: scaleX(-1); }
/* Mirror CAMERA confirm stills to match the mirrored live viewfinder — uploads
   show as-is. The STORED selfie stays unmirrored (canvas ignores CSS transforms). */
#facePreview.mirror { transform: scaleX(-1); }
.face-oval { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.face-msg {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  color: #fff; font-size: 14px; font-weight: 500;
  padding: 24px; text-align: center;
}
/* The capture sheet's heading is left, so its standing instruction is too —
   only the wizard centres its copy. */
/* Consent step — the notice the twin is created behind. Left-aligned against
   the wizard's centred copy on purpose: this is read, not glanced at. */
.consent-list {
  list-style: none;
  margin: 0 0 var(--sp-4);
  padding: 0;
  text-align: left;
}
.consent-list li {
  font-size: var(--fs-body);
  color: var(--secondary);
  line-height: 1.55;
  padding: var(--sp-3) 0;
  border-bottom: 1px solid var(--hairline);
}
.consent-list li:first-child { border-top: 1px solid var(--hairline); }
.consent-list b { color: var(--ink); font-weight: 600; }

.consent-agree {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  text-align: left;
  font-size: var(--fs-body);
  color: var(--ink);
  line-height: 1.5;
  padding: var(--sp-3) 0;
  cursor: pointer;
}
/* 20px and a 44px tap target around it: the one control on this screen. */
.consent-agree input[type="checkbox"] {
  flex: 0 0 auto;
  width: 20px; height: 20px;
  margin: 2px 0 0;
  accent-color: var(--ink);
  cursor: pointer;
}
.consent-note {
  font-size: var(--fs-meta);
  color: var(--secondary);
  text-align: left;
  margin: 0 0 var(--sp-4);
  line-height: 1.5;
}

.face-sheet .ob-sub { text-align: left; }
.face-sheet .sheet-actions { margin-top: 16px; }
.face-sheet .ob-upload { margin-bottom: 4px; }
/* The shutter. While the camera is armed the action is not a sentence, it is a
   button you press to take the picture, so it takes the shape every camera
   gives it: an ink disc inside a ring. It goes back to a labelled pill the
   moment it means something else ("Hold on…", "Try again", "Use photo"). */
.btn-primary.shutter {
  width: 68px; height: 68px;
  min-height: 68px;
  padding: 0;
  border-radius: 50%;
  box-shadow: inset 0 0 0 4px var(--ground), inset 0 0 0 5.5px var(--ink);
}
.btn-primary.shutter .lbl {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
/* Three columns so the disc is centred on the sheet and Cancel keeps its own
   place at the edge: the empty third column is the counterweight. */
.face-sheet .sheet-actions.shot-row {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
}
.face-sheet .sheet-actions.shot-row .btn-text { justify-self: start; }

/* ---------- Full-body onboarding (Mode B) ---------- */
.fb-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}
.fb-slot {
  position: relative;
  border: 1px dashed var(--drop-line);
  background: var(--plate);
  border-radius: var(--radius-tile);
  aspect-ratio: 3 / 4;
  min-height: 44px;
  padding: 0;
  cursor: pointer;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  color: var(--secondary-plate);
  transition: border-color 0.15s, transform 0.12s;
}
.fb-slot:active { transform: scale(0.96); }
.fb-slot .fb-ic { display: flex; color: var(--ink); }
.fb-slot .fb-ic svg { width: 26px; height: 26px; }
.fb-slot .fb-lbl { font-size: 11px; font-weight: 600; color: var(--secondary-plate); }
.fb-slot.has-photo { border-style: solid; border-color: transparent; }
.fb-slot.has-photo img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}
.fb-check {
  position: absolute; top: 6px; right: 6px;
  width: 20px; height: 20px;
  border-radius: 50%;
  background: var(--ink);
  color: var(--on-ink);
  display: flex;
  align-items: center;
  justify-content: center;
}
.fb-check svg { width: 12px; height: 12px; }
/* ===================================================================
   World B — the client-mockup system
   White ground, one grey plate, near-black ink, the platform's own sans.
   Emphasis is weight and fill, never hue: the only colour in the shell is
   the AI accent on the one moment a machine is working for you.
   Values, ratios and per-primitive usage rules: zilhouette/DESIGN.md.
   =================================================================== */
:root {
  /* ---------- Ground, ink, plate ---------- */
  --ground: #FFFFFF;             /* the app ground — everything sits on white */
  --plate: #F5F5F7;              /* the one recessed surface: photo wells, tracks,
                                    inactive chips, insets. Never a second tier. */
  --ink: #111111;                /* 18.88:1 on ground, 17.34:1 on plate */
  --secondary: #6B7280;          /* secondary text ON GROUND ONLY — 4.83:1 */
  --secondary-plate: #5F6673;    /* the same role on --plate. B's own #6B7280
                                    measures 4.44:1 there and fails the floor,
                                    so plate-mounted secondary text darkens
                                    to 5.31:1. Deliberate, documented. */
  --hairline: #E5E7EB;           /* the only divider weight in the app */
  --hairline-strong: #D1D5DB;    /* dashed drop zones, dimension lines */
  --on-ink: #FFFFFF;             /* text and icons on an ink fill */

  /* The AI accent. Used ONLY where a machine is working on the user's
     behalf — the pack-shot / try-on progress bar and its veil. Never body
     text, never a second brand colour. 7.10:1 on ground, far past the 3:1
     non-text floor. */
  --ai: #6D28D9;
  --ai-soft: #F3EFFD;

  /* ---------- Legacy names, remapped role for role ----------
     ~300 var(--…) uses across this file were written against the Ink &
     Lacquer set. Each name keeps its ROLE and takes B's value, so an
     untouched rule renders in B; primitives rewritten below use the new
     names above. DESIGN.md carries the full old → new map. */
  --frame-bg: #E5E7EB;           /* desktop surround behind the phone frame */
  --bg: var(--ground);
  --surface: #FFFFFF;            /* "raised card" is just the ground in B */
  --surface-2: var(--plate);
  --ink-soft: var(--secondary);
  --ink-faint: var(--secondary);  /* B has ONE secondary; nothing lighter exists */
  --line: var(--hairline);
  --line-strong: var(--hairline-strong);
  --accent: var(--ink);          /* B has no brand hue: emphasis is the ink fill */
  --accent-deep: #000000;
  --ivory: var(--on-ink);
  --focus: var(--ink);           /* 2px ink ring, 2px offset */

  /* ---------- Semantic — retuned to B, all AA on ground and plate ---------- */
  --ok: #15803D;                 /* 5.02:1 ground · 4.61:1 plate */
  --ok-soft: var(--plate);       /* B has no tinted panel: "good" sits on plate */
  --ok-ink: var(--ink);
  --warn: #B45309;               /* 5.02:1 ground */
  --verdict-buy: #15803D;        /* white on it: 5.02:1 */
  --verdict-maybe: #B45309;      /* white on it: 5.02:1 */
  --verdict-skip: #B91C1C;       /* white on it: 6.47:1 */

  /* ---------- Shape ----------
     Four values, each with one job. A tile is 12, anything that holds a
     composition is 16, anything you tap is a pill, and the segmented
     control is its own 10/8 pair so the thumb nests inside the track. */
  --radius-tile: 12px;           /* item photos, thumbs, collage cells, inputs */
  --radius-hero: 16px;           /* hero plates, viewports, verdict, stage */
  --radius-sheet: 16px;          /* sheet top corners */
  --radius-pill: 999px;          /* buttons, chips, FAB, toast */
  --radius-seg: 10px;            /* segmented-control track */
  --radius-seg-thumb: 8px;       /* its thumb */
  --radius-field: 12px;          /* text inputs and steppers */
  /* legacy radius names, mapped by role */
  --radius-lg: var(--radius-hero);
  --radius-md: var(--radius-tile);
  --radius-sm: var(--radius-tile);
  --radius-btn: var(--radius-field);  /* inputs kept this name; BUTTONS take
                                         --radius-pill explicitly below */
  --radius-chip: var(--radius-pill);
  --radius-fab: var(--radius-pill);

  /* ---------- Depth — two shadows, both with offset and blur ---------- */
  --shadow-float: 0 6px 18px rgba(0, 0, 0, 0.18);  /* FAB, toast, sheets */
  --shadow-thumb: 0 1px 2px rgba(0, 0, 0, 0.08);   /* segmented thumb, badge */
  --shadow-sheet: 0 -8px 32px rgba(0, 0, 0, 0.12);
  --shadow-fab: var(--shadow-float);

  /* ---------- Components ---------- */
  --chip-active-bg: var(--ink);
  --chip-active-border: var(--ink);
  --chip-active-ink: var(--on-ink);
  --btn-bg: var(--ink);
  --btn-ink: var(--on-ink);
  --btn2-line: var(--ink);
  --tab-active: var(--ink);
  --tabbar-bg: #FFFFFF;          /* opaque: B's tab bar is white over a hairline */
  --thumb-bg: #FFFFFF;
  /* The twin stage is the app's one sheet of paper: a spec sheet is drawn on
     white with a ruled edge, and the figure's own skin tone is the only
     colour in it. `.twin-stage` is the sole consumer of both (R3). */
  --stage-bg: #FFFFFF;
  --stage-line: var(--hairline);
  --shot-bg: var(--plate);       /* every photo well */
  --drop-line: var(--hairline-strong);
  --note-bg: var(--plate);
  --shape-fig: var(--secondary); /* the body-shape drawings are information */
  --sheet-bg: #FFFFFF;
  --scrim: rgba(17, 17, 17, 0.46);
  --toast-bg: var(--ink);
  --toast-ink: var(--on-ink);
  --rendering-veil: rgba(255, 255, 255, 0.72);

  /* ---------- Type — the platform's own sans, nothing loaded ---------- */
  --font-ui: -apple-system, system-ui, "Segoe UI", Roboto, sans-serif;
  --font-display: var(--font-ui);   /* B has no display face; weight carries it */
  --display-weight: 700;
  --display-tracking: -0.01em;
  --wordmark-weight: 700;
  --wordmark-tracking: 0.16em;
  --wordmark-case: uppercase;
  /* Section labels are body-weight headings now, not a rubric voice. */
  --label-font: var(--font-ui);
  --label-style: normal;
  --label-size: 15px;
  --label-weight: 600;
  --label-tracking: 0;
  --label-case: none;

  /* ---------- Scale ---------- */
  --fs-title: 22px;    /* screen title, 700 */
  --fs-section: 17px;  /* section heading, 600 */
  --fs-btn: 15px;      /* button and segment labels, 600 */
  --fs-body: 14px;     /* body copy */
  --fs-meta: 12px;     /* meta, captions, counts */
  --fs-tab: 11px;      /* tab labels, 500 / 600 active */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 20px;
  --sp-6: 24px;
}

/* ---------- Build tag (help sheet footer — pilot diagnostics) ---------- */
.build-tag {
  margin: 14px 0 0;
  text-align: center;
  font-size: var(--fs-tab);
  color: var(--secondary);
}

/* ===================================================================
   SPRINT S2 — the style card, the Style Challenge, per-style Worth it?
   Built from world B's own tokens: no new colour, radius, shadow or type
   size. Three new primitives, all in DESIGN.md — .style-cell (a chip and
   the disc that explains it), .style-pill / .tag-pill (a pill that OPENS
   and a pill that only LABELS), and the style card's own sections.
   =================================================================== */

/* ---------- A style chip and its "what is this?" disc ---------- */
/* Siblings, never nested: the chip's tap already means "choose this style",
   and a button cannot hold a button. The pair travels together so the disc
   never scrolls away from the name it belongs to. */
/* 8px, not 2px (R5): the disc's hit area now grows on all four sides to reach
   the 44px floor, and at a 2px gap that area would have overlapped the chip and
   stolen its right edge. At 8px the two hit boxes clear each other by 2px. */
.style-cell { display: inline-flex; align-items: center; gap: 8px; flex: 0 0 auto; }
.ghost-btn.style-info {
  width: 32px; height: 32px;
  position: relative;
  background: none;              /* the chip beside it already carries the plate */
  color: var(--secondary);
  display: inline-flex; align-items: center; justify-content: center;
}
.ghost-btn.style-info svg { width: 19px; height: 19px; }
.ghost-btn.style-info:hover { background: var(--plate); color: var(--ink); }
.ghost-btn.style-info:active { transform: scale(0.94); }
/* 32px drawn, 44px tapped — the app's rule wherever the art is smaller than
   the target. It used to grow on three sides only (`inset: -6px -6px -6px 0`)
   so it could not steal the chip's taps, but that left it 38 wide: under the
   floor on one axis, which is the same as being under it. R5 grows it on all
   four and opens `.style-cell`'s gap to 8px instead, so the two hit boxes clear
   each other by 2px and both are 44. */
.ghost-btn.style-info::after { content: ''; position: absolute; inset: -6px; }

/* ---------- A pill that OPENS a style card ---------- */
/* Not a .chip: a chip is a choice and carries aria-pressed. This one has
   nothing to press — it opens the card — so it says so with the same info
   mark the disc uses. */
.style-pill {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 44px;
  padding: 10px 14px;
  border: none;
  border-radius: var(--radius-pill);
  background: var(--plate);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: var(--fs-body); font-weight: 600;
  cursor: pointer;
  transition: background 0.15s, transform 0.12s;
}
.style-pill svg { width: 16px; height: 16px; color: var(--secondary-plate); }
.style-pill:hover { background: #ECEDF0; }
.style-pill:active { transform: scale(0.96); }
.style-pill-row { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

/* ---------- A pill that only LABELS ---------- */
/* The canon pieces and the occasions on a style card. It is not tappable and
   never was, so it takes no touch target and no pressed state. */
.tag-row { display: flex; flex-wrap: wrap; gap: 6px; }
.tag-pill {
  padding: 5px 11px;
  border-radius: var(--radius-pill);
  background: var(--plate);
  color: var(--ink);
  font-size: var(--fs-meta); font-weight: 500;
}

/* ===================================================================
   THE STYLE CARD (T4)
   =================================================================== */
.style-card { font-size: var(--fs-body); line-height: 1.5; color: var(--ink); }
.style-card p { margin: 0 0 var(--sp-4); }
/* More space above a heading than below it. */
.sc-lbl {
  font-family: var(--label-font);
  font-size: var(--label-size);
  font-weight: var(--label-weight);
  letter-spacing: var(--label-tracking);
  color: var(--ink);
  margin: var(--sp-6) 0 var(--sp-2);
}
.sc-lead { font-size: var(--fs-btn); font-weight: 500; line-height: 1.45; }

/* The photographs. A bleeding strip like every other photo row in the app;
   each tile is 200px tall and as wide as its own picture wants within
   132–260px, so a portrait and a group shot both keep their proportions and
   the row still lines up top and bottom. */
.style-shots {
  display: flex; gap: var(--sp-3);
  overflow-x: auto;
  /* The strip now carries pictures only, so the gap to its sources list is the
     list's own top margin — this margin-bottom drops to the small step. */
  margin: 0 -20px var(--sp-3);
  padding: 0 20px;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.style-shots::-webkit-scrollbar { display: none; }
.style-shot { flex: 0 0 auto; margin: 0; }
.style-shot img {
  width: 100%; height: 200px;
  object-fit: cover;
  border-radius: var(--radius-tile);
  display: block;
  background: var(--plate);
}
/* The sources, under the strip rather than inside it (R5). The photographs
   scroll at their own widths; their words do not, because a figure cut by the
   screen edge cut the author's surname with it — and CC BY / CC BY-SA are
   conditional on attribution, so a truncated credit is a licence problem, not
   a styling one. Full width, nothing clipped, in the strip's own order. */
.style-sources {
  list-style: none;
  margin: 0 0 var(--sp-5);
  padding: 0;
}
.style-source + .style-source { margin-top: var(--sp-3); }
.shot-shows {
  display: block;
  font-size: var(--fs-meta); font-weight: 500;
  line-height: 1.35;
  color: var(--ink);
}
/* 11px is the app's floor and this is the one place it is spent on running
   text — 4.83:1 on the ground. `cite` un-italicised: the world has one face
   and no italic in its scale. */
.style-credit {
  display: block;
  margin-top: 3px;
  font-size: var(--fs-tab); font-weight: 500;
  font-style: normal;
  line-height: 1.35;
  color: var(--secondary);
}

/* The palette, read as three rows rather than one legend: a reader who knows
   nothing about fashion should not have to decode dot sizes. Core is the big
   dot, accent the small one, avoid is struck through. */
.sc-palette { margin-bottom: var(--sp-4); }
.pal-group { display: flex; align-items: center; gap: var(--sp-3); margin-bottom: var(--sp-2); }
.pal-lbl {
  flex: 0 0 52px;
  font-size: var(--fs-meta); font-weight: 500;
  color: var(--secondary);
}
.pal-dots { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.pal-dot {
  width: 22px; height: 22px;
  border-radius: 50%;
  border: 1px solid var(--hairline);
  display: inline-block;
  position: relative;
}
.pal-dot.sm { width: 16px; height: 16px; }
/* Struck, not greyed: the colour still has to be recognisable to be refused.
   The rule is ink between two ground-coloured hairlines, so it stays visible
   on all fourteen swatches — an ink line vanishes on black, a white one on
   white, and the palette has both. */
.pal-dot.no::after {
  content: '';
  position: absolute; left: -3px; right: -3px; top: 50%;
  height: 2px;
  margin-top: -2px;
  background: var(--ink);
  border-top: 1px solid var(--ground);
  border-bottom: 1px solid var(--ground);
  transform: rotate(-45deg);
}

/* The dress-code ladder — `formal` alone carries one. */
.sc-tiers { margin: 0 0 var(--sp-4); }
.sc-tiers dt {
  font-weight: 600;
  margin-top: var(--sp-3);
}
.sc-tiers dt:first-child { margin-top: 0; }
.sc-tiers dd { margin: 4px 0 0; }
.tier-who {
  font-size: var(--fs-meta); font-weight: 500;
  color: var(--secondary);
  margin-right: 5px;
}

.sc-list { margin: 0 0 var(--sp-4); padding-left: 20px; }
.sc-list li { margin-bottom: 6px; }
.sc-list li::marker { color: var(--secondary); }

.sc-sources { margin: var(--sp-5) 0 var(--sp-5); }
.sc-sources summary {
  font-size: var(--fs-body); font-weight: 500;
  color: var(--secondary);
  cursor: pointer;
  min-height: 32px;
  padding: 6px 0;
}
.sc-sources summary:hover { color: var(--ink); }
.sc-sources ul { margin: var(--sp-2) 0 0; padding-left: 20px; }
.sc-sources li { font-size: var(--fs-meta); color: var(--secondary); margin-bottom: 5px; }

/* ===================================================================
   THE STYLE CHALLENGE (T5)
   =================================================================== */
/* One plate on the Style screen, under the vibe row it acts on. It holds a
   composition — a sentence, a second sentence and two actions — so it takes
   the 16px radius, and it is the only plate there: nothing nests inside it. */
.challenge {
  background: var(--plate);
  border-radius: var(--radius-hero);
  padding: var(--sp-4);
  margin-bottom: var(--sp-5);
}
.ch-lead {
  margin: 0;
  font-size: var(--fs-body); line-height: 1.5;
  color: var(--ink);
}
.ch-sub {
  margin: var(--sp-2) 0 0;
  font-size: var(--fs-body); line-height: 1.45;
  color: var(--secondary-plate);   /* 5.31:1 — this copy sits on the plate */
}
.ch-actions {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--sp-2);
  margin-top: var(--sp-3);
}
.ch-actions .btn-text { padding-inline: 8px; }
/* The block is a plate, and --secondary measures 4.44:1 on it — the same
   reason .btn-text's own hover goes to ink. On a plate it takes the darker
   token (5.31:1), which is DESIGN.md's rule for plate-mounted secondary text. */
.challenge .btn-text { color: var(--secondary-plate); }
.challenge .btn-text:hover { color: var(--ink); }

/* ===================================================================
   WORTH IT? — the per-style answer (T6)
   =================================================================== */
/* Outside the verdict plate, because no plate nests in that one. */
.worth-styles { margin-top: var(--sp-6); }
.worth-styles h3 {
  font-size: var(--fs-section); font-weight: 600;
  margin: 0 0 var(--sp-3);
}
.worth-hint {
  margin: var(--sp-3) 0 0;
  font-size: var(--fs-body); line-height: 1.5;
  color: var(--secondary);          /* 4.83:1 — on the ground, not on a plate */
}
.worth-hint b { color: var(--ink); font-weight: 600; }
