/* =============================================================================
   Automint — immersive homepage stylesheet
   Industrial-futurist editorial system. Standalone; does not depend on app.css.
   Palette: deep Automint blue foundation (from Blue 800 / Grey 900), cool grey
   light, Automint Blue signal accents (Blue 400 luminous / #1E2171 deep), and
   Automint Grey metallic highlights. Official brand palette — see
   Automint_brand_palette.csv.
   ========================================================================== */

/* Cross-document view transitions — this is a multi-page site, so every nav
   link is a full browser navigation. Opting in lets supporting browsers
   (Chromium-based, at time of writing) animate that navigation instead of
   an instant hard cut, and naming the masthead below keeps it visually
   persistent (it morphs in place rather than cross-fading with the rest of
   the page, since it's identical on every page anyway). Browsers without
   support simply ignore both rules and navigate exactly as before — this
   is pure progressive enhancement, no fallback branch needed. */
@view-transition {
    navigation: auto;
}

/* ----------------------------- Design tokens ----------------------------- */
/* Local fallbacks resized to the web fonts' metrics (size-adjust for width, ascent/descent
   overrides for height), so when Roboto Slab / Source Sans 3 / IBM Plex Mono arrive and
   replace the fallback, text doesn't move or re-wrap: that swap was the page's layout shift
   (CLS 0.26 desktop). One face per weight range, against the real bold fallback (Georgia
   Bold / Arial Bold) rather than a synthesised one, which is wider. Georgia, Arial and
   Consolas are on Windows (Georgia and Arial on macOS / iOS too); elsewhere the stacks fall
   through as before.
   Calibration: Roboto Slab (headings only) on the home hero heading, measured in the
   browser; the Caps face on the site's uppercase eyebrow text; body text on the page
   ledes, measured in the browser; Plex Mono from the two fonts' advance widths.
   Paired with max-widths in em rather than ch: `ch` is the width of the "0", and Georgia's
   old-style 0 is much narrower than Roboto Slab's, so ch-based widths changed on swap too. */
@font-face {
    font-family: 'Roboto Slab Fallback';
    src: local('Georgia Bold'), local('Georgia-Bold');
    font-weight: 500 900;
    size-adjust: 94.10%;
    ascent-override: 111.36%;
    descent-override: 28.80%;
    line-gap-override: 0%;
}
@font-face {
    font-family: 'Source Sans 3 Fallback';
    src: local('Arial'), local('ArialMT');
    font-weight: 100 500;
    size-adjust: 93.42%;
    ascent-override: 109.62%;
    descent-override: 42.82%;
    line-gap-override: 0%;
}
/* IBM Plex Mono (numerals, date lines) on Consolas. Monospace, so size-adjust is simply the
   ratio of the two advance widths (0.600 / 0.550 em). */
@font-face {
    font-family: 'IBM Plex Mono Fallback';
    src: local('Consolas');
    size-adjust: 109.13%;
    ascent-override: 93.92%;
    descent-override: 25.20%;
    line-gap-override: 0%;
}
/* The UPPERCASE eyebrows: capitals scale differently from running text, so they get their
   own fallback family (used only by .section-eyebrow / .hero__eyebrow, via --font-caps),
   calibrated on the site's eyebrow text. A separate family rather than a weight range, so
   the nav links (also weight 500, mixed case) keep the running-text calibration. */
@font-face {
    font-family: 'Source Sans 3 Caps Fallback';
    src: local('Arial'), local('ArialMT');
    size-adjust: 80.40%;
    ascent-override: 127.37%;
    descent-override: 49.75%;
    line-gap-override: 0%;
}
@font-face {
    font-family: 'Source Sans 3 Fallback';
    src: local('Arial Bold'), local('Arial-BoldMT');
    font-weight: 600 900;
    size-adjust: 83.33%;
    ascent-override: 122.89%;
    descent-override: 48.00%;
    line-gap-override: 0%;
}

:root {
    /* Core surfaces — deep Automint blue foundation.
       All solid values are exact official palette colours (see brand CSV). */
    --graphite:      #12164a;   /* Blue 800 — page foundation / primary surface  */
    --graphite-2:    #1e2171;   /* Automint Blue — raised secondary surface      */
    --ink:           #2a2b2d;   /* Grey 900 — deepest neutral (preloader/footer) */

    /* Cool light text + surfaces (Grey 50 / Grey 300 solid; Blue 50 via alpha) */
    --bone:          #fafafb;   /* Grey 50 — primary light text                  */
    --bone-2:        #c7c9cb;   /* Grey 300 — muted light text                   */
    --bone-dim:      rgba(232, 232, 241, 0.66);   /* Blue 50 @ 66%               */
    --bone-faint:    rgba(232, 232, 241, 0.42);   /* Blue 50 @ 42%               */

    /* Brand-blue signal accents */
    --signal:        #5759a3;   /* Blue 400 — luminous accent / CTA fill on dark */
    --signal-2:      #b5b6d3;   /* Blue 200 — brighter lines/text/focus on dark  */
    --signal-strong: #1e2171;   /* Automint Blue — CTA hover (AA with white)     */
    --signal-deep:   #1e2171;   /* Automint Blue — deep fills, glows, gradients  */

    /* Metallic highlight — Automint Grey / Grey 300 (exact palette) */
    --steel:         #9fa1a4;   /* Automint Grey                                 */
    --steel-2:       #c7c9cb;   /* Grey 300                                      */
    --steel-dim:     rgba(159, 161, 164, 0.55);   /* Automint Grey @ 55%         */

    /* Hairlines */
    --line:          rgba(232, 232, 241, 0.12);
    --line-strong:   rgba(232, 232, 241, 0.22);

    /* Typography — three deliberate roles, not one face doing every job.
       Display: Roboto Slab — a mechanical slab serif that reads like a stamped
       nameplate or a catalogue title block, used only for headings.
       Body: Source Sans (the established brand face) for everything read at
       length — trade copy, nav, buttons.
       Mono: IBM Plex Mono, reserved for things that are literally numbers —
       proof figures, sequence markers — so Automint's actual business
       (precise numeric identification) shows up as a design trait. */
    --font-display:  'Roboto Slab', 'Roboto Slab Fallback', 'Georgia', 'Times New Roman', serif;
    --font-body:     'Source Sans 3', 'Source Sans 3 Fallback', 'Source Sans Pro', 'Segoe UI', system-ui, -apple-system, Arial, sans-serif;
    --font-caps:     'Source Sans 3', 'Source Sans 3 Caps Fallback', 'Source Sans Pro', 'Segoe UI', system-ui, -apple-system, Arial, sans-serif;
    --font-mono:     'IBM Plex Mono', 'IBM Plex Mono Fallback', 'SFMono-Regular', Consolas, 'Liberation Mono', monospace;

    /* Spacing scale — a small set of steps, plus semantic aliases for gaps that
       repeat across components. Point every "space after a heading" (etc.) at
       the same alias and it stays consistent everywhere for free. */
    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 20px;
    --space-6: 28px;
    --space-7: 36px;
    --space-8: 48px;
    --space-9: 64px;

    --space-eyebrow: var(--space-5);  /* eyebrow → title/heading */
    --space-lede:    var(--space-6);  /* title → lede paragraph */
    --space-actions: var(--space-7);  /* lede → button row */
    --space-card:    var(--space-6);  /* internal card/panel padding */
    --space-grid:    var(--space-5);  /* card-grid gaps */
    --space-btnrow:  var(--space-4);  /* gap inside a row of buttons */

    /* Type scale — six static steps for running text/UI chrome, plus fluid
       display/figure/lede steps for headings and big numbers. Every heading,
       label and card title pulls from here instead of a one-off number, so a
       change to any tier is consistent everywhere it's used. */
    --text-2xs:  11px;   /* micro meta labels */
    --text-xs:   12px;   /* index/number marks, small tags */
    --text-sm:   13px;   /* tracked eyebrow-style labels */
    --text-base: 14px;   /* nav links, inline text-links */
    --text-md:   15px;   /* prominent small UI: buttons, phone chip */
    --text-body: 0.95rem; /* running copy inside cards/panels */

    --text-root:         17px;
    --text-root-mobile:  16px;

    --text-display-hero:     clamp(2.6rem, 8.2vw, 7.4rem);   /* homepage hero H1 */
    --text-display-hero-int: clamp(2.4rem, 6vw, 5.1rem);     /* interior hero H1 */
    --text-display-section:  clamp(2rem, 4.4vw, 3.9rem);     /* H2 section title */
    --text-display-name:     clamp(1.7rem, 3.4vw, 2.6rem);   /* supplier name */
    --text-display-place:    1.5rem;                         /* static place-name sub-heading */
    --text-display-card-lg:  clamp(1.4rem, 2.6vw, 2.1rem);   /* process step heading */
    --text-display-card:     clamp(1.3rem, 2.2vw, 1.8rem);   /* cap-card / timeline title */
    --text-display-card-sm:  clamp(1.2rem, 1.8vw, 1.5rem);   /* value card title */

    --text-figure-lg: clamp(3rem, 8vw, 6.4rem);       /* CTA band big figure */
    --text-figure:    clamp(2.6rem, 5.5vw, 4.6rem);   /* proof stat value */
    --text-lede-lg:   clamp(1.05rem, 1.5vw, 1.35rem); /* hero lede paragraph */
    --text-lede:      clamp(1rem, 1.3vw, 1.18rem);    /* section lede paragraph */

    /* Layout */
    --edge:          clamp(20px, 5vw, 84px);
    --maxw:          1520px;
    /* How far .masthead__inner sits from the true viewport edge once it's
       centred inside --maxw on wide screens (0 below that width). Shared
       with the wordmark's own backing panel below, so the two can never
       drift apart the way independently-tuned values would. */
    --maxw-offset:   max(0px, (100vw - var(--maxw)) / 2);
    /* Sized up from the original clamp(162px, 18vw, 190px): that size was
       tuned for the logo sitting inside a padded plate, which gave it
       cushion/weight the raw mark loses without a box behind it. */
    --logo-w:        clamp(178px, 19.5vw, 208px);

    /* Motion */
    --ease-out:      cubic-bezier(0.16, 1, 0.3, 1);
    --ease-inout:    cubic-bezier(0.65, 0, 0.35, 1);
}

/* ------------------------------- Reset ----------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

/* Accessible-hiding utility (clip-rect pattern — NOT display:none, so screen
   readers and search crawlers still see the content). Used for the hero's
   static component list; see Pages/Index.cshtml. */
.visually-hidden {
    position: absolute !important;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

html {
    -webkit-text-size-adjust: 100%;
    scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
}

body.home-v2 {
    margin: 0;
    background: var(--graphite);
    color: var(--bone);
    font-family: var(--font-body);
    font-size: var(--text-root);
    line-height: 1.6;
    letter-spacing: 0.005em;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    overflow-x: hidden;             /* guard against any horizontal overflow */
}

img, svg, canvas { display: block; max-width: 100%; }

h1, h2, h3 { margin: 0; font-family: var(--font-display); font-weight: 700; line-height: 1.02; }
p { margin: 0; }

a { color: inherit; text-decoration: none; }

::selection { background: var(--signal); color: #fff; }

/* Hide native cursor only when the custom one is live on fine pointers. */
body.cursor-active { cursor: none; }
body.cursor-active a,
body.cursor-active button { cursor: none; }

/* ----------------------------- Skip link --------------------------------- */
.skip-link {
    position: fixed;
    top: 10px; left: 10px;
    z-index: 200;
    padding: 10px 16px;
    background: var(--signal);
    color: #fff;
    font-weight: 600;
    border-radius: 4px;
    transform: translateY(-140%);
    transition: transform 0.2s var(--ease-out);
}
.skip-link:focus { transform: translateY(0); outline: 2px solid #fff; outline-offset: 2px; }

/* The main landmark is a programmatic focus target for the skip link only
   (tabindex="-1"), so suppress the persistent focus ring browsers would
   otherwise paint around the entire region. Keyboard tab order is unaffected. */
main[tabindex="-1"]:focus { outline: none; }

/* Shared focus treatment */
a:focus-visible, button:focus-visible {
    outline: 2px solid var(--signal-2);
    outline-offset: 3px;
    border-radius: 3px;
}

/* --------------------------- Shared section bits ------------------------- */
.section-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 12px;
    margin: 0 0 var(--space-eyebrow);
    font-family: var(--font-caps);
    font-size: var(--text-sm);
    font-weight: 500;
    letter-spacing: 0.28em;
    text-transform: uppercase;
    color: var(--steel-2);
}
.tick {
    width: 26px; height: 1px;
    background: var(--signal);
    display: inline-block;
    position: relative;
}
.tick::after {
    content: ""; position: absolute; right: 0; top: -2px;
    width: 5px; height: 5px; background: var(--signal); border-radius: 50%;
}
.section-title {
    font-size: var(--text-display-section);
    letter-spacing: -0.02em;
    max-width: 10.27em;
}
.section-lede {
    margin-top: var(--space-lede);
    max-width: 24.54em;
    color: var(--bone-dim);
    font-size: var(--text-lede);
}
.section-lede a { color: var(--signal-2); text-decoration: underline; text-underline-offset: 2px; }
.section-lede a:hover { color: var(--bone); }

/* -------------------------------- Buttons -------------------------------- */
.btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 12px;
    padding: 16px 28px;
    font-family: var(--font-body);
    font-size: var(--text-md);
    font-weight: 600;
    letter-spacing: 0.02em;
    border-radius: 2px;
    border: 1px solid transparent;
    will-change: transform;
    transition: transform 0.4s var(--ease-out), background-color 0.3s, color 0.3s, border-color 0.3s;
}
.btn svg { transition: transform 0.4s var(--ease-out); }
.btn--primary {
    background: var(--signal);
    color: #fff;
    box-shadow: 0 12px 40px -16px rgba(87, 89, 163, 0.95);
}
.btn--primary:hover { background: var(--signal-strong); border-color: var(--signal-2); }
.btn--primary:hover svg { transform: translateX(4px); }
.btn--ghost {
    background: transparent;
    color: var(--bone);
    border-color: var(--line-strong);
}
.btn--ghost:hover { border-color: var(--bone); background: rgba(232, 232, 241, 0.05); }

/* ---------------------------- Card/panel base ----------------------------- */
/* Shared recipe behind every bordered content box on the site — product/value
   cards, location and contact panels. What differs between them (hover lift,
   flex direction, cap-card's own hover glow) is layered on top as its own
   rule; this is the one place the shared border/fill/padding lives. */
.cap-card, .value, .locpanel, .method, .loc {
    padding: var(--space-card);
    border: 1px solid var(--line);
    border-radius: 4px;
    background: linear-gradient(180deg, var(--graphite-2), var(--graphite));
    transition: border-color 0.4s, transform 0.5s var(--ease-out);
}
.cap-card:hover, .value:hover, .locpanel:hover, .method:hover, .loc:hover {
    border-color: var(--line-strong);
}
.value:hover, .method:hover { transform: translateY(-4px); }
.cap-card:hover { transform: translateY(-6px); } /* the widest, most prominent card gets a bigger lift */

/* =============================== Preloader ================================ */
.preloader {
    position: fixed;
    inset: 0;
    z-index: 300;
    display: grid;
    place-items: center;
    background: var(--ink);
    transition: opacity 0.6s var(--ease-out), visibility 0.6s;
}
/* If JS never runs, the preloader must never hide content. */
html.no-js .preloader { display: none; }
.preloader.is-done { opacity: 0; visibility: hidden; pointer-events: none; }

.preloader__inner { text-align: center; }
/* The Automint A brand mark is colour artwork intended for light backgrounds, so it sits
   on a restrained light circular plate for contrast against the deep preloader surface.
   The mark itself is unmodified: no filter, recolour, crop, glow or shadow. */
.preloader__badge {
    display: inline-grid;
    place-items: center;
    width: 132px;
    height: 132px;
    border-radius: 50%;
    background: var(--bone);
}
.preloader__badge img { width: 80px; height: 80px; }
.preloader__sub {
    display: block;
    margin-top: 20px;
    font-size: var(--text-sm);
    letter-spacing: 0.08em;
    color: var(--steel-2);
}
.preloader__bar {
    margin: 30px auto 0;
    width: min(240px, 60vw);
    height: 2px;
    background: var(--line);
    overflow: hidden;
}
.preloader__bar i {
    display: block; height: 100%; width: 0%;
    background: var(--signal);
    transition: width 0.3s linear;
}

/* ============================ Scroll progress ============================ */
.scroll-progress {
    position: fixed;
    top: 0; left: 0; right: 0;
    height: 2px;
    z-index: 120;
    background: transparent;
    pointer-events: none;
}
.scroll-progress i {
    display: block; height: 100%; width: 0%;
    background: linear-gradient(90deg, var(--signal-deep), var(--signal), var(--signal-2));
    transform-origin: left;
}

/* ================================ Masthead =============================== */
.masthead {
    position: fixed;
    top: 0; left: 0; right: 0;
    z-index: 100;
    transition: background-color 0.4s var(--ease-out), border-color 0.4s, backdrop-filter 0.4s,
                transform 0.4s var(--ease-out), visibility 0.4s var(--ease-out);
    border-bottom: 1px solid transparent;
    view-transition-name: site-masthead;
}
.masthead.is-stuck {
    background: rgba(18, 22, 74, 0.72);
    backdrop-filter: blur(14px) saturate(1.2);
    -webkit-backdrop-filter: blur(14px) saturate(1.2);
    border-bottom-color: var(--line);
}
/* Slid off-screen: also remove it from the pointer and focus/AT surface so its
   links cannot be reached by keyboard while hidden. Restored when visible. */
.masthead.is-hidden {
    transform: translateY(-100%);
    visibility: hidden;
    pointer-events: none;
}
.masthead__inner {
    position: relative;
    max-width: var(--maxw);
    margin: 0 auto;
    padding: 20px var(--edge);
    display: flex;
    align-items: center;
    gap: 28px;
    transition: padding 0.4s var(--ease-out);
}
.masthead.is-stuck .masthead__inner { padding-top: 14px; padding-bottom: 14px; }

/* Wordmark surface — the marketing lockup is colour artwork made for light
   backgrounds. A small boxed plate around just the logo used to read as
   oddly detached from the masthead; instead, a full-bleed panel gives it a
   light surface belonging to the masthead itself — Grey 50 flush to the
   viewport edge, fading into the masthead's own dark surface just past the
   logo. Sized from --maxw-offset/--edge/--logo-w — the exact same tokens
   that position the logo itself — rather than independently-tuned values,
   so the two can never drift apart at any viewport width or zoom level. */
.masthead::before {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--bone);
    /* Solid right up to the logo's own right edge (plus a small buffer),
       THEN fade over a further fixed distance — both expressed as absolute
       offsets from the same wordmark-position tokens, not a percentage of
       some separately-guessed width, so the fade always starts exactly
       where the logo ends regardless of viewport size or zoom. */
    --wordmark-edge: calc(var(--maxw-offset) + var(--edge) + var(--logo-w));
    -webkit-mask-image: linear-gradient(to right,
        #000 0,
        #000 calc(var(--wordmark-edge) + 20px),
        transparent calc(var(--wordmark-edge) + 180px));
            mask-image: linear-gradient(to right,
        #000 0,
        #000 calc(var(--wordmark-edge) + 20px),
        transparent calc(var(--wordmark-edge) + 180px));
    pointer-events: none;
}
.wordmark { display: inline-flex; align-items: center; position: relative; }
.wordmark__img { display: block; height: auto; }
.masthead .wordmark__img {
    width: var(--logo-w);   /* comfortably above the 120px marketing minimum */
    /* The mark's own canvas has more clear space above the wordmark than
       below the sub-heading line, so centring the whole image box (via the
       masthead's flex row) reads as sitting slightly low. Nudge up to
       compensate — this is a visual correction for THIS asset's internal
       balance, not a general wordmark rule, so it's scoped to the masthead
       and not applied to the footer lockup (a different file). */
    transform: translateY(-4px);
}

.nav {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-left: auto;
}
.nav__link {
    position: relative;
    padding: 8px 14px;
    font-size: var(--text-base);
    font-weight: 500;
    color: var(--bone-dim);
    transition: color 0.25s;
}
.nav__link::after {
    content: ""; position: absolute; left: 14px; right: 14px; bottom: 4px;
    height: 1px; background: var(--signal);
    transform: scaleX(0); transform-origin: left;
    transition: transform 0.35s var(--ease-out);
}
.nav__link:hover { color: var(--bone); }
.nav__link:hover::after { transform: scaleX(1); }

.nav-call {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    padding: 11px 20px;
    border: 1px solid var(--line-strong);
    border-radius: 2px;
    font-family: var(--font-body);
    font-size: var(--text-base);
    font-weight: 600;
    color: var(--bone);
    white-space: nowrap;
    transition: border-color 0.3s, background-color 0.3s, transform 0.4s var(--ease-out);
    will-change: transform;
}
.nav-call__icon { color: var(--signal); display: inline-flex; }
.nav-call:hover { border-color: var(--signal); background: rgba(87, 89, 163, 0.12); }

.nav-toggle {
    display: none;
    width: 44px; height: 44px;
    margin-left: auto;
    background: none; border: 1px solid var(--line-strong); border-radius: 2px;
    flex-direction: column; align-items: center; justify-content: center; gap: 5px;
    cursor: pointer;
}
.nav-toggle span { width: 20px; height: 1.5px; background: var(--bone); transition: transform 0.3s, opacity 0.3s; }
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }

.mobile-nav {
    border-top: 1px solid var(--line);
    background: rgba(42, 43, 45, 0.96);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
}
.mobile-nav[hidden] { display: none; }
.mobile-nav nav { display: flex; flex-direction: column; padding: 12px var(--edge) 26px; }
.mobile-nav a {
    padding: 15px 0;
    border-bottom: 1px solid var(--line);
    font-family: var(--font-body);
    font-size: 18px;
    color: var(--bone);
}
.mobile-nav__call { color: var(--signal-2) !important; border-bottom: none !important; }

/* ================================= Hero ================================= */
.hero {
    position: relative;
    min-height: 100svh;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: 140px var(--edge) 60px;
    overflow: hidden;
    isolation: isolate;
}
.hero__canvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    z-index: -3;
    opacity: 0;
    transition: opacity 1.2s var(--ease-out);
}
.hero__canvas.is-ready { opacity: 1; }

/* Designed CSS fallback — visible when WebGL is unavailable. */
.hero__fallback {
    position: absolute; inset: 0; z-index: -4;
    background:
        radial-gradient(120% 90% at 72% 42%, rgba(87, 89, 163, 0.20), transparent 55%),
        radial-gradient(90% 80% at 20% 80%, rgba(159, 161, 164, 0.10), transparent 60%),
        linear-gradient(180deg, var(--ink), var(--graphite) 60%, var(--graphite-2));
}
.hero__fallback-rings {
    position: absolute; top: 50%; right: 8%; width: 46vmin; height: 46vmin;
    transform: translateY(-50%);
    background:
        repeating-radial-gradient(circle at 50% 50%,
            transparent 0 14px,
            rgba(159, 161, 164, 0.14) 14px 15px,
            transparent 15px 30px);
    -webkit-mask-image: radial-gradient(circle at 50% 50%, #000 40%, transparent 72%);
    mask-image: radial-gradient(circle at 50% 50%, #000 40%, transparent 72%);
    opacity: 0.9;
}
.hero__fallback-grid {
    position: absolute; inset: 0;
    background-image:
        linear-gradient(var(--line) 1px, transparent 1px),
        linear-gradient(90deg, var(--line) 1px, transparent 1px);
    background-size: 64px 64px;
    -webkit-mask-image: radial-gradient(circle at 60% 50%, #000, transparent 78%);
    mask-image: radial-gradient(circle at 60% 50%, #000, transparent 78%);
    opacity: 0.35;
}
/* Fallback ring/grid art shows by default (incl. no-JS). Hidden only once the
   live WebGL scene is confirmed rendering. */
body.webgl-ok .hero__fallback-rings,
body.webgl-ok .hero__fallback-grid { opacity: 0; }

/* home.js sets body.webgl-failed on every failure path (no WebGL context,
   module import rejected, init returning null, context lost, viewport under
   768px). Until now that class matched NO selector anywhere in the stylesheet —
   the fallback happened to work purely because it is the DEFAULT state, so the
   documented mechanism and the real one were inverted and the class every
   future maintainer would grep for was inert.

   These rules make it do the job it claims to. They also make recovery
   explicit: if a context is lost AFTER the scene became ready, this restores
   the fallback by itself rather than depending on .is-ready and .webgl-ok
   being removed in the right order. */
body.webgl-failed .hero__canvas { opacity: 0; }
body.webgl-failed .hero__fallback-rings { opacity: 0.9; }
body.webgl-failed .hero__fallback-grid { opacity: 0.35; }

/* ---------------------------------------------------------------------------
   Mobile-lite hero tier (body.webgl-lite, set by home.js on phones/tablets).

   The rig runs, but as background texture BEHIND the hero copy rather than as a
   subject composed beside it. There is no beside: measured at 390x844 the copy
   runs from y=120 to y=765 across the full column, so the entire first viewport
   is text. Legibility therefore cannot come from moving the subject out of the
   way — the camera's composition bias is switched off for this tier — and has to
   come from these two rules plus the lower tone-mapping exposure in
   hero/index.js QUALITY_LITE.

   Partial canvas opacity rather than a flat overlay on top of it: the canvas
   sits above .hero__fallback (z-index -4), which is the designed CSS gradient
   composition, so what the rig blends INTO is that artwork rather than a grey
   wash. The two levers are complementary and both are needed — exposure stops
   specular highlights blowing out, opacity sets how far forward the rig sits. */
body.webgl-lite .hero__canvas.is-ready { opacity: 0.72; }

/* The hero's own vignette, plus a vertical scrim to hold contrast under the
   copy. Extending the existing pseudo-element rather than adding a new layer:
   it already sits at z-index -1, i.e. above the canvas and grain and below the
   content, which is exactly where a scrim belongs.

   Qualified with .webgl-ok, not .webgl-lite alone. `webgl-lite` records the tier
   home.js CHOSE, which it does before knowing whether WebGL will actually
   initialise; `webgl-ok` is set from onFirstFrame and dropped again on context
   loss, so it records what is genuinely on screen. Without the qualifier a phone
   with no WebGL — or one that lost its context — would keep a scrim laid over
   the designed CSS fallback, dimming artwork that has nothing behind it to
   scrim. The canvas rule above gets this for free from `.is-ready`. */
body.webgl-lite.webgl-ok .hero::after {
    background:
        linear-gradient(180deg,
            rgba(42, 43, 45, 0.74) 0%,
            rgba(42, 43, 45, 0.60) 52%,
            rgba(42, 43, 45, 0.70) 100%),
        radial-gradient(120% 100% at 50% 40%, transparent 55%, rgba(42, 43, 45, 0.72) 100%);
}

/* Film grain overlay */
.hero__grain {
    position: absolute; inset: 0; z-index: -2; pointer-events: none;
    opacity: 0.05;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
    mix-blend-mode: overlay;
}
/* Vignette to seat the sculpture */
.hero::after {
    content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
    background: radial-gradient(120% 100% at 50% 40%, transparent 55%, rgba(42, 43, 45, 0.72) 100%);
}

/* Hero component callouts — a DOM overlay positioned each frame by
   projecting a 3D anchor to screen space (see /js/hero/callouts.js).
   Purely decorative and transient; aria-hidden in markup. */
.hero__callouts {
    position: absolute;
    inset: 0;
    z-index: 1;
    overflow: hidden;
    pointer-events: none;
}
.hero-callout {
    position: absolute;
    /* Placed by callouts.js through the `translate` property (no layout per frame). */
    left: 0; top: 0;
    display: flex;
    align-items: center;
    gap: 8px;
    transform: translate(0, -50%);
    opacity: 0;
    transition: opacity 0.5s var(--ease-out), transform 0.5s var(--ease-out);
    white-space: nowrap;
}
/* Only the 6-8 labels actually on screen get their own compositor layer, not all 20. */
.hero-callout.is-shown { opacity: 1; will-change: translate, opacity; }
/* Mirrored variant. Applied by callouts.js to anchors sitting in the right of
   frame, where a label growing rightwards would run off the viewport. Leader
   line and label swap sides and the whole row is offset left of the anchor, so
   the leader still touches the component it points at. */
.hero-callout--flip {
    flex-direction: row-reverse;
    transform: translate(-100%, -50%);
}
.hero-callout::before {
    content: "";
    width: 6px; height: 6px;
    flex: none;
    border-radius: 50%;
    background: var(--signal-2);
}
.hero-callout__leader {
    display: block;
    width: 26px; height: 1px;
    background: var(--line-strong);
}
.hero-callout__label {
    font-family: var(--font-display);
    font-size: 11px; font-weight: 500;
    letter-spacing: 0.14em; text-transform: uppercase;
    color: var(--bone-dim);
    background: rgba(18, 22, 74, 0.55);
    padding: 3px 8px;
    border: 1px solid var(--line);
    border-radius: 2px;
}

.hero__content { max-width: var(--maxw); margin: 0 auto; width: 100%; }
.hero__eyebrow {
    display: inline-flex; align-items: center; gap: 12px;
    font-family: var(--font-caps);
    font-size: var(--text-sm); font-weight: 500;
    letter-spacing: 0.22em; text-transform: uppercase;
    color: var(--steel-2);
    margin-bottom: var(--space-eyebrow);
}
.hero__heading {
    font-size: var(--text-display-hero);
    letter-spacing: -0.03em;
    line-height: 0.96;
    max-width: 9.12em;
}
.hero__line { display: block; }
.hero__accent { color: var(--signal-2); }
.hero__lede {
    margin-top: var(--space-lede);
    max-width: 21.71em;
    font-size: var(--text-lede-lg);
    color: var(--bone-dim);
}
.hero__actions { margin-top: var(--space-actions); display: flex; flex-wrap: wrap; gap: var(--space-btnrow); }

.hero__meta {
    max-width: var(--maxw);
    margin: 64px auto 0;
    width: 100%;
    display: flex;
    flex-wrap: wrap;
    gap: clamp(24px, 6vw, 90px);
    padding-top: 26px;
    border-top: 1px solid var(--line);
}
.hero__meta-item { display: flex; flex-direction: column; gap: 4px; }
.hero__meta-k {
    font-size: var(--text-2xs); letter-spacing: 0.2em; text-transform: uppercase; color: var(--steel-dim);
}
.hero__meta-v { font-family: var(--font-display); font-weight: 500; color: var(--bone); font-size: var(--text-md); }

.hero__scrollcue {
    position: absolute;
    left: var(--edge); bottom: 28px;
    display: inline-flex; align-items: center; gap: 12px;
    font-size: var(--text-2xs); letter-spacing: 0.24em; text-transform: uppercase;
    color: var(--bone-faint);
}
.hero__scrollcue i {
    width: 1px; height: 34px; background: var(--line-strong); position: relative; overflow: hidden;
}
.hero__scrollcue i::after {
    content: ""; position: absolute; top: 0; left: 0; width: 100%; height: 40%;
    background: var(--signal);
    animation: cueSlide 1.9s var(--ease-inout) infinite;
}

/* ---------------------------------------------------------------------------
   Laptop widths (1025-1599px, mouse/trackpad): the rig BELOW the copy.
   Beside the copy there is no room at these widths — the headline runs to ~74% of
   the frame at 1280 and the lede past the fold — so every component callout was
   suppressed (0 at 1280 and 1440). Here the hero reserves a full-width band under
   the copy and the canvas + callout layer move into it; index.js then centres the
   subject instead of biasing it right (setSafeRect: copy above the canvas).
   Only where home.js runs the full tier (1025px and up with a fine primary pointer;
   lite is < 1024 or coarse), so tablets / phones keep the lite background treatment
   and nothing re-lays out when the tier is decided. Reduced-motion visitors never get
   the animation, so they keep the original layout rather than an empty band. (Other
   failure paths — no WebGL, a failed load — leave the band showing the CSS fallback
   art; collapsing it then would itself be a layout shift.) 1600px and up keep the
   side-by-side composition.
   --hero-stage-gap separates the band from the .hero__meta row above it, with the
   scroll cue (~34px tall) sitting inside that gap, clear of both. */
@media (min-width: 1025px) and (max-width: 1599px) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
    .hero {
        --hero-stage-h: clamp(420px, 64vh, 640px);
        --hero-stage-gap: 76px;
        padding-bottom: calc(var(--hero-stage-h) + var(--hero-stage-gap));
    }
    .hero__canvas,
    .hero__callouts {
        inset: auto 0 0 0;
        height: var(--hero-stage-h);
    }
    .hero__scrollcue { bottom: calc(var(--hero-stage-h) + 21px); }
}
@keyframes cueSlide { 0% { transform: translateY(-100%); } 60%, 100% { transform: translateY(250%); } }

/* ============================ Process narrative ========================== */
/* No padding-top here: .process__sticky already carries its own generous
   vertical padding (clamp(90px, 12vh, 130px) below) in every mode, driven
   or not, so an extra top offset here only stacked on top of it — up to
   ~220px of combined clearance before any visible text. */
.process { position: relative; background: var(--graphite); }
.process__sticky {
    display: grid;
    grid-template-columns: 1.05fr 0.95fr;
    align-items: center;
    gap: clamp(30px, 5vw, 80px);
    max-width: var(--maxw);
    margin: 0 auto;
    padding: clamp(90px, 12vh, 130px) var(--edge);
}
/* Scroll-driven mode: JS adds .is-driven on capable desktops only.
   Without it (no JS, reduced motion, coarse pointer) the section flows normally. */
.process.is-driven { min-height: 300vh; }
.process.is-driven .process__sticky {
    position: sticky;
    top: 0;
    /* min-height only, deliberately no `height`. The copy column shows all
       three steps at once (only opacity differs), so on shorter viewports —
       and especially under browser zoom, which re-lays out at a smaller
       effective vh while rem/clamp-based text stays the same CSS-pixel size —
       that content can be taller than one viewport height. A hard `height`
       clipped nothing (default overflow: visible) and let the excess spill
       outside the sticky box, painting over the hero section still visible
       behind it. Letting the box grow keeps the content contained and simply
       pushes the rest of the page down instead of overlapping upward.
       align-content: center still centers things when there IS spare room. */
    min-height: 100vh;
    align-content: center;
}

.process__stage {
    position: relative;
    aspect-ratio: 1 / 1;
    border: 1px solid var(--line);
    border-radius: 4px;
    background:
        radial-gradient(80% 80% at 50% 40%, rgba(159, 161, 164, 0.06), transparent 70%),
        var(--graphite-2);
    overflow: hidden;
}
.process__canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
.process__index {
    position: absolute; top: 18px; left: 20px;
    display: flex; gap: 10px;
    font-family: var(--font-mono); font-size: var(--text-xs); letter-spacing: 0.1em;
    color: var(--bone-faint);
}
.process__index span { transition: color 0.4s; }
.process__index span.is-active { color: var(--signal); }

.process__steps { list-style: none; margin: 40px 0 0; padding: 0; }
.process__step {
    padding: 26px 0 26px 30px;
    border-left: 2px solid var(--line);
    opacity: 0.42;
    transition: opacity 0.5s var(--ease-out), border-color 0.5s;
}
.process__step.is-active { opacity: 1; border-left-color: var(--signal); }
.process__step-no {
    font-family: var(--font-mono); font-size: var(--text-xs); letter-spacing: 0.2em; color: var(--steel-2);
}
.process__step h3 { margin: 10px 0 12px; font-size: var(--text-display-card-lg); letter-spacing: -0.01em; }
.process__step p { color: var(--bone-dim); max-width: 20.77em; }

/* ============================== Capabilities ============================= */
.capabilities {
    max-width: var(--maxw);
    margin: 0 auto;
    padding: clamp(90px, 12vh, 160px) var(--edge);
    border-top: 1px solid var(--line);
}
.capabilities__head { max-width: 29.26em; margin-bottom: 64px; }
.capabilities__grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: var(--space-grid);
}
.cap-card {
    grid-column: span 2;
    display: flex;
    flex-direction: column;
    min-height: 340px;
    overflow: hidden;
    position: relative;
}
.cap-card::before {
    content: ""; position: absolute; inset: 0;
    background: radial-gradient(70% 60% at 80% 0%, rgba(87, 89, 163, 0.14), transparent 60%);
    opacity: 0; transition: opacity 0.5s;
}
.cap-card:hover::before { opacity: 1; }
.cap-card--wide { grid-column: span 4; flex-direction: row; align-items: center; gap: 40px; }

.cap-card__art {
    color: var(--steel);
    width: 96px; height: 96px;
    margin-bottom: auto;
    transition: color 0.5s, transform 0.6s var(--ease-out);
}
.cap-card:hover .cap-card__art { color: var(--signal-2); transform: translateY(-2px); }
.cap-card__art--wide { width: clamp(200px, 30%, 320px); height: auto; flex-shrink: 0; }
.cap-card__art--wide svg { width: 100%; height: auto; }

.cap-card__body { margin-top: 26px; }
.cap-card--wide .cap-card__body { margin-top: 0; }
.cap-card__no {
    font-family: var(--font-mono); font-size: var(--text-xs); letter-spacing: 0.24em; color: var(--steel-dim);
}
.cap-card h3 { margin: 12px 0 12px; font-size: var(--text-display-card); letter-spacing: -0.01em; }
.cap-card p { color: var(--bone-dim); font-size: var(--text-body); max-width: 21.71em; }
.cap-card__link {
    display: inline-flex; align-items: center; gap: 8px; margin-top: 18px;
    font-family: var(--font-body); font-weight: 600; font-size: var(--text-base); color: var(--signal-2);
}
.cap-card__link span { transition: transform 0.35s var(--ease-out); }
.cap-card__link:hover span { transform: translateX(5px); }

/* ================================= Proof ================================ */
.proof {
    border-top: 1px solid var(--line);
    background:
        radial-gradient(80% 120% at 100% 0%, rgba(87, 89, 163, 0.09), transparent 55%),
        var(--graphite);
}
.proof__inner, .proof__head, .proof__grid { max-width: var(--maxw); margin-left: auto; margin-right: auto; }
.proof__head { padding: clamp(90px, 12vh, 150px) var(--edge) 50px; }
.proof__grid {
    padding: 0 var(--edge) clamp(90px, 12vh, 150px);
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 1px;
    background: var(--line);
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
}
.stat {
    background: var(--graphite);
    padding: 46px 30px 40px;
    display: flex; flex-direction: column; gap: 16px;
}
.stat__value {
    font-family: var(--font-mono);
    font-size: var(--text-figure);
    font-weight: 600;
    letter-spacing: -0.03em;
    line-height: 1;
    color: var(--bone);
    font-variant-numeric: tabular-nums;
}
.stat__label { color: var(--bone-dim); font-size: var(--text-body); max-width: 11.33em; }

/* ================================ Contact =============================== */
.contact {
    border-top: 1px solid var(--line);
    background: linear-gradient(180deg, var(--graphite), var(--ink));
}
.contact__inner {
    max-width: var(--maxw);
    margin: 0 auto;
    padding: clamp(90px, 13vh, 170px) var(--edge);
    display: grid;
    grid-template-columns: 1.1fr 0.9fr;
    gap: clamp(40px, 6vw, 100px);
    align-items: start;
}
.contact__actions { margin-top: var(--space-actions); display: flex; flex-wrap: wrap; gap: var(--space-btnrow); }
/* Single column, not a 2-up grid: this sits in the narrower half of a
   1.1fr/0.9fr split, so a third card (all three Automint sites, not just
   two) stacks cleanly instead of being squeezed into cramped columns. */
.contact__locations { display: grid; grid-template-columns: 1fr; gap: var(--space-grid); }
.loc { font-style: normal; display: flex; flex-direction: column; gap: 10px; }
.loc__tag { font-size: var(--text-2xs); letter-spacing: 0.22em; text-transform: uppercase; color: var(--signal-2); }
.loc__name { font-family: var(--font-display); font-size: var(--text-display-place); font-weight: 600; color: var(--bone); }
.loc__lines { color: var(--bone-dim); font-size: var(--text-body); line-height: 1.7; }
.loc__note { color: var(--bone-dim); font-size: var(--text-body); line-height: 1.6; }
.loc__link {
    margin-top: 4px; font-family: var(--font-body); font-weight: 600; font-size: var(--text-base); color: var(--steel-2);
    transition: color 0.3s;
}
.loc__link:hover { color: var(--bone); }

/* ================================ Footer ================================ */
.footer { background: var(--ink); border-top: 1px solid var(--line); view-transition-name: site-footer; }
.footer__inner {
    max-width: var(--maxw);
    margin: 0 auto;
    padding: 48px var(--edge);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 24px 40px;
}
/* Footer identity: the supplied official inverted lockup (includes "Limited"), light
   artwork suited to the dark footer. Displayed above the 150px minimum, uncropped. */
.wordmark--footer .wordmark__img { width: clamp(168px, 22vw, 208px); }
.footer__nav { display: flex; flex-wrap: wrap; gap: 24px; margin-left: 8px; }
.footer__nav a { font-size: var(--text-base); color: var(--bone-dim); transition: color 0.25s; }
.footer__nav a:hover { color: var(--bone); }
.footer__legal {
    margin-left: auto;
    display: flex; flex-wrap: wrap; gap: 8px;
    font-size: var(--text-sm); color: var(--bone-dim);
}
.footer__sep { color: var(--line-strong); }
.footer__legal a { color: var(--bone-dim); text-decoration: underline; text-underline-offset: 2px; transition: color 0.25s; }
.footer__legal a:hover { color: var(--bone); }
/* Routes the occasional trade visitor to the right tool without competing with
   the site's own (non-existent) call to action — deliberately quiet. */
.footer__trade {
    flex-basis: 100%;
    font-size: var(--text-sm);
    color: var(--bone-faint);
}
.footer__trade a { color: var(--steel-2); text-decoration: underline; text-underline-offset: 2px; }
.footer__trade a:hover { color: var(--bone); }
/* Statutory company details (Companies Act): small, in the footer's quiet text style. */
.footer__statutory {
    flex-basis: 100%;
    display: flex; flex-wrap: wrap; gap: 4px 8px;
    font-size: var(--text-xs);
    color: var(--bone-dim);
}

/* ============================== Custom cursor =========================== */
.cursor, .cursor-dot {
    position: fixed; top: 0; left: 0; z-index: 400; pointer-events: none;
    border-radius: 50%;
    opacity: 0;
    transition: opacity 0.3s, width 0.3s var(--ease-out), height 0.3s var(--ease-out),
                background-color 0.3s, border-color 0.3s;
}
.cursor { width: 34px; height: 34px; border: 1px solid var(--steel-2); transform: translate(-50%, -50%); }
.cursor-dot { width: 5px; height: 5px; background: var(--signal); transform: translate(-50%, -50%); }
body.cursor-active .cursor, body.cursor-active .cursor-dot { opacity: 1; }
body.cursor-hover .cursor { width: 56px; height: 56px; border-color: var(--signal); background: rgba(87, 89, 163, 0.14); }
body.cursor-hover .cursor-dot { opacity: 0; }

/* ============================ Reveal choreography ======================= */
[data-reveal] {
    opacity: 0;
    transform: translateY(28px);
    transition: opacity 0.8s var(--ease-out), transform 0.8s var(--ease-out);
    will-change: opacity, transform;
}
[data-reveal].is-revealed { opacity: 1; transform: none; }
/* Without JS, never hide content. */
html.no-js [data-reveal] { opacity: 1; transform: none; }

/* Kinetic heading setup (chars injected by JS) */
.hero__heading .word { display: inline-block; white-space: nowrap; }
.hero__heading .char {
    display: inline-block;
    opacity: 0;
    transform: translateY(0.5em) rotate(2deg);
    transition: opacity 0.7s var(--ease-out), transform 0.7s var(--ease-out);
}
.hero__heading.is-lit .char { opacity: 1; transform: none; }

/* ============================== Responsive ============================== */
@media (max-width: 1180px) {
    .nav { gap: 0; }
    .nav__link { padding: 8px 10px; font-size: var(--text-sm); }
}
@media (max-width: 1024px) {
    .nav, .nav-call { display: none; }
    .nav-toggle { display: flex; }

    /* No-JS fallback: the hamburger cannot toggle without script, so expose the
       mobile navigation as a static header and hide the inert toggle. The UA
       stylesheet applies [hidden]{display:none} — override it explicitly. */
    html.no-js .masthead { position: static; }
    html.no-js .nav-toggle { display: none; }
    html.no-js .mobile-nav[hidden] { display: block !important; }

    .process__sticky { grid-template-columns: 1fr; }
    .process.is-driven { min-height: 0; }
    .process.is-driven .process__sticky { position: static; height: auto; min-height: 0; }
    .process__stage { max-width: 460px; margin: 0 auto 10px; width: 100%; }
    .capabilities__grid { grid-template-columns: repeat(2, 1fr); }
    .cap-card { grid-column: span 1; }
    .cap-card--wide { grid-column: span 2; }
    .contact__inner { grid-template-columns: 1fr; }
    .proof__grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 680px) {
    body.home-v2 { font-size: var(--text-root-mobile); }
    .hero { padding-top: 120px; }
    .hero__meta { gap: 20px 40px; }
    .capabilities__grid { grid-template-columns: 1fr; }
    .cap-card, .cap-card--wide { grid-column: span 1; }
    .cap-card--wide { flex-direction: column; align-items: flex-start; gap: 24px; }
    .cap-card__art--wide { width: 60%; }
    .proof__grid { grid-template-columns: 1fr; }
    .footer__legal { margin-left: 0; }
    .btn { padding: 14px 22px; }
}

/* ========================= Reduced motion path ========================= */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
    }
    .preloader { display: none; }
    [data-reveal] { opacity: 1; transform: none; }
    .hero__heading .char { opacity: 1; transform: none; }
    .hero__scrollcue i::after { display: none; }
    .process__sticky { position: static; min-height: 0; }
    .hero__canvas { display: none; }
    .hero__fallback-rings, .hero__fallback-grid { opacity: 0.6 !important; }
    .process.is-driven { min-height: 0; }
    .process.is-driven .process__sticky { position: static; height: auto; min-height: 0; }
}
