/* app.css — Fashion Square design system (W4.1). One hand-written stylesheet, no build step,
   no Node, no PostCSS. Every COLOUR below comes from a :root custom property — there is a test
   (tests/test_static_assets.py) asserting neither a hex colour literal nor an rgb/hsl colour
   function call appears outside that block. (Deliberately not spelling either pattern out
   literally in this sentence -- the test's own regexes would match the words themselves and
   trip as a false positive, the same trap the file-header comment on :root already avoids.)
   Space and most sizes are tokenised the same way; a few genuinely one-off values (icon pixel
   sizes, the two logo <img> heights, the name screen's min-height) are not, because no
   reusable token exists for them and inventing single-use ones would be noise, not discipline
   (fix-round-1, S6).

   Scope of THIS slice (docs/plans/W4.1-design-system-wishlist-home.md): the tab bar, the
   wishlist home (collections grid), the read-only profile, and a restyled name screen. Every
   other screen (search/idle, loading, shoe picker, results, error, link overlay) is still built
   from Tailwind utility classes (cdn.tailwindcss.com stays loaded through W4.3) and is
   deliberately untouched here — those elements live inside #app, which already carries
   Tailwind's own dark-background and white-text utility classes, so nothing in this file needs
   to (or should try to) fight that cascade: every rule below that matters for readability sets
   its OWN colour directly on the element that needs it, which always wins over an inherited
   value regardless of specificity or stylesheet order. (Deliberately not spelling out that
   Tailwind class's own hex literal here -- this file has a test asserting no hex colour
   appears outside :root, and quoting it in a comment would trip that test as a false positive.) */

:root {
  /* ---- colour (sampled from the real logo file, app/static/img/logo.png, 2026-08-12
     correction — the first pass read an auto-traced SVG whose colours had drifted) ---- */
  --bg:          #FFFFFF;  /* page */
  --surface:     #F4F3F1;  /* photo tile fill - warm neutral, the "same background" effect */
  --line:        #E5E3E0;  /* hairlines, used sparingly */
  --ink:         #2E3337;  /* primary text - from the logo. 12.77:1 on white, AA everywhere */
  --ink-muted:   #6B7176;  /* secondary text - 4.94:1 on white, AA for small text */
  --ink-faint:   #9AA0A5;  /* DECORATIVE ONLY - 2.64:1, never for text that must be read.
                               Unused by any rule in this file as of W4.1 -- kept (not deleted)
                               because it is a documented part of the owner's token set for
                               W4.2/W4.3 to draw on (fix-round-1, O3), not dead code. */
  --accent:      #EA770C;  /* from the logo. DECORATIVE/FILL ONLY on white - 2.94:1, below even
                               the 3:1 UI-component threshold. Never small text, never a
                               meaningful icon on white. */
  --accent-text: #B85A05;  /* darkened accent for SMALL text/icons ON WHITE - 4.66:1, AA */
  --ink-on-accent: #1A1D1F;  /* for text/icons sitting ON an --accent FILL, not on white -- do
                               not reuse --ink there: white-on-accent is 2.94:1 (fails outright);
                               --ink (#2E3337) on accent is 4.34:1 (fails AA for SMALL text,
                               which is everything in this file's type scale -- fix-round-1, S7
                               got this wrong by using --ink and trying to compensate with a
                               larger font size instead, which does not actually reach WCAG's
                               large-text threshold of 18.66px regular / 14pt~18.66px bold
                               either; fix-round-2, s4 corrects both). --ink-on-accent measures
                               5.76:1 -- AA for small text, no font-size workaround needed. */
  --scrim: rgba(46, 51, 55, 0.6);  /* W4.2: the item sheet's backdrop -- --ink at reduced
                               opacity (not a new hue), so a colour-function call site (the
                               sheet's own rule) still resolves to a var(--token) per this
                               file's discipline instead of an inline rgba() literal. */

  /* ---- type: ONE family (Satoshi). Clash Grotesk and Space Grotesk are dropped. ---- */
  --font: 'Satoshi', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --fs-meta:  11px;  /* uppercase, letterspaced - counts, labels, badges */
  --fs-body:  13px;  /* item titles, prices, body copy */
  --fs-title: 15px;  /* collection names */
  --fs-head:  22px;  /* screen headings - used rarely */
  --ls-wide:  0.08em;

  /* ---- space (4px base) ---- */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px;
  --sp-4: 16px; --sp-5: 24px; --sp-6: 32px; --sp-7: 48px;

  /* ---- shape: near-square. Inditex minimalism has essentially no radius. ---- */
  --radius-tile: 0;
  --radius-ctl:  2px;

  /* ---- form control sizing (fix-round-1, S6 -- these repeat across the name screen's field/
     button/warning/error and would otherwise be five and two hardcoded literals respectively) --- */
  --w-form: 280px;  /* max-width of the name screen's stacked controls */
  --h-ctl:  48px;   /* height of a full-size input/button */

  /* ---- motion ---- */
  --motion: 150ms;
  --ease: cubic-bezier(0.2, 0, 0.2, 1);

  /* ---- layers (W4.2; --z-toast added W4.3) ---- */
  --z-sheet: 50;  /* BOTH sheets: the item sheet (from the collection screen) and the save sheet
                     (from search/idle, W4.3, which replaced the Tailwind link overlay this
                     comment used to be pinned to). They are never shown together -- the item
                     sheet opens only from state-collection, the save sheet only from
                     state-idle/state-results -- and showState() hides both on navigation. */
  --z-toast: 60;  /* the save toast (W4.3 -- was a Tailwind z-[60], now this file's own). It sits
                     ABOVE both sheets on purpose: saveLinkItem's 409 branch shows the toast
                     BEFORE closing the sheet, so a toast that ranked below one would be invisible
                     in exactly the case it exists for. */
  --z-dialog: 70; /* the create/rename/delete confirm dialog (fix-round-2, S6) -- above the item
                     sheet (its own overlay, z-50) since the delete-ITEM confirm opens FROM
                     WITHIN the item sheet, and above the save toast (z-60) so a toast from an
                     unrelated, still-settling background call can never bury a dialog the user is
                     actively answering. The contract is dialog > toast > sheet. */
}
@media (prefers-reduced-motion: reduce) {
  :root { --motion: 0ms; }
}

/* ============================================================
   BASE
   ============================================================ */
body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  -webkit-font-smoothing: antialiased;
}

/* `.hidden` is defined NOWHERE else in this repo -- every existing use (including every state
   this file does not touch) works only because the Tailwind Play CDN injects it at runtime.
   W4.1 made that a real risk: .name-screen now sets its OWN `display: flex`, so if that CDN is
   blocked (a common mobile content-blocker target) an ALREADY-authenticated user would see a
   fully-styled, convincing "What's your name?" screen instead of nothing -- and typing a name
   there mints a brand-new anonymous user and permanently orphans the real one (ADR-0013:
   name-only identity has no recovery). `!important` is REQUIRED, not sloppiness: many elements
   carry BOTH `hidden` and a Tailwind display class at once (e.g. `class="... flex ... hidden"`),
   and a bare `display: none` here would lose to `.flex` on source order alone (equal
   specificity, and Tailwind's injected stylesheet is not guaranteed to load before this one),
   leaving those elements visible. With `!important` this wins for as long as `hidden` is
   present; once JS removes the class, this rule no longer matches that element at all, so the
   Tailwind display class (or this file's own `.screen`/`.name-screen` flex rules) applies
   normally again -- reasoned from the CSS cascade rules (an !important declaration only needs
   to out-rank a NON-!important one to win; removing the class removes the match entirely, not
   just its priority), NOT empirically confirmed in a real browser -- this repo has no headless
   browser in its toolchain, so nothing here could run one (fix-round-1, S2). This rule is also
   required independently of the CDN-outage case: W4.3 deletes Tailwind entirely, at which point
   this becomes the ONLY definition of `.hidden` in the app. */
.hidden {
  display: none !important;
}

/* Uppercase, letterspaced, muted — counts, labels, badges. Colour is the DEFAULT here; a
   component that needs a different colour (e.g. the active tab) overrides it explicitly on
   the element that carries the class, never by relying on inheritance from a container. */
.u-meta {
  text-transform: uppercase;
  font-size: var(--fs-meta);
  letter-spacing: var(--ls-wide);
  color: var(--ink-muted);
}

/* Shared shell for the new full-screen states (state-wishlist, state-profile). Mirrors the
   old screens' Tailwind shape (flex:1 + overflow-y:auto inside #app's flex column) so it sits
   correctly between the header and the tab bar without touching either. Sets its OWN
   background + text colour explicitly (see the file-header note) rather than depending on
   what #app happens to carry. */
.screen {
  flex: 1;
  overflow-y: auto;
  scrollbar-width: none;
  background: var(--bg);
  color: var(--ink);
  padding: var(--sp-5) var(--sp-4) var(--sp-7);
}
/* (No .screen-heading class: W4.1 has no on-screen heading text on either new screen -- the
   tab label already names them. Removed (fix-round-1, O3) rather than kept unused: it was
   never referenced by any markup and duplicated .profile-name/.name-screen h1's declarations. */

/* ============================================================
   HEADER — logo mark alone, no wordmark, no square frame (W4.1)
   ============================================================ */
.app-header {
  flex-shrink: 0;
  background: var(--bg);
  display: flex;
  align-items: center;
  padding: var(--sp-4) var(--sp-4) var(--sp-3);
}

/* The logo (app/static/img/logo.png) is flattened onto OPAQUE WHITE, not transparent — it
   must only ever sit on --bg (white), never --surface or a dark bar, or a white box appears
   around it. height is set both here and as an HTML attribute on the <img> so the header
   reserves its space before the stylesheet (or the image) has loaded, and a missing/broken
   file leaves a gap rather than a collapsed layout. width:auto — the file is 442x436, not
   square. */
.logo-mark {
  height: 28px;
  width: auto;
  display: block;
}

/* ============================================================
   TAB BAR — Search (btn-reset, relocated from the old header) / Wishlist / Profile.
   Replaces the 5-icon footer nav. The active tab is the only orange element on screen.
   ============================================================ */
.tab-bar {
  flex-shrink: 0;
  background: var(--bg);
  border-top: 1px solid var(--line);
  display: flex;
  align-items: stretch;
  justify-content: space-around;
  padding-bottom: env(safe-area-inset-bottom, 0px);
}

.tab-btn {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-1);
  padding: var(--sp-3) var(--sp-2);
  background: none;
  border: none;
  color: var(--ink-muted);
  cursor: pointer;
  transition: color var(--motion) var(--ease);
}

.tab-btn:active {
  opacity: 0.7;
}

.tab-btn svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The label's colour follows the BUTTON's own state (inherit), not .u-meta's own default —
   .u-meta alone would always win over inheritance because it is a direct rule on the span
   itself (a fixed base colour), which is exactly what would make the active-tab colour change
   invisible on the label if this override were missing. */
.tab-btn .u-meta {
  color: inherit;
}

/* --accent-text, not --accent: the active tab's icon+label are small graphics/text on white,
   where raw --accent (2.94:1) fails even the 3:1 non-text threshold (see the :root note). */
.tab-btn.is-active {
  color: var(--accent-text);
}

/* ============================================================
   WISHLIST HOME — collections grid (ADR-0014, amended 2026-08-12: two covers, not four)
   ============================================================ */
/* .grid-2 is W4.3's shared name for this two-column product grid (added to the list, not
   replacing the two screen-specific names — same additive reasoning as .state-message above). It
   serves the shoe picker and the results grid, which is what makes those screens read as the same
   system as the wishlist. The minmax(0, 1fr) rationale below applies to them identically: both
   render nowrap, ellipsised titles. */
.wishlist-grid,
.grid-2 {
  display: grid;
  /* minmax(0, 1fr), not a bare 1fr (fix-round-1, M1): a grid item's default min-width is
     `auto`, i.e. its content's min-content size -- and .collection-card__name below is
     `white-space: nowrap`, so a long name forced the track (and the whole grid) wider than the
     viewport instead of shrinking and letting text-overflow:ellipsis fire. minmax(0, 1fr)
     overrides that automatic minimum so the track CAN shrink below the name's full width. The
     two seed collection names ("Summer", "Winter") are short enough that this was invisible
     until a longer name was tried. Checked every other flex/grid container in this file for the
     same risk: none of the others (.tab-btn, .collection-card__cover, .screen, .name-screen)
     both establish a flex/grid main axis AND contain nowrap text -- this is the only site. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-5) var(--sp-4);
}

/* Tappable (W4.2 — was inert in W4.1); also the base for .new-collection-tile below, which
   reuses this class alongside its own so both get the same tap feedback for free. */
.collection-card {
  display: block;
  cursor: pointer;
}
.collection-card:active {
  opacity: 0.85;
}

.collection-card__covers {
  display: flex;
  gap: var(--sp-1);
}

/* "+ New collection" (W4.2 §4) — a grid tile matching a real collection-card's own footprint
   (dashed border instead of a photo fill is the only visual difference) so it sits naturally
   alongside real collections instead of reading as a stray button below the grid. */
.new-collection-tile__cover {
  aspect-ratio: 3 / 4;
  background: var(--surface);
  border: 1px dashed var(--line);
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-tile);
}

.new-collection-tile__cover span {
  font-size: var(--fs-head);
  color: var(--ink-muted);
  line-height: 1;
}

/* Portrait tile, filled with --surface, object-fit:contain so the photo is never cropped.
   Rendered identically whether it holds a real cover image or is left empty (0 or 1 covers) —
   the grid must stay rectangular either way. */
.collection-card__cover {
  flex: 1;
  aspect-ratio: 3 / 4;
  background: var(--surface);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border-radius: var(--radius-tile);
}

.collection-card__cover img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.collection-card__name {
  margin: var(--sp-2) 0 0;
  font-size: var(--fs-title);
  text-transform: uppercase;
  letter-spacing: var(--ls-wide);
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.collection-card__meta {
  margin: var(--sp-1) 0 0;
}

/* .state-message is W4.3's shared name for this exact treatment (quiet, centred, muted, no icon,
   no box). It is ADDED to this list rather than replacing the four screen-specific names,
   deliberately: swapping those would edit markup on the owner-accepted W4.1/W4.2 screens inside a
   story that cannot re-run their manual pass. The .collection-empty/.collection-error rule below
   still carries an identical copy; retro-fit all of them onto .state-message in W4.4, when those
   files are being touched anyway. */
.wishlist-empty,
.wishlist-error,
.state-message {
  padding: var(--sp-7) 0 0;
  text-align: center;
  color: var(--ink-muted);
  font-size: var(--fs-body);
}

/* ============================================================
   ICON BUTTON — shared by the collection-detail header (back/⋯) and the item sheet (close),
   W4.2. Square, near-zero radius (matches this file's whole shape discipline — see --radius-ctl
   in :root), not the circular Tailwind buttons the not-yet-redesigned overlays still use.
   ============================================================ */
.icon-btn {
  flex-shrink: 0;
  width: var(--h-ctl);
  height: var(--h-ctl);
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-ctl);
  background: var(--bg);
  color: var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.icon-btn:active {
  opacity: 0.7;
}
/* 20px/1.6 (not tokenised): matches .tab-btn svg's own bare icon-pixel-size precedent above —
   this file's header comment already carves out icon pixel sizes as a deliberate exception. */
.icon-btn svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* Bolder stroke for the "⋯" glyph specifically (three near-zero-length round-capped dashes,
   see index.html) — at the shared 1.6 weight the dots read as barely-visible flecks rather than
   a recognizable affordance. */
.icon-btn--dots svg {
  stroke-width: 3;
}

/* ============================================================
   COLLECTION DETAIL — open one collection (W4.2). Grid/tile treatment is DELIBERATELY
   identical to the wishlist home's covers (--surface fill, object-fit:contain, never cropped)
   so the two screens read as one system.
   ============================================================ */
.collection-detail__header {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin: 0 0 var(--sp-4);
}

.collection-detail__heading {
  flex: 1;
  min-width: 0;
}

.collection-detail__name {
  margin: 0;
  font-size: var(--fs-title);
  font-weight: 700;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.collection-detail__meta {
  margin: var(--sp-1) 0 0;
}

/* A plain, bordered stack of full-width rows — "a minimal control, not a dropdown with chrome"
   is this file's own W4.2 spec language for the SORT control below; this menu is smaller in
   scope (two destructive actions) and gets the same restraint. */
.collection-actions-menu {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--line);
  margin: 0 0 var(--sp-4);
}

.collection-actions-menu button {
  width: 100%;
  text-align: left;
  padding: var(--sp-3) var(--sp-4);
  background: var(--bg);
  border: none;
  border-bottom: 1px solid var(--line);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--fs-body);
  cursor: pointer;
}
.collection-actions-menu button:last-child {
  border-bottom: none;
}
.collection-actions-menu button:active {
  background: var(--surface);
}

/* Sort — "a minimal control, not a dropdown with chrome" (W4.2 §2): two plain toggle buttons,
   not a <select>. */
.sort-row {
  display: flex;
  gap: var(--sp-2);
  margin: 0 0 var(--sp-4);
}

.sort-btn {
  border: 1px solid var(--line);
  border-radius: var(--radius-ctl);
  background: var(--bg);
  color: var(--ink-muted);
  font-family: var(--font);
  font-size: var(--fs-meta);
  text-transform: uppercase;
  letter-spacing: var(--ls-wide);
  padding: var(--sp-2) var(--sp-3);
  cursor: pointer;
}
/* --accent-text, not --accent (see the :root note): small text on white. */
.sort-btn.is-active {
  color: var(--accent-text);
  border-color: var(--accent-text);
}

.collection-grid {
  display: grid;
  /* minmax(0, 1fr), not a bare 1fr — same reasoning as .wishlist-grid above: .item-tile__title
     is nowrap, so a long title would otherwise force the track (and the page) wider than the
     viewport instead of shrinking and letting text-overflow:ellipsis fire. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-5) var(--sp-4);
}

.item-tile {
  display: block;
  width: 100%;
  text-align: left;
  padding: 0;
  border: none;
  background: none;
  font-family: var(--font);
  cursor: pointer;
}
.item-tile:active {
  opacity: 0.85;
}

.item-tile__cover {
  aspect-ratio: 3 / 4;
  background: var(--surface);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border-radius: var(--radius-tile);
}
.item-tile__cover img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.item-tile__platform {
  margin: var(--sp-2) 0 0;
}

.item-tile__title {
  margin: var(--sp-1) 0 0;
  font-size: var(--fs-body);
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* SAME weight/size as the title, not bold (W4.2 §2 — explicitly overrides the original W4
   spec's "bold price" on the owner's direction). */
.item-tile__price {
  margin: var(--sp-1) 0 0;
  font-size: var(--fs-body);
  color: var(--ink);
}

.collection-empty,
.collection-error {
  padding: var(--sp-7) 0 0;
  text-align: center;
  color: var(--ink-muted);
  font-size: var(--fs-body);
}

/* ============================================================
   ITEM SHEET — a sheet OVER the collection, not a new screen (W4.2 §3). Slides up from the
   bottom over a scrim backdrop; closes on a backdrop tap or the close button (collection.js).
   ============================================================ */
.item-sheet {
  position: fixed;
  inset: 0;
  z-index: var(--z-sheet);
  background: var(--scrim);
  display: flex;
  align-items: flex-end;
}

.item-sheet__panel {
  position: relative;
  width: 100%;
  max-height: 85vh;
  overflow-y: auto;
  scrollbar-width: none;
  background: var(--bg);
  color: var(--ink);
  padding: var(--sp-5) var(--sp-4) var(--sp-7);
}

.item-sheet__close {
  position: absolute;
  top: var(--sp-4);
  right: var(--sp-4);
}

/* Larger, contain-on-surface treatment (identical language to a collection cover / item tile —
   W4.2 §3). max-width keeps it from sprawling edge-to-edge on a wide viewport; centred either
   way via the auto side margins. */
.item-sheet__image {
  aspect-ratio: 1 / 1;
  max-width: 240px;
  margin: 0 auto var(--sp-4);
  background: var(--surface);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.item-sheet__image img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.item-sheet__title {
  margin: var(--sp-2) 0 0;
  font-size: var(--fs-title);
  font-weight: 700;
  color: var(--ink);
}

.item-sheet__price {
  margin: var(--sp-1) 0 0;
  font-size: var(--fs-head);
  font-weight: 700;
  color: var(--ink);
}

/* The primary action — deliberately mirrors .btn-primary's accent-fill treatment (the name
   screen's own CTA) rather than reusing that class directly: .btn-primary's max-width:280px is
   specific to the name screen's stacked form controls and would leave this full-width sheet's
   CTA looking stranded on a wide panel. */
.item-sheet__open {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  width: 100%;
  height: var(--h-ctl);
  margin: var(--sp-5) 0 0;
  border: none;
  border-radius: var(--radius-ctl);
  background: var(--accent);
  color: var(--ink-on-accent);
  font-family: var(--font);
  font-size: var(--fs-meta);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--ls-wide);
  text-decoration: none;
  cursor: pointer;
}
.item-sheet__open:active {
  opacity: 0.8;
}

.item-sheet__section {
  margin: var(--sp-5) 0 0;
}

.item-sheet__chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin: var(--sp-2) 0 0;
}

/* Mirrors the existing (Tailwind) save-flow chip pattern's BEHAVIOUR — a small bordered,
   tappable control holding untrusted text (truncated+escaped at render) — reimplemented here
   with this file's own tokens rather than Tailwind utilities (W4.2 §3: "matching the existing
   save flow's chip pattern") so it stays inside the wishlist redesign's one system. Square, not
   a rounded pill (--radius-ctl is 2px) — this file's shape language throughout, not a deviation
   specific to chips. */
.chip {
  border: 1px solid var(--line);
  border-radius: var(--radius-ctl);
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--fs-body);
  padding: var(--sp-2) var(--sp-3);
  cursor: pointer;
}
.chip:active {
  background: var(--surface);
}

.item-sheet__delete {
  display: block;
  width: 100%;
  margin: var(--sp-5) 0 0;
  padding: var(--sp-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-ctl);
  background: none;
  color: var(--ink-muted);
  font-family: var(--font);
  font-size: var(--fs-body);
  cursor: pointer;
}
.item-sheet__delete:active {
  background: var(--surface);
}

/* ============================================================
   APP DIALOG — replaces native prompt()/confirm() (W4.2 fix-round-2, S6): a native box is
   browser chrome we do not control, rendered in the browser's own style and announcing the
   origin URL — the one thing on these screens that ignores this file's whole design system.
   (The cross-origin-iframe rationale originally given here was wrong for this deployment;
   corrected fix-round-3 — see collection.js's header.) One shared overlay serves both the text-input
   (askForName) and yes/no (askForConfirm) cases -- collection.js's openAppDialog() toggles
   #app-dialog-input's own `.hidden` class per mode. Centred, not a bottom sheet like
   .item-sheet -- a short, one-decision dialog reads as a dialog, not a drawer.
   ============================================================ */
.app-dialog {
  position: fixed;
  inset: 0;
  z-index: var(--z-dialog);
  background: var(--scrim);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-4);
}

.app-dialog__panel {
  width: 100%;
  max-width: var(--w-form);
  background: var(--bg);
  color: var(--ink);
  border: 1px solid var(--line);
  padding: var(--sp-5) var(--sp-4);
}

.app-dialog__message {
  margin: 0 0 var(--sp-4);
  font-size: var(--fs-body);
  line-height: 1.5;
  color: var(--ink);
}

.app-dialog__actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--sp-2);
  margin: var(--sp-4) 0 0;
}

.app-dialog__btn {
  height: var(--h-ctl);
  padding: 0 var(--sp-4);
  border: none;
  border-radius: var(--radius-ctl);
  font-family: var(--font);
  font-size: var(--fs-meta);
  text-transform: uppercase;
  letter-spacing: var(--ls-wide);
  font-weight: 700;
  cursor: pointer;
}
.app-dialog__btn:active {
  opacity: 0.8;
}

.app-dialog__btn--secondary {
  background: var(--bg);
  border: 1px solid var(--line);
  color: var(--ink);
}
.app-dialog__btn--secondary:active {
  background: var(--surface);
}

/* Mirrors .btn-primary's / .item-sheet__open's own accent-fill treatment -- --ink-on-accent,
   not --ink or white (see the :root note on --ink-on-accent for the contrast reasoning). */
.app-dialog__btn--primary {
  background: var(--accent);
  color: var(--ink-on-accent);
}

/* ============================================================
   PROFILE — read-only (name, items saved, collections). No destructive controls.
   ============================================================ */
.profile-name {
  font-size: var(--fs-head);
  color: var(--ink);
  margin: 0 0 var(--sp-6);
  font-weight: 700;
  overflow-wrap: anywhere;
}

.profile-stats {
  display: flex;
  gap: var(--sp-6);
}

.profile-stat__value {
  font-size: var(--fs-head);
  color: var(--ink);
  margin: 0;
  font-weight: 700;
}

.profile-stat__label {
  margin: var(--sp-1) 0 0;
}

/* ============================================================
   NAME SCREEN — restyle only. Behaviour (submitName/bootRefresh/fetchWithAuth, the W3.3
   warning, the W3.2 double-submit guard) is untouched; only markup structure and these
   classes changed.
   ============================================================ */
.name-screen {
  flex: 1;
  /* overflow-y:auto + scrollbar-width:none (fix-round-2, S2): .name-screen sits inside #app,
     which is h-screen + overflow-hidden, and had NO overflow handling of its own -- unlike
     every other screen in this app (.screen above, and every pre-W4.1 Tailwind state, all
     carry overflow-y-auto). On a short viewport (a landscape phone, a small desktop window)
     WITH the W3.3 session-expired warning also showing, content taller than the available
     flex space was clipped with no way to reach it -- including the submit button, which is a
     hard block on entering the app at all under name-only identity. */
  overflow-y: auto;
  scrollbar-width: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* safe center, not center (fix-round-2, S2): centres when the content fits, but falls back
     to start-aligned (scrollable from the top) rather than clipping symmetrically from BOTH
     ends when it doesn't -- the plain `center` keyword has no such fallback and can push
     content (the submit button, worst case) off both the top AND bottom simultaneously with
     nothing scrolled-to visible. */
  justify-content: safe center;
  text-align: center;
  background: var(--bg);
  color: var(--ink);
  padding: var(--sp-5);
  min-height: 70vh;
}

.name-screen__logo {
  height: 56px;
  width: auto;
  display: block;
  margin: 0 0 var(--sp-5);
}

.name-screen h1 {
  font-size: var(--fs-head);
  color: var(--ink);
  margin: 0 0 var(--sp-2);
  font-weight: 700;
}

.name-screen p {
  font-size: var(--fs-body);
  color: var(--ink-muted);
  max-width: var(--w-form);
  margin: 0 0 var(--sp-5);
}

/* Neutral (not accent, not Tailwind's amber) — the button below is this screen's one
   orange element, and stacking a second warm colour on the same screen would read as two.
   W4.3 adds .notice as the shared name and applies the same treatment to EVERY error surface in
   the app (the search error screen, the link field's error, the save sheet's error): the owner
   ruled on 2026-08-14 that errors stay neutral rather than red, because this palette has no error
   colour and inventing one would be guessing a colour instead of reading it off the logo — the
   same argument .field-error below already made. */
.name-screen__warning,
.notice {
  font-size: var(--fs-meta);
  line-height: 1.5;
  color: var(--ink);
  border: 1px solid var(--line);
  padding: var(--sp-3);
  max-width: var(--w-form);
  margin: 0 0 var(--sp-4);
}

.field {
  width: 100%;
  max-width: var(--w-form);
  height: var(--h-ctl);
  border: 1px solid var(--line);
  border-radius: var(--radius-ctl);
  padding: 0 var(--sp-4);
  font-size: var(--fs-body);
  font-family: var(--font);
  color: var(--ink);
  background: var(--bg);
  margin: 0 0 var(--sp-3);
  transition: border-color var(--motion) var(--ease);
}
.field:focus {
  outline: none;
  border-color: var(--ink);
}

/* The one deliberate orange element on this screen. --ink-on-accent text on the fill (fix-round-2,
   s4) -- NOT --ink (4.34:1, fails small text) and NOT white (2.94:1, fails outright); see the
   :root note. --ink-on-accent passes AA at any size in this file's scale, so the label is back
   to --fs-meta (11px), matching every other label on this screen -- the --fs-title bump from
   fix-round-1 was only ever working around the wrong colour and is no longer needed. */
.btn-primary {
  width: 100%;
  max-width: var(--w-form);
  height: var(--h-ctl);
  border: none;
  border-radius: var(--radius-ctl);
  background: var(--accent);
  color: var(--ink-on-accent);
  font-size: var(--fs-meta);
  text-transform: uppercase;
  letter-spacing: var(--ls-wide);
  font-family: var(--font);
  font-weight: 700;
  cursor: pointer;
  transition: opacity var(--motion) var(--ease);
}
.btn-primary:active {
  opacity: 0.8;
}
.btn-primary:disabled {
  opacity: 0.5;
  cursor: default;
}

/* The colour is now DECLARED here (W4.3) instead of being left to a Tailwind utility at the call
   site. The reasoning that kept it out is unchanged and was the right call -- the palette has no
   error colour and inventing one would be guessing instead of reading it off the logo -- but
   "no token" was being delivered as "whatever text-red-500 happens to mean", which is both a red
   this file never chose AND a dependency on a CDN that W4.4 deletes. The owner's 2026-08-14
   ruling settles it: errors are neutral --ink, like .notice above. */
.field-error {
  font-size: var(--fs-meta);
  margin: var(--sp-3) 0 0;
  max-width: var(--w-form);
  color: var(--ink);
}

/* ============================================================
   W4.3 — THE SEARCH FLOW. Search/idle, loading, shoe picker, results, error, the save sheet and
   the toast, brought into this file's language. Everything below reuses the vocabulary above:
   the same tile recipe (--surface fill, contain, never cropped), the same quiet uppercase meta
   type, the same near-zero radius, separation by whitespace rather than boxes.

   ONE ORANGE PER SCREEN. The tab bar is visible on every screen down here and its active tab is
   already --accent-text, so nothing below may add a second orange. The only exception is the save
   sheet's Save button, which follows .item-sheet__open's own precedent (an accent CTA inside a
   sheet that covers an already-orange-tabbed screen).
   ============================================================ */

/* Shared by every inline <svg> added in W4.3. The two shipped icon rules (.tab-btn svg,
   .icon-btn svg) carry their own identical copy of these five declarations; they are left alone
   rather than merged, for the same additive reason as .state-message. */
.drop-zone__action svg,
.step__check svg,
.result-card__save svg {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Reinstated in W4.3 (this file deleted an unused .screen-heading in W4.1 — see the note further
   up). The search flow has real on-screen headings, so it earns its place now. */
.screen-heading {
  font-size: var(--fs-head);
  color: var(--ink);
  margin: 0 0 var(--sp-2);
  font-weight: 700;
}

/* The neutral counterpart to .btn-primary: same height and shape, --line border instead of an
   accent fill. Used wherever a screen already spent its orange on the tab bar (Try again, the
   shoe-picker back control, the link field's Preview). */
.btn-secondary {
  height: var(--h-ctl);
  padding: 0 var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-ctl);
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--fs-meta);
  text-transform: uppercase;
  letter-spacing: var(--ls-wide);
  font-weight: 700;
  cursor: pointer;
  transition: background var(--motion) var(--ease);
}
.btn-secondary:active {
  background: var(--surface);
}
.btn-secondary:disabled {
  opacity: 0.5;
  cursor: default;
}

/* ---------- Search / idle ---------- */

.search-screen__greeting {
  font-size: var(--fs-title);
  color: var(--ink);
  margin: 0;
}

.search-screen__collections {
  margin: var(--sp-1) 0 var(--sp-5);
}

/* The drop zone is dominant by AREA, not by decoration -- it replaces a six-layer nested gradient
   construct. The dashed --surface tile is not an invention: it is .new-collection-tile__cover's
   recipe, already this system's "put something here" affordance. */
.drop-zone {
  aspect-ratio: 4 / 3;
  background: var(--surface);
  border: 1px dashed var(--line);
  border-radius: var(--radius-tile);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  transition: border-color var(--motion) var(--ease);
}
/* upload.js adds/removes .drag-over on #upload-zone -- unchanged by W4.3. */
.drop-zone.drag-over {
  border-color: var(--ink);
  border-style: solid;
}

.drop-zone__actions {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
}

.drop-zone__action {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  border: none;
  background: none;
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--fs-meta);
  text-transform: uppercase;
  letter-spacing: var(--ls-wide);
  cursor: pointer;
}
.drop-zone__action:active {
  opacity: 0.8;
}
.drop-zone__action svg {
  width: 24px;
  height: 24px;
}

.drop-zone__hint {
  margin: var(--sp-2) 0 0;
}

/* ---------- The inline link field (was a full-screen overlay before W4.3) ---------- */

.link-toggle {
  display: block;
  width: 100%;
  padding: var(--sp-4) 0;
  border: none;
  background: none;
  text-align: left;
  color: var(--ink-muted);
  font-family: var(--font);
  font-size: var(--fs-meta);
  text-transform: uppercase;
  letter-spacing: var(--ls-wide);
  cursor: pointer;
}
.link-toggle[aria-expanded="true"] {
  color: var(--ink);
}

.link-panel {
  border-top: 1px solid var(--line);
  padding-top: var(--sp-4);
}

.link-panel__row {
  display: flex;
  gap: var(--sp-2);
  margin: var(--sp-3) 0 0;
}

/* .field caps itself at --w-form and carries a bottom margin, both of which are name-screen
   assumptions leaking into a shared class. Overridden here rather than fixed at the source: the
   honest fix changes the name screen's rendering, which this story must not touch. W4.4. */
.link-panel__field {
  max-width: none;
  flex: 1;
  margin: 0;
}

/* ---------- Loading ---------- */

.loading-screen {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.loading-preview {
  width: 120px;
  aspect-ratio: 1 / 1;
  background: var(--surface);
  overflow: hidden;
  margin: 0 0 var(--sp-6);
}
.loading-preview img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.step-list {
  width: 100%;
  max-width: var(--w-form);
}

/* No border, no fill, no box -- separation by whitespace, like every other list in this file.
   NOTE the class is .step, deliberately NOT .step-row: index.html's legacy inline style block
   still carries .step-row[data-status="active"|"done"] rules that paint the old purple palette,
   and deleting that block is W4.4's job. Naming these .step means those rules simply stop
   matching -- no !important fight, no edit to an out-of-scope block. data-status keeps working
   because state.js sets the ATTRIBUTE, not the class. */
.step {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) 0;
}

.step__marker {
  position: relative;
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.step__dot {
  width: 8px;
  height: 8px;
  background: var(--line);
  border-radius: var(--radius-tile);
}
.step[data-status="active"] .step__dot {
  background: var(--ink);
  animation: step-pulse 1.4s var(--ease) infinite;
}
@keyframes step-pulse {
  50% { opacity: 0.35; }
}
/* --motion: 0ms does NOT neutralise a literal animation duration, so the opt-out is explicit. */
@media (prefers-reduced-motion: reduce) {
  .step[data-status="active"] .step__dot { animation: none; }
}

.step__check svg {
  width: 14px;
  height: 14px;
  color: var(--ink);
}

.step__title {
  font-size: var(--fs-body);
  color: var(--ink-muted);
  margin: 0;
}
.step[data-status="active"] .step__title,
.step[data-status="done"] .step__title {
  color: var(--ink);
}

.crop-preview {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin: var(--sp-3) 0;
}
.crop-preview__tile {
  width: 56px;
  aspect-ratio: 1 / 1;
  background: var(--surface);
  overflow: hidden;
  flex-shrink: 0;
}
.crop-preview__tile img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.loading-status {
  font-size: var(--fs-body);
  color: var(--ink-muted);
  text-align: center;
  margin: var(--sp-6) 0 0;
}

.fun-fact {
  max-width: var(--w-form);
  text-align: center;
  margin: var(--sp-6) 0 0;
}
.fun-fact__text {
  font-size: var(--fs-body);
  color: var(--ink-muted);
  line-height: 1.5;
  margin: var(--sp-2) 0 0;
}

/* ---------- Results ---------- */

.results-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  margin: 0 0 var(--sp-4);
}
/* The count must never wrap: the per-stage timing string next to it is long and open-ended
   (seven stages on a slow search), and left to itself flex would shrink the count first and
   stack "21 matches found" over three lines. */
.results-head .u-meta:first-child {
  flex-shrink: 0;
}
/* The timings are diagnostic, not a label -- lowercase and right-aligned, so a technical string
   reads as data rather than shouting in caps next to the count. */
.results-timing {
  text-align: right;
  text-transform: none;
  letter-spacing: 0;
}

.results-section {
  margin: var(--sp-6) 0 var(--sp-3);
}

/* The whole hero tile is the anchor, so the tap target is the photograph itself. */
.best-match__cover {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  background: var(--surface);
  overflow: hidden;
  margin: 0 0 var(--sp-3);
}
.best-match__cover img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* A quiet text link, NOT a filled button: the tab bar already spends this screen's one orange. */
.best-match__open {
  display: inline-block;
  margin: var(--sp-3) 0 0;
  text-decoration: none;
}

/* The save control is absolutely positioned over the tile but stays a SIBLING of the <a> in the
   DOM -- never a descendant. Nested interactive content is invalid HTML and would navigate to the
   listing on a save tap, which is the open W3 phone check this layout must not break. */
.result-card {
  position: relative;
}
/* Equal heights in a row come from the fixed-ratio cover in equal-width tracks plus a nowrap,
   ellipsised title -- structural, not an h-full patch. The visible proof is that no title wraps. */
.result-card .item-tile {
  height: 100%;
}

.result-card__save {
  position: absolute;
  top: var(--sp-2);
  right: var(--sp-2);
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--line);
  border-radius: var(--radius-ctl);
  background: var(--bg);
  color: var(--ink);
  cursor: pointer;
}
.result-card__save svg {
  width: 18px;
  height: 18px;
}
.result-card__save:active {
  background: var(--surface);
}
/* Was Tailwind's disabled:opacity-40, which applySavedMarks relies on to show a saved card as
   spent -- it sets btn.disabled = true and lets CSS do the rest. Re-homed here (W4.3). */
.result-card__save:disabled {
  opacity: 0.5;
  cursor: default;
}

/* Image-less StockX coverage rows: hairline-separated text, no tile. */
.stockx-row {
  display: block;
  padding: var(--sp-3) 0;
  border-bottom: 1px solid var(--line);
  text-decoration: none;
  color: var(--ink);
}
.stockx-row__title {
  font-size: var(--fs-body);
  color: var(--ink);
  margin: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* .item-tile serves a <button> (shoe picker), a <div> (collection item) and now an <a> (search
   result). Added as its own rule so the shipped .item-tile is untouched. */
a.item-tile {
  display: block;
  text-decoration: none;
  color: var(--ink);
}

/* ---------- Save sheet (reuses .item-sheet's own classes for everything else) ---------- */

.save-sheet__heading {
  font-size: var(--fs-title);
  font-weight: 700;
  color: var(--ink);
  margin: 0 0 var(--sp-4);
  padding-right: var(--h-ctl); /* clears the absolutely-positioned close button */
}

.chip.is-selected {
  background: var(--surface);
  border-color: var(--ink);
}

.item-sheet__open:disabled {
  opacity: 0.5;
  cursor: default;
}

/* ---------- Toast ---------- */

/* Was a dark translucent Tailwind pill, which read as foreign the moment the screens under it
   turned white (W4.3). Solid --ink with --bg text: 12.77:1, and no new colour. */
.toast {
  position: fixed;
  bottom: var(--sp-7);
  left: 50%;
  transform: translateX(-50%);
  z-index: var(--z-toast);
  max-width: var(--w-form);
  padding: var(--sp-3) var(--sp-4);
  background: var(--ink);
  color: var(--bg);
  border-radius: var(--radius-ctl);
  font-size: var(--fs-meta);
  text-transform: uppercase;
  letter-spacing: var(--ls-wide);
  text-align: center;
}

/* ---------- Name screen: data notice + age gate (2026-08-14) ---------- */

/* Quieter than .name-screen__warning next to it, and deliberately so: the W3.3 warning means
   "your wishlist is about to be lost", this is standing context. Same --w-form column as every
   other control on this screen. */
.name-screen__notice {
  max-width: var(--w-form);
  margin: 0 0 var(--sp-4);
  font-size: var(--fs-meta);
  line-height: 1.6;
  color: var(--ink-muted);
  text-align: left;
}
/* The one emphasised phrase. --ink, not --accent: this screen's single orange element is the
   button below it. */
.name-screen__notice strong {
  color: var(--ink);
  font-weight: 700;
}

/* The whole row is the <label>, so the text is part of the tap target -- a bare 16px checkbox is
   well under any reasonable touch minimum on a phone. */
.checkbox-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  max-width: var(--w-form);
  margin: 0 0 var(--sp-4);
  padding: var(--sp-2) 0;
  font-size: var(--fs-body);
  color: var(--ink);
  cursor: pointer;
  text-align: left;
}

/* accent-color paints the checked box with the brand orange using the browser's own control, so
   no custom SVG and no state-tracking CSS. The tick the browser draws on it is white, which is
   the one place white-on-accent is unavoidable -- it is a 2px glyph on a 20px box, not text, so
   the small-text contrast rule that governs --ink-on-accent does not apply to it. */
.checkbox-row__box {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

/* "New search", top of the results screen (2026-08-14). A quiet text control, deliberately not a
   .btn-secondary: the photography is what this screen is for, and a full-width bordered button
   above the hero would be the loudest thing on it. Colour is --ink rather than --ink-muted so it
   reads as an action rather than a label -- it is the answer to "how do I get back". */
.results-back {
  display: block;
  padding: 0 0 var(--sp-4);
  border: none;
  background: none;
  color: var(--ink);
  font-family: var(--font);
  cursor: pointer;
  text-align: left;
}
.results-back:active {
  opacity: 0.8;
}

/* ============================================================
   SHARE SETUP — the Profile screen's "save from other apps" block (W6).
   Quiet by design: it is one-time setup copy, not a feature of the screen, so it reads at
   --fs-meta in --ink-muted like .name-screen__notice rather than competing with the stats
   above it. No accent fill anywhere — the tab bar's active tab is this screen's one orange
   element and nothing here may add a second.
   ============================================================ */
.share-setup {
  margin-top: var(--sp-7);
  max-width: var(--w-form);
}

.share-setup h2 {
  margin: 0 0 var(--sp-3);
}

.share-setup__row {
  margin: 0 0 var(--sp-3);
  font-size: var(--fs-meta);
  line-height: 1.6;
  color: var(--ink-muted);
}

.share-setup__row strong {
  color: var(--ink);
  font-weight: 700;
}

.share-setup__row em {
  color: var(--ink);
  font-style: normal;
  font-weight: 500;
}

/* Underlined rather than coloured: --accent is 2.94:1 on white and fails for text at every size
   in this scale, and --ink alone would leave the link indistinguishable from the sentence. */
.share-setup__row a {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 2px;
}
