/* ==========================================================================
   Vyson Sdn Bhd - site stylesheet

   Lives in css/ so the url("../assets/...") references resolve identically
   from the static index.html and from the WordPress theme.

   LAYOUT LANGUAGE
   The page ground is white. Content sits on rounded panels rather than
   full-bleed colour bands, so each section reads as a card on a light page.

   PALETTE
   Four brand colours only, taken from the Vyson mascot itself:
   blue, red, yellow, white.

   TYPE SCALE LOCK
   Every font-size / font-weight / font-family for headings is declared once,
   in section 3 and nowhere else. Section rules may set colour on a heading;
   they must never set its size, weight or family. All sizes are fixed px.
   No rem, em, clamp() or vw anywhere in a font-size.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */
:root{
  /* ----------------------------------------------------------------------
     One ground, one accent.

     The page is a single continuous #f5f5f5 from the header to the footer;
     sections are separated by rhythm and a hairline, never by a block of
     colour. #d71920 is the only accent, and it appears only on the light
     ground - against the dark footer it is 3.07, which is enough for an icon
     but not for a link, so the dark blocks run on white instead.
     -------------------------------------------------------------------- */
  --page:       #f5f5f5;
  --paper:      #ffffff;
  --dark:       #222222;

  --ink:        #222222;
  /* body copy sits a shade off the headings, as the reference does */
  --ink-2:      #2b2b2b;
  --ink-soft:   #555555;

  --accent:      #d71920;
  --accent-deep: #a91218;
  /* The accent at 7% over white. Not a new colour: a tint of the one already
     in the palette, so the block reads as brand rather than as decoration.

     7% is a ceiling, not a preference. The eyebrow on this ground is the
     accent itself at 13px/700 - normal-size text, so it owes AA its 4.5:1 -
     and the accent measures 4.63:1 on a 7% tint, 4.56 on 8% and 4.40 on 10%.
     Past 7% the eyebrow would have to stop being red, and a red eyebrow is the
     same mark every other light block on the page carries. */
  --blush:       #fcefef;

  --line:       rgba(34,34,34,.14);

  /* One family, Inter, for headings and text alike. The two tokens stay so the
     display and text roles remain separable, but they resolve to the same face -
     a neutral grotesk carries the catalogue without the slab's period flavour.
     The fallback stack is the local UI grotesk on each platform, so a failed
     webfont degrades to a metrically similar face rather than to Times. */
  --font-display: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  --font-body:    "Inter", system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;

  /* Inter Light. The display weight: it is only ever used at 17px and above,
     where its stroke survives, and never for a label, a control or a price. */
  --w-light: 300;

  /* type sizes - fixed px, desktop. Redefined per breakpoint below. */
  --t-h1:      54px;
  --t-h2:      30px;
  --t-h3:      18px;
  --t-h4:      12px;
  --t-eyebrow: 13px;
  --t-lede:    17px;
  --t-body:    15px;
  --t-btn:     14px;
  --t-price:   20px;
  --t-small:   13px;

  --header-h: 74px;
  --gutter:  clamp(22px, 5vw, 74px);
  --shell:   1500px;
  --card-gap: 18px;
  --section: 100px;

  /* Square-ish. The old pill and 30px panel radii were half the toybox. */
  --r-panel: 10px;
  --r-card:  8px;
  --r-chip:  4px;
  --r-pill:  4px;

  /* Almost nothing. The reference's cards carry no shadow at all; what little
     is left here is for things that genuinely float, like a dropdown. */
  --shadow-sm:  0 1px 2px rgba(34,34,34,.06);
  --shadow-md:  0 6px 18px -10px rgba(34,34,34,.22);
  --shadow-lg:  0 14px 34px -20px rgba(34,34,34,.26);

  /* No overshoot anywhere. The old --ease bounced past its target, which is
     the single loudest cartoon signal in a page's motion. */
  --ease-out: cubic-bezier(.22,.61,.36,1);
}

@media (min-width:900px){
  :root{ --card-gap:22px }
}
@media (max-width:1279px){
  :root{ --t-h1:46px }
}
@media (max-width:1023px){
  :root{ --t-h1:40px; --t-h2:26px; --t-lede:16px; --section:78px }
}
@media (max-width:767px){
  :root{ --t-h1:32px; --t-h2:22px; --t-h3:17px; --t-lede:16px; --t-price:19px; --section:58px }
}

/* --------------------------------------------------------------------------
   2. Reset
   -------------------------------------------------------------------------- */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scrollbar-gutter:stable}
body,h1,h2,h3,h4,p,figure,blockquote,dl,dd,ul,ol{margin:0}
ul[class],ol[class]{list-style:none;padding:0}
/* The browser gives [hidden] a plain display:none, which ANY author display
   outranks - so el.hidden = true did nothing to a .arrivals grid or to its
   flex list items, and a filter that matched nothing still showed every
   card. !important is the point of this rule: hidden must mean hidden
   whatever a component set. */
[hidden]{display:none!important}
img,svg,video,iframe{max-width:100%;display:block}
img{height:auto}
button,input,select,textarea{font:inherit;color:inherit}
button{background:none;border:0;cursor:pointer}
a{color:inherit;text-decoration:none}
a:not([href]){cursor:default}
address{font-style:normal}

html{scroll-behavior:smooth;scroll-padding-top:96px}
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{animation-duration:.01ms!important;transition-duration:.01ms!important}
}

body{
  font-family:var(--font-body);font-size:var(--t-body);font-weight:400;
  line-height:1.7;color:var(--ink-2);background:var(--page);
  -webkit-font-smoothing:antialiased;overflow-x:hidden;
}

/* --------------------------------------------------------------------------
   3. Base type  -  THE ONLY PLACE HEADING SIZE/WEIGHT/FAMILY IS SET
   -------------------------------------------------------------------------- */
/* The whole signature lives here, case included. It used to stop short of
   text-transform, and the hero - the only h1 on the site at the time - added
   uppercase itself. A second page would then have carried an h1 that did not
   look like the first one, which is the one thing the lock at the top of this
   file forbids. */
h1{font-family:var(--font-display);font-weight:var(--w-light);font-size:var(--t-h1);
   line-height:1.1;letter-spacing:-1.4px;text-transform:uppercase;
   color:var(--ink);text-wrap:balance}
/* Caps and tracked open, after the reference, which sets its own section
   headings at 30px/800 with 1.2px of tracking. 700 is a real Inter weight, not
   a browser-faked bold, and 1.2px is the tracking uppercase needs at 30px -
   Inter's caps are set tight for lowercase company and close up without it. */
h2{font-family:var(--font-display);font-weight:700;font-size:var(--t-h2);line-height:1.15;letter-spacing:1.2px;text-transform:uppercase;color:var(--ink);text-wrap:balance}
h3{font-family:var(--font-display);font-weight:700;font-size:var(--t-h3);line-height:1.3;letter-spacing:-.2px;color:var(--ink);text-wrap:balance}
h4{font-family:var(--font-body);font-weight:700;font-size:var(--t-h4);line-height:1.30;letter-spacing:1.9px;text-transform:uppercase;color:var(--ink)}

@media (max-width:767px){ h2{letter-spacing:.8px} }

.eyebrow{
  font-family:var(--font-body);font-weight:700;font-size:var(--t-eyebrow);
  line-height:1.3;letter-spacing:1.9px;text-transform:uppercase;color:var(--accent);
}
.lede{font-family:var(--font-body);font-weight:var(--w-light);font-size:var(--t-lede);line-height:1.65;color:var(--ink-soft);max-width:56ch;text-wrap:balance}
.small{font-size:var(--t-small);line-height:1.55;color:var(--ink-soft)}

/* Colour is the ONLY thing a dark surface changes. Never size/weight/family. */
/* Only the footer is dark now. The hero's slides carry white copy too, but
   they set it themselves - their ground is a photograph under a veil, not a
   flat colour, and the veil's alpha is what their ratios are computed against. */
.on-dark{color:rgba(255,255,255,.78)}
.on-dark h1,.on-dark h2,.on-dark h3,.on-dark h4{color:#fff}
.on-dark .lede{color:rgba(255,255,255,.84)}
/* the accent is 3.07 against #222 - enough for an icon, short of the 4.5 a
   17px eyebrow needs - so on the dark block the eyebrow is simply white */
.on-dark .eyebrow{color:#fff}
/* yellow works on the blue surfaces (5.77) but only reaches 3.68 on the red
   panel, so that one takes white instead - 5.81. Colour is the one thing the
   type lock lets a surface change. */

/* --------------------------------------------------------------------------
   4. Layout and panels
   -------------------------------------------------------------------------- */
.shell{width:min(var(--shell),calc(100% - var(--gutter)*2));margin-inline:auto}
/* One column of words, at a width they can be read at.

   The four text pages - shipping, FAQ, and the two policies - are nothing but
   prose, and the 1500px shell is roughly three times too wide for a line of
   15px type. 72ch lands near 660px, the top of the comfortable range.

   The reset only strips lists that carry a class, so what the editor writes
   keeps its markers; what it does not keep is spacing, because the reset zeroes
   every margin. Links get their own treatment for the same reason: `a` inherits
   its colour and drops its underline everywhere else on the site, which is
   right for a nav and wrong inside a paragraph. */
/* The column is 72ch, but the BOX is still the shell - so the text starts on
   the same left edge the banner copy above it starts on.

   max-width:72ch on the shell itself was wrong: that shrank the box, and the
   shell's own margin-inline:auto then centred the shrunken box. The banner
   copy sat at x=74 and the first paragraph at x=452, a 378px jump to the
   right between one and the next. A single grid column of 72ch keeps the box
   where it was and puts the words at its start. */
/* The full shell, starting at its left edge.

   This began as a 72ch column pinned left, which left 656px dead down the
   right of a 1337px shell. Two intermediate versions were tried and thrown
   away: the heading in a column beside the body (filled the width, but the
   heading belongs above the text it introduces) and the body centred at 92ch
   (symmetrical, but still short of the edge).

   So it runs the shell, left edge to right, like the page head above it and
   the FAQ accordion. That is a long measure - about 160 characters - and it
   was the client's call over the reading-length objection, twice. What makes
   it work in practice is that these paragraphs are two to four lines at
   72ch, so at full width most of them are one or two lines and the eye
   rarely has to find the start of the next one. */
.shell--prose{display:grid;grid-template-columns:100%;justify-content:start}
.shell--prose > * + *{margin-top:1.1em}
.shell--prose h2{margin-top:1.9em}
.shell--prose h3{margin-top:1.55em}
.shell--prose > :first-child{margin-top:0}

.shell--prose ul,.shell--prose ol{display:grid;gap:.5em;padding-left:1.3em}
.shell--prose li::marker{color:var(--accent)}
.shell--prose a{color:var(--accent);text-decoration:underline;text-underline-offset:3px;text-decoration-thickness:1px}
.shell--prose a:hover{color:var(--accent-deep);text-decoration-thickness:2px}
.shell--prose strong{font-weight:700;color:var(--ink)}

/* A fact only the shop knows.

   Shipping rates, lead times, a returns window, which analytics are running -
   none of that can be guessed, and a guess on these pages is not a typo, it is
   a promise the shop then has to keep. So the draft carries the shape of the
   sentence and marks the hole, loudly, in the page and in the source.

   Deliberately ugly. It has to survive being skimmed. */
.todo{
  display:inline;
  background:#ffe8e8;color:var(--accent-deep);
  font-weight:700;
  padding:1px 7px;border-radius:4px;
  box-shadow:inset 0 0 0 2px var(--accent);
  white-space:normal;
}
.todo::before{content:"FILL IN: ";font-size:.82em;letter-spacing:.06em}
/* a whole paragraph or list item that is still a placeholder */
.todo-block{
  border-left:4px solid var(--accent);
  background:#ffe8e8;
  padding:14px 16px;border-radius:0 var(--r-chip) var(--r-chip) 0;
}
.todo-block > * + *{margin-top:.6em}
.section{padding-block:var(--section)}
/* The whole page is one ground now, so EVERY pair of blocks has the
   double-padding problem that used to belong only to two .section siblings -
   a band no longer has a colour change to justify its own top padding. Every
   block after the first drops it, and the rhythm between any two is exactly
   one --section. */
.section + .section,
.band + .band,
.band + .section,
.section + .band{padding-top:0}
/* Except either side of a block that really does change colour. That block has
   to hold its content off its own top edge, and the block after it can no longer
   borrow its padding-bottom. Same specificity as the rule above, later in the
   file, so it wins the tie. */
.section + .band--red,
.band + .band--red,
.band--red + .section,
.band--red + .band,
.section + .band--blush,
.band + .band--blush,
.band--blush + .section,
.band--blush + .band,
.section + .band--blush,
.band + .band--blush{padding-top:var(--section)}

/* A panel is a surface holding one section's content. On a page that is all
   one ground, a panel is what makes a block read as a block: white, hairlined,
   barely rounded. */
.panel{
  border-radius:var(--r-panel);padding:clamp(26px,3.6vw,56px);
  background:var(--paper);border:1px solid var(--line);
}

/* The bands all resolve to the page.

   There is no colour left to mark where one section ends and the next begins,
   which is the point - the reference runs a single #f5f5f5 from the header to
   the footer. What delimits a section now is the rhythm, and a hairline where
   two blocks need to be told apart. The classes stay so the markup and the
   WordPress template do not have to be rewritten, and so a section can be
   given a ground again later in one line.

   .band keeps overflow:hidden: the hero relies on it, and so does anything
   that bleeds past the shell. */
.band{position:relative;overflow:hidden;padding-block:var(--section);background:var(--page)}
.band--wash{background:var(--page)}
/* Best sellers, on a tint of the accent - the one block between the category
   grid and the clips that carries a colour of its own. --sky and --yellow went
   with it: they had no users left in either template. */
.band--blush{background:var(--blush)}


/* The closing block takes the accent back. It is the one saturated surface on
   the page, which is what a closing call to action is for, and it sits directly
   above the near-black footer - red into black reads as two deliberate blocks
   where dark into dark would read as one. White on it is 5.19:1, clear of AA
   for the 15px body as well as the heading. */
.band--red{background:var(--accent)}

/* No hairline between blocks. The reference separates its sections with
   nothing but whitespace, and one --section of rhythm either side of a join is
   already 100px - a rule on top of that reads as a table, not a page. */

.section-head{display:grid;gap:14px;margin-bottom:44px}
.section-head h2{max-width:30ch}
.section-head .lede{max-width:58ch}
/* One centred column: eyebrow, title, sub. The "view all" that used to sit at
   the far right of this row is now a button under the section's content, so
   the head has nothing to balance against and can simply sit on the axis the
   grid below it is already centred on.

   No rule under the head. The reference has none, and the whitespace either
   side of a section already reads as the break. */
.section-head--center{
  display:grid;justify-items:center;text-align:center;
  gap:10px;
  padding-bottom:0;
  margin-bottom:38px;
}
.section-head--center .section-head__lead{
  display:grid;justify-items:center;gap:10px;min-width:0;
}
/* Centred copy needs its measure back - it was dropped for the split row,
   where the lead had a link beside it and every cap forced a needless wrap.
   On the axis a long line reads worse the other way: it runs past the block
   below it. text-wrap:balance on h2/.lede evens the lines it does take. */
/* 30ch, measured: the longest title on the page ("What actually leaves the
   shop") sets at 28.5ch of its own 30px caps, so every one of the six holds a
   single line at desktop and the six heads stay the same height as each other.
   Below 1220px the h2 steps down with the scale and they wrap where they must. */
.section-head--center h2{max-width:30ch}
.section-head--center .lede{max-width:56ch}

/* The opening block of an inner page. No hero, so the head carries the whole
   first screen: it sits directly under the fixed bar and needs air above the
   title that a section in the middle of a page does not. */
.page-head{padding-top:calc(var(--section) * 0.72)}
.section-head--center h1{max-width:20ch}

/* The banner version of that head: a photograph strip with the words on it.

   It sits BELOW the bar, not under it. The hero can run under a transparent
   bar because its photographs are dark; these are composed with a deliberately
   empty PALE left half - a wall, a worktop, background paper - and white nav
   type on that is about 1.1:1. So the bar stays solid and the strip starts
   under it. */
.page-head--banner{
  position:relative;overflow:hidden;
  padding-block:0;
  /* Inner-page heroes sit below the fixed header, so their height subtracts
     it and the complete opening composition still fits one viewport. */
  min-height:calc(100svh - var(--header-h));
  display:grid;align-items:center;
}
@supports (height:100dvh){.page-head--banner{min-height:calc(100dvh - var(--header-h))}}
.page-head__shot{
  position:absolute;inset:0;z-index:0;
  width:100%;height:100%;object-fit:cover;
  /* the subject lives in the right half, so a narrower window keeps that side */
  object-position:70% 50%;
}
/* Heavy on the left where the words are, clearing to the right so the toys
   still read as toys.

   These alphas are measured, not chosen. The frame was composited in pixels -
   the WebP scaled by cover at 70% 50%, then this gradient over it - and the
   BRIGHTEST pixel under each line of type was taken as the ground that line
   actually has to survive. Swept across all four banners:

     .80/.74  worst line at 1.63x what AA asks   photo too buried
     .72/.66  worst line at 1.29x                <- here
     .64/.58  worst line at 1.02x                on the line, no margin
     .58/.52  worst line at 0.87x                fails

   .64 would pass and show more photograph, but 2% of margin is not margin;
   one different banner drops it under. */
.page-head--banner::after{
  content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background:linear-gradient(90deg,
    rgba(18,18,18,.72) 0%,
    rgba(18,18,18,.66) 40%,
    rgba(18,18,18,.28) 70%,
    rgba(18,18,18,.08) 100%);
}
.page-head--banner .shell{position:relative;z-index:2}
/* the copy goes left, against the shell's own gutter, at the hero's measure */
.page-head--banner .section-head{justify-items:start;text-align:left;margin-bottom:0;gap:10px}
.page-head--banner .section-head__lead{justify-items:start;text-align:left}
.page-head--banner h1{max-width:16ch}
/* 46ch, not 56: the veil is only opaque across the left 40% of the strip, and
   a longer line would run out from under it into the photograph. */
.page-head--banner .lede{max-width:46ch}
/* Category hero divider: mirror the homepage hero's white rule so the same
   title-to-description rhythm carries across every category page. */
.page-head--banner .page-head__divider{
  display:block;width:64px;height:4px;border-radius:2px;
  background:rgba(255,255,255,.82);
  margin:26px 0 20px;
}
/* The blanket rule three hundred lines up zeroes the top padding of any
   .section following another .section, on the grounds that the one above
   already paid for the gap. A banner has no bottom padding to lend - its
   photograph runs to its own edge - so the block under it was starting 28px
   below the picture. It gets the full rhythm back. */
.page-head--banner + .section,
.page-head--banner + .band{padding-top:var(--section)}
@media (max-width:759px){
  /* 74%, not 20%.

     The banner is min-height:100dvh here, so on a 375x812 phone the box is
     375x738 - 0.51:1 against the picture's 2.40:1. cover therefore fits by
     HEIGHT and keeps a 325px-wide slice of the 1536: twenty-one per cent of
     the photograph, and object-position chooses which twenty-one.

     At 20% that slice was x 242..567. Every one of these banners is composed
     for the desktop layout - subject on the right, empty floor on the left
     for the copy to sit on - so what a phone showed was the empty floor.
     Rendered all sixteen and looked: fifteen were bare beige, nothing in
     frame at all.

     At 74% the slice is x 896..1221 and the subject is in it on every one -
     the block car, the wooden kitchen, the basketball hoop, the swan float,
     the monster truck.

     The veil below is unaffected: it is flat and covers the whole box, so it
     does not care which part of the photograph is underneath. Measured over
     all sixteen at this crop, the worst contrast for white type is 8.19,
     against the 4.5 AA asks - the move costs about 0.3 and keeps 1.8x the
     margin. (The old comment here argued there was "no subject on the right
     to protect". There was; this crop was hiding it.) */
  .page-head__shot{object-position:74% 50%}
  /* The veil has to be rebuilt here, not just nudged.

     Its stops are percentages of the BOX, and the box is now 375px instead of
     1585. "Heavy across the left 40%" was 634px on a desktop and is 150px
     here, while the heading still runs to 299px - so the second line of it
     landed on the pale tail of the gradient, right where the toys are.
     Measured: 1.84:1 under the heading, against the 3.0 it owes. The desktop
     numbers gave no warning at all, because the geometry is not the same
     geometry.

     Moving the crop does not help: at 250px tall, cover fits the 2.4:1 strip
     by height, so the window is 62% of the image whatever object-position
     says, and some of the subject is always in it. Sweeping 0% to 34% moved
     the worst line by 0.01.

     So the gradient goes flat and heavy instead. At this size the photograph
     is texture, not content. .78/.72/.56 measures 1.52x what AA asks across
     all four banners; .74/.68/.50 was 1.35x and .72/.66/.28/.08 - the desktop
     value - was 0.58x, ie. failing. */
  .page-head--banner::after{
    /* Flat, not a gradient. The gradient shape existed to keep the subject on
       the right of the frame visible, but a phone is portrait and
       object-position is 20%, so the box shows the left third of the picture
       and there is no subject on the right to protect - while the lede spans
       nearly the full 375px and ran straight into the lightest stop. Measured
       across the eight category banners the worst lede was 4.54 against a
       floor of 4.5; flat .74 takes it to 6.91. And .74 is LIGHTER than this
       gradient's own dark end, so the side the text sits on gained brightness
       as well as margin. */
    background:rgba(18,18,18,.74);
  }
}

/* The section's own call to action, under its content rather than beside its
   title: a real button on the page's centre line, so the eye finishes the
   grid and lands on it instead of tracking back up to the corner. */
.section-foot{
  display:flex;justify-content:center;
  margin-top:42px;
}
/* the clips have no button of their own - the arrows are the whole footer -
   so the arrow row carries the gap to the section's bottom padding */
#reels .carousel__foot{margin-top:30px}
@media (max-width:767px){
  .section-head{margin-bottom:30px}
  .section-head--center{margin-bottom:28px}
  .section-foot{margin-top:32px}
}
/* On the dark band the accent is 3.07 against #222 - just over the 3:1 a
   control's own surface owes, with no margin for the eye. The button inverts
   to white there instead, the way it already does on the red block. */
/* On the red ground the primary inverts; that rule lives with the CTA in
   section 12, and this block is the same ground, so it takes the same one. */


.skip-link{position:absolute;left:-9999px;top:0;z-index:200;background:var(--ink);color:#fff;padding:12px 20px;border-radius:0 0 var(--r-chip) 0}
.skip-link:focus{left:0}
/* the accent on the light page; white wherever the ground is dark */
:focus-visible{outline:3px solid var(--accent);outline-offset:3px;border-radius:4px}
.on-dark :focus-visible,.hero :focus-visible{outline-color:#fff}

/* --------------------------------------------------------------------------
   5. Buttons
   -------------------------------------------------------------------------- */
.button{
  display:inline-flex;align-items:center;justify-content:center;gap:9px;
  font-family:var(--font-display);font-weight:600;font-size:var(--t-btn);line-height:1;
  /* min-height, not just padding: without it the height came from the icon
     glyph's line box, so different Phosphor glyphs - and an icon font that
     had not finished loading - gave buttons 46.5 to 48px. Pinned instead. */
  min-height:48px;
  padding:15px 26px;border-radius:var(--r-pill);white-space:nowrap;
  transition:transform .22s var(--ease-out),box-shadow .22s var(--ease-out),background-color .22s var(--ease-out),border-color .22s var(--ease-out),color .22s var(--ease-out);
}
.button i{font-size:18px;line-height:1}
.button:hover{transform:translateY(-2px)}
.button:active{transform:translateY(0) scale(.985)}

/* Yellow, with ink text: white on yellow is about 1.7, nowhere near AA, so the
   label goes dark. Hover deepens the yellow rather than lightening it, which
   keeps the label's contrast climbing instead of falling. */
/* white on the accent is 5.19; the old yellow needed ink on it, which is
   why this flips */
.button--primary{background:var(--accent);color:#fff}
.button--primary:hover{background:var(--accent-deep)}
.button--ghost{background:var(--paper);color:var(--ink);border:2px solid var(--line);padding:13px 24px}
.button--ghost:hover{border-color:var(--ink)}
/* .56 not .4: at 40% white the border is 2.65 against --blue, under the
   3:1 that WCAG 1.4.11 asks of a control's visual boundary. .56 gives 3.85.
   Live in the hero, where this ghost button sits straight on the artwork. */
.on-dark .button--ghost{background:transparent;color:#fff;border-color:rgba(255,255,255,.56)}
.on-dark .button--ghost:hover{border-color:#fff;background:rgba(255,255,255,.12)}
.button--block{width:100%}

/* --------------------------------------------------------------------------
   6. Header
   -------------------------------------------------------------------------- */
/* Transparent while it sits over the hero - the hero is cream, so the bar
   has nothing to add there - and solid stone once the page has scrolled
   past it. JS toggles is-stuck; the hide-on-scroll-down transform is a
   GSAP tween, so only the paint is described here.

   The bar has two grounds now, so it has two states. Over the hero it sits on
   a photograph under a dark veil, so it goes white; once the page scrolls past
   the hero it sits on the light sections, so it goes ink on stone. */
.site-header{
  --hdr:#fff;--hdr-accent:#fff;
  position:fixed;top:0;left:0;right:0;z-index:100;background:transparent;
  transition:background-color .3s var(--ease-out),box-shadow .3s var(--ease-out);
  will-change:transform;
}
/* While it is transparent the bar carries its own scrim, because its white text
   is then sitting on a photograph. Measured on the brightest pixel under the bar
   of each hero slide, with the hero's own .42 top veil already counted: white
   came out 3.11 / 3.89 / 3.37 / 3.05 - enough for the 24px/700 brand word, which
   is large text, and short of the 4.5 the 15px/600 nav links owe. .26 fading out
   over the bar's height takes the worst slide to about 4.6.

   Deepening the hero's veil instead would have darkened the whole top of the
   hero to fix the bar. This keeps the bar's legibility on the bar.

   It is only ever seen over the hero: every other block starts below the bar,
   and .is-stuck below replaces this background outright. */
.site-header:not(.is-stuck){
  background-image:linear-gradient(180deg,rgba(18,18,18,.26) 0%,rgba(18,18,18,0) 100%);
}
/* ...but only the homepage has a hero for the bar to be transparent over.

   Every other page starts on the light ground, and a white brand and white
   nav on #f5f5f5 is 1.1:1 - the bar is simply not there until you scroll. So
   a page with no hero opens in the state scrolling would have given it. The
   homepage is the exception here, not the rule.

   (0,2,1) against the scrim's (0,2,0): :has() carries its argument's weight,
   and :not() carries its own, so this wins on specificity rather than order. */
body:not(:has(.hero)) .site-header{
  --hdr:var(--ink);--hdr-accent:var(--accent);
  background:var(--page);background-image:none;
  box-shadow:0 8px 26px -16px rgba(34,34,34,.42);
}
.site-header.is-stuck{
  --hdr:var(--ink);--hdr-accent:var(--accent);
  background:var(--page);box-shadow:0 8px 26px -16px rgba(34,34,34,.42);
}
/* An open menu takes the same ground, wherever the page happens to be.

   The panel has none of its own, and the bar only gets one once the hero is
   behind you - so opening the menu at the top of the page put white links
   straight onto the hero photograph, across the hero's own copy, with the
   sub-links in --ink-soft on top of that. Unreadable, and the only way to
   reach it was the one thing a phone visitor does first.

   Rather than paint the panel - which would have left the bar above it
   transparent and the two disagreeing - an open menu puts the header into the
   state scrolling already gives it: one ground under both, and --hdr flips
   every label to ink to match. Specificity: :has() carries its argument's
   weight, so this is (0,3,0) against the scrim's (0,2,0) and wins outright. */
.site-header:has(.mobile-menu.is-open){
  --hdr:var(--ink);--hdr-accent:var(--accent);
  background:var(--page);box-shadow:0 8px 26px -16px rgba(34,34,34,.42);
}
/* fixed, so the page needs that height back. The hero cancels it again in
   section 8: it is the one block meant to run under the bar. */
body{padding-top:var(--header-h)}
.header__inner{display:flex;align-items:center;gap:clamp(16px,3vw,40px);min-height:var(--header-h)}

.brand{display:inline-flex;align-items:center;gap:10px;flex:none}
.brand img{height:44px;width:auto}
.brand__word{font-family:var(--font-display);font-weight:700;font-size:24px;line-height:1;letter-spacing:-.6px;color:var(--hdr)}
@media (max-width:767px){.brand img{height:36px}.brand__word{font-size:20px}}

.desktop-nav{display:none;align-items:center;gap:clamp(32px,3.7vw,58px);margin-inline:auto}
.desktop-nav a{position:relative;font-family:var(--font-body);font-weight:600;font-size:15px;color:var(--hdr);padding-block:6px;transition:color .2s var(--ease-out)}
.desktop-nav > a::after,.nav-group > a::after{
  content:"";position:absolute;left:0;right:0;bottom:0;height:2px;border-radius:2px;
  background:var(--hdr-accent);transform:scaleX(0);transform-origin:left;transition:transform .28s var(--ease-out);
}
/* red, not yellow: --yellow is 1.72 against the stone bar, so a hovered
   link would come out less readable than an idle one */
.desktop-nav a:hover,.desktop-nav a.is-active{color:var(--hdr-accent)}
.desktop-nav > a:hover::after,.desktop-nav > a.is-active::after,
.nav-group:hover > a::after,.nav-group > a.is-active::after{transform:scaleX(1)}

/* Full header height, so top:100% on the panel below lands exactly on the
   header's bottom edge instead of partway up the blue bar. Giving the group
   the height (rather than offsetting the panel) also keeps the hover area
   continuous - an offset panel would leave a dead strip between the link and
   the panel, and the menu would close while the pointer crossed it. */
.nav-group{position:relative;height:var(--header-h);display:flex;align-items:center}
.nav-group > a{display:inline-flex;align-items:center;gap:5px}
.nav-group__caret{font-size:13px;transition:transform .24s var(--ease-out)}
.nav-group:hover .nav-group__caret{transform:rotate(180deg)}
/* left-aligned to the link, not centred under it: the panel's left edge lines
   up with the S of the label */
.nav-drop{
  position:absolute;z-index:5;top:100%;left:0;display:grid;min-width:238px;padding:9px;
  border-radius:var(--r-card);background:var(--paper);border:1px solid var(--line);
  box-shadow:0 30px 60px -34px rgba(34,34,34,.55);
  opacity:0;translate:0 10px;pointer-events:none;
  transition:opacity .24s var(--ease-out),translate .24s var(--ease-out);
}
.nav-group:hover .nav-drop,.nav-group:focus-within .nav-drop{opacity:1;translate:0 0;pointer-events:auto}
.desktop-nav .nav-drop a{padding:9px 12px;border-radius:10px;font-size:14px;color:var(--ink-soft);white-space:nowrap;transition:background-color .2s var(--ease-out),color .2s var(--ease-out)}
.desktop-nav .nav-drop a::after{display:none}
.desktop-nav .nav-drop a:hover{background:var(--page);color:var(--ink)}

/* Account + cart, ready for WooCommerce. These show at every width - they are
   compact enough to sit beside the burger - so the auto margin that used to
   live on the menu button moves here, and the button follows them. */
.header__tools{display:flex;align-items:center;gap:6px;flex:none;margin-left:auto}
/* No chip behind the glyph. The 42px box stays as the hit area, so the target
   is still comfortable; hover reads through colour, matching the footer links. */
.tool{
  position:relative;flex:none;width:42px;height:42px;
  display:grid;place-items:center;color:var(--hdr);font-size:22px;
  transition:color .22s var(--ease-out),transform .22s var(--ease-out);
}
.tool:hover{color:var(--hdr-accent);transform:translateY(-2px)}
.tool__count{
  position:absolute;top:2px;right:0;min-width:19px;height:19px;padding:0 5px;
  border-radius:var(--r-pill);display:grid;place-items:center;
  background:var(--accent);color:#fff;
  font-family:var(--font-display);font-weight:700;font-size:11px;line-height:1;
}
/* an empty cart renders the span with no text, so there is nothing to show */
.tool__count:empty{display:none}

.menu-button{
  display:inline-flex;align-items:center;gap:8px;flex:none;margin-left:6px;
  font-family:var(--font-display);font-weight:600;font-size:15px;color:var(--hdr);
  /* No boundary any more, so 1.4.11 has nothing to measure here: the label is
     the control, and --hdr carries it at text contrast on both grounds. The
     padding stays as the tap target - 46px tall - but the right side comes off
     so the hamburger lands on the gutter, where the pill's edge used to be.
     Otherwise the label sits 13px inside it while the brand opposite is flush. */
  padding:10px 2px 10px 10px;border-radius:var(--r-pill);opacity:1;
}
.menu-button i{font-size:20px}
.menu-button .ph-x{display:none}
.menu-button[aria-expanded="true"] .ph-list{display:none}
.menu-button[aria-expanded="true"] .ph-x{display:block}

.mobile-menu{display:grid;grid-template-rows:0fr;transition:grid-template-rows .38s var(--ease-out);border-top:1px solid transparent}
/* currentColor, so the one value works white over the hero and ink on the
   stone bar without a second rule */
.mobile-menu.is-open{grid-template-rows:1fr;border-top-color:currentColor}
.mobile-menu__inner{overflow:hidden}
.mobile-menu nav{display:grid;padding-block:6px 20px}
.mobile-menu nav > a{font-family:var(--font-display);font-weight:600;font-size:18px;color:var(--hdr);padding:13px 2px;border-bottom:1px solid currentColor}
/* The last top-level link follows the indented category list, so a rule below
   alone left it reading as the tail of that block. One above too, and it is
   its own row again. */
.mobile-menu nav > a:last-of-type{border-top:1px solid currentColor}
.mobile-menu__sub{display:grid;padding:2px 0 10px 14px}
.mobile-menu__sub a{font-family:var(--font-body);font-weight:600;font-size:15px;padding:9px 2px;color:var(--ink-soft)}
.mobile-menu .button{margin-top:16px}
/* A short phone cannot reach the bottom of this list. Measured at 375x667:
   the panel is 781px of content hanging off a 75px fixed header, so the last
   188px - Building Block Toys, Contact Us and the WhatsApp button - is below
   the fold, and because the header is fixed, scrolling the page does not move
   it. The panel gets a ceiling and scrolls inside it instead.

   The ceiling goes on .shell rather than on __inner, whose overflow:hidden is
   the thing that lets the 0fr row collapse - making that the scroller would
   fight the open/close transition.

   svh rather than vh: on iOS, vh is the viewport with the address bar hidden,
   which is exactly the height we do NOT have at the moment someone opens the
   menu. The 8px keeps the last row visibly clipped, so there is a hint that
   the list continues. */
.mobile-menu .shell{
  max-height:calc(100vh - var(--header-h) - 8px);
  overflow-y:auto;
  overscroll-behavior:contain;
  -webkit-overflow-scrolling:touch;
  padding-bottom:env(safe-area-inset-bottom,0px);
}
@supports (height:100svh){
  .mobile-menu .shell{max-height:calc(100svh - var(--header-h) - 8px)}
}

@media (min-width:1040px){
  /* Grid, not flex, so the nav is centred on the HEADER rather than on the
     space between the brand and the tools. With auto margins it sat 17px
     right of centre, because the brand is wider than the two icon tools. */
  .header__inner{display:grid;grid-template-columns:1fr auto 1fr;align-items:center}
  .desktop-nav{display:flex;margin-inline:0}
  .header__tools{justify-self:end;margin-left:0}
  .menu-button,.mobile-menu{display:none}
}

/* Real brand marks, not the icon font's interpretation. Phosphor draws
   WhatsApp as an outlined bubble with an outlined handset; the actual mark is a
   solid bubble with the handset knocked out of it, which is what the shape is
   recognised by. Masked so they take currentColor like the glyphs they replace.
   Sized in px, never em, and the size is a variable each context can set.

   Facebook is the outline lockup - a 2px ring with the f letterform sitting
   inside it - because the solid disc version is the only one of the three that
   reads as a filled shape, and next to the outlined Instagram and WhatsApp it
   looked like a different weight of icon rather than a third social link. */
.bi{
  --bi-size:18px;
  display:inline-grid;place-items:center;flex:none;line-height:0;
}
/* The mask lives on a pseudo element, not on .bi itself. Some of these icons
   sit inside a coloured disc (the contact cards, the footer socials) and that
   disc is a background on the very same element - masking the element turned
   the disc INTO the glyph and swallowed it. */
.bi::before{
  content:"";display:block;
  width:var(--bi-size);height:var(--bi-size);
  background:currentColor;
  -webkit-mask:var(--bi) center/contain no-repeat;
          mask:var(--bi) center/contain no-repeat;
}
.bi--facebook {--bi:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><circle cx='12' cy='12' r='10.5' fill='none' stroke='black' stroke-width='2'/><path transform='translate(2.927 1.434) scale(.75)' d='M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246V23.691Z'/></svg>")}
.bi--instagram{--bi:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 0C8.74 0 8.333.015 7.053.072 5.775.132 4.905.333 4.14.63c-.789.306-1.459.717-2.126 1.384S.935 3.35.63 4.14C.333 4.905.131 5.775.072 7.053.012 8.333 0 8.74 0 12s.015 3.667.072 4.947c.06 1.277.261 2.148.558 2.913.306.788.717 1.459 1.384 2.126.667.666 1.336 1.079 2.126 1.384.766.296 1.636.499 2.913.558C8.333 23.988 8.74 24 12 24s3.667-.015 4.947-.072c1.277-.06 2.148-.262 2.913-.558.788-.306 1.459-.718 2.126-1.384.666-.667 1.079-1.335 1.384-2.126.296-.765.499-1.636.558-2.913.06-1.28.072-1.687.072-4.947s-.015-3.667-.072-4.947c-.06-1.277-.262-2.149-.558-2.913-.306-.789-.718-1.459-1.384-2.126C21.319 1.347 20.651.935 19.86.63c-.765-.297-1.636-.499-2.913-.558C15.667.012 15.26 0 12 0zm0 2.16c3.203 0 3.585.016 4.85.071 1.17.055 1.805.249 2.227.415.562.217.96.477 1.382.896.419.42.679.819.896 1.381.164.422.36 1.057.413 2.227.057 1.266.07 1.646.07 4.85s-.015 3.585-.074 4.85c-.061 1.17-.256 1.805-.421 2.227-.224.562-.479.96-.899 1.382-.419.419-.824.679-1.38.896-.42.164-1.065.36-2.235.413-1.274.057-1.649.07-4.859.07-3.211 0-3.586-.015-4.859-.074-1.171-.061-1.816-.256-2.236-.421-.569-.224-.96-.479-1.379-.899-.421-.419-.69-.824-.9-1.38-.165-.42-.359-1.065-.42-2.235-.045-1.26-.061-1.649-.061-4.844 0-3.196.016-3.586.061-4.861.061-1.17.255-1.814.42-2.234.21-.57.479-.96.9-1.381.419-.419.81-.689 1.379-.898.42-.166 1.051-.361 2.221-.421 1.275-.045 1.65-.06 4.859-.06l.045.03zm0 3.678a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4zm7.846-10.405a1.441 1.441 0 01-2.88 0 1.441 1.441 0 012.88 0z'/></svg>")}
.bi--whatsapp {--bi:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z'/></svg>")}
/* the contexts that used to size these through font-size */
.button .bi{--bi-size:17px}
.footer__socials .bi{--bi-size:19px}
.footer__contact .bi{--bi-size:16px}
.contact__fact .bi{--bi-size:18px}


/* --------------------------------------------------------------------------
   8. Hero  -  full-bleed four-slide carousel
   --------------------------------------------------------------------------
   All four slides share one grid cell, so the tallest of them gives the hero
   its height and nothing is positioned absolutely against a guess. Only
   opacity crossfades; the photograph never moves, so there is nothing for
   Lenis or ScrollTrigger to fight.

   Without JS the first slide is the one CSS paints - the shipped state is the
   finished state, not a hidden one. */
.hero{
  position:relative;overflow:hidden;
  /* cancels the body's header offset, so the photograph starts at y=0 and
     runs under the transparent bar */
  margin-top:calc(var(--header-h) * -1);
  background:var(--page);
  min-height:100svh;
}
@supports (height:100dvh){.hero{min-height:100dvh}}

.hero__slides{
  list-style:none;margin:0;padding:0;
  display:grid;grid-template-areas:"slide";
  min-height:inherit;
}
.hero__slide{
  grid-area:slide;position:relative;
  display:grid;align-content:center;
  padding-block:calc(46px + var(--header-h)) 118px;
  opacity:0;visibility:hidden;
  transition:opacity .7s var(--ease-out),visibility 0s linear .7s;
}
.hero__slide.is-active{
  opacity:1;visibility:visible;
  transition:opacity .7s var(--ease-out),visibility 0s;
}
@media (max-width:767px){
  .hero__slide{padding-block:calc(30px + var(--header-h)) 104px}
}
.hero__shot{
  position:absolute;inset:0;z-index:0;
  /* 35%, not 50%. On a wide screen the hero is about 2.2:1 and the picture
     1.5:1, so only the middle 46% of its height shows - and centring that
     window left the cars pressed against its top edge. Moving the window
     up brings the subject down into the middle of the frame. */
  width:100%;height:100%;object-fit:cover;object-position:60% 35%;
}
/* Two veils, each doing one job.

   The wide one runs left to right and is what the copy sits on: white 19px
   text needs 4.5, and the brightest thing that can end up behind it is a
   white truck cab or a white swan, so over pure white the veil has to hold
   about .60 alpha where the text ends. It holds .86 out to 42% and .58 to
   62% before letting the photograph through.

   The short one runs down from the top and is only there so the transparent
   header always has a dark ground under it, whatever the photograph does. */
.hero__slide::before,
.hero__slide::after{content:"";position:absolute;inset:0;z-index:1;pointer-events:none}
.hero__slide::before{
  background:linear-gradient(90deg,rgba(18,18,18,.90) 0%,rgba(18,18,18,.86) 42%,rgba(18,18,18,.58) 62%,rgba(18,18,18,.14) 82%,rgba(18,18,18,.06) 100%);
}
.hero__slide::after{
  background:linear-gradient(180deg,rgba(18,18,18,.42) 0,rgba(18,18,18,0) calc(var(--header-h) + 40px));
}
.hero__inner{position:relative;z-index:2}

/* --- the copy, in the reference's order ---------------------------------- */
.hero__copy{display:grid;justify-items:start;gap:0;max-width:640px}
.hero__copy .eyebrow{color:#fff;margin-bottom:18px}
/* Uppercase and hard against its own next line. h1 on slide one, p on the
   other three - the page keeps exactly one h1 - so both carry the type. */
/* The hero sets colour and its own tighter leading; size, weight, tracking
   and case all come from the base rule now. .hero__title is a <p> on the
   slides that are not the first - the page keeps exactly one h1 - so it still
   has to name the family and size itself. */
.hero h1,.hero__title{
  font-family:var(--font-display);font-weight:var(--w-light);
  font-size:var(--t-h1);letter-spacing:-1.4px;text-transform:uppercase;
  line-height:.92;color:#fff;margin:0;text-wrap:balance;
}
/* the accent is 2.93 over the hero's veil, so the hero runs on white and
   the red belongs to the light page below it */
.hero__dot{color:#fff}
.hero__rule{width:64px;height:4px;border-radius:2px;background:rgba(255,255,255,.82);margin:26px 0 20px}
.hero .lede{color:rgba(255,255,255,.92);max-width:40ch}
.hero__actions{display:flex;flex-wrap:wrap;gap:12px;margin-top:30px}
/* Yellow on a dark veil is 10.4 for the label's ink, so the primary button
   goes back to the page's own yellow here. The ghost button needs a border it
   can be seen by: .56 white is 3.85 against the veil, .4 would be 2.65. */
.hero .button--primary{background:var(--accent);color:#fff}
.hero .button--primary:hover{background:var(--accent-deep)}
.hero .button--ghost{background:transparent;color:#fff;border-color:rgba(255,255,255,.56)}
.hero .button--ghost:hover{border-color:#fff;background:rgba(255,255,255,.12)}

/* --- the controls -------------------------------------------------------- */
/* The dots keep the copy's left margin - the same max() the .shell uses, so on
   a wide screen they sit on the 1500px column's edge and on a narrow one they
   fall back to the gutter. Not .shell itself: that sets a width, and a width on
   an absolutely positioned box is what pushed 190px off the right-hand side. */
.hero__controls{
  position:absolute;z-index:3;bottom:38px;
  left:max(var(--gutter),(100% - var(--shell)) / 2);
  display:flex;align-items:center;gap:18px;
}
@media (max-width:767px){.hero__controls{bottom:26px;gap:12px}}

/* The arrows are pinned to the section's own edges, one each side, on the
   vertical centre - so they read as belonging to the whole hero rather than
   sitting in a row with the dots. They are direct children of .hero, not of
   the controls row, or they could not be placed apart. */
/* Bottom-right by default, beside the dots.

   An arrow on the vertical centre line is what the section wants, but the copy
   also lives on that line and starts at the gutter - so a centred arrow can
   only go OUTBOARD of the copy, in the 8..52px channel at the very edge. That
   channel only exists once the gutter is wider than the arrow, which happens
   at 1180px (5vw = 59). Below that they stay in the corner rather than land on
   top of "EVERY KIND OF PLAY.". */
.hero__arrow{
  position:absolute;z-index:4;bottom:34px;
  width:44px;height:44px;border-radius:50%;cursor:pointer;
  display:grid;place-items:center;font-size:17px;
  background:rgba(255,255,255,.12);color:#fff;
  /* 1.4.11 wants 3 of a control's boundary; .58 white over the veil is 4.0 */
  border:2px solid rgba(255,255,255,.58);
  transition:background-color .22s var(--ease-out),border-color .22s var(--ease-out);
}
.hero__arrow--prev{right:calc(var(--gutter) + 54px)}
.hero__arrow--next{right:var(--gutter)}
.hero__arrow:hover{background:rgba(255,255,255,.28);border-color:#fff}

/* Wide enough for the channel: one arrow each side, on the section's own edges
   and on its vertical centre. */
@media (min-width:1180px){
  .hero__arrow{top:50%;bottom:auto;translate:0 -50%;width:48px;height:48px;font-size:18px}
  .hero__arrow--prev{left:8px;right:auto}
  .hero__arrow--next{right:8px}
}
@media (max-width:479px){
  .hero__arrow{width:40px;height:40px;font-size:15px;bottom:24px}
  .hero__arrow--prev{right:calc(var(--gutter) + 48px)}
}
.hero__dots{display:flex;align-items:center;gap:10px}
.hero__dot-btn{
  width:11px;height:11px;padding:0;border-radius:50%;cursor:pointer;
  background:transparent;border:2px solid rgba(255,255,255,.62);
  transition:background-color .24s var(--ease-out),border-color .24s var(--ease-out),width .24s var(--ease-out);
}
.hero__dot-btn.is-active{width:30px;border-radius:var(--r-pill);background:#fff;border-color:#fff}

/* --- wide layouts ------------------------------------------------------- */
@media (min-width:900px){
  /* The copy stops at 46% so it never runs into the part of the frame the
     photograph's subject occupies - every one of the four is composed with
     its toy in the right two thirds. */
  .hero__copy{max-width:min(46%,700px)}
}
@media (min-width:1400px){
  .hero__slide{padding-block:calc(60px + var(--header-h)) 132px}
}
/* --------------------------------------------------------------------------
   In motion  -  the clip carousel
   --------------------------------------------------------------------------
   The card is 9:16 because the clips are: they come off a phone. Everything
   else - the scroller, the snap, the arrows, the rail, the drag - is the Best
   sellers carousel unchanged, so there is one scrolling mechanism on the page
   rather than two that can disagree. */
/* Odd counts on purpose. A centre-focus row needs a real middle card - at
   4.4 across there is no middle, the enlarged card would sit off-axis. 1, 3
   and 5 across each have one, with the .2/.4 as the peek that says the row
   keeps going. */
/* 1.35, not 1.25: in centre mode the inset either side is what is left of
   the viewport once the card is placed, so a wider card leaves a thinner
   peek. 1.25 left 21px of the next clip showing, 1.35 leaves 33px - enough
   to read as a row rather than as a single card. */
.carousel--reels .carousel__track{--per:1.35}
@media (min-width:720px){ .carousel--reels .carousel__track{--per:3.2} }
@media (min-width:1250px){ .carousel--reels .carousel__track{--per:5.4} }

/* 3:4, not the 9:16 the placeholder clip was.

   The clips are the shop's own product videos and they arrive in three shapes:
   four are 3:4, two are square and four are 16:9. 3:4 is the plurality and the
   cheapest compromise - measured across the ten, a 3:4 tile wastes 28% of its
   area on fill, a square tile 28% and a 9:16 tile 46%. Nothing is cropped:
   each clip is letterboxed onto a blurred, darkened copy of itself, so a
   landscape clip keeps its whole frame instead of losing 68% of its width. */
.reel{
  position:relative;margin:0;width:100%;aspect-ratio:3/4;
  border-radius:var(--r-card);overflow:hidden;
  /* the clips are dark, so the card's own ground is too - a white box behind a
     loading video flashes before the first frame lands */
  background:#1a1a1a;
  border:1px solid var(--line);
}
/* On the dark ground the ink hairline is invisible - and so is the card's own
   #1a1a1a against #222 - so the edge has to come from the other side.

   Deliberately left under 3:1, and not claimed as passing it. This is not a
   component boundary: the poster fills the card, so what delimits a clip is the
   picture itself, and the hairline is only seen in the moment before the poster
   lands. Taking it to the 0.34 the rail needs would draw a light frame round
   every clip - a worse card, for a rule that does not apply to it. */
.band--red .reel{border-color:rgba(255,255,255,.24)}
.reel__vid{width:100%;height:100%;display:block;object-fit:cover;background:#1a1a1a}
/* a ground for the caption, whatever the clip is doing underneath */
.reel::after{
  content:"";position:absolute;z-index:1;inset:auto 0 0 0;height:42%;
  pointer-events:none;
  background:linear-gradient(to top,rgba(18,18,18,.88),rgba(18,18,18,0));
}
/* the whole card is the control, so a tap anywhere plays or pauses */
.reel__toggle{
  position:absolute;inset:0;z-index:2;border:0;padding:0;cursor:pointer;
  display:grid;place-items:center;background:transparent;color:#fff;
}
.reel__toggle i{
  font-size:20px;width:52px;height:52px;border-radius:50%;
  display:grid;place-items:center;
  /* .62 of the ink over the brightest frame the clip can show is 5.4 for the
     glyph, and the 2px white edge carries the control's boundary at 4.0 */
  background:rgba(20,20,20,.62);border:2px solid rgba(255,255,255,.72);
  transition:opacity .25s var(--ease-out),transform .25s var(--ease-out);
}
/* while it plays the button gets out of the way, and comes back on hover so
   there is always a way to stop it */
.reel.is-playing .reel__toggle i{opacity:0;transform:scale(.86)}
@media (hover:hover){ .reel.is-playing:hover .reel__toggle i{opacity:1;transform:none} }
.reel.is-playing .reel__toggle:focus-visible i{opacity:1;transform:none}
.reel__cap{
  position:absolute;z-index:3;left:13px;right:13px;bottom:12px;
  font-family:var(--font-body);font-weight:700;font-size:13px;line-height:1.35;
  color:#fff;pointer-events:none;
  /* 13.35 against the veil over a white frame; the shadow is for the edges of
     the letters where the veil is thinnest, not for the ratio */
  text-shadow:0 1px 8px rgba(18,18,18,.9);
}

/* --------------------------------------------------------------------------
   9. Category grid
   -------------------------------------------------------------------------- */
.cats{display:grid;gap:clamp(14px,1.6vw,22px);grid-template-columns:repeat(2,1fr)}
@media (min-width:760px){.cats{grid-template-columns:repeat(4,1fr)}}

.cat{
  background:var(--paper);border:1px solid var(--line);border-radius:var(--r-card);
  overflow:hidden;box-shadow:var(--shadow-sm);
  transition:transform .32s var(--ease-out),box-shadow .32s var(--ease-out);
}
.cat:hover{transform:translateY(-3px);box-shadow:var(--shadow-md)}
/* Square, not 4/3. The photos are square, so a 4/3 window was cutting 25%
   off every one of them - the top and bottom of the actual product. */
.cat__shot{position:relative;aspect-ratio:1/1;overflow:hidden;background:var(--page)}
.cat__shot img{width:100%;height:100%;object-fit:cover}
/* The card grows, the photo does not. Zooming the photo inside a fixed
   window crops 6% off it on hover, which is the one thing these tiles are
   not allowed to do - the hover rule above scales the whole card instead. */
.cat__label{display:flex;align-items:center;gap:11px;padding:14px 16px 16px;min-height:80px}
/* one accent, not eight: the per-tile rainbow was the loudest cartoon
   signal in the grid */
.cat__label i{flex:none;width:34px;height:34px;border-radius:50%;display:grid;place-items:center;
  font-size:16px;background:var(--accent);color:#fff}
@media (max-width:759px){.cat__label{padding:12px 13px 14px;min-height:72px}.cat__label i{width:30px;height:30px;font-size:14px}}


/* --------------------------------------------------------------------------
   10. Best sellers - carousel
   -------------------------------------------------------------------------- */
/* A native overflow-x scroller with scroll-snap, NOT a transform carousel.
   Touch, trackpad, keyboard and find-in-page all work for free, the arrows are
   just scrollBy, and there is no transform for Lenis or ScrollTrigger to fight
   over. Mouse drag is layered on top with pointer events. */
.carousel{position:relative}
.carousel__track{
  --per:1.5;
  display:flex;gap:var(--card-gap);
  overflow-x:auto;overscroll-behavior-x:contain;
  scroll-snap-type:x mandatory;scroll-behavior:smooth;
  /* the cards lift and drop a shadow on hover, so the scroller needs bleed
     above and below or the overflow clips both. The negative margin puts the
     surrounding rhythm back. */
  margin:-14px -8px -20px;
  scrollbar-width:none;-ms-overflow-style:none;
}
/* The bleed the cards lift and grow into. It has to be written as
   .carousel .carousel__track, not .carousel__track: the reset's
   ul[class]{padding:0} is (0,1,1) and would win against a single class
   whatever the source order. */
.carousel .carousel__track{padding:14px 8px 28px;scroll-padding-inline:8px}
.carousel__track::-webkit-scrollbar{display:none}
.carousel__track > li{
  flex:0 0 calc((100% - (var(--per) - 1) * var(--card-gap)) / var(--per));
  scroll-snap-align:start;display:flex;
}
@media (min-width:620px){ .carousel__track{--per:2.5} }
@media (min-width:900px){ .carousel__track{--per:3.5} }
@media (min-width:1200px){ .carousel__track{--per:5} }
@media (prefers-reduced-motion:reduce){ .carousel__track{scroll-behavior:auto} }

/* grab cursor only where there is a real mouse; touch is the browser's job */
@media (hover:hover){
  .carousel__track{cursor:grab}
  .carousel.is-dragging .carousel__track{
    cursor:grabbing;scroll-snap-type:none;scroll-behavior:auto;
  }
  .carousel.is-dragging{user-select:none;-webkit-user-select:none}
  /* a drag that ends on a card must not fire that card's Add to cart */
  .carousel.is-dragging .pcard{pointer-events:none}
}

.carousel__nav{display:flex;align-items:center;justify-content:center;margin-top:30px}

/* Full-bleed track.

   The head and the "View all" stay on the shell; the row of clips runs the
   whole viewport between them. The gutter comes back as the track's own inline
   padding rather than as a container, so the first card still lines up with
   the heading above it and the row bleeds off the right edge instead of
   stopping short of it. The base rule's -8px inline margin - the bleed the
   cards lift into - is folded into that padding. */
.carousel--bleed .carousel__track{
  margin-inline:0;
  /* --edge is written by main.js on a centre carousel: half the gap between
     the viewport and one card, so card one can sit on the centre line with
     nothing to its left. Without it the row just starts at the gutter. */
  padding-inline:var(--edge, var(--gutter));
  scroll-padding-inline:var(--edge, var(--gutter));
}

/* Centre mode.

   The card on the middle line is the one being shown; the rest are held back
   at 86%. The row opens with card one already on that line and empty ground to
   its left, which is what the left inset is for, and it closes with the last
   card there.

   The slot keeps its width and only the card inside it scales, so an enlarged
   card never overlaps its neighbours and nothing reflows as the focus moves -
   the scroller's own geometry is untouched. */
.carousel--centre .carousel__track > li{
  scroll-snap-align:center;
  /* px, from main.js. The percentage basis below resolves against the content
     box, which the inset shrinks - so sizing the card that way would feed the
     inset back into the card width and never settle. The fallback is the
     original expression, for the frame before the script runs. */
  flex-basis:var(--card-w, calc((100% - (var(--per) - 1) * var(--card-gap)) / var(--per)));
}
.carousel--centre .reel{
  transform:scale(.86);
  transform-origin:50% 50%;
  transition:transform .42s var(--ease-out);
}
.carousel--centre li.is-focus .reel{transform:none}

/* Endless row. There is no first card and no last one, so there is no inset
   either: the clips run off both edges and the row opens already full, with
   the third of the five on the centre line. */
.carousel--loop .carousel__track{
  padding-inline:0;
  scroll-padding-inline:0;
}
@media (prefers-reduced-motion:reduce){
  .carousel--centre .reel{transition:none}
}

/* Arrows under the track rather than beside it. A row of two, centred on the
   same axis as the head and the button. */
.carousel__foot{
  display:flex;align-items:center;justify-content:center;gap:12px;
  margin-top:26px;
}
/* Out of the absolute slots the side arrows use. --card-h means nothing here. */
.carousel__foot .carousel__btn{
  position:static;top:auto;left:auto;right:auto;translate:none;
  /* and they show at every width: the side arrows hide below 1080 because the
     gutter cannot hold a target clear of the cards, which is not true down
     here. On this carousel they are also the only "there is more" mark left,
     now that the progress rail is gone. */
  display:grid;
}

/* The arrows sit outside the track on either side, level with the middle of
   the card. --card-h is written by main.js on every sync: the photo is
   square, so the card's height follows how many cards are in view and CSS
   cannot work it out on its own. The 14px is the track's own top padding. */
.carousel__btn{
  --btn:50px;--btn-gap:12px;
  position:absolute;z-index:5;
  top:calc(14px + var(--card-h, 420px) / 2);
  translate:0 -50%;
  width:var(--btn);height:var(--btn);border:0;border-radius:50%;cursor:pointer;
  display:grid;place-items:center;font-size:20px;
  /* Filled with the accent from the start. White was 1.05:1 against the
     --page ground it sits on - the shadow was the only thing drawing it, so on
     a bright screen the control simply was not there. The accent is 4.75:1
     against that ground, well over the 3:1 a control's own boundary owes, and
     white on it is 5.19:1 for the glyph. */
  background:var(--accent);color:#fff;box-shadow:var(--shadow-md);
  transition:background .24s var(--ease-out),color .24s var(--ease-out),
             scale .24s var(--ease-out),opacity .24s var(--ease-out);
}
/* Fully outside the track, never over a card. The offset is the button's own
   width plus a gap, so it has to fit inside the shell's gutter
   - clamp(22px,5vw,74px). Below 1080px that gutter is too narrow for a
   50px target to clear the cards, so the arrows go and the row is swiped. */
.carousel__btn--prev{left:calc(-1 * var(--btn) - var(--btn-gap))}
.carousel__btn--next{right:calc(-1 * var(--btn) - var(--btn-gap))}
/* Hover deepens rather than changes colour: 6.91:1 against the ground and
   7.53:1 under the glyph, so the state that follows the cursor is the one with
   the most contrast, not the least. Same step the primary button takes. */
.carousel__btn:hover:not(:disabled){background:var(--accent-deep)}
.carousel__btn:active:not(:disabled){scale:.94}
.carousel__btn:disabled{opacity:.3;cursor:default}

/* the gutter shrinks with the viewport, so the button shrinks with it */
@media (max-width:1279px){ .carousel__btn{--btn:44px;--btn-gap:8px;font-size:18px} }
@media (max-width:1079px){
  /* under here the gutter cannot hold a target clear of the cards, and every
     device this narrow has a touchscreen. The rail still shows the position. */
  .carousel__btn{display:none}
}
.carousel__rail{
  position:relative;flex:1 1 240px;max-width:300px;height:5px;
  border-radius:5px;background:var(--line);overflow:hidden;
}
.carousel__thumb{position:absolute;inset-block:0;left:0;width:25%;border-radius:5px;background:var(--accent)}
/* The band IS the accent now, so none of the furniture drawn in the accent can
   stay in it. Every value below was recomputed against #d71920, luminance
   0.1525 - a far brighter ground than the #222 this block used to be, and a
   much tighter one to fit a three-step control into.

   The arrow inverts: a white disc at 5.19:1 against the ground, carrying the
   accent as its glyph at 5.19:1 on the white. Hover steps to 88% white rather
   than deepening - on this ground a darker fill runs toward the ground's own
   colour - and still holds 4.25:1. */
.band--red .carousel__btn{background:#fff;color:var(--accent)}
.band--red .carousel__btn:hover:not(:disabled){background:rgba(255,255,255,.88)}
/* The rail is part of a control, so it owes 1.4.11 its 3:1 against that ground.
   3:1 wants the composited rail at 0.5574, and only a near-white reaches it:
   .70 white measures 0.5780, which is 3.10:1. The .34 the dark band used would
   have been 1.60:1 here. */
.band--red .carousel__rail{background:rgba(255,255,255,.70)}
/* Which leaves the thumb nowhere to go upward - white on that rail is 1.63:1,
   and the thumb is the one mark that says where you are in the row. So it goes
   down instead and takes the ink: 9.52:1 against its own rail and 3.07:1
   against the ground, which is how a scrollbar reads anyway. */
.band--red .carousel__thumb{background:var(--dark)}


/* All three sit clear of the card row, which runs from roughly 26% to 78% of
   the band. The third one used to be at 46% and landed on top of whichever
   card happened to be at the right edge. */

/* --------------------------------------------------------------------------
   New arrivals + the product card they share with Best sellers
   -------------------------------------------------------------------------- */
/* One card, two hosts. The grid in New arrivals and the carousel in Best
   sellers both stretch their children, so the card is a flex column and the
   button takes margin-top:auto - that lands every Add to cart on the same
   baseline however long the product name runs. */
/* auto-rows:1fr so a two-line name in the first row does not leave the
   second row shorter. The slack lands above the name, not in the gaps. */
.arrivals{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
          /* 18px, measured off the reference's four-column grid */
          grid-auto-rows:1fr;gap:18px}
@media (min-width:760px){.arrivals{grid-template-columns:repeat(3,minmax(0,1fr))}}
/* Five across, so the whole set shows at once with no second row. 1220 is
   where that stops squeezing: the shell is 1098 there, which leaves 205px a
   card - the same width the tightest card was designed at. */
@media (min-width:1220px){
  .arrivals{grid-template-columns:repeat(5,minmax(0,1fr))}
}
.arrivals > li{display:flex}

/* No shadow. The card is white on --sky, which separates it on its own, and a
   drop shadow under twelve of them in a row turned the band grey. */
.pcard{
  position:relative;width:100%;background:var(--paper);
  border-radius:var(--r-card);
  /* a hairline, not a shadow. White on the page ground is 1.07 - no readable
     edge at all on its own. box-sizing is border-box, so this costs no width. */
  border:1px solid var(--line);
  /* No padding: the photo meets the border on three sides. The card keeps
     overflow:hidden, so its own radius clips the top two corners of the image
     and the photo needs no radius of its own. Only the type is inset, by the
     body's padding. */
  padding:0;
  overflow:hidden;display:flex;flex-direction:column;
  transition:transform .32s var(--ease-out);
}
@media (hover:hover){
  /* the whole card grows on hover; the photo sits still inside it. Scaling
     both made the image creep against its own frame. */
  /* a lift and a firmer edge, not a zoom: scaling the whole card was the
     toybox's move, and on a hairlined tile it makes the border shimmer */
  .pcard:hover{transform:translateY(-3px);border-color:rgba(34,34,34,.30)}
}
.pcard__shot{width:100%;aspect-ratio:1/1;background:var(--page);overflow:hidden}
.pcard__shot img{
  width:100%;height:100%;object-fit:cover;display:block;
}

/* the rank chip sits on the photo, so it needs its own contrast rather than
   the page's: white on --red-dark is 6.05, white on --blue is 9.10 */
.pcard__rank{
  position:absolute;z-index:2;top:12px;left:12px;color:#fff;
  font-weight:700;font-size:11px;letter-spacing:1.2px;text-transform:uppercase;
  box-shadow:0 6px 14px -6px rgba(8,20,80,.5);
}
.pcard__rank{
  width:28px;height:28px;border-radius:50%;display:grid;place-items:center;
  font-family:var(--font-display);letter-spacing:.3px;
  --rank:var(--accent);background:var(--rank);
}

/* Centred, and the gaps are set per pair rather than by one shared value.
   Measured on the actual faces: the name's line box carries 4.5px of empty
   space above the glyphs and the price's carries 3.6px either side, so an
   equal 11px everywhere read as 20.5 / 15 / 15 / 18 down the card. These
   numbers subtract that leading so all four gaps land on ~16px of daylight. */
.pcard__body{
  display:flex;flex-direction:column;
  /* The body now owns the whole inset, since the card has none: 14px at the
     sides and foot, 12px at the head. 12 not 14 because the name's line box
     carries 4.5px of empty space above its glyphs, so 12 lands the gap under
     the photo on the same ~16px of daylight as the other three sides. */
  padding:12px 14px 14px;
  text-align:left;
  flex:1 1 auto;
}
/* No width cap.

   The old one was 156px on a 307px card - half the tile - and it forced short
   names into a narrow column in the middle, two lines wide enough to total
   87px inside 279px of room. Measured against Inter at 18px/700, the longest
   name in the catalogue ("Spraying Mist Kitchen Set", 25 characters) sets at
   227px, and at the capped 1500px shell a name has 252-256px, so every one of
   them fits on a single line. Below about 1500px the shell shrinks and those
   two kitchen sets take a second line - which is what the clamp is for.

   Nothing is reserved. Two lines WERE, to keep all four of a card's horizontals
   level across a row - but with every name on one line the reserve is an empty
   line sitting between the name and the price, and that is the most legible
   thing on the card.

   The slack goes under the price instead, on the button's auto margin. Of the
   four gaps that is where a row's cards can differ least visibly: the photo's
   foot and the name still line up across the row, so does the button, and
   price-to-button is white space between two blocks rather than a hole in the
   type. What it costs is the price's own baseline when one name in a row does
   wrap - it drops a line below its neighbours'.

   The clamp stays as the guard for a third line. */
.pcard h3{
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
  overflow:hidden;
  /* Two lines ALWAYS, not "up to two". The clamp alone caps a long name but
     lets a short one sit on one line, so in a row of five cards the prices
     and the buttons below them landed at different heights. Reserving the
     second line costs nothing and the row aligns. Not a font-size, so the
     type lock has no opinion; it tracks --t-h3 so the mobile step follows. */
  min-height:calc(var(--t-h3) * 2 * 1.3);
  margin-top:0;margin-bottom:10px;
}
.pcard__price{
  font-family:var(--font-display);font-weight:700;font-size:var(--t-price);
  line-height:1.1;color:var(--accent);
  display:flex;align-items:baseline;justify-content:flex-start;gap:6px;
  margin-bottom:14px;
}
.pcard__price small{
  font-family:var(--font-body);font-weight:600;font-size:var(--t-small);
  letter-spacing:.2px;color:var(--ink-soft);
}
/* the button carries the auto margin now, so any residual slack in an
   equalised row collects between the price and the button - the one gap on
   the card where a few pixels of drift cannot be seen */
.pcard__add{width:100%;padding-inline:14px;margin-top:auto}
/* At two-up on a phone the card is ~158px, and the button only clears
   ~122px of that. 16px of label plus an icon plus the button's own padding
   does not fit, and flex answers by crushing the padding to 4px a side.
   The icon is the part carrying no information, so below 480px it goes. */
@media (max-width:479px){ .pcard__add i{display:none} }

/* --------------------------------------------------------------------------
   11. Contact panel
   -------------------------------------------------------------------------- */
.contact{display:grid;gap:34px}
/* One gap governs the whole stack, so eyebrow -> heading and heading -> cards
   are identical by construction. align-content:start because the column is a
   stretched grid item now (that is what makes the map match its height), and
   without it the rows would spread whenever the map is the taller of the two. */
.contact__copy{display:grid;gap:14px;align-content:start}
@media (min-width:900px){.contact{grid-template-columns:.92fr 1.08fr;gap:44px;align-items:stretch}}
/* no margin-top: .section-head above already supplies the 44px that every
   other section puts between its heading block and its body. */
.contact__facts{display:grid;gap:14px}
/* Filled colour cards, not white cards with a coloured outline.

   All four are the same white card now. The old version gave each one its own
   ground and two of them failed: white measured 2.16 on --yellow-deep and 3.01
   on --green, against the 4.5 that 14-15px text needs - and those two carry the
   phone and the WhatsApp number. On white, --ink is 15.9 and the accent disc
   4.9, so the problem cannot come back.

   Everything inside still inherits with currentColor. */
.contact__fact{
  display:flex;gap:14px;align-items:flex-start;
  background:var(--paper);border:1px solid var(--line);color:var(--ink);
  border-radius:var(--r-card);padding:20px 22px;
}
/* the disc is the one piece of accent on the card; the glyph on it is white */
.contact__fact i{
  flex:none;width:40px;height:40px;border-radius:50%;display:grid;place-items:center;
  font-size:18px;background:var(--accent);color:#fff;
}
.contact__fact b{display:block;font-family:var(--font-display);font-weight:700;font-size:15px;line-height:1.3;color:currentColor}
.contact__fact p,.contact__fact a{display:block;font-size:14px;line-height:1.55;color:currentColor;margin-top:2px}
/* the old hover turned the link --red, which is invisible on the red card */
.contact__fact a:hover{text-decoration:underline;text-underline-offset:3px}

/* Card hover. Behind @media (hover:hover) so a phone does not leave a card
   stuck in its hovered state after a tap. The disc deepens rather than the
   card changing colour - the four grounds are already the section's colour
   story, and shifting them on hover would fight it. Each card names its own
   hover value because two sit on dark grounds and two on light. */
@media (hover:hover){
  .contact__fact{
    transition:transform .28s var(--ease-out),box-shadow .28s var(--ease-out);
  }
  .contact__fact i{transition:background-color .28s var(--ease-out)}
  .contact__fact:hover{
    transform:translateY(-4px);
    box-shadow:0 22px 40px -20px rgba(34,34,34,.55);
  }
  .contact__fact:hover i{background:var(--accent-deep)}
}
@media (prefers-reduced-motion:reduce){
  .contact__fact,.contact__fact i{transition:none}
  .contact__fact:hover{transform:none}
}
.map{position:relative;border-radius:var(--r-card);overflow:hidden;border:1px solid var(--line);
     box-shadow:var(--shadow-md);background:var(--page);min-height:390px}
.map iframe{position:absolute;inset:0;width:100%;height:100%;border:0}

/* --------------------------------------------------------------------------
   12. Final CTA
   -------------------------------------------------------------------------- */
/* .band supplies position, overflow and the vertical padding. No bottom
   margin: this band is meant to run straight into the blue footer. */
/* Tall enough for the props. They are centred on the band and the band clips,
   and with the blanket rule taking its top padding away the band came out about
   350px against a 315px prop - so both were cut off. It keeps its own padding
   now, like any coloured block, and this floor covers the case where the copy
   is short. */
.cta{
  text-align:center;
  min-height:clamp(430px,34vw,560px);
  display:grid;align-content:center;
}
.cta__inner{position:relative;z-index:2;display:grid;gap:18px;justify-items:center}
.cta__inner .lede{max-width:46ch}
/* The lede goes full white here. .on-dark sets it to 84% white, which is
   11.56:1 on the #222 band and only 3.97 on the accent - under the 4.5 that
   17px regular text owes. #fff on #d71920 is 5.19. */
.band--red .lede{color:#fff}
/* red on red is no button. It inverts on this ground - white pill, accent
   label, which is 5.19:1 the other way round. */
.band--red .button--primary{background:#fff;color:var(--accent)}
.band--red .button--primary:hover{background:rgba(255,255,255,.88)}
/* Centred on the band, not resting on its bottom edge - and never taller than
   the band, whatever the copy does to its height. object-fit keeps the picture
   from squashing when that cap is the one doing the work. */
/* The closing bar on an inner page.

   Not the homepage's block: that is a tall centred red panel with a teddy and
   a truck either side, and running the same thing at the foot of every page
   turns a finale into wallpaper. This is a short strip - copy left, one button
   right - on the same left-hand line the banner and the body already run on.

   It went out on the ink ground first, to be as unlike the red panel as
   possible. That was wrong for a reason contrast measurement could not catch:
   .site-footer is var(--dark) too, so the bar and the footer were the same
   #222 with no edge between them and read as one block. The blush does the
   separating instead - light into near-black - and it is still nothing like
   the homepage's ending. */
.cta-bar{display:grid;gap:20px;align-items:center}
.cta-bar__copy{display:grid;gap:9px}
.cta-bar .button{justify-self:start}
@media (min-width:860px){
  .cta-bar{grid-template-columns:1fr auto;gap:44px}
  .cta-bar .button{justify-self:end}
}

/* The Contact CTA sits over a photograph. Its frosted surface separates the
   message from the stockroom without hiding the image underneath. */
.band--panel{
  background:var(--dark);
  /* 100px of section padding left the frame cropped to a 373px strip, too
     thin for an aisle receding into the dark to read as anything. 1.45 gives
     it about 465px - still nothing like the homepage panel, which is 430-560
     of centred type with no picture at all. */
  padding-block:calc(var(--section) * 1.45);
}
/* Nothing on this photograph is text - the card is opaque - so the veil is
   only taking a little saturation out of the boxes. Heavier is worse, not
   safer: a veil moves the frame toward #222, which is the footer, so
   band-versus-footer falls from 1.75 at no veil to 1.17 at .45 and the
   texture that keeps them apart falls with it. .22 measured 1.55. */
.band--panel .band__shot{
  position:absolute;inset:0;z-index:0;width:100%;height:100%;
  object-fit:cover;object-position:50% 50%;
}
.band--panel::after{
  content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  /* .22 was optimised for the wrong number. Band-versus-footer only matters
     when two flat colours meet; a photograph always has texture to separate
     them. What actually went wrong at .22 is that the boxes were loud enough
     to compete with the card, so this is the value the picture wants. */
  background:rgba(18,18,18,.48);
}
.band--panel > .shell{position:relative;z-index:2}
/* the frame leaves its middle empty for the card, and a phone in portrait
   would cover-crop to that middle and nothing else */
@media (max-width:759px){
  .band--panel .band__shot{object-position:24% 50%}
}
/* the band above is --page as well, so the blanket rhythm rule would zero
   this the way it did for band--blush */
.section + .band--panel,
.band + .band--panel,
.band--panel + .section,
.band--panel + .band{padding-top:var(--section)}
/* .band + .band zeroes the top padding, and putting back a plain
   var(--section) left this band 100 above the card and 145 below it. The
   exception has to carry the same value padding-block asks for. */
.section + .band--panel,
.band + .band--panel{padding-top:calc(var(--section) * 1.45)}

.cta-panel{
  position:relative;
  max-width:1080px;margin-inline:auto;
  background:rgba(255,255,255,.14);
  color:#fff;
  border:1px solid rgba(255,255,255,.34);
  border-radius:var(--r-card);
  padding:30px clamp(22px,4vw,40px);
  box-shadow:0 24px 54px -26px rgba(0,0,0,.72);
  -webkit-backdrop-filter:blur(18px) saturate(125%);
  backdrop-filter:blur(18px) saturate(125%);
}
.cta-panel h2{color:#fff}
/* Not 82% white. Composited on the glass - which is the photograph, blurred
   and lifted 14% - an eyebrow at .82 measures 4.41 against its own ground,
   under the 4.5 a 13px label owes. Pure white measures clear of it, and it
   is what .on-dark .eyebrow already does for the same reason. */
.cta-panel .eyebrow{color:#fff}
.cta-panel .lede{color:rgba(255,255,255,.92)}
.cta-panel > *{position:relative;z-index:1}
@media (min-width:860px){
  .cta-panel{padding:38px clamp(34px,4vw,52px)}
}
.cta__prop{position:absolute;z-index:1;top:50%;translate:0 -50%;
           pointer-events:none;width:clamp(150px,23vw,340px);
           max-height:calc(100% - 40px);object-fit:contain}
.cta__prop--left{left:2%}
.cta__prop--right{right:2%}
@media (max-width:900px){.cta__prop{display:none}}

/* --------------------------------------------------------------------------
   13. Footer
   -------------------------------------------------------------------------- */
.site-footer{background:var(--dark);padding-top:60px}
.footer__grid{display:grid;gap:34px;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));padding-bottom:40px}
.footer__brand{display:grid;gap:15px;align-content:start}
.footer__brand p{font-size:15px;line-height:1.6;color:rgba(255,255,255,.72);max-width:32ch}
.site-footer h4{color:#fff;margin-bottom:15px}
.footer__links{display:grid;align-content:start}
/* 15px / 600 is the header nav link exactly, so the two navigations read as
   the same system top and bottom. */
.footer__links a{font-size:15px;font-weight:600;padding:6px 0;color:rgba(255,255,255,.78);transition:color .2s var(--ease-out)}
.footer__links a:hover{color:#fff}
.footer__contact{display:grid;gap:11px;align-content:start}
.footer__contact a,.footer__contact p{display:flex;gap:10px;align-items:flex-start;font-size:15px;line-height:1.55;color:rgba(255,255,255,.78);transition:color .2s var(--ease-out)}
.footer__contact a:hover{color:#fff}
.footer__contact i{color:rgba(255,255,255,.66);font-size:16px;flex:none;margin-top:2px}
.footer__socials{display:flex;gap:9px;margin-top:4px}
.footer__socials a{
  width:40px;height:40px;border-radius:50%;display:grid;place-items:center;
  background:rgba(255,255,255,.12);color:#fff;font-size:18px;
  transition:background-color .24s var(--ease-out),transform .24s var(--ease-out);
}
.footer__socials a:hover{background:rgba(255,255,255,.16);transform:translateY(-2px)}
.footer__bar{border-top:1px solid rgba(255,255,255,.14)}
.footer__bottom{display:flex;flex-wrap:wrap;gap:8px 24px;align-items:center;justify-content:space-between;padding-block:18px;font-size:14px;color:rgba(255,255,255,.62)}
.footer__made a{color:#fff;text-decoration:underline;text-underline-offset:3px}

/* --------------------------------------------------------------------------
   14. Utilities

   Last layer on purpose. Every measure cap above lives on a section rule at
   (0,2,0) - .section-head .lede, .cta__inner .lede - so an opt-out has to
   match that weight AND come after all of them. Two chained classes gets the
   weight; sitting here gets the order. Patching each section individually is
   what this replaces.
   -------------------------------------------------------------------------- */
/* one line, so the cap only needs to stop a very wide viewport stretching
   it - not to wrap it. text-wrap:balance on .lede would split a string
   that does not fit into two even lines, so the copy is kept short. */
.lede.lede--wide{max-width:1000px}

/* --------------------------------------------------------------------------
   15. Motion support

   GSAP owns every reveal. The FINISHED state ships in CSS on purpose, so the
   page is complete with JavaScript off, on a failed CDN, or under reduced
   motion. gsap.fromTo() supplies the start state at runtime and clears it.
   -------------------------------------------------------------------------- */
.split-line{will-change:transform}
html.lenis,html.lenis body{height:auto}
.lenis.lenis-smooth{scroll-behavior:auto !important}
.lenis.lenis-smooth [data-lenis-prevent]{overscroll-behavior:contain}
.lenis.lenis-stopped{overflow:hidden}
@media (prefers-reduced-motion:reduce){.split-line{will-change:auto}}

/* --------------------------------------------------------------------------
   16. WordPress housekeeping
   -------------------------------------------------------------------------- */
.screen-reader-text{position:absolute!important;width:1px;height:1px;overflow:hidden;clip:rect(1px,1px,1px,1px);clip-path:inset(50%);white-space:nowrap}
.admin-bar .site-header{top:32px}
@media screen and (max-width:782px){.admin-bar .site-header{top:46px}}


/* --------------------------------------------------------------------------
   17. Product page and WooCommerce controls

   A product page has no generated banner. Its own photograph is the opening
   image, and an aisle shot above it would push the thing being sold below the
   fold - so the first block is a tight section instead of a .page-head.

   WooCommerce prints its own markup for three things this file has to dress:
   the gallery (.woocommerce-product-gallery), the variation form
   (.variations, .single_variation) and the add-to-cart button. Everything
   else on the page is the site's own.
   -------------------------------------------------------------------------- */
.section--tight{padding-top:calc(var(--section) * 0.6)}

.crumbs{
  display:flex;flex-wrap:wrap;align-items:center;gap:8px;
  font-family:var(--font-body);font-weight:400;font-size:var(--t-small);
  line-height:1.4;color:var(--ink-soft);
  margin-bottom:26px;
}
.crumbs a{color:var(--ink-soft)}
.crumbs a:hover{color:var(--accent)}
.crumbs span[aria-hidden]{color:var(--line);}
.crumbs [aria-current]{color:var(--ink)}

/* Photograph left, everything you decide with on the right. 1.05fr against
   1fr, not 1fr each: the gallery is square and the column beside it holds the
   title, the price, a variation select and two buttons, which runs taller -
   so the media takes the small surplus rather than leaving a gap under it. */
.product-main{
  display:grid;
  /* A ceiling on the photograph, not a fraction. At 1.05fr / 1fr it took
     628px of a 1283px shell - one toy filling half the screen, and upscaled
     too, since the source files are 800-1024px. 500 went too far the other
     way: it shrank the column beside it as well, and the h1 fell to three
     lines. 560 moves only the photograph, and max-width keeps the block from
     leaving a dead strip down the right. */
  grid-template-columns:minmax(0,560px) minmax(0,1fr);
  max-width:1260px;
  /* 96 rather than 64 between the photograph and the column that sells it.
     They are two different things - one to look at, one to read and act on -
     and at 64 they read as one block with a seam down it. */
  gap:clamp(30px,6vw,96px);
  /* Centred, not top-aligned. The column that sells the toy is shorter than
     the photograph beside it - eyebrow, name, price, one sentence, a select
     and a button - so start left a dead strip roughly a fifth of the frame
     tall under it, and the two halves stopped looking like a pair.

     Centring does carry a risk worth knowing about: anything WooCommerce adds
     to the variation block on selection would change the column's height and
     re-centre it, moving the whole side while the shopper is reading it.
     Measured here at 1500 - selecting an option moves the column 0px, because
     .woocommerce-variation-price is hidden and the availability line is empty
     for this range. If a future change puts something in that block, this is
     the rule that will need a reserved height. */
  align-items:center;
}
.product-main__media{
  background:var(--paper);border-radius:var(--r-card);overflow:hidden;
  border:1px solid var(--line);
}
.product-main__info{min-width:0}
/* Shopee titles run to 100+ characters, so the product name is set well below
   the site H1 and keeps its own casing. */
.product-main__info h1{margin:10px 0 0;font-size:clamp(20px,2vw,26px);line-height:1.3;text-transform:none;letter-spacing:0}

.product-main__price{
  font-family:var(--font-display);font-weight:700;font-size:var(--t-price);
  line-height:1.1;color:var(--accent);
  display:flex;align-items:baseline;gap:7px;
  margin:18px 0 0;
}
.product-main__price small{
  font-family:var(--font-body);font-weight:600;font-size:var(--t-small);
  letter-spacing:.2px;color:var(--ink-soft);
}
.product-main__info .lede{margin-top:14px}
.product-main__wa{margin-top:16px}
.product-main__wa .button{width:100%}
.product-main__sku{margin-top:16px;letter-spacing:.4px}

/* The gallery. WooCommerce ships flexslider styles of its own; these override
   only what would otherwise fight the card language - a square frame, the
   thumbnail row as a four-up grid, no drop shadow. */
.woocommerce-product-gallery{position:relative}
.woocommerce-product-gallery__wrapper{margin:0}
.woocommerce-product-gallery__image img{
  display:block;width:100%;height:auto;aspect-ratio:1/1;object-fit:cover;
}
.woocommerce-product-gallery .flex-control-thumbs{
  display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;
  list-style:none;margin:8px;padding:0;
}
.woocommerce-product-gallery .flex-control-thumbs img{
  display:block;width:100%;aspect-ratio:1/1;object-fit:cover;
  border-radius:var(--r-chip);opacity:.62;cursor:pointer;
  transition:opacity .2s var(--ease-out);
}
.woocommerce-product-gallery .flex-control-thumbs img.flex-active,
.woocommerce-product-gallery .flex-control-thumbs img:hover{opacity:1}
.woocommerce-product-gallery__trigger{
  position:absolute;top:12px;right:12px;z-index:3;
  width:40px;height:40px;display:grid;place-items:center;
  background:var(--paper);border:1px solid var(--line);border-radius:var(--r-pill);
  font-size:16px;color:var(--ink);
}

/* The variation form. One select per attribute group; several of these
   products have 46 options behind that select, so it gets the full width and
   a real touch height rather than a browser-default 21px control. */
.variations{width:100%;border-collapse:collapse;margin:24px 0 0}
.variations th,
.variations td{display:block;text-align:left;padding:0}
.variations th{
  font-family:var(--font-body);font-weight:700;font-size:var(--t-eyebrow);
  line-height:1.3;letter-spacing:1.9px;text-transform:uppercase;color:var(--ink-soft);
  padding-top:14px;padding-bottom:6px;
}
.variations select,
.product-main__info select{
  width:100%;min-height:48px;
  font-family:var(--font-body);font-weight:400;font-size:16px;line-height:1.3;
  color:var(--ink);background:var(--paper);
  border:2px solid var(--line);border-radius:var(--r-chip);
  padding:11px 14px;
}
.variations select:focus-visible,
.product-main__info select:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.reset_variations{
  display:inline-block;margin-top:10px;
  font-family:var(--font-body);font-weight:600;font-size:var(--t-small);
  color:var(--ink-soft);
}
.reset_variations:hover{color:var(--accent)}

.single_variation_wrap{margin-top:18px}
.woocommerce-variation-price{
  font-family:var(--font-display);font-weight:700;font-size:var(--t-price);
  line-height:1.1;color:var(--accent);
}
.woocommerce-variation-availability,
.woocommerce-variation-description{
  font-family:var(--font-body);font-weight:400;font-size:var(--t-small);
  line-height:1.55;color:var(--ink-soft);margin-top:6px;
}

/* WooCommerce's add-to-cart button, wearing the site's primary button. */
.single_add_to_cart_button,
.woocommerce button.button,
.woocommerce a.button,
.woocommerce input.button{
  display:inline-flex;align-items:center;justify-content:center;gap:9px;
  font-family:var(--font-display);font-weight:600;font-size:var(--t-btn);line-height:1;
  min-height:48px;padding:15px 26px;border-radius:var(--r-pill);white-space:nowrap;
  background:var(--accent);color:#fff;border:0;cursor:pointer;
  transition:transform .22s var(--ease-out),background-color .22s var(--ease-out);
}
.single_add_to_cart_button{width:100%;margin-top:14px}
.single_add_to_cart_button:hover,
.woocommerce button.button:hover,
.woocommerce a.button:hover,
.woocommerce input.button:hover{background:var(--accent-deep);transform:translateY(-2px)}
.single_add_to_cart_button:disabled{opacity:.45;cursor:not-allowed;transform:none}
/* The accent rule above is (0,2,1) and .button--ghost is (0,1,0), so every
   outline button inside a WooCommerce wrapper came out solid red with no
   border. Restated for the variant at the same weight, and the padding with
   it: the ghost's 13px plus its 2px border is what makes it stand level with
   the solid button next to it. */
.woocommerce a.button--ghost,
.woocommerce button.button--ghost,
.woocommerce input.button--ghost{
  background:var(--paper);color:var(--ink);
  border:2px solid var(--line);padding:13px 24px;
}
.woocommerce a.button--ghost:hover,
.woocommerce button.button--ghost:hover,
.woocommerce input.button--ghost:hover{
  background:var(--paper);border-color:var(--ink);
}
/* WooCommerce's own sheet styles this button at .woocommerce button.button.alt
   - three classes and an element, which beats any single-class rule here - so
   the add-to-cart came out in WooCommerce's default lilac, not the brand red.
   Matched at the same weight to win it back.

   Disabled goes grey rather than a faded red. Until a variation is chosen the
   button genuinely cannot be pressed, and a washed-out version of the primary
   action reads as a broken button; grey reads as "not yet". */
.woocommerce button.button.alt,
.woocommerce input.button.alt,
.woocommerce a.button.alt{background:var(--accent);color:#fff}
.woocommerce button.button.alt:hover,
.woocommerce input.button.alt:hover,
.woocommerce a.button.alt:hover{background:var(--accent-deep)}
.woocommerce button.button.alt.disabled,
.woocommerce button.button.alt:disabled,
.woocommerce button.button.alt:disabled[disabled]{
  background:#e8e8e8;color:var(--ink-soft);opacity:1;
  box-shadow:inset 0 0 0 1px var(--line);
  cursor:not-allowed;transform:none;
}
.woocommerce button.button.alt.disabled:hover,
.woocommerce button.button.alt:disabled:hover{background:#e2e2e2;transform:none}
/* Quantity and Add to cart are one row: a fixed number field, a gap, and the
   button taking whatever is left. WooCommerce ships them as two floated
   boxes with a 4px margin between, which reads as one broken control. */
.woocommerce-variation-add-to-cart,
.variations_button{
  display:flex;align-items:stretch;flex-wrap:wrap;gap:12px;
  margin-top:18px;
}
.quantity{flex:0 0 92px;display:block;float:none;margin:0}
.quantity .qty{
  width:100%;height:100%;min-height:48px;
  font-family:var(--font-body);font-weight:600;font-size:16px;line-height:1.3;
  text-align:center;color:var(--ink);background:var(--paper);
  border:2px solid var(--line);border-radius:var(--r-chip);
  padding:11px 6px;
}
.quantity .qty:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* the screen-reader label WooCommerce prints above the field would otherwise
   push the box out of line with the button */
.quantity label{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
/* the button no longer carries its own top margin - the row owns the spacing -
   and it grows instead of filling the full width on its own line */
.single_add_to_cart_button{flex:1 1 190px;width:auto;margin-top:0}

/* Search, on 404 and on the results page. */
.findbar{display:flex;flex-wrap:wrap;gap:12px;max-width:640px;margin:0 auto 34px}
.findbar__field{flex:1 1 240px;min-width:0}
.findbar input[type="search"]{
  width:100%;min-height:48px;
  font-family:var(--font-body);font-weight:400;font-size:16px;line-height:1.3;
  color:var(--ink);background:var(--paper);
  border:2px solid var(--line);border-radius:var(--r-pill);
  padding:11px 20px;
}
.findbar input[type="search"]:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

/* The aisle list a 404 offers instead of a dead end. */
.cat-list{
  display:flex;flex-wrap:wrap;justify-content:center;gap:10px;
  list-style:none;margin:0 auto 34px;padding:0;max-width:900px;
}
.cat-list a{
  display:inline-flex;align-items:center;min-height:40px;
  font-family:var(--font-body);font-weight:600;font-size:var(--t-small);
  letter-spacing:.3px;color:var(--ink);
  background:var(--paper);border:1px solid var(--line);
  border-radius:var(--r-pill);padding:8px 18px;
}
.cat-list a:hover{border-color:var(--accent);color:var(--accent)}

/* A card title is a link once there is a product page to point it at. */
.pcard h3 a{color:inherit}
.pcard h3 a:hover{color:var(--accent)}

/* Pagination. Nothing paginates at 22 products; this exists so the archive
   does not fall back to unstyled numbers the day the range grows. */
.pagination,
.woocommerce-pagination{margin-top:44px}
.pagination .nav-links,
.woocommerce-pagination ul{
  display:flex;flex-wrap:wrap;justify-content:center;gap:8px;
  list-style:none;margin:0;padding:0;
}
.pagination .page-numbers,
.woocommerce-pagination .page-numbers{
  display:inline-flex;align-items:center;justify-content:center;
  min-width:44px;min-height:44px;padding:0 12px;
  font-family:var(--font-body);font-weight:600;font-size:var(--t-small);
  color:var(--ink);background:var(--paper);
  border:1px solid var(--line);border-radius:var(--r-pill);
}
.pagination .page-numbers.current,
.woocommerce-pagination .page-numbers.current{background:var(--accent);color:#fff;border-color:var(--accent)}
/* The shop pager is buttons, built by initFilters() in main.js. */
button.page-numbers{cursor:pointer;line-height:1}
button.page-numbers:not(.current):hover{border-color:var(--ink)}

/* WooCommerce's notice strip - "please choose an option", and whatever the
   cart says later. Styled here so it cannot arrive unstyled in front of a
   customer. */
.woocommerce-notices-wrapper .woocommerce-message,
.woocommerce-notices-wrapper .woocommerce-info,
.woocommerce-notices-wrapper .woocommerce-error{
  list-style:none;margin:0 0 22px;padding:14px 18px;
  font-family:var(--font-body);font-weight:400;font-size:var(--t-small);
  line-height:1.55;color:var(--ink);
  background:var(--paper);
  /* border-top:0 because woocommerce.css gives .woocommerce-info a 3px blue
     top border and .woocommerce-message a purple one - which showed up as a
     coloured line across the top AND our red line down the left. */
  border:0;border-left:4px solid var(--accent);
  border-radius:0 var(--r-chip) var(--r-chip) 0;
}

/* One column below 900: the gallery is square and the column beside it needs
   more than 300px before a 46-option select and two full-width buttons stop
   looking cramped. */
@media (max-width:899px){
  .product-main{grid-template-columns:1fr;gap:26px}
  .product-main__info h1{margin-top:2px}
}


/* --------------------------------------------------------------------------
   18. Cart - the drawer, the cart page, the checkout

   WooCommerce's own Cart and Checkout BLOCKS were swapped for the classic
   [woocommerce_cart] / [woocommerce_checkout] shortcodes. The blocks are a
   React app with its own several-hundred-rule stylesheet and its own type
   scale; the shortcodes are server-rendered tables and forms, which can wear
   this site's own type and spacing instead of fighting it.
   -------------------------------------------------------------------------- */

/* ---- the header badge ---- */
.tool__badge{position:absolute;inset:0;pointer-events:none}

/* ---- the drawer ---- */
.minicart{position:fixed;inset:0;z-index:120}
.minicart__scrim{
  position:absolute;inset:0;background:rgba(18,18,18,.5);
  opacity:0;transition:opacity .3s var(--ease-out);
}
.minicart__panel{
  position:absolute;top:0;right:0;bottom:0;
  width:min(420px,100%);
  display:flex;flex-direction:column;
  background:var(--paper);
  box-shadow:-18px 0 46px -22px rgba(0,0,0,.4);
  transform:translateX(100%);
  transition:transform .32s var(--ease-out);
}
.minicart.is-open .minicart__scrim{opacity:1}
.minicart.is-open .minicart__panel{transform:translateX(0)}
/* the page behind a modal must not scroll under it */
html.cart-open,html.cart-open body{overflow:hidden}

.minicart__head{
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:20px 22px;border-bottom:1px solid var(--line);flex:none;
}
.minicart__head h2{margin:0}
.minicart__close{
  flex:none;width:40px;height:40px;display:grid;place-items:center;
  font-size:20px;color:var(--ink-soft);border-radius:var(--r-pill);
  transition:color .2s var(--ease-out),background-color .2s var(--ease-out);
}
.minicart__close:hover{color:var(--ink);background:var(--page)}

/* The lines scroll; the head and the total do not. A cart of twenty lines
   must not push the Checkout button off the bottom of the screen. */
.minicart__inner{flex:1 1 auto;min-height:0;display:flex;flex-direction:column}
/* ul.minicart__list, not .minicart__list: the reset's ul[class] is an
   attribute selector at 0,1,1 and outranks a single class wherever it sits in
   the file, so the lines had no padding and the thumbnails sat against the
   panel's left edge while the header and footer were inset 22px. */
ul.minicart__list{
  flex:1 1 auto;min-height:0;overflow-y:auto;
  list-style:none;margin:0;padding:6px 22px;
}
.minicart__line{
  display:grid;grid-template-columns:66px 1fr 32px;gap:14px;align-items:start;
  padding:16px 0;border-bottom:1px solid var(--line);
}
.minicart__line:last-child{border-bottom:0}
.minicart__shot{margin:0;border-radius:var(--r-chip);overflow:hidden;background:var(--page)}
.minicart__shot img{display:block;width:100%;aspect-ratio:1/1;object-fit:cover}
.minicart__meta{min-width:0}
.minicart__meta h3{margin:0 0 4px}
.minicart__meta h3 a{color:inherit}
.minicart__meta h3 a:hover{color:var(--accent)}
.minicart__variant{
  font-family:var(--font-body);font-weight:400;font-size:var(--t-small);
  line-height:1.45;color:var(--ink-soft);margin:0 0 6px;
}
.minicart__sum{
  font-family:var(--font-display);font-weight:700;font-size:var(--t-small);
  color:var(--accent);margin:0;
}
.minicart__qty{color:var(--ink-soft);font-weight:600}
.minicart__remove{
  flex:none;width:32px;height:32px;display:grid;place-items:center;
  font-size:15px;color:var(--ink-soft);border-radius:var(--r-pill);
  transition:color .2s var(--ease-out),background-color .2s var(--ease-out);
}
.minicart__remove:hover{color:var(--accent);background:var(--blush)}
.minicart__remove.is-loading{opacity:.4;pointer-events:none}

.minicart__foot{flex:none;padding:18px 22px 22px;border-top:1px solid var(--line);background:var(--paper)}
.minicart__total{
  display:flex;align-items:baseline;justify-content:space-between;gap:12px;
  font-family:var(--font-display);font-weight:600;font-size:16px;color:var(--ink);
  margin:0;
}
.minicart__total strong{font-weight:700;font-size:var(--t-price);color:var(--accent)}
.minicart__note{margin:6px 0 0}
.minicart__actions{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:16px}
.minicart__actions .button{width:100%;padding-inline:14px}

.minicart__body--empty{
  flex:1 1 auto;display:grid;place-content:center;justify-items:center;gap:14px;
  padding:40px 22px;text-align:center;
}
.minicart__body--empty i{font-size:44px;color:var(--line)}
.minicart__body--empty .lede{max-width:24ch}

/* the add-to-cart button while the request is in flight */
.single_add_to_cart_button.is-loading{opacity:.6;pointer-events:none}

/* ---- cart and checkout pages: WooCommerce's classic markup ---- */
.woocommerce-cart-form,
.woocommerce-checkout{margin:0}

.shop_table{
  width:100%;border-collapse:collapse;
  font-family:var(--font-body);font-size:var(--t-small);color:var(--ink);
}
.shop_table th{
  text-align:left;padding:0 12px 12px 0;
  font-family:var(--font-body);font-weight:700;font-size:var(--t-eyebrow);
  line-height:1.3;letter-spacing:1.9px;text-transform:uppercase;color:var(--ink-soft);
  border-bottom:1px solid var(--line);
}
.shop_table td{padding:16px 12px 16px 0;border-bottom:1px solid var(--line);vertical-align:middle}
/* WooCommerce's sheet draws its row rules with a border-TOP on the cell
   while this file draws them with border-bottom, and its
   border-collapse:separate beats the collapse declared above, so the two
   never merged: every internal row boundary in every shop_table came out a
   2px line, 1px of --line under 1px of rgba(0,0,0,.2). The bottoms are the
   ones this design owns, so the tops go.

   Two selectors, because WooCommerce declares this twice at two weights:
   .woocommerce table.shop_table td is (0,2,2) and .woocommerce
   table.shop_table tbody th, ... tfoot td, ... tfoot th is (0,2,3). Writing
   only the (0,2,2) pair left every tfoot boundary doubled, and writing only
   the trio left the one under the table head. Equal weight is enough to win
   either, because style.css is registered as a dependant of
   woocommerce-general and so prints after it. */
.woocommerce table.shop_table td,
.woocommerce table.shop_table tbody th,
.woocommerce table.shop_table tfoot td,
.woocommerce table.shop_table tfoot th{border-top:0}
/* And a third declaration of the same thing, for the cart totals only:
   .woocommerce-cart .cart-collaterals .cart_totals tr th is (0,3,2), so it
   survived the two above and left the Total row with a line over it. */
.woocommerce-cart .cart-collaterals .cart_totals tr th,
.woocommerce-cart .cart-collaterals .cart_totals tr td{border-top:0}
.shop_table td:last-child,.shop_table th:last-child{padding-right:0;text-align:right}
.shop_table .product-thumbnail{width:90px;min-width:90px}
/* Matched at .woocommerce-cart table.cart td.product-thumbnail img, because
   WooCommerce's own .woocommerce-cart table.cart img is a class more specific
   than a plain .shop_table rule and pinned these to 32px - a thumbnail so
   small the toy in it could not be recognised. */
.shop_table .product-thumbnail img,
.woocommerce-cart table.cart td.product-thumbnail img,
.woocommerce table.shop_table td.product-thumbnail img{
  /* max-width:none because the global img{max-width:100%} in the reset caps
     this at the table cell's content width, which auto layout had settled at
     66px - a 74x74 square came out 66 wide and 74 tall. */
  display:block;width:74px;height:74px;max-width:none;aspect-ratio:1/1;object-fit:cover;
  border-radius:var(--r-chip);background:var(--page);
}
.shop_table .product-name a{color:var(--ink);font-weight:600}
.shop_table .product-name a:hover{color:var(--accent)}
.shop_table .variation{margin:4px 0 0;color:var(--ink-soft)}
.shop_table .variation dt,.shop_table .variation dd{display:inline;margin:0}
.shop_table .product-price,
.shop_table .product-subtotal{font-family:var(--font-display);font-weight:700}
.shop_table .product-remove{width:38px}
.shop_table a.remove{
  display:grid;place-items:center;width:30px;height:30px;
  border-radius:var(--r-pill);color:var(--ink-soft);font-size:18px;line-height:1;
  background:var(--page);
}
.shop_table a.remove:hover{color:#fff;background:var(--accent)}
.shop_table .actions{padding-top:18px;border-bottom:0;text-align:right}
.shop_table .actions .coupon{display:flex;gap:10px;flex-wrap:wrap;float:none;margin-bottom:14px}
.shop_table .actions .coupon input[type="text"]{flex:1 1 180px;min-width:0}
/* Update cart is a secondary action - it only matters after a quantity has
   been edited - so it wears the ghost button, not the primary red. And its
   disabled state is grey: WooCommerce's own disabled styling is its lilac at
   reduced opacity, which on a red button reads as a broken control rather
   than an inactive one. */
.shop_table .actions button[name="update_cart"],
.woocommerce .shop_table .actions button[name="update_cart"]{
  background:var(--paper);color:var(--ink);border:2px solid var(--line);
  padding:13px 24px;min-height:48px;
}
.shop_table .actions button[name="update_cart"]:hover{border-color:var(--ink);background:var(--paper);transform:translateY(-2px)}
.shop_table .actions button[name="update_cart"]:disabled,
.woocommerce .shop_table .actions button[name="update_cart"]:disabled{
  background:var(--page);color:var(--ink-soft);border-color:var(--line);
  opacity:1;cursor:not-allowed;transform:none;
}

/* The heading above the lines is printed by a hook in functions.php purely so
   this column and the totals column start at the same height. */
.vyson-cart-heading{margin:0 0 14px}
.cart_totals{margin-top:34px}
.cart_totals h2{margin:0 0 14px}
.cart_totals .shop_table th{
  border-bottom:1px solid var(--line);padding-bottom:14px;padding-top:14px;
  letter-spacing:1.2px;
}
.cart_totals .order-total td{font-family:var(--font-display);font-weight:700;font-size:var(--t-price);color:var(--accent)}
.wc-proceed-to-checkout{margin-top:20px}
.wc-proceed-to-checkout .checkout-button{width:100%}

/* WooCommerce's cart sheet styles this one at
   .woocommerce .wc-proceed-to-checkout a.checkout-button - (0,3,1), which
   beats the brand button rule - with font-size:1.25em and padding:1em. It
   printed an 18.75px label in a 56px box: the only .button on the site not
   at --t-btn, and the type scale is a hard rule. Matched at the same weight.

   display too: block loses min-height's vertical centring, so the label sat
   high in the 48px box. The rest of the brand rule (colour, radius, family)
   already wins, so only these four are restated. */
.woocommerce .wc-proceed-to-checkout a.checkout-button{
  display:flex;font-size:var(--t-btn);padding:15px 26px;min-height:48px;
}

/* The cart stacks: the lines take the full width, and the totals sit in a
   card under their right-hand end. See section 22 for the boxes. */
.woocommerce-cart .cart-collaterals{width:auto;float:none}

/* ---- checkout ---- */
.woocommerce-billing-fields h3,
.woocommerce-shipping-fields h3,
.woocommerce-additional-fields h3,
#order_review_heading{margin:0 0 16px}

/* "Ship to a different address?" is not a tick box that happens to sit in a
   heading - it IS this block's heading, and WooCommerce marks it up as one
   (h3#ship-to-different-address wrapping a label). So its words are the same
   H3 as "Billing details": same family, same 18px, same weight, same colour.
   `font:inherit` takes them from the h3 itself rather than restating them, so
   the two can never drift apart. The tick box in front is the only extra.

   The generic checkbox-label rule further down would otherwise drop it to
   13px body scale, which is what left this block with no heading at all. */
h3#ship-to-different-address{margin:34px 0 16px}
h3#ship-to-different-address label.woocommerce-form__label-for-checkbox{
  display:flex;align-items:center;gap:12px;
  font:inherit;letter-spacing:inherit;text-transform:none;color:inherit;
}
@media (max-width:759px){
  h3#ship-to-different-address{margin-top:28px}
}
.woocommerce-billing-fields__field-wrapper,
.woocommerce-shipping-fields__field-wrapper{display:grid;gap:14px}
@media (min-width:700px){
  .woocommerce-billing-fields__field-wrapper,
  .woocommerce-shipping-fields__field-wrapper{grid-template-columns:1fr 1fr}
  .form-row-wide,
  #billing_address_1_field,#billing_address_2_field,
  #shipping_address_1_field,#shipping_address_2_field{grid-column:1 / -1}
}
.form-row{margin:0;display:flex;flex-direction:column;gap:6px}
.form-row label{
  font-family:var(--font-body);font-weight:700;font-size:var(--t-eyebrow);
  line-height:1.3;letter-spacing:1.9px;text-transform:uppercase;color:var(--ink-soft);
}
.form-row .required{color:var(--accent);text-decoration:none}
.form-row input[type="text"],
.form-row input[type="tel"],
.form-row input[type="email"],
.form-row input[type="password"],
.form-row textarea,
.form-row .select2-selection,
.woocommerce-form__input-text,
input[type="text"].input-text,
.shop_table .actions .coupon input[type="text"]{
  width:100%;min-height:48px;
  font-family:var(--font-body);font-weight:400;font-size:16px;line-height:1.3;
  color:var(--ink);background:var(--paper);
  border:2px solid var(--line);border-radius:var(--r-chip);
  padding:11px 14px;
}
.form-row textarea{min-height:110px;resize:vertical}
.form-row input:focus-visible,
.form-row textarea:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.woocommerce-input-wrapper{display:block}
/* The tick boxes: "ship to a different address", "create an account",
   "remember me". Their words are a statement you are agreeing to, not the
   name of a field, so they stay in body case while .form-row label above
   turns real field names into eyebrows - and that rule is (0,1,1), so the
   bare class lost to it and never set display either, which left every box
   sitting on the text baseline instead of its middle. Restated at (0,2,1). */
.form-row label.woocommerce-form__label-for-checkbox,
.woocommerce-form__label-for-checkbox{
  display:flex;flex-direction:row;align-items:center;gap:10px;
  font-family:var(--font-body);font-weight:400;font-size:var(--t-small);
  line-height:1.4;letter-spacing:0;text-transform:none;color:var(--ink);
}
.woocommerce-form__label-for-checkbox input{width:18px;height:18px;margin:0;flex:none}

#order_review{margin-top:8px}
.woocommerce-checkout-review-order-table{width:100%;border-collapse:collapse}
.woocommerce-checkout-review-order-table th,
.woocommerce-checkout-review-order-table td{
  padding:12px 0;border-bottom:1px solid var(--line);
  font-family:var(--font-body);font-size:var(--t-small);
}
.woocommerce-checkout-review-order-table td:last-child,
.woocommerce-checkout-review-order-table th:last-child{text-align:right}
.woocommerce-checkout-review-order-table .order-total td{
  font-family:var(--font-display);font-weight:700;font-size:var(--t-price);color:var(--accent);
}
.woocommerce-shipping-totals ul#shipping_method{list-style:none;margin:0;padding:0;display:grid;gap:6px}
.woocommerce-shipping-totals ul#shipping_method label{
  display:inline-flex;align-items:center;gap:8px;
  font-family:var(--font-body);font-weight:400;font-size:var(--t-small);
  letter-spacing:0;text-transform:none;color:var(--ink);
}

#payment{margin-top:22px}
#payment ul.payment_methods{
  list-style:none;margin:0 0 18px;padding:0;display:grid;gap:10px;
}
#payment ul.payment_methods li{
  border:2px solid var(--line);border-radius:var(--r-card);padding:14px 16px;
}
#payment ul.payment_methods label{
  display:inline-flex;align-items:center;gap:10px;
  font-family:var(--font-display);font-weight:600;font-size:15px;
  letter-spacing:0;text-transform:none;color:var(--ink);
}
#payment .payment_box{
  margin:10px 0 0;padding:0;background:none;
  font-family:var(--font-body);font-size:var(--t-small);line-height:1.55;color:var(--ink-soft);
}
#payment .payment_box::before{display:none}
#payment .woocommerce-privacy-policy-text{
  font-size:var(--t-small);line-height:1.55;color:var(--ink-soft);
}
#place_order{width:100%}

/* Billing on the left, order review on the right. Below 1000 it stacks, with
   the review last - which is the right order to read it in. */
/* Same idea on the checkout: form.checkout is the grid WooCommerce already
   prints. #customer_details takes the left column, and the review heading and
   the review table stack in the right one - which is why the heading gets an
   explicit column rather than being left to flow. */
@media (min-width:1000px){
  .woocommerce-checkout form.checkout{
    display:grid;grid-template-columns:minmax(0,1.15fr) minmax(330px,.85fr);
    gap:8px clamp(30px,4vw,60px);align-items:start;
  }
  .woocommerce-checkout form.checkout > #customer_details{grid-column:1}
  .woocommerce-checkout form.checkout > .vyson-review{grid-column:2}
  /* the login / coupon prompts run across the top */
  .woocommerce-checkout .woocommerce-form-login-toggle,
  .woocommerce-checkout .woocommerce-form-coupon-toggle,
  .woocommerce-checkout .woocommerce-notices-wrapper{grid-column:1 / -1}
  .woocommerce-checkout .col2-set .col-1,
  .woocommerce-checkout .col2-set .col-2{width:auto;float:none}
  /* the summary follows the page down while the form is being filled in */
  .woocommerce-checkout form.checkout > .vyson-review{position:sticky;top:calc(var(--header-h) + 18px)}
}

/* WooCommerce's own notices, message strips and empty-cart page */
.woocommerce-message,
.woocommerce-info,
.woocommerce-error,
.woocommerce-notices-wrapper .woocommerce-message,
.woocommerce-notices-wrapper .woocommerce-info,
.woocommerce-notices-wrapper .woocommerce-error{
  list-style:none;margin:0 0 22px;padding:14px 18px;
  font-family:var(--font-body);font-weight:400;font-size:var(--t-small);
  line-height:1.55;color:var(--ink);
  background:var(--paper);border-left:4px solid var(--accent);
  border-radius:0 var(--r-chip) var(--r-chip) 0;
}
/* Restated at (0,2,0). woocommerce.css carries
   .woocommerce .woocommerce-info{background:#f6f5f8;border-top:3px solid #1e85be;
   padding:1em 2em 1em 3.5em} - the 3px blue line across the top showed up
   above our own red line down the left, and the 3.5em left padding is space
   for a ::before icon we do not print. */
.woocommerce .woocommerce-message,
.woocommerce .woocommerce-info,
.woocommerce .woocommerce-error,
.woocommerce-page .woocommerce-message,
.woocommerce-page .woocommerce-info,
.woocommerce-page .woocommerce-error{
  padding:14px 18px;
  background:var(--paper);color:var(--ink);
  border:0;border-left:4px solid var(--accent);
  border-radius:0 var(--r-chip) var(--r-chip) 0;
}
.woocommerce .woocommerce-message::before,
.woocommerce .woocommerce-info::before,
.woocommerce .woocommerce-error::before{display:none;content:none}

.woocommerce-message .button,
.woocommerce-info .button{margin-left:12px}
.cart-empty{font-family:var(--font-body);font-size:var(--t-lede);color:var(--ink-soft)}
.return-to-shop{margin-top:18px}

/* Brand red on the radios and checkboxes. Left alone they are the browser's
   own violet, which is the only non-palette colour anywhere on the page. */
input[type="radio"],
input[type="checkbox"]{accent-color:var(--accent)}

@media (max-width:759px){
  .minicart__actions{grid-template-columns:1fr}

  /* ---- the cart, stacked ----
     Four columns of text need about 385px of min-content, which does not fit
     a 375px phone, let alone inside the shell's gutter. Each line becomes a
     block instead, with the column headings coming back as labels from the
     data-title WooCommerce already writes on every cell. */
  .shop_table.cart,
  .shop_table.cart tbody,
  .shop_table.cart tr,
  .shop_table.cart td{display:block;width:auto}
  .shop_table.cart thead{display:none}
  .shop_table.cart tr.cart_item{
    position:relative;
    padding:18px 40px 18px 0;
    border-bottom:1px solid var(--line);
  }
  .shop_table.cart tr.cart_item td{
    display:flex;align-items:center;justify-content:space-between;gap:14px;
    padding:4px 0;border:0;text-align:left;
  }
  .shop_table.cart tr.cart_item td::before{
    content:attr(data-title);
    font-family:var(--font-body);font-weight:700;font-size:var(--t-eyebrow);
    line-height:1.3;letter-spacing:1.9px;text-transform:uppercase;color:var(--ink-soft);
  }
  /* the name needs no label - it is the heading of its own block */
  .shop_table.cart tr.cart_item td.product-name{
    display:block;padding-bottom:8px;
  }
  .shop_table.cart tr.cart_item td.product-name::before{display:none}
  .shop_table.cart tr.cart_item td.product-name a{font-size:15px}
  /* the thumbnail and the remove control leave the flow: the photo above the
     name, the cross pinned to the top right of the block */
  .shop_table.cart tr.cart_item td.product-thumbnail{display:block;padding:0 0 10px}
  .shop_table.cart tr.cart_item td.product-thumbnail::before{display:none}
  .shop_table.cart tr.cart_item td.product-remove{
    position:absolute;top:14px;right:0;padding:0;
  }
  .shop_table.cart tr.cart_item td.product-remove::before{display:none}
  .shop_table.cart tr.cart_item td .quantity{flex:0 0 92px}
  .shop_table.cart tr.actions{display:block;padding:18px 0 0;text-align:left}
  .shop_table.cart tr.actions td{padding:0}
  .shop_table.cart .actions button[name="update_cart"]{width:100%}

  /* ---- the checkout summary ----
     Two columns, so it fits - but the shipping cell's radios need room and
     the labels must not be squeezed to one word per line. */
  .woocommerce-checkout-review-order-table th{padding-right:10px}
  .woocommerce-shipping-totals ul#shipping_method label{align-items:flex-start}

  /* The shipment row's value is a sentence - "Standard delivery: RM 10.00" -
     and it shares its column with "RM 27.50", so the column is 113px and the
     sentence took three lines. Nothing can be taken from inside the row: the
     180px label column is set by the product name two rows above, not by the
     word "Shipment". Stacking this one row gives the method the table's full
     293px and one line. tfoot-scoped, so the product row keeps its columns. */
  /* The weight has to clear this file's own desktop rule for these cells,
     .woocommerce table.shop_table.woocommerce-checkout-review-order-table td
     at (0,3,2) - a tfoot-scoped selector of three classes and one element is
     (0,3,1) and silently lost its padding while keeping the display. Same
     prefix, so it wins by being later. */
  .woocommerce table.shop_table.woocommerce-checkout-review-order-table tfoot .woocommerce-shipping-totals th,
  .woocommerce table.shop_table.woocommerce-checkout-review-order-table tfoot .woocommerce-shipping-totals td{
    display:block;width:auto;text-align:left;padding-right:0;
  }
  /* One row, not two: the label sits tight above its own value. The row's
     bottom rule is deliberately absent - the free-delivery nudge below it
     carries it, see the :has(+ .ship-nudge) rule. */
  .woocommerce table.shop_table.woocommerce-checkout-review-order-table tfoot .woocommerce-shipping-totals th{
    border-bottom:0;padding:12px 0 4px;
  }
  .woocommerce table.shop_table.woocommerce-checkout-review-order-table tfoot .woocommerce-shipping-totals td{
    padding-top:0;
  }
}


/* WooCommerce prints the chosen variation's price inside the form. The page
   already has a headline price above the selects, so leaving this visible put
   the same figure on screen twice - identical, character for character, on a
   flat-priced product. main.js writes the value into the headline instead and
   this copy is hidden. Hidden, not removed: the markup is what the script
   reads, and WooCommerce owns that template.

   The availability line under it stays - "In stock" is not a duplicate. */
.woocommerce-variation-price{display:none}
.product-main__price .price{
  font-family:var(--font-display);font-weight:700;font-size:var(--t-price);
  line-height:1.1;color:var(--accent);
}
.product-main__price del{color:var(--ink-soft);font-weight:600;margin-right:6px}
.product-main__price ins{text-decoration:none}


/* WooCommerce stripes its tables - every second row a 2.5%-black fill, the
   footer rows a solid #f8f8f8. This site separates rows with a hairline and
   nothing else, and the filled bands read as a different component bolted on.
   Cleared on the two order tables only; the fill is not load-bearing. */
.woocommerce-checkout-review-order-table tbody tr td,
.woocommerce-checkout-review-order-table tbody tr th,
.woocommerce-checkout-review-order-table tfoot tr td,
.woocommerce-checkout-review-order-table tfoot tr th,
.woocommerce-checkout-review-order-table thead tr th,
.cart_totals .shop_table tbody tr td,
.cart_totals .shop_table tbody tr th,
.shop_table.cart tbody tr td{background:transparent}
/* the payment block ships on a lilac-grey panel with a speech-bubble arrow */
.woocommerce-checkout #payment{background:transparent;border-radius:0}
.woocommerce-checkout #payment div.form-row{padding:0}
.woocommerce-checkout #payment .payment_box::after{display:none}


/* The order summary's own heading, printed inside the wrapper. The one
   WooCommerce prints is hidden rather than removed - it is an anchor other
   code may look for. */
#order_review_heading{display:none}
.vyson-review__head{margin:0 0 16px}

/* the cart says where the delivery went - see woocommerce_after_cart_totals */
.cart-ship-note{
  margin:14px 0 0;
  font-family:var(--font-body);font-weight:400;font-size:var(--t-small);
  line-height:1.5;color:var(--ink-soft);
}


/* WooCommerce's own selectors are one class more specific than the section 18
   rules above, so these repeat them at matching weight rather than raising
   !important. Two things they were winning:

   the payment description sat on a lilac #dcd7e2 panel with a speech-bubble
   arrow, and the order table kept its 9px 12px cell padding - which inset the
   TOTAL from the right edge while the Place order button below it ran full
   width, so the two did not line up. */
.woocommerce-checkout #payment div.payment_box,
.woocommerce #payment div.payment_box{
  background:transparent;color:var(--ink-soft);
  margin:10px 0 0;padding:0;
}
.woocommerce-checkout #payment div.payment_box::before,
.woocommerce #payment div.payment_box::before,
.woocommerce-checkout #payment div.payment_box::after,
.woocommerce #payment div.payment_box::after{display:none;content:none}
.woocommerce #payment ul.payment_methods{border:0;padding:0}

.woocommerce table.shop_table.woocommerce-checkout-review-order-table th,
.woocommerce table.shop_table.woocommerce-checkout-review-order-table td{
  padding:12px 0;
}
.woocommerce table.shop_table.cart th,
.woocommerce table.shop_table.cart td{padding:16px 12px 16px 0}
/* Only the checkout's review table still runs flush to its own edge. The
   cart's table is a bordered panel now (section 22), so its last column keeps
   the panel's padding - zeroing it here put SUBTOTAL 1px off the border. */
.woocommerce table.shop_table.woocommerce-checkout-review-order-table th:last-child,
.woocommerce table.shop_table.woocommerce-checkout-review-order-table td:last-child{padding-right:0}
.woocommerce table.shop_table{border:0;border-radius:0}


/* The header row did not fit its own shell on a 375px phone. The brand (109),
   the two tools (90) and the Menu button plus two 16px gaps overran the 331px
   box, so the button hung past the shell - and past every piece of content
   below it, which is exactly the edge a reader lines the page up against.

   The button was 104px then because of its pill. Without the pill it is 81px
   and needs no slimming here, so only the gaps are tightened: the padding this
   rule used to force was what kept the hamburger 13px inside the gutter. The
   46px height is untouched, so the touch target stands. */
@media (max-width:479px){
  .header__inner{gap:10px}
  .menu-button{margin-left:0}
  .header__tools{gap:2px}
}


/* ---- order received ----
   The thank-you page renders through page-checkout.php, so it already has the
   full shell and the page head. These are the pieces WooCommerce prints below
   it: the receipt strip, the order table and the address block. They reuse
   .shop_table, which section 18 already dresses, so this is only the parts
   that are their own. */
.woocommerce-thankyou-order-received{
  font-family:var(--font-body);font-weight:var(--w-light);font-size:var(--t-lede);
  line-height:1.65;color:var(--ink-soft);margin:0 0 26px;max-width:56ch;
}
/* A grid, not flex-basis: five receipt facts with flex:1 1 150px settle at
   whatever the row leaves over and the last card ends up a different width
   from the rest. auto-fit/minmax gives every card the same width at every
   viewport and wraps to two rows on its own. */
ul.woocommerce-thankyou-order-details,
ul.order_details{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(196px,1fr));
  gap:12px;list-style:none;margin:0 0 34px;padding:0;
}
/* woocommerce.css puts ::before and ::after on this list with
   content:" ";display:table - a clearfix for the inline strip it used to be.
   In a grid those two pseudo-elements are GRID ITEMS: ::before took column 1
   and pushed all five cards one column right, ::after wrapped to a second
   row and kept column 1 occupied so auto-fit could not collapse the leftover
   sixth column. Net effect: the strip started a card-width in from the left
   and ended flush right, reading as though it were right-aligned. */
ul.order_details::before,
ul.order_details::after,
.woocommerce ul.order_details::before,
.woocommerce ul.order_details::after,
.woocommerce ul.woocommerce-thankyou-order-details::before,
.woocommerce ul.woocommerce-thankyou-order-details::after{display:none;content:none}
/* Restated at .woocommerce's own weight. woocommerce.css carries
   .woocommerce ul.order_details li{float:left;margin-right:2em;
   border-right:1px dashed;padding-right:2em;padding-left:0} at (0,2,1), which
   beat the (0,1,1) below it - so the label sat flush against the card's left
   edge, the value ran into its right edge, and each card had a dashed rule
   down its right side left over from WooCommerce's inline strip. */
ul.woocommerce-thankyou-order-details li,
ul.order_details li,
.woocommerce ul.woocommerce-thankyou-order-details li,
.woocommerce ul.order_details li{
  float:none;margin:0;
  background:var(--paper);border:1px solid var(--line);border-radius:var(--r-card);
  padding:15px 18px;
  font-family:var(--font-body);font-weight:700;font-size:var(--t-eyebrow);
  line-height:1.3;letter-spacing:1.9px;text-transform:uppercase;color:var(--ink-soft);
}
/* overflow-wrap:anywhere because an email address is one unbreakable word.
   At 196px minus the card's padding there are about 160px to put
   website@qcfixer.com in, and without this it simply ran out past the border. */
ul.woocommerce-thankyou-order-details li strong,
ul.order_details li strong,
.woocommerce ul.woocommerce-thankyou-order-details li strong,
.woocommerce ul.order_details li strong{
  display:block;margin-top:6px;
  font-family:var(--font-display);font-weight:700;font-size:16px;
  line-height:1.35;letter-spacing:0;text-transform:none;color:var(--ink);
  overflow-wrap:anywhere;
}
/* the labels are eyebrows - 1.9px of letter-spacing is what made
   "PAYMENT METHOD:" need two lines where the others needed one */
.woocommerce ul.order_details li{letter-spacing:1.2px}
.woocommerce-order-details__title,
.woocommerce-column__title{margin:0 0 14px}
.woocommerce-customer-details{margin-top:40px}
.woocommerce-customer-details address{
  font-style:normal;
  font-family:var(--font-body);font-weight:400;font-size:var(--t-small);
  line-height:1.7;color:var(--ink-soft);
  background:var(--paper);border:1px solid var(--line);border-radius:var(--r-card);
  padding:16px 18px;
}
@media (min-width:900px){
  .woocommerce-customer-details .woocommerce-columns--addresses{
    display:grid;grid-template-columns:1fr 1fr;gap:clamp(20px,3vw,40px);
  }
  .woocommerce-customer-details .col-1,
  .woocommerce-customer-details .col-2{width:auto;float:none}
}


/* An empty cart has no second column to fill. */
.vyson-cart-empty .shell > .woocommerce{display:block}
.vyson-cart-empty .cart-empty{margin-bottom:0}
.vyson-cart-empty .return-to-shop{margin-top:22px}


/* WooCommerce draws an icon before every notice with a glyph from its own
   icon font. It resolves to a missing-character box here, and the red rule
   down the left already says 'notice' - so the pseudo-element goes and the
   text starts where the padding puts it. */
.woocommerce-message::before,
.woocommerce-info::before,
.woocommerce-error::before,
.woocommerce-notices-wrapper .woocommerce-message::before,
.woocommerce-notices-wrapper .woocommerce-info::before,
.woocommerce-notices-wrapper .woocommerce-error::before{display:none;content:none}


/* WooCommerce prints its error notice as a <ul>, which the reset's ul[class]
   then strips the padding from - the same 0,1,1-beats-0,1,0 trap as the cart
   drawer's list. Restated at matching weight. */
ul.woocommerce-error,
ul.woocommerce-info,
ul.woocommerce-message{
  list-style:none;margin:0 0 22px;padding:14px 18px;
}


/* --------------------------------------------------------------------------
   19. What div.product switched on

   single-product.php wraps the product in div.product because WooCommerce's
   variation script finds the gallery through it. That also turns on every
   .woocommerce div.product ... rule in WooCommerce's stylesheet, which had
   never matched before. These are the ones that reached something.
   -------------------------------------------------------------------------- */

/* The add-to-cart button. WooCommerce's rule is .woocommerce div.product
   form.cart .button - four classes AND two elements - so it outranked the
   (0,4,1) rule above on background-color while losing on color. The result
   was the disabled state's grey text sitting on the enabled state's red:
   2.2:1 on the main control of the page. Restated here at matching weight so
   both properties come from the same place. */
.woocommerce div.product form.cart button.single_add_to_cart_button{
  background:var(--accent);color:#fff;
  padding:15px 26px;min-height:48px;float:none;
  border-radius:var(--r-pill);
}
.woocommerce div.product form.cart button.single_add_to_cart_button:hover{
  background:var(--accent-deep);
}
/* Until an option is picked the button cannot be pressed. Grey says that;
   a faded red says the control is broken. */
.woocommerce div.product form.cart button.single_add_to_cart_button.disabled,
.woocommerce div.product form.cart button.single_add_to_cart_button:disabled{
  /* Not var(--page): the page ground IS #f5f5f5, so a disabled button filled
     with it disappeared completely - grey words floating where a button
     should be. A slightly darker fill and a hairline keep the control
     visible while still reading as inactive. */
  background:#e8e8e8;color:var(--ink-soft);
  box-shadow:inset 0 0 0 1px var(--line);
  opacity:1;cursor:not-allowed;transform:none;
}
.woocommerce div.product form.cart button.single_add_to_cart_button.disabled:hover,
.woocommerce div.product form.cart button.single_add_to_cart_button:disabled:hover{
  background:var(--page);transform:none;
}

/* The price. main.js writes the chosen variation's price_html into the
   headline, and that markup is WooCommerce's own span.price - which
   div.product colours olive. */
.woocommerce div.product .product-main__price,
.woocommerce div.product .product-main__price span.price,
.woocommerce div.product .product-main__price .woocommerce-Price-amount{
  font-family:var(--font-display);font-weight:700;font-size:var(--t-price);
  line-height:1.1;color:var(--accent);
}
.woocommerce div.product .product-main__price small{
  font-family:var(--font-body);font-weight:600;font-size:var(--t-small);
  letter-spacing:.2px;color:var(--ink-soft);
}
.woocommerce div.product .product-main__price del{color:var(--ink-soft);font-weight:600;opacity:1}
.woocommerce div.product .product-main__price ins{text-decoration:none}

/* A 30px margin under the gallery, inside the media panel's own border, read
   as a strip of empty white below the photograph. */
.woocommerce div.product div.woocommerce-product-gallery{margin-bottom:0}
/* and a 16px right margin on a select that is already full width */
.woocommerce div.product form.cart .variations select{margin-right:0}
.woocommerce div.product form.cart{margin-bottom:0}


/* --------------------------------------------------------------------------
   20. Filter panel, toolbar and the two-handled price range

   A panel down the left, the grid to its right, and the sort in the strip
   above the grid. No "out of stock" row: nothing here tracks stock, so a
   Show/Hide pair would be a control that does nothing.
   -------------------------------------------------------------------------- */
/* The grid keeps the whole width and its own column counts. The panel is not
   a column - it comes over the top - so nothing here reserves space for it
   and nothing moves when it opens. */
.shop-layout{display:block}
.shop-main{min-width:0}

/* ---- the panel: an overlay at every width ---- */
.fpanel{
  position:fixed;top:0;left:0;bottom:0;z-index:130;
  width:min(340px,86%);
  background:var(--paper);padding:22px 24px;overflow-y:auto;
  box-shadow:18px 0 46px -22px rgba(0,0,0,.4);
  transform:translateX(-100%);
  transition:transform .32s var(--ease-out);
}
.shop-layout.is-open .fpanel{transform:translateX(0)}
.fpanel__scrim{
  position:fixed;inset:0;z-index:129;
  background:rgba(18,18,18,.5);
  opacity:0;pointer-events:none;transition:opacity .3s var(--ease-out);
}
.shop-layout.is-open .fpanel__scrim{opacity:1;pointer-events:auto}
html.filters-open,html.filters-open body{overflow:hidden}
.fpanel__head{
  display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding-bottom:14px;border-bottom:1px solid var(--line);
}
.fpanel__title{
  display:flex;align-items:center;gap:9px;margin:0;
  /* h2 size and weight come from section 3; only the icon is set here */
}
.fpanel__title i{font-size:20px;color:var(--ink-soft)}
.fpanel__collapse{
  flex:none;width:34px;height:34px;display:grid;place-items:center;
  font-size:15px;color:var(--ink-soft);border-radius:var(--r-pill);
  transition:color .2s var(--ease-out),background-color .2s var(--ease-out),transform .25s var(--ease-out);
}
.fpanel__collapse:hover{color:var(--ink);background:var(--page)}

/* ---- one collapsible block ---- */
.fblock{border-bottom:1px solid var(--line)}
.fblock__head{margin:0}
.fblock__head button{
  width:100%;display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:18px 0;text-align:left;
  font-family:var(--font-display);font-weight:700;font-size:var(--t-h3);
  line-height:1.3;letter-spacing:-.2px;color:var(--ink);
}
.fblock__head button i{
  font-size:13px;color:var(--ink-soft);
  transition:transform .25s var(--ease-out);
}
.fblock.is-shut .fblock__head button i{transform:rotate(180deg)}
.fblock__body{padding-bottom:22px}
.fblock.is-shut .fblock__body{display:none}

/* ---- price: two boxes ---- */
.fprice{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.fprice__field{
  display:flex;align-items:center;gap:5px;min-width:0;
  background:var(--paper);border:1px solid var(--line);border-radius:var(--r-chip);
  padding:11px 12px;
}
.fprice__field:focus-within{border-color:var(--ink)}
.fprice__unit{
  flex:none;
  font-family:var(--font-body);font-weight:400;font-size:14px;color:var(--ink-soft);
}
.fprice__field input{
  width:100%;min-width:0;border:0;background:none;padding:0;
  font-family:var(--font-body);font-weight:600;font-size:15px;line-height:1.2;
  color:var(--ink);
}
.fprice__field input:focus-visible{outline:none}
/* the spinners crowd a two-digit box */
.fprice__field input::-webkit-outer-spin-button,
.fprice__field input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
.fprice__field input[type="number"]{-moz-appearance:textfield;appearance:textfield}

/* ---- price: the two-handled slider ----
   A native range input has ONE thumb, and a two-handled control does not
   exist in HTML - so two inputs are stacked over a drawn track. Each input
   is transparent and ignores the pointer; only its thumb takes it, which is
   what lets the lower handle be grabbed where the two overlap. */
.frange{position:relative;height:34px;margin-top:18px}
.frange__track{
  position:absolute;left:0;right:0;top:50%;height:3px;transform:translateY(-50%);
  background:var(--line);border-radius:2px;
}
.frange__fill{position:absolute;top:0;bottom:0;background:var(--ink);border-radius:2px}
.frange__input{
  position:absolute;left:0;top:0;width:100%;height:34px;margin:0;
  -webkit-appearance:none;appearance:none;background:none;pointer-events:none;
}
.frange__input:focus-visible{outline:none}
.frange__input::-webkit-slider-thumb{
  -webkit-appearance:none;appearance:none;pointer-events:auto;
  width:20px;height:20px;border-radius:50%;
  background:var(--paper);border:2px solid var(--ink);
  cursor:grab;box-shadow:0 1px 3px rgba(0,0,0,.18);
}
.frange__input::-moz-range-thumb{
  pointer-events:auto;
  width:16px;height:16px;border-radius:50%;
  background:var(--paper);border:2px solid var(--ink);
  cursor:grab;
}
.frange__input:focus-visible::-webkit-slider-thumb{outline:2px solid var(--accent);outline-offset:2px}
.frange__input:focus-visible::-moz-range-thumb{outline:2px solid var(--accent);outline-offset:2px}

/* ---- category checkboxes ---- */
ul.fcheck{list-style:none;margin:0;padding:0;display:grid;gap:2px}
.fcheck label{
  display:flex;align-items:center;gap:11px;padding:7px 0;cursor:pointer;
  font-family:var(--font-body);font-weight:400;font-size:15px;line-height:1.4;
  color:var(--ink);
}
.fcheck label:hover{color:var(--accent)}
.fcheck input{
  flex:none;width:19px;height:19px;margin:0;
  accent-color:var(--accent);
}

.fpanel__clear{
  margin-top:18px;
  font-family:var(--font-body);font-weight:600;font-size:var(--t-small);
  color:var(--ink-soft);text-decoration:underline;text-underline-offset:3px;
}
.fpanel__clear:hover{color:var(--accent)}

/* ---- the strip above the grid ---- */
.ftoolbar{
  display:flex;align-items:center;gap:16px;
  padding-bottom:16px;margin-bottom:24px;
  border-bottom:1px solid var(--line);
}
/* Small caps in a 1px box - the same treatment as the eyebrows and the
   column headings, so it reads as a control rather than as a second CTA
   competing with the red Select options buttons in the grid below it. */
.ftoolbar__open{
  display:inline-flex;align-items:center;gap:8px;flex:none;
  font-family:var(--font-body);font-weight:700;font-size:var(--t-eyebrow);
  line-height:1.3;letter-spacing:1.9px;text-transform:uppercase;color:var(--ink);
  background:var(--paper);
  border:1px solid var(--line);border-radius:var(--r-chip);
  padding:11px 18px;min-height:42px;
  transition:border-color .2s var(--ease-out),color .2s var(--ease-out);
}
.ftoolbar__open:hover{border-color:var(--ink)}
.ftoolbar__open i{font-size:15px;letter-spacing:0}
.ftoolbar__count{
  margin:0;
  font-family:var(--font-body);font-weight:700;font-size:var(--t-eyebrow);
  line-height:1.3;letter-spacing:1.9px;text-transform:uppercase;color:var(--ink-soft);
}
.ftoolbar__sort{display:flex;align-items:center;gap:10px;margin-left:auto;min-width:0}
.ftoolbar__label{
  flex:none;
  font-family:var(--font-body);font-weight:700;font-size:var(--t-eyebrow);
  line-height:1.3;letter-spacing:1.9px;text-transform:uppercase;color:var(--ink-soft);
}
.ftoolbar__sort select{
  min-height:44px;min-width:190px;
  font-family:var(--font-body);font-weight:400;font-size:15px;line-height:1.3;
  color:var(--ink);background:var(--paper);
  border:1px solid var(--line);border-radius:var(--r-chip);
  padding:10px 14px;
}
.ftoolbar__sort select:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

.fempty{
  font-family:var(--font-body);font-weight:var(--w-light);font-size:var(--t-lede);
  line-height:1.65;color:var(--ink-soft);max-width:46ch;margin:0;
}

/* nothing in a panel needs a bottom hairline on its last block */
.fpanel .fblock:last-of-type{border-bottom:0}

@media (max-width:999px){
  .ftoolbar{flex-wrap:wrap}
  /* With the Filter button present the phone row is button + sort, and the
     count drops to its own line under them. Without it - the eight category
     pages - the count is the only thing left of the sort, so it stays on the
     first row instead of leaving a lone right-aligned select. */
  .ftoolbar__count{order:3;flex:1 1 100%}
  .ftoolbar--sort-only .ftoolbar__count{order:0;flex:0 1 auto}
  .ftoolbar__sort{gap:8px}
  .ftoolbar__label{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
  .ftoolbar__sort select{min-width:0}
}
@media (max-width:479px){
  .ftoolbar__sort select{min-width:150px}
}


/* --------------------------------------------------------------------------
   21. The transactional pages: cart, checkout, sign-in

   Measured on the cart at 1120px, the bottom of the h1 sat 161px above the
   first line of content: .page-head's 100px of padding-bottom plus the 44px
   margin under .section-head. That margin is there so a heading can sit
   above a grid in the SAME section - here the heading is alone in its
   section and the content is in the next one, so the two gaps are being paid
   twice for one job.

   The catalogue pages keep the wide rhythm; a banner and a full --section of
   air before a wall of toys is the site's opening move. A cart is not an
   opening move, it is a task, and a task should start near its own title.
   -------------------------------------------------------------------------- */
.section.page-head--tight{padding-bottom:34px}
.page-head--tight .section-head{margin-bottom:0}
@media (max-width:759px){
  .section.page-head--tight{padding-bottom:24px}
}

/* ---- sign in ----
   A narrow centred card. WooCommerce prints the form full width with its
   heading above it, which under a centred title read as though the form
   belonged to something else off to the left. */
.account-gate{max-width:430px;margin-inline:auto}
.account-gate .woocommerce{width:auto}
.account-gate h2{text-align:center;margin:0 0 20px}

/* One form per page. WooCommerce prints both halves of form-login.php whenever
   registration is on; the sign-in page shows the login half and /register/
   shows the other, so each page is one card doing one job. Hidden rather than
   templated away, so WooCommerce keeps owning the markup, the nonce and
   anything hooked onto woocommerce_register_form. */
.account-gate .u-column2{display:none}
.account-gate--register .u-column1{display:none}
.account-gate--register .u-column2{display:block}
/* the page's own h1 already says Register */
.account-gate--register h2{display:none}

.account-gate form.woocommerce-form-login,
.account-gate form.woocommerce-form-register.register,
.account-gate form.woocommerce-ResetPassword{
  margin:0;padding:26px 26px 24px;
  background:var(--paper);
  border:1px solid var(--line);border-radius:var(--r-card);
  display:grid;gap:16px;align-content:start;
}
/* WooCommerce sets .woocommerce form .form-row{padding:3px} at (0,2,1) and
   .woocommerce .woocommerce-form-login .woocommerce-form-login__submit
   {margin-right:1em} at (0,3,0). The resets here were a class short of both,
   so the fields sat 6px narrower than their own card and the Log in button
   carried 14px on its right. Matched at their weights; ours is later. */
.account-gate form .form-row{padding:0;margin:0}
.account-gate .woocommerce-form-login .woocommerce-form-login__submit,
.account-gate form.register button[name="register"]{width:100%;margin:0}

/* A custom element with nothing but hidden inputs in it. As a grid item it
   still takes a row and a gap either side, which opened a 32px hole between
   the password field and the privacy line. */
.account-gate wc-order-attribution-inputs{display:none}

.account-gate .woocommerce-privacy-policy-text{margin:0}
.account-gate .woocommerce-privacy-policy-text p{
  margin:0;
  font-family:var(--font-body);font-weight:400;
  font-size:var(--t-small);line-height:1.55;color:var(--ink-soft);
}
/* The label already carries .woocommerce-form__label-for-checkbox, so the
   flex rule up in the checkout block should have laid it out - except
   WooCommerce pins this one label with
   .woocommerce .woocommerce-form-login .woocommerce-form-login__rememberme
   {display:inline-block} at (0,3,0), which outranks it. Inline-block put the
   box on the text's baseline instead of its middle. Restated at the same
   weight, later in the cascade. */
.account-gate .woocommerce-form-login .woocommerce-form-login__rememberme{
  display:flex;margin:0;line-height:1.4;
}
.account-gate .woocommerce-form-login__rememberme input{margin:0}
.account-gate .lost_password{
  margin:0;text-align:center;
  font-family:var(--font-body);font-weight:400;font-size:var(--t-small);
}

/* The way across to the other page, at the foot of the card. A rule above it
   because it is a different proposition from the form it sits under, not the
   last field of it. */
.account-gate .acct-switch{
  display:grid;gap:12px;justify-items:center;
  margin:0;padding-top:18px;border-top:1px solid var(--line);
}
.account-gate .acct-switch p{
  margin:0;text-align:center;
  font-family:var(--font-body);font-weight:400;
  font-size:var(--t-small);color:var(--ink-soft);
}
.account-gate .acct-switch .button{width:100%}

/* ---- lost password, and the reset step behind the emailed link ---- */
/* A float clearer with nothing to clear. As a grid item it is a row of its own
   plus a gap either side - 32px of nothing between the field and the button. */
.account-gate form .clear{display:none}
/* the instruction above the field is copy, not a control */
.account-gate form.woocommerce-ResetPassword > p:first-child{
  margin:0;
  font-family:var(--font-body);font-weight:400;
  font-size:var(--t-small);line-height:1.6;color:var(--ink-soft);
}
.account-gate form.woocommerce-ResetPassword button{width:100%;margin:0}
.account-gate .lost_password a{color:var(--ink-soft);text-decoration:underline;text-underline-offset:3px}
.account-gate .lost_password a:hover{color:var(--accent)}
/* the notices belong above the card, at the card's width */
.account-gate .woocommerce-notices-wrapper{margin-bottom:18px}

/* ---- signed in: the dashboard ----
   Built to the arrangement the client pointed at - a panel on the left, a
   two-column grid of outlined icon tiles on the right. The tiles ARE the
   navigation, so WooCommerce's own account sidebar is removed in
   functions.php rather than styled; keeping both would have put every
   destination on the page twice in two different visual languages.

   Taken from the reference: the split, the tile grid, the icon beside the
   label, the outline. Not taken: its 25px corner and 2.4px stroke, which on
   this site would be the only shape like it - the tiles use --r-card and the
   same 2px the ghost button and the payment list already use.

   This page has no head, so the first section restores the page-head's own
   top rhythm rather than the full --section a bare .section would take. */
.section--top{padding-top:calc(var(--section) * 0.72)}

.account-dash{display:grid;gap:clamp(20px,3vw,34px)}
@media (min-width:900px){
  /* No align-items:start - the panel stretches to the row so its bottom edge
     lands on the tile grid's. And rather than leave the gained height as dead
     space under the last line, the panel becomes a flex column and the last
     order is pushed to the foot of it: who you are at the top, where your
     order got to at the bottom, the hairline finding its own place between. */
  .account-dash{grid-template-columns:minmax(0,.85fr) minmax(0,1.15fr)}
}

.acct-panel{
  background:var(--paper);
  border:1px solid var(--line);border-radius:var(--r-card);
  padding:clamp(22px,3vw,28px);
}
.acct-panel h2{margin:6px 0 6px}
.acct-panel__mail{
  margin:0;word-break:break-word;
  font-family:var(--font-body);font-size:var(--t-small);color:var(--ink-soft);
}
.acct-panel__last{margin-top:24px;padding-top:20px;border-top:1px solid var(--line)}
.acct-panel__order{
  margin:6px 0 0;
  font-family:var(--font-display);font-weight:700;font-size:var(--t-price);
}
.acct-panel__order a{color:var(--ink)}
.acct-panel__order a:hover{color:var(--accent)}
.acct-panel__order span{
  display:inline-block;margin-left:8px;padding:4px 11px;vertical-align:middle;
  background:var(--page);border-radius:var(--r-pill);
  font-family:var(--font-body);font-weight:700;font-size:var(--t-eyebrow);
  letter-spacing:1.2px;text-transform:uppercase;color:var(--ink-soft);
}
.acct-panel__date,
.acct-panel__none{
  margin:8px 0 0;
  font-family:var(--font-body);font-size:var(--t-small);color:var(--ink-soft);
}
.acct-panel__none{margin:0 0 16px}

/* After the base rules on purpose. These are the same specificity as
   .acct-panel__last above, so putting them in the media query further up let
   margin-top:24px win on source order - the panel stretched but its content
   stayed at the top with the gained height sitting dead at the bottom. */
@media (min-width:900px){
  .acct-panel{display:flex;flex-direction:column}
  .acct-panel__last{margin-top:auto;padding-top:24px}
}

ul.acct-tiles{list-style:none;margin:0;padding:0;display:grid;gap:14px}
@media (min-width:560px){
  ul.acct-tiles{grid-template-columns:1fr 1fr}
}
.acct-tile{
  display:flex;align-items:center;gap:16px;min-height:92px;height:100%;
  padding:20px 22px;
  background:var(--paper);color:var(--ink);
  border:2px solid var(--line);border-radius:var(--r-card);
  transition:border-color .2s var(--ease-out),transform .2s var(--ease-out);
}
.acct-tile i{font-size:26px;line-height:1;color:var(--accent);flex:none}
.acct-tile span{font-family:var(--font-display);font-weight:600;font-size:17px;line-height:1.25}
.acct-tile:hover{border-color:var(--ink);color:var(--ink);transform:translateY(-2px)}
.acct-tile:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

/* ---- signed in: one endpoint ----
   Orders, addresses, account details, a single order. Its own name is the
   heading - WooCommerce already rewrites the_title() for these - and the
   content sits in the same panel the cart and the checkout use. */
.acct-back{margin:14px 0 0;font-family:var(--font-body);font-size:var(--t-small)}
.acct-back a{display:inline-flex;align-items:center;gap:6px;color:var(--ink-soft)}
.acct-back a:hover{color:var(--accent)}
.acct-back i{font-size:13px}

/* The panel takes the shell. Capping the panel instead made an orders table
   stop halfway across the page, which read as truncated rather than
   deliberate. What actually needed capping was the forms: uncapped, the
   first/last name pair came out 585px each, where the checkout asks for the
   same two fields at about 410. So the panel is full width and the form
   inside it is 836px - two 411px columns and the 14px gap, the same field
   width as the checkout - sitting at the panel's left edge. */
.account-panelled form.edit-account,
.account-panelled .woocommerce-address-fields{max-width:836px}
.account-panelled .woocommerce-MyAccount-content{
  width:auto;float:none;
  background:var(--paper);
  border:1px solid var(--line);border-radius:var(--r-card);
  padding:clamp(20px,3vw,30px);
}
.account-panelled .woocommerce-MyAccount-content > :first-child{margin-top:0}
.account-panelled .woocommerce-MyAccount-content > :last-child{margin-bottom:0}

/* Account details and Addresses are forms of the same kind as the checkout's,
   and WooCommerce marks their fields up with the same form-row-first /
   form-row-last pair - but without the __field-wrapper the checkout gets, so
   nothing was laying them out and every field ran full width. Same two
   columns, same 14px, same breakpoint as the checkout. */
.account-panelled form.edit-account,
.account-panelled .woocommerce-address-fields__field-wrapper{display:grid;gap:14px}
@media (min-width:700px){
  .account-panelled form.edit-account{grid-template-columns:1fr 1fr}
  .account-panelled form.edit-account > fieldset,
  .account-panelled form.edit-account > .clear,
  .account-panelled form.edit-account > p:not(.form-row-first):not(.form-row-last){grid-column:1 / -1}
  .account-panelled .woocommerce-address-fields__field-wrapper{grid-template-columns:1fr 1fr}
  .account-panelled .woocommerce-address-fields__field-wrapper > .form-row-wide,
  .account-panelled .woocommerce-address-fields__field-wrapper > .address-field{grid-column:1 / -1}
  .account-panelled .woocommerce-address-fields__field-wrapper > .form-row-first,
  .account-panelled .woocommerce-address-fields__field-wrapper > .form-row-last{grid-column:auto}
}
.account-panelled form.edit-account fieldset{
  margin:10px 0 0;padding:clamp(18px,2.4vw,24px);
  border:1px solid var(--line);border-radius:var(--r-card);
  display:grid;gap:14px;
}
.account-panelled form.edit-account legend{
  padding:0;margin-bottom:2px;
  font-family:var(--font-display);font-weight:700;font-size:var(--t-h3);
  line-height:1.3;letter-spacing:-.2px;color:var(--ink);
}
.account-panelled form.edit-account em{
  font-style:normal;font-family:var(--font-body);font-size:var(--t-small);color:var(--ink-soft);
}
/* the two saved addresses side by side, each in its own card */
@media (min-width:760px){
  .account-panelled .woocommerce-Addresses{display:grid;grid-template-columns:1fr 1fr;gap:18px}
}
.account-panelled .woocommerce-Address{
  padding:20px;background:var(--page);border-radius:var(--r-card);
}
.account-panelled .woocommerce-Address .woocommerce-Address-title{
  display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-bottom:10px;
}
.account-panelled .woocommerce-Address address{
  margin:0;font-style:normal;
  font-family:var(--font-body);font-size:var(--t-small);line-height:1.6;color:var(--ink-soft);
}
.account-panelled .woocommerce-Address .edit{
  font-family:var(--font-body);font-weight:700;font-size:var(--t-eyebrow);
  letter-spacing:1.2px;text-transform:uppercase;color:var(--accent);
}


/* --------------------------------------------------------------------------
   22. The cart page as boxes

   Built to the arrangement the client sent: the lines in one bordered panel
   with a filled header band across the top, then the totals in a second,
   narrower panel tucked under its right-hand end - not the two columns this
   page had before.

   Taken from the reference: the panels, the header bands, the hairline
   between every row, the stacked order, the full-width action at the foot of
   the totals. NOT taken from it: the type. Its "Cart totals" is roughly
   48px and its labels are sentence case; ours stays the site's own h2 and
   the same uppercase eyebrow the checkout's field labels use, so the cart
   still reads as part of this site.
   -------------------------------------------------------------------------- */
.woocommerce-cart .woocommerce-cart-form{margin:0 0 34px}

/* border-collapse:separate, not collapse: a collapsed table cannot be
   clipped by border-radius, so the corners of the header band spill past the
   panel's own rounded corners. Every cell states its own hairline instead. */
.woocommerce-cart table.shop_table.cart{
  border:1px solid var(--line);border-radius:var(--r-card);
  border-collapse:separate;border-spacing:0;overflow:hidden;
  background:var(--paper);
}
/* Only while the table is still a table. Below 760 the rows stack into
   blocks (section 18), where a per-cell 18px inset and a hairline under every
   line would fight that layout - the panel's inset moves onto the row. */
@media (min-width:760px){
  .woocommerce-cart table.shop_table.cart thead th,
  .woocommerce table.shop_table.cart thead th{
    background:var(--page);
    padding:15px 18px;border-bottom:1px solid var(--line);
  }
  .woocommerce-cart table.shop_table.cart tbody td,
  .woocommerce table.shop_table.cart tbody td{
    padding:16px 18px;border-bottom:1px solid var(--line);
  }
  .woocommerce-cart table.shop_table.cart tbody tr:last-child td{border-bottom:0}
  .woocommerce-cart table.shop_table.cart td.actions,
  .woocommerce table.shop_table.cart td.actions{
    padding:16px 18px;text-align:right;background:var(--paper);
  }
}
@media (max-width:759px){
  .woocommerce-cart table.shop_table.cart tr.cart_item{padding:18px 18px}
  .woocommerce-cart table.shop_table.cart tr.cart_item td.product-remove{right:14px}
  .woocommerce-cart table.shop_table.cart tr.actions,
  .woocommerce table.shop_table.cart tr.actions{padding:16px 18px}
  .woocommerce-cart table.shop_table.cart tbody tr:last-child{border-bottom:0}
}
/* the first cell of a row carries the panel's own left padding */
.woocommerce-cart table.shop_table.cart td.product-remove{width:56px}
.woocommerce-cart table.shop_table.cart th:last-child,
.woocommerce-cart table.shop_table.cart td:last-child{text-align:right}

/* ---- the totals panel ---- */
.woocommerce-cart .cart-collaterals{display:flex;justify-content:flex-end}
.woocommerce-cart .cart_totals{
  width:100%;max-width:620px;margin:0;
  background:var(--paper);
  border:1px solid var(--line);border-radius:var(--r-card);overflow:hidden;
}
.woocommerce-cart .cart_totals h2{
  margin:0;padding:20px 24px;
  background:var(--page);border-bottom:1px solid var(--line);
}
.woocommerce-cart .cart_totals table.shop_table{
  border-collapse:separate;border-spacing:0;margin:0;
}
.woocommerce-cart .cart_totals table.shop_table th,
.woocommerce-cart .cart_totals table.shop_table td{
  padding:15px 24px;border-bottom:1px solid var(--line);
}
.woocommerce-cart .cart_totals table.shop_table th{padding-top:15px}
.woocommerce-cart .cart_totals table.shop_table td{text-align:right}
.woocommerce-cart .wc-proceed-to-checkout{margin:0;padding:20px 24px 22px}

@media (max-width:759px){
  .woocommerce-cart .cart_totals{max-width:none}
  .woocommerce-cart .cart_totals h2{padding:16px 18px}
  .woocommerce-cart .cart_totals table.shop_table th,
  .woocommerce-cart .cart_totals table.shop_table td{padding:14px 18px}
  .woocommerce-cart .wc-proceed-to-checkout{padding:16px 18px 18px}
}

/* "Add RM 12.36 more for free delivery." under the delivery line on
   checkout - see vyson_free_shipping_shortfall() in functions.php.

   It is a second line of the delivery row, not a row of its own, so the
   hairline moves down past it: the divider belongs between delivery and the
   total, not between the fee and the sentence explaining it. */
.ship-nudge td{
  padding-top:0!important;
  font-family:var(--font-body);font-weight:400;font-size:var(--t-small);
  line-height:1.5;color:var(--ink-soft);text-align:left!important;
}
.ship-nudge strong{color:var(--accent);font-weight:700}
.woocommerce-shipping-totals:has(+ .ship-nudge) th,
.woocommerce-shipping-totals:has(+ .ship-nudge) td{border-bottom:0}
/* woocommerce.css puts a border-TOP on every cell as well, and the table is
   border-collapse:collapse - so killing the delivery row's bottom edge still
   left the nudge drawing its own top edge, which is the line that was showing.
   .woocommerce table.shop_table td is (0,2,2), so this has to be (0,2,3). */
.woocommerce table.shop_table tr.ship-nudge td,
.woocommerce-checkout table.shop_table tr.ship-nudge td{border-top:0}


/* --------------------------------------------------------------------------
   23. The checkout's order panel

   Built to the second reference the client sent: the whole right-hand side -
   heading, lines, totals, payment, action - inside one bordered panel, the
   payment description on a grey shelf with a notch pointing up at the method
   it belongs to, and the action full width across the foot.

   .vyson-review already wraps exactly those things: it exists because the
   heading and the summary ship as two siblings, and in a two-column grid that
   made the billing column span two rows and share its height out between
   them. That wrapper is now also the panel.

   Not taken from the reference: its black action and its Points rows. The
   action stays this site's red, and there is no loyalty scheme here.
   -------------------------------------------------------------------------- */
.vyson-review{
  background:var(--paper);
  border:1px solid var(--line);border-radius:var(--r-card);
  padding:24px;
}
/* The cells already run edge to edge inside their own row (padding:12px 0),
   so TOTAL lands on the panel's inner edge - and the button below it, being
   full width of the same box, lines up with it exactly. */
.vyson-review__head{margin:0 0 6px}
.woocommerce-checkout #payment{margin-top:20px}

/* The method's own name is not a card - the reference leaves it as plain text
   with its description on a shelf beneath. Ours was a 2px bordered box, which
   read as a thing to choose at a point where there is only one. */
.woocommerce-checkout #payment ul.payment_methods li,
.woocommerce #payment ul.payment_methods li{
  border:0;border-radius:0;padding:0;
}
.woocommerce-checkout #payment div.payment_box,
.woocommerce #payment div.payment_box{
  position:relative;
  margin:12px 0 0;padding:14px 16px;
  background:var(--page);border-radius:var(--r-chip);
  color:var(--ink-soft);
}
/* No notch. WooCommerce draws ::before as a CSS triangle pointing from the
   description shelf up at the method name; with one payment method it points
   at the only thing on the screen, so both pseudo-elements go off. content:none
   as well as display:none, because WooCommerce sets content on ::before and a
   display alone leaves the box in the tree. */
.woocommerce-checkout #payment div.payment_box::before,
.woocommerce #payment div.payment_box::before,
.woocommerce-checkout #payment div.payment_box::after,
.woocommerce #payment div.payment_box::after{display:none;content:none}
.woocommerce-checkout #payment div.payment_box p:last-child{margin-bottom:0}
/* The method's own radio. checkout.js hides it with an inline display:none
   whenever only one method is offered, and sets that again after every
   order-review refresh - hence !important. Shown, it tells the shopper the
   method is already chosen. */
.woocommerce-checkout #payment ul.payment_methods li > input.input-radio{
  display:inline-block !important;vertical-align:middle;
  width:19px;height:19px;margin:0 10px 0 0;
  accent-color:var(--accent);
}
/* HitPay prints its payment_box even with no description: all it holds then
   is the drop-in layer, unused while drop-in is off, so the shelf showed as an
   empty grey bar. A description is printed as a <p>, which brings it back. */
.woocommerce-checkout #payment div.payment_box.payment_method_hitpay:not(:has(p)){display:none !important}

@media (max-width:759px){
  .vyson-review{padding:18px}
}


/* --------------------------------------------------------------------------
   24. An order, on two pages

   The same order appears twice: once on the thank-you page straight after
   checkout, and once inside the account as a record. WooCommerce prints
   nearly the same markup both times, but the surroundings are different - on
   the thank-you page the order IS the page, and in the account it is already
   inside .account-panelled. So the panel is added on one and deliberately
   not on the other; a bordered table inside a bordered panel is a box in a
   box, and the second frame carries no information.
   -------------------------------------------------------------------------- */

/* WooCommerce wraps the order number, date and status in <mark>, which the
   browser paints highlighter yellow - three yellow blocks in one sentence.
   The number and date are simply the emphasis they were meant to be; the
   status becomes the same chip the account panel already uses for it. */
.woocommerce mark,
.woocommerce-order mark,
.woocommerce-MyAccount-content mark{
  background:transparent;color:var(--ink);font-weight:700;
}
/* The status was a chip, which read well on its own but left the sentence's
   full stop floating a chip's padding away from the last word - "currently
   PROCESSING ." So it is emphasis inside the sentence instead: the accent
   colour carries the scannability the chip was there for. */
.woocommerce mark.order-status,
.woocommerce-MyAccount-content mark.order-status{
  background:transparent;color:var(--accent);
  font-weight:700;text-transform:uppercase;letter-spacing:.4px;
}

/* The gateway's own instruction on the thank-you page - for cash on delivery
   that is the one thing the customer still has to do, and it was arriving as
   a bare paragraph pressed against the ORDER DETAILS heading below it. */
/* --paper, not --page: this page's ground IS --page, so a --page shelf is an
   invisible one. Same trap as the disabled Add to cart button. */
.woocommerce-order > p:not([class]){
  margin:0 0 40px;padding:16px 18px;
  background:var(--paper);border:1px solid var(--line);border-radius:var(--r-card);
  font-family:var(--font-body);font-weight:400;font-size:var(--t-small);
  line-height:1.6;color:var(--ink);
}
/* the status sentence in the account reads as context, not an instruction */
.woocommerce-MyAccount-content > p:not([class]){
  margin:0 0 30px;
  font-family:var(--font-body);font-weight:400;font-size:var(--t-small);
  line-height:1.7;color:var(--ink-soft);
}

/* ---- the details table, panelled on the thank-you page ---- */
.woocommerce-order .woocommerce-order-details table.shop_table{
  background:var(--paper);
  border:1px solid var(--line);border-radius:var(--r-card);
  border-collapse:separate;border-spacing:0;overflow:hidden;
}
.woocommerce-order .woocommerce-order-details table.shop_table thead th,
.woocommerce .woocommerce-order .woocommerce-order-details table.shop_table thead th{
  background:var(--page);padding:14px 20px;border-bottom:1px solid var(--line);
}
.woocommerce-order .woocommerce-order-details table.shop_table tbody td,
.woocommerce-order .woocommerce-order-details table.shop_table tbody th,
.woocommerce-order .woocommerce-order-details table.shop_table tfoot td,
.woocommerce-order .woocommerce-order-details table.shop_table tfoot th,
.woocommerce .woocommerce-order .woocommerce-order-details table.shop_table td,
.woocommerce .woocommerce-order .woocommerce-order-details table.shop_table th{
  padding:14px 20px;border-bottom:1px solid var(--line);
}
.woocommerce-order .woocommerce-order-details table.shop_table tfoot tr:last-child td,
.woocommerce-order .woocommerce-order-details table.shop_table tfoot tr:last-child th{
  border-bottom:0;
}
/* the total, wherever WooCommerce decides to put that row - see the
   woocommerce_get_order_item_totals filter in functions.php */
.vyson-order-total,
.vyson-order-total .amount{
  font-family:var(--font-display);font-weight:700;font-size:var(--t-price);color:var(--accent);
}

/* ---- the same table inside the account: no second frame ---- */
.account-panelled .woocommerce-order-details table.shop_table,
.account-panelled table.shop_table{background:transparent;border:0;border-radius:0}

/* ---- the addresses ----
   On the thank-you page each one is its own card on the page ground. Inside
   the account panel they are shelves within it, so they drop the border and
   keep only the fill. */
.account-panelled .woocommerce-customer-details address{
  border:0;background:var(--page);
}
.woocommerce-order-details__title,
.woocommerce-column__title{margin:0 0 14px}
.account-panelled .woocommerce-customer-details{margin-top:34px}


/* One more row-level action: View, on every line of the orders table. It was
   wearing the 48px red primary - five of them stacked down a table read as
   five calls to action on a page whose actual action is elsewhere. Same ghost
   the cart's Update cart wears, and for the same reason. */
.account-panelled .woocommerce-orders-table__cell-order-actions a.button,
.woocommerce .account-panelled .woocommerce-orders-table__cell-order-actions a.button{
  min-height:0;padding:9px 18px;
  background:var(--paper);color:var(--ink);border:2px solid var(--line);
  font-family:var(--font-body);font-weight:700;font-size:var(--t-eyebrow);
  letter-spacing:1.2px;text-transform:uppercase;
}
.account-panelled .woocommerce-orders-table__cell-order-actions a.button:hover{
  border-color:var(--ink);background:var(--paper);color:var(--ink);transform:translateY(-2px);
}
.account-panelled .woocommerce-orders-table__cell-order-actions{text-align:right}




/* --------------------------------------------------------------------------
   26. The FAQ accordion

   Built to the arrangement the client sent: a hairline between every row, the
   question on the left, a plus on the right, everything closed to start with.
   Eight questions as a flat run of headings meant scrolling past seven
   answers to reach the one you came for; collapsed, all eight are on screen
   at once and the page answers "is my question here?" first.

   Native <details>/<summary>. Keyboard and screen-reader behaviour comes free
   and correct, there is no JavaScript to load, and the same markup works in
   the static build and in WordPress - wp_kses_post() allows both tags, so
   editing the page in Gutenberg will not flatten it.

   The question is an h3. The reference sets its questions at about 20px under
   a section heading; ours sit directly under the page's own h1, and this
   site's h2 is locked to one size, so an h2 question would land at 30px - a
   hair under the h1 above it, with the hierarchy collapsing. --t-h3 is 18px,
   already the size of "Billing details" and "Your order".
   -------------------------------------------------------------------------- */
/* The accordion takes the whole shell, not the 72ch reading column.

   It was briefly centred inside that column instead, which put its left edge
   193px in from the page's. The banner's h1 cannot follow it there: the veil
   over the photograph is a left-heavy gradient whose alphas were MEASURED
   line by line, and the right of the strip comes out at 0.58x what AA asks -
   moving white type into it fails, and that is a contrast fact, not a taste
   one. So the block lines up with the title rather than the title with the
   block. Full width also lands closer to the reference than a narrow centred
   column did: its rows run nearly the container's width with the sign hard
   right.

   The answer keeps a reading measure of its own - a 1067px line of prose is
   not something anyone should be asked to read.

   Scoped with :has() rather than a body class, because one stylesheet serves
   the static build and WordPress and neither puts a per-page class on the
   body that the other has too. */
.shell--prose:has(> .faq-list){grid-template-columns:100%;justify-content:start}

/* The heading the client asked for, above the list and centred - the one
   thing on this page that is centred, because the rows underneath it are a
   scannable block and their text has to start at a fixed left edge.

   It is an h2, so it is 30px like every other h2 on this site. The reference
   sets its own at roughly 48px; this site's type scale is locked and a
   one-off size here would be the only h2 on the site that is not the h2.

   It also repairs the outline: the page went h1 straight to the h3 questions
   before this, skipping a level. */
.faq-head{text-align:center;margin:0 0 clamp(26px,4vw,42px)}
.faq-head + .faq-list{margin-top:0}

.faq-list{border-top:1px solid var(--line)}
.faq-q{border-bottom:1px solid var(--line)}

/* list-style:none plus the two vendor markers: every engine hides its own
   disclosure triangle differently, and one left behind is a visible bullet. */
.faq-q > summary{
  display:flex;align-items:center;justify-content:space-between;gap:24px;
  padding:22px 2px;cursor:pointer;
  list-style:none;
}
.faq-q > summary::-webkit-details-marker{display:none}
.faq-q > summary::marker{content:''}
.faq-q > summary:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
.faq-q > summary h3{margin:0}
.faq-q > summary:hover h3{color:var(--accent)}

/* The plus is drawn by a pseudo-element, and that is not a stylistic choice.
   It started as a <span> in the markup, and WordPress kept breaking it:
   wpautop wraps loose inline content that follows a block element, so
   </h3><span> became </h3><p><span>, the span became display:inline, and an
   inline box cannot take the width the bars are drawn across - the sign
   rendered zero pixels wide. There is no arrangement of a real element in
   there that wpautop leaves alone.

   Two background layers, one bar each. The vertical layer's width animates to
   zero on open, so a real plus becomes a real minus - which is what the
   control means, where a rotated x would mean "close the page". It also keeps
   this off the 451 KB Phosphor sheet that is still on the list to slim down. */
.faq-q > summary::after{
  content:'';flex:none;width:16px;height:16px;
  background:
    linear-gradient(var(--ink-soft),var(--ink-soft)) center/16px 2px no-repeat,
    linear-gradient(var(--ink-soft),var(--ink-soft)) center/2px 16px no-repeat;
  transition:background-size .26s var(--ease-out);
}
.faq-q > summary:hover::after{
  background:
    linear-gradient(var(--accent),var(--accent)) center/16px 2px no-repeat,
    linear-gradient(var(--accent),var(--accent)) center/2px 16px no-repeat;
}
.faq-q[open] > summary::after,
.faq-q[open] > summary:hover::after{
  background:
    linear-gradient(var(--accent),var(--accent)) center/16px 2px no-repeat,
    linear-gradient(var(--accent),var(--accent)) center/0 16px no-repeat;
}

/* .shell--prose is a grid and spaces its children with `> * + *`, which only
   reaches DIRECT children - the accordion made every answer paragraph a
   grandchild and they came out with no space between them at all. Same 1.1em,
   restated one level in. */
/* No max-width. It had a 68ch reading measure, which on a 1500px shell left
   the answer using under half the row and a wide empty column beside it -
   the client's call was that it read as cut off, and they are right: an
   answer that stops halfway across its own row looks truncated rather than
   measured. The rows are full width, so the text is too. */
.faq-a{padding:0 2px 26px}
.faq-a > * + *{margin-top:1.1em}
.faq-a > :first-child{margin-top:0}
.faq-a > :last-child{margin-bottom:0}
.faq-a ul,.faq-a ol{display:grid;gap:.5em;padding-left:1.3em}
.faq-a li::marker{color:var(--accent)}
/* Defensive: the markup no longer gives wpautop a reason to insert a <br> in
   the summary, but a client editing this page in Gutenberg could bring one
   back, and a stray line break there would push the plus onto its own row. */
.faq-q > summary br{display:none}
/* The height snaps - <details> cannot be transitioned without either a
   height hack or ::details-content, which is too new to rely on. The content
   easing in covers the change without ever animating a wrong height. */
.faq-q[open] .faq-a{animation:faq-open .28s var(--ease-out) both}
@keyframes faq-open{
  from{opacity:0;transform:translateY(-6px)}
  to{opacity:1;transform:none}
}
@media (prefers-reduced-motion:reduce){
  .faq-q[open] .faq-a{animation:none}
  .faq-q > summary::after{transition:none}
}
@media (max-width:759px){
  .faq-q > summary{gap:16px;padding:18px 2px}
  .faq-a{padding-bottom:22px}
}


/* --------------------------------------------------------------------------
   27. About this toy

   Two panels, and the reason is worth writing down because the first attempt
   looked unfinished for a mechanical reason.

   That version put the facts in an auto-fit card grid across the whole shell.
   auto-fit built six 196px tracks in the 1337px row, collapsed the four empty
   ones, and gave the two remaining cards a 662px track each - then the
   max-width:360px on the card shrank the CARD but not the TRACK, so each card
   sat at the left of its own 662px cell with 400px of nothing between them.
   The bullets did the same: two 640px columns holding 300px of text.

   Stretching short content across 1337px does not make a block look
   designed, it makes it look empty. So it is held instead of stretched: the
   lines in one panel, the facts in another as label/value rows - a real spec
   table, which is what was asked for. Two dense blocks rather than four
   scattered items, and a panel with a single row still reads as a panel.
   -------------------------------------------------------------------------- */
.toyfacts__head .section-head{margin-bottom:26px}
/* Eyebrow to title. This head is not --center, so its lead stayed
   display:block on a desktop and the two sat 0px apart; the homepage heads
   are 10px. Below 768 the general non-center rule (18px) already applies. */
@media (min-width:768px){
  .toyfacts__head .section-head__lead{display:grid;gap:10px}
}

.toybox{display:grid;gap:clamp(18px,2.4vw,26px)}

.toybox__panel{
  background:var(--paper);
  border:1px solid var(--line);border-radius:var(--r-card);
  padding:clamp(20px,2.6vw,28px);
}

/* A title on each panel. Without one the bullets in the left panel had no
   owner while the notes below them did, so the red dots read as belonging to
   nothing.

   h3, not another eyebrow. .toyspecs dt and the "Please note" label are both
   eyebrows, and a third eyebrow stacked above them would be indistinguishable
   from the rows it introduces. h3 is 18px on this site - already the size of
   "Billing details" and "Your order" - so the block reads h2 30 > h3 18 >
   eyebrow 11 and every level is a size the site already uses. */
.toybox__title{margin:0 0 16px}

ul.toyfacts{
  list-style:none;margin:0;padding:0;
  font-family:var(--font-body);font-size:var(--t-small);line-height:1.6;color:var(--ink);
}
ul.toyfacts li{position:relative;padding-left:20px;margin-bottom:12px}
ul.toyfacts li:last-child{margin-bottom:0}
ul.toyfacts li::before{
  content:'';position:absolute;left:2px;top:.55em;
  width:6px;height:6px;border-radius:50%;background:var(--accent);
}
/* The safety copy. A hairline and its own label at the foot of the panel -
   "a swim ring is not a life-saving device" should not read as the fifth
   feature. Muted rather than alarmed: this site has no warning-box pattern,
   and inventing one for two sentences would shout. */
.toynote{margin-top:20px;padding-top:18px;border-top:1px solid var(--line)}
.toynote .eyebrow{margin:0 0 10px}
ul.toyfacts--note li{color:var(--ink-soft)}
ul.toyfacts--note li::before{background:var(--ink-soft)}

.toyfacts__prose{margin:0 0 16px}
.toyfacts__prose > :first-child{margin-top:0}
.toyfacts__prose > :last-child{margin-bottom:0}

/* label / value rows. A hairline between them, none under the last - the
   panel's own border is already there. */
dl.toyspecs{margin:0}
.toyspecs__row{
  display:grid;gap:2px 18px;
  padding:12px 0;border-bottom:1px solid var(--line);
}
@media (min-width:480px){
  .toyspecs__row{grid-template-columns:minmax(0,auto) minmax(0,1fr);align-items:baseline}
}
.toyspecs__row:first-child{padding-top:0}
.toyspecs__row:last-child{padding-bottom:0;border-bottom:0}
.toyspecs dt{
  margin:0;
  font-family:var(--font-body);font-weight:700;font-size:var(--t-eyebrow);
  line-height:1.3;letter-spacing:1.2px;text-transform:uppercase;color:var(--ink-soft);
}
.toyspecs dd{
  margin:0;
  font-family:var(--font-display);font-weight:700;font-size:15px;line-height:1.4;
  color:var(--ink);overflow-wrap:anywhere;
}
@media (min-width:480px){
  .toyspecs dd{text-align:right}
}
@media (max-width:759px){
  .toyfacts__head .section-head{margin-bottom:20px}
}


/* --------------------------------------------------------------------------
   28. The add-to-cart confirmation

   Below the fixed bar, centred, and it leaves on its own after 4.5s. It
   replaced the drawer opening rather than joining it: two confirmations for
   one click is noise, and the drawer is the one that covers the page and
   traps focus - wrong on a product page where the next move is often to add
   another option. The header badge still counts up, and the drawer is one
   click away on the cart button.
   -------------------------------------------------------------------------- */
.toast{
  position:fixed;z-index:140;
  top:calc(var(--header-h) + 14px);left:50%;
  display:flex;align-items:center;gap:14px;
  max-width:min(560px,calc(100% - 32px));
  padding:13px 14px 13px 18px;
  background:var(--ink);color:#fff;
  border-radius:var(--r-card);
  box-shadow:0 16px 40px rgba(18,18,18,.22);
  /* the -50% has to stay in the transform on both states or the toast walks
     to the right as it animates */
  transform:translate(-50%,-14px);opacity:0;
  transition:transform .28s var(--ease-out),opacity .28s var(--ease-out);
  pointer-events:none;
}
.toast.is-up{transform:translate(-50%,0);opacity:1;pointer-events:auto}
.toast > i{font-size:22px;flex:none;color:#fff}

.toast__text{margin:0;min-width:0}
.toast__text strong{
  display:block;
  font-family:var(--font-display);font-weight:700;font-size:15px;line-height:1.3;
}
.toast__text span{
  display:block;margin-top:2px;
  font-family:var(--font-body);font-weight:400;font-size:var(--t-small);
  line-height:1.4;color:rgba(255,255,255,.72);
}

.toast__link{
  flex:none;margin-left:auto;
  font-family:var(--font-body);font-weight:700;font-size:var(--t-eyebrow);
  letter-spacing:1.2px;text-transform:uppercase;color:#fff;
  text-decoration:underline;text-underline-offset:3px;
}
.toast__link:hover{color:#fff;text-decoration-thickness:2px}

.toast__close{
  flex:none;display:grid;place-items:center;
  width:30px;height:30px;padding:0;border:0;border-radius:var(--r-pill);
  background:rgba(255,255,255,.12);color:#fff;font-size:15px;cursor:pointer;
}
.toast__close:hover{background:rgba(255,255,255,.22)}
.toast__close:focus-visible{outline:2px solid #fff;outline-offset:2px}

@media (max-width:559px){
  .toast{
    gap:11px;padding:12px 12px 12px 14px;
    flex-wrap:wrap;
    /* An explicit width, because max-width alone was not enough. The toast is
       position:fixed with no width, so it is shrink-to-fit - and in a WRAPPING
       flex that resolved to 188px on a 375px phone, well under the 343 the
       max-width allowed. The row never had the room to be a row, so the icon,
       the message and the close button each took a line of their own and the
       close landed between the message and View cart. The space was always
       there; nothing was claiming it. */
    width:calc(100% - 32px);
    /* room at the right for the close, which comes out of the flow below */
    padding-right:46px;
  }
  /* Out of the flow and into the corner. In a wrapping flex the browser
     places items at their BASE size and wraps what will not fit - it does not
     shrink first - so the message's max-content width pushed both the tick
     and the close onto lines of their own, and the close ended up sitting
     between the message and View cart. Top right is where a dismiss belongs
     anyway; the row below is then just tick + message. */
  .toast__close{position:absolute;top:10px;right:10px}
  /* and the message shares the tick's line rather than claiming its own */
  .toast__text{flex:1 1 0}
  .toast__link{margin-left:0;order:3;width:100%;margin-top:2px}
}
@media (prefers-reduced-motion:reduce){
  .toast{transition:opacity .01ms}
  .toast,.toast.is-up{transform:translate(-50%,0)}
}


/* --------------------------------------------------------------------------
   29. The order's addresses, labelled

   WooCommerce prints these through get_formatted_billing_address(), which
   returns one blob - on a real order that was six lines reading "a a / q / q /
   q / Penang (Pulau Pinang) / 53100" with no way to tell the street from the
   unit from the town. A delivery address is the one block on the page where
   the reader needs to find a specific field, so the override in
   woocommerce/order/order-details-customer.php labels every one.

   Label left, value LEFT-aligned - not right like the spec rows. An address
   is read as a block of lines and a ragged left edge down the values would
   fight that.
   -------------------------------------------------------------------------- */
.addr-card{
  background:var(--paper);
  border:1px solid var(--line);border-radius:var(--r-card);
  padding:4px 20px;
}
.account-panelled .addr-card{background:var(--page);border:0}

dl.addr{margin:0}
.addr__row{
  display:grid;gap:2px 16px;
  padding:11px 0;border-bottom:1px solid var(--line);
}
@media (min-width:420px){
  .addr__row{grid-template-columns:minmax(0,104px) minmax(0,1fr);align-items:baseline}
}
.addr__row:last-child{border-bottom:0}
.addr dt{
  margin:0;
  font-family:var(--font-body);font-weight:700;font-size:var(--t-eyebrow);
  line-height:1.4;letter-spacing:1.2px;text-transform:uppercase;color:var(--ink-soft);
}
.addr dd{
  margin:0;
  font-family:var(--font-body);font-weight:400;font-size:var(--t-small);
  line-height:1.5;color:var(--ink);overflow-wrap:anywhere;
}
.addr__none{
  margin:12px 0;
  font-family:var(--font-body);font-size:var(--t-small);color:var(--ink-soft);
}


/* the my-account addresses page uses the same labelled block. Its cards are
   already .woocommerce-Address (section 22), so this is only the heading row
   and the intro line. */
.addr-intro{
  margin:0 0 22px;
  font-family:var(--font-body);font-weight:400;font-size:var(--t-small);
  line-height:1.6;color:var(--ink-soft);
}
.account-panelled .woocommerce-Address .woocommerce-Address-title h3{margin:0}
.account-panelled .woocommerce-Address dl.addr{margin-top:4px}


/* ==========================================================================
   30. Variation options as pictures

   "Evo Grey" and "Evo Purple" are two words in a dropdown; they are two
   different cars. Forty-six of them in a <select> is a list nobody reads, so
   each option carries the picture Shopee shows beside it.

   The select is still there, hidden, because WooCommerce's variation script
   keys price, availability and the Add to cart button off its value. The
   tiles are radio inputs that write into it. If main.js never runs, .is-tiled
   is never added and the select is simply visible - the page works exactly as
   it did before.

   On the pictures: they are cropped to the toy and capped at 96px. Both
   numbers were measured against the shop's watermark rather than chosen -
   see tools/build-variation-tiles.py. The watermark carries the Shopee shop's
   name, which does not belong on this site in any readable form.
   -------------------------------------------------------------------------- */

/* Restated at WooCommerce's own weight. woocommerce.css reaches this select
   as .woocommerce div.product form.cart .variations select (0,4,3), and
   while it sets no display of its own, matching the prefix costs nothing and
   removes the question. */
.variations select.is-tiled,
.woocommerce div.product form.cart .variations select.is-tiled{display:none}

.vtiles{
  display:grid;
  /* Three, stated rather than derived. auto-fit was tried and it counts the
     column's width, not the design: 615px of info column at a 1440 viewport
     fits four tracks and laid out four. The count is a design decision, so
     the breakpoints carry it and the browser is not asked to guess. */
  grid-template-columns:repeat(3,minmax(0,1fr));
  gap:6px;margin:10px 0 0;
}
/* Past about eleven options two columns stop fitting beside the photograph,
   and the picker would push Add to cart below the fold. Scrolling the list
   inside itself keeps the button where the shopper expects it. The max-height
   shows four whole rows and a sliver of the fifth, so the list is
   visibly cut off rather than looking like it simply ends. */
.vtiles--scroll{
  max-height:260px;overflow-y:auto;overscroll-behavior:contain;
  padding:2px 8px 2px 2px;
}

.vtile{
  /* The radio inside is absolutely positioned, and without this the nearest
     positioned ancestor is .product-main - nine levels up. Its static
     position then resolved against THAT box, which put the input 527px below
     its own tile, and a real click focused it and made the browser scroll
     down to reveal it. Worse the further the list was scrolled, which is why
     it only happened on the options near the end. */
  position:relative;
  display:flex;align-items:center;gap:8px;
  margin:0;padding:5px 7px;min-height:42px;
  background:var(--paper);
  /* --r-chip, not --r-card: at 42px tall an 8px radius is a fifth of the
     tile's height and the box starts reading as a pill. */
  border:1px solid var(--line);border-radius:var(--r-chip);
  cursor:pointer;
  transition:border-color .18s var(--ease-out), background-color .18s var(--ease-out);
}
/* Invisible rather than display:none - a display:none radio is not focusable,
   and the arrow-key navigation a radio group gives for free is the whole
   reason these are radios and not buttons. Sized to the tile, not to 1px in a
   corner: the browser scrolls a newly focused control into view, so the box
   it scrolls to has to be the tile the visitor just clicked. */
.vtile input{
  position:absolute;inset:0;width:100%;height:100%;
  opacity:0;pointer-events:none;margin:0;
}
.vtile img{
  width:30px;height:30px;flex:none;
  border-radius:3px;object-fit:cover;
  background:var(--page);
}
/* An option with no picture keeps its text on the same baseline as the ones
   that have one, so a mixed list does not look ragged. */
.vtile--nopic{padding-left:11px}

.vtile span{
  font-family:var(--font-body);font-weight:400;font-size:var(--t-small);
  line-height:1.25;color:var(--ink);
}

.vtile:hover{border-color:var(--ink-soft)}
.vtile.is-on{border-color:var(--accent);background:var(--blush)}
.vtile.is-on span{color:var(--accent);font-weight:700}

/* The ring goes on the label because the input it belongs to is off-screen. */
.vtile:focus-within{outline:2px solid var(--accent);outline-offset:2px}

.vtile.is-off{
  cursor:not-allowed;background:var(--page);border-color:var(--line);
}
.vtile.is-off img{opacity:.34}
.vtile.is-off span{color:var(--ink-soft)}

/* The Clear link sits under the picker rather than beside a dropdown now. */
.variations .vtiles + .reset_variations,
.variations select.is-tiled ~ .reset_variations{margin-top:12px}

@media (max-width:1023px){
  .vtiles{grid-template-columns:repeat(2,minmax(0,1fr))}
  .vtiles--scroll{max-height:260px}
}
/* No one-column step. It was there on the assumption that a 375px phone could
   not hold two tiles, but the column is 331px and a tile at two-up is 162 -
   a 30px thumbnail, a gap and about 110px of label, which the 13px label wraps
   into at most two lines inside the tile's own 42px. One-up spent 321px on a
   30px picture and doubled the scrolling: rc-monster has thirty-nine options,
   so that is thirty-nine rows in a 260px box instead of twenty. */


/* ==========================================================================
   31. The gallery's thumbnail rail

   Now that a product carries nine photographs instead of one, WooCommerce
   renders its flexslider thumbnail rail - and its own rule is

     .woocommerce div.product div.images .flex-control-thumbs li{width:25%}

   at (0,4,2), which on nine photographs laid them out as 33px squares in a
   float grid three rows deep. Restated at that exact weight as one scrolling
   row of real thumbnails; the prefix is assumed rather than discovered,
   because every rule WooCommerce writes for this carries it.
   -------------------------------------------------------------------------- */
.woocommerce div.product div.images .flex-control-thumbs{
  display:flex;gap:8px;
  margin:12px 0 0;padding:2px;
  list-style:none;
  overflow-x:auto;overscroll-behavior-x:contain;
  /* none, not thin. The arrows beside the rail are the affordance; a native
     bar under the row as well is a second, competing control. Set here rather
     than on .galrail, which at (0,2,0) lost to this very rule's (0,4,2). */
  scrollbar-width:none;
}
.woocommerce div.product div.images .flex-control-thumbs::-webkit-scrollbar{display:none}
.woocommerce div.product div.images .flex-control-thumbs li{
  width:66px;flex:none;float:none;margin:0;
}
/* woocommerce.css fades every thumbnail to .5 and the active one to 1. A
   photograph the shopper is being asked to choose between should not be
   dimmed - the border says which is current instead. */
.woocommerce div.product div.images .flex-control-thumbs img{
  display:block;width:66px;height:66px;
  object-fit:contain;background:var(--paper);
  border:1px solid var(--line);border-radius:var(--r-chip);
  opacity:1;cursor:pointer;
  transition:border-color .18s var(--ease-out);
}
.woocommerce div.product div.images .flex-control-thumbs img:hover{
  border-color:var(--ink-soft);opacity:1;
}
.woocommerce div.product div.images .flex-control-thumbs img.flex-active,
.woocommerce div.product div.images .flex-control-thumbs li img.flex-active{
  border-color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent);opacity:1;
}

/* The stage. WooCommerce sizes it off the image; a fixed square stops the
   column jumping when a portrait variation photo replaces a square one. */
.woocommerce div.product div.images .woocommerce-product-gallery__image img{
  aspect-ratio:1/1;object-fit:contain;background:var(--paper);
}

@media (max-width:1023px){
  .woocommerce div.product div.images .flex-control-thumbs li,
  .woocommerce div.product div.images .flex-control-thumbs img{
    width:58px;
  }
  .woocommerce div.product div.images .flex-control-thumbs img{height:58px}
}


/* ---- arrows either side of the thumbnail rail ---- */
.galrail{display:flex;align-items:center;gap:6px;margin:12px 0 0}
.galrail .flex-control-thumbs{
  margin:0;flex:1 1 auto;min-width:0;scroll-behavior:smooth;
  /* The arrows are the affordance now; a native bar under the row as well
     reads as a second, competing control. Still scrollable - by the arrows,
     by a swipe, and by a trackpad. */
  scrollbar-width:none;
}
.galrail__nav{
  flex:none;width:26px;height:66px;display:none;place-items:center;
  padding:0;background:var(--paper);color:var(--ink);
  border:1px solid var(--line);border-radius:var(--r-chip);
  cursor:pointer;font-size:13px;
  transition:background-color .18s var(--ease-out), opacity .18s var(--ease-out);
}
.galrail--paged .galrail__nav{display:grid}
.galrail__nav:hover:not(:disabled){background:var(--page)}
/* Kept in the layout once it runs out of rail rather than hidden: a control
   that vanishes at one end makes the whole row jump sideways by its width. */
.galrail__nav:disabled{opacity:.3;cursor:default}
.galrail__nav:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

@media (max-width:1023px){
  .galrail__nav{height:58px}
}


/* ==========================================================================
   32. The phone: what centres, and what does not

   Below 480px the product grid is still two-up and a card's type column is
   about 133px. Three things decide the alignment there:

   CENTRED - the card's name, price and button. At 133px a two-line name left
   aligns to a ragged right edge under a full-width button that is already
   centred, and the three read as three different columns. Centred they share
   one axis. Same for a reel's caption.

   LEFT - and these are deliberate, not leftovers:

     the hero, because its copy sits on a photograph with the carousel dots
     at the bottom left and the arrows at the bottom right. The copy's left
     edge lines up with the dots; centring it leaves both controls hanging.

     the category tiles, because each is an icon beside a label. Left, the
     icons form a straight column down each side of the grid. Centred, every
     tile's icon lands somewhere else.

     Find us, because it is an address, an opening time and two numbers. A
     centred address is a harder read, and the icon rows need a shared left
     edge to scan.

     the footer, for the same reason - link lists and an address.

   480 rather than 559: two-up stops being cramped once a card clears about
   210px, which is a 482px viewport. It is also the breakpoint the card's
   button icon already uses, so the card has one mobile step, not two.
   -------------------------------------------------------------------------- */
@media (max-width:479px){
  /* text-align, not align-items:center. The body is a flex column, and
     centring the ITEMS would shrink the clamped two-line name to its content
     width and let the button stop being full-bleed. Only the text moves. */
  .pcard__body{text-align:center;padding:12px 11px 13px}
  .pcard__price{justify-content:center}

  /* "Select options" is fourteen characters. At 14px it sets about 102px, and
     with the button's own 14px of padding either side it needed 130 of the
     127 available - so every card in the row carried a two-line button 48px
     tall. The label is the part that cannot be cut, so the padding and a
     point of size give it the room: at 13px it sets about 95px inside 113,
     and nowrap makes sure a longer label overflows visibly instead of
     silently wrapping again. */
  /* Two selectors, because /shop/ and the product page carry
     body.woocommerce and WooCommerce's own add-to-cart rule above is
     .woocommerce a.button - (0,2,1) against this one class. Specificity
     decides before order, so the single class lost font-size and padding
     and only the nowrap, which both rules declare, looked like it took:
     every card on /shop/ carried a two-line 48px button. Three classes
     wins it back without an !important. */
  .pcard__add,
  .woocommerce .pcard .pcard__add{padding-inline:10px;font-size:13px;white-space:nowrap}

  .reel__cap{text-align:center}
}


/* ---- the product carousel peeks on both sides, on a phone ----------------

   It snapped scroll-snap-align:start with 8px of inline padding, so a snapped
   card sat flush against the band's left edge and only the next card showed.
   A row that is flush on one side does not read as a row that continues.

   Centre snapping is what puts a sliver of BOTH neighbours on screen, and the
   inline padding is what stops the first and last cards - which cannot be
   centred, there is nothing beyond them - from sitting flush either.

   Scoped away from .carousel--reels, which already has its own bleed and
   centre treatment and must not get a second one. -reels has a modifier so
   :not() is enough; the product carousel carries no modifier at all.
   -------------------------------------------------------------------------- */
@media (max-width:619px){
  .carousel:not(.carousel--reels) .carousel__track{
    --per:1.1;
    padding-inline:30px;
    scroll-padding-inline:30px;
  }
  .carousel:not(.carousel--reels) .carousel__track > li{scroll-snap-align:center}
}


/* ---- the footer, centred on a phone -------------------------------------

   Its columns stack there, so the left edge they shared on desktop no longer
   exists - each block is its own full-width row and centring is what makes
   them read as one stack rather than three ragged ones. The icon rows have to
   be centred as flex containers, not just as text: their icon and label are
   two boxes, and text-align does not move a box.
   -------------------------------------------------------------------------- */
@media (max-width:759px){
  .footer__grid{text-align:center}
  .footer__grid > *{justify-items:center}
  .footer__brand{justify-content:center}
  .footer__brand p{max-width:34ch;margin-inline:auto}
  .footer__brand .brand{justify-content:center}
  .footer__socials{justify-content:center}
  .footer__links a{justify-self:center}
  .footer__contact a,
  .footer__contact p{justify-content:center;text-align:left}
  .footer__bottom{justify-content:center;text-align:center}
}


/* ---- the hero on a phone: centred, full screen, no arrows ----------------

   On a wide screen the copy sits in the left 46% of the frame, against the
   photograph's subject on the right, and the dots line up with the copy's
   left edge. A phone has no left and right - the photograph is behind the
   text, not beside it - so the whole block centres and the dots centre with
   it, or they read as belonging to a column that is no longer there.

   The rule and the lede need margin-inline:auto of their own: both are
   narrower than the column (64px and 40ch), and text-align does not move a
   block that has its own width.

   The arrows go. They sit in the bottom-right corner at this size, a 40px
   target beside the dots, and the hero advances by itself every few seconds
   with the dots available to jump - so they are the one control here that
   carries nothing the shopper cannot already do.

   Height is already 100dvh from the base rule; dvh rather than vh is what
   keeps it a true full screen while a phone browser's own bars come and go.
   -------------------------------------------------------------------------- */
@media (max-width:767px){
  .hero__copy{justify-items:center;text-align:center;margin-inline:auto}
  .hero__rule{margin-inline:auto}
  .hero .lede{margin-inline:auto}
  .hero__actions{justify-content:center}

  /* left:0;right:0 turns the absolutely positioned row into a full-width
     band so justify-content has something to centre inside. The base rule
     pins it to the copy's left margin, which is the thing being undone. */
  .hero__controls{left:0;right:0;justify-content:center}

  .hero__arrow{display:none}
}


/* ==========================================================================
   33. The phone, on every page but the homepage

   The homepage settled the rule: a block that announces something centres,
   a block that is read or filled in stays left. Section 32 applied it there.
   These are the same decisions on the pages that were still carrying the
   desktop's left edge - which on a 331px column is not a left edge, it is
   just where the text happens to start.

   CENTRED

     .page-head .section-head   every inner page's own headline, over its
                                banner. It is the page's hero; the homepage's
                                hero centres, so this does.
     .section-head              the base block. --center was doing this on the
                                homepage only, so "About this toy" on a product
                                and "Common Questions" on the FAQ sat left
                                while the same block centred one page over.
     .crumbs                    short, and stranded on the left under a
                                centred title.
     .cta-bar                   the closing band. The homepage's ending is
                                centred; Contact's was not.
     the product page's eyebrow, name, price and lede - the part that sells.

   LEFT, and each for a reason

     .shell--prose              paragraphs and lists. Centred body copy has no
                                left edge to return to and is measurably
                                slower to read; the four policy pages are the
                                longest reading on the site.
     .toyspecs / .toyfacts      a definition list and a bulleted list. Centred
                                bullets scatter; centred dt/dd stop being a
                                table.
     .contact__fact             an address, an opening time, two numbers.
     forms                      labels sit above their fields and the eye
                                tracks one edge down the form.
     the picker tiles           thumbnail beside label, same as the homepage's
                                category tiles: left keeps the thumbnails in a
                                column.
   -------------------------------------------------------------------------- */
@media (max-width:767px){
  .section-head,
  .page-head .section-head{justify-items:center;text-align:center}
  /* The lead needs BOTH, and it needs the banner's own prefix.
     .page-head--banner .section-head__lead sets justify-items:start AND
     text-align:left at (0,2,0). Overriding only justify-items centred the
     BOX and left its lines ranged left inside it - so a two-line title on a
     375px phone sat centred as a block and ragged as type, which reads as an
     accident rather than a decision. Matching the prefix removes any question
     of which one wins. */
  .section-head .section-head__lead,
  .page-head--banner .section-head,
  .page-head--banner .section-head__lead{justify-items:center;text-align:center}

  .crumbs{justify-content:center;text-align:center}

  .cta-bar{justify-items:center;text-align:center}
  .cta-bar__copy{justify-items:center}
  /* the base rule pins this to the start of the grid column */
  .cta-bar .button{justify-self:center}

  /* Named one by one rather than centring .product-main__info: the form is
     one of its children, and the picker, the quantity box and the labels
     inside it are the things that must not move. */
  .product-main__info > .eyebrow,
  .product-main__info > h1,
  .product-main__info > .lede{text-align:center}
  /* flex, so text-align does not reach it */
  .product-main__price{justify-content:center;text-align:center}
  /* the attribute's name - "MODEL" - over a grid that fills the column */
  .variations th{text-align:center}
  /* quantity beside Add to cart */
  .variations_button{justify-content:center}
}


/* ---- the hero's copy breathes on a phone ---------------------------------

   The measured gaps were 18 / 26 / 20 / 30 between eyebrow, title, rule, lede
   and buttons - the desktop's spacing, carried down unchanged. Two things
   make that read tighter on a phone than the numbers suggest:

   the title sets at line-height .92, so its glyphs sit proud of their line
   box at both ends and eat into the gap either side of it. The 18px above it
   is nearer 12px of daylight.

   and the block is centred here, so there is no shared left edge to carry the
   eye from one line to the next - the only thing separating the three pieces
   is the space between them.

   Raised proportionally rather than to one flat number, so the block keeps
   its rhythm: the biggest gap stays the one under the title.
   -------------------------------------------------------------------------- */
@media (max-width:767px){
  .hero__copy .eyebrow{margin-bottom:26px}
  /* margin-inline stays auto - it is what centres a 64px rule in the column */
  .hero__rule{margin:34px auto 26px}
  .hero__actions{margin-top:34px}
}


/* ---- the inner pages' banner copy, same treatment as the hero ------------

   Measured on /new-arrivals/ at 375px: eyebrow to title 0px, title to lede
   0px. Not "tight" - nothing at all. The three are children of
   .section-head__lead, which is display:block, and the 10px gap set on
   .section-head above it only ever applied between ITS children - and it has
   exactly one, the lead itself.

   So the lead becomes a grid here, which is also what makes gap mean
   anything. Worth noting for the same reason: justify-items on a block box
   does nothing either, so text-align was the only thing centring this copy -
   it centres the TEXT inside each box, never the box. That works while every
   box is full width and stops working the moment one has a max-width, which
   .page-head--banner h1 does.
   -------------------------------------------------------------------------- */
@media (max-width:767px){
  .page-head .section-head__lead{
    display:grid;justify-items:center;gap:18px;
  }
  /* the body copy sits a touch further off the title than the eyebrow does,
     so the eyebrow and title still read as one unit */
  .page-head .section-head__lead > .lede{margin-top:6px}
}


/* ---- the stacked cart: the quantity and the Update button ----------------

   Measured at 375px, with one line in the cart:

     price value      x269..322      right edge 322
     subtotal value   x269..322      right edge 322
     quantity input   x230..288      right edge 288  - 34px short
     Update button    x23..340       centre 181.5, against a panel centre
                                     of 187.5

   Two different causes.

   The quantity cell is right: its .quantity wrapper already ends at 322, in
   line with the money. But an <input> is inline-block, so inside that
   wrapper it sits at the start and leaves the 34px after it - the box is in
   the right place and the control is not. text-align on the wrapper moves
   it, because that is what text-align does to an inline-level box; and the
   digit inside gets the same treatment, so a two or three digit quantity
   grows leftwards from the same edge the prices end on.

   The button is a different fault. td.actions inherits the desktop table's
   padding:16px 12px 16px 0 - twelve on the right, nothing on the left - and
   on a stacked row that asymmetry is the whole 6px. Zeroing the cell lets
   the row's own symmetric 16/18 padding place it, which also lands it on
   x41..334: exactly the column the other cells occupy.
   -------------------------------------------------------------------------- */
@media (max-width:759px){
  .woocommerce-cart table.shop_table.cart td.product-quantity .quantity,
  .woocommerce table.shop_table.cart td.product-quantity .quantity{text-align:right}
  .woocommerce-cart table.shop_table.cart td.product-quantity .quantity input,
  .woocommerce table.shop_table.cart td.product-quantity .quantity input{text-align:right}

  /* 18px either side, which is the inset every other cell in the panel sits
     on - measured at x41..334 - so the button lands in the same column as the
     name, the price and the subtotal rather than bleeding to the panel's own
     border. Zeroing it centred the button but made it 330 wide against the
     cells' 294. */
  .woocommerce-cart table.shop_table.cart td.actions,
  .woocommerce table.shop_table.cart td.actions{padding:16px 18px}
}


/* ---- section heads inside a page, same gap fault as the banner -----------

   Measured on a product at 375px: "The details" to "About this toy" is 0px.
   The cause is the one already found on .page-head - .section-head__lead is
   display:block, so the 14px gap on .section-head above it never applies,
   because .section-head has exactly one child and that child IS the lead.
   The banner fix was scoped to .page-head and this block is not in one.

   --center is left alone deliberately. Those leads already carry their own
   10px from section 3, they are what the homepage uses throughout, and the
   homepage's spacing is signed off - so this only reaches the heads that
   currently have nothing.
   -------------------------------------------------------------------------- */
@media (max-width:767px){
  .section-head:not(.section-head--center) .section-head__lead{
    display:grid;justify-items:center;gap:18px;
  }
}


/* ---- the product's own name, off its eyebrow -----------------------------

   Measured at 375px: 2px. It comes from the <=1023px rule that drops the
   name's margin-top from 10 to 2 - written for the point where the two
   columns become one and the name needed to sit tight under the eyebrow it
   had just been moved beneath. On a phone the same 2px reads as the two
   lines having collided.

   14px, which is the gap the rest of this block already uses: name to price
   is 18 and price to lede is 14, so the eyebrow - a 13px label over a 32px
   name - sits on the smaller of the two and the block keeps its rhythm.

   Phones only. The tablet step keeps its 2px; nothing was reported there and
   the column is a different width.
   -------------------------------------------------------------------------- */
@media (max-width:767px){
  .product-main__info > h1{margin-top:14px}
}
