/* ============================================================
   Weststar Engineering — Main Stylesheet
   Depends on tokens.css (load tokens.css first).
   ============================================================ */

/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: auto; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}
img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
button { font: inherit; cursor: pointer; }
h1, h2, h3 { margin: 0; font-family: var(--font-display); font-weight: 500; letter-spacing: -0.02em; }
p { margin: 0; }

/* Visible keyboard focus (a11y floor) */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}

/* Atmospheric texture: faint grid, sits behind everything */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    linear-gradient(var(--grid-line) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid-line) 1px, transparent 1px);
  background-size: 64px 64px;
  mask-image: radial-gradient(ellipse 120% 80% at 50% 0%, #000 30%, transparent 80%);
}

/* Theme flip (T1): gentle surface/ink cross-fade. .nav is excluded — it has its own
   transition list (background is already in it). Off under reduced motion (below). */
body, .section, .footer { transition: background-color 0.4s var(--ease-out), color 0.4s var(--ease-out); }
@media (prefers-reduced-motion: reduce) {
  body, .section, .footer { transition: none; }
}

/* ---------- Layout primitives ---------- */
.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
/* Wider container variant — lets a row (e.g. the service cards) use more of the screen. */
.container--wide { max-width: min(1640px, 94vw); }
.section { position: relative; z-index: 1; padding-block: var(--section-y); }
.section--tight { padding-block: clamp(3.5rem, 7vw, 6rem); }

.eyebrow {
  font-family: var(--font-mono);
  font-size: var(--fs-eyebrow);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 1.1rem;
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
}
.eyebrow::before {
  content: "";
  width: 22px;
  height: 1px;
  background: var(--accent);
  opacity: 0.7;
}

.lead { font-size: var(--fs-lead); color: var(--text-muted); max-width: 54ch; line-height: 1.55; }
.muted { color: var(--text-muted); }

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.85rem 1.5rem;
  border-radius: var(--radius-pill);
  font-family: var(--font-mono);
  font-size: 0.82rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border: 1px solid transparent;
  transition: transform var(--dur-fast) var(--ease-out),
              background var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out);
}
.btn:hover { transform: translateY(-2px); }
.btn--primary { background: var(--accent-grad); color: var(--btn-ink); font-weight: 500; }
.btn--primary:hover { box-shadow: var(--shadow-glow); }
.btn--ghost { border-color: var(--border-strong); color: var(--text); }
.btn--ghost:hover { background: var(--surface-2); border-color: var(--accent); }
.btn .arrow { transition: transform var(--dur) var(--ease-out); }
.btn:hover .arrow { transform: translateX(4px); }

/* ============================================================
   NAVBAR
   ============================================================ */
.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: var(--z-nav);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem var(--gutter);
  transition: background var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out),
              backdrop-filter var(--dur) var(--ease-out),
              padding var(--dur) var(--ease-out);
  border-bottom: 1px solid transparent;
}
.nav.is-scrolled {
  background: var(--nav-glass);
  backdrop-filter: blur(18px) saturate(140%);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  border-bottom-color: var(--border);
  padding-block: 0.8rem;
}
.nav__brand { display: flex; align-items: center; gap: 0.7rem; font-family: var(--font-display); font-weight: 700; letter-spacing: -0.01em; font-size: 1.05rem; }
.nav__brand img { height: 44px; width: auto; display: block; } /* real logo: white-on-transparent, no filter. Enlarged; header height still anchored by the taller nav CTA button. */
@media (max-width: 480px) { .nav__brand img { height: 32px; } } /* cap on small screens so it never overflows */
.nav__links { display: flex; align-items: center; gap: 1.9rem; }
.nav__link {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  transition: color var(--dur-fast) var(--ease-out);
}
.nav__link:hover { color: var(--text); }
.nav__cta { margin-left: 0.4rem; }

.nav__toggle { display: none; background: none; border: 0; width: 40px; height: 40px; position: relative; }
.nav__toggle span { position: absolute; left: 8px; right: 8px; height: 1.5px; background: var(--text); transition: transform var(--dur) var(--ease-out), opacity var(--dur) var(--ease-out); }
.nav__toggle span:nth-child(1) { top: 14px; }
.nav__toggle span:nth-child(2) { top: 20px; }
.nav__toggle span:nth-child(3) { top: 26px; }

/* Touch-target hit-pads (B1-1): the three sub-44px controls reach ≥44×44 via
   negative-inset pseudo hit areas — zero layout/visual impact at any width
   (GATE B ruling: padding would shift navbar siblings). 40+8=48, 38+8=46. */
.theme-toggle, .footer__social a { position: relative; }
.nav__toggle::before, .theme-toggle::before, .footer__social a::before {
  content: ""; position: absolute; inset: -4px;
}

/* Tap feedback (B1-4): the UA grey tap-flash is removed ONLY on controls that
   get explicit pressed feedback in exchange (GATE B ruling — removing the flash
   must never leave touch users with zero feedback). Cards/tiles keep the native
   flash: their hover states already apply on first tap. filter (not
   transform/opacity) so reveals.js/hero-intro inline styles are never fought. */
.btn, .nav__link, .nav__toggle, .theme-toggle, .footer__social a, .footer__links a {
  -webkit-tap-highlight-color: transparent;
}
.btn:active, .nav__toggle:active, .theme-toggle:active { filter: brightness(0.88); }
.nav__link:active, .footer__links a:active, .footer__social a:active { color: var(--accent); }

/* Theme toggle (T1) — sun shown in dark ("switch to light"), moon in light. JS-driven
   (js/theme.js); hidden via <noscript> when JS is off. Matches nav chrome: hairline pill. */
.theme-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; margin-left: 0.4rem; padding: 0;
  background: transparent; border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill); color: var(--text-muted);
  font-family: var(--font-mono);
  transition: color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}
.theme-toggle:hover { color: var(--text); background: var(--surface-2); border-color: var(--accent); }
.theme-toggle svg { width: 16px; height: 16px; display: block; }
.theme-toggle .theme-toggle__moon { display: none; }
[data-theme="light"] .theme-toggle .theme-toggle__sun { display: none; }
[data-theme="light"] .theme-toggle .theme-toggle__moon { display: block; }

@media (max-width: 900px) {
  .nav__toggle { display: block; }
  /* While the menu is open, EVERY containing-block trap on .nav must come off — both
     the scrolled-state backdrop-filter AND any inline transform (hero-intro animates the
     nav with GSAP; a residual transform would trap the fixed overlay at header height —
     !important is required to beat the inline style). With these off, inset:0 = the real
     viewport at any scroll position. */
  .nav.is-open { transform: none !important; backdrop-filter: none; -webkit-backdrop-filter: none; }
  .nav__links {
    position: fixed; inset: 0; z-index: -1; /* below the brand + X inside the nav's own
      stacking context (z 100) — so the overlay covers ALL page content, while the
      logo/close row stays on top of it. */
    height: 100dvh; /* dynamic-viewport height: covers under mobile browser toolbars */
    flex-direction: column; justify-content: center; gap: 1rem;
    background: var(--bg); /* SOLID surface token — never relies on what's behind it */
    opacity: 0; pointer-events: none;
    transition: opacity var(--dur) var(--ease-out); /* fade applies on CLOSE only (open is instant, below) */
  }
  .nav__link { font-size: 1.05rem; padding: 0.65rem 1.4rem; } /* ~46px tap targets */
  .nav__cta { margin-top: 0.6rem; } /* GET IN TOUCH joins the stack (was display:none) */
  .theme-toggle { margin-top: 0.4rem; }
  .nav.is-open .nav__links { opacity: 1; pointer-events: auto; transition: none; } /* instant paint on open — no content visible beneath */
  .nav.is-open .nav__toggle span:nth-child(1) { top: 20px; transform: rotate(45deg); }
  .nav.is-open .nav__toggle span:nth-child(2) { opacity: 0; }
  .nav.is-open .nav__toggle span:nth-child(3) { top: 20px; transform: rotate(-45deg); }
}

/* ============================================================
   SCRUB CANVAS SECTION  (the signature element)
   Tall section drives scroll; sticky inner pins; canvas scrubs.
   ============================================================ */
.scrub { position: relative; z-index: 1; height: 400vh; }
.scrub--tunnel { height: 420vh; }
/* Hero spans TWO beats (intro -> "what we do" diagonal) over one continuous loop.
   PACING KNOBS — keep it just long enough to read, then release into the cards:
     • Section HEIGHT below = total scroll distance for the pinned sequence (the main knob;
       shorter = less dwell — raise it if the "what we do" block ever feels rushed).
     • Beat fractions of that height live on the markup (index.html):
         intro fade-out      = hero <section> data-fade  (0.12  -> first ~12% of scroll)
         "what we do" fade-in = .hero-beat data-show       (0.16)
         hold -> release      = data-hide (1.0), dissolving with the footage fade (~0.88->1).
   Tuned shorter so little scroll passes with nothing changing. */
.scrub--hero { height: 130vh; } /* was 240vh — reduced while hero-beat is data-ceo-hidden */
@media (max-width: 1024px) { .scrub { height: 340vh; } .scrub--tunnel { height: 380vh; } .scrub--hero { height: 125vh; /* was 220vh — reduced while hero-beat is data-ceo-hidden */ } }
@media (max-width: 768px)  { .scrub { height: 280vh; } .scrub--tunnel { height: 320vh; } .scrub--hero { height: 120vh; /* was 200vh — reduced while hero-beat is data-ceo-hidden */ } }

.scrub__sticky {
  position: sticky; top: 0;
  height: 100vh;  /* fallback for engines without small-viewport units */
  height: 100svh; /* B1-3: svh over dvh DELIBERATELY — dvh would animate the pinned
                     container's height as the iOS toolbar collapses mid-scrub,
                     which is exactly the jank being removed; svh stays stable. */
  overflow: hidden; background: var(--bg-deep);
}
.scrub__canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

/* Theme-swapped hero loop video ([data-scrub-video]). Hidden until the 'playing' event
   adds .is-playing (main.js) — the poster (duplicated as the sticky's CSS background)
   shows until then, and again during a theme-toggle src swap. Poster == first frame,
   so the ease-in is a seamless handover, not a visible crossfade. */
.scrub__video {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; display: block;
  opacity: 0; transition: opacity .6s var(--ease-out);
}
.scrub__video.is-playing { opacity: 1; }

/* legibility vignette over the footage */
.scrub__sticky::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(180deg, rgba(6,7,10,0.55) 0%, transparent 22%, transparent 60%, rgba(6,7,10,0.85) 100%),
    radial-gradient(ellipse 90% 60% at 50% 50%, transparent 40%, rgba(6,7,10,0.45) 100%);
}
.scrub__overlay { position: absolute; inset: 0; z-index: 2; }

/* Over-footage ink (T2): scrub overlays sit on dark footage in BOTH themes ("cinema
   panels"), so their text keeps the dark-theme ink via the on-media tokens (identical
   values in dark; light does not override them). The reduced-motion block at the bottom
   of this file resets the hero/tunnel beats back to theme ink — collapsed there, that
   text flows on the page background instead of over footage. */
.scrub__overlay { color: var(--text-on-media); }
.scrub__overlay .eyebrow { color: var(--accent-on-media); }
.scrub__overlay .eyebrow::before { background: var(--accent-on-media); }
.scrub__overlay .hero__sub,
.scrub__overlay .beat__sub,
.scrub__overlay .hero-beat__desc { color: var(--text-muted-on-media); }
.scrub__overlay .hero__title em,
.scrub__overlay .hero-beat__title em { background: var(--accent-grad-on-media); -webkit-background-clip: text; background-clip: text; }
.scrub__overlay .scroll-hint { color: var(--text-dim-on-media); }
.scrub__overlay .scroll-hint .mouse { border-color: var(--text-dim-on-media); }

/* Tunnel top-edge fade — the tunnel opens against the darker page bg above it, leaving a hard
   horizontal seam on scroll-in. This blends the canvas top into that bg. Attached to the pinned
   .scrub__sticky (not the tall section) so it stays glued to the top of the visible canvas the
   whole scrub. Sits ABOVE the canvas + vignette but BELOW the .scrub__overlay beats (z-index 2),
   so the heading text stays crisp. Colour = the section-above bg (page bg var(--bg)). Tunnel only. */
.scrub--tunnel .scrub__sticky::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 22%;
  z-index: 1; pointer-events: none;
  background: linear-gradient(to bottom, var(--bg) 0%, transparent 100%);
}

/* ---- Hero legibility scrim ----------------------------------------------------
   Vertical gradient over the hero footage only: darkest at the TOP (over the
   headline/subtext zone), fading to fully transparent toward the BOTTOM so the
   bright particle field stays lively. Sits ABOVE the canvas (z-index 1) but BELOW
   the text overlay (z-index 2), and is pointer-events:none. The hero opts OUT of
   the shared .scrub__sticky::after vignette (which darkens the bottom) so the lower
   footage isn't dimmed; tunnel/About keep that shared vignette. Tune the stops below. */
.scrub--hero .scrub__sticky::after { display: none; }
.hero-scrim {
  /* Tunable stops — colours (alpha = strength) and positions (top→bottom). */
  --scrim-1: rgba(0,0,0,0.80); --scrim-1-at:  0%;   /* darkest — top of headline zone */
  --scrim-2: rgba(0,0,0,0.62); --scrim-2-at: 35%;
  --scrim-3: rgba(0,0,0,0.30); --scrim-3-at: 62%;
  --scrim-4: rgba(0,0,0,0.00); --scrim-4-at: 85%;   /* clear — bottom 15% stays lively */
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: linear-gradient(
    to bottom,
    var(--scrim-1) var(--scrim-1-at),
    var(--scrim-2) var(--scrim-2-at),
    var(--scrim-3) var(--scrim-3-at),
    var(--scrim-4) var(--scrim-4-at)
  );
}

/* LIGHT THEME × home hero — the hero footage is now THEME-SWAPPED (light video in light
   mode), so the fixed on-media cinema ink no longer applies here: flip the hero overlay
   back to theme ink and swap the black legibility fog for a white one. Scoped to
   .scrub--hero ONLY — the tunnel and About footage stay dark in both themes and keep
   the on-media ink. (White/black gradient stops are alpha masks, not brand colours.) */
[data-theme="light"] .scrub--hero .scrub__overlay { color: var(--text); }
[data-theme="light"] .scrub--hero .scrub__overlay .eyebrow { color: var(--accent); }
[data-theme="light"] .scrub--hero .scrub__overlay .eyebrow::before { background: var(--accent); }
[data-theme="light"] .scrub--hero .scrub__overlay .hero__sub,
[data-theme="light"] .scrub--hero .scrub__overlay .hero-beat__desc { color: var(--text-muted); }
[data-theme="light"] .scrub--hero .scrub__overlay .hero__title em,
[data-theme="light"] .scrub--hero .scrub__overlay .hero-beat__title em { background: var(--accent-grad); -webkit-background-clip: text; background-clip: text; }
[data-theme="light"] .scrub--hero .scrub__overlay .scroll-hint { color: var(--text-dim); }
[data-theme="light"] .scrub--hero .scrub__overlay .scroll-hint .mouse { border-color: var(--text-dim); }
[data-theme="light"] .hero-scrim {
  --scrim-1: rgba(255,255,255,0.72);
  --scrim-2: rgba(255,255,255,0.50);
  --scrim-3: rgba(255,255,255,0.22);
  --scrim-4: rgba(255,255,255,0.00);
}
[data-theme="light"] .scrub--hero .hero-beat__head::before,
[data-theme="light"] .scrub--hero .hero-beat__desc::before {
  background: radial-gradient(ellipse 82% 132% at 50% 50%, rgba(255,255,255,0.66) 0%, transparent 74%);
}
[data-theme="light"] .scrub--hero .hero-beat__desc { text-shadow: 0 1px 12px rgba(255,255,255,0.6); }

/* Hero text block, centered, fades out on first scroll */
.hero {
  position: absolute; left: 0; right: 0; top: 50%;
  transform: translateY(-50%);
  text-align: center;
  padding-inline: var(--gutter);
  will-change: opacity;
}
.hero__title { font-size: var(--fs-hero); line-height: var(--lh-tight); max-width: 16ch; margin-inline: auto; }
.hero__title em { font-style: normal; background: var(--accent-grad); -webkit-background-clip: text; background-clip: text; color: transparent; }
/* Headline TYPEWRITER entrance (js/hero-intro.js). Characters are split into spans in
   JS and revealed via opacity only (no insertion/removal -> no reflow during typing).
   No-JS / reduced-motion leave the full headline visible. The scroll fade-out stays on
   the .hero parent, so it's unaffected. The caret is width:0 + a 2px right border, so
   moving it after each character never shifts the text. */
.hero__caret {
  display: inline-block;
  width: 0;
  height: 0.82em;
  vertical-align: -0.06em;
  border-right: 2px solid var(--accent);
  animation: heroCaretBlink 0.9s steps(1, end) infinite;
}
@keyframes heroCaretBlink { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }
.hero__caret.is-done { animation: none; opacity: 0; transition: opacity 0.4s var(--ease-out); }
.hero__sub { margin: 1.6rem auto 0; max-width: 50ch; color: var(--text-muted); font-size: var(--fs-lead); }

/* (Hero annotation cards / summary statement removed — the hero is intentionally
   clean: eyebrow + headline + subtext + scroll hint over the looping footage.) */

/* Scroll hint */
.scroll-hint {
  position: absolute; left: 50%; bottom: 2.2rem; transform: translateX(-50%);
  display: grid; place-items: center; gap: 0.5rem; z-index: 3;
  font-family: var(--font-mono); font-size: 0.66rem; letter-spacing: 0.22em;
  text-transform: uppercase; color: var(--text-dim);
}
.scroll-hint .mouse { width: 22px; height: 34px; border: 1.5px solid var(--text-dim); border-radius: 12px; position: relative; }
.scroll-hint .mouse::after { content: ""; position: absolute; left: 50%; top: 6px; width: 3px; height: 6px; border-radius: 2px; background: var(--accent); transform: translateX(-50%); animation: hint 1.6s var(--ease-out) infinite; }
@keyframes hint { 0%,100% { transform: translate(-50%, 0); opacity: 1; } 50% { transform: translate(-50%, 8px); opacity: 0.3; } }
/* Hero scroll hint sits over the bright (un-scrimmed) bottom zone — add a subtle dark
   shadow/backing so the label + mouse stay legible against the lively particles. */
.scrub--hero .scroll-hint { text-shadow: 0 1px 10px rgba(0,0,0,0.55); }
.scrub--hero .scroll-hint .mouse { box-shadow: 0 2px 12px rgba(0,0,0,0.45); }

/* ===== About hero: text moved RIGHT + right-anchored scrim for legibility over the bright
   HQ glass. Scoped to the About scrub only ([data-scrub="about"]); the homepage hero
   (data-scrub="hero") is untouched. The footage stays DARK in both themes, so the text-side
   scrim must too: it uses a FIXED dark rgba(6,7,10,a) — the same near-black as --scrim-deep /
   dark --bg-deep, deliberately NOT theme-flipped (identical pattern to --scrim-deep) — so
   light theme looks the same as dark. The LEFT (building + signage) stays fully clear. The
   hero text stays WHITE in both themes via the on-media tokens on .scrub__overlay (fixed,
   never flipped to dark). Scroll cue unchanged. */
[data-scrub="about"] .hero {
  left: auto;
  right: clamp(1.5rem, 7vw, 7rem);   /* inset from the right edge -> centre-right, breathing room */
  z-index: 2;                        /* above the scrim (1), below the scroll-hint (3) */
  max-width: min(70ch, 60%);         /* wider band -> H1 breaks into fewer, longer lines */
  padding-inline: 0;                 /* right offset handled by `right`, not padding */
  text-align: right;
}
[data-scrub="about"] .hero__title { max-width: none; margin-left: auto; margin-right: 0; }  /* fill the band -> ~2-3 lines */
[data-scrub="about"] .hero__sub   { max-width: 50ch; margin-left: auto; margin-right: 0; }
.about-hero__scrim {
  position: absolute; inset: 0; pointer-events: none; z-index: 1;
  background: linear-gradient(to left,
    rgba(6, 7, 10, 1) 0%,
    rgba(6, 7, 10, 0.72) 22%,
    rgba(6, 7, 10, 0.35) 45%,
    transparent 80%);
}
/* Mobile: the right-anchored desktop band doesn't fit — the block goes full-width and
   left-aligned (house mobile pattern), and the scrim flips to a bottom-up fixed-dark
   gradient (same rgba(6,7,10,…) family, mirroring the panel-heroes' mobile scrim). */
@media (max-width: 720px) {
  [data-scrub="about"] .hero {
    left: 0; right: 0;
    max-width: none;
    padding-inline: var(--gutter);
    text-align: left;
  }
  [data-scrub="about"] .hero__title,
  [data-scrub="about"] .hero__sub { margin-left: 0; margin-right: auto; }
  .about-hero__scrim {
    background: linear-gradient(0deg,
      rgba(6, 7, 10, 0.88) 0%,
      rgba(6, 7, 10, 0.55) 45%,
      rgba(6, 7, 10, 0.25) 72%,
      transparent 100%);
  }
}

/* ---- ABOUT content components (ported verbatim from about.html's head <style>,
   lines 48-55) — the About sections were moved into index.html but their inline
   page styles never travelled, so these classes rendered unstyled there. Ported
   as-is: token-only, theme-safe, no opacity/transform (reveals.js owns those
   inline). about.html still carries its identical inline copy for now (harmless
   duplicate; cleanup is a later task). Step 2 will extend this block with the
   remaining About components (intro-lead, vm, closing, …). */

/* Chronological timeline (a real sequence -> markers are meaningful) */
.timeline { display: grid; gap: 0; border-left: 1px solid var(--border); margin-left: 0.5rem; }
.tl { position: relative; padding: 0 0 2.6rem 2rem; }
.tl:last-child { padding-bottom: 0; }
.tl::before { content: ""; position: absolute; left: -5px; top: 4px; width: 9px; height: 9px; border-radius: 50%; background: var(--accent-grad); box-shadow: 0 0 12px var(--glow); }
/* Shared mono accent label — same component role on the timeline and the V&M panels
   (grouped by request so the two never drift; .vm__eyebrow's original 0.72rem/.2em
   values are superseded by these). */
.tl__year, .vm__eyebrow { font-family: var(--font-mono); font-size: 0.8rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--accent); }
.tl h3 { font-size: var(--fs-h3); margin: 0.5rem 0 0.5rem; }
.tl p { color: var(--text-muted); font-size: var(--fs-sm); max-width: 56ch; }

/* Vision & Mission — two panels (ported verbatim from about.html:57-66; .vm__eyebrow
   lives in the grouped label rule above) */
.vm { display: grid; grid-template-columns: 1fr; gap: 1.25rem; }
@media (min-width: 860px) { .vm { grid-template-columns: 1fr 1fr; } }
.vm__panel { padding: clamp(1.8rem, 3.5vw, 2.6rem); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-card); }
.vm__panel h3 { font-size: var(--fs-h3); margin: 0.7rem 0 0.9rem; }
.vm__intro { color: var(--text-muted); font-size: var(--fs-sm); line-height: 1.6; margin-bottom: 1.5rem; }
.vm-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.95rem; }
.vm-list li { position: relative; padding-left: 1.6rem; font-size: var(--fs-sm); line-height: 1.5; }
.vm-list li::before { content: ""; position: absolute; left: 0; top: 0.5em; width: 7px; height: 7px; border-radius: 50%; background: var(--accent-grad); box-shadow: 0 0 12px var(--glow); }

/* Intro / gradient text / map / presence / closing — REMAINDER of the about.html
   inline-block port (GATE B ruling 1; .timeline/.tl* and .vm* were ported in the
   two earlier approved steps). Verbatim from about.html:75-90 and 119-121.
   PARKED, NOT ported (markup sits in hidden data-ceo-hidden templates):
   .lead-head, .lead-top, .person family (+ its :root --avatar /
   --about-accent2-rgb vars and reduced-motion block), .directors, .capital,
   .chiefs — port them verbatim when those sections are restored.
   SKIPPED as about-page-only: .about-intro, .lede (that markup did not move). */
.about-grad { background: var(--accent-grad); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; }
.intro-lead h2 { font-size: var(--fs-h2); margin-top: 1rem; max-width: 20ch; }
.intro-lead__row { display: grid; grid-template-columns: 1.12fr 1fr; gap: clamp(1.6rem, 3.5vw, 2.75rem); margin-top: 2.25rem; align-items: stretch; }
@media (max-width: 860px) { .intro-lead__row { grid-template-columns: 1fr; gap: 1.6rem; } }
.intro-lead__text p { color: var(--text-muted); max-width: 62ch; }
.intro-lead__text p + p { margin-top: 1rem; }
.intro__map { width: 100%; height: auto; display: block; align-self: center;
  -webkit-mask-image: radial-gradient(115% 108% at 50% 50%, #000 55%, transparent 98%);
          mask-image: radial-gradient(115% 108% at 50% 50%, #000 55%, transparent 98%); }
.presence { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-top: 1.6rem; }
.presence span { font-family: var(--font-mono); font-size: 0.75rem; color: var(--text); border: 1px solid var(--border); background: var(--surface); padding: 0.55rem 0.9rem; border-radius: 9px; }
.closing__grid { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(1.4rem, 3vw, 2.5rem); margin-top: 1.4rem; }
@media (max-width: 820px) { .closing__grid { grid-template-columns: 1fr; gap: 1rem; } }
.closing__grid p { color: var(--text-muted); }

/* ---- Tunnel narrative beats: synced storytelling over the scrub ----
   Toggled by the engine's [data-card] is-visible mechanism (data-show/hide).
   Outer .beat = fade/slide entrance; inner wrapper = scroll-linked parallax
   drift via --beat-p (set per frame in main.js). The two transforms live on
   separate elements so they don't fight. */
.beat {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  text-align: center; padding-inline: var(--gutter);
  opacity: 0; visibility: hidden;
  transform: translateY(16px);
  transition: opacity 0.7s var(--ease-out), transform 0.7s var(--ease-out), visibility 0.7s;
  pointer-events: none; will-change: opacity, transform;
}
.beat.is-visible { opacity: 1; visibility: visible; transform: translateY(0); }
.beat__inner {
  position: relative; max-width: 48ch; margin-inline: auto;
  /* parallax: drift upward (+24px -> -24px) as you scroll through the beat */
  transform: translateY(calc((0.5 - var(--beat-p, 0.5)) * 48px));
  will-change: transform;
}
.beat__inner::before {
  /* soft legibility scrim behind the text — radial, no hard box */
  content: ""; position: absolute; left: 50%; top: 50%;
  width: 150%; height: 220%; transform: translate(-50%, -50%);
  z-index: -1; pointer-events: none;
  background: radial-gradient(ellipse 60% 60% at 50% 50%, var(--scrim-deep) 0%, transparent 72%);
}
.beat__title { font-size: var(--fs-h2); line-height: var(--lh-snug); max-width: 20ch; margin-inline: auto; }
.beat__sub { margin-top: 1.1rem; color: var(--text-muted); font-size: var(--fs-lead); max-width: 42ch; margin-inline: auto; }

/* Hero BEAT 2 — DIAGONAL "what we do" over the SAME footage. Overrides .beat's centered
   grid: heading block anchored TOP-LEFT, supporting description BOTTOM-RIGHT, with the
   footage showing through the diagonal gap. Each block gets its own soft, EDGELESS radial
   scrim (no card edges) + text-shadow. Closing phrase highlighted with the accent gradient. */
.beat.hero-beat {
  /* Entrance tuning (easy to tune): slow, elegant fade/slide; desc lags the heading. */
  --wd-in-dur: 1.05s;
  --wd-desc-delay: 0.22s;
  --wd-travel: 46px;
  --wd-ease: cubic-bezier(0.16, 1, 0.3, 1); /* gentle ease-out */
  display: flex; flex-direction: column; justify-content: space-between;
  text-align: left;
  padding: clamp(4rem, 11vh, 7.5rem) var(--gutter) clamp(3.5rem, 9vh, 6.5rem);
  /* Parent only toggles visibility — head + desc fade/slide individually (staggered). */
  opacity: 1; transform: none;
  transition: visibility var(--wd-in-dur);
}
.hero-beat__head {
  position: relative; align-self: flex-start;
  max-width: min(94vw, 60rem);              /* generous: strong upper-left statement */
  opacity: 0; transform: translateY(var(--wd-travel));
  transition: opacity var(--wd-in-dur) var(--wd-ease), transform var(--wd-in-dur) var(--wd-ease);
  will-change: opacity, transform;
}
.hero-beat__desc {
  position: relative; align-self: flex-end; text-align: right; margin: 0;
  max-width: min(94vw, 34rem);
  color: var(--text-muted); font-size: var(--fs-lead); line-height: 1.6;
  text-shadow: 0 1px 12px rgba(0,0,0,0.62);
  opacity: 0; transform: translateY(var(--wd-travel));
  transition: opacity var(--wd-in-dur) var(--wd-ease) var(--wd-desc-delay),
              transform var(--wd-in-dur) var(--wd-ease) var(--wd-desc-delay);
  will-change: opacity, transform;
}
.beat.hero-beat.is-visible .hero-beat__head,
.beat.hero-beat.is-visible .hero-beat__desc { opacity: 1; transform: none; }
/* soft, edgeless dark scrim behind each text block (radial -> transparent, no hard box) */
.hero-beat__head::before, .hero-beat__desc::before {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  inset: -1.75rem -3rem;
  background: radial-gradient(ellipse 82% 132% at 50% 50%, var(--scrim-deep) 0%, transparent 74%);
}
.hero-beat__title {
  font-size: clamp(2rem, 5vw, 4rem);        /* large statement */
  line-height: 1.1; letter-spacing: -0.01em;
  text-shadow: 0 2px 18px rgba(0,0,0,0.55), 0 0 2px rgba(0,0,0,0.45);
}
.hero-beat__title em {
  font-style: normal;
  background: var(--accent-grad);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
@media (max-width: 768px) {
  /* mobile: stack heading then description vertically, both left-aligned (no forced diagonal) */
  .beat.hero-beat { justify-content: center; gap: 1.75rem; padding-block: clamp(3.5rem, 12vh, 5.5rem); }
  .hero-beat__head { max-width: 92vw; }
  .hero-beat__desc { align-self: flex-start; text-align: left; max-width: 92vw; }
}

/* ============================================================
   STANDARD SECTIONS
   ============================================================ */
.section-head { max-width: 60ch; margin-bottom: clamp(2.5rem, 5vw, 4rem); }
.section-head h2 { font-size: var(--fs-h2); line-height: var(--lh-snug); }
.section-head .lead { margin-top: 1.2rem; }
/* Inline brand wordmark used in place of a word in a heading (e.g. Hikvision). Height is
   em-based so it scales with the heading font-size; keeps its native colours; sits on the
   text baseline with a little space before the next word. Line wraps naturally if needed. */
.hik-logo { height: 0.82em; width: auto; vertical-align: baseline; margin-right: 0.22em; }

/* What we do — split intro */
.intro-split { display: grid; grid-template-columns: 1fr; gap: 2.5rem; align-items: end; }
@media (min-width: 860px) { .intro-split { grid-template-columns: 1.2fr 1fr; } }
.intro-split h2 { font-size: var(--fs-h2); line-height: var(--lh-snug); }

/* Service tag chip (shared utility — also used on inner pages) */
.tag { font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.04em; padding: 0.3rem 0.65rem; border: 1px solid var(--border); border-radius: var(--radius-pill); color: var(--text-muted); }

/* ---- Tall vertical hover-reveal service cards (replaces the old .pillars) ----
   Default: dark card, text only (category + title + description + tags), anchored bottom.
   Hover: reveal the image — or a neutral gradient placeholder if the file is missing —
   scaled in as the background; description+tags fade out; the title stays pinned over a
   soft bottom scrim. Touch (hover:none) and reduced-motion show the image-background +
   text WITHOUT hover. Drop real images at assets/img/services/<slug>.jpg (no code change). */
.vcards { display: grid; grid-template-columns: repeat(4, 1fr); gap: clamp(1rem, 1.8vw, 1.6rem); }
@media (max-width: 980px) { .vcards { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 520px) { .vcards { grid-template-columns: 1fr; } }

.vcard {
  /* Hover-reveal tuning (easy to tune): slow, smooth ease-in-out; gentle scale-in. */
  --vc-dur: 0.55s;
  --vc-ease: cubic-bezier(0.65, 0, 0.35, 1); /* smooth ease-in-out (in AND out) */
  --vc-scale: 1.06;
  position: relative; isolation: isolate; display: block;
  aspect-ratio: 3 / 5;
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: var(--surface); overflow: hidden;
  box-shadow: var(--shadow-card);
  transition: border-color var(--vc-dur) var(--vc-ease);
}
.vcard:hover { border-color: var(--border-strong); }
/* neutral gradient placeholder (shows if the <img> is missing/not yet loaded) */
.vcard::before {
  content: ""; position: absolute; inset: 0; z-index: 0;
  background: linear-gradient(155deg, var(--bg-elev) 0%, var(--surface-2) 45%, var(--bg-deep) 100%);
  opacity: 0; transform: scale(var(--vc-scale));
  transition: opacity var(--vc-dur) var(--vc-ease), transform var(--vc-dur) var(--vc-ease);
}
.vcard__img {
  position: absolute; inset: 0; z-index: 1; width: 100%; height: 100%;
  object-fit: cover; opacity: 0; transform: scale(var(--vc-scale));
  transition: opacity var(--vc-dur) var(--vc-ease), transform var(--vc-dur) var(--vc-ease);
}
.vcard__img.is-missing { display: none; } /* hide broken image -> the ::before gradient shows */
.vcard__overlay {
  position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background: linear-gradient(to top, var(--scrim-deep) 0%, rgba(6,7,10,0.35) 40%, transparent 70%);
  opacity: 0; transition: opacity var(--vc-dur) var(--vc-ease);
}
.vcard:hover::before, .vcard:hover .vcard__img { opacity: 1; transform: scale(1); }
.vcard:hover .vcard__overlay { opacity: 1; }

.vcard__body {
  position: absolute; inset: 0; z-index: 3;
  display: flex; flex-direction: column; justify-content: flex-end; gap: 0.7rem;
  padding: clamp(1.5rem, 2.4vw, 2.3rem);
}
.vcard__eyebrow { font-family: var(--font-mono); font-size: 0.8rem; letter-spacing: 0.2em; color: var(--accent); }
.vcard__title { font-size: clamp(1.4rem, 1.9vw, 2rem); line-height: var(--lh-snug); text-shadow: 0 1px 12px rgba(0,0,0,0.55); }
.vcard__more { overflow: hidden; max-height: 18rem; opacity: 1; transition: opacity var(--vc-dur) var(--vc-ease), max-height var(--vc-dur) var(--vc-ease); }
.vcard__desc { margin: 0; color: var(--text-muted); font-size: var(--fs-body); line-height: 1.55; }
.vcard__tags { margin-top: 1.1rem; display: flex; flex-wrap: wrap; gap: 0.45rem; }
.vcard:hover .vcard__more { opacity: 0; max-height: 0; }

/* Over-image ink (T2): the hover-revealed photo + scrim stays dark in both themes, so the
   text that remains over it flips to on-media ink (identical values in dark). */
.vcard__eyebrow, .vcard__title { transition: color var(--vc-dur) var(--vc-ease); }
.vcard:hover .vcard__eyebrow { color: var(--accent-on-media); }
.vcard:hover .vcard__title { color: var(--text-on-media); }

/* Touch / no-hover: show the image (or gradient) as a dimmed background with text overlaid. */
@media (hover: none) {
  .vcard::before, .vcard__img { opacity: 1; transform: none; }
  .vcard__overlay { opacity: 1; background: linear-gradient(to top, var(--scrim-deep) 0%, rgba(6,7,10,0.5) 55%, rgba(6,7,10,0.34) 100%); }
  .vcard__more { opacity: 1; max-height: 18rem; }
  /* image always visible here -> all card text is over media */
  .vcard__eyebrow { color: var(--accent-on-media); }
  .vcard__title { color: var(--text-on-media); }
  .vcard__desc { color: var(--text-muted-on-media); }
}

/* ---- Hover GLOW — homepage "Our Services" cards (.vcard) only -------------------------
   Layered AROUND the existing text->image reveal; calm at rest, all on the same :hover so
   it stays in sync (uses the reveal's own --vc-dur/--vc-ease). Tokens only — no new colour.
   The bloom lives in the outer .vcard-wrap so it is NOT clipped by .vcard's overflow:hidden;
   the spot + edge live inside the card; the under-pool is the card's own (unclipped) shadow. */
.vcard {
  --svc-glow: linear-gradient(155deg, var(--accent-2), var(--accent) 60%, var(--accent));
  --svc-glow: linear-gradient(155deg in oklch, var(--accent-2), var(--accent) 60%, var(--accent));
  --svc-glow-rgb: var(--accent-rgb);    /* accent as R,G,B — under-pool */
  --svc-spot-rgb: var(--accent-2-rgb);  /* accent-2 as R,G,B — cursor highlight */
  transition: transform var(--vc-dur) var(--vc-ease),
              box-shadow var(--vc-dur) var(--vc-ease),
              border-color var(--vc-dur) var(--vc-ease);
}
.vcard:hover {
  transform: translateY(-7px);
  border-color: transparent;      /* only the gradient edge reads on hover */
  box-shadow: 0 34px 90px -26px rgba(var(--svc-glow-rgb), 0.42);
}
.vcard__body { z-index: 5; }       /* keep the text above the new glow layers */

.vcard-wrap { position: relative; isolation: isolate; }
/* outer bloom — outside the card's clip (blur halo extends past the card edges) */
.vcard__bloom {
  position: absolute; inset: -1px; border-radius: var(--radius-lg);
  background: var(--svc-glow); filter: blur(24px);
  opacity: 0; z-index: -1; pointer-events: none;
  transition: opacity var(--vc-dur) var(--vc-ease);
}
.vcard-wrap:hover .vcard__bloom { opacity: 0.5; }

/* crisp two-tone gradient border (mask trick), inside the card, hidden at rest */
.vcard__edge {
  position: absolute; inset: 0; border-radius: inherit; padding: 1.6px;
  background: var(--svc-glow);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
  opacity: 0; pointer-events: none; z-index: 4;
  transition: opacity var(--vc-dur) var(--vc-ease);
}
.vcard:hover .vcard__edge { opacity: 1; }

/* cursor-tracked highlight (enhancement; centred without JS) */
.vcard__spot {
  position: absolute; inset: 0; border-radius: inherit; z-index: 3; pointer-events: none;
  background: radial-gradient(180px circle at var(--mx, 50%) var(--my, 50%),
              rgba(var(--svc-spot-rgb), 0.20), transparent 62%);
  opacity: 0; mix-blend-mode: var(--sheen-blend);
  transition: opacity var(--vc-dur) var(--vc-ease);
}
.vcard:hover .vcard__spot { opacity: 1; }

/* Hover-only product logo (AIVA / ATP cards — .vcard--logo). Hidden at rest; on hover the
   card's whole text body fades out and the logo fades + slides IN at the BOTTOM-LEFT, inset
   to match the body padding so it lands where the text sat. z-index 3 keeps it below the
   spotlight border (.vcard__edge, z-index 4) and below the (transparent-on-hover) body.
   Tokens only — no new colour. */
.vcard__hover-logo {
  position: absolute; z-index: 3; pointer-events: none;
  left: clamp(1.5rem, 2.4vw, 2.3rem); bottom: clamp(1.5rem, 2.4vw, 2.3rem);
  width: auto; object-fit: contain;
  opacity: 0; transform: translateY(10px);
  transition: opacity var(--vc-dur) var(--vc-ease), transform var(--vc-dur) var(--vc-ease);
}
.vcard__hover-logo--aiva { height: 40px; } /* wordmark */
.vcard__hover-logo--atp  { height: 58px; } /* taller — includes the subtitle line */
.vcard__hover-logo--aws  { height: 66px; } /* compact near-square mark — taller so its optical weight matches the wide AIVA/ATP marks */
.vcard--logo:hover .vcard__hover-logo { opacity: 1; transform: translateY(0); }
/* Fade the ENTIRE text body out on hover for the logo cards, so nothing sits under the logo
   (base cards keep the title and only fade .vcard__more). */
.vcard--logo .vcard__body { transition: opacity var(--vc-dur) var(--vc-ease); }
.vcard--logo:hover .vcard__body { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  .vcard-wrap, .vcard-wrap * { transition: none !important; }
  .vcard:hover { transform: none; }
  .vcard__hover-logo { transform: none; } /* appear instantly on hover, no slide */
}

/* ---- ATP-ONLY media card (.vcard--media) — always-visible helicopter background ----------
   The ATP card breaks from the sibling hover-reveal pattern: the heli photo shows at REST and
   on hover (a "cinema panel"), darkened by a scrim so the rest-state text reads. On hover the
   text fades to the ATP logo, the heli brightens + scales, and the scrim eases so the heli pops.
   Everything is scoped to .vcard--media, so AIVA / IT Solution / AWS are untouched. The
   rgba(6,7,10,…) scrim darks are intentional (dark in BOTH themes — footage stays a cinema
   panel); text uses the T2 on-media ink so it stays legible over the dark heli in both themes. */
.vcard--media::before { display: none; }   /* kill the sibling hover gradient placeholder */
.vcard__media {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: center;
  filter: brightness(0.6) saturate(0.94);
  transition: filter 0.6s var(--ease-out), transform 0.6s var(--ease-out);
  z-index: 0;
}
.vcard--media:hover .vcard__media { filter: brightness(0.82) saturate(1); transform: scale(1.05); }
.vcard__scrim {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  transition: opacity 0.6s var(--ease-out);
  /* Bottom-weighted: top stays clear so the heli (upper ~60%) reads; heavy dark only in the
     lower third where the text band lives. */
  background: linear-gradient(180deg,
    rgba(6,7,10,0.30) 0%, rgba(6,7,10,0.05) 34%, rgba(6,7,10,0.55) 62%, rgba(6,7,10,0.94) 100%);
}
.vcard--media:hover .vcard__scrim { opacity: 0.82; }

/* Text sits in the dark lower band (the card is already bottom-anchored via .vcard__body's
   justify-content:flex-end). Trim the ATP card's type to demo sizes so the whole block —
   eyebrow -> title -> description -> tags — fits the lower third and never covers the heli. */
.vcard--media .vcard__body { gap: 0.5rem; }
.vcard--media .vcard__title { font-size: clamp(1.3rem, 1.5vw, 1.55rem); }
.vcard--media .vcard__desc { font-size: 0.85rem; line-height: 1.5; }

/* Rest-state legibility over the darkened heli. on-media ink = readable in both themes and
   identical to the dark values, so dark renders unchanged. */
.vcard--media .vcard__eyebrow { color: var(--accent-on-media); text-shadow: 0 1px 8px rgba(0,0,0,0.5); }
.vcard--media .vcard__title   { color: var(--text-on-media); text-shadow: 0 2px 12px rgba(0,0,0,0.55); }
.vcard--media .vcard__desc    { color: var(--text-muted-on-media); text-shadow: 0 1px 8px rgba(0,0,0,0.5); }
.vcard--media .vcard__tags .tag {
  color: var(--text-muted-on-media);
  border-color: rgba(255,255,255,0.22);
  background: rgba(6,7,10,0.32);
}

/* Reduced-motion: heli + scrim stay static — no brighten/scale/scrim-ease on hover. */
@media (prefers-reduced-motion: reduce) {
  .vcard--media .vcard__media { transition: none; }
  .vcard--media:hover .vcard__media { filter: brightness(0.6) saturate(0.94); transform: none; }
  .vcard--media:hover .vcard__scrim { opacity: 1; }
}

/* ---- Full-capability BENTO grid ----
   Asymmetric mosaic: 4-col grid; featured tiles span 2x2 (--lg) or 2-wide (--wide); the
   rest are 1x1. DOM order is arranged so it packs with no gaps. On hover a tile scales up
   (transform, so it overlaps neighbours cleanly via z-index — never reflows the grid) and a
   one-line description fades/slides in below the title. Tokens only. Tunable knobs below. */
.bento {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  grid-auto-flow: dense;          /* packs the asymmetric tiles tightly; tolerates added tiles */
  grid-auto-rows: clamp(8.5rem, 13vw, 11rem);
  gap: clamp(0.6rem, 1.1vw, 1rem);
}
.bento__tile--lg   { grid-column: span 2; grid-row: span 2; } /* big square (easy to tune) */
.bento__tile--wide { grid-column: span 2; }                   /* 2-wide banner */

.bento__tile {
  /* Hover tuning (easy to tune): */
  --bento-dur: 0.42s;
  --bento-ease: cubic-bezier(0.65, 0, 0.35, 1); /* smooth ease-in-out (in AND out) */
  --bento-scale: 1.04;
  position: relative; z-index: 0;
  display: flex; flex-direction: column; justify-content: flex-end;
  padding: clamp(1.15rem, 1.8vw, 1.6rem);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition: transform var(--bento-dur) var(--bento-ease),
              background var(--bento-dur) var(--bento-ease),
              border-color var(--bento-dur) var(--bento-ease),
              box-shadow var(--bento-dur) var(--bento-ease);
  will-change: transform;
}
.bento__tile:hover {
  transform: scale(var(--bento-scale));
  z-index: 5;
  background: var(--surface-hover);
  border-color: var(--border-strong);
  box-shadow: var(--card-shadow);
}
.bento__dot {
  position: absolute; top: clamp(1.15rem, 1.8vw, 1.6rem); left: clamp(1.15rem, 1.8vw, 1.6rem);
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--accent-grad); box-shadow: 0 0 12px var(--glow);
}
.bento__title { font-family: var(--font-display); font-weight: 500; font-size: clamp(1.05rem, 1.4vw, 1.3rem); line-height: var(--lh-snug); }
.bento__tile--lg .bento__title { font-size: clamp(1.3rem, 1.9vw, 1.7rem); } /* featured reads larger */
.bento__desc {
  margin: 0.55rem 0 0; max-width: 40ch;
  font-size: var(--fs-sm); color: var(--text-muted); line-height: 1.45;
  opacity: 0; max-height: 0; transform: translateY(6px); overflow: hidden;
  transition: opacity var(--bento-dur) var(--bento-ease),
              max-height var(--bento-dur) var(--bento-ease),
              transform var(--bento-dur) var(--bento-ease);
}
.bento__tile:hover .bento__desc { opacity: 1; max-height: 9rem; transform: translateY(0); }

/* Tablet: collapse to 2 columns — big squares become full-width banners, drop the wide span. */
@media (max-width: 900px) {
  .bento { grid-template-columns: repeat(2, 1fr); grid-auto-rows: clamp(8rem, 22vw, 10rem); }
  .bento__tile--lg   { grid-column: span 2; grid-row: span 1; }
  .bento__tile--wide { grid-column: span 2; }
}
/* Mobile: single column, drop ALL spans — a readable stack. */
@media (max-width: 600px) {
  .bento { grid-template-columns: 1fr; grid-auto-rows: minmax(7rem, auto); }
  .bento__tile--lg, .bento__tile--wide { grid-column: auto; grid-row: auto; }
}
/* Touch devices (no hover): show each description without needing hover. */
@media (hover: none) {
  .bento__desc { opacity: 1; max-height: 9rem; transform: none; }
}

/* ---- Cursor-proximity SPOTLIGHT BORDER — homepage "Full Capability" bento only --------
   A thin border that lights up only near the cursor and tracks it across the whole grid:
   every tile computes the cursor relative to ITSELF (--mx/--my set per tile in JS), so the
   edge/corner nearest the pointer glows — the gap between two tiles lights BOTH facing edges.
   Driven entirely by --mx/--my (no :hover, no transition on the glow) so it tracks instantly.
   At rest (cursor off-grid / no-JS / reduced-motion) the vars stay off-tile, so only the
   tile's own faint hairline (border:1px var(--border)) shows. overflow:hidden on the tile is
   fine — the glow is masked to the border ring / kept inside. Halo = --accent-2; core = a
   near-white cyan highlight (not a brand token). */
.bento__tile {
  --mx: -400px; --my: -400px;          /* default: spotlight is off the tile */
  --spot-size: 280px;
  --core: var(--spot-core);   /* near-white cyan highlight (core) */
  --halo: var(--spot-halo);   /* accent-2 alpha halo */
}
/* faint inner glow following the cursor — behind the tile's content (above the surface bg) */
.bento__sheen {
  position: absolute; inset: 0; border-radius: inherit; z-index: -1; pointer-events: none;
  background: radial-gradient(320px circle at var(--mx) var(--my),
              var(--sheen), transparent 55%);
}
/* the spotlight border: radial light at the cursor, masked to the edge ring only */
.bento__border {
  position: absolute; inset: 0; border-radius: inherit; padding: 1.5px; z-index: 3;
  pointer-events: none;
  background: radial-gradient(var(--spot-size) circle at var(--mx) var(--my),
              var(--core) 0%, var(--halo) 30%, transparent 62%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
}

/* ---- Bento tiles with an always-visible background photo (.bento__tile--media) — the 7 tiles
   that have imagery. Mirrors the major-card treatment, calmer for the dense grid: the photo is
   darkened at rest and brightens slightly on hover; a bottom-weighted scrim keeps the bottom-left
   text readable while each tile's focal subject (upper area) stays clear. Scoped so the 3 image-less
   tiles (Financial Product, Device Management, Telecommunications) are untouched. The rgba(6,7,10,…)
   scrim darks are intentional (dark in both themes). Text uses the T2 on-media ink so it stays
   legible over the dark photo in both themes. The faint inner .bento__sheen sits behind the opaque
   photo here (hidden); the cursor-proximity spotlight border (.bento__border, z-index 3) stays on
   top and still tracks. z-order: media(0) -> scrim(1) -> text/dot(2) -> spotlight border(3). */
.bento__tile--media { isolation: isolate; }
.bento__tile--media .bento__media {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: center;
  filter: brightness(0.55) saturate(0.92);
  transition: filter 0.5s var(--ease-out);
  z-index: 0;
}
.bento__tile--media:hover .bento__media { filter: brightness(0.72) saturate(1); }
.bento__tile--media .bento__scrim {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: linear-gradient(180deg,
    rgba(6,7,10,0.35) 0%, rgba(6,7,10,0.15) 40%, rgba(6,7,10,0.62) 68%, rgba(6,7,10,0.92) 100%);
}
/* Lift the existing bottom-left text + dot above the scrim; add legibility shadow + on-media ink. */
.bento__tile--media .bento__dot { z-index: 2; }
.bento__tile--media .bento__title {
  position: relative; z-index: 2; color: var(--text-on-media);
  text-shadow: 0 2px 12px rgba(0,0,0,0.55);
}
.bento__tile--media .bento__desc {
  position: relative; z-index: 2; color: var(--text-muted-on-media);
  text-shadow: 0 1px 8px rgba(0,0,0,0.5);
}
/* Reduced-motion: photo static — no brightness transition or hover brighten. */
@media (prefers-reduced-motion: reduce) {
  .bento__tile--media .bento__media { transition: none; }
  .bento__tile--media:hover .bento__media { filter: brightness(0.55) saturate(0.92); }
}

/* ===== LIGHT-MODE overrides for the homepage photo cards (T2 theme-aware swap) ==============
   In light mode the photos swap to their brighter -light versions (via js/theme.js), so the
   dark filters/scrims/ink must flip to a light treatment. Everything here is under
   [data-theme="light"], so DARK mode stays pixel-identical. The ATP heli (.vcard--media, same
   daylight photo both themes) and the swapped AWS light photo both read naturally at ~full
   brightness; bento light photos are already bright so filters stay subtle. */

/* Media — light photos are already bright, keep it subtle (ATP heli + AWS share the vcard rule). */
[data-theme="light"] .vcard__media { filter: brightness(0.95) saturate(1); }
[data-theme="light"] .vcard--media:hover .vcard__media { filter: brightness(1.02); }
[data-theme="light"] .bento__media { filter: brightness(0.96) saturate(1); }
[data-theme="light"] .bento__tile--media:hover .bento__media { filter: brightness(1.03); }

/* Scrims flip dark -> light (white gradient in the light bg tone). Text zone (lower ~40%)
   is near-solid so dark ink passes contrast over bright photos — incl. on hover, when the
   desc reveals over the brightened image. Upper zone stays subtle so the photo reads. */
[data-theme="light"] .vcard__scrim,
[data-theme="light"] .bento__scrim {
  background: linear-gradient(180deg,
    rgba(245,247,251,0.35) 0%, rgba(245,247,251,0.18) 38%, rgba(245,247,251,0.85) 62%, rgba(245,247,251,0.97) 100%);
}

/* Text flips to dark theme ink with a white glow so it reads over the bright photos. */
[data-theme="light"] .vcard--media .vcard__title,
[data-theme="light"] .bento__tile--media .bento__title { color: var(--text); text-shadow: 0 1px 10px rgba(255,255,255,0.65); }
[data-theme="light"] .vcard--media .vcard__desc,
[data-theme="light"] .bento__tile--media .bento__desc { color: var(--text-muted); text-shadow: 0 1px 12px rgba(255,255,255,0.85); }
[data-theme="light"] .vcard--media .vcard__eyebrow { color: var(--accent); text-shadow: 0 1px 10px rgba(255,255,255,0.65); }
[data-theme="light"] .vcard--media .vcard__tags .tag { color: var(--text); background: rgba(245,247,251,0.5); border-color: rgba(13,19,32,0.18); }

/* Reduced-motion in light mode: hover = light rest (no brighten). Placed last so it wins. */
@media (prefers-reduced-motion: reduce) {
  [data-theme="light"] .vcard--media:hover .vcard__media { filter: brightness(0.95) saturate(1); }
  [data-theme="light"] .bento__tile--media:hover .bento__media { filter: brightness(0.96) saturate(1); }
}

/* Capabilities — IT sub-tiles list */
.caps { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 1px; background: var(--border); border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; }
.cap { background: var(--bg-elev); padding: 1.6rem 1.5rem; transition: background var(--dur) var(--ease-out); }
.cap:hover { background: var(--surface-2); }
.cap__idx { font-family: var(--font-mono); font-size: 0.7rem; color: var(--text-dim); letter-spacing: 0.18em; }
.cap h3 { font-size: 1.15rem; margin: 0.7rem 0 0.4rem; }
.cap p { font-size: var(--fs-sm); color: var(--text-muted); }

/* Hikvision "product families" bento — a typographic index (no images/scrims) of the 7 Hikvision
   product families. 4-col grid with EXACT parity, so natural DOM order fills gap-free (no dense flow):
   row1 = 01+02 (span-2 each) · row2 = 03 (span-2) + 04 + 05 · row3 = 06 + 07 + CTA (span-2).
   Reuses .bento__tile (spotlight border/hover) + the shared spotlight JS (scoped to .hik-caps) +
   the data-reveal stagger. Scoped to this section; the .bento masonry grid is untouched. */
.hik-caps { display: grid; grid-template-columns: repeat(4, 1fr); gap: clamp(0.6rem, 1.1vw, 1rem); grid-auto-rows: minmax(clamp(7rem, 9vw, 9rem), auto); margin-top: clamp(1.5rem, 3vw, 2.5rem); }
/* tiles reuse .bento__tile (surface/border/radius/hover/spotlight); re-laid-out for a text index —
   top-aligned, always-visible copy (no hover-reveal). */
.hik-tile { justify-content: flex-start; padding: clamp(1rem, 1.6vw, 1.5rem); }
.hik-tile--span2 { grid-column: span 2; }
.hik-tile--sm { padding: clamp(0.85rem, 1.3vw, 1.15rem); }          /* span-1 tiles have less room */
.hik-tile--sm .hik-tile__desc { font-size: 0.8rem; }
.hik-tile__n     { font-family: var(--font-mono); font-size: 0.7rem; letter-spacing: 0.18em; color: var(--text-dim); }
.hik-tile__title { font-family: var(--font-display); font-weight: 500; font-size: clamp(1.05rem, 1.5vw, 1.4rem); line-height: var(--lh-snug); margin: 0.5rem 0 0; }
.hik-tile__desc  { margin: 0.5rem 0 0; font-size: var(--fs-sm); color: var(--text-muted); line-height: 1.5; }
.hik-tile__subs  { margin: 0.85rem 0 0; font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.02em; color: var(--text-dim); line-height: 1.7; }
/* CTA tile — accent treatment within tokens (accent border + text, no fill outside the set) */
.hik-tile--cta   { flex-direction: row; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 0.8rem 1.5rem; border-color: var(--accent); }
.hik-tile--cta:hover { transform: none; border-color: var(--accent); }
.hik-tile__note  { margin: 0; color: var(--accent-2); font-size: var(--fs-sm); }
/* Hikvision CTA card — two-button contact block (reuses .btn--primary / .btn--ghost; tokens only). */
.hik-cta { display: flex; flex-direction: column; align-items: flex-start; gap: 0.6rem; }
.hik-cta__btns { display: flex; gap: 10px; flex-wrap: wrap; }
.hik-cta__direct { margin: 0; color: var(--text-muted); font-family: var(--font-mono); font-size: var(--fs-sm); }
.btn__ico { width: 1.15em; height: 1.15em; flex: 0 0 auto; }
/* ≤1024px → 2 columns: span-2 tiles + CTA become full-width, span-1 half (all via their spans). */
@media (max-width: 1024px) { .hik-caps { grid-template-columns: repeat(2, 1fr); } }
/* ≤720px → single column; span-2 tiles clamp to full-width, sub-labels wrap freely. */
@media (max-width: 720px) {
  .hik-caps { grid-template-columns: 1fr; }
  .hik-tile--span2 { grid-column: 1 / -1; }
}

/* ---- BACKGROUND product art: one oversized, faded, edge-bleeding <img> per FAMILY tile (never the
   CTA). Sits BELOW the copy (z-index:-1) and ABOVE the tile bg; the tile's overflow:hidden crops the
   bleed to the rounded rect. A left-fading ALPHA mask (house feather pattern, #000→transparent)
   dissolves the art toward the text so copy zones stay clean with no hard edge. Very low opacity =
   ambient texture, not a product shot — so copy runs full width again (no padding reserve) and casts
   no shadow. Sized to the TILE, not the content. Alpha-only mask, no new colours. ---- */
.hik-tile__art {
  position: absolute; z-index: -1; pointer-events: none; display: block;
  width: auto; height: auto; opacity: 0.3;
  -webkit-mask-image: linear-gradient(to left, #000 45%, transparent 88%);
          mask-image: linear-gradient(to left, #000 45%, transparent 88%);
}
[data-theme="light"] .hik-tile__art { opacity: 0.22; }
/* span-2: tall art (~160% of tile height) anchored bottom-right; ~a third bleeds off the right + bottom (cropped). */
.hik-tile--span2 .hik-tile__art { height: 160%; width: auto; right: -12%; bottom: -35%; top: auto; left: auto; transform: none; max-height: none; max-width: none; }
/* span-1: ~130% of tile height, anchored bottom-right, bleeding past the corner. */
.hik-tile--sm .hik-tile__art { height: 130%; width: auto; right: -12%; bottom: -20%; top: auto; left: auto; transform: none; max-height: none; max-width: none; }

/* Clients — infinite logo marquee. A single row of small light chips scrolls RIGHT-TO-LEFT
   forever; the set is rendered twice and the track translates -50% for a seamless loop.
   Light chips keep full-colour / dark-text marks legible on the dark section. Pause on hover;
   soft edge-fade masks; reduced-motion shows a static wrapped row. Tunable knobs below. */
.logo-marquee {
  --mq-speed: 42s;                        /* full-loop duration — higher = slower */
  --mq-gap: clamp(0.8rem, 1.8vw, 1.4rem); /* gap between chips */
  --mq-fade: clamp(32px, 7vw, 96px);      /* soft edge-fade width */
  --chip-h: 62px;                         /* compact chip height */
  --logo-h: 38px;                         /* logo max-height inside the chip */
  position: relative; overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 var(--mq-fade), #000 calc(100% - var(--mq-fade)), transparent 100%);
          mask-image: linear-gradient(90deg, transparent 0, #000 var(--mq-fade), #000 calc(100% - var(--mq-fade)), transparent 100%);
}
.logo-marquee__track {
  display: flex; width: max-content;
  animation: clientMarquee var(--mq-speed) linear infinite; /* slow, continuous right-to-left */
  will-change: transform;
}
.logo-marquee:hover .logo-marquee__track { animation-play-state: paused; } /* pause to inspect */
.logo-marquee__set {
  list-style: none; margin: 0; padding: 0;
  display: flex; align-items: center; gap: var(--mq-gap);
  padding-right: var(--mq-gap);           /* keeps the gap consistent across the loop seam */
}
.logo-chip {
  flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  height: var(--chip-h); min-width: 142px;
  padding: 0 clamp(1rem, 1.8vw, 1.4rem);
  background: #f4f6fa;                     /* intentional light exception (logo legibility) */
  border: 1px solid rgba(0, 0, 0, 0.06);
  border-radius: 14px;
  box-shadow: 0 6px 18px -12px rgba(0, 0, 0, 0.5);
}
.logo-chip img { display: block; width: auto; height: auto; max-height: var(--logo-h); max-width: 100%; object-fit: contain; }

@keyframes clientMarquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }

/* Reduced motion: no auto-scroll — one static, centred, wrapped row (single set). */
@media (prefers-reduced-motion: reduce) {
  .logo-marquee { overflow: visible; -webkit-mask-image: none; mask-image: none; }
  .logo-marquee__track { animation: none; display: block; width: 100%; }
  .logo-marquee__set[aria-hidden="true"] { display: none; }
  .logo-marquee__set { flex-wrap: wrap; justify-content: center; row-gap: 1rem; padding-right: 0; }
}

/* Second client row — scrolls the opposite way (left-to-right) by reversing the shared
   keyframe; chips, mask, hover-pause and reduced-motion are all inherited from .logo-marquee. */
.logo-marquee + .logo-marquee { margin-top: clamp(0.9rem, 2vw, 1.5rem); }
.logo-marquee--reverse .logo-marquee__track { animation-direction: reverse; }

/* Case studies */
.cases { display: grid; grid-template-columns: 1fr; gap: 1.25rem; }
@media (min-width: 760px) { .cases { grid-template-columns: 1fr 1fr; } }
.case {
  position: relative; padding: clamp(1.8rem, 3.5vw, 2.6rem);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
  min-height: 240px; display: flex; flex-direction: column; justify-content: space-between;
  transition: border-color var(--dur) var(--ease-out), transform var(--dur) var(--ease-out);
}
.case:hover { transform: translateY(-4px); border-color: var(--border-strong); }
.case__label { font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.18em; text-transform: uppercase; color: var(--accent); }
.case h3 { font-size: var(--fs-h3); margin-top: 0.8rem; max-width: 22ch; }
.case__link { margin-top: 1.5rem; font-family: var(--font-mono); font-size: 0.78rem; letter-spacing: 0.04em; color: var(--text); display: inline-flex; gap: 0.4rem; }

/* CTA */
.cta { text-align: center; }
.cta h2 { font-size: clamp(2rem, 5vw, 4rem); line-height: 1.05; max-width: 18ch; margin: 0 auto 1.6rem; }
.cta .lead { margin: 0 auto 2.4rem; text-align: center; }
.cta__actions { display: flex; gap: 0.9rem; justify-content: center; flex-wrap: wrap; }

/* ============================================================
   INNER PAGES (case studies + service pages) — clean & light, no canvas
   Shared by all templated inner pages; tokens only.
   ============================================================ */
/* ---- Breadcrumb (inner pages only) — a quiet utility bar that clears the fixed nav and
   sits above the hero. Tokens only; small mono type; links go accent on hover; the current
   crumb is muted + non-interactive. Wraps gracefully on narrow screens (never overflows). */
.breadcrumb { position: relative; z-index: 1; padding-top: clamp(4.75rem, 7vh, 5.5rem); }
.breadcrumb__list {
  list-style: none; margin: 0;
  display: flex; flex-wrap: wrap; align-items: center;
  padding-bottom: 0.85rem;            /* .container supplies the horizontal padding */
  font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.06em;
}
.breadcrumb__item { display: inline-flex; align-items: center; min-width: 0; }
.breadcrumb__item + .breadcrumb__item::before {   /* decorative separator (not in the a11y tree) */
  content: "\203A"; margin: 0 0.55rem; color: var(--text-dim);
}
.breadcrumb__list a { color: var(--text-muted); transition: color var(--dur-fast) var(--ease-out); }
.breadcrumb__list a:hover, .breadcrumb__list a:focus-visible { color: var(--accent); }
.breadcrumb__item [aria-current="page"] { color: var(--text-dim); overflow-wrap: anywhere; } /* current = muted, non-link */

/* Text hero — top padding is small because the breadcrumb above already clears the nav */
.page-hero { position: relative; z-index: 1; padding-block: clamp(1rem, 2.5vw, 1.75rem) clamp(2.5rem, 5vw, 4rem); }
.page-hero__title { font-size: clamp(2rem, 4.6vw, 3.6rem); line-height: var(--lh-snug); max-width: 22ch; margin-top: 1.1rem; }
.page-hero__lead { margin-top: 1.6rem; max-width: 60ch; }

/* Back link (mono, subtle) */
.backlink { display: inline-flex; align-items: center; gap: 0.5rem; font-family: var(--font-mono); font-size: 0.74rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-muted); transition: color var(--dur-fast) var(--ease-out); }
.backlink:hover { color: var(--text); }
.backlink .arrow { transition: transform var(--dur) var(--ease-out); }
.backlink:hover .arrow { transform: translateX(-3px); }

/* Prose block (overview / feedback body) */
.prose { max-width: 72ch; display: grid; gap: 1.2rem; }
.prose p { color: var(--text-muted); }
.prose strong { color: var(--text); font-weight: 500; }

/* Stat callouts — big number + small label */
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1px; background: var(--border); border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; }
.stat { background: var(--bg-elev); padding: clamp(1.6rem, 3vw, 2.4rem); transition: background var(--dur) var(--ease-out); }
.stat:hover { background: var(--surface-2); }
.stat__num { font-family: var(--font-display); font-weight: 500; font-size: clamp(1.9rem, 4vw, 2.7rem); line-height: 1.05; letter-spacing: -0.02em; color: var(--accent); background: var(--accent-grad); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; }
/* Word-based stat values (e.g. "Lease-to-Own", "Active-Active") — stepped down so the phrase
   fits instead of clipping at the card edge; wraps at spaces/hyphens (break-word is a last-resort
   safety, NOT overflow-wrap:anywhere). Numeric stats keep the large size above. */
.stat__num--text { font-size: clamp(1.35rem, 2.4vw, 1.9rem); line-height: 1.15; overflow-wrap: break-word; }
.stat__label { font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--text-muted); margin-top: 0.9rem; }

/* Services list — numbered term + description */
.svc-list { display: grid; gap: 1px; background: var(--border); border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; }
.svc-item { background: var(--bg-elev); padding: clamp(1.3rem, 2.4vw, 1.8rem) clamp(1.4rem, 2.4vw, 2rem); display: grid; grid-template-columns: 2.6rem 1fr; gap: 0 1rem; align-items: start; transition: background var(--dur) var(--ease-out); }
.svc-item:hover { background: var(--surface-2); }
.svc-item__idx { font-family: var(--font-mono); font-size: 0.74rem; color: var(--accent); letter-spacing: 0.1em; padding-top: 0.25rem; }
.svc-item h3 { font-size: 1.1rem; margin: 0 0 0.4rem; }
.svc-item p { color: var(--text-muted); font-size: var(--fs-sm); max-width: 70ch; }

/* Customer feedback — accent callout card */
.feedback { max-width: 64ch; padding: clamp(1.8rem, 3.5vw, 2.6rem); background: var(--surface); border: 1px solid var(--border); border-left: 3px solid var(--accent); border-radius: var(--radius-lg); box-shadow: var(--shadow-card); }
.feedback p { font-family: var(--font-display); font-weight: 400; font-size: var(--fs-lead); line-height: 1.5; color: var(--text); }
.feedback__note { margin-top: 1.1rem; font-size: var(--fs-sm); color: var(--text-dim); font-style: italic; }

/* Sequential lifecycle / process steps (numbered — reads as an ordered flow) */
.steps { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1px; background: var(--border); border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; }
.step { background: var(--bg-elev); padding: clamp(1.6rem, 3vw, 2.2rem); transition: background var(--dur) var(--ease-out); }
.step:hover { background: var(--surface-2); }
.step__no { font-family: var(--font-display); font-weight: 500; font-size: clamp(1.8rem, 3.5vw, 2.6rem); line-height: 1; letter-spacing: -0.02em; color: var(--accent); background: var(--accent-grad); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; }
.step h3 { font-size: 1.15rem; margin: 0.8rem 0 0.4rem; }
.step p { font-size: var(--fs-sm); color: var(--text-muted); }

/* Chip row — small wrap of .tag pills (badges, tech pillars) */
.chip-row { display: flex; flex-wrap: wrap; gap: 0.5rem; }

/* ============================================================
   FOOTER
   ============================================================ */
.footer { position: relative; z-index: 1; border-top: 1px solid var(--border); padding-block: clamp(3rem, 6vw, 5rem) 2rem; background: var(--bg-deep); }
.footer__grid { display: grid; grid-template-columns: 1fr; gap: 2.5rem; }
@media (min-width: 760px) { .footer__grid { grid-template-columns: 1.5fr 1fr 1.2fr; } }
.footer__brand img { height: 44px; width: auto; display: block; } /* real logo: white-on-transparent, no filter */
.footer__tag { color: var(--text-muted); max-width: 32ch; margin-top: 1.1rem; font-size: var(--fs-sm); }
/* Social icons — monochrome inline SVG (currentColor); idle muted, hover/focus accent. Both themes.
   Links carry 10px padding for ~40px touch targets (a11y); gap 0 + negative offsets keep the
   icons' visual position and spacing essentially unchanged. */
.footer__social { display: flex; align-items: center; gap: 0; margin-top: calc(1.3rem - 10px); margin-left: -10px; }
.footer__social a { color: var(--text-muted); display: inline-flex; padding: 10px; transition: color var(--dur) var(--ease-out); }
.footer__social a:hover, .footer__social a:focus-visible { color: var(--accent); }
.footer__social svg { width: 20px; height: 20px; display: block; }
/* h4->h3 swap (C2-4): grouped + font-weight pinned to the UA-h4 700 the rule relied on. */
.footer h4, .footer h3 { font-family: var(--font-mono); font-size: 0.74rem; font-weight: 700; letter-spacing: 0.18em; text-transform: uppercase; color: var(--text-dim); margin: 0 0 1.1rem; }
.footer__links { display: grid; gap: 0.7rem; }
.footer__links a { font-size: var(--fs-sm); color: var(--text-muted); transition: color var(--dur-fast) var(--ease-out); }
.footer__links a:hover { color: var(--text); }
.footer__addr { font-size: var(--fs-sm); color: var(--text-muted); line-height: 1.6; font-style: normal; }
.footer__bottom { margin-top: 3rem; padding-top: 1.5rem; border-top: 1px solid var(--border); display: flex; justify-content: space-between; flex-wrap: wrap; gap: 1rem; font-family: var(--font-mono); font-size: 0.82rem; color: var(--text-dim); letter-spacing: 0.04em; }

/* ============================================================
   PRELOADER
   ============================================================ */
.preloader {
  position: fixed; inset: 0; z-index: var(--z-preloader);
  background: var(--bg-deep);
  display: grid; place-items: center;
  transition: opacity 0.6s var(--ease-out), visibility 0.6s;
}
.preloader.is-done { opacity: 0; visibility: hidden; }
.preloader__inner { text-align: center; }
.preloader__mark { font-family: var(--font-display); font-weight: 700; font-size: 1.4rem; letter-spacing: -0.01em; margin-bottom: 1.4rem; }
.preloader__bar { width: 200px; height: 2px; background: var(--surface-2); border-radius: 2px; overflow: hidden; margin: 0 auto; }
.preloader__fill { height: 100%; width: 0; background: var(--accent-grad); transition: width 0.2s linear; }
.preloader__label { font-family: var(--font-mono); font-size: 0.7rem; letter-spacing: 0.2em; text-transform: uppercase; color: var(--text-dim); margin-top: 1rem; }
/* Preloader logotype = the Weststar (reversed/white) logo. Height-sized; aspect comes from the
   width/height attrs so it reserves space (no layout shift). The preloader stays DARK in BOTH
   themes so the reversed mark + faint bar track + label always read — pinned to the existing
   dark-theme token values (no new colours). */
.preloader__logo { display: block; height: 40px; width: auto; margin: 0 auto; }
[data-theme="light"] .preloader { background: #06070a; }
[data-theme="light"] .preloader__bar { background: rgba(255, 255, 255, 0.06); }
[data-theme="light"] .preloader__label { color: #5d6573; }

/* ============================================================
   Reveal initial state (JS toggles via GSAP; CSS fallback below)
   ============================================================ */
[data-reveal] { will-change: transform, opacity; }

/* If JS/GSAP unavailable or reduced motion: show everything, no transform */
.no-anim [data-reveal] { opacity: 1 !important; transform: none !important; }

/* Tunnel beats: if the scrub engine can't run (no GSAP/ScrollTrigger), collapse
   the tall section and show all four beats as a readable vertical stack. */
.no-anim .scrub--tunnel { height: auto; }
.no-anim .scrub--tunnel .scrub__sticky { position: static; height: auto; overflow: visible; }
.no-anim .scrub--tunnel .scrub__canvas,
.no-anim .scrub--tunnel .scrub__sticky::before,
.no-anim .scrub--tunnel .scrub__sticky::after { display: none; }
.no-anim .scrub--tunnel .scrub__overlay { position: static; }
.no-anim .scrub--tunnel .beat { position: static; opacity: 1; visibility: visible; transform: none; pointer-events: auto; padding-block: clamp(2.5rem, 6vw, 4.5rem); }
.no-anim .scrub--tunnel .beat__inner { transform: none; max-width: 60ch; }
.no-anim .scrub--tunnel .beat__inner::before { display: none; }

/* Hero: same collapse when the scrub engine can't run — stack BEAT 1 (intro) +
   BEAT 2 ("what we do") as a readable vertical list; hide the footage layers. */
.no-anim .scrub--hero { height: auto; }
/* !important: the sticky carries the poster as an INLINE background (belt-and-braces for
   the video path) — the collapsed text stack must sit on the plain page surface instead. */
.no-anim .scrub--hero .scrub__sticky { position: static; height: auto; overflow: visible; background: var(--bg-deep) !important; }
.no-anim .scrub--hero .scrub__canvas,
.no-anim .scrub--hero .scrub__video,
.no-anim .scrub--hero .hero-scrim,
.no-anim .scrub--hero .scroll-hint { display: none; }
.no-anim .scrub--hero .scrub__overlay { position: static; }
.no-anim .scrub--hero .hero { position: static; transform: none; padding-block: clamp(3rem, 7vw, 5.5rem); }
.no-anim .scrub--hero .beat { position: static; opacity: 1; visibility: visible; transform: none; pointer-events: auto; padding-block: clamp(2.5rem, 6vw, 4.5rem); }
.no-anim .scrub--hero .hero-beat { display: block; }
.no-anim .scrub--hero .hero-beat__head,
.no-anim .scrub--hero .hero-beat__desc { opacity: 1; transform: none; } /* no entrance animation */
.no-anim .scrub--hero .hero-beat__desc { align-self: auto; text-align: left; max-width: 60ch; margin-top: 1.5rem; }
.no-anim .scrub--hero .hero-beat__head::before,
.no-anim .scrub--hero .hero-beat__desc::before { display: none; }

@media (prefers-reduced-motion: reduce) {
  .scroll-hint, .scroll-hint .mouse::after { animation: none; }
  * { scroll-behavior: auto !important; }

  /* The tunnel narrative must stay fully readable without scroll choreography:
     collapse the section and stack all four beats as a normal list. */
  .scrub--tunnel { height: auto; }
  .scrub--tunnel .scrub__sticky { position: static; height: auto; overflow: visible; }
  .scrub--tunnel .scrub__canvas,
  .scrub--tunnel .scrub__sticky::before,
  .scrub--tunnel .scrub__sticky::after { display: none; }
  .scrub--tunnel .scrub__overlay { position: static; }
  .scrub--tunnel .beat { position: static; opacity: 1; visibility: visible; transform: none; pointer-events: auto; padding-block: clamp(2.5rem, 6vw, 4.5rem); }
  .scrub--tunnel .beat__inner { transform: none; max-width: 60ch; }
  .scrub--tunnel .beat__inner::before { display: none; }

  /* Hero: collapse + stack BEAT 1 (intro) and BEAT 2 ("what we do") readably (matches tunnel). */
  .scrub--hero { height: auto; }
  /* !important: beat the inline poster background on the sticky (see .no-anim note). */
  .scrub--hero .scrub__sticky { position: static; height: auto; overflow: visible; background: var(--bg-deep) !important; }
  .scrub--hero .scrub__canvas,
  .scrub--hero .scrub__video,
  .scrub--hero .hero-scrim,
  .scrub--hero .scroll-hint { display: none; }
  .scrub--hero .scrub__overlay { position: static; }
  .scrub--hero .hero { position: static; transform: none; padding-block: clamp(3rem, 7vw, 5.5rem); }
  .scrub--hero .beat { position: static; opacity: 1; visibility: visible; transform: none; pointer-events: auto; padding-block: clamp(2.5rem, 6vw, 4.5rem); }
  .scrub--hero .hero-beat { display: block; }
  .scrub--hero .hero-beat__head,
  .scrub--hero .hero-beat__desc { opacity: 1; transform: none; } /* show final state, no entrance */
  .scrub--hero .hero-beat__desc { align-self: auto; text-align: left; max-width: 60ch; margin-top: 1.5rem; }
  .scrub--hero .hero-beat__head::before,
  .scrub--hero .hero-beat__desc::before { display: none; }

  /* Service cards: show the image-background + text without hover (no transitions). */
  .vcard, .vcard__more { transition: none; }
  .vcard::before, .vcard__img { opacity: 1; transform: none; transition: none; }
  .vcard__overlay { opacity: 1; }
  .vcard__more { opacity: 1; max-height: none; }

  /* Bento: no scale/motion — show titles + descriptions in a static readable state. */
  .bento__tile { transition: none; }
  .bento__tile:hover { transform: none; }
  .bento__desc { opacity: 1; max-height: 9rem; transform: none; transition: none; }

  /* Theme-ink resets (T2): hero/tunnel collapse above, so their text sits on the PAGE
     background — it must follow the theme again, not the on-media cinema ink. */
  .scrub--hero .scrub__overlay, .scrub--tunnel .scrub__overlay { color: inherit; }
  .scrub--hero .scrub__overlay .eyebrow, .scrub--tunnel .scrub__overlay .eyebrow { color: var(--accent); }
  .scrub--hero .scrub__overlay .eyebrow::before, .scrub--tunnel .scrub__overlay .eyebrow::before { background: var(--accent); }
  .scrub--hero .scrub__overlay .hero__title em,
  .scrub--hero .scrub__overlay .hero-beat__title em { background: var(--accent-grad); -webkit-background-clip: text; background-clip: text; }
  .scrub--hero .scrub__overlay .hero__sub,
  .scrub--hero .scrub__overlay .hero-beat__desc,
  .scrub--tunnel .scrub__overlay .beat__sub { color: var(--text-muted); }

  /* Service cards show their image permanently here -> keep on-media ink over it. */
  .vcard__eyebrow { color: var(--accent-on-media); }
  .vcard__title { color: var(--text-on-media); }
  .vcard__desc { color: var(--text-muted-on-media); }
}

/* ============================================================
   Bento — pipeline (parked-until-launch) tiles + inquiry CTA tile
   index.html product grid. Tokens/alpha only; no new colours.
   ============================================================ */

/* Pipeline tiles: non-interactive, greyed, STATIC (no hover recovery) so they
   read as firmly not-yet-open. The media filter compounds the base media filter
   (mirrored below) with an additional brightness(.75) saturate(.45) — i.e. dimmer
   + greyer than a live tile, in both themes. */
.bento-tile--pipeline { cursor: default; }
.bento-tile--pipeline:hover {
  transform: none; z-index: 0;
  background: var(--surface); border-color: var(--border); box-shadow: none;
}
.bento-tile--pipeline .bento__media,
.bento-tile--pipeline:hover .bento__media {
  filter: brightness(0.55) saturate(0.92) brightness(0.75) saturate(0.45);
}
[data-theme="light"] .bento-tile--pipeline .bento__media,
[data-theme="light"] .bento-tile--pipeline:hover .bento__media {
  filter: brightness(0.96) saturate(1) brightness(0.75) saturate(0.45);
}
.bento-tile--pipeline .bento__title { opacity: 0.7; }

/* Micro-label ("Available at launch") — house eyebrow type, smallest size, above the title. */
.bento-tile__micro {
  position: relative; z-index: 2;
  display: block; margin-bottom: 0.35rem;
  font-family: var(--font-mono); font-size: 0.62rem;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--text-dim);
}
/* Light mode: --text-dim is too weak over the (greyed but bright) photos — step up to the
   muted ink + the same white glow the other light-mode on-photo text uses. Dark unchanged. */
[data-theme="light"] .bento__tile .bento-tile__micro { color: var(--text-muted); text-shadow: 0 1px 10px rgba(255,255,255,0.65); }
/* ATP major card's micro-label uses the fixed pale on-media ink — inverts to unreadable over
   the light-scrimmed photo in light mode. Same muted-ink + white-glow treatment. */
[data-theme="light"] .vcard__body .bento-tile__micro { color: var(--text-muted); text-shadow: 0 1px 10px rgba(255,255,255,0.65); }

/* Inquiry CTA tile — reuses the Hikvision CTA-tile treatment (accent border + accent link);
   keeps the bento spotlight border, reveal and hover lift. */
.bento-tile--cta,
.bento-tile--cta:hover { border-color: var(--accent); }
.bento-tile__cta-link {
  position: relative; z-index: 2; margin-top: 0.55rem;
  display: inline-flex; align-items: center; gap: 0.4rem;
  font-family: var(--font-mono); font-size: var(--fs-sm);
  letter-spacing: 0.04em; color: var(--accent-2);
}
.bento-tile__cta-link .arrow { transition: transform var(--dur) var(--ease-out); }
.bento-tile--cta:hover .bento-tile__cta-link .arrow { transform: translateX(3px); }

/* ATP major card — same parked-until-launch treatment, adapted to the .vcard--media
   structure. Non-interactive + FULLY static: no hover lift/scale, no glow, no logo swap,
   no text fade. The media filter compounds the base .vcard__media filter (mirrored here)
   with the additional brightness(.75) saturate(.45). */
.vcard--pipeline { cursor: default; }
.vcard--pipeline:hover { transform: none; box-shadow: none; border-color: var(--border); }
.vcard--pipeline .vcard__media,
.vcard--pipeline:hover .vcard__media {
  filter: brightness(0.6) saturate(0.94) brightness(0.75) saturate(0.45);
  transform: none;
}
[data-theme="light"] .vcard--pipeline .vcard__media,
[data-theme="light"] .vcard--pipeline:hover .vcard__media {
  filter: brightness(0.95) saturate(1) brightness(0.75) saturate(0.45);
}
.vcard--pipeline .vcard__eyebrow,
.vcard--pipeline .vcard__title,
.vcard--pipeline .vcard__more { opacity: 0.7; }
.vcard--pipeline:hover .vcard__more { opacity: 0.7; max-height: 18rem; }
.vcard--pipeline:hover .vcard__edge,
.vcard--pipeline:hover .vcard__spot { opacity: 0; }
.vcard-wrap:has(.vcard--pipeline):hover .vcard__bloom { opacity: 0; }
/* micro-label reuses the bento style; on-media ink for legibility over the photo, body gap owns spacing */
.vcard__body .bento-tile__micro { margin-bottom: 0; color: var(--text-muted-on-media); text-shadow: 0 1px 8px rgba(0,0,0,0.5); }
