/* ============================================================
   pro.css - Design System + Motion Layer
   Defender System
   ------------------------------------------------------------
   Loaded AFTER tailwind.css. Purely additive: it never removes
   or renames Tailwind utilities, it only refines them.
   All spacing/positioning uses LOGICAL properties so the
   ============================================================ */

/* ------------------------------------------------------------
   1. TOKENS
   ------------------------------------------------------------ */
:root {
    /* Brand ramp - this block is the ONLY place the brand reds are defined.
       The -rgb channel twins exist so tailwind.config.js can emit
       `rgb(var(--ds-red-N-rgb) / <alpha-value>)` and therefore generate
       bg-primary / text-primary / hover:border-primary/30 from these exact
       values. Previously the ramp was duplicated as hex in tailwind.config.js
       and the two copies had already drifted apart - red-100 and red-600 were
       different colours depending on which file you asked. Do not add hex reds
       to tailwind.config.js; add a channel line here and point the config at
       it. */
    --ds-red-50:  #fdf0f0;
    --ds-red-100: #fbdcdc;
    --ds-red-200: #f5c2c0;
    --ds-red-300: #e88b88;
    --ds-red-500: #C11B17;
    --ds-red-600: #a41612;
    --ds-red-700: #8a1310;
    --ds-red-800: #5a0a08;

    --ds-red-50-rgb:  253 240 240;
    --ds-red-100-rgb: 251 220 220;
    --ds-red-200-rgb: 245 194 192;
    --ds-red-300-rgb: 232 139 136;
    --ds-red-500-rgb: 193 27 23;
    --ds-red-600-rgb: 164 22 18;
    --ds-red-700-rgb: 138 19 16;
    --ds-red-800-rgb: 90 10 8;

    /* Ink / neutrals (cool charcoal - softer than pure black) */
    --ds-ink-900: #14161a;
    --ds-ink-800: #1e2126;
    --ds-ink-700: #2a2e35;
    --ds-ink-600: #3d434c;
    --ds-ink-500: #5a616c;
    --ds-ink-400: #7b8290;
    --ds-ink-300: #a6adb9;
    --ds-ink-200: #d3d7de;
    --ds-ink-100: #e9ebef;
    --ds-ink-50:  #f6f7f9;

    /* Surfaces */
    --ds-surface:      #ffffff;
    --ds-surface-sunk: var(--ds-ink-50);
    --ds-border:       #e6e8ec;
    --ds-border-strong:#d3d7de;

    /* Semantic */
    --ds-success: #12805a;
    --ds-success-soft: #eaf7f1;
    --ds-warn:    #b45309;
    --ds-warn-soft:#fdf4e3;
    --ds-info:    #0e6f8e;
    --ds-info-soft:#e8f4f8;

    /* Typography */
    --ds-font-sans: "Inter", "Cairo", "Segoe UI", system-ui, -apple-system, "Helvetica Neue", sans-serif;
    --ds-font-mono: "JetBrains Mono", "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;

    --ds-fs-xs:   0.75rem;
    --ds-fs-sm:   0.875rem;
    --ds-fs-base: 1rem;
    --ds-fs-md:   1.0625rem;
    --ds-fs-lg:   clamp(1.125rem, 0.4vw + 1.05rem, 1.3rem);
    --ds-fs-xl:   clamp(1.35rem, 0.9vw + 1.1rem, 1.75rem);
    --ds-fs-2xl:  clamp(1.7rem, 1.6vw + 1.2rem, 2.5rem);
    --ds-fs-3xl:  clamp(2.1rem, 2.8vw + 1.3rem, 3.75rem);

    /* Radii */
    --ds-r-sm: 8px;
    --ds-r-md: 14px;
    --ds-r-lg: 20px;
    --ds-r-xl: 28px;
    --ds-r-pill: 999px;

    /* Elevation (layered, low-spread) */
    --ds-e-1: 0 1px 2px rgba(20,22,26,.05), 0 1px 1px rgba(20,22,26,.04);
    --ds-e-2: 0 2px 4px rgba(20,22,26,.04), 0 4px 12px rgba(20,22,26,.06);
    --ds-e-3: 0 4px 8px rgba(20,22,26,.04), 0 12px 28px rgba(20,22,26,.08);
    --ds-e-4: 0 8px 16px rgba(20,22,26,.05), 0 24px 56px rgba(20,22,26,.12);
    --ds-e-red: 0 4px 12px rgba(193,27,23,.18), 0 12px 32px rgba(193,27,23,.14);

    /* Motion */
    --ds-ease-out:    cubic-bezier(.22, 1, .36, 1);
    --ds-ease-in-out: cubic-bezier(.65, 0, .35, 1);
    --ds-ease-spring: cubic-bezier(.34, 1.56, .64, 1);
    --ds-dur-fast: 160ms;
    --ds-dur:      320ms;
    --ds-dur-slow: 620ms;
    --ds-stagger:  80ms;

    /* Layout rhythm */
    --ds-section-y: clamp(3.5rem, 6vw, 7rem);
    --ds-gutter:    clamp(1.25rem, 4vw, 2.5rem);
    --ds-maxw:      1200px;

    /* Notch / home-indicator insets. They resolve to 0px on every
       non-notched screen, so the safe-area rules in section 29 are
       no-ops there and only the header/footer/floating controls grow
       on a device with a cutout (viewport-fit=cover is declared in
       includes/header.php). */
    --ds-safe-t: env(safe-area-inset-top, 0px);
    --ds-safe-r: env(safe-area-inset-right, 0px);
    --ds-safe-b: env(safe-area-inset-bottom, 0px);
    --ds-safe-l: env(safe-area-inset-left, 0px);
}

/* ------------------------------------------------------------
   2. BASE
   ------------------------------------------------------------ */
html {
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%; /* stop mobile browsers inflating text over our rem scale */
    scroll-padding-block-start: 6rem; /* keeps anchors clear of the sticky header */
}

body {
    font-family: var(--ds-font-sans);
    color: var(--ds-ink-700);
    font-size: var(--ds-fs-base);
    line-height: 1.7;
    font-feature-settings: "kern" 1, "liga" 1, "calt" 1;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
    overflow-x: hidden;
}

/* Consistent, brand-tinted focus ring everywhere */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
    outline: 2px solid var(--ds-red-500);
    outline-offset: 3px;
    border-radius: 4px;
}

::selection {
    background: var(--ds-red-500);
    color: #fff;
}

/* ------------------------------------------------------------
   3. TYPOGRAPHY
   ------------------------------------------------------------ */
/* Wrapped in :where() -> zero specificity, so ANY Tailwind text-colour
   utility (text-white, text-primary, ...) still wins. A bare element
   selector here would beat the utility classes and render hero
   headings dark-on-dark. */
:where(h1, h2, h3, h4, h5, h6) {
    color: var(--ds-ink-900);
    line-height: 1.22;
    letter-spacing: -0.018em;
    font-weight: 700;
    text-wrap: balance;
}

:where(h1) { font-size: var(--ds-fs-3xl); letter-spacing: -0.03em; }
:where(h2) { font-size: var(--ds-fs-2xl); }
:where(h3) { font-size: var(--ds-fs-xl); }
:where(h4) { font-size: var(--ds-fs-lg); }

  p { text-wrap: pretty; }

  /* .ds-gradient-text, .ds-gradient-text--red and @keyframes ds-shimmer were
     removed here. All three had zero references in markup and in JS - they were
     reachable only by hand-editing a template - and both were infinite colour
     animations: a white->red-200->white sweep on one, red-500->red-600->red-500
     on the other, running 8s and 9s with no stop condition. Constantly shifting
     text colour is the opposite of the flat, legible brand this pass is
     consolidating, and dead code carrying an infinite animation is worse than
     dead code. If a shimmering effect is ever wanted, reintroduce it as a
     utility that only a deliberate data attribute can switch on. */


/* Eyebrow / kicker above section titles */
.ds-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-size: var(--ds-fs-xs);
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--ds-red-500);
    margin-block-end: 0.75rem;
}
.ds-eyebrow::before {
    content: "";
    inline-size: 28px;
    block-size: 2px;
    background: currentColor;
    border-radius: 2px;
    flex: none;
}

/* ------------------------------------------------------------
   4. LAYOUT RHYTHM
   ------------------------------------------------------------ */
.ds-section { padding-block: var(--ds-section-y); }
.ds-section--tight { padding-block: calc(var(--ds-section-y) * 0.62); }
.ds-section--sunk { background: var(--ds-surface-sunk); }

.ds-divider {
    block-size: 1px;
    background: linear-gradient(to right, transparent, var(--ds-border) 18%, var(--ds-border) 82%, transparent);
    border: 0;
    margin-block: 0;
}

/* Inner-page banner.
   Every inner page opens with the same short strip - breadcrumb and page
   title, nothing else - over the site's own building photograph under a
   charcoal -> brand-red wash, dark enough that white copy never sits on a
   busy part of the image. The fill lives here rather than in .bg-primary on
   the markup, so the ten banners that share this block cannot drift apart.
   The photograph is referenced from this stylesheet because the relative path
   resolves against this file on every deployment, while a URL written in a
   template would have to know the base path at build time.
   Lighting still comes from ::after below - white and black vignettes, so
   it adds depth without introducing another hue. */
.page-header {
    position: relative;
    overflow: hidden;
    background-color: var(--ds-ink-900);
    background-image:
        linear-gradient(100deg, rgba(20,22,26,.95) 0%, rgba(20,22,26,.86) 46%, rgba(90,10,8,.80) 100%),
        url("../images/101.jpg");
    background-size: cover;
    background-position: center 42%;
    background-repeat: no-repeat;
}
.page-header::after {
    content: "";
    position: absolute;
    inset-block: 0;
    inset-inline-end: -10%;
    inline-size: 45%;
    background:
        radial-gradient(60% 70% at 60% 40%, rgba(255,255,255,.16), transparent 65%),
        radial-gradient(50% 60% at 20% 80%, rgba(0,0,0,.22), transparent 70%);
    pointer-events: none;
}
.page-header > * { position: relative; z-index: 1; }
/* Fluid page title: one scale for all ten banners instead of the fixed
   1.8rem / 1.4rem pair the templates carried. Bigger and consistent on
   desktop, still comfortable on a phone. Wins over the utility classes
   (higher specificity) without editing every template. */
.page-header h1 { color: #fff; font-size: clamp(1.5rem, 4vw, 2.25rem); line-height: 1.15; letter-spacing: -0.02em; }
.page-header .breadcrumb a { text-decoration: none; transition: opacity var(--ds-dur-fast) linear; }
.page-header .breadcrumb a:hover { opacity: .7; text-decoration: underline; text-underline-offset: 3px; }

/* Banner layout: crumb on top, page title directly under it.
   The wrapper used to be `flex justify-between items-center`, which put the
   h1 on the left and the breadcrumb on the right of ONE row - two competing
   headings fighting for the same banner. Stacking them and lifting the crumb
   with `order` keeps the standard reading order (where you are, then what this
   page is) without editing the ten templates that share this markup.
   Tailwind utilities are unlayered here and pro.css is linked last, so this
   wins on source order at equal specificity. */
.page-header-content {
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-start;
    row-gap: .5rem;
}
.page-header-content .breadcrumb { order: -1; }
/* Below 992px the block is centred; keep it that way. */
@media (max-width: 992px) {
    .page-header-content { align-items: center; }
}

/* Section title + subtitle pairing used across templates */
.section-title { letter-spacing: -0.028em; }
.section-subtitle { color: var(--ds-ink-500); max-inline-size: 62ch; margin-inline: auto; }

/* ------------------------------------------------------------
   5. BUTTONS
   ------------------------------------------------------------ */
.ds-btn {
    --_bg: var(--ds-red-500);
    --_fg: #fff;
    --_shadow: var(--ds-e-red);
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.55rem;
    padding: 0.8rem 1.6rem;
    border: 0;
    border-radius: var(--ds-r-pill);
    background: var(--_bg);
    color: var(--_fg);
    font-family: inherit;
    font-size: var(--ds-fs-sm);
    font-weight: 600;
    line-height: 1.2;
    letter-spacing: 0.01em;
    text-decoration: none;
    cursor: pointer;
    overflow: hidden;
    isolation: isolate;
    box-shadow: var(--_shadow);
    transition:
        transform   var(--ds-dur) var(--ds-ease-spring),
        box-shadow var(--ds-dur) var(--ds-ease-out),
        background-color var(--ds-dur-fast) linear,
        opacity     var(--ds-dur-fast) linear;
    -webkit-tap-highlight-color: transparent;
    will-change: transform;
}

/* Sheen sweep on hover */
.ds-btn::after {
    content: "";
    position: absolute;
    inset-block: 0;
    inset-inline-start: -120%;
    inline-size: 60%;
    background: linear-gradient(100deg, transparent, rgba(255,255,255,.34), transparent);
    transform: skewX(-18deg);
    transition: inset-inline-start var(--ds-dur-slow) var(--ds-ease-out);
    z-index: -1;
}
.ds-btn:hover::after,
.ds-btn:focus-visible::after { inset-inline-start: 130%; }

.ds-btn:hover { transform: translateY(-2px); box-shadow: 0 6px 16px rgba(193,27,23,.24), 0 18px 44px rgba(193,27,23,.18); }
.ds-btn:active { transform: translateY(0) scale(.975); box-shadow: var(--ds-e-1); }
.ds-btn:focus-visible { outline-offset: 4px; }

.ds-btn--ghost {
    --_bg: transparent;
    --_fg: var(--ds-ink-900);
    --_shadow: none;
    box-shadow: inset 0 0 0 1.5px var(--ds-border-strong);
}
.ds-btn--ghost:hover { --_fg: var(--ds-red-500); box-shadow: inset 0 0 0 1.5px var(--ds-red-300); }

.ds-btn--dark { --_bg: var(--ds-ink-900); --_shadow: 0 4px 12px rgba(20,22,26,.22), 0 14px 34px rgba(20,22,26,.16); }
.ds-btn--dark:hover { --_bg: var(--ds-ink-800); }

.ds-btn--wa { --_bg: #1fa855; --_shadow: 0 4px 12px rgba(31,168,85,.24), 0 14px 34px rgba(31,168,85,.16); }
.ds-btn--wa:hover { --_bg: #189247; }

.ds-btn--sm { padding: 0.55rem 1.1rem; font-size: var(--ds-fs-xs); }
.ds-btn--lg { padding: 0.95rem 2rem; font-size: var(--ds-fs-base); }
.ds-btn--block { display: flex; inline-size: 100%; }

.ds-btn[disabled], .ds-btn[aria-disabled="true"] {
    opacity: .5;
    pointer-events: none;
    box-shadow: none;
}

/* Text link with animated underline */
.ds-link {
    position: relative;
    color: var(--ds-red-500);
    text-decoration: none;
    font-weight: 600;
    padding-block-end: 2px;
}
.ds-link::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    inset-block-end: 0;
    block-size: 2px;
    background: currentColor;
    border-radius: 2px;
    transform: scaleX(0);
    transform-origin: var(--ds-underline-origin, left);
    transition: transform var(--ds-dur) var(--ds-ease-out);
}
.ds-link:hover::after, .ds-link:focus-visible::after { transform: scaleX(1); }

/* ------------------------------------------------------------
   6. CARDS
   ------------------------------------------------------------ */
.ds-card {
    position: relative;
    background: var(--ds-surface);
    border: 1px solid var(--ds-border);
    border-radius: var(--ds-r-lg);
    padding: clamp(1.25rem, 2.2vw, 1.9rem);
    box-shadow: var(--ds-e-1);
    overflow: hidden;
    transition:
        transform   var(--ds-dur) var(--ds-ease-out),
        box-shadow var(--ds-dur) var(--ds-ease-out),
        border-color var(--ds-dur) var(--ds-ease-out);
    will-change: transform;
}
.ds-card::before {
    content: "";
    position: absolute;
    inset-block-start: 0;
    inset-inline: 0;
    block-size: 3px;
    /* Flat brand red. This was linear-gradient(90deg, red-500, red-300): a
       3px rule that faded to a pale pink at its right end, so on hover the card
       showed two different reds meeting in the middle and the rule read as two
       segments rather than one. Depth here comes from the wipe itself, not from
       a second red. */
    background: var(--ds-red-500);
    transform: scaleX(0);
    transform-origin: var(--ds-underline-origin, left);
    transition: transform var(--ds-dur-slow) var(--ds-ease-out);
}

.ds-card:hover {
    transform: translateY(-6px);
    box-shadow: var(--ds-e-3);
    border-color: var(--ds-red-100);
}
.ds-card:hover::before { transform: scaleX(1); }

/* Icon tile inside cards */
.ds-icon-tile {
    display: inline-grid;
    place-items: center;
    inline-size: 52px;
    block-size: 52px;
    border-radius: var(--ds-r-md);
    background: var(--ds-red-50);
    color: var(--ds-red-500);
    font-size: 1.35rem;
    flex: none;
    transition: transform var(--ds-dur) var(--ds-ease-spring), background-color var(--ds-dur) var(--ds-ease-out);
}
  .ds-card:hover .ds-icon-tile { transform: scale(1.08) rotate(-4deg); background: var(--ds-red-500); color: #fff; }

  /* Whole-card link.
     The catalogue cards used to carry a "View details ->" link pinned to the
     bottom with mt-auto. It was the only visible affordance on the card, and it
     made the click target the size of one line of text in a 546px-tall card -
     visitors were clicking the picture and the chips and nothing happened.

     This is an overlay link instead: an empty <a> is the last child of the
     card and covers the whole surface. The alternative - putting the link on
     the product name and throwing a transparent ::after across the card, the
     usual stretched-link pattern - was tried first and does NOT work here: the
     card carries .ds-tilt, and Chrome would not hit-test that pseudo over the
     <img> no matter the z-index, overflow or transform, so the picture stayed
     dead while the text area worked. A real last-child element hit-tests fine.

     Only ONE link exists per card, so the a11y tree is unchanged - no repeated
     product names for screen readers. Its accessible name comes from aria-label
     and is byte-identical to the visible heading, satisfying WCAG 2.5.3. */
  .ds-card-overlay {
      position: absolute;
      inset: 0;
      z-index: 2;
      }
  /* The hit area is invisible, so focus needs its own visible cue. Ring the
     card rather than nothing: the focused thing is the whole card.
     :focus is the baseline and :focus-not-:focus-visible suppresses it again on
     mouse clicks, so the ring appears for keyboard users even where
     :focus-visible is unsupported, and never lingers after a click. */
  .ds-card:has(.ds-card-overlay:focus) {
      outline: 2px solid var(--ds-red-500);
      outline-offset: 2px;
      }
  .ds-card:has(.ds-card-overlay:focus:not(:focus-visible)) {
      outline: none;
      }


/* ------------------------------------------------------------
   7. FORMS
   ------------------------------------------------------------ */
.ds-field { display: flex; flex-direction: column; gap: 0.4rem; }

.ds-label {
    font-size: var(--ds-fs-sm);
    font-weight: 600;
    color: var(--ds-ink-800);
    letter-spacing: -0.005em;
}

.ds-input, .ds-textarea, .ds-select {
    inline-size: 100%;
    font-family: inherit;
    font-size: var(--ds-fs-base);
    color: var(--ds-ink-900);
    background: var(--ds-surface);
    border: 1.5px solid var(--ds-border-strong);
    border-radius: var(--ds-r-md);
    padding: 0.8rem 1rem;
    line-height: 1.5;
    transition:
        border-color var(--ds-dur-fast) var(--ds-ease-out),
        box-shadow     var(--ds-dur) var(--ds-ease-out),
        background-color var(--ds-dur-fast) linear;
    -webkit-appearance: none;
    appearance: none;
}
.ds-textarea { min-block-size: 120px; resize: vertical; }

.ds-select {
    padding-inline-end: 2.75rem;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath fill='%235a616c' d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 1.1rem center;
    background-size: 12px;
    cursor: pointer;
}

.ds-input::placeholder, .ds-textarea::placeholder { color: var(--ds-ink-300); }

.ds-input:hover, .ds-textarea:hover, .ds-select:hover { border-color: var(--ds-ink-300); }

.ds-input:focus, .ds-textarea:focus, .ds-select:focus {
    outline: none;
    border-color: var(--ds-red-500);
    box-shadow: 0 0 0 4px rgba(193,27,23,.11);
}

.ds-input[aria-invalid="true"], .ds-textarea[aria-invalid="true"] {
    border-color: #dc2626;
    box-shadow: 0 0 0 4px rgba(220,38,38,.1);
}

.ds-hint { font-size: var(--ds-fs-xs); color: var(--ds-ink-400); }
.ds-error { font-size: var(--ds-fs-xs); color: #b91c1c; font-weight: 600; }

/* ------------------------------------------------------------
   7b. GLOBAL FORM-CONTROL REFINEMENT
   Every legacy Tailwind input on the site (contact, support,
   visit, distributor, maintenance, downloads, calculators)
   picks up the new look without touching the templates.
   Unlayered + attribute selectors so these beat Tailwind's
   single-class utilities (which are also unlayered).
   ------------------------------------------------------------ */
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]),
textarea,
select {
    border-color: var(--ds-border-strong) !important;
    border-width: 1.5px;
    border-style: solid;
    border-radius: var(--ds-r-md);
    background-color: var(--ds-surface);
    color: var(--ds-ink-900);
    transition:
        border-color var(--ds-dur-fast) var(--ds-ease-out),
        box-shadow     var(--ds-dur) var(--ds-ease-out);
}
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):hover,
textarea:hover,
select:hover { border-color: var(--ds-ink-300) !important; }

input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):focus,
textarea:focus,
select:focus {
    border-color: var(--ds-red-500) !important;
    box-shadow: 0 0 0 4px rgba(193,27,23,.11) !important;
    outline: none !important;
}
input::placeholder, textarea::placeholder { color: var(--ds-ink-300); }

/* Server- and client-side validation state, for the legacy Tailwind inputs
   that the rule above styles globally.
   Both that rule and the focus rule use !important, so an inline
   style.borderColor from JavaScript loses against them and the field never
   looks wrong. These selectors are more specific and come later, so they win
   without having to shout louder. aria-invalid rather than a class, because a
   screen reader announces it too. */
input[aria-invalid="true"]:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]),
textarea[aria-invalid="true"],
select[aria-invalid="true"] {
    border-color: #dc2626 !important;
    box-shadow: 0 0 0 4px rgba(220,38,38,.1) !important;
}

/* Checkboxes / radios get an accent-coloured brand tick */
input[type="checkbox"], input[type="radio"] {
    accent-color: var(--ds-red-500);
    inline-size: 1.05rem;
    block-size: 1.05rem;
    cursor: pointer;
}

input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
input[type="number"] { -moz-appearance: textfield; appearance: textfield; }

select {
    padding-inline-end: 2.5rem;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath fill='%235a616c' d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 1rem center;
    background-size: 11px;
    -webkit-appearance: none;
    appearance: none;
    cursor: pointer;
}

/* Range sliders */
input[type="range"] {
    -webkit-appearance: none;
    appearance: none;
    background: transparent;
    block-size: 6px;
    border-radius: var(--ds-r-pill);
    cursor: pointer;
}
input[type="range"]::-webkit-slider-runnable-track {
    block-size: 6px;
    border-radius: var(--ds-r-pill);
    background: var(--ds-ink-100);
}
input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    inline-size: 20px;
    block-size: 20px;
    margin-block-start: -7px;
    border-radius: 50%;
    background: var(--ds-red-500);
    border: 3px solid #fff;
    box-shadow: var(--ds-e-2);
    transition: transform var(--ds-dur-fast) var(--ds-ease-spring);
}
input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.18); }
input[type="range"]::-moz-range-track { block-size: 6px; border-radius: var(--ds-r-pill); background: var(--ds-ink-100); }
input[type="range"]::-moz-range-thumb {
    inline-size: 16px; block-size: 16px; border-radius: 50%;
    background: var(--ds-red-500); border: 3px solid #fff; box-shadow: var(--ds-e-2);
}

/* Labels: a touch more weight and breathing room */
form label { line-height: 1.4; }

/* Submit buttons: unified look site-wide */
form button[type="submit"],
form .ds-btn {
    transition:
        transform   var(--ds-dur) var(--ds-ease-spring),
        box-shadow  var(--ds-dur) var(--ds-ease-out),
        background-color var(--ds-dur-fast) linear,
        opacity var(--ds-dur-fast) linear;
}
form button[type="submit"]:hover { transform: translateY(-2px); }
form button[type="submit"]:active { transform: translateY(0) scale(.98); }
form button[type="submit"].is-loading { opacity: .72; pointer-events: none; }

/* Spinner state for the submitting button.
   font-size:0 collapses the label without needing colour tricks,
   so the label text stays intact for screen readers. */
form button[type="submit"].is-loading,
.ds-btn.is-loading {
    position: relative;
    pointer-events: none;
    font-size: 0 !important;
    --ds-spinner: #fff;
}
form button[type="submit"].is-loading::after,
.ds-btn.is-loading::after {
    content: "";
    position: absolute;
    inset-block-start: 50%;
    inset-inline-start: 50%;
    inline-size: 1.15rem;
    block-size: 1.15rem;
    margin-block-start: -.575rem;
    margin-inline-start: -.575rem;
    border: 2px solid var(--ds-spinner);
    border-block-start-color: transparent;
    border-inline-end-color: transparent;
    border-radius: 50%;
    animation: ds-spin .7s linear infinite;
}
/* Light submit buttons need a dark spinner */
form button[type="submit"].is-loading.ds-btn--light,
.ds-btn--light.is-loading { --ds-spinner: var(--ds-red-600); }
@keyframes ds-spin { to { transform: rotate(360deg); } }

/* ------------------------------------------------------------
   8. BADGES / PILLS
   ------------------------------------------------------------ */
.ds-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.3rem 0.75rem;
    border-radius: var(--ds-r-pill);
    background: var(--ds-red-50);
    color: var(--ds-red-600);
    font-size: var(--ds-fs-xs);
    font-weight: 700;
    letter-spacing: 0.04em;
    white-space: nowrap;
}
.ds-badge--ok   { background: var(--ds-success-soft); color: var(--ds-success); }
.ds-badge--warn { background: var(--ds-warn-soft);    color: var(--ds-warn); }
.ds-badge--info { background: var(--ds-info-soft);    color: var(--ds-info); }
.ds-badge--solid{ background: var(--ds-red-500); color: #fff; }

.ds-dot {
    inline-size: 7px;
    block-size: 7px;
    border-radius: 50%;
    background: currentColor;
    flex: none;
}
.ds-dot--pulse { animation: ds-pulse 2.2s var(--ds-ease-in-out) infinite; }
@keyframes ds-pulse {
    0%, 100% { opacity: 1;   transform: scale(1); }
    50%      { opacity: .45; transform: scale(.8); }
}

/* ------------------------------------------------------------
   9. SCROLL REVEAL  (the core motion system)
   ------------------------------------------------------------
   [data-reveal] starts hidden, .is-revealed plays the animation.
   Variants: up (default) | fade | down | start | end | scale | clip
   Delay via inline --reveal-delay or data-reveal-delay.
   ------------------------------------------------------------ */
[data-reveal] {
    opacity: 0;
    will-change: opacity, transform;
    transition:
        opacity   var(--ds-dur-slow) var(--ds-ease-out),
        transform var(--ds-dur-slow) var(--ds-ease-out),
        clip-path var(--ds-dur-slow) var(--ds-ease-out);
    transition-delay: var(--reveal-delay, 0ms);
}
[data-reveal="up"]    { transform: translate3d(0, 34px, 0); }
[data-reveal="down"]  { transform: translate3d(0, -34px, 0); }
[data-reveal="start"] { transform: translate3d(-38px, 0, 0); }
[data-reveal="end"]   { transform: translate3d(38px, 0, 0); }
[data-reveal="fade"]  { transform: none; }
[data-reveal="scale"] { transform: scale(.94); }
[data-reveal="clip"]  { clip-path: inset(0 0 100% 0); transform: translate3d(0, 20px, 0); }

[data-reveal].is-revealed {
    opacity: 1;
    transform: none;
    clip-path: inset(0 0 0 0);
}

/* Once finished, drop the compositing hints */
[data-reveal].is-settled {
    will-change: auto;
}

/* Text blocks: reveal line-by-line feel without splitting text */
[data-reveal="lines"] > * {
    opacity: 0;
    transform: translate3d(0, 18px, 0);
    transition: opacity var(--ds-dur-slow) var(--ds-ease-out), transform var(--ds-dur-slow) var(--ds-ease-out);
}
[data-reveal="lines"].is-revealed > * { opacity: 1; transform: none; }
[data-reveal="lines"].is-revealed > *:nth-child(1) { transition-delay: 0ms; }
[data-reveal="lines"].is-revealed > *:nth-child(2) { transition-delay: 90ms; }
[data-reveal="lines"].is-revealed > *:nth-child(3) { transition-delay: 180ms; }
[data-reveal="lines"].is-revealed > *:nth-child(4) { transition-delay: 270ms; }
[data-reveal="lines"].is-revealed > *:nth-child(5) { transition-delay: 360ms; }

/* ------------------------------------------------------------
   10. SCROLL PROGRESS + BACK TO TOP
   ------------------------------------------------------------ */
.ds-scroll-progress {
    position: fixed;
    inset-block-start: 0;
    inset-inline: 0;
    block-size: 3px;
    z-index: 9998;
    /* One flat red, deliberately. This was
       linear-gradient(90deg, red-500, #f0a04a, red-500) on a 200% background
       sliding on a 4s loop, which put an orange band in the middle of the bar
       and kept dragging it across: the bar read as two differently-coloured
       halves rather than as a single progress indicator. Progress is already
       communicated by scaleX below, so the colour has no work left to do. */
    background: var(--ds-red-500);
    transform: scaleX(var(--ds-progress, 0));
    transform-origin: left center;
    transition: transform 90ms linear;
    pointer-events: none;
}

.ds-to-top {
    position: fixed;
    inset-block-end: 1.5rem;
    inset-inline-end: 1.5rem;
    inline-size: 48px;
    block-size: 48px;
    z-index: 999;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 50%;
    background: var(--ds-ink-900);
    color: #fff;
    font-size: 1.1rem;
    cursor: pointer;
    box-shadow: var(--ds-e-3);
    opacity: 0;
    visibility: hidden;
    transform: translateY(14px) scale(.9);
    transition:
        opacity   var(--ds-dur) var(--ds-ease-out),
        transform var(--ds-dur) var(--ds-ease-spring),
        visibility var(--ds-dur) step-end,
        background-color var(--ds-dur-fast) linear;
}
.ds-to-top.is-visible {
    opacity: 1;
    visibility: visible;
    transform: none;
    transition:
        opacity   var(--ds-dur) var(--ds-ease-out),
        transform var(--ds-dur) var(--ds-ease-spring),
        visibility 0ms;
}
.ds-to-top:hover { background: var(--ds-red-500); transform: translateY(-3px) scale(1.06); }
.ds-to-top:active { transform: scale(.94); }

/* Ring that fills as you scroll back up */
.ds-to-top::before {
    content: "";
    position: absolute;
    inset: -5px;
    border-radius: 50%;
    background: conic-gradient(var(--ds-red-500) calc(var(--ds-progress, 0) * 360deg), transparent 0);
    z-index: -1;
    opacity: .5;
}

/* ------------------------------------------------------------
   11. HEADER STATE
   ------------------------------------------------------------ */
.ds-header {
    transition: background-color var(--ds-dur) var(--ds-ease-out),
                box-shadow      var(--ds-dur) var(--ds-ease-out),
                backdrop-filter var(--ds-dur) var(--ds-ease-out);
}
.ds-header.is-scrolled {
    background-color: rgba(255,255,255,.88);
    -webkit-backdrop-filter: blur(14px) saturate(180%);
    backdrop-filter: blur(14px) saturate(180%);
    box-shadow: 0 1px 0 rgba(20,22,26,.06), 0 8px 28px rgba(20,22,26,.08);
}

/* Scroll-spy: nav link for the section you're currently in */
.ds-navlink {
    position: relative;
}
.ds-navlink::after {
    content: "";
    position: absolute;
    inset-inline: 18px;
    inset-block-end: 0;
    block-size: 3px;
    background: var(--ds-red-500);
    border-radius: 3px 3px 0 0;
    transform: scaleX(0);
    transform-origin: var(--ds-underline-origin, center);
    transition: transform var(--ds-dur) var(--ds-ease-out);
}
.ds-navlink:hover::after { transform: scaleX(1); }
.ds-navlink.is-current { color: var(--ds-red-500) !important; }
.ds-navlink.is-current::after { transform: scaleX(1) !important; }

/* FAQ / details disclosure */
details > summary {
    list-style: none;
    position: relative;
    padding-inline-end: 2rem;
    transition: color var(--ds-dur-fast) var(--ds-ease-out);
}
details > summary::-webkit-details-marker { display: none; }
details > summary::after {
    content: "";
    position: absolute;
    inset-inline-end: 0.35rem;
    inset-block-start: 50%;
    inline-size: 0.6rem;
    block-size: 0.6rem;
    margin-block-start: -0.35rem;
    border-inline-end: 2px solid currentColor;
    border-block-end: 2px solid currentColor;
    transform: rotate(45deg);
    transition: transform var(--ds-dur) var(--ds-ease-out);
}
details[open] > summary::after { transform: rotate(-135deg); margin-block-start: -0.1rem; }
details[open] > summary { color: var(--ds-red-500); }
details[open] > summary + * { animation: ds-details-open var(--ds-dur) var(--ds-ease-out) both; }
@keyframes ds-details-open {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: none; }
}

/* ------------------------------------------------------------
   12. MICRO-INTERACTIONS
   ------------------------------------------------------------ */
/* Icon nudge on parent hover */
.ds-nudge .icon, .ds-nudge svg { transition: transform var(--ds-dur) var(--ds-ease-spring); }
.ds-nudge:hover .icon, .ds-nudge:hover svg { transform: translateX(3px); }

/* Floating / bobbing accent */
.ds-float { animation: ds-float 6s var(--ds-ease-in-out) infinite; }
@keyframes ds-float {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-10px); }
}

/* Soft breathing glow for hero orbs / status LEDs */
.ds-glow { animation: ds-glow 3.4s var(--ds-ease-in-out) infinite; }
@keyframes ds-glow {
    0%, 100% { box-shadow: 0 0 0 0 rgba(193,27,23,.34); }
    70%      { box-shadow: 0 0 0 14px rgba(193,27,23,0); }
}

/* Tilt target (set by motion.js) */
.ds-tilt { transform: perspective(900px) rotateX(0deg) rotateY(0deg); transition: transform 220ms var(--ds-ease-out); }

/* Magnetic target (set by motion.js) */
.ds-magnetic { transition: transform 420ms var(--ds-ease-out); }

/* Marquee for logos / partner strip */
.ds-marquee {
    display: flex;
    gap: 3rem;
    overflow: hidden;
    mask-image: linear-gradient(to right, transparent, #000 8%, #000 92%, transparent);
    -webkit-mask-image: linear-gradient(to right, transparent, #000 8%, #000 92%, transparent);
}
.ds-marquee__track {
    display: flex;
    gap: 3rem;
    align-items: center;
    flex: none;
    padding-inline-end: 3rem;
    animation: ds-marquee 32s linear infinite;
}
.ds-marquee:hover .ds-marquee__track { animation-play-state: paused; }
@keyframes ds-marquee {
    from { transform: translate3d(0,0,0); }
    to   { transform: translate3d(-100%,0,0); }
}


/* Image zoom inside a media frame */
.ds-media { overflow: hidden; border-radius: var(--ds-r-lg); }
.ds-media img {
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
    transition: transform 900ms var(--ds-ease-out);
    will-change: transform;
}
.ds-media:hover img { transform: scale(1.07); }

/* Parallax host */
[data-parallax] { will-change: translate; }

/* ------------------------------------------------------------
   13. SKELETON / STATES
   ------------------------------------------------------------ */
.ds-skeleton {
    background: linear-gradient(90deg, var(--ds-ink-100) 25%, var(--ds-ink-50) 37%, var(--ds-ink-100) 63%);
    background-size: 400% 100%;
    animation: ds-skeleton 1.4s ease infinite;
    border-radius: var(--ds-r-sm);
}
@keyframes ds-skeleton {
    from { background-position: 100% 50%; }
    to   { background-position: 0 50%; }
}

/* ------------------------------------------------------------
   14. SCROLL PROGRESS RING (nav "you are here")
   ------------------------------------------------------------ */
.ds-section-index {
    font-family: var(--ds-font-mono);
    font-size: var(--ds-fs-xs);
    color: var(--ds-ink-300);
    letter-spacing: 0.1em;
}

/* ------------------------------------------------------------
   15. PRINT
   ------------------------------------------------------------ */
@media print {
    .ds-scroll-progress, .ds-to-top, .ds-header {
        display: none !important;
    }
    [data-reveal] { opacity: 1 !important; transform: none !important; clip-path: none !important; }
    body { color: #000; background: #fff; }
}

/* ------------------------------------------------------------
   16. REDUCED MOTION - kill everything gracefully
   ------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
        scroll-behavior: auto !important;
    }
    [data-reveal] {
        opacity: 1 !important;
        transform: none !important;
        clip-path: none !important;
    }
    [data-reveal="lines"] > * { opacity: 1 !important; transform: none !important; }
    .ds-scroll-progress { display: none; }
    .ds-float, .ds-glow, .ds-marquee__track { animation: none !important; }
    html { scroll-behavior: auto !important; }
}

/* Keep floating controls clear of each other and the mobile action bar. */
.ds-to-top { inset-block-end: 5.5rem; inset-inline-end: 1rem; }
[data-track="whatsapp_floating"] { inset-inline-end: 1rem; }
@media (max-width: 767px) {
    .ds-to-top { inset-block-end: 10rem; }
    [data-track="whatsapp_floating"] { inset-block-end: 5rem; }
}

/* ------------------------------------------------------------
   17. HOME HERO - scene 01 of the five
   ------------------------------------------------------------
   Full-bleed banner in two beats: the photo is there on the first
   frame, the panel's three children land one after another, and the
   five icons then fade up across the bottom. Everything is tuned
   from the variables below:

     --hero-bg              path to the background image
     --hero-veil            the wash laid over the photo
     --hero-icon-delay      when the icon row starts
     --hero-icon-stagger    gap between two icons
     --hero-icon-duration   how long one icon takes

   The panel no longer animates as one block: its title, text and
   button carry their own delays in the rules below (.2s / .38s /
   .56s) so the eye reads down the panel, and the icon row follows.
   Each uses `backwards` rather than `both` fill, which is what
   holds the start state across the delay while still letting
   .hero__cta keep its own :hover transform afterwards.
   ------------------------------------------------------------ */
.hero {
    /* Repoint the background by editing this one line. */
    --hero-bg: url("../images/101.jpg");
    /* Charcoal at the top so the transparent header and the white panel
       edge hold, opening up through the middle and deepening into brand
       red at the bottom, where the white icon row sits. Same family of
       veil as the other four scenes, but tuned to this photo's darker
       left-hand side rather than copied from it. */
    --hero-veil: linear-gradient(180deg,
        rgba(20, 22, 26, .82) 0%,
        rgba(20, 22, 26, .52) 38%,
        rgba(74, 10, 8,  .66) 100%);

    --hero-font: "Oswald", "Arial Narrow", system-ui, sans-serif;
    --hero-ink: #111;
    --hero-panel-bg: rgba(255, 255, 255, .93);
    --hero-panel-max: 1150px;
    --hero-panel-pad: 60px;
    --hero-panel-width: 60%;
    --hero-accent: #1fa34a;
    --hero-accent-hover: #178a3d;
    --hero-space-title: 24px;
    --hero-space-cta: 30px;

    /* Icon row reveal: starts after the panel has finished writing
       itself (.2s title / .38s text / .56s button + .45s duration), so
       the row is the last thing to land. */
    --hero-icon-delay: .78s;
    --hero-icon-stagger: .08s;
    --hero-icon-duration: .45s;
    --hero-icon-size: 80px;
    --hero-icon-title: 22px;

    position: relative;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: space-between;
    gap: clamp(2.5rem, 6vw, 4.5rem);
    min-height: 100vh;
    padding-block-end: 60px;

    background-color: var(--ds-ink-900); /* shows while the photo loads */
    background-image: var(--hero-veil), var(--hero-bg);
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}
/* Hero photograph: AVIF/WebP where supported. The image-set is applied here,
   not inside --hero-bg: a browser that cannot parse image-set() would treat
   the substituted var() as invalid at computed-value time and drop the whole
   background rather than fall back, so the plain var(--hero-bg) JPEG above
   stays the fallback. */
@supports (background-image: image-set(url("../images/101.webp") type("image/webp"))) {
    .hero {
        background-image: var(--hero-veil),
            image-set(
                url("../images/101.avif") type("image/avif"),
                url("../images/101.webp") type("image/webp"),
                url("../images/101.jpg")  type("image/jpeg"));
    }
}

/* The panel: top-left, square corners, no shadow. The panel itself does
   not animate - its three children do, one after another (see below). */
.hero__panel {
    width: min(var(--hero-panel-width), var(--hero-panel-max));
    max-width: 100%;
    padding: var(--hero-panel-pad);
    background: var(--hero-panel-bg);
    /* The heading is sized from this box (see 17b) rather than the viewport. */
    container-type: inline-size;
}

/* Panel children, in reading order: heading, then the strapline, then
   the button. `backwards` holds each start state across its delay and
   then releases, so nothing here keeps a transform that a later :hover
   (on .hero__cta) would have to fight. */
.hero__title,
.hero__text,
.hero__cta {
    animation: ds-hero-reveal .7s ease-out backwards;
}
.hero__title { animation-delay: .2s; }
.hero__text  { animation-delay: .38s; }
.hero__cta   { animation-delay: .56s; }

.hero__title {
    font-family: var(--hero-font);
    font-size: clamp(40px, 6vw, 84px);
    font-weight: 400;
    line-height: 1;
    letter-spacing: .005em;
    text-transform: uppercase;
    color: var(--hero-ink);
    text-wrap: balance;
}

.hero__text {
    max-width: 760px;
    margin-block-start: var(--hero-space-title);
    font-family: var(--hero-font);
    font-size: 24px;
    font-weight: 300;
    line-height: 1.2;
    text-transform: uppercase;
    color: var(--hero-ink);
}

.hero__cta {
    display: inline-block;
    margin-block-start: var(--hero-space-cta);
    padding: 14px 30px;
    border-radius: 4px;
    background: var(--hero-accent);
    color: #fff;
    font-family: var(--hero-font);
    font-size: 18px;
    font-weight: 500;
    line-height: 1.2;
    text-decoration: none;
    transition: background-color var(--ds-dur-fast) ease, transform var(--ds-dur-fast) ease;
}
.hero__cta:hover,
.hero__cta:focus-visible {
    background: var(--hero-accent-hover);
    transform: translateY(-2px);
}

/* --- the five-item icon row --------------------------------- */
.hero-icons {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 24px;
    align-items: start;
    width: 100%;
    padding-inline: var(--ds-gutter);
}

.hero-icons__item {
    display: grid;
    justify-items: center;
    gap: 14px;
    color: #fff;
}

.hero-icons__icon {
    display: block;
    inline-size: var(--hero-icon-size);
    block-size: var(--hero-icon-size);
    color: #fff;
    transition: transform var(--ds-dur) var(--ds-ease-out);
}
.hero-icons__icon svg {
    display: block;
    inline-size: 100%;
    block-size: 100%;
    stroke: #fff;
    transition: filter var(--ds-dur) ease;
}
.hero-icons__item:hover .hero-icons__icon { transform: scale(1.08); }
.hero-icons__item:hover .hero-icons__icon svg {
    filter: drop-shadow(0 0 10px rgba(255, 255, 255, .55));
}

.hero-icons__title {
    font-family: var(--hero-font);
    font-size: var(--hero-icon-title);
    font-weight: 400;
    line-height: 1.4;
    letter-spacing: .02em;
    text-transform: uppercase;
    text-align: center;
    text-wrap: balance;
    color: #fff;
}

/* Left-to-right order drives the stagger delay; the row is a
   grid, so the index has to be stated rather than read. */
.hero-icons__item:nth-child(1) { --hero-i: 0; }
.hero-icons__item:nth-child(2) { --hero-i: 1; }
.hero-icons__item:nth-child(3) { --hero-i: 2; }
.hero-icons__item:nth-child(4) { --hero-i: 3; }
.hero-icons__item:nth-child(5) { --hero-i: 4; }

@keyframes ds-hero-reveal {
    from { opacity: 0; transform: translateY(30px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* Icons first, left to right; the panel follows. */
.hero-icons__item {
    animation: ds-hero-reveal var(--hero-icon-duration) ease-out
        calc(var(--hero-icon-delay) + (var(--hero-i, 0) * var(--hero-icon-stagger)))
        both;
}

/* Reduced motion: the row, the panel's children and the hint are simply
   there. The base states carry no opacity, so dropping the animations is
   all it takes. */
@media (prefers-reduced-motion: reduce) {
    .hero-icons__item,
    .hero__title,
    .hero__text,
    .hero__cta { animation: none; }
    .hero-icons__item:hover .hero-icons__icon { transform: none; }
    .hero__cta:hover,
    .hero__cta:focus-visible { transform: none; }
}

/* Tablet: three columns, the last two centred on the second row. */
@media (min-width: 601px) and (max-width: 992px) {
    .hero { --hero-panel-width: 85%; --hero-panel-pad: 44px; }
    .hero-icons { grid-template-columns: repeat(3, 1fr); }
    .hero-icons__item:nth-child(4) { grid-column: 1; }
    .hero-icons__item:nth-child(5) { grid-column: 3; }
}

/* Mobile: full-bleed panel, two columns. */
@media (max-width: 600px) {
    .hero {
        --hero-panel-width: 100%;
        --hero-panel-pad: 30px 20px;
        --hero-icon-size: 60px;
        --hero-icon-title: 16px;
        gap: 2.5rem;
        padding-block-end: 40px;
    }
    .hero__text { font-size: 16px; }
    .hero-icons { grid-template-columns: repeat(2, 1fr); gap: 28px 12px; }
    /* Five items in two columns leaves the last one alone; centre it. */
    .hero-icons__item:nth-child(5) { grid-column: 1 / -1; }
}

/* Desktop never shrinks below this, however short the window. */
@media (min-width: 993px) {
    .hero { min-height: max(700px, 100vh); }
}

/* Short desktop windows: trim the panel padding and the icon row so the
   whole of scene 01 still lands inside one screen at 768px tall, where
   the full-size values would push the icon row past the fold. */
@media (min-width: 993px) and (max-height: 840px) {
    .hero {
        --hero-panel-pad: 40px;
        --hero-icon-size: 64px;
        --hero-icon-title: 18px;
        gap: 2rem;
    }
}

/* ------------------------------------------------------------
   17b. HERO HEADING SCALE - sized from the panel, not the page
   ------------------------------------------------------------
   The longest authored line, "INTELLIGENT PROTECTION.", measures
   about 9.5em in Oswald. A viewport-relative size cannot keep it
   on one line: the panel is only 60% wide and its padding takes a
   fixed 120px, so 6vw overflows the box on anything under about
   1500px and the heading silently becomes three lines.

   Container query units solve it - 9.5cqw is 9.5% of the panel's
   own content width, which leaves the line about 5% of headroom
   at every viewport while still capping at 84px. Declared after
   every media query on purpose: it is the last word on the size.

   Browsers without container query units keep the 6vw fallback
   declared on .hero__title above.
   ------------------------------------------------------------ */
@supports (font-size: 1cqw) {
    .hero__title { font-size: clamp(34px, 9.5cqw, 84px); }
    @media (max-width: 600px) {
        .hero__title { font-size: clamp(34px, 8.5cqw, 44px); }
    }
}

/* ------------------------------------------------------------
   18. HEADER - three-zone industrial nav
   ------------------------------------------------------------
   LEFT   logo
   CENTER nav (Home / About / Products / Services / References /
          News / Contact)
   RIGHT  language selector + mobile hamburger

   Sticky. The scrolled state only adds a shadow and a subtle
   translucency - the bar keeps a CONSTANT height, so activating
   the sticky state can never shift the content underneath it.
   ------------------------------------------------------------ */
.site-header {
    /* The bar stays in normal flow (position: sticky), so its height is driven
       by its own content and can never overlap the page below it.
       --_hdr-h is the DESKTOP height and the links' own padding is what
       produces it: 22px + ~23px line + 22px + 1px hairline = 68px. It is kept
       equal to that on purpose, so the floor never fights the content.
       The old value also added padding-block .75rem here on top of the links'
       padding, which stacked two vertical paddings and made the bar ~97px.
       On mobile the links are compact, so the padding moves to .header-inner
       there (see the max-width block) and --_hdr-h drops to 59px.
       motion.js still measures the real height into --ds-header-h. */
    --_hdr-h: 68px;
    min-block-size: var(--_hdr-h);
    /* the hairline is always there; the shadow only appears on scroll */
    box-shadow: 0 1px 0 rgba(20, 22, 26, .05);
    transition: box-shadow var(--ds-dur) var(--ds-ease-out);
}
.site-header .header-inner {
    min-block-size: calc(var(--_hdr-h) - 1px);
    padding-block: 0;
}
.site-header.is-scrolled { box-shadow: 0 1px 0 rgba(20,22,26,.07), 0 6px 22px rgba(20,22,26,.08); }
.site-header.is-scrolled { background-color: rgba(255,255,255,.94); }
@supports (backdrop-filter: blur(1px)) {
    .site-header.is-scrolled {
        -webkit-backdrop-filter: blur(14px) saturate(180%);
        backdrop-filter: blur(14px) saturate(180%);
    }
}

/* --- LEFT: logo --------------------------------------------- */
.site-header .site-branding { flex: none; }
.site-header .site-branding > a { display: block; }
/* Original proportions preserved: the intrinsic 614x135 ratio drives the
   rendered size, and the width/height attributes above match it exactly so
   there is no layout jump before the bitmap loads. */
.site-header .site-branding img {
    block-size: 40px;
    inline-size: auto;
    max-inline-size: 100%;
}
@media (max-width: 480px) {
    .site-header .site-branding img { block-size: 34px; }
}
/* Very narrow phones (~320px): shrink the logo and the row gutters so the
   logo, status pill and menu button stay on one line instead of the pill
   and button wrapping onto a second row and doubling the bar's height. */
@media (max-width: 380px) {
    .site-header .header-inner { gap: .5rem; padding-inline: .85rem; }
    .site-header .site-branding img { block-size: 30px; }
    .site-header .header-actions { gap: .35rem; }
}

/* --- CENTER: nav links -------------------------------------- */
@media (min-width: 993px) {
    .site-header .main-navigation { gap: 0; margin-inline: auto; }
    /* Comfortable gutters: with the right zone reduced to the language
       selector alone, all 7 items + CTA fit in the 1200px container. */
    .site-header .main-navigation > ul > li { padding-inline: 16px; }
    .site-header .ds-navlink { font-size: .92rem; }
}
@media (min-width: 993px) and (max-width: 1279px) {
    .site-header .ds-navlink { font-size: .875rem; }
    .site-header .main-navigation > ul > li { padding-inline: 11px; }
}
@media (min-width: 993px) and (max-width: 1100px) {
    .site-header .ds-navlink { font-size: .84rem; gap: .25rem; }
    .site-header .main-navigation > ul > li { padding-inline: 7px; }
}
.site-header .ds-navlink {
    --ds-underline-origin: center;
    position: relative;
    color: #252525;
    font-weight: 500;
    letter-spacing: .005em;
    transition: color var(--ds-dur-fast) var(--ds-ease-out);
}
.site-header .ds-navlink:hover,
.site-header .ds-navlink:focus-visible { color: var(--ds-red-500) !important; }
.site-header .ds-navlink::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    inset-block-end: 0;
    block-size: 2.5px;
    background: var(--ds-red-500);
    border-radius: 2px 2px 0 0;
    transform: scaleX(0);
    transform-origin: var(--ds-underline-origin, center);
    transition: transform 260ms var(--ds-ease-out);
}
.site-header .ds-navlink:hover::after,
.site-header .ds-navlink:focus-visible::after { transform: scaleX(1); }
.site-header .ds-navlink.is-current { color: var(--ds-red-500) !important; font-weight: 600; }
.site-header .ds-navlink.is-current::after { transform: scaleX(1) !important; }

/* --- Dropdown panels (About / Products) -----------------------------------
   The panel is hidden with opacity + visibility and NEVER with display:none.
   Tailwind's `.hidden` utility sets display:none, and a rule that only changes
   opacity or visibility cannot undo it: a panel carrying both stayed invisible
   for good while its trigger still reported aria-expanded="true". The mobile
   rules further down do use display, because there the panel is a static block
   in a scrollable column and a zero-height box would leave a dead gap.
   --------------------------------------------------------------------- */
.site-header .dropdown-menu {
    inset-block-start: 100%;
    /* Anchor the panel's inline-start edge to its own <li> instead of centring
       it on the trigger. A 15rem panel centred on a ~4rem trigger juts out
       ~5.5rem on BOTH sides, so it reads as sitting beside the menu item
       rather than below it. `inset-inline-start` flips to `right` under RTL,
       so the panel stays tucked under its trigger in both directions. */
    inset-inline-start: 0;
    /* Pull the panel up by the bar's 1px hairline so no seam shows between the
       sticky header and the panel's own top border. */
    margin-block-start: -1px;
    z-index: 20;
    min-inline-size: 15rem;
    padding-block: .5rem;
    /* Was a flat `var(--ds-surface)` = #fff, which measured LIGHTER than the
       #fafafa bar it hangs from, so the panel read as a washed-out slab held
       on by a cold #e6e8ec hairline and nothing else. It had also lost the
       backdrop blur it used to carry. Now: a shallow warm gradient that sits
       just under the bar's own tone, translucent so the blur actually shows,
       a warm border, and a shadow in the brand's red rather than neutral grey. */
    background: linear-gradient(180deg,
        rgba(248, 246, 246, .95) 0%,
        rgba(252, 247, 247, .93) 55%,
        rgba(250, 238, 238, .90) 100%);
    -webkit-backdrop-filter: blur(12px) saturate(150%);
    backdrop-filter: blur(12px) saturate(150%);
    border: 1px solid #f0dcdb;
    border-radius: var(--ds-r-md);
    box-shadow: 0 18px 44px rgba(138, 19, 16, .13), 0 2px 6px rgba(20, 22, 26, .06);
    opacity: 0;
    visibility: hidden;
    /* Only a vertical offset now: the panel is already anchored inline. */
    transform: translate(0, -8px);
    transition: opacity   var(--ds-dur) var(--ds-ease-out),
                transform var(--ds-dur) var(--ds-ease-out),
                visibility 0s linear var(--ds-dur);
}
.site-header .dropdown-menu.show {
    opacity: 1;
    visibility: visible;
    transform: translate(0, 0);
    transition-delay: 0s;
}

/* --- Dropdown rows ------------------------------------------------------- */
.site-header .ds-dropdown-link {
    position: relative;
    display: flex;
    align-items: center;
    min-block-size: 44px; /* 44px tap target on the menu rows */
    padding-block: .55rem;
    /* Room on the inline-end for a future chevron, and on the inline-start for
       the accent bar below. */
    padding-inline: 1.5rem;
    font-size: .92rem;
    line-height: 1.45;
    color: var(--ds-ink-700);
    white-space: nowrap;
    transition: background-color var(--ds-dur-fast) var(--ds-ease-out),
                color            var(--ds-dur-fast) var(--ds-ease-out);
}
/* Slide-in accent bar on the inline-start edge */
.site-header .ds-dropdown-link::before {
    content: "";
    position: absolute;
    inset-inline-start: 0;
    inset-block: 18%;
    inline-size: 3px;
    background: var(--ds-red-500);
    border-radius: 0 3px 3px 0;
    transform: scaleY(0);
    transition: transform var(--ds-dur) var(--ds-ease-out);
}
.site-header .ds-dropdown-link:hover,
.site-header .ds-dropdown-link:focus-visible {
    background: var(--ds-red-50);
    color: var(--ds-red-500);
}
.site-header .ds-dropdown-link:hover::before,
.site-header .ds-dropdown-link:focus-visible::before { transform: scaleY(1); }
.site-header .ds-dropdown-link.is-current { color: var(--ds-red-500); font-weight: 600; }
.site-header .ds-dropdown-link.is-current::before { transform: scaleY(1); }
/* The first row of each dropdown is the group's overview page, not a peer of
   the children below it. */
.site-header .ds-dropdown-link.is-overview { font-weight: 600; color: var(--ds-red-600); }

/* --- Dropdown chevron ----------------------------------------------------
   A thin stroked caret rather than a solid arrow: at 0.95rem a filled glyph
   beside a one-word label reads as clutter. It rotates to point up while the
   panel is open, so the state is legible without relying on colour. */
.site-header .ds-chevron {
    inline-size: .7em;
    block-size: .7em;
    flex: none;
    opacity: .55;
    transition: transform var(--ds-dur) var(--ds-ease-out),
                opacity   var(--ds-dur-fast) var(--ds-ease-out);
}
.site-header .dropdown-trigger:hover .ds-chevron,
.site-header .dropdown-trigger:focus-visible .ds-chevron { opacity: 1; }
.site-header .dropdown-trigger[aria-expanded="true"] .ds-chevron {
    transform: rotate(180deg);
    opacity: 1;
}

/* --- RIGHT: language selector ------------------------------- */
.site-header .header-actions { display: flex; align-items: center; gap: .5rem; }

/* --- In-page anchor targets ------------------------------------
   The sticky bar is ~73px tall, so without this offset a jump lands the
   heading underneath it. scroll-padding on <html> covers plain #hash
   links; this covers targets reached from JS and from elements already
   in view. */
.ds-anchor { scroll-margin-block-start: 6.5rem; }

/* --- Mobile panel ------------------------------------------- */
@media (max-width: 992px) {
    .site-header { --_hdr-h: 59px; }
    /* Phones: no backdrop blur on the bar. The sticky header grows to ~72vh
       the instant the panel opens, and a real-time blur over a full-height
       area - with the home page's five full-screen scenes and two autoplaying
       videos behind it - collapses low-end mobile GPUs, which reads as the
       button "freezing" the phone. A flat opaque bar looks identical here. */
    .site-header,
    .site-header.is-scrolled,
    .ds-header.is-scrolled {
        background-color: #fff;
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
    }
    /* the desktop links carry the height; on mobile they are compact rows, so the
       vertical padding belongs to the container again */
    .site-header .header-inner { row-gap: 0; padding-block: .75rem; }

    /* Phones: no hamburger. The bar shows the four top-level stops as one
       always-visible row (Home / About / Products / Contact) and the toggle is
       dropped; only About and Products have children, and tapping one drops its
       submenu as a full-width sheet below the row. The row sits in normal flow
       at a fixed height and every submenu is absolutely positioned, so opening
       a menu never changes the bar's height and nothing underneath reflows.
       The old full-screen panel re-laid-out the whole page (five home scenes
       plus two videos) on every frame of its open animation, which is what
       froze the phone. */
    .site-header .main-navigation {
        position: relative;              /* containing block for the submenus */
        order: 3;                        /* drop below the brand + status row */
        flex-basis: 100%;
        inline-size: 100%;
        flex-direction: row;             /* four stops in a row, not a panel */
        align-items: center;
        min-block-size: 0;
        max-block-size: none;
        overflow: visible;               /* never clip a dropped submenu */
        opacity: 1;
        visibility: visible;
        padding-block: 0;
        border-block-start: 1px solid var(--ds-border, #eceef1);
    }
    .site-header .main-navigation > ul {
        flex-direction: row;
        flex-wrap: nowrap;
        inline-size: 100%;
        justify-content: space-between;
        padding-block: 0;
    }
    .site-header .main-navigation > ul > li {
        inline-size: auto;
        flex: 0 0 auto;
        padding-inline: 0;
    }
    /* Clear the max-[992px] row styling the markup carries (full-width items
       with a per-item top border); the row keeps a single rule on the nav. */
    .site-header .main-navigation .ds-navlink {
        padding: 12px 8px;
        border-block-end: 0;
        white-space: nowrap;
        font-size: .95rem;
    }
    /* Keep the underline lit while a tap-open submenu belongs to this item. */
    .site-header .main-navigation .menu-item-has-children.is-open > .ds-navlink::after {
        transform: scaleX(1) !important;
    }

    /* Tap-a-group submenu: one full-width sheet under the whole row, not an
       item-anchored card, so even a three-word product name stays on screen.
       Absolute, so it overlays the page and leaves the bar height untouched. */
    .site-header .main-navigation .menu-item-has-children { position: static; }
    .site-header .main-navigation .dropdown-menu {
        inset-inline: 0;
        inset-inline-start: 0;
        inset-block-start: 100%;
        margin-block-start: 0;
        min-inline-size: 0;
        padding-block: .25rem;
        background: #fff;
        border: 0;
        border-block-start: 1px solid var(--ds-border, #eceef1);
        border-radius: 0;
        box-shadow: 0 18px 40px rgba(20, 22, 26, .14);
        /* No blur on phones: it would composite over the scene videos. */
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
    }
    .site-header .main-navigation .ds-dropdown-link {
        padding-inline: 1.25rem;
        font-size: .92rem;
        white-space: normal;
        border-block-end: 1px solid var(--ds-border, #eceef1);
    }
    .site-header .main-navigation .ds-dropdown-link:last-child { border-block-end: 0; }
}

/* Narrowest phones: shrink the four stops a touch so they stay on one line. */
@media (max-width: 380px) {
    .site-header .main-navigation .ds-navlink { padding: 11px 3px; font-size: .8rem; }
}

/* --- Reduced motion: state changes without animating -------- */
@media (prefers-reduced-motion: reduce) {
}

/* The "Verification of Conformity" register card now speaks the same box
   language as the story (section 21) and policy (section 25) pages: one dark
   glass panel with white type and brand-red accents, instead of the old white
   document sheet. Hairline rules run at rgba(255,255,255,.16) and the accent
   colour is --ds-red-300, so light-on-dark contrast holds. The small register
   cards below and the page-header are intentionally left as they were. */
.certificate-document-card { grid-column:1/-1; padding:clamp(20px,3.4vw,34px)!important; border-radius:21px; background:rgba(18,20,24,.72); border:1px solid rgba(255,255,255,.22); box-shadow:0 22px 55px rgba(8,10,14,.42); color:#fff; text-shadow:0 1px 3px rgba(8,10,14,.45); }
.certificate-document-card:hover { transform:translateY(-6px); box-shadow:0 30px 66px rgba(8,10,14,.5); border-color:rgba(255,255,255,.34); }
.certificate-document-heading { display:flex; align-items:center; gap:15px; padding-bottom:23px; border-bottom:1px solid rgba(255,255,255,.16); }
.certificate-document-icon { display:grid; width:52px; height:52px; flex:0 0 auto; place-items:center; border:1px solid rgba(255,255,255,.22); border-radius:16px; background:rgba(255,255,255,.08); color:var(--ds-red-300); font-size:1.3rem; }
.certificate-document-eyebrow { color:var(--ds-red-300); font-size:.62rem; font-weight:800; letter-spacing:.13em; }
.certificate-document-heading h2, .certificate-document-heading h3 { margin:4px 0 0; color:#fff; font-size:1.45rem; font-weight:750; letter-spacing:-.035em; }
.certificate-document-meta { display:grid; grid-template-columns:repeat(5,minmax(0,1fr)); gap:12px; padding:20px 0; border-bottom:1px solid rgba(255,255,255,.16); }
.certificate-document-meta div { display:grid; align-content:start; gap:5px; }
.certificate-document-meta small { color:rgba(255,255,255,.55); font-size:.6rem; font-weight:800; letter-spacing:.1em; }
.certificate-document-meta strong { color:#fff; font-size:.78rem; overflow-wrap:anywhere; }
.certificate-document-body { display:grid; grid-template-columns:1fr; gap:22px; padding:21px 0; }
.certificate-document-body h3, .certificate-document-body h4 { margin:0 0 7px; color:var(--ds-red-300); font-size:.76rem; font-weight:800; letter-spacing:.035em; }
.certificate-document-body p { margin:0 0 13px; color:rgba(255,255,255,.82); font-size:.8rem; line-height:1.7; }
.certificate-standard-tags { display:flex; flex-wrap:wrap; gap:7px; margin:9px 0 12px; }
.certificate-standard-tags span { padding:6px 9px; border:1px solid rgba(255,255,255,.20); border-radius:999px; background:rgba(255,255,255,.08); color:rgba(255,255,255,.90); font-size:.66rem; font-weight:650; }
.certificate-document-body p.certificate-report-line { margin-bottom:0; color:rgba(255,255,255,.62); font-size:.72rem; }
.certificate-document-footer { display:flex; align-items:center; justify-content:space-between; gap:14px; padding-top:17px; border-top:1px solid rgba(255,255,255,.16); }
.certificate-document-footer>span { color:rgba(255,255,255,.62); font-size:.69rem; }
.certificate-document-footer>div { display:flex; flex-wrap:wrap; align-items:center; gap:16px; }
.certificate-download-link,.certificate-issuer-link { display:inline-flex; align-items:center; gap:7px; color:var(--ds-red-300); font-size:.75rem; font-weight:750; text-decoration:none; }
.certificate-download-link { padding:9px 13px; border:1px solid rgba(255,255,255,.28); border-radius:999px; background:rgba(255,255,255,.08); }
.certificate-download-link:hover { border-color:var(--ds-red-300); background:rgba(255,255,255,.14); }
.certificate-issuer-link:hover { text-decoration:underline; }
@media(max-width:800px) { .certificate-document-meta { grid-template-columns:repeat(3,minmax(0,1fr)); } .certificate-document-body { grid-template-columns:1fr; } }
@media(max-width:560px) { .certificate-document-meta { grid-template-columns:repeat(2,minmax(0,1fr)); } .certificate-document-footer { align-items:flex-start; flex-direction:column; } .certificate-document-footer>div { justify-content:flex-start; } }

/* ------------------------------------------------------------
   20. CONTACT PAGE - photo background behind the form
   ------------------------------------------------------------
   The contact page is the one inner page whose body is a single tall
   block (the form), so instead of flat white it sits - with no card
   behind it at all - on the site's own "Contact Us" photograph. Two
   layers, same recipe as the banner above:

     1. a charcoal -> brand-red veil, heavy under the form so the white
        copy reads, clearing towards the right where the picture is the
        point;
     2. the photograph itself (contact-us.jpg), anchored right and sized
        `cover` so it fills the section at any height without
        letterboxing - the form grows when errors show, and the
        background has to follow it.

   The fallback colour matches the darkest stop of the veil, so the
   section is already on-tone while the photo loads. */
.contact-section {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    background-color: var(--ds-ink-900);
    /* The card is pinned to the far left, so the veil is heavy there and
       clears towards the right, and the photo is anchored right: the part
       of the picture that used to sit behind the card is now the part that
       shows beside it. `cover` still fills the section at any height - the
       form grows when errors show, and the background follows it.
       (contact-us.jpg was restored from build/_cleanup_backup_20261009 and
       copied back into assets/images/.) */
    background-image:
        linear-gradient(100deg,
            rgba(20,22,26,.94) 0%,
            rgba(20,22,26,.90) 45%,
            rgba(20,22,26,.78) 62%,
            rgba(74,10,8,.50) 80%,
            rgba(74,10,8,.30) 100%),
        url("../images/contact-us.jpg");
    /* Capable browsers swap the photograph for AVIF/WebP; the declaration
       above is the JPEG fallback for anything that cannot parse image-set(). */
    background-image:
        linear-gradient(100deg,
            rgba(20,22,26,.94) 0%,
            rgba(20,22,26,.90) 45%,
            rgba(20,22,26,.78) 62%,
            rgba(74,10,8,.50) 80%,
            rgba(74,10,8,.30) 100%),
        image-set(
            url("../images/contact-us.avif") type("image/avif"),
            url("../images/contact-us.webp") type("image/webp"),
            url("../images/contact-us.jpg")  type("image/jpeg"));
    background-size: cover;
    background-position: right center;
    background-repeat: no-repeat;
}
/* The card is gone: no panel, no border, no shadow - just the form sitting
   on the photograph. The veil behind this column is ~90% charcoal, so the
   copy switches to white and the fields keep their own white surfaces; the
   contrast comes from the backdrop, not from a box. */
.contact-section .contact-form {
    color: #fff;
    background: transparent;
    border: 0;
    box-shadow: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
}
/* Title in plain white; the labels follow it (1rem, weight 600) so nothing
   gets lost against a bright patch of the photo. The required asterisk keeps
   an accent, but the light tint of the brand red (#e88b88 reads ~5.5:1 on
   this backdrop, the base #C11B17 only ~2.5:1). */
.contact-section .contact-form h2 { color: #fff; }
.contact-section .contact-form .ds-label,
.contact-section .contact-form label {
    color: #fff;
    font-size: var(--ds-fs-base);
    font-weight: 600;
}
.contact-section .contact-form .ds-label .text-primary,
.contact-section .contact-form label .text-primary { color: var(--ds-red-300); }
/* Hints (the phone country-code line) sit straight on the photograph, so
   the ds-hint grey is unreadable there - lifted to white-at-78%. */
.contact-section .contact-form .ds-hint { color: rgba(255, 255, 255, .78); font-size: var(--ds-fs-sm); }

/* Every field is glass, not just the subject select: the whole form sits
   on the photograph, so a row of solid white boxes would read as a panel
   again. Text is white on the dark veil behind it (~14:1), the placeholder
   sits a step below so it still reads as a hint, and the caret is redrawn
   white - the grey one the shared .ds-select paints disappears against the
   picture. border-color shouts !important because rule 7b sets it that way
   globally; the option LIST is drawn by the OS with an opaque background,
   so it gets an explicit white/dark pair or some browsers show white text
   on white. */
.contact-section .contact-form input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):not([type="hidden"]),
.contact-section .contact-form textarea,
.contact-section .contact-form select {
    background-color: rgba(255, 255, 255, .18);
    border-color: rgba(255, 255, 255, .45) !important;
    color: #fff;
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
}
.contact-section .contact-form input:not([type="hidden"]):hover,
.contact-section .contact-form textarea:hover,
.contact-section .contact-form select:hover {
    border-color: rgba(255, 255, 255, .75) !important;
}
.contact-section .contact-form :is(input, textarea)::placeholder { color: rgba(255, 255, 255, .6); }
.contact-section .contact-form select {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath fill='%23ffffff' d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 1.1rem center;
    background-size: 12px;
}
.contact-section .contact-form select option { background: #fff; color: var(--ds-ink-900); }

/* Machine translation (Chrome's built-in bar, the old Google Translate
   widget) wraps copy in <font>/<mark> nodes and can paint them with the
   page's accent red - the red boxes that appear behind the text. The
   section already opts out with translate="no" in the markup, because the
   server serves the form in the visitor's language; these rules are the
   backstop for extensions that ignore the attribute. Nothing that is
   SUPPOSED to be coloured is touched: the success/error banners are divs
   and the asterisks are colour, not background. */
.contact-section :is(font, mark, label, span, a) { background-color: transparent; }

/* The footer carries a 60px top margin so the red "Need help?" strip floats
   clear of the copy on the white pages. On the pages whose last section is
   a full-bleed photograph (contact, story, distributors, policy) that margin
   reads as a white band wedged between the photo and the strip - dropped
   there only, matched through the class those pages put on <main> rather
   than :has(), which older engines still lack. */
.main-contact + .site-footer,
.main-story + .site-footer,
.distributors-page + .site-footer,
.policy-page + .site-footer { margin-top: 0; }
/* .products-page is isolation:isolate, so <main> is one stacking context
   painted in the positioned/auto step - i.e. AFTER the plain in-flow footer.
   Its viewport-fixed ::before would then paint over the footer body, so the
   footer is lifted into the same step just above it. */
.products-page + .site-footer { margin-top: 0; position: relative; z-index: 1; }

/* ------------------------------------------------------------
   21. STORY PAGE - the story photograph, full bleed
   ------------------------------------------------------------
   Same language as .contact-section (section 20): the photograph fills
   the section edge to edge and the copy sits above it. The copy runs on
   its own dark panel, but the panel is TRANSPARENT glass - the photo reads
   through it (blurred) as well as around it - so the picture is visible
   behind the box; the tint + blur are what keep the white type legible.

       .story-section   the photograph + veil, full bleed
       .story-page      the 860px centred stage
       .story-content   the copy, on a see-through dark card

   Measured on story.jpeg (1600x1067, mean luminance 160, p95 241):
   the veil firms up only where the section meets the header box above,
   then gets out of the way, so the photograph reads from the first
   screenful - around the card and in the band below it. The card carries
   a translucent charcoal + blur so the photo shows through while the white
   copy still lands above the WCAG threshold on the bright picture. */
.story-section {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    background-color: var(--ds-ink-900);   /* on-tone while the photo loads */
    background-image:
        linear-gradient(180deg,
            rgba(20, 22, 26, .88)   0%,
            rgba(20, 22, 26, .54)  10%,
            rgba(20, 22, 26, .42)  55%,
            rgba(74, 10, 8,  .52) 100%),
        url("../images/story.jpeg");
    /* AVIF/WebP where supported, story.jpeg as the fallback. */
    background-image:
        linear-gradient(180deg,
            rgba(20, 22, 26, .88)   0%,
            rgba(20, 22, 26, .54)  10%,
            rgba(20, 22, 26, .42)  55%,
            rgba(74, 10, 8,  .52) 100%),
        image-set(
            url("../images/story.avif") type("image/avif"),
            url("../images/story.webp") type("image/webp"),
            url("../images/story.jpeg") type("image/jpeg"));
    background-size: cover;
    background-position: center 40%;
    background-repeat: no-repeat;
}
.story-section > * { position: relative; z-index: 1; }

.story-page {
    max-inline-size: 860px;
    margin-inline: auto;
}

/* Transparent glass reading surface: the copy sits on a see-through dark
   panel so the story photograph reads clearly BEHIND the box as well as
   around it. Kept very light (.25) per the owner, with just enough blur to
   calm the photo under the type and a soft text-shadow so the white copy
   still holds over the bright picture. */
.story-section .story-content.ds-card {
    background: rgba(18, 20, 24, .25);
    border: 1px solid rgba(255, 255, 255, .18);
    box-shadow: 0 22px 55px rgba(8, 10, 14, .30);
    -webkit-backdrop-filter: blur(3px) saturate(1.05);
    backdrop-filter: blur(3px) saturate(1.05);
    padding: clamp(1.5rem, 3vw, 2.5rem);
    color: #fff;
    text-shadow: 0 1px 3px rgba(8, 10, 14, .55);
}
/* There is a card now, so the card lifts. The pale red border
   .ds-card:hover paints would read as a light outline on the panel, so
   the hover keeps the lift and swaps in a brighter edge instead. */
.story-section .story-content.ds-card:hover {
    transform: translateY(-6px);
    box-shadow: 0 30px 66px rgba(8, 10, 14, .54);
    border-color: rgba(255, 255, 255, .34);
}
/* Type: white headings and body on the panel, the light brand tint for the
   accent lines (eyebrow + the objective sentence) - the same pairing the
   contact page uses for its accents over the photograph. */
.story-section .story-content :is(h2, h3) { color: #fff; text-wrap: balance; }
.story-section .story-content p,
.story-section .story-content li { color: rgba(255, 255, 255, .94); line-height: 1.8; }
.story-section .story-content .text-primary:not(:is(h2, h3)) { color: var(--ds-red-300); }

/* Stage pills: white glass instead of white plastic. The border has to be
   re-declared here because the old markup carried it as a Tailwind utility
   that no longer exists in the template. */
.story-section .ds-story-pill {
    border: 1px solid rgba(255, 255, 255, .35);
    background: rgba(255, 255, 255, .12);
    color: #fff;
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
}
/* The mono step number inherits the pill's white, but .ds-section-index
   paints itself --ds-ink-300 (a light grey meant for white surfaces), which
   drops to ~4.4:1 on the dark panel. A translucent white keeps the same
   visual weight and clears 6:1. */
.story-section .ds-story-pill .ds-section-index { color: rgba(255, 255, 255, .72); }

/* Tablet / phone: the crop moves up so the subject stays in frame. The
   veil stays where it is - the copy sits on its own panel, so a phone
   keeps the same clear picture as the desktop and only the crop and the
   card padding change. */
@media (max-width: 992px) {
    .story-section { background-position: center 35%; }
}
@media (max-width: 640px) {
    .story-section { background-position: center 30%; }
    /* The template's fixed pt-[84px] pb-[120px] consumes a third of a
       phone viewport; scale with the screen like the rest of the rhythm
       instead of holding those desktop figures. */
    .story-section { padding-block: clamp(2.5rem, 9vw, 3.5rem) clamp(3rem, 11vw, 4.5rem); }
    .story-section .story-content { padding: 1.1rem; }
}

/* ------------------------------------------------------------
   22. HOME - THE FIVE FULL-SCREEN SCENES
   ------------------------------------------------------------
   index.php is the only page that marks <html> with .ds-home, and every
   rule below is scoped to that marker or to the scene classes only it
   uses, so the other pages keep their ordinary document flow, their
   sticky white header and their snap-free scrolling untouched.

   The five scenes, in order:

     scene--01  hero.php           101.jpg, the panel and the icon row
     scene--02  home-products.php  products.jpg under the product cards
     scene--03  home-story.php     v1.jpg under the story prose and film
     scene--04  home-services.php  v2.jpg under the services prose and film
     scene--05  home-stats.php     v3.jpg under the figures and the CTA

   Each scene is min-height 100svh (100vh first, as the fallback for
   browsers without small-viewport units), and its photograph lives in
   its own .scene__bg layer rather than on the section. That layer is
   what motion.js parallaxes through the existing data-parallax hook,
   and it is inset -8% on every edge so the drift can never uncover a
   seam. The shared charcoal -> brand-red veil sits over the picture as
   a ::before; the copy sits above both (z-index 2).

   Scroll behaviour: proximity snap, not mandatory. A scene is exactly
   one screen on most desktops, but the services copy runs taller than
   the window at some widths, and mandatory snap beneath a scene that
   cannot fit traps the reader inside it. Proximity settles a scene that
   is already nearly aligned and leaves everything else to the native
   wheel, trackpad, touch and keyboard - there is no scroll hijacking
   here or in scenes.js.

   Content fitting: these are min-height, not height, so a scene whose
   copy needs more room grows instead of clipping, and overflow: hidden
   only crops the parallax layer, which is meant to be cropped.

   Reveals: every scene carries .no-reveal so motion.js does not
   auto-tag sections, headings and grid children on top of the plan,
   and the animation order is stated per element with data-reveal in
   the includes instead.
   ------------------------------------------------------------ */

html.ds-home {
    /* The bar keeps its normal sticky position in the document flow here,
       so every scene has to clear it: a snap position (or an anchor) must
       land a scene's top edge just UNDER the bar rather than beneath it.
       Section 1's 6rem would leave a strip of the scene before visible
       between the bar and the snap point. */
    scroll-padding-block-start: var(--ds-header-h, 72px);
    scroll-snap-type: y proximity;
}

/* --- scene shell ------------------------------------------------ */
.scene {
    position: relative;
    isolation: isolate;
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-height: 100vh;
    min-height: 100svh;
    /* Room under the sticky bar for scenes 02-05, which scroll beneath it,
       and a floor at the foot for the scroll hint on scene 01. */
    padding-block: calc(var(--ds-header-h, 72px) + 20px) 56px;
    overflow: hidden;
    scroll-snap-align: start;
    scroll-snap-stop: normal;
    background-color: var(--ds-ink-900);
}

/* The hero is a fixed column: panel at the top, icon row at the foot. */
.scene.scene--01 { justify-content: space-between; }

/* Scene 01 sits directly BELOW the bar, which is still in normal flow
   (sticky, not fixed), so it starts at document y = the bar's height: it
   must not pad for it a second time, and its own height is shortened by
   the bar so panel, icon row and scroll hint still end exactly at the
   fold. Scenes 02-05 do pad, because once the page is moving they scroll
   underneath the stuck bar. */
html.ds-home .scene.scene--01 {
    /* The white panel's top edge lands flush against the bar's bottom edge:
       no photo strip between them (the owner wants the panel to rise and
       align with the top bar). The +20px room in the shared .scene floor is
       for scenes 02-05 that scroll beneath the stuck bar; scene 01 sits
       directly below it, so it takes none. */
    padding-block-start: 0;
    min-height: calc(100vh - var(--ds-header-h, 72px));
    min-height: calc(100svh - var(--ds-header-h, 72px));
}

/* The photograph: its own layer, so parallax moves the picture without
   moving the copy on top of it. The scale is a slow settle once the
   scene is the one on screen - depth, not an effect. */
.scene__bg {
    position: absolute;
    inset: -8% 0;
    z-index: 0;
    background-color: var(--ds-ink-900);
    background-size: cover;
    background-repeat: no-repeat;
    transform: scale(1);
    transition: transform 1400ms var(--ds-ease-out);
}
.scene.is-active .scene__bg { transform: scale(1.05); }

/* The veil. Scene 01 keeps its own --hero-veil inside the background
   stack, so this rule is the other four. */
.scene--02::before,
.scene--03::before,
.scene--04::before,
.scene--05::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background: linear-gradient(180deg,
        rgba(20, 22, 26, .90)   0%,
        rgba(20, 22, 26, .78)  45%,
        rgba(74, 10, 8,  .86) 100%);
}

/* Copy above the veil; the background layer keeps its own stacking. */
.scene > * { position: relative; z-index: 2; }
.scene > .scene__bg { position: absolute; z-index: 0; }

/* --- photograph and crop per scene ------------------------------
   products.jpg is a portrait 841x1264 on a near-square scene, so its
   crop is pinned to the upper-middle; the three landscape sources are
   all ~3:2 and only need a centre anchor. */
.scene--02 .scene__bg {
    background-image: url("../images/products.jpg");
    background-image: image-set(
        url("../images/products.avif") type("image/avif"),
        url("../images/products.webp") type("image/webp"),
        url("../images/products.jpg")  type("image/jpeg"));
    background-position: center 30%;
}
.scene--03 .scene__bg {
    background-image: url("../images/v1.jpg");
    background-image: image-set(
        url("../images/v1.avif") type("image/avif"),
        url("../images/v1.webp") type("image/webp"),
        url("../images/v1.jpg")  type("image/jpeg"));
    background-position: center 45%;
}
.scene--04 .scene__bg {
    background-image: url("../images/v2.jpg");
    background-image: image-set(
        url("../images/v2.avif") type("image/avif"),
        url("../images/v2.webp") type("image/webp"),
        url("../images/v2.jpg")  type("image/jpeg"));
    background-position: center 50%;
}
.scene--05 .scene__bg {
    background-image: url("../images/v3.jpg");
    background-image: image-set(
        url("../images/v3.avif") type("image/avif"),
        url("../images/v3.webp") type("image/webp"),
        url("../images/v3.jpg")  type("image/jpeg"));
    background-position: center 55%;
}

/* --- white type over the veil ----------------------------------
   Scoped to the two wrappers that sit directly on the photograph -
   .home-copy for the prose, .home-products-head for the heading row -
   so the white never reaches the panels beside them: the product
   cards, the stat cards and the film frames keep their own colours. */
.home-products-head .ds-eyebrow,
.home-copy .ds-eyebrow { color: var(--ds-red-300); }
.home-products-head h2,
.home-copy h2,
.home-copy h3 { color: #fff; text-wrap: balance; }
.home-products-head .ds-link { color: #fff; }
/* line-height opens up because white on a photograph needs more air
   than the same words on white paper do. */
.home-copy p { color: rgba(255, 255, 255, .9); line-height: 1.85; }

/* The heading row over the photograph wants less room than a document
   section gives it - one scene, not one page. */
.home-products .home-products-head { margin-block-end: clamp(1rem, 2.2vw, 1.6rem); }

/* --- the films inside a scene -----------------------------------
   A scene is one screen, so the 420px-wide films also get a
   viewport-relative ceiling. It only bites on short windows: at a
   normal height the story film (420x581) is below it already and the
   scene is sized by its own copy. cover keeps the frame full rather
   than letterboxing it when the ceiling does apply. */
.scene-media {
    max-block-size: calc(100svh - var(--ds-header-h, 72px) - 170px);
    object-fit: cover;
}

/* --- scene 05: the close of the page ----------------------------
   The figures, then a sign-off and the way to the contact page. The
   kicker is set in the hero's condensed face but taken from a local
   fallback chain, because --hero-font is declared on .hero only and
   this block lives two scenes away. */
.scene-cta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .9rem 1.4rem;
    margin-block-start: clamp(1.1rem, 2.6vw, 1.9rem);
}
.scene-cta__kicker {
    font-family: var(--hero-font, "Oswald", "Arial Narrow", system-ui, sans-serif);
    font-size: clamp(1.1rem, 2.2vw, 1.5rem);
    font-weight: 400;
    line-height: 1.3;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: #fff;
}

/* --- scene 01: the scroll hint --------------------------------- */
.scene-hint {
    position: absolute;
    inset-block-end: 14px;
    inset-inline: 0;
    z-index: 3;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    color: rgba(255, 255, 255, .82);
    pointer-events: none;
    transition: opacity var(--ds-dur) linear;
}
.scene-hint.is-hidden { opacity: 0; }
.scene-hint__label {
    font-size: .66rem;
    font-weight: 600;
    letter-spacing: .24em;
    text-transform: uppercase;
}
.scene-hint__arrow {
    inline-size: 18px;
    block-size: 18px;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    animation: ds-scene-hint 2s ease-in-out infinite;
}
@keyframes ds-scene-hint {
    0%, 100% { transform: translateY(0); opacity: .5; }
    50%      { transform: translateY(7px); opacity: 1; }
}

/* --- the scene index, fixed to the right edge ------------------
   Buttons, not anchors: motion.js turns anchor clicks into a smooth
   jump offset by the header, and these positions are scene tops, not
   anchor targets under a header. scenes.js reads data-scene-target. */
.scene-nav {
    position: fixed;
    inset-block-start: 50%;
    inset-inline-end: 16px;
    transform: translateY(-50%);
    z-index: 900;
}
.scene-nav__list {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin: 0;
    padding: 0;
    list-style: none;
}
.scene-nav__btn {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 6px;
    border: 0;
    background: none;
    cursor: pointer;
    font-family: inherit;
    font-size: .7rem;
    font-weight: 600;
    letter-spacing: .12em;
    color: rgba(255, 255, 255, .6);
    /* The index floats over whatever the scene is showing at this
       height, which can be the bright part of a photograph. */
    text-shadow: 0 1px 6px rgba(0, 0, 0, .55);
    transition: color var(--ds-dur-fast) var(--ds-ease-out);
}
.scene-nav__btn:hover,
.scene-nav__btn:focus-visible,
.scene-nav__btn.is-active { color: #fff; }
.scene-nav__dot {
    inline-size: 8px;
    block-size: 8px;
    border: 1.5px solid currentColor;
    border-radius: 50%;
    transition:
        background-color var(--ds-dur-fast) linear,
        border-color     var(--ds-dur-fast) linear,
        transform        var(--ds-dur-fast) var(--ds-ease-out);
}
.scene-nav__btn.is-active .scene-nav__dot {
    background: var(--ds-red-500);
    border-color: var(--ds-red-500);
    transform: scale(1.3);
}
.scene-nav__label {
    max-inline-size: 0;
    opacity: 0;
    overflow: hidden;
    white-space: nowrap;
    text-transform: uppercase;
    transition:
        max-inline-size var(--ds-dur) var(--ds-ease-out),
        opacity         var(--ds-dur) linear;
}
.scene-nav:hover .scene-nav__label,
.scene-nav__btn:focus-visible .scene-nav__label,
.scene-nav__btn.is-active .scene-nav__label {
    max-inline-size: 120px;
    opacity: 1;
}

/* --- the header over the scenes --------------------------------
   Nothing to do: the bar keeps the site's ordinary white sticky
   treatment (Tailwind's bg-white/sticky in the markup plus section
   18), so it reads as one bar across the whole page and the scenes
   simply pass under it. What this page owes it is clearance, and
   that lives in .scene's top padding and in the scroll-padding
   declared on html.ds-home above - not in a second header state.
   The mobile panel keeps its white surface and dark links for the
   same reason. */

/* The footer follows scene 05 in ordinary flow, and carries its own
   snap position so proximity never pulls the reader back off it. */
html.ds-home .site-footer {
    margin-block-start: 0;
    scroll-snap-align: start;
}

/* --- phones ----------------------------------------------------- */
@media (max-width: 900px) {
    /* The index would sit on top of the copy at this width. */
    .scene-nav { display: none; }
}
@media (max-width: 640px) {
    .scene {
        padding-block: calc(var(--ds-header-h, 68px) + 14px) 44px;
    }
    /* The copy covers far more of the picture at this width, so the veil
       firms up and the portrait crop walks up with it. */
    .scene--02 .scene__bg { background-position: center 24%; }
    .scene--02::before,
    .scene--03::before,
    .scene--04::before,
    .scene--05::before {
        background: linear-gradient(180deg,
            rgba(20, 22, 26, .93)   0%,
            rgba(20, 22, 26, .88)  50%,
            rgba(74, 10, 8,  .92) 100%);
    }
}
@media (max-width: 600px) {
    /* The label would crowd the footer's edge; the arrow still reads. */
    .scene-hint__label { display: none; }
}

/* --- reduced motion --------------------------------------------
   Snap is a positional animation the reader did not ask for, so it goes
   with the rest: the scenes stay full-screen, the scroll stays native. */
@media (prefers-reduced-motion: reduce) {
    html.ds-home {
        scroll-snap-type: none;
        scroll-behavior: auto;
    }
    .scene__bg,
    .scene.is-active .scene__bg {
        transform: none;
        transition: none;
    }
    .scene-hint__arrow { animation: none; }
}

/* ------------------------------------------------------------
   23. DISTRIBUTORS - the site's own photograph behind the WHOLE page
   ------------------------------------------------------------
   The "Apply to become a distributor" call now opens the page instead of
   trailing the process list, and the picture is not a band behind that one
   callout: it is the page's backdrop, from the dark page-header strip down
   to the footer, the way the site already sits its contact and story pages
   on their photographs.

   The photograph (distributors.jpg, 1062x1008) is a near-white studio
   shot: median luminance 235, p5 = 203, only ~1% of pixels below 42
   (hair / eye detail). So the veil runs WHITE, not the charcoal the story
   and contact bands use - a dark veil over a picture this bright only
   produces flat grey. The white veil sits around .54 mid, which is where
   the arithmetic lands: the hair-dark pixels (42) behind free copy must
   reach 155 (4.5:1 for #333) - (1-.54)*42+.54*255 = 157 - and that same
   stop leaves the figure reading: subject cells (185 raw) land ~223
   against a ~246 ground, delta ~23, with the hair patch at ~157 keeping
   the silhouette. Past ~.6 the picture stops being a picture. It eases
   whiter toward the bottom edge of the screen so low copy keeps its
   contrast as the reader scrolls.

   Attachment is fixed: the photo stays put like wallpaper while the page
   scrolls over it (no wild zoom from cover-sizing a 1000px-tall image to
   a 3000px-tall document). Phones fall back to scroll - iOS handles
   fixed backgrounds badly - where cover crops to the subject centred.
   Free copy over the photo is #333 - footnote included - plus
   .ds-photo-lift: the bloom is invisible on the light ground and rescues
   the odd glyph crossing the subject's dark detail. Cards and the
   callout stay opaque, so their type never touches the backdrop. */
.distributors-page {
    position: relative;
    isolation: isolate;
    background-color: #fff;   /* on-tone while the photo loads */
    background-image:
        linear-gradient(180deg,
            rgba(255, 255, 255, .56)   0%,
            rgba(255, 255, 255, .54)  32%,
            rgba(255, 255, 255, .56)  68%,
            rgba(255, 255, 255, .78) 100%),
        url("../images/distributors.jpg");
    /* AVIF/WebP where supported, distributors.jpg as the fallback. */
    background-image:
        linear-gradient(180deg,
            rgba(255, 255, 255, .56)   0%,
            rgba(255, 255, 255, .54)  32%,
            rgba(255, 255, 255, .56)  68%,
            rgba(255, 255, 255, .78) 100%),
        image-set(
            url("../images/distributors.avif") type("image/avif"),
            url("../images/distributors.webp") type("image/webp"),
            url("../images/distributors.jpg")  type("image/jpeg"));
    background-attachment: fixed, fixed;
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}
/* Free copy that sits straight on the photograph: a white bloom that
   does nothing on the light ground and rescues the odd glyph crossing
   the subject's dark detail. */
.ds-photo-lift {
    text-shadow:
        0 0 2px  rgba(255, 255, 255, .95),
        0 0 10px rgba(255, 255, 255, .85),
        0 0 24px rgba(255, 255, 255, .70);
}
/* The whole programme now reads as ONE organised card - the same box language
   as the story (section 21) and policy (section 25) pages. The card is solid
   enough (.72) to hold white type over the white-veiled Distributors photo,
   and the interior is divided by hairline rules with brand-red accents so the
   content reads as sections, not as one long run of text. */
.distributors-page .distributors-content.ds-card {
    background: rgba(18, 20, 24, .72);
    border: 1px solid rgba(255, 255, 255, .22);
    box-shadow: 0 22px 55px rgba(8, 10, 14, .42);
    -webkit-backdrop-filter: blur(8px) saturate(1.05);
    backdrop-filter: blur(8px) saturate(1.05);
    padding: clamp(1.5rem, 3vw, 2.5rem);
    color: #fff;
    text-shadow: 0 1px 3px rgba(8, 10, 14, .5);
}
.distributors-page .distributors-content.ds-card:hover {
    transform: translateY(-6px);
    box-shadow: 0 30px 66px rgba(8, 10, 14, .5);
    border-color: rgba(255, 255, 255, .34);
}
.distributors-page .distributors-content :is(h1, h2, h3) { color: #fff; }
.distributors-page .distributors-content p,
.distributors-page .distributors-content li { color: rgba(255, 255, 255, .94); }

/* Structured interior: a hairline divider between each block. */
.distributors-page .distributors-content > :not(:first-child) {
    margin-block-start: clamp(1.25rem, 3vw, 1.75rem);
    padding-block-start: clamp(1.25rem, 3vw, 1.75rem);
    border-block-start: 1px solid rgba(255, 255, 255, .16);
}

/* The apply call leads the card. */
.distributors-page .distributors-content .distributors-apply-title {
    font-size: 1.3rem;
    font-weight: 600;
    margin-block-end: .5rem;
}
.distributors-page .distributors-content .distributors-apply-text {
    color: rgba(255, 255, 255, .80);
    margin-block-end: 1.25rem;
}

/* Intro line. */
.distributors-page .distributors-content .distributors-intro {
    max-inline-size: 760px;
    margin-inline: auto;
    text-align: center;
    line-height: 1.7;
    color: rgba(255, 255, 255, .88);
}

/* Benefit tiles: translucent panels on the card. */
.distributors-page .distributors-content .distributors-benefit {
    background: rgba(255, 255, 255, .06);
    border: 1px solid rgba(255, 255, 255, .14);
    border-radius: 16px;
    padding: 1.25rem;
}
.distributors-page .distributors-content .distributors-benefit-icon {
    inline-size: 48px;
    block-size: 48px;
    display: inline-grid;
    place-items: center;
    border-radius: 50%;
    background: rgba(255, 255, 255, .10);
    color: var(--ds-red-300);
    margin-block-end: 1rem;
}
.distributors-page .distributors-content .distributors-benefit-icon .icon {
    inline-size: 20px;
    block-size: 20px;
}
.distributors-page .distributors-content .distributors-benefit-title {
    font-size: 1.15rem;
    font-weight: 600;
    margin-block-end: .5rem;
}
.distributors-page .distributors-content .distributors-benefit-text {
    color: rgba(255, 255, 255, .78);
    font-size: .9rem;
    line-height: 1.6;
}

/* Process heading gets the brand-red accent bar, like the policy card. */
.distributors-page .distributors-content .distributors-process-title {
    color: var(--ds-red-300);
    padding-inline-start: .8rem;
    border-inline-start: 3px solid var(--ds-red-500);
    font-size: 1.5rem;
    font-weight: 300;
    line-height: 1.25;
    margin-block-end: 1.25rem;
}

/* Process steps: translucent panels with the mono index. */
.distributors-page .distributors-content .distributors-step {
    background: rgba(255, 255, 255, .06);
    border: 1px solid rgba(255, 255, 255, .14);
    border-radius: 16px;
    padding: 1.25rem;
}
.distributors-page .distributors-content .distributors-step-num {
    display: block;
    margin-block-end: .5rem;
    font-family: var(--ds-font-mono);
    font-variant-numeric: tabular-nums;
    font-size: 1.1rem;
    font-weight: 600;
    letter-spacing: .05em;
    color: var(--ds-red-300);
}
.distributors-page .distributors-content .distributors-step p {
    color: rgba(255, 255, 255, .82);
    font-size: .92rem;
    line-height: 1.6;
}

/* Fine print closes it. */
.distributors-page .distributors-content .distributors-note {
    color: rgba(255, 255, 255, .62);
    font-size: 11px;
}
/* iOS and friends render fixed backgrounds unreliably: scroll, and let
   cover centre the subject on the (tall) document instead of the viewport. */
@media (max-width: 640px) {
    .distributors-page {
        background-attachment: scroll, scroll;
        background-position: center 40%;
    }
}

/* ------------------------------------------------------------
   24. BUSINESS STATUS - the live open/closed pill + the hours table
   ------------------------------------------------------------
   One schedule, two views: assets/js/business-status.js owns CONFIG
   (UTC+02:00, Mon-Fri, 09:00-16:00) and fills both [data-biz-status]
   views - the pill at the far RIGHT of the header bar, and the status
   row in the footer's working-hours table.

   The header pill is an <a href="#working-hours-details">, so pressing
   closed/open takes the reader straight to the footer's details table
   (motion.js smooth-scrolls hash anchors with the sticky-header offset).
   There is no popover: the details live in exactly one place, the
   table, and the table carries the same live pill the header shows.

   State reads three ways at once - dot colour, shell tint, and the
   words themselves (the two .ds-status-word spans swap by is-open /
   is-closed on the pill) - so the answer survives colour blindness and
   greyscale printing: OPEN is green (#22c55e), CLOSED is the brand red
   (var(--ds-red-500), #C11B17). The clock in the header pill is
   business-local (UTC+2) with tabular-nums, so the minute change never
   nudges the pill's width.

   Markup ships is-closed: if the script never loads, the page claims
   the safe answer rather than an office that may be shut. */
.ds-status {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    margin: 0;
    padding: 6px 11px;
    border: 1px solid rgba(20, 22, 26, .10);
    border-radius: 999px;
    background: rgba(20, 22, 26, .045);
    color: var(--ds-ink-900, #14161a);
    font-family: inherit;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .05em;
    line-height: 1;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    transition:
        background-color .25s ease,
        border-color .25s ease,
        box-shadow .3s ease;
}
/* The dot carries the state even before the words resolve: red = closed
   (also the pre-JS default), green + glow = open. */
.ds-status-dot {
    inline-size: 7px;
    block-size: 7px;
    flex-shrink: 0;
    border-radius: 50%;
    background: var(--ds-red-500, #C11B17);
    box-shadow: 0 0 6px rgba(193, 27, 23, .55);
    transition: background-color .3s ease, box-shadow .3s ease;
}
.ds-status.is-open .ds-status-dot {
    background: #22c55e;
    box-shadow: 0 0 6px rgba(34, 197, 94, .85);
    animation: ds-status-breathe 2.2s ease-in-out infinite;
}
@keyframes ds-status-breathe {
    0%, 100% { box-shadow: 0 0 4px rgba(34, 197, 94, .55); }
    50%      { box-shadow: 0 0 10px 2px rgba(34, 197, 94, .9); }
}
/* Shell tints: closed = brand red, open = green - state visible at a
   glance without reading the words. */
.ds-status.is-closed {
    background: rgba(193, 27, 23, .07);
    border-color: rgba(193, 27, 23, .30);
}
.ds-status.is-open {
    background: rgba(34, 197, 94, .08);
    border-color: rgba(34, 197, 94, .35);
}
.ds-status.is-closed:hover {
    background: rgba(193, 27, 23, .11);
    border-color: rgba(193, 27, 23, .45);
}
.ds-status.is-open:hover {
    background: rgba(34, 197, 94, .12);
    border-color: rgba(34, 197, 94, .50);
}
/* Word swap: exactly one word is in flow, chosen by the state class. */
.ds-status-word { display: none; }
.ds-status.is-open .ds-status-word--open { display: inline; }
.ds-status.is-closed .ds-status-word--closed { display: inline; }
.ds-status-sep { opacity: .4; }
.ds-status-time {
    opacity: .85;
    font-variant-numeric: tabular-nums;
    letter-spacing: .02em;
}
/* --- the footer's working-hours table -------------------------
   Directly under the contact e-mail, bordered like an instrument
   panel: labels start-aligned, values end-aligned and tabular. The
   last row is the live status (same [data-biz-status] machinery as
   the header pill, dark-shell variant). #working-hours-details is
   the header pill's jump target - scroll-margin keeps it clear of
   the sticky header. */
.ds-hours {
    margin-block-start: 16px;
    scroll-margin-top: calc(var(--ds-header-h, 64px) + 18px);
}
.ds-hours-title {
    margin: 0 0 8px;
    font-size: .78rem;
    font-weight: 700;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, .45);
}
.ds-hours-table {
    inline-size: 100%;
    border-collapse: collapse;
    font-size: .86rem;
}
.ds-hours-table th,
.ds-hours-table td {
    padding: 7px 10px;
    border: 1px solid rgba(255, 255, 255, .12);
}
.ds-hours-table th {
    font-weight: 500;
    color: #ccc;
    background: rgba(255, 255, 255, .03);
    text-align: start;
}
.ds-hours-table td {
    color: #fff;
    font-weight: 600;
    text-align: end;
    font-variant-numeric: tabular-nums;
}
.ds-hours-status td {
    background: rgba(255, 255, 255, .02);
    text-align: start;
}
/* The dark-shell pill variant used inside the table. The state tints
   below out-specify the base rules, so open/closed still read red and
   green on the dark footer. */
.ds-status.ds-status--footer {
    background: rgba(255, 255, 255, .06);
    border-color: rgba(255, 255, 255, .16);
    color: #f3f4f6;
    cursor: default;
}
.ds-status.ds-status--footer.is-closed {
    background: rgba(193, 27, 23, .18);
    border-color: rgba(232, 139, 136, .45);
}
.ds-status.ds-status--footer.is-open {
    background: rgba(34, 197, 94, .14);
    border-color: rgba(34, 197, 94, .45);
}
/* --- responsive -------------------------------------------------
   992 = the nav's own collapse point: tighten the pill before it can
   squeeze the logo row. 640 = compact state word only ("● OPEN"), the
   spec's mobile form, keeping the actions row inside a 360px screen
   beside the 182px logo and the menu toggle. */
@media (max-width: 992px) {
    .ds-status {
        padding: 5px 9px;
        gap: 6px;
        font-size: 10.5px;
    }
}
@media (max-width: 640px) {
    .ds-status { padding: 5px 8px; }
    .ds-status-nowrap,
    .ds-status-sep,
    .ds-status-time { display: none; }
}
@media (max-width: 480px) {
    .ds-hours-table { font-size: .8rem; }
    .ds-hours-table th,
    .ds-hours-table td { padding: 6px 8px; }
}
/* The pulse is decoration; the dot's colour and the word still state
   it, and the jump falls back to an instant scroll. */
@media (prefers-reduced-motion: reduce) {
    .ds-status.is-open .ds-status-dot { animation: none; }
    html.scroll-smooth { scroll-behavior: auto; }
}

/* ------------------------------------------------------------
   25. POLICY PAGE - the site's own photograph behind the page
   ------------------------------------------------------------
   /policy.php is a single narrow text column (warranty, quality,
   privacy/terms), so it sits on its own photograph the way the
   contact, story and distributors pages do - the same recipe, so
   the pages read as one family.

   The shot (policy.jpg, 1168x912) is a near-white studio image:
   median luminance 249, p5 = 201, only ~0.3% of pixels below 42, so
   the veil runs WHITE, exactly as on distributors - a dark veil over
   a picture this bright only produces flat grey. The mid stop .56
   lands the darkest detail (33) at (1-.56)*33 + .56*255 = 157, the
   same 4.5:1 floor for #333 free copy, while the subject still reads
   (picture cells ~223 against a ~246 ground). It eases whiter toward
   the bottom edge so low copy keeps its contrast as the reader scrolls.

   Attachment is fixed: the photo stays put like wallpaper while the
   page scrolls over it. Phones fall back to scroll - iOS handles
   fixed backgrounds badly - with cover cropping to the subject centre.
   The policy copy now sits on its own dark card (.policy-content), the
   same box language as the story page, instead of running free over the
   photo - so the white-bloom helper (.ds-photo-lift, §23) is no longer
   needed on this page. */
.policy-page {
    position: relative;
    isolation: isolate;
    background-color: #fff;   /* on-tone while the photo loads */
    background-image:
        linear-gradient(180deg,
            rgba(255, 255, 255, .58)   0%,
            rgba(255, 255, 255, .56)  32%,
            rgba(255, 255, 255, .58)  68%,
            rgba(255, 255, 255, .80) 100%),
        url("../images/policy.jpg");
    /* AVIF/WebP where supported, policy.jpg as the fallback. */
    background-image:
        linear-gradient(180deg,
            rgba(255, 255, 255, .58)   0%,
            rgba(255, 255, 255, .56)  32%,
            rgba(255, 255, 255, .58)  68%,
            rgba(255, 255, 255, .80) 100%),
        image-set(
            url("../images/policy.avif") type("image/avif"),
            url("../images/policy.webp") type("image/webp"),
            url("../images/policy.jpg")  type("image/jpeg"));
    background-attachment: fixed, fixed;
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}
@media (max-width: 640px) {
    .policy-page {
        background-attachment: scroll, scroll;
        background-position: center 40%;
    }
}

/* Same box as the story page (section 21): the whole policy sits on one
   rounded, dark card over the page photograph instead of floating as free
   copy. The card is a touch more solid than the story glass (.72 vs .25)
   because the policy photo carries a WHITE veil - a very see-through dark
   card here would only read as flat grey with unreadable white type. Type
   flips to white on the card; the brand tint stays for the accent links. */
.policy-page .policy-content.ds-card {
    background: rgba(18, 20, 24, .72);
    border: 1px solid rgba(255, 255, 255, .22);
    box-shadow: 0 22px 55px rgba(8, 10, 14, .42);
    -webkit-backdrop-filter: blur(8px) saturate(1.05);
    backdrop-filter: blur(8px) saturate(1.05);
    padding: clamp(1.5rem, 3vw, 2.5rem);
    color: #fff;
    text-shadow: 0 1px 3px rgba(8, 10, 14, .5);
}
.policy-page .policy-content.ds-card:hover {
    transform: translateY(-6px);
    box-shadow: 0 30px 66px rgba(8, 10, 14, .5);
    border-color: rgba(255, 255, 255, .34);
}
.policy-page .policy-content :is(h1, h2, h3) { color: #fff; text-wrap: balance; }
.policy-page .policy-content p,
.policy-page .policy-content li { color: rgba(255, 255, 255, .94); }
.policy-page .policy-content strong { color: #fff; }
.policy-page .policy-content .text-primary:not(:is(h1, h2, h3)) { color: var(--ds-red-300); }

/* Structured interior: each block is its own section - a hairline divider
   between them, a brand-red accent on the heading, and room to breathe -
   so the card reads as organised content rather than one long text run. */
.policy-page .policy-content > :not(:first-child) {
    margin-block-start: clamp(1.25rem, 3vw, 1.75rem);
    padding-block-start: clamp(1.25rem, 3vw, 1.75rem);
    border-block-start: 1px solid rgba(255, 255, 255, .16);
}
.policy-page .policy-content h2 {
    color: var(--ds-red-300);
    padding-inline-start: .8rem;
    border-inline-start: 3px solid var(--ds-red-500);
    line-height: 1.25;
}
/* The quality tagline ("Design it right. · Build it consistently. ...")
   gets the brand accent; the small print drops back so it recedes. */
.policy-page .policy-content .policy-principle { color: var(--ds-red-300); }
.policy-page .policy-content .policy-note { color: rgba(255, 255, 255, .62); }

/* ------------------------------------------------------------
   26. PRODUCTS PAGE - the catalogue photograph behind the page
   ------------------------------------------------------------
   /products.php is the catalogue: a search/filter row sits on the
   photograph, then the white product cards cover most of the page.
   Same family recipe as the other photo pages (contact, story,
   distributors, policy) - but the layer is done differently here.

   The shot (products.jpg, 841x1264) is a portrait, lighter than the
   others (median luminance 233) but with darker mid-tones (p5 = 97),
   so the veil runs WHITE and slightly stronger than distributors:
   the mid stop .60 lands even the darkest detail at (1-.60)*0 + .60*255
   ~ 153, while the bright subject still reads. The page is dominated
   by opaque white cards (the photograph shows behind the filter row
   and in the gaps), so the veil eases whiter toward the bottom edge
   where the card grid sits.

   Why a fixed ::before layer instead of background-attachment: fixed?
   This catalogue is long (5000px+ on a phone). With attachment: scroll
   the cover background would size to the whole tall document and zoom
   the portrait ~4x - the "wild zoom" the distributors note warns
   about. A position: fixed layer is always exactly one viewport tall,
   so cover crops the portrait identically on a 390px phone and a
   1440px desktop, with no zoom and no iOS fixed-background jitter.
   The portrait crop is pinned upper-middle (center 30%), the same
   anchor the home page uses for this picture. main carries
   isolation: isolate so the negative-z layer paints over its own
   fallback colour but under the content. */
.products-page {
    position: relative;
    isolation: isolate;
    background-color: #fafafa;   /* on-tone while the photo loads */
}
.products-page::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background-image:
        linear-gradient(180deg,
            rgba(255, 255, 255, .56)   0%,
            rgba(255, 255, 255, .60)  30%,
            rgba(255, 255, 255, .72)  70%,
            rgba(255, 255, 255, .86) 100%),
        url("../images/products.jpg");
    /* AVIF/WebP where supported, products.jpg as the fallback. */
    background-image:
        linear-gradient(180deg,
            rgba(255, 255, 255, .56)   0%,
            rgba(255, 255, 255, .60)  30%,
            rgba(255, 255, 255, .72)  70%,
            rgba(255, 255, 255, .86) 100%),
        image-set(
            url("../images/products.avif") type("image/avif"),
            url("../images/products.webp") type("image/webp"),
            url("../images/products.jpg")  type("image/jpeg"));
    background-size: cover;
    background-position: center 30%;
    background-repeat: no-repeat;
}

/* ------------------------------------------------------------
   27. TOUCH TARGETS
   ------------------------------------------------------------
   Every standalone interactive control gets a 44x44 minimum hit
   area (Apple HIG / WCAG 2.5.5 AAA). These are all additive
   min-* rules, so a control that is already larger is untouched.

   Scope notes:
     - The header toggle, the brand link, the status pill and every
       .ds-btn are sized at all widths: they are the primary controls
       and the extra pixels are invisible next to their own padding.
     - Footer column links, the breadcrumb, the product filter chips
       and the catalogue search field are only enlarged on touch-sized
       viewports (<=992px), which keeps the desktop footer and banner
       rhythm exactly as it is.
     - Inline text links inside a sentence (the footer's mailto, the
       legal row, "Read more" prose links) are deliberately NOT
       touched: WCAG's inline exception covers them and forcing 44px
       would break the line box they sit in. */
.site-header .site-branding a { display: inline-flex; align-items: center; min-block-size: 44px; }
.site-header .ds-status { min-block-size: 44px; }
.ds-btn { min-block-size: 44px; }

/* Catalogue card: below lg the grid is two-up, and the .ds-card wrapper
   padding then stacks on top of the inner content padding - two lots of
   ~20px on a ~134px card squeezes the text to ~60px and wraps every line.
   Drop the wrapper padding here so the image runs edge-to-edge and only
   the inner padding applies. The framed desktop look (>=1024px) is
   untouched. */
@media (max-width: 1023px) {
    .products-page .product-card.ds-card { padding: 0; }
}
@media (max-width: 992px) {
    .page-header .breadcrumb a { display: inline-flex; align-items: center; min-block-size: 44px; }
    .page-header .breadcrumb { min-block-size: 44px; }
    .site-footer .footer-grid ul li a { display: inline-flex; align-items: center; min-block-size: 44px; }
    .site-footer .social-icons a { inline-size: 44px; block-size: 44px; }
    .products-page form button { min-block-size: 44px; }
    .products-page form .ds-input { min-block-size: 44px; }
    .home-products-head .ds-link { display: inline-flex; align-items: center; min-block-size: 44px; }
}

/* ------------------------------------------------------------
   28. TOUCH RELIABILITY
   ------------------------------------------------------------
   On several phones a quick second tap on a menu control was
   swallowed as a double-tap-zoom: the dropdown opened, but tapping
   it again did nothing until the gesture timed out, so it looked
   like it "would not close" and then vanished on the next outside
   tap. touch-action:manipulation drops the 300ms tap delay and the
   zoom gesture on these controls. */
.site-header .ds-navlink,
.site-header .dropdown-trigger,
.site-header .main-navigation a,
.site-header .dropdown-menu button { touch-action: manipulation; }

/* ------------------------------------------------------------
   29. MOBILE REFINEMENTS + SAFE AREAS
   ------------------------------------------------------------
   Additive, mobile-first touches layered on top of the design
   system. Nothing here changes the >=993px desktop rendering:
   every rule is either a max-width query, a style that only
   matters when a phone breadcrumb is long enough to wrap, or a
   no-op on a non-notched screen (the --ds-safe-* tokens from
   section 1 resolve to 0px there). */

/* --- 29a. Breadcrumbs wrap instead of clipping ---------------
   The inner-page banner (.page-header) is overflow:hidden, and its
   breadcrumb is a non-wrapping flex row, so a long product trail
   used to run off the right edge and be clipped on phones. Only
   contact.php had opted into wrapping; this makes it global. The
   crumb centres under the title (matching the column layout the
   banner uses below 992px) and the long "you are here" segment is
   allowed to break mid-word so no crumb pushes the row wider than
   the screen. */
@media (max-width: 640px) {
    .page-header .breadcrumb {
        flex-wrap: wrap;
        justify-content: center;
        row-gap: .25rem;
        max-inline-size: 100%;
    }
    .page-header .breadcrumb .current { overflow-wrap: anywhere; }
}

/* --- 29b. Phone hero: trim the vertical rhythm ---------------
   Scene 01 is a full-screen scene by design, but on a phone the
   panel (title + four-line strapline + CTA) and the five two-line
   icon tiles stacked to roughly 1.7 screens, so the icon row sat
   below the fold and the "one scene, one screen" idea fell apart.
   Tighter variable values and gaps keep the same content while
   bringing the row back up to the fold on typical phones. */
@media (max-width: 600px) {
    .hero {
        --hero-space-title: 16px;
        --hero-space-cta: 20px;
        --hero-icon-size: 52px;
        --hero-icon-title: 14px;
        gap: 1.75rem;
        padding-block-end: 28px;
    }
    .hero__text { font-size: 15px; line-height: 1.25; }
    .hero__cta { padding: 12px 24px; font-size: 16px; }
    .hero-icons { gap: 20px 10px; }
    .hero-icons__item { gap: 10px; }
}
/* Very narrow phones: one more notch down so the five tiles still
   fit two per row leaving the fifth centred without crowding. */
@media (max-width: 380px) {
    .hero { --hero-icon-size: 46px; --hero-icon-title: 13px; }
}

/* --- 29c. Scene index number ----------------------------------
   index.php renders <span class="scene-nav__num">01</span> inside
   each .scene-nav__btn, but nothing styled it specifically - it
   inherited the button's letter-spaced label face. Give it the
   mono index treatment the rest of the system uses (.ds-section-index). */
.scene-nav__num {
    font-family: var(--ds-font-mono);
    font-size: .66rem;
    font-variant-numeric: tabular-nums;
    letter-spacing: .08em;
}

/* --- 29d. Safe-area insets -----------------------------------
   With viewport-fit=cover in header.php, notched phones hand the
   page the full screen: the sticky bar and the fixed floating
   controls can otherwise sit under the status bar, rounded corners
   or the home indicator. The base positions are kept and the
   insets added on top, so non-notched screens (0px) are untouched.
   The header keeps its own background, so the safe padding paints
   white and never shows content underneath it. */
.site-header {
    padding-block-start: var(--ds-safe-t, 0px);
    padding-inline: var(--ds-safe-l, 0px) var(--ds-safe-r, 0px);
}
.site-footer { padding-block-end: var(--ds-safe-b, 0px); }

/* Floating WhatsApp / to-top: lift clear of the home indicator and
   stay inside the corner radius on the inset side. */
[data-track="whatsapp_floating"] {
    inset-inline-end: calc(1rem + var(--ds-safe-r, 0px));
    inset-block-end: calc(1rem + var(--ds-safe-b, 0px));
}
@media (max-width: 767px) {
    [data-track="whatsapp_floating"] { inset-block-end: calc(5rem + var(--ds-safe-b, 0px)); }
}
.ds-to-top {
    inset-inline-end: calc(1rem + var(--ds-safe-r, 0px));
    inset-block-end: calc(5.5rem + var(--ds-safe-b, 0px));
}
@media (max-width: 767px) {
    .ds-to-top { inset-block-end: calc(10rem + var(--ds-safe-b, 0px)); }
}
/* Toast stack (footer.php hands it an inline bottom:80px, so the
   bottom inset has to override that inline value). */
#toast-container {
    inset-inline-end: calc(1rem + var(--ds-safe-r, 0px));
    inset-block-end: calc(80px + var(--ds-safe-b, 0px)) !important;
}

/* --- 29e. Compact header on phones ---------------------------
   On a phone the top bar carries three things on one row: the logo, the
   live status pill, and the menu button. This steps all three - and the
   bar's own padding - down so they sit on ONE line down to ~320px; a wrap
   would drop the pill and button onto a second row and roughly double the
   bar's height. The menu button stays the largest target, and tap height is
   held near 40px (comfortably above the 24px WCAG floor). This block is
   last on purpose: the fixed 44px touch sizing in section 27 sets the same
   selectors earlier, and later source order is what lets these win. */
@media (max-width: 600px) {
    .site-header .header-inner { gap: .5rem; padding-inline: .8rem; padding-block: .55rem; }
    .site-header .site-branding img { block-size: 34px; }
    .site-header .site-branding a { min-block-size: 40px; }
    .site-header .header-actions { gap: .4rem; }
    .site-header .ds-status { min-block-size: 40px; padding: 4px 8px; gap: 5px; font-size: 10px; }
}
/* Very narrow phones (~320px): shave a little more so nothing wraps. */
@media (max-width: 380px) {
    .site-header .header-inner { gap: .4rem; padding-inline: .6rem; }
    .site-header .site-branding img { block-size: 30px; }
    .site-header .ds-status { padding: 3px 6px; gap: 4px; font-size: 9.5px; }
}

/* --- 29g. Trim compositing hints on touch devices -----------
   .ds-btn and .ds-card carry will-change: transform, and every [data-reveal]
   element carries will-change: opacity, transform from section 9. On a desktop
   that keeps a hover or reveal transform on its own layer, which is the point.
   On a phone there is no hover, and a content-heavy page hands dozens of
   elements a permanent composited layer each - memory and paint work the phone
   pays for on every scroll. The hints are dropped on touch-only devices here;
   the transitions themselves are unchanged. [data-parallax] keeps its hint: its
   layers really do move on scroll and there are only a handful of them. */
@media (hover: none), (pointer: coarse) {
    .ds-btn,
    .ds-card,
    .ds-media img,
    [data-reveal] { will-change: auto; }
}

