
/* The one base-layer rule worth carrying over: our header is fixed at 88px,
   so anchor jumps need that much padding or the section title lands under it.
   Everything else from the Violet Axis reset is deliberately left behind. */
html {
  /* literal, not var(--header-h): that token is scoped to the Violet Axis
     blocks so html can't read it — a var() here would silently use its
     fallback and stop tracking the real header height. Keep the two in sync. */
  /* The utility bar no longer rolls away on scroll, so an anchor has to clear
     BOTH bars, not just the header. Was 104px (header + air) back when the bar
     hid itself — section titles landed under it after that changed. */
  scroll-padding-top: 146px;   /* 42px topbar + 88px header + 16px air */
}
@media (max-width: 860px) {
  html { scroll-padding-top: 120px; }   /* 42 + 66 + 12 */   /* 66px header + 12px */
}
/* ==========================================================================

   Violet Axis blocks — header, hero, closing CTA, footer, small-screen gate

   Loaded after the JX sheet. Tokens are scoped to these blocks rather than

   :root, and the base/reset is deliberately absent, so nothing here reaches

   the JX sections in between.

   ========================================================================== */

.topbar,
.header,
.drawer,
.va-hero,
.cta,
.footer,
.va-wall {
  /* Brand violet ramp — sampled from the approved comp */
  --v-50:  #f6f3ff;
  --v-100: #ede7fe;
  --v-200: #ddd2fd;
  --v-300: #c4b0fb;
  --v-400: #a583f6;
  --v-500: #8b5cf6;
  --v-600: #7c3aed;   /* primary */
  --v-700: #6929d4;
  --v-800: #571faf;
  --v-900: #3f1580;

  /* Neutrals */
  --ink:      #0e0f1e;   /* headings */
  --ink-soft: #2a2b3d;
  --body:     #6b7280;   /* paragraph copy */
  --muted:    #9aa0ae;
  --line:     #e9e6f2;
  --line-soft:#f1eff8;
  --white:    #ffffff;
  --shell:    #faf9fe;

  /* Signature gradient used on the stat + CTA bands */
  --grad-band: linear-gradient(101deg, #5f21c9 0%, #7c3aed 44%, #a96ff3 100%);
  /* Deep enough to stay legible as a card when it sits on the tinted ground */
  --grad-soft: linear-gradient(135deg, #f3eeff 0%, #e8e0fd 100%);

  /* Type families */
  /* Chivo — a sturdy grotesque with a true 900. Sora's 700 read too light at
     display sizes; Chivo carries real stroke mass while keeping a full weight
     range, so one family covers the hero down to card titles. */
  --f-display: "Chivo", "Archivo", "Helvetica Neue", system-ui, sans-serif;
  --f-body:    "Manrope", "Helvetica Neue", -apple-system, system-ui, sans-serif;

  /* Type scale. Display sizes are tuned to Chivo's width and mass; every
     reading size sits 1px above the earlier set. */
  --t-hero: clamp(2.4rem, 6.55vw, 5.9rem);   /* full-bleed banner headline */
  --t-h1:   clamp(2.3rem, 4.35vw, 3.8rem);
  --t-h2:   clamp(1.44rem, 1.95vw, 1.8rem);
  --t-h3:   1.11rem;
  --t-cta:  clamp(1.44rem, 2.1vw, 1.92rem);
  --t-quote:clamp(1.12rem, 1.5vw, 1.38rem);
  --t-stat: clamp(1.5rem, 1.95vw, 1.85rem);
  --t-body: 1.0625rem;   /* 17px */
  --t-sm:   0.9375rem;   /* 15px */
  --t-xs:   0.875rem;    /* 14px */
  --t-eyebrow: 0.8125rem;/* 13px */
  --t-btn:  0.875rem;    /* 14px */

  /* Container widths. max-width includes the gutter, so the *content box*
     lands on 1328 / 1408 / 1472 — wider than the comp's 1200, which left the
     page feeling boxed-in on modern displays.
     The 32px gutter is what keeps content off the viewport edge once the
     viewport is narrower than the container itself. */
  --w-content: 1392px;   /* body copy, cards, footer  */
  --w-band:    1472px;   /* stats + closing CTA bands, header */
  --w-wall:    1536px;   /* client logo card          */
  /* 曾经是死值 32px。手机上这就是 390 屏宽里去掉 16% —— 而 JX 那半边的
     styles.css 用的是 clamp(20px, 4vw, 48px)，窄屏落到 20px。两套 gutter
     不一致的后果是页眉胶囊比它下面的卡片还窄，看着像没对齐。
     ≥800px 时 4vw 已经 ≥32，桌面端取值和以前完全一样。 */
  --gutter:    clamp(20px, 4vw, 32px);

  /* Rhythm.
     The comp stacks everything at ~26px, which reads flat. Zones get real
     breathing room instead, and alternating grounds carry the hierarchy. */
  --zone-y:      68px;
  --zone-y-sm:   54px;
  --head-gap:    28px;

  /* Radii */
  --r-btn:  4px;
  --r-card: 7px;
  --r-band: 11px;

  /* Elevation */
  --sh-wall:  0 14px 44px rgba(63, 21, 128, 0.10), 0 2px 6px rgba(63, 21, 128, 0.04);
  --sh-card:  0 1px 2px rgba(14, 15, 30, 0.04), 0 6px 18px rgba(63, 21, 128, 0.05);
  --sh-lift:  0 20px 46px rgba(63, 21, 128, 0.14);
  --sh-btn:   0 6px 18px rgba(105, 41, 212, 0.26);
  --sh-band:  0 18px 44px rgba(75, 26, 158, 0.22), 0 2px 8px rgba(75, 26, 158, 0.10);
  --sh-quote: 0 10px 30px rgba(63, 21, 128, 0.07);

  /* Motion */
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --ease-out-back: cubic-bezier(0.34, 1.32, 0.64, 1);
  --dur: 0.55s;

  --header-h: 88px;
  --topbar-h: 42px;
}

.skip {
  position: absolute;
  left: 16px;
  top: -60px;
  z-index: 200;
  padding: 10px 18px;
  background: var(--v-600);
  color: #fff;
  font: 700 var(--t-btn)/1 var(--f-display);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border-radius: var(--r-btn);
  transition: top 0.25s var(--ease);
}

.skip:focus { top: 14px; }

.shell    { width: 100%; max-width: var(--w-content); margin-inline: auto; padding-inline: var(--gutter); }

.shell-band { width: 100%; max-width: var(--w-band); margin-inline: auto; padding-inline: var(--gutter); }

.shell-wall { width: 100%; max-width: var(--w-wall); margin-inline: auto; padding-inline: var(--gutter); }

@keyframes ruleIn { to { transform: scaleX(1); } }

.va-btn {
  --btn-bg: var(--v-600);
  --btn-fg: #fff;
  --btn-bd: var(--v-600);
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 14px 24px;
  font-family: var(--f-display);
  font-size: var(--t-btn);
  font-weight: 700;
  letter-spacing: 0.095em;
  text-transform: uppercase;
  line-height: 1;
  color: var(--btn-fg);
  background: var(--btn-bg);
  border: 1px solid var(--btn-bd);
  border-radius: var(--r-btn);
  overflow: hidden;
  isolation: isolate;
  transition: transform 0.3s var(--ease), box-shadow 0.3s var(--ease),
              background-color 0.3s var(--ease), color 0.3s var(--ease),
              border-color 0.3s var(--ease);
}

.va-btn::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(100deg, transparent 20%, rgba(255, 255, 255, 0.26) 50%, transparent 80%);
  transform: translateX(-110%);
  transition: transform 0.62s var(--ease);
}

.va-btn:hover::after { transform: translateX(110%); }

.va-btn:hover { transform: translateY(-2px); box-shadow: var(--sh-btn); }

.va-btn:active { transform: translateY(0); }

.va-btn .va-ico { flex: none; transition: transform 0.32s var(--ease); }

.va-btn:hover .va-ico { transform: translateX(4px); }

.va-btn--primary { --btn-bg: var(--v-600); --btn-bd: var(--v-600); }

.va-btn--primary:hover { --btn-bg: var(--v-700); --btn-bd: var(--v-700); }

.va-btn--ghost {
  --btn-bg: transparent;
  --btn-fg: var(--ink);
  --btn-bd: #d8d4e6;
}

.va-btn--ghost:hover { --btn-bg: var(--ink); --btn-fg: #fff; --btn-bd: var(--ink); box-shadow: 0 6px 18px rgba(14, 15, 30, 0.2); }

/* 9% 白底压在 hero 亮部上几乎看不出，整个按钮读起来像禁用了 —— 提到 16%，
   描边 0.34 → 0.5。同时去掉 backdrop-filter：它每帧重算模糊，而这里衬底
   本来就是一张已经压过暗的照片，模糊了也看不出差别，纯亏帧。 */
.va-btn--glass {
  --btn-bg: rgba(255, 255, 255, 0.16);
  --btn-fg: #fff;
  --btn-bd: rgba(255, 255, 255, 0.5);
}

.va-btn--glass:hover { --btn-bg: #fff; --btn-fg: var(--ink); --btn-bd: #fff; }

.va-btn--white {
  --btn-bg: #fff;
  --btn-fg: var(--v-700);
  --btn-bd: #fff;
}

.va-btn--white:hover { --btn-fg: var(--v-800); box-shadow: 0 10px 26px rgba(38, 8, 84, 0.28); }

.va-btn--sm { padding: 12px 20px; font-size: 0.8rem; }

.va-ico { width: 1em; height: 1em; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }

.ico--arrow { width: 15px; height: 11px; stroke-width: 1.8; }

/* "Let's Talk" carried a generic arrow, which reads as "go somewhere" — the
   label is an invitation to a conversation, so it gets a speech bubble. The
   three dots inside are the typing indicator everyone already knows from a
   chat app: it says a person answers, not a form. */
.ico--chat { width: 16px; height: 16px; }

.va-btn:hover .ico--chat { transform: translateY(-1px) scale(1.08); }

.ico--chat path:last-child { stroke-linecap: round; opacity: 0.9; }

@media (prefers-reduced-motion: reduce) {
  .va-btn:hover .ico--chat { transform: none; }
}

/* 客户字标（UNIXPO，UNI + XPO 双色）。两份 PNG 叠放交叉淡入 ——
   UNI 在深底上是白、在浅底上是墨，一张图做不到，而 filter 会一起改掉 XPO 的紫。
   两张图各 17KB，永久缓存，切换只动 opacity（走合成器，和页眉胶囊变形同频）。 */
/* 字标比例恒为 5.703:1（1477/259），改高度即可，宽度按比例给足，
   免得 object-fit: contain 在盒子里留出上下留白 */
.va-brand {
  position: relative;
  display: block;
  flex: none;
  width: 183px;
  height: 32px;
}

.va-brand__lg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: left center;
  transition: opacity 0.34s var(--ease);
}

/* 默认浅底（内页、页眉固实态）；深底场景再翻过来 */
.va-brand__lg--dark  { opacity: 0; }
.va-brand__lg--light { opacity: 1; }

.header:not(.is-stuck) .va-brand__lg--dark,
.footer .va-brand__lg--dark   { opacity: 1; }
.header:not(.is-stuck) .va-brand__lg--light,
.footer .va-brand__lg--light  { opacity: 0; }

.va-brand--lg { width: 234px; height: 41px; }

.topbar {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 101;
  height: var(--topbar-h);
  display: flex;
  align-items: center;
  /* Opaque-ish and unblurred, deliberately. This bar is fixed at the very top,
     so for the whole first screen its backdrop is the playing hero video: a
     translucent bar re-blurring every video frame both costs a full-width
     backdrop re-raster per frame AND lets the video's own brightness swings
     show through — which is the flicker that showed up while scrolling.
     0.88 keeps a hint of the video behind it without either problem. */
  background: rgba(9, 3, 18, 0.88);
  border-bottom: 1px solid rgba(255, 255, 255, 0.09);
  /* transform + opacity only: both composite, so dismissing the bar costs no
     layout or paint. It is back because the bar can be dismissed again — this
     time by the visitor, not by scroll position. */
  transition: transform 0.4s var(--ease), opacity 0.3s var(--ease);
}

/* The dismiss control. Hidden while the hero is still on screen — up there the
   bar is part of the opening composition and there is nothing to get out of the
   way of. It fades in once the page has scrolled past it. */
.topbar__close {
  flex: none;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  margin-left: -8px;   /* 抵掉一截 inner 的 24px gap，让 × 贴住邮箱 */
  border-radius: 50%;
  color: rgba(255, 255, 255, 0.5);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.3s var(--ease), visibility 0.3s,
              color 0.24s var(--ease), background-color 0.24s var(--ease);
}

.topbar.is-scrolled .topbar__close { opacity: 1; visibility: visible; }

.topbar__close:hover { color: #fff; background: rgba(255, 255, 255, 0.14); }

.topbar__close .va-ico { width: 13px; height: 13px; }

/* Dismissed: rolls up out of the way and the header slides into its place. */
.topbar.is-hidden { transform: translateY(-100%); opacity: 0; pointer-events: none; }

.header.is-top-gone { transform: translateY(0); }

/* Anchor clearance follows the bar. Without this a jump to #process lands 42px
   too low once the bar has been dismissed. */
html:has(.topbar.is-hidden) { scroll-padding-top: 104px; }

@media (max-width: 860px) {
  html:has(.topbar.is-hidden) { scroll-padding-top: 78px; }
}

.topbar__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  width: 100%;
}

.topbar__note {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  font-size: 0.78rem;
  color: rgba(255, 255, 255, 0.74);
  white-space: nowrap;
}

.topbar__note i {
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #4ade80;
  animation: liveFade 2.6s var(--ease) infinite;
}

@keyframes liveFade {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.45; }
}

.topbar__note b {
  font-family: var(--f-display);
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #fff;
}

.topbar__note span { overflow: hidden; text-overflow: ellipsis; }

.topbar__note em {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-style: normal;
  font-family: var(--f-display);
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--v-400);
  padding-left: 4px;
  transition: color 0.22s var(--ease);
}

.topbar__note em .va-ico { transition: transform 0.28s var(--ease); }

.topbar__note:hover em { color: #fff; }

.topbar__note:hover em .va-ico { transform: translateX(4px); }

/* margin-left:auto 吃掉全部余量 —— 否则 space-between 会把 note / meta / ×
   三个子项均分成「左中右」，联系方式就被丢到中间去了。
   ≤1024px 时 meta 隐藏，只剩 note 和 ×，space-between 自己就对了。 */
.topbar__meta { display: flex; align-items: center; gap: 22px; flex: none; margin-left: auto; }

.topbar__meta a {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 0.78rem;
  color: rgba(255, 255, 255, 0.7);
  white-space: nowrap;
  transition: color 0.22s var(--ease);
}

/* Base for the line-icon set. These symbols are bare <circle>/<path>/<rect>
   with no fill attribute, so without `fill: none` SVG's default paints them
   solid black — which is exactly what the About page's value icons did. Every
   contextual rule below is more specific and still overrides size and colour. */
.ico-line {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.topbar__meta a .ico-line { width: 14px; height: 14px; stroke-width: 1.9; stroke: currentColor; }

.topbar__meta a:hover { color: #fff; }

.header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  height: var(--header-h);
  /* centres .header__inner, so the pill can shrink its own height without
     needing a compensating margin */
  display: flex;
  align-items: center;
  background: transparent;
  border-bottom: 1px solid transparent;
  transform: translateY(var(--topbar-h));
  /* backdrop-filter is NOT in this list on purpose: it cannot be composited,
     so transitioning it re-blurs the whole bar every frame for 400ms. It snaps
     instead, which the background-colour fade covers. */
  transition: transform 0.45s var(--ease), box-shadow 0.4s var(--ease),
              border-color 0.4s var(--ease), background-color 0.4s var(--ease);
}

.header:not(.is-stuck) .va-nav__link          { color: #fff; text-shadow: 0 1px 12px rgba(8, 2, 18, 0.5); }

.header:not(.is-stuck) .va-nav__link:hover,
.header:not(.is-stuck) .va-nav__link.is-current { color: #fff; }

.header:not(.is-stuck) .va-nav__link::after   { background: var(--v-400); }

/* 没有了盒子托底，白条要自己在照片上站住 —— 用和 .va-nav__link 同款的投影，
   而不是加一层 backdrop-filter（每帧重算模糊，topbar 闪烁的同一类代价）。 */
.header:not(.is-stuck) .burger span {
  background: #fff;
  box-shadow: 0 1px 10px rgba(8, 2, 18, 0.55);
}

/* Full-width solid bar — stuck, but not yet condensed. On the homepage the two
   states flip together so this is never seen; on inner pages the bar is solid
   from the first pixel and only condenses after 40px, which is what gives those
   pages the same morph. */
.header.is-stuck:not(.is-pill) {
  /* No backdrop-filter: at 96% opacity nothing of the backdrop comes through,
     so the blur was a full-width per-frame re-raster that painted nothing.
     The pill keeps its own — at 82% that one is actually visible. */
  background: rgba(255, 255, 255, 0.96);
  border-bottom-color: var(--line);
  box-shadow: 0 6px 26px rgba(63, 21, 128, 0.07);
}

/* ---- Condensed pill -------------------------------------------------------
   The bar itself goes fully transparent and .header__inner becomes the visible
   shell. Doing it on the inner element rather than on .header keeps the fixed
   positioning untouched, so nothing jitters while the width animates. */
.header.is-pill {
  background: transparent;
  border-bottom-color: transparent;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.header.is-pill .header__inner {
  /* Same width as the bar it morphs out of — the pill condenses in height and
     grows corners, it does NOT narrow. An earlier 1120px cap left it visibly
     inside the content width below it, which read as a mistake rather than a
     design. `min()` with the viewport only kicks in below --w-band, where it
     keeps the capsule off the screen edges instead of running flush to them. */
  max-width: min(var(--w-band), calc(100% - 2 * var(--gutter)));
  /* 72, not 60. Against an 88px bar it still reads as condensed, but 60 left
     the pill looking thin for its width once it stopped narrowing. */
  height: 72px;
  /* The cap's radius is half the height — 36px at 72. The padding has to clear
     that or the logo and the button sit inside the curve; --gutter + 6 lands at
     38px, which does, and keeps it in step with the bar it morphs out of.
     If the height goes up again, check this number with it. */
  padding-inline: calc(var(--gutter) + 6px);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.82);
  backdrop-filter: saturate(1.7) blur(18px);
  -webkit-backdrop-filter: saturate(1.7) blur(18px);
  box-shadow:
    0 10px 34px rgba(63, 21, 128, 0.14),
    0 2px 8px rgba(63, 21, 128, 0.06),
    inset 0 1px 0 rgba(255, 255, 255, 0.85);
  border: 1px solid rgba(124, 58, 237, 0.10);
}

/* the pill is tighter than the bar, so the gutter between brand / nav / cta
   closes up with it */
.header.is-pill .header__inner { gap: 16px; }
.header.is-pill .header__cta .va-btn { padding-block: 10px; }

.header__inner {
  height: 100%;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 24px;
  /* border is declared transparent up front so the pill's 1px does not add
     itself to the box mid-transition and nudge everything sideways */
  border: 1px solid transparent;
  border-radius: 0;
  transition:
    max-width 0.55s var(--ease), height 0.55s var(--ease),
    padding 0.55s var(--ease), gap 0.55s var(--ease),
    border-radius 0.5s var(--ease), background-color 0.45s var(--ease),
    box-shadow 0.5s var(--ease), border-color 0.45s var(--ease);
}

@media (prefers-reduced-motion: reduce) {
  .header__inner { transition: none; }
}

.va-nav { justify-self: center; }

.va-nav__list { display: flex; align-items: center; gap: 4px; }

.va-nav__item { position: relative; }

.va-nav__link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 10px 16px;
  /* display face at 700 — Manrope 500 read far too thin against the video */
  font-family: var(--f-display);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: -0.014em;
  color: var(--ink-soft);
  border-radius: 4px;
  transition: color 0.22s var(--ease);
}

.va-nav__link:hover, .va-nav__link[aria-expanded="true"] { color: var(--v-600); }

.va-nav__link.is-current { color: var(--v-600); font-weight: 600; }

.va-nav__link .caret {
  width: 9px;
  height: 6px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform 0.28s var(--ease);
}

.va-nav__link[aria-expanded="true"] .caret { transform: rotate(180deg); }

.va-nav__link::after {
  content: "";
  position: absolute;
  left: 14px;
  right: 14px;
  bottom: 4px;
  height: 1.5px;
  background: var(--v-600);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.32s var(--ease);
}

.va-nav__item:hover .va-nav__link::after,
.va-nav__link.is-current::after { transform: scaleX(1); }

.va-nav__panel {
  position: absolute;
  top: calc(100% + 12px);
  left: 50%;
  padding: 12px;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 16px;
  box-shadow: 0 28px 64px rgba(30, 8, 74, 0.2), 0 4px 14px rgba(30, 8, 74, 0.07);
  opacity: 0;
  visibility: hidden;
  transform: translate(-50%, -10px);
  transition: opacity 0.26s var(--ease), transform 0.26s var(--ease), visibility 0.26s;
}

.va-nav__item.is-open .va-nav__panel { opacity: 1; visibility: visible; transform: translate(-50%, 0); }

.va-nav__panel::before {
  content: "";
  position: absolute;
  left: 50%;
  top: -6px;
  width: 12px;
  height: 12px;
  margin-left: -6px;
  background: #fff;
  border-left: 1px solid var(--line);
  border-top: 1px solid var(--line);
  transform: rotate(45deg);
  border-radius: 2px 0 0 0;
}

.va-nav__panel {
  padding: 16px 14px 14px;
  width: max-content;
  max-width: min(780px, calc(100vw - 48px));
}

.va-nav__group {
  font-family: var(--f-display);
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 0 12px;
  margin-bottom: 10px;
}

.va-nav__group--sep {
  margin-top: 14px;
  padding-top: 16px;
  border-top: 1px solid var(--line-soft);
}

.va-nav__cols {
  display: grid;
  grid-template-columns: repeat(2, minmax(300px, 1fr));
  gap: 3px 18px;
}

.va-nav__panel a {
  position: relative;
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr);
  align-items: center;
  gap: 13px;
  padding: 11px 12px;
  border-radius: 10px;
  transition: background-color 0.22s var(--ease);
}

.va-nav__panel a::before {
  content: "";
  position: absolute;
  left: 3px;
  top: 50%;
  width: 2px;
  height: 0;
  border-radius: 2px;
  background: var(--v-600);
  transform: translateY(-50%);
  transition: height 0.26s var(--ease);
}

.va-nav__panel a:hover { background: var(--v-50); }

.va-nav__panel a:hover::before { height: 22px; }

.va-nav__panel a .ico-line {
  width: 21px;
  height: 21px;
  stroke: var(--v-500);
  stroke-width: 1.55;
  transition: stroke 0.24s var(--ease), transform 0.3s var(--ease);
}

.va-nav__panel a:hover .ico-line { stroke: var(--v-700); transform: scale(1.08); }

.va-nav__panel a span { display: block; min-width: 0; }

.va-nav__panel a b {
  display: block;
  font-family: var(--f-display);
  font-size: 0.9rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.3;
  color: var(--ink);
  white-space: nowrap;
}

.va-nav__panel a em {
  display: block;
  font-style: normal;
  font-size: 0.78rem;
  line-height: 1.35;
  color: var(--muted);
  margin-top: 1px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.va-nav__panel a:hover b { color: var(--v-700); }

.header__cta { justify-self: end; display: flex; align-items: center; gap: 12px; }

/* 不带容器。之前那个描边方盒在 hero 上是块悬空的补丁，落进白色胶囊里
   更糟 —— 圆角矩形叠在圆角胶囊上，盒子套盒子。图标本身够清楚，不需要框。
   触摸区仍是 44×44（无障碍下限），只是不可见；靠 margin 负值把它挂回
   光学右缘，否则 11px 的内缩会让图标看着比字标退进去一截。 */
.burger {
  display: none;
  width: 44px;
  height: 44px;
  margin-right: -11px;
  border: 0;
  background: none;
  border-radius: 50%;
  place-items: center;
  align-content: center;
  gap: 5.5px;
  -webkit-tap-highlight-color: transparent;
}

.burger span {
  display: block;
  width: 22px;
  height: 2px;
  background: var(--ink);
  border-radius: 2px;
  /* 只动 transform 和 opacity —— 两个都在合成器上 */
  transition: transform 0.36s var(--ease), opacity 0.22s var(--ease);
  transform-origin: center;
}

/* 静止时中间一条略短，是个刻意的不对称：三条等长太像默认图标。
   悬停/按下时它补满，收起时又缩回去 —— 一个不花钱的微交互。 */
.burger span:nth-child(2) { transform: scaleX(0.64); }

.burger:hover span:nth-child(2),
.burger:active span:nth-child(2) { transform: scaleX(1); }

/* 展开成 ×：中间条淡出，上下两条各走 7.5px（2px 条高 + 5.5px 间距）后旋转 */
.burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(7.5px) rotate(45deg); }

.burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; transform: scaleX(0.2); }

.burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7.5px) rotate(-45deg); }

.burger:focus-visible { outline: 2px solid var(--v-600); outline-offset: 2px; }

.drawer {
  position: fixed;
  inset: calc(var(--header-h) + var(--topbar-h)) 0 0 0;
  z-index: 95;
  background: #fff;
  padding: 26px var(--gutter) 40px;
  overflow-y: auto;
  overscroll-behavior: contain;   /* 抽屉滚到头不要把滚动传给背后的页面 */
  opacity: 0;
  visibility: hidden;
  transform: translateY(-10px);
  transition: opacity 0.32s var(--ease), transform 0.32s var(--ease), visibility 0.32s;
}

/* 顶栏被 × 收起后页眉整体上移 42px，抽屉顶边得跟着走，
   否则页眉底边和抽屉顶边之间会露出 42px 的页面。 */
html:has(.topbar.is-hidden) .drawer { top: var(--header-h); }

/* JS 一直在给 body 加 .is-locked，但 CSS 里没有任何对应规则 ——
   抽屉打开时背后的页面照样能滚。 */
body.is-locked { overflow: hidden; }

/* 抽屉打开时把页眉摊平成整幅白条。原来它保持胶囊形态：胶囊左右和上下
   都是内缩的，而抽屉从 chrome 底边才开始，于是胶囊四周露出一圈底下的
   页面内容 —— 就是"漏底"的成因。摊平后页眉和抽屉连成一张白纸。 */
/* .header 自带 1px 透明下边框，抽屉顶边正好卡在它下面 —— 亚像素舍入时会
   漏出一条发丝宽的页面。整条也铺白，缝隙就无从谈起。 */
body.is-locked .header { background: #fff; border-bottom-color: #fff; }

body.is-locked .header .header__inner {
  max-width: 100%;
  height: var(--header-h);
  padding-inline: var(--gutter);
  border-radius: 0;
  background: #fff;
  box-shadow: none;
  border-color: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* 白条上必须用深色那套。在 hero 顶部打开抽屉时页眉本来是 :not(.is-stuck)
   状态 —— 白字标、白汉堡条，摊平成白底后会整个消失。
   ⚠️ 选择器里那个多余的 .header 是必须的：要盖掉的
   `.header:not(.is-stuck) .va-brand__lg--dark` 是 (0,3,0)，
   而 `body.is-locked .va-brand__lg--dark` 只有 (0,2,1)，压不过。 */
body.is-locked .header .va-brand__lg--dark  { opacity: 0; }
body.is-locked .header .va-brand__lg--light { opacity: 1; }
body.is-locked .header .burger span { background: var(--ink); box-shadow: none; }

.drawer.is-open { opacity: 1; visibility: visible; transform: none; }

.drawer__group + .drawer__group { border-top: 1px solid var(--line-soft); }

.drawer__row {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 2px;
  font-family: var(--f-display);
  font-size: 1.03rem;
  font-weight: 600;
  color: var(--ink);
  text-align: left;
}

.drawer__row .caret { width: 11px; height: 7px; fill: none; stroke: var(--v-600); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; transition: transform 0.28s var(--ease); }

.drawer__row[aria-expanded="true"] { color: var(--v-600); }

.drawer__row[aria-expanded="true"] .caret { transform: rotate(180deg); }

.drawer__sub { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 0.34s var(--ease); }

.drawer__sub > div { overflow: hidden; }

.drawer__row[aria-expanded="true"] + .drawer__sub { grid-template-rows: 1fr; }

.drawer__sub a {
  display: block;
  padding: 10px 2px 10px 16px;
  font-size: 0.92rem;
  color: var(--body);
  border-left: 1.5px solid var(--v-100);
}

.drawer__sub a:hover { color: var(--v-600); border-left-color: var(--v-500); }

.drawer__foot { margin-top: 26px; display: grid; gap: 12px; }

.drawer__foot .va-btn { justify-content: center; }

/* 按钮以下原本是一大片空白 —— 抽屉是整屏高，内容只占顶上一截。
   把联系方式接上来，既填了空又是这个位置本来就该有的东西。 */
.drawer__meta {
  margin: 26px 0 0;
  padding: 22px 0 0;
  border-top: 1px solid var(--line-soft);
  display: grid;
  gap: 15px;
  list-style: none;
}

.drawer__meta li {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  font-size: 0.92rem;
  line-height: 1.5;
  color: var(--ink-soft);
}

.drawer__meta .ico-line {
  flex: none;
  width: 17px; height: 17px;
  margin-top: 2px;
  stroke-width: 1.7;
  color: var(--v-600);
}

.drawer__meta a { color: inherit; }
.drawer__meta a:hover { color: var(--v-600); }

.footer {
  /* --fp is the footer's scroll progress, 0 (just below the fold) -> 1 (fully
     in view). JS writes it every frame, so the whole block plays forward on
     the way down and reverses on the way back up. */
  --fp: 1;
  position: relative;
  padding: 104px 0 0;
  background: #0c0518;
  overflow: hidden;
  isolation: isolate;
}

.footer::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(46% 62% at 8% 0%, rgba(124, 58, 237, 0.26) 0%, transparent 68%),
    radial-gradient(40% 56% at 92% 8%, rgba(89, 33, 201, 0.18) 0%, transparent 66%);
  opacity: calc(0.25 + var(--fp) * 0.75);
  transform: scale(calc(1.14 - var(--fp) * 0.14));
  transform-origin: 50% 0%;
}

.footer__signature {
  position: absolute;
  left: 50%;
  bottom: -0.3em;
  z-index: 0;
  width: max-content;
  font-family: var(--f-display);
  font-size: clamp(4.4rem, 16.5vw, 16.5rem);
  font-weight: 900;
  letter-spacing: -0.045em;
  line-height: 1;
  color: transparent;
  -webkit-text-stroke: 1px rgba(255, 255, 255, 0.095);
  white-space: nowrap;
  pointer-events: none;
  user-select: none;
  opacity: calc(var(--fp) * var(--fp));
  transform: translate(-50%, calc((1 - var(--fp)) * 76px));
}

.footer__top > * {
  opacity: calc(0.05 + var(--fp) * 0.95);
  transform: translateY(calc((1 - var(--fp)) * var(--off, 40px)));
}

.footer__top > *:nth-child(2) { --off: 54px; }

.footer__top > *:nth-child(3) { --off: 68px; }

.footer__top > *:nth-child(4) { --off: 82px; }

.footer__top > *:nth-child(5) { --off: 96px; }

.footer__bar {
  opacity: calc(0.05 + var(--fp) * 0.95);
  transform: translateY(calc((1 - var(--fp)) * 32px));
}

@media (prefers-reduced-motion: reduce) {
  .footer { --fp: 1 !important; }
  .footer::before,
  .footer__signature,
  .footer__top > *,
  .footer__bar { transform: none !important; opacity: 1 !important; }
}

.footer .shell { position: relative; z-index: 1; }

.footer__top {
  display: grid;
  grid-template-columns: 1.45fr repeat(3, 0.92fr) 1.15fr;
  gap: 40px;
  padding-bottom: 78px;
}

.footer__blurb { margin-top: 18px; font-size: 0.925rem; line-height: 1.62; color: rgba(255, 255, 255, 0.56); max-width: 30ch; }

.socials { display: flex; gap: 9px; margin-top: 20px; }

.socials a {
  width: 36px;
  height: 36px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.14);
  color: rgba(255, 255, 255, 0.82);
  transition: background-color 0.28s var(--ease), border-color 0.28s var(--ease),
              color 0.28s var(--ease), transform 0.28s var(--ease);
}

.socials svg { width: 15px; height: 15px; fill: currentColor; }

.socials a:hover {
  background: var(--v-600);
  border-color: var(--v-600);
  color: #fff;
  transform: translateY(-3px);
}

.footer__ttl {
  font-family: var(--f-display);
  font-size: 0.78rem;
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--v-400);
  margin-bottom: 17px;
}

.footer__list { display: grid; gap: 12px; line-height: 1.5; }

/* Touch targets. On a phone these sat at ~21px tall — well under the 44px
   Apple asks for and the 48dp Android does. Padding rather than a min-height
   so the row grows around the text instead of leaving it top-aligned. */
@media (max-width: 860px) {
  .footer__list { gap: 2px; }
  .footer__list a, .footer__list span { display: inline-block; padding-block: 9px; }
  /* same treatment for the rest of the footer's links */
  .footer__contact a { display: inline-block; padding-block: 8px; }
  .footer__contact li { align-items: center; }
  .footer__legal { gap: 6px 22px; }
  .footer__legal a { display: inline-block; padding-block: 9px; }
  .socials a { width: 42px; height: 42px; }
}

.footer__list a, .footer__list span {
  font-size: 0.925rem;
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.6);
  transition: color 0.22s var(--ease), transform 0.22s var(--ease);
}

.footer__list a { display: inline-block; }

.footer__list a:hover { color: #fff; transform: translateX(3px); }

.footer__list .is-strong { color: #fff; font-weight: 700; }

.footer__contact { display: grid; gap: 14px; }

.footer__contact li {
  display: grid;
  grid-template-columns: 17px minmax(0, 1fr);
  gap: 11px;
  align-items: start;
}

.footer__contact .ico-line {
  width: 15px;
  height: 15px;
  stroke: var(--v-400);
  stroke-width: 1.7;
  margin-top: 3px;
}

.footer__contact a,
.footer__contact span {
  font-size: 0.925rem;
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.6);
  transition: color 0.22s var(--ease);
}

.footer__contact a:hover { color: #fff; }

.footer__contact .is-lead {
  font-family: var(--f-display);
  font-size: 1.02rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  color: #fff;
}

.footer__contact .is-lead:hover { color: var(--v-400); }

.flag-us {
  display: inline-block;
  width: 19px;
  height: 10px;
  margin-left: 7px;
  vertical-align: -1px;
  border-radius: 2px;
  /* hairline so the white stripes don't bleed into the dark ground */
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.22);
}

.footer__cta { margin-top: 22px; }

.footer__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  padding: 26px 0 34px;
  border-top: 1px solid rgba(255, 255, 255, 0.1);
  font-size: 0.83rem;
  color: rgba(255, 255, 255, 0.45);
}

.footer__bar b { color: rgba(255, 255, 255, 0.72); font-weight: 800; }

.footer__legal { display: flex; gap: 22px; flex-wrap: wrap; }

.footer__legal a:hover { color: #fff; }

[data-reveal] {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.72s var(--ease), transform 0.72s var(--ease);
  transition-delay: var(--d, 0s);
}

[data-reveal].is-in { opacity: 1; transform: none; }

.rise { opacity: 0; transform: translateY(26px); animation: rise 0.92s var(--ease) forwards; animation-delay: var(--d, 0s); }

@keyframes rise { to { opacity: 1; transform: none; } }

.fade-in { opacity: 0; animation: fadeIn 1.2s var(--ease) forwards; animation-delay: var(--d, 0s); }

@keyframes fadeIn { to { opacity: 1; } }

@media (prefers-reduced-motion: reduce) {
  [data-reveal] { opacity: 1; transform: none; }
  .rise, .fade-in { opacity: 1; transform: none; }
}

.va-btn .wd { display: inline-flex; }

.va-btn .ch { display: inline-block; }

.va-btn:hover .ch,
.va-btn:focus-visible .ch {
  animation: btnFlick 0.52s var(--ease) forwards;
  animation-delay: calc(var(--i) * 17ms);
}

@keyframes btnFlick {
  0%   { opacity: 1;    text-shadow: none; }
  18%  { opacity: 0.22; text-shadow: none; }
  34%  { opacity: 1;    text-shadow: 0 0 13px rgba(255, 255, 255, 0.95); }
  52%  { opacity: 0.45; text-shadow: none; }
  70%  { opacity: 1;    text-shadow: 0 0 9px rgba(255, 255, 255, 0.6); }
  84%  { opacity: 0.78; }
  100% { opacity: 1;    text-shadow: none; }
}

@media (prefers-reduced-motion: reduce) {
  .va-btn:hover .ch, .va-btn:focus-visible .ch { animation: none; }
}

@media (max-width: 860px) {
  /* a column, so the logo / tag / heading each get their own line however
     wide the screen is — inline-level children sat side by side at 768px */
}

.va-hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  align-items: center;
  padding-top: calc(var(--header-h) + var(--topbar-h) + 40px);
  padding-bottom: 96px;
  isolation: isolate;
  overflow: hidden;
  background: #14061f;
}

.va-hero__bg { position: absolute; inset: 0; z-index: 0; overflow: hidden; }

.va-hero__bg video,
.va-hero__bg img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 56% 45%;
  /* Near-natural now. The violet used to be baked in with a heavy desaturate,
     which flattened the whole frame into one purple sheet. */
  filter: saturate(0.92) contrast(1.05) brightness(0.98);
  transform: scale(1.06);
  animation: heroPan 26s ease-in-out infinite alternate;
}

@keyframes heroPan {
  from { transform: scale(1.06) translate3d(0, 0, 0); }
  to   { transform: scale(1.14) translate3d(-1.6%, -1.2%, 0); }
}

.va-hero__bg::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  /* Violet drawn in from both edges now that the copy sits centre-stage —
     a left-only wash reads lopsided under centred type. The middle band stays
     the most natural part of the frame. */
  background:
    linear-gradient(102deg,
      rgba(76, 24, 168, 0.92) 0%,
      rgba(105, 44, 222, 0.6) 20%,
      rgba(124, 58, 237, 0.22) 38%,
      transparent 54%),
    linear-gradient(258deg,
      rgba(66, 22, 146, 0.72) 0%,
      rgba(110, 48, 224, 0.34) 20%,
      transparent 44%),
    linear-gradient(178deg,
      rgba(52, 16, 118, 0.42) 0%,
      transparent 40%);
  mix-blend-mode: multiply;
}

.va-hero__bg::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  background:
    /* soft ellipse under the centred copy — carries the text contrast without
       flattening the whole frame */
    radial-gradient(66% 58% at 50% 50%, rgba(8, 2, 16, 0.62) 0%, rgba(8, 2, 16, 0.3) 52%, transparent 80%),
    linear-gradient(0deg, rgba(8, 2, 16, 0.46) 0%, transparent 32%),
    /* Chrome 带的压底。停靠点用 px 而不是 %：chrome 恒为 130px（顶栏 42 +
       页眉 88），而 hero 高度是 88svh、随屏而变 —— 用 % 的话屏幕越高
       遮罩越"稀"，正是客户看到字标糊进背景的成因（那个高度上只剩 ~23%）。
       0.70 的起点接着上面深色顶栏往下收，读作自然衰减而不是一条横杠。 */
    linear-gradient(180deg,
      rgba(8, 2, 16, 0.70) 0,
      rgba(8, 2, 16, 0.56) 118px,
      rgba(8, 2, 16, 0.20) 190px,
      transparent 268px);
}

.va-hero__grain {
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  opacity: 0.14;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}

.va-hero__inner {
  position: relative;
  z-index: 6;
  width: 100%;
  text-align: center;
}

.va-hero__title {
  /* Was inheriting JX's h1 rule — the one thing the merge notes said these
     blocks must never do. 900 is why Chivo was picked in the first place: it
     gives the hero a weight the rest of the page never reaches. */
  font-family: var(--f-display);
  font-weight: 900;
  font-size: var(--t-hero);
  line-height: 1.0;
  letter-spacing: -0.034em;
  color: #fff;
  margin-bottom: 28px;
  white-space: nowrap;   /* the rotating word must not drop to its own line */
}

.va-hero__rotator {
  display: inline-block;
  vertical-align: top;
  position: relative;
  overflow: hidden;
  height: 1.0em;
  /* JS sets the width to whichever word is showing; reserving the widest one
     instead left a dead gap after every short word. */
  transition: width 0.52s var(--ease);
}

.va-hero__word {
  position: absolute;
  left: 50%;
  top: 0;
  display: block;
  color: var(--v-400);
  opacity: 0;
  transform: translate(-50%, 100%);
  transition: opacity 0.55s var(--ease), transform 0.55s var(--ease);
  white-space: nowrap;
}

.va-hero__word.is-in  { opacity: 1; transform: translate(-50%, 0); }

.va-hero__word.is-out { opacity: 0; transform: translate(-50%, -100%); }

.va-hero__lead {
  /* wide enough for the sentence to set on two lines rather than three;
     balance keeps those two lines close to equal length */
  max-width: 860px;
  margin: 0 auto 38px;
  color: rgba(255, 255, 255, 0.88);
  font-size: 1.28rem;
  line-height: 1.62;
  text-wrap: balance;
}

.va-hero__actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 16px; }

.va-hero__actions .va-btn { padding: 17px 30px; font-size: 0.95rem; }

.va-hero__actions .va-btn .ico--arrow { width: 17px; height: 13px; }

@keyframes marqRun {
  from { transform: translateX(0); }
  to   { transform: translateX(calc(-1 * var(--marq-w, 50%))); }
}

@media (max-width: 1150px) {
}

@media (max-width: 1024px) {
  .va-hero { padding-bottom: 72px; }
  .footer__top { grid-template-columns: 1fr 1fr 1fr; gap: 30px; }
  .footer__brand { grid-column: span 3; }
}

/* 汉堡断点。原本是 860px —— 但导航条自然宽 606px、CTA 152px、两个 16px
   间隙，合计 790px，而胶囊在 880px 视口下的内可用宽只有 740px：**不给字标
   留任何宽度就已经溢出**。所以 861–1100 这一段的页眉一直是坏的（"Contact"
   被胶囊右缘切掉、LET'S TALK 探到胶囊外），跟字标多大无关，只是没人在这个
   宽度看过。1180px 是实测导航真正放得下的位置：那里给字标的余量约 228px，
   而字标要 183px。 */
@media (max-width: 1180px) {
  .va-nav, .header__cta .va-btn { display: none; }
  .burger { display: grid; }
  .header__inner { grid-template-columns: auto 1fr; }
  .header__cta { justify-self: end; }
}

@media (max-width: 860px) {
  .va-hero { min-height: 88svh; padding-top: calc(var(--header-h) + var(--topbar-h) + 28px); padding-bottom: 64px; }
  .va-hero__lead { max-width: 42ch; font-size: 1rem; }
  .va-hero__bg::after {
    background:
      radial-gradient(80% 56% at 50% 48%, rgba(8, 2, 16, 0.66) 0%, rgba(8, 2, 16, 0.34) 56%, transparent 84%),
      linear-gradient(0deg, rgba(8, 2, 16, 0.56) 0%, transparent 40%),
      linear-gradient(180deg,
        rgba(8, 2, 16, 0.70) 0,
        rgba(8, 2, 16, 0.56) 118px,
        rgba(8, 2, 16, 0.20) 190px,
        transparent 268px);
  }
}

@media (max-width: 640px) {
  /* 字标 5.7:1 很扁，小屏上按高度收一档就够，宽度自然跟着走 */
  .va-brand { width: 114px; height: 20px; }
  .va-brand--lg { width: 160px; height: 28px; }

  /* 胶囊在手机上只装着字标和汉堡，72px 高显得空。降到 62px；
     半径随之从 36 落到 31，左右内边距也跟着收，否则内容离curve太远、
     整条更显空。（几何上只需清掉 ~2px：内容半高 10px 时曲线内缩
     31-sqrt(31²-10²)≈1.7px —— 38px 那个数字是视觉留白，不是硬约束。） */
  .header.is-pill .header__inner {
    height: 62px;
    padding-inline: calc(var(--gutter) + 2px);
  }

  .va-hero { min-height: 76svh; padding-top: calc(var(--header-h) + var(--topbar-h) + 28px); padding-bottom: 56px; }
  .va-hero__actions .va-btn { flex: 1 1 100%; justify-content: center; }
  /* On a phone one line would force the type down to something meek — let the
     rotating word drop below instead and keep the headline big. */
  .va-hero__title {
    line-height: 1.04;
    font-size: clamp(2.15rem, 9.6vw, 2.9rem);
    white-space: normal;
  }
  .va-hero__rotator { display: block; vertical-align: baseline; margin-inline: auto; }
  .va-hero__lead { font-size: 1.06rem; }
  /* 两个按钮原本同宽同高（326×46）上下堆着，主次只靠颜色分——不够。
     主按钮加高 + 投影往前推，次按钮留在原高度往后退，用体量拉开层级。
     ⚠️ 这条必须排在 --primary 之前：它用的是 padding 简写，会把
     padding-block 整个清掉。之前顺序反了，加高完全没生效。 */
  .va-hero__actions .va-btn { padding: 15px 20px; font-size: 0.86rem; }
  .va-hero__actions .va-btn--primary {
    padding-block: 19px;
    box-shadow: 0 10px 26px rgba(76, 24, 168, 0.45);
  }
  .footer { padding-top: 40px; }
  .footer__top { grid-template-columns: 1fr 1fr; gap: 26px 22px; }
  .footer__brand { grid-column: span 2; }
  .footer__bar { flex-direction: column; align-items: flex-start; gap: 12px; }
}

@keyframes hotPing {
  0%   { transform: scale(1);   opacity: 0.9; }
  70%  { transform: scale(2.1); opacity: 0; }
  100% { transform: scale(2.1); opacity: 0; }
}

.cta {
  position: relative;
  padding-block: 84px;
  background: linear-gradient(104deg, #4b18a8 0%, #7c3aed 46%, #9a63f2 100%);
  overflow: hidden;
  isolation: isolate;
}

.cta__field {
  position: absolute;
  inset: -22%;
  z-index: 0;
  pointer-events: none;
}

.cta__field span {
  position: absolute;
  border-radius: 50%;
  filter: blur(58px);
  opacity: 0.55;
}

.cta__field span:nth-child(1) {
  width: 46%; height: 78%;
  left: 4%; top: 6%;
  background: radial-gradient(circle, rgba(196, 176, 251, 0.95) 0%, transparent 68%);
  animation: drift1 22s ease-in-out infinite alternate;
}

.cta__field span:nth-child(2) {
  width: 40%; height: 70%;
  right: 6%; bottom: 2%;
  background: radial-gradient(circle, rgba(63, 21, 128, 0.95) 0%, transparent 66%);
  animation: drift2 27s ease-in-out infinite alternate;
}

.cta__field span:nth-child(3) {
  width: 30%; height: 56%;
  left: 42%; top: 30%;
  background: radial-gradient(circle, rgba(139, 92, 246, 0.9) 0%, transparent 68%);
  animation: drift3 19s ease-in-out infinite alternate;
}

@keyframes drift1 { to { transform: translate3d(16%, 12%, 0) scale(1.16); } }

@keyframes drift2 { to { transform: translate3d(-14%, -16%, 0) scale(1.2); } }

@keyframes drift3 { to { transform: translate3d(-10%, 14%, 0) scale(0.86); } }

.cta__mark {
  position: absolute;
  right: -3%;
  top: 50%;
  transform: translateY(-50%);
  width: 420px;
  height: 420px;
  z-index: 1;
  opacity: 0.13;
  pointer-events: none;
}

.cta__mark svg { width: 100%; height: 100%; }

.cta__mark path { fill: #fff; }

.cta__dots {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0.3;
  background-image: radial-gradient(rgba(255, 255, 255, 0.85) 1px, transparent 1px);
  background-size: 18px 18px;
  mask-image: linear-gradient(100deg, #000 0%, transparent 46%);
  -webkit-mask-image: linear-gradient(100deg, #000 0%, transparent 46%);
}

.cta__grain {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0.1;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}

.cta__inner {
  position: relative;
  z-index: 2;
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 36px;
  align-items: center;
}

.cta__ttl {
  font-size: var(--t-cta);
  line-height: 1.14;
  letter-spacing: -0.03em;
  color: #fff;
  max-width: 20ch;
  margin-bottom: 12px;
  text-wrap: balance;
}

.cta__sub { font-size: 1rem; color: rgba(255, 255, 255, 0.82); max-width: 46ch; }

/* Three points across the foot of the band. The block was a headline, one line
   of copy and a button on an otherwise empty gradient — the space was being
   held by decoration alone. These say something the visitor can act on, and
   the icons give the band the texture it was missing. */
.cta__points {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(18px, 2.4vw, 40px);
  margin: 0;
  padding: clamp(24px, 2.8vw, 36px) 0 0;
  list-style: none;
  border-top: 1px solid rgba(255, 255, 255, 0.18);
}

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

/* No chip behind the icon — the band is already a gradient with a grain and a
   dot field on it, and a translucent tile on top of all that just added a
   fourth layer of noise. The stroke alone reads fine at this size on violet.
   (The backdrop-filter went with it: one less compositing layer per icon.) */
.cta__pico {
  flex: none;
  width: 30px; height: 30px;
  display: grid; place-items: center;
  color: #fff;
  transition: transform 0.3s var(--ease);
}

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

.cta__pico .ico-line { width: 26px; height: 26px; stroke-width: 1.5; }

.cta__point b {
  display: block;
  font-family: var(--f-display);
  font-size: 0.95rem; font-weight: 800; letter-spacing: -0.01em;
  color: #fff;
}

.cta__point em {
  display: block;
  font-style: normal;
  margin-top: 3px;
  font-size: 0.82rem; line-height: 1.5;
  color: rgba(255, 255, 255, 0.68);
}

@media (max-width: 860px) {
  .cta__points { grid-template-columns: 1fr; gap: 16px; }
}

@media (max-width: 1150px) {
  .cta__mark { width: 320px; height: 320px; right: -6%; }
}

@media (max-width: 860px) {
  .cta { padding-block: 62px; }
  .cta__inner { grid-template-columns: 1fr; gap: 26px; }
  .cta__mark { opacity: 0.09; }
}

@media (max-width: 640px) {
  .cta { padding-block: 52px; }
  .cta__inner .va-btn { width: 100%; justify-content: center; }
}

@media (max-width: 1024px) {
  .topbar__meta a:nth-child(2) { display: none; }
  .topbar__note em { display: none; }
}

@media (max-width: 760px) {
  .topbar__meta { display: none; }
  .topbar__note { font-size: 0.72rem; }
  .topbar__note b { font-size: 0.64rem; letter-spacing: 0.12em; }
}

@media (max-width: 520px) {
  .topbar__note b { display: none; }
}

.cta__ttl .wd { display: inline-block; white-space: nowrap; }

.cta__ttl .ch {
  display: inline-block;
  opacity: 0;
  transform: translateY(0.14em);
}

.cta__ttl.is-lit .ch {
  animation: chLight 0.66s var(--ease) forwards;
  animation-delay: calc(var(--i) * 24ms);
}

@keyframes chLight {
  0%   { opacity: 0;    transform: translateY(0.14em); text-shadow: none; }
  30%  { opacity: 1;    transform: translateY(0); text-shadow: 0 0 22px rgba(255, 255, 255, 0.95), 0 0 44px rgba(196, 176, 251, 0.6); }
  44%  { opacity: 0.28; text-shadow: none; }
  58%  { opacity: 1;    text-shadow: 0 0 14px rgba(255, 255, 255, 0.6); }
  72%  { opacity: 0.62; }
  100% { opacity: 1;    transform: translateY(0); text-shadow: none; }
}

.cta__scan {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0;
  background: linear-gradient(100deg,
    transparent 38%,
    rgba(255, 255, 255, 0.05) 46%,
    rgba(255, 255, 255, 0.16) 50%,
    rgba(255, 255, 255, 0.05) 54%,
    transparent 62%);
  transform: translateX(-60%);
}

.cta.is-lit .cta__scan { animation: ctaScan 2.1s var(--ease) 0.16s; }

@keyframes ctaScan {
  0%   { opacity: 0;   transform: translateX(-60%); }
  12%  { opacity: 1; }
  88%  { opacity: 1; }
  100% { opacity: 0;   transform: translateX(60%); }
}

.cta.is-lit .cta__dots { animation: ctaPixels 1.5s var(--ease); }

@keyframes ctaPixels {
  0%   { opacity: 0.62; }
  40%  { opacity: 0.16; }
  100% { opacity: 0.3; }
}

@media (prefers-reduced-motion: reduce) {
  .cta__ttl .ch { opacity: 1 !important; transform: none !important; animation: none !important; }
  .cta__scan { display: none; }
  .cta.is-lit .cta__dots { animation: none; }
}

/* ==========================================================================
   Client logo wall — replaces the JX image strip. Their version padded each
   copy of the artwork, so every loop showed ~124px of dead space between the
   two halves; this one measures a real group and clones to fill.
   ========================================================================== */
/* Shared marquee mechanics.
   The travel distance is ONE group width, published by JS as --marq-w, and JS
   clones the group until the track is at least twice the container. A fixed
   `-50%` only works when a single group is already wider than the container —
   the logo row isn't, which would leave a visible gap at the loop point. */
.va-marq__track,
.va-wall__track {
  display: flex;
  width: max-content;
  will-change: transform;
  animation: marqRun var(--marq-dur, 38s) linear infinite;
}
.va-marq__track[data-paused] { animation-play-state: paused; }
@keyframes marqRun {
  from { transform: translateX(0); }
  to   { transform: translateX(calc(-1 * var(--marq-w, 50%))); }
}
.va-marq__group { display: flex; flex: none; }

/* --------------------------------------------------------------------------
   2. Client logo wall
   -------------------------------------------------------------------------- */
/* Full-bleed band butted straight against the hero — no card, no gap. */
.va-wall {
  position: relative;
  z-index: 8;
  background: #fff;
  border-bottom: 1px solid var(--line);
  padding-block: 26px 28px;
}

.va-wall__eyebrow {
  text-align: center;
  font-family: var(--f-display);
  font-size: 0.92rem;
  font-weight: 800;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 20px;
}
.va-wall__eyebrow b { color: var(--v-600); font-weight: 700; }

/* the logo row scrolls continuously instead of sitting as a static 6-up grid */
.va-wall__marquee { position: relative; overflow: hidden; }
.va-wall__marquee::before,
.va-wall__marquee::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 72px;
  z-index: 2;
  pointer-events: none;
}
.va-wall__marquee::before { left: 0;  background: linear-gradient(90deg, #fff 26%, transparent); }
.va-wall__marquee::after  { right: 0; background: linear-gradient(270deg, #fff 26%, transparent); }

.va-wall__marquee:hover .va-wall__track { animation-play-state: paused; }

/* The brand vectors are set to currentColor, so the whole row reads in the
   theme violet rather than as a greyscale logo soup. */
.va-wall__logo {
  flex: none;
  /* the widest normalised logo is 150px, so 200 gives it air without leaving
     the narrow marks stranded in the middle of an over-wide cell */
  width: 200px;
  display: grid;
  place-items: center;
  min-height: 58px;
  padding: 4px 20px;
  border-left: 1px solid var(--line);
  transition: opacity 0.35s var(--ease), transform 0.35s var(--ease);
}
.va-wall__logo:hover { opacity: 1; transform: translateY(-2px); }

/* Client lockups.
   GSK / TCL / Talesun / Sieyuan ship as complete official logos; UGREEN only
   exists as a raster wordmark and comes in as a mask; the remaining four have
   no obtainable artwork and are set in type until the client supplies files.
   Brand colours are preserved in the SVGs so the greyscale hover reveals them. */
.cl {
  display: flex;
  align-items: center;
  gap: 9px;
  white-space: nowrap;
}

/* Real client logos, in mixed formats and aspect ratios from 0.90 to 7.86.
   Greyscale at rest, back to their own colours when you hover that one logo —
   not the whole row.

   TWO STACKED COPIES, and only `opacity` animates. filter cannot be composited,
   so transitioning one would re-run it every frame on up to 74 images inside a
   marquee that is already moving. The grey copy sits underneath permanently
   (rasterised once) and the colour copy fades in over it. Same image, same
   alpha, so at full opacity it covers its own silhouette exactly.

   A brand-violet tint was tried and dropped. It needed a six-function chain
   (lift the blacks, then colourise) because sepia() has nothing to work with on
   pure black — adidas, TEKMAND, SF Express and Sinopec stayed black while the
   rest went violet. Greyscale needs none of that and reads calmer besides. */
.plogo {
  position: relative;
  display: block;
  flex: none;
}

.plogo img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.plogo__t {
  filter: grayscale(1) opacity(0.72);
}

.plogo__c {
  opacity: 0;
  transition: opacity 0.34s var(--ease);
}

.va-wall__logo:hover .plogo__c,
.trust__item:hover .plogo__c { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .plogo__c { transition: none; }
}


@media (max-width: 640px) {
  .va-wall__logo { width: 168px; padding-inline: 14px; }
}

/* A 224px cell on a 390px phone is 57% of the screen — barely more than one
   logo visible at a time, which reads as a broken row rather than a marquee.
   The JS measures the group and re-clones on resize, so narrowing the cell is
   all that is needed; the loop maths follows. */
@media (max-width: 480px) {
  .va-wall__logo { width: 132px; padding-inline: 10px; min-height: 50px; }
  .va-wall__eyebrow { font-size: 0.78rem; letter-spacing: 0.1em; margin-bottom: 14px; }
}

/* A stat whose real figure hasn't been supplied yet. Dimmed so it reads as
   deliberately pending rather than as a zero. */
.sfig__num--tbd { opacity: .38; }

/* --------------------------------------------------------------------------
   Hero tagline — the brochure cover line, set above the headline.
   Wide tracking and hairline separators so it reads as a strapline rather
   than as a category label.
   -------------------------------------------------------------------------- */
.va-hero__tagline {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(12px, 1.6vw, 22px);
  margin: 0 0 20px;
  font-family: var(--f-display);
  font-size: clamp(.72rem, 1.05vw, .88rem);
  font-weight: 800;
  letter-spacing: .34em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .9);
}
.va-hero__tagline span { text-indent: .34em; }   /* offset the trailing track */
.va-hero__tagline i {
  flex: none;
  width: 1px;
  height: 13px;
  background: var(--v-400);
  opacity: .75;
}
@media (max-width: 640px) {
  .va-hero__tagline { gap: 10px; letter-spacing: .22em; font-size: .68rem; margin-bottom: 16px; }
  .va-hero__tagline i { height: 11px; }
}
