/* =============================================================================
   Sooq — the storefront.

   Written from scratch on the designed palette in tokens.css. Every class name
   here is load-bearing: shop.js, catalog.js, product-page.js, cart-page.js,
   checkout.js and account.js emit them. Nothing is renamed.

   ---------------------------------------------------------------------------
   THE DESIGN, in five decisions

   1. THE PAGE OPENS AND CLOSES IN INDIGO. The masthead, the front page's
      window and the footer are one saturated field; everything between is cool
      paper. That band of solid colour top and bottom is what gives the page a
      shape you can see from across the room.

   2. THE SHELF IS A DARK CARD HOLDING A LIGHT PLATE. Every cell is --surface
      with a --plate panel inside it carrying the photograph, because a product
      shot on a white sweep would glare on a black card. It has been four pale
      accents rotating under the captions, then one tinted card, then one white
      one; each is written up in CLAUDE.md, and the plate is where the whole
      argument lands.

   3. ONE FAMILY, AND IT IS A GROTESK. Archivo sets the wordmark, every
      heading, every price, every control and every line of copy. The register
      comes from weight, width and tracking rather than from a second typeface.
      The serif that used to carry the wordmark and the prices is gone: a serif
      price is the loudest period signal a shop can put on a screen.

   4. NOTHING FLOATS. Separation is a rule, a change of ground, or space. The
      only shadow in the file is under the mobile sheet, which genuinely does
      float over the page it covers.

   5. THE SHOP'S OWN COLOUR HAS ONE JOB: navigation state. The section you are
      in, and the section you are pointing at. It is never asked to carry the
      design — the palette does that.
   ============================================================================= */

*, *::before, *::after { box-sizing: border-box; }

/* [hidden] MUST WIN. It is an attribute, so it carries almost no specificity,
   and any rule that sets `display` on the same element beats it — which is how
   `.or` kept drawing its "or" divider on a sign-in page where Google is not
   configured and the element was hidden. The pages set .hidden through the
   property (`el.hidden = true`) everywhere, so one rule fixes every one of
   them rather than each block remembering to guard itself. */
[hidden] { display: none !important; }

/* `<address>` IS ITALIC BY DEFAULT, and four separate rules in this file
   already say otherwise — the footer's, the account card's, the address list's
   and the checkout's address picker. The fifth place, the collection block on
   the checkout, simply missed, and the shop's own address was set in italic
   there while the identical lines in the footer were roman. A default that has
   to be undone in every place it appears is a default to undo once. */
address { font-style: normal; }

/* Focus must not land under the masthead. WCAG 2.2 AA 2.4.11, solved on the
   scroll container rather than on every focusable element. --sticky is the
   masthead's COMPRESSED height, with room to spare. */
html {
  scroll-padding-top: calc(var(--sticky) + var(--sp-4));
  -webkit-text-size-adjust: 100%;
  /* THE SCROLL GUTTER IS ALWAYS RESERVED, for two reasons.

     A short page has no scrollbar and a long one does, so every move between
     them shifted the entire layout sideways by its width. That was always true
     and nobody had written it down.

     And --sbw, which the full-bleed band's margins read, is measured as
     innerWidth minus clientWidth. Taken in Shop.boot that is BEFORE the shelf
     renders, when the page is one screen tall and there is no scrollbar to
     measure: it came out 0 and the band stayed a scrollbar too wide. Reserving
     the gutter makes the measurement true whenever it is taken.

     `stable` rather than `scroll`, so a platform with overlay scrollbars — a
     phone, a Mac with the default setting — reserves nothing and --sbw is
     correctly 0 there. */
  scrollbar-gutter: stable;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 400 var(--fs-body)/var(--lh-body) var(--font-ui);
  -webkit-font-smoothing: antialiased;
}

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

/* HEADINGS ARE ARCHIVO, WIDENED — see tokens.css. The serif is now the price
   face and the wordmark, and nothing else: a serif carrying every heading is one
   leg of the commonest generated-design tell, and it was doing nothing for the
   subject. A souq is painted signage and printed price cards. */
h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--font-ui);
  font-weight: 800;
  line-height: var(--lh-head);
  font-variation-settings: var(--sign);
  letter-spacing: -.032em;
}

a, button, input, select, textarea, label, summary {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

::selection { background: var(--signal); color: var(--on-gold); }

/* THE ANTIQUE GOLD, NOT THE CHAMPAGNE. A focus indicator has to clear 3:1
   against what it sits on (WCAG 2.2, 1.4.11) and the site has two grounds: warm
   paper and the black field. Bright champagne is 8.24:1 on the field and
   1.89:1 on paper — it would have been an invisible ring on every page of the
   shop. --signal-ink clears 4.82:1 on paper and 8.5:1 on the field.
   (This comment named saffron and an indigo field, two palettes ago.) */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--signal-ink);
  outline-offset: 3px;
}

.sr {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* The measure. A catalogue uses the page: a 1280px column centred in a 1900px
   window is a document about a shop rather than the shop. */
.wrap, .top__in, .topnav__in, .shell, .foot__in, .vendor {
  max-width: var(--measure);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* ==========================================================================
   UTILITIES — a closed set. The pages are rendered from JavaScript strings, and
   the alternative is a style attribute per element: that is how twenty hard
   coded colours once ended up in six files, every one naming a token that had
   been renamed and therefore doing nothing at all.
   ========================================================================== */

.t-muted { color: var(--ink-3); }
.t-quiet { color: var(--ink-2); }
.t-error { color: var(--error); }
.t-brand { color: var(--signal-ink); }
.t-small { font-size: var(--fs-small); }
.t-tiny  { font-size: var(--fs-tiny); }
.t-lead  { font-size: var(--fs-h4); }
.flush    { margin: 0; }
.stack-sm { margin: var(--sp-2) 0 0; }
.stack    { margin: var(--sp-3) 0 0; }
.stack-lg { margin: var(--sp-5) 0 0; }
.row { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.row--split { justify-content: space-between; align-items: baseline; }
.plain { text-decoration: none; }
.sep   { color: var(--ink-3); }
.num   { font-variant-numeric: tabular-nums; }

/* A button that is really a link — "Remove" beside a basket line, where a real
   button would outrank the thing it is attached to. */
.linkish {
  padding: 0; border: 0; background: none; cursor: pointer;
  font: 500 var(--fs-small)/1.4 var(--font-ui);
  color: var(--ink-2);
  text-decoration: underline; text-underline-offset: 3px;
  text-decoration-color: var(--line-2);
}
.linkish:hover { color: var(--ink); text-decoration-color: var(--signal-ink); }
.linkish--danger { color: var(--error); text-decoration-color: currentColor; }

/* An aside inside a panel. A rule down its left edge, not a tinted box. */
.inset { padding-left: var(--sp-4); border-left: 2px solid var(--line-2); }

/* ==========================================================================
   THE PRIMARY CONTROL

   Declared HERE, before the shelf, because `.add` and `.add--card` are both a
   single class and whichever is written last wins. With this block at the
   bottom of the file — beside the product detail, where it naturally belongs —
   every quiet text action on the shelf comes back as a filled rectangle.

   Indigo, going to ochre under the pointer. One filled control in the whole
   storefront; the shelf's action is type.
   ========================================================================== */

.add, .btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  min-height: var(--btn-h); padding: 0 var(--sp-6);
  /* CHAMPAGNE FILL, ANTIQUE GOLD EDGE, BLACK TEXT.

     The fill is the bright champagne because this is the loudest control on the
     page and it should be the colour the shop is named after. What it carries is
     --on-gold at 8.24:1; white on champagne is 2.25:1 and is the trap this
     palette sets.

     THE BORDER IS DOING REAL WORK, not decoration. A champagne fill is 1.89:1
     against the paper it sits on, which is under the 3:1 a UI component needs to
     have a findable edge. --signal-ink gives it one at 4.82:1. */
  border: 1px solid var(--signal-ink); border-radius: var(--r-ctl);
  background: var(--signal); color: var(--on-gold);
  font: 600 var(--fs-button)/1 var(--font-ui);
  letter-spacing: -.005em;
  text-decoration: none; white-space: nowrap; cursor: pointer;
  transition: background var(--t-fast) var(--ease),
              border-color var(--t-fast) var(--ease);
}
/* DARKER on hover, not lighter. --signal-lit is the band's ink and is nearly
   white; as a fill on paper it would be a button fading out under the pointer. */
.add:hover, .btn:hover { background: var(--signal-press); border-color: var(--signal-press); color: #FFFFFF; }
.add:active, .btn:active { background: var(--field); border-color: var(--field); color: var(--field-ink); }
.add[disabled], .btn[disabled] {
  background: none; color: var(--ink-3); border-color: var(--line-2); cursor: not-allowed;
}
.btn--ghost { background: none; color: var(--ink); border-color: var(--line-2); }
.btn--ghost:hover { background: var(--field); border-color: var(--field); color: var(--field-ink); }
.btn--sm { min-height: var(--btn-h-sm); padding-inline: var(--sp-4); font-size: var(--fs-small); }
.btn--lg { min-height: var(--btn-h-lg); padding-inline: var(--sp-8); font-size: var(--fs-h4); }
.wide { width: 100%; }

/* ==========================================================================
   THE MASTHEAD — the field, and the shop's name set as a wordmark.

   Not a bar with a logo in it. The name is the first thing on the page at
   27–46px, and it compresses to a line of type once anything scrolls under it,
   so what stays pinned is type rather than a slab.
   ========================================================================== */

/* NAVY, AND IT RUNS INTO THE STAGE. The reference's top 860px is one unbroken
   navy field: the bar and the hero are not two bands, they are one, and that is
   most of why its opening reads as a single object. A near-black bar sitting on
   a navy stage reads as two things stacked. */
.top {
  position: sticky; top: 0; z-index: 40;
  background: var(--band);
  color: var(--field-fg);
}
.top__in {
  display: flex; align-items: flex-end; gap: var(--sp-5);
  padding-block: clamp(var(--sp-5), 2.6vw, var(--sp-8)) var(--sp-4);
  transition: padding-block var(--t-slow) var(--ease);
}
.top.is-stuck .top__in { padding-block: var(--sp-3); align-items: center; }

.brand { display: flex; align-items: baseline; gap: var(--sp-3); text-decoration: none; min-width: 0; }

/* THE MONOGRAM PLATE IS NOT DRAWN. A coloured square with two letters in it is
   what a product with no identity reaches for; a name set properly IS the mark.
   The element is still emitted by shop.js, so it is hidden rather than removed. */
.brand__mark { display: none; }

/* The wordmark, in the same grotesk as everything else. It was a serif, which
   is what made the whole page read as printed matter rather than as a shop. */
.brand__name {
  font-variation-settings: var(--sign);
  font-size: clamp(24px, 2.8vw, 38px);
  font-weight: 800; line-height: 1; letter-spacing: -.035em;
  color: var(--field-ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  transition: font-size var(--t-slow) var(--ease);
}
.top.is-stuck .brand__name { font-size: clamp(18px, 1.7vw, 22px); line-height: 1.2; }

/* Search is a ruled line on the field, not a box floating on it. */
/* A CAPSULE, not a ruled line. An underlined input is a paper form; a search
   field has been a rounded surface for a decade and it is what people look for. */
.search {
  display: flex; align-items: center; margin-left: auto; min-width: 0; flex: 0 1 340px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, .10);
  border: 1px solid var(--field-line);
  padding-inline: var(--sp-4) var(--sp-2);
  transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.search:focus-within { background: rgba(255, 255, 255, .16); border-color: var(--signal-lit); }
.search input {
  flex: 1; min-width: 0;
  height: 40px; padding: 0; border: 0;
  background: none; color: var(--field-ink);
  font: 400 var(--fs-small)/1 var(--font-ui);
}
.search input::placeholder { color: var(--field-fg); opacity: .8; }
.search input:focus { outline: none; }
/* 36px, so it is a target rather than a glyph to aim at — the same size the
   share on a product card gets, and for the same reason. */
.search button {
  flex: none; display: grid; place-items: center;
  width: 36px; height: 36px; padding: 0; border: 0; border-radius: 50%;
  background: none; color: var(--field-fg); cursor: pointer;
  transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.search button svg { width: 18px; height: 18px; }
.search button:hover { color: var(--field-ink); background: rgba(255, 255, 255, .16); }

/* TARGETS, NOT TEXT. These were 13.5px of unpadded type in the corner of the
   masthead — about 70 square pixels to hit on the device most of this shop is
   used on, against the 24x24 minimum WCAG 2.2 asks for and the 44px a thumb
   actually wants. They are capsules now, the same shape as the search beside
   them, and they answer the pointer with a ground rather than only a colour. */
.acct, .cartlink {
  flex: none;
  display: inline-flex; align-items: center; gap: var(--sp-1);
  min-height: 40px; padding-inline: var(--sp-3);
  border-radius: var(--r-pill);
  text-decoration: none; white-space: nowrap;
  font: 500 var(--fs-small)/1 var(--font-ui); color: var(--field-fg);
  transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
/* A GROUND AT REST, not only on hover. On a coloured masthead a control with
   no ground until it is pointed at is a control somebody has to find, and these
   two are the account and the basket. */
.acct, .cartlink { background: rgba(255, 255, 255, .10); }
.acct:hover, .cartlink:hover { background: rgba(255, 255, 255, .22); color: var(--field-ink); }

/* SIGN OUT IS THE ONE CONTROL UP HERE THAT DOES NOT WANT TO BE FOUND FAST.

   It takes the same capsule GEOMETRY as the two beside it — 40px, pill, real
   padding — because the reason for that was never emphasis: it was that 13.5px
   of unpadded type is about 70 square pixels to hit on a phone. A target is a
   target whatever it does.

   What it gives up is the ground at rest. The rule above says a control with no
   ground until it is pointed at is a control somebody has to hunt for, and that
   is exactly right FOR THE ACCOUNT AND THE BASKET. Sign out is the opposite
   errand: nobody arrives at a shop looking for it, and a press of it by mistake
   costs a sign-in. Quiet at rest, unmistakable under the pointer — the same
   trade `.linkish--danger` makes on a row's Delete. */
/* ONE TILE FOR THE WHOLE SUBJECT. The name, the way into the account and the
   way out were three loose controls in a row — three targets for one person,
   and about 260px of a masthead that also carries a wordmark, a search and a
   basket. They are one capsule now: a monogram plate, the first name, a caret,
   and the two actions in a panel under it.

   The capsule takes the same shape and the same resting ground as the account
   and basket beside it, because it IS one of them — it is what the account link
   becomes once there is somebody to name. */
.acctbox { position: relative; flex: none; }
.acctbox__btn {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  min-height: 40px; padding-inline: var(--sp-2) var(--sp-3);
  border: 0; border-radius: var(--r-pill);
  background: rgba(255, 255, 255, .10); color: var(--field-fg);
  font: 500 var(--fs-small)/1 var(--font-ui); white-space: nowrap; cursor: pointer;
  transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.acctbox__btn:hover { background: rgba(255, 255, 255, .22); color: var(--field-ink); }
/* The plate is the one solid thing in the capsule, so the tile reads as a
   person rather than as another word in a row of words. White on the masthead's
   blue, which is the pair this palette already measures at 9.50:1. */
.acctbox__mark {
  flex: none; display: grid; place-items: center; width: 26px; height: 26px;
  border-radius: 50%;
  background: var(--field-ink); color: var(--signal);
  font-size: var(--fs-tiny); font-weight: 700; line-height: 1;
}
.acctbox__name { max-width: 12ch; overflow: hidden; text-overflow: ellipsis; }
.acctbox__caret { flex: none; width: 14px; height: 14px; opacity: .8; }
.acctbox__btn[aria-expanded="true"] { background: rgba(255, 255, 255, .22); color: var(--field-ink); }
.acctbox__btn[aria-expanded="true"] .acctbox__caret { transform: rotate(180deg); }

/* THE PANEL IS PAPER, NOT MORE MASTHEAD. It hangs below the band, on the page,
   so it takes the page's surface and the page's ink — a translucent white panel
   on the blue would be a third ground nothing else here uses. Right-aligned to
   the capsule because it is at the right-hand end of the row and the other
   direction runs off the screen. */
.acctbox__menu {
  position: absolute; top: calc(100% + var(--sp-2)); right: 0; z-index: 60;
  min-width: 190px; padding: var(--sp-2);
  /* `--line-2`, not `--line`. The panel opens OVER the section strip, which is
     white — so a hairline that reads as an edge on the page ground has nothing
     to separate there and the panel melts into the strip behind it. The shadow
     alone is not enough on a surface the same colour. */
  border: 1px solid var(--line-2); border-radius: var(--r-md);
  background: var(--surface); box-shadow: var(--el-2);
}
.acctbox__item {
  display: flex; align-items: center; width: 100%;
  min-height: 40px; padding-inline: var(--sp-3);
  border: 0; border-radius: var(--r-sm);
  background: none; color: var(--ink); text-align: left; text-decoration: none;
  font: 500 var(--fs-small)/1 var(--font-ui); white-space: nowrap; cursor: pointer;
  transition: background var(--t-fast) var(--ease);
}
.acctbox__item:hover { background: var(--paper-2); }
.acctbox__item[disabled] { opacity: .6; cursor: default; }
/* WHITE WITH THE BLUE ON IT. This was --signal on --field back when the masthead
   was near-black; the masthead IS --signal now, so the chip was blue on blue —
   present, padded, and invisible. White reads 9.50:1 on the band and on the
   capsule alike. */
.cartlink b {
  display: inline-block; min-width: 20px; margin-left: var(--sp-1);
  padding: 2px 6px; border-radius: var(--r-pill);
  background: var(--field-ink); color: var(--signal);
  font-size: var(--fs-tiny); font-weight: 700; line-height: 1.35; text-align: center;
  font-variant-numeric: tabular-nums;
}
/* The one announced motion on the storefront, and it answers an action: the
   basket count has just changed, and this is what changed. */
@keyframes tick { 0% { transform: scale(1); } 40% { transform: scale(1.4); } 100% { transform: scale(1); } }
.cartlink--bumped b { display: inline-block; animation: tick 320ms var(--ease-spring); }

/* ==========================================================================
   THE SECTION STRIP — on paper, directly under the field.

   This is where THE SHOP'S OWN COLOUR lives: the section you are in is marked
   with it, and so is the one you are pointing at. On paper rather than on the
   field because a shopkeeper's colour can be anything, and half of the possible
   choices are illegible on deep spruce.
   ========================================================================== */

/* NO GROUND OF ITS OWN. The blue of .top shows through and the white is the
   row inside, which is what makes the pill a pill rather than a strip. The
   horizontal scroll moves to .topnav__links with it — a rounded bar that scrolls
   at its own edges clips its corners off. */
.topnav {
  background: none;
  border-bottom: 0;
  padding-bottom: clamp(var(--sp-6), 3vw, var(--sp-10));
}
.topnav::-webkit-scrollbar { display: none; }
/* THE PILL. Three columns, the outer two equal, so the links are centred on
   the BAR and do not move when a shop adds a section.

   --r-pill is the radius this scale already has for a capsule — the search and
   the basket chip use it — so the bar belongs to the same family rather than
   introducing a fourth corner. */
.topnav__in {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-2) var(--sp-5);
  background: var(--surface);
  border-radius: var(--r-pill);
  box-shadow: var(--lift-rest);
  /* INSET BY THE GUTTER. The shared measure rule gives this max-width and auto
     margins, and --measure is 1520px — wider than most screens — so on its own
     the bar ran edge to edge and stopped being a floating object. Its own
     padding had replaced the gutter that used to hold it off the edge, so the
     inset is taken back here: the measure less two gutters, or the viewport
     less two gutters, whichever is smaller. */
  max-width: min(calc(var(--measure) - var(--gutter) * 2),
                 calc(100% - var(--gutter) * 2));
}
.topnav__links {
  display: flex; gap: var(--sp-6); justify-content: center; align-items: center;
  min-width: 0; overflow-x: auto; scrollbar-width: none;
  /* The row scrolls, not the bar — see the note on .topnav. */
  padding-inline: var(--sp-2);
}
.topnav__links::-webkit-scrollbar { display: none; }

/* THE LINKS GO BELOW 860px, because the burger beside them opens a drawer that
   holds the same tree plus the filters. Six sections squeezed into 148px of a
   354px pill is a scrolling strip nobody can read — measured: scrollWidth 294
   against clientWidth 148 — and the reference collapses to the burger alone
   there for the same reason. */
@media (max-width: 860px) { .topnav__links { display: none; } }
/* AFTER the base rule, not before it. Placed above, the media query lost to the
   unconditional display:flex on source order and the links stayed squeezed —
   the same ordering trap as the duplicate .add--card and the stale .stage__in,
   hit a third time in one session. */
.topnav__tools { display: flex; justify-content: flex-end; align-items: center; gap: var(--sp-4); }

/* THE BURGER opens the same drawer the toolbar's Refine button does. 44px,
   because it is a control on the device this shop is mostly used on. */
.topnav__burger {
  display: inline-grid; place-items: center;
  width: 44px; height: 44px;
  border: 0; border-radius: var(--r-ctl); background: none;
  color: var(--ink-2); cursor: pointer;
  transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.topnav__burger svg { width: 22px; height: 22px; }
.topnav__burger:hover { color: var(--ink); background: var(--paper-2); }
.topnav a {
  position: relative; padding-bottom: 3px;
  white-space: nowrap; text-decoration: none;
  font: 500 var(--fs-small)/1.4 var(--font-ui); color: var(--ink-2);
  transition: color var(--t-fast) var(--ease);
}
.topnav a:hover { color: var(--ink); }
/* Quieter than a section, because it is not one and it is not for customers.
   It keeps a real target size rather than becoming a scrap of small print.
   The margin-left:auto it used to carry is gone: the grid puts it in the right
   column, and the auto margin was what made the links sit off-centre. */
.topnav__console { color: var(--ink-3); }
.topnav__console:hover { color: var(--signal-ink); }
.topnav a[aria-current="page"] { color: var(--ink); font-weight: 600; }
.topnav a[aria-current="page"]::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: -1px;
  height: 2px; background: var(--brand);
}

/* ==========================================================================
   THE STAGE — the front page's opening: one photograph at the scale of the
   screen, with a solid band of the field colour along the bottom of it.

   WHAT CHANGED, AND WHY. The photograph is still full-bleed. What has gone is
   the GRADIENT SCRIM the type used to sit on.

   The scrim was a contrast floor: type over a photograph has to clear 4.5:1
   over the WORST photograph a shopkeeper might upload, which is a white one —
   a product shot on a white sweep, and half of this catalogue is exactly that.
   It was tuned to alpha .82 across the whole bottom 42%, and it worked. The
   cost is that it was equally opaque over every GOOD photograph: the bottom
   two-fifths of every stage was near-black, and the picture stopped being a
   picture precisely where the scrim was heaviest. On the demo shops — a plug
   on a blue wall, a cable on marble — the result read as mud.

   A SOLID BAND MAKES CONTRAST A PROPERTY OF THE MARKUP. --field-ink on --field
   is 17.90:1 and --signal-lit is 15.53:1, on every photograph ever uploaded and
   on one that has not been. There is no gradient to re-measure when the type
   moves, no alpha to get wrong, and the photograph above the band is untouched.

   THE BAND IS NOT SOLID, THOUGH, AND THAT IS THE TRAP. It is glass, so those
   two numbers are the ones it would have if it were — and the ground it is
   actually painted on is far lighter. Both inks are solved against the
   COMPOSITED value the auditor measures. See the note beside --field-fg.

   IT IS ONE BAND, NOT TWO PANELS. Two rounded panels floating on an image is
   the card kit with a photograph behind it. This runs the full width of the
   page and is divided down the middle by a rule: the shop's statement on the
   left, the product it is leading with on the right.

   THERE IS NO SECOND ROW OF PRODUCTS UNDER IT. Three more used to stand below
   the stage, taken from the shelf the page was already fetching — which on a
   fifteen-product shop put the same three products on the screen twice, four
   hundred pixels apart. See Shop.hero.
   ========================================================================== */

/* The ground behind the stage, seen only where a shop has uploaded no
   photograph — so it is the navy the band would have been. */
.hero { background: var(--band); color: var(--field-fg); }

/* ==========================================================================
   THE STAGE — two columns on the navy: the shop's statement, and a picture.

   IT WAS A FULL-BLEED PHOTOGRAPH WITH TYPE ON IT, and before that a scrim over
   one, and before that a flat band. The reference this follows does neither: its
   opening is a heading, a line and a button on the left with one large image on
   the right, both on a solid navy field.

   WHAT THAT BUYS, beyond looking like the reference: contrast becomes a property
   of the markup again. --field-ink is 14.19:1 on #152859 whatever photograph is
   beside it, and every number the glass band needed solving for — the tint, the
   brightness, the lit accent measured against a worst-case white photograph —
   simply stops existing. Two of those had been got wrong and caught by the
   auditor.
   ========================================================================== */
.stage {
  background: var(--band);
  color: var(--field-ink);
}

/* THE TWO COLUMNS. Words left, picture right, centred in the measure.

   Deleted twice by accident and restored here: without it the band had no
   max-width and no gutter, so the headline started at x=0 on a phone and the
   row ran the full width of any screen. The two media queries below override
   this one; both were left pointing at nothing. */
.stage__in {
  max-width: var(--measure); margin-inline: auto;
  padding: clamp(var(--sp-12), 6vw, var(--sp-24)) var(--gutter);
  display: grid; grid-template-columns: minmax(0, 6fr) minmax(0, 5fr);
  gap: clamp(var(--sp-8), 4vw, var(--sp-16));
  align-items: center;
}

/* NO PHOTOGRAPH, NO SECOND COLUMN. A shop that has uploaded nothing gets the
   statement across the full width rather than a hole where a picture goes. */
.stage--flat .stage__in { grid-template-columns: minmax(0, 1fr); }

/* THE HERO ARRIVES, ONCE.

   Taken from the reference's own markup, not invented: its heading, text and
   button carry `fadeInLeft` at delays of 100, 400 and 600, and its image
   carries `fadeInRight`. Same stagger, same directions.

   It is the ONLY entrance animation in this stylesheet. "Fade-and-slide-up
   entrances on each section" is on the list of tells and it is on that list
   because it is everywhere; one orchestrated moment on the opening band is a
   different thing. Nothing below the fold animates in — the shelf, the sections
   and the bands are simply there when you reach them. */
@keyframes rise-left { from { opacity: 0; transform: translateX(-24px); } }
@keyframes rise-right { from { opacity: 0; transform: translateX(28px); } }

/* THE STAGGER IS OPENED OUT with the duration. The four lines land over about
   1.1 seconds now rather than 0.8 — slow enough to be read as one sequence
   arriving in order rather than four things appearing at once. */
.stage__say > * { animation: rise-left var(--t-enter) var(--ease-out) backwards; }
.stage__eyebrow { animation-delay: 80ms; }
.stage__say h1 { animation-delay: 180ms; }
.stage__lede { animation-delay: 420ms; }
.stage__go { animation-delay: 640ms; }
.stage__shot { animation: rise-right var(--t-enter) var(--ease-out) 180ms backwards; }

.stage__say { min-width: 0; }
.stage__lede {
  margin: var(--sp-6) 0 0;
  max-width: 46ch;
  font-size: var(--fs-h4); line-height: 1.55;
  color: var(--field-fg);
}
/* WHITE ON THE BAND, not the signal. .btn is a --signal fill and the band IS
   --signal now, so the inherited button was a blue rectangle on a blue ground —
   invisible, and exactly the failure that follows from a control tuned for a
   near-black field meeting a saturated one. The reference SHOP HERE is white
   with the blue on it, which is 9.50:1 both ways. */
.stage__go {
  margin-top: var(--sp-8);
  background: var(--paper); border-color: var(--paper); color: var(--signal);
}
.stage__go:hover { background: var(--field-ink); border-color: var(--field-ink); color: var(--signal-press); }
.stage__go:active { background: var(--paper-2); border-color: var(--paper-2); color: var(--signal-press); }

/* THE PICTURE. A rounded frame rather than a bleed: in a column it is an object
   on the field, and an object needs an edge. */
.stage__shot {
  min-width: 0;
  border-radius: var(--r-tile);
  overflow: hidden;
  box-shadow: var(--lift);
}
.stage__img {
  display: block; width: 100%; height: 100%;
  aspect-ratio: 5 / 4; object-fit: cover;
}

@media (max-width: 860px) {
  /* THE PICTURE GOES FIRST on a phone, because the reference stacks it that way
     and because a screen of type before any image is a page nobody scrolls. */
  .stage__in { grid-template-columns: minmax(0, 1fr); }
  .stage__shot { order: -1; }
  .stage__img { aspect-ratio: 16 / 10; }
}

/* THE OLD .stage__in WAS HERE and it was winning at every width.

   The two-column stage has its own .stage__in above, and a media query stacking
   it below 860px. This one — 7fr/5fr, align-items: end — survived the rewrite
   and sat BELOW both, so on source order it beat them: the desktop split was the
   old one and the mobile stacking never applied at all. The hero was two columns
   on a phone, silently, and the only visible symptom was the left column sitting
   low because align-items: end was winning too.

   Same defect as the duplicate .add--card earlier: two rules for one selector,
   the later one winning, and nothing in the output saying which. An
   unconditional rule placed after a media query beats that query at every
   width. */
.hero h1 {
  font-family: var(--font-display);
  font-size: calc(var(--fs-cover) * 1.2);
  line-height: 1;
  font-weight: 400;
  text-transform: uppercase;
  /* The reference tracks its hero wider than its headings — 5px against 2px. */
  letter-spacing: .05em;
  color: var(--field-ink);
  text-wrap: balance;
  max-width: 22ch;
}
.stage__eyebrow {
  display: block; margin-bottom: var(--sp-3);
  font: 500 var(--fs-small)/1.4 var(--font-ui);
  /* WHITE, not the signal. The band is a neutral grey glass now, and
     --signal-lit — a mid blue tuned against a near-black field — measures
     2.44:1 on it. On this ground the hierarchy is carried by WEIGHT and SIZE
     rather than by hue, which is what the band has to do anyway: the photograph
     behind it is different on every shop. */
  color: var(--field-ink);
}
/* THE DETAIL COLUMN'S META — the SKU and the section, as label/value rows
   under a hairline. The reference sets exactly these two and they are the two
   facts somebody telephones a shop about. */
.meta {
  display: grid; grid-template-columns: auto 1fr; gap: var(--sp-2) var(--sp-6);
  margin: var(--sp-6) 0 0; padding-top: var(--sp-5);
  border-top: 1px solid var(--line);
  font-size: var(--fs-small);
}
.meta dt { color: var(--ink-3); }
.meta dd { margin: 0; color: var(--ink); }
.meta dd a { color: var(--signal-ink); text-decoration: none; }
.meta dd a:hover { text-decoration: underline; text-underline-offset: 3px; }

/* ==========================================================================
   THE PAGE HEADER — every page that is not the front of the shop.

   Captured from the reference's product page and its cart: a navy band under
   the masthead, the page's name in the display face, centred, a breadcrumb
   under it. It is what makes its inner pages belong to its front page, and ours
   had a line of grey 14px type in a toolbar instead.

   IT CARRIES THE h1. One h1 a page, so a page using this does not set another
   heading in the article below — see Shop.pagehead.
   ========================================================================== */
.pagehead { background: var(--band); color: var(--field-ink); }
.pagehead__in {
  max-width: var(--measure); margin-inline: auto;
  padding: clamp(var(--sp-10), 5vw, var(--sp-16)) var(--gutter);
  text-align: center;
}
.pagehead h1 {
  margin: 0;
  font-family: var(--font-display);
  font-size: calc(var(--fs-h1) * 1.1); line-height: 1;
  font-weight: 400; text-transform: uppercase;
  letter-spacing: var(--track-display);
  text-wrap: balance;
}
.pagehead__crumb {
  margin-top: var(--sp-5);
  font-size: var(--fs-small); color: var(--field-fg);
}
/* THE LINKS LOOK LIKE LINKS. Everything in this row was one weight and one
   colour, so a four-part trail read as a single grey string rather than as
   three places you can go and one you are. The rule is there at rest and
   brightens under the pointer; the current page is the only part that is not a
   link and is the only part set solid. */
/* SC 2.5.8: 16px tall with a 7px gap between "Everything" and the next crumb.
   Inline-block with vertical padding takes the hit area to 26px without moving
   the text, which matters here — the band's spacing is set against the h1. */
.pagehead__crumb a, .pagehead__crumb b { display: inline-block; padding-block: 5px; }
.pagehead__crumb a {
  color: var(--field-fg); text-decoration: underline;
  text-underline-offset: 4px; text-decoration-thickness: 1px;
  text-decoration-color: rgba(255, 255, 255, .38);
  transition: color var(--t-fast) var(--ease), text-decoration-color var(--t-fast) var(--ease);
}
.pagehead__crumb a:hover { color: var(--field-ink); text-decoration-color: var(--field-ink); }
.pagehead__crumb b { color: var(--field-ink); font-weight: 700; }
/* The slashes are structure, not content, and should not compete with either. */
/* .70, NOT .45. The slashes are structure and should not compete with the
   names either side — but at .45 they measured 3.02:1 on the band, which is a
   fail, and "decorative" is not a category the floor recognises for text that
   is actually painted. .70 composites to rgb(183,198,235) and reads 5.55:1;
   the separation still comes from weight rather than from colour. */
.pagehead__crumb span { color: rgba(255, 255, 255, .70); padding-inline: 2px; }

/* ==========================================================================
   THE ASSURANCE STRIP — what the shop offers, directly under the stage.

   The reference runs four of these and its hero above them carries a heading, a
   line and a button and nothing else. Ours had the same facts inside the stage
   band, which is why a strip was refused twice as a duplicate — it is not one
   now, because the facts MOVED rather than being copied.

   On the page's own white, not on the navy: the reference's is a white strip
   between a navy hero and the catalogue, and it is what stops the top of the
   page being one unbroken block of dark.
   ========================================================================== */
.assure { background: var(--paper); border-bottom: 1px solid var(--line); }
.assure__in {
  max-width: var(--measure); margin-inline: auto;
  padding: var(--sp-8) var(--gutter);
  display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: var(--sp-6) var(--sp-8);
}
.assure__i-wrap { display: flex; align-items: center; gap: var(--sp-4); min-width: 0; }
.assure__i { width: 34px; height: 34px; flex: none; color: var(--signal); }
.assure__i-wrap b {
  display: block; min-width: 0;
  font: 700 var(--fs-small)/1.35 var(--font-ui);
  text-transform: uppercase; letter-spacing: .04em;
  color: var(--ink);
}
.assure__i-wrap span {
  display: block; font-size: var(--fs-tiny); color: var(--ink-3); line-height: 1.4;
}

/* THE FACT GRID WAS HERE, and it is gone with the glass band it sat on. The
   opening is two columns on the navy now — see .stage — so there is no
   translucent band to tint, no worst-case white photograph to measure a scrim
   against, and no facts laid over a picture. Delivery, opening times, the
   telephone number and where to collect are the assurance strip under the
   stage, which has its own rules and reads them from config itself.

   Two smoke assertions named .stage__band and failed when it went. The
   class-existence check exists because a deleted block is silent and a page
   quietly loses its styling; this is that check catching the same defect from
   the other side — rules nothing renders. */

/* ==========================================================================
   THE SHELL

   The category rail is out of the page flow. The strip above and the index
   below both carry the tree, and a third copy down the left margin was the
   admin sidebar this design is trying not to be. It survives as the mobile
   sheet, which is the one place a long tree genuinely helps.
   ========================================================================== */

.shell { display: grid; grid-template-columns: minmax(0, 1fr); }
.main { min-width: 0; padding-block: var(--sp-12) var(--sp-20); }
/* A page that is one document rather than a catalogue - the basket. Narrower
   than the shelf's measure, and it brings its own gutter because it is not
   inside .shell. It replaces a style="max-width:860px" attribute in the markup:
   a layout decision written where the stylesheet cannot see it. */
.main--sheet {
  width: 100%; max-width: 1080px; margin-inline: auto;
  padding-inline: var(--gutter);
}

/* A DRAWER AT EVERY WIDTH, off the right edge on a desktop and up from the
   bottom on a phone.

   It was a bottom sheet shown only below 1023px, and the button that opens it
   was `display: none` above that — so on a desktop the price and reduced-only
   filters inside it could be set from the URL and removed from a chip, and
   there was nothing on the screen that could apply them. A permanent sidebar
   was the other way out of that, and it is the one this design has turned down
   twice: a third copy of the category tree down the left margin is the admin
   sidebar the storefront is trying not to be. */
.rail {
  position: fixed; inset: 0 0 0 auto; z-index: 60; display: none;
  width: min(420px, 92vw);
  overflow-y: auto; overscroll-behavior: contain;
  padding: var(--sp-6) var(--sp-8) var(--sp-12);
  background: var(--paper); border-left: 3px solid var(--field);
  box-shadow: var(--sheet-shadow);
}
/* IN FROM THE EDGE, not a nudge. It was translateX(24px) and a fade, which
   makes the panel appear roughly where it already is; a drawer should be seen
   to come from off-screen, because that is what tells somebody where it goes
   when they close it. 100% of its own width, so the distance is right at every
   size it takes. */
.rail[data-open="true"] {
  display: block;
  animation: drawer-in var(--t-enter) var(--ease-spring);
}
@keyframes drawer-in { from { transform: translateX(100%); } }
.veil { animation: veil-in var(--t-enter) var(--ease); }
@keyframes veil-in { from { opacity: 0; } }

/* The veil is what makes the drawer dismissable by pressing the page it covers,
   which is what somebody will try first. Created once by Shop.toggleRail. */
.veil {
  position: fixed; inset: 0; z-index: 50;
  /* The field's own black rather than a neutral one, so the veil belongs to the
     page it covers. */
  background: rgba(21, 19, 15, .52);
}

.rail__top {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4);
  padding-bottom: var(--sp-4); margin-bottom: var(--sp-5);
  border-bottom: 1px solid var(--line-2);
}
.rail__top h2 {
  margin: 0;
  font-variation-settings: var(--sign);
  font-size: var(--fs-h3); font-weight: 800; letter-spacing: -.028em; color: var(--ink);
}
.rail__x {
  flex: none;
  min-height: 36px; padding-inline: var(--sp-4);
  border: 1px solid var(--line-2); border-radius: var(--r-pill);
  background: var(--surface); color: var(--ink-2);
  font: 500 var(--fs-small)/1 var(--font-ui); cursor: pointer;
  transition: border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.rail__x:hover { border-color: var(--ink-3); color: var(--ink); }
.rail__h {
  font-family: var(--font-ui); font-size: var(--fs-small); font-weight: 600;
  color: var(--ink-3); margin: var(--sp-6) 0 var(--sp-2);
}
.rail ul { list-style: none; margin: 0; padding: 0; }
.rail ul ul { padding-left: var(--sp-4); }
.rail a {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  min-height: 46px; padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--line);
  text-decoration: none; font-size: var(--fs-body); color: var(--ink-2);
}
.rail a:hover { color: var(--ink); }
.rail a span { flex: none; font-size: var(--fs-tiny); color: var(--ink-3); font-variant-numeric: tabular-nums; }
.rail a[aria-current="true"] { color: var(--brand); font-weight: 600; }

/* ==========================================================================
   THE TOOLBAR — a line of type over the catalogue, ruled off.
   ========================================================================== */

.bar {
  display: flex; align-items: baseline; gap: var(--sp-4); flex-wrap: wrap;
  padding-bottom: var(--sp-3); margin-bottom: var(--sp-8);
  border-bottom: 1px solid var(--line-2);
}
/* Inside the products section it sits under the section heading, which already
   carries the heavy rule. Two of them a line apart is a fence, not a hierarchy. */
#products .bar { border-bottom: 0; padding-bottom: 0; margin-bottom: var(--sp-5); }
.crumb { font-size: var(--fs-small); color: var(--ink-3); }
.crumb a { color: var(--ink-3); text-decoration: none; }
.crumb a:hover { color: var(--ink); text-decoration: underline; }

/* ONLY THE CRUMB THAT IS THE HEADING IS SET AS ONE. Shop.crumb emits an <h1>
   for the current section on a category or search page and a <b> everywhere
   else — and on the product page that <b> is the product's name, which is
   already the <h1> one line below. Styling both large printed the product's
   name twice, at heading size, one above the other. */
.crumb h1 {
  display: inline;
  font-family: var(--font-ui); font-variation-settings: var(--sign);
  font-size: clamp(24px, 2.6vw, 34px); font-weight: 700; letter-spacing: -.022em;
  color: var(--ink);
}
.crumb b { font-weight: 600; color: var(--ink); }

.bar__count { font-size: var(--fs-small); color: var(--ink-3); font-variant-numeric: tabular-nums; }
.bar__spacer { flex: 1 1 auto; }
.bar select, #sections {
  height: var(--btn-h-sm); padding: 0 var(--sp-4);
  border: 1px solid var(--line-2); border-radius: var(--r-pill);
  background: var(--surface); color: var(--ink);
  font: 500 var(--fs-small)/1 var(--font-ui); cursor: pointer;
  transition: border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.bar select:hover, #sections:hover { border-color: var(--ink-3); }
/* SHOWN AT EVERY WIDTH. It is the only way to reach the price filter, and it
   used to be display:none on exactly the screens with the most room for one. */
#sections {
  display: inline-flex; align-items: center;
  order: -1; font-weight: 600; color: var(--ink);
}
#sections[aria-expanded="true"] { border-color: var(--ink); }

/* ==========================================================================
   SECTION HEADS
   ========================================================================== */

.sect { margin-bottom: var(--sp-16); }
/* THE SECTION HEADINGS JUMP SCALE, CENTRE, AND LOSE THE RULE.

   They were --fs-h2 with a hairline under them — the same weight as everything
   else on a page where everything else was already the same weight, and a rule
   under a heading is how a ledger is set. A heading that does not announce
   anything is a label.

   Centred because the two things above them are not: the stage's statement is
   left and the shelf is a grid. A centred display line between them is a change
   of rhythm, which is the whole job. Space replaces the rule. */
.sect__head {
  display: flex; align-items: baseline; justify-content: center; gap: var(--sp-4);
  flex-wrap: wrap; text-align: center;
  margin-bottom: var(--sp-10);
}
/* THE DISPLAY FACE. Bebas Neue at weight 400, tracked out, set solid — which
   is the reference's, read out of its stylesheet rather than guessed at.

   Uppercase here is a DISPLAY treatment, not the tracked-out eyebrow label on
   the calibration list of tells: that is a small caps string ABOVE a heading,
   and this is the heading. The face has no lower case to choose anyway. */
.sect__head h2 {
  font-family: var(--font-display);
  font-size: calc(var(--fs-h1) * 1.15); line-height: 1;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: var(--track-display);
}
/* A "see all" link, when there is one, goes under rather than beside: pushed to
   the right of a centred heading it drags the heading off centre. */
.sect__head .sect__more { margin-left: 0; flex: 1 0 100%; }
.sect__head p { margin: 0; font-size: var(--fs-small); color: var(--ink-3); }
.sect__more {
  margin-left: auto; font: 500 var(--fs-small)/1.4 var(--font-ui); color: var(--ink);
  text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--signal-ink);
}
.sect__more:hover { color: var(--signal-ink); }

/* ==========================================================================
   THE SECTION INDEX — the shop's departments, as PHOTOGRAPHS.

   WHAT THIS REPLACES, twice over. It was a full-width ruled list of names at
   40px, which spent 420 pixels of height on six words and six numbers. Then it
   was a wrapping row of names with a count chip beside each — accurate, compact,
   and a table of contents.

   Two things were wrong with the row. It was the THIRD copy of the same tree on
   one screen, after the strip under the masthead and the refine drawer, and it
   was the least useful of the three because it was the only one taking a whole
   band of the page to say it. And nobody navigates a shop by reading department
   names: they look.

   So each section is a photograph of something filed under it, with the name set
   over the bottom. The picture comes from the API — the first product at or
   below that node, the shop's featured one first, then the newest — so a section
   shows the same picture on every load.

   THE LABEL SITS ON A SCRIM, and the scrim is a contrast floor rather than a
   taste: it has to hold over the worst photograph a shopkeeper can upload, which
   is a white one. Measured against pure white, rgba(field, .86) gives a ground
   of rgb(49, 50, 54) — white 12.78:1 and --field-fg 5.23:1. It holds at .86
   across the whole band the type occupies and only then falls away.
   ========================================================================== */

.blocks, .cats {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: var(--sp-4);
}

.block, .cat {
  position: relative; isolation: isolate; overflow: hidden;
  display: block; aspect-ratio: 4 / 3;
  border-radius: var(--r-tile);
  background: var(--paper-2);
  text-decoration: none; color: var(--field-ink);
  transition: transform var(--t-base) var(--ease), box-shadow var(--t-base) var(--ease);
}
.block:hover, .cat:hover { transform: translateY(-3px); box-shadow: var(--lift); }

.block__img { position: absolute; inset: 0; display: block; }
.block__img img {
  width: 100%; height: 100%; object-fit: cover;
  transition: transform var(--t-zoom) var(--ease);
}
.block:hover .block__img img { transform: scale(1.05); }

/* A SOLID background-color UNDER THE TEXT, and the fade as a separate strip
   above it.

   The first version put the whole scrim in a linear-gradient on this element and
   nothing else. It looked identical and it is the defect this project has
   already been caught by once: A GRADIENT-ONLY BACKGROUND COMPUTES AS
   TRANSPARENT. Anything reading the box's colour — a contrast checker, forced-
   colours mode, a browser that fails to paint a layer — sees the page's light
   ground behind near-white text and measures about 1.1:1. scratchpad/audit.mjs
   found it here exactly as accesslint found it in the display case.

   So the label's own box states rgba(field, .86), which is what the type is
   actually sitting on, and ::before carries the fade from .86 to nothing over
   the photograph above it. Same picture, and the number a reader computes is
   now the number a person sees. */
.block__label {
  position: absolute; inset: auto 0 0 0; z-index: 1;
  display: flex; flex-direction: column; gap: 1px;
  padding: var(--sp-4);
  background-color: rgba(var(--field-rgb), .86);
}
.block__label::before {
  content: ''; position: absolute; left: 0; right: 0; bottom: 100%;
  height: var(--sp-12); pointer-events: none;
  background: linear-gradient(to top,
    rgba(var(--field-rgb), .86) 0%,
    rgba(var(--field-rgb), .34) 55%,
    rgba(var(--field-rgb), 0) 100%);
}
.block__name, .cat__name {
  font-variation-settings: var(--sign);
  font-size: 18px; font-weight: 700; letter-spacing: -.022em; line-height: 1.2;
  color: var(--field-ink);
}
/* Not a chip any more. A count in a lozenge beside a name reads as a badge, and
   a badge on a photograph is the one thing this block was redesigned away from;
   here it is simply the line under the name. */
.block__count, .cat__count {
  font-family: var(--font-ui); font-size: var(--fs-tiny); font-weight: 500;
  color: var(--field-fg); font-variant-numeric: tabular-nums;
}

/* The initials plate, for a section with no photograph anywhere under it. It is
   drawn on --paper-2 rather than on a tint, so an empty section is visibly
   empty — which is the state a shop mid-setup is genuinely in. */
.block .ghost b { color: rgba(11, 13, 18, .22); }

/* ==========================================================================
   THE SHELF

   A photograph, then a caption block in colour. No card, no border, no shadow:
   the block of colour IS the card's job, done with one value instead of four.
   The four wells rotate by position so a long catalogue has rhythm.
   ========================================================================== */

/* FOUR ACROSS AND NEVER FEWER, which is what makes the reduced row read as a
   row rather than as the start of the catalogue. It is the same .grid otherwise
   — same cell, same badge, same action. */
.grid--deals { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 900px) { .grid--deals { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.grid {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-6);
}
/* ONE SURFACE, NOT FOUR COLOURS.

   The shelf used to rotate --well-1 to --well-4 under the captions, on a
   five-step cycle so the colours could not line up into stripes down a column.
   It was there to give a long catalogue rhythm without cards or borders — and
   with the palette repainted it was the only multi-coloured thing left on the
   page, which made the shelf read as a different design from everything above
   it.

   A card on --surface with a hairline does the same job with one value: the
   rhythm comes from the photographs, which is where it should come from in a
   shop. The wells are still declared in tokens.css — they are part of the
   palette and something will want them — but nothing on the shelf reads them. */
/* ONE GROUND, AND IT IS WHITE.

   It was --card, a pale periwinkle tint, with the photograph's well white on top
   of it — because a product shot on a white sweep, CONTAINED rather than
   cropped, letterboxes against any other colour and shows its own rectangle. So
   every cell was two grounds with a seam across the middle, and the join is what
   read as cheap. The tint was not chosen for itself either: it arrived to stop a
   white card disappearing into near-white paper, which is a job for elevation.

   The card is now white throughout, the photograph sits on the same white as the
   caption, and there is no join anywhere in it. */
/* NO CARD. The reference puts its products on the page ground with nothing
   round them, and it is right: fifteen bordered boxes is a table of contents,
   fifteen photographs with type under them is a shelf.

   What was here — a white surface, a resting shadow, padding on all four sides —
   is gone. --surface equals --paper on this palette anyway, so the card had
   stopped being visible as anything but its own shadow. */
.cell {
  display: flex; flex-direction: column; min-width: 0;
  background: none;
  padding: 0;
  text-align: center;
}

.cell__link { display: flex; flex-direction: column; flex: 1; min-width: 0; text-decoration: none; color: inherit; }

.cell__img {
  position: relative;
  /* NOT overflow: hidden any more — the discount badge overlaps this box's top
     right corner deliberately and hidden would cut it in half. The hover zoom
     that needed the clipping is on the IMG's own wrapper now. */
  overflow: visible;
  /* SQUARE, and it used to be 3:4 on the reasoning that portrait reads as goods
     rather than as thumbnails. That holds for a catalogue of tall things. This
     shop sells cables, keyboards, power bricks and batteries, which are wide —
     and a wide photograph CONTAINED in a tall frame is mostly empty frame. A
     square is the shape that wastes least across both. */
  /* 4:3, NOT SQUARE. Contained rather than cropped, the frame's shape decides
     how much of it is empty, and almost everything this shop sells is WIDE — a
     cable, a keyboard, a SO-DIMM, a battery pack. In a square those left a third
     of the frame blank above and below, which is how a product ends up looking
     small in a large box. 4:3 is the shape most of the photography already is.

     It was 3:4 before either — portrait, on the reasoning that portrait reads as
     goods rather than as thumbnails. That is true of a catalogue of tall things
     and this is the opposite of one. */
  aspect-ratio: 4 / 3;
  /* THE PLATE, which on a light theme is the card's own white — so a
     white-sweep product shot has no visible edge at all and the object appears
     to sit directly on the card.

     The token still earns its name. On the dark theme it was a lit panel inset
     in a black card, because the same photograph would have glared there: same
     fact, opposite answer, and naming the ground rather than the colour is what
     let both be expressed without touching this rule.

     The radius is kept for the dark case and costs nothing here. */
  background: var(--plate);
  /* THE ONE RULE LEFT IN THE CELL, and it is what lets photographs on marble,
     grey and black sit on a white page without reading as loose rectangles. The
     reference needs none because its fifteen are one consistent sweep. */
  border: 1px solid var(--line);
  border-radius: var(--r-ctl);
}
/* CONTAIN, NOT COVER — the whole product, never a crop of it.

   Cover fills the frame by discarding whatever does not fit, and on a
   photograph of a cable what it discards is the connector. It is the right
   default for a ground and the wrong one for a subject: the SSD's ribbon ran
   off the right edge of its own product page, and a customer deciding whether a
   part is the right part cannot be shown three quarters of it.

   The padding is inside the image's own box, so `contain` fits within it and
   the object never touches the frame. */
.cell__img img {
  width: 100%; height: 100%; object-fit: contain;
  /* Only enough to keep the object off the card's own edge on the one axis
     where a wide photograph reaches it. The card's padding does the rest. */
  padding: var(--sp-2); box-sizing: border-box;
  transition: transform var(--t-zoom) var(--ease);
}
/* The photograph is the only thing that answers the pointer. Nothing lifts,
   nothing gains a shadow, nothing changes colour. */
.cell__link:hover .cell__img img { transform: scale(1.04); }

.cell__body {
  display: flex; flex-direction: column; flex: 1; min-width: 0; align-items: center;
  padding: var(--sp-5) 0 0;
}
/* IT ANSWERS THE POINTER. At rest the card is flat — a hairline and a change of
   ground, nothing more — and it lifts only when it is pointed at or focused,
   which is the page saying "this is pressable" rather than decorating itself
   with a shadow it has not earned. `overflow: hidden` is what makes the
   photograph take the card's own top corners. Cancelled under (hover: none): on
   a touch screen a hover fires on TAP and then stays until something else is
   touched. */
/* IT IS RAISED AT REST AND IT RISES FURTHER WHEN POINTED AT.

   The card used to be flat until hovered — a hairline and a change of ground,
   nothing more — on the principle that a shadow should be earned. It was earned:
   the ground is gone, and --surface against --paper is four thousandths of
   luminance, so a flat white card on near-white paper is invisible except for
   its border. The border is gone too. The elevation is now the whole of the
   card's edge, which is one device doing the work of two.

   Cancelled under (hover: none): on a touch screen a hover fires on TAP and then
   stays until something else is touched. */
/* NOTHING LIFTS ANY MORE, because there is no card to lift. The elevation was
   load-bearing while the shelf was white cards on a near-white page; on a bare
   page it would be a shadow under a photograph, which is the SaaS-card tell the
   brief rules out. The photograph itself answers the pointer — see .cell__img. */
.cell { border-radius: 0; }
/* ==========================================================================
   THE LEAD — the featured product, breaking the grid.

   A shelf of fifteen identically sized cards is a spreadsheet. One tile at four
   times the area gives the shelf a focal point and the page a second thing worth
   stopping at, and it costs no new markup: it is the same .cell with a modifier,
   so the action, the share and the keyboard order are unchanged.

   TWO COLUMNS BY TWO ROWS, which is why the photograph can be 4:3 and still fill
   it. Below 1180px the grid is three columns and it takes two of them; below
   640 it takes the full width and stops being a square, because a 2x2 tile in a
   2-column grid is the whole shelf.
   ========================================================================== */
.cell--lead { grid-column: span 2; grid-row: span 2; }
/* 5:4, not the shelf's 4:3 and not the 16:11 this started at. The tile is
   roughly square, so the photograph's shape decides how much empty card sits
   under the caption: at 16:11 the picture took a third of the tile and left a
   void nobody had designed. */
.cell--lead .cell__img { aspect-ratio: 5 / 4; }
.cell--lead .cell__img img { padding: var(--sp-6); }
/* The name and the price step up with the tile. A tile at four times the area
   with 15px type on it reads as a mistake rather than as emphasis. */
.cell--lead .cell__name { font-size: var(--fs-h4); letter-spacing: -.02em; }
.cell--lead .cell__sum { font-size: var(--fs-small); -webkit-line-clamp: 2; }
.cell--lead .price--cell { font-size: var(--fs-price-lg); }
/* NO STRETCH. .cell__body carries flex: 1 so a row of cards ends with their
   actions aligned — which is right for a one-row card and wrong for a tile
   spanning two, where it opens a hand's width of white between the summary and
   the price. The lead's content sits together under its photograph. */
.cell--lead .cell__body { flex: 0 0 auto; padding-top: var(--sp-6); }
.cell--lead .cell__link { flex: 0 0 auto; }

@media (max-width: 1180px) {
  .cell--lead { grid-row: span 1; }
  .cell--lead .cell__img { aspect-ratio: 3 / 2; }
}
@media (max-width: 640px) {
  .cell--lead { grid-column: span 2; grid-row: span 1; }
}

/* ==========================================================================
   THE CLAIM — the shop's own promise, at display scale, on the black.

   The front page ran the stage, then two thousand pixels of light ground
   carrying tiles and cards at one weight, then the footer. This is the second
   dark moment and the only display type after the h1.

   THE WORDS ARE THE SHOPKEEPER'S. Shop.claim reads the last paragraph of the
   About and renders nothing when there is not one — see the note there. Nothing
   about this band is written in CSS except how loud it is.
   ========================================================================== */
/* FULL-BLEED, and it has to break out of the content column to get there.

   `margin-inline: calc(50% - 50vw)` widens the element to the viewport from
   inside a centred container without any change to the markup. The one trap is
   that 100vw INCLUDES the scrollbar on a desktop, so the naive version is a few
   pixels of horizontal overflow on every page — which is why the width is taken
   from the two margins rather than set outright, and why scratchpad/measure and
   the CDP overflow check are run after touching this.

   It was a contained panel with one line in it, and the reference this follows
   is right: a band the width of the screen is the second thing on the page you
   cannot miss, and a contained one is a box. */
.claim {
  margin-block: 0 var(--sp-16);
  /* THE SCROLLBAR IS INSIDE 100vw AND OUTSIDE THE CONTENT BOX.

     calc(50% - 50vw) is the usual way to break a full-bleed band out of a
     centred column, and on a desktop it is a few pixels WIDER than the page:
     100vw counts the scrollbar, clientWidth does not. That is a horizontal
     scrollbar on every page of the shop, which the CDP overflow check found at
     768, 1024 and 1440 and which nobody would notice by looking.

     There is no CSS unit for the difference, so it is measured once at boot and
     on resize and published as --sbw. Half of it at each side puts the band
     exactly on the page. The fallback of 0px is the old behaviour, which is
     right for a phone, where there is no scrollbar to count. */
  margin-inline: calc(50% - 50vw + (var(--sbw, 0px) / 2));
  padding: clamp(var(--sp-16), 8vw, var(--sp-24)) var(--gutter);
  /* --field-2, NOT --field. The reference runs two darks on purpose: #141414
     for the band that interrupts the page and #0A0A0A for the footer that ends
     it. With one value for both, the page finishes in the same colour it was
     interrupted by and the band stops being an interruption. */
  background: var(--field-2);
}
.claim__in {
  max-width: var(--measure); margin-inline: auto;
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-8);
  text-align: center;
}
/* CENTRED, and the stage's statement is not — which is the point. Two
   left-aligned display blocks one scroll apart read as the same thing said
   twice; centring this one makes it a statement rather than a second headline. */
.claim__line {
  margin: 0;
  max-width: 22ch;
  font-family: var(--font-display);
  font-size: calc(var(--fs-cover) * 1.2);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: var(--track-display);
  line-height: 1;
  color: var(--field-ink);
  text-wrap: balance;
}
/* THE ACCENT IS A MARK, NOT A HIGHLIGHTED WORD. Setting one word of the
   sentence in champagne would mean this stylesheet deciding which of a
   shopkeeper's words matters, and it is their sentence. */
.claim__line::after {
  content: ""; display: block;
  width: 64px; height: 3px; margin: var(--sp-6) auto 0;
  background: var(--signal);
  border-radius: 2px;
}
.claim__go { scroll-margin-top: 120px; }

/* THE REDUCTION, ON THE PHOTOGRAPH.

   A fact the shopkeeper entered, which the page was showing as a small
   struck-through number nobody looks at. The percentage is computed in
   Shop.cell from the two prices and rounded down.

   TOP LEFT, not top right. The share sits bottom right and the eye that has
   found one corner looks at the other; and on a contained photograph the right
   edge is where a wide object's detail usually runs to. */
/* A ROUND BADGE, which is the reference's, sitting ON the corner of the well
   rather than inside it — the reference's overlaps its photograph's edge and
   that overlap is most of why it reads as applied rather than printed. */
.cell__off {
  position: absolute; top: -10px; right: -8px; z-index: 2;
  display: grid; place-items: center;
  width: 52px; height: 52px; border-radius: 50%;
  background: var(--signal); color: var(--on-signal);
  font: 700 var(--fs-label)/1 var(--font-ui);
  letter-spacing: -.02em; text-align: center;
  pointer-events: none;
}
/* The badge hangs off the well's TOP RIGHT corner on every cell, including
   this one — it was still pinned left here from when the well clipped it. */
.cell--lead .cell__off { top: -14px; right: -12px; width: 68px; height: 68px; font-size: var(--fs-small); }

.cell__name {
  font-family: var(--font-ui);
  /* 600 and drawn in, rather than 500 at its natural width. A product name is
     the line the eye lands on after the photograph, and at 15px the extra weight
     is the difference between a caption and a label on a shelf. */
  font-size: var(--fs-name); font-weight: 600; line-height: 1.35;
  letter-spacing: -.012em;
  color: var(--ink);
  margin-bottom: var(--sp-1);
}
.cell__link:hover .cell__name { text-decoration: underline; text-underline-offset: 3px; }

/* --ink-2 rather than --ink-3. The caption's ground is --surface now, where
   --ink-3 would pass — but this line is 12px and --ink-2 costs nothing. */
.cell__sum {
  margin: 0 0 var(--sp-2); font-size: var(--fs-tiny); line-height: 1.5; color: var(--ink-2);
  display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden;
}
/* NOT CHIPS. A specification is a fact; set small with air around it, it reads
   better than the same fact in a grey lozenge — and a row of lozenges is the
   loudest "assembled from a component library" signal there is. */
.specs { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin: 0 0 var(--sp-2); min-width: 0; }
.spec {
  max-width: 100%; min-width: 0;
  font-size: var(--fs-tiny); color: var(--ink-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* PRICE LEFT, SHARE RIGHT. The row is outside .cell__link now — see Shop.cell —
   so it is its own band between the caption and the action, with the card's own
   padding around it. */
/* THE PRICE LINE IS CENTRED AND THE SHARE SITS WITH IT rather than at the far
   edge — with the card gone there is no far edge for it to sit at, and a share
   icon floating in white two hundred pixels from its price is a control that
   belongs to nothing. */
.cell__price, .cell__foot {
  display: flex; align-items: center; justify-content: center;
  gap: var(--sp-2);
  padding: var(--sp-3) 0;
}
.cell__money { display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: wrap; min-width: 0; }

/* THE SHARE. A quiet target beside a loud price: the price is what the eye is
   scanning for and this must not compete with it, but it is still a 36px box
   rather than a 16px glyph somebody has to aim at. */
.wa {
  flex: none; display: grid; place-items: center;
  width: 36px; height: 36px; border-radius: var(--r-ctl);
  color: var(--ink-3);
  transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.wa:hover, .wa:focus-visible {
  /* WhatsApp's own green, and ONLY on hover. A brand colour sitting on fifteen
     cards at rest would be the loudest thing on the shelf, and the shelf is for
     the goods. */
  color: #25D366; background: var(--surface);
}
.wa__i { width: 19px; height: 19px; display: block; }

/* On the product page the share is a labelled control rather than an icon: there
   is one of it, and room for the word. */
.share { display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap; }
.share__wa { gap: var(--sp-2); }
.share__wa .wa__i { width: 16px; height: 16px; }
.share__wa:hover { background: #25D366; border-color: #25D366; color: #FFFFFF; }

/* THE ACTION IS A BUTTON, and it is always visible.

   It was type: a quiet underlined line at the foot of the caption, on the theory
   that one filled control on the page is enough. It read as a footnote. The most
   important thing a shelf can offer is a way to buy without opening the product,
   and an action that has to be looked for is an action most people will not take
   — so it is now shaped like what it is, at the full width of the cell and at a
   size a thumb can hit.

   Revealing it on hover is the tidier-looking choice and still the wrong one:
   hover does not exist on a touch screen, which is where most of this shop is
   used, and a control nobody can see is a feature that has been removed. */
/* A SIBLING OF .cell__link, not a child of .cell__body — the card is an
   <article> holding a link, a price row and a button, because anything
   interactive inside an <a> closes the anchor early and is invalid HTML. So it
   is already the full width of the cell: the negative margins it was first given
   pushed it 16px past the photograph on both sides. */
.cell__act { padding: 0; }

/* THE ACTION IS TYPE WITH A RULE UNDER IT, which is the reference's, and it is
   44px TALL, which is not.

   This file has argued this out once before and landed the other way: a quiet
   text action was tried, read as a footnote, and became a filled button. What
   made that true was the action being SMALL — the reference's is about 19px of
   clickable height. Set in small caps at a proper weight, centred under the
   price, with a rule under it and a 44px box around it, it reads as the shelf's
   control and a thumb still finds it.

   The rule is the accent, so the shelf's action is the only blue on a white
   page besides the price it sits under. */
.add--card {
  display: inline-flex; align-items: center; justify-content: center;
  width: auto; min-height: 44px; padding: 0 var(--sp-2);
  border: 0; border-radius: 0; background: none;
  color: var(--ink);
  /* The reference's label: 14px, weight 500, 1.2px of tracking, uppercase. */
  font: 500 var(--fs-small)/1 var(--font-ui);
  text-transform: uppercase; letter-spacing: .086em;
  box-shadow: inset 0 -2px 0 0 var(--signal);
  text-decoration: none; white-space: nowrap; cursor: pointer;
  transition: color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
.add--card:hover { background: none; color: var(--signal); box-shadow: inset 0 -2px 0 0 var(--signal); }
.add--card:active { color: var(--signal-press); }
a.add--card { background: none; color: var(--ink); border: 0; }
a.add--card:hover { background: none; color: var(--signal); }
.add--done, .add--done:hover, .add--done[disabled] {
  background: none; color: var(--success-ink);
  box-shadow: inset 0 -2px 0 0 var(--success-ink);
}
/* The action is the last thing in the cell and needs air under it, which the
   card's padding used to give. */
.cell__act { display: flex; justify-content: center; padding-bottom: var(--sp-4); }

/* The shelf's action is declared ONCE, beside .cell__act, and this is where a
   second copy of it used to live — a filled-button block that won on source
   order and kept the shelf in rectangles while the rule meant to style it sat
   two hundred lines above doing nothing. Two rules for one class is how that
   happens; there is one now. */

/* A product with no photograph: its initials on the caption's own colour. */
.ghost { display: grid; place-items: center; width: 100%; height: 100%; background: transparent; }
.ghost b {
  font-family: var(--font-ui); font-variation-settings: var(--sign-lg);
  font-size: clamp(30px, 5vw, 64px); font-weight: 700; letter-spacing: -.03em;
  color: rgba(20, 22, 28, .26);
}

/* ==========================================================================
   PRICE — Archivo at 700, tight and tabular, so a column of them lines up.
   It was set in a serif, which is the thing that made the whole page read as
   printed matter rather than as a shop.
   ========================================================================== */

/* CHAMPAGNE, and this is where the accent is spent.

   The price was --ink, the same off-white as everything else on the card, and
   on a dark theme with one accent that is a waste of the only colour in the
   palette. A price is what a shop is FOR; it is the thing being scanned, and it
   is the one line on a shelf that earns a colour of its own. --signal-ink reads
   7.35:1 on --surface and 8.67:1 on the page.

   Nothing else on the shelf is coloured, so this stays loud. */
.price {
  font-variation-settings: 'wdth' 100;
  font-weight: 700; font-variant-numeric: tabular-nums;
  letter-spacing: -.02em; line-height: 1.1; white-space: nowrap;
  color: var(--signal-ink);
}
.price i {
  font-style: normal;
  /* The currency mark, a step quieter than the figure it belongs to so the
     number is what the eye lands on. --ink-2 clears 7.85:1 on a card. */
  font-size: .62em; font-weight: 600; color: var(--ink-2);
  margin-right: .4em; letter-spacing: 0;
}
.price sup { font-size: .58em; vertical-align: .46em; margin-left: .06em; }
.price--cell { font-size: var(--fs-price); }
.price--big, .price--open { font-size: var(--fs-price-lg); letter-spacing: -.02em; }
/* A reduction is the one thing that gets the signal colour, because it is the
   one fact on a shelf that expires. */
.price--was {
  font-family: var(--font-ui); font-size: var(--fs-tiny); font-weight: 500;
  /* --ink-2, NOT --sale. A struck-through old price sits on a well as often
     as on paper, and --danger measures about 4.0 on a well. Red is the NEW
     price's job anyway; this one only has to be readable and obviously past. */
  color: var(--ink-2); text-decoration: line-through;
}
.price--from { font-family: var(--font-ui); font-size: var(--fs-tiny); font-weight: 500; color: var(--ink-2); }

.tag { font-size: var(--fs-tiny); font-weight: 600; color: var(--signal-ink); }
.tag--off { color: var(--sale); }

/* ==========================================================================
   PAGINATION, EMPTY, LOADING
   ========================================================================== */

.pager {
  display: flex; align-items: baseline; gap: var(--sp-5);
  margin-top: var(--sp-16); padding-top: var(--sp-4);
  border-top: 1px solid var(--line-2);
}
.pager button {
  min-height: 0; padding: 0; border: 0; background: none; cursor: pointer;
  font: 600 var(--fs-small)/1.4 var(--font-ui); color: var(--ink);
  text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--signal-ink);
}
.pager button:hover:not([disabled]) { color: var(--signal-ink); }
.pager button[disabled] { color: var(--ink-3); text-decoration: none; cursor: default; }
.pager span { margin-left: auto; font-size: var(--fs-small); color: var(--ink-3); font-variant-numeric: tabular-nums; }

/* Left-aligned and narrow. A centred block of apology in the middle of an empty
   page is the template filling space it has nothing to say about. */
.nothing { padding: var(--sp-20) 0; max-width: 46ch; }
.nothing h2 { font-size: var(--fs-h1); letter-spacing: -.02em; margin-bottom: var(--sp-3); }
.nothing p { margin: 0 0 var(--sp-5); color: var(--ink-2); }

/* Blanks in the shape of the answer, so the page does not reflow when the
   products arrive. */
.skeleton { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--sp-8) var(--sp-5); }
.skeleton i {
  /* The blank is the shape of the answer, so it follows the cell it becomes —
     a blank of a different height reflows the page exactly as this is meant to
     prevent. It is WHITE and RAISED for the same reason: with the page grey,
     a recessed grey blank turning into a raised white card is a flash of the
     thing changing colour under the reader. */
  display: block; aspect-ratio: 1; background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-tile); box-shadow: var(--lift-rest);
  animation: pulse 1.5s var(--ease) infinite;
}
@keyframes pulse { 50% { opacity: .5; } }

/* ==========================================================================
   THE PRODUCT

   Large photography on the left; the facts in a column beside it, separated by
   rules. Not one card per fact — that is how a product page ends up reading as
   a settings screen.
   ========================================================================== */

.detail {
  display: grid; grid-template-columns: minmax(0, 6fr) minmax(0, 5fr);
  gap: clamp(var(--sp-8), 4.5vw, var(--sp-20));
  align-items: start;
}
.detail__media, .detail__body { min-width: 0; }
/* The buy column is a panel now, so it carries the panel's padding — and it
   sticks, because on a tall product page the price and the Add button are what
   you scroll away from. */
.detail__body { position: sticky; top: calc(var(--sticky) + var(--sp-6)); align-self: start; }
.detail__body.panel { padding: var(--sp-6); }

/* Capped at the viewport: a 3:4 crop in an 800px column is 1060px tall, so on a
   laptop the name and the price were both below the fold on the one page whose
   job is to sell the thing. */
/* The photograph gets the card's own ground and hairline, so the two columns
   read as a pair rather than as a picture beside a panel. */
.shot {
  /* 4:3 and contained, matching .cell__img — the same reasoning and the same
     goods. This is the one screen whose whole job is letting somebody look at
     the thing, so the shape that wastes least is the shape to use. */
  aspect-ratio: 4 / 3; max-height: 76vh; overflow: hidden;
  background: var(--plate); border: 1px solid var(--line);
  border-radius: var(--r-tile);
}
.shot img {
  width: 100%; height: 100%; object-fit: contain;
  padding: var(--sp-6); box-sizing: border-box;
}
.strip { display: flex; gap: var(--sp-2); margin-top: var(--sp-2); flex-wrap: wrap; }
.strip button {
  /* The same shape as the shot above them: a thumbnail cropped differently from
     the picture it selects is a thumbnail that looks like a different picture. */
  width: 96px; aspect-ratio: 4 / 3; min-height: 0; height: auto; padding: 0; overflow: hidden;
  border: 0; border-radius: var(--r-ctl); background: var(--plate);
  border: 1px solid var(--line); cursor: pointer;
  opacity: .5; transition: opacity var(--t-fast) var(--ease);
}
.strip button:hover, .strip button[aria-current="true"] { opacity: 1; }
/* A thumbnail that crops differently from the shot above it is a thumbnail
   that looks like a different photograph. */
.strip img { width: 100%; height: 100%; object-fit: contain; padding: 4px; box-sizing: border-box; }

.detail h1 { font-size: var(--fs-h1); line-height: var(--lh-tight); letter-spacing: -.02em; margin-bottom: var(--sp-4); }
.detail .lede { margin: 0 0 var(--sp-5); font-size: var(--fs-h4); line-height: 1.55; color: var(--ink-2); max-width: 46ch; }

/* A price and what it was are a LINE, with air between them. Emitted as two
   inline spans they ran together — "OMR 14900OMR 18.900" — which on a price is
   worse than merely unreadable. */
.price-line { display: flex; align-items: baseline; gap: var(--sp-4); flex-wrap: wrap; margin-bottom: var(--sp-6); }

.opt { margin: var(--sp-6) 0; padding-top: var(--sp-5); border-top: 1px solid var(--line); }
.opt__name { margin-bottom: var(--sp-3); font-size: var(--fs-small); font-weight: 600; color: var(--ink-3); }
.opt__row { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.opt__row button {
  min-height: 40px; padding: 8px var(--sp-4);
  border: 1px solid var(--line-2); border-radius: var(--r-ctl);
  background: var(--surface); color: var(--ink);
  font: 500 var(--fs-small)/1.4 var(--font-ui); cursor: pointer;
  transition: border-color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.opt__row button:hover:not([disabled]) { border-color: var(--field); }
/* BLACK, not champagne. Gold is the colour of the ACTIONS on this page — the
   button, the link, the price — and a chosen option is a state. Two different
   meanings in one colour is how a palette stops signalling anything. */
.opt__row button[aria-pressed="true"] { background: var(--field); border-color: var(--field); color: var(--field-ink); }
.opt__row button[disabled] { color: var(--ink-3); border-color: var(--line); text-decoration: line-through; cursor: not-allowed; }
.opt__row .sw { width: 14px; height: 14px; display: inline-block; border-radius: var(--r-chip); vertical-align: -2px; margin-right: 8px; }

/* A column. As a wrapping row, the quantity, the button, the item code and the
   share link came out side by side on one line, reading as four unrelated
   controls. */
.buy {
  display: flex; flex-direction: column; align-items: stretch; gap: var(--sp-3);
  margin-top: var(--sp-6); padding-top: var(--sp-5); border-top: 1px solid var(--line-2);
}
.buy .add { flex: 0 1 auto; }
/* SC 2.5.8 Target Size (Minimum), new in WCAG 2.2: 24x24, and the spacing
   exception cannot rescue these because they TOUCH — measured at 42x20 and
   48x20 with a 0px gap, on the product page and on every basket line.

   36px rather than 24, which is what `.wa` already takes on a product card and
   for the same reason written down there: a target rather than a glyph to aim
   at. This is the control a customer uses most on a phone. */
.qty { display: inline-flex; align-items: stretch; min-height: 36px; border: 1px solid var(--line-2); border-radius: var(--r-ctl); overflow: hidden; background: var(--surface); }
.qty button { width: 42px; min-height: 36px; border: 0; background: none; color: var(--ink); font: 400 18px/1 var(--font-ui); cursor: pointer; }
.qty button:hover { background: var(--paper-2); }
.qty input {
  width: 48px; border: 0; border-inline: 1px solid var(--line);
  background: none; color: var(--ink);
  font: 600 var(--fs-body)/1 var(--font-ui); text-align: center; font-variant-numeric: tabular-nums;
}

/* THE DESCRIPTION, UNDER BOTH COLUMNS AND AT A READING MEASURE. It used to be
   the last block of the buy column — a shop's longest prose in the page's
   narrowest measure, beside a left column that had run out of photograph. */
.pdp__prose {
  max-width: 68ch; margin-top: var(--sp-12);
  padding-top: var(--sp-6); border-top: 1px solid var(--line-2);
}
.pdp__prose h2 {
  margin-bottom: var(--sp-4);
  font-size: var(--fs-h3); letter-spacing: -.026em;
}
.pdp__prose p { margin: 0 0 var(--sp-4); color: var(--ink-2); line-height: var(--lh-body); }
.pdp__prose p:last-child { margin-bottom: 0; }

.prose { margin-top: var(--sp-8); padding-top: var(--sp-5); border-top: 1px solid var(--line); }
.prose h2 { font-family: var(--font-ui); font-size: var(--fs-small); font-weight: 600; color: var(--ink-3); margin-bottom: var(--sp-3); }
.prose p { margin: 0 0 var(--sp-3); color: var(--ink-2); max-width: 62ch; }

/* ==========================================================================
   THE FOOTER — the field again. A shop that opens in its own colour and closes
   in grey has not finished.
   ========================================================================== */

.foot { background: var(--field); color: var(--field-fg); padding-block: var(--sp-16) var(--sp-6); }
.foot__in { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: var(--sp-10) var(--sp-8); }
/* A RULE UNDER EACH HEADING, which is the reference's, and it is doing work
   here: five columns of small type on a black ground run together without one. */
.foot__in h2 { padding-bottom: var(--sp-3); border-bottom: 1px solid var(--field-line); }
/* What the shop accepts. Chips rather than logos — see the note in shop.js. */
.foot__pays { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.foot__pays li {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--field-line); border-radius: var(--r-ctl);
  background: var(--field-2);
  font: 600 var(--fs-tiny)/1.4 var(--font-ui);
  color: var(--field-ink); white-space: nowrap;
}
.pay__i { width: 22px; height: 22px; flex: none; color: var(--signal-lit); }
/* THE REAL MARKS ARE MONOCHROME by design, so they take the footer's own ink
   and sit as one set rather than as five logos at five saturations. The filter
   is what paints an <img> — a mask would need each file inlined. */
.pay__m {
  width: 28px; height: 22px; flex: none; object-fit: contain;
  filter: brightness(0) invert(1);
  opacity: .92;
}
.foot__brand { grid-column: 1 / -1; display: flex; align-items: baseline; gap: var(--sp-3); }
.foot__name {
  font-variation-settings: var(--sign);
  font-size: clamp(24px, 2.6vw, 36px); font-weight: 800; letter-spacing: -.035em;
  color: var(--field-ink);
}
.foot h2 {
  font-family: var(--font-ui); font-size: var(--fs-small); font-weight: 600;
  color: var(--signal-lit); margin-bottom: var(--sp-2);
}
.foot p, .foot address { margin: 0; font-style: normal; font-size: var(--fs-small); line-height: 1.7; color: var(--field-fg); }
/* A day and its hours, a zone and its fee: a PAIR, set as a pair. They were
   joined with a middle dot, which is both the meta-string tell and harder to
   read — the eye has to find the separator before it can find the two halves. */
/* THE LABEL COLUMN IS GONE. `min-width: 10ch` on the label lined the values up
   beautifully in a wide panel and, in a 190px footer column, left the value
   about forty pixels to wrap in: "OMR 1.000 / Same day if ordered before 4pm"
   came out one word per line. A pair wraps like prose now — the label keeps its
   own width and the value takes what is left, and when there is not enough the
   value moves to the next line whole. */
.pair { display: flex; flex-wrap: wrap; gap: 0 var(--sp-3); }
/* DARK BY DEFAULT, light only on a dark ground.

   It was the other way round — `--field-ink` (white) as the default, corrected
   back to `--ink` by `.card .pair b, .sheet .pair b`. That works only as long as
   every light-ground use happens to sit inside a `.card` or a `.sheet`, and the
   day checkout stopped being a `.sheet` the delivery rate card lost its zone
   names: white text on a white panel, with the prices still indented by the
   space the invisible names took. The ground decides, so the ground opts in. */
.pair b { flex: 0 1 auto; min-width: 0; font-weight: 600; color: var(--ink); }
.pair > span { min-width: 0; }
.foot .pair b, .summary .pair b { color: var(--field-ink); }
/* In the footer the columns are narrow enough that the value is nearly always
   on its own line anyway, so it is put there deliberately and the rows are
   given air between them. */
.foot .pair { display: block; margin-bottom: var(--sp-3); }
.foot .pair b { display: block; }
.foot__links { list-style: none; margin: 0; padding: 0; }
.foot__links li { font-size: var(--fs-small); line-height: 1.9; }
/* SC 2.5.8 again. These were 16px tall with 12px between them — a column of
   links is not "a sentence of text", so the inline exception does not apply and
   the spacing exception fails at 12px. Padding the anchor rather than opening
   the line-height, so the footer's rhythm is unchanged and only the HIT AREA
   grows. */
.foot__links a { display: inline-block; padding-block: 5px; }
/* SC 2.5.8 once more, on the product page's spec list. These are links inside a
   <dd> and the criterion's INLINE exception arguably covers them — but "arguably"
   is not a thing to leave in an audit when the fix is one rule, so they are given
   the hit area outright and the question does not arise. */
.meta a { display: inline-block; padding-block: 4px; }
.foot a { color: var(--field-fg); text-decoration: none; }
.foot a:hover { color: var(--field-ink); text-decoration: underline; }
.vendor {
  display: flex; justify-content: space-between; gap: var(--sp-4); flex-wrap: wrap;
  padding-top: var(--sp-5); margin-top: var(--sp-10);
  border-top: 1px solid var(--field-line);
  font-size: var(--fs-tiny); color: var(--field-fg);
}
.vendor a { color: var(--field-fg); }
/* The console link sits between the two ends of the row rather than crowding
   either: whose shop this is on the left, who built it on the right, and the way
   in for whoever runs it in the middle. */
/* FOUR LINKS NOW, not one — Help, Privacy, Terms and the console. They need a
   gap and they need to wrap on a phone rather than pushing the copyright off
   the row. */
.vendor__mid { margin-inline: auto; display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-4); }
.vendor__mid a { text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--field-line); }
.vendor__mid a:hover { color: var(--field-ink); text-decoration-color: var(--signal-lit); }

/* ==========================================================================
   BACK TO THE TOP

   The control has been built by shop.js on every page all along and THIS BLOCK
   WAS NOT HERE. With no rule, `position` fell back to `static`, so what a
   customer got past a screen and a half was a 16x21 grey default button in
   normal flow BELOW THE FOOTER — measured at `top: 2778` in viewport
   coordinates. Present, unhidden, in the tab order, and unreachable.

   Same failure this file records twice already: the range replacement that
   swallowed the assurance strip's styling, and the renamed custom property that
   invalidated thirty-five declarations. A missing rule does not warn, and
   neither does a missing block.

   z-index 45: above the page and above the sticky masthead (40), UNDER the
   refine drawer's veil (50) — the drawer is something somebody deliberately
   opened and this is not — and under the demo floater (90).
   ========================================================================== */

.totop {
  position: fixed; inset: auto var(--sp-5) var(--sp-5) auto; z-index: 45;
  display: grid; place-items: center;
  /* 48, not 40. The one control on the page a thumb reaches for without
     looking, and WCAG 2.2 asks 24 as a floor rather than as a target. */
  width: 48px; height: 48px; padding: 0;
  border: 0; border-radius: var(--r-pill);
  /* THE BAND'S OWN BLUE — --band, not --signal, so it carries the exact value
     of the masthead and the page header this button returns you to. A control
     the colour of its destination is the easiest kind to learn.

     WHAT SEPARATES IT FROM THE DISCOUNT BADGE IS THE RING, not the hue.
     `.cell__off` is a 52px circle in nearly this colour and it never leaves its
     cell; the ring of page colour under the shadow is what says this one floats
     above the page rather than sitting on a card. */
  background: var(--band); color: var(--on-signal);
  box-shadow: 0 0 0 4px var(--paper), var(--lift); cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  transition: background var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
  /* It arrives rather than appearing: the scroll listener flips `hidden`, and a
     control that materialises at full strength mid-scroll reads as a glitch. */
  animation: totop-in var(--t-base) var(--ease) both;
}
.totop svg { width: 20px; height: 20px; }
.totop:hover { background: var(--signal-press); transform: translateY(-2px); }
.totop:active { transform: none; }
.totop:focus-visible { outline: 3px solid var(--signal); outline-offset: 3px; }

@keyframes totop-in { from { opacity: 0; transform: translateY(10px); } }

/* THE DEMO FLOATER OWNS THIS CORNER on the account pages — `.ql`, a 40px tab on
   the same --sp-5 inset. Step up over it rather than land on it. */
body:has(.ql) .totop { bottom: calc(var(--sp-5) + 40px + var(--sp-3)); }

@media (prefers-reduced-motion: reduce) {
  .totop { animation: none; }
  .totop:hover { transform: none; }
}

/* ==========================================================================
   THE ACCOUNT SCREENS — the customer's own pages.

   WHAT THIS REPLACES. Three pages built out of `.sheet` + `.card` with a
   hairline between each block and nothing else: one flat column of unrelated
   things, no hierarchy, and the form a customer rarely touches sitting above
   the orders they came to look at. The markup was also carrying a dozen inline
   `style=` attributes naming --muted, --rule and --panel — tokens renamed two
   rewrites ago, which means every one of those declarations was doing NOTHING.
   A missing custom property does not warn. That is most of why these screens
   read as unfinished: buttons with no border, notes with no colour.

   The shapes below are the theme's own — --surface with a --line hairline and
   --r-tile, the same card the shelf uses — so the account looks like the shop
   it belongs to rather than like a form somebody bolted on.
   ========================================================================== */

.account { padding-block: var(--sp-10) var(--sp-20); }

/* The head: who you are, and the one control that is not about shopping. */
.account__head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: var(--sp-5); flex-wrap: wrap;
  padding-bottom: var(--sp-5); margin-bottom: var(--sp-8);
  border-bottom: 1px solid var(--line-2);
}
.account__who { min-width: 0; }
.account__who h1 { font-size: var(--fs-h1); letter-spacing: -.03em; }
.account__mail { margin: var(--sp-2) 0 0; font-size: var(--fs-small); color: var(--ink-2); }

.account__grid {
  display: grid; grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
  gap: var(--sp-6); align-items: start;
}
.account__col { display: grid; gap: var(--sp-6); min-width: 0; }

/* A panel. The shelf's card, at page scale. */
.panel {
  min-width: 0; padding: var(--sp-6);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-tile);
}
.panel__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-4); margin-bottom: var(--sp-5);
}
.panel__head h2 { font-size: var(--fs-h3); letter-spacing: -.026em; }
.panel__head p { margin: 0; font-size: var(--fs-small); color: var(--ink-2); }
/* The last field in a panel has the panel's own padding under it already.

   :last-CHILD, not :last-of-type. `.f` is a <label>, and `:last-of-type` matches
   the last LABEL among its siblings — which on a sign-in form is the password
   field, with the submit button after it. So the rule stripped the gap between
   the password box and the button and they sat welded together. `:last-child`
   only matches when the field really is the end of the form. */
.panel form .f:last-child { margin-bottom: 0; }
.panel .card { padding: 0; border: 0; }

/* ---- an order in the list ------------------------------------------------

   THE OLD ROW WAS BROKEN MARKUP, not a styling problem. It carried TWO `class`
   attributes on one <a> — `class="addr" class="row row--split plain"` — and the
   parser keeps the first and discards the second, so it never became a split
   row and never lost its underline. The price span had the same fault. What
   reached the screen was "1 item, NewLKR 3,750.00": the status and the currency
   with nothing between them, every word underlined.                          */

.orow {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--sp-2) var(--sp-5); align-items: center;
  padding: var(--sp-4); margin-bottom: var(--sp-2);
  border: 1px solid var(--line); border-radius: var(--r-ctl);
  background: var(--surface); color: inherit; text-decoration: none;
  transition: border-color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.orow:last-child { margin-bottom: 0; }
.orow:hover, .orow:focus-visible { border-color: var(--line-2); background: var(--paper); }
.orow__no {
  font-variation-settings: var(--sign);
  font-size: var(--fs-h4); font-weight: 700; letter-spacing: -.02em; color: var(--ink);
}
.orow__meta {
  grid-column: 1; display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--sp-2) var(--sp-3);
  font-size: var(--fs-small); color: var(--ink-2);
}
.orow__total { grid-row: 1 / 3; grid-column: 2; text-align: right; }

/* The status, as a chip. It was a bare word run into the line beside it. */
.stat {
  display: inline-flex; align-items: center;
  padding: 2px var(--sp-2); border-radius: var(--r-chip);
  background: var(--paper-3); color: var(--ink);
  font-size: var(--fs-tiny); font-weight: 600; letter-spacing: 0;
}
.stat--live { background: var(--well-1); color: var(--signal-ink); }
.stat--done { background: var(--well-4); color: var(--success-ink); }
/* --danger-ink, matching the two chips beside it: .stat--live takes
   --signal-ink and .stat--done takes --success-ink, and this one was the only
   of the three still wearing a FILL colour as words. 4.44:1 on --well-3. */
.stat--off  { background: var(--well-3); color: var(--danger-ink); }

/* ---- the order's own page ------------------------------------------------ */

.ohead {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--sp-5); flex-wrap: wrap; margin-bottom: var(--sp-6);
}
.ohead h1 { font-size: var(--fs-h1); letter-spacing: -.03em; }
.ohead p { margin: var(--sp-2) 0 0; font-size: var(--fs-small); color: var(--ink-2); }

/* The progress track. It was five words in a row with the current one bolded,
   which reads as a sentence rather than as a position. */
/* The paperwork lines on a customer's order. A label, the file and its size —
   three elements set as three, the same shape the trust strip's facts take,
   rather than one string with separators in it. */
.docline { display: grid; gap: 2px; margin: var(--sp-4) 0 0; }
.docline > b { font-size: var(--fs-tiny); }
.docline__file {
  font-size: var(--fs-small); color: var(--signal-ink); text-decoration: none;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.docline__file:hover { text-decoration: underline; }
.docline__sub { font-size: var(--fs-tiny); color: var(--ink-2); line-height: 1.5; }
.docline__act { display: flex; align-items: center; gap: var(--sp-3); margin: var(--sp-3) 0 0; }

.track { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: var(--sp-2); margin: 0; padding: 0; list-style: none; }
.track li { font-size: var(--fs-tiny); color: var(--ink-3); }
.track li::before {
  content: ''; display: block; height: 4px; margin-bottom: var(--sp-2);
  border-radius: 2px; background: var(--paper-3);
}
.track li[data-on="1"] { color: var(--ink); font-weight: 600; }
.track li[data-on="1"]::before { background: var(--signal); }
.track li[data-on="cancelled"] { color: var(--danger); font-weight: 600; }
.track li[data-on="cancelled"]::before { background: var(--danger); }

/* One line of an order. The figure was a bare number with no currency mark
   beside a total that had one — two ways of writing money in one panel. */
.oline {
  display: grid; grid-template-columns: 56px minmax(0, 1fr) auto;
  gap: var(--sp-4); align-items: center;
  padding: var(--sp-3) 0; border-bottom: 1px solid var(--line);
}
.oline:last-of-type { border-bottom: 0; }
.oline__img {
  width: 56px; aspect-ratio: 1; overflow: hidden;
  /* THE PLATE, for the reason on .cell__img: the photograph is contained, so
     what shows around it is this, and these are shot on white sweeps. */
  background: var(--plate); border: 1px solid var(--line); border-radius: var(--r-chip);
}
.oline__img img { width: 100%; height: 100%; object-fit: contain; }
/* BLOCKS. They are spans inside a span — the middle cell of the grid — so
   without this the name and the quantity run together as "Rugged case, Galaxy
   S24Qty 1". */
.oline__name { display: block; font-size: var(--fs-small); font-weight: 600; color: var(--ink); }
.oline__meta { display: block; margin-top: 1px; font-size: var(--fs-tiny); color: var(--ink-2); }
.oline__sum { text-align: right; font-size: var(--fs-small); font-variant-numeric: tabular-nums; }

/* The order lines inside the summary panel: the same row, on the field.

   TWO COLUMNS, NOT THREE. The panel is 320px wide, so a third column left the
   name about 115px to wrap in and "8 GB DDR4 SODIMM, 3200 MHz" came out over
   three lines beside a price set on one. The money moves down onto the
   quantity's row — there is room for both there, and the pair reads as one line
   item rather than as two columns that happen to be adjacent. */
.summary .oline { grid-template-columns: 44px minmax(0, 1fr); padding: var(--sp-3) 0; border-bottom-color: var(--field-line); }
.oline__body { min-width: 0; }
.oline__foot {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--sp-3); margin-top: 2px;
}
/* The block's own top margin would stack with the row's. */
.oline__foot .oline__meta { margin-top: 0; }
.oline__foot .oline__sum { font-variant-numeric: tabular-nums; white-space: nowrap; }
.summary .oline:last-of-type { border-bottom: 0; }
.summary .oline__img { width: 44px; background: var(--field-2); }
.summary .oline__name { color: var(--field-ink); }
.summary .oline__meta { color: var(--field-fg); }
.summary .oline__sum { color: var(--field-ink); }

.ototal {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--sp-4); margin-top: var(--sp-4); padding-top: var(--sp-4);
  border-top: 1px solid var(--line-2);
}
.ototal b { font-variation-settings: var(--sign); font-size: var(--fs-h4); }

/* ---- an address ---------------------------------------------------------- */

.addrs { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: var(--sp-3); }
.addrcard {
  padding: var(--sp-4); border: 1px solid var(--line); border-radius: var(--r-ctl);
  background: var(--surface);
}
.addrcard--default { border-color: var(--signal-ink); }
.addrcard h3 {
  display: flex; align-items: center; gap: var(--sp-2);
  font-family: var(--font-ui); font-size: var(--fs-small); font-weight: 700;
  letter-spacing: 0; margin-bottom: var(--sp-2);
}
.addrcard address {
  font-style: normal; font-size: var(--fs-small); line-height: 1.6; color: var(--ink-2);
}

/* ---- the sign-in, register and reset screens ----------------------------- */

.gate { display: grid; place-items: center; padding-block: var(--sp-16) var(--sp-20); }
.gate__in { width: 100%; max-width: 420px; }
/* Register carries a two-column pair of phone fields; the sign-in's 420px would
   squeeze them to nothing. */
.gate__in--wide { max-width: 520px; }
.gate__mark {
  display: block; margin-bottom: var(--sp-3);
  font-variation-settings: var(--sign);
  font-size: var(--fs-tiny); font-weight: 600; color: var(--signal-ink);
}
.gate h1 { font-size: var(--fs-h2); letter-spacing: -.03em; }
.gate > .gate__in > .lede { margin: var(--sp-2) 0 var(--sp-6); font-size: var(--fs-small); color: var(--ink-2); }
.gate .panel { padding: var(--sp-6); }
.gate__foot {
  margin: var(--sp-5) 0 0; text-align: center;
  font-size: var(--fs-small); color: var(--ink-2);
}
.gate__foot a { color: var(--signal-ink); }

@media (max-width: 900px) {
  .account__grid { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 640px) {
  .orow { grid-template-columns: minmax(0, 1fr); }
  .orow__total { grid-row: auto; grid-column: 1; text-align: left; }
  .oline { grid-template-columns: 44px minmax(0, 1fr) auto; }
  .oline__img { width: 44px; }
}

/* ==========================================================================
   FORMS AND PANELS — account, basket, checkout.
   ========================================================================== */

.sheet { max-width: 480px; margin-inline: auto; padding: var(--sp-16) var(--gutter) var(--sp-20); }
.sheet--wide { max-width: 940px; }
.sheet h1 { font-size: var(--fs-h1); letter-spacing: -.02em; margin-bottom: var(--sp-3); }
.sheet > p.lede { margin: 0 0 var(--sp-8); font-size: var(--fs-h4); color: var(--ink-2); }

/* A "card" here is a ruled section. Nothing is boxed. */
.card { padding: var(--sp-6) 0 0; border-top: 1px solid var(--line-2); background: none; }
.card + .card { margin-top: var(--sp-8); }
.card h2 { font-size: var(--fs-h3); margin-bottom: var(--sp-4); }

/* A FIELD IS A SURFACE. The underlined input was the other paper-form tell —
   it is a line to write on, and it gives a customer no target to tap at on a
   phone. A bordered, rounded, filled field is both the current idiom and a
   bigger hit area; the focus state is a ring in the signal colour rather than a
   thicker rule. */
.f { display: block; margin-bottom: var(--sp-5); }
.f > span { display: block; margin-bottom: var(--sp-2); font-size: var(--fs-label); font-weight: 600; color: var(--ink-2); }
.f input, .f select, .f textarea {
  width: 100%; height: var(--input-h); padding: 0 var(--sp-4);
  border: 1px solid var(--line-2); border-radius: var(--r-ctl);
  background: var(--surface); color: var(--ink);
  font: 400 var(--fs-body)/1 var(--font-ui);
  transition: border-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
.f textarea { height: auto; min-height: 112px; padding-block: var(--sp-3); line-height: var(--lh-body); }
.f input::placeholder, .f textarea::placeholder { color: var(--ink-3); }
.f input:focus, .f select:focus, .f textarea:focus {
  /* THE RING FOLLOWS THE SIGNAL. It was a literal rgba(227, 162, 27, .26) — a
     saffron from a palette two rewrites ago, which meant the focus ring had
     quietly stopped matching the border it sits outside. --signal-rgb exists
     for this. */
  outline: none; border-color: var(--signal);
  box-shadow: 0 0 0 3px rgba(var(--signal-rgb), .30);
}
.f small { display: block; margin-top: var(--sp-2); font-size: var(--fs-tiny); color: var(--ink-3); }
.f--pair { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr); gap: var(--sp-5); }
/* A checkbox and its words on one line. It was an inline style doing the same
   thing, in markup, three times. */
.f--check { display: flex; align-items: center; gap: var(--sp-2); }
.f--check input { width: auto; height: auto; }
.f--check > span { margin: 0; }

.note { margin-bottom: var(--sp-5); padding-left: var(--sp-4); border-left: 3px solid var(--line-2); font-size: var(--fs-small); color: var(--ink-2); }
.note--bad { border-left-color: var(--error); color: var(--error); }
.note--good { border-left-color: var(--success); color: var(--success-ink); }

.meter { height: 3px; margin-top: var(--sp-2); background: var(--paper-3); }
.meter i { display: block; height: 100%; width: 0; background: var(--error); transition: width var(--t-base) var(--ease), background var(--t-base) var(--ease); }
.meter i[data-level="2"] { background: var(--warning); }
.meter i[data-level="3"] { background: var(--success); }

.or { display: flex; align-items: center; gap: var(--sp-4); margin: var(--sp-6) 0; font-size: var(--fs-small); color: var(--ink-3); }
.or::before, .or::after { content: ''; flex: 1; height: 1px; background: var(--line); }

.addr { padding: var(--sp-4) 0; border-bottom: 1px solid var(--line); }
.addr--default { padding-left: var(--sp-4); border-left: 3px solid var(--signal); }
.addr h3 { font-family: var(--font-ui); font-size: var(--fs-small); font-weight: 600; margin-bottom: var(--sp-1); }
.addr address { font-style: normal; font-size: var(--fs-small); line-height: 1.6; color: var(--ink-2); }
.addr__acts { display: flex; gap: var(--sp-4); flex-wrap: wrap; margin-top: var(--sp-3); }
.addr__acts button {
  padding: 0; border: 0; background: none; cursor: pointer;
  font: 500 var(--fs-small)/1.4 var(--font-ui); color: var(--ink-2);
  text-decoration: underline; text-underline-offset: 3px;
}
.addr__acts button:hover { color: var(--ink); }
.addr__acts button.danger { color: var(--error); }

/* ==========================================================================
   BASKET, CHECKOUT, ORDER
   ========================================================================== */

/* THE BASKET, AND WHY IT IS TWO COLUMNS NOW.

   It was one pale sheet: three hairline rows, a subtotal sitting on a rule, and
   the checkout button loose on a line beside two other links. Nothing on it said
   which of the three was the thing to press, and nothing on it looked like the
   shop the customer had just walked round - this is the screen where somebody
   decides whether to hand over money, and it was the palest one on the site.

   What has to be read together is grouped: the goods down the left, and what
   they cost and how to go on in one panel to the right, set on the field.       */

.cartgrid {
  display: grid; grid-template-columns: minmax(0, 1fr) 320px;
  gap: var(--sp-10); align-items: start;
}

.basket { min-width: 0; border-top: 1px solid var(--line-2); }
.line {
  display: grid; grid-template-columns: 96px minmax(0, 1fr) auto;
  gap: var(--sp-5); align-items: start;
  padding: var(--sp-5) 0; border-bottom: 1px solid var(--line);
}
/* SQUARE, AND ON ONE OF THE SHELF'S OWN GROUNDS. A basket is a short list read
   at a glance, and the 3:4 portrait the shelf uses made every row 144px tall for
   a thumbnail nobody is studying. The tint rotates the way the shelf's does, so
   a product is recognisably the same object in both places. */
.line__img {
  width: 96px; aspect-ratio: 1; overflow: hidden;
  background: var(--plate); border: 1px solid var(--line); border-radius: var(--r-ctl);
}
.line__img img { width: 100%; height: 100%; object-fit: contain; }
.line__body { min-width: 0; }
.line__name {
  margin: 0 0 var(--sp-1);
  font-variation-settings: var(--sign);
  font-size: var(--fs-h4); font-weight: 700;
}
/* --ink-2, not --ink-3: this line carries the chosen options and the unit price,
   which are facts somebody is checking rather than decoration. */
.line__meta { margin: 0 0 var(--sp-3); font-size: var(--fs-small); color: var(--ink-2); }
.line__right { display: grid; gap: var(--sp-3); justify-items: end; text-align: right; }
/* A line the shop can no longer supply. Marked, never silently dropped - a
   basket that quietly loses a row is a basket nobody can trust. */
.line--gone { color: var(--error); }
.line--gone .line__img { opacity: .4; }

/* ---- the summary panel ---------------------------------------------------

   Sticky on a desktop, because a long basket scrolls and the total is the thing
   being scrolled away from.                                                    */

.summary {
  position: sticky; top: calc(var(--sticky) + var(--sp-6));
  padding: var(--sp-6);
  background: var(--field); color: var(--field-fg);
  border-radius: var(--r-tile);
}
.summary__head {
  margin-bottom: var(--sp-4); padding-bottom: var(--sp-4);
  border-bottom: 1px solid var(--field-line);
  font-size: var(--fs-h3); color: var(--field-ink);
}
.summary__row {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3);
  margin-bottom: var(--sp-3); font-size: var(--fs-small);
}
.summary__row .price { color: var(--field-ink); }
.summary__row .price i { color: var(--field-fg); }
.summary__row--total {
  margin: var(--sp-4) 0 var(--sp-5);
  padding-top: var(--sp-4); border-top: 1px solid var(--field-line);
  font-size: var(--fs-h4); color: var(--field-ink);
}
/* The one champagne figure on the summary panel, because it is the number the
   whole screen is about. --signal-lit rather than --signal-ink: this sits on the
   field, where the brighter of the two belongs. */
.summary__row--total .price { color: var(--signal-lit); font-size: var(--fs-h2); }
.summary__row--total .price i { color: var(--field-fg); }
.summary .btn {
  background: var(--signal); border-color: var(--signal);
  min-height: var(--btn-h-lg);
}
.summary .btn:hover { background: var(--signal-lit); border-color: var(--signal-lit); color: var(--field); }
.summary .btn[disabled] { background: none; border-color: var(--field-line); color: var(--field-fg); }
.summary__warn {
  margin: 0 0 var(--sp-4); padding-left: var(--sp-3);
  border-left: 3px solid var(--signal-lit);
  font-size: var(--fs-small); color: var(--field-ink);
}
.summary__note {
  margin: var(--sp-4) 0 0;
  font-size: var(--fs-tiny); line-height: 1.6; color: var(--field-fg);
}
.summary__back {
  display: inline-block; margin-top: var(--sp-4);
  font-size: var(--fs-small); color: var(--field-fg);
  text-decoration: underline; text-underline-offset: 3px;
  text-decoration-color: var(--field-line);
}
.summary__back:hover { color: var(--field-ink); text-decoration-color: var(--signal-lit); }

/* An empty basket is an invitation to act, so it carries the control that acts. */
.nothing--cart .btn { margin-top: var(--sp-2); }

.totals {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-4);
  padding: var(--sp-6) 0; border-bottom: 1px solid var(--line-2);
}
.totals__label { font-variation-settings: var(--sign-lg); font-size: var(--fs-h2); font-weight: 700; }

.pick2 { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr); gap: var(--sp-3); margin-bottom: var(--sp-5); }
.pick2 label, .addrpick {
  display: block; padding: var(--sp-4); cursor: pointer;
  border: 1px solid var(--line); border-radius: var(--r-ctl); background: var(--surface);
  transition: border-color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.pick2 label:hover, .addrpick:hover { border-color: var(--line-2); }
/* Chosen is a doubled rule AND a change of ground, never colour alone. */
.pick2 label:has(input:checked), .addrpick:has(input:checked) {
  border-color: var(--field); background: var(--paper-2);
  box-shadow: inset 0 0 0 1px var(--field);
}
.pick2 b { display: block; margin-bottom: 2px; font-weight: 600; }
/* --ink-2: the chosen card's ground is --paper-2, where --ink-3 measures
   4.35:1. --ink-3 clears on --paper and nowhere else; see tokens.css. */
.pick2 small { font-size: var(--fs-small); color: var(--ink-2); }
.addrpick { margin-bottom: var(--sp-2); }
.addrpick address { font-style: normal; margin: var(--sp-1) 0 0 26px; font-size: var(--fs-small); color: var(--ink-2); }

/* ---- how the goods reach the customer -----------------------------------

   ONE OBJECT, TWO CONTENTS. Delivered shows the rate card; Collect shows the
   counter's address and, where a shop publishes them, its opening times. They
   answer the same question and they are the two halves of a single radio pair,
   so they are the same block on screen rather than a filled card against a
   hairline aside. */
.factbox {
  margin-top: var(--sp-5); padding: var(--sp-4) var(--sp-5);
  background: var(--paper-2); border-radius: var(--r-ctl);
}
.factbox__head { margin: 0 0 var(--sp-1); font-size: var(--fs-small); font-weight: 700; color: var(--ink); }
/* --ink-2, not --ink-3: the ground here is --paper-2, where the quiet ink drops
   under the floor. It clears on the page itself and nowhere else. */
.factbox > address { margin: var(--sp-2) 0 0; font-size: var(--fs-small); line-height: 1.7; color: var(--ink-2); }

/* ---- the list inside it -------------------------------------------------

   BOTH CONTENTS ARE THE SAME LIST: the delivery zones against their fees, and
   the days against their opening times. Both were laid out with `.pair`, which
   wraps like prose — the right behaviour in a 190px footer column and the wrong
   one for a column of values meant to be compared. The fees started at three
   different left edges and Muttrah's two opening times at two. A list of values
   that does not line up is a list somebody has to read twice.

   THE RATE CARD IS ON THE CHECKOUT AND NOWHERE ELSE ON THE SHOP — what it costs
   to send something is a question asked when somebody is about to buy, and it
   was cluttering the front page. Nothing in it is added to the total and the
   copy says so: no zone is matched against the customer's address, because the
   shop settles delivery on the telephone, and a figure that looks like part of
   the bill and is not is worse than no figure at all. */
.factbox__list { margin: 0; }
.factrow {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--sp-4); align-items: baseline;
  padding: var(--sp-3) 0; border-top: 1px solid var(--line-2);
}
.factrow__k { margin: 0; font-size: var(--fs-small); color: var(--ink); }
/* --ink-2, not --ink-3: this sits on --paper-2, where the quiet ink drops under
   the floor. The quiet ink clears on the page itself and nowhere else. */
.factrow__note { display: block; margin-top: 2px; font-size: var(--fs-tiny); color: var(--ink-2); }
.factrow__v { margin: 0; font-size: var(--fs-small); color: var(--ink); }
/* TABULAR FIGURES ON MONEY ONLY, and nowrap with them. A column of fees that
   does not line up digit for digit is the fault being fixed; an opening time is
   longer than the day beside it and has to be allowed to wrap on a phone. */
.factrow--money .factrow__v {
  font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap;
}
.factbox__foot { margin: var(--sp-3) 0 0; font-size: var(--fs-tiny); color: var(--ink-2); }

/* The gap between the two blocks of the checkout's left column, which used to
   be an inline style on the second `<section>`. */
.cartgrid .panel + .panel { margin-top: var(--sp-6); }

.sum { display: flex; justify-content: space-between; gap: var(--sp-4); padding: var(--sp-2) 0; font-size: var(--fs-small); color: var(--ink-2); }
.sum--total {
  margin-top: var(--sp-3); padding-top: var(--sp-4); border-top: 1px solid var(--line-2);
  font-size: var(--fs-h4); font-weight: 600; color: var(--ink);
}

/* The order's progress as a line of type. Filled segments were four coloured
   boxes in a row — the same component-library tell as the chips. */
.steps {
  display: flex; gap: var(--sp-5); flex-wrap: wrap;
  margin: 0 0 var(--sp-8); padding: 0 0 var(--sp-3);
  list-style: none; border-bottom: 1px solid var(--line);
}
.steps li { white-space: nowrap; font-size: var(--fs-small); color: var(--ink-3); }
.steps li[data-on="1"] { color: var(--ink); font-weight: 600; }
.steps li[data-on="cancelled"] { color: var(--error); font-weight: 600; }

/* ==========================================================================
   RESPONSIVE — each size is designed, not scaled down from the one above.
   ========================================================================== */

/* The well rotation no longer changes per breakpoint: a five-step cycle cannot
   line up with 4, 3 or 2 columns. See .cell. */
@media (max-width: 1180px) {
  .grid, .skeleton { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* THE BAND STACKS BEFORE THE COLUMNS GET TIGHT. Two columns of type under a
   photograph works down to about 900px; below that the statement is wrapping
   every three words and the lead product has no room for a price and a button
   on one line. */
@media (max-width: 900px) {
  /* THE PHOTOGRAPH STAYS THE GROUND HERE TOO. It used to become a block of its
     own above the band — a 4:3 picture, then a panel — which was two things
     stacked where the desktop had one. Now the stage is a screen of photograph
     with the shop set over the bottom of it, which is what a hero looks like on
     a phone and what the band was made translucent for. */
  .stage { min-height: clamp(560px, 88vh, 720px); }
  .hero h1 { max-width: none; }
  .detail { grid-template-columns: minmax(0, 1fr); }
  .detail__body { position: static; }
}



@media (max-width: 640px) {
  .grid, .skeleton { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-6) var(--sp-3); }
  .blocks, .cats { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3); }
  .block, .cat { aspect-ratio: 1; border-radius: var(--r-ctl); }
  .block__name, .cat__name { font-size: 16px; }
  .stage__in { padding-block: var(--sp-6) var(--sp-8); }
  /* TWO COLUMNS, NOT FOUR STACKED ROWS. At one column the strip was 380px of
     shop facts between the statement and the first product — which is the
     opposite of why it was moved up here. */
  /* The card carries the inset at every width; only its size changes. */
  .stage__in {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp-6);
    padding-block: var(--sp-8);
  }

  .cell { padding: var(--sp-2); }
  .cell__body, .cell__price { padding-inline: 0; }

  /* The masthead stacks into two lines, and which two matters. The name takes
     the first on its own — it is the wordmark, and truncating it to make room
     for a Sign in link is the wrong trade. Search, account and basket share the
     second, where a thumb reaches all three. */
  .top__in { flex-wrap: wrap; align-items: center; gap: var(--sp-2) var(--sp-3); padding-block: var(--sp-5) var(--sp-3); }
  .brand { flex: 1 0 100%; }
  /* A DEFINITE BASIS, or the row wraps a third time. `flex: 1 1 auto` bases the
     search on its own max-content — the placeholder plus the word Search — which
     is wider than what is left of a 390px screen once the account and the basket
     have taken theirs, so it wrapped rather than shrinking and the basket ended
     up alone on a line of its own. */
  .search { order: 2; flex: 1 1 120px; margin-left: 0; padding-inline: var(--sp-3) var(--sp-1); }
  .search button { padding-inline: var(--sp-2); }
  .acct { order: 3; padding-inline: var(--sp-2); }
  .cartlink { order: 4; padding-inline: var(--sp-2); }

  /* THE TILE KEEPS ITS PLATE AND DROPS ITS NAME. The comment on `.search` above
     records what this row costs: search, account and basket share the second
     line, and anything wider wraps it a third time and leaves the basket alone.

     Three loose controls could not have fitted here at all — which is most of
     why they became one. A monogram and a caret is about 60px, narrower than
     the Sign in link it replaces, and the name is one tap away inside it. */
  .acctbox { order: 3; }
  .acctbox__btn { padding-inline: var(--sp-1) var(--sp-2); }
  /* HIDDEN FROM THE EYE, NOT FROM THE ACCESSIBILITY TREE. `display: none` here
     would leave a button whose only remaining content is an `aria-hidden`
     monogram and a caret — a control a screen reader announces as "button" and
     a voice user cannot name at all. This is the same treatment `.sr` gives,
     applied by a breakpoint rather than by a class. */
  .acctbox__name {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }

  /* Full width, and it comes up from the bottom rather than in from the side:
     on a phone the reachable edge is the bottom one. */
  .rail {
    inset: auto 0 0 0; width: auto; max-height: 82vh;
    padding: var(--sp-6) var(--gutter) var(--sp-10);
    border-left: 0; border-top: 3px solid var(--field);
  }
  .rail[data-open="true"] { animation: sheet-up var(--t-base) var(--ease-spring); }
  @keyframes sheet-up { from { transform: translateY(22px); opacity: 0; } }

  /* The summary stops being a column and becomes the last thing on the page,
     which is where a thumb reaches it. */
  .cartgrid { grid-template-columns: minmax(0, 1fr); gap: var(--sp-8); }
  .summary { position: static; }
  .line { grid-template-columns: 84px minmax(0, 1fr); }
  .line__img { width: 84px; }
  .line__right { grid-column: 1 / -1; justify-items: start; text-align: left; }
  .pick2, .f--pair { grid-template-columns: minmax(0,1fr); }
  .sheet { padding-top: var(--sp-10); }
  .buy { position: sticky; bottom: 0; z-index: 20; background: var(--paper); padding-block: var(--sp-3); }
  /* AND THE FLOATER LIFTS CLEAR OF IT. A circle sitting on top of "Add to
     basket" on a phone is worse than no circle — the buy bar is the control
     this whole page exists for. */
  body:has(.buy) .totop { bottom: calc(var(--sp-5) + 72px); }
}

/* A hover state on a touch screen fires on TAP and then stays until something
   else is touched. */
@media (hover: none) {
  .cell:hover, .cell:focus-within { transform: none; box-shadow: none; }
  .cell__link:hover .cell__img img { transform: none; }
  .cell__link:hover .cell__name { text-decoration: none; }
  .block:hover, .cat:hover { transform: none; box-shadow: none; }
  .block:hover .block__img img { transform: none; }
}

@media (pointer: coarse) {
  /* .add--card is not here any more: it is a full-width 44px button at every
     width now, and inline-flex would undo the block it is laid out as. */
  .pager button, .linkish, .addr__acts button {
    min-height: 44px; display: inline-flex; align-items: center;
  }
  .opt__row button { min-height: 46px; }
}

/* ==========================================================================
   LESS MOTION, WHEN SOMEBODY HAS ASKED FOR IT.

   THIS FILE HAD NO HANDLING FOR THIS AT ALL — zero occurrences of the query
   before it was added. The stylesheet has carried a drawer animation, a zoom on
   every product photograph under the pointer, a lift on every tile and a smooth
   scroll back to the top all along, and a reader who has told their system they
   want less movement was getting every one of them.

   `prefers-reduced-motion: reduce` is not "no animation" — it is a request not
   to be moved through space. So transitions of COLOUR are left alone and
   everything that TRANSLATES, ZOOMS or SLIDES is cut to nothing: the entrance
   lands finished, the drawer is simply there, the photograph does not creep
   under the pointer.

   Written with the near-zero durations rather than `animation: none`, because
   `none` on an element whose animation carries its final state — anything using
   `backwards` — leaves it at the pre-animation value, which for the hero is
   invisible.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    /* THE DELAYS GO TOO, and leaving them out was a real defect rather than an
       omission. An entrance using `backwards` fill holds its PRE-animation
       state through its delay — so with the duration cut to nothing and the
       delay left at 640ms, the hero stayed invisible for two thirds of a second
       and then snapped in. Measured on the live page: dur=1e-05s delay=0.64s.
       That is worse than the animation it was meant to spare somebody. */
    animation-delay: .01ms !important;
    transition-duration: .01ms !important;
    transition-delay: .01ms !important;
    scroll-behavior: auto !important;
  }
  /* The two that are TRANSFORMS rather than durations, so the rule above does
     not reach them: a tile that still jumps a pixel and a photograph that still
     scales, both instantly, are worse than neither moving. */
  .cell:hover, .cell:focus-within { transform: none; }
  .cell__link:hover .cell__img img { transform: none; }
}
