/* ==========================================================================
   Koolabrew — site styles
   No framework. Custom properties + grid. Light by default; dark is opt-in.
   ========================================================================== */

/* ---------- fonts ----------
   Self-hosted latin subsets of Inter and Outfit (SIL Open Font License 1.1).
   Served from our own origin so no render-blocking round trip to Google.
   Both are variable fonts: one file covers the whole weight range.
   -------------------------------------------------------------------- */
/* Hide expired offers before site.js restores the ordinary hero. */
:root[data-promo-live="false"] [data-promo-only] { display: none !important; }
:root[data-promo-live="false"] [data-promo-hero] { visibility: hidden; }

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/inter-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Outfit";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/outfit-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ---------- tokens ---------- */
:root {
  --ink:            #0e1b24;
  --ink-2:          #3d515e;
  --ink-3:          #6a7f8c;
  --bg:             #ffffff;
  --bg-2:           #f4f7f9;
  --bg-3:           #e8eef2;
  --line:           #dbe4ea;
  --line-2:         #c5d3dc;

  --brand:          #0b3a52;
  --brand-2:        #12809f;
  --brand-ink:      #ffffff;
  --accent:         #ef6a3d;
  /* The accent pressed darker. It was already the primary button's hover and is
     `primary-dark` in DESIGN.md, but it only ever existed as a literal. The promo
     backdrop needed it as a token: over a pale scrim --accent is 2.5:1 and fails even
     the 3:1 large-text bar, where this clears it at 3.2:1. Deliberately NOT restated
     in the dark block -- the hover was this colour in both themes before it had a
     name, and it still is. */
  --accent-strong:  #d9552a;
  --accent-2:       #ffb03a;

  --ok:             #0f8a5f;

  /* ---- the prism ----
     A rainbow that has already been through something: bounced off a mostly opaque
     white surface rather than split clean out of a prism. So no pure spectrum
     primaries -- every stop is high-lightness and mid-to-low saturation, and the arc
     runs between the two hues the brand already owns, amber (--accent-2) at one end
     and a brand-2 aqua at the other, through the coral of --accent and the pinks and
     lilacs that fall between them. That is what keeps it Koolabrew's rainbow and not
     a generic holographic sticker.

     It exists because the site is nearly all white. Used as a FIELD it would fight the
     product photography, which is the one thing here allowed to carry colour, so it is
     only ever an EDGE: a hairline, or a soft bloom decaying off one. See the prismatic
     edges section further down for where that is spent and why. */
  --prism-1:        #ffd7a3;
  --prism-2:        #ff9f8c;
  --prism-3:        #f0a8d8;
  --prism-4:        #b9aef0;
  --prism-5:        #8fc6ea;
  --prism-6:        #7fd8cf;
  /* The full arc, for an edge that spans its element. */
  --prism-sweep: linear-gradient(90deg,
    var(--prism-1) 0%, var(--prism-2) 16%, var(--prism-3) 32%, var(--prism-4) 50%,
    var(--prism-5) 68%, var(--prism-6) 84%, var(--prism-1) 100%);
  /* The arc compressed into the first third and then gone, for an edge that is lit
     from one end -- the header, where the source is the logo. */
  --prism-fade: linear-gradient(90deg,
    var(--prism-1) 0%, var(--prism-2) 9%, var(--prism-3) 18%, var(--prism-4) 27%,
    var(--prism-5) 35%, var(--prism-6) 42%,
    color-mix(in srgb, var(--prism-6) 40%, transparent) 52%, transparent 66%);
  --prism-bloom:    .6;
  --prism-band:     .55;

  --radius:         14px;
  --radius-sm:      9px;
  --radius-lg:      22px;
  --shadow-1:       0 1px 2px rgba(11,58,82,.06), 0 2px 8px rgba(11,58,82,.05);
  --shadow-2:       0 2px 6px rgba(11,58,82,.07), 0 12px 32px rgba(11,58,82,.10);
  --shadow-3:       0 8px 20px rgba(11,58,82,.10), 0 28px 60px rgba(11,58,82,.16);

  --wrap:           1200px;
  --gut:            clamp(1rem, 4vw, 2.5rem);
  --header-h:       68px;

  --font: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --display: "Outfit", var(--font);

  /* The ground behind a cut-out product photograph. It is white in BOTH themes and is
     deliberately absent from the dark block below: every product here was shot on white,
     so white is the only ground where the whole range reads. On the dark page the clear
     glassware and the black tumblers nearly disappeared -- #806, #860 and #907 were the
     worst. The hero stage and the order-line thumbnails already did this; the catalog
     grid and the product page were the two places that did not. */
  --well-ground:    #ffffff;

  /* Stock art is black line work on a white field -- see ".art-tile". */
  --art-ground:     #ffffff;
  --art-ink:        none;
  --art-blend:      normal;

  color-scheme: light;
}

/* Dark is opt-in, never inherited from the OS: the ONLY thing that turns it on is
   `data-theme="dark"` on <html>, which the toggle sets and the boot script restores.
   A visitor who has never touched the toggle gets the light tokens above, whatever
   their system is set to. Do not add a `prefers-color-scheme` block here -- it would
   hand the theme back to the OS and half the site would load dark again. */
:root[data-theme="dark"] {
  --ink:      #e9f1f6;
  --ink-2:    #a9bdc9;
  --ink-3:    #7d95a3;
  --bg:       #0c1519;
  --bg-2:     #121f26;
  --bg-3:     #1a2c35;
  --line:     #233842;
  --line-2:   #33505d;
  --brand:    #0d2f42;
  --brand-2:  #3ab6d8;
  --brand-ink:#eaf6fb;
  --accent:   #ff8154;
  --accent-2: #ffc464;
  /* The same arc with the lightness taken out of it. The light set is calibrated to
     read ON white; dropped onto #0c1519 unchanged it goes chalky and reads as haze
     rather than as light. These are the same six hues, deeper and a little more
     saturated, and both opacities come down because a glow on a dark ground needs far
     less of itself to be seen. */
  --prism-1:  #ffb15e;
  --prism-2:  #ff7a63;
  --prism-3:  #e87ac4;
  --prism-4:  #9a86e8;
  --prism-5:  #5aa8dd;
  --prism-6:  #43c4b8;
  --prism-bloom: .42;
  --prism-band:  .36;
  --shadow-1: 0 1px 2px rgba(0,0,0,.4);
  --shadow-2: 0 2px 6px rgba(0,0,0,.35), 0 12px 32px rgba(0,0,0,.45);
  --shadow-3: 0 8px 20px rgba(0,0,0,.4), 0 28px 60px rgba(0,0,0,.55);
  --art-ground: var(--bg-2);
  --art-ink:    invert(1);
  --art-blend:  screen;
  color-scheme: dark;
}

/* ---------- reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}
body {
  margin: 0;
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.6;
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}
main { flex: 1; }
img, svg, video { max-width: 100%; height: auto; display: block; }
h1, h2, h3, h4 { font-family: var(--display); line-height: 1.12; letter-spacing: -.02em; margin: 0 0 .5em; font-weight: 700; }
h1 { font-size: clamp(2rem, 1.2rem + 3.4vw, 3.6rem); }
h2 { font-size: clamp(1.5rem, 1.1rem + 1.7vw, 2.35rem); }
h3 { font-size: clamp(1.15rem, 1rem + .7vw, 1.5rem); }
p  { margin: 0 0 1em; }
a  { color: var(--brand-2); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { color: var(--accent); }
ul, ol { margin: 0 0 1em; padding-left: 1.25em; }
hr { border: 0; border-top: 1px solid var(--line); margin: 2.5rem 0; }
:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
::selection { background: var(--accent); color: #fff; }

/* Grid and flex children default to min-width:auto, which lets a wide child (a product
   photo, a pricing table) push the whole page wider than the viewport on small screens. */
.pd > *, .hero-inner > *, .contact-grid > *, .cat-grid > *, .prod-grid > *,
.feat-grid > *, .steps > *, .cta-band > *, .ftr-grid > * { min-width: 0; }

.wrap { width: min(var(--wrap), 100% - var(--gut) * 2); margin-inline: auto; }
.wrap-narrow { width: min(760px, 100% - var(--gut) * 2); margin-inline: auto; }
.skip {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--accent); color: #fff; padding: .7rem 1.1rem; border-radius: 0 0 var(--radius-sm) 0; font-weight: 600;
}
.skip:focus { left: 0; }
.sr { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

/* ---------- buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  font: inherit; font-weight: 600; line-height: 1;
  padding: .8rem 1.35rem; border-radius: 999px; border: 1px solid transparent;
  text-decoration: none; cursor: pointer; white-space: nowrap;
  transition: transform .15s ease, box-shadow .2s ease, background-color .2s ease, color .2s ease, border-color .2s ease;
}
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
.btn-primary { background: var(--accent); color: #fff; box-shadow: var(--shadow-1); }
.btn-primary:hover { background: var(--accent-strong); color: #fff; box-shadow: var(--shadow-2); }
.btn-ghost { background: transparent; color: var(--ink); border-color: var(--line-2); }
.btn-ghost:hover { background: var(--bg-2); color: var(--ink); border-color: var(--ink-3); }
.btn-on-dark { background: rgba(255,255,255,.12); color: #fff; border-color: rgba(255,255,255,.32); backdrop-filter: blur(6px); }
.btn-on-dark:hover { background: rgba(255,255,255,.22); color: #fff; }
.btn-sm { padding: .55rem 1rem; font-size: .875rem; }
.btn-lg { padding: .95rem 1.7rem; font-size: 1.05rem; }

/* ---------- header ---------- */
.hdr {
  position: sticky; top: 0; z-index: 100;
  background: color-mix(in srgb, var(--bg) 86%, transparent);
  backdrop-filter: saturate(1.6) blur(12px);
  border-bottom: 1px solid var(--line);
}
.hdr-bar { display: flex; align-items: center; gap: 1rem; height: var(--header-h); }

/* ---------- prismatic edges ----------
   Two of them, and they are the same idea at two boundaries: the bottom of the header
   and the bottom of the hero. The site is white on white on white, and these are what
   separate one white plane from the next without drawing a box around anything.

   The header's is lit from the LEFT, because that is where the logo is -- `--prism-fade`
   puts the whole arc in the first third and is gone by two thirds, so it reads as light
   coming off the mark rather than as a decorative stripe across the page. Two layers: a
   2px line doing the work and a 16px blurred bloom under it doing the softness. The
   bloom hangs below the header on purpose and is not clipped, so it falls onto whatever
   the page has at the top.

   The hero's spans the full width (`--prism-sweep`) because nothing lights it from one
   side; it is the plane itself refracting at its own edge. A 2px line again, and above
   it a 120px band masked to fade upward into the white, so the colour has somewhere to
   come from.

   Both replace a `--line` border rather than joining one. A prismatic edge under a grey
   rule reads as two edges.

   The hero rules are `:not(.hero--backdrop)` and that is structural, not stylistic:
   `.hero--backdrop` already spends BOTH pseudo-elements on the photograph and its scrim,
   so an unscoped rule here would silently delete the promo photo. It is also the right
   answer visually -- a hero with a photograph in it does not need help standing out, and
   the edge would fight the inset panel's radius. The prism is what dresses an UNdressed
   hero, which is exactly the gap it was asked to fill. */
.hdr { border-bottom-color: transparent; }
.hdr::before, .hdr::after { content: ""; position: absolute; pointer-events: none; }
.hdr::after { left: 0; right: 0; bottom: 0; height: 2px; background: var(--prism-fade); }
.hdr::before {
  left: 0; bottom: -16px; width: min(620px, 62%); height: 16px;
  filter: blur(9px); opacity: var(--prism-bloom); background: var(--prism-fade);
}
.brand { display: flex; align-items: center; gap: .6rem; text-decoration: none; color: var(--ink); margin-right: auto; }
.brand:hover { color: var(--ink); }
/* A background rather than an <img>, so a visitor downloads only the file for their
   theme. The dark file differs only in the registered mark, which its dark ink would lose
   against the dark header. */
.brand-logo {
  display: block; flex: none; width: 149px; height: 44px;
  background: url(../img/koolabrew-logo.svg) center / contain no-repeat;
}
:root[data-theme="dark"] .brand-logo { background-image: url(../img/koolabrew-logo-dark.svg); }
.brand-tag { display: none; white-space: nowrap; font-size: .75rem; color: var(--ink-3); letter-spacing: .09em; text-transform: uppercase; font-weight: 600; }
@media (min-width: 1280px) { .brand-tag { display: block; border-left: 1px solid var(--line-2); padding-left: .6rem; margin-left: .2rem; } }

.nav { display: none; }
@media (min-width: 960px) { .nav { display: flex; align-items: center; gap: .15rem; } }
.nav > * { position: relative; }
.nav a, .nav button.navtop {
  font: inherit; font-weight: 600; font-size: .9375rem; color: var(--ink); background: none; border: 0; cursor: pointer;
  padding: .55rem .8rem; border-radius: 999px; text-decoration: none; display: inline-flex; align-items: center; gap: .3rem;
  white-space: nowrap;
}
.nav a:hover, .nav button.navtop:hover, .nav [aria-expanded="true"] { background: var(--bg-2); color: var(--ink); }
.nav a[aria-current="page"] { color: var(--accent); }
.navtop svg { transition: transform .2s ease; }
.navtop[aria-expanded="true"] svg { transform: rotate(180deg); }

.mega {
  position: absolute; top: calc(100% + .5rem); left: 50%; transform: translateX(-50%) translateY(-6px);
  width: max-content; max-width: min(900px, 92vw);
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-3); padding: 1.25rem; opacity: 0; visibility: hidden;
  transition: opacity .18s ease, transform .18s ease, visibility .18s;
}
.mega[data-open="true"] { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
.mega-grid { display: grid; grid-template-columns: repeat(3, minmax(190px, 1fr)); gap: .35rem 1.5rem; }
.mega a { display: block; padding: .6rem .7rem; border-radius: var(--radius-sm); font-weight: 600; font-size: .9375rem; text-decoration: none; color: var(--ink); }
.mega a:hover { background: var(--bg-2); color: var(--accent); }
.mega a small { display: block; font-weight: 400; font-size: .75rem; color: var(--ink-3); margin-top: .1rem; line-height: 1.35; }
.mega-foot { margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--line); display: flex; gap: .6rem; flex-wrap: wrap; }

.hdr-cta { display: none; }
@media (min-width: 1120px) { .hdr-cta { display: inline-flex; } }

.burger {
  display: grid; place-items: center; width: 42px; height: 42px; flex: none;
  background: var(--bg-2); border: 1px solid var(--line); border-radius: 12px; cursor: pointer; color: var(--ink);
}
@media (min-width: 960px) { .burger { display: none; } }

.theme-btn {
  display: grid; place-items: center; width: 42px; height: 42px; flex: none;
  background: transparent; border: 1px solid var(--line); border-radius: 12px; cursor: pointer; color: var(--ink-2);
}
.theme-btn:hover { color: var(--ink); background: var(--bg-2); }
.theme-btn .moon { display: none; }
:root[data-theme="dark"] .theme-btn .moon { display: block; }
:root[data-theme="dark"] .theme-btn .sun { display: none; }

/* ---------- mobile drawer ---------- */
.drawer { position: fixed; inset: 0; z-index: 150; display: none; }
.drawer[data-open="true"] { display: block; }
.drawer-scrim { position: absolute; inset: 0; background: rgba(6,20,28,.55); backdrop-filter: blur(2px); animation: fade .2s ease; }
.drawer-panel {
  position: absolute; inset: 0 0 0 auto; width: min(400px, 88vw);
  background: var(--bg); box-shadow: var(--shadow-3); overflow-y: auto; overscroll-behavior: contain;
  animation: slide .24s cubic-bezier(.22,1,.36,1);
  padding-bottom: 3rem;
}
@keyframes fade { from { opacity: 0 } }
@keyframes slide { from { transform: translateX(100%) } }
.drawer-head { display: flex; align-items: center; justify-content: space-between; height: var(--header-h); padding: 0 1rem 0 1.25rem; border-bottom: 1px solid var(--line); position: sticky; top: 0; background: var(--bg); z-index: 1; }
.drawer-x { width: 42px; height: 42px; display: grid; place-items: center; background: var(--bg-2); border: 1px solid var(--line); border-radius: 12px; cursor: pointer; color: var(--ink); }
.drawer-body { padding: 1rem 1.25rem; }
.drawer-body h4 { font-size: .75rem; text-transform: uppercase; letter-spacing: .1em; color: var(--ink-3); margin: 1.6rem 0 .5rem; font-family: var(--font); font-weight: 700; }
.drawer-body h4:first-child { margin-top: .4rem; }
.drawer-link { display: block; padding: .7rem .8rem; border-radius: var(--radius-sm); text-decoration: none; color: var(--ink); font-weight: 600; }
.drawer-link small { display: block; font-weight: 400; font-size: .8125rem; color: var(--ink-3); }
.drawer-link:hover { background: var(--bg-2); color: var(--ink); }
.drawer-cta { display: grid; gap: .6rem; margin-top: 1.75rem; }

/* ---------- promo bar ---------- */
/* The live announcement, above the header on every page. It SCROLLS AWAY rather than
   sticking: .hdr is "position: sticky; top: 0" and six other rules offset from
   --header-h, so a sticky bar would have to push every one of them and would eat real
   estate on a phone. Wording, dates and booth number are all in data/promo.json. */
.promobar { background: var(--brand); color: #eaf6fb; font-size: .875rem; }
.promobar[hidden] { display: none; }
.promobar-in { display: flex; align-items: center; gap: .7rem; padding: .6rem 0; }
.promobar p { margin: 0; flex: 1 1 auto; min-width: 0; }
.promobar-dot {
  flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--accent-2);
  box-shadow: 0 0 0 3px rgba(255,176,58,.25);
}
.promobar a {
  flex: none; color: #fff; font-weight: 700; text-decoration: none;
  display: inline-flex; align-items: center; gap: .3rem; transition: gap .2s ease;
}
.promobar a:hover { color: #fff; gap: .55rem; }
.promobar-x {
  flex: none; background: none; border: 0; padding: .25rem; cursor: pointer;
  color: #9fc4d6; border-radius: var(--radius-sm); display: grid; place-items: center;
}
.promobar-x:hover { color: #fff; background: rgba(255,255,255,.12); }
@media (max-width: 620px) {
  .promobar { font-size: .8125rem; }
  .promobar a { display: none; }   /* the whole bar is not a link; the hero chip carries it */
}

/* ---------- hero ---------- */
/* A light, flat hero on the page's own surface -- no gradient, no dot texture, no white
   card. Because it is built from the theme tokens it simply follows the theme toggle:
   near-white for a light visitor, near-black for a dark one, one rule set for both.
   That is also why nothing in here hard-codes a colour the way the old navy hero did.

   Three things replaced the old furniture:
   - .pchip, the promo pill, in place of a fixed "Shallotte, North Carolina" eyebrow.
   - .cred, one small line of credentials, in place of four big stat numbers.
   - .hero-art, a mosaic of six real products, in place of four shots in a white box. */
.hero { background: var(--bg); border-bottom: 1px solid var(--line); position: relative; isolation: isolate; }
/* See "prismatic edges" above -- particularly why this is :not(.hero--backdrop). */
.hero:not(.hero--backdrop) { border-bottom-color: transparent; }
.hero:not(.hero--backdrop)::before,
.hero:not(.hero--backdrop)::after {
  content: ""; position: absolute; left: 0; right: 0; z-index: -1; pointer-events: none;
}
.hero:not(.hero--backdrop)::before { bottom: 0; height: 2px; opacity: .85; background: var(--prism-sweep); }
.hero:not(.hero--backdrop)::after {
  bottom: 0; height: 120px; opacity: var(--prism-band); filter: blur(34px);
  background: var(--prism-sweep);
  -webkit-mask: linear-gradient(180deg, transparent, #000);
  mask: linear-gradient(180deg, transparent, #000);
}
.hero-inner {
  display: grid; gap: clamp(2rem, 4vw, 3.5rem); align-items: center;
  padding: clamp(2.25rem, 4.5vw, 3.75rem) 0 clamp(2.5rem, 5vw, 4rem);
}
@media (min-width: 980px) { .hero-inner { grid-template-columns: minmax(0, 1.02fr) minmax(0, 1fr); } }
.hero-say { min-width: 0; }
.hero h1 {
  color: var(--ink); margin: 0 0 1rem;
  font-size: clamp(2.3rem, 1.35rem + 3.3vw, 4rem); line-height: 1.02; letter-spacing: -.025em;
}
.hero h1 em { font-style: normal; color: var(--accent); }
.hero-sub {
  font-size: clamp(1rem, .95rem + .25vw, 1.12rem); line-height: 1.55;
  color: var(--ink-2); max-width: 46ch; margin: 0 0 1.6rem;
}
.hero-actions { display: flex; flex-wrap: wrap; gap: .6rem; margin-bottom: 1.8rem; }

/* the promo pill above the headline */
.pchip {
  display: inline-flex; align-items: center; gap: .5rem; text-decoration: none;
  font-size: .8125rem; font-weight: 650; color: var(--ink); margin-bottom: 1.15rem;
  padding: .42rem .85rem .42rem .6rem; border-radius: 999px;
  background: var(--bg-2); border: 1px solid var(--line);
  transition: border-color .2s ease, background .2s ease;
}
.pchip:hover { color: var(--ink); border-color: var(--line-2); background: var(--bg-3); }
.pchip b { font-weight: 800; }
.pchip-sep { opacity: .38; }
.pchip-dot {
  flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 26%, transparent);
}
/* A promo featuring a product warms the chip, so the pill reads as the offer it leads
   rather than as the neutral eyebrow it is the rest of the time. */
.pchip--offer {
  background: color-mix(in srgb, var(--accent) 12%, var(--bg-2));
  border-color: color-mix(in srgb, var(--accent) 34%, var(--line));
}
.pchip--offer:hover { background: color-mix(in srgb, var(--accent) 18%, var(--bg-2)); }

/* The featured price, between the headline and the blurb. The figure is read out of
   catalog.json at build time -- there is no second copy of a price anywhere -- and the
   badge beside it is free text from promo.json that nothing enforces.

   Baseline-aligned rather than centred: "as low as", the figure and "each at 1,000" are
   one sentence set at three sizes, and centring them made the small text float. */
.pfprice {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .3rem .55rem;
  margin: 0 0 1.25rem;
}
.pfprice-lede { font-size: .8125rem; font-weight: 600; color: var(--ink-3); }
.pfprice-now {
  font-family: var(--display); font-size: clamp(1.75rem, 1.4rem + 1.5vw, 2.5rem);
  font-weight: 800; line-height: 1; letter-spacing: -.02em; color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.pfprice-unit { font-size: .8125rem; color: var(--ink-3); }
/* The badge wraps to its own line on a phone, so it gets a full flex row rather than
   sitting in the baseline run and dragging the price line taller. */
/* Amber rather than the orange accent, and dark text rather than white. This is 12px
   bold -- too small to count as large text -- so it needs the full 4.5:1, and white on
   --accent measures 3.09:1. Dark ink on --accent-2 measures 9.6:1 and is louder, which
   is what a deal badge wants anyway. The ink is LITERAL: --ink flips to near-white in
   dark mode, which would drop this to nothing on an amber pill. */
.pfoffer {
  flex: none; align-self: center;
  font-size: .75rem; font-weight: 800; letter-spacing: .02em;
  color: #0e1b24; background: var(--accent-2);
  padding: .34rem .7rem; border-radius: 999px;
}

/* the credential line -- was four .fact blocks at 1.75rem each */
.cred {
  display: flex; flex-wrap: wrap; align-items: center; gap: .45rem 1rem;
  list-style: none; margin: 0; padding: 1.2rem 0 0;
  border-top: 1px solid var(--line); font-size: .8125rem; color: var(--ink-3);
}
.cred li { min-width: 0; }
.cred li:not(:last-child)::after { content: " /"; margin-left: .55rem; opacity: .36; }
.cred b { font-weight: 800; color: var(--ink); }

/* The stage: one large product at a time, cycling. White on purpose -- the shots are
   photographed on white, so a white ground is the only one where the product has no
   visible edge and appears to float. It stays white in dark mode for the same reason:
   these are product photographs, not UI, and tinting the ground would put a grey box
   around every one of them.

   The shots are stacked and cross-faded, so they are absolutely positioned rather than
   laid out. That sidesteps the percentage-height trap described under "image wells":
   `inset: 0` resolves against the well's padding box, which is definite once the
   well's aspect-ratio resolves, so no percentage height is involved anywhere. */
.hero-art { display: block; min-width: 0; }
.stage {
  background: #fff; border: 1px solid var(--line); border-radius: var(--radius-lg);
  overflow: hidden; min-width: 0;
}
/* The padding moved off the card and onto the well so the footer can run edge to edge.
   overflow: hidden is what mitres the footer into the card's bottom corners -- safe on a
   div; it is on a <button> that it collapses the box (see the stock art tiles). */
.stage-well {
  --well-ratio: 1; position: relative; aspect-ratio: var(--well-ratio); min-height: 0;
  margin: clamp(.9rem, 2vw, 1.4rem);
}
.stage .shot {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: contain; padding: 4%;
  opacity: 0; transition: opacity .45s ease;
}
.stage .shot.is-on { opacity: 1; }
/* Without JS nothing ever gets .is-on removed or added, so the first shot has to be
   visible on its own. It carries .is-on from build.py for exactly that reason. */

/* A footer band rather than a rule and some text: its own surface, running the full
   width of the card and sitting into its bottom corners. Like the stage above it, the
   colours are literal in both themes -- a theme-tinted band under a white card would be
   a dark stripe on a white box in dark mode. */
.stage-foot {
  display: flex; align-items: center; gap: 1rem; justify-content: space-between;
  padding: .7rem clamp(.9rem, 2vw, 1.4rem);
  background: #f2f6f9; border-top: 1px solid #e3ebf1;
}
.stage-id {
  display: flex; align-items: baseline; gap: .55rem;
  margin: 0; min-width: 0; transition: opacity .2s ease;
}
/* Tabular figures so the chip does not change width as the number changes and nudge
   the product name sideways on every rotation. */
.stage-num {
  flex: none; font-size: .6875rem; font-weight: 800; letter-spacing: .04em;
  font-variant-numeric: tabular-nums;
  color: #5b7484; background: #e2eaf0; border-radius: 999px;
  padding: .2rem .48rem;
}
.stage-num::before { content: "#"; opacity: .55; }
.stage-title {
  min-width: 0; font-size: .8125rem; font-weight: 650; color: #16303f;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* A featured product does not rotate, so its footer carries the price where the
   rotating stage carries its dots. Literal colours like the rest of the footer -- the
   stage is a white lightbox in both themes. */
.stage-price {
  flex: none; margin: 0; font-size: .8125rem; font-weight: 800; color: #16303f;
  font-variant-numeric: tabular-nums;
}
.stage-dots { display: flex; align-items: center; gap: .4rem; flex: none; }
.stage-dot {
  width: 6px; height: 6px; padding: 0; border: 0; border-radius: 50%;
  background: #c0cfda; cursor: pointer;
  transition: background .25s ease, transform .25s ease;
}
.stage-dot:hover { background: #7e97a6; }
.stage-dot.is-on { background: var(--accent); transform: scale(1.4); }
/* The dots are the only way to steer the rotation, so they need a real hit area. The
   button stays 7px to look at and gets the rest as transparent padding via ::before --
   growing the button itself would push the row apart. */
.stage-dot::before { content: ""; position: absolute; width: 24px; height: 24px; transform: translate(-50%, -50%); }
.stage-dot { position: relative; }
.stage-dot::before { left: 50%; top: 50%; }

/* Stacked, the stage leads instead of trailing. Below 980px the hero is one column, and
   with the stage last the product sat under the buttons and the credential line: on a
   390x844 phone it began at y=641, so all anyone saw above the fold was the top edge of a
   white box. Ordered first it reads as the picture the headline is about, and the headline
   and both buttons still clear the fold.

   It also goes edge to edge rather than staying a rounded card. The negative inline margin
   cancels .wrap's gutter; the radius and side borders go with it, so the white reads as a
   band of the page rather than a slab dropped on the photograph. The white itself stays,
   because that is the ground these product shots were photographed on.

   The well turns 4:3 here. Square costs about 110px of height that a phone does not have
   spare, and it was the difference between the primary button sitting above the fold and
   just below it. */
@media (max-width: 979px) {
  .hero-inner { gap: 1.15rem; padding-block: 1rem 1.75rem; }
  .hero-art {
    order: -1; max-width: none;
    width: calc(100% + var(--gut) * 2); margin-inline: calc(var(--gut) * -1);
  }
  .stage { border-radius: 0; border-left: 0; border-right: 0; }
  /* The height cap matters more than the ratio. Full bleed at 768px, a 4:3 well is
     753 wide and 565 tall, which swallows the screen and shoves the headline off it --
     the max-width: 520px this replaced had been quietly preventing that. Capped, the
     band stays a band at every width and object-fit: contain centres the shot in it. */
  /* The width has to be stated. With an auto width, the ratio carries the 340px cap back
     into the inline axis, so from about 500px up the well shrank to 453px and sat against
     the left of its 645px card, and every shot sat off centre with it. A definite width
     stays put under the cap and object-fit: contain centres the shot. */
  .stage-well {
    --well-ratio: 4 / 3; width: calc(100% - 2.2rem); max-height: 340px;
    margin: .7rem auto;
  }
  /* Trimmed to clear the fold with the promo bar UP, which is the state a first-time
     visitor arrives in. The earlier numbers here were measured with the bar dismissed in
     localStorage from previous testing, and it costs 61px -- enough to push the primary
     button 2px under on a 390x844 phone. A short viewport (a 667px SE) still cannot fit
     the whole hero and is not worth compressing the rest of the design for. */
  .hero-sub, .hero-actions { margin-bottom: .95rem; }
  /* The featured-product price line is pure addition to a hero that was already trimmed
     to the millimetre above, so it pays its own way down here: a smaller figure, tighter
     rows, and the same trimmed margin as .hero-sub. The badge still gets its own row --
     "No setup fee on new orders" will not sit beside a price at 390px, and dropping the
     badge to save the row would remove the one line the promo exists to say.

     This does NOT get the buttons back above the fold on the smaller phones, and no
     amount of copy will: cutting the sub to 48 characters still left a 390x844 short.
     The only lever after that is the product shot, which is the one thing a product
     feature exists to show. Re-measured against the inset panel above, promo bar UP,
     as clearance between the primary button and the fold -- greeting figures are the
     ones in that rule's own table:

                     greeting      featured
       320x800         -67px        -106px
       360x800          +6px         -95px
       375x812          +6px         -69px
       390x844         +27px         -49px
       412x915         +82px          +7px   clears
       768x900         +12px         +41px   clears

     A feature costs about 75-100px over the greeting, which is the price line, the badge
     and a product name. On the phones that do not clear, everything that SELLS the promo
     still does -- shot, chip, headline, price, badge and sub are all above the fold at
     390x844 -- and the buttons are one short scroll down. Written down rather than
     quietly claimed.
  */
  .pfprice { gap: .2rem .45rem; margin-bottom: .95rem; }
  .pfprice-now { font-size: 1.85rem; }
}

/* ---------- hero, dressed for a promo ---------- */
/* While a promo carries a `backdrop`, the hero keeps the SAME photograph in both themes
   and changes the scrim over it: a white wash under dark ink for a light visitor, the
   dark wash under white ink for a dark one. The block used to commit to the dark one in
   both themes, on the reasoning that one photo cannot be light enough for dark ink and
   dark enough for white ink at once. True of the photo, but the scrim is not the photo --
   invert the scrim and the same file serves both.

   That was worth fixing rather than documenting. A backdrop covers most of the fold, so
   committing it to dark meant a light visitor's home page had no white on it anywhere and
   the theme toggle changed almost nothing they could see. It was reported three times as
   a broken light theme, which is what it looked like.

   So the LIGHT treatment is the base here and reads tokens like the rest of the file;
   `:root[data-theme="dark"]` restates the dark one below each rule it changes. The dark
   values are the literals this block has always used and are unchanged.

   Every number in the light set is measured against the photo's DARKEST pixel, which in
   this one is pure black -- so these hold for any photograph, not just the Smokies:

     scrim .90 white over #000 = #e6e6e6, and on that ground
       --ink        #0e1b24   14.0:1     headline, .cred b
       --ink-2      #3d515e    6.6:1     .hero-sub, .cred
       --accent-strong #d9552a 3.2:1     h1 em, large text, AA needs 3.0
       --ink-3      #6a7f8c    3.4:1     FAILS -- which is why .cred steps up to --ink-2
       --accent     #ef6a3d    2.5:1     FAILS -- which is why the em is --accent-strong

   Do not lower the light scrim under .90 anywhere text sits, and do not put --ink-3 or
   plain --accent on it. (--ink-3 is 4.2:1 on plain #fff too, so .cred is marginal on the
   undressed hero as well; that is a separate question about the base hero, not this one.)

   --hero-shot is the only part that changes per promo; build.py emits it inline on the
   home page, because the picture changes with the show and site.css is shared by all 59
   pages. Two files, swapped at 960px, so a phone does not pull a 1920px photograph. */
/* It is INSET rather than full bleed, and that is a theme fix, not a decoration. Run
   edge to edge it was 81% of a 900px desktop fold and 84% of a 375x844 phone one, so on
   the home page the only thing the theme toggle visibly changed was the 69px header
   strip -- the promo bar is dark in both themes too. The site read as stuck in a dark
   mode slightly unlike its own: the backdrop is #0a2130 under a blue-navy scrim, while
   --bg dark is #0c1519. Reported as "the toggle is buggy", and it was a fair reading.

   The gutter fixes it without touching what the block commits to: every colour in here
   is still literal, the scrim maths below is untouched, and the ink is still white in
   both themes. What changes is only that --bg now frames the panel, so the page around
   it says plainly which theme is on. The radius is --radius-lg because DESIGN.md calls
   a large standalone panel 22px; the inset is --gut because that is the page gutter.

   The top margin is 1rem on a desktop and .5rem on a phone, where it replaces rather
   than adds to .hero-inner's top padding. That matters: 61px of hero once pushed the
   primary button under the fold on a 390x844 phone, and this must not spend it again.
   It does not -- see the phone block below, which ends up ahead of where it started. */
.hero--backdrop {
  position: relative; isolation: isolate; overflow: hidden;
  background-color: var(--bg-2);   /* what shows while the photo is still loading */
  border-bottom: 0;
  color: var(--ink);
  margin: .5rem var(--gut) clamp(2rem, 4vw, 3rem);
  border-radius: var(--radius-lg);
}
@media (min-width: 980px) { .hero--backdrop { margin-top: 1rem; } }
@media (max-width: 979px) {
  /* The .5rem margin above stands in for .hero-inner's 1rem top padding rather than
     adding to it, so the panel starts 8px higher than the full-bleed hero did. */
  .hero--backdrop .hero-inner { padding-top: 0; }

  /* The stage stops bleeding past the panel and sits inside its gutter. That is not
     tidiness -- it is what pays for the inset and then some. A narrower well is a
     shorter well (4/3, and the 340px cap is not binding at phone widths), and the
     height it gives back is worth more than the inset and the re-wrap cost together.
     Measured as clearance between the primary button and the fold, promo bar up, each
     baseline taken by reverting these rules live on the same page:

                     full bleed      inset
       320x800         -67px         -67px
       360x800         -52px          +6px
       375x812         -51px          +6px
       390x844          +7px         +27px
       412x915         +86px         +82px
       768x900          +4px         +12px

     Better or equal everywhere but 412x915, which gives up 4px of an 86px margin. On a
     375x812 and a 360x800 the button is 51px UNDER the fold today and this is what
     brings it back up; 320x800 is the short viewport the full-bleed rule already gave
     up on, and it is no worse.

     The full-bleed rule above argued the white should read as a band of the page rather
     than a slab on the photograph. That still holds -- the page is now the panel, and
     the stage is a card on it again, so it takes its radius and its side borders back.
     14px inside the panel's 22px: a card nested in a panel, per DESIGN.md's radius rule.
     The border colour is stated literally like everything else in this block. */
  .hero--backdrop .hero-art { width: 100%; margin-inline: 0; }
  .hero--backdrop .stage { border: 1px solid var(--line); border-radius: var(--radius); }
  :root[data-theme="dark"] .hero--backdrop .stage { border-color: rgba(255,255,255,.5); }
}
/* A phone spends the top padding on the fold. A tablet has the height to spare, and with
   it at 0 the card sat flush against the top of a panel with 30px or more at its sides.
   It costs 16px of clearance between the primary button and the fold, promo bar up:
   768x1024 went from +115px to +99px. Portrait tablets clear easily. 768x900 goes from
   -9px to -25px and 979x800 from -106px to -122px, and neither cleared before this. */
@media (min-width: 700px) and (max-width: 979px) {
  .hero--backdrop .hero-inner { padding-top: 1rem; }
}
.hero--backdrop::before,
.hero--backdrop::after { content: ""; position: absolute; inset: 0; z-index: -1; }
.hero--backdrop::before {
  background-image: var(--hero-shot);
  background-size: cover; background-position: center 42%;
}
/* The scrim is heaviest on the left, where the headline is, and thins out over the
   mosaic on the right so the ridgelines stay visible behind the tiles. Both themes run
   the same shape; only the colour it washes towards changes. */
.hero--backdrop::after {
  /* White, held at >=.90 everywhere text sits and dropped to .74 over the stage, which
     carries its own white ground and needs no help. .90 is the floor because it is what
     keeps --ink-2 at 6.6:1 and the headline em at 3.2:1 over a black pixel; see the
     table at the top of this section. The photo survives as roughly 25 levels of
     ridgeline, which is the point -- on a light page it should read as texture, not as
     a picture. */
  background:
    linear-gradient(101deg, rgba(255,255,255,.94) 0%, rgba(255,255,255,.92) 30%,
                            rgba(255,255,255,.90) 48%, rgba(255,255,255,.80) 74%,
                            rgba(255,255,255,.74) 100%);
}
/* Held at >=.78 everywhere text sits, and lifted to .40 over the mosaic so the ridges
   are actually legible as ridges. .78 is not a guess: composited over the single
   brightest pixel in the photograph it still leaves the weakest text (.cred) at
   5.2:1, against a 4.5 requirement. Lighten it further and that colour fails. */
:root[data-theme="dark"] .hero--backdrop::after {
  background:
    linear-gradient(101deg, rgba(5,22,32,.88) 0%, rgba(5,22,32,.82) 30%,
                            rgba(5,22,32,.78) 48%, rgba(5,22,32,.50) 74%,
                            rgba(5,22,32,.40) 100%);
}
@media (max-width: 979px) {
  /* Stacked, the headline sits ON the picture rather than beside it, so the scrim runs
     top to bottom instead of left to right. It stays heavy to 74%, which is past the
     last line of text, and only then lifts -- the bottom quarter is the mosaic, whose
     tiles carry their own backing, so the ridges can show through there. */
  .hero--backdrop::after {
    background: linear-gradient(180deg, rgba(255,255,255,.93) 0%, rgba(255,255,255,.91) 74%,
                                        rgba(255,255,255,.82) 100%);
  }
  :root[data-theme="dark"] .hero--backdrop::after {
    background: linear-gradient(180deg, rgba(5,22,32,.86) 0%, rgba(5,22,32,.84) 74%,
                                        rgba(5,22,32,.58) 100%);
  }
}
/* Light: the ink is the page's own, so a dressed hero and an undressed one are the same
   headline. The two exceptions are measured, not stylistic -- .cred steps up from
   --ink-3 to --ink-2, and the em from --accent to --accent-strong, because neither
   original clears AA over the wash. Both are in the table at the top of this section. */
.hero--backdrop h1 { color: var(--ink); }
.hero--backdrop h1 em { color: var(--accent-strong); }
.hero--backdrop .hero-sub { color: var(--ink-2); }
.hero--backdrop .cred { color: var(--ink-2); border-top-color: var(--line-2); }
.hero--backdrop .cred b { color: var(--ink); }
:root[data-theme="dark"] .hero--backdrop h1 { color: #fff; }
:root[data-theme="dark"] .hero--backdrop h1 em { color: var(--accent-2); }
:root[data-theme="dark"] .hero--backdrop .hero-sub { color: #c6dbe6; }
:root[data-theme="dark"] .hero--backdrop .cred { color: #a7c3d2; border-top-color: rgba(255,255,255,.18); }
:root[data-theme="dark"] .hero--backdrop .cred b { color: #fff; }

/* The price line takes the same two measured exceptions the headline and .cred take over
   the wash, and for the same reasons. On light, --ink-3 is too faint against a .90 white
   scrim so the small text steps to --ink-2 like .cred, and the figure steps from --accent
   to --accent-strong: --accent is 2.5:1 there and fails even as large text. On dark the
   pair are the colours that block already uses. See the table at the top of this section. */
.hero--backdrop .pfprice-lede,
.hero--backdrop .pfprice-unit { color: var(--ink-2); }
.hero--backdrop .pfprice-now { color: var(--accent-strong); }
:root[data-theme="dark"] .hero--backdrop .pfprice-lede,
:root[data-theme="dark"] .hero--backdrop .pfprice-unit { color: #a7c3d2; }
:root[data-theme="dark"] .hero--backdrop .pfprice-now { color: var(--accent-2); }

/* The chip and the phone button are glass in both themes -- the same frosted panel,
   pushed towards white on a light page and towards the photo on a dark one, so the
   ridgelines still show through the control either way. */
.hero--backdrop .pchip {
  background: rgba(255,255,255,.72); border-color: var(--line-2); color: var(--ink);
  backdrop-filter: blur(6px);
}
.hero--backdrop .pchip:hover { background: rgba(255,255,255,.88); color: var(--ink); border-color: var(--line-2); }
:root[data-theme="dark"] .hero--backdrop .pchip {
  background: rgba(255,255,255,.12); border-color: rgba(255,255,255,.26); color: #ffe2bd;
}
:root[data-theme="dark"] .hero--backdrop .pchip:hover { background: rgba(255,255,255,.2); color: #fff; }
/* The phone button is .btn-ghost, which is ink-on-light. On the dark wash it would
   vanish and on the light one it would float with no ground, so it is restyled rather
   than swapped in the markup -- the button class never depends on the promo. */
.hero--backdrop .btn-ghost {
  background: rgba(255,255,255,.72); color: var(--ink);
  border-color: var(--line-2); backdrop-filter: blur(6px);
}
.hero--backdrop .btn-ghost:hover { background: rgba(255,255,255,.9); color: var(--ink); }
:root[data-theme="dark"] .hero--backdrop .btn-ghost {
  background: rgba(255,255,255,.12); color: #fff; border-color: rgba(255,255,255,.32);
}
:root[data-theme="dark"] .hero--backdrop .btn-ghost:hover { background: rgba(255,255,255,.22); color: #fff; }
/* The stage stays white over the photograph -- it is a lightbox for the product, and
   that reads as deliberate against the ridgelines. Only its edge and shadow soften; on
   a light page it is a card on the page again, so it takes the page's own. */
.hero--backdrop .stage { border-color: var(--line); box-shadow: var(--shadow-2); }
:root[data-theme="dark"] .hero--backdrop .stage { border-color: rgba(255,255,255,.5); box-shadow: 0 24px 60px rgba(2,14,22,.45); }


/* ---------- sections ---------- */
.sect { padding: clamp(3rem, 7vw, 5.5rem) 0; }
.sect-alt { background: var(--bg-2); }
.sect-head { max-width: 58ch; margin-bottom: clamp(1.75rem, 4vw, 2.75rem); }
.sect-head.center { margin-inline: auto; text-align: center; }
.sect-head p { color: var(--ink-2); font-size: 1.05rem; margin-bottom: 0; }
.kicker {
  font-size: .75rem; font-weight: 700; letter-spacing: .13em; text-transform: uppercase;
  color: var(--accent); margin-bottom: .6rem; display: block;
}

/* ---------- category cards ---------- */
.cat-grid { display: grid; gap: 1.1rem; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.cat-card {
  position: relative; display: flex; flex-direction: column; overflow: hidden;
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius);
  text-decoration: none; color: var(--ink);
  transition: transform .2s ease, box-shadow .25s ease, border-color .2s ease;
}
.cat-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-2); border-color: var(--line-2); color: var(--ink); }
.cat-card-img { aspect-ratio: 16 / 10; background: var(--bg-3); display: grid; place-items: center; overflow: hidden; }
.cat-card-img img { width: 100%; height: 100%; object-fit: cover; transition: transform .4s cubic-bezier(.22,1,.36,1); }
.cat-card:hover .cat-card-img img { transform: scale(1.05); }
.cat-card-body { padding: 1.15rem 1.25rem 1.35rem; display: flex; flex-direction: column; gap: .35rem; flex: 1; }
.cat-card-body h3 { margin: 0; font-size: 1.2rem; }
.cat-card-body p { color: var(--ink-2); font-size: .9375rem; margin: 0; }
.cat-card-more { margin-top: auto; padding-top: .8rem; font-weight: 700; font-size: .875rem; color: var(--accent); display: inline-flex; align-items: center; gap: .35rem; transition: gap .2s ease; }
.cat-card:hover .cat-card-more { gap: .6rem; }

/* ---------- image wells ---------- */
/* .prod-img, .pd-media and .mtile are all the same thing: a fixed-shape well holding
   a photo of some other shape. All three were built the same wrong way and all three are
   fixed here together, so read this once and the three rules below make sense.

   The old rule was "max-height: 100%; width: auto" on the image, and it failed twice over.
   A percentage height on a child only resolves when the parent's height is definite, and
   a height that comes from "aspect-ratio" is not definite -- so max-height computed to
   none, the image sized itself off max-width instead, and a 318x512 photo came out 316px
   tall in a 140px well. The well then grew to fit it, because a flex or grid item's
   automatic minimum size is its content's height and that minimum outranks aspect-ratio.
   So no well kept its shape: measured on the home page they came out 169, 231, 243, 271
   and 346px tall, which is why the card bodies stopped lining up across a row.

   The fix has to avoid percentages on the image entirely. The image gets the well's own
   aspect-ratio and "width: 100%" -- a percentage WIDTH, which always resolves -- so its
   box is exactly the well's shape with no percentage height anywhere. "object-fit:
   scale-down" then fits the photo inside that box and, unlike "contain", never blows a
   small photo up past its natural size. "min-height: 0" on the well stops the automatic
   minimum from ever stretching it again.

   Two things to keep in mind when touching these:
   - Stretching the image to fill the well instead (align-self, height: 100%) does not
     work. Percentages fail as above, and "stretch" behaves as "start" on replaced
     elements, so an <img> never stretches to its grid area.
   - The padding lives on the children, not on the well. It cannot live on the well: the
     image's aspect-ratio would then describe the content box while the well's describes
     the border box, and the image would overflow by the difference. --well-pad keeps the
     figure in one place; .prod-ph is the no-photo placeholder and takes the same inset.
     The hero mosaic tile (.mtile) is the third of these and pads its image bottom-heavy,
     so the photo never sits on top of its own caption. */
.prod-grid { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); }
.prod {
  display: flex; flex-direction: column; text-decoration: none; color: var(--ink);
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden;
  transition: transform .18s ease, box-shadow .25s ease, border-color .2s ease;
}
.prod:hover { transform: translateY(-3px); box-shadow: var(--shadow-2); border-color: var(--line-2); color: var(--ink); }
.prod-img {
  --well-ratio: 4 / 3; --well-pad: .9rem;
  aspect-ratio: var(--well-ratio); background: var(--well-ground); display: grid; place-items: center;
  border-bottom: 1px solid var(--line); min-height: 0;
}
.prod-img img { width: 100%; aspect-ratio: var(--well-ratio); object-fit: scale-down; padding: var(--well-pad); }
.prod-ph { color: #b6c6cf; display: grid; place-items: center; gap: .3rem; font-size: .6875rem; letter-spacing: .1em; text-transform: uppercase; font-weight: 700; text-align: center; padding: var(--well-pad, .9rem); }
.prod-body { padding: .85rem .95rem 1rem; display: flex; flex-direction: column; gap: .2rem; flex: 1; }
.prod-sku { font-size: .75rem; font-weight: 700; letter-spacing: .07em; color: var(--ink-3); }
.prod-name { font-family: var(--display); font-weight: 700; font-size: 1.02rem; line-height: 1.2; }
.prod-desc { font-size: .8125rem; color: var(--ink-3); line-height: 1.4; }
.prod-price { margin-top: auto; padding-top: .6rem; font-size: .875rem; color: var(--ink-2); }
.prod-price b { font-family: var(--display); font-size: 1.1rem; color: var(--ink); }
.prod-price .call { color: var(--brand-2); font-weight: 600; }
.prod-min { display: block; margin-top: .15rem; font-size: .75rem; color: var(--ink-2); }
.badge {
  align-self: flex-start; font-size: .6875rem; font-weight: 700; text-transform: uppercase; letter-spacing: .08em;
  padding: .2rem .5rem; border-radius: 999px; background: var(--bg-3); color: var(--ink-2); margin-top: .3rem;
}
.badge-off { background: #fde8e0; color: #a03a12; }
:root[data-theme="dark"] .badge-off { background: #40200f; color: #ffb08c; }

/* ---------- filter bar ---------- */
.filters {
  position: sticky; top: var(--header-h); z-index: 20;
  background: color-mix(in srgb, var(--bg) 90%, transparent); backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line); padding: .85rem 0;
  margin-bottom: 2rem;
}
.filters-inner { display: flex; flex-wrap: wrap; gap: .6rem; align-items: center; }
.search { position: relative; flex: 1 1 240px; min-width: 200px; }
.search input {
  width: 100%; font: inherit; font-size: .9375rem; color: var(--ink);
  padding: .62rem .9rem .62rem 2.35rem; border: 1px solid var(--line-2); border-radius: 999px;
  background: var(--bg); appearance: none;
}
.search input::placeholder { color: var(--ink-3); }
.search svg { position: absolute; left: .85rem; top: 50%; transform: translateY(-50%); color: var(--ink-3); pointer-events: none; }
.chips { display: flex; gap: .4rem; flex-wrap: wrap; }
.chip {
  font: inherit; font-size: .875rem; font-weight: 600; cursor: pointer;
  padding: .5rem .9rem; border-radius: 999px; border: 1px solid var(--line-2);
  background: var(--bg); color: var(--ink-2); white-space: nowrap;
  transition: background-color .15s, color .15s, border-color .15s;
}
.chip:hover { border-color: var(--ink-3); color: var(--ink); }
.chip[aria-pressed="true"] { background: var(--brand); border-color: var(--brand); color: #fff; }
:root[data-theme="dark"] .chip[aria-pressed="true"] { background: var(--brand-2); border-color: var(--brand-2); color: #06202b; }
.result-count { font-size: .875rem; color: var(--ink-3); margin-left: auto; }
.empty { text-align: center; padding: 4rem 1rem; color: var(--ink-3); }

/* Colours come in ranges -- foam runs in 20, fabric in all of them -- so the page shows
   which is which instead of one undifferentiated wall. */
.color-set + .color-set { margin-top: 2.5rem; }
.color-set h3 { display: flex; align-items: baseline; gap: .5rem; margin: 0 0 .3rem; }
.color-set h3 span { font-family: var(--font); font-size: .8125rem; font-weight: 400; color: var(--ink-3); }
.color-note { font-size: .875rem; color: var(--ink-2); margin: 0 0 .8rem; max-width: 70ch; }

/* ---------- product detail ---------- */
.crumbs { font-size: .875rem; color: var(--ink-3); padding: 1.25rem 0 0; }
.crumbs a { color: var(--ink-3); text-decoration: none; }
.crumbs a:hover { color: var(--accent); text-decoration: underline; }
.crumbs i { font-style: normal; margin: 0 .4rem; opacity: .5; }

.pd { display: grid; gap: clamp(1.75rem, 4vw, 3.5rem); padding: 1.5rem 0 clamp(3rem, 6vw, 5rem); }
@media (min-width: 860px) { .pd { grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); align-items: start; } }
.pd-media {
  background: var(--well-ground); border: 1px solid var(--line); border-radius: var(--radius-lg);
  --well-ratio: 4 / 3; --well-pad: clamp(1.5rem, 4vw, 3rem);
  display: grid; place-items: center; aspect-ratio: var(--well-ratio);
  min-height: 0;
}
.pd-media img { width: 100%; aspect-ratio: var(--well-ratio); object-fit: scale-down; padding: var(--well-pad); }
@media (min-width: 860px) { .pd-info { position: sticky; top: calc(var(--header-h) + 1.5rem); } }
.pd-sku { font-size: .8125rem; font-weight: 700; letter-spacing: .1em; color: var(--accent); text-transform: uppercase; }
.pd h1 { font-size: clamp(1.9rem, 1.4rem + 2vw, 2.9rem); margin: .2rem 0 .6rem; }
.pd-lede { font-size: 1.08rem; color: var(--ink-2); }
.pd-tags { display: flex; gap: .4rem; flex-wrap: wrap; margin: 1.1rem 0 1.5rem; }
.tag {
  font-size: .75rem; font-weight: 600; padding: .3rem .7rem; border-radius: 999px;
  background: var(--bg-2); border: 1px solid var(--line); color: var(--ink-2); text-decoration: none;
}
a.tag:hover { border-color: var(--accent); color: var(--accent); }

.pricebox { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; margin-bottom: 1.5rem; background: var(--bg-2); }
.pricebox-head { padding: 1rem 1.15rem; border-bottom: 1px solid var(--line); display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.pricebox-head h2 { font-size: 1rem; margin: 0; font-family: var(--font); font-weight: 700; }
.pricebox-head .from { font-size: .8125rem; color: var(--ink-3); text-align: right; }
.pricebox-head .from b { font-family: var(--display); font-size: 1.5rem; color: var(--ink); display: block; line-height: 1.1; }
table.tiers { width: 100%; border-collapse: collapse; font-size: .9375rem; background: var(--bg); }
table.tiers th, table.tiers td { padding: .7rem 1.15rem; text-align: left; }
table.tiers th { font-size: .75rem; text-transform: uppercase; letter-spacing: .08em; color: var(--ink-3); font-weight: 700; border-bottom: 1px solid var(--line); }
table.tiers td { border-bottom: 1px solid var(--line); }
table.tiers tr:last-child td { border-bottom: 0; }
table.tiers td:last-child { text-align: right; font-weight: 700; font-variant-numeric: tabular-nums; }
table.tiers tr:last-child td:last-child { color: var(--ok); }
.pricebox-note { padding: .8rem 1.15rem; font-size: .8125rem; color: var(--ink-3); background: var(--bg-2); }

.order-cta { display: flex; gap: .7rem; flex-wrap: wrap; margin-bottom: 1.25rem; }
.pd-meta { font-size: .875rem; color: var(--ink-3); }
.pd-meta a { color: var(--brand-2); }

/* ---------- feature strip ---------- */
.feat-grid { display: grid; gap: 1.5rem 2rem; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
.feat h3 { font-size: 1.08rem; margin-bottom: .35rem; }
.feat p { color: var(--ink-2); font-size: .9375rem; margin: 0; }
.feat-ico {
  width: 40px; height: 40px; border-radius: 11px; display: grid; place-items: center; margin-bottom: .85rem;
  background: color-mix(in srgb, var(--brand-2) 14%, transparent); color: var(--brand-2);
}

/* ---------- steps ---------- */
.steps { counter-reset: s; display: grid; gap: 1.25rem; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.step { position: relative; padding-top: 3.1rem; }
.step::before {
  counter-increment: s; content: counter(s);
  position: absolute; top: 0; left: 0;
  width: 2.35rem; height: 2.35rem; border-radius: 50%;
  background: var(--accent); color: #fff; font-family: var(--display); font-weight: 800;
  display: grid; place-items: center; font-size: 1.1rem;
}
.step h3 { font-size: 1.05rem; margin-bottom: .3rem; }
.step p { font-size: .9375rem; color: var(--ink-2); margin: 0; }

/* ---------- CTA band ---------- */
.cta-band {
  background: linear-gradient(135deg, #0b3a52 0%, #0a2637 100%);
  color: #fff; border-radius: var(--radius-lg); padding: clamp(2rem, 5vw, 3.5rem);
  display: grid; gap: 1.5rem; align-items: center; box-shadow: var(--shadow-2);
}
@media (min-width: 800px) { .cta-band { grid-template-columns: 1fr auto; } }
.cta-band h2 { color: #fff; margin-bottom: .4rem; }
.cta-band p { color: #b9d3e0; margin: 0; max-width: 52ch; }
.cta-band-actions { display: flex; gap: .7rem; flex-wrap: wrap; }

/* ---------- colors & swatches ---------- */
.color-grid { display: grid; gap: .75rem; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
.color {
  border-radius: var(--radius); aspect-ratio: 5/3; position: relative; overflow: hidden;
  border: 1px solid rgba(0,0,0,.08);
  display: flex; align-items: flex-end; padding: .65rem .75rem;
  font-weight: 700; font-size: .875rem;
}
.color code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .6875rem; opacity: .7; position: absolute; top: .6rem; left: .75rem; }
.swatch-grid { display: grid; gap: .75rem; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); }
.swatch { border-radius: var(--radius); overflow: hidden; border: 1px solid var(--line); background: var(--bg); }
.swatch img { width: 100%; aspect-ratio: 1; object-fit: cover; }
.swatch span { display: block; padding: .5rem .6rem; font-size: .8125rem; font-weight: 700; color: var(--ink-2); }

/* ---------- stock art ---------- */
/* Hub: one card per collection. Title and description lead, so you know what
   you are looking at before you look at it. */
.art-grid { display: grid; gap: 1.25rem; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
.art-card {
  border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden;
  background: var(--bg); isolation: isolate;
  display: flex; flex-direction: column; text-decoration: none; color: var(--ink);
  transition: transform .2s ease, box-shadow .25s ease, border-color .2s ease;
}
.art-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-2); border-color: var(--line-2); color: var(--ink); }
.art-head { padding: 1rem 1.15rem 1.05rem; border-bottom: 1px solid var(--line); display: flex; flex-direction: column; flex: 1; }
.art-head h3 { margin: 0 0 .2rem; font-size: 1.1rem; }
.art-head p { margin: 0; color: var(--ink-3); font-size: .875rem; line-height: 1.45; }
.art-more { margin-top: auto; padding-top: .8rem; font-weight: 700; font-size: .875rem; color: var(--accent); display: inline-flex; align-items: center; gap: .35rem; transition: gap .2s ease; }
.art-card:hover .art-more { gap: .6rem; }
/* The art is black line work on a white field, which is a white block in dark mode.
   Inverting turns the line work white and the field black, and screening then drops
   that black field into the surface behind it -- so the imprint is left floating on
   the card, which is closer to how it prints than a white patch is. The ground comes
   from the parent, not the image, so a tile that has not loaded yet looks the same as
   one that has. "isolation" keeps the blend inside the card. */
.art-card img {
  width: 100%; margin-top: auto;
  filter: var(--art-ink); mix-blend-mode: var(--art-blend);
}

/* Collection page: every design in the collection. */
.chips--art { margin: 1.5rem 0 .25rem; }
.chips--art .chip { text-decoration: none; }
.chips--art .chip small { font-weight: 600; opacity: .6; margin-left: .3rem; }
.chips--art .chip[aria-current="page"] { background: var(--brand); border-color: var(--brand); color: #fff; }
:root[data-theme="dark"] .chips--art .chip[aria-current="page"] { background: var(--brand-2); border-color: var(--brand-2); color: #06202b; }

.art-tiles { display: grid; gap: .75rem; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); }
.art-tile {
  margin: 0; border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden;
  background: var(--art-ground); isolation: isolate;
}
.art-tile img {
  display: block; width: 100%; aspect-ratio: 1; object-fit: contain;
  filter: var(--art-ink); mix-blend-mode: var(--art-blend);
}
/* The design code is printed inside the art as well; repeating it as text makes it
   selectable, searchable and readable to a screen reader. */
.art-tile figcaption {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .75rem; font-weight: 700;
  letter-spacing: .04em; text-align: center; padding: .4rem .3rem .5rem;
  color: var(--ink-2); background: var(--bg); border-top: 1px solid var(--line);
}
@media (max-width: 560px) { .art-tiles { grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); } }
/* On paper the art is black on white again -- printing the dark-mode treatment would
   put white line work on a white page. */
@media print {
  .art-tile { background: #fff; }
  .art-tile img, .art-card img { filter: none; mix-blend-mode: normal; background: #fff; }
}

/* ---------- FAQ ---------- */
details.qa {
  border: 1px solid var(--line); border-radius: var(--radius); margin-bottom: .7rem; background: var(--bg);
  overflow: hidden; transition: border-color .2s ease, box-shadow .2s;
}
details.qa[open] { border-color: var(--line-2); box-shadow: var(--shadow-1); }
details.qa summary {
  cursor: pointer; list-style: none; padding: 1rem 1.25rem; font-weight: 700; font-size: 1.02rem;
  display: flex; align-items: center; justify-content: space-between; gap: 1rem; font-family: var(--display);
}
details.qa summary::-webkit-details-marker { display: none; }
details.qa summary::after {
  content: ""; flex: none; width: 10px; height: 10px; border-right: 2px solid var(--ink-3); border-bottom: 2px solid var(--ink-3);
  transform: rotate(45deg) translateY(-2px); transition: transform .2s ease;
}
details.qa[open] summary::after { transform: rotate(225deg) translateY(-2px); }
details.qa summary:hover { color: var(--accent); }
/* The accordions run the full width of the page so their left edge lines up with
   the heading above them, but the answers are capped to a readable measure rather
   than stretched across 1200px. Same trick as ".sect-head". */
.qa-body { padding: 0 1.25rem 1.25rem; color: var(--ink-2); max-width: 78ch; }
.faq-group-intro { max-width: 62ch; }
.qa-body > :last-child { margin-bottom: 0; }
.qa-body strong { color: var(--ink); }

.callout {
  border-left: 3px solid var(--accent); background: var(--bg-2); padding: 1rem 1.25rem;
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0; margin: 1.5rem 0; font-size: .9375rem; color: var(--ink-2);
}
.callout > :last-child { margin-bottom: 0; }

/* ---------- contact ---------- */
.contact-grid { display: grid; gap: 2rem; }
@media (min-width: 820px) { .contact-grid { grid-template-columns: 1fr 1fr; gap: 3rem; } }
.quote-actions { display: flex; flex-wrap: wrap; gap: .65rem; }
.quote-status { margin: .75rem 0; font-size: .875rem; color: var(--ink-2); }
.quote-status:empty { display: none; }
.info-card { border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 1.5rem; background: var(--bg-2); }
.info-card dl { margin: 0; }
.info-row { display: flex; gap: .9rem; padding: .8rem 0; border-bottom: 1px solid var(--line); }
.info-row:last-child { border-bottom: 0; padding-bottom: 0; }
.info-row:first-child { padding-top: 0; }
.info-row svg { flex: none; color: var(--brand-2); margin-top: .2rem; }
.info-row dt { font-size: .75rem; text-transform: uppercase; letter-spacing: .08em; color: var(--ink-3); font-weight: 700; margin-bottom: .1rem; }
.info-row dd { margin: 0; font-weight: 600; }
.info-row dd a { text-decoration: none; }
.info-row dd small { display: block; font-weight: 400; font-size: .875rem; color: var(--ink-3); }

.field { margin-bottom: 1rem; }
.field label { display: block; font-size: .875rem; font-weight: 700; margin-bottom: .35rem; }
.field input, .field select, .field textarea {
  width: 100%; font: inherit; font-size: .9375rem; color: var(--ink); background: var(--bg);
  padding: .7rem .9rem; border: 1px solid var(--line-2); border-radius: var(--radius-sm);
}
.field textarea { min-height: 130px; resize: vertical; }
.field-row { display: grid; gap: 1rem; }
@media (min-width: 560px) { .field-row { grid-template-columns: 1fr 1fr; } }

/* ---------- footer ---------- */
.ftr { background: var(--bg-2); border-top: 1px solid var(--line); padding: clamp(2.5rem, 5vw, 4rem) 0 2rem; }
.ftr-grid { display: grid; gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
.ftr-brand { max-width: 34ch; }
.ftr-brand p { font-size: .875rem; color: var(--ink-3); margin: .8rem 0 0; }
.ftr h4 { font-size: .75rem; text-transform: uppercase; letter-spacing: .1em; color: var(--ink-3); font-family: var(--font); margin-bottom: .9rem; }
.ftr ul { list-style: none; padding: 0; margin: 0; display: grid; gap: .5rem; }
.ftr a { color: var(--ink-2); text-decoration: none; font-size: .9375rem; }
.ftr a:hover { color: var(--accent); }
.ftr-bot {
  margin-top: 2.5rem; padding-top: 1.5rem; border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; gap: .75rem 1.5rem; justify-content: space-between;
  font-size: .8125rem; color: var(--ink-3);
}

/* ---------- utilities ---------- */
.stack > * + * { margin-top: 1rem; }
.mb-0 { margin-bottom: 0; }
.center { text-align: center; }
.muted { color: var(--ink-3); }
.tabular { font-variant-numeric: tabular-nums; }
[hidden] { display: none !important; }
.scroll-x { overflow-x: auto; }

/* ---------- order builder ---------- */
.order-layout { display: grid; gap: 2rem; align-items: start; }
@media (min-width: 940px) { .order-layout { grid-template-columns: minmax(0, 1fr) 340px; gap: 2.5rem; } }
.order-layout > * { min-width: 0; }

.olabel { display: block; font-size: .875rem; font-weight: 700; margin-bottom: .4rem; }
.ohint { font-size: .75rem; font-weight: 400; color: var(--ink-3); }
.ohint--block { display: block; margin: .5rem 0 0; line-height: 1.45; }
.ohint--pad { padding: 0 1.1rem 1.1rem; margin-top: 0; }
/* A hint that leads its field instead of trailing it -- it has to clear the label
   above and sit tight to the input below. */
.ohint--lead { margin: -.1rem 0 .45rem; }

.order-add { border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 1.25rem; background: var(--bg-2); }
.order-add-row { display: flex; gap: .6rem; flex-wrap: wrap; }
.order-add-row select {
  flex: 1 1 260px; min-width: 0; font: inherit; font-size: .9375rem; color: var(--ink);
  background: var(--bg); padding: .7rem .9rem; border: 1px solid var(--line-2); border-radius: var(--radius-sm);
}

.order-lines { display: grid; gap: 1rem; margin: 1.5rem 0; }
/* min-width:0 is doing the job overflow:hidden used to. As a grid item the card's
   automatic minimum size is its content's min-content width, which overflow:hidden
   silently forced to zero; dropping that for sticky brought the overflow back. */
.oline { border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--bg); min-width: 0; }

/* The artwork label and the product title ride together and stay on screen, so a long
   card never leaves you wondering which item you are filling in. Each bar is bounded by
   its own card, so the next one pushes it off rather than stacking. */
.oline-sentinel { height: 1px; margin-bottom: -1px; }
.oline-top {
  position: sticky; top: var(--header-h); z-index: 3;
  background: var(--bg); border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  transition: box-shadow .18s ease, padding .18s ease;
}
/* Stuck, it tightens: two stacked headers should not eat the screen on a phone. */
.oline-top.is-stuck {
  box-shadow: 0 6px 16px -8px rgba(11,58,82,.35);
  border-bottom: 1px solid var(--line);
}
.oline-top.is-stuck .oline-artrow { padding-top: .45rem; }
.oline-top.is-stuck .oline-head { padding-top: .25rem; padding-bottom: .5rem; }
.oline-top.is-stuck .oline-desc { display: none; }
.oline-top.is-stuck .oline-media { width: 40px; height: 32px; }
@media (prefers-reduced-motion: reduce) { .oline-top { transition: none; } }
.oline-head { display: flex; align-items: center; gap: .9rem; padding: .9rem 1.1rem; }
.oline-top:not(.is-stuck) .oline-head { border-bottom: 1px solid var(--line); }
.oline-media { flex: none; width: 62px; height: 48px; background: #fff; border: 1px solid var(--line); border-radius: var(--radius-sm); display: grid; place-items: center; overflow: hidden; }
.oline-media img { max-height: 100%; max-width: 100%; width: auto; object-fit: contain; }
.oline-ph { font-size: .6875rem; font-weight: 700; color: var(--ink-3); }
.oline-id { flex: 1; min-width: 0; }
.oline-sku { display: flex; align-items: center; gap: .45rem; flex-wrap: wrap; font-size: .6875rem; font-weight: 700; letter-spacing: .07em; color: var(--ink-3); }
/* The artwork label rides above the product identity -- which design this block is
   for outranks which koolie it is printed on. */
.oline-artrow { padding: .8rem 1.1rem 0; }
/* The label belongs to the card title, so it sits close to it rather than floating
   halfway between the card edge and the product. */
.oline-artrow + .oline-head { padding-top: .4rem; }
.oline-art {
  display: inline-block; max-width: 100%; padding: .2rem .6rem;
  border: 1px solid var(--line-2); border-radius: 999px;
  font-size: .75rem; font-weight: 700; letter-spacing: .04em; color: var(--ink-2);
  background: var(--bg-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Quantity: a stepper plus the price breaks, rather than a bare spinner nobody can hit. */
.ofield--qty { display: flex; flex-wrap: wrap; align-items: center; align-content: flex-start; gap: .35rem .5rem; }
.ofield.ofield--qty label { width: 100%; margin-bottom: 0; }
.oqty { display: flex; align-items: stretch; border: 1px solid var(--line-2); border-radius: 999px; overflow: hidden; background: var(--bg); }
.oqty-btn {
  flex: none; width: 42px; font: inherit; font-size: 1.15rem; font-weight: 700; line-height: 1;
  color: var(--ink-2); background: var(--bg-2); border: 0; cursor: pointer;
  transition: background-color .15s ease, color .15s ease;
}
.oqty-btn:hover { background: var(--brand-2); color: var(--brand-ink); }
.oqty-btn:active { background: var(--brand); color: var(--brand-ink); }
.ofield .oqty input {
  width: 5.5rem; padding: .6rem .3rem; text-align: center;
  font: inherit; font-size: 1.05rem; font-weight: 700; font-variant-numeric: tabular-nums;
  color: var(--ink); background: transparent; border: 0; border-inline: 1px solid var(--line);
  -moz-appearance: textfield;
}
.ofield .oqty input::-webkit-outer-spin-button,
.ofield .oqty input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.ofield .oqty input:focus { outline: none; background: var(--bg-2); }
.oqty-step { font-size: .75rem; color: var(--ink-3); }
.oqty-breaks { display: flex; flex-wrap: wrap; gap: .35rem; width: 100%; margin-top: .2rem; }
.oqty-chip {
  display: flex; flex-direction: column; align-items: center; gap: .05rem;
  padding: .3rem .6rem; font: inherit; line-height: 1.15; cursor: pointer;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: transparent; color: var(--ink-2);
  transition: border-color .15s ease, background-color .15s ease, color .15s ease;
}
.oqty-chip b { font-size: .8125rem; font-variant-numeric: tabular-nums; }
.oqty-chip small { font-size: .6875rem; color: var(--ink-3); }
.oqty-chip:hover { border-color: var(--brand-2); color: var(--ink); }
.oqty-chip.is-on { border-color: var(--brand-2); background: color-mix(in srgb, var(--brand-2) 12%, transparent); color: var(--ink); }
.oqty-chip.is-on small { color: var(--brand-2); }
@media (prefers-reduced-motion: reduce) { .oqty-btn, .oqty-chip { transition: none; } }

/* Numbered steps. Picking the product is step 1 and is what created the card, so the
   steps inside it run 1..3 over what is left to decide. */
.ostep { border-top: 1px solid var(--line); }
.ostep-head {
  display: flex; align-items: center; gap: .6rem; width: 100%;
  padding: .75rem 1.1rem; font: inherit; font-size: .875rem; font-weight: 700;
  color: var(--ink); background: transparent; border: 0; cursor: pointer; text-align: left;
}
.ostep-head:hover { color: var(--brand-2); }
.ostep-n {
  flex: none; display: grid; place-items: center; width: 22px; height: 22px;
  border-radius: 50%; border: 1px solid var(--line-2);
  font-size: .75rem; font-weight: 700; color: var(--ink-3); background: var(--bg-2);
}
.ostep.is-done .ostep-n { border-color: var(--ok); color: var(--ok); }
.ostep-title { flex: 1; }
.ostep-state {
  font-weight: 600; font-size: .75rem; color: var(--ink-3);
  max-width: 45%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ostep.is-done .ostep-state { color: var(--ok); }
.ostep-head svg { flex: none; transition: transform .18s ease; }
.ostep.is-shut .ostep-head svg { transform: rotate(-90deg); }
.ostep.is-shut .ostep-body { display: none; }
.ostep-body { padding-bottom: .4rem; }

/* A line that cannot carry its own design is short one thing -- pieces -- and the fix is
   a number it can type. So the card dims down to that number and the panel under it.

   The dimming is applied to each sibling rather than to the card, because opacity on a
   parent cannot be undone by a child: `opacity` creates a stacking context and everything
   inside is composited through it, so `.oline { opacity:.4 }` with `.ofield--qty
   { opacity:1 }` leaves the field at .4 like everything else. Each region is faded on its
   own and the quantity field is simply never named. */
.oline.is-blocked .ostep[data-step="art"],
.oline.is-blocked .ostep[data-step="colors"],
.oline.is-blocked > .oline-fold > .ofield,
.oline.is-blocked .odeco,
.oline.is-blocked .oline-body > .ofield:not(.ofield--qty),
.oline.is-blocked .oink {
  opacity: .38;
  transition: opacity .18s ease;
}
/* Dimmed is not disabled -- everything stays usable, it is just not where to look. */
.oline.is-blocked .ostep[data-step="art"]:focus-within,
.oline.is-blocked .ostep[data-step="colors"]:focus-within,
.oline.is-blocked .oink:focus-within { opacity: 1; }

/* And the number itself, lit. */
.oline.is-blocked .ofield--qty label { color: var(--ink); }
.oline.is-blocked .ofield--qty .oqty input {
  border-color: var(--warn, #a86a00);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--warn, #a86a00) 18%, transparent);
}

/* The artwork step, reduced to saying why it is not asking. */
.oartlock { padding: 0 1.1rem .9rem; }
.oartlock-stub { margin: 0; font-size: .8125rem; line-height: 1.45; color: var(--ink-3); }
.oartlock-stub b { color: var(--ink-2); }

/* The panel under the quantity input. */
.ofield-fix:empty { display: none; }
.oblock {
  margin-top: .6rem; padding: .7rem .8rem;
  background: var(--bg-2); border: 1px solid var(--line);
  border-left: 3px solid var(--warn, #a86a00); border-radius: var(--radius-sm);
}
.oblock-why {
  margin: 0; font-size: .8125rem; line-height: 1.5; color: var(--ink-2);
}
.oblock-why b { color: var(--ink); }
.oblock-alt { margin: .6rem 0 .35rem; font-size: .75rem; color: var(--ink-3); }
.oblock-picks { display: flex; flex-wrap: wrap; gap: .4rem; }
.oblock-pick {
  font: inherit; font-size: .8125rem; font-weight: 600; color: var(--ink);
  padding: .4rem .65rem; background: var(--bg); cursor: pointer;
  border: 1px solid var(--line-2); border-radius: var(--radius-sm);
  max-width: 100%; overflow-wrap: anywhere; text-align: left;
}
.oblock-pick span { color: var(--brand-2); }
.oblock-pick:hover { border-color: var(--brand-2); }
.oblock-pick--all { border-style: dashed; color: var(--ink-2); font-weight: 500; }

/* Ink colors: which ink goes on the press, not just how many. */
/* Decoration: the same product screen printed or sublimated, priced differently. */
.odeco { padding: 0 1.1rem .9rem; }

/* Two-button pickers. Decoration and artwork source are both either/or choices, and
   they share this shape so each step of the form is answered the same way. */
.opick-row { display: grid; gap: .4rem; margin-bottom: .5rem; }
@media (min-width: 520px) { .opick-row { grid-template-columns: 1fr 1fr; } }
@media (min-width: 760px) { .opick-row--3 { grid-template-columns: 1fr 1fr 1fr; } }
.opick-opt {
  display: flex; flex-direction: column; gap: .1rem; padding: .55rem .75rem;
  font: inherit; text-align: left; cursor: pointer; color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: transparent;
}
.opick-opt b { font-size: .875rem; }
.opick-opt small { font-size: .75rem; color: var(--ink-3); }
.opick-opt:hover { border-color: var(--ink-3); }
.opick-opt.is-on { border-color: var(--brand-2); background: color-mix(in srgb, var(--brand-2) 12%, transparent); }
.opick-opt.is-on small { color: var(--brand-2); }

/* Product page: two price tables side by side where an item comes both ways. */
.pricebox-two { display: grid; gap: 1.1rem; }
@media (min-width: 560px) { .pricebox-two { grid-template-columns: 1fr 1fr; } }
.pricebox-sub { font-size: .9375rem; margin: 0 0 .4rem; }
.pricebox-sub span { font-weight: 400; color: var(--ink-3); font-size: .85em; }

.oink { padding: 0 1.1rem 1.1rem; }
.oink-head { display: flex; align-items: baseline; gap: .6rem; margin-bottom: .5rem; }
.oink-head .olabel { margin: 0; }
.oink-count { font-size: .75rem; color: var(--ink-3); margin-left: auto; }
.oink-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); gap: .4rem; }
.oink-sw {
  display: flex; align-items: center; gap: .5rem; padding: .45rem .6rem;
  font: inherit; font-size: .8125rem; cursor: pointer; text-align: left;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: transparent; color: var(--ink);
}
.oink-sw:hover { border-color: var(--ink-3); }
.oink-sw.is-on { border-color: var(--brand-2); background: color-mix(in srgb, var(--brand-2) 12%, transparent); font-weight: 700; }
.oink-sw.is-blocked { opacity: .4; cursor: not-allowed; }
.oink-dot { flex: none; width: 16px; height: 16px; border-radius: 50%; border: 1px solid rgba(0,0,0,.25); }
.oink-note { margin-top: .5rem; }

/* Where the two inks go: one per colour, or a colour halved between them. Never two
   inks on one piece -- the shop does not print those. */
.oinkmap { margin-top: .8rem; padding-top: .8rem; border-top: 1px dashed var(--line); }
.oinkmap-rows { display: grid; gap: .35rem; }
.oinkmap-item { display: grid; gap: .3rem; }
.oinkmap-row { display: grid; grid-template-columns: 16px 1fr auto; align-items: center; gap: .5rem; }
.oinkmap-color { font-size: .8125rem; }
.oinkmap-warn { color: var(--accent); font-weight: 600; }
.oinkmap-sel {
  font: inherit; font-size: .8125rem; padding: .3rem .5rem; color: var(--ink);
  background: var(--bg); border: 1px solid var(--line-2); border-radius: var(--radius-sm);
}

/* The halving slider sits under its own colour, indented past the swatch so it reads as
   belonging to that row rather than to the list. One slider: two inks is one degree of
   freedom, and both ends are labelled so neither number has to be worked out. */
.oinkmap-split {
  display: grid; grid-template-columns: auto 1fr auto; align-items: center;
  gap: .5rem; margin-left: 1.5rem; padding: .4rem .6rem;
  background: var(--bg-2); border-radius: var(--radius-sm);
}
.oinkmap-split input[type="range"] { width: 100%; min-width: 0; accent-color: var(--brand-2); }
.oinkmap-end { font-size: .75rem; color: var(--ink-2); white-space: nowrap; }
.oinkmap-end b { color: var(--ink); font-variant-numeric: tabular-nums; }
.oinkmap-end--b { text-align: right; }

/* Out of reach: the run cannot carry another colour at the minimum per colour. */
.oswatch.is-blocked { opacity: .35; cursor: not-allowed; }

/* Artwork block: where the art comes from is settled first, because the answer
   decides which panel appears under it. The block owns the gap off the header divider. */
.oart { padding: 0 1.1rem .2rem; }
.oart .olabel { margin-bottom: .4rem; }

/* Only the panel belonging to the chosen source is in the page at all -- the others
   would be inviting a second, contradictory answer. Doubled class: the generic
   .ofield rules sit below these and would otherwise win. */
.ofield.ofield--src { padding-top: .55rem; padding-bottom: 0; }
.ofield.ofield--src.is-off { display: none; }

/* Stock art on a line: nothing picked yet, then the design itself. */
.ostock-empty { display: flex; flex-direction: column; align-items: flex-start; }
.ostock-empty .ohint { margin-top: .5rem; }
.ostock-chosen {
  display: flex; align-items: center; gap: .75rem; padding: .5rem .6rem;
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--bg-2);
}
.ostock-chosen img {
  flex: none; width: 56px; height: 56px; object-fit: contain; border-radius: 6px;
  background: var(--art-ground); filter: var(--art-ink); mix-blend-mode: var(--art-blend);
}
.ostock-id { display: flex; flex-direction: column; gap: .1rem; min-width: 0; }
.ostock-id b { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .875rem; letter-spacing: .04em; }
.ostock-id small { font-size: .75rem; color: var(--ink-3); }
.ostock-chosen .ostock-open { margin-left: auto; }
.ostock-nd { margin-top: .75rem; }

/* Artwork files. Nothing is uploaded -- these ride in the order package zip. */
.oatt { margin-top: .85rem; display: flex; flex-direction: column; align-items: flex-start; gap: .4rem; }
.oatt-item {
  display: flex; align-items: center; gap: .6rem; width: 100%; padding: .45rem .3rem .45rem .6rem;
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--bg-2);
  font-size: .8125rem;
}
.oatt-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
.oatt-size { margin-left: auto; font-size: .75rem; color: var(--ink-3); white-space: nowrap; }
/* A name with no bytes behind it: the draft came back from storage, the file did not. */
.oatt-item.is-gone { border-style: dashed; }
.oatt-item.is-gone .oatt-name { color: var(--ink-3); text-decoration: line-through; }
.oatt-item.is-gone .oatt-size { color: var(--accent); }
.oatt-x {
  flex: none; width: 26px; height: 26px; display: grid; place-items: center; font-size: 1.1rem;
  line-height: 1; color: var(--ink-3); background: transparent; border: 0; border-radius: 6px;
  cursor: pointer;
}
.oatt-x:hover { background: var(--bg-3); color: var(--ink); }
.oatt .ohint { margin-top: .15rem; }

/* ---------- the stock art dialog ----------
   The library over the form rather than instead of it: leaving the page to go and look
   at /stockart/ means leaving a half-filled order behind. */
.omodal { position: fixed; inset: 0; z-index: 200; display: grid; place-items: center; padding: clamp(.5rem, 3vw, 2rem); }
.omodal[hidden] { display: none; }
.omodal-scrim { position: absolute; inset: 0; background: rgba(6,20,28,.6); backdrop-filter: blur(2px); animation: fade .2s ease; }
.omodal-panel {
  position: relative; display: flex; flex-direction: column; width: min(1000px, 100%);
  max-height: min(88vh, 900px); background: var(--bg); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-3); overflow: hidden;
}
.omodal-head { display: flex; align-items: flex-start; gap: 1rem; padding: 1rem 1.1rem .8rem; border-bottom: 1px solid var(--line); }
.omodal-head h2 { font-size: 1.15rem; margin: 0 0 .2rem; }
.omodal-head .ohint { display: block; max-width: 60ch; line-height: 1.45; }
.omodal-x {
  flex: none; margin-left: auto; width: 36px; height: 36px; display: grid; place-items: center;
  font-size: 1.4rem; line-height: 1; color: var(--ink-2); background: var(--bg-2);
  border: 1px solid var(--line); border-radius: 10px; cursor: pointer;
}
.omodal-x:hover { background: var(--bg-3); color: var(--ink); }
.omodal-tools { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; padding: .75rem 1.1rem; border-bottom: 1px solid var(--line); }
.omodal-search {
  font: inherit; font-size: .875rem; padding: .55rem .75rem; width: min(260px, 100%);
  color: var(--ink); background: var(--bg-2); border: 1px solid var(--line-2); border-radius: var(--radius-sm);
}
.omodal-search:focus { outline: 2px solid var(--brand-2); outline-offset: 1px; }
.omodal-chips { display: flex; gap: .35rem; flex-wrap: wrap; }
.omodal-chip {
  font: inherit; font-size: .8125rem; font-weight: 600; cursor: pointer; white-space: nowrap;
  padding: .4rem .7rem; border-radius: 999px; border: 1px solid var(--line-2);
  background: var(--bg); color: var(--ink-2);
}
.omodal-chip small { font-weight: 400; color: var(--ink-3); margin-left: .25rem; }
.omodal-chip:hover { border-color: var(--ink-3); color: var(--ink); }
.omodal-chip.is-on { background: var(--brand); border-color: var(--brand); color: #fff; }
.omodal-chip.is-on small { color: rgba(255,255,255,.75); }
:root[data-theme="dark"] .omodal-chip.is-on { background: var(--brand-2); border-color: var(--brand-2); color: #06202b; }
.omodal-count { width: 100%; margin: 0; font-size: .75rem; color: var(--ink-3); }
@media (min-width: 720px) { .omodal-count { width: auto; margin-left: auto; } }
.omodal-grid {
  display: grid; gap: .6rem; grid-template-columns: repeat(auto-fill, minmax(112px, 1fr));
  padding: .9rem 1.1rem 1.2rem; overflow-y: auto; overscroll-behavior: contain;
}
.omodal-grid .empty { grid-column: 1 / -1; padding: 3rem 1rem; }
/* No overflow:hidden here, however much the rounded corners want it: a <button> with
   a clipped overflow stops sizing to its own content in Chrome and collapses to a
   5px sliver. The children carry the rounding instead. */
.art-pick {
  display: block; padding: 0; cursor: pointer; isolation: isolate;
  background: var(--art-ground); border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.art-pick img {
  display: block; width: 100%; aspect-ratio: 1; object-fit: contain;
  border-radius: 8px 8px 0 0;
  filter: var(--art-ink); mix-blend-mode: var(--art-blend);
}
/* The code is printed inside the art too, but only as pixels -- this is the copy a
   screen reader and a search can reach. */
.art-pick span {
  display: block; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .6875rem; font-weight: 700; letter-spacing: .04em; padding: .3rem .2rem .35rem;
  color: var(--ink-2); background: var(--bg); border-top: 1px solid var(--line);
  border-radius: 0 0 8px 8px;
}
.art-pick:hover { border-color: var(--brand-2); }
.art-pick:hover span { color: var(--ink); }
.art-pick.is-on { border-color: var(--brand-2); box-shadow: 0 0 0 2px var(--brand-2); }
.art-pick.is-on span { background: var(--brand-2); color: #fff; border-top-color: var(--brand-2); }
/* The dialog scrolls its own grid; the page behind it must not scroll with it. */
body.is-modal { overflow: hidden; }

/* Attached files cannot leave on a mail link, and that is worth saying loudly. */
.onote-files {
  margin: 0 0 .9rem; padding: .6rem .75rem; line-height: 1.45;
  border: 1px solid var(--accent); border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--accent) 8%, transparent); color: var(--ink-2);
}
.onote-files b { color: var(--ink); }
.onote-steps { margin: .5rem 0 .5rem; padding-left: 1.2rem; display: grid; gap: .2rem; }
.onote-steps li { padding-left: .1rem; }

/* The last step, and the one that used to get forgotten. It appears only after the order
   has gone, because until then there is no file name to give. Weighted heavier than the
   note above it: by the time this shows, it is the only thing left to do. */
.order-upload {
  margin: 0 0 .9rem; padding: .9rem 1rem; line-height: 1.5;
  border: 2px solid var(--brand-2); border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--brand-2) 8%, transparent); color: var(--ink-2);
}
.order-upload[hidden] { display: none; }
.order-upload h3 { margin: 0 0 .5rem; font-size: 1rem; color: var(--ink); }
.order-upload p { margin: 0 0 .6rem; }
.order-upload p:last-child { margin-bottom: 0; }
.order-upload b { color: var(--ink); }
.order-upload .btn { width: 100%; }
/* The code is read down a phone line and typed into a file name, so it is set apart from
   the sentence around it and spaced like something to be copied. */
.oref {
  font-size: .8125rem; padding: .45rem .6rem; border-radius: var(--radius-sm);
  background: var(--bg-2); border: 1px solid var(--line);
}
.oref b { letter-spacing: .06em; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }

/* Sublimated pieces have no stock colour, because the print covers the whole surface.
   What they have instead is a background: their own art to the edges, or one of ours. */
.obg { padding: 0 1.1rem .2rem; }
.obg .olabel { margin-bottom: .4rem; }
.obg-wall { padding: 0 1.1rem 1.1rem; }
.obg-wall[hidden] { display: none; }
.obg-group {
  font-size: .75rem; text-transform: uppercase; letter-spacing: .09em; font-weight: 700;
  font-family: var(--font); color: var(--ink-3); margin: .9rem 0 .45rem;
}
.obg-group:first-child { margin-top: .3rem; }
.obg-grid { display: grid; gap: .45rem; grid-template-columns: repeat(auto-fill, minmax(92px, 1fr)); }
/* Same trap as the stock art tiles: overflow:hidden on a <button> stops it sizing to
   its content in Chrome, so the corners are rounded on the children. */
.obg-sw {
  display: block; padding: 0; cursor: pointer; color: var(--ink);
  background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.obg-sw img { display: block; width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: 8px 8px 0 0; }
.obg-sw span {
  display: block; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .6875rem; font-weight: 700; letter-spacing: .03em; padding: .28rem .2rem .32rem;
  color: var(--ink-2); background: var(--bg); border-top: 1px solid var(--line);
  border-radius: 0 0 8px 8px;
}
.obg-sw span small {
  display: block; font-family: var(--font); font-size: .6875rem; font-weight: 700;
  letter-spacing: 0; color: var(--accent);
}
.obg-sw.is-on span small { color: rgba(255,255,255,.9); }
.obg-sw:hover { border-color: var(--ink-3); }
.obg-sw.is-on { border-color: var(--brand-2); box-shadow: 0 0 0 2px var(--brand-2); }
.obg-sw.is-on span { background: var(--brand-2); color: #fff; border-top-color: var(--brand-2); }

/* The estimate is a sum now, not a single figure, so its parts line up above the total
   rather than hiding inside it. */
.osum-line { display: flex; align-items: baseline; gap: .6rem; font-size: .875rem; color: var(--ink-2); padding: .15rem 0; }
.osum-line b { margin-left: auto; font-variant-numeric: tabular-nums; color: var(--ink); }
.osum-line + .osum-total { margin-top: .3rem; padding-top: .5rem; border-top: 1px solid var(--line); }

/* One design for the whole order. It sits above the items because it decides what they
   show: with this on, no line carries an artwork step of its own.

   A card like everything else in this column. It used to be a bare region -- a label and
   two buttons loose on the page background, with a second card appearing under them in
   one-design mode and nothing at all in per-item mode. Now the wrapper is the card and
   the design panel is a section inside it, divided by a rule, the same way .oline divides
   its steps. Padding lives on the regions rather than the wrapper, again like .oline, so
   the divider can reach both edges. */
.order-art {
  border: 1px solid var(--line); border-radius: var(--radius-lg);
  background: var(--bg);
  margin: 1.1rem 0;
  /* Adding the first item scrolls here rather than to the item, so the card has to clear
     the sticky header the same way the line cards do. */
  scroll-margin-top: calc(var(--header-h) + 1rem);
}
.order-art[hidden] { display: none; }
.oshared-head { padding: .9rem 1.1rem .5rem; margin-bottom: 0; }
.oshared-head .olabel { margin: 0; }
.order-art > .opick-row { margin-bottom: 0; padding: 0 1.1rem; }
.order-art > .ohint--block { margin: .7rem 0 0; padding: 0 1.1rem .95rem; }
.oshared { margin-top: .9rem; border-top: 1px solid var(--line); }
.oshared-title {
  display: flex; align-items: baseline; gap: .6rem; padding: .8rem 1.1rem .2rem;
}
.oshared-title .olabel { margin: 0; }
.oshared-state { margin-left: auto; font-size: .8125rem; color: var(--ink-3); text-align: right; }
.oshared.is-done .oshared-state { color: var(--ok); }
.oshared .oartwork { padding-bottom: .9rem; }
/* The flash a problem sends here now has the wrapper's own border to light up, so it goes
   on the card rather than on the panel inside it -- which also means it still shows in
   per-item mode, where there is no panel. */
.order-art.is-pulse { animation: okb-pulse 1.3s ease-out 1; }

/* colour breakdown: a fixed pot shared out in blocks */
.osplit { margin-top: .7rem; border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg-2); overflow: hidden; }
.osplit-head { display: flex; align-items: center; gap: .6rem; width: 100%; padding: .6rem .8rem; font: inherit; font-size: .875rem; font-weight: 700; color: var(--ink); background: transparent; border: 0; cursor: pointer; text-align: left; }
.osplit-head:hover { color: var(--brand-2); }
.osplit-title { flex: 1; }
.osplit-state { font-weight: 600; font-size: .75rem; color: var(--ink-3); }
.osplit.is-done .osplit-state { color: var(--ok); }
.osplit-head svg { flex: none; transition: transform .18s ease; }
.osplit.is-shut .osplit-head svg { transform: rotate(-90deg); }
.osplit.is-shut .osplit-body { display: none; }
.osplit-body { padding: 0 .8rem .8rem; }
.osplit-bar { position: relative; height: 26px; border-radius: 999px; background: var(--bg-3); overflow: hidden; display: grid; place-items: center; margin-bottom: .7rem; }
.osplit-fill { position: absolute; inset: 0 auto 0 0; background: color-mix(in srgb, var(--brand-2) 26%, transparent); transition: width .18s ease; }
.osplit.is-done .osplit-fill { background: color-mix(in srgb, var(--ok) 26%, transparent); }
.osplit-left { position: relative; font-size: .75rem; font-weight: 700; letter-spacing: .01em; }
.osplit-rows { display: grid; gap: .35rem; }
.osplit-row { display: grid; grid-template-columns: 16px minmax(72px, 1fr) 2fr 3.2rem; align-items: center; gap: .5rem; }
.osplit-swatch { width: 16px; height: 16px; border-radius: 50%; border: 1px solid rgba(0,0,0,.18); }
.osplit-row label { font-size: .8125rem; margin: 0; }
.osplit-row input[type=range] { width: 100%; accent-color: var(--brand-2); cursor: pointer; }
.osplit-val { font-size: .8125rem; font-weight: 700; text-align: right; font-variant-numeric: tabular-nums; }
.osplit-val.is-idle { color: var(--ink-3); font-weight: 400; }
.osplit-actions { display: flex; gap: .4rem; flex-wrap: wrap; margin-top: .7rem; }
.osplit-foot { margin-top: .5rem; }
@media (max-width: 520px) {
  .osplit-row { grid-template-columns: 16px 1fr 3.2rem; }
  .osplit-row input[type=range] { grid-column: 1 / -1; }
}
@media (prefers-reduced-motion: reduce) {
  .osplit-head svg, .osplit-fill { transition: none; }
}

/* keep a copy */
.field--po { margin-bottom: .9rem; }
.field--po input { font-variant-numeric: tabular-nums; }
/* Each unmet requirement is a button that takes you to the field that fixes it. */
.osum-fix {
  display: block; width: 100%; text-align: left; font: inherit; font-size: inherit;
  color: inherit; background: none; border: 0; padding: 0; cursor: pointer;
  text-decoration: underline; text-decoration-style: dotted; text-underline-offset: 3px;
}
.osum-fix:hover { color: var(--accent); }
.osum-fix::after { content: " \2192"; opacity: .55; }

/* One flash on arrival, so it is obvious where you were sent. */
@keyframes okb-pulse {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent); }
  70%  { box-shadow: 0 0 0 10px color-mix(in srgb, var(--accent) 0%, transparent); }
  100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 0%, transparent); }
}
.is-pulse {
  animation: okb-pulse 1.3s ease-out 1;
  border-radius: var(--radius-sm);
}
@media (prefers-reduced-motion: reduce) {
  .is-pulse { animation: none; outline: 2px solid var(--accent); outline-offset: 2px; }
}

/* Land below both sticky bars rather than under them. */
.ostep, .oline { scroll-margin-top: calc(var(--header-h) + 96px); }

.order-file { margin-top: 1.1rem; padding-top: 1.1rem; border-top: 1px solid var(--line); }
.order-file-row { display: flex; gap: .4rem; flex-wrap: wrap; margin-bottom: .5rem; }
.linkish { font: inherit; color: var(--brand-2); background: none; border: 0; padding: 0; cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }
.linkish:hover { color: var(--accent); }

/* an order sheet worth filing: drop the site furniture, keep the order */
@media print {
  .hdr, .ftr, .drawer, .omodal, .order-side, .order-add, .breadcrumbs, .skip,
  .oline-toggle, .oline-x, .oswatch-actions, .osplit-actions, .order-file { display: none !important; }
  body { background: #fff; color: #000; }
  .oline, .osplit { break-inside: avoid; border-color: #999; box-shadow: none; }
  /* A sticky bar on paper either repeats or lands in the wrong place. */
  .oline-top { position: static; box-shadow: none; border-bottom: 1px solid #999; }
  .oline-top .oline-desc { display: block; }
  .is-pulse { animation: none; box-shadow: none; }
  .osplit.is-shut .osplit-body, .ostep.is-shut .ostep-body { display: block; }
  .order-layout { display: block; }
  a[href]::after { content: ""; }
}
.oline-name { display: block; font-family: var(--display); font-weight: 700; font-size: 1.05rem; line-height: 1.2; }
.oline-desc { display: block; font-size: .75rem; color: var(--ink-3); }
.oline-x {
  flex: none; width: 32px; height: 32px; border-radius: 8px; cursor: pointer;
  border: 1px solid var(--line); background: var(--bg-2); color: var(--ink-3); font-size: 1.2rem; line-height: 1;
}
.oline-x:hover { color: var(--accent); border-color: var(--accent); }
.oline-toggle {
  flex: none; width: 30px; height: 30px; display: grid; place-items: center; cursor: pointer;
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: transparent;
  color: var(--ink-2); transition: transform .18s ease, color .18s ease, border-color .18s ease;
}
.oline-toggle:hover { color: var(--brand-2); border-color: var(--brand-2); }
.oline-toggle svg { transition: transform .18s ease; }
.oline.is-collapsed .oline-toggle svg { transform: rotate(-90deg); }
/* Collapsed: swap the catalog blurb for a recap of what was actually chosen. */
.oline-sum { display: none; font-size: .75rem; color: var(--ink-2); }
.oline.is-collapsed .oline-fold { display: none; }
.oline.is-collapsed .oline-head { border-bottom: 0; }
.oline.is-collapsed .oline-desc { display: none; }
.oline.is-collapsed .oline-sum { display: block; }
@media (prefers-reduced-motion: reduce) {
  .oline-toggle, .oline-toggle svg { transition: none; }
}

.oline-body { display: grid; gap: 1rem; padding: 1.1rem; }
@media (min-width: 760px) { .oline-body { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }
.ofield label { display: block; font-size: .875rem; font-weight: 700; margin-bottom: .35rem; }
.ofield input, .ofield select {
  width: 100%; font: inherit; font-size: .9375rem; color: var(--ink); background: var(--bg);
  padding: .6rem .8rem; border: 1px solid var(--line-2); border-radius: var(--radius-sm);
}
.ofield--full { padding: 0 1.1rem 1.1rem; }

.oswatches { padding: 0 1.1rem 1.1rem; }
.oswatch-head { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; margin-bottom: .6rem; }
.oswatch-head .olabel { margin: 0; }
.oswatch-actions { display: flex; gap: .4rem; margin-left: auto; flex-wrap: wrap; }
.oswatch-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); gap: .4rem; }
.oswatch {
  display: flex; align-items: center; gap: .5rem; text-align: left;
  font: inherit; font-size: .8125rem; cursor: pointer; color: var(--ink);
  padding: .4rem .55rem; border: 1px solid var(--line); border-radius: 999px; background: var(--bg);
  transition: border-color .15s, background-color .15s;
}
.oswatch:hover { border-color: var(--ink-3); }
.oswatch.is-on { border-color: var(--brand-2); background: color-mix(in srgb, var(--brand-2) 10%, transparent); font-weight: 600; }
.oswatch-dot { flex: none; width: 16px; height: 16px; border-radius: 50%; border: 1px solid rgba(0,0,0,.18); }

.order-you { border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 1.5rem; background: var(--bg-2); }
.order-you h2 { font-size: 1.25rem; margin-bottom: 1rem; }

@media (min-width: 940px) { .order-side { position: sticky; top: calc(var(--header-h) + 1.5rem); } }
.order-summary { border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 1.35rem; background: var(--bg); }
.order-summary h2 { font-size: 1.1rem; margin-bottom: .9rem; }
.osum-rows { display: grid; gap: .1rem; }
.osum-row { display: flex; justify-content: space-between; gap: 1rem; font-size: .875rem; font-weight: 600; margin-top: .7rem; }
.osum-row b { font-variant-numeric: tabular-nums; }
.osum-sub { font-size: .75rem; color: var(--ink-3); line-height: 1.4; }
.osum-total {
  display: flex; justify-content: space-between; align-items: baseline; gap: 1rem;
  margin-top: 1rem; padding-top: .9rem; border-top: 1px solid var(--line); font-size: .875rem; color: var(--ink-2);
}
.osum-total b { font-family: var(--display); font-size: 1.3rem; color: var(--ink); }
.osum-note { font-size: .75rem; color: var(--ink-3); margin: .7rem 0 0; line-height: 1.45; }
.osum-problems { margin: 1rem 0 0; padding-left: 1.1rem; font-size: .8125rem; color: var(--warn, #a86a00); }
:root[data-theme="dark"] .osum-problems { color: var(--accent-2); }
.osum-problems li + li { margin-top: .25rem; }
.order-actions { display: grid; gap: .55rem; margin-top: 1.25rem; }
.order-actions .btn:disabled { opacity: .45; cursor: not-allowed; transform: none; box-shadow: none; }
.ostatic {
  margin: 0; font-size: .9375rem; font-weight: 600; padding: .6rem .8rem;
  border: 1px dashed var(--line-2); border-radius: var(--radius-sm); background: var(--bg-2);
}

/* count badge on the order link, and the product CTA once that item is in the draft */
.order-count {
  display: inline-grid; place-items: center; min-width: 1.25rem; height: 1.25rem;
  margin-left: .4rem; padding: 0 .35rem; border-radius: 999px;
  background: var(--accent); color: #fff; font-size: .75rem; font-weight: 700;
  line-height: 1; font-variant-numeric: tabular-nums;
}
.drawer-link .order-count { margin-left: .5rem; }
.ftr .order-count { background: var(--accent); }
.btn.is-in-order { border-color: var(--ok); color: var(--ok); }
.btn.is-in-order:hover { background: color-mix(in srgb, var(--ok) 10%, transparent); color: var(--ok); }
