/* ============================================================
   Style Engine — Editorial visual system
   Fonts: Instrument Serif (display) + Karla (UI). Self-hosted.
   ============================================================ */

@font-face {
  font-family: 'Instrument Serif'; font-style: normal; font-weight: 400;
  font-display: swap; src: url('fonts/instrument-serif-400.woff2') format('woff2');
}
@font-face {
  font-family: 'Instrument Serif'; font-style: italic; font-weight: 400;
  font-display: swap; src: url('fonts/instrument-serif-400-italic.woff2') format('woff2');
}
@font-face {
  font-family: 'Karla'; font-style: normal; font-weight: 400 600;
  font-display: swap; src: url('fonts/karla-400-600.woff2') format('woff2');
}

:root {
  /* colour — light (default) */
  --paper: #efe9e3;
  --panel: #fffdfb;
  --ink: #231f20;
  --ink-soft: #3f3833;
  --muted: #5f5550;
  --muted-2: #6c625d;
  --quiet: #7d716d;
  --line: #dcd3cc;
  --line-soft: #ece4e0;
  --accent: #a4677a;
  --accent-ink: #9a7f89;
  --accent-note: #6b4a55;
  /* a recessed panel — the sticky selection pane sits below the page, not on
     it (round 2, §6.6). The spec names this --panel-sunk; it had no
     equivalent, so it's the one token this pass adds. */
  --panel-sunk: #e9e2dc;
  /* a translucent panel for controls that float on top of a photo, so they stay
     legible against whatever is behind them — and, being a token, invert with
     the theme instead of stranding light text on a light pill. */
  --panel-scrim: rgba(255, 253, 251, .9);
  --photo: #e2d8d2;
  --photo-2: #ded2cd;
  --photo-3: #d5c8c2;
  --photo-label: #967c86;
  --on-ink: #f6f2ef;
  --on-ink-muted: #a79ea2;
  /* dark "surface" colour, distinct from --ink text so dark mode can keep
     surfaces dark while text inverts */
  --ink-surface: #231f20;
  --keep-bar: #231f20;
  --swatch-ring: rgba(35, 31, 32, .18);
  --rail-rule: #3a3235;
  --rail-body: #cbc2c5;

  --serif: 'Instrument Serif', Georgia, 'Times New Roman', serif;
  --sans: 'Karla', system-ui, -apple-system, 'Helvetica Neue', Arial, sans-serif;
  --radius: 4px;

  /* spacing scale — the only legal gaps in the app (cohesion pass, §7) */
  --space-2xs: 4px;
  --space-xs:  8px;
  --space-sm:  12px;
  --space-md:  20px;
  --space-lg:  32px;
  --space-xl:  48px;
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }

html, body { height: 100%; }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
#app { min-height: 100%; }

button { font: inherit; color: inherit; cursor: pointer; border: 0; background: none; }
img { display: block; max-width: 100%; }

/* shared type helpers */
.kicker { font-family: var(--sans); font-weight: 600; font-size: 10px; letter-spacing: .24em;
  text-transform: uppercase; color: var(--accent-ink); margin: 0; }
.serif { font-family: var(--serif); font-weight: 400; }
.note { font-family: var(--serif); font-style: italic; font-size: 16px; line-height: 1.45;
  color: var(--accent-note); padding-left: 14px; border-left: 2px solid var(--accent); }

:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }

/* ---- toned photo placeholder (id block; no glyphs) ---- */
.ph { position: relative; background: var(--photo); overflow: hidden; margin: 0; }
/* contain, not cover, as the default. Every photo in this app is reference
   imagery meant to be seen whole, and the ratios vary a lot — full-length
   outfit portraits at 864×1821 (~0.47), square makeup/hair shots at 1254²,
   landscape brows tutorials at 1535×1024 — so filling a fixed frame cropped
   41% of an outfit's height (the model's head and feet) and 47% of the brows
   photo's width. `contain` was already the documented rule for the routine
   reference column; it is now simply the rule. */
.ph > img { width: 100%; height: 100%; object-fit: contain; }
.ph-label { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-family: var(--sans); font-weight: 500; font-size: 11px; letter-spacing: .2em;
  text-transform: uppercase; color: var(--photo-label); }

/* ============================================================
   Screen 1 — Answer the day  (desktop)
   ============================================================ */
.screen-form { display: flex; min-height: 100vh; }

/* Sticky, self-scrolling pane (same pattern as .screen-split__pane) — added
   once the new bottom section-card links (below) made the rail's own
   content routinely taller than one viewport. Without this, reaching them
   meant scrolling the whole page rather than just the rail; sticky + its
   own overflow keeps the rail (and the cards pinned to its bottom) on
   screen while the form content scrolls past on the right. */
.rail { flex: 0 0 420px; background: var(--ink-surface); color: var(--on-ink);
  display: flex; flex-direction: column; padding: 44px 40px;
  position: sticky; top: 0; align-self: flex-start;
  min-height: 100vh; max-height: 100vh; overflow-y: auto; }
.rail .kicker { color: #c99caa; }
.rail-worn { margin: 14px 0 0; font-family: var(--serif); font-style: italic;
  font-size: 28px; line-height: 1.3; color: var(--on-ink); }
.rail-worn .worn-name { font-style: normal; border-bottom: 1px solid currentColor; }
.rail-rule { height: 1px; background: var(--rail-rule); margin: 24px 0; border: 0; }

/* practical + standing live in the content pane (light) */
.form-practical { margin-top: 30px; }
.form-weather { margin: 14px 0 0; font-size: 14px; color: var(--muted); }
.form-weather-link { margin-left: 4px; font-family: var(--sans); font-weight: 600; font-size: 13px;
  color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
.form-weather-link:hover { color: var(--accent-note); }
.form-standing { margin-top: 30px; padding-top: 22px; border-top: 1px solid var(--line);
  font-size: 14px; line-height: 1.65; color: var(--muted); }
/* cancels the border/margin/padding above when this block opens a screen
   right under its own heading, instead of sitting mid-flow (cohesion pass,
   §7 — replaces a one-off inline override). */
.form-standing.flush { margin-top: 0; border-top: none; padding-top: 0; }
.form-standing .kicker { margin-bottom: 8px; }
.form-standing .swatch { vertical-align: -3px; margin-right: 6px; }
/* the whole Standing block doubles as a button linking to the Looks page */
.form-standing-link { display: block; width: 100%; background: none; border: none;
  border-top: 1px solid var(--line); text-align: left; cursor: pointer; font: inherit;
  padding: 0; padding-top: 22px; margin-top: 30px; }
.form-standing-link:hover { color: var(--ink); }
/* Nails: has a leading swatch; give Hair: a matching offset so both labels align */
.standing-row-spacer { display: inline-block; width: 14px; margin-right: 6px; }

/* recently-liked: pinned to half the rail's height, own scroll if it overflows */
.rail-liked { flex: 0 0 50%; min-height: 0; overflow-y: auto; margin-top: 24px;
  padding-top: 22px; border-top: 1px solid var(--rail-rule); }
.liked-list { display: flex; flex-direction: column; gap: 12px; margin-top: 14px; }
.liked-row { display: flex; align-items: center; gap: 10px; }
.liked-thumb { width: 34px; height: 45px; border-radius: 3px; flex: none; background: var(--photo-3); }
.liked-thumb .ph-label { font-size: 6px; letter-spacing: .1em; }
.liked-text { min-width: 0; }
.liked-name { font-family: var(--serif); font-size: 14px; line-height: 1.2; color: var(--on-ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.liked-date { font-size: 11px; color: var(--on-ink-muted); margin-top: 2px; }
.liked-empty { font-size: 13px; color: var(--on-ink-muted); margin-top: 12px; line-height: 1.5; }

.form-content { flex: 1; padding: 52px 56px; display: flex; flex-direction: column; }
.form-date { color: var(--quiet); font-weight: 500; font-size: 12px; margin: 0 0 28px; }

.sentence { font-family: var(--serif); font-size: 44px; line-height: 1.35; color: var(--ink);
  max-width: 760px; margin: 0; }
.token { display: inline-block; padding: 2px 14px; border-radius: var(--radius);
  font-family: var(--serif); font-size: 38px; line-height: 1.15; vertical-align: baseline;
  transition: background .14s ease-out, color .14s ease-out; }
.token.answered { background: var(--ink-surface); color: var(--on-ink); }
.token.unanswered { border: 1px dashed #a1948f; color: #7d716d; }

.slot-options { margin: 22px 0 0; display: none; flex-wrap: wrap; gap: 8px; }
.slot-options.open { display: flex; }
/* Occasions group into a labelled stack instead of one long wrapping row
   (owner request) — same chips, just organized. Used both by the day form's
   .slot-options and the outfit builder's .standing-chips. */
.slot-options.occasion-groups { flex-direction: column; align-items: flex-start; gap: 16px; }
.occasion-group + .occasion-group { margin-top: 16px; }
.occasion-group .chip-label { margin-bottom: 8px; }
/* Quieter than a field label (.chip-label's own .kicker look) — Work/Home/
   Social/Active are sub-groups nested under the Occasions field, not peers
   of it (§7tt, 2026-08-02). Overrides .kicker's uppercase/letter-spacing/
   bold/accent-color treatment with a plain small muted line. */
.occasion-category-label { font-family: var(--sans); font-weight: 500; font-size: 11px;
  letter-spacing: 0; text-transform: none; color: var(--quiet); }

/* Section heading, not a field label — "Details" introduces a group of
   fields (Occasions/Effort/Temperature/Seasons/Formality) rather than
   labelling one input directly, so it reads as a real (if small) heading
   instead of sharing the same 10px kicker style as everything under it
   (§7tt, 2026-08-02). .section-label still supplies the spacing. */
.details-heading { font-family: var(--serif); font-weight: 400; font-size: 20px;
  line-height: 1.2; letter-spacing: 0; text-transform: none; color: var(--ink); }

/* Outfit builder's Details section. Effort/Occasions/Formality/Seasons/
   Temperature all stack full-width, one per row (§7vv, 2026-08-02, owner
   request: "get rid of the two column constraint" — the §7rr/§7uu paired
   2-up grid, .outfit-details-grid, is gone; Effort also moved ahead of
   Occasions here since it's this section's one required field, everything
   else is "leave blank for any"). Occasions' own four category groups
   still stack vertically inside their own block (§7ss) — .occasion-groups-grid
   is a plain wrapper, kept only so the "Occasions" label paragraph stays a
   sibling outside it rather than a stray grid item (§7rr's own lesson:
   display:grid catches every direct child, not just the intended ones). */
/* Every button within Effort/Temperature/Seasons/Formality is still the
   same width as its own row-mates (§7uu, 2026-08-02, "add more
   consistency") — a 4-column grid rather than flex-wrap sizing each chip
   to its own text. 4 because Temperature/Seasons/Formality each have 4
   options; Effort's 3 just leave the 4th cell empty rather than
   stretching to fill it, same rule already used for Occasions (§7ss).
   .outfit-detail-field (renamed from .outfit-details-grid in §7vv, since
   these fields no longer pair up in a grid of their own — the class now
   only scopes this per-field chip sizing) is added directly to each of
   the four fields' own wrapper div; kept off occasionChips (its own
   .occasion-group treatment already covers it) and off multiChipRow()'s
   other caller, the item-level Season picker on the Add Item screen.
   minmax(0, 1fr), not bare 1fr — a bare 1fr track still floors at its own
   content's width, which silently broke this exact rule the first time
   (§7uu: computed grid-template-columns came back as 4 different pixel
   values matching each button's own text, not 4 equal ones, caught via
   getBoundingClientRect() before trusting it). Full-width rows have far
   more room than the old paired ~328px columns did, but minmax(0, 1fr)
   stays regardless — the equal-width guarantee shouldn't depend on
   container width being "wide enough" to mask the underlying floor. */
.outfit-detail-field .chip-row { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; }
/* Tighter chip padding + a mid-word wrap backstop (§7uu) — needed at the
   old ~76px paired column width to stop "Standard"/"polished"/"elevated"/
   "summer" overflowing their own button. Left in place at the new
   full-width columns (§7vv) as a harmless safety net — doesn't reproduce
   the overflow, so it's inert unless a future longer chip label needs it. */
.outfit-detail-field .chip,
.occasion-group .chip { padding-left: 10px; padding-right: 10px; overflow-wrap: break-word; }
/* Every occasion chip in a category is the same width, regardless of label
   length (§7ss, 2026-08-02, owner request) — a 3-column grid rather than
   flex-wrap, so "Working from Home" and "Office" no longer size to their
   own text while "Leadership Meeting" wraps alone onto a lonely full-width
   line. 3 columns because the largest category (Work/Home/Social) has 3
   items; a 2-item category (Active) just leaves its third cell empty
   rather than stretching to fill it — still every chip the same width as
   every other chip on the page, which was the actual ask. minmax(0, 1fr)
   rather than bare 1fr (§7uu, 2026-08-02) — this happened to render
   correctly with bare 1fr at the time, since Occasions' own container is
   wide enough that no chip's natural text width exceeds its fair share,
   but that's incidental to the current width/content, not guaranteed —
   .outfit-details-grid's own chip-row hit exactly this failure mode in a
   narrower container. minmax(0, 1fr) makes both robust the same way. */
.occasion-group .chip-row { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }

.form-rule { height: 1px; background: var(--line); border: 0; margin: 30px 0 18px; }

.chip { font-family: var(--sans); font-weight: 500; font-size: 15px; line-height: 20px;
  padding: 11px 20px; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--panel); color: var(--ink); min-height: 44px;
  transition: background .14s ease-out, color .14s ease-out, border-color .14s ease-out; }
/* Accent fill + checkmark, not an ink-surface invert (owner report,
   2026-08-02: "on dark mode, desktop it is very hard to tell what
   elements are selected"). The old ink-surface treatment relied on a
   stark light-panel/dark-surface flip that only exists in light mode —
   in dark mode --panel and --ink-surface are both near-black (#241f21 vs
   #2b2427, a ~7-unit RGB difference), so selected and unselected chips
   were nearly indistinguishable by color alone, with only a barely-
   visible border as the sole differentiator (confirmed live via
   getComputedStyle() on a real selected/unselected pair in dark mode, not
   assumed from the CSS source). --accent is a genuinely different hue
   from either surface color in both modes (#a4677a light / #c07e90 dark),
   so it reads clearly regardless of theme — same fix this codebase
   already proved out for .effort-chip/.practical-chip (now redundant with
   this rule, see below). The ✓ suffix adds a second, color-independent
   cue on top. */
.chip[aria-pressed="true"] { background: var(--accent); color: #fff; border-color: var(--accent);
  font-weight: 600; }
.chip[aria-pressed="true"]::after { content: ' ✓'; }
.chip-row { display: flex; flex-wrap: wrap; gap: 8px; }
.chip-label { margin: 0 0 12px; }

/* in-page filter chip (cohesion pass, §7) — a quiet outline, never a fill.
   Distinct from .chip's own accent+✓ fill (above) — filter-chip's selected
   state is a bold --ink border/text instead, which still reads clearly in
   dark mode since --ink correctly inverts to near-white there (unlike the
   old ink-surface-vs-panel confusion .chip itself had, see above). */
.filter-chip { font-family: var(--sans); font-weight: 500; font-size: 14px;
  line-height: 20px; padding: 9px 18px; min-height: 44px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--panel); color: var(--muted);
  transition: color .14s ease-out, border-color .14s ease-out; }
.filter-chip[aria-pressed="true"] { font-weight: 600; color: var(--ink);
  border-color: var(--ink); }
.filter-row { display: flex; gap: var(--space-xs); align-items: center;
  margin-top: var(--space-md); flex-wrap: wrap; }

/* practical-chip/effort-chip's own selected-state rule is gone (2026-08-02)
   — it's identical to the base .chip[aria-pressed="true"] rule above now
   that that rule uses the same accent+✓ treatment these two originated,
   so it was pure duplication. font-size is the only thing left that's
   actually specific to practical-chip. */
.chip.practical-chip { font-size: 14px; }

.form-actions { margin-top: auto; padding-top: 40px; display: flex; align-items: center; gap: 18px; }
.btn-primary { background: var(--ink-surface); color: var(--on-ink); font-family: var(--serif);
  font-size: 22px; min-height: 58px; padding: 0 38px; border-radius: var(--radius);
  transition: filter .14s ease-out; }
.btn-primary:hover { filter: brightness(1.15); }
.hint { color: var(--quiet); font-size: 14px; }

/* ============================================================
   Screen 2 — The day  (desktop)
   ============================================================ */
/* Detail on the left, hero photo on the right (owner direction) — the content
   column is the flexible one, the photo keeps its fixed 520px. */
.screen-day { display: grid; grid-template-columns: 1fr 520px; min-height: 100vh; }

.day-photo { position: relative; background: var(--photo); overflow: hidden; }
/* Phase 9 (Backend Design.md §16) — the generate-mode side of the Day
   screen's photo column has no outfit yet to show a photo of, so it's just
   a quiet placeholder rather than reusing outfitThumb()'s own chain. */
.day-photo-generate { display: flex; align-items: center; justify-content: center; }
.day-generate-placeholder { font-size: 64px; opacity: .35; }
.day-photo > img { width: 100%; height: 100%; object-fit: cover; }
.day-photo .ph-label { font-size: 11px; }
/* the outfit's thumbnail fills the column — outfitThumb() decides whether that
   is a portrait, a 2×2 member composite, or a placeholder (round 2, §6.4);
   replaces the old .day-photo-collage vertical stack, which was this screen's
   own one-off version of the same idea. */
.day-photo > .ph { width: 100%; height: 100%; border-radius: 0; }
/* cover here, against the app-wide contain default. This hero is a full-bleed
   column whose height comes from the content beside it, so it is far narrower
   per unit height (~0.43) than any outfit photo (0.47–0.50). contain therefore
   fitted them to width and left bands of --photo above and below — visible
   padding, worst on the widest photos. cover fills the column instead, and
   because the frame is narrower than every photo it crops width, never height:
   the model's head and feet survive, only side backdrop is trimmed. */
.day-photo > .ph > img { object-fit: cover; }
/* §15.14/§15.15 (owner: the render button was overlapping the alt-thumbnail
   strip on narrower screens; then the original below-the-keep-bar
   placement "didn't work") — Show another and the AI-render action both
   moved out of .day-photo into .day-actions-row, called from inside
   .day-body directly above scoreAuditPanel(r) (in .day-content, not
   here), so the photo pane now only ever positions .day-alts. Both
   buttons keep the same pill visual as before, just as normal inline
   elements instead of position: absolute. */
.day-reroll { font-family: var(--sans); font-weight: 500;
  font-size: 12px; letter-spacing: .14em; text-transform: uppercase; color: var(--ink);
  background: var(--panel); border: 1px solid var(--line);
  padding: 8px 12px; border-radius: var(--radius); cursor: pointer; }
.day-render-action { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
.day-render-btn { font-family: var(--sans); font-weight: 500; font-size: 12px;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink);
  background: var(--panel); border: 1px solid var(--line);
  padding: 8px 12px; border-radius: var(--radius); cursor: pointer; }
.day-render-btn:disabled { opacity: .6; cursor: default; }
.day-render-error { font-size: 11px; color: var(--accent-note); margin: 0; }
/* Lives inside .day-body, which already carries the column's own 40px side
   padding — no horizontal padding of its own, or it'd double up. Render
   action on the left, Show another on the right; the spacer keeps Show
   another pinned right even when no render action is offered (a curated
   look with its own real photo). */
.day-actions-row { display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; margin-top: 22px; }
.day-actions-spacer { flex: 1; }
.day-alts { position: absolute; left: 20px; bottom: 20px; display: flex; gap: 8px; }
.alt-thumb { width: 58px; height: 77px; background: var(--photo-3); border-radius: var(--radius);
  overflow: hidden; padding: 0; position: relative; }
.alt-thumb.current { outline: 2px solid var(--ink); outline-offset: -2px; }
.alt-thumb > img { width: 100%; height: 100%; object-fit: contain; }
.alt-thumb > .ph { width: 100%; height: 100%; border-radius: 0; }

.day-content { display: flex; flex-direction: column; min-height: 100vh; min-width: 0; padding: 0; }
.meta-bar { display: flex; justify-content: space-between; align-items: baseline;
  padding: 26px 40px 0; font-family: var(--sans); font-weight: 500; font-size: 12px; color: var(--quiet); }
.meta-bar a, .meta-bar button { color: var(--quiet); font-size: 12px; letter-spacing: .04em; }
.day-body { padding: 18px 40px 0; }
.day-body .kicker { margin-bottom: 12px; }
/* Phase 9 UX fix (owner: "more space between the tabs and the title text")
   — dayModeToggle() sits directly above the kicker on both Day-screen
   modes; the tab row's own bottom border/padding read as touching the
   title beneath it with no real gap. */
.libtab-row + .kicker { margin-top: var(--space-lg); }
.look-name { font-family: var(--serif); font-size: 40px; line-height: 1.05; color: var(--ink); margin: 0; }
.formula { font-size: 16px; line-height: 1.45; color: var(--muted); max-width: 520px; margin: 10px 0 0; }

.composition { list-style: none; margin: 22px 0 0; padding: 0; }
.composition li { display: flex; justify-content: space-between; gap: 20px; padding: 9px 0;
  border-top: 1px solid var(--line); }
.composition li:last-child { border-bottom: 1px solid var(--line); }
.composition .slot { flex: 0 0 100px; font-family: var(--sans); font-weight: 600; font-size: 10px;
  letter-spacing: .12em; text-transform: uppercase; color: var(--accent-ink); padding-top: 3px; }
.composition .val { text-align: right; font-size: 15px; color: var(--muted); }

.note-row { display: flex; align-items: stretch; gap: 18px; margin: 20px 0 0; }
.note-row .note { flex: 1; }
.nail-pill { flex: none; display: flex; align-items: center; gap: 10px; background: var(--panel);
  border: 1px solid var(--line); border-radius: 99px; padding: 8px 14px 8px 10px; }
.swatch { display: inline-block; width: 26px; height: 26px; border-radius: 50%;
  box-shadow: inset 0 0 0 1px var(--swatch-ring); flex: none; }
.nail-pill .nail-text .kicker { margin: 0 0 2px; }
.nail-pill .nail-name { font-family: var(--serif); font-size: 15px; line-height: 1.1; }

/* Audit/testing panel (§7yy) — the scoreOutfit() math for the outfit on
   screen. Deliberately quiet/collapsed by default: it's for auditing, not
   part of the everyday reading flow of the page. */
.score-audit { margin: 18px 0 0; border: 1px solid var(--line); border-radius: var(--radius);
  padding: 2px 14px; }
.score-audit summary { cursor: pointer; padding: 10px 0; font-family: var(--sans);
  font-weight: 500; font-size: 12px; letter-spacing: .02em; color: var(--quiet); }
.score-audit[open] summary { border-bottom: 1px solid var(--line); }
.score-audit-list { list-style: none; margin: 0; padding: 4px 0 10px; }
.score-audit-list li { display: flex; align-items: baseline; gap: 10px; padding: 5px 0; }
.score-audit-label { flex: 0 0 84px; font-family: var(--sans); font-weight: 600; font-size: 10px;
  letter-spacing: .1em; text-transform: uppercase; color: var(--accent-ink); }
.score-audit-detail { flex: 1; font-size: 13px; color: var(--muted-2); }
.score-audit-points { flex: none; font-variant-numeric: tabular-nums; font-size: 13px;
  font-weight: 600; color: var(--muted); min-width: 42px; text-align: right; }

.routine-band { margin-top: auto; display: grid; grid-template-columns: 1fr 1fr;
  border-top: 1px solid var(--line); }
.routine-cell { padding: 16px 18px; display: flex; flex-direction: column; gap: 10px; }
.routine-cell + .routine-cell { border-left: 1px solid var(--line); }
.routine-cell .ph { aspect-ratio: 4 / 5; border-radius: var(--radius); background: var(--photo-2); }
.routine-cell .r-title { font-family: var(--serif); font-size: 20px; line-height: 1.15; margin: 0; }
.routine-preview { margin: 0; display: flex; flex-direction: column; gap: 5px; }
.routine-preview .step { font-size: 12px; line-height: 1.45; color: var(--muted-2); }
.routine-preview .step .num { font-family: var(--serif); font-size: 12px; color: var(--accent-ink);
  margin-right: 7px; }
.routine-more { font-family: var(--sans); font-weight: 500; font-size: 12px; color: var(--accent-ink);
  text-align: left; letter-spacing: .02em; }

.keep-bar { background: var(--keep-bar); color: var(--on-ink); padding: 18px 40px;
  display: flex; align-items: center; gap: 16px; }
.btn-keep { background: var(--on-ink); color: var(--ink-surface); font-family: var(--serif); font-size: 19px;
  min-height: 48px; padding: 0 24px; border-radius: var(--radius); }
.keep-bar .keep-sub { color: var(--on-ink-muted); font-size: 14px; }
.keep-bar.saved .btn-keep { display: none; }

/* ============================================================
   Screen 3 — Routine pages (makeup / hair)
   ============================================================ */
.screen-routine-wrap { display: flex; flex-direction: column; min-height: 100vh; position: relative; }
.screen-routine-wrap .meta-bar { padding: 26px 44px 0; }
/* 600px is the default for every routine reference photo now (cohesion
   pass §7, finding 5) — was 400px, with a Brows-only 600px override; that
   override is retired since 600 is now the shared column for Makeup, Hair,
   Brows, Nails, and self-built outfits alike. Reference photos are square
   and already use object-fit:contain below, so the wider column shows more
   image, not more crop. */
/* One frame, defined once, shared by the four guides, Result, and Wardrobe's
   Outfits tab (round 2, §6.6) — the 600px column is no longer restated per
   screen. .screen-routine keeps only what's specific to it. */
/* Detail on the left, image/selection column on the right (owner direction) — the
   text column is the flexible one, the image column keeps its fixed 600px. Both
   emit the detail first, so DOM order matches reading order. */
.screen-split, .screen-routine { display: grid; grid-template-columns: 1fr 600px; align-items: start; }
.screen-routine { flex: 1; }
.routine-photos { display: flex; flex-direction: column; gap: 24px; padding: 18px 44px 44px; position: sticky; top: 0; }
.routine-photos .ph { width: 100%; aspect-ratio: 1 / 1; border-radius: var(--radius); flex: none; }
.routine-photos .ph.ref { background: var(--photo-2); }               /* routine reference photo */
/* contain (not cover) so a reference photo is never cropped */
.routine-photos .ph.ref img { object-fit: contain; object-position: center; }

/* A reference photo whose source isn't square — the brow and technique shots come
   in at 1.333, 1.5 and 2.0. The declared 3:2 is only the placeholder's shape;
   setPhoto() replaces it with the image's real ratio once it loads, so the frame
   ends up exactly the shape of its photo and there are no bars in any direction.
   Wins over the square frame above wherever both apply. */
.routine-photos .ph.ref-fit, .howto-card .ph.ref-fit { aspect-ratio: 3 / 2; }

.routine-shop .kicker { margin: 0 0 10px; }
.shop-list { list-style: none; margin: 0; padding: 0; }
.shop-list li { border-top: 1px solid var(--line); padding: 9px 0; font-size: 13px; line-height: 1.4; }
.shop-list li:last-child { border-bottom: 1px solid var(--line); }
.shop-link { color: var(--ink-soft); text-decoration: underline; text-underline-offset: 2px; text-decoration-color: var(--line); }
.shop-link:hover { color: var(--accent-ink); text-decoration-color: var(--accent-ink); }
.shop-plain { color: var(--muted); }
.shop-optional .shop-link, .shop-optional .shop-plain { color: var(--quiet); }

.rp-content { display: flex; flex-direction: column; min-height: 0; min-width: 0; }
.rp-body { padding: 18px 44px 0; flex: 1; }
.rp-body .look-name { margin: 4px 0 0; }
.rp-intro { font-size: 16px; line-height: 1.5; color: var(--muted); max-width: 520px; margin: 12px 0 0; }

.step-list { list-style: none; margin: 26px 0 0; padding: 0; }
.step-list li { display: flex; gap: 22px; padding: 17px 2px; border-top: 1px solid var(--line); }
.step-list li:last-child { border-bottom: 1px solid var(--line); }
.step-num { flex: 0 0 28px; font-family: var(--serif); font-size: 20px; line-height: 1.3; color: var(--accent-ink); }
.step-body { font-size: 16px; line-height: 1.65; color: var(--ink-soft); }
.step-body b { font-weight: 600; }
.tech-note { margin: 22px 0 0; }

.handoff { display: grid; grid-template-columns: 1fr 1fr; border-top: 1px solid var(--line); margin-top: 26px; }
.handoff-cell { padding: 16px 44px 16px 0; }
.handoff-cell + .handoff-cell { padding-left: 24px; border-left: 1px solid var(--line); }
.handoff-cell .kicker { margin: 0 0 6px; }
.handoff-link { font-family: var(--serif); font-size: 18px; text-align: left; color: var(--ink); }
.handoff-nails { display: flex; align-items: center; gap: 10px; }
.handoff-nails .nail-name { font-family: var(--serif); font-size: 15px; }

/* ============================================================
   Screen 3b — Makeup library (every look, one scrollable page)
   ============================================================ */
/* No max-width — card grids (Wardrobe, Makeup/Hair/Brows/Nails libraries) reflow
   via auto-fill and gain columns on a wide viewport rather than plateauing; prose
   elements inside already carry their own local max-width (e.g. .library-sub),
   so nothing here goes unreadably wide on its own. */
.screen-library { width: 100%; }
/* The head spans the full width on every tab, split or not, so the title and
   tabs sit at the same left edge throughout and don't jump sideways when you
   move between Routine and Foundation. Same meta-bar inset in both paths. */
.screen-library .meta-bar { padding: 26px 44px 0; }
.library-head { padding: 30px 44px 6px; }
.library-title { font-family: var(--serif); font-size: 44px; line-height: 1.05; color: var(--ink); margin: 6px 0 0; }
/* Nails' routine intro is the only remaining library intro line — the Routine
   and Outfits tabs' versions were instruction rather than information, and the
   cards already carry their own action label. */
.library-sub { font-size: 15px; line-height: 1.5; color: var(--muted); max-width: 560px; margin: 12px 0 0; }
/* The §6.2 .look-link row is retired: Makeup's and Hair's Routine tabs now use
   the card grid + hero pane, so there is no long page left to jump down. A look
   reference is a card, which satisfies the same rule (never inline body copy)
   more strongly. */

.hub-yours-grid { display: grid; grid-template-columns: 1fr 1fr;
  gap: var(--space-md); margin-top: var(--space-md); }
.hub-row { display: grid; grid-template-columns: 64px 1fr auto; gap: var(--space-sm);
  align-items: center; min-height: 96px; background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: var(--space-sm) var(--space-md);
  text-align: left; font-family: var(--sans); color: inherit; cursor: pointer; }
.hub-row .ph { width: 64px; height: 64px; border-radius: 3px; flex: none; }
.hub-row-text { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.hub-row-name { font-family: var(--serif); font-size: 24px; line-height: 1.1; }
.hub-row-desc { font-size: 13px; line-height: 1.5; color: var(--muted); }
.hub-row-action { font-weight: 600; font-size: 10px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--accent-note); }

/* Brows' Routine tab is the only .library-look on screen at once (unlike
   the stacked-list pages this was originally built for), so its own
   border-top just sat directly under the tab row's — same stray-double-
   line pattern fixed elsewhere. Its gap to the tab row also didn't match
   the 36px every other guide tab now uses (library-head's 6px bottom
   padding + .rp-body's own 18px top padding + .look-name/.guide-h2's own
   margin = some other number entirely) — swapped .rp-body's top padding
   for guidelines-wrap's own 30px and zeroed the first child's margin, so
   6 + 30 = the same 36px everywhere. Scoped to .library-look only —
   .rp-body is shared with the unrelated Day result screen
   (renderRoutinePage()), which keeps its own spacing untouched. */
.library-look .rp-body { padding-top: 30px; padding-bottom: 40px; }
.library-look .rp-body > :first-child { margin-top: 0; }
.library-look .routine-photos { padding-bottom: 40px; }
/* page-nav underline tabs (cohesion pass, §7) — replaces .chip-row/.chip in
   libraryTabBar(). A tab row is never a pill row: underline only, no fill,
   so it can't be confused with a filter or a selection. */
.libtab-row { display: flex; gap: 28px; border-bottom: 1px solid var(--line);
  margin-top: var(--space-lg); flex-wrap: wrap; }
.libtab { font-family: var(--sans); font-weight: 500; font-size: 15px;
  padding: 0 0 12px; color: var(--quiet); background: none; border: 0;
  cursor: pointer; }
.libtab[aria-selected="true"] { font-weight: 600; color: var(--ink);
  box-shadow: inset 0 -2px 0 0 var(--ink); }
.howto-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 28px; padding: 30px 44px 44px; }
/* One row of photos at a shared height, each as wide as its own ratio implies —
   so a 2:1 before/after sits as tall as the 3:2 shots beside it rather than half
   their height. flex-grow comes from the image ratio, stamped by setPhoto(); the
   even split below is just what holds until the images load. No horizontal
   padding: this row sits inside .guidelines-wrap, which already insets it, and
   more would push the photos in past the heading above them. */
.photo-row { display: flex; align-items: flex-start; gap: 28px; margin: var(--space-md) 0 var(--space-lg); }
.photo-row > * { flex: 1 1 0; min-width: 0; }
.howto-card { display: flex; flex-direction: column; height: 100%; }
.howto-card .ph { aspect-ratio: 1 / 1; border-radius: var(--radius); background: var(--photo-2);
  margin-bottom: 12px; flex: none; }
/* Removed a `margin-top: auto` rule here (§7ddd, 2026-08-02 — owner-caught
   misalignment on Hair's color-option cards). It was written to bottom-align
   .photo-row captions against each other despite .ref-fit photos of
   different heights — but .photo-row uses `align-items: flex-start`
   (confirmed live via getComputedStyle), which never lets its cards stretch
   to a shared height in the first place, so the rule had zero effect there
   (computed margin-top: 0px regardless of caption length). The one place it
   DID do something was .howto-grid, a CSS Grid whose items stretch to their
   row's height by default with no override — there, a card's title (the
   first sibling after .ph) got pushed down by however much text sat below
   it, so titles landed at different heights depending on each card's own
   script/note length instead of sitting at a fixed spot right under the
   photo. Dropping the rule fixes the actual bug and changes nothing in
   .photo-row, where it was already inert. */
.howto-card h3 { font-family: var(--serif); font-size: 18px; margin: 0 0 6px; color: var(--ink); }
.howto-card p { font-size: 13px; line-height: 1.5; color: var(--muted); margin: 0; }
/* A color option's owner call-out pill(s) (Recommended, Low Maintenance) —
   same .tag-pill/.tag-pill-accent treatment as Items/Outfits, on their own
   line under the title. Always rendered (even empty) and height-reserved so
   every card's title sits at the same spot and every script paragraph
   starts at the same spot, whether or not that option has a pill. */
.howto-card-pills { display: flex; flex-wrap: wrap; gap: var(--space-2xs); min-height: 20px; margin-bottom: 6px; }
.howto-card-pills .tag-pill { margin-top: 0; }
.guidelines-wrap { padding: 30px 44px 44px; }
/* The gap under the tab bar should be identical everywhere, but it was
   really (library-head's own bottom padding) + (whatever margin-top the
   first element inside happened to carry) — 36px if a grid/list came
   first, 48px for an intro paragraph, 68px for an h2 title. Zeroing the
   first child's own margin makes the container's padding the only thing
   that sets the gap, on every guide's every tab. */
.guidelines-wrap > :first-child, .products-wrap > :first-child { margin-top: 0; }
.principle-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 24px; margin: 0 0 44px; }
.principle-card { border-top: 1px solid var(--line); padding-top: 14px; }
.principle-n { font-family: var(--sans); font-size: 11px; letter-spacing: .08em; color: var(--quiet); }
.principle-card h3 { font-family: var(--serif); font-size: 19px; margin: 6px 0 8px; color: var(--ink); }
.principle-card p { font-size: 14px; line-height: 1.5; color: var(--muted); margin: 0; }
/* guide heading hierarchy (cohesion pass, §7) — two levels replace one class
   used at 17 different weights. h2 = a full named option (a cut, a colour);
   h3 = a minor sub-label (Avoid, Schedule, Colour options) and reuses the
   existing .kicker treatment rather than inventing a new size. */
.guide-h2 { font-family: var(--serif); font-weight: 400; font-size: 26px;
  line-height: 1.15; color: var(--ink); margin: var(--space-lg) 0 0; }
.guide-h3 { font-family: var(--sans); font-weight: 600; font-size: 10px;
  letter-spacing: .24em; text-transform: uppercase; color: var(--accent-ink);
  margin: var(--space-md) 0 0; }
/* Nails' Weekly Routine is the one place a .guide-h2 is directly followed
   by a .step-list (elsewhere a paragraph sits between them) — step-list's
   own 26px top margin then reads as a stray extra gap the way timeline-list
   (which has none) doesn't. Zeroed only for this direct-adjacency case. */
.guide-h2 + .step-list { margin-top: 0; }
/* Brows Maintenance's two-col-grid is .guidelines-wrap's first child (the
   only Maintenance tab structured this way), so the app-wide first-child
   margin reset above only zeroes the grid's own margin — the "Guidance"/
   "Avoid" headings one level further in still carried their normal 32px
   top margin, landing 68px under the tab row instead of 36px like every
   other tab. Scoped to first-child-of-guidelines-wrap specifically so
   Hair Maintenance's own two-col-grid (which sits lower on the page, after
   its Schedule section, where that spacing is correct) is unaffected. */
.guidelines-wrap > .two-col-grid:first-child > * > :first-child { margin-top: 0; }
/* §6.1's fourth level (.guide-h2-alt / .guide-alt-group) is retired: per owner
   direction the alternate hair cuts read as peers of the established one, at the
   same .guide-h2 weight, grouped under their own "Other options" kicker instead
   of being demoted under an "Also works" caption. */
/* a second kicker-led section further down the same tab, which needs the
   separation the first one gets from the wrap's own top padding */
.guide-section-kicker { margin-top: var(--space-xl); }
.table-wrap { overflow-x: auto; }
.guide-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.guide-table th, .guide-table td { text-align: left; padding: 10px 20px 10px 0;
  border-bottom: 1px solid var(--line); white-space: nowrap; }
.guide-table th { font-family: var(--sans); font-weight: 600; font-size: 11px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--quiet); }
.guide-table td:first-child, .guide-table th:first-child { font-weight: 600; color: var(--ink); }
.products-wrap { padding: 30px 44px 44px; }
.product-tier { margin-bottom: 36px; }
/* Section titles across every guide's Products tab promoted to the same
   .guide-h2 style the top-level tab titles use elsewhere (Hairstyles,
   Looks, Schedule, …) — was its own smaller one-off size (20px h3).
   Margin overridden back to product-tier's original 0-top/6px-bottom
   (guide-h2's own 32px top margin is for spacing a title from whatever
   precedes it elsewhere; here that job already belongs to product-tier's
   own margin-bottom, spacing one tier from the next). */
.product-tier .guide-h2 { margin: 0 0 6px; }
.tier-note { font-size: 13px; line-height: 1.5; color: var(--muted); margin: 0 0 6px; max-width: 640px; }
.tiered-shop-list .shop-category { display: block; font-size: 10px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--quiet); margin-bottom: 3px; }
.tiered-shop-list .shop-link, .tiered-shop-list .shop-plain { display: block; font-weight: 600; }
.tiered-shop-list .shop-note { display: block; font-size: 12px; line-height: 1.5; color: var(--muted); margin-top: 3px; }
.tiered-shop-list .shop-alt { display: block; font-size: 12px; color: var(--quiet); margin-top: 3px; }
.check-list { list-style: none; margin: 10px 0 0; padding: 0; }
.check-list li { padding: 9px 0; border-top: 1px solid var(--line); font-size: 14px; color: var(--muted); }
.check-list li:last-child { border-bottom: 1px solid var(--line); }
/* Hair Guide: Maintenance timeline + Salon script */
.timeline-list { margin: 0 0 40px; }
.timeline-row { display: flex; gap: 24px; padding: 12px 0; border-top: 1px solid var(--line); }
.timeline-row:last-child { border-bottom: 1px solid var(--line); }
.timeline-cadence { flex: 0 0 130px; font-family: var(--sans); font-weight: 600; font-size: 12px;
  letter-spacing: .04em; color: var(--ink); }
.timeline-text { font-size: 14px; line-height: 1.5; color: var(--muted); }
.salon-quote { font-family: var(--serif); font-style: italic; font-size: 18px; line-height: 1.55;
  color: var(--ink); max-width: 720px; margin: 0; padding-left: 20px; border-left: 2px solid var(--accent); }
.two-col-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 8px 48px; }

/* Compare Cuts (§7hhh, 2026-08-14) content. Its own sub-nav (a nested chip
   row one level below the Hair Guide's tab row, .compare-subnav) is gone as
   of §7www — both Compare Cuts and Makeup's Compare Looks (§7ooo before it)
   dropped their sub-nav for one continuous page, so nothing references that
   class anymore; removed rather than left dead. */
.compare-intro { color: var(--muted); font-size: 15px; line-height: 1.55; max-width: 640px; margin: 0 0 4px; }
.compare-overview-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: 24px; margin-top: 20px; }
.compare-choice { display: flex; gap: 18px; align-items: flex-start; text-align: left; width: 100%;
  background: none; border: 0; border-top: 1px solid var(--line); padding: 20px 0 0; cursor: pointer; font: inherit; color: inherit; }
.compare-choice .ph { width: 130px; flex: none; }
/* §7xxx — Hair's role cards now toggle the pane (Makeup's track cards still
   just scroll, so this only lights up when aria-pressed is actually set). */
.compare-choice[aria-pressed="true"] .ph { outline: 2px solid var(--ink); outline-offset: 2px; }
.compare-choice-text { min-width: 0; }
.compare-rank { font-family: var(--sans); font-weight: 700; font-size: 11px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--accent-ink); margin: 0; }
.compare-choice-name { font-family: var(--serif); font-weight: 400; font-size: 21px; line-height: 1.2; margin: 3px 0 0; color: var(--ink); }
.compare-choice-color { font-weight: 600; font-size: 14px; color: var(--ink); margin: 4px 0 0; }
.compare-choice-summary { font-size: 13px; line-height: 1.5; color: var(--muted); margin: 6px 0 0; }
.compare-recommendation { margin-top: 28px; padding: 14px 18px; border-left: 3px solid var(--accent);
  background: var(--panel); font-size: 14px; line-height: 1.55; }
/* §7bbbb — Hair's own Recommendation callout is a scannable label+text list
   now instead of one dense paragraph (Makeup's own recommendation above
   stays a plain string in the same .compare-recommendation shell; these two
   rules only ever match when that list markup is actually present). */
.compare-recommendation-label { font-family: var(--sans); font-weight: 700; font-size: 11px;
  letter-spacing: .08em; text-transform: uppercase; color: var(--accent-ink); margin: 0 0 8px; }
.compare-recommendation-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.compare-recommendation-list li { margin: 0; }
.compare-recommendation-list strong { color: var(--ink); }
.compare-detail-head { display: flex; justify-content: space-between; align-items: flex-end; gap: 20px; margin-bottom: 4px; }
.compare-maintenance { color: var(--muted); font-size: 13px; text-align: right; margin: 0; }
.compare-notes-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 14px 28px;
  margin-top: 26px; padding-top: 18px; border-top: 1px solid var(--line); }
.compare-notes-grid p { font-size: 13px; line-height: 1.55; color: var(--muted); margin: 0; }
.compare-notes-grid strong { display: block; color: var(--ink); font-size: 11px; letter-spacing: .06em; text-transform: uppercase; margin-bottom: 3px; }
.stylist-script { margin-top: 28px; padding: 20px; border: 1px solid var(--line); border-radius: var(--radius); }
.stylist-script h3 { font-family: var(--serif); font-weight: 400; font-size: 20px; margin: 0 0 4px; color: var(--ink); }
.script-intro { color: var(--muted); font-size: 13px; margin: 0 0 16px; }
.script-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px; }
.script-block h4 { margin: 0 0 6px; color: var(--accent-ink); font-size: 10px; letter-spacing: .1em; text-transform: uppercase; font-weight: 600; }
.script-block p { font-size: 14px; line-height: 1.55; color: var(--ink); margin: 0; }
.compare-quality-note { margin-top: 28px; font-size: 12px; line-height: 1.5; color: var(--quiet); max-width: 720px; }

@media (max-width: 900px) {
  .compare-choice { flex-direction: column; }
  .compare-choice .ph { width: 100%; max-width: 220px; }
  .compare-detail-head { display: block; }
  .compare-maintenance { text-align: left; margin-top: 4px; }
}
/* Every outfit carries a thumbnail on every surface (cohesion pass round 2,
   §6.4). A self-built outfit has no portrait of its own, so its identity is a
   2×2 composite of its member items: the 1px gap over a --line background
   paints the hairlines between cells, and an empty slot fills with --panel so
   the grid keeps its shape instead of collapsing to an uneven one. */
.outfit-thumb { display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr;
  gap: 1px; background: var(--line); }
.outfit-thumb-cell { background: var(--panel); overflow: hidden; }
.outfit-thumb-cell img { width: 100%; height: 100%; object-fit: contain; display: block; }

.outfit-row-star { font-size: 15px; color: var(--accent); }

/* The selected item lives in a sticky pane (round 2, §6.6) — it used to render
   after the library, so choosing one meant scrolling past everything you didn't
   choose. Only the other column scrolls; the pane is always present, so selecting
   never reflows the grid. The pane sits on the right now, so its divider is on
   its leading (left) edge. */
.screen-split__pane { position: sticky; top: 0; align-self: start;
  min-height: 100vh; max-height: 100vh;
  overflow-y: auto; padding: var(--space-md) 36px var(--space-lg);
  background: var(--panel-sunk); border-left: 1px solid var(--line);
  display: flex; flex-direction: column; gap: var(--space-md); }
.screen-split-main { min-width: 0; padding-bottom: var(--space-xl); }

.pane-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-sm); }
.pane-clear { font-family: var(--sans); font-weight: 600; font-size: 10px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--quiet); background: none; border: 0; cursor: pointer; }
.pane-clear:hover { color: var(--ink); }
.pane-empty { font-size: 15px; line-height: 1.55; color: var(--quiet); margin: 0; }
/* The selected outfit's photo is the hero of this page: same 0.46 frame as an
   outfit card so it fills edge to edge with no bars, sized as large as will
   still leave the name and Wear today above the fold on a 900px viewport, and
   centred in the pane rather than hugging its left edge.
   flex: none and align-self both matter — the pane is a flex column, so the
   default stretch would force the thumb to full pane width and override
   width:auto (breaking the ratio and putting the bars back), and a shrinkable
   item would get squashed vertically. */
.pane-thumb { height: 70vh; max-height: 680px; aspect-ratio: 0.46; width: auto;
  max-width: 100%; align-self: center; border-radius: var(--radius); flex: none;
  background: var(--panel); }
/* A square hero is width-driven instead: at 1:1 a height cap would leave the
   pane's width half empty, so it takes the pane's width up to 460px. */
.pane-thumb-square { aspect-ratio: 1; width: 100%; max-width: 460px; height: auto;
  max-height: none; }
.pane-title { font-family: var(--serif); font-weight: 400; font-size: 32px; line-height: 1.05;
  color: var(--ink); margin: 0; }
.pane-meta { font-size: 14px; color: var(--quiet); margin: var(--space-xs) 0 0; }
/* what the look is for — the same treatment .rp-intro gave it on the old
   stacked routine card */
.pane-intro { font-size: 16px; line-height: 1.5; color: var(--muted); margin: var(--space-sm) 0 0; }
.pane-actions { display: flex; align-items: center; gap: var(--space-xs); flex-wrap: wrap; }
.pane-btn { font-family: var(--sans); font-weight: 600; font-size: 11px; letter-spacing: .14em;
  text-transform: uppercase; border-radius: var(--radius); padding: 14px 22px;
  border: 1px solid transparent; cursor: pointer; min-height: 44px; }
/* --ink/--paper rather than --ink-surface/--on-ink: both invert with the theme,
   so the primary action stays high-contrast against the sunk pane in dark mode
   (--ink-surface sits only a few points off --panel-sunk there) and the spec's
   inverted dark button falls out without a dark-mode override. */
.pane-btn-primary { background: var(--ink); color: var(--paper); }
.pane-btn-primary:hover { filter: brightness(1.15); }
.pane-btn-outline { background: var(--panel); color: var(--ink); border-color: var(--line); }
.pane-btn-outline:hover { border-color: var(--ink); }
.pane-btn-quiet { background: none; border: 0; color: var(--accent-ink); font-size: 10px; padding: 14px 4px; }
.pane-btn-quiet:hover { color: var(--accent-note); }
.pane-members { display: flex; flex-direction: column; }
.pane-members .kicker { padding-bottom: 6px; }
.pane-member { display: grid; grid-template-columns: 34px 1fr auto; gap: 14px;
  align-items: center; min-height: 56px; padding: var(--space-xs) 0; border-top: 1px solid var(--line); }
.pane-member-thumb, .pane-member > .ph { width: 34px; height: 42px; border-radius: 3px;
  object-fit: contain; background: var(--photo-2); }
.pane-member-name { font-size: 15px; line-height: 1.3; color: var(--ink-soft); }
.pane-member-slot { font-weight: 600; font-size: 10px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--accent-ink); }

/* ============================================================
   Screen 4 — Standing state
   ============================================================ */
.screen-standing { max-width: 760px; margin: 0 auto; padding: 56px 40px 120px; position: relative; min-height: 100vh; }
.screen-standing .back { color: var(--quiet); font-size: 12px; font-weight: 500; }
/* Build/Edit outfit (owner request, 2026-08-19: match the Wardrobe outfit
   pane's own layout) — .screen-split-main has no side padding of its own
   (the split screens that already use it, e.g. Wardrobe, supply their own
   per-section padding), so this supplies the same page margins
   .screen-standing used to before this screen moved off it. */
.build-outfit-main { padding: 56px 40px 100px; position: relative; }
.build-outfit-main .back { color: var(--quiet); font-size: 12px; font-weight: 500; }
.standing-title { font-family: var(--serif); font-size: 34px; line-height: 1.1; margin: 10px 0 28px; }
.settings-subhead { font-family: var(--serif); font-size: 22px; line-height: 1.2; color: var(--ink); margin: 0 0 16px; }
.current-card { display: flex; align-items: center; gap: 16px; background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 16px 18px; }
.current-card .swatch { width: 52px; height: 52px; }
.current-card .cc-name { font-family: var(--serif); font-size: 22px; line-height: 1.2; }
.current-card .cc-meta { font-size: 13px; color: var(--quiet); margin-top: 2px; }
.current-card .cc-change { margin-left: auto; }

.section-label { margin: 34px 0 14px; }
.colour-library { display: flex; flex-wrap: wrap; gap: 18px; }
.colour-item { width: 66px; display: flex; flex-direction: column; align-items: center; gap: 8px; text-align: center; padding: 0; background: none; }
.colour-dot { width: 42px; height: 42px; border-radius: 50%; box-shadow: inset 0 0 0 1px var(--swatch-ring); }
.colour-item[aria-pressed="true"] .colour-dot { box-shadow: 0 0 0 2px var(--ink), inset 0 0 0 1px var(--swatch-ring); }
.colour-caption { font-size: 11px; line-height: 1.3; color: var(--muted); }
.colour-add .colour-dot { display: flex; align-items: center; justify-content: center; box-shadow: none;
  border: 1px dashed #a1948f; font-family: var(--serif); font-size: 22px; color: var(--quiet); }
.standing-note { font-size: 15px; line-height: 1.55; color: var(--muted); max-width: 540px; margin: 26px 0 0; }
/* cancels the top margin above when this note sits right under a heading
   that already owns the gap (cohesion pass, §7). */
.standing-note.flush { margin-top: 0; }
.standing-chips { margin: 26px 0 0; }

.location-row { display: flex; gap: 8px; margin-top: 10px; max-width: 420px; }
.location-input { flex: 1; font-family: var(--sans); font-size: 15px; padding: 11px 14px;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel);
  color: var(--ink); min-height: 44px; }
.location-input:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }
.filter-select { font-family: var(--sans); font-size: 15px; padding: 11px 14px;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel);
  color: var(--ink); min-height: 44px; max-width: 280px; width: 100%; }
.filter-select:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }
.location-error { margin: 8px 0 0; font-size: 13px; color: var(--accent-note); }

.save-bar { position: absolute; left: 0; right: 0; bottom: 0; background: var(--ink-surface); color: var(--on-ink);
  display: flex; align-items: center; gap: 12px; padding: 18px 32px; }

/* ============================================================
   Screen 4b — Closet (Backend Design.md §7, Phase 3)
   ============================================================ */
/* contain: this is the confirm-before-save preview of the photo she just picked,
   so it has to show the whole frame she's actually uploading, not a square crop
   of it. Phone photos are 3:4 or 9:16, never square. */
.item-photo-preview { display: block; width: 100%; max-width: 320px; aspect-ratio: 1 / 1; object-fit: contain;
  border-radius: var(--radius); background: var(--photo-2); margin: 14px 0 0; }
/* Items and Outfits are one component (cohesion pass round 2, §6.5, option
   2a) — one card grid, one 4:5 thumbnail, one caption anatomy, so switching
   tabs changes what is listed rather than how the screen works. An outfit's
   thumbnail is the 2×2 composite; an item's is its own photo. Replaces the
   old .closet-* grid, which was square-cropped and Items-only. */
.wardrobe-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: var(--space-md); }
.wardrobe-grid-wrap { padding: var(--space-md) 44px 0; }
.wardrobe-card { display: flex; flex-direction: column; gap: var(--space-xs);
  background: none; border: 0; padding: 0; text-align: left;
  font-family: var(--sans); color: var(--ink); }
.wardrobe-card > .ph, .wardrobe-photo { width: 100%; aspect-ratio: 4 / 5; border-radius: 3px; }
/* An item photo is an arbitrary phone ratio, so its frame stays 4:5 and the
   photo is contained inside it — there is no single ratio that could fill it.
   The ground goes to --panel rather than the usual --photo taupe: these are shot
   on a near-white studio backdrop, so a near-white surround reads as continuous
   rather than letterboxed. */
.wardrobe-photo { object-fit: contain; background: var(--panel); display: block; }
.wardrobe-card > .ph { background: var(--panel); }

/* An OUTFIT card is taller, because its photo is a full-length figure. The
   catalog's 60 portraits run from 0.4734 to 0.6667 wide-over-tall, so a frame at
   0.46 — just under the narrowest, with a little headroom for future photos —
   is guaranteed to be narrower than every one of them. `cover` therefore always
   scales to fill the width and trims horizontally, never vertically: the frame
   fills edge to edge with no bars, and the model's head and feet can never be
   cut off. Side trim is nil-to-5% background for 59 of the 60; the one 4:6
   outlier loses 29% of its width, all of it studio backdrop either side of a
   centred figure. */
.wardrobe-card-outfit > .ph { aspect-ratio: 0.46; }

/* A makeup look or hairstyle is shot square (every one is 1254x1254), so its
   card and its hero both take a 1:1 frame and fill it exactly — same reasoning
   as the outfit frame above, different source ratio. */
.wardrobe-card-square > .ph { aspect-ratio: 1; background: var(--panel); }

.wardrobe-card-outfit > .ph > img,
.wardrobe-card-square > .ph > img,
.pane-thumb > img { object-fit: cover; }
.wardrobe-card-body { display: flex; flex-direction: column; gap: 2px; }
.wardrobe-card-name { font-family: var(--serif); font-size: 17px; line-height: 1.15; }
/* Small status pill under a card's name — Owned/Wishlist on an item, My
   Outfit/Suggested on an outfit. Same neutral/accent treatment either way. */
.tag-pill { align-self: flex-start; margin-top: 2px; font-weight: 600; font-size: 10px;
  letter-spacing: .08em; text-transform: uppercase; padding: 2px 8px; border-radius: 999px;
  border: 1px solid var(--line); color: var(--muted); background: var(--panel-sunk); }
.tag-pill-accent { color: var(--accent-note); border-color: var(--accent-ink); }
.wardrobe-card-meta { font-size: 12px; color: var(--quiet); }
.wardrobe-card-actions { display: flex; gap: var(--space-sm); margin-top: var(--space-2xs); }
/* an outfit card only selects — it never carries Edit, which the pane owns
   (round 2, §6.6). The outline marks which one is feeding the pane. */
.wardrobe-card-select { font-weight: 600; font-size: 10px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--accent-ink); margin-top: 2px; }
.wardrobe-card[aria-pressed="true"] .wardrobe-card-select { color: var(--ink); }
.wardrobe-card[aria-pressed="true"] > .ph { outline: 2px solid var(--ink); outline-offset: 2px; }

/* Outfit builder's per-slot picker (§7pp, 2026-08-02) — replaces the old
   always-expanded wall of one full chip-row per category (up to 89 chips
   across 9 sections). Each category is now one compact tappable summary
   row; tapping opens a search + the same wardrobe-card photo grid used
   everywhere else in the app, so an item is recognised by its photo
   instead of read out of a 17-name alphabetical list. */
.slot-picker { margin-top: 10px; }
.slot-picker:first-child { margin-top: 0; }
.slot-summary { display: flex; align-items: center; gap: 12px; width: 100%;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 10px 14px; text-align: left; font-family: var(--sans); color: var(--ink);
  transition: border-color .14s ease-out; }
.slot-summary[aria-expanded="true"] { border-color: var(--ink); }
.slot-summary-thumb { width: 40px; height: 40px; border-radius: 3px; object-fit: cover;
  background: var(--photo); flex-shrink: 0; }
.slot-summary-thumb-empty { border: 1px dashed var(--line); }
.slot-summary-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.slot-summary-label { font-family: var(--sans); font-weight: 600; font-size: 10px;
  letter-spacing: .14em; text-transform: uppercase; color: var(--quiet); }
.slot-summary-name { font-size: 15px; line-height: 1.25; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
.slot-summary-name.slot-summary-empty { color: var(--quiet); }
.slot-summary-action { flex-shrink: 0; font-weight: 600; font-size: 12px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--accent-ink); }
.slot-picker-expanded { margin-top: 8px; padding: 14px; background: var(--panel-sunk);
  border-radius: var(--radius); }
.slot-picker-search { margin-bottom: 12px; }
.slot-picker-grid.wardrobe-grid { grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: var(--space-sm); }

.save-bar .swatch { width: 20px; height: 20px; }
.save-bar .save-name { font-family: var(--serif); font-size: 18px; }
.save-bar .save-undo { margin-left: auto; color: var(--accent-ink); font-weight: 500; font-size: 12px;
  letter-spacing: .08em; text-transform: uppercase; }

/* ============================================================
   Screen 0 — Sign in (renderNoMatch and its escape-hatch rows are gone —
   see Backend Design.md §7t — but the sign-in screen still reuses this
   layout shell)
   ============================================================ */
.screen-nomatch { max-width: 640px; margin: 0 auto; padding: 90px 40px; }
.nomatch-head { font-family: var(--serif); font-size: 38px; line-height: 1.1; max-width: 440px; margin: 14px 0 0; }

/* ============================================================
   Responsive — mobile (< 900px): single column, stacked
   ============================================================ */
@media (max-width: 900px) {
  /* form */
  .screen-form { flex-direction: column; }
  /* Mobile's job is answering the day, not browsing the rail's Guide
     links — content first, rail (with its new bottom guide cards) after,
     so the sentence/effort/practical form is what greets her, not a
     scroll past six link rows first. Visual order only (`order`, not DOM
     order) so desktop's row layout — which relies on rail being the
     first child to sit on the left — is untouched. */
  .form-content { order: 1; }
  /* align-self: flex-start (added for desktop's sticky pane, which needs
     the rail to size to its own content in the row's cross axis) has to
     be reset here — .screen-form is column-direction on mobile, so that
     same property shrinks the rail to its content's width instead of
     stretching it full-width. */
  .rail { order: 2; flex: none; align-self: stretch; padding: 20px 24px;
    position: static; min-height: 0; max-height: none; overflow-y: visible; }
  .rail-worn, .rail-rule { display: none; }
  .rail-liked { flex: none; max-height: 220px; margin-top: 16px; padding-top: 16px; }
  .form-content { padding: 28px 24px 36px; }
  .sentence { font-size: 32px; }
  .token { font-size: 27px; padding: 1px 10px; }
  .form-actions { flex-direction: column; align-items: stretch; gap: 10px; margin-top: 28px; }
  .btn-primary { width: 100%; text-align: center; }
  .hint { text-align: center; }
  .chip-row .chip { flex: 1 1 auto; justify-content: center; text-align: center; }

  /* day */
  .screen-day { grid-template-columns: minmax(0, 1fr); min-height: 0; }
  /* stacked on a phone the photo still leads — seeing the outfit is the point of
     the screen. Only the two-column desktop layout puts it on the right. */
  .day-photo { height: auto; aspect-ratio: 3 / 4; order: -1; }
  .day-content { min-height: 0; }
  .meta-bar { padding: 20px 24px 0; font-size: 11px; }
  .day-body { padding: 16px 24px 0; }
  .look-name { font-size: 30px; }
  .note-row { flex-direction: column; gap: 12px; align-items: flex-start; }
  .routine-band { grid-template-columns: 1fr; margin-top: 24px; }
  .routine-cell + .routine-cell { border-left: 0; border-top: 1px solid var(--line); }
  .routine-cell .ph { aspect-ratio: 1 / 1; }
  .keep-bar { padding: 16px 24px; }
  .day-actions-row { margin-top: 16px; }

  /* routine page */
  .screen-routine-wrap .meta-bar { padding: 20px 24px 0; }
  .screen-routine, .screen-split { grid-template-columns: minmax(0, 1fr); }
  /* the pane un-sticks and stacks above the grid — same behaviour as the guide
     routine column at this breakpoint (round 2, §6.6). order:-1 keeps it first
     in the stack now that it comes second in the DOM, the same way .day-photo
     stays first on a phone. */
  .screen-split__pane { position: static; min-height: 0; max-height: none;
    border-left: 0; border-bottom: 1px solid var(--line);
    padding: var(--space-md) 24px var(--space-lg); order: -1; }
  .screen-library .meta-bar { padding: 20px 24px 0; }
  /* likewise the reference photo column leads the stack on a phone */
  .routine-photos { position: static; padding: 16px 24px 24px; gap: 18px; order: -1; }
  .routine-photos .ph { aspect-ratio: 1 / 1; }

  /* makeup library */
  .library-head { padding: 24px 24px 6px; }
  .library-title { font-size: 32px; }
  .howto-grid, .guidelines-wrap, .products-wrap { padding-left: 24px; padding-right: 24px; }
  /* the row stacks on a phone; each photo goes full width and keeps its own ratio,
     so the flex-grow stamped from the image ratio stops mattering */
  .photo-row { flex-direction: column; }
  .photo-row > * { flex: none; width: 100%; }
  .wardrobe-grid-wrap { padding-left: 24px; padding-right: 24px; }
  .timeline-row { flex-direction: column; gap: 2px; }
  .timeline-cadence { flex: none; }
  .salon-quote { font-size: 16px; padding-left: 16px; }
  .rp-content { min-height: 0; }
  .rp-body { padding: 16px 24px 0; }
  .step-list li { padding: 14px 2px; gap: 16px; }
  .step-body { font-size: 15px; }
  .handoff { grid-template-columns: 1fr; }
  .handoff-cell, .handoff-cell + .handoff-cell { padding: 14px 0; border-left: 0; }
  .handoff-cell + .handoff-cell { border-top: 1px solid var(--line); }

  /* standing / no-match */
  .screen-standing { padding: 40px 24px 110px; }
  .build-outfit-main { padding: 40px 24px 90px; }
  .standing-title { font-size: 28px; }
  /* .outfit-detail-field's chip-row is a fixed 4-column grid regardless of
     viewport (§7uu/§7vv) — fine at desktop's ~680px full-width rows, but
     mobile's own content width (~327px after page padding) is roughly the
     same as the old paired-desktop columns that first needed the overflow
     fix, so the exact same mid-word wrapping reappeared here (caught live
     at 375px, not assumed safe just because desktop was verified — mobile
     is this app's primary use case, so this isn't a hypothetical). Two
     columns instead of four gives ~155px per chip on a phone, comfortably
     enough for "Standard"/"polished"/"elevated" on one line. */
  .outfit-detail-field .chip-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .hub-yours-grid { grid-template-columns: 1fr; }
  .settings-subhead { font-size: 20px; }
  .screen-nomatch { padding: 60px 24px; }
  .nomatch-head { font-size: 30px; }
}

/* ============================================================
   Dark mode — auto (prefers-color-scheme) + manual (data-theme)
   ============================================================ */
:root[data-theme="dark"] {
  --paper: #1b1719; --panel: #241f21;
  --ink: #f4eff1; --ink-soft: #d9d1d4; --muted: #b3a9ad; --muted-2: #a79ea2; --quiet: #9a9095;
  --line: #382f33; --line-soft: #2f272b;
  --accent: #c07e90; --accent-ink: #c99caa; --accent-note: #dcb3c0;
  --panel-sunk: #241f21; --panel-scrim: rgba(36, 31, 33, .92);
  --photo: #2b2427; --photo-2: #3a3135; --photo-3: #3a3135; --photo-label: #a98b96;
  --on-ink: #f6f2ef; --on-ink-muted: #a79ea2;
  --ink-surface: #2b2427; --keep-bar: #0f0d0e; --swatch-ring: rgba(244,239,241,.25);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper: #1b1719; --panel: #241f21;
    --ink: #f4eff1; --ink-soft: #d9d1d4; --muted: #b3a9ad; --muted-2: #a79ea2; --quiet: #9a9095;
    --line: #382f33; --line-soft: #2f272b;
    --accent: #c07e90; --accent-ink: #c99caa; --accent-note: #dcb3c0;
    --panel-sunk: #241f21; --panel-scrim: rgba(36, 31, 33, .92);
  --photo: #2b2427; --photo-2: #3a3135; --photo-3: #3a3135; --photo-label: #a98b96;
    --on-ink: #f6f2ef; --on-ink-muted: #a79ea2;
    --ink-surface: #2b2427; --keep-bar: #0f0d0e; --swatch-ring: rgba(244,239,241,.25);
  }
}
/* Direct Wardrobe/Makeup/Hair/Brows/Nails/Shopping card links — the app's
   only nav now that the Looks hub is deprecated (2026-07-31) — pinned to
   the bottom of the rail (the toggle + sign-out row rides along under
   them, so the group's bottom edge is theirs). */
.rail-bottom { margin-top: auto; }
.rail-section-links { display: flex; flex-direction: column; }
.rail-section-card { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  width: 100%; padding: 13px 0; border-top: 1px solid var(--rail-rule);
  background: none; border-left: none; border-right: none; border-bottom: none;
  color: var(--on-ink); text-align: left; cursor: pointer; }
.rail-section-card:last-child { border-bottom: 1px solid var(--rail-rule); }
.rail-section-card:hover { color: var(--accent-note); }
.rail-section-card-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.rail-section-card-name { font-family: var(--serif); font-size: 16px; line-height: 1.2; }
.rail-section-card-meta { font-size: 11px; color: var(--on-ink-muted); }
.rail-section-card-arrow { flex: none; font-size: 14px; color: var(--on-ink-muted); }
.rail-section-card:hover .rail-section-card-arrow { color: var(--accent-note); }
/* Theme toggle + sign-out (the latter moved here from the deprecated Looks
   page, its only other home) share one row at the very bottom. */
.rail-bottom-row { display: flex; align-items: center; justify-content: space-between;
  margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--rail-rule); }
.theme-toggle { color: var(--rail-body); font-size: 12px; font-weight: 500; letter-spacing: .06em; text-align: left; }
.rail-signout { color: var(--rail-body); font-size: 12px; font-weight: 500; letter-spacing: .06em;
  text-align: left; background: none; border: none; cursor: pointer; }
.rail-signout:hover { color: var(--accent-note); }

/* ---- motion: quiet photo fade on (re)render ---- */
@keyframes phFade { from { opacity: 0; } to { opacity: 1; } }
.day-photo > img, .routine-photos img, .routine-cell img, .alt-thumb img { animation: phFade .18s ease-out; }

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