/* =========================================================================
   SENTIZONE DEFENCE — "SIGNAL DEPTH"
   Foundation layer. Everything below this block consumes these tokens.

   Depth in this system comes from three places and nowhere else:
     1. a five-step navy surface ladder that shifts hue as it deepens,
     2. two large ambient fields (indigo left, cyan right) that live only
        behind the hero and the closing band — never on a UI element,
     3. real elevation: offset + blur, tinted to the navy. No zero-offset
        colour halos, no neon.
   ========================================================================= */

:root {
    /* ---- Surface ladder ------------------------------------------------ */
    --ink-900: #060b16;   /* footer floor, deepest point of the page       */
    --ink-800: #0a1120;   /* page base                                     */
    --ink-700: #0e1728;   /* raised section band                           */
    --ink-600: #131f35;   /* card / panel surface                          */
    --ink-500: #1a2942;   /* card hover, elevated surface                  */
    --ink-450: #22334f;   /* strongest quiet fill                          */

    /* ---- Text ---------------------------------------------------------- */
    --text-hi:  #eaf1fa;
    --text-mid: #a3b4cc;
    --text-low: #6d7f99;

    /* ---- Signal (recalibrated cyan ramp; each stop has one job) -------- */
    --sig-100: #a5f3fc;   /* cyan text on dark — the only cyan safe as copy */
    --sig-400: #22d3ee;   /* accent marks, focus ring, active state         */
    --sig-600: #0e7490;   /* fills, live hairlines                          */
    --sig-800: #164e63;   /* quiet borders                                  */

    /* ---- Ambient (large fields only — never a UI accent) --------------- */
    --amb-indigo: #141033;
    --amb-cyan:   #0e4a5c;

    /* ---- Hairlines ------------------------------------------------------ */
    --line:        rgba(154, 182, 220, 0.10);
    --line-strong: rgba(154, 182, 220, 0.18);

    /* ---- Elevation (offset + blur, tinted; three levels, no more) ------ */
    --e1: 0 1px 2px rgba(3, 8, 18, 0.50);
    --e2: 0 10px 30px -12px rgba(3, 8, 18, 0.70);
    --e3: 0 28px 70px -20px rgba(3, 8, 18, 0.85);
    --e-top: inset 0 1px 0 rgba(255, 255, 255, 0.05);

    /* ---- Shape (one scale; pill is reserved for the language switcher) - */
    --r-sm: 6px;
    --r-md: 10px;
    --r-lg: 16px;
    --r-pill: 999px;

    /* ---- Paper (the white header and its mobile sheet) ----------------- */
    --paper:          #ffffff;
    --paper-ink:      #0b1626;
    --paper-ink-mid:  #4a5a72;
    --paper-line:     rgba(11, 22, 38, 0.10);
    --paper-hover:    rgba(11, 22, 38, 0.045);

    /* ---- Type ----------------------------------------------------------- */
    --font-heading: 'Outfit', system-ui, -apple-system, 'Segoe UI', sans-serif;
    --font-sans:    var(--font-heading);
    /* A real mono stack. Reserved for code, data and measurement — it is
       deliberately no longer the body face. */
    --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;

    /* Capped at 3.4rem so the hero statement resolves to two lines against a
       40rem measure rather than three narrow ones. */
    --t-display: clamp(2.3rem, 4vw, 3.4rem);
    --t-h2:      clamp(1.85rem, 3vw, 2.7rem);
    --t-h3:      clamp(1.08rem, 1.3vw, 1.3rem);
    --t-lead:    clamp(1.02rem, 1.2vw, 1.18rem);
    --t-body:    clamp(0.95rem, 0.95vw, 1.02rem);
    --t-label:   0.75rem;

    /* Legacy aliases — older page templates still reference these. */
    --text-h1:    var(--t-display);
    --text-h2:    var(--t-h2);
    --text-h3:    var(--t-h3);
    --text-body:  var(--t-body);
    --text-small: var(--t-label);

    /* ---- Space ---------------------------------------------------------- */
    --shell: 1200px;
    --gutter: clamp(20px, 4vw, 40px);
    --page-gutter: var(--gutter);
    --s-section: clamp(5rem, 9vw, 8.5rem);
    /* Slimmer than the 72px it opened at. 64px still clears the 44px minimum
       tap target on everything the bar carries, with 10px of air either side.
       Every hero's height arithmetic, the fixed header's own offset and the
       services page's sticky pin all read this one token, so the bar can be
       re-proportioned here and nowhere else. */
    --header-h: 64px;
}

@media (max-width: 900px) {
    :root { --header-h: 58px; }
}

/* ---- Phone tier ---------------------------------------------------------
   Three structural tiers, and every layout query in this file uses one of
   them so a component can never collapse at a width no other component
   knows about:

     phone   <= 767px   one column
     tablet  768-1023px desktop composition, hamburger nav
     desktop >= 1024px  as authored

   The boundaries are written `767.98px` / `1023.98px`, not `767px` / `1023px`.
   A viewport can be a fractional number of CSS pixels — page zoom, some
   Android densities, a scrollbar taken off an odd width — and a plain `1023px`
   ceiling against a `1024px` floor leaves 1023.5 matching NEITHER tier. That
   gap is not theoretical: a 1023.x-wide emulated viewport hit it during this
   pass and served the desktop pin to a layout sized for the phone one.

   The clamps above were written for a viewport that reaches their upper
   bound; on a 390px screen every one of them pins to its *floor*, which was
   chosen as a desktop minimum, not as a phone value. Re-declaring the floors
   here is what stops the phone inheriting desktop proportions. Same ramp,
   same relationships, one tier down. */
@media (max-width: 767.98px) {
    :root {
        --t-display: clamp(1.95rem, 8.2vw, 2.3rem);
        --t-h2:      clamp(1.55rem, 6.4vw, 1.85rem);
        --t-h3:      1.0625rem;
        --t-lead:    1rem;
        --s-section: clamp(3rem, 11vw, 4rem);
        --gutter:    18px;
        /* One vertical padding shared by every hero on the site. It exists so
           the hero-centring rule near the end of this file can be a single
           exact calculation instead of a per-page guess — see the comment
           there for why that matters. */
        --hero-pad:  1.25rem;
    }
}

/* Global page reset/background.
   The border-box reset is load-bearing, not housekeeping: several components
   in this theme combine `width: 100%` with horizontal padding (timeline rows,
   full-width buttons, the FAQ question row), and under the default content-box
   those overflow the viewport on narrow screens. */
*,
*::before,
*::after { box-sizing: border-box; }

html, body {
    background-color: var(--ink-800);
    margin: 0;
    padding: 0;
    overflow-x: clip;
    /* Pairs with the theme-color meta in header.php. The CSS property is what
       engines that ignore the meta read, and it is also what keeps UA-drawn
       surfaces — scrollbars, form controls, the overscroll glow — from coming
       out of a light default on a document that is dark everywhere. */
    color-scheme: dark;
}

body {
    font-family: var(--font-sans);
    font-size: var(--t-body);
    line-height: 1.7;
    color: var(--text-mid);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* Everything gets the sans face by default. Components that genuinely show
   code, data or measurement opt back into --font-mono explicitly. */
h1, h2, h3, h4, h5, h6,
p, li, a, button, input, textarea, select, span, div {
    font-family: inherit;
}

h1, h2, h3, h4 {
    color: var(--text-hi);
    font-weight: 600;
    letter-spacing: -0.02em;
    line-height: 1.12;
    text-wrap: balance;
}

p { text-wrap: pretty; }

::selection {
    background: var(--sig-600);
    color: #ffffff;
}

/* ---- Shared shell --------------------------------------------------------
   One container, one gutter, used by every section on the home page. */
.shell {
    width: 100%;
    max-width: var(--shell);
    margin: 0 auto;
    padding-inline: var(--gutter);
    box-sizing: border-box;
}

/* ---- Section heading pattern ---------------------------------------------
   A heading and, when the section earns it, one supporting line. No eyebrow,
   no section number, no accent rule above the title. */
.section-head {
    max-width: 46ch;
    margin: 0 0 clamp(2.75rem, 5vw, 4.25rem);
}

.section-head h2 {
    font-size: var(--t-h2);
    margin: 0;
}

.section-head p {
    margin: 1rem 0 0;
    font-size: var(--t-lead);
    line-height: 1.6;
    color: var(--text-mid);
    max-width: 52ch;
}

.section-head--center {
    margin-inline: auto;
    text-align: center;
}
.section-head--center p { margin-inline: auto; }

/* ---- Buttons -------------------------------------------------------------
   Two buttons for the whole site. Both are pill-free, both use the one
   radius scale, both have a real pressed state. */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.6em;
    padding: 0.92em 1.6em;
    border-radius: var(--r-sm);
    border: 1px solid transparent;
    font-family: var(--font-sans);
    font-size: 0.95rem;
    font-weight: 600;
    letter-spacing: -0.005em;
    line-height: 1;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    transition: background-color 0.22s ease, border-color 0.22s ease,
                color 0.22s ease, transform 0.12s ease;
}

.btn:active { transform: translateY(1px); }

.btn .btn-arrow {
    display: inline-block;
    transition: transform 0.28s cubic-bezier(0.16, 1, 0.3, 1);
}
.btn:hover .btn-arrow { transform: translateX(3px); }

.btn-primary,
.btn-primary:visited {
    background: var(--sig-400);
    border-color: var(--sig-400);
    color: #04141b;
}
.btn-primary:hover,
.btn-primary:focus-visible {
    background: var(--sig-100);
    border-color: var(--sig-100);
    color: #04141b;
}

.btn-ghost,
.btn-ghost:visited {
    background: transparent;
    border-color: var(--line-strong);
    color: var(--text-hi);
}
.btn-ghost:hover,
.btn-ghost:focus-visible {
    border-color: var(--sig-600);
    background: rgba(34, 211, 238, 0.06);
    color: var(--text-hi);
}

/* The bar's call to action: `.btn-ghost` at bar size. A size modifier only —
   no new colours — because the header's CTA is deliberately the same object
   as the hero's secondary button, and the hero's cyan primary stays the one
   filled button on the screen. */
.btn-bar {
    min-height: 44px;
    padding: 0 1.15em;
    font-size: 0.875rem;
    /* A hairline border on transparent glass can read as a floating rectangle
       of nothing. A trace of fill gives it a surface without giving it
       weight. */
    background: rgba(234, 241, 250, 0.05);
}

/* On the white mobile sheet the primary button inverts to navy-on-paper. */
.btn-paper,
.btn-paper:visited {
    background: var(--paper-ink);
    border-color: var(--paper-ink);
    color: #ffffff;
    min-height: 44px;
    padding: 0 1.35em;
    font-size: 0.875rem;
}
.btn-paper:hover,
.btn-paper:focus-visible {
    background: #17263c;
    border-color: #17263c;
    color: #ffffff;
}

/* GPU Hardware Lock — keeps animated text and hover-interactive components on
   their own compositor layer permanently. This does two jobs:
     (a) prevents the sub-pixel "snap"/re-rasterization when a reveal animation
         finishes and its transient layer would otherwise be dropped, and
     (b) keeps :hover transforms instant and in-sync — the layer already exists,
         so a hover translateY starts on the compositor without a promotion hitch.
   The reveal JS strips the reveal-* / .is-active classes once an entrance
   animation completes, so those high-specificity transforms never linger to
   override :hover. Do NOT "optimize" this away — removing it desyncs/janks the
   metric, hud, pillar and tenet card hovers. */


/* =========================================================================
   HEADER — a pane of the page's own atmosphere

   The bar used to be a white slab. It was the one light surface on an
   otherwise deep-navy site, so it read as browser chrome pasted above the
   page rather than as part of it, and on a phone it put 58px of flat white
   over a full-bleed hero with a hard seam between them.

   It is now glass: the hero runs the full height of the screen and the bar
   sits ON it, tinted and blurred, so the indigo and cyan fields underneath
   show through. That is the point of the blur here — not decoration, but the
   only way the bar can take its colour from whatever it happens to be over.
   It spans the full width edge to edge; it is deliberately not a floating
   pill with its own margins.

   THE TINT AND THE BLUR NEVER CHANGE. An earlier version condensed both on
   scroll, and it read as fake: on the services hero, where the atmosphere
   behind the bar is the same colour top to bottom, the bar visibly darkened
   under a reader who could see the ground had not moved. Real glass does not
   know whether you have scrolled. Everything that separates the bar from what
   is under it — the hairline, the shadow, the progress rule — is constant too
   or is genuinely about position, never about opacity.

   Reading progress rides the bottom edge: `--read` (0..1, written by app.js)
   scales a cyan rule across it. Not a borrowed blog affordance — it is the
   same gesture the defence-layers spine and the services phase rail make,
   which is the site's signature: a line that fills to say where you are. It
   is the one thing `.is-stuck` still switches, because a rule sitting at 0%
   is a rule that means nothing yet.

   IMPORTANT: `backdrop-filter` lives on the ::before pseudo-element, never on
   `.site-header` itself. A non-`none` backdrop-filter makes the element a
   containing block for fixed-position DESCENDANTS, and `.mobile-menu-panel`
   is a fixed-position child of this header — putting the filter on the header
   would silently re-anchor the whole mobile sheet to a 64px-tall box. The
   pseudo-element has no descendants, so it cannot cause that.
   ========================================================================= */
.site-header {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 10000;
    background: transparent;
    /* One edge, one value, always. A hairline that fades in on scroll is the
       same lie the tint was. */
    border-bottom: 1px solid rgba(154, 182, 220, 0.10);
}

/* The pane. Tint and blur only — every readable thing in the bar sits in
   `.header-inner` above it. */
.site-header::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    /* EXACTLY the mobile sheet's recipe — same tint, same radius, same
       saturation — because they are one object and any difference between
       them shows up the moment the menu opens.

       This ran at 0.22 with saturate(165%) and a top-down gradient, and it was
       wrong twice over. Too little tint meant the bar sat lighter than the
       page it was over rather than settling into it, so the top of every
       screen read as a pale blue band against normal-blue everywhere else;
       and 165% pumped the indigo underneath into a hue the page does not
       otherwise contain, which is what made it look like a colour had been
       added rather than a material laid over. The gradient made the mismatch
       uneven on top of that. One flat tint, one modest saturation lift.
       Saturation is still worth having — it is what stops the indigo and cyan
       washing to grey under a veil — it just should not be inventing colour.

       Contrast is unaffected: every surface this passes over is one of the ink
       steps, and a heavier tint only ever helps the text it carries. */
    background-color: rgba(6, 11, 22, 0.55);
    -webkit-backdrop-filter: blur(30px) saturate(125%);
    backdrop-filter: blur(30px) saturate(125%);
    /* Its own compositor layer. A backdrop-filter on a fixed element sampling
       a canvas that repaints underneath it is the classic mobile flicker, and
       promoting the pane means the blur is composited rather than recomputed
       against a surface that is moving. Safe on the pseudo-element: a
       transform here creates a containing block for descendants, and a
       pseudo-element has none. On `.site-header` itself the same declaration
       would silently re-anchor the fixed mobile sheet. */
    transform: translateZ(0);
}

/* Scrolling changes where you are, not what the bar is made of. The only
   thing it earns is a shadow — there is genuinely something under the bar now
   — and the progress rule below. */
.site-header.is-stuck {
    box-shadow: 0 20px 44px -32px rgba(0, 0, 0, 0.95);
    transition: box-shadow 0.3s ease;
}

/* No backdrop-filter support, or a reader who has asked for less
   transparency: the bar becomes a plain dark band. It still belongs to the
   palette, it just stops being a window. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .site-header::before { background-color: rgba(6, 11, 22, 0.93); }
}
@media (prefers-reduced-transparency: reduce) {
    .site-header::before {
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
        background-color: rgba(6, 11, 22, 0.96);
    }
}

/* Reading progress. Sits over the hairline rather than beside it, so the bar
   keeps exactly one bottom edge. Hidden at the top of the page — a progress
   rule at 0% is a rule that means nothing yet — and appears with the shadow. */
.site-header::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: -1px;
    height: 2px;
    background: var(--sig-400);
    transform: scaleX(var(--read, 0));
    transform-origin: left;
    opacity: 0;
    transition: opacity 0.3s ease;
    pointer-events: none;
    z-index: 21;
}
.site-header.is-stuck::after { opacity: 1; }

.site-header .header-inner { position: relative; z-index: 20; }

/* The sheet the toggle opens is glass on the same recipe as this bar, so
   nothing in the header flips when it opens. The bar keeps its tint, the
   lockup keeps its ink, the toggle keeps its light glyph, and the whole
   header/sheet object is one continuous piece of the same material. The one
   suppression left is the progress rule: with a full-height sheet in front of
   the document, a line reporting how far down that document you are is
   reporting on something you can no longer see. */
.site-header:has(.mobile-menu-panel.open)::after { opacity: 0; }

/* Three-column grid rather than an absolutely-positioned centre slot. The
   nav is centred on the container, and the outer columns can never overlap
   it as they grow. */
.header-inner {
    max-width: var(--shell);
    margin: 0 auto;
    height: var(--header-h);
    padding-inline: var(--gutter);
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 1rem;
    position: relative;
}

.header-left  { justify-self: start; min-width: 0; }
.header-center{ justify-self: center; }
.header-right { justify-self: end; display: flex; align-items: center; gap: 0.5rem; }

/* ---- Brand --------------------------------------------------------------
   `center`, not `baseline`. With a logo image in the flex row, baseline
   alignment resolves against the image's bottom edge and drops the wordmark
   below the logo's optical centre. */
.site-branding { display: flex; align-items: center; gap: 0.65rem; }

/* The lockup. `SENTIZONE` at 700 beside `Defense` at 300 — the same weight
   step the hero sets the name in at 80px (see .hero-wordmark), which is the
   brand's one typographic idea and the reason the bar and the hero read as
   the same object. The split is built in header.php from the stored site
   name, so this is only ever styling two spans.

   Negative tracking, not the positive value the old caps block carried: at
   this size the caps set loose enough already, and letterspaced capitals are
   exactly the treatment the rest of the site retired. */
.site-branding .site-title {
    margin: 0;
    font-size: 1rem;
    font-weight: 700;
    letter-spacing: -0.015em;
    line-height: 1;
}

/* `center`, not `baseline`. The two spans are the same face at the same size,
   so their baselines coincide under either value — but with `align-items:
   baseline` the whole line parks at the top of the 44px tap target instead of
   in the middle of it. */
.site-branding .site-title a {
    display: inline-flex;
    align-items: center;
    gap: 0.32em;
    min-height: 44px;
    color: var(--text-hi);
    text-decoration: none;
    transition: color 0.2s ease;
}

.site-title-mark { font-weight: 700; }

/* Weight is the ONLY difference between the two halves. Not colour, not
   opacity — the site's rule is that emphasis comes from weight and size and
   never from a second value, and the hero sets this same lockup that way.
   Lighter weights set narrower, so the tail takes slightly less negative
   tracking to share the mark's rhythm; the hero wordmark makes exactly the
   same correction for exactly the same reason. */
.site-title-tail {
    font-weight: 300;
    letter-spacing: -0.005em;
}

.site-branding .site-title a:hover { color: #ffffff; }

/* The lockup already carries the name in two weights; a tagline hung off it
   would be a third string in a 64px bar. It stays in the markup for anyone
   who has set one, at the quietest weight the bar has. */
.site-branding .site-description {
    margin: 0;
    font-size: 0.7rem;
    font-weight: 500;
    letter-spacing: 0.02em;
    line-height: 1;
    color: var(--text-low);
    padding-left: 0.7rem;
    border-left: 1px solid var(--line-strong);
    white-space: nowrap;
}

/* Belt as well as braces on the logo link. The alt text is empty now (see
   my_raw_theme_custom_logo_attributes in functions.php), but a logo can also
   fail to load for reasons the theme does not control — a moved upload, a
   blocked host, an offline device. Inheriting the bar's own ink means the
   worst case is a blank space rather than a stray blue link in the corner of
   an otherwise finished header. */
.custom-logo-link {
    display: inline-flex;
    align-items: center;
    color: var(--text-hi);
    text-decoration: none;
    flex: 0 0 auto;
}

/* The cap is derived from the bar height so the two stay in proportion, but
   the subtrahend has to be re-checked whenever `--header-h` moves. It was
   authored as `- 30px` against a 72px desktop bar (42px logo); the pass that
   slimmed the bar to 64/58 shrank the logo to 34/28 as an unintended side
   effect, and at 28px a portrait mark in a 58px bar reads as missing rather
   than small. `- 20px` gives 44px desktop / 38px mobile — the desktop value
   matching `.site-title a`'s own 44px min-height exactly, so the logo and the
   wordmark beside it occupy the same row height and the bar cannot grow. */
.site-branding img {
    max-height: calc(var(--header-h) - 20px);
    /* A broken image reports its intrinsic width (512px here), which would
       shove the title and the toggle off the bar. Capping the box means the
       failure stays the size of the logo. */
    max-width: calc(var(--header-h) - 20px);
    width: auto;
    display: block;
    object-fit: contain;
    flex: 0 0 auto;
    /* The supplied mark is a navy-to-blue gradient shield. Its dark end is
       within a few percent of the bar's own ground, so on glass the whole
       lower-left of the shield disappears and what is left reads as a broken
       image rather than as a logo. Flattened to a near-white silhouette here:
       the mark's three-swoosh shape is what identifies it, not the ramp, and
       a single-value mark is also what the rest of this site does — colour is
       reserved for state, never for decoration.

       It reverts to the full-colour original on paper (the mobile sheet,
       below), which is the one place the ramp reads correctly. */
    filter: brightness(0) invert(1);
    opacity: 0.94;
    transition: opacity 0.2s ease;
}
.custom-logo-link:hover img { opacity: 1; }

/* Below 1240px the description is the first thing to go, then the CTA label
   shortens, so the bar never wraps to a second line. */
@media (max-width: 1240px) {
    .site-branding .site-description { display: none; }
}


/* ---- Navigation ---------------------------------------------------------
   Plain text on glass. Sentence case, no tracking, no uppercase. The only
   affordance is a 2px accent rule that draws in from the left on hover and
   stays put on the current page.

   The rule is --sig-400, not the --sig-600 it was on the white bar. 600 is
   the fills-and-hairlines stop, calibrated to sit quietly against paper; on
   a dark ground it goes nearly invisible. 400 is the ramp's accent/active
   stop and is the right one here — the same value the mobile sheet's current
   -page underline and the reading-progress rule already use. */
.main-navigation ul {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    align-items: center;
    gap: clamp(0.25rem, 1.6vw, 1.5rem);
}

.main-navigation li { margin: 0; }

.main-navigation a {
    position: relative;
    display: inline-flex;
    align-items: center;
    /* 44px minimum tap target. The underline is positioned off the element's
       bottom edge, so growing the box vertically would push it away from the
       text; it is re-anchored to the text baseline below instead. */
    min-height: 44px;
    padding: 0 0.25rem;
    color: var(--text-mid);
    text-decoration: none;
    font-family: var(--font-sans);
    font-size: 0.9375rem;
    font-weight: 500;
    letter-spacing: -0.005em;
    text-transform: none;
    white-space: nowrap;
    transition: color 0.22s ease;
}

.main-navigation a::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    /* Sits just under the text rather than at the bottom of the enlarged tap
       target, so the rule still reads as an underline. */
    bottom: calc(50% - 0.95em);
    height: 2px;
    background: var(--sig-400);
    transform: scaleX(0);
    transform-origin: left;
    transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}

.main-navigation a:hover,
.main-navigation a:focus-visible { color: var(--text-hi); }

.main-navigation a:hover::after,
.main-navigation a:focus-visible::after { transform: scaleX(1); }

.main-navigation a,
.main-navigation a:visited,
.main-navigation a:active { color: var(--text-mid); }

/* Current page */
.main-navigation .current-menu-item > a,
.main-navigation .current_page_item > a,
.main-navigation .current-menu-ancestor > a {
    color: var(--text-hi);
    font-weight: 600;
}
.main-navigation .current-menu-item > a::after,
.main-navigation .current_page_item > a::after,
.main-navigation .current-menu-ancestor > a::after { transform: scaleX(1); }

@media (max-width: 1240px) {
    .main-navigation a { font-size: 0.875rem; }
    .header-inner { gap: 0.5rem; }
}

.mobile-menu-list a,
.mobile-menu-list a:visited,
.mobile-menu-list a:active,
.mobile-menu-list a:focus {
    color: var(--text-mid);
}

/* Generic anchors inside cards that should not shift colour once visited */
.hud-card a,
.hud-card a:visited,
.hud-card a:active,
.hud-card a:focus {
    color: inherit;
}


/* Mobile: hide centered nav and show toggle.
   The toggle lives inside the glass bar, so it is a light glyph on the hero
   with no chrome of its own — and it flips to dark ink the moment the bar
   becomes paper, i.e. the moment the sheet it opens is on screen. */
.nav-toggle{
    display: none; /* shown at <=900px */
    position: relative;
    background: transparent;
    border: none;
    box-shadow: none;
    width: 44px;
    height: 44px;
    border-radius: var(--r-sm);
    cursor: pointer;
    padding: 0;
    align-items: center;
    justify-content: center;
    transition: background-color 0.2s ease;

    /* Prevent double-tap-to-zoom on touch devices while preserving pan gestures */
    touch-action: manipulation;
    -ms-touch-action: manipulation;
    -webkit-touch-callout: none;
    -webkit-user-drag: none;
}

.nav-toggle:hover { background: rgba(234, 241, 250, 0.08); }
/* Remove mobile tap highlight and browser focus ring for the circular nav toggle */
.nav-toggle {
    -webkit-tap-highlight-color: transparent !important;
    -webkit-user-select: none !important;
    user-select: none !important;
    -webkit-appearance: none !important;
    appearance: none !important;
}

/* Remove the outline / border shown on click/tap (forced) */
.nav-toggle:focus,
.nav-toggle:active {
    outline: none !important;
    box-shadow: none !important;
    border-color: transparent !important;
    border: none !important;
}

/* Firefox inner focus for <button> */
.nav-toggle::-moz-focus-inner { border: 0; }

/* Accessible alternative: show a visible ring only for keyboard users */
.nav-toggle:focus-visible {
    outline: 2px solid rgba(0,230,255,0.35);
    outline-offset: 3px;
}
/* iOS / Safari overrides: aggressively suppress tap/focus highlights and callouts.
   Uses a feature-detection block to limit scope to WebKit-based platforms that
   advertise touch-scrolling support. This helps remove the persistent white
   border/tap highlight seen on some iPhones. */
@supports (-webkit-overflow-scrolling: touch) {
    .nav-toggle,
    .nav-toggle * {
        -webkit-tap-highlight-color: transparent !important;
        -webkit-user-select: none !important;
        user-select: none !important;
        -webkit-touch-callout: none !important;
        touch-action: manipulation !important;
        outline: none !important;
        box-shadow: none !important;
        border: none !important;
    }
    /* Extra blanket rule to reduce chance of UA highlight on parent/root */
    html, body { -webkit-tap-highlight-color: transparent !important; }
}
/* Two bars, not three, and asymmetric while closed: the lower one is short.
   Opening resolves both to the same length as they cross — the mark is
   uneven at rest and exact when open, which is a small piece of character
   the default three-bar glyph does not have. */
.nav-toggle .hamburger{
    display: block;
    width: 22px;
    height: 11px;
    position: relative;
    z-index: 2;
}
.nav-toggle .bar{
    display: block;
    position: absolute;
    left: 0;
    height: 1.5px;
    background: var(--text-hi);
    border-radius: 1px;
    transition: top 0.34s cubic-bezier(.4,0,.2,1),
                width 0.34s cubic-bezier(.16,1,.3,1),
                transform 0.34s cubic-bezier(.4,0,.2,1);
    z-index: 3;
}
.nav-toggle .bar1{ top: 0;    width: 22px; }
.nav-toggle .bar2{ top: 9.5px; width: 14px; }

.nav-toggle.active .bar1{
    top: 4.75px;
    width: 22px;
    transform: rotate(45deg);
}
.nav-toggle.active .bar2{
    top: 4.75px;
    width: 22px;
    transform: rotate(-45deg);
}

@media (prefers-reduced-motion: reduce) {
    .nav-toggle .bar { transition: none; }
}

/* Aggressively suppress any UA tap/focus highlights that may appear
   on some iOS/Safari builds. This applies to the toggle, its pseudo
   underlay, and the inner bars. Use !important to beat user-agent rules. */
.nav-toggle,
.nav-toggle::before,
.nav-toggle .bar,
.nav-toggle * {
    -webkit-tap-highlight-color: transparent !important;
    -webkit-touch-callout: none !important;
    touch-action: manipulation !important;
    -ms-touch-action: manipulation !important;
    outline: none !important;
    box-shadow: none !important;
    border: none !important;
}

/* Pointer states only. `:focus-visible` is deliberately NOT in this list: the
   keyboard ring for this button is declared once, with everything else's, in
   the Keyboard focus block near the end of this file. Suppressing it here as
   well left two rules disagreeing about the same button, and the later one
   won anyway — so the suppression read as intent that was never in force. */
.nav-toggle:focus,
.nav-toggle:active {
    outline: none !important;
    box-shadow: none !important;
    border: none !important;
}

/* =========================================================================
   MOBILE MENU — a glass sheet that continues the bar downward, so opening the
   menu never flips the user into a different visual world.

   It was white paper, back when the bar was. Now the bar is glass, and the
   sheet is the same material on the same recipe — a heavier tint and a wider
   blur, because a full screen of it has to hold display type over whatever
   section the reader happened to stop on. Nothing in the header changes state
   when it opens; the sheet simply extends the bar down the screen.

   It used to be a list of 17px links with a button under it and two thirds
   of a phone screen left blank below that. It is now a column that actually
   uses the sheet: the links are set at display size, the way a full screen
   given over to four words deserves, and the foot is pushed to the bottom
   with the CTA and the two lines someone opening a menu on a phone is most
   likely hunting for. Each row rises in on open, sequenced — the site's one
   authored gesture, applied to the one component that never had it.
   ========================================================================= */
.mobile-menu-panel{ display: none; }
.mobile-menu-list{ list-style: none; margin: 0; padding: 0; }
.mobile-menu-list li{ margin: 0; }

.mobile-menu-list a{
    position: relative;
    display: inline-flex;
    align-items: center;
    padding: 0.45rem 0;
    color: var(--text-mid);
    text-decoration: none;
    font-family: var(--font-sans);
    font-size: clamp(1.75rem, 8.5vw, 2.375rem);
    font-weight: 600;
    letter-spacing: -0.035em;
    line-height: 1.15;
    text-transform: none;
    /* transform, not padding — the nudge must not relayout the sheet. */
    transition: color 0.22s ease, transform 0.28s cubic-bezier(0.16, 1, 0.3, 1);
}
/* :focus-visible, not :focus. The panel moves focus to its first link when it
   opens (see app.js), so a plain :focus rule meant the top item — Home — came
   up full ink and nudged 6px right every single time the menu was opened,
   on every page. It read as Home being the current page, and as one row being
   mis-aligned with the other three. The nudge is a pointer/keyboard
   affordance; it should not fire on programmatic focus. */
.mobile-menu-list a:hover,
.mobile-menu-list a:focus-visible{
    color: var(--text-hi);
    transform: translateX(6px);
}

/* The page you are on: full ink, and the same cyan underline the desktop bar
   uses for the same state — one indicator, two breakpoints. */
.mobile-menu-list .current-menu-item > a,
.mobile-menu-list .current_page_item > a{ color: var(--text-hi); }

.mobile-menu-list .current-menu-item > a::after,
.mobile-menu-list .current_page_item > a::after{
    content: '';
    position: absolute;
    left: 0;
    bottom: 0.5rem;
    width: 2.25rem;
    height: 2px;
    background: var(--sig-400);
}

@media (max-width: 900px){
    /* Two columns, not three. `display:none` on the centre slot removes it
       from grid flow entirely, so with a three-column template the right slot
       auto-placed into the middle column and stopped short of the container
       edge. That is what was leaving the hamburger un-right-aligned. */
    .header-inner{ grid-template-columns: 1fr auto; align-items: center; }
    .header-center{ display: none; }
    /* The primary action moves to the foot of the sheet at this width, so
       there is exactly one of it per viewport. */
    .header-right .btn-bar{ display: none; }

    .nav-toggle{
        display: inline-flex;
        align-items:center;
        justify-content:center;
        position: relative;
        z-index: 100002;
        margin-right: -10px;
        touch-action: manipulation;
        -ms-touch-action: manipulation;
        -webkit-touch-callout: none;
        -webkit-user-drag: none;
    }

    .mobile-menu-panel{
        display: flex;
        flex-direction: column;
        position: fixed;
        top: var(--header-h);
        right: 0;
        left: 0;
        height: calc(var(--vh, 1vh) * 100 - var(--header-h));
        height: calc(100dvh - var(--header-h));
        /* Same material as the bar, barely heavier. The first pass ran this at
           0.72 against the bar's 0.22 and the two read as separate panels with
           a step between them — the sheet is supposed to be the bar continuing
           down the screen, not a second object under it. 0.55 is as close as
           the two can get while the sheet still holds 38px display type over
           whatever section the reader stopped on; the menu links are large
           text, so 3:1 is the floor they have to clear and they clear it
           against every ink step on the site with room to spare.

           It is still the page behind it that supplies the colour, which is
           why the sheet picks up indigo on the home hero and the cyan field on
           services. */
        background-color: rgba(6, 11, 22, 0.55);
        -webkit-backdrop-filter: blur(30px) saturate(125%);
        backdrop-filter: blur(30px) saturate(125%);
        box-shadow: 0 24px 48px -28px rgba(0, 0, 0, 0.65);
        border-top: 1px solid var(--line);
        z-index: 100001;
        padding: clamp(1.75rem, 6vw, 2.5rem) var(--gutter) clamp(1.5rem, 5vw, 2rem);
        box-sizing: border-box;
        overflow-y: auto;
        /* Stops a flick inside the sheet from scrolling the page behind it,
           without locking the body — which would make the document a scroll
           container and break the services page's sticky pin. */
        overscroll-behavior: contain;
        transition: transform 0.42s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.28s ease;
        transform: translateY(-8px);
        opacity: 0;
        pointer-events: none;
        will-change: transform, opacity;
    }
    .mobile-menu-panel.open{
        transform: translateY(0);
        opacity: 1;
        pointer-events: auto;
    }

    /* The cookie banner is fixed at z-index 999999, and the panel is a child
       of the header — so no z-index on the panel itself can clear it, because
       the header's own stacking context caps at 10000. Lift the header for
       exactly as long as the menu is open; the banner is back on top the
       moment it closes, so consent is never permanently buried. Without
       this, the banner sits over the foot of the sheet, which is where the
       menu's call to action now lives. */
    .site-header:has(.mobile-menu-panel.open) { z-index: 1000000; }

    /* Scroll lock while the sheet is open. `top` is written inline by app.js
       as the negative of the scroll offset, which is what keeps the page
       looking still instead of snapping to the top the instant body leaves
       flow; see the lockScroll comment there for why this is done with
       out-of-flow positioning rather than `overflow: hidden`. */
    body.scroll-locked {
        position: fixed;
        left: 0;
        right: 0;
        width: 100%;
    }

    .mobile-menu-nav { flex: 0 0 auto; }

    /* Sequenced entrance. The rows are the content; they should arrive after
       the sheet, not with it. Delays are per-position because the markup
       comes from wp_nav_menu and cannot carry a --i of its own. */
    .mobile-menu-list li,
    .mobile-menu-foot > * {
        opacity: 0;
        transform: translateY(14px);
        transition: opacity 0.42s ease, transform 0.55s cubic-bezier(0.16, 1, 0.3, 1);
    }
    .mobile-menu-panel.open .mobile-menu-list li,
    .mobile-menu-panel.open .mobile-menu-foot > * {
        opacity: 1;
        transform: none;
    }
    .mobile-menu-panel.open .mobile-menu-list li:nth-child(1) { transition-delay: 0.06s; }
    .mobile-menu-panel.open .mobile-menu-list li:nth-child(2) { transition-delay: 0.11s; }
    .mobile-menu-panel.open .mobile-menu-list li:nth-child(3) { transition-delay: 0.16s; }
    .mobile-menu-panel.open .mobile-menu-list li:nth-child(4) { transition-delay: 0.21s; }
    .mobile-menu-panel.open .mobile-menu-list li:nth-child(5) { transition-delay: 0.26s; }
    .mobile-menu-panel.open .mobile-menu-list li:nth-child(6) { transition-delay: 0.31s; }
    .mobile-menu-panel.open .mobile-menu-list li:nth-child(7) { transition-delay: 0.36s; }
    .mobile-menu-panel.open .mobile-menu-list li:nth-child(n+8) { transition-delay: 0.4s; }
    .mobile-menu-panel.open .mobile-menu-foot > *:nth-child(1) { transition-delay: 0.3s; }
    .mobile-menu-panel.open .mobile-menu-foot > *:nth-child(2) { transition-delay: 0.36s; }

    /* Pushed to the bottom of the sheet — this is what stops a four-item menu
       leaving most of a phone screen empty. */
    .mobile-menu-foot {
        margin-top: auto;
        padding-top: clamp(1.75rem, 6vw, 2.5rem);
    }

    /* The CTA the desktop bar carries reappears at the foot of the sheet. It
       is filled here, not ghost: with the sheet covering the screen there is
       no hero button left to compete with, and this is the one action the
       whole surface is for. */
    .mobile-menu-panel .btn-primary{
        display: inline-flex;
        width: 100%;
        padding: 1rem 1.25rem;
        font-size: 0.95rem;
    }

    .mobile-menu-lines {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 0.5rem 1.25rem;
        margin: 1.5rem 0 0;
        padding-top: 1.25rem;
        border-top: 1px solid var(--line);
    }
    .mobile-menu-lines dt {
        font-size: 0.8125rem;
        color: var(--text-low);
        margin-bottom: 0.125rem;
    }
    .mobile-menu-lines dd { margin: 0; }
    .mobile-menu-lines dd a {
        display: inline-flex;
        align-items: center;
        min-height: 44px;
        font-size: 0.9375rem;
        font-weight: 600;
        letter-spacing: -0.01em;
        color: var(--text-hi);
        text-decoration: none;
        transition: color 0.22s ease;
    }
    .mobile-menu-lines dd a:hover,
    .mobile-menu-lines dd a:focus { color: var(--sig-100); }
}

@media (prefers-reduced-motion: reduce) {
    .mobile-menu-list li,
    .mobile-menu-foot > * {
        opacity: 1 !important;
        transform: none !important;
        transition: none !important;
    }
    .mobile-menu-panel { transition: opacity 0.2s ease; }
}

/* =========================================================================
   HERO — lattice depth, left-weighted composition
   The canvas paints the atmosphere (see app.js): a diagonal indigo-to-navy
   backdrop, two static ambient fields, and a still dot lattice that only
   moves under the pointer. The copy sits in the left half; the lattice is
   allowed to be the visual on the right.
   ========================================================================= */

/* Hero and the credibility band share one viewport-height block, so the band
   is on screen at load rather than a scroll away. The hero flexes to fill
   whatever the band does not use, which keeps the arithmetic exact no matter
   how the band's content reflows. */
/* The bar is glass, so this block starts at the top of the screen rather than
   below a 64px band, and the header's own height is held INSIDE the canvas as
   padding instead (see .cyber-hero-canvas). Every number below is the old one
   plus --header-h, which is what keeps the composition byte-identical: the
   block still ends exactly at the fold, and the shell still centres in the
   space the bar leaves. Only the atmosphere gained ground — it now paints the
   full screen, including the strip the bar sits on. */
.hero-block {
    /* --vh is set by app.js from visualViewport; the dvh line below wins where
       supported and needs no JS at all. */
    height: calc(var(--vh, 1vh) * 100);
    height: 100dvh;
    /* svh last: a viewport-tracking height inside the hero is what the canvas
       ResizeObserver reads, and any change to it reallocates the WebGL buffer
       mid-scroll. See the hero-copy note near the end of this file. */
    height: 100svh;
    min-height: calc(640px + var(--header-h));
    max-height: calc(1060px + var(--header-h));
    display: flex;
    flex-direction: column;
}

.cyber-hero-canvas {
    position: relative;
    width: 100%;
    flex: 1 1 auto;
    min-height: 0;
    background: var(--ink-800);
    overflow: hidden;
    isolation: isolate;
    display: flex;
    align-items: center;
    /* The band the glass bar covers. Padding, not margin, and this is the
       whole trick: the painted canvas and the veil are absolutely positioned
       at `inset: 0`, which resolves against the PADDING box, so both still
       cover the full screen and show through the bar. `.hero-shell` is a flex
       child laid out in the content box, so it centres in the space below the
       bar exactly as it did when the bar had its own band. */
    padding-top: var(--header-h);
    /* The hero is a surface you push on, not a passage you read. Dragging
       across the field to move the light was starting a text selection and
       leaving the headline highlighted behind the cursor — two gestures on one
       drag, and the wrong one won. Turning selection off inside the hero is
       what makes the drag mean only the thing it looks like it means.
       Deliberately scoped to the hero: every other surface on the site stays
       selectable and copyable. */
    -webkit-user-select: none;
    user-select: none;
}

.cyber-hero-canvas canvas {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 1;
}

/* The veil does two jobs: it lifts contrast under the copy on the left, and
   it dissolves the lattice into the next section at the bottom so the hero
   has no hard edge. */
.hero-veil {
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;
    background:
        linear-gradient(to right,
            rgba(6, 11, 22, 0.88) 0%,
            rgba(6, 11, 22, 0.62) 34%,
            rgba(6, 11, 22, 0.10) 62%,
            rgba(6, 11, 22, 0) 100%),
        linear-gradient(to bottom,
            rgba(10, 17, 32, 0) 62%,
            var(--ink-800) 100%);
}

.hero-shell {
    position: relative;
    z-index: 3;
    width: 100%;
    max-width: var(--shell);
    margin: 0 auto;
    padding-inline: var(--gutter);
    box-sizing: border-box;
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 25rem);
    align-items: center;
    gap: clamp(2rem, 4vw, 4rem);
}

.hero-copy {
    max-width: 40rem;
}

/* =========================================================================
   ENGAGEMENT STACK
   The object on the right of the hero: four parallel reasons to work with
   us, not sequential steps, so there is no correct reading order. All four
   are visible at once, so it reads as a summary rather than a slideshow that
   hides most of itself; the active one expands to show its line and
   advances on a timer.

   Deliberately not a simulated product interface. A fake console, threat feed
   or dashboard built from divs is the most recognisable generated-UI tell
   there is, and this is editorial content in a panel instead.
   ========================================================================= */
.hero-stack {
    position: relative;
    background: var(--ink-700);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--e3), var(--e-top);
    padding: 1.5rem 1.5rem 0.75rem;
    overflow: hidden;
    isolation: isolate;
}

/* The same ambient pair as the hero backdrop, so the panel sits inside the
   atmosphere rather than on top of it. */
.hero-stack::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 0;
    background:
        radial-gradient(120% 90% at 100% 0%, rgba(43, 31, 107, 0.55) 0%, rgba(20, 16, 51, 0) 60%),
        radial-gradient(90% 80% at 0% 100%, rgba(14, 74, 92, 0.35) 0%, rgba(14, 74, 92, 0) 62%);
    pointer-events: none;
}

.hero-stack-caption {
    position: relative;
    z-index: 1;
    margin: 0 0 1rem;
    font-size: 0.8125rem;
    color: var(--text-low);
}

.hero-stack-list {
    position: relative;
    z-index: 1;
    list-style: none;
    margin: 0;
    padding: 0;
}

.hero-stage {
    border-top: 1px solid var(--line);
}
.hero-stage:first-child { border-top: none; }

.hero-stage-btn {
    display: flex;
    align-items: baseline;
    width: 100%;
    min-height: 44px;
    padding: 0.6rem 0;
    background: none;
    border: none;
    text-align: left;
    cursor: pointer;
    font-family: var(--font-sans);
    color: var(--text-low);
    transition: color 0.3s ease;
}

.hero-stage-btn:hover { color: var(--text-mid); }

.hero-stage-name {
    font-size: 1rem;
    font-weight: 600;
    letter-spacing: -0.015em;
    line-height: 1.25;
}

.hero-stage.is-active .hero-stage-btn { color: var(--text-hi); }

/* grid-template-rows rather than height: this is the one way to animate a
   collapse without the browser doing layout work per frame. */
.hero-stage-detail {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}
.hero-stage.is-active .hero-stage-detail { grid-template-rows: 1fr; }

.hero-stage-detail > div { overflow: hidden; }

.hero-stage-detail p {
    margin: 0;
    padding: 0 0 0.875rem;
    font-size: 0.875rem;
    line-height: 1.6;
    color: var(--text-mid);
    opacity: 0;
    transition: opacity 0.3s ease 0.06s;
}
.hero-stage.is-active .hero-stage-detail p { opacity: 1; }

/* Dwell indicator. Runs only while the stack is actually rotating, so it is
   never a bar that fills against nothing. */
.hero-stage-progress {
    height: 1px;
    background: var(--sig-400);
    transform: scaleX(0);
    transform-origin: left;
}
.hero-stack.is-rotating .hero-stage.is-active .hero-stage-progress {
    animation: stage-dwell var(--dwell, 4600ms) linear forwards;
}

@keyframes stage-dwell {
    from { transform: scaleX(0); }
    to   { transform: scaleX(1); }
}

@media (max-width: 767.98px) {
    /* The panel keeps every part it has on desktop; it just stops paying
       desktop rent for them. Four collapsed rows at 44px plus one open body
       is ~250px here against ~300px before. */
    .hero-stack { padding: 1.125rem 1.125rem 0.5rem; }
    .hero-stack-caption { margin-bottom: 0.625rem; }
    .hero-stage-btn { min-height: 44px; padding: 0.4rem 0; }
    .hero-stage-detail p { padding-bottom: 0.75rem; font-size: 0.9375rem; }
}

/* Below the two-column threshold the panel drops under the copy. It stays the
   same instrument: one stage open at a time, auto-advancing, dwell rule
   running, tap to jump. Flattening it to four open paragraphs — which is what
   this breakpoint used to do — was the single biggest reason the phone read as
   a longer, duller version of the page rather than the same one. */
@media (max-width: 1023.98px) {
    .hero-shell { grid-template-columns: minmax(0, 1fr); }
    .hero-stack { max-width: 34rem; }
}

@media (prefers-reduced-motion: reduce) {
    .hero-stage-detail { transition: none; }
    .hero-stage-progress { animation: none !important; }
}

/* ---- Hero wordmark ------------------------------------------------------
   The front page opens on the name rather than on a claim. Two words is far
   too little text to carry a display size on its own, so the lockup does the
   work instead:

     - SENTIZONE at 700 over DEFENCE at 300, one weight step apart in the same
       face. The system's own rule is that emphasis comes from weight and size,
       never from a second colour or a gradient, and a wordmark is the one
       place that rule can be spent generously.
     - "Defence" is `display: block`, so the two words stack at EVERY width.
       Letting them reflow would mean the lockup reads as one line on a wide
       screen and two on a narrow one — a logo that changes shape, which is the
       one thing a logo may not do.
   Sized on its own clamp rather than --t-display: this is a wordmark, and it
   wants to be bigger than a sentence would be at the same width.

   There was a short cyan rule above the name for a while, borrowed from the
   timeline spine and the phase rail. It is gone: those rules all measure
   something — progress, position, sequence — and a mark above a name measures
   nothing. Decoration that imitates a system's load-bearing gesture makes the
   gesture mean less everywhere else it appears. */
.hero-wordmark {
    position: relative;
    font-family: var(--font-sans);
    font-size: clamp(3rem, 6.4vw, 5rem);
    font-weight: 700;
    letter-spacing: -0.045em;
    line-height: 0.96;
    color: var(--text-hi);
    text-transform: none;
    margin: 0 0 1.5rem;
}

.hero-wordmark span {
    display: block;
    font-weight: 300;
    /* Lighter weight sets narrower, so it needs slightly less negative tracking
       to sit on the same optical rhythm as the line above it. */
    letter-spacing: -0.035em;
}

/* Every measure in the hero is expressed in `ch`, not pixels or rem.
   The display size scales with the viewport while the copy column did not,
   so between roughly 900px and 1200px the headline kept crossing a wrap
   boundary and flipping between two and three lines while the lead and the
   buttons stayed put. A ch-based measure breaks at the same character count
   at every size, so the wrap is identical at every width. */
.hero-lead {
    font-family: var(--font-sans);
    font-size: var(--t-lead);
    font-weight: 400;
    letter-spacing: 0;
    text-transform: none;
    line-height: 1.65;
    color: var(--text-mid);
    max-width: 48ch;
    margin: 0 0 2.5rem;
}

.hero-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.875rem;
}

@media (max-width: 1023.98px) {
    /* The block stops being viewport-locked once the stack sits under the copy;
       it grows to fit instead of squeezing the hero. */
    .hero-block {
        height: auto;
        min-height: calc(var(--vh, 1vh) * 100);
        min-height: 100dvh;
        /* svh last, so this block is another term that stops moving when the
           address bar does — see the hero-copy note near the end of this file
           for why any viewport-tracking height inside the hero shows up as a
           flicker rather than as a smooth resize. */
        min-height: 100svh;
        max-height: none;
    }
    .cyber-hero-canvas {
        /* padding-TOP, because the glass bar sits on this canvas rather than
           above it. A `padding-block` shorthand here silently drops the bar's
           height and slides the wordmark up underneath it — which is exactly
           what it did between 768 and 1024, the one tier the phone rule at the
           end of this file does not cover. */
        padding-top: calc(clamp(2.5rem, 10vw, 4rem) + var(--header-h));
        padding-bottom: clamp(2.5rem, 10vw, 4rem);
    }
    .hero-veil {
        background:
            linear-gradient(to bottom,
                rgba(6, 11, 22, 0.55) 0%,
                rgba(6, 11, 22, 0.78) 45%,
                var(--ink-800) 100%);
    }
    /* Still ch-based on small screens, so the wrap stays predictable rather
       than snapping to full width at the breakpoint. */
    .hero-copy  { max-width: none; }
    .hero-lead  { max-width: 42ch; }
}

@media (max-width: 767.98px) {
    /* ---- Phone hero: the name gets a screen of its own -------------------
       The previous pass squeezed the whole hero — name, lead, buttons AND the
       panel — into one viewport. It fit, but everything was crammed into the
       top half against a hard stop, and the first thing a visitor met was a
       wall rather than a statement.

       So the copy block is given a screen's height and its content is centred
       inside it. The name now lands where the eye already is, the panel starts
       just under the fold, and the two are read in sequence instead of
       competing in one glance.

       The 8rem taken off that height is deliberate: it leaves about 90px of
       the panel showing above the fold, which is what tells the visitor to
       keep going. A hero that ends exactly at the fold reads as the end of
       the page. */
    /* Vertical padding for this hero is set with every other hero's, in the
       shared centring block near the end of this file. */
    .hero-wordmark { margin-bottom: 1.25rem; }
    .hero-lead  { max-width: none; margin-bottom: 1.75rem; line-height: 1.55; }
    .hero-actions { gap: 0.625rem; }
    /* Two buttons, one row: the secondary is short enough to share the line. */
    .hero-actions .btn { flex: 1 1 auto; padding-inline: 0.875rem; }

    /* Air between the statement and the panel, now that they are two beats
       rather than one block. */
    .hero-shell { gap: 2.25rem; }
}

/* =========================================================================
   AMBIENT — the page atmosphere, off the home page

   The front page gets its depth from the canvas (see app.js), which paints
   three things in this order: a diagonal backdrop running indigo → navy →
   near-black, an indigo field centred near the top-left at 0.78 of the
   viewport's long edge, and a cyan field near the bottom-right at 0.62.
   The veil then darkens the left so the statement reads, and dissolves the
   bottom into the next section.

   Secondary pages have no canvas, and the two faint radials they used to
   carry left them looking like flat navy next to the home page — which is
   most obvious on the half of the hero the copy does not use. These rules
   are that same three-part composition rebuilt in CSS, at the same
   positions, the same radii and the same alphas, so a visitor moving from
   the front page to any other one stays in the same room.

   What does NOT come across is the dot lattice. It is the home page's
   alone; here the atmosphere is the whole of it.
   ========================================================================= */
.services-hero-atmos,
.contact-hero::before,
.about-hero::before,
.legal-hero::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background:
        /* Veil: lifts the copy off the field on the left, and dissolves the
           bottom edge into whatever section follows. Painted first because a
           CSS background list stacks front to back. Much lighter than the
           home page's, which has to hold a moving dot lattice off the
           statement; here there is nothing under the copy but colour. */
        linear-gradient(to right,
            rgba(6, 11, 22, 0.62) 0%,
            rgba(6, 11, 22, 0.30) 42%,
            rgba(6, 11, 22, 0) 74%),
        linear-gradient(to bottom,
            rgba(10, 17, 32, 0) 74%,
            var(--ink-800) 100%),
        /* The two ambient fields. */
        radial-gradient(84vmax 84vmax at 17% 10%,
            rgba(50, 36, 126, 0.68) 0%, rgba(43, 31, 107, 0) 100%),
        radial-gradient(68vmax 68vmax at 87% 87%,
            rgba(15, 84, 104, 0.58) 0%, rgba(14, 74, 92, 0) 100%),
        /* The backdrop they sit on. `to bottom right` would resolve the
           indigo corner away in the first fifth of a 16:9 box; the explicit
           angle keeps the same spread the canvas draws. */
        linear-gradient(122deg, #16113a 0%, #0d1729 42%, #060b16 100%);
}

/* Interior sections: the same light, a third of the amplitude, entering from
   alternating sides down the page so the atmosphere reads as one source
   rather than the hero's gradient stamped again per section. No veil and no
   backdrop here — these sit on the surface ladder, they do not replace it. */
.cyber-services-section,
.solutions-section,
.faq-section,
.about-narrative-section,
.values-section,
.legal-section { position: relative; }

.cyber-services-section > .shell,
.solutions-section > .shell,
.faq-section > .shell,
.about-narrative-section > .shell,
.values-section > .shell,
.legal-section > .shell { position: relative; z-index: 1; }

.cyber-services-section::before,
.solutions-section::before,
.faq-section::before,
.about-narrative-section::before,
.values-section::before,
.legal-section::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background:
        radial-gradient(64vmax 40vmax at 86% -14%,
            rgba(43, 31, 107, 0.42) 0%, rgba(43, 31, 107, 0) 72%),
        radial-gradient(54vmax 38vmax at 4% 108%,
            rgba(14, 74, 92, 0.30) 0%, rgba(14, 74, 92, 0) 72%);
}

/* Mirrored on every other section, so consecutive bands never repeat. */
.solutions-section::before,
.values-section::before {
    background:
        radial-gradient(64vmax 40vmax at 10% -14%,
            rgba(43, 31, 107, 0.38) 0%, rgba(43, 31, 107, 0) 72%),
        radial-gradient(54vmax 38vmax at 94% 108%,
            rgba(14, 74, 92, 0.32) 0%, rgba(14, 74, 92, 0) 72%);
}

@media (max-width: 1023.98px) {
    /* Portrait viewports make vmax the *height*, which blows both fields up
       past the section and flattens them back into a wash. Sizing off the
       width keeps the same shape the desktop composition has. */
    .services-hero-atmos,
    .contact-hero::before,
    .about-hero::before,
    .legal-hero::before {
        background:
            linear-gradient(to bottom,
                rgba(6, 11, 22, 0.45) 0%,
                rgba(6, 11, 22, 0.74) 52%,
                var(--ink-800) 100%),
            radial-gradient(120vw 120vw at 18% 10%,
                rgba(43, 31, 107, 0.62) 0%, rgba(43, 31, 107, 0) 100%),
            radial-gradient(100vw 100vw at 92% 84%,
                rgba(14, 74, 92, 0.5) 0%, rgba(14, 74, 92, 0) 100%),
            linear-gradient(to bottom right, #16113a 0%, #0d1729 34%, #060b16 100%);
    }

    .cyber-services-section::before,
    .solutions-section::before,
    .faq-section::before,
    .about-narrative-section::before,
    .values-section::before,
    .legal-section::before {
        background:
            radial-gradient(110vw 60vw at 84% -8%,
                rgba(43, 31, 107, 0.38) 0%, rgba(43, 31, 107, 0) 72%),
            radial-gradient(96vw 54vw at 6% 104%,
                rgba(14, 74, 92, 0.28) 0%, rgba(14, 74, 92, 0) 72%);
    }
}

/* =========================================================================
   MOTION — one authored entrance
   Eight reveal variants and roughly sixty !important delay overrides have
   been replaced by a single gesture: rise and resolve. Sequence within a
   group is expressed by --i on the element, not by a selector per child.
   ========================================================================= */
/* The attribute selector picks up the retired .reveal-01 … .reveal-13 names
   that the interior page templates still emit, so those pages get the new
   single gesture without needing their markup touched in this pass. */
.reveal,
[class*="reveal-"] {
    opacity: 0;
    transform: translateY(18px);
}

.reveal.is-active,
[class*="reveal-"].is-active {
    opacity: 1;
    transform: none;
}

.reveal.is-animating,
[class*="reveal-"].is-animating {
    transition:
        opacity   0.75s cubic-bezier(0.16, 1, 0.3, 1),
        transform 0.75s cubic-bezier(0.16, 1, 0.3, 1);
    transition-delay: calc(var(--i, 0) * 70ms);
}

/* Cards must not take a hover mid-flight. The JS strips the class on
   completion, so this only covers the animation itself. */
.reveal.is-animating,
[class*="reveal-"].is-animating { pointer-events: none; }

/* =========================================================================
   CREDIBILITY STRIP
   A cybersecurity buyer looks for proof before they look at capability, and
   the shipped page had none. This sits directly under the hero, which is
   where a proof band belongs: never inside the hero, always immediately
   after it. Deliberately typographic — these are standards and frameworks,
   not company logos, and inventing badge artwork for them would be a claim
   the brand has not made.
   ========================================================================= */
.credibility {
    background: var(--ink-800);
    border-block: 1px solid var(--line);
}

.credibility .shell {
    display: flex;
    align-items: center;
    gap: clamp(1rem, 2.4vw, 2.25rem);
    /* Slim: a band you pass through, not a section you stop at. */
    padding-block: 0.875rem;
}

.credibility-label {
    margin: 0;
    flex: 0 0 auto;
    font-size: 0.8125rem;
    line-height: 1.3;
    color: var(--text-low);
    max-width: 26ch;
}

/* ---- Marquee -------------------------------------------------------------
   The only marquee on the page. It earns its place because the list is a
   continuous set of equal-weight items with no reading order, which is
   exactly the content shape a marquee suits. Two identical copies of the
   track translate by half their width, so the loop has no seam. */
.credibility-viewport {
    position: relative;
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    /* Entries dissolve at both edges instead of being cut off. */
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 4rem, #000 calc(100% - 4rem), transparent 100%);
    mask-image: linear-gradient(to right, transparent 0, #000 4rem, #000 calc(100% - 4rem), transparent 100%);
}

.credibility-track {
    display: flex;
    width: max-content;
    animation: credibility-slide 38s linear infinite;
}

/* Pause on hover and while a keyboard user is focused inside, so the content
   can actually be read. */
.credibility-viewport:hover .credibility-track,
.credibility-viewport:focus-within .credibility-track {
    animation-play-state: paused;
}

@keyframes credibility-slide {
    from { transform: translate3d(0, 0, 0); }
    to   { transform: translate3d(-50%, 0, 0); }
}

.credibility-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    align-items: center;
    flex: 0 0 auto;
}

.credibility-list li {
    position: relative;
    padding-inline: clamp(1rem, 1.8vw, 1.75rem);
    font-size: clamp(0.875rem, 0.95vw, 0.9375rem);
    font-weight: 600;
    letter-spacing: -0.005em;
    color: var(--text-mid);
    white-space: nowrap;
}

/* Hairline sits on the leading edge of every entry, so the rhythm stays even
   across the seam where one copy of the track meets the next. */
.credibility-list li::before {
    content: '';
    position: absolute;
    left: 0;
    top: 50%;
    width: 1px;
    height: 0.9em;
    background: var(--line-strong);
    transform: translateY(-50%);
}

@media (max-width: 767.98px) {
    .credibility .shell { gap: 0.5rem; padding-block: 0.75rem; }
    .credibility-viewport {
        -webkit-mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 2.5rem), transparent 100%);
        mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 2.5rem), transparent 100%);
    }
}

/* Reduced motion: stop the loop and let the row scroll by hand instead. */
@media (prefers-reduced-motion: reduce) {
    .credibility-track { animation: none; }
    .credibility-viewport { overflow-x: auto; }
}

body.perf-low .credibility-track { animation: none; }
body.perf-low .credibility-viewport { overflow-x: auto; }

/* =========================================================================
   AUDIENCE BAND
   Three routes into the product, on a raised band under the hero. No card
   chrome and no floating box: the columns are separated by hairlines and
   the band itself carries the elevation. Copy is always visible — the old
   version hid each column's text behind a hover overlay, which meant the
   content only existed for users with a pointer.
   ========================================================================= */
/* A floating island rather than a full-bleed band: the page background runs
   past it on both sides and above it, so it reads as an object sitting on the
   page instead of a stripe cut through it. */
.audience-band {
    position: relative;
    background: transparent;
    border: none;
    padding-block: clamp(2.25rem, 4.5vw, 4rem);
}

.audience-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    background: var(--ink-700);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--e2);
    overflow: hidden;
}

.audience-col {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.875rem;
    padding: clamp(2.5rem, 4vw, 3.5rem) clamp(1.5rem, 2.6vw, 2.5rem);
    text-decoration: none;
    background: transparent;
    transition: background-color 0.32s ease;
}

.audience-col + .audience-col {
    border-left: 1px solid var(--line);
}

/* The accent rule is the hover signal. It draws from the left, 1px, and it
   is the only place the band uses colour. */
.audience-col::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 2px;
    background: var(--sig-400);
    transform: scaleX(0);
    transform-origin: left;
    transition: transform 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}

.audience-col:hover,
.audience-col:focus-visible,
.audience-col.touch-hover { background: var(--ink-600); }

.audience-col:hover::before,
.audience-col:focus-visible::before,
.audience-col.touch-hover::before { transform: scaleX(1); }

.audience-icon {
    display: flex;
    width: 26px;
    height: 26px;
    margin-bottom: 0.25rem;
}
.audience-icon svg {
    width: 100%;
    height: 100%;
    fill: none;
    stroke: var(--sig-400);
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
    transition: stroke 0.3s ease;
}

.audience-col h3 {
    margin: 0;
    font-size: var(--t-h3);
    font-weight: 600;
    color: var(--text-hi);
    text-transform: none;
    letter-spacing: -0.015em;
}

.audience-col p {
    margin: 0;
    font-family: var(--font-sans);
    font-size: 0.9375rem;
    line-height: 1.65;
    color: var(--text-mid);
    max-width: 34ch;
}

.audience-more {
    margin-top: auto;
    padding-top: 1.25rem;
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--sig-100);
    letter-spacing: -0.005em;
}
.audience-more span {
    display: inline-block;
    transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
.audience-col:hover .audience-more span,
.audience-col.touch-hover .audience-more span { transform: translateX(4px); }

@media (max-width: 767.98px) {
    .audience-grid { grid-template-columns: 1fr; }
    .audience-col + .audience-col {
        border-left: none;
        border-top: 1px solid var(--line);
    }
    .audience-more { margin-top: 0.25rem; padding-top: 0.5rem; }
}

/* Small accessibility helpers */
.screen-reader-text{ position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); border: 0; }


/* =========================================================================
   OPERATIONS TIMELINE
   The structure is unchanged: an alternating left/right ladder on a central
   spine that illuminates as you scroll. Only the surface has been rebuilt.
   Gone: the clip-path notch, the three stacked border layers, the 30px neon
   bloom on the spine, the uppercase mono body. What replaces them is a
   hairline spine, a filled junction dot, and a plain panel that gains a
   quiet cyan border when its row powers on.
   ========================================================================= */
.cyberpunk-data-grid {
    background-color: var(--ink-800);
    color: var(--text-hi);
    padding: var(--s-section) 0;
    overflow: hidden;
}

.container {
    width: 100%;
    max-width: var(--shell);
    margin: 0 auto;
    position: relative;
    padding-inline: var(--gutter);
    box-sizing: border-box;
}

.section-main-title {
    font-family: var(--font-sans);
    font-size: var(--t-h2);
    font-weight: 600;
    letter-spacing: -0.02em;
    text-transform: none;
    color: var(--text-hi);
    margin: 0 0 1rem;
    text-align: center;
}

.section-main-subtitle {
    font-family: var(--font-sans);
    color: var(--text-mid);
    font-size: var(--t-lead);
    line-height: 1.6;
    letter-spacing: 0;
    text-transform: none;
    max-width: 52ch;
    margin: 0 auto clamp(3.5rem, 6vw, 5.5rem);
    text-align: center;
    opacity: 1;
}

/* --- Spine ---------------------------------------------------------------- */
.cyber-timeline-wrapper {
    position: relative;
    max-width: 940px;
    margin: 0 auto;
    padding: 8px 0;
}

.timeline-track {
    position: absolute;
    top: 0; bottom: 0;
    left: 50%;
    transform: translateX(-50%);
    width: 1px;
    /* The unlit spine has to stay legible on its own — it is the structure the
       whole section reads from, and the fill only lights the part you have
       already scrolled past. */
    background: var(--line-strong);
    z-index: 1;
}

/* Driven by scroll position in app.js. A 1px rule that fills — no bloom, no
   shadow; the progress reads as a line being drawn. The fill is a scaleY on a
   full-height element rather than an animated height, so scrolling the section
   never triggers layout. app.js sets --fill (0..1). */
.timeline-glow {
    position: absolute;
    top: 0;
    left: 50%;
    width: 1px;
    height: 100%;
    background: linear-gradient(to bottom, var(--sig-600), var(--sig-400));
    transform: translateX(-50%) scaleY(var(--fill, 0));
    transform-origin: top;
    z-index: 2;
    will-change: transform;
}

/* --- Rows (alternating) --------------------------------------------------- */
.timeline-row {
    display: flex;
    align-items: center;
    width: 50%;
    margin-bottom: clamp(2rem, 3.5vw, 3rem);
    position: relative;
    z-index: 5;
}
.timeline-row:last-child { margin-bottom: 0; }

.timeline-row.left  { justify-content: flex-end;   padding-right: 44px; }
.timeline-row.right { margin-left: 50%; justify-content: flex-start; padding-left: 44px; }

/* Connector: a hairline plus the junction dot that sits on the spine. */
.timeline-connector {
    position: absolute;
    top: 50%;
    width: 44px;
    height: 1px;
    background: rgba(154, 182, 220, 0.26);
    z-index: 3;
    transition: background-color 0.45s ease;
}
.timeline-row.left  .timeline-connector { right: 0; }
.timeline-row.right .timeline-connector { left: 0; }

.timeline-connector::after {
    content: '';
    position: absolute;
    top: 50%;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--ink-800);
    border: 1px solid rgba(154, 182, 220, 0.34);
    transform: translateY(-50%);
    transition: background-color 0.45s ease, border-color 0.45s ease, box-shadow 0.45s ease;
}
.timeline-row.left  .timeline-connector::after { right: -5px; }
.timeline-row.right .timeline-connector::after { left: -5px; }

/* --- Node panel ----------------------------------------------------------- */
.cyber-node {
    position: relative;
    width: 320px;
    max-width: 100%;
    padding: 1.5rem 1.625rem;
    box-sizing: border-box;
    background: var(--ink-600);
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    box-shadow: var(--e2), var(--e-top);
    cursor: default;
    touch-action: manipulation;
    -webkit-tap-highlight-color: transparent;
    transform: translateY(0);
    transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1),
                background-color 0.35s ease,
                border-color 0.35s ease,
                box-shadow 0.35s ease;
}

.cyber-node:hover,
.cyber-node.touch-hover {
    transform: translateY(-4px);
    background: var(--ink-500);
    border-color: var(--sig-800);
    box-shadow: var(--e3), var(--e-top);
}

/* The old three-layer notch frame is retired. The markup still carries the
   divs on existing pages, so they are neutralised rather than left to paint. */
.border-layer-1,
.border-layer-2 { display: none; }

.node-content {
    position: static;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.node-content h3 {
    font-family: var(--font-sans);
    font-size: 1.0625rem;
    font-weight: 600;
    text-transform: none;
    letter-spacing: -0.015em;
    color: var(--text-hi);
    margin: 0;
    line-height: 1.2;
    transition: color 0.35s ease;
}

.node-content p {
    font-family: var(--font-sans);
    color: var(--text-mid);
    font-size: 0.9375rem;
    line-height: 1.6;
    margin: 0;
}

/* --- The powered-on state (driven by scroll in app.js) -------------------- */
.timeline-row.active-node .timeline-connector { background: var(--sig-600); }

.timeline-row.active-node .timeline-connector::after {
    background: var(--sig-400);
    border-color: var(--sig-400);
}

.timeline-row.active-node .cyber-node {
    background: var(--ink-500);
    border-color: var(--sig-800);
}

.timeline-row.active-node .node-content h3 { color: var(--sig-100); }

/* --- Mobile: single left-hand spine --------------------------------------- */
@media (max-width: 767.98px) {
    .timeline-track { left: 4px; transform: none; }
    .timeline-glow  { left: 4px; transform: scaleY(var(--fill, 0)); }
    .timeline-row {
        width: 100%;
        margin-left: 0 !important;
        justify-content: flex-start !important;
        padding-left: 36px !important;
        padding-right: 0 !important;
    }
    .timeline-connector { left: 4px !important; width: 32px !important; }
    .timeline-row.left  .timeline-connector::after,
    .timeline-row.right .timeline-connector::after { left: -4px; right: auto; }
    .cyber-node { width: 100%; }
}

/* =========================================================================
   POSITIONING
   Editorial split. The statement, its body and its action stack in the left
   column; the four proof figures run down the right as hairline-divided
   rows. Deliberately not four shadowed metric cards — that template gives
   every figure identical weight and turns the section into another grid.
   ========================================================================= */
.cyber-about-section {
    background-color: var(--ink-800);
    padding: var(--s-section) 0;
    position: relative;
}

.about-container {
    display: grid;
    grid-template-columns: 1.12fr 0.88fr;
    gap: clamp(3rem, 6vw, 6.5rem);
    align-items: start;
}

.about-narrative { padding-right: 0; }

/* Shared section heading (also used by the about / services / contact
   templates). Sentence case, tight tracking, no accent rule above it. */
.cyber-heading {
    font-family: var(--font-sans);
    font-size: var(--t-h2);
    font-weight: 600;
    color: var(--text-hi);
    text-transform: none;
    letter-spacing: -0.022em;
    line-height: 1.1;
    margin: 0 0 1.5rem 0;
    position: relative;
}
.cyber-heading::before,
.cyber-heading::after { display: none; }

/* The old 60x4 cyan bar under every heading is retired. Kept as a no-op so
   templates that still emit .accent-line do not leave a gap. */
.accent-line { display: none; }

.lead-text {
    font-family: var(--font-sans);
    font-size: clamp(1.2rem, 1.7vw, 1.55rem);
    color: var(--text-hi);
    line-height: 1.4;
    letter-spacing: -0.018em;
    margin: 0 0 1.5rem;
    font-weight: 500;
    max-width: 30ch;
}

/* The one place cyan carries meaning in body type: the turn in the
   statement. --sig-100 is the only cyan that clears 4.5:1 on this ink. */
.cyan-text { color: var(--sig-100); }

.body-text {
    font-family: var(--font-sans);
    font-size: var(--t-body);
    color: var(--text-mid);
    line-height: 1.75;
    margin: 0 0 2.25rem;
    max-width: 62ch;
}

/* --- Proof figures ---------------------------------------------------- */
.about-metrics {
    display: flex;
    flex-direction: column;
    border-top: 1px solid var(--line);
}

.metric-card {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1.5rem;
    padding: 1.375rem 0;
    border-bottom: 1px solid var(--line);
    background: none;
    box-shadow: none;
    height: auto;
    transition: border-color 0.3s ease;
}
.metric-card:hover { border-bottom-color: var(--sig-800); }

.metric-value {
    font-family: var(--font-sans);
    font-size: clamp(1.5rem, 2.1vw, 1.9rem);
    font-weight: 600;
    color: var(--text-hi);
    letter-spacing: -0.03em;
    line-height: 1;
    margin: 0;
    text-shadow: none;
    font-variant-numeric: tabular-nums;
    flex: 0 0 auto;
}

.metric-label {
    font-family: var(--font-sans);
    font-size: 0.875rem;
    color: var(--text-low);
    text-transform: none;
    letter-spacing: 0;
    line-height: 1.4;
    /* Wide enough for the longest label ("Monitored infrastructure uptime") to
       sit on one line at desktop. The previous 17ch cap was forcing a wrap and
       leaving an orphan word against the right-aligned edge. */
    max-width: 32ch;
    text-align: right;
    text-wrap: balance;
}

@media (max-width: 1023.98px) {
    .about-container {
        grid-template-columns: 1fr;
        gap: clamp(2.5rem, 8vw, 3.5rem);
    }
    .lead-text { max-width: none; }
    .metric-label { max-width: 20ch; }
}


/* =========================================================================
   CAPABILITIES
   Six capabilities, six cells, three different row rhythms. The previous
   version was six identical 280px cards in an auto-fit grid, which gave
   every capability the same weight and made this the third card grid in a
   row. Two cells carry a real surface treatment so the grid is not six
   flat text boxes.
   ========================================================================= */
.cyber-services-section {
    background-color: var(--ink-800);
    padding: var(--s-section) 0;
    position: relative;
    border-top: none;
}

.section-cta {
    margin-top: clamp(2.5rem, 4vw, 3.5rem);
}

.section-header-centered {
    text-align: center;
    max-width: 46ch;
    margin: 0 auto clamp(3rem, 5.5vw, 4.5rem);
}

.section-subtitle {
    font-family: var(--font-sans);
    color: var(--text-mid);
    font-size: var(--t-lead);
    font-weight: 400;
    line-height: 1.6;
    margin: 1rem 0 0;
}

.center-line { display: none; }

/* --- Bento ---------------------------------------------------------------- */
.capability-grid {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    gap: clamp(0.75rem, 1.2vw, 1.125rem);
}

.capability {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    gap: 0.625rem;
    min-height: 210px;
    padding: clamp(1.5rem, 2.2vw, 2.125rem);
    background: var(--ink-600);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--e1);
    overflow: hidden;
    transition: background-color 0.32s ease, border-color 0.32s ease,
                transform 0.32s cubic-bezier(0.16, 1, 0.3, 1),
                box-shadow 0.32s ease;
}

.capability:hover,
.capability.touch-hover {
    transform: translateY(-3px);
    background: var(--ink-500);
    border-color: var(--sig-800);
    box-shadow: var(--e2);
}

.capability h3 {
    position: relative;
    z-index: 2;
    margin: 0;
    font-size: var(--t-h3);
    font-weight: 600;
    letter-spacing: -0.018em;
    text-transform: none;
    color: var(--text-hi);
}

.capability p {
    position: relative;
    z-index: 2;
    margin: 0;
    font-family: var(--font-sans);
    font-size: 0.9375rem;
    line-height: 1.65;
    color: var(--text-mid);
    max-width: 46ch;
}

/* Row rhythm: 4+2, 2+4, 3+3. */
.capability--a { grid-column: span 4; min-height: 300px; }
.capability--b { grid-column: span 2; }
.capability--c { grid-column: span 2; }
.capability--d { grid-column: span 4; }
.capability--e { grid-column: span 3; }
.capability--f { grid-column: span 3; }

/* The lead cell gets the indigo ambient field — the same hue the hero uses,
   so the page's atmosphere reappears rather than a new decoration. */
.capability--a { justify-content: flex-end; }
.capability--a::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 0;
    background:
        radial-gradient(120% 90% at 85% 0%, rgba(43, 31, 107, 0.75) 0%, rgba(20, 16, 51, 0) 62%),
        radial-gradient(80% 70% at 12% 108%, rgba(14, 74, 92, 0.45) 0%, rgba(14, 74, 92, 0) 60%);
    pointer-events: none;
}
.capability--a h3 { font-size: clamp(1.4rem, 2vw, 1.85rem); }
.capability--a p   { max-width: 52ch; }

/* The wide second-row cell takes the other end of the ambient pair: cyan
   entering from the right, where the lead cell's indigo entered from the
   left. Same device as the hero and the closing band, so the grid gets its
   second textured surface out of the system's own vocabulary rather than
   introducing a pattern fill. */
.capability--d::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 0;
    background:
        radial-gradient(110% 130% at 100% 60%, rgba(14, 74, 92, 0.85) 0%, rgba(14, 74, 92, 0) 64%),
        radial-gradient(70% 90% at 88% 10%, rgba(43, 31, 107, 0.40) 0%, rgba(43, 31, 107, 0) 62%);
    pointer-events: none;
}

@media (max-width: 1023.98px) {
    .capability-grid { grid-template-columns: repeat(2, 1fr); }
    .capability--a { grid-column: span 2; min-height: 260px; }
    .capability--b,
    .capability--c,
    .capability--e,
    .capability--f { grid-column: span 1; }
    .capability--d { grid-column: span 2; }
}

@media (max-width: 640px) {
    .capability-grid { grid-template-columns: 1fr; }
    .capability,
    .capability--a,
    .capability--b,
    .capability--c,
    .capability--d,
    .capability--e,
    .capability--f {
        grid-column: span 1;
        min-height: 0;
    }
}

/* --- Services-page grid (kept for template-services.php) ------------------
   Same surface language as the bento, without the span rhythm. The HUD
   brackets and the scanning laser are retired outright. */
.holographic-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
    gap: clamp(0.75rem, 1.2vw, 1.125rem);
    align-items: stretch;
}

.hud-card {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    gap: 0.625rem;
    background: var(--ink-600);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--e1);
    padding: clamp(1.5rem, 2.2vw, 2.125rem);
    min-height: 220px;
    height: auto;
    overflow: hidden;
    text-align: left;
    transition: background-color 0.32s ease, border-color 0.32s ease,
                transform 0.32s cubic-bezier(0.16, 1, 0.3, 1),
                box-shadow 0.32s ease;
}

.hud-content { position: relative; z-index: 2; width: 100%; max-width: none; margin: 0; }

.hud-card h3 {
    font-family: var(--font-sans);
    font-size: var(--t-h3);
    font-weight: 600;
    letter-spacing: -0.018em;
    color: var(--text-hi);
    text-transform: none;
    margin: 0 0 0.625rem 0;
    line-height: 1.2;
    transition: color 0.3s ease;
}

.hud-card p {
    font-family: var(--font-sans);
    color: var(--text-mid);
    font-size: 0.9375rem;
    line-height: 1.65;
    margin: 0;
}

.hud-card:hover,
.hud-card.touch-hover {
    transform: translateY(-3px);
    background: var(--ink-500);
    border-color: var(--sig-800);
    box-shadow: var(--e2);
}

/* Retired sci-fi chrome. The markup still emits these nodes on the services
   page, so they are neutralised rather than left to paint. */
.hud-brackets,
.scanner-laser { display: none; }

/* Mobile hint: prefer 'manipulation' to reduce double-tap zoom on supporting browsers
   This does NOT disable pinch-zoom. Kept in a narrow media query to avoid
   affecting desktop/tablet behaviors. */
@media (max-width: 900px) {
    html, body { touch-action: manipulation; -ms-touch-action: manipulation; }
}

/* =========================================================================
   CONTACT PAGE — composition pass
   Three sections: a full-viewport hero (own `.contact-hero`, not the shared
   `.internal-hero` the legal pages still use), the info/form split, and the
   FAQ accordion (unchanged behaviour, retinted). No closing CTA band — the
   whole page is already the ask.
   ========================================================================= */
.contact-hero {
    position: relative;
    background: var(--ink-700);
    border-bottom: 1px solid var(--line);
    /* Shorter than the site's section rhythm on purpose. This hero carries
       the form, so its whole job is to fit — entirely, panel edge included —
       inside the first screen of an ordinary laptop. The full --s-section
       pushed the bottom of the panel under the fold on anything under about
       950px tall. Everything inside the panel is trimmed to match; see the
       CONTACT FORM 7 block, where the message field is sized in vh so it
       gives back height on a short viewport instead of overflowing it. */
    /* Top padding carries the glass bar's own height on top of the section's
       own, so the section starts at the top of the screen and its `::before`
       atmosphere paints through the bar, while the copy sits exactly where it
       sat when the bar had a band of its own. Same conversion on all four
       interior heroes. */
    padding: calc(clamp(1.75rem, 3.5vw, 3rem) + var(--header-h)) 0 clamp(1.75rem, 3.5vw, 3rem);
    min-height: calc(var(--vh, 1vh) * 100);
    min-height: 100dvh;
    /* svh, so this section and the copy block centred inside it stop moving
       together when a mobile address bar retracts, instead of one chasing the
       other. */
    min-height: 100svh;
    display: flex;
    align-items: center;
    overflow: hidden;
    isolation: isolate;
}
.contact-hero .shell { position: relative; z-index: 1; }

/* Statement + direct lines on the left, the form on the right — the slot the
   home and services heroes give their panels. The right column is wider than
   theirs because a form has a minimum comfortable measure that a text panel
   does not. */
.contact-hero-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 30rem);
    align-items: center;
    gap: clamp(2.5rem, 5vw, 4.5rem);
}

.contact-hero-copy { max-width: 34rem; }
.contact-hero-title {
    font-family: var(--font-sans);
    font-size: var(--t-display);
    font-weight: 700;
    letter-spacing: -0.035em;
    line-height: 1.04;
    color: var(--text-hi);
    max-width: 16ch;
    margin: 0 0 1.5rem;
}
.contact-hero-lead {
    font-family: var(--font-sans);
    font-size: var(--t-lead);
    line-height: 1.65;
    color: var(--text-mid);
    max-width: 40ch;
    margin: 0;
}

/* ---- Direct lines --------------------------------------------------------
   Under the lead, not in a card: someone who would rather phone than fill in
   a form should find the number in the same glance as the headline, and a
   second panel opposite the form would only compete with it. Hairlines, one
   label column, no box. */
.contact-lines {
    margin: clamp(2.25rem, 4vw, 3rem) 0 0;
    padding: 0;
    max-width: 30rem;
}

.contact-line {
    display: grid;
    grid-template-columns: 4.5rem minmax(0, 1fr);
    align-items: center;
    gap: 1rem;
    /* The link inside carries the 44px tap target, so the row only needs
       enough padding to separate the hairlines. */
    padding: 0.375rem 0;
    border-top: 1px solid var(--line);
}
.contact-line:first-child { border-top: none; }

.contact-line dt {
    font-family: var(--font-sans);
    font-size: 0.8125rem;
    color: var(--text-low);
}

.contact-line dd {
    margin: 0;
    font-family: var(--font-sans);
    font-size: 1.0625rem;
    font-weight: 500;
    line-height: 1.45;
    color: var(--text-hi);
}
.contact-line dd span {
    color: var(--text-low);
    font-weight: 400;
    font-size: 0.9375rem;
}
.contact-line dd a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    color: var(--text-hi);
    text-decoration: none;
    transition: color 0.25s ease;
}
.contact-line dd a:hover { color: var(--sig-100); }

@media (max-width: 1023.98px) {
    .contact-hero-grid { grid-template-columns: minmax(0, 1fr); }
    .contact-hero-copy { max-width: 40rem; }
    .contact-form-panel { max-width: 34rem; }
}

@media (max-width: 480px) {
    .contact-line {
        grid-template-columns: minmax(0, 1fr);
        gap: 0.125rem;
    }
    /* 44px, not 40. The 40 was bought during the pass that squeezed this hero
       onto one desktop screen; on a phone the hero stacks and scrolls anyway,
       so the four pixels were being saved against a constraint that no longer
       applies at this width, on the two links most likely to be tapped. */
    .contact-line dd a { min-height: 44px; }
}

/* =========================================================================
   CONTACT FORM 7
   The field markup is admin-authored and lives in the database — the class
   names below (.cyber-field, .cyber-label, .cyber-input, .cyber-submit-btn)
   are what that form emits, so they are kept exactly as they are and only
   what they look like is rewritten. This block was the last thing on the
   site still running the retired HUD language: neon #00e6ff labels in
   uppercase monospace with a `>` prefix, a clip-path notch on the submit,
   and a colour-halo focus glow. All of it is gone; the form is now built
   from the same surfaces, hairlines and focus ring as every other control.
   ========================================================================= */
.contact-form-panel {
    position: relative;
    background: var(--ink-700);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--e3), var(--e-top);
    padding: clamp(1.25rem, 1.7vw, 1.625rem);
    overflow: hidden;
    isolation: isolate;
}

.contact-form-panel::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 0;
    background:
        radial-gradient(120% 90% at 100% 0%, rgba(43, 31, 107, 0.5) 0%, rgba(20, 16, 51, 0) 62%),
        radial-gradient(90% 80% at 0% 100%, rgba(14, 74, 92, 0.3) 0%, rgba(14, 74, 92, 0) 64%);
    pointer-events: none;
}

.contact-form-panel > * { position: relative; z-index: 1; }
.contact-form-fallback { margin: 0; color: var(--text-mid); }
.contact-form-fallback a { color: var(--sig-100); }

.wpcf7 { position: relative; z-index: 1; margin: 0; }
.wpcf7 form.wpcf7-form { margin: 0; }

/* CF7 wraps every control in its own <p>; those margins would fight the
   field spacing set below. */
.cyber-field p,
.cyber-submit-container p,
.cyber-form-header p { margin: 0; }

/* The label is already `display: block`, so CF7's trailing <br> only adds a
   stray line box. */
.cyber-label + br { display: none; }

.cyber-form-header {
    margin-bottom: 1.125rem;
    padding-bottom: 1rem;
    border-bottom: 1px solid var(--line);
}
.form-title {
    font-family: var(--font-sans);
    font-size: 1.1875rem;
    font-weight: 600;
    letter-spacing: -0.018em;
    line-height: 1.25;
    text-transform: none;
    color: var(--text-hi);
    margin-bottom: 0.5rem;
}
.form-description {
    font-family: var(--font-sans);
    font-size: 0.875rem;
    line-height: 1.6;
    color: var(--text-mid);
}

.cyber-field { margin-bottom: 0.875rem; }

.cyber-label {
    display: block;
    font-family: var(--font-sans);
    font-size: 0.8125rem;
    font-weight: 500;
    letter-spacing: 0;
    text-transform: none;
    color: var(--text-low);
    margin-bottom: 0.4375rem;
}

.wpcf7 form input[type="text"],
.wpcf7 form input[type="email"],
.wpcf7 form input[type="tel"],
.wpcf7 form textarea,
.wpcf7-form-control.cyber-input {
    width: 100%;
    background: var(--ink-900);
    border: 1px solid var(--line-strong);
    border-radius: var(--r-md);
    padding: 0.75rem 0.875rem;
    color: var(--text-hi);
    font-family: var(--font-sans);
    font-size: 0.9375rem;
    line-height: 1.5;
    outline: none;
    box-sizing: border-box;
    transition: border-color 0.22s ease, background-color 0.22s ease, box-shadow 0.22s ease;
}

/* CF7 emits rows="10" from the admin form template, which would make the
   message field taller than the hero it now sits in. Height wins over rows;
   the field is still resizable by hand. */
.wpcf7 form textarea,
textarea.wpcf7-form-control.cyber-input {
    display: block;
    /* Sized off the viewport so a short laptop screen gets its height
       back from the one field that can afford to give it. */
    height: clamp(4.5rem, 10vh, 7.5rem);
    min-height: 4.5rem;
    resize: vertical;
}

.wpcf7 form input[type="text"]:hover,
.wpcf7 form input[type="email"]:hover,
.wpcf7 form input[type="tel"]:hover,
.wpcf7 form textarea:hover { border-color: rgba(154, 182, 220, 0.3); }

.wpcf7 form input[type="text"]:focus,
.wpcf7 form input[type="email"]:focus,
.wpcf7 form input[type="tel"]:focus,
.wpcf7 form textarea:focus,
.wpcf7-form-control.cyber-input:focus {
    border-color: var(--sig-400);
    background: var(--ink-800);
    /* A focus ring, not a halo: it reads as the same ring every other
       control on the site uses. */
    box-shadow: 0 0 0 3px rgba(34, 211, 238, 0.16);
}

.wpcf7-form-control.cyber-input::placeholder {
    color: var(--text-low);
    font-family: var(--font-sans);
    opacity: 1;
}

.cyber-submit-container { margin-top: 1rem; }

/* CF7 puts the submit and its spinner in a <p>. Left as a line box, that
   adds a descender's worth of dead space under the button, so the row is
   flex — and the spinner is taken out of flow, because as a flex sibling it
   ate ~36px off a submit that is meant to be exactly as wide as the fields
   above it. Absolute puts it inside the button's right end, which is where
   a submitting spinner belongs anyway. */
.cyber-submit-container p {
    position: relative;
    display: flex;
    align-items: center;
}

.wpcf7 input[type="submit"],
.wpcf7 input.cyber-submit-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    min-height: 44px;
    padding: 0.92em 1.6em;
    border-radius: var(--r-sm);
    border: 1px solid transparent;
    background: var(--sig-400);
    color: var(--ink-900);
    font-family: var(--font-sans);
    font-size: 0.95rem;
    font-weight: 600;
    letter-spacing: -0.005em;
    line-height: 1;
    text-transform: none;
    cursor: pointer;
    outline: none;
    transition: background-color 0.22s ease, color 0.22s ease, transform 0.12s ease;
}
.wpcf7 input[type="submit"]:hover,
.wpcf7 input[type="submit"]:focus-visible,
.wpcf7 input.cyber-submit-btn:hover,
.wpcf7 input.cyber-submit-btn:focus-visible { background: var(--sig-100); }
.wpcf7 input[type="submit"]:active,
.wpcf7 input.cyber-submit-btn:active { transform: translateY(1px); }

/* CF7 system messages */
.wpcf7 form .wpcf7-response-output {
    margin: 1.25rem 0 0 0 !important;
    padding: 0.875rem 1rem !important;
    border-radius: var(--r-md);
    border: 1px solid var(--sig-800) !important;
    background: rgba(14, 74, 92, 0.22);
    font-family: var(--font-sans);
    font-size: 0.875rem;
    line-height: 1.55;
    color: var(--text-hi);
}
.wpcf7 form.invalid .wpcf7-response-output,
.wpcf7 form.failed .wpcf7-response-output,
.wpcf7 form.aborted .wpcf7-response-output {
    border-color: rgba(244, 63, 94, 0.55) !important;
    background: rgba(244, 63, 94, 0.12);
}
.wpcf7-not-valid-tip {
    display: block;
    font-family: var(--font-sans);
    font-size: 0.8125rem;
    line-height: 1.5;
    color: #fda4af;
    margin-top: 0.4375rem;
}
.wpcf7 form input.wpcf7-not-valid,
.wpcf7 form textarea.wpcf7-not-valid { border-color: rgba(244, 63, 94, 0.6); }

.wpcf7-spinner {
    position: absolute;
    right: 1rem;
    top: 50%;
    transform: translateY(-50%);
    margin: 0;
}

/* =========================================
   FAQ ACCORDION
   ========================================= */
.faq-section { padding: var(--s-section) 0; background: var(--ink-800); }
.faq-grid { max-width: 48rem; margin: 0 auto; display: flex; flex-direction: column; gap: 0.75rem; }

/* The accordion is a centred 48rem column, but `.section-head` is a
   shell-width left-aligned block, so the heading was starting a long way
   left of the questions it introduces. Give the head the accordion's own
   measure and centre it the same way: the heading's left edge now lands on
   the questions' left edge. */
.faq-section .section-head {
    max-width: 48rem;
    margin-inline: auto;
}

.faq-item {
    background: var(--ink-600);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--e1);
    overflow: hidden;
    transition: background-color 0.35s ease, border-color 0.35s ease, box-shadow 0.35s ease;
}

.faq-question {
    width: 100%;
    min-height: 44px;
    text-align: left;
    padding: 1.25rem 1.5rem;
    background: transparent;
    border: none;
    color: var(--text-hi);
    font-family: var(--font-sans);
    font-size: 1.05rem;
    font-weight: 600;
    letter-spacing: -0.01em;
    cursor: pointer;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 1rem;
    transition: color 0.25s ease;
}

.faq-question:hover { color: var(--sig-100); }
.faq-icon { color: var(--sig-400); font-size: 1.5rem; font-weight: 300; line-height: 1; transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1); }

.faq-answer { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 0.35s ease-out; }
.faq-answer-inner { overflow: hidden; }
.faq-answer-inner > p {
    margin: 0;
    padding: 0 1.5rem 1.5rem;
    font-family: var(--font-sans);
    font-size: var(--t-body);
    line-height: 1.7;
    color: var(--text-mid);
}

.faq-item.active { border-color: var(--sig-800); box-shadow: var(--e2); }
.faq-item.active .faq-question { color: var(--sig-100); }
.faq-item.active .faq-icon { transform: rotate(45deg); }
.faq-item.active .faq-answer { grid-template-rows: 1fr; }

/* =========================================
   CONTACT PAGE MOBILE RESPONSIVENESS
   ========================================= */
@media (max-width: 900px) {
    .faq-question { font-size: 1rem; }
}

@media (max-width: 640px) {
    .contact-hero-title { max-width: none; }
    .contact-hero-lead { max-width: none; }
}

/* =========================================
   EVENT HORIZON - CYBER FOOTER
   ========================================= */

.cyber-footer {
    background-color: var(--ink-900);
    position: relative;
    padding: clamp(3.5rem, 7vw, 5.5rem) 0 2rem;
    border-top: 1px solid var(--line);
    overflow: hidden;
}

.footer-grid {
    width: 100%;
    max-width: var(--shell);
    margin: 0 auto;
    padding-inline: var(--gutter);
    box-sizing: border-box;
    display: grid;
    grid-template-columns: 1.7fr 1fr 1fr 1.2fr;
    gap: clamp(2rem, 4vw, 3.5rem);
}

.footer-brand .footer-logo {
    font-family: var(--font-sans);
    font-size: 1.375rem;
    font-weight: 700;
    margin: 0 0 0.75rem 0;
    letter-spacing: 0.01em;
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    text-decoration: none;
    text-transform: none;
    color: var(--text-hi);
    background: none;
    -webkit-text-fill-color: currentColor;
    animation: none;
    transition: color 0.22s ease;
}
.footer-brand .footer-logo:hover { color: var(--sig-100); }

.footer-brand p {
    font-family: var(--font-sans);
    font-size: 0.9375rem;
    line-height: 1.7;
    color: var(--text-low);
    max-width: 34ch;
    margin: 0 0 1.75rem 0;
}

.footer-col-title {
    font-family: var(--font-sans);
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--text-low);
    text-transform: uppercase;
    letter-spacing: 0.11em;
    margin: 0 0 1.125rem 0;
    display: block;
}
.footer-col-title::after { display: none; }

.footer-nav {
    list-style: none;
    padding: 0;
    margin: 0;
}

/* Footer link rows carry vertical padding rather than list margin, so the
   tap target is the link itself and clears the 44px minimum. */
.footer-nav li { margin-bottom: 0; }

.footer-nav a {
    color: var(--text-mid);
    text-decoration: none;
    font-size: 0.9375rem;
    display: flex;
    align-items: center;
    min-height: 44px;
    transition: color 0.22s ease;
}
.footer-nav a::before { content: none; }
.footer-nav a:hover { color: var(--text-hi); }

.social-icons {
    display: flex;
    gap: 0.5rem;
    margin-bottom: 1.5rem;
}

.social-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    background: transparent;
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    color: var(--text-low);
    text-decoration: none;
    transition: color 0.22s ease, border-color 0.22s ease, background-color 0.22s ease;
}

.social-icon:hover {
    background: var(--ink-700);
    border-color: var(--sig-800);
    color: var(--sig-100);
    box-shadow: none;
    transform: none;
}

.legal-buttons {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    align-items: flex-start;
}

.sol-btn {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 0;
    background: none;
    border: none;
    color: var(--text-low);
    font-family: var(--font-sans);
    font-size: 0.8125rem;
    text-transform: none;
    letter-spacing: 0;
    text-decoration: none;
    border-radius: 0;
    font-weight: 400;
    width: auto;
    transition: color 0.22s ease;
}
.sol-btn:hover {
    border: none;
    background: none;
    box-shadow: none;
    color: var(--text-mid);
}

.footer-bottom {
    width: 100%;
    max-width: var(--shell);
    margin: clamp(3rem, 5vw, 4rem) auto 0 auto;
    padding: 1.5rem var(--gutter) 0;
    box-sizing: border-box;
    border-top: 1px solid var(--line);
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: 1rem;
}

.footer-copyright {
    font-family: var(--font-sans);
    font-size: 0.8125rem;
    color: var(--text-low);
    margin: 0;
    line-height: 1.6;
}

.revoke-consent-link {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    font-family: var(--font-sans);
    font-size: 0.8125rem;
    color: var(--text-low);
    text-decoration: none;
    transition: color 0.22s ease;
}
.revoke-consent-link:hover { color: var(--text-mid); }

/* Language switcher — the one place a pill is used, because it is a segmented
   two-state control and the shape carries that meaning. */
.cyber-lang-switcher ul {
    list-style: none;
    margin: 0;
    padding: 3px;
    display: inline-flex;
    gap: 2px;
    background: var(--ink-800);
    border: 1px solid var(--line);
    border-radius: var(--r-pill);
}

.cyber-lang-switcher li { margin: 0; }

.cyber-lang-switcher a {
    display: inline-flex;
    align-items: center;
    min-height: 38px;
    padding: 0 0.9rem;
    border-radius: var(--r-pill);
    color: var(--text-low);
    text-decoration: none;
    font-family: var(--font-sans);
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    transition: color 0.22s ease, background-color 0.22s ease;
}
.cyber-lang-switcher a:hover { color: var(--text-hi); }

.cyber-lang-switcher .current-lang a {
    color: var(--ink-900);
    background: var(--sig-100);
}

@media (max-width: 1024px) {
    .footer-grid { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 600px) {
    .footer-grid { grid-template-columns: 1fr; }
    .footer-bottom { flex-direction: column; align-items: flex-start; }
}

/* =========================================================
   ADAPTIVE QUALITY + ACCESSIBILITY
   ========================================================= */

/* Reveal backstop for the LOW tier / reduced-motion. The reveal JS strips the
   reveal-* classes in these states (so this rarely applies), but if it hasn't
   yet, force the content visible. NOTE: deliberately does NOT touch `transform`
   or `transition` — doing so with !important would clobber the component :hover
   lifts, which we keep on the low tier. */
body.perf-low [class*="reveal-"],
body.reduced-motion [class*="reveal-"] {
    opacity: 1 !important;
    visibility: visible !important;
    filter: none !important;
    clip-path: none !important;
}


/* prefers-reduced-motion: neutralise all continuous/decorative motion site-wide
   (shimmer gradients, rotating HUD rings, laser sweep, etc.).
   The hero canvas is skipped entirely in JS for these users. */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
        scroll-behavior: auto !important;
    }
}

/* KILL SWITCH: Destroy CPU-heavy Blurs and Infinite Animations.

   The header bar and its mobile sheet are exempt. They are not decoration
   that a weak machine can simply drop: the blur IS the surface, and without
   it the bar is a flat slab of tint sitting on an atmosphere it is supposed
   to be showing. Two contained surfaces — a 64px strip and one screen-sized
   panel that only exists while a menu is open — are also nothing like the
   full-page blurs this switch was written for. They keep the material at a
   reduced radius instead (see the LOW POWER TIER block below).

   Note this rule never reached the bar in the first place: `*` does not match
   pseudo-elements, and the bar's pane is `.site-header::before`. So before
   the exemption the two halves of one object disagreed on weak hardware —
   glass bar, flat sheet — which is worse than either choice made
   consistently. */
html.perf-low *:not(.mobile-menu-panel),
body.perf-low *:not(.mobile-menu-panel) {
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
}

html.perf-low .hero-wordmark,
html.perf-low .footer-logo, 
html.perf-low .scroll-drip, 
html.perf-low .scroll-emitter::after {
    animation: none !important;
}

/* Keep count-up digits from reflowing the card as the number changes width. */
.metric-value { font-variant-numeric: tabular-nums; }

/* Keyboard focus visibility is defined once, near the end of this file, so
   there is a single source of truth for the ring. */

/* =========================================
   SYSTEM LOADING SCREEN & GRACEFUL DEGRADATION
   ========================================= */
.cyber-loader-overlay {
    position: fixed; 
    top: 0; left: 0; right: 0; bottom: 0;
    width: 100vw; height: 100vh;
    background-color: #0c162a;
    display: flex; align-items: center; justify-content: center;
    z-index: 9999999; 
    opacity: 1; 
    transition: opacity 0.5s ease, visibility 0.5s ease;
}
.cyber-loader-overlay.fade-out { opacity: 0; visibility: hidden; }
/* Session already calibrated (see the inline <head> script in header.php): never
   paint the calibration overlay again — keeps post-first-visit navigation instant
   instead of flashing the loader for what is, after the first page, a no-op. */
.perf-known .cyber-loader-overlay { display: none !important; }
.loader-content { display: flex; align-items: center; justify-content: center; }

/* -----------------------------------------------------------------------
   The mark drawing itself in.

   Three beats, deliberately overlapped rather than sequenced: the whole
   shield resolves out of a blur while its segments are still arriving, and
   the light sweep crosses just as the blur reaches zero. Sequencing them
   end-to-end instead would triple the wait for the same information.

   Every duration, delay and curve here is the animation as authored — do
   not "tidy" the numbers, they are tuned against each other. The keyframe
   names carry an sz- prefix only because `reveal` is the name this theme
   already uses for its scroll-reveal *classes*; an unprefixed @keyframes
   reveal would be a trap for whoever adds one next.

   app.js holds the overlay until this has finished (~2.05s: focus-in runs
   1.9s, the sweep ends at 1s + 1.05s). Changing a duration here without
   changing MIN_MS there means the overlay cuts the mark off mid-draw.
   ----------------------------------------------------------------------- */
/* The mark does not move. It resolves.

   The entrance used to also drift up 8px and scale down from 1.06, and that
   settle read as instability rather than polish — it never looked deliberate,
   it looked like the logo had not finished loading. Everything now happens
   without displacing the mark by a single pixel: the focus pull, the segment
   cascade and the light sweep all play against a shape that is exactly where
   it will end up, from the first frame to the last.

   That also retires a whole class of rendering bugs. There is no animated
   transform left, so there is no per-frame re-tessellation, no subpixel drift,
   and nothing for flex centring on a half pixel to amplify — the problems that
   made this jitter in Firefox cannot occur without motion to carry them.

   The wrapper stays because the two copies need a stacking context to
   cross-fade in. It no longer carries a transform, so it no longer needs
   promoting. */
.cyber-loader-overlay .loader-mark {
    display: grid;              /* one cell; both copies sit in it, stacked */
}
.cyber-loader-overlay .loader-logo {
    grid-area: 1 / 1;           /* same cell, so they overlap rather than stack */
    width: 100px;
    height: auto;
    display: block;
    will-change: opacity;
}

/* ...resolving from soft into sharp focus as it lands.

   The blur is a fixed property here and must stay one. Animating it is what
   the entrance originally did, and it is why the mark jittered in Firefox:
   Chromium can run a filter animation on the compositor, Gecko cannot, so
   there it was main-thread work repainting the mark every frame. A blur that
   never changes is rasterised once, and the focus pull comes from cross-fading
   the blurred copy against the sharp one instead. Opacity composites on every
   engine, so nothing repaints mid-animation.

   The .5s hold on the way out is not a stylistic beat, it stops a visible dip.
   Two identical copies at opacity a and b composite to a + b - ab, so a plain
   symmetric cross-fade (a + b = 1) bottoms out at 0.75 halfway through and the
   mark reads as dimming before it lands. Holding the soft copy at full while
   the sharp one climbs keeps the sum above 1 throughout. */
.cyber-loader-overlay .loader-logo-soft {
    /* 3.7px, not the 7px this was authored with, because the mark shrank.
       A blur radius is an absolute length while the thing it softens is not,
       so 7px against the original 190px mark (3.7% of its width) became 7% of
       a 100px one — the same number reading roughly twice as strong. 3.7px on
       100px is the proportion the effect was drawn at. Resize the mark again
       and this wants rescaling with it: keep it at 3.7% of the width. */
    filter: blur(3.7px);
    animation: sz-soft-out 1.4s cubic-bezier(.2, .6, .2, 1) .5s both;
}
.cyber-loader-overlay .loader-logo-sharp {
    animation: sz-sharp-in 1.9s cubic-bezier(.2, .6, .2, 1) both;
}

/* the three segments fade in on a slow, spaced cascade */
.cyber-loader-overlay .seg {
    opacity: 0;
    animation: sz-reveal 1.1s ease-out forwards;

    /* The mark's gradients run to near-black at their core (#000B1F, #020F24,
       #04132A) because it was drawn for a white page. On --ink-800 (#0a1120)
       that core lands at a 1.04:1 contrast ratio against the backdrop — the
       lower-left of the shield was not dim, it was gone. The gradient's centre
       resolves to roughly (19.5, 65.3) in mark space, outside the shape and
       below-left of it, which is why segments 2 and 3 lost the most.

       A hairline outline fixes it without touching the fills, so the brand
       gradient still reads exactly as drawn wherever it has enough light.

       non-scaling-stroke is doing real work here: it takes the width out of
       the viewBox's units, so the line stays a hairline whether the mark is
       rendered at 100px or someone doubles it later. Without it, 0.7 user
       units against a 70-unit viewBox would scale with the logo and thicken
       into a cartoon outline.

       Stroking these paths cannot disturb the light sweep: clipPath resolves
       against fill geometry and ignores strokes, so #logoClip is unchanged. */
    stroke: rgba(84, 175, 243, 0.60);
    stroke-width: 0.7;
    vector-effect: non-scaling-stroke;
}
.cyber-loader-overlay .seg-1 { animation-delay: .15s; }    /* top segment first */
.cyber-loader-overlay .seg-2 { animation-delay: .45s; }
.cyber-loader-overlay .seg-3 { animation-delay: .75s; }

/* the light sweep, timed to arrive as the mark snaps into focus */
.cyber-loader-overlay .glint { animation: sz-glint 1.05s 1s ease-in-out forwards; }

/* The two halves of the focus pull. Nothing here touches transform: the mark's
   position is fixed for the whole entrance, by design. */
@keyframes sz-soft-out {
    from { opacity: 1; }
    to   { opacity: 0; }
}
@keyframes sz-sharp-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}
@keyframes sz-reveal {
    from { opacity: 0; }
    to   { opacity: 1; }
}
/* Translates the wrapper group around the streak (see header.php), which every
   engine animates — the earlier version animated the rect's own x, an SVG
   geometry property Gecko does not animate, so the sweep never travelled in
   Firefox.

   110px is not a new number: it is the 25 -> 135 the x-based version covered.
   The rect keeps its x=25 and its skew attribute, and shifting a sheared shape
   horizontally is the same shape shifted horizontally — skewX displaces x by
   a function of y alone, so it commutes with a horizontal translation. Same
   pixels as before on Chromium, and now the same pixels on Gecko too. */
@keyframes sz-glint {
    from { transform: translateX(0); }
    to   { transform: translateX(110px); }
}

/* Users who ask for no motion get the finished mark, painted static. app.js
   reads the same preference and skips its hold, so the overlay clears at once
   instead of sitting on a still image for two and a half seconds. */
@media (prefers-reduced-motion: reduce) {
    /* The blurred copy is dropped rather than faded: with no animation running
       it would otherwise sit at full opacity over the sharp one forever. */
    .cyber-loader-overlay .loader-logo       { animation: none; will-change: auto; }
    .cyber-loader-overlay .loader-logo-soft  { display: none; }
    .cyber-loader-overlay .loader-logo-sharp { opacity: 1; }
    .cyber-loader-overlay .seg               { opacity: 1; animation: none; }
    .cyber-loader-overlay .glint             { animation: none; }
}

/* =========================================================================
   LOW POWER TIER
   The header bar and its mobile sheet keep their glass here — it is the one
   material the site's chrome is made of, and dropping it leaves a flat band
   where a window should be. What the low tier gets is a cheaper radius: blur
   cost scales with the radius, and the two surfaces are small enough that a
   modest one still reads as glass. Saturation is free, so it stays at full
   strength; it is what keeps the colour underneath recognisable, which is
   most of the effect anyway.
   ========================================================================= */
/* An animated blur is the single most expensive thing a software renderer can
   be asked to composite, and the loading screen is exactly where a struggling
   machine is already busiest. Low tier gets the finished mark instead, which
   costs one paint. Safe to snap like this because the tier is settled before
   the overlay paints: instantlyIncapable() runs on the first line of app.js and
   a stored verdict is read from sessionStorage, so this never toggles mid-draw. */
body.perf-low .cyber-loader-overlay .loader-logo       { animation: none !important; will-change: auto !important; }
body.perf-low .cyber-loader-overlay .loader-logo-soft  { display: none !important; }
body.perf-low .cyber-loader-overlay .loader-logo-sharp { opacity: 1 !important; }
body.perf-low .cyber-loader-overlay .seg               { opacity: 1 !important; animation: none !important; }
body.perf-low .cyber-loader-overlay .glint             { animation: none !important; }

/* Both surfaces, one declaration, because they are one material — a low tier
   that made the bar and its sheet diverge would reintroduce exactly the seam
   the full tier was tuned to remove. Only the radius drops; tint and
   saturation are unchanged, and they carry most of the effect. */
html.perf-low .site-header::before,
body.perf-low .site-header::before,
html.perf-low .mobile-menu-panel,
body.perf-low .mobile-menu-panel {
    -webkit-backdrop-filter: blur(14px) saturate(125%);
    backdrop-filter: blur(14px) saturate(125%);
}

/* =========================================================================
   LOW TIER -- keep the look, kill only the per-frame-expensive transitions.
   With the blanket lockdown removed, the low tier regains every static style
   (shadows, clip-path shapes, gradient text) and every cheap hover effect
   (lifts, colour/opacity fades, the hover laser). The ONLY transitions removed
   here are the ones that reflow or re-rasterise on every animation frame:
     - .faq-answer : animates grid-template-rows (layout reflow per frame)
     - timeline nodes : toggle their highlight as the spine fill passes them
                        while the page scrolls; made instant so scrolling
                        stays cheap
     - .capability-pillar : animates flex-grow on hover (layout reflow);
                        removed so a stray hover on weak hardware can't
                        trigger a janky resize, not because it runs per frame
   Heavy blurs (backdrop-filter), the front-page canvas and the entrance
   reveals remain stripped/gated elsewhere. The services hero's phase deck is
   handled at its own rule (blur dropped, cross-fade kept).
   ========================================================================= */
body.perf-low .faq-answer { transition: none !important; }
body.perf-low .cyber-node,
body.perf-low .border-layer-1,
body.perf-low .border-layer-2,
body.perf-low .node-content { transition: none !important; }
body.perf-low .capability-pillar { transition: opacity 0.35s ease, border-color 0.35s ease !important; }

/* =========================================================================
   LOW-TIER HINT: "enable hardware acceleration" (hidden on the full tier)
   ========================================================================= */
.ha-hint { display: none; }
body.perf-low .ha-hint {
    display: block;
    position: fixed; left: 16px; bottom: 16px; z-index: 2147483000;
    font-family: var(--font-mono, monospace);
}
.ha-hint-btn {
    width: 44px; height: 44px; border-radius: var(--r-sm);
    border: 1px solid var(--line-strong); background: var(--ink-700); color: var(--sig-100);
    font-size: 18px; font-weight: 700; line-height: 1; cursor: pointer;
    display: flex; align-items: center; justify-content: center;
}
.ha-hint-panel {
    display: none; position: absolute; left: 0; bottom: 46px;
    width: min(340px, 80vw); padding: 16px 18px;
    background: var(--ink-700); border: 1px solid var(--line-strong); border-radius: var(--r-lg);
    box-shadow: var(--e3);
    color: var(--text-mid); font-size: 0.82rem; line-height: 1.55;
}
.ha-hint:hover .ha-hint-panel,
.ha-hint:focus-within .ha-hint-panel,
.ha-hint.open .ha-hint-panel { display: block; }
.ha-hint-panel strong { display: block; color: var(--text-hi); font-size: 0.92rem; margin-bottom: 8px; }
.ha-hint-panel p { margin: 0 0 10px; }
.ha-hint-instr { margin-top: 10px; }
.ha-hint-os { display: block; color: #00e6ff; font-weight: 700; letter-spacing: 1px; margin-bottom: 2px; }

/* =========================================================================
   GPU HARDWARE LOCK (FULL TIER ONLY)
   Permanently promotes these elements to their own compositor layer so they
   don't re-rasterise (and sub-pixel "snap") when an animation/transition ends.
   Scoped to the full tier with :where(.perf-full) — :where() carries ZERO
   specificity, so this keeps the original (low) specificity of each selector
   and does not over-clobber the entrance-reveal transforms. The low tier has
   no animations to snap, and permanent layers would only add compositing cost
   to the software renderer, so it is deliberately excluded.
   ========================================================================= */
/* :not(.reveal) is load-bearing. The lock exists to stop a component
   re-rasterising once its animation ends; while an element is still carrying
   .reveal it owns its own transform, and letting this rule win there would
   flatten the entrance to nothing. The reveal JS removes .reveal on
   completion, at which point the lock takes over — which is exactly the
   handoff this rule was written for. */
:where(html.perf-full) :is(
    .cyber-heading, .body-text, .lead-text,
    .section-subtitle, .section-main-subtitle, .section-main-title,
    .hero-wordmark, .hero-lead,
    .btn,
    .capability, .audience-col, .cyber-node, .metric-card, .hud-card,
    .faq-item, .faq-answer,
    .capability-pillar, .solution-tile, .value-card
):where(:not(.reveal, [class*="reveal-"])) {
    /* Wrapped in :where() so the exclusion costs no specificity — otherwise this
       rule would tie with, and then beat, every component's :hover transform. */
    transform: translateZ(0);
    backface-visibility: hidden;
}

body.perf-low .capability,
body.perf-low .audience-col,
body.perf-low .cyber-node,
body.perf-low .hud-card,
body.perf-low .metric-card,
body.perf-low .btn,
body.perf-low .solution-tile,
body.perf-low .value-card,
body.perf-low .faq-item {
    transition: none !important;
}

body.perf-low .hero-wordmark,
body.perf-low .hero-lead {
    transition: none !important;
    animation: none !important;
    opacity: 1 !important;
    visibility: visible !important;
    transform: none !important;
}

/* =========================================================================
   LEGAL PAGES — Privacy Policy & Terms and Conditions — composition pass
   Both documents share one hero (`.legal-hero`, own class — the shared
   `.internal-hero`/`.hero-graphic` HUD is no longer used by any page after
   this pass, but the CSS itself stays: it costs nothing to leave and a
   future page may still want it) and one document body. The numbered
   section headings are a real, referenceable sequence (§1, §2…), so the
   counter stays; the uppercase, letter-spacing and glow around it do not.
   ========================================================================= */

.legal-hero {
    position: relative;
    background: var(--ink-700);
    border-bottom: 1px solid var(--line);
    padding: calc(var(--s-section) + var(--header-h)) 0 var(--s-section);
    min-height: calc(var(--vh, 1vh) * 100);
    min-height: 100dvh;
    /* svh - same reason as .contact-hero above. */
    min-height: 100svh;
    display: flex;
    align-items: center;
    overflow: hidden;
    isolation: isolate;
}
.legal-hero .shell { position: relative; z-index: 1; }
.legal-hero-title {
    font-family: var(--font-sans);
    font-size: var(--t-display);
    font-weight: 700;
    letter-spacing: -0.035em;
    line-height: 1.04;
    color: var(--text-hi);
    max-width: 20ch;
    margin: 0 0 1rem;
}
.legal-hero-updated {
    font-family: var(--font-sans);
    font-size: 0.9rem;
    color: var(--text-low);
}

.legal-section {
    position: relative;
    padding: var(--s-section) 0;
    background: var(--ink-800);
}

.legal-doc {
    max-width: 58rem;
    margin: 0 auto;
    counter-reset: legal-section;
}

.legal-intro { margin-bottom: clamp(2rem, 4vw, 3rem); }
.legal-intro p {
    font-family: var(--font-sans);
    font-size: var(--t-lead);
    line-height: 1.75;
    color: var(--text-mid);
    margin: 0;
}

/* --- Numbered document blocks -------------------------------------------- */
.legal-block {
    position: relative;
    padding: clamp(1.5rem, 2.5vw, 2rem) clamp(1.5rem, 2.5vw, 2.25rem);
    margin-bottom: 1.25rem;
    background: var(--ink-600);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
}

.legal-block h2 {
    counter-increment: legal-section;
    display: flex;
    align-items: baseline;
    gap: 0.875rem;
    font-size: var(--t-h3);
    font-weight: 600;
    letter-spacing: -0.018em;
    color: var(--text-hi);
    margin: 0 0 1rem;
}
.legal-block h2::before {
    content: counter(legal-section, decimal-leading-zero);
    flex: 0 0 auto;
    color: var(--sig-100);
    font-size: 0.85rem;
    font-variant-numeric: tabular-nums;
    font-weight: 700;
}

.legal-block h3 {
    font-size: 1rem;
    font-weight: 600;
    color: var(--text-hi);
    margin: 1.5rem 0 0.625rem;
}

.legal-block p {
    font-family: var(--font-sans);
    font-size: var(--t-body);
    line-height: 1.75;
    color: var(--text-mid);
    margin: 0 0 0.875rem;
}
.legal-block p:last-child { margin-bottom: 0; }

/* --- Lists ---------------------------------------------------------------- */
.legal-list {
    list-style: none;
    margin: 0 0 0.875rem;
    padding: 0;
}
.legal-list li {
    position: relative;
    padding-left: 1.5rem;
    margin-bottom: 0.625rem;
    font-family: var(--font-sans);
    font-size: var(--t-body);
    line-height: 1.7;
    color: var(--text-mid);
}
.legal-list li::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0.6em;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--sig-600);
}
.legal-list li:last-child { margin-bottom: 0; }
.legal-list li strong { color: var(--text-hi); font-weight: 600; }

/* --- Inline links (the contact-box CTA is a .btn, styled separately) ------ */
.legal-section a:not(.btn) {
    color: var(--sig-100);
    text-decoration: none;
    border-bottom: 1px solid var(--sig-800);
    transition: color 0.25s ease, border-color 0.25s ease;
}
.legal-section a:not(.btn):hover {
    color: var(--text-hi);
    border-bottom-color: var(--sig-400);
}

/* --- Contact / CTA box at the end ---------------------------------------- */
.legal-contact-box {
    margin-top: clamp(2rem, 4vw, 3rem);
    padding: clamp(2rem, 3.5vw, 2.75rem);
    text-align: center;
    background: var(--ink-600);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--e1);
}
.legal-contact-box h3 {
    font-size: var(--t-h3);
    font-weight: 600;
    letter-spacing: -0.018em;
    color: var(--text-hi);
    margin: 0 0 0.625rem;
}
.legal-contact-box p {
    max-width: 46ch;
    margin: 0 auto 1.75rem;
    font-family: var(--font-sans);
    color: var(--text-mid);
    line-height: 1.7;
}

/* --- Responsive ----------------------------------------------------------- */
@media (max-width: 640px) {
    .legal-hero-title { max-width: none; }
}

@media (max-width: 460px) {
    .legal-block h2 { flex-direction: column; gap: 0.25rem; }
}


/* =========================================
   GDPR BANNER UI
   ========================================= */
.cyber-cookie-banner {
    position: fixed; bottom: 24px; left: 24px; right: 24px; max-width: 600px;
    background: var(--ink-700);
    border: 1px solid var(--line-strong);
    border-radius: var(--r-lg);
    z-index: 999999; padding: 24px; display: flex; flex-direction: column; gap: 20px;
    box-shadow: var(--e3);
}

.cookie-actions { display: flex; gap: 12px; justify-content: flex-end; }

@media (min-width: 768px) {
    .cyber-cookie-banner { flex-direction: row; align-items: center; justify-content: space-between; }
    .cookie-actions { flex-shrink: 0; }
}


/* =========================================================================
   CLOSING BAND
   The home page previously ran out of sections and fell straight into the
   footer with no ask. This is the one place on the page where centring is
   earned: a single statement, a single action, and the indigo/cyan ambient
   field from the hero returning to close the loop.
   ========================================================================= */
.cta-band {
    position: relative;
    background: var(--ink-700);
    border-top: 1px solid var(--line);
    padding: clamp(4.5rem, 9vw, 8rem) 0;
    overflow: hidden;
    isolation: isolate;
}

.cta-band::before {
    content: '';
    position: absolute;
    inset: -20% -10%;
    z-index: 0;
    background:
        radial-gradient(50% 70% at 18% 20%, rgba(43, 31, 107, 0.65) 0%, rgba(20, 16, 51, 0) 68%),
        radial-gradient(45% 65% at 84% 82%, rgba(14, 74, 92, 0.5) 0%, rgba(14, 74, 92, 0) 70%);
    pointer-events: none;
}

.cta-band .shell {
    position: relative;
    z-index: 1;
    text-align: center;
}

.cta-band h2 {
    font-size: var(--t-h2);
    font-weight: 600;
    letter-spacing: -0.025em;
    color: var(--text-hi);
    margin: 0 auto 1.25rem;
    max-width: 22ch;
}

.cta-band p {
    font-size: var(--t-lead);
    line-height: 1.65;
    color: var(--text-mid);
    margin: 0 auto 2.5rem;
    max-width: 54ch;
}

.cta-band .hero-actions { justify-content: center; }


/* =========================================================================
   SERVICES PAGE — composition pass
   Five sections, five layout families: an asymmetric hero, a numbered
   process list, the shared capability bento, a two-group solutions grid,
   and the closing band defined above (reused as-is).

   Retires the horizontal-scroll state machine, the text-decrypt effect, the
   HUD reticle and the pillar accordion this page used to run — all four
   were the same "layout-thrash under scroll" and "fake-console" problems
   the home page's rebuild had already ruled out. This pass deliberately did
   not touch `.internal-hero`, `.hero-graphic` or `.cyber-button` at the
   time, since About, Contact and the legal pages had not had their own
   composition pass yet. They have since (see ABOUT PAGE, CONTACT PAGE and
   LEGAL PAGES below) — `.internal-hero`/`.hero-graphic`/`.cyber-button`
   are fully retired now, no page emits them.
   ========================================================================= */

/* ---- Hero: pinned stage, scroll-driven phase deck -----------------------
   The section is a tall TRACK; the STAGE inside it is sticky and exactly one
   viewport high. So while the track scrolls past, the copy on the left and
   the panel on the right hold perfectly still and only the card inside the
   panel changes — the reader advances the sequence at their own pace instead
   of watching a timer, which is the one thing the home page's panel could
   not do honestly (see the ENGAGEMENT STACK note above).

   Nothing here hijacks the scroll: `position: sticky` is native, the wheel
   keeps its normal distance, and the page can be scrolled straight past.

   Note `overflow` lives on the stage, never on the section — an
   `overflow: hidden` ancestor turns itself into the sticky element's
   containing scroller and silently kills the pin. */
/* The stage is a full viewport tall and pins at `top: 0`, not at the header's
   height, because `.services-hero-atmos` lives INSIDE the stage: pinning it
   below the bar would leave the top 64px of every screen painting the
   section's flat `--ink-700` instead of the atmosphere, and the glass would
   have nothing to show through. The stage carries the bar's height as its own
   top padding instead, so the grid centres below the bar exactly as before.

   The pin arithmetic is unchanged. app.js computes range as
   (trackHeight - stageHeight) and both grew by --header-h. */
.services-hero {
    position: relative;
    background: var(--ink-700);
    border-bottom: 1px solid var(--line);
    /* Scroll distance spent on each phase after the first. */
    --phase-dwell: 62vh;
    --stage-h: calc(var(--vh, 1vh) * 100);
}

@supports (height: 100dvh) {
    .services-hero { --stage-h: 100dvh; }
}
/* svh wins where it exists: --stage-h is a term in app.js scroll-to-card
   arithmetic, and a pinned box whose height tracks the address bar is a moving
   target for the number deciding which card shows. */
@supports (height: 100svh) {
    .services-hero { --stage-h: 100svh; }
}

.services-hero-track {
    position: relative;
    height: calc(var(--stage-h) + (var(--phase-count, 4) - 1) * var(--phase-dwell));
}

.services-hero-stage {
    position: sticky;
    top: 0;
    height: var(--stage-h);
    min-height: calc(560px + var(--header-h));
    padding-top: var(--header-h);
    display: flex;
    align-items: center;
    overflow: hidden;
    isolation: isolate;
}

.services-hero-grid {
    position: relative;
    z-index: 1;
    width: 100%;
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 27rem);
    align-items: center;
    gap: clamp(2rem, 4vw, 4rem);
}

.services-hero-copy { max-width: 40rem; }

.services-hero-title {
    font-family: var(--font-sans);
    font-size: var(--t-display);
    font-weight: 700;
    letter-spacing: -0.035em;
    line-height: 1.04;
    color: var(--text-hi);
    max-width: 18ch;
    margin: 0 0 1.5rem;
}

.services-hero-lead {
    font-family: var(--font-sans);
    font-size: var(--t-lead);
    line-height: 1.65;
    color: var(--text-mid);
    max-width: 46ch;
    margin: 0 0 2.5rem;
}

@media (max-width: 767.98px) {
    .services-hero-title { max-width: 14ch; margin-bottom: 0.875rem; }
    .services-hero-lead { max-width: none; margin-bottom: 1.5rem; line-height: 1.55; }
    .services-hero .hero-actions { gap: 0.625rem; }
    .services-hero .hero-actions .btn { flex: 1 1 auto; padding-inline: 0.875rem; }
}

/* ---- Phase panel ---------------------------------------------------------
   The object on the right of the services hero. Same surface language as the
   home page's engagement stack — one panel, the site's ambient pair inside
   it — but a different instrument, because the content is a different shape:
   four ordered phases, one at a time, advanced by scroll rather than shown
   all at once.

   The deck is a single grid cell that every card is placed into, so the
   panel's height is the tallest card's and nothing reflows when the card
   changes. Cards below the current one wait underneath, cards already read
   sit above: the vertical direction of the transition matches the direction
   of the scroll that caused it. */
.phase-panel {
    position: relative;
    background: var(--ink-700);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--e3), var(--e-top);
    padding: 1.5rem 1.5rem 1.625rem;
    overflow: hidden;
    isolation: isolate;
}

.phase-panel::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 0;
    background:
        radial-gradient(120% 90% at 100% 0%, rgba(43, 31, 107, 0.55) 0%, rgba(20, 16, 51, 0) 60%),
        radial-gradient(90% 80% at 0% 100%, rgba(14, 74, 92, 0.35) 0%, rgba(14, 74, 92, 0) 62%);
    pointer-events: none;
}

.phase-panel-head { position: relative; z-index: 1; }

.phase-panel-title {
    font-family: var(--font-sans);
    font-size: 1.0625rem;
    font-weight: 600;
    letter-spacing: -0.015em;
    color: var(--text-hi);
    margin: 0;
}

.phase-panel-note {
    font-size: 0.8125rem;
    line-height: 1.55;
    color: var(--text-low);
    margin: 0.375rem 0 0;
}

/* Progress: one segment per phase, each filling across its own stretch of
   scroll. It answers "how many of these are there, and where am I" without a
   second copy of the four titles. app.js writes --p (0..1) per segment. */
.phase-rail {
    position: relative;
    z-index: 1;
    list-style: none;
    display: flex;
    gap: 0.375rem;
    margin: 1.25rem 0 1.5rem;
    padding: 0;
}

.phase-tick {
    flex: 1 1 0%;
    height: 2px;
    border-radius: 1px;
    background: var(--line-strong);
    overflow: hidden;
}

.phase-tick span {
    display: block;
    height: 100%;
    background: var(--sig-400);
    transform: scaleX(var(--p, 0));
    transform-origin: left;
}

.phase-deck {
    position: relative;
    z-index: 1;
    display: grid;
    list-style: none;
    margin: 0;
    padding: 0;
}

.phase-card {
    grid-area: 1 / 1;
    opacity: 0;
    transform: translateY(20px);
    filter: blur(5px);
    transition: opacity 0.5s ease, transform 0.6s cubic-bezier(0.16, 1, 0.3, 1),
                filter 0.5s ease;
}

/* Already read: waits above, in the direction it left. */
.phase-card.is-past { transform: translateY(-20px); }

.phase-card.is-current {
    opacity: 1;
    transform: none;
    filter: none;
}

.phase-card-index {
    display: block;
    font-family: var(--font-sans);
    font-size: 0.8125rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.02em;
    color: var(--sig-100);
    margin-bottom: 0.5rem;
}

.phase-card-title {
    font-family: var(--font-sans);
    font-size: 1.3125rem;
    font-weight: 600;
    letter-spacing: -0.02em;
    line-height: 1.2;
    color: var(--text-hi);
    margin: 0 0 0.625rem;
}

.phase-card-body {
    font-family: var(--font-sans);
    font-size: 0.9375rem;
    line-height: 1.65;
    color: var(--text-mid);
    margin: 0 0 1.125rem;
}

.phase-tags {
    list-style: none;
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin: 0;
    padding: 0;
}
.phase-tags li {
    font-family: var(--font-sans);
    font-size: 0.75rem;
    line-height: 1.4;
    color: var(--text-mid);
    border: 1px solid var(--line-strong);
    border-radius: var(--r-sm);
    padding: 0.28rem 0.6rem;
}

/* ---- Below desktop: the pin moves from the stage to the panel -------------
   A 100dvh stage holding a display statement, a lead, two buttons AND the
   deck does not fit a 768px-wide screen, let alone a 390px one. So the pin
   changes what it holds rather than switching itself off: the copy scrolls
   normally, and the PANEL is the sticky element, holding still while the
   scroll advances the deck exactly as it does on desktop.

   The extra scroll distance the deck needs lives as padding-bottom on the
   grid, because the grid is the panel's sticky containing block — padding on
   the track would not extend it. app.js reads whichever element is actually
   sticky, so CSS still decides and the arithmetic is the same either way. */
@media (max-width: 1023.98px) {
    .services-hero { padding-block: 0; --phase-dwell: 54vh; }

    .services-hero-track { height: auto; }

    .services-hero-stage {
        position: static;
        height: auto;
        min-height: 0;
        /* The stage clipped the deck on desktop; a static stage must not, or
           it becomes the panel's scroll container and kills the pin. */
        overflow: visible;
        display: block;
        /* Still carrying the glass bar's height on top of its own padding —
           `padding-block` here would silently drop it and slide the statement
           under the bar. */
        padding-top: calc(clamp(1.75rem, 6vw, 3rem) + var(--header-h));
        padding-bottom: clamp(1.75rem, 6vw, 3rem);
    }

    /* Block flow, not a one-column grid. A sticky GRID ITEM is constrained to
       its own grid area, and with the rows sized to their content that area is
       exactly the panel's own height — so it has nowhere to travel and the pin
       silently does nothing. As a block child, the panel is constrained by the
       container's content box instead, which the spacer below makes tall. */
    .services-hero-grid { display: block; }

    /* The scroll distance the deck needs, as real height on the panel's
       containing block. A pseudo-element rather than padding, because the
       content box is what the sticky constraint reads. */
    .services-hero-grid::after {
        content: '';
        display: block;
        height: calc((var(--phase-count, 4) - 1) * var(--phase-dwell));
    }

    .services-hero-copy {
        max-width: 40rem;
        margin-bottom: clamp(1.75rem, 5vw, 2.5rem);
    }

    /* The wrapper is what gets pinned, and it stands exactly one viewport tall
       under the header. The panel is then centred inside it by flexbox, which
       is how the desktop stage has always done it.

       The obvious alternative — pinning the panel itself and pushing it down
       with a `top` offset — cannot work, because centring needs the panel's
       height and CSS has no way to read it. Every version of that is a guess
       that drifts the moment a card's copy changes length. A full-height
       sticky box makes the browser do the arithmetic. */
    .phase-pin {
        position: sticky;
        top: var(--header-h);
        /* svh, not dvh, and this is the one place on the site where that is
           the right choice. `dvh` tracks the address bar, so on a phone this
           box grew and shrank as you scrolled — and its height is a term in
           the scroll-to-card arithmetic in app.js. A pinned element whose
           height changes mid-pin is a moving target for the very number that
           decides which card is showing.

           `svh` is the viewport with the browser chrome shown: a fixed number
           for the whole session. The box is then slightly shorter than the
           screen once the bar retracts, which costs nothing here because the
           panel is centred inside it rather than filling it.

           Declared three times on purpose: the JS --vh fallback first, then
           dvh, then svh, so each browser takes the best line it understands. */
        height: calc(var(--vh, 1vh) * 100 - var(--header-h));
        height: calc(100dvh - var(--header-h));
        height: calc(100svh - var(--header-h));
        display: flex;
        align-items: center;
    }

    .phase-panel {
        width: 100%;
        max-width: 38rem;
        /* Never taller than the box it is centred in; if a card's copy ever
           outgrows the screen the panel scrolls internally rather than
           spilling past the pin. */
        max-height: 100%;
        overflow-y: auto;
    }
}

/* Motion turned down: no pin anywhere, no deck. The track collapses to its
   content and all four phases are a plain ordered list under the copy.
   Layout decides it, so the page is correct before app.js runs and correct
   if it never does. */
@media (prefers-reduced-motion: reduce) {
    .services-hero {
        padding-block: clamp(3rem, 8vw, 5rem);
    }
    .services-hero-track { height: auto; }
    .services-hero-stage {
        position: static;
        height: auto;
        min-height: 0;
        overflow: visible;
        /* The bar's height stays on the stage, not on the section, so the
           phone tier further down can raise it to header + hero-pad without
           the two offsets stacking. */
        padding-top: var(--header-h);
        padding-bottom: 0;
    }
    .services-hero-grid {
        grid-template-columns: minmax(0, 1fr);
        padding-bottom: 0;
    }
    .phase-pin {
        position: static;
        height: auto;
        display: block;
    }
    .phase-panel {
        position: static;
        max-width: 38rem;
        max-height: none;
        overflow: hidden;
    }
    .phase-rail { display: none; }
    .phase-deck {
        gap: 1.75rem;
        grid-auto-flow: row;
    }
    .phase-card {
        grid-area: auto;
        opacity: 1;
        transform: none;
        filter: none;
        transition: none;
        padding-top: 1.5rem;
        border-top: 1px solid var(--line);
    }
    .phase-card:first-child {
        padding-top: 0;
        border-top: none;
    }
}

@media (max-width: 767.98px) {
    /* The sticky panel must stay shorter than the space under the header, or
       the browser sticks it to the BOTTOM of the viewport instead and the
       deck advances off screen. Trimming the head and the rail keeps the
       tallest card's panel around 400px against ~760px of room at 844px, and
       still clears it on a 667px iPhone SE. */
    .phase-panel { padding: 1.125rem 1.125rem 1.25rem; }
    .phase-panel-note { margin-top: 0.25rem; }
    .phase-rail { margin: 0.875rem 0 1.125rem; }
}

/* The blur is the one expensive property in the deck; on the low tier the
   cards still cross-fade and travel, they just do not defocus. */
body.perf-low .phase-card {
    filter: none;
    transition: opacity 0.4s ease, transform 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}

/* ---- Capabilities: widening pillars ---------------------------------------
   Five vertical pillars, one widening on hover/focus. Three things the
   previous version got wrong, and how each is fixed here:

   1. NOTHING HELD THE TOP. An index number floated up there and the text
      was pinned to the bottom, leaving two thirds of a 500px column empty.
      Every pillar now carries a mark at the top and a light wash rising from
      the base, so the column is composed top to bottom in BOTH states, and
      the mark, the vertical title and the expanded copy all sit on one left
      axis rather than three different alignments.

   2. THE TEXT RE-WRAPPED WHILE THE PILLAR COLLAPSED, which read as the copy
      being squeezed vertically before it disappeared. Every absolutely
      positioned child now has a width that does not depend on the pillar's:
      the vertical title is a fixed narrow column, and the panel copy is
      sized in container-query units off `.capability-pillars`, which never
      changes width. The pillar's `overflow: hidden` clips the copy on the
      way out instead of reflowing it — and it is already invisible by then,
      because the exit transition is a fast opacity with no delay while the
      entrance is the slower, delayed one.

   3. FIVE IDENTICAL SLABS. Opening one now dims the other four (`:has`), so
      the row resolves to a single object with a focus rather than a set of
      equally loud boxes.

   The mobile tap accordion still animates height, but only ever on a tap —
   never on a scroll frame, which is what made the retired version costly. */
.capability-pillars {
    display: flex;
    gap: clamp(0.5rem, 0.8vw, 0.75rem);
    /* Shorter than the retired version's 500px: that height was set by the
       vertical labels needing room, and left the expanded copy stranded at
       the bottom of a column two thirds of which was empty. */
    height: clamp(380px, 33vw, 465px);
    /* The stable width every child measures against. */
    container-type: inline-size;
}

.capability-pillar {
    position: relative;
    flex: 1 1 0%;
    min-width: 0;
    background: linear-gradient(to bottom, var(--ink-700) 0%, var(--ink-600) 62%);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--e1);
    overflow: hidden;
    cursor: pointer;
    transition: flex-grow 0.55s cubic-bezier(0.16, 1, 0.3, 1),
                opacity 0.35s ease, border-color 0.35s ease, box-shadow 0.35s ease;
}

/* The ambient pair, banked into the base of the pillar so the expanded copy
   has ground under it instead of floating in flat navy. Off until the pillar
   opens — a wash on all five at once would just be five gradients. */
.capability-pillar::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    opacity: 0;
    background:
        radial-gradient(120% 62% at 6% 104%, rgba(14, 74, 92, 0.62) 0%, rgba(14, 74, 92, 0) 68%),
        radial-gradient(90% 50% at 96% -6%, rgba(43, 31, 107, 0.5) 0%, rgba(43, 31, 107, 0) 70%);
    transition: opacity 0.45s ease;
}

.capability-pillar:hover,
.capability-pillar:focus-visible,
.capability-pillar.is-active {
    flex-grow: 3.4;
    border-color: var(--sig-800);
    box-shadow: var(--e3), var(--e-top);
}

.capability-pillar:hover::before,
.capability-pillar:focus-visible::before,
.capability-pillar.is-active::before { opacity: 1; }

/* Focus deepens: while one pillar is open the rest step back. */
.capability-pillars:has(.capability-pillar:hover) .capability-pillar:not(:hover),
.capability-pillars:has(.capability-pillar.is-active) .capability-pillar:not(.is-active) {
    opacity: 0.55;
}

/* Centred while the pillar is closed, on the left axis once it opens. Both
   the mark and the label below it are fixed-size boxes, so `left` only ever
   slides them — there is no width for text to re-wrap inside. */
.capability-pillar-mark {
    position: absolute;
    top: 1.5rem;
    /* `left: 50%` rather than a fixed offset plus a transform, because the
       four pillars that are NOT open also change width when a sibling opens —
       a percentage keeps every closed mark centred through that, which a
       constant translate could not. The half-width correction is a transform,
       so `left` is the only layout property in play. */
    left: 50%;
    translate: -50% 0;
    z-index: 2;
    display: block;
    width: 28px;
    height: 28px;
    color: var(--text-low);
    transition: color 0.35s ease,
                left 0.5s cubic-bezier(0.16, 1, 0.3, 1),
                translate 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
.capability-pillar:hover .capability-pillar-mark,
.capability-pillar:focus-visible .capability-pillar-mark,
.capability-pillar.is-active .capability-pillar-mark {
    left: 1.5rem;
    translate: 0 0;
}
.capability-pillar-mark svg {
    display: block;
    width: 100%;
    height: 100%;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.4;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.capability-pillar:hover .capability-pillar-mark,
.capability-pillar:focus-visible .capability-pillar-mark,
.capability-pillar.is-active .capability-pillar-mark { color: var(--sig-400); }

/* A fixed-width column, centred under the mark. Because the box never
   changes width, the title cannot re-wrap while the pillar collapses — which
   is what used to read as the copy being squeezed on the way out. */
.capability-pillar-label {
    position: absolute;
    top: 5rem;
    bottom: 1.75rem;
    left: 50%;
    width: 2rem;
    translate: -50% 0;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    writing-mode: vertical-rl;
    transform: rotate(180deg);
    font-family: var(--font-sans);
    font-size: 1.125rem;
    font-weight: 600;
    letter-spacing: -0.012em;
    color: var(--text-hi);
    white-space: nowrap;
    /* Returning: waits for the width to finish before fading back in. */
    transition: opacity 0.3s ease 0.3s;
}
.capability-pillar:hover .capability-pillar-label,
.capability-pillar:focus-visible .capability-pillar-label,
.capability-pillar.is-active .capability-pillar-label {
    opacity: 0;
    /* Leaving: immediately, so it never overlaps the copy arriving. */
    transition: opacity 0.18s ease;
}

.capability-pillar-panel {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    padding: 1.5rem;
    opacity: 0;
    transform: translateY(14px);
    /* Exit: opacity only, fast, no delay — the transform snaps back after
       the panel is already invisible, so the copy is never seen moving. */
    transition: opacity 0.16s ease, transform 0s linear 0.16s;
    pointer-events: none;
}
.capability-pillar:hover .capability-pillar-panel,
.capability-pillar:focus-visible .capability-pillar-panel,
.capability-pillar.is-active .capability-pillar-panel {
    opacity: 1;
    transform: none;
    pointer-events: auto;
    transition: opacity 0.36s ease 0.14s,
                transform 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0.14s;
}

/* Sized off the pillars row, not the pillar, so the measure is identical at
   every point of the width transition. 34cqw is comfortably inside the
   expanded pillar (which lands near 44cqw) at every viewport it runs at. */
.capability-pillar-panel-inner {
    width: min(24rem, 34cqw);
    /* Draws the top edge of the content zone, so the space above it reads as
       held open rather than left over. */
    padding-top: 1.25rem;
    border-top: 1px solid var(--line-strong);
}

.capability-pillar-panel h3 {
    font-family: var(--font-sans);
    font-size: 1.2rem;
    font-weight: 600;
    letter-spacing: -0.018em;
    line-height: 1.2;
    color: var(--text-hi);
    margin: 0 0 0.625rem;
}
.capability-pillar-body {
    font-family: var(--font-sans);
    font-size: 0.9375rem;
    line-height: 1.65;
    color: var(--text-mid);
    margin: 0;
}
.capability-pillar-scope {
    font-family: var(--font-sans);
    font-size: 0.8125rem;
    line-height: 1.4;
    color: var(--text-low);
    margin: 0.875rem 0 0;
}

@media (max-width: 767.98px) {
    .capability-pillars { flex-direction: column; height: auto; }
    .capability-pillar {
        flex: none;
        height: 72px;
        transition: height 0.4s cubic-bezier(0.16, 1, 0.3, 1),
                    opacity 0.35s ease, border-color 0.35s ease, box-shadow 0.35s ease;
    }
    .capability-pillar.is-active { height: 300px; }

    .capability-pillar-mark {
        top: 1.375rem;
        left: 1.5rem;
        translate: 0 0;
        width: 22px;
        height: 22px;
        transition: color 0.35s ease;
    }

    .capability-pillar-label {
        top: 0;
        bottom: 0;
        left: 3.75rem;
        right: 1.5rem;
        width: auto;
        translate: 0 0;
        writing-mode: horizontal-tb;
        transform: none;
        justify-content: flex-start;
        font-size: 1rem;
    }

    /* Docked to the bottom on desktop, where the column is tall; on the
       accordion the box is short and flex-end would leave a dead gap under
       the mark, so the copy starts directly beneath it. */
    .capability-pillar-panel {
        justify-content: flex-start;
        padding: 3.25rem 1.5rem 1.25rem 3.75rem;
    }
    .capability-pillar-panel-inner { width: auto; }
}

/* ---- Solutions ----------------------------------------------------------- */
.solutions-section {
    padding: var(--s-section) 0;
    background: var(--ink-800);
    border-top: 1px solid var(--line);
}

.solutions-group + .solutions-group { margin-top: clamp(3rem, 6vw, 4.5rem); }

.solutions-group-title {
    font-size: var(--t-h3);
    font-weight: 600;
    color: var(--text-hi);
    margin: 0 0 clamp(1.5rem, 3vw, 2rem);
}

.solutions-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: clamp(0.75rem, 1.2vw, 1.125rem);
}

.solution-tile {
    background: var(--ink-600);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--e1);
    padding: clamp(1.5rem, 2vw, 1.875rem);
    transition: background-color 0.32s ease, border-color 0.32s ease,
                transform 0.32s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.32s ease;
}
.solution-tile:hover,
.solution-tile.touch-hover {
    transform: translateY(-3px);
    background: var(--ink-500);
    border-color: var(--sig-800);
    box-shadow: var(--e2);
}

.solution-icon {
    display: flex;
    width: 26px;
    height: 26px;
    margin-bottom: 1rem;
}
.solution-icon svg {
    width: 100%;
    height: 100%;
    fill: none;
    stroke: var(--sig-400);
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.solution-tile h4 {
    font-family: var(--font-sans);
    font-size: 1rem;
    font-weight: 600;
    letter-spacing: -0.015em;
    color: var(--text-hi);
    margin: 0 0 0.5rem;
}

.solution-tile p {
    font-family: var(--font-sans);
    font-size: 0.9375rem;
    line-height: 1.6;
    color: var(--text-mid);
    margin: 0;
}

@media (max-width: 1023.98px) {
    .solutions-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 559px) {
    .solutions-grid { grid-template-columns: 1fr; }
}


/* =========================================================================
   ABOUT PAGE — composition pass
   Five sections, five layout families: a split statement hero, a split
   narrative + unhoused pull quote, a rule-divided practice triptych, an
   icon-led value grid, and the shared closing band.

   The hero used to be a statement alone in a full-width shell, with
   mission/vision/approach as their own hairline-list section further down.
   That left the hero's right half empty on a page whose whole job is to
   say who this company is, and buried the three statements that answer it.
   They now sit in that empty half. Crucially they are NOT in a panel:
   Home and Services both fill the same slot with a bordered, filled box,
   and a third would read as the site having exactly one idea for a hero.
   See `.about-creed` — hairlines and type on the page's own atmosphere.

   Retires the HUD hero graphic's markup on this page, the Romanian copy,
   the material-icon tenet glyphs and the notched `.cyber-button`.
   ========================================================================= */

/* ---- Hero ----------------------------------------------------------------
   Same fill-the-viewport treatment as the services hero (see SERVICES PAGE
   above); kept as its own class rather than shared, so a future edit to
   one never silently reshapes the other. */
.about-hero {
    position: relative;
    background: var(--ink-700);
    border-bottom: 1px solid var(--line);
    padding: calc(clamp(2.5rem, 5vw, 4.5rem) + var(--header-h)) 0 clamp(2.5rem, 5vw, 4.5rem);
    min-height: calc(var(--vh, 1vh) * 100);
    min-height: 100dvh;
    /* svh - same reason as .contact-hero above. */
    min-height: 100svh;
    display: flex;
    align-items: center;
    overflow: hidden;
    isolation: isolate;
}

.about-hero .shell {
    position: relative;
    z-index: 1;
}

.about-hero-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 25rem);
    align-items: center;
    gap: clamp(2.5rem, 5vw, 5rem);
}

.about-hero-copy { max-width: 40rem; }

/* ---- Creed: the hero's right-hand column ---------------------------------
   Mission, vision and approach, which used to be their own section below
   the fold. Deliberately unhoused: Home and Services already fill this slot
   with a bordered, filled panel, and a third would say the site has one
   idea. Hairlines and type only, sitting on the hero's own atmosphere.

   Each rule carries a short cyan lead-in — the one accent in the column,
   and the thing that keeps three plain hairlines from reading as a table. */
.about-creed {
    position: relative;
    padding-left: clamp(0rem, 2vw, 2rem);
}

.about-creed-title {
    font-family: var(--font-sans);
    font-size: 1.0625rem;
    font-weight: 600;
    letter-spacing: -0.015em;
    line-height: 1.3;
    color: var(--text-hi);
    margin: 0 0 1.5rem;
}

.about-creed-list { margin: 0; }

.about-creed-entry {
    position: relative;
    padding-top: 1.25rem;
    margin-top: 1.25rem;
}

/* The rule is a pseudo-element rather than a border-top, because it changes
   colour partway across and a border cannot. (`border-image` with a gradient
   can, in principle, but it slices the image into nine regions and renders a
   1px strip as broken segments — a background does exactly what is wanted.) */
.about-creed-entry::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 1px;
    background: linear-gradient(to right,
        var(--sig-400) 0 2.5rem,
        var(--line-strong) 2.5rem);
}

.about-creed-entry:first-child {
    padding-top: 0;
    margin-top: 0;
}
.about-creed-entry:first-child::before { content: none; }

.about-creed-entry dt {
    font-family: var(--font-sans);
    font-size: 1rem;
    font-weight: 600;
    letter-spacing: -0.015em;
    color: var(--text-hi);
    margin-bottom: 0.375rem;
}

.about-creed-entry dd {
    margin: 0;
    font-family: var(--font-sans);
    font-size: 0.9375rem;
    line-height: 1.6;
    color: var(--text-mid);
}

@media (max-width: 1023.98px) {
    .about-hero-grid { grid-template-columns: minmax(0, 1fr); }
    .about-hero-copy { max-width: 46rem; }
    .about-creed {
        max-width: 40rem;
        padding-left: 0;
        margin-top: clamp(1rem, 3vw, 2rem);
    }
}

.about-hero-title {
    font-family: var(--font-sans);
    font-size: var(--t-display);
    font-weight: 700;
    letter-spacing: -0.035em;
    line-height: 1.12;
    color: var(--text-hi);
    /* Narrower than it was, because the statement no longer has the whole
       shell to itself — the creed column takes the right of the hero. */
    max-width: 17ch;
    margin: 0 0 2.25rem;
}

@media (max-width: 640px) {
    .about-hero-title { max-width: none; }
    .about-hero .hero-actions .btn { flex: 1 1 auto; }
}

/* ---- Narrative ------------------------------------------------------------
   Same split proportion as the home page's positioning section (1.12fr /
   0.88fr), new classes so the two pages' sections stay independently
   editable. */
.about-narrative-section {
    padding: var(--s-section) 0;
    background: var(--ink-800);
}

.about-narrative-grid {
    display: grid;
    grid-template-columns: 1.12fr 0.88fr;
    gap: clamp(3rem, 6vw, 6.5rem);
    /* The quote is much shorter than the narrative beside it. Top-aligned,
       all of that difference piled up as dead space under the quote; centred,
       it splits above and below and the quote reads as a deliberate aside. */
    align-items: center;
}

/* The site-wide section-head margin assumes a heading introducing a whole
   section below it. Here the heading sits directly on top of its own two
   paragraphs in the same column, so the full gap read as a break in the
   copy rather than a heading. */
.about-narrative-copy .section-head { margin-bottom: clamp(1.25rem, 2vw, 1.75rem); }

.about-narrative-copy p {
    font-family: var(--font-sans);
    font-size: var(--t-body);
    line-height: 1.75;
    color: var(--text-mid);
    max-width: 58ch;
    margin: 0 0 1.25rem;
}
.about-narrative-copy p:last-child { margin-bottom: 0; }

/* Unhoused. The card this used to sit in was the page's third bordered box,
   and it ended a long way short of the narrative beside it, leaving the
   right column half empty. Set large with a cyan rule above it instead, the
   quote fills its own column and stops competing with the value cards. */
.about-quote {
    margin: 0;
    padding: 1.75rem 0 0;
    border-top: 2px solid var(--sig-400);
}
.about-quote p {
    margin: 0;
    font-family: var(--font-sans);
    font-size: clamp(1.2rem, 1.7vw, 1.5rem);
    font-weight: 500;
    letter-spacing: -0.018em;
    line-height: 1.45;
    color: var(--text-hi);
    text-wrap: pretty;
}
.about-quote p::before  { content: '\201C'; }
.about-quote p::after   { content: '\201D'; }

@media (max-width: 1023.98px) {
    .about-narrative-grid { grid-template-columns: 1fr; gap: clamp(2.5rem, 8vw, 3.5rem); }
}

/* ---- Practice -------------------------------------------------------------
   Who is in the room. Three columns divided by vertical hairlines — no
   container, no icon, no index — so it carries the opposite weight to the
   boxed, icon-led value cards directly below it and the two three-up
   sections never read as the same shape twice. */
.practice-section {
    position: relative;
    padding: var(--s-section) 0;
    background: var(--ink-700);
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
}
.practice-section > .shell { position: relative; z-index: 1; }

.practice-section::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background:
        radial-gradient(64vmax 40vmax at 86% -14%,
            rgba(43, 31, 107, 0.42) 0%, rgba(43, 31, 107, 0) 72%),
        radial-gradient(54vmax 38vmax at 4% 108%,
            rgba(14, 74, 92, 0.30) 0%, rgba(14, 74, 92, 0) 72%);
}

.practice-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: clamp(2rem, 4vw, 3.5rem);
}

.practice-column {
    padding-left: clamp(1.5rem, 3vw, 2.75rem);
    border-left: 1px solid var(--line-strong);
}
/* The first column sits on the shell's own left edge, so a rule there would
   read as a stray margin rather than a divider. */
.practice-column:first-child {
    padding-left: 0;
    border-left: none;
}

.practice-column h3 {
    font-family: var(--font-sans);
    font-size: clamp(1.15rem, 1.5vw, 1.375rem);
    font-weight: 600;
    letter-spacing: -0.02em;
    line-height: 1.2;
    color: var(--text-hi);
    margin: 0 0 0.875rem;
}

.practice-column p {
    font-family: var(--font-sans);
    font-size: var(--t-body);
    line-height: 1.7;
    color: var(--text-mid);
    margin: 0;
}

@media (max-width: 767.98px) {
    .practice-grid { grid-template-columns: minmax(0, 1fr); gap: 0; }
    .practice-column {
        padding: clamp(1.5rem, 4vw, 2rem) 0 0;
        margin-top: clamp(1.5rem, 4vw, 2rem);
        border-left: none;
        border-top: 1px solid var(--line-strong);
    }
    .practice-column:first-child {
        padding-top: 0;
        margin-top: 0;
        border-top: none;
    }
}

/* ---- Values ---------------------------------------------------------------
   Same card language as the services page's `.solution-tile` (ink-600
   surface, hover lift, sig-400 outline icon) — the system's one card
   component, reused rather than reinvented a third time. */
.values-section {
    padding: var(--s-section) 0;
    background: var(--ink-800);
}

.values-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: clamp(0.75rem, 1.2vw, 1.125rem);
}

.value-card {
    background: var(--ink-600);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--e1);
    padding: clamp(1.75rem, 2.4vw, 2.25rem);
    transition: background-color 0.32s ease, border-color 0.32s ease,
                transform 0.32s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.32s ease;
}
.value-card:hover,
.value-card.touch-hover {
    transform: translateY(-3px);
    background: var(--ink-500);
    border-color: var(--sig-800);
    box-shadow: var(--e2);
}

.value-icon {
    display: flex;
    width: 28px;
    height: 28px;
    margin-bottom: 1.25rem;
}
.value-icon svg {
    width: 100%;
    height: 100%;
    fill: none;
    stroke: var(--sig-400);
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.value-card h3 {
    font-size: 1.05rem;
    font-weight: 600;
    letter-spacing: -0.015em;
    color: var(--text-hi);
    margin: 0 0 0.625rem;
}

.value-card p {
    font-family: var(--font-sans);
    font-size: 0.9375rem;
    line-height: 1.65;
    color: var(--text-mid);
    margin: 0;
}

@media (max-width: 767.98px) {
    .values-grid { grid-template-columns: 1fr; }
}


/* =========================================================================
   SHARED UTILITY RETINTS
   Every page template has now had its own composition pass (home, services,
   about, contact, privacy, terms). What is left here are classes with no
   template of their own.

   The Contact Form 7 retints that used to sit here have been deleted: they
   were a patch layered over the old HUD form styles, and now that the CF7
   block itself is written to the system (see CONTACT FORM 7 above) a second,
   later-cascading copy could only fight it — its `--ink-700` input fill,
   for one, is the exact colour of the panel the form now sits in.
   ========================================================================= */

/* Interior-page surfaces retinted onto the ladder. Same three shadows, one
   hairline, no neon rims and no 3-4px coloured side bars. */
.faq-item {
    background: var(--ink-600);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--e1);
}

.faq-item.active {
    background: var(--ink-500);
    border-color: var(--sig-800);
    box-shadow: var(--e2);
}

/* Cookie banner */
.cyber-cookie-banner {
    background: var(--ink-700);
    border: 1px solid var(--line-strong);
    border-left: 1px solid var(--line-strong);
    border-radius: var(--r-lg);
    box-shadow: var(--e3);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}
.cookie-title {
    margin: 0 0 0.375rem;
    font-family: var(--font-sans);
    font-size: 1rem;
    font-weight: 600;
    letter-spacing: -0.015em;
    color: var(--text-hi);
}
.cookie-copy {
    margin: 0;
    font-family: var(--font-sans);
    font-size: 0.875rem;
    line-height: 1.6;
    color: var(--text-mid);
}
.cookie-copy a { color: var(--sig-100); }
.cookie-actions .btn { min-height: 44px; padding: 0 1.25em; font-size: 0.875rem; }

/* Loading overlay: the mark drawing itself in, not a terminal splash. The
   wordmark and hairline this rule used to restyle are gone — the shield says
   the same thing without asking anyone to read during a page load. */
.cyber-loader-overlay { background-color: var(--ink-800); }
.loader-status { display: none; }

/* Keyboard focus */
a:focus-visible,
button:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible,
[tabindex]:focus-visible,
.btn:focus-visible,
.audience-col:focus-visible,
.faq-question:focus-visible,
.social-icon:focus-visible,
.sol-btn:focus-visible,
.nav-toggle:focus-visible {
    outline: 2px solid var(--sig-400) !important;
    outline-offset: 3px !important;
}
/* The bar and its sheet are both glass now, so they take the site-wide
   --sig-400 ring like every other dark surface. There is no paper surface
   left on the site to need the darker --sig-600 stop. */

/* Reveal kill switch, covering the retired .reveal-NN names too. */
body.perf-low .reveal,
body.perf-low [class*="reveal-"],
body.reduced-motion .reveal,
body.reduced-motion [class*="reveal-"] {
    opacity: 1 !important;
    transform: none !important;
}

@media (prefers-reduced-motion: reduce) {
    .reveal,
    [class*="reveal-"] { opacity: 1; transform: none; }
}


/* =========================================================================
   TOUCH AND PHONE TIER
   Two separate concerns, deliberately keyed off two different things.

   `pointer: coarse` is about the INPUT, not the screen: a touchscreen laptop
   and a 1024px tablet both need a finger-sized target and neither is narrow.
   Sizing these off a width query would have missed both. Desktop pointer
   sizing is untouched.

   The width block below it is about the SCREEN: components whose padding and
   type were authored against a 1200px shell and whose clamps bottom out at a
   desktop minimum.
   ========================================================================= */

@media (pointer: coarse) {
    /* 44px on everything a finger has to hit. */
    .cyber-lang-switcher a { min-height: 44px; }
    .custom-logo-link,
    .site-branding > a:first-child {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-width: 44px;
        min-height: 44px;
        margin-left: -8px;
    }
    .faq-question { min-height: 44px; }
}

/* ---- Phone heroes: the statement gets a screen of its own ----------------
   Every hero on the site is a two-part object: a statement on the left and a
   second thing beside it — the engagement stack, the phase deck, the creed,
   the contact form. On a wide screen those sit side by side and the eye reads
   the statement first because it is left and larger.

   Stacked on a phone that ordering collapses. Each of these sections already
   said `min-height: 100dvh; align-items: center`, but centring the WHOLE
   stack centres the seam between the two parts, not the statement — so the
   title ended up jammed against the top with its partner crowding underneath,
   and nothing had priority.

   Giving the copy block its own screen and centring inside THAT restores the
   order the desktop layout gets from left-to-right: statement first, at the
   height the eye already rests, then the second part on the next beat.

   Centred means centred. An earlier version of this rule held a few rem back
   at the bottom so a sliver of the next part would show through as a "keep
   scrolling" cue. On a real phone that read as a mistake rather than an
   invitation: the statement sat visibly high on the screen, and the sliver
   below looked like a section that had failed to clear the fold. The cue is
   not worth the cost of every hero on the site looking slightly wrong.

   Getting exact centring needs the section's own vertical padding in the sum,
   and each hero used to declare a different one — which is precisely why the
   first attempt drifted by a different amount on every page. So the four
   heroes share one --hero-pad on phones, and the copy block takes the screen
   minus both paddings:

       copy top     = header + pad
       copy height  = screen - 2*pad
       copy centre  = header + pad + (screen - 2*pad)/2  =  header + screen/2

   which is the centre of the space under the header, exactly, on every page
   and at every phone height. The padding cancels out of the result but still
   protects the type from the header and the section edge when a statement is
   long enough to fill the box.

   With the copy filling the screen, the part below it now starts just past
   the fold rather than peeking above it. */
@media (max-width: 767.98px) {
    /* padding-TOP, not padding-block. Each of these five now carries the glass
       bar's own height as well as the shared hero padding, because the bar
       sits ON the hero rather than above it. A `padding-block` shorthand here
       would drop the header term and slide every statement on the site up
       under the bar — which is also why the sum below still reads
       `- var(--header-h)`: the copy block's screen is still the screen minus
       the bar, the bar's height has just moved from a margin to a padding. */
    .cyber-hero-canvas,
    .services-hero-stage,
    .about-hero,
    .contact-hero,
    .legal-hero {
        padding-top: calc(var(--header-h) + var(--hero-pad));
        padding-bottom: var(--hero-pad);
    }

    /* svh wins here, and it is the fix for the hero flickering under a thumb.
       `dvh` tracks the address bar, so on a phone SCROLLING IS A RESIZE: the
       bar collapses on the way down and returns on the way up, and this
       min-height changed with it. That grew `.cyber-hero-canvas`, which the
       hero's ResizeObserver answers by calling `app.renderer.resize()` and
       re-running `initDots()` — reallocating the WebGL drawing buffer and
       rebuilding the whole lattice, mid-scroll, every time the reader changed
       direction. The blank frame between teardown and repaint is the flicker.

       `svh` is the viewport with the browser chrome shown: one number for the
       whole session. The element then never changes size, so the observer's
       own equality guard catches every chrome-driven resize and the canvas is
       never reallocated at all. Fixing the geometry is the real repair;
       damping the JS would only have hidden it.

       Declared three deep on purpose — the JS `--vh` fallback, then dvh, then
       svh — so each engine takes the best line it understands, the same
       ladder `.phase-pin` already uses for the same reason. */
    .hero-copy,
    .services-hero-copy,
    .about-hero-copy,
    .contact-hero-copy {
        display: flex;
        flex-direction: column;
        justify-content: center;
        min-height: calc(var(--vh, 1vh) * 100 - var(--header-h) - 2 * var(--hero-pad));
        min-height: calc(100dvh - var(--header-h) - 2 * var(--hero-pad));
        min-height: calc(100svh - var(--header-h) - 2 * var(--hero-pad));
    }

    /* The legal hero is only a title and a date — no second part to out-rank,
       so the section's own `align-items: center` already centres it. With the
       shared padding above it now lands on the same axis as the other four. */
    .legal-hero {
        min-height: calc(var(--vh, 1vh) * 100);
        min-height: 100dvh;
        min-height: 100svh;
    }
}

@media (max-width: 767.98px) {
    /* ---- Type floor -----------------------------------------------------
       12px is below the readable floor for anything that is not a tag. These
       four were the only strings under it. */
    .footer-col-title   { font-size: 0.8125rem; letter-spacing: 0.09em; }
    .credibility-label  { font-size: 0.8125rem; }
    .contact-lines dt   { font-size: 0.8125rem; }
    .phase-tags li      { font-size: 0.8125rem; }

    /* ---- Card density ---------------------------------------------------
       Same surfaces, same borders, same radii. Only the air a 1200px shell
       could afford and a 390px one cannot. */
    .audience-col   { padding: 1.75rem 1.25rem; gap: 0.625rem; }
    .value-card,
    .solution-tile  { padding: 1.375rem 1.25rem; }
    .cyber-node     { padding: 1.25rem 1.25rem; }
    .capability     { padding: 1.375rem 1.25rem; }

    /* The credibility strip's label was wrapping to three lines against a
       9ch measure and colliding with the marquee beside it. Above it now,
       full width, which is also the reading order it should have had. */
    .credibility .shell {
        flex-direction: column;
        align-items: stretch;
        gap: 0.5rem;
    }
    .credibility-label { max-width: none; }

    /* Section heads carry a desktop margin that reads as a gap on a phone. */
    .section-head { margin-bottom: clamp(1.5rem, 5vw, 2rem); }

    /* The timeline's spine gutter was tuned for a wider row. */
    .timeline-row       { padding-left: 30px !important; }
    .timeline-connector { width: 26px !important; }

    /* The closing band is the one section that does not read --s-section; its
       own clamp bottoms out at 4.5rem, which made it the tallest band on the
       page on the smallest screen. */
    .cta-band { padding: clamp(3rem, 11vw, 4.5rem) 0; }
}