/* ==========================================================================
   PddFalcon storefront — site-wide premium layer

   Loaded last, after snap.css, snap-inner.css and home-premium.css, so it
   refines them without !important. Everything here is a token from tokens.css
   or a value built from one.

   Two jobs:
     1. The footer, which is on every page and was the least finished band.
     2. The shared furniture — header, buttons, cards, fields — where a small
        amount of craft is felt on every screen rather than on one.
   ========================================================================== */

/* ==========================================================================
   The newsletter band
   ========================================================================== */

/*
 * The card straddles the footer's top edge.
 *
 * It used to live inside the footer and try to lift itself with
 * `translate: 0 -2.4rem`. Two things stopped it. The reveal animation owns
 * `translate`, so the moment the card came into view its own offset was
 * overwritten — measured `translate: 0px` against a stylesheet asking for
 * -2.4rem. And the footer paints with `contain: paint` and `overflow: hidden`
 * for its watermark, so even a lift that survived would have been clipped at
 * the footer's edge rather than rising above it.
 *
 * So the card is out of the footer now and the band pulls the footer up
 * underneath it with a negative bottom margin — a real layout offset that no
 * animation touches. The top of the card sits on the page, the bottom sits on
 * the navy, and the shadow finally has something light to fall on.
 */
.sp-newsband {
  position: relative;
  /* Later siblings paint on top; the footer is a later sibling. */
  z-index: 2;
  /* The footer used to hold a 4rem margin to separate itself from the page.
     The band stands between them now, so it inherits that job — and the
     footer's margin is zeroed below, because it was silently eating the
     negative pull: measured, -3.5rem of band against +4rem of footer left the
     two 8px apart instead of overlapping. */
  padding-block-start: 4rem;
  margin-block-end: -3.5rem;
}

/* The footer's own content has to clear the part of the card lying over it:
   the 3.5rem of overlap, plus room to breathe. */
.sp-footer { margin-block-start: 0; padding-block-start: 6.25rem; }

@media (max-width: 47.99rem) {
  /* A tall card on a narrow screen would hang too far into the footer, so the
     overlap is a token gesture rather than half the card. */
  .sp-newsband { padding-block-start: 2.5rem; margin-block-end: -2rem; }
  .sp-footer { padding-block-start: 4.25rem; }
}

/* Floating on the page rather than sunk into the footer, the card earns a
   deeper shadow and a lit top edge. */
.sp-news {
  box-shadow: 0 40px 80px -36px rgb(6 16 49 / .55), 0 4px 14px -8px rgb(6 16 49 / .3), inset 0 1px 0 rgb(255 255 255 / .16);
}

/* The title was breaking as "once a / month." because the text column was too
   narrow for it. A little more of the row, and a measure that lets the line
   break where the sentence does. */
@media (min-width: 62rem) {
  .sp-news { grid-template-columns: 1.35fr 1fr; gap: 2.5rem; padding: 2.25rem 2.5rem; }
  .sp-news__title { max-inline-size: 26ch; }
}

/* ==========================================================================
   The category rail on a phone
   ========================================================================== */

/*
 * The rail parks under the header instead of sliding beneath it.
 *
 * Above 62rem the rail is already sticky at the header's height. Below it, it
 * was `position: static` with no stacking of its own, so as the page moved the
 * chips travelled under a sticky header that shrinks from 170px to 118px.
 * Measured at scrollY 80: the header ended at 118 and a chip ran 96 to 140 —
 * 22px of it hidden above, 22px showing below, which is the half-cut pill the
 * client photographed. There is no scroll position where that looks deliberate.
 *
 * Sticking it directly beneath the header removes the state entirely: the rail
 * either sits whole under the header or is not in the way at all. The offset is
 * the header's own measured height, which `chrome-premium.css` keeps in
 * `--sp-header-h` as the header shrinks, so the two stay touching.
 */
@media (max-width: 61.99rem) {
  .sp-nav {
    position: sticky;
    inset-block-start: var(--sp-header-h, 4.6rem);
    z-index: 48;
    background: #fff;
  }
}

/* ==========================================================================
   Section heads on the home page
   ========================================================================== */

/*
 * The rhythm of a stacked head.
 *
 * Measured on "What comes as standard": 8px from the eyebrow to the title, 6px
 * from the title to the sentence under it — and the title's red rule lives
 * inside that 6px, so the rule was touching the text it sits above. The chips
 * beneath had 0px. Four things in 14px of vertical space reads as one crowded
 * block, which is what the client meant by "chipka".
 *
 * Given room, the same four read as eyebrow, heading, sentence, specifics.
 * Scoped to the stacked head so the two-column heads, which have their own
 * balance, are left alone.
 */
.s-home .sp-head--stack .sp-eyebrow { margin-block-end: .75rem; }
.s-home .sp-head--stack .sp-title::after { margin-block-start: .9rem; }
.s-home .sp-head--stack .sp-sub { margin-block-start: 1.1rem; }
.s-home .sp-head--stack .sp-steel__chips { margin-block-start: 1.35rem; }

/* ==========================================================================
   Footer
   ========================================================================== */

/*
 * The top edge.
 *
 * The footer used to begin with nothing but a colour change. A hairline that
 * carries the brand's three inks across the full width says the page has
 * arrived somewhere rather than simply run out.
 */
.sp-footer::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-start: 0;
  block-size: 2px;
  background: linear-gradient(90deg, transparent, var(--s-accent-500) 22%, var(--s-cool-500) 55%, var(--s-brand-500) 80%, transparent);
  opacity: .75;
  z-index: 2;
}

.sp-footer { position: relative; }

/*
 * The columns.
 *
 * At 390 the grid was two 163px columns, which left the link lists 71px wide
 * and pushed the "Talk to us" card 31px past the viewport — clipped rather
 * than scrolled, because the footer paints with `contain`. One column on a
 * phone, and the card gets its full width back.
 */
@media (max-width: 47.99rem) {
  .sp-footer__grid { grid-template-columns: minmax(0, 1fr); gap: 1.75rem; }
  .sp-footer__links { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.5rem 1rem; }
  .sp-footer__card { inline-size: auto; }
  .sp-footer__card .sp-btn { align-self: stretch; text-align: center; justify-content: center; }
}

/* Nothing in the footer may be clipped by the paint containment that the
   watermark needs. Belt and braces after the grid fix above. */
.sp-footer__card,
.sp-footer__links,
.sp-footer__brand { min-inline-size: 0; }
.sp-footer__card * { overflow-wrap: anywhere; }

/*
 * The brand column used to end a hundred pixels above the row beneath it —
 * exactly the dead space the client keeps asking us to remove. The payment
 * strip moved up out of the bottom bar to live here, where it belongs with
 * the rest of the shop's assurances and closes the column off.
 */
.sp-payblock { margin-block-start: 1.25rem; }
.sp-payblock__label {
  margin: 0 0 .5rem;
  font-size: var(--s-text-2xs);
  font-weight: 800;
  letter-spacing: var(--s-tracking-caps);
  text-transform: uppercase;
  color: rgb(255 255 255 / .55);
}
.sp-payblock .sp-pay { gap: .35rem; }
.sp-payblock__note {
  margin: .6rem 0 0;
  font-size: var(--s-text-xs);
  color: rgb(255 255 255 / .5);
}

/* Four badges, one row that never orphans the fourth onto a line of its own. */
.sp-footer__badges { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: .5rem; }
.sp-footer__badges li { min-inline-size: 0; align-items: flex-start; }
@media (max-width: 22.5rem) { .sp-footer__badges { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/*
 * Links answer the pointer with a mark rather than only a colour: a red rule
 * grows in from the left as the row slides. Colour alone is the one change a
 * red-green deficiency cannot see.
 */
.sp-footer__col li { position: relative; }
.sp-footer__col li a { position: relative; padding-inline-start: 0; transition: color .2s var(--sp-ease), padding-inline-start .2s var(--sp-ease); }
.sp-footer__col li a::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: 50%;
  translate: 0 -50%;
  inline-size: 0;
  block-size: 2px;
  border-radius: 2px;
  background: var(--s-accent-500);
  transition: inline-size .2s var(--sp-ease);
}
.sp-footer__col li a:hover { padding-inline-start: 1rem; color: #fff; }
.sp-footer__col li a:hover::before { inline-size: .7rem; }
@media (prefers-reduced-motion: reduce) {
  .sp-footer__col li a,
  .sp-footer__col li a::before { transition: none; }
}

/* The contact card, lit rather than filled. */
.sp-footer__card {
  position: relative;
  overflow: hidden;
  /* As tall as what is in it, not as tall as the link columns beside it.
     A grid item stretches to its row by default, and the row here is set by
     the tallest column of footer links: measured at 1536px the card was 373px
     tall with its last button ending at 453 against a card bottom of 547 —
     about 72px of lit, bordered nothing under the button. */
  align-self: start;
  background: linear-gradient(160deg, rgb(255 255 255 / .09), rgb(255 255 255 / .04));
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .12);
}
.sp-footer__card::after {
  content: "";
  position: absolute;
  inset: auto -30% -40% auto;
  inline-size: 16rem;
  block-size: 16rem;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgb(196 38 12 / .22), transparent);
  pointer-events: none;
}
.sp-footer__card > * { position: relative; }
.sp-footer__line i { box-shadow: inset 0 0 0 1px rgb(255 184 0 / .22); }

/* The newsletter card carries the eye into the footer, so it gets the lift. */
.sp-news { box-shadow: 0 34px 70px -34px rgb(0 0 0 / .75), inset 0 1px 0 rgb(255 255 255 / .14); }

/*
 * The wordmark behind the bottom bar.
 *
 * It was a ghost sitting half under the copyright line. Pushed down and cut
 * off at the baseline it reads as an emboss in the ink — visible when you look
 * for it, invisible when you are reading.
 */
.sp-footer__mark {
  inset-block-end: -.28em;
  color: rgb(255 255 255 / .028);
  letter-spacing: .08em;
}
@media (max-width: 47.99rem) { .sp-footer__mark { font-size: clamp(3.5rem, 22vw, 8rem); } }

/* The bottom bar now carries only who owns the shop and who built it. */
.sp-footer__bottom .sp-wrap { grid-template-columns: 1fr auto; align-items: center; }
@media (max-width: 47.99rem) { .sp-footer__bottom .sp-wrap { grid-template-columns: minmax(0, 1fr); } }

/* ==========================================================================
   Shared furniture
   ========================================================================== */

/*
 * Buttons. A primary action gets a lit top edge and a shadow that tightens
 * when pressed, so a tap reads as a press rather than a colour change.
 */
.sp-btn--primary,
.s-btn--primary {
  box-shadow: 0 10px 22px -12px rgb(196 38 12 / .75), inset 0 1px 0 rgb(255 255 255 / .18);
}
.sp-btn--primary:active,
.s-btn--primary:active {
  box-shadow: 0 4px 10px -8px rgb(196 38 12 / .9), inset 0 1px 0 rgb(255 255 255 / .12);
}

/* Cards lift on a curve rather than a straight line, and their hairline warms
   towards the brand as they rise. */
.sp-card,
.sp-widget,
.sp-cat,
.sp-coll {
  transition: box-shadow .35s var(--sp-ease), translate .35s var(--sp-ease), border-color .35s var(--sp-ease);
}
.sp-card:hover { border-color: rgb(13 36 84 / .14); }

/* The header's search is the one control on every page; give the focused
   state a little more presence without moving anything. */
.sp-search__form:focus-within { box-shadow: 0 0 0 4px rgb(13 36 84 / .10), 0 12px 26px -18px rgb(13 36 84 / .5); }

/* A field that is being filled should feel like the active thing on screen. */
.s-input:focus,
.s-field input:focus,
.s-field textarea:focus,
.s-field select:focus { box-shadow: 0 0 0 4px rgb(13 36 84 / .12), 0 10px 24px -18px rgb(13 36 84 / .45); }

/* Section titles keep their red rule; on hover of a whole shelf the rule
   stretches, which ties the heading to the row it names. */
.sp-section:hover .sp-title::after { inline-size: 3.4rem; }
.sp-title::after { transition: inline-size .35s var(--sp-ease); }
@media (prefers-reduced-motion: reduce) { .sp-title::after { transition: none; } }

/*
 * The payment pills.
 *
 * Six pills needing 440px in a 306px column wrapped four-and-two, which reads
 * as a row that ran out rather than a set. A three-column grid makes it two
 * even rows of three whatever the labels say, and every pill the same width.
 */
.sp-payblock .sp-pay {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: .4rem;
}
.sp-payblock .sp-pay span {
  text-align: center;
  padding-inline: .35rem;
  font-size: .6rem;
  letter-spacing: .02em;
}
@media (max-width: 47.99rem) { .sp-payblock .sp-pay { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

/*
 * Column separators.
 *
 * The merchant's menu gives one column six links and another one, so the row
 * will always be ragged. A hairline between the columns turns the space under
 * a short list into structure instead of absence — the space is still there,
 * but it now reads as a column that ends rather than a hole in the footer.
 */
@media (min-width: 62rem) {
  .sp-footer__links { align-items: stretch; }
  .sp-footer__col + .sp-footer__col { position: relative; padding-inline-start: 1.5rem; }
  .sp-footer__col + .sp-footer__col::before {
    content: "";
    position: absolute;
    inset-block: .2rem 0;
    inset-inline-start: 0;
    inline-size: 1px;
    background: linear-gradient(180deg, rgb(255 255 255 / .12), rgb(255 255 255 / .02) 65%, transparent);
  }
}

/* The separators cost each column 24px, which pushed "Your account" and
   "Help & policies" onto two lines. The link block takes a little more of the
   row back and the inset is trimmed, so every title sits on one line again. */
@media (min-width: 62rem) {
  .sp-footer__grid { grid-template-columns: 1.05fr 2.45fr 1.05fr; }
  .sp-footer__col + .sp-footer__col { padding-inline-start: 1rem; }
  .sp-footer__title { letter-spacing: .03em; white-space: nowrap; }
}

/* Final balance: the contact card holds a postal address, so it gets the wider
   share; the link block keeps enough for one-line titles; the brand column is
   sized to its badges and pills rather than to its paragraph. */
@media (min-width: 62rem) {
  .sp-footer__grid { grid-template-columns: 1fr 2.3fr 1.25fr; }
  .sp-footer__badges span { font-size: .58rem; line-height: 1.15; }
}

/* ==========================================================================
   The bag line — page and drawer share this markup
   ========================================================================== */

/*
 * The line total sits with the name, not at the far end of the stepper's row.
 * Measured on the bag page at 1440: the stepper ended at x=259 and the total
 * began at x=809, so a shopper read 550px of nothing between the thing they
 * were changing and the number it changed.
 */
.s-line__body { display: flex; flex-direction: column; gap: .15rem; min-inline-size: 0; }
.s-line__top { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.s-line__top .s-line__name { min-inline-size: 0; }
.s-line__top .s-line__price { flex: none; font-size: var(--s-text-lg); font-weight: 800; color: var(--s-brand-800); white-space: nowrap; }
.s-line__foot { justify-content: flex-start; gap: 1rem; margin-block-start: .65rem; }
.s-line__foot .s-line__remove { margin: 0; }
.s-line .s-qty { flex: none; }

/* On a phone the stepper and Remove share the row rather than stacking, and
   the total stays on the name's line where the eye already is. */
@media (max-width: 30rem) {
  .s-line__top { flex-wrap: wrap; }
  .s-line__foot { gap: .75rem; }
}


/* ==========================================================================
   The finger, and what the browser paints under it
   ========================================================================== */

/*
 * Measured on every a, button, input and select on the home page: all of them
 * `-webkit-tap-highlight-color: rgba(51, 181, 229, .4)`. That is Android
 * Chrome's own sky blue — the one colour in this shop that belongs to no
 * brand — and it is painted as a hard rectangle over the border box, so the
 * pill-shaped Search button flashes square. Not one of the seventeen sheets
 * had ever set the property.
 *
 * The shop's navy at 6% takes its place. The finger is still answered, in an
 * ink that is ours. Furniture that carries a measured press state of its own
 * takes `transparent` a few rules down, so a press is never told twice.
 */
:root { --sp-tap: rgb(13 36 84 / .06); }

a, button, summary, label,
input, select, textarea,
[role="button"] { -webkit-tap-highlight-color: var(--sp-tap); }

/*
 * Double-tap-to-zoom is why a phone browser waits before it believes a tap. On
 * a control it is never what was meant — nobody zooms by hitting the quantity
 * stepper twice, they are buying two. `manipulation` keeps panning and keeps
 * pinch-zoom, and drops only the double-tap gesture.
 */
a, button, summary, label,
.s-btn, .sp-btn, .s-qty__btn,
input[type="submit"], input[type="button"] { touch-action: manipulation; }

/*
 * The controls that are furniture rather than content. No highlight, because
 * each has its own press state below; no callout, because a long press on the
 * tab bar should not offer to open the shop in a new tab; no selection,
 * because dragging across "Account" should not select the word. Content links
 * — a product name, a policy — are deliberately left out of this list.
 */
.s-bottomnav__item,
.sp-nav__link, .sp-slabs a,
.sp-action, .sp-cart, .sp-search__go,
.sp-iconbtn, .s-icon-btn, .s-qty__btn,
.s-menu__link,
.sp-btn, .s-btn {
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

/*
 * Selected text was the browser's default blue — the second unbranded colour a
 * customer meets. Navy on the page; on the navy surfaces navy would be nothing
 * at all, so the announcement bar, the footer and the drawer's masthead take
 * the deal yellow those same surfaces already close themselves with.
 */
::selection { background: var(--s-brand-800); color: #fff; }
.sp-announce ::selection,
.sp-footer ::selection,
.s-menu__head ::selection { background: var(--s-cool-500); color: var(--s-brand-950); }

/* ==========================================================================
   The lock the drawers have been asking for
   ========================================================================== */

/*
 * `shop.js` runs one overlay controller for the cart drawer, the phone menu,
 * the filter sheet and the quick view, and it puts `s-locked` on <body> the
 * moment any of them opens. No stylesheet has ever defined that class.
 * Measured with the cart drawer open at 390: scrolling the page moved it the
 * full 900px asked for, behind a panel covering the entire screen. A sheet
 * that lets the page slide about underneath it is a web page, not a sheet.
 *
 * Held to a coarse pointer at phone and tablet widths, where the scrollbar is
 * an overlay and taking the page out of flow costs nothing — measured 0px of
 * shift at both 390 and 768. The same rule on a desktop would widen the
 * content from 1425 to 1440 the instant a drawer opened, a 15px jump on every
 * single open (measured in a real window; `scrollbar-gutter: stable` does not
 * prevent it here). A mouse therefore keeps today's behaviour, and the panels
 * contain their own scrolling instead — see the drawer rules below.
 */
@media (max-width: 61.99rem) and (pointer: coarse) {
  body.s-locked { overflow: hidden; }
}

/* ==========================================================================
   The bottom tab bar
   ========================================================================== */

/*
 * A tab took no notice of being pressed. The bar transitions `color` over
 * 200ms and nothing else, so between the finger landing and the next page
 * painting there was no answer at all — on a slow connection, a second of
 * nothing.
 *
 * A plate behind the icon, the way a native tab bar answers. `z-index: -1`
 * puts it behind the icon and the label but still in front of the bar's own
 * white: `.s-bottomnav` is positioned and carries a z-index, so it is a
 * stacking context, and a negative child is clamped to just above its root's
 * background rather than vanishing underneath it.
 */
.s-bottomnav__item::after {
  content: "";
  position: absolute;
  inset: .3rem .45rem;
  z-index: -1;
  border-radius: 13px;
  background: var(--s-brand-800);
  opacity: 0;
  transform: scale(.82);
  transition: opacity .16s var(--sp-ease), transform .16s var(--sp-ease);
}
.s-bottomnav__item:active::after { opacity: .08; transform: scale(1); }
/* `transform` rather than `scale`, so the press replaces snap.css's resting
   lift on the current tab instead of multiplying with it — .92 against 1.08
   composes to .99, which is no press at all. */
.s-bottomnav__item:active svg { transform: scale(.92); }

/*
 * The ring on a tab was being cut in half. The cell is 59px tall and sits
 * flush with the bottom of the screen, so the global 2px outline offset put
 * its lower edge at y=846 on an 844px viewport — two pixels off the bottom of
 * the world. Inset instead, and rounded to the plate it now sits on. The rule
 * here is that a focus ring stays visible, not that it stays where it was.
 */
.s-bottomnav__item { border-radius: 13px; }
.s-bottomnav__item:focus-visible { outline-offset: -4px; }

/* The count reads as something stuck onto the icon rather than floating near
   it, once it has the bar's own white behind its edge. */
.s-bottomnav__count { box-shadow: 0 0 0 2px var(--s-surface); }

/*
 * Portrait is already right: layout.css sizes the bar `calc(height +
 * safe-bottom)` and spends the inset as padding, so the white runs under the
 * home indicator and the five tabs sit above it. Landscape was not. The bar is
 * still on screen at 844x390, the notch has moved to one side, and the first
 * or last tab sits under it.
 */
.s-bottomnav { padding-inline: env(safe-area-inset-left, 0px) env(safe-area-inset-right, 0px); }

/*
 * Two fixed things above the bar measure their offset from its height and
 * forget its inset — `calc(var(--s-bottomnav-height) + 1rem)`, against the
 * back-to-top button's `calc(height + safe-bottom + 1rem)`. All three agree at
 * 76px on a phone with no home indicator and disagree by the whole inset on
 * one that has, so the toast would land on the tab bar it is meant to clear.
 *
 * Latent rather than visible today: the page's viewport meta carries no
 * `viewport-fit=cover`, so `env(safe-area-inset-bottom)` measures 0px even on
 * an iPhone. It is written the way the other three already are, so the day
 * that attribute is added nothing has to be found again.
 *
 * Phone and tablet only — components.css gives the toast region its own
 * desktop offset, where there is no bar to clear.
 */
@media (max-width: 61.99rem) {
  .s-toasts,
  .s-install { inset-block-end: calc(var(--s-bottomnav-height) + var(--s-safe-bottom) + 1rem); }
}

/* ==========================================================================
   The search, and every other field, on a phone
   ========================================================================== */

/*
 * iOS zooms the whole page in when a text field under 16px takes focus, and it
 * does not zoom back out afterwards. Measured: every typing control in the
 * shop is 15px or 13px — the header search, the pincode box, email, password,
 * the filter's min and max, the sort menu. The first thing a customer does on
 * an iPhone is tap the search field at the top of the home page, and the shop
 * lurches under their thumb.
 *
 * 16px at phone and tablet widths only; the desktop keeps the 15px the design
 * was drawn at. Nothing moves: these fields are held open by `min-block-size:
 * 3rem` and the search pill by a fixed `block-size: 2.9rem`, both of which
 * already clear a 16px line — measured, the header is 117.6px tall before and
 * after, and page overflow stays 0 at 360, 390, 768 and 1440.
 */
@media (max-width: 61.99rem) {
  .sp-search__form input,
  .s-search__input,
  .s-input, .s-select, .s-coupon__input,
  .s-field input:not([type="checkbox"]):not([type="radio"]),
  .s-field textarea, .s-field select { font-size: 1rem; }
}

/*
 * The placeholder is the search field's only visible label — the real one is
 * `.s-visually-hidden` — and snap.css paints it a raw #8b93a3 that measures
 * 2.77:1 against the field's own #f1f3f6. The token two sheets earlier was
 * already the right answer, and carries a comment in tokens.css saying it was
 * darkened for precisely this kind of case: --s-muted measures 4.57:1.
 */
.sp-search__form input::placeholder { color: var(--s-muted); }

/* A caret in the brand's red. The smallest possible mark that the thing being
   typed into is this shop and not the browser. */
.sp-search__form input,
.s-input, .s-select, .s-coupon__input,
.s-field input, .s-field textarea { caret-color: var(--s-accent-500); }

/* ==========================================================================
   Buttons: the press, the ring, and the state that refuses one
   ========================================================================== */

/*
 * A defect in this file's own opening pages. The lit shadow set on
 * `.sp-btn--primary` above carries no guard, so a disabled Place order — the
 * state the checkout holds it in while it talks to the payment gateway — kept
 * a 22px red glow under it and went on reading as the live button it had
 * stopped being. Opacity alone was carrying the whole message.
 */
.sp-btn--primary:disabled, .s-btn--primary:disabled,
.sp-btn--primary[aria-disabled="true"], .s-btn--primary[aria-disabled="true"],
.s-btn:disabled, .s-btn[aria-disabled="true"] { box-shadow: none; }

/* `cursor: not-allowed` says nothing at all to a thumb. A press that visibly
   goes nowhere does. (A real `:disabled` never sees `:active`; a link carrying
   `aria-disabled` does.) */
.s-btn[aria-disabled="true"]:active,
.sp-btn[aria-disabled="true"]:active { transform: none; translate: none; }

/*
 * Two controls lost their tap highlight above and had nothing of their own to
 * put in its place. The quantity stepper's press was written once, inside
 * `.s-cartpage`, and the product page's copy of the same control never got it;
 * `.s-icon-btn` answers a pointer but not a finger.
 *
 * `scale` rather than `transform`, so the dip composes with chrome-premium's
 * rotate on the two drawer close buttons instead of cancelling it.
 */
.s-qty__btn:active:not(:disabled) { scale: .9; }
.s-icon-btn:active, .sp-iconbtn:active { scale: .92; }

/*
 * Two rings were being clipped by the box that holds them. The global ring
 * needs 4px of clear space outside the element; a link at the top of the phone
 * menu's scrolling body, and a row in the cart drawer's, have none, so the
 * ring was cut along the scroll edge. Inset, they are whole.
 */
.s-menu__body a:focus-visible,
.s-menu__body button:focus-visible,
.s-cart__body a:focus-visible,
.s-cart__body button:focus-visible { outline-offset: -3px; }

/* ==========================================================================
   The swipeable rails
   ========================================================================== */

/*
 * The category rail is 1176px of pills in 358px of phone, by design. What was
 * not designed is what happens when a swipe runs past either end: at
 * `overscroll-behavior-inline: auto` the leftover motion is handed up to the
 * page, and on iOS a fling against the leading edge of a horizontal scroller
 * is the browser's own back gesture. A customer flicking through categories
 * leaves the shop.
 *
 * `contain` keeps the fling inside the rail. The scroll padding is for the
 * keyboard: tabbing to the pill after the last visible one used to bring it to
 * rest flush against the gutter, half of it under the fade.
 */
.sp-nav__list, [data-navscroll] {
  overscroll-behavior-inline: contain;
  scroll-padding-inline: var(--sp-gutter);
}

/* ==========================================================================
   Drawers and sheets
   ========================================================================== */

/*
 * The filter sheet already contains its own scrolling and wears a thin
 * scrollbar (catalog-premium). The two drawers and the quick view were simply
 * missed — measured `overscroll-behavior-y: auto` and `scrollbar-width: auto`
 * on all three. Scroll the bag to its last line and the page carries on moving
 * behind it; open the bag on a desktop and a full-width system scrollbar runs
 * down the inside edge of a 27rem panel.
 */
.s-cart__body,
.s-menu__body,
.s-modal__panel {
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: rgb(13 36 84 / .22) transparent;
}

/*
 * Both drawers stand the full height of the screen, so on a notched phone
 * their first row is the row under the status bar. The masthead takes the top
 * inset on top of its own padding; the two feet already take the bottom one.
 */
.s-menu__head { padding-block-start: calc(var(--s-4) + env(safe-area-inset-top, 0px)); }
.s-cart[data-cart] .s-cart__head { padding-block-start: calc(var(--s-4) + env(safe-area-inset-top, 0px)); }

/* ==========================================================================
   prefers-reduced-motion — every animation added above, cancelled
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .s-bottomnav__item::after { transition: none; }
  /* The plate is opacity, which is not motion, so the press is still answered.
     The icon's dip is motion, so it goes. */
  .s-bottomnav__item:active svg { transform: none; }
  .s-qty__btn:active:not(:disabled),
  .s-icon-btn:active,
  .sp-iconbtn:active { scale: none; }

  /*
   * `.s-btn` — the one press this sheet reaches for and nobody guards.
   *
   * snap.css's global cancel list names `.sp-btn` and stops there, and the two
   * sheets that actually move `.s-btn` carry no reduced-motion block at all:
   * components.css presses it `translateY(1px)` over `--s-fast`, snap-inner
   * re-declares the same button with `transform .15s`, polish.css lifts the
   * primary `-1px` on hover. Three other premium sheets have since patched it
   * one scope at a time — account-premium for `.s-authbox`, commerce-premium
   * for `.s-summary` and the coupon row — so the shop has been guarding this
   * button piecemeal and missing everywhere else.
   *
   * Measured on the bag at 390 with the reduce flag set: 60ms into a press the
   * primary sat at matrix(1, 0, 0, 1, 0, 0.741) with a transform transition
   * still running — three quarters of the way through a 150ms travel a visitor
   * had asked not to see. This sheet is loaded last and already owns the
   * button's pressed shadow, so the cancel belongs here rather than as a
   * fourth local patch.
   *
   * Only the travel goes. Background, colour and the shadow keep their .2s, so
   * the press is still answered in every way that is not movement — the same
   * line the tab bar's plate is drawn on eight rules above.
   */
  .s-btn { transition: background .2s, color .2s, box-shadow .2s; }
  .s-btn:active:not(:disabled),
  .s-btn--primary:hover:not(:disabled) { transform: none; }
}
