@font-face{font-family:"Epic Pro";src:url("../assets/fonts/GCEpicPro-Bold.woff") format("woff");font-weight:700;font-style:normal;font-display:swap}
/* GCEpicPro maps U+2014 but its emdash is a composite whose component lands
   outside the glyph box, so every em dash in a display heading rendered as a
   blank gap ("BMW M3 __ FULL PPF"). The endash and middot in the same face
   are fine. A second @font-face for the same family, declared after the
   first and scoped to that one codepoint, hands the em dash to a system face
   and leaves the rest of the heading in Epic Pro. */
@font-face{font-family:"Epic Pro";src:local("Arial Bold"),local("Helvetica Bold"),local("Arial");
  unicode-range:U+2014;font-weight:700;font-style:normal}
@property --py{syntax:'<percentage>';inherits:false;initial-value:-7%}
*,*::before,*::after{box-sizing:border-box}
/* The page can be dragged past its own top and bottom on a trackpad or a
   phone; the elastic area is painted by the canvas, not by <body>, so it
   showed as a band that did not belong to the design. Paint the root, and
   stop the bounce so the document has hard ends. */
html{-webkit-text-size-adjust:100%;background:var(--bg);overscroll-behavior-y:none}
body{overscroll-behavior-y:none}
body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--font-body);
  font-weight:300;line-height:1.6;-webkit-font-smoothing:antialiased}
/* body 上不能再有 overflow-x:hidden。tokens.css 给了 html{overflow-x:clip} 之后,
   视口的 overflow 改从 html 取,body 自己那条 overflow-x:hidden 于是真的作用在 body 上,
   把 overflow-y 连带算成 auto —— body 成了一个滚不动的滚动容器,滚轮事件被它吃掉,
   整站鼠标/触摸都滚不动(scrollTo() 还能动,所以脚本验收看不出来)。横向溢出已经由
   html 的 clip 兜住了,这条纯属多余。 */
img,video{display:block;max-width:100%}
a{color:inherit;text-decoration:none}
h1,h2,h3,p,ul{margin:0}
ul{list-style:none;padding:0}
.mono{font-family:var(--font-mono);font-size:15.5px;letter-spacing:var(--tracking-label);
  text-transform:uppercase;color:var(--ink-subtle)}
:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:var(--radius)}

/* ── loader ─────────────────────────────────────────── */
.loader{position:fixed;inset:0;z-index:90;background:var(--bg);
  display:grid;place-content:center;gap:var(--sp-5);justify-items:center;
  transition:clip-path 1s var(--ease);clip-path:inset(0 0 0 0)}
.loader.is-out{clip-path:inset(0 0 100% 0)}
.loader__logo{width:66px;height:auto;filter:drop-shadow(0 0 34px rgba(255,0,128,.45))}
.loader__label{font-family:var(--font-mono);font-size:14px;letter-spacing:var(--tracking-label);
  text-transform:uppercase;color:var(--ink-subtle)}
.loader__count{margin-top:var(--sp-3);font-family:var(--font-mono);font-size:16.5px;
  color:var(--ink-subtle);letter-spacing:.16em;font-variant-numeric:tabular-nums}
.loader__count i{font-style:normal;opacity:.5}
.loader__track{width:min(240px,52vw);height:1px;background:var(--line)}
.loader__fill{height:100%;width:0;background:var(--brand-sweep)}

/* ── hero — laocoon skeleton ────────────────────────── */
/* the region is tall; the stage inside it is what you see. The distance
   between them IS the scrub — 280svh gives the 40-frame reel room to read
   as motion rather than a slideshow. */
.hero{position:relative;height:calc(200svh / var(--z))}
.hero__stage{position:sticky;top:0;min-height:calc(100svh / var(--z));height:calc(100svh / var(--z));
  display:grid;grid-template-rows:auto 1fr auto;overflow:hidden;
  padding:var(--sp-6) clamp(var(--sp-5),4vw,var(--sp-8)) clamp(var(--sp-5),2.4vw,var(--sp-6))}
.hero__media{position:absolute;inset:0;z-index:0}
/* the logo ramp laid over the footage in colour mode — hue only, luminance is
   the clip's own. The scrims sit above it, the type above those. */
.hero__media::after{content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background:linear-gradient(118deg,#ff0000 0%,#a20086 54%,#8a00b0 100%);
  mix-blend-mode:color;opacity:.30}
.hero__video{position:absolute;inset:0;width:100%;height:100%;display:block;
  object-fit:cover;filter:saturate(.62) contrast(1.12) brightness(.98)}
/* the scrims exist to keep type legible, not to dim the shop's own footage.
   Each one is heavy only where type actually sits — behind the masthead and
   behind the base band — and clears out of the middle of the frame entirely. */
.hero__topfade{position:absolute;inset:0 0 auto;height:46%;z-index:2;
  background:linear-gradient(180deg,rgba(0,0,0,.80) 0%,rgba(0,0,0,.52) 30%,
    rgba(0,0,0,.20) 62%,transparent 100%)}
.hero__basefade{position:absolute;inset:auto 0 0;height:44%;z-index:2;
  background:linear-gradient(180deg,transparent 0%,rgba(0,0,0,.30) 26%,rgba(0,0,0,.72) 58%,rgba(0,0,0,.92) 82%,var(--bg) 99%)}
/* hairline thirds — a quiet instrument grid over the frame */
.hero__rules{position:absolute;inset:0;max-width:1560px;margin:0 auto;padding:0 clamp(var(--sp-5),4vw,var(--sp-8));display:grid;grid-template-columns:repeat(6,1fr);pointer-events:none;z-index:1}
.hero__rules i{border-left:1px solid rgba(255,255,255,.055)}
.hero__rules i:first-child{border-left:0}
.hero__bloom{position:absolute;left:-8%;bottom:-30%;width:78vw;height:78vw;max-width:1180px;max-height:1180px;background:radial-gradient(circle at 50% 50%,var(--glow) 0%,var(--glow-violet) 38%,transparent 70%);filter:blur(120px);opacity:.36;mix-blend-mode:screen;pointer-events:none;z-index:1}

.hero__nav,.hero__title,.hero__nav{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-5);
  padding-bottom:var(--sp-5);border-bottom:1px solid var(--line)}
.brand{display:flex;align-items:center;gap:var(--sp-3)}
.brand img{width:30px;height:auto;filter:drop-shadow(0 0 16px rgba(255,0,128,.42))}
.brand__txt b{display:block;font-family:var(--font-display);font-weight:700;font-size:16.5px;
  letter-spacing:.10em;line-height:1;text-shadow:0 1px 12px rgba(0,0,0,.75)}
.brand__txt i{display:block;margin-top:5px;font-family:var(--font-mono);font-style:normal;
  font-size:13.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-muted)}
/* 手机端才出现的第二行小字(见 css/mobile.css)。桌面上字标只放 HUMBLE CREW +
   Plano, Texas,加第三行会把导航条撑高,所以默认藏起来。 */
.brand__txt i.brand__sub{display:none}
.hero__links{display:flex;gap:var(--sp-5);align-items:center}
/* The Services item is a <button>, and .svcmenu__btn sets --font-mono on it.
   These links set no family at all, so on the homepage they fell back to the
   body face while SERVICES sat next to them in Rajdhani. Landing pages already
   set mono on both (landing.css `.lnav__links a`); match that here, and take
   the button's fluid size so the two also agree on wide screens. */
.hero__links a{font-family:var(--font-mono);font-weight:600;
  font-size:clamp(16px,1.145vw,18px);letter-spacing:.12em;text-transform:uppercase;
  color:var(--ink);text-shadow:0 1px 12px rgba(0,0,0,.75);
  transition:color var(--dur-fast) var(--ease)}
.hero__links a:hover{color:var(--ink)}
.hero__links .is-cta{position:relative;color:var(--ink)}
.hero__links .is-cta::after{content:"";position:absolute;left:0;right:0;bottom:-6px;height:1px;
  background:var(--brand-sweep);transform:scaleX(0);transform-origin:right;
  transition:transform var(--dur) var(--ease)}
.hero__links .is-cta:hover::after{transform:scaleX(1);transform-origin:left}

/* base band — the only place type lives */
/* title sits just under the masthead, top-left; the middle of the frame is
   left entirely to the picture, and the base band carries the proof + actions */
/* the three the shop leads with. On their own site these words are baked into
   the wordmark PNG at 12px; set as live type they can carry the frame. */
.hero__trio{display:flex;align-items:center;flex-wrap:wrap;
  gap:clamp(10px,1.1vw,20px);margin:0}
.hero__trio span{font-family:var(--font-mono);font-size:clamp(15.5px,1.254vw,18.5px);
  letter-spacing:.17em;text-transform:uppercase;color:var(--ink);
  text-shadow:0 1px 10px rgba(0,0,0,.9)}
.hero__trio i{display:block;width:clamp(16px,2.4vw,40px);height:1px;background:var(--brand-sweep);opacity:.85}
/* title sits just under the masthead, top-left */
.word{display:inline-block;overflow:hidden;vertical-align:top;
  padding-bottom:.14em;margin-bottom:-.14em}
.word > span{display:inline-block;will-change:transform,opacity,filter}

/* the action cluster sits hard bottom-right; the lede spreads full width
   underneath it, so the sentence reads as a line rather than a column */
.rating__stars{font-size:14px;letter-spacing:.18em;
  background:var(--brand-sweep);-webkit-background-clip:text;background-clip:text;color:transparent}
.rating__txt{font-family:var(--font-mono);font-size:11.5px;letter-spacing:.13em;
  text-transform:uppercase;color:var(--ink-muted)}
.rating__txt b{color:var(--ink);font-weight:500}
/* the highlight treatment: emphasised terms take the logo's own ramp.
   Background-clip text has no shadow, so it gets a soft plate of its own —
   without it the gradient dissolves into a bright video frame. */
.em{background:var(--brand-type);-webkit-background-clip:text;background-clip:text;
  color:transparent;font-weight:600;
  paint-order:stroke fill;
  -webkit-text-stroke:.45px rgba(0,0,0,.34)}
@supports not (background-clip:text){ .em{color:var(--accent);background:none} }
/* seven services will not sit on one line at every width — let the strip
   scroll itself rather than shrink the type or drop items */
/* seven services do not sit on one line at every width. Rather than shrink
   the type or drop items, the strip walks — slowly enough to read, and it
   stands still for anyone who asked for less motion. */
.hero__svc{flex:1 1 auto;min-width:0;overflow:hidden;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 3%,#000 92%,transparent);
          mask-image:linear-gradient(90deg,transparent,#000 3%,#000 92%,transparent)}
.hero__svcTrack{display:flex;width:max-content;animation:svcwalk 46s linear infinite}
.hero__svcTrack .mono{color:var(--ink-subtle);white-space:nowrap}
.hero__svc:hover .hero__svcTrack{animation-play-state:paused}
@keyframes svcwalk{from{transform:translateX(0)}to{transform:translateX(-50%)}}
@media (prefers-reduced-motion:reduce){.hero__svcTrack{animation:none}}

/* The gradient lives on .btn::before, parked at translateY(101%) until hover.
   An <a> is transparent by default so that reads fine, but a real <button>
   falls back to the UA's `buttonface` grey — white text on #efefef, 1.15:1.
   Every "Book now" button on the 24 post pages was effectively invisible. */
.btn{background:transparent;position:relative;display:inline-flex;align-items:center;justify-content:center;
  min-height:54px;padding:0 var(--sp-6);border:1px solid var(--line-accent);border-radius:var(--radius);
  font-family:var(--font-mono);font-size:16.5px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink);overflow:hidden;isolation:isolate;cursor:pointer;white-space:nowrap;
  transition:border-color var(--dur) var(--ease),color var(--dur) var(--ease)}
.btn::before{content:"";position:absolute;inset:0;z-index:-1;background:var(--brand-sweep);
  transform:translateY(101%);transition:transform var(--dur) var(--ease)}
.btn:hover{border-color:transparent;color:#fff}
.btn:hover::before{transform:translateY(0)}
.btn--ghost{border-color:var(--line-strong);color:var(--ink-muted)}
.btn--ghost:hover{color:var(--ink)}

.scrollcue{display:inline-flex;align-items:center;gap:var(--sp-3);flex:0 0 auto;
  white-space:nowrap;font-family:var(--font-mono);font-size:14px;
  letter-spacing:var(--tracking-label);text-transform:uppercase;color:var(--ink-faint)}
.scrollcue i{display:block;width:34px;height:1px;background:var(--line-strong);position:relative;overflow:hidden}
.scrollcue i::after{content:"";position:absolute;inset:0;background:var(--brand-sweep);
  animation:cue 2.4s var(--ease-in-out) infinite}
@keyframes cue{0%{transform:translateX(-100%)}55%,100%{transform:translateX(100%)}}

/* ── responsive ─────────────────────────────────────── */
@media (max-width:960px){
  .hero{height:calc(200svh / var(--z))}
  .hero__stage{padding:var(--sp-5)}
      .hero__btns .btn{width:100%}
      /* 删除:这是手机菜单出现之前的遗留 —— 它把首页除 CTA 外的所有导航项
         全部 display:none。手机抽屉接管 <=1100px 之后,这条会让抽屉整个是空的。
         我上一轮把压住它的 `display:block` 一起删了,首页导航就此变成空壳。 */
  .hero__links .is-cta{white-space:nowrap;font-size:14.5px}
  .brand__txt i{display:none}
  .brand__txt b{font-size:16.5px;white-space:nowrap}
      .hero__trio{gap:8px}
  .hero__trio span{font-size:14px;letter-spacing:.12em}
  .hero__trio i{width:14px}
    .hero__rules{grid-template-columns:repeat(3,1fr)}
  /* one column: the actions sit under the sentence rather than beside it */
          /* portrait puts the copy block far higher up the frame, into the bright
     part of the clip — so the base fade has to climb with it */
  .hero__basefade{height:50%;
    background:linear-gradient(180deg,transparent 0%,rgba(0,0,0,.42) 24%,
      rgba(0,0,0,.84) 58%,var(--bg) 96%)}
  .hero__topfade{height:44%}
  .hero__video{filter:saturate(.94) contrast(1.04) brightness(.96)}
  .col__body{font-size:18.5px}
}
@media (prefers-reduced-motion:reduce){
  .scrollcue i::after{animation:none}
  .hero__media{transform:none!important}
}

/* 2026-09-14 拦截已移除(客户要求),.hero__prog / .hero__progFill /
   .hero__skip / .is-holding 四条规则随之删掉 —— 它们只在拦截期间有意义,
   留着就是永远不显示的死样式。滚动催快视频的效果保留在 js/reel.js 里。 */

/* ── services — vexon-showcase skeleton ──────────────── */
.svc{position:relative;padding:clamp(var(--sp-8),9vw,var(--sp-10)) clamp(var(--sp-5),4vw,var(--sp-8));
  max-width:1560px;margin:0 auto}
.svc__head{display:grid;grid-template-columns:repeat(12,1fr);gap:var(--sp-6);
  margin-bottom:clamp(var(--sp-7),5vw,var(--sp-9))}
.svc__label{grid-column:1/span 3;color:var(--ink-faint);padding-top:.55em}
.svc__intro{grid-column:4/span 9}
.svc__h2{font-family:var(--font-display);font-weight:700;text-transform:uppercase;
  font-size:clamp(24px,3.1vw,50px);line-height:1.06;letter-spacing:var(--tracking-display);
  text-wrap:balance}
.svc__sub{margin-top:var(--sp-5);font-size:19.5px;color:var(--ink-muted);max-width:44ch}

.svc__grid{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(var(--sp-4),1.6vw,var(--sp-5))}

/* the stage owns the lens; the face turns inside it */
.card{position:relative;perspective:60rem;isolation:isolate}
.card__hit{position:absolute;inset:0;z-index:3;border-radius:var(--radius)}
.card__face{position:relative;transform-style:preserve-3d;
  display:flex;flex-direction:column;gap:var(--sp-4);
  min-height:clamp(300px,26vw,380px);padding:clamp(var(--sp-5),2vw,var(--sp-6));
  background:var(--surface);overflow:hidden;
  border:1px solid var(--line);border-radius:var(--radius);
  transition:border-color var(--dur) var(--ease)}
/* the accent arrives as light on the face, never as a fill */
.card__face::after{content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  background:radial-gradient(120% 90% at 18% 0%,rgba(255,0,128,.18),transparent 58%),
             radial-gradient(90% 70% at 100% 100%,rgba(138,0,176,.16),transparent 60%);
  opacity:0;transition:opacity var(--dur) var(--ease)}
.card:hover .card__face{border-color:var(--line-accent)}
.card:hover .card__face::after{opacity:1}

/* corner brackets converge on the card — artefakt's hover language:
   subtractive, never a filled rectangle */
.card__br{position:absolute;width:13px;height:13px;z-index:2;pointer-events:none;
  border:0 solid var(--accent);opacity:0;transition:opacity var(--dur) var(--ease),
  transform var(--dur) var(--ease)}
.card__br--tl{top:-1px;left:-1px;border-top-width:1px;border-left-width:1px;transform:translate(6px,6px)}
.card__br--tr{top:-1px;right:-1px;border-top-width:1px;border-right-width:1px;transform:translate(-6px,6px)}
.card__br--bl{bottom:-1px;left:-1px;border-bottom-width:1px;border-left-width:1px;transform:translate(6px,-6px)}
.card__br--br{bottom:-1px;right:-1px;border-bottom-width:1px;border-right-width:1px;transform:translate(-6px,-6px)}
.card:hover .card__br{opacity:1;transform:translate(0,0)}

/* the lift stands off the face at rest, so it does not appear to detach
   the moment the cursor arrives */
.card__lift{transform:translate3d(0,0,2.5rem);display:flex;flex-direction:column;gap:var(--sp-3)}
.card__idx{color:var(--ink-faint);letter-spacing:.2em}
.card__title{font-family:var(--font-display);font-weight:700;text-transform:uppercase;
  font-size:clamp(20.5px,1.635vw,27.5px);line-height:1.14;letter-spacing:.02em}
.card__body{font-size:18.5px;line-height:1.6;color:var(--ink-subtle);flex:1}
.card__more{color:var(--ink-muted);display:inline-flex;align-items:center;gap:8px;
  transition:color var(--dur-fast) var(--ease)}
.card__more i{font-style:normal;display:inline-block;transition:transform var(--dur) var(--ease)}
.card:hover .card__more{color:var(--accent)}
.card:hover .card__more i{transform:translateX(5px)}
.card__hit:focus-visible{outline:2px solid var(--accent);outline-offset:3px}

@media (max-width:1100px){ .svc__grid{grid-template-columns:repeat(2,1fr)} }
@media (max-width:960px){
  .svc__head{grid-template-columns:1fr;gap:var(--sp-4)}
  .svc__label,.svc__intro{grid-column:1/-1}
  .svc__intro{max-width:none}
  .svc__h2{max-width:none}
}
@media (max-width:660px){ .svc__grid{grid-template-columns:1fr} }
@media (prefers-reduced-motion:reduce){
  .card__face,.card__lift{transform:none!important}
}

.svc__head,.svc__grid{position:relative;z-index:1}


/* each card carries the shop's own footage for that service. It sits still
   as a poster until you point at it — six autoplaying clips would cost more
   than they give, and the clip coming alive under the cursor is the reward
   for the hover the tilt already invited. */
.card__media{position:absolute;inset:0;border-radius:inherit;overflow:hidden;z-index:0}
.card__poster,.card__vid{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  filter:saturate(.82) contrast(1.08) brightness(.66);
  transition:opacity var(--dur-slow) var(--ease),filter var(--dur-slow) var(--ease),
             transform var(--dur-slow) var(--ease);
  transform:scale(1.04)}
.card__vid{opacity:0}
.card:hover .card__vid{opacity:1}
.card:hover .card__poster{opacity:0}
.card:hover .card__poster,.card:hover .card__vid{filter:saturate(1) contrast(1.06) brightness(.88);
  transform:scale(1)}
/* the plate has to stay dark enough for the copy at every frame */
.card__veil{position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(0,0,0,.82) 0%,rgba(0,0,0,.44) 46%,rgba(0,0,0,.86) 100%)}
.card__lift,.card__body,.card__more{position:relative;z-index:1}

/* ── page ground ────────────────────────────────────── */
.ground{position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden;background:var(--bg)}
.ground__light{position:absolute;inset:0;width:100%;height:100%;display:block}
/* hairline columns, the whole document long — structure you feel, not see */
.ground__rules{position:absolute;inset:0;max-width:1560px;margin:0 auto;
  padding:0 clamp(var(--sp-5),4vw,var(--sp-8));
  display:grid;grid-template-columns:repeat(6,1fr)}
.ground__rules i{border-left:1px solid rgba(255,255,255,.05)}
.ground__rules i:first-child{border-left:0}
/* one slow light, brand-tinted, that never resolves into a shape */
.ground__drift{position:absolute;left:50%;top:38%;width:calc(150vmax / var(--z));height:calc(150vmax / var(--z));
  margin:calc(-75vmax / var(--z)) 0 0 calc(-75vmax / var(--z));
  background:
    radial-gradient(closest-side,rgba(255,0,128,.14),transparent 72%) 32% 40%/62% 62% no-repeat,
    radial-gradient(closest-side,rgba(138,0,176,.12),transparent 74%) 70% 62%/58% 58% no-repeat;
  animation:drift 64s cubic-bezier(.45,0,.55,1) infinite alternate}
@keyframes drift{
  0%   {transform:translate3d(-4%,-3%,0) scale(1)}
  50%  {transform:translate3d(3%,2%,0) scale(1.08)}
  100% {transform:translate3d(-2%,4%,0) scale(1.02)}
}
/* grain: kills the banding a large flat black always shows on OLED */
.ground__grain{position:absolute;inset:-50%;
  background-image:url("../assets/grain.png");background-size:128px 128px;
  opacity:.028;animation:grainshift 5.6s steps(6) infinite}
@keyframes grainshift{
  0%{transform:translate(0,0)}      16%{transform:translate(-3%,2%)}
  33%{transform:translate(2%,-3%)}  50%{transform:translate(-2%,-2%)}
  66%{transform:translate(3%,1%)}   83%{transform:translate(-1%,3%)}
  100%{transform:translate(0,0)}
}
/* Every body child is lifted above the fixed ground. This rule is (0,1,1) and
   silently outranks a bare `.loader`/`.lnav`/`.bookm` (0,1,0), so anything that
   must be fixed is named here too — otherwise it lands in the flow and pushes
   the page down by its own height. That is exactly what made the hero appear
   in the wrong place and then jump once the curtain was removed. */
body > *:not(.ground){position:relative;z-index:1}
body > .loader{position:fixed}
@media (prefers-reduced-motion:reduce){
  .ground__drift,.ground__grain{animation:none}
}

/* ── proof feeds ────────────────────────────────────── */
.proof{position:relative;max-width:1560px;margin:0 auto;
  padding:clamp(var(--sp-8),8vw,var(--sp-10)) clamp(var(--sp-5),4vw,var(--sp-8)) clamp(var(--sp-8),9vw,var(--sp-10));
  display:flex;flex-direction:column;gap:clamp(var(--sp-8),7vw,var(--sp-10))}
.feed{display:grid;grid-template-columns:56px minmax(0,1fr);gap:clamp(var(--sp-4),2vw,var(--sp-6))}
.feed__rail{display:flex;flex-direction:column;align-items:center;gap:var(--sp-4);padding-top:.35em}
.feed__no{color:var(--ink-faint);letter-spacing:.2em}
.feed__line{flex:1;width:1px;background:linear-gradient(180deg,var(--line-accent),transparent 78%)}
.feed__head{display:flex;align-items:flex-end;justify-content:space-between;gap:var(--sp-6);
  padding-bottom:var(--sp-5);margin-bottom:var(--sp-5);border-bottom:1px solid var(--line)}
.feed__h2{font-family:var(--font-display);font-weight:700;text-transform:uppercase;
  font-size:clamp(19px,2.2vw,34px);line-height:1.1;letter-spacing:var(--tracking-display);
  text-wrap:balance;max-width:22ch}
.feed__stat{display:flex;align-items:flex-end;gap:var(--sp-4);flex:0 0 auto}
.feed__big{font-family:var(--font-display);font-weight:700;font-size:clamp(34px,4.4vw,62px);
  line-height:.86;letter-spacing:-.01em;
  background:var(--brand-type);-webkit-background-clip:text;background-clip:text;color:transparent}
.feed__meta{color:var(--ink-subtle);text-align:right;line-height:1.7;letter-spacing:.13em}
/* the embed keeps its own look; the frame is ours */
.feed__frame{position:relative;padding:clamp(var(--sp-4),1.6vw,var(--sp-5));
  border:1px solid var(--line);border-radius:var(--radius);
  background:linear-gradient(180deg,rgba(255,255,255,.022),rgba(255,255,255,0) 46%),var(--surface);
  min-height:200px}
.fbr{position:absolute;width:14px;height:14px;pointer-events:none;border:0 solid var(--accent);opacity:.72}
.fbr--tl{top:-1px;left:-1px;border-top-width:1px;border-left-width:1px}
.fbr--tr{top:-1px;right:-1px;border-top-width:1px;border-right-width:1px}
.fbr--bl{bottom:-1px;left:-1px;border-bottom-width:1px;border-left-width:1px}
.fbr--br{bottom:-1px;right:-1px;border-bottom-width:1px;border-right-width:1px}
@media (max-width:820px){
  .feed{grid-template-columns:1fr;gap:var(--sp-4)}
  .feed__rail{flex-direction:row;align-items:center;padding-top:0}
  .feed__line{width:auto;height:1px;flex:1;
    background:linear-gradient(90deg,var(--line-accent),transparent 78%)}
  .feed__head{flex-direction:column;align-items:flex-start;gap:var(--sp-4)}
  .feed__stat{align-items:center}
  .feed__meta{text-align:left}
}

/* ── quote ──────────────────────────────────────────────────────────────
   THE COUNTER.

   Everything else on this page floats on the raking light. The contact
   section does the opposite: the page settles onto one full-bleed machined
   surface, the light fades out across its leading edge, and copy + form
   share that one face. Three problems fall out at once —

   1. contrast. .em is filled with --brand-type via background-clip:text, and
      its darkest stop (#ff0080) needs a ground no lighter than ~L 0.0118 to
      clear 4.5:1. The WebGL shaft (js/raking.js, colorD #ffd9ec) can put a
      near-white core anywhere in the viewport at any point in its cycle, so
      no amount of tuning the glow fixes this — the ground under the type has
      to be *deterministic*. .quote__plate makes it so.
   2. the rejected scrim. That was a soft radial behind the left column only:
      a shape, so it read as a blob. This plate has no shape — it is the full
      width of the section (the section is full-bleed now; .quote__inner
      carries the 1560 column), it starts inside the EMPTY bottom padding of
      the section above, and it ramps out through the section's own bottom
      padding. No left edge, no right edge, no top step, no bottom step.
   3. two unrelated slabs. The form card is gone. On a plate, a second dark
      box is just another rectangle — so the form becomes milled slots on the
      shared face, a lit hairline runs the seam between the columns, and the
      shop's facts are stamped across the full width along the bottom. One
      object, three zones.
   ────────────────────────────────────────────────────────────────────── */

/* How far the surface reaches up into the previous section. Must stay under
   .proof's bottom padding (clamp(--sp-8,9vw,--sp-10) = 64→128px) so the ramp
   lives entirely in empty space and never dims the last review card. */
.quote{--lead-in:clamp(56px,7.5vw,118px);
  /* --tail is BOTH the section's bottom padding and the length of the plate's
     fade-out, so the ramp lives entirely in empty space. Getting these out of
     sync is what put the facts strip inside the fade, where its 15.5px mono
     labels were sitting on live glow at 4.9:1. */
  --tail:clamp(var(--sp-9),11vw,168px);
  position:relative;max-width:none;margin:0;padding:0}

.quote__plate{position:absolute;left:0;right:0;bottom:0;
  top:calc(-1 * var(--lead-in));z-index:0;pointer-events:none;
  background:
  /* light caught on the leading edge. Painted here rather than left to the
     shader so it is always exactly at the rule and always dead by ~230px —
     i.e. it never reaches the .em words, which start at ~280px. */
  radial-gradient(112% 128px at 50% var(--lead-in),rgba(255,0,128,.16),transparent 72%),
  radial-gradient(52% 96px at 76% var(--lead-in),rgba(138,0,176,.20),transparent 76%),
  linear-gradient(180deg,
    rgba(0,0,0,0)     0,
    rgba(0,0,0,.30)   calc(var(--lead-in) * .46),
    rgba(0,0,0,.86)   var(--lead-in),
    rgba(0,0,0,.972)  calc(var(--lead-in) + 130px),
    rgba(0,0,0,.972)  calc(100% - var(--tail)),
    rgba(0,0,0,0)     100%)}

.quote__inner{position:relative;z-index:1;max-width:1560px;margin:0 auto;
  padding:clamp(var(--sp-7),5vw,var(--sp-9)) clamp(var(--sp-5),4vw,var(--sp-8)) var(--tail)}

/* .ground__rules is 6 columns inside the same 1560 box with the same padding,
   at 5% white — the plate swallows it, so it is redrawn here at a strength
   the surface can carry. Same geometry, so the lines continue the document's
   without a jog. */
.quote__rules{position:absolute;inset:0;z-index:-1;pointer-events:none;
  padding:0 clamp(var(--sp-5),4vw,var(--sp-8));
  display:grid;grid-template-columns:repeat(6,1fr)}
.quote__rules i{border-left:1px solid rgba(255,255,255,.055)}
.quote__rules i:first-child{border-left:0}

/* gap:0 + a symmetric pad on each half puts the column boundary at exactly
   50% of the content box — which is where .ground__rules' middle hairline
   already is. The seam is that hairline, lit. */
.quote__grid{position:relative;display:grid;
  grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  gap:0;align-items:stretch;--gutter:clamp(var(--sp-5),2.6vw,var(--sp-7))}
/* The leading edge, lit. Same hairline the section always had, but it now
   carries the brand across its middle — the one place in this section where
   the ramp is allowed to be a line instead of a falloff, because a rule is
   read as structure, not as the edge of a shape. (.feed__line already
   establishes the lit-hairline idiom on this page.) */
.quote__grid::before{content:"";position:absolute;left:0;right:0;top:0;height:1px;
  background:linear-gradient(90deg,var(--line) 0,var(--line-accent) 22%,
             rgba(255,77,163,.92) 50%,var(--line-accent) 78%,var(--line) 100%)}

.quote__lead{display:flex;flex-direction:column;min-width:0;
  padding:clamp(var(--sp-7),5vw,var(--sp-9)) var(--gutter) 0 0}
.quote__label{color:var(--ink-faint);margin-bottom:var(--sp-5)}
/* This is the page's one job, so the headline gets the page's second-largest
   voice. At 46px in a 728px column it was smaller than the review stats. */
.quote__h2{font-family:var(--font-display);font-weight:700;text-transform:uppercase;
  font-size:clamp(26px,4.1vw,66px);line-height:1.04;letter-spacing:var(--tracking-display)}
.quote__sub{margin-top:clamp(var(--sp-5),1.8vw,var(--sp-6));
  font-size:19.5px;line-height:1.65;color:var(--ink-muted);max-width:46ch}

/* Contact block — margin-top:auto drops it onto the same baseline as the
   bottom of the form, which is what locks the two columns together. */
.quote__reach{margin:auto 0 0;padding-top:clamp(var(--sp-7),4vw,var(--sp-9))}
.quote__reach dt{color:var(--ink-faint);margin-bottom:var(--sp-4)}
.quote__reach dd{margin:0;display:flex;flex-direction:column;align-items:flex-start;gap:var(--sp-3)}
.quote__tel{font-family:var(--font-display);font-weight:700;text-transform:uppercase;
  font-size:clamp(26px,2.3vw,38px);line-height:1;letter-spacing:var(--tracking-display);
  color:var(--ink);transition:color var(--dur-fast) var(--ease)}
.quote__mail{font-size:18.5px;color:var(--ink-muted);
  transition:color var(--dur-fast) var(--ease)}
.quote__tel:hover,.quote__mail:hover{color:var(--accent)}

/* The seam. One hairline down the gutter, full height of the first row —
   this is what turns "two columns" into "one face, divided". */
.quote__col{position:relative;display:flex;flex-direction:column;min-width:0;
  padding:clamp(var(--sp-7),5vw,var(--sp-9)) 0 0 var(--gutter)}
/* No brand colour at the head of the seam — a pink tick here crosses the lit
   top rule and reads as a stray "+" mark. The brand belongs to the leading
   edge; the seam is structure. */
.quote__col::before{content:"";position:absolute;left:0;top:0;bottom:0;width:1px;
  background:linear-gradient(180deg,var(--line-strong) 0,var(--line-strong) 22%,
             var(--line) 70%,transparent 100%)}

/* The spec plate along the bottom edge. */
.quote__facts{grid-column:1 / -1;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  gap:var(--sp-6) clamp(var(--sp-6),4vw,var(--sp-9));
  margin-top:clamp(var(--sp-7),4.5vw,var(--sp-9));
  padding-top:clamp(var(--sp-5),2.4vw,var(--sp-7));
  border-top:1px solid var(--line)}
.quote__facts dt{color:var(--ink-faint);margin-bottom:var(--sp-3)}
.quote__facts dd{margin:0;font-size:18.5px;line-height:1.65;color:var(--ink-muted)}

/* ── the form, as milled slots ──────────────────────────────────────────
   No card, no border, no fill: on the plate those would just rebuild the
   rectangle. Each field is a shallow channel — a bottom-weighted wash plus
   one rule — which is the same hairline language as .ground__rules and the
   seam above it. The rest-state rule is white @42% (3.7:1 on the plate), so
   the control boundary still clears 1.4.11 without a box.            */
.quote__form{display:flex;flex-direction:column;gap:clamp(var(--sp-4),1.6vw,var(--sp-5));
  padding:0;border:0;border-radius:0;background:none}
.qrow{display:grid;grid-template-columns:1fr 1fr;gap:clamp(var(--sp-4),1.6vw,var(--sp-5))}
.fld{display:flex;flex-direction:column;gap:9px}
.fld > span{color:var(--ink-faint)}
.fld input,.fld textarea{font:inherit;font-size:17.5px;color:var(--ink);
  border:0;border-radius:0;padding:10px 2px 12px;
  background:
    linear-gradient(90deg,rgba(255,255,255,.42),rgba(255,255,255,.42)) no-repeat left bottom/100% 1px,
    linear-gradient(180deg,rgba(255,255,255,0) 0,rgba(255,255,255,.055) 100%);
  transition:background-size var(--dur-fast) var(--ease),
             box-shadow var(--dur-fast) var(--ease)}
.fld input::placeholder,.fld textarea::placeholder{color:var(--ink-faint);opacity:.5}
.fld input:hover,.fld textarea:hover{
  background:
    linear-gradient(90deg,rgba(255,255,255,.62),rgba(255,255,255,.62)) no-repeat left bottom/100% 1px,
    linear-gradient(180deg,rgba(255,255,255,0) 0,rgba(255,255,255,.08) 100%)}
.fld input:focus,.fld textarea:focus{outline:none;
  background:
    var(--brand-sweep) no-repeat left bottom/100% 2px,
    linear-gradient(180deg,rgba(255,255,255,0) 0,rgba(255,0,128,.11) 100%);
  box-shadow:0 10px 26px -18px var(--glow)}
.fld textarea{resize:vertical;min-height:96px}

.fset{border:0;padding:0;margin:var(--sp-2) 0 0}
.fset legend{color:var(--ink-faint);margin-bottom:var(--sp-4);padding:0}
/* four chips, one row: the wrap that put "Other" on a line of its own cost
   52px of form height and made the column outrun the copy beside it */
.chips{display:flex;flex-wrap:wrap;gap:10px}
.chip{position:relative;cursor:pointer}
.chip input{position:absolute;opacity:0;width:0;height:0}
.chip span{display:inline-flex;align-items:center;gap:9px;min-height:40px;padding:0 14px;
  border:1px solid rgba(255,255,255,.40);border-radius:99px;font-size:16px;color:var(--ink-muted);
  transition:color var(--dur-fast) var(--ease),border-color var(--dur-fast) var(--ease),
             background var(--dur-fast) var(--ease)}
/* the state mark: checked is not signalled by fill alone */
.chip span::before{content:"";width:8px;height:8px;flex:0 0 8px;border-radius:1px;
  border:1px solid rgba(255,255,255,.55);
  transition:background var(--dur-fast) var(--ease),border-color var(--dur-fast) var(--ease)}
.chip:hover span{color:var(--ink);border-color:var(--line-strong)}
/* --brand-sweep-fill, not --brand-sweep: the raw ramp opens on #ff0000 and
   measured 3.72:1 under this white label. See tokens.css. */
.chip input:checked + span{color:#fff;border-color:transparent;background:var(--brand-sweep-fill)}
.chip input:checked + span::before{background:#fff;border-color:#fff}
.chip input:focus-visible + span{outline:2px solid var(--accent);outline-offset:3px}

.qfoot{display:flex;align-items:center;gap:var(--sp-5);flex-wrap:wrap;
  margin-top:clamp(var(--sp-4),1.6vw,var(--sp-5))}
/* same fix for this section's primary CTA (measured 3.69:1 on the raw ramp).
   Scoped to .quote on purpose — .btn::before is shared by 45 pages. */
.quote .btn--solid::before{background:var(--brand-sweep-fill)}
.btn--solid{border-color:transparent}
.btn--solid::before{transform:translateY(0)}
.btn--solid:hover::before{transform:translateY(0);filter:brightness(1.14)}
.qnote{color:var(--ink-faint);transition:color var(--dur-fast) var(--ease)}
/* 提交失败时的提示 —— 用品牌洋红而不是纯红,纯红在黑底上对比度只有 4.0 */
.qnote.is-bad{color:#ff5fa2}

@media (max-width:960px){
  /* one column: the seam lies down and becomes the rule above the form */
  .quote__grid{grid-template-columns:1fr}
  /* padding-right was the left half of the gutter; stacked, it just indents */
  .quote__lead{padding:clamp(var(--sp-6),5vw,var(--sp-7)) 0 clamp(var(--sp-7),6vw,var(--sp-8))}
  .quote__reach{margin-top:0;padding-top:clamp(var(--sp-6),4vw,var(--sp-7))}
  .quote__col{padding:clamp(var(--sp-7),6vw,var(--sp-8)) 0 0}
  .quote__col::before{left:0;right:0;top:0;bottom:auto;width:auto;height:1px;
    background:linear-gradient(90deg,var(--line-strong) 0,var(--line) 26%,transparent 100%)}
  .quote__facts{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (max-width:560px){
  .qrow{grid-template-columns:1fr}
  .quote__facts{grid-template-columns:1fr}
}

/* ── certifications ─────────────────────────────────── */
/* Their two installer brands, straight off their own pages. Brand marks are
   never recoloured — both already ship on transparent and are drawn for a
   dark ground, so they only need sizing to equal optical weight (the NXTZEN
   wordmark is wide and short, the UltraFit lockup is stacked). */
@media (max-width:960px){
        }

/* ── the founder — a pinned stage the next section covers ──────
   From GetLayers `lando`: "each block pins at the top of the viewport
   and the next comes out over it while the covered one recedes to 0.9
   and darkens by 55%." The recede is what stops it reading as a bug. */
.stackwrap{position:relative}
.stack__pin{position:sticky;top:0;height:calc(100svh / var(--z));display:flex;align-items:center;
  transform-origin:50% 30%;will-change:transform,filter}
/* opaque where it overlaps the pinned founder while sliding over him, then
   clears out so the raking light shows through the rest of the section */
.stack__over{position:relative;z-index:2;
  background:radial-gradient(120% 60% at 14% 0%,rgba(138,0,176,.20),rgba(255,0,128,.06) 40%,transparent 70%),
    linear-gradient(180deg,var(--bg) 0,var(--bg) calc(60svh / var(--z)),transparent calc(130svh / var(--z)));
  box-shadow:0 -40px 80px -20px rgba(0,0,0,.9)}
.svc__rules{position:absolute;inset:0;z-index:0;max-width:1560px;margin:0 auto;
  padding:0 clamp(var(--sp-5),4vw,var(--sp-8));display:grid;grid-template-columns:repeat(6,1fr);pointer-events:none}
.svc__rules i{border-left:1px solid rgba(255,255,255,.05)}
.svc__rules i:first-child{border-left:0}
.svc__head,.svc__grid{position:relative;z-index:1}

/* ::before 那块暗底 inset 是负的百分比,右侧探出 84px —— 它比视口宽,
   于是首页在 1440 上真的能横向滚 27px。body 的 overflow-x:hidden 挡不住
   (溢出从 html 逃出去),验收脚本也因此一直是绿的。暗底边缘早就透明了,
   在这里裁掉看不出来。用 clip 不用 hidden:hidden 会让它变成滚动容器。 */
.mission{overflow-x:clip;max-width:1560px;margin:0 auto;width:100%;
  padding:0 clamp(var(--sp-5),4vw,var(--sp-8));
  display:grid;grid-template-columns:86px clamp(300px,min(43vw,calc(70svh / var(--z))),880px) minmax(0,1fr);
  align-items:center;gap:clamp(var(--sp-5),3.2vw,var(--sp-7))}
/* Jesse's own portrait — a background image on their site, so the DOM sweep
   missed it. Framed rather than cropped to a circle: the shop's work is
   physical, and so is he. */
.mission__fig{position:relative;margin:0}
.mission__frame{position:relative;overflow:hidden;border-radius:var(--radius);
  border:1px solid var(--line);aspect-ratio:4/5;background:var(--surface)}
.mission__img{width:100%;height:118%;object-fit:cover;object-position:50% 32%;
  display:block;filter:saturate(.86) contrast(1.06);
  transform:translate3d(0,var(--py,-7%),0);will-change:transform}
.mission__sheen{position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(196deg,transparent 42%,rgba(255,0,128,.10) 78%,rgba(138,0,176,.16) 100%),
    linear-gradient(180deg,transparent 58%,rgba(0,0,0,.55) 100%)}
.mission__br{position:absolute;width:16px;height:16px;pointer-events:none;
  border:1px solid var(--accent);opacity:.55}
.mission__br--tl{top:-1px;left:-1px;border-right:0;border-bottom:0}
.mission__br--br{bottom:-1px;right:-1px;border-left:0;border-top:0}
.mission__fig figcaption{margin-top:var(--sp-4);font-size:15.5px;
  letter-spacing:.16em;text-transform:uppercase;color:var(--ink-faint)}
.mission__rail{display:flex;flex-direction:column;align-items:flex-start;gap:var(--sp-4);
  color:var(--ink-faint);padding-top:.5em}
.mission__rail .mono{white-space:nowrap}   /* "PLANO, TX" 不能断成两行 */
.mission__line{width:1px;flex:1;min-height:90px;
  background:linear-gradient(180deg,var(--line-accent),transparent 80%)}
.mission__body{max-width:62ch}
.mission__h2{font-family:var(--font-display);font-weight:700;text-transform:uppercase;
  font-size:clamp(26px,3.6vw,58px);line-height:1.04;letter-spacing:var(--tracking-display);
  margin-bottom:clamp(var(--sp-5),2.4vw,var(--sp-6))}
.mission__lead{font-size:clamp(20.5px,1.962vw,29.5px);font-weight:500;line-height:1.55;
  color:var(--ink);margin-bottom:var(--sp-5)}
.mission__p{font-size:19.5px;line-height:1.65;color:var(--ink-muted);margin-bottom:var(--sp-4);max-width:58ch}
.mission__p a,.mission__lead a{color:var(--accent);text-decoration:underline;
  text-underline-offset:3px;text-decoration-thickness:1px;
  transition:color var(--dur-fast) var(--ease)}
.mission__p a:hover{color:var(--accent-quiet)}
.mission__sig{font-family:var(--font-mono);font-size:16px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-subtle);margin:var(--sp-5) 0 var(--sp-6)}
.mission__cta{display:flex;gap:var(--sp-3);flex-wrap:wrap}
@media (max-width:900px){
  .stack__pin{position:static;height:auto;padding:clamp(var(--sp-8),12vw,var(--sp-9)) 0}
  .stack__over{background:none}
  .mission{grid-template-columns:1fr;gap:var(--sp-5)}
  .mission__frame{aspect-ratio:16/10}
  .mission__img{object-position:50% 26%;height:100%;transform:none}
  .mission__rail{flex-direction:row;align-items:center}
  .mission__line{width:auto;height:1px;min-height:0;flex:1;
    background:linear-gradient(90deg,var(--line-accent),transparent 80%)}
}

/* ── tesla ──────────────────────────────────────────── */
.tesla{position:relative;max-width:1560px;margin:0 auto;
  padding:clamp(var(--sp-8),8vw,var(--sp-10)) clamp(var(--sp-5),4vw,var(--sp-8))}
.tesla__inner{position:relative;overflow:hidden;text-align:center;
  padding:clamp(var(--sp-7),6vw,var(--sp-9)) clamp(var(--sp-5),4vw,var(--sp-8));
  border:1px solid var(--line);border-radius:var(--radius);
  background:linear-gradient(180deg,rgba(255,255,255,.03),transparent 52%),var(--surface)}
.tesla__glow{position:absolute;left:50%;top:-60%;width:min(1100px,120%);aspect-ratio:1;
  transform:translateX(-50%);pointer-events:none;
  background:radial-gradient(closest-side,rgba(255,0,128,.20),rgba(138,0,176,.10) 46%,transparent 72%);
  filter:blur(50px)}
.tesla__label{position:relative;color:var(--ink-faint);margin-bottom:var(--sp-5)}
.tesla__h2{position:relative;font-family:var(--font-display);font-weight:700;
  text-transform:uppercase;font-size:clamp(26px,4.2vw,64px);line-height:1.04;
  letter-spacing:var(--tracking-display)}
.tesla__p{position:relative;margin:var(--sp-5) auto 0;max-width:52ch;
  font-size:clamp(19.5px,1.635vw,25px);line-height:1.6;color:var(--ink-muted)}
.tesla__cta{position:relative;margin-top:clamp(var(--sp-6),3vw,var(--sp-7))}

/* ── footer ─────────────────────────────────────────── */
/* The client's own footer runs eight services and seven cities as two tall
   stacked lists — on a phone that is nineteen rows of link before the
   copyright. Same links, same words: services run two-up, the cities become
   a wrapping chip row, and the certifications share the legal line. */
.foot{position:relative;max-width:1560px;margin:0 auto;
  padding:clamp(var(--sp-8),7vw,var(--sp-9)) clamp(var(--sp-5),4vw,var(--sp-8)) clamp(var(--sp-6),3vw,var(--sp-7));
  border-top:1px solid var(--line)}
.foot__top{display:flex;align-items:flex-end;justify-content:space-between;
  gap:var(--sp-6);flex-wrap:wrap;
  padding-bottom:clamp(var(--sp-6),3vw,var(--sp-7));margin-bottom:clamp(var(--sp-6),3vw,var(--sp-7));
  border-bottom:1px solid var(--line)}
.foot__id{display:flex;flex-direction:column;gap:var(--sp-4);min-width:0}
.foot__brand img{width:clamp(180px,17vw,260px);height:auto;opacity:.92}
.foot__tag{max-width:40ch;font-size:18.5px;line-height:1.55;color:var(--ink-subtle)}
.foot__cta{flex:0 0 auto}

.foot__cols{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,1fr);
  gap:clamp(var(--sp-6),4vw,var(--sp-8))}
.foot__col h3{color:var(--ink-faint);margin-bottom:var(--sp-4);font-weight:400}
.foot__col a{font-size:18px;color:var(--ink-muted);
  transition:color var(--dur-fast) var(--ease)}
.foot__col a:hover{color:var(--accent)}
/* eight links, four rows */
.foot__two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:10px clamp(var(--sp-4),2vw,var(--sp-6))}
.foot__addr{display:flex;flex-direction:column;gap:9px;font-style:normal;
  font-size:18px;line-height:1.5;color:var(--ink-muted)}
.foot__addr a{font-size:18px}
/* the shop's own footer marks them as glyphs on white discs, not as a row of
   words — same three networks, same order, read at a glance. */
.foot__social{display:flex;gap:14px;margin-top:var(--sp-5);flex-wrap:wrap}
.foot__social a{display:grid;place-items:center;width:46px;height:46px;
  border-radius:50%;background:#fff;color:var(--accent);
  transition:transform .32s cubic-bezier(.22,1,.36,1),box-shadow .32s ease,color .32s ease}
.foot__social svg{display:block;width:22px;height:22px}
.foot__social a:hover,.foot__social a:focus-visible{
  transform:translateY(-3px);color:#c8006c;box-shadow:0 12px 28px -12px var(--glow)}

/* seven cities as one wrapping row instead of seven rows */
.foot__areas{margin-top:clamp(var(--sp-6),3.4vw,var(--sp-7));
  padding-top:clamp(var(--sp-5),2.4vw,var(--sp-6));border-top:1px solid var(--line);
  display:flex;align-items:baseline;gap:var(--sp-5) clamp(var(--sp-5),3vw,var(--sp-6));flex-wrap:wrap}
.foot__areas h3{color:var(--ink-faint);font-weight:400;flex:0 0 auto}
.foot__chips{display:flex;flex-wrap:wrap;gap:8px 10px;min-width:0}
.foot__chips a{display:block;padding:7px 13px;border:1px solid var(--line);border-radius:var(--radius);
  font-size:16px;color:var(--ink-muted);
  transition:color var(--dur-fast) var(--ease),border-color var(--dur-fast) var(--ease)}
.foot__chips a:hover{color:var(--ink);border-color:var(--line-accent)}

.foot__base{display:flex;align-items:center;justify-content:space-between;
  gap:var(--sp-5) clamp(var(--sp-5),3vw,var(--sp-6));flex-wrap:wrap;
  margin-top:clamp(var(--sp-6),3.4vw,var(--sp-7));
  padding-top:clamp(var(--sp-5),2.2vw,var(--sp-6));border-top:1px solid var(--line)}
.foot__marks{display:flex;align-items:center;gap:clamp(var(--sp-4),2vw,var(--sp-5))}
.foot__marks .mono{color:var(--ink-faint);white-space:nowrap}
.foot__marks img{width:auto;opacity:.75}
.foot__marks img:first-of-type{height:15px}
.foot__marks img:last-of-type{height:29px}
.foot__legal{display:flex;align-items:center;gap:var(--sp-5);flex-wrap:wrap}
.foot__legal .mono{color:var(--ink-faint)}
.foot__legal a:hover{color:var(--accent)}

@media (max-width:760px){
  .foot{padding-top:clamp(var(--sp-7),12vw,var(--sp-8))}
  .foot__top{gap:var(--sp-5)}
  .foot__cta{width:100%}
  .foot__cols{grid-template-columns:1fr;gap:var(--sp-6)}
  .foot__areas{flex-direction:column;gap:var(--sp-4)}
  .foot__base{flex-direction:column;align-items:flex-start;gap:var(--sp-5)}
}
@media (max-width:339px){
  .foot__two{grid-template-columns:1fr}
}


/* ── hero — composition: lumora-hero ─────────────────────── */
.hero__grid{position:relative;z-index:2;align-self:center;
  display:grid;grid-template-columns:minmax(0,8fr) minmax(0,4fr);
  column-gap:clamp(var(--sp-6),4vw,var(--sp-9));align-items:center}
/* same as .lhero__foot::before in landing.css — a guaranteed ground under the
   homepage headline instead of whatever the hero clip is showing */
.hero__left{position:relative;isolation:isolate;display:flex;flex-direction:column;align-items:flex-start;
  gap:clamp(var(--sp-4),1.6vw,var(--sp-5));min-width:0}
.hero__left::before{content:"";position:absolute;z-index:-1;
  inset:-42% -46% -46% -40%;pointer-events:none;
  /* 加强过一次:.hero__lede 里的渐变词组压在自动播放的 hero 视频上,
     实测只有 2.94-3.67:1。核心提到 .94 之后测得 5.4+,和报价区同一档。
     视频仍然透得出来 —— 压暗的是文字所在的那一小片,不是整帧。 */
  background:radial-gradient(66% 58% at 40% 52%,rgba(0,0,0,.94) 0%,
    rgba(0,0,0,.9) 32%, rgba(0,0,0,.72) 52%,
    rgba(0,0,0,.36) 72%, rgba(0,0,0,.12) 84%, transparent 94%)}
.hero__h1{font-family:var(--font-display);font-weight:700;text-transform:uppercase;
  font-size:clamp(30px,min(4.9vw,calc(8.6svh / var(--z))),78px);line-height:1.02;letter-spacing:var(--tracking-display);
  max-width:16ch;text-wrap:balance;
  padding-bottom:.14em;margin:0 0 -.14em;
  text-shadow:0 2px 30px rgba(0,0,0,.7)}
.rating{display:flex;align-items:center;gap:var(--sp-3);flex-wrap:wrap}
.hero__btns{display:flex;gap:var(--sp-3);flex-wrap:wrap}

.hero__right{display:flex;flex-direction:column;align-items:flex-end;justify-self:end;
  width:100%;max-width:440px;gap:clamp(var(--sp-5),2vw,var(--sp-6))}
/* the small translucent panel from the skeleton — it carries their own
   introduction, the one sentence the site opens with */
.hero__card{width:100%;padding:clamp(18px,1.5vw,26px);
  border:1px solid rgba(255,255,255,.13);border-radius:var(--radius);
  background:linear-gradient(160deg,rgba(255,255,255,.05),rgba(255,255,255,0) 40%),rgba(8,3,10,.50);
  backdrop-filter:blur(16px) saturate(1.25);-webkit-backdrop-filter:blur(16px) saturate(1.25);
  box-shadow:0 24px 60px -24px rgba(0,0,0,.8),inset 0 1px 0 rgba(255,255,255,.07)}
.hero__lede{font-size:clamp(17.5px,1.199vw,19.5px);font-weight:500;line-height:1.62;
  color:var(--ink);letter-spacing:-.004em;margin:0}
.hero__cardFoot{display:flex;justify-content:space-between;gap:var(--sp-4);flex-wrap:wrap;
  margin-top:var(--sp-4);padding-top:var(--sp-4);border-top:1px solid rgba(255,255,255,.10);
  color:var(--ink-faint)}
.creds{display:flex;flex-direction:column;align-items:flex-end;gap:var(--sp-3)}
.creds__label{color:var(--ink-subtle);text-align:right;
  text-shadow:0 1px 10px rgba(0,0,0,.95),0 0 22px rgba(0,0,0,.8)}
/* 两个合作方各占一行(Jason 2026-09-10)—— 原来挤成一行,窄一点就折成
   "…DEALER · CERTIFIED / INSTALLER" 那种断在中间的样子。桌面和手机都是两行。 */
.creds__label b{display:block;font-weight:inherit;white-space:nowrap}
.creds__marks{display:flex;align-items:center;gap:clamp(var(--sp-4),1.6vw,var(--sp-6))}
.creds__mark{height:auto;width:auto;opacity:.92;filter:drop-shadow(0 2px 12px rgba(0,0,0,.9));
  transition:opacity var(--dur-fast) var(--ease)}
.creds__mark--nx{height:20px}
.creds__mark--uf{height:40px}
.creds:hover .creds__mark{opacity:1}

.hero__rail{position:relative;z-index:2;align-self:end;
  display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;
  gap:clamp(var(--sp-5),3vw,var(--sp-7));
  padding-top:clamp(var(--sp-4),1.4vw,var(--sp-5));border-top:1px solid var(--line)}
.hero__railAddr{color:var(--ink-faint);white-space:nowrap}

@media (max-width:960px){
  /* one column, and the stage is a normal block — a sticky stage taller than
     the viewport can never show its own bottom */
  .hero{height:auto}
  .hero__stage{position:relative;height:auto;min-height:calc(100svh / var(--z));
    grid-template-rows:auto 1fr auto;row-gap:var(--sp-5)}
  .hero__grid{grid-template-columns:1fr;row-gap:var(--sp-5)}
  .hero__left{gap:var(--sp-3)}
  .hero__btns{flex-direction:column;width:100%}
  .hero__btns .btn{width:100%}
  .hero__right{max-width:none;align-items:flex-start}
  .hero__card{padding:16px}
  .hero__lede{font-size:17.5px}
  .hero__cardFoot{display:none}
  .creds{flex-direction:row;align-items:center;gap:var(--sp-4);flex-wrap:wrap}
  .creds__label{text-align:left;font-size:13.5px}
  .creds__mark--nx{height:15px}
  .creds__mark--uf{height:30px}
  .hero__rail{grid-template-columns:minmax(0,1fr) auto}
  .hero__railAddr{display:none}
}
@media (max-height:700px) and (min-width:961px){
  .hero__cardFoot{display:none}
  .hero__lede{font-size:16.5px}
}


/* ── review pass — measured fixes ─────────────────────────── */
/* #2 the headline is the one display event on the page: an 8|4 split gives it
   the measure, and it is larger than every h2 below it */
.hero__h1{font-size:clamp(34px,min(5.2vw,calc(10svh / var(--z))),86px);line-height:.98;letter-spacing:-.012em;
  max-width:none;text-wrap:initial}
/* #5 one continuous ramp across the sentence — each emphasised term takes the
   slice of the gradient where it sits, instead of restarting the ramp per word */
.sweep{background:var(--brand-type);-webkit-background-clip:text;background-clip:text;text-shadow:none}
.sweep .em{background:none;color:transparent;-webkit-text-stroke:0;paint-order:normal}
/* #6 the shop's strongest proof at display scale */
.rating{align-items:baseline;gap:var(--sp-3)}
.rating__stars{font-size:19.5px;background:none;-webkit-background-clip:initial;background-clip:initial;
  color:var(--brand-red);letter-spacing:.08em}
.rating__txt b{font-family:var(--font-display);font-weight:700;font-size:clamp(22px,2.071vw,30.5px);line-height:1;
  background:var(--brand-type);-webkit-background-clip:text;background-clip:text;color:transparent;
  margin-right:.15em}
.rating__txt{font-size:16.5px;color:var(--ink-muted)}
/* #8 rhythm: services closes tighter, and the Tesla beat is the one full-bleed
   band on the page */
.svc{padding-bottom:clamp(var(--sp-7),4.5vw,var(--sp-8))}
.tesla{max-width:none;padding:0}
.tesla__inner{border-left:0;border-right:0;border-radius:0;
  padding-block:clamp(var(--sp-8),9vw,var(--sp-10))}
/* card footage takes the same grade as the hero, lighter */
.card__poster,.card__vid{filter:saturate(.7) contrast(1.08) brightness(.9)}
@media (max-width:960px){
  .hero__h1{font-size:clamp(34px,9.6vw,52px)}
  .rating__txt b{font-size:24px}
}


/* ── type pass: Tint Pros stack ─────────────────────────── */
.mono{font-family:var(--font-mono);font-weight:600}
.hero__h1{letter-spacing:-.005em;line-height:.94}
.mission__h2,.svc__h2,.tesla__h2,.feed__h2,.quote__h2{letter-spacing:-.005em}
body{font-weight:400}

/* the masthead gets its own compositing layer: under a colour-blended sibling
   (the hero grade), software renderers dropped its non-positioned text */
/* The masthead sits above the hero's own copy layers, otherwise the services
   panel opens *behind* the translucent lede card (which is z-index 2 inside
   the same stage). isolate keeps the panel's z-index local, so the nav itself
   is what has to outrank the grid. */
.hero__nav{isolation:isolate;will-change:transform;z-index:30}
/* the links beside it carry this over the hero footage; the button should too */
.hero__nav .svcmenu__btn{text-shadow:0 1px 12px rgba(0,0,0,.75)}


/* ── size pass: nothing below 13px, body copy 17–18px, the Tesla button is the
   biggest control on the page ─────────────────────────────────────────── */
.tesla__cta{min-height:clamp(60px,5vw,72px);padding:0 clamp(var(--sp-7),4vw,64px);
  font-size:clamp(17.5px,1.417vw,20.5px);letter-spacing:.1em}
.foot__base .mono,.foot__legal .mono,.foot__marks .mono{font-size:14.5px}
.creds__label{font-size:14px}
.hero__cardFoot{font-size:14px}
.feed__meta{font-size:15.5px}
.card__cta,.mission__cta{font-size:16.5px}
@media (max-width:960px){
  .tesla__cta{width:100%;min-height:62px}
  .btn{min-height:54px}
}

/* ── footer, phone ───────────────────────────────────────────────────────
   Brand and button share one row, the tagline goes (it is the services
   line, already said three times above), Services runs two-up, Visit lays its
   four facts in a 2-col grid, the city chips wrap beside their label, and the
   base row is one line. ~700px instead of 1070px. */
@media (max-width:760px){
  .foot{padding:var(--sp-6) var(--sp-5) var(--sp-5)}
  .foot__top{flex-direction:row;flex-wrap:nowrap;align-items:center;justify-content:space-between;
    gap:var(--sp-4);padding-bottom:var(--sp-5);margin-bottom:var(--sp-5)}
  .foot__brand img{width:126px}
  .foot__tag{display:none}
  .foot__cta{width:auto;min-height:48px;padding:0 16px;font-size:14.5px}
  .foot__cols{grid-template-columns:1fr;gap:var(--sp-5)}
  .foot__col h3{margin-bottom:10px}
  .foot__col a{font-size:17px}
  .foot__two{grid-template-columns:1fr 1fr;gap:9px 12px}
  .foot__addr{display:grid;grid-template-columns:1fr 1fr;gap:8px 12px;font-size:17px;line-height:1.4}
  .foot__addr a{font-size:17px}
  .foot__addr a:first-child,.foot__addr span{grid-column:1/-1}
  .foot__addr a:nth-child(3){word-break:break-all}
  .foot__social{gap:12px;margin-top:var(--sp-3)}
  .foot__social a{width:44px;height:44px}
  .foot__social svg{width:21px;height:21px}
  .foot__areas{flex-direction:row;align-items:baseline;gap:var(--sp-3) var(--sp-4);
    margin-top:var(--sp-5);padding-top:var(--sp-5)}
  .foot__areas h3{font-size:14px}
  .foot__chips{gap:6px 8px}
  .foot__chips a{padding:6px 11px;font-size:14.5px}
  .foot__base{flex-direction:row;align-items:center;justify-content:space-between;gap:var(--sp-4);
    margin-top:var(--sp-5);padding-top:var(--sp-5)}
  .foot__marks{gap:var(--sp-3)}
  .foot__marks .mono{display:none}
  .foot__marks img:first-of-type{height:12px}
  .foot__marks img:last-of-type{height:24px}
  .foot__legal{gap:var(--sp-3)}
  .foot__base .mono,.foot__legal .mono{font-size:13px}
}
@media (max-width:339px){
  .foot__two,.foot__addr{grid-template-columns:1fr}
}

/* ── booking overlay — the shop's own Urable virtual shop ──────────────
   Opened by any [data-book]. The frame is light-themed and belongs to
   Urable, so it is set in a dark chrome with its own bar rather than
   pretending to be part of the page.                                   */
/* `body > *:not(.ground)` above forces every body child to position:relative —
   the overlay is appended to body, so it needs the same specificity to stay
   fixed. Without this it lands in flow at the end of the document. */
body > .bookm{position:fixed;inset:0;z-index:120;display:grid;place-items:center;
  padding:clamp(10px,2vw,28px)}
/* An author `display` beats the UA's [hidden]{display:none}, so closing the
   overlay left a transparent z-120 sheet over the whole page and every click
   on the site died silently. This is the rule that actually closes it. */
body > .bookm[hidden]{display:none}
/* the phone override has to match `body > .bookm` or it never applies —
   media queries add no specificity */
@media (max-width:760px){ body > .bookm{padding:0} }
.bookm__backdrop{position:absolute;inset:0;background:rgba(2,0,3,.78);
  backdrop-filter:blur(10px);opacity:0;transition:opacity var(--dur) var(--ease)}
.bookm.is-open .bookm__backdrop{opacity:1}
.bookm__panel{position:relative;display:flex;flex-direction:column;
  width:min(1180px,100%);height:min(860px,calc(92svh / var(--z)));overflow:hidden;
  border:1px solid var(--line-strong);border-radius:var(--radius);background:var(--surface);
  box-shadow:0 40px 120px rgba(0,0,0,.7),0 0 80px rgba(255,0,128,.16);
  opacity:0;transform:translateY(14px) scale(.985);
  transition:opacity var(--dur) var(--ease),transform var(--dur) var(--ease)}
.bookm.is-open .bookm__panel{opacity:1;transform:none}
.bookm__bar{display:flex;align-items:center;gap:var(--sp-4);flex:0 0 auto;
  padding:12px clamp(12px,1.6vw,20px);border-bottom:1px solid var(--line);
  background:linear-gradient(180deg,rgba(255,255,255,.05),transparent)}
.bookm__title{flex:1;min-width:0;color:var(--ink);white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis}
.bookm__out{flex:0 0 auto;color:var(--accent-quiet);letter-spacing:var(--tracking-label);
  text-transform:uppercase;white-space:nowrap}
.bookm__out:hover{color:var(--ink)}
.bookm__x{flex:0 0 auto;width:38px;height:38px;display:grid;place-items:center;
  background:none;border:1px solid var(--line);border-radius:var(--radius);
  color:var(--ink);font-size:26px;line-height:1;cursor:pointer;
  transition:border-color var(--dur-fast) var(--ease),color var(--dur-fast) var(--ease)}
.bookm__x:hover{border-color:var(--line-accent);color:var(--accent-quiet)}
.bookm__stage{position:relative;flex:1;min-height:0;background:#f4f4f6}
.bookm__frame{position:absolute;inset:0;width:100%;height:100%;border:0;
  opacity:0;transition:opacity var(--dur) var(--ease)}
.bookm.is-ready .bookm__frame{opacity:1}
.bookm__spin{position:absolute;left:50%;top:50%;width:34px;height:34px;translate:-50% -50%;
  border:2px solid rgba(0,0,0,.14);border-top-color:var(--accent);border-radius:50%;
  animation:bookspin .9s linear infinite}
.bookm.is-ready .bookm__spin{display:none}
@keyframes bookspin{to{transform:rotate(360deg)}}
@media (max-width:760px){
  .bookm__panel{width:100%;height:calc(100svh / var(--z));border:0;border-radius:0}
  .bookm__out{display:none}
}
@media (prefers-reduced-motion:reduce){
  .bookm__backdrop,.bookm__panel,.bookm__frame{transition:none}
  .bookm__spin{animation:none}
}
.foot__book{margin:var(--sp-4) 0 0;display:flex;gap:var(--sp-5);flex-wrap:wrap}
.foot__book a{font-family:var(--font-mono);font-weight:600;font-size:16.5px;
  letter-spacing:.12em;text-transform:uppercase;color:var(--accent-quiet)}
.foot__book a:hover{color:var(--ink)}

/* ── the founder block must fit one screen ─────────────────────────────
   It is a sticky pin at 100svh with the services section sliding over it.
   After the type pass its content ran 702px inside a 680px box on a 813px
   laptop, so the cover reached the two buttons while they were still the
   thing you were reading. The block is now tied to the viewport height the
   same way the hero is: it shrinks with the screen instead of spilling. */
@media (min-width:961px){
  .mission{align-content:center}
  .mission__h2{font-size:clamp(26px,min(3.4vw,calc(7.4svh / var(--z))),52px);line-height:1.02}
  .mission__lead{font-size:clamp(18.5px,min(1.766vw,calc(3.379svh / var(--z))),26px);line-height:1.45;
    margin-bottom:clamp(var(--sp-4),calc(2.2svh / var(--z)),var(--sp-5))}
  .mission__p{font-size:clamp(17.5px,min(1.352vw,calc(2.507svh / var(--z))),20.5px);line-height:1.55;
    margin-bottom:clamp(10px,calc(1.5svh / var(--z)),var(--sp-4))}
  .mission__sig{margin-top:clamp(10px,calc(1.6svh / var(--z)),var(--sp-5))}
  .mission__cta{margin-top:clamp(var(--sp-4),calc(2.4svh / var(--z)),var(--sp-6))}
  .mission__fig{align-self:center}
  .mission__frame{height:clamp(340px,calc(84svh / var(--z)),1000px)}
  .mission__img{height:118%;object-fit:cover}
}
/* the shortest laptops: tighten spacing and the plate, never hide a sentence */
@media (min-width:961px) and (max-height:720px){
  .mission{column-gap:clamp(var(--sp-5),3vw,var(--sp-7))}
  .mission__h2{font-size:clamp(24px,calc(6.6svh / var(--z)),40px);margin-bottom:clamp(10px,calc(2svh / var(--z)),var(--sp-5))}
  .mission__lead{font-size:clamp(17.5px,calc(3.107svh / var(--z)),22px);line-height:1.4}
  .mission__p{font-size:17px;line-height:1.5;margin-bottom:9px}
  .mission__sig{margin-top:9px;font-size:14px}
  .mission__cta{margin-top:clamp(12px,calc(2svh / var(--z)),var(--sp-5))}
  .mission__frame{height:clamp(260px,calc(54svh / var(--z)),440px)}
  .mission__fig figcaption{margin-top:10px;font-size:13.5px}
  .btn{min-height:46px}
}

/* 手机导航统一在下面的 ══ 手机抽屉 ══ 一段里,首页和落地页共用。 */

/* ══════════════════════════════════════════════════════════════════════
   Masthead behaviour shared by EVERY page. This lived in landing.css,
   which the homepage does not load — so the homepage rendered a native
   grey button and an already-open panel that shoved the nav apart.
   The markup and js/nav.js are shared, so the CSS has to be too.
   ══════════════════════════════════════════════════════════════════════ */
/* ── phone masthead ─────────────────────────────────────────────────────
   Below 1100 the section links were simply hidden, which left a phone
   visitor with a wordmark and one button and no way through the page.
   They now fold into a panel behind a control.                          */
.lnav__burger{display:none;flex:0 0 auto;width:44px;height:44px;
  align-items:center;justify-content:center;gap:6px;flex-direction:column;
  background:none;border:1px solid var(--line-strong);border-radius:var(--radius);
  cursor:pointer;transition:border-color var(--dur-fast) var(--ease)}
.lnav__burger:hover{border-color:var(--line-accent)}
.lnav__burger i{display:block;width:18px;height:1px;background:var(--ink);
  transition:transform var(--dur) var(--ease),opacity var(--dur) var(--ease)}
.lnav__burger[aria-expanded="true"] i:first-child{transform:translateY(3.5px) rotate(45deg)}
.lnav__burger[aria-expanded="true"] i:last-child{transform:translateY(-3.5px) rotate(-45deg)}

@media (max-width:1100px){ .lnav__burger{display:flex} }
@media (prefers-reduced-motion:reduce){
  .lnav__links{transition:none}
  .lnav__burger i{transition:none}
}


/* ══ services menu in the masthead ══════════════════════════════════════
   The client's own site puts every service behind one "Our services" item;
   this is the same idea, built as a real disclosure so it answers the
   keyboard and closes on Escape.                                        */
.svcmenu{position:relative}
.svcmenu__btn{display:inline-flex;align-items:center;gap:8px;background:none;border:0;
  cursor:pointer;color:var(--ink);font-family:var(--font-mono);font-weight:600;
  font-size:clamp(16px,1.145vw,18px);letter-spacing:.12em;text-transform:uppercase;
  padding:0;transition:color var(--dur-fast) var(--ease)}
.svcmenu__btn:hover{color:var(--accent-quiet)}
.svcmenu__btn i{display:block;width:8px;height:8px;border-right:1px solid currentColor;
  border-bottom:1px solid currentColor;transform:rotate(45deg) translate(-2px,-2px);
  transition:transform var(--dur) var(--ease)}
.svcmenu__btn[aria-expanded="true"] i{transform:rotate(225deg) translate(-3px,-3px)}
.svcmenu__panel{position:absolute;top:calc(100% + 14px);left:50%;translate:-50% 0;z-index:60;
  min-width:290px;margin:0;padding:8px;list-style:none;
  border:1px solid var(--line-strong);border-radius:var(--radius);
  background:rgba(6,3,8,.96);backdrop-filter:blur(18px) saturate(1.2);
  box-shadow:0 26px 70px rgba(0,0,0,.66),0 0 44px rgba(255,0,128,.14);
  opacity:0;visibility:hidden;transform:translateY(-6px);
  transition:opacity var(--dur) var(--ease),transform var(--dur) var(--ease),visibility 0s linear var(--dur)}
.svcmenu.is-open .svcmenu__panel{opacity:1;visibility:visible;transform:none;transition-delay:0s}
.svcmenu__panel a{display:block;padding:11px 14px;border-radius:var(--radius);
  font-family:var(--font-body);font-weight:500;font-size:17px;letter-spacing:0;
  text-transform:none;color:var(--ink);white-space:nowrap;
  transition:background var(--dur-fast) var(--ease),color var(--dur-fast) var(--ease)}
.svcmenu__panel a:hover,.svcmenu__panel a:focus-visible{background:rgba(255,255,255,.07);color:#fff}
.svcmenu__panel li + li{border-top:1px solid rgba(255,255,255,.06)}

@media (max-width:1100px){
  .svcmenu{width:100%}
  .svcmenu__btn{width:100%;justify-content:space-between;padding:15px 2px;font-size:16.5px}
  /* max-height:0 + overflow:hidden clips the panel but leaves its eight links
     focusable — a keyboard user tabbed into nothing eight times. Hiding has to
     be `visibility`, delayed on the way out so the collapse still animates. */
  .svcmenu__panel{position:static;translate:none;transform:none;min-width:0;width:100%;
    opacity:1;visibility:hidden;background:none;backdrop-filter:none;border:0;box-shadow:none;
    padding:0 0 0 var(--sp-4);max-height:0;overflow:hidden;
    transition:max-height var(--dur) var(--ease),visibility 0s linear var(--dur)}
  .svcmenu.is-open .svcmenu__panel{max-height:520px;visibility:visible;transition-delay:0s}
  .svcmenu__panel a{padding:12px 0;font-size:16.5px}
}

/* ══ the process (paint correction) ═════════════════════════════════════
   baseline-programs without the index numerals: an eyebrow and heading,
   then equal steps whose plates carry the picture and whose copy sits
   under a hairline. Order is real here, so the list is an <ol>.         */
.steps{position:relative;z-index:1;max-width:1560px;margin:0 auto;
  padding:clamp(56px,4.6vw,84px) clamp(var(--sp-5),4vw,var(--sp-8))}
.steps__head{display:grid;grid-template-columns:minmax(0,4fr) minmax(0,8fr);
  gap:clamp(var(--sp-5),3vw,var(--sp-7));align-items:start;
  margin-bottom:clamp(var(--sp-6),3.4vw,var(--sp-7))}
.steps__label{margin:0;color:var(--ink);letter-spacing:var(--tracking-label);
  text-transform:uppercase;padding-top:.5em}
.steps__h2{margin:0;font-family:var(--font-display);font-weight:700;text-transform:uppercase;
  font-size:clamp(28px,3.5vw,58px);line-height:1.04;letter-spacing:-.005em}
.steps__p{margin:var(--sp-5) 0 0;font-size:clamp(18.5px,1.439vw,22px);line-height:1.6;
  color:var(--ink);max-width:60ch}
.steps__list{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
  gap:clamp(var(--sp-5),2.4vw,var(--sp-6));margin:0;padding:0;list-style:none;counter-reset:none}
.step{display:flex;flex-direction:column;padding-top:clamp(var(--sp-4),1.8vw,var(--sp-5));
  border-top:1px solid var(--line)}
.step__media{position:relative;overflow:hidden;border-radius:var(--radius);aspect-ratio:3/2;background:#000;
  margin-bottom:clamp(var(--sp-4),1.8vw,var(--sp-5))}
.step__media img{width:100%;height:100%;object-fit:cover;display:block;
  filter:saturate(.8) contrast(1.08) brightness(.8);
  transition:transform var(--dur-slow) var(--ease),filter var(--dur) var(--ease)}
.step:hover .step__media img{transform:scale(1.04);filter:saturate(.95) brightness(.95)}
.step__t{margin:0;font-family:var(--font-display);font-weight:700;text-transform:uppercase;
  font-size:clamp(19.5px,1.635vw,26px);line-height:1.12;letter-spacing:.01em}
.step__p{margin:var(--sp-4) 0 0;font-size:clamp(17.5px,1.308vw,19.5px);line-height:1.55;color:var(--ink)}

@media (max-width:1100px){ .steps__list{grid-template-columns:repeat(2,minmax(0,1fr))} }
@media (max-width:960px){
  .steps__head{grid-template-columns:1fr;gap:var(--sp-4)}
  .steps__label{padding-top:0}
}
@media (max-width:560px){ .steps__list{grid-template-columns:1fr} }

/* two-up variants of the package grid and the card row */
.pkg__grid--two,.reasons__grid--two{grid-template-columns:repeat(2,minmax(0,1fr))}
.tier__note{margin:var(--sp-4) 0 0;font-size:clamp(17px,1.254vw,18.5px);line-height:1.5;
  color:var(--ink);font-style:italic}
.rcard__title b{display:block;margin-top:8px;font-family:var(--font-mono);font-weight:600;
  font-size:16px;letter-spacing:.12em;
  background:var(--brand-type);-webkit-background-clip:text;background-clip:text;color:transparent}
@supports not (background-clip:text){ .rcard__title b{color:var(--accent-quiet)} }
@media (max-width:860px){ .pkg__grid--two,.reasons__grid--two{grid-template-columns:1fr} }
.cpanel__plate--photo{padding:0;overflow:hidden;background:#000}
.cpanel__plate--photo img{width:100%;height:100%;object-fit:cover;filter:saturate(.8) contrast(1.06) brightness(.86)}
.cpanel__p{margin:clamp(var(--sp-5),2vw,var(--sp-6)) 0 0;font-size:clamp(18px,1.395vw,20.5px);
  line-height:1.55;color:var(--ink);max-width:56ch}
.cpanel__p + .cpanel__list{margin-top:var(--sp-4)}



/* ── skip link ─────────────────────────────────────────────────────────
   Every page puts 30–56 tab stops (masthead, services dropdown, footer nav)
   in front of <main>. Off-screen until focused, then a real, visible target. */
body > .skiplink,
.skiplink{position:fixed;top:0;left:50%;translate:-50% -120%;z-index:200;
  padding:14px 22px;border-radius:0 0 var(--radius) var(--radius);
  background:var(--bg);color:var(--ink);border:1px solid var(--line);border-top:0;
  font-family:var(--font-mono);font-size:16.5px;letter-spacing:var(--tracking-label);
  text-transform:uppercase;transition:translate .22s cubic-bezier(.22,1,.36,1)}
/* :focus, not :focus-visible — a skip link is reached only by keyboard, and
   :focus-visible does not match focus moved by script or by some ATs. */
.skiplink:focus{translate:-50% 0;outline:2px solid var(--accent-quiet);outline-offset:3px}
/* "Fields marked * are required" is the form column's own label — it sits on
   the same baseline as "/ Get an estimate today" across the seam, so it takes
   the same bottom margin. (The old +6px hack aligned it to the top border of
   the form card; there is no card any more.) */
.qreq{color:var(--ink-faint);margin:0 0 var(--sp-5)}

/* 表单的字 + 旁边那一栏的字加粗 */
.quote__sub{font-weight:500}
.quote__facts dt,.quote__reach dt{font-weight:700}
.quote__facts dd,.quote__mail{font-weight:500}
.quote__form .fld > span,
.quote__form .fset legend,
.qreq{font-weight:700}
.quote__form .fld input,
.quote__form .fld textarea{font-weight:500}
.quote__form .fld input::placeholder{font-weight:400}
.chip span{font-weight:600}
.qnote{font-weight:500}

/* ── quote: 光晕怎么压掉的(历史) ─────────────────────────────────
   .em 用 --brand-type 渐变填字,tokens.css 里那几个对比度(5.99/5.56/7.81)
   是「在 #000 上」量的。这一段背后是 js/raking.js 那块 fixed 的 WebGL 光轴
   (colorD #ffd9ec,亮核会飘到视口任意位置),实测这四个词曾是手机
   1.91/2.21/1.03/1.98:1、桌面 2.94/1.35/3.49/3.36:1。

   ✗ 旧解法是给 .quote__lead 加一个 ::before 径向暗块 —— 它是个「形状」,
     浮在光晕上就是一坨黑斑,客户连续否掉。已删除,别再加回来。
   ✓ 现在由 .quote__plate 解决:整段做成一个通栏表面,左右到视口边缘所以没有
     侧边、上沿的渐变起点落在上一段的空白 padding 里所以没有台阶、下沿在本段
     自己的 padding 里淡出所以没有底边。字下面的地面因此是确定值,不再取决于
     光轴此刻飘到哪儿。

   .quote__grid 的 overflow-x:clip 留着当兜底 —— 这一段现在没有负 inset 的
   伪元素了,但再有人往里塞东西时它还能挡住横向拖动。 */
.quote__grid{overflow-x:clip}

/* 只给读屏器的标签 —— 不能用 display:none,那样读屏器也读不到 */
.visually-hidden{position:absolute;width:1px;height:1px;margin:-1px;padding:0;
  overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}

/* ── 渐变文字的可读性兜底 ─────────────────────────────────────
   .em 用 --brand-type 填字,tokens.css 里那几个对比度是在 #000 上量的。
   实测在首页有两处压根不成立:
     .mission__lead 的 "Jesse"  1.34:1(桌面) / 1.90:1(手机) —— 背后是
       .ground__drift 那块 150vmax 的 fixed 动画色块;
     .hero__lede 的四个服务名   2.94-3.67:1 —— 背后是自动播放的 hero 视频。
   两种背景要两种解法。 */

/* founder:背景是可以压暗的(和 .quote__lead / .hero__left 同一套) */
.mission__body{position:relative}
.mission__body::before{display:none}

/* hero:视频背景。
   ✗ 试过给 .sweep .em 加 text-shadow 当暗色光晕 —— 失败并且更糟:
     .sweep 是父元素做 background-clip:text、子元素 .em 透明露出色带,
     黑色阴影从抗锯齿的细笔画里透出来,把整组字压成了暗红/暗紫,
     Jason 一眼就看出来了。渐变填字不能靠 text-shadow 提对比度。
   ✓ 正解还是压暗背景本身,和 .quote__lead / .mission__body 同一套
     (那两处实测 5.4-6.5:1)。见下方 .hero__left::before 的加强。 */

/* ══ 手机抽屉 ══════════════════════════════════════════════════════════
   旧版是把 <ul> 压成一个下拉面板:所有行同一个小号等宽字、细方框、✕ 挤在
   角落、BOOK NOW 只是又一行 —— Jason 的原话是"太丑了"。
   参考站(mankato / advncd-labs / vitora)是同一套做法:整屏覆盖、链接用
   display 字体放到很大、汉堡直接变 X、CTA 单独落在底部。这里照这个结构做,
   但用本站自己的语言(Epic Pro + 品牌光晕)。

   纯 CSS,不动 44 个页面的标记 —— 同一个 <ul> 在桌面是横排、在手机变全屏层。
   ══════════════════════════════════════════════════════════════════════ */
@media (max-width:1100px){

  /* 抽屉开着时锁住页面滚动,否则背后的页面会跟着划 */
  html:has(.lnav.is-open), html:has(.hero__nav.is-open){overflow:hidden}

  /* 品牌和汉堡要浮在覆盖层之上 —— 覆盖层是 .lnav 的子元素,
     默认会盖住同为子元素的它们。 */
  .lnav .brand,.hero__nav .brand{position:relative;z-index:3}
  .lnav__burger{position:relative;z-index:3;border-color:transparent;width:46px;height:46px}
  .lnav.is-open .lnav__burger,.hero__nav.is-open .lnav__burger{border-color:var(--line-strong)}
  .lnav__burger i{width:20px;transition:transform var(--dur) var(--ease),
                                        opacity var(--dur-fast) var(--ease)}

  /* `will-change:transform` 会让 .lnav / .hero__nav 成为 fixed 后代的包含块,
     那样 inset:0 只会铺满导航条本身(约 70px 高),而不是整屏。开的时候撤掉。 */
  .lnav.is-open,.hero__nav.is-open{will-change:auto;background:none;backdrop-filter:none;
    border-bottom-color:transparent}

  /* 上面那条是 (0,2,0),而 landing.css 里的 `.lnav.is-stuck` 也是 (0,2,0) 且加载在
     site.css 之后 —— 吸顶态下 backdrop-filter 被原样盖回去,于是 .lnav 又成了
     fixed 后代的包含块,抽屉的 inset:0 被裁成一条 122px 的缝(实测 gallery /
     blog / book-online / 7 个城市页一进来就是吸顶态;其余页面往下滚一屏也会变成
     吸顶态,手机菜单一样开不出来)。写到 (0,3,0) 才压得住。 */
  .lnav.is-stuck.is-open{background:none;backdrop-filter:none;-webkit-backdrop-filter:none;
    border-bottom-color:transparent}

  /* ── 覆盖层本体 ───────────────────────────────────────── */
  .lnav .lnav__links,.hero__nav .hero__links{
    position:fixed;inset:0;z-index:2;
    display:flex;flex-direction:column;justify-content:flex-start;align-items:stretch;
    gap:0;margin:0;list-style:none;
    padding:calc(96px + env(safe-area-inset-top)) clamp(20px,6vw,34px)
            calc(26px + env(safe-area-inset-bottom));
    overflow-y:auto;overscroll-behavior:contain;
    background:
      radial-gradient(118% 74% at 94% 2%, rgba(255,0,128,.17), transparent 56%),
      radial-gradient(104% 64% at 2% 98%, rgba(138,0,176,.20), transparent 60%),
      #050206;
    opacity:0;visibility:hidden;
    transition:opacity var(--dur) var(--ease),visibility 0s linear var(--dur);
    max-height:none}
  .lnav.is-open .lnav__links,.hero__nav.is-open .hero__links{
    opacity:1;visibility:visible;transition-delay:0s}

  /* ── 每一项 ───────────────────────────────────────────── */
  .lnav .lnav__links > li,.hero__nav .hero__links > li{
    display:block;margin:0;border:0;border-bottom:1px solid rgba(255,255,255,.11);
    opacity:0;translate:0 14px;
    transition:opacity .38s var(--ease),translate .38s var(--ease)}
  .lnav.is-open .lnav__links > li,.hero__nav.is-open .hero__links > li{
    opacity:1;translate:0 0}
  /* 逐条错开进场 —— 一次性全亮会像刷新,而不是像打开 */
  .lnav.is-open .lnav__links > li:nth-child(1),.hero__nav.is-open .hero__links > li:nth-child(1){transition-delay:.04s}
  .lnav.is-open .lnav__links > li:nth-child(2),.hero__nav.is-open .hero__links > li:nth-child(2){transition-delay:.08s}
  .lnav.is-open .lnav__links > li:nth-child(3),.hero__nav.is-open .hero__links > li:nth-child(3){transition-delay:.12s}
  .lnav.is-open .lnav__links > li:nth-child(4),.hero__nav.is-open .hero__links > li:nth-child(4){transition-delay:.16s}
  .lnav.is-open .lnav__links > li:nth-child(5),.hero__nav.is-open .hero__links > li:nth-child(5){transition-delay:.20s}
  .lnav.is-open .lnav__links > li:nth-child(6),.hero__nav.is-open .hero__links > li:nth-child(6){transition-delay:.24s}
  .lnav.is-open .lnav__links > li:nth-child(7),.hero__nav.is-open .hero__links > li:nth-child(7){transition-delay:.28s}

  /* 链接本身:display 字体、大号 */
  .lnav .lnav__links > li > a,.hero__nav .hero__links > li > a,
  .lnav .lnav__links .svcmenu__btn,.hero__nav .hero__links .svcmenu__btn{
    display:flex;align-items:center;justify-content:space-between;width:100%;
    font-family:var(--font-display);font-weight:700;text-transform:uppercase;
    font-size:clamp(25px,7.2vw,38px);line-height:1.04;letter-spacing:.015em;
    color:var(--ink);text-shadow:none;
    padding:clamp(13px,calc(2.1vh / var(--z)),20px) 0;
    transition:color var(--dur-fast) var(--ease)}
  .lnav__links > li > a:hover,.hero__links > li > a:hover{color:var(--accent-quiet)}

  /* CTA:落到底部,做成实心渐变按钮,而不是又一行文字 */
  .lnav .lnav__links > li:has(> .is-cta),.hero__nav .hero__links > li:has(> .is-cta){
    border-bottom:0;margin-top:auto;padding-top:clamp(var(--sp-5),calc(4vh / var(--z)),var(--sp-7))}
  .lnav .lnav__links .is-cta,.hero__nav .hero__links .is-cta{
    justify-content:center;border:0;border-radius:var(--radius);
    background:var(--brand-sweep);color:#fff;
    font-size:clamp(17px,4.4vw,21px);letter-spacing:.14em;
    padding:clamp(15px,calc(2.2vh / var(--z)),19px) 18px;margin:0;
    box-shadow:0 14px 44px rgba(255,0,128,.24)}

  /* ── 服务子菜单:缩进的小一号列表 ───────────────────────── */
  .lnav .lnav__links .svcmenu,.hero__nav .hero__links .svcmenu{width:100%}
  .lnav .lnav__links .svcmenu__btn i,.hero__nav .hero__links .svcmenu__btn i{
    width:11px;height:11px;flex:0 0 auto;opacity:.8}
  .lnav .lnav__links .svcmenu__panel,.hero__nav .hero__links .svcmenu__panel{
    padding:0 0 clamp(10px,calc(1.6vh / var(--z)),16px) 2px}
  .lnav .lnav__links .svcmenu__panel a,.hero__nav .hero__links .svcmenu__panel a{
    font-family:var(--font-mono);font-weight:600;font-size:16.5px;
    letter-spacing:.08em;text-transform:uppercase;color:var(--ink-muted);
    padding:11px 0;border-radius:0}
  .lnav .lnav__links .svcmenu__panel li + li,.hero__nav .hero__links .svcmenu__panel li + li{
    border-top:1px solid rgba(255,255,255,.06)}
  .lnav .lnav__links .svcmenu__panel a:hover,.hero__nav .hero__links .svcmenu__panel a:hover{
    background:none;color:var(--accent-quiet)}
}

@media (max-width:1100px) and (prefers-reduced-motion:reduce){
  .lnav .lnav__links > li,.hero__nav .hero__links > li{transition:none;opacity:1;translate:none}
}

/* 矮屏(横放的手机):字号让给行数,别把 BOOK NOW 顶出屏幕 */
@media (max-width:1100px) and (max-height:620px){
  .lnav .lnav__links > li > a,.hero__nav .hero__links > li > a,
  .lnav .lnav__links .svcmenu__btn,.hero__nav .hero__links .svcmenu__btn{
    font-size:clamp(19px,5vw,26px);padding:10px 0}
  .lnav .lnav__links,.hero__nav .hero__links{justify-content:flex-start;padding-top:calc(84px + env(safe-area-inset-top))}
}

/* ── 视频文字稿(可折叠) ──────────────────────────────────────────
   为什么要放在页面上而不是只塞进 JSON-LD:结构化数据应当反映页面上真实
   存在的内容;而且能被读取的正文才是 AI 搜索取材的对象。默认折叠,免得
   一段 3000 词的逐字稿把版面压垮。
   ⚠️ 内容是语音识别产物(已按客户官网写法校正过品牌词),不是手写文案。 */
.ts{margin:var(--sp-5) 0 0;border-top:1px solid var(--line)}
.ts__sum{list-style:none;cursor:pointer;padding:var(--sp-4) 30px var(--sp-4) 0;
  position:relative;font-size:13px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-muted);transition:color var(--dur-fast) var(--ease)}
.ts__sum::-webkit-details-marker{display:none}
.ts__sum:hover{color:var(--ink)}
.ts__sum::after{content:"";position:absolute;right:6px;top:50%;width:9px;height:9px;
  border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;
  translate:0 -70%;rotate:45deg;transition:rotate var(--dur) var(--ease)}
.ts[open] .ts__sum::after{rotate:225deg;translate:0 -20%}
.ts__body{padding:0 0 var(--sp-5);max-width:74ch;
  max-height:min(60svh, 520px);overflow-y:auto;overscroll-behavior:contain}
.ts__body p{margin:0 0 var(--sp-4);font-size:15.5px;line-height:1.72;
  color:var(--ink-faint)}
.ts__body p:last-child{margin-bottom:0}

/* 页脚的 LeadGend 署名走品牌渐变。
   不直接套 .em —— .em 上那条 .45px 暗色描边是给首屏大字压在亮视频上用的,
   拿到页脚 13px 的 mono 小字上只会糊成一团;这里底色是纯黑,不需要描边。
   渐变三个色标对黑底分别是 6.0 / 5.6 / 7.8:1,小字也读得清。
   ⚠ 特异度:.foot__legal .mono 已经把 color 设成 --ink-faint(0,2,0),
   .foot__legal a:hover 又是(0,2,1) —— 选择器写浅了 color:transparent 会被盖掉,
   渐变直接不显示/一 hover 就消失。所以这里必须写到 a.foot__by 这一级。 */
.foot__legal a.foot__by{background:var(--brand-type);
  -webkit-background-clip:text;background-clip:text;
  color:transparent;font-weight:700;letter-spacing:.02em}
/* clip 成文字之后 color 不可见,hover 只能靠亮度做反馈 */
.foot__legal a.foot__by:hover{color:transparent;filter:brightness(1.18)}
@supports not (background-clip:text){
  .foot__legal a.foot__by{color:var(--accent);background:none}
}

/* ── 5% off 弹窗 ───────────────────────────────────────────────────────
   复用站上已有的表单语言(.fld / .btn / .mono),不新造输入框样式 ——
   弹窗要长在这套设计里,不是贴一层别的皮。
   遮罩用纯色 + blur;注意 backdrop-filter 会给后代建立包含块,
   所以 .promo 自己不能是被 filter 的那一层的子元素(这里它是 body 直属)。 */
/* ⚠️ 必须写成 `body > .promo` —— 上面第 335 行的 `body > *:not(.ground)`
   是 (0,1,1),会把裸 `.promo` (0,1,0) 的 position:fixed 压成 relative,
   弹窗就被排到文档流最底下(实测 top=13318)。.loader 和 .bookm 早就踩过同一个坑。 */
body > .promo{position:fixed;inset:0;z-index:240;display:grid;place-items:center;
  padding:clamp(var(--sp-4),3vw,var(--sp-5));
  background:rgba(0,0,0,.72);backdrop-filter:blur(10px);
  opacity:0;transition:opacity var(--dur) var(--ease)}
.promo.is-open{opacity:1}
.promo__win{position:relative;width:min(470px,100%);
  max-height:calc(100svh - 2 * clamp(var(--sp-4),4vw,var(--sp-6)));
  overflow-y:auto;overscroll-behavior:contain;
  padding:clamp(var(--sp-5),3vw,var(--sp-6));
  background:linear-gradient(180deg,rgba(18,10,20,.96),rgba(6,3,8,.98));
  border:1px solid var(--line-accent);border-radius:var(--radius);
  box-shadow:0 40px 120px -40px var(--glow);
  translate:0 14px;transition:translate var(--dur) var(--ease)}
.promo.is-open .promo__win{translate:0 0}
.promo__x{position:absolute;top:10px;right:12px;z-index:2;
  width:40px;height:40px;display:grid;place-items:center;
  background:none;border:0;cursor:pointer;
  font-size:19px;line-height:1;color:var(--ink-faint);
  transition:color var(--dur-fast) var(--ease)}
.promo__x:hover{color:var(--ink)}
.promo__eyebrow{color:var(--ink-faint);margin:0 0 var(--sp-3);font-size:13px}
.promo__h2{margin:0;font-family:var(--font-display);font-weight:700;
  text-transform:uppercase;line-height:.96;letter-spacing:-.005em;
  font-size:clamp(25px,4.2vw,34px)}
.promo__sub{margin:var(--sp-3) 0 var(--sp-5);color:var(--ink-muted);
  max-width:40ch;font-size:15.5px;line-height:1.5}
.promo__form{display:flex;flex-direction:column;gap:var(--sp-4)}
.promo__row{display:grid;grid-template-columns:1fr 1fr;gap:var(--sp-4)}
.promo__form .btn{margin-top:var(--sp-2);width:100%;min-height:48px;font-size:15px}
.promo__note{margin:var(--sp-3) 0 0;font-size:13px;color:var(--ink-faint);
  transition:color var(--dur-fast) var(--ease)}
.promo__note.is-bad{color:#ff5fa2}
.promo__fine{margin:var(--sp-4) 0 0;font-size:13px;color:var(--ink-faint);opacity:.72}

/* 成功态:表单换成券码。用 [hidden] 会被 display:flex 盖掉,所以走类切换 */
.promo__done{display:none}
.promo__body.is-done .promo__ask{display:none}
.promo__body.is-done .promo__done{display:block}
.promo__code{display:inline-block;margin:var(--sp-5) 0 var(--sp-4);
  padding:14px 26px;border:1px dashed var(--line-accent);border-radius:var(--radius);
  font-family:var(--font-mono);font-size:clamp(19px,3.4vw,25px);
  font-weight:700;letter-spacing:.14em;
  background:var(--brand-type);-webkit-background-clip:text;background-clip:text;
  color:transparent}
@supports not (background-clip:text){ .promo__code{color:var(--accent);background:none} }
.promo__copy{margin-left:var(--sp-4);background:none;cursor:pointer;
  border:1px solid var(--line);border-radius:var(--radius);padding:9px 15px;
  font-family:var(--font-mono);font-size:13px;letter-spacing:.1em;
  text-transform:uppercase;color:var(--ink-muted);
  transition:color var(--dur-fast) var(--ease),border-color var(--dur-fast) var(--ease)}
.promo__copy:hover{color:var(--ink);border-color:var(--line-accent)}

/* 2026-09-15 线上事故:这里原本有一条
     html.promo-open,html.promo-open body{overflow:hidden}
   锁背景滚动。只要弹窗因为任何原因该关没关、或者被误触发,
   整站在手机上就彻底滑不动 —— 而且没有任何报错,用户只会直接离开。
   一个营销弹窗不值得拿"整站能不能用"去换。现在不锁了,背景照常滚。 */

@media (max-width:560px){
  .promo__row{grid-template-columns:1fr}
  .promo__win{padding:var(--sp-6) var(--sp-5)}
}

/* 弹窗里的字段比页面上的报价表单小一号 —— 那个是页面主表单,这个是插话 */
.promo__form .fld input{font-size:15.5px;padding:7px 2px 9px}
.promo__form .fld > span{font-size:12.5px}

/* 手机端再收一档 —— 弹窗是插话,不该占满整屏 */
@media (max-width:560px){
  body > .promo{padding:var(--sp-4);align-items:center}
  .promo__win{padding:var(--sp-5) var(--sp-4) var(--sp-5)}
  .promo__h2{font-size:22px}
  .promo__sub{font-size:14px;margin:10px 0 var(--sp-4)}
  .promo__eyebrow{font-size:12px;margin-bottom:8px}
  .promo__form{gap:13px}
  .promo__row{gap:13px}
  .promo__form .fld input{font-size:15px;padding:6px 2px 8px}
  .promo__form .fld > span{font-size:11.5px}
  .promo__form .btn{min-height:46px;font-size:14px;margin-top:2px}
  .promo__note{font-size:12px;margin-top:10px}
  .promo__x{width:36px;height:36px;top:6px;right:8px}
}

/* ⚠️ 2026-09-15 线上事故:手机端整站滑不动。
   原因:上面 `body > .promo{display:grid}` (0,2,0) 盖掉了 [hidden] 自带的
   display:none,弹窗那层 position:fixed;inset:0 的遮罩**一直盖在页面上**
   (opacity:0 看不见,但照样吃触摸)。桌面滚轮能穿透,手指不能 —— 所以只在手机上炸。
   必须用同等特异度把 [hidden] 真正还原成不显示。
   额外加 pointer-events:none 兜底:万一哪天又有人动 display,也不会再吃事件。 */
body > .promo[hidden]{display:none!important}
body > .promo:not(.is-open){pointer-events:none}
body > .promo.is-open{pointer-events:auto}
