/* site/app/screens/proshop.css — the pro shop.
 * Depends on tokens.css + ui.css + shell.css. Nothing here defines a colour, a duration or an
 * easing curve of its own; a hex code in this file is a bug, because it will not follow the
 * palette into dark mode and the app is used courtside at night.
 *
 * The shape is a conventional storefront on purpose (Syd: "a dupe of a shopify store... JUST
 * so i can give me comments"). What is NOT conventional is the payment gate: the cart is
 * styled as a cart the whole way to the pay button, and only the paid order gets the navy
 * card treatment that the rest of the app reserves for a thing that is real.
 */

/* ── View push ────────────────────────────────────────────────────────────── */
/* ⚠ GATED ON [data-ready]. boot.js stamps that after the first frame; without the gate the
   whole shop slides into place on load as its initial styles resolve, which reads as jank on
   the slow phones that are actually at the venue. */
/* ⚠⚠ THE CONTAINER IS THE SCROLLER, NOT THE PANE, AND THE REASON IS `position: fixed`. In
   Chromium before 127 (and the Android emulator's Chrome 124 is exactly that), `container-type`
   still carried layout containment — which makes the element the containing block for its
   FIXED descendants. The commit dock and the sheets are fixed children of the pane, and inside a
   contained pane they anchored to the pane's box instead of the viewport: on a phone that box
   runs 40–56px under the browser's bar, and the scrim landed under the tab bar. Measured on
   the emulator before this moved: scrim [0,12,412,842] against a viewport 786 tall. Nothing
   fixed lives inside the scroller, so the query container can be there without a cost. */
.shop > .pane-scroll{ container: shop-screen / inline-size; }

.shop .shop-view{ opacity: 1; transform: translate3d(0, 0, 0); }
[data-ready] .shop .shop-view{
  transition: transform var(--t-menu) var(--ease-out),
              opacity var(--t-menu) var(--ease-out);
}
/* ⚠ THE RESTING STATE IS ABOVE, THE OFFSET IS THE STARTING STYLE — not the other way round.
   The first version set the offset inline in JS and cleared it in requestAnimationFrame,
   which makes the screen's correct layout conditional on a callback of ours running; rAF is
   suspended in a backgrounded tab, so a view painted while the app was in the background
   measured 12px across and transparent until the tab came back. Same pattern as .enter in
   tokens.css, and a transition can still be interrupted and retargeted where a @keyframes
   would restart from zero. */
@starting-style{ .shop .shop-view.from-right{ opacity: 0; transform: translate3d(12px, 0, 0); } }
@starting-style{ .shop .shop-view.from-left{ opacity: 0; transform: translate3d(-12px, 0, 0); } }

/* ── Top bar ──────────────────────────────────────────────────────────────── */
/* ⚠ IT IS THE TITLE AND NOTHING ELSE NOW. The cart used to sit in this flex row, which is why
   the row carried its own `padding-right: 20px` — that padding is now WRONG, because
   `.pane .screen-head` already reserves 76px for whatever occupies the corner and the two
   would stack into a title that wraps early for no visible reason. */
.shop .shop-topbar{ display: block; }

.shop .shop-backbar{
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-3);
  padding: 4px var(--gutter) 2px;
}
/* ⚠ NOT ORANGE, AND cafe.css ALREADY SAYS WHY IN ITS OWN WORDS. Going back is navigation,
   not the action — and the action colour is already on this view, worn by Add to cart or Pay.
   It also could not be read: 15px orange on the cream page is the lowest-contrast text on the
   screen, so the two shops disagreed and the pro shop was the one that was wrong. */
.shop .shop-back{
  display: inline-flex; align-items: center; gap: 3px;
  border: 0; background: transparent; cursor: pointer;
  padding: 8px 10px 8px 4px; margin-left: -4px; border-radius: var(--r-full);
  color: var(--ink-secondary);
  font: 600 15px/1 var(--font-ui); letter-spacing: -.008em;
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
  min-height: var(--tap-min);
}
.shop .shop-back svg{ width: 18px; height: 18px; }

/* ── The cart, as the floating pill ─────────────────────────────────────────
 * ⚠⚠ THE POSITION AND THE SKIN ARE shell.css's `.commitdock` / `.commitpill`, shared with the
 *   Courts screen and the Cafe. Everything that used to be here — the corner offsets, the
 *   solid fill, the ring, the badge — went with the corner button on 2026-09-04. Only the two
 *   parts that are this shop's own are left.
 * ⚠ IT CANNOT LIVE AT THE BOTTOM AND ALSO BE A SECOND CONTROL. The old note here said the cart
 *   could not move to the bottom because "the floating nav already owns that band, and two
 *   floating things fighting for the same thumb is how a customer taps Book when they meant to
 *   check out". That was true when the band was undefined; it is not any more — the band is
 *   `.commitdock`, it leaves room for the nav, the code button and the live pill by
 *   construction, and the live pill moves aside when a pill appears in it.
 */
/* The count reads as a number, not a mark: solid page colour rather than the 16% wash a glyph
   gets in that disc, or the digit sinks into it. Same decision as the Cafe's. */
.shop .shop-cart-n {
  background: var(--bg); color: var(--ink);
  font: 600 14px/1 var(--font-ui);
}
/* ⚠ NO MINIMISED SIZES OF ITS OWN ANY MORE — the dock scales with the bar; see cafe.css. */
/* ⚠ THE ONE ORANGE THING ON THE PILL, and it is the action — DESIGN.md's rule. It is what
   makes this read as a step rather than a status. */
.shop .shop-cart-cta {
  flex: none; padding: 8px 14px; border-radius: var(--r-full);
  background: var(--action); color: var(--action-ink);
  font: 600 13px/1 var(--font-ui);
}

/* ── Product art ──────────────────────────────────────────────────────────── */
/* ⚠ NO PHOTOGRAPHS. site/_headers has no img-src for anything off-origin, so a stock photo
   URL is a silently broken box. These are flat brand shapes drawn inline, and they inherit
   the theme instead of being a picture that is wrong in the dark. */
.shop .shop-art{ width: 78%; height: auto; display: block; }
.shop .shop-art .f-fig{ fill: var(--ink); }
.shop .shop-art .f-soft{ fill: var(--ink); opacity: .28; stroke: var(--ink); }
.shop .shop-art .f-cut{ fill: var(--bg-sunken); stroke: var(--bg-sunken); }
/* Chartreuse appears on exactly one object in this screen: the ball. DESIGN.md §2 —
   chartreuse is energy and the ball, never decoration and never "success". */
.shop .shop-art .f-live{ fill: var(--live); }

/* ── The grid ─────────────────────────────────────────────────────────────── */
/* ⚠⚠ 20px, LIKE EVERY OTHER GUTTER IN THE APP, AND IT WAS 16 — WHICH IS THE WHOLE OF SYD'S
   "margins not working" ON THIS TAB (2026-09-04). shell.css sets `.sec-head { padding: 0 20px }`
   and `.screen-head { padding: 6px 20px 12px }`, so the section heading "PADDLES" sat four
   pixels INSIDE the cards it was labelling, on every section, down the whole page. Four pixels
   is not a thing anybody can name on sight and it is exactly what makes a screen look slightly
   broken without anyone being able to say why. Every page-level block in this file is on 20
   now; the numbers INSIDE a card (its own padding) are unchanged and have nothing to do with
   the page's edge. */
.shop .shop-grid{
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px; padding: 0 var(--gutter);
}

/* ⚠ FLEX COLUMN, ART flex:none. As a plain block the card stretches to its row-mate's height
   and the square art stretches with it, so two paddles side by side came out at visibly
   different sizes whenever one name wrapped to a second line. */
.shop .pcard{
  display: flex; flex-direction: column;
  width: 100%; padding: 0; border: 0; text-align: left; cursor: pointer;
  background: var(--bg-raised); border-radius: var(--r-lg);
  box-shadow: var(--shadow-1); overflow: hidden;
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
  font-family: var(--font-ui); color: var(--ink);
  transition: transform var(--t-press) var(--ease-out), color var(--t-press) var(--ease-out);
}
.shop .pcard-art{
  position: relative; flex: none; display: grid; place-items: center;
  aspect-ratio: 1 / 1; background: var(--bg-sunken);
}
/* Sold out dims the picture AND says the words. Colour alone would be invisible to a checker,
   to a colourblind player, and in direct sun on a court. */
.shop .pcard[data-out] .pcard-art .shop-art{ opacity: .34; }
.shop .pcard[data-out] .pcard-name{ color: var(--ink-tertiary); }
.shop .pcard-flag{ position: absolute; left: 8px; bottom: 8px; }

/* ⚠ "2 LEFT" AND "CART · NOT PAID" ARE STATUS, NOT THE ACTION — AND THEY WERE THE QUIETEST
   TEXT ON THE CHECKOUT SCREEN. shell.css paints .tag.due in --action, which is orange at 11px
   on a 16% orange tint: the lowest-contrast pair in the app, on the one badge the payment-first
   rule needs a customer to actually read. Amber is a different hue from the pay button, so the
   two can never be confused, and it survives being read in sunlight.
   ⚠ THE SCOPED OVERRIDE THAT USED TO LIVE HERE IS GONE. It guessed the palette would name
   the token --attention; it shipped as --attn, so `var(--attention, var(--warn))` never
   resolved and quietly fell through to plain --warn — the right idea rendered in the wrong
   role's colour, on every screen but this one still orange. The fix now lives on .tag.due in
   shell.css using --attn-bg/--attn-ink, so it applies to every unpaid-cart badge. Nothing
   .shop-specific is needed. */

.shop .pcard-body{ flex: 1; display: block; padding: 10px 12px 13px; }
.shop .pcard-brand{
  display: block; font: 600 10px/1 var(--font-ui);
  letter-spacing: .07em; text-transform: uppercase; color: var(--ink-tertiary);
}
.shop .pcard-name{
  display: block; margin-top: 5px;
  font: 500 14px/1.3 var(--font-ui); letter-spacing: -.006em; color: var(--ink);
}
.shop .pcard-price{
  display: block; margin-top: 6px;
  font: 600 15px/1 var(--font-ui); letter-spacing: -.01em; color: var(--ink);
}

.shop .shop-footnote{
  margin: 2px var(--gutter) 0; padding: 0;
  font: 400 13px/1.5 var(--font-ui); color: var(--ink-tertiary);
}

/* ── Product page ─────────────────────────────────────────────────────────── */
.shop .pdp-art{
  display: grid; place-items: center;
  margin: 6px var(--gutter) 16px; aspect-ratio: 4 / 3;
  background: var(--bg-sunken); border-radius: var(--r-lg);
}
.shop .pdp-art .shop-art{ width: 52%; }

.shop .pdp-head{ padding: 0 var(--gutter); }
.shop .pdp-brand{
  margin: 0; font: 600 11px/1 var(--font-ui);
  letter-spacing: .07em; text-transform: uppercase; color: var(--ink-tertiary);
}
.shop .pdp-name{
  margin: 8px 0 0; font-family: var(--font-display); font-weight: 600;
  font-size: 26px; line-height: 1.14; letter-spacing: -.016em; color: var(--ink);
}
.shop .pdp-priceline{ display: flex; align-items: center; gap: 10px; margin-top: 10px; }
.shop .pdp-price{ font: 600 20px/1 var(--font-ui); letter-spacing: -.014em; }

.shop .pdp-blurb{
  margin: 14px var(--gutter) 0;
  font: 400 15px/1.55 var(--font-ui); color: var(--ink-secondary);
}

.shop .pdp-choosers{ padding: 18px var(--gutter) 0; display: grid; gap: 16px; }
.shop .chooser-label{
  margin: 0 0 8px;
  font: 600 12px/1 var(--font-ui); letter-spacing: .07em;
  text-transform: uppercase; color: var(--ink-tertiary);
}
.shop .chips{ display: flex; flex-wrap: wrap; gap: 8px; }

/* ⚠ CHIPS, NOT A NATIVE <select>. Syd's standing rule, and there is a hard reason as well as
   a stylistic one: an OS select wheel cannot show "Sold out" against a single option, so the
   customer picks US 10, taps through, and only then learns it does not exist. */
.shop .chip{
  display: inline-flex; align-items: center; gap: 7px;
  min-height: var(--tap-min); padding: 10px 15px;
  border: 1.5px solid var(--rule); border-radius: var(--r-full);
  background: var(--bg-raised); color: var(--ink); cursor: pointer;
  font: 500 15px/1 var(--font-ui); letter-spacing: -.006em;
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
  transition: transform var(--t-press) var(--ease-out),
              background-color var(--t-press) var(--ease-out),
              border-color var(--t-press) var(--ease-out),
              color var(--t-press) var(--ease-out);
}
/* Selected is INK-filled, not orange. Orange is the one thing on the screen you are meant to
   press — here that is Add to cart, and a selected chip would be competing with it. */
.shop .chip[aria-checked="true"]{
  background: var(--ink); border-color: var(--ink); color: var(--bg-raised); font-weight: 600;
}
.shop .chip[aria-disabled="true"]{
  color: var(--ink-tertiary); border-style: dashed; cursor: default;
  background: transparent;
}
.shop .chip[aria-disabled="true"] > span:first-child{ text-decoration: line-through; }
.shop .chip-out{
  font: 600 10px/1 var(--font-ui); letter-spacing: .05em; text-transform: uppercase;
  color: var(--ink-tertiary);
}

/* ⚠ THE BUY BUTTON STICKS ABOVE THE FLOATING BAND — the same arrangement as the cafe's
   `.cafe-commit`, for the same reason (see cafe.css): on a phone the product page's last
   control otherwise sits under the live pill at rest. A sticky box can only move inside its
   parent, so the clearance the scroller reserved under the content is reserved by `.pdp`
   itself on the product page, and by the view everywhere else. */
.shop .pane-scroll{ padding-bottom: 0; }
.shop .shop-view{ padding-bottom: var(--fnav-clear); }
.shop .shop-view:has(> .pdp){ padding-bottom: 0; }
.shop .pdp{ padding-bottom: var(--fnav-clear); }
.shop .pdp-buy{
  padding: 22px var(--gutter) 0; position: sticky; bottom: var(--fnav-clear); z-index: 2;
  background: linear-gradient(to top, var(--bg) 72%, transparent);   /* see .cafe-commit */
}
/* A disabled primary button must stop looking like the one thing you are meant to press.
   Orange is the action colour; a sold-out button that keeps it is a lie the eye reads first
   and the word second. */
.shop .shop-view .btn[disabled]{ background: var(--bg-sunken); color: var(--ink-tertiary); cursor: default; }

/* The cart, frozen while a payment is in flight. The customer can still read every line —
   they just cannot change what has already been sent to the payment provider. */
.shop .is-locked{ pointer-events: none; opacity: .5; }
.shop .pdp-gate{
  margin: 10px 2px 0; text-align: center;
  font: 400 13px/1.45 var(--font-ui); color: var(--ink-tertiary);
}

.shop .pdp-spec{ margin: 22px var(--gutter) 0; }
.shop .pdp-spec-row{
  display: flex; gap: 16px; justify-content: space-between;
  padding: 11px 4px; border-bottom: 1px solid var(--rule);
  font: 400 13px/1.4 var(--font-ui); color: var(--ink-secondary);
}
.shop .pdp-spec-row:last-child{ border-bottom: 0; }
.shop .pdp-spec-row > span:first-child{
  flex: none; font-weight: 600; color: var(--ink-tertiary);
  letter-spacing: .04em; text-transform: uppercase; font-size: 11px; padding-top: 2px;
}
.shop .pdp-spec-row > span:last-child{ text-align: right; }

/* ── Cart ─────────────────────────────────────────────────────────────────── */
/* ⚠ ui.css's .row .title / .meta are block elements everywhere else in the app. Here they are
   spans — the row's contents sit inside a <button>-free span so the whole line can hold a
   stepper — and inline spans do not stack: the variant ran on straight after the product
   name as "Ridgeline CX-13 Power 8.1 oz · 4 in". Scoped to .shop-view so this does not reach
   into any other screen's rows. */
.shop .shop-view .row .title, .shop .shop-view .row .meta{ display: block; }

.shop .cart-row{ align-items: flex-start; }
.shop .cart-thumb{
  flex: none; width: 52px; height: 52px; border-radius: var(--r-sm);
  background: var(--bg-sunken); display: grid; place-items: center; overflow: hidden;
}
.shop .cart-thumb .shop-art{ width: 74%; }

.shop .cart-stepper{ display: flex; align-items: center; gap: 4px; margin-top: 9px; flex-wrap: wrap; }
.shop .step{
  width: 32px; height: 32px; flex: none;
  display: grid; place-items: center;
  border: 1px solid var(--rule); border-radius: var(--r-full);
  background: var(--bg-raised); color: var(--ink); cursor: pointer;
  font: 500 17px/1 var(--font-ui);
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
  transition: transform var(--t-press) var(--ease-out),
              background-color var(--t-press) var(--ease-out);
}
/* ⚠ 32px IS UNDER THE 44px FLOOR, so the hit area is grown past the drawn circle instead of
   the circle being grown. A stepper drawn at 44px sits next to a 52px thumbnail and squeezes
   the product name onto three lines on a narrow phone. The pseudo-element is the real target. */
.shop .step{ position: relative; }
.shop .step::before{ content: ""; position: absolute; inset: -7px; border-radius: inherit; }
.shop .step[disabled]{ color: var(--ink-tertiary); background: var(--bg-sunken); cursor: default; }
.shop .cart-qty{ min-width: 26px; text-align: center; font: 600 15px/1 var(--font-ui); }
.shop .cart-cap{
  font: 500 11px/1 var(--font-ui); color: var(--ink-tertiary); margin-left: 4px;
}

/* ⚠⚠ THE ITEMS LIST HAD NO PAGE GUTTER AT ALL. ui.css's `.list` supplies the card — a raised
   ground, a 16px radius and a shadow — and no margin, because it is used inside sections that
   provide their own. The cart and the paid receipt drop it straight onto the page, so at 375px
   it ran 0 → 375 with its rounded corners and its shadow sliced off at both screen edges,
   under a heading and above a totals card that were both inset 20. Every other page-level
   block in this file was moved to 20 on 2026-09-04; this one is rendered from `viewCart` and
   `viewPaid` rather than declared here, which is why it was missed. */
.shop .list { margin: 0 var(--gutter); }

/* ── Fulfilment choice ────────────────────────────────────────────────────── */
.shop .choices{ margin: 0 var(--gutter); display: grid; gap: 10px; }
.shop .choice{
  display: flex; align-items: flex-start; gap: 12px; text-align: left;
  width: 100%; padding: 14px 15px; cursor: pointer;
  border: 1.5px solid var(--rule); border-radius: var(--r-lg);
  background: var(--bg-raised); color: var(--ink);
  font-family: var(--font-ui);
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
  transition: transform var(--t-press) var(--ease-out),
              border-color var(--t-press) var(--ease-out);
}
.shop .choice[aria-checked="true"]{ border-color: var(--ink); }
.shop .choice .grow{ flex: 1; min-width: 0; }
.shop .choice-tick{
  flex: none; width: 22px; height: 22px; margin-top: 1px;
  display: grid; place-items: center; border-radius: 50%;
  border: 1.5px solid var(--rule); color: transparent;
}
.shop .choice-tick svg{ width: 13px; height: 13px; }
.shop .choice[aria-checked="true"] .choice-tick{
  background: var(--ink); border-color: var(--ink); color: var(--bg-raised);
}
.shop .choice-title{ display: block; font: 600 15px/1.3 var(--font-ui); letter-spacing: -.008em; }
.shop .choice-meta{
  display: block; margin-top: 4px;
  font: 400 13px/1.45 var(--font-ui); color: var(--ink-tertiary);
}
.shop .choice-price{ flex: none; font: 600 14px/1 var(--font-ui); margin-top: 2px; }

.shop .addr{
  margin: 10px var(--gutter) 0; padding: 14px 15px;
  background: var(--bg-sunken); border-radius: var(--r-lg);
}
.shop .addr p{ margin: 0; }
.shop .addr-label{
  font: 600 11px/1 var(--font-ui); letter-spacing: .07em;
  text-transform: uppercase; color: var(--ink-tertiary);
}
.shop .addr-name{ margin-top: 8px !important; font: 600 15px/1.3 var(--font-ui); }
.shop .addr-line{ margin-top: 3px !important; font: 400 14px/1.45 var(--font-ui); color: var(--ink-secondary); }
/* Two problems in one control: it was the third orange thing on a screen whose one orange
   thing is Pay, and its box was 21px tall — half the 44px floor, inside a flow that takes
   money. Ink with an underline reads as a link without borrowing the action colour. */
.shop .addr-change{
  display: inline-flex; align-items: center;
  border: 0; background: none; padding: 8px 0 0; margin: 0; cursor: pointer;
  min-height: var(--tap-min);
  font: 600 13px/1 var(--font-ui); color: var(--ink); text-decoration: underline;
  text-underline-offset: 3px;
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}

/* ── Totals and the pay button ────────────────────────────────────────────── */
.shop .totals{
  margin: 0 var(--gutter); padding: 15px 16px;
  background: var(--bg-raised); border-radius: var(--r-lg); box-shadow: var(--shadow-1);
}
.shop .totals-row{
  display: flex; justify-content: space-between; gap: 16px; padding: 4px 0;
  font: 400 15px/1.4 var(--font-ui); color: var(--ink-secondary);
}
.shop .totals-row.is-total{
  font: 600 18px/1.3 var(--font-ui); letter-spacing: -.012em; color: var(--ink);
}

.shop .shop-actions{ padding: 18px var(--gutter) 0; }
.shop .shop-actions .btn + .btn{ margin-top: 10px; }
.shop .paynote{
  margin: 11px 2px 0; text-align: center;
  font: 400 13px/1.5 var(--font-ui); color: var(--ink-tertiary); text-wrap: pretty;
}

/* ── The paid order ───────────────────────────────────────────────────────── */
/* ⛔ THE NAVY CARD IS RESERVED FOR A THING THAT IS REAL. Everything before payment is drawn
   on cream, because navy is the app's identity surface — a ticket, a live board, a paid
   order. Styling a cart this way would tell the customer they own something they do not. */
/* ⚠ INK ON THIS CARD IS MIXED FROM --cream, NOT WRITTEN AS A HEX.
   These four values were #9FB3C6, #C9D6E2, rgba(246,248,237,.16) and #B9F2D0. The card is
   navy in BOTH themes, so the theme-aware --ink-secondary cannot be used here (in light mode
   it is a dark blue, i.e. invisible) — which is why the hexes went in. But a hex is a copy of
   the brand colour that stops tracking it: change --cream and this card keeps the old one,
   silently, on the one screen that says a customer's money arrived. bookings.css hit the same
   wall on its ticket and solved it this way; this is the same solution, so the two navy
   surfaces in the app cannot drift apart. */
.shop .paid-card{
  margin-top: 8px;
  --paid-dim:  color-mix(in srgb, var(--cream) 62%, transparent);
  --paid-soft: color-mix(in srgb, var(--cream) 80%, transparent);
  --paid-rule: color-mix(in srgb, var(--cream) 16%, transparent);
}
.shop .paid-top{ display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.shop .paid-date{ font: 500 13px/1 var(--font-ui); }
.shop .paid-kicker{ display: block; margin: 18px 0 0; color: var(--paid-dim); }
.shop .paid-no{
  margin: 4px 0 0;
  font-family: var(--font-display); font-weight: 600;
  font-size: 44px; line-height: 1; letter-spacing: -.03em;
  font-variant-numeric: tabular-nums; color: var(--cream);
}
.shop .paid-name{ margin: 8px 0 0; font: 500 15px/1.3 var(--font-ui); color: var(--paid-soft); }
.shop .paid-card .divider{ background: var(--paid-rule); }
.shop .paid-how{ margin: 0; font: 400 14px/1.5 var(--font-ui); color: var(--paid-soft); }

/* The paid tag has to hold its own on navy; the cream-ground version of .tag.paid is too
   quiet against it. */
.shop .paid-card .tag.paid{
  background: color-mix(in srgb, var(--ok) 26%, transparent);
  color: color-mix(in srgb, var(--ok) 45%, var(--cream));
}

/* ⚠ IN DARK, A NAVY CARD ON A NAVY GROUND IS NOT A CARD. Checked side by side: the receipt
   was floating on nothing but its shadow while the plain .list and .totals below it read
   perfectly, because those use --bg-raised. The navy treatment is a light-mode device for
   saying "this one is real"; in the dark the same job is done by the ground the whole app
   already sits on, plus the order number set in Fraunces at 44px. */
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .shop .paid-card{
    background: var(--bg-raised); border: 1px solid var(--rule);
  }
}
:root[data-theme="dark"] .shop .paid-card{
  background: var(--bg-raised); border: 1px solid var(--rule);
}

/* ── Hover, gated ─────────────────────────────────────────────────────────── */
@media (hover: hover) and (pointer: fine){
  /* An underline, not orange: a hover tint on a 14px product name is unreadable orange text
     on white, and the card is not the action — Add to cart is. */
  .shop .pcard:hover .pcard-name{ text-decoration: underline; text-underline-offset: 2px; }
  .shop .chip:not([aria-disabled="true"]):hover{ border-color: var(--ink-secondary); }
  .shop .choice:hover{ border-color: var(--ink-secondary); }
  .shop .step:not([disabled]):hover{ background: var(--bg-sunken); }
}

/* ── Tablet: the grid gets a third column ─────────────────────────────────── */
/* ⚠ @container, NOT @media, AND THAT IS THE WHOLE BUG. This asked the BROWSER WINDOW how wide
   it was. The app is drawn inside a phone-width frame, so on a tablet the window cleared 620px
   while the frame was still 390 — the grid went to three columns of 111px cards and the
   "SOLD OUT" flag took up 70% of a card. A container query asks the box the grid is actually
   laid out in, which is the frame. Named `shop-screen`, declared on .shop at the top of this
   file, so a container some other stylesheet adds later cannot capture it. */
@container shop-screen (min-width: 620px){
  .shop .shop-grid{ grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .shop .pdp-art{ aspect-ratio: 16 / 9; }
}

/* ── A real account: the shop's frame under "Coming soon" (proshop.js renderSoon) ──────────────
 * Syd, 2026-10-05: "for like cafe and store put a coming soon indicator ... i dont want a blank page".
 * The grid's own boxes — the tools bar, the 38px rail pills under the rail's fade, the two-up cards
 * with their square art, the footnote — with the shell's `.skel` shapes where the words and prices go.
 * Nothing here moves, so the block below has nothing new to name. ⚠ THE TOOLS STICK, as the shop's do
 * (`.shop-tools` below): the preview and the app are "1:1". */
/* (The notice is shell.css's `.soon` on the page gutter, the edge `.shop-search` and the grid are on, so
   it needs no edge of its own here; until 2026-10-07 the shell drew it at 16 and this put it back to 20.) */
.shop .shop-search.is-off { cursor: default; }
/* Safari dims a disabled field's text to ~40%, under the contrast floor on --bg-sunken. */
.shop .shop-search-in:disabled { opacity: 1; cursor: default; -webkit-text-fill-color: var(--ink-tertiary); }
.shop .shop-skel-rail { display: flex; gap: 8px; padding: 0 var(--gutter); overflow: hidden; }
.shop .shop-skel-chip { height: 38px; border-radius: var(--r-full); }
.shop .shop-skel-head { height: 9px; margin: 2px 0 1px; }
.shop .shop-skel-card { cursor: default; }
.shop .shop-skel-brand { height: 7px; }
.shop .shop-skel-name { height: 11px; margin-top: 9px; }
.shop .shop-skel-price { height: 11px; margin-top: 11px; }
/* The rail's arrow as a shape: the 30px disc where `.shop-railnav.is-next` sits, over the rail's fade.
   ⚠ OPAQUE, the skeleton's ink mixed into the page rather than over it: `.skel` is translucent, and the
   chip under the disc showed through it as a second, smaller disc. */
.shop .shop-skel-nav {
  position: absolute; top: 50%; right: var(--gutter); z-index: 2; translate: 0 -50%;
  width: 30px; height: 30px; border-radius: var(--r-full);
  background: color-mix(in srgb, var(--ink) 8%, var(--bg));
}
@media (prefers-contrast: more) {
  .shop .shop-skel-nav { background: color-mix(in srgb, var(--ink) 16%, var(--bg)); }
}
.shop .shop-skel-note { height: 10px; margin-top: 4px; }

/* ── Reduced motion / transparency / contrast ─────────────────────────────── */
/* Naming this file's own animations, per DESIGN.md §4.8: the view push (.shop-view), the
   press scale on cards, chips, choices and steppers. The push becomes a cross-fade — the
   information that the screen changed survives; the sideways movement does not. */
@media (prefers-reduced-motion: reduce){
  [data-ready] .shop .shop-view{ transition-property: opacity; }
  @starting-style{
    .shop .shop-view.from-right, .shop .shop-view.from-left{ opacity: 0; transform: none; }
  }
  .shop .chip, .shop .choice, .shop .step, .shop .pcard{ transition-property: background-color, border-color, color; }
}

@media (prefers-contrast: more){
  .shop .pcard, .shop .totals, .shop .choice, .shop .chip, .shop .step{ border: 1px solid var(--ink); }
  .shop .pcard[data-out] .pcard-art .shop-art{ opacity: .5; }
  /* The two solid discs already sit at full ink-on-page contrast; what they lack is a hard
     edge where they overlap a chip or a card. */
  .shop .shop-railnav{ box-shadow: 0 0 0 2px var(--bg); }
}

/* ══ THE SHOP'S OWN BAR ═════════════════════════════════════════════════════
   Syd, 2026-09-03: "pro shop should have navigation bar of its own some how / search bar".

   ⚠ THE SAME COMPONENT AS THE CAFE'S RAIL, ON PURPOSE. Two long lists in one app that move
     in two different ways is the inconsistency Apple's familiarity principle is about — a
     control that looks the same must behave the same. Sticky, jumps to a section, lights the
     section you are in.
   ⚠ IT STICKS TO 0, AND IT ARRIVES THERE AS THE CART BUTTON LEAVES. app.js hides the code
     button on this tab precisely so the cart can own the top-right corner — and the cart, by
     wearing `.codebtn`, fades out with the title row at 44px of scroll, which is the same
     moment this bar reaches the top of the scrollport. They trade places rather than overlap;
     if the cart is ever made to stay, this bar needs room kept clear on its right. */
.shop .shop-tools {
  position: sticky; top: 0; z-index: 5;
  display: grid; gap: 9px;
  margin: 0 0 14px; padding: 8px 0 9px;
  background: var(--bg);
  border-bottom: 1px solid var(--rule);
}

/* ⚠ A REAL <input type="search"> INSIDE A STYLED SHELL, NOT A DIV PRETENDING. Syd's standing
   rule is about custom DROPDOWNS — a native select is an OS wheel that covers the screen. A
   text field is the opposite case: the OS keyboard, autocorrect, dictation, the clear gesture
   and every assistive technology are all attached to the real element, and none of them can
   be re-implemented. So the element is native and only its chrome is ours. */
.shop .shop-search {
  display: flex; align-items: center; gap: 9px;
  margin: 0 var(--gutter); padding: 0 12px;
  min-height: 42px; border-radius: var(--r-full);
  background: var(--bg-sunken);
  border: 1px solid transparent;
  transition: border-color 160ms var(--ease-out), background-color 160ms var(--ease-out);
}
.shop .shop-search:focus-within { border-color: var(--focus); background: var(--bg); }
.shop .shop-search-i { display: grid; place-items: center; flex: none; color: var(--ink-tertiary); }
.shop .shop-search-i svg { width: 17px; height: 17px; display: block; }
.shop .shop-search-in {
  flex: 1 1 auto; min-width: 0;
  border: 0; background: transparent; outline: none; padding: 0;
  /* ⚠ 16px, NOT 15, AND IT IS NOT A TYPOGRAPHIC CHOICE. iOS Safari zooms the whole page in when
     a field with text smaller than 16px takes focus, and it does not zoom back out on blur.
     Measured on the simulator with this at 15px: the viewport went from 402 to 377 wide, every
     floating control slid 25px left, and the shop stayed that way after the keyboard closed. */
  font: 500 16px/1.2 var(--font-ui); color: var(--ink);
}
.shop .shop-search-in::placeholder { color: var(--ink-tertiary); }
/* Safari draws its own clear button and a decoration on type="search"; both are the wrong
   shape here and the second one cannot be styled at all. */
.shop .shop-search-in::-webkit-search-decoration,
.shop .shop-search-in::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.shop .shop-search-x {
  flex: none; display: grid; place-items: center;
  width: 26px; height: 26px; margin-right: -4px;
  border: 0; border-radius: 50%; cursor: pointer;
  background: color-mix(in srgb, var(--ink) 12%, transparent); color: var(--ink-secondary);
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}
.shop .shop-search-x svg { width: 13px; height: 13px; display: block; }
/* ⚠ THE UA'S `[hidden] { display: none }` IS A BARE TYPE-LESS SELECTOR AND LOSES TO ANY CLASS
   RULE THAT SETS display. `display: grid` above therefore un-hid the clear button, and it sat
   in an empty field offering to clear nothing — visible on the deployed build before anyone
   typed a character. This is why `el.hidden = true` silently fails so often. */
.shop .shop-search-x[hidden] { display: none; }
/* The visual button is 26px because a 44px circle inside a 42px field is the field. The TARGET
   is 44px, extended past the drawing — the same trick the cafe's + on a tile uses. */
.shop .shop-search-x::after { content: ""; position: absolute; inset: -9px; }
.shop .shop-search-x { position: relative; }

/* ⚠⚠ IT WRAPS. IT DOES NOT SCROLL SIDEWAYS, AND THAT IS THE SECOND HALF OF "elements
   clipping" (Syd, 2026-09-04). Three chips measure 424px against a 375px phone, so the last
   one — "Bags, shoes and kit" — was cut through the middle of a word at the screen edge with
   no fade, no arrow and nothing else to say it could be dragged. It read as a rendering fault,
   which is what a clipped word always reads as.
   ⚠ WRAPPING RATHER THAN SHRINKING THE WORDS, because the chip's label IS the section's
     heading further down the page — this file already warns against "a thing called
     'Overgrip 3-pack' in one place and 'Balls and grips' in another". Two lines of chips costs
     38px on the narrowest phone and nothing at all above about 430px, where all three fit.
   ⚠ AND IT MATCHES A DECISION SYD HAS ALREADY MADE ONCE. On the courts timetable: "i dont want
     to scroll, i want it all to be visible and selectable without needing to scroll at all
     horizontally." The same argument holds here — a control you cannot see is a control that
     does not exist. */
/* ── The section rail, and its arrows ─────────────────────────────────────── */
/* SPEC (Syd, 2026-09-04): "the navigation on the proshop needs to have arrows kind of like
   this … carousel-navigator". The reasoning for putting them OVER the track's ends instead of
   flanking it is in proshop.js, where the behaviour lives. */
/* ⚠⚠ min-width: 0, AND WITHOUT IT THE WHOLE PRO SHOP SCROLLS SIDEWAYS. Measured on the
     deployed build at 402px within an hour of shipping the rail: `.shop-rail` computed to
     424px inside a 402px pane, which stretched `.shop-tools` — the grid it sits in — to 424
     too, and with it the search field (20 → 404) and the product grid. The page gained 34px of
     horizontal scroll, so the title, the chips and the cards all sat shifted and clipped.
     Syd: "pro shop can scroll sideways, not recommended and overall proshop is super broken".
   ⚠ THE SCROLLER IS THE CHILD, WHICH IS EXACTLY WHY THIS BITES. `.shop-railtrack` carries the
     `overflow-x`, so ITS min-content is zero — but `.shop-rail` is a plain block between the
     scroller and the grid, so the grid asked IT for a minimum and it answered with its
     content's full width. A grid or flex item's automatic minimum is min-content, and the one
     place that rule is invisible is when the thing that would have absorbed it is one level
     further down. */
.shop .shop-rail { position: relative; min-width: 0; }

/* ⚠⚠ ONE LINE THAT SCROLLS, NOT TWO THAT WRAP. This was `flex-wrap: wrap`, and three sections
     with labels this long ("Bags, shoes and kit") always took both of them — a 38px second row
     inside a bar that is STICKY, so it cost that height at the top of the screen for the whole
     length of the shop. It is also what made the arrows meaningless: nothing was off-screen to
     scroll to.
   ⚠ overscroll-behavior-x: contain — WITHOUT IT, FLICKING THE RAIL PAST ITS END TRIGGERS
     SAFARI'S BACK-SWIPE and the customer leaves the shop. The rail is the only horizontal
     scroller in the app that sits at the left edge of the screen, so it is the only place this
     can happen.
   ⚠ scroll-padding — `scrollIntoView` treats "nearest" as flush with the edge, which is
     exactly where the arrows are; without this the chip it reveals lands underneath one. */
.shop .shop-railtrack {
  display: flex; flex-wrap: nowrap; gap: 8px;
  padding: 0 var(--gutter);
  min-width: 0;
  overflow-x: auto; overflow-y: hidden;
  overscroll-behavior-x: contain;
  scroll-padding: 0 calc(var(--gutter) + 39px);
  scrollbar-width: none; -ms-overflow-style: none;
  -webkit-overflow-scrolling: touch;
}
.shop .shop-railtrack::-webkit-scrollbar { display: none; }

/* The page colour, fading out under each arrow so a chip does not run into the disc. It is a
   MASK, not decoration: without it the arrow sits on live text and both become unreadable. */
/* ⚠ MEASURED FROM THE ARROW, which sits on the page gutter: solid to 20px past the disc's outer edge and
   clear 55px past the screen's, as it was when the disc sat 7px in (62px wide, solid to 44%). */
.shop .shop-rail::before,
.shop .shop-rail::after {
  content: ""; position: absolute; top: -2px; bottom: -2px; width: calc(var(--gutter) + 55px);
  pointer-events: none; opacity: 0;
  transition: opacity 220ms var(--ease-out);
}
.shop .shop-rail::before { left: 0; background: linear-gradient(90deg, var(--bg) calc(var(--gutter) + 20px), transparent); }
.shop .shop-rail::after { right: 0; background: linear-gradient(270deg, var(--bg) calc(var(--gutter) + 20px), transparent); }
.shop .shop-rail.can-prev::before,
.shop .shop-rail.can-next::after { opacity: 1; }

/* ⚠ 30px PAINTED, 44px TAPPED — the same `::after` overlay `.codebtn` uses. A 44px disc on a
     38px chip row would be taller than the row it sits in.
   ⚠ visibility IS PART OF THE HIDE, WITH A DELAY EQUAL TO THE FADE. An arrow at opacity 0 is
     still clickable, and it covers the first chip — so the rail would have an invisible dead
     spot exactly where "Paddles" is. Same pattern, same reason, as the corner button. */
.shop .shop-railnav {
  position: absolute; top: 50%; z-index: 2;
  width: 30px; height: 30px; padding: 0;
  display: grid; place-items: center;
  border: 0; border-radius: var(--r-full);
  background: var(--ink); color: var(--bg);
  box-shadow: var(--shadow-1);
  cursor: pointer; touch-action: manipulation; -webkit-tap-highlight-color: transparent;
  opacity: 0; visibility: hidden; translate: 0 -50%; scale: .8;
  transition: opacity 220ms var(--ease-out), scale 220ms var(--ease-out),
              visibility 0s linear 220ms;
}
.shop .shop-railnav::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: var(--tap-min); height: var(--tap-min);
}
.shop .shop-railnav svg { width: 15px; height: 15px; display: block; }
/* ⚠ ON THE PAGE GUTTER, WHERE THE FIRST AND LAST CHIP REST (2026-10-07). It was 7px from the edge, the
   nearest thing to it on any screen. ⚠ NEVER UNDER 7: the button is 30px and its ::after tap overlay
   is 44, so the overlay hangs 7px past the button on each side. At 6 the right-hand one ended 1px
   outside the pane, which made the whole Shop tab scroll sideways by that pixel. The scroll-padding
   above is the disc's far edge plus 9, so a chip scrolled to never stops under it. */
.shop .shop-railnav.is-prev { left: var(--gutter); }
.shop .shop-railnav.is-next { right: var(--gutter); }
.shop .shop-rail.can-prev .shop-railnav.is-prev,
.shop .shop-rail.can-next .shop-railnav.is-next {
  opacity: 1; visibility: visible; scale: 1;
  transition: opacity 220ms var(--ease-out), scale 220ms var(--ease-out),
              visibility 0s linear 0s;
}
[data-ready] .shop .shop-railnav { transition: transform var(--t-press) var(--ease-out),
                                               opacity 220ms var(--ease-out), scale 220ms var(--ease-out),
                                               visibility 0s linear 220ms; }
.shop .shop-railnav:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }

.shop .shop-railbtn {
  flex: none; white-space: nowrap;
  min-height: 38px; padding: 0 15px; border-radius: var(--r-full);
  border: 1px solid var(--rule); background: var(--bg); color: var(--ink-secondary);
  font: 600 13.5px/1 var(--font-ui); letter-spacing: -.004em; cursor: pointer;
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
  transition: background-color 160ms var(--ease-out), color 160ms var(--ease-out),
              border-color 160ms var(--ease-out);
}
/* Filled, not tinted — the section you are in gets a SHAPE, so it survives greyscale and
   sunlight. Same decision as the selected tab in the floating nav. */
.shop .shop-railbtn[aria-current="true"] {
  background: var(--ink); border-color: var(--ink); color: var(--bg);
}
