/* =============================================================
   JX EXHIBITS — Exhibition Design & Build
   Design system / global stylesheet
   ============================================================= */

@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700;800&family=Inter:wght@400;500;600;700&family=Playfair+Display:wght@600;700&display=swap');

/* ---------- Tokens ------------------------------------------ */
:root {
  --ink:        #0f1016;
  --ink-2:      #1c1d25;
  --body:       #565b6b;
  --muted:      #888ea0;
  --line:       #e6e7ec;
  --line-soft:  #eef0f3;

  --bg:         #ffffff;
  --bg-soft:    #f5f6f8;
  --bg-soft-2:  #eceef2;
  --bg-tint:    #eaecfb;   /* brand-tinted section ground (featured showcase) */

  --brand:      #4038d4;   /* primary solid            */
  --brand-700:  #332ac2;
  --brand-600:  #4038d4;
  --brand-500:  #4f46e5;
  --brand-400:  #6a5cf5;
  --brand-ink:  #4a40e0;   /* links / eyebrows on white */
  --brand-soft: #ecebfb;

  --grad-cta:   linear-gradient(112deg, #2d29c4 0%, #4234d8 46%, #5b3fe6 100%);
  --grad-btn:   linear-gradient(115deg, #4038d4 0%, #5b3fe6 100%);

  /* dark surface */
  --dk-bg:      #0a0a14;
  --dk-bg-2:    #0d0d1a;
  --dk-card:    #14141f;
  --dk-line:    #262636;
  --dk-text:    #edecf5;
  --dk-muted:   #9b9bb4;

  --shadow-sm:  0 1px 2px rgba(16,18,30,.06), 0 6px 20px rgba(16,18,30,.06);
  --shadow-md:  0 10px 30px rgba(16,18,30,.10);
  --shadow-lg:  0 24px 60px rgba(16,18,30,.16);
  --shadow-brand: 0 14px 34px rgba(64,56,212,.32);

  --radius:     14px;
  --radius-lg:  20px;
  --radius-sm:  10px;

  --container:  1220px;
  --gutter:     clamp(20px, 4vw, 48px);
  /* Trimmed from clamp(64px, 8.5vw, 128px). The section grounds now alternate
     white / tint / soft-grey, so the boundary is carried by colour and no
     longer needs ~122px of air either side of it to register. */
  --section:    clamp(56px, 7vw, 104px);

  --ease:       cubic-bezier(.22,.61,.36,1);
  /* Inter / Poppins / Playfair were never in the page's font request — only
     Chivo and Manrope are. They resolved on the author's machine because all
     three happened to be installed locally, and would have silently fallen back
     to system-ui and Georgia on the client's. Repointed at the two families
     that actually load, which also collapses the two type systems the merge
     left on one page into one. --font-serif is gone: its single user, the
     "What We Do" heading, no longer sets a serif. */
  --font-body:  'Manrope', system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif;
  --font-disp:  'Chivo', var(--font-body);
}

/* ---------- Reset / base ------------------------------------ */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  font-family: var(--font-body);
  color: var(--body);
  background: var(--bg);
  line-height: 1.7;
  font-size: 16px;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* clip, not hidden. Both stop the same horizontal overflow, but `hidden`
     makes body a scroll container, which is what silently kills descendant
     `position: sticky`. Nothing is broken today — .showcase__copy does stick —
     this is defensive: the next sticky element added under body would not.
     The `hidden` line stays first as the fallback for engines without `clip`. */
  overflow-x: hidden;
  overflow-x: clip;
}
img, svg, video { display: block; max-width: 100%; }
img { height: auto; }
a { color: inherit; text-decoration: none; }
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; background: none; border: none; }
ul { list-style: none; padding: 0; }
h1,h2,h3,h4,h5 { font-family: var(--font-disp); color: var(--ink); font-weight: 700; line-height: 1.08; letter-spacing: -.015em; }
::selection { background: rgba(64,56,212,.16); }

:focus-visible { outline: 2px solid var(--brand-500); outline-offset: 3px; border-radius: 4px; }

.skip-link { position: absolute; left: 16px; top: -60px; z-index: 100; background: var(--brand); color:#fff; padding: 10px 18px; border-radius: 8px; font-family: var(--font-disp); font-weight: 600; font-size: 13px; transition: top .25s var(--ease); }
.skip-link:focus { top: 16px; }

/* ---------- Scroll reveal (base layer) ---------------------
   This sits ABOVE the components on purpose. `[data-reveal].is-visible` and a
   component's `.card:hover` are both (0,2,0), so whichever comes last wins —
   and while these rules lived at the bottom of the file, `transform: none`
   silently killed the hover lift on every card that was also a reveal target
   (.insight, and later .voice). Keeping the base layer first lets component
   states override it the way the cascade is meant to work. */
[data-reveal] { opacity: 0; transform: translateY(26px); transition: opacity .7s var(--ease), transform .7s var(--ease); will-change: opacity, transform; }
[data-reveal].is-visible { opacity: 1; transform: none; }
[data-reveal][data-reveal-delay="1"] { transition-delay: .08s; }
[data-reveal][data-reveal-delay="2"] { transition-delay: .16s; }
[data-reveal][data-reveal-delay="3"] { transition-delay: .24s; }
[data-reveal][data-reveal-delay="4"] { transition-delay: .32s; }
@media (prefers-reduced-motion: reduce) { [data-reveal] { opacity: 1 !important; transform: none !important; transition: none; } }

/* ---------- Layout helpers --------------------------------- */
.container { width: 100%; max-width: var(--container); margin-inline: auto; padding-inline: var(--gutter); }
.section { padding-block: var(--section); }
.section--tight { padding-block: clamp(42px, 5vw, 72px); }
.bg-soft { background: var(--bg-soft); }
.bg-soft-2 { background: var(--bg-soft-2); }
/* ---- Tinted sections: a slow, soft wash ---------------------------------
   The flat #eaecfb was doing a lot of work across two large sections. Three
   oversized radial blobs drift over it on a long, offset loop, so the ground
   is never quite the same twice without ever calling attention to itself.

   Only `transform` animates — no blur filters, no background-position — so the
   whole thing stays on the compositor and never repaints.

   The clip is on .tint-fx, deliberately. Putting overflow on the section would
   break .showcase__copy, which is position: sticky. */
.bg-tint { background: var(--bg-tint); position: relative; }

/* content has to out-stack the wash */
.bg-tint > .container { position: relative; z-index: 1; }

.tint-fx {
  position: absolute; inset: 0; z-index: 0;
  overflow: hidden; pointer-events: none;
  border-radius: inherit;
  /* Masked away from the top and bottom edges. The pixel dissolves that join
     these sections to their neighbours land on the flat --bg-tint; the wash put
     a different colour under that landing and reopened the very seam the
     dissolve exists to close. Keeping the outer ~170px flat gives the dissolve
     the exact tone it was tuned against, and the wash still has the whole
     middle of the section to move through. */
  -webkit-mask-image: linear-gradient(180deg,
    transparent 0, #000 170px, #000 calc(100% - 170px), transparent 100%);
  mask-image: linear-gradient(180deg,
    transparent 0, #000 170px, #000 calc(100% - 170px), transparent 100%);
}

.tint-fx i {
  position: absolute; display: block; border-radius: 50%;
  will-change: transform;
}

/* violet, indigo, periwinkle — all drawn from the brand ramp, kept under 30%
   so text contrast on the cards above is untouched */
.tint-fx i:nth-child(1) {
  width: 62%; height: 92%; left: -12%; top: -26%;
  background: radial-gradient(circle at 50% 50%, rgba(124, 92, 245, .34) 0%, rgba(124, 92, 245, 0) 68%);
  animation: tintDriftA 34s ease-in-out infinite alternate;
}

.tint-fx i:nth-child(2) {
  width: 56%; height: 86%; right: -10%; top: -18%;
  background: radial-gradient(circle at 50% 50%, rgba(64, 56, 212, .26) 0%, rgba(64, 56, 212, 0) 66%);
  animation: tintDriftB 43s ease-in-out infinite alternate;
}

.tint-fx i:nth-child(3) {
  width: 70%; height: 84%; left: 22%; bottom: -34%;
  background: radial-gradient(circle at 50% 50%, rgba(150, 128, 255, .28) 0%, rgba(150, 128, 255, 0) 70%);
  animation: tintDriftC 51s ease-in-out infinite alternate;
}

/* Three different periods (34s / 43s / 51s), so the trio never returns to the
   same arrangement — a shared period would read as a visible loop. */
@keyframes tintDriftA {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(16%, 12%, 0) scale(1.16); }
}
@keyframes tintDriftB {
  from { transform: translate3d(0, 0, 0) scale(1.08); }
  to   { transform: translate3d(-14%, 16%, 0) scale(1); }
}
@keyframes tintDriftC {
  from { transform: translate3d(0, 0, 0) scale(1.05); }
  to   { transform: translate3d(-12%, -14%, 0) scale(1.2); }
}

@media (prefers-reduced-motion: reduce) {
  .tint-fx i { animation: none; }
}
/* Inside a run of same-coloured sections the two paddings stack, which left
   200–290px of blank ground between blocks. Halve both sides so the run reads
   as one zone with tight divisions; the first and last keep full padding
   against the differently-coloured neighbours. */
.bg-tint + .bg-tint { padding-top: clamp(26px, 3.2vw, 46px); }
.bg-tint:has(+ .bg-tint) { padding-bottom: clamp(26px, 3.2vw, 46px); }
.divider { height: 1px; background: var(--line); border: 0; }

.eyebrow {
  font-family: var(--font-disp);
  font-weight: 600;
  font-size: 12px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--brand-ink);
  display: inline-block;
  margin-bottom: 18px;
}
.eyebrow--muted { color: var(--muted); }

.dot { color: var(--brand-500); }
.accent { color: var(--brand-ink); }

.section-head { max-width: 640px; }
.section-title {
  font-size: clamp(28px, 3.4vw, 44px);
  letter-spacing: -.02em;
  color: var(--ink);
}
.section-title--lg { font-size: clamp(34px, 4.6vw, 60px); line-height: 1.03; }
.lead { font-size: clamp(16px, 1.3vw, 18px); color: var(--body); max-width: 46ch; }

.row-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; flex-wrap: wrap; margin-bottom: clamp(28px, 3vw, 44px); }

/* ---------- Buttons ---------------------------------------- */
.btn {
  --btn-fg: #fff;
  display: inline-flex; align-items: center; gap: 12px;
  font-family: var(--font-disp);
  font-weight: 600; font-size: 12.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--btn-fg);
  padding: 15px 26px; border-radius: 999px;
  transition: transform .3s var(--ease), box-shadow .3s var(--ease), background .3s var(--ease), color .3s var(--ease), border-color .3s var(--ease);
  white-space: nowrap;
}
.btn .ico { width: 16px; height: 16px; transition: transform .35s var(--ease); flex: none; }
.btn:hover .ico { transform: translateX(4px); }

/* Flat: no cast shadow, no coloured halo. The lift on hover carries the
   affordance on its own. */
.btn--primary { background: var(--grad-btn); box-shadow: none; }
.btn--primary:hover { transform: translateY(-2px); box-shadow: none; }

.btn--outline { color: var(--ink); border: 1.5px solid var(--line); background: transparent; }
.btn--outline:hover { border-color: var(--ink); transform: translateY(-2px); }

.btn--on-dark { color: #fff; border: 1.5px solid rgba(255,255,255,.4); }
.btn--on-dark:hover { border-color: #fff; background: rgba(255,255,255,.06); transform: translateY(-2px); }

.btn--white { background: #fff; color: var(--ink); }
.btn--white:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }

.btn--block { width: 100%; justify-content: center; }

/* tap target: ~20px tall on a phone without this */
@media (max-width: 860px) { .link-arrow { padding-block: 10px; } }

.link-arrow {
  display: inline-flex; align-items: center; gap: 9px;
  font-family: var(--font-disp); font-weight: 600; font-size: 12.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--brand-ink);
}
.link-arrow .ico { width: 15px; height: 15px; transition: transform .35s var(--ease); }
.link-arrow:hover .ico { transform: translateX(5px); }
.link-arrow--ink { color: var(--ink); }

/* ---------- Header / nav ----------------------------------- */
.site-header {
  position: fixed; inset: 0 0 auto 0; z-index: 60;
  transition: background .35s var(--ease), box-shadow .35s var(--ease), backdrop-filter .35s var(--ease);
}
.site-header .container { display: flex; align-items: center; justify-content: space-between; height: 84px; }
.site-header.is-scrolled { background: rgba(255,255,255,.9); backdrop-filter: saturate(160%) blur(14px); box-shadow: 0 1px 0 rgba(16,18,30,.06), 0 8px 30px rgba(16,18,30,.06); }
.site-header.is-scrolled .container { height: 70px; }
/* Over the tinted run the bar takes that run's colour: a shade deeper than the
   section so it still reads as a bar, with the hairline and shadow shifted from
   neutral grey to brand violet. Inherits the same .35s fade as .is-scrolled,
   so it eases in and out rather than switching. */
.site-header.is-tinted.is-scrolled {
  background: rgba(228, 231, 250, .88);
  backdrop-filter: saturate(150%) blur(14px);
  box-shadow: 0 1px 0 rgba(64, 56, 212, .1), 0 8px 30px rgba(64, 56, 212, .07);
}

.brand { display: inline-flex; align-items: center; gap: 12px; }
.logo-mark { width: 40px; height: 40px; flex: none; }
.logo-img { width: 54px; height: 54px; object-fit: contain; flex: none; display: block; margin: -3px; filter: brightness(1.5) saturate(1.25); }
.site-header--over-dark:not(.is-scrolled) .logo-img { filter: brightness(1.7) saturate(1.2) drop-shadow(0 0 6px rgba(120,100,255,.5)); }
.logo-shield, .logo-letter { stroke: var(--brand); fill: none; }
.logo-mark .logo-shield { stroke-width: 2.2; }
.logo-mark .logo-letter { stroke-width: 2.6; }
.brand-name { display: none; }

.nav { display: flex; align-items: center; gap: clamp(18px, 2.4vw, 34px); }
.nav a {
  font-family: var(--font-disp); font-weight: 500; font-size: 13px; letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink-2); position: relative; padding-block: 6px; transition: color .25s;
}
.nav a::after { content:""; position: absolute; left: 0; right: 100%; bottom: 0; height: 2px; background: var(--brand); transition: right .3s var(--ease); }
.nav a:hover { color: var(--brand-ink); }
.nav a:hover::after, .nav a.is-active::after { right: 0; }
.nav a.is-active { color: var(--brand-ink); }

.header-cta { display: inline-flex; align-items: center; gap: 20px; }
.nav-toggle { display: none; width: 44px; height: 44px; border-radius: 10px; align-items: center; justify-content: center; }
.nav-toggle span { position: relative; width: 22px; height: 2px; background: var(--ink); border-radius: 2px; transition: transform .3s var(--ease), opacity .2s; }
.nav-toggle span::before, .nav-toggle span::after { content:""; position:absolute; left:0; width:22px; height:2px; background: var(--ink); border-radius:2px; transition: transform .3s var(--ease), top .3s var(--ease); }
.nav-toggle span::before { top: -7px; } .nav-toggle span::after { top: 7px; }
body.nav-open .nav-toggle span { background: transparent; }
body.nav-open .nav-toggle span::before { top:0; transform: rotate(45deg); }
body.nav-open .nav-toggle span::after { top:0; transform: rotate(-45deg); }

/* Dark header variant */
.site-header--dark .brand-name { color: #fff; }
.site-header--dark .nav a { color: rgba(255,255,255,.86); }
.site-header--dark .nav a.is-active { color: #fff; }
.site-header--dark .logo-shield, .site-header--dark .logo-letter { stroke: #fff; }
.site-header--dark .nav-toggle span, .site-header--dark .nav-toggle span::before, .site-header--dark .nav-toggle span::after { background:#fff; }
.site-header--dark.is-scrolled { background: rgba(10,10,20,.82); backdrop-filter: saturate(160%) blur(14px); box-shadow: 0 1px 0 rgba(255,255,255,.06); }
.site-header--dark.is-scrolled .brand-name, .site-header--dark.is-scrolled .nav a.is-active { color:#fff; }

/* ---------- Mobile drawer ---------------------------------- */
.nav-scrim { position: fixed; inset: 0; background: rgba(8,9,16,.5); opacity: 0; visibility: hidden; transition: opacity .3s, visibility .3s; z-index: 55; backdrop-filter: blur(2px); }
body.nav-open .nav-scrim { opacity: 1; visibility: visible; }

/* ---------- Hero (light split) ----------------------------- */
.hero { position: relative; padding-top: 84px; overflow: hidden; }
.hero-inner { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1.06fr); align-items: center; gap: clamp(24px, 4vw, 56px); padding-block: clamp(40px, 5vw, 76px); }
.hero-copy { max-width: 560px; }
.hero-title { font-size: clamp(40px, 5.6vw, 74px); line-height: 1.0; letter-spacing: -.028em; font-weight: 700; color: var(--ink); }
.hero-title .dot { color: var(--brand-500); }
.hero-sub { margin-top: 22px; font-size: clamp(15px, 1.2vw, 17px); color: var(--body); max-width: 42ch; }
.hero-actions { margin-top: 34px; display: flex; gap: 16px; flex-wrap: wrap; }
.hero-media { position: relative; border-radius: var(--radius-lg); overflow: hidden; aspect-ratio: 5/4; box-shadow: var(--shadow-lg); }
.hero-media img { width: 100%; height: 100%; object-fit: cover; }
.hero::before {
  content:""; position:absolute; inset:0; z-index:-1;
  background:
    radial-gradient(1100px 520px at 88% 12%, rgba(64,56,212,.06), transparent 60%),
    var(--bg-soft);
}
.hero-glyph { position: absolute; left: -60px; top: 34%; width: 320px; opacity: .5; z-index: -1; color: #dfe2ea; pointer-events:none; }

/* watermark side text */
.hero-media__tag { position:absolute; top:16px; right:12px; writing-mode: vertical-rl; font-size:9px; letter-spacing:.35em; color: rgba(255,255,255,.5); font-family: var(--font-disp); text-transform: uppercase; }

/* ---------- Quote / story block ---------------------------- */
.quote-block { position: relative; }
/* The slanted bar pair Poppins draws for the opening double quote. Tried the
   serif's round comma pair here and it was rejected — leave it on the display
   font. Covers all eight uses across five pages. */
.quote-mark { font-family: var(--font-disp); font-weight: 700; color: var(--brand-500); font-size: 56px; line-height: .6; display: block; margin-bottom: 6px; }
.quote-lg { font-family: var(--font-disp); font-weight: 500; font-size: clamp(21px, 2.5vw, 30px); line-height: 1.28; letter-spacing: -.01em; color: var(--ink); }
.quote-lg .accent { color: var(--brand-ink); }
.story-grid { display: grid; grid-template-columns: minmax(0,.82fr) minmax(0,1.18fr); gap: clamp(28px, 4vw, 64px); align-items: center; }
.story-copy p { margin-top: 16px; }
.story-copy .link-arrow { margin-top: 26px; }

.collage { position: relative; border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow-md); }
.collage img { width: 100%; height: 100%; object-fit: cover; aspect-ratio: 16/10; }

/* ---------- Media / cards ---------------------------------- */
.media { position: relative; overflow: hidden; border-radius: var(--radius); background: var(--bg-soft-2); }
.media img { width: 100%; height: 100%; object-fit: cover; transition: transform .7s var(--ease); }
.media:hover img { transform: scale(1.055); }

.tile { position: relative; display: block; overflow: hidden; border-radius: var(--radius); background: #10131c; box-shadow: var(--shadow-sm); }
.tile img { width: 100%; height: 100%; object-fit: cover; transition: transform .7s var(--ease); }
.tile::after { content:""; position:absolute; inset:0; background: linear-gradient(to top, rgba(8,9,16,.86) 4%, rgba(8,9,16,.28) 40%, rgba(8,9,16,0) 70%); }
.tile:hover img { transform: scale(1.06); }
.tile__body { position: absolute; left: 0; right: 0; bottom: 0; padding: 20px; z-index: 2; color: #fff; }
.tile__title { font-family: var(--font-disp); font-weight: 600; font-size: 17px; color:#fff; letter-spacing: -.01em; }
.tile__meta { font-size: 13px; color: rgba(255,255,255,.78); margin-top: 3px; }
.tile__cat { display:inline-block; font-family: var(--font-disp); font-weight:600; font-size:10.5px; letter-spacing:.12em; text-transform:uppercase; color:#fff; background: rgba(255,255,255,.16); backdrop-filter: blur(4px); padding: 5px 11px; border-radius: 999px; margin-bottom: 10px; }

/* Featured projects (home) — 2 big + 2 small */
.feat-grid { display: grid; grid-template-columns: 1.5fr 1.5fr 1fr; grid-template-rows: 1fr 1fr; gap: 18px; }
.feat-grid .tile:nth-child(1) { grid-row: 1 / span 2; }
.feat-grid .tile:nth-child(2) { grid-row: 1 / span 2; }
.feat-grid .tile { min-height: 210px; }
.feat-grid .tile:nth-child(1), .feat-grid .tile:nth-child(2) { min-height: 440px; }

/* Service strip (5-up) */
.svc-strip { display: grid; grid-template-columns: repeat(5, 1fr); gap: 14px; }
.svc-strip .tile { aspect-ratio: 3/3.6; }
.svc-strip .tile__title { font-size: 15px; }

/* ---------- Grids ------------------------------------------- */
.grid { display: grid; gap: clamp(16px, 2vw, 26px); }
.grid--3 { grid-template-columns: repeat(3, 1fr); }
.grid--4 { grid-template-columns: repeat(4, 1fr); }

/* ---------- Split layouts ----------------------------------- */
.split { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(28px, 4vw, 64px); align-items: center; }
/* headline + supporting paragraph: give the display type room so it doesn't
   break one word per line */
.split--headline { grid-template-columns: 1.35fr 1fr; align-items: end; }
.split--headline > .split__copy { max-width: none; }
.split--headline > .split__copy + .split__copy { max-width: 46ch; }
.split--media-pair { grid-template-columns: 1.1fr .9fr; }
.media-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.media-pair .media { aspect-ratio: 3/3.4; }
.split__copy { max-width: 460px; }
.split__copy p { margin-top: 16px; }
.split__copy .link-arrow { margin-top: 26px; }
.media-tall { aspect-ratio: 4/5; }
.media-wide { aspect-ratio: 16/10; }

/* ---------- Feature columns --------------------------------- */
.feature-cols { display: grid; grid-template-columns: repeat(4, 1fr); gap: clamp(22px, 3vw, 44px); }
.feature-col h3 { font-family: var(--font-disp); font-weight: 600; font-size: 18px; color: var(--ink); }
.feature-col p { margin-top: 10px; font-size: 15px; }

/* ---------- Project cards ----------------------------------- */
.pcard .media { display: block; aspect-ratio: 4/3; margin-bottom: 14px; }
.pcard__title { font-family: var(--font-disp); font-weight: 600; font-size: 17px; color: var(--ink); letter-spacing: -.01em; }
.pcard__meta { margin-top: 4px; font-size: 13.5px; color: var(--muted); }

/* ---------- What We Do — numbered service tabs -------------------
   Compact by design: a numbered rail on the left drives one panel on the
   right, so the whole block sits inside a laptop viewport instead of the
   ~950px the previous card grid needed. */
.svcx__head { margin-bottom: clamp(20px, 2.4vw, 32px); }
/* Was the only Playfair on the page — a lone serif among four sans headings,
   which read as an accident rather than an accent. Now it matches the section
   titles either side of it, so all four zones announce themselves the same way:
   eyebrow, then a display-weight title. */
.svcx__heading {
  font-family: var(--font-disp); font-weight: 700; color: var(--ink);
  font-size: clamp(26px, 2.6vw, 36px); line-height: 1.06; letter-spacing: -.02em;
  margin-top: 9px;
}
.svcx__steps {
  display: flex; align-items: center; gap: 10px; margin-top: 10px;
  font-family: var(--font-disp); font-weight: 600; font-size: 11px;
  letter-spacing: .16em; text-transform: uppercase; color: var(--muted);
}
/* Dots, not em dashes. Between the divider, the leading rule this line used to
   carry and the active-tab marker, the rail had six separate strokes in it —
   three of them were these separators, which read as more rules rather than as
   punctuation. The decorative bar that opened the line is gone outright: it
   anchored nothing. */
.svcx__steps i { color: var(--brand-400); font-style: normal; }
.svcx {
  display: grid; grid-template-columns: minmax(270px, 1fr) 1.85fr;
  gap: clamp(22px, 3vw, 48px);
  /* stretch, not start: the rail is ~286px tall and every panel is taller
     (305–403px), so with `start` its divider stopped in mid-air well above the
     panel's bottom edge. Stretching only moves the border — the heading and
     tabs inside stay put at the top. */
  align-items: stretch;
  /* shared by the rail's padding and the active marker that sits on its
     border — the two must stay in lockstep or the marker drifts off the line */
  --rail-pad: clamp(18px, 2.4vw, 36px);
}

/* heading + tab list share one column, so the divider runs the full height of
   the pair instead of starting halfway down the section */
.svcx__rail {
  border-right: 1px solid rgba(74, 64, 224, .18);
  padding-right: var(--rail-pad);
}
.svcx__list { display: grid; gap: clamp(4px, .6vw, 9px); }
/* the rule's column is reserved at all times and the rule scales into it —
   letting it take part in sizing made the active row grow and shoved every
   item below it down the page */
.svcx__item {
  position: relative;
  /* the third column used to hold the active marker; it now rides the divider,
     which hands those ~46px back to the label — enough for "Cross-Border
     Logistics & Operations" to hold one line */
  display: grid; grid-template-columns: auto 1fr;
  align-items: center; gap: 12px;
  width: 100%; text-align: left; padding: 9px 0; color: var(--body);
  transition: color .25s var(--ease);
}
.svcx__num {
  font-family: var(--font-disp); font-weight: 600; font-size: 12.5px;
  letter-spacing: .06em; color: var(--muted); transition: color .25s var(--ease);
  font-variant-numeric: tabular-nums;
}
/* Wraps. It used to be nowrap + ellipsis so all five rows stayed the same
   height, but the three business categories that replaced them are far longer
   — "Cross-Border Logistics & Operations" was being cut to "…& Opera…". Equal
   height no longer buys anything: the rule keeps its own reserved column, so
   selecting a row still can't reflow the ones beneath it. */
.svcx__name {
  font-family: var(--font-disp); font-weight: 600; font-size: clamp(13.5px, .95vw, 15px);
  letter-spacing: -.005em; line-height: 1.32; text-wrap: balance;
}
/* Sits ON the divider rather than floating to the left of it. As a detached
   horizontal dash it read as a stray rule — nothing tied it to the row it was
   marking. As a segment of the vertical rule it reinforces the line already
   there instead of adding a seventh one, and it points at the panel. */
.svcx__bar {
  position: absolute;
  /* -1.5px, not -1: the marker is 2px and the border it covers is 1px, so its
     right edge has to clear the border by 1.5px for the two to share a centre
     line. At -1px it sat half a pixel to the left of the rule. */
  right: calc(-1 * var(--rail-pad) - 1.5px);
  top: 7px; bottom: 7px; width: 2px;
  background: var(--brand); border-radius: 2px;
  transform: scaleY(0); transform-origin: center;
  transition: transform .35s var(--ease);
}
.svcx__item:hover { color: var(--ink); }
.svcx__item:hover .svcx__num { color: var(--brand-ink); }
.svcx__item.is-on { color: var(--ink); }
.svcx__item.is-on .svcx__num { color: var(--brand); }
.svcx__item.is-on .svcx__bar { transform: scaleY(1); }

.svcx__stage { position: relative; display: grid; }
/* All three panels share one grid cell, so the stage is always as tall as the
   tallest of them. Without this the section — and now the rail's divider with
   it — jumped 69px at 1440 and 98px at 1080 on every tab click, and a divider
   that visibly grows and shrinks pulls more attention than a short one ever
   did. The panels stay laid out and hide with visibility, which keeps the
   inactive ones out of the accessibility tree just as the `hidden` attribute
   main.js sets intends. */
.svcx__panel {
  grid-area: 1 / 1;
  display: grid;
  visibility: hidden;
  opacity: 0;
  /* copy now carries a chip list as well as the description, so it takes the
     larger share — at 1fr / 1.3fr the paragraph broke over five lines and the
     chips fell one per row */
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: clamp(18px, 2.4vw, 40px); align-items: center;
  /* neutral grey against the tinted section — a tinted panel on a tinted
     ground would vanish, and plain white read too stark */
  background: var(--bg-soft); border-radius: var(--radius-lg);
  padding: clamp(16px, 1.9vw, 28px);
}
.svcx__panel.is-on { visibility: visible; opacity: 1; animation: svcxIn .38s var(--ease) both; }
@keyframes svcxIn { from { opacity: 0; transform: translateY(9px); } }
@media (prefers-reduced-motion: reduce) { .svcx__panel.is-on { animation: none; } }
.svcx__title { font-family: var(--font-disp); font-weight: 700; font-size: clamp(17px, 1.3vw, 20px); color: var(--brand-ink); letter-spacing: -.01em; }
.svcx__desc { margin-top: 10px; color: var(--body); font-size: 14px; line-height: 1.6; max-width: 46ch; }
.svcx__copy .link-arrow { margin-top: clamp(14px, 1.6vw, 22px); }
.svcx__media { border-radius: var(--radius); overflow: hidden; background: #fff; box-shadow: var(--shadow-sm); }
.svcx__media img { width: 100%; height: 100%; object-fit: cover; aspect-ratio: 16/9.6; }

@media (max-width: 860px) {
  .svcx { grid-template-columns: 1fr; gap: clamp(18px, 3vw, 26px); }
  .svcx__rail {
    border-right: 0; border-bottom: 1px solid var(--line);
    padding-right: 0; padding-bottom: 10px;
  }
  /* one column here, so there is no sibling row to stretch against */
  .svcx { align-items: start; }

  /* The active marker rides the rail's right border, which this layout does
     not have — it was left hanging on the screen edge, 20px outside the rail.
     A filled row reads more clearly at this size anyway, and gives the tab a
     proper tap target at the same time. */
  .svcx__bar { display: none; }
  .svcx__item { padding: 12px; border-radius: 10px; }
  .svcx__item.is-on { background: rgba(64, 56, 212, .06); }
}
@media (max-width: 620px) {
  .svcx__panel { grid-template-columns: 1fr; }
  /* image first reads better once the panel is a single column */
  .svcx__media { order: -1; }
  .svcx__desc { max-width: none; }
}

/* ---------- How it works — 8-step process -----------------------
   Cards in a 4x2 grid, each carrying its own step number. The flow between
   them is drawn in the gaps rather than on a rail above, so nothing overlaps
   the cards and the connector needs no clearance of its own. */
.hiw { background: var(--bg-tint); }

.hiw__head { text-align: center; max-width: 44rem; margin: 0 auto clamp(40px, 4.4vw, 64px); }

.hiw__title { font-size: clamp(30px, 3.6vw, 48px); letter-spacing: -.025em; color: var(--ink); }

.hiw__sub { margin-top: 14px; color: var(--body); font-size: clamp(14.5px, 1.15vw, 16px); }

.hiw__steps {
  /* The gap holds the connector, so it has to fit the marker AND clearance on
     both sides of it. At the old 36px the marker spanned the whole gap and
     touched both cards — 0px of air at either end, which read as cramped. */
  --hiw-gap: clamp(38px, 3.1vw, 52px);
  /* fixed marker width: 16px of dashes + a 7px chevron + its rotation overhang.
     Fixed rather than gap-relative so the clearance grows with the gap instead
     of the marker growing to fill it. */
  --hiw-mark: 26px;
  display: grid; grid-template-columns: repeat(4, 1fr);
  column-gap: var(--hiw-gap);
  row-gap: clamp(26px, 3vw, 44px);
  /* the global reset only clears markers on ul, and this is an ol */
  list-style: none; padding: 0;
}

.hiw__step { position: relative; }

/* Flow marker, drawn in the gap to the right of each card. Row-final cards are
   excluded — at four columns the run has to stop at 4n or the line jumps the
   grid to the card below. Centred on the card rather than pinned to a fixed
   offset, so it stays put whatever the copy does. */
.hiw__step:not(:nth-child(4n)):not(:last-child)::before,
.hiw__step:not(:nth-child(4n)):not(:last-child)::after {
  content: ""; position: absolute; top: 50%; z-index: 1;
}

/* both halves start from the same inset, so the marker sits centred in the gap
   whatever the gap resolves to */
.hiw__step:not(:nth-child(4n)):not(:last-child)::before {
  left: calc(100% + (var(--hiw-gap) - var(--hiw-mark)) / 2);
  width: 16px; height: 2px; margin-top: -1px;
  background: repeating-linear-gradient(90deg,
    rgba(64, 56, 212, .32) 0 4px, transparent 4px 9px);
}

.hiw__step:not(:nth-child(4n)):not(:last-child)::after {
  left: calc(100% + (var(--hiw-gap) - var(--hiw-mark)) / 2 + 17px);
  width: 7px; height: 7px; margin-top: -4px;
  border-top: 2px solid rgba(64, 56, 212, .52);
  border-right: 2px solid rgba(64, 56, 212, .52);
  transform: rotate(45deg);
}

/* Opaque white, not a translucent frost: the supplied renders carry a solid
   white ground, and against a 72%-white card that showed as a visible lighter
   rectangle around each illustration. */
.hiw__card {
  position: relative;
  height: 100%; background: #fff; border: 1px solid rgba(255, 255, 255, .9);
  border-radius: var(--radius-lg); padding: clamp(20px, 2.1vw, 30px);
  transition: transform .35s var(--ease), border-color .35s var(--ease), box-shadow .35s var(--ease);
}

.hiw__step:hover .hiw__card,
.hiw__step:focus-within .hiw__card {
  transform: translateY(-6px);
  border-color: rgba(106, 92, 245, .45);
  box-shadow: 0 18px 38px rgba(64, 56, 212, .13);
}

@media (prefers-reduced-motion: reduce) {
  .hiw__step:hover .hiw__card { transform: none; }
}

/* Big and faint. Solid it competed with the title; at this size and weight it
   reads as the graphic that gives eight otherwise identical cards a rhythm,
   and it is what replaced the pin. */
.hiw__num {
  display: block;
  font-family: var(--font-disp); font-weight: 800;
  font-size: clamp(30px, 2.7vw, 40px); line-height: 1;
  letter-spacing: -.03em;
  color: var(--brand); opacity: .2;
  margin-bottom: clamp(12px, 1.4vw, 18px);
  transition: opacity .35s var(--ease);
}

.hiw__step:hover .hiw__num { opacity: .38; }

/* Left-aligned, and no fixed title height. Centring eight cards with a rule
   under each title was what made the block read as wallpaper; ragged-right
   copy gives the row a reading rhythm instead. */
.hiw__card h3 {
  font-size: clamp(15px, 1.15vw, 17.5px); color: var(--ink);
  letter-spacing: -.01em; line-height: 1.28; text-wrap: balance;
}

.hiw__card p { margin-top: 9px; font-size: 13.5px; line-height: 1.62; color: var(--body); }

.hiw__foot { margin-top: clamp(34px, 4vw, 56px); display: grid; justify-items: center; gap: 18px; }
.hiw__pill {
  display: inline-flex; align-items: center; gap: 11px;
  padding: 13px 26px; border-radius: 999px;
  border: 1px solid rgba(64,56,212,.32); background: rgba(255,255,255,.55);
  color: var(--brand-ink); font-size: clamp(14px, 1.15vw, 16px); font-weight: 500;
}
.hiw__pill .ico { width: 19px; height: 19px; flex: none; }

@media (max-width: 900px) {
  .hiw__steps { grid-template-columns: repeat(2, 1fr); row-gap: clamp(28px, 4vw, 40px); }
  /* the run only makes sense along a row — at two columns the break lands on
     every 2nd step, and the 4n rules above no longer match it */
  .hiw__step:nth-child(4n)::before, .hiw__step:nth-child(4n)::after { display: block; }
  .hiw__step:nth-child(2n)::before, .hiw__step:nth-child(2n)::after { display: none; }
}
/* 展开按钮：只在单列（≤560px）出现。双列时 8 张卡是 4 行，不算长，没必要折叠。 */
.hiw__more { display: none; }

@media (max-width: 560px) {
  .hiw__steps { grid-template-columns: 1fr; }
  .hiw__step::before, .hiw__step::after { display: none; }

  .hiw:not(.is-open) .hiw__step:nth-child(n + 4) { display: none; }

  .hiw__more {
    /* flex + fit-content 才能被 margin auto 居中；inline-flex 是行内级的，
       auto 外边距对它无效，之前整个按钮贴在左边。 */
    display: flex; align-items: center; gap: 9px;
    width: fit-content;
    margin: 22px auto 0;
    padding: 13px 26px;
    border: 1px solid rgba(64, 56, 212, .26);
    border-radius: 999px;
    background: rgba(255, 255, 255, .72);
    font-family: var(--font-disp); font-weight: 600;
    font-size: 12.5px; letter-spacing: .1em; text-transform: uppercase;
    color: var(--brand-ink);
    /* 只动 transform / opacity / 颜色，都不触发布局 */
    transition: transform .28s var(--ease), background-color .28s var(--ease), border-color .28s var(--ease);
  }

  .hiw__more:active { transform: scale(.97); }

  .hiw__more .ico--caret { width: 12px; height: 8px; stroke-width: 2.2; }

  .hiw.is-open .hiw__more { display: none; }

  /* 展开的 5 张各自淡入上移。display 从 none 变回来时动画会自动播，
     不需要 JS 里强制重排再加 class。每张只跑 transform + opacity —— 纯合成器。
     容器高度的那一次跳变省不掉（内容变多必然重排），但只发生一次，
     而不是用 max-height 过渡去逐帧重排。 */
  .hiw.is-open .hiw__step:nth-child(n + 4) {
    animation: hiwStepIn .46s var(--ease) both;
  }
  .hiw.is-open .hiw__step:nth-child(4) { animation-delay: .02s; }
  .hiw.is-open .hiw__step:nth-child(5) { animation-delay: .09s; }
  .hiw.is-open .hiw__step:nth-child(6) { animation-delay: .16s; }
  .hiw.is-open .hiw__step:nth-child(7) { animation-delay: .23s; }
  .hiw.is-open .hiw__step:nth-child(8) { animation-delay: .30s; }
}

@keyframes hiwStepIn {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .hiw.is-open .hiw__step:nth-child(n + 4) { animation: none; }
}

/* ---------- Insight cards ---------------------------------- */
.insight { display: flex; flex-direction: column; background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; transition: transform .35s var(--ease), box-shadow .35s var(--ease), border-color .35s; }
.insight:hover { transform: translateY(-4px); box-shadow: var(--shadow-md); border-color: transparent; }
.insight .media { border-radius: 0; aspect-ratio: 16/9; }
.insight__body { padding: 20px 22px 24px; }
.insight__cat { font-family: var(--font-disp); font-weight: 600; font-size: 11px; letter-spacing: .12em; text-transform: uppercase; color: var(--brand-ink); }
.insight__title { font-family: var(--font-disp); font-weight: 600; font-size: 18px; color: var(--ink); margin: 10px 0 10px; line-height: 1.25; letter-spacing: -.01em; }
.insight__excerpt { font-size: 14px; }
.insight__body .link-arrow { margin-top: 16px; font-size: 11.5px; }

/* horizontal insight (row) */
.insight-row { display: grid; grid-template-columns: 1fr auto; gap: 18px; align-items: center; padding: 18px; border: 1px solid var(--line); border-radius: var(--radius); transition: box-shadow .3s, transform .3s var(--ease); }
.insight-row:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); }
.insight-row .media { width: 116px; height: 92px; border-radius: var(--radius-sm); }

/* ---------- Tabs / pills ----------------------------------- */
/* one scrollable row — six labelled tabs overflow 1220px, and wrapping used to
   drop the last one onto a line of its own */
.tabs { display: flex; gap: 4px; flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; -ms-overflow-style: none; border-bottom: 1px solid var(--line); }
.tabs::-webkit-scrollbar { display: none; }
.tab { flex: none; display: inline-flex; align-items: center; gap: 6px; padding: 14px 11px; font-family: var(--font-disp); font-weight: 600; font-size: 12px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); white-space: nowrap; position: relative; transition: color .25s; }
.tab .ico { width: 16px; height: 16px; opacity: .7; }
.tab::after { content:""; position:absolute; left:0; right:100%; bottom:0; height:2px; background: var(--brand); transition: right .3s var(--ease); }
.tab.is-active, .tab:hover { color: var(--brand-ink); }
.tab.is-active::after { right: 0; }

.pills { display: flex; gap: 10px; flex-wrap: wrap; }
.pill { padding: 10px 20px; border-radius: 999px; border: 1px solid var(--line); font-family: var(--font-disp); font-weight: 500; font-size: 13px; color: var(--ink-2); transition: all .25s; }
.pill:hover { border-color: var(--ink); }
.pill.is-active { background: var(--brand); color:#fff; border-color: var(--brand); }

/* ---------- Featured project detail ------------------------ */
.fp { display: grid; grid-template-columns: 1.08fr .92fr; gap: clamp(28px, 4vw, 56px); align-items: center; }
.fp .media { aspect-ratio: 4/3; }
.fp__title { font-size: clamp(24px, 2.8vw, 34px); margin: 12px 0 14px; }
.spec { margin-top: 22px; border-top: 1px solid var(--line); }
.spec__row { display: grid; grid-template-columns: 128px 1fr; gap: 16px; padding: 13px 0; border-bottom: 1px solid var(--line); font-size: 14px; }
.spec__row dt { font-family: var(--font-disp); font-weight: 600; font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--brand-ink); align-self: center; }
.spec__row dd { color: var(--ink-2); }
.fp .link-arrow { margin-top: 22px; }

/* ---------- Selected work strip ---------------------------- */
.work-strip { display: grid; grid-template-columns: repeat(6, 1fr); gap: 14px; }
.work-item .media { aspect-ratio: 4/3; margin-bottom: 12px; }
.work-item__title { font-family: var(--font-disp); font-weight: 600; font-size: 14px; color: var(--ink); }
.work-item__meta { font-size: 12px; color: var(--muted); margin-top: 2px; }

/* ---------- Process steps ---------------------------------- */
.process { display: grid; grid-template-columns: .82fr 1.18fr; gap: clamp(28px, 4vw, 56px); align-items: start; }
.steps { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; }
.step .media { aspect-ratio: 4/3; margin-bottom: 16px; }
.step__num { font-family: var(--font-disp); font-weight: 700; font-size: 13px; color: var(--brand-ink); letter-spacing: .05em; }
.step__title { font-family: var(--font-disp); font-weight: 600; font-size: 15px; color: var(--ink); margin: 2px 0 8px; }
.step p { font-size: 13.5px; }

/* ---------- Stats ------------------------------------------ */
.stats-card { background: var(--bg-soft); border-radius: var(--radius-lg); padding: clamp(32px, 4vw, 56px); display: grid; grid-template-columns: .9fr 1.1fr; gap: clamp(28px, 4vw, 56px); align-items: center; }
.stats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 2px; background: var(--line); border-radius: 12px; overflow: hidden; }
.stat { background: var(--bg-soft); padding: 22px clamp(18px, 2vw, 30px); }
.stat__num { font-family: var(--font-disp); font-weight: 700; font-size: clamp(40px, 4.4vw, 58px); color: var(--brand-ink); line-height: 1; letter-spacing: -.02em; }
.stat__label { font-family: var(--font-disp); font-weight: 600; color: var(--ink); margin-top: 12px; font-size: 15px; }
.stat__sub { font-size: 13px; color: var(--muted); margin-top: 2px; }

/* ---------- Our Studio (copy + points | overlapped media | figures) ------
   Copy leads on the left; the media is one large shot with a second frame
   breaking out of its bottom-right corner. The figure card underneath spans
   the full width so the numbers read as one row rather than four boxes. */
.studio__top {
  display: grid;
  grid-template-columns: minmax(0, .92fr) minmax(0, 1.08fr);
  gap: clamp(30px, 4.4vw, 68px);
  align-items: center;
}
.studio__copy { max-width: 500px; }
.studio__lede { margin-top: 16px; max-width: 44ch; }

.studio__points { margin-top: clamp(18px, 2.2vw, 26px); display: grid; gap: clamp(14px, 1.6vw, 18px); }
.studio__point { display: grid; grid-template-columns: auto 1fr; gap: 16px; align-items: start; }
.studio__ico {
  flex: none; width: 46px; height: 46px; border-radius: 13px;
  display: grid; place-items: center;
  background: rgba(64,56,212,.09); color: var(--brand);
  border: 1px solid rgba(64,56,212,.1);
  transition: transform .35s var(--ease), background .35s var(--ease), border-color .35s var(--ease);
}
.studio__ico svg { width: 23px; height: 23px; }
.studio__pointBody h3 { font-family: var(--font-disp); font-weight: 600; font-size: 16.5px; color: var(--ink); }
.studio__pointBody p { margin-top: 4px; font-size: 14.5px; max-width: 34ch; }
.studio__point:hover .studio__ico {
  transform: translateY(-3px);
  background: rgba(64,56,212,.16); border-color: rgba(64,56,212,.28);
}
.studio__copy .link-arrow { margin-top: clamp(20px, 2.4vw, 28px); }

/* The inset overhangs the main shot, so the wrapper needs bottom room for it
   or the figure card below collides with the overhang. */
/* --alt drives the cross-fade. Hovering the inset flips it, and .is-swapped
   pins it — the fourth rule is the XOR that makes hover always preview the
   OPPOSITE of the pinned state instead of doing nothing once pinned. */
.studio__media { position: relative; padding-bottom: clamp(28px, 3.4vw, 48px); --alt: 0; }
.studio__media:has(.studio__inset:hover) { --alt: 1; }
.studio__media:has(.studio__inset:focus-visible) { --alt: 1; }
.studio__media.is-swapped { --alt: 1; }
.studio__media.is-swapped:has(.studio__inset:hover) { --alt: 0; }

.studio__shot { position: relative; border-radius: var(--radius-lg); overflow: hidden; box-shadow: var(--shadow-md); }
.studio__shot img { display: block; width: 100%; height: 100%; object-fit: cover; aspect-ratio: 16/11; transition: transform .8s var(--ease); }
/* Hovering the big photo zooms it; hovering the inset swaps instead, so the two
   transforms never fight over the same element. */
.studio__media:hover:not(:has(.studio__inset:hover)) .studio__shot img { transform: scale(1.045); }

/* The incoming copy settles down from slightly oversized, which reads as the
   small frame growing into the big one rather than a flat dissolve. */
.studio__alt {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  opacity: var(--alt);
  transform: scale(calc(1 + (1 - var(--alt)) * .07));
  transition: opacity .5s var(--ease), transform .7s var(--ease);
}
/* Breaks out of the main shot on two sides — past the right edge and below the
   bottom — so the pair reads as one composition instead of two stacked photos.
   The white border is what separates it from the photo underneath. */
.studio__inset {
  position: absolute; right: clamp(-26px, -1.6vw, -12px); bottom: 0;
  width: clamp(150px, 33%, 270px);
  border-radius: var(--radius); overflow: hidden;
  border: 6px solid #fff;
  box-shadow: var(--shadow-md);
  transition: transform .5s var(--ease);
}
.studio__inset { cursor: pointer; }
.studio__inset img { display: block; width: 100%; height: 100%; object-fit: cover; aspect-ratio: 5/4; }
.studio__media:hover .studio__inset { transform: translateY(-5px); }
.studio__inset:hover, .studio__inset:focus-visible { transform: translateY(-8px) scale(1.035); }
.studio__inset:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }

/* Advertises the interaction: visible at rest, spins a half turn on hover. */
.studio__swap {
  position: absolute; top: 8px; right: 8px; z-index: 2;
  width: 26px; height: 26px; border-radius: 50%;
  display: grid; place-items: center;
  background: rgba(255,255,255,.9); color: var(--brand);
  box-shadow: 0 2px 8px rgba(16,18,30,.18);
  opacity: .82;
  transition: opacity .3s var(--ease), transform .45s var(--ease);
}
.studio__swap svg { width: 14px; height: 14px; }
.studio__inset:hover .studio__swap,
.studio__inset:focus-visible .studio__swap { opacity: 1; transform: rotate(180deg); }

/* Figure row: dividers come from the gap showing through, so there is no
   trailing rule after the last column to hide. */
.studio__stats {
  margin-top: clamp(26px, 3vw, 42px);
  /* Flat equivalents of rgba(255,255,255,.62) and rgba(255,255,255,.9) over the
     #eaecfb ground — identical to look at, but opaque. [data-reveal] puts
     will-change on this card, which makes it a stacking context, and a
     translucent background inside one leaves mix-blend-mode: multiply on the
     booth render with nothing but that translucent white to blend against, so
     the render stayed a white rectangle instead of sinking into the card. */
  /* The section ground under this card is now white, so the old #f7f8fd — a
     flat stand-in for 62% white over the lavender — had nothing left to sit
     against. Inverted instead: the card carries the brand tint and the section
     stays white, which also keeps it opaque for the booth render's multiply. */
  background: var(--bg-tint);
  border: 1px solid rgba(64, 56, 212, .12);
  border-radius: var(--radius-lg);
  padding: clamp(16px, 1.9vw, 24px) clamp(18px, 2.2vw, 30px);
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr)) minmax(0, .92fr);
  gap: clamp(16px, 2vw, 30px);
}
.sfig { position: relative; padding-right: clamp(16px, 2vw, 30px); }
.sfig + .sfig::before {
  content: ""; position: absolute; left: calc(clamp(16px, 2vw, 30px) / -2);
  top: 6px; bottom: 6px; width: 1px; background: rgba(64,56,212,.13);
}
.sfig__ico {
  width: 44px; height: 44px; border-radius: 50%;
  display: grid; place-items: center;
  background: rgba(64,56,212,.09); color: var(--brand);
  transition: transform .35s var(--ease), background .35s var(--ease);
}
.sfig__ico svg { width: 21px; height: 21px; }
.sfig__num {
  font-family: var(--font-disp); font-weight: 700; line-height: 1;
  font-size: clamp(30px, 3vw, 42px); letter-spacing: -.02em;
  color: var(--brand-ink); margin-top: 12px;
}
.sfig__label { font-family: var(--font-disp); font-weight: 600; font-size: 14.5px; color: var(--ink); margin-top: 8px; }
.sfig__sub { font-size: 13px; color: var(--muted); margin-top: 3px; max-width: 22ch; }
.sfig:hover .sfig__ico { transform: translateY(-3px); background: rgba(64,56,212,.16); }

.sfig--art { padding-right: 0; display: grid; place-items: center; overflow: hidden; }
/* The render's ground is clipped to pure white in the file itself, so multiply
   erases it into the card exactly — no pale rectangle, and the linework picks
   up the card's own tint, which is what keeps the violet in the same family as
   the figures beside it without a colour to hand-match.
   Scaled past its cell and cropped: at column width the whole frame reduces to
   mush, so the crop trades the empty margins for legible linework. */
.booth {
  width: 132%; max-width: none; height: auto;
  mix-blend-mode: multiply;
}

@media (max-width: 1080px) {
  .studio__stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* the divider is a left-hand rule, so it must not fall on a row start */
  .sfig + .sfig::before { display: none; }
  .sfig:nth-child(even)::before { display: block; }
  /* Once the figures wrap, the booth claims a whole row of its own and adds
     ~250px for no information. It is decoration, so it goes. */
  .sfig--art { display: none; }
}
@media (max-width: 900px) {
  .studio__top { grid-template-columns: 1fr; }
  .studio__copy { max-width: 620px; }
}
@media (max-width: 560px) {
  .studio__stats { grid-template-columns: 1fr; }
  .sfig, .sfig:nth-child(even) { padding-right: 0; }
  .sfig::before, .sfig:nth-child(even)::before { display: none; }
}

/* ---------- Client testimonials ----------------------------
   Three quote cards, each closing on the one number that quote is really
   about. The metric is what separates this from a wall of praise. */
/* The wordmark row below reads as part of this band, so the two meet on a
   shorter gap than a full section break would give — and the map above already
   closes with its own padding, so the top comes in too. */
.voices { padding-block: clamp(22px, 2.6vw, 36px) clamp(24px, 3vw, 40px); }
.voices__head { max-width: 640px; margin: 0 auto clamp(24px, 2.8vw, 34px); text-align: center; }
.voices__title {
  font-family: var(--font-disp); font-weight: 700;
  font-size: clamp(24px, 2.7vw, 36px); line-height: 1.15;
  letter-spacing: -.02em; color: var(--ink); margin-top: 10px;
}
.voices__lede { margin-top: 11px; font-size: 14.5px; }

.voices__grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(16px, 2vw, 26px); }
.voice {
  display: flex; flex-direction: column;
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: clamp(20px, 2.2vw, 28px);
  transition: transform .35s var(--ease), box-shadow .35s var(--ease), border-color .35s var(--ease);
}
.voice:hover { transform: translateY(-4px); box-shadow: var(--shadow-md); border-color: rgba(64,56,212,.2); }
/* Same glyph and font as the Story band's mark, just smaller. Reusing
   .quote-mark means the two can never drift apart. */
.quote-mark--sm { font-size: 38px; margin-bottom: 2px; }
.voice__quote { margin-top: 14px; font-size: 14.5px; line-height: 1.65; color: var(--ink-2); }

/* mt:auto pins the attribution and metric to the bottom, so the three cards
   line up even when the quotes run to different depths */
.voice__by { margin-top: auto; padding-top: 20px; display: flex; align-items: center; gap: 12px; }
.voice__face {
  width: 42px; height: 42px; flex: none; border-radius: 50%;
  object-fit: cover; background: var(--bg-soft-2);
}
.voice__who { display: block; line-height: 1.35; }
.voice__who strong { display: block; font-family: var(--font-disp); font-weight: 600; font-size: 14px; color: var(--ink); }
.voice__who span { display: block; font-size: 12px; color: var(--muted); }
.voice__co { color: var(--brand-ink) !important; font-weight: 500; }

.voice__metric {
  margin-top: 16px; padding-top: 15px; border-top: 1px solid var(--line);
  display: flex; align-items: baseline; gap: 8px;
}
.voice__metric .ico { width: 15px; height: 15px; color: var(--brand); flex: none; align-self: center; }
.voice__metric b {
  font-family: var(--font-disp); font-weight: 700; font-size: clamp(20px, 2vw, 26px);
  line-height: 1; letter-spacing: -.02em; color: var(--brand-ink);
}
.voice__metric span { font-size: 12px; color: var(--muted); line-height: 1.35; }
.voice:hover .voice__metric .ico { transform: translate(2px, -2px); }
.voice__metric .ico { transition: transform .3s var(--ease); }

/* ---------- Trusted by (wordmark row) ----------------------
   Set in type rather than logo files: these are the studio's own client names,
   so there is no artwork to license and nothing to go stale. */
.trust { padding-block: clamp(22px, 2.6vw, 36px); border-top: 1px solid var(--line); }
.trust__label {
  display: block; text-align: center;
  font-family: var(--font-disp); font-weight: 600; font-size: 10.5px;
  letter-spacing: .18em; text-transform: uppercase; color: var(--brand-ink);
}
/* Was one JPEG strip of AWS/Google/Microsoft placeholders. Now the real nine,
   drawn from the same #lg-* symbols the hero marquee uses, so there is one
   source per mark. Greyscale and smaller here — the marquee already carries
   these in violet, and repeating that treatment read as a duplicate block.
   Four brands have no usable artwork anywhere (SINOPEC, EcoFlow, Black Shark,
   Fans-tech) and stand in as type until the client supplies files. */
.trust__grid {
  display: flex; flex-wrap: wrap; justify-content: center; align-items: center;
  gap: clamp(22px, 3.4vw, 52px);
  margin: clamp(14px, 1.8vw, 22px) 0 0; padding: 0; list-style: none;
}

.trust__item {
  display: grid; place-items: center;
  color: #9aa1b4;
  transition: color .35s var(--ease), transform .35s var(--ease);
}

.trust__item:hover { color: var(--brand); transform: translateY(-2px); }

.trust__grid { flex-wrap: wrap; }

.trust__item {
  display: grid; place-items: center;
  padding: 2px 4px;
  /* quieter than the marquee — same tint, less presence, since these two rows
     carry the same logos and the marquee is the one meant to be looked at */
  opacity: .78;
  transition: transform .35s var(--ease), opacity .35s var(--ease);
}

.trust__item:hover { opacity: 1; }

.trust__item:hover { transform: translateY(-2px); }


@media (max-width: 900px) {
  .voices__grid { grid-template-columns: 1fr; }
}

/* ---------- Map / where we work ---------------------------- */
.map-wrap { position: relative; }
.map-svg { width: 100%; height: auto; color: #d3d7e0; }
.map-pin { position: absolute; transform: translate(-50%,-50%); display: flex; align-items: center; gap: 8px; font-family: var(--font-disp); font-weight: 600; font-size: 13px; color: var(--ink); white-space: nowrap; }
.map-pin::before { content:""; width: 10px; height: 10px; border-radius: 50%; background: var(--brand); box-shadow: 0 0 0 5px rgba(64,56,212,.16); flex: none; animation: pulse 2.4s var(--ease) infinite; }
.map-pin.is-left { flex-direction: row-reverse; }

.where-cols { display: grid; grid-template-columns: repeat(6, 1fr); gap: clamp(16px, 2vw, 28px); }
.where-col h4 { font-family: var(--font-disp); font-weight: 700; font-size: 16px; color: var(--brand-ink); margin-bottom: 14px; }
.where-col li { font-size: 14px; color: var(--body); padding: 5px 0; position: relative; padding-left: 16px; }
.where-col li::before { content:""; position:absolute; left:0; top:13px; width:5px; height:5px; border-radius:50%; background: var(--brand-400); }

/* ---------- Testimonial ------------------------------------ */
.testi { display: grid; grid-template-columns: .8fr 1.2fr; gap: clamp(28px, 4vw, 56px); align-items: center; }
.testi__track { overflow: hidden; }
.testi__slides { display: flex; transition: transform .55s var(--ease); }
.testi__slide { flex: 0 0 100%; }
.testi__quote { font-family: var(--font-disp); font-weight: 500; font-size: clamp(18px, 1.9vw, 23px); line-height: 1.5; color: var(--ink); }
.testi__by { margin-top: 24px; padding-top: 18px; border-top: 1px solid var(--line); }
.testi__name { font-family: var(--font-disp); font-weight: 600; color: var(--ink); }
.testi__co { color: var(--brand-ink); font-size: 14px; }
.testi__nav { display: flex; gap: 12px; margin-top: 26px; }
.tnav { width: 46px; height: 46px; border-radius: 50%; border: 1px solid var(--line); display: inline-flex; align-items: center; justify-content: center; color: var(--ink); transition: all .25s; }
.tnav:hover { border-color: var(--ink); }
.tnav--solid { background: var(--brand); color:#fff; border-color: var(--brand); }
.tnav--solid:hover { background: var(--brand-700); }
.tnav .ico { width: 18px; height: 18px; }

/* ---------- Newsletter ------------------------------------- */
.subscribe { display: flex; gap: 12px; max-width: 460px; }
.subscribe input { flex: 1; border: 1px solid var(--line); border-radius: 999px; padding: 15px 22px; font-size: 15px; background:#fff; transition: border-color .25s, box-shadow .25s; }
.subscribe input:focus { outline: none; border-color: var(--brand-400); box-shadow: 0 0 0 4px rgba(79,70,229,.12); }

/* ---------- CTA band --------------------------------------- */
.cta-band { position: relative; overflow: hidden; border-radius: var(--radius-lg); background: var(--grad-cta); color:#fff; padding: clamp(34px, 4.5vw, 60px) clamp(28px, 4vw, 64px); display: grid; grid-template-columns: 1.3fr auto 1fr; align-items: center; gap: clamp(20px, 3vw, 48px); }
.cta-band__title { font-family: var(--font-disp); font-weight: 700; font-size: clamp(26px, 3vw, 38px); color:#fff; line-height: 1.1; letter-spacing:-.02em; }
.cta-band__sub { color: rgba(255,255,255,.82); margin-top: 12px; font-size: 15px; max-width: 40ch; }
.cta-band__logo { justify-self: end; width: clamp(120px, 14vw, 190px); height: auto; opacity: .95; }
.cta-band__logo-img { justify-self: end; width: clamp(124px, 15vw, 200px); height: auto; opacity: .9; filter: brightness(0) invert(1) drop-shadow(0 0 14px rgba(255,255,255,.25)); }
.cta-band__logo .logo-shield, .cta-band__logo .logo-letter { stroke: rgba(255,255,255,.85); }
.cta-band::before { content:""; position:absolute; right:-6%; top:-40%; width: 480px; height: 480px; border-radius:50%; background: radial-gradient(circle, rgba(255,255,255,.14), transparent 62%); }
.cta-band__wave { position:absolute; inset:0; opacity:.5; pointer-events:none; }
.cta-band .btn--white, .cta-band .btn--on-dark { justify-self: center; }

/* ---------- Footer ----------------------------------------- */
.site-footer { background: var(--dk-bg); color: var(--dk-muted); padding-block: clamp(30px, 3vw, 40px); }
.site-footer .container { display: flex; align-items: center; justify-content: space-between; gap: 20px; flex-wrap: wrap; }
.footer-left { font-size: 13px; }
.footer-links { display: flex; gap: 26px; flex-wrap: wrap; font-size: 13px; }
.footer-links a:hover { color: #fff; }
.footer-social { display: flex; gap: 10px; }
.footer-social a { width: 34px; height: 34px; border-radius: 8px; display: inline-flex; align-items: center; justify-content: center; background: rgba(255,255,255,.06); color: rgba(255,255,255,.8); transition: background .25s, color .25s; }
.footer-social a:hover { background: var(--brand); color:#fff; }
.footer-social .ico { width: 16px; height: 16px; }

/* ---------- Contact (light) -------------------------------- */
.contact-grid { display: grid; grid-template-columns: 1.35fr .9fr; gap: clamp(28px, 4vw, 60px); }
.form-card { background:#fff; border: 1px solid var(--line); border-radius: var(--radius-lg); padding: clamp(24px, 3vw, 40px); box-shadow: var(--shadow-sm); }
.form-card h2 { font-size: 24px; margin-bottom: 22px; }

/* 联系页专用：卡片要从深色带里"浮"出来，所以阴影给足、描边收掉。
   原来是 1px 灰描边 + shadow-sm，压在白底上几乎看不出边界。 */
.cx .form-card {
  border-color: transparent;
  border-radius: 18px;
  box-shadow:
    0 28px 70px rgba(30, 8, 78, .22),
    0 6px 18px rgba(30, 8, 78, .10),
    inset 0 1px 0 rgba(255, 255, 255, .9);
}

.cx .form-card h2 {
  font-size: clamp(21px, 1.7vw, 25px);
  letter-spacing: -.02em;
}

/* 首页的 .eyebrow 是紫色小字母间距，表单标签沿用同一套语言 —— 
   原来是灰色，在整页里读起来像禁用态。 */
.cx .field label { color: var(--brand-ink); }

.cx .field input,
.cx .field select,
.cx .field textarea {
  background: #fbfaff;
  border-color: #e4e0f2;
  transition: border-color .22s var(--ease), box-shadow .22s var(--ease), background-color .22s var(--ease);
}

.cx .field input:hover,
.cx .field select:hover,
.cx .field textarea:hover { border-color: #cfc7ea; }

.cx .field input:focus,
.cx .field select:focus,
.cx .field textarea:focus {
  background: #fff;
  border-color: var(--brand);
  box-shadow: 0 0 0 4px rgba(124, 58, 237, .12);
}
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.field { position: relative; }
.field--full { grid-column: 1 / -1; }
.field label { display:block; font-family: var(--font-disp); font-weight:600; font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); margin-bottom: 7px; }
.field input, .field select, .field textarea {
  width: 100%; border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 14px 16px; font-size: 15px; background: #fff; color: var(--ink); transition: border-color .2s, box-shadow .2s;
}
.field textarea { min-height: 132px; resize: vertical; }
.field input:focus, .field select:focus, .field textarea:focus { outline: none; border-color: var(--brand-400); box-shadow: 0 0 0 4px rgba(79,70,229,.1); }
.field select { appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23888ea0' stroke-width='2.5'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 16px center; }
.form-note { font-size: 13px; color: var(--muted); margin-top: 14px; }
.form-status { margin-top: 14px; font-size: 14px; font-weight: 500; }
.form-status.ok { color: #17915a; }
.form-status.err { color: #d0344a; }

.info-list { display: flex; flex-direction: column; }
.info-item { padding: 20px 0; border-bottom: 1px solid var(--line); }
.info-item:first-child { padding-top: 0; }
.info-item .k { font-family: var(--font-disp); font-weight: 600; font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); margin-bottom: 6px; }
.info-item .v { color: var(--ink-2); font-size: 15px; line-height: 1.6; }
.info-item .v a:hover { color: var(--brand-ink); }
.info-title { font-family: var(--font-disp); font-weight: 600; color: var(--brand-ink); letter-spacing: .04em; text-transform: uppercase; font-size: 12px; margin-bottom: 18px; }

/* local support columns */
.support-cols { display: grid; grid-template-columns: 1.2fr repeat(5, 1fr); gap: clamp(18px, 2vw, 32px); align-items: start; }
.support-cols h4 { font-family: var(--font-disp); font-weight: 700; font-size: 15px; color: var(--ink); margin-bottom: 8px; }
.support-cols .region-sub { font-size: 12px; color: var(--brand-ink); font-weight: 600; letter-spacing:.03em; margin-bottom: 8px; }
.support-cols p { font-size: 13px; }

/* ---------- Dark page (lets-talk) -------------------------- */
body.theme-dark { background: var(--dk-bg); color: var(--dk-muted); }
.theme-dark .hero::before { background: radial-gradient(900px 500px at 78% 30%, rgba(90,63,230,.22), transparent 60%), var(--dk-bg); }
.hero-dark { position: relative; padding-top: 84px; overflow: hidden; background: var(--dk-bg); }
.hero-dark::before { content:""; position:absolute; inset:0; background: radial-gradient(1000px 620px at 74% 34%, rgba(96,72,240,.28), transparent 58%), radial-gradient(700px 500px at 100% 0%, rgba(64,56,212,.2), transparent 60%); }
.hero-dark .container { position: relative; }
.hero-dark-inner { display: grid; grid-template-columns: 1fr 1fr; align-items: center; gap: 40px; min-height: 74vh; padding-block: 60px; }
.hero-dark h1 { font-family: var(--font-disp); font-weight: 800; color:#fff; font-size: clamp(52px, 8vw, 104px); line-height: .96; letter-spacing: -.03em; text-transform: uppercase; }
.hero-dark h1 .accent { color: var(--brand-400); }
.hero-dark .rule { width: 84px; height: 3px; background: var(--brand-400); margin: 26px 0; border-radius: 3px; }
.hero-dark p { color: rgba(255,255,255,.72); font-size: clamp(15px,1.3vw,18px); max-width: 44ch; }

.neon-logo { position: relative; justify-self: center; width: min(100%, 480px); filter: drop-shadow(0 0 40px rgba(96,72,240,.5)); animation: float 6s ease-in-out infinite; }
.neon-logo svg { width: 100%; height: auto; overflow: visible; position: relative; z-index: 1; }
.neon-logo-img { width: 100%; height: auto; display: block; position: relative; z-index: 1; }
.neon-logo::before { content:""; position:absolute; top:-34%; right:-14%; width:60%; height:154%; background: linear-gradient(212deg, rgba(170,145,255,.36) 0%, rgba(130,100,250,.09) 38%, transparent 64%); transform: rotate(20deg); filter: blur(18px); border-radius: 45%; pointer-events:none; z-index:0; }

.theme-dark .section { background: var(--dk-bg-2); }
.theme-dark .eyebrow { color: var(--brand-400); }
.theme-dark h1,.theme-dark h2,.theme-dark h3 { color:#fff; }
.dark-contact { display: grid; grid-template-columns: .82fr 1.18fr; gap: clamp(30px,4vw,64px); align-items: start; }
.dark-contact h2 { font-size: clamp(26px,3vw,34px); color:#fff; }
.dark-contact .dc-sub { color: rgba(255,255,255,.66); margin-top: 14px; max-width: 34ch; }
.dc-info { margin-top: 34px; display: flex; flex-direction: column; gap: 22px; }
.dc-info-item { display: flex; gap: 16px; align-items: flex-start; }
.dc-ico { width: 44px; height: 44px; border-radius: 12px; background: rgba(96,72,240,.16); color: var(--brand-400); display: inline-flex; align-items: center; justify-content: center; flex: none; }
.dc-ico .ico { width: 20px; height: 20px; }
.dc-k { font-family: var(--font-disp); font-weight: 600; font-size: 11px; letter-spacing: .12em; text-transform: uppercase; color: var(--brand-400); }
.dc-v { color: #fff; font-size: 15px; margin-top: 2px; }
.form-card--dark { background: rgba(255,255,255,.02); border: 1px solid var(--dk-line); border-radius: var(--radius-lg); padding: clamp(24px,3vw,40px); }
.form-card--dark .field input, .form-card--dark .field textarea { background: rgba(255,255,255,.03); border-color: var(--dk-line); color:#fff; }
.form-card--dark .field input::placeholder, .form-card--dark .field textarea::placeholder { color: rgba(255,255,255,.42); }
.form-card--dark .field input:focus, .form-card--dark .field textarea:focus { border-color: var(--brand-400); box-shadow: 0 0 0 4px rgba(96,72,240,.16); }
.theme-dark .site-footer { background: #06060d; }

/* Industry cards (image + text overlay bottom, taller) */
.industry-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 14px; }
.industry-grid .tile { aspect-ratio: 3/4.1; }
.industry-grid .tile__desc { font-size: 12px; color: rgba(255,255,255,.76); margin-top: 6px; line-height: 1.5; }

/* Show environment cards */
.showenv-grid { display: grid; grid-template-columns: repeat(4,1fr); gap: 16px; }
.showenv .media { aspect-ratio: 5/4; margin-bottom: 14px; background:#0d1018; }
.showenv h4 { font-family: var(--font-disp); font-weight: 600; font-size: 16px; color: var(--ink); margin-bottom: 6px; }
.showenv p { font-size: 13.5px; }

/* experience collage (mixed grid) */
.exp-collage { display: grid; grid-template-columns: 1fr 1fr 1fr; grid-auto-rows: 1fr; gap: 12px; }
.exp-collage .media { aspect-ratio: 1/1; }
.exp-collage .media.span-2 { grid-column: span 2; aspect-ratio: 2/1; }

/* ---------- Animations -------------------------------------
   The [data-reveal] rules live up in the base layer, above the components —
   see the note there. */
@media (prefers-reduced-motion: reduce) { .map-pin::before, .neon-logo { animation: none; } }

/* ---------- Hero enhancements ------------------------------ */
.hero-inner { grid-template-columns: minmax(0,0.95fr) minmax(0,1.16fr); }
.hero-media { aspect-ratio: auto; height: clamp(360px, 40vw, 552px); }
.hero-media img { position: relative; z-index: 0; }
.hero-media::after {
  content:""; position:absolute; inset:0; z-index:1; pointer-events:none;
  background:
    radial-gradient(120% 82% at 72% -6%, rgba(126,102,255,.32), transparent 54%),
    linear-gradient(126deg, rgba(46,41,196,.30) 0%, rgba(12,10,30,.02) 46%, rgba(90,63,230,.30) 100%);
}
.hero-media__tag { z-index: 2; }

/* ---------- Home cover hero (full-bleed background image) --- */
.hero--cover { position: relative; min-height: 90vh; display: flex; align-items: center; overflow: hidden; }
.hero--cover::before { display: none; }
.hero-bg { position: absolute; inset: 0; z-index: -2; }
.hero-bg img { width: 100%; height: 100%; object-fit: cover; object-position: 58% center; }
.hero--cover::after {
  content:""; position:absolute; inset:0; z-index:-1;
  background:
    linear-gradient(90deg, rgba(6,6,16,.93) 0%, rgba(8,7,20,.74) 32%, rgba(12,10,30,.34) 58%, rgba(26,17,58,.12) 100%),
    linear-gradient(180deg, rgba(6,6,16,.42) 0%, transparent 24%, transparent 70%, rgba(6,6,16,.58) 100%);
}
/* ---------- Pixel dissolve (section seam) ------------------------
   Cells are injected by main.js. */
.pixel-fade {
  position: relative; overflow: hidden; line-height: 0;
  height: clamp(120px, 13vw, 192px);
  background: var(--bg);
}
.pixel-fade__cell {
  position: absolute; width: 30px; height: 30px;
  background-color: transparent;
  transition: background-color .12s linear;
}
/* Rides the bottom edge of the section above with a transparent ground, so
   unlit cells show that section's own image and lit cells are already the
   colour of the section below — one continuous dissolve rather than two.
   z-index 1 keeps it under .container, so copy always stays on top. */
.pixel-fade--overlay {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  /* Taller than the copy's clearance on purpose: on the copy side the ground
     already matches the cells, so the extra rows are invisible there and only
     bite where there is photo to dissolve.
     Raised from 240px: at CELL = 30 that was only 8 rows, and the density curve
     had to drop from a solid join to two-thirds in a single 30px step — which
     is what read as a hard edge with squares sprinkled over it rather than a
     dissolve. 360px buys 12 rows for the same curve to spend itself over. */
  height: clamp(200px, 25vw, 360px);
  background: transparent;
  pointer-events: none;
}
@media (max-width: 620px) { .pixel-fade--overlay { height: 150px; } }

/* Exit seam: the mirror of the entry above, so the tinted run is bracketed by
   the same texture at both ends instead of starting soft and stopping dead.
   Any section can host one — it just needs to be the positioning context and
   to keep its copy above the cells. */
.section--seam { position: relative; }
.section--seam > .container { position: relative; z-index: 3; }
/* Extra room above the copy purely so the dissolve has somewhere to live. The
   flipped front settles ~64% up the strip, so the strip may reach down past
   the heading as long as those last rows stay empty — this padding buys that. */
/* Clearance for the dissolve mounted at this section's top edge. Raised with
   the strip itself (240 → 360px): the extra rows reach further down, and at the
   old 150px the eyebrow sat among cells still ~22% filled. */
.section--seam { padding-top: calc(var(--section) + clamp(70px, 7vw, 100px)); }
/* Hangs off the TOP edge instead of the bottom: the tint above trails down
   into this section rather than this section creeping up into the tint. */
.pixel-fade--seam { top: 0; bottom: auto; height: clamp(200px, 25vw, 360px); }
@media (max-width: 620px) { .pixel-fade--seam { height: 150px; } }

/* ---------- Featured showcase (mosaic + copy) --------------------
   Image mosaic on the left, headline / rule / two-up detail on the right.
   Sits on the standard soft grey; the photos supply the dark weight. */
.showcase {
  background: var(--bg-tint);
  padding-block: clamp(64px, 8vw, 118px);
}
.showcase__grid {
  /* copy column deliberately narrower than the mosaic: the text is short, and
     at equal widths the rule and lead stretch into over-long lines */
  display: grid; grid-template-columns: 1.24fr .76fr;
  gap: clamp(30px, 4.4vw, 74px); align-items: start;
}
.showcase__mosaic {
  display: grid; grid-template-columns: 1.02fr .98fr;
  grid-template-rows: repeat(3, 1fr);
  gap: clamp(10px, 1.1vw, 16px);
  min-height: clamp(430px, 40vw, 620px);
}
.showcase__tall { grid-row: 1 / -1; }
.showcase .tile { min-height: 0; box-shadow: none; }

/* Brand-tinted veil over all four photos: levels their different exposures,
   gives the white captions a consistent ground, and darkens the surface so
   the blue beam reads. Lifts on hover so the photo comes forward. */
.showcase .tile img {
  filter: brightness(.7) saturate(1.08);
  transition: transform .7s var(--ease), filter .45s var(--ease);
}
/* hover clears the veil entirely — the photo returns to its true colour */
.showcase .tile:hover img,
.showcase .tile:focus-visible img { filter: brightness(1) saturate(1); }
/* Flat tint as background-COLOR (interpolable, so it can fade) with the
   caption gradient as background-IMAGE painted over it, so the caption keeps
   its footing even once the tint is gone. */
.showcase .tile::after {
  background-color: rgba(22, 24, 66, .3);
  background-image: linear-gradient(to top, rgba(8, 9, 16, .9) 4%, rgba(8, 9, 16, .34) 44%, rgba(8, 9, 16, .05) 74%);
  transition: background-color .45s var(--ease);
}
.showcase .tile:hover::after,
.showcase .tile:focus-visible::after { background-color: rgba(22, 24, 66, 0); }

/* Travelling light around the image border: a short conic arc whose start
   angle is animated. Needs @property so the angle can interpolate; without it
   the arc simply sits still instead of breaking. */
@property --beam-angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0turn;
}
@keyframes beamRotate { to { --beam-angle: 1turn; } }

.tile--accent::before {
  content: ""; position: absolute; inset: 0; z-index: 4;
  border-radius: inherit; padding: var(--beam-w, 1.25px);
  /* Blues sampled from the logo artwork: #3c3ca0 is its brightest saturated
     tone (hue 240°), brightened through #4552d8 to a white-hot head.
     The arc runs ~45% of the circle — a short one leaves most of the border
     dark at any instant, which reads as "the effect isn't on". */
  background: conic-gradient(from var(--beam-angle),
    transparent 55%,
    rgba(60, 60, 160, .32) 67%,
    rgba(69, 82, 216, .75) 79%,
    rgba(120, 140, 255, .95) 88%,
    var(--beam-hot, #ffffff) 93%,
    rgba(120, 140, 255, .9) 97%,
    rgba(60, 60, 160, .3) 99.5%,
    transparent 100%);
  /* keep only the padding ring — punch the inner box back out */
  -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;
  animation: beamRotate var(--beam-speed, 7s) linear infinite;
  animation-delay: var(--beam-delay, 0s);
  pointer-events: none;
  filter: drop-shadow(0 0 4px rgba(69, 82, 216, .7));
  transition: filter .4s var(--ease);
}
/* Four tiles on one animation ran in perfect lockstep — every light sat at the
   same angle, so the group blinked as one object. Mismatched periods (never a
   common multiple) plus offset starts keep them drifting apart for good. */
.showcase__mosaic .tile--accent:nth-child(1) { --beam-speed: 6.4s; --beam-delay: -0.4s; }
.showcase__mosaic .tile--accent:nth-child(2) { --beam-speed: 7.9s; --beam-delay: -2.6s; }
.showcase__mosaic .tile--accent:nth-child(3) { --beam-speed: 5.7s; --beam-delay: -4.1s; }
.showcase__mosaic .tile--accent:nth-child(4) { --beam-speed: 8.6s; --beam-delay: -1.3s; }
/* hover: the light speeds up, thickens and blooms */
.tile--accent:hover::before,
.tile--accent:focus-visible::before {
  --beam-speed: 2.4s;
  --beam-w: 2px;
  filter: drop-shadow(0 0 9px rgba(69, 82, 216, .95));
}
@media (prefers-reduced-motion: reduce) {
  .tile--accent::before { animation: none; }
}

.showcase__title {
  font-family: var(--font-disp); font-weight: 700; color: var(--ink);
  /* sized so "Built to Command" still clears the narrowed column on one line
     — any larger and the manual break lands mid-phrase */
  font-size: clamp(28px, 2.9vw, 40px); line-height: 1.06; letter-spacing: -.02em;
  margin-top: 8px;
}
/* -6px lifts the whole block by the eyebrow's half-leading, so its glyphs sit
   flush with the top edge of the mosaic instead of 6px below it. Applied to
   the grid item rather than the eyebrow itself — the eyebrow is inline-block,
   where a negative top margin gets absorbed by the line box. */
/* The copy pins to the viewport while the taller mosaic keeps scrolling past
   it — 287px of travel at 1440. Needs the grid's `align-items: start`, or a
   stretched item has nothing to slide against. Offset clears the fixed 70px
   header and is capped so the block still fits a short viewport. */
.showcase__copy {
  max-width: 460px; margin-top: -6px;
  position: sticky; top: clamp(88px, 12vh, 110px);
}
@media (max-width: 1000px) { .showcase__copy { position: static; } }
/* Few words in a narrow column: global body leading (1.7) and the standard
   section rhythm leave this looking loose, so both are tightened locally. */
.showcase__copy .eyebrow { margin-bottom: 10px; }
.showcase__lead { color: var(--body); margin-top: 12px; max-width: 34ch; line-height: 1.55; }
/* the neutral --line reads dirty on the tinted ground, so tint the rule too */
.showcase__rule { border: 0; height: 1px; background: rgba(74, 64, 224, .18); margin: clamp(20px, 2.1vw, 28px) 0; }
.showcase__cols { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(18px, 2.2vw, 30px); }
.showcase__col h3 { font-size: 17px; font-weight: 600; color: var(--ink); line-height: 1.25; }
.showcase__col p { margin-top: 7px; color: var(--body); font-size: 15px; max-width: 30ch; line-height: 1.55; }
.showcase .link-arrow { transition: color .22s var(--ease); }
.showcase .link-arrow:hover { color: var(--ink); }
.showcase__all { margin-top: clamp(20px, 2.1vw, 28px); }

@media (max-width: 1000px) {
  .showcase__grid { grid-template-columns: 1fr; }
  .showcase__mosaic { min-height: clamp(380px, 62vw, 520px); }
}
@media (max-width: 620px) {
  .showcase__mosaic { grid-template-columns: 1fr; grid-template-rows: none; min-height: 0; }
  .showcase__tall { grid-row: auto; }
  .showcase .tile { aspect-ratio: 16/10; }
  .showcase__cols { grid-template-columns: 1fr; }
}

/* ---------- Client marquee (seam between dark hero and light) ----
   Continues the hero's dark surface, then hands off to the white story
   band below. Names dissolve at both edges rather than hard-cutting. */
.marquee {
  position: relative; overflow: hidden;
  background: var(--dk-bg);
  border-bottom: 1px solid var(--dk-line);
  padding-block: clamp(17px, 1.9vw, 25px);
}
/* fade the names out over the band's own dark surface — masking the section
   itself would punch holes through to the white page behind it */
.marquee::before, .marquee::after {
  content: ""; position: absolute; top: 0; bottom: 0; z-index: 2;
  width: clamp(48px, 8vw, 130px); pointer-events: none;
}
.marquee::before { left: 0; background: linear-gradient(90deg, var(--dk-bg) 0%, rgba(10,10,20,0) 100%); }
.marquee::after { right: 0; background: linear-gradient(270deg, var(--dk-bg) 0%, rgba(10,10,20,0) 100%); }
.marquee__track { display: flex; width: max-content; animation: marquee 46s linear infinite; }
.marquee:hover .marquee__track { animation-play-state: paused; }
.marquee__list { display: flex; align-items: center; }
.marquee__list li {
  display: flex; align-items: center; gap: clamp(26px, 3.2vw, 52px);
  padding-inline: clamp(13px, 1.6vw, 26px);
  font-family: var(--font-disp); font-weight: 700;
  font-size: clamp(15px, 1.3vw, 20px);
  letter-spacing: .045em; text-transform: uppercase;
  color: rgba(237, 236, 245, .82); white-space: nowrap;
  transition: color .3s var(--ease);
}
/* brand-violet bead as the separator, echoing the `.dot` accent in headings */
.marquee__list li::after {
  content: ""; flex: none; width: 5px; height: 5px; border-radius: 50%;
  background: var(--brand-400); opacity: .6;
}
.marquee__list li:hover { color: #fff; }
@keyframes marquee { to { transform: translateX(-50%); } }

/* Logo strip version. The artwork ships already inverted onto the band's exact
   colour, rather than being flipped at runtime: the track animates transform,
   which makes it a stacking context, and that isolates mix-blend-mode so the
   image's black ground blends against nothing and shows as a black box. */
.marquee--logos .marquee__track { align-items: center; }
.marquee__logos {
  flex: none; display: block; height: auto;
  width: clamp(760px, 62vw, 1080px);
  padding-inline: clamp(24px, 3vw, 52px);
  opacity: .82;
  transition: opacity .35s var(--ease);
}
.marquee--logos:hover .marquee__logos { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .marquee__track { animation: none; }
  .marquee { overflow-x: auto; }
}

/* ---------- Story band (full-bleed design background) ------
   The artwork is the section background, not a framed card: copy sits
   directly on the light left half of the image. */
.story-band {
  position: relative; overflow: hidden;
  display: flex; align-items: center;
  min-height: clamp(520px, 44vw, 700px);
  padding-block: clamp(56px, 6vw, 100px);
  background: var(--bg);
}
.story-band__bg { position: absolute; inset: 0; z-index: 0; }
.story-band__bg img {
  width: 100%; height: 100%; object-fit: cover; object-position: 50% 50%;
  transform: scale(1.07); transition: transform 1.8s var(--ease);
}
.story-band.is-visible .story-band__bg img { transform: none; }
/* White veil over the copy side, so the studio photograph stays bright and
   the type sits on clean paper. Fully opaque for the first 30% — at 97% the
   3% of photo bleeding through was enough to make the dissolve cells below
   faintly visible beside the text. */
.story-band::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(90deg,
    rgba(255,255,255,1) 0%,
    rgba(255,255,255,1) 30%,
    rgba(255,255,255,.6) 46%,
    rgba(255,255,255,.07) 64%,
    rgba(255,255,255,0) 100%);
}
/* Stacking here is load-bearing: ::after is generated as the last child, so at
   equal z-index the veil paints OVER the dissolve and the cells only ever show
   where the veil is transparent. Veil 1 < dissolve 2 < copy 3. */
.story-band > .container { position: relative; z-index: 3; }
.story-band__copy { max-width: 520px; }
.story-band__copy .quote-lg { font-size: clamp(23px, 2.8vw, 34px); }
.story-band__copy .story-copy p { margin-top: 18px; }
.story-band__copy .story-copy .link-arrow { margin-top: 26px; }
@media (prefers-reduced-motion: reduce) {
  .story-band__bg img { transform: none; transition: none; }
}

.hero-cover-copy { position: relative; max-width: 600px; color: #fff; padding-block: clamp(70px, 10vw, 120px); }
.hero--cover .hero-title { color: #fff; }
.hero--cover .hero-title .dot { color: var(--brand-400); }
.hero--cover .hero-sub { color: rgba(255,255,255,.84); }
.hero--cover .hero-glyph { color: rgba(255,255,255,.08); z-index: 0; opacity: 1; }
.hero--cover .hero-media__tag { position: absolute; top: 104px; right: 22px; z-index: 1; color: rgba(255,255,255,.5); }

/* ---------- Footer reveal -----------------------------------
   The CTA band is opaque and in normal flow; the footer is pinned to the
   viewport bottom BEHIND it. Scrolling the last stretch walks the CTA up and
   off, uncovering the footer — nothing animates, the layering does the work.
   The spacer is what makes that last stretch scrollable, and JS sets its
   height to the footer's so the reveal ends exactly as the footer is full. */
main { position: relative; z-index: 1; background: var(--bg); }
.footer-cta { position: relative; z-index: 2; }
/* carries the CTA's gradient now that it is no longer nested inside it — the
   deck's ink wash is translucent and needs that violet underneath, or it grades
   against the white page and comes out grey */
.footer-reveal { position: fixed; left: 0; right: 0; bottom: 0; z-index: 0; background: var(--grad-cta); }
/* Carries the same gradient as the footer it stands in for. The spacer occupies
   exactly the strip the footer is revealed through, so if its height is ever a
   few pixels out of step — a webfont landing late, a lazy image resizing the
   deck — the seam shows violet instead of a white band. */
.footer-spacer { height: 0; background: var(--grad-cta); }
/* not enough viewport to uncover it — fall back to plain stacking */
body.no-footer-reveal .footer-reveal { position: static; }
body.no-footer-reveal .footer-spacer { display: none; }

/* ---------- Footer deck -------------------------------------
   Sits on the CTA's own gradient with an ink wash over it, so the three bands
   read as one descending ramp — bright CTA, deep deck, darkest bar — without
   inventing a second palette to keep in sync. The contour lines are the same
   idea as the blueprint map: brand hue, barely there. */
.footer-deck {
  position: relative;
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 320' preserveAspectRatio='none'%3E%3Cg fill='none' stroke='%23ffffff' stroke-width='1.1' opacity='.09'%3E%3Cpath d='M0 250C150 210 260 300 420 262 580 224 700 296 860 254 1000 218 1100 268 1200 240'/%3E%3Cpath d='M0 272C150 232 260 322 420 284 580 246 700 318 860 276 1000 240 1100 290 1200 262'/%3E%3Cpath d='M0 294C150 254 260 344 420 306 580 268 700 340 860 298 1000 262 1100 312 1200 284'/%3E%3Cpath d='M0 228C150 188 260 278 420 240 580 202 700 274 860 232 1000 196 1100 246 1200 218'/%3E%3Cpath d='M0 206C150 166 260 256 420 218 580 180 700 252 860 210 1000 174 1100 224 1200 196'/%3E%3Cpath d='M0 184C160 148 270 232 430 198 590 164 710 230 870 192 1010 158 1105 204 1200 178'/%3E%3C/g%3E%3C/svg%3E") bottom center / 100% auto no-repeat,
    linear-gradient(180deg, rgba(9,7,38,.30), rgba(9,7,38,.52));
  border-top: 1px solid rgba(255,255,255,.09);
  padding-block: clamp(34px, 4.2vw, 62px);
  color: rgba(255,255,255,.78);
}
.footer-deck__grid {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) repeat(3, minmax(0, 1fr));
  gap: clamp(26px, 3.4vw, 56px);
}
.fcol__brand {
  font-family: var(--font-disp); font-weight: 700; font-size: 19px;
  letter-spacing: .04em; color: #fff;
}
.fcol--brand p { margin-top: 12px; font-size: 13.5px; line-height: 1.65; color: rgba(255,255,255,.7); max-width: 40ch; }
.fcol__title {
  font-family: var(--font-disp); font-weight: 600; font-size: 12px;
  letter-spacing: .14em; text-transform: uppercase; color: #fff;
}
.fcol__title--tight { margin-top: 22px; }
.fcol__list { margin-top: 14px; display: grid; gap: 9px; font-size: 13.5px; }
.fcol__list a { color: rgba(255,255,255,.72); transition: color .25s var(--ease), transform .25s var(--ease); display: inline-block; }
.fcol__list a:hover { color: #fff; transform: translateX(3px); }
.fcol__list--plain li { color: rgba(255,255,255,.7); }

.fnews { margin-top: 22px; }
.fnews__label { display: block; font-family: var(--font-disp); font-weight: 600; font-size: 13px; color: #fff; }
.fnews__row { margin-top: 10px; display: flex; gap: 8px; max-width: 360px; }
.fnews__input {
  flex: 1 1 auto; min-width: 0;
  background: rgba(255,255,255,.07); border: 1px solid rgba(255,255,255,.22);
  border-radius: 10px; padding: 11px 14px;
  font-family: var(--font-body); font-size: 13.5px; color: #fff;
  transition: border-color .25s var(--ease), background .25s var(--ease);
}
.fnews__input::placeholder { color: rgba(255,255,255,.5); }
.fnews__input:focus { outline: none; border-color: rgba(255,255,255,.6); background: rgba(255,255,255,.12); }
.fnews__btn {
  flex: none; border-radius: 10px; padding: 11px 20px;
  background: #fff; color: var(--brand);
  font-family: var(--font-disp); font-weight: 600; font-size: 12.5px;
  letter-spacing: .08em; text-transform: uppercase;
  transition: transform .25s var(--ease), box-shadow .25s var(--ease);
}
.fnews__btn:hover { transform: translateY(-2px); box-shadow: 0 10px 24px rgba(0,0,0,.28); }
.fnews.is-done .fnews__btn { background: rgba(255,255,255,.22); color: #fff; }
.fnews.is-bad .fnews__input { border-color: #ff9a8a; background: rgba(255,120,100,.14); }
.fnews__msg { min-height: 17px; margin-top: 7px; font-size: 12.5px; color: rgba(255,255,255,.82); }
.fnews.is-bad .fnews__msg { color: #ffb3a6; }

.fsocial { margin-top: 12px; display: flex; gap: 10px; }
.fsocial a {
  width: 34px; height: 34px; border-radius: 10px;
  display: grid; place-items: center;
  background: rgba(255,255,255,.1); color: #fff;
  transition: background .25s var(--ease), transform .25s var(--ease);
}
.fsocial a:hover { background: rgba(255,255,255,.22); transform: translateY(-2px); }
.fsocial .ico { width: 16px; height: 16px; }

@media (max-width: 900px) {
  .footer-deck__grid { grid-template-columns: 1fr 1fr; }
  .fcol--brand { grid-column: 1 / -1; }
}
@media (max-width: 560px) {
  .footer-deck__grid { grid-template-columns: 1fr; }
}

/* ---------- Back to top ------------------------------------- */
.to-top {
  position: fixed; right: clamp(16px, 2vw, 28px); bottom: clamp(16px, 2vw, 28px);
  z-index: 70; width: 46px; height: 46px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--grad-btn); color: #fff;
  box-shadow: var(--shadow-brand);
  opacity: 0; transform: translateY(14px) scale(.9); pointer-events: none;
  transition: opacity .35s var(--ease), transform .35s var(--ease), box-shadow .35s var(--ease);
}
.to-top svg { width: 19px; height: 19px; }
.to-top.is-on { opacity: 1; transform: none; pointer-events: auto; }
.to-top:hover { transform: translateY(-3px); box-shadow: 0 18px 40px rgba(64,56,212,.5); }

/* header: white text while over the dark hero, reverts on scroll */
.site-header--over-dark:not(.is-scrolled) .brand-name { color: #fff; }
.site-header--over-dark:not(.is-scrolled) .nav a { color: rgba(255,255,255,.88); }
.site-header--over-dark:not(.is-scrolled) .nav a.is-active { color: #fff; }
.site-header--over-dark:not(.is-scrolled) .logo-shield,
.site-header--over-dark:not(.is-scrolled) .logo-letter { stroke: #fff; }
.site-header--over-dark:not(.is-scrolled) .nav-toggle span,
.site-header--over-dark:not(.is-scrolled) .nav-toggle span::before,
.site-header--over-dark:not(.is-scrolled) .nav-toggle span::after { background: #fff; }

/* ---------- Insight on-image category pill ----------------- */
.insight .media { position: relative; }
.insight__pill {
  position: absolute; top: 12px; left: 12px; z-index: 3;
  background: #fff; color: var(--ink);
  font-family: var(--font-disp); font-weight: 600; font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  padding: 6px 13px; border-radius: 999px; box-shadow: 0 4px 14px rgba(16,18,30,.18);
}

/* ---------- Home insights: head, filter, cards, duo band ----
   The category moved off the photo and into the body as a tinted chip, and each
   card now carries an excerpt and a date/read-time footer. */
.insights__head { margin-bottom: clamp(20px, 2.4vw, 30px); }
.insights__headRow {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 24px; flex-wrap: wrap; margin-top: 10px;
}
.insights__lede { margin-top: 10px; max-width: 700px; font-size: 15px; }
.insights__grid { margin-top: clamp(20px, 2.4vw, 30px); }

.pills--sm { gap: 8px; }
.pills--sm .pill { padding: 8px 17px; font-size: 12.5px; }

.insight__chip {
  /* the body is a flex column, so without this the chip stretches the full
     card width instead of hugging its label */
  align-self: flex-start;
  background: var(--brand-soft); color: var(--brand-ink);
  font-family: var(--font-disp); font-weight: 600;
  font-size: 10px; letter-spacing: .1em; text-transform: uppercase;
  padding: 5px 10px; border-radius: 999px;
}
.insight__body .insight__title { margin: 12px 0 8px; }
/* mt:auto pins the footer to the card bottom, so the three footers line up even
   when the titles wrap to different depths */
.insight__foot {
  margin-top: auto; padding-top: 16px;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
}
.insight__foot .link-arrow { margin-top: 0; }
.insight__meta { font-size: 12px; color: var(--muted); }
.insight__body { display: flex; flex-direction: column; flex: 1; }

@media (max-width: 620px) {
  .insight__foot { flex-direction: column; align-items: flex-start; gap: 8px; }
}

/* ---------- Inner pages: clearing the fixed chrome ---------------------------
   The topbar and header are both position: fixed, so the first section of any
   page that is not the homepage slides underneath them. The homepage hero
   clears them with calc(var(--header-h) + var(--topbar-h) + 40px), but those
   tokens are scoped to the Violet Axis blocks and are not readable from this
   sheet — a var() here would silently fall back and the gap would be wrong.
   Literals, therefore, and they must be kept in step with va-blocks.css.
   -------------------------------------------------------------------------- */
:root { --chrome-h: 130px; }                 /* 88px header + 42px topbar */
@media (max-width: 860px) { :root { --chrome-h: 108px; } }   /* 66 + 42 */
@media (max-width: 760px) { :root { --chrome-h: 104px; } }   /* 66 + 38 */

.page-top { padding-top: calc(var(--chrome-h) + clamp(30px, 4vw, 62px)); }

/* ---------- Projects page ---------------------------------------------------
   Reuses the site's existing filter contract (data-filter-group + data-cat) and
   the tinted-section wash, so the page inherits the homepage's behaviour rather
   than growing a parallel set of its own.
   -------------------------------------------------------------------------- */
.wk-hero { padding-bottom: clamp(24px, 3vw, 40px); }

.wk-hero__title {
  margin-top: 10px;
  font-size: clamp(34px, 5vw, 64px); line-height: 1.02;
  letter-spacing: -.03em; color: var(--ink);
}

.wk-hero__lede {
  margin-top: 18px; max-width: 52ch;
  font-size: clamp(15px, 1.2vw, 17px); color: var(--body);
}

.wk-sec .pills { margin-bottom: clamp(22px, 2.6vw, 34px); }

/* auto-fit rather than a fixed count: filtering removes cards, and a fixed
   3-up leaves a ragged hole when a sector has two */
.wk__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: clamp(18px, 2.2vw, 32px);
}

.wk {
  background: #fff; border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  transition: transform .4s var(--ease), box-shadow .4s var(--ease);
}

.wk:hover { transform: translateY(-6px); box-shadow: var(--shadow-md); }

.wk__shot { position: relative; overflow: hidden; background: var(--bg-soft); }

.wk__shot img {
  width: 100%; height: 100%; display: block;
  aspect-ratio: 3 / 2; object-fit: cover;
  transition: transform .7s var(--ease);
}

.wk:hover .wk__shot img { transform: scale(1.05); }

.wk__body { padding: clamp(16px, 1.8vw, 24px); }

.wk__sector {
  display: inline-block;
  font-family: var(--font-disp); font-weight: 700;
  font-size: .68rem; letter-spacing: .16em; text-transform: uppercase;
  color: var(--brand-ink);
}

.wk__client {
  margin-top: 7px;
  font-size: clamp(17px, 1.4vw, 21px); letter-spacing: -.015em; color: var(--ink);
}

.wk__desc { margin-top: 8px; font-size: 13.5px; line-height: 1.6; color: var(--body); }

@media (max-width: 560px) {
  .wk__grid { grid-template-columns: 1fr; }
}

/* ---------- About page ------------------------------------------------------
   Copy is lifted verbatim from the brochure (About Our Company, Our Value,
   Our Factory) rather than rewritten, so the site and the deck say the same
   thing to the same client.
   -------------------------------------------------------------------------- */
.ab-hero__title {
  margin-top: 10px;
  font-size: clamp(32px, 4.6vw, 60px); line-height: 1.03;
  letter-spacing: -.03em; color: var(--ink);
}

/* two columns of body copy, not one long measure: the brochure paragraphs run
   long and a single 46ch column made the page open with a wall of text */
.ab-hero__cols {
  margin-top: clamp(22px, 2.6vw, 34px);
  display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: clamp(20px, 2.6vw, 44px);
}

.ab-hero__cols p { font-size: clamp(14.5px, 1.15vw, 16px); line-height: 1.72; color: var(--body); }

.ab-val__head { text-align: center; max-width: 40rem; margin: 0 auto clamp(30px, 3.4vw, 46px); }

.ab-val__title {
  margin-top: 9px;
  font-size: clamp(28px, 3.2vw, 44px); letter-spacing: -.025em; color: var(--ink);
}

.ab-val__title i { font-style: normal; color: var(--brand-400); margin-inline: .12em; }

.ab-val__lede { margin-top: 14px; color: var(--body); font-size: clamp(14.5px, 1.15vw, 16px); }

.ab-val__grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: clamp(18px, 2.2vw, 30px);
}

.val {
  background: #fff; border-radius: var(--radius-lg);
  padding: clamp(22px, 2.4vw, 32px);
  border: 1px solid rgba(255, 255, 255, .9);
  transition: transform .35s var(--ease), box-shadow .35s var(--ease);
}

.val:hover { transform: translateY(-5px); box-shadow: 0 18px 38px rgba(64, 56, 212, .13); }

.val__ico {
  display: grid; place-items: center;
  width: 46px; height: 46px; border-radius: 13px;
  background: rgba(64, 56, 212, .09); color: var(--brand);
  border: 1px solid rgba(64, 56, 212, .14);
}

.val__ico svg { width: 23px; height: 23px; }

.val__name {
  margin-top: 16px;
  font-size: clamp(18px, 1.5vw, 22px); letter-spacing: -.015em; color: var(--ink);
}

.val__desc { margin-top: 8px; font-size: 14px; line-height: 1.66; color: var(--body); }

.fac__head { max-width: 46rem; margin-bottom: clamp(26px, 3vw, 40px); }

.fac__title {
  margin-top: 9px;
  font-size: clamp(28px, 3.2vw, 44px); line-height: 1.06;
  letter-spacing: -.025em; color: var(--ink);
}

.fac__lede { margin-top: 14px; max-width: 52ch; color: var(--body); font-size: clamp(14.5px, 1.15vw, 16px); }

/* first shot takes two columns — these are the only three photographs of the
   workshop that exist, and an even 3-up made them read as stock filler */
.fac__shots {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: clamp(12px, 1.4vw, 20px);
}

.fac__shot { border-radius: var(--radius-lg); overflow: hidden; background: var(--bg-soft); }

.fac__shot:first-child { grid-column: span 2; }

.fac__shot img {
  width: 100%; height: 100%; display: block;
  aspect-ratio: 4 / 3; object-fit: cover;
  transition: transform .7s var(--ease);
}

.fac__shot:hover img { transform: scale(1.04); }

.fac__methods { margin-top: clamp(20px, 2.4vw, 32px); }

.fac__methods .chips__lead { margin-top: 0; }

@media (max-width: 860px) {
  .fac__shots { grid-template-columns: repeat(2, 1fr); }
  .fac__shot:first-child { grid-column: span 2; }
}

@media (max-width: 560px) {
  .fac__shots { grid-template-columns: 1fr; }
  .fac__shot:first-child { grid-column: auto; }
}

/* ---------- Insights page ---------------------------------------------------
   The card, the grid and the filter are all the homepage insights block's, so
   an article looks and behaves the same in both places. Only the page frame
   is new.
   -------------------------------------------------------------------------- */
.in-hero__title {
  margin-top: 10px;
  font-size: clamp(32px, 4.6vw, 58px); line-height: 1.03;
  letter-spacing: -.03em; color: var(--ink);
}

.in-hero__lede { margin-top: 16px; max-width: 52ch; color: var(--body); font-size: clamp(15px, 1.2vw, 17px); }

.in-sec .pills { margin-bottom: clamp(22px, 2.6vw, 34px); }

/* ---------- Contact page ----------------------------------------------------
   Almost nothing new here: .form-card / .form-grid / .field / .form-status were
   already in this sheet from JX's original contact page, so the form is reused
   as-is and only the page frame and the detail rail are added.
   -------------------------------------------------------------------------- */
/* 深色开场带。
   原来整页从头到尾是白底细描边，接在首页（满屏视频 hero + 紫色渐变带 +
   大号显示字）后面落差太大，读起来像另一个网站。这条带子把首页的开场语言
   接过来：同一条渐变（.cta 用的那条）、同样的白色大字。

   带子高度用 px 而不是 %：它要盖住的是 chrome(130) + 标题区，
   跟视口高度无关；用 % 的话屏幕越高带子越长，标题会浮在中间。 */
.cx { position: relative; isolation: isolate; padding-top: 0; }

.cx__band {
  position: absolute; inset: 0 0 auto 0; z-index: -1;
  height: clamp(430px, 46vw, 560px);
  background: linear-gradient(104deg, #4b18a8 0%, #7c3aed 46%, #9a63f2 100%);
  overflow: hidden;
  pointer-events: none;
}

/* 两块柔光，给纯渐变一点纵深。只用 radial-gradient，不引入图片。 */
.cx__band i {
  position: absolute; border-radius: 50%;
  background: radial-gradient(closest-side, rgba(255,255,255,.16), transparent);
}
.cx__band i:nth-child(1) { width: 46vw; height: 46vw; top: -16vw; right: -8vw; }
.cx__band i:nth-child(2) { width: 34vw; height: 34vw; bottom: -18vw; left: -6vw;
  background: radial-gradient(closest-side, rgba(20,4,48,.34), transparent); }

/* 带子下沿的收口，避免和下面白底硬碰硬切一条线 */
.cx__band::after {
  content: ""; position: absolute; inset: auto 0 0 0; height: 120px;
  background: linear-gradient(180deg, transparent, rgba(255,255,255,.10));
}

.cx__head {
  max-width: 44rem;
  padding-top: clamp(112px, 12vw, 150px);
  margin-bottom: clamp(34px, 3.6vw, 52px);
}

.cx__head .eyebrow { color: rgba(255,255,255,.72); }

.cx__title {
  margin-top: 10px;
  font-size: clamp(32px, 4.6vw, 58px); line-height: 1.03;
  letter-spacing: -.03em; color: #fff;
}

.cx__title .dot { color: var(--brand-300, #c4b0fb); }

.cx__lede {
  margin-top: 16px; max-width: 50ch;
  color: rgba(255,255,255,.80);
  font-size: clamp(15px, 1.2vw, 17px);
}

.cx__grid {
  display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  gap: clamp(24px, 3vw, 52px); align-items: start;
}

/* 右栏两块也做成卡片。原来是裸内容直接放在页面底色上，
   和左边那张大卡片一比几乎没有存在感，两栏严重失衡。 */
.cx__side > * + * { margin-top: 18px; }

.cx__list {
  margin: 0; padding: clamp(22px, 2.4vw, 30px);
  list-style: none; display: grid; gap: 20px;
  background: #fff;
  border-radius: 18px;
  box-shadow: 0 20px 52px rgba(30, 8, 78, .16), 0 4px 12px rgba(30, 8, 78, .07);
}

.cx__row { display: grid; grid-template-columns: auto 1fr; gap: 14px; align-items: start; }

.cx__ico {
  flex: none; width: 40px; height: 40px; border-radius: 12px;
  display: grid; place-items: center;
  background: rgba(64, 56, 212, .08); color: var(--brand);
  border: 1px solid rgba(64, 56, 212, .14);
}

.cx__ico svg { width: 19px; height: 19px; }

.cx__label {
  font-family: var(--font-disp); font-weight: 700;
  font-size: .68rem; letter-spacing: .16em; text-transform: uppercase;
  color: var(--brand-ink);
}

.cx__val { margin-top: 4px; font-size: 14.5px; line-height: 1.55; color: var(--ink-2); }
.cx__val a { border-bottom: 1px solid rgba(64, 56, 212, .28); transition: border-color .25s var(--ease), color .25s var(--ease); }
.cx__val a:hover { color: var(--brand-ink); border-bottom-color: var(--brand); }

.cx__steps {
  margin: 0; padding: clamp(22px, 2.4vw, 30px);
  list-style: none; display: grid; gap: 18px;
  background: var(--bg-tint); border-radius: 18px;
}

.cx__step { display: grid; grid-template-columns: auto 1fr; gap: 14px; align-items: start; }

/* 首页 .hiw__num 是大号淡色幽灵数字，这里沿用同一套语言 ——
   原来是 15px 的小字，在卡片里读起来像项目符号，不像编号。 */
.cx__step > span {
  font-family: var(--font-disp); font-weight: 800; font-size: 26px;
  color: var(--brand); opacity: .32; letter-spacing: -.03em;
  line-height: 1; padding-top: 2px; min-width: 34px;
}

.cx__step h3 { font-size: 15px; letter-spacing: -.01em; color: var(--ink); }
.cx__step p { margin-top: 3px; font-size: 13.5px; line-height: 1.55; color: var(--body); }

@media (max-width: 900px) {
  .cx__grid { grid-template-columns: 1fr; }
}

@media (max-width: 860px) {
  /* tap targets — the phone number, email and site link sat at 20px */
  .cx__val a { display: inline-block; padding-block: 9px; }
  .cx__row { align-items: center; }
}

/* ---------- Chips ------------------------------------------------------------
   One list component, two uses: the build methods in the studio block and the
   sub-services under each What We Do tab. Both are sets of labels rather than
   features, so they read as a group and must not compete with the heading.
   These first shipped in va-blocks.css using the Violet Axis violet (#7c3aed),
   which was wrong twice over: the blocks they sit in are JX's, painted in the
   indigo brand, and va-blocks.css is meant to hold our blocks only. */
.chips__lead {
  margin: 26px 0 12px;
  font-family: var(--font-disp);
  font-size: .74rem; font-weight: 700; letter-spacing: .16em;
  text-transform: uppercase; color: var(--muted);
}

.chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 26px; padding: 0; list-style: none; }

.chips li {
  padding: 8px 14px; border-radius: 999px;
  border: 1px solid rgba(64, 56, 212, .22);
  background: rgba(64, 56, 212, .05);
  font-size: .84rem; font-weight: 600; color: var(--brand-ink);
  white-space: nowrap;
  transition: background-color .28s var(--ease), border-color .28s var(--ease), transform .28s var(--ease);
}

.chips li:hover { background: rgba(64, 56, 212, .12); border-color: rgba(64, 56, 212, .42); transform: translateY(-2px); }

/* White fill inside the panel: the panel ground is --bg-soft, against which the
   default 5% brand wash reads as a smudge. On the studio block the chips keep
   the wash — there the ground is --bg-tint and the border carries them.
   No bottom margin either: the link below brings its own margin-top, and the
   two stacked left a ~44px hole under the chips. */
.svcx__copy .chips { margin: 16px 0 0; }
.svcx__copy .chips li { background: #fff; border-color: rgba(64, 56, 212, .18); }
.svcx__copy .chips li:hover { background: rgba(255, 255, 255, .6); border-color: rgba(64, 56, 212, .4); }

@media (max-width: 640px) { .chips li { font-size: .8rem; padding: 7px 12px; } }

/* ---------- Where we work (city list | blueprint map) -------
   The map is a generated SVG; the pins are HTML on top of it, positioned with
   percentages from the same projection that drew the coastline. Text stays in
   HTML so the labels use the page font rather than the SVG's fallback. */
/* Standard container width: running wide left this section's eyebrow and
   heading hanging out past every other block on the page. The extra map width
   is not worth breaking the page's left edge — dropping the support card is
   what recovers the vertical balance the wide container was buying. */
.nw.section--tight { padding-block: clamp(24px, 2.8vw, 40px) clamp(16px, 2vw, 28px); }
.nw__grid {
  display: grid;
  /* Back to 320: the country list no longer lives in this column — it sits
     under the whole grid, grouped by continent — so this side carries only the
     eyebrow, title and lede, and the map takes the width back. */
  grid-template-columns: minmax(0, 320px) minmax(0, 1fr);
  gap: clamp(28px, 3.2vw, 46px);
  align-items: center;
}
.nw__title {
  font-family: var(--font-disp); font-weight: 700;
  /* capped so "Exhibition Support" holds one line in a 320px column */
  font-size: clamp(25px, 2.5vw, 33px); line-height: 1.12;
  letter-spacing: -.02em; color: var(--ink); margin-top: 9px;
}
.nw__rule { display: block; width: 44px; height: 2px; background: var(--brand-400); margin: 14px 0 13px; }
.nw__lede { font-size: 13.5px; line-height: 1.6; }

/* ---- Countries, grouped by continent -------------------------------------
   Three columns under the map. Only the region heading carries a rule and the
   rows are separated by space — thirteen hairlines stacked across three columns
   would be the same "too many lines" problem the service rail had. */
.nw__regions {
  margin-top: clamp(30px, 3.6vw, 52px);
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(22px, 3vw, 54px);
}

.nw__regionName {
  font-family: var(--font-disp); font-weight: 700;
  font-size: .74rem; letter-spacing: .17em; text-transform: uppercase;
  color: var(--brand-ink);
  padding-bottom: 11px; margin-bottom: 6px;
  border-bottom: 1px solid var(--line);
}

.nw__cities { margin: 0; padding: 0; list-style: none; }
.nw__city {
  display: flex; align-items: center; gap: 11px;
  padding: 7px 6px; border-radius: 7px;
  font-size: 14px; color: var(--ink-2); cursor: default;
  transition: color .25s var(--ease), background .25s var(--ease), transform .25s var(--ease);
}
/* 4x3 flags at 21x16, with a hairline so the white-edged ones (Japan, Korea,
   Canada) don't dissolve into the page */
.nw__flag {
  flex: none; width: 21px; height: 16px; border-radius: 2px;
  object-fit: cover;
  box-shadow: 0 0 0 1px rgba(15, 16, 22, .1);
  transition: transform .25s var(--ease);
}

/* Country only — the hub city lives in the hover chip on the map. It read as a
   stutter on the rows where country and city are the same word (Singapore,
   Hong Kong), and the chip carries it for the rest. */
.nw__city span { white-space: nowrap; }
.nw__city.is-hot { color: var(--brand-ink); background: rgba(64, 56, 212, .06); transform: translateX(3px); }
.nw__city.is-hot .nw__flag { transform: scale(1.12); }

/* Margin, NOT padding. The pins are positioned in percentages of this box, so
   any padding here would make the box wider than the image and shift every pin
   right by that fraction — which is exactly how they first drifted offshore.
   A margin shrinks the box instead, keeping it identical to the image while
   still leaving room for New York's label, the rightmost, to clear the edge. */
.nw__map { position: relative; margin-right: 18px; }
.nw__map > img {
  display: block; width: 100%; height: auto;
  /* the world map is an SVG with a transparent ground and its land colour baked
     in, so the multiply blend the old JPEG needed is gone */
}

/* Dots, not teardrops. At world scale London, Paris, Frankfurt and Milan land
   within 14px of one another, so a 24px pin would bury its neighbours and the
   labels overlapped outright. The name rides in a chip that appears on hover —
   which is also what disambiguates the European cluster, since you cannot tell
   which of two touching dots is the one that grew. */
.nw__pin {
  position: absolute; z-index: 3;
  transform: translate(-50%, -50%);
  pointer-events: auto; cursor: default;
}

.nw__pin > i {
  display: block; width: 9px; height: 9px; border-radius: 50%;
  background: var(--brand);
  box-shadow: 0 0 0 2px #fff, 0 2px 5px rgba(64,56,212,.45);
  transition: transform .28s var(--ease), box-shadow .28s var(--ease);
}

.nw__pin.is-hot { z-index: 5; }

.nw__pin.is-hot > i {
  transform: scale(1.5);
  box-shadow: 0 0 0 2px #fff, 0 0 0 7px rgba(64,56,212,.18), 0 4px 10px rgba(64,56,212,.5);
}

.nw__pin > b {
  position: absolute; left: 50%; bottom: calc(100% + 9px);
  transform: translate(-50%, 4px);
  /* Flex, not inline flow. Absolutely positioned inline content shrink-wrapped
     8px narrower than it measured, so the last of the city name sat outside the
     dark plate — visible on every one of the thirteen. A flex row sizes to its
     items and their gap, and the flag no longer needs a margin of its own. */
  display: flex; align-items: center; gap: 7px;
  padding: 5px 11px; border-radius: 7px;
  background: var(--ink); color: #fff;
  font-family: var(--font-disp); font-weight: 600; font-size: 11.5px;
  white-space: nowrap; opacity: 0; visibility: hidden;
  /* the chip is a label, not a target: without this it steals the hover from
     whichever neighbouring dot it happens to cover — eight pairs do overlap */
  pointer-events: none;
  transition: opacity .22s var(--ease), transform .22s var(--ease), visibility .22s;
}

.nw__pin > b em { font-style: normal; font-weight: 400; opacity: .62; }

/* the chip is the only place the dot can say which country it is, so it
   carries the same flag the list below does */
.nw__chipflag {
  flex: none; display: block; width: 18px; height: 13px;
  border-radius: 2px; object-fit: cover;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, .25);
}

.nw__pin > b::after {
  content: ''; position: absolute; left: 50%; top: 100%;
  width: 7px; height: 7px; margin: -4px 0 0 -3.5px;
  background: inherit; transform: rotate(45deg);
}

.nw__pin.is-hot > b { opacity: 1; visibility: visible; transform: translate(-50%, 0); }

@media (max-width: 1080px) {
  .nw__grid { grid-template-columns: 1fr; }
  .nw__map { margin-right: 0; order: -1; }
}

@media (max-width: 720px) {
  .nw__regions { grid-template-columns: 1fr; gap: 20px; }
}
@media (max-width: 700px) {
  .nw__pin > i { width: 7px; height: 7px; }
}

/* ---------- Full-bleed footer + CTA (home) ----------------- */
.footer-cta { position: relative; overflow: hidden; background: var(--grad-cta); color: #fff; }
.footer-cta::before { content:""; position:absolute; right:-4%; top:-46%; width: 560px; height: 560px; border-radius:50%; background: radial-gradient(circle, rgba(255,255,255,.12), transparent 62%); pointer-events:none; }
/* two columns now — the CTA sits under its own copy rather than off to the
   side, so the ask reads in one continuous line of thought */
.footer-cta__main { position: relative; display: grid; grid-template-columns: 1fr auto; align-items: center; gap: clamp(20px,3vw,52px); padding-block: clamp(46px,6vw,86px); }
.footer-cta__copy .btn { margin-top: clamp(24px, 2.8vw, 34px); }
.footer-cta__copy h2 { font-family: var(--font-disp); font-weight: 700; color: #fff; font-size: clamp(26px,3vw,40px); line-height: 1.1; letter-spacing: -.02em; }
.footer-cta__copy p { color: rgba(255,255,255,.82); margin-top: 12px; font-size: 15px; max-width: 40ch; }
.footer-cta__logo { justify-self: end; width: clamp(130px,15vw,210px); height: auto; filter: drop-shadow(0 0 26px rgba(150,120,255,.55)); }
.footer-cta__logo-img { justify-self: end; width: clamp(140px,16vw,220px); height: auto; opacity: .92; filter: brightness(0) invert(1) drop-shadow(0 0 18px rgba(255,255,255,.28)); }
.footer-cta__logo .logo-shield, .footer-cta__logo .logo-letter { stroke: rgba(255,255,255,.92); }
.footer-cta__bar { position: relative; border-top: 1px solid rgba(255,255,255,.16); background: rgba(6,6,20,.26); }
.footer-cta__bar .container { display: flex; align-items: center; justify-content: space-between; gap: 20px; flex-wrap: wrap; padding-block: 18px; }
.footer-cta__bar .footer-left { color: rgba(255,255,255,.72); font-size: 13px; }
.footer-cta__bar .footer-links a { color: rgba(255,255,255,.78); }
.footer-cta__bar .footer-links a:hover { color: #fff; }
.footer-cta__bar .footer-social a { background: rgba(255,255,255,.12); color: #fff; }
.footer-cta__bar .footer-social a:hover { background: #fff; color: var(--brand); }
@media (max-width: 900px) {
  .hero-media { height: 300px; }
  .footer-cta__main { grid-template-columns: 1fr; }
  .footer-cta__logo { display: none; }
}

/* ---------- Page utilities --------------------------------- */
.split--aside { grid-template-columns: .82fr 2.5fr; align-items: center; }
.split--reverse .split__copy { order: 2; }
.collage--inset { position: relative; overflow: visible; }
.collage--inset img { border-radius: var(--radius); }
/* `.collage img` (0,1,1) would otherwise beat a bare `.collage__inset` (0,1,0)
   and stretch the inset to 100% — keep the selector at least as specific. */
.collage img.collage__inset { position: absolute; left: 20px; bottom: -26px; width: 42%; height: auto; border-radius: 12px; box-shadow: var(--shadow-lg); border: 5px solid #fff; aspect-ratio: 16/10; object-fit: cover; }
.stack-sm > * + * { margin-top: 8px; }
.two-para p + p { margin-top: 14px; }
.quote-band { background: var(--bg-soft); border-radius: var(--radius-lg); padding: clamp(30px,4vw,56px); display: grid; grid-template-columns: 1.15fr 1fr; gap: clamp(28px,4vw,56px); align-items: center; }
.quote-band .quote-lg { font-size: clamp(20px,2.2vw,27px); }
.quote-band__aside { border-left: 1px solid var(--line); padding-left: clamp(24px,3vw,44px); color: var(--body); font-size: 15px; }

@keyframes pulse { 0%,100% { box-shadow: 0 0 0 0 rgba(64,56,212,.28); } 50% { box-shadow: 0 0 0 9px rgba(64,56,212,0); } }
@keyframes float { 0%,100% { transform: translateY(-8px) rotate(-1deg); } 50% { transform: translateY(8px) rotate(1deg); } }
@keyframes neonPulse { 0%,100% { opacity: .85; } 50% { opacity: 1; } }

/* ---------- Responsive ------------------------------------- */
@media (max-width: 1080px) {
  .feature-cols { grid-template-columns: 1fr 1fr; row-gap: 30px; }
  .svc-strip { grid-template-columns: repeat(3, 1fr); }
  .svc-strip .tile:nth-child(4), .svc-strip .tile:nth-child(5) { grid-column: auto; }
  .industry-grid { grid-template-columns: repeat(3,1fr); }
  .work-strip { grid-template-columns: repeat(3,1fr); }
  .where-cols { grid-template-columns: repeat(3,1fr); row-gap: 26px; }
  .steps { grid-template-columns: repeat(2,1fr); row-gap: 26px; }
  .showenv-grid { grid-template-columns: repeat(2,1fr); }
  .support-cols { grid-template-columns: repeat(3, 1fr); row-gap: 26px; }
}
@media (max-width: 900px) {
  .hero-inner { grid-template-columns: 1fr; }
  .hero-media { order: -1; aspect-ratio: 16/11; }
  .story-grid, .split, .split--media-pair, .fp, .process, .testi, .stats-card, .contact-grid, .dark-contact, .hero-dark-inner { grid-template-columns: 1fr; }
  .grid--3, .grid--4 { grid-template-columns: repeat(2, 1fr); }
  .feat-grid { grid-template-columns: 1fr 1fr; grid-auto-rows: 200px; }
  .feat-grid .tile:nth-child(1), .feat-grid .tile:nth-child(2) { grid-row: auto; min-height: 0; grid-column: span 2; height: 300px; }
  .cta-band { grid-template-columns: 1fr; text-align: left; }
  .cta-band__logo { display: none; }
  .cta-band .btn { justify-self: start; }
  .stats-grid { margin-top: 8px; }
  .exp-collage { grid-template-columns: 1fr 1fr; }
  .neon-logo { max-width: 340px; margin-top: 20px; }
}
@media (max-width: 720px) {
  .nav, .header-cta .btn { display: none; }
  .nav-toggle { display: inline-flex; }
  .nav.is-drawer {
    display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
    position: fixed; top: 0; right: 0; bottom: 0; width: min(84vw, 340px); z-index: 58;
    background: #fff; padding: 96px 30px 40px; box-shadow: var(--shadow-lg);
    transform: translateX(100%); transition: transform .4s var(--ease);
  }
  body.nav-open .nav.is-drawer { transform: none; }
  .nav.is-drawer a { font-size: 17px; padding: 12px 0; width: 100%; border-bottom: 1px solid var(--line-soft); }
  .nav.is-drawer a::after { display:none; }
  .site-header--dark .nav.is-drawer { background: var(--dk-card); }
  .site-header--dark .nav.is-drawer a { color: rgba(255,255,255,.9); border-color: var(--dk-line); }
  .grid--3, .grid--4, .svc-strip, .industry-grid, .work-strip, .where-cols, .steps, .showenv-grid, .support-cols, .form-grid, .feature-cols { grid-template-columns: 1fr; }
  .feat-grid { grid-template-columns: 1fr; }
  .feat-grid .tile, .feat-grid .tile:nth-child(1), .feat-grid .tile:nth-child(2) { grid-column: auto; height: 260px; min-height: 0; }
  .stats-grid { grid-template-columns: 1fr; }
  .subscribe { flex-direction: column; }
  .subscribe .btn { justify-content: center; }
  .exp-collage { grid-template-columns: 1fr; }
  .support-cols { grid-template-columns: 1fr; }
  .fp .media, .hero-title { }
  .footer-links { gap: 16px; }
  .spec__row { grid-template-columns: 100px 1fr; }
}
@media (max-width: 900px) {
  /* narrow crops lose the light left half, so veil the whole frame and let the
     artwork sit behind the copy as texture rather than competing with it */
  .story-band { min-height: 0; padding-block: clamp(56px, 11vw, 88px); }
  .story-band__bg img { object-position: 64% center; }
  .story-band::after {
    background: linear-gradient(180deg, rgba(255,255,255,.94) 0%, rgba(255,255,255,.9) 50%, rgba(255,255,255,.96) 100%);
  }
  .story-band__copy { max-width: none; }
  .split--aside, .quote-band { grid-template-columns: 1fr; }
  .quote-band__aside { border-left: 0; padding-left: 0; border-top: 1px solid var(--line); padding-top: 20px; }
  .collage img.collage__inset { position: static; width: 60%; height: auto; margin-top: 14px; border-width: 0; box-shadow: var(--shadow-md); }
}


/* ---------- 联系页 · 窄屏 ---------------------------------- */
@media (max-width: 900px) {
  .cx__grid { grid-template-columns: 1fr; }
}

@media (max-width: 640px) {
  /* 带子按内容收：窄屏标题换行更多，但整体尺度小，430 会盖到表单上 */
  .cx__band { height: clamp(360px, 78vw, 430px); }
  .cx__head { padding-top: clamp(96px, 26vw, 124px); }
  .cx .form-card { border-radius: 15px; }
  .cx__list, .cx__steps { border-radius: 15px; }
}

/* ---------- 粘性页脚 -------------------------------------------
   内容短的页面（404、只有一个区块的页面）以前页脚会停在内容末尾，
   下面留一大片白。把 body 做成纵向 flex，让 <main> 吃掉剩余高度。
   只影响短页面，长页面行为不变。 */
html { height: 100%; }

body {
  min-height: 100%;
  display: flex;
  flex-direction: column;
}

body > main { flex: 1 0 auto; }
body > .footer { flex: none; }

/* ---------- 表单卡片底部 ---------------------------------------
   .form-status 是给提交结果用的 <p>，平时是空的但仍占着
   margin-top:14px + 一行行高 —— 加上卡片自己的 padding，
   按钮下面就空出一大块。空的时候直接不占位。 */
.form-status:empty { display: none; }
.form-note:empty { display: none; }

/* ---------- 404 ----------------------------------------------- */
.nf__inner { max-width: 42rem; padding-block: clamp(30px, 6vw, 70px); }

.nf__title {
  margin-top: 10px;
  font-size: clamp(30px, 4.2vw, 52px); line-height: 1.05;
  letter-spacing: -.03em; color: var(--ink);
}

.nf__lede { margin-top: 16px; color: var(--body); font-size: clamp(15px, 1.2vw, 17px); }

.nf__links { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 28px; }

.nf__list {
  margin: 34px 0 0; padding: 22px 0 0;
  list-style: none; display: flex; flex-wrap: wrap; gap: 8px 26px;
  border-top: 1px solid var(--line);
}

.nf__list a {
  font-family: var(--font-disp); font-weight: 600; font-size: 13px;
  letter-spacing: .08em; text-transform: uppercase; color: var(--brand-ink);
}

/* ---------- 询价表单：文件上传 -------------------------------
   原生 file input 的按钮各浏览器长得都不一样，也没法完全控。
   ::file-selector-button 是标准里唯一能可靠改的部分，把它做成
   和站内次级按钮同一套语言，剩下的文件名交给浏览器渲染。 */
.field--file input[type="file"] {
  width: 100%;
  padding: 10px 12px;
  font-size: 14px;
  color: var(--body);
  background: #fbfaff;
  border: 1px solid #e4e0f2;
  border-radius: var(--radius-sm, 10px);
  cursor: pointer;
  transition: border-color .22s var(--ease), background-color .22s var(--ease);
}

.field--file input[type="file"]:hover { border-color: #cfc7ea; }

.field--file input[type="file"]:focus {
  outline: none;
  background: #fff;
  border-color: var(--brand);
  box-shadow: 0 0 0 4px rgba(124, 58, 237, .12);
}

.field--file input[type="file"]::file-selector-button {
  margin-right: 12px;
  padding: 8px 16px;
  font-family: var(--font-disp); font-weight: 600;
  font-size: 12px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--brand-ink);
  background: #efecfb;
  border: 0; border-radius: 999px;
  cursor: pointer;
  transition: background-color .22s var(--ease), color .22s var(--ease);
}

.field--file input[type="file"]::file-selector-button:hover {
  background: var(--brand); color: #fff;
}

.field__hint { margin-top: 7px; font-size: 12.5px; line-height: 1.5; color: var(--muted); }

/* 蜜罐。不能用 display:none —— 有些机器人会跳过隐藏字段，
   而部分屏幕阅读器也会。挪出视口 + aria-hidden 才是既骗到机器人
   又不影响无障碍的做法。 */
.hp {
  position: absolute !important;
  left: -9999px; top: auto;
  width: 1px; height: 1px; overflow: hidden;
}

/* ---------- 长文页（法务 / 政策）---------------------------
   正文是客户在编辑器里敲的自由富文本，所以样式全挂后代选择器 ——
   他们不需要知道任何 class 名。 */
.legal__head { max-width: 46rem; margin-bottom: clamp(26px, 3vw, 40px); }

.legal__title {
  margin-top: 10px;
  font-size: clamp(30px, 4vw, 50px); line-height: 1.05;
  letter-spacing: -.03em; color: var(--ink);
}

.legal__meta { margin-top: 14px; font-size: 14px; color: var(--muted); }

.legal__body {
  max-width: 46rem;
  color: var(--body);
  font-size: clamp(15px, 1.15vw, 16.5px);
  line-height: 1.72;
}

.legal__body > * + * { margin-top: 1.05em; }

.legal__body h2 {
  margin-top: 2.2em;
  font-size: clamp(20px, 1.8vw, 25px);
  letter-spacing: -.02em; color: var(--ink);
}

.legal__body h3 {
  margin-top: 1.7em;
  font-size: clamp(16px, 1.3vw, 18px);
  letter-spacing: -.01em; color: var(--ink);
}

.legal__body ul, .legal__body ol { padding-left: 1.25em; }
.legal__body li + li { margin-top: .5em; }
.legal__body li::marker { color: var(--brand); }

.legal__body a {
  color: var(--brand-ink);
  border-bottom: 1px solid rgba(64, 56, 212, .28);
  transition: border-color .25s var(--ease);
}
.legal__body a:hover { border-bottom-color: var(--brand); }

.legal__body strong { color: var(--ink); }

/* 网站地图用的分栏列表 */
.legal__body .sitemap-cols {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 18px 32px; padding-left: 0; list-style: none;
}
.legal__body .sitemap-cols > li { margin-top: 0; }
