/* =========================================================================
   PXP Peptides — brand.css
   Tokens, reset, base typography, the handful of utilities every page uses.

   Load order on every page is brand.css → site.css → shop.css. Nothing here
   knows about a header or a product card; everything below this file assumes
   the tokens in it exist. Change a value here and the whole site moves,
   which is the point — there are no raw hex codes anywhere else.

   THERE IS A DARK MODE, and this header used to say the opposite. The
   objection it recorded was a real one — an inversion would leave the navy
   logo sitting on navy — and what answers it is at the foot of this file: a
   hand-written dark palette rather than an inversion, in which the mark is
   knocked out to white by a filter rather than left to fend for itself.

   Every colour below is still set explicitly, and that reason has not
   changed: the browser's default in a dark UA stylesheet is not white.
   ========================================================================= */

/* -------------------------------------------------------------------------
   Tokens
   ------------------------------------------------------------------------- */

:root {
  /* The palette, read off the banner: the navy band, one blue, the chrome
     highlights and a lot of white. --chrome-500 is in here for hairlines and
     icon strokes only: it is under 4.5:1 on white, so it must never carry a
     sentence.

     ONE BLUE, DELIBERATELY — AND IT IS THE NAVY.

     This has now been collapsed twice. The first pass removed an electric
     #2E8BFF from the top of a four-step ramp and left --blue-700 (#0A3FB0)
     as the accent. That was still two blues: a navy on the headings, bands
     and logo, and a distinctly brighter blue on every button, link and
     border right beside it. Measured across four pages they were the two
     most-used colours on the site, 24 elements each.

     So the accent is gone too. There is one blue, --navy-800, and it does
     everything: type, buttons, links, borders, the bands, the mark. Depth
     comes from --navy-900 and --navy-700 either side of it, which are the
     same hue rather than a second one.

     The --blue-* names are kept as aliases so the ~30 rules that reference
     them did not all need rewriting, and so this is reversible.

     AND IT HAS BEEN REVERSED, on request, with one difference that is the
     whole point: what came back is not a second blue, it is an ACCENT. The
     navy is no longer the type colour — type is neutral grey now — so the
     two are not competing for the same job. Navy is the solid mass: buttons,
     the dark bands, the badge, the mark. Grey is every word. The accent is
     the mark on top of both: links, icons, ticks, focus, hover.

     The --blue-* aliases turned out to be a map of exactly where an accent
     belongs, because that is what they were before the collapse — the focus
     outline, the tick, the chevrons, the current-page bar, the eyebrow, the
     info rule. They are repointed at the accent rather than renamed, for the
     same reason as before: it keeps this reversible.

     Adding a new rule? Use --ink for words, --navy-800 for a solid, and
     --accent-ink for an accent that has to be read. */
  /* ---- The accent -----------------------------------------------------
     #02BBFD, supplied. It is an electric sky blue and it has one property
     that decides every rule below: IT CANNOT BE READ ON WHITE. Measured,
     2.20:1 — under the 4.5 a sentence needs and under even the 3.0 a border
     or an icon needs. White text on it is the same 2.20:1, so it cannot be a
     button fill with a white label either.

     It replaced a cyan, #02DEFE, which was 1.63:1 on white. Bluer and a
     little darker, so it is half a point better and still nowhere near
     enough: the structure below did not change, only the numbers in it.

     What it IS good at is the opposite arrangement. Against the dark page it
     is 8.5:1, and near-black text ON it is the same 8.5:1. So:

       --accent       the colour as supplied. Used where nothing depends on
                      reading it (a hover rim, the ambient wash), or as a
                      FILL with --on-accent text on top of it, or anywhere in
                      dark mode, where it is at its best.
       --accent-ink   the same hue taken down to 28% lightness. 6.2:1 on
                      white, 5.6:1 on --paper-2, 4.7:1 on the hero band. This
                      is what carries anything that must be read or seen:
                      links, icon strokes, the tick, chevrons, focus
                      outlines.

                      It was 30% and #017098, which is 4.26:1 on the hero
                      band — under the floor. That did not matter while the
                      primary button had a white fill under its label, and
                      started mattering the moment the fill came off and the
                      label sat on the band itself. Two points of lightness
                      fixes it everywhere at once and is invisible next to
                      the old value.
       --accent-deep  8.6:1. Hover and pressed states of the above.
       --accent-tint  the pale fill. Icon discs, hover grounds, ::selection.
       --on-accent    text that sits ON --accent. Near-black, and it does NOT
                      flip in dark mode — the accent is the same colour in
                      both themes, so what sits on it has to be too.

     In dark mode all of these collapse toward the supplied colour, because
     there it needs no protecting. */
  --accent: #02BBFD;
  --accent-ink: #01698E;
  --accent-deep: #01526F;
  --accent-tint: #E1F7FF;
  --on-accent: #0B1220;

  /* The primary button's hover fill, and the label that goes on it. It needs
     its own pair because it is the one place the two themes genuinely want
     different arrangements rather than the same arrangement in different
     values.

     In daylight the label is WHITE, on request, and white forces the fill:
     it is 2.2:1 on the accent as supplied and 5.6:1 on --accent-ink, so a
     white label and a #02BBFD fill cannot both be had.

     In the dark the constraint runs the other way. A deep blue button on a
     near-black page is barely a button, and the accent at full strength is
     the right fill there — which means the label has to be near-black, since
     white on it is the same 2.2:1 it is in daylight. So the dark block
     inverts this pair rather than restating it.

     Kept as tokens so site.css does not need a dark-mode selector for it. */
  --btn-fill: var(--accent-ink);
  --btn-fill-ink: #FFFFFF;

  --navy-900: #071634;
  --navy-800: #0B1F4B;
  --navy-700: #12305F;
  --navy-600: #1A3F7A;
  --blue-800: var(--accent-deep);   /* pressed / hover */
  --blue-700: var(--accent-ink);
  --blue-600: var(--accent-ink);
  --blue-500: var(--accent-ink);
  --blue-400: var(--accent-ink);
  /* The pale fill. Was #DCEAFF, then the neutral #E8ECF3 while the palette
     had no accent to be a tint OF. It is a tint of the accent again now,
     which is what makes an icon disc or a hover ground read as belonging to
     accent rather than as a grey box. --ink on it is 14.1:1 and
     --accent-ink 5.0:1, so both the label and the icon still clear. */
  --blue-100: var(--accent-tint);
  /* Neutral, and they were the last blue-greys in the palette. The type,
     the grounds, the hairlines and the wash all came off the blue axis over
     the last few revisions and these three did not, so a ghost button's
     border and the text on the dark band were quietly the only cool things
     left on the page. --chrome-500 is still for hairlines and icon strokes
     only: it is under 4.5:1 on white, so it must never carry a sentence. */
  --chrome-200: #E8E8EA;
  --chrome-300: #D2D2D6;
  --chrome-500: #8E8E94;
  /* Dark grey, on request, and it used to be the navy. Every word on the
     site is this: body copy, headings, nav, labels, prices, table cells.

     #232328 rather than a black. It is 15.6:1 on white against the navy's
     16.0, so the page keeps the weight it had — the change is that the blue
     has come out of the type, not that the type got lighter. Same axis as
     --ink-soft below (R and G equal, B a couple of points up), so the two
     read as one family at two strengths rather than as two greys.

     The navy has NOT gone. It is the solid mass now — the primary button,
     the dark bands, the skip link, the mark — and nothing sets it as a
     colour on words any more. Three jobs, three tokens: --ink for words,
     --navy-800 for solids, --accent for the mark on top. */
  --ink: #232328;
  /* Neutral grey. Was #46586F, a blue-grey — every paragraph of secondary
     text on the site was faintly blue. 7.4:1 on white, 7.0:1 on --paper-2. */
  --ink-soft: #56565C;
  /* The page and the things on it, and they are no longer the same colour.
     --page is the document's ground: a flat light grey since the wash came
     off. --paper is the colour of a surface sitting on it — cards, the
     header, the hero card when it had one — and stays white, which is what
     gives those surfaces an edge without a border drawing one.

     Anything that used --paper as "the background" and meant the page rather
     than a panel wants --page. There were none at the time of writing; the
     only rule that painted the document was `html` in site.css. */
  --page: #F1F1F3;
  /* ---- INK FOR SURFACES THAT DO NOT FOLLOW THE THEME --------------------

     Two surfaces on this site stay the same colour in both palettes. The navy
     band and the compliance panel are dark in daylight and darker still at
     night (--band-ground goes #0B1F4B -> #080808), and the chrome pill is light
     in both. Text on either of them must not flip, and this is the pair of
     tokens that says so.

     THEY ARE DEFINED HERE AND NOWHERE ELSE. The dark palettes below override
     the tokens they need to and inherit the rest, so leaving these out of them
     is what keeps them fixed. Adding them to a dark block would be the bug.

     WHAT WENT WRONG WITHOUT THEM. Every one of these used --paper, which is a
     SURFACE token and flips to #121212 at night. On the compliance panel that
     put #121212 headings on an #080808 ground — 1.07:1, which is not low
     contrast, it is invisible — on nine pages. And --chrome-200 does not flip,
     so the chrome pill kept its light ground and took the dark palette's
     --ink-soft on top of it, at 1.98:1.

     If you are about to write `color: var(--paper)`, check what it is sitting
     on first. If that thing is dark in both themes, you want --on-band. */
  /* The clip's own field, as the browser paints it, and NOT themed. The canvas
     fills itself with this before drawing a frame, so the bands the crop
     removed are the picture's own colour by construction rather than by this
     token and --hero-ground being kept equal by hand. In the dark palette the
     canvas inverts the lot, and 255 - 226 = 29, which is what --hero-ground is
     down there. Change one and change all three. */
  --clip-field: 226, 226, 226;

  --on-band: #FFFFFF;
  --on-chrome: #56565C;   /* == --ink-soft in daylight, so light is unchanged */

  --paper: #FFFFFF;

  /* The header's filled state: white glass. It was a neutral near-black for
     one revision — the note is kept because the machinery for it is still in
     site.css and takes one value to bring back.

     --head-alpha is what makes it glass rather than paint. At 1 the bar is a
     white rectangle and the page slides under it invisibly; at 0.75 you can
     see shapes moving through it and the blur turns them into light rather
     than content. Below about 0.6 the type on the bar starts competing with
     whatever is passing under it.

     Written as channels as well because the bar over the hero composites its
     ground at a fractional alpha that changes with scroll position, and it
     has to multiply this one. Keep --head-fill and --head-fill-rgb in step.

     --head-ink is what sits on it, and it is just the page's ink: a white bar
     wants the same type as a white page. It exists as its own name so the
     four things in the bar read one variable — if the fill ever goes dark
     again, this is the only line that has to change with it. */
  --head-fill: #FFFFFF;
  --head-fill-rgb: 255, 255, 255;
  --head-alpha: .75;
  --head-ink: var(--ink);

  /* How far the glass blurs what is behind it — 10px on a phone, 18 above
     960 (see site.css).

     It is a token because it is a PERFORMANCE value as much as a look. A
     backdrop-filter re-blurs whatever is passing beneath it on every frame of
     a scroll, and on the home page what is passing beneath it is a canvas
     being repainted with a new photograph. Two expensive things over the same
     pixels, sixty times a second, on the device with the least to spare.

     10 still reads as glass at this alpha; the shapes underneath are already
     soft. If the bar ever stops sitting over the clip, this can go back up. */
  --head-blur: 10px;
  /* The same white as --paper, as channels. rgba() cannot take a hex token,
     and the header over the hero needs its ground at a fractional alpha that
     changes with scroll position. Keep the two in step. */
  --paper-rgb: 255, 255, 255;
  /* Neutral, not blue-tinted. Was #F5F8FC — a ground with blue in it is a
     second blue by another name, and on a site that has just been collapsed
     to one it was the thing still making pages read cool. RGM's #F5F5F7. */
  --paper-2: #F5F5F7;
  /* A hairline, not a coloured border. Black at 13% sits on any ground and
     never competes with the brand; #DDE5F0 was a pale blue drawing boxes
     around everything. --line-soft is for dividers inside a panel. */
  --line: rgba(0, 0, 0, .13);
  --line-soft: rgba(0, 0, 0, .07);
  /* --hero-ground, and it is back after being deleted once. The history is
     worth keeping because it explains the shape of what is here now.

     It was --navy-800: the top band of every page was the one blue at full
     strength with a photograph blended into it. It went neutral charcoal to
     get the blue out. Then the panel inverted, dark wireframe on pale rather
     than pale on dark, and it became #EDEDF0. Then the hero stopped being a
     panel at all: the artwork blended against the PAGE, and the token was
     deleted because there was no ground left to name.

     It returns as a very light grey-blue, on request, because the artwork
     against the bare page read as clear rather than as sitting on anything.
     It was briefly a gradient that faded out before the hero ended, to avoid
     reinstating the horizontal seam that the old solid ground drew; it is
     flat again, also on request, and the seam is now wanted. The header is a
     band that ends. See .hero in site.css.

     It does not cost any contrast, which is not obvious and is worth
     writing down. `darken` keeps the per-channel minimum, so under a strand
     the strand still wins and the composite there is exactly what it was;
     the ground only replaces the artwork's white field, which was never the
     worst case. Measured, not assumed. */
  /* On the same hue axis as the page wash and the scrim, not neutral grey.

     The first value here was 234,238,245, which is a grey with a slight blue
     lean. Against the wash below the hero, measured at 245,249,254, it read
     as a different colour rather than a deeper one: grey band, blue page.
     The band is most exposed at the foot of the hero, where the veil's mask
     has faded out and the ground is showing raw, which is exactly where the
     hard edge puts it next to the wash.

     It is a flat neutral grey now, and the four values it has been are the
     whole method in one place:

       233, 240, 250   axis R-17 G-10   too blue. Read as a colour, not a
                                        ground: nearly double the wash's tint.
       238, 239, 241   axis R-3  G-2    too neutral — AT THE TIME. Read as a
                                        grey block against a blue page.
       237, 242, 247   axis R-10 G-5    the wash's own axis, while the wash
                                        still had one.
       230, 230, 230   no axis          neutral, matching the neutral wash.
       225, 225, 225   no axis          this. SAMPLED off the hero clip.

     Note what the second and fourth have in common and why only one of them
     was wrong. A near-neutral band failed when the page around it was blue:
     the fault was never the grey, it was the mismatch. The rule is the same
     every time — MATCH THE WASH'S AXIS, then set the lightness to taste — and
     the wash has no axis any more, so neither does this.

     Deeper as well: 237 to 230, on the same request that took the colour out.

     And then 230 to 225, which is a different kind of decision and the reason
     this value is now fixed rather than free. The home page's hero is a clip
     of orbs floating on a flat grey field, and that field measures 225,225,225
     — sampled, not eyeballed. The clip is bottom-anchored so its empty top is
     what gets cropped, and the band shows above it; the two only read as one
     surface if they are the same number. They are.

     So: this is the clip's ground. Change the clip and re-sample it (any
     corner pixel of assets/hero-orbs/1280/000.jpg will do). The about page's
     hero uses the same token and has no such constraint. */
  /* 226, THE CLIP'S FIELD AS THE BROWSER PAINTS IT — and that qualifier is the
     whole point. Read the JPEGs with an image library and the field measures
     222. Read the canvas the browser has drawn them onto and it measures
     226.05. The frames carry the video's colour profile and the browser
     transforms them into the canvas's space on the way in; the number in the
     file is not the number on the screen, and this token has to match the
     screen. It was 225 for a long time, which was one level out and never
     mattered.

     IT MATTERS NOW. The frames are cropped top and bottom (see
     data-frames-crop in index.html), the canvas deliberately does not draw the
     bands that were removed, and what shows through there is THIS. Get it
     wrong and the crop announces itself as a band edge straight across the
     hero. Measure it on the canvas, never on the file. */
  --hero-ground: 226, 226, 226;

  /* --hero-scrim survives because the veil over the photograph still needs a
     colour at four different alphas and rgba() cannot take a hex token. It
     lightens rather than darkens: the type over it is --ink, so the scrim's
     job is to lift the background away from the text.

     It is a PALE BLUE, not white, and that is the compromise in this hero.
     The veil has to be strong enough that a strand crossing the lede cannot
     take it under 4.5:1, and anything that strong also flattens the page's
     ambient wash — which is why the hero read as a white block while the
     section under it was tinted. White scrim and visible gradient cannot
     both be had. Tinting the scrim toward the wash means the hero carries
     the same TONE as its neighbours, even though it cannot carry the
     gradient's variation.

     This value is the darkest tint that still clears the floor, and the
     limit is set on a PHONE, not on a desk. At 375 the artwork is cropped to
     the densest part of the helix at 2x, so the strands behind the lede are
     at their heaviest exactly where the scrim is doing the most work.
     Measured there, at the mobile veil alphas:

       227,240,254   lede 4.43   FAILS
       236,245,255   lede 4.63
       242,248,255   lede 4.74   <- this
       255,255,255   lede 5.06   (white, no tint)

     Darken it and re-measure at 375. Desktop has room to spare and will not
     tell you anything. */
  /* Neutral, with the band — and now EXACTLY the band, which closes a seam
     that has been there since the hero inverted.

     It used to be a pale blue, tinted toward the wash so the hero carried the
     same tone as its neighbours; and it used to be five or six points lighter
     than the band, because its job was to lift a pale ground away from dark
     type sitting straight on the artwork. Both reasons have gone. The wash is
     grey, and the type moved onto an opaque card, so nothing in the hero
     needs lifting any more.

     What the gap was costing: at desktop widths the left of the band is 96%
     veiled, so it composited to the SCRIM's lightness, not the band's — while
     the .hero + .section transition below it starts at the BAND's. A six
     point step, right on the join, on every page. Setting the two equal makes
     that zero by construction rather than by tuning, which is the same fix
     the transition itself got.

     The veil now only mutes the artwork toward the ground it sits on. It does
     not change that ground's colour anywhere, at any alpha. */
  --hero-scrim: 225, 225, 225;
  /* There is still no --logo-blue token, and the reason is worth keeping now
     that there IS an accent, because the two are not the same argument.

     The supplied logo carries a vivid royal blue. It was sampled to #1B6BDC
     and used to fill the trust discs, then removed, because a component
     helping itself to a brighter colour is how a palette grows a second
     accent by accident. That is still true. The accent above arrived the
     other way round: it was chosen, named, given a readable form and a
     contrast budget, and applied deliberately.

     The logo may be brighter than the palette. It is a supplied file, not a
     surface this stylesheet paints. Nothing else gets the same latitude. */
  --good: #0F8A5F;
  --warn: #B4741A;
  --bad: #B3261E;

  /* WhatsApp's own green, and the one colour on this site that belongs to
     somebody else. It is here rather than in site.css because a hard-coded
     colour there cannot follow the theme — see the note at the top of the
     dark block.

     NOT #25D366 in daylight, which is the green everyone pictures. Against
     the white bar it measures 1.98:1, and an icon that carries meaning wants
     3:1 (WCAG 1.4.11). This is the darker green from WhatsApp's own logo
     gradient, at 3.1:1 — still unmistakably the WhatsApp green, and legible.
     The dark theme takes the bright one, where it measures 8.19:1. */
  --whatsapp: #1DA851;

  /* The dark bands — .section--navy and .ruo-panel — and they are tokens now
     rather than three navy stops written into site.css.

     They HAD to become tokens. The stops were --navy-900, --navy-800 and
     --navy-700, and in dark mode --navy-800 is near-white, because it is
     also the primary button's fill and a button has to invert. So the band
     was painting a gradient from near-black to near-white with pale grey
     text on it, on seven pages. One token cannot be both a dark ground and a
     light fill; this is the ground, and --navy-800 stays the fill. */
  --band-ground: #0B1F4B;
  --grad-band: linear-gradient(135deg, #071634 0%, #0B1F4B 48%, #12305F 100%);

  /* Derived. The two gradients are the banner's own: the brand sweep behind
     the dark band, and the chrome sheen on the mark. */
  --grad-brand: linear-gradient(135deg, #0A1C46 0%, #0B1F4B 55%, #16336B 100%);
  --grad-chrome: linear-gradient(180deg, #FFFFFF 0%, #C8D3E2 100%);
  /* The focus ring, and it is the accent at a HIGH alpha rather than the
     usual low one. A focus ring has to clear 3:1 against the ground it sits
     on, and the accent composited onto white at .30 lands at 1.29:1 — a
     ring you can see only if you already know it is there. --accent-ink at
     .85 composites to #2580A0, which is 4.5:1 and unmistakable. */
  --ring: 0 0 0 3px rgba(1, 105, 142, .85);

  /* Two shadows, and the first one is almost nothing on purpose.
     --shadow-1 is the resting elevation for cards and the scrolled header;
     stripped back to a single hairline so a page of cards reads as a set of
     panels on a ground rather than a tray of floating tiles. Depth is carried
     by the hairline border and the tinted ground instead.
     --shadow-2 is kept real, because the two things that use it — the basket
     drawer and the gate — genuinely sit above the page and have to look it. */
  --shadow-1: 0 1px 2px rgba(0, 0, 0, .04), 0 8px 28px rgba(0, 0, 0, .06);
  --shadow-2: 0 12px 44px rgba(14, 30, 48, .14), 0 2px 8px rgba(14, 30, 48, .06);

  /* ---- Glass ----------------------------------------------------------
     Ported from the RGM site, with one change that matters: RGM splits its
     refraction into cyan and violet, which is what makes its edges read as
     real glass. Two extra hues is exactly what this palette just spent two
     passes removing, so PXP's refraction is navy against white instead —
     less spectral, still a split, and it stays one colour.

     Glass only works if there is something behind it to bend. On a flat
     white page a blurred translucent panel is indistinguishable from a grey
     one, which is why --amb-* below exists and why body::before paints it. */
  --glass-bg: rgba(255, 255, 255, .50);
  --glass-blur: 22px;
  --glass-rim-hi: rgba(255, 255, 255, .95);   /* the lit edge */
  --glass-rim-lo: rgba(255, 255, 255, .20);   /* the shaded edge */
  --glass-sheen: rgba(255, 255, 255, .70);
  --glass-shadow: 0 12px 44px rgba(7, 22, 52, .11), 0 2px 8px rgba(7, 22, 52, .05);

  /* The split, either side of a pane. Navy one side, white the other. */
  --refract-a: rgba(11, 31, 75, .30);
  --refract-b: rgba(255, 255, 255, .55);
  --grain-opacity: .035;

  /* Glass on the dark hero: the pane is a lightening rather than a tint. */
  --glass-bg-dark: rgba(255, 255, 255, .12);

  /* The panel surface. One flat colour and the site's hairline, on cards,
     product cards, the trust discs and the ticked circles.

     This replaced a five-token glass recipe: a translucent fill, a
     backdrop-filter blur, a lit top edge, a shaded bottom edge and an inner
     bloom. It looked right in daylight and never quite did in dark, where a
     pane has to be darker than the page rather than lighter and the effect
     lives entirely in the edge. Flat is one value per theme and correct in
     both by construction.

     --paper in daylight, so a card is white on the wash. Dark uses --paper-2
     rather than --paper: a panel that is the same colour as the page behind
     it has no surface at all, and on a dark ground the hairline alone is not
     enough to say where it starts. */
  --surface: var(--paper);
  --glass-rim-dark: rgba(255, 255, 255, .45);

  /* The ambient wash — CURRENTLY UNUSED. The page is a flat --page grey; the
     rule that painted these is gone from site.css. They are kept, not
     deleted, for the same reason as the last time this happened: the wash has
     been switched off and back on once already, and these four lines are what
     makes that a two-minute change rather than a rediscovery. The rule to
     restore is quoted at `html` in site.css.

     Three radial gradients on body, sized to the whole document, placed at
     8%/4%, 94%/26% and 34%/72%. See that note for how it is laid down and the
     three ways of doing it that did not work.

     NEUTRAL GREY, and darker than it was. On request, and it is a reversal
     worth recording because the reasoning it replaces was a good argument
     for something the site did not want.

     The three lamps used to be a blue, an indigo and a cyan — the one place
     a colour other than the navy was allowed, on the grounds that atmosphere
     is not a brand colour and that a pane of glass can only bend light that
     is there. The glass has gone. What is left is a flat page, and on a flat
     page three coloured lamps are not atmosphere, they are a tint: every
     surface on the site was sitting on a faintly blue ground while the
     palette claimed to be neutral.

     So they are black at alpha over --paper, which composites to a genuinely
     neutral grey — R, G and B equal by construction, not by choosing a grey
     and hoping. Nothing to keep in step with a hue, and no way for the page
     to drift cool again.

     Darker, too: peaks of about 231, 236 and 240 against the old ~245-250.
     The wash used to be at the edge of visibility on a bright screen. These
     are still atmosphere and not bands — the radials are several screens
     wide — but the page now has a shape to it in ordinary light.

     --hero-ground below is derived from these. It is a flat band sitting on
     this wash, and the one thing that has been learnt about it twice is that
     it has to be on the SAME AXIS as the wash or the join between them reads
     as two materials. The wash has no axis now, so neither does the band. */
  --amb-1: rgba(0, 0, 0, .095);
  --amb-2: rgba(0, 0, 0, .075);
  --amb-3: rgba(0, 0, 0, .058);

  /* Type. One family doing both jobs.

     This was Montserrat for display and Inter for body. Be Vietnam Pro covers
     both: it is geometric enough to hold the banner's wide-tracked uppercase
     at 700/800, and its lowercase is even and open enough to read as body
     text at 400 — which is the pair of demands that usually forces two
     families. Using one costs a single stylesheet request instead of two and
     removes the mismatch you get when a display face and a body face
     disagree about x-height halfway down a card.

     The two tokens stay separate even though they now resolve to the same
     stack. They mean different things — "the face headings are set in" and
     "the face sentences are set in" — and keeping them apart is what lets
     that decision be revisited without a search-and-replace through every
     rule in three stylesheets.

     Both stacks name real fallbacks all the way down, because a page opened
     by double-clicking it with no network still has to look like this site
     and not like Times New Roman. The fallbacks are the system UI faces
     rather than Arial first: they are metrically closer to Be Vietnam Pro's
     proportions, so the reflow when the webfont arrives is smaller. */
  --font-display: "Be Vietnam Pro", -apple-system, BlinkMacSystemFont,
                  "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-body: "Be Vietnam Pro", -apple-system, BlinkMacSystemFont,
               "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

  /* Was clamp(2.1rem, 1.2rem + 4.2vw, 4.2rem) — 67px at desktop, which is
     larger than the biggest heading on the RGM site and read as shouting
     rather than confident. 52px is RGM's ceiling and the right one here. */
  /* 56px at the top end, 33.6 at the bottom, and it is THE HERO TITLE'S OWN
     SIZE — .hero__title reads this token now rather than carrying its own
     clamp, so a section heading and the headline above it cannot be different
     sizes by accident. They were 21.6 and 33.6 at 1280 until this.

     It has been 67px, then 52, then 28 when the headline moved onto a small
     card and was closer to a large h2 than to a display size, and now 56 on
     request — with the card gone again and the headline back on the band, a
     display size is what it is.

     h1 and h2 both use it. See the note below the heading rule for why they
     are deliberately the same, and note what does NOT use it: .card__title
     and .p-card__name are --t-h3 and smaller, .trust__title is --t-label.
     Those are labels and captions on components, not the title of a section,
     and none of them moved. */
  --t-hero: clamp(2.1rem, 1.35rem + 3.1vw, 3.5rem);
  --t-h1: clamp(1.8rem, 1.2rem + 2.6vw, 3rem);
  --t-h2: clamp(1.4rem, 1.1rem + 1.4vw, 2.1rem);
  --t-h3: clamp(1.12rem, 1rem + .5vw, 1.4rem);
  --t-body: clamp(1rem, .97rem + .15vw, 1.075rem);
  --t-small: .9rem;
  --t-label: .74rem;

  --wrap: 1200px;
  --wrap-narrow: 760px;
  --gutter: clamp(20px, 5vw, 48px);

  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;
  --sp-7: 48px;
  --sp-8: 64px;
  --sp-9: 96px;

  /* Tightened for the minimal pass: was 8 / 14 / 22. A 22px corner on a
     full-width panel reads as a soft app card; 12px reads as a document,
     which is what this site is closer to. --r-pill is untouched — a pill
     that is not fully round is just a rectangle with regrets. */
  --r-sm: 8px;
  --r-md: 12px;
  --r-lg: 18px;
  --r-pill: 999px;

  --section-y: clamp(56px, 8vw, 112px);

  /* The one curve the whole site eases on. Named rather than repeated so
     that "subtle" stays one decision instead of forty. */
  --ease: cubic-bezier(.2, .7, .3, 1);
}

/* -------------------------------------------------------------------------
   Dark mode

   TOKENS ONLY. Nothing below this block knows the theme: every rule in
   site.css and shop.css keeps using --ink, --paper, --line and the rest, and
   they resolve differently. If a dark-mode fix ever needs a selector in
   site.css, it is a sign a colour was hard-coded there instead of tokenised.

   ONE SELECTOR, AND ONLY ONE. `[data-theme="dark"]` is the toggle in the
   header, which writes to the root element and persists. There is deliberately
   no prefers-color-scheme rule beside it: this site does not follow the system
   setting, dark is opt-in, and js/theme.js explains why at length. There used
   to be a second copy of this whole palette behind such a query; it was
   removed rather than left unreachable, so that a failure to load theme.js
   cannot quietly resurrect the old behaviour.

   The palette is not the light one inverted. Pure white on near-black is
   harsh at body size, so --ink lands short of #FFF.

   IT IS KEYED TO THE HOME PAGE'S CLIP, which is what took the blue out of it.
   The grounds used to be a blue-black — #0B1220 and friends — chosen so the
   dark site kept the same temperature the light one had, back when the light
   one was cool. Then the wash went neutral grey, and then the hero became a
   clip of orbs on a flat field which dark mode inverts. That field is pinned
   at exactly 226 by the extractor, so inverted it is 29,29,29 — a neutral dark
   grey — and a blue-black page
   around it made the footage read as a warm rectangle pasted onto a cool
   page. Measured, not guessed: sample any frame's corner.

   So --hero-ground is 29,29,29 exactly, and everything else is built on that
   axis. The one thing that stays coloured is the accent, which is in the
   footage: the orbs carry real cyan in the glass, which is why the inversion
   is paired with a hue-rotate rather than left as a plain invert. See
   js/hero-frames.js for how that inversion is actually performed — not with a
   CSS filter, which loses 22 of the 256 levels.
   ------------------------------------------------------------------------- */

/* Light is the floor. js/theme.js always writes an explicit data-theme, so the
   override below is the only thing that can lift it — and a machine set to
   dark gets light form controls and scrollbars like everyone else. */
:root {
  color-scheme: light;
}

:root[data-theme="dark"] { color-scheme: dark; }

/* The palette itself. It used to be written twice — once here and once behind
   a prefers-color-scheme query — because CSS cannot share a declaration block
   between two selectors. With system-following gone there is one copy, which
   is one fewer thing to keep in step. */
:root[data-theme="dark"] {
  --page: #121212;
  --paper: #121212;

  /* Dark glass. Just above --paper, so the bar separates from the page rather
     than from the content on it. --head-ink is not redefined: it resolves to
     --ink, which this block has already flipped. */
  --head-fill: #202024;
  --head-fill-rgb: 32, 32, 36;
  --paper-rgb: 18, 18, 18;
  --paper-2: #1A1A1A;

  --ink: #ECECEE;
  --ink-soft: #A6A6AB;

  /* The canonical brand green, which only works on a dark bar: 8.19:1 here
     against 1.98:1 on white. See --whatsapp in the light palette. */
  --whatsapp: #25D366;

  /* Used as a FOREGROUND across the site — headings, nav links, the footer
     titles, the tick in the purity list — so in dark it has to be light. The
     name stops describing the value here, which is the cost of a palette that
     names its colours rather than its jobs. */
  --navy-800: #ECECEE;
  --navy-700: #CFCFD3;
  --navy-600: #A6A6AB;
  /* --navy-900 is a BACKGROUND — so it goes the other way and gets darker,
     not lighter. */
  --navy-900: #080808;

  /* The dark bands, deeper than the page rather than lighter than it. In
     daylight the band is the darkest thing on the page and that is how it
     signals "this is the compliance statement"; the same signal here means
     going below --paper, not above it. */
  --band-ground: #080808;
  --grad-band: linear-gradient(135deg, #050505 0%, #0E0E0E 48%, #171717 100%);

  /* The accent needs no protecting here. On this ground it is 8.5:1, so
     every alias collapses onto the colour as supplied and the site finally
     shows it at full strength. --accent-deep goes LIGHTER for the hover
     state, not darker, which is the one place this palette is not a mirror
     of the daylight one.

     --on-accent is deliberately NOT redefined. The accent is the same colour
     in both themes, so the text that sits on it has to be the same
     near-black in both. */
  --accent: #02BBFD;
  --accent-ink: #02BBFD;
  --accent-deep: #81DDFE;
  --accent-tint: #002938;

  /* Inverted, not restated. See the note in the light block: on a dark page
     the primary button wants the accent at full strength for its fill, which
     forces a near-black label. */
  --btn-fill: var(--accent);
  --btn-fill-ink: var(--on-accent);

  --blue-800: var(--accent-deep);
  --blue-700: var(--accent-ink);
  --blue-600: var(--accent-ink);
  --blue-500: var(--accent-ink);
  --blue-400: var(--accent-ink);
  --blue-100: var(--accent-tint);

  --line: rgba(255, 255, 255, .14);
  --line-soft: rgba(255, 255, 255, .07);

  /* The band behind the hero and the footer. */
  /* 29 = 255 - 226, the inverse of the field, because the dark palette inverts
     the canvas with a filter but paints this band directly. Dark mode is off
     (js/theme.js), so this has never actually been seen. */
  --hero-ground: 29, 29, 29;

  /* The veil over the hero artwork. In daylight it LIGHTENS, lifting a pale
     background away from dark type; here it does the opposite job with the
     same mechanism, so it takes the ground's colour and mutes the artwork
     down into it. The alphas in site.css are unchanged and do not need to be:
     they were tuned as "how much of the artwork survives", which is the same
     question in either theme. */
  --hero-scrim: 29, 29, 29;

  --ring: 0 0 0 3px rgba(2, 187, 253, .55);

  /* Shadows do almost nothing on a dark ground, so the glass leans on its rim
     instead of its shadow, and the rim gets brighter. */
  --shadow-1: 0 1px 2px rgba(0, 0, 0, .30), 0 8px 28px rgba(0, 0, 0, .35);
  --shadow-2: 0 12px 44px rgba(0, 0, 0, .48), 0 2px 8px rgba(0, 0, 0, .30);

  --glass-bg: rgba(255, 255, 255, .06);
  --glass-rim-hi: rgba(255, 255, 255, .28);
  --glass-rim-lo: rgba(255, 255, 255, .06);
  --glass-sheen: rgba(255, 255, 255, .12);
  --glass-shadow: 0 12px 44px rgba(0, 0, 0, .45), 0 2px 8px rgba(0, 0, 0, .30);

  /* Lighter than the page, not the same as it. A panel that matches its
     ground has no surface, and on a dark page a hairline alone does not carry
     the edge. */
  --surface: #202022;

  /* Neutral here too, and lightening rather than darkening: black at alpha
     over a near-black page does nothing at all, so the mirror of the daylight
     wash is white at alpha. Still colourless — white over any ground keeps
     that ground's hue and only raises it.

     Dimmer than daylight. At the light alphas these read as three lamps
     behind a dark page rather than as an ambient wash. */
  --amb-1: rgba(255, 255, 255, .055);
  --amb-2: rgba(255, 255, 255, .045);
  --amb-3: rgba(255, 255, 255, .032);

  --grad-chrome: linear-gradient(180deg, #2E2E2E 0%, #131313 100%);

  /* The status colours are lifted: the daylight versions are chosen for
     contrast against white and fail against a dark ground. */
  --good: #4ADE9B;
  --warn: #E3B341;
  --bad: #F1707B;
}

/* -------------------------------------------------------------------------
   Reset

   Modern rather than scorched-earth: box-sizing, margins off the things that
   get their own spacing, and sensible defaults for media and form controls.
   ------------------------------------------------------------------------- */

*,
*::before,
*::after { box-sizing: border-box; }

html {
  /* `clip`, not `hidden`. Both stop a stray wide child scrolling the page
     sideways at 320px, but `hidden` turns the root into a scroll container
     and quietly kills `position: sticky` on the header. */
  overflow-x: clip;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100%;
  font-family: var(--font-body);
  font-size: var(--t-body);
  font-weight: 400;
  line-height: 1.6;
  color: var(--ink);
  /* --page, not --paper. They were the same colour until the document's wash
     came off and the ground went grey; body painting --paper here covered the
     `html` rule in site.css completely, and the page stayed white while every
     token said otherwise. Two elements painting the same ground is one too
     many — this is the one that wins, so this is the one that has to be
     right. */
  background: var(--page);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Set while a modal or the basket drawer is open. `overflow: hidden` and not
   `position: fixed`: fixing the body zeroes the scroll offset, so closing the
   drawer would drop the reader back at the top of the catalogue they had
   scrolled halfway down. This way the page stays exactly where it was.

   The root has to be locked as well, and that is not belt and braces — on its
   own the body rule does nothing here. Overflow propagates from <body> up to
   the viewport only while the root's overflow is `visible` in both axes, and
   `overflow-x: clip` above means it is not. So the viewport takes the root's
   values, and `overflow: hidden` on an auto-height body clips a box that was
   never going to scroll anyway. The page carried on scrolling behind the
   gate and behind the basket until this rule existed.

   `:has()` rather than a second class, so gate.js and storefront.js keep
   setting exactly one thing. Where `:has()` is unsupported the lock simply
   does not engage — the modal still works, the page behind it still scrolls,
   which is the old behaviour and not a new fault. */
html:has(body.is-locked) { overflow: hidden; }
body.is-locked { overflow: hidden; }

h1, h2, h3, h4, h5, h6,
p, figure, blockquote, dl, dd { margin: 0; }

ul, ol { margin: 0; padding: 0; list-style: none; }

img, svg, video { display: block; max-width: 100%; }
img { height: auto; }

button, input, select, textarea {
  font: inherit;
  color: inherit;
  margin: 0;
}

button { background: none; border: 0; padding: 0; cursor: pointer; }

table { border-collapse: collapse; width: 100%; }

hr {
  border: 0;
  border-top: 1px solid var(--line);
  margin: var(--sp-6) 0;
}

/* Class rules would otherwise beat the attribute — the basket count in the
   header carries both `hidden` and a class that sets `display: inline-flex`,
   and without this it shows a nought before anything is in it. */
[hidden] { display: none !important; }

/* -------------------------------------------------------------------------
   Base typography
   ------------------------------------------------------------------------- */

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  /* 600, MATCHING THE HERO, on request — and it is a real change of house
     rule rather than a value, so the history is worth keeping.

     This went 700, then 500, then 400. The argument for coming down was that
     the hero's tagline was set light because a bold weight at that size reads
     as shouting, the rest of the site stayed heavy, and the two never
     matched; the fix was to make every heading one weight and let SIZE carry
     the hierarchy. The matching still holds — it is just that the hero went
     UP to 600 in the meantime, and the headings have followed it rather than
     the other way round. One weight across every heading, still.

     600 is loaded. The pages request `wght@400;500;600`, which is what makes
     this a real weight rather than one the browser fakes by smearing the 500.
     Ask for 700 and it will be synthesised, which at heading size looks like
     a rendering fault. Load the face first.

     There is no 400 ceiling any more. It said "nothing above 400 except the
     hero title and <strong>", and this is the line that broke it.

     Anything that sets its OWN weight is deliberately unaffected and stays
     light — .p-card__name, .trust__title, .point and the footer titles are
     labels rather than headings, whatever element they happen to use. */
  font-weight: 600;
  /* Tight, and the same -.03em the hero title uses, on request: the two were
     -.01 and -.03 and read as two different typefaces at a glance.

     Display type set at body leading reads as a paragraph; tight leading with
     negative tracking is what makes a heading look set rather than typed. The
     tracking had opened to -.01 when the weight came DOWN to 400, because
     lighter strokes need marginally more air than heavy ones — going back to
     600 is what makes -.03 sit right again. The two moved together and had
     to. */
  line-height: 1.1;
  letter-spacing: -.03em;
  color: var(--ink);
  text-wrap: balance;
}

/* h1 and h2 are the SAME SIZE, and that is intentional.

   A page title and a section title are both "the title of this thing"; the
   difference between them is structural, not visual, and the document
   outline carries it. --t-hero is the size the hero's tagline uses, so the
   headline a visitor lands on and every heading below it are set alike. */
h1 { font-size: var(--t-hero); }
h2 { font-size: var(--t-hero); }
/* h3 stays smaller — it is a sub-level inside a page's content, and it is
   the one place the outline still needs a visible step. */
h3 { font-size: var(--t-h3); }
h4 { font-size: 1.05rem; }

p { text-wrap: pretty; }

a { color: var(--blue-700); text-underline-offset: .18em; }
/* --blue-800, not --blue-600. The blue tokens all resolve to one value now,
   so a hover that moved from --blue-700 to --blue-600 changed nothing at all.
   Hover goes darker rather than lighter: there is no lighter blue left. */
a:hover { color: var(--blue-800); }

/* Nothing on this site is bold. Every weight above 500 was taken out on
   request, and 500 is now the ceiling: headings, labels, product names, the
   FAQ questions, <strong>, all of it.

   <strong> is the one that needed a second thought rather than a smaller
   number. It marks the sentences that matter most here, and several of them
   are compliance lines: "Research use only. Not for human or veterinary
   consumption." Dropping the weight alone would have left those looking
   exactly like the paragraph around them.

   So the emphasis moves off weight and onto colour. --ink at full strength
   against --ink-soft body copy is a real difference. The tag stays in the
   markup either way: it is what tells a screen reader the sentence is
   emphasised, and that has never depended on how it looks.

   THIS 500 IS THE ONE EXCEPTION on a site whose ceiling is otherwise 400, and
   it is deliberate rather than missed. Everything else came down a step; if
   this had come with it, <strong> would be identical to its surroundings
   anywhere the body copy is already --ink, which includes the compliance
   sentences on checkout.html and legal.html. 500 against 400 is not bold. It
   is the smallest mark the typeface can make, which is what emphasis in this
   design should be. */
strong, b {
  font-weight: 500;
  color: var(--ink);
}

small { font-size: var(--t-small); }

/* Formulas, sequences and CAS numbers are long unbroken strings. Left alone
   they are the single most likely thing to push a phone page sideways, so
   anything monospaced is told it may break anywhere. */
code, kbd, samp {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .92em;
  overflow-wrap: anywhere;
}

::selection {
  background: var(--blue-100);
  color: var(--ink);
}

/* -------------------------------------------------------------------------
   Utilities
   ------------------------------------------------------------------------- */

/* One visible focus style for the whole site, on everything, always. It is
   the last line of defence for a keyboard-only journey and it is cheaper to
   set once here than to remember it on forty components. */
:focus-visible {
  outline: 2px solid var(--blue-400);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

.wrap {
  width: 100%;
  max-width: calc(var(--wrap) + var(--gutter) * 2);
  margin-inline: auto;
  /* The gutter, unless the notch is wider than it. Every page declares
     `viewport-fit=cover`, which is what lets the fixed basket drawer reach
     the bottom of the screen — but cover also means that in landscape on a
     notched phone the sensor housing eats into the left or right edge, and
     until this rule the first character of every line went under it. `max()`
     leaves the gutter alone on the 99% of screens that have no inset. */
  padding-inline:
    max(var(--gutter), env(safe-area-inset-left))
    max(var(--gutter), env(safe-area-inset-right));
}

.wrap--narrow { max-width: calc(var(--wrap-narrow) + var(--gutter) * 2); }

/* The banner's signature: wide-tracked uppercase display type. Size is
   deliberately not set here — a tracked eyebrow, a tracked button and a
   tracked trust heading are three different sizes of the same treatment. */
.tracked {
  text-transform: uppercase;
  letter-spacing: .18em;
  font-family: var(--font-display);
  font-weight: 400;
}

/* First thing in the tab order on every page, and invisible until it has
   focus. Positioned rather than clipped so it lands over the header instead
   of shifting it. */
.skip {
  position: absolute;
  top: var(--sp-2);
  left: var(--sp-2);
  z-index: 200;
  padding: 12px 18px;
  border-radius: var(--r-sm);
  background: var(--navy-800);
  color: var(--paper);
  font-weight: 400;
  text-decoration: none;
  transform: translateY(-160%);
}

.skip:focus {
  transform: translateY(0);
  color: var(--paper);
}

/* Long-form text: legal.html, faq.html, the About page's body. Everything
   the reset stripped is put back, because inside prose a bulleted list has
   to look like a bulleted list. */
.prose {
  max-width: 68ch;
  color: var(--ink-soft);
}

.prose > * + * { margin-top: var(--sp-4); }

.prose h2 {
  margin-top: var(--sp-8);
  color: var(--ink);
  /* Anchored sections on legal.html land under the sticky header without
     this. The number is the header's height plus a little air; it is a
     literal rather than a token because it is a fact about one component. */
  scroll-margin-top: 96px;
}

.prose h3 {
  margin-top: var(--sp-6);
  color: var(--ink);
  scroll-margin-top: 96px;
}

.prose > h2:first-child,
.prose > h3:first-child { margin-top: 0; }

.prose ul,
.prose ol {
  padding-left: 1.25em;
  list-style: disc;
}

.prose ol { list-style: decimal; }

/* Lettered sub-clauses, terms.html clause 2.3. `type="a"` is a presentational
   hint and author CSS beats it, so the rule above would renumber (a) to (d) as
   1 to 4 without this. Those letters are how a warranty gets referred to in a
   contract, so they are content rather than decoration. */
.prose ol[type="a"] { list-style: lower-alpha; }
.prose li + li { margin-top: var(--sp-2); }
/* --ink-soft, not --chrome-500. A list marker is text — on legal.html the
   numbers are how a clause gets referred to out loud — and --chrome-500 is
   2.89:1 on white, which this file's own comment says must never carry a
   sentence. --ink-soft is 7.3:1 and still reads as quieter than the body. */
.prose li::marker { color: var(--ink-soft); }

.prose a { font-weight: 400; }

.prose strong { color: var(--ink); }

/* The scroll-reveal hook, driven by nav.js.

   The hidden state hangs off `[data-shown="false"]` and never off
   `[data-reveal]` alone. That is the whole safety property: `data-reveal` is
   written into the markup by the page, but `data-shown` is only ever written
   by nav.js, and nav.js writes it after it has checked that the reader has
   not asked for less motion and that IntersectionObserver exists. So a
   marked-up element whose script never ran, or ran and decided not to
   animate, has no rule hiding it — it is simply on the page.

   Hiding content on `[data-reveal]` and relying on script to reveal it is
   the version of this that ships a blank page to anyone the script fails
   for, including a crawler. */
[data-reveal] { will-change: auto; }

@media (prefers-reduced-motion: no-preference) {
  /* HIDING IS INSTANT. REVEALING IS THE TRANSITION.

     The transition used to sit on `[data-shown]` — either value — and that is
     the subtler half of why the hero appeared instead of fading. An element
     starts at opacity 1, because nothing hides it until the script says so.
     Setting "false" with a transition already attached does not hide it: it
     starts a 380ms fade DOWN from 1. The observer then sets "true" a moment
     later and the fade reverses. What that looks like is a brief dip, or on
     anything already on screen, nothing at all.

     On "true" only, the sequence is the one that was intended: "false" snaps
     to opacity 0 with no transition to run, and "true" animates 0 to 1.

     nav.js has to force a style recalculation between the two — see the note
     there. Both halves are needed. Without the recalc the browser coalesces
     them and there is no change to transition; without this the change it
     transitions is the wrong way round. */
  [data-reveal][data-shown="false"] {
    opacity: 0;
    transform: translateY(8px);
  }

  [data-reveal][data-shown="true"] {
    opacity: 1;
    transform: none;
    transition: opacity .38s var(--ease), transform .38s var(--ease);
  }
}

/* -------------------------------------------------------------------------
   Reduced motion

   Everything, everywhere, off. `!important` rather than careful ordering
   because this has to beat rules in site.css and shop.css that load after
   it, and a reader who has asked for stillness is not interested in our
   cascade. Durations are 0.01ms rather than 0 so that `transitionend`
   listeners still fire and nothing waits forever for a callback.
   ------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }

  html { scroll-behavior: auto !important; }

  /* Belt and braces: if anything ever ships a hidden-until-revealed state
     outside the no-preference query, it is visible here. */
  [data-reveal] {
    opacity: 1 !important;
    transform: none !important;
  }

  /* The hover movements, cancelled rather than merely made instant.

     The blanket rule above sets every transition to 0.01ms, which stops
     things ANIMATING but not from MOVING: an icon would still jump 2px and
     grow when pointed at, and an instant jump is arguably worse than a smooth
     one for a reader who asked for stillness. So the transforms are removed
     outright.

     The blue rim on a card is deliberately NOT cancelled here. It is a colour
     change, not a movement, and someone who has asked for less motion has not
     asked for less feedback about what they are pointing at. */
  .trust:hover .trust__icon,
  .card:hover .card__icon,
  .spec:hover .spec__icon,
  .step:hover .step__n,
  .btn--icon:hover svg,
  .p-card:hover {
    transform: none !important;
  }
}

/* -------------------------------------------------------------------------
   Print

   The legal page and the certificate-of-analysis material get printed, so
   printing has to produce a document rather than a screenshot of a website.
   Component-level print rules (hiding the nav, the basket, the gate) sit at
   the foot of site.css and shop.css beside the things they hide.
   ------------------------------------------------------------------------- */

@media print {
  html, body {
    background: #fff !important;
    color: #000 !important;
    font-size: 11pt;
  }

  h1, h2, h3, h4, h5, h6, .prose, p, li { color: #000 !important; }

  .skip { display: none !important; }

  a { color: #000 !important; text-decoration: underline; }

  /* Where a link goes is lost on paper. Only absolute and mail links are
     spelled out — printing "(faq.html)" after every internal link is noise,
     not information. */
  a[href^="http"]::after,
  a[href^="mailto:"]::after {
    content: " (" attr(href) ")";
    font-size: 9pt;
    word-break: break-all;
  }

  h2, h3 { break-after: avoid; }
  p, li, blockquote { orphans: 3; widows: 3; }
}
