/* ==========================================================================
   GARN — Design System
   Palette inspired by Hill People Gear: brick-red/rust as the primary brand
   accent, khaki tan as the secondary accent, dark neutral ink + cream as the
   base. Sage green is kept defined but no longer used for primary UI so the
   site reads as two disciplined earth-tone families instead of three.
   ========================================================================== */

@font-face {
  font-family: 'Traditio AH';
  src: url('../assets/fonts/traditio.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'KG Always A Good Time';
  src: url('../assets/fonts/KGAlwaysAGoodTime.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'F25 Executive';
  src: url('../assets/fonts/F25_Executive.otf') format('opentype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Typewriter Condensed';
  src: url('../assets/fonts/typewcond_regular.otf') format('opentype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Typewriter Condensed';
  src: url('../assets/fonts/typewcond_demi.otf') format('opentype');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Typewriter Condensed';
  src: url('../assets/fonts/typewcond_bold.otf') format('opentype');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
/* block, not swap: Lambda is --font-body, so it sets the width of nearly
   every line on the site. Under swap the browser paints in the system
   fallback first -- which is wider -- then reflows when Lambda arrives,
   which showed up as a flash of over-wide text on load. block holds the
   text invisible briefly instead, so it only ever paints at its real
   width. The file is self-hosted and 120KB, so that wait is imperceptible;
   the pages also preload it. */
@font-face {
  font-family: 'Lambda';
  src: url('../assets/fonts/Lambda-Regular.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: block;
}
@font-face {
  font-family: 'Lambda';
  src: url('../assets/fonts/Lambda-Italic.ttf') format('truetype');
  font-weight: 400;
  font-style: italic;
  font-display: block;
}

:root {
  /* Raw palette */
  --forest: #131313;       /* deep neutral ink, softened from the raw forest swatch so dark sections read moody rather than saturated-green — the page ground, a step above the footer */
  --forest-deep: #202020;  /* the site's only other approved background tone — the .section-alt band, and the About story cards */
  --sage: #4d5947;
  --sage-dim: #1c1d19;
  --sage-light: #9aa88f;
  --tan: var(--rust-bright);          /* was warm khaki tan, then a darker mismatched brick red, now matches the actual brand red */
  --tan-light: #e26969;
  --rust: var(--rust-bright);         /* brand accent red — was a darker mismatched brick red */
  --rust-bright: #b8322f;
  --rust-deep: #6e1f1a;    /* darkest rust, for hover-darken effects */
  --rust-vivid: #c2402b;   /* brighter tint of --rust, for accents on dark backgrounds */
  --tan-vivid: #d44740;    /* brighter tint of --tan, for accents on dark backgrounds */
  --btn-red: #b02b25;      /* solid mid-tone, for text/border/accent uses */
  --btn-red-light: #d6413d; /* hover-sweep tint for redish buttons */
  /* The promo bar's fill. Same red as the two button washes above -- only the
     alpha differs, and this is the lowest of the three: the bar is a standing
     element behind white text, so it wants to sit under the page rather than
     on it. Flat, edge to edge. */
  --promo-wash: rgba(214, 65, 61, 0.12);
  --btn-red-gradient: linear-gradient(to bottom, #d6413d 0%, #b02b25 50%, #901711 100%); /* button fill, matches user-supplied pill graphic */
  --btn-gray-dark: #5c5c5c; /* flat hover fill for all the big buttons — desktop only now; the phone rests every button on --btn-white-solid instead of lifting some of them to this */
  --btn-white-solid: #404040; /* flat lighter-gray resting fill for all the big buttons, desktop and mobile */
  /* The hero button's pair, standing in for the two grays above on that one
     button. --title-red at rest so it is the same red as the eyebrows and the
     star ratings rather than a second one, and a lighter step of it for the
     sweep, which is the direction the grays move on hover too.

     The step is smaller than the grays' (+17 per channel against their +28)
     and that is deliberate: it is as far as the fill can lift before the
     off-white label drops under 4.5:1 against it.

     Hover only. The gray buttons hold their LIGHTER value statically on
     phones, where there is no hover to reach it -- the red does not, so this
     value is never on screen without a pointer over it. */
  --btn-red-solid: var(--title-red);
  --btn-red-hover: #c93f3c;
  /* The drop shadow under every pill button. Not a new value: this is what
     the hero's button and the Subscribe band's button already carried, each
     writing it out for itself because those two sit on photography and needed
     to lift off it. The rest of the buttons sit on flat dark grounds and had
     none. They all have it now, from here, so there is one shadow to turn.

     Tight and low -- 3px down, 18px of blur -- because these are pills a
     couple of hundred pixels wide, not panels. --shadow, the site's other
     shadow, is 20px down and 44px of blur: that one is for the cart drawer
     and the popups, and under a button it reads as the button floating
     several inches off the page. */
  --btn-shadow: 0 3px 18px rgba(0, 0, 0, 0.35);
  --title-gold: #c6ac5a;    /* review-success celebration icon only — star ratings and eyebrow/title text use --title-red instead */
  --title-red: #b8322f;     /* color for eyebrow/title labels */
  --offwhite: #fbf7f3;

  /* Dark theme (default — most of the site) */
  --bg: var(--forest);
  --bg-alt: var(--forest-deep);
  --surface: #202020;
  --surface-2: #131313;
  /* The site header and the dropdown menus that hang off it.

     It used to follow --featured-ground, so the bar and the hero band under
     it read as one field and what marked the header was its border-bottom
     rather than a change of tone. It is a literal now: the frame -- header
     and footer -- sits one step under the page instead, and the two ends of
     the document carry the same value so they read as a matched pair. */
  --chrome-ground: #141414;
  /* The footer. A step under the page rather than level with it: the footer
     is the end of the document and the only element below the last section,
     so it is the one piece of furniture that should read as underneath the
     content rather than as more of it.

     The same value as --chrome-ground, written out rather than referring to
     it. The two are equal on purpose right now -- they came apart while the
     header sat at #101010 and the header has moved back -- but they are
     opposite ends of the page, so they stay separate and either can move
     without dragging the other with it. */
  --footer-ground: #141414;
  /* The product page's mobile buy bar. It used to share the frame's token
     with the header and footer and no longer does: the bar is the one piece
     of furniture that has controls sitting ON it -- the colour select and the
     quantity pill, both --field-fill -- so it has to stay a step under them,
     which the frame at --forest no longer is. Raised from #0d0d0d, the value
     the frame carried when the bar was built against it; still well clear of
     --field-fill above it. */
  --buy-bar-ground: #111111;
  /* The panel behind a popup -- the quick view and the write-a-review modal,
     which are the same colour. Its own token rather than --surface, which
     they used to share with the cart drawer, the filter sidebar and a dozen
     form fields.

     The quick view's controls used to be built as a veil laid over this value
     and so moved with it; they sit on --surface directly now, so this can
     move on its own -- but the two have to stay far enough apart that the
     controls still read as sitting ON the panel. */
  --modal-panel: #131313;
  /* The fill behind the buy bar's controls -- its colour selector, that
     selector's open menu, and the quantity pill. Well above the bar's own
     --buy-bar-ground, so they read as controls sitting on it rather than cut
     out of it.

     --surface, lowered from #252525 to match: the same fill the product
     column's controls and the quick view popup's carry, so a colour selector
     looks like itself in all three places. Kept as its own token because the
     bar is the one place where the fill has to clear the surface under it by
     a set amount -- if --surface ever moves, check it still does. */
  --field-fill: var(--surface);
  --border: #333332;
  --border-strong: #545352;

  /* The two topo strips: Free Membership on the home page, Get In Touch on
     the About page. --topo-ground is the flat colour the texture sits on;
     --topo-strength is how much of the texture shows through it. Those are
     the only two numbers -- see the topo block near the end of the file.

     With --topo-strength now at 1 the texture is opaque and this is what
     shows BEFORE it arrives rather than through it -- the colour the strip
     paints on first load. Set to the artwork's own ground so the swap is not
     a visible step as the image comes in; it has followed the artwork from
     #0a0a0a out to #151515 and back down to here, and the current topo meters
     neutral grey at about #0c0c0c. It matters again the moment the strength
     drops below 1. */
  --topo-ground: #0c0c0c;

  /* The Featured Collection section, and the band under the hero photo --
     .hero paints this in the strip below .hero-photo, so the section appears
     to run up underneath the photo rather than stopping at a bar of its own.

     --forest, the site's base dark. The About Us section directly below used
     to share it, so the two ran together as one field with only the ruled
     border between them. The stories have since been lifted to #1a1a1a, so
     that border now separates two tones rather than sitting inside one
     field.

     Its own token rather than a literal written twice, because the hero band
     has to follow it. Whatever this becomes, the band under the hero photo
     becomes too, or a bar appears between the hero and the section.

     One value not to give it: anything between #050505 and #101010. The
     product photos are shot on a dark surface that vignettes across exactly
     that range, so a ground inside it is lighter than part of every photo and
     darker than the rest -- which reads as a failed match rather than a
     choice. --forest clears the top of that range, black clears the bottom;
     both read as intentional, and the middle does not. */
  --featured-ground: var(--forest);
  /* 1, up from the 0.25 this sat at for years, and that is not a decision to
     show the texture more -- it is the current artwork's own strength, shown
     unmodified.

     The file this replaced was WHITE contour lines on black: measured across
     it the values ran the full 0-252, so it had to be held at a quarter or
     the lines would have been stripes of glare across the bottom of the page.
     The topo in use now is already finished and dark -- its lines peak around
     21 against a ground near 16, a range of about 10 where the old one had
     250. Dimming that a second time in CSS would put the lines a value or two
     off their own ground, and a parallax you cannot see is not one.

     Note this is the CEILING. The texture is now as strong as CSS can show
     it, so if it ever reads too faint the fix is a higher-contrast file, not
     a number here -- there is no headroom left in this direction. Lowering it
     still works: that blends the texture back toward --topo-ground below. */
  --topo-strength: 1;

  /* Where the header stops, in viewport terms -- the line every sliding sheet
     opens up to and every backdrop starts at. Written by initSheetTop in
     main.js on every scroll that moves it, because the header is sticky on a
     phone: at the top of a page its foot is below the promo bar, and once you
     have scrolled past that bar it is the header's own height and stays there.
     This value is the resting one, for the moment before the script runs. */
  --sheet-top: calc(var(--promo-h) + var(--header-h));

  /* Height of the promo bar above the header. A token because three other
     things are measured off it -- see the .promo-banner block for which. */
  --promo-h: 38px;
  /* Height of the header itself. A token for the same reason: the nav
     dropdown on a narrow window hangs from a fixed offset down the page and
     has to start exactly where the header ends. */
  --header-h: 96px;

  /* The darkening any full-screen viewer lays over the page behind it -- the
     stacked photo gallery and the quick view popup's backdrop. One token so
     opening either one dims the page by exactly the same amount. */
  --overlay-dim: rgba(0, 0, 0, 0.7);

  --text: #fbf7f3;          /* near-white, not true white */
  --text-muted: rgba(251, 247, 243, 0.72);
  --text-faint: rgba(251, 247, 243, 0.5);
  --title: #ececec;         /* headline color for h1/h2/h4 titles, light gray instead of near-white */

  /* Dark sections (header, hero, footer, subscribe) */
  --on-dark: var(--offwhite);
  --on-dark-muted: rgba(251, 247, 243, 0.72);
  --on-dark-faint: rgba(251, 247, 243, 0.5);
  --on-dark-border: #383737;
  --on-dark-border-strong: #5d5c5b;

  --accent: var(--rust);
  --accent-bright: var(--tan);

  --danger: #a1302a;

  /* Type

     One family across the whole site, which is what the home page had already
     gone to a rule at a time -- sixteen blocks in this file name Barlow
     Condensed outright. Both tokens now resolve to it, so the other ten pages
     follow in one move rather than sixteen more.

     The two tokens are kept apart even though they are equal. They are two
     ROLES -- the face headings and controls are set in, and the face copy is
     set in -- and every rule in this file asks for one or the other by role.
     Collapsing them into a single token would lose that, and the site has
     already run two families and may again.

     What this replaces: Koulen, an uppercase-only display face, and Lambda,
     the body serif. Both -face blocks and both files stay where they
     are, unused, exactly as Typewriter Condensed already was -- nothing now
     asks for any of the three.

     Read the note on letter-spacing before adjusting anything that looks
     wide: the 0.2em on .btn and the tracking on several headings were set for
     Koulen, which is narrow enough to need that much air. Barlow is narrower
     still in the letterform but spaces differently, and those numbers have
     not been swept. */
  --font-display: 'Barlow Condensed', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --font-body: 'Barlow Condensed', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --font-gold: var(--font-display);

  /* Spacing / radius */
  --radius-sm: 4px;
  /* ---- What every photo on the site fades up OUT OF ---------------------
     Seven rules carry the image entrance -- the global img rule, the product
     card wrapper, the gallery and popup slides, the Coming Soon plate, the
     split-media photos and the five page-header banners -- and all seven read
     this. It is a filter list rather than a colour because that is what those
     rules apply: brightness(0) renders any image as a flat rectangle, which
     is the "before" the entrance settles out of.

     The catch is that brightness(0) has exactly one endpoint and it is pure
     #000. The hero worked around that years-of-passes ago by painting
     #141414 on the layer BEHIND the photo and fading the photo up with
     opacity, which is how it reaches a colour a filter cannot -- see the note
     on .hero-slide-img. Everything else on the site still uses the filter,
     and on the dark bands a #000 rectangle sitting in a #141414 section reads
     as a hole in the page rather than as a photo about to arrive.

     invert() is what closes the gap. invert(a) mixes a colour with its own
     inverse, so on black it resolves to `a` flat: 0.0784 is 20/255, and 20 is
     #14. `brightness(0) invert(0.0784)` is therefore exactly #141414, with no
     second element and no ground to paint.

     The end state stays `brightness(1)`. A filter list interpolates against a
     shorter one by padding it with each function's identity value, so
     brightness(1) is read as `brightness(1) invert(0)` for the transition and
     both components animate together.

     Every width. It went in mobile-only, then more images were pointed at
     that were not on a phone -- which is the answer to the question the
     split was posing anyway: a photo arriving out of #000 on one screen and
     #141414 on another is not two decisions, it is one decision made twice.
     The media query that used to carry this is gone with it. */
  --reveal-from: brightness(0) invert(0.0784);
  --radius: 8px;
  --radius-lg: 16px;
  /* The home page's bands -- the two stories, the category tiles and Get In
     Touch. One value for the three, because they sit under each other down
     one page and a curve that differs between them reads as a mistake.

     6.4px: --radius-lg was the first try and was 60% too much on boxes this
     large. Written as its own token rather than as a calc off --radius-lg,
     so it can move without dragging the nine other places that use that. */
  --band-radius: 6.4px;
  /* The quick view popup's corner. It borrowed --band-radius for a moment,
     matching the home page's bands, and that curve is too tight for a panel
     this size -- 6.4px on a 1300px-wide sheet barely reads as a corner at
     all. 16px is --radius-lg, the widest curve already in the file, used on
     the About page's photo frames.

     Its own token rather than a reference to --radius-lg, because the two
     are not the same decision: this one follows the popup and that one
     follows a photograph. Turn this number to change the popup and nothing
     else -- all four rules that set the modal's corner read it. */
  --qv-radius: 10px;
  --container: 1500px;
  /* The page's own side margin -- the inset .container holds its content off
     the window edge with. Named because it is no longer only a horizontal
     measure: the home page's white run (Featured Collection, the two
     stories, the membership band) uses it vertically as well, so the space
     between those sections is the same as the space beside them and the
     whole block reads as one gutter carried round the page. */
  --page-gutter: clamp(16px, 4vw, 64px);
  /* The space between cards in a product grid. Named because the hero's
     white frame is set to the same thickness -- the two are meant to be the
     same measure, and a shared value is what keeps them so if either moves. */
  --grid-gap: 18px;
  /* The ground the home page's panels share: the two story copy panels, the
     panel under every product card, and the Free Membership band. It was
     written out as a literal in all three places, which is three places to
     miss when it moves -- and it has moved a dozen times now. One value.

     A gradient again, after a run of flat colours: #f6f6f6 / #c6c6c6 light,
     twice darker, inverted to #434646 / #141414, turned to run from the top
     right off a warm #6e6060, then flat through #544144, #3f4345, #2f322d,
     #777a73, #3f4541, #515c7b and #394c58, then vertical fades in grey and in plum.

     This one runs vertically -- `to bottom`, so #4c3e3b is the head and it
     fades down to #261f1e -- the same colour at half the value, channel for
     channel: 76/62/59 becomes 38/31/30. One hue, one fade, no drift toward
     a second colour on the way down. Vertical, not the 135deg or 225deg diagonals it
     used before, and the difference matters on these surfaces: a diagonal
     puts its light corner somewhere different on a tall story panel than on
     a short category tile, so three panels of different proportions never
     agreed. A vertical fade lands the same way on all of them -- lighter at
     the head, dark at the foot, whatever the height. Downward, which behaves
     like light falling on the panel rather than rising out of it.

     Both stops are a cool slate: blue and green level with each other, red
     nine under them. Enough to read as cool without reading as blue. The
     pairing before it leaned the other way -- an olive grey -- so this is the
     same idea with the temperature reversed.

     Lighter than the grey it replaces, and worth watching: white on this head
     is about 5.9:1 where that gave 7.7:1. Still clear of the 4.5:1 normal
     text needs and the foot is far past it, so nothing on the panels has to
     change -- but this is the second-lightest ground these have taken, and
     the taupe two before it is the one that failed.

     It is also the closest any of these grounds has come to the promo bar's
     red -- it is that red taken most of the way to brown -- and the bar is
     set to the head stop, so the two are the same colour rather than two
     versions of one. Worth noting against the
     rest of the site rather than assuming it: --forest is the name of the
     dark ground everything else is built on, but its value is #131313, a
     neutral ink deliberately softened off the raw forest swatch so those
     sections read moody rather than saturated-green. So this does not match
     anything elsewhere -- it is the green the site's own token only claims
     in its name -- and it is the only hue on the page besides the promo
     bar's red.

     White type clears at about 5.9:1 at the light end and about 12:1 at the
     dark, so nothing on the panels needs a weight or a size changed.

     Still named a ground though it is a gradient again: it is what the three
     surfaces are painted in, flat or not, and renaming it every time the
     value changes shape would churn the call sites for nothing.

     Two of the three references are in the desktop home blocks; the third,
     .category-info, is a base rule, so the phone gets this too -- the
     category tiles are new content rather than a desktop repaint of
     something it already had. */
  /* The two stops, named, because three other things in this file need to
     know one or the other of them and none of them can read a stop out of a
     gradient: the promo bar is the head as a flat fill, and the About page's
     scroll fade has to end on the foot so the copy dissolves into the panel
     rather than into a colour that is not behind it.

     The bar was a hand-typed literal for a long time and went stale between
     edits more than once. It is a token reference now, so a colour change is
     these two lines and nothing else. */
  /* The light pages' ground. Six surfaces were painting #ffffff literally --
     four page bodies, two sections -- plus the two panels the quick view is
     built from, and a colour used in eight places by eight separate rules is
     one that will go stale in some of them. One value now.

     White. It was #efeff1 for a moment -- a hair off white and a hair cool
     with it -- and is back to where it was. The token stays: it is not the
     colour, it is the eight rules no longer each carrying their own copy of
     it, and the next time this moves it is one line. */
  --page-ground: #ffffff;
  /* ---- The three-colour drop --------------------------------------------
     Colours arrive three at a time and fill four slots, always the same
     four. Written down so the next set goes to the same places without
     being asked:

       1. the gradient   -- --panel-head here, with --panel-foot at half its
                            value; drives the story panels, the category
                            tiles and the Free Membership band
       2. the buttons    -- all four of them, one fill: the hero, Contact Us
                            and the two story buttons
       3. the accent     -- the story eyebrows (ABOUT US, USA SOURCED &
                            SEWN) and the drop-in underline under a link,
                            in the header nav and in the footer columns

     Which colour goes to which slot is decided by the colours, not by the
     order they arrive in:

       - the BUTTONS take the most saturated, and their label is white. That
         is fixed and it is decided first.
       - the GRADIENT takes the darkest of what is left.
       - the ACCENT takes the lightest.

     A drop can also name its own assignment, and when it does that wins over
     all of the above. It can carry FOUR colours rather than three, in which
     case the accent's two uses part company -- the story eyebrows take one
     and the header and footer underlines take another. They are separate
     declarations and always have been; sharing a value was a convention, not
     a constraint.

     Saturation here means chroma -- how far the three channels spread --
     rather than HSL's S, which reads a pale wash as highly saturated because
     it divides by how close the colour is to white. By RGB spread this set
     is 60, 49, 31 and by HSL it would be 0.38, 0.31, 0.24, which name
     different winners; the spread is the one that matches what the eye
     calls vivid.

     Two tensions to watch, and a set will usually hit one of them.

     The first: the most saturated colour is often also the darkest, and the
     gradient is the slot that most needs a dark one -- it carries white type
     across a whole panel and its fade needs somewhere dark to fall to. When
     the button takes the dark one the panels get a light ground and their
     white type fails.

     The second: the ACCENT has two uses on very different grounds. As the
     underline it is a 1px rule on near-black chrome, which needs 3:1. As the
     story eyebrow it is small text on --panel-head, which needs 4.5:1 and is
     a much lighter ground. A mid-value accent clears the first and fails the
     second, and that happens whenever the accent and the panel are close in
     value -- which is to say, whenever all three colours in a set come from
     the same narrow band.

     Both are avoided the same way: send three colours that are genuinely far
     apart in value, not three shades of one. A vivid one for the buttons, a
     dark one for the panels, and a light one for the accent. */
  --panel-head: #3d3d3d;
  /* Half of --panel-head on every channel, rounded up, which is the ratio
     this gradient has held through every colour it has been given: 61/61/61
     to 31/31/31. Not a look decided per colour -- it is what keeps the run's
     depth constant while its hue moves.

     This one has no hue at all. #332a29 before it was a warm grey, ten levels
     of red across the span; this is three equal channels, the first fully
     neutral gradient the panels have carried. It is also the popup's
     controls' value and the store cards' Quick Add fill, so the same grey now
     appears as a panel, as a control and as a button on three different
     pages.

     It is LIGHTER than anything before it -- 7.2:1 to white at the head where
     #332a29 read 14.0:1, and 16.5:1 at the foot. Still far past the 4.5:1
     white type needs, but half the room it had, and the loss lands on the
     things that have to separate FROM the panel rather than sit on it. See
     the hero-button note for the one that no longer does. */
  --panel-foot: #1f1f1f;
  /* The promo bar, and with it the strip of canvas above the page -- see the
     `html` rule and .promo-banner, the only two things that read this.

     Its own token as of now. It was --panel-head, and that was honest while
     the two were meant to be one colour: the bar sat directly above a page
     whose panels ran from the same value, so the page appeared to come out
     from under it. They have been given different colours, so a token that
     said "the bar is the gradient's head stop" would now be saying something
     false in two places at once.

     The canvas has to follow the BAR and not the gradient. That strip is only
     ever seen at the top of a fast scroll that overshoots, where the thing
     directly under it is the bar -- which is the whole reason it stopped
     being white. Reading --panel-head there would put a lighter warm brown
     above a dark red bar, which is the same visible seam in a new colour.

     Red again, and the site's own: var(--btn-red), #b02b25. That is the fill
     on Shop The Store in the hero, on both story buttons and on Contact Us,
     so the bar is now the same red as every button on the page rather than a
     red of its own -- which is the point of taking the token instead of the
     hex. The bar has been through a 12% red wash, a full-strength brand red,
     a run of browns, and #3d3d3d gray to get back here.

     White on it measures 6.5:1, comfortably past the 4.5:1 body text wants,
     so the #ffffff on .promo-banner needs no revisiting. */
  --promo-ground: var(--btn-red);
  --panel-ground: linear-gradient(to bottom,
                                  var(--panel-head) 0%,
                                  var(--panel-foot) 100%);
  /* The depth of the home page's four bands -- the two stories, the category
     grid and Get In Touch. They were three different heights, each set by
     whatever was inside it: the stories by a 16:10 photograph in the wider of
     two grid columns, the grid by a square plate plus however deep a panel of
     copy came out, Get In Touch by its own padding. One value now drives all
     four, so they read as one run down the page.

     Where the number comes from -- it is the story bands' own height, kept
     because that was the proportion the page was designed around:

       columns    minmax(0, 1.15fr) minmax(0, 2fr), gap 0
       so photo   2 / 3.15 of the container's content width
       at 16:10   x 10 / 16 of its own width
       together   2 / 3.15 x 0.625 = 0.396825

     Measured on .container's content box -- the page measure less its two
     gutters -- not on the sections themselves. Get In Touch is inset by
     margins rather than by a container, so past 1500px it runs wider than the
     stories do; the min() is what keeps every band the same height anyway.

     100vw includes the scrollbar where the platform draws one over the page,
     so with classic scrollbars this can be a few pixels tall below 1500px.
     macOS overlay scrollbars measure zero and it is exact there.

     Only the desktop blocks read it. */
  --band-h: calc((min(100vw, var(--container)) - 2 * var(--page-gutter)) * 0.396825);
  /* The two story bands, a fifth deeper than the other two -- they were a
     third deeper a moment ago and this is a step back from that. These are
     the bands carrying a photograph AND a paragraph side by side, which is
     why they take more than the rest.

     The photo's proportion goes with the height, since it fills the row, so
     the ratio is arithmetic rather than a second decision:

       media column   2 / 3.15 of the container   = 0.63492
       height         0.396825 x 6 / 5            = 0.47619
       ratio          0.63492 / 0.47619           = 1.3333, i.e. 4:3

     The two move together in one direction -- a deeper band is a squarer
     photo -- and 1:1 is where the pictures stop being wide at all. That is
     the limit on how far these can go. */
  --band-h-story: calc(var(--band-h) * 6 / 5);

  --shadow: 0 20px 44px rgba(0, 0, 0, 0.16);
}

/* ---- Reset ---- */
*, *::before, *::after { box-sizing: border-box; }
/* The background on the ROOT, which is what fills the canvas -- including
   the strip revealed above the page when a fast scroll overshoots the top
   and rubber-bands.

   --promo-ground, the promo bar's own fill, so the page appears to come out
   from under the bar rather than off the top of a separate sheet. It read
   --panel-head until the bar and the gradient were given different colours;
   what this strip has to match is the BAR, which is the only thing it is
   ever seen against.

   Which makes this rule look, at the moment, like it does nothing. The bar is
   white and the strip is white, and the strip was white to begin with -- with
   no background declared here the body's propagates to the canvas, and on
   the light pages the body is #ffffff. The declaration is still doing the
   work: it is white BECAUSE the bar is, not because nothing was set, and it
   follows the bar the moment that moves again. Deleting it would look
   identical today and be wrong on the next colour drop.

   Setting this changes where the body's background paints -- it stops
   propagating and covers the body box only. That box is the full document,
   so nothing on any page looks different; only the two overscroll strips do.

   The one at the BOTTOM is handled separately, on .site-footer, because a
   root background can only be one colour and the bar's fill under the footer
   would be as wrong there as an unrelated colour is here. */
html {
  scroll-behavior: smooth;
  font-size: 18px;
  background: var(--promo-ground);
}
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.8;
  letter-spacing: 0.0375em;
  -webkit-font-smoothing: antialiased;
}
img, svg { display: block; max-width: 100%; }
/* Every photo on the site settles in the same way the hero does: starts at
   1.28 and shrinks to its resting size over 2.7s, with no fade -- images are at
   full opacity from the first frame. Their containers clip the overflow.

   This rule covers the static images in the markup. Generated product images
   carry .product-media-img, which opts out of it (see that rule) -- those
   animate through their wrapper instead, which is what keeps the product
   page's thumbnails and the stacked popup still. */
img {
  transform: scale(1.28);
  filter: var(--reveal-from);
  transition: transform 2.7s cubic-bezier(0.16, 1, 0.3, 1), filter 2.7s cubic-bezier(0.16, 1, 0.3, 1);
}
img.is-visible { transform: scale(1); filter: brightness(1); }
a { color: inherit; text-decoration: none; }
ul { list-style: none; margin: 0; padding: 0; }
button { font: inherit; cursor: pointer; }
input, textarea, select { font: inherit; }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  letter-spacing: 0.0375em;
  /* Koulen ships a single weight, so asking for 700 only got the browser's
     synthetic bold -- thicker, muddier strokes. 400 is the face as drawn. */
  font-weight: 400;
  margin: 0 0 0.5em;
  line-height: 1.3;
}
h1, h2, h4 { color: var(--title); }
h1 { font-size: clamp(2.5rem, 4.5vw, 3.75rem); }
h2 { font-size: clamp(1.75rem, 3.2vw, 2.3rem); }
h3 { font-size: 1.25rem; font-weight: 400; }
p { margin: 0 0 1em; color: var(--text-muted); font-size: 1.0625rem; }

.container {
  width: 100%;
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 var(--page-gutter);
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-gold);
  letter-spacing: 0.18em;
  font-size: 0.9375rem;
  font-weight: 600;
  text-transform: uppercase;
  color: var(--title-red);
  margin-bottom: 14px;
}
/* The 22px rule that used to sit before the label is gone. It was a ::before
   here -- content: '' at 22x2 in currentColor -- and it came off on mobile
   first and now at every width, which is why the rule is deleted rather than
   turned off: `content: none` in a media query was the mobile-only answer,
   and keeping it as a base rule plus an override to cancel it would leave two
   places to read to find out that nothing is drawn.

   .eyebrow keeps `display: inline-flex` and its 8px gap. Both are inert with
   a single text child -- a gap needs two items to sit between -- and they are
   what the mark needs back if the rule ever returns. */
/* On dark sections (hero, subscribe), lighten the eyebrow for contrast */
.hero .eyebrow { color: var(--title-red); }
.eyebrow-lg::before { width: 28px; }

/* ---- Buttons ----
   Standard, no-gimmick pattern: solid buttons darken slightly on hover,
   outline buttons fill solid on hover. Simple color/background transitions
   only — nothing sliding, growing, or animating. */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 14px 28px;
  border-radius: 999px;
  border: none;
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.2em;
  font-size: 0.9375rem;
  font-weight: 400;
  white-space: nowrap;
  transition: background-color 0.2s ease, color 0.2s ease;
}

.btn-ghost { padding: 10px 16px; }

.btn-block { width: 100%; }
.btn-sm { padding: 10px 18px; font-size: 0.75rem; }

/* Wood-sign image buttons — still just `.btn` underneath, so the lantern
   glow (::after) and light-cast shadow (:hover box-shadow) rules above keep
   working unchanged; this just strips the padding/fill/border so the PNG
   (with its own baked-in shape) is all that shows. */
.hero-sign-btn {
  padding: 0;
  background: transparent;
  border: none;
  border-radius: 10px;
  overflow: hidden;
  width: clamp(150px, 16vw, 210px);
  cursor: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 24 24'><defs><mask id='fl2'><rect x='0' y='0' width='24' height='24' fill='white'/><rect x='10.4' y='17' width='3.2' height='3.2' rx='0.3' fill='black'/></mask></defs><g transform='rotate(-45 12 12)'><line x1='12' y1='1.3' x2='12' y2='6.7' stroke='white' stroke-width='1.7' stroke-linecap='round'/><line x1='6.8' y1='3' x2='9.4' y2='6.1' stroke='white' stroke-width='1.7' stroke-linecap='round'/><line x1='17.2' y1='3' x2='14.6' y2='6.1' stroke='white' stroke-width='1.7' stroke-linecap='round'/><g mask='url(%23fl2)'><path d='M8.2 9 L15.8 9 Q16.6 9 16.3 10 L14.7 14.2 Q14.5 15 13.5 15 L10.5 15 Q9.5 15 9.3 14.2 L7.7 10 Q7.4 9 8.2 9 Z' fill='white'/><rect x='9.3' y='14.5' width='5.4' height='8' rx='2.3' fill='white'/></g></g></svg>") 10 10, pointer;
}
.hero-sign-btn img {
  width: 100%;
  height: auto;
  display: block;
}
.hero-sign-text {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 10%;
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--on-dark);
  font-size: clamp(0.8rem, 1.7vw, 1.05rem);
  line-height: 1;
  text-align: center;
  text-shadow: 0 2px 5px rgba(0, 0, 0, 0.55);
  pointer-events: none;
}

.btn:active { filter: brightness(0.92); }

/* ---- Header ---- */
/* ---- Promo banner -------------------------------------------------------
   A thin red bar above the header on every page, showing one message at a
   time: the current one leaves to the left as the next arrives from the
   right, with dots at the right edge for how many there are and which is up.

   The messages are data (PROMO_MESSAGES in main.js) and the slides and dots
   are built from them, so the pages carry an empty [data-promo-banner] and
   the copy lives in one place instead of eleven.

   Its height is a token because three other things are measured off it: the
   home page's header floats over the hero and has to start below the bar,
   the hero's own 100vh has to lose it, and the narrow-desktop nav dropdown
   hangs from a fixed offset down the page. */
.promo-banner {
  position: relative;
  z-index: 101;
  height: var(--promo-h);
  /* Its own token now rather than the Add to Cart buttons' resting fill --
     see --promo-wash for why the two came apart. border-box sizing means the
     line below comes out of --promo-h rather than adding to it, so the bar is
     the same height it always was and nothing measured off that token
     moves. */
  /* Flat, and red again -- a dark one. It was --promo-wash, a 12% red over
     the page, deliberately low so a standing bar sat under the page rather
     than on it, with a solid --btn-red hairline under it doing the actual
     dividing. Then the brand red at full strength, where the bar IS the
     divider, which is why the line went with it: a second red edge under a
     red band is a line drawn on top of itself. Then a run of browns and
     greys, following the gradient. That reasoning still holds at every one
     of them, and holds hardest here, where the bar is a red once more.

     --promo-ground, and a flat one: the whole gradient was here for a moment
     and a fade compressed into 38px reads as a bar that has not finished
     loading rather than as a lit surface -- the panels have the height to
     carry it and this does not.

     It read --panel-head for a long stretch, back when the bar was meant to
     be the gradient's own head stop. It is a colour of its own now, so it has
     a token of its own -- shared with nothing except the canvas strip above
     the page, which has to match the bar rather than the panels.

     Site-wide, not scoped to a page. The bar is shared chrome and has always
     changed as one thing; the nine pages still dark carry it as easily as
     they carried the red.

     border-box sizing means the missing border does not change --promo-h, so
     the header offset, the hero's 100vh and the narrow-desktop dropdown that
     all measure off that token stay exactly where they are. */
  background: var(--promo-ground);
  border-bottom: none;
  /* White on #3d3d3d: 7.16:1, against the 4.5:1 small text wants.

     Dark again, after a white bar and a light-gray one -- and dark in the
     panels' own value, since #3d3d3d is --panel-head. The bar and the page's
     panels have been one colour before and been split apart since; this is
     them landing on the same value again without the token being re-joined,
     which is the arrangement that lets either move alone.


     ---- The list this finally settles

     Every ground the bar has been given, metered against the WHITE type it
     carried until now: 15.6:1 on #3a110c, 12.1:1 on #552211, 8.3:1 on
     #684038 and again on #79352f, 8.1:1 on #7d3733, 7.8:1 on #524e41, 7.2:1
     on #56543b, 5.9:1 on the brand red, 5.5:1 on #7e5e3e and on #5d6668,
     4.0:1 on #797f51, 3.8:1 on #887b7b, 3.6:1 on #8b8074, 3.5:1 on #81886d
     and on #7a877b, 3.4:1 on #838886, 2.6:1 on #959d9a, 2.1:1 on #f8973a
     and on #acb0a8, 1.6:1 on #cfc2af, 1.5:1 on #e6c883, 1.2:1 on #d0e6c5.

     Every dark ground cleared 4.5:1 and every light one failed it. #f8973a
     is a saturated orange and #acb0a8 a desaturated sage; they have no hue
     in common and they metered within half a percent of each other, which is
     the clearest line in the list: lightness was the whole of it, and hue
     never entered into it once.

     Which is why a white bar is not the twenty-second entry in that list. It
     is the first one where the OTHER half moved. The type had been the one
     fixed thing for twenty-one grounds -- the fill was the entire variable,
     so a light fill could only ever fail. Turn the type over with it and the
     same reasoning that made every dark ground work runs in reverse and
     lands further from the line than any of them did.

     The bar has no fade to fall into, which is why it has always been the
     strictest surface on the site: the panels' type slides down into darker
     ground as the gradient falls, and 38px of flat colour is all there is
     here, so the fill is the whole of the contrast. That is still true. It
     just stopped being a constraint.

     The pair moves together -- change the bar's fill and this is the other
     half of it. */
  color: #ffffff;
  overflow: hidden;
  /* Set by JS as the messages move: 1 running forwards, -1 stepping back
     through the dots. The parked and leaving positions below are written as
     multiples of it, so one sign flip mirrors the whole animation. */
  --promo-dir: 1;
  /* How far the dots sit in from the container's own edge, so they clear it
     rather than running up against it. */
  --promo-dots-inset: 14px;
  /* Space the message may not run into: the dots' own width (about 42px at
     their widest, with one stretched into a pill) plus that inset. Only the
     right end holds dots, but the left is kept clear to match it, so the
     message is centred in the bar rather than in what is left beside them.
     On a phone that symmetry costs more room than it is worth -- see the
     mobile block below. */
  --promo-dots-w: 56px;
  --promo-lead: var(--promo-dots-w);
}
.promo-banner-inner { position: relative; height: 100%; }
/* The two gutters exist to keep the message centred in the BAR rather than in
   what is left beside the dots -- see --promo-lead, which is deliberately the
   dots' own width. With a single message initPromoBanner renders no dots at
   all, and 112px of the bar was being held for something that is not there.

   :has() is what lets CSS see that. The dots container is only written into
   the markup when there is more than one message, so its absence IS the
   condition, and this reads it directly rather than needing the script to
   stamp a class for it. The file uses :has() elsewhere (.section-alt has one
   up at the subscribe block), so nothing new is being relied on.

   Add a second message and the gutters come back on their own. */
.promo-banner:not(:has(.promo-banner-dots)) {
  --promo-dots-w: 0px;
  --promo-lead: 0px;
}
/* The window the messages slide through: the full width of the bar, with the
   reserved space taken as padding on the slides inside it instead of as an
   inset here. Either way the message centres in the same place and a slide's
   own 100% is exactly the distance it has to travel to clear the window --
   padding keeps that true while leaving the window itself the width of the
   bar, so nothing painted on a slide can be cut off short of the page edge.
   Its overflow is what clips the messages on the way through. */
.promo-banner-viewport {
  position: absolute;
  inset: 0;
  overflow: hidden;
}
.promo-banner-slide {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  /* The space the message may not run into: the dots' own width on the right,
     matched on the left so the message is centred in the BAR rather than in
     what is left beside them. justify-content centres within the content box,
     so the words land inside these gutters. */
  padding-left: var(--promo-lead);
  padding-right: var(--promo-dots-w);
  font-family: var(--font-body);
  /* Up from 0.78rem. There is room: the bar is --promo-h (38px) and the line
     box at this size and leading comes to about 22px, so it still sits
     clear of both edges. One message rather than three is what allows it --
     a rotating bar has to hold the longest of them on one line, and this
     only has to hold its own.

     Italic, and a real one: Lambda registers an italic face at the top of
     this file, so nothing here is a browser-slanted upright. */
  /* Fluid, where this was a flat 0.95rem. The message it was set for was
     'STORE LAUNCHING SOON!' -- 21 characters, which fits across a 900px
     window at that size with room to spare. The one in the bar now is four
     times that, and at 17px it sets to about 700px: fine at 1440, and wider
     than the space left between the two gutters somewhere around 880.

     The clamp is what keeps it on ONE line for the whole desktop range
     instead of wrapping into a bar that is 38px deep and has nowhere to put
     a second. 1.3vw reaches the 0.95rem ceiling at about 1270px and holds
     there; the 0.68rem floor is reached around 940 and carries the rest of
     the way to the breakpoint, where the line still measures some 200px
     inside what is available. Below 860 it wraps on purpose -- see the block
     down the file that gives it the depth to. */
  font-size: clamp(0.68rem, 1.3vw, 0.95rem);
  font-style: italic;
  line-height: 1.3;
  /* 0.05em, up from 0.02. Tracking rather than size: the bar is 38px and the
     line box at this size already comes to about 22px, so there is not much
     room to grow the type without crowding the edges -- but there is room
     either side of a centred line, which is where opening the letters puts
     the extra width. An italic at a small size takes tracking well; the slope
     already opens the spacing on one side of each letter and this evens it
     out. */
  letter-spacing: 0.05em;
  text-align: center;
  /* Parked off whichever side it will enter from. Deliberately no transition
     in this state: only the two moving states carry one, so a slide waiting
     its turn changes sides the instant --promo-dir flips instead of being
     dragged across the bar to get there. */
  transform: translateX(calc(100% * var(--promo-dir)));
}
.promo-banner-slide.is-active { transform: translateX(0); }
.promo-banner-slide.is-leaving { transform: translateX(calc(-100% * var(--promo-dir))); }
.promo-banner-slide.is-active,
.promo-banner-slide.is-leaving { transition: transform 0.45s cubic-bezier(0.65, 0, 0.35, 1); }
@media (prefers-reduced-motion: reduce) {
  .promo-banner-slide.is-active,
  .promo-banner-slide.is-leaving { transition: none; }
}
/* The link is words inside the sentence, not a separate item after it, so
   the italic is what marks it as one -- there is a real Lambda italic face,
   so this is a drawn italic rather than a synthesised slant. inline-block
   and nowrap so the phrase travels as a unit: the underline below is
   positioned against this box, and an inline box broken across two lines
   would leave it measuring only the first fragment. Same underline as the
   main nav's -- it fades in while sliding down from behind the text, and
   lifts back up on the way out. */
.promo-banner-link {
  position: relative;
  display: inline-block;
  color: #ffffff;
  font-style: italic;
  text-decoration: none;
  white-space: nowrap;
}
.promo-banner-link::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -3px;
  height: 1px;
  background: currentColor;
  opacity: 0;
  transform: translateY(-6px);
  transition: transform 0.3s ease, opacity 0.3s ease;
}
.promo-banner-link:hover::after { opacity: 1; transform: translateY(0); }
.promo-banner-dots {
  position: absolute;
  right: var(--promo-dots-inset);
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  gap: 7px;
  z-index: 2;
}
.promo-banner-dot {
  position: relative;
  width: 6px;
  height: 6px;
  padding: 0;
  border: none;
  border-radius: 999px;
  /* Held back from the message beside them, which is full white: the dots are
     an indicator, not something to read. The active one goes to solid below,
     so weight marks it as well as its stretch.

     0.6, not the 0.45 this carried on every previous ground. That value
     worked on a near-black bar and on a white one, because in both cases the
     dot at 0.45 still had most of the distance to the ground left; #3d3d3d is
     a MID grey and there is much less of that distance to spend. At 0.45 the
     dots meter 2.36:1, under the 3:1 a non-text indicator wants; 0.6 brings
     them to 3.34:1. The alpha is not a constant -- it is a fraction of
     whatever room the ground leaves. */
  background: rgba(255, 255, 255, 0.6);
  transition: width 0.25s ease, background 0.25s ease;
}
/* A 6px target is far too small to tap. The dot stays 6px; this is the part
   your finger actually has to land on. */
.promo-banner-dot::after {
  content: '';
  position: absolute;
  inset: -14px -4px;
}
.promo-banner-dot.is-active { width: 16px; background: #ffffff; }
/* On a phone the bar is two lines deep rather than one: the longest message
   will not fit across a narrow screen at a readable size, so it is given the
   room to wrap instead of being shrunk until it does. The left gutter drops
   to a hair as well -- the message then centres in what is left beside the
   dots rather than in the bar, which buys back most of a line. Nothing at
   this width measures off --promo-h (the header below is sticky and in flow),
   so the taller bar costs nothing but its own height. */
@media (max-width: 860px) {
  /* On :root, not on .promo-banner: the hero and the header read this too,
     and neither is inside the bar for it to inherit down to. */
  :root { --promo-h: 48px; }
  .promo-banner {
    --promo-dots-inset: 8px;
    --promo-dots-w: 46px;
    --promo-lead: 8px;
  }
  .promo-banner-slide { font-size: 0.62rem; }
  .promo-banner-dots { gap: 6px; }
  .promo-banner-dot { width: 5px; height: 5px; }
  .promo-banner-dot.is-active { width: 14px; }
}
/* Where the bar's Free Membership link lands. Cleared of the mobile header,
   which is sticky and would otherwise sit over the top of the column --
   html { scroll-behavior: smooth } does the travelling, from this page or
   from any other. */
#free-membership { scroll-margin-top: 86px; }

.site-header {
  position: relative;
  z-index: 100;
  background: var(--chrome-ground);
  /* No rule under the bar, on any page. It separated dark chrome from a dark
     page, which was its whole job -- and two of the eleven pages are white
     under it now, where near-black meeting white is about as clear an edge as
     a seam gets and a hairline across it draws a line where there is already
     a wall. On the nine that are still dark it was doing little enough that
     losing it costs nothing, and one header everywhere is worth more than a
     line on some of them. */
  border-bottom: none;
}
/* Floats over the hero -- so it starts below the bar rather than at the top
   of the page, which is where top: 0 would put it (its containing block is
   the page, not the window). */
.page-home .site-header {
  position: absolute;
  top: var(--promo-h);
  left: 0;
  right: 0;
}
/* Phones: the bar holds the top of the screen and the page runs under it,
   frosted -- see-through and blurred -- the whole time, in both directions.

   It used to do this only while you were scrolling down, and go solid on the
   way back up. Two states meant the bar changed under you for a reason that
   had nothing to do with what was behind it, and the change was the only part
   you noticed. One state is quieter and there is nothing to keep in sync, so
   no JS runs on scroll for it any more.

   The transparency is in the background colour, NOT in `opacity` on the
   header. That distinction is the whole reason the blur is visible. With
   opacity on the element, the browser draws the header -- blurred backdrop
   included -- as one group and then composites that group over the ORIGINAL,
   unblurred page. The blurred copy is sealed behind the bar's own opaque
   ground where nothing can see it, and what comes through is the sharp page
   underneath: everything looks right except the one part that was asked for.
   A translucent ground leaves the header at full opacity, so what shows
   through it is the filtered backdrop and nothing else.

   0.72 rather than something lower on purpose. This is the only chrome on the
   screen and it is live -- the cart and the menu are there to be tapped -- so
   it has to read as a bar you could reach for. The blur is what keeps it
   readable at that alpha: what comes through is soft colour rather than
   legible words and edges competing with the mark and the icons over them.

   Prefixed as well as not: iOS Safari still wants -webkit-.

   Split into two blocks below, and the split is the point. The bar's POSITION
   is a width question -- a narrow window wants a sticky bar whatever is
   driving the pointer. Its transparency is not. A desktop window dragged
   narrow keeps the desktop header, and on the home page that header is
   absolutely positioned OVER the hero photograph: made see-through, it showed
   the photo through itself and came out a flat mid-gray, which is a bar that
   looks broken rather than one that looks frosted. So the frosting is gated
   on a touch pointer, which is the only place the bar is over the page's own
   ground rather than over a picture. */
@media (max-width: 860px) {
  .site-header, .page-home .site-header {
    position: sticky;
    top: 0;
  }
}
/* The bar was a different COLOUR on touch devices: --chrome-ground at 72%
   with a 10px backdrop blur under it, so the page showed through the header
   as you scrolled. On a computer the same bar is that colour at full
   strength and shows nothing through.

   Gone, at the asking -- mobile is to match desktop on colour. The whole
   block goes rather than being set back to opaque: a translucent fill and
   the blur that makes it legible are one decision, and leaving the blur on
   an opaque bar would cost a compositor layer to blur what nobody can see
   through.

   Nothing else in it moved. The bar is still sticky at these widths (that is
   the block just above, on width alone), still the same height, and the
   pointer query that scoped this is not used by anything else here.

   To put it back, this block is the whole of it. */
.header-inner {
  position: relative;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  height: var(--header-h);
}
/* ---- Which third of the bar each block sits in --------------------------
   The grid above is 1fr auto 1fr and the markup order is logo, nav, actions.
   The order asked for is nav, logo, actions -- so the first two swap columns
   here rather than in the HTML, because that markup is copied into eleven
   pages and this is one line.

   grid-column rather than the `order` property. Both would do it, but
   grid-column says which cell a thing is in, which is the thing being
   decided; order says where it comes in a sequence and leaves the reader to
   work the cell out. It also leaves the DOM sequence alone, which is what a
   keyboard walks: the logo is still the first thing you tab to, as it is on
   every site, even though it is now painted in the middle.

   The centre column is `auto`, so it is the logo that sets its width and the
   two 1fr columns split what is left. That is what centres the badge against
   the WINDOW rather than against the nav -- the flanking columns are equal
   whatever is in them. */
/* grid-ROW: 1 on all three, and it is not decoration.

   Auto-placement walks the items in DOM order -- logo, nav, actions -- and
   keeps a cursor as it goes. The logo is pinned to column 2, so it is placed
   there and the cursor ends up past column 2 of row 1. The nav then asks for
   column 1, which is BEHIND the cursor, and the default sparse packing will
   not go back for it: it starts a new row instead. So the bar came out as two
   rows -- the logo alone on the first, the nav and the icons together on the
   second -- inside an element with a fixed 82px height, which they then
   overflowed. The nav and the icons were sitting on the hero photograph
   below the black bar, and no amount of align-items on a two-row grid was
   going to centre them in it.

   Naming the row for every item is what makes the placement explicit rather
   than a consequence of source order. grid-auto-flow: column would also fix
   it, but it fixes it by describing the flow instead of the destination, and
   the destination is the thing being decided. */
.header-inner .main-nav { grid-row: 1; grid-column: 1; justify-self: start; }
.header-inner .logo { grid-row: 1; grid-column: 2; justify-self: center; }
.header-inner .header-actions { grid-row: 1; grid-column: 3; justify-self: end; }
.logo { display: inline-flex; align-items: center; gap: 10px; line-height: 0; }
/* ---- The circular badge -------------------------------------------------
   The mark carries its own wordmark around the rim, so the header's <a> holds
   one image and nothing else -- and .logo's 10px gap has nothing left to sit
   between, which costs nothing and is left alone.

   No sizing here on purpose. The badge kept the .logo-skull class precisely
   so the eleven heights written against it -- 82px bar, 66px bar, the
   scroll-shrink from 58 to 38 -- all keep working untouched, and a height of
   its own on this class would fight them at equal specificity depending only
   on which rule came later in the file. The one thing worth knowing is that
   it is SMALLER than the mark it replaced at the same height: the skull file
   is 987x720, so at 58px tall it was 79px wide, where a circle at 58px tall
   is 58px wide.

   The file is transparent outside the circle, so nothing here rounds it --
   what makes it a disc on the dark bar is the artwork, not a border-radius.
   The reveal opt-out it needs is already written, at .logo .logo-skull. */
.logo-skull { display: block; height: 40px; width: auto; }
.logo-wordmark { display: block; height: 24px; width: auto; }
@media (min-width: 860.02px) {
  .logo-skull { height: 58px; }
  .logo-wordmark { height: 34px; }
}
/* Text stand-in for the wordmark image, used while previewing a rebrand
   (RINGTAIL GEAR) — mirrors the .logo-wordmark image's sizing/breakpoints
   above so it drops in without other layout changes. Single-line version
   (quick view's mini brand label only). */
.logo-wordmark-text {
  font-family: var(--font-display);
  color: var(--on-dark);
  line-height: 1;
  letter-spacing: 0.02em;
  font-size: 19px;
  white-space: nowrap;
  transform: translateY(-3px);
}
@media (min-width: 860.02px) {
  .logo-wordmark-text { font-size: 27px; }
}
.qv-mini-logo.logo-wordmark-text { height: auto; font-size: 14px; }

/* The wordmark — header and footer only: RINGTAIL GEAR on one line, both
   words the same size, sitting beside the skull. Fades/scales in on scroll
   into view, same as the skull image next to it.

   This used to be two stacked lines: RINGTAIL over a smaller GEAR, flanked by
   rules that split the leftover width so the "— GEAR —" row came out the
   width of RINGTAIL above it. The markup still has that shape -- a primary
   span, then a row holding rule/GEAR/rule -- because it is duplicated across
   eleven pages' headers and the footer, and restyling it costs one block here
   instead of twenty-two edits. So the row is flattened rather than removed
   and the rules are hidden; see below.

   The size lives on this element, and both words inherit it. That is what
   keeps them equal: one value per context (17px, 24px on desktop, 27px in
   the footer) rather than a pair that has to be kept in step. */
.logo-wordmark-stack {
  display: flex;
  height: auto;
  flex-direction: row;
  align-items: center;
  /* A space between the words, in em so it tracks the size. */
  gap: 0.3em;
  font-size: 17px;
  /* The hero headline's tracking, not the nav's. Same typeface as HANDCRAFTED
     GEAR all along -- both are Koulen -- but at 0.2em the letters stood so far
     apart that the mark read as a different face entirely. At the headline's
     own 0.0375em the two are plainly the same lettering, which is what a
     wordmark and the page's biggest words ought to be.

     Declared as a variable because the rule under GEAR has to cancel exactly
     this much trailing space -- see .logo-wordmark-secondary. */
  --logo-track: 0.0375em;
  letter-spacing: var(--logo-track);
  /* --font-display, the same Koulen the nav carries. Both words inherit it,
     the same way they inherit the size and the tracking -- that shared
     inheritance is what keeps RINGTAIL and GEAR identical. */
  font-family: var(--font-display);
  font-weight: 400;
  color: var(--on-dark);
  opacity: 0;
  transform: translateY(-1px) scale(1.12);
  transition: transform 1.6s cubic-bezier(0.16, 1, 0.3, 1), opacity 1.6s ease;
}
.logo-wordmark-stack.is-visible { opacity: 1; transform: translateY(-1px) scale(1); }
.logo-wordmark-primary {
  /* Family, weight, size and tracking all come from the stack -- see there.
     Nothing about RINGTAIL differs from GEAR any more, so nothing is set
     twice. */
  line-height: 1;
  white-space: nowrap;
}
/* display: contents, so GEAR becomes a direct flex child of the stack above
   and sits in the row beside RINGTAIL. The wrapper stays in the markup and
   simply stops generating a box of its own -- which is what lets the eleven
   copies of this header keep the two-line structure they were written with. */
.logo-wordmark-secondary-row { display: contents; }
/* The two flanking rules belonged to the stacked layout, where they filled
   the width RINGTAIL left over. On one line there is nothing for them to
   fill. */
.logo-wordmark-rule { display: none; }
.logo-wordmark-secondary {
  /* As above: GEAR is the same wordmark as RINGTAIL now rather than a
     subtitle under it, so it takes everything from the stack. */
  line-height: 1;
  white-space: nowrap;
  /* Letter-spacing is applied AFTER every letter, the last one included, so
     the text box runs one space past the R and the rule under it would too.
     This takes that trailing space back off, and the line ends where the word
     does. Read from the same variable the tracking is set with, so the two
     cannot drift. Safe to pull the box in from the right because the logo is
     left-aligned in the header and centred as a unit in the footer. */
  margin-right: calc(var(--logo-track) * -1);
}
@media (min-width: 860.02px) {
  .logo-wordmark-stack { font-size: 24px; }
}
.footer-brand .logo-wordmark-stack { font-size: 27px; }
/* ---- Nothing renders .logo-wordmark-stack any more ----------------------
   RINGTAIL and GEAR were set as type in two places -- the header and the
   footer -- and both now carry the badge, which has the words in the
   artwork. So this rule and the .logo-wordmark-* block above it match
   nothing in any of the eleven pages.

   Kept rather than deleted, and this note is the price of keeping it: the
   block is the whole of how the wordmark was drawn -- the shared size that
   holds the two words equal, the tracking, the trailing-space correction
   under GEAR -- and it is a great deal easier to put the spans back into a
   header than to derive all of that again. Delete it the day the badge
   stops being the mark.

   Not to be confused with .logo-wordmark-TEXT, a few rules up, which is a
   different thing and is still live: the quick view's mini logo and the
   product page's buy bar both set RINGTAIL GEAR with it. */
/* Stacked in the footer, where the header has them side by side: there is
   height to spend down here and the brand block is the footer's own heading,
   so it reads better as a mark over a name. align-items centres the wordmark
   under the skull rather than lining their left edges up, which matters
   because the skull is much the narrower of the two. */
/* The column and the gap were what stacked the skull over the wordmark. The
   <a> holds one image now, so neither has anything to act on -- kept because
   they are the right rules for whatever goes in this slot, and removing them
   would only mean writing them again if a second element ever joins it. */
.footer-brand .logo {
  flex-direction: column;
  align-items: center;
  gap: 8px;
}
/* 152px, where the header's is 80 -- nearly double, and not an oversight.
   Up there the mark shares a fixed-depth bar with the nav and the icons and
   is bounded by it. Down here it is the footer's own heading with a column to
   itself and no ceiling over it, so the only thing setting the size is what
   the block should weigh on the page, and it is the last thing anyone sees
   before they leave.

   Scoped to .footer-brand, and it has to be: the generic .logo-skull heights
   up the file are what the header rides on, and the two contexts want very
   different numbers from the same class. */
.footer-brand .logo-badge { height: 152px; }

/* ---- Narrow phones: the header closes ranks in three steps ---------------
   RINGTAIL and GEAR sit on one line beside the skull, and both are nowrap, so
   the wordmark cannot shrink -- past some width it spills out of its own box
   and the search button ends up printed over GEAR. Three things happen on the
   way down, cheapest first, and the last one is the one that changes how the
   mark reads.

   1. The buttons close up. 18px between them is a desktop rhythm; 12 is
      plenty on a phone and it is 12px the logo does not have to give up.

   2. The mark comes down a size. The skull is the wide part -- about twice as
      wide as it is tall -- so a few pixels off its height is what actually
      gives the row room, and the wordmark follows so the two stay in
      proportion.

   3. Only then does the wordmark fold, RINGTAIL over GEAR -- how this markup
      was written in the first place, and how the footer still sets it.

   Where the fold lands is measured, not guessed: with the tracking the mark
   now carries the whole logo is 158px wide, so one line and the three buttons
   fit side by side with room to spare down to about 346px, and to 330 once
   the first two steps have been taken. Anything above that and the buttons
   simply slide right up to the wordmark, which is where they belong.

   Scoped to .header-inner, so the footer's own stacked mark is untouched. */
@media (max-width: 400px) {
  /* .header-inner scoping, not for the cascade's sake but for the file's:
     this block sits above .header-actions' own rule, and a bare selector
     here would lose to it on source order. */
  .header-inner .header-actions { gap: 12px; }
}
@media (max-width: 340px) {
  .header-inner .logo-skull { height: 50px; }
  .header-inner .logo-wordmark-stack { font-size: 15px; }
  .header-inner .header-actions { gap: 10px; }
}
@media (max-width: 330px) {
  .header-inner .logo-wordmark-stack {
    flex-direction: column;
    align-items: flex-start;
    /* Tighter than the word space this had on one line: stacked, that gap is
       leading between two lines rather than a space between two words. */
    gap: 0.08em;
  }
  /* The negative margin took the trailing letter-space off the end of the
     line. Stacked, GEAR is its own line and there is nothing to its right to
     pull it towards. */
  .header-inner .logo-wordmark-secondary { margin-right: 0; }
}

/* Desktop only -- see the collapse block below for where the mobile sheet
   takes over. Wider gaps than before, with room to spare now that the centre
   column holds four items instead of five. */
/* align-items: center, which the row did not have. .header-inner centres
   the nav as a BLOCK, but inside it the default is stretch: .nav-dropdown
   is the tallest item -- its trigger carries a 12px chevron beside the
   label -- so the three plain links were stretched to its height and their
   text sat at the top of a box taller than itself. HOME, ABOUT and CONTACT
   were riding a couple of pixels above STORE because of it. */
.main-nav { display: flex; align-items: center; gap: 52px; }
/* The Store trigger is a <span>, not an <a>: it opens the menu below it and
   goes nowhere itself, so it is not a link and does not claim to be one. It
   still has to LOOK like its neighbours -- the type ramp here, and the hover
   underline further down -- which is why it is named alongside them rather
   than left to pick up whatever a bare span gets. */
.main-nav a,
.main-nav .nav-dropdown-trigger {
  position: relative;
  /* The .btn type ramp, the same one the buttons, the two red text links and
     Sort By carry. Family, weight and uppercasing already matched; the size
     came down from 1.125rem and the tracking went up from 0.0375em, which is
     most of the change. The 52px gap above still holds -- the labels come out
     close to the width they were, narrower type at wider tracking. */
  font-family: var(--font-display);
  font-size: 0.9375rem;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  /* Muted at rest, full white on hover and on the current page -- the same
     two-tone treatment the rest of the site's links use. --on-dark-muted is
     the counterpart to the --on-dark below, so the pair moves together. */
  color: var(--on-dark-muted);
  padding: 8px 0;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.45), 0 1px 8px rgba(0, 0, 0, 0.3);
  transition: color 0.2s ease;
}
/* The line that drops in under a nav label on hover, and stays under the
   current page's.

   White, after a run of accent colours in this slot -- #5c8073, #b35754,
   #805149. Each of those was picked to make the mark that says WHERE YOU ARE
   a colour rather than more of the same white the label is already made of,
   and each one measured fine on its own: a 1px rule needs 3:1, not text's
   4.5:1, and on --chrome-ground all three cleared 4:1 comfortably.

   What white gives up is exactly that distinction: the line is now the same
   value as the label above it, so the underline reads as part of the word
   rather than as a separate mark. What it gains is 18.4:1 on near-black
   chrome -- the most a 1px line can have -- and one fewer colour on a page
   whose accents are down to the buttons.

   The footer's column links carry the identical line on .footer-col a::after
   and have followed this slot through every one of those changes. The two
   move together; they are one decision written in two places because the
   selectors have nothing in common. */
.main-nav a::after,
.main-nav .nav-dropdown-trigger::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: 6px;
  height: 1px;
  width: 100%;
  background: #ffffff;
  opacity: 0;
  transform: translateY(-10px);
  transition: transform 0.3s ease, opacity 0.3s ease;
}
/* White for the page you are on (.active, set in the markup on every page
   that has a nav entry), for the link under the cursor, and for the Store
   trigger while its own dropdown is open -- without that last one the label
   would drop back to gray the moment the pointer left it for the menu
   hanging off it, which is the one place the two are not the same thing. */
.main-nav a:hover,
.main-nav a.active,
.main-nav a[aria-expanded="true"],
.main-nav .nav-dropdown-trigger.active,
.main-nav .nav-dropdown:hover .nav-dropdown-trigger { color: var(--on-dark); }
/* The Store trigger takes the underline too, even though it is not a link and
   goes nowhere. It is one nav row and the labels in it should answer the
   pointer the same way; the trigger being a <span> is a semantic fact, not
   something the hover state needs to advertise.

   Keyed to the DROPDOWN's hover rather than the label's own, which is what
   the colour rule above does and for the same reason: crossing the gap from
   the label down into the open menu would otherwise drop the underline the
   moment the pointer left the text, and the label should stay lit for as long
   as the menu it opened is showing.

   .active is separate from all of that -- a you-are-here mark rather than a
   response to the pointer. Store carries it on the store and product pages. */
.main-nav a:hover::after,
.main-nav a.active::after,
.main-nav .nav-dropdown-trigger.active::after,
.main-nav .nav-dropdown:hover .nav-dropdown-trigger::after { opacity: 1; transform: translateY(0); }

/* ---- Store dropdown (hover-based) ---- */
.main-nav .nav-dropdown { position: relative; }
/* Default cursor, not the pointer an <a> would have given it and not the
   I-beam a bare span gets: nothing here is clickable or selectable-looking,
   the label just sits there and the menu comes down under it.

   tabindex on the span (see the markup) so it still takes focus. That is not
   decoration -- the menu is visibility: hidden until :focus-within, and a
   hidden element cannot be focused, so without something focusable at the top
   of the dropdown a keyboard tab would skip the Store categories entirely.
   Focusing the label opens the menu and the links inside become reachable. */
.main-nav .nav-dropdown-trigger { display: flex; align-items: center; gap: 6px; cursor: default; }
.main-nav .nav-dropdown-trigger svg { width: 12px; height: 12px; transition: transform 0.2s ease; }
.main-nav .nav-dropdown:hover .nav-dropdown-trigger svg,
.main-nav .nav-dropdown:focus-within .nav-dropdown-trigger svg { transform: rotate(180deg); }

/* invisible bridge so the pointer doesn't lose hover crossing the gap to the menu */
.main-nav .nav-dropdown::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  height: 14px;
}

.nav-dropdown-menu {
  position: absolute;
  top: 100%;
  left: 0;
  /* The header's own colour, not the page's. This hangs off the bar and reads
     as part of it, so it follows --chrome-ground wherever that goes rather
     than staying on --forest and showing a seam at the join. */
  background: var(--chrome-ground);
  border-radius: 0 0 var(--radius) var(--radius);
  padding: 8px;
  min-width: 190px;
  box-shadow: var(--shadow);
  z-index: 150;
  transform-origin: top;
  transform: scaleY(0);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s ease, visibility 0.28s;
}
.main-nav .nav-dropdown:hover .nav-dropdown-menu,
.main-nav .nav-dropdown:focus-within .nav-dropdown-menu {
  transform: scaleY(1);
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}
.nav-dropdown-menu a {
  display: block;
  padding: 10px 14px;
  border-radius: var(--radius-sm);
  font-family: var(--font-body);
  text-transform: none;
  letter-spacing: normal;
  font-size: 0.88rem;
  color: var(--on-dark-muted);
  text-shadow: none;
  border-bottom: none;
  transition: color 0.2s ease;
}
.nav-dropdown-menu a:hover { background: transparent; color: var(--offwhite); }

.header-actions { display: flex; align-items: center; gap: 18px; }
.icon-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  color: var(--on-dark);
  border: 1.3px solid var(--on-dark-border-strong);
  border-radius: var(--radius-sm);
  background: transparent;
  transition: border-color 0.2s ease;
}
/* Pointer devices only. A tap on a touch screen leaves :hover stuck on the
   thing you tapped until you touch something else, so on a phone these lit up
   white and stayed that way -- a button reporting a pointer that is not there.
   Nothing is lost: there is no hover on a phone to show. */
@media (hover: hover) and (pointer: fine) {
  .icon-btn:hover { border-color: var(--on-dark); }
}
.icon-btn svg { width: 18px; height: 18px; }
.cart-count {
  position: absolute;
  top: -7px;
  right: -7px;
  background: var(--title-red);
  color: var(--offwhite);
  font-size: 0.65rem;
  font-weight: 700;
  min-width: 18px;
  height: 18px;
  border-radius: 999px;
  /* Hidden until updateCartCount() has run and found something in the cart.
     The badge ships in the markup reading "0", so painting it by default meant
     a red 0 flashed over the cart icon on every load before the script hid it.
     updateCartCount sets display:flex inline when the count is above zero. */
  display: none;
  align-items: center;
  justify-content: center;
  padding: 0 4px;
}

.nav-toggle {
  display: none;
  width: 40px;
  height: 40px;
  border: 1.3px solid var(--on-dark-border-strong);
  border-radius: var(--radius-sm);
  background: transparent;
  align-items: center;
  justify-content: center;
  color: var(--on-dark);
  transition: border-color 0.2s ease;
}
/* White on hover, and on hover only -- the same edge the search and cart
   buttons beside it take, and pointer-gated for the same reason they are.
   Being open does not hold it lit either: press it, move away, and it goes
   back to gray with the X still showing. The icon is what says the menu is
   open; the border only ever says where the pointer is. */
@media (hover: hover) and (pointer: fine) {
  .nav-toggle:hover { border-color: var(--on-dark); }
}
.nav-toggle svg { width: 20px; height: 20px; flex-shrink: 0; }

/* ---- The three bars fold into an X -------------------------------------
   Driven off aria-expanded, which the toggle already carried for screen
   readers -- so the state has one home and the icon cannot disagree with it.

   transform-box: view-box puts each bar's origin in the icon's own 24-unit
   grid, which is why the origins below can be written as the coordinates the
   `d` attributes already use. fill-box would be the tidier-looking choice and
   is the wrong one here: a horizontal line has a zero-height bounding box,
   and browsers have not always agreed on where the centre of nothing is.

   Each outer bar turns about its own centre and then travels to the middle:
   7 + 5 and 17 - 5 both land on 12. The middle bar just goes. */
.nav-toggle-bar {
  transform-box: view-box;
  transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.16s ease;
}
.nav-toggle-bar-top { transform-origin: 12px 7px; }
.nav-toggle-bar-mid { transform-origin: 12px 12px; }
.nav-toggle-bar-bot { transform-origin: 12px 17px; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bar-top { transform: translateY(5px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle-bar-mid { opacity: 0; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bar-bot { transform: translateY(-5px) rotate(-45deg); }

/* ---- Where the menu collapses ------------------------------------------
   The header is a 1fr auto 1fr grid: logo left, nav centred, actions right.
   The nav is centred on the HEADER, not in the space left over, so the two
   side columns each get (width - nav) / 2 whatever they actually need -- and
   the logo is the wider of the two. Give it less than it needs and it does
   not wrap or shrink; it runs under the nav.

   With the wordmark on one line at 0.2em tracking the logo is about 260px
   wide and the nav about 350px, so the collision arrives a little under
   900px -- above the 860px the rest of the file switches at, which is why
   the two overlapped before this. 1024px is comfortably clear of it, and
   it is a breakpoint the file already uses elsewhere.

   What the sheet ITSELF looks like at this width is decided elsewhere: the
   panel styles are unconditional, and the narrow-desktop block further down
   (also 1024px now) is what turns them into an anchored dropdown on a
   pointer device rather than a full-width sheet off the bottom of the
   screen. The 860px block still does the rest of the phone-width bar -- the
   shorter header, the smaller logo, the promo bar wrapping to two lines --
   which is correct, since none of that is about whether the menu is a row or
   a panel. */
@media (max-width: 1024px) {
  .main-nav { display: none; }
  .nav-toggle { display: inline-flex; order: 3; }
  /* The centre column has nothing in it once the nav is gone, and an empty
     1fr between the logo and the actions pushes them to opposite edges of a
     bar that no longer needs to be that wide. Two columns, spaced apart. */
  .header-inner { grid-template-columns: auto auto; justify-content: space-between; }
  /* ---- The logo goes back to the left here ------------------------------
     Above 1024 it is centred, in column 2 of a 1fr / auto / 1fr bar with the
     nav on one side of it and the icons on the other -- the two flanking
     columns are what hold it in the middle of the WINDOW rather than in the
     middle of what is left over.

     Neither of those is true here. The nav is display: none and is not
     placed at all, so nothing balances the icons, and a centred mark with a
     cluster of controls on one side of it and empty gutter on the other is
     not centred, it is adrift. Left, with the icons opposite, is the
     arrangement the collapsed bar wants -- and it is what this block's own
     two-column template was already describing.

     These have to be said, not left to the template. The base rules pin the
     logo to grid-column 2 and the actions to 3, and those columns do not
     exist in a two-track grid: the actions were landing in an IMPLICIT third
     column, and space-between was then spreading three tracks -- an empty one
     at the left, the logo, the icons -- which is what put the mark in the
     middle of a bar that has no middle column. Naming the two real columns
     is the fix. */
  .header-inner .logo { grid-column: 1; justify-self: start; }
  .header-inner .header-actions { grid-column: 2; justify-self: end; }
  .header-actions [data-open-search] { order: 1; }
  .header-actions [data-open-cart] { order: 2; }
  /* Log in moves into the dropdown at this width -- it is a row in there --
     so keeping the icon as well would show it twice. */
  .header-actions [data-open-account] { display: none; }
}

/* ---- Hero ---- */
.hero {
  position: relative;
  isolation: isolate;
  /* The band under the photo. .hero-photo and .hero-overlay both stop this
     far short of the hero's bottom edge and have to stop at the SAME place,
     so it is one number rather than two that must be kept in step.

     A fallback: both width blocks down the file set this to 58px, and between
     them they cover every width, so nothing reads the value here. Kept in
     step with them anyway -- a fallback that disagrees with the rule it backs
     up is worse than none. */
  --hero-band: 58px;
  /* What shows in that band: the Featured Collection's own ground, so the
     section reads as running up under the photo instead of the photo ending
     on a bar of its own. The overlay's hairline above it is what marks where
     the photo stops. */
  background: var(--featured-ground);
  /* The bar sits above it in flow, so a full 100vh here would push the hero
     that much past the fold. */
  min-height: calc(100vh - var(--promo-h));
  display: flex;
  /* Bottom left. Where the copy sits is decided in two places and this is the
     up-and-down one; left is .hero-content's own doing, below.

     It has now been every one of them -- bottom, centred, top, bottom again
     -- and each move is one word here. That is the whole point of keeping the
     vertical on the section and the horizontal on the element inside it:
     neither ever has to know what the other is doing.

     flex-end puts the block against the bottom of the CONTENT box, which ends
     where the band under the photograph begins -- .hero carries a
     padding-bottom of --hero-band in both width blocks -- so the copy is
     never in danger of sitting over the social row. What holds it off that
     edge is .hero-content's own 60px of padding. */
  align-items: flex-end;
  overflow: hidden;
  color: var(--on-dark);
}
/* Real element (not a pseudo-element on .hero) with its own overflow:hidden,
   so the scale(1.12)->scale(1) reveal animation is clipped right at this
   box's edges -- including the bottom one -- instead of bleeding past the
   band and painting over the black bar/border while it's mid-shrink. */
.hero-photo {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: var(--hero-band); /* stop short of the hero's bottom edge so the border below reads as a real border, not a line drawn on top of the photo */
  z-index: -2;
  overflow: hidden;
  /* What the photo fades up out of. This element had no fill for a while, so
     at opacity 0 the colour showing through was .hero's own background --
     --featured-ground, which is white on this page. Asked for #141414
     instead, so the ground is painted here rather than inherited.

     It covers exactly the photo's area and stops where the photo stops, at
     --hero-band, so the strip below is untouched and still shows the section
     ground running up under the picture. */
  background: #141414;
}
/* ---- The slide track ----------------------------------------------------
   Two pictures side by side in a box one picture wide, moved as a unit. The
   flex row overflows .hero-photo, which clips it, so exactly one slide shows
   and translate3d(-100%) brings the next one in from the right. The
   percentage is of the TRACK's own box -- 100% of .hero-photo, not of the
   two slides together -- so one step is one slide however many there are.

   Why a track and not a crossfade: two shots of the same product on the same
   black ground crossfade into a smear, since most of both frames is the same
   dark weave and only the object moves. Sliding says which way you went, and
   it is the gesture the dots and arrows are promising.

   0.85s on the site's own settle curve -- the same cubic-bezier the photo
   entrance and the page headers take, decelerating hard at the end so the
   picture arrives rather than stops. */
.hero-slides {
  position: absolute;
  inset: 0;
  display: flex;
  transition: transform 0.85s cubic-bezier(0.16, 1, 0.3, 1);
  will-change: transform;
}
.hero-slide {
  position: relative;
  /* No shrinking: each slide is exactly the width of the frame, and the row
     is allowed to overflow. flex-basis 100% with flex-shrink 0 is what makes
     that true at any number of slides. */
  flex: 0 0 100%;
  height: 100%;
}
/* The picture itself: the layer inside each slide that actually holds a
   photograph. What it is NOT is the entrance -- that is on the first slide
   alone, in the rule below this one, and everything here is true of every
   slide whether it animates or not.

   `center / cover` and no vertical offset, which is worth being straight
   about: both files are square in a landscape frame, so cover scales them
   to the WIDTH and crops top and bottom evenly, and the pack -- centred in
   its file -- comes out centred in the frame too. It is also large: at a
   1440x770 hero the pack alone is about two thirds of the height, so the
   copy in the bottom left sits ON it rather than beside it.

   That is a choice, and the corner scrim on .hero-overlay is what pays for
   it. The alternative was pushing the crop up with a position-y around
   70%, which buys about 25px of clearance and costs the top of the
   drawstrings -- a worse trade than darkening one corner of a photograph
   that is already almost entirely black. The old landscape shot needed
   `center 30%` for the opposite reason: it was WIDER than the frame in
   proportion, so the offset was choosing which horizontal band survived. */
.hero-slide-img {
  position: absolute;
  inset: 0;
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}
.hero-slide-pack .hero-slide-img { background-image: url('../assets/images/hero-hip-pack.jpg'); }
.hero-slide-sling .hero-slide-img { background-image: url('../assets/images/hero-sling-pack.jpg'); }
/* The entrance belongs to the FIRST slide and to nothing else.

   It was on every slide for a while, all of them settling together on
   .hero.is-visible. The reasoning was that only one is on screen when the
   class lands, so the rest cost nothing -- which is true right up until you
   press an arrow inside the first three seconds, and then the picture you
   asked for arrives already mid-shrink, fading up out of black under your
   finger. The entrance is the page opening; a slide you swiped to is not the
   page opening, and should simply be there.

   :first-child rather than .hero-slide-pack, so it is the SLOT that opens
   and not that particular photograph -- reorder the slides in the markup and
   the right one still animates.

   Everything after it is left at its natural state: opaque, unscaled, and
   with no transition on transform, so the only thing that ever moves a later
   slide is the track sliding it into view. */
.hero-slide:first-child .hero-slide-img {
  opacity: 0;
  transform: scale(1.28);
  transition: transform 2.7s cubic-bezier(0.16, 1, 0.3, 1), opacity 2.2s ease;
}
.hero.is-visible .hero-slide:first-child .hero-slide-img { opacity: 1; transform: scale(1); }
@media (prefers-reduced-motion: reduce) {
  /* Scoped to match the rule above it, or it would lose on specificity and
     the first slide would keep its 2.7s settle. */
  .hero-slide:first-child .hero-slide-img { transition: none; opacity: 1; transform: none; }
  /* The move still happens -- it is what the control was pressed for -- but
     it happens at once rather than sliding. */
  .hero-slides { transition: none; }
}
/* The social row that sits in the band under the photo. Off by default and
   switched on in the desktop block: the phone's band has always been an empty
   strip and nothing about it is being changed. */
.hero-social-bar { display: none; }
.hero-overlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: var(--hero-band);
  z-index: -1;
  /* The flat 35% scrim that used to sit under this is gone: it was dimming
     the whole photo, which is the one thing a screen carried outdoors cannot
     spare. What is left is the soft darkening along the top and bottom edges,
     so the photo sinks into the page at those edges instead of ending on a
     hard line -- into the header above it, and into the band and hairline
     below. This element already shares .hero-photo's bottom inset, so the
     shading covers the photo exactly and stops where the photo does.

     The 8% / 92% stops are read along the gradient line, which is the hero's
     HEIGHT here rather than its width: at a full-viewport hero that is a fade
     of roughly 60-70px at each end. Nothing between those two stops is
     touched -- the middle of the photo, which is all of it you actually look
     at, is now completely clear.

     What carries the type instead is the text's own shadow, which was always
     there under the scrim: it darkens the pixels immediately behind each
     letter rather than the photograph. */
  /* Two layers, and they do different jobs.

     The first is the ground under the copy, and it stopped being a corner
     when the copy did. It was an ellipse anchored in whichever corner the
     block sat in -- four of them, over four passes. The block is not in a
     corner any more: the type is on the right gutter and the button on the
     left, so it spans the foot of the picture, and an ellipse in either
     corner would leave half of it on undimmed photograph.

     A band across the bottom instead: 62% black on the edge, gone by a little
     under half way up. It covers what the copy occupies for the same reason
     the ellipse did, and covers no more of the picture than it has to -- the
     top half of the frame, where the pack is, is untouched by it. So the
     headline, the
     lead and the button have a darkened ground under them and the rest of
     the picture does not. It is the argument the Get In Touch plate makes and
     the vertical scrim there lost: cover what the copy occupies, not the
     whole photograph. What is different here is that the copy sits on a
     product shot rather than a landscape, and the product is centred and
     large -- so the type lands on the pack itself, and something has to hold
     the two apart. The text-shadow on the h1 and the lead is still doing its
     own half of that; this is under it, not instead of it.

     The second is the pair of edge fades that were here before: the photo
     sinks into the header above it and into the band below rather than
     ending on a hard line. The middle of the frame is untouched by both. */
  background:
    linear-gradient(to top,
      rgba(0, 0, 0, 0.62) 0%,
      rgba(0, 0, 0, 0.34) 22%,
      rgba(0, 0, 0, 0) 48%),
    linear-gradient(to bottom,
      rgba(0, 0, 0, 0.18) 0,
      rgba(0, 0, 0, 0) 8%,
      rgba(0, 0, 0, 0) 92%,
      rgba(0, 0, 0, 0.18) 100%);
  border-bottom: 1.3px solid var(--on-dark-border);
}
/* ---- The copy row -------------------------------------------------------
   .hero-content keeps .container's measure and gutter now instead of
   overriding them with a 780px centred column. That cap was what centred the
   copy; with the copy in the bottom left, the container IS the alignment --
   the headline starts on the same gutter every other section on the page
   starts on, and the controls end on the one they end on.

   A row, so the two halves can sit at opposite ends of it: the copy at the
   left, the arrows and dots at the right, both on the same baseline. It
   becomes a column under 640 -- see the block further down.

   The bottom padding is what keeps the block off the floor. --hero-band is
   the strip below the photo (the social bar's, on desktop), so the copy has
   to clear that as well as have air of its own; 56px is that air, measured
   to the button's box rather than to the text. */
/* The copy, and only the copy. This was a two-part row -- copy at one end,
   the carousel controls at the other, both on the same baseline -- for as
   long as the two shared a line. The controls are pinned to the bottom of
   the picture now and are a sibling of this element rather than a child, so
   what is left here has one thing in it and does not need to be a flex row
   to hold it. The block keeps position: relative, which is what puts it and
   its copy above .hero-overlay. */
.hero-content {
  position: relative;
  /* The top value is the one doing work now that the block is anchored to the
     top: 60px is the distance from the underside of the header to the cap
     line of the headline. The bottom is inert -- nothing sits below the copy
     in this element any more -- and is kept equal rather than zeroed so the
     box still has a shape if anything is ever added under it.

     The sides are .container's own gutter, restated because this is a
     shorthand and would otherwise zero what the container set. That matters
     in a way it did not before: the copy used to be a centred 780px column,
     where the headline's left edge now IS the gutter. */
  padding: 60px var(--page-gutter);
  /* The type is ranged left and the button is ranged right -- the block is
     split across the foot of the picture rather than stacked in one corner.

     That the alignment mechanisms are separate is what makes this possible at
     all. text-align here carries the headline and the lead to the left
     gutter; .hero-actions is a flex row and does not answer to text-align, so
     its own justify-content holds the button on the right independently of
     it. Two opposite alignments out of one block, with nothing overriding
     anything. */
  text-align: left;
}
/* The two copy blocks, stacked in one grid cell. Both are in normal flow, so
   the cell -- and with it the hero -- is as tall as the taller of them and
   nothing shifts when they swap. Absolute positioning would have taken the
   second one out of flow and let the hero resize under the crossfade. */
.hero-copies {
  display: grid;
  /* No cap and no margins: this box is the full container now, because the
     two things inside it are pinned to OPPOSITE gutters and a box narrower
     than the page cannot reach both.

     The measure that used to live here -- 640px, opened to 720 on desktop --
     has moved onto the lead, which is the only part of the copy that ever
     needed one. A headline is one line and a button is as wide as its label;
     it was always the paragraph the cap was for. */
  max-width: none;
  margin: 0;
}
.hero-copy {
  grid-area: 1 / 1;
  /* The one that is not showing is still in the grid -- it has to be, or it
     would stop holding the cell open -- so it is hidden by opacity and taken
     out of the pointer and tab order rather than by display. */
  opacity: 0;
  /* A crossfade, and only that. There was a 14px lift under it on the site's
     settle curve -- the one that decelerates hard at the end -- which on a
     line of type reads as the words dropping into place rather than as the
     photograph handing over. Taken out at the asking: the picture is what
     moves, the words simply change. */
  pointer-events: none;
  transition: opacity 0.45s ease;
  /* ---- Two columns, so the type reaches the bottom edge -----------------
     The three pieces used to stack: headline, lead, button, in that order and
     in that direction. That put the BUTTON on the bottom edge and left the
     type sitting above it, which is the wrong way round now that the two are
     ranged to opposite gutters -- the copy was asked for on the bottom right,
     and it could not get there with a button underneath it.

     So they share the line instead. The type stacks in a column on the left
     and the button takes a column of its own on the right, and align-items:
     end sets both of them on the same floor. The whole block still sits at
     the foot of the picture, which .hero's flex-end does; this decides what
     the bottom of the block IS.

     Which column is which is two lines -- the template below and the
     grid-column on each item -- and the type's own ranging is two more, on
     .hero-content and on the lead. Four again, and separate again, which is
     what has made every one of these passes a handful of values rather than
     a rebuild.

     Placed explicitly, every item, because auto-placement in a two-column
     grid would put the headline and the lead side by side and drop the
     button underneath them -- the reverse of all three intentions at once.
     The header taught this lesson once already.

     The button spans both rows rather than being pinned to the second: the
     type is one line or three depending on the slide and the window, and
     align-self: end on a full-height cell means the button finds the floor
     wherever the type leaves it. */
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
  column-gap: 40px;
}
.hero-copy h1 { grid-column: 1; grid-row: 1; }
.hero-copy p.lead { grid-column: 1; grid-row: 2; }
.hero-copy .hero-actions {
  grid-column: 2;
  grid-row: 1 / 3;
  align-self: end;
  justify-self: end;
  /* .hero-actions carries margin-top: 21px from the hero's own stacked
     layout, where it was the gap under the paragraph. There is no paragraph
     above it any more -- it is beside one -- and a top margin on a bottom-
     aligned item only shortens the box it is aligning inside. */
  margin-top: 0;
}

/* ---- Under 640px the two columns give up -------------------------------
   A button column and a type column need a page wide enough for both, and
   the type is the half that suffers: at 500px the lead is down to about
   250px of measure beside a 190px button and breaks after two or three
   words. Back to the stack there, in source order -- headline, lead, button
   -- which is the arrangement the markup already reads as, so this is the
   grid standing down rather than a second layout.

   The type is no longer on the bottom edge at that width. That is the trade,
   and it is the right way round: a phone has one column of room and the copy
   is what should have it. */
.hero-copy.is-active {
  opacity: 1;
  pointer-events: auto;
}
/* The copy crossfades where the picture slides, and that is on purpose rather
   than an oversight: sliding both is a slideshow, and this is a photograph
   that changes with a line of type over it. The 0.45s fade sits inside the
   picture's 0.85s move, so the words have settled before it lands. */
@media (prefers-reduced-motion: reduce) {
  .hero-copy { transition: opacity 0.2s ease; }
}
.hero-content h1 {
  /* Vidaloka is a wider serif than the site's old condensed sans — the
     global h1 clamp (up to 4.2rem) is wide enough at large viewports that
     "Handcrafted Gear" wraps inside the 780px hero-content box. Cap it
     lower here so it reliably stays on one line. */
  font-size: clamp(2.45rem, 4.6vw, 3.6rem);
  /* White, rather than the --title light gray every other heading takes or
     the warm --offwhite the body carries. This is the one piece of type on
     the site sitting straight on a photograph in daylight, and the extra few
     percent is the difference between reading it and squinting at it. */
  color: #fff;
  /* Was nowrap, which the desktop block's fixed 3.6rem was leaning on:
     HANDCRAFTED GEAR sets to about 500px in this face and the box was
     always wider, so the line could be pinned whole.

     There are two headlines now and the longer one -- USA SOURCED & SEWN,
     about 555px -- shares the box with a 186px control cluster beside it.
     It still fits inside the 720px cap at every desktop width, so nothing
     actually wraps; what changes is the failure. nowrap fails by running
     the headline off the side of the photograph, and wrapping fails by
     taking a second line, which is the failure this design can absorb --
     the copy is bottom-anchored, so a second line grows upward into the
     picture rather than pushing anything down. The block under 860 was
     already setting normal for the same reason. */
  white-space: normal;
  text-shadow: 0 3px 14px rgba(0, 0, 0, 0.65);
  /* The default 0.5em heading margin reads as a much bigger gap here than
     it looks on body text, since this heading's line-height packs in a lot
     more empty space above/below the glyphs than the lead paragraph below
     it — cut it down so the visual gap matches the lead-to-button gap. */
  margin-bottom: 0.12em;
}
.hero-content h1 .hero-title-sub {
  display: block;
  font-size: 0.5em;
  margin-top: 0.15em;
  white-space: normal;
}
.hero-content p.lead {
  font-size: 1.3rem;
  color: #fff;
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.6);
  max-width: 540px;
  /* ---- Sitting the type on the button's floor --------------------------
     Two things had to come off the bottom of this paragraph, and the second
     is the one that is easy to miss.

     The first was p's `margin: 0 0 1em`, the gap between the lead and the
     button while the button was underneath it. Beside it, that margin stopped
     being a gap between two things and became a strip of empty space holding
     the last line up off the floor.

     Zeroing it was not enough, because align-items: end aligns BOXES and a
     line of type does not fill its box. line-height is 1.8, inherited from
     body, so the line box is 0.8em taller than the text in it and half of
     that -- 0.4em, about 11px at this size -- sits under the last baseline as
     leading. Under that again is the font's own descent, 0.2em in Barlow
     Condensed. So with both boxes flush the LETTERS were sitting some 16px
     above the button's edge, which is the gap you can see.

     -0.4em takes back exactly the leading and no more. What is left below the
     baseline is the descent, which is ink: the tail of the y in Quality lands
     on the button's bottom edge, which is what "the bottom of the text"
     means when you look at it rather than measure it.

     Written as a calc from the line-height rather than as -0.4em, so the two
     cannot drift apart -- and the line-height is restated just above it for
     the same reason. It is the value this already inherits, so declaring it
     changes nothing and gives the calc a source you can check. */
  line-height: 1.8;
  margin-bottom: calc((1 - 1.8) * 0.5em);
  /* The lead is the only capped thing left in the hero copy, and the cap is
     what stops it setting a 1372px line now that the block around it is the
     full container. auto on the RIGHT puts all of its slack on that side, so
     the line starts on the left gutter under the headline. */
  margin-left: 0;
  margin-right: auto;
}
.hero-actions { display: flex; justify-content: center; gap: 12px; margin-top: 21px; flex-wrap: wrap; }
/* Scoped to .hero: the shared rule above centres the row, which is right on
   the About page and in Get In Touch, and wrong beside a headline ranged to
   the other gutter. Both axes, because .hero-actions turns into a COLUMN
   under 640px and it is align-items rather than justify-content that places
   the button on the cross axis there -- set one and the button drifts back to
   the middle at whichever width you did not think about.

   Above 640 this is nearly inert: justify-self: end on the grid item already
   shrinks the row to its button. It is the stacked layout below that width
   that these two are really for, where the row is the full page measure and
   the button would otherwise sit at its left end, under type that starts
   there -- which is the one arrangement this pass was moving away from. */
.hero .hero-actions { justify-content: flex-end; align-items: flex-end; }

/* ---- The hero copy on a phone: stacked, and centred ---------------------
   Two changes that are really one. Asked for: centre the hero text and the
   button at this width.

   Centring them means stacking them first, and that is not a liberty -- a
   two-column split with the type ranged to one gutter and the button to the
   other has no centre to put anything on. So the grid stands down here and
   the three pieces go back to a column in source order, which is what the
   markup reads as anyway.

   The breakpoint has moved twice: 640 -> 860.01 for the headline's sake --
   above 640 the split gave the type `container minus the button minus the
   gap`, about 414px at a 700px window against the 520px the longer headline
   needs, and no font size fixes a column that narrow -- and now 860.01 ->
   1024 to agree with the header.

   1024 is where the nav collapses into the hamburger and the logo moves to
   the left gutter. That is the width at which the page starts LOOKING like
   a phone, and it is the width the word "mobile" has meant in every request
   about this header. The hero was the one thing on the page still switching
   at 860, which left a 164px band -- 861 to 1024 -- showing a collapsed
   header over a hero split across two gutters. One boundary for the whole
   page is worth more than the 164px of two-column hero it costs.

   Centring is four declarations for the reason it is always four on this
   element: text-align does the headline and the lead, the lead's own auto
   margins place its narrower box inside the block, and .hero-actions is a
   flex row that answers to neither -- both of its axes, since it becomes a
   column under 640 and it is align-items that places the button there.

   What does NOT change is the vertical: .hero keeps align-items: flex-end,
   so the block still sits on the foot of the picture. */
@media (max-width: 1024px) {
  .hero-copy { display: block; }
  .hero-content { text-align: center; }
  .hero-content p.lead { margin-left: auto; margin-right: auto; }
  .hero .hero-actions { justify-content: center; align-items: center; }
  /* The optical pull on the lead is for type sitting BESIDE a button --
     see .hero-content p.lead, where -0.4em takes back the half-leading so
     the last line sits on the button's floor. Stacked, the button is under
     the paragraph again and that pull would eat a third of the 21px gap
     between them, so it goes back to zero and the gap is the button's own
     margin-top, as it was. */
  .hero-copy p.lead { margin-bottom: 0; }
  .hero-copy .hero-actions { margin-top: 21px; }
}

/* The hero button's shadow used to be declared here, softer than the h1's
   text-shadow above -- same shape, lower alpha and a wider blur, so it
   grounded the button on the photo without reading as a drop shadow of its
   own. Every pill button carries that value now, from --btn-shadow, so this
   rule was saying twice what the base rule already says. */
/* Desktop: a bigger headline and lead than the shared hero sizing. The h1 is
   white-space: nowrap, so the box it sits in has to grow with it or the text
   would run past the edge rather than wrap -- hence the wider hero-content.
   The 5.6vw middle term keeps it in bounds at the narrow end of the range:
   the 4.6rem cap is only reached around a 1300px viewport, by which point
   there is room for the line. */
@media (min-width: 860.02px) {
  /* The cap that was on .hero-copies, now on the lead -- see that rule for
     why it moved. 660px is what the paragraph gets here; the headline and the
     button place themselves and want no measure at all. */
  .hero-content h1 { font-size: clamp(3.2rem, 5.6vw, 4.6rem); }
  .hero-content p.lead { font-size: 1.55rem; max-width: 660px; }
}
/* ---- The carousel controls: < * * > -------------------------------------
   One cluster, on the opposite end of the copy row: two arrows with the dots
   between them, sitting on the container's right gutter the way the headline
   sits on its left.

   Round, both of them. The square version argued that everything on this page
   that is not a header icon is square -- the hero's own Shop the Store, the
   two story buttons, Contact Us -- and that round controls would be the only
   curve in the frame. Asked for round anyway, and it is the better call: the
   thing those square buttons have in common is that they are things you press
   to GO somewhere, and these are not. A circle says the control belongs to
   the picture rather than to the page.

   These sit on the picture, above .hero-overlay, because they are inside
   .hero-content, which is position: relative and paints after both. */
/* ---- The cluster ---------------------------------------------------------
   A plain flex row, and nothing else. It was absolutely positioned across the
   foot of the photograph -- left/right 0, and a bottom offset that had to add
   --hero-band back because absolute positioning resolves against the padding
   box and would otherwise have dropped it inside the bar. All of that is
   gone: it sits IN the bar now, in the middle cell of the grid up the file,
   and a grid cell needs no offsets to be told where it is. */
.hero-carousel-nav {
  display: flex;
  align-items: center;
  gap: 4px;
}
.hero-arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 40px in a 58px bar: 9px of air over and under. It has been 42 on the
     photograph, then 34 when the bar was 46px deep and 42 would have left two
     pixels either side -- a control jammed into a slot cut for it rather than
     sitting in one. The bar is deeper now and this follows it, since what the
     size is really set by is the depth of what holds it. */
  width: 40px;
  height: 40px;
  padding: 0;
  /* No border, and no fill at rest either -- what is left of the control is
     the chevron.

     The ring was 1.3px of white at 32%, drawn when these sat on the
     photograph and needed an edge of their own to be a control rather than a
     mark on a picture. In the band they sit on flat #141414, which separates
     them on its own, and 32% white on that ground reads as a gray circle
     round a white arrow -- the ring became the loudest part of a control
     whose job is to be quiet. The fill went with it for the same reason it
     stopped mattering: rgba(20,20,20,0.28) over #141414 IS #141414, so it was
     painting nothing. So did the backdrop-filter, which was blurring a flat
     colour.

     border-radius stays. It is doing nothing visible at rest, and it is what
     shapes the hover -- see below, where the fill comes back as a disc rather
     than as a square behind a round arrow. */
  border: none;
  border-radius: 50%;
  background: transparent;
  color: #ffffff;
  cursor: pointer;
  transition: background 0.25s ease, transform 0.25s ease;
}
.hero-arrow svg { width: 20px; height: 20px; }
/* Pointer devices only, for the reason .icon-btn gives up the file: a tap on
   a touch screen leaves :hover stuck on the control you tapped. */
@media (hover: hover) and (pointer: fine) {
  /* The hover is now the whole of the control's chrome: a red disc appears
     under the chevron where before it only changed colour inside a ring that
     was already there. border-color has nothing left to move. */
  .hero-arrow:hover { background: rgba(176, 43, 37, 0.92); }
}
.hero-arrow:focus-visible {
  outline: 2px solid #ffffff;
  outline-offset: 2px;
}
.hero-arrow:active { transform: scale(0.94); }
.hero-dots {
  display: flex;
  align-items: center;
  gap: 8px;
  /* The dots live between the arrows and need air on both sides of them,
     which is the only reason this has padding rather than the cluster's gap
     being bigger -- that gap would open the arrows away from the row's own
     ends too. */
  padding: 0 8px;
}
/* A dot is a 9px circle. The hit area is the whole 22x28 button -- a 9px
   target is under half of what a finger needs -- and the mark inside it is
   the ::after, which is what actually changes state. Keeping those two
   separate is what lets the target be finger-sized without the dot being
   drawn that big. */
.hero-dot {
  position: relative;
  /* The target, which is deliberately much bigger than the mark: 9px of
     drawn dot is well under what a finger needs. It tracks the bar's depth
     along with everything else in there -- 30px in a 58px bar -- while
     staying the largest part of the control. */
  width: 22px;
  height: 30px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}
.hero-dot::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 9px;
  height: 9px;
  margin: -4.5px 0 0 -4.5px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.42);
  transition: background 0.25s ease, transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}
.hero-dot.is-active::after {
  background: #ffffff;
  /* The live one is bigger as well as brighter. Two states told twice reads
     at a glance on a photograph, where an alpha change on its own can be
     lost against a light passage of the picture. 1.45 on a 9px dot is 13px,
     which is the same jump the old diamond took before the dots went
     round. */
  transform: scale(1.45);
}
@media (hover: hover) and (pointer: fine) {
  .hero-dot:hover::after { background: #ffffff; }
}
.hero-dot:focus-visible {
  outline: 2px solid #ffffff;
  outline-offset: -2px;
}
@media (prefers-reduced-motion: reduce) {
  .hero-arrow, .hero-dot::after { transition: none; }
}

/* The narrow-window block that used to live here is gone with the row it was
   for. While the controls sat beside the copy they had to drop below it under
   750px, or the headline would have been squeezed into a column narrower than
   its own first word. Pinned to the bottom of the picture they are out of the
   copy's way at every width, and the phone gets the same arrangement the
   desktop does -- which is one fewer layout to keep true. */

.hero-stats {
  position: absolute;
  right: 18px;
  bottom: 30px;
  display: flex;
  gap: 40px;
}
.hero-stat { text-align: right; }
.hero-stat strong {
  display: block;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 1.8rem;
  color: var(--tan-light);
}
.hero-stat span {
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  color: var(--on-dark-faint);
}

/* ---- Sections ---- */
.section { padding: 72px 0; }
.section-alt { background: var(--bg-alt); border-top: 1.3px solid var(--border); border-bottom: 1.3px solid var(--border); }
/* A rule across the top of a section that keeps the page's own background --
   the separation .section-alt gets from its band, for a section that has no
   band. Same hairline that class uses, so a divided section and an alt one
   sit at the same weight. */
.section-ruled { border-top: 1.3px solid var(--border); }
/* The two About story sections are section-alt, but on that page they are
   not meant to read as a band at all -- no rules above or below, and the same
   black as the page behind them, so the two run on unbroken and the only
   thing marking a section is the space around its content. --surface-2 is the
   page's own black, so the sections disappear into it exactly -- named for
   the page rather than borrowed from the frame, even though the frame now
   happens to sit at the same value. The cards inside them carry their own
   fill -- see .about-card.

   Scoped to .page-about. section-alt is used on nine other pages, where it is
   still the gray band with its hairlines. */
.page-about .section-alt {
  background: var(--surface-2);
  border-top: none;
  border-bottom: none;
  /* Closer than the shared .section 72px. With no rules and no colour change
     between them these read as one page rather than separate bands, so they
     do not need the separation a section between two different backgrounds
     would. */
  padding: 40px 0;
}
/* The first one is the exception: 40px is the gap BETWEEN story sections, and
   above the first there is no story to be close to -- just the Our Story
   header. That break wants the full section's worth of air. */
.page-about .section-alt:first-of-type { padding-top: 80px; }
/* The page's own ground, the same value .home-featured names right
   above it -- the two sections run on as one field of colour, with only
   section-alt's hairline between them. */
/* No rules at all. The top is bare because Featured Collection above draws
   the line between the two, and the bottom is bare because the Free
   Membership band underneath draws its own top rule -- one line at that
   seam, from the band, exactly as on the store page. */
/* Lighter than the page, not level with it: the About Us / Cut & Sewn pair
   lift off --forest as one raised band. Both stories live in this single
   section -- they are two copy panels swapped in one grid cell, not two
   sections -- so this one ground covers them both. */
.home-about { background: #1a1a1a; }
/* The two stories, stacked. Each is its own row -- see .home-story below --
   and this is only the space between them; the desktop block widens it.
   Single column is the base so it holds at every width the desktop query
   does not cover. */
.home-stories { display: grid; grid-template-columns: minmax(0, 1fr); gap: 40px; }
/* The small label over a story heading -- ABOUT US over The Craft,
   USA SOURCED & SEWN over Field-Tested. It carries what the band is about
   so the heading is free to be short -- both headings on this page were the
   label until the labels arrived, and both have since been rewritten.

   Note that the older comments in this file still call that lower band "Cut
   & Sewn", which was its heading for a long time and is no longer any text
   on the page. It is the .home-about-lower row.

   Not .eyebrow, which is the site's older label: --font-gold at --title-red
   with a 22px rule struck before it. That was drawn for the red-accented
   pages and is the last red left in this part of the file; these panels are
   Barlow throughout and have no rules on them.

   A span rather than a p, set to block. As a p it would inherit the story
   paragraph's size, colour and margins from .home-about .home-story-copy p
   and need every one of them overridden -- and it is a label, not prose.

   ---- This slot has never once measured, and it is worth writing down why

   The label sits about a third of the way down the panel's fade, where the
   ground now interpolates to roughly #333333. Each of these was metered on
   the panel it was actually given on, so they are a history rather than a
   comparison:

     #b35754  2.4:1     #9db3ab  5.1:1     #b34632  2.5:1     #7d3733  1.8:1
     #b02b25  1.9:1

   Only the green cleared, and it cleared because it was LIGHT -- luminance
   0.42 against the panel's 0.026. Every red the slot has been given has come
   in between 0.07 and 0.15, one to six times the ground's own value, where
   4.5:1 on a ground this dark needs about 0.30. Hue has nothing to do with
   it: a mid-value colour on a dark panel cannot reach the bar for small
   text no matter which mid-value colour it is, and this is the SMALLEST text
   on the panel at 0.8125rem, so the bar is 4.5 and not 3.

   ---- And then it was white, which is the answer the list was pointing at

   #ffffff: 12.6:1 where the label sits, against the 4.5:1 this size needs.
   The slot clears for the first time, and not narrowly.

   Everything above is why. Seven values, six failures, and the one that
   worked -- #9db3ab at 5.1:1 -- worked because it was LIGHT, luminance 0.42
   against a panel at 0.03. Every red came in between 0.07 and 0.15, one to
   five times the ground's own value, where 4.5:1 on a ground this dark takes
   about 0.30. The reds were never failing because they were the wrong red.

   What it gives up is the point the slot existed to make. An eyebrow in an
   accent said "this label belongs to the same family as the buttons"; white
   says only "this is a label". That distinction is now carried by size,
   weight and tracking -- 0.8125rem at 500 on 0.16em, against a heading at
   3.25rem -- which is what carried it on the near-black pages before any
   accent was tried here, and is the more durable of the two anyway: it does
   not have to be re-metered every time the panel moves.

   A base rule, at every width. It is new content rather than a repaint of
   something the phone already had, which is how the category tiles were
   handled for the same reason. */
.story-eyebrow {
  display: block;
  font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
               'Segoe UI', Roboto, sans-serif;
  font-weight: 500;
  font-size: 0.8125rem;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  line-height: 1.2;
  color: #ffffff;
  margin-bottom: 12px;
}
/* One story: copy and photo. Stacked here, side by side on desktop, and the
   copy is first in the markup in both rows so this order is the reading
   order everywhere -- the desktop flip is placement, not source. */
.home-story { display: grid; grid-template-columns: minmax(0, 1fr); gap: 28px; }
/* The two edges swapped with the section order. 52px on top is the headroom
   Featured Collection used to take directly under the hero -- a section
   opening out of the photo needs less than one between two others. The 40px
   underneath meets Featured Collection's own 40px to make that seam 80px,
   the same one this page puts between all its sections. */
.section.home-about { padding-top: 52px; padding-bottom: 40px; }
/* Colour lives on --featured-ground (see :root) because the hero's band reads
   it too -- the two have to move together or a bar reappears under the photo.
   This used to be the same value as .home-about's ground, so the seam below
   was this section's bottom rule and nothing else; the stories now sit on a
   lighter band and the rule draws the top edge of it. */
.home-featured {
  background: var(--featured-ground);
  /* 40px, meeting the stories' 40px above to make the standard 80px seam.
     This was 52px when the section sat directly under the hero and could
     take less headroom than a section between two others; it is between two
     others now. */
  padding-top: 40px;
  /* No bottom rule. It used to draw the line between this section and the
     stories; the Free Membership band is what follows it now, and that band
     draws its own top rule on the stated assumption that whatever precedes
     it draws none. Left in place the seam would be ruled twice. */
  border-bottom: none;
}
/* Facing edges, written as .section.<name> rather than the bare class so the
   mobile `.section { padding: 64px 0 }` further down the file cannot put its
   own value back: that rule comes later and would win at equal specificity.

   The heading also sits closer to the product row than the shared
   .section-head 36px, now that the eyebrow line above it is gone.

   72px underneath, the shared .section value: this is the last section
   before the Free Membership band, which is where the stories used to leave
   72px and where the store page leaves it too -- the two pages end the
   same way. */
.section.home-featured { padding-bottom: 72px; }
.home-featured .section-head { margin-bottom: 22px; }
.home-videos { background: var(--forest); }
.section-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 18px;
  margin-bottom: 36px;
  flex-wrap: wrap;
}
.section-head p { max-width: 480px; margin: 0; }


/* ---- Product grid & cards ---- */
/* minmax(0, 1fr), not a plain 1fr. A bare 1fr is minmax(auto, 1fr), and that
   auto floor is the item's min-content width -- which for a card is its
   photo's intrinsic width. Once the window is narrow enough that the floor
   bites, the tracks stop shrinking and the row overflows the container,
   running the last card off the side of the screen. */
.product-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--grid-gap);
}
@media (min-width: 1025px) {
  [data-featured-grid] { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
/* Store and related cards end in the star row, and the next row's photo
   starts right under it -- too close for the rating to read as belonging
   to the card above rather than to the one below. Twice the space:
   .product-info already carries 4px of bottom padding, so 40 + 4 = 44,
   double the 18 + 4 = 22 the plain grid gap gives. Column-gap is untouched
   and stays at .product-grid's 18px. */
[data-store-grid],
[data-related-grid] { row-gap: 40px; }
.product-card {
  background: transparent;
  border: none;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.product-media-wrap { position: relative; }

.product-media {
  position: relative;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  /* The same gray edge the product page's gallery and the stacked viewer's
     photos carry. This box is only ever a product card's photo -- the one
     with a Quick Add bar under it -- so the border lands on those and nowhere
     else. Safe against the aspect-ratio because the file is border-box: the
     border eats into the square rather than adding to it. */
  border: 1.3px solid var(--border-strong);
  /* Square on all four corners, as is the Quick Add bar welded to the bottom
     of it: the pair is one hard-edged rectangle, the only one on the card.

     Written out rather than left unsaid, because --radius runs through this
     file as a habit and a later rule picking it up would round these again
     by accident.

     No -webkit-mask-image either. That hack is what makes WebKit clip a
     transformed child to a radius, and .product-media-link does carry the
     entrance scale -- but with no radius there is nothing for it to clip to,
     and overflow above already holds the child to the box. */
  border-radius: 0;
  transform: translateZ(0);
  background:
    radial-gradient(circle at 30% 20%, rgba(169,136,90,0.06), transparent 70%),
    #5e5e5e;
}
/* ---- Coming Soon: what every product photo is at the moment --------------
   Real photography does not exist yet, so mediaMarkup returns one of these
   in place of every product image on the site -- card grids, the product
   page's gallery and thumbnails, the stacked viewer, the quick view popup
   and its fullscreen viewer, cart lines, the review modal, search results.
   One rule covers all of them because one function builds all of them.

   Fills whatever box it is dropped into. Each of those containers is either
   a flex box centring its child or a fixed square, and 100%/100% satisfies
   both, so nothing here has to know which one it landed in. */
.product-media-soon {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  /* A step lighter than --surface, so the plate reads as a filled panel
     against the card and the popup rather than as a hole in them. */
  background: #2a2a2a;
  color: var(--offwhite);
  /* Never a target: the plate sits inside links and buttons that are. */
  pointer-events: none;
  /* Two words at a fixed floor will not fit the smallest plates. They are
     hidden outright below, but this is the guarantee -- nothing spills out
     of the tile whatever it ends up in. */
  overflow: hidden;
}
.product-media-soon span {
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.2em;
  /* Pulls the tracking off the last letter so the words stay optically
     centred rather than sitting a fifth of an em to the left. */
  text-indent: 0.2em;
  text-align: center;
  line-height: 1.2;
  font-size: 0.8rem;
}
/* The plate turns up at wildly different sizes -- a 68px cart thumbnail, a
   ~330px card, a gallery slide most of the window tall -- and one type size
   cannot serve all of them. Container units size the label against the plate
   it is actually in, so each one comes out proportionate without a rule per
   context. Behind @supports because the fixed size above is a perfectly good
   answer where they are unavailable. */
@supports (container-type: inline-size) {
  .product-media-soon { container-type: inline-size; }
  .product-media-soon span { font-size: clamp(0.55rem, 9cqw, 1.15rem); }
  /* Below this there is no size at which two words of tracked display type
     read as anything but noise -- the cart line's 68px thumbnail and the
     product page's four-up thumbnail mosaic. Those get the plate as a plain
     tile, which says the same thing in that context. */
  @container (max-width: 96px) {
    .product-media-soon span { display: none; }
  }
}

.product-media-link {
  /* Positioning context for the crossfade overlay below. */
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  border: none;
  background: transparent;
  padding: 0;
  font: inherit;
  cursor: pointer;
  /* Same shrink as the hero but quicker, and lifting out of black as it
     lands. brightness() rather than opacity, so it reads as the photo
     lighting up rather than the card behind it showing through. */
  transform: scale(1.28);
  filter: var(--reveal-from);
  transition: transform 1.2s cubic-bezier(0.16, 1, 0.3, 1), filter 1.2s ease;
}
.product-media-link.is-visible { transform: scale(1); filter: brightness(1); }
.product-media-link svg { width: 34%; height: 34%; color: var(--text-faint); transition: transform 0.35s ease; }
.product-media-wrap:hover .product-media-link svg { transform: scale(1.08); }
/* Real product photos fill their container edge-to-edge (unlike the
   centered, size-limited icon glyphs) — one rule shared by every media
   container: product cards, galleries, thumbs, cart lines, search results. */
/* Overrides the generic img{opacity:0;transform:scale(1.12)} scroll-reveal
   defaults — these live inside wrappers (.product-media-link, etc.) that
   already own their own reveal animation, so the img itself just shows. */
.product-media-img { width: 100%; height: 100%; object-fit: cover; display: block; opacity: 1; transform: none; filter: none; transition: transform 0.35s ease; }
/* Quick Add hover switches a card to its second photo, with no transition --
   the overlay is simply shown or hidden. The two photos stay stacked rather
   than one src being swapped, so the change is instant: the second file is
   already loaded and painted, with nothing to decode at hover time.

   transform stays on the base image's 0.35s ease, so both stacked layers zoom
   together on hover rather than sliding out of step. */
.product-media-img-alt {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  transition: transform 0.35s ease;
}
.product-media-link.is-previewing > .product-media-img-alt { opacity: 1; }
/* The 1% resting scale is not decoration -- it is the same fix the product
   page's thumbs already carry (see .product-detail-gallery .product-thumb img).
   Grid columns are 1fr, so a card's width lands on fractional pixels; the
   hover transform promotes the photo to its own layer, whose edges are then no
   longer pixel-snapped to the box it fills, and .product-media's gray
   placeholder background flashed through the seam as a hairline along an edge
   while the transition ran -- most visible on the way back down, where the
   photo settles flush against the box instead of overflowing it. A 1% overfill
   is invisible and leaves no seam to show through, and backface-visibility
   keeps the same layer alive between hovers so the promotion itself isn't what
   blinks. Both stacked photos take it, so the Quick Add preview swap and the
   photo under it stay in step. */
.product-media-wrap .product-media-link .product-media-img {
  transform: scale(1.01);
  backface-visibility: hidden;
}
/* Keyed to .product-media-wrap, which holds the photo and the Quick Add
   button together: the photo stays big while the cursor moves between the
   two, and shrinks back the moment it leaves both. */
.product-media-wrap:hover .product-media-link img { transform: scale(1.08); }
.qv-fullscreen-slide .product-media-img { object-fit: contain; }
/* The generated images that do get the entrance: the product page's main
   photo and the quick view popup's, which share one declaration so the two
   are identical by construction rather than by two sets of numbers kept in
   step by hand. Declared on the image rather than its slide because the
   slide's own transform belongs to the gallery. The small thumbnails and both
   stacked viewers are deliberately left out, so they simply never match this
   -- the stacks show their photos plainly, with no entrance at all. */
.product-detail-media-slide .product-media-img,
.qv-media-slide .product-media-img {
  transform: scale(1.28);
  filter: var(--reveal-from);
  transition: transform 1.2s cubic-bezier(0.16, 1, 0.3, 1), filter 1.2s ease;
}
.product-detail-media-slide .product-media-img.is-visible,
.qv-media-slide .product-media-img.is-visible { transform: scale(1); filter: brightness(1); }
/* And the placeholder, on exactly the same numbers. Every photo on the site
   is a Coming Soon plate at the moment, and a plate is a div, not an <img> --
   so the gallery and the popup had no entrance at all while the card grids,
   whose entrance sits on the wrapper around the photo, did. Same rule, so the
   two cannot drift apart, and the day real photos land the plate rule simply
   stops matching anything.

   On the slide's child rather than the slide, for the reason above: the
   slide's own transform belongs to the gallery. Scoped to slides, so the
   thumbnails and the stacked viewers -- which show their photos plainly --
   are left out. */
.product-detail-media-slide .product-media-soon,
.qv-media-slide .product-media-soon {
  transform: scale(1.28);
  filter: var(--reveal-from);
  transition: transform 1.2s cubic-bezier(0.16, 1, 0.3, 1), filter 1.2s ease;
}
.product-detail-media-slide .product-media-soon.is-visible,
.qv-media-slide .product-media-soon.is-visible { transform: scale(1); filter: brightness(1); }
.product-badge {
  position: absolute;
  top: 12px;
  left: 12px;
  z-index: 2;
  font-family: var(--font-display);
  font-size: 0.65rem;
  letter-spacing: 0.08em;
  padding: 5px 10px;
  border-radius: var(--radius-sm);
  background: var(--title-red);
  color: var(--offwhite);
}
.product-badge.badge-muted { background: var(--surface); color: var(--text-muted); border: 1.3px solid var(--border-strong); }
.product-badge.badge-accent { background: var(--forest); color: var(--offwhite); }

/* ---- Quick Add: a bordered bar that always sits in normal flow between
   the product image and the title/description, at every viewport width
   -- no hover reveal, no absolute positioning. ---- */
.product-media-actions {
  position: static;
  z-index: 2;
  display: flex;
  width: 100%;
  /* No gap at all: the bar is welded to the bottom of the photo. Between
     them, the photo's own bottom border is the seam, and the two square
     edges that meet there -- the photo's bottom, the bar's top -- are what
     let them join without a join showing. Their far edges are both rounded,
     so the pair reads as one panel rather than two things touching.

     One value for every width: nothing about this is proportional to the
     grid, because there is no space to scale. */
  margin-top: 0;
  /* The same gray edge the photo above carries, so the outline runs
     unbroken around the pair. Sides and bottom only: the photo's own
     bottom border is already the line where the two meet, and a top
     border here would sit against it and draw that one seam at 2.6px
     while every other edge stayed at 1.3px. */
  border: 1.3px solid var(--border-strong);
  border-top: none;
  /* Square, like the photo it is welded to. overflow below is doing the
     clipping on its own now: there is no shape for a radius to follow, but
     the hover sweep still has to be kept out of sight while it is
     mid-travel above the button. */
  border-radius: 0;
  overflow: hidden;
}
.cart-overlay-btn {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 13px 0;
  background: var(--btn-white-solid);
  color: var(--offwhite);
  border: none;
  /* The .btn type ramp, matched value for value so Quick Add and Add to
     Cart read as the same control. The family, weight and uppercasing were
     already shared; the size and the letter-spacing were not -- this ran a
     tighter 1.05rem with no tracking at all. */
  font-family: var(--font-display);
  font-size: 0.9375rem;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  cursor: pointer;
}
.cart-overlay-btn::after {
  content: '';
  position: absolute;
  top: -16px;
  left: 0;
  right: 0;
  height: calc(100% + 32px);
  z-index: -1;
  background: linear-gradient(to bottom, transparent 0px, var(--btn-gray-dark) 16px, var(--btn-gray-dark) calc(100% - 16px), transparent 100%);
  pointer-events: none;
  transform: translateY(-100%);
  transition: none;
}
.cart-overlay-btn.is-hovering::after {
  transform: translateY(0%);
  transition: transform 0.22s ease;
}
.cart-overlay-btn.is-leaving::after {
  transform: translateY(100%);
  transition: transform 0.22s ease;
}
.cart-overlay-btn.is-resetting::after {
  transition: none;
}
.cart-overlay-btn svg { width: 18px; height: 18px; flex-shrink: 0; }
.cart-overlay-btn span { white-space: nowrap; }

/* ---- Quick view: modal ---- */
.quick-view-backdrop {
  position: fixed;
  inset: 0;
  /* The same darkening the stacked gallery lays down -- see --overlay-dim.
     Was rgba(26, 26, 26, 0.6): lighter, and warmer, so the page behind the
     popup sat at a visibly different level from the page behind the stack. */
  background: var(--overlay-dim);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
  z-index: 300;
}
.quick-view-backdrop.open { opacity: 1; pointer-events: auto; }
/* ---- Custom pointer cursor ---------------------------------------------
   .quick-view-close redrawn as a cursor, at that button's own 38px: the ring
   under the pointer and the button in the popup's corner are the same size.
   The viewBox is 38 units too, so every coordinate below is a real pixel and
   the artwork is 1:1 with the button. White ring and white X (--on-dark, the
   site's white) over
   a black disc at the same 0.55 the gallery arrows carry as --glass-fill --
   enough to seat the white marks on a light photo, while the photo or page
   still carries on through it. Cursor and arrows are one set, so those two
   numbers move together.

   r=18.35, not 19, because the 1.3 ring straddles the path: half of it falls
   outside the radius, and at 19 that half would be clipped off by the
   38-unit viewBox.

   The glyph is the close button's own path verbatim, written in that button's
   24-unit viewBox and placed by a translate/scale group: 0.8333 at
   translate(9 9), for a 10px X on a 1.33 stroke. The disc now matches the
   button exactly but the X inside it is still deliberately the larger of the
   two -- the button draws the same path at 0.6667 (a 16px <svg> on a 24
   viewBox) for 8px on 1.067.

   The translate is not free to choose: the path centres on (12,12) locally,
   so the offset has to be 19 - 12 * scale for the middle to land on the
   disc's centre. Rescale the glyph and that number moves with it.

   Held as a custom property because the data: URI is ~400 characters and
   inlining it at both use sites would be unreadable; `cursor` takes a var()
   substitution fine. Colours are literals inside the URI -- a data: URI
   cannot read a custom property -- so if the palette moves, these move with
   it by hand. Each use site ends in `pointer` as a fallback, so anything that
   will not take an SVG cursor still reads as clickable. */
:root {
  --cursor-close:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='38' height='38' viewBox='0 0 38 38'%3E%3Ccircle cx='19' cy='19' r='18.35' fill='black' fill-opacity='0.55' stroke='%23878583' stroke-width='1.3'/%3E%3Cg transform='translate(9 9) scale(0.8333)' fill='none' stroke='%23fbf7f3' stroke-width='1.6' stroke-linecap='round'%3E%3Cpath d='M6 6l12 12M18 6L6 18'/%3E%3C/g%3E%3C/svg%3E") 19 19;
  /* The smoked fill behind both pairs of gallery arrows and the stacked
     gallery's close button -- black at the same 0.55 the cursor above
     carries, so a ring under the pointer and a ring sitting on the photo
     weigh the same. The cursor cannot read this: a data: URI has no access to
     custom properties, so it writes the value out by hand as
     `fill='black' fill-opacity='0.55'`, in SVG's own attribute form rather
     than an rgba() string. Two places, one value. */
  --glass-fill: rgba(0, 0, 0, 0.55);
}
/* Off the popup, the pointer becomes the close button -- clicking out here
   closes the quick view (see main.js), so the cursor says so rather than
   leaving a bare arrow over dead space. Pointer-only: a touch device has no
   cursor to restyle, and the popup is a bottom sheet there anyway. */
@media (hover: hover) and (pointer: fine) {
  .quick-view-backdrop,
  .review-modal-backdrop,
  /* Same idea over the nav panel's dimmed page: off the panel and off the
     header, the pointer is a close button, because that is what clicking
     there does. It only ever catches the pointer while the panel is open. */
  .mobile-nav-backdrop,
  .cart-drawer-backdrop { cursor: var(--cursor-close), pointer; }
}

.quick-view-modal {
  position: fixed;
  /* Equal gap on all four sides, guaranteed by the box model itself --
     replaces the old top/left/width/max-height combo (94vw vs 94vh caps),
     which produced generous side margins but a nearly-clipped top/bottom.
     Tied to the page's own .container content edge -- same side padding
     formula PLUS the auto-margin that kicks in once the viewport is wider
     than --container's 1500px cap -- minus 1px, so the modal always
     overlaps the product grid behind it by at least a hair instead of
     leaving a gap that peeks through (previously this only matched the
     padding half of that formula, so it drifted off the true page margin
     on any screen wider than 1500px). */
  --qv-inset: calc(max(0px, (100vw - var(--container)) / 2) + clamp(16px, 4vw, 64px) - 1px);
  inset: var(--qv-inset);
  overflow: hidden;
  background: var(--modal-panel);
  /* Gray edge, same as the product page's gallery. Held in a property
     because the grid formula below has to subtract it -- see there. */
  --qv-border: 1.3px;
  border: var(--qv-border) solid var(--border-strong);
  /* Square. The panel is a full-bleed dialog inset to the page's own content
     edge, not a card floating on it, so it takes the same hard corners the
     product cards do. The mobile bottom sheet keeps its rounded top -- see
     the max-width: 760px block, where it is the sheet's only shaping. */
  /* --band-radius, the curve the home page's four bands share -- the two
     story rows, the category tiles and Get In Touch. This modal is the
     same kind of object: a panel floating inset from the window edge, so
     it takes the same corner. overflow: hidden above is what makes the
     photograph and the info column follow it rather than square it off. */
  border-radius: var(--qv-radius);
  box-shadow: var(--shadow);
  z-index: 301;
  opacity: 0;
  pointer-events: none;
  transform: scale(0.96);
  transition: opacity 0.25s ease, transform 0.25s ease;
  display: grid;
  /* Image column width = modal height (100vh minus 2x the inset above), so
     that column is an exact square whenever the modal has room for it --
     capped at 56% of the modal's own width so a narrow-but-tall window
     can't squeeze the info column down to nothing (image just goes
     slightly rectangular instead once that cap kicks in).

     The border comes off that height. Grid tracks are laid out in the
     content box, so the column's height is the modal MINUS its two borders,
     while 100vh - 2 * inset is the modal including them -- left unsubtracted
     the column would be 2.6px wider than it is tall, and the square this
     whole expression exists to guarantee would not be one. */
  grid-template-columns: min(calc(100vh - 2 * var(--qv-inset) - 2 * var(--qv-border)), 56%) 1fr;
}
.quick-view-modal.open { opacity: 1; pointer-events: auto; transform: scale(1); }

.quick-view-close {
  position: absolute;
  top: 16px;
  right: 16px;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 1.3px solid var(--border-strong);
  background: var(--surface);
  color: var(--text);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2;
  transition: border-color 0.2s ease, color 0.2s ease;
}
.quick-view-close:hover { border-color: var(--on-dark); color: var(--on-dark); }
.quick-view-close svg { width: 16px; height: 16px; transition: transform 0.2s ease; }
.quick-view-close:hover svg { transform: scale(1.35); }
.quick-view-handle { display: none; }

.quick-view-media {
  position: relative;
  height: 100%;
  /* Square, following the panel. This used to round its outer two corners to
     sit inside the panel's; with that square there is nothing to follow, and
     a curve here would only show the panel's own ground through each
     corner. */
  /* The dark half has to carry the panel's curve on the two corners it
     touches, and this is the fix for the white showing through them.

     The modal's own background is --page-ground, which is #ffffff -- the
     light treatment sets it on .quick-view-modal and .quick-view-info
     together. The info half being white too means its corners are
     invisible against it. The media half is not: it is a near-black photo
     plate, and with square corners inside a parent clipped at 24px, the
     white parent showed as a crescent in each of its two corners.

     Rounding the child is what removes it. Clipping the parent harder
     cannot -- the gap is INSIDE the parent's own rounded edge, so there is
     nothing there for the clip to cut.

     Left pair only: in this two-column form the media is the left half and
     its right edge meets the info panel square. Same token as the panel,
     so the two curves cannot drift apart. */
  border-radius: var(--qv-radius) 0 0 var(--qv-radius);
  overflow: hidden;
  /* An opaque ground under the clip, and this is what kills the light edge
     along the curve.

     This element had no background at all -- the dark you see is its CHILD,
     the Coming Soon plate, painting inside it. A rounded overflow: hidden
     clips that child with an antialiased edge, and antialiased means the
     outermost pixels of the curve are partly transparent. Partly transparent
     over what? The modal's own background, which the light treatment sets to
     #ffffff. So every pixel along the arc was a blend of the photo and white,
     which reads as a pale rim following the corner.

     #181818 is the plate's own ground -- the colour under the topo file in
     .quick-view-media .product-media-soon -- so the blend is now dark on dark
     and the rim has nothing light to pick up. It also covers the case the
     plate is replaced by a real photograph: the backing stays dark either
     way. */
  background: #181818;
  /* No cursor and no zoom. The popup is a quick look before adding to the
     cart, not a photo viewer -- the product page is where the photography is
     examined, and it has the stacked gallery for it. */
}
.qv-media-track { width: 100%; height: 100%; position: relative; }
.qv-media-slide {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: none;
  align-items: center;
  justify-content: center;
  transition: transform 0.15s ease-out;
  background:
    radial-gradient(circle at 30% 20%, rgba(169,136,90,0.06), transparent 70%),
    #5e5e5e;
}
.qv-media-slide.is-active { display: flex; }
.qv-media-slide-icon {
  width: 100%; height: 100%;
  display: flex; align-items: center; justify-content: center;
}
.qv-media-slide svg { width: 34%; height: 34%; color: var(--text-faint); }

/* Tap-to-expand, on the product page's gallery only. The class is named for
   the quick view popup, which used to carry one of these as well -- the
   popup's photos are a quick look before adding to the cart and deliberately
   no longer enlarge, so the selectors below are scoped to the product page
   to keep the button from ever coming back there. Hidden except on a phone;
   a pointer device opens the stacked viewer by clicking the photo itself. */
.qv-media-expand { display: none; }

/* ---- Quick view (mobile): real swipeable filmstrip, exactly one photo on
   screen at a time — plus a thumbnail strip below to jump straight to any
   photo. ---- */
.qv-media-thumbs { display: none; }
@media (max-width: 860px) {
  .qv-media-track {
    display: flex;
    overflow-x: auto;
    /* Same fix as .product-detail-media-track: overflow-x on its own makes
       overflow-y compute to auto, so a downward drag panned the photos
       instead of scrolling the popup. */
    overflow-y: hidden;
    /* Both axes, not pan-x alone. pan-x does not hand a vertical drag to the
       sheet behind the photo -- it forbids one outright, which is why a finger
       starting on the image could page the photos sideways and do nothing at
       all up or down. With pan-y allowed as well the browser reads the
       direction and gives the gesture to whichever wants it: sideways to the
       strip, up and down to the sheet. */
    touch-action: pan-x pan-y pinch-zoom;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .qv-media-track::-webkit-scrollbar { display: none; }
  /* Full width, so one photo fills the frame and the next is genuinely off
     screen rather than peeking in at the edge. */
  .qv-media-slide {
    position: relative;
    display: flex !important;
    flex: 0 0 100%;
    scroll-snap-align: start;
  }
  .quick-view-media .qv-media-arrow { display: none; }
  /* Against the frame, not against a slide. It used to be rendered into every
     photo, which meant it travelled with the filmstrip -- it slid off the
     left edge on a swipe while the next photo's copy slid in from the right.
     One button, parked. */
  .product-detail-media .qv-media-expand {
    display: flex; align-items: center; justify-content: center;
    position: absolute; top: 12px; right: 12px; z-index: 4;
    width: 44px; height: 44px; border-radius: 50%; border: none;
    background: rgba(19, 19, 19, 0.55); color: #d6d6d6;
    cursor: pointer;
  }
  .qv-media-expand svg { width: 20px; height: 20px; color: #d6d6d6; }
  .qv-media-thumbs {
    display: flex;
    justify-content: center;
    gap: 10px;
    padding: 10px 16px 0;
    overflow-x: auto;
    scrollbar-width: none;
  }
  .qv-media-thumbs::-webkit-scrollbar { display: none; }
  .qv-media-thumb {
    position: relative;
    flex: 0 0 72px;
    width: 72px; height: 72px;
    border-radius: var(--radius-sm);
    border: 1.3px solid transparent;
    background: #5e5e5e;
    padding: 0;
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    overflow: hidden;
  }
  .qv-media-thumb.is-active { border-color: var(--text); }
  .qv-media-thumb svg { width: 60%; height: 60%; color: var(--text-faint); }
  /* Shown on the 4th thumb only when there are more images than fit —
     darkens that thumb and overlays "+N" for the rest. */
  .qv-media-thumb-count {
    display: none;
    position: absolute;
    inset: 0;
    align-items: center;
    justify-content: center;
    background: rgba(19, 19, 19, 0.65);
    color: #ececec;
    font-family: var(--font-display);
    font-size: 1rem;
    font-weight: 600;
  }
  .qv-media-thumb.has-more .qv-media-thumb-count { display: flex; }
}
.qv-media-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 1.3px solid var(--border-strong);
  /* Smoked rather than solid, so the photo carries on behind the arrows
     instead of stopping at a disc. Same fill as the zoom cursors, and both
     pairs take it from here -- the popup's and the product page's, which
     share this class. */
  background: var(--glass-fill);
  color: var(--text);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  /* Above the slides, which sit at 1 and 2 while one is sliding in over the
     other. The prev arrow comes BEFORE the track in the markup, so at an equal
     z-index the active slide painted straight over it and only the left arrow
     vanished. Also above the dot row at 3. */
  z-index: 4;
  transition: border-color 0.2s ease, color 0.2s ease;
}
.qv-media-arrow:hover { border-color: var(--on-dark); color: var(--on-dark); }
.qv-media-arrow svg { width: 18px; height: 18px; transition: transform 0.2s ease; }
.qv-media-arrow:hover svg { transform: scale(1.35); }
.qv-media-prev { left: 16px; }
.qv-media-next { right: 16px; }
/* ---- Product detail page image gallery ---- */
@media (min-width: 860.02px) {
  /* Desktop only: the gallery stays pinned in view while the info column
     (which runs much longer once the accordion and reviews are open)
     scrolls past it, instead of scrolling away after the first screen.
     Give it more of the row than the info column -- past the halfway
     mark -- since the thumbnail column below now shares this space with
     the main image. */
  /* .split.product-detail-split (not just .product-detail-split) so this
     beats the plain .split rule declared later in the file at equal
     specificity. */
  /* Not an fr ratio any more, because one cannot hold the margins even. The
     photo is capped at --pd-photo-cap, so on a short window the gallery needs
     LESS width than any fixed ratio hands it, and the leftover opens up as
     slack between the photo and the info column -- a left margin that grows
     as the window gets shorter while the right one stays put. A ratio tuned
     to look even at one height is wrong at every other.

     So the first track asks for exactly what the gallery needs and no more.
     Whichever of the two terms is smaller wins, and either way the gallery
     fills its track with nothing left over:

       - min(58%, ...) on a tall window, where the cap never binds and the
         photo is sized by this track;
       - the calc on a short one, which is the gallery's own width at the cap:
         photo + gap + thumb, and a thumb is (photo - 4 gaps) / 5, so the
         whole column comes to 1.2 photo + 0.2 gap. Those two constants are
         PD_THUMB_WINDOW = 5 written out -- change the thumb count in main.js
         and they change with it.

     With slack gone, the space between photo and info is just this rule's
     gap, so setting that to the container's own side padding makes the info
     column's left margin exactly its right one at every width. */
  .product-detail-split.split {
    /* Where the sticky gallery comes to rest: directly under the breadcrumb
       bar, which is stuck to the top of the screen. Not a round number by
       accident -- it is that bar's exact height, 12px of padding either side
       of a 23px line (0.8rem on the body's 1.8 line-height). Change the bar's
       type size or padding and this has to move with it, or the gallery will
       either slide under the bar or leave a gap below it. */
    /* Where the breadcrumb-and-photos block comes to rest: a little air above
       the breadcrumb, and that is all. The breadcrumb is part of what sticks,
       so it is still on screen at the stop -- no pinned bar of its own. */
    --pd-sticky-top: 12px;
    /* The breadcrumb's own line box (0.8rem on the body's 1.8 line-height)
       and the space under it. Both are inside the sticky block, so both come
       off the height the photo has to play with. */
    --pd-crumb: 23px;
    --pd-crumb-gap: 12px;
    /* Fill the screen below the breadcrumb, less a little air at the bottom,
       so the stopped block sits in the viewport with no dead space under it.
       Only binds on a SHORT window -- see the track below, where page width
       takes over on a tall one. */
    --pd-photo-cap: calc(
      100vh - var(--pd-sticky-top) - var(--pd-crumb) - var(--pd-crumb-gap) - 24px
    );
    --pd-gallery-gap: 10px;
    grid-template-columns:
      min(64%, calc(var(--pd-photo-cap) * 1.2 + var(--pd-gallery-gap) * 0.2))
      1fr;
    gap: clamp(16px, 4vw, 64px);
  }
  /* One photo, so no thumb column. The track above is 1.2x the photo's cap
     precisely to leave room for one; without it that extra fifth is not a
     narrower photo, it is dead space opening between the photo and the
     product info. So the track drops to the photo's own width.

     Selected on the gallery from its parent, because the class that says
     "one photo" is set by initProductGallery on the gallery itself and the
     track is declared up here. */
  .product-detail-split.split:has(.product-detail-gallery.is-single-slide) {
    grid-template-columns: min(64%, var(--pd-photo-cap)) 1fr;
  }
  /* The breadcrumb and the photos stick as one block, which is the whole
     point of the wrapper: the text stops with the images, sits over the page
     background like any other text rather than on a bar of its own, and when
     the info column finally runs out the two scroll away together.

     No background and no z-index, and neither is an oversight. Nothing ever
     passes beneath this: the gallery is inside it, and the info column is a
     separate grid track alongside. It releases with the section, so the
     reviews below never reach it either. */
  .product-detail-sticky {
    position: sticky;
    top: var(--pd-sticky-top);
  }
  .product-detail-sticky .breadcrumb { margin-bottom: var(--pd-crumb-gap); }
  /* Drops the info column by the height of the breadcrumb and the gap under
     it, so RINGTAIL GEAR starts level with the top of the photos rather than
     level with the breadcrumb above them. The first two terms are exact and
     derived from the breadcrumb's own values, so they track any change to it.

     The third is an eyeball correction and the only number here that is: box
     alignment is not optical alignment. The photo's top edge is a hard 1.3px
     line, while the eyebrow's is a cap-height that sits some way inside its
     line box, so boxes that line up perfectly still do not LOOK lined up.
     Tune this one, not the two above it. */
  .product-detail-info {
    --pd-info-nudge: 8px;
    padding-top: calc(var(--pd-crumb) + var(--pd-crumb-gap) + var(--pd-info-nudge));
  }
  .product-detail-gallery {
    display: flex;
    /* .product-detail-media is first in the markup and .product-thumbs
       second -- reversing the row puts the thumbnail column on the left
       without needing to reorder the actual DOM. */
    flex-direction: row-reverse;
    /* Not stretch: that would force the image's height to match the
       thumb column, overriding its own 1:1 aspect-ratio and making it
       non-square. The image sizes itself (aspect-ratio from its
       flex-computed width); the thumb column matches it via JS instead
       (see initProductThumbSizing in main.js). */
    align-items: flex-start;
    /* row-reverse runs the main axis right-to-left, so flex-end packs toward
       the LEFT edge. That matters once the photo is height-capped below and
       the row no longer fills its column: the slack has to fall on the right,
       between the photo and the product info, rather than opening a gap to
       the left of the thumbnails. */
    justify-content: flex-end;
    /* One spacing value for the whole gallery: between the thumb column and
       the main image, and between the thumbs themselves. It is declared on
       .product-detail-split and inherited down to here, because the split's
       own track formula has to do arithmetic with it -- see there. Also read
       back by sizeProductThumbs in main.js, which subtracts it before
       dividing the image's height into thumbs, so the column still ends level
       with the photo. */
    gap: var(--pd-gallery-gap);
  }
  /* .product-detail-gallery .product-detail-media (not just
     .product-detail-media) so border-radius:0 outranks the plain,
     unconditional rule declared later in the file at equal specificity. */
  /* The photo and its dot row share a column, so the dots sit under the photo
     rather than becoming a third item in this row-reverse row -- which would
     have stood them up as a vertical strip between the thumbs and the image.
     The flex sizing moves to the column; the photo just fills it. */
  .product-detail-gallery .product-detail-media-col {
    flex: 1;
    min-width: 0;
    /* A height cap, written as a width: the photo is square, so bounding one
       bounds the other. Without it a wide window sizes the photo purely from
       the column it sits in and it grows taller than the screen -- the
       gallery is sticky at top: 24px, so the bottom of the photo and its dot
       row simply never come into view. The cap leaves room for both plus the
       sticky offset. The thumbs follow: sizeProductThumbs divides whatever
       height this ends up at, so capping the photo shrinks them with it.

       Declared on .product-detail-split, whose track formula sizes the
       gallery's column from this same value so the two cannot disagree. */
    max-width: var(--pd-photo-cap);
  }
  .product-detail-gallery .product-detail-media {
    border-radius: 0;
    /* Square corners, but still outlined -- the gray edge is what separates
       a near-black product shot from the near-black page behind it. */
    border: 1.3px solid var(--border-strong);
  }
  /* Scoped to .product-detail-gallery (not just .product-thumbs/.product-thumb)
     so these outrank the plain, unconditional rules declared later in the
     file at equal specificity. */
  .product-detail-gallery .product-thumbs {
    flex-direction: column;
    /* The same value that separates the column from the photo, so the space
       around a thumb is even on every side. The column still ends level with
       the main image: sizeProductThumbs reads this gap and takes the three of
       them off the height before dividing it into PD_THUMB_WINDOW rows. */
    gap: var(--pd-gallery-gap);
    flex-shrink: 0;
    margin-top: 0;
  }
  /* Exact square size (big, small gap between) is computed in JS from the
     main image's actual rendered height -- see initProductThumbSizing in
     main.js. CSS alone can't do it: the thumb size depends on the image's
     height, which depends on the width left over after the thumb column,
     which depends on the thumb size -- a genuine circular dependency that
     flexbox + aspect-ratio doesn't resolve reliably here. */
  /* Same gray edge as the main image. Now that the thumbs are separated
     rather than flush, each border stands on its own -- no doubled seams. */
  .product-detail-gallery .product-thumb {
    flex: none;
    border-radius: 0;
    border: 1.3px solid var(--border-strong);
  }
  /* The border used to be the hover tell. With it gone the photo itself grows
     inside the (overflow: hidden) thumb, matching how the product cards
     behave on hover.

     The 1.01 resting scale is not decoration. Thumb sizes come out of a JS
     division of the main image's height, so they land on fractional pixels;
     the hover transform promotes the photo to its own layer, whose edges are
     no longer pixel-snapped to the box, and the thumb's gray placeholder
     background flashed through the seam along the bottom as the transition
     started and ended. A 1% overfill is invisible and leaves no seam to show
     through. backface-visibility keeps the same layer alive between hovers so
     the promotion itself isn't what blinks. */
  .product-detail-gallery .product-thumb img {
    transform: scale(1.01);
    transition: transform 0.35s ease;
    backface-visibility: hidden;
  }
  .product-detail-gallery .product-thumb:hover img { transform: scale(1.1); }
}
.product-detail-media {
  position: relative;
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-lg);
  /* Gray edge, same as the thumbs below it. Safe against the aspect-ratio
     because everything here is border-box: the border eats into the square
     rather than adding to it, so the box stays square and the JS that sizes
     the thumb column off this height still measures what it expects. */
  border: 1.3px solid var(--border-strong);
  overflow: hidden;
  /* A plain hand rather than the popup's ringed + and -: this photo grows on
     hover the way the product cards do, so it reads as a clickable image
     instead of a magnifier. Click-to-zoom still works (see initImageZoom),
     and the hand stays put while zoomed, since clicking again zooms out. */
  cursor: pointer;
}
.product-detail-media-track { width: 100%; height: 100%; position: relative; }
/* Pointer devices only. Below that the track is a swipeable filmstrip with
   its own overflow, scroll-snap and touch-action, and a transform on it would
   fight all three. */
@media (hover: hover) and (pointer: fine) {
  /* The same grow, easing and 1% resting overfill the product cards use --
     see .product-media-wrap .product-media-link .product-media-img for why
     the resting scale is 1.01 and not 1.

     Keyed to the track rather than to the photo, which is the element the
     cards grow. Here the photo already owns the 1.2s entrance transition and
     the slide already owns the zoom transform, so the wrapper between them is
     the one part of the stack free to take this without colliding with
     either. It scales whichever slide is showing, and .product-detail-media
     clips the overflow exactly as the cards' .product-media does. */
  .product-detail-media-track {
    transform: scale(1.01);
    backface-visibility: hidden;
    transition: transform 0.35s ease;
  }
  .product-detail-media:hover .product-detail-media-track { transform: scale(1.08); }
  /* Zoomed, the active slide is already at 2.2 and the pointer is by
     definition still over the photo -- growing the wrapper as well would
     multiply the two into 2.38. Declared after the hover rule so it wins on
     source order at equal specificity. */
  .product-detail-media.is-zoomed .product-detail-media-track { transform: scale(1.01); }
}
.product-detail-media-slide {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: none;
  align-items: center;
  justify-content: center;
  transition: transform 0.15s ease-out;
  background:
    radial-gradient(circle at 30% 20%, rgba(169,136,90,0.06), transparent 70%),
    #5e5e5e;
}
.product-detail-media-slide.is-active { display: flex; }
.product-detail-media.is-zoomed .product-detail-media-slide.is-active { transform: scale(2.2); }
.product-thumbs { display: flex; gap: 10px; margin-top: 14px; }
.product-thumb {
  width: 68px;
  height: 68px;
  padding: 0;
  border-radius: var(--radius-sm);
  border: 1.3px solid var(--border-strong);
  /* Near-black, not the gray placeholder this used to carry. Thumb heights
     come out of a JS division and land on fractional pixels, so a hairline of
     the box can show below the photo -- against #5e5e5e that read as a gray
     line along the bottom edge, most obviously under the mosaic tile. The
     fallback icon these were gray for is no longer used by any product. */
  background: #131313;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  overflow: hidden;
  transition: border-color 0.2s ease;
}
.product-thumb svg { width: 55%; height: 55%; color: var(--text-faint); }
/* The border is gray and stays gray -- no state lightens it. Neither hover
   nor .is-active, which used to mark the photo currently in the main image
   and was the white edge that showed on the first thumb at rest. The desktop
   gallery's hover tell is the photo growing inside the thumb instead; the
   current photo is told by the main image itself. */

.detail-divider { border-top: 1.3px solid var(--border-strong); margin: 16px 0; }
/* .product-detail-info and .quick-view-info-scroll both lay their children
   out with explicit flex `order` values (see below) instead of relying on
   source order, so a plain divider with the default order:0 would float
   to the very front of the column regardless of where it sits in the
   HTML. Give each divider the same order as the element right before it
   in reading order -- ties resolve by DOM order, so it still lands
   immediately after that element. */
.product-detail-info .detail-divider { order: 1; }
.product-detail-info .detail-divider.detail-divider-after-cart { order: 3; }
.quick-view-info-scroll .detail-divider { order: 1; }
/* NO .detail-divider-after-cart in the popup, and this note is here to stop
   it being added a third time.

   There already IS a line under Add to Cart and it is not a divider: it is
   .quick-view-link-full's own border-top, which that link has carried since
   it was built -- 18px of padding under a rule, so View Full Details reads
   as a footer to the column rather than as another thing in the form. Adding
   a divider element under the actions puts a second rule about 22px above
   that one. */
.quick-view-info .detail-divider { border-top-color: var(--on-dark-border); }
.product-color-select { margin: 20px 0; display: flex; align-items: center; gap: 12px; }
/* display: flex above outranks the browser's own [hidden] rule, so a block
   the script hides -- no sizes, or no colours in Shopify -- has to be told. */
.product-color-select[hidden] { display: none; }
.color-select-row { display: flex; align-items: flex-start; gap: 12px; }
.color-select-row .color-select-dropdown { flex-shrink: 0; }
.color-select-row .qv-stock-status { flex: 1 1 auto; min-width: 0; padding-top: 12px; }
.product-color-label {
  font-family: var(--font-display);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
}
.color-select-dropdown { position: relative; }
/* Flashed when Add to Cart is blocked because no color is selected yet. */
.color-select-dropdown.needs-color .color-select-trigger {
  border-color: var(--danger);
  animation: color-select-shake 0.4s ease;
}
@keyframes color-select-shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-6px); }
  40% { transform: translateX(5px); }
  60% { transform: translateX(-4px); }
  80% { transform: translateX(3px); }
}
.color-select-trigger {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 14px;
  border-radius: var(--radius-sm);
  border: 1.3px solid var(--border-strong);
  background: transparent;
  color: var(--text);
  font-family: var(--font-body);
  font-size: 0.85rem;
  cursor: pointer;
}
.color-select-trigger svg { width: 13px; height: 13px; color: var(--text-muted); transition: transform 0.2s ease; flex-shrink: 0; }
[data-color-select-label].is-placeholder { color: var(--text-muted); }
.quick-view-info [data-color-select-label].is-placeholder { color: var(--on-dark-muted); }
.color-select-dropdown.is-open .color-select-trigger { border-color: var(--text); }
.color-select-dropdown.is-open .color-select-trigger svg { transform: rotate(180deg); }
.color-select-list {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  min-width: 160px;
  background: var(--surface);
  border: 1.3px solid var(--border-strong);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
  z-index: 20;
  padding: 6px;
  list-style: none;
  margin: 0;
}
.color-select-list li {
  list-style: none;
  padding: 8px 10px;
  border-radius: 6px;
  font-family: var(--font-body);
  font-size: 0.85rem;
  color: var(--text-muted);
  cursor: pointer;
  white-space: nowrap;
}
.color-select-list li:hover { background: rgba(0, 0, 0, 0.06); color: var(--text); }
.color-select-list li[aria-selected="true"] { color: var(--text); font-weight: 600; }
.color-select-list li[data-placeholder] { font-weight: 400; }
/* A size row is "Large" followed by what Large actually measures -- the
   choice is made on the numbers, not on the word, which means nothing on its
   own. The name stays in the row's own weight and colour; the measurement
   sits after it, smaller and quieter, and the trigger shows the name alone
   once the row is picked (see data-label). */
.color-select-list li .option-note {
  display: block;
  margin-top: 2px;
  font-size: 0.76rem;
  color: var(--text-faint);
  font-weight: 400;
}
.quick-view-info .color-select-list li .option-note { color: var(--on-dark-faint); }
/* Hidden for the products that come in one size. The block is display:flex,
   which outranks the browser's own [hidden] rule, so it needs saying here. */
.product-size-select[hidden] { display: none; }
.pd-buybar-size[hidden] { display: none; }
.color-select-list li[data-placeholder][aria-selected="true"] { color: var(--text-muted); }
.quick-view-info .color-select-trigger { border-color: var(--border-strong); color: var(--on-dark); }
.quick-view-info .color-select-dropdown.is-open .color-select-trigger { border-color: var(--on-dark); }
.quick-view-info .color-select-trigger svg { color: var(--on-dark-faint); }
.quick-view-info .color-select-list { border-color: var(--border-strong); }
.quick-view-info .color-select-list li { color: var(--on-dark-muted); }
.quick-view-info .color-select-list li:hover { background: rgba(251, 247, 243, 0.08); color: var(--on-dark); }
.quick-view-info .color-select-list li[aria-selected="true"] { color: var(--on-dark); }
.quick-view-info .color-select-list li[data-placeholder][aria-selected="true"] { color: var(--on-dark-muted); }
.qv-stock-status {
  display: flex;
  align-items: flex-start;
  gap: 7px;
  font-family: var(--font-body);
  text-transform: uppercase;
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--title-red);
  min-width: 0;
}
.qv-stock-status:not([data-status]) { display: none; }

/* ---- A product with a single photo ----
   The arrows, the dots and the thumb strip all describe a set of photos to
   move between. With one photo there is no set: the arrows step nowhere, the
   single dot says nothing, and the thumb column was taking a fifth of the
   gallery's width to show the same picture a second time. Higher specificity
   than the rules they switch off, so they win wherever those are written --
   inside a media query or not. This lifts by itself the moment a product
   carries real photography. */
.product-detail-gallery.is-single-slide .pd-media-arrow,
.product-detail-gallery.is-single-slide .media-dots,
.product-detail-gallery.is-single-slide .product-thumbs,
.quick-view-modal.is-single-slide .qv-media-arrow,
.quick-view-modal.is-single-slide .media-dots,
.quick-view-modal.is-single-slide .qv-media-thumbs { display: none; }

/* ---- The popup: the stock line's slot is always there --------------------
   .color-select-row stacks at every width, so the status sits UNDER the
   dropdown -- and being display:none until a colour is picked, it appeared
   out of nothing and pushed the price, the qty stepper, Add to Cart and View
   Full Details down the panel with it.

   So in the popup the row is held open whether or not there is anything in
   it: visibility rather than display, which keeps the box (and the 12px of
   padding above it) in the layout while showing nothing -- the dot on the
   ::before included.

   The reserved height is that padding plus one line, and line-height is
   pinned here rather than left to inherit so the two numbers are the same
   number. min-height, not height, so a status long enough to wrap still
   fits; it is only the first line that has to cost nothing.

   Scoped to .quick-view-info. The product page has the same markup and the
   same jump, but its column is a page rather than a panel and reserving the
   slot there is a separate call. */
.quick-view-info .qv-stock-status {
  line-height: 1.3;
  min-height: calc(12px + 1.3em);
}
.quick-view-info .qv-stock-status:not([data-status]) {
  display: flex;
  visibility: hidden;
}
/* Two margins met under the stock line and neither collapsed -- these are
   flex children, so .product-color-select's 20px bottom and
   .quick-view-actions' 14px top simply added up to 34px of nothing between
   the status and the quantity stepper. Dropping the first leaves the
   actions' own 14px, which reads against the 12px above the status rather
   than dwarfing it. Only the bottom: the 20px above the block still
   separates it from the divider over it. */
.quick-view-info .product-color-select { margin-bottom: 0; }
.qv-stock-status::before {
  content: '';
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
  margin-top: 7px;
  background: var(--title-red);
}

.quick-view-info {
  /* The right pair of the panel's corners. Now that the white lives on this
     element rather than on the panel behind it, this half has to carry the
     curve itself or its square corner paints over the panel's rounded one. */
  border-radius: 0 var(--qv-radius) var(--qv-radius) 0;
  position: relative;
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
  background: var(--modal-panel);
  color: var(--on-dark);
  /* The divider between the photo and this column. On the info side rather
     than as a border-right on .quick-view-media, because that column's width
     is calculated to make it an exact square and a border would eat 1.3px out
     of it. This column is the grid's 1fr, so it just gives up the space.
     Dropped in the stacked mobile form, where the two sit one above the other
     and a line down the left of the text would mean nothing. */
  border-left: 1.3px solid var(--border-strong);
}
.quick-view-info-scroll {
  padding: 48px 64px;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.quick-view-info-scroll::-webkit-scrollbar { display: none; }
.quick-view-scroll-cue {
  position: absolute;
  right: 18px;
  bottom: 18px;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: rgba(251, 247, 243, 0.12);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--on-dark);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
  z-index: 2;
  animation: qv-scroll-cue-bounce 1.6s ease-in-out infinite;
}
.quick-view-scroll-cue svg { width: 16px; height: 16px; }
.quick-view-info.is-scrollable .quick-view-scroll-cue { opacity: 1; }
@keyframes qv-scroll-cue-bounce {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(4px); }
}
.product-detail-header .product-price,
.quick-view-info .product-price { margin: 12px 0 10px; }
.product-detail-header .product-price .price,
.quick-view-info .product-price .price { font-size: 1.5625rem; font-family: var(--font-body); }
.product-detail-header .product-price .price,
.quick-view-info .product-price .price { color: var(--text); }
.quick-view-info .product-price .price-was { color: var(--on-dark-faint); }
/* Match the quick view popup's h3 exactly (same size/weight) instead of
   the page's own much larger default h1 -- consistency between the two
   "product page" contexts matters more here than semantic heading size. */
/* --font-body, not the --font-display every other heading on the site takes
   from the h1-h4 rule. Koulen is an uppercase-only face -- its Latin
   lowercase maps to capitals -- so a product title set in it reads as SUMMIT
   DAYPACK however it is written in products.js. There is no text-transform to
   remove; the family IS the capitalisation, and this is the only way off it.

   Lambda instead, which is what the card titles under the grid already use,
   so the name reads the same in the grid, in the popup and on the page. */
.product-detail-header h1,
.quick-view-info h3 { font-family: var(--font-body); font-size: 1.5625rem; font-weight: 400; }
.quick-view-info h3 { color: var(--on-dark); }
.qv-mini-logo { display: block; height: 18px; width: auto; margin-bottom: 10px; opacity: 0.85; }
.quick-view-info p { line-height: 1.65; color: var(--on-dark-muted); }
.quick-view-info ul { margin: 16px 0 6px; padding-left: 20px; list-style: disc; }
.quick-view-info ul li { margin-bottom: 8px; color: var(--on-dark-muted); font-size: 0.92rem; }
.quick-view-info .product-color-label { color: var(--on-dark-muted); }
.quick-view-info .quick-view-link { color: var(--on-dark-muted); }
.quick-view-info .quick-view-link:hover { color: var(--on-dark); }
/* No price here, and the qty stepper stacks above a full-width Add to
   Cart button instead of sitting side by side with it -- at every
   viewport width, same as the product detail page's cart row. */
.quick-view-actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 12px;
  margin-top: 14px;
}
.quick-view-actions .qv-actions-price { display: none; }
.quick-view-actions [data-qv-add-to-cart] {
  width: 100%;
}
.quick-view-actions .qty-stepper {
  width: 100%;
  height: 46px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-radius: 999px;
  border: 1.3px solid var(--border-strong);
  padding: 0 6px;
}
.quick-view-actions .qty-stepper-btn {
  display: flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; flex-shrink: 0;
  border: none; background: transparent;
  color: var(--on-dark); cursor: pointer;
}
.quick-view-actions .qty-stepper-btn svg { width: 18px; height: 18px; }
.quick-view-actions .qty-stepper .qty-input {
  flex: 1;
  width: auto; height: auto;
  border: none; background: transparent;
}

/* Partial-fill star rating: an outline row and a gold row stacked exactly on
   top of each other; the gold row is clipped to a % width so a rating like
   4.8/5 fills the 5th star only 80% of the way instead of jumping to whole
   stars. */
.star-rating { position: relative; display: inline-flex; }
.star-rating-row { display: flex; gap: 2px; }
.star-rating-row svg { width: 16px; height: 16px; flex-shrink: 0; }
.star-rating-bg svg { fill: rgba(251, 247, 243, 0.18); }
.star-rating-fg { position: absolute; top: 0; left: 0; overflow: hidden; white-space: nowrap; }
.star-rating-fg svg { fill: var(--title-red); }
.star-rating.is-small .star-rating-row svg { width: 13px; height: 13px; }

.qv-review-card { padding-bottom: 18px; border-bottom: 1.3px solid var(--on-dark-border); }
.qv-review-card.is-last-visible { border-bottom: none; padding-bottom: 0; }
.qv-review-card-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.qv-review-date { font-family: var(--font-body); font-size: 0.75rem; color: var(--on-dark-faint); }
.qv-review-text { font-size: 0.88rem; line-height: 1.55; color: var(--on-dark-muted); margin: 0 0 6px; }
.qv-review-author { font-family: var(--font-display); font-size: 0.76rem; letter-spacing: 0.05em; text-transform: uppercase; color: var(--on-dark); }

/* ---- Write a Review modal ---- */
.review-modal-backdrop {
  position: fixed;
  inset: 0;
  /* The same darkening every other full-screen viewer lays down -- see
     --overlay-dim. Was rgba(26, 26, 26, 0.6): lighter and warmer, so opening
     this popup dimmed the page to a different level than the quick view or
     the stacked gallery did. */
  background: var(--overlay-dim);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
  z-index: 700;
}
.review-modal-backdrop.open { opacity: 1; pointer-events: auto; }
.review-modal {
  position: fixed;
  top: 50%;
  left: 50%;
  width: min(460px, 92vw);
  max-height: 85vh;
  overflow-y: auto;
  background: var(--modal-panel);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  z-index: 701;
  opacity: 0;
  pointer-events: none;
  transform: translate(-50%, -50%) scale(0.96);
  transition: opacity 0.25s ease, transform 0.25s ease;
}
.review-modal.open { opacity: 1; pointer-events: auto; transform: translate(-50%, -50%) scale(1); }
.review-modal-handle { display: none; }
.review-modal-close {
  position: absolute;
  top: 16px;
  right: 16px;
  z-index: 2;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 1.3px solid var(--border-strong);
  background: var(--surface);
  color: var(--text);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: border-color 0.2s ease, color 0.2s ease;
}
.review-modal-close:hover { border-color: var(--on-dark); color: var(--on-dark); }
.review-modal-close svg { width: 16px; height: 16px; transition: transform 0.2s ease; }
.review-modal-close:hover svg { transform: scale(1.35); }
.review-modal-scroll { padding: 48px 32px 36px; }

.review-modal-product { display: flex; align-items: center; gap: 14px; margin-bottom: 30px; }
.review-modal-product-media {
  width: 56px;
  height: 56px;
  flex-shrink: 0;
  border-radius: var(--radius-sm);
  background: radial-gradient(circle at 30% 20%, rgba(169,136,90,0.06), transparent 70%), #5e5e5e;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.review-modal-product-media svg { width: 55%; height: 55%; color: var(--text-faint); }
.review-modal-product h3 { color: var(--on-dark); font-size: 1.05rem; margin: 0; }

.review-modal-step { text-align: center; padding-bottom: 6px; }
.review-modal-prompt { color: var(--on-dark-muted); font-size: 0.9rem; margin: 0 0 18px; }
.review-star-picker { display: flex; justify-content: center; gap: 10px; }
.review-star-btn {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  transition: transform 0.15s ease;
}
.review-star-btn:hover { transform: scale(1.1); }
.review-star-btn svg {
  width: 34px;
  height: 34px;
  fill: none;
  stroke: var(--on-dark-border-strong);
  stroke-width: 1.5px;
  transition: fill 0.15s ease, stroke 0.15s ease;
}
.review-star-btn.is-filled svg { fill: var(--title-red); stroke: var(--title-red); }

/* Read-only star recap shown on the form step: same rating, no longer
   editable. Filled stars appear already red and pulse once together. */
.review-form-stars-wrap { position: relative; display: flex; justify-content: center; }
.review-form-stars { position: relative; z-index: 2; display: flex; justify-content: center; gap: 8px; }
/* These are <button>s now -- the rating can be changed here, not just read
   back -- so they need the usual stripping down to their artwork. */
.review-form-star {
  padding: 0;
  border: none;
  background: transparent;
  line-height: 0;
  cursor: pointer;
}
/* Unrated stars take the exact gray the overall-rating row uses for its empty
   stars (.star-rating-bg), so the two read as the same thing in different
   sizes.

   stroke-width: 0 is what makes a translucent colour safe here. A stroke
   straddles the shape's edge, so its inner half lies over the fill and two
   18% layers composite to about 33% right along the border -- that is the
   lighter outline these had before. With no stroke width there is nothing to
   double up, and the colour can stay the token's own rgba rather than an
   opaque approximation that would only match over one backdrop. */
.review-form-star svg {
  width: 36px;
  height: 36px;
  fill: rgba(251, 247, 243, 0.18);
  stroke: rgba(251, 247, 243, 0.18);
  stroke-width: 0;
}
.review-form-star.is-filled svg {
  fill: var(--title-red);
  stroke: var(--title-red);
  transform: scale(1);
  /* `backwards` is what makes the wave read as a wave: it applies the 0%
     frame during the delay, so a star that has not had its turn yet sits
     small and dark from the moment of the click rather than showing its
     finished red and then pulsing for no visible reason. */
  animation: review-form-star-pulse 0.6s ease backwards;
  animation-delay: var(--star-delay);
}
@keyframes review-form-star-pulse {
  /* Just the plain star: normal size, the brand red -- which is the darker
     one, since #ff5347 at 45% is the bright flash it pulses through rather
     than the base. Fixed values, not the rest variables: this is the "not
     yet" state and should look the same for every star whether or not the
     cursor is on one. Only the landing at 100% follows rest. */
  0% { transform: scale(1); fill: var(--title-red); stroke: var(--title-red); }
  45% { transform: scale(1.18); fill: #ff5347; stroke: #ff5347; }
  100% { transform: scale(1); fill: var(--title-red); stroke: var(--title-red); }
}
/* Desktop only, this popup only: hovering previews the rating the click would
   set. Nothing grows and nothing brightens -- the run simply fills in at the
   plain red, and the empty stars beyond it stay gray. */
@media (min-width: 860.02px) {
  .review-form-star.is-filled svg { transition: fill 0.2s ease, stroke 0.2s ease; }
  .review-form-star:not(.is-filled) svg { transition: fill 0.2s ease, stroke 0.2s ease; }
  /* The whole run up to the cursor turns plain red -- the star under it and
     every star before it, at the same colour and the same size, so the row
     reads as the rating the click would set rather than singling one out.

     The :has() selector is what reaches leftward: a star with the hovered
     star somewhere after it is, by definition, before it.

     Nothing here is permanent -- every star keeps its own resting fill, so
     the moment the pointer leaves, the ones that were only previewing go back
     to gray and the committed ones stay red. */
  .review-form-star:hover svg,
  .review-form-star:has(~ .review-form-star:hover) svg {
    fill: var(--title-red);
    stroke: var(--title-red);
  }
}

.review-modal-form { display: flex; flex-direction: column; gap: 16px; margin-top: 26px; }
.review-modal-form[hidden] { display: none; }
.review-field { display: flex; flex-direction: column; gap: 6px; }
.review-field-label {
  font-family: var(--font-display);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--on-dark-muted);
}
.review-field input, .review-field textarea {
  width: 100%;
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  border: 1.3px solid var(--on-dark-border);
  background: rgba(251, 247, 243, 0.06);
  color: var(--on-dark);
  font-family: var(--font-body);
  font-size: 0.9rem;
  resize: vertical;
}
.review-field input::placeholder, .review-field textarea::placeholder { color: var(--on-dark-faint); }
.review-field input:focus, .review-field textarea:focus { outline: none; border-color: var(--on-dark); }
.review-modal-form .btn-light { margin-top: 6px; width: auto; align-self: center; padding-left: 32px; padding-right: 32px; }

.review-modal-success { position: relative; display: none; flex-direction: column; align-items: center; text-align: center; padding: 24px 10px 10px; gap: 10px; }
.review-modal-success.is-visible { display: flex; }
.review-modal-success svg { width: 44px; height: 44px; color: #5cb87a; }
.review-modal-success p { font-family: var(--font-display); font-size: 1.05rem; color: var(--on-dark); margin: 0; }
.review-modal-success span { font-size: 0.85rem; color: var(--on-dark-muted); }

/* Extra excitement for 4-5 star reviews: a gold checkmark. */
.review-modal-success.is-celebrate svg { color: var(--title-gold); }

@media (max-width: 760px) {
  .review-modal {
    top: auto;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    max-width: 100%;
    max-height: calc(100vh - var(--sheet-top));
    max-height: calc(100dvh - var(--sheet-top));
    border-radius: 0;
    transform: translateY(100%);
  }
  .review-modal.open { transform: translateY(0); }
  .review-modal-handle {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 28px;
    flex-shrink: 0;
    position: sticky;
    top: 0;
    z-index: 5;
    background: var(--modal-panel);
  }
  .review-modal-handle::before {
    content: '';
    width: 44px;
    height: 5px;
    border-radius: 3px;
    background: var(--on-dark-border-strong);
  }
  .review-modal-scroll { padding: 8px 24px 32px; }
  /* Shown on a phone too now, in the sheet's top-right corner: the drag
     handle alone is a gesture you have to know about. Above that handle,
     which is a sticky bar across the top of the sheet -- level with it, it
     would take the taps meant for this. */
  .review-modal-close { display: flex; z-index: 6; }
}

.qty-input {
  width: 56px;
  height: 46px;
  text-align: center;
  background: var(--surface);
  border: 1.3px solid var(--border-strong);
  border-radius: var(--radius-sm);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 0.95rem;
  flex-shrink: 0;
  -moz-appearance: textfield;
  transition: border-color 0.15s ease;
}
.qty-input::-webkit-outer-spin-button,
.qty-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.qty-input:focus { outline: none; border-color: #d6d6d6; }
.quick-view-info .qty-input { background: transparent; border-color: var(--on-dark-border); color: var(--on-dark); }
.quick-view-info .qty-input:focus { border-color: #d6d6d6; }
/* Desktop: the stepper wrapper generates no box of its own — the arrow
   buttons stay hidden and .qty-input renders exactly like a bare input,
   same as before the wrapper existed. Mobile media queries below turn it
   into a real bordered pill with visible arrows. */
.qty-stepper { display: contents; }
.qty-stepper-btn { display: none; }

.quick-view-link {
  position: relative;
  display: inline-flex;
  align-items: center;
  font-family: var(--font-body);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 0.82rem;
  font-weight: 600;
  gap: 8px;
  color: var(--text-muted);
  transition: color 0.2s ease;
}
.quick-view-link:hover { color: var(--text); }
.quick-view-link svg { width: 16px; height: 16px; transition: transform 0.5s ease; }
.quick-view-link:hover svg { animation: quick-view-link-spin 0.5s ease; }

/* Stands alone at the bottom of the quick view popup now that the
   Contents/Details/Reviews accordion is gone. */
.quick-view-link-full {
  order: 5;
  justify-content: center;
  width: 100%;
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1.3px solid var(--on-dark-border);
  font-size: 0.88rem;
}
.quick-view-link-full svg { width: 17px; height: 17px; }
/* Colour comes from .quick-view-info .quick-view-link up the file --
   --on-dark-muted at rest, --on-dark on hover. Nothing is set here; the pair
   of rules that painted this one the brand red instead has been removed. */

/* ---- The popup's text link: the arrow holds still ------------------------
   View Full Details inherits the shared spin from .quick-view-link:hover svg.
   That is the only thing it needs from that rule: nothing about the arrow
   changes on hover, so the animation is turned off and no replacement is put
   in its place. The underline below is the whole of the hover state.

   Featured Collection's View All Products used to be the other half of every
   rule here. That link is gone from the markup, so its selectors have gone
   with it rather than being left to match nothing -- .home-story-copy
   .story-cta, which was written to match it, is what still uses the
   underline below.

   Everything the arrow used to do here -- a scale, then a longer drawn path,
   with the overflow and transition overrides those needed -- has been removed
   rather than left inert. */
.quick-view-info .quick-view-link-full:hover svg { animation: none; }
/* The header nav's underline, in red. Same shape exactly -- a 1px rule that
   drops into place from 10px above while it fades up, and lifts back out the
   same way on leave (see .main-nav a::after).

   Hung off the SPAN but stretched past it, rather than put on the link
   itself. The link would seem the obvious place, but the popup's is
   `width: 100%` with its content centred -- a rule across it would run the
   full width of the panel instead of the width of the words. The span is the
   text, and -25px on its right edge is the arrow's 17px plus the flex row's
   8px gap, so the line reaches the arrow's far side exactly. Both links use
   the same two numbers, which is why one value covers them.

   currentColor, so it is whatever red the text is at that moment: it fades in
   already brightening toward --btn-red-light alongside the label rather than
   arriving on a colour of its own. */
.quick-view-info .quick-view-link-full span,
.home-story-copy .story-cta span,
.contact-faq-more span { position: relative; }
.quick-view-info .quick-view-link-full span::after,
.home-story-copy .story-cta span::after,
.contact-faq-more span::after {
  content: '';
  position: absolute;
  left: 0;
  right: -25px;
  bottom: -3px;
  height: 1px;
  background: currentColor;
  opacity: 0;
  transform: translateY(-10px);
  transition: transform 0.3s ease, opacity 0.3s ease;
}
.quick-view-info .quick-view-link-full:hover span::after,
.home-story-copy .story-cta:hover span::after,
.contact-faq-more:hover span::after {
  opacity: 1;
  transform: translateY(0);
}

.link-arrow {
  position: relative;
  display: inline-flex;
  align-items: center;
  font-family: var(--font-body);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 0.85rem;
  font-weight: 700;
  gap: 8px;
  color: var(--text-muted);
  transition: color 0.2s ease;
}
.link-arrow:hover { color: var(--text); }
/* --font-body: Lambda, the face the About Us copy is set in and the one
   body text carries everywhere on the site. These two used to run the .btn
   type ramp in --font-display alongside Quick Add and Add to Cart; the family
   is the part that has come back.

   The tracking comes from the same place: 0.0375em is what body sets and
   what the paragraph inherits, so these labels are spaced exactly as that
   copy is. It replaces the 0.2em the .btn ramp gave them, which is a large
   step down -- both labels come out noticeably narrower.

   The uppercasing is gone too, so what is left of that ramp is the size and
   the weight. text-transform: none rather than dropping the line, because
   .link-arrow and .quick-view-link both set uppercase themselves further up
   -- there is a declaration to beat here, not just one to omit. The labels
   reads as written in the markup: "View Full Details".

   .link-arrow sets its own type and .quick-view-link-full overrides the size
   .quick-view-link sets, so both need the full set rather than a value or
   two; and both selectors carry their page/panel scope, since the unscoped
   classes are shared with links elsewhere that keep the old look. */
.quick-view-info .quick-view-link-full {
  font-family: var(--font-body);
  font-size: 0.9375rem;
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0.0375em;
}
.link-arrow svg { width: 17px; height: 17px; transition: transform 0.5s ease; }
/* The two home story links are a .btn in the markup and a text link on
   desktop -- see .home-story-copy .story-cta. The arrow belongs to the
   second of those only, so it is hidden here and shown there. Declared on
   the base rather than inside a max-width query so that any width the
   desktop block does not cover gets the button, arrow and all off. */
.story-cta > svg { display: none; }
.link-arrow:hover svg { animation: quick-view-link-spin 0.5s ease; }

@keyframes quick-view-link-spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

@media (max-width: 760px) {
  .quick-view-modal {
    grid-template-columns: 1fr;
    top: auto; left: 0; right: 0; bottom: 0;
    width: 100%; max-width: 100%;
    /* As tall as the room under the header, rather than a flat 90vh that took
       no notice of where the header actually was -- plus the width of one
       rule.

       That last 1.3px is what puts the sheet's own top border ON the header's
       bottom border rather than directly beneath it. Flush, the two stacked
       into a single thicker line that read as the header's edge getting
       heavier when the sheet arrived; overlapped, there is one line, and it is
       the sheet's -- it rides up with the sheet as it opens and leaves with
       it. The header's own border is underneath, unchanged, for when nothing
       is open. */
    max-height: calc(100vh - var(--sheet-top) + 1.3px);
    max-height: calc(100dvh - var(--sheet-top) + 1.3px);
    /* One rule, along the top: the sheet comes to rest against the foot of the
       header, and this is the header's own bottom border carried on across the
       seam -- same width, same token -- so the two edges meet as one line
       rather than as a bar sitting under a gap. The other three sides stay
       open: it fills the width of the screen and runs off the bottom of it,
       so there is nothing there for an outline to describe.

       Drawn on the sheet, not on the strip inside it, so it holds its place
       while the sheet's contents scroll under it. */
    border: none;
    border-top: 1.3px solid var(--on-dark-border);
    /* Top corners only. Under 760px this stops being an inset panel and
       becomes a sheet that slides up from the bottom edge -- it is flush
       to the left, right and bottom of the screen, and rounding edges that
       are off-screen would only cut the corners off the content. The two
       corners you can actually see take the band curve. */
    border-radius: var(--qv-radius) var(--qv-radius) 0 0;
    transform: translateY(100%);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    /* Stops the scroll carrying on into the page behind the sheet, and leaves
       the rubber band alone. `none` would take the bounce with it, but only at
       both ends -- the property works per axis, not per edge, and the bottom
       one is wanted. What made the top bounce read badly was the rule down the
       left of the close button coming away from the sheet's top edge as the
       strip pulled down; that rule is gone, so there is nothing left to come
       apart. */
    overscroll-behavior: contain;
  }
  .quick-view-modal.open { transform: translateY(0); }
  /* The header keeps its own bottom edge the whole time, and the sheet keeps
     its top one. At rest the two sit on the same row and read as a single
     line; drag the sheet down and they part -- the header's stays where the
     header is, the sheet's travels with the sheet.

     That is both of them being right rather than one of them being wrong.
     Taking the header's away while the sheet was up did put a single
     travelling line on the screen, but it also left the header with no bottom
     edge for the length of the drag, which is worse than the thing it fixed. */
  .quick-view-handle {
    display: flex;
    align-items: center;
    justify-content: center;
    /* 52, not 44. The strip is also the X's box -- the two are one square
       -- so the strip's height is how much room the glyph has around it.
       At 44 the X sat 14px off each edge and read as crowded against the
       corner; at 52 it is 18. */
    width: 100%; height: 52px; flex-shrink: 0;
    position: sticky;
    top: 0;
    z-index: 5;
    /* White, matching the sheet under it. --modal-panel is #131313 and was
       right while this popup was a near-black panel; it has been white for a
       while and this strip was the last of the dark one still showing.

       Set here AND on the unscoped rule down the file. Those two are the same
       declaration written twice, which is normally the thing to avoid -- it
       is deliberate here because this strip has been wrong twice for ordering
       reasons, and one rule that loses silently is worse than two that agree.
       If they ever disagree, the later one is the live value. */
    background: var(--page-ground);
    /* Named into the same cell as the X. Without this the X claimed row 1 and
       auto-placement pushed the handle down into a row of its own -- an 88px
       strip with the button stacked above the grab bar, and the photo pushed
       44px further down than it belongs. */
    grid-row: 1;
    grid-column: 1;
    /* The rule under the strip. It used to be the photo's top border, which
       made it the picture's edge rather than the strip's -- so the X sat in an
       open-bottomed corner instead of a box. Here it closes the strip, and the
       photo's top edge sits straight against it. */
    border-bottom: 1.3px solid var(--border-strong);
  }
  .quick-view-handle::before {
    content: '';
    width: 44px; height: 5px; border-radius: 3px;
    background: var(--border-strong);
  }
  /* A square photo: the slide is the full width of the sheet, so the media
     box is as tall as the sheet is wide. */
  /* An explicit height, not aspect-ratio, because everything below this
     element (.qv-media-track, .qv-media-slide, .qv-media-slide-icon and the
     <img> itself) sizes with height:100%. Those percentages need a DEFINITE
     height to resolve against; the base rule's own height:100% only makes
     sense in the desktop two-column grid, and here -- a single-column grid
     with auto rows -- it resolves against a row this element is itself
     sizing, so the whole chain collapses (min-height:0 removes the last
     floor, and the lazy-loaded <img> contributes no intrinsic height).
     The modal is full viewport width at this breakpoint (left:0;right:0),
     so a full-width slide is 100vw across and the box is 100vw tall. The
     two gray rules are added on top of that rather than taken out of it
     (box-sizing is border-box everywhere, so the height has to grow by
     their combined 2.6px) -- otherwise the photo lands 2.6px short of the
     square this expression exists to guarantee. */
  .quick-view-media {
    /* Under 760px the panel is a sheet from the bottom edge and the media
       is the full width across its TOP, so the two corners it owns WOULD be
       the top pair rather than the left pair -- except that it does not own
       them. The handle strip is above it and is what meets the sheet's top
       edge, so the curve belongs there; rounding the photo as well put a
       second pair of curves one strip further down, in the middle of the
       sheet, cutting the corners off the picture against a white bar.

       Square, then. The bottom of the sheet is off-screen and square too, so
       this element rounds nothing at all. */
    border-radius: 0;
    min-height: 0;
    --qv-media-rule: 1.3px;
    /* One rule to add back, not two: the line above the photo belongs to the
       strip now, so only the bottom one comes out of this box. Same reason as
       before -- box-sizing is border-box, so a rule eats into the square this
       height exists to guarantee unless the height grows by it. */
    height: calc(100vw + var(--qv-media-rule));
    border-top: none;
    border-bottom: var(--qv-media-rule) solid var(--border-strong);
    flex-shrink: 0;
  }
  .qv-media-slide { min-height: 0; }
  /* Shown on a phone too -- see the note on .review-modal-close. Above the
     drag handle, which is a full-width sticky bar across the top of the sheet
     at z-index 5: level with it, the handle took the taps meant for this. */
  /* The X takes the right-hand end of the strip the drag handle sits in: a
     44px square against a 44px strip, flush to the edge, with one hairline
     down its left running the strip's full height. No ring -- the sheet is
     edge to edge and square-cornered, and a pill floating in the corner read
     as something stuck on.

     Sticky, not absolute, and placed into the handle's own grid cell so it
     takes no row of its own. Absolute, it scrolled away with the photo the
     moment the sheet was dragged -- measured at -142px after a 260px scroll --
     while the handle beside it stayed put. Now the two hold the top of the
     sheet together, which is what the strip is for.

     Its own ground, in the panel's colour, so the photo passing under it on a
     scroll does not show through the square. */
  .quick-view-close {
    display: flex;
    position: sticky;
    /* 6px, matching the margin below -- so where it sticks and where it sits
       are the same place. */
    top: 6px;
    right: auto;
    grid-row: 1;
    grid-column: 1;
    justify-self: end;
    align-self: start;
    /* A 40px button inside the 52px strip: 6px off the top, which centres it,
       and the page's own side margin off the right, so its right edge lines up
       with the header buttons above it and with everything else on the page.
       The sheet runs edge to edge, so that margin has to be said here -- it is
       .container's padding, restated. */
    width: 40px;
    height: 40px;
    margin: 6px clamp(16px, 4vw, 64px) 0 0;
    z-index: 6;
    /* The header's icon buttons' edge and fill, drawn round rather than
       square. It no longer needs a rule of its own along the bottom either --
       inset from the strip's edges, it stops covering the line the strip
       draws, so that line runs the full width on its own. */
    border: 1.3px solid var(--on-dark-border-strong);
    border-radius: 50%;
    background: transparent;
  }
  /* 20px, the size the header's own button draws its X at -- the two are the
     same control in two places, and at the quick view's usual 16 this one read
     as the smaller of the pair. */
  .quick-view-close svg { width: 20px; height: 20px; }
  .quick-view-info {
    max-height: none;
    overflow: visible;
    border-left: none;
    /* Square here. In the sheet form this half sits at the BOTTOM, and the
       sheet's bottom edge runs off the screen -- there is no corner of the
       panel for it to follow. The media half above owns the two that show. */
    border-radius: 0;
  }
  .quick-view-info-scroll { padding: 32px 26px; overflow: visible; }
  .quick-view-scroll-cue { display: none; }
}

/* ---- Quick view on a narrow DESKTOP window ----
   Same reasoning as the product gallery block further down: a resized
   desktop window still reports hover:hover / pointer:fine, a phone or
   tablet reports hover:none / pointer:coarse. Gating on the pointer type
   keeps the popup in its centered two-column desktop form at any window
   width on a computer, and leaves the bottom-sheet + filmstrip blocks
   above untouched on real touch devices. Declared after both of those
   blocks so it wins on source order at equal specificity. */
@media (max-width: 860px) and (hover: hover) and (pointer: fine) {
  /* Back to the centered dialog: equal inset on all four sides, square
     image column beside the info column, no sheet slide-up or page-level
     scrolling of the modal itself. */
  .quick-view-modal {
    top: var(--qv-inset); right: var(--qv-inset);
    bottom: var(--qv-inset); left: var(--qv-inset);
    width: auto; max-width: none; max-height: none;
    grid-template-columns: min(calc(100vh - 2 * var(--qv-inset) - 2 * var(--qv-border)), 56%) 1fr;
    /* And its border back: that comes off only for the mobile bottom sheet.
       Same for the corners -- the sheet rounds its top two, the dialog is
       square, and this block is the dialog. */
    border: var(--qv-border) solid var(--border-strong);
    /* Same curve as the base rule -- this block only re-states the inset
       geometry for a narrow window with a mouse, so it has to re-state the
       corner with it or the square one from further up wins. */
    border-radius: var(--qv-radius);
    transform: scale(0.96);
    overflow: hidden;
  }
  .quick-view-modal.open { transform: scale(1); }
  .quick-view-handle { display: none; }
  .quick-view-close { display: flex; }
  /* Fills the grid column again rather than the mobile square sheet box,
     and drops that box's two gray rules along with it -- here the photo is
     bounded by the modal's own border and the info column's. */
  .quick-view-media {
    height: 100%;
    /* The left half's outer corners, matching the panel's. Same reason as
       the base rule below -- this block re-states the inset geometry for a
       narrow window with a mouse, so it has to re-state this too. */
    border-radius: var(--qv-radius) 0 0 var(--qv-radius);
    border-top: none;
    border-bottom: none;
  }
  /* Undo the swipe filmstrip: one absolutely-positioned slide at a time,
     advanced by the arrows, with click-to-zoom in place. */
  .qv-media-track {
    display: block;
    overflow: visible;
    scroll-snap-type: none;
  }
  .qv-media-slide {
    position: absolute;
    inset: 0;
    display: none !important;
    flex: none;
    margin-right: 0;
    scroll-snap-align: none;
  }
  .qv-media-slide.is-active { display: flex !important; }
  .quick-view-media .qv-media-arrow { display: flex; }
  /* Pointer devices step with the arrows rather than swiping, so the
     thumbnail strip stays out -- and the product page's photos are opened
     by clicking them, so its tap-to-expand button does too. */
  .product-detail-media .qv-media-expand { display: none; }
  .qv-media-thumbs { display: none; }
  /* Info column scrolls within its own panel again, next to the image,
     instead of the whole sheet scrolling as one unit. */
  .quick-view-info {
    overflow: hidden;
    border-left: 1.3px solid var(--border-strong);
    /* The right pair, matching the panel. Same reason as the base rule. */
    border-radius: 0 var(--qv-radius) var(--qv-radius) 0;
  }
  .quick-view-info-scroll { padding: 48px 64px; overflow-y: auto; }
  .quick-view-scroll-cue { display: flex; }
  .quick-view-actions { order: 4; }
  /* The info column gets genuinely narrow at these widths. Padding scaled
     back from the full-size 48/64 so the controls inside it keep a sane
     amount of room. */
  .quick-view-info-scroll { padding: 32px 30px; }
  /* Stack Share under the rating rather than beside it. Side by side in a
     column this narrow, Share steals enough width that the score wraps
     onto its own line under the stars, which reads worse than simply
     putting the button on the next line. */
  .quick-view-info .pd-rating-share-row {
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;
  }
}

/* Desktop pointers: let the quantity input shrink inside its pill. A flex
   item will not go below its min-content width unless min-width is set, so
   in a narrow info column the number input overflows the stepper and shoves
   the "+" button outside the pill's border. No effect where there is room,
   and never matches a touch device, so mobile is unaffected. */
@media (hover: hover) and (pointer: fine) {
  .quick-view-actions .qty-stepper .qty-input { min-width: 0; }
  .product-detail-cart-row .qty-stepper .qty-input { min-width: 0; }
}

.product-info { padding: 14px 2px 4px; display: flex; flex-direction: column; gap: 6px; flex: 1; min-width: 0; }
/* No text-transform: the names read as written in products.js -- "Summit
   Daypack", not "SUMMIT DAYPACK". The 0.0375em tracking is body's own, so
   the titles are spaced like the copy around them. */
.product-info h3 { font-size: 1rem; letter-spacing: 0.0375em; font-family: var(--font-body); font-weight: 400; }
.product-info h3 a { position: relative; display: inline-block; color: var(--text-muted); transition: color 0.2s ease; }
.product-info h3 a:hover { color: var(--text); }
.product-info h3 a::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: -2px;
  height: 1px;
  width: 100%;
  background: #d6d6d6;
  opacity: 0;
  transform: translateY(-6px);
  transition: transform 0.3s ease, opacity 0.3s ease;
}
.product-info h3 a:hover::after { opacity: 1; transform: translateY(0); }
.product-price { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-top: -2px; }
.product-price .price { font-family: var(--font-body); font-size: 1.05rem; color: var(--text-muted); }
.product-price:has(.price-was:not([style*="none"])) .price { color: var(--title-red); }
.product-price .price-was { position: relative; display: inline-block; font-size: 0.85rem; color: var(--text-faint); text-decoration: none; }
.product-price .price-was::after {
  content: '';
  position: absolute;
  left: -2px;
  right: -2px;
  top: 50%;
  height: 1px;
  background: var(--title-red);
  transform: rotate(-12deg);
}
.qv-rating-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; min-width: 0; }
.qv-rating-row:empty { display: none; }
[data-featured-grid] .qv-rating-row { display: none; }
.qv-rating-row .star-rating.is-small .star-rating-row svg { width: 15px; height: 15px; }
.qv-rating-score { font-family: var(--font-body); font-size: 0.95rem; font-weight: 400; line-height: 1; color: var(--text-faint); }
.qv-rating-count { font-size: 0.85rem; line-height: 1; color: var(--text-faint); }
/* Stands in for the stars on a product nobody has reviewed yet. Same size
   and same quiet colour as the count it replaces, so the header line keeps
   its shape. */
.qv-rating-empty { font-size: 0.9rem; line-height: 1; color: var(--text-faint); }
.pd-rating-share-row .qv-rating-empty { font-size: 1rem; }
.quick-view-info .qv-rating-empty { color: var(--on-dark-faint); }
.pd-rating-share-row .qv-rating-row .star-rating.is-small .star-rating-row svg { width: 20px; height: 20px; }
.pd-rating-share-row .qv-rating-score { font-size: 1.2rem; font-weight: 400; }
.pd-rating-share-row .qv-rating-count { font-size: 1rem; }
.pd-rating-share-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.pd-share-btn { display: flex; align-items: center; gap: 8px; flex-shrink: 0; border: none; background: transparent; padding: 0; font-family: var(--font-body); font-size: 1rem; color: var(--text-faint); cursor: pointer; transition: color 0.2s ease; }
.pd-share-btn:hover { color: var(--text); }
.pd-share-btn svg { width: 20px; height: 20px; max-width: none; flex-shrink: 0; }
.quick-view-info .pd-share-btn { color: var(--on-dark-faint); }
.quick-view-info .pd-share-btn:hover { color: var(--on-dark); }
/* Every button on the site shares this same treatment: lighter-gray fill
   at rest, white text. On hover, a lighter-gray overlay sweeps in from the
   left, growing to fill the whole button. */
.btn-cart, .btn-tan, .btn-primary, .btn-outline, .btn-ghost, .btn-light {
  position: relative;
  isolation: isolate;
  background: var(--btn-white-solid);
  color: var(--offwhite);
  border: none;
  overflow: hidden;
  /* Sits outside the box, so `overflow: hidden` above does not touch it --
     that clips children and backgrounds, not the shadow. */
  box-shadow: var(--btn-shadow);
  transform: scale(1);
  transition: transform 0.15s ease;
}
.btn-cart::after, .btn-tan::after, .btn-primary::after, .btn-outline::after, .btn-ghost::after, .btn-light::after {
  content: '';
  position: absolute;
  top: -16px;
  left: 0;
  right: 0;
  height: calc(100% + 32px);
  z-index: -1;
  background: linear-gradient(to bottom, transparent 0px, var(--btn-gray-dark) 16px, var(--btn-gray-dark) calc(100% - 16px), transparent 100%);
  pointer-events: none;
  transform: translateY(-100%);
  transition: none;
}
.btn-cart.is-hovering::after, .btn-tan.is-hovering::after, .btn-primary.is-hovering::after, .btn-outline.is-hovering::after, .btn-ghost.is-hovering::after, .btn-light.is-hovering::after {
  transform: translateY(0%);
  transition: transform 0.22s ease;
}
.btn-cart.is-leaving::after, .btn-tan.is-leaving::after, .btn-primary.is-leaving::after, .btn-outline.is-leaving::after, .btn-ghost.is-leaving::after, .btn-light.is-leaving::after {
  transform: translateY(100%);
  transition: transform 0.22s ease;
}
.btn-cart.is-resetting::after, .btn-tan.is-resetting::after, .btn-primary.is-resetting::after, .btn-outline.is-resetting::after, .btn-ghost.is-resetting::after, .btn-light.is-resetting::after {
  transition: none;
}

/* Hero button, and Read Our Story / Learn More below: same lighter-gray
   resting fill / hover-sweep-from-left treatment as the outline-pill
   buttons above. */
.btn-hero {
  position: relative;
  isolation: isolate;
  background: var(--btn-white-solid);
  color: var(--offwhite);
  border: none;
  overflow: hidden;
  box-shadow: var(--btn-shadow);
}
.btn-hero::after {
  content: '';
  position: absolute;
  top: -16px;
  left: 0;
  right: 0;
  height: calc(100% + 32px);
  z-index: -1;
  background: linear-gradient(to bottom, transparent 0px, var(--btn-gray-dark) 16px, var(--btn-gray-dark) calc(100% - 16px), transparent 100%);
  pointer-events: none;
  transform: translateY(-100%);
  transition: none;
}
.btn-hero.is-hovering::after {
  transform: translateY(0%);
  transition: transform 0.22s ease;
}
.btn-hero.is-leaving::after {
  transform: translateY(100%);
  transition: transform 0.22s ease;
}
.btn-hero.is-resetting::after {
  transition: none;
}
.brand-strip .btn-hero { background: var(--btn-white-solid); color: var(--offwhite); }

/* Shop the Store, in red -- the one button on the site that is not gray.

   Scoped to .hero, not left on .btn-hero, because that class is also on Shop
   All Products down in the Cut & Sewn story, which stays gray: the red is
   here to mark the single primary action on the page, and a second red button
   further down would spend exactly what this one is buying.

   Rest and sweep only. Everything else about the button -- the shape, the
   sweep's geometry and timing, the is-hovering/is-leaving classes JS puts on
   it -- comes from .btn-hero above and is not repeated here. */
.hero .btn-hero { background: var(--btn-red-solid); }
.hero .btn-hero::after {
  background: linear-gradient(to bottom, transparent 0px, var(--btn-red-hover) 16px, var(--btn-red-hover) calc(100% - 16px), transparent 100%);
}

/* ---- About split ---- */
.split {
  display: grid;
  /* minmax(0, 1fr), never a bare 1fr. A bare 1fr is minmax(auto, 1fr), and
     that auto floor is the item's min-content width -- which for a cell
     holding a photo is the image file's own intrinsic width, several hundred
     pixels of it. Narrow the window past that and the floor stops the column
     shrinking: the two halves overflow their tracks and the photo rides over
     the copy beside it instead of the two staying in their columns.

     Here on the base rule rather than only on .about-card, which is where
     this was first fixed, because .about-card's layout lives inside a
     min-width media query and the base rule is what holds between that
     query's lower edge and the mobile query's upper one -- see the note on
     the breakpoint pair below. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 48px;
  align-items: center;
}
.split .hero-actions { justify-content: flex-start; }
.split-media {
  position: relative;
  /* See .split above: the track can shrink below the image's intrinsic
     width, and this is what lets the frame shrink with it. Without both
     halves the photo still spills over the copy. */
  min-width: 0;
  aspect-ratio: 4/3;
  border-radius: var(--radius-lg);
  border: 1.3px solid var(--border);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* The 35% dark wash that used to sit over every section photo is gone. It was
   there to settle the photo into the page; nothing is written on these, so it
   was only costing brightness -- which is the one thing a screen carried
   outdoors cannot spare. The element is kept, empty and inert, because
   .split-media is a positioning context that other rules paint into. */
.split-media::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.split-media svg { width: 30%; height: 30%; color: var(--rust); }
.split-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: var(--reveal-from);
  /* The hero photo's entrance, to the value: 2.7s on the same curve for both
     the shrink and the lift out of black. These are the other full-bleed
     photographs on the site and they are as tall as the window, so they want
     the hero's long settle rather than the product cards' quick one -- at
     1.2s the lift was over before much of the photo had come up.

     No transition declared here on purpose: this IS the site-wide `img` rule
     at the top of the file, which is the same declaration the hero carries.
     Restating it would be a second copy to keep in step with the first. */
}
.split-media img.is-visible { filter: brightness(1); }
/* is-unfiltered used to be the opt-out from the house grade. There is no
   house grade any more -- every photo settles at brightness(1) and its own
   colour -- so these two say what the rules above already say. Kept because
   the class still carries meaning on the frame (see below), and because a
   photo that wants a treatment of its own would hang it here. */
.split-media img.is-unfiltered { filter: var(--reveal-from); }
.split-media img.is-unfiltered.is-visible { filter: brightness(1); }
/* No scrim to drop any more -- the base ::after paints nothing. Left in place
   so nothing downstream can quietly give these frames one back. */
.split-media.is-unfiltered::after { content: none; }
/* Square frame instead of the default 4/3 -- what the story cards use, so
   the photo column is exactly as tall as it is wide. The Oak Creek photo is
   itself square and so crops nothing at 1:1; the other two are landscape and
   object-fit: cover takes the difference off their sides, from the centre
   unless is-focus-right below says otherwise. */
.split-media.is-square { aspect-ratio: 1 / 1; }
/* Wider than the base 4/3, for the home page's two story cards. A square
   photo half the container across made those sections enormously tall --
   the photo is what sets a card's height, so the ratio is the lever. */
.split-media.is-wide { aspect-ratio: 3 / 2; }
/* Pulls a wide photo's crop toward its right edge. object-fit: cover centres
   by default, which on the trail-cam shot cut the ringtail's tail off at the
   frame -- the animal is left of centre and the tail runs out to the right. */
.split-media img.is-focus-right { object-position: 78% center; }
/* The vertical counterpart, and the same 78. The files these frames take
   are square and the frames are 4:3, so `cover` scales by the WIDTH and it
   is the top and bottom of the picture that go -- a band of 75% of the file
   survives, centred, with 12.5% cut off each end. That is the right crop
   for a landscape but not for a photograph with a person standing in it:
   Oak Creek was losing his legs below the knee to the bottom edge.

   78% slides the window down to cover 19.5% - 94.5% of the file instead of
   12.5% - 87.5%. The seven points it gains at the bottom are the ones with
   the figure in them; what it spends is sky and bare branches, which is the
   half of that frame with nothing in it. */
.split-media img.is-focus-low { object-position: center 78%; }
/* Photo on the left, copy on the right. The two columns are already in
   that order in the markup, so this needs no rule on desktop -- but the
   single-column mobile layout should still lead with the heading, so the
   DOM order is flipped back below the breakpoint. */
@media (max-width: 860px) {
  .split.split-media-first > .split-media { order: 2; }
  .split.split-media-first > :not(.split-media) { order: 1; }
  /* Square corners on a phone, like every other photo down here -- the
     product cards, the gallery, the thumbnails. .split-media carries a
     --radius-lg frame by default and the About page's two photos were the
     only rounded things left on the page. Desktop drops the radius too, in
     the .about-card rule further down; it just did it from the other side of
     the breakpoint, where the photo is a panel with a seam rather than a
     framed picture. */
  .about-card .split-media { border-radius: 0; }
}

.stat-row { display: flex; gap: 30px; margin-top: 24px; }
.stat-row div strong { display: block; font-family: var(--font-display); font-size: 1.9rem; color: var(--tan); }
.stat-row div span { font-size: 0.75rem; letter-spacing: 0.08em; color: var(--text-faint); }

/* ---- Ambassador / Influencer banner ---- */
.ambassador-banner {
  display: grid;
  grid-template-columns: 1fr 1fr;
  background: var(--forest);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.ambassador-media {
  position: relative;
  aspect-ratio: 4 / 3;
  overflow: hidden;
}
.ambassador-media img { width: 100%; height: 100%; object-fit: cover; }
/* The gradient that used to darken this from 15% at the top to 55% at the
   bottom is gone with every other scrim on the site. The sign sitting over it
   carries its own drop-shadow (see below), which is what holds it against the
   photo -- the wash was only ever helping. */
.ambassador-media::after {
  content: '';
  position: absolute;
  inset: 0;
}
.ambassador-sign {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 62%;
  transform: translate(-50%, -50%) rotate(-6deg);
  z-index: 2;
  filter: drop-shadow(0 10px 20px rgba(0, 0, 0, 0.45));
}
.ambassador-sign img { width: 100%; height: auto; display: block; }
.ambassador-sign-text {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 0 8%;
}
.ambassador-sign-text .line1 {
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: clamp(1.1rem, 2.6vw, 1.8rem);
  color: var(--offwhite);
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.6);
}
.ambassador-sign-text .line2 {
  font-family: var(--font-body);
  font-size: clamp(0.62rem, 1.3vw, 0.85rem);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--tan-light);
  margin-top: 4px;
}
.subscribe-col {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 22px;
}
.subscribe-col h2 { color: var(--title); margin: 0; }
.subscribe-col .subscribe-stack { max-width: 100%; }

/* ---- Home: Free Membership ----
   One centred card on the topo strip. The texture, its fade to black at the
   section's top and bottom edges, and the fixed-attachment parallax all
   belong to the SECTION (see the topo block at the end of this file); the
   frame and the lighter fill belong to the CARD. That split is the whole
   point -- the topo reads as ground the card is sitting on rather than a
   panel the card is part of. */
.membership-strip {
  /* 40px, the same as every other seam between the home page's sections. */
  padding: 40px 0;
  /* The ground and the texture both come from the topo block at the end of
     the file, which this now shares with the About page's Get In Touch strip
     and the product page's own membership section -- so the three read as one
     thing across the site and there is a single place to change them. */
  /* Ruled off top and bottom, so the band is closed on both sides rather
     than running into whatever precedes it. Safe to draw the top one here:
     neither section that comes before this -- the home page's stories, the
     store page's grid -- draws a rule underneath itself, so there is nothing
     for it to double up against. */
  border-top: 1.3px solid var(--border);
  border-bottom: 1.3px solid var(--border);
  color: var(--on-dark);
}
.membership-card {
  max-width: 560px;
  margin: 0 auto;
  padding: 44px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 22px;
  text-align: center;
  /* No fill, no frame, no radius: the copy sits straight on the band. What
     is left of this rule is only the measure and the centring -- and the
     padding, which no longer holds anything off a border and is simply the
     air above and below the block now. */
}
.membership-card h2 { color: var(--title); margin: 0; }
/* The heading centres with the card; the stack keeps the left alignment it
   has everywhere else, so the blurb and the two fields read the same here as
   they do on the other pages' sign-up. */
.membership-card .subscribe-stack { max-width: 100%; }

/* ---- Brand strip ---- */
/* Same ground as About Us directly above it, and no rule between the two:
   they read as one run of page with two cards on it. The 40px top pairs with
   About Us's 40px bottom for the 80px the About page puts between its own two
   cards. */
.brand-strip {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--forest);
  color: var(--on-dark);
  padding: 40px 0;
  border-bottom: 1.3px solid var(--border);
}
/* The strip holds one story card now, so its copy sits on the card's own
   fill rather than directly on a photo or texture. The centring, the
   hero-style text shadows and the button's drop shadow all went with that --
   they were there to lift the block off a busy background, and the flat fill
   needs none of it. */
.brand-strip h2 { color: var(--title); }
.brand-strip p { color: var(--on-dark-muted); }
.brand-strip .hero-actions { justify-content: flex-start; }

/* ---- Video tutorials ---- */
.video-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 28px;
  margin-top: 36px;
}
.video-card { display: block; }
.video-card-media {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius);
  overflow: hidden;
  border: 1.3px solid var(--border-strong);
  background:
    radial-gradient(circle at 30% 20%, rgba(169,136,90,0.06), transparent 70%),
    #5e5e5e;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: border-color 0.2s ease;
}
.video-card:hover .video-card-media { border-color: var(--offwhite); }
.video-card-media svg:first-child {
  width: 56px;
  height: 56px;
  color: var(--offwhite);
  opacity: 0;
  transform: scale(1.12);
  transition: transform 1.6s cubic-bezier(0.16, 1, 0.3, 1), opacity 1.6s ease;
}
.video-card-media.is-visible svg:first-child { opacity: 0.92; transform: scale(1); }
.video-card:hover .video-card-media.is-visible svg:first-child { transition: transform 0.35s ease; transform: scale(1.08); }
.video-card-play { position: absolute; width: 22px; height: 22px; }
.video-card-title-banner {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 14px 16px;
  background: rgba(19, 19, 19, 0.55);
  color: var(--offwhite);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.88rem;
  line-height: 1.25;
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.5);
}
.video-card h3 {
  margin-top: 14px;
  margin-bottom: 4px;
  font-size: 1rem;
  font-weight: 400;
  color: var(--text);
}
.video-card-date { display: block; font-size: 0.8rem; color: var(--text-faint); margin-top: 10px; }
.video-grid-page {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 32px;
}

/* ---- Videos page: Filter Results as a compact anchored dropdown ---- */
.filter-sidebar-trigger { display: none; }
.filter-dropdown { position: relative; display: inline-block; margin-bottom: 20px; }
.filter-trigger-label-mobile { display: none; }
.filter-sidebar-sort-section { display: none; }
.filter-sidebar-category-section { display: none; }
.store-filter-dropdown { display: none; }
.filter-sidebar {
  background: var(--surface);
  border: 1.3px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
@media (min-width: 860.02px) {
  .filter-sidebar-trigger {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 10px 18px;
    background: transparent;
    border: 1.3px solid var(--border-strong);
    border-radius: var(--radius-sm);
    color: var(--text);
    font-family: var(--font-display);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-size: 0.82rem;
    font-weight: 600;
    cursor: pointer;
    transition: color 0.2s ease, border-color 0.2s ease;
  }
  .filter-sidebar-trigger svg { width: 16px; height: 16px; }
  .filter-sidebar-trigger:hover,
  .filter-sidebar-trigger[aria-expanded="true"] { border-color: var(--text); }
  .filter-sidebar {
    position: absolute;
    top: calc(100% + 8px);
    left: 0;
    width: 300px;
    max-height: 70vh;
    overflow-y: auto;
    box-shadow: var(--shadow);
    border-color: var(--border-strong);
    z-index: 20;
    opacity: 0;
    pointer-events: none;
    transform: translateY(-6px);
    transition: opacity 0.2s ease, transform 0.2s ease;
  }
  .filter-sidebar.open { opacity: 1; pointer-events: auto; transform: translateY(0); }
  .filter-sidebar-handle { display: none; }
}
.filter-sidebar-head { display: none; }
/* Only the phone sheet has one -- the desktop sidebar sits in the page and
   has nothing to close. */
.filter-sidebar-close { display: none; }
.filter-sidebar-head svg { width: 18px; height: 18px; flex-shrink: 0; }
.filter-sidebar-group { padding: 18px; border-bottom: 1.3px solid var(--border); transition: background 0.2s ease; }
.filter-sidebar-group:last-child { border-bottom: none; }
.filter-sidebar-group:hover { background: var(--surface-2); }
.filter-sidebar-group-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;
  cursor: pointer;
}
.filter-sidebar-group-head h4 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 0.9rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text);
}
.filter-collapse-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  border: none;
  background: transparent;
  cursor: pointer;
  transform: scale(1);
  transition: transform 0.2s ease;
}
.filter-collapse-toggle svg { width: 18px; height: 18px; max-width: none; flex-shrink: 0; color: var(--offwhite); transition: transform 0.2s ease; }
.filter-collapse-toggle[aria-expanded="true"] svg { transform: rotate(90deg); }
.filter-sidebar-group:hover .filter-collapse-toggle {
  transform: scale(1.25);
}
.filter-sidebar-options { display: flex; flex-direction: column; gap: 12px; overflow: hidden; }
.filter-sidebar-options[hidden] { display: none; }
.filter-check {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 0.9rem;
  color: var(--text-muted);
  cursor: pointer;
}
.filter-check input { width: 15px; height: 15px; accent-color: var(--btn-red); cursor: pointer; }
.filter-check:hover { color: var(--text); }
.filter-count { color: var(--text-faint); }

.videos-toolbar {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 24px;
  padding-bottom: 20px;
  border-bottom: 1.3px solid var(--border);
}
.videos-showing { font-size: 0.9rem; color: var(--text-muted); }
.videos-showing strong { color: var(--text); font-weight: 400; }
.videos-sort-col { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; }
.videos-count { font-size: 0.8rem; color: var(--text-faint); }

.video-section-side {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 12px;
  max-width: 380px;
  text-align: right;
}
.video-section-side p { margin: 0; font-size: 0.85rem; color: var(--text-muted); }
.video-link-arrow:hover { color: var(--text); }
.video-stack { display: flex; flex-direction: column; gap: 28px; }
.video-row { display: flex; gap: 24px; }
.video-row .video-card { flex: 1; min-width: 0; }
.video-row .video-card-media { aspect-ratio: 16 / 10; }
.video-row .video-card-title-banner {
  padding: 16px 18px;
  font-size: 1rem;
  line-height: 1.25;
}
.video-playlist { display: flex; flex-direction: column; gap: 18px; }
.video-playlist-item { display: flex; align-items: center; gap: 16px; }
.video-playlist-thumb {
  position: relative;
  width: 168px;
  flex-shrink: 0;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background:
    radial-gradient(circle at 30% 20%, rgba(169,136,90,0.06), transparent 70%),
    #5e5e5e;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.2s ease;
}
.video-playlist-item:hover .video-playlist-thumb { transform: scale(1.03); }
.video-playlist-thumb svg:first-child { width: 30px; height: 30px; color: var(--offwhite); opacity: 0.92; }
.video-playlist-thumb .video-card-play { position: absolute; width: 13px; height: 13px; }
.video-playlist-info h3 { margin: 0 0 4px; font-size: 0.95rem; font-weight: 400; color: var(--text); }

.video-modal {
  position: fixed;
  top: 50%;
  left: 50%;
  width: min(1060px, 95vw);
  max-height: 90vh;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  z-index: 301;
  opacity: 0;
  pointer-events: none;
  transform: translate(-50%, -50%) scale(0.96);
  transition: opacity 0.25s ease, transform 0.25s ease;
}
.video-modal.open { opacity: 1; pointer-events: auto; transform: translate(-50%, -50%) scale(1); }
.video-modal-body {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background:
    radial-gradient(circle at 30% 20%, rgba(169,136,90,0.06), transparent 70%),
    #5e5e5e;
}
.video-modal-body svg:first-child { width: 72px; height: 72px; color: var(--offwhite); opacity: 0.92; }
.video-modal-body .video-card-play { position: absolute; width: 28px; height: 28px; }
.video-modal .quick-view-close {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.4s ease, border-color 0.2s ease, color 0.2s ease;
}
.video-modal.show-controls .quick-view-close { opacity: 1; pointer-events: auto; }

/* ---- Subscribe ---- */
.subscribe {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  color: var(--on-dark);
  padding: 48px 0;
  /* Matches the line closing the section off at the bottom, which is the
     footer's own border-top -- same width, same colour. */
  border-top: 1.3px solid var(--on-dark-border);
}
/* The canyon photo and its scrim that used to sit here were replaced by the
   topo texture, which is painted on the section's own background instead of
   a pseudo-element -- background-attachment: fixed cannot survive the
   transform the photo's reveal animation used. Same move as .brand-strip;
   see the topo block at the end of this file. */
/* Same shadows the hero and the brand strip use, so the heading, blurb and
   button sit on the topo the way they sit on the hero photo. */
.subscribe h2 { text-shadow: 0 3px 14px rgba(0, 0, 0, 0.65); }
.subscribe p { text-shadow: 0 2px 8px rgba(0, 0, 0, 0.6); }
/* The button's shadow is not repeated here any more -- it comes from
   --btn-shadow on the base rule, which is this same value. */
.subscribe-row {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 24px;
}
.subscribe-heading { flex-shrink: 0; }
.subscribe-heading h2 { margin: 0; white-space: normal; }
.subscribe-stack {
  display: flex;
  flex-direction: column;
  gap: 20px;
  width: 100%;
  max-width: 460px;
  text-align: left;
}
.subscribe-block {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.subscribe-block-label {
  display: block;
  font-family: var(--font-gold);
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--title-red);
}
.subscribe-block-desc {
  color: var(--on-dark-muted);
  font-size: 0.85rem;
  line-height: 1.5;
  margin: 0;
}
.subscribe-form {
  display: flex;
  width: 100%;
}
.subscribe-input-wrap {
  position: relative;
  width: 100%;
  /* Clips the arrow to the field. It slides in from translateX(12px), which
     puts about 7px of it past this box's right edge, and the opacity fade
     under the slide is only partway through by then -- so mid-transition the
     tip was visible outside the input.

     Safe to clip here, which is worth saying because the arrow sits over the
     field's right corner: .subscribe-form input has border-radius: 0, so
     this box is a plain rectangle and the glyph is cut on a straight edge
     rather than on a curve. If the field is ever rounded, this needs the
     same radius or the corner will shave the arrow.

     Nothing else needs to escape: the arrow is the only thing inside this
     wrap besides the input, and .subscribe-success is a sibling of it. The
     focus state sets outline: none and draws a border instead, which is
     inside the box and unaffected. */
  overflow: hidden;
}
.subscribe-form input[type="email"], .subscribe-form input[type="tel"] {
  width: 100%;
  /* 52px on the right. It is not decoration: the arrow button is 38px wide
     sitting 8px in, so it occupies the last 46px of the field, and the text
     has to stop before it. 52 leaves 6px between the two. Move the button's
     size or its offset and this is the number that has to follow -- it has
     had to three times now, 34 to 44 to 48 to 52. */
  padding: 8px 52px 8px 2px;
  border-radius: 0;
  border: 1.3px solid transparent;
  border-bottom: 1.3px solid var(--on-dark-border);
  background: transparent;
  color: var(--on-dark);
  transition: border-color 0.2s ease;
}
.subscribe-form input[type="email"]::placeholder, .subscribe-form input[type="tel"]::placeholder { color: var(--on-dark-faint); }
.subscribe-form input[type="email"]:hover, .subscribe-form input[type="tel"]:hover {
  outline: none;
  border-bottom-color: var(--on-dark);
}
.subscribe-form input[type="email"]:focus, .subscribe-form input[type="tel"]:focus {
  outline: none;
  border: 1.3px solid var(--on-dark);
}
.subscribe-arrow {
  position: absolute;
  top: 50%;
  /* 8px in from the field's edge, up from 2px. The input's right padding
     below is what keeps the text clear of it and had to move with it. */
  right: 8px;
  transform: translateY(-50%);
  /* 34px, up from 30 and 28 before that. The box is invisible -- transparent
     fill, and the border-radius: 50% below rounds nothing you can see -- so
     this is the pointer target and the centring frame for the glyph, nothing
     more. It stays a few pixels wider than the glyph so the two share a
     centre and the target is not exactly the artwork. */
  width: 38px;
  height: 38px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  padding: 0;
  color: var(--offwhite);
  cursor: pointer;
}
/* The story buttons' arrow, at this button's size. The art in the markup is
   the same path on the same 34x12 viewBox that Contact Us and Learn More
   carry -- a long rule with a chevron on the end -- rather than the 24x24
   short arrow that was here.

   22px inside a 28px button, and the button is untouched. It is absolutely
   positioned in .subscribe-input-wrap, so its box never contributed to the
   field's height or width in the first place; only the art inside it has
   changed.

   The stroke is not set here and does not need to be: stroke-width 2.25 is
   on the path in the markup and scales with this width, which is how the
   hero's copy of the same arrow works. At 34px the glyph is drawn at its
   viewBox's own scale, so the stroke lands at 2.25px exactly -- against 1.99
   at 30px, 1.72 at 26px, 1.46 at the 22px this started at, and 1.5 for the
   short arrow before any of it. Half again as heavy as it began.

   Where that leaves it: the story buttons draw this same path at 17px, so
   these are twice their size with twice their line weight. They are no
   longer the same glyph at a different size -- they are the field's own
   arrow, which happens to share a path with the buttons'. A fine thing to
   be, but worth having written down, because matching those buttons is why
   this art was chosen.

   If a size step is ever wanted WITHOUT the weight going with it, the lever
   is stroke-width in the markup: 2.25 * 34 / (this width) holds the line
   wherever the box goes, and nothing in this rule has to change for it. */
.subscribe-arrow svg {
  width: 34px;
  height: 34px;
  transition: transform 0.28s cubic-bezier(0.33, 1, 0.68, 1),
              opacity 0.22s ease;
}
/* ---- It arrives with the field, not with the pointer on itself -----------
   Hidden at rest and slid in from the right when the field is hovered or has
   focus inside it. It used to be permanently visible and nudge 3px when the
   pointer was on the button itself, which is a small target and the wrong
   one: the thing being pointed at is the input.

   Keyed to .subscribe-input-wrap rather than to the button. :hover on the
   wrap covers the whole field, and :focus-within covers the input AND the
   button -- so tabbing to either brings the arrow in, and it stays in while
   you are typing. That is what makes this work for a keyboard, where a
   :hover-only reveal would leave the submit control invisible.

   12px of travel with an opacity fade under it. Both are needed and they do
   different jobs: the fade carries the last of it out of sight, and
   .subscribe-input-wrap clips the rest, so the arrow is never seen outside
   the field on its way in. The clip is on the wrap and not here -- see that
   rule for why it is safe there.

   Only where there is a real pointer. On touch there is no hover to reveal
   it with, and an invisible submit button that appears only after the field
   is tapped is worse than one that is simply there -- so the whole reveal is
   inside (hover: hover) and (pointer: fine), and a phone keeps the arrow
   showing at all times. */
@media (hover: hover) and (pointer: fine) {
  .subscribe-arrow svg {
    opacity: 0;
    transform: translateX(12px);
  }
  .subscribe-input-wrap:hover .subscribe-arrow svg,
  .subscribe-input-wrap:focus-within .subscribe-arrow svg {
    opacity: 1;
    transform: translateX(0);
  }
  /* And a further 3px left once the pointer is on the arrow itself. Two
     stages, and they answer two different things: the field says "there is a
     button here", the button says "this one". Left rather than right because
     it is the direction the arrow already travelled to arrive -- carrying on
     the way it came in reads as the same gesture continuing, where reversing
     it would read as a second, unrelated one.

     Both selectors carry the wrap as well as the button, which is what gets
     them past the reveal rules above: those are two classes and a
     pseudo-class each, so a bare .subscribe-arrow:hover would lose on
     specificity and nothing would move. The wrap's state is always true when
     the button's is -- you cannot hover the arrow without hovering the field
     it sits in -- so naming it costs nothing. */
  .subscribe-input-wrap:hover .subscribe-arrow:hover svg,
  .subscribe-input-wrap:focus-within .subscribe-arrow:focus-visible svg {
    transform: translateX(-3px);
  }
}
@media (prefers-reduced-motion: reduce) {
  .subscribe-arrow svg { transition: none; }
}
.subscribe-success { display: none; color: var(--tan-light); font-size: 0.8rem; margin: 0; }

/* ---- Footer ---- */
/* position/z-index are not layering the footer over anything -- they give it
   a stacking context of its own, which kept it out of the repaint region of
   the fixed-background section above. That section no longer uses a fixed
   attachment (see the topo block), so this is belt and braces now rather than
   load-bearing -- it costs nothing, and a footer owning its own stacking
   context is reasonable on its own terms. The translateZ(0) that sat here
   promoting it to a compositor layer is gone: that was expensive, and its
   reason went with the fixed layer. */
/* No rule across the top, on any page -- the same call the header's bottom
   border took, and for the same reason. It was a 1.3px --on-dark-border
   separating dark chrome from a dark page, which was its whole job. Two of
   the pages above it are light now, where near-black meeting white is about
   as clear an edge as a seam gets and a hairline over it draws a line where
   there is already a wall. On the rest it was doing little enough that
   losing it costs nothing.

   It is also drawn where the Free Membership band now ends. That band runs
   full-bleed and hard against this element, so the border was a line inside
   what reads as one continuous dark block at the foot of the page.

   border-box sizing means dropping it does not move anything: the 1.3px came
   out of the padding box, so the copy inside sits where it always has. */
.site-footer { position: relative; z-index: 1; color: var(--on-dark); background: var(--footer-ground); border-top: none; padding: 48px 0 21px; overflow-x: hidden;
  /* The other end of the same problem. The root's background now fills the
     canvas in --panel-head, which is right above the promo bar and wrong
     below the footer; this covers the bottom strip in the footer's own
     ground so an overshoot there comes off the footer rather than out of the
     bar's colour.

     A box-shadow rather than an element: it paints outside the border box,
     costs no layout, and adds nothing to the scrollable area -- so 1000px of
     it is free. It is not clipped by the overflow-x above either, which
     applies to descendants and not to the element's own shadow. */
  box-shadow: 0 1000px 0 var(--footer-ground); }
.footer-grid {
  position: relative;
  display: grid;
  grid-template-columns: 1.4fr repeat(3, 1fr);
  gap: 30px;
  padding-bottom: 30px;
}
.footer-grid::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: 0;
  width: 100vw;
  transform: translateX(-50%);
  border-bottom: 1.3px solid var(--on-dark-border);
}
.footer-brand .footer-social { margin-top: 16px; }
.footer-connect {
  display: none;
  position: relative;
  flex-direction: column; align-items: center;
  gap: 12px; flex-basis: 100%;
  padding-bottom: 20px;
}
.footer-connect-head {
  font-family: var(--font-display);
  font-size: 0.8rem;
  letter-spacing: 0.1em;
  color: var(--on-dark);
  margin: 0;
}
.footer-social { display: flex; gap: 10px; }
/* No box round them, and the glyph carries the whole state -- the same
   treatment the header's three icons and the home page's hero band row take,
   so every set of marks on the site behaves alike.

   The border going is what makes the colour necessary. .icon-btn's hover was
   a border-color lift, which has nothing left to lift; gray to white is the
   pair the nav labels use and the one these now use too. */
.footer-social .icon-btn {
  border: none;
  color: var(--on-dark-muted);
  transition: color 0.2s ease;
}
@media (hover: hover) and (pointer: fine) {
  .footer-social .icon-btn:hover { color: var(--on-dark); }
}
/* The column headings -- Products, Company, Legal. A step ABOVE the links
   under them, which is the way round it should have been: 1rem against the
   0.9rem on .footer-col a below. It was 0.8rem, so the heading was the
   smaller of the two and the list read as the more important half of its own
   column.

   Two things were hiding it. The heading is --on-dark against the links'
   --on-dark-muted, so it was brighter even while it was smaller; and it is
   uppercase at 0.1em, which fills more width per word than the sentence-case
   links do. Between them the hierarchy looked deliberate rather than
   inverted.

   1rem, not more. --font-display is Barlow Condensed, which is narrow enough
   that a size step reads as less of one than it would in a normal-width
   face -- this is about 11% up on the links and it is the smallest step that
   is unmistakably a step. */
.footer-col h4 {
  font-family: var(--font-display);
  font-size: 1rem;
  letter-spacing: 0.1em;
  color: var(--on-dark);
  margin-bottom: 18px;
}
.footer-col a, .footer-col address {
  position: relative;
  display: block;
  width: fit-content;
  font-style: normal;
  color: var(--on-dark-muted);
  padding: 6px 0;
  font-size: 0.9rem;
  transition: color 0.2s ease;
}
/* The footer's own drop-in line, matching the header's -- see the long note
   on .main-nav a::after for why this slot is white again after three accent
   colours. A link answers the pointer the same way at either end of the
   page, and --footer-ground and --chrome-ground are both #141414, so the
   18.4:1 up there is the figure down here too. */
.footer-col a::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: 4px;
  height: 1px;
  width: 100%;
  background: #ffffff;
  opacity: 0;
  transform: translateY(-10px);
  transition: transform 0.3s ease, opacity 0.3s ease;
}
.footer-col a:hover { color: var(--on-dark); }
.footer-col a:hover::after { opacity: 1; transform: translateY(0); }
.footer-utility {
  position: relative;
  display: grid;
  /* Region | copyright | badges. The copyright gets its own middle column
     instead of being absolutely centered, so a long badge row wraps inside
     its column rather than sliding under the copyright text. */
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: 16px 24px;
  margin-top: 24px;
  font-size: 0.8rem;
  color: var(--on-dark-faint);
}
.footer-utility .region-dropdown { grid-column: 1; grid-row: 1; }
.footer-utility .payment-icons { grid-column: 3; grid-row: 1; justify-content: flex-end; min-width: 0; }
.footer-bottom {
  grid-column: 2;
  grid-row: 1;
  text-align: center;
  white-space: nowrap;
}
/* Nine badges need ~460px, which the side column only has on wide screens.
   Below that the copyright drops to its own line so the badges can stay on
   one row beside the region selector instead of wrapping around it. */
@media (min-width: 860.02px) and (max-width: 1400px) {
  .footer-utility {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
  }
  .footer-utility .payment-icons { margin-left: auto; }
  .footer-bottom { order: 3; flex-basis: 100%; }
}
@media (max-width: 860px) {
  .footer-utility .payment-icons { justify-content: center; max-width: 100%; margin-inline: auto; }
}
/* No country picker in the footer (removed until Shopify Markets is set up):
   nothing is left to balance the badges against, so the row stacks and
   centres -- badges, then the copyright -- at every width. Keyed to the
   picker's absence, so putting it back restores the three-part row. */
.footer-utility:not(:has(.region-dropdown)) {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
}
.footer-utility:not(:has(.region-dropdown)) .payment-icons {
  justify-content: center;
  margin-inline: auto;
}
.footer-utility:not(:has(.region-dropdown)) .footer-bottom {
  position: static;
  transform: none;
  white-space: normal;
  text-align: center;
}
/* Too narrow for one row: cap at five badges so it splits 5 + 4 rather
   than leaving one badge alone on a second line. */
@media (max-width: 520px) {
  .footer-utility .payment-icons { max-width: 252px; }
}
.region-dropdown { position: relative; justify-self: start; }
.region-select {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  background: transparent;
  color: var(--on-dark-muted);
  /* No box. It was 1.3px of --on-dark-border, which is #383737 -- a gray
     rectangle that, on a control with no fill, was the loudest thing about it
     and the only rectangle in either place it appears.

     What is left is the label and its chevron, which is how the header's
     three marks are drawn a few rules up and how the footer's social row is
     drawn beside this one. The hover is the same two values those use --
     muted at rest, white under the pointer -- and it carries the whole state
     now that there is no edge to light.

     border-radius stays, doing nothing visible: it costs nothing and is the
     shape the control would take if a fill ever came back.

     The padding is kept as the click target -- a bare line of 12px type is a
     thin thing to hit -- but see .header-region below, where the sides come
     off: with no border there is nothing to hold the label away from, and
     that padding was reading as an uneven gap next to the icons. */
  border: none;
  border-radius: var(--radius-sm);
  padding: 8px 12px;
  font-size: 0.78rem;
  font-family: var(--font-body);
  cursor: pointer;
  transition: color 0.2s ease;
}
.region-select svg { width: 12px; height: 12px; transition: transform 0.2s ease; }
.region-select:hover { color: var(--on-dark); }
.region-select[aria-expanded="true"] svg { transform: rotate(180deg); }
.region-dropdown-list {
  position: absolute;
  bottom: calc(100% + 6px);
  left: 0;
  min-width: 100%;
  background: var(--forest);
  /* No border, matching the button that opens it -- that lost its own
     rectangle of --on-dark-border a pass ago, and a bordered panel hanging
     off an unbordered control was the two halves of one thing disagreeing.

     What separates the panel from the page now is its fill and its shadow.
     The fill is the honest half of that: --forest is #131313 and flat, where
     everything this opens over is either photographic or textured, so the
     edge is legible as a change of surface rather than as a drawn line. The
     shadow is 0 20px 44px at 16% black, which is a soft thing to ask to hold
     an edge over a dark photograph -- if the panel ever reads as floating
     without a shape, that alpha is the value to raise, not the border to put
     back. */
  border: none;
  border-radius: var(--radius-sm);
  padding: 6px;
  box-shadow: var(--shadow);
  z-index: 20;
  max-height: 260px;
  overflow-y: auto;
}
.region-dropdown-list[hidden] { display: none; }
.region-dropdown-list li {
  position: relative;
  padding: 9px 12px;
  font-size: 0.82rem;
  color: var(--on-dark-muted);
  cursor: pointer;
  white-space: nowrap;
  transition: color 0.15s ease;
}
.region-dropdown-list li::after {
  content: '';
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: 4px;
  height: 1px;
  background: var(--on-dark);
  opacity: 0;
  transform: translateY(-6px);
  transition: transform 0.3s ease, opacity 0.3s ease;
}
.region-dropdown-list li:hover,
.region-dropdown-list li.is-active {
  background: transparent;
  color: var(--on-dark);
}
.region-dropdown-list li:hover::after,
.region-dropdown-list li.is-active::after { opacity: 1; transform: translateY(0); }
.region-dropdown-list li[aria-selected="true"] { color: var(--on-dark); font-weight: 600; }
.payment-icons { justify-self: end; display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.pay-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 26px;
  padding: 0;
  border-radius: 4px;
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  line-height: 1;
  white-space: nowrap;
  font-family: var(--font-body);
  background: var(--surface);
  color: var(--text-muted);
  border: 1.3px solid var(--border-strong);
}
.pay-visa { font-style: italic; }
.pay-mastercard svg { width: 22px; height: 14px; display: block; }
.pay-discover { font-size: 0.42rem; letter-spacing: -0.02em; }
.pay-diners { font-size: 0.46rem; letter-spacing: -0.01em; }
.pay-applepay { font-size: 0.46rem; letter-spacing: -0.01em; }
.pay-googlepay { font-size: 0.4rem; letter-spacing: -0.02em; }
.pay-paypal { font-size: 0.5rem; font-style: italic; letter-spacing: -0.01em; }
.pay-shoppay { font-size: 0.46rem; letter-spacing: -0.01em; }

/* ---- Page header (interior pages) ---- */
.page-header {
  padding: 107px 0 80px;
  border-bottom: 1.3px solid var(--border);
  background: linear-gradient(180deg, rgba(77,89,71,0.08), transparent);
}
/* No .page-header-product rules any more: the product page has no page header
   block. Its breadcrumb moved inside the gallery column so the two stick as
   one -- see .product-detail-sticky. This padding is the space above the
   breadcrumb that the page header used to provide. */
[data-product-root] { padding-top: 12px; }
.page-header-store, .page-header-about, .page-header-faq, .page-header-contact, .page-header-videos {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding: 155px 0 32px;
  border-bottom: 1.3px solid var(--on-dark-border);
  color: var(--on-dark);
}
/* Store, About and Contact carry a deeper photo than FAQ does, and their text
   sits in the bottom-left of it rather than partway down. Nearly all of the
   band's height is above the text, leaving just enough beneath it to clear
   the edge. The 360/34 split keeps the same overall depth the earlier
   155/240 gave. It lines up with the page's content edge, not the viewport's,
   because it rides in the same .container as everything else. */
.page-header-store, .page-header-about, .page-header-contact {
  padding-top: 360px;
  padding-bottom: 34px;
}
/* ...and the section under it starts sooner. The shared .section 72px on top
   of a photo band this deep left a large empty gap between the two. */
.page-header-store + .section,
.page-header-about + .section,
.page-header-contact + .section { padding-top: 28px; }
.page-header-store::before, .page-header-about::before, .page-header-faq::before, .page-header-contact::before, .page-header-videos::before {
  content: '';
  position: absolute;
  /* -1px, not 0. This layer is scaled -- 1.28 down to 1 on the reveal -- and a
     transformed box lands on subpixel boundaries, so at rest it can fall a
     fraction short of its own edges and leave a hairline of whatever is
     behind it showing along the top. Behind it is .page-header's own
     background, a translucent olive over the page, and that hairline was
     invisible for as long as the page under it was near-black. It is a line
     across the top of the photograph on a white one.

     Overshooting by a pixel on every side costs nothing: the parent carries
     overflow: hidden, so the extra is clipped rather than drawn. */
  inset: -1px;
  z-index: -2;
  background-size: cover;
  background-repeat: no-repeat;
}
/* Dead centre, and unlike the landscapes below it that is not a taste call.
   The bag is the subject and it sits in the middle of the frame, so the crop
   has to keep the middle: any offset here trades a strap end for empty ground.

   The square frame, uncropped and unpadded -- store-header-bag-front.jpg,
   1200x1200, the photograph's own ground running edge to edge.

   It was store-header-bag.jpg until now: that same square padded out to
   2880x1200 (2.4:1) on flat #131313, on the reasoning that a square cannot
   fill a band this wide without losing the top and bottom of the bag, and
   that the flat flanks would read as the vignette carrying on rather than as
   letterboxing. They do not. `cover` fits a file by whichever axis falls
   short, so the padding stays outside the crop only while the band is TALLER
   than it is wide. This band is about 470px deep, so from roughly 470px of
   window upward the flanks come inside it and land as two flat bars with a
   hard edge against the photograph's texture -- 103px a side at 700px of
   window, 228px a side at 950px. Above 1000px none of it was ever visible,
   because the two-frame treatment further down replaces the image outright;
   that is why this only showed on a narrowed window.

   The square costs what the padding was there to avoid -- at 700x470 the
   crop keeps the middle 67% of the frame -- but the bag sits small and
   centred in it, so what goes is ground rather than bag. Full width, no
   bars, at every size under 1000px. */
.page-header-store::before { background-image: url('../assets/images/store-header-bag-front.jpg'); background-position: center center; }
.page-header-about::before { background-image: url('../assets/images/about-header-oak-creek-sunset.jpg'); background-position: center 48%; }
.page-header-faq::before { background-image: url('../assets/images/faq-header-canyon.jpg'); background-position: center 55%; }
/* 62%, well down from the 35% the previous photo wanted. This frame is mostly
   sky -- a monsoon cell over the red rock with the storm light behind it --
   and the band is a wide strip, so a crop near the top would have been cloud
   and nothing else. Pulling it down holds the rock line and the desert floor
   with the weather still above them. */
.page-header-contact::before { background-image: url('../assets/images/contact-header-monsoon.jpg'); background-position: center 62%; }
.page-header-videos::before { background-image: url('../assets/images/videos-header-aspen.jpg'); background-position: center 40%; }
/* Page header photos settle in like the hero. The section is what carries
   the is-visible class, so the trigger sits on the parent and the transform
   on the ::before that holds the photo. */
.page-header-store::before, .page-header-about::before, .page-header-faq::before, .page-header-contact::before, .page-header-videos::before {
  transform: scale(1.28);
  filter: var(--reveal-from);
  transition: transform 2.7s cubic-bezier(0.16, 1, 0.3, 1), filter 2.7s cubic-bezier(0.16, 1, 0.3, 1);
}
.page-header-store.is-visible::before, .page-header-about.is-visible::before, .page-header-faq.is-visible::before, .page-header-contact.is-visible::before, .page-header-videos.is-visible::before { transform: scale(1); filter: brightness(1); }
/* Desktop: the store band carries BOTH bag photographs, one per half, instead
   of the single padded one above.

   Two background layers on the one ::before rather than two elements. The
   pseudo element is already here, already the thing the reveal scales, and
   already sitting behind the type -- splitting it in two would mean a second
   element that has to be kept in step with all three.

   `50% auto` is what makes them fill their halves. Each frame is SQUARE, so
   at half the band's width it renders half the band's width TALL -- which on
   any desktop window is a good deal taller than the band, so each one is
   cropped by the band top and bottom and there is no gap anywhere. That is
   the zoom: the photographs are held at the size that fills the space and the
   band decides how much of each survives, rather than being fitted whole into
   a strip they do not suit. It also means the two are cropped independently,
   which is the reason for two positions below rather than one shared.

   Gated at 1000px because the trick has a floor: the squares are half the
   window wide, so under roughly 930px they stop being as tall as the band and
   bars open up above and below them. Narrower than that keeps the single
   padded frame, which has no such floor. */
@media (min-width: 1000px) {
  .page-header-store::before {
    background-image:
      url('../assets/images/store-header-bag-front.jpg'),
      url('../assets/images/store-header-bag-angle.jpg');
    background-size: 50% auto, 50% auto;
    /* The front-on frame is centred in its own photograph, so it takes the
       middle. The angled one sits low in its frame -- the strap runs up out
       of the top -- so its window is pulled down to follow the bag rather
       than the picture. Those are the two numbers to turn if the framing
       wants adjusting; nothing else here is a taste call. */
    background-position: left 50%, right 58%;
    background-repeat: no-repeat, no-repeat;
  }
}
/* No scrim on these any more either. It was 55% black across the whole band
   -- by some way the heaviest darkening on the site -- and the photo behind
   it is the first thing on the page. The element stays, empty, because these
   sections stack on it and because a treatment that ever comes back belongs
   here rather than somewhere new. */
.page-header-store::after, .page-header-about::after, .page-header-faq::after, .page-header-contact::after, .page-header-videos::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
}
/* The type is white and now sits on bare photography, so it carries its own
   shadow -- the hero's, to the value, since the two are the same problem. A
   shadow darkens only the pixels behind each letter, so it buys the contrast
   the scrim used to and costs the photograph nothing. */
.page-header-store h1, .page-header-about h1, .page-header-faq h1, .page-header-contact h1, .page-header-videos h1 {
  text-shadow: 0 3px 14px rgba(0, 0, 0, 0.65), 0 1px 4px rgba(0, 0, 0, 0.5);
}
.page-header-store p, .page-header-about p, .page-header-faq p, .page-header-contact p, .page-header-videos p,
.page-header-store .breadcrumb, .page-header-about .breadcrumb, .page-header-faq .breadcrumb, .page-header-contact .breadcrumb, .page-header-videos .breadcrumb,
.page-header-store .eyebrow, .page-header-about .eyebrow, .page-header-faq .eyebrow, .page-header-contact .eyebrow, .page-header-videos .eyebrow {
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.6), 0 1px 3px rgba(0, 0, 0, 0.5);
}
.page-header-store .breadcrumb, .page-header-about .breadcrumb, .page-header-faq .breadcrumb, .page-header-contact .breadcrumb, .page-header-videos .breadcrumb { color: var(--on-dark-muted); }
/* Home, on hover, over a page header's photograph. White, and it is the
   direction that makes it right rather than the colour.

   It was #7d3733, the drop's accent, and before that --tan-light. Both
   pointed the wrong way: the resting colour is --on-dark-muted, a near-white,
   so a mid-value accent could only take the link from plainly legible to
   barely there -- 2.2:1 on the store header's #141414, 1.7:1 on the
   photograph over it, against the 4.5:1 that 0.8rem text needs. The link
   read as going OUT under the pointer.

   Muted lifting to full white is the same two-tone the footer columns and
   the nav use, and it is the only version of this that gains contrast rather
   than spending it: 18.4:1 on the header's own ground.

   This rule is the five photo headers together -- store, about, FAQ, contact
   and videos -- because they have always shared one declaration and they all
   have the same near-white resting label on the same kind of dark
   photograph. The plain .breadcrumb a:hover below is a different case: those
   sit on a page rather than an image, and on this site's white pages white
   would be the invisible one. */
.page-header-store .breadcrumb a:hover, .page-header-about .breadcrumb a:hover, .page-header-faq .breadcrumb a:hover, .page-header-contact .breadcrumb a:hover, .page-header-videos .breadcrumb a:hover { color: #ffffff; }
/* #ffffff, not --title.

   The comment up at the head of this block opens "The type is white and now
   sits on bare photography" -- and it was not. --title is #ececec, a
   near-white built for a heading on a dark PANEL, where a hair off white
   keeps the display size from glaring. There is no panel here. These five
   headings sit on a photograph with nothing under them but their own
   text-shadow, so the value that was buying restraint on a flat ground was
   only costing separation on a picture.

   Sampled off the store banner it read #ececec against the #ffffff the promo
   bar, the hero heading and the two story headings all carry -- the one
   white on the site that was not white. The band is the page masthead; it
   should not hold the dimmest heading on the page.

   Scoped to the banner h1 and nothing else. --title itself is untouched: it
   dresses every other heading on the dark pages and those all sit on panels,
   which is the case it was written for. */
.page-header-store h1, .page-header-about h1, .page-header-faq h1, .page-header-contact h1, .page-header-videos h1 { color: #ffffff; }
/* ---- ...and the face, which was the bigger half of it -------------------
   These five headings were the only display type on the site still taking
   the BASE h1 rule: --font-display at weight 400, 0.0375em of tracking, no
   text-transform, and a font-size that ran clamp(2.5rem, 4.5vw, 3.75rem) --
   45px up to 67.5px depending on the window.

   Every heading the pages actually show -- THE CRAFT, ABOUT US, USA SOURCED,
   GET IN TOUCH, FREE MEMBERSHIP -- is Barlow Condensed 700, uppercase,
   2.5rem, 0.02em, 1.1. That set is written out in six places in this file by
   now (the home page desktop block, the narrow-desktop mirror below 860, and
   the membership strip in both) and it is what the client means by "the
   titles on the home page".

   Against it the banner headings read as another site: All Products, Our
   Story and Get In Touch came out title-case in the 400 weight, tracked
   nearly twice as wide, and up to half again as large. Light, airy, and
   wide, over a photograph -- next to a page of condensed black caps.

   All five values move together, because they are one type style and taking
   any of them alone leaves a heading that matches nothing:

     weight      400        ->  700
     transform   none       ->  uppercase
     tracking    0.0375em   ->  0.02em
     size        45-67.5px  ->  45px flat
     leading     1.3        ->  1.1

   The size going flat is the point of the change rather than a side effect
   of it: 4.5vw meant the masthead was a different size in every window, and
   the whole run of work on this site has been the opposite -- one size at
   every width. 2.5rem is what the sections carry, so the banner now opens
   the page at the size the page continues at.

   The h1 keeps its own text-shadow from the rule above; that one is about
   sitting on a photograph and has nothing to do with the face. */
.page-header-store h1, .page-header-about h1, .page-header-faq h1, .page-header-contact h1, .page-header-videos h1 {
  font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
               'Segoe UI', Roboto, sans-serif;
  font-weight: 700;
  text-transform: uppercase;
  font-size: 2.5rem;
  letter-spacing: 0.02em;
  line-height: 1.1;
}
.breadcrumb { font-size: 0.8rem; color: var(--text-faint); margin-bottom: 14px; }
.breadcrumb a:hover { color: #7d3733; }


/* ---- Filters (store page) ---- */
.store-toolbar-top { display: flex; justify-content: flex-end; margin-bottom: 8px; }
.filter-group {
  display: flex;
  flex-direction: row;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 32px;
}
.filter-group h4 {
  font-family: var(--font-gold);
  font-size: 1.05rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--title-red);
}
.filter-tabs {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
}
.filter-option {
  position: relative;
  display: inline-flex;
  align-items: center;
  padding: 8px 16px;
  border-radius: 999px;
  border: 1.3px solid transparent;
  background: none;
  color: var(--text-muted);
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 0.82rem;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: color 0.25s ease, border-color 0.25s ease;
}
.filter-option input { position: absolute; width: 0; height: 0; opacity: 0; }
.filter-option::after {
  content: '';
  position: absolute;
  left: 16px;
  right: 16px;
  bottom: 4px;
  height: 1px;
  background: currentColor;
  opacity: 0;
  transform: translateY(-6px);
  transition: transform 0.3s ease, opacity 0.3s ease;
}
.filter-option:hover { color: var(--text); }
.filter-option:hover::after { opacity: 1; transform: translateY(0); }
.filter-option:has(input:checked) { color: var(--text); }
.filter-option:has(input:checked)::after { opacity: 1; transform: translateY(0); }
.store-toolbar { display: flex; justify-content: flex-end; align-items: center; margin-bottom: 28px; padding-bottom: 20px; border-bottom: 1.3px solid var(--border); flex-wrap: wrap; gap: 12px; }
/* Put the product count and Sort By on one row -- count left, sort right --
   and tighten the vertical space they sit in. Applies at 861px+ on anything,
   and at any width on a pointer device, so a desktop window dragged narrow
   keeps this row rather than falling back to the stacked default. Real touch
   devices under 860px match neither arm and keep the bottom-sheet treatment. */
@media (min-width: 860.02px), (hover: hover) and (pointer: fine) {
  .store-toolbar-wrap {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 14px;
    padding-bottom: 12px;
    border-bottom: 1.3px solid var(--border);
  }
  .store-toolbar-top { margin-bottom: 0; }
  .store-toolbar { margin-bottom: 0; padding-bottom: 0; border-bottom: none; }
}
.sort-dropdown { position: relative; }
.sort-select {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: transparent;
  border: none;
  padding: 8px 2px;
  color: var(--text-muted);
  /* The same type as the View Full Details text link and the home page's two
     story links, which is the About Us body copy's face at the size the
     buttons use.
     Mixed case is the reason for the family: --font-display is Koulen, an
     uppercase-only face that renders lowercase letters as capitals, so
     dropping text-transform alone would have changed nothing.

     On the base rule rather than scoped to the store toolbar, so the videos
     page's identical Sort By moves with it. The touch pill further down the
     file keeps its own tighter uppercase type -- it matches the Filter
     trigger beside it, which shares that rule, and only one of the two
     changing would leave the pair mismatched. That rule is later at equal
     specificity, so it wins without needing to say so. */
  font-family: var(--font-body);
  text-transform: none;
  letter-spacing: 0.0375em;
  font-size: 0.9375rem;
  font-weight: 400;
  white-space: nowrap;
  cursor: pointer;
  transition: color 0.2s ease;
}
.sort-select:hover, .sort-select[aria-expanded="true"] { color: var(--text); }
.sort-select svg { width: 12px; height: 12px; transition: transform 0.2s ease; }
.sort-select[aria-expanded="true"] svg { transform: rotate(180deg); }
.sort-dropdown-list {
  position: absolute;
  top: calc(100% + 2px);
  right: 0;
  min-width: 190px;
  background: var(--surface);
  border: 1.3px solid var(--border-strong);
  border-radius: var(--radius-sm);
  padding: 6px;
  box-shadow: var(--shadow);
  z-index: 20;
}
.sort-dropdown-list[hidden] { display: none; }
.sort-dropdown-handle { display: none; }
.sort-dropdown-list li {
  position: relative;
  padding: 9px 12px;
  font-size: 0.85rem;
  color: var(--text-muted);
  border-radius: var(--radius-sm);
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s ease, color 0.15s ease;
}
.sort-dropdown-list li::after {
  content: '';
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: 4px;
  height: 1px;
  background: currentColor;
  opacity: 0;
  transform: translateY(-6px);
  transition: transform 0.3s ease, opacity 0.3s ease;
}
.sort-dropdown-list li:hover,
.sort-dropdown-list li.is-active { background: transparent; color: var(--text); }
.sort-dropdown-list li:hover::after,
.sort-dropdown-list li.is-active::after { opacity: 1; transform: translateY(0); }
.sort-dropdown-list li[aria-selected="true"] { font-weight: 600; color: var(--text); }
.result-count { color: var(--text-faint); font-size: 0.85rem; }
@media (min-width: 1025px) {
  [data-store-grid], [data-related-grid] { grid-template-columns: repeat(4, 1fr); }
}
/* Desktop only (861px+, above the mobile breakpoint): step the store and
   related grids 4 -> 3 before the shared 2-column rule takes over. Higher
   specificity than the plain .product-grid rule at max-width:1024px so this
   wins in that range. */
@media (min-width: 860.02px) and (max-width: 1024px) {
  .product-grid[data-store-grid], .product-grid[data-related-grid] { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* ---- FAQ accordion ---- */
.accordion-item { border-bottom: 1.3px solid var(--border); }
.accordion-trigger {
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 22px 0;
  background: none;
  border: none;
  color: var(--text);
  text-align: left;
  font-size: 1.02rem;
  font-weight: 600;
}
.accordion-trigger svg { width: 18px; height: 18px; flex-shrink: 0; color: var(--tan); transition: transform 0.2s ease; }
.accordion-item.open .accordion-trigger svg { transform: rotate(45deg); }
.accordion-panel { max-height: 0; overflow: hidden; transition: max-height 0.25s ease; }
.accordion-item.open .accordion-panel { max-height: 400px; }
.accordion-panel p { padding-bottom: 22px; margin: 0; }

/* ---- Product detail page: color/cart sit right under the price, above
   the info accordion, at every viewport width -- driven by flex order on
   the same markup, no duplicated content. ---- */
.product-detail-info { display: flex; flex-direction: column; }
.product-detail-header { order: 1; }
.product-color-select { order: 2; }
.product-detail-cart-row { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 12px; align-items: center; margin: 24px 0; order: 3; }
.product-detail-accordion { order: 4; }
.qv-actions-price { font-family: var(--font-body); font-size: 1.1rem; font-weight: 400; }
/* Color select, qty stepper and Add to Cart all span the full width of
   the info column, at every viewport width -- no "Color" label, no price
   next to the Add to Cart button. */
.product-color-select {
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
}
.product-color-label { display: none; }
.color-select-row { flex-direction: column; align-items: stretch; }
.color-select-dropdown { width: 100%; }
.color-select-trigger {
  width: 100%;
  height: 46px;
  justify-content: space-between;
  /* This rule is unscoped and later than the base one, so it is the radius
     every one of these triggers ends up with -- the product page's Select
     Color, the reviews toolbar's two filters, the buy bar's and the quick
     view popup's. One value keeps them identical by construction rather than
     by several that happen to match, and it is the 8px --radius rather than
     the base rule's smaller --radius-sm because these are 46px tall. */
  border-radius: var(--radius);
  border-width: 1.3px;
  border-color: var(--border-strong);
}
.color-select-list { width: 100%; }
.product-detail-cart-row {
  flex-direction: column;
  align-items: stretch;
  flex-wrap: nowrap;
  gap: 12px;
}
.product-detail-cart-row .qv-actions-price { display: none; }
.product-detail-cart-row [data-add-to-cart-main] {
  width: 100%;
}
.product-detail-cart-row .qty-stepper {
  width: 100%;
  height: 46px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-radius: 999px;
  border: 1.3px solid var(--border-strong);
  padding: 0 6px;
}
.product-detail-cart-row .qty-stepper-btn {
  display: flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; flex-shrink: 0;
  border: none; background: transparent;
  color: var(--text); cursor: pointer;
}
.product-detail-cart-row .qty-stepper-btn svg { width: 18px; height: 18px; }
.product-detail-cart-row .qty-stepper .qty-input {
  flex: 1;
  width: auto; height: auto;
  border: none; background: transparent;
}
@media (max-width: 860px) {
  /* Under the main image, spanning its width: five squares sharing the row
     evenly, the fifth being the "+N" tile into the stack. Not centred with a
     fixed 68px each, which is the desktop column's size and left the row
     floating in the middle of a phone screen. */
  .product-thumbs { justify-content: flex-start; }
  .product-thumb {
    width: auto;
    height: auto;
    flex: 1;
    aspect-ratio: 1 / 1;
    border-radius: 0;
  }
  /* One photo, edge to edge: square corners, a square container, and
     full-bleed width by cancelling out .container's own side padding. The
     small thumbnails below keep their rounded corners and stay inside the
     normal container width.

     Square, not the 20:17 this used to be. That ratio existed to make an
     85%-wide slide crop to a 1:1 square with the next photo peeking in at
     the right -- the popup still works that way. Here a slide is the full
     width of the strip, so the container's own ratio is the photo's. */
  .product-detail-media {
    border-radius: 0;
    /* No rules down the sides. The photo runs edge to edge here, so those two
       landed on the very edge of the screen -- a frame around something that
       has no frame. The top and bottom stay: those are real seams, between the
       photo and the breadcrumb above it and the thumbs below. */
    border-left: none;
    border-right: none;
    aspect-ratio: 1 / 1;
    width: calc(100% + clamp(16px, 4vw, 64px) * 2);
    margin-left: calc(clamp(16px, 4vw, 64px) * -1);
  }
  /* A swipeable filmstrip, one photo to a screen: each slide is the full
     width of the track, so a swipe carries you from one photo to the next
     with nothing of either neighbour showing, and native scroll-snap lands
     it. (The quick view popup keeps the 85% slide that peeks -- there the
     peek is what says the popup has more photos in it, since it has no
     arrows and no thumb row to say so.) */
  .product-detail-media-track {
    display: flex;
    overflow-x: auto;
    /* Explicit, and it matters: setting one axis to something other than
       `visible` makes the OTHER axis compute to `auto` rather than staying
       visible. So overflow-x: auto on its own silently made the strip
       vertically scrollable too, and a downward drag over the photo panned
       the images instead of scrolling the page. */
    overflow-y: hidden;
    /* Both axes, not pan-x alone. This is the same trap the quick view's
       filmstrip fell into -- see .qv-media-track further up, which now reads
       the same way.

       pan-x does NOT hand a vertical drag to the page. It forbids one
       outright: the element declares that horizontal panning is the only
       gesture it participates in, and the browser then refuses the vertical
       drag rather than passing it to an ancestor. So a finger starting on a
       product photo could page the strip sideways and do nothing at all up or
       down -- the page simply would not scroll from there.

       overflow-y: hidden above is what stops the strip scrolling vertically
       itself. This line is only about which gestures the browser will route,
       and naming both axes lets it read the direction and give the gesture to
       whichever wants it: sideways to the strip, up and down to the page. */
    touch-action: pan-x pan-y pinch-zoom;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .product-detail-media-track::-webkit-scrollbar { display: none; }
  .product-detail-media-slide {
    position: relative;
    display: flex !important;
    flex: 0 0 100%;
    scroll-snap-align: start;
  }
  .product-detail-header .product-price { align-items: baseline; gap: 8px 10px; }
}
/* ---- Product gallery on a narrow DESKTOP window ----
   A desktop browser resized small is still a desktop: it reports
   hover:hover / pointer:fine, while phones and tablets report
   hover:none / pointer:coarse. Gating on the pointer type instead of on
   width alone is what lets the gallery keep its desktop behaviour at any
   window size on a computer while leaving the mobile filmstrip block
   above completely untouched on real touch devices -- width alone cannot
   tell the two apart, which is why this needs the pointer query. */
@media (max-width: 860px) and (hover: hover) and (pointer: fine) {
  /* Undo the full-bleed 20:17 filmstrip container: back to a square image
     inside the container, with the same static border as the desktop
     layout. Square corners, matching desktop.

     --border-strong, not --border. This rule said "the same static border as
     the desktop layout" and then drew it in the other token: #333332 against
     the #545352 the full-width rule uses, so the frame quietly darkened as
     you narrowed a desktop window. The comment was right and the value was
     wrong; the value now agrees with it. */
  .product-detail-media {
    aspect-ratio: 1 / 1;
    width: auto;
    margin-left: 0;
    border-radius: 0;
    border: 1.3px solid var(--border-strong);
  }
  /* Undo the swipe track: show one slide at a time, stacked and absolutely
     positioned again, with no scroll-snap. Clicking zooms in place via
     initImageZoom, exactly as on the full-size desktop layout. */
  .product-detail-media-track {
    display: block;
    overflow: visible;
    scroll-snap-type: none;
  }
  .product-detail-media-slide {
    position: absolute;
    inset: 0;
    display: none !important;
    flex: none;
    margin-right: 0;
    scroll-snap-align: none;
  }
  .product-detail-media-slide.is-active { display: flex !important; }
  /* No tap-to-expand affordance on a pointer device. */
  [data-pd-media-expand] { display: none; }
  /* Thumbnails sit under the main image and span its full width -- declared
     for every width under 860 now, phones included, in the block above. */
  /* Overview / Details / Reviews stay plain, always-open sections rather
     than becoming clickable dropdowns -- same as desktop. */
  .product-detail-accordion .accordion-trigger { pointer-events: none; cursor: default; }
  .product-detail-accordion .accordion-trigger svg { display: none; }
  .product-detail-accordion .accordion-panel { max-height: none !important; overflow: visible; }
}
/* Desktop: the accordion items show as plain, always-open sections
   rather than clickable dropdowns -- dropdown behavior is a
   mobile-only ask. */
@media (min-width: 860.02px) {
  .product-detail-accordion .accordion-trigger { pointer-events: none; cursor: default; }
  .product-detail-accordion .accordion-trigger svg { display: none; }
  .product-detail-accordion .accordion-panel { max-height: none !important; overflow: visible; }
}
/* Reviews: full-width section below the gallery/info split on desktop
   (matches Tuff Possum Gear's layout) instead of sitting in the sticky
   info column's side-scroll. JS moves the same accordion-item node
   between this section and its normal spot in .product-detail-accordion
   depending on viewport width -- no duplicated markup or data hooks. */
.product-reviews-desktop-section { display: none; }
@media (min-width: 860.02px) {
  .product-reviews-desktop-section { display: block; padding-top: 16px; }
}
/* The accordion-trigger's "Reviews" label is redundant here -- the panel
   already has its own "Reviews (n)" heading -- and it's non-interactive
   in this slot anyway (see the min-width:861px rule above), so just hide
   it rather than showing the word twice. */
[data-product-reviews-desktop-accordion] .accordion-trigger { display: none; }
/* "Customer Reviews" title + divider above the bars/score boxes --
   desktop-only, same node as the mobile panel so this stays hidden there
   instead of showing on top of its own "Reviews (n)" heading further
   down. */
.product-reviews-desktop-heading, .product-reviews-desktop-divider { display: none; }
[data-product-reviews-desktop-accordion] .product-reviews-desktop-heading {
  display: block;
  margin: 0 0 16px;
  font-family: var(--font-body);
  font-size: 1.02rem;
  font-weight: 600;
  color: var(--text);
}
[data-product-reviews-desktop-accordion] .product-reviews-desktop-divider {
  display: block;
  border-top: 1.3px solid var(--border);
  margin-bottom: 22px;
}
/* No trailing line after the last item (Details) in the info column's
   accordion on desktop -- the reviews section right below now starts
   with its own "Customer Reviews" divider instead. */
@media (min-width: 860.02px) {
  .product-detail-accordion .accordion-item:last-child { border-bottom: none; }
}
@media (max-width: 860px) {
  /* overflow:visible once open (not just a taller max-height) so the
     Sort by / rating filter dropdowns aren't clipped by this panel --
     they're position:absolute popovers that render outside the panel's
     own in-flow content box. */
  /* Generously large (not just "tall enough for the default content") so
     expanding Reviews via Show More -- which can add many more review
     cards after the panel is already open -- never gets clipped by a cap
     sized for the initial render. */
  .product-detail-accordion .accordion-item.open .accordion-panel { max-height: 20000px; overflow: visible; }
}
.product-detail-accordion .accordion-panel ul { padding-bottom: 22px; margin: 0; }
[data-product-specs] {
  padding-left: 20px;
  list-style: disc;
}
[data-product-specs] li { margin-bottom: 10px; }
[data-product-specs] li:last-child { margin-bottom: 0; }
/* ---- The chevrons on Overview / Details / Reviews -----------------------
   The glyph in the markup is `M9 6l6 6-6 6`, which draws a chevron pointing
   RIGHT. It was rotate(0) closed and rotate(90deg) open -- so it read as
   pointing at the heading when shut and downward when open, which is the
   wrong way round: a closed row should point at the panel it will reveal
   (down) and an open one back at the heading that closes it (up).

   90deg and -90deg, a half turn apart, so the transition between them is the
   180deg sweep the eye reads as the row flipping rather than a quarter turn
   that looks like it stopped early.

   No media query on either. These only render on a phone -- the accordion is
   expanded on desktop and at narrow widths with a mouse, and both of those
   blocks set `display: none` on this svg -- so a rule here IS a rule for
   mobile. Written at the base rather than duplicated into a query, because a
   second copy of a value is what has gone stale in this file every time. */
/* Closed points DOWN, open points UP, and the glyph does half the work now.

   It was drawn right-pointing -- `M9 6l6 6-6 6`, the same chevron the gallery
   arrows use -- and rotated 90deg to face down, -90deg to face up. That is
   two rotations away from the artwork in both states, which is the kind of
   thing to get wrong once and never notice: the closed state looked correct,
   so nothing drew attention to the open one.

   These three triggers carry `M6 9l6 6 6-6` in the markup now, which points
   down as drawn, and these rules are 0 and 180. That is the pattern
   .contact-faq uses further down this file and has used since it was built --
   the same component, the same glyph, the same pair of values -- so the two
   accordions on this site are one behaviour written one way rather than two
   descriptions of it.

   Only the three inside .accordion-trigger were swapped. The right-pointing
   path appears thirteen times in product.html -- the gallery arrows, the
   thumbnails, the buy bar -- and those all still mean "next".

   No media query. These only render on a phone: the accordion is expanded on
   desktop and at narrow widths with a mouse, and both of those blocks set
   `display: none` on this svg, so a rule here is a rule for mobile. */
/* Black, and pinned to the right edge.

   --offwhite is what this was, and it is the same class of mistake the SORT
   button had: a near-white token, drawn for the dark page this component
   came from, left in place when the product page went white. The chevrons
   were there the whole time at about 1.1:1 against the ground -- visible
   enough in a screenshot to be identified as gray, not visible enough to
   read as a control. #141414 is what every other dark-on-light mark on this
   page is set to.

   margin-left: auto is belt and braces. .accordion-trigger is already
   justify-content: space-between, which pushes the glyph to the right edge
   on its own -- but that only holds while the row has exactly two children,
   and the label is a bare text node rather than an element, so a third
   thing added to this button would quietly redistribute the space instead.
   An auto margin says right and keeps saying it. */
.product-detail-accordion .accordion-trigger svg {
  transform: rotate(0deg);
  color: #141414;
  margin-left: auto;
}
.product-detail-accordion .accordion-item.open .accordion-trigger svg { transform: rotate(180deg); }
/* Reviews overview: three equal boxes, left to right -- the rating bars, the
   overall score, and the star picker that starts a review. Grid rather than
   flex so the three stay exactly even regardless of their content, and so
   the stacked state is one property change. The boxes carry the border and
   padding; the blocks inside them are plain. */
.product-reviews-overview {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
  padding-bottom: 0;
}
.reviews-box {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* The title stays put at the top of every box; .reviews-box-body takes the
     rest of the height and centres its own contents in it, so the three
     titles line up across the row however unevenly the boxes fill. */
  justify-content: flex-start;
  /* No shared gap: the title and subtext carry their own margins so the
     header reads as one tight block, and everything below it is spaced from
     the subtext rather than from an even gap between all three children. */
  gap: 0;
  padding: 8px 20px;
  border: 1.3px solid var(--border-strong);
  /* Square, like the product cards' photo and Quick Add bar. Written out
     rather than left unsaid: --radius runs through this file as a habit, and
     a later rule picking it up would round these again by accident. */
  border-radius: 0;
}
/* These two sit inside .reviews-box-body now
, so the child combinator that
   used to reach them from .reviews-box no longer applies. */
.reviews-box-body > .rating-breakdown,
.reviews-box-body > .product-reviews-summary {
  width: 100%;
  padding-bottom: 0;
  margin-bottom: 0;
  border: none;
}
.reviews-box-body {
  /* flex: 1 takes whatever height is left under the title and subtext, and
     centring on both axes puts the content -- the bars, the score, the star
     row -- in the middle of it. */
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  width: 100%;
}
/* Title and subtext both sit at the top of the box, above the body that
   centres whatever content the box holds. */
.reviews-box-title,
.reviews-box > .reviews-box-note {
  flex: none;
}
/* One consistent step from the header block down to the content in every
   box, whether that content is the bars, the score, or the stars. */
.reviews-box > .reviews-box-note { margin-bottom: 6px; }
.reviews-box-title {
  margin: 0 0 3px;
  font-family: var(--font-display);
  font-size: 1.05rem;
  letter-spacing: 0.0375em;
  color: var(--title);
  text-align: center;
}
.reviews-box-note {
  margin: 0;
  font-size: 0.78rem;
  line-height: 1.45;
  color: var(--on-dark-faint);
  text-align: center;
}
/* Stars sit centred under the title; the note sits under them. Hovering
   paints red from the left up to the star under the cursor, and turns those
   outlines white, so the row reads as a rating rather than one lit star. */
.review-star-picker-inline {
  display: flex;
  justify-content: center;
  gap: 8px;
  /* Fills its box so the squares below have a width to divide up. */
  width: 100%;
}
.review-star-picker-inline[hidden] { display: none; }
/* Each star sits in its own square, matching the footer's social buttons:
   same 1.3px --on-dark-border-strong edge, same --radius-sm corners, same
   transparent fill and same --on-dark on hover. */
.review-star-picker-inline .review-star-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 56px is the cap, not the size. The column these sit in is an exact third
     of the row now, so five fixed squares would overflow it on a narrower
     window -- before, they widened the column instead, which is what made the
     three boxes unequal. Flexing down keeps them inside it, and aspect-ratio
     keeps them square while they do. */
  flex: 1 1 0;
  max-width: 56px;
  height: auto;
  aspect-ratio: 1 / 1;
  padding: 0;
  /* --on-dark-faint, the same gray the rating snapshot's percentages use, so
     the two boxes' quiet type and these frames sit at one weight. */
  border: 1.3px solid var(--on-dark-faint);
  border-radius: var(--radius-sm);
  background: transparent;
  /* No transition here: nothing about the square itself changes -- border,
     background and size all hold. Only the star inside it moves, and that
     carries its own. */
}
/* No per-button scale: the whole row lights up together, so lifting only
   the star under the cursor would fight that. */
.review-star-picker-inline .review-star-btn:hover { transform: none; }
/* Locked: the five squares look exactly as they do when they work, because
   this is the shape of the box, not a disabled control the eye should skip.
   Only the cursor changes. (A disabled button fires no pointer events, so
   the hover paint never runs and the stars stay gray on their own.) */
.review-star-picker-inline.is-locked .review-star-btn { cursor: default; }
.review-star-picker-inline.is-locked .review-star-btn:disabled { opacity: 1; }
/* Solid, and the same gray the overall-rating row uses for its empty stars
   (.star-rating-bg) -- the two sit side by side in that box, so they should
   be one colour.

   stroke-width: 0 is what makes a translucent colour safe here. A stroke
   straddles the shape's edge, so its inner half lies over the fill and two
   18% layers composite to about 33% right along the border -- that is the
   lighter outline these had before. With no stroke width there is nothing to
   double up, and the colour can stay the token's own rgba rather than an
   opaque approximation that would only match over one backdrop. */
.review-star-picker-inline .review-star-btn svg {
  width: 31px;
  height: 31px;
  stroke-width: 0;
  fill: rgba(251, 247, 243, 0.18);
  stroke: rgba(251, 247, 243, 0.18);
  /* Restates the base rule's fill/stroke because it adds transform to the
     list, and `transition` is not additive -- naming one property here would
     drop the other two. */
  transition: fill 0.15s ease, stroke 0.15s ease, transform 0.2s ease;
}
/* Lit: the star goes solid red and grows a little. Fill and stroke both,
   since it is solid now and leaving the outline gray would ring it.

   Colour only -- nothing grows. .is-hot runs from the left up to the hovered
   star, so the row lights as a group; a size change on top of that is more
   movement than the gesture needs. (.review-star-btn's own :hover scale is
   cancelled just above for the same reason.) */
.review-star-picker-inline .review-star-btn.is-hot svg {
  fill: var(--title-red);
  stroke: var(--title-red);
}

/* Three across only once there is room; below that they stack, and the
   mobile layout was already stacked. */
@media (min-width: 860.02px) {
  .product-reviews-overview {
    /* minmax(0, 1fr), not 1fr. A plain 1fr is minmax(auto, 1fr), and `auto`
       as a minimum means a track can refuse to shrink below its content --
       so as the window narrowed, the box with the widest contents (the five
       star buttons) pushed its own column wider and squeezed the other two.
       A zero minimum holds all three to an exact third whatever is in them.
       The stars flex to suit -- see .review-star-picker-inline below. */
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 20px;
    align-items: stretch;
  }
  /* The bars are the tallest content in the row, so they set every box's
     height -- tightening their gaps shortens all three. */
  /* These four gaps are what set the height of all three boxes, since the
     bars are the tallest content in the row. */
  .product-reviews-overview .rating-breakdown { justify-content: center; gap: 2px; }
  /* Both blocks sit a few pixels above dead centre in their box bodies --
     optical centring, since the subtext above them makes exact centring read
     as slightly low. Relative offsets, so neither changes the row's height. */
  .product-reviews-overview .rating-breakdown { position: relative; top: -6px; }
  .product-reviews-overview .review-star-picker-inline { position: relative; top: -8px; }
  .product-reviews-overview .product-reviews-summary {
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    gap: 10px;
  }
  .product-reviews-overview .product-reviews-score { font-size: 2.6rem; }
  .product-reviews-overview .product-reviews-summary .star-rating.is-small .star-rating-row svg { width: 26px; height: 26px; }
  .product-reviews-overview .product-reviews-count { font-size: 1rem; }
}
.product-reviews-summary { display: flex; align-items: center; gap: 10px; padding-bottom: 16px; }
.product-reviews-score { font-family: var(--font-display); font-size: 1.3rem; line-height: 1; color: var(--on-dark); }
.product-reviews-score-suffix { font-family: var(--font-body); font-weight: 400; font-size: 0.5em; }
.product-reviews-count { font-size: 0.82rem; line-height: 1; color: var(--on-dark-faint); }
@media (max-width: 860px) {
  /* Border and padding live on .reviews-box now, so this only handles the
     stacked arrangement and the larger type. */
  .product-reviews-summary {
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 14px;
  }
  .product-reviews-score { font-size: 2.6rem; }
  .product-reviews-summary .star-rating.is-small .star-rating-row svg { width: 28px; height: 28px; }
  .product-reviews-count { font-size: 1rem; }
}
.rating-breakdown { display: flex; flex-direction: column; gap: 8px; padding-bottom: 16px; }
.rating-breakdown-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  border: none;
  background: transparent;
  padding: 0;
  font: inherit;
  cursor: pointer;
}
.rating-breakdown-label { flex: 0 0 52px; text-align: left; font-size: 0.82rem; color: var(--on-dark-faint); transition: color 0.15s ease; }
.rating-breakdown-track { flex: 1; height: 8px; border-radius: 999px; background: var(--on-dark-border); overflow: hidden; }
.rating-breakdown-fill { height: 100%; border-radius: inherit; background: var(--title-red); transition: background 0.15s ease; }
.rating-breakdown-count { flex: 0 0 32px; text-align: right; font-size: 0.82rem; color: var(--on-dark-faint); transition: color 0.15s ease; }
/* A row with nothing behind it. It keeps its label, its empty track and its
   0%, and loses only the pointer and the hover lift -- the :disabled:hover
   pairs below outrank the plain :hover rules above them on specificity, not
   on order. */
.rating-breakdown-row:disabled { cursor: default; }
.rating-breakdown-row:disabled:hover .rating-breakdown-label,
.rating-breakdown-row:disabled:hover .rating-breakdown-count { color: var(--on-dark-faint); }
.rating-breakdown-row:disabled:hover .rating-breakdown-fill { background: var(--title-red); }
.rating-breakdown-row:hover .rating-breakdown-label,
.rating-breakdown-row:hover .rating-breakdown-count,
.rating-breakdown-row.is-active .rating-breakdown-label,
.rating-breakdown-row.is-active .rating-breakdown-count { color: var(--offwhite); }
.rating-breakdown-row:hover .rating-breakdown-fill,
.rating-breakdown-row.is-active .rating-breakdown-fill { background: #ff5347; }
@media (max-width: 860px) {
  .rating-breakdown {
    gap: 4px;
    padding: 18px 16px;
    margin-bottom: 16px;
    border: 1.3px solid var(--border-strong);
    border-radius: var(--radius);
  }
  .rating-breakdown-label { flex-basis: 58px; font-size: 0.9rem; }
  .rating-breakdown-count { flex-basis: 38px; font-size: 0.9rem; }
}
.product-reviews-purchase-note { margin: 0; font-size: 0.82rem; color: var(--text-faint); }
.product-reviews-heading {
  display: flex;
  align-items: baseline;
  gap: 6px;
  /* The top margin stands in for the Write a Review block that used to sit
     between this heading and the boxes above it. */
  margin: 44px 0 16px;
  font-family: var(--font-body);
  font-size: 1.02rem;
  font-weight: 600;
  color: var(--text);
}
.product-reviews-heading-count { font-weight: 400; color: var(--text-faint); }
.product-reviews-divider { border-top: 1.3px solid var(--border); margin-bottom: 22px; }
.product-reviews-toolbar { display: flex; flex-direction: column; align-items: stretch; gap: 10px; padding-bottom: 16px; }
.product-reviews-toolbar[hidden] { display: none; }
/* Same size/border/corner-radius as the sort and rating-filter dropdowns
   right below it -- .color-select-trigger is height:46px with
   border-radius:var(--radius), so this matches those exactly instead of
   its own smaller radius/padding-based height. */
.reviews-search-input {
  width: 100%;
  height: 46px;
  /* Right side padded clear of the icon that sits over it. */
  padding: 0 40px 0 14px;
  border-radius: var(--radius);
  border: 1.3px solid var(--border-strong);
  background: transparent;
  color: var(--text);
  font-family: var(--font-body);
  font-size: 0.85rem;
  transition: border-color 0.2s ease;
}
.reviews-search-input::placeholder { color: var(--text-muted); }
.reviews-search-input:focus { outline: none; border-color: var(--text); }
/* The magnifier, and the clear button that replaces it the moment there is
   anything to clear. Both grays match the placeholder and the sort/rating
   chevrons beside them, so the whole toolbar reads as one row. */
.reviews-search-field {
  position: relative;
  width: 100%;
  /* One pair of numbers for both icons: the button is wider than the glyph,
     for a comfortable tap target, and is centred on the glyph's spot rather
     than offset into place on its own -- so nothing shifts as they swap. */
  --reviews-search-glyph: 17px;
  --reviews-search-inset: 15px;
  --reviews-search-btn: 36px;
}
.reviews-search-icon {
  position: absolute;
  top: 50%;
  right: var(--reviews-search-inset);
  transform: translateY(-50%);
  width: var(--reviews-search-glyph);
  height: var(--reviews-search-glyph);
  color: var(--text-muted);
  pointer-events: none;
}
.reviews-search-clear {
  display: none;
  position: absolute;
  top: 50%;
  right: calc(var(--reviews-search-inset)
              - (var(--reviews-search-btn) - var(--reviews-search-glyph)) / 2);
  transform: translateY(-50%);
  width: var(--reviews-search-btn);
  height: var(--reviews-search-btn);
  align-items: center;
  justify-content: center;
  padding: 0;
  border: none;
  background: none;
  color: var(--text-muted);
  transition: color 0.2s ease;
}
.reviews-search-clear svg {
  width: var(--reviews-search-glyph);
  height: var(--reviews-search-glyph);
}
.reviews-search-clear:hover { color: var(--text); }
.reviews-search-field.has-query .reviews-search-icon { display: none; }
.reviews-search-field.has-query .reviews-search-clear { display: flex; }
/* Desktop: search, sort, and rating filter sit in one equal-width row
   instead of stacking -- only stacks again if the screen gets narrow
   enough to fall back to the mobile layout. */
@media (min-width: 860.02px) {
  /* Grid, not flex: three equal columns regardless of each item's own
     content-based minimum width (flex:1 on all three still let the
     search input end up wider than the dropdowns, since flex's
     min-content floor doesn't apply the same way to an <input> as it
     does to the dropdown buttons' text). */
  .product-reviews-toolbar {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);
    align-items: center;
    /* Matches .product-reviews-overview's gap so each control lines up with
       the box above it -- same columns, same gutters, same widths. */
    gap: 20px;
  }
  .product-reviews-toolbar .reviews-search-field,
  .product-reviews-toolbar .color-select-dropdown { width: auto; min-width: 0; }
}
/* The closed field itself (search input, or a dropdown's collapsed
   trigger showing the current value) never gets a hover line. Inside the
   opened list, no background highlight either -- roomy padding, no
   rounded corners, and a line under the text (inset from the list's own
   edges, not touching them) that fades in while sliding down into place
   on hover, and fades back up when the cursor leaves -- same motion as
   the main nav's underline. Covers both the reviews toolbar dropdowns
   and the product page's Select Color dropdown. */
.quick-view-info .color-select-list,
.pd-buybar-color .color-select-list,
.product-reviews-toolbar .color-select-list,
.product-color-select .color-select-list {
  padding: 0;
  /* No shadow. --shadow is 0 20px 44px rgba(0,0,0,0.16) -- a light-UI shadow,
     and these panels are near-black on a near-black page. It reads as depth
     nowhere and instead darkens a 20px band directly under the panel, which
     looks like the panel carrying on past its last row. That band is the
     "extra space at the bottom"; the list itself ends flush with the last
     item, since padding is 0 above. */
  box-shadow: none;
}
/* Spaced like the header's Store menu, which is 0.88rem on 10px of vertical
   padding -- about 45px an item. These were 14px, which at 0.85rem came out
   near 53px and read as a looser list than the one in the nav. Matching the
   nav's font size as well as its padding is what makes the two identical
   rather than merely close. */
.quick-view-info .color-select-list li,
.pd-buybar-color .color-select-list li,
.product-reviews-toolbar .color-select-list li,
.product-color-select .color-select-list li {
  position: relative;
  padding: 10px 14px;
  font-size: 0.88rem;
  border-radius: 0;
  transition: color 0.2s ease;
}
.quick-view-info .color-select-list li::after,
.pd-buybar-color .color-select-list li::after,
.product-reviews-toolbar .color-select-list li::after,
.product-color-select .color-select-list li::after {
  content: '';
  position: absolute;
  /* Inset to the item's own side padding, and lifted just clear of the
     baseline -- both follow the padding above, so they move with it. */
  left: 14px;
  right: 14px;
  bottom: 4px;
  height: 1px;
  background: var(--offwhite);
  opacity: 0;
  transform: translateY(-6px);
  transition: transform 0.3s ease, opacity 0.3s ease;
}
.quick-view-info .color-select-list li:hover,
.pd-buybar-color .color-select-list li:hover,
.product-reviews-toolbar .color-select-list li:hover,
.product-color-select .color-select-list li:hover {
  background: transparent;
  color: var(--offwhite);
}
.quick-view-info .color-select-list li:hover::after,
.pd-buybar-color .color-select-list li:hover::after,
.product-reviews-toolbar .color-select-list li:hover::after,
.product-color-select .color-select-list li:hover::after {
  opacity: 1;
  transform: translateY(0);
}
.product-reviews-empty { margin: 0; padding-bottom: 16px; font-size: 0.9rem; color: var(--text-faint); }
.product-reviews-visible-count { margin: 0 0 12px; text-align: center; font-size: 0.82rem; color: var(--text-faint); }
.product-reviews-list { padding-bottom: 22px; }
.product-reviews-list .qv-review-card:last-child { border-bottom: none; padding-bottom: 0; }
.product-reviews-show-more {
  display: flex;
  align-items: center;
  justify-content: center;
  width: auto;
  margin: 4px auto 22px;
  padding: 10px 26px;
  border-radius: 999px;
  border: 1.3px solid var(--border-strong);
  background: transparent;
  color: var(--text);
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 0.78rem;
  font-weight: 400;
  cursor: pointer;
  transition: border-color 0.2s ease, color 0.2s ease;
}
.product-reviews-show-more:hover { border-color: var(--text); color: var(--text); }
.product-reviews-show-more[hidden] { display: none; }

/* ---- Quick view popup: same color/cart-above-info-accordion pattern as
   the product detail page. ---- */
.quick-view-info-scroll { display: flex; flex-direction: column; }
.qv-detail-header { order: 1; }
.quick-view-actions { order: 4; }
@media (max-width: 860px) {
  .quick-view-actions { order: 3; }
}
.product-detail-accordion .accordion-panel .link-arrow { display: flex; width: fit-content; padding-bottom: 22px; }
.product-detail-accordion .accordion-panel > p,
[data-product-specs] li {
  color: var(--text-muted);
  font-size: 0.92rem;
}

/* ---- Contact ---- */
/* One column down the middle. The page used to be a 1fr 1fr grid with the
   form on the left and two info cards on the right, which spent half the
   width on two lines of text and left the form -- the only thing on the page
   anyone came to use -- squeezed into the other half. Everything is stacked
   now and shares one measure.

   680px, not the full container. A form is read line by line like body copy,
   and fields running the width of a 1500px page make every label a long way
   from its own input. */
.contact-lead,
.contact-form,
.contact-direct,
.contact-faq { max-width: 680px; margin-left: auto; margin-right: auto; }
.contact-lead { margin-bottom: 40px; }
.contact-lead p { color: var(--text-muted); margin-bottom: 14px; }
.contact-lead p:last-child { margin-bottom: 0; }
/* No shadow on Send Message. Every other pill on the site sits on a page or a
   photograph and the shadow lifts it off; this one sits at the bottom of a
   column of framed inputs, and against those flat panels it read as the one
   thing on the form trying to float. */
.contact-form .btn { box-shadow: none; }
/* The "optional" tag on a label, at the label's own size but quieter -- it
   marks the one field that is not required without a second row of type. */
.form-optional {
  text-transform: none;
  letter-spacing: 0.02em;
  color: var(--text-faint);
  opacity: 0.75;
}
/* The old side column, laid flat: the two facts worth stating, in a row under
   the form rather than a stack beside it. Drops to one column with the rest
   of the page. */
.contact-direct {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
  margin-top: 44px;
}
/* Before You Write, lifted off the page as its own band -- the same #1a1a1a
   the home page's story section carries, so the two read as the same kind of
   raised block rather than two lightly different greys.

   The class is on the SECTION, not .contact-faq, because the band has to run
   the full width of the window while the questions inside it stay on the
   680px measure the form above them uses.

   .section-ruled's border-top stays. It used to be the only thing marking the
   start of this section, and now it draws the top edge of the band. */
.contact-faq-section { background: #1a1a1a; }
/* This accordion opens with a chevron, not the tan plus-into-cross the FAQ
   page uses: white, pointing down when the panel is shut and up when it is
   open. A plus says "add", and it says it in the one accent colour on the
   page, which put four small red marks down a column that is otherwise type.
   A chevron says which way the thing is about to move and says it quietly --
   the same disclosure the mobile nav's Store row uses.

   Scoped to .contact-faq. The FAQ page's own accordion still carries the
   plus; move that too if the two should match.

   180deg, overriding the 45deg that turns a plus into a cross -- the markup
   here is a down chevron (see contact.html), so a quarter turn would leave it
   pointing sideways. */
.contact-faq .accordion-trigger svg { color: var(--offwhite); }
.contact-faq .accordion-item.open .accordion-trigger svg { transform: rotate(180deg); }
.contact-faq h2 { margin-bottom: 8px; }
.contact-faq-lead { color: var(--text-muted); margin-bottom: 24px; }
/* The home page's story links, not the popup's View Full Details: same body
   face at the button's size, an arrow after the label, and the underline that
   slides in on hover -- which this shares by selector further up the file
   rather than by a second copy of the same three rules.

   The arrow HOLDS STILL. It used to be a .quick-view-link, whose arrow spins
   a full turn on hover; that treatment is not used any more. The underline is
   the whole of the hover state, exactly as on the home page. */
.contact-faq-more {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 28px;
  font-family: var(--font-body);
  font-size: 0.9375rem;
  font-weight: 400;
  letter-spacing: 0.0375em;
  color: var(--text-muted);
  transition: color 0.2s ease;
}
.contact-faq-more:hover { color: var(--text); }
.contact-faq-more svg { width: 17px; height: 17px; flex-shrink: 0; }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px; }
.form-row .form-field { margin-bottom: 0; }
@media (max-width: 640px) {
  .form-row { grid-template-columns: 1fr; gap: 0; }
  .form-row .form-field { margin-bottom: 20px; }
}
.form-field { margin-bottom: 20px; }
.form-field label {
  display: block;
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  color: var(--text-faint);
  margin-bottom: 8px;
}
.required-mark {
  color: var(--rust);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
  font-size: 2.4em;
  line-height: 0;
  margin-left: 3px;
  vertical-align: middle;
}
.form-field input, .form-field textarea, .form-field select {
  width: 100%;
  padding: 13px 16px;
  border-radius: var(--radius-sm);
  border: 1.3px solid var(--border-strong);
  background: var(--surface);
  color: var(--text);
  transition: border-color 0.2s ease;
}
.form-field input:hover, .form-field textarea:hover, .form-field select:hover { border-color: var(--offwhite); }
.form-field input:focus, .form-field textarea:focus, .form-field select:focus { outline: none; border-color: var(--rust); }
/* Sign In drawer only: underline-style fields, matching the email
   signup inputs — gray line at rest, white on hover, full white border
   when focused/clicked. */
.account-drawer-body .form-field input {
  padding: 10px 2px;
  border-radius: 0;
  border: 1.3px solid transparent;
  border-bottom: 1.3px solid var(--border-strong);
  background: transparent;
}
.account-drawer-body .form-field input:hover {
  border-bottom-color: var(--offwhite);
}
.account-drawer-body .form-field input:focus {
  border: 1.3px solid var(--offwhite);
}
/* Padding down from 32px and the bottom margin gone: these were sized as a
   stack of full-width panels down a column, and three of them across one row
   need to be quieter than that. The grid gap does the spacing now. */
.contact-info-card {
  background: var(--surface);
  border: 1.3px solid var(--border);
  border-radius: var(--radius);
  padding: 22px;
}
.contact-info-card h4 { margin-bottom: 6px; }
.contact-info-card p { margin: 0; color: var(--text-muted); font-size: 0.92rem; }
.contact-info-card a { color: inherit; }
.contact-info-card a:hover { color: var(--text); }

/* ---- Legal pages ---- */
.legal-content { max-width: 780px; }
.legal-content h2 { margin-top: 2.2em; font-size: 1.3rem; }
.legal-content h2:first-child { margin-top: 0; }
.legal-content p, .legal-content li { color: var(--text-muted); }
.legal-content ul { padding-left: 20px; list-style: disc; margin-bottom: 1em; }
.legal-updated { color: var(--text-faint); font-size: 0.85rem; margin-bottom: 40px; }

/* ---- Cart page layout ---- */
.cart-layout { display: grid; grid-template-columns: 1fr 360px; gap: 48px; align-items: start; }
@media (max-width: 860px) {
  .cart-layout { grid-template-columns: 1fr; }
}

/* ---- Cart drawer ---- */
/* 400, with the drawer at 401 -- the same tier the nav sheet and the search
   overlay sit on, and above the quick view at 300/301. All three are opened
   from the header, the header stays out from under the quick view's backdrop,
   so all three have to be able to arrive over it. They never contend with each
   other for the tier: opening any one of them closes the other two. */
/* Black at 50%, the value every other panel dims with, rather than the warm
   near-black this carried. And starting at the header's foot rather than the
   top of the window, at every width -- the same as the nav panel: the header
   stays lit, and its own buttons stay live while the cart is open. */
.cart-drawer-backdrop {
  position: fixed; inset: 0; top: var(--sheet-top);
  background: rgba(0, 0, 0, 0.5);
  opacity: 0; pointer-events: none; transition: opacity 0.25s ease; z-index: 400;
}
.cart-drawer-backdrop.open { opacity: 1; pointer-events: auto; }
/* A column down the right of the page from the header's foot to the bottom of
   the window -- the shape the nav panel takes at a narrow width, at its own
   width and with its own slide in from the right. bottom: 0 with no height, so
   it ends where the window does whatever --sheet-top turns out to be. */
.cart-drawer {
  position: fixed; top: var(--sheet-top); bottom: 0; right: 0; width: 420px; max-width: 92vw;
  background: var(--surface); border-left: 1.3px solid var(--border);
  display: flex; flex-direction: column;
  transform: translateX(100%); transition: transform 0.3s ease; z-index: 401;
}
.cart-drawer.open { transform: translateX(0); }
.cart-drawer-handle { display: none; }
.cart-drawer-head { display: flex; justify-content: space-between; align-items: center; padding: 22px 24px; border-bottom: 1.3px solid var(--border); }
.cart-drawer-head .icon-btn { color: var(--text); border-color: var(--border-strong); }
.cart-drawer-head .icon-btn:hover { border-color: var(--on-dark); }
.cart-drawer-head .icon-btn svg { transition: transform 0.2s ease; }
.cart-drawer-head .icon-btn:hover svg { transform: scale(1.35); }
.cart-drawer-items { flex: 1; overflow-y: auto; padding: 16px 24px; }
.cart-line { display: flex; gap: 14px; padding: 16px 0; border-bottom: 1.3px solid var(--border); }
.cart-line-media { width: 68px; height: 68px; border-radius: var(--radius-sm); background: var(--surface-2); display: flex; align-items: center; justify-content: center; flex-shrink: 0; overflow: hidden; }
.cart-line-media svg { width: 50%; height: 50%; color: var(--text-faint); }
.cart-line-info { flex: 1; }
.cart-line-info h4 { font-size: 0.92rem; margin: 0 0 4px; text-transform: none; letter-spacing: 0; font-family: var(--font-body); }
/* Which size and colour this line is. Only drawn when there is something to
   say -- a patch comes one way and gets no label. */
.cart-line-variant {
  margin: -2px 0 6px;
  font-family: var(--font-body);
  font-size: 0.78rem;
  color: var(--text-faint);
}
.cart-line-row { display: flex; align-items: center; gap: 12px; margin-top: 10px; }
.cart-line-qty-stepper {
  display: flex;
  align-items: center;
  height: 38px;
  border-radius: 999px;
  border: 1.3px solid var(--border-strong);
  padding: 0 2px;
  flex-shrink: 0;
}
.cart-line-qty-stepper .qty-stepper-btn {
  display: flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; flex-shrink: 0;
  border: none; background: transparent;
  color: var(--text); cursor: pointer;
}
.cart-line-qty-stepper .qty-stepper-btn svg { width: 14px; height: 14px; }
.cart-line-qty-input {
  width: 26px;
  height: auto;
  border: none;
  background: transparent;
  padding: 0;
  font-size: 0.85rem;
}
.cart-line-price { font-family: var(--font-body); font-size: 0.92rem; margin-left: auto; }
.cart-line-remove { font-size: 0.78rem; color: var(--text-faint); margin-top: 6px; display: inline-block; background: transparent; border: none; padding: 0; }
.cart-line-remove:hover { color: var(--danger); }
.cart-drawer-foot { padding: 22px 24px; border-top: 1.3px solid var(--border); }
.cart-subtotal { display: flex; justify-content: space-between; margin-bottom: 16px; }
.cart-subtotal span:last-child { font-family: var(--font-body); }
.cart-empty { color: var(--text-faint); text-align: center; padding: 60px 0; }
@media (max-width: 860px) {
  /* Each cart line in its own bordered, rounded card instead of a shared
     row divider — and Remove becomes a real tappable pill button instead
     of a bare text link. */
  .cart-line {
    border: 1.3px solid var(--border-strong);
    border-radius: var(--radius);
    padding: 14px;
    margin-bottom: 12px;
  }
  .cart-line:last-child { margin-bottom: 0; }
  .cart-line-remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-top: 10px;
    padding: 6px 16px;
    border-radius: 999px;
    border: 1.3px solid var(--border-strong);
    background: transparent;
    color: var(--text-muted);
    font-family: var(--font-display);
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    cursor: pointer;
    transition: border-color 0.2s ease, color 0.2s ease;
  }
  .cart-line-remove:hover { border-color: var(--danger); color: var(--danger); }
  #cartDrawer {
    /* One rule up, so the sheet's own top border lands ON the header's rather
       than under it -- the same trick the quick view uses. */
    top: calc(var(--sheet-top) - 1.3px); right: 0; left: 0; bottom: 0; height: auto; width: auto; max-width: none;
    border-left: none;
    /* The header's bottom border, carried on across the seam. */
    border-top: 1.3px solid var(--on-dark-border);
    border-radius: 0;
    transform: translateY(100%) translateZ(0);
    transition: transform 0.22s ease;
    will-change: transform;
    backface-visibility: hidden;
    -webkit-backface-visibility: hidden;
  }
  #cartDrawer.open { transform: translateY(0) translateZ(0); }
  /* The strip across the top, as the quick view has it: 52px tall, the grab
     bar centred in it, the close at its right, and one rule closing it off.
     The bar itself moves to a ::before so the element can be the strip. */
  #cartDrawer .cart-drawer-handle {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%; height: 52px; flex-shrink: 0;
    margin: 0;
    background: transparent;
    border-radius: 0;
    border-bottom: 1.3px solid var(--border);
  }
  #cartDrawer .cart-drawer-handle::before {
    content: '';
    width: 44px; height: 5px; border-radius: 3px;
    background: var(--border-strong);
  }
  /* Lifted out of the head and into the strip above it, which leaves Your Cart
     on its own below the rule. Absolute against the drawer, which is fixed, so
     it sits in the strip without being a child of it -- the markup is the same
     on eleven pages and this is one rule instead. */
  #cartDrawer .cart-drawer-head .icon-btn {
    display: inline-flex;
    position: absolute;
    top: 6px;
    right: clamp(16px, 4vw, 64px);
    width: 40px;
    height: 40px;
    border-radius: 50%;
    z-index: 6;
  }
  #cartDrawer .cart-drawer-head .icon-btn svg { width: 20px; height: 20px; }
}

/* ---- Mobile nav bottom sheet ---- */
.mobile-nav-backdrop,
.sort-dropdown-backdrop,
.filter-sidebar-backdrop {
  position: fixed; inset: 0; background: rgba(0, 0, 0, 0.5);
  opacity: 0; pointer-events: none; transition: opacity 0.25s ease; z-index: 300;
}
.mobile-nav-backdrop.open,
.sort-dropdown-backdrop.open,
.filter-sidebar-backdrop.open { opacity: 1; pointer-events: auto; }
/* The sheet itself never moves and never gets a transform: it's a static
   "porthole" that just provides the rounded-corner clip. The .open toggle
   from JS still lands here (so nothing else in the codebase needs to know
   about the split), but the actual slide-up transform lives one level
   down on .mobile-nav-sheet-inner. Putting both the rounded-corner
   overflow:hidden clip AND the sliding transform on the same element is
   exactly the combination that made Safari/Chrome show a trailing seam
   along the clip mask mid-animation; splitting them onto two elements
   avoids it entirely. */
/* Above every other sheet, and that is the whole reason for the number. The
   header stays out from under the backdrops now, so its buttons are live
   while something else is open -- and the menu one of those buttons opens has
   to arrive on top of whatever it was opened over, the quick view included.
   Anything level with it would be decided by document order instead, and the
   quick view sits later in the markup. Still under the two full-screen photo
   viewers at 600 and the review sheet at 700: those cover the header, so the
   button is not reachable while they are up. */
.mobile-nav-backdrop { z-index: 400; }
.mobile-nav-sheet {
  /* Up to the foot of the header, not an arbitrary tenth of the window: at
     10% the sheet stopped wherever it happened to stop, which on a tall phone
     left a band of dimmed page above it and on a short one ran under the
     header. */
  position: fixed; left: 0; right: 0; bottom: 0; top: var(--sheet-top);
  /* Square. These meet the header's own edge and fill the width of the
     screen -- a rounded top corner would be a card's tell on something
     that is not a card. */
  border-radius: 0;
  z-index: 401;
  overflow: hidden;
  pointer-events: none;
}
.mobile-nav-sheet.open { pointer-events: auto; }
.mobile-nav-sheet-inner {
  height: 100%;
  /* No rule along the top: the sheet meets the header's own bottom edge,
     and two lines a pixel apart read as a seam rather than an edge. */
  background: var(--forest);
  display: flex; flex-direction: column;
  transform: translateY(100%) translateZ(0); transition: transform 0.22s ease;
  will-change: transform;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}
.mobile-nav-sheet.open .mobile-nav-sheet-inner { transform: translateY(0) translateZ(0); }
/* The strip across the top of the sheet, built the way the quick view's and
   the review sheet's are: a full-width bar with the grab bar centred in it and
   a rule closing it off, rather than a lone pill floating on some margin. The
   bar is the ::before, so the strip itself is a box with a height to give the
   pill its air -- which is what makes this read as the head of the sheet.

   52px and centred, matching the quick view's strip. That one is 52 because
   it is also the box its X sits in; there is no X here -- this sheet is
   dismissed by the header's own toggle and by the backdrop -- but the height
   is what the two have in common visually, so it stays.

   No sticky and no background of its own, which the other two both need: they
   sit INSIDE their scrolling panel, and this is a flex sibling above
   .mobile-nav-list, so it is already outside everything that scrolls. */
.mobile-nav-handle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 52px;
  flex-shrink: 0;
  /* The sheet's own divider, the same rule that runs between the nav rows
     below it -- not the quick view's --border-strong, which is the light
     context's line and would be the one bright edge in here. */
  border-bottom: 1.3px solid var(--on-dark-border);
}
.mobile-nav-handle::before {
  content: '';
  width: 44px; height: 5px; border-radius: 3px;
  background: var(--on-dark-border-strong);
}
.mobile-nav-list { flex: 1; overflow-y: auto; padding: 8px 24px 24px; }
.mobile-nav-link {
  display: flex; align-items: center; justify-content: space-between;
  width: 100%; background: none; border: none;
  box-shadow: 0 1.3px 0 0 var(--on-dark-border);
  padding: 16px 0; font-family: var(--font-display); font-size: 1.05rem;
  text-transform: uppercase; letter-spacing: 0.04em; color: var(--on-dark); text-align: left;
}
.mobile-nav-plus {
  display: flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; flex-shrink: 0;
}
.mobile-nav-plus svg { width: 18px; height: 18px; color: var(--offwhite); transition: transform 0.2s ease; }
/* Pointing down at rest and flipping up when the Store list is out -- the
   usual way a disclosure says which way it is about to move. It used to go
   right-to-down, which said something was coming but not which direction it
   would leave in.

   The icon is drawn pointing RIGHT, so neither state is its natural one: the
   two are a quarter turn either side of it. Rotating the drawing rather than
   swapping the path keeps the flip animatable -- transform tweens, `d` does
   not -- and the same span is the only chevron in this sheet, so nothing else
   is riding on its resting angle. */
.mobile-nav-plus svg { transform: rotate(90deg); }
[data-mobile-nav-toggle-store][aria-expanded="true"] .mobile-nav-plus svg { transform: rotate(-90deg); }
.mobile-nav-submenu {
  max-height: 0; overflow: hidden; transition: max-height 0.3s ease;
  display: flex; flex-direction: column;
}
.mobile-nav-submenu.open { max-height: 320px; }
.mobile-nav-sublink {
  padding: 12px 0 12px 16px; font-family: var(--font-body); font-size: 0.92rem;
  color: var(--on-dark-muted); border-bottom: 1.3px solid var(--on-dark-border);
}
.mobile-nav-sublink:hover { color: var(--on-dark); }
.mobile-nav-foot {
  padding: 20px 24px 28px; flex-shrink: 0;
  display: flex; flex-direction: column; gap: 18px;
}
.mobile-nav-region.region-dropdown { justify-self: auto; }
.mobile-nav-social { display: flex; gap: 10px; }
.mobile-nav-login {
  display: flex; align-items: center; gap: 10px;
  background: none; border: none; color: var(--on-dark); padding: 0;
  font-family: var(--font-display); font-size: 0.95rem; text-transform: uppercase; letter-spacing: 0.04em;
}
.mobile-nav-login svg { width: 20px; height: 20px; }

/* ---- Search overlay ---- */
/* Same tier as the cart and the nav, and for the same reason -- see the note
   on .cart-drawer-backdrop.

   Black at 50%, the value the nav and filter sheets dim with, rather than the
   warm near-black this used to carry. Everything under the search bar is meant
   to read as put aside while you type, and it should look the same as it does
   under every other panel the header opens. Full height on purpose, unlike
   those two: the bar slides down over the header, so there is no header left
   showing to keep bright. */
.search-overlay-backdrop {
  position: fixed; inset: 0; background: rgba(0, 0, 0, 0.5);
  opacity: 0; pointer-events: none; transition: opacity 0.25s ease; z-index: 400;
}
.search-overlay-backdrop.open { opacity: 1; pointer-events: auto; }
.search-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  background: var(--surface);
  border-bottom: 1.3px solid var(--border);
  box-shadow: var(--shadow);
  transform: translateY(-100%);
  transition: transform 0.3s ease;
  z-index: 401;
  padding: 28px 0;
  max-height: 80vh;
  overflow-y: auto;
}
.search-overlay.open { transform: translateY(0); }
.search-input-row {
  display: flex;
  align-items: center;
  gap: 14px;
  border-bottom: 1.3px solid var(--border-strong);
  padding-bottom: 16px;
}
.search-input-icon { width: 22px; height: 22px; color: var(--text-faint); flex-shrink: 0; }
.search-input-row .icon-btn svg { transition: transform 0.2s ease; }
.search-input-row .icon-btn:hover svg { transform: scale(1.35); }
.search-input-row input {
  flex: 1;
  border: none;
  outline: none;
  background: none;
  font-family: var(--font-display);
  font-size: 1.4rem;
  color: var(--text);
}
.search-input-row input::placeholder { color: var(--text-faint); }
.search-results {
  margin-top: 24px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 20px;
}
.search-result-item { display: flex; flex-direction: column; gap: 8px; color: var(--text); }
.search-result-media {
  aspect-ratio: 1;
  background: var(--surface-2);
  border-radius: var(--radius);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.search-result-media svg { width: 40%; height: 40%; color: var(--text-faint); }
.search-result-item h4 { margin: 0; font-size: 0.92rem; font-weight: 400; font-family: var(--font-display); }
.search-result-item .price { font-size: 0.85rem; color: var(--text-muted); }
.search-empty { color: var(--text-faint); padding: 12px 0; font-size: 0.9rem; }

/* ---- Account drawer ---- */
.account-drawer-body { padding: 24px; }
.account-alt { margin-top: 18px; font-size: 0.85rem; color: var(--text-faint); text-align: center; }
.account-alt a { color: var(--text-faint); text-decoration: underline; transition: color 0.2s ease; }
.account-alt a:hover { color: var(--on-dark); }

/* ---- Back to top ---- */
.back-to-top {
  position: fixed;
  right: 24px;
  bottom: 24px;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--offwhite);
  border: 1.3px solid var(--on-dark-border-strong);
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 4px 14px rgba(26, 26, 26, 0.25);
  opacity: 0;
  transform: translateY(12px);
  pointer-events: none;
  transition: opacity 0.25s ease, transform 0.25s ease, border-color 0.2s ease;
  z-index: 150;
  cursor: pointer;
}
.back-to-top.visible { opacity: 1; transform: translateY(0); pointer-events: auto; }
.back-to-top:hover { border-color: var(--on-dark); }
.back-to-top svg { width: 20px; height: 20px; }

/* ---- Utilities ---- */
.text-center { text-align: center; }
.mt-0 { margin-top: 0; }
.badge-row { display: flex; gap: 10px; flex-wrap: wrap; }

/* ---- Responsive ---- */
@media (max-width: 1024px) {
  .product-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .footer-grid { grid-template-columns: 1fr 1fr; }
  .video-grid { grid-template-columns: 1fr 1fr; }
  .video-grid-page { grid-template-columns: 1fr 1fr; }
  .video-section-split { grid-template-columns: 1fr; gap: 32px; }
}

@media (max-width: 640px) {
  .video-grid, .video-grid-page { grid-template-columns: 1fr; }
}

@media (max-width: 860px) {
  .main-nav { display: none; }
  .header-inner { grid-template-columns: auto auto; justify-content: space-between; }
  .nav-toggle { display: inline-flex; order: 3; }
  .header-actions [data-open-search] { order: 1; }
  .header-actions [data-open-cart] { order: 2; }
  .header-actions [data-open-account] { display: none; }
  .split { grid-template-columns: 1fr; gap: 24px; }
  .ambassador-banner { grid-template-columns: 1fr; }
  /* The contact page is a single centred column at every width now, so there
     is no two-up grid left to collapse -- .contact-grid is gone. What does
     need collapsing is the row of three info cards under the form. */
  .contact-direct { grid-template-columns: 1fr; gap: 12px; }
  .membership-card { padding: 32px 22px; }
  .hero-stats { position: static; margin-top: 30px; justify-content: center; }
  /* The gutter again, for the reason the base rule restates it: this is a
     shorthand and `0` on the sides was harmless while the copy was a
     centred column inside a 780px cap. Ranged left, it put the headline
     flush against the window edge at every width under 860. */
  .hero-content { padding: 42px var(--page-gutter); }
}

@media (max-width: 640px) {
  :root { --header-h: 78px; }
  .product-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
  .footer-grid { grid-template-columns: 1fr; gap: 0; }
  .section { padding: 64px 0; }
  [data-product-root] { padding-top: 12px; }
  .subscribe { padding: 28px 0; }
  .subscribe-row { gap: 20px; }
  .subscribe-stack { max-width: 100%; }
  .subscribe-heading h2 { white-space: normal; }
  .hero-actions { flex-direction: column; align-items: center; }
  .section-head { flex-direction: column; align-items: flex-start; }
  .video-section-side { align-items: flex-start; text-align: left; max-width: 100%; }
  .footer-grid { padding-bottom: 0; }
  .footer-utility { flex-direction: column; justify-content: center; gap: 16px; margin-top: 20px; }
  .footer-bottom { text-align: center; white-space: normal; }
  .footer-social { justify-content: center; }
}

/* Touch devices fire :hover on tap and it sticks until you tap elsewhere,
   which makes the desktop hover effects look glitchy/stuck on mobile.
   Neutralize the button hover effects specifically for touch. */
@media (hover: none) {
  .btn-cart:hover, .btn-tan:hover, .btn-primary:hover, .btn-outline:hover, .btn-ghost:hover, .btn-light:hover,
  .btn-hero:hover, .brand-strip .btn-hero:hover,
  .cart-overlay-btn:hover {
    background: var(--btn-white-solid);
    color: var(--offwhite);
  }
  .icon-btn:hover,
  /* The phone's border-color hover is gone with the border itself -- see
     .footer-social .icon-btn up the file. */
  .cart-drawer-head .icon-btn:hover { border-color: var(--border-strong); }
  .cart-drawer-head .icon-btn:hover svg,
  .search-input-row .icon-btn:hover svg { transform: none; }
  .product-media-wrap:hover .product-media-link svg,
  .product-card:focus-within .product-media-link svg { transform: none; }
  /* Not `none` like its siblings: the photo's resting scale is the seam
     overfill, not a hover effect, so it has to survive here -- zeroing it
     would open the seam back up and jump the photo by 1% on every tap. */
  .product-media-wrap:hover .product-media-link img { transform: scale(1.01); }
  .quick-view-close:hover { border-color: var(--border-strong); color: inherit; }
  .quick-view-close:hover svg { transform: none; }
  .qv-media-arrow:hover { border-color: var(--border-strong); color: var(--text); }
  .qv-media-arrow:hover svg { transform: none; }
  .quick-view-link:hover, .link-arrow:hover, .video-link-arrow:hover { color: inherit; }
  .quick-view-link:hover svg, .link-arrow:hover svg, .subscribe-arrow:hover svg { animation: none; }
  /* A tap would otherwise leave the underline down until the next one. */
  .quick-view-info .quick-view-link-full:hover span::after,
  .home-story-copy .story-cta:hover span::after {
    opacity: 0;
    transform: translateY(-10px);
  }
  .product-info h3 a:hover::after { opacity: 0; transform: translateY(-10px); }
  .video-card:hover .video-card-media { border-color: var(--border-strong); }
  .video-card:hover .video-card-media.is-visible svg:first-child { transform: none; }
  .footer-col a:hover { color: var(--on-dark-muted); }
  .footer-col a:hover::after { opacity: 0; transform: translateY(-10px); }
  .region-select:hover { color: var(--on-dark-muted); }
  .region-dropdown-list li:hover { background: transparent; color: inherit; }
  .region-dropdown-list li:hover::after { opacity: 0; }
  .breadcrumb a:hover { color: inherit; }
  .filter-option:hover { color: var(--text-muted); }
  .filter-option:hover::after { opacity: 0; transform: translateY(-6px); }
  .sort-dropdown-list li:hover { background: transparent; color: inherit; }
  .sort-dropdown-list li:hover::after { opacity: 0; }
  .cart-line-remove:hover { color: var(--text-faint); }
  .mobile-nav-sublink:hover { color: var(--on-dark-muted); }
  .account-alt a:hover { color: var(--text-faint); }
}

/* Static solid light-gray fill + black text for the outline-pill buttons
   on mobile -- there's no hover on a phone, so the rest-state fill from
   the desktop rules above already applies and just needs to stay put
   (no hover/click fill-change on mobile).
   Gated on (hover: none) as well as width, because "no hover on a phone"
   is the actual condition this block is describing. A desktop window
   resized under 860px still has a mouse, and width alone was flattening
   every button's hover state there. Phones and tablets always match
   hover:none, so their rendering is unchanged. */
@media (max-width: 860px) and (hover: none) {
  .btn-cart::after, .btn-tan::after, .btn-primary::after, .btn-outline::after, .btn-ghost::after, .btn-light::after,
  .btn-hero::after, .brand-strip .btn-hero::after,
  .cart-overlay-btn::after {
    content: none;
  }
  .btn-cart:hover, .btn-tan:hover, .btn-primary:hover, .btn-outline:hover, .btn-ghost:hover, .btn-light:hover {
    background: var(--btn-white-solid);
    color: var(--offwhite);
  }
  .btn-cart:active, .btn-tan:active, .btn-primary:active, .btn-outline:active, .btn-ghost:active, .btn-light:active {
    filter: none;
  }
  /* There used to be a second gray here. Shop All Products, the brand strip's
     button and Quick Add were lifted to --btn-gray-dark (#5c5c5c) on touch,
     on the reasoning that with no hover to reach the lighter fill they should
     just sit on it -- while every other button rested on --btn-white-solid
     (#404040). So a phone showed two grays, and the story carousel showed
     both of them a few lines apart: Learn More dark, Shop All Products light.

     It was also self-contradictory. The (hover: none) block further up sets
     these same three back to #404040 on tap, so they rested light and went
     dark when touched -- the opposite way round from every other button.

     Removed, so all of them rest on --btn-white-solid, tap on it, and match
     what they rest on with a mouse. One gray on the phone. */
  /* Shop the Store is the exception and always was: it holds its resting red
     rather than any gray. The ::after is already gone -- the rule at the top
     of this block drops it for every .btn-hero -- so this is the whole
     button. */
  .hero .btn-hero, .hero .btn-hero:hover {
    background: var(--btn-red-solid);
    color: var(--offwhite);
  }
}

/* ---- Store toolbar (mobile): category pills and sort merge into one
   Filter & Sort bottom sheet, same pattern as the videos page ---- */
@media (max-width: 860px) {
  .filter-group { display: none; }
}

/* Swapping the Sort By dropdown for the Refine bottom sheet is a TOUCH
   treatment, not a narrow-window one. A desktop browser dragged below 860px
   still reports hover:hover / pointer:fine, and there it should keep the
   desktop toolbar -- the sheet is built for thumbs, and it turned up on the
   desktop store any time the window got small. Width alone can't tell the
   two apart; the pointer media features can. */
@media (max-width: 860px) and (hover: none) and (pointer: coarse) {
  .store-filter-dropdown { display: block; margin-bottom: 0; }
  .store-toolbar-wrap {
    display: flex; flex-direction: column; align-items: center;
    padding-bottom: 20px; margin-bottom: 24px; border-bottom: 1.3px solid var(--border);
  }
  .store-toolbar { display: none; }
  .store-toolbar-top { justify-content: center; margin: 0; }
  .sort-dropdown-list {
    /* Content-height, so it has no top edge to place -- but it can never
       reach past the header either. */
    position: fixed; left: 0; right: 0; bottom: 0; top: auto;
    max-height: calc(100vh - var(--sheet-top));
    max-height: calc(100dvh - var(--sheet-top));
    background: var(--forest); border: none;
    border-radius: 0;
    padding: 8px 24px 28px;
    box-shadow: none;
    z-index: 301;
    max-height: none;
    display: flex; flex-direction: column;
    transform: translateY(100%);
    transition: transform 0.22s ease;
  }
  .sort-dropdown-list[hidden] { display: flex; }
  .sort-dropdown-list:not([hidden]) { transform: translateY(0); }
  .sort-dropdown-list .sort-dropdown-handle {
    display: block;
    width: 44px; height: 5px; padding: 0; border-radius: 3px; flex-shrink: 0;
    background: var(--on-dark-border-strong);
    margin: 12px auto 8px;
    pointer-events: none;
  }
  .sort-dropdown-list .sort-dropdown-handle::after { content: none; }
  .sort-dropdown-list li[role="option"] {
    padding: 16px 0; border-bottom: 1.3px solid var(--on-dark-border);
    color: var(--on-dark-muted); font-family: var(--font-body);
    text-transform: uppercase; letter-spacing: 0.04em; background: transparent;
  }
  .sort-dropdown-list li[role="option"]::after { display: none; }
  .sort-dropdown-list li[aria-selected="true"] { color: var(--on-dark); font-weight: 600; }
}

/* ---- Videos page (mobile): Filter Results becomes a bottom sheet too, and
   Sort merges into that same sheet as a second section (see JS: the real
   sort-dropdown-list gets moved into filter-sidebar-sort-section, its own
   trigger button/backdrop are just hidden here). ---- */
@media (max-width: 860px) and (hover: none) and (pointer: coarse) {
  .filter-trigger-label-desktop { display: none; }
  .filter-trigger-label-mobile { display: inline; }
  .filter-dropdown { display: block; margin-bottom: 0; }
  .filter-sidebar-trigger, .sort-select {
    position: relative;
    display: flex; align-items: center; justify-content: center; gap: 8px;
    width: fit-content; min-width: 180px; margin: 0 auto; padding: 11px 18px;
    background: transparent; border: 1.3px solid var(--border-strong); border-radius: 999px;
    color: var(--text); font-family: var(--font-display); text-transform: uppercase;
    letter-spacing: 0.06em; font-size: 0.78rem; font-weight: 600;
  }
  .filter-sidebar-trigger { margin-bottom: 20px; }
  .filter-sidebar-trigger svg {
    display: block; width: 15px; height: 15px;
    position: absolute; left: 18px; top: 50%; transform: translateY(-50%);
  }
  .sort-select svg { display: none; }
  .sort-select:hover, .sort-select[aria-expanded="true"] { color: var(--text); }
  .videos-showing { display: none; }
  .videos-toolbar { justify-content: center; }
  .videos-sort-col { align-items: center; }
  .videos-sort-col .sort-dropdown { display: none; }
  .store-toolbar .sort-select { margin: 0 auto; }
  .filter-sidebar {
    /* One rule up, so its top border lands on the header's -- see #cartDrawer. */
    position: fixed; left: 0; right: 0; bottom: 0; top: calc(var(--sheet-top) - 1.3px); margin-top: 0;
    background: var(--forest); border: none;
    border-top: 1.3px solid var(--on-dark-border);
    border-radius: 0;
    z-index: 301;
    display: flex; flex-direction: column;
    transform: translateY(100%) translateZ(0);
    transition: transform 0.22s ease;
    will-change: transform;
    backface-visibility: hidden;
    -webkit-backface-visibility: hidden;
  }
  .filter-sidebar.open { transform: translateY(0) translateZ(0); }
  /* The same strip the cart and the quick view carry -- see #cartDrawer. */
  .filter-sidebar-handle {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%; height: 52px; flex-shrink: 0;
    margin: 0;
    background: transparent;
    border-radius: 0;
    border-bottom: 1.3px solid var(--on-dark-border);
  }
  .filter-sidebar-handle::before {
    content: '';
    width: 44px; height: 5px; border-radius: 3px;
    background: var(--on-dark-border-strong);
  }
  .filter-sidebar-head { background: transparent; color: var(--on-dark); padding: 8px 24px 12px; }
  /* The one sheet with no close of its own. Same circle, same size and same
     place in the strip as the quick view's. */
  .filter-sidebar-close {
    position: absolute;
    top: 6px;
    right: clamp(16px, 4vw, 64px);
    width: 40px;
    height: 40px;
    border-radius: 50%;
    border: 1.3px solid var(--on-dark-border-strong);
    background: transparent;
    color: var(--on-dark);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 6;
  }
  .filter-sidebar-close svg { width: 20px; height: 20px; }
  .filter-sidebar-group { padding: 14px 24px; border-bottom-color: var(--on-dark-border); overflow-y: auto; }
  .filter-sidebar-group-head h4 { color: var(--title); }
  .filter-check { color: var(--on-dark-muted); }
  .filter-check:hover { color: var(--on-dark); }
  .filter-count { color: var(--on-dark-muted); }

  /* Sort section — visually set apart from Filter above it with a divider
     and its own label, so the two stay distinct inside one merged sheet. */
  /* No border-top: it used to divide this section from the Category list
     above it, and with that list gone it was just a line under the header. */
  .filter-sidebar-sort-section {
    display: block;
    padding: 18px 24px 24px;
  }
  .filter-sidebar-sort-label {
    font-family: var(--font-display);
    font-size: 0.8rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--on-dark-muted);
    margin-bottom: 12px;
  }
  .filter-sidebar-sort-section .sort-dropdown-list {
    position: static;
    transform: none;
    background: transparent;
    border: none;
    border-radius: 0;
    padding: 0;
    box-shadow: none;
    max-height: none;
    display: flex;
    flex-direction: column;
    gap: 2px;
  }
  .filter-sidebar-sort-section .sort-dropdown-handle { display: none; }
  .filter-sidebar-sort-section .sort-dropdown-list li[role="option"] {
    position: relative;
    padding: 10px 36px 10px 12px;
    border-bottom: none;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--on-dark-muted);
    font-family: var(--font-body);
    text-transform: uppercase;
    letter-spacing: 0.04em;
  }
  .filter-sidebar-sort-section .sort-dropdown-list li[role="option"]::after {
    content: '';
    display: block;
    position: absolute;
    left: auto;
    right: 12px;
    bottom: auto;
    top: 50%;
    width: 16px;
    height: 16px;
    transform: translateY(-50%);
    background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none'%3E%3Cpath d='M5 13l4 4L19 7' stroke='white' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat center / contain;
    opacity: 0;
  }
  .filter-sidebar-sort-section .sort-dropdown-list li[aria-selected="true"] {
    background: rgba(251, 247, 243, 0.08);
    color: var(--on-dark);
    font-weight: 600;
  }
  .filter-sidebar-sort-section .sort-dropdown-list li[aria-selected="true"]::after { opacity: 1; }

  /* Store page: Category is single-select — All Products, Bags & Pouches,
     Accessories, or Merch, never combined — styled to match Sort By. */
  .filter-sidebar-category-section {
    display: block;
    padding: 18px 24px;
    border-bottom: 1.3px solid var(--on-dark-border);
  }
  .filter-sidebar-category-label {
    font-family: var(--font-display);
    font-size: 0.8rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--on-dark-muted);
    margin-bottom: 12px;
  }
  .filter-sidebar-category-list {
    display: flex;
    flex-direction: column;
    gap: 2px;
  }
  .filter-sidebar-category-option {
    position: relative;
    display: block;
    width: 100%;
    padding: 10px 36px 10px 12px;
    border-radius: var(--radius-sm);
    border: none;
    background: transparent;
    color: var(--on-dark-muted);
    font-family: var(--font-body);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-size: 0.85rem;
    text-align: left;
    cursor: pointer;
  }
  .filter-sidebar-category-option:hover { color: var(--on-dark); }
  .filter-sidebar-category-option::after {
    content: '';
    display: block;
    position: absolute;
    right: 12px;
    top: 50%;
    width: 16px;
    height: 16px;
    transform: translateY(-50%);
    background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none'%3E%3Cpath d='M5 13l4 4L19 7' stroke='white' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat center / contain;
    opacity: 0;
  }
  .filter-sidebar-category-option.is-active {
    background: rgba(251, 247, 243, 0.08);
    color: var(--on-dark);
    font-weight: 600;
  }
  .filter-sidebar-category-option.is-active::after { opacity: 1; }
}

/* ---- Mobile slide-up sheets: staggered text entrance ---- */
@media (max-width: 860px) {
  @keyframes nav-row-fade-in {
    from { opacity: 0; }
    to { opacity: 1; }
  }
  /* Every row starts at 0.22s, matching each sheet's own 0.22s
     slide-up transition, so the text entrance begins right as the
     sheet arrives at the top, not while it's still moving. Each
     subsequent row in the list adds another 0.03s on top of that,
     covering every row via nth-child (see tiers below), not just
     the first few. Deliberately scoped to '.filter-sidebar.open'/
     '.mobile-nav-sheet.open' only (no standalone
     '.sort-dropdown-list:not([hidden])' fallback): that selector
     used to also match here once the sort list got merged into the
     filter sheet and un-hidden by JS, which made the Sort rows'
     entrance fire once on page load (immediately, before the sheet
     was ever opened) instead of when the sheet actually opens, so
     by the time a user opened it the animation had already long
     since finished.
     A vertical fade+drop was used everywhere (rather than an
     earlier horizontal slide) because on the header nav list,
     translateX on .mobile-nav-link rows -- combined with the
     row's box-shadow divider and the sheet's own overflow:hidden
     clip -- produced a visible trailing seam/line during the
     horizontal motion. The vertical version avoids that
     clip-boundary crossing entirely, so it's used for all of
     these sheets rather than just the one that showed the bug. */
  .filter-sidebar.open .filter-sidebar-group,
  .filter-sidebar.open .filter-sidebar-category-list .filter-sidebar-category-option,
  .filter-sidebar.open .filter-sidebar-sort-section .sort-dropdown-list li[role="option"],
  .mobile-nav-sheet.open .mobile-nav-list > * {
    animation: nav-row-fade-in 0.22s ease both;
    animation-delay: 0.22s;
  }
  /* .filter-sidebar-group and the sort list's own drag-handle are
     leading siblings ahead of the rows we're animating, so those
     rows land on nth-child(3+) and nth-child(2+) respectively,
     offsets below are tuned to each list's real DOM position, not
     a plain 1-based count. The sort section merged inside the
     filter sheet (mobile videos.html) never toggles its own
     [hidden] after the initial merge, it's gated by
     .filter-sidebar.open instead, same as the category list. */
  .filter-sidebar.open .filter-sidebar-group:nth-child(4),
  .filter-sidebar.open .filter-sidebar-category-list .filter-sidebar-category-option:nth-child(2),
  .filter-sidebar.open .filter-sidebar-sort-section .sort-dropdown-list li[role="option"]:nth-child(3),
  .mobile-nav-sheet.open .mobile-nav-list > *:nth-child(2) {
    animation-delay: 0.25s;
  }
  .filter-sidebar.open .filter-sidebar-group:nth-child(5),
  .filter-sidebar.open .filter-sidebar-category-list .filter-sidebar-category-option:nth-child(3),
  .filter-sidebar.open .filter-sidebar-sort-section .sort-dropdown-list li[role="option"]:nth-child(4),
  .mobile-nav-sheet.open .mobile-nav-list > *:nth-child(3) {
    animation-delay: 0.28s;
  }
  .filter-sidebar.open .filter-sidebar-group:nth-child(6),
  .filter-sidebar.open .filter-sidebar-category-list .filter-sidebar-category-option:nth-child(4),
  .filter-sidebar.open .filter-sidebar-sort-section .sort-dropdown-list li[role="option"]:nth-child(5),
  .mobile-nav-sheet.open .mobile-nav-list > *:nth-child(4) {
    animation-delay: 0.31s;
  }
  .filter-sidebar.open .filter-sidebar-group:nth-child(7),
  .filter-sidebar.open .filter-sidebar-category-list .filter-sidebar-category-option:nth-child(5),
  .filter-sidebar.open .filter-sidebar-sort-section .sort-dropdown-list li[role="option"]:nth-child(6),
  .mobile-nav-sheet.open .mobile-nav-list > *:nth-child(5) {
    animation-delay: 0.34s;
  }
  .filter-sidebar.open .filter-sidebar-group:nth-child(8),
  .filter-sidebar.open .filter-sidebar-category-list .filter-sidebar-category-option:nth-child(6),
  .filter-sidebar.open .filter-sidebar-sort-section .sort-dropdown-list li[role="option"]:nth-child(7),
  .mobile-nav-sheet.open .mobile-nav-list > *:nth-child(6) {
    animation-delay: 0.37s;
  }
  .filter-sidebar.open .filter-sidebar-group:nth-child(9),
  .filter-sidebar.open .filter-sidebar-category-list .filter-sidebar-category-option:nth-child(7),
  .filter-sidebar.open .filter-sidebar-sort-section .sort-dropdown-list li[role="option"]:nth-child(8),
  .mobile-nav-sheet.open .mobile-nav-list > *:nth-child(7) {
    animation-delay: 0.4s;
  }
  .filter-sidebar.open .filter-sidebar-group:nth-child(10),
  .filter-sidebar.open .filter-sidebar-category-list .filter-sidebar-category-option:nth-child(8),
  .filter-sidebar.open .filter-sidebar-sort-section .sort-dropdown-list li[role="option"]:nth-child(9),
  .mobile-nav-sheet.open .mobile-nav-list > *:nth-child(8) {
    animation-delay: 0.43s;
  }
  .filter-sidebar.open .filter-sidebar-group:nth-child(11),
  .filter-sidebar.open .filter-sidebar-category-list .filter-sidebar-category-option:nth-child(9),
  .filter-sidebar.open .filter-sidebar-sort-section .sort-dropdown-list li[role="option"]:nth-child(10),
  .mobile-nav-sheet.open .mobile-nav-list > *:nth-child(9) {
    animation-delay: 0.46s;
  }
  .filter-sidebar.open .filter-sidebar-group:nth-child(12),
  .filter-sidebar.open .filter-sidebar-category-list .filter-sidebar-category-option:nth-child(10),
  .filter-sidebar.open .filter-sidebar-sort-section .sort-dropdown-list li[role="option"]:nth-child(11),
  .mobile-nav-sheet.open .mobile-nav-list > *:nth-child(10) {
    animation-delay: 0.49s;
  }
}

/* ---- Header + nav menu on a narrow DESKTOP window ----
   Declared after every mobile header/nav block above so it wins on source
   order at equal specificity. Pointer-gated, so phones and tablets keep
   the sticky hide-on-scroll header and the slide-up sheet exactly as they
   are; only a mouse-driven window gets this. */
/* 1024px, matching the collapse block further up, not the 860px the phone
   layout switches at. The hamburger now appears at 1024 -- and everything in
   here is what turns it into a DESKTOP control: an anchored dropdown under
   the button, a header that scrolls with the page, and hover underlines on
   the rows. Left at 860 it would only start applying a whole 164px after the
   button did, and in that band a desktop window got the phone's full-width
   bottom sheet with its drag handle. */
@media (max-width: 1024px) and (hover: hover) and (pointer: fine) {
  /* Header logo keeps its full-size dimensions. The two min-width:860.02px
     blocks near the top of the file scale it up for desktop; below that
     width it falls back to the smaller mobile values, which a desktop
     window should not be getting. Scoped to the header so the footer logo
     keeps whatever sizing its own rules give it. */
  .header-inner .logo-skull { height: 58px; }
  .header-inner .logo-wordmark-stack { font-size: 24px; }

  /* Header behaves exactly as it does at full width: it scrolls away with
     the page instead of sticking and hiding/revealing on scroll direction. */
  .site-header {
    position: relative;
    transition: none;
  }
  .page-home .site-header {
    position: absolute;
    top: var(--promo-h);
    left: 0;
    right: 0;
  }
  /* The hamburger stays (it is the right control at this width), but it
     opens an anchored dropdown under itself -- same idea as the Store
     dropdown in the full-size nav -- rather than a full-height sheet
     sliding up from the bottom of the screen. */
  /* Everything below the header goes dark and stops answering: the panel is a
     column down the right of a page you are meant to be finished with for the
     moment, not a menu floating over a live one. The header is deliberately
     NOT under it -- the backdrop starts at the header's bottom edge -- so the
     search, cart and account buttons stay lit and clickable up there, and
     pressing one both does its job and puts the panel away.

     --sheet-top is the header's bottom edge in viewport terms -- the same one
     every sliding sheet on a phone opens up to. Not a constant, since at this
     width the header scrolls with the page; initSheetTop keeps it current. */
  .mobile-nav-backdrop {
    top: var(--sheet-top);
    background: rgba(0, 0, 0, 0.5);
  }
  /* A full-height column against the right edge, with one line down its left
     side and nothing anywhere else -- it is not a floating card, it is the
     right-hand side of the window for as long as it is open. */
  .mobile-nav-sheet {
    position: fixed;
    top: var(--sheet-top);
    right: 0;
    left: auto;
    bottom: 0;
    width: 260px;
    max-width: calc(100vw - 32px);
    border-radius: 0;
  }
  .mobile-nav-sheet-inner {
    height: 100%;
    max-height: none;
    overflow-y: auto;
    border: none;
    border-left: 1.3px solid var(--on-dark-border);
    border-radius: 0;
    box-shadow: var(--shadow);
    /* Grows down out of the header, matching .nav-dropdown-menu. */
    transform-origin: top;
    transform: scaleY(0);
    opacity: 0;
    visibility: hidden;
    transition: transform 0.24s cubic-bezier(0.4, 0, 0.2, 1),
                opacity 0.18s ease,
                visibility 0.24s;
  }
  .mobile-nav-sheet.open .mobile-nav-sheet-inner {
    transform: scaleY(1);
    opacity: 1;
    visibility: visible;
  }
  /* The drag handle is a bottom-sheet affordance and does not belong on a
     dropdown. */
  .mobile-nav-handle { display: none; }
  /* The rows arrive one line at a time, on the Store menu's own keyframe and
     its own 50ms step -- see dropdown-item-appear further down the file. Both
     blocks need hover:hover and pointer:fine, so wherever this one applies
     that one is defined.

     Not the bottom sheet's nav-row-fade-in, which fades AND drops each row:
     a row moving down inside a panel that is itself unrolling downward is two
     movements arguing over the same 240ms. Opacity only here, so what you see
     is a line appearing, then the next. */
  .mobile-nav-sheet .mobile-nav-list > *,
  .mobile-nav-sheet .mobile-nav-foot {
    opacity: 0;
    animation: none;
  }
  .mobile-nav-sheet.open .mobile-nav-list > *,
  .mobile-nav-sheet.open .mobile-nav-foot {
    animation: dropdown-item-appear 0.01s linear forwards;
  }
  /* 0.16s is the panel's own 0.24s slide less a hair, so the first row lands
     as the panel settles rather than racing it down. */
  .mobile-nav-sheet.open .mobile-nav-list > *:nth-child(1) { animation-delay: 0.16s; }
  .mobile-nav-sheet.open .mobile-nav-list > *:nth-child(2) { animation-delay: 0.21s; }
  .mobile-nav-sheet.open .mobile-nav-list > *:nth-child(3) { animation-delay: 0.26s; }
  .mobile-nav-sheet.open .mobile-nav-list > *:nth-child(4) { animation-delay: 0.31s; }
  .mobile-nav-sheet.open .mobile-nav-list > *:nth-child(5) { animation-delay: 0.36s; }
  /* Log in and the social row are the last line of the panel, so they follow
     the list rather than arriving with it. */
  .mobile-nav-sheet.open .mobile-nav-foot { animation-delay: 0.41s; }
  /* Tighter rows to suit a 250px panel rather than a full-screen sheet. */
  /* Fills the column, which puts Log in and the social row at the foot
     of the panel rather than trailing the last link with empty space
     under them. */
  .mobile-nav-list { flex: 1; padding: 10px 18px; }
  .mobile-nav-link { padding: 12px 0; font-size: 0.95rem; }
  .mobile-nav-sublink { padding: 10px 0 10px 14px; font-size: 0.86rem; }
  .mobile-nav-foot { padding: 14px 18px 18px; gap: 14px; }
  .mobile-nav-login { font-size: 0.88rem; }

  /* Same hover underline as the full-size nav, with no static divider
     lines between rows. Every row -- including Log in -- is full width and
     anchors its underline to left:0/right:0, so the lines all start and
     end at the same place rather than hugging each label. The list and the
     footer share the same 18px side padding, which is what keeps the Log in
     line aligned with the ones above it. */
  .mobile-nav-link,
  .mobile-nav-sublink,
  .mobile-nav-login {
    position: relative;
    width: 100%;
    box-shadow: none;
    border-bottom: none;
    /* White at rest, like the phone's sheet -- not the full-size nav's gray.
       The underline below is the whole of the hover state here: these rows are
       the only thing on a panel of their own, rather than four labels sharing
       a bar with a logo and three icons, so there is nothing for a dimmed
       state to separate them from. */
    transition: color 0.2s ease;
  }
  /* The chevron and the Log in glyph go with their row rather than holding a
     colour of their own. */
  .mobile-nav-plus svg,
  .mobile-nav-login svg { color: inherit; }
  /* The down/up flip used to be set up here, for narrow desktop windows only,
     while the phone sheet kept the old right-to-down turn. Both do the same
     thing now, so it lives on the base rule and this block no longer needs a
     copy of it. */
  /* Padding gives the underline room to sit clear of the text. */
  .mobile-nav-login { padding: 12px 0; }
  .mobile-nav-link::after,
  .mobile-nav-sublink::after,
  .mobile-nav-login::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 6px;
    height: 1px;
    background: var(--offwhite);
    opacity: 0;
    transform: translateY(-10px);
    transition: transform 0.3s ease, opacity 0.3s ease;
  }
  .mobile-nav-link:hover::after,
  .mobile-nav-sublink:hover::after,
  .mobile-nav-login:hover::after { opacity: 1; transform: translateY(0); }
}
/* The header is shorter below 640px, so the dropdown hangs from lower. */
@media (max-width: 640px) and (hover: hover) and (pointer: fine) {
  .mobile-nav-sheet-inner { max-height: calc(100vh - 100px); }
}

/* ---- Home page (mobile): swipeable sliders ---- */
@media (max-width: 860px) {
  [data-featured-grid] {
    display: flex;
    grid-template-columns: none;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    gap: 14px;
    padding-bottom: 4px;
  }
  [data-featured-grid]::-webkit-scrollbar { display: none; }
  /* Two cards and a slice of the third. 40% of the track, twice, plus the two
     14px gaps, leaves roughly 45px of the next card showing on a 390px phone
     -- enough that the row plainly continues rather than ending at the edge.
     It was 72%, which showed one card and a sliver and read as a single item
     you had to work through one at a time.

     A percentage of the track rather than a fixed width, so the arithmetic
     holds on every phone: a wider screen gets wider cards and the same
     two-and-a-bit. */
  [data-featured-grid] .product-card {
    flex: 0 0 auto;
    width: 40%;
    scroll-snap-align: start;
  }
  .video-row {
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding-bottom: 4px;
  }
  .video-row::-webkit-scrollbar { display: none; }
  .video-row .video-card {
    flex: 0 0 auto;
    width: 80%;
    scroll-snap-align: start;
  }
}

/* ---- Featured Collection on DESKTOP: a 4-up row that shrinks before it scrolls ----
   All four cards stay whole whenever they fit. --featured-card-w is the design
   size -- the exact width a card has at the container's full 1500px, i.e.
   (container - 2x64px padding - 3x18px gap) / 4 -- and cards never exceed it,
   so a maximised window looks exactly as it did. Narrower than that the four
   shrink together to fill the row rather than the last one being clipped by
   the scroller, down to a floor of --featured-card-min. Only once even that
   floor overflows (a window around 1190px) does the row start scrolling, so
   the slide shows up only on a window genuinely too small for the row.

   Before this, the cards were a fixed flex: 0 0 --featured-card-w, so the row
   overflowed and clipped below roughly a 1490px window -- most desktops.

   Declared after the mobile slider block above and pointer-gated, so phones
   keep their own 72%-wide swipe slider. */
@media (hover: hover) and (pointer: fine) {
  [data-featured-grid] {
    --featured-card-w: calc((var(--container) - 2 * 64px - 3 * 18px) / 4);
    --featured-card-min: 260px;
    display: flex;
    grid-template-columns: none;
    gap: 18px;
    overflow-x: auto;
    scrollbar-width: none;
    padding-bottom: 4px;
  }
  [data-featured-grid]::-webkit-scrollbar { display: none; }
  [data-featured-grid] .product-card {
    /* Basis is the design size with grow off, so a card never exceeds it.
       Shrink is on, paired with an explicit min-width -- flex items default to
       min-width:auto, which resolves to the content size and would refuse to
       shrink at all. */
    flex: 0 1 var(--featured-card-w);
    min-width: var(--featured-card-min);
  }
}

/* ---- Footer (mobile): centered utility row, accordion link columns ----
   Company + Legal collapse into one "Support" column on mobile only;
   desktop keeps them as separate columns.
   Pointer-gated as well as width-gated: a narrow desktop window keeps the
   real footer -- separate link columns, all links visible, left-aligned
   brand -- instead of collapsing into tap-to-open accordions. Phones and
   tablets always match hover:none, so they are unaffected. ---- */
.footer-collapse-toggle { display: none; }
.footer-col-mobile-only { display: none; }
@media (max-width: 860px) and (hover: none) {
  .site-footer { padding-top: 0; }
  .section-alt:has(.subscribe-col) { padding-bottom: 28px; }
  .footer-brand {
    display: flex; flex-direction: column; align-items: center; text-align: center;
    gap: 16px; padding: 24px 0 16px;
  }
  /* Smaller here than the 152px above -- a phone's footer wanting less of
     everything rather than a rule about the mark, and a disc that wide would
     be a third of a 360px screen. The two .logo-wordmark lines that stood
     beside this one are gone with the spans they sized. */
  .footer-brand .logo-badge { height: 116px; }
  .footer-brand .footer-social { margin-top: 0; justify-content: center; }
  .footer-social { justify-content: center; }
  .footer-col-desktop-only { display: none; }
  .footer-col-mobile-only { display: block; }
  .footer-utility {
    display: flex; flex-direction: column; align-items: center;
    gap: 16px; margin-top: 20px;
  }
  .footer-utility .region-dropdown { order: 2; }
  .payment-icons { order: 3; justify-self: auto; }
  .footer-bottom {
    order: 4; white-space: normal;
    position: static; left: auto; top: auto; transform: none;
  }
  .footer-col-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: 16px 0; border-top: 1.3px solid var(--on-dark-border);
    cursor: pointer;
  }
  .footer-col h4 { margin-bottom: 0; }
  .footer-collapse-toggle {
    display: flex; align-items: center; justify-content: center;
    width: 28px; height: 28px; flex-shrink: 0;
    background: none; border: none;
    cursor: pointer;
  }
  .footer-collapse-toggle svg { width: 18px; height: 18px; color: var(--offwhite); transition: transform 0.2s ease; }
  .footer-collapse-toggle[aria-expanded="true"] svg { transform: rotate(90deg); }
  .footer-col-links {
    max-height: 0; overflow: hidden;
    display: flex; flex-direction: column;
    transition: max-height 0.3s ease;
  }
  .footer-col-links.open { max-height: 300px; }
  .footer-col-links a { width: 100%; }
}

/* ---- Footer layout on a narrow DESKTOP window ----
   The width-only rules above drop the footer grid to two columns and then
   one, which breaks Store / Company / Legal apart across rows. Here they
   stay side by side as a single three-column row and the brand block moves
   above them, spanning the full width and centering itself -- so the
   columns keep their left-to-right reading order at any window size.
   The utility row below also stops overlapping itself: the copyright is
   absolutely centered by default, which collides with the region selector
   and payment badges once the row gets narrow, so here it drops onto its
   own line instead. */
@media (max-width: 1024px) and (hover: hover) and (pointer: fine) {
  .footer-grid {
    grid-template-columns: repeat(3, 1fr);
    gap: 30px;
    padding-bottom: 30px;
  }
  .footer-brand {
    grid-column: 1 / -1;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
  }
  .footer-brand .footer-social { justify-content: center; }

  .footer-utility {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 14px 24px;
    margin-top: 24px;
  }
  .region-dropdown { justify-self: auto; }
  .payment-icons { justify-self: auto; }
  .footer-bottom {
    position: static;
    left: auto;
    top: auto;
    transform: none;
    order: 3;
    flex-basis: 100%;
    text-align: center;
    white-space: normal;
  }
}

/* ---- Featured Collection + Store grid (mobile): Quick Add bar pinned
   below the image instead of tap-to-enlarge ---- */
.product-media-expand { display: none; }
/* Gated to hover: none -- see the pointer block further down the file.
   Everything in here is the card as a PHONE wants it: the Quick Add bar
   pinned under the photograph because a touch screen has no hover to
   reveal it with, and the spacing that bar needs around it -- a 52px row
   gap, a tightened price, more padding over the title. On a mouse none of
   that applies: the bar goes back to sliding up over the photo and the
   card goes back to its full-width spacing. */
@media (max-width: 860px) and (hover: none) {
  /* Grid items default to stretching to the tallest card in the row —
     if one product name wraps to 2 lines, every other card in that row
     gets stretched taller too, leaving dead space below its price that
     looks like an oversized gap before the next row. Sizing each card to
     only its own content keeps the row-to-row gap equal to the real
     grid gap regardless of how any one title wraps. */
  [data-featured-grid],
  [data-store-grid],
  [data-related-grid] {
    align-items: start;
  }
  /* Bigger vertical gap between rows than the gap between cards side by
     side (column-gap stays whatever .product-grid sets). Doubled from the
     24px it used to be, so the star row stays clear of the next row's
     photo. .product-info already has 4px of its own bottom padding
     below the price — 52 + 4 = a true visible 56px gap. */
  [data-store-grid],
  [data-related-grid] { row-gap: 52px; }
  /* The h3's default bottom margin and the price's -2px nudge are zeroed
     out so the flex gap is the only thing setting the spacing. The price
     also gets line-height:1 — it was inheriting the body's 1.8 line-height,
     which padded it with extra invisible leading the h3 doesn't have,
     making the gap above it look bigger than the gap above the h3 even
     though the box-to-box distance was identical. */
  [data-featured-grid] .product-info h3,
  [data-store-grid] .product-info h3,
  [data-related-grid] .product-info h3 { margin-bottom: 0; }
  [data-featured-grid] .product-price,
  [data-store-grid] .product-price,
  [data-related-grid] .product-price { margin-top: 0; line-height: 1; }
  /* "More from this category" only: price matches the plain description
     paragraph's font (regular body font, not the bold display font) and
     size instead of the store/featured grids' bold price treatment. */
  [data-related-grid] .product-price .price {
    font-family: var(--font-body);
    font-size: 1.0625rem;
    font-weight: 400;
  }
  /* Featured Collection is a swipeable carousel (see "Home page (mobile):
     swipeable sliders" below) with its own constant `gap: 14px` between
     cards at every mobile width — the padding above the title matches that
     exactly, instead of the store grid's responsive value. */
  [data-featured-grid] .product-info { padding-top: 14px; gap: 14px; }
  /* Store and related grids are real CSS grids using .product-grid's own
     gap, which is 18px down to 640px. */
  [data-store-grid] .product-info,
  [data-related-grid] .product-info { padding-top: 18px; gap: 18px; }
}
/* Below 640px .product-grid's own gap shrinks to 14px — match it here so
   the store/related grids' spacing stays equal to their card-to-card gap. */
/* hover: none, same as the 860px block above and for the same reason: this
   is the phone's card spacing, sized around a Quick Add bar pinned under
   the photograph. On a mouse the bar rides over the photo instead and the
   card keeps its full-width spacing, so none of this should reach it. */
@media (max-width: 640px) and (hover: none) {
  [data-store-grid] .product-info,
  [data-related-grid] .product-info { padding-top: 14px; gap: 14px; }
  [data-store-grid],
  [data-related-grid] { row-gap: 52px; }
}

/* The price belongs to the title above it, so it sits tight under it rather
   than floating halfway to the next card.

   .product-info holds exactly two things -- the h3 and the price -- so its
   gap IS the distance between them, and the two blocks above set that to the
   card-to-card gap (18px, 14px under 640px). That value is right for the
   space above the title, which is padding-top on the same rule and is left
   alone; it is far too much between a name and its own price. 6px is what
   the desktop card uses.

   After both blocks and matching the wider one, so it beats the 18px and the
   14px alike -- same specificity, later wins -- while their padding-top,
   which this does not touch, still applies. */
@media (max-width: 860px) {
  [data-featured-grid] .product-info,
  [data-store-grid] .product-info,
  [data-related-grid] .product-info { gap: 6px; }
}

/* ---- Quick view (mobile): tap-to-fill-screen image viewer ---- */
.qv-fullscreen {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 600;
  background: var(--surface);
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
}
.qv-fullscreen.open { display: flex; }
/* One tall scroller filling the overlay, rather than a square window you
   swipe sideways: every photo is stacked down the page and you scroll
   through them. The square element keeps its name and its markup slot; it
   is just the scroll container now. */
.qv-fullscreen-square {
  position: absolute;
  inset: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.qv-fullscreen-track {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* No gap and no padding: the photos butt together as one continuous run. */
  gap: 0;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.qv-fullscreen-track::-webkit-scrollbar { display: none; }
.qv-fullscreen-slide {
  /* flex: none plus an explicit ratio -- in a column the old flex-basis of
     100% would have sized against height, not width. */
  flex: none;
  width: 100%;
  aspect-ratio: 1 / 1;
  /* Clips the entrance: the photo starts at scale(1.28), and with the slides
     now butted together with no gap it would otherwise spill over its
     neighbours on the way in. */
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background:
    radial-gradient(circle at 30% 20%, rgba(169,136,90,0.06), transparent 70%),
    #5e5e5e;
}
.qv-fullscreen-slide svg { width: 55%; height: 55%; color: var(--text-faint); }
/* Fixed, not absolute: it stays in the top-right corner for the whole
   scroll instead of riding away with the first photo. */
.qv-fullscreen-close {
  position: fixed;
  top: 16px;
  right: 16px;
  z-index: 2;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  border: none;
  background: rgba(26, 26, 26, 0.55);
  color: #d6d6d6;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.qv-fullscreen-close svg { width: 20px; height: 20px; color: #d6d6d6; }
/* Round bouncing arrow in the bottom-right of the stacked viewer, saying
   there is more below. Styled off the close button so the two corners
   match, fixed to the viewport for the same reason, and pointer-events:
   none because it is an indicator, not a control -- the X is the only
   thing in here you can press. Reuses the quick view's bounce keyframes.
   JS adds .has-scrolled on the first scroll and never takes it off, so it
   fades out once and stays gone for that viewing. */
.qv-fullscreen-cue {
  position: fixed;
  /* Centred with auto margins between a pinned left and right edge rather
     than translateX(-50%): the bounce animation owns `transform`, and a
     centring transform here would be overwritten by every keyframe. */
  left: 0;
  right: 0;
  margin: 0 auto;
  bottom: 20px;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background: rgba(26, 26, 26, 0.55);
  color: #d6d6d6;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  z-index: 2;
  opacity: 0;
  transition: opacity 0.25s ease;
  animation: qv-scroll-cue-bounce 1.6s ease-in-out infinite;
}
.qv-fullscreen-cue svg { width: 20px; height: 20px; }
/* The twin at the top, for when the stack opens partway down and there are
   photos above as well. Same button, flipped to the other edge and bouncing
   the way it points. */
.qv-fullscreen-cue-up {
  top: 20px;
  bottom: auto;
  animation-name: qv-scroll-cue-bounce-up;
}
@keyframes qv-scroll-cue-bounce-up {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-4px); }
}
/* Each arrow is shown only if there is actually something that way, so a
   stack opened at the very bottom gets the up arrow alone. */
.qv-fullscreen.can-scroll-down .qv-fullscreen-cue:not(.qv-fullscreen-cue-up) { opacity: 1; }
.qv-fullscreen.can-scroll-up .qv-fullscreen-cue-up { opacity: 1; }
/* Both go at the first scroll and stay gone. These have to carry the
   can-scroll-* class as well: the two rules above are three classes deep, so
   a plain `.qv-fullscreen.has-scrolled .qv-fullscreen-cue` loses to them on
   specificity and the arrows would simply never hide. */
.qv-fullscreen.can-scroll-down.has-scrolled .qv-fullscreen-cue:not(.qv-fullscreen-cue-up),
.qv-fullscreen.can-scroll-up.has-scrolled .qv-fullscreen-cue-up { opacity: 0; }
/* Hidden: these tracked which slide of a horizontal filmstrip you were on,
   which says nothing once the photos are a vertical scroll. The inline
   .media-dots under the main image carry that job now. */
.qv-fullscreen-dots { display: none; }
.qv-fullscreen-dot {
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: rgba(251, 247, 243, 0.4);
  transition: width 0.25s ease, background 0.25s ease;
}
.qv-fullscreen-dot.is-active { width: 18px; background: rgba(251, 247, 243, 0.95); }
@media (min-width: 860.02px) {
  .qv-fullscreen { display: none !important; }
}

/* ---- Topo texture: Free Membership (home) and Get In Touch (About) ----
   A flat --topo-ground on the section, and the texture over it on a ::before
   at --topo-strength opacity. Two numbers, and they do not fight each other:
   the ground sets how dark the strip is, the opacity sets how much of the
   contour lines you see on it. Turn either without touching the other.

   Two tones meet here and have to stay legible against each other:

     --topo-ground   #0c0c0c  this strip's ground
     --footer-ground #101010  the footer directly below

   This list used to carry a third line -- --forest, "the card sitting on it"
   -- and that has not been true for a while: .membership-card has no fill,
   no frame and no radius, so the copy sits straight on the band and there is
   nothing stacked on this ground to clash with it. Nothing else inside the
   three strips carries a background either. Worth knowing, because it means
   this value is free to follow the artwork wherever it goes.

   And it does go. Across a run of candidate topos this ground has been as
   light as #151515 -- above the footer rather than below it, inverting the
   order this list is written in -- before coming back down to where it is
   now, a few levels under the footer again, which is where it sat for a long
   time at #0a0a0a. Not drift: the topo runs at full strength, so this ground
   has to match whatever file is loaded or the strip changes colour as the
   image arrives. The seam reads at any of those values, because the footer's
   own border-top was always what drew that line rather than the few levels
   between the two.

   This used to be built out of stacked background layers: the texture
   blended with `lighten` against the section's background-colour, under a
   scrim, with background-attachment: fixed pinning the texture to the
   viewport so the page slid over a stationary topo. That looked right but
   painted wrong, and both bugs traced to the same place. A fixed layer and a
   blend mode are a bad pair: the blend was skipped on the first paint, so
   the raw -- much lighter -- texture showed until something forced a repaint
   and it snapped to the blended version. The footer got caught in the same
   repaint region and came into view carrying this strip's ground before
   correcting to its own. Dropping the fixed attachment was not enough; the
   blend mode alone still misfired.

   So: no blend mode and no fixed attachment. Plain opacity over a plain
   background composites in one pass with nothing to skip. The mottling that
   `lighten` was there to suppress -- render noise sitting just above black in
   the texture's flat areas -- used to be a non-issue because the opacity of a
   quarter scaled a few levels of noise down to less than one. That safety net
   is gone now the texture runs at full strength: whatever noise is in the
   file is what lands on the page, which is one more reason the artwork itself
   has to be clean rather than corrected here.

   The parallax is the thing genuinely lost here. Getting it back means a
   position: fixed pseudo-element inside an overflow: hidden container rather
   than background-attachment, which composites cleanly but is more moving
   parts than this section has needed so far. */
/* Three sections carry this: the About page's Get In Touch strip, the Free
   Membership band on the home and store pages, and the membership section at
   the foot of a product page -- which has no class of its own and is given
   .topo-strip in the markup for the purpose. */
.subscribe,
.membership-strip,
.topo-strip {
  position: relative;
  background: var(--topo-ground);
  /* The texture is a viewport-sized layer clipped to the band. Nothing should
     be able to paint outside it. */
  overflow: hidden;
}
.subscribe::before,
.membership-strip::before,
.topo-strip::before {
  content: '';
  position: absolute;
  inset: 0;
  background: url('../assets/images/topo-texture-v7.jpg') center top / cover no-repeat;
  opacity: var(--topo-strength);
  pointer-events: none;
}
/* The parallax: the texture is anchored to the viewport rather than to the
   section, so the page slides over a stationary topo. This is the same
   background-attachment: fixed that used to be here and had to come out --
   but what broke it then was pairing it with background-blend-mode, and the
   blend is gone: this is a plain image at a plain opacity now, which is a
   far better-tested combination. If any flicker ever comes back, this is the
   first thing to pull.

   Desktop only, as before: mobile Safari handles fixed backgrounds badly. */
@media (hover: hover) and (pointer: fine) {
  .subscribe::before,
  .membership-strip::before,
  .topo-strip::before {
    background-attachment: fixed;
    /* Sized against the viewport now, not the section, so `cover` has to be
       restated against that box. */
    background-size: cover;
    background-position: center;
  }
}
/* The content has to be told to sit above the texture: ::before is painted
   over the background but under nothing else by default, and in-flow
   children with no position of their own would otherwise land beneath it. */
.subscribe > .container,
.membership-strip > .container,
.topo-strip > .container {
  position: relative;
  z-index: 1;
}

/* The same treatment on the About page's Get In Touch strip: topo texture
   under the same scrim, with the texture layer pinned to the viewport so
   the section slides over it. */

/* ---- "+N" overlay on the last thumb (product page, desktop) ----
   A product with more photos than the column shows puts the remainder
   behind its last thumb: the photo is darkened and the count of the other
   photos sits on top, big. Clicking it opens the stacked popup. The thumb
   column itself keeps its original four-square spacing -- the count rides
   on a thumb rather than adding a row of its own. */
.product-thumb { position: relative; }
.product-thumb-count {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Darkens the photo it sits on so the number stays readable. */
  background: rgba(19, 19, 19, 0.62);
  color: var(--offwhite);
  /* Body face, not the display face the headings use. */
  font-family: var(--font-body);
  font-size: 2.5rem;
  letter-spacing: 0.02em;
  line-height: 1;
  transition: background 0.2s ease;
}
.product-thumb.has-count:hover .product-thumb-count { background: rgba(19, 19, 19, 0.5); }
/* The "+N" tile's 2x2 mosaic. The 1px gaps sit on a near-black ground so the
   seams read as thin dark lines rather than strips of the thumb's gray
   placeholder background showing through. */
.product-thumb-mosaic {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  gap: 1px;
  background: #131313;
}
.product-thumb-mosaic .product-media-img { width: 100%; height: 100%; }

/* ---- Stacked photo popup (product page, desktop) ----
   Replaces click-to-zoom on the main image: every photo in one vertical
   scroller. Only the inner .pd-stack-scroll scrolls, so the scroll cue -- a
   sibling of it, not a child -- stays fixed in the corner the whole way down.

   There is no close button. The empty columns beside the photos do that job
   instead (.pd-stack-dismiss): the pointer becomes the ringed X over them and
   a click dismisses, the same gesture as clicking off the quick view popup.
   Escape closes it too, and the left column is a real <button>, so the
   gallery is still reachable without a pointer.

   .quick-view-scroll-cue is reused as-is for the cue so it matches the quick
   view, bounce animation included; the rules here only re-position it and
   drive its visibility, since its own opacity rule keys off .quick-view-info,
   which isn't here. */
.pd-stack {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 600;
  /* Black, but let down so the product page stays faintly readable behind the
     viewer rather than being replaced by it. True black rather than the
     header's #131313: this sits directly beside product shots taken on a
     near-black backdrop, and next to those #131313 reads as a gray panel.
     The slides stay transparent, so this one colour fills every gap -- the
     spaces between the photos and everything out to the edge. Shared with the
     quick view's backdrop as --overlay-dim. */
  background: var(--overlay-dim);
  /* One photo width, read by both the slides and the dismiss columns beside
     them -- those are sized as the leftover, so a photo that grows here
     narrows them by exactly as much and no gap or overlap can open up.

     On any screen wider than it is tall the vh term is the one that binds,
     because the slide is square: at 16:9 112vh works out to about 63vw, so
     the vw term rarely gets a say and raising it alone changes little. Past
     100vh deliberately -- a photo is now taller than the window and runs off
     the top and bottom as you scroll through, which is the point: the stack
     is for looking closely, and the scroll is what moves between shots. */
  --pd-stack-photo: min(88vw, 112vh);
}
.pd-stack.open { display: block; }
/* One column, edge to edge. This used to be two -- a rail of thumbnails down
   the left beside the full-size run -- which is why the scroller is still
   absolutely positioned rather than simply filling the parent. */
.pd-stack-scroll {
  position: absolute;
  inset: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.pd-stack-track {
  display: flex;
  flex-direction: column;
  /* Centred in the scroller, which is now the full width of the screen. */
  align-items: center;
  /* Separate photos rather than one continuous run, so the darkened page
     shows between them. The same value pads the ends, so the first and last
     photo are inset from the top and bottom the way they are from each
     other instead of starting flush against the edge. */
  gap: clamp(10px, 1.8vh, 22px);
  padding: clamp(10px, 1.8vh, 22px) 0;
}
.pd-stack-slide {
  /* Sized off BOTH axes because the slide is square: vw alone would overflow
     a short window, vh alone would leave a narrow one nearly empty. Whichever
     is tighter wins, so a photo always sits comfortably inside the viewport
     with the gaps above and below it visible. */
  width: var(--pd-stack-photo);
  aspect-ratio: 1 / 1;
  border-radius: 0;
  /* No frame. The gray edge was here because these sit on a let-down black
     ground with near-black photography, so a dark photo could end without
     anything saying where -- a real problem while every other photo on the
     site carried the same edge and the pages behind them were near-black too.

     Both halves of that have gone. The pages these open from are white now,
     and their grids and galleries dropped every border in the light pass, so
     this was the last framed photograph on the site. A photo viewer's job is
     to show the photograph, not to draw a box round it. */
  border: none;
  overflow: hidden;
  background: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
}
.pd-stack-slide img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pd-stack-slide svg { width: 38%; height: 38%; color: var(--text-faint); }
/* Fixed rather than absolute: pinned to the viewport, never scrolled away. */
/* The empty column either side of the photos, and the way out of the gallery:
   the pointer becomes the ringed X over it and a click closes. That is the
   whole close control -- there is no button in the corner.

   Fixed, so a column stays put over the full height of the viewport however
   far the stack has been scrolled. Being fixed does not detach it from the
   scroller in the DOM, which is the part that matters: a wheel over a column
   still bubbles to .pd-stack-scroll and moves the photos.

   Width is the leftover beside one photo, halved. 100vw rather than 100%
   because a fixed box resolves percentages against the viewport anyway, and
   writing it as vw says so. If the scroller shows a scrollbar the right-hand
   column starts a few pixels clear of the photo rather than overlapping it,
   which is the harmless direction for that rounding to go. */
.pd-stack-dismiss {
  position: fixed;
  top: 0;
  bottom: 0;
  width: calc((100vw - var(--pd-stack-photo)) / 2);
  padding: 0;
  border: none;
  background: transparent;
  cursor: var(--cursor-close), pointer;
}
.pd-stack-dismiss-left { left: 0; }
.pd-stack-dismiss-right { right: 0; }
/* Matched to the mobile viewer's arrow: same 46px disc, same colours, and
   centred along the bottom rather than tucked into the corner. Centred with
   auto margins between a pinned left and right edge, not translateX(-50%),
   because the inherited bounce animation owns `transform`. */
.pd-stack-cue {
  position: fixed;
  left: 0;
  right: 0;
  margin: 0 auto;
  bottom: 20px;
  width: 46px;
  height: 46px;
  background: var(--glass-fill);
  color: #d6d6d6;
}
.pd-stack-cue svg { width: 20px; height: 20px; }
/* Shown only if there is somewhere to scroll to, and gone for good at the
   first scroll -- same as mobile. The hide rule carries .can-scroll too, or
   it would lose to the three-class show rule above and never apply. */
.pd-stack.can-scroll .pd-stack-cue { opacity: 1; }
.pd-stack.can-scroll.has-scrolled .pd-stack-cue { opacity: 0; }

/* ---- Logo: no entrance animation ----
   Everything else on the site fades and settles in from a 1.12 scale on first
   view -- the global img rule and .logo-wordmark-stack's own transition. The
   logo opts out of both and paints at full size and opacity immediately, in
   the header and the footer alike. The wordmark keeps its -1px optical nudge;
   only the scale and the fade go. Declared last so it outranks
   .logo-wordmark-stack.is-visible, which has the same specificity. */
.logo .logo-skull {
  opacity: 1;
  transform: none;
  /* filter too, or the global img rule would black it out until the observer
     reaches it -- and with no transition it would snap, not settle. */
  filter: none;
  transition: none;
}
.logo .logo-wordmark-stack {
  opacity: 1;
  transform: translateY(-1px);
  transition: none;
}


/* ---- Resize transition guard -------------------------------------------
   #cartDrawer rests off-screen two different ways depending on the
   breakpoint: translateX(100%) past the right edge on desktop, and
   translateY(100%) below the bottom edge as a mobile sheet. Both are out of
   sight, but the element also carries a transform transition -- so dragging
   the window across 860px animates between the two, and every value in
   between (translate(50%, 50%) at the midpoint) puts a closed cart panel
   right in the middle of the screen. It slides across and back out, on every
   page, purely from resizing.

   .filter-sidebar and .sort-dropdown-list have the milder version of the
   same fault: no transform at all on desktop, translateY(100%) on mobile,
   which animates them sliding down as the breakpoint is crossed.

   JS holds .is-resizing on <html> while the window is being dragged, and
   for a moment after it stops. These are all resting positions, so cutting the
   transition only means the panel is already where it belongs the moment
   the new breakpoint applies. Selectors are written to outrank the
   originals on specificity (#cartDrawer needs the id) rather than leaning
   on !important. */
.is-resizing #cartDrawer,
.is-resizing .cart-drawer-backdrop,
.is-resizing .filter-sidebar,
.is-resizing .sort-dropdown-list,
.is-resizing .mobile-nav-sheet-inner { transition: none; }

/* ---- Inline photo dots (touch only) ------------------------------------
   Replaces the thumbnail strip under the main image on phones: a row of
   dots showing how many photos there are and which one you're on, with the
   current one stretched into a pill. Sized for a light background, unlike
   .qv-fullscreen-dot which was drawn for the dark overlay. Hidden by
   default so desktop -- including a desktop window dragged narrow -- keeps
   its thumbnails and never shows these. */
.media-dots {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  margin-top: 16px;
}
.media-dot {
  width: 8px;
  height: 8px;
  border-radius: 999px;
  /* Lighter than the ground, not darker: --surface is #202020, so the near-
     black these started as was invisible against it. Same value the
     fullscreen dots use. */
  background: rgba(251, 247, 243, 0.4);
  transition: width 0.25s ease, background 0.25s ease;
}
.media-dot.is-active { width: 24px; background: var(--text); }
/* Only the popup's. The product page's own thumb row stays on a phone now --
   with ten photos to a product, the row and its "+6" tile are the way into
   the set, and the dots alone only say how many there are. The popup is a
   look before adding to the cart, not the gallery, so it keeps its filmstrip
   and dots and nothing else. */
@media (max-width: 860px) and (hover: none) and (pointer: coarse) {
  .qv-media-thumbs { display: none; }
}

/* ---- Phones: the header is never dimmed ---------------------------------
   Every sheet on a phone rises to the foot of the header, so the darkening
   behind it starts there too. Dimming the header as well made it look
   switched off while its own buttons were still the way out of whatever had
   just opened. Only the sliding sheets are listed: the search overlay is a
   full-window surface rather than something sliding up under the header. */
@media (max-width: 860px) {
  .mobile-nav-backdrop,
  .cart-drawer-backdrop,
  .filter-sidebar-backdrop,
  .sort-dropdown-backdrop,
  .quick-view-backdrop,
  .review-modal-backdrop {
    top: var(--sheet-top);
  }
}

/* ---- Photo loading spinner ---------------------------------------------
   The product shots run 600-800K each and the stacked viewer holds five of
   them, so on a slow connection there is a real wait. The entrance animation
   fires on visibility rather than on load, which meant a photo that hadn't
   arrived yet animated from black... to black, and then popped in whenever
   the bytes landed. JS marks a slide .is-media-loading until its image has
   actually decoded, and this draws a small ring spinner over it meanwhile.

   position: relative so the ring has something to centre against --
   .qv-fullscreen-slide is a static flex item and would otherwise position it
   against the page. z-index puts it over the photo, which is sitting there at
   brightness(0) waiting for its entrance. */
.is-media-loading { position: relative; }
.is-media-loading::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 26px;
  height: 26px;
  margin: -13px 0 0 -13px;
  border-radius: 50%;
  border: 2px solid rgba(251, 247, 243, 0.16);
  border-top-color: rgba(251, 247, 243, 0.7);
  animation: media-spin 0.75s linear infinite;
  pointer-events: none;
  z-index: 1;
}
@keyframes media-spin { to { transform: rotate(360deg); } }
/* Still turning, just slowly enough not to read as motion. */
@media (prefers-reduced-motion: reduce) {
  .is-media-loading::after { animation-duration: 2.4s; }
}

/* Held on a photo for the single frame in which its entrance is rewound.
   The entrance transition is declared on the resting state, so simply
   removing .is-visible animates the photo BACKWARDS to scale(1.28) and black
   over 1.2s -- and re-adding it a frame later then plays forward from
   wherever that rewind had got to, which is roughly nowhere. Reopening the
   stacked viewer looked like it had no entrance at all. Cutting the
   transition for the reset makes it instant, so the replay starts from the
   real beginning. Declared here, after the entrance rule, so it wins on
   source order at equal specificity. */
.product-media-img.is-reveal-resetting { transition: none; }

/* ---- Product page: main-image arrows and the slide-over transition -------
   Hidden by default and shown only where the gallery is in its desktop form:
   861px and up on anything, plus any width on a pointer device -- the same
   compound query the gallery itself uses, since a desktop window dragged
   narrow keeps the desktop gallery.

   Not on a phone. Two buttons sitting on top of the photo, over the part of
   it you would swipe, to do the thing swiping already does -- and the thumb
   row underneath says how many photos there are and jumps to any of them,
   which is what the arrows were also there for. */
.pd-media-arrow { display: none; }
/* Nothing page-specific left here: both pairs of arrows now take the same
   smoked fill from .qv-media-arrow, the class they share. */
@media (min-width: 860.02px), (hover: hover) and (pointer: fine) {
  .pd-media-arrow { display: flex; }
  /* The outgoing photo keeps its display and sits one layer down, so the
     incoming one slides in ON TOP of it rather than over bare background.
     The quick view popup gets the same treatment -- its slides are stacked
     the same way, one shown at a time. */
  .product-detail-media-slide.is-prev,
  .qv-media-slide.is-prev { display: flex; z-index: 1; }
  .product-detail-media-slide.is-active,
  .qv-media-slide.is-active { z-index: 2; }
  /* No fill-mode on the popup's pair. The end value equals the resting
     transform anyway, and holding it would outrank the is-zoomed scale the
     popup still uses on desktop -- click-to-zoom would stop working after the
     first photo change. */
  .qv-media-slide.slide-from-right {
    animation: pd-slide-in-right 0.45s cubic-bezier(0.16, 1, 0.3, 1);
  }
  .qv-media-slide.slide-from-left {
    animation: pd-slide-in-left 0.45s cubic-bezier(0.16, 1, 0.3, 1);
  }
  .product-detail-media-slide.slide-from-right {
    animation: pd-slide-in-right 0.45s cubic-bezier(0.16, 1, 0.3, 1) both;
  }
  .product-detail-media-slide.slide-from-left {
    animation: pd-slide-in-left 0.45s cubic-bezier(0.16, 1, 0.3, 1) both;
  }
}
@keyframes pd-slide-in-right {
  from { transform: translateX(100%); }
  to { transform: translateX(0); }
}
@keyframes pd-slide-in-left {
  from { transform: translateX(-100%); }
  to { transform: translateX(0); }
}
@media (prefers-reduced-motion: reduce) {
  .product-detail-media-slide.slide-from-right,
  .product-detail-media-slide.slide-from-left,
  .qv-media-slide.slide-from-right,
  .qv-media-slide.slide-from-left { animation: none; }
}

/* In the quick view the dots sit ON the photo, along its bottom edge, at
   every width. They live inside .quick-view-media rather than beside it, so
   this is simply an offset from the photo's own bottom edge -- and being
   absolute keeps them out of the modal's grid, which would otherwise
   auto-place an in-flow dot row into a cell of its own and push the info
   column out. pointer-events off so the strip of photo they cover still
   takes a swipe. */
.quick-view-media .media-dots {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 18px;
  margin: 0;
  z-index: 3;
  pointer-events: none;
}

/* Product page: the dots sit on the photo, along its bottom edge, matching
   the popup. Every width -- the phone used to keep them in normal flow
   underneath, which put a band of dots between the photo and the thumb row
   now under it. Positioned against the column rather than the image itself
   because they are its sibling, not its child -- and taking them out of flow
   collapses the column back to exactly the photo's height, which also closes
   the gap they used to leave underneath it. */
.product-detail-media-col { position: relative; }
.product-detail-media-col > .media-dots {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 18px;
  margin: 0;
  z-index: 3;
}

/* ---- Animated stand-in for the custom cursors ---------------------------
   The --cursor-* rings above are CSS cursors, and a cursor cannot animate:
   the browser rasterises the SVG once and hands the window system a still
   bitmap, so keyframes, transitions and SMIL inside it are all ignored. To
   have the ring swell into place it has to be a real element that follows the
   pointer, which is what this is -- the same 48px ring and the same glyph
   geometry, drawn in the DOM. The inner svg keeps the 38-unit viewBox the
   URIs use and stretches to the box, so the two sizes are set in one place
   each and the artwork scales itself.

   .has-media-cursor is added by JS (see initMediaCursor in main.js), so the
   plain --cursor-close stays in place if the script never runs rather than
   leaving these surfaces with no pointer at all.

   Both surfaces mean the same thing -- click here to dismiss what is open --
   so the ring only ever draws an X. The photos themselves are absent: the
   product page's uses a plain hand, and the popup's is not clickable at
   all. */
.has-media-cursor .quick-view-backdrop,
.has-media-cursor .review-modal-backdrop,
.has-media-cursor .mobile-nav-backdrop,
.has-media-cursor .cart-drawer-backdrop,
.has-media-cursor .pd-stack-dismiss { cursor: none; }

/* Two elements, because one transform cannot do both jobs: the outer holds
   the pointer position, written from JS on every move with no transition so
   it tracks exactly, and the inner holds the entrance, animated by CSS alone.
   Nested, they never contend for the same property. */
.media-cursor {
  position: fixed;
  left: 0;
  top: 0;
  width: 38px;
  height: 38px;
  /* Centres the ring on the pointer -- the same 19,19 hotspot the CSS cursor
     declares. Half the box, so it moves with the size above. */
  margin: -19px 0 0 -19px;
  /* Never a hit target: it sits under the pointer at all times, so anything
     else would mean it swallows every click meant for the photo. */
  pointer-events: none;
  /* Above every layer in the file -- the quick view modal is 301, the stacked
     gallery 600, the review modal 701. This is the pointer: nothing should
     ever be able to paint over it. */
  z-index: 800;
  will-change: transform;
}
.media-cursor-art {
  display: block;
  width: 100%;
  height: 100%;
  /* Glyph colour, inherited by the paths through currentColor. */
  color: var(--on-dark);
  /* A dot that swells into the ring on the way in, and nothing at all on the
     way out. The two directions are separate rules because a transition
     belongs to the state it moves TO -- this rule is the exit, the
     .is-visible one below is the entrance.

     Exit: none at all -- the ring is simply gone on the frame the pointer
     leaves. `transition: none` here rather than a zero duration on each
     property, because this also has to snap the transform back to scale(0.2)
     for the next entrance, and with nothing on screen there is no reset to
     hide any more. */
  transform: scale(0.2);
  opacity: 0;
  transition: none;
}
/* Entrance: the same 0.8s swell, but solid in 0.25s -- the ring is fully
   there almost at once and spends the rest of the time growing into place. */
.media-cursor.is-visible .media-cursor-art {
  transform: scale(1);
  opacity: 1;
  transition: transform 0.8s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.25s ease;
}
/* Opacity rather than display, so the element is always in the tree with a
   starting style to transition FROM. Toggling display would skip the
   entrance entirely on the frame it appears. */
.media-cursor-art svg { display: block; width: 100%; height: 100%; }
/* The ring is drawn IN the SVG, not as a CSS border on the box: a border
   insets the content box, which would shrink the 38-unit viewBox inside it
   and render every glyph a few percent under the size the data: URIs give
   them. Same circle as those URIs, r and stroke-width included -- but reading
   the palette through CSS, which an inline SVG can do and a data: URI cannot. */
/* The ring is the gray of the review picker's square borders, which are
   --on-dark-faint. Written opaque rather than as that token: this stroke
   straddles the disc's edge, so half of it lies over the smoked fill and half
   over whatever is behind the pointer -- a translucent value would come out
   two different tones down the same 1.3px ring. #878583 is what the token
   resolves to over the page, matched by hand here and in --cursor-close,
   which as a data: URI cannot read a custom property at all. */
.mc-disc { fill: var(--glass-fill); stroke: #878583; }

/* ---- Product page: the buy bar ------------------------------------------
   Drops in from the top once the real Add to Cart is well clear of the
   screen, carrying the same three controls so the product can be bought
   without scrolling back. Fixed, off-screen by a full translate rather than
   display:none, so it slides rather than appears.

   Pointer-and-desktop only. It is three controls plus a name in one row: on a
   phone that row has nowhere to go, and a version that works there is a
   different layout rather than the same one narrowed. */
.pd-buybar { display: none; }
@media (min-width: 860.02px) {
  .pd-buybar {
    display: block;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    /* Under the site header (100), which the bar slides out from behind, and
       under every overlay -- the quick view is 301, the stacked gallery 600. */
    z-index: 90;
    /* A step under --field-fill (#202020), the fill the colour select and the
       quantity pill carry: enough to sit them proud of the bar without the
       flat black gap that #000 opened up between them. Its own token -- see
       --buy-bar-ground in :root -- because the header and footer it used to
       share it with have gone their own way. */
    background: var(--buy-bar-ground);
    border-bottom: 1.3px solid var(--border-strong);
    transform: translateY(-100%);
    transition: transform 0.32s cubic-bezier(0.16, 1, 0.3, 1);
  }
  .pd-buybar.is-visible { transform: translateY(0); }
  .pd-buybar-inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 24px;
    /* Tight to the controls, and this is the bar's whole height now: they are
       46px, so it comes to about 66px -- where 12px padding round a 55px
       button made it 79px. The gradient fades across exactly this. */
    padding-top: 10px;
    padding-bottom: 10px;
  }
  /* The title block yields first: it can ellipsis, the controls cannot
     usefully shrink at all.

     Its own flex column with a gap, rather than two blocks and a margin. The
     row above centres this box, so the box has to be the same height as the
     text in it -- and it was not: the body's 1.8 line-height wraps each line
     in roughly half its own height again of leading, which the eyebrow's
     11px and the name's 1.05rem carry in different amounts. Cutting both to
     their own size and spacing them with a real gap makes the box the text,
     so centring it centres what you actually see, and the two lines sit at a
     readable distance instead of the eyebrow riding up against the name. */
  .pd-buybar-title {
    min-width: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 5px;
  }
  /* .pd-buybar .pd-buybar-eyebrow, not the one class: the element also carries
     .qv-mini-logo.logo-wordmark-text, whose two-class selector sets its own
     font-size and outranks a single class no matter where it sits in the
     file. The 11px here did nothing until this selector matched its weight --
     the eyebrow was still rendering at 14px, which is most of why it sat too
     close to the name. */
  .pd-buybar .pd-buybar-eyebrow {
    /* Tighter than the one in the product column -- this sits above a single
       line in a 66px bar, not above a whole header block. */
    font-size: 11px;
    line-height: 1;
    margin-bottom: 0;
    opacity: 0.75;
  }
  .pd-buybar-name {
    display: block;
    font-family: var(--font-display);
    font-size: 1.05rem;
    /* 1.1, not 1: both lines are uppercase and have no descenders, but the
       display face still wants a hair of room or the caps sit on the edge. */
    line-height: 1.1;
    letter-spacing: 0.0375em;
    color: var(--on-dark);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .pd-buybar-controls {
    display: flex;
    align-items: center;
    gap: 14px;
    flex-shrink: 0;
  }
  /* Reads before the colour it applies to, which is the order the sentence
     goes in: in stock, in this colour, this many. */
  .pd-buybar-stock { white-space: nowrap; }
  /* The list already opens downward (top: 100% + 6px), which is the right
     direction out of a bar pinned to the top, so nothing to override there. */
  .pd-buybar-color { width: 190px; }
  /* Solid, not the transparent default these carry. On the product column
     they sit on an opaque panel so transparent reads as the panel; here the
     bar is see-through, and a transparent control would show the page
     scrolling past inside it. The open menu takes the same fill -- it hangs
     over the page with nothing behind it to borrow from, so it has to be
     opaque too, and matching the trigger it drops out of is the point. */
  .pd-buybar .color-select-trigger,
  .pd-buybar .qty-stepper,
  .pd-buybar-color .color-select-list { background: var(--field-fill); }
  /* The same pill the product column uses -- arrows inside, left and right,
     with the count between them -- rather than the bare input the base
     .qty-stepper collapses to (display: contents, buttons hidden). Wider than
     the column's, because here it has the room. */
  .pd-buybar .qty-stepper {
    width: 148px;
    height: 46px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    border-radius: 999px;
    border: 1.3px solid var(--border-strong);
    padding: 0 6px;
    flex-shrink: 0;
  }
  .pd-buybar .qty-stepper-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    flex-shrink: 0;
    border: none;
    background: transparent;
    color: var(--text);
    cursor: pointer;
  }
  .pd-buybar .qty-stepper-btn svg { width: 18px; height: 18px; }
  .pd-buybar .qty-stepper .qty-input {
    flex: 1;
    width: auto;
    height: auto;
    min-width: 0;
    border: none;
    background: transparent;
  }
  /* 46px, the height of the colour select and the quantity pill beside it.
     .btn's own 14px padding on a 27px line box comes out at 55, which is
     right in the product column where the button stands alone but leaves it
     the odd one out in a row of controls -- and sets the whole bar's height.
     An explicit height with the vertical padding dropped, so the label stays
     centred by .btn's own align-items. */
  .pd-buybar-add {
    height: 46px;
    padding: 0 28px;
    white-space: nowrap;
  }
}

/* ---- The story card ------------------------------------------------------
   One gray frame with a photo welded flush into one side of it and the copy
   on the other. Used three times: the About page's "How it started" and the
   home page's About Us, both copy left and photo right, and the About page's
   "Behind the Name", mirrored.

   Desktop only: below 860px .split collapses to a single column, where a
   "card" with a photo welded to one side has no side to weld it to -- the
   two stack as normal there.

   The photo sets the card's height, and there are two ways of holding it to
   that. Where the copy is short enough to sit beside the photo it stays in
   flow (.about-card-copy) and the taller column wins, which is the photo.
   Where it is not -- "How it started" -- the copy is taken out of flow
   instead (.about-card-scroll) so it cannot push the card past the photo's
   height no matter how long it runs; it scrolls within it. Without that the
   card would grow to fit the text and the photo would stretch with it. */
/* 860.02px, not 861px, and the same everywhere this pair appears. The mobile
   half is max-width: 860px; a viewport of 860.5px -- ordinary on a scaled
   display or at a browser zoom that is not 100% -- matches neither 860 nor
   861, and both queries drop out together. Everything below would vanish at
   that one width: the card would lose its columns, its gap:0 and its frame,
   and the photo would spill over the copy. 860.02 is the smallest step that
   closes the gap without overlapping the query below it. */
@media (min-width: 860.02px) {
  .about-card {
    /* No gap: the photo has to reach the frame's edge, and the copy gets its
       breathing room from the scroller's own padding instead. */
    gap: 0;
    align-items: stretch;
    /* minmax(0, 1fr), not the plain 1fr .split sets. A bare 1fr is
       minmax(auto, 1fr), and that auto floor is the item's min-content width
       -- which for the photo cell is the image's own intrinsic width. Narrow
       the window and that floor stops the column shrinking, so the halves
       overflow their tracks and the photo rides over the copy beside it
       instead of the two staying in their columns. Same fix, same reason, as
       the reviews toolbar further up. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    /* --forest-deep, the site's other approved background tone. On the card,
       not the section around it: the fill is what makes this a box sitting on
       the page rather than a frame drawn on it, and the page keeps its own
       black either side. */
    background: var(--forest-deep);
    border: 1.3px solid var(--border-strong);
    /* Square, like every other framed box on the site. The photo welded
       into one side already sets its own radius to 0 -- see
       .about-card .split-media below -- so the two agree without either
       having to clip the other. */
    border-radius: 0;
    /* Kept even with no corners to clip into: the photo's entrance scales
       it past its box, and this is what holds it to the frame. */
    overflow: hidden;
  }
  .about-card-text {
    position: relative;
    min-width: 0;
  }
  /* Absolute, not just overflow:auto. A scrolling box still reports its full
     content height to the grid, so the row would size to the text and the
     card would grow after all. Out of flow it contributes nothing, the row
     takes the photo's height, and this fills whatever that turns out to be. */
  .about-card-scroll {
    position: absolute;
    inset: 0;
    overflow-y: auto;
    padding: 44px;
    /* No scrollbar. It still scrolls -- the bar itself is hidden, the way the
       gallery rails and filmstrips elsewhere in this file do it. The fade at
       the bottom edge is the affordance now, which is tidier than a bar
       appearing over the copy on hover. */
    scrollbar-width: none;
  }
  .about-card-scroll::-webkit-scrollbar { display: none; }
  .about-card-scroll > :last-child { margin-bottom: 0; }
  /* Copy that runs past the card fades out at the bottom edge, so the cut is
     a soft one and there is something to say "keep reading". Drawn on the
     CELL, not the scroller: inside the scroller it would ride up with the
     text and fade the wrong line. JS adds the class only while there is more
     to see -- see initAboutCardScroll -- so short copy gets no fade and
     reaching the end clears it. Opacity rather than a mask, because a mask
     cannot be transitioned and this should ease away, not blink off. */
  .about-card-text::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    /* The height IS where the fade starts -- it is anchored to the bottom, so
       a taller box begins dissolving the copy further up. 110px catches the
       last two lines or so rather than only the final one. */
    height: 110px;
    /* Ends on the card's own fill, so the copy dissolves into the box rather
       than into a black that is not behind it. Named the same way .about-card
       names it -- the two have to stay the same tone. */
    background: linear-gradient(180deg, transparent, var(--forest-deep));
    opacity: 0;
    transition: opacity 0.25s ease;
    /* Never eats a scroll: the scroller sits under it. */
    pointer-events: none;
  }
  .about-card-text.is-faded-bottom::after { opacity: 1; }
  /* The other way to fill a card's copy side: no scroller, the text simply
     centred against the photo. For copy short enough that it will never need
     to scroll -- it stays in flow, so anything long enough to overflow would
     stretch the card and the square photo with it. Same padding as the
     scroller, so the two cards' text starts in the same place. */
  .about-card-copy {
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: 44px;
    min-width: 0;
  }
  .about-card-copy > :last-child { margin-bottom: 0; }
  /* The outer frame is the card's now -- but the seam where the photo meets
     the copy keeps a rule of its own, so the card reads as two halves rather
     than a photo bleeding into a block of text. The columns are 1fr 1fr with
     no gap, so that edge IS the middle of the card.

     Drawn on the photo rather than on the copy because the photo is the one
     element present in both cards: the mirrored card leads with it, so the
     rule simply moves to its other side rather than needing a second element
     to hang off.

     No height here on purpose: the photo's aspect-ratio is what gives the
     row its height, and pinning height: 100% would make it depend on the row
     that depends on it. align-items: stretch above is what fills the
     column. */
  .about-card .split-media {
    /* The other half of the same guard: the track can now shrink, and this
       lets its contents shrink with it. */
    min-width: 0;
    border: none;
    border-left: 1.3px solid var(--border-strong);
    border-radius: 0;
  }
  /* Mirrored card: the photo leads, so its right edge is the one that meets
     the copy. (The DOM order is the visual order at this width -- the only
     rule that reorders them is gated to mobile, where the card is stacked
     and has no seam to draw.) */
  .about-card.split-media-first .split-media {
    border-left: none;
    border-right: 1.3px solid var(--border-strong);
  }

  /* ---- Home page: two alternating rows -------------------------------------
     Copy on one side, a wide photo on the other, and the second row is the
     mirror of the first.

     What was here before: ONE grid cell holding both copies stacked, parked
     in position: sticky, with the visible one swapped by JS at the seam
     between two tall portrait photos running down the other column. The
     photos were 4/5 for a reason -- the taller the column, the longer the
     text held -- and the whole thing was tuned around how long a reader
     spends between two seams.

     All of it is gone. Each story is its own row, both are simply on the
     page, and there is nothing to park against or swap at. Removed with it:
     position: sticky and the --story-sticky-top custom property JS wrote into
     it, the stacked grid-area: 1/1 that put both copies in one cell, the
     visibility toggle that hid the one not current, and the .is-swapping
     class the changeover ran on. initStoryStickyCopy and initStoryCopySwap
     are gone from main.js along with them.

     The flip is CSS, not markup order. Both rows put the copy first in the
     source -- see index.html -- so the reading order is heading, copy, link,
     photo in each case, whichever side the photo is on. Reordering the source
     instead would have made the second row read photo-first to a screen
     reader and put the keyboard's tab stop in the wrong place. */
  /* The gap between the two stories: twice --grid-gap. Written as the
     doubling rather than as 36px so it still moves with the grid if that
     token changes -- the relationship is the point, not the number.

     Three of the page's four seams are this measure now: here, above the
     Featured Collection bar and below the product grid. The single gap is
     kept for the one seam inside a run -- the bar to the images it heads,
     which belong together -- so the doubled value reads as the space BETWEEN
     things and the single one as the space WITHIN them. */
  .home-stories { gap: calc(var(--grid-gap) * 2); }
  /* The first and third seams. The bottom one is the whole of the space
     between the second story and the Featured Collection bar below: that
     section's own top padding is zeroed, so this rule is the only thing
     holding them apart. Twice --grid-gap: the bar heads a new section, and
     the doubled measure is what this page uses between sections.

     The top is zero. It was the gutter plus --grid-gap while these rows
     opened the page straight out of the hero; the category grid does that
     now, and has taken the value with it -- see .section.home-featured. One
     section owns each gap on this page and it is always the one above, so
     with the grid overhead there is nothing for this to add.

     Written as .section.home-about rather than the bare class so the mobile
     `.section { padding: 64px 0 }` further down the file cannot put its own
     value back: that rule comes later and would win at equal specificity. */
  .section.home-about {
    padding-top: 0;
    padding-bottom: calc(var(--grid-gap) * 2);
  }
  /* The second copy of the stories section -- Cut & Sewn. Its top padding is
     zero because About Us above already leaves the seam: one section owns
     each gap on this page, and it is always the one above.

     Redundant as it stands -- the base rule directly above now says the same
     thing, since neither copy of this section opens the page any more. Kept
     because the two are zero for different reasons: that one because the
     grid overhead owns its seam, this one because its own first copy does,
     and either could take a value back without the other wanting one.

     Written as .section.home-about.home-about-lower rather than the modifier
     alone, so it outranks the rule directly above it whatever order the two
     end up in. */
  .section.home-about.home-about-lower { padding-top: 0; }
  .home-story {
    /* The photo takes roughly twice the copy. Inside its own padding the copy
       measures about 390px on a 1372px container, which is a comfortable
       width for a three-line paragraph -- wider and the lines get long
       enough to need more leading than the type is set with. */
    /* min-height, NOT height. A fixed height here overflows: the copy column
       narrows with the window while --band-h shrinks faster, so somewhere
       around 1100px the paragraph wraps to more lines than the band is deep
       and the panel spills over the section below it. A floor holds the four
       bands level while the copy fits and lets this one grow when it does
       not, which is the only failure mode of the two that is not broken. */
    min-height: var(--band-h-story);
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 2fr);
    /* Rounded, and clipping so the two halves round with it. The radius is
       on the ROW, not on the panel and the photo separately -- they meet
       edge to edge and are meant to read as one band, so only the four outer
       corners should curve. Set on each half instead, the seam between them
       would round too and the band would come apart into two tiles.

       --band-radius, shared with the tiles and Get In Touch below so the
       three bands down this page curve alike. See :root. */
    border-radius: var(--band-radius);
    overflow: hidden;
    /* No gap, and that is the point of it. The copy sits on a panel now, and
       the panel has to meet the photo edge to edge for the two to read as one
       band across the page rather than as a grey box near a picture. The
       breathing room that used to be the gap is the panel's padding instead,
       which is the same space measured from the inside. */
    gap: 0;
    /* stretch, not centre. The panel takes its height from the row, and the
       row's height is the photo -- so the two are exactly as tall as each
       other and their shared edge runs the full height. The copy is centred
       WITHIN the panel instead, which is where that job moved to. */
    align-items: stretch;
  }
  /* The panel. A diagonal wash, light grey into a slightly darker one, on a
     section whose ground is white -- so the box reads as a surface the text
     is set on rather than as a block of colour.

     135deg runs top-left to bottom-right, so the light corner is the one
     nearest the top of the page and the darker one meets the photo's bottom
     edge. Both rows take the same angle rather than mirroring it on the
     flipped one; mirroring is the other reasonable choice and is a single
     value if you want the pair symmetrical.

     The two greys are about 19% of value apart (#f6f6f6 to #c6c6c6), which
     is double the range they started at -- far enough that the wash reads as
     a gradient rather than as a panel that might just be catching the light.
     The dark end is the one to watch if it goes further: the text on this is
     #141414, which has contrast to spare, but the panel also has to stay
     clearly lighter than the photograph beside it or the row stops reading
     as copy-next-to-picture. */
  .home-story-copy {
    display: flex;
    flex-direction: column;
    justify-content: center;
    /* Roughly the 64px the gap used to be, taken from the inside. */
    padding: 56px;
    background: var(--panel-ground);
  }
  .home-story-flip { grid-template-columns: minmax(0, 2fr) minmax(0, 1.15fr); }
  /* Both are placed explicitly rather than one of them being nudged: naming
     the row as well as the column keeps them in the same row when the source
     order and the visual order disagree, which is exactly what is happening
     here. */
  .home-story-flip > .home-story-copy { grid-column: 2; grid-row: 1; }
  .home-story-flip > .home-story-media { grid-column: 1; grid-row: 1; }

  /* ---- The entrance, removed ---------------------------------------------
     There is none on the copy any more. The heading swept in from the left
     and the paragraph and the link lifted from below just behind it, each
     firing on its own .is-visible from the reveal observer. All of it is
     gone: both @keyframes, the three animation rules, the resting opacity: 0
     they needed, and the prefers-reduced-motion block whose only job was to
     put that opacity back to 1 when the animations were switched off.

     The same call .home-touch took. Its three lines were animated here for a
     while on the argument that they are the same shape of thing -- heading,
     a line of copy, a button -- and they were removed for the same reason.
     What is left in all three bands is the photograph's own entrance, which
     is untouched: the picture arrives, and the words beside it are already
     there.

     The three selectors also came out of the observer's target list in
     main.js. Nothing reads .is-visible on them now, so leaving them would
     have been harmless -- but a watch list naming elements that no longer
     react to being watched is exactly the kind of thing that outlives the
     reason for it. */
  .home-story-copy .hero-actions { justify-content: flex-start; }
  /* ---- Desktop: the story links are text links, not buttons --------------
     The same treatment the quick view popup's View Full Details carries: the
     label in the body face at the button's size, an arrow after it, and the
     underline that drops into place on hover (that last one is shared with
     that link, further up the file).

     The markup is a .btn, so this is a strip rather than a build. Each thing
     turned off is one of the button's own, and all of them have to go for the
     label to sit on the page as text:

     - the fill, the pill radius and the padding, from .btn and the shared
       .btn-tan colour rule;
     - the drop shadow, also from that shared rule. With no fill left to cast
       it, it drew a soft pill floating behind the words -- the outline of a
       button that is not there;
     - overflow: hidden and isolation, which exist to clip the hover sweep;
     - ::after, which IS the hover sweep -- left alone it would still animate,
       invisibly, behind a link with no fill to reveal;
     - the display face, the uppercasing and the 0.2em tracking, replaced with
       the body face and the tracking the copy above it is set in.

     Below 860px none of this applies and the button is a button, arrow
     hidden -- see .story-cta > svg. */
  .home-story-copy .story-cta {
    justify-content: flex-start;
    gap: 8px;
    padding: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
    overflow: visible;
    isolation: auto;
    font-family: var(--font-body);
    font-size: 0.9375rem;
    font-weight: 400;
    text-transform: none;
    letter-spacing: 0.0375em;
    color: var(--text-muted);
    transition: color 0.2s ease;
  }
  .home-story-copy .story-cta::after { content: none; }
  .home-story-copy .story-cta:hover { color: var(--text); }
  /* The arrow holds still on hover, exactly as it does on the two links this
     matches -- the underline is the whole of the hover state. */
  .home-story-copy .story-cta > svg {
    display: block;
    width: 17px;
    height: 17px;
    flex-shrink: 0;
  }
  /* Wide, and the photo column is the bigger of the two -- the shape of the
     row is the whole of what changed here. It was 4/5 portrait, tall on
     purpose, because the text column parked in sticky for the height of this
     one and a taller photo held it longer. Nothing parks any more, so the
     height has no job and the frame is free to be the shape the picture
     wants.

     The ratio is on the WRAPPER, not on .split-media, and the frame inside
     it is height: 100%. That split is what keeps the panel and the photo the
     same height as each other in both directions.

     Put the ratio on the frame itself and it sets its own height and stops
     there. That is fine while the photo is the taller of the two -- the row
     takes its height from the photo and the panel stretches to match -- but
     it fails the other way round. Narrow the window and the copy gains lines
     while the photo, tied to its ratio, loses height; past the crossover the
     copy is taller, the row grows with it, and the photo simply stays its
     ratio height with the panel standing taller beside it.

     On the wrapper, the ratio is only the height the row asks for. Once the
     row is measured -- the taller of the photo's ratio height and the copy's
     content -- align-items: stretch gives the wrapper that height for real,
     and height: 100% passes it to the frame. So the photo is 16/10 whenever
     it is the thing setting the height, and taller than 16/10 when the copy
     is, which crops a little further rather than leaving an edge unmatched.

     16/10 rather than anything wider. Both files are portrait (864x1080 and
     944x1180) and object-fit: cover crops to fill, so every step wider throws
     away more of the top and bottom of the original. This keeps a band across
     the middle of each -- which is where both subjects happen to sit -- and
     is the widest that stays true of them. object-position on the img is the
     lever if either one loses its subject; see .split-media img.is-focus-right
     for that move. */
  .home-story-media {
    /* A flex container, and that is what closes the gap. The ratio used to be
       HERE, and it was the gap: an element with a definite height and an
       aspect-ratio computes its width from the two, which meant this box
       stopped being as wide as its track the moment the row was any taller
       than 16/10 -- and the row is exactly that whenever the copy beside it
       is the taller of the two, which "CUT & SEWN IN ARIZONA" over two lines
       makes it. The photo shrank to keep its ratio and left white between
       itself and the panel.

       Flex instead: the frame inside stretches to this box's full height by
       default, and takes width: 100% below, so it fills the track in both
       directions no matter what set the row's height. The ratio moves back
       onto the frame, where it is only what the row ASKS for -- used while
       the height is being worked out, ignored once stretch has made it
       definite. */
    display: flex;
    /* A grid item defaults to min-width: auto, which is its MIN-CONTENT width
       -- and the min-content width of a box holding a photo is that file's own
       intrinsic width. 864px for the first story, 944px for the second. The
       tracks are minmax(0, ...) so they shrink willingly, but the item inside
       refuses to go under its picture and spills out of its column onto the
       panel beside it, painting over the copy.

       That is why only the second row showed it at full width: on a 1372px
       container the wide track is about 871px, which the 864px photo just
       fits inside and the 944px one does not. Narrow the window and the first
       row does exactly the same thing.

       .split-media already carries this for the same reason (see its rule up
       the file); the wrapper it now sits in needs it too. */
    min-width: 0;
  }
  /* Beats the 4/3 .split-media carries for the About page's photos, and does
     it with a definite height rather than another ratio: an element with a
     definite height ignores aspect-ratio outright, so there are not two
     rules arguing about the same number. */
  /* Both, and they do not fight -- aspect-ratio is ignored the moment BOTH
     dimensions are definite, so this is a fill with a shape to fall back on.

     Which matters, because the fill is not guaranteed. height: 100% needs a
     definite height on the parent chain, and the row is min-height now rather
     than height: an auto-sized grid row with a floor. Where a browser does
     not treat the stretched item's height as definite, the percentage
     resolves to auto -- and with no ratio the box then takes the image's own
     proportion, which for these files is SQUARE. That is the bug this fixes:
     the photos came out square rather than wide.

     With the ratio back, the worst case is a wide box of exactly the depth
     the row was going to be anyway -- 4:3 of the media column works out to
     --band-h-story, which is what the row's floor is set to. See that token
     for the arithmetic. So the two agree at full width, and the ratio only
     does anything when the row has grown past its floor and the fill has
     failed. */
  .home-story-media .split-media {
    aspect-ratio: 4 / 3;
    width: 100%;
    height: 100%;
    /* The ground the photo fades up out of. .split-media has no fill of its
       own, so at opacity 0 what showed through was the band behind it --
       #ffffff on this page. Asked for black instead, so it is painted here.

       Desktop only, deliberately. The base rule this sits on applies at every
       width, and the phone's copy of this band is still #1a1a1a: it is
       already fading up out of near-black, and putting #141414 underneath it
       would be changing the phone for no reason. */
    background: #141414;
  }
}

/* ---- The stories hold two columns down to 750px -------------------------
   Reworked against a reference the client supplied (tuffpossumgear.com) at
   about 766px of window: there the band is still TWO columns, split evenly,
   with the copy in the left half and the photograph filling the right half
   top to bottom -- upright, roughly 0.6 wide-to-tall. It stacks only below
   that, and when it does the photo sits at about 7:6.

   This supersedes the earlier reasoning here, which stacked at 1150 and then
   1024 to keep the frame from going upright. An upright frame at these
   widths is now what is wanted, so the constraint is gone and the split can
   run much further down.

   50/50 rather than the desktop 1.15fr / 2fr. The desktop ratio gives the
   copy 36.5% of the row, which is a comfortable column at 1400px and a
   206px slot at 800px -- the reason the band had to stack early in the first
   place. Half the row keeps the heading on two lines and the paragraph off
   the ragged edge all the way down to 750.

   750px is the floor because that is where the reference stacks, and because
   half of 750 less the panel's own padding is 231px of text -- about as
   narrow as a paragraph reads before it starts to break up. */
@media (min-width: 750px) and (max-width: 1024px) {
  .page-home .home-story,
  .page-home .home-story-flip {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    min-height: var(--band-h-story);
    align-items: stretch;
  }
  /* The flip keeps the photograph on the left, which is what alternates the
     two bands down the page. Naming the row as well as the column is what
     holds them in the same row when source order and visual order disagree. */
  .page-home .home-story-flip > .home-story-copy { grid-column: 2; grid-row: 1; }
  .page-home .home-story-flip > .home-story-media { grid-column: 1; grid-row: 1; }
  .page-home .home-story-media {
    display: flex;
    /* A grid item's default min-width is its min-content width, which for a
       box holding a photograph is that file's own intrinsic width -- it would
       refuse to shrink and spill over the panel beside it. */
    min-width: 0;
  }
  /* Fills its half in both directions, so the frame is as tall as the row and
     goes upright as the window closes. That is the reference's behaviour and
     it is the whole point of holding two columns this far down. */
  .page-home .home-story-media .split-media {
    width: 100%;
    height: 100%;
    aspect-ratio: auto;
  }
  /* The desktop plate's padding, not the 36px the narrow rule gives it: at
     half a row the copy has the width to carry it. */
  .page-home .home-story-copy { padding: 56px; }
}

/* ---- Home story photos: a grade, and a little weather -------------------
   Both story photos are marked is-unfiltered, which opts them out of the
   colour treatment every other section photo on the site carries and drops
   the flat 35% scrim that goes with it. That is still the right starting
   point -- the house grade is heavier than these two want, and the scrim
   flattens them. What they were missing is any grade at all.

   So: a lighter version of the same idea, and only for these two.

   The filter lands on the .is-visible state rather than the resting one
   because is-unfiltered's entrance runs brightness(0) -> brightness(1); this
   is what it arrives at instead, so the photo still lifts out of black on
   scroll and simply settles a little further down.

   The scrim is a gradient rather than the flat wash: dark at the top and
   bottom edges and all but clear across the middle, so it reads as light
   falling off at the frame rather than as a sheet over the subject. Every
   stop is black at a different alpha -- never `transparent`, which is
   rgba(0,0,0,0) and would drag the interpolation through it unevenly.

   Both selectors carry .is-unfiltered and the media column, so they outrank
   the rules they are bending without depending on source order. */
.home-story-media .split-media {
  /* No frame. .split-media draws a 1.3px edge and a 16px radius for the
     About page's photos; these two want neither -- nothing is holding them,
     they sit straight on the section's ground. Both said here rather than in
     the desktop block below, because the base rule they are overriding
     applies at every width and so must these. */
  border: none;
  border-radius: 0;
}
/* These two fade up out of black and settle from slightly oversize, rather
   than sliding in from the side as they did before.

   Black again, then, but not the way the rest of the site gets there. Every
   other photo lifts out of filter: brightness(0), which has exactly one
   endpoint and it is #000. These fade over a painted #141414 -- see the
   desktop .split-media rule above -- which is the value the rest of the
   site's dark surfaces use and the same ground the hero and Get In Touch
   fade up out of. The four bands now arrive the same way.

   For a while they came up out of the page instead: .split-media had no fill
   and opacity 0 showed the white band through. That is what the painted
   ground replaces.

   The shrink is the site's own default entrance, scaled down. Every other
   photo starts at scale(1.28) and settles over 2.7s; 1.08 over 1.4s is the
   same idea at a size that suits a wide frame. A 28% overscale on a 16/10
   landscape throws a lot of picture outside the frame on the way in, and the
   crop these two already carry is tight enough without it.

   The frame is overflow: hidden (see .split-media), so the oversize state is
   clipped to it -- the photo overfills and comes back to its edges rather
   than spilling into the layout.

   The fade is a touch shorter than the shrink, so the picture is fully
   present while the last of the movement settles rather than still arriving
   after it has stopped.

   brightness(1) is said in both states because the rule up the file at
   .split-media img.is-unfiltered starts these at brightness(0). Two ways of
   arriving out of black would fight rather than add: the filter darkens the
   photo's own pixels while the fade is already revealing it against a dark
   ground, so the picture would come up flat and then brighten separately.
   One mechanism, and here it is the ground. */
.home-story-media .split-media img.is-unfiltered {
  opacity: 0;
  transform: scale(1.08);
  filter: brightness(1);
  transition: opacity 1.1s ease, transform 1.4s cubic-bezier(0.16, 1, 0.3, 1);
}
.home-story-media .split-media img.is-unfiltered.is-visible {
  opacity: 1;
  transform: scale(1);
  filter: brightness(1);
}
/* Off, the photo is simply in place at full strength. The fade could
   arguably stay -- an opacity change is not motion -- but it is half of one
   effect here, and half an entrance reads worse than none. */
@media (prefers-reduced-motion: reduce) {
  .home-story-media .split-media img.is-unfiltered,
  .home-story-media .split-media img.is-unfiltered.is-visible {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* ---- Home page stories (mobile): a plain stacked read --------------------
   Nothing to undo any more. Each story is its own row carrying its own copy
   and its own photo, in that order in the markup, so one column is what the
   base .home-story rule already gives -- heading, copy, link, photo, twice.
   The order is identical to what this block used to produce.

   What it took before: display: contents on the two wrappers to dissolve
   them, so their four children became direct items of .home-stories, then
   order: 1..4 to interleave the copies and the photos back together. That
   was the cost of one text slot serving two photos on desktop. The
   restructure paid it off, and the four order rules, the grid-area reset and
   the visibility override all went with it.

   The copy's entrance is desktop-only -- the opacity: 0 that arms it lives
   inside the min-width block -- so there is nothing to switch off here
   either, which is how this section already behaved. */
@media (max-width: 860px) {
  /* The underline belongs to the text-link treatment, which only exists
     above this breakpoint. Here the link is the .btn it is in the markup,
     and a rule dropping in under the label inside a filled pill is not the
     hover state that wants. */
  .home-story-copy .story-cta span::after { content: none; }
}


/* ---- Product page: filled form controls ---------------------------------
   The colour select, the quantity pill, the reviews search box and the two
   reviews dropdowns all sit on the page's own near-black and were drawn as
   outlines on nothing. --surface is one step up from that background, so
   they read as fields you can act on rather than as rules floating on the
   page, without turning into panels.

   Scoped to the product page's own containers. .color-select-trigger is
   shared with the quick view popup, which sits on --surface already and
   would lose its outline against its own panel if it were filled here. */
.product-color-select .color-select-trigger,
.product-detail-cart-row .qty-stepper,
.product-reviews-toolbar .color-select-trigger,
.product-reviews-toolbar .reviews-search-input {
  background: var(--surface);
}
/* The popup's controls take the same fill as the product page's above, which
   is what makes a colour selector look like itself wherever it turns up.

   These used to be a thin white veil laid over --modal-panel, from back when
   the popup's panel and --surface were the same value: the veil lifted
   whatever was under it by a fixed amount, so the controls followed the panel
   wherever it went. The panel has since moved to #131313 and the two came
   apart -- the veil landed on a tone that was neither the panel nor the
   product page's controls -- so these are pinned to --surface now and no
   longer track the panel. Move the panel and these stay put.

   Opaque, which the veil was built to be as well. It matters for the open
   menu, the only one of the three with anything behind it: a translucent fill
   would show the product copy running underneath the options. */
.quick-view-info .color-select-trigger,
.quick-view-actions .qty-stepper,
.quick-view-info .color-select-list {
  background: var(--surface);
}

/* ---- Dropdown menus: slide down, then the items arrive ------------------
   The panel unrolls from its top edge and the options fade in one after
   another behind it. Applied to every menu of the kind: the product page's
   colour selector, the buy bar's, the reviews toolbar's sort and rating
   filters, the store page's Sort By, and the header's Store menu.

   Pointer devices only. On a phone the colour list is a plain list and Sort
   is folded into the Refine sheet, which has staggered reveals of its own
   further up -- those live in a (max-width: 860px) block and never meet
   these.

   No JS was touched. `hidden` is still the state these menus toggle; the
   first rule just stops it meaning display:none HERE, so the panel stays in
   the layout with something to animate. visibility does the work display:none
   was doing -- untabbable, out of the accessibility tree -- and unlike it,
   visibility can be transitioned. */
@media (hover: hover) and (pointer: fine) {
  .color-select-list[hidden],
  .sort-dropdown-list[hidden] { display: block; }

  .color-select-list,
  .sort-dropdown-list {
    /* Unrolls from the top edge rather than the middle. */
    transform-origin: top;
  }
  /* The Store menu opens animated and closes instantly. Its transition is
     declared on the base rule, which means it runs BOTH ways -- so the way to
     make only the close instant is to cancel it there and re-declare it on
     the open state. A transition on the state you are moving TO is the one
     that runs. */
  .nav-dropdown-menu { transition: none; }
  .main-nav .nav-dropdown:hover .nav-dropdown-menu,
  .main-nav .nav-dropdown:focus-within .nav-dropdown-menu {
    transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1),
                opacity 0.2s ease,
                visibility 0.28s;
  }
  .color-select-list[hidden],
  .sort-dropdown-list[hidden] {
    visibility: hidden;
    opacity: 0;
    transform: scaleY(0);
    pointer-events: none;
  }
  /* Only the colour selectors roll back up. They are click menus -- you
     dismiss them deliberately, and the close is worth watching. visibility
     flips only after the roll-up finishes, or the panel would vanish on the
     first frame and take the animation with it. */
  .color-select-list[hidden] {
    transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1),
                opacity 0.14s ease,
                visibility 0s linear 0.2s;
  }
  /* Sort By just goes. It closes when the pointer leaves it, and by then the
     pointer is already somewhere else -- an animation there is something you
     have stopped looking at. */
  .sort-dropdown-list[hidden] { transition: none; }
  .color-select-list:not([hidden]),
  .sort-dropdown-list:not([hidden]) {
    visibility: visible;
    opacity: 1;
    transform: scaleY(1);
    transition: transform 0.28s cubic-bezier(0.16, 1, 0.3, 1),
                opacity 0.18s ease,
                visibility 0s;
  }

  /* The options. Hidden to begin with, and they do not fade -- each one is
     simply not there and then is, once the panel has finished unrolling.

     An animation rather than a transition, and the distinction matters. A
     transition would have to carry the delay on `transition-delay`, which
     applies to every property in the list -- so a menu item's colour would
     also refuse to change for a third of a second while open, and hovering
     one would lag. An animation keeps its own clock and leaves `transition`
     free for the hover colour these already had.

     0.01s, not 0s: a zero-length animation never runs, so it would never
     apply its forwards fill. This is as close to instant as it can be while
     still being an animation. */
  /* Closing is the other way round: the options hold their place while the
     panel rolls up, and only blink out once it has gone. Without that they
     vanish on the first frame and what rolls up is an empty box -- which
     reads as the menu being dismissed rather than sliding back under its
     trigger. A zero-length opacity transition with a delay does it: nothing
     animates, the value just waits its turn. The delay is each panel's own
     roll-up time. Colour keeps its own timing in the same shorthand, so
     hovering an option still responds at once. */
  .color-select-list li {
    opacity: 0;
    transition: opacity 0s linear 0.2s, color 0.2s ease;
  }
  /* No hold for these two: their panels do not roll up, so there is nothing
     for the options to stay behind for. They go with it. */
  .sort-dropdown-list li[role="option"],
  .nav-dropdown-menu a {
    opacity: 0;
    transition: color 0.2s ease;
  }
  @keyframes dropdown-item-appear { to { opacity: 1; } }
  .color-select-list:not([hidden]) li,
  .sort-dropdown-list:not([hidden]) li[role="option"],
  .main-nav .nav-dropdown:hover .nav-dropdown-menu a,
  .main-nav .nav-dropdown:focus-within .nav-dropdown-menu a {
    animation: dropdown-item-appear 0.01s linear forwards;
  }

  /* The stagger. 0.28s is the panel's own slide, so the first option lands as
     the panel settles rather than racing it down; the rest follow every 50ms.
     Delays sit on the OPEN state only -- closing drops the animation
     entirely and every option goes at once, because a menu that unwinds item
     by item reads as slow.

     The Sort By list runs one ahead of the others: its first child is
     .sort-dropdown-handle, the grab bar the mobile sheet uses, so its options
     start at nth-child(2). nth-child counts every child, not just the ones a
     selector matched -- and with the numbers shared, its last option fell off
     the end of the list and appeared with no delay at all. */
  .color-select-list:not([hidden]) li:nth-child(1),
  .sort-dropdown-list:not([hidden]) li[role="option"]:nth-child(2),
  .main-nav .nav-dropdown:hover .nav-dropdown-menu a:nth-child(1),
  .main-nav .nav-dropdown:focus-within .nav-dropdown-menu a:nth-child(1) { animation-delay: 0.18s; }
  .color-select-list:not([hidden]) li:nth-child(2),
  .sort-dropdown-list:not([hidden]) li[role="option"]:nth-child(3),
  .main-nav .nav-dropdown:hover .nav-dropdown-menu a:nth-child(2),
  .main-nav .nav-dropdown:focus-within .nav-dropdown-menu a:nth-child(2) { animation-delay: 0.23s; }
  .color-select-list:not([hidden]) li:nth-child(3),
  .sort-dropdown-list:not([hidden]) li[role="option"]:nth-child(4),
  .main-nav .nav-dropdown:hover .nav-dropdown-menu a:nth-child(3),
  .main-nav .nav-dropdown:focus-within .nav-dropdown-menu a:nth-child(3) { animation-delay: 0.28s; }
  .color-select-list:not([hidden]) li:nth-child(4),
  .sort-dropdown-list:not([hidden]) li[role="option"]:nth-child(5),
  .main-nav .nav-dropdown:hover .nav-dropdown-menu a:nth-child(4),
  .main-nav .nav-dropdown:focus-within .nav-dropdown-menu a:nth-child(4) { animation-delay: 0.33s; }
  .color-select-list:not([hidden]) li:nth-child(5),
  .sort-dropdown-list:not([hidden]) li[role="option"]:nth-child(6) { animation-delay: 0.38s; }
  .color-select-list:not([hidden]) li:nth-child(6),
  .sort-dropdown-list:not([hidden]) li[role="option"]:nth-child(7) { animation-delay: 0.43s; }
  .color-select-list:not([hidden]) li:nth-child(n + 7),
  .sort-dropdown-list:not([hidden]) li[role="option"]:nth-child(n + 8) { animation-delay: 0.48s; }
}
/* Reduced motion gets the menus with no slide and no stagger -- they simply
   appear, which is what they did before any of this. */
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: reduce) {
  .color-select-list,
  .sort-dropdown-list,
  .color-select-list li,
  .sort-dropdown-list li[role="option"],
  .nav-dropdown-menu a {
    transition: none;
    transform: none;
    /* opacity and animation too, not just transform: the items are hidden at
       0 above and revealed by an animation. Cancel that without resetting
       opacity and every menu opens showing nothing at all. */
    animation: none;
    opacity: 1;
    visibility: visible;
  }
  .color-select-list[hidden],
  .sort-dropdown-list[hidden] { display: none; }
}

/* ---- Add to Cart: red -----------------------------------------------------
   The one action on the site that should not look like every other button.
   Covers all four: the product page's, the buy bar's, and the quick view
   popup's on each of the three pages that carry one. Two selectors because
   they are not one class -- the first two are .btn-cart, the popup's is
   .btn-light -- and the data attribute is what actually means "add to cart".

   Declared here, at the end, rather than edited into the shared button rules
   above: those set the gray fill and the sweep for six button types at once,
   and this needs to override only for these. Equal specificity, later wins. */
.btn-cart,
[data-qv-add-to-cart] {
  /* Solid, not a wash: the fill is the button. No border and no padding
     override either -- .btn and the shared rule above already say
     border: none, so these keep .btn's own 14px/28px and come out the exact
     size every other button on the site does. */
  background: var(--btn-red);
  color: var(--offwhite);
}
/* The same sweep every button on the site has, in the tint the palette
   already keeps for exactly this -- see --btn-red-light, "hover-sweep tint
   for redish buttons". Only the two colour stops differ from the gray one
   above; the geometry, the feathered top and bottom edges and the 0.22s
   timing are identical, so this button behaves like its neighbours and simply
   is not gray.

   The 0px and 100% stops stay transparent -- that is what feathers the
   leading and trailing edges as the panel slides through. */
.btn-cart::after,
[data-qv-add-to-cart]::after {
  background: linear-gradient(to bottom,
    transparent 0px,
    var(--btn-red-light) 16px,
    var(--btn-red-light) calc(100% - 16px),
    transparent 100%);
}
/* Touch devices flatten every button's hover to the solid gray further up.
   These two keep their red instead -- a tap should not turn the cart button
   into a different button. */
@media (hover: none) {
  .btn-cart:hover,
  [data-qv-add-to-cart]:hover {
    background: var(--btn-red);
    color: var(--offwhite);
  }
}


/* ============================================================
   About Us / Cut & Sewn -- the light band (desktop only)
   ============================================================
   The one section on the site that inverts: a white ground with dark type,
   between the Featured Collection above and the Free Membership band below.
   Everything here is inside a min-width query on purpose -- the phone keeps
   the dark #1a1a1a band and the pill button it has always had, and none of
   these rules are written where it can see them.

   860.02px is the site's desktop gate, the same number the story layout
   itself opens at further up. That matters more than it looks: the layout
   block turns these links into stripped text links at exactly this width, so
   the box below and the strip it undoes come in and out together. Split them
   across two different breakpoints and there would be a band of widths
   showing a half-built button. */
@media (min-width: 860.02px) {
  /* Overrides .home-about at the top of the file. Written as .section
     .home-about rather than the bare class so that nothing later in the
     cascade at plain (0,1,0) can put the dark ground back. */
  .section.home-about { background: var(--page-ground); }
  /* h2 takes --title and p takes --text-muted, both near-white, and neither
     is set anywhere nearer than the bare element rules at the top of the
     file -- so both need saying here or they vanish into the new ground.
     One value for both: the muted step exists to sit a paragraph back from
     its heading on a dark ground, and inverted it would only make the copy
     harder to read rather than quieter. */
  .home-about .home-story-copy h2,
  .home-about .home-story-copy p { color: #ffffff; }
  /* ---- The heading and the copy: Barlow Condensed, light -----------------
     Barlow Condensed at the same two light weights Oswald was carrying. It
     answers the original objection from the other direction: Oswald is a
     signage grotesque that modulates and only stops looking uneven once you
     take the weight off it, while Barlow is drawn low-contrast to begin
     with -- a rounded grotesk whose strokes hold their thickness around a
     curve as a matter of design rather than as a side effect of being
     light. Same narrow letters, evenness that does not depend on the
     weight.

     Which means the weight is free to be whatever looks right, and it is
     500 -- the medium cut, for both the heading and the copy. The route
     here was 300 and 200, then 400, then this. The two light cuts were
     inherited from Oswald, where lightness was doing a job; with a face
     that is even at every weight it was only making the band faint.

     500 over 400 is the step that specimens tend to hide, and there is a
     reason for it: Google Fonts previews white on near-black, and light
     type on a dark ground blooms -- the strokes spread slightly and read
     heavier than the same weight does as dark ink on white. A weight
     chosen there and set on white lands lighter than it looked. Which is
     the general rule this section ended up proving: take a weight off that
     preview at the value it looked good at, or one step up, never one step
     down.

     ---- What this costs ----
     A new family in every page's Google Fonts link, which now asks for
     Barlow Condensed 500 alongside Koulen, Oswald and Jost. Oswald has gone
     back to the four weights it had before this section started borrowing
     from it; it is the fallback behind Koulen in --font-display and never
     needed a light cut of its own. IBM Plex Sans Condensed, the step before
     Oswald, is gone from the link entirely. One weight is requested because
     one weight is used -- there is no second cut here to fall back to. */
  .home-about .home-story-copy h2 {
    font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
                 'Segoe UI', Roboto, sans-serif;
    /* Bold, and a real cut -- 700 is in the request alongside 400 and 500.
       Worth saying because the base heading rule pins 400 for the opposite
       reason: Koulen ships a single weight, so anything heavier there was
       the browser faking a bold by smearing the outline sideways.

       This is the top of a three-step ramp the band now runs entirely on
       one family: 700 for the headings, 500 for the copy beneath them, 400
       for the two button labels. Three weights, one face, and the hierarchy
       carried by weight alone rather than by changing typeface. */
    font-weight: 700;
    /* Kept from what was here before, and originally from Koulen, whose
       letterforms read as caps whether or not the markup is uppercase --
       which is why these two headings have looked like caps since long
       before any of this. Oswald has real lowercase, so the look has to be
       asked for. */
    text-transform: uppercase;
    /* Held at 0.02em through the move from 500 to 700, which is not the
       obvious direction. Tracking usually comes DOWN as weight goes up --
       heavier letters have enough ink to hold themselves apart, which is
       what took this from 0.05em when the heading was 300. Bold condensed
       caps are the exception: the stems thicken while the letter widths
       barely move, so the gaps between them close from both sides at once.
       Coming down again would start to fuse them. */
    letter-spacing: 0.02em;
    /* Fixed, deliberately. Every other heading on the site is fluid --
       the site-wide h2 is clamp(1.75rem, 3.2vw, 2.3rem), and this band ran
       its own clamp until now -- so a single number here is a departure
       from the pattern rather than an oversight. 3.25rem against this
       page's 18px root is 58.5px, which is what the clamp was already
       giving on any window past about 1393px; on a wide screen nothing
       changes.

       What changes is everything narrower. A clamp is not decoration -- its
       floor exists to keep a heading inside its column, and dropping it
       hands that job to wrapping instead. The arithmetic, so it is on record
       rather than discovered later: the copy column is (container - 48px
       gap) / 2, the container is the window less clamp(16px, 4vw, 64px) of
       padding each side, and "CUT & SEWN IN ARIZONA" -- the longer of the
       two headings, and the one to check -- sets to about 430px at 58.5px.
       Those meet at roughly 987px of window.

       So: one line above ~987px, two lines below it, down to the 860px
       where this whole block stops applying. That is a real band of desktop
       widths, not a corner case, and a two-line heading there is the cost
       of the size holding still. It wraps cleanly -- the break falls at a
       space and the sticky copy slot is measured in JS, so the layout
       follows it -- but it is a different-looking section at that width.

       2.5rem now, down from 3.25. It is the Free Membership band's size, and
       that band was the one heading here set smaller than the rest -- on the
       argument that a heading over a form in a strip is not a heading that
       owns half a row. Levelling them the other way settles it: one display
       size for the whole page, and the size is the smaller one. 45px against
       an 18px root.

       The wrap arithmetic above improves with it. 'CUT & SEWN IN ARIZONA'
       sets to about 330px at 45px rather than 430px at 58.5px, so the width
       where it breaks to two lines drops well below the 860px this block
       stops at -- the two-line band described above is gone. */
    font-size: 2.5rem;
    /* Down from 1.2 with the size going up. Leading is a ratio, so a fixed
       one adds real space as the type grows -- and display-size lines want
       proportionally less between them than reading-size ones do, not more.
       These headings are two lines at most, so this is mostly about the pair
       in "CUT & SEWN IN ARIZONA" sitting as one block. */
    line-height: 1.1;
  }
  .home-about .home-story-copy p {
    font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
                 'Segoe UI', Roboto, sans-serif;
    /* Regular. The band's three weights are no longer a ladder -- the copy
       now sits a step BELOW the button labels beside it, which is the right
       way round for what each is doing: a paragraph is read continuously and
       wants the lightest colour it can hold, while a label is a small target
       reversed out of red and needs more. */
    font-weight: 400;
    /* Up a step from the base p rule. Only one reason left for it now -- a
       condensed face at a given size reads smaller than a normal-width one,
       because the size is the height and the narrowness is what the eye
       takes for smallness. The second reason, that a 200-weight face reads
       fainter as well, has gone with the weight. */
    font-size: 1.125rem;
    /* Down from the 1.75 the light cut needed. Thin strokes give the eye
       little to hold on to tracking back to the next line, so that row had
       to be unmistakable; at 500 the lines carry themselves and this is
       simply a comfortable measure for a narrow face. Still above the 1.5 or
       so a normal-width face would take, because narrow letters put more
       characters on a line and a long line wants more leading. */
    line-height: 1.6;
    /* Near enough to the face's own spacing to leave it alone. Barlow is
       drawn for text and needs nothing added; this is a hair's worth against
       condensed stems sitting close, and far under body's 0.0375em, which is
       Lambda's tracking and belongs to Lambda. */
    letter-spacing: 0.005em;
  }
  /* ---- The story links: a tight red box ----------------------------------
     The desktop layout block above strips these two back to text links --
     no fill, no padding, no radius, and ::after (the hover sweep) turned
     off. This puts a box back around the label, but not the pill that was
     stripped: square corners, a flat fill, and padding that hugs the words
     rather than the wide pill inset .btn carries.

     The sweep stays off. It is a panel that slides down through the button
     on a 0.22s transform, and what was asked for here is a colour change --
     so the hover is the fill itself moving, in one step, and the ::after
     that would slide behind it is left as the layout block left it.

     No shadow either. That was removed from these two deliberately (they
     are the only buttons on the site without one), and on a white ground it
     would read louder than it ever did on the dark. */
  .hero-actions .story-cta {
    /* The box's height is the line box plus the padding, and the line box was
       the problem: nothing here set a line-height, so these inherited body's
       (1.65, tuned for paragraphs) and the label carried ~10px of leading it
       had no use for inside a button. Pinning it to 1.15 is most of the
       height that came off; the padding drop is the rest.

       There is a floor underneath both: the row is align-items: center, so
       its height is the taller of the label's line box and the arrow's box,
       and the button cannot come in under that plus its padding. At 20.25px
       and 1.15 the line box is 23.3px against the arrow's 14px, so the type
       is what sets the height here and the arrow has room to spare. */
    line-height: 1.15;
    /* Tight: enough to clear the cap height and to keep the label off the
       side edges, and no more. .btn's own inset is 14px 28px. The sides are
       left wide on purpose -- taking them down with the top and bottom would
       just make a smaller button rather than a flatter one.

       Top and bottom are NOT equal, and that is the point of this value.
       align-items: center centres the line BOX, not the letters in it, and
       the line box is built from the font's ascent and descent -- which in
       Barlow Condensed are 1.000em and 0.200em against a cap height of
       0.700em. So the caps hang 0.300em below the top of that box and only
       0.200em above the bottom of it: set equal padding and the cap block
       lands 0.1em below the middle of the button. The correction is a
       fraction of the EM, so it tracks the type -- 1.8px at 18px, 2.25px at
       22.5px, 2.03px at the 20.25px this is now. 8px and 10px is that, to
       within a fortieth of a pixel.

       All-caps is what makes it visible. Lowercase fills the descent room
       the metrics reserve, so the block of text reads as centred even when
       the caps are not; strip the descenders out and the only ink left in
       the button is the cap block, and where it sits is the whole of what
       the eye is judging.

       These numbers are per-face and have to be rechecked when the face
       changes. They were set for Typewriter Condensed Demi, whose ascent,
       descent and REAL cap height were 1.034, 0.288 and 0.634 -- that font
       advertises 0.722 in its OS/2 table and does not draw it, which is why
       the cap was measured off the outlines rather than read off the
       metrics. Different numbers entirely; they happen to ask for the same
       2px. Barlow is the better-behaved of the two: it declares 0.700 and
       draws 0.700, and it sets USE_TYPO_METRICS, so a browser has no second
       set of numbers to choose from. Its win metrics (1.075 / 0.274) would
       give -1.82px anyway, so this padding holds either way.

       If it reads high or low after a face change, these two are the whole
       adjustment: their difference is the correction, their sum is the
       height. */
    padding: 8px 22px 10px;
    /* Barlow Condensed, the same face as the heading and the copy above --
       so the band is one voice again rather than a sans against a
       typewriter. 500 here, a step ABOVE the 400 of the copy beside it: the
       label is small and reversed out of a saturated red, which is the
       hardest reading on the page, and the extra weight is what holds it
       there. The headings above sit at 700, so the band runs 700 / 500 / 400
       from heading to label to copy.

       It replaces Typewriter Condensed Demi, self-hosted at the top of this
       file. Nothing on the site sets that face now -- the @font-face blocks
       for all three of its weights are still there, unused, as they were
       before this section ever borrowed one.

       The stack behind it is the system sans, matching the rules above.

       1.125rem, which is 20.25px against this page's 18px root -- the
       midpoint between the 18px this started at and the 22.5px it went to.
       Still a step above the 0.9375rem every other button label on the site
       carries, and for two reasons: a condensed face at a given size reads
       visibly smaller than a normal-width one (the size is the height, and
       the narrowness is what the eye takes for smallness), and these two
       stand alone under a heading rather than sitting in a row of their own
       kind.

       Every number below that is measured in pixels rather than ems was
       halved back with it -- the padding, the arrow, its lead and its
       travel. Anything in ems needed nothing: the tracking, the negative
       margin that cancels it, and the line-height all scale themselves. */
    font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
                 'Segoe UI', Roboto, sans-serif;
    font-size: 1.125rem;
    font-weight: 500;
    /* The layout block above sets text-transform: none, as part of turning
       these into lowercase text links -- so caps here is a declaration to
       beat, not just one to add. */
    text-transform: uppercase;
    /* Down from the 0.09em the typewriter took, up from the 0.02em the
       heading takes. Both directions have a reason. The typewriter carried
       wide spacing in its own outlines and needed less added; Barlow does
       not, so caps in it close up faster. But the heading runs to 58.5px and
       this is 20.25px, and small caps need proportionally more air than
       large ones -- at display size the letterforms are big enough to hold
       themselves apart.

       Either way, well under the 0.2em .btn's display face uses, and clear
       of the 0.0375em the layout block inherits from the body copy, which
       is Lambda's paragraph tracking and set for lowercase. */
    letter-spacing: 0.06em;
    /* A box, not the site's pill. Every other button on the page is fully
       rounded, so this is the one shape that says it is a different kind of
       thing. */
    border-radius: 0;
    /* .btn-tan casts --btn-shadow under every button that carries it. The two
       story buttons never showed it -- the layout block up the file strips it
       along with the rest of the pill -- and the hero's did, because that
       block is scoped to the stories. Dropped here so all three match, and
       because a drop shadow under a white button on a photograph reads as a
       pill floating over the picture rather than as a button on it. */
    box-shadow: none;
    /* The fill and the label are NOT here. Everything above is shape, type
       and mechanics, shared now by the two story buttons and the hero's --
       and those two want opposite colours. The pairs are set below, each
       scoped to its own place. */
    /* No transition declared here on purpose. Neither the fill nor the label
       changes on hover any more -- the arrow and the button's width are the
       whole of the hover state, and both of those are transitioned on the
       arrow itself further down. The layout block's `transition: color 0.2s
       ease` is left standing rather than overridden: it is a no-op against a
       colour that never moves. */
    /* The layout block sets gap: 8px, for a row whose arrow was permanently
       visible. The arrow is closed at rest now, and flex gap is paid whether
       or not the item beside it has any width -- so left at 8px it would
       hold an 8px void inside the right edge and the two insets would not
       match. The arrow carries its own gap as a margin instead, which costs
       nothing while it is shut. */
    gap: 0;
    /* Back on, after the layout block turned it off. It had been turned off
       because the only thing it clipped was the hover sweep, and the sweep
       is gone -- but it is now the mechanism of the reveal itself. The arrow
       is parked outside this box; overflow: hidden is what keeps it out of
       sight until the box is wide enough to contain it. */
    overflow: hidden;
  }
  /* The fill is deliberately absent here: the button holds --btn-red-solid
     through the hover, and the opening edge and the arrow are the entire
     response. The rule still exists for the label, though, and has to --
     the layout block sets a hover that lifts it from muted to full --text,
     which was the whole hover state back when this was a text link. Left
     alone that would shift the label a shade on hover, which is exactly the
     colour change being removed. Pinning it to the resting white is what
     stops it. */
  /* The two colour pairs. Both write the hover as well as the rest state,
     because the layout block up the file sets a hover that lifts the label
     from muted to full --text -- which was the whole hover when these were
     text links, and would shift the label a shade if left standing. Neither
     button changes colour on hover now; the arrow is the whole of it. */
  /* ---- The two story buttons ---------------------------------------------
     Learn More and Shop All Products. #9c423b, having been white boxes with
     a #75332d label, then #286e5d green, then a #ed645a coral.

     These two sit ON the panels. #e6453f is a bright red against a dark warm
     grey one -- the only colour on the page now that the panels are near
     neutral -- and it meters 2.4:1 against the panel behind it, which is
     enough to read as an object on it.

     NEITHER LABEL IS COMFORTABLE, and this is the second fill to land there.
     White measures 4.0:1 and misses; near-black measures 4.6:1 and passes by
     a tenth. Small text wants 4.5:1. Like #bd534a before it, this colour
     sits close enough to the middle of the range that white is running out
     of room and dark has barely found any.

     Dark is the better of the two and it is the one that passes -- but at
     4.6:1 it is a technical pass, not a comfortable one. Moving the fill a
     few steps darker opens white up properly; a few steps lighter does the
     same for a dark label. The middle is the expensive place to sit.

     The run, best to worst: 14.4:1 dark on #57ffbc, 11.9:1 white on #183d35,
     9.8:1 white on #2b4c1a, 9.3:1 dark on #a8bdb8, 9.2:1 dark on #e6aa83,
     6.8:1 dark on #ff7066, 6.5:1 white on #9c423b, 6.0:1 white on #286e5d
     and 6.0:1 forest on #a8bdb8, 4.7:1 white on #bd534a, 4.6:1 dark here,
     4.0:1 white here, 3.2:1 white on #ed645a, 2.7:1 white on #ff7066, 2.0:1
     white on #e6aa83 and on #a8bdb8, 1.3:1 white on #57ffbc.

     This also puts these two back into the arrangement the Contact Us
     button already has: a pale box with a dark label. Different colour, same
     idea.

     Contact Us on Get In Touch is NOT included -- it sits on a photograph
     rather than on a panel, and white is doing a job there that a colour
     would not. The hero's is its own thing, #6e2f2a. So the four buttons are
     three treatments. */
  .home-story-copy .story-cta,
  .home-story-copy .story-cta:hover {
    /* Red on white type. This pair went red, then near-black, then a white
       box with a #75332d label, then through most of a dozen reds -- #592823,
       #6e2f2a, #b35754, #e6453f, #e64527 -- before landing here.

       #b02b25 is the one that finally measures. White on it is 6.53:1,
       comfortably past the 4.5:1 small text needs, where the brighter reds
       before it were stuck at about 4.0 and the choice was between a white
       label that did not quite clear and a dark one that did not either. The
       fix was going darker, not brighter.

       It is also --btn-red exactly -- the value the product page's Add to
       Cart and the buy bar have carried all along -- so every red button on
       the site is now one colour rather than three near-misses.

       All four take it together: the hero, Contact Us and this pair.

       Worth knowing it is NOT a token reference, despite equalling one.
       --panel-head moves every time a new panel colour is tried and this
       would follow it there, which is wrong: a button over a panel or a
       photograph should not change because the panel did. Literal on
       purpose. */
    background: #b02b25;
    color: #ffffff;
  }
  /* The hero's button, and it is the same #b02b25 as the other three. This
     rule exists as a separate declaration rather than as part of theirs
     because the two have not always agreed -- there was a stretch where the
     hero was black and the story pair white, and another where the hero was
     a light mint with a dark label -- and keeping the selector split means
     the next disagreement does not require pulling one out of a list.

     The condition it has to satisfy that the others do not: it sits on the
     hero photograph, which is already dark and already carrying an 18% scrim
     at its edges, so a very light fill there is the brightest thing on the
     page by a wide margin. A dark red is not.

     Contrast, for the record, and one of these is a live problem. The LABEL
     is fine everywhere: white on #b02b25 is 6.53:1, past the 4.5:1 small text
     needs. The BOX is not, and it has just got much worse.

     Against --panel-head at #3d3d3d -- the panel the two story buttons sit on
     -- the fill meters 1.10:1. Not "under the 3:1 a component's boundary
     wants" in the way 2.14:1 on #332a29 was under it; 1.10:1 means the button
     and the panel behind it are within a hundredth of the same luminance. The
     red and the grey are the same WEIGHT and differ only in hue, so at the
     top of the gradient a Learn More button has effectively no edge: what you
     see is a white label floating on the panel, and the box only appears as
     the ground darkens under it further down.

     The run, so the direction is clear: 1.85:1 on #4a2f2d, 2.14:1 on #332a29,
     1.10:1 on #3d3d3d. Every step the panel took toward neutral helped until
     this one, which took it toward the BUTTON's own lightness instead, and
     that is the axis that matters -- hue never separated these two, value
     did.

     It does not arise on the hero's, which sits on a photograph rather than
     on the panel. And it moves with the gradient rather than with the button:
     a DARKER head stop fixes it without touching this value, which is the
     opposite of what fixes the eyebrow. Those two want the panel moved in
     opposite directions and only one of them can be satisfied by moving it.

     A literal, not a token, even though it equals --btn-red: --panel-head
     moves whenever a new panel colour is tried and a button over a photograph
     should not follow it there.

     Both states named, so the pair holds through the hover exactly as the
     other three do. */
  .hero .btn-hero.story-cta,
  .hero .btn-hero.story-cta:hover {
    background: #b02b25;
    color: #ffffff;
  }
  /* ---- and a size up, this button only -----------------------------------
     Scoped to .hero, which is not fussiness. `.hero-actions .story-cta`
     further down sets the size for this button, and .hero-actions is also
     the wrapper around Learn More, Shop All Products and Contact Us -- it
     was written for the hero and reused. Worse, it sits AFTER
     `.home-story-copy .story-cta` at the same specificity, so it already
     wins for those three. Editing it would have grown all four.

     The step is 1/9 on everything, so the button scales rather than
     stretches: 1.125rem to 1.25rem, 8/22/10 of padding to 9/24/11, the arrow
     27px to 30px with its 16px lead to 18px.

     Two numbers are derived from those rather than chosen. margin-right at
     rest is -(width + margin-left), which is what tucks the arrow out of
     sight -- -48px now. And the hover value keeps the same air past the
     arrow that -6px gave against 22px of padding: -7px against 24px leaves
     17px of box, where -6 and 22 left 16.

     The stroke comes out better than it was. stroke-width 2.25 on a 34-unit
     viewBox at 30px is 1.99px, and Barlow Condensed 500 draws its stems at
     about 0.09em -- 2.03px at this 22.5px label. The old pairing was 1.79
     against 1.82. Closer at the larger size, not further. */
  .hero .hero-actions .story-cta {
    font-size: 1.25rem;
    padding: 9px 24px 11px;
  }
  .hero .hero-actions .story-cta > svg {
    width: 30px;
    margin-left: 18px;
    margin-right: -48px;
  }
  .hero .hero-actions .story-cta:hover > svg,
  .hero .hero-actions .story-cta:focus-visible > svg {
    margin-right: -7px;
  }
  /* The dropping underline goes with it. It is hung off the span and runs
     25px past its right edge to reach under the arrow (see the shared rule
     with the quick view's View Full Details) -- inside a box that overhang
     lands on the fill, and the hover is the box now regardless. */
  .hero-actions .story-cta span::after { content: none; }
  /* letter-spacing is applied AFTER every character, the last one included,
     so a tracked label carries a trailing sliver of space that the padding
     knows nothing about -- at 0.06em on a 20.25px font that is about 1.2px
     of extra room inside the right edge and none inside the left. Pulling the
     same value back off the span's right margin is what actually makes the
     space beside the L of LEARN equal to the space beside the E of MORE.
     It has to track the letter-spacing above: change one, change both. */
  .hero-actions .story-cta span { margin-right: -0.06em; }
  /* ---- The arrow: in from the right, back out the same way ---------------
     Two things happen, and the order they happen in is the point: the red
     opens first, and the arrow follows the edge in.

     The BUTTON's width is margin-right on this element. A flex item
     contributes its own width plus its margins to the container, negatives
     included -- so -43px (the 16px lead plus the 27px of arrow) cancels the
     arrow out of the sum completely. At rest the button measures the label
     and its two equal insets and nothing more, exactly as if the arrow were
     not in the markup. Ease that negative toward zero and the difference
     lands in the sum, and the button grows -- rightward only, because
     .hero-actions and the button's own row are both justify-content:
     flex-start, so the label cannot move.

     The ARROW's travel is transform, which costs the layout nothing: it
     slides through the space the width above has opened without moving
     anything. It rests 27px to the RIGHT of where it lands and comes in
     leftward, chasing the opening edge -- the edge outruns it, uncovers its
     tail partway through, and the arrow slides the rest of the way in under
     its own steam.

     Because it arrives from outside the box and leaves the same way, the
     clip is the whole of the hiding and there is no fade here at all. That
     also means the entrance and the exit ARE reverses of each other, which
     is what lets plain :hover drive both -- when it comes in from the left
     they are not, and this needed the .is-hovering / .is-leaving classes
     initButtonSweep puts on every .btn-tan to tell the two directions
     apart. Those classes are still being set on these buttons; nothing
     hangs off them any more, and the sweep they exist for is already
     switched off here by ::after { content: none }.

     The lead -- the gap between the end of the label and the arrow's landing
     spot -- is 16px, tighter than the button's own 22px inset, so the arrow
     reads as belonging to the label rather than as a second thing sharing
     the box with it. Unlike the left-hand entrance, the travel is not
     bounded by that number: the arrow starts on the far side of the label,
     not behind it, so it can run as far as it likes without ever meeting
     the text. Only the resting margin-right below has to move with it.

     ---- Timing ----
     The arrow waits 0.06s. The wait exists so the edge is unmistakably the
     thing that moves first -- the arrow follows it rather than racing it --
     and 0.06s is about as short as that can be and still read as an order
     of events rather than as both starting together. It was 0.15s, which is
     long enough to be felt as a pause: the arrow appeared to be waiting for
     an invitation rather than following the edge in.

     Note what the delay does NOT need to buy any more. Coming in from the
     left it was covering for the arrow crossing the gap beside the label
     before the red had opened far enough to honestly show it -- a masking
     job, and one that also wanted a fade. From the right there is nothing to
     mask, so the number is free to be whatever looks right.

     0.28s over the 27px, on cubic-bezier(0.33, 1, 0.68, 1) -- an ease that
     settles but spreads the travel across its time rather than front-loading
     it the way the site's usual cubic-bezier(0.16, 1, 0.3, 1) does. Entrance
     is about 0.34s end to end against the width's 0.3s, so the arrow still
     comes to rest just after the opening finishes. The exit takes the base
     rule's delay of zero, so it leaves at once -- a thing going does not
     need watching the way a thing arriving does. */
  .hero-actions .story-cta > svg {
    display: block;
    /* This number is half of the arrow's stroke weight, and the stroke-width
       in the markup is the other half. The path is drawn on a 34-unit-wide
       viewBox, so at 27px the whole thing is scaled by 27/34 -- change the
       width here and the line thickens or thins with it.

       The weight it is set to match is the label's, measured off the font
       rather than guessed. Barlow Condensed 500 draws its horizontal
       strokes at 83 units per em (the hyphen bar) and its vertical ones at
       96 (the pipe), so call it 0.09em -- about 1.8px at this button's
       20.25px. stroke-width 2.25 through the 0.794 scale lands at 1.79px.

       That number has now survived the box growing AND shrinking without
       being touched: the width scales the art inside it, so the stroke
       follows the button's size on its own. It only has to be reset when
       the label's WEIGHT changes, which is the thing the width does not
       track.

       It has moved once already, and both times for the same reason. At
       Typewriter Condensed Demi (56 and 64) and at Barlow Condensed 400 (62
       and 71) it sat at 1.6, because those two faces are a fifth of a pixel
       apart despite being unrelated. Barlow 500 is a third heavier than its
       own 400 -- weight moves a stroke far more than family does -- so the
       arrow had to follow. This number tracks the label's WEIGHT, not its
       typeface: change one and re-measure. */
    width: 27px;
    /* A box for the art rather than the art's own height: the file is 34x12,
       so at 27px wide it draws about 9.5px tall and centres in this. */
    height: 14px;
    margin-left: 16px;
    margin-right: -43px;
    transform: translateX(27px);
    flex-shrink: 0;
    /* Declared once, here, for both directions. A transition is read off the
       state being moved TO, so this list governs the exit; the entrance
       borrows it and adds a delay below. */
    transition: margin-right 0.3s cubic-bezier(0.33, 1, 0.68, 1),
                transform 0.28s cubic-bezier(0.33, 1, 0.68, 1);
  }
  /* :focus-visible beside :hover so the arrow is not something only a
     pointer can reach -- tabbing to the link brings it in the same way, and
     blur takes it back out.

     -6px, not 0: the space left past the arrow is this margin plus the 22px
     of padding-right, so 0 would leave a full 22px of empty red out there --
     as much room as the label gets on its other side, which made the button
     look like it had overshot rather than opened just far enough. -6px
     leaves 16px of box; the art's round cap ends about 2px inside its 27px
     viewport, so what reads is about 18px.

     transition-delay is a list against the transition-property order above:
     the width starts immediately, the arrow waits. Set here rather than in
     the base rule so it applies to the entrance only. */
  .hero-actions .story-cta:hover > svg,
  .hero-actions .story-cta:focus-visible > svg {
    margin-right: -6px;
    transform: translateX(0);
    transition-delay: 0s, 0.06s;
  }
  /* A button changing width, with something sliding through it, is a good
     deal more movement than this band's other hover states. Off, the arrow
     is simply there on hover and the button simply wider -- the same end
     state, arrived at instantly. */
  @media (prefers-reduced-motion: reduce) {
    .hero-actions .story-cta,
    .hero-actions .story-cta > svg { transition: none; }
  }
}


/* ============================================================
   Featured Collection: the light band, and cards on a panel
   ============================================================
   The two additions productCardMarkup now makes are off everywhere by
   default. One template serves every grid on the site -- featured, store,
   related, popular -- and only this one wants them, so they are switched on
   below rather than rendered conditionally. */
.product-blurb,
.product-info-link { display: none; }
/* No box at all, so on the store, related and popular grids the three lines
   inside it stay direct flex items of .product-info and lay out exactly as
   they did before the wrapper existed. */
.product-info-lines { display: contents; }

/* ---- Category tiles (home page) -----------------------------------------
   Four doors into the store, standing where the featured products used to.
   Written at every width rather than as a desktop treatment over a phone
   layout: this is new content, not a repaint of something the phone already
   had, so it gets one look and only the column count changes on desktop.

   The whole tile is one anchor -- see index.html -- so the plate and the
   panel are two halves of a single link rather than two links with an
   overlay between them, which is what a product card needed. */
.category-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--grid-gap);
}
.category-card {
  display: flex;
  flex-direction: column;
  min-width: 0;
  text-decoration: none;
  color: inherit;
}
/* The whole tile is the picture now. It was a square plate with a panel
   welded under it carrying the name and a line of description; the panel and
   the description are gone, and the name has moved inside the picture with a
   product count under it.

   3 / 4 rather than 1 / 1, and that is what keeps the tile the size it was:
   the old card was a 445px square over a 139px panel, 584px in all at the
   1500px container, and 445 at 3:4 is 593. Nine pixels, on a band that is
   one of four running down the page -- near enough that nothing else has to
   move, and a stated ratio rather than the sum of two boxes that happened to
   add up.

   Still clipping: the plate inside grows on hover and this is the box it
   grows inside, and the label below rides on the same edge. */
.category-media {
  position: relative;
  aspect-ratio: 3 / 4;
  overflow: hidden;
}
/* The name and the count, laid over the foot of the picture.

   Absolutely positioned rather than in flow, so the picture keeps its whole
   3:4 box and the type sits on it instead of taking height from it. The
   plate behind is a photograph's stand-in and will be a photograph; type in
   flow under it would put the card back to two boxes.

   The gradient is what carries the type. It runs from nothing at 0 to 88%
   black at the bottom edge, over the lower 45% of the tile -- so the words
   have a dark ground under them and the middle of the picture, which is the
   part being looked at, is untouched. On the Coming Soon plate this is
   barely visible, since the plate is already near-black; it is there for the
   photographs that replace it. */
.category-label {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 22px 22px 20px;
  background: linear-gradient(to bottom,
                              rgba(20, 20, 20, 0) 0%,
                              rgba(20, 20, 20, 0.88) 100%);
  /* Never a target: the whole tile is one anchor and this is painted on it. */
  pointer-events: none;
}
.category-label h3 {
  font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
               'Segoe UI', Roboto, sans-serif;
  font-weight: 700;
  font-size: 1.5rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  line-height: 1.15;
  color: #ffffff;
  margin: 0;
}
/* The count, a step down from the name in size and in weight -- it is a
   fact about the category rather than its title. --on-dark-muted against
   the gradient's foot measures about 13:1, so holding it back costs
   nothing. */
.category-count {
  display: block;
  margin-top: 4px;
  font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
               'Segoe UI', Roboto, sans-serif;
  font-weight: 400;
  font-size: 0.9375rem;
  letter-spacing: 0.06em;
  line-height: 1.2;
  color: var(--on-dark-muted);
}

/* ---- The three tiles hold their row on a narrowed desktop window ---------
   Below the 860.02px desktop step the grid falls back to the base rule's
   single column, which is right on a phone and wrong on a desktop browser
   dragged down to half a screen -- three full-width 3:4 tiles stacked is a
   very long scroll for what is meant to be read as one row of doors.

   So the row is held from 600px up and the tiles are shrunk to fit rather
   than being allowed to stack. 600px is the floor because that is about
   where a desktop window stops and a real phone begins: a half-screen
   Chrome window on a 1280 display is 640px, on a 1440 it is 720px, and a
   phone in portrait is 320-430px. Under 600px the base rule still stacks
   them, so the phone keeps the layout it was written for.

   The type is what has to give. At 600px a tile is about 172px wide and the
   name at its full 1.5rem does not fit on one line inside 22px of padding,
   so both scale with the window across this band and land back on their
   base values at the top of it. The vw terms are read against the window
   rather than the tile, which is close enough: the container is the window
   less two gutters the whole way across this range. */
@media (max-width: 860.01px) {
  .category-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  /* The plate and its curve, which the desktop block sets and which the tiles
     lost under 860: the card went transparent and square, so three dark
     photographs sat on white with nothing holding them. */
  .home-featured .category-card {
    background: #141414;
    border-radius: var(--band-radius);
    overflow: hidden;
  }
  /* The label ramp is tuned to LAND on the desktop values at the top of this
     range rather than to merely stay legible: 3.15vw is 27.1px at 860, which
     the 1.5rem ceiling trims to the 27px the full-width rule sets, so there
     is no step at the breakpoint. Below that it scales with the tile, because
     the tile is a third of the window and 27px stops fitting inside one at
     about 830px -- see the note in the block at the foot of this file. */
  .category-label { padding: clamp(9px, 2.6vw, 22px); }
  .category-label h3 {
    font-size: clamp(0.6875rem, 3.15vw, 1.5rem);
    letter-spacing: 0.02em;
  }
  .category-count {
    margin-top: 2px;
    font-size: clamp(0.5625rem, 1.96vw, 0.9375rem);
    letter-spacing: 0.06em;
  }
}

/* Everything else here is desktop-only, matching the About Us / Cut & Sewn
   band above it: the phone keeps the dark Featured Collection it has always
   had, and none of these rules are written where it can see them. */
@media (min-width: 860.02px) {
  /* Beats --featured-ground, which this section shares with the hero's band
     so the two move together. That token stays where it is -- the hero still
     needs it -- and this overrides only here. */
  /* The band under the hero photo, closed. .hero-photo and .hero-overlay
     both stop --hero-band short of the section's bottom edge, and what shows
     in that gap is the hero's own background -- 48px of it. The point of it
     was that the gap be filled with the Featured Collection's ground, so the
     section read as running up underneath the picture instead of the photo
     ending on a bar of its own.

     That argument only holds while the two grounds are dark and the seam is
     invisible. Against white the band was a white bar with a hairline over
     it -- not the section running under the photo, just a strip. Zero, so
     the photo runs to the section's edge and there is no band to colour.

     Set on --hero-band rather than by moving the two insets, because they
     have to stop at the SAME place and the variable is what guarantees it.
     The colour override that was here has gone with it: with no gap left
     there is nothing of .hero's background on screen to set. */
  /* ---- The hero's white frame -------------------------------------------
     .hero-photo and .hero-overlay are absolutely positioned inside the hero
     and were pinned to its edges on three sides, with --hero-band holding
     them off the fourth. Insetting all four by the same amount and painting
     the hero itself white turns that inset into a border: what shows around
     the photo IS the section's own background.

     A real border on .hero-photo would not do it -- that element clips the
     photograph with overflow: hidden, and a border sits outside the padding
     box, so the picture would run underneath it rather than stopping at it.

     It was --grid-gap while it was a frame -- exactly the space between two
     product cards in the grid below, so the two read as one measure. Only
     the bottom side survives, and it is sized off the Shop the Store button
     now instead; see --hero-band on .hero below.

     --hero-band was 48px and covered the bottom edge alone. Its old job --
     leaving a strip of the Featured Collection's ground showing under the
     photo, so the section read as running up beneath it -- ended when that
     section went white. It is the bottom side of the frame now, which is why
     it is set to the same value as the other three rather than removed. */
  /* The header comes out of the hero and sits above it.

     Every other page has it in flow already; the home page alone pulls it to
     position: absolute so it floats over the photograph, with top set to
     --promo-h so it starts under the promo bar rather than at the top of the
     document. That is what put the nav on top of the picture, and it is also
     what left the frame's top edge with nothing to show -- the header was
     covering the strip the other three sides run through.

     Back to relative, which is what .site-header itself sets, so it takes
     its own space in the flow and the hero begins underneath it. Only the
     positioning is undone: the bar keeps its own --chrome-ground and its
     hairline, so it reads as the header it is on every other page.

     The hero's height has to answer for that. It was one viewport less the
     promo bar, on the assumption that the header was floating and cost
     nothing; now that the header is in flow, its height comes out of the
     same sum or the three of them together run past the fold. --header-h is
     the same token the cart and search sheets measure their top from, so
     there is one number for the bar's height and not two. */
  .page-home .site-header {
    position: relative;
    top: auto;
  }
  .hero {
    /* The strip under the photo. .hero-photo and .hero-overlay stop
       --hero-band short of this section's bottom edge, and this is what
       shows in the gap -- the band is the hero's own background, not an
       element of its own. */
    background: #141414;
    /* As deep as the Shop the Store button standing above it, so the bar and
       the button read as the same weight of thing rather than the bar being
       a hairline the button sits on.

       Written as the button's own box arithmetic rather than as a measured
       pixel value: its label is 1.25rem at line-height 1.15, and its padding
       is 9px over and 11px under, with no border. 22.5 * 1.15 + 20 =
       45.88px. Every term is a literal from the .hero .hero-actions
       .story-cta block up the file -- change the padding or the type size
       there and this has to be brought with it, which the calc at least
       makes legible. It has been brought with it once: the button was
       1.125rem on 8/10 and this read `1.125rem * 1.15 + 18`, for 41.29px.

       It no longer follows --grid-gap. That link existed while the band was
       the fourth side of a frame and had to match the space between two
       product cards; the band is its own element of the design now, and the
       other three sides of the frame are gone. */
    /* 58px, a flat value. This was calc(1.25rem * 1.15 + 20px) -- about 46px
       -- and every term in it was read off the Shop The Store button, because
       for a while the band's job was to be exactly as deep as that button so
       the two read as one row. The button is in the hero copy now and the
       band holds the carousel controls, so that arithmetic was describing a
       relationship that no longer exists; keeping it would have been a
       calculation nobody could check.

       The number is chosen against what is actually in there now: 40px of
       control with 9px of air over and under it. */
    --hero-band: 58px;
    min-height: calc(100vh - var(--promo-h) - var(--header-h));
    /* Centres the copy between the header above and the bar below, rather
       than in the section as a whole.

       .hero-social-bar is absolutely positioned along the bottom, so it is
       out of flow and align-items: center was centring .hero-content in the
       FULL height -- which put its middle --hero-band/2 below the middle of
       the space it actually occupies, about 21px low. Padding is the honest
       way to say it: the content box ends where the bar begins, and centring
       inside that box is centring between the two edges the eye reads.

       Safe on the photo layers. .hero-photo and .hero-overlay are absolutely
       positioned and resolve against the PADDING box, whose edges padding
       does not move -- they still start at the section's top and stop
       --hero-band short of its bottom, exactly as before. And with
       border-box sizing the min-height above is unchanged too. */
    padding-bottom: var(--hero-band);
  }
  /* The scroll zoom. --hero-zoom is written on .hero-photo by
     initHeroScrollZoom in main.js and runs 1 to 1.08 as the hero passes the
     top of the window.

     The `scale` property, not transform. The entrance already owns transform
     here -- scale(1.28) settling to scale(1) over 2.7s, with a transition on
     it -- and driving the same property from scroll would restart that
     interpolation on every frame. `scale` is a separate slot that multiplies
     into the same matrix, so the two compose and neither has to know about
     the other.

     No transition on it, deliberately: the value is already a function of
     scroll position, so easing it would put the photograph behind the page.

     Desktop only, which is why the rule is here and not beside the entrance.
     The JS writes the variable at every width; below 860px nothing reads it,
     and the phone's hero is unchanged. The fallback in the var() is what
     holds the photo at its own size before the first scroll event and if the
     script never runs at all.

     Written on .hero-slide-img rather than .hero-photo::before, which no
     longer exists: the picture moved into the slide track. --hero-zoom is
     still set on .hero-photo by the script and inherits down to every
     slide from there, so both take the same zoom and the one on screen is
     the one you watch take it. On the slide rather than on the track,
     which is what keeps the origin under the visible picture: the track is
     two slides wide and its centre is the seam between them. */
  .hero-slide-img { scale: var(--hero-zoom, 1); }

  /* ---- The chrome holds over the hero, then leaves ----------------------
     The bar and the header stay on screen for the length of the hero and
     slide off together once it has passed.

     Two sticky elements rather than one, because they are two siblings and
     there is nothing wrapping them -- wrapping would mean the same edit in
     all eleven pages. The bar sticks at 0 and the header at --promo-h, which
     is the bar's own height, so they hold their existing relationship
     instead of the header climbing over the bar. Their z-indexes are already
     101 and 100 from the base rules; nothing on the page outranks them.

     Sticky, not fixed. Fixed takes them out of flow, and the hero's height
     is `100vh minus the two of them` -- it would immediately be that much
     too tall and the band at its foot would fall below the fold. Sticky
     leaves the layout at rest exactly as it is and only changes what happens
     once you scroll.

     The slide is a transform, so it moves what is painted without moving
     what is measured: the hero's rect is the same before and after, which is
     what keeps initHeroChrome from oscillating around its own threshold.

     --chrome-h is written by that function from the elements' real heights,
     with the two tokens as the fallback for the frame before it runs and for
     the case where the script never does. Measuring rather than trusting the
     tokens matters here: --header-h is used to size the hero, so if it ever
     drifts from the header's actual height the hero is merely a little tall,
     while this rule would leave a strip of chrome stuck on screen.

     Scoped to .page-home. The trigger is the bottom of the hero and no other
     page has one -- on those the header stays where it has always been. */
  /* The header alone. The promo bar was sticky here too and held above it as
     a pair; it scrolls away with the page now and only the header stays, so
     the header sticks at 0 rather than at --promo-h -- there is nothing
     above it to leave room for once the bar has gone.

     The bar is not otherwise touched: it keeps its place at the top of the
     document and its fill, and it comes back when you scroll back up to it,
     because it is simply in flow again. */
  .page-home .site-header { position: sticky; top: 0; }
  .page-home .site-header {
    transition: transform 0.45s cubic-bezier(0.4, 0, 0.2, 1);
  }
  /* ---- The header keeps its fill ---------------------------------------
     It was translucent while scrolled for a while -- 0, then 55%, 78% and
     65% of --chrome-ground, with a 14px backdrop blur under it -- and the
     photograph read through the bar. All of it is gone; the header is the
     solid --chrome-ground of its base rule at every scroll position, on
     every page.

     Two things went with it and are worth knowing were only ever there to
     serve it. The header carried its fill on a ::before rather than on
     itself, because an element with backdrop-filter becomes a backdrop root
     and the Store menu inside it could then blur nothing; with no filter
     anywhere there is no root to escape and the base rule paints the bar
     directly again. And the nav's contrast over the photograph -- 1.9:1 at
     no fill, about 5.8:1 at 65% -- stops being a question at all: the type
     is back on an opaque near-black bar, where it measures what it does
     everywhere else on the site.

     .is-scrolled stays. It still drives the condense below; it simply no
     longer touches the colour. */
  /* ---- and it condenses with the fill -----------------------------------
     82px down to 64px, which is one --grid-gap shorter -- a measure the page
     already uses rather than a number picked to look right. The skull comes
     down with it, 46px to 38px, so the bar condenses rather than merely
     cropping: a logo that stays put in a shorter bar reads as a bar that has
     been trimmed.

     Set as --header-h ON THE HEADER, not in :root. .header-inner takes its
     height from that token, so overriding it here is enough -- and keeping
     it off :root is the point: the hero's own height is
     `100vh - --promo-h - --header-h`, and moving the root value would
     resize the hero every time you scrolled a pixel.

     This does reflow. The header is sticky, so it holds its place in flow,
     and 18px off its height lifts everything below it by 18px. Two things
     make that acceptable rather than a jump: it is transitioned, so the page
     eases up rather than snapping, and it fires on `is-scrolled` -- the
     first pixel of movement, when the page is already starting to move and
     the fill is fading in on the same beat. Later would be worse, not
     better.

     Nothing measured off the header goes stale for it. initHeroChrome reads
     header.offsetHeight live on every frame, so the slide-away threshold and
     --chrome-h both follow the new height; and the region of the document
     above the hero shrinks by exactly the same 18px, so the dark strip that
     --chrome-h sizes still covers precisely that region. */
  /* The same split, and it matters more here than on the fill: this is the
     one that moves the page. Growing back pushes 18px of content down, so it
     gets the long decelerating curve and finishes gently; condensing pulls
     it up while you are already scrolling away and can be brisk. */
  body.page-home .header-inner,
  body.page-home .header-inner .logo-skull {
    transition: height 0.55s cubic-bezier(0.22, 1, 0.36, 1);
  }
  body.page-home.is-scrolled .header-inner,
  body.page-home.is-scrolled .header-inner .logo-skull {
    transition: height 0.3s ease;
  }
  body.page-home.is-scrolled .site-header { --header-h: 76px; }
  body.page-home.is-scrolled .header-inner .logo-skull { height: 66px; }

  /* ---- The Store menu hangs off the header, not off the label -----------
     It was top: 100% -- the bottom of the trigger span -- so it started
     partway UP the header and its first 18px were behind the bar it was
     supposed to be hanging from. Now its top edge is the header's bottom
     edge and the two meet on a line.

     The arithmetic, and it is exact rather than a fitted number: .main-nav is
     centred in .header-inner, so .nav-dropdown's own vertical centre is the
     header's vertical centre. From the dropdown's top that centre is 50%,
     and the header's bottom is half a header below it. --header-h is read
     off the header by inheritance, so when the bar condenses to 64px the
     menu follows it up without anything being told to.

     Not by stretching .main-nav to the header's height, which is the other
     way to get here and would have been wrong: the nav underlines are
     positioned `bottom: 6px` against each item's own box, so items grown to
     82px would put every underline half a header below its label. */
  .main-nav .nav-dropdown-menu { top: calc(50% + var(--header-h) / 2); }
  /* The invisible bridge that keeps :hover alive across the gap, resized to
     the gap it now has to cover: half a header, less half the trigger. It
     was a flat 14px, which was enough when the menu started at the label. */
  .main-nav .nav-dropdown::after { height: calc(var(--header-h) / 2 - 50%); }

  /* The menu had the header's glass too, on the same class and the same
     values, so the panel and the bar it hangs from would not disagree. Both
     are solid --chrome-ground again -- which its base rule already paints,
     so nothing is needed here. The join above is what connects them now. */
  @media (prefers-reduced-motion: reduce) {
    body.page-home .header-inner,
    body.page-home .header-inner .logo-skull { transition: none; }
  }
  /* Two sets of timings, one per direction, which is what a transition
     declared on each STATE gets you: the rule on the resting state governs
     the return, the rule on .is-scrolled governs the departure.

     Coming back is the slower and softer of the two -- 0.55s on a decelerating
     curve against 0.3s on a linear-ish one. Returning to the top is a move
     the reader is making deliberately and watching the whole way, and it
     lands with the page stopping under it; leaving is incidental, happens
     while everything else is already in motion, and wants to be out of the
     way. Equal timings made the return feel like a snap even though it was
     the same 0.35s that read as fine on the way out. */
  .page-home .site-header {
    transition: transform 0.45s cubic-bezier(0.4, 0, 0.2, 1);
  }
  /* -100%, its own height, which is all that is needed now that the header
     travels alone. It was --chrome-h -- the bar's height plus the header's
     -- because the two moved together and each had to clear the other. */
  body.page-home.is-chrome-away .site-header {
    transform: translateY(-100%);
  }
  /* The leaving is motion; the holding is not. Under the preference the
     chrome still sticks and still goes, it simply does not travel. */
  @media (prefers-reduced-motion: reduce) {
    .page-home .site-header { transition: none; }
  }
  /* ---- The social row in the band ---------------------------------------
     Laid over the band rather than being it. The band is .hero's background
     showing through where the photo and the overlay stop, so it is a gap and
     not an element -- this bar is pinned to the same edge and given the same
     height, which is what keeps the two in step through any change to
     --hero-band.

     .hero is a flex row that centres .hero-content; an extra child in flow
     would be centred alongside it. Absolute, so it is out of that flow
     entirely and the headline stays where it is. No fill of its own: the
     band's colour is already underneath it. */
  .hero-social-bar {
    display: block;
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: var(--hero-band);
  }
  /* Three things in the bar now, so it is a grid rather than a flex row with
     an auto margin pushing one end over: the icons at the left gutter, the
     carousel controls in the middle, the flag at the right gutter.

     1fr auto 1fr, which is the header's own arrangement one section down the
     page and for the same reason: the centre column is sized by what is in
     it and the two 1fr columns split what is left, so the controls are
     centred against the BAR rather than against whichever of the two ends is
     wider. .container's gutter is symmetric, so bar-centred is also
     window-centred.

     grid-row: 1 on all three, named rather than left to auto-placement. The
     header learnt this the hard way: place an item in column 2 and then ask
     for column 1, and sparse packing starts a new row instead of going back
     -- which inside a fixed-height bar means the row simply falls out of the
     bottom of it. */
  .hero-social-bar .container {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    height: 100%;
  }
  .hero-social-bar .hero-social { grid-row: 1; grid-column: 1; justify-self: start; }
  .hero-social-bar .hero-carousel-nav { grid-row: 1; grid-column: 2; justify-self: center; }
  .hero-social-bar .hero-band-flag { grid-row: 1; grid-column: 3; justify-self: end; }
  /* margin-left: auto rather than justify-content: space-between on the row,
     so each end is pinned to its own gutter and neither moves if a third
     thing is ever put in the band between them. The flag carried this while
     it was the one on the right; the two swapped ends at the asking, and the
     margin swapped with them. */
    /* No auto margin any more -- justify-self on the grid above places
     both ends, and a margin as well would fight it. */
.hero-social { display: flex; gap: 20px; }
  /* The bordered 40px square goes. Every .icon-btn on the site is a boxed
     button -- a bordered tile that reads as a control -- and in a 41px band
     four of them in a row would be a strip of boxes inside a strip.

     Sizing the box to the GLYPH rather than around it is what puts the first
     icon on the gutter: an 18px mark centred in a 40px tile starts 11px in
     from the tile's edge, which is 11px out of alignment with the type above
     it. At the glyph's own width there is nothing to be out by. The height
     stays the full band, so the click target is the whole depth of the bar
     even though only 18px of it is drawn. */
  .hero-social .icon-btn {
    width: 18px;
    height: 100%;
    border: none;
    border-radius: 0;
    background: transparent;
    color: var(--on-dark-muted);
    transition: color 0.2s ease;
  }
  /* The same pair as the header above -- gray at rest, white under the
     cursor -- so the two rows of marks at either end of the hero behave
     alike. It was a dim on opacity for a while, back when the header's icons
     changed nothing and this row needed an answer of its own that did not
     contradict them. The header answers in colour now, so these do too.

     Pointer devices only, like every other hover in this file: a tap leaves
     :hover stuck on whatever was touched until something else is.

     Note for whoever reads the history: the @media line here was written
     without its @, which makes the condition parse as a selector -- so this
     hover has been dead since it went in, not merely subtle. */
  @media (hover: hover) and (pointer: fine) {
    .hero-social .icon-btn:hover { color: var(--on-dark); }
  }
  .hero-social .icon-btn svg { width: 18px; height: 18px; }
  /* ---- The state flag, right end of the band ---------------------------
     All that is left of the note. The MADE IN ARIZONA line it sat beside is
     gone, so the flag stands alone and the em sizing that tied it to that
     label's cap height went with it -- there is no label to measure against
     any more.

     22px tall, 33px wide -- the 3:2 the flag is drawn at. A step above the
     18px social marks at the other end rather than level with them, which is
     the right relation: those are four repeating marks and this is one, and
     a lone object matched exactly to a row of them reads as the smallest
     thing on the bar. It still clears the band's 41px by a comfortable
     margin top and bottom.

     No margin of its own any more: it is first in the row and sits on the
     container's left edge, which is the page gutter. The auto margin that
     used to push it to the far end is on .hero-social now. */
  .hero-band-flag {
    width: 33px;
    height: 22px;
    flex: none;
  }
  /* The frame is one band along the bottom, and there is no rule drawing it
     -- .hero-photo and .hero-overlay are already pinned to top: 0, left: 0,
     right: 0 by their own rules up the file, so three sides of the old inset
     are simply the override being gone. --hero-band on .hero above holds
     both short of the section's bottom edge and the section's background
     fills the gap. That is the arrangement the hero has always had; only the
     colour and the size of it have changed.

     ---- Why the photo now carries a fill of its own ----
     The hero photo fades in on opacity, and at 0 what showed through was
     .hero's background -- which is how it came up out of white without a
     colour being written anywhere. That worked only while the band and the
     photo's backdrop were the same thing. They are not any more: the band is
     #141414 and the entrance still has to be white.

     So the two are separated. .hero-photo covers exactly the photo's area --
     top: 0 to bottom: --hero-band, which is the strip's top edge -- so a
     fill here is white behind the picture and nowhere else. The band below
     it is .hero's, untouched. */
  /* No fill here. .hero-photo carried white for a while so the photo's
     opacity fade came up out of white while the strip below it stayed dark;
     the white read as a flash across the whole hero on load, which is a lot
     of screen to hand to an entrance. Without it, opacity 0 shows .hero's
     own #141414 -- so the photo fades up out of the near-black it is
     bracketed by, and the shrink runs as it always has. */
  /* The hairline the overlay drew along the bottom of the photo. It marked
     where the picture stopped against the band below it; the white frame is
     that boundary now, and a dark rule inside a white border reads as a
     mistake rather than an edge. */
  .hero-overlay { border-bottom: none; }
  /* ---- The seams down the white run -------------------------------------
     Featured Collection to About Us, About Us to Cut & Sewn, and Cut & Sewn
     to the Free Membership band: all three set to --page-gutter, the same
     inset the container holds the content off the sides of the window with.

     A seam between two sections is the SUM of what each puts against it, so
     these are written as one side carrying the whole value and the other
     carrying nothing, rather than half each. Halves are the harder version
     to reason about later: it is not obvious from either rule what the gap
     actually comes to, and changing one of them silently halves the change.

     They were 80px (40 + 40), 96px and 72px -- three different numbers for
     three seams in a run that is meant to read as one continuous white
     surface. */
  .section.home-featured {
    /* --page-gutter, down one rung from the gutter plus --grid-gap this
       carried when the grid took over as the section that opens the page.

       That sum was reasoning about the hero's black bar: a solid edge needs
       more clearance than an edge that fades, so the one seam on the page
       with a bar on the other side of it got both measures instead of one.
       The argument holds; the amount was simply more than it needed -- 82px
       against the 36px every other seam here takes. This is 64px, still the
       largest gap on the page and still a measure the page already uses,
       and the step between the two is exactly one --grid-gap.

       The bottom is twice --grid-gap, the seam between two sections, which
       About Us below reads the space above itself off. */
    padding-top: var(--page-gutter);
    padding-bottom: calc(var(--grid-gap) * 2);
    background: var(--page-ground);
    /* Kept explicit even though the base rule no longer draws one. It was a
       hairline of --border (#333332) meant to separate two dark bands; with
       everything on this page white it is a dark line with nothing either
       side of it to divide. */
    border-bottom: none;
  }
  /* ---- The category grid -------------------------------------------------
     Three across, one per tile, at every desktop width.

     It was four across past 1025px and a 2x2 below that, which is what four
     tiles want. Three want neither: a 2x2 leaves an orphan on the second row,
     and the 1025px step would have split them 3-up and 3-up for no reason.
     One column count for the whole desktop range is the simpler answer, and
     it is also the wider tile -- each one takes a third of the container
     rather than a quarter, which is the increase that came with dropping the
     fourth.

     Everything below the grid itself is set on the base rules outside this
     block, not here. The category tiles are new content rather than a light
     treatment applied to something the phone already had, so they get one
     look at every width and only the column count is a desktop decision. */
  .category-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  /* The floor goes on the card, not the grid. On the grid it depended on the
     row stretching to fill a container taller than its content, and on the
     plate's percentage height resolving against that -- two things that only
     work if the height is definite the whole way down. On the card it simply
     is. */
  /* Same treatment as the story bands, and for the same reason: the plate
     and the panel under it are welded edge to edge, so the radius goes on
     the card that holds both. overflow: hidden is what makes the plate's top
     corners follow it -- the plate is a background on a child and would
     otherwise square them off inside the rounded card. */
  .category-card {
    min-height: var(--band-h);
    border-radius: var(--band-radius);
    overflow: hidden;
  }
  /* The plate is the whole card now, so it takes the card's height rather
     than a ratio of its own -- the 3 / 4 on the base rule is what sets that
     height, and there is nothing under it to divide with.

     flex: 1 with a zeroed minimum, so if the card is ever taller than the
     ratio asks for -- the min-height above is the only thing that could do
     it -- the picture fills the difference instead of leaving a strip.

     At the 1500px container the card comes out 593px, against --band-h's
     544px for Get In Touch below and --band-h-story's 653px for the two
     stories. Same order down the page as before; it was 584px when the
     square plate and the copy panel were adding up to it. */
  .category-media {
    flex: 1;
    min-height: 0;
  }
}


/* ============================================================
   Featured Collection: the label, and the Coming Soon plates
   ============================================================
   Desktop only, like everything else in this section's white treatment. */
@media (min-width: 860.02px) {
  /* The panel does not change on hover. It carried a second gradient layer on
     a ::before for a while -- first a full inversion to near-black, then an
     8% darkening -- crossfaded in, because a background-image cannot be
     transitioned and swapping the gradient outright cuts. Both are gone: the
     copy going and the label arriving is the whole of the hover, and the
     panel is the fixed thing they happen on.

     The z-index: 1 those two content layers needed has gone with it. That
     existed only to keep them above a positioned pseudo-element, which would
     otherwise have painted over static children whatever the source order --
     with no pseudo-element there is nothing to sit above. */

  /* ---- The Coming Soon plates -------------------------------------------
     Every product photo on the site is one of these at the moment -- a div,
     not an <img>, which is why neither the site's card entrance nor its
     hover zoom reaches them. Both of those are written for images: the
     entrance sits on .product-media-link and the zoom on the img inside it.

     Entrance: fade up out of the page, the same treatment the two story
     photos take and for the same reason. Nothing paints a colour here --
     .product-media-link has no fill, so at opacity 0 what shows through is
     the section's own white. The scale-and-lift-out-of-black that
     .product-media-link carries is turned off with it; a plate lifting out
     of black on a white page is an entrance written for a different site.

     Hover: 1.06, a good deal less than the 1.08 the real photos take. A
     photograph can be pushed because there is detail to reveal; a flat panel
     with two words on it only gets bigger, and the words grow with it. */
  /* The ground the plate fades up out of, on the CARD rather than on the
     fading box itself.

     This was on .category-media first and did nothing at all, which is worth
     writing down because it looks correct: opacity applies to an element AND
     everything it paints, its own background included. A box at opacity 0
     does not show its fill through -- it shows whatever is behind the box.
     So the plate went on fading up out of the white section exactly as
     before, with a black fill that was never once visible.

     The other three bands avoid this by accident of structure: the hero and
     Get In Touch fade a pseudo-element over a painted parent, and the story
     photos fade an <img> inside a painted frame. In every one of those the
     ground sits on something that is not itself fading. Here the fading box
     was the only box, so the ground moves up a level to the card.

     The plate is the whole card now -- there is no panel under it -- so this
     ground is seen through the picture's own box for the length of its fade
     in, and nowhere else. */
  .home-featured .category-card { background: #141414; }
  .home-featured .category-media {
    opacity: 0;
    transition: opacity 1.1s ease;
  }
  .home-featured .category-media.is-visible { opacity: 1; }
  .home-featured .product-media-soon {
    transition: transform 0.4s cubic-bezier(0.33, 1, 0.68, 1);
    /* A near-black plate -- darker, now, than the flat #2a2a2a the shared
       rule paints. That value is a step lighter than --surface and reads as
       a filled panel on the dark pages this component was drawn for; in
       this band it sat between the
       #141414 Quick Add bar sliding over it and the --panel-ground panel
       under it, close enough to both that the three ran together.

       The file behind it has moved a long way. It averaged #a1a1a1 first --
       near enough to the white page that the plate read as a gap in it --
       then #545454, then #10100f, and now #181818 -- a shade off black. Each step
       has been away from the page and toward the #141414 bar that slides
       over it; at this value the plate is darker than the bar rather than
       lighter, and the two meet as one dark mass rather than as a control
       over a surface. That is a look, not a fault, but it is worth knowing
       it is what changed.

       Scoped to .home-featured, so the store page, the cart and the popup
       keep the dark plate they are built around.

       A topographic contour texture rather than a flat fill -- the same idea
       as --topo-ground elsewhere on the site, and now at much the same
       value: topo-texture-v7.jpg, the dark bands' own texture, measures 11
       to 15 out of 255, and this file averages 16. The distinction that
       justified a separate file has narrowed to almost nothing -- if these
       two ever want to be one asset, this is the moment they could be.

       1630x1630 and square, dropped into a square plate, so `cover` scales
       it without cropping either axis. The flat #181818 stays underneath as
       the fallback -- the file's own average, re-measured each time the file
       changes, so a 404 leaves the plate the grey it was rather than a
       transparent hole with a white label on it.

       The label stays white -- --offwhite from the shared rule, said again
       here so the pairing is visible at the point the ground is set. On the
       light plate it was the low-contrast option and was chosen anyway, at
       about 2.4:1; on this ground it measures about 16.7:1. The intent has
       not changed -- COMING SOON stands in for a photograph, and a label
       sitting in the texture is closer to what will replace it than hard
       type would be -- but keeping it white is no longer a compromise. */
    background: #181818 url('../assets/images/coming-soon-topo.jpg') center / cover no-repeat;
    color: var(--offwhite);
  }
  /* One hover, keyed to the whole tile. The card is a single anchor now, so
     the plate and the panel are both inside the hovered element and there is
     no crossing over to arrange -- point anywhere on the tile and the plate
     grows.

     What this replaces: two rules and a :has(). A product card had a Quick
     Add bar sitting between the photo and the panel, which meant the photo
     had to lose :hover when the pointer reached the bar, and the panel had
     to reach BACKWARDS to the photo above it -- something CSS can only do
     from a common ancestor, with :has() on the card. None of that survives
     the tile being one link with nothing interactive inside it. */
  .home-featured .category-card:hover .product-media-soon {
    transform: scale(1.06);
  }

  @media (prefers-reduced-motion: reduce) {
    .home-featured .category-media,
    .home-featured .product-media-soon { transition: none; }
    .home-featured .category-card:hover .product-media-soon { transform: none; }
  }
}

/* ============================================================
   The header: one bar, all eleven pages
   ============================================================
   This block was the home page's header alone -- dark where the other ten
   were darker, white type where they were gray, unboxed icons where they had
   tiles. It is every page's header now, and the merge went in both
   directions rather than the home page simply winning:

     from the home page   the near-black fill, no rule underneath, no boxes
                          round the icons, glyphs at 28px, 20px apart, and
                          Barlow Condensed on the labels
     from the other ten   the gray nav that lifts to white under the cursor
                          and on the page you are on, and its underline

   The two colour changes are not here. --chrome-ground moved from #101010 to
   #141414 and .site-header dropped its border, both up the file, because the
   header shares that token with its dropdown and with the phone's translucent
   variant -- setting it in one place is what keeps the three in step.

   What is left in this block is desktop-only, which is why the sizing and the
   face are here and the colours are not: the phone keeps the icon tiles and
   the display face it has always had until that side is worked on. The bar's
   colour reaches it either way, since that is set on the token.

   Still one .page-home rule elsewhere, and it is not about looks: the home
   page pulls the header out of the flow so the hero can start beneath it.
   See .page-home .site-header near the top of the file. */
@media (min-width: 860.02px) {
  /* The fill and the missing rule are on .site-header itself now, and so is
     the wordmark's colour -- this bar is every page's bar. What was here:
     background #141414, border-bottom none, and a flat white on the wordmark
     over the --on-dark the stack already gave it. The first two moved up the
     file with --chrome-ground; the third was a distinction of a few levels
     that only existed to separate this header from the other ten, and there
     is nothing to separate it from now. */
  /* The skull is light artwork already -- it was drawn to sit on the dark
     bar. The filter that was here inverted it to near-black for the light
     header and is simply gone; .logo .logo-skull sets filter: none for its
     own reasons and that is now the right answer again. */

  /* The nav's flat white is gone. It held one colour at rest, hovered and
     active, with the underline doing all the answering -- and it had to name
     :hover and .active explicitly to beat the shared rules up the file.

     Those shared rules are the behaviour again: --on-dark-muted at rest,
     --on-dark under the cursor and on the page you are on. Removing the
     override is the whole change; nothing here replaces it. The underline
     comes back with them, at --offwhite rather than currentColor, which is
     the same white the lit label is. */
  /* No box round them, and the glyph carries the whole state. The outline
     came off when the bar was light and stays off now: the icons read on
     this ground too, and three boxed squares beside four unboxed words was
     the only chrome left in the bar.

     Gray at rest, white under the cursor -- the same two values as the nav
     labels beside them, so the whole bar answers the pointer one way. They
     held a flat white for a while, on the argument that the labels did too;
     the labels do not any more.

     The base .icon-btn:hover lifts border-color, which is a no-op here now
     that there is no border to lift, so nothing has to be turned off. */
  .site-header .icon-btn {
    color: var(--on-dark-muted);
    border: none;
    transition: color 0.2s ease;
    /* Sized to the glyph. With the border gone the 40px tile is invisible but
       still spent: a mark centred in it carries dead box on each side, so
       three of them 18px apart read as 40px apart. At the glyph's own width
       the gap below is the gap you see.

       The height is deliberately left at 40px. .cart-count is absolutely
       positioned against this box (top: -7px, right: -7px), so changing the
       height would lift the badge off the cart; narrowing only pulls it in
       to sit 7px off the glyph rather than 7px off an invisible tile edge,
       which is where it looked like it belonged anyway.

       It also squares the two ends of the bar: the cart glyph finishes on
       the gutter the wordmark starts on -- and on the home page, on the same
       gutter the Instagram mark in the band under the hero starts on.

       This value has to move with the svg size below it, every time. They
       are the same measurement written twice -- the box is the mark -- and
       a glyph wider than its box overflows a centred box in both
       directions, which shows up as the three marks creeping toward each
       other rather than as anything obviously broken. */
    width: 36px;
  }
  /* 36px, by way of 28, 34 and 40, and well up from the 18px .icon-btn carries
     everywhere else on the site. That 18 was drawn for a 40px bordered tile,
     where the box did the work of being a button and the mark inside it only
     had to be legible. With the border gone the mark IS the button, and 18px
     of it in a 96px bar is a small thing floating in a tall space.

     40 is also the ceiling here without another change: .icon-btn's height
     stays 40px because .cart-count hangs off it, so a bigger glyph would run
     past the box vertically as well. The box above is matched to it so the
     spacing stays glyph to glyph. */
  /* Pointer devices only, like every other hover in this file: a tap leaves
     :hover stuck on whatever was touched until something else is, so on a
     phone these would light up and stay lit. */
  @media (hover: hover) and (pointer: fine) {
    .site-header .icon-btn:hover { color: var(--on-dark); }
  }
  .site-header .icon-btn svg { width: 36px; height: 36px; }
  /* 20px, the same as .hero-social. Both are now glyph-to-glyph rather than
     tile-to-tile, so the one number means the same thing in both places. */
  .site-header .header-actions { gap: 20px; }
  /* The skull, down from 58px. That height was set when the bar's furniture
     was smaller -- 18px glyphs inside 40px bordered tiles -- and the mark had
     to carry the left end on its own. The icons are 28px and unboxed now, so
     it does not, and at 58px in an 82px bar it was filling nearly the whole
     height while everything else sat well inside it.

     Scoped to .header-inner, which is what keeps the footer's 64px copy out
     of it. Written here rather than by editing .logo-skull up the file
     because that rule is the mark's own size wherever it appears; this is the
     header's opinion about it.

     It also beats the 58px in the narrow-desktop block at 1024px and under --
     same specificity, later in the file -- so the bar keeps one mark across
     every desktop width instead of growing one as the window narrows.

     86px, up from 46 by way of 60, 68 and 80. 46 was set for the skull, which
     sat beside a wordmark and had to leave room for it; the badge is the
     whole logo and has the middle third of the bar to itself.

     86 in a 96px bar leaves 5px of air over and under, and that is the end of
     the road at this bar depth -- a disc closer to the edges than that stops
     reading as a big mark and starts reading as a mark too big for its bar.
     Going further means --header-h going with it. Its condensed twin further
     down moved too, and holds the same 5px inside the 76px scrolled bar. */
  .header-inner .logo-skull { height: 86px; }
  /* ---- The Store menu ---------------------------------------------------
     Nothing here for it any more. It reads --chrome-ground, the
     header's own token, which is what this block used to override -- and that
     token is #141414 now, so the panel follows the bar without being told to.
     Its items go back to the shared muted-to-white pair as well, matching the
     labels in the bar above them. */
  /* The cart badge is left alone: --title-red with white on it is the site's
     one alert colour and it reads on either ground. */
}


/* ============================================================
   Home page: the header and the hero, in the band's face
   ============================================================
   Barlow Condensed, matching the About Us / Cut & Sewn band and the product
   panels -- so the top of the page and the middle of it are set in one
   family rather than the display face meeting a condensed one at the fold.

   The LOGO is deliberately not in here. The wordmark is the mark, not text
   that happens to be at the top of the page, and it keeps --font-display.

   Desktop and .page-home only, for the same reason the light bar is: the
   header is shared by eleven pages and the other ten still run the display
   face on a dark ground. */
@media (min-width: 860.02px) {

  /* ---- The nav ----------------------------------------------------------
     Barlow Condensed at 500, still uppercased, at 0.09em. The labels keep
     the shape the .btn type ramp gave them -- caps at the bar's own size --
     and only the family and the spacing come from this page's face.

     0.09em is the number that had to move with the family. The ramp's own
     0.2em is set for Koulen, a narrow display face where that much air is
     what keeps caps apart; Barlow at 0.2em pulls four short words into four
     separate runs of letters. This is the same relationship the file uses
     for small caps elsewhere.

     It went to 400 sentence case for a moment, matching the band's body
     copy. Caps at 500 is what it is back to: a navigation bar is labels, and
     labels are not paragraphs however closely the two are related. */
  .site-header .main-nav a,
  .site-header .main-nav .nav-dropdown-trigger {
    font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
                 'Segoe UI', Roboto, sans-serif;
    font-weight: 500;
    letter-spacing: 0.09em;
  }
  /* The menu's own items are set apart from the bar's labels on purpose --
     sentence case, body face, normal tracking -- so a submenu reads as a
     list rather than as more nav. That contrast is kept; only the family
     moves. */
  .nav-dropdown-menu a {
    font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
                 'Segoe UI', Roboto, sans-serif;
    font-weight: 400;
    font-size: 1rem;
  }

  /* ---- The hero ---------------------------------------------------------
     The headline in the same 700 the band's headings take, and uppercased to
     match them. Vidaloka -- the serif this was set in -- is what the size
     clamp above was written around: it is a wide face, and the cap existed
     to stop "Handcrafted Gear" wrapping inside the 780px hero box. Barlow
     Condensed is far narrower, so that constraint is gone and the headline
     can take the size it wants. white-space: nowrap on the h1 still holds
     the line either way.

     The white and the text-shadows stay exactly as they are. This is the one
     piece of type on the site sitting straight on a photograph in daylight,
     and neither the colour nor the shadow has anything to do with the face
     it is set in. */
  .hero-content h1 {
    font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
                 'Segoe UI', Roboto, sans-serif;
    font-weight: 700;
    text-transform: uppercase;
    /* Fixed, and smaller. It was clamp(3rem, 5.6vw, 4.6rem) -- 54px at the
       narrow end of desktop, 82.8px at the wide end, sliding with the window
       between them. Now one number at every desktop width, matching the two
       band headings below it, which were pinned for the same reason.

       white-space: nowrap on the h1 is what makes a fixed size safe here:
       "HANDCRAFTED GEAR" sets to roughly 360px in this face at 64.8px, well
       inside the 780px hero-content box even at 860px of window, so nothing
       is relying on the size shrinking to keep the line whole. */
    font-size: 3.6rem;
    letter-spacing: 0.02em;
    line-height: 1.05;
  }
  /* .btn-tan's hover sweep -- a panel of --btn-gray-dark sliding down
     through the button on the is-hovering class JS adds. The story buttons
     lose it in the layout block up the file; the hero's is not inside that
     block, so it is turned off here. It would otherwise animate behind a
     white button, invisibly, and there is nothing left for it to reveal. */
  .hero .btn-hero::after { content: none; }
  .hero-content p.lead {
    font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
                 'Segoe UI', Roboto, sans-serif;
    font-weight: 400;
    font-size: 1.5rem;
    letter-spacing: 0.01em;
  }
  /* Shop the Store, on the same settings the two story buttons carry: 500,
     caps, the 0.06em those take at label size -- and their square corners.
     .btn sets border-radius: 999px for every button on the site; this is the
     third to opt out, and the three that have are the three on the home
     page's white run.

     White at rest with near-black type, going red with white type on hover:
     the inverse of the two story buttons, which are red at rest and do not
     move at all. Both directions are written out because .hero .btn-hero
     sets --btn-red-solid up the file and .btn-hero sets --offwhite, so this
     is beating two rules rather than adding one.

     The sweep is turned off. .btn-hero's ::after is a panel of --btn-gray-
     dark that slides down through the button on the is-hovering class JS
     adds, and it sits BEHIND the label -- so it cannot carry a hover that
     has to change the type as well as the fill. A flat crossfade of both,
     on one timing, is the whole of it instead.

     0.22s is the sweep's own duration, kept so the button changes at the
     speed it always did. */
  /* Nothing left for the hero's button on its own. Its shape, type,
     padding, square corners and the arrow that opens out of its right edge
     all come from the .story-cta block up the file now, shared with the two
     buttons in the About Us band; its white-on-near-black pair is set there
     too, beside their red-on-white one.

     What was here -- border-radius, the fill, the label colour, the family,
     the weight, the tracking, and a background-color transition for a hover
     that no longer changes colour -- was every one of those said a second
     time. */
}


/* ============================================================
   Free Membership: the light band (home and store)
   ============================================================
   The same ground the story panels and the category tiles take, floating on
   a margin with no texture over it -- see .page-home .membership-strip::before
   below for why the topo layer had to go rather than be toned down.

   Both light pages read these rules, so the two copies of the band cannot
   drift apart. Adding the store meant broadening every selector here rather
   than writing a second block, which is the only arrangement that keeps that
   true. A third page joins the same way: add it to each list.

   Three pages now. The product page joined by having its section REPLACED
   rather than restyled: it carried a much older piece of markup -- a
   .section.section-alt.topo-strip wrapping a 560px centred .subscribe-col --
   which is not this band with different colours, it is a different band. The
   store's copy was lifted into it verbatim, so all three run the same
   elements and these selectors reach them all.

   Still scoped, and the scope is doing real work: .membership-strip is also
   the About page's Get In Touch strip, which is still dark. Desktop only,
   like the rest of the light treatment. */
@media (min-width: 860.02px) {
  .page-home .membership-strip,
  .page-store .membership-strip,
  .page-product .membership-strip {
    /* Flat, not the gradient the story panels and the category tiles carry,
       and the FOOT of it rather than the head.

       Flat because the band has no depth left to fade over. --panel-ground
       runs head to foot across whatever it is painted on, which reads as a
       lit surface on a 650px story panel and as a bar that has not finished
       loading on a strip of this height -- the same reason the promo bar is
       flat, and it became true here when the section went to two columns and
       lost half its depth.

       The foot rather than the head, so this band is the dark end of the
       page: it is the last thing above the footer, and the story panels
       directly overhead already end on this exact value where they meet it.
       The two read as one surface continuing down rather than as a light bar
       under a fade.

       It also settles the contrast question this band has been carrying. On
       the light grounds white here fell as low as 3.6:1, under the 4.5:1
       small text wants, and the copy is 0.85rem -- smaller than most on the
       site. On this foot it is about 15.7:1.

       A token either way, so a colour change is still the two lines in
       :root and nothing here. */
    background: var(--panel-foot);
    /* Inset on the sides by --page-gutter, the same measure .container holds
       the page's content off the window with. So the panel's left and right
       edges line up with the text and photographs further up the page rather
       than sitting at some inset of their own.

       The bottom no longer matches them. It was --page-gutter as well, on the
       principle that a floating band should be evenly inset; it is twice
       --grid-gap now, which is what every other seam between two sections on
       this page measures. The two readings disagree and the seam wins: this
       edge is read against the gap above the band and the gap below the
       stories, not against the strip of page beside it.

       No top margin, deliberately: the seam above is already set by
       .home-featured's bottom padding, and a margin here would stack with it
       and make that one gap twice the others. The band's top edge is set by
       the section above; its other three are set here. */
    /* No margin at all: the band runs the full width of the window and sits
       hard against the footer.

       It floated before -- inset by --page-gutter on the left and right so
       its edges lined up with the text and photographs up the page, and held
       off the footer by twice --grid-gap, the page's section seam. Both are
       gone, so the band is the one full-bleed element on these pages and the
       footer begins where it ends.

       Two things followed from the old margins that no longer apply: the
       strips of page they exposed beside and below the band (which is why
       .page-home and .page-store paint white), and the note below about this
       edge being read against the seam rather than against the page beside
       it. There is no page beside it now. The white grounds stay -- the
       seams higher up the page still show them. */
    margin: 0;
    /* The space above the heading and below the last field is --page-gutter
       -- the same measure as the margin holding this band off the left, the
       right and the bottom of the page. So the panel is inset from the page
       by one value and its contents are inset from the panel by the same
       one, and the band reads as evenly cushioned rather than as a box with
       its own idea of spacing.

       It takes two rules because the space was coming from two boxes: this
       band's own 40px top and bottom, and .membership-card's 44px inset
       inside it. 84px in total, which was neither of the values it might
       have been. Zeroed here and set on the card below, so one number
       governs it rather than a sum that has to be worked out. */
    padding-top: 0;
    padding-bottom: 0;
    /* Both rules, top and bottom. They closed the band off against whatever
       came before and after it, back when it ran edge to edge and needed
       edges of its own. It floats now -- inset on three sides by the margin
       above -- so the space around it is the boundary, and a dark hairline
       across a panel bounded by space is an edge drawn where there is
       already one.

       Written as `border: none` rather than the two sides separately: with
       the left and right already absent on the base rule, naming all four is
       the shorter way to say the panel has no border at all. */
    border: none;
  }
  /* The margins expose the page behind the band, and the page is --bg, which
     is near-black. Every section on this page paints its own ground, so the
     body's shows in exactly one place: the three strips this margin opens
     up. White, so they read as the page continuing round the panel rather
     than as a dark frame drawn on it. */
  .page-home { background: var(--page-ground); }
  /* ---- and a dark strip at the very top of it --------------------------
     What sits behind the header before the hero gets there. The bar and the
     header are in flow ABOVE the hero, not over it, so at rest the only
     thing behind the header is the page -- and the page is white. With the
     header's fill dropped to 55% on scroll, that white showed straight
     through it and the band read as a white bar with ghost type on it.

     It is a real gap, not a rounding error: the hero's top edge is
     --promo-h + --header-h down the document, so the header is over the page
     rather than over the photograph for the whole of the first 82px of
     scrolling, and only afterwards is there a picture behind it.

     A background-image on the body rather than a layer of its own, because
     this has to scroll AWAY. Anything fixed or sticky -- a pseudo-element on
     the bar, a strip pinned to the viewport -- would follow the header down
     the page and sit behind it permanently, which would make the fill opaque
     again and undo the transparency it exists to support. Painted on the
     body it is anchored to the document: it covers exactly the region above
     the hero, and once that region has scrolled past, it is gone.

     The height is --chrome-h, the pair's real measured height, which
     initHeroChrome already writes for the slide-away transform. Same value,
     same reason to measure rather than trust the tokens; they are the
     fallback for the first frame.

     background-size on top of the colour set above -- the shorthand there
     would clear this if the two were merged, so the colour stays the
     shorthand and this stays the image. */
  .page-home {
    background-image: linear-gradient(#141414, #141414);
    background-repeat: no-repeat;
    background-size: 100% var(--chrome-h,
                             calc(var(--promo-h) + var(--header-h)));
  }
  /* ---- The About page, on the same treatment ----------------------------
     Its two stories are .home-story now and its foot is .home-touch, so it
     needs the ground those bands are drawn against for the same reason the
     home page does: .section.home-about paints its own white, but the seams
     between the bands and the strips beside Get In Touch are body, and body
     is --bg. Three rules is the whole of it -- everything else the page
     needed came with the components. */
  .page-about { background: var(--page-ground); }
  /* The seam under the photo header. Two rules already have an opinion about
     this and they are the same specificity, so the later one was winning by
     source order alone: `.page-header-about + .section` sets 28px near the
     top of the file, and `.section.home-about` sets a gutter plus a grid-gap
     further down, which is written for a band opening under the HERO. Under
     a header photo that is a large gap for nothing. This says which, at a
     specificity that is not an accident, and uses the page's own section
     seam -- twice --grid-gap, the value every other gap on these pages is. */
  .page-about .page-header-about + .section.home-about {
    padding-top: calc(var(--grid-gap) * 2);
  }
  /* ---- Both rows the same depth, and the copy scrolls to fit -------------
     height, not min-height. The two rows carry very different amounts of
     copy -- two full paragraphs against three lines -- and a floor lets the
     longer one grow, which is what made them unequal. A fixed height makes
     them the same object; what does not fit becomes reachable instead of
     being allowed to push the band open.

     --band-h-story is the value the shorter row was already sitting at, so
     Behind the Name does not move at all. How It Started comes down to meet
     it. */
  .page-about .home-story { height: var(--band-h-story); }
  /* The panel gives up its padding to the scroller and becomes the box the
     scroller is positioned against, plus the surface the fade is drawn on. */
  .page-about .home-story-copy {
    position: relative;
    padding: 0;
  }
  /* Absolute, not merely overflow: auto. A scrolling box still reports its
     full content height to its parent, so the panel would size to the copy
     and the row would grow after all -- the same trap the old .about-card
     hit, and the reason that one was built this way too. Out of flow it
     contributes no height, and inset: 0 fills whatever the row turns out to
     be.

     safe center, with plain center under it for anything that does not know
     the keyword. Centred is right while the copy fits -- it is what the
     panel does on the home page and what Behind the Name still wants -- but
     centring content taller than its box pushes the start of it out of the
     top, where it cannot be scrolled back to. `safe` falls back to
     flex-start exactly when that would happen, which is the case this whole
     block exists for.

     No scrollbar: the fade below is the affordance, the same choice the
     gallery rails elsewhere in this file make. */
  .page-about .story-scroll {
    position: absolute;
    inset: 0;
    overflow-y: auto;
    padding: 56px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    justify-content: safe center;
    scrollbar-width: none;
  }
  .page-about .story-scroll::-webkit-scrollbar { display: none; }
  /* The copy ends on a paragraph where the home page's ends on a button, so
     the base p's 1em bottom margin has nothing under it to space. */
  .page-about .story-scroll > :last-child { margin-bottom: 0; }
  /* The fade, on the PANEL and not on the scroller -- inside the scroller it
     would ride up with the text and dissolve whichever line happened to be
     under it rather than the last visible one.

     It ends on --panel-foot, which is why that stop is a token now: the
     panel's gradient is at its foot by the time it reaches this edge, so the
     copy has to dissolve into that exact colour or the fade shows as a band
     of the wrong tone. A literal here would go stale the next time the
     panel's colour moves, which has happened about a dozen times.

     110px, anchored to the bottom, so the dissolve takes the last two lines
     or so rather than only the final one.

     opacity rather than a mask, because a mask cannot be transitioned and
     this should ease away when the reader reaches the end rather than blink
     off. JS adds the class only while there is more to see -- see
     initAboutCardScroll in main.js -- so copy that fits gets no fade at all,
     which is what happens on a wide window and on Behind the Name at any
     width. */
  .page-about .home-story-copy::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 110px;
    background: linear-gradient(180deg, transparent, var(--panel-foot));
    opacity: 0;
    transition: opacity 0.25s ease;
    pointer-events: none;
  }
  .page-about .home-story-copy.is-faded-bottom::after { opacity: 1; }
  /* ---- The header photograph, out of #141414 -----------------------------
     The four other page-header photos on the site lift out of
     filter: brightness(0), which is the shared entrance and is pure #000 --
     the filter has one endpoint and that is it. This page's photographs all
     fade up out of a painted #141414 now (the story frames and Get In Touch
     carry it on their own rules), so the header is brought onto the same
     ground rather than being the one image on the page arriving out of a
     different black.

     Painted here, on the section, because the photo is on the ::before and a
     ground under a fading layer has to sit on something that is not itself
     fading -- .page-header-about is that.

     brightness is turned off in both states. Left running it would darken
     the photo's own pixels while the fade is already revealing it against a
     dark ground: the picture would arrive flat and then brighten separately,
     which is two entrances where there should be one. The scale is untouched
     -- 1.28 over 2.7s, the site's settle, shared with every other header. */
  .page-about .page-header-about { background: #141414; }
  .page-about .page-header-about::before {
    filter: none;
    opacity: 0;
    transition: transform 2.7s cubic-bezier(0.16, 1, 0.3, 1), opacity 2.2s ease;
  }
  .page-about .page-header-about.is-visible::before {
    filter: none;
    opacity: 1;
  }
  /* No texture on this band. The topo layer is turned off outright rather
     than styled down.

     It could not be made to work here. topo-texture-v7.jpg measures 11 to 15
     out of 255 across the whole file -- a four-level range sitting on black.
     That is exactly right on the dark bands, where --topo-ground is #0c0c0c
     and the contours are a whisper either side of it, and it is unusable on
     a light one: screened over this gradient it is too dark to lift
     anything, multiplied it floods the band. What was here in its place was
     a four-step filter chain rebuilding the contrast from nothing -- invert,
     drop to mid-grey, spread it, lift it back toward white -- and then a
     blend mode and an opacity to land it. Five values deep to approximate a
     texture the file does not contain.

     The gradient alone is what the band was after: the same wash as the
     story panels and the product panels above it, which is the thing this
     section is meant to match.

     content: none rather than display: none, so the pseudo-element is never
     generated at all -- and the parallax note below no longer applies, since
     there is nothing left carrying background-attachment on this band. The
     dark bands elsewhere still have their texture and their parallax; this
     rule is scoped away from them. */
  .page-home .membership-strip::before { content: none; }
  .page-store .membership-strip::before { content: none; }
  .page-product .membership-strip::before { content: none; }

  /* ---- Two columns: the copy left, the fields right ----------------------
     The card was a 560px centred stack -- heading, then the line under it,
     then the two forms, four things down one narrow column. Side by side it
     is two, and the band is about half the height for it.

     Grid rather than flex, and 1fr / 1fr rather than a measured split: the
     left column holds a heading and one line of copy and the right holds two
     fields, and neither has a natural width to defer to. Even halves keep
     the fields wide enough to type in without the copy running to a line
     length that needs its own leading.

     align-items: center, so the two columns hang off a common middle line
     whichever turns out to be taller -- at the moment that is the fields,
     by a little.

     The 560px cap has to go with the stack. It was the measure of a single
     centred column and there is no single column now; the row takes the
     container's width, which is the same measure the sections above it hold.

     Vertical padding down from --page-gutter (64px) to twice --grid-gap
     (36px), which is this page's section seam and the value the band's own
     margins used to be. Between that and the halved content height, the band
     is a strip now rather than a block -- which is what it should have been
     once it lost its frame and its texture and became one line of sign-up. */
  .page-home .membership-card,
  .page-store .membership-card,
  .page-product .membership-card {
    max-width: none;
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: center;
    gap: var(--page-gutter);
    text-align: left;
    padding: calc(var(--grid-gap) * 2) 44px;
  }
  /* The stack keeps its own 460px cap, pushed to the outer end of its half
     so the fields sit against the right of the row rather than floating in
     the middle of their column with a gap either side. */
  .page-home .membership-card .subscribe-stack,
  .page-store .membership-card .subscribe-stack,
  .page-product .membership-card .subscribe-stack {
    margin-left: auto;
  }
  /* The description is 0.85rem set for a narrow centred column. In half a
     row it would run long, so it takes a measure of its own. */
  .page-home .membership-intro .subscribe-block-desc,
  .page-store .membership-intro .subscribe-block-desc {
    max-width: 46ch;
    margin-top: 10px;
  }

  /* ---- The type ---------------------------------------------------------
     The band's own face and colour, matching the sections above it. */
  .page-home .membership-strip h2,
  .page-store .membership-strip h2,
  .page-product .membership-strip h2 {
    font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
                 'Segoe UI', Roboto, sans-serif;
    font-weight: 700;
    text-transform: uppercase;
    /* Smaller than the stories' 3.25rem. That size is for a heading that
       owns half a row on its own; this one sits above a form in a strip. */
    font-size: 2.5rem;
    letter-spacing: 0.02em;
    line-height: 1.1;
    color: #ffffff;
  }
  .page-home .membership-strip p,
  .page-store .membership-strip p,
  .page-product .membership-strip p,
  .page-home .membership-strip .subscribe-block-desc,
  .page-store .membership-strip .subscribe-block-desc,
  .page-product .membership-strip .subscribe-block-desc {
    font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
                 'Segoe UI', Roboto, sans-serif;
    font-weight: 400;
    font-size: 1.125rem;
    line-height: 1.6;
    letter-spacing: 0.005em;
    color: #ffffff;
  }
  /* The form controls come with it. The fields were --field-fill on a dark
     band -- a filled box a step up from its ground; the same idea inverted
     is white on grey. Their type takes the band's face too, or the
     placeholder and what you type into it would be the last two things on
     the page still set in Lambda. */
  .page-home .membership-strip input,
  .page-store .membership-strip input,
  .page-product .membership-strip input {
    font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
                 'Segoe UI', Roboto, sans-serif;
    font-size: 1.0625rem;
    /* The base rule insets these 2px on the left -- which is right for what
       it was drawing, a bare line with the text sitting on it, where any
       more would have the words floating off the start of the rule. This is
       a filled field now, and 2px inside a box puts the placeholder against
       the edge. 12px gives it the same kind of room the right side already
       has, without touching the 34px there that keeps the text clear of the
       submit arrow. */
    padding-left: 20px;
    /* Back to a step UP from the ground, which is what --field-fill was doing
       on the dark bands before this band ever went light: a translucent white
       rather than a flat colour, so the field lifts by the same amount at
       both ends of a gradient that runs from #43 to #1c. A solid fill picked
       for one end is invisible at the other. */
    background: rgba(255, 255, 255, 0.1);
    color: #ffffff;
    border-color: rgba(255, 255, 255, 0.22);
  }
  .page-home .membership-strip input::placeholder { color: rgba(255, 255, 255, 0.55); }
  .page-store .membership-strip input::placeholder { color: rgba(255, 255, 255, 0.55); }
  .page-product .membership-strip input::placeholder { color: rgba(255, 255, 255, 0.55); }
  .page-home .membership-strip .subscribe-arrow { color: #ffffff; }
  .page-store .membership-strip .subscribe-arrow { color: #ffffff; }
  .page-product .membership-strip .subscribe-arrow { color: #ffffff; }
}


/* ============================================================
   Quick view popup: the light treatment
   ============================================================
   The panel a Quick Add bar opens, on the same surfaces as the pages that
   open it: white ground, near-black type, Barlow Condensed, square corners.

   No longer scoped to .page-home. That scope was real while the store page
   was near-black -- a light popup over a dark grid would have been the wrong
   panel, not a finished one -- and it stopped being true when the store went
   white. The modal is one element in the body of all eleven pages and there
   is one of it, so unscoping is the whole change.

   Not inside a min-width query, unlike most of the light treatment: the
   popup is the same panel at every width and the phone opens the same one.

   Every colour below was chosen against --modal-panel (#131313). None of
   them survive the ground inverting, so each is restated -- there is no
   light counterpart in the palette to swap them for. */
.quick-view-modal {
  /* Transparent, not white -- see the note further down where the white is
     handed to .quick-view-info alone. Left white here it would still paint
     under the media half and the pale rim would come back. */
  background: transparent;
  /* --band-radius, matching the home page's bands. This rule is unscoped and
     sits far below the three that set the modal's geometry, so it wins on
     source order at equal specificity -- which is why setting the curve up
     there alone left the corners square. It has to be set here too.

     One value for every width: the sheet form under 760px overrides it with
     its own top-corners-only pair, and that rule is in a media query, so it
     still lands over this. */
  border-radius: var(--qv-radius);
  /* The 1.3px --border-strong outline goes. That gray was drawn to give a
     near-black panel an edge against a near-black page -- two darks that
     needed a line between them to read as two things. A white sheet on a
     dimmed page already has one: the backdrop IS the edge, and a gray box
     ruled around it was the last piece of the dark panel still showing.

     Zeroed through --qv-border rather than removed, because that property is
     not decoration -- the grid formula subtracts it to keep the photo column
     square (tracks are laid out in the CONTENT box, so the column's height is
     the modal minus its two borders while the inset expression is the modal
     including them). Set to 0px the subtraction is still there and still
     correct, and the mobile block's own restatement of the border further up
     the file resolves to `0px solid` on its own. Write `border: none` alone
     and that formula would take 2.6px off a modal that no longer has it. */
  --qv-border: 0px;
  border: none;
}
/* The bar you drag the sheet down by on a phone, and the close cross. Both
   were drawn as light marks on a dark panel.

   ---- Why this is a literal and not an alpha

   It was rgba(20, 20, 20, 0.22), and that value is right: 22% black is what
   this strip is meant to look like against the white sheet it caps.

   The trouble is that there is no white under it. .quick-view-modal is
   `background: transparent` a few rules up -- deliberately, so the media half
   can paint its own near-black without a pale rim showing round it -- and the
   two halves each paint their own ground instead. The handle is neither of
   them: it is a strip above both, on the modal itself, so an alpha there
   composites against whatever is BEHIND the sheet. On a phone that is the
   dimmed page, which is why the top of the popup was see-through.

   It was #cbcbcb for one pass -- that same 22% black resolved over white, so
   the strip kept the tint it was drawn with and was merely opaque. Plain
   white was asked for instead, and it is the better answer anyway: this strip
   is the top of the sheet rather than a control on it, and a gray band across
   a white panel reads as a piece of chrome that does something.

   The grab bar inside it is --border-strong on ::before and is untouched --
   a light mark on a dark strip becomes a dark mark on a light one, which is
   the same mark either way.

   If the modal ever paints its own ground again, this can go back to an
   alpha -- it would then be compositing against the thing it was written
   for. */
.quick-view-handle { background: var(--page-ground); }
/* ---- The type ----------------------------------------------------------
   The product name takes the band's heading treatment at a size that suits a
   panel rather than a page: 700, caps, tight tracking. Everything else in
   the column takes the copy settings. */
.quick-view-info h3[data-qv-name] {
  font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
               'Segoe UI', Roboto, sans-serif;
  font-weight: 700;
  text-transform: uppercase;
  font-size: 2rem;
  letter-spacing: 0.02em;
  line-height: 1.1;
  color: #141414;
}
.quick-view-info,
.quick-view-info p,
.quick-view-info .price,
.quick-view-info .product-color-label,
.quick-view-info .qv-rating-row {
  font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
               'Segoe UI', Roboto, sans-serif;
  color: #141414;
}
/* The wordmark over the product name. Its fourth colour: near-white on the
   dark panel, --title-red when the panel went light, #141414 after that, and
   now the panel's own gray.

   It measures 1.97:1 on white, well under the 4.5:1 small text asks for --
   deliberately, and the only place in this column where that is the right
   answer. This is a mark, not a line to read: it says whose popup this is
   above a product name set at 2rem in black, and holding it back is what
   keeps it from competing with that name. Every line under it that carries
   actual information -- name, price, labels, options -- is #141414 or a dark
   alpha that clears 4.5. */
.quick-view-info .qv-mini-logo { color: #BBB8B5; opacity: 1; }
.quick-view-info .price-was { color: rgba(20, 20, 20, 0.5); }
/* Share, beside the rating. #141414 going to #7f7f7f -- the reverse of what
   it did, which was 0.6 alpha lifting to solid.

   Which way it travels is worth naming, because it is the third control in
   this popup to be asked for it: the label is 18.4:1 at rest and #7f7f7f is
   4.00:1, so pointing at it takes the words below the 4.5:1 they clear four
   times over at rest. It reads as the button stepping back rather than
   lighting up. Deliberate here -- Share is a secondary action beside a
   rating, and a secondary action receding under the pointer is a defensible
   thing for it to do -- but it is the same shape as the breadcrumb's Home
   link and it should be recognised as such rather than rediscovered. */
.quick-view-info .pd-share-btn { color: #141414; }
.quick-view-info .pd-share-btn:hover { color: #7f7f7f; }
/* --border-strong is a light hairline for dark panels; a dark at low alpha
   is its counterpart here. */
.quick-view-info .detail-divider { border-top-color: rgba(20, 20, 20, 0.14); }
/* ---- The controls ------------------------------------------------------
   The colour and size pickers, the quantity stepper and the open menu were
   all filled with --surface: a step up from a dark panel, so they read as
   fields you can act on rather than outlines floating on it. The same idea
   inverted is a step DOWN from white, which is the gradient's light end. */
.quick-view-actions .qty-stepper {
  background: #f0f0f0;
  color: #141414;
  border-color: rgba(20, 20, 20, 0.14);
}
/* ---- Select Size and Select Color: two colours, and only two ------------
   The pickers came off the dark panel with three states in them: an
   --on-dark near-white label on --surface, the hovered row and the chosen row
   lifting to full --offwhite, and a white hairline dropping in under whatever
   the pointer was on. The light pass moved the RESTING text to near-black and
   the fill to #f0f0f0 and stopped -- so the two states that lift were still
   painting near-white, now onto a near-white box. Point at a colour and its
   name disappeared; the row already chosen was blank; the hairline was white
   on white and had never been visible at all.

   #BBB8B5 for the control and #141414 for everything drawn on it. That is
   the whole palette here -- fill, label, chevron, every row, the hovered row,
   the chosen row, and the hairline under it.

   The fill moving is the half of this that is easy to miss. #f0f0f0 was near
   enough to white to be the other part of what read as white in here, and
   putting the gray on the GROUND rather than on the type is what makes it
   work: #141414 on #BBB8B5 measures 9.3:1, where #BBB8B5 as text on a light
   box would have been 1.7:1 -- a gray this light can be a surface or it can
   be illegible, and it cannot be type.

   The hover is now the hairline by itself. It used to be the hairline plus
   the label lifting from muted to white; with one text colour there is
   nothing left to lift, which is what a two-colour rule costs and buys.

   So is the placeholder. "Select Color" and a colour you have picked are the
   same #141414 -- a third value is what telling them apart would take, and
   the field's own chevron and menu are what say it is a picker either way. */
.quick-view-info .color-select-trigger,
.quick-view-info .color-select-list {
  background: #BBB8B5;
  color: #141414;
  border-color: rgba(20, 20, 20, 0.14);
}
.quick-view-info .color-select-list li,
.quick-view-info .color-select-list li:hover,
.quick-view-info .color-select-list li[aria-selected="true"],
.quick-view-info .color-select-list li[data-placeholder][aria-selected="true"],
.quick-view-info .color-select-list li .option-note { color: #141414; }
/* Each of those beats a near-white it is written directly against: the
   hovered row's --offwhite in the shared four-panel block, the chosen row's
   --on-dark up in the popup's original dark rules, and the note's
   --on-dark-faint. Same selectors, later in the file. */
.quick-view-info .color-select-list li:hover { background: transparent; }
/* The hairline under the hovered row used to be set here too. It is not any
   more -- the block at the END of this file owns the menu's colours, and a
   second declaration up here only ever lost to it while looking like it was
   in charge. */
/* ---- Add to Cart -------------------------------------------------------
   Square, and in the band's face at the weight the story buttons take. The
   red stays: it is the one primary action in the panel and the same
   --btn-red the cart badge and the story buttons already carry, so the popup
   is not introducing a colour of its own. */
.quick-view-actions [data-qv-add-to-cart] {
  /* Every number here is the hero button's, taken from
     `.hero .hero-actions .story-cta` and its base rule rather than picked
     again: 1.25rem at 500, caps, 0.06em, square, and 9/24/11 of padding on a
     1.15 line-height. The uneven top and bottom are load-bearing -- caps in
     Barlow Condensed hang 0.300em below the top of their line box and only
     0.200em above the bottom of it, so equal padding lands the cap block
     0.1em low. 8/10 at 20.25px is that correction; 9/11 is it at 22.5px.

     It comes out 46px tall, which is the quantity stepper's height exactly.
     That was not arranged -- 22.5 x 1.15 is 25.9, plus 20 of padding -- but
     it is why the pair reads as one control stacked on another. */
  line-height: 1.15;
  padding: 9px 24px 11px;
  font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
               'Segoe UI', Roboto, sans-serif;
  font-size: 1.25rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  border-radius: 0;
  box-shadow: none;
  /* Full width again, across the column like the stepper above it.

     It hugged its label while the arrow existed, and had to: the arrow was
     parked outside the button's own right edge, so the box had to measure the
     label and its padding and nothing more or there would have been no edge
     in the right place to hide it behind. With the arrow gone that constraint
     goes with it, and what is left is a primary action in a narrow column,
     which is a shape a full-width bar suits.

     The 24px of side padding stays. It does nothing to the box now -- the
     width is the column's -- but it is the hero button's inset and this
     button is still the hero button's box in every other respect. */
  width: 100%;
  /* .btn's 8px sits between the label and the arrow whether or not the arrow
     has any width, so the resting box would carry 8px of empty red past the
     last letter. The arrow's own margin-left is the gap, and it only exists
     once the arrow is on its way in. */
  gap: 0;
}
/* Black, going red. The two states are the whole hover now -- the arrow that
   used to be it is gone, so the fill is free to move again, which it was not
   while the opening edge was carrying the response.

   #141414 and #b02b25 are the popup's other two colours already: the Quick
   Add bars on the cards that open it are the first, and every red button on
   the site is the second. White reads 18.4:1 on the black and 6.53:1 on the
   red, so the label is fixed and only the ground travels.

   The transition has to be declared here. .btn sets one for background-color,
   but .btn-light overrides the whole property with `transform 0.15s` for the
   scale it no longer uses -- so without this line the fill would jump. */
.quick-view-actions [data-qv-add-to-cart] {
  background: #141414;
  color: #ffffff;
  transition: background-color 0.22s ease;
}
.quick-view-actions [data-qv-add-to-cart]:hover,
.quick-view-actions [data-qv-add-to-cart]:focus-visible {
  background: #b02b25;
  color: #ffffff;
}
/* The sweep off, as on all four of the band buttons: it is a panel sliding
   down behind the label on a 0.22s transform, and there is already a good
   deal of movement in a button that changes width. */
.quick-view-actions [data-qv-add-to-cart]::after { content: none; }
/* The label's trailing letter-space is corrected the other way now. On a
   hugging button the 1.35px that 0.06em leaves after the last character was
   padding on one side only, and pulling it back off the span was what made
   the room beside the A of ADD equal to the room beside the T of CART. On a
   full-width button the label is CENTRED in the box, so that same sliver
   throws the centring off by half of itself -- and the correction has to be
   half the value rather than all of it. */
.quick-view-actions [data-qv-add-to-cart] span { margin-right: -0.03em; }
/* The arrow is gone, and out of the markup on all three pages that carry a
   popup rather than hidden here -- it was added for this button last pass and
   nothing else in the file refers to it, so there is no shared copy to keep
   working around. The `overflow: hidden` and the `gap: 0` above are what it
   leaves behind: both are .btn-light's and .btn's own, and neither does
   anything to a button whose only child is its label.

   The box does NOT go back to full width with it. That was never about the
   arrow -- it is the hero button's proportions, which is what was asked for,
   and a button that hugs its label reads as an action rather than as a bar. */
/* ---- What was still near-white on the white panel -----------------------
   Four marks the light pass missed, each for the same reason: a rule further
   up the file names them more specifically than the block-wide sweep does, so
   the sweep never reached them.

   The price is the one that matters. `.quick-view-info .product-price .price`
   is a three-class selector holding --text, the site's near-white; the light
   block's `.quick-view-info .price` is two, and loses. The popup's price has
   been white on white since the panel turned over. The struck compare-at
   price beside it is the same shape of problem, and is hidden on most
   products, which is why neither showed up.

   The rating figures are --text-faint and the empty-state line is
   --on-dark-faint: near-white at half and at a fifth. And the star row's
   BACKING stars -- the outlines the gold ones are clipped over -- are
   near-white at 0.18, so a 4-star product was drawing four stars and a gap
   where the fifth outline should be. Same alpha, inverted. */
.quick-view-info .product-price .price { color: #141414; }
.quick-view-info .product-price .price-was { color: rgba(20, 20, 20, 0.5); }
.quick-view-info .qv-rating-score,
.quick-view-info .qv-rating-count,
.quick-view-info .qv-rating-empty { color: rgba(20, 20, 20, 0.6); }
.quick-view-info .star-rating-bg svg { fill: rgba(20, 20, 20, 0.18); }
/* View Full Details, at the foot of the column. #777777 lifting to #141414,
   with the line that drops in taking the hover colour too.

   This is the site's own two-tone link treatment, back where it started. The
   link has been through the inverse of it in the meantime -- #141414 at rest
   going OUT to #999691 on hover -- which was the drop's accent landing on a
   label that already had all the contrast there was, so the only direction
   left was down. Muted-to-solid points the right way: the label arrives
   rather than leaves.

   The numbers. #141414 lit is 18.4:1. #777777 at rest is 4.48:1 against the
   4.5:1 small text wants at this size -- 0.9375rem, 16.9px, weight 400, so
   the large-text 3:1 does not apply. Two hundredths short, which is close
   enough to read as fine and far enough that it is written down: #767676,
   one level darker, clears at 4.54:1 if that matters.

   ---- The hover is the colour, and it is Share's

   #141414 going to #7f7f7f, identical to the Share button at the top of the
   column, so the popup's two text buttons answer the pointer the same way.

   No line. That ::after has been on and off this link three times now -- the
   site's standard fade-and-slide rule, then off when the label started
   moving, then back in the product cards' own values when the label was
   pinned. It is off again, with content: none, which removes the
   pseudo-element rather than hiding it: the :hover rule up the file that
   fades it in has nothing to act on and no state can bring it back. The
   story buttons and the FAQ more-link share that ::after and keep theirs.

   Which also retires the right: 0 pull-back. It only ever existed because
   the rule is hung off the SPAN and stretched 25px past it to reach under an
   arrow -- and this link's arrow has been gone for a while.

   1.0625rem, up from 0.9375. It is the last line in the column and it had
   been set smaller than the option rows in the pickers above it, which is
   backwards for a link that leaves the popup. 19.1px at weight 400 is still
   SMALL text for contrast purposes -- large starts at 24px, or 18.66 bold --
   so nothing about the numbers below changes with the size.

   The direction, once, since this is the second button here to take it: the
   label is 18.4:1 at rest and #7f7f7f is 4.00:1, so pointing at it moves the
   words below the 4.5:1 they clear four times over. It reads as the link
   stepping back rather than lighting up. Deliberate, and matched to Share on
   purpose -- but it is the same shape as the breadcrumb's Home link. */
.quick-view-info .quick-view-link-full {
  font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
               'Segoe UI', Roboto, sans-serif;
  font-size: 1.0625rem;
  color: #141414;
  /* Left, not centred. It is a full-width flex row -- the width is what
     carries the border-top across the column -- so this moves the label
     inside the row rather than shrinking the row to the label, and the rule
     above it still runs edge to edge.

     The left margin, which is where everything else in this column starts:
     the wordmark, the product name, the price, the labels in the two
     pickers, and the Add to Cart that only appears centred because it fills
     its own box. It sat centred, then right, and this is the one of the
     three that puts it on the column's own reading edge. */
  justify-content: flex-start;
}
.quick-view-info .quick-view-link-full:hover { color: #7f7f7f; }
.quick-view-info .quick-view-link-full span::after { content: none; }
/* The arrow goes. It never moved -- the shared spin was turned off for this
   link years of edits ago and nothing replaced it -- so it was a mark sitting
   beside the words doing nothing, and the underline is the whole of the hover
   anyway. display: none rather than cutting it from the markup, because the
   same <svg> is written into three pages' copies of this popup.

   The underline used to have to come in with it: it is hung off the SPAN and
   stretched 25px past its right edge -- the arrow's 17px plus the row's 8px
   gap -- so the rule reached under the arrow, and with no arrow there that
   overhang was 25px of line running into nothing. The pull-back is gone with
   the line itself; see the rule above, where the ::after is switched off
   outright. The overhang stays on the two links that still have arrows. */
.quick-view-info .quick-view-link-full svg { display: none; }
/* ---- The photo column --------------------------------------------------
   The arrows, the dots and the thumbnail frames were all light marks meant
   to sit over or beside a dark panel. The photographs themselves are
   untouched: a picture is a picture on either ground.

   ---- The arrows are the close button, laid on its side

   Same solid #141414 disc, same white glyph, same 1.35 scale on hover over
   the same 0.2s. That last part was already true and had been all along --
   `.qv-media-arrow:hover svg` and `.quick-view-close:hover svg` are the same
   two declarations up the file. What made them read as different controls was
   the disc: the close button went solid black when the panel went light and
   these stayed on a translucent white with a near-black chevron, so the one
   that grew looked like a button and the two that grew looked like marks on
   the photograph.

   The border goes with it. --border-strong was a light hairline giving a
   smoked disc an edge against a photo; a solid black disc is its own edge,
   and the close button has had none since it went black.

   Unscoped, which is what carries this to the product page. Both pairs of
   arrows have shared this class since the smoked fill was unified -- the
   popup's are `.qv-media-arrow`, the gallery's are that plus
   `.pd-media-arrow` for its own positioning -- so there is one rule here and
   not two, and there is nothing page-specific left to write.

   The hover's colour is pinned rather than left to the base rule, which
   lifts it to --on-dark. That was a near-white arriving on a smoked disc,
   where it was the hover; on a black one it is a shade off the white the
   glyph already is, and the scale is the whole response. */
.qv-media-arrow {
  color: #ffffff;
  background: #141414;
  border: none;
}
.qv-media-arrow:hover { color: #ffffff; }
.qv-media-thumb { border-color: rgba(20, 20, 20, 0.14); }
.qv-media-thumb.is-active { border-color: #141414; }
/* ---- The dots ----------------------------------------------------------
   White for the one you are on, light gray for the rest. They sit ON the
   photograph -- or on the Coming Soon plate, which is where they are most
   often seen -- so they are marks over an image and not over the panel, and
   the near-black they took when the panel went light was the one place that
   change did not belong. Both are solid rather than white at an alpha: over a
   plate this dark an alpha would read as gray anyway, and over a pale
   photograph the same two values would drift together.

   Written on .media-dot as well as on the popup's copy, so the product page's
   dots -- which are the same row under the same kind of image -- take them
   too, and neither rule can be left behind if the other moves. */
.media-dot,
.quick-view-media .media-dot { background: #d6d6d6; }
.media-dot.is-active,
.quick-view-media .media-dot.is-active { background: #ffffff; }
/* The Coming Soon plate inside the popup, matched to the one on the cards
   that open it -- the slide you land on should be the tile you clicked, not a
   dark rectangle in a white sheet.

   Same declaration as .home-featured .product-media-soon, including the flat
   #181818 underneath as the fallback and the white label over it. The two are
   written out separately rather than shared through a selector list: one is
   inside the desktop home block and this is not, because the popup takes the
   light treatment at every width.

   No longer scoped to a page: the popup went light everywhere when the store
   did, so every copy of it shows this plate. */
.quick-view-media .product-media-soon {
  background: #181818 url('../assets/images/coming-soon-topo.jpg') center / cover no-repeat;
  color: var(--offwhite);
}

/* ---- The controls' details ---------------------------------------------
   The panel's own ground is no longer set here. It is the home page's
   gradient now, painted from the block at the very end of this file -- see
   "Quick view popup: the panel takes the home page's gradient", which also
   carries the type and control colours that had to turn over with it. The
   two elements that paint the sheet, and why it has to be both, are
   explained there.

   What stays in this block is everything about these controls that the
   ground does not decide: their shape, and where the light pass left the
   parts of them the gradient did not reach. */
/* And the rule down the middle of the panel, which is the same hairline in
   the same gray doing the same job -- separating the photo column from the
   copy column back when both sat on near-black. The photograph's own edge is
   what divides them on white, so the line is one more than is needed. */
.quick-view-info { border-left: none; }
/* Square, like every other control on this page's white run. --radius-sm on
   the pickers and a full pill on the stepper were drawn for a dark panel
   where a rounded fill reads as a soft object on a soft ground; on white,
   beside a square Add to Cart, they were the only rounded things left. */
.quick-view-info .color-select-trigger,
.quick-view-info .color-select-list,
.quick-view-actions .qty-stepper,
.quick-view-actions .qty-input { border-radius: 0; }
/* The placeholder inside the picker is set by its own rule, scoped to
   .quick-view-info and pointing at --on-dark-muted -- a near-white, chosen
   for the dark panel. On the new fill it was white on near-white, which is
   why the two pickers read as empty boxes. This is the same relationship on
   light: legible, and clearly not a chosen value yet. */
.quick-view-info [data-color-select-label],
.quick-view-info [data-color-select-label].is-placeholder,
.quick-view-info .color-select-trigger svg { color: #141414; }
.quick-view-info .color-select-dropdown.is-open .color-select-trigger { border-color: #141414; }
/* The stepper's two arrows and the number between them, on the same fill. */
.quick-view-actions .qty-stepper-btn,
.quick-view-actions .qty-input {
  color: #141414;
  background: transparent;
}
/* The close button. It has been three things: a light disc with a dark cross,
   drawn to lift off the popup's original near-black panel; a solid #141414
   disc with the cross reversed out, when the panel went white and the button
   had to become the dark object rather than the light one; and now the
   controls' own #E6E6E6 with a #141414 cross, which puts it in the same
   family as the pickers and the stepper instead of being the one solid black
   mark in a pale column.

   #141414 on #E6E6E6 is 14.76:1. The DISC is 1.25:1 against the panel, the
   same as the controls' fills and faint for the same reason -- but the cross
   inside it is doing more work here than a label does inside a picker, so
   the button reads as a button on the strength of its glyph rather than its
   ground.

   The hover has to be pinned. The base rule lifts the cross to --on-dark, a
   near-white chosen for the dark panel, which on this disc would be 1.2:1 --
   the mark disappearing at the moment you point at it. The response is the
   1.35 scale on the svg, which is untouched and is now the whole of it. */
.quick-view-close,
.quick-view-close:hover {
  background: #E6E6E6;
  color: #141414;
  border-color: transparent;
}


/* ============================================================
   Store page: the light treatment
   ============================================================
   The same move the home page took, applied to the grid page: a white ground,
   near-black type, and every rule and outline taken out so the photographs
   are the only things drawing edges.

   Desktop only, like the home page's, and for the same reason -- the phone
   keeps the dark store it has always had until that side is worked on.

   Scoped to .page-store, which is new: store.html carried a bare <body>, so
   there was nothing to hang page-specific rules on. Everything here is inside
   that class, so the ten other pages are untouched. */
@media (min-width: 860.02px) {
  /* The ground. The body's own --bg is near-black and every section on this
     page is transparent over it, so this one declaration turns the whole
     page over -- there is no per-section background to chase. */
  .page-store { background: var(--page-ground); }

  /* ---- Every rule and divider on the page -------------------------------
     Four of them, and they were all drawn for a dark page: a hairline of
     --border or --on-dark-border is a lift against near-black and a scar
     against white. What separated things here was never really the lines --
     it was the space around them -- so removing them costs nothing.

     The photo band's bottom rule goes with the rest. It marked where the
     picture ended against the page; against white the picture's own edge
     does that unaided. */
  /* No ground of its own, and on a white page that shows. The photograph is
     on a ::before that is SCALED -- 1.28 down to 1 over 2.7s -- and a scaled
     layer lands on subpixel boundaries, so along its edges a fraction of
     whatever is behind it comes through while it settles. What is behind it
     is .page-header's translucent olive over .page-store, and .page-store is
     #ffffff: a white seam around a photograph, and it moves, which is what
     makes it read as a fault rather than a border.

     inset: -1px on the ::before was the first go at this and it is not
     enough on its own -- it fixes the resting position and does nothing for
     the 2.7s of travel before it, where the overshoot is being scaled along
     with everything else.

     An opaque ground under the photo settles it outright: there is no longer
     anything white to come through, at any point in the animation or at any
     device pixel ratio. #141414 rather than a colour, because it is what the
     photograph's own dark edges meet.

     The store page only. The four other page headers sit on dark pages where
     the seam was always invisible; the About page carries its own copy of
     this for the same reason as here. */
  .page-store .page-header-store {
    border-bottom: none;
    background-color: #141414;
  }
  .page-store .store-toolbar-wrap,
  .page-store .store-toolbar { border-bottom: none; }
  .page-store .filter-sidebar-group { border-bottom: none; }
  /* The gray edge round every product photo, and the matching one round the
     Quick Add bar welded under it. Same reasoning as the home page's cards:
     the border was holding a square photo against a dark page, and on white
     a box round a picture that is already a picture is one line too many. */
  .page-store .product-media { border: none; }
  /* ---- Quick Add rides on the photograph --------------------------------
     It was welded under the picture and always showing, taking its own band
     of height in the card. Now it is parked below the picture's bottom edge
     and slides up over it when the pointer is anywhere on the media.

     Absolute, so it stops taking height: the card is shorter by the bar and
     the name and price under it move up to meet the photo. That is the trade
     for the bar being over the image rather than beside it.

     .product-media-wrap is the positioning context and already
     position: relative from the base rule -- it is the box that holds the
     photo and the bar, which is exactly the pair this needs. overflow is
     added here so the parked bar is clipped rather than hanging into the
     card below.

     Keyed to the WRAP's hover and not the photo's. The bar is a sibling of
     .product-media, so once it has slid up the pointer is on the bar and no
     longer on the photo -- keyed to the photo it would retract the moment
     you reached for it. The wrap contains both, so the state holds. */
  .page-store .product-media-wrap { overflow: hidden; }
  .page-store .product-media-actions {
    border: none;
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    transform: translateY(100%);
    transition: transform 0.32s cubic-bezier(0.33, 1, 0.68, 1);
  }
  .page-store .product-media-wrap:hover .product-media-actions,
  .page-store .product-media-wrap:focus-within .product-media-actions {
    transform: translateY(0);
  }
  @media (prefers-reduced-motion: reduce) {
    .page-store .product-media-actions { transition: none; }
  }
  /* The Free Membership band's rules are not dropped here. That band now
     reads the shared light-band block up the file, which floats it on a
     margin and takes all four borders off in one place -- see "Free
     Membership: the light band (home and store)". */
  /* The sort menu's box. It floats over the page on a shadow, which is what
     separates it; the border was the dark page's version of that. */
  .page-store .sort-dropdown-list { border: none; }

  /* ---- Type, on the new ground ------------------------------------------
     Not asked for, and not optional: --text-muted and --text-faint are light
     grays chosen to sit back from white type on near-black. Left alone on a
     white page they are light gray on white -- the product names, the prices
     and the result count would all be very nearly invisible. Turning the
     ground over means turning these with it.

     The photo band at the top is the exception and keeps its light type: it
     is a photograph, not the page, and it was never white behind that text. */
  .page-store .product-info h3 a,
  .page-store .product-info h3 a:hover { color: #141414; }
  .page-store .product-price .price { color: #141414; }
  .page-store .product-price .price-was { color: rgba(20, 20, 20, 0.55); }
  .page-store .qv-rating-row,
  .page-store .qv-rating-score,
  .page-store .qv-rating-count { color: rgba(20, 20, 20, 0.6); }
  .page-store .result-count { color: rgba(20, 20, 20, 0.6); }
  .page-store .sort-select { color: #141414; }

  /* ---- Quick Add --------------------------------------------------------
     Near-black with a white label, where the shared rule paints
     --btn-white-solid. Same fill as the home page's bar and the same reason:
     on a white page the button has to be the dark thing, not the light one.

     ---- The hover is the fill, and only the fill

     #141414 to #b02b25 over 0.22s, white throughout. It is the same pair the
     popup's Add to Cart carries, which is the button this one opens the way
     to -- so the bar and the button it leads to answer the pointer with the
     same colour change at the same speed.

     Two other gestures have been through here and both are gone. The
     site-wide ::after sweep -- a panel sliding down behind the label -- is
     off, as it is on every button on the light pages. And the label used to
     grow, 1.04 and then 1.12, which was the home page's Quick Add gesture
     brought across; a bar that changes colour AND swells its type is doing
     two things where the colour alone is unambiguous.

     The transition has to be declared. .cart-overlay-btn sets none of its
     own -- the sweep was its entire hover and a transform on a pseudo-element
     needs nothing from the button -- so without this line the fill would
     jump.

     Both states are no longer named together. They were, deliberately, back
     when the fill had to hold through the hover because the label was the
     response; now the fill IS the response and the two have to differ. */
  .page-store .cart-overlay-btn {
    background: #141414;
    color: #ffffff;
    transition: background-color 0.22s ease;
  }
  .page-store .cart-overlay-btn:hover,
  .page-store .cart-overlay-btn:focus-visible {
    background: #b02b25;
    color: #ffffff;
  }
  .page-store .cart-overlay-btn::after { content: none; }

  /* ---- The Coming Soon plates -------------------------------------------
     The topographic contour file, the same one the home page's tiles and the
     quick view popup take. Written out rather than shared with those through
     a selector list: they sit in different blocks under different conditions,
     and one list spanning all three would have to be repeated in each anyway.

     The flat #181818 stays underneath as the fallback, and the label stays
     --offwhite, exactly as on the home page. */
  .page-store .product-media-soon {
    background: #181818 url('../assets/images/coming-soon-topo.jpg') center / cover no-repeat;
    color: var(--offwhite);
    transition: transform 0.4s cubic-bezier(0.33, 1, 0.68, 1);
  }
  /* 1.06 on hover, the same step the home page's tiles take -- less than the
     1.08 a real photograph gets, because a photo can be pushed in on for the
     detail it reveals and a contour plate only gets larger.

     Keyed to .product-media, the photo's own box, and that choice carries a
     behaviour with it. The Quick Add bar is a SIBLING of this box rather than
     a child, so once the pointer is on the bar it is no longer on the photo:
     the plate settles back to its own size under the button. Point at the
     picture and it grows, move down onto Quick Add and it shrinks away. The
     clipping is .product-media's own overflow, which is already there.

     Not keyed to the whole card. The name and the price below it are a link
     to the same product page, so growing the plate from there would be
     defensible -- but it would also fire from the Quick Add bar between them,
     which is a different destination, and the shrink above is what tells you
     you have left the picture. */
  .page-store .product-media-wrap:hover .product-media-soon { transform: scale(1.06); }

  @media (prefers-reduced-motion: reduce) {
    /* Only the plate's zoom is left to turn off. The Quick Add label used to
       scale here too and no longer moves at all -- its hover is a colour, and
       a colour change is not motion. */
    .page-store .product-media-soon { transition: none; }
    .page-store .product-media-wrap:hover .product-media-soon { transform: none; }
  }
}


/* ============================================================
   Product page: the light treatment
   ============================================================
   The third page to turn over, after the home page and the store. Same move
   and the same reasons: a white ground, near-black type, every hairline
   taken out, and the buttons rebuilt as the square black boxes the rest of
   the redesign uses.

   Desktop only, and scoped to .page-product -- a class this file added to
   product.html, which carried a bare <body> exactly as store.html did.

   The quick view popup is NOT here. It stopped being scoped to the home page
   when the store went white, so the panel this page's related grid opens is
   already the light one. */
@media (min-width: 860.02px) {
  .page-product { background: var(--page-ground); }

  /* ---- Rules and dividers -----------------------------------------------
     Four kinds, all drawn to lift against near-black.

     .section-ruled is the one worth naming: it exists ONLY to draw a rule,
     as the separation an alt band gets without the colour change. With the
     rule gone the class has nothing left to do on this page, and the space
     around the section is the separation -- which is what every seam on the
     home page settled into as well. */
  .page-product .section-ruled { border-top: none; }
  .page-product .section-alt { border-top: none; border-bottom: none; }
  .page-product .detail-divider { border-top: none; }
  .page-product .accordion-item { border-bottom: none; }
  /* The gray edge round the gallery photo and the thumbnails, same call as
     the store's cards: a box round a picture that is already a picture. */
  .page-product .product-detail-media,
  .page-product .product-media { border: none; }

  /* ---- Type -------------------------------------------------------------
     As on the store page, this is not optional. --text, --text-muted and
     --text-faint are a light ramp built to sit at three depths against
     near-black; on white they run from barely-there to invisible. The same
     three depths are restated as near-black at three opacities.

     --title is the same story: #ececec, a light gray for headings on dark. */
  .page-product h1,
  .page-product h2,
  .page-product h3,
  .page-product h4 { color: #141414; }
  .page-product .product-detail-info,
  .page-product .product-detail-info p,
  .page-product .accordion-panel,
  .page-product .accordion-trigger { color: #141414; }
  .page-product .breadcrumb,
  .page-product .eyebrow,
  .page-product .pd-share-btn,
  .page-product .product-color-label,
  .page-product .qv-rating-score,
  .page-product .qv-rating-count,
  .page-product .qv-rating-empty { color: rgba(20, 20, 20, 0.6); }
  .page-product .breadcrumb a:hover,
  .page-product .pd-share-btn:hover { color: #141414; }
  .page-product .product-price .price { color: #141414; }
  .page-product .product-price .price-was { color: rgba(20, 20, 20, 0.55); }
  /* The related row's cards, matching the store's. */
  .page-product .product-info h3 a,
  .page-product .product-info h3 a:hover { color: #141414; }

  /* ---- Add to Cart ------------------------------------------------------
     The page's one real button, rebuilt as the story buttons and the store's
     Quick Add are: a square near-black box with a white label in Barlow
     Condensed, no pill, no shadow, and no sweep panel behind the type.

     The type is the .story-cta ramp value for value -- 1.125rem at 500, caps
     at 0.06em -- rather than .btn's 0.9375rem at 0.2em, which was set for
     Koulen and reads wide in a condensed face. */
  .page-product .btn-cart,
  .page-product .btn-cart:hover {
    background: #141414;
    color: #ffffff;
  }
  .page-product .btn-cart {
    border-radius: 0;
    box-shadow: none;
    font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
                 'Segoe UI', Roboto, sans-serif;
    font-size: 1.125rem;
    font-weight: 500;
    letter-spacing: 0.06em;
    /* The growth on hover, which everywhere else on the site is done to the
       LABEL rather than the box -- a Quick Add bar welded between a photo and
       a panel has nowhere to grow into, so only its span moves. This button
       has room on every side and no span in its markup to reach for, so the
       box itself takes it. 1.01 rather than 1.04, because a whole button
       moving that far is a lurch where a few words growing is a nudge. */
    transition: transform 0.3s cubic-bezier(0.22, 0.61, 0.36, 1);
    will-change: transform;
  }
  .page-product .btn-cart:hover { transform: scale(1.01); }
  .page-product .btn-cart::after { content: none; }

  /* The quantity stepper beside it, and the colour and size pickers: all
     --field-fill boxes with light type, which is a filled box a step up from
     a dark ground and a light box on a light one. Squared off with the
     button, as the popup's copies of these already are. */
  .page-product .qty-stepper,
  .page-product .color-select-trigger,
  .page-product .color-select-list { border-radius: 0; }
  .page-product .qty-stepper-btn,
  .page-product .qty-input { color: #141414; background: transparent; }
  .page-product [data-color-select-label] { color: #141414; }
  .page-product [data-color-select-label].is-placeholder { color: rgba(20, 20, 20, 0.55); }

  /* ---- Coming Soon ------------------------------------------------------
     The topo plate, as on the home page's tiles, the store's cards and the
     popup. Every plate the light pages show is this one now; the dark pages
     keep the flat #2a2a2a the shared rule paints. */
  .page-product .product-media-soon {
    background: #181818 url('../assets/images/coming-soon-topo.jpg') center / cover no-repeat;
    color: var(--offwhite);
  }

  /* ---- The related row's Quick Add --------------------------------------
     Written out rather than shared with the store's block: the two sit under
     different page classes, and a combined selector list would have to be
     repeated in both places anyway. */
  .page-product .cart-overlay-btn {
    background: #141414;
    color: #ffffff;
    transition: background-color 0.22s ease;
  }
  .page-product .cart-overlay-btn:hover,
  .page-product .cart-overlay-btn:focus-visible {
    background: #b02b25;
    color: #ffffff;
  }
  .page-product .cart-overlay-btn::after { content: none; }
  .page-product .product-media-actions { border: none; }

  @media (prefers-reduced-motion: reduce) {
    .page-product .btn-cart { transition: none; }
    .page-product .btn-cart:hover { transform: none; }
  }
}


/* ============================================================
   Popup and product page: no borders, filled controls
   ============================================================
   Every remaining outline on the two light panels comes off, and the pickers
   and steppers stop being outlined boxes and become filled ones -- a flat
   #f0f0f0 tile with square corners, gray placeholder, dark chevron.

   That swap is the reason the borders can go at all. An outlined control on
   white is a rectangle drawn in hairlines; a filled one is a shape. Taking
   the outline off without filling it would have left the pickers as invisible
   click targets, which is a different result from the one asked for.

   The popup half is unscoped and at every width, matching the rest of the
   popup's light block. The product page's half is desktop-only, matching the
   rest of that page's. Same values, two conditions -- which is why they are
   written twice rather than shared through one selector list.

   HALF OF THIS IS SUPERSEDED. The popup's two pickers and its stepper are
   filled and bordered again from the block at the end of this file -- a
   near-white #f4f2ef inside a 3:1 hairline. The reasoning above turned out to
   be right about the swap and wrong about what it bought: an unbordered fill
   light enough to sit quietly on white is not a shape either, it is a word
   floating on the panel. See that block for the numbers. Everything else here
   still stands, and all of the product page's half does. */

/* ---- The popup --------------------------------------------------------- */
.quick-view-info,
.quick-view-info .detail-divider,
.quick-view-info .color-select-trigger,
.quick-view-info .color-select-list,
.quick-view-info .color-select-dropdown.is-open .color-select-trigger,
.quick-view-actions .qty-stepper,
.quick-view-actions .qty-input,
.quick-view-close { border: none; }
/* The filled tile. The stepper takes it; the number inside it stays
   transparent so one fill runs the width of the control rather than a box
   sitting inside a box. */
.quick-view-info .color-select-trigger,
.quick-view-info .color-select-list,
.quick-view-actions .qty-stepper {
  background: #f0f0f0;
  border-radius: 0;
}
.quick-view-actions .qty-input { background: transparent; }
/* The open picker had its border darken to say so. With no border, the fill
   does it -- a step down from the closed state, which is the same signal in
   the only language the control has left. */
.quick-view-info .color-select-dropdown.is-open .color-select-trigger { background: #e6e6e6; }
/* The thumbnails' frames go with the rest, and the active one loses the
   border that marked it. Opacity carries that instead: the unselected frames
   sit back and the current one is at full strength. Not a border in disguise
   -- it is the one indicator available that is not an outline. */
.qv-media-thumb { border: none; opacity: 0.55; transition: opacity 0.2s ease; }
.qv-media-thumb.is-active { border: none; opacity: 1; }

/* ---- The product page -------------------------------------------------- */
@media (min-width: 860.02px) {
  .page-product .color-select-trigger,
  .page-product .color-select-list,
  .page-product .color-select-dropdown.is-open .color-select-trigger,
  .page-product .product-detail-cart-row .qty-stepper,
  .page-product .qty-input,
  .page-product .qv-media-thumb,
  .page-product .product-detail-media,
  .page-product .product-media,
  .page-product .product-media-actions { border: none; }
  .page-product .color-select-trigger,
  .page-product .color-select-list,
  .page-product .product-detail-cart-row .qty-stepper {
    background: #f0f0f0;
    border-radius: 0;
  }
  .page-product .qty-input { background: transparent; }
  .page-product .color-select-dropdown.is-open .color-select-trigger { background: #e6e6e6; }
  /* The chevron and the placeholder, matching the popup's. */
  .page-product .color-select-trigger svg { color: rgba(20, 20, 20, 0.55); }
  .page-product .product-detail-cart-row .qty-stepper-btn { color: #141414; }
}


/* ============================================================
   Get In Touch (home page): a band on a photograph
   ============================================================
   The one section on this page whose ground is a picture rather than a
   colour, sitting between the category tiles and Free Membership. It opens
   the foot of the page the way the hero opens the top.

   Base first, so the phone gets a working band rather than nothing: the photo
   is a background on the section itself, with a scrim over it and the copy
   centred on top. The desktop block below only insets it and sets the type. */
.home-touch {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  /* The ground the photograph fades up out of, and what shows if the file
     never arrives. The picture itself is on ::after below -- a background on
     this element could not be scaled on its own without taking the copy with
     it. */
  background: #141414;
  color: var(--on-dark);
  text-align: center;
  /* ---- The width of the plate, one expression for the whole range -------
     This was three: one column of the 1.15/3.15 story row above 1024, half
     the container from 750 to 1024, and the full page measure below 750 --
     which is what put the box edge to edge with the photograph on a phone,
     with none of the picture left beside it. Every handoff was also a step:
     484 -> 416 at 1024, and 289 -> the whole band at 860.

     One line instead, straight in the band's own inner width W:

       plate = 0.189 * W + 233px,  capped at 86% of the band

     Two anchors and nothing else. At a 1440px window W is 1324.8 and the
     line gives 483px, which is the 1.15/3.15 column the desktop layout was
     already on -- so the wide end is unchanged to the pixel. At a 390px
     phone W is 358 and it gives 301px, about 84% of the band, so the plate
     is still a box ON the picture with the photograph running past it on
     both sides. That is the part being kept: what the band looks like at
     full width is what it should still look like on a phone, at a phone's
     size.

     In between it is a straight line, so there is no width where the box
     jumps -- 416 at 1024, 363 at 750, 325 at 530 -- and the fraction of the
     band it covers opens smoothly from 36% to 84% on the way down, which is
     the same trade every other band on this page makes as the columns give
     out.

     The 86% cap takes over below a 379px window, at exactly the width where
     the two expressions cross (0.189W + 233 = 0.86W at W = 347), so min()
     changes hands with no step there either. Its only job is to keep a
     margin of photograph beside the box on the narrowest phones, where the
     line on its own would reach the band's full width.

     Read by .home-touch-panel below, at every width -- there is no second
     definition of this anywhere in the file now. */
  --touch-col: min(86%, calc((min(100vw, var(--container))
                              - 2 * var(--page-gutter)) * 0.189 + 233px));
}
/* The photograph, and the band's whole entrance: it fades up and settles out
   of a slight enlargement, the same shrink-into-place the hero and the page
   header photos take. The copy does not animate -- it is there when the band
   is -- so this layer is the only thing that moves.

   inset: -1px rather than 0 for the reason the page header photos carry it: a
   scaled layer lands on subpixel boundaries and can fall a fraction short of
   its own edges, leaving a hairline of what is behind it along one side.
   Overshooting costs nothing under the overflow: hidden above.

   z-index: -2. It is negative and still paints above this element's own
   background, because isolation makes it a stacking context -- so the order
   is ground, photo, copy. There was a scrim at -1 between the last two; see
   the note below for what it did and why it is not there. */
.home-touch::after {
  content: '';
  position: absolute;
  inset: -1px;
  z-index: -2;
  background: url('../assets/images/home-get-in-touch.jpg') center / cover no-repeat;
  opacity: 0;
  transform: scale(1.12);
  transition: opacity 1.1s ease, transform 2.2s cubic-bezier(0.16, 1, 0.3, 1);
}
.home-touch.is-visible::after { opacity: 1; transform: scale(1); }
@media (prefers-reduced-motion: reduce) {
  .home-touch::after { transition: none; opacity: 1; transform: none; }
}
/* The scrim, removed. .home-touch::before carried a vertical gradient in
   #141414 -- 28% at the top and bottom edges, 62% through the middle where
   the copy sits -- so the type had a darkened band to sit on and the rim of
   the canyon and the sky kept close to their own value.

   It is gone at the asking. Nothing dims the photograph now, and nothing
   else is helping the copy either: this band has never carried a
   text-shadow, unlike the hero and the membership strip, which set one on
   the heading and the paragraph and always have. The scrim was the whole of
   the contrast here.

   If the white does turn out to break up over the lighter passages of the
   photo, the shadow is the cheaper fix of the two -- it darkens the pixels
   immediately behind each letter rather than the whole picture, and the
   values are already in the file at .subscribe h2 and .subscribe p. */
/* The container gives up everything but its existence, at every width. These
   four declarations were written in the desktop block and repeated in the
   750-860 one, while the base rule capped it at 640px -- a third measure,
   and the one that made the plate WIDEN as the window closed, since the cap
   held while the gutter it sat inside shrank. It is a centring flex row now
   and nothing else, so the plate is centred against the photograph itself
   rather than against a measure laid over it, and --touch-col is the only
   thing that changes on the way down. */
.home-touch .container {
  max-width: none;
  padding: 0;
  display: flex;
  justify-content: center;
}
.home-touch h2 { margin: 0 0 14px; }
.home-touch p { margin: 0 auto 26px; }

/* ---- The plate ---------------------------------------------------------
   The box the copy sits on, and the whole of the band's structure: a flex
   column one --touch-col wide, centred in the photograph by the container
   above, with the fill at rgba(20,20,20,0.6) -- #141414 at an alpha, the
   way every dark surface here is written.

   This rule is the only place any of that is set. It used to be written
   three and a half times: here for under 860, again in the desktop block
   for above it, and again in each half of the About-Us-curve block that
   sat between them -- which is how the box ended up a different shape in
   four different ranges of the window. The desktop block keeps only what
   is genuinely a desktop decision: the type sizes, the left-ranged copy
   and the band's own inset and depth.

   No radius, at any width. The plate is square where the band around it
   is not, which is the same relationship the About Us copy panel has to
   its own band. */
.home-touch-panel {
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* One box at every width now -- the width, the padding and the fill are
     all set here and nowhere else. Above 860.02 the desktop block used to
     restate the first two and hold the padding at a flat 56px; the 750-860
     and sub-750 blocks each set a third and fourth value. That is what the
     clamp replaces.

     4.2vw passes through both of the values those blocks were stepping
     between: 56px at 1333px of window and above, where it meets the ceiling,
     and 36.1px at 860 -- the About Us panel's number, so the two boxes still
     read as one object at the width they sit closest together. From there it
     keeps giving rather than sitting flat, down to a 20px floor at 476.
     The band's depth follows its padding and its wrapping, so every step
     here was a step in the photograph behind it. */
  width: 100%;
  max-width: var(--touch-col);
  padding: clamp(20px, 4.2vw, 56px);
  background: rgba(20, 20, 20, 0.6);
}
/* The button row centres itself: .hero-actions is already a centred flex row
   -- it was written for the hero, where the same is true -- so nothing here
   has to place it. */

@media (min-width: 860.02px) {
  /* Inset on the sides by --page-gutter and held off Free Membership by the
     page's section seam, so this band and the one below it read as a pair of
     panels floating at the foot of the page rather than one full-bleed strip
     above one inset panel.

     No top margin, for the same reason the membership band has none: the seam
     above is already the category grid's bottom padding, and a margin here
     would stack with it. */
  .home-touch {
    /* No overflow needed with it: this element already clips, which is what
       holds the photograph's scale(1.12) entrance inside the band. */
    border-radius: var(--band-radius);
    margin: 0 var(--page-gutter) calc(var(--grid-gap) * 2);
    /* One of the page's four bands, all the same depth -- see --band-h in
       :root for where the number comes from and why it is measured on the
       container rather than on this section.

       min-height rather than height, unlike the stories and the tile grid,
       which take it outright. Those two hold pictures and can be given an
       exact size; this holds three lines of type, and if they ever outgrow
       the proportion -- a longer heading wrapping, a bigger type scale -- the
       band should grow rather than spill. */
    min-height: var(--band-h);
    /* centre, not stretch. The plate is sized by what it holds and floats in
       the band rather than running its full depth -- so it reads as a box ON
       the photograph, which is what a full-depth panel stops doing: at the
       band's own height it is a column beside a picture, the way
       .home-story-copy is, and the picture is no longer behind it.

       The vertical padding is the floor under that: at --band-h the box has
       room to spare, but it guarantees the box cannot reach the band's edges
       if the copy ever grows. */
    display: flex;
    align-items: center;
    padding: 64px 0;
    /* The plate's width -- --touch-col -- is defined once on the base
       rule up the file and is unbroken from here down; it used to be
       redefined twice below this block. */
  }
/* ---- The copy, ranged left on the plate --------------------------------
   Centring is written into three separate places and all three have to
   move or the block only half-shifts: the section's text-align, the
   paragraph's auto side margins, and .hero-actions -- a centred flex row
   inherited from the hero, where the same was wanted. All three are
   turned over here and only here; under 860.02 the band keeps its centred
   treatment, which is what was asked for at that width.

   The plate itself is centred in the photograph at EVERY width -- that is
   the container's job, up the file, and it is no longer restated here.
   Where the box sits and how the type sits inside it are two separate
   decisions: a centred box of ranged-left copy is a normal thing.

   Nothing dims the photograph any more -- see the note where the ::before
   scrim used to be defined. That matters more with the copy off centre:
   the scrim was a vertical gradient, even across the width, so it dimmed
   the whole picture to serve type that occupies a third of it. Whatever
   replaces it, if anything does, should cover the plate rather than the
   middle. */
  .home-touch { text-align: left; }
  /* Was `0 auto 26px` -- the auto is what centred it. No max-width: the
     plate's own padding is the measure now, where the 112px here assumed
     a flat 56px of it that the fluid padding no longer holds to. */
  .home-touch p { margin: 0 0 26px; }
  .home-touch .hero-actions { justify-content: flex-start; }
/* ---- Why the plate is black at 60% ------------------------------------
   Kept here because it is the reasoning, not a rule; the fill itself is on
   .home-touch-panel up the file, at every width.

   Two fifths of the photograph's own value still comes through. This is
   not carrying the type and never was -- the copy sits over the dark
   canyon wall, which measures about 9.8:1 against white with nothing over
   it at all, 13.6:1 at the old 45% and about 15:1 at this one. The plate
   is a surface, not a scrim, and that is the difference from the ::before
   that used to be here: that one dimmed the whole photograph to serve the
   copy, this covers only what the copy occupies. Which is also why it can
   be taken this far without costing the picture anything -- the frame
   outside the box is untouched, and on a phone that margin of untouched
   photograph is the whole reason the box is inset rather than full-bleed.

   rgba of 20,20,20 rather than of 0,0,0 -- #141414, the value every other
   dark surface on this site is set to, at an alpha. */
  /* The band's type, matching the sections above it: one display size across
     the page, and the copy at the size and weight the story panels use. */
  .home-touch h2 {
    font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
                 'Segoe UI', Roboto, sans-serif;
    font-weight: 700;
    font-size: 2.5rem;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    line-height: 1.1;
    color: #ffffff;
  }
  .home-touch p {
    font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
                 'Segoe UI', Roboto, sans-serif;
    font-weight: 400;
    font-size: 1.125rem;
    line-height: 1.6;
    letter-spacing: 0.005em;
    color: #ffffff;
  }
  /* The About Us band's pair exactly -- white box, near-black label, holding
     through the hover. Everything else about the button (square corners, the
     Barlow label, the arrow that opens out of the right edge) comes from the
     shared .story-cta block up the file; only the colour is a decision this
     section has to make, because .story-cta deliberately leaves the fill to
     whichever surface it lands on. */
  /* ---- Contact Us, sharing the hero's fill ------------------------------
     #9de6cd, the same box the hero carries -- and the same box the two story
     buttons carry -- so the page is one button repeated four times.

     #141414 on it measures 12.8:1, against the 4.5:1 small text wants. See
     the hero's copy of this rule for the reasoning; the two are the same
     button, and so is every other one on the page. */
  .home-touch .story-cta,
  .home-touch .story-cta:hover {
    background: #b02b25;
    color: #ffffff;
  }
  /* No sweep and no colour change. #53534f wiped in from the left and out to
     the right for a while, with the label turning white under it -- first on
     a timer, then per-letter at the panel's own edge, using a gradient
     clipped to the glyphs. All of it is gone: this button holds black on
     white through the hover.

     Which puts it back in step with the two it was copied from. The About Us
     and Cut & Sewn buttons do not change colour either; the arrow opening out
     of the right edge is the whole of their hover, and it is the whole of
     this one's. The .is-hovering / .is-leaving / .is-resetting classes
     main.js puts on every .btn-tan are still arriving here and now match
     nothing, which is the same as on those two. */
  .home-touch .story-cta::after { content: none; }
}

/* ============================================================
   Quick view popup: the panel, and the three controls on it
   ============================================================
   Back to white. The panel wore the home page's gradient for one pass; this
   is --page-ground again, which is #ffffff, and with it every type colour in
   the popup's light block up the file comes back into force on its own --
   they were never removed, only outranked. So nothing here restates them.

   Both elements again. The info column carries a ground of its OWN --
   --modal-panel, the popup's original near-black -- so turning only the modal
   white leaves the right-hand column dark with near-black type set on it.
   That has now been the failure mode twice, in both directions.

   ---- Why this block sits at the very end of the file

   One thing genuinely needs to be last, and it is the controls below. Three
   later blocks would otherwise beat them at equal specificity:

     - "Popup and product page: no borders, filled controls" refills the two
       pickers and the stepper with #f0f0f0. It is the reason the #BBB8B5
       asked for two passes ago never actually appeared: same specificity,
       later in the file, so it simply won and the pickers stayed near-white.
     - `.page-store` and `.page-product` set `.product-price .price`,
       `.price-was` and the three rating values for their own grids. The
       modal is a child of <body>, so those reach INTO the popup on those two
       pages. On a white panel they happen to agree with it, which is why
       nothing about the type has to be restated here -- but they are the
       reason this block cannot simply be folded back up the file either.

   ---- The three controls: Select Size, Select Color, and the quantity

   Light gray with near-black on it: #e6e6e6 carrying #141414.

   Neutral, and that is the notable part. Every light fill this control has
   worn held red a few levels over blue -- a tan tint through most of them, a
   pink one in #f5f0ef -- and it was the single constant across the whole run.
   230/230/230 has no cast at all. On a panel whose type, photography and
   panels are warm, a neutral control is either the quiet thing in the room or
   the one that does not belong to it, and at this lightness there is not
   enough colour in play for it to be the second.

   ---- Where this sits, which is at the bottom of the range

   1.25:1 against the white panel -- a step back up from the near-whites,
   between #f5f0ef and #e2dfda, and about the lightest value that still reads
   as a filled box rather than as a tint.

   The full run, faintest first: #f4f2ef 1.05:1 (bordered), #f0f0f0 1.09:1,
   #f5f3ef 1.11:1, #f5f0ef 1.13:1, #e6e6e6 1.25:1, #e2dfda 1.33:1, #cfccc7
   1.60:1, #BBB8B5 1.97:1, #999691 2.95:1, #767676 4.54:1, #3d3d3d 10.86:1,
   #332a29 13.97:1.

   A component's boundary wants 3:1, and no light value reaches it -- 3:1
   against white needs luminance at or below 0.30, which lands around
   #949494, a MID gray. "Light" and "3:1 below white" are disjoint ranges,
   which is the whole reason this has gone back and forth so many times: the
   requirement and the intent point in opposite directions and no single flat
   fill satisfies both.

   What holds the control together at this value is the TYPE. #141414 on
   #e6e6e6 is 14.76:1 -- as high as it has ever read here -- so the label,
   the chevron, the stepper's arrows and number, every menu row and the
   hairline under the hovered one are all as legible as they can be. The box
   is a tint; the contents are the control.

   The one arrangement that gives both is a border, and this file has already
   built it once: the #f4f2ef pass put an almost-white fill inside a 1.3px
   hairline at #979390, metered to exactly 3.07:1. Same near-white ground as
   this, with an edge that clears. It is one rule away if the boxes ever need
   to be findable without reading them.

   ---- The open state, still one step down

   #dcdcdc, ten levels down and neutral with it. Down rather than up, as on
   every light fill: on a light control the step that reads is toward dark.
   1.37:1 against the panel, so the open picker is still the more visible box
   of the two -- upside down, and unavoidable while the resting value sits
   this close to the ground.

   The quantity stepper is not a drop-down and is here anyway. The three are
   one family of controls stacked in one column, and a difference in grey
   between them would read as a mistake rather than as a distinction. */
/* The white belongs to the INFO half, not to the panel behind it.

   Both used to carry it, and that is what drew the pale rim round the
   corners. The panel paints white and clips it at --qv-radius; the media
   half paints near-black and clips it at the same radius. Antialiasing
   means the outermost pixels of an arc are partly covered, so along that
   curve you get a partly-covered dark over a partly-covered white -- and
   the white shows through. Two rounded layers cannot cancel: the top one
   only ever covers as much as its own coverage allows.

   With the panel transparent there is one painted layer at each corner --
   the media's own dark on the left, the info's own white on the right --
   so each arc antialiases against the dimmed page and nothing bleeds. The
   two halves fill the panel between them at every width, so it has no
   ground of its own left to show. */
.quick-view-modal { background: transparent; }
.quick-view-info { background: var(--page-ground); }
.quick-view-info .color-select-trigger,
.quick-view-info .color-select-list,
.quick-view-actions .qty-stepper {
  background: #e6e6e6;
  border: none;
}
.quick-view-info .color-select-dropdown.is-open .color-select-trigger {
  background: #dcdcdc;
  border: none;
}
/* The stepper's number keeps a transparent ground so one fill runs the width
   of the control rather than a box sitting inside a box. */
.quick-view-actions .qty-input { background: transparent; }
/* Every mark on all three controls, and the hairline that drops in under the
   menu row you are pointing at. Each of these beats a value written directly
   against it further up the file -- the resting row's #141414 from the light
   pass, the hovered row's --offwhite in the shared four-panel block, the
   chosen row's --on-dark in the popup's original dark rules, and the note's
   --on-dark-faint. */
.quick-view-info [data-color-select-label],
.quick-view-info [data-color-select-label].is-placeholder,
.quick-view-info .color-select-trigger svg,
.quick-view-actions .qty-stepper-btn,
.quick-view-actions .qty-input,
.quick-view-info .color-select-list li,
.quick-view-info .color-select-list li:hover,
.quick-view-info .color-select-list li[aria-selected="true"],
.quick-view-info .color-select-list li[data-placeholder][aria-selected="true"],
.quick-view-info .color-select-list li .option-note { color: #141414; }
.quick-view-info .color-select-list li:hover { background: transparent; }
/* The rule that drops in under the menu row you are pointing at. Gray, not
   the near-black the type is: at #141414 it was the same weight as the words
   above it and read as a second line of the list rather than as a mark on
   the one you were on.

   #a8a8a8, after #141414, #7f7f7f and #8f8f8f -- each step lighter, and this
   one a real step rather than a nudge: 25 levels off the last.

   1.90:1 on the menu's own #e6e6e6 fill, against the 3:1 a 1px graphical
   element wants. #7f7f7f at 3.21:1 was the last value above that line, and
   there is very little road between "clears" and "reads as a highlight" on a
   ground this pale -- 230 is the fill and every usable value is crowded
   below it.

   Acceptable here, and the reason is worth stating rather than assuming: the
   row this marks is #141414 at 14.76:1 and is not relying on the line to be
   found. The line says WHICH row, only one is ever drawn, and the pointer is
   already there. It is a confirmation, not an indicator.

   Where the floor is, since this has moved four times: the fill is 230 and
   the line is at 168, so there are about 60 levels left. Somewhere around
   #cfcfcf it stops being visible on the fill at all.

   Not #d6d6d6, which is the gray the View Full Details line takes. That one
   is drawn on the WHITE panel and clears there; on #e6e6e6 it would be
   1.16:1 and effectively gone. Two hover lines, two grounds, two values --
   the ground picks them, not a house colour. */
.quick-view-info .color-select-list li::after { background: #a8a8a8; }
/* ---- The wordmark, a step darker ---------------------------------------
   #9a9691 where it was #BBB8B5. Still a held-back mark rather than a line to
   read -- it says whose popup this is above a product name set at 2rem in
   black -- but the step takes it from 1.97:1 to 3.07:1 on the panel, which is
   the difference between a label you have to look for and one you can see.

   The same #9a9691 that was the controls' hairline until the inversion above
   made borders unnecessary. Not a coincidence worth preserving, but it is
   the one gray in this file chosen against white by measurement, and it is
   the right weight for the same reason here. */
.quick-view-info .qv-mini-logo { color: #9a9691; opacity: 1; }
/* ---- The two rules in the column --------------------------------------
   Both at #e6e6e6 -- the controls' own fill, drawn as a line. They are not
   the same kind of thing, which is worth knowing before either is moved:
   the first is a .detail-divider element above the pickers, and the second
   is .quick-view-link-full's own border-top under Add to Cart, which is part
   of that link rather than a separate element.

   The divider had been removed outright at one point: .quick-view-info
   .detail-divider is in the "no borders, filled controls" list up the file,
   written when every outline on the panel came off together and a hairline
   at 0.14 alpha was not separating anything worth keeping.

   It came back at #3d3d3d, which was a structural rule -- deliberately NOT
   following the control fills around, on the reasoning that a divider is
   about the column rather than about the boxes in it. That is now reversed:
   the rule takes the fills' value, and the column has one light gray running
   through it instead of a dark line above three pale boxes.

   Which means the number goes with it. 1.25:1 against the white panel,
   against the 3:1 a 1px graphical element wants -- where #3d3d3d was 7.16:1.
   It is a seam rather than a rule now: enough to say the header ends and the
   form begins if you are looking, not enough to divide the column on its
   own. The 16px of margin above and below it is doing more of that work than
   the line is.

   border-top rather than border, because .detail-divider is an empty div and
   the base rule draws only its top edge; `border: none` above kills all four,
   so this has to name the one that does the drawing. */
.quick-view-info .detail-divider { border-top: 1.3px solid #e6e6e6; }
/* The line under Add to Cart. --on-dark-border is #383737, a near-black
   chosen to lift a hairline off the popup's original dark panel; left alone
   on the white one it is the darkest mark in the column by some distance and
   the only thing in here still set for that panel. */
.quick-view-info .quick-view-link-full { border-top-color: #e6e6e6; }


/* ============================================================
   Quick view popup: the form column's spacing, and IN STOCK
   ============================================================
   ---- IN STOCK moves into the picker

   It was a block UNDER the Select Color box -- a sibling of the dropdown
   inside .color-select-row, which is flex-direction: column here, so it
   stacked below and took a reserved slot of its own
   (min-height: calc(12px + 1.3em), held so the column would not jump when a
   product's status arrived). That slot was the odd interval in the run: two
   boxes, then a line of red text on the panel, then two more boxes.

   Now it sits inside the box, right of the value and clear of the chevron.
   Absolutely, against .color-select-row rather than the trigger -- the status
   is the trigger's SIBLING, not its child, so the row is the nearest thing
   that can be its positioning context. That works out exactly because the
   open menu is itself absolute against .color-select-dropdown, so the row's
   height is the trigger's height and nothing else: top: 0 / bottom: 0 centres
   the status on the box at any size.

   Not moved in the markup, which was the other way to do this. The status is
   a <div> and the trigger is a <button>, whose content model is phrasing
   only -- a div inside it is invalid, so the move would mean changing the
   element in three pages' copies of the popup and checking what fills it.

   right: 40px clears the chevron: 13px of art plus the trigger's 14px of
   right padding is 27px, and the rest is air. pointer-events: none so the
   status cannot eat a click meant for the button underneath it.

   The dot's 7px top margin goes. That existed to drop it onto the first
   line's baseline in a flex-start block; centred on a single line it is
   simply centred.

   One number moves with the ground. The status is --title-red (#b8322f),
   which read 5.94:1 on the white panel and reads 4.76:1 on the picker's
   #e6e6e6 -- still past the 4.5:1 small text wants, with less room than it
   had. */
.quick-view-info .color-select-row { position: relative; }
.quick-view-info .qv-stock-status {
  position: absolute;
  top: 0;
  bottom: 0;
  right: 40px;
  margin: 0;
  padding: 0;
  min-height: 0;
  align-items: center;
  pointer-events: none;
}
.quick-view-info .qv-stock-status::before { margin-top: 0; }
/* ---- One interval down the column --------------------------------------
   18px between everything: the divider, Select Size, Select Color, the
   quantity stepper. The column had been carrying four different values --
   the divider's 16, the pickers' 20 top with 0 bottom, the actions' 14 top
   and their own 12 gap -- which is what made the spacing read as arbitrary
   rather than as rhythm. Taking the status out of the flow above is half of
   this; the other half is simply saying one number once.

   Add to Cart is the exception, and deliberately: 28px above it rather than
   18. It is the one thing in this column that commits, and the extra 10px is
   what separates it from the stepper it would otherwise look welded to. The
   gap does the work rather than a rule -- there is already a line under the
   button, and a second one over it would box the button in. */
.quick-view-info .detail-divider { margin: 18px 0; }
.quick-view-info .product-color-select { margin: 0 0 18px; }
.quick-view-actions { margin-top: 0; gap: 18px; }
.quick-view-actions [data-qv-add-to-cart] { margin-top: 10px; }


/* ---- The three controls are one height --------------------------------
   46px on all of them, which is what they were always meant to be and were
   not above 1024px.

   The stepper carries `height: 46px` unconditionally, from its own rule near
   the top of the file. The pickers get theirs from a block scoped
   `@media (min-width: 860.02px) and (max-width: 1024px)` -- a narrow-desktop
   layout rule that happens to also be the only place the trigger's height is
   ever set. So on a window wider than 1024px the pickers fall out of that
   query and back to auto: 9px of padding either side of a 0.85rem line box,
   about 42px, against the stepper's 46. Four pixels, on two boxes stacked
   directly above a third, which is exactly the kind of difference that reads
   as sloppy rather than as intentional.

   Declared here rather than by moving the value out of that query, because
   that block is a layout rule for one width band and the height is not --
   pulling one declaration out of it would leave the remaining rules there
   looking like they belong to a set they no longer belong to.

   Both the popup and the product page's detail column. It is one component
   with one bug, and the product page's Select Color sits above a stepper
   with the same unconditional 46px. Scoped through .product-color-select on
   that side so the reviews toolbar's own dropdowns -- which are a smaller
   control at an auto width -- are untouched. */
.quick-view-info .color-select-trigger,
.page-product .product-color-select .color-select-trigger { height: 46px; }
/* ---- Add to Cart takes the Quick Add bar's height ---------------------
   The bar on the product card is what opens this popup, so the button that
   completes the action it started is the same object at the same size. It is
   the one control in this column NOT sized to the three boxes above it, and
   that is the point: 56.375px against their 46 makes it the largest thing
   here, which is what a commit should be.

   Where the number comes from, written as the sum rather than as a literal
   so it can be checked against .cart-overlay-btn without measuring anything:

     line box   0.9375rem x 1.8   = 30.375px
     padding    13px + 13px       = 26px
                                    56.375px

   The 1.8 is body's line-height reaching the bar through `button { font:
   inherit }` in the reset -- the `font` shorthand carries line-height, and
   .cart-overlay-btn overrides the family, size and weight but never that. Set
   only font-size there and the bar would be about 47px on a UA-default
   `normal`, which is close enough to 46 that this whole difference would not
   have been visible. It is 1.8, so it is not.

   The svg beside the label is 18px and never sets the height: the line box
   is taller, and the flex row takes the larger of the two.

   Re-derive if the bar's type or padding moves. A calc() cannot read another
   rule's values, so this tracks the root font-size and nothing else.

   The uneven padding still does its job underneath. .btn centres on the
   cross axis, so 9 top and 11 bottom lift the line box about 1px off centre,
   and lifting it is exactly what caps need -- Barlow Condensed hangs them
   0.300em below the top of their line box and only 0.200em above the bottom.
   Pinning the height did not cost the correction; it stopped the height
   depending on it. */
.quick-view-actions [data-qv-add-to-cart] { height: calc(0.9375rem * 1.8 + 26px); }


/* ---- The hero button, at the same height ------------------------------
   56.375px, which is the Quick Add bar's height and now the popup's Add to
   Cart's -- see the rule above for where the number comes from. Three
   buttons on three different pages at one height, and it is the height of
   the largest of them rather than an average.

   It was 45.875px, arrived at the same way that button was: 1.25rem at
   line-height 1.15 is a 25.875px line box, plus 9 and 11 of padding.

   Written as the same sum rather than as a literal so the two rules can be
   read against each other. A calc() cannot reference another rule, so if the
   Quick Add bar's type or padding ever moves, both of these have to be
   re-derived together -- which is the cost of tying three buttons to one
   number that lives in a fourth.

   Scoped to .hero and inside the desktop query, matching the block that sets
   this button's size in the first place. `.hero-actions` is also the wrapper
   around Learn More, Shop All Products and Contact Us, so an unscoped rule
   would grow all four; and the phone keeps the button it has, like the rest
   of the mobile layout.

   The padding stays 9/24/11. It no longer sets the height, but it still
   holds the label off the side edges, and the uneven top and bottom still
   lift the caps about 1px off centre -- Barlow Condensed hangs them 0.300em
   below the top of their line box and only 0.200em above the bottom. The
   arrow's own geometry is untouched: it is positioned by margins on the
   inline axis and centred on the cross axis, so a taller box moves it down
   and changes nothing else. */
@media (min-width: 860.02px) {
  .hero .hero-actions .story-cta { height: calc(0.9375rem * 1.8 + 26px); }
}


/* ============================================================
   Product page: the detail column matched to the popup
   ============================================================
   The quick view is the same product in a smaller frame, so the two columns
   should be one design. This block is the difference between them, element
   by element -- nothing here is a new decision, every value is read off the
   popup's own rules further up.

   Desktop only, matching the rest of this page's light treatment and leaving
   the phone as it is. The popup's copy of all this is unscoped, because a
   popup is the same panel at every width; a page column is not.

   NOT ported: the close button. The popup is a sheet you dismiss and the page
   is not. */
@media (min-width: 860.02px) {
  /* ---- The header block -------------------------------------------------
     The name takes the popup's treatment, which is the band headings' --
     700, caps, tight tracking -- where it had been sharing
     `.product-detail-header h1, .quick-view-info h3` at 1.5625rem/400 and the
     popup had overridden its half only. 2rem here as there: the two columns
     are close enough in width that a size step between them would read as a
     mistake rather than as hierarchy.

     The wordmark takes #9a9691, deliberately held back at 3.07:1 -- it names
     the shop above a product name at 18.4:1 and is not meant to compete with
     it. */
  .page-product .product-detail-header h1 {
    font-weight: 700;
    text-transform: uppercase;
    font-size: 2rem;
    letter-spacing: 0.02em;
    line-height: 1.1;
    color: #141414;
  }
  .page-product .qv-mini-logo { color: #9a9691; opacity: 1; }
  /* Share: solid, receding to #7f7f7f under the pointer. It was the inverse
     -- 0.6 alpha lifting to solid -- which is the treatment the whole site
     used before these two columns went light. 18.4:1 at rest, 4.00:1 lit. */
  .page-product .pd-share-btn { color: #141414; }
  .page-product .pd-share-btn:hover { color: #7f7f7f; }

  /* ---- The two rules ----------------------------------------------------
     Back, at #e6e6e6 -- the controls' own fill drawn as a line, 1.25:1 on
     white. They were removed outright by the light treatment above, at a
     point when every hairline on this page came off together.

     This page keeps BOTH of its dividers where the popup kept one. The
     popup's second line is .quick-view-link-full's border-top, which is part
     of the View Full Details link rather than an element; this page has no
     such link -- it IS the full details -- so the line under its cart row is
     a real .detail-divider-after-cart. Same result, two mechanisms. */
  .page-product .detail-divider { border-top: 1.3px solid #e6e6e6; margin: 18px 0; }

  /* ---- The three controls -----------------------------------------------
     #e6e6e6 with #141414 on it, no borders, 46px each. The heights were
     matched in the rule above this block; these are the colours.

     The open picker steps to #dcdcdc, ten levels down -- on a light control
     the step that reads is toward dark.

     The placeholder stops sitting back at 0.55 alpha. Two values in a picker
     is a distinction the popup gave up when the fills went light, and the
     chevron and the menu say it is a picker without help from the type. */
  .page-product .color-select-trigger,
  .page-product .color-select-list,
  .page-product .product-detail-cart-row .qty-stepper {
    background: #e6e6e6;
    border: none;
  }
  .page-product .color-select-dropdown.is-open .color-select-trigger {
    background: #dcdcdc;
    border: none;
  }
  .page-product .product-detail-cart-row .qty-stepper { height: 46px; }
  .page-product .qty-input { background: transparent; }
  .page-product [data-color-select-label],
  .page-product [data-color-select-label].is-placeholder,
  .page-product .color-select-trigger svg,
  .page-product .color-select-list li,
  .page-product .color-select-list li:hover,
  .page-product .color-select-list li[aria-selected="true"],
  .page-product .color-select-list li[data-placeholder][aria-selected="true"],
  .page-product .color-select-list li .option-note { color: #141414; }
  .page-product .color-select-list li:hover { background: transparent; }
  /* #a8a8a8, which is 1.90:1 on the menu's own fill -- under the 3:1 a 1px
     graphical element wants, and acceptable for the reason it is in the
     popup: the row it marks is #141414 at 14.76:1, only one line is ever
     drawn, and the pointer is already on that row. */
  .page-product .color-select-list li::after { background: #a8a8a8; }

  /* ---- IN STOCK moves into the picker -----------------------------------
     Same move as the popup's, and the same reason it works: the status is the
     trigger's SIBLING, so .color-select-row is the nearest thing that can be
     its positioning context -- and because the open menu is itself absolute
     against .color-select-dropdown, that row's height is exactly the
     trigger's, so top: 0 / bottom: 0 centres the status on the box.

     right: 40px clears the chevron -- 13px of art plus the trigger's 14px of
     right padding. pointer-events: none so it cannot eat a click meant for
     the button under it. The dot's 7px top margin goes: it existed to drop
     the dot onto a first line's baseline in a flex-start block, and centred
     on one line it is simply centred. */
  .page-product .color-select-row { position: relative; }
  .page-product .qv-stock-status {
    position: absolute;
    top: 0;
    bottom: 0;
    right: 40px;
    margin: 0;
    padding: 0;
    min-height: 0;
    align-items: center;
    pointer-events: none;
  }
  .page-product .qv-stock-status::before { margin-top: 0; }

  /* ---- The cart row becomes a column ------------------------------------
     The stepper and the button sat side by side above 1024px -- the rule that
     stacks them lives in a `(min-width: 860.02px) and (max-width: 1024px)`
     block, so the wide layout was the one that never got it. The popup has
     always stacked them, and stacked full-width is also simply the better
     shape: a stepper is a small control and a commit is a large one, and
     putting them on one line makes the commit share a row with something it
     is not comparable to. */
  .page-product .product-detail-cart-row {
    flex-direction: column;
    align-items: stretch;
    flex-wrap: nowrap;
    gap: 18px;
    margin: 0;
  }
  .page-product .product-detail-cart-row .qty-stepper,
  .page-product .product-detail-cart-row [data-add-to-cart-main] { width: 100%; }

  /* ---- Add to Cart ------------------------------------------------------
     The popup's button exactly: 1.25rem where this was 1.125, and 56.375px
     tall -- the Quick Add bar's height, which the popup's copy and the hero's
     button also take. See that rule for where the number comes from.

     The hover is the fill and nothing else. #141414 to #b02b25 over 0.22s,
     white throughout, which is the Quick Add bars' pair as well. The 1.01
     box scale this used to carry is gone: growth on hover came off every
     button on the site in the same pass, and a button that changes colour AND
     size is doing two things where one is unambiguous.

     will-change goes with the transform it existed for. */
  .page-product .btn-cart {
    background: #141414;
    color: #ffffff;
    font-size: 1.25rem;
    line-height: 1.15;
    padding: 9px 24px 11px;
    height: calc(0.9375rem * 1.8 + 26px);
    transition: background-color 0.22s ease;
    will-change: auto;
    /* 10px on top of the row's 18px gap, so the commit sits 28px below the
       stepper rather than in step with it -- the popup's own offset, and for
       the same reason: it is the one control in the column that acts, and
       the extra distance is what stops it looking welded to the control
       above it. */
    margin-top: 10px;
  }
  .page-product .btn-cart:hover,
  .page-product .btn-cart:focus-visible {
    background: #b02b25;
    color: #ffffff;
    transform: none;
  }

  /* ---- One interval down the column -------------------------------------
     18px between the divider, Select Size, Select Color and the stepper, with
     the cart row's own 18px gap carrying it through to Add to Cart. The
     column had been running on four different values, exactly as the popup's
     did before the same pass. */
  .page-product .product-color-select { margin: 0 0 18px; }
}


/* ============================================================
   The buy bar, rebuilt
   ============================================================
   The sticky bar that drops in once the real Add to Cart has scrolled clear.

   Two things brought it here. It had drifted out of step with the page it
   belongs to -- rounded controls and a rounded red pill, on a page whose
   every other control had gone square -- and then the product column's light
   treatment started reaching into it: `.page-product .color-select-trigger`
   and friends match the bar's copies as readily as the column's, so its
   pickers came out as light gray boxes with near-black type on a near-black
   bar. This block is written LAST for exactly that reason. It is the bar's
   own description and it outranks anything that arrives by accident.

   ---- Thinner

   38px controls, 6px of padding: about 50px, where 46px controls on 10px
   came to 66. The bar is chrome that covers the page while you read it, so
   the less of the page it takes the better -- and at 38px the controls are
   still comfortably above the 24px minimum a pointer target wants.

   ---- Gray and white type

   The eyebrow #9a9691 and the name #ffffff, which is the pairing the popup
   and the product column both use for the same two lines. It replaces
   --on-dark at 0.75 opacity on one and --on-dark on the other: the same
   near-white twice, distinguished only by an alpha, where the point of the
   pair is that they are different KINDS of thing.

   #9a9691 on this ground is 6.0:1 and white is 17.4:1, both well past 4.5.

   ---- No borders, no round corners

   The pickers, the quantity pill and the button. The pill was 999px and the
   pickers --radius-sm; every control on the product column and in the popup
   has been square for some time and these were the last rounded ones. The
   1.3px --border-strong outlines go with them: a filled control on a bar
   this dark is its own edge, which is the same reasoning the popup's fills
   came to.

   #333333 for the fills, carrying white. It is a step up from the bar's
   #111111 rather than the near-black --field-fill they had, so the controls
   sit ON the bar instead of being cut out of it -- 2.3:1 against the bar for
   the box, and white on the fill at 11.9:1.

   ---- The button is white, and turns red

   #ffffff with #141414 on it, going to #b02b25 with white over 0.22s. That
   is the one place this bar deliberately does NOT match the page: the
   column's Add to Cart is black going red, because it sits on white and the
   button has to be the dark thing. Here the ground is already near-black, so
   the same logic inverts and the button is the light thing. Both end at the
   same red, which is what keeps them the same button.

   #141414 on white is 18.4:1 and white on #b02b25 is 6.53:1. */
@media (min-width: 860.02px) {
  /* Vertical only. The element is `container pd-buybar-inner`, and .container
     is what gives the bar its side margins -- `padding: 0 var(--page-gutter)`,
     the same gutter every other row on the site is set to. A `padding: 6px 0`
     shorthand here reset that to zero and put the wordmark against the left
     edge of the window and Add to Cart against the right. Two longhands
     instead, so the gutter survives. */
  .pd-buybar-inner { padding-top: 6px; padding-bottom: 6px; }
  .pd-buybar .pd-buybar-eyebrow { color: #9a9691; opacity: 1; }
  .pd-buybar-name { color: #ffffff; }

  .pd-buybar .color-select-trigger,
  .pd-buybar .qty-stepper,
  .pd-buybar-color .color-select-list {
    background: #333333;
    border: none;
    border-radius: 0;
  }
  .pd-buybar .color-select-trigger { height: 38px; }
  .pd-buybar .qty-stepper { height: 38px; }
  /* The stepper's own arrows come down with the pill they sit in -- 38px
     buttons inside a 38px control would fill it edge to edge. */
  .pd-buybar .qty-stepper-btn { width: 32px; height: 32px; }
  .pd-buybar .color-select-dropdown.is-open .color-select-trigger {
    background: #3d3d3d;
    border: none;
  }
  /* Every mark on the three controls. Named against the product column's
     light rules, which reach in here and paint them #141414. */
  .pd-buybar [data-color-select-label],
  .pd-buybar [data-color-select-label].is-placeholder,
  .pd-buybar .color-select-trigger svg,
  .pd-buybar .qty-stepper-btn,
  .pd-buybar .qty-input,
  .pd-buybar-color .color-select-list li,
  .pd-buybar-color .color-select-list li:hover,
  .pd-buybar-color .color-select-list li[aria-selected="true"],
  .pd-buybar-color .color-select-list li[data-placeholder][aria-selected="true"],
  .pd-buybar-color .color-select-list li .option-note { color: #ffffff; }
  .pd-buybar-color .color-select-list li:hover { background: transparent; }
  .pd-buybar-color .color-select-list li::after { background: #ffffff; }
  .pd-buybar .qty-input { background: transparent; }

  /* Two classes, not one, and that is not style. The element is
     `btn btn-primary btn-cart pd-buybar-add`, so every `.page-product
     .btn-cart` rule in the column's block matches it at two classes -- the
     56.375px height, the 1.25rem type, the 9/24/11 padding, the 10px top
     margin that drops the column's button clear of its stepper. A single
     `.pd-buybar-add` loses all of those on specificity no matter how late it
     sits. `.pd-buybar .pd-buybar-add` ties and wins on order; the hover pair
     needs three to clear `.page-product .btn-cart:hover`.

     margin-top is named for that reason and no other: it is 0 here because
     the bar is a row, and the 10px it would otherwise inherit was measured
     for a column. */
  .pd-buybar .pd-buybar-add {
    height: 38px;
    padding: 0 28px;
    margin-top: 0;
    font-size: 1rem;
    line-height: 1;
    border-radius: 0;
    box-shadow: none;
    background: #ffffff;
    color: #141414;
    transition: background-color 0.22s ease, color 0.22s ease;
    transform: none;
  }
  .pd-buybar .pd-buybar-add:hover,
  .pd-buybar .pd-buybar-add:focus-visible {
    background: #b02b25;
    color: #ffffff;
    transform: none;
  }
  .pd-buybar .pd-buybar-add::after { content: none; }
}


/* ============================================================
   Product page: the accordions, the reviews, and the related row
   ============================================================
   Everything below this page's buy column that the light pass did not reach.
   The pattern is the one the popup's price turned up: the light block paints
   in broad two-class selectors, and anything named more specifically further
   up the file kept the near-white it was given for the dark page. On white
   that is not "a bit low" -- it is invisible.

   Desktop only, matching the rest of this page's light treatment. */
@media (min-width: 860.02px) {
  /* ---- The accordion panels ---------------------------------------------
     `.product-detail-accordion .accordion-panel > p` and
     `[data-product-specs] li` both name --text-muted, and both outrank
     `.page-product .accordion-panel`, so Overview, Details and Specs have
     been white on white since this page turned over. */
  .page-product .product-detail-accordion .accordion-panel > p,
  .page-product [data-product-specs] li,
  .page-product .accordion-panel a { color: #141414; }

  /* ---- Customer Reviews -------------------------------------------------
     Every line of type in the section, at #141414. The values it replaces
     are --on-dark, --on-dark-faint, --text and --text-faint -- four names for
     near-white, which is why this needs a list rather than one rule.

     Placeholders are the exception, at 0.55 alpha: a placeholder set at the
     same strength as a typed value stops reading as a prompt. */
  .page-product .reviews-box-title,
  .page-product .reviews-box-note,
  .page-product .product-reviews-desktop-heading,
  .page-product .product-reviews-heading,
  .page-product .product-reviews-heading-count,
  .page-product .product-reviews-count,
  .page-product .product-reviews-empty,
  .page-product .product-reviews-visible-count,
  .page-product .product-reviews-purchase-note,
  .page-product .rating-breakdown-label,
  .page-product .rating-breakdown-count,
  .page-product .rating-breakdown-row:hover .rating-breakdown-label,
  .page-product .rating-breakdown-row:hover .rating-breakdown-count,
  .page-product .rating-breakdown-row.is-active .rating-breakdown-label,
  .page-product .rating-breakdown-row.is-active .rating-breakdown-count,
  .page-product .rating-breakdown-row:disabled:hover .rating-breakdown-label,
  .page-product .rating-breakdown-row:disabled:hover .rating-breakdown-count,
  .page-product .qv-review-date,
  .page-product .qv-review-text,
  .page-product .qv-review-author,
  .page-product .review-field-label,
  .page-product .review-field input,
  .page-product .review-field textarea,
  .page-product .reviews-search-input,
  .page-product .product-reviews-show-more { color: #141414; }
  .page-product .review-field input::placeholder,
  .page-product .review-field textarea::placeholder,
  .page-product .reviews-search-input::placeholder { color: rgba(20, 20, 20, 0.55); }
  /* The review card's divider and the form fields' own edges, which were a
     light hairline lifting off a dark page and are a scar on a white one. */
  .page-product .qv-review-card { border-bottom-color: rgba(20, 20, 20, 0.14); }
  .page-product .review-field input,
  .page-product .review-field textarea {
    background: #f4f2ef;
    border-color: rgba(20, 20, 20, 0.14);
  }
  /* The empty stars in both pickers -- the outline row the gold is clipped
     over. rgba(251, 247, 243, 0.18) is near-white at a fifth, so a 3-star
     rating drew three stars and a gap. Same alpha, inverted. */
  .page-product .review-form-star svg,
  .page-product .review-star-picker-inline .review-star-btn svg {
    fill: rgba(20, 20, 20, 0.18);
    stroke: rgba(20, 20, 20, 0.18);
  }
  .page-product .review-star-picker-inline .review-star-btn { border-color: rgba(20, 20, 20, 0.14); }

  /* ---- The breakdown bars -----------------------------------------------
     #e6e6e6, the same light gray the pickers and the dividers carry, so the
     five tracks read as part of the page's own furniture rather than as five
     dark slots cut into it. It was --on-dark-border (#383737), a near-black
     drawn to be a step UP from a near-black page -- on white it is the
     darkest thing in the section by some way, and five of them stacked read
     as the content rather than as what the content sits in.

     1.25:1 against the page, which is fine here for a reason it is not
     elsewhere: this track is not a boundary or an indicator, it is the empty
     part of a bar, and what carries the information is the FILL against it.
     That pair is #b8322f on #e6e6e6, 4.8:1. */
  .page-product .rating-breakdown-track { background: #e6e6e6; }

  /* ---- The score --------------------------------------------------------
     #b02b25, the site's red, on the one number the section exists to report.
     6.53:1 on white, and at 2.6rem it is large text anyway, where the bar is
     3:1. It was --on-dark. */
  .page-product .product-reviews-score { color: #b02b25; }

  /* ---- More from this category ------------------------------------------
     The store grid's cards, exactly: the Quick Add bar parked below the
     photograph and sliding up over it on hover, rather than welded under it
     and always showing. The two grids run the same markup from
     productCardMarkup, and the related row is the store grid with four
     products in it, so there was never a reason for them to behave
     differently -- the store simply got the treatment first.

     Keyed to the WRAP's hover and not the photo's. The bar is a sibling of
     .product-media, so once it has slid up the pointer is on the bar and no
     longer on the photo -- keyed to the photo it would retract the moment you
     reached for it. The plate's zoom moves to the same hover for the same
     reason.

     Absolute, so the bar stops taking height: these cards get shorter and
     the name and price move up to meet the photograph, which is the trade for
     the bar being over the image rather than beside it. */
  .page-product .product-media-wrap { overflow: hidden; }
  .page-product .product-media-actions {
    border: none;
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    transform: translateY(100%);
    transition: transform 0.32s cubic-bezier(0.33, 1, 0.68, 1);
  }
  .page-product .product-media-wrap:hover .product-media-actions,
  .page-product .product-media-wrap:focus-within .product-media-actions {
    transform: translateY(0);
  }
  .page-product .product-media-soon { transition: transform 0.4s cubic-bezier(0.33, 1, 0.68, 1); }
  .page-product .product-media-wrap:hover .product-media-soon { transform: scale(1.06); }
  @media (prefers-reduced-motion: reduce) {
    .page-product .product-media-actions,
    .page-product .product-media-soon { transition: none; }
    .page-product .product-media-wrap:hover .product-media-soon { transform: none; }
  }
}


/* ---- Every rule on the product page is the popup's -------------------
   1.3px of #e6e6e6, which is what .detail-divider carries in the quick view
   and in this page's own buy column. The page had four other kinds of
   divider and no two of them agreed:

     .product-reviews-divider          --border, still drawn
     .product-reviews-desktop-divider  --border, still drawn
     .accordion-item                   --border on the bottom, still drawn
     .qv-review-card                   --on-dark-border on the bottom
     .reviews-box                      --border-strong, all four sides
     .section-ruled / .section-alt     removed outright by the light pass

   --border and --on-dark-border are near-blacks -- hairlines drawn to lift
   off a near-black page. Against white they are the darkest marks in their
   sections, which is why the reviews area reads as a stack of boxes and the
   buy column above it does not.

   1.25:1 on white, well under the 3:1 a 1px graphical element wants, and
   that is the point of the value rather than a compromise in it: these are
   seams saying where one part ends, not indicators. The space around them
   does the dividing -- 18px in the buy column, 22px under the reviews
   headings -- and the line only confirms it.

   The two section rules that were removed stay removed. They separated
   whole page sections on a dark page, and the sections have their own
   padding; a rule across the full width of a white page is a much louder
   mark than a rule inside a column. */
@media (min-width: 860.02px) {
  .page-product .product-reviews-divider,
  .page-product .product-reviews-desktop-divider,
  .page-product [data-product-reviews-desktop-accordion] .product-reviews-desktop-divider {
    border-top: 1.3px solid #e6e6e6;
  }
  .page-product .accordion-item { border-bottom: 1.3px solid #e6e6e6; }
  .page-product .product-detail-accordion .accordion-item:last-child { border-bottom: none; }
  .page-product .qv-review-card { border-bottom: 1.3px solid #e6e6e6; }
  .page-product .product-reviews-list .qv-review-card:last-child,
  .page-product .qv-review-card.is-last-visible { border-bottom: none; }
  /* The three snapshot boxes. All four sides, where every other rule here is
     one edge -- they are boxes rather than seams, and the light gray is what
     stops three --border-strong frames reading as the loudest thing on the
     page. */
  .page-product .reviews-box { border: 1.3px solid #e6e6e6; }
}


/* ============================================================
   The header's scroll behaviour, on three more pages
   ============================================================
   The store, About and Contact pages take what the home page has had: the
   header sticks to the top, slides away once you are past the picture it
   belongs to, comes back on any upward scroll, and condenses to 64px while
   it is away from the top.

   Written as a selector list beside the home page's rules rather than as a
   copy of them, so the four cannot drift. Everything here is the home
   block's value verbatim -- see it for why each number is what it is; the
   two timings in particular are per-direction on purpose.

   What is NOT ported is the dark strip. `.page-home` paints
   `linear-gradient(#141414, #141414)` across the top --chrome-h of the body,
   so the page appears to come out from under the bar while the header is
   still over the page rather than over the hero. That is a home page problem
   specifically: its header sits ABOVE a photograph that starts lower down.
   On these three the header sits on its own ground at rest -- the product
   page's white, the About and Contact pages' photo headers -- and a strip
   would be a dark band painted over the thing that is already there.

   The trigger is settled in initHeroChrome: the store, About and Contact
   pages hand it their .page-header, which plays the hero's part.

   The product page is deliberately NOT here. It was for one pass and came
   back out: it opens on a breadcrumb and a sticky gallery, and it already has
   chrome of its own in .pd-buybar, which drops in from the top once the real
   Add to Cart has scrolled clear. Two bars arriving and leaving at that edge
   on their own schedules is one too many. */
@media (min-width: 860.02px) {
  .page-store .site-header,
  .page-about .site-header,
  .page-contact .site-header {
    position: sticky;
    top: 0;
    transition: transform 0.45s cubic-bezier(0.4, 0, 0.2, 1);
  }
  body.page-store .header-inner,
  body.page-store .header-inner .logo-skull,
  body.page-about .header-inner,
  body.page-about .header-inner .logo-skull,
  body.page-contact .header-inner,
  body.page-contact .header-inner .logo-skull {
    transition: height 0.55s cubic-bezier(0.22, 1, 0.36, 1);
  }
  body.page-store.is-scrolled .header-inner,
  body.page-store.is-scrolled .header-inner .logo-skull,
  body.page-about.is-scrolled .header-inner,
  body.page-about.is-scrolled .header-inner .logo-skull,
  body.page-contact.is-scrolled .header-inner,
  body.page-contact.is-scrolled .header-inner .logo-skull {
    transition: height 0.3s ease;
  }
  body.page-store.is-scrolled .site-header,
  body.page-about.is-scrolled .site-header,
  body.page-contact.is-scrolled .site-header { --header-h: 76px; }
  body.page-store.is-scrolled .header-inner .logo-skull,
  body.page-about.is-scrolled .header-inner .logo-skull,
  body.page-contact.is-scrolled .header-inner .logo-skull { height: 66px; }
  body.page-store.is-chrome-away .site-header,
  body.page-about.is-chrome-away .site-header,
  body.page-contact.is-chrome-away .site-header {
    transform: translateY(-100%);
  }
  /* The leaving is motion; the holding is not. Under the preference the
     chrome still sticks and still goes, it simply does not travel. */
  @media (prefers-reduced-motion: reduce) {
    .page-store .site-header,
    .page-about .site-header,
    .page-contact .site-header { transition: none; }
    body.page-store .header-inner,
    body.page-store .header-inner .logo-skull,
    body.page-about .header-inner,
    body.page-about .header-inner .logo-skull,
    body.page-contact .header-inner,
    body.page-contact .header-inner .logo-skull { transition: none; }
  }
}


/* ---- The footer's social row, back on the margin ----------------------
   .icon-btn is a 40px box round an 18px glyph, so the artwork sits 11px in
   from the button's own left edge -- (40 - 18) / 2. In the footer these
   buttons have no border, so that 11px is invisible: what you see is the
   first icon starting 11px right of the wordmark above it and of the column
   headings across from it, with nothing to explain why.

   The row is pulled back by exactly that inset, so the Instagram mark lands
   on the same line as everything else in the column. It is optical
   alignment, which is why the number is derived from the button and the
   glyph rather than chosen: change either and this has to change with it.

   The box keeps its size. The 40px is the tap target -- pulling the ROW is
   what moves the artwork without shrinking what the pointer has to hit, and
   the 11px that now hangs past the margin is empty and unclickable-looking
   either way.

   Desktop only. On a phone this row is centred (justify-content: center in
   the mobile footer block), and a negative left margin on a centred row just
   moves it off centre by half of itself. */
@media (min-width: 860.02px) {
  .footer-brand .footer-social { margin-left: -11px; }
}


/* ============================================================
   A narrowed desktop window keeps the desktop treatment
   ============================================================
   Everything the site does with colour and with buttons is written inside
   the min-width:860.02px block, so a window dragged under 860 did not just
   reflow -- it changed identity. The ground went from white to near-black,
   the four red buttons became grey translucent pills, the header's controls
   grew boxes around them, and the membership band picked up a topo texture
   and a pair of bare underlines for fields. That is the phone's look, and a
   desktop window at 700px is not a phone.

   So the LOOK comes down, with no floor under it: one identity at every
   width. It was stopped at 600px to begin with, on the reasoning that a
   desktop window does not go under about 640 and a phone should keep what it
   was written for -- but that left the site changing character halfway down,
   which is the thing this block exists to stop.

   LAYOUT is what still moves, and that distinction is the whole rule here.
   The stories stay stacked (see the 1150px block up the file), the
   membership row stays one centred column, and the category tiles still fall
   to a single column under 600px. Only fill, type and edge cross the line;
   how many things sit in a row never does.

   Values are not invented. Every one was read off the computed style of the
   same element at 900px, which is the narrowest width that was already
   correct. */
@media (max-width: 860.01px) {

  /* ---- 1. The header's three controls lose their boxes ----------------
     .icon-btn is a 40px tile with a 1.3px border round an 18px glyph. Above
     860.02px .site-header .icon-btn drops the border and narrows the box to
     the glyph, so the mark IS the button; below it the tiles came back.

     The hamburger is the odd one: .nav-toggle carries its own border and is
     never touched by the desktop rule, because above 1024px it is not on
     screen to touch. So between 860 and 1024 the search and cart sat bare
     next to a boxed hamburger. It is listed here with them. */
  .site-header .icon-btn,
  .site-header .nav-toggle {
    border: none;
    /* Sized to the glyph, like the desktop rule. The 40px tile is invisible
       once the border goes but is still spent, so three marks 18px apart
       read as 40px apart. Height stays 40px: .cart-count is positioned
       against this box and moving the height would lift the badge. */
    width: 28px;
    color: rgba(251, 247, 243, 0.72);
  }
  /* 24px, not the desktop rule's 28px. That size was drawn for an 82px bar;
     this one is 66px, and 28px of glyph in it crowds the wordmark. */
  .site-header .icon-btn svg,
  .site-header .nav-toggle svg { width: 32px; height: 32px; }
  .site-header .header-actions { gap: 18px; }

  /* ---- 2. The white run -----------------------------------------------
     --page-ground, the same token the desktop block paints these three
     with. The body shows only in the gutters beside the bands; the bands
     themselves paint their own ground, which is why this is three rules and
     not a repaint of the page. */
  .page-home { background: var(--page-ground); }
  .page-home .section.home-featured,
  .page-home .section.home-about { background: var(--page-ground); }

  /* The copy panel has to come with the white, and this is not decoration.
     The heading and the paragraph in these two rows are WHITE -- they are
     written for a dark ground, and above 860.02px that ground is this panel.
     Paint the section white without it and the copy disappears into it.

     --panel-ground and 56px of padding are the desktop rule's, the padding
     down to 36px because the band is narrower here. */
  .page-home .home-story-copy,
  .page-about .home-story-copy {
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: 36px;
    background: var(--panel-ground);
  }
  /* The row curves and clips like the tiles above it and Get In Touch below,
     which is what the desktop block does with the same token. Stacked, the
     panel and the photo are still one band and only the four outer corners
     should round. */
  .page-home .home-story,
  .page-about .home-story {
    border-radius: var(--band-radius);
    overflow: hidden;
    /* The base rule holds the two halves 28px apart. On the dark ground that
       seam was invisible; on white it is a bar splitting the band in two.
       gap: 0 is what the desktop block sets, and for the same reason -- the
       panel and the photograph are meant to meet edge to edge and read as one
       band. The breathing room is the panel padding above. */
    gap: 0;
  }

  /* ---- 3. The four red buttons ----------------------------------------
     Shop The Store, Learn More, Shop All Products and Contact Us. Under 860
     they fell back to .btn, which is a 999px pill with 0.2em of tracking --
     and for three of the four, a flat grey fill.

     #b02b25 is --btn-red's value, literal here exactly as it is in the
     desktop rule: a button over a panel or a photograph should not change
     colour because the panel did. White on it is 6.53:1. */
  .page-home .hero .hero-actions .story-cta,
  .page-home .home-story-copy .story-cta,
  .page-home .home-touch .story-cta,
  .page-about .home-touch .story-cta {
    border-radius: 0;
    background: #b02b25;
    color: #ffffff;
    font-weight: 500;
    letter-spacing: 0.06em;
    gap: 0;
    box-shadow: none;
    padding: 8px 22px 10px;
    font-size: 1.125rem;
    /* 1.15, the desktop value. Without it the label inherits the body 1.8 and
       the box comes out 13px taller than the same button at 900px -- the
       padding is only half of what sets a button height. */
    line-height: 1.15;
  }
  /* The hero's is a size up from the other three, as it is above 860.02px. */
  .page-home .hero .hero-actions .story-cta {
    font-size: 1.25rem;
    padding: 9px 24px 11px;
    /* The hero button alone is given an explicit height above 860.02px, so
       it matches Add to Cart on the product page and the quick view. Padding
       and line-height on their own land it 10px short of that. */
    height: calc(0.9375rem * 1.8 + 26px);
  }
  /* .btn-tan and friends carry a sweep on ::after for the hover wipe. The
     desktop rules turn it off on these four -- a square red box does not
     want a pill-shaped wash sliding under its label -- and so does this. */
  .page-home .hero .hero-actions .story-cta::after,
  .page-home .home-story-copy .story-cta::after,
  .page-home .home-touch .story-cta::after,
  .page-about .home-touch .story-cta::after { content: none; }

  /* ---- 4. Free Membership ---------------------------------------------
     The band was on the phone's treatment: a near-black ground with the topo
     texture washing over it, a rule top and bottom, a title-case heading and
     two fields drawn as bare underlines. Above 860.02px it is a flat
     --panel-foot strip, no texture, no rules, an uppercase heading and two
     filled boxes. It takes the second here.

     What does NOT come down is the two-column row. At 700px the container is
     644px and the card's own padding takes 88 of it, so two columns would be
     264px each -- FREE MEMBERSHIP wraps and the fields stop being fields. It
     stays one centred column, the way the stories stay stacked. */
  .page-home .membership-strip,
  .page-store .membership-strip,
  .page-product .membership-strip {
    background: var(--panel-foot);
    border-top: none;
    border-bottom: none;
    padding: 0;
  }
  /* The topo wash, off. */
  .page-home .membership-strip::before,
  .page-store .membership-strip::before,
  .page-product .membership-strip::before { content: none; }
  .page-home .membership-card,
  .page-store .membership-card,
  .page-product .membership-card {
    max-width: none;
    margin: 0;
    padding: 36px 44px;
  }
  .page-home .membership-strip h2,
  .page-store .membership-strip h2,
  .page-product .membership-strip h2 {
    font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
                 'Segoe UI', Roboto, sans-serif;
    font-weight: 700;
    text-transform: uppercase;
    font-size: 2.5rem;
    letter-spacing: 0.02em;
    line-height: 1.1;
    color: #ffffff;
  }
  .page-home .membership-strip p,
  .page-store .membership-strip p,
  .page-product .membership-strip p { color: #ffffff; }
  .page-home .membership-intro .subscribe-block-desc,
  .page-store .membership-intro .subscribe-block-desc,
  .page-product .membership-intro .subscribe-block-desc {
    max-width: 46ch;
    margin: 10px auto 0;
  }
  /* Fields, from underline to filled box. The fill is a translucent white
     rather than a flat colour, so the field lifts by the same amount however
     the ground moves. All four values are the desktop rule's. */
  .page-home .membership-strip input,
  .page-store .membership-strip input,
  .page-product .membership-strip input {
    font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
                 'Segoe UI', Roboto, sans-serif;
    font-size: 1.0625rem;
    padding-left: 20px;
    background: rgba(255, 255, 255, 0.1);
    color: #ffffff;
    border-color: rgba(255, 255, 255, 0.22);
  }
  .page-home .membership-strip input::placeholder,
  .page-store .membership-strip input::placeholder,
  .page-product .membership-strip input::placeholder { color: rgba(255, 255, 255, 0.55); }
  .page-home .membership-strip .subscribe-arrow,
  .page-store .membership-strip .subscribe-arrow,
  .page-product .membership-strip .subscribe-arrow { color: #ffffff; }
}


/* ============================================================
   ...and the chrome and the arrows come with it
   ============================================================
   Second pass on the same range. The block above brought the fill, the type
   and the edges down; these are the three things it left behind -- the
   sliding arrow on the four buttons, the header's furniture, and the promo
   bar. All three are sized or switched by rules that live outside the
   860.02px block, which is why carrying that block's look down did not move
   them. Values here are the ones measured at 900px, same as above. */
@media (max-width: 860.01px) {

  /* ---- The arrow, and the hover it belongs to --------------------------
     .story-cta > svg is display: none on the base rule and turned back on
     inside the desktop block, so under 860 the four buttons were a label in
     a box with nothing to slide. The whole hover was the arrow: the button
     opens to the right and the arrow travels in through the space it makes.

     At rest the arrow sits translated 27px right of where it lands and the
     box is pulled in by a negative margin-right, so neither is visible --
     the button measures label plus padding. On hover the margin relaxes and
     the transform goes to 0, which widens the box and walks the arrow into
     the room at the same time.

     -6px rather than 0 on the open state: the gap past the arrow is this
     margin plus the button's 22px of padding-right, and 0 left as much empty
     red out there as the label gets on its other side. All of these are the
     desktop rule's numbers. */
  .page-home .hero-actions .story-cta > svg,
  .page-home .home-touch .story-cta > svg,
  .page-about .home-touch .story-cta > svg {
    display: block;
    width: 27px;
    height: 14px;
    margin-left: 16px;
    margin-right: -43px;
    transform: translateX(27px);
    flex-shrink: 0;
    transition: margin-right 0.3s cubic-bezier(0.33, 1, 0.68, 1),
                transform 0.3s cubic-bezier(0.33, 1, 0.68, 1);
  }
  /* The hero's arrow is a size up, as its label is. */
  .page-home .hero .hero-actions .story-cta > svg {
    width: 30px;
    margin-left: 18px;
    margin-right: -48px;
  }
  .page-home .hero-actions .story-cta:hover > svg,
  .page-home .hero-actions .story-cta:focus-visible > svg,
  .page-home .home-touch .story-cta:hover > svg,
  .page-about .home-touch .story-cta:hover > svg,
  .page-home .home-touch .story-cta:focus-visible > svg,
  .page-about .home-touch .story-cta:focus-visible > svg {
    margin-right: -6px;
    transform: translateX(0);
    /* A list against the transition-property order above: the box starts
       widening immediately, the arrow waits 0.06s and follows it in. */
    transition-delay: 0s, 0.06s;
  }
  .page-home .hero .hero-actions .story-cta:hover > svg,
  .page-home .hero .hero-actions .story-cta:focus-visible > svg {
    margin-right: -7px;
  }
  /* The label carries an underline on ::after everywhere else; inside a
     filled box it would draw a line under the text. Off, as on desktop. */
  .page-home .hero-actions .story-cta span::after,
  .page-home .home-touch .story-cta span::after,
  .page-about .home-touch .story-cta span::after { content: none; }
  /* Whatever the box does, it does instantly for anyone who has asked for
     less movement -- a button changing width with something sliding through
     it is a good deal more motion than the rest of this page. */
  @media (prefers-reduced-motion: reduce) {
    .page-home .hero-actions .story-cta,
    .page-home .hero-actions .story-cta > svg,
    .page-home .home-touch .story-cta,
    .page-about .home-touch .story-cta
    .page-home .home-touch .story-cta > svg,
    .page-about .home-touch .story-cta > svg { transition: none; }
  }

  /* ---- The header's furniture -----------------------------------------
     The skull is 46px in the bar above 860.02px and 58px below it -- the
     narrow value is set by the max-width:1024 pointer block, which the
     desktop rule outranks at full width and which stands alone under 860.
     68px is the one to keep, matching the desktop block: the bar's own depth
     does not change here -- it is held at 82px a few lines down -- so the
     mark is the same size on both sides of 860 and there is no step at the
     breakpoint. It was 46, set when a wordmark sat beside it. */
  .header-inner .logo-skull { height: 86px; }
  /* --header-h drops to 66px under 640px, which clips the bottom of the
     range this block covers. Held at its full-width 82px so the bar is one
     depth the whole way down to the floor. */
  :root { --header-h: 96px; }

  /* ---- The promo bar --------------------------------------------------
     This block used to pull the bar back to its full-width values here: 38px
     deep with the message at 0.95rem, against the 48px and 0.62rem set for it
     further up the file. The argument was that a thin line of type reads
     better than a tall band with small type in the middle of it, and for a
     21-character message across a phone that was true.

     It is not true of the message the bar carries now, which is four times
     as long and cannot cross a 390px screen on one line at any size worth
     reading. So the wrap comes back, and with it the depth to hold it: 54px
     is two lines of 0.7rem at this leading with air over and under, measured
     rather than picked.

     0.7rem rather than the 0.62 further up. The gutters that block was
     working around are gone with a single message -- see the :has() rule up
     the file -- so the line has the bar's full width to wrap into, and it
     can afford the extra pixel and a half of type.

     Nothing at this width measures off --promo-h in a way a taller bar
     breaks: the header below is sticky and in flow, and both the hero's
     100vh and the nav sheet's top offset are written as subtractions from it
     and follow it up. The dots and their spacing stay as this block had
     them, for the day a second message brings them back. */
  :root { --promo-h: 54px; }
  .promo-banner {
    --promo-dots-inset: 14px;
    --promo-dots-w: 56px;
    --promo-lead: var(--promo-dots-w);
  }
  .promo-banner-slide { font-size: 0.7rem; }
  .promo-banner-dots { gap: 7px; }
  .promo-banner-dot { width: 6px; height: 6px; }
  .promo-banner-dot.is-active { width: 16px; }
}


/* ============================================================
   The hamburger, unboxed, wherever it shows
   ============================================================
   .nav-toggle appears at 1024px and down, and it carries its own 1.3px
   border and a full-strength --on-dark label. Its two neighbours do not:
   .site-header .icon-btn drops the border and holds its mark at 72% from
   860.02px up, and the block further up this file carries that down to 600.
   So the hamburger was the one boxed control in the bar at every width it
   was on screen -- a white box next to two bare grey glyphs.

   Written as its own block rather than folded into either of those, because
   its range is neither of theirs: it starts at 1024, where the desktop rule
   has already stopped, and runs past 860, where the narrow block starts.

   Colour and border here, size below: under 860 the bar is 66px rather than
   82px and the block up the file already sets a 24px glyph to suit it, so
   this must not put 28px back. */
@media (max-width: 1024px) {
  .site-header .nav-toggle {
    border: none;
    /* The same grey the search and cart marks carry -- 72% of --offwhite.
       With the border gone the mark IS the button, so it holds back at rest
       and comes up to full white under the pointer. */
    color: rgba(251, 247, 243, 0.72);
    transition: color 0.2s ease;
  }
  /* Pointer devices only, like every other hover in this file: a tap leaves
     :hover stuck on whatever was touched until something else is, so on a
     phone this would light up and stay lit. */
  @media (hover: hover) and (pointer: fine) {
    .site-header .nav-toggle:hover { color: var(--on-dark); }
  }
}
/* Sized to the glyph, matching .site-header .icon-btn's 28px in the band
   where the bar is still at its full 82px depth. The 40px tile is invisible
   once the border goes but is still spent, so without this the hamburger
   would sit 12px further from the cart than the cart does from the search. */
@media (min-width: 860.02px) and (max-width: 1024px) {
  .site-header .nav-toggle { width: 36px; }
  .site-header .nav-toggle svg { width: 36px; height: 36px; }
}


/* ---- ...but the very small screens keep their own header sizing ---------
   The block above sets the skull to 46px and the action gap to 18px at every
   width under 860, which is right down to about 400px and wrong below it: it
   lands later in the file than the 400px and 340px steps near the top, so it
   was overriding both. On a 320px window that put a 46px skull next to the
   wordmark and pushed it onto two lines.

   These re-assert the two values those steps set, at a selector that beats
   the block above rather than merely matching it. The wordmark's own
   font-size and its stacking at 330px were never touched -- this block does
   not set them -- so they still apply. */
@media (max-width: 400px) {
  .site-header .header-actions { gap: 12px; }
}
@media (max-width: 340px) {
  .site-header .header-inner .logo-skull { height: 50px; }
  .site-header .header-actions { gap: 10px; }
}


/* ---- Add to Cart: one shape at every width ------------------------------
   The product page's buy button is the last control on the site that still
   changes character with the window. Above 860.02px it is the square box
   with the same type ramp as every other button here -- 1.25rem at 500,
   0.06em of tracking, 1.15 line-height, no shadow. Below it, it fell back to
   .btn and came out a 999px pill at 0.9375rem/400 with 0.2em of tracking and
   a drop shadow under it.

   Geometry and type only. The FILL is deliberately left alone, because it is
   not a free choice: it is #141414 above 860.02px because the page under it
   is white there, and --btn-red below because the page is still dark. Fixing
   that means bringing the white ground onto the store, product and about
   pages the way .page-home already has it, which is a larger change than a
   button and is not assumed here.

   .btn-primary rides along: it appears in exactly one place in the markup,
   on this same button, so the two are one control rather than two. */
@media (max-width: 860.01px) {
  .btn-cart,
  .btn-primary {
    border-radius: 0;
    padding: 9px 24px 11px;
    font-size: 1.25rem;
    font-weight: 500;
    letter-spacing: 0.06em;
    line-height: 1.15;
    box-shadow: none;
    /* The same expression the desktop rule uses, so the two cannot drift:
       it is what Add to Cart, the buy bar and the quick view all measure. */
    height: calc(0.9375rem * 1.8 + 26px);
  }
  /* The hover sweep on ::after is drawn for a pill; the desktop rules turn it
     off on this button and so does this. */
  .btn-cart::after,
  .btn-primary::after { content: none; }
}


/* ============================================================
   Store, Product and About take the white run at every width
   ============================================================
   The home page has carried the desktop treatment down since the block up
   the file; these three had not, so they still swapped to the dark phone
   look under 860.02px. The visible cost was the buy button: black on white
   at full width, red on dark when narrow, because the FILL follows the
   ground and only the ground was changing.

   These are the desktop block's own colour rules for those three pages,
   re-emitted here so they apply at every width. Generated from that block
   rather than retyped, and filtered to background / colour / border / shadow
   only -- none of the layout that sits beside them in the source comes with
   it, so grids, positions and sizes are untouched and each page keeps the
   arrangement it already had at this width.

   Order matters: these must land after the max-width blocks they override,
   which is why they are at the foot of the file rather than beside the rules
   they mirror. */
@media (max-width: 860.01px) {
  .page-home .membership-strip, .page-store .membership-strip, .page-product .membership-strip {
    background: var(--panel-foot);
    border: none;
  }
  .page-about {
    background: var(--page-ground);
  }
  .page-about .home-story-copy::after {
    background: linear-gradient(180deg, transparent, var(--panel-foot));
  }
  .page-about .page-header-about {
    background: #141414;
  }
  .page-home .membership-strip h2, .page-store .membership-strip h2, .page-product .membership-strip h2 {
    color: #ffffff;
  }
  .page-home .membership-strip p, .page-store .membership-strip p, .page-product .membership-strip p, .page-home .membership-strip .subscribe-block-desc, .page-store .membership-strip .subscribe-block-desc, .page-product .membership-strip .subscribe-block-desc {
    color: #ffffff;
  }
  .page-home .membership-strip input, .page-store .membership-strip input, .page-product .membership-strip input {
    background: rgba(255, 255, 255, 0.1);
    color: #ffffff;
    border-color: rgba(255, 255, 255, 0.22);
  }
  .page-store .membership-strip input::placeholder {
    color: rgba(255, 255, 255, 0.55);
  }
  .page-product .membership-strip input::placeholder {
    color: rgba(255, 255, 255, 0.55);
  }
  .page-store .membership-strip .subscribe-arrow {
    color: #ffffff;
  }
  .page-product .membership-strip .subscribe-arrow {
    color: #ffffff;
  }
  .page-store {
    background: var(--page-ground);
  }
  .page-store .page-header-store {
    border-bottom: none;
    background-color: #141414;
  }
  .page-store .store-toolbar-wrap, .page-store .store-toolbar {
    border-bottom: none;
  }
  .page-store .filter-sidebar-group {
    border-bottom: none;
  }
  .page-store .product-media {
    border: none;
  }
  .page-store .product-media-actions {
    border: none;
  }
  .page-store .sort-dropdown-list {
    border: none;
  }
  .page-store .product-info h3 a, .page-store .product-info h3 a:hover {
    color: #141414;
  }
  .page-store .product-price .price {
    color: #141414;
  }
  .page-store .product-price .price-was {
    color: rgba(20, 20, 20, 0.55);
  }
  .page-store .qv-rating-row, .page-store .qv-rating-score, .page-store .qv-rating-count {
    color: rgba(20, 20, 20, 0.6);
  }
  .page-store .result-count {
    color: rgba(20, 20, 20, 0.6);
  }
  .page-store .sort-select {
    color: #141414;
  }
  .page-store .cart-overlay-btn {
    background: #141414;
    color: #ffffff;
  }
  .page-store .cart-overlay-btn:hover, .page-store .cart-overlay-btn:focus-visible {
    background: #b02b25;
    color: #ffffff;
  }
  .page-store .product-media-soon {
    background: #181818 url('../assets/images/coming-soon-topo.jpg') center / cover no-repeat;
    color: var(--offwhite);
  }
  .page-product {
    background: var(--page-ground);
  }
  .page-product .section-ruled {
    border-top: none;
  }
  .page-product .section-alt {
    border-top: none;
    border-bottom: none;
  }
  .page-product .detail-divider {
    border-top: none;
  }
  .page-product .accordion-item {
    border-bottom: none;
  }
  .page-product .product-detail-media, .page-product .product-media {
    border: none;
  }
  .page-product h1, .page-product h2, .page-product h3, .page-product h4 {
    color: #141414;
  }
  .page-product .product-detail-info, .page-product .product-detail-info p, .page-product .accordion-panel, .page-product .accordion-trigger {
    color: #141414;
  }
  .page-product .breadcrumb, .page-product .eyebrow, .page-product .pd-share-btn, .page-product .product-color-label, .page-product .qv-rating-score, .page-product .qv-rating-count, .page-product .qv-rating-empty {
    color: rgba(20, 20, 20, 0.6);
  }
  .page-product .breadcrumb a:hover, .page-product .pd-share-btn:hover {
    color: #141414;
  }
  .page-product .product-price .price {
    color: #141414;
  }
  .page-product .product-price .price-was {
    color: rgba(20, 20, 20, 0.55);
  }
  .page-product .product-info h3 a, .page-product .product-info h3 a:hover {
    color: #141414;
  }
  .page-product .btn-cart, .page-product .btn-cart:hover {
    background: #141414;
    color: #ffffff;
  }
  .page-product .btn-cart {
    box-shadow: none;
  }
  .page-product .qty-stepper-btn, .page-product .qty-input {
    color: #141414;
    background: transparent;
  }
  .page-product [data-color-select-label] {
    color: #141414;
  }
  .page-product [data-color-select-label].is-placeholder {
    color: rgba(20, 20, 20, 0.55);
  }
  .page-product .product-media-soon {
    background: #181818 url('../assets/images/coming-soon-topo.jpg') center / cover no-repeat;
    color: var(--offwhite);
  }
  .page-product .cart-overlay-btn {
    background: #141414;
    color: #ffffff;
  }
  .page-product .cart-overlay-btn:hover, .page-product .cart-overlay-btn:focus-visible {
    background: #b02b25;
    color: #ffffff;
  }
  .page-product .product-media-actions {
    border: none;
  }
  .page-product .color-select-trigger, .page-product .color-select-list, .page-product .color-select-dropdown.is-open .color-select-trigger, .page-product .product-detail-cart-row .qty-stepper, .page-product .qty-input, .page-product .qv-media-thumb, .page-product .product-detail-media, .page-product .product-media, .page-product .product-media-actions {
    border: none;
  }
  .page-product .color-select-trigger, .page-product .color-select-list, .page-product .product-detail-cart-row .qty-stepper {
    background: #f0f0f0;
  }
  .page-product .qty-input {
    background: transparent;
  }
  .page-product .color-select-dropdown.is-open .color-select-trigger {
    background: #e6e6e6;
  }
  .page-product .color-select-trigger svg {
    color: rgba(20, 20, 20, 0.55);
  }
  .page-product .product-detail-cart-row .qty-stepper-btn {
    color: #141414;
  }
  .page-product .product-detail-header h1 {
    color: #141414;
  }
  .page-product .qv-mini-logo {
    color: #9a9691;
  }
  .page-product .pd-share-btn {
    color: #141414;
  }
  .page-product .pd-share-btn:hover {
    color: #7f7f7f;
  }
  .page-product .detail-divider {
    border-top: 1.3px solid #e6e6e6;
  }
  .page-product .color-select-trigger, .page-product .color-select-list, .page-product .product-detail-cart-row .qty-stepper {
    background: #e6e6e6;
    border: none;
  }
  .page-product .color-select-dropdown.is-open .color-select-trigger {
    background: #dcdcdc;
    border: none;
  }
  .page-product .qty-input {
    background: transparent;
  }
  .page-product [data-color-select-label], .page-product [data-color-select-label].is-placeholder, .page-product .color-select-trigger svg, .page-product .color-select-list li, .page-product .color-select-list li:hover, .page-product .color-select-list li[aria-selected="true"], .page-product .color-select-list li[data-placeholder][aria-selected="true"], .page-product .color-select-list li .option-note {
    color: #141414;
  }
  .page-product .color-select-list li:hover {
    background: transparent;
  }
  .page-product .color-select-list li::after {
    background: #a8a8a8;
  }
  .page-product .btn-cart {
    background: #141414;
    color: #ffffff;
  }
  .page-product .btn-cart:hover, .page-product .btn-cart:focus-visible {
    background: #b02b25;
    color: #ffffff;
  }
  .page-product .product-detail-accordion .accordion-panel > p, .page-product [data-product-specs] li, .page-product .accordion-panel a {
    color: #141414;
  }
  .page-product .reviews-box-title, .page-product .reviews-box-note, .page-product .product-reviews-desktop-heading, .page-product .product-reviews-heading, .page-product .product-reviews-heading-count, .page-product .product-reviews-count, .page-product .product-reviews-empty, .page-product .product-reviews-visible-count, .page-product .product-reviews-purchase-note, .page-product .rating-breakdown-label, .page-product .rating-breakdown-count, .page-product .rating-breakdown-row:hover .rating-breakdown-label, .page-product .rating-breakdown-row:hover .rating-breakdown-count, .page-product .rating-breakdown-row.is-active .rating-breakdown-label, .page-product .rating-breakdown-row.is-active .rating-breakdown-count, .page-product .rating-breakdown-row:disabled:hover .rating-breakdown-label, .page-product .rating-breakdown-row:disabled:hover .rating-breakdown-count, .page-product .qv-review-date, .page-product .qv-review-text, .page-product .qv-review-author, .page-product .review-field-label, .page-product .review-field input, .page-product .review-field textarea, .page-product .reviews-search-input, .page-product .product-reviews-show-more {
    color: #141414;
  }
  .page-product .review-field input::placeholder, .page-product .review-field textarea::placeholder, .page-product .reviews-search-input::placeholder {
    color: rgba(20, 20, 20, 0.55);
  }
  .page-product .qv-review-card {
    border-bottom-color: rgba(20, 20, 20, 0.14);
  }
  .page-product .review-field input, .page-product .review-field textarea {
    background: #f4f2ef;
    border-color: rgba(20, 20, 20, 0.14);
  }
  .page-product .review-form-star svg, .page-product .review-star-picker-inline .review-star-btn svg {
    fill: rgba(20, 20, 20, 0.18);
  }
  .page-product .review-star-picker-inline .review-star-btn {
    border-color: rgba(20, 20, 20, 0.14);
  }
  .page-product .rating-breakdown-track {
    background: #e6e6e6;
  }
  .page-product .product-reviews-score {
    color: #b02b25;
  }
  .page-product .product-media-actions {
    border: none;
  }
  .page-product .product-reviews-divider, .page-product .product-reviews-desktop-divider, .page-product [data-product-reviews-desktop-accordion] .product-reviews-desktop-divider {
    border-top: 1.3px solid #e6e6e6;
  }
  .page-product .accordion-item {
    border-bottom: 1.3px solid #e6e6e6;
  }
  .page-product .product-detail-accordion .accordion-item:last-child {
    border-bottom: none;
  }
  .page-product .qv-review-card {
    border-bottom: 1.3px solid #e6e6e6;
  }
  .page-product .product-reviews-list .qv-review-card:last-child, .page-product .qv-review-card.is-last-visible {
    border-bottom: none;
  }
  .page-product .reviews-box {
    border: 1.3px solid #e6e6e6;
  }
}


/* ============================================================
   The rest of the chrome, held to one appearance
   ============================================================
   Six things that were still switching at 860.02px. All of the values are
   the desktop block's own, read off the computed style at 900px. */
@media (max-width: 860.01px) {

  /* ---- 1. The header marks, full size, and lighting on hover ----------
     The glyphs were held at 24px here on the reasoning that the bar was
     66px rather than 82px. It is 82px now -- an earlier block pins
     --header-h -- so the reason has gone and they take the desktop 28px.

     The hover was simply missing: .site-header .icon-btn:hover lives inside
     the desktop block, so under 860 the marks sat at 72% and stayed there
     with the pointer on them. The hamburger already had one from its own
     block; this gives the other two the same. */
  .site-header .icon-btn svg,
  .site-header .nav-toggle svg { width: 36px; height: 36px; }
  .site-header .header-actions { gap: 20px; }
  @media (hover: hover) and (pointer: fine) {
    .site-header .icon-btn:hover { color: var(--on-dark); }
  }

  /* ---- 3. The hero's social band --------------------------------------
     display: none on the base rule, switched on only inside the desktop
     block, so the whole band -- four marks and the state flag -- was absent
     under 860. Everything here is that block's, term for term: the band
     absolute along the hero's foot at --hero-band deep, the marks sized to
     the glyph with no box, and one end pushed to the far side by margin
     rather than by justification -- the flag on the left gutter, the icons
     carrying the auto margin over to the right one. */
  .hero-social-bar {
    display: block;
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: var(--hero-band);
  }
  /* Three things in the bar now, so it is a grid rather than a flex row with
     an auto margin pushing one end over: the icons at the left gutter, the
     carousel controls in the middle, the flag at the right gutter.

     1fr auto 1fr, which is the header's own arrangement one section down the
     page and for the same reason: the centre column is sized by what is in
     it and the two 1fr columns split what is left, so the controls are
     centred against the BAR rather than against whichever of the two ends is
     wider. .container's gutter is symmetric, so bar-centred is also
     window-centred.

     grid-row: 1 on all three, named rather than left to auto-placement. The
     header learnt this the hard way: place an item in column 2 and then ask
     for column 1, and sparse packing starts a new row instead of going back
     -- which inside a fixed-height bar means the row simply falls out of the
     bottom of it. */
  .hero-social-bar .container {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    height: 100%;
  }
  .hero-social-bar .hero-social { grid-row: 1; grid-column: 1; justify-self: start; }
  .hero-social-bar .hero-carousel-nav { grid-row: 1; grid-column: 2; justify-self: center; }
  .hero-social-bar .hero-band-flag { grid-row: 1; grid-column: 3; justify-self: end; }
    /* No auto margin any more -- justify-self on the grid above places
     both ends, and a margin as well would fight it. */
.hero-social { display: flex; gap: 20px; }
  .hero-social .icon-btn {
    width: 18px;
    height: 100%;
    border: none;
    border-radius: 0;
    background: transparent;
    color: var(--on-dark-muted);
    transition: color 0.2s ease;
  }
  .hero-social .icon-btn svg { width: 18px; height: 18px; }
  @media (hover: hover) and (pointer: fine) {
    .hero-social .icon-btn:hover { color: var(--on-dark); }
  }
  .hero-band-flag {
    width: 33px;
    height: 22px;
    flex: none;
  }

  /* ---- 4. The hero title, one size -------------------------------------
     3.6rem at 700 in caps, which is what the full-width rule sets. The base
     rule shrinks it to a clamp topping out at 2.45rem and drops the caps and
     the weight with it.

     white-space is the catch. The base rule sets nowrap, and the desktop
     comment says outright that nowrap is what makes a fixed size safe --
     true at 900px, where HANDCRAFTED GEAR measures about 500px inside a
     828px container. At 505px and below it does not fit, and nowrap would
     push it off the side of the page rather than wrap it. So the line is
     allowed to break here: same size at every width, on one line down to
     about 560px and on two under that. */
  .hero-content h1 {
    font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
                 'Segoe UI', Roboto, sans-serif;
    font-weight: 700;
    text-transform: uppercase;
    /* Fluid here, where the desktop block holds a flat 3.6rem.

       3.6rem is 64.8px, and the longer of the two headlines -- USA SOURCED &
       SEWN, eighteen characters -- sets to about 8em in this face at this
       tracking, so roughly 520px. A 390px phone has 358px of measure between
       its gutters. It was wrapping, and a two-line headline over a one-line
       one made the two slides different heights as well as different shapes.

       10.2vw is what keeps the longer line whole: at 320px it asks for
       32.6px against the 36px that would just fit, at 390 it asks for 40
       against 45, and it holds that margin the whole way up. The 3.6rem
       ceiling is reached at about 635px, so from there to the breakpoint the
       headline is the desktop size and there is no step at 860.

       The 1.75rem floor is a floor and not a target -- it is only reached
       under 309px, narrower than any phone still in use, and it is there so
       the type cannot vanish on some future device rather than to be seen.

       white-space stays normal. The clamp is sized to make wrapping
       unnecessary, but nowrap would turn a bad estimate into a headline
       running off the side of the page; wrapping is the failure this design
       can absorb, since the copy is bottom-anchored and a second line grows
       upward into the picture. */
    font-size: clamp(1.75rem, 10.2vw, 3.6rem);
    letter-spacing: 0.02em;
    line-height: 1.05;
    white-space: normal;
  }

  /* ---- 5. The header's scroll behaviour --------------------------------
     initHeaderScroll toggles .is-scrolled on the body at every width -- it
     is not gated -- but everything that READS it is written inside the
     desktop block, so under 860 the class landed on a page with no rules to
     answer it and the bar never condensed. These are those rules, for the
     four pages that carry the behaviour. */
  body.page-home .header-inner,
  body.page-home .header-inner .logo-skull,
  body.page-store .header-inner,
  body.page-store .header-inner .logo-skull,
  body.page-about .header-inner,
  body.page-about .header-inner .logo-skull,
  body.page-contact .header-inner,
  body.page-contact .header-inner .logo-skull {
    transition: height 0.55s cubic-bezier(0.22, 1, 0.36, 1);
  }
  /* Condensing is brisk, growing back is not: growing pushes content down and
     wants the long decelerating curve; condensing happens while you are
     already scrolling away and can be quick. */
  body.page-home.is-scrolled .header-inner,
  body.page-home.is-scrolled .header-inner .logo-skull,
  body.page-store.is-scrolled .header-inner,
  body.page-store.is-scrolled .header-inner .logo-skull,
  body.page-about.is-scrolled .header-inner,
  body.page-about.is-scrolled .header-inner .logo-skull,
  body.page-contact.is-scrolled .header-inner,
  body.page-contact.is-scrolled .header-inner .logo-skull {
    transition: height 0.3s ease;
  }
  body.page-home.is-scrolled .site-header,
  body.page-store.is-scrolled .site-header,
  body.page-about.is-scrolled .site-header,
  body.page-contact.is-scrolled .site-header { --header-h: 76px; }
  body.page-home.is-scrolled .header-inner .logo-skull,
  body.page-store.is-scrolled .header-inner .logo-skull,
  body.page-about.is-scrolled .header-inner .logo-skull,
  body.page-contact.is-scrolled .header-inner .logo-skull { height: 66px; }
  @media (prefers-reduced-motion: reduce) {
    body.page-home .header-inner,
    body.page-home .header-inner .logo-skull,
    body.page-store .header-inner,
    body.page-store .header-inner .logo-skull,
    body.page-about .header-inner,
    body.page-about .header-inner .logo-skull,
    body.page-contact .header-inner,
    body.page-contact .header-inner .logo-skull { transition: none; }
  }
}


/* ---- The social marks inside the nav dropdown ---------------------------
   These had no rule of their own at all -- .mobile-nav-social sets a flex
   row and a gap and stops, so the four links fell through to the base
   .icon-btn: a 40px tile with a 1.3px border and a 4px radius, at full
   --on-dark. Every other set of social marks on the site lost its boxes
   long ago; this one was the last still wearing them.

   Same treatment as the hero band's, term for term: no box, the mark sized
   to the glyph, held back to --on-dark-muted at rest and coming up to full
   white under the pointer.

   The gap goes from 10px to 20px with it. That 10px was measured against
   40px tiles, so the marks read about 50px apart; at the glyph's own width
   the same spacing has to come from the gap instead.

   The height stays 40px. The width is what sets the spacing, and dropping
   the height would take the tap target down with it for no gain -- this
   panel opens on touch as well as under a pointer.

   No media query: .mobile-nav-social exists only inside this panel, so the
   rule cannot reach anything else, and the marks look the same wherever the
   panel opens. */
.mobile-nav-social { gap: 20px; }
.mobile-nav-social .icon-btn {
  width: 22px;
  border: none;
  border-radius: 0;
  background: transparent;
  color: var(--on-dark-muted);
  transition: color 0.2s ease;
}
.mobile-nav-social .icon-btn svg { width: 22px; height: 22px; }
/* Pointer devices only, like every other hover in this file: a tap leaves
   :hover stuck on whatever was touched until something else is. */
@media (hover: hover) and (pointer: fine) {
  .mobile-nav-social .icon-btn:hover { color: var(--on-dark); }
}


/* ---- Two more the desktop block was holding on to ------------------------ */
@media (max-width: 860.01px) {

  /* The topographic plate on the category tiles. The base .product-media-soon
     is a flat #2a2a2a; the texture is set only inside the desktop block, on
     .home-featured .product-media-soon, so under 860 the three tiles were
     plain dark rectangles with COMING SOON on them.

     #181818 under the file, not instead of it: the colour is what shows
     while the jpg is still arriving and what fills any edge `cover` leaves.
     The label stays --offwhite -- against this ground it measures about
     16.7:1, so holding it white costs nothing. */
  .home-featured .product-media-soon {
    background: #181818 url('../assets/images/coming-soon-topo.jpg') center / cover no-repeat;
    color: var(--offwhite);
  }

  /* The hairline over the social band. .hero-overlay draws a 1.3px bottom
     border on the base rule -- it was there to mark where the photograph
     stops, back when the strip below it was the Featured Collection's ground
     running up under the picture. The desktop block turns it off; under 860
     it stayed, and with the social band now restored underneath it reads as
     a grey rule across the foot of the hero rather than as an edge. */
  .hero-overlay { border-bottom: none; }
}


/* ============================================================
   The header's scroll behaviour, and the hover on the plates
   ============================================================ */
@media (max-width: 860.01px) {

  /* ---- The bar sticks, as it does at full width -----------------------
     The condense was already carried down -- .is-scrolled drops --header-h
     to 64px and the skull to 38px -- but it was condensing a bar that had
     already scrolled off the top of the window, so none of it was ever
     seen. The POSITION is the part that matters and it was still split:
     sticky at 860.02px and up, absolute (home) or relative (the rest) below
     it, set by the base rule and by the 1024px pointer block.

     Sticky, not fixed. Fixed takes the bar out of flow, and the hero is
     sized as `100vh minus the bar and the promo strip` -- it would
     immediately be that much too tall and the band at its foot would fall
     under the fold. Sticky leaves the layout at rest exactly as it is and
     only changes what happens once you scroll. */
  .page-home .site-header,
  .page-store .site-header,
  .page-about .site-header,
  .page-contact .site-header {
    position: sticky;
    top: 0;
    transition: transform 0.45s cubic-bezier(0.4, 0, 0.2, 1);
  }
  /* And slides away once the hero has passed. initHeroChrome sets this class
     at every width; like .is-scrolled, nothing under 860 was reading it. The
     slide is a transform, so it moves what is painted without moving what is
     measured -- which is what keeps that function from oscillating around
     its own threshold. */
  body.page-home.is-chrome-away .site-header,
  body.page-store.is-chrome-away .site-header,
  body.page-about.is-chrome-away .site-header,
  body.page-contact.is-chrome-away .site-header {
    transform: translateY(-100%);
  }
  @media (prefers-reduced-motion: reduce) {
    .page-home .site-header,
    .page-store .site-header,
    .page-about .site-header,
    .page-contact .site-header { transition: none; }
  }

  /* ---- The hero, which the bar no longer lies on top of ----------------
     With the header absolute it was painted OVER the hero, so the hero took
     the full `100vh - --promo-h` and the black strip behind the bar was part
     of it. Sticky puts the bar in flow above the photograph, so the hero has
     to give back its height -- otherwise the two stack and the band at the
     foot drops below the fold.

     The other three are the desktop values for the same box: the ground the
     band shows against, the band's own depth (the Shop The Store button's
     box arithmetic -- 22.5px of label at 1.15 plus 9 and 11 of padding), and
     the padding that holds the photo clear of it. */
  .hero {
    background: #141414;
    /* 58px, a flat value. This was calc(1.25rem * 1.15 + 20px) -- about 46px
       -- and every term in it was read off the Shop The Store button, because
       for a while the band's job was to be exactly as deep as that button so
       the two read as one row. The button is in the hero copy now and the
       band holds the carousel controls, so that arithmetic was describing a
       relationship that no longer exists; keeping it would have been a
       calculation nobody could check.

       The number is chosen against what is actually in there now: 40px of
       control with 9px of air over and under it. */
    --hero-band: 58px;
    /* ---- svh, so the band lands on the address bar ---------------------
       100vh on iOS is the LARGE viewport -- the height the page would have
       if Safari's toolbars were hidden -- so a hero measured with it is
       taller than what you can actually see when the page first loads, and
       the black band under the photo starts life below the fold. You have to
       scroll to find the foot of a section that is meant to be the fold.

       svh is the small viewport: the height with the toolbars OUT, which is
       exactly the state the page opens in. The band's bottom edge now sits
       against the top of the address bar at rest.

       svh and not dvh. dvh tracks the toolbars as they collapse and expand,
       which would be correct at every instant and would also mean the hero
       changing height under your finger the whole way down the page --
       reflowing four sections on every scroll frame. Fixed at the smaller
       value, the only cost is that the hero does not grow to fill the space
       the collapsing toolbar gives back, and what fills it instead is the
       next section, which is what scrolling is for.

       The vh line stays underneath as the fallback -- the two are declared in
       that order so a browser without svh takes the old value rather than
       nothing. Every browser that has the floating toolbar this is for has
       had svh since 2022. */
    min-height: calc(100vh - var(--promo-h) - var(--header-h));
    min-height: calc(100svh - var(--promo-h) - var(--header-h));
    padding-bottom: var(--hero-band);
  }

  /* ---- The Coming Soon plates lift on hover ---------------------------
     Every one of these is written inside the desktop block -- the category
     tiles on the home page, the store grid and the product page's related
     row -- so under 860 the plates sat still. scale(1.06) with the 0.4s
     curve, which is the value all three carry.

     Pointer-gated: a tap leaves :hover stuck on whatever was touched until
     something else is, which would leave a plate enlarged after a tap. */
  @media (hover: hover) and (pointer: fine) {
    .home-featured .product-media-soon,
    .page-store .product-media-soon,
    .page-product .product-media-soon {
      transition: transform 0.4s cubic-bezier(0.33, 1, 0.68, 1);
    }
    .home-featured .category-card:hover .product-media-soon,
    .page-store .product-media-wrap:hover .product-media-soon,
    .page-product .product-media-wrap:hover .product-media-soon {
      transform: scale(1.06);
    }
    @media (prefers-reduced-motion: reduce) {
      .home-featured .product-media-soon,
      .page-store .product-media-soon,
      .page-product .product-media-soon { transition: none; }
      .home-featured .category-card:hover .product-media-soon,
      .page-store .product-media-wrap:hover .product-media-soon,
      .page-product .product-media-wrap:hover .product-media-soon { transform: none; }
    }
  }
}


/* ---- The seams down the home page, one set of values --------------------
   The gap between the category tiles and About Us measured 36px at full
   width and 124px under 860. Two sections were each contributing their own
   half of it and both halves were wrong:

     .section.home-featured   padding-bottom   36px  ->  72px
     .section.home-about      padding-top       0px  ->  52px

   The full-width values are a pair of expressions rather than numbers, which
   is the point: --page-gutter opens the seam by the same measure that holds
   the content off the window edge, and calc(--grid-gap * 2) is the doubled
   card gap every other seam on this page uses. --grid-gap is 18px at every
   width, so the closing seam is 36px throughout; --page-gutter is a clamp on
   4vw, so the opening one breathes with the window exactly as it does above
   860.02px.

   Get In Touch comes with them. It is one of the four bands down this page
   and was running full-bleed here while the other three sat inside the
   gutter, so the rhythm broke at the foot of the page even with the seams
   above it fixed. */
@media (max-width: 860.01px) {
  .section.home-featured {
    padding-top: var(--page-gutter);
    padding-bottom: calc(var(--grid-gap) * 2);
  }
  .section.home-about {
    padding-top: 0;
    padding-bottom: calc(var(--grid-gap) * 2);
  }
  /* The second run of stories carries no headroom of its own: the seam above
     it is the first run's bottom padding and a value here would stack. */
  .section.home-about.home-about-lower { padding-top: 0; }
  .home-touch {
    margin: 0 var(--page-gutter) calc(var(--grid-gap) * 2);
    padding: 64px 0;
    border-radius: var(--band-radius);
  }
}

/* ---- The nav panel's left edge, gone at every width it opens at ---------
   .mobile-nav-sheet-inner draws `border-left: 1.3px solid var(--on-dark-border)`
   inside the 1024px pointer block. It was turned off once already, but under
   max-width: 860.01px -- one breakpoint too narrow. The panel appears from
   1024px down, so between 861 and 1024 the hairline survived, and that band
   is exactly where a half-screen desktop window sits.

   Measured before the change: 1px solid rgb(56, 55, 55) at 1000, 900 and
   861px; 0px none at 859 and below. Same range as the hamburger's own block
   up the file, and for the same reason -- it is the range the control and its
   panel exist over.

   The box-shadow stays: it is 0 20px 44px cast downward and is not what draws
   the line. */
@media (max-width: 1024px) {
  .mobile-nav-sheet-inner { border-left: none; }
}

/* ---- The story photographs hold one size curve ---------------------------
   Measured across the range, the frame was doing three different things:

     1500-1150   a column beside the copy, 4:3   876x657 down to 672x504
     1149        the row stacks, frame goes full width      1057x705
     1149-320    3:2, shrinking with the window   1057x705 down to 288x192

   The step at 1149 is the one that reads as wrong: in a single pixel of
   window the photo goes from 672 wide to 1057 and from 504 tall to 705 --
   57% wider and 40% taller than it was a moment before. That is the "normal
   to big"; the long fall to 288x192 by 320px is the "to small".

   The cause is that the frame was sized by RATIO on a box whose width had
   just changed from a third of the row to all of it. A ratio cannot help
   here -- the width is what jumped, so anything derived from it jumps too.

   So the stacked frame takes a height directly and lets the width do as it
   likes. 51vw lands on 522px at 1024px of window, which is what the column
   measured at 1025 -- so the two sides of the breakpoint meet within a pixel
   and the height stops jumping at the switch. The width still changes there,
   and always will: that is what stacking IS. The 260px floor stops the frame
   collapsing on a phone, where 51vw would otherwise reach 163px.

   Both files are square and cropped by object-fit: cover, so a shorter frame
   takes more off the top and bottom and never distorts.

   .page-home only. The About page builds its rows from these same classes
   but keeps two columns at every width, where the frame is a column beside
   the copy and none of this applies. */
@media (max-width: 749.98px) {
  .page-home .home-story-media .split-media,
  .page-about .home-story-media .split-media {
    /* 7:6 -- 1.167 wide to tall, measured off the reference's stacked
       band. The frame was 1.80 here, which is why the photograph read as
       a short wide strip under the copy rather than as half the band.
       A ratio rather than a height, because the width is stable now:
       below 750 the frame is always the full band, so nothing jumps. */
    aspect-ratio: 7 / 6;
    height: auto;
  }
}


/* ---- The story and Get In Touch copy keeps its full-width type ----------
   Both headings and both paragraphs were dropping to the base ramp under
   860px while the desktop block held the real one from 860.02px up:

     h2   45px / 700 / caps / 0.02em   ->  31.5px / 400 / sentence case
     p    20.25px / 0.005em / 1.6      ->  19.125px / 0.6px / 1.9
     colour  #ffffff                   ->  #ececec and 72% white

   These are the desktop block's values, so the type now reads the same at
   every width and only the measure changes. Sizes are given in rem against
   an 18px root -- 2.5rem is that 45px, 1.125rem is the 20.25px.

   Alignment is deliberately NOT included for Get In Touch: the desktop rule
   sets text-align: left there, but this band is a centred plate under 860
   and centring it is what makes it work at that width. Only the face, size,
   weight, spacing and colour come down. */
@media (max-width: 860.01px) {
  .page-home .home-story-copy h2,
  .page-about .home-story-copy h2,
  .page-home .home-touch h2,
  .page-about .home-touch h2 {
    font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
                 'Segoe UI', Roboto, sans-serif;
    font-weight: 700;
    text-transform: uppercase;
    font-size: 2.5rem;
    letter-spacing: 0.02em;
    line-height: 1.1;
    color: #ffffff;
    margin-bottom: 1.25rem;
  }
  .page-home .home-story-copy p,
  .page-about .home-story-copy p,
  .page-home .home-touch p,
  .page-about .home-touch p {
    font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
                 'Segoe UI', Roboto, sans-serif;
    font-weight: 400;
    font-size: 1.125rem;
    letter-spacing: 0.005em;
    line-height: 1.6;
    color: #ffffff;
    margin-bottom: 1.125rem;
  }

  /* ---- The two story buttons stay on the left edge --------------------
     .hero-actions is a centred flex row -- it was written for the hero,
     where centring is right. The desktop block turns that off for the story
     panels with justify-content: flex-start, because there the button sits
     under a left-aligned heading and a left-aligned paragraph and centring
     it leaves it floating away from both. That rule stopped at 860.02px and
     the button drifted back to the middle.

     Get In Touch is not included, for the same reason as the type above:
     that plate is centred by design at this width and its button belongs in
     the middle of it. */
  /* Both axes, because which one does the aligning changes under 640px:
     .hero-actions turns into a COLUMN there (flex-direction: column,
     align-items: center, up the file), and in a column it is align-items
     that places the button horizontally -- justify-content switches to
     the vertical axis and stops having any say. So flex-start on its own
     held the button left down to 640 and let it drift back to the middle
     below that. Setting both covers the row case and the column case. */
  .page-home .home-story-copy .hero-actions,
  .page-about .home-story-copy .hero-actions {
    justify-content: flex-start;
    align-items: flex-start;
  }
}

/* Get In Touch sets its own margins rather than the story panel's -- 14px
   under the heading and 26px under the paragraph, against the 22.5/20.25 the
   shared rule above gives them. Restored, so the band's internal spacing is
   the full-width one too. The paragraph's auto side margins stay: they are
   what centres it in this plate. */
@media (max-width: 860.01px) {
  .page-home .home-touch h2,
  .page-about .home-touch h2 { margin: 0 0 14px; }
  .page-home .home-touch p,
  .page-about .home-touch p { margin: 0 auto 26px; }
}

/* ============================================================
   Get In Touch: where the three ranges went
   ============================================================
   This block held the plate's width in three pieces -- one story column
   above 1024, half the container to 860, the full page measure below 750
   -- with the container and the padding restated in each. It is gone, and
   the two things it was for are now single expressions on the base rule
   up the file: --touch-col for the width, clamp(20px, 4.2vw, 56px) for
   the padding. Both are continuous, so the widths this table used to
   record steps at (1025, 1024, 860, 859, 750, 749) are no longer events.

   Two things it did are deliberately kept, and they live where they
   always did:

     - the band's own margins, radius and vertical padding under 860.01,
       in the seams block up the file, and the clamp on that padding
       under 750 further down;
     - the centred type and centred button under 860.01, in the type
       block above -- the plate is centred at every width, but the copy
       inside it only ranges left where there is a measure to range it
       against.

   What is NOT kept is the plate reaching the band's edges under 750.
   That was the reason for the full-measure range, and it is the one
   thing about this band that read as broken: the box is the point, and a
   box the width of the picture is not a box on a picture. */


/* ---- The membership fields stop shrinking -------------------------------
   The card is a two-column row above 860.02px with the fields in the right
   half, so their width is half the card less its padding. Measured down the
   range: 616px at 1500, 484 at 1200, 396 at 1000, and 335 at 861 -- then 702
   at 859, where the card goes to one column and they take the full width.

   So the narrowest they ever get is in the 861-1024 band, and 335px is below
   the ~394px the client marked as the floor. The card goes to one column at
   1024 instead, which is where the story bands' own two-column range ends --
   one breakpoint for the page rather than two. In that band the fields are
   the card's full width, which is wider than 396 everywhere, so the floor
   holds all the way down.

   Their HEIGHT was never the problem: 52px at every width already. */
@media (min-width: 860.02px) and (max-width: 1024px) {
  .page-home .membership-card,
  .page-store .membership-card,
  .page-product .membership-card {
    grid-template-columns: minmax(0, 1fr);
    text-align: center;
  }
  .page-home .membership-card .subscribe-stack,
  .page-store .membership-card .subscribe-stack,
  .page-product .membership-card .subscribe-stack {
    margin-left: auto;
    margin-right: auto;
  }
  .page-home .membership-intro .subscribe-block-desc,
  .page-store .membership-intro .subscribe-block-desc {
    margin-left: auto;
    margin-right: auto;
  }
}

/* ---- ...and they stop GROWING, which was the other half of it -----------
   The card is two columns above 1024px with the fields in the right half,
   and one centred column below it. Nothing capped the stack in that second
   state, so at the moment it stacked the fields inherited the card's full
   width and the pair went from 407px to 854px in one pixel of window --
   they more than doubled in size on the way DOWN. From there they narrowed
   with the container: 854 at 1024, 740 at 900, 702 at 859, 556 at 700.

   So the fields were widest at exactly the width where the layout had just
   admitted there was not room for two columns, and a field the width of the
   whole band stops reading as a field -- it reads as a rule across the page
   with a word at one end.

   407px is not a chosen number. It is what the right-hand column measures at
   1025px, the last width before the stack: the container is 943px there,
   less 88px of card padding and one --page-gutter of column gap, halved. So
   the fields keep the size they had the instant before the reflow and simply
   carry it down -- which is what the client asked for, in both directions at
   once: the two-column size is the one they liked, and the stack must not be
   bigger than it.

   It is a max-width and not a width, so the container still governs below
   about 500px, where 407 no longer fits between the gutters. Under that the
   fields track the card as they always did.

   .membership-card .subscribe-stack up the file sets max-width: 100% and is
   what this has to beat; one more class does it. The auto margins are what
   centre the stack once it is narrower than its column -- without them a
   capped stack sits against the left edge under centred copy. */
@media (max-width: 1024px) {
  .page-home .membership-card .subscribe-stack,
  .page-store .membership-card .subscribe-stack,
  .page-product .membership-card .subscribe-stack {
    max-width: 407px;
    margin-left: auto;
    margin-right: auto;
  }
}


/* ---- Quick Add slides up on a narrowed window, as it does at full width --
   Above 860.02px the bar is not a bar at all: it is absolutely positioned
   along the foot of the photograph, pushed a full height out of sight, and
   brought back up when the pointer enters the card. Below that it fell to
   the base rule -- position: static -- and became a permanent strip under
   every image, which is the phone's design and reads as a different card.

   Pointer-gated, and that is the whole reason the two designs exist. A touch
   screen has no hover to reveal anything with, so a hidden control there is
   an unreachable one; the pinned bar stays for touch (see the block up the
   file, now scoped to hover: none) and only a mouse gets the overlay back.

   The values are the desktop block's own: translateY(100%) at rest, 0 on
   hover, over 0.32s. focus-within is listed with :hover so the bar also
   comes up for a keyboard tabbing into the card, which is the same rule the
   full-width block carries. */
@media (max-width: 860.01px) and (hover: hover) and (pointer: fine) {
  /* The clip, and it is not optional. The bar is pushed a full height DOWN
     out of the card; with the wrap left overflow: visible -- which it is at
     this width, where the full-width rule setting it does not reach -- that
     hidden state is not hidden at all. It renders just below the photograph,
     which is exactly where the product title sits, and a 56px opaque black
     bar lands on top of the name. Hiding it is what the clip does. */
  .page-store .product-media-wrap,
  .page-product .product-media-wrap { overflow: hidden; }

  .page-store .product-media-actions,
  .page-product .product-media-actions {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    border: none;
    transform: translateY(100%);
    transition: transform 0.32s cubic-bezier(0.33, 1, 0.68, 1);
  }
  .page-store .product-media-wrap:hover .product-media-actions,
  .page-store .product-media-wrap:focus-within .product-media-actions,
  .page-product .product-media-wrap:hover .product-media-actions,
  .page-product .product-media-wrap:focus-within .product-media-actions {
    transform: translateY(0);
  }

  /* The label goes red under the pointer. The colours were already carried
     down; what was missing is the transition to ease between them, and the
     ::after sweep that has to come off first.

     That sweep is the wipe .btn-cart and friends use -- a panel that slides
     up through the button on hover. On this control the desktop block turns
     it off outright, because the button already moves: it is riding up the
     photograph at the same moment, and a second thing sliding inside it
     reads as a glitch rather than as a hover. With it left on, it also
     paints OVER the red. */
  .page-store .cart-overlay-btn,
  .page-product .cart-overlay-btn {
    transition: background-color 0.22s ease;
  }
  .page-store .cart-overlay-btn::after,
  .page-product .cart-overlay-btn::after { content: none; }

  @media (prefers-reduced-motion: reduce) {
    .page-store .product-media-actions,
    .page-product .product-media-actions { transition: none; }
  }
}

/* ============================================================
   The three bands stop growing on the way down
   ============================================================
   Asked for: About Us, USA Sourced and Get In Touch must not get TALLER as
   the window narrows, at any width.

   Why they did. Every one of these bands is a column of type whose measure
   shrinks with the window while the type itself is pinned at the full-width
   size -- 2.5rem headings and 1.125rem copy, held there deliberately, because
   holding them there is what the last several passes were about. A fixed
   line in a narrowing column is a line that wraps sooner, and every wrap is
   another 32px of band. That is the whole mechanism; there is nothing else
   in it. Measured, as the window closed:

     Get In Touch   489 at 1200  ->  521 at 1025  ->  521 at 861
                    417 at 640   ->  449 at 500   ->  481 at 390  ->  563 at 320
     About Us       458 at 900   ->  490 at 861
                    450 at 859   ->  482 at 800

   The one at the foot is the worst of them: a third taller at 320 than at
   640, and the client saw it.

   Worth saying plainly, because it is the reason this block is shaped the
   way it is: the request cannot be met in full. A band of type that keeps
   its type size and loses its measure MUST get taller -- holding
   "GET IN TOUCH" and its sentence at full size in a 184px column and in a
   557px column at the same depth is not a layout problem, it is arithmetic.
   Something has to give, and the only question is what and how little.

   Three things give here, in order of how visible they are:

   1. GEOMETRY FIRST, because it costs the type nothing. Under 750 the Get In
      Touch plate takes the band edge to edge -- it was inset one gutter
      inside it -- which buys 60px of measure for nothing but a margin
      nobody was reading as a margin. That alone took the band from
      417/449/481/563 down the range to a flat 417.

   2. PADDING, fluid rather than fixed. The panel padding and the band's own
      vertical padding shrink with the viewport instead of holding 36px and
      64px to the floor. Both clamps meet their full-width value exactly at
      749.98px -- 6vw is 44.9px there and 9vw is 67.4px, so both are already
      capped -- which is what keeps the breakpoint itself seamless. Nothing
      steps at 750; the numbers only start moving below it.

   3. TYPE, last and least. The copy in these bands gives up 11% -- 1.125rem
      down to 1rem, 20.25px to 18px -- and only below about 1160px, where
      1.55vw crosses under the ceiling. This is the one thing here that walks
      back an earlier instruction, so it is worth being exact about the size
      of it: 18px against 20.25px is a difference you can see in a side by
      side and not one you can see in a window. The headings do not move at
      all; they are still 2.5rem everywhere.

      A 1.0625rem floor was tried first and left too many wraps standing --
      Get In Touch went 484 at 1200 to 514 at 1100 with it. A 0.9375rem floor
      removed no more wraps than 1rem did and cost another 6% of size. 1rem
      is where the two curves crossed.

   What that buys, measured end to end:

     About Us     631 570 526 482 495 471 438 438 438 400 400 400 | 933 894
                  847 779 754 717 690 682 652 644
     USA Sourced  631 570 526 482 449 471 437 414 396 395 368 345 | 876 866
                  818 750 696 660 632 595 595 557
     Get In Touch 526 489 481 478 507 449 478 478 478 438 438 438 | 409 407
                  397 377 359 353 378 373 369 395

   (1440 1300 1200 1100 1025 | 1024 950 900 861 | 859 800 750 | then the
   stack, 749 700 640 560 500 460 430 390 360 320.)

   Both story bands are monotonic below 750 now -- 933 down to 644, and 876
   down to 557, never once up. Above it they hold flat across each layout
   range and only move where the layout itself does, at 1024 and at 749.

   Get In Touch has two 25px steps left in it, at 430 and at 320, and one at
   1025. All three are the same event: the sentence taking one more line at a
   width where no amount of padding was left to give it. The band is never
   taller than it is at 749 anywhere below 749, which is the part of the
   promise that can be kept. Going further means taking the copy under 18px
   or the heading under 2.5rem, and both were asked for the other way. */
@media (max-width: 749.98px) {
  .page-home .home-story-copy { padding: clamp(20px, 6vw, 36px); }
  /* The band's own air above and below the plate. 9vw is 67.4px at 749.98,
     so this too meets 64px at the breakpoint and gives from there. */
  .page-home .home-touch,
  .page-about .home-touch { padding: clamp(44px, 9vw, 64px) 0; }
}
/* The 11%. Unscoped by width on purpose: 1.55vw is 22.3px at 1440 and the
   1.125rem ceiling holds it there, so the declaration is inert across the
   whole desktop range and a media query around it would only be a second
   place to keep the same number.

   .page-home, so the About page's own story panels -- which have a different
   shape and were not part of the complaint -- are untouched. Two classes and
   an element beats the desktop block's .home-touch p and ties
   .home-about .home-story-copy p, which this sits below in the file. */
.page-home .home-story-copy p,
.page-about .home-story-copy p,
.page-home .home-touch p,
.page-about .home-touch p { font-size: clamp(1rem, 1.55vw, 1.125rem); }

/* ---- The hero bar, on a phone -------------------------------------------
   Three things share one row in there now -- four social icons, the carousel
   controls, the flag -- and at their full-width sizes they add up to about
   321px plus two gutters. That overflows a 320px window outright, at which
   point the icons push the controls off centre before anything visibly
   breaks, which is the worse failure of the two because it looks intentional.

   So everything in the bar comes in a step under 560px -- back to the sizes
   it carried while the bar was 46px deep, which is a happy accident and not
   a plan. The measurements are the point of the numbers, not taste:

     social   4 x 18px + 3 x 14px gap    = 114px   (was 132)
     controls 2 x 34px + dots 61px + 4   = 133px   (was 156)
     flag                                  33px
                                          -------
                                           280px, inside a 320px window's 288

   Nothing here is scoped to .page-home: the bar belongs to the hero and the
   hero is only on this page, so the class would be noise. Placed at the end
   of the file because it has to beat the two blocks that build the bar --
   above and below 860 -- and it ties both on specificity. */
@media (max-width: 560px) {
  .hero-social { gap: 14px; }
  .hero-arrow { width: 34px; height: 34px; }
  .hero-arrow svg { width: 18px; height: 18px; }
  .hero-dots { gap: 7px; padding: 0 5px; }
  .hero-dot { width: 20px; height: 26px; }
  .hero-carousel-nav { gap: 2px; }
}

/* ---- The country picker in the header -----------------------------------
   A second copy of the footer's control, in .header-actions to the left of
   search, account and cart. It takes every rule the footer's copy takes --
   .region-dropdown, .region-select, .region-dropdown-list -- because it is
   the same markup with the same classes, and it takes the behaviour for the
   same reason: initRegionDropdown() initialises every .region-dropdown on the
   page and keys nothing to an id.

   Three things differ, and .header-region is the whole of what says so. */

/* 1. It opens DOWNWARD. The footer's list is pinned by `bottom` because that
      control sits near the foot of the page and a menu below it would fall
      off the document; the header's is at the top, where the opposite is
      true. bottom: auto is not optional -- setting top alone leaves both
      edges pinned and the list stretches between them. */
.header-region .region-dropdown-list {
  top: calc(100% + 6px);
  bottom: auto;
  /* Right-aligned to the control for the same reason: the control sits a
      gutter from the window's right edge, and a list that is wider than its
      button -- these options are -- would otherwise grow off the screen.
      left: auto releases the footer's pin the way bottom: auto does. */
  left: auto;
  right: 0;
}

/* 2. It steps aside under 1024px. That is where the nav collapses into the
      hamburger, and it is the width at which the bar stops having room for a
      150px control that is not one of the three things people came to the
      header for. The footer's copy is still there at every width, so nothing
      is lost -- only moved. */
@media (max-width: 1024px) {
  .header-region { display: none; }
}

/* 3. A step up in size, because of what it now sits beside. The footer's
      control is 0.78rem in a row of 18px social marks; this one is next to
      three 40px glyphs in a 96px bar, and at the footer's size it read as a
      control that had been dropped in from a smaller design. The border and
      the colours are untouched -- it should still be recognisably the same
      object in both places. */
.header-region .region-select {
  font-size: 0.875rem;
  /* Vertical padding only. The sides were holding the label off a border that
     is not there any more, and in a row whose gap is already 20px they were
     adding a second 14px on top of it -- so the space between this label and
     the search mark read as half again the space between the marks
     themselves. Zero on the sides makes the row label-to-glyph the way the
     three icons beside it are glyph-to-glyph. The vertical stays: it is the
     click target, and it costs nothing in a 96px bar. */
  padding: 10px 0;
  gap: 12px;
}
.header-region .region-select svg { width: 14px; height: 14px; }

/* ---- The cart controls go square on a phone -----------------------------
   Select Colour / Select Size, the quantity stepper and Add to Cart, on the
   product page and in the quick view sheet alike.

   The colours were already right: the block up the file that carries the
   desktop treatment down to this width fills all three with #e6e6e6 and
   takes their borders off. What it does not carry is the CURVE, and that is
   not an oversight in it -- that block was generated from the desktop rules
   and filtered to background / colour / border / shadow, precisely so it
   could not drag layout down with it. border-radius fell on the layout side
   of that filter.

   So the controls kept the shapes their base rules give them: 8px on the
   select trigger (--radius) and a full 999px pill on the stepper, which is
   what makes the row read as a stack of lozenges rather than as the flat
   blocks it is meant to be.

   Two of these three are also what the desktop already does -- the stepper
   is squared at 13077 and Add to Cart at 12943, both inside the min-width
   block -- so squaring them here closes a gap between the two rather than
   opening one.

   The select trigger is the exception and worth knowing about: nothing
   squares it above 860 either, so it is 8px-rounded on a desktop and square
   on a phone after this. That is what was asked for, and the desktop rule is
   one line away if the two should meet.

   The list is squared with the trigger it drops out of. A square control
   opening a rounded menu is the seam you notice.

   At the foot of the file because it has to outrank the base rules at 6072
   and 6093, which it ties on specificity. */
@media (max-width: 860.01px) {
  .page-product .color-select-trigger,
  .page-product .color-select-list,
  .page-product .product-detail-cart-row .qty-stepper,
  .page-product .btn-cart,
  .quick-view-info .color-select-trigger,
  .quick-view-info .color-select-list,
  .quick-view-actions .qty-stepper,
  .quick-view-actions .btn-cart { border-radius: 0; }
}

/* ---- 1. The product page's header lets go on a phone --------------------
   .site-header goes position: sticky under 860px for every page. On the
   product page that means the bar rides down over a column that is already
   long -- gallery, options, description, reviews, related -- and takes 96px
   of a phone screen with it the whole way.

   relative, not static: static would also drop it out of the z-index: 100 it
   carries on its base rule, and that number is doing real work -- the cart
   drawer, the search panel and the quick view all sit above the page and
   below the chrome by it. relative keeps the stacking and only gives up the
   pinning. top: auto for the same reason -- the 0 the sticky rule sets is
   inert on a relative box, but leaving it would be a value that means
   nothing sitting in a rule that means something.

   Only this page and only this width. The home page's header is deliberately
   sticky over the hero (initHeaderChrome drives it), the contact page pins
   its own two blocks down the file, and everything above 860 is unchanged. */
@media (max-width: 860.01px) {
  .page-product .site-header {
    position: relative;
    top: auto;
  }
}

/* ---- 2. Section headings take the site's display ramp -------------------
   YOU MAY ALSO LIKE / More from this category, at the foot of the product
   page, and the same pair on the affiliate and FAQ pages -- three uses of one
   component.

   The h2 was on the BASE heading rule: --font-display at 400, title case,
   0.0375em of tracking and a size that ran clamp(1.75rem, 3.2vw, 2.3rem).
   Every other heading the site actually shows -- THE CRAFT, ABOUT US, GET IN
   TOUCH, FREE MEMBERSHIP, and the five page banners -- is Barlow Condensed
   700 in caps at a flat 2.5rem. Beside them a light, wide, title-case line
   reads as another site's heading.

   These are the five values that block up the file lists, taken together for
   the reason it gives: they are one type style, and any of them alone leaves
   a heading that matches nothing.

     weight      400        ->  700
     transform   none       ->  uppercase
     tracking    0.0375em   ->  0.02em
     size        28-37px    ->  40px flat
     leading     1.3        ->  1.1

   The eyebrow above it is untouched -- it was already the site's, and only
   the heading under it was out of step.

   Not scoped to a page: three pages carry this component and a heading style
   that is right on one of them is right on all three. */
.section-head h2 {
  font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
               'Segoe UI', Roboto, sans-serif;
  font-weight: 700;
  text-transform: uppercase;
  font-size: 2.5rem;
  letter-spacing: 0.02em;
  line-height: 1.1;
}

/* ---- 3. SORT reads on the store page ------------------------------------
   The button was `color: var(--text)`, and that token is #fbf7f3 -- the
   site's NEAR-WHITE, for type on the dark pages this control was drawn for.
   The store page is white now, so the label was near-white on near-white and
   only its outline showed. Both rules that set it -- the desktop one and the
   touch one -- carry the same token, so this was true at every width; the
   phone is only where it was noticed.

   #141414 literally rather than a token, matching .page-store .sort-select
   two rules of this kind up the file, which had the same problem fixed the
   same way. The icon needs no rule: it is stroke="currentColor" and follows
   the label.

   Scoped to .page-store, because .filter-sidebar-trigger is shared with the
   filter drawer on any page that grows one, and the fix belongs to the white
   ground rather than to the control. */
.page-store .filter-sidebar-trigger { color: #141414; }

/* ---- The close cross, now that the strip under it is white --------------
   It sits ON .quick-view-handle, and that strip just went from #131313 to
   the sheet's own white. Everything about this button was drawn for the dark
   one: transparent fill, a --on-dark-border-strong ring, and --text for the
   glyph -- which is #fbf7f3, the site's near-white. Left alone it would be a
   near-white cross in a pale ring on a white bar: present in the DOM, gone
   from the screen.

   Three values, one for each of those. The fill stays transparent, because
   the strip behind it is now the surface the button should read against
   rather than something to be hidden from. The ring drops to 18% black --
   the same weight the light ring had against the dark strip, inverted. The
   glyph goes to #141414, which is what every other dark-on-light control in
   this popup is set to.

   Scoped to the same 760px the sheet form is, so the inset popup on a wider
   window -- where this button sits over the photograph, not over a strip --
   keeps the treatment it was drawn with. */
@media (max-width: 760px) {
  .quick-view-close {
    background: transparent;
    border-color: rgba(20, 20, 20, 0.18);
    color: #141414;
  }
}

/* The block that turned the eyebrow's dash off on a phone is gone with the
   dash itself -- see .eyebrow up the file, where the ::before it was
   cancelling has been deleted. It came off mobile first and desktop a pass
   later; there is nothing left here to switch by width. */

/* ============================================================
   The product page's own type and spacing, at every width
   ============================================================
   Two things the phone was missing, and they are missing for the same
   reason. The block at the foot of this file that carries the desktop
   treatment down to mobile was generated from the desktop rules and filtered
   to background / colour / border / shadow, so that it could not drag layout
   with it. That filter is why the product page's COLOURS have matched for a
   while and its type and spacing have not.

   These are the declarations that fell on the other side of it. */
@media (max-width: 860.01px) {
  /* ---- 1. The product name ---------------------------------------------
     It was on the base heading rule: --font-display at 400, title case,
     0.0375em of tracking. The desktop block gives it the popup's treatment,
     which is the band headings' -- 700, caps, 2rem, tight tracking -- and
     that is what the name is meant to be. Beside RINGTAIL GEAR above it and
     the price below, a light title-case line was the odd element in a column
     of condensed black caps.

     All five values together, for the reason the page-header block up the
     file gives: they are one type style and any of them alone leaves a
     heading that matches nothing. */
  .page-product .product-detail-header h1 {
    font-weight: 700;
    text-transform: uppercase;
    font-size: 2rem;
    letter-spacing: 0.02em;
    line-height: 1.1;
  }

  /* ---- 2. The gaps down the options column ------------------------------
     The base rules give .product-color-select `margin: 20px 0` and the cart
     row `margin: 24px 0`. These are flex children, and flex margins do NOT
     collapse -- so Select Size to Select Color came out at 20 + 20 = 40px,
     and Select Color to the stepper at 20 + 24 = 44px. That is the run of
     large uneven gaps down the column.

     The desktop block solved this by giving every one of them a single
     bottom margin of 18px and taking the rest off. Same values here, so the
     column is one rhythm at both widths: 18px between the divider, Select
     Size, Select Color and the stepper, with the cart row's own 18px gap
     carrying it through to Add to Cart. */
  .page-product .product-color-select { margin: 0 0 18px; }
  .page-product .product-detail-cart-row {
    /* A column, not a wrapping row. The base rule is `flex-wrap: wrap` with
       space-between, which on a narrow screen happens to wrap into a stack --
       but by running out of room rather than by being asked to, so the gaps
       it leaves are the row's 12px and not the column's 18px. Saying column
       outright is what makes the spacing predictable. */
    flex-direction: column;
    align-items: stretch;
    flex-wrap: nowrap;
    gap: 18px;
    margin: 0;
  }
  .page-product .product-detail-cart-row .qty-stepper,
  .page-product .product-detail-cart-row [data-add-to-cart-main] { width: 100%; }
  /* Add to Cart sits a little further from the stepper than the stepper does
     from Select Color -- the popup's own offset, and for the same reason: it
     is the one control in the column that acts, and the extra distance is
     what stops it looking welded to the control above it. */
  .page-product .btn-cart { margin-top: 10px; }
}

/* ---- The social row leaves the hero band on mobile ----------------------
   The band under the photograph carries three things on a wide screen: the
   four social marks on the left gutter, the carousel controls in the middle,
   the flag on the right. The marks come out below 1024px.

   1024 rather than 860, matching the hero above it and the header above that
   -- one width for "mobile" on this page.

   Nothing else in the bar has to move for it. .hero-social-bar .container is
   a 1fr / auto / 1fr grid with each item placed by name, so an absent item
   leaves an empty track rather than shifting the two that remain: the
   controls stay centred against the bar and the flag stays on the right
   gutter. That is the difference between placing items and letting them flow
   -- with flow, losing the first of three would have slid the other two left.

   The marks are not lost, only this copy of them. The footer carries the
   same four at every width. */
@media (max-width: 1024px) {
  .hero-social { display: none; }
}

/* ---- IN STOCK moves into the picker on a phone too ----------------------
   The desktop block lifts the status out of the flow and sets it inside the
   select box, right-aligned before the chevron. On mobile it was still an
   in-flow child of .color-select-row -- which is flex-direction: column
   there -- so it dropped onto its own line under the box, with the dot
   hanging off the left gutter beside nothing.

   The same four rules, and they work here for the same reason they work up
   there: the status is the trigger's SIBLING, so .color-select-row is the
   nearest thing that can be its positioning context -- and because the open
   menu is itself absolute against .color-select-dropdown, that row's height
   is exactly the trigger's, so top: 0 / bottom: 0 centres the status on the
   box rather than on a taller container.

   right: 40px clears the chevron -- 13px of art plus the trigger's 14px of
   right padding. pointer-events: none so a tap aimed at the picker is not
   eaten by the label sitting over it, which matters more on a touch screen
   than it did with a mouse. The dot's 7px top margin goes: it existed to
   drop the dot onto a first line's baseline in a flex-start block, and
   centred on one line it is simply centred. */
@media (max-width: 860.01px) {
  .page-product .color-select-row { position: relative; }
  .page-product .qv-stock-status {
    position: absolute;
    top: 0;
    bottom: 0;
    right: 40px;
    margin: 0;
    padding: 0;
    min-height: 0;
    align-items: center;
    pointer-events: none;
  }
  .page-product .qv-stock-status::before { margin-top: 0; }
}

/* ---- The stock line stays on one line inside the picker -----------------
   Inside the box the status is absolute, so a line that wrapped grew
   leftward over the colour name. It is short now ("Only 5 left"), and
   nowrap keeps it a single line at every width. */
.page-product .qv-stock-status,
.quick-view-info .qv-stock-status { white-space: nowrap; }

/* ---- The buy bar's stock line is in the row, not over it ----------------
   .pd-buybar-stock also carries .qv-stock-status, so the picker rules above
   (.page-product .qv-stock-status: absolute, right: 40px) caught it too and
   pinned it over the quantity stepper. In the bar it is simply the first
   item in the controls row, left of the pickers. */
.page-product .pd-buybar .pd-buybar-stock {
  position: static;
  top: auto;
  bottom: auto;
  right: auto;
  align-items: center;
}

/* ---- The hero button, centred for certain -------------------------------
   .hero-copy is display: block below 1024 and .hero .hero-actions is
   justify-content: center there, which should have been the whole of it --
   and was not. The button kept sitting against the right gutter.

   What was still on it: .hero-copy .hero-actions carries grid-column: 2,
   grid-row: 1 / 3, align-self: end and justify-self: end from the two-column
   desktop layout. Those are meant to be inert once the parent stops being a
   grid, and mostly they are -- but leaving a full set of placement
   declarations pointing at a column that no longer exists is a bet on which
   of them the layout engine ignores, and it is not a bet worth making twice.

   So they are unset by name rather than left to be overlooked, and the
   centring is stated three ways that cannot disagree: the box is the full
   width of the block, its contents are centred in it, and its own alignment
   -- should anything ever treat it as a grid or flex item again -- is
   centre rather than end.

   Last in the file so nothing outranks it. */
@media (max-width: 1024px) {
  .hero-copy .hero-actions {
    grid-column: auto;
    grid-row: auto;
    align-self: auto;
    justify-self: center;
    width: 100%;
    justify-content: center;
    align-items: center;
    margin-top: 21px;
  }
}

/* The width-scoped copy of --reveal-from that used to sit here is gone: the
   value moved onto :root itself, so every image on the site fades up out of
   #141414 at every width rather than only on a phone. See that token for
   what the filter list does and why #141414 needs an invert() to reach. */

/* ---- The hero photo rides up on a phone ---------------------------------
   Asked for: move it up a bit so more of the bag shows above the copy.

   background-position cannot do it, and the reason is worth writing down. The
   files are square, 1800x1800. A phone's hero frame is portrait -- about
   390x694 -- so `cover` scales the image by its HEIGHT, not its width: the
   rendered image is 694 tall and 694 wide, and it is the SIDES that overflow
   and get cropped. Vertically it fits the frame exactly, which means there is
   no slack for background-position-y to move it into. `center 30%` and
   `center 70%` render identically here. On the desktop's landscape frame the
   opposite is true, which is why that offset works up there and is why this
   looked like it should have been a one-word change.

   So the slack has to be made first. scale: 1.16 renders the layer 16% larger
   than the frame -- 8% hanging off the top and 8% off the bottom -- and
   translate: 0 -6% then slides it up into six of those eight, leaving 2% of
   margin at the bottom so nothing uncovers. The bag's centre lands about 6%
   of the frame higher than it did, roughly 40px on a phone, which clears the
   headline without the photo reading as a different crop.

   These are the `translate` and `scale` PROPERTIES, not transform functions,
   and that is what makes them safe here. transform on this element already
   belongs to the entrance -- scale(1.28) settling to scale(1) over 2.7s --
   and writing this as a transform would have meant either fighting that
   animation or restating it. The individual properties compose into the same
   matrix without either knowing about the other, exactly as --hero-zoom does
   on desktop, so during the first seconds of the page the photo simply
   settles from 1.49 to 1.16 with the offset held throughout.

   Mobile only. The desktop frame is landscape, the crop is horizontal there,
   and `scale` on that element is already spoken for by --hero-zoom. */
@media (max-width: 860.01px) {
  .hero-slide-img {
    scale: 1.16;
    translate: 0 -6%;
  }
}

/* ============================================================
   Rebuilt after a stylesheet loss
   ============================================================
   Everything below was written earlier today and lost when the file was
   truncated. It is restored here in final form rather than as the sequence of
   passes it was written in, so each rule states where it ended up rather than
   how it got there. The slide-up sheets -- the nav drawer, Sort, cart, search
   and the quick view -- are deliberately NOT part of this: that work is left
   out at the asking.

   These are appended rather than edited back into place. Where one of them
   restates a rule that already exists further up the file, it wins on source
   order at equal specificity, which is the same mechanism the file already
   relies on in a dozen places. */

/* ---- The hero clips its slides, twice over -----------------------------
   overflow: hidden on .hero-photo should be enough on paper, but
   .hero-slides carries will-change: transform, and a promoted compositor
   layer is composited against its ancestors' clips rather than painted
   inside them -- so a strip of the next slide could bleed past the right
   edge. clip-path is a hard clip on the same rectangle that survives layer
   promotion.

   The per-slide clip is the one that matters most. .hero-slide-img is
   inset: 0 in its slide but does not stay there: the entrance scales it to
   1.28, the phone rule to 1.3, and --hero-zoom to 1.08 as the page scrolls.
   For the slide on screen that overflow lands outside .hero-photo and is
   clipped; for the slide NEXT to it, the overflow on its leading edge lands
   inside the visible window -- which is the neighbour appearing at the right
   edge and growing as you scroll. */
.hero-photo { clip-path: inset(0); }
.hero-slide { overflow: hidden; }

/* ---- The story photos' ground -----------------------------------------
   These two do not fade through --reveal-from: they carry .is-unfiltered and
   fade with OPACITY against the frame behind them, which is why the ground is
   load-bearing rather than decorative. It was set inside the desktop block,
   so on a phone they came up out of whatever was behind .split-media --
   white, since that band went light. Unscoped now, and it covers the About
   page's two story photos as well. */
.home-story-media .split-media { background: #141414; }

/* ---- The About banner drops its bottom rule ---------------------------
   The store page's banner gave up the shared 1.3px --on-dark-border a while
   back; this is the same removal. Two classes against the shared list's one,
   so it wins at every width without a media query. Nothing replaces it: the
   banner is #141414 against a white section, and a hairline across that edge
   draws a line where there is already a wall. */
.page-about .page-header-about { border-bottom: none; }

/* ---- The category plates shrink into place ----------------------------
   They had half an entrance -- an opacity fade over the card's #141414,
   written inside the desktop block -- so they arrived out of the right colour
   without the settle, and on a phone did not animate at all.

   opacity over the card's painted ground rather than the --reveal-from
   filter: the plate IS the card, there is no panel under it, so a filter
   would have nothing behind it to fade against. .home-featured
   .category-card carries background: #141414 in both width blocks, and
   opacity over that lands on exactly the colour wanted.

   1.2s, the product cards' timing rather than the hero's 2.7s: these arrive
   as a row -- revealTogether() lights the whole run at once -- and a long
   settle on three things moving together reads as the page still loading. */
.home-featured .category-media {
  opacity: 0;
  transform: scale(1.28);
  transition: opacity 1.1s ease,
              transform 1.2s cubic-bezier(0.16, 1, 0.3, 1);
}
.home-featured .category-media.is-visible { opacity: 1; transform: scale(1); }
@media (prefers-reduced-motion: reduce) {
  .home-featured .category-media { transition: opacity 1.1s ease; transform: none; }
  .home-featured .category-media.is-visible { transform: none; }
}

/* ---- The YouTube mark is drawn, not filled ----------------------------
   Its neighbours are stroked paths in currentColor over fill: none. YouTube
   alone was a SOLID rounded rectangle with a second path on top of it,
   painted the panel's own #1c1d19 to stand in for the play triangle -- a hole
   punched by painting over, which only works while the paint matches what is
   behind it. Drawn properly it cannot be broken by any ground.

   All three rows: the footer, the band under the hero, and the nav drawer.
   The selector is on the ROW so a fourth would have to opt in deliberately.
   :first-of-type and :last-of-type name the two paths without touching
   markup, which matters -- that nav is copied into eleven pages. */
.mobile-nav-social a[aria-label="YouTube"] svg path:first-of-type,
.footer-social a[aria-label="YouTube"] svg path:first-of-type,
.hero-social a[aria-label="YouTube"] svg path:first-of-type {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linejoin: round;
}
.mobile-nav-social a[aria-label="YouTube"] svg path:last-of-type,
.footer-social a[aria-label="YouTube"] svg path:last-of-type,
.hero-social a[aria-label="YouTube"] svg path:last-of-type {
  fill: currentColor;
}

/* ---- The country picker and the social row stay on a desktop ----------
   Both are hidden under 1024px further up the file. A desktop browser dragged
   narrow is still a desktop, so they are given back for a pointer that says
   it is a mouse.

   Written as a restore rather than as a narrower hide, and the order is the
   point: the phone case is the DEFAULT, so it is right from the first frame.
   Written the other way round -- displayed by default, taken away by a
   pointer query -- the control paints, the query resolves, and the control
   goes, which is a flash you can see on every page load. */
@media (max-width: 1024px) and (hover: hover) and (pointer: fine) {
  .header-region { display: block; }
  .hero-social { display: flex; }
}

/* ---- The hero photo rides up on a phone -------------------------------
   background-position cannot do this, and the reason is worth keeping. The
   files are square, 1800x1800, and a phone's hero frame is portrait -- so
   `cover` scales by HEIGHT, the rendered image fits the frame exactly
   vertically, and it is the SIDES that overflow. There is no vertical slack
   for background-position-y to move into; center 30% and center 70% render
   identically. On the desktop's landscape frame the opposite is true, which
   is why the offset works up there.

   So the slack is made first, and the two values are locked to each other:
   an upward translate can never exceed half of what the scale adds, or the
   bottom of the frame uncovers.

     scale: 1.3        30% larger than the frame -- 15% off the top, 15% off
                       the bottom
     translate: -13%   up into thirteen of those fifteen, leaving 2% of margin

   Roughly 90px on a phone, which puts the whole bag clear above the copy. It
   was 1.16 / -6% first and that was not enough. What it costs is the sides:
   on a portrait frame, up means in, and the buckle at the left edge goes
   first. To retune, move both numbers together and keep the translate at
   least 2 under half the scale's overshoot.

   These are the `translate` and `scale` PROPERTIES, not transform functions.
   transform on this element already belongs to the entrance -- scale(1.28)
   settling over 2.7s -- so writing this as a transform would mean fighting
   that animation or restating it. The individual properties compose into the
   same matrix without either knowing about the other, exactly as --hero-zoom
   does on desktop. */
@media (max-width: 860.01px) {
  .hero-slide-img {
    scale: 1.3;
    translate: 0 -13%;
  }
}

/* ---- The header's three marks: white, and 44px -------------------------
   Two separate faults, one visible result.

   COLOUR. The two icons took .icon-btn's base var(--on-dark) -- #fbf7f3,
   near-white -- and the hamburger sat at rgba(251, 247, 243, 0.72). That 72%
   is the resting half of a hover pair: on desktop these marks rest muted and
   come up under the pointer. A touch screen has no pointer to lift them, so
   the muted state is the only state and all three read as grey. #ffffff
   literally -- there is no second state here to vary between, and --on-dark
   would leave them a shade under the logo's white beside them.

   SIZE. .site-header .icon-btn svg is 36px inside the min-width: 860.02px
   block, so below that breakpoint the two marks fell back to .icon-btn svg's
   own 18px -- the size drawn for a bordered 40px tile, where the box was the
   button. The hamburger did not fall back, being styled in the mobile blocks
   in its own right, so the bar had a 36px hamburger beside two 18px marks.

   44px on all three. The marks ARE the buttons now that the border has gone,
   44 is the size a touch target wants, and in a 96px bar it leaves 26px of
   air over and under.

   The BOX takes the glyph's size, height included. With the border gone the
   40px tile is invisible but still spent -- a mark centred in it carries dead
   space each side, so marks 20px apart read as far wider. And the box must be
   square: .cart-count hangs off it at top/right -7px, so at 44 wide and 40
   tall the glyph would overflow its own box and the badge would sit inside
   the mark rather than off its corner. */
@media (max-width: 860.01px) {
  .site-header .icon-btn,
  .site-header .nav-toggle {
    width: 44px;
    height: 44px;
  }
  .site-header .icon-btn svg,
  .site-header .nav-toggle svg {
    width: 44px;
    height: 44px;
  }
  .site-header .icon-btn,
  .site-header .nav-toggle,
  .site-header .header-actions .icon-btn svg,
  .site-header .nav-toggle svg {
    color: #ffffff;
  }
}

/* ---- The social marks in the footer and the hero band ------------------
   Same hold-back, same reason it is wrong on a phone: var(--on-dark-muted) is
   the resting half of a hover pair and there is no hover to complete it.
   Both rows sit on near-black at every width, so they can take white without
   a second thought.

   The nav drawer's copy of this row is deliberately not here -- that sheet is
   part of the slide-up work being left out. */
@media (max-width: 860.01px) {
  .footer-social .icon-btn,
  .hero-social .icon-btn {
    color: #ffffff;
  }
}

/* ---- The quick add sheet is square on a phone --------------------------
   The sheet is flush to the left, right and bottom of the screen -- rounding
   edges that are off-screen only cuts corners off the content -- so the two
   at the top were the last curve on it.

   This has to be said HERE, at the foot of the file, and that is the whole
   trick of it. The max-width: 760px block up the file already gives
   .quick-view-modal a radius, and an unscoped .quick-view-modal further down
   sets var(--qv-radius) -- and between two rules of equal specificity the
   later one wins whatever media query either sits in. A media query adds
   nothing to specificity. The sheet's own rule was losing, silently, and it
   went unnoticed for a long time because the two disagreed invisibly: the
   sheet asked for the top two corners rounded, the later rule gave it all
   four, and the bottom two are off the bottom of the screen. Only asking for
   square gave the disagreement a visible side.

   The photo below the handle is squared with it. It was rounding its own top
   pair, but it does not own them -- the handle strip is above it and is what
   meets the sheet's top edge, so rounding the photo as well put a second pair
   of curves one strip further down, in the middle of the sheet.

   Then both are given back for a mouse. Under 860px with a fine pointer the
   popup is not a sheet at all: it returns to an inset panel floating on a
   dimmed page, and an inset panel wants its corners. Square is the default
   because the phone is the case that must not depend on a pointer query
   resolving in time -- it is the one you would see. */
@media (max-width: 760px) {
  .quick-view-modal { border-radius: 0; }
  .quick-view-media { border-radius: 0; }
}
@media (max-width: 760px) and (hover: hover) and (pointer: fine) {
  .quick-view-modal { border-radius: var(--qv-radius); }
  .quick-view-media { border-radius: var(--qv-radius) 0 0 var(--qv-radius); }
}

/* ---- The close crosses give up their rings on a phone ------------------
   Sort's, the cart's, Sign In's, search's and the quick view's. Every one of
   them is an .icon-btn or a copy of one: a bordered 40px tile that its own
   sheet then rounds into a circle.

   That ring was the tile doing the work of being a button, from when these
   marks were 18px and needed a box to be findable. The header's three marks
   gave theirs up on the same argument -- with the border gone the MARK is the
   button -- and these are the last boxed controls left on the site. A ringed
   cross beside a bare grab bar reads as two different kinds of control in one
   strip.

   border-radius: 0 as well as border: none. The radius is invisible without
   a border or a fill, but leaving it behind means the ring comes back the
   moment anything gives one of these a background -- which is exactly the
   sort of thing a later pass does without noticing.

   No colour here. These sheets are still dark, so the crosses are already
   light-on-dark and correct; setting a colour would be guessing at a ground
   that has not changed. Only the box goes.

   The 40px hit area is untouched -- .icon-btn keeps its width and height, so
   the target is the same size it was and only the drawing changes. */
@media (max-width: 860.01px) {
  .filter-sidebar-close,
  .search-input-row .icon-btn,
  .quick-view-close,
  #cartDrawer .cart-drawer-head .icon-btn,
  #accountDrawer .cart-drawer-head .icon-btn {
    border: none;
    border-radius: 0;
    background: transparent;
  }
}

/* ---- The drawer gets a rule along its top edge -------------------------
   .mobile-nav-sheet-inner was written with no top border, and the note on it
   says why: "the sheet meets the header's own bottom edge, and two lines a
   pixel apart read as a seam rather than an edge."

   That reasoning depended on the header HAVING a bottom border, and it does
   not any more -- it gave it up in the pass that argued near-black meeting
   white is a clear enough edge without a hairline across it. So the drawer
   has been sliding up to meet nothing, and its top edge has been wherever the
   dark of the sheet happens to stop against the dark of the bar.

   --on-dark-border, #383737: the same token the rules between the menu rows
   are drawn in, so the drawer is bounded by the mark it is divided by rather
   than by a second grey chosen for the edge.

   box-sizing is border-box everywhere in this file (line 592), and this
   element is height: 100% -- so the 1.3px comes out of that height rather
   than adding to it, and the sheet does not grow past the screen or push its
   own foot below the fold. */
@media (max-width: 860.01px) {
  .mobile-nav-sheet-inner {
    border-top: 1.3px solid var(--on-dark-border);
  }
}

/* ---- Browse The Store, and Sign In with it -----------------------------
   Square, unshadowed, white with black type.

   Three class names, and that is not belt and braces -- it is the reason an
   earlier attempt at this button missed it entirely. The drawer's empty state
   is rendered by cart.js, and the markup it writes is `btn btn-outline
   btn-sm`; the cart PAGE's copy of the same message writes `btn btn-primary
   btn-sm`; and Sign In in the account drawer is `btn btn-tan`. A rule naming
   only one of the three styles a button that is not there.

     white on black   .btn-outline reads --btn-white-solid for its fill, a mid
                      grey drawn to sit on a dark panel -- so the label came
                      out grey on grey at about 2:1. #ffffff with #141414 on
                      it is 17.9:1, and on this dark sheet a white button is
                      the one thing that reads as the thing to press.
     square           999px comes from .btn. This is the fourth control to opt
                      out of that pill after Add to Cart, the story buttons
                      and the hero's, and the argument is the same: nothing
                      else in this sheet is a lozenge.
     no shadow        --btn-shadow is 0 3px 18px at 35% black, drawn to ground
                      a pale button on a dark panel. Under the only object in
                      an otherwise empty pane it is a smudge.

   Scoped to .cart-drawer, which is both drawers -- the cart and Sign In --
   and deliberately NOT the cart page, whose own empty state carries
   .btn-primary on a white ground where a white button would disappear. */
@media (max-width: 860.01px) {
  .cart-drawer .btn-outline,
  .cart-drawer .btn-outline:hover,
  .cart-drawer .btn-primary,
  .cart-drawer .btn-primary:hover,
  .cart-drawer .btn-tan,
  .cart-drawer .btn-tan:hover {
    background: #ffffff;
    color: #141414;
    border: none;
    border-radius: 0;
    box-shadow: none;
  }
  /* .btn-* carry a hover sweep on ::after that slides a panel through the
     button. On a white fill there is nothing left for it to reveal, and it
     would animate a grey wash across black type. */
  .cart-drawer .btn-outline::after,
  .cart-drawer .btn-primary::after,
  .cart-drawer .btn-tan::after { content: none; }
}

/* ---- The drawer's foot is bounded at the top --------------------------
   LOG IN and the social row sit below the menu with nothing marking where
   one ends and the other begins -- just the 24px of padding the list carries
   and the 20px the foot carries, which reads as the menu having trailed off
   rather than as a section of its own.

   --on-dark-border again, #383737: the third place in this drawer to use it
   after the rules between the menu rows and the sheet's own top edge, so the
   whole panel is divided by one mark at one weight rather than by a set of
   near-greys.

   On the foot rather than on the list's bottom. Both would draw the same
   pixel, but the line belongs to the block it introduces: give it to the
   list and it moves when the menu's padding is retuned, which is the sort of
   thing that has already drifted twice in this drawer. */
@media (max-width: 860.01px) {
  .mobile-nav-foot {
    border-top: 1.3px solid var(--on-dark-border);
  }
}

/* ---- The cart and Sign In take the same treatment at every width -------
   The two drawers were done on the phone first and left there. Nothing about
   either change was ever about screen size, though: .cart-drawer is
   var(--surface), #202020, at every width, so the button and the cross sit on
   the same dark panel on a desktop as they do on a phone -- and the reasons
   hold identically.

   So these are the mobile rules again with the media query taken off, rather
   than a desktop variant of them. Written after those, so where the two
   overlap this is simply the same declaration arriving second.

     the buttons   .btn-outline (what cart.js writes into the drawer),
                   .btn-primary and .btn-tan (Sign In). White fill, near-black
                   label, square, no shadow -- and the ::after hover sweep off,
                   since it slides a grey panel through a button that is now
                   white and would wash across black type.

     the crosses   the bordered 40px tile that each drawer's head rounds into
                   a circle. With the border gone the mark is the button, the
                   same call the header's three marks and every other close on
                   the site have taken. The hit area is untouched -- only the
                   drawing changes.

   The other three crosses -- Sort, search and the quick view -- stay
   mobile-only. They were asked for on the phone and their sheets are a
   different shape on a desktop; this is the cart and Sign In only. */
.cart-drawer .btn-outline,
.cart-drawer .btn-outline:hover,
.cart-drawer .btn-primary,
.cart-drawer .btn-primary:hover,
.cart-drawer .btn-tan,
.cart-drawer .btn-tan:hover {
  background: #ffffff;
  color: #141414;
  border: none;
  border-radius: 0;
  box-shadow: none;
}
.cart-drawer .btn-outline::after,
.cart-drawer .btn-primary::after,
.cart-drawer .btn-tan::after { content: none; }

#cartDrawer .cart-drawer-head .icon-btn,
#accountDrawer .cart-drawer-head .icon-btn {
  border: none;
  border-radius: 0;
  background: transparent;
}

/* ---- The drawers' buttons take the story CTA's shape and its hover -----
   Browse The Store and Sign In, built as the red LEARN MORE box is built --
   same box, same type ramp, same arrow sliding out of the right edge -- but
   white with a near-black label, because these sit on #202020 rather than on
   a photograph.

   ---- What had to change in the markup

   The story buttons are `<a class="btn btn-tan story-cta"><span>Label</span>
   <svg/></a>`, and the arrow is a real element: the whole hover is that svg
   moving, so there is nothing CSS alone can do on a button whose markup is a
   bare text node. So both buttons were given the span, the arrow and the
   .story-cta class -- Sign In in all eleven pages, Browse The Store in the
   two places cart.js writes it. They are now the same object as the story
   buttons, which is why most of what follows is colour rather than shape.

   ---- The arrow, and why the numbers look odd

   width 27 + margin-left 16 + margin-right -43 = 0. The arrow contributes
   nothing to the button's width at rest: it is parked outside the right
   padding by the negative margin and pushed a further 27px out by the
   translate, and .btn's own overflow: hidden clips it. On hover the margin
   comes back to -6 and the translate to 0, so the arrow travels in and the
   button does not resize under the pointer -- which is the point of paying
   for it with a negative margin rather than by animating width.

   Those five values are the home page's own, read off
   .page-home .home-touch .story-cta > svg rather than chosen again here.

   ---- Colour

   White fill, #141414 label and arrow -- 17.9:1. The fill does not move on
   hover: the story buttons hold their colour and let the arrow be the whole
   answer, and a button that changes colour AND grows a mark is doing two
   things where one is unambiguous. The ::after sweep stays off for the same
   reason it was turned off last pass.

   Every width, not just desktop. The drawer is #202020 at all of them, the
   mobile buttons were already white and square from the previous pass, and
   the arrow is as much use under a thumb as under a pointer. */
.cart-drawer .story-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  gap: 0;
  border-radius: 0;
  border: none;
  box-shadow: none;
  background: #ffffff;
  color: #141414;
  font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
               'Segoe UI', Roboto, sans-serif;
  font-weight: 500;
  font-size: 1.125rem;
  letter-spacing: 0.06em;
  line-height: 1.15;
  text-transform: uppercase;
  padding: 8px 22px 10px;
}
.cart-drawer .story-cta:hover,
.cart-drawer .story-cta:focus-visible {
  background: #ffffff;
  color: #141414;
}
.cart-drawer .story-cta > svg {
  display: block;
  width: 27px;
  height: 14px;
  margin-left: 16px;
  margin-right: -43px;
  transform: translateX(27px);
  flex-shrink: 0;
  transition: margin-right 0.3s cubic-bezier(0.33, 1, 0.68, 1),
              transform 0.3s cubic-bezier(0.33, 1, 0.68, 1);
}
.cart-drawer .story-cta:hover > svg,
.cart-drawer .story-cta:focus-visible > svg {
  margin-right: -6px;
  transform: translateX(0);
}
.cart-drawer .story-cta::after { content: none; }
@media (prefers-reduced-motion: reduce) {
  .cart-drawer .story-cta > svg { transition: none; }
}

/* ---- The mark becomes the ringtail's face -----------------------------
   Two placements, one file: assets/images/logo-mask.png, white line art with
   a transparent ground.

   The ground had to be made. The artwork arrived as a JPEG -- white on solid
   black, no alpha -- and a black square on the #101010 bar would have shown
   its own edge. It was converted by reading each pixel's LUMINANCE as the
   alpha and painting the colour flat white, which is the right transform for
   white-on-black line art specifically: the strokes come through opaque, the
   ground falls away, and the antialiased edges survive as partial alpha
   rather than as a hard 1-bit cutout. 14% of the file is opaque, 4.5% is
   edge, the rest is clear.

   ---- The header

   No text beside it, which is what it already was: the badge's own rim
   carried RINGTAIL GEAR, so the wordmark spans came out of the header a long
   pass ago. The face has no lettering in it, so the mark in the bar is now
   purely a mark -- and the bar has the logo centred with the nav and the
   icons flanking it, which is an arrangement that reads better without a
   name attached anyway.

   Still .logo-skull for sizing. That class is a name and not a description --
   it has been a skull, a badge and now a face, and the eleven heights, the
   scroll-shrink ramp and the entrance opt-out are all written against it.
   .logo-mask is added beside it for what is specific to this drawing.

   ---- The footer

   The same face with RINGTAIL GEAR to the right of it, so the foot of the
   page says the name the header no longer does. The wordmark markup came
   back with it -- and the block that styles it was kept on purpose when it
   fell out of use, with a note saying so, which is why restoring it took no
   new type rules at all.

   .footer-brand .logo is flex-direction: column from when it stacked a mark
   over a wordmark. A row now, centred, with a gap between them. */
.logo-mask { display: block; width: auto; }

/* Stacked: the face on top, RINGTAIL GEAR centred under it. */
.footer-brand .logo {
  flex-direction: column;
  align-items: center;
  gap: 12px;
}
/* Shorter than the badge's 152px: that was a disc, which fills its box, and
   this is a face on a square canvas with clear air above and below the art --
   so the same box height would render a noticeably smaller mark. 96px lands
   the face at about the weight the disc had, and it now shares the line with
   a 27px wordmark rather than standing alone. */
.footer-brand .logo-mask { height: 96px; }
@media (max-width: 860px) and (hover: none) {
  .footer-brand .logo-mask { height: 76px; }
}

/* Shopify test-mode banner — cart page only, and only while the store is not
   live. Deliberately plain and loud rather than styled to match the page: it
   is a notice to whoever is building the store, not part of the storefront,
   and it should not start looking like furniture. */
.shopify-test-banner {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 28px;
  padding: 14px 18px;
  border: 1px solid var(--title-gold);
  border-left-width: 4px;
  border-radius: 6px;
  background: rgba(198, 172, 90, 0.09);
  font-size: 0.86rem;
  line-height: 1.5;
}
.shopify-test-banner strong { color: var(--title-gold); }
.shopify-test-banner span { color: var(--text-faint); }
.shopify-test-banner code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.92em;
  color: var(--offwhite);
}

/* ============================================================
   Cart page: the product page's light treatment
   ============================================================
   cart.html carries .page-product as well as .page-cart, so the white
   ground, near-black headings, header, Free Membership band and the square
   black .btn-cart all come from the product page's own rules. What is here
   is only what the product page has no equivalent of.

   Scoped to #cartPageItems / #cartPageSummary rather than .cart-line on its
   own: the cart drawer lives on this page too and stays dark. */
.page-cart .page-header {
  background: var(--page-ground);
  border-bottom: none;
  padding: 56px 0 8px;
}
/* Set as the product page sets a product's name. */
.page-cart .page-header h1 {
  font-size: 36px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
#cartPageItems .cart-line { border-color: rgba(20, 20, 20, 0.12); }
#cartPageItems .cart-line-media { background: #f0f0f0; }
#cartPageItems .cart-line-media svg { color: rgba(20, 20, 20, 0.4); }
#cartPageItems .cart-line-variant { color: rgba(20, 20, 20, 0.6); }
#cartPageItems .cart-line-price { color: #141414; }
/* The quantity stepper as the product page draws its own: a flat #f0f0f0
   tile, square, no outline. */
#cartPageItems .cart-line-qty-stepper {
  background: #e6e6e6;
  border: none;
  border-radius: 0;
}
#cartPageItems .qty-stepper-btn,
#cartPageItems .cart-line-qty-input { color: #141414; font-size: 17.1px; }
#cartPageItems .cart-line-remove { color: rgba(20, 20, 20, 0.6); border-color: rgba(20, 20, 20, 0.2); border-radius: 0; }
#cartPageItems .cart-line-remove:hover { color: #b02b25; border-color: #b02b25; }
#cartPageItems .cart-empty { color: rgba(20, 20, 20, 0.6); }

#cartPageSummary.contact-info-card {
  background: #f0f0f0;
  border: none;
  border-radius: 0;
}
#cartPageSummary .cart-subtotal { color: #141414; }
#cartPageSummary p { color: rgba(20, 20, 20, 0.6); }
#cartPageSummary code { color: #141414; }
#cartPageSummary a { color: #141414; text-decoration: underline; }

.page-cart .shopify-test-banner { background: rgba(198, 172, 90, 0.12); }
.page-cart .shopify-test-banner strong { color: #7a5f12; }
.page-cart .shopify-test-banner span { color: rgba(20, 20, 20, 0.65); }
.page-cart .shopify-test-banner code { color: #141414; }

/* ============================================================
   Cart drawer: the same light treatment
   ============================================================
   White, near-black type, square controls and the product page's black
   Add to Cart box, at every width and on every page. #cartDrawer rather than
   .cart-drawer: the Sign In drawer shares that class and is not part of
   this. The id also outranks the dark-drawer button rules above. */
#cartDrawer {
  background: var(--page-ground);
  border-left: none;
  color: #141414;
}
#cartDrawer .cart-drawer-head,
#cartDrawer .cart-drawer-foot,
#cartDrawer .cart-drawer-handle,
#cartDrawer .cart-line { border-color: rgba(20, 20, 20, 0.12); }
#cartDrawer .cart-drawer-handle::before { background: rgba(20, 20, 20, 0.25); }
#cartDrawer .cart-drawer-head h3 {
  color: #141414;
  font-size: 1.5rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
#cartDrawer .cart-drawer-head .icon-btn,
#cartDrawer .cart-drawer-head .icon-btn:hover { color: #141414; }

#cartDrawer .cart-line-media { background: #f0f0f0; }
#cartDrawer .cart-line-media svg { color: rgba(20, 20, 20, 0.4); }
#cartDrawer .cart-line-info h4,
#cartDrawer .cart-line-price,
#cartDrawer .cart-subtotal { color: #141414; }
#cartDrawer .cart-line-variant,
#cartDrawer .cart-empty { color: rgba(20, 20, 20, 0.6); }
#cartDrawer .cart-line-qty-stepper {
  background: #e6e6e6;
  border: none;
  border-radius: 0;
}
#cartDrawer .qty-stepper-btn { color: #141414; }
#cartDrawer .cart-line-qty-input { color: #141414; font-size: 17.1px; }
#cartDrawer .cart-line-remove { color: rgba(20, 20, 20, 0.6); border-color: rgba(20, 20, 20, 0.2); border-radius: 0; }
#cartDrawer .cart-line-remove:hover { color: #b02b25; border-color: #b02b25; }

/* View Cart & Checkout, and Browse the Store when the cart is empty: the
   product page's Add to Cart as it finally resolves -- 1.25rem Barlow
   Condensed at 500, the Quick Add bar's height, and a hover that is the fill
   going #141414 to #b02b25 and nothing else (no growth). */
#cartDrawer .btn-tan,
#cartDrawer .btn-outline {
  background: #141414;
  color: #ffffff;
  border: none;
  border-radius: 0;
  box-shadow: none;
  font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
               'Segoe UI', Roboto, sans-serif;
  font-size: 1.25rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  line-height: 1.15;
  padding: 9px 24px 11px;
  height: calc(0.9375rem * 1.8 + 26px);
  transform: none;
  transition: background-color 0.22s ease;
}
#cartDrawer .btn-tan:hover,
#cartDrawer .btn-tan:focus-visible,
#cartDrawer .btn-outline:hover,
#cartDrawer .btn-outline:focus-visible {
  background: #b02b25;
  color: #ffffff;
  transform: none;
}
#cartDrawer .btn-tan::after,
#cartDrawer .btn-outline::after { content: none; }

/* ============================================================
   Contact page: the same light treatment
   ============================================================
   White ground and near-black type, with the product page's values for
   every part the two share: the review form's fields for these fields, the
   black Add to Cart box for Send Message, the #f0f0f0 square tile for the
   info cards, and #e6e6e6 rules between accordion rows.

   The photo header keeps its light type -- it is a photograph, not the page
   -- and takes the store header's opaque ground so no white seam shows round
   the picture while it scales in.

   Scoped to the page's own blocks rather than .page-contact h2/h4, so the
   footer's column headings stay light on the dark footer. */
.page-contact { background: var(--page-ground); }
.page-contact .page-header-contact {
  border-bottom: none;
  background-color: #141414;
}

.page-contact .contact-lead p { color: rgba(20, 20, 20, 0.72); }

.page-contact .form-field label { color: rgba(20, 20, 20, 0.6); }
.page-contact .form-optional { color: rgba(20, 20, 20, 0.55); }
.page-contact .form-field input,
.page-contact .form-field textarea,
.page-contact .form-field select {
  background: #f4f2ef;
  border-color: rgba(20, 20, 20, 0.14);
  border-radius: 0;
  color: #141414;
}
.page-contact .form-field input::placeholder,
.page-contact .form-field textarea::placeholder { color: rgba(20, 20, 20, 0.55); }
.page-contact .form-field input:hover,
.page-contact .form-field textarea:hover,
.page-contact .form-field select:hover { border-color: rgba(20, 20, 20, 0.4); }
.page-contact .form-field input:focus,
.page-contact .form-field textarea:focus,
.page-contact .form-field select:focus { border-color: #141414; }
.page-contact #contactNote { color: rgba(20, 20, 20, 0.6); }

.page-contact .contact-form .btn-tan {
  background: #141414;
  color: #ffffff;
  border: none;
  border-radius: 0;
  box-shadow: none;
  font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
               'Segoe UI', Roboto, sans-serif;
  font-size: 1.25rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  line-height: 1.15;
  padding: 9px 24px 11px;
  height: calc(0.9375rem * 1.8 + 26px);
  transform: none;
  transition: background-color 0.22s ease;
}
.page-contact .contact-form .btn-tan:hover,
.page-contact .contact-form .btn-tan:focus-visible {
  background: #b02b25;
  color: #ffffff;
  transform: none;
}
.page-contact .contact-form .btn-tan::after { content: none; }

.page-contact .contact-info-card {
  background: #f0f0f0;
  border: none;
  border-radius: 0;
}
.page-contact .contact-info-card h4 { color: #141414; }
.page-contact .contact-info-card p { color: rgba(20, 20, 20, 0.6); }
.page-contact .contact-info-card a:hover { color: #141414; }

/* Before You Write: a raised band on the dark page, a quiet warm-white one
   here -- the review fields' #f4f2ef, so the band and the fields above it
   are the same off-white. */
.page-contact .contact-faq-section {
  background: #f4f2ef;
  border-top: none;
}
.page-contact .contact-faq h2 {
  color: #141414;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
.page-contact .contact-faq-lead { color: rgba(20, 20, 20, 0.6); }
.page-contact .contact-faq .accordion,
.page-contact .contact-faq .accordion-item { border-color: #e6e6e6; }
.page-contact .contact-faq .accordion-trigger,
.page-contact .contact-faq .accordion-trigger svg { color: #141414; }
.page-contact .contact-faq .accordion-panel,
.page-contact .contact-faq .accordion-panel p { color: rgba(20, 20, 20, 0.72); }
.page-contact .contact-faq-more { color: rgba(20, 20, 20, 0.6); }
.page-contact .contact-faq-more:hover { color: #141414; }

/* ============================================================
   FAQ page: the same light treatment
   ============================================================
   Same values as the contact page. The accordion now opens on the contact
   page's chevron (faq.html swapped its plus icons for it): near-black,
   pointing down when shut and flipped up when open -- 180deg, overriding
   the 45deg that turned the plus into a cross. */
.page-faq { background: var(--page-ground); }
/* No photograph: faq.html's header dropped .page-header-faq, so this is a
   plain white header set the way the cart page's is. */
.page-faq .page-header {
  background: var(--page-ground);
  border-bottom: none;
  padding: 56px 0 8px;
}
/* On the questions' 820px measure (set inline on faq.html's section), so
   the title lines up with the column under it. */
.page-faq .page-header .container { max-width: 820px; }
.page-faq .page-header .breadcrumb { color: rgba(20, 20, 20, 0.6); }
.page-faq .page-header h1 {
  color: #141414;
  font-size: 36px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

/* Orders & Shipping, Products, Returns. */
.page-faq .section > .container > h3 {
  color: #141414;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
.page-faq .accordion-item { border-color: #e6e6e6; }
.page-faq .accordion-trigger,
.page-faq .accordion-trigger svg { color: #141414; }
.page-faq .accordion-item.open .accordion-trigger svg { transform: rotate(180deg); }
.page-faq .accordion-panel p { color: rgba(20, 20, 20, 0.72); }
.page-faq .accordion-panel a { color: #141414; }

/* Still have questions? -- the contact page's off-white band. */
.page-faq .section-alt {
  background: #f4f2ef;
  border-top: none;
  border-bottom: none;
}
.page-faq .section-alt h2 {
  color: #141414;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
.page-faq .section-alt p { color: rgba(20, 20, 20, 0.72); }
.page-faq .section-alt .eyebrow { color: #141414; }
/* Contact Us: the home page's About Us button (Learn More), value for value
   and at every width -- the red box, and the arrow parked past its right edge
   that walks in as the box opens on hover. Written out here because the home
   page's rules are scoped to .home-story-copy / .hero-actions. */
.page-faq .section-alt .story-cta {
  display: inline-flex;
  align-items: center;
  gap: 0;
  padding: 8px 22px 10px;
  border: none;
  border-radius: 0;
  box-shadow: none;
  overflow: hidden;
  background: #b02b25;
  color: #ffffff;
  font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
               'Segoe UI', Roboto, sans-serif;
  font-size: 1.125rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  line-height: 1.15;
  transform: none;
}
.page-faq .section-alt .story-cta:hover,
.page-faq .section-alt .story-cta:focus-visible {
  background: #b02b25;
  color: #ffffff;
  transform: none;
}
.page-faq .section-alt .story-cta::after,
.page-faq .section-alt .story-cta span::after { content: none; }
.page-faq .section-alt .story-cta span { margin-right: -0.06em; }
.page-faq .section-alt .story-cta > svg {
  display: block;
  width: 27px;
  height: 14px;
  margin-left: 16px;
  margin-right: -43px;
  transform: translateX(27px);
  flex-shrink: 0;
  transition: margin-right 0.3s cubic-bezier(0.33, 1, 0.68, 1),
              transform 0.28s cubic-bezier(0.33, 1, 0.68, 1);
}
.page-faq .section-alt .story-cta:hover > svg,
.page-faq .section-alt .story-cta:focus-visible > svg {
  margin-right: -6px;
  transform: translateX(0);
  transition-delay: 0s, 0.06s;
}
@media (prefers-reduced-motion: reduce) {
  .page-faq .section-alt .story-cta,
  .page-faq .section-alt .story-cta > svg { transition: none; }
}

/* ============================================================
   Privacy Policy, Terms of Service, Refund Policy: the light treatment
   ============================================================
   Like cart.html, the three carry .page-product beside .page-legal, so the
   white ground, the header and the Free Membership band come from the
   product page's own rules. This is the plain white page header the cart
   and FAQ pages use, and the policy text on white. */
.page-legal .page-header {
  background: var(--page-ground);
  border-bottom: none;
  padding: 56px 0 8px;
}
.page-legal .page-header .breadcrumb { color: rgba(20, 20, 20, 0.6); }
.page-legal .page-header h1 {
  color: #141414;
  font-size: 36px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
.page-legal .legal-updated { color: rgba(20, 20, 20, 0.6); }
.page-legal .legal-content h2 {
  color: #141414;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
.page-legal .legal-content p,
.page-legal .legal-content li { color: rgba(20, 20, 20, 0.72); }
.page-legal .legal-content a,
.page-legal .legal-content strong { color: #141414; }

/* ============================================================
   Affiliate Program: the light treatment
   ============================================================
   The contact page's values: photo header kept on its opaque ground, near-
   black type, the off-white band, square fields and the black Add to Cart
   box for Join. The fields are white here rather than #f4f2ef, because the
   form sits on the #f4f2ef band and a field the colour of its ground is not
   a field. */
.page-affiliate { background: var(--page-ground); }
.page-affiliate .page-header-contact {
  border-bottom: none;
  background-color: #141414;
}

.page-affiliate .split .eyebrow { color: #141414; }
.page-affiliate .split h2,
.page-affiliate .section-alt h2 {
  color: #141414;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
.page-affiliate .split p { color: rgba(20, 20, 20, 0.72); }
.page-affiliate .split-media {
  background: #f0f0f0;
  border: none;
  border-radius: 0;
}

.page-affiliate .section-alt {
  background: #f4f2ef;
  border-top: none;
  border-bottom: none;
}
.page-affiliate .form-field label { color: rgba(20, 20, 20, 0.6); }
.page-affiliate .form-field input,
.page-affiliate .form-field textarea,
.page-affiliate .form-field select {
  background: #ffffff;
  border-color: rgba(20, 20, 20, 0.14);
  border-radius: 0;
  color: #141414;
}
.page-affiliate .form-field input::placeholder { color: rgba(20, 20, 20, 0.55); }
.page-affiliate .form-field input:hover,
.page-affiliate .form-field textarea:hover,
.page-affiliate .form-field select:hover { border-color: rgba(20, 20, 20, 0.4); }
.page-affiliate .form-field input:focus,
.page-affiliate .form-field textarea:focus,
.page-affiliate .form-field select:focus { border-color: #141414; }
.page-affiliate #affiliateNote { color: rgba(20, 20, 20, 0.6); }
.page-affiliate #affiliateNote a { color: #141414; text-decoration: underline; }

.page-affiliate #affiliateForm .btn-tan {
  background: #141414;
  color: #ffffff;
  border: none;
  border-radius: 0;
  box-shadow: none;
  font-family: 'Barlow Condensed', -apple-system, BlinkMacSystemFont,
               'Segoe UI', Roboto, sans-serif;
  font-size: 1.25rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  line-height: 1.15;
  padding: 9px 24px 11px;
  height: calc(0.9375rem * 1.8 + 26px);
  transform: none;
  transition: background-color 0.22s ease;
}
.page-affiliate #affiliateForm .btn-tan:hover,
.page-affiliate #affiliateForm .btn-tan:focus-visible {
  background: #b02b25;
  color: #ffffff;
  transform: none;
}
.page-affiliate #affiliateForm .btn-tan::after { content: none; }
