/* RAFly Unified Core Stylesheet Bundle — Generated 2026-09-07 11:51:50 */

/* --- File: css/00-tokens.css --- */
/* ==========================================================================
   LOGO BLUE — design tokens. The single source of truth.
   ==========================================================================

   The palette is sampled from logo.png, not chosen beside it. The three
   colours that came out of the mark are #0a63ff (the blue), #1bba3a (the
   green) and a near-black that resolves to #050f33 once it is neutralised
   against the blue rather than against grey. Everything below is built from
   those three.

   THE GROUND IS COOL PAPER. Not white and not warm. #ffffff is a screen
   default; #f6f8fc is a decision, and it is the half-step of blue that makes
   the accent look like it belongs to the page instead of sitting on top of
   it. Three tones exist so a long page can have CHAPTERS without hard section
   edges — the eye reads a half-step of tone as "new part" far more quietly
   than a rule or a colour block does.

   This REPLACES the machined-paper set (warm #f7f6f2 paper, #14161a ink,
   #1b4fd8 blue). That palette was not wrong; it was a different direction,
   and it lost to one Naveen approved against a running prototype rather than
   against a description. The type scale, spacing, radii, elevation structure,
   motion tiers and grain blocks below are carried over unchanged, because
   none of them encode a direction and rewriting them would be pure diff noise.

   MEASURED, NOT CHOSEN BY EYE
   ---------------------------
   Every ratio below is computed per WCAG relative luminance,
   (L1 + .05) / (L2 + .05), against the real ground it sits on, by
   `node inc/tools/contrast.mjs` — which runs on the values BEFORE they are
   written here, so a failure is an edit rather than a rewrite.
   inc/tools/shoot.mjs then re-measures the RENDERED page, and it is the
   authority: it knows about the grain overlay, inherited opacity and the
   ancestor grounds that this file cannot see.

   THE ONE RULE MOST LIKELY TO BE BROKEN LATER
   -------------------------------------------
   THE LOGO BLUE IS NOT A TEXT COLOUR. --blue #0a63ff measures 4.66:1 on
   paper-01 and only 4.07:1 on paper-03. It is a FILL. The accent as text is
   --blue-ink #0b52d8 (6.15 / 5.83 / 5.37), and --accent-fg points at it.

   This inverts the previous palette's failure mode, which is what makes it
   dangerous. The machined-paper blue was 6.15:1 everywhere, so `color:
   var(--blue)` was safe everywhere and the habit is in the codebase. It is
   now a bug on every well, input and table stripe on the site. Any new
   `color: var(--blue)` is wrong; read --accent-fg.

   And the mirror still holds on the dark ground:
     --blue-ink       5.37:1 on paper-03 /  2.86:1 on the chapter — unreadable
     --accent-chapter 2.43:1 on paper-01 /  7.25:1 on the chapter
   --blue itself is 3.78:1 on the chapter, which clears the 3:1 non-text
   minimum — so the FILL is the one thing that works on both grounds, and
   white on it is 4.95:1 either way. That is why --accent does not have to be
   remapped in the chapter and --accent-fg does.
   Each is dangerous exactly where the other is safe, and a component that
   hardcodes either looks completely correct on the ground it was written on.
   css/08-ground.css remaps --accent-fg per ground; components read that.
   ========================================================================== */

:root {
    /* --- Paper: the three grounds ---------------------------------------
       01 is the page. 02 marks a chapter. 03 is a recess — a well, an input,
       a table stripe. Anything below 03 stops reading as paper and starts
       reading as a grey box, which is why there is no 04 in the scale of
       grounds; --paper-4 exists only as a pressed state.

       Each step keeps the same blue cast rather than desaturating toward
       grey. A ground that loses its hue as it darkens reads as dirt on the
       page, and the recess is exactly where the accent has least contrast to
       spare — which is why --blue's 4.07:1 on paper-03 is the number this
       whole file is organised around. */
    --paper:        #f6f8fc;   /* paper-01 — the page ground                */
    --paper-2:      #eef2fa;   /* paper-02 — chapter ground                 */
    --paper-3:      #e3e9f6;   /* paper-03 — wells, inputs, stripes         */
    --paper-4:      #d5ddef;   /* pressed / active well                     */

    /* --- Ink ------------------------------------------------------------ */
    --ink:          #050f33;   /* display, headings   17.59 / 16.67 / 15.37 */
    --ink-2:        #3a4468;   /* body                 8.95 /  8.48 /  7.82 */
    --ink-3:        #5b6384;   /* captions, metadata   5.54 /  5.25 /  4.84 */
    --ink-4:        #767ea6;   /* soft subtitles                           */

    --line:         #e2e7f2;   /* hairline — cool, so it belongs to paper   */
    --line-strong:  #cbd3e6;

    /* --- The accent ------------------------------------------------------
       ONE accent, and it is the mark's own blue. It is the CTA, the link,
       the active state and the lit edge in the 3-D scene — the same blue in
       the render and in the interface, which is what makes the object read
       as part of the brand rather than as an illustration dropped onto it.

       TWO VARIABLES, NOT ONE, and the split is the point:
         --blue      the FILL. Buttons, bars, the packet on a circuit trace,
                     the glow. Never text, never a border that carries
                     meaning: 4.07:1 on paper-03 clears neither the 4.5
                     text minimum nor, on a well, much headroom over the 3.0
                     non-text one.
         --blue-ink  the accent AS TEXT. Links, active labels, the focus
                     ring. 6.15 / 5.83 / 5.37, safe on all three papers.

       The name stays --blue because ~200 call sites across nine stylesheets
       already read it. Renaming the variable and changing the colour in the
       same pass is how a redesign acquires a week of unrelated diff noise. */
    --blue:         #0a63ff;   /* 4.66 / 4.41 / 4.07 — FILL ONLY            */
    --blue-ink:     #0b52d8;   /* 6.15 / 5.83 / 5.37 — the accent as TEXT   */
    --blue-hover:   #0842ad;   /* 8.25 / 7.82 / 7.21                        */
    --blue-deep:    #0230c6;   /* gradient mid — white on it is 9.37:1      */
    --blue-abyss:   #001a7a;   /* gradient deep end, and the aurora's floor */
    --blue-tint:    #e8effe;   /* pale accent wash — a GROUND, never text   */
    --blue-tint-2:  #d5e2fd;

    /* The logo green. It is 2.43:1 on paper — below even the 3:1 NON-TEXT
       minimum — so on paper it can be a FILL and nothing else: not a tick,
       not a status dot, not a border. Ink on it measures 7.24:1, and on the
       chapter ground it measures 7.24:1 as text. Those are its only two
       legal uses, and --success-ink exists because "green text" on paper has
       to be a different green. */
    --green:        #1bba3a;

    /* --- Semantic -------------------------------------------------------- */
    --pill:         #050f33;   /* the ink pill CTA — the same ink as display */
    --pill-fg:      #f6f8fc;   /* the pill LABEL — 18.71:1, inverts in a chapter */
    --pill-hover:   #0c1a45;
    --wash:         rgba(5, 15, 51, .04);   /* hover wash, flips light in a chapter */
    --sheen-face:   rgba(255, 255, 255, .78);
    --sheen-edge:   rgba(255, 255, 255, .92);
    --success-ink:  #0e6f31;   /* success AS TEXT   5.52 / — / 4.82         */
    --success:      var(--success-ink);  /* legacy name, same value          */
    --danger:       #c62828;   /* 5.29 / — / 4.62                            */
    --warning:      #96560a;   /* 5.44 / — / 4.75                            */
    --whatsapp:     #128c4a;   /* brand mark, non-text — 4.05                */

    /* --- On a coloured (accent) band -------------------------------------
       White on --blue is 4.95:1, which passes but has under half a point of
       headroom. Anything smaller than body text on a blue fill should use
       --blue-deep as the fill instead (9.37:1) rather than shrinking the
       margin further. */
    --on-dark-1:    #ffffff;
    --on-dark-2:    #d3e0ff;
    --on-dark-3:    #a6bffb;
    --on-dark-line: rgba(255, 255, 255, .20);

    /* --- Type -----------------------------------------------------------
       No new font files. Space Grotesk and Inter are already self-hosted
       variable faces and cover the whole scale; the monospaced tier below is
       a system stack, so the technical voice this design leans on costs zero
       additional bytes. */
    --font-display: 'Space Grotesk', 'Segoe UI', system-ui, sans-serif;
    --font-body:    'Inter', 'Segoe UI', system-ui, sans-serif;
    --font-mono:    ui-monospace, 'SFMono-Regular', 'Cascadia Mono', Consolas, monospace;

    /* DISPLAY XL is the hero and only the hero. If a second element ever
       needs this size, the hero has stopped being the loudest thing here. */
    --fs-display-xl: clamp(3rem, 0.85rem + 9vw, 7rem);
    --fs-display: clamp(2.75rem, 1.05rem + 7.1vw, 5.75rem);
    --fs-h1:      clamp(2.125rem, 1.30rem + 3.45vw, 3.75rem);
    --fs-h2:      clamp(1.75rem,  1.20rem + 2.30vw, 3rem);
    --fs-h3:      clamp(1.375rem, 1.15rem + 0.94vw, 1.875rem);
    --fs-h4:      clamp(1.125rem, 1.05rem + 0.31vw, 1.3125rem);
    --fs-lead:    clamp(1.0625rem, 1.00rem + 0.31vw, 1.25rem);
    --fs-base:    clamp(1rem, 0.98rem + 0.10vw, 1.0625rem);
    --fs-sm:      0.9375rem;
    --fs-xs:      0.8125rem;
    --fs-2xs:     0.75rem;

    /* The METADATA tier. Small, uppercase, wide-tracked, monospaced — the
       voice of a specification sheet. It carries part numbers, durations and
       section indices, and it is a large part of why the page reads as
       engineering rather than as marketing. It must never carry a sentence,
       and it must never carry a number the site cannot back.

       It is also SMALL TEXT at every size in this tier, so it reads --ink-3
       and never --ink-4. */
    --fs-meta:    0.6875rem;
    --tr-meta:    0.18em;

    --lh-display: 0.94;
    --lh-tight:   1.06;
    --lh-heading: 1.16;
    --lh-body:    1.65;

    --tr-display: -0.038em;
    --tr-heading: -0.024em;
    --tr-eyebrow:  0.14em;

    --fw-body:    400;
    --fw-medium:  500;
    --fw-semi:    600;
    --fw-bold:    700;

    /* --- Space ----------------------------------------------------------- */
    --sp-1: 0.25rem;  --sp-2: 0.5rem;   --sp-3: 0.75rem;  --sp-4: 1rem;
    --sp-5: 1.5rem;   --sp-6: 2rem;     --sp-7: 2.5rem;   --sp-8: 3rem;
    --sp-9: 4rem;     --sp-10: 5rem;    --sp-11: 6.5rem;  --sp-12: 8rem;

    --section-y:   clamp(2.75rem, 1.8rem + 3vw, 4.5rem);
    --section-y-s: clamp(1.75rem, 1.2rem + 1.8vw, 3rem);

    /* --- Measure --------------------------------------------------------- */
    --container:   1240px;
    --container-w: 1400px;
    --container-n: 860px;
    --gutter:      clamp(1.125rem, 0.6rem + 2.2vw, 2.5rem);
    --measure:     66ch;

    /* --- Radius ---------------------------------------------------------- */
    --r-xs:   4px;
    --r-sm:   8px;
    --r:      12px;
    --r-lg:   18px;
    --r-xl:   24px;
    --r-2xl:  32px;
    --r-pill: 999px;

    /* --- Elevation ------------------------------------------------------
       Two-part: a tight contact term and a wide ambient term, which is how a
       real shadow behaves and why these do not look like a preset.

       The TINT follows the ground and always has. On the warm paper this
       replaces it was rgba(38, 32, 20) — a warm brown, because a neutral
       black shadow on warm paper reads as dirt. On cool paper the same
       reasoning inverts: the tint is the ink itself, rgba(5, 15, 51), so the
       shadow reads as the page's own colour in shade. The structure and the
       opacities are unchanged. */
    --sh-1: 0 1px 1px rgba(5, 15, 51, .05),
            0 2px 6px rgba(5, 15, 51, .045);
    --sh-2: 0 1px 2px rgba(5, 15, 51, .06),
            0 8px 20px rgba(5, 15, 51, .06);
    --sh-3: 0 2px 4px rgba(5, 15, 51, .06),
            0 18px 40px rgba(5, 15, 51, .09);
    --sh-4: 0 4px 8px rgba(5, 15, 51, .07),
            0 36px 80px rgba(5, 15, 51, .13);
    --sh-pill: 0 1px 2px rgba(5, 15, 51, .18),
               0 8px 20px rgba(5, 15, 51, .16);
    --sh-blue: 0 8px 24px rgba(10, 99, 255, .28);

    /* --- Motion ---------------------------------------------------------
       Four tiers, and the tier IS the meaning:
         1  micro    160ms   a state change on one property
         2  ui       280ms   a control responding
         3  section  420-700ms  content arriving
         4  cine     1100-1400ms a scene changing
       There is deliberately no tier above 4. "Cinematic" is a budget, not a
       licence. */
    --dur-1: 160ms;
    --dur-2: 280ms;
    --dur-3: 420ms;
    --dur-4: 700ms;
    --dur-5: 1100ms;
    --dur-6: 1400ms;
    --stagger: 70ms;

    /* One easing family. Entrances decelerate hard (they arrive and settle);
       scene changes ease at both ends (they are camera moves). Nothing here
       overshoots. */
    --ease:        cubic-bezier(.22, .61, .36, 1);
    --ease-out:    cubic-bezier(.16, 1, .3, 1);
    --ease-in-out: cubic-bezier(.65, 0, .35, 1);
    --ease-cine:   cubic-bezier(.65, .02, .22, 1);

    /* --- Chrome ---------------------------------------------------------- */
    --header-h:   74px;
    --nav-pill-h: 56px;

    /* --- Focus ----------------------------------------------------------
       The ring is --blue-ink, NOT --blue. A focus ring is non-text UI and
       holds to the 3:1 minimum; --blue on paper-03 is 4.07:1 against the
       page, but the ring's inner halo is --paper, and the pair that actually
       has to be distinguishable is ring-against-ground. --blue-ink gives
       5.37:1 in the worst case with no thinking required. */
    --focus:      var(--blue-ink);
    --focus-ring: 0 0 0 3px var(--paper), 0 0 0 6px var(--blue-ink);

    /* --- Layers ---------------------------------------------------------- */
    --z-base: 1;
    --z-raised: 10;
    --z-sticky: 100;
    --z-header: 500;
    --z-drawer: 700;
    --z-modal: 900;
    --z-toast: 1000;

    /* --- Ground-neutral aliases -----------------------------------------
       The contract every component holds to. These default to paper; a
       .ground-chapter ancestor remaps them (css/08-ground.css). A component
       that reads these instead of --paper / --ink / --blue works on both
       grounds for free, and that is the entire reason the dark chapter is
       not a second copy of 03-components.css.

       --accent and --accent-fg are DIFFERENT VALUES on this palette. That is
       new, and it is the single most important line in the file: --accent is
       the fill, --accent-fg is the same accent when it is text. On the
       previous palette they were the same colour and the distinction cost
       nothing to ignore. */
    --surface:      var(--paper);
    --surface-2:    var(--paper-2);
    --surface-3:    var(--paper-3);
    --surface-line: var(--line);
    --on-surface:   var(--ink);
    --on-surface-2: var(--ink-2);
    --on-surface-3: var(--ink-3);
    --accent:       var(--blue);       /* FILL                              */
    --accent-fg:    var(--blue-ink);   /* the accent AS TEXT on this ground */
    --elevate-1:    var(--sh-1);
    --elevate-2:    var(--sh-2);
    --elevate-3:    var(--sh-3);
}

/* ==========================================================================
   THE CHAPTER GROUND

   The dark sections are the ink colour used as a ground — the same #050f33
   that sets every heading on paper. That is why walking into one reads as
   the same room with the lights off rather than as a different site, and it
   is also why the accent has to change: --blue-ink is 1.94:1 down here.
   Everything below is measured against #050f33.
   ========================================================================== */
:root {
    --ink-chapter:    #050f33;   /* the chapter ground                      */
    --ink-chapter-2:  #0a1746;   /* raised — a panel inside the chapter     */
    --ink-chapter-3:  #101f57;   /* well inside the chapter                 */

    --on-chapter-1:   #f6f8fc;   /* display, headings          17.59:1      */
    --on-chapter-2:   #c4cbe4;   /* body                       11.59:1      */
    --on-chapter-3:   #8a96c6;   /* captions, metadata          6.47:1      */
                                 /* The prototype used #6c78a8 here, which  */
                                 /* is 4.36:1 — a fail at the 12.5px this   */
                                 /* tier is set at.                         */
    --chapter-line:        rgba(246, 248, 252, .12);
    --chapter-line-strong: rgba(246, 248, 252, .22);

    /* The accent, lifted so it survives the dark ground. 7.25:1 here and
       1.63:1 on paper — the mirror of --blue-ink, and the reason
       --accent-fg exists rather than a literal at the call site. */
    --accent-chapter: #6ea0ff;

    /* Depth on a dark ground comes from a lighter surface and a lit top
       edge, never from a darker shadow. */
    --sh-chapter-1: 0 1px 0 0 rgba(255, 255, 255, .05) inset, 0 2px 8px rgba(0, 0, 0, .40);
    --sh-chapter-2: 0 1px 0 0 rgba(255, 255, 255, .06) inset, 0 12px 32px rgba(0, 0, 0, .46);
    --sh-chapter-3: 0 1px 0 0 rgba(255, 255, 255, .07) inset, 0 28px 64px rgba(0, 0, 0, .52);
}

/* One accent variable, remapped per service page, so a service template never
   has to hardcode a colour. All five are measured on paper-01 AND paper-03
   and clear 4.5:1 as small text on both: the hue moves, the weight does not.

   --svc-marketing was #a8430f, the last surviving value from the retired
   orange ladder. It is now a magenta-leaning purple: it had to move anyway,
   and orange beside #0a63ff is the one pairing on the wheel that makes the
   brand blue look like a mistake.

   Note these set --accent-fg, which is TEXT — so each value is a text-safe
   colour, not a fill. --accent stays the fill and stays the brand blue on
   every service page, because the CTA is the same CTA everywhere. */
:root {
    --svc-web:       #0b52d8;   /* 6.15 / 5.37                              */
    --svc-security:  #046070;   /* 6.78 / 5.92                              */
    --svc-marketing: #8c2fbf;   /* 6.03 / 5.27                              */
    --svc-content:   #6134c9;   /* 6.95 / 6.07                              */
    --svc-ecom:      #0d6b34;   /* 6.23 / 5.44                              */
}
body.svc-web       { --accent-fg: var(--svc-web);       }
body.svc-security  { --accent-fg: var(--svc-security);  }
body.svc-marketing { --accent-fg: var(--svc-marketing); }
body.svc-content   { --accent-fg: var(--svc-content);   }
body.svc-ecom      { --accent-fg: var(--svc-ecom);      }

/* ==========================================================================
   Paper grain

   One fractalNoise filter, rendered by the browser into a 140px tile that
   repeats seamlessly (stitchTiles). It is a few hundred bytes of markup
   inside a CSS value: no request, no binary, no licence, and it resolves at
   whatever pixel ratio the screen has rather than at the one a PNG was
   exported for.

   Three strengths, because the same noise over paper, over a panel and
   inside a letterform needs three different amounts to read as one material.
   --grain-text is the only one that lands on measured contrast, so it is the
   one number in this file that inc/tools/audit.mjs will fail us over if it
   grows.
   ========================================================================== */
:root {
    --grain-fine: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.86' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23g)' opacity='0.038'/%3E%3C/svg%3E");
    --grain-card: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.92' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23g)' opacity='0.05'/%3E%3C/svg%3E");
    --grain-text: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.98' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23g)' opacity='0.10'/%3E%3C/svg%3E");
}

@media (max-width: 700px) {
    :root { --header-h: 66px; --nav-pill-h: 52px; }
}


/* --- File: css/01-base.css --- */
/* ==========================================================================
   Reset + base typography. Everything below assumes 00-tokens.css.
   ========================================================================== */

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

html {
    -webkit-text-size-adjust: 100%;
    -moz-text-size-adjust: 100%;
    text-size-adjust: 100%;
    scroll-behavior: smooth;
    scroll-padding-top: calc(var(--header-h) + var(--sp-5));
    tab-size: 4;
    background: var(--ink);
}

body {
    margin: 0;
    background: var(--paper);
    color: var(--ink-2);
    font-family: var(--font-body);
    font-size: var(--fs-base);
    line-height: var(--lh-body);
    font-weight: var(--fw-body);
    font-synthesis-weight: none;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
    overflow-x: clip;
}

/* A very soft warm wash behind the top of the page: the light in the render
   comes from upper left, so the paper it stands on is brightest there and
   falls away to the right. It is a lighting decision, not decoration, and it
   is what makes the object's shadow agree with the page it lies on.

   The previous pair of gradients was COOL — #eef2fb and a blue bloom at 88%/4%
   — which put a blue cast under a warm-neutral render and made the ceramic
   read grey. Four percent of tone, and it was fighting the entire palette. */
body::before {
    content: '';
    position: fixed;
    inset: 0 0 auto 0;
    height: 120vh;
    pointer-events: none;
    z-index: -1;
    background:
        radial-gradient(90% 60% at 18% -6%, rgba(255, 252, 244, .85) 0%, rgba(255, 252, 244, 0) 64%),
        radial-gradient(80% 50% at 92% 10%, rgba(226, 221, 208, .38) 0%, rgba(226, 221, 208, 0) 72%);
}

h1, h2, h3, h4, h5, h6 {
    margin: 0;
    font-family: var(--font-display);
    font-weight: var(--fw-bold);
    color: var(--ink);
    line-height: var(--lh-heading);
    letter-spacing: var(--tr-heading);
    text-wrap: balance;
}

h1 { font-size: var(--fs-h1); line-height: var(--lh-tight); }
h2 { font-size: var(--fs-h2); line-height: var(--lh-tight); }
h3 { font-size: var(--fs-h3); }
h4 { font-size: var(--fs-h4); }

p { margin: 0 0 var(--sp-4); }
p:last-child { margin-bottom: 0; }

a {
    color: var(--accent-fg);
    text-decoration: none;
    text-underline-offset: 3px;
    transition: color var(--dur-1) var(--ease);
}
a:hover { color: var(--blue-hover); }

/* Links inside running prose always carry an underline — colour alone is not
   a sufficient affordance, and it is the one place a reader is scanning. */
.prose a,
p > a:not(.btn):not(.chip):not(.link-arrow):not(.deflist-value) { text-decoration: underline; }

strong, b { font-weight: var(--fw-semi); color: var(--ink); }

ul, ol { margin: 0 0 var(--sp-4); padding-left: 1.25em; }
li { margin-bottom: var(--sp-2); }
ul.plain, ol.plain { list-style: none; padding: 0; margin: 0; }
ul.plain > li, ol.plain > li { margin: 0; }

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

button, input, select, textarea {
    font: inherit;
    color: inherit;
    margin: 0;
}
button { background: none; border: 0; cursor: pointer; }

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

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

code, kbd, pre, samp { font-family: var(--font-mono); font-size: 0.9em; }

blockquote { margin: 0; }

::selection { background: var(--blue); color: #fff; }

/* --- Focus ------------------------------------------------------------- */
:focus { outline: none; }
:focus-visible {
    outline: 3px solid var(--blue);
    outline-offset: 2px;
    border-radius: var(--r-xs);
}
/* On a dark band the blue ring loses contrast, so it becomes white there. */
.on-dark :focus-visible,
.band-blue :focus-visible,
.band-ink :focus-visible,
.band-ink :focus-visible { outline-color: #fff; }

.skip-link {
    position: absolute;
    left: var(--sp-4);
    top: -100px;
    z-index: 9999;
    padding: var(--sp-3) var(--sp-5);
    background: var(--pill);
    color: #fff;
    border-radius: var(--r-pill);
    font-weight: var(--fw-semi);
    transition: top var(--dur-2) var(--ease);
}
.skip-link:focus { top: var(--sp-4); color: #fff; }

.visually-hidden {
    position: absolute !important;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* --- Type helpers ------------------------------------------------------- */
.display {
    font-family: var(--font-display);
    font-size: var(--fs-display);
    font-weight: var(--fw-bold);
    line-height: var(--lh-display);
    letter-spacing: var(--tr-display);
    color: var(--ink);
    margin: 0;
    text-wrap: balance;
}

/* The reference headlines are two-tone: some words full ink, the rest grey.
   .display .soft is that second tone. */
/* :is() so a .soft span works in any heading, not only .display. Without it
   a two-tone h2 silently fell back to the light-ground grey on a blue band. */
:is(.display, h1, h2, h3, h4) .soft { color: var(--ink-4); }
:is(.display, h1, h2, h3, h4) .blue { color: var(--accent-fg); }

.lead {
    font-size: var(--fs-lead);
    line-height: 1.6;
    color: var(--ink-3);
    max-width: 58ch;
}

.eyebrow {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    font-family: var(--font-body);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semi);
    letter-spacing: var(--tr-eyebrow);
    text-transform: uppercase;
    color: var(--accent-fg);
    margin: 0 0 var(--sp-4);
}
.eyebrow::before {
    content: '';
    width: 18px;
    height: 2px;
    border-radius: 2px;
    background: var(--blue-tint-2);
    flex: none;
}
.eyebrow.is-blue { color: var(--accent-fg); }
.eyebrow.is-blue::before { background: var(--blue); }
.eyebrow.on-dark { color: #fff; }
.eyebrow.on-dark::before { background: rgba(255, 255, 255, .7); }
.eyebrow.is-bare::before { display: none; }

.muted { color: var(--ink-3); }
.mono  { font-family: var(--font-mono); }

/* Numerals — the +01 / +02 process markers from the reference. */
.numeral {
    font-family: var(--font-display);
    font-weight: var(--fw-bold);
    letter-spacing: -0.02em;
    color: var(--accent-fg);
    font-variant-numeric: tabular-nums;
}

.prose { max-width: var(--measure); }
.prose h2 { margin: var(--sp-8) 0 var(--sp-4); }
.prose h3 { margin: var(--sp-7) 0 var(--sp-3); }
.prose > :first-child { margin-top: 0; }
.prose ul, .prose ol { margin-bottom: var(--sp-5); }


/* ==========================================================================
   Page texture

   One fixed grain layer, and nothing else. Fixed rather than scrolled on
   purpose: the content slides across a stationary surface, which is how paper
   behaves. It sits behind everything at z-index -1 and never takes a click.

   Kept faint — a texture you can SEE on a business site reads as a pattern;
   one you only notice when it is removed reads as stock.
   ========================================================================== */
.bg-field {
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background-color: var(--paper);
    background-image: var(--grain-fine);
    background-size: 140px 140px;
}

/* Sections that paint their own ground sit on top of the field; the ones that
   do not let it show through. */
.band-paper { background: transparent; }

@media (prefers-reduced-motion: reduce) {
    /* A fixed layer under a scrolling page is a parallax effect whether or not
       anything is animated, so it stands down with the rest. */
    .bg-field { position: absolute; height: 100%; }
}


/* inc/helpers.php photo() wraps every image in a <picture> when a WebP twin
   exists. The rule above already gives <picture> display:block, but not a
   height — so an <img> written as height:100% resolves that percentage against
   an auto-height parent, which means auto. In a square frame holding a
   landscape photograph that is visible as a band of the fallback gradient
   across the bottom of the tile.

   display:contents removes the wrapper box entirely and lets the <img> be laid
   out by the real parent, which is what every one of those rules already
   assumed. Done here rather than per component so the next frame that holds a
   photo() cannot inherit the same bug. */
picture { display: contents; }


/* ==========================================================================
   Letterpress grain on the display type

   Naveen: "text ka ander bhi paper type textures add kar."

   LARGE HEADINGS ONLY, and that restriction is the whole reason this is safe.
   The grain tile is 140px; inside a 60px letterform its variation lands in the
   middle of a stroke and reads as paper. Inside a 15px letterform the same
   tile lands ON the stroke edge and reads as a rendering fault — which is what
   makes a site look cheap rather than expensive. So: .display and section
   headings, nothing else. Not leads, not h3, not buttons, not labels, not body.

   HOW IT IS BUILT
   Two layers, and the order matters. The ink is a gradient rather than the
   `color` property because `color` is what background-clip:text throws away;
   painting the ink AS a background layer is the only way to keep the grain on
   top of a solid, full-strength letter instead of on top of nothing.

   THE FALLBACK IS DECLARED FIRST, OUTSIDE THE @supports
   `color` is set to ink normally. An engine that does not take
   background-clip:text ignores the whole block and keeps that colour. If the
   transparent colour were declared unconditionally, the same engine would
   paint invisible headings — a total content loss from a decorative effect.
   ========================================================================== */
@supports (background-clip: text) or (-webkit-background-clip: text) {
    .display,
    .sec-head h2,
    .sec-head-split h2,
    .sec-head-center h2,
    .cap-head h2 {
        background-image: var(--grain-text), linear-gradient(var(--ink), var(--ink));
        background-size: 140px 140px, 100% 100%;
        background-repeat: repeat, no-repeat;
        -webkit-background-clip: text;
        background-clip: text;
        color: transparent;
        /* Chrome paints the text-clipped background with its own antialiasing;
           without this the strokes thin out and the heading looks lighter than
           the same size elsewhere on the page. */
        -webkit-text-fill-color: transparent;
    }

    /* On the dark bands the heading is PAPER on ink, so a grain built to
       lighten ink would erase it. Those keep their solid colour — the ground
       under them already carries the texture. */
    .band-ink .display,
    .band-ink h2,
    .band-blue .display,
    .band-blue h2 {
        background-image: none;
        color: var(--paper);
        -webkit-text-fill-color: currentColor;
    }
}


/* --- File: css/02-layout.css --- */
/* ==========================================================================
   Layout — container, section rhythm, grids, full-bleed bands.

   The old site's failure was that every section was the same gesture: centred
   eyebrow, centred title, centred standfirst, grid. So the section header here
   comes in three variants (left / centre / split) and templates are expected
   to alternate them.
   ========================================================================== */

.container {
    width: 100%;
    max-width: var(--container);
    margin-inline: auto;
    padding-inline: var(--gutter);
}
.container-wide   { max-width: var(--container-w); }
.container-narrow { max-width: var(--container-n); }
.container-flush  { padding-inline: 0; }

/* --- Sections ----------------------------------------------------------- */
.section {
    position: relative;
    padding-block: var(--section-y);
}
.section-sm  { padding-block: var(--section-y-s); }
.section-top { padding-top: var(--section-y); padding-bottom: 0; }
.section-bot { padding-top: 0; padding-bottom: var(--section-y); }

/* --- Grounds ------------------------------------------------------------ */
.band-paper { background: var(--paper); }
/* Each band gets the grain at its own weighting, so the page reads as three
   papers rather than as three greys with the same surface. */
.band-soft  { background: var(--grain-fine) repeat 0 0 / 140px 140px, var(--paper-2); }
.band-well  { background: var(--paper-3); }

.band-blue {
    background: var(--blue);
    color: var(--on-dark-2);
    --line: var(--on-dark-line);
}
.band-ink {
    /* Not a flat slab. Two soft blooms keep a full-width dark band from reading
       as a hole punched in the page, and they are what stops the two ink
       sections on the homepage (the process band and the closing band) looking
       like the same element repeated. */
    background:
        /* Grain on ink lightens rather than darkens, which is what stops a
           full-width black band looking like printed vinyl. Stronger here than
           on paper: the same amplitude that reads as texture on white is
           invisible against #0a0e1a. */
        var(--grain-card) repeat 0 0 / 140px 140px,
        radial-gradient(ellipse 70% 60% at 18% 0%, rgba(22, 82, 224, .26), transparent 62%),
        radial-gradient(ellipse 54% 52% at 88% 100%, rgba(255, 107, 53, .13), transparent 66%),
        var(--ink);
    color: var(--on-dark-2);
    --line: var(--on-dark-line);
}
.band-blue h1, .band-blue h2, .band-blue h3, .band-blue h4,
.band-ink  h1, .band-ink  h2, .band-ink  h3, .band-ink  h4,
.band-blue .display, .band-ink .display,
.band-blue strong, .band-ink strong { color: #fff; }
.band-blue .lead, .band-ink .lead,
.band-blue .muted, .band-ink .muted { color: var(--on-dark-2); }
.band-blue :is(.display, h1, h2, h3, h4) .soft,
.band-ink :is(.display, h1, h2, h3, h4) .soft,
.band-ink  :is(.display, h1, h2, h3, h4) .soft { color: rgba(255, 255, 255, .62); }

/* Rounded top corners where a coloured band meets the paper above it —
   the reference band sits like a card edge, not a hard cut. */
.band-round-t { border-radius: var(--r-2xl) var(--r-2xl) 0 0; }
.band-round   { border-radius: var(--r-2xl); }

/* --- Section headers ---------------------------------------------------- */
.sec-head { margin-bottom: var(--sp-5); max-width: 60ch; }
.sec-head .lead { margin-top: var(--sp-3); }

.sec-head-center {
    margin-inline: auto;
    text-align: center;
    max-width: 62ch;
}
.sec-head-center .eyebrow { justify-content: center; }
.sec-head-center .lead { margin-inline: auto; }

/* Split: title left, standfirst + action right. The rhythm-breaker. */
.sec-head-split {
    display: grid;
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
    gap: var(--sp-4) var(--sp-6);
    align-items: end;
    max-width: none;
    margin-bottom: var(--sp-5);
}
.sec-head-split .lead { margin-top: 0; }
@media (max-width: 860px) {
    .sec-head-split { grid-template-columns: 1fr; align-items: start; gap: var(--sp-4); }
}

/* --- Grids -------------------------------------------------------------- */
.grid { display: grid; gap: var(--sp-5); }
.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.grid-auto { grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); }

/* Asymmetric splits — used instead of 50/50 wherever a section has a visual
   and a text column, so consecutive sections do not read identically. */
.split {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: clamp(2rem, 1rem + 4vw, 5rem);
    align-items: center;
}
.split-wide-l { grid-template-columns: minmax(0, 1.28fr) minmax(0, 1fr); }
.split-wide-r { grid-template-columns: minmax(0, 1fr) minmax(0, 1.28fr); }
.split-top    { align-items: start; }
.split-rev > :first-child { order: 2; }

@media (max-width: 960px) {
    .grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 860px) {
    .grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .split, .split-wide-l, .split-wide-r { grid-template-columns: 1fr; }
    .split-rev > :first-child { order: 0; }
}
@media (max-width: 620px) {
    .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; }
}

/* --- Stack / cluster ---------------------------------------------------- */
.stack   { display: flex; flex-direction: column; gap: var(--sp-4); }
.stack-2 { gap: var(--sp-2); }
.stack-3 { gap: var(--sp-3); }
.stack-6 { gap: var(--sp-6); }

.cluster {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-3);
}
.cluster-4 { gap: var(--sp-4); }
.cluster-center { justify-content: center; }
.cluster-between { justify-content: space-between; }

/* --- Full bleed --------------------------------------------------------- */
.bleed {
    width: 100vw;
    max-width: 100vw;
    margin-inline: calc(50% - 50vw);
}

/* --- Page shell --------------------------------------------------------- */
main { display: block; min-height: 50vh; }

.page-head {
    padding-top: calc(var(--header-h) + clamp(3rem, 1.5rem + 5vw, 6rem));
    padding-bottom: var(--section-y-s);
    position: relative;
    /* The head's 3-D object is inset past the right edge on purpose, the same
       way the hero's is — an object that stops dead at the viewport edge stops
       reading as depth. That overhang has to be clipped or it widens the
       document, which is exactly what it did: 173px of horizontal overflow on
       every secondary page.

       overflow-x, and specifically CLIP rather than hidden. `hidden` on one
       axis forces the other axis to `auto`, which would make this a scroll
       container and cut the object off at the section's bottom edge as well.
       `clip` has no such coupling, so the vertical overhang survives. */
    overflow-x: clip;
}

.breadcrumbs {
    font-size: var(--fs-xs);
    color: var(--ink-3);
    margin-bottom: var(--sp-5);
}
.breadcrumbs ol {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
    list-style: none;
    margin: 0;
    padding: 0;
}
.breadcrumbs li { margin: 0; display: flex; align-items: center; gap: var(--sp-2); }
.breadcrumbs li + li::before {
    content: '/';
    color: var(--line-strong);
}
.breadcrumbs a { color: var(--ink-3); }
.breadcrumbs a:hover { color: var(--accent-fg); }
.breadcrumbs [aria-current] { color: var(--ink); font-weight: var(--fw-medium); }


/* --- File: css/03-components.css --- */
/* ==========================================================================
   Components.

   Material rule taken from the references: on a light ground an object is a
   WHITE surface with real, soft, wide elevation — never a near-invisible fill
   with a hairline. Hairlines are for dividers, not for making things exist.
   ========================================================================== */

/* ==========================================================================
   Icons
   ========================================================================== */
.icon {
    width: 1.25em;
    height: 1.25em;
    flex: none;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.75;
    stroke-linecap: round;
    stroke-linejoin: round;
    vertical-align: middle;
}
.icon-fill { fill: currentColor; stroke: none; }
.icon-sm { width: 1em;    height: 1em; }
.icon-lg { width: 1.5em;  height: 1.5em; }
.icon-xl { width: 2rem;   height: 2rem; }

.icon-box {
    display: inline-grid;
    place-items: center;
    width: 46px;
    height: 46px;
    border-radius: var(--r);
    background: var(--blue-tint);
    color: var(--accent-fg);
    flex: none;
}
.icon-box-note { background: var(--paper-3); color: var(--ink-2); }
.icon-box-ink    { background: var(--ink); color: #fff; }
.icon-box-lg     { width: 56px; height: 56px; border-radius: var(--r-lg); }

/* ==========================================================================
   Buttons — the black pill is the primary CTA in every reference
   ========================================================================== */
.btn {
    --_bg: var(--pill);
    --_fg: var(--pill-fg, #fff);
    --_bd: transparent;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-2);
    padding: 0.875rem 1.5rem;
    border: 1px solid var(--_bd);
    border-radius: var(--r-pill);
    background: var(--_bg);
    color: var(--_fg);
    font-family: var(--font-body);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semi);
    line-height: 1;
    letter-spacing: -0.005em;
    text-align: center;
    white-space: nowrap;
    cursor: pointer;
    transition: background var(--dur-2) var(--ease),
                color var(--dur-2) var(--ease),
                border-color var(--dur-2) var(--ease),
                transform var(--dur-2) var(--ease),
                box-shadow var(--dur-2) var(--ease);
}
.btn:hover { color: var(--_fg); transform: translateY(-2px); }
.btn:active { transform: translateY(0); }
.btn .icon { width: 1.05em; height: 1.05em; stroke-width: 2; }

/* Primary — solid black pill */
.btn-pill { box-shadow: var(--sh-pill); }
.btn-pill:hover { --_bg: var(--pill-hover); }

/* Blue */
.btn-blue { --_bg: var(--blue); box-shadow: var(--sh-blue); }
.btn-blue:hover { --_bg: var(--blue-hover); }

/* Outline on light */
.btn-line {
    --_bg: transparent;
    --_fg: var(--ink);
    --_bd: var(--line-strong);
}
.btn-line:hover { --_bd: var(--ink); --_bg: var(--wash); }

/* Quiet */
.btn-ghost { --_bg: transparent; --_fg: var(--ink-2); }
.btn-ghost:hover { --_bg: var(--paper-3); --_fg: var(--ink); }

/* On a coloured band */
.btn-white { --_bg: #fff; --_fg: var(--ink); box-shadow: 0 8px 22px rgba(0, 0, 0, .18); }
.btn-white:hover { --_bg: #f2f5fb; --_fg: var(--ink); }
.btn-on-dark {
    --_bg: transparent;
    --_fg: #fff;
    --_bd: rgba(255, 255, 255, .38);
}
.btn-on-dark:hover { --_bg: rgba(255, 255, 255, .12); --_bd: rgba(255, 255, 255, .7); --_fg: #fff; }

.btn-sm  { padding: 0.625rem 1.125rem; font-size: var(--fs-xs); }
.btn-lg  { padding: 1.0625rem 1.875rem; font-size: var(--fs-base); }
.btn-block { display: flex; width: 100%; }

.btn[disabled], .btn.is-loading { opacity: .6; pointer-events: none; }
.btn.is-loading::after {
    content: '';
    width: 14px; height: 14px;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    animation: btn-spin .6s linear infinite;
}
@keyframes btn-spin { to { transform: rotate(360deg); } }

/* The orange circular arrow button from reference 2 */
.btn-circle {
    display: inline-grid;
    place-items: center;
    width: 48px;
    height: 48px;
    border-radius: 50%;
    /* --orange-mid, not --orange: a white arrow on #ff6b35 measures 2.84:1
       and a non-text control needs 3:1. */
    background: var(--blue);
    color: #fff;
    flex: none;
    transition: transform var(--dur-2) var(--ease), background var(--dur-2) var(--ease);
}
.btn-circle:hover { transform: rotate(45deg) scale(1.06); color: #fff; }
.btn-circle .icon { width: 1.15rem; height: 1.15rem; stroke-width: 2.25; }

/* Text link that grows an arrow */
.link-arrow {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    font-weight: var(--fw-semi);
    font-size: var(--fs-sm);
    color: var(--accent-fg);
}
.link-arrow .icon { transition: transform var(--dur-2) var(--ease); }
.link-arrow:hover .icon { transform: translateX(4px); }

/* ==========================================================================
   Chips, badges, tags
   ========================================================================== */
.chip {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    padding: 0.4375rem 0.875rem;
    border-radius: var(--r-pill);
    background: var(--paper);
    border: 1px solid var(--line);
    color: var(--ink-2);
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    white-space: nowrap;
    transition: border-color var(--dur-2) var(--ease), color var(--dur-2) var(--ease),
                background var(--dur-2) var(--ease);
}
a.chip:hover { border-color: var(--ink); color: var(--ink); }
.chip.is-active, a.chip[aria-current="true"] {
    background: var(--ink);
    border-color: var(--ink);
    color: #fff;
}
.chip .icon { color: var(--ink-3); }
.chip.is-active .icon { color: inherit; }

.badge {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    padding: 0.3125rem 0.6875rem;
    border-radius: var(--r-pill);
    background: var(--blue-tint);
    color: var(--accent-fg);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semi);
    letter-spacing: 0.02em;
}
.badge-accent { background: var(--blue-tint); color: var(--accent-fg); }
.badge-ink    { background: var(--ink); color: #fff; }
.badge-soft   { background: var(--paper-3); color: var(--ink-2); }

.tag {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semi);
    letter-spacing: var(--tr-eyebrow);
    text-transform: uppercase;
    color: var(--ink-3);
}

/* ==========================================================================
   Cards
   ========================================================================== */
.card {
    position: relative;
    display: flex;
    flex-direction: column;
    /* Grain over the fill, in one background shorthand. A ::before overlay
       would have been the other way, and it would sit above .card-media —
       putting texture on the photographs, which is the one place on a card
       that must stay untouched. */
    background: var(--grain-card) repeat 0 0 / 140px 140px, var(--paper);
    border-radius: var(--r-lg);
    box-shadow: var(--sh-2);
    overflow: hidden;
    transition: transform var(--dur-3) var(--ease-out),
                box-shadow var(--dur-3) var(--ease-out);
}
.card-hover:hover { transform: translateY(-6px); box-shadow: var(--sh-4); }
.card-body { padding: var(--sp-6); display: flex; flex-direction: column; gap: var(--sp-3); flex: 1; }
.card-body-sm { padding: var(--sp-5); }
.card-title { font-size: var(--fs-h4); color: var(--ink); }
.card-text  { color: var(--ink-3); font-size: var(--fs-sm); margin: 0; }
.card-foot {
    margin-top: auto;
    padding-top: var(--sp-4);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
}

/* Photo top — the media band on a card */
.card-media {
    position: relative;
    aspect-ratio: 16 / 10;
    overflow: hidden;
    background: var(--paper-3);
}
.card-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform var(--dur-4) var(--ease-out);
}
.card-hover:hover .card-media img { transform: scale(1.05); }
.card-media-tall { aspect-ratio: 3 / 4; }
.card-media-wide { aspect-ratio: 2 / 1; }

/* A whole card that is a link */
.card-link::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
}

/* Flat panel — a soft inset region, not an object */
.panel {
    background: var(--grain-card) repeat 0 0 / 140px 140px, var(--paper-2);
    border-radius: var(--r-lg);
    padding: var(--sp-6);
}
.panel-line { background: var(--grain-card) repeat 0 0 / 140px 140px, var(--paper); border: 1px solid var(--line); }
.panel-blue { background: var(--grain-card) repeat 0 0 / 140px 140px, var(--blue-tint); }

/* ==========================================================================
   Stats / counters
   ========================================================================== */
.stat { display: flex; flex-direction: column; gap: var(--sp-1); }
.stat-value {
    font-family: var(--font-display);
    font-size: clamp(2rem, 1.4rem + 2.2vw, 3rem);
    font-weight: var(--fw-bold);
    line-height: 1;
    letter-spacing: -0.03em;
    color: var(--ink);
    font-variant-numeric: tabular-nums;
}
.stat-label {
    font-size: var(--fs-xs);
    color: var(--ink-3);
    line-height: 1.4;
}
.band-blue .stat-value, .band-ink .stat-value { color: #fff; }
.band-blue .stat-label, .band-ink .stat-label { color: var(--on-dark-2); }

/* ==========================================================================
   Avatars + stars (reference 1, top of the FAQ card)
   ========================================================================== */
.avatar {
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: var(--blue-deep);
    color: #fff;
    font-family: var(--font-display);
    font-weight: var(--fw-bold);
    font-size: var(--fs-sm);
    letter-spacing: 0.02em;
    flex: none;
    overflow: hidden;
    box-shadow: 0 0 0 3px var(--paper);
}
.avatar img { width: 100%; height: 100%; object-fit: cover; }
.avatar-sm { width: 34px; height: 34px; font-size: var(--fs-2xs); }
.avatar-lg { width: 56px; height: 56px; font-size: var(--fs-base); }
/* Deterministic tints so a roster is not one flat colour */
.avatar-t1 { background: #1652e0; }
.avatar-t2 { background: #0e7490; }
.avatar-t3 { background: #7c3aed; }
.avatar-t4 { background: #c7440e; }
.avatar-t5 { background: #15803d; }
.avatar-t6 { background: #0d47a1; }

.avatar-stack { display: flex; }
.avatar-stack .avatar + .avatar { margin-left: -12px; }

.stars { display: inline-flex; gap: 2px; color: var(--ink-3); }
.stars .icon { width: 14px; height: 14px; fill: currentColor; stroke: none; }
.stars .is-empty { color: var(--line-strong); }

.person {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
}
.person-name { font-weight: var(--fw-semi); color: var(--ink); font-size: var(--fs-sm); line-height: 1.3; }
.person-role { font-size: var(--fs-xs); color: var(--ink-3); line-height: 1.3; }

/* ==========================================================================
   Lists
   ========================================================================== */
.list-check { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-3); }
.list-check li {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-3);
    margin: 0;
    color: var(--ink-2);
    font-size: var(--fs-sm);
    line-height: 1.55;
}
.list-check .icon {
    color: var(--accent-fg);
    margin-top: 0.15em;
    width: 1.15em;
    height: 1.15em;
    stroke-width: 2.25;
}
.band-blue .list-check .icon, .band-ink .list-check .icon { color: #fff; }
.list-check.is-green .icon { color: var(--success); }

/* Definition rows — used for contact details, meta blocks */
.deflist { display: grid; gap: var(--sp-4); }
.deflist-row { display: flex; align-items: flex-start; gap: var(--sp-4); }
.deflist-label {
    display: block;
    font-size: var(--fs-2xs);
    letter-spacing: var(--tr-eyebrow);
    text-transform: uppercase;
    font-weight: var(--fw-semi);
    color: var(--ink-3);
    margin-bottom: 2px;
}
.deflist-value { color: var(--ink); font-weight: var(--fw-medium); }

/* ==========================================================================
   Accordion (FAQ)
   ========================================================================== */
.accordion { border-top: 1px solid var(--line); }
.accordion-item { border-bottom: 1px solid var(--line); }
.accordion-trigger {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-4);
    width: 100%;
    padding: var(--sp-5) 0;
    text-align: left;
    font-family: var(--font-body);
    font-size: var(--fs-base);
    font-weight: var(--fw-medium);
    color: var(--ink);
    line-height: 1.45;
    transition: color var(--dur-2) var(--ease);
}
.accordion-trigger:hover { color: var(--accent-fg); }
.accordion-icon {
    flex: none;
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background: var(--paper-3);
    color: var(--ink-2);
    transition: transform var(--dur-2) var(--ease), background var(--dur-2) var(--ease),
                color var(--dur-2) var(--ease);
}
.accordion-icon .icon { width: 15px; height: 15px; stroke-width: 2.25; }
.accordion-trigger[aria-expanded="true"] .accordion-icon {
    transform: rotate(180deg);
    background: var(--ink);
    color: #fff;
}
.accordion-panel {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows var(--dur-3) var(--ease-out);
}
.accordion-panel > div { overflow: hidden; }
.accordion-trigger[aria-expanded="true"] + .accordion-panel { grid-template-rows: 1fr; }
.accordion-panel p {
    color: var(--ink-3);
    font-size: var(--fs-sm);
    line-height: 1.7;
    padding-bottom: var(--sp-5);
    max-width: 62ch;
    margin: 0;
}

/* ==========================================================================
   Forms
   ========================================================================== */
.form { display: grid; gap: var(--sp-4); }
.form-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-4); }
@media (max-width: 620px) { .form-row { grid-template-columns: 1fr; } }

.field { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; }
.field label {
    font-size: var(--fs-xs);
    font-weight: var(--fw-semi);
    color: var(--ink-2);
}
.field label .req { color: var(--accent-fg); }

.form-control {
    width: 100%;
    padding: 0.8125rem 1rem;
    border: 1px solid var(--line-strong);
    border-radius: var(--r);
    background: var(--paper);
    color: var(--ink);
    font-size: var(--fs-sm);
    line-height: 1.5;
    transition: border-color var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease);
}
.form-control::placeholder { color: var(--ink-3); opacity: .75; }
.form-control:hover { border-color: var(--ink-3); }
.form-control:focus {
    outline: none;
    border-color: var(--blue);
    box-shadow: 0 0 0 4px rgba(22, 82, 224, .14);
}
textarea.form-control { min-height: 130px; resize: vertical; }
select.form-control {
    appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236b7688' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.875rem center;
    padding-right: 2.5rem;
}

.field.has-error .form-control { border-color: var(--danger); }
.field.has-error .form-control:focus { box-shadow: 0 0 0 4px rgba(198, 40, 40, .14); }
.field-error {
    font-size: var(--fs-xs);
    color: var(--danger);
    display: flex;
    align-items: center;
    gap: var(--sp-2);
}
.field-hint { font-size: var(--fs-xs); color: var(--ink-3); }

.check {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-3);
    font-size: var(--fs-xs);
    line-height: 1.6;
    color: var(--ink-3);
    cursor: pointer;
}
.check input[type="checkbox"] {
    appearance: none;
    width: 20px;
    height: 20px;
    flex: none;
    margin-top: 1px;
    border: 1px solid var(--line-strong);
    border-radius: var(--r-xs);
    background: var(--paper);
    cursor: pointer;
    transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease);
}
.check input[type="checkbox"]:checked {
    background: var(--blue) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center/14px no-repeat;
    border-color: var(--blue);
}
.check a { text-decoration: underline; }

/* The inline pill input from reference 1 — input and submit share one pill */
.pill-input {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: 6px 6px 6px var(--sp-5);
    background: var(--paper);
    border: 1px solid var(--line-strong);
    border-radius: var(--r-pill);
    max-width: 420px;
    transition: border-color var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease);
}
.pill-input:focus-within {
    border-color: var(--blue);
    box-shadow: 0 0 0 4px rgba(22, 82, 224, .14);
}
.pill-input input {
    flex: 1;
    min-width: 0;
    border: 0;
    background: none;
    outline: none;
    font-size: var(--fs-sm);
    color: var(--ink);
}
.pill-input .btn { padding: 0.75rem 1.375rem; box-shadow: none; }

/* The honeypot field must never be seen, focused, or read out */
.hp-field {
    position: absolute !important;
    left: -9999px !important;
    width: 1px; height: 1px;
    overflow: hidden;
    opacity: 0;
    pointer-events: none;
}

/* ==========================================================================
   Comparison table
   ========================================================================== */
.table-wrap {
    overflow-x: auto;
    border-radius: var(--r-lg);
    background: var(--paper);
    box-shadow: var(--sh-2);
    -webkit-overflow-scrolling: touch;
}
.table-compare { width: 100%; min-width: 680px; border-collapse: collapse; font-size: var(--fs-sm); }
.table-compare th, .table-compare td {
    padding: var(--sp-4) var(--sp-5);
    text-align: left;
    border-bottom: 1px solid var(--line);
    vertical-align: top;
}
.table-compare thead th {
    font-family: var(--font-display);
    font-size: var(--fs-xs);
    font-weight: var(--fw-bold);
    letter-spacing: var(--tr-eyebrow);
    text-transform: uppercase;
    color: var(--ink-3);
    background: var(--paper-2);
    white-space: nowrap;
}
.table-compare thead th.is-us { color: var(--accent-fg); background: var(--blue-tint); }
.table-compare tbody th {
    font-family: var(--font-body);
    font-weight: var(--fw-semi);
    color: var(--ink);
}
.table-compare td.is-us { background: rgba(22, 82, 224, .045); color: var(--ink); font-weight: var(--fw-medium); }
.table-compare tbody tr:last-child th, .table-compare tbody tr:last-child td { border-bottom: 0; }
.table-compare .icon { width: 1.05em; height: 1.05em; }
.table-compare .yes { color: var(--success); }
.table-compare .no  { color: var(--ink-3); }

/* ==========================================================================
   Quote / testimonial
   ========================================================================== */
.quote {
    display: flex;
    flex-direction: column;
    gap: var(--sp-5);
    padding: var(--sp-6);
    background: var(--paper);
    border-radius: var(--r-lg);
    box-shadow: var(--sh-2);
    height: 100%;
}
.quote-text {
    font-size: var(--fs-base);
    line-height: 1.65;
    color: var(--ink-2);
    margin: 0;
    flex: 1;
}

/* ==========================================================================
   Marquee
   ========================================================================== */
.marquee {
    display: flex;
    overflow: hidden;
    gap: var(--sp-6);
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
    mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.marquee-track {
    display: flex;
    align-items: center;
    gap: var(--sp-6);
    flex: none;
    min-width: 100%;
    animation: marquee-scroll var(--marquee-dur, 42s) linear infinite;
}
.marquee:hover .marquee-track,
.marquee:focus-within .marquee-track { animation-play-state: paused; }
.marquee.is-reverse .marquee-track { animation-direction: reverse; }
@keyframes marquee-scroll {
    from { transform: translate3d(0, 0, 0); }
    to   { transform: translate3d(-50%, 0, 0); }
}

/* ==========================================================================
   Toast
   ========================================================================== */
.toast-stack {
    position: fixed;
    right: var(--sp-4);
    bottom: var(--sp-4);
    z-index: var(--z-toast);
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    pointer-events: none;
    max-width: min(380px, calc(100vw - 2rem));
}
.toast {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-3);
    padding: var(--sp-4) var(--sp-5);
    background: var(--ink);
    color: #fff;
    border-radius: var(--r);
    box-shadow: var(--sh-4);
    font-size: var(--fs-sm);
    pointer-events: auto;
    animation: toast-in var(--dur-3) var(--ease-out) both;
}
.toast.is-out { animation: toast-out var(--dur-2) var(--ease) forwards; }
.toast-ok .icon    { color: #6ee7a8; }
.toast-error .icon { color: #ffa4a4; }
@keyframes toast-in  { from { opacity: 0; transform: translateY(14px); } }
@keyframes toast-out { to   { opacity: 0; transform: translateY(8px); } }

/* ==========================================================================
   Modal
   ========================================================================== */
.modal {
    position: fixed;
    inset: 0;
    z-index: var(--z-modal);
    display: grid;
    place-items: center;
    padding: var(--sp-4);
    background: rgba(10, 14, 26, .55);
    opacity: 0;
    visibility: hidden;

    /* A CLOSED MODAL SHOULD COST NOTHING.

       backdrop-filter used to be declared here unconditionally, and a
       backdrop-filtered element is its own backdrop root and owns a
       compositing layer whether or not anyone can see it — a full-viewport
       fixed layer, blurring the page behind it, permanently, on every route.
       It is only ever needed while the modal is open, so that is where it now
       lives, and content-visibility skips rendering the subtree until then.

       (This was found while chasing form labels that appeared to ghost across
       the top of the page. It was NOT the cause — that was a class-name
       collision, recorded on .field-canvas in css/09-scenes.css — but it is
       worth keeping on its own merits.) */
    content-visibility: hidden;
    transition: opacity var(--dur-2) var(--ease), visibility var(--dur-2);
}
.modal.is-open {
    opacity: 1;
    visibility: visible;
    content-visibility: visible;
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
}
.modal-card {
    position: relative;
    width: min(560px, 100%);
    max-height: min(86vh, 900px);
    overflow-y: auto;
    background: var(--paper);
    border-radius: var(--r-xl);
    box-shadow: var(--sh-4);
    padding: clamp(1.5rem, 1rem + 2vw, 2.5rem);
    transform: translateY(16px) scale(.98);
    transition: transform var(--dur-3) var(--ease-out);
}
.modal.is-open .modal-card { transform: none; }
.modal-close {
    position: absolute;
    top: var(--sp-4);
    right: var(--sp-4);
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    border-radius: 50%;
    background: var(--paper-3);
    color: var(--ink-2);
    transition: background var(--dur-2) var(--ease);
}
.modal-close:hover { background: var(--line-strong); }
.modal-head { margin-bottom: var(--sp-5); padding-right: 3rem; }

body.modal-open { overflow: hidden; }

/* ==========================================================================
   Back to top + sticky mobile CTA
   ========================================================================== */
.to-top {
    position: fixed;
    right: var(--sp-4);
    bottom: var(--sp-4);
    z-index: var(--z-sticky);
    display: grid;
    place-items: center;
    width: 46px;
    height: 46px;
    border-radius: 50%;
    background: var(--pill);
    color: #fff;
    box-shadow: var(--sh-3);
    opacity: 0;
    visibility: hidden;
    transform: translateY(10px);
    transition: opacity var(--dur-2) var(--ease), transform var(--dur-2) var(--ease),
                visibility var(--dur-2);
}
.to-top.is-on { opacity: 1; visibility: visible; transform: none; }

.sticky-cta {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: var(--z-sticky);
    display: none;
    gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4) calc(var(--sp-3) + env(safe-area-inset-bottom));
    background: rgba(255, 255, 255, .92);
    backdrop-filter: blur(14px);
    border-top: 1px solid var(--line);
}
.sticky-cta .btn { flex: 1; }
.sticky-cta .btn-wa { --_bg: var(--whatsapp); flex: none; padding-inline: 1.125rem; }
@media (max-width: 760px) {
    .sticky-cta { display: flex; }
    .to-top { bottom: calc(72px + env(safe-area-inset-bottom)); }
    .toast-stack { bottom: calc(72px + env(safe-area-inset-bottom)); }
}

/* ==========================================================================
   Price card — shared by the homepage #pricing grid and /pricing
   ========================================================================== */
.price-card {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: var(--sp-5);
    padding: var(--sp-6);
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--r-xl);
    box-shadow: var(--sh-1);
    transition: transform var(--dur-3) var(--ease-out), box-shadow var(--dur-3) var(--ease-out);
}
.price-card:hover { transform: translateY(-6px); box-shadow: var(--sh-3); }

/* The featured tier inverts. Position does not decide that; the flag does. */
.price-card.is-featured {
    background: var(--ink);
    border-color: var(--ink);
    color: var(--on-dark-2);
    box-shadow: var(--sh-4);
}
.price-card.is-featured .price-name,
.price-card.is-featured .price-value { color: #fff; }
.price-card.is-featured .price-sub,
.price-card.is-featured .list-check li { color: var(--on-dark-2); }
.price-card.is-featured .list-check .icon { color: #fff; }
.price-card.is-featured .price-index { color: rgba(255, 255, 255, .35); }

.price-flag {
    position: absolute;
    top: calc(var(--sp-4) * -1);
    left: var(--sp-6);
    padding: 0.375rem 0.75rem;
    border-radius: var(--r-pill);
    background: var(--blue);
    color: #fff;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semi);
    letter-spacing: 0.02em;
}

.price-head { display: flex; flex-direction: column; gap: var(--sp-2); }
.price-index {
    font-family: var(--font-display);
    font-size: var(--fs-sm);
    font-weight: var(--fw-bold);
    color: var(--ink-3);
    font-variant-numeric: tabular-nums;
}
.price-name  { font-size: var(--fs-h3); }
.price-sub   { font-size: var(--fs-sm); color: var(--ink-3); margin: 0; }
.price-value {
    font-family: var(--font-display);
    font-size: var(--fs-h3);
    font-weight: var(--fw-bold);
    color: var(--ink);
    margin: var(--sp-2) 0 0;
    letter-spacing: var(--tr-heading);
}
.price-list { flex: 1; }
.price-cta { margin-top: auto; }

/* ==========================================================================
   A button that has to sit inside a paragraph and read as a link
   ========================================================================== */
.link-inline {
    display: inline;
    padding: 0;
    font: inherit;
    color: var(--accent-fg);
    text-decoration: underline;
    text-underline-offset: 3px;
    cursor: pointer;
}
.link-inline:hover { color: var(--blue-hover); }

/* ==========================================================================
   Notice pages — 404, thank-you, and the form-failure page in submit.php.

   These three carry no per-page stylesheet (a failure page must never depend
   on one more request succeeding), so their styles live here in the core.
   ========================================================================== */
.notice-wrap { text-align: center; }
.notice-wrap .lead { margin-inline: auto; }
.notice-wrap .eyebrow { justify-content: center; }

.notice-icon {
    display: grid;
    place-items: center;
    width: 64px;
    height: 64px;
    margin: 0 auto var(--sp-5);
    border-radius: 50%;
    background: var(--blue-tint);
    color: var(--accent-fg);
}
.notice-icon .icon { width: 30px; height: 30px; stroke-width: 2.5; }

.notice-code {
    font-family: var(--font-display);
    font-size: clamp(4rem, 2rem + 9vw, 7rem);
    font-weight: var(--fw-bold);
    line-height: 1;
    letter-spacing: var(--tr-display);
    /* --ink-4 rather than a hairline grey: it is aria-hidden, but a sighted
       visitor still reads "404" off it, and at 1.46:1 they could not. */
    color: var(--ink-4);
    margin: 0 0 var(--sp-4);
}

.notice-links {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--sp-3);
    margin: var(--sp-8) 0;
    text-align: left;
}
.notice-link {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    padding: var(--sp-4);
    border-radius: var(--r-lg);
    background: var(--paper);
    box-shadow: var(--sh-1);
    color: var(--ink-3);
    font-size: var(--fs-xs);
    transition: transform var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease);
}
.notice-link:hover { transform: translateY(-3px); box-shadow: var(--sh-3); color: var(--ink-3); }
.notice-link strong { display: block; font-size: var(--fs-sm); color: var(--ink); margin-bottom: 2px; }
.notice-actions { margin-top: var(--sp-6); }
@media (max-width: 620px) { .notice-links { grid-template-columns: 1fr; } }

/* ==========================================================================
   The closing CTA band (partials/cta-band.php)
   ========================================================================== */
.cta-band { text-align: left; }
.cta-card-shell {
    position: relative;
    background: linear-gradient(145deg, rgba(15, 23, 42, 0.95) 0%, rgba(10, 14, 26, 0.98) 100%);
    border: 1px solid rgba(10, 99, 255, 0.25);
    border-radius: var(--r-xl);
    padding: clamp(1.25rem, 2.5vw, 2rem) clamp(1.25rem, 3vw, 2.5rem);
    box-shadow: 0 20px 50px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.1);
    overflow: hidden;
    isolation: isolate;
}
.cta-ambient-glow {
    position: absolute;
    inset: -50%;
    background: radial-gradient(500px circle at 50% 30%, rgba(10, 99, 255, 0.2) 0%, rgba(0, 242, 254, 0.04) 45%, transparent 70%);
    pointer-events: none;
    z-index: 1;
}
.cta-compact-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: clamp(1.5rem, 3vw, 3rem);
    position: relative;
    z-index: 2;
}
@media (max-width: 991px) {
    .cta-compact-row {
        flex-direction: column;
        align-items: flex-start;
        gap: 1.25rem;
    }
}
.cta-compact-text {
    flex: 1;
    min-width: 0;
}
.cta-badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: rgba(10, 99, 255, 0.15);
    border: 1px solid rgba(0, 242, 254, 0.3);
    color: #38bdf8;
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 700;
    padding: 3px 10px;
    border-radius: 99px;
    text-transform: uppercase;
    letter-spacing: var(--tr-meta);
    margin-bottom: 8px;
    box-shadow: 0 0 12px rgba(10, 99, 255, 0.2);
}
.cta-badge .pulse-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #00f2fe;
    box-shadow: 0 0 8px #00f2fe;
}
.cta-band-title {
    font-size: clamp(1.35rem, 2.5vw, 1.85rem) !important;
    font-weight: 800;
    color: #ffffff;
    line-height: 1.2 !important;
    letter-spacing: -0.02em;
    margin: 0;
}
.cta-band-text {
    margin: 6px 0 0 !important;
    color: #94a3b8;
    font-size: 0.95rem !important;
    line-height: 1.45 !important;
    max-width: 58ch;
}
.cta-compact-right {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 10px;
    flex-shrink: 0;
}
@media (max-width: 991px) {
    .cta-compact-right {
        align-items: flex-start;
        width: 100%;
    }
}
.cta-band-actions {
    margin-top: 0 !important;
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
}
.cta-main-btn {
    box-shadow: 0 4px 16px rgba(10, 99, 255, 0.3);
    padding: 8px 18px !important;
    font-size: 14px !important;
}
.cta-wa-btn {
    background: #25D366 !important;
    color: #ffffff !important;
    border-color: #25D366 !important;
    box-shadow: 0 4px 16px rgba(37, 211, 102, 0.2);
    padding: 8px 18px !important;
    font-size: 14px !important;
}
.cta-wa-btn:hover {
    background: #1DA851 !important;
    box-shadow: 0 8px 24px rgba(37, 211, 102, 0.35);
}
.cta-inline-chips {
    display: flex;
    gap: 8px;
    font-size: 11px;
    color: #cbd5e1;
}
.cta-inline-chips span {
    display: flex;
    align-items: center;
    gap: 4px;
    background: rgba(255, 255, 255, 0.05);
    padding: 2px 8px;
    border-radius: 4px;
    border: 1px solid rgba(255, 255, 255, 0.08);
}
.cta-inline-chips .icon {
    width: 12px;
    height: 12px;
    color: #38bdf8;
    stroke-width: 2.5;
}

/* ==========================================================================
   "Ways to reach us" card (/contact)
   ========================================================================== */
.way-card .card-body { gap: var(--sp-4); }
.way-card .icon-box { margin-bottom: var(--sp-2); }


/* ==========================================================================
   Key-word highlights

   Three ways to make one word in a heading carry the promise. Two of them draw
   themselves as the line arrives; the third is a static recolour.

   CONTRAST, WHICH IS WHERE THIS NORMALLY GOES WRONG
   None of these changes the colour of the text. `--orange` is 2.84:1 on white:
   fine as a rule, a line or a fill (non-text, needs 3:1), a failure the instant
   it carries a word. So .ul-draw paints UNDER the text and .ul-mark paints
   BEHIND it, and in both cases the word keeps --ink at 16:1. The one variant
   that does recolour text, .ul-ink, uses --orange-ink (4.93:1) and nothing else.

   ANIMATION IS SCROLL-TIMELINE ONLY, ON PURPOSE
   There is no IntersectionObserver fallback here. A browser without timelines
   shows the finished underline, which is the whole point — the line is the
   message, the drawing of it is the flourish. Wiring an observer to a decorative
   mark inside a heading would mean adding a JS dependency to text.
   ========================================================================== */

/* --- A rule that wipes in under the word -------------------------------- */
.ul-draw {
    position: relative;
    text-decoration: none;
    white-space: nowrap;
}
.ul-draw::after {
    content: '';
    position: absolute;
    left: -0.04em;
    right: -0.04em;
    bottom: -0.04em;
    height: 0.055em;
    min-height: 3px;
    border-radius: var(--r-pill);
    background: var(--ul-colour, var(--blue));
    transform-origin: 0 50%;
    /* Resting state is the finished rule. Rounded caps read as drawn rather
       than as a border, and the scale needs its own box — which is why this is
       a pseudo element and not text-decoration. */
}
@supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
        .ul-draw::after {
            animation: ul-wipe 1ms linear both;
            animation-timeline: view();
            animation-range: entry 15% cover 40%;
        }
    }
}
@keyframes ul-wipe { from { transform: scaleX(0); } to { transform: scaleX(1); } }

.ul-draw.is-accent { --ul-colour: var(--accent-fg); }     /* a RULE, not text — ground-aware */
.band-ink .ul-draw,
.band-blue .ul-draw { --ul-colour: var(--on-dark-1); }    /* on the accent band the rule is white */

/* --- A highlighter swipe behind the word -------------------------------- */
/* --mark-h is read by the r-mark keyframes in css/06-motion.css, so the band
   grows sideways only. Animating the height as well smears rather than sweeps. */
.ul-mark {
    --mark-h: 42%;
    background-image: linear-gradient(var(--mark-tint, var(--blue-tint-2)), var(--mark-tint, var(--blue-tint-2)));
    background-repeat: no-repeat;
    background-position: 0 88%;
    background-size: 100% var(--mark-h);
    padding-inline: 0.12em;
    border-radius: 0.12em;
    box-decoration-break: clone;
    -webkit-box-decoration-break: clone;
}
.ul-mark.is-gold   { --mark-tint: rgba(255, 184, 0, .45); }
.ul-mark.is-accent { --mark-tint: var(--blue-tint); }
.band-ink .ul-mark,
.band-blue .ul-mark { --mark-tint: rgba(255, 107, 53, .32); }

/* --- Orange as actual text: the only token that may, at 4.93:1 ---------- */
.ul-ink { color: var(--accent-fg); }
.band-blue .ul-ink { color: var(--on-dark-1); }


/* .ul-ink recolours the TEXT, where .ul-draw and .ul-mark paint behind it. A
   heading with background-clip:text has -webkit-text-fill-color: transparent,
   which is inherited and beats `color` on every descendant — so inside a
   grained heading this word would come out in the heading's ink instead of in
   orange, losing the highlight without any warning. It opts back out. */
.display .ul-ink,
.sec-head h2 .ul-ink,
.sec-head-split h2 .ul-ink,
.sec-head-center h2 .ul-ink,
.cap-head h2 .ul-ink {
    -webkit-text-fill-color: var(--accent-fg);
    color: var(--accent-fg);
}

/* ==========================================================================
   SURFACE — light and texture inside the components

   Naveen: "diagine models effect textures bahut kuch chahiya, UI ka ander."

   The grain added earlier is on the page ground, the bands and the display
   letterforms. This is the same idea one level in: the cards, panels, buttons
   and frames themselves.

   Everything here is a BACKGROUND LAYER or a box-shadow — never a text colour,
   never a filter on a container. That is deliberate and it is what makes it
   safe to add this much at once: not one rule below can move a contrast ratio,
   because not one of them touches type.

   Layer order in the shorthands is: sheen, grain, fill. Grain under the sheen
   so the highlight reads as light ON the paper rather than as another texture
   beside it; both above the fill, which never moves.
   ========================================================================== */

/* --- The specular sweep ---------------------------------------------------
   A flat fill has no direction. One soft diagonal band of lighter paper gives
   the surface an angle to face, which is the whole difference between "a grey
   rectangle" and "a card catching the light". Kept under 5% so it reads as a
   sheen and not as a stripe. */
.card,
.panel {
    background:
        linear-gradient(118deg,
            rgba(255, 255, 255, 0) 18%,
            rgba(255, 255, 255, .55) 42%,
            rgba(255, 255, 255, 0) 62%) no-repeat,
        var(--grain-card) repeat 0 0 / 140px 140px,
        var(--paper);
    background-size: 240% 100%, 140px 140px, auto;
    background-position: 120% 0, 0 0, 0 0;
    /* Only the sweep moves on hover. Transitioning `background` wholesale
       would re-rasterise the grain tile every frame for no visible gain. */
    transition: background-position var(--dur-4) var(--ease-out),
                transform var(--dur-3) var(--ease-out),
                box-shadow var(--dur-3) var(--ease-out);
}
.panel { background-color: var(--paper-2); }
.panel-line { background-color: var(--paper); }
.panel-blue { background-color: var(--blue-tint); }

.card-hover:hover,
.panel:hover { background-position: -20% 0, 0 0, 0 0; }

@media (prefers-reduced-motion: reduce) {
    /* A highlight that travels on hover is motion. The surface keeps its
       angle; it just stops sliding. */
    .card, .panel { transition: none; }
    .card-hover:hover, .panel:hover { background-position: 120% 0, 0 0, 0 0; }
}

/* --- Lit edges ------------------------------------------------------------
   A one-pixel highlight along the top and a soft dark line under the base.
   The cheapest trick there is for making a surface look lit rather than drawn:
   it is what a real edge does under a light that is above it. Inset shadows,
   so it costs no pseudo-element and cannot cover content. */
.card,
.panel,
.faq-card {
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .9),
        inset 0 -1px 0 rgba(10, 14, 26, .05),
        var(--sh-2);
}
.card-hover:hover {
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .95),
        inset 0 -1px 0 rgba(10, 14, 26, .06),
        var(--sh-4);
}

/* --- Buttons --------------------------------------------------------------
   The primary pill is the darkest object on a light page, so it is the one
   that most obviously wants a top light. */
.btn-pill {
    background-image: linear-gradient(180deg, rgba(255, 255, 255, .16), rgba(255, 255, 255, 0) 58%);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .22),
        0 10px 24px -12px rgba(10, 14, 26, .55);
}
.btn-pill:hover {
    background-image: linear-gradient(180deg, rgba(255, 255, 255, .24), rgba(255, 255, 255, 0) 62%);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .3),
        0 14px 30px -12px rgba(10, 14, 26, .6);
}
/* The sheen is a LIGHT-GROUND effect: a white top-light on a white button so
   it reads as a raised surface. Left hardcoded it becomes a grey slab on
   obsidian, because 80% white over a near-black ground is just grey. Both
   stops are therefore tokens, and 08-ground.css drops them to a tenth on ink
   where the same gesture only has to suggest an edge. */
.btn-line {
    background-image: linear-gradient(180deg, var(--sheen-face), rgba(255, 255, 255, 0));
    box-shadow: inset 0 1px 0 var(--sheen-edge), 0 2px 8px -4px rgba(10, 14, 26, .18);
}

/* --- Media frames ---------------------------------------------------------
   An inner vignette and a glass edge, so a photograph sits IN its frame rather
   than being pasted on top of one. ::after and not a background, because the
   frame's background is the image. pointer-events:none so it cannot eat a
   click on a card that is a link. */
.card-media,
.cs-cover,
.blog-card-media,
.blog-featured-visual {
    position: relative;
}
.card-media::after,
.cs-cover::after,
.blog-card-media::after,
.blog-featured-visual::after {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(120% 100% at 50% 0%,
                rgba(10, 14, 26, 0) 55%, rgba(10, 14, 26, .16) 100%);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .34);
}

/* --- Small surfaces -------------------------------------------------------
   Badges and chips are 24px tall, so they get the same edge at their own
   scale — a full sweep inside one would land entirely on one letter. */
.badge,
.chip {
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .75),
                inset 0 -1px 0 rgba(10, 14, 26, .05);
}

/* --- The ink bands --------------------------------------------------------
   A very slow, very low-contrast light drift across the dark sections. Bound
   to `view()` rather than an infinite loop on purpose: a forever-animating
   gradient on a full-width band is a permanent compositor job on every page
   that has one, and it moves when the reader is not even looking at it. */
.band-ink { position: relative; }
.band-ink::before {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 0;
    background: linear-gradient(104deg,
                rgba(255, 255, 255, 0) 30%,
                rgba(255, 255, 255, .05) 50%,
                rgba(255, 255, 255, 0) 70%);
    background-size: 260% 100%;
    background-position: 130% 0;
    background-repeat: no-repeat;
}
.band-ink > * { position: relative; z-index: 1; }

@supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
        .band-ink::before {
            animation: band-sheen 1ms linear both;
            animation-timeline: view();
            animation-range: cover 0% cover 100%;
        }
    }
}
@keyframes band-sheen {
    from { background-position: 130% 0; }
    to   { background-position: -30% 0; }
}


/* --- File: css/04-nav.css --- */
/* ==========================================================================
   Navigation — the floating pill bar from reference 3.

   The header is a fixed, transparent strip; the visible object is the pill
   inside it. It gains a shadow once the page scrolls, and shrinks slightly.
   ========================================================================== */

.site-header {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: var(--z-header);
    padding: var(--sp-3) var(--gutter);
    transition: transform var(--dur-3) var(--ease), padding var(--dur-3) var(--ease);
    pointer-events: none;
}
.site-header > * { pointer-events: auto; }

/* Hide on scroll down, show on scroll up — but never while a menu is open */
.site-header.is-hidden { transform: translateY(calc(-100% - 12px)); }
body.nav-open .site-header,
body.modal-open .site-header { transform: none; }

.nav-pill {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    width: 100%;
    max-width: var(--container-w);
    margin-inline: auto;
    height: var(--nav-pill-h);
    padding: 6px 6px 6px var(--sp-5);
    border-radius: var(--r-pill);
    background: rgba(255, 255, 255, .78);
    border: 1px solid rgba(227, 232, 240, .9);
    backdrop-filter: saturate(180%) blur(18px);
    -webkit-backdrop-filter: saturate(180%) blur(18px);
    box-shadow: 0 1px 2px rgba(10, 14, 26, .03);
    transition: background var(--dur-3) var(--ease), box-shadow var(--dur-3) var(--ease),
                border-color var(--dur-3) var(--ease);
}
.site-header.is-stuck .nav-pill {
    background: rgba(255, 255, 255, .92);
    box-shadow: var(--sh-2);
    border-color: rgba(227, 232, 240, 1);
}

.nav-logo { display: flex; align-items: center; flex: none; }
.nav-logo img { height: 30px; width: auto; }

/* --- Desktop links ------------------------------------------------------ */
.nav-menu {
    display: flex;
    align-items: center;
    gap: var(--sp-1);
    list-style: none;
    margin: 0 auto;
    padding: 0;
}
.nav-menu > li { margin: 0; position: relative; }

.nav-link {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 0.5rem 0.875rem;
    border-radius: var(--r-pill);
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--ink-2);
    white-space: nowrap;
    transition: background var(--dur-2) var(--ease), color var(--dur-2) var(--ease);
}
.nav-link:hover { background: var(--paper-3); color: var(--ink); }
.nav-link.is-active { color: var(--ink); font-weight: var(--fw-semi); }
.nav-link .icon { width: 14px; height: 14px; stroke-width: 2.25; transition: transform var(--dur-2) var(--ease); }
.nav-link[aria-expanded="true"] { background: var(--paper-3); color: var(--ink); }
.nav-link[aria-expanded="true"] .icon { transform: rotate(180deg); }

.nav-actions { display: flex; align-items: center; gap: var(--sp-2); flex: none; }

/* --- Dropdown ----------------------------------------------------------- */
.nav-drop {
    position: absolute;
    top: calc(100% + 14px);
    left: 50%;
    translate: -50% 0;
    min-width: 320px;
    padding: var(--sp-3);
    background: var(--paper);
    border-radius: var(--r-lg);
    border: 1px solid var(--line);
    box-shadow: var(--sh-4);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-8px);
    transition: opacity var(--dur-2) var(--ease), transform var(--dur-2) var(--ease),
                visibility var(--dur-2);
}
.nav-drop.is-open { opacity: 1; visibility: visible; transform: none; }
.nav-drop-wide { min-width: 440px; }

.nav-drop-item {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-3);
    padding: var(--sp-3);
    border-radius: var(--r);
    transition: background var(--dur-1) var(--ease);
}
.nav-drop-item:hover { background: var(--paper-2); }
.nav-drop-item .icon-box { width: 36px; height: 36px; border-radius: var(--r-sm); }
.nav-drop-item .icon-box .icon { width: 17px; height: 17px; }
.nav-drop-title { font-size: var(--fs-sm); font-weight: var(--fw-semi); color: var(--ink); line-height: 1.35; }
.nav-drop-desc  { font-size: var(--fs-2xs); color: var(--ink-3); line-height: 1.45; }

.nav-drop-foot {
    margin-top: var(--sp-2);
    padding: var(--sp-3);
    border-top: 1px solid var(--line);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
}

/* --- Toggle ------------------------------------------------------------- */
.nav-toggle {
    display: none;
    place-items: center;
    width: 42px;
    height: 42px;
    border-radius: 50%;
    background: var(--paper-3);
    color: var(--ink);
    flex: none;
}
.nav-toggle .icon { width: 20px; height: 20px; }
.nav-toggle .icon-close { display: none; }
body.nav-open .nav-toggle .icon-close { display: block; }
body.nav-open .nav-toggle .icon-open  { display: none; }

/* ==========================================================================
   Mobile drawer
   ========================================================================== */
.drawer {
    position: fixed;
    inset: 0;
    z-index: var(--z-drawer);
    display: flex;
    flex-direction: column;
    background: var(--paper);
    padding: calc(var(--nav-pill-h) + var(--sp-7)) var(--gutter) var(--sp-7);
    overflow-y: auto;
    overscroll-behavior: contain;
    opacity: 0;
    visibility: hidden;
    transform: translateY(-10px);
    transition: opacity var(--dur-2) var(--ease), transform var(--dur-3) var(--ease-out),
                visibility var(--dur-2);
}
.drawer.is-open { opacity: 1; visibility: visible; transform: none; }
body.nav-open { overflow: hidden; }

.drawer-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.drawer-list > li { margin: 0; border-bottom: 1px solid var(--line); }

.drawer-link {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    width: 100%;
    padding: var(--sp-4) 0;
    font-family: var(--font-display);
    font-size: var(--fs-h4);
    font-weight: var(--fw-semi);
    color: var(--ink);
    text-align: left;
    letter-spacing: var(--tr-heading);
}
.drawer-link .icon { color: var(--ink-3); transition: transform var(--dur-2) var(--ease); }
.drawer-link[aria-expanded="true"] .icon { transform: rotate(180deg); color: var(--accent-fg); }

/* Collapsed sub-panels: `hidden` (not just height 0) so the links inside are
   genuinely out of the tab order. The old build animated height only, which
   left focus landing on invisible links — a real keyboard defect on the one
   navigation phones depend on. */
.drawer-sub {
    list-style: none;
    margin: 0;
    padding: 0 0 var(--sp-4);
    display: grid;
    gap: var(--sp-1);
}
.drawer-sub[hidden] { display: none; }
.drawer-sub > li { margin: 0; border: 0; }
.drawer-sub a {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-3);
    border-radius: var(--r);
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--ink-2);
    background: var(--paper-2);
}
.drawer-sub a:hover { background: var(--paper-3); color: var(--ink); }
.drawer-sub .icon-box { width: 32px; height: 32px; border-radius: var(--r-xs); }
.drawer-sub .icon-box .icon { width: 15px; height: 15px; }

.drawer-foot {
    margin-top: auto;
    padding-top: var(--sp-7);
    display: grid;
    gap: var(--sp-3);
}
.drawer-meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-4);
    padding-top: var(--sp-5);
    font-size: var(--fs-xs);
    color: var(--ink-3);
}
.drawer-meta a { color: var(--ink-2); }

/* Where the fixed .social-rail goes below 1240px. Same 34px discs as the rail
   so it is recognisably the same control, laid out in a row rather than a
   column because the drawer has width and no height to spare. */
.drawer-social {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    padding-top: var(--sp-4);
}
.drawer-social a {
    display: grid;
    place-items: center;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: var(--paper-3);
    color: var(--ink-2);
    transition: background var(--dur-2) var(--ease), color var(--dur-2) var(--ease);
}
.drawer-social a:hover { background: var(--ink); color: #fff; }
.drawer-social .icon { width: 17px; height: 17px; }

/* ==========================================================================
   Breakpoints
   ========================================================================== */
@media (max-width: 1080px) {
    .nav-menu { gap: 0; }
    .nav-link { padding-inline: 0.6875rem; }
}
@media (max-width: 940px) {
    .nav-menu { display: none; }
    .nav-toggle { display: grid; }
    .nav-actions .btn-quote { display: none; }
    .nav-pill { justify-content: space-between; padding-right: 7px; }
}
@media (min-width: 941px) {
    .drawer { display: none; }
}

/* ==========================================================================
   Social rail — restrained. The previous build ran five brand-coloured icons
   down the left edge of every page; colour arrives on hover only.
   ========================================================================== */
.social-rail {
    position: fixed;
    left: var(--sp-4);
    top: 50%;
    translate: 0 -50%;
    z-index: var(--z-sticky);
    display: grid;
    gap: var(--sp-2);
    padding: var(--sp-2);
    border-radius: var(--r-pill);
    background: rgba(255, 255, 255, .7);
    border: 1px solid var(--line);
    backdrop-filter: blur(12px);
    transition: opacity var(--dur-2) var(--ease), visibility var(--dur-2) var(--ease);
}
.social-rail.is-hidden {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}
.social-rail a {
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    color: var(--ink-3);
    transition: background var(--dur-2) var(--ease), color var(--dur-2) var(--ease);
}
.social-rail a:hover { background: var(--paper-3); color: var(--ink); }
.social-rail a .icon { width: 16px; height: 16px; }
@media (max-width: 1240px) { .social-rail { display: none; } }


/* --- File: css/05-footer.css --- */
/* ==========================================================================
   Footer — a near-black slab with rounded top corners, so the page closes on
   an object rather than trailing off.
   ========================================================================== */

.site-footer {
    position: relative;
    background: var(--ink);
    color: var(--on-dark-2);
    border-radius: var(--r-2xl) var(--r-2xl) 0 0;
    margin-top: var(--section-y);
    overflow: hidden;
}
/* When a coloured band closes the page, the footer meets it flush — a white
   gap between two dark surfaces reads as a rendering fault.
   :has() rather than a sibling selector on the band itself, because the band is
   the last child of <main> and the footer is <main>'s sibling, not the band's.
   A general sibling combinator, not an adjacent one: partials/tail.php emits
   the back-to-top button, the sticky CTA, the toast stack and the modal
   between </main> and the footer, so they are never actually adjacent. */
main:has(> section:last-child.band-blue) ~ .site-footer,
main:has(> section:last-child.band-ink) ~ .site-footer,
main:has(> section:last-child.band-ink)  ~ .site-footer { margin-top: 0; border-radius: 0; }

.site-footer::before {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    background:
        radial-gradient(70% 55% at 12% 0%, rgba(22, 82, 224, .30) 0%, rgba(22, 82, 224, 0) 68%),
        radial-gradient(50% 45% at 92% 8%, rgba(255, 107, 53, .12) 0%, rgba(255, 107, 53, 0) 70%);
}
.site-footer > * { position: relative; }

.footer-top {
    padding-block: clamp(3rem, 2rem + 4vw, 5.5rem) var(--sp-9);
    display: grid;
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 2fr);
    gap: clamp(2rem, 1rem + 4vw, 5rem);
}

.footer-brand { display: grid; gap: var(--sp-5); align-content: start; }
.footer-logo img { height: 30px; width: auto; }
.footer-statement {
    font-family: var(--font-display);
    font-size: var(--fs-h3);
    font-weight: var(--fw-bold);
    line-height: 1.2;
    letter-spacing: var(--tr-heading);
    color: #fff;
    margin: 0;
    max-width: 18ch;
}
.footer-blurb { color: var(--on-dark-3); font-size: var(--fs-sm); max-width: 44ch; margin: 0; }

.footer-cols {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--sp-6) var(--sp-5);
}
.footer-col-title {
    font-size: var(--fs-2xs);
    letter-spacing: var(--tr-eyebrow);
    text-transform: uppercase;
    font-weight: var(--fw-semi);
    color: #fff;
    margin: 0 0 var(--sp-4);
}
.footer-links { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-3); }
.footer-links li { margin: 0; }
.footer-links a {
    color: var(--on-dark-3);
    font-size: var(--fs-sm);
    transition: color var(--dur-1) var(--ease);
}
.footer-links a:hover { color: #fff; }

.footer-contact { display: grid; gap: var(--sp-4); }
.footer-contact-row { display: flex; gap: var(--sp-3); align-items: flex-start; font-size: var(--fs-sm); }
.footer-contact-row .icon { color: var(--on-dark-3); margin-top: 0.2em; width: 1.05em; height: 1.05em; }
.footer-contact-row a { color: var(--on-dark-2); }
.footer-contact-row a:hover { color: #fff; }

.footer-social { display: flex; gap: var(--sp-2); margin-top: var(--sp-2); }
.footer-social a {
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    border-radius: 50%;
    background: rgba(255, 255, 255, .07);
    color: var(--on-dark-2);
    transition: background var(--dur-2) var(--ease), color var(--dur-2) var(--ease),
                box-shadow var(--dur-2) var(--ease);
}
.footer-social a:hover { background: rgba(255, 255, 255, .16); color: #fff; box-shadow: 0 4px 12px rgba(10, 99, 255, 0.25); }
.footer-social .icon { width: 16px; height: 16px; pointer-events: none; }

.footer-bottom {
    padding-block: var(--sp-5);
    border-top: 1px solid var(--on-dark-line);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3) var(--sp-5);
    font-size: var(--fs-xs);
    color: var(--on-dark-3);
}
.footer-bottom a { color: var(--on-dark-3); }
.footer-bottom a:hover { color: #fff; }
.footer-legal { display: flex; flex-wrap: wrap; gap: var(--sp-4); }

@media (max-width: 940px) {
    .footer-top { grid-template-columns: 1fr; }
}
@media (max-width: 640px) {
    .footer-cols { grid-template-columns: 1fr; gap: var(--sp-6); }
    .footer-bottom { flex-direction: column; align-items: flex-start; }
}
@media (max-width: 760px) {
    .site-footer { padding-bottom: calc(72px + env(safe-area-inset-bottom)); }
}


/* --- File: css/06-motion.css --- */
/* ==========================================================================
   Motion — ONE vocabulary.

   The previous build ran two reveal systems side by side (an IntersectionObserver
   one and a scroll-timeline one) and they collided whenever both landed on the
   same element. Here there is a single attribute, `data-r`, and two engines that
   are mutually exclusive by @supports:

     • Browsers with `animation-timeline: view()` drive it natively. Resting
       state IS the finished state, so nothing is hidden and no JS is involved.
     • Browsers without it fall back to IntersectionObserver, and their resting
       state is scoped to `.js-motion` (set by js/motion.js) so no-JS still
       renders a complete page.

   `prefers-reduced-motion` disables both and everything sits at its finished
   state.
   ========================================================================== */

/* ==========================================================================
   1. Native scroll-driven timelines — the primary engine
   ========================================================================== */
@supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {

        [data-r] {
            animation-timeline: view();
            animation-range: entry 4% cover 40%;
            animation-fill-mode: both;
            animation-duration: 1ms;   /* required; the timeline supplies progress */
        }

        [data-r="rise"]  { animation-name: r-rise; }
        [data-r="lift"]  { animation-name: r-lift; }
        [data-r="left"]  { animation-name: r-left; }
        [data-r="right"] { animation-name: r-right; }
        [data-r="scale"] { animation-name: r-scale; }
        [data-r="blur"]  { animation-name: r-blur; }
        [data-r="wipe"]  { animation-name: r-wipe; animation-range: entry 6% cover 46%; }
        [data-r="fade"]  { animation-name: r-fade; }

        /* A group whose children arrive in sequence. The stagger is an
           animation-RANGE offset rather than a delay, so scrubbing backwards
           behaves correctly. */
        [data-r="group"] > * {
            animation: r-rise 1ms both;
            animation-timeline: view();
            animation-range: entry 2% cover 34%;
        }
        [data-r="group"] > :nth-child(1) { animation-range: entry  2% cover 30%; }
        [data-r="group"] > :nth-child(2) { animation-range: entry  6% cover 34%; }
        [data-r="group"] > :nth-child(3) { animation-range: entry 10% cover 38%; }
        [data-r="group"] > :nth-child(4) { animation-range: entry 14% cover 42%; }
        [data-r="group"] > :nth-child(5) { animation-range: entry 18% cover 46%; }
        [data-r="group"] > :nth-child(n+6) { animation-range: entry 22% cover 50%; }

        /* A heading that resolves line by line. */
        [data-r="lines"] > * {
            animation: r-line 1ms both;
            animation-timeline: view();
        }
        [data-r="lines"] > :nth-child(1) { animation-range: entry  0% entry 55%; }
        [data-r="lines"] > :nth-child(2) { animation-range: entry  8% entry 64%; }
        [data-r="lines"] > :nth-child(3) { animation-range: entry 16% entry 73%; }
        [data-r="lines"] > :nth-child(4) { animation-range: entry 24% entry 82%; }
        [data-r="lines"] > :nth-child(5) { animation-range: entry 32% entry 91%; }
        [data-r="lines"] > :nth-child(n+6) { animation-range: entry 40% entry 100%; }

        /* An SVG stroke that draws itself. --len is the path length; the
           element sets it, because only the markup knows the path. */
        [data-r="draw"] path,
        [data-r="draw"] line { animation-name: r-draw; animation-range: entry 18% cover 44%; }

        /* A highlighter swipe painted BEHIND the word. It grows the background
           rather than changing the text colour, so the ink stays at full
           contrast for the whole sweep instead of passing through a washed-out
           middle — the failure mode of every "fade the highlight in" version. */
        [data-r="mark"] { animation-name: r-mark; animation-range: entry 20% cover 46%; }

        /* Depth — travels across the whole time the element is on screen. */
        [data-r="parallax"] {
            animation-name: r-parallax;
            animation-range: cover 0% cover 100%;
            animation-timing-function: linear;
        }
    }
}

@keyframes r-rise  { from { opacity: 0; transform: translate3d(0, 34px, 0); } }
@keyframes r-lift  { from { opacity: 0; transform: translate3d(0, 26px, 0) scale(.975); } }
@keyframes r-left  { from { opacity: 0; transform: translate3d(-44px, 0, 0); } }
@keyframes r-right { from { opacity: 0; transform: translate3d(44px, 0, 0); } }
@keyframes r-scale { from { opacity: 0; transform: scale(.9); } }
@keyframes r-blur  { from { opacity: 0; filter: blur(12px); } }
@keyframes r-fade  { from { opacity: 0; } }
@keyframes r-wipe  { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 0 0 0); } }
@keyframes r-line  { from { opacity: 0; transform: translate3d(0, 0.5em, 0); } }
@keyframes r-draw  { from { stroke-dashoffset: var(--len, 400); } to { stroke-dashoffset: 0; } }
/* --mark-h lets the highlighted element choose the band height (.ul-mark in
   03-components.css sets 42%) so only the width sweeps. Defaults to the full
   box for anything that just wants a plain background reveal. */
@keyframes r-mark  { from { background-size: 0 var(--mark-h, 100%); } to { background-size: 100% var(--mark-h, 100%); } }
@keyframes r-parallax {
    from { transform: translate3d(0, calc(var(--depth, 40px) * 1), 0); }
    to   { transform: translate3d(0, calc(var(--depth, 40px) * -1), 0); }
}

/* ==========================================================================
   2. IntersectionObserver fallback — only where timelines are unavailable
   ========================================================================== */
@supports not (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {

        .js-motion [data-r] {
            opacity: 0;
            transition: opacity var(--dur-4) var(--ease-out),
                        transform var(--dur-4) var(--ease-out),
                        filter var(--dur-4) var(--ease-out),
                        clip-path var(--dur-4) var(--ease-out);
        }
        .js-motion [data-r="rise"]  { transform: translate3d(0, 34px, 0); }
        .js-motion [data-r="lift"]  { transform: translate3d(0, 26px, 0) scale(.975); }
        .js-motion [data-r="left"]  { transform: translate3d(-44px, 0, 0); }
        .js-motion [data-r="right"] { transform: translate3d(44px, 0, 0); }
        .js-motion [data-r="scale"] { transform: scale(.9); }
        .js-motion [data-r="blur"]  { filter: blur(12px); }
        .js-motion [data-r="wipe"]  { opacity: 1; clip-path: inset(0 100% 0 0); }
        .js-motion [data-r="parallax"] { opacity: 1; }
        .js-motion [data-r="draw"] { opacity: 1; }
        .js-motion [data-r="draw"] path,
        .js-motion [data-r="draw"] line {
            stroke-dashoffset: var(--len, 400);
            transition: stroke-dashoffset var(--dur-4) var(--ease-out);
        }
        .js-motion [data-r="draw"].is-in path,
        .js-motion [data-r="draw"].is-in line { stroke-dashoffset: 0; }

        .js-motion [data-r="mark"] {
            opacity: 1;
            background-size: 0 var(--mark-h, 100%);
            transition: background-size var(--dur-4) var(--ease-out);
        }
        .js-motion [data-r="mark"].is-in { background-size: 100% var(--mark-h, 100%); }

        .js-motion [data-r].is-in {
            opacity: 1;
            transform: none;
            filter: none;
            clip-path: inset(0 0 0 0);
        }

        .js-motion [data-r="group"] { opacity: 1; }
        .js-motion [data-r="group"] > * {
            opacity: 0;
            transform: translate3d(0, 30px, 0);
            transition: opacity var(--dur-4) var(--ease-out), transform var(--dur-4) var(--ease-out);
            transition-delay: calc(var(--i, 0) * var(--stagger));
        }
        .js-motion [data-r="group"].is-in > * { opacity: 1; transform: none; }

        .js-motion [data-r="lines"] { opacity: 1; }
        .js-motion [data-r="lines"] > * {
            opacity: 0;
            transform: translate3d(0, .5em, 0);
            transition: opacity var(--dur-4) var(--ease-out), transform var(--dur-4) var(--ease-out);
            transition-delay: calc(var(--i, 0) * 90ms);
        }
        .js-motion [data-r="lines"].is-in > * { opacity: 1; transform: none; }
    }
}

/* ==========================================================================
   3. Split text — words, driven by js/motion.js
   ========================================================================== */
.split-word { display: inline-block; will-change: transform; }
.js-motion [data-split] .split-word {
    opacity: 0;
    transform: translate3d(0, 0.45em, 0);
    transition: opacity 620ms var(--ease-out), transform 620ms var(--ease-out);
    transition-delay: calc(var(--w, 0) * 42ms);
}
.js-motion [data-split].is-in .split-word { opacity: 1; transform: none; }

/* ==========================================================================
   4b. data-fx — CONTINUOUS scroll-linked motion

   `data-r` is an entrance: it plays once and ends at a finished state. Most of
   what this pass adds is different — objects that keep moving for as long as
   they are on screen. Putting both on one attribute is exactly the collision
   the note at the top of this file describes, so they get separate namespaces.

   THE ONE RULE: an element's UN-ANIMATED CSS state must be the state you want
   to see. Everything here is symmetric about that pose (from -x to +x, or
   travel-in to settled), so an element with `animation: none` sits exactly
   where it belongs. That is what lets inc/tools/shoot.mjs freeze the page for
   a screenshot, lets audit.mjs assert on a no-JS render, and lets the reduced
   motion switch below stay a one-liner instead of a special case per effect.

   No JS is involved. Knobs are custom properties on the element:
   --travel (in-*), --depth (drift), --turn (spin/tilt/in-*), --grow (zoom).
   ========================================================================== */
@supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {

        [data-fx] {
            animation-timeline: view();
            animation-range: cover 0% cover 100%;
            animation-fill-mode: both;
            animation-duration: 1ms;
            animation-timing-function: linear;
        }

        /* Arrivals from the sides. These resolve well before the section is
           centred — an object still sliding in after you have finished reading
           the heading beside it reads as lag, not as motion. */
        [data-fx="in-left"]  { animation-name: fx-in-left;  animation-range: entry 0% cover 42%; }
        [data-fx="in-right"] { animation-name: fx-in-right; animation-range: entry 0% cover 42%; }
        [data-fx="in-up"]    { animation-name: fx-in-up;    animation-range: entry 0% cover 46%; }

        /* Continuous, symmetric about the resting pose. */
        [data-fx="drift"] { animation-name: fx-drift; }
        [data-fx="spin"]  { animation-name: fx-spin; }
        [data-fx="zoom"]  { animation-name: fx-zoom; }
        [data-fx="tilt"]  { animation-name: fx-tilt; }
    }
}

/* Transform-only, deliberately. The freeze stylesheets in inc/tools/*.mjs
   neutralise animation, transition, opacity, transform, filter and clip-path —
   nothing else. An effect built on the separate `rotate`, `translate` or
   `scale` properties would survive the freeze and photograph mid-move. */
@keyframes fx-in-left {
    from { opacity: 0; transform: translate3d(calc(var(--travel, 34%) * -1), 0, 0) rotate(calc(var(--turn, 5deg) * -1)); }
    to   { opacity: 1; transform: none; }
}
@keyframes fx-in-right {
    from { opacity: 0; transform: translate3d(var(--travel, 34%), 0, 0) rotate(var(--turn, 5deg)); }
    to   { opacity: 1; transform: none; }
}
@keyframes fx-in-up {
    from { opacity: 0; transform: translate3d(0, var(--travel, 60px), 0) scale(.94); }
    to   { opacity: 1; transform: none; }
}
@keyframes fx-drift {
    from { transform: translate3d(0, var(--depth, 40px), 0); }
    to   { transform: translate3d(0, calc(var(--depth, 40px) * -1), 0); }
}
@keyframes fx-spin {
    from { transform: rotate(calc(var(--turn, 10deg) * -1)); }
    to   { transform: rotate(var(--turn, 10deg)); }
}
@keyframes fx-zoom {
    from { transform: scale(calc(1 - var(--grow, .09))); }
    to   { transform: scale(calc(1 + var(--grow, .09))); }
}
@keyframes fx-tilt {
    from { transform: perspective(1200px) rotateX(var(--turn, 6deg)); }
    to   { transform: perspective(1200px) rotateX(calc(var(--turn, 6deg) * -1)); }
}

/* THE FALLBACK, AND WHERE IT STOPS.

   drift, spin, zoom and tilt get NO IntersectionObserver fallback, and that is
   a decision rather than an omission. They are continuous and symmetric about
   the resting pose; an observer can only fire once, and a one-shot transition
   would then contradict the scroll position for the rest of the page. Without
   scroll timelines they sit at the resting pose, which IS the composed layout.

   in-left, in-right and in-up are different in kind. They resolve at cover 42%
   and end at a defined finished state — entrances that happen to live in this
   namespace because they are authored as travel rather than as a reveal. Those
   three DO get the observer, on the same .js-motion path data-r uses, because
   side arrivals are now the site's main motion on every page and "nothing
   happens on this browser" is not an acceptable answer for the main motion.

   js/motion.js only registers these when scroll timelines are ABSENT, so the
   two engines stay mutually exclusive — the collision the note at the top of
   this file was written about. */
@supports not (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {

        .js-motion [data-fx="in-left"],
        .js-motion [data-fx="in-right"],
        .js-motion [data-fx="in-up"] {
            opacity: 0;
            transition: opacity var(--dur-4) var(--ease-out),
                        transform var(--dur-4) var(--ease-out);
        }
        /* Percentages, matching the keyframes — the travel of a device that
           arrives from off-stage has to scale with the device, not with a pixel
           count measured once at 1440. */
        .js-motion [data-fx="in-left"] {
            transform: translate3d(calc(var(--travel, 34%) * -1), 0, 0) rotate(calc(var(--turn, 5deg) * -1));
        }
        .js-motion [data-fx="in-right"] {
            transform: translate3d(var(--travel, 34%), 0, 0) rotate(var(--turn, 5deg));
        }
        .js-motion [data-fx="in-up"] {
            transform: translate3d(0, var(--travel, 60px), 0) scale(.94);
        }

        .js-motion [data-fx="in-left"].is-in,
        .js-motion [data-fx="in-right"].is-in,
        .js-motion [data-fx="in-up"].is-in {
            opacity: 1;
            transform: none;
        }
    }
}

/* ==========================================================================
   5. Page scroll progress
   ========================================================================== */
.scroll-progress {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 3px;
    z-index: calc(var(--z-header) + 1);
    background: var(--blue);
    transform-origin: 0 50%;
    transform: scaleX(0);
    pointer-events: none;
}
@supports (animation-timeline: scroll()) {
    @media (prefers-reduced-motion: no-preference) {
        .scroll-progress {
            animation: sp-grow 1ms linear both;
            animation-timeline: scroll(root block);
        }
    }
}
@keyframes sp-grow { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* ==========================================================================
   6. Idle motion
   ========================================================================== */
.float { animation: float-y 6s var(--ease-in-out) infinite; }
.float-slow { animation: float-y 9s var(--ease-in-out) infinite; }
@keyframes float-y {
    0%, 100% { transform: translate3d(0, 0, 0); }
    50%      { transform: translate3d(0, -12px, 0); }
}

.shake { animation: shake-x 420ms var(--ease); }
@keyframes shake-x {
    0%, 100% { transform: translateX(0); }
    20%, 60% { transform: translateX(-6px); }
    40%, 80% { transform: translateX(6px); }
}

/* ==========================================================================
   7. Reduced motion — one kill switch for everything above
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }

    *, *::before, *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
        scroll-behavior: auto !important;
    }
    /* .arc-TILE, never .arc-cell: the cell's transform is the tile's PLACEMENT
       on the ring, and resetting it would stack all thirteen on the pivot. The
       same distinction is why inc/tools/*.mjs freeze .arc-tile only. */
    /* [data-fx] but NOT [data-fx] > *. A continuous effect sits on the object
       that moves; its children may be carrying their own layout transforms —
       .arc is [data-fx="spin"] and every .arc-cell inside it is placed BY a
       transform, so resetting the children collapses the whole ring onto its
       pivot. The same line was wrong in all three inc/tools/*.mjs freezes. */
    [data-r], [data-r] > *, [data-fx], .split-word, .arc-tile {
        opacity: 1 !important;
        transform: none !important;
        filter: none !important;
        clip-path: none !important;
    }
    [data-r="draw"] path, [data-r="draw"] line { stroke-dashoffset: 0 !important; }
    [data-r="mark"] { background-size: 100% var(--mark-h, 100%) !important; }
    .marquee-track { animation: none !important; }
    .scroll-progress { display: none; }
}


/* --- File: css/07-fx.css --- */
/* ==========================================================================
   EFFECTS — what is left of them.
   ==========================================================================

   This file was 892 lines. It is now about a tenth of that, and the deletion
   is the point rather than a side effect.

   WHAT WENT, AND WHY
   ------------------
   .arc / .arc-tile        a 13-tile ring rotating around the hero. A fourth
                           focal device on a page that should have one.
   .carousel*              a 3-D perspective carousel. A fifth.
   .app-stage / .app-panel a device mockup carrying an "LCP 1.2s / 88%" panel
                           that was hardcoded and unverified.
   .device-laptop/-phone   mockup chrome for that stage.
   .three-stage / .hero-3d / .gl-host / .gl-still
                           the previous build ran a three.js stage AND a WebGL
                           point cloud AND the ring AND the carousel. There is
                           now one object, one canvas, one still, and its
                           styling lives in css/pages/home.css where the
                           composition it belongs to lives.
   .head-3d                a decorative object floating in every inner page's
                           header. Three-dimensional work is allowed in three
                           places on this site — the hero, the exploded
                           assembly and the close — and a page head is not one
                           of them.
   .scribble / .spark      hand-drawn marks in the retired orange. A firm
                           asking a business owner to trust it with their
                           checkout does not underline its headings by hand.
   .t-blue/.t-deep/.t-ink/.t-orange/.t-tint
                           tints for the ring tiles.
   .glow-blue, .badge-glass, .cap-*
                           glow and glass. Both retired with the dark edition.

   Grep before restoring any of them: data-carousel, data-gl and data-cursor
   have no handlers left either.
   ========================================================================== */

/* ==========================================================================
   The numbered process row (+01 … +05)

   Used by service.php and case-studies.php. It reads --accent-fg rather than
   a literal, so it is correct on paper AND inside a chapter with no override
   at all — which is what the two hardcoded orange rules underneath it used to
   be doing by hand, one of them duplicated.
   ========================================================================== */

.steps-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 190px), 1fr));
    gap: var(--sp-5);
    border-top: 1px solid var(--line);
    padding-top: var(--sp-6);
}
.step-item {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    position: relative;
}
.step-num {
    font-family: var(--font-mono);
    font-size: var(--fs-meta);
    letter-spacing: var(--tr-meta);
    font-weight: 500;
    color: var(--accent-fg);
    font-variant-numeric: tabular-nums;
}
.step-title {
    font-family: var(--font-display);
    font-weight: var(--fw-semi);
    color: var(--on-surface);
    font-size: var(--fs-base);
}
.step-text {
    font-size: var(--fs-xs);
    color: var(--on-surface-3);
    margin: 0;
    line-height: 1.6;
}
.step-time {
    font-family: var(--font-mono);
    font-size: var(--fs-meta);
    letter-spacing: var(--tr-meta);
    text-transform: uppercase;
    color: var(--on-surface-3);
}
.band-ink .steps-row { border-color: var(--chapter-line); }

/* ==========================================================================
   The emphasis mark

   Replaces .scribble. A single hairline rule under the phrase, in the one
   accent, drawn on scroll where the browser supports a view timeline and
   simply present where it does not.

   It is a RULE, not a drawing: the previous mark was a hand-sketched curve,
   which is a register that belongs to an illustrator's portfolio rather than
   to an engineering practice.
   ========================================================================== */

.mark {
    position: relative;
    display: inline-block;
    white-space: nowrap;
}
.mark::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: -0.12em;
    height: 2px;
    background: var(--accent-fg);
    transform-origin: left center;
}

@supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
        .mark::after {
            animation: markDraw 1ms linear both;
            animation-timeline: view();
            animation-range: entry 20% cover 40%;
        }
    }
}
@keyframes markDraw {
    from { transform: scaleX(0); }
    to   { transform: scaleX(1); }
}

/* ==========================================================================
   Media frames

   photo() returns an empty string when a file is missing, so a figure with no
   picture in it has to stand on its own rather than showing a grey slab.
   ========================================================================== */

.figure {
    position: relative;
    border-radius: var(--r-lg);
    overflow: hidden;
    background: var(--paper-3);
    box-shadow: var(--elevate-2);
}
.figure img { width: 100%; height: 100%; object-fit: cover; display: block; }
.figure:not(:has(img)) {
    background: linear-gradient(160deg, var(--paper-2), var(--paper-3));
}
.figure-wide { aspect-ratio: 16 / 10; }

/* The page head's own content sits in front of whatever the head paints.
   .page-head is already position:relative (css/08-ground.css), so this is a
   stacking-order guarantee and nothing else. */
.page-head > .container { position: relative; z-index: 1; }



/* ==========================================================================
   HOMEPAGE-LEVEL VISUAL ENHANCEMENTS FOR ALL INTERNAL PAGES
   ========================================================================== */

/* 1. Glassmorphic HUD Cards & Micro-Interactions */
.page-service .card,
.page-service .panel,
.page-service .bento-card,
.page-about .card,
.page-about .panel,
.page-team .card,
.page-team .panel,
.page-case-studies .card,
.page-case-studies .panel,
.page-pricing .card,
.page-pricing .panel,
.page-contact .card,
.page-contact .panel,
.page-blog .card,
.page-blog .panel,
.page-location .card,
.page-location .panel,
.way-card,
.step-item {
    background: color-mix(in srgb, var(--surface) 92%, var(--on-surface) 8%) !important;
    backdrop-filter: blur(12px) saturate(160%);
    -webkit-backdrop-filter: blur(12px) saturate(160%);
    border: 1px solid color-mix(in srgb, var(--accent-fg) 22%, transparent) !important;
    border-radius: var(--r-lg) !important;
    box-shadow: 0 8px 30px rgba(0, 0, 0, 0.08), inset 0 1px 0 rgba(255, 255, 255, 0.2) !important;
    transition: all 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94) !important;
}

.page-service .card:hover,
.page-service .panel:hover,
.page-about .card:hover,
.page-team .card:hover,
.page-case-studies .card:hover,
.page-pricing .card:hover,
.page-contact .card:hover,
.page-blog .card:hover,
.page-location .card:hover,
.way-card:hover {
    transform: translateY(-5px) scale(1.005) !important;
    border-color: var(--accent-fg) !important;
    box-shadow: 0 14px 40px color-mix(in srgb, var(--accent-fg) 18%, transparent), inset 0 1px 0 rgba(255, 255, 255, 0.3) !important;
}

/* 2. Monospace HUD Telemetry Badges & Pulsing Nodes */
.hud-badge,
.chip-mono {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-family: var(--font-mono) !important;
    font-size: var(--fs-meta) !important;
    letter-spacing: var(--tr-meta) !important;
    text-transform: uppercase !important;
    padding: 0.25rem 0.75rem !important;
    border-radius: 9999px !important;
    background: color-mix(in srgb, var(--accent-fg) 10%, transparent) !important;
    border: 1px solid color-mix(in srgb, var(--accent-fg) 30%, transparent) !important;
    color: var(--accent-fg) !important;
    font-weight: 600 !important;
}

/* 3. Glowing Accent Rule Under Headings */
.page-head .display {
    position: relative;
}

.page-head .display::after {
    content: '';
    display: block;
    width: 60px;
    height: 3px;
    background: linear-gradient(90deg, var(--accent-fg), transparent);
    border-radius: 2px;
    margin-top: 1rem;
}

/* ==========================================================================
   RAFLY ANIMATED TYPOGRAPHY SYSTEM
   ========================================================================== */

.rafly-underline {
    position: relative;
    display: inline-block;
    color: inherit;
}
.rafly-underline::after {
    content: '';
    position: absolute;
    left: 0;
    bottom: -0.15em;
    width: 100%;
    height: 3px;
    background: linear-gradient(90deg, var(--blue), var(--accent-fg));
    border-radius: 2px;
    transform-origin: left center;
    transform: scaleX(0);
    transition: transform var(--dur-4) var(--ease-out);
}
.is-in .rafly-underline::after,
.rafly-underline.is-in::after,
.rafly-underline:hover::after {
    transform: scaleX(1);
}

@supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
        .rafly-underline::after {
            animation: raflyUnderlineSweep 1ms linear both;
            animation-timeline: view();
            animation-range: entry 20% cover 45%;
        }
    }
}
@keyframes raflyUnderlineSweep {
    from { transform: scaleX(0); }
    to   { transform: scaleX(1); }
}

/* RAFly Section Heading Entrance & Stagger Reveal */
.rafly-reveal {
    opacity: 0;
    transform: translate3d(0, 30px, 0);
    transition: opacity var(--dur-4) var(--ease-out), transform var(--dur-4) var(--ease-out);
}
.rafly-reveal.is-in,
.is-in .rafly-reveal {
    opacity: 1;
    transform: translate3d(0, 0, 0);
}

.rafly-stagger > * {
    opacity: 0;
    transform: translate3d(0, 24px, 0);
    transition: opacity var(--dur-3) var(--ease-out), transform var(--dur-3) var(--ease-out);
}
.rafly-stagger.is-in > *,
.is-in.rafly-stagger > * {
    opacity: 1;
    transform: translate3d(0, 0, 0);
}
.rafly-stagger > *:nth-child(1) { transition-delay: 0ms; }
.rafly-stagger > *:nth-child(2) { transition-delay: 80ms; }
.rafly-stagger > *:nth-child(3) { transition-delay: 160ms; }
.rafly-stagger > *:nth-child(4) { transition-delay: 240ms; }
.rafly-stagger > *:nth-child(5) { transition-delay: 320ms; }

/* ==========================================================================
   MASTER CUSTOM MAGNETIC CURSOR DESIGN
   ========================================================================== */
.rafly-cursor,
.cursor {
    position: fixed;
    top: 0;
    left: 0;
    width: 32px;
    height: 32px;
    margin-top: -16px;
    margin-left: -16px;
    border-radius: 50%;
    border: 1.5px solid rgba(10, 99, 255, 0.6);
    background: rgba(10, 99, 255, 0.04);
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
    box-shadow: 0 0 16px rgba(10, 99, 255, 0.2);
    pointer-events: none !important;
    z-index: 999999;
    will-change: transform;
    opacity: 0;
    visibility: hidden;
    transition: width 0.25s var(--ease, ease), height 0.25s var(--ease, ease), 
                margin 0.25s var(--ease, ease), background 0.25s var(--ease, ease), 
                border-color 0.25s var(--ease, ease), box-shadow 0.25s var(--ease, ease),
                opacity 0.2s ease, visibility 0.2s ease;
}

@media (pointer: fine) {
    .rafly-cursor,
    .cursor {
        opacity: 1;
        visibility: visible;
    }
}

.rafly-cursor.is-on,
.cursor.is-on {
    opacity: 1;
    visibility: visible;
}

.rafly-cursor .cursor-dot,
.cursor .cursor-dot {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: linear-gradient(135deg, #0a63ff 0%, #00f2fe 100%);
    box-shadow: 0 0 8px #00f2fe;
    pointer-events: none !important;
    transition: width 0.2s ease, height 0.2s ease, background 0.2s ease;
}

.rafly-cursor .cursor-badge,
.cursor .cursor-label {
    position: absolute;
    left: 100%;
    top: 50%;
    transform: translateY(-50%);
    margin-left: 10px;
    font-family: var(--font-mono, monospace);
    font-size: 11px;
    font-weight: 700;
    color: #ffffff;
    background: #0a63ff;
    padding: 3px 10px;
    border-radius: 99px;
    white-space: nowrap;
    box-shadow: 0 4px 12px rgba(10, 99, 255, 0.3);
    pointer-events: none !important;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.2s ease, visibility 0.2s ease;
}

.rafly-cursor.has-badge .cursor-badge,
.cursor.is-explore .cursor-label,
.cursor.is-view .cursor-label,
.cursor.is-open .cursor-label {
    opacity: 1;
    visibility: visible;
}

.rafly-cursor.is-hovering,
.cursor.is-hovering,
.cursor.is-link,
.cursor.is-explore,
.cursor.is-view,
.cursor.is-open {
    width: 50px;
    height: 50px;
    margin-top: -25px;
    margin-left: -25px;
    background: rgba(10, 99, 255, 0.12);
    border-color: #00f2fe;
    box-shadow: 0 0 24px rgba(0, 242, 254, 0.4);
}

.rafly-cursor.is-hovering .cursor-dot,
.cursor.is-hovering .cursor-dot {
    width: 8px;
    height: 8px;
}

.rafly-cursor.is-clicking,
.cursor.is-clicking {
    width: 28px;
    height: 28px;
    margin-top: -14px;
    margin-left: -14px;
    background: rgba(0, 242, 254, 0.25);
    border-color: #0a63ff;
}




/* --- File: css/08-ground.css --- */
/* ==========================================================================
   GROUNDS
   ==========================================================================

   The site is paper. One section of it is not.

   THE MECHANISM
   -------------
   A ground class does not restyle components. It REMAPS the semantic tokens
   the components already read — --surface, --on-surface, --accent-fg, --line
   — so css/03-components.css (1,177 lines) works unchanged on both grounds.
   That is the entire reason the dark chapter costs about 90 lines instead of
   a second copy of the component sheet.

   The corollary, and it is the rule most likely to be broken later: a
   component must read --on-surface / --accent-fg, NOT --ink / --blue. On the
   logo-blue palette --blue is a FILL and is not a legal text colour on any
   ground — 4.07:1 in a paper well, 3.78:1 in the chapter — so `color:
   var(--blue)` is now wrong everywhere rather than wrong in one place, and
   it will still look completely fine on the screen you wrote it on.

   THE THREE PAPERS
   ----------------
   paper-01 is the page. paper-02 marks a chapter boundary. paper-03 is a
   recess. Because they are within a half-step of each other, a section can
   change ground with no rule, no border and no hard edge — the eye reads
   "new part" without being told. That is why there are three of them and why
   none of them is white.
   ========================================================================== */

/* --------------------------------------------------------------- the papers */

.ground-1 { background: var(--paper); }
.ground-2 { background: var(--paper-2); --surface: var(--paper-2); }
.ground-3 { background: var(--paper-3); --surface: var(--paper-3); }

/* A panel sitting ON paper-02 has to step the other way to stay legible as a
   panel, so it takes paper-01 rather than a shadow. Depth by tone, not by
   elevation, is the quieter move and it survives being printed. */
.ground-2 .panel,
.ground-2 .card { background: var(--paper); }

/* ==========================================================================
   THE CHAPTER

   Selected work, and nothing else on the homepage. Inner pages reuse it for
   the closing CTA band, which is the same idea at a smaller scale: a change
   of room to mark a change of register.

   The chapter ground is #050f33 — the same navy that sets every heading on
   paper. Using the ink colour as the ground is why walking into the chapter
   reads as the same room with the lights off rather than as a different site.

   Measured against #050f33:
     --on-chapter-1   #f6f8fc  17.59:1
     --on-chapter-2   #c4cbe4  11.59:1
     --on-chapter-3   #8a96c6   6.47:1
     --accent-chapter #6ea0ff   7.25:1
     --blue           #0a63ff   3.78:1  — non-text minimum only, i.e. a fill
     --blue-ink       #0b52d8   2.86:1  — unreadable here, hence --accent-fg
   ========================================================================== */

.ground-chapter,
.band-ink {
    /* Tells the browser to render form controls, scrollbars and the caret in
       their dark variants. Without it a text input inside the chapter paints
       a white box with a light caret. */
    color-scheme: dark;

    background: var(--ink-chapter);
    color: var(--on-chapter-2);

    --surface:      var(--ink-chapter);
    --surface-2:    var(--ink-chapter-2);
    --surface-3:    var(--ink-chapter-3);
    --surface-line: var(--chapter-line);
    --on-surface:   var(--on-chapter-1);
    --on-surface-2: var(--on-chapter-2);
    --on-surface-3: var(--on-chapter-3);

    /* The mirror pair, and the one line where paper and chapter genuinely
       differ. --accent is NOT remapped: the brand blue is 3.78:1 on this
       ground, which clears the non-text minimum a fill is held to, and white
       on it stays 4.95:1 — so a CTA is the same colour in both rooms, which
       is the point of having one accent. --accent-fg has to lift, because
       --blue-ink is 2.86:1 here and unreadable.

       --blue is deliberately left alone too. On the previous palette it was
       remapped as a safety net for components that read it as text; those no
       longer exist (every `color: var(--blue)` was migrated to --accent-fg
       when this palette landed), and remapping the fill to #6ea0ff would put
       white labels on a light blue at 2.05:1. */
    --accent-fg: var(--accent-chapter);

    /* Paper's own names, remapped, so a component that still reads --ink or
       --paper directly degrades gracefully instead of rendering ink on ink.
       This is a safety net, not a licence — see the rule at the top. */
    --paper:       var(--ink-chapter);
    --paper-2:     var(--ink-chapter-2);
    --paper-3:     var(--ink-chapter-3);
    --ink:         var(--on-chapter-1);
    --ink-2:       var(--on-chapter-2);
    --ink-3:       var(--on-chapter-3);
    --line:        var(--chapter-line);
    --line-strong: var(--chapter-line-strong);

    --pill:     var(--on-chapter-1);
    --pill-fg:  var(--ink-chapter);
    --wash:     rgba(246, 248, 252, .06);
    --blue-tint:   rgba(110, 160, 255, .12);
    --blue-tint-2: rgba(110, 160, 255, .20);

    --sheen-face: rgba(255, 255, 255, .06);
    --sheen-edge: rgba(255, 255, 255, .10);

    --elevate-1: var(--sh-chapter-1);
    --elevate-2: var(--sh-chapter-2);
    --elevate-3: var(--sh-chapter-3);

    /* The focus ring is two rings: a gap in the current ground, then the
       accent. On paper the gap is paper; here it has to become the chapter,
       or the ring renders a white halo around every focused control. */
    --focus-ring: 0 0 0 3px var(--ink-chapter), 0 0 0 6px var(--accent-chapter);
}

.ground-chapter h1, .ground-chapter h2, .ground-chapter h3,
.band-ink h1, .band-ink h2, .band-ink h3 { color: var(--on-chapter-1); }

.ground-chapter .meta,
.band-ink .meta { color: var(--on-chapter-3); }

/* ==========================================================================
   SEAMS

   Where two grounds meet. Never a border: a 1px line between two tones that
   are already a half-step apart is louder than the change it is marking.

   The paper seams are a 96px gradient of the incoming ground — the boundary
   is felt rather than seen. The chapter seams are longer and darker, because
   the step is much bigger and a hard edge there reads as a stacked block.
   ========================================================================== */

.seam-top,
.seam-bottom { position: relative; }

.seam-top::before,
.seam-bottom::after {
    content: '';
    position: absolute;
    left: 0; right: 0;
    height: 96px;
    pointer-events: none;
    z-index: 0;
}
.seam-top::before    { top: 0;    background: linear-gradient(var(--seam-from, transparent), transparent); }
.seam-bottom::after  { bottom: 0; background: linear-gradient(transparent, var(--seam-to, transparent)); }

.seam-paper-2 { --seam-from: var(--paper-2); --seam-to: var(--paper-2); }

/* Entering and leaving the chapter. 180px, and asymmetric on purpose: the
   way in is slower than the way out, so arriving feels like a door and
   leaving feels like daylight returning. */
.ground-chapter.seam-top::before   { height: 180px; background: linear-gradient(var(--paper-2), transparent); opacity: .10; }
.ground-chapter.seam-bottom::after { height: 120px; background: linear-gradient(transparent, var(--paper)); opacity: .12; }

/* ==========================================================================
   PAGE HEADS

   Every inner page opens with one. Paper-02, so the head is legibly a
   different part of the document from the body beneath it, with no rule.
   ========================================================================== */

.page-head {
    position: relative;
    background: var(--paper-2);
    --surface: var(--paper-2);
    padding-block: calc(var(--header-h) + var(--sp-4)) var(--sp-5);
    border-bottom: 1px solid var(--line);
}
.page-head::after {
    content: '';
    position: absolute;
    inset: 0;
    background-image: var(--grain-fine);
    pointer-events: none;
}
.page-head > * { position: relative; z-index: 1; }

/* ==========================================================================
   CHROME OVER A CHAPTER

   The header is fixed and translucent, so when a chapter scrolls under it,
   ink-on-ink. js/ui.js already toggles .over-ink on the header; all this
   does is give that class somewhere to land.
   ========================================================================== */

.site-header.over-ink {
    --surface: var(--ink-chapter);
    --on-surface: var(--on-chapter-1);
    --on-surface-2: var(--on-chapter-2);
    --line: var(--chapter-line);
    color: var(--on-chapter-1);
    background: color-mix(in srgb, var(--ink-chapter) 88%, transparent);
    border-bottom-color: var(--chapter-line);
}
.site-header.over-ink a { color: var(--on-chapter-2); }
.site-header.over-ink a:hover { color: var(--on-chapter-1); }

/* ==========================================================================
   GRAIN

   Applied to grounds, not to <body>, so a fixed background layer never has
   to be invented and the texture scrolls with the surface it belongs to.
   ========================================================================== */

.grain { position: relative; }
.grain::after {
    content: '';
    position: absolute;
    inset: 0;
    background-image: var(--grain-fine);
    pointer-events: none;
    z-index: 0;
}
.grain > * { position: relative; z-index: 1; }

.ground-chapter.grain::after { opacity: .55; }

@media (prefers-reduced-transparency: reduce) {
    .site-header.over-ink { background: var(--ink-chapter); }
}


/* --- File: css/09-scenes.css --- */
/* ==========================================================================
   SCENES — the reusable pieces the approved design is built from
   ==========================================================================

   Loaded LAST, after css/08-ground.css.

   ONE HARD RULE: 09-scenes MAY NEVER DEFINE A GROUND TOKEN.
   Not --surface, not --on-surface, not --accent-fg, not --paper, not --ink.
   Grounds are 08-ground's job and only 08-ground's job. This file may READ
   any of them and may define its own scene-local variables, and that is all.
   It is safe to load this afterwards precisely because of that: custom
   properties resolve per element at use time, so a .ground-chapter ancestor
   still wins over anything written here.

   Everything in this file degrades to a designed still. Every canvas sits
   over a CSS fallback that reproduces the same composition frozen, so a
   visitor with no WebGL2, no JS, reduced motion or Save-Data sees the hero as
   drawn — not a different hero, and never an empty box.
   ========================================================================== */

/* ==========================================================================
   THE FIELD — the page-wide technical background

   One fixed canvas behind everything, painted by js/field.js: right-angle
   circuit traces on a 42px grid with data packets travelling along them, plus
   a cursor-reactive dot network.

   Its fallback is the dot grid below, which is the network's own geometry
   standing still. That is why the fallback is a texture and not a colour: the
   design is a grid of points either way, and only the movement is optional.
   ========================================================================== */

/* .field-canvas, NOT .field. THIS CLASS NAME WAS A REAL BUG, NOT A STYLE
   PREFERENCE: css/03-components.css:432 has used .field for a form field
   wrapper since the first build, and this file loads after it — so every
   labelled input on every page inherited position:fixed, inset:0, z-index:-1
   and a masked dot grid. The contact form, both lead forms and the antibot
   check were all full-viewport fixed overlays stacked behind the page, which
   is why form labels appeared to ghost through at the top of the window at
   deep scroll positions. There was nothing wrong with the compositor; there
   were nine elements genuinely sitting there.

   Caught by listing every fixed and sticky element on the page and finding
   twelve DIV.field among them. */
.field-canvas {
    position: fixed;
    inset: 0;
    /* -1, NOT 0, and this is not a nicety.

       At z-index 0 a fixed canvas forms its own compositing layer and CHANGES
       THE BACKDROP ROOT. The site header uses backdrop-filter, and with the
       backdrop root moved Chrome sampled a different set of layers into it —
       including the consultation modal, which is visibility:hidden and
       opacity:0 but still owns a composited layer. The result was the modal's
       form labels ghosting through the header at the top of every page.

       Sitting at -1 beside .bg-field leaves the backdrop root exactly as it
       was, and the content above needs no z-index of its own. */
    z-index: -1;
    pointer-events: none;
    /* The still form. js/field.js paints over the top of it; when it never
       arrives, this IS the background rather than a placeholder for it. */
    background-image: radial-gradient(color-mix(in srgb, var(--blue) 16%, transparent) 1px, transparent 1px);
    background-size: 42px 42px;
    /* Faded at the edges so it reads as depth rather than as wallpaper. */
    mask-image: radial-gradient(ellipse 90% 70% at 50% 32%, #000 12%, transparent 78%);
    -webkit-mask-image: radial-gradient(ellipse 90% 70% at 50% 32%, #000 12%, transparent 78%);
}

/* Once the canvas is drawing, the CSS grid underneath would double the dots. */
.field-canvas.is-live { background-image: none; }

/* ==========================================================================
   TEXTURES

   Three, and each has one job. They are decoration and are excluded from
   contrast measurement by being behind an opaque or near-opaque ground —
   never over body copy.
   ========================================================================== */

.tex-grid,
.tex-hatch {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 0;
}

/* A denser plotting grid, for a section that should read as a workspace. */
.tex-grid {
    opacity: .45;
    background-image: radial-gradient(color-mix(in srgb, var(--ink) 14%, transparent) 1px, transparent 1px);
    background-size: 32px 32px;
}

/* Engineering hatch, at 5% — felt, not seen. */
.tex-hatch {
    opacity: .05;
    background-image: repeating-linear-gradient(-45deg, var(--ink) 0 1px, transparent 1px 9px);
}

/* Centre-weighted mask. A full-bleed texture reads as wallpaper; the same
   texture fading out at the edges reads as a lit area. */
.tex-mask-c {
    mask-image: radial-gradient(ellipse 72% 60% at 50% 45%, #000 18%, transparent 76%);
    -webkit-mask-image: radial-gradient(ellipse 72% 60% at 50% 45%, #000 18%, transparent 76%);
}

/* A blurred colour orb. Position and size are set per instance; the blur and
   the fact that it never accepts a pointer are not negotiable. */
.orb {
    position: absolute;
    border-radius: 50%;
    pointer-events: none;
    filter: blur(58px);
    z-index: 0;
}

/* WHAT A DECORATED SECTION NEEDS, ONCE, INSTEAD OF SIX TIMES.

   A texture layer is inset:0 absolute and an orb is deliberately larger than
   the box it sits in, so its parent has to establish a containing block, clip,
   and its own stacking context — or the orb bleeds into the section above and
   the texture lands behind the page background.

   overflow: CLIP, never hidden. hidden makes the box a scroll container, and
   any sticky child inside it silently stops pinning. That bug shipped once
   already on .platform; this rule is where it does not come back. */
.has-tex {
    position: relative;
    overflow: clip;
    isolation: isolate;
}
/* Content above the decoration, without a z-index on every child. */
.has-tex > .container { position: relative; z-index: 1; }

/* --- the capability strip ---------------------------------------------
   Between the statement and the platform. css/03-components.css has carried a
   complete marquee since the first build and js/motion.js has cloned its
   track for just as long, and until now nothing on the site used either.

   It is aria-hidden and every word in it appears somewhere else as real copy —
   it is a rhythm between two heavy sections, not a list of claims. */
.strip {
    padding-block: var(--sp-6);
    border-block: 1px solid var(--line);
    background: var(--paper-2);
}
.strip .marquee-track > span {
    font-family: var(--font-display);
    font-size: clamp(1rem, .8rem + .7vw, 1.4rem);
    font-weight: var(--fw-semi);
    letter-spacing: -.01em;
    color: var(--ink-2);
    white-space: nowrap;
}
/* The separator is the logo blue as a FILL — a 6px dot carries no text and is
   the one place --blue is right at this size. */
.strip .marquee-track > i {
    width: 6px; height: 6px;
    border-radius: 50%;
    background: var(--blue);
    flex: none;
}

/* ==========================================================================
   THE HEAD FIELD — every inner page's hero ground

   Rendered by partials/head-object.php, which twelve pages include. Four
   layers, all CSS, no JavaScript:

     grid    the 42px module js/field.js uses on the homepage, so an inner
             page reads as the same system framed tighter rather than as a
             second visual language
     sweep   a soft band crossing it on a 9s loop — the one thing here that
             reads as instrumentation rather than as decoration
     orbs    two blurred accent pools, which is what stops a flat grid from
             looking like graph paper

   IT TAKES THE PAGE ACCENT FOR FREE. Everything reads --accent-fg, which
   css/00-tokens.css remaps per body.svc-*, so each service page's head is in
   that service's own colour with no per-page rule.

   CONTRAST. Every layer is under .page-head content, which css/08-ground.css
   already raises with `.page-head > * { position: relative; z-index: 1 }`.
   The heaviest layer here is the orb at 14% of an accent over paper-02, which
   moves measured luminance by under a hundredth of a ratio point — but
   inc/tools/shoot.mjs re-measures all sixteen routes anyway, because that is
   the whole reason it exists.
   ========================================================================== */

.head-field {
    position: absolute;
    inset: 0;
    overflow: hidden;
    pointer-events: none;
    z-index: 0;

    /* Faded top and bottom so the band has no hard edge against the sections
       either side of it. A page head is a passage, not a panel. */
    mask-image: linear-gradient(180deg, transparent, #000 22%, #000 74%, transparent);
    -webkit-mask-image: linear-gradient(180deg, transparent, #000 22%, #000 74%, transparent);
}

.head-field > * { position: absolute; display: block; }

/* --- the grid ---------------------------------------------------------
   Two layers: hairlines for the module, and a dot at every intersection.
   The dots are what make it read as a NETWORK — a plain ruled grid is
   stationery, and points that could be connected are infrastructure. */
.head-field-grid {
    inset: -1px;
    background-image:
        linear-gradient(color-mix(in srgb, var(--accent-fg) 11%, transparent) 1px, transparent 1px),
        linear-gradient(90deg, color-mix(in srgb, var(--accent-fg) 11%, transparent) 1px, transparent 1px),
        radial-gradient(color-mix(in srgb, var(--accent-fg) 30%, transparent) 1.5px, transparent 1.5px);
    background-size: 42px 42px, 42px 42px, 42px 42px;
    background-position: 0 0, 0 0, 0 0;

    /* Perspective without a transform: the grid fades toward the top, so the
       band reads as a surface receding rather than as wallpaper. A real
       rotateX here would need a 3-D context on every page head and would put
       the hairlines on a sub-pixel grid, which is where a crisp 1px line
       turns into a grey smear. */
    mask-image: linear-gradient(180deg, transparent 8%, #000 58%);
    -webkit-mask-image: linear-gradient(180deg, transparent 8%, #000 58%);
}

/* --- the sweep --------------------------------------------------------
   One soft band travelling left to right. It is deliberately WIDER and
   FAINTER than it wants to be: a tight bright sweep reads as a loading bar,
   which is the one thing a page header must not look like. */
.head-field-sweep {
    top: 0; bottom: 0;
    left: -40%;
    width: 40%;
    background: linear-gradient(90deg,
        transparent,
        color-mix(in srgb, var(--accent-fg) 13%, transparent) 45%,
        color-mix(in srgb, var(--accent-fg) 19%, transparent) 55%,
        transparent);
    filter: blur(22px);
    animation: head-sweep 9s var(--ease-in-out) infinite;
}

@keyframes head-sweep {
    0%        { transform: translateX(0); opacity: 0; }
    12%       { opacity: 1; }
    88%       { opacity: 1; }
    100%      { transform: translateX(350%); opacity: 0; }
}

/* --- the orbs ---------------------------------------------------------
   Two, at different sizes and drifting at different rates, because two pools
   moving in step read as one shape with a seam down the middle. */
.head-field-orb {
    border-radius: 50%;
    filter: blur(58px);
}
.head-field-orb-a {
    width: 520px; height: 520px;
    left: -6%; top: -40%;
    background: radial-gradient(circle,
        color-mix(in srgb, var(--accent-fg) 18%, transparent), transparent 66%);
    animation: head-orb-a 22s var(--ease-in-out) infinite alternate;
}
.head-field-orb-b {
    width: 420px; height: 420px;
    right: 2%; bottom: -46%;
    background: radial-gradient(circle,
        color-mix(in srgb, var(--blue-deep) 12%, transparent), transparent 66%);
    animation: head-orb-b 27s var(--ease-in-out) infinite alternate;
}

@keyframes head-orb-a { to { transform: translate3d(90px, 26px, 0) scale(1.12); } }
@keyframes head-orb-b { to { transform: translate3d(-70px, -20px, 0) scale(1.08); } }

/* On a phone the head is short and the orbs are most of it, which turns the
   band into a coloured blur behind the h1. The grid stays; the pools go. */
@media (max-width: 700px) {
    .head-field-orb { display: none; }
    .head-field-sweep { animation-duration: 13s; }
}

@media (prefers-reduced-motion: reduce) {
    /* The grid IS the design; the sweep and the drift are emphasis on it.
       The sweep is parked mid-band rather than hidden, so the composition
       keeps its highlight and simply stops moving. */
    .head-field-sweep { animation: none; left: 30%; opacity: 1; }
    .head-field-orb   { animation: none; }
}


