﻿/* ============================================================
   PROTEK × mymind MOTION LAYER
   ------------------------------------------------------------
   Ports the motion language of mymind.com onto Protek.
   Loaded AFTER styles.css so it can refine existing motion.

   SCOPE RULE:
   Every rule here is scoped to [data-mm]. The #tech and
   #sectors sections deliberately DO NOT carry [data-mm],
   so their existing animations stay byte-for-byte untouched.
   ============================================================ */

/* ---------- 1. Easing scale (mirrors Semplice's token set) ---------- */
:root {
    --ease-in-sine: cubic-bezier(0.47, 0, 0.745, 0.715);
    --ease-out-sine: cubic-bezier(0.39, 0.575, 0.565, 1);
    --ease-in-out-sine: cubic-bezier(0.445, 0.05, 0.55, 0.95);
    --ease-in-quad: cubic-bezier(0.55, 0.085, 0.68, 0.53);
    --ease-out-quad: cubic-bezier(0.25, 0.46, 0.45, 0.94);
    --ease-in-out-quad: cubic-bezier(0.455, 0.03, 0.515, 0.955);
    --ease-in-cubic: cubic-bezier(0.55, 0.055, 0.675, 0.19);
    --ease-out-cubic: cubic-bezier(0.215, 0.61, 0.355, 1);
    --ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1);
    --ease-in-quart: cubic-bezier(0.895, 0.03, 0.685, 0.22);
    --ease-out-quart: cubic-bezier(0.165, 0.84, 0.44, 1);
    --ease-in-out-quart: cubic-bezier(0.77, 0, 0.175, 1);
    --ease-in-quint: cubic-bezier(0.755, 0.05, 0.855, 0.06);
    --ease-out-quint: cubic-bezier(0.23, 1, 0.32, 1);
    --ease-in-out-quint: cubic-bezier(0.86, 0, 0.07, 1);
    --ease-in-expo: cubic-bezier(0.95, 0.05, 0.795, 0.035);
    --ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1);
    --ease-in-out-expo: cubic-bezier(1, 0, 0, 1);
    --ease-in-circ: cubic-bezier(0.6, 0.04, 0.98, 0.335);
    --ease-out-circ: cubic-bezier(0.075, 0.82, 0.165, 1);
    --ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.15, 0.86);
    --ease-in-back: cubic-bezier(0.6, -0.28, 0.735, 0.045);
    --ease-out-back: cubic-bezier(0.175, 0.885, 0.32, 1.275);
    --ease-in-out-back: cubic-bezier(0.68, -0.55, 0.265, 1.55);

    /* mymind's measured durations */
    --mm-reveal: 0.9s;      /* scroll reveal            */
    --mm-hover: 0.4s;       /* card hover (Power3.inOut) */
    --mm-swap: 0.7s;        /* text / image swap        */
    --mm-hero: 2s;          /* hero entrance            */
    --mm-lift-scale: 1.04;  /* mymind hovers 4%, no more */
}

/* ---------- 2. First-paint fade (gsap.to(content, .6, Sine.easeOut)) ----------
   #tech and #sectors are excluded so those two sections keep their
   existing behaviour exactly. #navbar is excluded too: it already
   owns a multi-property `transition` for its scrolled pill state,
   and redeclaring transition here would wipe it out.            */
body.mm-boot .hero,
body.mm-boot .tech-ticker-section,
body.mm-boot .section:not(#tech):not(#sectors),
body.mm-boot .footer {
    opacity: 0;
}
.hero,
.tech-ticker-section,
.section:not(#tech):not(#sectors),
.footer {
    opacity: 1;
    transition: opacity 0.6s var(--ease-out-sine);
}

/* Navbar slides down on load. Uses the inner container because
   .navbar.scrolled already owns `transform` for its pill state. */
.nav-container {
    transition: transform 0.9s var(--ease-out-expo);
}
body.mm-boot .nav-container {
    transform: translateY(-100%);
}

/* ---------- 3. Reveal, retuned to mymind restraint ----------
   mymind never rotates or scales on entry. It drifts 35-90px
   and fades, on a hard ease-out. No 3D, no overshoot.        */
[data-mm] .reveal {
    opacity: 0;
    transform: translate3d(0, 42px, 0);
    transition: opacity var(--mm-reveal) var(--ease-out-expo),
                transform var(--mm-reveal) var(--ease-out-expo);
    will-change: opacity, transform;
}
[data-mm] .reveal.visible {
    opacity: 1;
    transform: translate3d(0, 0, 0);
}
[data-mm] .reveal-left,
[data-mm] .reveal-right {
    opacity: 0;
    transition: opacity 1s var(--ease-out-expo),
                transform 1s var(--ease-out-expo);
    will-change: opacity, transform;
}
[data-mm] .reveal-left { transform: translate3d(-46px, 0, 0); }
[data-mm] .reveal-right { transform: translate3d(46px, 0, 0); }
[data-mm] .reveal-left.visible,
[data-mm] .reveal-right.visible {
    opacity: 1;
    transform: translate3d(0, 0, 0);
}
[data-mm] .reveal-scale {
    opacity: 0;
    transform: scale(0.96);
    transition: opacity var(--mm-reveal) var(--ease-out-expo),
                transform var(--mm-reveal) var(--ease-out-expo);
}
[data-mm] .reveal-scale.visible {
    opacity: 1;
    transform: scale(1);
}

/* Drop will-change once the element has landed */
[data-mm] .reveal.mm-settled,
[data-mm] .reveal-left.mm-settled,
[data-mm] .reveal-right.mm-settled,
[data-mm] .reveal-scale.mm-settled {
    will-change: auto;
}

/* 3b. Independent transform properties.
   A card both reveals (0.9s drift) and lifts on hover (0.4s scale).
   On the `transform` shorthand those two fight — whichever rule wins
   specificity owns the whole property. Splitting them onto `translate`
   and `scale` lets both run at their own duration and compose.
   The block above stays as the fallback for older engines.        */
@supports (translate: 0 1px) {
    [data-mm] .reveal {
        transform: none;
        translate: 0 42px;
        transition: opacity var(--mm-reveal) var(--ease-out-expo),
                    translate var(--mm-reveal) var(--ease-out-expo);
    }
    [data-mm] .reveal.visible {
        translate: 0 0;
    }
    [data-mm] .reveal-left {
        transform: none;
        translate: -46px 0;
    }
    [data-mm] .reveal-right {
        transform: none;
        translate: 46px 0;
    }
    [data-mm] .reveal-left,
    [data-mm] .reveal-right {
        transition: opacity 1s var(--ease-out-expo),
                    translate 1s var(--ease-out-expo);
    }
    [data-mm] .reveal-left.visible,
    [data-mm] .reveal-right.visible {
        translate: 0 0;
    }
    [data-mm] .reveal-scale {
        transform: none;
        scale: 0.96;
        transition: opacity var(--mm-reveal) var(--ease-out-expo),
                    scale var(--mm-reveal) var(--ease-out-expo);
    }
    [data-mm] .reveal-scale.visible {
        scale: 1;
    }
    @media (max-width: 767px) {
        [data-mm] .reveal { translate: 0 28px; }
        [data-mm] .reveal-left { translate: -24px 0; }
        [data-mm] .reveal-right { translate: 24px 0; }
    }
}

/* ---------- 4. Masked line rise for headings ----------
   The mymind text-swap trick: text lives in an overflow-hidden
   box and rises in from below its own baseline.              */
[data-mm] .mm-clip {
    overflow: hidden;
    display: block;
    /* Display type here runs a line-height below 1, so glyph ink
       overflows the line box. The padding gives the mask room for
       descenders; the matching negative margin cancels it so the
       surrounding layout does not shift by a single pixel. */
    padding-bottom: 0.26em;
    margin-bottom: -0.26em;
}
[data-mm] .mm-clip-inner {
    display: block;
    transform: translate3d(0, 108%, 0);
    transition: transform 1.05s var(--ease-out-expo);
    transition-delay: var(--mm-delay, 0s);
    will-change: transform;
}
[data-mm] .mm-clip.mm-in .mm-clip-inner {
    transform: translate3d(0, 0, 0);
    will-change: auto;
}
/* Tag pills rise + fade, they are small enough that pure clip reads odd */
[data-mm] .section-tag.mm-tag {
    opacity: 0;
    transform: translate3d(0, 16px, 0);
    transition: opacity 0.7s var(--ease-out-expo),
                transform 0.7s var(--ease-out-expo);
}
[data-mm] .section-tag.mm-tag.mm-in {
    opacity: 1;
    transform: translate3d(0, 0, 0);
}
/* Description copy trails the headline */
[data-mm] .section-desc.mm-sub {
    opacity: 0;
    transform: translate3d(0, 20px, 0);
    transition: opacity 0.9s var(--ease-out-expo),
                transform 0.9s var(--ease-out-expo);
    transition-delay: 0.22s;
}
[data-mm] .section-desc.mm-sub.mm-in {
    opacity: 1;
    transform: translate3d(0, 0, 0);
}

/* ---------- 5. Parallax carriers ----------
   Transform is written by js/motion.js each frame with a
   lerp, reproducing ScrollTrigger's `scrub: 2` catch-up lag.
   No CSS transition here or the two would fight.            */
[data-mm] [data-mm-parallax] {
    will-change: transform;
}

/* ---------- 6. Hover: 4% scale, reversible, Power3.easeInOut ----------
   mymind's entire hover vocabulary is a 1.04 scale over 0.4s.
   Because it is a CSS transition it reverses along its own
   curve when the pointer leaves mid-animation.               */
@media (hover: hover) and (pointer: fine) {
    [data-mm] .mm-lift {
        transition: transform var(--mm-hover) var(--ease-in-out-cubic),
                    box-shadow var(--mm-hover) var(--ease-in-out-cubic),
                    border-color 0.25s ease,
                    background-color 0.25s ease;
    }
    [data-mm] .mm-lift:hover {
        transform: scale(var(--mm-lift-scale));
        z-index: 2;
    }
    @supports (scale: 1) {
        [data-mm] .mm-lift {
            transition: scale var(--mm-hover) var(--ease-in-out-cubic),
                        box-shadow var(--mm-hover) var(--ease-in-out-cubic),
                        border-color 0.25s ease,
                        background-color 0.25s ease;
        }
        [data-mm] .mm-lift:hover {
            transform: none;
            scale: var(--mm-lift-scale);
        }
    }
    /* Grid neighbours recede slightly so the hovered card owns focus */
    [data-mm] .mm-dim-group:hover > .mm-lift:not(:hover) {
        opacity: 0.72;
        transition: opacity var(--mm-hover) var(--ease-in-out-cubic);
    }
}

/* ---------- 7. Image reveal on hover: 0.7s ease-out-expo ----------
   The single most-used declaration on mymind (17 instances).  */
[data-mm] .mm-zoom {
    overflow: hidden;
}
[data-mm] .mm-zoom img,
[data-mm] img.mm-zoom-img {
    transition: transform var(--mm-swap) var(--ease-out-expo),
                filter var(--mm-swap) var(--ease-out-expo);
}
@media (hover: hover) and (pointer: fine) {
    [data-mm] .mm-zoom:hover img,
    [data-mm] img.mm-zoom-img:hover {
        transform: scale(1.06);
    }
}

/* Bottom gradient mask so media dissolves into the page
   instead of ending on a hard edge (mymind hero video).

   Currently unused. The hero video was the only thing carrying this class
   and it was removed: fading the bottom of real footage let the building
   illustration behind the hero show through the subjects' legs. Kept as a
   utility because it is still the right treatment for decorative media,
   but nothing references it today. */
[data-mm] .mm-mask-bottom {
    -webkit-mask-image: linear-gradient(180deg, #fff 78%, transparent 100%);
    mask-image: linear-gradient(180deg, #fff 78%, transparent 100%);
}

/* ---------- 8. Button effects (ported from Semplice) ---------- */

/* 8a. slide-top-to-bottom: a ::after panel grows 0 -> 100% height */
[data-mm] .mm-btn-fill {
    position: relative;
    overflow: hidden;
    isolation: isolate;
}
[data-mm] .mm-btn-fill::after {
    content: "";
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 0;
    background: var(--color-midnight-ink);
    transition: height var(--mm-swap) var(--ease-out-expo);
    z-index: -1;
}
[data-mm] .mm-btn-fill:hover::after {
    height: 100%;
}
[data-mm] .mm-btn-fill:hover {
    color: #ffffff;
}

/* 8b. colorfade: mymind's one accent moment, #ffe152 */
[data-mm] .mm-btn-colorfade {
    transition: background-color var(--mm-hover) var(--ease-out-expo),
                color var(--mm-hover) var(--ease-out-expo),
                border-color var(--mm-hover) var(--ease-out-expo),
                box-shadow var(--mm-hover) var(--ease-out-expo),
                transform var(--mm-hover) var(--ease-out-expo);
}
[data-mm] .mm-btn-colorfade:hover {
    background-color: var(--gold-light);
    border-color: var(--gold-light);
    color: var(--color-carbon);
}

/* 8c. icon-move: the arrow nudges via padding, not transform */
[data-mm] .mm-icon-move i,
[data-mm] .mm-icon-move svg {
    transition: padding var(--mm-hover) var(--ease-out-expo),
                transform var(--mm-hover) var(--ease-out-expo);
}
[data-mm] .mm-icon-move:hover i,
[data-mm] .mm-icon-move:hover svg {
    transform: translateX(6px);
}

/* 8d. top-out-bottom-in: the label leaves upward, its twin
       arrives from below. Both are pseudo elements.          */
[data-mm] .mm-text-swap {
    position: relative;
    overflow: hidden;
}
[data-mm] .mm-text-swap > .mm-swap-a,
[data-mm] .mm-text-swap > .mm-swap-b {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    transition: transform var(--mm-swap) var(--ease-out-expo),
                opacity var(--mm-swap) var(--ease-out-expo);
}
[data-mm] .mm-text-swap > .mm-swap-b {
    position: absolute;
    inset: 0;
    justify-content: center;
    transform: translateY(100%);
    opacity: 0;
}
[data-mm] .mm-text-swap:hover > .mm-swap-a {
    transform: translateY(-100%);
    opacity: 0;
}
[data-mm] .mm-text-swap:hover > .mm-swap-b {
    transform: translateY(0);
    opacity: 1;
}

/* ---------- 9. Overshoot pop (mymind's play button) ----------
   Settles at 1.15, not 1. The exit dips under to 0.95.
   The exit animation is gated behind .mm-popped, which JS adds on
   the first real hover — otherwise every icon would play its
   retract animation once on page load.                        */
@keyframes mmPop {
    0%   { transform: scale(1); }
    60%  { transform: scale(1.2); }
    100% { transform: scale(1.15); }
}
@keyframes mmPopOut {
    0%   { transform: scale(1.15); }
    60%  { transform: scale(0.95); }
    100% { transform: scale(1); }
}
@media (hover: hover) and (pointer: fine) {
    [data-mm] .mm-pop-target.mm-popped .mm-pop {
        animation: mmPopOut 0.2s ease-in-out forwards;
    }
    [data-mm] .mm-pop-target:hover .mm-pop {
        animation: mmPop 0.2s ease-in-out forwards;
    }
}

/* ---------- 10. Hero entrance: 2s, scale 0.8 -> 1 ----------
   mymind's single slowest move. Oversized + center-anchored so
   the scale-up can never expose an edge.                      */
[data-mm] .mm-hero-media {
    opacity: 0;
    transform: scale(0.8);
    transition: opacity var(--mm-hero) cubic-bezier(0.25, 0.89, 0.58, 1),
                transform var(--mm-hero) cubic-bezier(0.25, 0.89, 0.58, 1);
    will-change: opacity, transform;
}
[data-mm] .mm-hero-media.mm-in {
    opacity: 1;
    transform: scale(1);
    will-change: auto;
}

/* Hero copy: staggered rise, ease-out-expo instead of the
   stock fade-in-up keyframes.                                */
[data-mm] .mm-hero-line {
    opacity: 0;
    transform: translate3d(0, 34px, 0);
    transition: opacity 1.1s var(--ease-out-expo),
                transform 1.1s var(--ease-out-expo);
    transition-delay: var(--mm-delay, 0s);
}
[data-mm] .mm-hero-line.mm-in {
    opacity: 1;
    transform: translate3d(0, 0, 0);
}

/* ---------- 11. Rotating word (mymind's cycling nouns) ----------
   Old word exits upward, next word arrives from below, on the
   same 0.7s ease-out-expo as the button text swap.            */
.mm-rotator-line {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin-top: 22px;
    font-size: clamp(15px, 1.5vw, 18px);
    font-weight: 500;
    color: var(--text-secondary);
    letter-spacing: 0.2px;
}
.mm-rotator {
    position: relative;
    display: inline-block;
    overflow: hidden;
    vertical-align: middle;
    /* Box height must equal the word's line box exactly, or the
       overflow mask shaves the top and bottom off the glyphs. */
    height: 1.6em;
    line-height: 1.6em;
    transition: width 0.7s var(--ease-out-expo);
    will-change: width;
}
.mm-rotator-word {
    display: block;
    height: 1.6em;
    line-height: 1.6em;
    white-space: nowrap;
    font-weight: 700;
    /* Not --gold. #f69251 on the near-white hero measures 2.3:1, and the
       clamp takes this down to 15px on phones — under the 18.66px
       threshold for the large-text exemption, so it needs the full
       4.5:1. #b35a1f passed on desktop but only reached 4.4:1 at the
       mobile size. This measures 5.0:1 at both and stays warm. */
    color: #2f5476;
    transition: transform 0.7s var(--ease-out-expo),
                opacity 0.7s var(--ease-out-expo);
}
.mm-rotator-word.is-out {
    transform: translateY(-110%);
    opacity: 0;
}
.mm-rotator-word.is-next {
    position: absolute;
    inset: 0;
    transform: translateY(110%);
    opacity: 0;
}
.mm-rotator-word.is-next.is-in {
    transform: translateY(0);
    opacity: 1;
}

/* ---------- 12. Marquee CTA (mymind repeats its label 3x) ---------- */
.mm-marquee {
    position: relative;
    display: block;
    overflow: hidden;
    margin: 34px auto 0;
    max-width: 720px;
    padding: 18px 0;
    border-top: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
    text-decoration: none;
    -webkit-mask-image: linear-gradient(90deg, transparent, #fff 12%, #fff 88%, transparent);
    mask-image: linear-gradient(90deg, transparent, #fff 12%, #fff 88%, transparent);
}
.mm-marquee-track {
    display: flex;
    width: max-content;
    animation: mmMarquee 18s linear infinite;
}
.mm-marquee:hover .mm-marquee-track {
    animation-play-state: paused;
}
.mm-marquee-item {
    display: inline-flex;
    align-items: center;
    gap: 14px;
    padding-right: 40px;
    font-family: 'Barlow Condensed', 'Oswald', sans-serif;
    font-size: clamp(22px, 3vw, 34px);
    font-weight: 800;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--color-carbon);
    white-space: nowrap;
    transition: color var(--mm-hover) var(--ease-out-expo);
}
.mm-marquee:hover .mm-marquee-item {
    color: var(--gold);
}
.mm-marquee-item i {
    font-size: 0.62em;
    color: var(--gold);
}
@keyframes mmMarquee {
    from { transform: translate3d(0, 0, 0); }
    to   { transform: translate3d(-50%, 0, 0); }
}

/* ---------- 13. Timed pill carousel (mymind's 5s progress bar) ---------- */
[data-mm] .board-pill {
    position: relative;
    overflow: hidden;
}
[data-mm] .board-pill .mm-progress {
    position: absolute;
    left: 0;
    bottom: 0;
    height: 2px;
    width: 0;
    background: rgba(255, 255, 255, 0.85);
    pointer-events: none;
}
[data-mm] .board-pill:not(.active) .mm-progress {
    width: 0 !important;
    transition: none !important;
}
[data-mm] .mymind-masonry-grid .mymind-card.mm-filtered-out {
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.32s var(--ease-out-expo);
}
@supports (scale: 1) {
    [data-mm] .mymind-masonry-grid .mymind-card.mm-filtered-out {
        scale: 0.96;
        transition: opacity 0.32s var(--ease-out-expo),
                    scale 0.32s var(--ease-out-expo);
    }
}

/* ---------- 14. Section rule sweeps ----------
   clip-path rather than scaleX. These rules are 3px tall, and
   scaleX(0) collapses the box to zero area — an IntersectionObserver
   with a non-zero threshold cannot reliably report a zero-area
   target, so the sweep could never be triggered. clip-path leaves
   the layout box intact.                                        */
[data-mm] .mm-rule {
    clip-path: inset(0 100% 0 0);
    transition: clip-path 1.2s var(--ease-out-expo);
    transition-delay: 0.18s;
}
[data-mm] .training-line.mm-rule {
    clip-path: inset(0 50% 0 50%);
}
[data-mm] .mm-rule.mm-in {
    clip-path: inset(0 0 0 0);
}

/* ---------- 15. Reduced-motion kill switch ----------
   mymind ships none of this. Protek does.                    */
@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }

    [data-mm] .reveal,
    [data-mm] .reveal-left,
    [data-mm] .reveal-right,
    [data-mm] .reveal-scale,
    [data-mm] .mm-hero-line,
    [data-mm] .mm-hero-media,
    [data-mm] .section-tag.mm-tag,
    [data-mm] .section-desc.mm-sub {
        opacity: 1 !important;
        transform: none !important;
        translate: none !important;
        scale: none !important;
        transition: none !important;
    }
    [data-mm] .mm-lift:hover {
        scale: none !important;
    }
    [data-mm] .mymind-masonry-grid .mymind-card.mm-filtered-out {
        scale: none !important;
    }
    [data-mm] .mm-clip-inner {
        transform: none !important;
        transition: none !important;
    }
    [data-mm] .mm-rule {
        clip-path: none !important;
        transition: none !important;
    }
    [data-mm] [data-mm-parallax] {
        transform: none !important;
        will-change: auto;
    }
    [data-mm] .mm-lift:hover,
    [data-mm] .mm-zoom:hover img {
        transform: none !important;
    }
    .mm-marquee-track { animation: none !important; }
    .mm-rotator-word,
    .mm-rotator { transition: none !important; }
    [data-mm] .mm-pop-target:hover .mm-pop,
    [data-mm] .mm-pop-target.mm-popped .mm-pop { animation: none !important; }
    body.mm-boot .nav-container { transform: none !important; }
    body.mm-boot .hero,
    body.mm-boot .tech-ticker-section,
    body.mm-boot .section,
    body.mm-boot .footer { opacity: 1 !important; }
}

/* ---------- 16. Mobile trims ---------- */
@media (max-width: 767px) {
    [data-mm] [data-mm-parallax] { transform: none !important; }
    .mm-marquee-track { animation-duration: 12s; }
}
/* Shorter travel on small screens. Only for engines without
   independent transform properties — the @supports block in
   section 3b already carries its own mobile override. */
@supports not (translate: 0 1px) {
    @media (max-width: 767px) {
        [data-mm] .reveal { transform: translate3d(0, 28px, 0); }
        [data-mm] .reveal-left { transform: translate3d(-24px, 0, 0); }
        [data-mm] .reveal-right { transform: translate3d(24px, 0, 0); }
    }
}

/* ============================================================
   17. ATMOSPHERE — the section-to-section handoff
   ------------------------------------------------------------
   This is what makes mymind read as one continuous surface
   rather than a stack of boxes, and it is not an animation.

   Two mechanics, both measured off the live site:

   a) A MONOTONIC COLOUR ARC. mymind never alternates. Its
      sections walk forward and stay put:
        #fff -> #fff -> #f6f4f2 -> #ff5924 -> #fff -> #fff1f1
        -> #fff1f1 -> #fff1f1 ...
      Adjacent sections repeatedly share a value, so most joins
      have no visible edge at all. Protek was ping-ponging
      #f7f7f7 / #ffffff on every single section, which puts a
      hard seam at every boundary and resets the eye each time.

   b) A SOFT ORB anchored to the boundary. On mymind these are
      pre-rendered JPEGs (mymind_manifesto_neue-1.jpg,
      Closing_statement_bg_new.png) set to
      `background-size: cover; background-position: 50% 100%`,
      so the glow is at its densest exactly where one section
      ends and dissolves into the next section's base colour.
      Reproduced here as a radial-gradient that fades to fully
      transparent, so it dissolves into whatever is behind it.

   #tech and #sectors carry no [data-mm] and are untouched. The
   two dark sections become the arc's punctuation block, which
   is the same role mymind's full-bleed #ff5924 section plays.
   ============================================================ */

:root {
    --atmos-0: #ffffff;   /* opening       */
    --atmos-1: #f8fbfe;   /* first warmth  */
    --atmos-2: #f2f7fc;   /* mid           */
    --atmos-3: #ecf3fa;   /* resting tint  */
    --atmos-orb: 127, 168, 208;
}

/* The arc. Values repeat across joins on purpose — a shared
   value is a seam the eye cannot find. */
[data-mm]#about,
[data-mm].whychoose-section {
    background: var(--atmos-0);
}
[data-mm]#solutions,
[data-mm]#training {
    background: var(--atmos-1);
}
[data-mm]#leadership,
[data-mm]#clients {
    background: var(--atmos-2);
}
[data-mm]#registrations,
[data-mm]#contact,
[data-mm].footer {
    background: var(--atmos-3);
}
/* Overscroll rubber-band should land on the resting tint, not
   on a colour that appears nowhere on the page. */
body {
    background-color: var(--atmos-3);
}

/* The orb. Anchored to a boundary, densest at the join. */
[data-mm].mm-atmos {
    position: relative;
}
[data-mm].mm-atmos > .container {
    position: relative;
    z-index: 1;
}
[data-mm].mm-atmos::before {
    content: "";
    position: absolute;
    /* Inset to the section rather than a vw width. A 165vw orb
       overhangs the viewport and creates horizontal scroll, since
       sections do not clip. A wide ellipse gives the same broad
       bloom while staying inside the box. */
    left: 0;
    right: 0;
    height: 78%;
    max-height: 780px;
    background: radial-gradient(78% 58% at 50% 50%,
        rgba(var(--atmos-orb), 0.40) 0%,
        rgba(var(--atmos-orb), 0.26) 34%,
        rgba(var(--atmos-orb), 0.12) 56%,
        rgba(var(--atmos-orb), 0.04) 72%,
        rgba(var(--atmos-orb), 0) 82%);
    pointer-events: none;
    z-index: 0;
}
/* Densest toward the bottom, contained inside the section.
   A downward overhang looked safe because the next section's opaque
   background covers it — but that is exactly the problem: the covered
   strip leaves the section ABOVE tinted warmer than the flat colour
   that starts below it, so the boundary steps. Measured at the footer:
   a jump of 17 at rel+1 from #contact's orb being clipped. */
[data-mm].mm-atmos-down::before {
    bottom: 0;
}
/* Rising from the top of the section — but contained inside it.
   This used to be `top: -14%`, which on a 780px orb meant it hung
   279px ABOVE the section. Sections are opaque siblings, so a
   downward overhang is harmlessly covered by the next section,
   but an upward one paints over the previous section's bottom.
   Two consequences, both measured:
     1. It tinted the bottom of #sectors warm brown, rgb(89,60,50)
        instead of rgb(24,24,37) — a locked section I must not touch.
     2. That warm tint then met the dark curve at #training's top
        edge, producing a straight full-width line with a jump of
        75 in RGB distance. Contained, both sides read rgb(24,24,37)
        and the jump drops to 4.7. */
[data-mm].mm-atmos-up::before {
    top: 0;
}

/* The two dark sections are the arc's punctuation. Feather the
   approach and the exit so the eye is led into them rather than
   hitting a wall. These sit on [data-mm] sections only — the
   dark sections themselves are never styled. */
[data-mm].mm-atmos-into-dark::after,
[data-mm].mm-atmos-outof-dark::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    height: 190px;
    pointer-events: none;
    z-index: 0;
}
[data-mm].mm-atmos-into-dark::after {
    bottom: 0;
    background: linear-gradient(180deg, transparent, rgba(20, 28, 42, 0.10));
}
[data-mm].mm-atmos-outof-dark::after {
    top: 0;
    background: linear-gradient(0deg, transparent, rgba(20, 28, 42, 0.10));
}

/* ---------- 18. DEPTH — the inner animation ----------
   mymind's five review cards do NOT share a drift distance.
   They are -2.22 / -4.72 / -5.56 / -2.33 / -3.78 rem, each on
   its own scrub:2 timeline, so the section has depth inside
   itself. Protek was drifting whole grids as one rigid block,
   which is why the interior felt static next to mymind.

   Distances are assigned per grid row by js/motion.js. Row
   grouping rather than per-card keeps horizontal alignment
   intact — mymind can offset individual cards because its
   review layout is deliberately staggered; a strict grid
   cannot without looking broken.                            */
[data-mm] [data-mm-depth] > * {
    will-change: transform;
}
@media (max-width: 767px), (prefers-reduced-motion: reduce) {
    [data-mm] [data-mm-depth] > * {
        transform: none !important;
        will-change: auto;
    }
}
@media (prefers-reduced-motion: reduce) {
    [data-mm].mm-atmos::before,
    [data-mm].mm-atmos-into-dark::after,
    [data-mm].mm-atmos-outof-dark::after {
        display: block;
    }
}

/* ============================================================
   19. HERO BUILDING BACKDROP
   ------------------------------------------------------------
   Sits ABOVE .hero-skyscraper-overlay in the paint order,
   because that overlay is a near-opaque white wash
   (rgba(247,247,247,0.98)) and would bury anything behind it.

   To use a real photograph instead of the drawn skyline, point
   this one variable at it. Nothing else needs to change:
       --hero-building: url('../assets/your_building.jpg');
   A landscape crop around 1600x900 works best.
   ============================================================ */
:root {
    /* Illustrated skyline, re-encoded to JPEG q82: 1419 KB -> 112 KB.
       Lossless PNG is wasted on a decorative backdrop that renders at
       under half opacity behind a mask, so no artefact is reachable.
       Desktop only: the 767px block below repoints this variable at a
       portrait crop for phones, and that file is left untouched. */
    --hero-building: url('../assets/hero_building.jpg');
    --hero-building-opacity: 0.46;
}

.hero-building {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 82%;
    background-image: var(--hero-building);
    background-repeat: no-repeat;
    background-position: 50% 100%;
    background-size: cover;
    opacity: var(--hero-building-opacity);
    /* A photograph carries its own colour cast, which fights the page
       palette when laid under coloured type. Pulling some saturation out
       leaves the structure and lets the page's own tone come through.
       Grayscale eased 0.55 -> 0.38 as part of raising visibility: at
       higher opacity the desaturation was what made it read washed out,
       not the opacity alone. */
    /* No blur here any more. It existed to dissolve a hard 3px
       horizontal band baked into the previous photograph, measured
       as a 20.7 row-to-row luminance jump at y=843. The replacement
       illustration has no such defect: its largest jump is 8.1, and
       that is the legitimate ground line where the buildings meet
       the base. Blurring a clean file would only soften detail for
       no reason, so the filter now carries colour work only. */
    filter: grayscale(0.38) contrast(1.14);
    pointer-events: none;
    /* Dissolve into the page at the top and sides so the image
       reads as atmosphere rather than a pasted-in picture.
       Plateau widened (34/76 -> 20/88 vertical, 18/82 -> 10/90
       horizontal) so more of the frame is at full strength. The
       fades stay, since they are what keep it from looking pasted in. */
    -webkit-mask-image:
        linear-gradient(180deg, transparent 0%, #fff 20%, #fff 88%, transparent 100%),
        linear-gradient(90deg, transparent 0%, #fff 10%, #fff 90%, transparent 100%);
    mask-image:
        linear-gradient(180deg, transparent 0%, #fff 20%, #fff 88%, transparent 100%),
        linear-gradient(90deg, transparent 0%, #fff 10%, #fff 90%, transparent 100%);
    -webkit-mask-composite: source-in;
    mask-composite: intersect;
}
/* ------------------------------------------------------------
   Phones get their own photograph.
   The desktop file is landscape (1672x941). In a phone-shaped
   box, background-size: cover crops that to a narrow centre
   slice — the building was still being painted, but only a
   sliver of sky was left in frame, which at 0.30 opacity read
   as nothing at all. Hence a portrait crop (740x1600) instead
   of trying to rescue the landscape one with positioning.

   Measured mean luminance of the portrait file is 220-239/255
   across every fifth of its height, i.e. uniformly light. That
   is what allows the opacity to go up rather than down: there
   is no dark region to sit behind the hero's near-black
   headline, so raising it costs no text contrast.

   Neither file is blurred any more. Desktop used to carry
   blur(1.1px) to hide a hard 3px band in the old photograph;
   both that photograph and the blur are gone now, and this
   portrait crop never had the defect either.
   ------------------------------------------------------------ */
@media (max-width: 767px) {
    .hero-building {
        --hero-building: url('../assets/hero_building_mobile.jpg');
        /* Portrait source in a portrait box: it can fill the hero
           instead of hugging the bottom 62% as the landscape crop had to. */
        height: 100%;
        /* Raise the skyline to meet the intro paragraph.
           Measured in the file: the skyline top edge is at row 796 of
           1600 (49.8% down) and the buildings end at row 1220, below
           which the file is 380px of blank white (row sd drops from 37
           to 2.5). With background-size: cover the file's height maps
           1:1 onto the hero, so the skyline landed at 49.8% of the hero
           while the paragraph starts at 30.2% — that gap is what you saw.

           Both values below are percentages, so this holds on any
           screen height rather than being tuned to one phone:
             height 150%      -> image is 1.5x the hero, so 0.498 x 1.5
                                 = 74.6% of hero height sits above the
                                 skyline before shifting
             position-y 89%   -> shifts up by 0.89 x 0.5 = 44.5% of hero
           74.6% - 44.5% = 30.1% of hero height. That is the paragraph.

           Zoom stays at 1.5x rather than the ~3x it would take to also
           stretch the buildings to the hero's bottom edge. At 3x only
           the middle third of the skyline survives the horizontal crop,
           upscaled from a 740px-wide source. The lower hero is covered
           by the CTA buttons and the video card anyway. */
        background-size: auto 150%;
        background-position: 50% 89%;
        --hero-building-opacity: 0.55;
        filter: grayscale(0.15) contrast(1.16);
        /* Wider plateau than desktop. A phone frame is narrow enough that
           the desktop side fades alone would eat most of the building. */
        -webkit-mask-image:
            linear-gradient(180deg, transparent 0%, #fff 12%, #fff 92%, transparent 100%),
            linear-gradient(90deg, transparent 0%, #fff 4%, #fff 96%, transparent 100%);
        mask-image:
            linear-gradient(180deg, transparent 0%, #fff 12%, #fff 92%, transparent 100%),
            linear-gradient(90deg, transparent 0%, #fff 4%, #fff 96%, transparent 100%);
    }
}

/* ============================================================
   20. SECTION APPROACH — the transition before you arrive
   ------------------------------------------------------------
   js/motion.js writes --mm-enter (0 -> 1) onto every [data-mm]
   section as its top edge travels from the bottom of the
   viewport up to roughly 55% of viewport height. So the effect
   plays while the section is still on its way in, which is the
   "before we reach it" part.

   The layers live in an injected <span class="mm-enter-fx">
   rather than pseudo elements, because .mm-atmos already
   spends ::before and .mm-atmos-into-dark spends ::after.
   ============================================================ */
[data-mm] {
    --mm-enter: 1;
}
.mm-enter-fx {
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    height: 100%;
    pointer-events: none;
    z-index: 2;
    overflow: hidden;
}

/* a. Hairline sweeping open from the centre at the leading edge */
.mm-enter-line {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 2px;
    display: block;
    background: linear-gradient(90deg,
        transparent 0%,
        rgba(var(--atmos-orb), 0.15) 18%,
        rgba(var(--atmos-orb), 0.95) 50%,
        rgba(var(--atmos-orb), 0.15) 82%,
        transparent 100%);
    transform: scaleX(var(--mm-enter));
    transform-origin: 50% 50%;
    opacity: calc(var(--mm-enter) * 0.9);
    transition: none;
}

/* b. Bloom that swells at the leading edge, then settles.
      Peaks near 60% progress via the doubled-then-clamped ramp. */
.mm-enter-bloom {
    position: absolute;
    top: -1px;
    left: 50%;
    display: block;
    width: 118%;
    height: 300px;
    transform: translateX(-50%);
    background: radial-gradient(60% 100% at 50% 0%,
        rgba(var(--atmos-orb), 0.30) 0%,
        rgba(var(--atmos-orb), 0.14) 40%,
        rgba(var(--atmos-orb), 0.04) 68%,
        transparent 84%);
    opacity: calc(var(--mm-enter) * (1.35 - var(--mm-enter) * 0.75));
}

/* c. Veil that clears as the section resolves into place */
.mm-enter-veil {
    position: absolute;
    inset: 0;
    display: block;
    background: linear-gradient(180deg,
        rgba(255, 255, 255, 0.55) 0%,
        rgba(255, 255, 255, 0.22) 26%,
        transparent 52%);
    opacity: calc(1 - var(--mm-enter));
}

/* The dark pair keeps its own look, so the sections either side
   get a cooler leading edge to match what they are handing to. */
[data-mm].mm-atmos-into-dark .mm-enter-line,
[data-mm].mm-atmos-outof-dark .mm-enter-line {
    background: linear-gradient(90deg,
        transparent 0%,
        rgba(20, 28, 42, 0.10) 18%,
        rgba(var(--atmos-orb), 0.95) 50%,
        rgba(20, 28, 42, 0.10) 82%,
        transparent 100%);
}

@media (prefers-reduced-motion: reduce) {
    [data-mm] { --mm-enter: 1 !important; }
    .mm-enter-veil,
    .mm-enter-bloom { display: none; }
    .mm-enter-line { transform: none; opacity: 0.55; }
}

/* ============================================================
   21. TAGGED SHOWCASE — mymind's auto-tagging panel
   ------------------------------------------------------------
   On mymind this visual is baked into an mp4
   (adding-to-mymind-video), so there is no CSS to copy. Rebuilt
   here in the DOM instead, which keeps the labels crisp at any
   zoom, carries real content, and costs no video download.

   Mechanics kept from the reference: a dotted measurement grid,
   scattered specks, and rounded white labels that pop in one
   after another with an overshoot, then drift very slowly.
   ============================================================ */
.mm-tagpanel {
    position: relative;
    max-width: 820px;
    margin: 0 auto 64px;
    aspect-ratio: 16 / 10;
    border-radius: var(--radius-lg);
    overflow: hidden;
    background: #e6edf5;
    isolation: isolate;
}
@media (max-width: 640px) {
    .mm-tagpanel { aspect-ratio: 4 / 5; margin-bottom: 44px; }
}

.mm-tagpanel-media {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    filter: saturate(1.05) contrast(1.02);
    transform: scale(1.02);
    transition: transform 1.6s var(--ease-out-expo);
}
.mm-tagpanel:hover .mm-tagpanel-media {
    transform: scale(1.05);
}

/* Warm wash so the panel belongs to the tangerine arc */
.mm-tagpanel-wash {
    position: absolute;
    inset: 0;
    background:
        radial-gradient(70% 60% at 50% 42%, rgba(var(--atmos-orb), 0.10), transparent 76%),
        linear-gradient(180deg, rgba(230, 237, 245, 0.42), rgba(230, 237, 245, 0.06) 46%, rgba(230, 237, 245, 0.5));
    pointer-events: none;
}

/* The dotted measurement grid */
.mm-tagpanel-dots {
    position: absolute;
    inset: 0;
    background-image: radial-gradient(rgba(255, 255, 255, 0.75) 1.6px, transparent 1.7px);
    background-size: 26px 26px;
    background-position: 13px 13px;
    opacity: 0;
    transition: opacity 1.1s var(--ease-out-expo);
    pointer-events: none;
}
.mm-tagpanel.mm-in .mm-tagpanel-dots {
    opacity: 0.7;
}

/* Scattered specks, each drifting on its own cycle */
.mm-speck {
    position: absolute;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #ffffff;
    opacity: 0;
    transform: scale(0);
    transition: opacity 0.7s var(--ease-out-expo), transform 0.7s var(--ease-out-back);
    transition-delay: var(--mm-delay, 0s);
    pointer-events: none;
}
.mm-tagpanel.mm-in .mm-speck {
    opacity: 0.9;
    transform: scale(1);
    animation: mmSpeckDrift var(--mm-drift, 7s) ease-in-out infinite alternate;
    animation-delay: var(--mm-delay, 0s);
}
@keyframes mmSpeckDrift {
    from { translate: 0 0; }
    to   { translate: 0 -9px; }
}

/* The labels */
.mm-pin {
    position: absolute;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 9px 18px;
    background: #ffffff;
    border-radius: var(--radius-full);
    font-size: clamp(12px, 1.15vw, 15px);
    font-weight: 500;
    letter-spacing: 0.1px;
    color: var(--color-midnight-ink);
    white-space: nowrap;
    box-shadow: 0 6px 18px rgba(20, 28, 42, 0.14);
    /* mymind's overshoot, as a transition rather than keyframes so
       an interrupted state still resolves cleanly */
    opacity: 0;
    transform: scale(0.55) translateY(10px);
    transition: opacity 0.42s var(--ease-out-quad),
                transform 0.52s var(--ease-out-back);
    transition-delay: var(--mm-delay, 0s);
    z-index: 3;
}
.mm-tagpanel.mm-in .mm-pin {
    opacity: 1;
    transform: scale(1) translateY(0);
}
.mm-tagpanel.mm-in .mm-pin {
    animation: mmTagFloat var(--mm-drift, 6s) ease-in-out infinite alternate;
    animation-delay: calc(var(--mm-delay, 0s) + 0.6s);
}
@keyframes mmTagFloat {
    from { translate: 0 0; }
    to   { translate: 0 -7px; }
}
.mm-pin-swatch {
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: var(--gold);
    flex: 0 0 auto;
}
.mm-pin-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: #10b981;
    flex: 0 0 auto;
}
.mm-pin i {
    font-size: 0.86em;
    color: var(--gold);
}
/* Small satellite labels, like the reference's tiny stray tags */
.mm-pin.is-mini {
    padding: 4px 9px;
    font-size: 9px;
    letter-spacing: 0.3px;
    text-transform: uppercase;
    box-shadow: 0 3px 10px rgba(20, 28, 42, 0.12);
}

@media (prefers-reduced-motion: reduce) {
    .mm-tagpanel-dots { opacity: 0.7 !important; transition: none !important; }
    .mm-speck,
    .mm-tagpanel.mm-in .mm-speck,
    .mm-pin,
    .mm-tagpanel.mm-in .mm-pin {
        opacity: 1 !important;
        transform: none !important;
        transition: none !important;
        animation: none !important;
    }
    .mm-tagpanel-media { transition: none !important; transform: none !important; }
}

/* ============================================================
   22. HALF-OVAL FIELD + DISPLAY ITALIC
   ------------------------------------------------------------
   The arc in the reference is the top edge of a very large
   ellipse anchored below the section, which is why it reads as
   a clean curve rather than a soft blur. Reproduced with a
   hard-ish radial stop instead of mymind's pre-rendered PNG.

   Playfair Display italic is already in the page's Google
   Fonts request (ital,wght@1,400;1,600), so the display face
   costs nothing extra to use.
   ============================================================ */
[data-mm].mm-halfoval {
    position: relative;
    overflow: hidden;
}
[data-mm].mm-halfoval > .container {
    position: relative;
    z-index: 1;
}
.mm-halfoval-field {
    position: absolute;
    left: 50%;
    bottom: -46%;
    width: 176%;
    height: 132%;
    transform: translateX(-50%);
    border-radius: 50% 50% 0 0 / 62% 62% 0 0;
    background:
        radial-gradient(58% 74% at 50% 96%,
            rgba(127, 168, 208, 0.92) 0%,
            rgba(127, 168, 208, 0.72) 26%,
            rgba(166, 196, 226, 0.46) 48%,
            rgba(196, 216, 235, 0.24) 66%,
            rgba(226, 238, 248, 0.10) 82%,
            rgba(255, 255, 255, 0) 100%);
    pointer-events: none;
    z-index: 0;
}

.mm-statement {
    position: relative;
    z-index: 1;
    text-align: center;
    padding: 26px 0 6px;
}
.mm-statement-line {
    display: block;
    font-family: 'Playfair Display', Georgia, 'Times New Roman', serif;
    font-style: italic;
    font-weight: 400;
    font-size: clamp(30px, 5.4vw, 68px);
    line-height: 1.06;
    letter-spacing: -0.5px;
    color: var(--color-midnight-ink);
}
.mm-statement-line.is-indent {
    padding-left: 1.4em;
}
.mm-statement-line em {
    font-style: italic;
    color: var(--gold-dark);
}
/* Over the dense part of the oval, flip to white like the reference */
.mm-statement.on-field .mm-statement-line {
    color: #ffffff;
    text-shadow: 0 2px 18px rgba(40, 74, 108, 0.22);
}
.mm-statement.on-field .mm-statement-line em {
    color: #fff3e8;
}

/* ============================================================
   23. DEPLOYMENT MAP
   ------------------------------------------------------------
   The tagged panel now carries a schematic Bengaluru map with
   one clickable pin per client site. Each pin opens that site's
   location in Google Maps.

   Structure note: the pin is a zero-size anchor point that owns
   the left/top placement, and the visible pill lives inside it.
   Keeping them separate means the pop-in can animate `transform`
   freely without fighting a centring translate.
   ============================================================ */
.mm-tagpanel.is-map {
    background: #e6edf5;
    aspect-ratio: 16 / 11;
}
@media (max-width: 640px) {
    .mm-tagpanel.is-map { aspect-ratio: 1 / 1; }
}
.mm-map-surface {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}
/* No hover zoom on the map — the pins are positioned against the
   panel, so scaling the artwork would slide them off their areas. */
.mm-tagpanel.is-map:hover .mm-map-surface {
    transform: none;
}

/* The anchor IS the pill, so it has a real hit area. A zero-size
   anchor with the label overflowing outside it looks fine but is
   not reliably clickable or focusable. `translate` handles the
   centring so `scale` stays free for the pop-in. */
.mm-mappin {
    position: absolute;
    z-index: 4;
    display: inline-flex;
    align-items: center;
    gap: 7px;
    margin-left: 22px;
    padding: 7px 13px;
    background: #ffffff;
    border-radius: var(--radius-full);
    font-size: 12.5px;
    font-weight: 500;
    letter-spacing: 0.1px;
    color: var(--color-midnight-ink);
    white-space: nowrap;
    text-decoration: none;
    box-shadow: 0 5px 16px rgba(20, 28, 42, 0.16);
    translate: 0 -50%;
    opacity: 0;
    scale: 0.6;
    transform-origin: 0 50%;
    transition: opacity 0.42s var(--ease-out-quad),
                scale 0.52s var(--ease-out-back),
                box-shadow var(--mm-hover) var(--ease-out-expo),
                background-color var(--mm-hover) var(--ease-out-expo),
                color var(--mm-hover) var(--ease-out-expo);
    transition-delay: var(--mm-delay, 0s);
}
.mm-mappin.is-left {
    margin-left: 0;
    margin-right: 22px;
    translate: -100% -50%;
    transform-origin: 100% 50%;
}
.mm-tagpanel.mm-in .mm-mappin {
    opacity: 1;
    scale: 1;
}
/* Fallback for engines without independent transform properties */
@supports not (translate: 0 1px) {
    .mm-mappin { transform: translateY(-50%); opacity: 1; }
    .mm-mappin.is-left { transform: translate(-100%, -50%); }
}

/* The location dot, sitting exactly on the coordinate */
.mm-mappin-dot {
    position: absolute;
    left: -29px;
    top: 50%;
    margin-top: -7px;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: var(--gold);
    box-shadow: 0 0 0 3px #ffffff, 0 3px 8px rgba(20, 28, 42, 0.28);
    opacity: 0;
    transform: scale(0.2);
    transition: opacity 0.4s var(--ease-out-quad),
                transform 0.5s var(--ease-out-back);
    transition-delay: var(--mm-delay, 0s);
}
.mm-mappin.is-left .mm-mappin-dot {
    left: auto;
    right: -29px;
}
/* Radar ping, so an active site reads as live */
.mm-mappin-dot::after {
    content: "";
    position: absolute;
    inset: -5px;
    border-radius: 50%;
    border: 1.5px solid var(--gold);
    opacity: 0;
}
.mm-tagpanel.mm-in .mm-mappin-dot {
    opacity: 1;
    transform: scale(1);
}
.mm-tagpanel.mm-in .mm-mappin-dot::after {
    animation: mmPing 2.8s var(--ease-out-quad) infinite;
    animation-delay: calc(var(--mm-delay, 0s) + 0.5s);
}
@keyframes mmPing {
    0%   { opacity: 0.85; transform: scale(0.6); }
    70%  { opacity: 0;    transform: scale(2.1); }
    100% { opacity: 0;    transform: scale(2.1); }
}

/* Text content sits inline inside the anchor */
.mm-mappin-label {
    display: inline-flex;
    align-items: center;
    gap: 7px;
}
.mm-mappin-code {
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: 0.6px;
    color: var(--gold-dark);
    background: var(--gold-50);
    border-radius: 4px;
    padding: 2px 5px;
}
.mm-mappin-label i {
    font-size: 9.5px;
    color: var(--color-pebble);
    transition: color var(--mm-hover) var(--ease-out-expo),
                transform var(--mm-hover) var(--ease-out-expo);
}

@media (hover: hover) and (pointer: fine) {
    .mm-mappin:hover {
        z-index: 6;
        background: var(--color-midnight-ink);
        color: #ffffff;
        box-shadow: 0 10px 26px rgba(20, 28, 42, 0.3);
    }
    .mm-mappin:hover .mm-mappin-code {
        color: #ffd9bd;
        background: rgba(127, 168, 208, 0.22);
    }
    .mm-mappin:hover i {
        color: var(--gold-light);
        transform: translate(2px, -2px);
    }
    .mm-mappin:hover .mm-mappin-dot {
        transform: scale(1.35);
    }
}
.mm-mappin:focus-visible {
    outline: 2px solid var(--color-midnight-ink);
    outline-offset: 3px;
}

/* Caption strip under the map */
.mm-map-note {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 5;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    padding: 12px 18px;
    background: linear-gradient(0deg, rgba(255, 255, 255, 0.92), rgba(255, 255, 255, 0.55) 62%, transparent);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.8px;
    text-transform: uppercase;
    color: var(--color-stone);
}
.mm-map-note .mm-map-live {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    color: var(--color-graphite);
}
.mm-map-note .mm-map-live b {
    color: var(--gold-dark);
}

@media (max-width: 900px) {
    .mm-mappin { font-size: 11px; padding: 5px 10px; }
    .mm-mappin-code { font-size: 8.5px; }
}
@media (max-width: 640px) {
    /* Eight labels cannot tile on a phone without colliding, and a
       14px dot is well under a usable touch target. So each pin
       becomes a 34px circular target sitting on its coordinate,
       and the cards below carry the names. The anchor keeps its
       aria-label, so screen readers still get the full site name. */
    .mm-mappin {
        width: 34px;
        height: 34px;
        margin: 0;
        padding: 0;
        gap: 0;
        border-radius: 50%;
        background: transparent;
        box-shadow: none;
        translate: -50% -50%;
        transform-origin: 50% 50%;
    }
    .mm-mappin.is-left {
        margin: 0;
        translate: -50% -50%;
        transform-origin: 50% 50%;
    }
    .mm-mappin-label { display: none; }
    .mm-mappin .mm-mappin-dot,
    .mm-mappin.is-left .mm-mappin-dot {
        left: 50%;
        right: auto;
        top: 50%;
        margin-top: -8px;
        margin-left: -8px;
        width: 16px;
        height: 16px;
    }
    @supports not (translate: 0 1px) {
        .mm-mappin { transform: translate(-50%, -50%); }
        .mm-mappin.is-left { transform: translate(-50%, -50%); }
    }
}

@media (prefers-reduced-motion: reduce) {
    .mm-mappin-dot,
    .mm-tagpanel.mm-in .mm-mappin-dot,
    .mm-mappin,
    .mm-tagpanel.mm-in .mm-mappin {
        opacity: 1 !important;
        transform: none !important;
        /* `scale` is the pop-in, so it resets. `translate` is how the
           pill is centred on its coordinate, so it must survive. */
        scale: none !important;
        transition: none !important;
    }
    .mm-tagpanel.mm-in .mm-mappin-dot::after { animation: none !important; }
}

/* ============================================================
   24. CURVED SECTION BOUNDARIES
   ------------------------------------------------------------
   Replaces the flat horizontal seam between sections with a
   wide, shallow arc, the way the reference resolves one colour
   field into the next.

   The curve sits INSIDE the top of a section and carries the
   PREVIOUS section's colour, bulging downward. Two reasons for
   that direction rather than a dome overhanging upward:

     1. Sections are opaque siblings, so a later section can
        paint over an earlier one but never the reverse. An
        upward dome from section B would work, but it would
        also paint over B's predecessor's content, since a
        `position: relative; z-index: auto` section creates no
        stacking context to contain it.
     2. An overhang needs a width greater than the viewport to
        read as a shallow arc, which reintroduces the horizontal
        scroll problem solved in section 17.

   Anchored left:0 right:0, the border-radius percentages give
   the full-width shallow arc with no overflow at all.
   ============================================================ */
.mm-curve {
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    height: var(--curve-h, 120px);
    background: var(--curve-fill, var(--atmos-0));
    /* horizontal radius 50% each side, vertical radius the full
       height: one clean elliptical arc across the whole width */
    border-radius: 0 0 50% 50% / 0 0 100% 100%;
    pointer-events: none;
    z-index: 0;
}

/* A section carrying a curve must stack its content above it. */
[data-mm].has-curve {
    position: relative;
}
[data-mm].has-curve > .container {
    position: relative;
    z-index: 1;
}

/* Per-boundary fill = the colour of the section immediately above,
   so the previous field appears to pour down into this one. */
[data-mm]#about {
    --curve-fill: var(--color-fog);      /* from the hero */
}
[data-mm]#solutions {
    --curve-fill: var(--atmos-0);        /* from why-choose */
}
[data-mm]#training {
    --curve-fill: #141c2a;               /* from the dark pair */
    --curve-h: 84px;
}
[data-mm]#leadership {
    --curve-fill: var(--atmos-1);        /* from training */
}
[data-mm]#registrations {
    --curve-fill: var(--atmos-2);        /* from clients */
}

/* The dark spill gets a soft inner edge so it reads as a shadow
   falling away rather than a painted shape. */
[data-mm]#training .mm-curve {
    background:
        radial-gradient(120% 100% at 50% 0%,
            rgba(20, 28, 42, 1) 0%,
            rgba(20, 28, 42, 0.96) 62%,
            rgba(20, 28, 42, 0.82) 100%);
}

/* The orb in .mm-atmos sits at z-index 0 as well. Nudge the curve
   above it so the boundary edge stays crisp. */
[data-mm].mm-atmos .mm-curve {
    z-index: 1;
}
[data-mm].mm-atmos.has-curve > .container {
    z-index: 2;
}

/* The approach hairline should follow the arc, not cut across it,
   so it is suppressed where a curve owns the top edge. */
[data-mm].has-curve .mm-enter-line {
    display: none;
}

@media (max-width: 900px) {
    .mm-curve { height: var(--curve-h-md, 84px); }
    [data-mm]#training { --curve-h-md: 62px; }
}
@media (max-width: 560px) {
    .mm-curve { height: var(--curve-h-sm, 58px); }
    [data-mm]#training { --curve-h-sm: 44px; }
}

/* ---------- 24b. Softer, rounder surfaces throughout ----------
   The reference has no square edges anywhere. Cards and panels
   already use the radius tokens; these are the leftovers that
   still had hard corners. */
[data-mm] .mission-box,
[data-mm] .contact-cta,
[data-mm] .mymind-board-frame {
    border-radius: var(--radius-xl);
}
[data-mm] .benefit-item,
[data-mm] .compliance-card,
[data-mm] .contact-card,
[data-mm] .emergency-item {
    border-radius: var(--radius-md);
}
[data-mm] .training-img-wrap {
    border-radius: var(--radius-md) var(--radius-md) 0 0;
    overflow: hidden;
}
[data-mm] .collage-top,
[data-mm] .collage-item {
    border-radius: var(--radius-lg);
    overflow: hidden;
}

/* ============================================================
   25. ORPHANED PALETTE TOKENS  (pre-existing bug fix)
   ------------------------------------------------------------
   styles.css references eight custom properties that are never
   defined anywhere in the project — leftovers from an earlier
   plum/iris palette. Each `var()` call without a fallback
   resolved to nothing, so:

     .tech-ticker-section  had NO background and invisible
                           borders, which is why it computed to
                           rgba(0,0,0,0) and the band vanished
     .ticker-item          had no colour (inherited)
     .ticker-item i        had no icon colour
     .ticker-sep           had no separator colour
     .comp-pill            had no border colour
     .comp-dot             had no fill or glow

   Mapped onto the current tangerine palette rather than
   reinstating the old plum, so the ticker becomes the dark
   punctuation band it was clearly designed to be.
   ============================================================ */
:root {
    --deep-plum: #141c2a;                      /* same ink as the dark sections */
    --aubergine: #1a2c46;
    --periwinkle: rgba(255, 255, 255, 0.10);   /* hairline on dark */
    --cream-mist: #eef4fa;                     /* ticker label on dark */
    --lavender-mist: rgba(255, 255, 255, 0.28);/* ticker separator */
    --mint-keyhole: #7fa8d0;                   /* ticker icons, brand tangerine */
    --iris: #10b981;                           /* live status dot */
    --warm-sand: rgba(20, 28, 42, 0.12);       /* compliance pill hairline */
}

/* With the ticker now a real dark band, the section below it gets
   the same dark spill treatment as the one below the dark pair,
   so every dark band resolves into the next section as a U. */
[data-mm]#about {
    --curve-fill: #141c2a;
    --curve-h: 92px;
    --curve-h-md: 68px;
    --curve-h-sm: 48px;
}
[data-mm]#about .mm-curve {
    background:
        radial-gradient(120% 100% at 50% 0%,
            rgba(20, 28, 42, 1) 0%,
            rgba(20, 28, 42, 0.96) 62%,
            rgba(20, 28, 42, 0.82) 100%);
}
/* The ticker sits directly on that curve, so drop its bottom
   hairline — the arc is the edge now. */
.tech-ticker-section {
    border-bottom-color: transparent;
}

/* ============================================================
   26. CURVED TICKER
   ------------------------------------------------------------
   The band's text used to slide along a straight line while
   everything under it curved. Now it travels an arc that runs
   parallel to the dome spilling into #about.

   Built as SVG <textPath> rather than CSS `offset-path`. Both
   can move things along a curve, but offset-path drives one
   element at a time, which means recreating inline text flow by
   hand — measuring every item and mapping cumulative widths onto
   path length. textPath does that natively and correctly, and it
   keeps letter-spacing and kerning intact around the bend.

   js/motion.js builds the SVG from the existing markup, reading
   each icon's real codepoint out of the FontAwesome ::before
   rule, so no glyph values are hardcoded here.
   ============================================================ */
.tech-ticker-section.has-arc {
    padding: 0;
    overflow: hidden;
}

/* Once the curved version is live, the straight track is hidden
   but stays in the DOM for screen readers. */
.tech-ticker-section.arc-ready .ticker-track {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

.ticker-arc {
    display: block;
    width: 100%;
    height: var(--ticker-arc-h, 104px);
}
.ticker-arc-text {
    font-family: 'Inter', -apple-system, 'Segoe UI', Roboto, sans-serif;
    font-size: 13px;
    font-weight: 800;
    letter-spacing: 1.5px;
    fill: var(--cream-mist);
    text-transform: uppercase;
    dominant-baseline: middle;
}
.ticker-arc-text .t-icon {
    font-family: 'Font Awesome 6 Free', 'FontAwesome';
    font-weight: 900;
    fill: var(--mint-keyhole);
    letter-spacing: 0;
}
.ticker-arc-text .t-sep {
    fill: var(--mint-keyhole);
    letter-spacing: 0;
}

@media (max-width: 900px) {
    .ticker-arc { height: var(--ticker-arc-h-md, 84px); }
    .ticker-arc-text { font-size: 12px; letter-spacing: 1.2px; }
}
@media (max-width: 560px) {
    .ticker-arc { height: var(--ticker-arc-h-sm, 64px); }
    .ticker-arc-text { font-size: 10.5px; letter-spacing: 0.9px; }
}

/* ============================================================
   27. TICKER OWNS ITS OWN CURVE  (supersedes part of 24/25/26)
   ------------------------------------------------------------
   The label motion has to follow the SAME line as the U-shaped
   boundary, not a separate arc of its own. That is only
   guaranteeable if one piece of code produces both, so
   js/motion.js now draws the dark band as an SVG path whose
   bottom edge and whose text baseline are sampled from a single
   ellipse function.

   Consequences:
   - #about no longer needs its own .mm-curve. The dark U lives
     in the ticker now, so the previous one would have drawn a
     second, unrelated arc directly beneath this one.
   - The ticker's own background becomes the same white as
     #about, so the shoulders either side of the dip read as
     continuous with the section below.
   ============================================================ */
.tech-ticker-section.arc-ready {
    background: var(--atmos-0);
    border-top-color: transparent;
    border-bottom-color: transparent;
}

/* The hero's bottom edge is not a uniform colour: .hero-skyscraper-overlay
   runs a 135deg gradient, so its last row measures anywhere from
   rgb(248,248,248) at the right to rgb(254,252,250) near the middle.
   Any flat colour underneath it therefore steps somewhere along the
   width, which is the horizontal line that showed up under the band.
   Resolving the hero's own last 72px to flat white removes the cause
   rather than chasing it — the ticker's white then matches exactly. */
.hero::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 72px;
    background: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, var(--atmos-0) 82%);
    pointer-events: none;
    z-index: 4;   /* above .hero-bg layers, below .hero-container-left (5) */
}
/* The scroll cue sits 33px off the hero's bottom, inside that 72px fade,
   and its z-index was auto — so the fade was painting straight over it. */
.hero .scroll-indicator {
    z-index: 5;
}
/* Band colour — no new value, only ones already on the page.
   Two earlier attempts were both wrong:
     #181825  the only cool surface on a warm page
     #f69251  solid accent, which read as a third colour because the
              tangerine had never been used as a large flat surface
   What the page does use at surface scale is a tangerine WASH:
   rgba(--atmos-orb, alpha) over white, in the section orbs and the
   half-oval field. The band is now that same wash, shaped into a
   band, so it belongs to the family already established.
   Stops live in js/motion.js because `fill` cannot take a CSS
   gradient — it needs an SVG paint server. */
.ticker-arc-fill {
    /* fallback only, if the gradient reference ever fails.
       NOTE: js/motion.js sets the gradient via an inline style, not
       setAttribute('fill'). A CSS `fill` declaration outranks an SVG
       presentation attribute, so the attribute form was silently
       losing to this line and painting the band a flat colour. */
    fill: rgba(127, 168, 208, 0.15);
}
/* The arc's own hairline, so the boundary still reads now that the
   fill is as pale as the surrounding creams. */
.ticker-arc-edge {
    fill: none;
    stroke: rgba(127, 168, 208, 0.55);
    stroke-width: 1.5;
    stroke-linecap: round;
}
.ticker-arc-text {
    fill: var(--color-midnight-ink);
}
.ticker-arc-text .t-icon {
    fill: rgba(20, 28, 42, 0.72);
}
.ticker-arc-text .t-sep {
    /* White reads on a saturated band but disappears on a soft wash */
    fill: rgba(20, 28, 42, 0.38);
}
.ticker-arc {
    /* height is written inline by the layout pass, from the same
       metrics that generate the curve */
    height: auto;
}

/* #about's own .mm-curve element is gone from the markup entirely —
   the ticker's arc is this boundary now. It keeps the .has-curve
   class only so the straight approach hairline stays suppressed. */
[data-mm]#about {
    padding-top: 64px;
}
@media (max-width: 900px) {
    [data-mm]#about { padding-top: 48px; }
}

/* ============================================================
   28. NO HORIZONTAL EDGES AT SECTION STARTS
   ------------------------------------------------------------
   Measured cause, not a guess: scanning full-width smooth rows
   around each section's top found a hard edge 2px into #clients
   and #contact with a jump of ~19-20 in RGB distance. Hiding
   .mm-enter-bloom removed it; hiding the veil or the hairline
   changed nothing.

   Why the bloom had an edge: it is
       radial-gradient(60% 100% at 50% 0%, rgba(orb,.30) ...)
   so its FIRST row is the gradient's densest stop, and it sits
   at the section's top boundary. A radial origin on the edge of
   its own box always produces a straight line of maximum colour
   along that edge.

   Fix is to feather the layer's top so the swell builds up from
   nothing. The horizontal falloff — light at the sides, warmer
   through the middle — is unchanged; that part was already the
   right palette.
   ============================================================ */
.mm-enter-bloom {
    -webkit-mask-image: linear-gradient(180deg,
        rgba(0, 0, 0, 0) 0%,
        rgba(0, 0, 0, 0.35) 22%,
        #fff 55%,
        #fff 100%);
    mask-image: linear-gradient(180deg,
        rgba(0, 0, 0, 0) 0%,
        rgba(0, 0, 0, 0.35) 22%,
        #fff 55%,
        #fff 100%);
}

/* Same reasoning for the veil: it opened at 0.55 white on its
   first row, which is a straight edge of its own. */
.mm-enter-veil {
    background: linear-gradient(180deg,
        rgba(255, 255, 255, 0) 0%,
        rgba(255, 255, 255, 0.42) 18%,
        rgba(255, 255, 255, 0.20) 38%,
        transparent 62%);
}

/* The approach hairline is removed rather than feathered. It was a
   2px rule across the full width at the top of every section —
   which is precisely the horizontal divider being objected to.
   The curve and the bloom carry the transition on their own. */
.mm-enter-line {
    display: none !important;
}

/* The footer carries `border-top: 1px solid var(--border)` from
   styles.css — an actual horizontal rule, measured at a jump of 34.7
   three pixels into the section. It shares --atmos-3 with #contact
   above it, so the colours already meet seamlessly and the border is
   the only thing dividing them. */
[data-mm].footer {
    border-top-color: transparent;
}

/* ============================================================
   29. THE DARK PAIR — warm palette + arched top
   ------------------------------------------------------------
   #tech and #sectors were the two sections held back from the
   rest of the work. They are now brought into the palette.

   Before: base #181825, cards #242433, borders rgba(255,255,255,.09)
   — a cool blue-black, the only cool surface on a warm page.
   After: a warm dark brown taken from the supplied swatch, with
   the card surface kept one step lighter than the base so the
   raised relationship survives.

   Text is re-tinted because the warmer surfaces are lighter:
   #949494 on the new card measures 3.0:1, under AA. The warm
   grey replacing it measures 4.8:1.
   ============================================================ */
:root {
    --dk-top: #1e3350;      /* section base, upper */
    --dk-bottom: #16263c;   /* section base, lower */
    --dk-card: #27405f;     /* raised card surface */
    --dk-card-hi: #2f4b6e;  /* card hover */
    --dk-line: rgba(226, 238, 250, 0.11);
    --dk-text: #ffffff;
    --dk-muted: #afc2d6;
    /* Small accent text needs to be lighter than --gold-light here.
       The tag and code chips sit on translucent tangerine over a warm
       brown, which blends to about rgb(98,66,53) and rgb(105,72,59).
       #f8a873 on those measures 4.1:1 and 3.1:1 — both under AA for
       10-12px. This lands at 5.4:1 and 4.8:1. */
    --dk-accent: #cfe0f2;
}

.tech-section,
.sectors-section {
    color: var(--dk-text) !important;
}
/* The two read as one block, so the gradient is split across them:
   #tech runs top -> mid, #sectors carries mid -> bottom. That leaves
   no step at the seam between them. */
.tech-section {
    background: linear-gradient(180deg, var(--dk-top) 0%, #1a2c46 100%) !important;
}
.sectors-section {
    background: linear-gradient(180deg, #1a2c46 0%, var(--dk-bottom) 100%) !important;
}

.tech-card,
.tech-anim-card,
.tactical-card,
.sector-card {
    background: var(--dk-card) !important;
    border-color: var(--dk-line) !important;
}
.tech-card:hover,
.tech-anim-card:hover,
.tactical-card:hover,
.sector-card:hover {
    background: var(--dk-card-hi) !important;
}
.tactical-telemetry-bar {
    background: rgba(39, 64, 95, 0.95) !important;
    border-color: rgba(127, 168, 208, 0.3) !important;
}
.telemetry-divider {
    background: rgba(226, 238, 250, 0.16);
}
/* Telemetry text was tuned for the old #242433. On the lighter warm
   bar it measured 3.48:1 (label) and 4.16:1 (value), both under AA
   for 12px. These land at 4.9:1 and 4.9:1. */
.sectors-section .telemetry-label,
.tech-section .telemetry-label {
    color: var(--dk-muted) !important;
}
.sectors-section .text-green,
.tech-section .text-green {
    color: #34d399 !important;
}
.sectors-section .text-gold,
.tech-section .text-gold {
    color: var(--dk-accent) !important;
}
.sectors-section .text-cyan,
.tech-section .text-cyan {
    color: #7dd3fc !important;
}

/* Text tuned for the lighter warm surfaces */
.tech-sublabel,
.sectors-section .section-desc,
.tech-section .section-desc {
    color: var(--dk-muted) !important;
}
.tech-section .section-tag,
.sectors-section .section-tag {
    background: rgba(127, 168, 208, 0.16) !important;
    border-color: rgba(127, 168, 208, 0.4) !important;
    color: var(--dk-accent) !important;
}
/* Needs the section prefix and !important: an existing
   .sectors-section .sector-code rule already overrides the base one. */
.sectors-section .sector-code,
.tech-section .sector-code {
    color: var(--dk-accent) !important;
    background: rgba(127, 168, 208, 0.14) !important;
    border-color: rgba(127, 168, 208, 0.32) !important;
}

/* The field either side of the dark pair.
   These were a neutral brown-grey wash, which put a cool, desaturated
   band directly against the dark arc — measured rgb(233,226,221) just
   under the U. Against rgb(59,41,39) that greyness is what made the
   edge read as a cut rather than a transition.
   They are now the same light-orange wash used everywhere else on the
   page, so the arc sits in warm colour on both sides instead of
   meeting a grey. */
[data-mm].mm-atmos-into-dark::after {
    height: 260px;
    background: linear-gradient(180deg,
        rgba(127, 168, 208, 0) 0%,
        rgba(127, 168, 208, 0.14) 46%,
        rgba(127, 168, 208, 0.30) 100%);
}
[data-mm].mm-atmos-outof-dark::after {
    height: 300px;
    background: linear-gradient(0deg,
        rgba(127, 168, 208, 0) 0%,
        rgba(127, 168, 208, 0.14) 42%,
        rgba(127, 168, 208, 0.34) 100%);
}
/* The spill into #training carries #sectors' bottom colour */
[data-mm]#training {
    --curve-fill: var(--dk-bottom);
}
[data-mm]#training .mm-curve {
    background:
        radial-gradient(120% 100% at 50% 0%,
            var(--dk-bottom) 0%,
            rgba(22, 38, 60, 0.97) 62%,
            rgba(22, 38, 60, 0.84) 100%);
}

/* ---------- 29b. Arched top on the pair ----------
   The cream-to-dark boundary was a straight rule. This arches it.
   #tech comes after #solutions in the document, so it is allowed to
   paint upward over it — the reverse would not work. The dome is
   sized to stay inside #solutions' 90px bottom padding, and
   #solutions' .container ends before that padding begins, so no
   content is covered. */
.mm-arch {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 100%;
    height: var(--arch-h, 88px);
    background: var(--dk-top);
    border-radius: 50% 50% 0 0 / 100% 100% 0 0;
    pointer-events: none;
    z-index: 0;
}
@media (max-width: 900px) {
    .mm-arch { --arch-h: 64px; }
}
@media (max-width: 560px) {
    .mm-arch { --arch-h: 46px; }
}

/* ============================================================
   30. #registrations — match the curve to what is actually above
   ------------------------------------------------------------
   Measured step across this boundary was 66.9 in RGB distance:
       6px above  rgb(247,210,184)   (#clients' half-oval field)
       6px below  rgb(253,246,240)   (--atmos-2, the old curve fill)
   That is the cutting line.

   A flat fill cannot close it, because #clients' bottom edge is not
   one colour — the half-oval is a radial, so it reads
       centre  rgb(250,200,166)
       mid     rgb(251,211,184)
       edges   rgb(252,227,209)
   The curve therefore carries a radial with the same horizontal
   falloff, so it meets the field at every x rather than only in
   the middle.
   ============================================================ */
[data-mm]#registrations {
    --curve-h: 150px;
    --curve-h-md: 110px;
    --curve-h-sm: 74px;
}
[data-mm]#registrations .mm-curve {
    background:
        radial-gradient(80% 128% at 50% 0%,
            rgb(188, 214, 235) 0%,
            rgb(205, 225, 241) 30%,
            rgb(220, 234, 246) 56%,
            rgb(236, 244, 251) 78%,
            var(--atmos-3) 100%);
}

/* ============================================================
   31. ABOUT BODY COPY — reference serif
   ------------------------------------------------------------
   The reference sets its manifesto in Louize (205TF), a warm
   contemporary serif — not a licence we have. Lora is the closest
   free equivalent: comparable stroke contrast, similar large
   x-height and open apertures, and a genuine italic rather than a
   slanted roman.

   The reference's other three traits matter as much as the face:
     - size well above UI body copy (this was 14.5px)
     - generous leading, around 1.55 at the larger size
     - a slate rather than a neutral grey (#3A475A on the reference;
       the existing --color-graphite #484758 is already a slate, so
       it is kept)
   ============================================================ */
[data-mm]#about .about-text-group p {
    font-family: 'Lora', 'Playfair Display', Georgia, 'Times New Roman', serif;
    font-size: clamp(16.5px, 1.35vw, 19.5px);
    font-weight: 400;
    line-height: 1.62;
    letter-spacing: 0.1px;
    color: var(--color-graphite);
    margin-bottom: 1.15em;
}
/* The opening paragraph carries the section, so it leads slightly larger. */
[data-mm]#about .about-text-group p:first-child {
    font-size: clamp(17.5px, 1.5vw, 21px);
    line-height: 1.56;
    color: #38465c;
}
/* Quotation marks in the copy are typographic, so let the serif's own
   punctuation do the work rather than the UI font's. */
[data-mm]#about .about-text-group p,
[data-mm]#about .mission-box p {
    font-variant-numeric: oldstyle-nums;
    -webkit-font-smoothing: antialiased;
}
[data-mm]#about .mission-box p {
    font-family: 'Lora', 'Playfair Display', Georgia, 'Times New Roman', serif;
    font-size: clamp(15.5px, 1.2vw, 17.5px);
    font-weight: 400;
    line-height: 1.6;
    color: var(--color-midnight-ink);
}

@media (max-width: 767px) {
    [data-mm]#about .about-text-group p,
    [data-mm]#about .about-text-group p:first-child {
        line-height: 1.66;
    }
}

/* ============================================================
   32. DEPLOYMENT MAP — round frame + cycling zone labels
   ------------------------------------------------------------
   Eight labels held open at once read as a static diagram. Now
   only one is expanded at a time and it advances on a timer, so
   the zone names pop up in sequence.

   Inactive pins collapse to a 28px chip rather than disappearing,
   which keeps every zone clickable and focusable at all times —
   hiding them outright would make 7 of 8 links unreachable
   between ticks.
   ============================================================ */
.mm-tagpanel.is-map {
    aspect-ratio: 1 / 1;
    max-width: 660px;
    border-radius: 50%;
    /* Labels are allowed outside the disc; only the artwork is
       clipped to the circle. */
    overflow: visible;
    background: transparent;
}
@media (max-width: 640px) {
    .mm-tagpanel.is-map { aspect-ratio: 1 / 1; max-width: 340px; }
}

.mm-tagpanel.is-map .mm-map-surface,
.mm-tagpanel.is-map .mm-tagpanel-wash,
.mm-tagpanel.is-map .mm-tagpanel-dots {
    border-radius: 50%;
    -webkit-clip-path: circle(50% at 50% 50%);
    clip-path: circle(50% at 50% 50%);
}
/* Rim */
.mm-tagpanel.is-map::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 50%;
    border: 1px solid rgba(127, 168, 208, 0.38);
    box-shadow: 0 0 0 10px rgba(127, 168, 208, 0.05),
                0 18px 50px rgba(20, 28, 42, 0.10);
    pointer-events: none;
    z-index: 2;
}

/* Radar sweep across the disc */
.mm-map-sweep {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    -webkit-clip-path: circle(50% at 50% 50%);
    clip-path: circle(50% at 50% 50%);
    background: conic-gradient(from 0deg,
        rgba(127, 168, 208, 0.30) 0deg,
        rgba(127, 168, 208, 0.10) 26deg,
        rgba(127, 168, 208, 0) 58deg,
        rgba(127, 168, 208, 0) 360deg);
    animation: mmSweep 7s linear infinite;
    pointer-events: none;
    z-index: 1;
    opacity: 0;
    transition: opacity 1s var(--ease-out-expo);
}
.mm-tagpanel.mm-in .mm-map-sweep { opacity: 1; }
@keyframes mmSweep { to { transform: rotate(360deg); } }

/* Expanding rings from the centre */
.mm-map-pulse {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 30%;
    height: 30%;
    margin: -15% 0 0 -15%;
    border-radius: 50%;
    border: 1px solid rgba(127, 168, 208, 0.5);
    pointer-events: none;
    z-index: 1;
    opacity: 0;
}
.mm-tagpanel.mm-in .mm-map-pulse { animation: mmMapPulse 4.4s var(--ease-out-quad) infinite; }
.mm-tagpanel.mm-in .mm-map-pulse.p2 { animation-delay: 1.47s; }
.mm-tagpanel.mm-in .mm-map-pulse.p3 { animation-delay: 2.94s; }
@keyframes mmMapPulse {
    0%   { opacity: 0.75; transform: scale(0.2); }
    80%  { opacity: 0;    transform: scale(3.2); }
    100% { opacity: 0;    transform: scale(3.2); }
}

/* ---- pin states ----
   Closed, the pin is a 30px disc centred ON the coordinate, so the dot
   and the hit target are the same thing — a 14px empty chip sitting
   beside its dot both looked wrong and fell under the 24px minimum
   touch size. Open, it re-anchors to the left edge and grows rightward,
   which is what produces the pop-out. Both `translate` and
   `margin-left` transition, so the two anchorings animate between. */
.mm-mappin {
    gap: 0;
    width: 30px;
    height: 30px;
    padding: 0;
    margin-left: 0;
    border-radius: 50%;
    translate: -50% -50%;
    justify-content: center;
    transition: opacity 0.42s var(--ease-out-quad),
                scale 0.52s var(--ease-out-back),
                translate 0.5s var(--ease-out-expo),
                margin-left 0.5s var(--ease-out-expo),
                width 0.5s var(--ease-out-expo),
                padding 0.5s var(--ease-out-expo),
                border-radius 0.4s var(--ease-out-expo),
                box-shadow var(--mm-hover) var(--ease-out-expo),
                background-color var(--mm-hover) var(--ease-out-expo),
                color var(--mm-hover) var(--ease-out-expo);
    z-index: 4;
}
.mm-mappin.is-left { margin-right: 0; }

/* the dot is centred while closed */
.mm-mappin .mm-mappin-dot,
.mm-mappin.is-left .mm-mappin-dot {
    left: 50%;
    right: auto;
    top: 50%;
    margin: -7px 0 0 -7px;
}

/* opened by the cycle, by hover, or by keyboard focus */
.mm-mappin.is-on,
.mm-mappin:focus-visible {
    width: auto;
    height: auto;
    border-radius: var(--radius-full);
    translate: 0 -50%;
    margin-left: 22px;
    padding: 7px 14px 7px 12px;
}
.mm-mappin.is-on.is-left,
.mm-mappin.is-left:focus-visible {
    translate: -100% -50%;
    margin-left: 0;
    margin-right: 22px;
}
.mm-mappin.is-on .mm-mappin-dot,
.mm-mappin:focus-visible .mm-mappin-dot {
    left: -29px;
    top: 50%;
    margin: -7px 0 0 0;
}
.mm-mappin.is-on.is-left .mm-mappin-dot,
.mm-mappin.is-left:focus-visible .mm-mappin-dot {
    left: auto;
    right: -29px;
}

.mm-mappin .mm-mappin-label {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    max-width: 0;
    opacity: 0;
    overflow: hidden;
    white-space: nowrap;
    transition: max-width 0.55s var(--ease-out-expo),
                opacity 0.32s var(--ease-out-quad),
                margin-left 0.55s var(--ease-out-expo);
}
.mm-mappin.is-on {
    z-index: 7;
    box-shadow: 0 10px 26px rgba(20, 28, 42, 0.22);
}
.mm-mappin.is-on .mm-mappin-label,
.mm-mappin:focus-visible .mm-mappin-label {
    max-width: 300px;
    opacity: 1;
    margin-left: 2px;
}
.mm-mappin.is-on .mm-mappin-dot {
    transform: scale(1.3);
}
@media (hover: hover) and (pointer: fine) {
    .mm-mappin:hover {
        width: auto;
        height: auto;
        border-radius: var(--radius-full);
        translate: 0 -50%;
        margin-left: 22px;
        padding: 7px 14px 7px 12px;
        z-index: 8;
    }
    .mm-mappin.is-left:hover {
        translate: -100% -50%;
        margin-left: 0;
        margin-right: 22px;
    }
    .mm-mappin:hover .mm-mappin-dot { left: -29px; top: 50%; margin: -7px 0 0 0; }
    .mm-mappin.is-left:hover .mm-mappin-dot { left: auto; right: -29px; }
    .mm-mappin:hover .mm-mappin-label {
        max-width: 300px;
        opacity: 1;
        margin-left: 2px;
    }
}

/* Caption moves below the disc now that the frame is round */
.mm-tagpanel.is-map .mm-map-note {
    bottom: auto;
    top: 100%;
    margin-top: 18px;
    justify-content: center;
    gap: 22px;
    padding: 0;
    background: none;
}

/* Labels are back on for phones. Section 23 hid them there because
   eight open at once could not tile on a 340px disc — with one open at
   a time that no longer applies, and the disc no longer clips them. */
@media (max-width: 640px) {
    .mm-tagpanel.is-map .mm-mappin-label { display: inline-flex; }
    .mm-tagpanel.is-map .mm-mappin-label i,
    .mm-tagpanel.is-map .mm-mappin-code { display: inline-flex; }
    .mm-tagpanel.is-map .mm-mappin { width: 30px; height: 30px; font-size: 10.5px; }
    .mm-tagpanel.is-map .mm-mappin.is-on,
    .mm-tagpanel.is-map .mm-mappin:focus-visible {
        width: auto;
        height: auto;
        padding: 5px 11px 5px 9px;
        margin-left: 17px;
    }
    .mm-tagpanel.is-map .mm-mappin.is-on.is-left,
    .mm-tagpanel.is-map .mm-mappin.is-left:focus-visible {
        margin-left: 0;
        margin-right: 17px;
    }
    .mm-tagpanel.is-map .mm-mappin.is-on .mm-mappin-dot { left: -23px; }
    .mm-tagpanel.is-map .mm-mappin.is-on.is-left .mm-mappin-dot { left: auto; right: -23px; }
    .mm-tagpanel.is-map .mm-mappin-code { font-size: 8.5px; }
}

@media (prefers-reduced-motion: reduce) {
    .mm-map-sweep,
    .mm-map-pulse { display: none; }
    .mm-mappin .mm-mappin-label {
        max-width: 300px !important;
        opacity: 1 !important;
        transition: none !important;
    }
    .mm-mappin {
        width: auto !important;
        height: auto !important;
        border-radius: var(--radius-full) !important;
        translate: 0 -50% !important;
        margin-left: 22px !important;
        padding: 7px 14px 7px 12px !important;
    }
    .mm-mappin.is-left {
        translate: -100% -50% !important;
        margin-left: 0 !important;
        margin-right: 22px !important;
    }
    .mm-mappin .mm-mappin-dot { left: -29px !important; top: 50%; margin: -7px 0 0 0 !important; }
    .mm-mappin.is-left .mm-mappin-dot { left: auto !important; right: -29px !important; }
}

/* ============================================================
   33. SPINNABLE GLOBE  (supersedes 32's flat disc)
   ------------------------------------------------------------
   Removed: the radar sweep wedge, the centre pulse rings, and the
   white dot grid — all three were reading as clutter over the map.

   The globe is a CSS sphere rather than WebGL: the Bengaluru map
   tiles horizontally as a texture and its background-position is
   driven by js/motion.js, which is what makes the surface turn.
   Shading and limb darkening supply the volume.

   Honest about the projection: the eight sites are all within
   ~40km of each other, so their true longitudes would occupy under
   half a degree and the globe would have nothing to reveal as it
   turned. Their longitudes are spread deliberately so each zone
   comes round in sequence. Latitudes keep their real north/south
   ordering. It is a stylised globe, not a survey.
   ============================================================ */
.mm-tagpanel.is-globe {
    position: relative;
    width: min(390px, 78vw);
    max-width: none;
    aspect-ratio: 1 / 1;
    margin: 0 auto 96px;
    background: none;
    border-radius: 50%;
    overflow: visible;
}
.mm-tagpanel.is-globe::after { content: none; }

@media (max-width: 640px) {
    .mm-tagpanel.is-globe { width: min(250px, 68vw); margin-bottom: 84px; }
}

.mm-globe {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    cursor: grab;
    touch-action: pan-y;
    -webkit-tap-highlight-color: transparent;
}
.mm-globe.is-dragging { cursor: grabbing; }

/* Map texture. repeat-x + a background-position driven from JS is the
   rotation; a sphere needs no 3D transform for this read. */
.mm-globe-tex {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background-image: url('../assets/bengaluru_map.svg');
    background-repeat: repeat-x;
    background-size: auto 118%;
    background-position: 0 52%;
    filter: saturate(0.75) contrast(1.02);
    opacity: 0.92;
    -webkit-clip-path: circle(50% at 50% 50%);
    clip-path: circle(50% at 50% 50%);
}
/* Meridians and parallels, so the turn is legible */
.mm-globe-grid {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    -webkit-clip-path: circle(50% at 50% 50%);
    clip-path: circle(50% at 50% 50%);
    background-image:
        repeating-linear-gradient(90deg, rgba(20, 28, 42, 0.075) 0 1px, transparent 1px 12.5%),
        repeating-linear-gradient(0deg, rgba(20, 28, 42, 0.06) 0 1px, transparent 1px 16.6%);
    opacity: 0.85;
    pointer-events: none;
}
/* Volume: highlight top-left, limb darkening at the edge */
.mm-globe-shade {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    pointer-events: none;
    background:
        radial-gradient(circle at 32% 26%,
            rgba(255, 255, 255, 0.60) 0%,
            rgba(255, 255, 255, 0.22) 30%,
            rgba(255, 255, 255, 0) 54%),
        radial-gradient(circle at 50% 50%,
            rgba(127, 168, 208, 0) 52%,
            rgba(74, 116, 158, 0.16) 78%,
            rgba(35, 64, 94, 0.30) 100%);
}
.mm-globe-rim {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    pointer-events: none;
    border: 1px solid rgba(127, 168, 208, 0.4);
    box-shadow: 0 0 0 9px rgba(127, 168, 208, 0.05),
                0 20px 46px rgba(20, 28, 42, 0.13),
                inset 0 0 22px rgba(255, 255, 255, 0.35);
}

/* Pins: left/top are written each frame by the projection, so no
   transition on position — only on the open/close of the label. */
.mm-tagpanel.is-globe .mm-mappin {
    left: 50%;
    top: 50%;
    transition: opacity 0.25s linear,
                width 0.45s var(--ease-out-expo),
                padding 0.45s var(--ease-out-expo),
                border-radius 0.35s var(--ease-out-expo),
                margin-left 0.45s var(--ease-out-expo),
                margin-right 0.45s var(--ease-out-expo),
                box-shadow var(--mm-hover) var(--ease-out-expo),
                background-color var(--mm-hover) var(--ease-out-expo),
                color var(--mm-hover) var(--ease-out-expo);
}
/* round the back of the sphere */
.mm-tagpanel.is-globe .mm-mappin.is-behind {
    opacity: 0;
    pointer-events: none;
}

.mm-tagpanel.is-globe .mm-map-note {
    top: 100%;
    margin-top: 22px;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    text-align: center;
}
.mm-map-hint {
    font-size: 10.5px;
    letter-spacing: 0.6px;
    color: var(--color-pebble);
}

@media (prefers-reduced-motion: reduce) {
    /* No spin. The globe holds still with the front hemisphere shown. */
    .mm-globe { cursor: default; }
}

/* ============================================================
   34. GLOBE — actual sphere shading
   ------------------------------------------------------------
   The previous version read as a rolling cylinder for two
   reasons, both now fixed:

     1. The texture scrolled linearly. On a sphere the surface
        compresses toward the limbs, and a constant scroll rate
        is the single clearest sign that it is not one.
     2. The graticule was a flat repeating lattice. Real meridians
        bunch together at the edges and parallels curve, and that
        is the strongest depth cue available.

   js/motion.js now projects the graticule properly through a
   tilted orthographic projection, so meridians narrow and crowd
   at the limbs as they turn. The tilt is what lets parallels read
   as ellipses at all — viewed square-on from the equator they
   would project to straight horizontal lines.

   The layers below supply the volume: body gradient, terminator,
   specular highlight, rim light and an atmosphere halo.
   ============================================================ */

/* soft halo, sits outside the disc */
.mm-globe-atmo {
    position: absolute;
    inset: -9%;
    border-radius: 50%;
    background: radial-gradient(circle at 50% 50%,
        rgba(127, 168, 208, 0) 52%,
        rgba(127, 168, 208, 0.16) 68%,
        rgba(127, 168, 208, 0.07) 82%,
        rgba(127, 168, 208, 0) 100%);
    pointer-events: none;
}

/* the sphere itself: lit from upper-left, falling away to the lower-right */
.mm-globe-body {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background:
        radial-gradient(circle at 34% 28%,
            #fbfdff 0%,
            #e9f2fb 26%,
            #c8dcf0 52%,
            #94b8d8 74%,
            #4f7aa6 92%,
            #2c4f74 100%);
    pointer-events: none;
}

/* map surface, kept subtle now that the graticule carries the read */
.mm-globe-tex {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background-image: url('../assets/bengaluru_map.svg');
    background-repeat: repeat-x;
    background-size: auto 128%;
    background-position: 0 50%;
    filter: saturate(0.5) contrast(1.1);
    opacity: 0.3;
    mix-blend-mode: multiply;
    -webkit-clip-path: circle(50% at 50% 50%);
    clip-path: circle(50% at 50% 50%);
    pointer-events: none;
}

.mm-globe-wire {
    position: absolute;
    inset: 0;
    overflow: visible;
    pointer-events: none;
}
.mm-globe-wire .g-mer,
.mm-globe-wire .g-par {
    fill: none;
    stroke: rgba(26, 50, 80, 0.30);
    stroke-width: 0.7;
    vector-effect: non-scaling-stroke;
}
.mm-globe-wire .g-par { stroke: rgba(26, 50, 80, 0.24); }
.mm-globe-wire .g-eq {
    stroke: rgba(47, 84, 118, 0.5);
    stroke-width: 1.1;
}

/* terminator — the shadow rolling off toward the lower-right limb */
.mm-globe-shade {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    pointer-events: none;
    background:
        radial-gradient(circle at 33% 27%,
            rgba(12, 30, 52, 0) 30%,
            rgba(12, 30, 52, 0.10) 58%,
            rgba(10, 26, 46, 0.34) 82%,
            rgba(8, 20, 38, 0.55) 100%);
}
/* specular highlight */
.mm-globe-spec {
    position: absolute;
    left: 14%;
    top: 9%;
    width: 42%;
    height: 32%;
    border-radius: 50%;
    pointer-events: none;
    background: radial-gradient(ellipse at 50% 50%,
        rgba(255, 255, 255, 0.72) 0%,
        rgba(255, 255, 255, 0.28) 40%,
        rgba(255, 255, 255, 0) 72%);
    filter: blur(3px);
}
.mm-globe-rim {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    pointer-events: none;
    /* bright crescent along the lit limb, dark on the shadowed side */
    background: linear-gradient(138deg,
        rgba(255, 255, 255, 0.5) 0%,
        rgba(255, 255, 255, 0) 22%,
        rgba(255, 255, 255, 0) 78%,
        rgba(32, 58, 90, 0.28) 100%);
    box-shadow:
        inset 0 0 14px rgba(234, 244, 253, 0.30),
        inset -10px -12px 30px rgba(14, 32, 54, 0.34),
        0 26px 44px -14px rgba(24, 48, 78, 0.42);
    border: 1px solid rgba(47, 84, 118, 0.34);
}

/* pins get a touch of depth: nearer the front reads slightly larger,
   written per frame by the projection */
.mm-tagpanel.is-globe .mm-mappin-dot {
    box-shadow: 0 0 0 2.5px #ffffff, 0 3px 7px rgba(10, 26, 46, 0.5);
    /* --pin-depth is written per frame from the z of the projection, so
       markers near the front of the sphere sit slightly larger.
       `scale` rather than `transform`, which .is-on already uses. */
    scale: var(--pin-depth, 1);
}

/* ============================================================
   35. REORDER — sectors moved between why-choose and solutions
   ------------------------------------------------------------
   New order: about -> why-choose -> sectors -> solutions ->
   technologies -> training -> ...

   This splits the two dark sections apart. They used to be
   adjacent and were treated as one block: #tech carried the top
   half of a shared gradient and #sectors the bottom half, with a
   single arch above and a single spill below. Now each is its own
   island between light sections, so each needs a full gradient,
   its own arch on top and its own spill underneath.

   It also means #solutions now has dark ABOVE and BELOW it. The
   old feathers used ::after on the section, so one element could
   only ever carry one of them — section 36 replaces them with an
   injected layer that holds both.
   ============================================================ */

/* each dark island now spans the full tonal range on its own */
.tech-section,
.sectors-section {
    background: linear-gradient(180deg, var(--dk-top) 0%, var(--dk-bottom) 100%) !important;
}

/* the spill out of #sectors lands in #solutions */
[data-mm]#solutions {
    --curve-fill: var(--dk-bottom);
    --curve-h: 96px;
    --curve-h-md: 70px;
    --curve-h-sm: 50px;
}
[data-mm]#solutions .mm-curve {
    background:
        radial-gradient(120% 100% at 50% 0%,
            var(--dk-bottom) 0%,
            rgba(22, 38, 60, 0.97) 62%,
            rgba(22, 38, 60, 0.84) 100%);
}

/* ============================================================
   36. DARK-EDGE FIELDS as an injected layer
   ------------------------------------------------------------
   Replaces .mm-atmos-into-dark / .mm-atmos-outof-dark, which both
   used ::after and so could not coexist on one section.
   js/motion.js injects .mm-field-fx into every [data-mm] section;
   the two classes below decide which edges light up.
   ============================================================ */
.mm-field-fx {
    position: absolute;
    inset: 0;
    overflow: hidden;
    pointer-events: none;
    z-index: 0;
}
.mm-field-fx > i {
    position: absolute;
    left: 0;
    right: 0;
    display: none;
}
/* bottom edge runs into a dark section below */
[data-mm].mm-above-dark .mm-field-bot {
    display: block;
    bottom: 0;
    height: 260px;
    background: linear-gradient(180deg,
        rgba(127, 168, 208, 0) 0%,
        rgba(127, 168, 208, 0.14) 46%,
        rgba(127, 168, 208, 0.30) 100%);
}
/* top edge comes out of a dark section above */
[data-mm].mm-below-dark .mm-field-top {
    display: block;
    top: 0;
    height: 300px;
    background: linear-gradient(0deg,
        rgba(127, 168, 208, 0) 0%,
        rgba(127, 168, 208, 0.14) 42%,
        rgba(127, 168, 208, 0.34) 100%);
}
/* the curve sits above the field so the boundary edge stays crisp */
[data-mm].has-curve .mm-curve { z-index: 1; }
[data-mm].has-curve > .container { z-index: 2; }

/* ============================================================
   37. PALETTE — cool
   ------------------------------------------------------------
   Colour only. No geometry, motion, timing, easing, layout or
   effect is touched by this section — the arches, curves, globe,
   ticker arc, reveals and depth all keep their existing values.

   Mapping:
     light orange  ->  light blue    (soft steel, #7fa8d0)
     brown darks   ->  dark blue     (#1e3350 -> #16263c)
     warm creams   ->  cool tints    (#f8fbfe -> #ecf3fa)
     plus white, and gold used sparingly

   Every shade is deliberately desaturated. A fully saturated blue
   at these sizes reads as a "sharp" tone and fights the soft
   blends the layout depends on, so saturation is kept in the
   25-45% range and the steps between tints stay under 6 units per
   channel.

   These override the tokens declared in styles.css. Because that
   file references them through var(), redefining them here
   recolours every rule that uses them without editing it.
   ============================================================ */
:root {
    /* ---- accent: light blue ---- */
    --color-tangerine-tag: #7fa8d0;
    --gold: #7fa8d0;
    --gold-light: #a6c4e2;
    --gold-dark: #4e7ba8;
    --gold-50: rgba(127, 168, 208, 0.08);
    --gold-100: rgba(127, 168, 208, 0.15);
    --gold-200: rgba(127, 168, 208, 0.28);
    --gold-300: rgba(127, 168, 208, 0.45);
    --shadow-gold: 0 6px 20px rgba(127, 168, 208, 0.28);
    --border-hover: #7fa8d0;

    /* ---- true gold, used sparingly ---- */
    --au: #c9a96a;
    --au-light: #dcc391;
    --au-dark: #9c7f45;

    /* ---- neutrals, cooled ---- */
    --color-midnight-ink: #141c2a;
    --color-graphite: #42505f;
    --color-stone: #5c6675;
    --color-pebble: #8d97a5;
    --color-ash: #869099;
    --color-fog: #f4f7fb;
    --color-deep-slate: #27405f;
    --border: rgba(20, 28, 42, 0.09);
    --shadow-sm: 0 1px 3px rgba(20, 28, 42, 0.04), 0 2px 6px rgba(20, 28, 42, 0.03);
    --shadow-md: rgba(20, 28, 42, 0.08) 0px 4px 12px -2px;
    --shadow-lg: rgba(20, 28, 42, 0.12) 0px 10px 24px -4px;
    --shadow-subtle: rgba(20, 28, 42, 0.10) 0px 2px 5px -2px;

    /* ---- the orphaned palette tokens from section 25 ---- */
    --deep-plum: #16263c;
    --aubergine: #1a2c46;
    --periwinkle: rgba(226, 238, 250, 0.10);
    --cream-mist: #eef4fa;
    --lavender-mist: rgba(255, 255, 255, 0.28);
    --mint-keyhole: #7fa8d0;
    --iris: #10b981;
    --warm-sand: rgba(20, 28, 42, 0.12);
}

/* ---- gold, applied only in the few places it earns its keep ---- */
.mission-tag {
    color: var(--au-dark) !important;
}
[data-mm] .mission-box {
    border-left-color: var(--au) !important;
}
.mm-statement-line em {
    color: var(--au-dark);
}
.mm-statement.on-field .mm-statement-line em {
    color: var(--au-light);
}
/* the globe's equator is the one line that should read warm */
.mm-globe-wire .g-eq {
    stroke: rgba(201, 169, 106, 0.6);
}
.mm-marquee-item i {
    color: var(--au);
}

/* Sector icons keep a hint of gold so the grid is not wall-to-wall blue.
   Needs !important: styles.css sets these with !important of its own. */
.sectors-section .sector-icon {
    background: rgba(201, 169, 106, 0.13) !important;
    border-color: rgba(201, 169, 106, 0.30) !important;
}
.sectors-section .sector-icon i {
    color: var(--au-light) !important;
}

/* ---- contrast corrections to the new palette ----
   Measured against the real composited backdrop:
     MISSION tag   #9c7f45 on white = 3.79:1  (11px needs 4.5)
     SECURED badge #5c9c86 on white = 3.20:1  (11px needs 4.5)
   Both are my own new shades, so both are corrected here.       */
:root {
    --au-dark: #7d6534;   /* 5.1:1 on white */
}
/* The softened green reads fine as a dot at 3:1, but as 11px text on a
   white client card it needs to be darker. Scoped so the dark sections
   keep their lighter green, which needs to stay light to work there. */
.clients-section .cc-status {
    color: #3f7a66 !important;
}

/* ============================================================
   38. SECTOR FLASH CARD
   ------------------------------------------------------------
   Clicking a sector card opens a detail panel. Not scoped to
   [data-mm] because #sectors deliberately carries no [data-mm]
   attribute — the whole point of that exclusion was to leave
   the section's own reveal/canvas behaviour alone, and this is
   a new overlay rather than a change to it.

   Surface is --dk-card (#27405f), the same raised value the
   sector cards themselves use, so the panel reads as one of
   them enlarged rather than as a foreign element.

   Motion reuses the existing language: 0.7s ease-out-expo on
   the scrim, ease-out-back on the panel so it overshoots once
   and settles, matching section 9's pop vocabulary.
   ============================================================ */

.sf-scrim {
    position: fixed;
    inset: 0;
    z-index: 4000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background: rgba(11, 18, 30, 0.62);
    -webkit-backdrop-filter: blur(6px) saturate(1.1);
    backdrop-filter: blur(6px) saturate(1.1);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.42s var(--ease-out-expo),
                visibility 0s linear 0.42s;
}
.sf-scrim.is-open {
    opacity: 1;
    visibility: visible;
    transition: opacity 0.42s var(--ease-out-expo),
                visibility 0s linear 0s;
}

.sf-card {
    position: relative;
    width: min(760px, 100%);
    max-height: min(86vh, 860px);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 34px 34px 30px;
    background: var(--dk-card);
    border: 1px solid var(--dk-line);
    /* Same U-shape family as the section boundaries, at card scale. */
    border-radius: 22px;
    box-shadow: 0 34px 90px rgba(6, 12, 22, 0.55);
    opacity: 0;
    transform: translateY(26px) scale(0.94);
    transition: opacity 0.42s var(--ease-out-quad),
                transform 0.62s var(--ease-out-back);
}
.sf-scrim.is-open .sf-card {
    opacity: 1;
    transform: translateY(0) scale(1);
}

/* Gold hairline across the top only — a full border would put a
   bright rectangle around the panel, which the rest of the page
   never does. */
.sf-card::before {
    content: "";
    position: absolute;
    top: 0;
    left: 26px;
    right: 26px;
    height: 2px;
    border-radius: 0 0 2px 2px;
    background: linear-gradient(90deg,
        rgba(201, 169, 106, 0) 0%,
        rgba(201, 169, 106, 0.85) 30%,
        rgba(201, 169, 106, 0.85) 70%,
        rgba(201, 169, 106, 0) 100%);
    pointer-events: none;
}

.sf-head {
    display: flex;
    align-items: center;
    gap: 16px;
    padding-right: 40px;
}
.sf-icon {
    flex: 0 0 auto;
    width: 54px;
    height: 54px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 14px;
    background: rgba(201, 169, 106, 0.13);
    border: 1px solid rgba(201, 169, 106, 0.30);
}
.sf-icon i {
    font-size: 22px;
    color: var(--au-light);
}
.sf-code {
    display: block;
    font-family: 'Barlow Condensed', 'Oswald', sans-serif;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 2px;
    text-transform: uppercase;
    /* 5.6:1 on #27405f */
    color: #a9c4de;
    margin-bottom: 2px;
}
.sf-title {
    margin: 0;
    font-family: 'Barlow Condensed', 'Oswald', sans-serif;
    font-size: clamp(24px, 3.4vw, 31px);
    font-weight: 800;
    line-height: 1.08;
    letter-spacing: 0.4px;
    color: #ffffff;
}

/* Body copy. The bullet list this replaced held four short
   fragments; the real sector copy is two to three full
   paragraphs, so the panel is now a reading surface. */
.sf-body {
    margin: 22px 0 0;
}
.sf-body p {
    margin: 0 0 1.05em;
    font-size: 15px;
    line-height: 1.72;
    /* 8.0:1 on #27405f */
    color: #dbe7f3;
}
.sf-body p:last-child {
    margin-bottom: 0;
}
/* The opening paragraph carries the sector, so it leads slightly
   larger - the same relationship #about uses for its first para. */
.sf-body p:first-child {
    font-size: 16px;
    line-height: 1.68;
    color: #e8f1fa;
}
/* Paragraphs stagger in behind the panel, which is the interior
   depth idea from section 18 at a much smaller scale. */
.sf-scrim.is-open .sf-body p {
    animation: sfPara 0.5s var(--ease-out-expo) both;
    animation-delay: calc(0.18s + var(--sf-i, 0) * 0.09s);
}
@keyframes sfPara {
    from { opacity: 0; transform: translateY(10px); }
    to   { opacity: 1; transform: translateY(0); }
}
.sf-close {
    position: absolute;
    top: 16px;
    right: 16px;
    width: 38px;
    height: 38px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--dk-line);
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.06);
    color: #dbe7f3;
    font-size: 15px;
    cursor: pointer;
    transition: background-color var(--mm-hover) var(--ease-out-expo),
                border-color var(--mm-hover) var(--ease-out-expo),
                color var(--mm-hover) var(--ease-out-expo),
                transform var(--mm-hover) var(--ease-out-expo);
}
.sf-close:hover {
    background: rgba(201, 169, 106, 0.18);
    border-color: rgba(201, 169, 106, 0.42);
    color: #ffffff;
    transform: rotate(90deg);
}
.sf-close:focus-visible,
.sector-card:focus-visible {
    outline: 2px solid var(--au-light);
    outline-offset: 3px;
}

/* The cards become buttons, so say so to the pointer. */
.sector-card[role="button"] {
    cursor: pointer;
}

@media (max-width: 600px) {
    .sf-scrim { padding: 14px; }
    .sf-card { padding: 28px 22px 24px; border-radius: 18px; }
    .sf-icon { width: 46px; height: 46px; border-radius: 12px; }
    .sf-icon i { font-size: 19px; }
}

@media (prefers-reduced-motion: reduce) {
    .sf-scrim,
    .sf-card {
        transition: none !important;
    }
    .sf-card {
        transform: none !important;
    }
    .sf-scrim.is-open .sf-body p {
        animation: none !important;
    }
    .sf-close:hover {
        transform: none !important;
    }
}

/* ============================================================
   39. SECTORS GRID — a fixed 5 × 3
   ------------------------------------------------------------
   styles.css used `repeat(auto-fill, minmax(175px, 1fr))`, which
   at container width fitted 6 columns and left the last row with
   3 orphans. 15 cards divide evenly by 5, so pinning the count
   gives three full rows with no ragged edge.

   Scoped to min-width 769px so the existing 3-column (â‰¤768px)
   and 2-column (â‰¤480px) rules in styles.css still apply — an
   unconditional rule here would win on load order and force
   five 40px-wide cards onto a phone.
   ============================================================ */
@media (min-width: 769px) {
    .tactical-sectors-grid {
        grid-template-columns: repeat(5, 1fr);
    }
}

/* ============================================================
   40. ARCH CLEARANCE
   ------------------------------------------------------------
   Measured, not guessed: with 90px of bottom padding on the
   section above, the 88px dome filled the whole padding band and
   its apex landed 2px under the last content box. That is why the
   arch read as touching "Mobile app for updates…" in #why-choose
   and the last solution card in #solutions.

   Fix is padding, not arch geometry — shrinking the dome would
   flatten the curve the rest of the page is built around. Each
   value is the arch height for that breakpoint plus a deliberate
   gap, so the clearance survives the two arch-height overrides
   in section 29b.

     desktop  88 + 44 = 132px
     â‰¤900px   64 + 34 =  98px
     â‰¤560px   46 + 26 =  72px
   ============================================================ */
[data-mm].mm-above-dark {
    padding-bottom: 132px;
}
@media (max-width: 900px) {
    [data-mm].mm-above-dark { padding-bottom: 98px; }
}
@media (max-width: 560px) {
    [data-mm].mm-above-dark { padding-bottom: 72px; }
}

/* ============================================================
   41. NAV — keep the logo clear of the links when scrolled
   ------------------------------------------------------------
   Measured in the scrolled pill state at 1440px wide:

     .nav-container inner width      896px
     .nav-logo + .nav-links need     926px
     logo content right edge         x=381
     first link ("About") left edge  x=355

   So the two overlapped by 26px, and the logo wordmark printed
   underneath "About". The logo's flex box measures only 80px
   while its wordmark renders to 106px, so `space-between` saw
   no shortfall and never pushed them apart.

   Three changes, each doing part of the work:
     - the pill gets 60px wider (980 -> 1040), still inside the
       calc(100vw - 32px) cap at the 1081px breakpoint where the
       links first appear
     - a real 26px gap, so flex can never let them touch again
     - links tightened slightly (padding 12->10, gap 6->5) to buy
       back 33px of headroom for font-loading variance

   The logo is also pinned to flex: 0 0 auto. It must never be
   the element that shrinks, since its overflow is what caused
   the collision in the first place.
   ============================================================ */
.navbar.scrolled {
    width: 1040px;
}
.nav-container {
    gap: 26px;
}
.nav-logo {
    flex: 0 0 auto;
}
.nav-links {
    gap: 5px;
    flex: 0 1 auto;
    min-width: 0;
}
.nav-link {
    padding: 6px 10px;
    white-space: nowrap;
}

/* Below the pill breakpoint the links are display:none anyway, so
   the narrower pill is restored to avoid a wide empty bar. */
@media (max-width: 1080px) {
    .navbar.scrolled {
        width: 980px;
    }
}

/* ============================================================
   42. RESPONSIVE NAV — desktop bar, mobile side drawer
   ------------------------------------------------------------
   Desktop (>=1081px) keeps the horizontal pill exactly as it is.
   Below that, styles.css already swaps .nav-links out for the
   hamburger; what it opened was a full-width panel sliding DOWN
   from under the navbar in rgba(5,10,21,0.98) — a near-black
   left over from before the cool palette, and a shape that
   covers the page rather than sitting beside it.

   This turns it into a right-hand drawer: slides in from the
   edge, sized to the content rather than the viewport, over a
   dimmed scrim. The palette is the same dark-blue gradient the
   #tech and #sectors islands use, so the drawer belongs to the
   page instead of introducing a third dark value.

   Closed state is visibility:hidden + pointer-events:none, not
   just an off-screen transform. A fixed element translated past
   the right edge can still extend the scroll width on mobile
   engines, and this page is already fighting an 8px overflow.
   ============================================================ */
@media (max-width: 1080px) {
    .mobile-menu {
        /* anchored to the right edge, full height */
        top: 0;
        right: 0;
        left: auto;
        bottom: 0;
        width: min(84vw, 340px);
        max-height: none;
        height: 100%;
        padding: 84px 20px 28px;
        gap: 4px;
        overflow-y: auto;
        overscroll-behavior: contain;
        -webkit-overflow-scrolling: touch;

        background: linear-gradient(180deg, var(--dk-top) 0%, var(--dk-bottom) 100%);
        border-bottom: 0;
        /* gold hairline on the leading edge only — the one place the
           drawer needs to read as separate from the page behind it */
        border-left: 1px solid rgba(201, 169, 106, 0.34);
        box-shadow: -18px 0 54px rgba(6, 12, 22, 0.34);
        z-index: 1201;

        transform: translateX(100%);
        opacity: 1;
        visibility: hidden;
        pointer-events: none;
        transition: transform 0.52s var(--ease-out-expo),
                    visibility 0s linear 0.52s;
    }
    .mobile-menu.open {
        transform: translateX(0);
        visibility: visible;
        pointer-events: auto;
        transition: transform 0.52s var(--ease-out-expo),
                    visibility 0s linear 0s;
    }

    /* Links rise in behind the panel, staggered. Same idea as the
       sector flash card's bullets, at drawer scale. */
    .mobile-menu > * {
        opacity: 0;
        transform: translateX(18px);
        transition: opacity 0.4s var(--ease-out-quad),
                    transform 0.5s var(--ease-out-expo);
    }
    .mobile-menu.open > * {
        opacity: 1;
        transform: translateX(0);
        /* --mm-i is the child's index, written by js/motion.js. Using a
           variable rather than nth-child means the stagger does not have
           to be re-numbered whenever a link is added or removed. If the
           script does not run, --mm-i falls back to 0 and the whole list
           simply arrives together — the drawer still works. */
        transition-delay: calc(0.10s + var(--mm-i, 0) * 0.05s);
    }

    .mobile-link {
        display: flex;
        align-items: center;
        gap: 12px;
        padding: 14px 14px;
        font-size: 13.5px;
        letter-spacing: 1.4px;
        /* #94a3b8 measured 5.08:1 on the new gradient — passing but
           dim for a primary navigation target. This is 11.1:1. */
        color: #dbe7f3;
        border-radius: 12px;
        border: 1px solid transparent;
    }
    .mobile-link:hover,
    .mobile-link:focus-visible {
        color: #ffffff;
        background: rgba(201, 169, 106, 0.14);
        border-color: rgba(201, 169, 106, 0.30);
        /* the old rule shifted padding-left, which made the row jump */
        padding-left: 14px;
    }
    .mobile-link.is-current {
        color: #ffffff;
        background: rgba(127, 168, 208, 0.16);
        border-color: rgba(127, 168, 208, 0.34);
    }
    /* leading dot, mirroring the desktop nav-link dots */
    .mobile-link::before {
        content: "";
        flex: 0 0 auto;
        width: 6px;
        height: 6px;
        border-radius: 50%;
        background: var(--au);
        opacity: 0.75;
        transition: transform 0.25s var(--ease-out-back), opacity 0.25s ease;
    }
    .mobile-link:hover::before,
    .mobile-link.is-current::before {
        transform: scale(1.5);
        opacity: 1;
    }

    /* Scrim, injected by js/motion.js */
    .mm-drawer-scrim {
        position: fixed;
        inset: 0;
        z-index: 1200;
        background: rgba(11, 18, 30, 0.52);
        -webkit-backdrop-filter: blur(3px);
        backdrop-filter: blur(3px);
        opacity: 0;
        visibility: hidden;
        transition: opacity 0.42s var(--ease-out-expo),
                    visibility 0s linear 0.42s;
    }
    .mm-drawer-scrim.is-open {
        opacity: 1;
        visibility: visible;
        transition: opacity 0.42s var(--ease-out-expo),
                    visibility 0s linear 0s;
    }

    /* Close button, injected by js/motion.js. The hamburger sits at
       z-index 1001, below the drawer, so it is unreachable once the
       drawer is open and the drawer needs its own affordance. */
    .mm-drawer-close {
        position: absolute;
        top: 20px;
        right: 18px;
        width: 42px;
        height: 42px;
        display: flex;
        align-items: center;
        justify-content: center;
        border: 1px solid var(--dk-line);
        border-radius: 50%;
        background: rgba(255, 255, 255, 0.07);
        color: #dbe7f3;
        font-size: 16px;
        cursor: pointer;
        transition: background-color var(--mm-hover) var(--ease-out-expo),
                    border-color var(--mm-hover) var(--ease-out-expo),
                    color var(--mm-hover) var(--ease-out-expo),
                    transform var(--mm-hover) var(--ease-out-expo);
    }
    .mm-drawer-close:hover {
        background: rgba(201, 169, 106, 0.18);
        border-color: rgba(201, 169, 106, 0.42);
        color: #ffffff;
        transform: rotate(90deg);
    }
    /* The stagger rules above target every child, and the close button
       is one. It must not fade with the list. */
    .mobile-menu > .mm-drawer-close {
        opacity: 1;
        transform: none;
        transition: background-color var(--mm-hover) var(--ease-out-expo),
                    border-color var(--mm-hover) var(--ease-out-expo),
                    color var(--mm-hover) var(--ease-out-expo),
                    transform var(--mm-hover) var(--ease-out-expo);
    }

    .mm-drawer-title {
        margin: 0 0 6px 14px;
        font-family: 'Barlow Condensed', 'Oswald', sans-serif;
        font-size: 11px;
        font-weight: 700;
        letter-spacing: 2.4px;
        text-transform: uppercase;
        color: #a9c4de;
    }

    .mobile-menu .btn {
        margin-top: 16px !important;
    }
    .mobile-link:focus-visible,
    .mm-drawer-close:focus-visible {
        outline: 2px solid var(--au-light);
        outline-offset: 3px;
    }
}

/* Above the breakpoint the drawer must be fully inert, in case a
   resize happens while it is open. */
@media (min-width: 1081px) {
    .mobile-menu,
    .mm-drawer-scrim {
        display: none !important;
    }
}

@media (prefers-reduced-motion: reduce) {
    .mobile-menu,
    .mobile-menu > *,
    .mm-drawer-scrim {
        transition: none !important;
    }
    .mobile-menu > * {
        opacity: 1 !important;
        transform: none !important;
    }
    .mm-drawer-close:hover {
        transform: none !important;
    }
}

/* ============================================================
   43. WHATSAPP ENQUIRY CTA + STATIC MARQUEE
   ------------------------------------------------------------
   The scrolling "Book a security audit" band used to be an <a>
   pointing at tel:. It now sits lower and is a plain <div>, so
   the two rules below that treated it as a link have to go:
   the hover colour shift and the pause-on-hover both implied it
   was interactive.

   The WhatsApp CTA takes its old slot. Green is the one place on
   this page a non-palette colour is correct — WhatsApp's brand
   green is how the action is recognised, and inventing a steel
   blue version of it would just make it unrecognisable. It is
   kept to a single element and the deeper #128C7E is used for
   text on white so the contrast holds.
   ============================================================ */
:root {
    --wa-green: #25d366;    /* brand, used as a surface only */
    /* WhatsApp's own dark brand teal is #128c7e, but that measures
       4.14:1 on white and the label renders at 18px/800 — under the
       18.66px large-text threshold, so it needs the full 4.5. Darkened
       one step to 5.2:1; still unmistakably the WhatsApp teal. */
    --wa-green-dk: #0f7a6d;
}

.wa-cta {
    display: flex;
    align-items: center;
    gap: 16px;
    width: fit-content;
    max-width: 100%;
    margin: 26px auto 0;
    padding: 14px 22px 14px 16px;
    border-radius: 999px;
    text-decoration: none;
    background: #ffffff;
    border: 1px solid rgba(18, 140, 126, 0.28);
    box-shadow: 0 10px 30px rgba(18, 140, 126, 0.14);
    transition: transform var(--mm-hover) var(--ease-in-out-cubic),
                box-shadow var(--mm-hover) var(--ease-out-expo),
                border-color var(--mm-hover) var(--ease-out-expo),
                background-color var(--mm-hover) var(--ease-out-expo);
}
@media (hover: hover) and (pointer: fine) {
    .wa-cta:hover {
        transform: translateY(-2px);
        border-color: rgba(18, 140, 126, 0.5);
        box-shadow: 0 16px 40px rgba(18, 140, 126, 0.22);
    }
}
.wa-cta:focus-visible {
    outline: 2px solid var(--wa-green-dk);
    outline-offset: 3px;
}

.wa-cta-icon {
    flex: 0 0 auto;
    width: 44px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--wa-green);
    color: #ffffff;
    box-shadow: 0 4px 14px rgba(37, 211, 102, 0.42);
}
.wa-cta-icon i {
    font-size: 24px;
    line-height: 1;
}

.wa-cta-copy {
    display: flex;
    flex-direction: column;
    gap: 2px;
    text-align: left;
}
.wa-cta-title {
    font-family: 'Barlow Condensed', 'Oswald', sans-serif;
    font-size: 18px;
    font-weight: 800;
    letter-spacing: 0.6px;
    text-transform: uppercase;
    /* 5.2:1 on white; the brand #25d366 would only reach 1.9:1 */
    color: var(--wa-green-dk);
}
.wa-cta-sub {
    font-size: 12.5px;
    font-weight: 500;
    line-height: 1.4;
    color: #4a5465;
}
.wa-cta-arrow {
    flex: 0 0 auto;
    font-size: 13px;
    color: var(--wa-green-dk);
}

@media (max-width: 560px) {
    .wa-cta {
        gap: 12px;
        padding: 12px 16px 12px 12px;
    }
    .wa-cta-icon { width: 38px; height: 38px; }
    .wa-cta-icon i { font-size: 20px; }
    .wa-cta-title { font-size: 16px; }
    .wa-cta-sub { font-size: 11.5px; }
    .wa-cta-arrow { display: none; }
}

/* ---- the band is no longer interactive ---- */
.mm-marquee.is-static {
    margin-top: 40px;      /* pushed down, below the WhatsApp CTA */
    cursor: default;
    pointer-events: none;  /* nothing here is clickable */
}
/* Kill the two link affordances. Both were scoped to .mm-marquee:hover,
   which a div still matches on pointer-over, so they need overriding
   rather than simply leaving alone. */
.mm-marquee.is-static:hover .mm-marquee-track {
    animation-play-state: running;
}
.mm-marquee.is-static:hover .mm-marquee-item {
    color: var(--color-carbon);
}

/* ============================================================
   41. SECTOR GRID — centre the orphan card on phones
   ------------------------------------------------------------
   Phones (<=480px) lay the sector grid out in 2 columns. There
   are 15 sectors, an odd number, so the last card has no partner
   and grid drops it into column 1 — it read as left-aligned
   rather than as the end of the list.

   The card is given the full row and centred in it, at exactly
   the width one column would have been:
       column = (100% - 10px gap) / 2 = 50% - 5px
   so it stays the same size as every other card, just centred.

   Scoped two ways on purpose:
     - inside the 480px query, so laptops and tablets are
       untouched. At 3 columns (<=768px) and 5 columns (desktop)
       15 divides evenly, so no orphan exists there anyway.
     - :last-child:nth-child(odd) means the rule only fires while
       the count IS odd. Add a 16th sector and it stops applying
       by itself, with no CSS to remember to undo.
   ============================================================ */
@media (max-width: 480px) {
    .sectors-grid .sector-card:last-child:nth-child(odd) {
        grid-column: 1 / -1;
        justify-self: center;
        width: calc(50% - 5px);
    }
}

/* ============================================================
   44. ABOUT COLLAGE - entrance, idle float, pointer response
   ------------------------------------------------------------
   Which CSS property each effect uses is the whole design here.
   These three boxes carry data-mm-parallax, and js/motion.js
   writes `transform` on them every frame on desktop. Two writers
   on one property is the stutter this file warns about in
   section 19, so nothing below touches `transform` on the box:

       transform  -> parallax engine (JS, desktop only)
       scale      -> entrance settle + pointer response
       translate  -> idle float
       ::after    -> sheen sweep

   scale and translate are standalone properties, applied before
   `transform` and composed with it, so the parallax keeps
   working untouched. The img keeps `transform` for the hover
   zoom that was already there.

   Everything is gated behind .js-ac, which js/motion.js adds
   only when it is actually going to animate. Without JS, or
   under prefers-reduced-motion, the boxes are never hidden in
   the first place - so the photographs can't get stranded at
   opacity 0.
   ============================================================ */
.js-ac .collage-top,
.js-ac .collage-item {
    opacity: 0;
    scale: 0.94;
    transition: opacity 0.72s var(--ease-out-expo),
                scale 0.9s var(--ease-out-back),
                box-shadow var(--mm-hover) var(--ease-out-expo);
}
.js-ac .collage-top.ac-in,
.js-ac .collage-item.ac-in {
    opacity: 1;
    scale: 1;
}
/* Stagger reads top-down, the order the eye travels. */
.js-ac .collage-bottom .collage-item:nth-child(1) {
    transition-delay: 0.1s;
}
.js-ac .collage-bottom .collage-item:nth-child(2) {
    transition-delay: 0.2s;
}

/* ------------------------------------------------------------
   Idle float.
   Three different durations, and the third runs counter-phase,
   so the boxes drift apart instead of bobbing in lockstep -
   which is what makes a float read as organic rather than
   mechanical. Amplitude is 6-9px: enough to notice at rest,
   small enough that it never fights the parallax on desktop.
   The delay lets the entrance settle before the drift starts.
   ------------------------------------------------------------ */
@keyframes acFloatA {
    0%, 100% { translate: 0 0; }
    50%      { translate: 0 -9px; }
}
@keyframes acFloatB {
    0%, 100% { translate: 0 0; }
    50%      { translate: 0 -6px; }
}
@keyframes acFloatC {
    0%, 100% { translate: 0 -6px; }
    50%      { translate: 0 0; }
}
.js-ac .collage-top.ac-in {
    animation: acFloatA 9s 0.95s ease-in-out infinite;
}
.js-ac .collage-bottom .collage-item:nth-child(1).ac-in {
    animation: acFloatB 7s 1.05s ease-in-out infinite;
}
.js-ac .collage-bottom .collage-item:nth-child(2).ac-in {
    animation: acFloatC 7.6s 1.15s ease-in-out infinite;
}

/* Sheen sweep. Lives on ::after so it needs a positioned box;
   the boxes already clip with overflow: hidden, which is what
   keeps the highlight inside the rounded corners. */
.collage-top,
.collage-item {
    position: relative;
}
.js-ac .collage-top::after,
.js-ac .collage-item::after {
    content: "";
    position: absolute;
    inset: 0 auto 0 0;
    width: 55%;
    background: linear-gradient(100deg,
        rgba(255, 255, 255, 0) 0%,
        rgba(255, 255, 255, 0.28) 50%,
        rgba(255, 255, 255, 0) 100%);
    translate: -190% 0;
    opacity: 0;
    pointer-events: none;
}
@keyframes acSheen {
    0%   { translate: -190% 0; opacity: 0; }
    14%  { opacity: 1; }
    100% { translate: 330% 0; opacity: 0; }
}

/* ------------------------------------------------------------
   Pointer response, split by input type.
   Desktop gets hover: a small lift via scale, a deeper shadow,
   and the sheen. Touch screens have no hover, so they get a
   press state instead - without it, a tap on these boxes would
   feel dead. Same visual language, different trigger.
   ------------------------------------------------------------ */
@media (hover: hover) and (pointer: fine) {
    .js-ac .collage-top.ac-in:hover,
    .js-ac .collage-item.ac-in:hover {
        scale: 1.02;
        box-shadow: rgba(20, 28, 42, 0.22) 0px 18px 42px;
    }
    .js-ac .collage-top.ac-in:hover::after,
    .js-ac .collage-item.ac-in:hover::after {
        animation: acSheen 0.85s var(--ease-out-quad);
    }
}
@media (hover: none) {
    .js-ac .collage-top.ac-in:active,
    .js-ac .collage-item.ac-in:active {
        scale: 0.985;
        transition-duration: 0.18s;
    }
}

/* Reduced motion: .js-ac is never added, so the boxes stay
   visible and static. These are belt and braces in case the
   class arrives by another route. */
@media (prefers-reduced-motion: reduce) {
    .js-ac .collage-top,
    .js-ac .collage-item {
        opacity: 1;
        scale: 1;
        transition: none;
        animation: none !important;
    }
    .js-ac .collage-top::after,
    .js-ac .collage-item::after {
        display: none;
    }
}

/* ============================================================
   45. CLIENTS - DISPLAY HEADING
   ------------------------------------------------------------
   Replaces the "Active Deployments" tag and the "Sites under our
   watch." sans title. The descriptive sentence is now the section
   heading.

   Font choice is not arbitrary: this section already spoke in
   Playfair Display italic through .mm-statement ("Every site
   remembered."). That block was removed, so without this the
   section would drop to the plain sans title every other section
   uses and lose its display-serif character. This keeps that
   voice, using a face already loaded in the document - no extra
   web-font request.

   Sized well below .mm-statement-line's clamp(30px, 5.4vw, 68px)
   because that styled a two-word line, while this is a 137
   character sentence. At 68px it would run to five or six lines
   on a laptop.

   text-wrap: balance evens out the line lengths so a long
   sentence does not end on a single orphaned word. Browsers
   without it simply wrap normally.
   ============================================================ */
.clients-display-title {
    font-family: 'Playfair Display', Georgia, 'Times New Roman', serif;
    font-style: italic;
    font-weight: 400;
    font-size: clamp(23px, 3.1vw, 42px);
    line-height: 1.26;
    letter-spacing: -0.4px;
    text-align: center;
    color: var(--color-midnight-ink);
    max-width: 960px;
    margin: 0 auto;
    text-wrap: balance;
}
/* Gold accent on the claim itself. The heading is already italic,
   so the emphasis reads through colour rather than slant. */
.clients-display-title em {
    font-style: italic;
    color: var(--gold-dark);
}
@media (max-width: 767px) {
    .clients-display-title {
        /* Narrower measure and a touch more leading: at phone widths
           the balanced wrap produces more, shorter lines. */
        max-width: 34ch;
        line-height: 1.32;
        letter-spacing: -0.2px;
    }
}

/* ============================================================
   46. CLIENTS - CLOSE THE GAP UNDER THE GLOBE CAPTION
   ------------------------------------------------------------
   Measured, not guessed. After the client card grid and the
   .mm-statement block were removed, 146px of empty space was
   left under the "zones - Bengaluru" caption on desktop:

       .mm-tagpanel margin-bottom      96px
       minus the caption's overhang   -40px  (22px margin + 18px tall,
                                              it sits below the panel box)
       .clients-section padding-bottom 90px
       ------------------------------------
       total void under the caption   146px

   That 96px margin existed to separate the globe from the
   statement and the card grid. With nothing below it any more,
   it is pure dead space.

   Both values are scoped to .clients-section so the shared
   .mm-tagpanel and .section rules keep their spacing everywhere
   else they are used.

   The panel margin is set to 40px, which exactly matches the
   caption's overhang - so the caption clears the panel box with
   no residual gap, and the remaining breathing room below is
   owned solely by the section padding. One number to tune
   instead of two interacting ones.
   ============================================================ */
.clients-section .mm-tagpanel {
    margin-bottom: 40px;
}
.clients-section {
    padding-bottom: 48px;
}
@media (max-width: 767px) {
    .clients-section .mm-tagpanel {
        margin-bottom: 40px;
    }
    .clients-section {
        padding-bottom: 32px;
    }
}

/* ============================================================
   47. TECH GRID - centre the orphan card on phones
   ------------------------------------------------------------
   MOBILE ONLY. Nothing here can reach a laptop: the rule lives
   inside max-width 768px, which is the same breakpoint where
   styles.css switches .tech-grid to 2 columns with a 12px gap.
   Above 768px the grid is auto-fill/minmax(185px), which yields
   4 or more columns and a normal ragged last row rather than a
   single stranded card, so there is nothing to fix there.

   There are 15 tech cards. Odd count in a 2-column grid leaves
   the last one - Motion Sensors - alone in column 1, which read
   as left-aligned rather than as the end of the list.

   Given the full row and centred at exactly one column's width:
       column = (100% - 12px gap) / 2 = 50% - 6px
   so it stays the same size as every other card.

   :last-child:nth-child(odd) means this only applies while the
   count IS odd. Add a 16th card and the rule switches itself
   off, with no CSS left behind to undo.
   ============================================================ */
@media (max-width: 768px) {
    .tech-grid .tech-card:last-child:nth-child(odd) {
        grid-column: 1 / -1;
        justify-self: center;
        width: calc(50% - 6px);
    }
}

/* ============================================================
   48. HERO KICKER - keep it on one line on phones
   ------------------------------------------------------------
   MOBILE ONLY. Everything below sits inside max-width 767px, so
   laptops keep the base 13px / 2px letter-spacing / 8px gap
   exactly as they are.

   Measured cause. At the base size the label needs 349px on one
   line, but the hero column only offers:
       Galaxy S9+ 320px ->  281px available   (68px short)
       iPhone 12  390px ->  336px available   (13px short)
       Pixel 5    393px ->  339px available   (10px short)
   so it wrapped to two lines. At 480px it already fits, which is
   why the problem only showed on phones.

   The icon misalignment was a symptom of the same thing, not a
   separate bug: .hero-kicker is a flex row with align-items:
   center, so with a two-line label the icon centred against the
   full two-line box and landed 10.4px below the first line's
   centre - reading as "not aligned" with the text beside it.
   Once the label is a single line the icon centres on it exactly,
   which is already how it behaves at 480px and above.

   Sizes are fluid rather than stepped so the label fits every
   phone width instead of only the two or three tested. The
   ceilings are the desktop values, so the mobile and desktop
   rendering converge at 510px (13 / 0.0255) with no visible jump
   at the breakpoint.

   Width check at the two tightest widths, using the measured
   advance of 0.552 x font-size + letter-spacing per character
   across 38 characters:
       320px -> 9.00px font + 0.96px ls = 225px  (fits 281px)
       393px -> 10.0px font + 1.18px ls = 255px  (fits 339px)
   Comfortable margin at both, so nowrap cannot cause overflow.
   ============================================================ */
@media (max-width: 767px) {
    .hero-kicker {
        font-size: clamp(9px, 2.55vw, 13px);
        letter-spacing: clamp(0.6px, 0.3vw, 2px);
        gap: clamp(5px, 1.6vw, 8px);
        line-height: 1.15;
        /* Safe because the sizes above are proven to fit. */
        white-space: nowrap;
        align-items: center;
    }
    .kicker-icon {
        font-size: clamp(9.5px, 2.7vw, 14px);
        /* Never let the icon be squeezed or stretched by the flex row. */
        flex: 0 0 auto;
        line-height: 1;
    }
}

/* ============================================================
   49. TRAINING BOARD - horizontal carousel on phones
   ------------------------------------------------------------
   MOBILE ONLY, everything inside max-width 767px. Laptops keep
   the 4-column masonry collage exactly as it is.

   The problem: at <=768px styles.css collapses
   .mymind-masonry-grid to a single column, so all 11 cards stack
   vertically. That is a very long scroll on a phone - roughly
   eleven full-height cards before the section ends.

   The fix turns the same markup into a snap carousel without
   touching the HTML:
     .mymind-masonry-grid -> horizontal flex scroller
     .masonry-col         -> display: contents, so the four
                             columns stop being boxes and their
                             cards become direct flex items
   display: contents is what makes this possible with no markup
   change. It is safe here because the only thing that cares
   about .masonry-col as a box is the parallax engine, and
   initParallax() returns early below 768px - so nothing else is
   reading those columns on a phone.

   Cards sit at 86% width so the next one peeks in at the edge.
   That peek is the affordance that says "swipe" - without it a
   snap carousel reads as a static card. 7% side padding lets the
   first and last card still centre under scroll-snap-align.
   ============================================================ */
@media (max-width: 768px) {
    .mymind-masonry-grid {
        /* cancel the grid from styles.css */
        display: flex;
        grid-template-columns: none;
        flex-direction: row;
        align-items: stretch;
        gap: 14px;
        overflow-x: auto;
        overflow-y: hidden;
        scroll-snap-type: x mandatory;
        scroll-behavior: smooth;
        -webkit-overflow-scrolling: touch;
        /* side padding lets the end cards reach centre */
        padding: 4px 7% 12px;
        /* the peeking card is the scroll cue; a bar would be noise */
        scrollbar-width: none;
        -ms-overflow-style: none;
    }
    .mymind-masonry-grid::-webkit-scrollbar {
        display: none;
    }
    .masonry-col {
        display: contents;
    }
    .mymind-masonry-grid .mymind-card {
        flex: 0 0 86%;
        max-width: 86%;
        scroll-snap-align: center;
        /* `scale` is animated below rather than `transform`, because the
           base .mymind-card rule already transitions transform and the
           desktop softening rule writes transform: scale(). Keeping them
           on separate properties avoids one clobbering the other. */
        transition: transform 0.5s cubic-bezier(0.19, 1, 0.22, 1),
                    box-shadow 0.5s cubic-bezier(0.19, 1, 0.22, 1),
                    opacity 0.4s ease,
                    scale 0.45s cubic-bezier(0.19, 1, 0.22, 1);
        scale: 0.965;
        opacity: 0.72;
    }
    /* The centred card lifts and clears; js/motion.js sets .is-current
       from an IntersectionObserver scoped to the scroller. */
    .mymind-masonry-grid .mymind-card.is-current {
        scale: 1;
        opacity: 1;
        box-shadow: 0 10px 30px rgba(20, 28, 42, 0.14);
    }
    /* Without JS every card stays fully legible rather than dimmed. */
    .no-js-tb .mymind-masonry-grid .mymind-card {
        scale: 1;
        opacity: 1;
    }

    /* The desktop "focus pop-out" dims every unhovered card. On a
       touch screen :hover sticks after a tap, which would fight the
       .is-current treatment above. */
    .mymind-masonry-grid:hover .mymind-card:not(:hover) {
        opacity: inherit;
        transform: none;
        filter: none;
    }

    /* ---- progress dots, injected by js/motion.js ---- */
    .tb-dots {
        display: flex;
        justify-content: center;
        align-items: center;
        gap: 7px;
        margin: 4px 0 0;
        padding: 0;
        list-style: none;
    }
    .tb-dot {
        width: 7px;
        height: 7px;
        padding: 0;
        border: 0;
        border-radius: 50%;
        background: rgba(20, 28, 42, 0.18);
        cursor: pointer;
        transition: width 0.32s cubic-bezier(0.19, 1, 0.22, 1),
                    background-color 0.32s ease,
                    border-radius 0.32s ease;
    }
    .tb-dot.is-on {
        width: 22px;
        border-radius: 4px;
        background: var(--color-tangerine-tag);
    }
    .tb-dot:focus-visible {
        outline: 2px solid var(--color-tangerine-tag);
        outline-offset: 3px;
    }
}

/* Dots never exist on desktop because the JS only builds them below
   768px, but this guarantees it even if one is left in the DOM after
   a resize. */
@media (min-width: 769px) {
    .tb-dots {
        display: none !important;
    }
}

/* Reduced motion, but STILL width-scoped. Without the max-width this
   block reached the desktop masonry and forced opacity: 1 !important on
   every training card, which would have overridden the .mm-filtered-out
   fade that the desktop filter and initPillCycle() rely on. Caught by
   measuring card opacity at 769px and 1024px. */
@media (prefers-reduced-motion: reduce) and (max-width: 768px) {
    .mymind-masonry-grid {
        scroll-behavior: auto;
    }
    .mymind-masonry-grid .mymind-card {
        transition: none !important;
        scale: 1 !important;
        opacity: 1 !important;
    }
}

/* ============================================================
   50. PERFORMANCE - release GPU layers, kill the 9px sideways drift
   ------------------------------------------------------------
   Measured problem: 101 elements still had will-change set to
   something other than auto AFTER every reveal had finished and
   the page had been scrolled end to end. Each one is a retained
   compositor layer. On a phone that is real memory pressure and
   is a known cause of stutter and shimmering during scroll.

   The release rule already existed at section 3, but it was
   scoped to [data-mm] .reveal.mm-settled - and #sectors carries
   no [data-mm] by design, so its 15 cards were never covered.
   These rules are unscoped, so every reveal releases once it has
   landed. js/motion.js adds .mm-settled 1200ms after .visible,
   which is well past the transition, so nothing is interrupted
   mid-animation.
   ============================================================ */
.reveal.mm-settled,
.reveal-left.mm-settled,
.reveal-right.mm-settled,
.reveal-scale.mm-settled,
.reveal-fade.mm-settled {
    will-change: auto;
}

/* The 11 training cards declared will-change: transform, box-shadow
   permanently in styles.css. box-shadow in particular is expensive to
   keep on its own layer. Promote only while the pointer is actually on
   the card, which is the only time either property animates. */
.mymind-card {
    will-change: auto;
}
@media (hover: hover) and (pointer: fine) {
    .mymind-card:hover {
        will-change: transform, box-shadow;
    }
}

/* Horizontal drift. On a 393px phone viewport the document measured
   402px wide - the off-canvas mobile drawer sits to the right of the
   viewport and pushed scrollWidth out by 9px. body already had
   overflow-x: hidden but html did not, so the page could still be
   nudged sideways, which reads as the whole layout wobbling.

   overflow-x: clip rather than hidden: clip does not create a scroll
   container, so it cannot break position: sticky or scroll anchoring
   elsewhere on the page. Browsers without clip simply keep the old
   behaviour rather than getting something worse. */
html {
    overflow-x: clip;
}

/* ============================================================
   51. THE SHAKE - scroll anchoring vs the curved ticker
   ------------------------------------------------------------
   This is the actual cause of the page vibrating when you scroll
   down to the About section and stop.

   The curved ticker animates by rewriting the textPath startOffset
   attribute every frame (see initTickerArc in js/motion.js). That is
   not a transform - it re-lays out every glyph along the arc, so the
   position of real DOM nodes changes 60 times a second.

   Chrome's scroll anchoring picks a DOM node near the top of the
   viewport and, if that node moves, silently adjusts scrollTop to keep
   it visually still. When you stop scrolling with the ticker band near
   the top of the viewport, the anchor it picks is inside the ticker
   text. So every frame: the glyphs advance along the arc, scroll
   anchoring "corrects" the scroll position to follow them, that
   correction moves the band, and the glyphs advance again. The page
   ends up oscillating against itself.

   Measured at a 1024x430 viewport, scrolled to the ticker with the
   page then left completely alone for 3 seconds: window.scrollY swung
   over a 10px range and changed direction 52 times in 180 frames.
   Pausing the ticker loop dropped it to 0. Removing the SVG dropped it
   to 0. Freezing every parallax transform changed nothing - so the
   parallax code was never the culprit.

   overflow-anchor: none takes this subtree out of the running for
   anchor selection. Chrome then anchors to something that is actually
   stationary, and the feedback loop cannot form. The ticker keeps
   animating exactly as before; scroll anchoring keeps working
   everywhere else on the page, which is why this is scoped to the SVG
   rather than switched off globally on html.

   Verified: scrollY range 0, 0 reversals, ticker still animating.
   ============================================================ */
svg.ticker-arc,
svg.ticker-arc * {
    overflow-anchor: none;
}

/* ============================================================
   52. OFF-SCREEN ANIMATION PAUSING  (mobile only)
   ------------------------------------------------------------
   Paired with initOffscreenAnimations() in js/motion.js, which adds
   .mm-anim-off to any section, ticker strip or footer that is more
   than 200px away from the viewport. Read the comment there for the
   measurements - the short version is that this page had 74 CSS
   animations running at once on a phone, 59 of them off screen and
   69 of them infinite, and twelve of those animate properties that
   force layout or paint on the main thread every single frame.

   animation-play-state rather than animation: none, because none
   snaps the element back to its unanimated position, which would be
   a visible jump on anything that happens to be mid-cycle. paused
   freezes it exactly where it is and resumes from there.

   Guarded to <=768px on purpose: the standing instruction on this
   project is that the laptop view does not change.
   ============================================================ */
@media (max-width: 768px) {

    .mm-anim-off,
    .mm-anim-off *,
    .mm-anim-off *::before,
    .mm-anim-off *::after {
        animation-play-state: paused !important;
    }
}
