/* Lime Apparel -- beta. Same tokens as the full platform, so this looks like the brand
   and not like a starter template. */

/* Self-hosted fonts -- no external requests. Space Grotesk is the variable master (wght 300-700);
   Space Mono is Regular + Bold. Latin subset, woff2.

   These live at /fonts/, NOT under /assets/, and that path is load-bearing rather than cosmetic.
   _headers gives /assets/* a revalidate-always rule and fonts a one-year immutable rule. While the
   fonts sat under /assets/fonts/ BOTH rules matched, Cloudflare merged them, and every woff2
   served a malformed doubled Cache-Control -- "max-age=0, must-revalidate, max-age=31536000,
   immutable". Browsers resolve a conflict like that conservatively, so the year-long pin was dead
   and the fonts revalidated on every visit. Moving them out from under the catch-all is what makes
   double-matching structurally impossible, instead of relying on rule ordering to be read the way
   we hoped. Do not move these back under /assets/. */
@font-face {
  font-family: 'Space Grotesk';
  src: url('/fonts/space-grotesk.woff2') format('woff2');
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Space Mono';
  src: url('/fonts/space-mono-400.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Space Mono';
  src: url('/fonts/space-mono-700.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

:root {
  --lime: #8cb81e;
  --lime-deep: #6f9417;
  --lime-ink: #142000;
  --ink: #0b0b0b;
  --ink-2: #121212;
  --ink-3: #181818;
  --chip: #151515;
  --off: #f4f4ef;
  --text: #d7d7d2;
  --muted: #9a9a93;
  --faint: #6e6e68;
  --line: rgba(255, 255, 255, 0.1);
  --line-2: rgba(255, 255, 255, 0.16);
  --down: #d98a3d;
  --sans: 'Space Grotesk', system-ui, sans-serif;
  --mono: 'Space Mono', ui-monospace, monospace;
  --wrap: 1180px;
  --nav-h: 64px; /* the sticky header's height -- used to fit the product page to the viewport */
}

* { box-sizing: border-box; margin: 0; padding: 0; }

body {
  background: var(--ink);
  color: var(--text);
  font-family: var(--sans);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; text-decoration: none; }
img { display: block; max-width: 100%; }

.wrap { max-width: var(--wrap); margin: 0 auto; padding: 0 1.5rem; }

.kick {
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--lime);
}
.muted { color: var(--muted); }
.mono { font-family: var(--mono); }

/* ---- nav ---- */
.nav {
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  background: rgba(11, 11, 11, 0.92);
  backdrop-filter: blur(8px);
  z-index: 20;
}
.nav-in {
  max-width: var(--wrap);
  margin: 0 auto;
  padding: 1rem 1.5rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}
.brand { display: flex; align-items: center; gap: 0.6rem; }
.brand .mark {
  width: 30px; height: 30px; border-radius: 22%;
  background: var(--chip); color: var(--lime);
  display: flex; align-items: center; justify-content: center;
}
.brand .mark svg { width: 100%; height: 100%; display: block; }
.brand b { color: var(--off); letter-spacing: 0.06em; font-size: 0.95rem; }
.nav-links { display: flex; gap: 1.6rem; align-items: center; }
/* Hidden until the breakpoint that collapses the links behind it. Declared here rather than only
   inside the media query so that a nav rendered above the breakpoint never flashes a burger. */
.nav-burger { display: none; }
/* This codebase has no global focus rule -- every control carries its own, and several use the
   lime ring. Without this the burger fell back to the browser default, which is visible but is
   not ours. focus-visible, not focus, so a mouse click does not leave a ring behind. */
.nav-burger:focus-visible { outline: 2px solid var(--lime); outline-offset: 2px; }
.nav-links a { color: var(--muted); font-size: 0.9rem; transition: 0.15s; }
.nav-links a:hover, .nav-links a.on { color: var(--off); }

/* Women / Men / All toggle -- a pill segmented control by the logo, matching the main platform. */
.ctx-switch {
  display: inline-flex; margin-left: 1.2rem; margin-right: auto;
  border: 1px solid var(--line-2); background: var(--ink-2);
  border-radius: 40px; padding: 2px;
}
.ctx-switch .ctx {
  font-family: var(--mono); font-weight: 700; font-size: 0.66rem;
  letter-spacing: 0.08em; text-transform: uppercase;
  padding: 0.4rem 0.72rem; border: 0; background: transparent;
  color: var(--muted); cursor: pointer; border-radius: 40px; transition: 0.15s;
}
.ctx-switch .ctx:hover { color: var(--off); }
.ctx-switch .ctx.on { background: var(--lime); color: var(--lime-ink); }

/* ---- buttons ---- */
.btn {
  display: inline-block;
  background: var(--lime);
  color: var(--lime-ink);
  border: 0;
  border-radius: 999px;
  padding: 0.85rem 1.8rem;
  font-family: var(--sans);
  font-weight: 600;
  font-size: 0.92rem;
  cursor: pointer;
  transition: 0.15s;
}
.btn:hover { background: var(--lime-deep); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-ghost {
  display: inline-block;
  background: transparent;
  color: var(--text);
  border: 1px solid var(--line-2);
  border-radius: 999px;
  padding: 0.85rem 1.8rem;
  font-family: var(--sans);
  font-size: 0.92rem;
  cursor: pointer;
}
.btn-ghost:hover { border-color: var(--off); color: var(--off); }
.btn-full { width: 100%; text-align: center; }

/* ---- admin sign-out pill (lime, nav-scaled) ---- */
#signout {
  display: inline-flex;
  align-items: center;
  background: var(--lime);
  color: var(--lime-ink);
  font-family: var(--mono);
  font-weight: 700;
  font-size: 0.66rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.42rem 0.9rem;
  border-radius: 999px;
  text-decoration: none;
  transition: background 0.15s, color 0.15s;
}
#signout:hover { background: var(--lime-deep); color: var(--lime-ink); }

/* ---- section head ---- */
.sec { padding: 3rem 0; }

/* Fluid page headings. The size grows with the viewport instead of jumping at a breakpoint, so a
   tablet gets a tablet-sized heading rather than the phone one or the desktop one.
   The middle term is written as rem + vw, never vw alone, and that is the whole accessibility
   story: a pure vw size ignores the reader's browser zoom and font-size setting, so someone who
   zooms to 200% would get no larger text at all. Keeping a rem in the sum means the reader's own
   setting still scales it.
   The floors are readability limits, not layout convenience: 24px for a page title and 20px for
   the shop's section heading stay comfortable on the smallest phone. The ceilings are exactly
   today's desktop sizes, so nothing above 840px changes at all.
   Deliberately a CLASS and never a bare h1 selector: admin.html shares this stylesheet and its
   own headings are h1 elements, so an element selector here would quietly resize the admin. */
.page-h1 {
  color: var(--off);
  font-size: clamp(1.5rem, 1.05rem + 2vw, 2.1rem);
  font-weight: 700;
}
/* The shop's section intro: heading and sort control share the top row, subheading sits under
   both. The vertical rhythm is fluid for the same reason the type is -- on a phone this block was
   eating 221px before the first product card, most of it fixed desktop spacing that a small screen
   cannot afford.
   flex-wrap on the head row is a safety net rather than a layout: the pill's width follows the
   SELECTED label, so a long one ("Price: High to Low") can leave the heading too little room. When
   that happens the control drops below the heading instead of squashing it. */
.sec-intro { text-align: left; margin-bottom: clamp(1.3rem, 0.5rem + 2vw, 2.6rem); }
.sec-intro-head {
  display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap;
  gap: 0.7rem 1rem; margin-bottom: clamp(0.45rem, 0.2rem + 0.8vw, 0.9rem);
}
.sec-intro-tools { flex: 0 0 auto; }
/* h1, not h2, and the ELEMENT is what moved rather than the styling. The shop grid is the
   homepage, and it carried no h1 at all -- every other page on the site has exactly one, and the
   single most important page had none for a screen reader to land on or a crawler to read.
   Promoting it to .page-h1 was the wrong fix: that is the big page-title size, and the shop's
   heading is deliberately a section heading. So the element changed and this selector followed,
   which keeps the rendering byte-identical. Scoped under .sec-intro, which exists only on
   index.html -- admin.html shares this stylesheet and uses bare h1 elements, so an unscoped h1
   rule here would quietly resize the admin, exactly as the note on .page-h1 above warns. */
.sec-intro h1 { color: var(--off); font-size: clamp(1.25rem, 0.9rem + 1.6vw, 1.7rem); font-weight: 700; margin: 0; }
/* One line at desktop by intent, free to wrap on a phone. Forcing a single line at every width
   would mean truncating with an ellipsis, which reads worse than an honest second line. */
.sec-intro p { max-width: 46rem; margin: 0; }

/* ---- product grid + card ----
   The card is a faithful copy of the built shop's ProductCard / CardMedia (web/src/components):
   a 1:1 .ph frame that CONTAINS any aspect ratio (never crops), a glassy badge overlaid
   top-left, hover-revealed arrows + dots, and below the frame a .meta row with name+lead on
   the left and price on the right. Rendered with this beta's own tokens -- same brand, same
   fonts -- so nothing about the styling changes, only the card's shape and behaviour. */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 1.4rem;
}
.pcard { position: relative; display: flex; flex-direction: column; color: inherit; transition: transform 0.18s; }
.pcard:hover { transform: translateY(-3px); }
.pcard .ph {
  position: relative;
  aspect-ratio: 1 / 1;
  border-radius: 16px;
  overflow: hidden;
  border: 1px solid var(--line);
  background: var(--ink-3);
  display: flex; align-items: center; justify-content: center;
}
.pcard .ph img,
.pcard .ph video {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: contain; display: block; background: #0e0e0e;
}
.pcard:hover .ph { border-color: var(--line-2); }
.pcard .ph-empty {
  font-family: var(--mono); font-size: 0.6rem; letter-spacing: 0.14em;
  color: var(--faint); text-transform: uppercase;
}
.pcard .arw {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 3;
  width: 30px; height: 30px; border-radius: 50%;
  border: 1px solid var(--line-2); background: rgba(11, 11, 11, 0.5);
  backdrop-filter: blur(6px); color: var(--off); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  font-size: 1.1rem; line-height: 1; opacity: 0; transition: opacity 0.16s;
}
.pcard .ph:hover .arw { opacity: 1; }
.pcard .arw.prev { left: 8px; }
.pcard .arw.next { right: 8px; }
.pcard .arw:hover { border-color: var(--lime); color: var(--lime); }
.pcard .dots {
  position: absolute; left: 0; right: 0; bottom: 9px; z-index: 3;
  display: flex; gap: 6px; justify-content: center;
}
.pcard .dot {
  width: 6px; height: 6px; padding: 0; border: 0; border-radius: 50%;
  background: rgba(255, 255, 255, 0.45); cursor: pointer; transition: 0.16s;
}
.pcard .dot.on { background: var(--lime); transform: scale(1.3); }
/* .pcard .tags / .tag / .tag.mtm removed with the overlaid "Made to measure" badge -- the
   wording moved into the .ct meta line beside the lead time, because the badge sat on the
   model's face once the imagery stopped being stock scenery. Scoped rules, so the blog's own
   .tags/.tag markup is untouched. */
/* The per-product accent bar. A pseudo-element, so no extra node enters the DOM and the card
   markup gains only the --pc-accent custom property. align-self is load-bearing: .meta aligns its
   children to the baseline, and a pseudo-element inheriting that alignment collapses to ZERO
   height -- correct-looking CSS in the served file that renders nothing at all. Stretching it
   explicitly is what makes it exist. The gradient fades to transparent so it reads as a tick
   beside the name rather than a stripe competing with the garment photography above it. The
   fallback colour keeps the bar visible if a card ever renders without an accent. */
.pcard .meta::before {
  content: "";
  align-self: stretch;
  flex: 0 0 3px;
  border-radius: 3px;
  background: linear-gradient(var(--pc-accent, var(--line-2)), transparent);
}
.pcard .meta {
  padding: 0.85rem 0.15rem 0;
  display: flex; justify-content: space-between; gap: 0.8rem; align-items: baseline;
}
.pcard .meta > div:first-child { flex: 1; min-width: 0; }
.pcard .nm {
  color: var(--off); font-weight: 600; font-size: 0.98rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pcard .ct {
  font-family: var(--mono); font-weight: 700; font-size: 0.6rem;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted);
  margin-top: 0.2rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pcard .pr {
  color: var(--lime); font-weight: 700; font-family: var(--mono); font-size: 0.9rem;
  white-space: nowrap; flex-shrink: 0;
}

/* ---- product detail ---- */
.pdp { display: grid; grid-template-columns: 1.1fr 1fr; gap: 3rem; padding: 2rem 0 3rem; align-items: start; }
/* The gallery column pins under the sticky header, so the media stays fully in view while the
   details scroll. Its exact height is set to fit the viewport by the fitGallery() pass in
   product.html; the max-height here is a no-JS safety net so the frame is never a giant. */
.pdp .gallery {
  position: sticky;
  top: calc(var(--nav-h) + 1rem);
  align-self: start;
}
/* Spacing only. The product title carries .page-h1 for colour, weight and the fluid size, so the
   clamp is written in exactly one place. It used to be duplicated verbatim here, which meant the
   product page silently opted out of the shared treatment while looking like it had opted in --
   and a later edit to .page-h1 would have moved every heading on the site except this one. */
.pdp h1 { margin: 0.5rem 0 0.6rem; }
.price { font-family: var(--mono); font-size: 1.5rem; color: var(--off); margin-bottom: 1.4rem; }

/* What is added to the price at checkout -- shipping, and a note that tax is worked out from the
   delivery address. Sits directly under .price and pulls up into the margin that element already
   reserves, so the pair reads as one block rather than two stray lines. Muted and small: it is a
   qualifier on the price, not a second price competing with it. */
.price-extra {
  margin: -1.1rem 0 1.4rem;
  font-size: .78rem;
  color: var(--muted);
  max-width: 44ch;
}
.field { margin-bottom: 1.2rem; }
.field label {
  display: block; font-family: var(--mono); font-size: 0.62rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--off); font-weight: 700; margin-bottom: 0.45rem;
}
.field input, .field select, .field textarea {
  width: 100%; background: var(--ink-2); border: 1px solid var(--line);
  border-radius: 10px; padding: 0.75rem 0.9rem; color: var(--text);
  font-family: var(--sans); font-size: 0.92rem;
}
.field textarea { min-height: 110px; resize: none; scrollbar-width: thin; scrollbar-color: var(--line-2) transparent; }
.field textarea::-webkit-scrollbar { width: 10px; }
.field textarea::-webkit-scrollbar-track { background: transparent; }
.field textarea::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: 10px; }
.field textarea::-webkit-scrollbar-thumb:hover { background: var(--muted); }
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none; border-color: var(--lime);
}
.field input:-webkit-autofill,
.field input:-webkit-autofill:hover,
.field input:-webkit-autofill:focus,
.field input:-webkit-autofill:active,
.field select:-webkit-autofill,
.field textarea:-webkit-autofill {
  -webkit-text-fill-color: var(--text);
  -webkit-box-shadow: 0 0 0 1000px var(--ink-2) inset;
  box-shadow: 0 0 0 1000px var(--ink-2) inset;
  caret-color: var(--text);
  border: 1px solid var(--line);
  transition: background-color 100000s ease-in-out 0s;
}
.field input:-webkit-autofill:focus,
.field select:-webkit-autofill:focus,
.field textarea:-webkit-autofill:focus {
  border-color: var(--lime);
}
.field input:autofill,
.field select:autofill,
.field textarea:autofill {
  -webkit-text-fill-color: var(--text);
  box-shadow: 0 0 0 1000px var(--ink-2) inset;
  caret-color: var(--text);
}
.field .hint { font-size: 0.78rem; color: var(--faint); margin-top: 0.35rem; }
/* The fit-notes counter. Inherits .hint so it sits in the same quiet register as the line above
   it, and only changes colour once the cap is actually reached -- var(--down) is the same amber
   this page already uses for an invalid field, so "you have hit a limit" reads the same way twice
   rather than inventing a second vocabulary for it. */
.field .fitcount { font-variant-numeric: tabular-nums; }
.field .fitcount.at-cap { color: var(--down); }
/* Stands where the size picker's label would be when a made-to-measure piece carries no sizes.
   Deliberately borrows .field label's metrics rather than inventing a second treatment -- same
   mono face, same size, same tracking -- and differs only in colour, so it reads as the label
   slot answering itself rather than as a new kind of element. */
.field .nosize {
  font-family: var(--mono); font-size: 0.62rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--lime); font-weight: 700;
  margin: 0 0 0.45rem;
}
.fld-select { position: relative; }
.fld-select-trigger {
  width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
  background: var(--ink-2); border: 1px solid var(--line); border-radius: 10px;
  padding: 0.75rem 0.9rem; color: var(--text); font-family: var(--sans); font-size: 0.92rem;
  text-align: left; cursor: pointer;
}
.fld-select-trigger.placeholder { color: var(--faint); }
.fld-select-trigger:focus { outline: none; border-color: var(--lime); }
.fld-select.open .fld-select-trigger { border-color: var(--lime); }
.fld-select-chev { flex-shrink: 0; color: var(--muted); transition: transform 0.18s, color 0.18s; }
.fld-select.open .fld-select-chev { transform: rotate(180deg); color: var(--lime); }
.fld-select-menu {
  position: absolute; left: 0; right: 0; top: calc(100% + 6px); z-index: 30;
  background: var(--ink-3); border: 1px solid var(--line-2); border-radius: 10px; padding: 5px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}
.fld-select-menu[hidden] { display: none; }
.fld-select-opt {
  display: block; width: 100%; text-align: left; background: transparent; border: 0;
  color: var(--text); font-family: var(--sans); font-size: 0.9rem; padding: 0.55rem 0.7rem;
  border-radius: 6px; cursor: pointer;
}
.fld-select-opt:hover, .fld-select-opt:focus { outline: none; background: var(--ink-2); color: var(--off); }
.fld-select-opt[aria-selected="true"] { color: var(--lime); font-weight: 700; }

/* Compact pill variant of the select above, for the shop's sort control. A MODIFIER rather than a
   change to the component: custom.html leans on the full-width field styling for its budget and
   date pickers, and breaking that would be breaking a page next to checkout. The control sits
   beside the Women/Men/All switch, so it borrows that pill's mono-uppercase treatment and reads
   as its sibling rather than as a stray form field. Its type and padding are fluid so the pill
   stays proportionate to the heading it now sits beside. */
.fld-select.sel-pill { display: inline-block; }
.sel-pill .fld-select-trigger {
  width: auto; border-radius: 999px; border-color: var(--line-2);
  padding: 0.42rem clamp(0.6rem, 0.4rem + 0.5vw, 0.8rem) 0.42rem clamp(0.65rem, 0.45rem + 0.5vw, 0.95rem);
  gap: 0.4rem;
  font-family: var(--mono); font-weight: 700;
  font-size: clamp(0.6rem, 0.54rem + 0.16vw, 0.66rem);
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted);
}
.sel-pill .fld-select-trigger:hover { color: var(--off); }
.sel-pill.open .fld-select-trigger { color: var(--off); }
.sel-pill .fld-select-chev { width: 12px; height: 12px; }
/* Right anchored, which is correct while the control sits at the right of the intro row. The
   max-width only stops the panel being wider than the screen; it does NOT stop it being anchored
   off the edge -- a menu wider than a short pill at the left of a phone row starts at a negative
   x and gets cut off. mountSelect measures on open and adds .align-left when that would happen. */
.sel-pill .fld-select-menu {
  left: auto; right: 0; min-width: 100%; width: max-content;
  max-width: min(calc(100vw - 2rem), 18rem);
}
/* Applied by mountSelect when right-anchoring would run the panel off the left of the screen --
   which happens on a phone, where the control wraps to the left of its own row and the menu is
   wider than a short pill. */
.sel-pill .fld-select-menu.align-left { left: 0; right: auto; }
.sel-pill .fld-select-opt { font-size: 0.82rem; white-space: nowrap; }
.fld-date { position: relative; }
.cal {
  position: absolute; left: 0; top: calc(100% + 6px); z-index: 30; width: 320px; max-width: 100%;
  background: var(--ink-3); border: 1px solid var(--line-2); border-radius: 12px; padding: 0.8rem;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}
.cal[hidden] { display: none; }
.fld-date.open-up .cal { top: auto; bottom: calc(100% + 6px); }
.cal-quick { display: flex; gap: 0.1rem; }
.cal-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.6rem; }
.cal-title { font-family: var(--sans); font-weight: 700; color: var(--off); font-size: 0.92rem; }
.cal-nav {
  background: transparent; border: 0; color: var(--muted); cursor: pointer; padding: 0.3rem;
  border-radius: 6px; display: inline-flex;
}
.cal-nav:hover { color: var(--lime); background: var(--ink-2); }
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.cal-wd {
  text-align: center; font-family: var(--mono); font-size: 0.58rem; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--faint); padding: 0.3rem 0;
}
.cal-day {
  aspect-ratio: 1; min-height: 34px; display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: 1px solid transparent; color: var(--text);
  font-family: var(--sans); font-size: 0.82rem; border-radius: 8px; cursor: pointer;
}
.cal-day:hover:not(.past):not(.blank) { background: var(--ink-2); color: var(--off); }
.cal-day:focus { outline: none; border-color: var(--lime); }
.cal-day.today { border-color: var(--lime); color: var(--lime); }
.cal-day.selected { background: var(--lime); color: var(--lime-ink); border-color: var(--lime); font-weight: 700; }
.cal-day.past { color: var(--line-2); cursor: default; }
.cal-day.blank { visibility: hidden; cursor: default; }
.cal-foot {
  display: flex; align-items: center; justify-content: space-between;
  margin-top: 0.6rem; padding-top: 0.6rem; border-top: 1px solid var(--line);
}
.cal-btn {
  background: transparent; border: 0; color: var(--lime); font-family: var(--mono);
  font-size: 0.72rem; letter-spacing: 0.04em; cursor: pointer; padding: 0.3rem 0.4rem; border-radius: 6px;
}
.cal-btn:hover { background: var(--ink-2); }
.cal-btn.clear { color: var(--muted); }
.cal-btn.clear:hover { color: var(--off); }
.dropzone {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.3rem;
  text-align: center; padding: 1rem 1rem; border: 1.5px dashed var(--line-2); border-radius: 12px;
  background: var(--ink-2); color: var(--muted); cursor: pointer; transition: border-color 0.15s, background 0.15s;
}
.dropzone:hover, .dropzone:focus { outline: none; border-color: var(--lime); }
.dropzone.drag { border-color: var(--lime); background: var(--ink-3); }
.dropzone-ic { color: var(--lime); margin-bottom: 0.1rem; }
.dropzone-title { font-family: var(--sans); font-weight: 700; color: var(--off); font-size: 0.95rem; }
.dropzone-sub { font-family: var(--sans); font-size: 0.8rem; color: var(--faint); max-width: 34ch; }
.thumbs { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.thumbs:not(:empty) { margin-top: 0.7rem; }
.thumb { position: relative; width: 72px; height: 72px; border-radius: 8px; overflow: hidden; border: 1px solid var(--line-2); }
.thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.thumb-x {
  position: absolute; top: 3px; right: 3px; width: 20px; height: 20px; border: 0; border-radius: 50%;
  background: rgba(0, 0, 0, 0.7); color: var(--off); font-size: 0.9rem; line-height: 1; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.thumb-x:hover { background: var(--down); color: #fff; }
.sizes { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.size-b {
  background: var(--ink-2); border: 1px solid var(--line); color: var(--text);
  border-radius: 8px; padding: 0.55rem 1rem; cursor: pointer;
  font-family: var(--mono); font-size: 0.82rem;
}
.size-b.on { background: var(--lime); color: var(--lime-ink); border-color: var(--lime); font-weight: 700; }

.note {
  border-left: 2px solid var(--lime); background: var(--ink-2);
  padding: 0.85rem 1rem; margin: 1.4rem 0; font-size: 0.85rem; color: var(--muted);
}
.err {
  border-left: 2px solid var(--down); background: var(--ink-2);
  padding: 0.85rem 1rem; margin: 1rem 0; font-size: 0.85rem; color: var(--down);
}

/* Per-field validation message on the commission intake form. Sits under its own control rather
   than in a banner: that form is tall enough for the offending field to be off-screen entirely,
   which is the whole reason the native bubble was worth replacing.

   NOTE the missing `display`. These are toggled with the `hidden` attribute, and `hidden` is only
   honoured by the UA stylesheet -- ANY author display declaration silently overrides it and every
   error paragraph renders permanently, empty, from page load. The [hidden] rule below is the
   guard against someone adding one later. */
.fld-err { font-size: 0.78rem; color: var(--down); margin-top: 0.35rem; }
.fld-err[hidden] { display: none; }
/* Colour alone never carries the message -- the text does. This only reinforces it. */
.field input.invalid, .field textarea.invalid { border-color: var(--down); }

/* ---- footer ----
   Two rows: the brand block beside the link columns, then a hairline and the copyright line.
   .foot-cols is a FLEX row, and that is a fix rather than a preference. It was
   `grid-template-columns: repeat(auto-fit, minmax(8rem, max-content))`, which needs a definite
   container width to fit tracks into -- and .foot-cols is a flex ITEM of .foot-top, so it sized
   to its own content instead. auto-fit resolved to a single 128px track and the columns rendered
   STACKED, measured live at 1280px. A flex row cannot collapse that way, and it still takes the
   third column (COMPANY, once About and Contact have destinations) with no rule change.
   .foot-col a gets a focus ring. .nav-links a still has none -- that is pre-existing and shared
   with the desktop nav, and is left alone here deliberately rather than widened by accident. */
.foot { border-top: 1px solid var(--line); margin-top: 4rem; padding: 3rem 0 2rem; }
.foot-top { display: flex; justify-content: space-between; gap: 2.5rem; flex-wrap: wrap; }
.foot-brand { max-width: 22rem; }
.foot-brand-link { display: inline-flex; align-items: center; gap: 0.6rem; color: var(--off); text-decoration: none; }
.foot-brand-link b { font-size: 0.95rem; letter-spacing: 0.06em; }
.foot-mark { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px;
  border-radius: 8px; background: var(--lime); color: var(--ink); }
.foot-mark svg { width: 17px; height: 17px; }
.foot-tag { margin: 0.9rem 0 0; font-size: 0.86rem; line-height: 1.6; color: var(--muted); }
/* Sits BETWEEN the brand block and the columns. .foot-top is space-between, so a third child
   lands in the middle on its own; align-self centres it against the taller neighbours rather
   than letting it hang off the top. The Instagram link leaves the site, so it carries
   rel="noopener noreferrer" -- the mail link does not, and must not open in a new tab. */
.foot-social { display: flex; align-items: center; align-self: center; gap: 0.9rem; }
.foot-soc { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px;
  border: 1px solid var(--line); border-radius: 50%; color: var(--muted);
  transition: color 0.15s, border-color 0.15s; }
.foot-soc svg { width: 19px; height: 19px; }
.foot-soc:hover { color: var(--off); border-color: var(--line-2); }
.foot-soc:focus-visible { outline: none; color: var(--off); box-shadow: 0 0 0 2px var(--ink), 0 0 0 4px var(--lime); }
.foot-cols { display: flex; flex-wrap: wrap; gap: 2.75rem; }
.foot-col { display: flex; flex-direction: column; gap: 0.7rem; min-width: 7rem; }
.foot-col-h { margin: 0 0 0.3rem; font-family: var(--mono); font-size: 0.6rem; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--faint); }
.foot-col a { color: var(--muted); text-decoration: none; font-size: 0.88rem; border-radius: 3px; transition: color 0.15s; }
.foot-col a:hover { color: var(--off); }
.foot-col a:focus-visible { outline: none; color: var(--off); box-shadow: 0 0 0 2px var(--ink), 0 0 0 4px var(--lime); }
.foot-in { display: flex; justify-content: center; text-align: center; gap: 1rem; flex-wrap: wrap;
  font-size: 0.82rem; color: var(--faint);
  margin-top: 2.75rem; padding-top: 1.5rem; border-top: 1px solid var(--line); }
/* Centre the whole footer once it stops being a single row. 860px is MEASURED, not guessed: the
   brand block, the icons and the columns hold one flex line down to 860px of container width and
   break to two at 840. Left-aligned content stacked in a narrow column reads as a mistake rather
   than a choice, so everything centres together -- brand row, tagline, icons, the column group and
   the links inside it. text-align does the work for the inline-flex brand link and the column
   links; justify-content does it for the flex rows.
   The copyright bar is NOT listed here: .foot-in centres at every width via its own base rule, so
   an override in this block would be dead CSS. */
@media (max-width: 860px) {
  .foot-top { justify-content: center; text-align: center; gap: 2rem; }
  /* Each block gets its OWN line. Without this the three children pair up on shared flex lines
     and justify-content centres each LINE as a group rather than each block -- measured at 800px,
     the icons sat 285px right of centre and the brand 63px left, which reads as broken alignment
     rather than as centred. Full-basis rows make every block centre on its own axis. */
  .foot-top > * { flex: 0 0 100%; }
  .foot-brand { max-width: 34rem; margin: 0 auto; }
  .foot-social { justify-content: center; }
  .foot-cols { justify-content: center; }
}
@media (max-width: 560px) {
  .foot-cols { width: 100%; gap: 2rem; }
}

/* ---- admin ---- */
.kpis { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.85rem; margin-bottom: 2rem; }
.kpi { background: var(--ink-2); border: 1px solid var(--line); border-radius: 14px; padding: 1rem 1.05rem; }
.kpi .k { font-family: var(--mono); font-size: 0.58rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--faint); margin-bottom: 0.5rem; }
.kpi .v { font-family: var(--mono); font-size: 1.5rem; font-weight: 700; color: var(--off); white-space: nowrap; }
.kpi .s { font-family: var(--mono); font-size: 0.68rem; color: var(--lime); margin-top: 0.3rem; }

/* EIGHT KPI cards: four money cards, then tax, shipping and the two COUNT cards.
   Four columns give 4 + 4, which is the grouping the row means: what was earned and returned on
   top, what is merely held plus the counts underneath.
   The old .kpi-row2 { grid-column: 2 } centred two count cards when there were six. With eight it
   would shove the fifth card into column two and leave a hole beside it, so it is gone from both
   the CSS and the markup.
   Three columns is deliberately skipped. Eight cards at three across is 3 + 3 + 2, which splits
   the two halves across a row boundary and puts Refund next to Tax Collected as though they were
   the same kind of figure. Every value already passes through shrinkFont, so four narrow columns
   stay legible far longer than the grid would otherwise allow. */
@media (max-width: 860px) { .kpis { grid-template-columns: repeat(2, 1fr); } }

.panel { background: var(--ink-2); border: 1px solid var(--line); border-radius: 14px;
  padding: 1.3rem; margin-bottom: 1.5rem; }
.panel h3 { color: var(--off); font-size: 1rem; margin-bottom: 1rem; }

table { width: 100%; border-collapse: collapse; font-size: 0.86rem; }
th {
  text-align: left; font-family: var(--mono); font-size: 0.58rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--faint); font-weight: 400;
  padding: 0.5rem 0.6rem; border-bottom: 1px solid var(--line);
}
td { padding: 0.7rem 0.6rem; border-bottom: 1px solid var(--line); vertical-align: top; overflow-wrap: anywhere; }
td.num, th.num { text-align: right; font-family: var(--mono); white-space: nowrap; }
td.mono { white-space: nowrap; }
tbody tr:last-child td { border-bottom: 0; }

.page-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 1rem; flex-wrap: wrap; margin-bottom: 2rem; }
.page-head .ahead { margin: 0; }

/* THE HEADING TAKES ITS OWN LINE, but only on the two headers that carry a download button, and
   only once the row is genuinely too tight for all three items.
   Left to wrap on its own, flex drops the WIDEST item first -- the tab strip -- leaving
   "Shop Orders [Download Excel]" sitting together on line one with the period filter stranded
   below, which reads as though the button belonged to the title. Forcing the heading full-width
   puts the actions and the tabs on the next line TOGETHER.
   THE THRESHOLD IS MEASURED. This rule used to sit inside the 800px block, which was right when
   the header held heading + button + tabs. Adding the awaiting-booking chip widened the actions
   group, and at 925px of header -- what a 1280 CSS pixel display leaves -- the requirements are
   Shop 843 and Custom 884 with the current short label. Those clear 925, so both screens hold one
   row at the width this admin is actually used at. They cross it at roughly 1198 and 1239
   viewport respectively, so 1250 catches both just before they wrap.
   Deliberately NOT higher: 1300 would have fired at 1280 and cost a line on the very display the
   panel is run on, which is what the first attempt at this got wrong.
   SCOPED to .has-export: .page-head appears four times. Dashboard is heading plus tabs and still
   fits, Shipping is a heading alone, and splitting either would cost a line for nothing. */
@media (max-width: 1250px) {
  .page-head.has-export .ahead { flex-basis: 100%; }
}
.tabs { display: inline-flex; flex-wrap: wrap; gap: 2px; padding: 3px; background: var(--ink-2); border: 1px solid var(--line-2); border-radius: 9px; }
.tab {
  background: transparent; border: 0; color: var(--muted);
  border-radius: 6px; padding: 0.4rem 0.7rem; cursor: pointer;
  font-size: 0.78rem; font-weight: 600; white-space: nowrap;
}
.tab:hover { color: var(--off); }
.tab.on { background: var(--lime); color: var(--lime-ink); font-weight: 700; }

/* An action shaped like the period filter beside it, so the header row reads as one control group
   rather than a button that wandered in. The tokens are BORROWED, not approximated: .tabs' own
   --ink-2 ground, --line-2 hairline and 9px radius, and .tab's 0.78rem/600 type. The padding is
   .tab's 0.4rem 0.7rem plus the 3px the .tabs container adds around it, which is what lands both
   controls on the same height and the same baseline.
   NEUTRAL, not lime-filled. .tab.on is lime, and a second lime pill on the same row would read as
   though the filter had two active values -- this is an action, not a state. */
.headbtn {
  background: var(--ink-2); border: 1px solid var(--line-2); border-radius: 9px;
  color: var(--muted); cursor: pointer;
  padding: calc(0.4rem + 3px) calc(0.7rem + 3px);
  font-family: inherit; font-size: 0.78rem; font-weight: 600; white-space: nowrap;
  transition: color 0.15s, border-color 0.15s;
}
.headbtn:hover { color: var(--lime); border-color: var(--lime); }
/* .tab carries no focus ring -- a pre-existing gap, not one to inherit. */
.headbtn:focus-visible { outline: 2px solid var(--lime); outline-offset: 2px; }
/* While a file is being built the button reads "Preparing..." and is disabled. Without this it
   looks identical to a live control, so the click reads as having done nothing. */
.headbtn:disabled { opacity: 0.55; cursor: default; }
.headbtn:disabled:hover { color: var(--muted); border-color: var(--line-2); }
/* The download hint takes a full line of its own inside .page-head, so appearing cannot shove the
   heading and the tabs sideways. Hidden it costs nothing: [hidden] is display:none !important. */
.headbtn-hint { flex-basis: 100%; margin: 0; }

.thumbs { display: flex; gap: 0.4rem; flex-wrap: wrap; }
.thumbs a {
  display: block; width: 54px; height: 54px; border-radius: 8px; overflow: hidden;
  border: 1px solid var(--line);
}
.thumbs img { width: 100%; height: 100%; object-fit: cover; }
/* Dashboard Custom Orders card only: 3 tiles per row (max 6 uploads -> a clean 3x2 grid). */
#customs .thumbs { display: grid; grid-template-columns: repeat(3, 54px); }

.empty { text-align: center; padding: 2.5rem 1rem; color: var(--faint); font-size: 0.9rem; }

@media (max-width: 800px) {
  .pdp { grid-template-columns: 1fr; gap: 1.8rem; }
  /* Stacked layout: media on top, details below -- the gallery should scroll away normally, not
     pin. fitGallery() caps it to ~70% of the screen so you see the photo AND the details start. */
  .pdp .gallery { position: static; }
  .nav-links { gap: 1rem; }
  .nav-links a { font-size: 0.82rem; }
  /* Tighten the department toggle so brand + toggle + links fit a phone width. */
  .ctx-switch { margin-left: 0.7rem; }
  .ctx-switch .ctx { padding: 0.34rem 0.5rem; font-size: 0.6rem; }
}

/* ================================================================================
   MEDIA -- PDP gallery + lightbox (the product-detail page).
   The card carousel lives up in the product-grid section above; it follows the built shop's
   CardMedia. Gallery + lightbox here follow product-mtm.html, which lives in the MAIN PLATFORM
   repo under mockups/ -- there is no mockups/ directory in this repo, so it cannot be found by
   searching here. Everywhere, media is
   object-fit: CONTAIN, never cropped -- a cropped garment photo is a lie about the garment.
   ================================================================================ */


/* ---- PDP gallery ---- */
.gallery { display: flex; flex-direction: column; gap: 0.75rem; }
.gmain {
  aspect-ratio: 1 / 1; position: relative; border-radius: 16px; overflow: hidden;
  border: 1px solid var(--line); background: var(--ink-3); cursor: zoom-in;
  /* Same 1:1 frame + contain treatment as the shop card, just bigger. No-JS safety net: never
     taller than the viewport minus the header and a little breathing room. fitGallery() in
     product.html then sets an exact width+height that keeps the 1:1 shape while fitting the
     available space. width:auto lets the frame narrow when height is the limit. */
  max-height: calc(100dvh - var(--nav-h) - 5rem);
  max-width: 100%;
  width: auto;
  margin-inline: auto;
}
.gmain .gmedia { position: absolute; inset: 0; }
.gmain .gmedia img,
.gmain .gmedia video {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: contain; display: block; background: #0e0e0e;
}
.gmain .ph {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-family: var(--mono); font-size: 0.6rem; letter-spacing: 0.14em;
  color: var(--faint); text-transform: uppercase;
}
.gthumbs { display: flex; gap: 0.6rem; }
.gthumb {
  flex: 1; aspect-ratio: 1 / 1; border-radius: 11px; overflow: hidden;
  border: 1px solid var(--line); cursor: pointer; position: relative;
  transition: border-color 0.16s; background: var(--ink-2); padding: 0;
}
.gthumb img { width: 100%; height: 100%; object-fit: contain; display: block; }
.gthumb:hover { border-color: var(--line-2); }
.gthumb.on { border-color: var(--lime); }
.gthumb .play {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  color: var(--off); pointer-events: none;
}
.gthumb .play svg { width: 26%; height: 26%; filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.6)); }

/* ---- lightbox ---- */
.lightbox {
  position: fixed; inset: 0; z-index: 300; display: none;
  align-items: center; justify-content: center; padding: 3vh 3vw;
}
.lightbox.open { display: flex; }
.lb-bg { position: absolute; inset: 0; background: rgba(4, 4, 4, 0.92); backdrop-filter: blur(6px); }
.lb-media {
  position: relative; z-index: 1; display: flex; align-items: center; justify-content: center;
  max-width: 92vw; max-height: 92vh;
}
.lb-media img, .lb-media video {
  max-width: 92vw; max-height: 92vh; width: auto; height: auto;
  display: block; border-radius: 8px; background: var(--ink-2);
}
.lb-close {
  position: absolute; top: 14px; right: 16px; z-index: 3; width: 42px; height: 42px;
  border-radius: 50%; border: 1px solid var(--line-2); background: rgba(11, 11, 11, 0.6);
  color: var(--off); cursor: pointer; font-size: 1.3rem; line-height: 1; transition: 0.16s;
}
.lb-close:hover { border-color: var(--lime); color: var(--lime); }
.lb-arw {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 3;
  width: 46px; height: 46px; border-radius: 50%; border: 1px solid var(--line-2);
  background: rgba(11, 11, 11, 0.55); backdrop-filter: blur(6px); color: var(--off);
  cursor: pointer; display: flex; align-items: center; justify-content: center;
  font-size: 1.5rem; line-height: 1; transition: 0.16s;
}
.lb-arw:hover { border-color: var(--lime); color: var(--lime); }
.lb-arw.prev { left: 2vw; }
.lb-arw.next { right: 2vw; }
.lb-counter {
  position: absolute; top: 18px; left: 18px; z-index: 3;
  font-family: var(--mono); font-size: 0.66rem; letter-spacing: 0.1em; color: var(--muted);
}
.lb-dots {
  position: absolute; left: 0; right: 0; bottom: 18px; z-index: 3;
  display: flex; gap: 8px; justify-content: center;
}
.lb-dot {
  width: 8px; height: 8px; border-radius: 50%; background: rgba(255, 255, 255, 0.35);
  border: 0; cursor: pointer; padding: 0; transition: 0.16s;
}
.lb-dot.on { background: var(--lime); transform: scale(1.25); }
.lb-dot:hover { background: rgba(255, 255, 255, 0.8); }

/* Admin customs lightbox: the two nav arrows sit along the bottom (per the Custom Orders spec),
   reusing the round .lb-arw look but reset out of their side-anchored absolute position. */
.lb-nav { position: absolute; left: 0; right: 0; bottom: 3vh; z-index: 3; display: flex; gap: 1.4rem; align-items: center; justify-content: center; }
.lb-nav .lb-arw { position: static; transform: none; }
.lb-count { font-family: var(--mono); font-size: 0.72rem; letter-spacing: 0.1em; color: var(--muted); min-width: 3.5em; text-align: center; }

/* ---- details, from the product folder's markdown body ---- */
.details { margin-top: 1.9rem; border-top: 1px solid var(--line); padding-top: 1.2rem; }
.details h4 {
  font-family: var(--mono); font-size: 0.62rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--faint); font-weight: 400; margin-bottom: 0.6rem;
}
.details p { color: var(--muted); font-size: 0.9rem; margin-bottom: 0.7rem; }
.details p:last-child { margin-bottom: 0; }

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

/* ---- admin app shell (sidebar + content) ---- */
.ashell { display: grid; grid-template-columns: 256px 1fr; min-height: 100vh; min-height: 100dvh; }
.aside {
  position: sticky; top: 0; align-self: start; height: 100vh; height: 100dvh;
  display: flex; flex-direction: column; gap: 1.2rem;
  padding: 1.4rem 1rem; border-right: 1px solid var(--line);
  background: var(--ink-2); overflow-y: auto;
}
.aside-brand { padding: 0.3rem 0.55rem 0.5rem; }
/* The burger exists only below 900px -- see the block at the end of this section. */
.aside-burger { display: none; }
/* Everything the burger collapses: the nav and the user block together.
   ON DESKTOP THIS MUST CARRY THE COLUMN LAYOUT ITSELF. .aside-foot pins to the bottom with
   margin-top:auto, and an auto margin resolves against its own flex container -- so the moment
   these two were wrapped in a panel, that container stopped being .aside and the user block
   stopped sticking to the bottom of the sidebar. The gap moved here for the same reason: it used
   to sit on .aside and separate brand / nav / foot, and after the wrap it would have separated
   brand / panel only, collapsing the space between the nav and the user block. */
.aside-panel { display: flex; flex-direction: column; flex: 1; min-height: 0; gap: 1.2rem; }
.aside-nav { display: flex; flex-direction: column; gap: 0.25rem; }
.aside-group {
  font-family: var(--mono); font-size: 0.58rem; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--faint); padding: 0.55rem 0.7rem 0.35rem;
}
.aside-item {
  display: flex; align-items: center; gap: 0.7rem;
  padding: 0.62rem 0.7rem; border-radius: 10px;
  color: var(--muted); font-size: 0.94rem; font-weight: 600;
  border: 1px solid transparent; transition: 0.15s;
}
.aside-item:hover { color: var(--off); background: var(--ink-3); }
.aside-item.on {
  color: var(--off); background: rgba(140, 184, 30, 0.10);
  border-color: rgba(140, 184, 30, 0.30);
}
.aside-ic { width: 18px; height: 18px; flex-shrink: 0; display: inline-flex; color: var(--lime); }
.aside-ic svg { width: 100%; height: 100%; display: block; }
/* How many orders or briefs have arrived since this browser last loaded that list. Pushed to the
   far edge by an auto margin so it sits at the end of the item whatever the label length. */
.aside-badge {
  margin-left: auto; flex-shrink: 0;
  min-width: 20px; height: 20px; padding: 0 6px; border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--lime); color: var(--lime-ink);
  font-family: var(--mono); font-size: 0.68rem; font-weight: 700; line-height: 1;
}
/* The reload offer above a list. Deliberately not a count: the list can be filtered by search and
   by period, so a number here would promise rows a reload need not surface. The count lives on the
   sidebar badge, where it is unconditional. */
.newbar {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap;
  margin: -0.5rem 0 1.1rem; padding: 0.6rem 0.9rem;
  border-left: 3px solid var(--lime); background: rgba(140, 184, 30, 0.08);
  color: var(--off); font-size: 0.86rem;
}
.aside-foot {
  margin-top: auto; display: flex; align-items: center; gap: 0.6rem;
  padding-top: 1rem; border-top: 1px solid var(--line);
}
.aside-user { display: flex; align-items: center; gap: 0.6rem; flex: 1; min-width: 0; }
.aside-mono {
  width: 34px; height: 34px; flex-shrink: 0; border-radius: 26%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--lime); color: var(--lime-ink);
  font-family: var(--mono); font-weight: 700; font-size: 0.95rem;
}
.aside-name {
  color: var(--off); font-size: 0.9rem; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.aside-foot #signout { flex-shrink: 0; }
.amain { padding: 2.6rem 2.6rem 4rem; min-width: 0; }
.ahead { color: var(--off); font-size: 2.15rem; font-weight: 700; letter-spacing: -0.01em; margin: 0.25rem 0 2rem; }
.amain .kick { margin-bottom: 0.35rem; }

/* Below 900px the sidebar becomes a bar with a burger, matching the storefront's own pattern.
   It used to flip to flex-direction:row with flex-wrap:wrap, which is a 256px vertical sidebar
   relabelled as a bar: nothing shortened, so "Shop Orders" and "Custom Orders" broke onto two
   lines each, the user block claimed a row of its own, and roughly 330px of chrome sat above the
   first figure on a phone.

   THE BURGER HAS ITS OWN CLASS, deliberately. Reusing .nav-burger would inherit the storefront's
   own rule, which only turns it on at 560px -- so the admin burger would be invisible between
   560 and 900, which is exactly the range where the old layout was already breaking. */
@media (max-width: 900px) {
  .ashell { grid-template-columns: 1fr; }
  .aside {
    /* Sticky, because the orders list is long and a nav you have to scroll back up to reach is a
       nav you stop using. z-index 40 is chosen, not arbitrary: it clears the sticky table headers
       inside .panel-scroll at 1, the sort menus at 20 and the stage pickers at 30, while staying
       well under the reference-image lightbox at 300 so opening a photo still covers the bar. */
    position: sticky; top: 0; z-index: 40;
    /* height and overflow BOTH reset. The base rule sets height:100dvh and overflow-y:auto, and
       when one overflow axis is auto the other is promoted from visible to auto -- so the bar grew
       a horizontal scrollbar of its own, which is the grey strip that used to sit under it. */
    height: auto; overflow: visible;
    flex-direction: row; flex-wrap: nowrap; align-items: center;
    gap: 0.7rem; padding: 0.75rem 1rem;
    border-right: 0; border-bottom: 1px solid var(--line);
  }
  /* Brand left, burger hard right. */
  .aside-brand { padding: 0; margin-right: auto; }
  .aside-burger {
    display: flex; align-items: center; justify-content: center;
    width: 44px; height: 44px; flex: none;
    background: transparent; border: 0; border-radius: 10px;
    color: var(--off); cursor: pointer;
  }
  .aside-burger svg { width: 22px; height: 22px; }
  .aside-burger:hover { color: var(--lime); }
  .aside-burger:focus-visible { outline: 2px solid var(--lime); outline-offset: 2px; }
  /* Below this width the sidebar items are inside the collapsed menu, so their badges are
     invisible until the burger is opened. Without a mark on the burger itself the whole feature
     would do nothing on a phone. A dot rather than a number: the count is one tap away, and a
     numeral on a 44px control is noise. */
  .aside-burger { position: relative; }
  .aside-burger.has-unseen::after {
    content: ""; position: absolute; top: 8px; right: 8px;
    width: 9px; height: 9px; border-radius: 50%;
    background: var(--lime); border: 2px solid var(--ink-2);
  }

  /* A contained card hanging off the burger, the same shape the storefront panel uses, rather
     than an edge-to-edge band that reads as a second navigation bar. display:none rather than a
     visual collapse, so the items leave the keyboard tab order too. */
  .aside-panel[data-collapsible] {
    display: none;
    position: absolute;
    top: calc(100% + 0.5rem); right: 1rem; left: auto;
    width: max-content; min-width: 220px; max-width: min(78vw, 320px);
    flex: none; gap: 0;
    /* Horizontal padding is deliberately small. The storefront card pads 1rem and zeroes its
       links; here .aside-item keeps its own 0.7rem because the active item is a filled pill and
       zeroing it would crop that pill to the text. Stacking both would inset the labels 1.45rem
       from the card edge, so the card gives way instead and the effective inset lands at the same
       1rem the storefront uses. */
    padding: 0.4rem 0.35rem 0.5rem;
    background: var(--ink-2); border: 1px solid var(--line);
    border-radius: 16px; box-shadow: 0 14px 34px rgba(0, 0, 0, 0.55);
  }
  .aside.menu-open .aside-panel[data-collapsible] { display: flex; }
  /* One group heading above four items reads as noise inside a dropdown. */
  .aside-group { display: none; }
  /* Comfortable thumb targets, matching the storefront panel's own minimum. */
  .aside-item { min-height: 44px; }
  /* Inside the card the user block is the last section rather than a pinned footer, so the auto
     margin has nothing to do and the rule above it is what separates the two. */
  .aside-foot { margin-top: 0.4rem; padding-top: 0.7rem; border-top: 1px solid var(--line); }
  .amain { padding: 1.9rem 1.3rem 3rem; }
  .ahead { font-size: 1.8rem; }
}

/* ---- admin orders view (accordion pills) ---- */
/* ---- admin orders search ----
   Reuses the storefront tracker's input treatment rather than inventing a second field style:
   --ink-2 ground, --line-2 hairline, lime focus border. The clear button only appears when there
   is something to clear. Font-size is bumped to 16px on phones because anything smaller makes iOS
   zoom the whole page on focus. */
.osearch { margin: -1rem 0 1.4rem; }
.osearch-box { position: relative; display: flex; align-items: center; }
.osearch-ic { position: absolute; left: 0.85rem; width: 16px; height: 16px; color: var(--faint); pointer-events: none; }
.osearch-input {
  width: 100%; background: var(--ink-2); border: 1px solid var(--line-2); border-radius: 10px;
  color: var(--off); font-family: var(--sans); font-size: 0.88rem;
  padding: 0.7rem 2.4rem 0.7rem 2.5rem; transition: border-color 0.15s;
}
/* A dedicated colour, deliberately NOT the shared --faint token. This placeholder is the only
   SIGHTED indication of what the field searches -- there is no visible label, just an aria-label
   that screen readers get and sighted users do not -- so it carries instruction rather than
   decoration and has to clear 4.5:1 like any other body text. --faint measured 3.65:1 against the
   rgb(18,18,18) actually painted behind this field, at 14px, which fails AA. This value measures
   4.98:1. Changing --faint itself would have been the smaller diff and the worse change: it would
   have repainted every muted string on the storefront AND the dashboard, which is exactly the
   shared-token blast radius this codebase has been bitten by before. */
.osearch-input::placeholder { color: #84847d; }
.osearch-input:focus { outline: none; border-color: var(--lime); }
/* the native search widget's own clear affordance would sit beside ours */
.osearch-input::-webkit-search-cancel-button { display: none; }
.osearch-clear {
  position: absolute; right: 0.6rem; display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; padding: 0; background: transparent; border: 0; border-radius: 999px;
  color: var(--faint); cursor: pointer; transition: color 0.15s, background 0.15s;
}
.osearch-clear:hover { color: var(--off); background: var(--ink-3); }
.osearch-clear:focus-visible { outline: none; color: var(--off); box-shadow: 0 0 0 2px var(--ink-2), 0 0 0 4px var(--lime); }
.osearch-count { margin: 0.55rem 0 0; font-family: var(--mono); font-size: 0.68rem; letter-spacing: 0.05em; text-transform: uppercase; color: var(--faint); }

/* The chase-queue toggle, sitting beside Download Excel in the header row.
   Tokens MATCH .headbtn rather than .tab, because that is what it stands next to: the same 9px
   radius and the same calc(0.4rem + 3px) padding, which is what lands both controls on one height.
   It was briefly styled from .tab's smaller metrics while it lived under the search box, and a
   6px pill beside a 9px one reads as a mistake rather than a pair.
   Lime when active, matching .tab.on -- unlike .headbtn this IS a state, so the fill is right. */
.ofilter {
  background: var(--ink-2); border: 1px solid var(--line-2); border-radius: 9px;
  color: var(--muted); cursor: pointer;
  padding: calc(0.4rem + 3px) calc(0.7rem + 3px);
  font-family: inherit; font-size: 0.78rem; font-weight: 600; white-space: nowrap;
  transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.ofilter:hover { color: var(--off); border-color: var(--faint); }
.ofilter.on { background: var(--lime); border-color: var(--lime); color: var(--lime-ink); font-weight: 700; }
.ofilter.on:hover { color: var(--lime-ink); }
/* Matching .headbtn's ring rather than .osearch-clear's, for the same reason the metrics match. */
.ofilter:focus-visible { outline: 2px solid var(--lime); outline-offset: 2px; }

/* Export and filter as one unit. .page-head is space-between, so leaving them as two loose
   children would redistribute the row and push the period tabs off the right edge. align-items
   centre so the pair share a midline even if one wraps to two lines at a narrow width. */
.head-actions { display: inline-flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
.osearch-all { margin-top: 0.9rem; }
@media (max-width: 800px) {
  .osearch-input { font-size: 16px; }
}

/* Both pill lists share ONE rule. The spacing lives on the CONTAINER as a flex gap, never as a
   margin on .opill: a gap applies itself to every child including ones appended later by Load
   more, needs no last-child exception, and does not change when a pill expands. A margin-based
   version would have to be re-stated for each new list and would leave a trailing space after the
   final pill. Custom Orders was missing this rule entirely and its pills rendered flush against
   each other at 0px while Shop Orders had 11.2px -- measured, then fixed by sharing the selector
   so the two lists cannot drift apart again. */
#orders-list, #customs-list { display: flex; flex-direction: column; gap: 0.7rem; margin-top: 0.4rem; }
.opill { background: var(--ink-2); border: 1px solid var(--line); border-radius: 24px; overflow: hidden; transition: border-color 0.15s; }
.opill.open { border-color: var(--line-2); }
.opill-head {
  width: 100%; display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; padding: 0.95rem 1.4rem; background: transparent; border: 0;
  color: var(--text); cursor: pointer; text-align: left; font-family: inherit; font-size: 0.92rem;
}
.opill-head:hover { background: var(--ink-3); }
.opill-left { display: flex; align-items: center; gap: 0.85rem; min-width: 0; flex: 1; }
.opill-num { font-family: var(--mono); font-size: 0.74rem; color: var(--faint); min-width: 1.4rem; text-align: right; flex-shrink: 0; }
.opill-thumb { width: 34px; height: 34px; border-radius: 9px; background: var(--ink-3); border: 1px solid var(--line-2); position: relative; overflow: hidden; flex-shrink: 0; display: grid; place-items: center; color: var(--faint); }
.opill-thumb img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.opill-ph { width: 17px; height: 17px; opacity: 0.6; }
.opill-basic { display: flex; align-items: center; gap: 0.6rem; min-width: 0; flex-wrap: wrap; }
.opill-name { color: var(--off); font-weight: 600; }
.opill-prod { color: var(--text); font-weight: 600; }
.opill-sep { color: var(--faint); }
.opill-sz { font-family: var(--mono); font-size: 0.72rem; color: var(--faint); border: 1px solid var(--line-2); border-radius: 999px; padding: 0.08rem 0.5rem; }
.opill-right { display: flex; align-items: center; gap: 0.9rem; flex-shrink: 0; }
.opill-amt { font-family: var(--mono); color: var(--off); font-weight: 700; white-space: nowrap; }
.opill-date { font-family: var(--mono); font-size: 0.72rem; color: var(--faint); white-space: nowrap; }
.opill-refunded { font-family: var(--mono); font-size: 0.62rem; letter-spacing: 0.05em; text-transform: uppercase; color: var(--down); border: 1px solid var(--down); border-radius: 999px; padding: 0.1rem 0.45rem; }
.opill-chev { color: var(--faint); display: inline-flex; transition: transform 0.18s, color 0.18s; }
.opill.open .opill-chev { transform: rotate(180deg); color: var(--lime); }
.opill-detail { padding: 0 1.4rem 1.3rem; border-top: 1px solid var(--line); }

/* ---- fulfillment stage timeline (admin order detail) ----
   The stage is the thing this panel exists to change, so it leads rather than hiding in a field,
   and it mirrors exactly what the customer sees on the tracker. Each stage is a real button:
   keyboard reachable, labelled for screen readers, and carrying aria-current. */
.otl { display: flex; gap: 0.4rem; padding: 1.1rem 0 0.2rem; }
.otl[aria-busy="true"] { opacity: 0.55; pointer-events: none; }
.otl-step { flex: 1 1 0; min-width: 0; position: relative; background: transparent; border: 0; padding: 0 0.1rem; cursor: pointer; font-family: var(--sans); text-align: center; }
/* The connector runs from this dot to the next one; the last stage has nothing to connect to. */
.otl-step::before { content: ""; position: absolute; top: 6px; left: 50%; right: -50%; height: 2px; background: var(--line-2); }
.otl-step:last-child::before { display: none; }
.otl-step.linked::before { background: var(--lime); }
.otl-dot { display: block; width: 14px; height: 14px; border-radius: 50%; border: 2px solid var(--line-2); background: var(--ink-2); margin: 0 auto 0.55rem; position: relative; z-index: 1; box-sizing: border-box; transition: 0.15s; }
.otl-step.done .otl-dot { border-color: var(--lime); background: var(--lime); }
.otl-step.current .otl-dot { box-shadow: 0 0 0 3px rgba(140, 184, 30, 0.25); }
.otl-lab { display: block; font-size: 0.68rem; line-height: 1.25; color: var(--faint); }
.otl-step.done .otl-lab { color: var(--text); }
.otl-step.current .otl-lab { color: var(--lime); font-weight: 700; }
.otl-step:hover:not(.current) .otl-lab { color: var(--off); }
/* This codebase sets outline:none everywhere, which leaves keyboard users with nothing to follow on
   a 14px circle. These controls get a real focus ring instead. */
.otl-step:focus-visible { outline: none; }
.otl-step:focus-visible .otl-dot { box-shadow: 0 0 0 2px var(--ink-2), 0 0 0 4px var(--lime); }
.otl-hint { font-family: var(--mono); font-size: 0.62rem; color: var(--faint); margin: 0.55rem 0 0; text-align: right; }
.otl-hint.ok { color: var(--lime); }
.otl-hint.err { color: var(--down); }

/* ---- ruled detail rows ----
   Label and value on a hairline, two columns. Denser and faster to scan than boxes when several
   orders are open at once, which is the normal way this screen is used. */
.orows { display: grid; grid-template-columns: repeat(auto-fit, minmax(290px, 1fr)); gap: 0 2.2rem; padding-top: 0.6rem; }
.orow { display: flex; gap: 1rem; padding: 0.55rem 0; border-bottom: 1px solid rgba(255, 255, 255, 0.07); }
.orow-wide { grid-column: 1 / -1; }
.orow-lab { font-family: var(--mono); font-size: 0.6rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--faint); width: 78px; flex: none; padding-top: 0.2rem; }
.orow-val { font-size: 0.86rem; color: var(--text); line-height: 1.5; overflow-wrap: anywhere; min-width: 0; flex: 1; }
.orow-val .oaddr, .orow-val p { margin: 0; }
.orow-ref { font-family: var(--mono); font-size: 0.95rem; color: var(--off); letter-spacing: 0.05em; }
/* addrLines() emits an <address>, which browsers italicise by default -- hence font-style: normal.
   Both pills now render through .orows, so the old .odetail grid rules are gone; these three
   helpers survive because addrLines and the rows still use them. */
.oaddr { color: var(--text); font-style: normal; line-height: 1.55; overflow-wrap: anywhere; }
.osame { color: var(--lime); font-size: 0.84rem; }
.omuted { color: var(--faint); }
.opill-status { font-family: var(--mono); font-size: 0.62rem; letter-spacing: 0.05em; text-transform: uppercase; color: var(--lime); border: 1px solid var(--lime); border-radius: 999px; padding: 0.1rem 0.5rem; white-space: nowrap; }
.opill-status[data-stat="delivered"] { color: var(--lime-ink); background: var(--lime); }
/* The admin stage picker reuses the storefront's themed listbox, just denser to suit a table row.
   A native select cannot restyle its option popup, so the platform uses .fld-select everywhere a
   dropdown has to look like it belongs to the shop. */
.ostatus-dd { max-width: 230px; }
.ostatus-dd .fld-select-trigger { padding: 0.42rem 0.65rem; font-size: 0.84rem; border-radius: 8px; border-color: var(--line-2); }
.ostatus-dd .fld-select-trigger:disabled { opacity: 0.6; cursor: default; }
.ostatus-dd .fld-select-opt { font-size: 0.84rem; padding: 0.45rem 0.6rem; }
/* .opill clips its overflow so its 24px corners stay clean, which would also slice the open menu
   in half. The clip is lifted only while a menu is open. */
.opill.menu-open { overflow: visible; }
.ostatus-note { color: var(--faint); font-size: 0.7rem; }

/* ---- track order (storefront) ---- */
.trk-form { margin-bottom: 1.4rem; }
.trk-field { margin-bottom: 1.1rem; }
.trk-field label { display: block; font-family: var(--mono); font-size: 0.62rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--off); font-weight: 700; margin-bottom: 0.45rem; }
.trk-input { width: 100%; background: var(--ink-2); border: 1px solid var(--line-2); border-radius: 10px; color: var(--off); font-family: var(--sans); font-size: 0.95rem; padding: 0.7rem 0.85rem; }
.trk-input::placeholder { color: var(--faint); }
.trk-input:focus { outline: none; border-color: var(--lime); }
/* Chrome paints its own pale background on autofilled fields, which overrides background-color and
   blows a white box into the dark form. The background itself is not overridable, so the standard
   workaround is a huge inset box-shadow in our own colour, plus text-fill-color for the text. Same
   pattern as the .field inputs on the custom page -- see that block above. */
.trk-input:-webkit-autofill,
.trk-input:-webkit-autofill:hover,
.trk-input:-webkit-autofill:focus,
.trk-input:-webkit-autofill:active {
  -webkit-text-fill-color: var(--off);
  -webkit-box-shadow: 0 0 0 1000px var(--ink-2) inset;
  box-shadow: 0 0 0 1000px var(--ink-2) inset;
  caret-color: var(--off);
  border: 1px solid var(--line-2);
  transition: background-color 100000s ease-in-out 0s;
}
.trk-input:-webkit-autofill:focus { border-color: var(--lime); }
.trk-input:autofill {
  -webkit-text-fill-color: var(--off);
  box-shadow: 0 0 0 1000px var(--ink-2) inset;
  caret-color: var(--off);
}
.trk-err { color: var(--down); font-size: 0.86rem; margin-top: 1rem; }
.trk-steps { list-style: none; margin: 0; padding: 0.3rem 0 0; }
.trk-step { position: relative; display: flex; align-items: center; gap: 0.95rem; padding-bottom: 1.5rem; }
.trk-step:last-child { padding-bottom: 0; }
.trk-step::before { content: ""; position: absolute; left: 7px; top: 16px; bottom: 0; width: 2px; background: var(--line-2); }
.trk-step:last-child::before { display: none; }
.trk-step.done:not(.current)::before { background: var(--lime); }
.trk-dot { position: relative; z-index: 1; width: 16px; height: 16px; border-radius: 50%; border: 2px solid var(--line-2); background: var(--ink); flex-shrink: 0; }
.trk-step.done .trk-dot { border-color: var(--lime); background: var(--lime); }
.trk-lab { font-size: 0.95rem; color: var(--muted); }
.trk-step.done .trk-lab { color: var(--text); }
.trk-step.current .trk-lab { color: var(--lime); font-weight: 700; }
/* ---- thank-you order reference card ---- */
.refcard { text-align: center; }

/* The above-the-fold action cue on the thank-you page. One lime line under the heading so the
   customer knows there is a step to take before they scroll -- the booking panel is directly
   below it. Kept small and quiet: it points, it does not shout. Margin is deliberately tight (the
   whole top of this page was trimmed so the booking card clears the fold on a phone). */
.thanks-cue { margin: 0 0 1rem; font-family: var(--mono); font-size: 0.8rem; letter-spacing: 0.04em;
  color: var(--lime); }

/* How-it-works strip on the thank-you page. Four steps, horizontal on desktop with a lime
   connector line threading the numbered badges, stacking to a vertical list on narrow screens.
   Built in the same idiom as .otl-step / .trk-step (numbered dot, lime connector) but kept under
   its own .hiw- prefix so it cannot collide with the order timeline's live/current/done state
   classes. The connector is drawn behind the badges and clipped at the first and last so it does
   not float past the ends. */
.hiw { display: flex; gap: 0; margin: 0.4rem 0 0; padding: 0; list-style: none; }
.hiw-step { flex: 1 1 0; min-width: 0; position: relative; text-align: center; padding: 0 0.35rem; }
.hiw-step::before { content: ""; position: absolute; top: 15px; left: -50%; width: 100%; height: 2px;
  background: var(--line-2); z-index: 0; }
.hiw-step:first-child::before { display: none; }
.hiw-num { position: relative; z-index: 1; display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: 50%; background: var(--lime); color: var(--lime-ink);
  font-family: var(--mono); font-size: 0.82rem; font-weight: 700; }
.hiw-lab { margin: 0.55rem 0 0.2rem; font-size: 0.82rem; font-weight: 700; color: var(--off); line-height: 1.3; }
.hiw-sub { margin: 0; font-size: 0.72rem; color: var(--muted); line-height: 1.4; }
.hiw-note { margin: 1.2rem 0 0; font-size: 0.8rem; color: var(--faint); line-height: 1.55; text-align: center; }
@media (max-width: 560px) {
  /* Compact the stacked strip so the "Choose a time" button clears a 667px phone fold. Measured:
     the loose layout stacked to 235px and the button bottom landed at 733px (66px over); tightening
     the gap and step rhythm here, together with one-line sub-text, brings the strip to ~166px and
     the button bottom to 655px -- 12px of clearance, a margin rather than a knife-edge. The sub-text
     copy is kept short enough to sit on one line at 380px; each two-line wrap adds 16px back. */
  .hiw { flex-direction: column; gap: 0.65rem; text-align: left; }
  .hiw-step { display: grid; grid-template-columns: 30px 1fr; column-gap: 0.85rem; align-items: start;
    text-align: left; padding: 0; }
  .hiw-step::before { top: 30px; left: 15px; width: 2px; height: calc(100% + 0.65rem); }
  .hiw-step:first-child::before { display: none; }
  .hiw-num { grid-row: span 2; }
  .hiw-lab { margin: 0 0 0.1rem; grid-column: 2; }
  .hiw-sub { grid-column: 2; line-height: 1.3; }
  /* On a phone the strip stacks tall (~235px), which pushes the "Choose a time" button below the
     fold if the prep note sits between them. Measured: with the note in flow the button lands at
     854px; moving it below the button lifts the button into the opening view. Flex order does this
     without changing the source, so desktop (where the strip is one short horizontal row) keeps
     the natural reading order strip -> note -> button. */
  .booking-panel { display: flex; flex-direction: column; }
  .booking-panel h3 { order: 0; }
  .booking-panel .hiw { order: 1; }
  .booking-panel #cal { order: 2; }
  .booking-panel .hiw-note { order: 3; margin-top: 1.3rem; }
}

.refcard-ref { font-family: var(--mono); font-size: 1.75rem; font-weight: 700; letter-spacing: 0.09em; color: var(--off); margin: 0.15rem 0 0; }
.refcard-ref.pending { font-size: 1rem; font-weight: 400; letter-spacing: 0.03em; color: var(--faint); }
.refcard-save { display: inline-flex; align-items: center; gap: 6px; background: transparent; border: 0; padding: 0.4rem 0.2rem; margin-top: 0.5rem; color: var(--faint); font-family: var(--sans); font-size: 0.74rem; cursor: pointer; transition: color 0.15s; }
.refcard-actions { display: flex; justify-content: center; gap: 1.4rem; flex-wrap: wrap; }
.refcard-save:hover:not(:disabled) { color: var(--lime); }
.refcard-save:disabled { opacity: 0.55; cursor: default; }
.refcard-btn { display: block; max-width: 230px; margin: 1.15rem auto 0; text-align: center; }

/* The hidden attribute must always win. The UA stylesheet's [hidden] rule is weaker than any
   author class that sets display, so a class like .refcard-save { display: inline-flex } silently
   defeats it and the element stays on screen with its property reading hidden === true. That is
   the "reports success while being wrong" pattern in CSS form, so it is closed here once for the
   whole site rather than per component. */
[hidden] { display: none !important; }

/* ================================================================================
   PHONE. Most customers arrive on a phone, so this is the common case, not an edge one.
   Two real problems are fixed here, both measured rather than guessed:

   1. The nav overflowed. Brand plus the department toggle plus the links needed about 474px of
      a 375px viewport, so EVERY page scrolled sideways. Wrapping the links onto their own row
      fixes it without hiding any control -- nothing is removed, it just stacks.
   2. Inputs were 14.7-15.2px. Below 16px, iOS Safari zooms the whole page in when a field is
      focused and does not zoom back out, which is why so many mobile forms feel broken. Every
      text field goes to 16px on small screens; the visual size barely changes, the zoom stops.
   ================================================================================ */
@media (max-width: 560px) {
  .nav-in { flex-wrap: wrap; padding: 0.75rem 1rem; gap: 0.55rem; }
  .brand b { font-size: 0.88rem; }
  .brand .mark { width: 26px; height: 26px; }
  /* Brand and department toggle stay TOGETHER on the left; the auto margin after the toggle sends
     the burger to the right edge. Previously this was margin-left:auto on the toggle, which shoved
     it to the right and left the logo stranded alone. */
  .ctx-switch { margin-left: 0.55rem; margin-right: auto; }
  .nav-burger {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    flex: none;
    background: transparent;
    border: 0;
    border-radius: 10px;
    color: var(--off);
    cursor: pointer;
  }
  .nav-burger svg { width: 22px; height: 22px; }
  .nav-burger:hover { color: var(--lime); }

  /* The links become a panel under the bar, positioned against .nav -- which is sticky, so it is
     already a containing block.
     SCOPED TO [data-collapsible], and that scoping is load-bearing: 404.html carries a STATIC
     two-link nav with the same .nav-links class and NO burger. Collapsing that one would hide its
     only navigation behind a control that does not exist on the page.
     display:none rather than a visual collapse, so the links leave the keyboard tab order too --
     a hidden link that can still be tabbed to is worse than no link. */
  .nav-links[data-collapsible] {
    display: none;
    position: absolute;
    /* A contained card, not a full-bleed strip. It hangs from the burger it belongs to, which is
       what makes the relationship between control and panel legible; an edge-to-edge band reads
       as a second navigation bar instead. Inset 1rem to match .nav-in's own padding, so its right
       edge lines up with the burger above it rather than floating at an arbitrary offset. */
    top: calc(100% + 0.5rem);
    right: 1rem;
    left: auto;
    width: max-content;
    min-width: 200px;
    /* Capped so a long label can never push it off the left of a small screen. */
    max-width: min(78vw, 320px);
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: 0.4rem 1rem 0.5rem;
    background: var(--ink-2);
    border: 1px solid var(--line);
    border-radius: 16px;
    box-shadow: 0 14px 34px rgba(0, 0, 0, 0.55);
  }
  .nav.menu-open .nav-links[data-collapsible] { display: flex; }
  .nav-links[data-collapsible] a,
  .nav-links[data-collapsible] .instl-nav {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    min-height: 44px;
    font-size: 0.95rem;
    padding: 0;
    text-align: left;
  }

  /* 16px minimum: stops iOS zooming on focus. */
  .trk-input, .field input, .field select, .field textarea { font-size: 16px; }

  /* Comfortable thumb targets. */
  .refcard-save { padding: 0.6rem 0.75rem; font-size: 0.8rem; }
  .refcard-ref { font-size: 1.55rem; }
  .refcard-btn { max-width: 100%; }

  /* Admin order rows. The right-hand cluster does not shrink and .opill clips its overflow, so on
     a phone the status pill, amount and date were cut off with no way to scroll to them -- the
     founder updating an order from a phone could not see the status at all. Give that cluster its
     own row instead of squeezing it. */
  .opill { border-radius: 18px; }
  .opill-head { flex-wrap: wrap; gap: 0.5rem; padding: 0.85rem 1rem; }
  .opill-left { flex: 1 1 100%; }
  .opill-right { flex: 1 1 100%; justify-content: flex-end; flex-wrap: wrap; gap: 0.55rem; }
  .opill-detail { padding: 0 1rem 1.1rem; }

  /* Six stages side by side need about 500px to stay readable, so below that the timeline turns
     vertical -- the same shape the customer sees on the tracker, and it turns six cramped targets
     into full-width rows that are easy to hit with a thumb. */
  .otl { flex-direction: column; gap: 0; padding: 0.9rem 0 0.2rem; }
  .otl-step { display: flex; align-items: center; gap: 0.85rem; text-align: left; padding: 0.5rem 0; min-height: 44px; box-sizing: border-box; }
  .otl-step::before { top: 50%; bottom: -50%; left: 6px; right: auto; width: 2px; height: auto; }
  .otl-dot { margin: 0; flex: none; }
  .otl-lab { font-size: 0.82rem; }
  .otl-hint { text-align: left; }
  .orows { grid-template-columns: 1fr; gap: 0; }
  .orow-lab { width: 68px; }
}
.oload-wrap { text-align: center; margin-top: 1.4rem; }

/* ---- admin revenue view (strategic breakdown bars) ---- */
.rv-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.5rem; margin-bottom: 1.5rem; }
.rv-head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.6rem; margin-bottom: 1.1rem; }
.rv-head h3 { margin: 0; }
.rv-cap { font-family: var(--mono); font-size: 0.56rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--faint); white-space: nowrap; }
.rv-row { margin-bottom: 1.05rem; }
.rv-row:last-child { margin-bottom: 0; }
.rv-lab { display: flex; align-items: baseline; justify-content: space-between; gap: 0.6rem; margin-bottom: 0.42rem; }
.rv-name { color: var(--text); font-size: 0.9rem; }
.rv-vals { display: flex; align-items: baseline; gap: 0.5rem; white-space: nowrap; }
.rv-val { font-family: var(--mono); font-weight: 700; font-size: 0.86rem; color: var(--off); }
.rv-pct { font-family: var(--mono); font-size: 0.72rem; color: var(--faint); }
.rv-bar { height: 9px; border-radius: 6px; background: var(--ink-3); overflow: hidden; }
.rv-bar > i { display: block; height: 100%; width: 0; background: var(--lime); border-radius: 6px; transition: width 0.35s ease; }
.rv-bar.alt > i { background: var(--down); }
@media (max-width: 760px) { .rv-grid { grid-template-columns: 1fr; } }

/* ---- admin revenue-by-product table (dashboard) ---- */
.bp-table { min-width: 560px; }
.bp-table td { vertical-align: middle; }
.bp-name { color: var(--off); font-weight: 700; }
.bp-dept { font-family: var(--mono); font-size: 0.6rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); border: 1px solid var(--line-2); border-radius: 999px; padding: 0.14rem 0.5rem; white-space: nowrap; }
.bp-rev { color: var(--lime); font-weight: 700; }
.bp-share { display: inline-flex; flex-direction: column; align-items: flex-end; gap: 4px; }
.bp-pct { color: var(--muted); }
.bp-bar { width: 130px; max-width: 100%; height: 5px; border-radius: 5px; background: var(--ink-3); overflow: hidden; }
.bp-bar > i { display: block; height: 100%; width: 0; background: var(--lime); border-radius: 5px; transition: width 0.35s ease; }

/* ---- fixed-height scroll container with themed scrollbar + sticky header (dashboard tables) ---- */
.panel-scroll { overflow: auto; scrollbar-width: thin; scrollbar-color: var(--line-2) transparent; }
.panel-scroll table { margin: 0; }
.panel-scroll thead th { position: sticky; top: 0; background: var(--ink-2); z-index: 1; }
.panel-scroll::-webkit-scrollbar { width: 10px; height: 10px; }
.panel-scroll::-webkit-scrollbar-track { background: transparent; }
.panel-scroll::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: 10px; }
.panel-scroll::-webkit-scrollbar-thumb:hover { background: var(--muted); }
.panel-scroll::-webkit-scrollbar-corner { background: transparent; }

/* ---- custom themed sort dropdown (non-native select) ---- */
.sortdd { position: relative; display: inline-block; }
.sortdd-trigger { display: inline-flex; align-items: center; gap: 0.4rem; background: var(--ink-2); border: 1px solid var(--line-2); color: var(--muted); font-family: var(--mono); font-size: 0.66rem; letter-spacing: 0.06em; text-transform: uppercase; padding: 0.4rem 0.7rem; border-radius: 8px; cursor: pointer; white-space: nowrap; }
.sortdd-trigger:hover { color: var(--off); border-color: var(--muted); }
.sortdd-chev { transition: transform 0.16s; flex-shrink: 0; }
.sortdd.open .sortdd-chev { transform: rotate(180deg); }
.sortdd.open .sortdd-trigger { color: var(--off); border-color: var(--lime); }
.sortdd-menu { position: absolute; right: 0; top: calc(100% + 6px); z-index: 20; min-width: 190px; background: var(--ink-3); border: 1px solid var(--line-2); border-radius: 10px; padding: 5px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); }
.sortdd-opt { display: block; width: 100%; text-align: left; background: transparent; border: 0; color: var(--text); font-family: var(--sans); font-size: 0.82rem; padding: 0.5rem 0.6rem; border-radius: 6px; cursor: pointer; white-space: nowrap; }
.sortdd-opt:hover { background: var(--ink-2); color: var(--off); }
.sortdd-opt[aria-selected="true"] { color: var(--lime); font-weight: 600; }

/* ---- Turnstile on very narrow phones ----
   The Cloudflare Turnstile widget renders an iframe with a hard 300px minimum width. At a 320px
   viewport the page column is only about 272px wide, so the widget overflowed its container and
   dragged the whole custom page into a 19px sideways scroll -- measured, not guessed.

   Widening the gutter cannot fix this: closing a 43px gap at 320px would mean removing essentially
   all page padding. Scaling alone cannot fix it either, because transform shrinks what you SEE and
   not the layout box, so the 300px iframe would keep contributing to scrollWidth and the page would
   still scroll. So it takes both: overflow hidden on the container stops the oversized box
   propagating, and the scale shrinks the widget to fit inside that container so nothing is
   actually clipped. 0.82 x 300px = 246px, which fits the ~252px column of even a 300px viewport.

   Only below 375px. At 375px and up the column is 327px and the widget fits at full size, so it is
   left completely alone.

   The scale targets the wrapper div Turnstile injects, NOT an iframe selector: the widget's inner
   markup is Cloudflare's and can change, and betting on it being an iframe would mean the scale
   silently stops matching while the overflow clip keeps working -- which would crop the challenge
   instead of shrinking it. Scaling the wrapper shrinks whatever is inside it. */
/* The Turnstile widget is a cross-origin iframe from challenges.cloudflare.com, so nothing in
   this file can style its interior -- no lime check mark, no brand colours, no removing the
   Cloudflare mark. Only the data- attributes on the element control its appearance, and the
   complete set is theme, size, language and appearance. What CAN be controlled is placement, so
   it is centred here rather than sitting hard left of a centred form. With
   appearance:"interaction-only" it renders nothing at all in the normal case. */
.cf-turnstile { display: flex; justify-content: center; }

@media (max-width: 374px) {
  .cf-turnstile { overflow: hidden; }
  /* center top, not left top: with the flex centring above, scaling from the left edge would
     drag the widget off centre on exactly the narrow phones this rule exists for. */
  .cf-turnstile > div { transform: scale(0.82); transform-origin: center top; }
}

/* ---- Commission stages and quotes (admin Custom Orders) --------------------------------------
   NEW selectors only. This file is shared with the storefront, so nothing above is touched. */

/* Stages past the paid gate. Shown rather than hidden: the commission really does continue
   through them, and displaying the road while refusing to drive it is more honest than
   pretending the process ends at Accepted. */
.otl-step.locked { opacity: .38; cursor: not-allowed; }
.otl-step.locked .otl-dot { border-style: dashed; }
.otl-step:disabled { cursor: not-allowed; }

/* A declined commission halts the bar instead of branching it. */
.otl.halted { opacity: .5; }

/* Same idea on the customer-facing tracker. */
.trk-steps.halted { opacity: .45; }

.cq-halted {
  margin: .5rem 0 0;
  font-size: .8rem;
  color: var(--down);
}

.cq-none { margin: 0; font-size: .85rem; color: var(--faint); }

.cq-current {
  border-left: 2px solid var(--lime);
  padding-left: .7rem;
}
.cq-current.expired { border-left-color: var(--down); }

.cq-top { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }

.cq-amt {
  font-family: "Space Mono", ui-monospace, monospace;
  font-weight: 700;
  font-size: 1.05rem;
  color: var(--off);
}

.cq-badge {
  font-size: .68rem;
  letter-spacing: .04em;
  text-transform: uppercase;
  padding: .18rem .45rem;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  color: var(--faint);
}
.cq-badge.err { color: var(--down); border-color: var(--down); }

.cq-meta { font-size: .8rem; color: var(--muted); margin-top: .25rem; }

.cq-form {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid rgba(255, 255, 255, 0.10);
}

.cq-fields { display: flex; gap: .7rem; flex-wrap: wrap; }

/* The Tax and Shipping form reuses the quote form's field styling by JOINING its selectors rather
   than copying the declarations. Two blocks that must look identical are two blocks that will stop
   looking identical. */
.cq-f,
.ts-f { display: flex; flex-direction: column; gap: .3rem; flex: 1 1 10rem; }
.cq-f.narrow,
.ts-f.narrow { flex: 0 0 7rem; }
.cq-f.wide { flex: 1 1 100%; margin-top: .7rem; }

.cq-f > span,
.ts-f > span {
  font-size: .68rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--faint);
}

.cq-f input,
.cq-f textarea,
.ts-f input {
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 8px;
  padding: .5rem .6rem;
  color: var(--off);
  font: inherit;
  font-size: .88rem;
  width: 100%;
}
.cq-f textarea { resize: vertical; }
.cq-f input:focus,
.cq-f textarea:focus,
.ts-f input:focus { outline: 2px solid var(--lime); outline-offset: 1px; }

.cq-actions { display: flex; gap: .6rem; flex-wrap: wrap; margin-top: .9rem; }
.cq-actions .btn,
.cq-actions .btn-ghost,
.ts-actions .btn,
.ts-actions .btn-ghost { width: auto; padding: .5rem .9rem; font-size: .82rem; }

.cq-hint { margin: .6rem 0 0; font-size: .8rem; color: var(--muted); }
.cq-hint.ok { color: var(--lime); }
.cq-hint.err { color: var(--down); }

/* Names the gap between the gender bars and the Revenue card once a commission is paid. */
.rv-note {
  margin: .9rem 0 0;
  font-size: .7rem;
  color: var(--faint);
}

/* ---- Shipping -------------------------------------------------------------------------------
   One field and two explanatory panels. This was a four-row per-country grid with a percentage
   and an amount on each row, a toggle and a live preview table; all of it went when tax moved to
   Stripe and shipping collapsed to a single number. The field styling itself is .ts-f, which
   joins the .cq-f selectors further up rather than repeating their declarations. */
.ts-note { margin: 0 0 1rem; font-size: .8rem; color: var(--muted); max-width: 60ch; }
.ts-note + .ts-note { margin-top: -.4rem; }

.ts-actions { display: flex; gap: .6rem; flex-wrap: wrap; margin-top: 1rem; }

.ts-who { margin: .6rem 0 0; font-size: .7rem; color: var(--faint); font-family: var(--mono); }

/* The single input keeps its fixed narrow width on desktop but is allowed to fill the panel on a
   phone, where a 7rem box next to a wide empty panel just looks like a mistake. */
@media (max-width: 560px) {
  .ts-f.narrow { flex: 1 1 auto; }
}

/* ------------------------------------------------------------------------------------------
   BLOGS -- the post page.

   A measure of about 68 characters, which is where long-form prose stops being tiring to read;
   the shop's full 1180px wrap is right for a grid of cards and wrong for a paragraph. Scoped
   under .bprose so the generated markdown gets typography without any element selector leaking
   into the admin or the product pages, whose markup this stylesheet also serves.
   ------------------------------------------------------------------------------------------ */
/* Top padding tracks .blist-head so the two blog surfaces open on the same nav gap. */
.bpost { max-width: 44rem; margin: 0 auto; padding: 2rem 0 1rem; }
.bpost .page-h1 { margin: 0.5rem 0 0.9rem; }
.bpost-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: baseline;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.74rem;
  letter-spacing: 0.06em;
}
.bpost .tags { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 1.1rem; }
.bpost .tags .tag { background: var(--chip); color: var(--muted); border: 1px solid var(--line); }

.bprose { margin-top: 2.4rem; color: var(--text); font-size: 1.02rem; line-height: 1.75; }
.bprose > *:first-child { margin-top: 0; }
.bprose h2 {
  margin: 2.4rem 0 0.8rem;
  color: var(--off);
  font-size: 1.35rem;
  font-weight: 600;
  letter-spacing: -0.01em;
}
.bprose h3 { margin: 1.8rem 0 0.6rem; color: var(--off); font-size: 1.1rem; font-weight: 600; }
.bprose p { margin: 0 0 1.15rem; }
.bprose a { color: var(--lime); text-decoration: underline; text-underline-offset: 0.18em; }
.bprose a:hover { color: var(--off); }
.bprose strong { color: var(--off); font-weight: 600; }
.bprose ul, .bprose ol { margin: 0 0 1.15rem; padding-left: 1.3rem; }
.bprose li { margin-bottom: 0.4rem; }
.bprose img { display: block; width: 100%; height: auto; border-radius: 10px; margin: 1.8rem 0; }
.bprose table { width: 100%; border-collapse: collapse; margin: 0 0 1.4rem; font-size: 0.92rem; }
.bprose th, .bprose td { text-align: left; padding: 0.6rem 0.7rem; border-bottom: 1px solid var(--line); }
.bprose th { color: var(--off); font-family: var(--mono); font-size: 0.72rem; letter-spacing: 0.08em; text-transform: uppercase; }
.bprose blockquote {
  margin: 1.6rem 0;
  padding: 0.2rem 0 0.2rem 1.1rem;
  border-left: 2px solid var(--lime);
  color: var(--muted);
}

/* The constructs markdown can emit that had no rule and fell back to browser defaults. On a dark
   theme those defaults are actively wrong -- a default <hr> is a light bevel that nearly
   disappears, and unstyled <code> loses the mono treatment the rest of the site uses. h4 was
   inheriting nothing and rendering smaller than the body copy it was meant to head. */
.bprose h4 { margin: 2rem 0 0.6rem; color: var(--off); font-size: 1rem; font-weight: 600; line-height: 1.35; }
.bprose hr { margin: 2.4rem 0; border: 0; border-top: 1px solid var(--line); }
.bprose code {
  font-family: var(--mono);
  font-size: 0.86em;
  background: var(--ink-2);
  border: 1px solid var(--line);
  border-radius: 5px;
  padding: 0.1em 0.35em;
  color: var(--off);
}
.bprose pre {
  margin: 1.6rem 0;
  padding: 1rem 1.1rem;
  background: var(--ink-2);
  border: 1px solid var(--line);
  border-radius: 10px;
  overflow-x: auto;
}
/* Inside a pre the chip styling would double up, so the inline treatment is undone. */
.bprose pre code { background: none; border: 0; padding: 0; font-size: 0.84rem; line-height: 1.6; }

.bpost-back { margin: 3rem 0 0; font-family: var(--mono); font-size: 0.78rem; }
.bpost-back a { color: var(--muted); }
.bpost-back a:hover { color: var(--lime); }

/* ------------------------------------------------------------------------------------------
   BLOGS -- the listing page, its cards, search and pagination.
   ------------------------------------------------------------------------------------------ */
/* Capped, but at a measure that fits a one-line tagline rather than wrapping it with half the
   row still empty. Not full width: this is running prose, and a line that spans the whole grid is
   hard to read back to. 56rem holds roughly ninety characters, which is a comfortable measure. */
/* Top padding is 2rem, not the 3.5rem this block opened with. The kicker, the title, the tagline
   and the search field stack up fast, and at 3.5rem the first row of cards fell below the fold on
   a short viewport. The heading margins below are tightened for the same reason. */
.blist-head { padding: 2rem 0 0; max-width: 56rem; }
.blist-head .page-h1 { margin: 0.25rem 0 0.5rem; }

.bgrid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
  margin-top: 1.5rem;
}
@media (min-width: 720px) { .bgrid { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1080px) { .bgrid { grid-template-columns: repeat(3, 1fr); } }

.bcard {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--ink-2);
  transition: border-color 0.18s ease, background 0.18s ease, transform 0.18s ease;
}
/* The accent rail down the left edge, in lime where the reference uses its own pink -- the same
   re-skin the TV frame got. overflow:hidden is what makes it follow the rounded corners instead
   of squaring them off. */
.bcard::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--lime);
  opacity: 0.85;
  transition: opacity 0.18s ease;
}
.bcard:hover::before { opacity: 1; }
.bcard:hover { border-color: var(--line-2); background: var(--ink-3); transform: translateY(-2px); }
.bcard-link { display: flex; flex-direction: column; height: 100%; padding: 1.15rem 1.2rem 1.1rem; }
.bcard-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  margin-bottom: 0.85rem;
  color: var(--faint);
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.07em;
}
/* Uppercase and letterspaced in the accent, as the reference renders its section tag. Scoped to
   .bcard because .tag is shared with the product cards, which keep their own look. */
.bcard .tag { color: var(--lime); text-transform: uppercase; letter-spacing: 0.12em; font-size: 0.62rem; }
.bcard-title { margin: 0 0 0.5rem; color: var(--off); font-size: 1.08rem; font-weight: 600; line-height: 1.3; }
.bcard-excerpt { margin: 0 0 1.1rem; color: var(--muted); font-size: 0.88rem; line-height: 1.6; }
.bcard-more { margin-top: auto; color: var(--lime); font-family: var(--mono); font-size: 0.72rem; letter-spacing: 0.06em; }

/* Full content width, matching the card grid beneath it -- the reference runs the field edge to
   edge rather than capping it at the prose measure, and a search box that stops short of the
   results it filters reads like it belongs to something else. The HEADING stays capped: that is
   running prose and wants a readable measure, which is a different job from a form control. */
.bsearch { margin-top: 1.25rem; }
.bsearch-field {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--ink-2);
  padding: 0 0.9rem;
  transition: border-color 0.18s ease, background 0.18s ease;
}
.bsearch-field:focus-within { border-color: var(--lime); background: var(--ink-3); }
.bsearch-ic { width: 17px; height: 17px; color: var(--faint); flex-shrink: 0; }
.bsearch-input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  color: var(--text);
  font-family: var(--sans);
  font-size: 0.95rem;
  /* This padding is the ONLY thing setting the height of the field: the wrapper has no vertical
     padding of its own and the clear button is shorter than the text, so nothing else floors it.
     0.55rem gives a 38px control -- 18.4px of text, 17.6px of padding, 2px of border -- down from
     the 46px it opened with, which sat taller than it needed to above a single row of cards. */
  padding: 0.55rem 0;
  outline: none;
}
.bsearch-input::placeholder { color: var(--faint); }
.bsearch-input::-webkit-search-cancel-button { -webkit-appearance: none; }
/* Touch target. 38px is comfortable under a mouse and short of what a thumb wants: WCAG 2.5.8
   puts the AA floor at 24px, but Apple's guidance and WCAG AAA both ask for 44. So phones get the
   larger control and the desktop keeps the tighter one -- which is why this is a media query and
   not a new base value. 0.75rem yields 44.4px: 18.4px of text, 24px of padding, 2px of border.
   Sized here rather than on .bsearch-field because the input's padding is the only thing that
   sets the height of that control -- see the note above. */
@media (max-width: 720px) { .bsearch-input { padding: 0.75rem 0; } }
.bsearch-clear {
  border: 0;
  background: transparent;
  color: var(--muted);
  font-size: 1.3rem;
  line-height: 1;
  cursor: pointer;
  padding: 0.1rem 0.35rem;
  border-radius: 8px;
}
.bsearch-clear:hover { color: var(--off); }
.bsearch-status { color: var(--muted); font-family: var(--mono); font-size: 0.72rem; letter-spacing: 0.04em; margin: 0.9rem 0 0; }

.bpagination {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  margin: 2.6rem 0 0.5rem;
}
.bpage-list { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem; list-style: none; margin: 0; padding: 0; }
.bpage-num,
.bpage-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.4rem;
  height: 2.4rem;
  padding: 0 0.7rem;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--ink-2);
  color: var(--text);
  font-family: var(--mono);
  font-size: 0.8rem;
}
.bpage-num:hover, .bpage-btn:hover { border-color: var(--line-2); color: var(--off); }
.bpage-num.is-current { background: var(--lime); border-color: var(--lime); color: var(--lime-ink); font-weight: 700; }
.bpage-btn.is-disabled { opacity: 0.35; }
.bpage-gap { min-width: 1.4rem; text-align: center; color: var(--faint); font-family: var(--mono); font-size: 0.8rem; }
.bpage-status { flex-basis: 100%; text-align: center; color: var(--faint); font-family: var(--mono); font-size: 0.68rem; letter-spacing: 0.08em; margin: 0.5rem 0 0; }

/* ------------------------------------------------------------------------------------------
   BLOGS -- the two screens under a post.

   A CRT television in the brand's own colours, following the reference project's shape: title
   above, SQUARE screen, sheen, wordmark, power LED, splayed legs. The screen was 16:9 until
   session 31; it is 1:1 now so it obeys the CardMedia contract stated in app.js -- a 1:1 frame
   that CONTAINS any aspect ratio and never crops. It was the only place in the codebase
   breaking that rule, and a square product photo was losing its top and bottom to object-fit:
   cover. The television metaphor survives the squaring by founder decision.

   The controls carry the same
   .arw / .dots class names the shop card uses, because the same initCardMedia drives both --
   but those rules are scoped to .pcard, so the frame needs its own copies rather than a
   refactor of the card's styling mid-feature.

   The legs are .tv-leg, NOT .foot: .foot is the site footer and the collision would have been
   silent and site-wide.
   ------------------------------------------------------------------------------------------ */
.article-tvs { display: grid; grid-template-columns: 1fr; gap: 1.9rem; margin: 3.2rem 0 0.5rem; }
@media (min-width: 600px) { .article-tvs { grid-template-columns: 1fr 1fr; gap: 1.5rem; } }
.article-tvs .tv-cell { min-width: 0; }

.tvframe { display: block; width: 100%; max-width: 24rem; margin-inline: auto; }
.tvframe.is-link { cursor: pointer; transition: transform 0.22s ease; }
.tvframe.is-link:hover { transform: translateY(-4px); }
.tvframe.is-link:focus-visible { outline: 2px solid var(--lime); outline-offset: 5px; border-radius: 14px; }

.tv-title {
  margin: 0 0 0.7rem;
  text-align: center;
  color: var(--off);
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.3;
}

.tv-panel {
  position: relative;
  padding: 8px 8px 22px;
  background: linear-gradient(160deg, var(--ink-3), var(--ink));
  border-radius: 12px;
  box-shadow: inset 0 0 0 1px var(--line), inset 0 1px 0 rgba(255, 255, 255, 0.06), 0 24px 44px -22px rgba(0, 0, 0, 0.9);
  transition: box-shadow 0.22s ease;
}
.tvframe.is-link:hover .tv-panel {
  box-shadow: inset 0 0 0 1px var(--line-2), inset 0 1px 0 rgba(255, 255, 255, 0.08), 0 30px 56px -20px rgba(0, 0, 0, 0.95);
}

.tv-screen {
  position: relative;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  border-radius: 5px;
  background:
    radial-gradient(90% 120% at 28% 16%, rgba(140, 184, 30, 0.18), transparent 60%),
    radial-gradient(80% 120% at 86% 96%, rgba(140, 184, 30, 0.10), transparent 60%),
    var(--chip);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.7), inset 0 0 40px rgba(0, 0, 0, 0.55);
  display: grid;
  place-items: center;
}
/* CONTAIN, never cover -- the same contract as .pcard .ph and the PDP gallery. The dark
   backing is what a letterboxed non-square photo sits against. */
.tv-screen img,
.tv-screen video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; display: block; background: #0e0e0e; }

.tv-mono { display: grid; place-items: center; width: 40%; }
.tv-mono svg { width: 100%; height: auto; display: block; }
.tv-mono rect { fill: var(--ink-2); }
.tv-mono path { fill: var(--lime); }
.tv-screen.has-media .tv-mono { display: none; }

.tv-sheen {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(135deg, rgba(255, 255, 255, 0.07) 0%, transparent 34%);
  z-index: 3;
}
.tv-shop {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 4;
  font-family: var(--mono);
  font-size: 0.58rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--lime);
  background: rgba(11, 11, 11, 0.82);
  border: 1px solid var(--line-2);
  padding: 4px 9px;
  border-radius: 999px;
  transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}
.tvframe.is-link:hover .tv-shop { background: var(--lime); border-color: var(--lime); color: var(--lime-ink); }

.tv-brand {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 7px;
  text-align: center;
  font-family: var(--mono);
  font-size: 0.54rem;
  letter-spacing: 0.3em;
  text-indent: 0.3em;
  color: var(--faint);
}
.tv-led {
  position: absolute;
  left: 50%;
  bottom: 4px;
  transform: translateX(-50%);
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--lime);
  box-shadow: 0 0 6px rgba(140, 184, 30, 0.8);
  opacity: 0.75;
}

.tv-stand { position: relative; height: 26px; }
.tv-leg { position: absolute; top: -2px; width: 42px; height: 26px; }
.tv-leg::before, .tv-leg::after {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  width: 4px;
  height: 26px;
  background: linear-gradient(var(--ink-3), var(--ink));
  border-radius: 0 0 3px 3px;
}
.tv-leg::before { transform: rotate(24deg); transform-origin: top center; }
.tv-leg::after { transform: rotate(-24deg); transform-origin: top center; }
.tv-leg-l { left: 11%; }
.tv-leg-r { right: 11%; }

/* Slideshow controls -- same markup as the shop card, whose rules are scoped to .pcard.
   A screen with NO media never has initCardMedia run on it, so nothing hides its controls the
   way the one-item branch does. Without this, hovering a monogram screen revealed two arrows
   that did nothing -- and with no product photographed yet, that is every screen on the site. */
.tv-screen:not(.has-media) .arw,
.tv-screen:not(.has-media) .dots { display: none; }

.tv-screen .arw {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 5;
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid var(--line-2);
  background: rgba(11, 11, 11, 0.6);
  color: var(--off);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}
.tv-screen:hover .arw { opacity: 0.9; }
/* The arrows are the KEYBOARD pause control for the autoplay switched on in session 31, and they
   sit at opacity 0 until hover. Without this rule a keyboard user would tab to an invisible
   button, which is not a pause control and fails focus visibility as well. */
.tv-screen .arw:focus-visible { opacity: 1; outline: 2px solid var(--lime); outline-offset: 2px; }
.tv-screen .arw.prev { left: 8px; }
.tv-screen .arw.next { right: 8px; }
.tv-screen .arw:hover { border-color: var(--lime); color: var(--lime); }
.tv-screen .dots { position: absolute; left: 0; right: 0; bottom: 7px; z-index: 5; display: flex; gap: 6px; justify-content: center; }
.tv-screen .dot {
  width: 6px;
  height: 6px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.35);
  cursor: pointer;
  transition: background 0.2s ease, transform 0.2s ease;
}
.tv-screen .dot.on { background: var(--lime); transform: scale(1.3); }

/* ------------------------------------------------------------------------------------------
   THE INSTALL INVITATION -- the surfaces created by assets/install.js.

   Chrome's own install banner is suppressed by that file, so this is what a shopper on Android
   actually sees. It is deliberately a bar and not a modal: an interstitial that blocks the page
   to ask a favour is the pattern everyone has learned to close without reading.

   z-index 200 sits BELOW the lightbox at 300, and that ordering is the point -- opening a photo
   must cover this bar, never the other way round.
   ------------------------------------------------------------------------------------------ */
.instl {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 200;
  background: var(--ink-2);
  border-top: 1px solid var(--line);
  /* NOTE, load-bearing: nothing compensates the page for these 61 fixed pixels -- no body
     padding-bottom, deliberately, because adding it when the bar mounts would shove the whole
     page upward in front of the reader. It is safe TODAY only because every page ends with
     trailing space below its last element: measured at 400x640 scrolled fully to the bottom on
     /, /custom and /blogs, nothing lands underneath. A future page that ends flush against a
     button would put that button under this bar. If that happens, compensate the page, do not
     shrink the bar. */
  /* The safe-area inset keeps the buttons clear of the home indicator on gesture-nav phones,
     where the bottom of the viewport is not the bottom of the reachable screen. */
  /* 0.5rem, not the 0.85rem this opened with. The bar is an offer, not an announcement, and the
     taller version ate a strip of a phone screen to say four words. Height is now driven by the
     Install button, not by padding: 8 + 44 + 8 + 1px border = 61px. */
  padding: 0.5rem 1rem calc(0.5rem + env(safe-area-inset-bottom, 0px));
  transform: translateY(110%);
  transition: transform 0.28s ease;
}
.instl.on { transform: translateY(0); }
@media (prefers-reduced-motion: reduce) { .instl { transition: none; } }

.instl-in {
  max-width: 1180px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: 0.9rem;
}
.instl-mark {
  flex-shrink: 0;
  width: 38px;
  height: 38px;
  border-radius: 10px;
  background: var(--lime);
  color: #0b0b0b;
  display: flex;
  align-items: center;
  justify-content: center;
}
.instl-mark svg { width: 62%; height: 62%; }
.instl-copy { flex: 1; min-width: 0; }
/* Truncates rather than wraps: a second line of title would push the bar taller, which is the
   one thing this layout is built to avoid. */
.instl-title {
  margin: 0;
  color: var(--off);
  font-size: 0.95rem;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.instl-sub { margin: 0.15rem 0 0; color: var(--muted); font-size: 0.8rem; line-height: 1.45; }

.instl-acts { display: flex; align-items: center; gap: 0.25rem; flex-shrink: 0; }
/* The Install button stays 44px. The pill shape is borrowed from the reference bar; the touch
   target is NOT -- theirs is about 32px, which is under the minimum we set for the search field
   in the same session. Height here comes from the button, so keeping 44 costs nothing: the bar
   lands at the same visual weight either way. */
.instl-go {
  min-height: 44px;
  border-radius: 999px;
  cursor: pointer;
  border: 0;
  background: var(--lime);
  color: #0b0b0b;
  font-family: var(--sans);
  font-size: 0.88rem;
  font-weight: 600;
  padding: 0 1.15rem;
}
/* Dismiss is a glyph, not a word: "Not now" cost more width than it earned next to a button that
   already says what the bar is for. Still 44px square, and still labelled "Not now" for screen
   readers -- the cross is shorthand for later, not for never, and the 30-day suppression behind
   it means the softer word is the accurate one. */
.instl-no {
  width: 44px;
  height: 44px;
  flex: none;
  cursor: pointer;
  background: transparent;
  border: 0;
  color: var(--muted);
  font-size: 1.4rem;
  line-height: 1;
  opacity: 0.75;
}
.instl-no:hover { color: var(--off); opacity: 1; }
/* A lime outline on the lime Install button would be invisible, so that one uses the two-ring
   box-shadow this codebase already uses on .osearch-clear: an ink ring to separate, then lime.
   The dismiss glyph sits on the dark panel, where a plain outline reads fine. */
.instl-go:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--ink-2), 0 0 0 4px var(--lime);
}
.instl-no:focus-visible {
  outline: 2px solid var(--lime);
  outline-offset: -2px;
  color: var(--off);
  opacity: 1;
}

/* Narrow phones drop the second line rather than wrapping the row. Wrapping was the previous
   answer and it made the bar TALLER on exactly the screens with the least room to spare, which is
   the opposite of the point. The title and the Install button carry the meaning between them; the
   sub-line is elaboration, and elaboration is what a small screen can afford to lose. */
@media (max-width: 480px) {
  .instl-sub { display: none; }
}

/* The permanent entry point, appended to the nav only when an install is actually available.
   Styled to sit with the nav links rather than to stand out -- it is a way back to an offer that
   was declined, not a second attempt at making it. */
.instl-nav {
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
  color: var(--lime);
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.instl-nav:hover { color: var(--off); }
.instl-nav:focus-visible { outline: 2px solid var(--lime); outline-offset: 3px; border-radius: 4px; }

/* ================================================================================
   VERY NARROW PHONES -- 400px and under.

   Measured, not guessed. The nav bar must fit brand + department toggle + burger on ONE row, and
   below about 380px it does not: the wordmark is 139px and the toggle 126px, which against a
   360px viewport leaves the row 14px short, and 54px short at 320px. A 360px phone is one of the
   most common widths there is, so this is not an edge case.

   The wordmark goes and the monogram stays. That recovers roughly 110px, which is far more than
   the shortfall, and it is the right thing to drop: the department toggle is a CONTROL and the
   wordmark is decoration next to a logo that is still on screen.

   Placed after the 560px block deliberately -- same specificity, so the later rule wins.
   ================================================================================ */
@media (max-width: 400px) {
  .brand b { display: none; }
}
