@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('assets/fonts/inter-latin-var.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;
}

:root {
  color-scheme: dark;
  /* OFF THE DUCK DISTRO FLYER. The art is a bold cartoon drawn with a black outline
     on a dark leafy ground, and that is what the ground is here: a near-black warm
     green, with cards a dark brown lifted off it -- the duck's own body colour. Ink
     is the flyer's warm cream.

     Two brand colours do the work the old white theme's leaf/moss pair used to:
     GOLD, the hard-hat yellow, is the primary (buttons, headings, prices, links, the
     gate); GREEN, the duck's own head green, is the darker accent that carries the top
     bar, the cart badge and the moment the gate opens.

     The feet's ORANGE stays inside the artwork and is used nowhere in the chrome, so
     red is still unambiguously an error: on a gold-and-green page red is the one
     thing neither brand colour can be mistaken for, and an error painted in a brand
     colour is not an error. That is the one job neither of these can do. */
  --bg: #12170e;
  --surface: #1e1810;
  --surface-2: #281f14;
  --border: #3d3020;
  --border-light: #5a4529;
  --text: #f6f0e0;
  --text-muted: #b6ab92;

  --text-faint: #8a7f68;
  --red: #f2564b;

  /* Green. The duck's head green, deep enough that cream sits on it cleanly, which
     on a dark page is where the contrast has to come from now that the ground is no
     longer white. It carries the support bar, the cart badge and the gate button --
     everything the old theme's moss did. */
  --green: #2f7d34;
  --green-hi: #4aa64e;
  --green-deep: #12330f;
  --green-metal: linear-gradient(178deg, #4aa64e 0%, #3c8c3e 22%, #2f7d34 55%, #1f5c22 78%, #12330f 100%);
  --green-glow: 0 0 22px rgba(74,166,78,.34);

  /* Gold, the PRIMARY -- the hard hat and the bill. On a dark ground the ramp runs
     bright at the top down into a deep amber, never a dark top: a dark-topped ramp on
     a near-black page is invisible, and this same ramp is clipped to heading TEXT as
     well as poured into buttons, so it has to read as gold either way. --gold-hi is
     the readable gold for prices and links ON the dark ground; --gold-ink is the
     near-black that sits on a gold fill -- the one place the old cream would vanish. */
  --gold: #f1c230;
  --gold-hi: #ffd54a;
  --gold-deep: #a3770f;
  --gold-ink: #191204;

  --gold-metal: linear-gradient(177deg, #ffd94f 0%, #f1c230 34%, #d5a41d 66%, #a3770f 100%);
  --gold-glow: 0 0 16px rgba(241,194,48,.30);
  --gold-emboss: inset 0 1px 0 rgba(255,255,255,.30), inset 0 -1px 0 rgba(0,0,0,.34);

  --radius: 0px;
  --radius-sm: 0px;
  --radius-lg: 0px;
  --shadow: 0 12px 28px rgba(0,0,0,.6);
  --font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --container: 1280px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
button { font-family: inherit; cursor: pointer; }
ul { list-style: none; margin: 0; padding: 0; }
h1, h2, h3, h4 { font-family: var(--font-body); margin: 0; font-weight: 800; letter-spacing: -.01em; }
.container { max-width: var(--container); margin: 0 auto; padding: 0 24px; }
.section { padding: 56px 0; }

.section.section--tight { padding: 16px 0; }
.eyebrow {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 11px; font-weight: 700; letter-spacing: 1.5px; text-transform: uppercase;
  color: var(--text-muted);
}
.section-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; margin-bottom: 24px; flex-wrap: wrap; border-bottom: 1px solid var(--border); padding-bottom: 16px; }
/* Page headings are METAL-poured rather than metal-coloured. The shop and cart
   headings carry inline font sizing from their templates and sit outside
   .section-head, so they opt in with .gold-head. */
.section-head h2,
.gold-head {
  background: var(--gold-metal); -webkit-background-clip: text; background-clip: text;
  color: transparent; -webkit-text-fill-color: transparent;
  filter: drop-shadow(0 0 16px rgba(241,194,48,.32));
}
.section-head h2 { font-size: 20px; letter-spacing: 1px; text-transform: uppercase; }
.section-head .view-all { font-size: 12px; font-weight: 600; letter-spacing: .5px; text-transform: uppercase; color: var(--text-muted); white-space: nowrap; }
.section-head .view-all:hover { color: var(--text); }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 46px; padding: 12px 26px; border-radius: 0; border: 1px solid var(--gold);
  font-size: 12.5px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;
  transition: background .15s ease, color .15s ease, box-shadow .15s ease; white-space: nowrap;
}

.btn-primary {
  background: var(--gold-metal); color: var(--gold-ink); border-color: var(--gold-hi);
  box-shadow: var(--gold-emboss), 0 3px 16px rgba(241,194,48,.32);
  text-shadow: 0 1px 0 rgba(255,255,255,.35);
}
.btn-primary:hover {
  background: transparent; color: var(--gold-hi);
  box-shadow: var(--gold-glow); text-shadow: none;
}
.btn-outline { background: transparent; color: var(--gold-hi); }
.btn-outline:hover {
  background: var(--gold-metal); color: var(--gold-ink);
  box-shadow: var(--gold-emboss), 0 3px 16px rgba(241,194,48,.32);
}
.btn-sm { min-height: 38px; padding: 8px 16px; font-size: 11px; }
.btn-block { width: 100%; }

.ticker { border-bottom: 1px solid var(--border); background: var(--bg); overflow: hidden; }
.ticker .container { display: flex; align-items: center; justify-content: center; height: 38px; }
.ticker-text {
  font-size: 11px; font-weight: 700; letter-spacing: 1.5px; text-transform: uppercase; color: var(--text-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.utility-bar { border-bottom: 1px solid var(--border); }
.utility-bar .container { display: flex; align-items: center; justify-content: flex-end; gap: 20px; height: 38px; }
.utility-link { font-size: 11px; font-weight: 700; letter-spacing: .5px; text-transform: uppercase; color: var(--text-muted); display: flex; align-items: center; gap: 6px; }
.utility-link:hover { color: var(--text); }
.utility-link svg { width: 14px; height: 14px; }

.support-bar {
  background: var(--green-metal); color: var(--text);
  box-shadow: inset 0 -1px 0 rgba(0,0,0,.28), 0 2px 18px rgba(47,125,52,.28);
  font-size: 11px; letter-spacing: .5px; text-transform: uppercase;
}
.support-bar .container {
  display: flex; align-items: center; justify-content: center;
  padding: 0; flex-wrap: wrap;
}

.support-bar-link {
  display: flex; align-items: center; justify-content: center; flex-wrap: wrap;
  width: 100%; gap: 2px 8px; min-height: 26px; padding: 3px 14px;
  color: var(--text); text-align: center;
}
.support-bar-label { font-weight: 600; }
.support-bar-hours { opacity: .8; font-weight: 600; }
.support-bar-num { font-weight: 800; letter-spacing: 1px; white-space: nowrap; }
.support-bar-link:hover .support-bar-num { text-decoration: underline; text-underline-offset: 3px; }
/* A phone gets the same bar, only narrower: it is a standing offer, not a headline,
   so it should cost one line of screen and no more. Shrinking the type rather than
   the touch area keeps the whole strip a working tap target. */
@media (max-width: 680px) {
  .support-bar { font-size: 10px; letter-spacing: .3px; }
  .support-bar-link { gap: 1px 6px; min-height: 24px; padding: 2px 12px; }
}
@media (max-width: 380px) {
  .support-bar { font-size: 9.5px; }
  .support-bar-link { gap: 1px 5px; padding: 2px 10px; }
}

.header {
  position: sticky; top: 0; z-index: 50; background: var(--bg); border-bottom: 1px solid var(--border);
}
.header-logo-row { display: flex; align-items: center; justify-content: center; position: relative; padding: 8px 24px; }
.logo { font-size: clamp(24px, 4.5vw, 38px); font-weight: 900; letter-spacing: 2px; color: var(--gold-hi); text-transform: uppercase; text-shadow: var(--gold-glow); }

.header-left { position: absolute; left: 24px; top: 50%; transform: translateY(-50%); display: flex; align-items: center; gap: 2px; }
.burger { display: none; background: none; border: none; color: var(--text); width: 40px; height: 40px; align-items: center; justify-content: center; }
.header-icons { position: absolute; right: 24px; top: 50%; transform: translateY(-50%); display: flex; align-items: center; gap: 14px; }
.icon-btn { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px; color: var(--text); }
.icon-btn svg { width: 19px; height: 19px; }

.icon-btn--chat svg { width: 17px; height: 17px; }
.icon-btn--chat { color: var(--text-muted); }
.icon-btn--chat:hover { color: var(--text); }
.badge { position: absolute; top: 0; right: 0; background: var(--green-metal); box-shadow: 0 0 8px rgba(47,125,52,.45); color: var(--text); font-size: 10px; font-weight: 800; width: 15px; height: 15px; border-radius: 50%; display: flex; align-items: center; justify-content: center; }
.mobile-only { display: none; }

.main-nav-row { border-top: 1px solid var(--border); }
.main-nav { display: flex; align-items: center; justify-content: center; gap: 0; flex-wrap: wrap; }
.main-nav > li > a {
  display: block; padding: 16px 18px; font-size: 12.5px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;
  color: var(--text-muted); transition: color .15s;
}
.main-nav > li > a:hover { color: var(--gold-hi); }

.main-nav > li.has-sub { position: relative; }
.main-nav > li.has-sub > a::after {
  content: '▾'; font-size: 9px; margin-left: 6px; vertical-align: middle; opacity: .7;
}
.nav-sub {
  position: absolute; top: 100%; left: 0; min-width: 230px; z-index: 60;
  background: var(--surface); border: 1px solid var(--border); border-top: none;
  display: none; box-shadow: var(--shadow);
}
.main-nav > li.has-sub:hover > .nav-sub,
.main-nav > li.has-sub:focus-within > .nav-sub,
.main-nav > li.has-sub.open > .nav-sub { display: block; }
.nav-sub a {
  display: block; padding: 12px 16px; font-size: 11.5px; font-weight: 700;
  letter-spacing: .8px; text-transform: uppercase; color: var(--text-muted);
  border-bottom: 1px solid var(--border-light);
}
.nav-sub li:last-child a { border-bottom: none; }
.nav-sub a:hover { color: var(--text); background: var(--surface-2); }

.drawer-sub { margin: 2px 0 6px 14px; border-left: 2px solid var(--border); }
.drawer-sub a { font-size: 12px; opacity: .9; }

.header-logo-row .logo { display: inline-flex; align-items: center; gap: 11px; }
/* MARK PLUS TEXT, because the logo is the flyer's duck and the flyer's duck is
   PORTRAIT -- 151x304, so 0.497:1. A portrait mark sized by height the way the old
   landscape lockup was comes out ~36px wide at 72px tall, and the "DUCK DISTRO"
   lettered on the duck's own body is illegible long before that. So the shop's name
   is set as TEXT beside it instead of being read off the artwork, and the duck is
   given more height than the lockup needed: 100px of duck is 50px wide, still far
   narrower than the old lockup, so the centre column cannot collide with either
   icon group. Sized by HEIGHT so width follows the art. */
.header-logo-img { height: clamp(60px, 7.6vw, 100px); width: auto; display: block; }
.logo-word {
  font-size: clamp(15px, 2.1vw, 25px); font-weight: 900; letter-spacing: 1.5px;
  line-height: 1.02; text-transform: uppercase; white-space: nowrap;
  background: var(--gold-metal); -webkit-background-clip: text; background-clip: text;
  color: transparent; -webkit-text-fill-color: transparent;
}
.logo-word span { display: block; }
/* The phone sizing is NOT here. It lives with the 860px block further down, which
   sets .header-logo-img again and, being later in the file at equal specificity,
   wins at every width a phone reports -- a rule put here reads correctly and does
   nothing. */

.drawer { position: fixed; inset: 0; z-index: 100; display: none; }
.drawer.open { display: block; }
.drawer-backdrop { position: absolute; inset: 0; background: rgba(0,0,0,.72); }
.drawer-panel { position: absolute; top: 0; left: 0; bottom: 0; width: 82%; max-width: 320px; background: var(--surface); border-right: 1px solid var(--border); padding: 24px; overflow-y: auto; }
.drawer-panel .logo { margin-bottom: 24px; font-size: 22px; display: block; }

.drawer-logo-img { width: auto; height: 168px; display: block; margin-bottom: 22px; }
.drawer-panel li a { display: block; padding: 14px 0; font-weight: 700; text-transform: uppercase; font-size: 13px; letter-spacing: .5px; border-bottom: 1px solid var(--border); }

.grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px; background: var(--border); border: 1px solid var(--border); }

.card { background: var(--surface-2); display: flex; flex-direction: column; }
.card-media { position: relative; aspect-ratio: 1/1; overflow: hidden; }
.card-media img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

.card-media--clickable { cursor: zoom-in; }

.card-media--album { display: grid; grid-template-columns: 2fr 1fr; gap: 2px; }
.card-media--album .album-side { display: grid; grid-auto-rows: 1fr; gap: 2px; }

.album-cell { position: relative; overflow: hidden; }

.album-cell--soon { background: var(--surface-2); cursor: default; }
.album-cell--soon img { object-fit: contain; }

.card-media--soon { background: var(--surface-2); }
.card-media--soon img { object-fit: contain; }
.gallery-main--soon .gallery-frame { background: var(--surface-2); }
.gallery-main--soon .gallery-frame img { object-fit: contain; cursor: default; }

.card-tags { position: absolute; top: 10px; left: 10px; display: flex; flex-direction: column;
             gap: 6px; align-items: flex-start; max-width: calc(100% - 20px); }
.tag { font-size: 10px; font-weight: 800; letter-spacing: .5px; text-transform: uppercase; padding: 4px 8px; }
.tag-out { background: #000; color: #fff; border: 1px solid rgba(255,255,255,.55); }

.tag-low {
  background: #000; color: var(--red); border: 1px solid var(--red);
  animation: tag-low-pulse 1.8s ease-in-out infinite;
}
@keyframes tag-low-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(224,192,50,.5); }
  50% { box-shadow: 0 0 0 4px rgba(224,192,50,0); }
}
@media (prefers-reduced-motion: reduce) {
  .tag-low { animation: none; }
}

.tag-note {
  background: #000; color: #fff; border: 1px solid #fff;

  white-space: normal; line-height: 1.25;
}
@media (max-width: 520px) {

  .tag-note { font-size: 9px; padding: 3px 6px; }
}
.card-quick { position: absolute; top: 10px; right: 10px; display: flex; flex-direction: column; gap: 6px; opacity: 0; transition: opacity .15s; }
.card:hover .card-quick { opacity: 1; }
.qbtn { width: 30px; height: 30px; background: rgba(255,255,255,.92); border: 1px solid var(--gold); display: flex; align-items: center; justify-content: center; color: var(--gold-hi); box-shadow: 0 1px 4px rgba(0,0,0,.12); }

.qbtn:hover { background: var(--gold); color: var(--gold-ink); border-color: var(--gold-hi); }
.qbtn svg { width: 13px; height: 13px; }
.card-body { padding: 14px 16px 20px; display: flex; flex-direction: column; gap: 4px; flex: 1; }
.card-cat { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .5px; color: var(--text-faint); }
.card-title { font-size: 12.5px; font-weight: 700; letter-spacing: .2px; text-transform: uppercase; color: var(--text); }
.card-title a:hover { color: var(--text-muted); }
/* The price is bare text when nothing is discounted and only wraps itself in
   .now when there is a .was to sit beside — so the gold metal goes on the container,
   or every full-price item on the site stays off-brand cream. */
.card-price { font-size: 13px; font-weight: 700; margin-top: 2px; color: var(--gold-hi); text-shadow: 0 0 14px rgba(241,194,48,.28); }
.card-price .was { color: var(--text-faint); text-decoration: line-through; font-weight: 500; font-size: 12px; margin-right: 6px; text-shadow: none; }
.card-price .now { color: var(--gold-hi); }
.card-add { margin-top: auto; padding-top: 12px; }

.p1 { background: #241c12; }
.p2 { background: #1f1a11; }
.p3 { background: #2a2015; }
.p4 { background: #1b1d12; }
.p5 { background: #262114; }
.p6 { background: #1d2113; }

.category-grid {
  /* ONE COLUMN. The art is a set of wide banner plates, all 3.1034:1, and a plate that
     wide dropped into the old 1.872:1 paired slot would have been cropped to about half
     its width -- taking the artwork off one end and the type off the other. The 1-2-2-2-1
     rhythm existed to make three-ish shapes tile neatly; these are all one shape. */
  display: grid; grid-template-columns: 1fr;

  row-gap: clamp(6px, 1.1vh, 14px);

  --tile-cap: 360px;
  --pack-w: 96%;

  /* Everything on the home page that is NOT this grid, measured (not guessed) in Chrome
     at six viewports. The px part is the fixed furniture -- support bar, header, and the
     44px Join Signal button -- and it differs by breakpoint, so it stays in the two media
     blocks below. This is only the part that scales: the section's own padding and the
     cta band's, both twice over for top and bottom. The numbers here mirror
     .page-home .section--tight and .page-home .cta-band--links exactly; change one and
     you must change the other, or the grid is sized against a budget that no longer
     exists and the scroll comes back.

     vh, not dvh, deliberately, even though the totals below subtract from 100dvh: those
     two rules are written in vh, so the padding really is sized off the LARGE viewport.
     Subtracting a vh amount from a dvh total over-subtracts while a mobile address bar
     is showing, which leaves the grid a few pixels small. That is the safe direction --
     the other way round is a page that scrolls the moment the bar appears. */
  --home-pad: calc(2 * clamp(10px, 2.4vh, 30px) + 2 * clamp(8px, 1.5vh, 18px));

  /* The fixed part of that furniture: support bar + sticky header + the 44px Join Signal
     button. Measured across seventeen widths from 360 to 1920, and it is NOT a step
     function -- .logo is font-size: clamp(24px, 4.5vw, 38px), so the header grows
     CONTINUOUSLY with width: 53px up to about 489px wide, then about 0.09px per px until
     it caps at 61px around 578px, and above the 680px breakpoint the support bar gains
     2px and the header keeps climbing to its 81px cap by 1440px.

     So this is deliberately the WORST CASE for each block rather than a fitted curve:

       <= 680px   support 24 + header 61 (its cap) + button 44 = 129, + 4px slack = 133
       >= 681px   support 26 + header 81 (its cap) + button 44 = 151, + 4px slack = 155

     Over-subtracting is free here and under-subtracting is a scroll. A narrow phone
     really only spends 121px, so the grid comes out about 12px shorter than it strictly
     needs to be -- and body.page-home is a flex column that hands the leftover to the
     ::after spacer, so those 12px show up as breathing room, not as a gap in the wrong
     place. Fit a curve to the header instead and every future logo tweak silently
     un-fits the page. */
  /* 162px, not the 151px the measurements below add up to: the +4px slack became +11px
     when the plates got deeper (3.10:1 against the old ~3.9:1). Over-subtracting is free
     -- body.page-home is a flex column and hands the leftover to the ::after spacer, so
     it shows up as breathing room -- and under-subtracting is a 4px scroll, which is what
     161px still left at 1366x900. */
  --home-furniture: 162px;

  /* Declared once, here, and NOT repeated in the two media blocks below: a var() inside a
     custom property resolves against the cascaded values on the same element, and both
     --home-furniture and --budget land on .category-grid, so the mobile block's 133px
     override is what this sees there. Two copies of the same formula is how one of them
     ends up stale. */
  --budget: calc(100dvh - var(--home-furniture) - var(--home-pad));
  max-width: min(var(--tile-cap), 94vw);
  margin-left: auto; margin-right: auto;

  align-items: center;
  justify-items: center;
}

.category-card { width: var(--pack-w); }

/* No border and no background of its own. Each plate is cropped to its OWN frame now,
   so that frame IS the tile edge -- the card used to add a second border outside it, with
   27px of the art's black margin showing in between, and you saw both. */
.category-card {
  display: block; background: none; border-radius: 11px; overflow: hidden;
  border: 0;
  transition: box-shadow .18s ease;
}

/* Flat tiles: the art already carries the depth, so the card behind it does not.
   Pointing at one lights its edge rather than lifting it off the page. */
.category-card:hover,
.category-card:focus-visible {
  box-shadow: 0 0 22px rgba(47,125,52,.30);
}

/* ONE aspect for all eight. The new artwork arrives as a single sheet of eight plates cut
   on the same grid, so they really are one shape -- measured 3.079 to 3.135 across the
   cut files, a spread of 0.06, normalised to 3.1034 on export. The old per-plate
   overrides existed because the previous art was eight different depths; keeping them
   here would pin seven plates to a ratio none of them has. */
.category-media { display: block; aspect-ratio: 3.1034; }

.category-media img { display: block; width: 100%; height: 100%; object-fit: cover; }
.category-blob { width: 100%; height: 100%; border-radius: 50%; filter: blur(1px); opacity: .9; }

/* Fallback swatches for a category with no photograph. Currently unreferenced -- every
   tile on the homepage has real art -- but kept because that is what they are FOR, and a
   missing tile should still land on-theme. They used to be a rainbow, one hue per
   category; with three colours to play with the identity has to come from VALUE instead,
   gold stepping down from bright to dark, and green reserved for the fire sale. */
.cat-firesale { background: radial-gradient(circle at 40% 25%, #4aa64e, #2f7d34 45%, #12330f); }
.cat-flower { background: radial-gradient(circle at 35% 30%, #ffe9a6, #c1901a); }
.cat-wax { background: radial-gradient(circle at 35% 30%, #ffd54a, #a3770f); }
.cat-snowballs { background: radial-gradient(circle at 35% 30%, #fffaf0, #d5a41d); }
.cat-moonrocks { background: radial-gradient(circle at 35% 30%, #f1c230, #7a4726); }
.cat-vapes { background: radial-gradient(circle at 35% 30%, #e0b026, #5c3419); }
.cat-prerolls { background: radial-gradient(circle at 35% 30%, #ffd94f, #96690c); }
.cat-edibles { background: radial-gradient(circle at 35% 30%, #d8a81f, #4a2c14); }
.cat-mushies { background: radial-gradient(circle at 35% 30%, #b9871a, #3d2412); }
.cat-merch { background: radial-gradient(circle at 35% 30%, #a3770f, #2a1c0e); }

@media (max-width: 680px) {
  /* The long hot dog, but sized so it is never a scroll. One column of eight plates, and
     the only free variable is how WIDE they are -- each plate's height follows from its
     own aspect ratio, so the whole stack is:

         height = width * SUM(1/ratio) + 7 * gap

     SUM(1/ratio) over the eight plates is 2.57780 -- they share one ratio now, so it is
     simply 8 / 3.1034. Solve it for width against the space left over and the stack lands
     on the budget instead of past it. These plates are DEEPER than the old ones (3.10 vs
     ~3.9), so the sum grew from 2.10622 and the solved width comes out smaller; that
     constant and .category-media's aspect-ratio must be changed together or the phone
     gets a scroll back.

     One width for all eight, not one per plate: ragged widths down a column read as a
     mistake, and the height still falls out of each plate's own ratio, so nothing is
     cropped and nothing is letterboxed. Narrowing the card is the whole mechanism --
     object-fit: cover would take the lettering off these plates, and contain would
     letterbox them inside a box we just went to the trouble of sizing.

     --home-furniture is 133px here rather than the 155px above: below 681px the support
     bar narrows and the header's logo clamp has not yet grown. See its comment. */
  .category-grid {
    --home-furniture: 133px;
    --gap: clamp(3px, 0.7vh, 8px);
    --pack-w: 100%;
    row-gap: var(--gap); column-gap: clamp(2px, 0.5vh, 6px);
    /* Floor, so the page scrolls again on purpose rather than rendering slivers: a min()
       with nothing under it can resolve to a few pixels, which "fits" perfectly and shows
       nothing at all. 160px and not more: at 200px the floor was winning at a 617px-tall
       viewport -- a real 360x640 Android -- and putting back a 3 to 11px scroll on a
       phone where the solved width, 193px, was perfectly readable. Measured at 217px on
       an iPhone SE and the lettering is crisp; the floor is for viewports shorter than
       any phone, not for phones. */
    max-width: max(160px, min(var(--tile-cap), 94vw,
                 calc((var(--budget) - 7 * var(--gap)) / 2.57780)));
  }
  .category-card { border-radius: 12px; }
  .section.section--tight { padding: 10px 0; }
}

/* No hover on a touch screen, so the edge lights on press instead. */
@media (hover: none) and (pointer: coarse) {
  .category-card:active { border-color: var(--green-hi); }
}

/* Desktop: two across, the way LAK and BMF lay out, four rows of pairs.

   Not BMF's 1-2-2-2-1 rhythm, and the reason is the art rather than taste. BMF's six
   middle tiles are 1.87:1, so spanning its first and last across both columns costs
   little height. All eight plates here are 3.10:1 banners with no squarer hero among
   them, and a full-width 3.10:1 plate eats a third of the budget on its own; spanning
   one shrinks the paired tiles to slivers. Four even rows it is.

   Same trick as mobile, solved for four rows instead of eight in a column. Every plate
   now shares ONE ratio, so a row is exactly as tall as either of its pair and the sum is
   just four of them:

       each row  1 / 3.1034 = 0.322226
       4 rows              = 1.288904

       height = width * 1.288904 + 3 * row gap,   so   width = (budget - 3 gaps) / 1.288904

   and 2 / 1.288904 = 1.55171 turns that width into the grid's, both columns plus the gap.

   Because the ratios are now identical there is no stagger between the two plates in a
   row and nothing is cropped by object-fit: cover -- the box is the art's own shape. That
   was NOT true of the previous art, which ran 3.37:1 to 4.02:1 and had to keep a ratio
   per plate to stop cover() trimming a frame edge off the shallow ones.

   The budget itself is --home-furniture + --home-pad off 100dvh; both are declared on the
   base rule above with the measurements that produced them. */
@media (min-width: 681px) {
  .category-grid {
    --cgap: clamp(4px, 0.6vw, 14px);
    --rgap: clamp(4px, 1.1vh, 14px);
    grid-template-columns: repeat(2, 1fr);
    column-gap: var(--cgap); row-gap: var(--rgap);
    max-width: max(300px, min(1100px, 94vw,
                 calc((var(--budget) - 3 * var(--rgap)) * 1.55171 + var(--cgap))));
  }
  /* Side by side the first and last plate are not rails any more, so they fill their
     column like the rest. align-items: center on the base rule centres the shorter plate
     of a pair in its row. */
  .category-grid .category-card { width: 100%; justify-self: stretch; }
}

/* The old (max-height: 620px) and (min-width: 680px) block lived here. It went two
   across for short windows; the rule above now does that at every height, and its
   hardcoded max-width: min(1100px, 94vw) would have overridden the height-solved one
   on exactly the short windows that need it most. Its min-width was 680px against the
   mobile block's max-width 680px, so both applied at exactly 680px wide. */

@media (max-height: 760px) {
  .section.section--tight { padding: 10px 0; }
}


/* The two links are the band. Slimmer than a page button, still 44px to the thumb. */

body.page-home { min-height: 100vh; display: flex; flex-direction: column; }
@supports (height: 100dvh) {
  body.page-home { min-height: 100dvh; }
}

.page-home .section.section--tight {
  flex: 1 1 auto; min-height: 0;
  display: flex; align-items: center;
  padding: clamp(10px, 2.4vh, 30px) 0;
}
.page-home .section.section--tight > .container { width: 100%; }


.page-home::after {
  content: ''; display: block; flex: 1 1 0; max-height: clamp(0px, 5vh, 56px);
}

.footer { border-top: 1px solid var(--border); padding-top: 44px; font-size: 14px; }
.footer-grid { display: grid; grid-template-columns: 1.4fr 1fr 1fr 1.2fr; gap: 32px; padding-bottom: 32px; border-bottom: 1px solid var(--border); }
.footer h4 { font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: var(--text); margin-bottom: 16px; font-weight: 800; }
.footer-grid p { color: var(--text-muted); font-size: 13px; line-height: 1.7; max-width: 320px; }
.footer-grid li { margin-bottom: 9px; }
.footer-grid a { color: var(--text-muted); font-size: 13px; }
.footer-grid a:hover { color: var(--text); }
.footer-contact { display: flex; flex-direction: column; gap: 10px; }
.footer-contact a { display: inline-flex; align-items: center; gap: 8px; color: var(--text-muted); font-weight: 600; font-size: 13px; text-transform: uppercase; letter-spacing: .3px; }
.footer-contact a:hover { color: var(--text); }
.footer-contact .dot { width: 5px; height: 5px; background: var(--gold); box-shadow: 0 0 8px rgba(241,194,48,.7); flex-shrink: 0; }
.pay-row { font-size: 11px; letter-spacing: .5px; text-transform: uppercase; color: var(--text-faint); margin-top: 14px; }
.footer-legal { padding: 22px 0 26px; color: var(--text-faint); font-size: 11px; line-height: 1.8; }
.footer-legal p { margin: 0 0 10px; }
.footer-bottom { display: flex; justify-content: space-between; align-items: center; padding: 18px 0; border-top: 1px solid var(--border); color: var(--text-faint); font-size: 11.5px; text-transform: uppercase; letter-spacing: .3px; flex-wrap: wrap; gap: 10px; }
.footer-bottom .links { display: flex; gap: 18px; }
.footer-bottom .links a:hover { color: var(--text); }

.breadcrumb { padding: 16px 0; font-size: 11.5px; text-transform: uppercase; letter-spacing: .3px; color: var(--text-faint); border-bottom: 1px solid var(--border); }
.breadcrumb .container { display: flex; flex-wrap: wrap; row-gap: 4px; }
.breadcrumb a:hover { color: var(--text); }
.breadcrumb .sep { margin: 0 8px; }
.breadcrumb .current { color: var(--text-muted); overflow-wrap: anywhere; }

@media (max-width: 520px) {
  .breadcrumb { font-size: 10.5px; letter-spacing: .2px; }
  .breadcrumb .sep { margin: 0 5px; }
}

.shop-layout { display: grid; grid-template-columns: 230px 1fr; gap: 32px; align-items: start; }
.filter-box { border: 1px solid var(--border); padding: 18px; margin-bottom: 14px; }
.filter-box h4 { font-size: 11px; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 14px; color: var(--text-muted); }
.filter-row { display: flex; align-items: center; justify-content: space-between; padding: 7px 0; font-size: 13px; color: var(--text-muted); min-height: 32px; }
.filter-row:hover { color: var(--text); }
.filter-row input { accent-color: var(--gold); width: 15px; height: 15px; }
.filter-row .count { font-size: 11px; color: var(--text-faint); }
.range-slider { width: 100%; accent-color: var(--gold); margin: 10px 0; }
.shop-toolbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 22px; flex-wrap: wrap; gap: 12px; border-bottom: 1px solid var(--border); padding-bottom: 18px; }
.shop-toolbar .count-txt { color: var(--text-muted); font-size: 12.5px; text-transform: uppercase; letter-spacing: .3px; }

.shop-tools { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; justify-content: flex-end; }
.search-box { position: relative; display: flex; align-items: center; }
.search-input {
  -webkit-appearance: none; appearance: none;
  background: transparent; border: 1px solid var(--border-light); color: var(--text);
  padding: 10px 34px 10px 14px; border-radius: 0; font-size: 12px; font-weight: 700;
  min-height: 40px; width: 220px; letter-spacing: .5px; text-transform: uppercase;
}
.search-input::placeholder { color: var(--text-muted); font-weight: 700; letter-spacing: .5px; }
.search-input:focus { outline: none; border-color: var(--gold); }

.search-input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.search-clear {
  position: absolute; right: 2px; top: 50%; transform: translateY(-50%);
  width: 30px; min-height: 34px; display: flex; align-items: center; justify-content: center;
  background: none; border: 0; color: var(--text-muted); font-size: 20px; line-height: 1; cursor: pointer;
}
.search-clear:hover { color: var(--text); }

.search-clear[hidden] { display: none; }
.select {
  background: transparent; border: 1px solid var(--border-light); color: var(--text);
  padding: 10px 14px; border-radius: 0; font-size: 12px; font-weight: 700; min-height: 40px;
  text-transform: uppercase; letter-spacing: .5px;
}
.pagination { display: flex; gap: 0; justify-content: center; margin-top: 36px; border: 1px solid var(--border); width: fit-content; margin-left: auto; margin-right: auto; }
.pagination a, .pagination span { min-width: 38px; height: 38px; display: flex; align-items: center; justify-content: center; border-right: 1px solid var(--border); font-size: 12px; font-weight: 700; padding: 0 6px; }
.pagination a:last-child, .pagination span:last-child { border-right: none; }
.pagination .active { background: var(--gold-metal); color: var(--gold-ink); box-shadow: var(--gold-emboss); }
.pagination a:hover { color: var(--gold-hi); background: var(--surface-2); }

.product-layout { display: grid; grid-template-columns: 1fr 1fr; gap: 48px; }
.gallery-main { aspect-ratio: 1/1; margin-bottom: 10px; border: 1px solid var(--border); }

.gallery-main--photo { aspect-ratio: auto; border: none; }
.gallery-frame { position: relative; aspect-ratio: 1/1; border: 1px solid var(--border); overflow: hidden; }
.gallery-frame img { width: 100%; height: 100%; object-fit: cover; display: block; cursor: zoom-in; }
.gallery-thumbs { display: flex; gap: 8px; margin-top: 8px; }
.gallery-thumb { width: 64px; height: 64px; flex-shrink: 0; padding: 0; border: 1px solid var(--border); background: none; opacity: .6; }
.gallery-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.gallery-thumb:hover { opacity: .85; }
.gallery-thumb.active { opacity: 1; border-color: var(--gold); }

.gallery-nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 40px; height: 40px; border-radius: 50%;
  background: rgba(8,12,6,.85); border: 1px solid var(--gold);
  display: flex; align-items: center; justify-content: center;
  font-size: 22px; line-height: 1; color: var(--gold-hi); cursor: pointer;
}
.gallery-nav:hover { background: var(--gold); color: var(--gold-ink); }
.gallery-nav--prev { left: 10px; }
.gallery-nav--next { right: 10px; }

.lightbox { position: fixed; inset: 0; z-index: 1000; display: flex; align-items: center; justify-content: center; }
.lightbox[hidden] { display: none; }
.lightbox:not([hidden]) { animation: lightbox-fade .18s ease; }
@keyframes lightbox-fade { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .lightbox:not([hidden]) { animation: none; } }
body.lightbox-open { overflow: hidden; }
.lightbox-backdrop { position: absolute; inset: 0; background: rgba(0,0,0,.9); }
.lightbox-img { position: relative; z-index: 1; max-width: 90vw; max-height: 88vh; object-fit: contain; }
.lightbox-close {
  position: absolute; top: 16px; right: 16px; z-index: 2;
  width: 44px; height: 44px; background: transparent; border: none;
  color: #fff; font-size: 30px; line-height: 1; cursor: pointer;
}
.lightbox-nav {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
  width: 52px; height: 52px; border-radius: 50%;
  background: rgba(255,255,255,.12); border: 1px solid rgba(255,255,255,.35);
  color: #fff; font-size: 28px; line-height: 1;
  display: flex; align-items: center; justify-content: center; cursor: pointer;
}
.lightbox-nav:hover { background: rgba(255,255,255,.24); }
.lightbox-nav--prev { left: 12px; }
.lightbox-nav--next { right: 12px; }
@media (max-width: 520px) {
  .lightbox-nav { width: 44px; height: 44px; font-size: 24px; }
  .lightbox-nav--prev { left: 6px; }
  .lightbox-nav--next { right: 6px; }
}
.product-info .card-cat { font-size: 11px; }
.product-title { font-size: 20px; font-weight: 800; margin: 8px 0 12px; letter-spacing: .3px; text-transform: uppercase; }
.product-price { font-size: 18px; font-weight: 700; color: var(--gold-hi); margin-bottom: 14px; text-shadow: var(--gold-glow); }

.stock-meter { margin-bottom: 20px; max-width: 280px; }
.stock-meter-track { height: 6px; background: var(--surface-2); border: 1px solid var(--border); overflow: hidden; }
.stock-meter-fill { height: 100%; width: 0%; background: var(--gold-metal); box-shadow: 0 0 10px rgba(241,194,48,.5); transition: width .5s ease; }
.stock-meter-label { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .3px; color: var(--text-muted); margin-top: 7px; }
.stock-meter--low .stock-meter-fill { background: var(--red); animation: stock-fill-pulse 1.8s ease-in-out infinite; }
.stock-meter--low .stock-meter-label { color: var(--red); }
.stock-meter--out .stock-meter-fill { background: var(--text-faint); }
.stock-meter--out .stock-meter-label { color: var(--text-faint); }
@keyframes stock-fill-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: .5; }
}
@media (prefers-reduced-motion: reduce) {
  .stock-meter--low .stock-meter-fill { animation: none; }
}

.qty-add { display: flex; gap: 10px; align-items: center; margin-bottom: 22px; }
.qty-box { display: flex; align-items: center; border: 1px solid var(--border-light); }
.qty-box button { width: 40px; height: 46px; background: transparent; border: none; color: var(--text); font-size: 16px; }
.qty-box button:hover { color: var(--text-muted); }
.qty-box input { width: 40px; text-align: center; background: transparent; border: none; color: var(--text); font-size: 14px; }
.product-meta-row { display: flex; gap: 20px; font-size: 12px; text-transform: uppercase; letter-spacing: .3px; color: var(--text-muted); border-top: 1px solid var(--border); padding-top: 16px; flex-wrap: wrap; }

.cart-layout { display: grid; grid-template-columns: 1fr 360px; gap: 40px; align-items: start; }
.cart-row { display: grid; grid-template-columns: 80px 1fr auto auto auto; gap: 16px; align-items: center; padding: 18px 0; border-bottom: 1px solid var(--border); }

.cart-block {
  font-size: 12px; font-weight: 700; letter-spacing: .3px; text-align: center; margin-top: 10px;
  color: var(--red); text-transform: uppercase;
}
.cart-note { font-size: 11px; font-weight: 600; letter-spacing: .3px; margin-top: 4px; color: var(--text-muted); }
.cart-note--gone { color: var(--red); }
.cart-row--gone .cart-thumb, .cart-row--gone .card-title { opacity: .55; }
.cart-thumb { width: 72px; height: 72px; position: relative; overflow: hidden; }
.cart-thumb img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.cart-remove { display: inline-flex; padding: 8px; color: var(--text-faint); }
.cart-remove:hover { color: var(--text); }
.summary-box { border: 1px solid var(--border); padding: 24px; }
.summary-box h3 { font-size: 14px; letter-spacing: 1px; text-transform: uppercase; margin-bottom: 18px; font-weight: 800; }
.summary-row { display: flex; justify-content: space-between; padding: 10px 0; font-size: 13px; text-transform: uppercase; letter-spacing: .3px; color: var(--text-muted); border-bottom: 1px solid var(--border); }
.summary-row.total { color: var(--text); font-weight: 800; font-size: 15px; border-bottom: none; padding-top: 14px; }
/* Why the shipping line says what it says -- "Add 12 more, or any item sold by the pound,
   and shipping is free." A fee with no reason next to it reads as a mistake, and this one
   is worth acting on: it is the difference between $25 and nothing. Empty most of the
   time, and :empty keeps it from reserving space when there is nothing to say. */
.summary-note { margin-top: 10px; font-size: 12px; line-height: 1.5; color: var(--text-muted); }
.summary-note:empty { display: none; }
.coupon-row { display: flex; gap: 8px; margin: 16px 0; }
.coupon-row input { flex: 1; background: transparent; border: 1px solid var(--border-light); padding: 11px 14px; color: var(--text); font-size: 12.5px; min-height: 40px; text-transform: uppercase; letter-spacing: .3px; }
.empty-cart { text-align: center; padding: 72px 24px; color: var(--text-muted); }

.order-note-label {
  display: block; margin-top: 16px; margin-bottom: 6px;
  font-size: 10px; font-weight: 700; letter-spacing: .5px; text-transform: uppercase;
  color: var(--text-muted);
}
.order-note {
  width: 100%; background: transparent; border: 1px solid var(--border-light);
  padding: 10px 12px; color: var(--text); font-family: inherit; font-size: 13px;
  resize: vertical;
}
.order-note:focus { outline: none; border-color: var(--gold); }

.send-panel { margin-top: 18px; border-top: 1px solid var(--border); padding-top: 16px; }
.send-panel[hidden] { display: none; }
.send-panel h4 {
  font-size: 11px; letter-spacing: 1px; text-transform: uppercase;
  margin-bottom: 8px; color: var(--text);
}
.send-hint { font-size: 12px; line-height: 1.6; color: var(--text-muted); margin: 0 0 12px; }
.send-actions { display: flex; flex-direction: column; gap: 8px; }


@media (max-width: 520px) {
}


.btn-plain {
  display: block; width: 100%; min-height: 44px; margin-top: 10px;
  background: none; border: 0; padding: 12px 8px;
  font: inherit; font-size: 12px; font-weight: 700; letter-spacing: .3px;
  text-transform: uppercase; color: var(--text-muted); text-decoration: underline;
  cursor: pointer;
}
.btn-plain:hover { color: var(--text); }

.invoice-modal { position: fixed; inset: 0; z-index: 1100; display: flex; align-items: center; justify-content: center; padding: 16px; }
.invoice-modal[hidden] { display: none; }
.invoice-modal:not([hidden]) { animation: lightbox-fade .18s ease; }
@media (prefers-reduced-motion: reduce) { .invoice-modal:not([hidden]) { animation: none; } }
body.invoice-open { overflow: hidden; }
.invoice-modal-backdrop { position: absolute; inset: 0; background: rgba(0,0,0,.7); }
.invoice-modal-box {
  position: relative; z-index: 1; width: 100%; max-width: 460px;
  max-height: 88vh; display: flex; flex-direction: column;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border-light);
  border-radius: 14px; padding: 18px 16px 16px;
  box-shadow: 0 18px 50px rgba(0,0,0,.7);
}
@supports (height: 1dvh) { .invoice-modal-box { max-height: 88dvh; } }
.invoice-modal-box h4 { font-size: 14px; letter-spacing: .5px; text-transform: uppercase; margin: 0 0 6px; }

.invoice-modal-text {
  flex: 0 1 auto; min-height: 0; margin-bottom: 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; line-height: 1.5;
  white-space: pre-wrap; overflow-wrap: anywhere;
  overflow-x: hidden; overflow-y: auto; resize: none;
}

@media (max-width: 420px) {
  .invoice-modal-box { padding: 16px 12px 12px; }
  .invoice-modal-text { font-size: 11px; }
}
.invoice-modal-close {
  position: absolute; top: 6px; right: 8px; width: 40px; height: 40px;
  background: none; border: 0; font-size: 26px; line-height: 1;
  color: var(--text-muted); cursor: pointer;
}
.invoice-modal-close:hover { color: var(--text); }

.send-actions .btn { width: 100%; }

#checkout-btn[disabled] { opacity: .45; cursor: not-allowed; }

.filter-toggle-btn { display: none; }
@media (max-width: 1080px) {
  .grid { grid-template-columns: repeat(3, 1fr); }
  .footer-grid { grid-template-columns: 1fr 1fr; }
  .product-layout { grid-template-columns: 1fr; }
  .cart-layout { grid-template-columns: 1fr; }
}
@media (max-width: 860px) {
  .main-nav-row { display: none; }
  .burger { display: flex; }
  .mobile-only { display: inline-flex !important; }

  .header-logo-row { padding: 4px 14px; justify-content: space-between; gap: 6px; flex-wrap: wrap; }
  .header-left,
  .header-icons { position: static; transform: none; }
  .header-icons { gap: 6px; }
  /* The mark must NOT come down to meet the 44px tap targets any more. It used to be
     a landscape lockup, which stayed legible when it did; the duck is portrait, so
     44px of height is 22px of width and the character becomes a smudge. It holds 72px
     and the row grows to fit -- measured at 375px: the icon groups leave ~175px for
     the logo pair and the pair wants ~134px. */
  .header-logo-img { height: 72px; }
  .logo-word { font-size: 14px; letter-spacing: 1px; }
  .header-logo-row .logo { gap: 7px; }
  .icon-btn--chat { width: 30px; }
  .utility-bar .container { justify-content: center; gap: 16px; }
  .grid { grid-template-columns: repeat(2, 1fr); }
  .shop-layout { grid-template-columns: 1fr; }
  /* ...but not the links band. That rule is for the copy-plus-buttons band, which reads
     better left-aligned once it stacks; the links band is a single centred button and
     inherits flex-start from it otherwise. Only 681-860px was affected -- the <=680px
     block already re-centres -- which is exactly the width nobody opens by hand. */
  .filter-toggle-btn { display: inline-flex; }

  .shop-tools { width: 100%; justify-content: flex-start; }
  .search-box { flex: 1 1 100%; order: -1; }
  .search-input { width: 100%; }

  .search-input,
  .select { font-size: 16px; }
  .filters-col { display: none; }
  .filters-col.open { display: block; }
  .section { padding: 40px 0; }
  .product-title { font-size: 17px; }
}
@media (max-width: 520px) {
  .footer-grid { grid-template-columns: 1fr; }
  .container { padding: 0 16px; }
  .cart-row { grid-template-columns: 60px 1fr; row-gap: 10px; }
  .cart-row > *:nth-child(3) { grid-column: 2; }
  .cart-row > *:nth-child(4) { grid-column: 2; }
  .cart-row > *:nth-child(5) { grid-column: 2; justify-self: start; }
  .qty-add { flex-wrap: wrap; }
  .qty-add .btn { width: 100%; order: 3; }
}

@media (max-width: 680px) {
}
/* Only the very narrowest phones lose the name. 380px was the wrong line to draw it
   at: a 375px iPhone SE is one of the commonest widths there is, and it has 106px of
   slack either side of a 39px duck while the stacked name wants ~73px. Below 330px it
   genuinely stops clearing the icon groups. Placed after the 860px block on purpose --
   see the note beside .header-logo-img. */
@media (max-width: 330px) {
  .logo-word { display: none; }
  .header-logo-img { height: 60px; }
}
@media (max-height: 760px) {
}

@media (hover: none) and (pointer: coarse) {
  .burger { width: 44px; height: 44px; }
  /* Square crest: the home link is no wider than it is tall, so it gets padded out
     to a 44px target rather than growing the emblem past the row height. */
  .header-logo-row .logo { min-height: 44px; padding-inline: 5px; }

  .icon-btn:not(.icon-btn--chat) { width: 44px; height: 44px; }
  .icon-btn--chat { height: 44px; }
  .cart-remove { padding: 14px; }
  .btn-sm { min-height: 44px; }

  .pagination a, .pagination span { min-width: 44px; min-height: 44px; }
  .filter-row { min-height: 44px; }
  .filter-row input { width: 20px; height: 20px; }

  .order-note,
  .coupon-row input,
  .qty-box input,
  .search-input,
  .select { font-size: 16px; }
}

.gate-wrap { min-height: 82vh; display: flex; align-items: center; justify-content: center; padding: 24px; }
.lock-card {
  max-width: 460px; width: 100%; text-align: center;
  display: flex; flex-direction: column; align-items: center; gap: 32px;
  position: relative; z-index: 2;
}
/* The hero is STEADY. It used to breathe -- an animated filter: drop-shadow plus a 1.5%
   scale pulse -- and on a 700px alpha illustration that is two expensive things per frame:
   the shadow is re-rasterised from the alpha channel, and the bitmap is resampled at a new
   sub-pixel size, so the detail crawls. Against the smoke rising off it, that reads as the
   picture flickering rather than as anything glowing.

   The glow stays; it is simply painted once and left alone. Nothing here animates now, so
   the only moving thing on the splash is the smoke, which is where the movement belongs. */
.gate-logo {
  /* HEIGHT, not width: the duck is portrait, and 400px of width would be 875px of
     duck -- taller than most laptops and every phone. */
  height: clamp(210px, 34vh, 360px); width: auto; display: block;
  filter: drop-shadow(0 10px 26px rgba(0,0,0,.18));
}
.lock-form { display: flex; flex-direction: column; align-items: center; gap: 26px; width: 100%; }
.gate-group { display: flex; flex-direction: column; align-items: center; gap: 10px; width: 100%; }
.lock-label {
  font-size: 11px; font-weight: 700; letter-spacing: 2px; text-transform: uppercase;
  color: var(--text-muted); margin: 0;
}
.lock-input {
  width: 100%; max-width: 260px; text-align: center;
  background: var(--surface); border: 1px solid var(--border); color: var(--text);
  padding: 13px 16px; font-size: 15px; letter-spacing: 3px; font-family: inherit;
}
.lock-input::placeholder { color: var(--text-faint); letter-spacing: 1px; }
.lock-input:focus { outline: none; border-color: var(--gold); }
.lock-input.is-shaking { animation: lock-shake .32s ease; }
.lock-btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 46px; min-width: 200px; padding: 12px 26px;
  border: 1px solid var(--gold); background: transparent; color: var(--gold-hi);
  font-size: 12.5px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;
  cursor: pointer; transition: background .15s ease, color .15s ease;
}
.lock-btn:hover { background: var(--green-metal); color: var(--text); box-shadow: var(--gold-emboss), 0 4px 20px rgba(47,125,52,.35); }
.lock-btn[disabled] { opacity: .6; cursor: default; }

.lock-btn.is-nudged { animation: lock-nudge .5s ease; }
@keyframes lock-nudge {
  0%, 100% { background: transparent; color: var(--gold-hi); transform: none; }
  25%      { background: var(--green); color: var(--text); transform: scale(1.04); }
  60%      { background: var(--green); color: var(--text); transform: none; }
}
.lock-error {
  margin: 0; font-size: 12px; letter-spacing: .3px; text-transform: uppercase;
  color: var(--red); font-weight: 600;
}
.lock-error[hidden] { display: none; }
.lock-note {
  font-size: 11px; color: var(--text-faint); letter-spacing: .3px; max-width: 340px;
  line-height: 1.6; margin: 0;
}

@media (max-height: 760px) {
  .gate-wrap { min-height: 100vh; padding: 16px; }
  .lock-card { gap: 18px; }
  .lock-form { gap: 16px; }
  .gate-group { gap: 8px; }
  .gate-logo { height: clamp(190px, 30vh, 260px); }
}
@media (max-height: 560px) {
  .lock-card { gap: 12px; }
  .lock-form { gap: 12px; }
  .gate-logo { height: clamp(150px, 26vh, 200px); }
  .lock-note { font-size: 10px; line-height: 1.45; max-width: 420px; }
}
@keyframes lock-shake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-8px); }
  75% { transform: translateX(8px); }
}

/* The gate IS a wall — a plain dark screen hiding the shop until the password is
   right. This overlay is that same wall rendered on the page behind it: two halves
   sharing one jagged seam, covering it exactly (same --bg), so showing it changes
   nothing the visitor can see. Verifying age is what cracks it open. Hidden by
   default and only shown by the inline script in home.part.php when the gate just
   redirected here — a plain later visit to the homepage never sees it. */
/* The gate is a wall; verifying age SHATTERS it onto the shop.
   Cracks race out from the impact point first, then the wall itself breaks into
   chunks that fly away — inner ones first, so the hole opens at the middle and grows,
   the way a real wall gives. Every shard is a full-viewport box clipped to one
   polygon; PHP emits the geometry, so the whole thing is declarative CSS that starts
   on its own the instant it renders, with no JS in the path to mistime or skip. */
/* The shop, pulled in behind the wall before it breaks. A snapshot with its scripts
   inert -- it is on screen for well under a second, mostly behind flying chunks, and
   the real page replaces it the moment the break finishes. Mirrors body.page-home's
   own column layout so the shop lands where it will land for real. */
.lk-reveal {
  position: fixed; inset: 0; z-index: 1; overflow: hidden;
  display: flex; flex-direction: column;
  background: var(--bg);
  /* Hidden until the smoke has actually covered the screen. visibility as well as
     opacity, so nothing in here can be tabbed to or read out while it is invisible. */
  opacity: 0; visibility: hidden;
}
/* Cross-faded in rather than switched on. It happens under an opaque ground now, so a
   hard cut would not actually be seen -- but the fade costs nothing and it is what keeps
   this correct if the cover ever stops being opaque again. */
.lk-reveal { transition: opacity 170ms ease; }
.lk-reveal.is-shown { opacity: 1; visibility: visible; }
/* ---- Splash --------------------------------------------------------------------
   Nothing moves on the gate until the password lands. On success the duck logo grows
   out of the logo already on screen, spinning, the ground behind it goes solid, the
   shop is dropped in behind it, and then the logo shrinks away and the ground fades
   off, uncovering the real page.

   The GROUND is a separate element from the logo, and has to be: nesting the logo
   inside a fading ground multiplies the two opacities, so the logo would be invisible
   for exactly the frames it is meant to be growing through.

   Its own compositor layer, and only transform/opacity animate, so a full-viewport
   cover costs no rasterising. It only ever exists during the burst, so it starts life
   on top. */
.lk-splash {
  position: fixed; inset: 0; z-index: 600; pointer-events: none;
  display: flex; align-items: center; justify-content: center;
  transform: translateZ(0);
}
.lk-splash-bg {
  position: absolute; inset: 0; background: var(--bg);
  opacity: 0; will-change: opacity;
}
.lk-splash-img {
  position: relative;
  /* Big, but it has to SPIN inside the viewport: at 90 degrees a portrait logo is as
     wide as it is tall, so the height is capped against the WIDTH too (76vw) or the
     duck's head and feet are sliced off every half turn. */
  height: min(54vh, 76vw, 440px); width: auto; display: block;
  transform-origin: 50% 50%; backface-visibility: hidden;
  will-change: transform, opacity;
  filter: drop-shadow(0 20px 44px rgba(0,0,0,.55));
}
@media (max-width: 520px) {
  .lk-splash-img { height: min(44vh, 74vw, 340px); }
}

.promo-pop { position: fixed; inset: 0; z-index: 200; display: flex; align-items: center; justify-content: center; padding: 20px; }
.promo-pop[hidden] { display: none; }
.promo-pop-backdrop { position: absolute; inset: 0; background: rgba(0,0,0,.82); }
.promo-pop-box {
  position: relative; width: 100%; max-width: 620px; text-align: center;
  background: linear-gradient(158deg, #7a4726 0%, #5c3419 26%, #3d2412 55%, #1c3d16 78%, #2f7d34 100%);
  color: var(--text); border: 4px solid var(--gold);
  padding: 46px 34px 40px; box-shadow: 0 24px 60px rgba(0,0,0,.7);
  animation: promo-pop-in .32s cubic-bezier(.2,.9,.3,1.2);
  max-height: calc(100vh - 40px); overflow-y: auto;
}
.promo-pop-kicker { font-size: clamp(28px, 7vw, 54px); font-weight: 900; line-height: .98; letter-spacing: -1px; text-transform: uppercase; margin: 0 0 14px; }
.promo-pop-line { font-size: clamp(15px, 2.6vw, 21px); font-weight: 800; letter-spacing: .5px; text-transform: uppercase; line-height: 1.35; margin: 0 0 8px; }
.promo-pop-deals { display: flex; flex-direction: column; gap: 6px; margin: 14px 0 4px; }
.promo-pop-deal { display: flex; align-items: baseline; justify-content: center; gap: 10px; flex-wrap: wrap; }
.promo-pop-off { font-size: clamp(30px, 7.4vw, 56px); font-weight: 900; line-height: 1.05; letter-spacing: -2px; }
.promo-pop-per { font-size: clamp(12px, 2.3vw, 17px); font-weight: 800; letter-spacing: 1px; text-transform: uppercase; opacity: .95; }
.promo-pop-terms { font-size: clamp(11px, 2vw, 13.5px); font-weight: 700; letter-spacing: .6px; text-transform: uppercase; line-height: 1.35; opacity: .9; margin: 10px 0 0; }
.promo-pop-cta {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  margin-top: 22px; min-height: 54px; padding: 14px 34px;
  background: var(--bg); color: var(--gold-hi); border: 1px solid var(--gold);
  font-size: 17px; font-weight: 900; letter-spacing: 1.5px; text-transform: uppercase;
}
.promo-pop-cta:hover { background: var(--gold-hi); color: var(--gold-ink); }
.promo-pop-num { display: block; margin-top: 14px; font-size: 13px; font-weight: 700; letter-spacing: 1px; color: var(--text); opacity: .92; }
.promo-pop-close {
  position: absolute; top: 6px; right: 10px; width: 44px; height: 44px;
  background: none; border: none; color: var(--text); font-size: 30px; line-height: 1; cursor: pointer;
}
.promo-pop-close:hover { opacity: .7; }
.promo-pop-dismiss {
  display: block; margin: 16px auto 0; background: none; border: none; color: var(--text);
  font-size: 11px; letter-spacing: .8px; text-transform: uppercase; opacity: .8;
  text-decoration: underline; text-underline-offset: 3px; cursor: pointer;
}
@keyframes promo-pop-in {
  from { transform: scale(.88) translateY(14px); opacity: 0; }
  to   { transform: none; opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .category-card { transition: border-color .15s ease; }

  .lock-input.is-shaking { animation: none; }

  .lock-btn.is-nudged { animation: none; background: var(--green); color: var(--text); }
  .promo-pop-box { animation: none; }
  /* The smoke canvas is never created under reduced motion -- the gate navigates
     straight through -- so there is nothing here to switch off. */
}

/* ======================== LIQUID METAL =========================
   Third pass. The first two built the metal out of a many-banded
   gradient, and the bands were the problem: a gradient can only make
   STRAIGHT lines, so the surface read as a striped ramp sliding
   sideways. Straight stripes are what cheap metal looks like.

   Real pattern-welded steel -- damascus -- has folded, flowing grain, and
   no gradient of any number of stops can produce it. So the grain is now
   a texture: assets/metal-grain.webp, 256px, 10.8KB, neutral grey so it
   retints with whatever body ramp it is blended over, generated by
   domain-warping a periodic field and then contouring it. Every term in
   that field is a sinusoid at an integer frequency over the tile, so it
   wraps exactly and the flow never shows a seam.

   Three layers, and only two of them move:

     BODY   the metal itself, a GENTLE gradient held STILL. Nothing about
            the body slides any more. That single change is most of what
            makes this read as a solid metal object catching light rather
            than as a video playing inside a button.
     GRAIN  the damascus tile, blended soft-light so it MODULATES the
            body instead of painting over it -- mid grey in the texture is
            a no-op, which is why it is histogram-centred when generated.
            It drifts one whole tile per cycle, diagonally, so the grain
            flows along its own lines. Movement ALONG the grain is far
            quieter than bands crossing it.
            The tile is mostly narrow specular glints with only a whisper
            of broad figure, and that ratio is the whole trick: wood grain
            and damascus grain have nearly the same geometry, and what
            separates them to the eye is that metal throws thin bright
            lines while timber has broad soft bands. Rendered bold, the
            figure read as maple veneer on every button. See
            tools/make-metal-grain.py --river / --ridge.
     GLOSS  one soft specular band, sized in percent so a large surface
            gets a large highlight, crossing once per cycle.

   The separate speck layers are gone. The grain carries fine detail of
   its own, and confetti on top of flowing steel is the opposite of
   subtle. Restoring them is one line if they are missed.
   ============================================================== */
:root {
  /* THE SPEED KNOB -- every metal surface is tied to it. */
  --gold-cycle: 22s;
  --gold-halo-cycle: 11s;
  /* Tile scale. The animation below moves the grain by exactly this much,
     so the two have to change together or the loop gains a seam. */
  --gold-grain: 110px;

  /* The BODY is now light-from-above rather than a bright band sliding
     across, and its range is deliberately narrow. Three versions of this
     were rendered against the real 26px support bar and compared:
       a wide horizontal ramp put an arbitrary bright blob mid-bar;
       a flat fill with no ramp at all read as matte paint, not metal;
       gentle top-lighting read as a strip catching light.
     2deg off vertical, because a perfectly axis-aligned ramp looks
     printed. The grain carries the character; this gives the form.

     BRIGHTNESS, second pass. A ramp that stays pale the whole way down reads
     as painted card rather than metal, so the shadow end drops into a deep
     forest green while the top edge stays cream -- which is the pairing the
     logo's own wordmark uses.
  */
  --gold-body: linear-gradient(178deg,
    #ffdb5c 0%, #f6c936 22%, #e0b026 52%, #bd8f18 76%, #96690c 100%);
  --gold-grain-img: url('assets/metal-grain.webp?v=590e2ab6a8');
  --gold-gloss: linear-gradient(102deg,
    rgba(255,255,255,0) 40%, rgba(255,255,255,.10) 46%,
    rgba(255,255,255,.34) 50%, rgba(255,255,255,.10) 54%,
    rgba(255,255,255,0) 60%);

  --green-body: linear-gradient(178deg,
    #55b45a 0%, #3c8c3e 26%, #2f7d34 55%, #1f5c22 78%, #12330f 100%);
  --green-layers: var(--gold-gloss), var(--gold-grain-img), var(--green-body);

  --gold-layers: var(--gold-gloss), var(--gold-grain-img), var(--gold-body);
  --gold-layer-size: 240% 100%, var(--gold-grain) var(--gold-grain), 100% 100%;
  --gold-layer-repeat: no-repeat, repeat, no-repeat;
  --gold-layer-blend: screen, soft-light, normal;
}

/* The grain steps exactly one tile in each axis, so the loop is seamless;
   the gloss sweeps the element; the body never moves. */
@keyframes gold-live {
  from { background-position: -70% 0, 0 0, 0 0; }
  to   { background-position: 180% 0, calc(var(--gold-grain) * -1) var(--gold-grain), 0 0; }
}

/* GOLD fills -- all of these previously said `background: var(--gold-metal)`. */
.btn-primary,
.btn-outline:hover,
.qbtn:hover,
.gallery-nav:hover,
.pagination .active,
.stock-meter-fill {
  background-image: var(--gold-layers);
  background-size: var(--gold-layer-size);
  background-repeat: var(--gold-layer-repeat);
  background-blend-mode: var(--gold-layer-blend);
  animation: gold-live var(--gold-cycle) linear infinite;
}

/* GREEN fills — the same live metal, duck-green body. The support bar is the shop's
   banner and the gate button is the moment the doors open: the darkest, most solid
   green on the site, so both read as weight against the dark page. */
.support-bar,
.badge,
.lock-btn:hover,
.lock-btn.is-nudged {
  background-image: var(--green-layers);
  background-size: var(--gold-layer-size);
  background-repeat: var(--gold-layer-repeat);
  background-blend-mode: var(--gold-layer-blend);
  animation: gold-live var(--gold-cycle) linear infinite;
  color: var(--text);
}

/* GOLD text. Same material clipped to the glyphs. A text-shadow cannot be
   used on transparent text -- it draws the shadow of a shape nobody can
   see -- so the outer glow is a drop-shadow filter, which follows the
   visible pixels. */
.section-head h2,
.gold-head,
.product-price,
.card-price,
.card-price .now {
  background-image: var(--gold-layers);
  background-size: var(--gold-layer-size);
  background-repeat: var(--gold-layer-repeat);
  background-blend-mode: var(--gold-layer-blend);
  -webkit-background-clip: text; background-clip: text;
  color: transparent; -webkit-text-fill-color: transparent;
  text-shadow: none;
  filter: drop-shadow(0 0 12px rgba(241,194,48,.34));
}
/* Headings and the single product price animate. Card prices do not: that
   one repeats dozens of times per screen, and sixty small elements
   repainting every frame costs real battery for something illegible at
   13px. They keep the material, still. */
.section-head h2,
.gold-head,
.product-price {
  animation: gold-live var(--gold-cycle) linear infinite;
}

/* LEAF borders: a 1px ring of the same material, as a masked pseudo-
   element, because a border cannot hold a gradient. The grain is left out
   -- at one pixel there is nothing of it to see.

   Geometry: the border stays 1px and only turns transparent, so nothing
   reflows. The ring sits at inset:-1px, the BORDER box; an absolutely
   positioned child is offset from the PADDING box, so inset:0 would draw
   it a pixel inside the border and leave a transparent hairline outside.

   GUARDED, and the guard is not decoration. Without mask compositing the
   two mask layers UNION instead of subtracting, so the ring would paint
   as a SOLID METAL RECTANGLE over the whole control -- covering the label
   on every outline button, the checkout send buttons included. This site
   is opened inside Instagram and TikTok webviews on purpose, which is
   exactly where an old engine turns up. No mask support, no ring, and the
   original solid border is left exactly as it was. */
@supports ((mask-composite: exclude) or (-webkit-mask-composite: xor)) {
  .btn,
  .qbtn,
  .gallery-nav,
  .lock-btn {
    position: relative;
    border-color: transparent;
  }
  .btn::after,
  .qbtn::after,
  .gallery-nav::after,
  .lock-btn::after {
    content: ''; position: absolute; inset: -1px; padding: 1px;
    border-radius: inherit; pointer-events: none;
    background-image: var(--gold-gloss), var(--gold-body);
    background-size: 240% 100%, 100% 100%;
    background-repeat: no-repeat, no-repeat;
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
            mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
            mask-composite: exclude;
    animation: gold-edge var(--gold-cycle) linear infinite;
  }
  .gallery-nav::after { border-radius: 50%; }
}
@keyframes gold-edge {
  from { background-position: -70% 0, 0 0; }
  to   { background-position: 180% 0, 0 0; }
}

/* Thin gold trim on the category tiles.
   NOT the masked ring above, and it cannot be: .category-card sets
   overflow:hidden, which clips a pseudo-element at inset:-1px straight off
   the card. This rides the card's OWN background instead -- a solid fill
   clipped to the padding box, the metal clipped to the border box, so the
   metal paints in the 1px border channel and nowhere else.
   background-origin/background-clip ignore overflow, and the corner radius
   comes along for free. */
.category-card {
  border-color: transparent;
  background-image: linear-gradient(#1e1810, #1e1810), var(--gold-gloss), var(--gold-body);
  background-size: auto, 240% 100%, 100% 100%;
  background-repeat: no-repeat, no-repeat, no-repeat;
  background-origin: border-box;
  background-clip: padding-box, border-box, border-box;
  animation: gold-trim var(--gold-cycle) linear infinite;
}
@keyframes gold-trim {
  from { background-position: 0 0, -70% 0, 0 0; }
  to   { background-position: 0 0, 180% 0, 0 0; }
}

/* The logo is a PNG, not text, so there are no glyphs to clip a gradient
   to. It gets a slow breathing halo instead, on its own clock so the
   header does not pulse in lockstep with the bar above it. */
@keyframes gold-halo {
  0%, 100% { filter: drop-shadow(0 0 6px rgba(241,194,48,.28)); }
  50%      { filter: drop-shadow(0 0 15px rgba(242,241,224,.52)); }
}
.header-logo-img,
.drawer-logo-img {
  animation: gold-halo var(--gold-halo-cycle) ease-in-out infinite;
}

/* ---- Breathing glow, the same idea as the logo halo ----
   A metal surface on jet black wants to look LIT, not printed, and the
   cheapest honest way to say that is an outer glow that breathes. This is
   filter: drop-shadow rather than box-shadow deliberately: every one of
   these elements already carries a box-shadow (emboss insets, lift), and
   animating that property would have to restate all of it and would drop
   whatever it forgot. A filter composes on top and touches nothing.

   All of it runs on --gold-halo-cycle, the LOGO's clock, so the header
   mark and the metal around it breathe together instead of drifting in and
   out of phase.

   WHY IT IS NOT ON EVERY METAL THING. Counted live: the shop grid renders
   25 .btn-primary and 24 .card-price on one screen. A blurred shadow
   repainting 25 times a frame is a real cost on a phone, for a glow
   nobody looks at on a card button. So the breathing glow goes on the
   handful of one-off surfaces, and the grid buttons get a stronger STATIC
   glow instead. .btn-sm is the tell -- it is only ever on the grid
   cards. */
@keyframes gold-breathe {
  0%, 100% { filter: drop-shadow(0 0 5px rgba(255,213,74,.26)); }
  50%      { filter: drop-shadow(0 0 17px rgba(242,241,224,.54)); }
}
/* The tiles sit in a grid with gaps as small as 3px, so a full-strength
   glow on each would pool into one warm haze instead of reading as eight
   lit objects. Theirs is deliberately about a third as strong. */
@keyframes gold-breathe-soft {
  0%, 100% { filter: drop-shadow(0 0 4px rgba(255,213,74,.16)); }
  50%      { filter: drop-shadow(0 0 11px rgba(242,241,224,.32)); }
}
.support-bar,
.badge,
.pagination .active,
.btn-primary:not(.btn-sm) {
  animation: gold-live var(--gold-cycle) linear infinite,
             gold-breathe var(--gold-halo-cycle) ease-in-out infinite;
}
.category-card {
  animation: gold-trim var(--gold-cycle) linear infinite,
             gold-breathe-soft var(--gold-halo-cycle) ease-in-out infinite;
}
/* The 25 grid buttons: same warmth, no per-frame cost. */
.btn-primary.btn-sm {
  box-shadow: var(--gold-emboss), 0 0 18px rgba(255,213,74,.34), 0 3px 14px rgba(0,0,0,.5);
}

/* Reduced motion: the material, none of the movement. Still metal, just
   standing still. */
@media (prefers-reduced-motion: reduce) {
  .btn-primary, .btn-primary:not(.btn-sm), .btn-outline:hover, .qbtn:hover, .gallery-nav:hover,
  .lock-btn:hover, .lock-btn.is-nudged, .support-bar, .badge,
  .pagination .active, .stock-meter-fill,
  .section-head h2, .gold-head, .product-price,
  .btn::after, .qbtn::after, .gallery-nav::after, .lock-btn::after,
  .category-card,
  .header-logo-img, .drawer-logo-img {
    animation: none;
  }
}

/* ==================== APP-STYLE HOME PAGE ====================
   Search pill, a row of category circles, horizontal product rails and a
   bottom tab bar -- the shape of a shop app, in Duck Distro's own colours.

   Appended at the END of the file ON PURPOSE. styles.css sets several of these
   properties again inside earlier media queries, and at equal specificity the
   later rule wins; a block placed further up reads correctly and does nothing.
   That already cost a round on .header-logo-img.

   ROUNDED, where the rest of the site is square (--radius is 0px and stays 0px).
   These are new components and the reference this was built from is round; the
   catalog grid, the buttons and the panels are untouched, so the shop still
   looks like the shop.
   ============================================================== */
:root {
  --r-card: 16px;
}

/* The "Shop" heading uses the site's OWN .section-head / .view-all, not a second
   heading component. There was one here, cloned for the rails; the rails are gone and
   two flex-heading rules that must be kept in step is how they stop being in step. */

/* ---- the shelf banners ----
   The art is a set of 3.44:1 banners, so the grid is ONE column on a phone and two
   from the tablet breakpoint up. Not four: at 1440px four across makes each plate
   ~310px wide, and "CONCENTRATES" hand-lettered across that is 24px of word -- the
   plate stops being readable before the grid stops fitting.

   The plates carry their own rounded green frame with a little of the sheet's dark
   ground outside it in the corners. The radius here clips that off; it is deliberately
   modest so it never eats into the frame itself at desktop sizes. */
/* SIZED TO FIT THE VIEWPORT, so all eight shelves are on screen with no scrolling.
   The plate WIDTH is solved from the height available to the grid, which is the only
   free variable: each plate's height follows from its 760x221 aspect, so eight of them
   in one column are 8*(W/3.43891) + 7 gaps tall, and that has to come out under the
   space the furniture leaves.

     one column   W = (fit - 7*gap) * 3.43891/8  = (fit - 7*gap) * 0.4298638
     two columns  W = (fit - 3*gap) * 3.43891/4, and the grid is 2W + gap

   --furn is that furniture, MEASURED and not guessed, and deliberately rounded UP:
   over-subtracting costs a little plate width, under-subtracting puts the scrollbar
   back, which is the whole thing this is here to stop. Measured at 375x812 --
   support 24 + header 81 + section padding 2x19.5 + Join Signal band 83 + 4px of
   borders = 231. The header grows with width (69px at 320, 81 at 375, 117 at 1440)
   because the logo is a clamp, so --furn is set per breakpoint at each one's worst case.

   The FLOOR matters, and where it sits was measured against real viewport heights. A
   clamp with nothing under it resolves to a few pixels on a very short viewport, which
   "fits" perfectly and shows nothing legible -- so below the floor the page scrolls
   again, on purpose.

   The two floors are set in DIFFERENT units and that matters: the one-column floor is
   the plate (180px), the two-column one is the GRID (600px, so a 295px plate). 240px of
   grid was the first attempt and it was far too low -- a landscape phone came out
   "fitting" with 123px plates, which is art nobody can read. Below either floor the page
   scrolls instead, which is the honest failure.

   Everything from a 667px iPhone SE upward fits: 667, 740, 780, 812, 844, 896, 915 in
   portrait, and every desktop window down to 600px tall. A 320x568 (2016 SE) still needs
   ~36px of scroll, and a landscape phone scrolls -- eight 3.44:1 banners in 390px of
   height cannot be legible at any size. */
.cat-plates {
  display: grid; grid-template-columns: 1fr;
  --gap: 5px;
  --furn: 176px;
  gap: var(--gap);
  max-width: 560px;                                    /* no-dvh fallback */
  max-width: clamp(180px, calc((100dvh - var(--furn) - 7 * var(--gap)) * 0.4298638), 560px);
  margin-inline: auto;
}
@media (min-width: 521px) { .cat-plates { --gap: 7px; --furn: 188px; } }
.cat-plate {
  position: relative; display: block; overflow: hidden;
  border-radius: 10px;
  /* From the art, not guessed: the plates are cut and normalised to 760x221. */
  aspect-ratio: 760 / 221;
  background: var(--surface-2);
  transition: transform .15s ease, box-shadow .15s ease;
}
.cat-plate img { width: 100%; height: 100%; object-fit: cover; display: block; }
.cat-plate:hover, .cat-plate:focus-visible {
  transform: translateY(-2px);
  box-shadow: 0 8px 22px rgba(0,0,0,.5), var(--gold-glow);
}
.cat-plate:active { transform: none; }

/* Shown ONLY on a shelf with nothing in it -- see main.js. Backed and cornered so it
   reads off a busy leaf pattern, and paired with the dimming below so an empty shelf is
   obvious before it is tapped rather than after. */
.cat-plate-count {
  position: absolute; top: 8px; right: 8px;
  padding: 3px 8px; border-radius: var(--r-card);
  background: rgba(8,12,6,.82); color: var(--text);
  font-size: 10.5px; font-weight: 800; letter-spacing: .8px; text-transform: uppercase;
  line-height: 1.5; pointer-events: none;
}
/* Nothing in stock: dimmed, still tappable. shop.php says why. */
/* An empty shelf is NOT greyed out. It used to be dimmed to .38 and desaturated, and on
   art this saturated that reads as a broken image rather than as a state -- one plate
   looking washed out beside seven that do not. The pill says "Restocking" and that is
   enough; the plate keeps its colour. */
.cat-plate.is-empty .cat-plate-count { background: rgba(8,12,6,.88); }

/* TWO COLUMNS AT 768, and the grid CAPPED at 1040 above that.

   Two things this is balancing. It must not switch so early that two columns are
   narrower than the one column they replaced -- at the 681px drawer breakpoint two
   columns give 310px plates against the 343px a phone gets, so the art would shrink as
   the window grew. And it must not leave a tablet scrolling two screens of 620px
   banners, which is what waiting until 1024 did.

   768 is tablet portrait: two plates clear 350px each there, and the whole shelf list
   comes down to about one screen. The 1040 cap stops a wide desktop inflating them back
   to 609px -- 513px is plenty for hand-lettering that only has to be recognised. */
@media (min-width: 768px) {
  .cat-plates {
    grid-template-columns: repeat(2, 1fr);
    --gap: 9px;
    --furn: 220px;
    max-width: 1280px;                                 /* no-dvh fallback */
    max-width: clamp(600px, calc((100dvh - var(--furn) - 3 * var(--gap)) * 1.7194552 + var(--gap)), 1280px);
  }
  .cat-plate-count { font-size: 11px; top: 10px; right: 10px; }
}
@media (max-width: 520px) {
  .cat-plate { border-radius: 8px; }
  .cat-plate-count { font-size: 9.5px; padding: 2px 6px; letter-spacing: .4px; }
}

/* ---- the Join Signal band at the foot of the home page ----
   One centred button under the shelves. Its own rules rather than the fourteen the old
   band had: that markup carried a .container, an .actions row and two buttons, and all
   of it existed to lay out things that are no longer there. */
/* NO divider above the button, and none under the header on this page either -- see
   .page-home .header below. Nothing scrolls under the header here, which is the only
   job that line has. */
.cta-band { margin-top: 0; }
.cta-band .container {
  display: flex; justify-content: center;
  padding: 9px 24px calc(9px + env(safe-area-inset-bottom, 0px));
}
/* Small on purpose, and not only for its own sake: this band is part of the height
   budget the shelves are sized against, so every pixel taken off it comes back as
   plate width -- 0.43px of it per pixel in one column, 0.86 in two. 32px is the floor
   I am willing to go to; below that it stops being a comfortable thumb target. */
.cta-band .btn { min-height: 32px; padding: 6px 20px; font-size: 10.5px; letter-spacing: .8px; }
@media (max-width: 520px) {
  .cta-band .container { padding: 8px 16px calc(8px + env(safe-area-inset-bottom, 0px)); }
}

/* The header's bottom rule goes ON THE HOME PAGE ONLY. Its job is to separate the
   sticky header from content sliding under it, and on this page nothing does -- the
   whole thing fits one screen. It stays on the shop, product and cart pages, where
   content really does scroll underneath. */
.page-home .header { border-bottom-color: transparent; }

/* The shelves' section padding is budget as well. clamp(10px, 2.4vh, 30px) top AND
   bottom spent 39px of an 812px phone on white space above and below the only thing on
   the page; this keeps the breathing room proportional but much tighter, and hands the
   difference to the plates. */
.page-home .section.section--tight { padding: clamp(5px, 0.8vh, 11px) 0; }

/* ---- the home page SCROLLS now, and that undoes a whole scheme above ----
   The old home page was one screen: eight banner plates, centred, never scrolling.
   That is what body.page-home's flex column, the ::after spacer and .category-grid's
   --budget / --home-furniture / --home-pad / --tile-cap arithmetic are all for -- the
   grid's max-width is SOLVED from a viewport-height budget so the plates fit without a
   scrollbar.

   With a search field, a category row and product rails above them the page scrolls by
   design, so that budget is not just unnecessary, it is wrong: on a 375x812 phone it
   solved to a 223px-wide grid, which left the plates two-thirds width and floating in
   the middle of the page. Those custom properties are now unused -- the comments
   upstream still describe how they were measured, and this is the note saying they no
   longer decide anything. */
body.page-home { display: block; }
.page-home::after { display: none; }
.page-home .section.section--tight {
  flex: none; display: block; align-items: initial;
}
.page-home .category-grid {
  max-width: none; --pack-w: 100%;
  row-gap: 10px; column-gap: 10px;
}
@media (min-width: 681px) {
  .page-home .category-grid { max-width: 1100px; }
}
