/* =============================================================================
   Ravenspire Games - component CSS
   -----------------------------------------------------------------------------
   Class names are the contract: they are the Toytastic storefront's, so the
   ported templates style without an edit. Values are tokens.css's: there is
   no hex, no bare millisecond and no px font-size below this line, and
   mock/contrast-check.js fails the build if one arrives.

   DARK-FIRST. The page is --rs-ground, raised things are --rs-purple, the
   accent is --rs-gold, and the only light surfaces are the ones tokens.css
   names: inputs, the order summary, the consent banner, the suggest panel.
   On those four the ink flips to --paper-ink and the focus ring to purple;
   everywhere else the ring is gold. That is the whole colour system.

   Type: Reem Kufi for display - the wordmark, headings, prices, buttons,
   the kicker - and Figtree for everything else. Both self-hosted below.
   Figtree ships Light/Regular/Medium only, so nothing here asks Figtree
   for 700: b and strong are 500, and anything heavier is Reem Kufi's job.
   ============================================================================= */

/* ---- faces --------------------------------------------------------------- */
/* local() first so a machine with the font installed never downloads it;
   swap so the fallback stack paints immediately and the face lands over it.
   The storefront serves mock/assets at /assets, hence the absolute URLs. */
@font-face{font-family:"Reem Kufi";font-weight:400;font-style:normal;font-display:swap;
  src:local("Reem Kufi"),local("ReemKufi-Regular"),url("/assets/fonts/ReemKufi-Regular.woff2") format("woff2")}
@font-face{font-family:"Reem Kufi";font-weight:500;font-style:normal;font-display:swap;
  src:local("Reem Kufi Medium"),local("ReemKufi-Medium"),url("/assets/fonts/ReemKufi-Medium.woff2") format("woff2")}
@font-face{font-family:"Reem Kufi";font-weight:700;font-style:normal;font-display:swap;
  src:local("Reem Kufi Bold"),local("ReemKufi-Bold"),url("/assets/fonts/ReemKufi-Bold.woff2") format("woff2")}
@font-face{font-family:"Figtree";font-weight:300;font-style:normal;font-display:swap;
  src:local("Figtree Light"),local("Figtree-Light"),url("/assets/fonts/Figtree-Light.woff2") format("woff2")}
@font-face{font-family:"Figtree";font-weight:400;font-style:normal;font-display:swap;
  src:local("Figtree"),local("Figtree-Regular"),url("/assets/fonts/Figtree-Regular.woff2") format("woff2")}
@font-face{font-family:"Figtree";font-weight:500;font-style:normal;font-display:swap;
  src:local("Figtree Medium"),local("Figtree-Medium"),url("/assets/fonts/Figtree-Medium.woff2") format("woff2")}

/* ---- base ---------------------------------------------------------------- */
*{margin:0;padding:0;box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
/* Smoothing on both engines at once: with only the -webkit half, Firefox on
   macOS sets body copy a weight heavier than every other browser. */
/* 16px on the body, stated as a token so unclassed prose (the error page,
   the confirmation, content pages) sits on the ladder: components that want
   the 14px workhorse say so themselves. */
body{font-family:var(--font-body);font-weight:400;font-size:var(--fs-md);color:var(--ink);background:var(--rs-ground);
     line-height:var(--lh-body);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
     min-height:100svh;display:flex;flex-direction:column}
/* The sticky footer: main takes the slack so a short page still ends at the
   bottom of the viewport. width:100% on a top-level .wrap because an auto
   margin on a flex item's cross axis cancels stretch and the page shrinks
   to its content. */
body > main{flex:1 0 auto}
body > .ftr{margin-top:auto}
body > .wrap{width:100%}
/* THE BOTTOM INSET IS iOS ONLY. The page opts into drawing to the screen's
   edges (viewport-fit=cover, base.html) so the bottom bar, the sheets and
   the consent banner can pad themselves past the iPhone's home indicator,
   which env(safe-area-inset-bottom) reports and which the page really is
   drawn under. Chrome on a three-button Android reports the same inset,
   48px, for a navigation bar the page is NOT drawn under - and only while
   its address bar is showing, so the bar stood 48px too tall and shrank as
   you scrolled (Sean's recording, 19 Sep, measured frame by frame: 116px
   with the address bar, 64px without). So the bottom inset is read through
   this property, which is 0 everywhere but WebKit on iOS: the one engine
   where it has meant the home indicator and nothing else. The side insets
   stay env(): a notch in landscape is real on any phone that has one. */
:root{--inset-b:0px}
@supports (-webkit-touch-callout:none){:root{--inset-b:env(safe-area-inset-bottom,0px)}}
.wrap{max-width:var(--wrap);margin:0 auto;
      padding:0 calc(32px + env(safe-area-inset-right,0px)) 0 calc(32px + env(safe-area-inset-left,0px))}
a{text-decoration:none;color:inherit}
img,svg{max-width:100%;display:block}
button{font:inherit;cursor:pointer;color:inherit}
[hidden]{display:none !important}
b,strong{font-weight:500}
/* Every heading is Reem Kufi. Its ascenders are tall, so headings take the
   tight leading or they read double-spaced. */
h1,h2,h3,h4{font-family:var(--font-display);font-weight:700;line-height:var(--lh-tight);text-wrap:balance}
p,li,figcaption,blockquote{text-wrap:pretty}

/* ---- accessibility primitives -------------------------------------------- */
.vh{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.skip{position:absolute;left:-9999px}
.skip:focus{left:16px;top:16px;z-index:100;background:var(--rs-gold);color:var(--rs-purple);padding:12px 18px;border-radius:var(--r-input);outline:var(--focus-ring);font-family:var(--font-display);font-weight:500}
/* ONE RING, AND ONE RULE FOR WHICH: the ring is gold on every dark surface
   (13.6:1 on the ground, 9.1:1 on a card) and PURPLE on every light one,
   where a gold ring is 1.3:1 to 1.5:1 and simply is not there. The light
   ones are the paper sheet a content page is set on, the lilac and paper
   bands, the order summary, the consent banner, the suggest panel, the gold
   visit band, every input, and every packshot plate. The ring is never in a
   transition list: a ring that fades in is not there when the key lifts. */
:where(a,button,input,select,textarea,summary,[tabindex]):focus-visible{outline:var(--focus-ring);outline-offset:2px;border-radius:4px}
.bsum :focus-visible,.consent :focus-visible,.suggest :focus-visible,.visit :focus-visible,
.pagesheet :focus-visible,.playfree :focus-visible,.discord :focus-visible,
.thumb:focus-visible,.galthumb:focus-visible,.zoomtrig:focus-visible,
.ainput:focus-visible,.search input:focus-visible,.sortbox select:focus-visible,
.qty input:focus-visible,.pricefacet .nums input:focus-visible,.notifyrow input:focus-visible{outline:var(--focus-ring-paper)}
/* A paper input sits on the dark page, so the purple ring would land on the
   ground where it is 1.6:1. Offset inwards: it lands on the paper instead.
   Same for a thumbnail, which is a paper plate on the dark page. */
.ainput:focus-visible,.search input:focus-visible,.sortbox select:focus-visible,
.qty input:focus-visible,.pricefacet .nums input:focus-visible,.notifyrow input:focus-visible,
.thumb:focus-visible,.galthumb:focus-visible,.zoomtrig:focus-visible{outline-offset:-3px}

/* ---- kicker ---------------------------------------------------------------
   The uppercase gold eyebrow. Reem Kufi, tracked, one rung under body; it
   sits above a heading in the hero, the bands and the rails. */
.kicker{display:block;font-family:var(--font-display);font-weight:500;font-size:var(--fs-xs);
        letter-spacing:1.6px;text-transform:uppercase;color:var(--accent-ink);line-height:var(--lh-snug);margin-bottom:8px}

/* =============================================================================
   THE HEADER - ONE BAR, and that is the whole idea
   -----------------------------------------------------------------------------
   The client's own site puts everything on a single PURPLE bar: the raven
   mark at the left, the games straight after it as the navigation, and at
   the far right a gold pill and two gold discs. This is that bar, plus the
   two things a shop needs and a brochure site does not - a search field and
   a wishlist.

   WHAT IT IS NOT, deliberately: three stacked strips - a utility line, then
   [mark | full-width search pill | three labelled icon buttons], then a
   full-bleed mega bar. That silhouette belongs to the storefront this theme
   was ported from, and it is the thing the client recognised. So:

     - the utility strip is GONE. Its one line, "Play for free every
       lunchtime", is stated properly in the USP row, on the Visit page and
       on the Events page; its two links are in the footer and the sheet.
     - the mark is 62px and does real work, instead of a 52px lozenge.
     - the games ARE the nav. Four games, then the two category pillars,
       then Sale. This shop sells four games, not seven generic pillars.
     - search is a 220px field at the right, not the widest object on the
       page, and its submit is a quiet purple glyph rather than a gold disc:
       the gold discs are the icons' now, which is where the dev site has
       them.
     - the mega panel HANGS BELOW the bar as a centred card on --rs-ground-2
       - a step down from the purple bar rather than a second bar.

   Above 1080px that is the whole header. Below it the bar cannot hold six
   pillars and a field without cramming, so the pillars fold into the
   .mobnav sheet and the bar keeps the mark, the discs and search.
   ============================================================================= */

/* Sticky, and padded for the notch: inside an app shell with
   viewport-fit:cover, top:0 is under the status bar. The gold keyline is
   the footer's rule the right way up (9.1:1 against the purple). */
/* THE CHROME. The masthead and the footer are brand surfaces in BOTH themes:
   purple at the top, the deep purple ground at the bottom, whatever the page
   between them is doing. So they are dark surfaces sitting on a light page,
   and their ink must not flip with it.

   Rather than pinning some sixty descendant rules across the two, the ROLES
   are redeclared for each subtree and everything inside inherits them: the
   nav, the mega panels, the icon row and every footer column go on resolving
   --ink, --muted and --accent-ink exactly as they always did, and every
   contrast figure measured for the dark theme still holds inside them. This
   is the whole reason the roles are custom properties, and it is why the
   mega menu needed no changes at all.

   The mega panel comes with the masthead - it hangs off it and reads as part
   of the same chrome - so --rs-ground-2 is restated dark here too. */
/* Everything that sits ON a purple surface, listed once. These are the
   selectors that paint --surface-raised, plus the three bands that are brand
   chrome outright, and they all need the same thing: the dark theme's ink,
   because a purple surface is a dark surface whatever the page around it is
   doing. Adding a purple surface later means adding it here - which is the
   trade for not pinning a colour on every descendant. */
.hdr,.ftr,.hero,.arthero,.brandhero.paired,.brandhero.plain,.visit-map,
.card,.gametile,.catchip,.catgrid .cattile,.campcard,.campcard-copy,.chip,
.fact,.prelovedbox,.anote,.aside,.aorder,.blogcard,.meganav .megacampaign,
.appjoin,.warn,.notifybox,.pagehead,.fixtures .fixture,.pageaside{--ink:var(--chrome-ink);--ink-2:var(--chrome-ink-2);--muted:var(--chrome-muted);
     --faint:var(--chrome-faint);--accent-ink:var(--rs-gold);--price-ink:var(--chrome-ink);
     --surface-raised:var(--rs-purple);--rs-ground:var(--chrome-ground);
     --rs-ground-2:var(--chrome-ground-2);--line-control:var(--chrome-control);
     --line:rgba(255,255,255,.10);--line-2:rgba(255,255,255,.16);
     --focus-ring:3px solid var(--rs-gold)}
/* The dev site's leather grain behind the menu - its own nav texture, drawn
   quieter than the castle band because a sticky bar is furniture, not
   scenery. The purple stays underneath as the loading colour. */
.hdr{position:sticky;top:0;z-index:40;background:var(--rs-purple) url("/assets/nav-bg.jpg") center/cover;border-bottom:3px solid var(--rs-gold);
     padding-top:env(safe-area-inset-top,0px)}
.hdr .wrap{display:flex;align-items:center;gap:18px;min-height:76px}

/* THE MARK. The PNG is 220x197 - raven, moon and wordmark - so it is sized
   by HEIGHT and never by width. Give it a width and a max-height together
   and the aspect belongs to whichever rule wins, which is exactly how the
   footer copy came out squashed. The text fallback the template swaps in
   when the file fails is gold display type. */
.logo{font-family:var(--font-display);font-weight:700;font-size:var(--fs-2xl);color:var(--accent-ink);
      white-space:nowrap;display:flex;align-items:center;line-height:var(--lh-flat);flex:none}
.logo i{font-style:normal;color:var(--ink)}
.logo img{width:auto;max-width:100%;object-fit:contain}
/* OBJECT-FIT IS LOAD-BEARING, NOT DECORATION. `max-width:100%` is what stops
   the mark overflowing a narrow header, but it constrains the WIDTH while the
   height beside it stays pinned - so on a phone whose header grid gives the
   logo column less room than the mark's natural width, the raven was squashed
   rather than scaled. Reported on Android, 14 Sep 2026. `contain` makes the
   constrained box letterbox instead of distort, so the worst case is a
   smaller logo rather than a wrong one. */
.hdr .logo img{height:62px}

/* THE GAMES ARE THE NAVIGATION. Each item fills the bar's height so its
   gold underline lands on the bar's own foot, where the panel starts: no
   dead strip for the pointer to cross on the way down. */
/* flex:none, so a bar that is one pillar too wide shrinks the FIELD and
   never lets the pillars slide under it. */
.nav{display:flex;align-items:stretch;flex:none;align-self:stretch}
.nav > ul{display:flex;gap:20px;list-style:none;align-items:stretch}
.nav > ul > li{display:flex;align-items:stretch}
.nav a{display:flex;align-items:center;padding-top:3px;border:0;border-bottom:3px solid transparent;
       color:var(--ink);background:none;font-family:var(--font-display);font-weight:500;font-size:var(--fs-md);
       white-space:nowrap;
       transition-property:border-color,color;transition-duration:var(--t-fast);transition-timing-function:var(--ease-out)}
.nav a:hover,.nav a.on,.megaitem:hover > a,.megaitem:focus-within > a{border-color:var(--rs-gold);color:var(--accent-ink)}
.nav a:focus-visible{outline-offset:-4px}
/* One rule between the four games and the two category pillars: the games
   are the shop, the rest is what you play them with. The rule is drawn by
   the LINK's pseudo-element, not the item's: .megaitem has to stay
   unpositioned, because the panel inside it sizes itself against .hdr. */
.nav .megaitem:not(.navcat) + .navcat > a{position:relative;padding-left:22px;margin-left:2px}
.nav .megaitem:not(.navcat) + .navcat > a::before{content:"";position:absolute;left:0;top:50%;height:20px;
                                                  margin-top:-10px;border-left:1px solid var(--line-2)}
/* Sale is gold TEXT. The one gold pill in this bar is Events, because that
   is the pill the dev site has in that corner. */
.nav a.hl{color:var(--accent-ink)}
@keyframes rs-livepulse{0%,100%{transform:scale(1);opacity:1}50%{transform:scale(1.45);opacity:.55}}
.nav a.hl .livedot{display:inline-block;width:8px;height:8px;margin-right:8px;border-radius:50%;background:var(--rs-gold);
                   animation:rs-livepulse calc(var(--t-slow) * 8) var(--ease-in-out) infinite}
@media (max-width:1240px){ .nav > ul{gap:15px} }

/* THE PANEL. Hover and focus-within, no script. It is a CARD that hangs off
   the bar rather than a band that spans the window: it stops at the wrap's
   width, keeps the bar's own corners at the top and rounds the two at the
   foot. visibility is DELAYED by --t-hold rather than faded: for that hold
   the panel is transparent but present, so a diagonal slip re-opens it. */
.meganav{position:static}
.megapanel{position:absolute;top:100%;left:50%;translate:-50% 0;width:min(var(--wrap),calc(100% - 24px));
           background:var(--rs-ground-2);border:1px solid var(--line-2);border-top:0;
           border-radius:0 0 var(--r-sheet) var(--r-sheet);box-shadow:var(--shadow-pop);z-index:30;
           visibility:hidden;opacity:0;
           transition-property:opacity,visibility;transition-duration:var(--t-fast),0s;
           transition-timing-function:var(--ease-out),linear;transition-delay:0s,var(--t-hold)}
.megaitem{display:flex;align-items:stretch;align-self:stretch}
.megaitem:hover > .megapanel,.megaitem:focus-within > .megapanel{visibility:visible;opacity:1;transition-delay:0s,0s}
/* THE OPEN NOW WAITS, AND THE TWO WAYS IN ARE SPLIT TO DO IT. A pointer must
   rest on the pillar for --t-open before the panel arrives, so crossing the
   bar on the way somewhere no longer throws a card over the page.

   FOCUS-WITHIN IS DECLARED SECOND AND THAT IS LOAD-BEARING: equal specificity,
   so the later rule wins where a pillar is both focused and hovered. A
   keyboard visitor has not "hovered" anything - they have pressed a key and
   are owed the answer at once. A delay on Tab reads as the Tab having failed.

   No !important anywhere, unlike the sibling fix in the Toytastic tree: this
   file has no reduced-motion rule restating the panel's transition, so plain
   source order is enough. Worth keeping in mind if one is ever added - it
   would have to sit ABOVE these two, or it takes the intent delay back out. */
.megaitem:hover > .megapanel{transition-delay:var(--t-open),var(--t-open)}
.megaitem:focus-within > .megapanel{transition-delay:0s,0s}
/* THE KEYBOARD TOLL-ROAD, closed. :focus-within alone meant tabbing the
   header walked through every link of every pillar's panel - about a
   hundred stops before the search box. nav.js marks a pillar .kbclosed
   when keyboard focus arrives, which beats the reveal above (three simple
   selectors to its two) and, because the panel hides by visibility, drops
   its links out of the tab order entirely: six pillars to cross, ArrowDown
   to enter one, Escape to leave. Hover never carries the class, so mouse
   behaviour is byte-identical. */
.megaitem.kbclosed:focus-within > .megapanel{visibility:hidden;opacity:0}
.meganav .megarow{display:flex;gap:28px;align-items:flex-start;flex-wrap:wrap;min-height:0;padding-top:26px;padding-bottom:28px}
.meganav .megacol{min-width:168px}
.meganav .megacol h3{font-size:var(--fs-xs);letter-spacing:1.4px;color:var(--accent-ink);margin-bottom:12px;text-transform:uppercase;font-weight:500}
.meganav .megacol h3 + ul + h3{margin-top:18px}
.meganav .megacol ul{display:block;list-style:none;gap:0}
.meganav .megacol ul a{display:block;border:0;padding:6px 0;font-family:var(--font-body);font-size:var(--fs-sm);font-weight:500;color:var(--ink);white-space:normal}
.meganav .megacol ul a:hover{color:var(--accent-ink);text-decoration:underline;text-underline-offset:3px}
/* The pillar's own categories are the wide column, set in two text columns
   so ten children read as a block rather than a ladder past the fold. */
.meganav .megacol-wide{flex:1 1 300px;min-width:230px}
.meganav .megacol-wide ul{columns:2;column-gap:30px}
.meganav .megacol-wide li{break-inside:avoid}
.meganav .megafeat{flex:1 1 340px;display:grid;grid-template-columns:repeat(2,minmax(140px,1fr));gap:22px 26px}
.meganav .megafeat .megacol{min-width:0}
/* The lede closes the row: a purple card with the pillar photo on a PAPER
   plate (the packshot rule - a photograph gets a light field), one factual
   line, and the way in. */
.meganav .megalede{margin-left:auto;flex:0 0 236px;background:var(--rs-purple);border-radius:var(--r-card);padding:18px 20px}
.meganav .megalede p{font-size:var(--fs-xs);line-height:var(--lh-body);color:var(--ink-2);margin-bottom:14px}
.meganav .megalede .btn{border:0}
/* A BUTTON IN THE NAV IS STILL A BUTTON. `.nav a` is (0,1,1) and sets
   background:none and color:var(--ink); `.btn` is (0,1,0), so it loses both,
   and every "Shop X" in the mega menu rendered as a plain white link with no
   fill and no affordance. Hover was worse, and worse in a way that reads as a
   bug rather than a style: `.nav a:hover` (0,2,1) won the COLOUR and made it
   gold, while `.btn:hover` (0,2,0) still won the BACKGROUND and made that
   gold too, so the label vanished into its own fill. Stated at (0,2,1) for
   both states, which is the lowest that actually wins. Purple on gold is
   7.2:1; gold on gold is 1:1, and white on gold would be 1.6:1 - the label on
   a gold fill is purple everywhere in this file for that reason. */
.nav a.btn{background:var(--rs-gold);color:var(--rs-purple)}
.nav a.btn:hover,.nav a.btn:focus-visible{background:var(--rs-gold-hover);color:var(--rs-purple)}
.meganav .megapromo .promoart{display:block;height:132px;background:var(--paper-2);border-radius:calc(var(--r-card) - 6px);overflow:hidden;margin-bottom:14px}
.meganav .megapromo .promoart img{width:100%;height:100%;object-fit:contain;padding:6px;
                                  transition-property:opacity;transition-duration:var(--t-fast);transition-timing-function:var(--ease-out)}
.meganav .megapromo .promoart:empty{display:none}
/* A generic promo card inside the panel, kept for a campaign when one runs:
   same box as the lede, gold title, no theme classes. */
.meganav .megapromo:has(> .megacampaign){background:none;padding:0}
.meganav .megacampaign{display:block;background:var(--surface-raised);color:var(--ink);border-radius:var(--r-card);padding:16px 18px 18px}
.meganav .megacampaign .mcart{display:block;height:132px;background:var(--paper-2);border-radius:calc(var(--r-card) - 6px);overflow:hidden;margin-bottom:12px}
.meganav .megacampaign .mcart img{width:100%;height:100%;object-fit:contain;padding:6px}
.meganav .megacampaign .mcart:empty{display:none}
.meganav .megacampaign .mct{display:block;font-family:var(--font-display);font-size:var(--fs-md);line-height:var(--lh-snug);font-weight:700;color:var(--accent-ink);font-variant-numeric:tabular-nums}
.meganav .megacampaign .mcs{display:block;margin-top:5px;font-size:var(--fs-xs);line-height:var(--lh-body);color:var(--ink-2)}
.meganav .megacampaign .btn{margin-top:13px}
/* inline-block so the pill does not take the link's hover underline */
.meganav .offerpill{display:inline-block;margin-left:7px;padding:2px 8px 0;border-radius:var(--r-pill);background:var(--rs-gold);color:var(--rs-purple);
                    font-family:var(--font-display);font-size:var(--fs-2xs);font-weight:700;letter-spacing:.4px;text-transform:uppercase;line-height:var(--lh-snug);vertical-align:middle}

/* SEARCH. A field, not the page's widest object: 220px at rest, and it
   grows to 320 while it has focus so a long query is readable without the
   bar ever being built around it. Paper, because an input is paper here and
   because the header wants one light note in it; the submit is a purple
   glyph on that paper (12.7:1) rather than a gold disc. */
.search{flex:0 1 220px;min-width:150px;position:relative;display:flex;margin-left:auto;
        transition-property:flex-basis;transition-duration:var(--t-base);transition-timing-function:var(--ease-out)}
.search:focus-within{flex-basis:320px}
.search input{width:100%;height:42px;border:2px solid var(--line-control-paper);border-radius:var(--r-input);
              padding:0 44px 0 14px;font-size:var(--fs-sm);color:var(--paper-ink);background:var(--paper);
              transition-property:border-color;transition-duration:var(--t-fast);transition-timing-function:var(--ease-out)}
.search input::placeholder{color:var(--paper-muted);opacity:1}
.search input:focus{border-color:var(--rs-purple)}
.search button{position:absolute;right:2px;top:2px;width:38px;height:38px;border:0;border-radius:8px;
               background:transparent;color:var(--rs-purple);display:flex;align-items:center;justify-content:center}
.search button::after{content:"";position:absolute;inset:-3px}
.search button:hover{background:var(--paper-2)}
.search button:focus-visible{outline:var(--focus-ring-paper);outline-offset:-2px}
@media (pointer:coarse){
  .search input{height:46px;padding-right:48px}
  .search button{width:42px;height:42px}
}

/* THE ICONS are the dev theme's own device: a gold disc with the purple
   glyph it was drawn for (both SVGs ship filled #1e0032). The label is read
   out, not printed - three stacked icon-and-label buttons is the other
   storefront's tell, and a 44px disc is a bigger target than a 20px glyph
   with a caption under it ever was. */
.hicons{display:flex;gap:8px;align-items:center;list-style:none;flex:none}
.hicon{display:flex;align-items:center;justify-content:center;position:relative;
       width:var(--tap-min);height:var(--tap-min);border-radius:50%;
       background:var(--rs-gold);color:var(--rs-purple);
       transition-property:background-color,scale;transition-duration:var(--t-fast),var(--t-press);transition-timing-function:var(--ease-out)}
.hicon:hover{background:var(--rs-gold-hover)}
.hicon:active{scale:var(--press-scale)}
.hicon:focus-visible{border-radius:50%}
/* The pip counts change while the shopper watches: tabular so 1 -> 2 does
   not re-centre it. It is gold on the purple bar and needs a purple ring to
   part it from the gold disc it overlaps (9.1:1 both ways). */
.pip{position:absolute;top:-3px;right:-3px;background:var(--rs-gold);color:var(--rs-purple);font-family:var(--font-display);
     font-size:var(--fs-3xs);font-weight:700;border-radius:9px;padding:2px 5px 0;font-variant-numeric:tabular-nums;
     line-height:var(--lh-tight);box-shadow:0 0 0 2px var(--rs-purple)}

/* The gold pill at the end of the bar: the dev site's own "Our Events". */
.hdrbtn{display:inline-flex;align-items:center;justify-content:center;flex:none;background:var(--rs-gold);color:var(--rs-purple);
        font-family:var(--font-display);font-weight:500;font-size:var(--fs-sm);line-height:var(--lh-flat);
        min-height:var(--tap-min);padding:12px 20px 10px;border-radius:var(--r-pill);white-space:nowrap;
        transition-property:background-color,scale;transition-duration:var(--t-fast),var(--t-press);transition-timing-function:var(--ease-out)}
.hdrbtn:hover{background:var(--rs-gold-hover)}
.hdrbtn:active{scale:var(--press-scale)}
.hdrbtn:focus-visible{border-radius:var(--r-pill)}

/* =============================================================================
   THE MENU SHEET - a <details>, so it works with scripts off
   -----------------------------------------------------------------------------
   It stands in for the pillars from 1080px down, not from 768px: that is
   where six pillars and a search field stop fitting on one bar, and one bar
   is the point of the header above.
   ============================================================================= */
.mobnav{display:none;flex:none}
.mobnav > summary{list-style:none;cursor:pointer;min-width:var(--tap-min);min-height:var(--tap-min);display:flex;align-items:center;
                  justify-content:center;color:var(--accent-ink);border:2px solid var(--rs-gold);border-radius:50%;
                  transition-property:background-color,color,scale;transition-duration:var(--t-fast),var(--t-fast),var(--t-press);transition-timing-function:var(--ease-out)}
.mobnav > summary::-webkit-details-marker{display:none}
.mobnav > summary::marker{content:""}
.mobnav > summary:hover{background:var(--rs-gold);color:var(--rs-purple)}
.mobnav > summary:active{scale:var(--press-scale)}
.mobnav > summary:focus-visible{border-radius:50%}
.mobnav-panel{position:fixed;inset:auto 0 0 0;top:auto;max-height:78svh;overflow-y:auto;overscroll-behavior:contain;
              background:var(--rs-ground-2);border-top:3px solid var(--rs-gold);
              border-radius:var(--r-sheet) var(--r-sheet) 0 0;box-shadow:var(--shadow-sheet);z-index:70;
              padding:10px 20px calc(24px + var(--inset-b))}
.mobnav-list{list-style:none;margin:0;padding:0}
.mobnav-list > li{border-bottom:1px solid var(--line)}
.mobnav-list > li:last-child{border-bottom:0}
/* Every row a full-width 44px target: a menu is where a mis-tap costs the visit. */
.mobnav-list summary,.mobnav-list a{display:flex;align-items:center;min-height:var(--tap-min);padding:10px 2px;
                                    font-family:var(--font-display);font-weight:500;font-size:var(--fs-md);color:var(--ink);cursor:pointer}
.mobnav-list summary{list-style:none;justify-content:space-between}
.mobnav-list summary::-webkit-details-marker{display:none}
.mobnav-list summary::marker{content:""}
.mobnav-list summary::after{content:"";width:9px;height:9px;flex:none;border-right:2.5px solid var(--rs-gold);border-bottom:2.5px solid var(--rs-gold);
                            rotate:45deg;margin-right:6px;transition-property:rotate;transition-duration:var(--t-fast);transition-timing-function:var(--ease-out)}
.mobnav-list details[open] > summary::after{rotate:225deg}
.mobnav-list details ul{list-style:none;margin:0;padding:0 0 8px 14px}
.mobnav-list details a{font-family:var(--font-body);font-weight:500;font-size:var(--fs-sm);color:var(--ink-2)}
.mobnav-list details a:hover{color:var(--accent-ink)}
.mobnav-more{margin-top:8px;border-top:1px solid var(--line)}
.mobnav-sale{color:var(--accent-ink)}

/* THE TWO STEPS DOWN.

   1080 and under: the pillars fold into the sheet and the bar keeps the
   mark, search, the discs and the Events pill - still ONE row.

   680 and under: the row cannot hold a usable field beside four other
   objects, so search takes a line of its own under a [menu | mark | discs]
   row. The Events pill goes with the pillars into the sheet. Two rows on a
   phone, never three, and the mark stays at the left rather than being
   centred over a menu button. */
/* Between 1081 and 1200 the bar is full but tight. The Events pill stands
   down rather than the pillars crowding: it is in every game panel, in the
   menu sheet and in the footer, and the six pillars are the navigation. */
@media (max-width:1200px){
  .hdr .wrap{gap:14px}
  .hdrbtn{display:none}
  .search{flex-basis:170px}
  .hicons{gap:6px}
}
@media (max-width:1080px){
  .megapanel{display:none}
  .hdr .nav{display:none}
  .mobnav{display:block}
  /* The pillars have gone into the sheet, so there is room for the pill again. */
  .hdrbtn{display:inline-flex}
  .search{flex-basis:240px}
  .hicons{gap:8px}
}
/* The phone search disc. Declared BEFORE the phone block that turns it on,
   because the two rules have the same specificity and the later one would
   otherwise win at every width - which is how it first shipped invisible on
   the phone it exists for. */
.hsearchbtn{display:none}   /* phones only; turned on in the phone block */
.hsearch{background:var(--rs-gold);color:var(--rs-purple);border:0;cursor:pointer}
.hsearch[aria-expanded="true"]{background:var(--rs-gold-hover)}
@media (max-width:680px){
  .hdr .wrap{display:grid;align-items:center;grid-template-columns:auto auto 1fr auto;
             grid-template-areas:"menu   logo   icons  sbtn" "search search search search";
             row-gap:12px;column-gap:12px;min-height:auto;padding-top:12px;padding-bottom:14px}
  .hdr .mobnav{grid-area:menu}
  .hdr .logo{grid-area:logo}
  .hdr .logo img{height:52px}
  .hdr .hicons{grid-area:icons;justify-content:flex-end}
  .hdr .search{grid-area:search;flex-basis:auto;min-width:0;margin-left:0}
  .hdr .search:focus-within{flex-basis:auto}
  .hdrbtn{display:none}
  /* The search disc appears and the field folds away behind it. The row is
     kept in the grid so opening it pushes the page down rather than
     covering it, and "no JS" degrades to the field simply being there. */
  .hsearchbtn{display:flex;grid-area:sbtn;justify-content:flex-end}
  .hdr .search[data-collapsed]{display:none}
  /* AND NO GAP ABOVE A ROW THAT IS NOT THERE. The search row is deliberately
     kept in the grid so opening it pushes the page down rather than covering
     it - but while it is collapsed the row measures 0 and the 12px row-gap
     above it does not, so the header carried 12px of dead space under the
     logo on every phone page load. The gap comes back the moment the field
     does. */
  .hdr .wrap:has(.search[data-collapsed]){row-gap:0}
}
/* =============================================================================
   SIXTEEN PIXELS, OR THE PHONE ZOOMS
   -----------------------------------------------------------------------------
   REPORTED ON THE SEARCH DISC, 14 SEP 2026: pressing it zoomed the whole page
   in. Not the disc's doing - iOS Safari and Android Chrome both zoom the
   viewport when a focused text control's font-size is under 16px, and this
   shop's workhorse size is 14. Focus lands, the browser magnifies to make the
   text legible, and the visitor is left on a page they now have to pinch back
   out of.

   SO THE CONTROLS GET 16PX ON A PHONE, not `maximum-scale=1` in the viewport
   tag - which is the fix everybody reaches for first and which disables pinch
   zoom for the whole site, taking it away from people who need it to read
   anything at all. A font size costs nobody anything.

   EVERY text control, not just the search field. The one that actually
   matters is the checkout address form: a page that zooms itself while
   somebody is typing their postcode with a card in their other hand is worse
   than a search box doing it.
   ============================================================================= */
@media (max-width:680px){
  input[type=text],input[type=search],input[type=email],input[type=tel],
  input[type=password],input[type=number],input[type=url],textarea,select{
    font-size:var(--fs-md)}
  /* --fs-md IS 16px, and the ledger refuses a raw one - correctly, since a
     hard-coded size is how a design system stops being one. That it happens
     to be the number the browsers want is luck; if the token ever moves below
     16 the zoom comes back, so the token is the thing to check, not this
     rule. */
  .search input{font-size:var(--fs-md)}
}

/* =============================================================================
   THE PHONE'S BOTTOM BAR
   -----------------------------------------------------------------------------
   SIX THINGS IN A PHONE HEADER IS FIVE TOO MANY. Menu, logo, search, theme,
   wishlist, account and basket were sharing about 400 points, which is why
   the raven was being squeezed thin enough to notice: the icons take the
   free column and the logo takes what is left.

   So on a phone the discs move to the foot of the screen, where a thumb
   actually is, and the header keeps the three things that are about WHERE YOU
   ARE - menu, mark, search - while the bar keeps the four that are about
   WHAT IS YOURS.

   ONE SET OF MARKUP, MOVED. This is the same `.hicons` list, repositioned -
   not a second copy in the template. A duplicate would mean two basket
   badges to keep in step, two sets of labels for a screen reader to read out
   and two places to forget. `position:fixed` takes it out of the header grid
   on its own, which is also what collapses the header to a single row.

   It does not hide on scroll. A basket you can always reach is the point of
   putting it there, and a bar that comes and goes is one you have to hunt
   for at the moment you want it.
   ============================================================================= */
@media (max-width:680px){
  .hicons{position:fixed;left:0;right:0;bottom:0;z-index:60;
          display:flex;justify-content:space-around;align-items:center;
          gap:0;margin:0;
          /* THE HOME INDICATOR. --inset-b is the iPhone's, and 0 on Android:
             see the note above .wrap. Without viewport-fit=cover Chrome on
             Android 15 drew its own "chin" under the page, a bar that hides
             as you scroll and takes the viewport's bottom edge with it -
             the bar floating above a strip of page in the client's
             screenshot (19 Sep). The side insets are the notch, in
             landscape. */
          padding:8px calc(10px + env(safe-area-inset-right,0px))
                  calc(8px + var(--inset-b)) calc(10px + env(safe-area-inset-left,0px));
          background:var(--rs-ground-2);border-top:1px solid var(--line);
          box-shadow:0 -2px 14px rgb(0 0 0 / .28)}
  .hicons > li{flex:1 1 0;display:flex;justify-content:center}

  /* THE SEARCH DISC GOES, AND THE FIELD COMES BACK. The disc existed to buy
     the header its height back; the bar has just bought it. A disc at the
     FOOT that opens a field at the HEAD would be the one control on this page
     whose effect happens somewhere you are not looking. */
  /* THE HEADER IS THREE THINGS AND A DROPPED FIELD. The icons area is empty
     now the list is fixed to the foot, so the row is menu, mark, and the
     search disc on the right; the field lands underneath when the disc is
     pressed and takes no height at all until then. */
  .hdr .wrap{grid-template-areas:"menu logo sbtn" "search search search";
             grid-template-columns:auto 1fr auto}

  /* THE FOOTER MUST NOT END UNDER THE BAR. Its last line is the company
     details and the cookie link, which is the one thing a visitor goes
     looking for deliberately. */
  body{padding-bottom:calc(68px + var(--inset-b))}
}

/* 480, NOT 400. The header grid is menu / logo / icons with the icons taking
   the free column, and five 44px discs plus their gaps leave the logo about
   a hundred pixels on a 412pt Android - less than the mark needs at 52px
   tall. Dropping it to 44px earlier gives the width back, and the object-fit
   above means the phones between 400 and 480 were never distorted, only
   tight. */
@media (max-width:480px){
  .hdr .logo img{height:44px}
  .hdr .wrap{column-gap:8px}
  .hicons{gap:6px}
}

/* =============================================================================
   BUTTON SYSTEM
   -----------------------------------------------------------------------------
     PRIMARY  .btn, .big, .addbtn         gold fill, PURPLE label (7.2:1 -
                                          white on gold is 1.6:1, so never)
     GHOST    .btn.ghost, .addbtn.alt,    gold 2px outline, gold label;
              .addbtn.preorder, .wishbtn, hover fills gold
              .fbar button, .readmore
     SOLID    .btn.navy                   purple-2 fill, ink label (the
                                          account page's Sign out keeps its
                                          class name; the colour is ours)
     DANGER   .btn.danger                 red-deep fill, ink label (6.4:1)
     QUIET    .fmore, .clearall, .alink,  gold text, no box
              .aalt a, .bremove, .chip a
   Buttons are Reem Kufi 500, which is what the dev site's .bttn is.
   ============================================================================= */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:9px;background:var(--rs-gold);color:var(--rs-purple);
     font-family:var(--font-display);font-weight:500;font-size:var(--fs-md);line-height:var(--lh-flat);
     padding:14px 26px 12px;border:0;border-radius:var(--r-pill);min-height:var(--tap-min);text-align:center;white-space:nowrap;
     transition-property:background-color,border-color,color,box-shadow,translate,scale;
     transition-duration:var(--t-fast),var(--t-fast),var(--t-fast),var(--t-fast),var(--t-fast),var(--t-press);
     transition-timing-function:var(--ease-out)}
.btn:hover{background:var(--rs-gold-hover);translate:0 -1px;box-shadow:var(--glow-gold)}
.btn:active{translate:0 0;box-shadow:none;scale:var(--press-scale)}
.btn.sm{padding:10px 20px 8px;font-size:var(--fs-sm)}
.btn.ghost{background:transparent;color:var(--accent-ink);border:2px solid var(--rs-gold);padding-top:12px;padding-bottom:10px}
.btn.ghost:not(:disabled):hover{background:var(--rs-gold);color:var(--rs-purple)}
.btn.navy{background:var(--rs-purple-2);color:var(--ink)}
.btn.navy:not(:disabled):hover{background:var(--rs-purple-3);box-shadow:none}
.btn.danger{background:var(--rs-red-deep);color:var(--ink)}
.btn.danger:not(:disabled):hover{background:var(--rs-red-deep);box-shadow:none;translate:0}
/* The PDP buy button: one rung up, a 54px floor. */
.big{flex:1;min-height:54px;border-radius:var(--r-pill);border:0;background:var(--rs-gold);color:var(--rs-purple);
     font-family:var(--font-display);font-size:var(--fs-lg);font-weight:500;padding:14px 30px 12px;line-height:var(--lh-flat)}
.big:hover{background:var(--rs-gold-hover)}
/* The card's add button. .preorder is the outlined variant: the template
   writes "Pre-order" in it, the outline says it is a promise and not a
   shelf. */
.addbtn{width:100%;background:var(--rs-gold);color:var(--rs-purple);border:0;border-radius:var(--r-pill);min-height:var(--tap-min);
        font-family:var(--font-display);font-weight:500;font-size:var(--fs-md);line-height:var(--lh-flat);padding:12px 14px 10px}
.addbtn:not(:disabled):hover{background:var(--rs-gold-hover)}
.addbtn.alt,.addbtn.preorder{background:transparent;color:var(--accent-ink);border:2px solid var(--rs-gold)}
.addbtn.alt:not(:disabled):hover,.addbtn.preorder:not(:disabled):hover{background:var(--rs-gold);color:var(--rs-purple)}

/* ONE transition contract for everything that reads as a button, named
   properties only, scale on its own duration. */
.btn,.big,.addbtn,.fbar button,.sortbox select,.linkchip,.readmore summary,.pricefacet .bands button,
.chip a,.chip button,.qty button,.fmore,.clearall,.bremove,.alink,.aalt a,.pager a,.wish,.lbclose,.search button,
.wishbtn,.galthumb,.consentbtn,.catchip,.cattile,.gametile,.campcard,.suggest-item,.heronav,.heroplay{
  transition-property:background-color,border-color,color,scale;
  transition-duration:var(--t-fast),var(--t-fast),var(--t-fast),var(--t-press);
  transition-timing-function:var(--ease-out)}
/* THE PRESS: 0.96 from the token, so the reduced-motion block stops every
   press with one declaration. */
.btn:active,.big:active,.addbtn:active,.fbar button:active,.linkchip:active,.readmore summary:active,
.pricefacet .bands button:active,.chip a:active,.chip button:active,.qty button:active,.fmore:active,.clearall:active,
.bremove:active,.alink:active,.aalt a:active,.pager a:active,.wish:active,.lbclose:active,.search button:active,
.wishbtn:active,.galthumb:active,.consentbtn:active,.catchip:active,.suggest-item:active,
.heronav:active,.heroplay:active,.herodot:active{scale:var(--press-scale)}
/* DISABLED: one look for every family. Ground-2 box, muted label, a
   line-control edge - legible (7.9:1) and unmistakably off. */
.btn:disabled,.big:disabled,.addbtn:disabled,.fbar button:disabled,.sortbox select:disabled,
.pricefacet .bands button:disabled,.qty button:disabled,.wish:disabled,.lbclose:disabled,.search button:disabled{
  background:var(--rs-ground-2);color:var(--muted);border:2px solid var(--line-control);box-shadow:none;
  cursor:not-allowed;scale:1;translate:0}
.fmore:disabled,.clearall:disabled,.bremove:disabled,.alink:disabled{background:none;border:0;color:var(--muted);text-decoration:none;cursor:not-allowed;scale:1}
/* FOCUS: the global ring is a 4px rectangle, right for a field and wrong
   for a pill. Each family restates its own radius under focus. */
.btn:focus-visible,.big:focus-visible,.addbtn:focus-visible,.fbar button:focus-visible,.sortbox select:focus-visible,.wishbtn:focus-visible{border-radius:var(--r-pill)}
.linkchip:focus-visible,.readmore summary:focus-visible,.pricefacet .bands button:focus-visible,.condition:focus-visible{border-radius:var(--r-chip)}
.wish:focus-visible,.lbclose:focus-visible,.chip a:focus-visible,.chip button:focus-visible,.search button:focus-visible{border-radius:50%}
.cattile:focus-visible,.gametile:focus-visible,.campcard:focus-visible,.card:focus-within .art a{border-radius:var(--r-card)}
/* QUIET buttons: gold text, 44px tall, an underline on hover. */
.fmore,.clearall,.bremove,.alink{display:inline-flex;align-items:center;min-height:var(--tap-min);background:none;border:0;padding:0 4px;
                                 color:var(--accent-ink);font-weight:500;font-size:var(--fs-sm)}
.fmore:hover,.clearall:hover,.alink:hover{text-decoration:underline;text-underline-offset:3px}

/* =============================================================================
   HERO - the dark map, the gold headline
   -----------------------------------------------------------------------------
   The texture is a ::before at 35% over the solid ground, NOT a background
   the copy sits on directly: at that strength the text pair is still ink on
   ground to within a tenth of a ratio, which is the only way text over a
   picture can be measured. The gradient to --rs-purple-3 at the far end is
   the dev site's, under the texture, decorative.
   ============================================================================= */
/* THE HERO IS A BAND, not a page section, and it is the same band in both
   themes - the masthead's colour carried down over the fold. Tinting it to
   match a white page was the first attempt and it read as washed-out: a pale
   grey-lilac field with the headline floating in it, which is a light theme
   happening TO the brand rather than the brand on a light page.

   It is in the chrome scope above, so --rs-ground and --surface-raised are
   the dark values inside it and the gradient and the ink are all the ones
   every figure was measured against. */
.hero{position:relative;overflow:hidden;color:var(--ink);isolation:isolate;
      background:linear-gradient(110deg,var(--rs-ground) 0%,var(--rs-ground) 55%,var(--surface-raised) 100%)}
/* The map linework is gone from behind the headline - the client's call,
   31 Aug: houses and mountains behind display type read as clutter where
   the menu's leather grain reads as material. The FOOTER's copy of the
   grain, not the nav's, because this band is tall and the nav strip's
   grain blown up to hero height turns to noise. */
.hero::before{content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;opacity:.5;
              background:url("/assets/footer-bg.jpg") center/cover no-repeat}
.hero .wrap{display:flex;align-items:center;gap:40px;padding-top:64px;padding-bottom:64px;position:relative;z-index:2}
.hero .txt{flex:1;max-width:640px}
.eyebrow{display:inline-block;font-family:var(--font-display);color:var(--accent-ink);font-size:var(--fs-xs);font-weight:500;
         letter-spacing:1.6px;text-transform:uppercase;padding:6px 0;margin-bottom:12px}
/* THE HEADLINE, and its h2 twin. Only the first slide may carry the h1
   - four slides must not mean four page titles - so the other three are
   h2s and they are set in the same type, or turning the hero over would
   change the size of the words in it. */
.hero h1,.hero .heroslide h2{font-size:var(--fs-8xl);line-height:var(--lh-flat);margin-bottom:18px;color:var(--ink)}
.hero h1 em,.hero .heroslide h2 em{font-style:normal;color:var(--accent-ink)}
.hero p{font-size:var(--fs-lg);line-height:var(--lh-relaxed);color:var(--ink-2);max-width:470px;margin-bottom:28px}
/* The art slot: the compass or a product shot, framed and floated. */
.hero .art{flex:1;display:flex;align-items:center;justify-content:center}
.heroart img,.hero .art img{max-height:300px;width:auto;border-radius:var(--r-sheet);box-shadow:var(--shadow-pop)}
.blob{display:none}
/* ONE staged entrance, on the homepage only, declared inside no-preference
   so under reduce it never exists. Backwards fill so nothing is left pinned
   to a compositing layer afterwards. */
@keyframes rs-hero-in{from{opacity:0;transform:translateY(12px);filter:blur(4px)}to{opacity:1;transform:translateY(0);filter:blur(0)}}
@media (prefers-reduced-motion:no-preference){
  .hero .txt > *{animation-name:rs-hero-in;animation-duration:var(--t-slow);animation-timing-function:var(--ease-emphasis);animation-fill-mode:backwards}
  .hero .txt > *:nth-child(1){animation-delay:0s}
  .hero .txt > *:nth-child(2){animation-delay:var(--t-stagger)}
  .hero .txt > *:nth-child(3){animation-delay:calc(var(--t-stagger) * 2)}
  .hero .txt > *:nth-child(n+4){animation-delay:calc(var(--t-stagger) * 3)}
}
@media (max-width:1024px){ .hero h1,.hero .heroslide h2{font-size:var(--fs-6xl)} .hero .wrap{padding-top:48px;padding-bottom:48px} }
@media (max-width:768px){
  .hero .wrap{flex-direction:column;align-items:flex-start;padding-top:36px;padding-bottom:36px}
  .hero h1,.hero .heroslide h2{font-size:var(--fs-5xl)}
  .hero .art{order:-1;margin-bottom:4px}
  /* FULL WIDTH, CROPPED HEIGHT, ON A PHONE. `max-height` with `width:auto`
     is a portrait rule: the key art is landscape, so constraining the height
     left it a small square in the middle of the screen with a column of
     ground down each side. A phone has one dimension worth having and this
     was spending it on margin.
     So the art takes the width it is given and the crop takes the height.
     `object-fit:cover` rather than a shorter source file, because these are
     the publishers' own key art and we do not get to re-cut them - and
     `object-position` favours the top, where the logo and the characters
     are; a centre crop on a 5:2 window takes the sky and the feet. */
  .hero .art,.heroart{width:100%}
  .heroart img,.hero .art img{width:100%;max-height:none;height:200px;
                              object-fit:cover;object-position:50% 32%;
                              border-radius:var(--r-card)}
}
@media (max-width:400px){ .hero h1,.hero .heroslide h2{font-size:var(--fs-3xl)} .hero .wrap{padding-top:28px;padding-bottom:28px} }

/* =============================================================================
   THE HERO SLIDER - the four games in the one band
   -----------------------------------------------------------------------------
   One slide per game and ALL FOUR ARE SERVER-RENDERED. Slides two to four
   arrive carrying the hidden attribute and the control bar arrives hidden
   too, so with no script the hero is the first slide entire: usable, not
   half drawn, and the header above still reaches every game. hero.js takes
   those attributes off and adds .is-live, and only then does anything below
   lay the slides side by side. A stylesheet that hid them itself would hide
   them for ever on the day the script did not arrive.

   A slide's innards are the hero's own .txt, .art, .heroart and .eyebrow,
   so every pair in here is the pair it always was: ink, ink-2 and gold on
   the band the hero is painted with. The machinery is the only new thing.
   ============================================================================= */
.heroslides{position:relative}
/* THE TRACK. Four full-width slides moved a whole slide at a time. The
   offset is a custom property because hero.js has no business knowing what
   a slide is worth in pixels: it sets a number, this decides what the
   number means, and every dimension stays in this file. */
.heroslider.is-live .heroslides{display:flex;flex-wrap:nowrap;align-items:stretch;
  transform:translateX(calc(var(--hero-i, 0) * -100%));
  transition-property:transform;transition-duration:var(--t-slow);transition-timing-function:var(--ease-emphasis)}
/* Every slide the same width and stretched to the same height, so the band
   does not change shape when a shorter game comes round.

   OFF SCREEN IS NOT HIDDEN, and that is what this pair of declarations is
   for: a slide parked past the edge of an overflow:hidden box is still
   focusable and still read out, so a keyboard walks into a hero nobody can
   see and a screen reader recites four of them. visibility:hidden takes a
   slide out of the tab order AND out of the accessibility tree. The zero
   duration with a delay is what keeps the outgoing slide on screen for the
   length of its travel and hides it at the end of it; under reduce --t-slow
   is 0s, so it goes in the same frame. */
.heroslider.is-live .heroslide{flex:0 0 100%;max-width:100%;
  visibility:hidden;transition-property:visibility;transition-duration:0s;transition-delay:var(--t-slow)}
.heroslider.is-live .heroslide.is-current{visibility:visible;transition-delay:0s}
/* The two calls to action. This was an inline style on the paragraph; four
   slides carry it now, so it is a class. */
.herobtns{display:flex;gap:12px;flex-wrap:wrap}

/* A SLIDE WHOSE ARTWORK IS DRAWN ON PAPER. The Pokemon picture is booster
   packs cut out on a near-white field (tools/brand_tile.py). Dropped into a
   dark band unchanged it reads as a white rectangle somebody forgot to
   crop, so the rule this stylesheet already applies to every product
   photograph decides it: a picture that is CONTAINED gets a light plate,
   which is the whole reason --paper-2 exists. The plate is the picture's
   own ground to within a shade, and the padding is what turns a bleeding
   edge into a plate somebody meant.

   THE BAND STAYS DARK, unlike .brandhero.light, which flips its ink and
   all: there the artwork IS the field and the words sit on it, whereas here
   it is one object in a column beside the words. Flipping this band would
   also mean flipping it back every six seconds while the slider runs, which
   is a strobe rather than a design. */
.heroslide.light .heroart img{background:var(--paper-2);padding:14px}

/* THE CONTROL BAR. Inside the band and on the wrap's own gutters, so it
   lines up under the copy instead of floating in the middle of nothing.
   .hero .wrap is the slide's two-column row, hence the restatement. */
/* flex-direction is stated and not left to inherit: the hero's .wrap
   turns into a column on a phone, and a control bar that took that rule
   drew itself as a totem pole of discs half a screen tall. */
.hero .wrap.heroctl{display:flex;flex-direction:row;flex-wrap:wrap;align-items:center;gap:8px;
  padding-top:0;padding-bottom:30px;position:relative;z-index:2}
/* The slide hands the bottom of its padding to the bar, and only while the
   bar is there: with no script the hero keeps the padding it always had. */
.heroslider.is-live .heroslide .wrap{padding-bottom:26px}
/* Previous, next and the rotation toggle: the ghost button drawn as a disc,
   at the tap floor, with the ghost's own hover. The glyph is 20px inside a
   44px target, because a chevron that fills its button is a chevron nobody
   can aim at. The toggle leads the row so a keyboard meets "stop this"
   before it meets the things that change slide. */
.heronav,.heroplay{display:inline-flex;align-items:center;justify-content:center;flex:none;
  width:var(--tap-min);height:var(--tap-min);border:2px solid var(--rs-gold);border-radius:50%;
  background:transparent;color:var(--accent-ink)}
.heronav:hover,.heroplay:hover{background:var(--rs-gold);color:var(--rs-purple)}
.heroplay{margin-right:6px}
/* THE DOTS. The button is 44px square because a finger needs that; the
   mark inside it is small, because a 44px dot is a button pretending to be
   a dot. The current one is a gold pill - a different shape as well as a
   different colour, so colour is not the only thing saying where you are. */
.herodots{display:flex;align-items:center}
.herodot{display:inline-flex;align-items:center;justify-content:center;flex:none;
  width:var(--tap-min);height:var(--tap-min);background:none;border:0;padding:0}
.herodot::before{content:"";flex:none;width:10px;height:10px;border-radius:50%;background:var(--muted);
  transition-property:background-color,width;transition-duration:var(--t-fast);transition-timing-function:var(--ease-out)}
.herodot:hover::before{background:var(--ink)}
.herodot[aria-current="true"]::before{width:26px;border-radius:var(--r-pill);background:var(--rs-gold)}
/* A disc focuses as a disc. The global ring is a 4px rectangle, which is
   right for a field and wrong for anything round. */
.heronav:focus-visible,.heroplay:focus-visible,.herodot:focus-visible{border-radius:50%}
/* Seven targets at the tap floor is 322px of the 350px a 390px phone has
   between its gutters, so the gaps come in rather than the targets. */
@media (max-width:768px){
  .hero .wrap.heroctl{gap:4px;padding-bottom:22px}
  .heroplay{margin-right:2px}
  .heroslider.is-live .heroslide .wrap{padding-bottom:20px}
}

/* =============================================================================
   SECTIONS - rhythm and headings
   ============================================================================= */
.sec{padding-top:52px;padding-bottom:52px}
.ages{padding-top:46px;padding-bottom:10px}
.sech{display:flex;align-items:flex-end;justify-content:space-between;margin-bottom:26px;gap:12px;row-gap:10px}
.sech h2{font-size:var(--fs-4xl);line-height:var(--lh-tight);color:var(--ink)}
.sech p{color:var(--muted);font-size:var(--fs-sm);margin-top:7px;line-height:var(--lh-body)}
.sech a{color:var(--accent-ink);font-family:var(--font-display);font-weight:500;font-size:var(--fs-md);padding-top:3px;padding-bottom:3px;white-space:nowrap}
.sech a:hover{text-decoration:underline;text-underline-offset:3px}
@media (max-width:1024px){ .sec{padding-top:44px;padding-bottom:44px} .ages{padding-top:38px;padding-bottom:8px} }
@media (max-width:768px){
  .sec{padding-top:36px;padding-bottom:36px}
  .ages{padding-top:32px;padding-bottom:8px}
  .sech{flex-direction:column;align-items:flex-start;gap:8px;margin-bottom:20px}
  .sech h2{font-size:var(--fs-2xl)}
}
@media (max-width:400px){ .sec{padding-top:32px;padding-bottom:32px} }

/* =============================================================================
   GAME TILES - the four games, the homepage's first move
   -----------------------------------------------------------------------------
   The tile IS the artwork. A ground gradient rises from the foot so the game
   logo at bottom-left sits on near-ground whatever the art does, and the
   whole tile is one link. Hover zooms the art at --t-slow, the one place
   that duration is spent. 2x2 on a phone, four across from 900px.
   ============================================================================= */
.gametiles{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;list-style:none;margin:0;padding:0}
.gametiles > li{min-width:0}
.gametile{position:relative;display:block;aspect-ratio:4/5;overflow:hidden;border-radius:var(--r-card);background:var(--surface-raised);
          box-shadow:var(--shadow-card);isolation:isolate;
          transition-property:translate,box-shadow,scale;transition-duration:var(--t-base),var(--t-base),var(--t-press);transition-timing-function:var(--ease-out)}
.gametile:hover{translate:0 -3px;box-shadow:var(--shadow-pop)}
.gametile:active{scale:var(--press-scale)}
.gametile:focus-visible{outline:var(--focus-ring);outline-offset:3px}
/* THE ARTWORK, AND ONLY THE ARTWORK. ":not(.gametile-logo)" is load-bearing:
   the logo is also a direct child img, so this rule claimed it too - and at
   (0,1,1) it outranks .gametile-logo at (0,1,0) on every property they
   share. The logo was being stretched to fill the tile, cropped to cover
   and dropped to z-index 0 behind the scrim, so it rendered as nothing.
   Worse, an image that is fully covered and marked loading="lazy" is one
   the browser never fetches at all, so the four logo files were never even
   requested and the bug looked like a missing asset. */
.gametile > img:not(.gametile-logo),.gametile-art{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:0;
                              transition-property:scale;transition-duration:var(--t-slow);transition-timing-function:var(--ease-out)}
.gametile:hover > img:not(.gametile-logo),.gametile:hover .gametile-art{scale:1.06}
/* The ramp: solid ground at the foot, gone by 60% up. The logo lives in the
   bottom third, so everything it overlaps is ground at 85% or better. */
.gametile::after{content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
                 background:linear-gradient(to top,var(--rs-ground) 0%,var(--rs-ground) 12%,transparent 62%);opacity:.92}
/* A BOX BEFORE THE IMAGE LOADS, which is what width:auto;height:auto could
   not give it. An <img> has no intrinsic size until its bytes arrive, so
   auto/auto measured 0x0 - and a zero-area image marked loading="lazy" is
   one the browser never bothers to fetch, so it stayed 0x0 for ever. A
   stated box breaks the circle; object-fit:contain letterboxes each logo
   inside it, so four marks of four different shapes still line up on the
   same baseline. */
.gametile-logo{position:absolute;left:16px;bottom:16px;z-index:2;width:min(62%,200px);height:64px;object-fit:contain;object-position:left bottom;
               filter:drop-shadow(var(--shadow-card))}
/* The name: a visible caption under the logo in the kicker's voice, or the
   template's .vh fallback when the logo says it already. */
/* A TILE DRAWN ON PAPER. Same argument as .brandhero.light one component
   along: the ramp that lifts a white caption off dark key art becomes a
   white one doing the same job the other way up, and the caption goes dark
   with it. The other three tiles carry the client's own photographic key
   art and stay as they are - this is for artwork we drew, which is drawn
   light so that the tile and the brand banner of the same packs read as one
   pair rather than as two shops. */
.gametile.light{background:var(--paper-2)}
.gametile.light::after{background:linear-gradient(to top,var(--paper) 0%,var(--paper) 8%,transparent 46%);opacity:.85}
.gametile.light .gametile-name{color:var(--paper-ink)}
.gametile.light .gametile-logo{filter:none}
.gametile.light:focus-visible{outline:var(--focus-ring-paper)}
.gametile-name{position:absolute;left:16px;right:16px;bottom:16px;z-index:2;font-family:var(--font-display);font-weight:500;
               font-size:var(--fs-sm);letter-spacing:1.2px;text-transform:uppercase;color:var(--ink);line-height:var(--lh-snug)}
.gametile-logo + .gametile-name,.gametile-logo ~ .gametile-name{bottom:auto;top:14px;left:16px;right:auto;font-size:var(--fs-2xs);color:var(--accent-ink)}
@media (min-width:900px){ .gametiles{grid-template-columns:repeat(4,minmax(0,1fr));gap:18px} .gametile{aspect-ratio:3/4} }
@media (max-width:480px){ .gametiles{gap:10px} .gametile-logo{left:12px;bottom:12px;height:48px} .gametile-name{left:12px;bottom:12px} }

/* =============================================================================
   USP STRIP - four promises under the hero
   ============================================================================= */
.usp{background:var(--rs-ground-2);border-top:1px solid var(--line);border-bottom:1px solid var(--line);margin-bottom:10px}
.usp .wrap{display:grid;grid-template-columns:repeat(4,1fr);gap:18px 26px;padding:22px 32px;list-style:none}
.uspitem{display:flex;gap:12px;align-items:flex-start}
.uspitem .ic{width:38px;height:38px;border-radius:50%;background:var(--rs-gold);color:var(--rs-purple);display:flex;align-items:center;justify-content:center;flex:none;margin-top:1px}
.uspitem b{display:block;font-family:var(--font-display);font-weight:500;font-size:var(--fs-md);color:var(--ink);line-height:var(--lh-snug)}
.uspitem small{color:var(--muted);font-size:var(--fs-xs)}
@media (max-width:1024px){ .usp .wrap{grid-template-columns:repeat(2,1fr);padding:22px 26px} }
@media (max-width:768px){ .usp .wrap{padding:22px 20px;gap:20px 22px} .usp{margin-bottom:6px} }
/* ONE PER ROW ON A PHONE, at the same 599px the game tiles use.
   This said 400px, which almost no phone is - a 393pt iPhone squeaks under it
   and a 412pt Android does not, so most phones got a 2x2 of four-word
   promises squeezed into half a column each. These are sentences, not tiles;
   they want the width more than they want the company. */
@media (max-width:599px){ .usp .wrap{grid-template-columns:1fr;gap:14px;padding:20px 18px} }

/* =============================================================================
   CATEGORY TILES AND CHIPS
   -----------------------------------------------------------------------------
   .catgrid is the photographed grid: the tile is the picture, the name sits
   on a solid purple plate under a gold keyline, the go chip is gold. .catrow
   and .catchip are the smaller scrolling strip, kept for any page that
   still uses it. .linkchips are text chips; .catchips is the same chip under
   a brand hero.
   ============================================================================= */
.catrow{display:flex;gap:14px;list-style:none}
.catchip{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:flex-start;gap:10px;background:var(--surface-raised);
         border:2px solid transparent;border-radius:var(--r-card);padding:14px 10px 13px;text-align:center;min-height:var(--tap-min)}
.catchip:hover{border-color:var(--rs-gold)}
.catchip .ic{width:64px;height:56px;border-radius:10px;overflow:hidden;background:var(--paper-2);display:flex;align-items:center;justify-content:center;flex:none}
.catchip .ic img{width:100%;height:100%;object-fit:contain;padding:3px}
.catchip .l{font-family:var(--font-display);font-size:var(--fs-sm);font-weight:500;color:var(--ink);line-height:var(--lh-snug)}
.cattile .l{font-size:var(--fs-sm);line-height:var(--lh-snug)}
@media (max-width:768px){
  .catrow{overflow-x:auto;scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch;padding-bottom:10px;gap:12px}
  .catchip{min-width:104px;scroll-snap-align:start}
  .catrow .catchip:focus-visible{outline-offset:-3px}
}
@media (max-width:400px){ .catchip{min-width:96px} }

/* A horizontal row keeps its overflow to itself; the body never scrolls sideways. */
.scrollrow{display:flex;flex-wrap:nowrap;max-width:100%;overflow-x:auto;overscroll-behavior-x:contain;-webkit-overflow-scrolling:touch;scroll-snap-type:x proximity;padding-bottom:10px}
.scrollrow > li{flex:1 0 auto;min-width:140px;scroll-snap-align:start}
@media (max-width:768px){ .scrollrow > li{min-width:132px} }

.catgrid{display:grid;grid-template-columns:repeat(4,1fr);grid-auto-rows:200px;gap:14px;list-style:none}
.catgrid > li{min-width:0}
/* Seven tiles in four columns leave a hole; the FIRST tile takes it, so the
   row reads as an editorial lead rather than a gap patched at the end. */
.catgrid > li:first-child{grid-column:span 2}
.catgrid .cattile{position:relative;display:block;height:100%;padding:0;border:0;border-radius:var(--r-card);overflow:hidden;
                  background:var(--surface-raised);box-shadow:var(--shadow-card);
                  transition-property:translate,scale,box-shadow;transition-duration:var(--t-base),var(--t-press),var(--t-base);transition-timing-function:var(--ease-out)}
.catgrid .cattile:hover{translate:0 -3px;box-shadow:var(--shadow-pop)}
.catgrid .cattile:active{scale:var(--press-scale)}
.catgrid .cattile:focus-visible{outline:var(--focus-ring);outline-offset:3px}
.catgrid .cattile-art{position:absolute;inset:0;overflow:hidden;background:var(--surface-raised)}
.catgrid .cattile-art img{width:100%;height:100%;object-fit:cover;display:block;transition-property:scale;transition-duration:var(--t-slow);transition-timing-function:var(--ease-out)}
.catgrid .cattile:hover .cattile-art img{scale:1.06}
/* A composed tile brings its own field and is already keyed to the brand, so
   the purple plate behind it and the 14% wash over it are both working
   against it: the plate shows as a dark seam where the artwork does not
   quite fill, and the wash greys three product photographs that were put
   there to be looked at. The modifier is named for the field nearly every
   one of them uses - Card Cases is drawn on the ground because clear acrylic
   is invisible on paper (tools/brand_tile.py) - and what it does either way
   is the same: no plate, no wash, the artwork as composed. */
.catgrid .cattile.light .cattile-art{background:var(--paper-2)}
.catgrid .cattile.light .cattile-art::before{opacity:0}
/* A purple wash at 14% pulls packshots and lifestyle shots into one key;
   it lifts on hover so the picture comes forward. Decorative, no text on it. */
.catgrid .cattile-art::before{content:"";position:absolute;inset:0;z-index:1;pointer-events:none;background:var(--rs-purple);opacity:.14;
                              transition-property:opacity;transition-duration:var(--t-base);transition-timing-function:var(--ease-out)}
.catgrid .cattile:hover .cattile-art::before{opacity:.04}
/* The label on a SOLID plate, never on the picture: ink on purple is a
   constant 11:1 however bright the photograph under it. */
.catgrid .cattile-label{position:absolute;left:0;right:0;bottom:0;z-index:3;display:flex;align-items:center;gap:10px;
                        background:var(--rs-purple);color:var(--ink);border-top:3px solid var(--rs-gold);
                        font-family:var(--font-display);font-weight:500;font-size:var(--fs-md);line-height:var(--lh-snug);padding:11px 13px 10px 15px}
.catgrid > li:first-child .cattile-label{font-size:var(--fs-lg);padding:12px 15px 11px 17px}
.catgrid .cattile-go{flex:none;margin-left:auto;width:30px;height:30px;border-radius:50%;background:var(--rs-gold);color:var(--rs-purple);
                     display:flex;align-items:center;justify-content:center;
                     transition-property:translate,background-color;transition-duration:var(--t-base);transition-timing-function:var(--ease-out)}
.catgrid .cattile-go svg{margin-left:1px}
.catgrid .cattile:hover .cattile-go{translate:3px 0;background:var(--rs-gold-hover)}
@media (max-width:1024px){ .catgrid{gap:12px;grid-auto-rows:186px} }
@media (max-width:768px){
  .catgrid{grid-template-columns:repeat(3,1fr);grid-auto-rows:132px}
  .catgrid .cattile-label{font-size:var(--fs-sm);padding:9px 10px 8px 12px;gap:8px}
  .catgrid > li:first-child .cattile-label{font-size:var(--fs-md)}
  .catgrid .cattile-go{width:28px;height:28px}
  .catgrid > li:last-child{grid-column:span 2}
}
@media (max-width:480px){ .catgrid{grid-template-columns:repeat(2,1fr)} .catgrid > li:last-child{grid-column:auto} }

/* Text chips: ink on a line-control outline, gold on hover. */
.linkchips,.catchips{display:flex;gap:10px;flex-wrap:wrap;list-style:none;max-width:100%;overscroll-behavior-x:contain}
.linkchip,.catchips a{display:inline-flex;align-items:center;background:transparent;border:1.5px solid var(--line-control);border-radius:var(--r-chip);
          padding:9px 16px;font-size:var(--fs-sm);font-weight:500;color:var(--ink);min-height:var(--tap-min)}
.linkchip:hover,.catchips a:hover{border-color:var(--rs-gold);color:var(--accent-ink)}
/* THE COUNT NEEDED A GAP AND NEVER HAD ONE. brands.html writes the name, a
   literal space, then <span class="cnt">, and .linkchip is inline-flex - a
   flex container drops whitespace between its items, so the space was thrown
   away and every chip read "Dragon Shield14". The gap has to be a margin
   here, not a character. Muted and tabular so a column of them lines up and
   the count reads as an annotation rather than as part of the name. */
.linkchip .cnt{margin-left:7px;color:var(--muted);font-weight:500;font-variant-numeric:tabular-nums}
.linkchip:hover .cnt{color:inherit}
.linkchip.on,.catchips a.on,.catchips [aria-current]{background:var(--rs-gold);border-color:var(--rs-gold);color:var(--rs-purple)}
.catchips{margin:18px 0 8px}
@media (max-width:768px){
  .catchips{flex-wrap:nowrap;overflow-x:auto;scroll-snap-type:x proximity;-webkit-overflow-scrolling:touch;padding-bottom:8px}
  .catchips > li{flex:none;scroll-snap-align:start}
  .catchips a:focus-visible{outline-offset:-3px}
}
.pbrands{padding-top:10px;padding-bottom:6px;display:flex;align-items:center;gap:12px 18px;flex-wrap:wrap}
.pbrands h2{font-family:var(--font-display);font-size:var(--fs-xs);font-weight:500;letter-spacing:1.4px;text-transform:uppercase;color:var(--accent-ink);flex:none}
.mws{padding-top:10px;padding-bottom:46px}
.mws .sech{margin-bottom:8px}
.mwsgrp{margin-top:20px}
.mwsgrp h3{font-size:var(--fs-xs);letter-spacing:1.4px;color:var(--accent-ink);margin-bottom:11px;text-transform:uppercase;font-weight:500}
@media (max-width:768px){ .pbrands{padding-top:6px;padding-bottom:2px} .mws{padding-top:6px;padding-bottom:38px} }

/* =============================================================================
   PRODUCT GRID AND CARD
   -----------------------------------------------------------------------------
   A purple panel on the ground. The photograph sits on a PAPER PLATE, and
   that is the one rule that decides which surfaces in this file are light:

     A PHOTOGRAPH THAT IS CONTAINED GETS A LIGHT FIELD. A PICTURE THAT IS
     CROPPED TO FILL ITS BOX KEEPS THE PURPLE ONE.

   Sealed product is shot dark, busy, and nearly always against white, so on
   a purple plate it either melts into the card or drags a white rectangle
   across it. On --paper-2 it reads as stock on a shelf, the shop's own
   "Awaiting image" placeholder (a lilac disc drawn for a white ground)
   finally reads as drawn, and the page stops being purple from the masthead
   to the legal line. The plate is inset 8px inside a 14px card, so its
   radius is the difference.

   NOTHING GOLD MAY TOUCH THAT PLATE: gold is 1.3:1 on it. Every flag, disc
   and ring that sits on the art is purple-on-paper or gold-on-purple below,
   and the ring for anything focusable there flips at the top of this file.

   Gold price in Reem Kufi, stock line in green, amber or red from the
   tokens, the add button gold - all of those are in the BODY, which is
   still purple, and none of them moved.
   ============================================================================= */
.grid{display:grid;gap:20px;list-style:none}
.g4{grid-template-columns:repeat(4,1fr)}
.g3{grid-template-columns:repeat(3,1fr)}
.card{border:0;border-radius:var(--r-card);overflow:hidden;background:var(--surface-raised);position:relative;display:flex;flex-direction:column;height:100%;
      box-shadow:0 0 0 1px var(--line),var(--shadow-card);
      transition-property:translate,box-shadow;transition-duration:var(--t-base);transition-timing-function:var(--ease-out)}
.card:hover{translate:0 -3px;box-shadow:0 0 0 1px var(--line-2),var(--shadow-pop)}
.card .art{height:196px;margin:8px 8px 0;border-radius:calc(var(--r-card) - 8px);background:var(--paper-2);display:flex;align-items:center;justify-content:center;position:relative;padding:10px;overflow:hidden}
/* contain, never cover: a product photograph is not cropped. The zoom is
   slower than the card's lift so the picture settles after the card does. */
.art img{max-width:100%;max-height:100%;width:auto;height:auto;object-fit:contain;transition-property:scale;transition-duration:var(--t-slow);transition-timing-function:var(--ease-out)}
/* THE PLATE'S OWN ARTWORK, AND ONLY THAT. The child combinator is load
   bearing and it is the second time this trap has cost us a rendered
   element (the first was .gametile > img claiming .gametile-logo): as a
   plain descendant rule this reached the wishlist heart nested in its form
   and drew a 17px glyph at 78% of a 44px button, so the heart was twice the
   size the markup asks for. A placeholder is a child of the plate; a
   control on top of the plate never is. */
.art > svg{width:auto;height:78%;max-width:82%}
.card:hover .art img{scale:1.04}
.card .body{padding:15px 16px 17px;display:flex;flex-direction:column;flex:1;gap:0}
/* A card with nothing to buy - sold out, or a line with no price - has no
   button, and the title absorbs the slack instead, which floated its price
   44px below every neighbour's in the same row. Reserve the button's row so
   the prices sit on one line across the grid. */
.card .body:not(:has(form))::after{content:"";display:block;min-height:var(--tap-min)}
.brandline{font-family:var(--font-display);font-size:var(--fs-2xs);font-weight:500;letter-spacing:1.1px;color:var(--muted);text-transform:uppercase;margin-bottom:6px;line-height:var(--lh-snug)}
.brandline a{padding-top:5px;padding-bottom:5px}
.brandline a:hover{color:var(--accent-ink)}
.card h3{font-family:var(--font-body);font-size:var(--fs-sm);line-height:var(--lh-body);font-weight:500;margin-bottom:10px;flex:1 0 auto;min-height:calc(var(--lh-body) * 2em);color:var(--ink)}
.card h3 a{padding-top:3px;padding-bottom:3px}
.card h3 a:hover{text-decoration:underline;text-underline-offset:3px}
.stars{display:flex;align-items:center;gap:5px;font-size:var(--fs-2xs);color:var(--muted);margin-bottom:10px;flex-wrap:wrap;row-gap:2px}
.stars .s{color:var(--accent-ink);letter-spacing:1px}
/* PRICE: the one gold thing in the body, display face, tabular so four cards
   in a row keep pounds and pence in register. The was-price drops in size
   and colour, not only a line through it. */
.price{display:flex;align-items:baseline;gap:8px;margin-bottom:12px;flex-wrap:wrap;row-gap:2px;line-height:var(--lh-tight);font-variant-numeric:tabular-nums}
.price .now{font-family:var(--font-display);font-size:var(--fs-xl);font-weight:700;color:var(--price-ink)}
.price .was{font-size:var(--fs-xs);font-weight:500;color:var(--muted);text-decoration:line-through;text-decoration-thickness:1px}
/* FLAGS on the art plate. Every one of them is a FILL with its label on it,
   and every one carries a purple keyline (12.1:1 on the plate) because a
   gold or a green sticker on near-white has no edge of its own. */
.badge{position:absolute;top:9px;left:9px;z-index:2;background:var(--rs-gold);color:var(--rs-purple);font-family:var(--font-display);
       font-size:var(--fs-2xs);font-weight:700;letter-spacing:.5px;text-transform:uppercase;font-variant-numeric:tabular-nums;
       padding:6px 10px 4px;border-radius:var(--r-pill);box-shadow:0 0 0 1.5px var(--rs-purple),var(--shadow-card);
       white-space:nowrap;line-height:var(--lh-flat)}
.badge.g{background:var(--rs-green);color:var(--rs-ground)}
.badge.d,.badge.last{background:var(--rs-ground);color:var(--ink)}
/* Pre-order was a gold outline with a gold label, and the plate under it is
   paper now: 1.3:1, twice over. It is the same flag the other way up - a
   purple fill with a gold label (9.1:1) under a gold keyline. */
.badge.preorder{background:var(--rs-purple);color:var(--rs-gold);box-shadow:0 0 0 2px var(--rs-gold),var(--shadow-card)}
.badge+.badge{top:41px}
.agetag{position:absolute;bottom:11px;left:11px;background:var(--rs-ground);color:var(--ink);font-size:var(--fs-2xs);font-weight:500;padding:4px 9px;border-radius:12px}
/* The heart: a ground disc on the paper plate (17.4:1, so the disc needs no
   edge of its own) with a GOLD RING in every state, and pressed it is a
   purple disc with a gold heart filled in. The disc stays dark because the
   plate under it is --paper-2, where gold is 1.3:1: a gold disc there would
   be a sticker nobody could see.

   THE RING IS INSET, and that is the same argument the focus ring makes one
   line down: inset, the gold is surrounded by ground (13.6:1) and reads;
   outset, its far edge lands on the paper and does not. It rides in the
   same box-shadow as the card lift, because a border would push the glyph
   off centre by its own width. */
.wish{position:absolute;top:8px;right:8px;z-index:2;width:var(--tap-min);height:var(--tap-min);border:0;border-radius:50%;background:var(--rs-ground);
      display:flex;align-items:center;justify-content:center;color:var(--ink);
      box-shadow:inset 0 0 0 2px var(--accent-ink),var(--shadow-card)}
.wish:hover{color:var(--accent-ink)}
.wish[aria-pressed="true"]{background:var(--rs-purple);color:var(--rs-gold)}
.wish:focus-visible{outline-offset:-3px}
/* THE PLATE'S INK, AND NOT THE CONTROLS ON IT. Same trap as .art > svg
   above, and this one had been eating the heart's colour outright: at
   (0,2,1) a descendant rule outranks .wish[aria-pressed="true"] at (0,2,0),
   so the saved heart was painted --paper-ink-2 - 1.9:1 on its own ground
   disc - and had never once gone gold whatever the state said. A colour set
   ON the svg also beats anything the button inherits down to it, so the
   hover never worked either. The plate tints ITS OWN artwork; a button on
   top of the plate owns its glyph. */
.card .art > svg{color:var(--paper-ink-2)}
/* STOCK: green is on the shelf, amber is on order or short, red is gone,
   and .preorder is amber because it is a date and not a count. Each is also
   a word, never a colour alone. */
.stock{font-size:var(--fs-xs);font-weight:500;color:var(--rs-green);display:flex;align-items:center;gap:6px;margin-bottom:13px;line-height:var(--lh-snug);font-variant-numeric:tabular-nums}
.stock.low,.stock.preorder{color:var(--rs-amber)}
.stock.gone{color:var(--rs-red)}
.stock.check{color:var(--ink-2)}
.stock.check a{color:inherit;text-decoration:underline}
.stock i{width:7px;height:7px;border-radius:50%;background:currentColor;display:block;flex:none}
/* Under the price: the condition or pre-order sentence. Ink-2, because a
   card carries a buy button and this is part of what is being bought. */
.preline{font-size:var(--fs-xs);font-weight:500;color:var(--ink-2);line-height:var(--lh-snug);margin-bottom:10px}
/* A grade, as a small pill: "Near Mint", "Lightly Played". Ground plate so
   it reads as a fact about the item rather than a flag about the price. */
.condition{display:inline-flex;align-items:center;background:var(--rs-ground);color:var(--ink-2);border:1px solid var(--line-2);
           border-radius:var(--r-chip);font-family:var(--font-display);font-size:var(--fs-2xs);font-weight:500;letter-spacing:.4px;
           padding:4px 9px 2px;line-height:var(--lh-snug);margin-bottom:10px;vertical-align:middle}
.fambadge{font-size:var(--fs-2xs);font-weight:500;color:var(--muted);margin-bottom:6px}
@media (max-width:1024px){ .g4{grid-template-columns:repeat(3,1fr)} }
@media (max-width:768px){
  .g4,.g3{grid-template-columns:repeat(2,1fr)}
  .grid{gap:16px}
  .card .body{padding:13px 14px 15px}
  .card h3{min-height:0}
  .card .art{height:172px}
}
@media (max-width:400px){ .g4,.g3{grid-template-columns:1fr} .card .art{height:210px} }

/* =============================================================================
   RAIL - a horizontal product row with its heading
   -----------------------------------------------------------------------------
   On a phone it is a scroll-snap strip, 72% a card so the next one shows
   its edge and nothing has to announce the scroll. From 700px it is a grid:
   three across, four from 1025px. The <li> carries .card.
   ============================================================================= */
.rail-h{display:flex;align-items:flex-end;justify-content:space-between;gap:12px;margin-bottom:18px}
.rail-h h2{font-size:var(--fs-3xl);line-height:var(--lh-tight);color:var(--ink)}
.rail-h p{color:var(--muted);font-size:var(--fs-sm);margin-top:5px}
.rail-h a{color:var(--accent-ink);font-family:var(--font-display);font-weight:500;font-size:var(--fs-md);white-space:nowrap;padding:3px 0}
.rail-h a:hover{text-decoration:underline;text-underline-offset:3px}
.rail{display:flex;gap:14px;list-style:none;margin:0;padding:0 0 10px;max-width:100%;overflow-x:auto;overscroll-behavior-x:contain;
      -webkit-overflow-scrolling:touch;scroll-snap-type:x mandatory;scroll-padding-inline:4px}
.rail > li{flex:0 0 72%;min-width:0;scroll-snap-align:start}
/* The scroller clips an outset ring, so it goes inset here. */
.rail .wish:focus-visible,.rail .addbtn:focus-visible{outline-offset:-3px}
@media (min-width:700px){
  .rail{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:18px;overflow:visible;padding-bottom:0}
  .rail > li{flex:auto}
  .rail .wish:focus-visible,.rail .addbtn:focus-visible{outline-offset:2px}
}
@media (min-width:1025px){ .rail{grid-template-columns:repeat(4,minmax(0,1fr));gap:20px} }

/* =============================================================================
   BANDS - visit, play free, discord, brand hero
   ============================================================================= */

/* VISIT THE SHOP: the dev site's gold section. Purple on gold (7.2:1), a
   photograph beside the copy. Buttons on it flip to the purple-2 fill, and
   the ring is purple here (see focus at the top). */
/* THE VISIT BAND, remade to the dev site's own contact layout at the
   client's ask (31 Aug): the dark torn map IS the panel - the client's
   point when the map-as-texture kept losing - overhanging a flat gold
   band, the compass marking the address, the phone raven signing the
   number, and the photograph on the band beside it. The map panel sits in
   the chrome scope at the top of this sheet - dark in both themes, so its
   ink and gold are the chrome's own and the ledger keeps measuring them.
   The map PNG stretches 100%/100% rather than covering, because its torn
   edge is the panel's whole silhouette and a crop would cut it off. */
.visit{display:grid;grid-template-columns:minmax(0,1.05fr) minmax(0,1fr);gap:48px;align-items:center;
       background:var(--rs-gold);color:var(--rs-purple);border-radius:var(--r-sheet);
       padding:26px 44px;margin:58px 0 48px}
.visit-map{background:url("/assets/dark-map.png") center/100% 100% no-repeat;
           padding:68px 74px 88px;margin:-58px 0;color:var(--ink)}
.visit-map .kicker{color:var(--accent-ink)}
.visit-map h2{font-size:var(--fs-5xl);color:var(--ink);margin-bottom:14px}
.visit-where{display:flex;gap:22px;align-items:center;margin:8px 0 2px}
.visit-where > img{width:74px;height:72px;flex:none}
.visit-name{font-size:var(--fs-lg);font-weight:700;letter-spacing:.6px;color:var(--ink);margin-bottom:4px}
.visit-map address{font-style:normal;line-height:var(--lh-relaxed)}
.visit-map address a{color:var(--accent-ink);text-decoration:underline;text-underline-offset:3px;font-weight:500}
.visit-map table{border-collapse:collapse;margin:14px 0 4px;font-size:var(--fs-sm);color:var(--accent-ink)}
.visit-map th{text-align:left;font-weight:500;padding:3px 16px 3px 0}
.visit-map td{padding:3px 0}
.visit-tel{display:flex;align-items:center;gap:14px;margin-top:8px}
.visit-tel a{color:var(--ink);font-weight:700;font-size:var(--fs-lg)}
.visit-tel img{width:60px;height:59px}
/* Gold outline on the dark map - the purple-filled button this replaced
   would simply vanish into the panel. */
.visit .btn{background:transparent;color:var(--rs-gold);border:2px solid var(--rs-gold);
            margin-top:16px;box-shadow:none}
.visit .btn:hover{background:var(--rs-gold);color:var(--rs-purple)}
.visit-art{border-radius:var(--r-card);overflow:hidden;box-shadow:var(--shadow-pop);background:var(--surface-raised)}
.visit-art img{width:100%;height:100%;min-height:280px;object-fit:cover;display:block}
@media (max-width:900px){
  .visit{gap:26px;padding:22px 26px}
  .visit-map{padding:52px 54px 68px;margin:-46px 0}
  .visit-map h2{font-size:var(--fs-4xl)}
}
/* PHONES. The torn PNG is stretched to the panel's own box, and a panel
   twice as tall as it is wide stretches the torn edges with it: the bite
   at the top right reaches an eighth of the way down and the one at the
   bottom left an eighth of the way up, which is where the kicker was being
   lost and the button was standing in the gold (client screenshot, 19 Sep).
   The top and bottom padding are those eighths, measured off the PNG's
   alpha; the sides come in so the address has a line to sit on; the compass
   stands above the address instead of taking a third of its width; and the
   number keeps to one line with a smaller raven beside it. */
@media (max-width:700px){
  .visit{grid-template-columns:1fr;padding:16px 12px;border-radius:var(--r-card);margin:44px 0 0}
  /* THE MAP RUNS OFF THE SIDES. Wider than its gold band and wider than the
     screen (client's ask, 19 Sep): the torn side edges fall off the screen
     rather than standing inside the band, and a wider panel is a shorter
     one for the same words, which stretches the torn top and bottom less.
     The section clips it at the screen edge so the page cannot scroll
     sideways; the band's own overflow stays visible for the top overhang. */
  .visit-band{overflow-x:clip}
  .visit-map{margin:-36px -44px 0;padding:56px 48px 80px}
  /* AND THEN NO MAP AT ALL ON A PHONE (Sean, 20 Sep, from the device in his
     hand). The rule above made the torn panel wider than the screen so its
     edges ran off the sides; on a real phone it still read as a stretched
     picture behind a column of text, because a PNG drawn landscape and
     pulled to a box twice as tall as it is wide has nothing left of the map
     but smear. So the phone gets the WORDS on the site's own dark ground, as
     a plain card inside the gold band, and the torn map stays where it has
     the width to be one.

     Declared after the rule it overrides rather than instead of it, so
     taking this block out puts the run-off map back exactly as it was. The
     ground is a token the contrast ledger already measures ink and gold
     against, which the map's own pixels never were. */
  .visit-map{background:var(--rs-ground-2);margin:0;padding:26px 22px 28px;
             border-radius:var(--r-card)}
  .visit-art img{min-height:0;aspect-ratio:16/10}
  .visit-map h2{font-size:var(--fs-3xl)}
  /* No compass on a phone (Sean, 19 Sep): beside the address it took a
     third of the line, above it a block of height, and at either size it
     was not a fit. The address stands on its own. */
  .visit-where > img{display:none}
  .visit-tel{gap:12px}
  .visit-tel a{white-space:nowrap}
  .visit-tel img{width:48px;height:47px}
}

/* PLAY FREE: PAPER, which is where the dev site has it. Its own band sits
   straight on the white body with purple copy and the wizard standing on
   nothing, because the wizard PNG was drawn for a white ground: on a purple
   panel it needed a plate under it, and a plate on a panel is two boxes
   doing one box's job. The heading is purple (12.7:1), the copy paper-ink-2
   (8.8:1), and the button keeps its gold fill with the purple label. */
.playfree{display:grid;grid-template-columns:minmax(200px,2fr) minmax(0,3fr);gap:32px;align-items:center;
          background:var(--paper);color:var(--paper-ink);border-radius:var(--r-sheet);padding:32px 36px;margin:10px 0}
.playfree-art{display:flex;align-items:center;justify-content:center;min-height:220px}
.playfree-art img{max-height:280px;width:auto;object-fit:contain}
.playfree .kicker{color:var(--rs-purple)}
.playfree h2{font-size:var(--fs-5xl);color:var(--rs-purple);margin-bottom:12px}
.playfree p{font-size:var(--fs-md);line-height:var(--lh-relaxed);color:var(--paper-ink-2);max-width:56ch}
.playfree p b,.playfree p strong{color:var(--paper-ink)}
.playfree p + p{margin-top:10px}
.playfree .btn{margin-top:18px}
.playfree a:not(.btn){color:var(--rs-purple);text-decoration:underline;text-underline-offset:3px;font-weight:500}
/* PHONES: THE WIZARD SITS BESIDE THE HEADING, not above it. Stacked, he
   stood on a hundred pixels of nothing between the gold band and the words
   (client screenshot, 19 Sep). The heading and the wizard now share the
   first row, both standing on its baseline, and the copy runs full width
   beneath. The text wrapper becomes display:contents so its children can
   take grid cells of their own; the markup is unchanged and the desktop
   layout above never sees it. margin:0 because the section before it
   already pays its 36px, and the band before that no longer pays twice. */
@media (max-width:700px){
  .playfree{grid-template-columns:minmax(0,1fr) auto;gap:0 14px;align-items:end;padding:20px;border-radius:var(--r-card);margin:0}
  .playfree > div:not(.playfree-art){display:contents}
  .playfree-art{grid-column:2;grid-row:1;min-height:0;align-self:end}
  .playfree-art img{max-height:140px}
  .playfree h2{grid-column:1;grid-row:1;align-self:end;font-size:var(--fs-3xl);margin-bottom:0}
  .playfree p,.playfree .btn{grid-column:1 / -1}
  .playfree h2 ~ p:first-of-type{margin-top:12px}
}

/* DISCORD: the LILAC band, which is the dev site's own bg-lp row, with the
   purple mark straight on it (6.7:1 as a shape) rather than on a gold disc
   it never had. Copy is purple, secondary copy paper-ink-2 (5.1:1), and the
   button is purple with an ink label - on a light band the button is never
   gold, because a gold fill on lilac is a 1.4:1 edge. */
/* THE COMPANION APP ROW. Ravenspire's own event app, so this is a purple
   panel with the shop's gold on it rather than the lilac the Discord row
   borrows from the dev theme: one is the same brand, the other is somewhere
   else. Ink on purple is 11.9:1 and the gold button's purple label 9.1:1. */
.appjoin{display:flex;align-items:center;gap:24px;flex-wrap:wrap;background:var(--rs-purple);
  color:var(--ink);border-radius:var(--r-card);padding:22px 26px;margin:26px 0;
  border:1px solid var(--line-2);box-shadow:var(--shadow-card)}
.appjoin-icon{flex:none;width:72px;height:72px;border-radius:var(--r-input)}
.appjoin h2{font-size:var(--fs-3xl);color:var(--accent-ink);line-height:var(--lh-tight)}
.appjoin p{font-size:var(--fs-md);color:var(--ink-2);line-height:var(--lh-body);margin-top:4px}
/* The words take the slack and the mark and the button keep their size, so
   the three stay on one line until there is genuinely no room. Without this
   the paragraph claimed its full measure and pushed the mark onto a line of
   its own, which read as a band with a logo floating above it. */
.appjoin > div:not(.appjoin-actions){flex:1 1 300px;min-width:0}
/* THE ACTIONS STACK: the web-app button on top, the two store links as a
   row beneath it, the column sitting at the band's right edge. The store
   links are ghost buttons - gold outline on the purple, an existing ledger
   pair - so the one gold fill stays the primary action. */
.appjoin-actions{margin-left:auto;display:flex;flex-direction:column;gap:10px;align-items:stretch}
.storelinks{display:flex;gap:8px;list-style:none;margin:0;padding:0}
.storelinks li{flex:1 1 0}
.storelinks .btn{width:100%;white-space:nowrap}
@media (max-width:680px){
  .appjoin{gap:16px;padding:18px}
  .appjoin-actions{margin-left:0;width:100%}
  .storelinks{flex-wrap:wrap}
  .storelinks li{flex:1 1 100%}
}
.discord{display:flex;align-items:center;gap:24px;flex-wrap:wrap;background:var(--rs-lilac);color:var(--rs-purple);
         border-radius:var(--r-sheet);padding:26px 32px;margin:10px 0}
.discord img,.discord svg,.discord-icon{flex:none;width:56px;height:56px;padding:0;border-radius:0;background:none}
.discord h2{font-size:var(--fs-3xl);color:var(--rs-purple);line-height:var(--lh-tight)}
.discord p{font-size:var(--fs-md);color:var(--paper-ink-2);line-height:var(--lh-body);margin-top:4px}
.discord .btn{margin-left:auto;background:var(--rs-purple);color:var(--on-fill);box-shadow:none}
.discord .btn:hover{background:var(--rs-purple-2);box-shadow:none}
@media (max-width:700px){
  .discord{padding:20px;gap:16px;border-radius:var(--r-card)}
  .discord .btn{margin-left:0;width:100%}
}

/* BRAND HERO: the game's art as the field, a ground ramp at the foot for
   the logo and the heading, the same plate logic as the game tiles. */
.brandhero{position:relative;overflow:hidden;border-radius:var(--r-sheet);background:var(--rs-purple);min-height:280px;
           display:flex;align-items:flex-end;margin:8px 0 18px;isolation:isolate;box-shadow:var(--shadow-card)}
/* THE BANNER OPPOSES THE PAGE - the client's call, 30 Aug: the light page
   mounts the DARK plate and the dark page mounts the LIGHT one, because a
   band that matches its page dissolves into it and the artwork is the one
   thing this section exists to show. The template hands both files over as
   custom properties and the theme resolves the opposite one; only the
   resolved background-image is fetched, so this costs one download, not
   two. An art-riding brand sets both properties to its one file and is the
   same both ways. The ink counterchanges with the plate: .brandhero.paired
   sits in the chrome scope at the top of this sheet, so on the light page
   the words are the dark theme's over the dark plate, and the dark-page
   paper treatment below overrides that back the other way. */
.brandhero > img,.brandhero-art{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:0}
.brandhero-art{background:var(--bh-dark) center/cover no-repeat}
:root[data-theme="dark"] .brandhero-art{background-image:var(--bh-light)}
.brandhero::after{content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
                  background:linear-gradient(to top,var(--rs-ground) 0%,var(--rs-ground) 18%,transparent 75%);opacity:.94}
/* NO ARTWORK, NO SCRIM. The scrim's one job is fading artwork into the
   page ground; over a bare plate it bleached the band toward the page and
   the result read as a missing cover image (the client's words, 1 Sep).
   A plain brand keeps the solid purple plate in both themes and takes the
   chrome ink from the scope above - the deliberate look for a brand whose
   photographs have not arrived yet, not a broken load. The band also does
   not need 280px of nothing: it hugs its own copy. */
.brandhero.plain::after{content:none}
.brandhero.plain{min-height:0}
.brandhero > *:not(img):not(.brandhero-art){position:relative;z-index:2}
.brandhero-copy,.brandhero .txt{padding:32px 36px 30px;max-width:640px}
.brandhero h1{font-size:var(--fs-6xl);color:var(--ink);line-height:var(--lh-flat)}
.brandhero h1 em{font-style:normal;color:var(--accent-ink)}
.brandhero p{font-size:var(--fs-md);color:var(--ink-2);line-height:var(--lh-relaxed);margin-top:10px;max-width:56ch}

/* A PAIRED HERO ON THE DARK PAGE rides the LIGHT plate, so the paper
   treatment takes over: the band, the scrim and the ink all go the way of
   the artwork, not the page. Explicit colours rather than the role tokens,
   because the chrome scope has already redefined those on this element -
   these rules are the counterchange's other half. The gold accent becomes
   purple because gold on paper is 1.3:1, and the scrim that lifts white
   text off dark artwork becomes a white one doing the same job the other
   way up. The paper tokens do not flip with the theme, so this band is the
   one light thing on the dark page - which is the point. */
:root[data-theme="dark"] .brandhero.paired{background:var(--paper-2)}
/* Much shallower than the dark hero's scrim, and for a reason: that one
   lifts white text off unpredictable artwork, whereas here the type is dark
   on paper and legible without help. Copied at the dark version's depth it
   simply bleached the bottom third of every pack. */
:root[data-theme="dark"] .brandhero.paired::after{background:linear-gradient(to top,var(--paper) 0%,transparent 32%);opacity:.7}
:root[data-theme="dark"] .brandhero.paired h1{color:var(--paper-ink)}
:root[data-theme="dark"] .brandhero.paired h1 em{color:var(--rs-purple)}
:root[data-theme="dark"] .brandhero.paired p{color:var(--paper-ink-2)}
:root[data-theme="dark"] .brandhero.paired :focus-visible{outline:var(--focus-ring-paper);outline-offset:2px}
@media (max-width:768px){
  /* THE HERO STACKS ON A PHONE, and it has to. The band is object-fit:
     cover across the full width: at 1920x480 in a 460px box the crop lands
     square on the artwork's middle, which is exactly where the product is,
     so the heading was printed straight over the packs and could not be
     read. Overlaying words on a picture only works while there is somewhere
     on the picture with nothing in it, and at this width there is not.
     So the artwork becomes a strip along the top and the copy sits under it
     on the band's own colour, where it is words on a flat ground again. */
  .brandhero{display:block;min-height:0;border-radius:var(--r-card)}
  .brandhero > img,.brandhero-art{position:relative;height:132px;
                                                       object-position:right center}
  .brandhero::after{display:none}
  .brandhero-copy,.brandhero .txt{padding:18px 20px 20px;max-width:none}
  .brandhero h1{font-size:var(--fs-4xl)}
}

/* PROMO CARDS - a generic campaign rail, four across, one per live promo.
   The copy plate is solid purple under a gold keyline; the wash is
   decorative. No themes: one look, the brand's. */
.camprow{display:grid;grid-template-columns:repeat(auto-fit,minmax(232px,1fr));gap:14px;list-style:none;margin:0;padding:0;max-width:100%}
.camprow > li{min-width:0}
.campcard{position:relative;display:block;height:300px;overflow:hidden;border-radius:var(--r-card);background:var(--surface-raised);box-shadow:var(--shadow-card);
          transition-property:translate,scale,box-shadow;transition-duration:var(--t-base),var(--t-press),var(--t-base);transition-timing-function:var(--ease-out)}
.campcard:hover{translate:0 -3px;box-shadow:var(--shadow-pop)}
.campcard:active{scale:var(--press-scale)}
.campcard:focus-visible{outline:var(--focus-ring);outline-offset:3px}
.campcard-art{position:absolute;inset:0;overflow:hidden;background:var(--surface-raised)}
.campcard-art img{width:100%;height:100%;object-fit:cover;display:block;transition-property:scale;transition-duration:var(--t-slow);transition-timing-function:var(--ease-out)}
.campcard:hover .campcard-art img{scale:1.06}
.campcard-art::before{content:"";position:absolute;inset:0;z-index:1;pointer-events:none;background:var(--surface-raised);opacity:.17;
                      transition-property:opacity;transition-duration:var(--t-base);transition-timing-function:var(--ease-out)}
.campcard:hover .campcard-art::before{opacity:.06}
.campcard-copy{position:absolute;left:0;right:0;bottom:0;z-index:3;display:flex;flex-direction:column;gap:5px;padding:14px 16px 15px;
               background:var(--surface-raised);color:var(--ink);border-top:3px solid var(--rs-gold)}
.campcard-name{font-family:var(--font-display);font-size:var(--fs-lg);font-weight:700;line-height:var(--lh-tight);color:var(--accent-ink);font-variant-numeric:tabular-nums}
.campcard-line{font-size:var(--fs-xs);line-height:var(--lh-snug);color:var(--ink-2);display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden}
.campcard-cta{display:flex;align-items:center;gap:9px;margin-top:3px;font-family:var(--font-display);font-size:var(--fs-sm);font-weight:500;letter-spacing:.2px}
.campcard-go{flex:none;width:30px;height:30px;border-radius:50%;background:var(--rs-gold);color:var(--rs-purple);display:flex;align-items:center;justify-content:center;
             transition-property:translate;transition-duration:var(--t-base);transition-timing-function:var(--ease-out)}
.campcard-go svg{margin-left:1px}
.campcard:hover .campcard-go{translate:3px 0}
.campcard-type .campcard-copy{top:0;justify-content:flex-end;padding:18px 18px 17px}
.campcard-type .campcard-name{font-size:var(--fs-2xl)}
.campcard-type .campcard-line{-webkit-line-clamp:4}
@media (max-width:1024px){ .camprow{grid-template-columns:repeat(auto-fit,minmax(216px,1fr))} .campcard{height:268px} }
@media (max-width:768px){ .camprow{grid-template-columns:repeat(2,minmax(0,1fr))} .campcard{height:250px} .campcard-name{font-size:var(--fs-md)} }
@media (max-width:480px){ .camprow{grid-template-columns:minmax(0,1fr)} .campcard{height:236px} }

/* =============================================================================
   FOOTER - ground-2 under a gold rule
   ============================================================================= */
/* The dev site's leather grain under the footer, over the same ground the
   flat colour painted - texture as depth, not as information, so nothing
   was re-measured. */
.ftr{background:var(--rs-ground-2) url("/assets/footer-bg.jpg") center/cover;color:var(--ink-2);margin-top:14px;border-top:3px solid var(--rs-gold)}
/* The grain is 436px tall and a stacked phone footer runs four times
   that: cover scales by the element's height and smears the leather into
   fog - the client caught it. So the phone repeats instead of stretching:
   the tile is the grain mirrored onto itself (footer-bg-tile), which
   makes every join its own reflection and therefore seamless, laid at the
   panel's width so the grain keeps its density. */
@media (max-width:768px){
  .ftr{background:var(--rs-ground-2) url("/assets/footer-bg-tile.jpg") top center/100% auto repeat-y}
}
/* Social roundels: gold discs that carry their own contrast, so the footer
   needs nothing from them but alignment. */
.ftr .soc{display:inline-flex;align-items:center;gap:8px}
.ftr .soc img{border-radius:50%}
.ftr .wrap{padding:40px 32px 30px;display:flex;gap:52px;flex-wrap:wrap}
.ftr h2{color:var(--accent-ink);font-size:var(--fs-xs);font-weight:500;margin-bottom:12px;letter-spacing:1.4px;text-transform:uppercase}
.ftr ul{list-style:none}
.ftr a{display:block;font-size:var(--fs-sm);padding:8px 0;color:var(--ink-2)}
.ftr a:hover{color:var(--accent-ink);text-decoration:underline;text-underline-offset:3px}
.ftr .about{max-width:280px;font-size:var(--fs-sm);line-height:var(--lh-relaxed)}
.ftr .logo i{color:var(--ink)}
/* THE FOOTER MARK, and why it was wrong. It used to ask for width:180px
   here while .logo img asked for max-height:52px there. width won the width
   and max-height won the height, so a 220x197 raven was drawn at 180x52:
   the aspect was not squashed by one rule, it was decided by two. Both
   contexts now state a HEIGHT and leave the width to the file, which is the
   only way an aspect survives a second opinion. */
.ftrlogo img{height:84px;width:auto;max-width:100%}
.ftrlogo i{font-style:normal}
/* The company identification: present and permanent, not prominent. */
.ftrlegal{margin-top:12px;font-size:var(--fs-2xs);line-height:var(--lh-relaxed);color:var(--muted)}
.ftrlegal + .ftrlegal{margin-top:8px}
.ftrtrust{background:var(--rs-ground)}
.ftr .ftrtrust .wrap{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px 32px;padding-top:18px;padding-bottom:18px;list-style:none}
.ftrtrust li{font-size:var(--fs-xs);line-height:var(--lh-body);color:var(--muted)}
.ftrtrust b{display:block;color:var(--ink);font-size:var(--fs-sm);margin-bottom:2px}
@media (min-width:1025px){
  .ftr .wrap{display:grid;align-items:start;grid-auto-flow:column;grid-template-columns:minmax(0,1.6fr);grid-auto-columns:minmax(0,1fr);gap:34px 40px}
  .ftr .about{max-width:36ch}
}
@media (max-width:1024px){
  .ftr .wrap{display:grid;grid-template-columns:1fr 1fr;gap:28px 36px;padding:34px 26px 30px}
  .ftr .about{grid-column:1/-1;max-width:none}
  .ftr .ftrtrust .wrap{grid-template-columns:repeat(2,minmax(0,1fr));gap:14px 26px}
}
@media (max-width:768px){ .ftr .wrap{gap:24px;padding:30px 20px 26px} }
@media (max-width:480px){ .ftr .ftrtrust .wrap{grid-template-columns:1fr;gap:12px} }
@media (max-width:400px){ .ftr .wrap{gap:22px 18px;padding:26px 18px 24px} }
@media (pointer:coarse){ .ftr a{display:flex;align-items:center;min-height:var(--tap-min);padding:0} }

/* =============================================================================
   LISTINGS - crumb, head, sort, rail, chips, sheet, pager
   ============================================================================= */
.crumb{font-size:var(--fs-xs);color:var(--muted);padding:16px 0}
.crumb ol{list-style:none;display:flex;gap:6px;flex-wrap:wrap}
.crumb a{padding-top:5px;padding-bottom:5px}
.crumb a:hover{color:var(--accent-ink);text-decoration:underline;text-underline-offset:3px}
/* The add-to-basket receipt band: quiet confirmation in the shop's own
   good-news green, not a modal - the shopper asked to keep browsing and
   this must not take the page from them. */
.addednote{margin:14px 0 0;padding:11px 16px;border-radius:var(--r-card);
           background:var(--stock-tint,rgba(31,107,69,.12));color:var(--ink);
           font-size:var(--fs-sm)}
.addednote a{color:var(--accent-ink);font-weight:600;text-decoration:underline;text-underline-offset:3px}

/* THE HOME CRUMB IS THE RAVEN - the dev site's own mark for the start of
   the trail, a bird on a gold branch. Two colourways because the plumage
   has to be the page's ink: the purple bird sits on the light page and a
   light-feathered sibling (recoloured from the same file, branch untouched)
   takes the dark one. The word Home stays in the link for screen readers -
   the picture is decoration, the word is the label. */
.crumb-home{display:inline-block;width:27px;height:23px;vertical-align:-7px;
            background:url("/assets/icon_bird_branch_purple.svg") center/contain no-repeat}
:root[data-theme="dark"] .crumb-home{background-image:url("/assets/icon_bird_branch_light.svg")}
.crumb [aria-current]{color:var(--ink);font-weight:500}
.plphead{display:flex;justify-content:space-between;align-items:flex-end;margin-bottom:18px;gap:14px;flex-wrap:wrap}
.plphead h1{font-size:var(--fs-5xl);color:var(--ink)}
.plphead .cnt,.bhead .cnt{color:var(--muted);font-size:var(--fs-sm);margin-top:6px;font-variant-numeric:tabular-nums}
.plphead .cnt b{color:var(--ink)}
.plplead{max-width:68ch;color:var(--ink-2);font-size:var(--fs-md);line-height:var(--lh-body);margin:-4px 0 18px}
.plpguide{max-width:68ch;margin:36px 0 8px;padding-top:26px;border-top:1px solid var(--line)}
.plpguide h2{font-size:var(--fs-2xl);margin-bottom:10px;color:var(--ink)}
.plpguide p{color:var(--ink-2);font-size:var(--fs-sm);line-height:var(--lh-body);margin-top:12px}
/* The sort select is a paper control, like every input. */
.sortbox{display:flex;gap:9px;align-items:center;font-size:var(--fs-sm);font-weight:500}
.sortbox select{border:2px solid var(--line-control-paper);border-radius:var(--r-pill);padding:9px 17px;font-size:var(--fs-sm);font-weight:500;
                background:var(--paper);color:var(--paper-ink);min-height:var(--tap-min)}
.plpbody{display:flex;gap:36px;align-items:flex-start;padding-bottom:44px}

/* THE FILTER RAIL. Real checkboxes, gold accent. A zero-count option is
   shown, disabled, with its zero; the mega menu's rule is the opposite. */
.filters{width:256px;flex:none;padding-right:6px}
.fgrp{border:0;border-bottom:1px solid var(--line-2);margin:0;min-width:0;padding:18px 0}
/* A game can carry thirty live sets; the group scrolls rather than making
   the rail a column of one facet. */
.fgrp.setfacet ul{max-height:264px;overflow-y:auto}
.fgrp:first-of-type{padding-top:0}
.fgrp:last-of-type{border-bottom:0}
.fgrp legend,.fgrp h3{font-family:var(--font-display);font-size:var(--fs-md);font-weight:500;color:var(--ink);margin:0 0 13px;padding:0;width:100%}
.fgrp ul{list-style:none}
.fopt{display:flex;align-items:center;gap:9px;font-size:var(--fs-sm);padding:5px 0;color:var(--ink-2);min-height:32px;cursor:pointer}
.fopt input[type=checkbox]{width:18px;height:18px;margin:0;flex:none;accent-color:var(--rs-gold);border-radius:5px}
.fopt .c{margin-left:auto;padding-left:12px;min-width:26px;text-align:right;color:var(--muted);font-size:var(--fs-xs);font-variant-numeric:tabular-nums}
.fopt:has(input:disabled){color:var(--muted);cursor:not-allowed}
.fopt:hover{color:var(--ink)}
.fmore{font-size:var(--fs-xs)}
/* The price facet: bands, two handles, and the number inputs that are the
   non-dragging alternative SC 2.5.7 asks for. */
.pricefacet .bands{display:flex;flex-wrap:wrap;gap:7px;margin-bottom:14px;list-style:none}
.pricefacet .bands button{border:1.5px solid var(--line-control);background:transparent;border-radius:var(--r-chip);padding:7px 12px;font-size:var(--fs-xs);font-weight:500;color:var(--ink);min-height:var(--tap-min);font-variant-numeric:tabular-nums}
.pricefacet .bands button[aria-pressed=true]{background:var(--rs-gold);border-color:var(--rs-gold);color:var(--rs-purple)}
.pricefacet .bands button:not([aria-pressed=true]):not(:disabled):hover{border-color:var(--rs-gold);color:var(--accent-ink)}
.pricefacet .sliders{position:relative;height:34px;margin:4px 0 6px}
.pricefacet input[type=range]{position:absolute;left:0;right:0;top:8px;width:100%;margin:0;background:none;pointer-events:none;-webkit-appearance:none;appearance:none;height:18px}
.pricefacet input[type=range]::-webkit-slider-thumb{pointer-events:auto;-webkit-appearance:none;width:20px;height:20px;border-radius:50%;background:var(--rs-gold);border:3px solid var(--rs-purple);box-shadow:var(--shadow-card)}
.pricefacet input[type=range]::-moz-range-thumb{pointer-events:auto;width:20px;height:20px;border-radius:50%;background:var(--rs-gold);border:3px solid var(--rs-purple)}
.pricefacet .track{position:absolute;left:0;right:0;top:15px;height:4px;background:var(--line-2);border-radius:3px}
.pricefacet .fill{position:absolute;top:0;bottom:0;background:var(--rs-gold);border-radius:3px}
.pricefacet .nums{display:flex;align-items:center;gap:8px}
.pricefacet .nums label{font-size:var(--fs-xs);color:var(--muted)}
.pricefacet .nums input{width:78px;border:2px solid var(--line-control-paper);border-radius:var(--r-input);padding:7px 9px;font-size:var(--fs-sm);
                        color:var(--paper-ink);background:var(--paper);min-height:var(--tap-min);font-variant-numeric:tabular-nums}

/* APPLIED CHIPS: a purple pill with the filter's name and a gold cross. The
   cross is 36px and reaches 44 through a 4px ring that stays inside the
   chip, so wrapped chips never overlap targets. */
.chips{display:flex;gap:9px;flex-wrap:wrap;margin-bottom:18px;align-items:center;list-style:none}
.chip{background:var(--surface-raised);color:var(--ink);border:1.5px solid var(--line-2);border-radius:var(--r-chip);padding:0 6px 0 12px;
      font-size:var(--fs-xs);font-weight:500;display:flex;gap:4px;align-items:center;min-height:var(--tap-min);font-variant-numeric:tabular-nums}
.chip a,.chip button{background:none;border:0;color:var(--accent-ink);font-weight:700;font-size:var(--fs-md);line-height:1;width:36px;height:36px;border-radius:50%;
                     display:flex;align-items:center;justify-content:center;position:relative}
.chip a::after,.chip button::after{content:"";position:absolute;inset:-4px}
.chip a:hover,.chip button:hover{background:var(--rs-purple-2)}
.clearall{font-size:var(--fs-xs)}

/* The phone trigger and the sheet. .fbar is the Filter button in the head;
   .sheetbar is the Apply bar fixed under the open sheet. */
.fbar{display:none;gap:10px;padding:0 0 14px}
.fbar button{flex:1;border:2px solid var(--rs-gold);background:transparent;border-radius:var(--r-pill);min-height:var(--tap-min);
             font-family:var(--font-display);font-weight:500;font-size:var(--fs-md);color:var(--accent-ink);display:flex;align-items:center;justify-content:center;gap:8px;padding-top:2px}
.fbar button:not(:disabled):hover{background:var(--rs-gold);color:var(--rs-purple)}
.fbar .count{background:var(--rs-gold);color:var(--rs-purple);border-radius:11px;padding:2px 7px 0;font-size:var(--fs-xs);font-weight:700;font-variant-numeric:tabular-nums}
.fbar button:hover .count{background:var(--rs-purple);color:var(--rs-gold)}
.sheetbar{display:none}
.filters:not([data-open]){box-shadow:none}

.pager{display:flex;gap:8px;justify-content:center;padding:28px 0 8px;list-style:none;flex-wrap:wrap}
.pager a,.pager span{min-width:var(--tap-min);min-height:var(--tap-min);display:flex;align-items:center;justify-content:center;border:2px solid var(--line-2);
                     border-radius:var(--r-input);font-family:var(--font-display);font-weight:500;font-size:var(--fs-md);padding:2px 12px 0;font-variant-numeric:tabular-nums;color:var(--ink)}
.pager [aria-current]{background:var(--rs-gold);color:var(--rs-purple);border-color:var(--rs-gold)}
.pager a:hover{border-color:var(--rs-gold);color:var(--accent-ink)}

@media (max-width:1024px){ .plpbody{gap:30px} .filters{width:232px} }
@media (max-width:768px){
  /* One row: Filter left, sort right, equal halves. Go hides only once the
     script has said so by setting data-autosubmit. */
  .plphead{display:grid;grid-template-columns:1fr 1fr;grid-template-areas:"title title" "filter sort";gap:12px 10px;align-items:center}
  .plphead > div:first-child{grid-area:title}
  .plphead h1{font-size:var(--fs-2xl)}
  .plphead .fbar{grid-area:filter;padding:0;display:flex}
  .plphead .fbar button{width:100%}
  .plphead .sortbox{grid-area:sort;width:auto;gap:0}
  .plphead .sortbox label{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}
  .plphead .sortbox form{display:flex;gap:8px;width:100%}
  .plphead .sortbox select{width:100%;min-height:var(--tap-min)}
  .plphead .sortbox form[data-autosubmit] .btn{display:none}
  .fbar{display:flex}
  .plpbody{flex-direction:column;gap:0}
  /* THE SHEET. 68svh, not vh: vh counts the browser chrome that hides on
     scroll. The bottom padding clears the fixed Apply bar plus the home
     indicator. translateY(101%) is how it is off-screen when closed, so
     the reduced-motion block narrows the transition rather than removing
     the transform. */
  .filters{position:fixed;inset:auto 0 0 0;width:auto;max-height:68svh;overflow:auto;background:var(--rs-ground-2);
           border-top:3px solid var(--rs-gold);border-radius:var(--r-sheet) var(--r-sheet) 0 0;box-shadow:var(--shadow-sheet);
           padding:20px 20px calc(100px + var(--inset-b));z-index:60;transform:translateY(101%);
           transition-property:transform;transition-duration:var(--t-base);transition-timing-function:var(--ease-in-out)}
  .filters[data-open]{transform:none}
  .sheetbar{display:block;position:fixed;left:0;right:0;bottom:0;padding:12px 20px calc(14px + var(--inset-b));
            background:var(--rs-ground-2);border-top:1px solid var(--line-2);z-index:61}
  .filters:not([data-open]) ~ .sheetbar{display:none}
  .sheetbar .btn{width:100%}
  .chips{gap:8px;margin-bottom:16px}
  .fgrp{padding:16px 0}
}
@media (pointer:coarse){ .fopt{min-height:var(--tap-min)} .fopt input[type=checkbox]{width:22px;height:22px} }

/* =============================================================================
   PRODUCT PAGE
   ============================================================================= */
.pdp{display:flex;gap:44px;padding:8px 0 44px;align-items:flex-start}
.gal{width:540px;flex:none}
/* The main photograph on the same purple-2 plate the card uses. */
.galmain{height:430px;background:var(--paper-2);border-radius:var(--r-sheet);display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden}
.galmain img{max-width:92%;max-height:92%;width:auto;height:auto;object-fit:contain}
.zoomtrig{position:relative;display:flex;align-items:center;justify-content:center;align-self:stretch;flex:1;min-width:0;border-radius:var(--r-sheet)}
/* Purple disc, gold glyph: a gold disc on the paper plate would have no
   edge at all (1.3:1), and this one has to be findable to be used. */
.zoomcue{position:absolute;right:12px;bottom:12px;width:38px;height:38px;border-radius:50%;background:var(--rs-purple);color:var(--rs-gold);
         display:flex;align-items:center;justify-content:center;box-shadow:var(--shadow-card)}
.zoomtrig:hover .zoomcue{background:var(--rs-purple-2)}
.thumbs,.galthumbs{display:flex;gap:10px;margin-top:12px;list-style:none;overflow-x:auto;padding-bottom:4px;flex-wrap:nowrap}
.thumb,.galthumb{width:72px;height:72px;padding:4px;background:var(--paper-2);border:2px solid transparent;border-radius:var(--r-input);cursor:pointer;
                 display:flex;align-items:center;justify-content:center;flex:none}
.thumb img,.galthumb img{max-width:86%;max-height:86%;object-fit:contain}
/* Both edges are measured against the PAPER plate they sit on, not against
   the page: --line-control is 2.0:1 there and gold is 1.3:1, so hover takes
   the paper line (4.2:1) and the current thumbnail takes purple (12.1:1). */
.thumb:hover,.galthumb:hover{border-color:var(--line-control-paper)}
.thumb[aria-current=true],.galthumb.on{border-color:var(--rs-purple)}
.thumb:focus-visible,.galthumb:focus-visible{outline-offset:-3px}

.pinfo{flex:1;min-width:0}
.pinfo > * + *{margin-top:14px}
.pinfo .brandline{margin:0 0 2px}
.pinfo .brandline a{color:inherit;text-decoration:underline;text-underline-offset:3px}
.pinfo h1{font-size:var(--fs-5xl);line-height:var(--lh-tight);margin:6px 0 0;color:var(--ink)}
.pinfo .stars{margin-top:10px}
.pprice{display:flex;align-items:baseline;gap:12px;margin:16px 0 0;flex-wrap:wrap;row-gap:4px;font-variant-numeric:tabular-nums}
.pprice .now{font-family:var(--font-display);font-size:var(--fs-6xl);font-weight:700;color:var(--price-ink);line-height:var(--lh-flat)}
.pprice .was{font-size:var(--fs-lg);color:var(--muted);text-decoration:line-through}
.pprice .save{background:var(--rs-gold);color:var(--rs-purple);font-family:var(--font-display);font-size:var(--fs-xs);font-weight:700;padding:6px 11px 4px;border-radius:var(--r-pill);line-height:var(--lh-flat)}
.subst{font-size:var(--fs-xs);color:var(--muted);margin-top:6px;line-height:var(--lh-body)}
.pinfo .preline{margin-top:10px;font-size:var(--fs-sm)}
.pinfo .condition{margin:10px 0 0;font-size:var(--fs-xs)}
.pinfo .stock{margin:16px 0 0;font-size:var(--fs-sm)}
.buyrow{display:flex;gap:12px;margin:18px 0 0;align-items:stretch}
/* The quantity capsule is a paper control: paper buttons with a purple
   glyph, a paper field. Its rings go inset because overflow:hidden clips. */
.qty{display:flex;align-items:center;border:2px solid var(--line-control-paper);border-radius:var(--r-pill);overflow:hidden;background:var(--paper)}
.qty button{width:44px;min-height:var(--tap-min);border:0;background:var(--paper);font-size:var(--fs-xl);font-weight:700;color:var(--rs-purple)}
.qty button:hover{background:var(--paper-2)}
.qty input{width:44px;border:0;text-align:center;font-weight:500;font-size:var(--fs-md);color:var(--paper-ink);background:var(--paper);-moz-appearance:textfield;font-variant-numeric:tabular-nums}
.qty input::-webkit-outer-spin-button,.qty input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
.qty button:focus-visible{outline:var(--focus-ring-paper);outline-offset:-3px}
.bstep .bstepval{width:44px;text-align:center;font-weight:500;font-size:var(--fs-md);color:var(--paper-ink);font-variant-numeric:tabular-nums}
/* Save to wishlist on the PDP: a ghost pill that fills gold when saved. */
.pdpwish{margin-top:12px}
.wishbtn{display:inline-flex;align-items:center;gap:9px;min-height:var(--tap-min);padding:9px 16px;border:2px solid var(--rs-gold);border-radius:var(--r-pill);
         background:transparent;color:var(--accent-ink);font-family:var(--font-display);font-size:var(--fs-sm);font-weight:500;cursor:pointer}
.wishbtn:hover,.wishbtn[aria-pressed="true"]{background:var(--rs-gold);color:var(--rs-purple)}
.keyfacts{display:grid;grid-template-columns:1fr 1fr;gap:11px;margin:20px 0 0;list-style:none}
.fact{background:var(--surface-raised);border-radius:var(--r-input);padding:13px 15px}
/* block is load-bearing: these are spans, and inline they run together. */
.fact .k{display:block;font-family:var(--font-display);font-size:var(--fs-2xs);letter-spacing:1px;color:var(--muted);font-weight:500;text-transform:uppercase;line-height:var(--lh-snug)}
.fact .v{display:block;font-size:var(--fs-sm);font-weight:500;margin-top:4px;line-height:var(--lh-snug);color:var(--ink);font-variant-numeric:tabular-nums}
.fact-gap{background:transparent;border:1px dashed var(--line-control)}
.fact-gap .v{color:var(--muted)}
.gapflag{display:inline-block;margin-left:6px;background:var(--rs-gold);color:var(--rs-purple);font-family:var(--font-display);font-size:var(--fs-3xs);font-weight:700;letter-spacing:.5px;text-transform:uppercase;padding:3px 6px 1px;border-radius:8px;vertical-align:1px}
.feats{list-style:none;margin:16px 0 0}
.feats li{position:relative;padding:4px 0 4px 22px;font-size:var(--fs-sm);color:var(--ink-2);line-height:var(--lh-body)}
.feats li::before{content:"";position:absolute;left:4px;top:12px;width:7px;height:7px;border-radius:50%;background:var(--rs-gold)}
.delivbox{border:2px solid var(--line-2);border-radius:var(--r-card);padding:16px 18px;margin:20px 0 0;list-style:none}
.delivrow{display:flex;gap:12px;align-items:flex-start;padding:9px 0;font-size:var(--fs-sm)}
.delivrow[hidden]{display:none}
.delivrow b{display:block;margin-bottom:2px;line-height:var(--lh-snug);color:var(--ink)}
.delivrow small{display:block;color:var(--muted);line-height:var(--lh-body)}
.delivrow svg{color:var(--rs-green)}
.delivleft,#deliv-promise time{font-variant-numeric:tabular-nums;font-weight:500;color:var(--ink)}
/* A caution panel: purple with an amber edge, ink text. Amber says "read
   this", the words carry the meaning. */
.warn{background:var(--rs-purple);border-left:3px solid var(--rs-amber);border-radius:var(--r-input);padding:13px 15px;font-size:var(--fs-sm);color:var(--ink);display:flex;gap:10px;margin:18px 0 0;line-height:var(--lh-relaxed)}
.warn small{display:block;margin-top:7px;line-height:var(--lh-body);color:var(--ink-2)}
.warn svg{color:var(--rs-amber)}
.prelovedbox{margin-top:12px;padding:11px 13px;background:var(--surface-raised);border-left:3px solid var(--rs-green);border-radius:var(--r-input);font-size:var(--fs-sm);color:var(--ink);line-height:var(--lh-body)}
.prelovedbox b{color:var(--accent-ink)}
/* Read more is the ghost chip on a <summary>; the two spans swap on open. */
.readmore{margin-top:14px}
.readmore summary{display:inline-flex;align-items:center;background:transparent;border:1.5px solid var(--rs-gold);border-radius:var(--r-chip);padding:9px 16px;
                  font-family:var(--font-display);font-size:var(--fs-sm);font-weight:500;color:var(--accent-ink);min-height:var(--tap-min);cursor:pointer;list-style:none}
.readmore summary::-webkit-details-marker{display:none}
.readmore summary:hover{background:var(--rs-gold);color:var(--rs-purple)}
.readmore summary .less{display:none}
.readmore[open] summary .more{display:none}
.readmore[open] summary .less{display:inline}
.faqs{margin-top:26px}
.faq{border-bottom:1px solid var(--line-2)}
.faq summary{list-style:none;cursor:pointer;padding:14px 30px 14px 0;position:relative;font-family:var(--font-display);font-weight:500;color:var(--ink);font-size:var(--fs-md);min-height:var(--tap-min);display:flex;align-items:center}
.faq summary::-webkit-details-marker{display:none}
.faq summary::after{content:"";position:absolute;right:6px;top:50%;width:10px;height:10px;margin-top:-6px;border-right:2px solid var(--rs-gold);border-bottom:2px solid var(--rs-gold);
                    transform:rotate(45deg);transition-property:transform;transition-duration:var(--t-base);transition-timing-function:var(--ease-out)}
.faq[open] summary::after{transform:rotate(-135deg);margin-top:-2px}
.faq p{padding:0 30px 16px 0;color:var(--ink-2);font-size:var(--fs-sm);line-height:var(--lh-relaxed)}
/* Back in stock: a quiet purple block where the buy row would be. */
.notifybox{margin:16px 0 4px;padding:16px;background:var(--rs-purple);border-radius:var(--r-card)}
.notifybox label{display:block;font-family:var(--font-display);font-size:var(--fs-md);font-weight:500;color:var(--ink);margin-bottom:8px}
.notifyrow{display:flex;gap:8px;flex-wrap:wrap}
.notifyrow input{flex:1;min-width:180px;min-height:var(--tap-min);border:2px solid var(--line-control-paper);border-radius:var(--r-pill);padding:0 16px;font-size:var(--fs-sm);background:var(--paper);color:var(--paper-ink)}
.notifyrow input::placeholder{color:var(--paper-muted);opacity:1}
.notifyrow .btn{flex:0 0 auto}
.notifybox small{display:block;margin-top:9px;font-size:var(--fs-2xs);color:var(--muted);line-height:var(--lh-body)}
.notifydone{font-size:var(--fs-sm);font-weight:500;color:var(--rs-green);line-height:var(--lh-body)}
.notifydone.warn{color:var(--rs-amber);background:none;border:0;padding:0;display:block}
.relrail{border-top:1px solid var(--line)}
.xsell{padding:26px 0 8px}
@media (max-width:1024px){ .pdp{gap:28px} .gal{width:min(440px,48%)} }
@media (max-width:768px){
  .pdp{flex-direction:column;gap:22px}
  .gal{width:100%}
  /* 4:3 capped, not square: a square gallery at 768 pushed the price two
     screens down. */
  .galmain{height:auto;aspect-ratio:4/3;max-height:460px}
  .pinfo h1{font-size:var(--fs-2xl)}
  .pprice .now{font-size:var(--fs-4xl)}
  .keyfacts{grid-template-columns:1fr;gap:10px}
  .pinfo .buyrow{margin:14px 0 4px;gap:10px}
  .delivbox{padding:15px 16px}
}
@media (max-width:400px){
  .buyrow{flex-wrap:wrap}
  .qty,.big{flex-basis:100%}
  .pprice .now{font-size:var(--fs-3xl)}
  .notifyrow .btn{width:100%}
}

/* THE LIGHTBOX: a native <dialog>, every display rule scoped to [open].
   Backdrop is the ground at 90%, so it moves with the brand. */
.lightbox{border:0;padding:0;background:transparent;max-width:100%;max-height:100%;width:100%;height:100%}
.lightbox[open]{display:flex;align-items:center;justify-content:center}
.lightbox::backdrop{background:var(--rs-ground);opacity:.9}
.lbstage{border:0;background:transparent;padding:0;display:flex;align-items:center;justify-content:center;max-width:92vw;max-height:82vh;
         min-width:var(--tap-min);min-height:var(--tap-min);cursor:zoom-in;touch-action:none;overflow:hidden}
.lbimg{max-width:92vw;max-height:82vh;width:auto;height:auto;object-fit:contain;border-radius:var(--r-input);background:var(--paper-2)}
.lightbox.zoomed .lbstage{cursor:grab}
.lightbox.zoomed .lbstage:active{cursor:grabbing}
.lightbox.zoomed .lbimg{-webkit-user-select:none;user-select:none}
.lbclose{position:fixed;top:calc(16px + env(safe-area-inset-top,0px));right:calc(16px + env(safe-area-inset-right,0px));width:var(--tap-min);height:var(--tap-min);
         border:0;border-radius:50%;background:var(--rs-gold);color:var(--rs-purple);display:flex;align-items:center;justify-content:center;box-shadow:var(--shadow-pop)}
.lbclose:hover{background:var(--rs-gold-hover)}
.lbhint{position:fixed;left:0;right:0;bottom:calc(18px + var(--inset-b));text-align:center;font-size:var(--fs-xs);color:var(--muted)}
@media (max-width:768px){
  .zoomcue{right:10px;bottom:10px;width:34px;height:34px}
  .lbstage,.lbimg{max-width:94vw;max-height:74vh}
  .lbhint{bottom:12px;padding:0 20px;line-height:var(--lh-body)}
}

/* =============================================================================
   BASKET
   -----------------------------------------------------------------------------
   Lines on the ground; the summary is PAPER, as tokens.css says it is: the
   one panel on the page a shopper does arithmetic on, and it reads as a
   receipt. Its ink is --paper-ink, its link is purple, its ring is purple.
   ============================================================================= */
.bhead{display:flex;align-items:baseline;gap:14px;margin-bottom:4px;flex-wrap:wrap}
.bhead h1{font-size:var(--fs-5xl);color:var(--ink)}
.bhead .warn{flex-basis:100%;margin:10px 0 0}
.basket{display:flex;gap:36px;align-items:flex-start;padding:14px 0 8px}
.bmain{flex:1;min-width:0}
.blines{list-style:none}
.bline{display:grid;grid-template-columns:112px 1fr auto;gap:18px;padding:20px 0;border-bottom:1px solid var(--line-2);align-items:start}
.bline:first-child{padding-top:6px}
.bimg{width:112px;height:112px;background:var(--paper-2);border-radius:var(--r-input);display:flex;align-items:center;justify-content:center;padding:10px;flex:none}
.bimg img{max-width:100%;max-height:100%;width:auto;height:auto;object-fit:contain}
.binfo h3{font-family:var(--font-body);font-size:var(--fs-md);line-height:var(--lh-body);font-weight:500;margin-bottom:7px;max-width:52ch;color:var(--ink)}
.binfo h3 a:hover{text-decoration:underline;text-underline-offset:3px}
.bprice{font-family:var(--font-display);font-size:var(--fs-md);font-weight:500;color:var(--price-ink);display:flex;gap:8px;align-items:baseline;margin-bottom:7px;flex-wrap:wrap;font-variant-numeric:tabular-nums}
.bprice .was{font-family:var(--font-body);font-size:var(--fs-xs);color:var(--muted);text-decoration:line-through;font-weight:500}
.binfo .stock{margin-bottom:0}
.binfo .preline{margin:4px 0 6px}
.bqtyrow{display:flex;align-items:center;gap:18px;margin-top:12px;flex-wrap:wrap}
/* The destructive control: ink-2 at rest, red the moment it is pointed at. */
.bremove{color:var(--ink-2);font-size:var(--fs-xs);text-decoration:underline;text-underline-offset:3px;padding:6px 2px}
.bremove:not(:disabled):hover{color:var(--rs-red)}
.bltotal{font-family:var(--font-display);font-size:var(--fs-lg);font-weight:700;color:var(--accent-ink);text-align:right;font-variant-numeric:tabular-nums;padding-top:4px}
/* MARGIN UNDER IT, NOT JUST PADDING INSIDE IT. A full basket ends in the
   order summary and the checkout button, which is a tall column that holds
   the footer well clear. Empty, the page is one dashed box - and with nothing
   below it the footer starts a few pixels under the dashes and the two read
   as one grey mass. The one page where a shopper has nothing to do is the one
   that most needs to look deliberate rather than truncated. */
.bempty{border:2px dashed var(--line-control);border-radius:var(--r-card);padding:26px;margin-bottom:48px;text-align:center;color:var(--muted);font-size:var(--fs-sm)}
.bempty a{color:var(--accent-ink);font-weight:500;text-decoration:underline;text-underline-offset:3px}

.bsum{width:340px;flex:none;background:var(--paper);color:var(--paper-ink);border-radius:var(--r-card);padding:22px 22px 24px;position:sticky;top:94px}
.bsum h2{font-size:var(--fs-xl);margin-bottom:14px;color:var(--paper-ink)}
.srows{list-style:none}
.srow{display:flex;justify-content:space-between;gap:14px;padding:7px 0;font-size:var(--fs-sm);align-items:baseline;color:var(--paper-ink-2);font-variant-numeric:tabular-nums}
.srow small{display:block;color:var(--paper-muted);font-size:var(--fs-xs);font-weight:400;line-height:var(--lh-body);margin-top:2px}
.srow b{flex:none;color:var(--paper-ink);font-variant-numeric:tabular-nums}
.srow.total{border-top:1.5px solid var(--line-paper);margin-top:8px;padding-top:13px;font-family:var(--font-display);font-size:var(--fs-lg);font-weight:700;color:var(--paper-ink)}
.srow.total b{color:var(--rs-purple)}
.bsum .btn{width:100%;margin-top:16px}
.paynote{display:block;text-align:center;color:var(--paper-muted);font-size:var(--fs-xs);margin-top:9px;line-height:var(--lh-body)}
.contlink{text-align:center;margin-top:12px;font-size:var(--fs-sm)}
.contlink a,.bsum a:not(.btn){color:var(--rs-purple);font-weight:500;text-decoration:underline;text-underline-offset:3px}
/* The sentence under an order's status, on the account page and /track. */
.astatus{font-size:var(--fs-sm);line-height:var(--lh-body);margin:8px 0 10px;color:var(--ink-2)}
/* The address step, same narrow column as the card page so the two read as
   one flow rather than two pages that happen to follow each other. */
.addrform{display:block}
.addrform label{display:block;font-size:var(--fs-sm);margin:14px 0 5px;color:var(--ink)}
.addrform label.opt span{color:var(--muted);font-weight:400}
.addrform input{width:100%;background:var(--paper);color:var(--paper-ink);border:1.5px solid var(--line-paper);border-radius:var(--r-input);padding:12px 13px;font:inherit;font-size:var(--fs-sm)}
.addrform input[aria-invalid="true"]{border-color:rgba(190,40,40,.7)}
.addrform small{display:block;color:var(--muted);font-size:var(--fs-xs);margin-top:5px}
.addrnote{color:var(--muted);font-size:var(--fs-xs);margin:16px 0 4px}
/* The card page. One column, narrow, nothing to look at but the fields and
   the button - it is the last screen before money and every other thing on it
   is a thing to click instead of paying. Square's iframes size themselves, so
   the box only provides the frame around them. */
.pay{max-width:460px;margin:0 auto;padding:32px 0 56px}
.pay h1{font-size:var(--fs-2xl);margin-bottom:6px}
.paytotal{font-size:var(--fs-sm);color:var(--ink-2);margin-bottom:20px}
.paytotal b{color:var(--rs-gold);font-variant-numeric:tabular-nums;font-size:var(--fs-lg)}
.cardbox{background:var(--paper);border-radius:var(--r-card);padding:18px;margin-bottom:14px;min-height:96px}
.payerr{background:rgba(190,40,40,.12);border:1.5px solid rgba(190,40,40,.5);border-radius:var(--r-input);padding:11px 13px;font-size:var(--fs-sm);line-height:var(--lh-body);margin-bottom:14px}
.pay .btn{width:100%}
/* THE SUMMARY PANEL'S RHYTHM. Delivery, discount and totals were three
   blocks of the same weight with no space between them, each ending in a
   full-width gold button - "a bit too mushed together", and the discount box
   worst of all. Sections now carry a hairline and a heading, the quiet
   controls are ghost buttons, and gold is reserved for Checkout. */
.sumsec{padding:14px 0;border-top:1.5px solid var(--line-paper)}
.sumsec-h{font-size:var(--fs-xs);letter-spacing:.08em;text-transform:uppercase;color:var(--paper-muted);margin-bottom:8px;font-weight:600}
.sumsec .srow.dopt{padding:6px 0}
.bsum .btn.sm.sumbtn{width:100%;margin-top:10px}
/* The code box, closed until asked for. */
.dcode-more{margin:0}
.dcode-more summary{cursor:pointer;font-size:var(--fs-sm);color:var(--rs-purple);text-decoration:underline;text-underline-offset:3px;padding:2px 0;list-style:none}
.dcode-more summary::-webkit-details-marker{display:none}
.dcode-more summary::after{content:" +";font-weight:700;text-decoration:none}
.dcode-more[open] summary::after{content:" \2212"}
.dcode-more[open] summary{margin-bottom:10px}
/* Visually hidden, still read aloud: the input has a placeholder AND a label,
   because a placeholder is not a label and disappears the moment somebody
   types. */
.vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* Discount code, in the paper panel above the totals. The applied state
   replaces the form rather than sitting beside it: one code per basket, so a
   second input would offer something the tenant refuses. */
.dcode{padding:14px 0;border-top:1.5px solid var(--line-paper)}
.dcode label{display:block;font-size:var(--fs-xs);color:var(--paper-muted);margin-bottom:6px}
/* STACKED, NOT SIDE BY SIDE. The summary panel is 340px and drops to
   300px on a laptop, so an input and a button sharing that row left the
   code box too narrow to read a code back in - Sean flagged it as
   cramped and it is worse on the phone. A full-width input above a
   full-width button costs one row of height on a panel that has it. */
.dcode-row{display:flex;flex-direction:column;gap:8px}
.dcode-row input{flex:1;min-width:0;background:var(--paper-2);color:var(--paper-ink);border:1.5px solid var(--line-paper);border-radius:var(--r-input);padding:10px 12px;font:inherit;font-size:var(--fs-sm);text-transform:uppercase}
.dcode-row input::placeholder{color:var(--paper-muted);text-transform:none}
.dcode-row .btn{width:100%;margin-top:0}
.dcode-on{display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:var(--fs-sm);color:var(--paper-ink-2)}
.dcode-on b{color:var(--paper-ink);font-weight:700;letter-spacing:.04em}
.dcode-on .btn{width:auto;flex:none;margin-top:0}
/* SCOPED TO THE PANEL, and it was not for one commit. Written as a bare
   `.btn.ghost` it sat AFTER the gold ghost button at line 455 and won
   everywhere, so the hero's second button turned dark grey on dark purple
   and could not be read. A paper-panel colour on a global class is a
   promise about a background this rule cannot see. */
.dcode .btn.ghost{background:transparent;color:var(--paper-ink-2);border:1.5px solid var(--line-paper)}
.dcode .btn.ghost:not(:disabled):hover{background:var(--line-paper);color:var(--paper-ink)}
.dcode-note{margin-top:8px;font-size:var(--fs-xs);line-height:var(--lh-body);color:var(--paper-ink-2)}
/* The free-delivery progress line, inside the paper panel. width, not a
   transform, because the fill's width is an inline style the page owns. */
.fdprog{background:var(--paper-2);border:1.5px solid var(--line-paper);border-radius:var(--r-input);padding:12px 14px;margin-bottom:12px}
.fdprog p{font-size:var(--fs-xs);color:var(--paper-ink-2);line-height:var(--lh-body);margin-bottom:8px}
.fdprog b{color:var(--rs-purple);font-variant-numeric:tabular-nums}
.fdbar{height:8px;background:var(--line-paper);border-radius:4px;overflow:hidden}
.fdbar i{display:block;height:100%;background:var(--rs-purple);border-radius:4px;transition-property:width;transition-duration:var(--t-base);transition-timing-function:var(--ease-out)}
/* The account page's panel and order cards share the basket's shape, on purple. */
@media (max-width:1024px){ .basket{gap:24px} .bsum{width:300px} }
@media (max-width:768px){
  .basket{flex-direction:column;gap:20px}
  .bsum{width:100%;position:static;top:auto}
  .bhead h1{font-size:var(--fs-2xl)}
  .bline{grid-template-columns:96px 1fr auto;gap:14px;padding:16px 0}
  .bimg{width:96px;height:96px}
  .xsell{padding:20px 0 4px}
}
@media (max-width:400px){
  .bline{grid-template-columns:84px 1fr}
  .bimg{width:84px;height:84px;grid-row:1/3}
  .bltotal{grid-column:2;text-align:left;padding-top:0}
  .bqtyrow{gap:12px}
}

/* =============================================================================
   ACCOUNT AND SIGN-IN
   -----------------------------------------------------------------------------
   The form control, defined once: paper, paper-ink, a --line-control-paper
   edge (4.0:1 on paper), purple on focus. Notices are purple panels with a
   gold edge; .bad swaps the edge for red, and the wording carries the
   meaning.
   ============================================================================= */
.afield{margin-bottom:14px}
.afield label,.afield .alabel{display:block;font-size:var(--fs-sm);font-weight:500;margin-bottom:6px;color:var(--ink)}
.ainput{width:100%;background:var(--paper);border:2px solid var(--line-control-paper);border-radius:var(--r-input);padding:10px 12px;
        font-size:var(--fs-md);color:var(--paper-ink);min-height:var(--tap-min);
        transition-property:border-color;transition-duration:var(--t-fast);transition-timing-function:var(--ease-out)}
.ainput::placeholder{color:var(--paper-muted);opacity:1}
.ainput:focus{border-color:var(--rs-purple)}
.ainput.code{font-family:var(--font-display);font-size:var(--fs-2xl);font-weight:700;letter-spacing:6px;text-align:center;font-variant-numeric:tabular-nums}
.astatic{display:block;font-size:var(--fs-sm);color:var(--ink-2);padding:2px 0 4px}
.achk{display:flex;align-items:flex-start;gap:10px;margin:2px 0 18px}
.achk input{width:20px;height:20px;flex:none;margin-top:2px;accent-color:var(--rs-gold)}
.achk label{font-size:var(--fs-sm);line-height:var(--lh-body);color:var(--ink-2)}
.anote{max-width:440px;background:var(--surface-raised);border-left:3px solid var(--rs-gold);border-radius:var(--r-input);padding:12px 14px;font-size:var(--fs-sm);line-height:var(--lh-body);color:var(--ink);margin-bottom:18px}
.anote.bad{border-color:var(--rs-red)}
.aalt{margin-top:16px;display:flex;align-items:center;gap:6px 18px;flex-wrap:wrap;font-size:var(--fs-sm)}
.aalt a{display:inline-flex;align-items:center;min-height:var(--tap-min);color:var(--accent-ink);font-weight:500;font-size:var(--fs-sm)}
.alink{text-decoration:underline;text-underline-offset:3px;padding:0}
.aalt a:hover{text-decoration:underline;text-underline-offset:3px}
.acol{max-width:440px;padding:6px 0 52px}
.alead{color:var(--ink-2);font-size:var(--fs-sm);line-height:var(--lh-relaxed);margin:8px 0 20px}
.alead b{color:var(--ink)}
.acct{display:flex;gap:36px;align-items:flex-start;padding:14px 0 52px}
.amain{flex:1;min-width:0}
.aside{width:340px;flex:none;background:var(--surface-raised);border-radius:var(--r-card);padding:22px 22px 24px}
.aside h2,.ah2{font-size:var(--fs-xl);margin-bottom:14px;color:var(--ink)}
.asignout{margin-top:6px}
.aorders{list-style:none;display:flex;flex-direction:column;gap:14px}
.aorder{border:1px solid var(--line);border-radius:var(--r-card);background:var(--surface-raised);padding:16px 18px 18px}
/* h2 AND h3, because the same card is a list item on the account page and
   the whole answer on /track, where it is the page's own heading. Styling
   the h3 alone left the tracking page's title inheriting the PAGE's dark
   ink on a purple card - black on purple, found by Sean 13 Sep 2026. A
   rule that names a heading LEVEL rather than a role breaks the moment the
   same component appears at a different depth. */
.aorder h2,.aorder h3{font-size:var(--fs-lg);margin-bottom:4px;color:var(--ink)}
.aorder .srow{color:var(--ink-2)}
.aorder .srow b{color:var(--ink)}
.ameta{font-size:var(--fs-xs);color:var(--muted);margin-bottom:10px;font-variant-numeric:tabular-nums}
.atrack{margin-top:12px;font-size:var(--fs-sm)}
.atrack a{color:var(--accent-ink);font-weight:500}
.atrack a:hover{text-decoration:underline;text-underline-offset:3px}
.aempty{border:2px dashed var(--line-control);border-radius:var(--r-card);padding:26px;color:var(--muted);font-size:var(--fs-sm);line-height:var(--lh-relaxed);max-width:60ch}
@media (max-width:1024px){ .acct{gap:24px} .aside{width:300px} }
@media (max-width:768px){ .acct{flex-direction:column;gap:20px;padding-bottom:40px} .amain,.aside{width:100%} .acol{padding-bottom:40px} }

/* =============================================================================
   COOKIE CONSENT - paper, so it reads as a separate thing
   -----------------------------------------------------------------------------
   Pinned to the foot, not a modal. The two buttons are IDENTICAL - same
   size, weight and fill - because refusing must be as easy as accepting,
   and Reject comes first in source. Purple on paper throughout, purple ring.
   ============================================================================= */
.consent{position:fixed;left:0;right:0;bottom:0;z-index:90;background:var(--paper);color:var(--paper-ink);border-top:3px solid var(--rs-purple);box-shadow:var(--shadow-sheet)}
.consentin{max-width:1200px;margin:0 auto;padding:18px calc(32px + env(safe-area-inset-right,0px)) calc(18px + var(--inset-b)) calc(32px + env(safe-area-inset-left,0px));
           display:flex;align-items:center;gap:28px;flex-wrap:wrap}
.consent h2{font-size:var(--fs-lg);color:var(--paper-ink);margin-bottom:5px}
.consent p{font-size:var(--fs-xs);color:var(--paper-ink-2);line-height:var(--lh-body);max-width:68ch}
.consentmore{margin-top:6px}
.consentmore a{color:var(--rs-purple);font-weight:500;text-decoration:underline;text-underline-offset:3px;padding-top:4px;padding-bottom:4px}
.consentacts{display:flex;gap:10px;margin-left:auto;flex-wrap:wrap}
.consentacts .btn{white-space:nowrap}
.consentbtn{background:var(--paper);color:var(--rs-purple);border:2px solid var(--rs-purple);box-shadow:none;padding-top:12px;padding-bottom:10px}
.consentbtn:hover{background:var(--paper-2);translate:0;box-shadow:none}
@media (max-width:768px){
  .consentin{padding:16px 18px calc(16px + var(--inset-b));gap:14px}
  .consentacts{width:100%;margin-left:0}
  .consentacts form{flex:1}
  .consentacts .btn{width:100%}
}
/* The footer link that reopens the choice. A button because it posts. */
.consentreset{background:none;border:0;padding:4px 0;color:var(--muted);font-size:var(--fs-2xs);text-decoration:underline;text-underline-offset:3px;cursor:pointer;min-height:24px}
.consentreset:hover{color:var(--accent-ink)}

/* =============================================================================
   SEARCH SUGGEST - the typeahead under the search pill, on paper
   ============================================================================= */
/* The field is 220px now, and a 220px suggestion row cannot hold a product
   title, so the panel is wider than the control it hangs from and hangs
   from its RIGHT edge, where there is room. */
.suggest{position:absolute;top:calc(100% + 8px);right:0;left:auto;width:min(420px,calc(100vw - 40px));
         background:var(--paper);color:var(--paper-ink);border-radius:var(--r-card);padding:6px;
         box-shadow:var(--shadow-pop);list-style:none;overflow:hidden;z-index:50}
.suggest-item{display:flex;align-items:center;gap:12px;padding:9px 14px;min-height:var(--tap-min);cursor:pointer;border-radius:calc(var(--r-card) - 6px)}
.suggest-item[aria-selected=true],.suggest-item:hover{background:var(--paper-2)}
.suggest-art{width:44px;height:44px;flex:none;background:var(--paper-2);border-radius:10px;display:flex;align-items:center;justify-content:center;overflow:hidden}
.suggest-art img{max-width:86%;max-height:86%;width:auto;height:auto;object-fit:contain}
.suggest-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
.suggest-brand{font-family:var(--font-display);font-size:var(--fs-3xs);font-weight:500;letter-spacing:1px;color:var(--paper-muted);text-transform:uppercase}
.suggest-title{font-size:var(--fs-sm);font-weight:500;color:var(--paper-ink);line-height:var(--lh-snug);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.suggest-price{font-family:var(--font-display);font-size:var(--fs-md);font-weight:700;color:var(--rs-purple);flex:none;font-variant-numeric:tabular-nums}
@media (max-width:680px){ .suggest{top:calc(100% + 6px);left:0;right:0;width:auto} .suggest-item{padding:8px 12px;gap:10px} .suggest-art{width:40px;height:40px} }

/* =============================================================================
   CONTENT PAGES - a head band, a paper sheet, and a rail beside it
   -----------------------------------------------------------------------------
   These used to be an <article> at 72ch pinned to the left of a 1280px wrap
   with the headings centred over left-aligned copy and half the window
   empty: an unstyled document with a shop's chrome around it. Three things
   fix that, and the events page is the reason all three are worth it.

     THE HEAD BAND. Crumb, kicker, title and lede on the ground under the
     dark-map texture - the same texture the hero and the dev site's own
     Visit band use. The page starts as a page.

     THE SHEET. The prose is PAPER. Legal wording, opening hours and event
     times are the one thing on this site nobody reads for pleasure, and a
     document reads as a document: dark ink, purple headings, purple links,
     70ch of measure. It also breaks the run of purple the client asked
     about, on eight pages at once. With no rail beside it the sheet is
     centred at 820px rather than left-pinned at 72ch, so the empty half of
     the window is gone.

     THE RAIL. Address, phone and the way in, as an aside rather than as the
     last paragraph. Purple, so it reads as a card ON the sheet.

   NOTHING GOLD IS ON THE SHEET. Gold is 1.5:1 on paper: the headings take
   purple, the rule under them takes --line-paper, and a link is purple
   underlined. The gold lives on the head band and on the aside, which are
   both dark.
   ============================================================================= */
.pagehead{position:relative;overflow:hidden;isolation:isolate;margin-bottom:26px;
          background:linear-gradient(110deg,var(--rs-ground) 0%,var(--rs-ground) 52%,var(--rs-purple) 100%)}
/* The dev site's castle band - thorned trees, spires, a wing at the frame's
   edge - where it used the same art: over the page-title band. Drawn in the
   band's own purples, so it reads as depth rather than as competition for
   the heading; the opacity is higher than the old map texture's because
   this is authored scenery, not a watermark. */
.pagehead::before{content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;opacity:.55;
                  background:url("/assets/header-banner-bg.jpg") center/cover no-repeat}
.pagehead .wrap{padding-top:4px;padding-bottom:32px;position:relative;z-index:2}
.pagehead .crumb{padding-bottom:12px}
/* The band is brand chrome - purple in both themes - so the crumb raven
   riding it is the light-feathered one in both, or it vanishes. */
.pagehead .crumb-home{background-image:url("/assets/icon_bird_branch_light.svg")}
.pagehead h1{font-size:var(--fs-5xl);line-height:var(--lh-tight);color:var(--ink)}
.pagelede{max-width:62ch;margin-top:14px;font-size:var(--fs-lg);line-height:var(--lh-relaxed);color:var(--ink-2)}

.pagebody{display:grid;grid-template-columns:minmax(0,1fr);gap:26px;align-items:start;padding-bottom:52px}
.pagebody:not(:has(.pagerail)) .pagesheet{max-width:820px;margin:0 auto}
.pagerail{display:flex;flex-direction:column;gap:18px}
@media (min-width:1000px){
  .pagebody:has(.pagerail){grid-template-columns:minmax(0,1fr) 336px;gap:36px}
  .pagerail{position:sticky;top:100px}
}

.pagesheet{background:var(--paper);color:var(--paper-ink);border-radius:var(--r-sheet);
           padding:34px 42px 40px;box-shadow:var(--shadow-card)}
/* The measure lives on the sheet's children, not on the sheet: the fixture
   rows and the bands want the full width, the prose does not. */
.pagesheet .article-body > p,.pagesheet .article-body > ul,.pagesheet .article-body > h3{max-width:70ch}
.pagesheet .article-body p{color:var(--paper-ink-2);margin:0 0 16px}
.pagesheet .article-body b,.pagesheet .article-body strong{color:var(--paper-ink)}
.pagesheet .article-body h2{font-size:var(--fs-2xl);color:var(--rs-purple);width:auto;margin:34px 0 12px;
                            padding-bottom:9px;border-bottom:2px solid var(--line-paper)}
.pagesheet .article-body > *:first-child{margin-top:0}
.pagesheet .article-body h3{font-size:var(--fs-lg);color:var(--rs-purple);margin:24px 0 8px}
/* > ul > li AND NOT li, because .fixture is an li too: an unscoped list
   rule reached into the fixture cards, took their padding away and put a
   bullet on each one, which is how a price ends up clipped by its card. */
.pagesheet .article-body > ul{margin:0 0 20px;padding-left:0;list-style:none}
.pagesheet .article-body > ul > li{color:var(--paper-ink-2);position:relative;padding:4px 0 4px 24px}
.pagesheet .article-body > ul > li::before{content:"";position:absolute;left:5px;top:14px;width:7px;height:7px;border-radius:50%;background:var(--rs-purple)}
.pagesheet .article-body a:not(.btn){color:var(--rs-purple);text-decoration:underline;text-underline-offset:3px;font-weight:500}
.pagesheet .article-body table{border-collapse:collapse;margin:0 0 22px;width:auto;min-width:min(320px,100%);font-variant-numeric:tabular-nums}
.pagesheet .article-body th,.pagesheet .article-body td{text-align:left;padding:9px 26px 9px 0;font-size:var(--fs-md);
                                                        border-bottom:1px solid var(--line-paper);color:var(--paper-ink)}
.pagesheet .article-body th{font-family:var(--font-display);font-weight:500;color:var(--paper-ink-2)}
.pagesheet .article-body tr:last-child th,.pagesheet .article-body tr:last-child td{border-bottom:0}
.pagesheet .article-body .btn{margin:2px 0 22px}
/* The bands run the sheet's full width and lose their own outer margin:
   inside a sheet they are sections of it, not floating panels. Their
   headings opt out of the sheet's h2 rule - a band heading with 34px above
   it and a rule under it is a band that is mostly air. */
.pagesheet .playfree,.pagesheet .discord{margin:28px 0}
.pagesheet .playfree{padding:0;background:none}
.pagesheet .playfree-art img{max-height:230px}
.pagesheet .article-body .playfree h2,.pagesheet .article-body .discord h2{margin:0 0 8px;padding-bottom:0;border-bottom:0}
.pagesheet .article-body .playfree h2{font-size:var(--fs-3xl)}
.pagesheet .article-body .discord h2{font-size:var(--fs-2xl)}
.pagesheet .article-body .playfree p,.pagesheet .article-body .discord p{margin:0}
.pagesheet .article-body .playfree p + p{margin-top:10px}

/* THE COMPANION BAND IS THE ONE DARK SECTION OF A LIGHT SHEET, so unlike
   the two above it cannot borrow the sheet's ink. The sheet paints an h2
   purple and a paragraph paper-ink-2, and both of those on a purple panel
   are the invisible heading this band shipped with for one draft: 1.1:1 and
   1.05:1. They are restated here rather than made more specific further up,
   because this is where the sheet's own rules are answered and a reader
   comparing the three bands should find all three answers together. */
.pagesheet .discord,.pagesheet .appjoin{margin-bottom:6px}
.pagesheet .article-body .appjoin h2{margin:0 0 8px;padding-bottom:0;border-bottom:0;
  font-size:var(--fs-2xl);color:var(--accent-ink)}
.pagesheet .article-body .appjoin p{margin:0;color:var(--ink-2)}
.pagesheet .article-body .appjoin b,.pagesheet .article-body .appjoin strong{color:var(--ink)}

/* THE FIXTURES. Two weekly nights were two bullet points with the day, the
   time and the price run together in a sentence. They are the reason most
   people open this page, so each one is a card with the four facts in four
   places: whose game it is, what it is called, when it runs, what it costs.
   Purple on the paper sheet, gold price, a gold edge on the left. */
.fixtures{list-style:none;display:grid;gap:12px;margin:0 0 26px;padding:0}
/* .fixtures .fixture and not .fixture, because .article-body li carries a
   padding of its own at one class and one element - which outranks a single
   class, takes the card's padding away and clips the price against its own
   right edge. Two classes settles it. */
.fixtures .fixture{display:grid;grid-template-columns:minmax(0,1fr) auto auto;column-gap:26px;align-items:center;
         background:var(--rs-purple);border-radius:var(--r-card);padding:15px 20px 16px;
         border-left:4px solid var(--rs-gold)}
.fixtures .fixture::before{content:none}
/* On the homepage the card is a door into the tournament app's event page:
   the name carries the link and stretches it over the whole card, the same
   pattern every card on the site uses for its one destination. */
.fixtures .fixture{position:relative}
.fixture-name a{color:inherit}
.fixture-name a::after{content:"";position:absolute;inset:0}
.fixtures .fixture:has(.fixture-name a):hover{background:var(--rs-purple-2)}
.fixture-game{grid-area:1/1;font-family:var(--font-display);font-weight:500;font-size:var(--fs-2xs);letter-spacing:1.1px;
              text-transform:uppercase;color:var(--accent-ink);line-height:var(--lh-snug)}
.fixture-name{grid-area:2/1;font-family:var(--font-display);font-weight:700;font-size:var(--fs-xl);color:var(--ink);
              line-height:var(--lh-tight);margin-top:3px}
.fixture-when{grid-area:1/2/3/3;text-align:right;font-size:var(--fs-sm);color:var(--ink-2);line-height:var(--lh-snug);
              font-variant-numeric:tabular-nums}
/* .pagesheet .fixtures, because the sheet's own strong rule paints a <b>
   in paper-ink and this one is on a PURPLE card: near-black on purple is
   1.6:1, and the day of the week is the fact most people came for. */
.pagesheet .fixtures .fixture-when b,.fixture-when b{display:block;color:var(--ink);font-weight:500;margin-bottom:2px}
.fixture-price{grid-area:1/3/3/4;font-family:var(--font-display);font-weight:700;font-size:var(--fs-2xl);
               color:var(--accent-ink);line-height:var(--lh-flat);font-variant-numeric:tabular-nums}
/* TWO ABREAST on the homepage rail (the client's note, 2 Sep): four
   full-width rows were a screen of purple for four lines of fact. Each card
   keeps its three-column inner grid at half the wrap - the name column
   still has 300px+ - and the pair collapses back to one column where it
   would not. The events page's list never takes this class: its article
   measure is too narrow to split. */
.fixtures.two-up{grid-template-columns:repeat(2,minmax(0,1fr))}
@media (max-width:760px){
  .fixtures.two-up{grid-template-columns:minmax(0,1fr)}
}
@media (max-width:600px){
  .fixture{grid-template-columns:minmax(0,1fr) auto;column-gap:16px;row-gap:8px}
  .fixture-when{grid-area:3/1/4/2;text-align:left}
  .fixture-price{grid-area:1/2/3/3;font-size:var(--fs-xl);align-self:start}
}

/* THE ASIDE. A purple card with the compass on it, which is how the dev
   site marks an address. Gold heading, ink address, gold phone number. */
.pageaside{background:var(--rs-purple);border-radius:var(--r-card);padding:22px 24px 24px;box-shadow:0 0 0 1px var(--line)}
.pageaside h2{font-size:var(--fs-xl);color:var(--accent-ink);margin-bottom:14px;display:flex;align-items:center;gap:12px}
.pageaside h2 img{width:38px;height:38px;flex:none}
.pageaside address{font-style:normal;font-size:var(--fs-sm);line-height:var(--lh-relaxed);color:var(--ink)}
.pageaside p{font-size:var(--fs-sm);line-height:var(--lh-relaxed);color:var(--ink-2);margin-top:10px}
.pageaside .tel{display:inline-flex;align-items:center;min-height:var(--tap-min);margin-top:6px;
                font-family:var(--font-display);font-weight:500;font-size:var(--fs-lg);color:var(--accent-ink);
                font-variant-numeric:tabular-nums}
.pageaside .tel:hover{text-decoration:underline;text-underline-offset:3px}
.pageaside .btn{margin-top:14px;width:100%}
@media (max-width:768px){
  .pagehead h1{font-size:var(--fs-2xl)}
  .pagelede{font-size:var(--fs-md);margin-top:10px}
  .pagehead .wrap{padding-bottom:24px}
  .pagesheet{padding:24px 22px 28px;border-radius:var(--r-card)}
  .pagesheet .article-body h2{font-size:var(--fs-xl);margin-top:26px}
}

/* =============================================================================
   BLOG AND ARTICLE
   ============================================================================= */
.blogcards{display:grid;grid-template-columns:repeat(3,1fr);gap:18px;list-style:none;margin:0;padding:0}
.blogcard{background:var(--surface-raised);border-radius:var(--r-card);overflow:hidden;display:flex;flex-direction:column;box-shadow:0 0 0 1px var(--line),var(--shadow-card)}
.blogcard-art{aspect-ratio:16/9;background:var(--rs-purple-2);overflow:hidden}
.blogcard-art img{width:100%;height:100%;object-fit:cover;display:block}
.blogcard-body{padding:16px 18px 18px;display:flex;flex-direction:column;gap:6px}
.blogcard-body h2,.blogcard-body h3{font-size:var(--fs-lg);line-height:var(--lh-tight);color:var(--ink)}
.blogcard-body h2 a:hover,.blogcard-body h3 a:hover{color:var(--accent-ink);text-decoration:underline;text-underline-offset:3px}
.blogcard-date{font-size:var(--fs-xs);color:var(--muted)}
.blogcard-excerpt{font-size:var(--fs-sm);line-height:var(--lh-relaxed);color:var(--ink-2)}
.article{max-width:none}
.article-art{border-radius:var(--r-card);overflow:hidden;margin:4px 0 26px;background:var(--paper-2);box-shadow:var(--shadow-card)}
.article-art img{width:100%;height:auto;display:block}
.article-lead{font-size:var(--fs-lg);line-height:var(--lh-relaxed);color:var(--ink);max-width:680px;margin:0 auto 18px}
.article-body h2{font-size:var(--fs-2xl);border-bottom:3px solid var(--rs-gold);width:fit-content;padding-bottom:6px;margin:28px auto 10px;color:var(--ink)}
.article-body h3{max-width:680px;font-size:var(--fs-lg);margin:22px auto 8px;color:var(--ink)}
.article-body p{font-size:var(--fs-md);line-height:var(--lh-relaxed);color:var(--ink-2);margin:0 auto 15px;max-width:680px}
.article-body ul{margin:0 auto 16px;padding-left:22px;max-width:680px}
.article-body li{font-size:var(--fs-md);line-height:var(--lh-relaxed);color:var(--ink-2);padding:2px 0}
/* :not(.btn) IS THE FIX AND IT IS LOAD-BEARING. Without it this rule beats
   .btn's own colour on specificity, and every call to action inside an
   article rendered as a gold label on a gold fill: an empty gold pill, on
   the events page, where the Discord button is. A link in the prose is
   gold; a link wearing a button is the button's business. */
.article-body a:not(.btn){color:var(--accent-ink);text-decoration:underline;text-underline-offset:3px}
.article-body img{max-width:100%;height:auto;border-radius:var(--r-input);display:block;margin:20px 0}
.article-body figure{margin:22px auto;max-width:640px}
.article-body figcaption{font-size:var(--fs-xs);color:var(--muted);text-align:center;padding-top:8px;max-width:60ch;margin:0 auto}
.arthero{background:linear-gradient(110deg,var(--rs-ground) 0%,var(--surface-raised) 100%);color:var(--ink);margin-bottom:34px}
.arthero .wrap{display:flex;align-items:center;gap:44px;padding-top:40px;padding-bottom:40px}
.arthero .txt{flex:1.2;min-width:0}
.arthero h1{font-size:var(--fs-6xl);line-height:var(--lh-flat);margin:2px 0 14px}
.arthero-meta{color:var(--muted);font-size:var(--fs-sm);margin-bottom:14px}
.arthero-tags{display:flex;gap:8px;flex-wrap:wrap;list-style:none;margin:0;padding:0}
.arthero-tags a{display:inline-flex;align-items:center;min-height:var(--tap-min);border:1.5px solid var(--line-control);color:var(--ink);font-size:var(--fs-xs);font-weight:500;letter-spacing:.6px;padding:4px 13px;border-radius:var(--r-chip)}
.arthero-tags a:hover{border-color:var(--rs-gold);color:var(--accent-ink)}
.arthero .art{flex:1;background:var(--paper-2);border-radius:var(--r-card);padding:16px;box-shadow:var(--shadow-pop)}
.arthero .art img{width:100%;height:230px;object-fit:contain;display:block}
@media (max-width:1024px){ .blogcards{grid-template-columns:repeat(2,1fr)} }
@media (max-width:900px){ .arthero .wrap{flex-direction:column;align-items:stretch;gap:22px;padding-top:28px;padding-bottom:28px} .arthero h1{font-size:var(--fs-4xl)} }
@media (max-width:768px){ .blogcards{grid-template-columns:1fr;gap:14px} }

/* =============================================================================
   TABULAR NUMERALS - anything that changes or aligns against another number
   ============================================================================= */
.price,.pprice,.bprice,.bltotal,.suggest-price,.srow,.cnt,.fopt .c,.fbar .count,.pip,.qty input,.bstep .bstepval,
.pager a,.pager span,.stars,.stock,.badge,.subst,.fdprog b,.ameta,.fact .v,.pricefacet .bands button,.pricefacet .nums input,
.chip,.ainput.code,.delivleft,#deliv-promise time,.visit-map table{font-variant-numeric:tabular-nums}

/* =============================================================================
   GUTTERS - 32 / 26 / 20 / 18 down the breakpoints
   ============================================================================= */
@media (max-width:1024px){ .wrap{padding-left:26px;padding-right:26px} }
@media (max-width:768px){ .wrap{padding-left:20px;padding-right:20px} }
@media (max-width:400px){ .wrap{padding-left:18px;padding-right:18px} }

/* =============================================================================
   REDUCED MOTION - remove the movement, keep the information
   -----------------------------------------------------------------------------
   --press-scale to 1 stops every press in one declaration; the four
   durations to zero stop every transition and the hero entrance. The sheet
   keeps its transform because translateY(101%) is how it is closed; at zero
   duration it appears rather than travels. --t-hold is an intent delay, not
   motion, and is left alone so the mega menu still forgives a diagonal slip.
   ============================================================================= */
@media (prefers-reduced-motion:reduce){
  :root{--press-scale:1;--t-fast:0s;--t-press:0s;--t-base:0s;--t-slow:0s;--t-stagger:0s}
  *,*::before,*::after{animation-duration:0s!important;animation-iteration-count:1!important}
  .card,.card:hover,.gametile,.gametile:hover,.catgrid .cattile,.catgrid .cattile:hover,.campcard,.campcard:hover,.btn:hover,.btn:active{translate:none}
  .card:hover .art img,.gametile:hover > img,.gametile:hover .gametile-art,.catgrid .cattile:hover .cattile-art img,.campcard:hover .campcard-art img{scale:none}
  .catgrid .cattile:hover .cattile-go,.campcard:hover .campcard-go{translate:none}
  .nav a.hl .livedot{animation:none}
  /* THE HERO SLIDER STOPS, it does not slow down: hero.js reads this same
     query and never starts a timer under it, so there is no interval here
     to fight. What is left is the travel, and the zeroed durations above
     already take it - the track changes slide in one frame and the slide
     leaving is hidden in the same one. Restated here so that a later hand
     giving the track a duration of its own cannot bring the slide back. */
  .heroslider.is-live .heroslides{transition-duration:0s}
}

/* =============================================================================
   PRINT - white paper, black ink, nothing that cannot be read on a desk
   ============================================================================= */
@media print{
  body{background:white;color:black;display:block}
  .hdr,.nav,.mobnav,.ftr,.consent,.skip,.fbar,.sheetbar,.filters,.wish,.addbtn,.buyrow,.pdpwish,.notifybox,.lightbox,.suggest,.hero::before,.pagehead::before,.pager,.usp,.discord,.heroctl{display:none!important}
  .hero,.card,.fact,.aside,.aorder,.anote,.bsum,.visit,.playfree,.brandhero,.gametile,.blogcard,.megapanel,.warn,.notifybox,.chip,.pagehead,.pagesheet,.fixture,.pageaside{background:white!important;color:black!important;box-shadow:none!important;border:1px solid black!important}
  .hero h1,.hero h1 em,.hero .heroslide h2,.hero .heroslide h2 em,.hero p,.sech h2,.sech p,.sech a,.card h3,.price .now,.price .was,.pprice .now,.pprice .was,.bprice,.bltotal,.brandline,.stock,
  .fact .k,.fact .v,.kicker,.crumb,.crumb [aria-current],.plphead h1,.pinfo h1,.bhead h1,.srow,.srow b,.visit-map h2,.visit-map p,.visit-map address,
  .playfree h2,.playfree p,.ftrlegal,.article-body p,.article-body h2,.article-body h3,.article-body li,.delivrow b,.delivrow small,.feats li,.faq summary,.faq p,.preline,.condition,
  .pagehead h1,.pagelede,.pageaside h2,.pageaside address,.pageaside .tel,.fixture-game,.fixture-name,.fixture-when,.fixture-when b,.fixture-price{color:black!important}
  .card .art,.galmain,.bimg,.gametile > img,.brandhero > img{background:white!important}
  a[href^="http"]::after{content:" (" attr(href) ")";font-size:var(--fs-2xs)}
  .wrap{max-width:none;padding:0}
  .grid,.rail{display:block}
  .card,.rail > li{break-inside:avoid;margin-bottom:12px}
}

/* --- The light/dark switch --------------------------------------------------
   Sits in the icon row, so it inherits .hicon's size, shape and tap target and
   only has to say which glyph shows. A <button>, not a link: it changes the
   page rather than going anywhere, which also keeps it out of the lander's
   Coming Soon gate without that gate needing to know it exists. */
.themebtn{border:0;cursor:pointer;font:inherit}
.ico-sun{display:none}
:root[data-theme="dark"] .ico-moon{display:none}
:root[data-theme="dark"] .ico-sun{display:block}

/* The confirmation page's order summary. Added 10 Sep 2026: the page had
   told a shopper the amount and nothing else - not one item name, on the
   only page they see after paying - because the checkout-session read
   carries totals and no lines. Deliberately plain: this is a receipt to
   check, not a shelf to browse, so no images and no links away. Tabular
   numerals so the figures line up against the total the way a receipt
   should. */
.orderlines{list-style:none;margin:10px 0 0;border-top:1px solid var(--line-2)}
.orderlines li{display:grid;grid-template-columns:auto 1fr auto;gap:14px;align-items:baseline;padding:11px 0;border-bottom:1px solid var(--line-2)}
.olqty{color:var(--muted);font-size:var(--fs-sm);font-variant-numeric:tabular-nums}
.olname{color:var(--ink);font-size:var(--fs-sm);line-height:var(--lh-body);max-width:52ch}
.olprice{font-family:var(--font-display);font-weight:500;color:var(--price-ink);font-variant-numeric:tabular-nums}
/* The pre-discount figure on a receipt line, struck. Muted and smaller, so
   the charged figure is the one read first. */
.olwas{color:var(--muted);font-weight:400;font-size:var(--fs-sm);margin-right:4px}
.oldeliv{display:flex;justify-content:space-between;max-width:34rem;color:var(--muted);font-size:var(--fs-sm);padding:10px 0 0;font-variant-numeric:tabular-nums}
.oltotal{display:flex;justify-content:space-between;max-width:34rem;font-family:var(--font-display);font-size:var(--fs-lg);font-weight:700;color:var(--ink);border-top:1.5px solid var(--line-2);margin-top:8px;padding-top:11px;font-variant-numeric:tabular-nums}

/* The add-to-basket bounce notice. Added 11 Sep 2026: an add that the shop
   or the tenant refuses used to reload the product page unchanged, so the
   button looked broken. Stated where the button is, not at the top of the
   page, because that is where the shopper is looking. */
.addnotice{background:var(--paper-2);border-left:3px solid var(--rs-purple);border-radius:var(--r-input);padding:11px 14px;margin-bottom:14px;font-size:var(--fs-sm);line-height:var(--lh-body);color:var(--ink);max-width:52ch}
.addnotice a{color:var(--rs-purple);font-weight:500;text-decoration:underline;text-underline-offset:3px}
