/* ==========================================================================
   GPTECH — 吉派环境技术(浙江)有限公司
   Global stylesheet.  Brand indigo #313D95 sampled from the official logo.
   Drop Barlow / Archivo webfonts into assets/fonts and fill the @font-face
   block below to match the original comps exactly; the stack degrades
   gracefully to system faces until then.
   ========================================================================== */

/* ---------- tokens ---------- */
:root{
  /* brand */
  --brand:#313d95;            /* logo indigo */
  --brand-700:#28327c;
  --brand-500:#3f4db2;
  --blue:#1e5fd6;             /* action blue */
  --blue-600:#1a52ba;
  --blue-400:#4c8cf5;
  --cyan:#35c3f3;             /* telemetry / tech accent */

  /* neutrals */
  --ink-900:#050b1a;
  --ink-800:#0a1631;
  --ink-700:#10203f;
  --ink-600:#1b2c4f;
  --slate-700:#334155;
  --slate-500:#64748b;
  --slate-400:#94a3b8;
  --line:#e3e8ef;
  --line-soft:#eef1f6;
  --paper:#f4f6fa;
  --paper-2:#eaeef5;
  --white:#fff;

  /* type */
  --font:-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Helvetica,Arial,
         "PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",sans-serif;
  --font-display:var(--font);
  --font-mono:"SFMono-Regular",Consolas,"Liberation Mono",Menlo,monospace;

  /* metrics */
  --wrap:1240px;
  --gut:32px;
  --r-sm:4px;
  --r:8px;
  --r-lg:14px;
  --header-h:72px;
  --shadow-sm:0 1px 2px rgba(10,22,49,.06),0 2px 8px rgba(10,22,49,.05);
  --shadow:0 4px 14px rgba(10,22,49,.08),0 12px 32px rgba(10,22,49,.07);
  --shadow-lg:0 18px 48px rgba(10,22,49,.14);
  --ease:cubic-bezier(.22,.61,.36,1);

  /* arrow glyph reused as a CSS mask so it can inherit colour and animate
     without another inline SVG in the markup */
  --arrow:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 12h15'/%3E%3Cpath d='m13 6 6 6-6 6'/%3E%3C/svg%3E");
}

/* ---------- reset ---------- */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}
body{
  margin:0;background:var(--white);color:var(--ink-800);
  font-family:var(--font);font-size:16px;line-height:1.65;
  -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
}
/* The sideways guard lives on html, not body. Setting overflow-x on body makes
   the spec compute the other axis to `auto` as well, which turns body into a
   scroll container — and then every position:sticky in the page measures
   itself against a box that never scrolls (html is what actually moves), so
   nothing ever sticks. Same protection, on the element that is already the
   scrolling one. */
html{overflow-x:hidden}
img,svg,video{max-width:100%;height:auto;display:block}
a{color:inherit;text-decoration:none}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
input,textarea,select{font:inherit;color:inherit}
h1,h2,h3,h4,h5,p,figure,ul,ol{margin:0}
ul,ol{padding:0;list-style:none}
table{border-collapse:collapse;width:100%}
:focus-visible{outline:2px solid var(--blue);outline-offset:3px;border-radius:2px}
::selection{background:var(--brand);color:#fff}

/* ---------- layout ---------- */
.wrap{width:100%;max-width:var(--wrap);margin-inline:auto;padding-inline:var(--gut)}
.wrap-wide{width:100%;max-width:1480px;margin-inline:auto;padding-inline:var(--gut)}
.sec{padding-block:84px}
/* The products archive stacks one .sec per family; the generic 84+84 rhythm
   between them reads as a hole once a family is a single rail. */
.post-type-archive-gp_product .sec{padding-block:30px}
.sec-sm{padding-block:60px}
.sec-tight{padding-block:56px}
.bg-paper{background:var(--paper)}
.bg-ink{background:var(--ink-800);color:#fff}
.grid{display:grid;gap:24px}
.center{text-align:center}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0}
.skip{position:absolute;left:16px;top:-60px;z-index:200;background:var(--blue);color:#fff;
  padding:10px 18px;border-radius:var(--r);transition:top .2s}
.skip:focus{top:12px}

/* ---------- type ---------- */
.h-display{
  font-family:var(--font-display);font-weight:800;line-height:1.06;
  letter-spacing:-.022em;text-transform:uppercase;
  font-size:clamp(30px,4.4vw,52px);margin:0;
}
.h1{font-weight:800;font-size:clamp(28px,3.6vw,42px);line-height:1.12;letter-spacing:-.02em;text-transform:uppercase;text-wrap:balance}
.h2{font-weight:800;font-size:clamp(24px,2.7vw,32px);line-height:1.2;letter-spacing:-.015em;text-transform:uppercase;text-wrap:balance}
.h3{font-weight:700;font-size:19px;line-height:1.3;letter-spacing:-.01em}
.h4{font-weight:700;font-size:16px;line-height:1.35}
/* text-wrap:pretty asks the browser to look at the paragraph as a whole rather
   than filling one line at a time, which is what leaves a last line holding a
   single word — 「…not catalogue / averages.」 The rag gets evener too. It is a
   progressive enhancement: where it is not understood the text simply wraps
   the way it does today. */
.lede{font-size:17px;line-height:1.7;color:var(--slate-500);text-wrap:pretty}
.lede-light{font-size:17px;line-height:1.7;color:rgba(255,255,255,.78);text-wrap:pretty}
.muted{color:var(--slate-500)}
.eyebrow{font-size:12px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;color:var(--blue)}
.rule{width:52px;height:3px;background:var(--blue);border-radius:2px;margin-top:14px}
.rule-c{margin-inline:auto}
/* CJK reads better slightly looser and without forced caps */
:lang(zh) .h-display,:lang(zh) .h1,:lang(zh) .h2{text-transform:none;letter-spacing:.005em;line-height:1.22}
:lang(zh) body,:lang(zh) .lede{line-height:1.8}

.sec-head{margin-bottom:38px}
.sec-head .h2{margin:0}
.sec-head.center .rule{margin-inline:auto}

/* ---------- heading reveal, shared by .sec-head and .band-head ----------
   Instead of the whole block fading in, each line travels up out of its own
   clip, so the sub-line reads as being drawn out from under the heading.
   data-reveal stays on the container purely as the scroll trigger — the
   container itself no longer moves, or the two motions would fight. */
.sec-head[data-reveal],.band-head[data-reveal]{opacity:1;transform:none;transition:none}
.sec-head .h2,.sec-head .lede,.band-head .h2,.band-head p{overflow:hidden}
/* headings are uppercase Latin or Han, neither of which descends; the sub-line
   is sentence case, so it needs room under the baseline or g and y get shaved */
.sec-head .lede,.band-head p{padding-bottom:.2em}
.rise{
  display:block;transform:translateY(105%);
  transition:transform .8s cubic-bezier(.22,.61,.24,1);
}
.is-in .rise{transform:none}
/* the sub-line follows a beat later — a sequence, not two things arriving at
   the same moment */
.sec-head .lede .rise,.band-head p .rise{transition-delay:.18s}
/* the rule draws once the heading has landed */
.sec-head .rule{transform:scaleX(0);transform-origin:left;
  transition:transform .5s cubic-bezier(.22,.61,.24,1) .32s}
.sec-head.center .rule{transform-origin:center}
.sec-head.is-in .rule{transform:none}

@media (prefers-reduced-motion:reduce){
  .rise,.sec-head .rule{transform:none}
}

/* ---------- buttons ---------- */
.btn{
  display:inline-flex;align-items:center;gap:10px;
  padding:13px 24px;border-radius:var(--r-sm);
  font-size:14px;font-weight:700;letter-spacing:.03em;
  transition:background .18s var(--ease),color .18s,border-color .18s,transform .18s var(--ease),box-shadow .18s;
  white-space:nowrap;border:1.5px solid transparent;
}
.btn svg{width:15px;height:15px;flex:none;transition:transform .22s var(--ease)}
.btn:hover{transform:translateY(-2px)}
.btn:hover svg{transform:translateX(3px)}
.btn-primary{background:var(--blue);color:#fff;box-shadow:0 4px 14px rgba(30,95,214,.28)}
.btn-primary:hover{background:var(--blue-600);box-shadow:0 8px 24px rgba(30,95,214,.34)}
.btn-ghost{background:rgba(255,255,255,.9);color:var(--ink-800);border-color:rgba(255,255,255,.9)}
.btn-ghost:hover{background:#fff}
.btn-outline{border-color:var(--line);color:var(--ink-800);background:#fff}
.btn-outline:hover{border-color:var(--blue);color:var(--blue)}
.btn-outline-light{border-color:rgba(255,255,255,.45);color:#fff}
.btn-outline-light:hover{border-color:#fff;background:rgba(255,255,255,.1)}
.btn-white{background:#fff;color:var(--blue);box-shadow:var(--shadow-sm)}
.btn-white:hover{box-shadow:var(--shadow)}
.btn-sm{padding:10px 18px;font-size:13px}

.link-arrow{display:inline-flex;align-items:center;gap:7px;font-size:14px;font-weight:700;color:var(--blue)}
.link-arrow svg{width:14px;height:14px;transition:transform .22s var(--ease)}
.link-arrow:hover svg{transform:translateX(4px)}

/* circular arrow chip used on every image card */
.chip{
  width:34px;height:34px;border-radius:50%;flex:none;
  display:grid;place-items:center;
  border:1.5px solid rgba(255,255,255,.75);color:#fff;
  transition:background .2s,border-color .2s,transform .2s var(--ease);
}
.chip svg{width:13px;height:13px}
.chip-dark{border-color:var(--line);color:var(--slate-500)}

/* ==========================================================================
   HEADER
   ========================================================================== */
.header{
  position:sticky;top:0;z-index:100;background:#fff;
  border-bottom:1px solid var(--line-soft);
  transition:box-shadow .25s,background .25s;
}
.header.is-stuck{box-shadow:0 2px 18px rgba(10,22,49,.09)}
.header-in{display:flex;align-items:center;gap:26px;height:var(--header-h)}
.brand{display:flex;align-items:center;flex:none}
.brand img{height:26px;width:auto}
.nav{display:flex;align-items:center;gap:4px;margin-inline:auto}
.nav a{
  position:relative;padding:8px 14px;font-size:14.5px;font-weight:600;color:var(--ink-700);
  border-radius:var(--r-sm);transition:color .18s;
}
.nav a::after{
  content:"";position:absolute;left:14px;right:14px;bottom:1px;height:2px;
  background:var(--blue);border-radius:2px;transform:scaleX(0);transform-origin:left;
  transition:transform .24s var(--ease);
}
.nav a:hover{color:var(--blue)}
.nav a:hover::after,.nav a[aria-current="page"]::after{transform:scaleX(1)}
.nav a[aria-current="page"]{color:var(--blue)}

/* ---------- products mega menu ----------
   Drops a card over a dimmed page, so the whole catalogue is one hover away.
   The trigger stays a link: clicking it still opens the Products page. */
.nav-mega{display:inline-flex;align-items:center;gap:6px}
.nav-mega .cv{display:inline-flex;transition:transform .26s var(--ease)}
.nav-mega .cv svg{width:11px;height:11px}
.nav-mega[aria-expanded="true"]{color:var(--blue)}
.nav-mega[aria-expanded="true"]::after{transform:scaleX(1)}
.nav-mega[aria-expanded="true"] .cv{transform:rotate(180deg)}

.mega{
  position:absolute;left:0;right:0;top:100%;z-index:99;
  padding-top:10px;                     /* the gap the card floats below */
  opacity:0;visibility:hidden;
  transition:opacity .2s var(--ease),visibility 0s .24s;
}
/* visibility, not display: it also takes the links out of the tab order and
   the accessibility tree while closed, which display:none would do too but
   without letting the panel fade. */
.mega.is-open{opacity:1;visibility:visible;transition-delay:0s}
/* The page behind steps back, so the menu reads as a layer over the site
   rather than a strip attached to the header.
   pointer-events:none is load-bearing, not a tidy-up: this pseudo-element is a
   child box of .mega and covers the whole viewport, so while it accepts the
   pointer the cursor is *always* inside .mega and mouseleave can never fire —
   the menu would only ever close from a button. */
.mega::before{
  content:"";position:fixed;left:0;right:0;top:var(--header-h);bottom:0;
  background:rgba(8,16,38,.4);pointer-events:none;
}
.mega-card{
  position:relative;background:#fff;border-radius:14px;
  box-shadow:0 26px 58px rgba(10,22,49,.24);
  padding:36px 34px 38px;
  transform:translateY(-12px);
  transition:transform .3s var(--ease);
}
.mega.is-open .mega-card{transform:none}
/* The panel is an overlay, not part of the reading column, so it does not have
   to sit inside the 1240px --wrap. Five columns of English product names need
   more than that: at 1240 each got 199px and 「Containerised Energy Storage
   System」 wanted exactly 199, so most names broke to a second line. Widening
   to 1440 buys each column ~240px, which clears all but the longest two.
   min() keeps it inside the viewport on narrower desktops. */
.mega .wrap{max-width:min(1440px, calc(100vw - 48px))}
/* One track per family, plus the card that leads to the full catalogue. The
   count was hard-coded at three and a fourth family pushed that card onto a
   second row, so it is written from the data now: add a family and the menu
   still comes out as one row. */
/* Equal shares gave every column 199px — comfortable for Chinese, where the
   longest name measures 145px, and one pixel short for English, where
   「Containerised Energy Storage System」 wants exactly 199. Eleven of the
   seventeen names wrapped and the columns ran from 187 to 426px tall.

   The columns still share the row equally, but the share is now a floor rather
   than a ceiling: 1fr grows the short ones, minmax lets a column that needs
   more take it. Plain `auto` was tried first and is wrong here — with Chinese
   names it collapsed the columns to 78–145px and gave the whole surplus to the
   feature panel, which ballooned to 550px. */
.mega-grid{
  display:grid;
  grid-template-columns:repeat(var(--mega-cols,4),minmax(0,1fr)) minmax(200px,1.05fr);
  gap:26px;align-items:start;
}
/* the family name doubles as a link to that section of the Products page */
/* Two lines' worth of height whether the name needs them or not. "Data Center
   Liquid Cooling" wraps at this column width and the others do not, and
   without a floor the rule under it — and the whole list below — would sit
   lower in that one column than in its neighbours. */
.mega-cat{
  display:block;
  font-size:15.5px;font-weight:800;color:var(--ink-900);
  /* content-box on purpose: under the sheet's border-box default the floor
     would cover the padding and the rule too, so a two-line name would still
     end up 13px lower than a one-line one. */
  line-height:1.35;min-height:2.7em;box-sizing:content-box;
  padding-bottom:12px;margin-bottom:12px;border-bottom:1px solid var(--line);
  transition:color .18s,border-color .18s;
}
.mega-cat:hover{color:var(--blue);border-bottom-color:#c9d6ea}
.mega-col ul{list-style:none;margin:0;padding:0}
/* balance, so a name that still has to wrap breaks into two even lines rather
   than a full line plus one trailing word. */
.mega-col li a{
  display:block;padding:7px 0;font-size:14.5px;line-height:1.45;
  color:var(--slate-700);text-wrap:balance;
  transition:color .18s,transform .2s var(--ease);
}
.mega-col li a:hover{color:var(--blue);transform:translateX(4px)}

/* Second-level families (移动空调 / 车载冰箱 …) are the links themselves —
   the model count trails in muted text so the group advertises its depth
   without printing every model. */
.mega-col li a .mega-n{
  display:block;margin-top:1px;
  font-size:11.5px;font-weight:500;letter-spacing:.02em;
  color:var(--slate-400);
}

/* Three short columns would leave the right of the card empty; this cell
   carries the way through to the full catalogue. */
.mega-feature{
  display:block;border-radius:10px;overflow:hidden;background:var(--ink-800);
  position:relative;isolation:isolate;
}
/* contain, not cover: the picture here is a transparent-background line-up of
   four machines rather than a photograph, so the panel's own dark ground shows
   through and cropping it would cut the outer two in half. A photograph
   dropped in from the media library still fills the band — it just letterboxes
   instead of cropping, which is the safer failure for a picture nobody has
   art-directed for this shape. */
.mega-feature img{display:block;width:100%;height:132px;object-fit:contain;padding:10px 12px 0;
  transition:transform .55s var(--ease)}
.mega-feature:hover img{transform:scale(1.06)}
.mega-feature-body{display:block;padding:15px 17px 17px;color:#fff}
.mega-feature-body b{display:block;font-size:15px;font-weight:800;margin-bottom:6px}
.mega-feature-desc{display:block;font-size:12.5px;line-height:1.6;
  color:rgba(255,255,255,.72)}
/* inline-flex, not block: the reset sets svg to display:block, so in a block
   container the arrow would drop onto its own line */
.mega-more{
  display:inline-flex;align-items:center;gap:6px;margin-top:12px;
  font-size:13px;font-weight:700;color:var(--blue-400);
}
.mega-more svg{width:14px;height:14px;transition:transform .2s var(--ease)}
.mega-feature:hover .mega-more svg{transform:translateX(4px)}

/* The menu only exists above 1024px, so this covers the whole awkward band
   between the phone breakpoint and a roomy desktop. A fifth track there
   squeezes each column to about 156px, which breaks "Precision Air
   Conditioning" across two lines in every column at once. The catalogue card
   is what gives way, not the catalogue: each family heading already links
   through to its section of the Products page. */
@media (max-width:1280px){
  .mega-grid{grid-template-columns:repeat(var(--mega-cols,4),minmax(0,1fr));gap:22px}
  .mega-feature{display:none}
}

@media (prefers-reduced-motion:reduce){
  .mega,.mega-card,.mega-col li a,.mega-feature img,.mega-more svg,.nav-mega .cv{transition:none}
  .mega-card{transform:none}
}

.header-act{display:flex;align-items:center;gap:14px;flex:none}
/* CTA duplicated inside the collapsed menu; only one of the two is ever shown */
.nav-cta{display:none}
.lang{
  display:inline-flex;align-items:center;gap:6px;font-size:13.5px;font-weight:600;
  color:var(--slate-500);padding:7px 10px;border-radius:var(--r-sm);transition:color .18s,background .18s;
}
.lang:hover{color:var(--blue);background:var(--paper)}
/* a touch larger than the surrounding text icons: the continents need the
   extra pixels to stay legible as shapes */
.lang svg{width:17px;height:17px}
.burger{display:none;width:40px;height:40px;border-radius:var(--r-sm);place-items:center}
.burger span{display:block;width:19px;height:2px;background:var(--ink-800);border-radius:2px;
  position:relative;transition:transform .25s var(--ease),background .2s}
.burger span::before,.burger span::after{content:"";position:absolute;left:0;width:19px;height:2px;
  background:var(--ink-800);border-radius:2px;transition:transform .25s var(--ease),top .2s}
.burger span::before{top:-6px}.burger span::after{top:6px}
.burger[aria-expanded="true"] span{background:transparent}
.burger[aria-expanded="true"] span::before{top:0;transform:rotate(45deg)}
.burger[aria-expanded="true"] span::after{top:0;transform:rotate(-45deg)}

/* ==========================================================================
   HERO — home
   ========================================================================== */
/* One turn of the backdrop. The fade is long on purpose: at 1.2s the change
   registers as the light shifting rather than as a slide being swapped, which
   is the whole difference between a backdrop and a carousel. */
.hero{--hero-hold:4000ms;--hero-fade:1200ms;
  position:relative;min-height:620px;display:flex;align-items:center;
  background:var(--ink-900);overflow:hidden;isolation:isolate}
/* The portable units must retain their top controls and caster wheels on
   wide screens; the old shallow panorama cropped both ends of the product. */
.hero-campaign{min-height:clamp(620px,45vw,900px)}
.hero-media{position:absolute;inset:0;z-index:-2;background:var(--ink-900)}

/* Nine photographs stacked in one cell. Only the arriving one animates: it
   fades up over the outgoing frame, which is never faded out. A true
   cross-dissolve dips in the middle — two half-transparent images over black
   sum to less than one — and on a dark hero that dip reads as a flicker.
   Covering avoids it outright. Script raises the arriving frame to z-index 2
   and drops the one it replaced to 1; everything else sits at 0.
   Frames that are not up are hidden outright rather than just made
   transparent: nine 1920×1080 photographs left visible is a lot of decoded
   bitmap for a phone to hold. Visibility is withheld until the fade has run
   so the outgoing frame stays on screen for the whole of it. */
.hero-slide{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  object-position:var(--pos,58% 46%);
  opacity:0;visibility:hidden;
  transition:opacity var(--hero-fade) cubic-bezier(.4,0,.35,1),
             visibility 0s var(--hero-fade);
}
.hero-slide.is-on{
  opacity:1;visibility:visible;will-change:opacity,transform;
  transition:opacity var(--hero-fade) cubic-bezier(.4,0,.35,1),visibility 0s;
}
/* The drift is what keeps a still photograph from going dead under four
   seconds of scrutiny — about one percent of scale a second, well under the
   threshold where the eye starts tracking it as movement. It runs longer than
   the frame is up (4s lit + 1.2s being covered) so it is always cut off mid
   -travel rather than parking at the end and freezing. Alternating the drift
   direction keeps nine of them from beating like a metronome. */
.hero-slide.is-on{animation:heroDriftA 6.6s linear forwards}
.hero-slide:nth-child(even).is-on{animation-name:heroDriftB}
/* Keep the reviewed product framing stable through each cross-fade. */
.hero-slide-product.is-on{animation:none;transform:none}
/* 1.06 base leaves 3% of margin on each edge, so a 1.4% drift can never pull
   the photograph off its own frame. */
@keyframes heroDriftA{
  from{transform:scale(1.06) translate3d(0,0,0)}
  to{transform:scale(1.15) translate3d(-1.4%,-.8%,0)}
}
@keyframes heroDriftB{
  from{transform:scale(1.06) translate3d(0,0,0)}
  to{transform:scale(1.15) translate3d(1.4%,-.6%,0)}
}

/* Light enough to keep the plant floor readable, dark enough under the
   headline to hold contrast. The directional wash is sized for the low-key
   frames; the flat layer under it is for the bright ones — a white shopfront
   arriving after a dark server aisle would otherwise blow the section open.
   Explicit z-index because the slides below carry one of their own. */
.hero-media::after{
  content:"";position:absolute;inset:0;z-index:3;
  background:
    linear-gradient(100deg,rgba(5,11,26,.84) 0%,rgba(6,14,32,.64) 30%,rgba(8,18,40,.26) 58%,rgba(8,18,40,.12) 100%),
    linear-gradient(0deg,rgba(5,11,26,.62),rgba(5,11,26,0) 42%),
    rgba(6,14,32,.14);
}

/* ---------- scene ticks ---------- */
/* Absolute, and a .wrap of its own, so the row lands on the content column's
   right edge without joining the flex row the copy sits in. */
/* The strip shrinks to its nine buttons rather than filling the column: it is
   what the pointer-pause listens on, and a full-width band would stop the
   rotation from anywhere along the bottom of the hero. */
.hero-nav{position:absolute;left:0;right:0;bottom:36px;z-index:2;pointer-events:none;
  display:flex;justify-content:flex-end}
.hero-dots{display:flex;gap:9px;pointer-events:auto}
/* The visible tick is 3px; the button around it is 24px so it can actually
   be hit. */
.hero-dot{
  position:relative;width:28px;height:24px;padding:0;border:0;background:none;
  cursor:pointer;-webkit-appearance:none;appearance:none;
}
.hero-dot::before,.hero-dot::after{
  content:"";position:absolute;top:50%;height:3px;margin-top:-1.5px;border-radius:2px;
}
.hero-dot::before{left:0;right:0;background:rgba(255,255,255,.26);transition:background .2s}
.hero-dot::after{left:0;width:0;background:#fff}
.hero-dot:hover::before{background:rgba(255,255,255,.5)}
.hero-dot:focus-visible{outline:2px solid var(--cyan);outline-offset:3px;border-radius:2px}
/* The fill is the clock: it reaches the far end exactly as the next frame
   starts arriving, so the rotation never feels like it fires without warning. */
.hero-dot.is-on::after{animation:heroTick var(--hero-hold) linear forwards}
@keyframes heroTick{from{width:0}to{width:100%}}
/* Paused for a hidden tab, and while the strip has the pointer or the focus —
   reaching for a tick is the one moment the backdrop must hold still. */
.hero.is-paused .hero-slide,
.hero.is-paused .hero-dot.is-on::after{animation-play-state:paused}

@media (prefers-reduced-motion:reduce){
  .hero-slide{transition:none}
  .hero-slide.is-on{animation:none;transform:scale(1.02)}
  .hero-dot.is-on::after{animation:none;width:100%}
}
.hero-in{width:100%;padding-block:96px 120px}
.hero-copy{max-width:620px}
/* The English headline is broken by hand into two lines and fits 620px. The
   Chinese one runs unbroken at twelve characters, which needs ~630px at the
   display size — so the column is widened just for it. There is room: the
   photograph's subject sits well to the right of this block. */
:lang(zh) .hero-copy{max-width:760px}
/* Eight of the nine headlines are paragraphs, not h1 — see the markup for
   why — so nothing here may hang off the tag name. */
.hero-head .hero-say{color:#fff}
/* Chinese has no spaces, so a wrap lands wherever the line runs out — at 390px
   the default size broke 直达 down the middle. Scaling the Chinese headline by
   viewport instead keeps all twelve characters on one line down to about
   360px; balance is the safety net below that. */
:lang(zh) .hero-head .hero-say{font-size:clamp(25px,7.2vw,52px);text-wrap:balance}
.hero .lede-light{max-width:560px}
.hero-lede{margin-top:20px}
.hero-cta{display:flex;flex-wrap:wrap;gap:14px;margin-top:34px}

/* ---------- copy that changes with the photograph ---------- */
/* All nine share one grid cell, so the block stands as tall as its longest
   line whichever one is showing. Without that the buttons and the telemetry
   below would hop every four seconds as a two-line sentence replaced a
   one-line one. */
.hero-rotator{display:grid}
.hero-say{grid-area:1/1;align-self:start}
/* Words cannot cross-fade the way photographs can — two sentences at half
   opacity are just unreadable. So the outgoing line is gone (.3s) before the
   incoming one starts (.26s delay), and only one is ever legible. The whole
   swap lands in ~.85s, inside the 1.2s the photograph takes, so the sentence
   has settled by the time the picture behind it has. */
.hero-say{
  opacity:0;visibility:hidden;transform:translateY(12px);
  transition:opacity .3s var(--ease),transform .3s var(--ease),visibility 0s .3s;
}
.hero-say.is-on{
  opacity:1;visibility:visible;transform:none;
  transition:opacity .55s .26s var(--ease),transform .55s .26s var(--ease),visibility 0s;
}
/* Headline and sentence arriving on the same frame reads as a flicker; a
   breath apart reads as one movement that happens to have two parts. */
.hero-head .hero-say.is-on{transition-delay:.26s,.26s,0s}
.hero-lede .hero-say.is-on{transition-delay:.34s,.34s,0s}
.hero-tele .hero-say.is-on{transition-delay:.42s,.42s,0s}

/* The buttons say the same thing on every scene, but they sit in the middle
   of the cascade — headline, sentence, buttons, readings — and leaving them
   still made the wave break around them. So they take the same gesture in
   one movement instead of two: down as the old copy leaves, back up as the
   new copy lands. It never reaches zero and never stops taking a click; a
   primary CTA that blinks out every four seconds is a CTA someone eventually
   clicks and misses. */
.hero-cta.is-cue{animation:heroCtaSettle .86s var(--ease)}
@keyframes heroCtaSettle{
  0%{opacity:1;transform:none}
  34%{opacity:.55;transform:translateY(6px)}
  100%{opacity:1;transform:none}
}
@media (prefers-reduced-motion:reduce){
  .hero-cta.is-cue{animation:none}
}
/* The English headlines run two lines and the brand claim runs three, so the
   cell is three lines tall throughout. Bottom-aligned, the slack collects
   above the label — where the eye has nothing to measure it against —
   instead of opening a hole between the headline and the sentence that
   explains it. Chinese is one line for all nine and never sees any of this. */
.hero-head .hero-say{align-self:end}

/* The label carries no leading rule: the tracking and the cyan already set it
   apart from the headline, and a second mark that far up the page was one
   ornament too many. */
.hero-tag{
  display:block;margin-bottom:20px;
  font-size:12px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;
  color:var(--cyan);
}
/* Four Chinese characters set at .16em read as four separated characters,
   and CJK takes no capitals. */
:lang(zh) .hero-tag{font-size:13px;letter-spacing:.06em;text-transform:none}

@media (prefers-reduced-motion:reduce){
  .hero-say{transition:opacity .01s,visibility 0s;transform:none}
  .hero-say.is-on{transition:opacity .01s,visibility 0s}
}

/* ---------- telemetry strip: a live plant readout ---------- */
.hero-tele{margin-top:64px}
/* Fixed columns rather than content-sized. Nine different label sets share
   this cell, and under flex each would put its readings at a different x —
   the strip would look like it was reshuffling itself every four seconds
   instead of just changing what it says. */
.telemetry{display:grid;grid-template-columns:repeat(3,minmax(0,126px));gap:0 6px}
.tele{
  display:flex;flex-direction:column;gap:3px;position:relative;
  padding:6px 12px 12px;border-radius:var(--r-sm);
  transition:background .3s var(--ease),transform .3s var(--ease);
}
.tele::after{content:"";position:absolute;left:12px;bottom:4px;width:0;height:1.5px;border-radius:2px;
  background:linear-gradient(90deg,var(--cyan),rgba(53,195,243,0));transition:width .4s var(--ease)}
.telemetry dt{font-size:10.5px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;
  color:rgba(255,255,255,.5);transition:color .3s}
/* 10.5px with .16em of tracking is set for Latin caps; Chinese needs the size
   back and most of the tracking off, or the two characters read as separated. */
:lang(zh) .telemetry dt{font-size:12px;letter-spacing:.08em;text-transform:none}
.telemetry dd{margin:0;display:flex;align-items:baseline;gap:5px;
  font-family:var(--font-mono);font-size:21px;font-weight:600;color:var(--cyan);
  font-variant-numeric:tabular-nums;transition:text-shadow .3s}

/* Width is reserved from the widest rendering, so a digit changing never
   shifts the unit that follows it. */
.tele-v{display:inline-block;min-width:var(--w);text-align:right}
.tele-u{font-size:15px;opacity:.72}

/* value swap: the new reading rises into place */
.tele-v.is-new{animation:teleIn .34s var(--ease)}
@keyframes teleIn{
  0%{opacity:0;transform:translateY(-6px)}
  60%{opacity:1}
  100%{opacity:1;transform:none}
}

.tele:hover,.tele:focus-visible{background:rgba(53,195,243,.07);transform:translateY(-2px)}
.tele:hover dt,.tele:focus-visible dt{color:rgba(255,255,255,.8)}
.tele:hover dd,.tele:focus-visible dd{text-shadow:0 0 18px rgba(53,195,243,.5)}
.tele:hover::after,.tele:focus-visible::after{width:44px}

@media (prefers-reduced-motion:reduce){
  .tele-v.is-new{animation:none}
}

/* ---------- product run ----------
   The whole catalogue in one continuous pass, under the tabbed matrix.

   Same ink as .cap and no gap between them, so the two read as one dark
   region: the range, then the argument for it. The equipment is shot on
   transparent ground, and silver machinery on near-black is the contrast the
   photographs were lit for — on paper they turn to grey on grey. */
.prun-sec{background:#040a18;padding-block:54px 40px}

/* Edge fade is a mask on the element, not a painted gradient, so it takes the
   section's ink with it and needs no colour of its own. */
.prun{
  overflow:hidden;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 7%,#000 93%,transparent);
          mask-image:linear-gradient(90deg,transparent,#000 7%,#000 93%,transparent);
}
.prun-track{
  display:flex;width:max-content;margin:0;padding:0;list-style:none;
  animation:prunRun var(--prun-dur,64s) linear infinite;
}
/* Stop so a visitor can actually read — and reach — a card they spotted. */
.prun:hover .prun-track,
.prun:focus-within .prun-track{animation-play-state:paused}

/* Spacing on the items, not a flex gap: a gap would also fall between the two
   runs and break the exact-half offset the seamless loop depends on. */
.prun-track > li{flex:none;width:190px;padding:0 10px}

.prun-item{display:flex;flex-direction:column;gap:12px}
/* No card. On this ground the product is its own shape — a panel around it
   would only draw a box the photograph does not need. */
.prun-fig{
  display:grid;place-items:center;aspect-ratio:4/3;position:relative;
  transition:transform .3s var(--ease);
}
.prun-fig img{
  width:100%;height:100%;object-fit:contain;
  /* Lifts the machined greys off near-black without touching the colour. */
  filter:drop-shadow(0 10px 22px rgba(0,0,0,.55));
  transition:transform .45s var(--ease);
}
.prun-item:hover .prun-fig{transform:translateY(-4px)}
.prun-item:hover .prun-fig img{transform:scale(1.05)}
.prun-item:focus-visible{outline:2px solid var(--blue);outline-offset:6px;border-radius:var(--r-sm)}

.prun-name{
  font-size:12.5px;font-weight:600;line-height:1.45;
  color:rgba(255,255,255,.62);
  text-align:center;text-wrap:balance;
  transition:color .2s;
}
.prun-item:hover .prun-name{color:#fff}

@keyframes prunRun{from{transform:translateX(0)}to{transform:translateX(-50%)}}

/* No travel when motion is turned down. The duplicate pass exists only to make
   the loop seamless, so with the loop gone it is noise — hidden rather than
   left to pad out a grid with a second copy of every product. */
@media (prefers-reduced-motion:reduce){
  .prun{-webkit-mask-image:none;mask-image:none}
  .prun-track{
    display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
    gap:26px 16px;animation:none;
    width:100%;max-width:var(--wrap);margin-inline:auto;padding-inline:var(--gut);
  }
  .prun-track > li{width:auto;padding:0}
  .prun-track > li[aria-hidden="true"]{display:none}
}

/* ---------- liquid cooling page ----------
   Built around the loop the brochure is organised by: cold source → primary
   ring → CDU → secondary ring → rack. */

/* The chain doubles as the page's nav, so it is an ordered list of links and
   the arrows between them are drawn, never typed — a copied heading should not
   come out full of chevrons, and a screen reader should hear five destinations
   rather than five destinations and four arrows. */
.lcchain{
  display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:0;
  list-style:none;margin:34px 0 0;padding:0;
}
.lcchain > li{position:relative;padding:0 10px}
.lcchain > li + li::before{
  content:"";position:absolute;left:-8px;top:38px;width:16px;height:16px;
  background:currentColor;color:var(--slate-400);
  -webkit-mask:var(--arrow) center/contain no-repeat;mask:var(--arrow) center/contain no-repeat;
}
.lcchain a{
  display:flex;flex-direction:column;align-items:center;gap:9px;text-align:center;
  padding:22px 12px 20px;border:1px solid var(--line);border-radius:var(--r);
  background:#fff;height:100%;
  transition:border-color .22s,box-shadow .26s,transform .26s var(--ease);
}
.lcchain a:hover{border-color:#c9d6ea;box-shadow:var(--shadow);transform:translateY(-3px)}
.lcchain-ic{
  display:grid;place-items:center;width:44px;height:44px;border-radius:50%;
  background:rgba(30,95,214,.08);color:var(--blue);flex:none;
}
.lcchain-ic svg{width:20px;height:20px}
.lcchain b{font-size:14.5px;font-weight:700;color:var(--ink-800);line-height:1.35}
.lcchain em{font-style:normal;font-size:12.5px;color:var(--slate-500);line-height:1.45}
.lcchain-note{
  margin-top:20px;text-align:center;font-size:13.5px;color:var(--slate-500);
  text-wrap:pretty;
}

/* ---------- shared blocks ---------- */
.lcnum{
  display:block;font-size:12px;font-weight:800;letter-spacing:.14em;
  color:var(--blue);font-variant-numeric:tabular-nums;margin-bottom:8px;
}
.lcnarrow{max-width:78ch;margin-top:26px}
.lcduo{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,320px),1fr));gap:34px;margin-top:30px}
.lcduo h3{font-size:18px;font-weight:800;color:var(--ink-800);margin-bottom:10px}
.lcduo p{color:var(--slate-700);line-height:1.72;text-wrap:pretty}

.lcquad{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,215px),1fr));gap:28px 26px;margin-top:38px}
.lcquad b{display:block;font-size:15px;font-weight:700;color:var(--ink-800);margin-bottom:6px}
.lcquad p{font-size:14px;color:var(--slate-600);line-height:1.66;text-wrap:pretty}

.lctrio{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,250px),1fr));gap:30px;margin-top:34px}
.lctrio .ic{
  display:grid;place-items:center;width:40px;height:40px;border-radius:var(--r-sm);
  background:rgba(30,95,214,.08);color:var(--blue);margin-bottom:14px;
}
.lctrio .ic svg{width:19px;height:19px}
.lctrio b{display:block;font-size:15.5px;font-weight:700;color:var(--ink-800);margin-bottom:6px}
.lctrio p{font-size:14px;color:var(--slate-600);line-height:1.68;text-wrap:pretty}

.lcsplit{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,1fr);gap:48px;margin-top:32px;align-items:start}
.lcsplit-even{grid-template-columns:repeat(auto-fit,minmax(min(100%,320px),1fr));gap:34px}
.lcsplit p{color:var(--slate-700);line-height:1.74;text-wrap:pretty}
.lcsplit p + p{margin-top:16px}

.lcbox{background:#fff;border:1px solid var(--line);border-radius:var(--r);padding:26px 28px}
.bg-paper .lcbox{background:#fff}
.lcbox h4{font-size:16px;font-weight:800;color:var(--ink-800);margin-bottom:10px}
.lcbox p{font-size:14.5px;color:var(--slate-700);line-height:1.72;text-wrap:pretty}
.lcbox ul{margin:14px 0 0;padding-left:18px}
.lcbox li{font-size:14px;color:var(--slate-600);line-height:1.6;margin-top:7px}
.lcbox-lead p{font-size:15px}

/* A line of guidance rather than a card: it qualifies the block above it and
   should read as a footnote to it, not as another item in the set. */
.lcadvice{
  margin-top:30px;padding:16px 20px;border-left:3px solid var(--blue);
  background:rgba(30,95,214,.045);border-radius:0 var(--r-sm) var(--r-sm) 0;
  font-size:14.5px;color:var(--slate-700);line-height:1.7;text-wrap:pretty;
}
.lcadvice b{color:var(--ink-800);margin-right:8px}
.lcnote{margin-top:30px;font-size:12.5px;color:var(--slate-400);line-height:1.7;text-wrap:pretty}

/* ---------- a CDU ---------- */
.lcunit{border-top:1px solid var(--line-soft);padding-top:34px;margin-top:34px}
.lcunit:first-of-type{border-top:0;padding-top:0}
.lcunit > header{max-width:70ch}
.lcunit > header h3{font-size:clamp(20px,2vw,25px);font-weight:800;color:var(--ink-800);letter-spacing:-.01em}
.lcunit > header p{margin-top:8px;color:var(--slate-600);font-size:15px;line-height:1.66;text-wrap:pretty}
.lcunit-body{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.1fr);gap:44px;margin-top:26px;align-items:start}
.lcunit-body-2{grid-template-columns:repeat(auto-fit,minmax(min(100%,300px),1fr));gap:26px}
.lcunit-body > div > p{color:var(--slate-700);line-height:1.74;margin-bottom:18px;text-wrap:pretty}
.lcsub{font-size:13px;font-weight:700;color:var(--slate-500);letter-spacing:.04em;margin-bottom:10px}

/* The reference table scrolls inside its own box. Five capacities will not fit
   a phone at any type size worth reading, and a spec table shrunk to fit is a
   spec table nobody checks a number in. Scoped so the page body never scrolls
   sideways with it. */
.lcspec-wrap{overflow-x:auto;border:1px solid var(--line);border-radius:var(--r);background:#fff}
.lcspec{border-collapse:collapse;width:100%;min-width:520px}
.lcspec th,.lcspec td{
  padding:13px 16px;text-align:left;font-size:13.5px;white-space:nowrap;
  border-bottom:1px solid var(--line-soft);
}
.lcspec tbody tr:last-child th,.lcspec tbody tr:last-child td{border-bottom:0}
.lcspec thead th{
  background:var(--paper);font-weight:800;color:var(--ink-800);font-size:13px;
  border-bottom:1px solid var(--line);
}
.lcspec tbody th{font-weight:600;color:var(--slate-500);background:#fcfdff}
.lcspec td{font-weight:600;color:var(--ink-800);font-variant-numeric:tabular-nums}

/* ---------- precision AC matrix ---------- */
.lcmatrix{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:22px;margin-top:32px}
.lcmatrix > div{background:#fff;border:1px solid var(--line);border-radius:var(--r);padding:24px 24px 22px}
.lcmatrix b{display:block;font-size:15.5px;font-weight:700;color:var(--ink-800)}
/* The capacity is what a reader is scanning for, so it is the largest thing in
   the card — the name above it is the label, not the headline. */
.lccap{
  display:block;margin-top:12px;font-size:clamp(19px,1.8vw,23px);font-weight:800;
  color:var(--blue);letter-spacing:-.01em;font-variant-numeric:tabular-nums;
}
.lcmatrix p{margin-top:10px;font-size:13.5px;color:var(--slate-500);line-height:1.55}

/* ---------- delivery steps ---------- */
.lcsteps{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,250px),1fr));
  gap:30px 32px;list-style:none;margin:32px 0 0;padding:0;
}
.lcsteps > li{position:relative;padding-top:18px;border-top:2px solid var(--line)}
.lcsteps > li:first-child{border-top-color:var(--blue)}
.lcsteps b{display:block;font-size:16px;font-weight:800;color:var(--ink-800);margin-bottom:7px}
.lcsteps p{font-size:14px;color:var(--slate-600);line-height:1.66;text-wrap:pretty}

/* ---------- page hero (inner pages) ---------- */
.phero{position:relative;background:var(--ink-900);overflow:hidden;isolation:isolate;
  padding-block:64px 62px}
.phero-media{position:absolute;inset:0;z-index:-2}
.phero-media img{width:100%;height:100%;object-fit:cover;object-position:center right}
.phero-media::after{content:"";position:absolute;inset:0;
  background:linear-gradient(95deg,rgba(5,11,26,.96) 0%,rgba(7,16,38,.9) 40%,rgba(10,22,49,.5) 78%,rgba(10,22,49,.35) 100%)}
/* The contact page's photograph is already a dark blue frame with its own
   empty left third — the wash above was cut for bright daytime photographs,
   and at .96/.90 it takes this one down to flat black. Lifted to .88/.62,
   which still holds the headline at AA over the busiest part of the pipework.
   Scoped to this template: the other four heroes are bright photographs and
   still need the full wash. */
.page-template-page-contact .phero-media::after{
  background:linear-gradient(95deg,rgba(5,11,26,.88) 0%,rgba(6,14,34,.62) 38%,rgba(8,18,42,.22) 72%,rgba(8,18,42,.12) 100%)
}
/* The about hero is the plant itself, and the point of that photograph is the
   sign on the wall — which the full wash was burying at .90 right where it
   sits. There was room to spare: the headline measured 17.5:1 against white,
   nearly four times what AA asks of body text. Lifting to .88/.60 still holds
   it at 11.6:1 — above AAA — and takes the sign from .020 to .076 luminance.

   Its own rule rather than sharing the contact page's: same numbers today,
   but the two are lighter for opposite reasons — that photograph is dark and
   needed less wash, this one is bright and can afford less. Replace either
   photograph and only that page's wash should move. */
.page-template-page-about .phero-media::after{
  background:linear-gradient(95deg,rgba(5,11,26,.88) 0%,rgba(6,14,34,.60) 38%,rgba(8,18,42,.22) 72%,rgba(8,18,42,.12) 100%)
}
/* `center right` is right for the other heroes — the wash darkens the left, so
   the subject belongs on the side the text is not. This photograph's subject
   is the sign, and it sits at 28–46% of the frame. Anchored right, a phone
   showed the rightmost 31%: blank corrugated wall, the sign entirely outside
   the crop, which is the one thing the photograph was chosen for.

   37% is the sign's centre, so it stays in frame at every width — at desktop
   the crop is wide enough that the anchor barely matters, and as the viewport
   narrows the window closes in on the sign instead of away from it. */
.page-template-page-about .phero-media img{object-position:37% 50%}
.crumb{display:flex;align-items:center;gap:8px;font-size:13px;color:rgba(255,255,255,.55);margin-bottom:26px}
.crumb a:hover{color:#fff}
.crumb svg{width:11px;height:11px;opacity:.6}
/* Headlines are pre-broken with <br> in the markup, so let each line run its
   natural width instead of wrapping again inside a narrow measure. */
.phero h1{color:#fff;max-width:none}
.phero .lede-light{margin-top:18px;max-width:560px}

/* The category archives are the one page whose hero is a plain dark band with
   no photograph, and they were the one place these two elements had no rule of
   their own — the number inherited the body's near-black ink and sat at 1.1:1
   on a near-black ground (invisible, and set at body weight with no tracking,
   nothing like the small tracked badge it is everywhere else), and the intro
   used .lede, whose slate grey is pitched for white paper and lands at 4.13:1
   here, under the 4.5 that ordinary text is expected to clear. */
.phero .num{
  display:block;font-size:12.5px;font-weight:800;letter-spacing:.16em;
  color:var(--blue-400);margin-bottom:10px
}
.phero .lede{max-width:560px;margin-top:18px;color:rgba(255,255,255,.78)}
/* the marker-swipe under the second headline line */
.swipe{position:relative;display:inline-block}
.swipe::after{content:"";position:absolute;left:-2px;right:-2px;bottom:.1em;height:.3em;
  background:linear-gradient(90deg,rgba(53,195,243,.55),rgba(30,95,214,.15));z-index:-1;border-radius:2px}

/* hero stat row (inner pages) */
.phero-stats{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:26px;margin-top:44px;max-width:800px}
.phero-stats .ic{width:34px;height:34px;border-radius:var(--r-sm);display:grid;place-items:center;
  background:rgba(255,255,255,.09);border:1px solid rgba(255,255,255,.14);margin-bottom:12px}
.phero-stats .ic svg{width:17px;height:17px;color:#fff}
.phero-stats b{display:block;font-size:27px;font-weight:800;color:var(--blue-400);line-height:1.1;letter-spacing:-.02em}
.phero-stats span{display:block;font-size:12.5px;color:rgba(255,255,255,.62);margin-top:5px}
/* feature variant (Solutions hero) */
.phero-feats{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:26px;margin-top:44px;max-width:760px}
.phero-feats b{display:block;font-size:14.5px;font-weight:700;color:#fff;margin-top:12px}
.phero-feats span{display:block;font-size:12.5px;color:rgba(255,255,255,.6);margin-top:4px;line-height:1.5}
.phero-feats .ic{width:36px;height:36px;display:grid;place-items:center;color:var(--blue-400)}
.phero-feats .ic svg{width:26px;height:26px}
/* contact variant */
/* Content-sized columns, not four equal ones. These four values are wildly
   different lengths — the address wants 62px and the email address 227px —
   and splitting 820px evenly gave every one of them 186px: too much for the
   address, 41px short for the email, which then broke across two lines while
   the column beside it sat two-thirds empty.

   auto lets each column take what it needs; the wider column gap keeps them
   reading as four separate facts rather than one run-on line. The four
   together come to about 700px, so they still finish inside the measure the
   headline above them sets. */
.phero-contact{
  display:grid;grid-template-columns:repeat(4,auto);justify-content:start;
  /* 46px was the gap when each item was a bare column of text. The disc now
     sits inside the item and gives it its own left edge, so the same gap
     reads as a gulf — 38px of it is doing the separating already. */
  gap:24px 34px;margin-top:40px;max-width:100%;
}
/* Mark beside the pair, not stacked above it. Three stacked rows made each
   item as tall as a paragraph and pushed the value — the only part anyone is
   here to read — to the third line. Held to two lines with the disc spanning
   both, the label and the value read as one object and the mark becomes what
   it should be: the thing you scan for to find the phone number, not a
   decoration sitting on top of it.

   The narrow layout already did this; it was the wide one that was out of
   step. Now it is one rule for every width. */
.phero-contact > div{
  display:grid;grid-template-columns:auto minmax(0,1fr);
  column-gap:13px;align-items:center;
}
.phero-contact .ic{
  width:38px;height:38px;border-radius:50%;display:grid;place-items:center;
  background:rgba(255,255,255,.09);border:1px solid rgba(255,255,255,.16);
  grid-row:1 / span 2;align-self:center;flex:none;
  transition:background .22s,border-color .22s;
}
.phero-contact > div:hover .ic{background:rgba(255,255,255,.15);border-color:rgba(255,255,255,.3)}
.phero-contact .ic svg{width:17px;height:17px;color:#fff}
.phero-contact b{
  grid-column:2;display:block;font-size:12px;font-weight:600;
  color:rgba(255,255,255,.55);letter-spacing:.02em;
}
/* The value carries the weight — it is the line a visitor came for. */
.phero-contact span{
  grid-column:2;display:block;font-size:15px;color:#fff;margin-top:3px;font-weight:600;
}

/* ==========================================================================
   CAPABILITY STORIES — one slide at a time, card left, paired images right
   ========================================================================== */
/* The section is tall — one screenful of scroll per story — and the stage
   inside it is pinned. Scrolling past the section is what advances the story;
   the height here is the timeline.

   Dark ground, white card, image butted up against it: the pair reads as one
   panel rather than two floating objects. */
/* Pale ground, the same paper tone the rest of the page uses. */
.story{--story-count:4;position:relative;background:var(--paper);
  height:calc((var(--story-count) + 0.25) * 100vh)}
.story-stage{position:sticky;top:var(--header-h);height:calc(100vh - var(--header-h));
  display:grid;align-items:stretch;padding-block:44px;overflow:hidden}
.story-grid{display:grid;grid-template-columns:minmax(0,.52fr) minmax(0,1fr);
  gap:10px;align-items:stretch;height:100%;max-height:820px;align-self:center}

/* Copy sits at the top of a full-height card; the space below it is deliberate
   — it is what makes the panel feel like a page rather than a box. */
/* White against the pale ground — an off-white card would go muddy on it. */
.story-card{position:relative;background:#fff;border-radius:10px;
  padding:52px 46px;display:grid;overflow:hidden;
  box-shadow:0 1px 2px rgba(10,22,49,.04),0 14px 36px rgba(10,22,49,.06)}

/* Blocks share one cell and cross-fade. Nothing is display:none — swapping
   that would snap, and the point is that only the words move. Visibility is
   held back until the fade finishes so the outgoing block stays readable
   while it leaves. */
.story-copy{
  grid-area:1/1;align-self:start;
  opacity:0;transform:translateY(16px);visibility:hidden;pointer-events:none;
  transition:opacity .5s var(--ease),transform .5s var(--ease),visibility 0s .5s;
}
.story-copy.is-on{
  opacity:1;transform:none;visibility:visible;pointer-events:auto;
  transition:opacity .55s .12s var(--ease),transform .55s .12s var(--ease),visibility 0s;
}
/* The reference sets the eyebrow as plain small text, not a coloured all-caps
   label — the heading below is what carries the emphasis, so the line above it
   stays quiet. */
/* Near-neutral ink on the off-white card, not the site's blue-blacks. */
.story-eyebrow{display:block;font-size:14px;font-weight:600;color:var(--slate-700)}
.story-card h2{margin-top:26px;font-size:clamp(28px,2.7vw,40px);font-weight:800;
  line-height:1.15;letter-spacing:-.02em;color:var(--ink-900)}
:lang(zh) .story-card h2{letter-spacing:.01em;line-height:1.3}
/* rule between the name and the words, as in the reference */
.story-quote{margin:22px 0 0;padding-top:26px;border-top:1px solid var(--line)}
.story-quote p{font-size:18px;line-height:1.62;color:var(--ink-800)}
:lang(zh) .story-quote p{font-size:18px;line-height:1.78}
/* the quotation marks belong to the setting, not to the copy */
.story-quote p::before{content:"\201C"}
.story-quote p::after{content:"\201D"}
.story-by{margin-top:24px;font-size:13.5px;line-height:1.6;font-style:italic;
  color:var(--slate-500)}
/* solid block, the way the reference ends the card */
.story-more{display:inline-flex;align-items:center;gap:8px;margin-top:26px;
  padding:13px 20px;border-radius:var(--r-sm);
  background:var(--brand);color:#fff;font-size:14px;font-weight:700;
  transition:background .2s,transform .2s var(--ease)}
.story-more svg{width:14px;height:14px;transition:transform .22s var(--ease)}
.story-more:hover{background:var(--brand-700);transform:translateY(-2px)}
.story-more:hover svg{transform:translateX(3px)}

/* Matches the card's height rather than its own aspect ratio, so the two
   halves square off against each other. */
.story-media{position:relative;border-radius:10px;overflow:hidden;background:var(--ink-800);
  box-shadow:0 1px 2px rgba(10,22,49,.04),0 14px 36px rgba(10,22,49,.06)}
/* Each photograph waits below the frame and slides up over the one before it.
   z-index ascends with the running order, so the arriving image covers the
   outgoing one instead of the two blending — and reversing simply drops it
   back down to reveal what was underneath. */
.story-shot{
  position:absolute;inset:0;display:block;width:100%;height:100%;object-fit:cover;
  transform:translateY(101%);
  transition:transform .78s cubic-bezier(.33,0,.15,1);
  will-change:transform;
}
.story-shot.is-on{transform:translateY(0)}
@media (prefers-reduced-motion:reduce){
  .story-shot{transition:none}
  .story-copy,.story-copy.is-on{transition:opacity .01s,visibility 0s}
}

@media (prefers-reduced-motion:reduce){.story-slide.is-on{animation:none}}

/* ==========================================================================
   CUSTOMER STRIP
   Hairline grid, no cards: the logos already sit on white, so a border box
   around each one would just double the outline.
   ========================================================================== */
/* The bottom padding looks smaller than the top because the marquee items
   carry 14px of their own — 32 above the heading and 18+14 below the marks is
   what actually centres the block in the white band. */
.trust{background:#fff;border-bottom:1px solid var(--line-soft);padding-block:32px 18px;overflow:hidden}
.trust-head{text-align:center;font-size:12.5px;font-weight:600;letter-spacing:.14em;
  text-transform:uppercase;color:var(--slate-400);margin-bottom:20px}
.trust-head b{font-weight:800;color:var(--brand)}
:lang(zh) .trust-head{letter-spacing:.06em;text-transform:none;font-size:14px}

/* Runs edge to edge rather than inside the content column, so marks drift in
   and out of frame instead of appearing at a hard boundary. The mask feathers
   both ends; without it the loop reads as a filmstrip being yanked past. */
.trust-marquee{
  overflow:hidden;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 7%,#000 93%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 7%,#000 93%,transparent);
}
.trust-track{
  display:flex;width:max-content;
  animation:trustRun 52s linear infinite;
}
/* pause so a visitor can actually look at a mark they recognise */
.trust-marquee:hover .trust-track{animation-play-state:paused}
/* Spacing lives on the items, not on a flex gap: a gap would also fall
   between the two runs and break the half-width offset. */
.trust-track li{flex:none;display:grid;place-items:center;padding:14px 28px}
/* Each mark is trimmed to its own ink, so the aspect ratios differ wildly.
   --oh is that mark's own optically-balanced height, worked out per logo in
   data.js; the width cap is the backstop that keeps the longest wordmarks from
   dominating the row. The fallback matters only if the inline style is lost. */
.trust-track img{
  width:auto;height:auto;max-width:136px;max-height:var(--oh,36px);
  /* settle the mixed brand colours to one weight, then restore on hover */
  filter:grayscale(1) opacity(.55);
  transition:filter .3s var(--ease);
}
.trust-track li:hover img{filter:none}
@keyframes trustRun{from{transform:translateX(0)}to{transform:translateX(-50%)}}

/* No travel when motion is turned down. Laid out as an even grid rather than
   left to wrap: flex-wrap fills each line to capacity and leaves the remainder
   stranded on the last row (12 marks came out 8 + 4). A fixed column count
   divides them evenly instead. */
@media (prefers-reduced-motion:reduce){
  .trust-marquee{-webkit-mask-image:none;mask-image:none}
  /* Ten marks divide evenly into five columns or two — nothing else. Four
     would strand two on a second row, three would strand one. */
  .trust-track{
    display:grid;grid-template-columns:repeat(5,1fr);animation:none;
    width:100%;max-width:var(--wrap);margin-inline:auto;padding-inline:var(--gut);
  }
  .trust-track li{padding:18px 16px}
  .trust-track li[aria-hidden]{display:none}
  /* In a grid the column is the hard limit — a mark wide enough to need more
     would otherwise push the row past the viewport. */
  .trust-track img{max-width:min(136px,100%)}
}
@media (prefers-reduced-motion:reduce) and (max-width:640px){
  .trust-track{grid-template-columns:repeat(2,1fr)}
  .trust-track li{padding:14px 10px}
}

/* ==========================================================================
   IMAGE CARDS  (scenario / category tiles)
   ========================================================================== */
.tile{position:relative;display:block;overflow:hidden;border-radius:var(--r);background:var(--ink-800);
  isolation:isolate}
.tile img{width:100%;height:100%;object-fit:cover;transition:transform .7s var(--ease)}
.tile::after{content:"";position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(5,11,26,.12) 0%,rgba(5,11,26,.34) 45%,rgba(5,11,26,.8) 100%);
  transition:background .3s}
.tile:hover img{transform:scale(1.055)}
.tile:hover::after{background:linear-gradient(180deg,rgba(5,11,26,.2) 0%,rgba(8,17,40,.45) 45%,rgba(6,12,30,.86) 100%)}
/* No chip on a tile any more — the hover response is the darkening wash, the
   image push-in and the rule drawn under the heading. */
.tile-body{position:absolute;inset:auto 0 0 0;z-index:1;padding:28px;display:flex;
  flex-direction:column;align-items:center;gap:18px;text-align:center}
/* Line breaks are set in the content with "|", so no width cap is needed here.
   All-caps needs positive tracking — the display face's default negative
   tracking closes the counters up and hurts legibility over a photograph.
   text-wrap:balance is the safety net if a title ever wraps on its own. */
.tile-body h3{
  color:#fff;font-size:19px;font-weight:800;text-transform:uppercase;
  letter-spacing:.02em;line-height:1.3;text-wrap:balance;
  text-shadow:0 1px 2px rgba(5,11,26,.5),0 4px 18px rgba(5,11,26,.45);
}
.tiles-2 .tile-body h3{font-size:clamp(18px,1.55vw,22px);line-height:1.28}
:lang(zh) .tile-body h3{text-transform:none;letter-spacing:.06em;line-height:1.45}
.tiles-2{display:grid;grid-template-columns:1fr 1fr;gap:22px}
.tiles-2 .tile{aspect-ratio:16/9.4}
.tiles-3{display:grid;grid-template-columns:repeat(3,1fr);gap:20px}
.tiles-3 .tile{aspect-ratio:16/10.6}
/* rule under the heading, drawn on hover — the same gesture the stat bar and
   telemetry strip use, so the whole page responds in one accent */
.tiles-3 .tile-body h3{position:relative;padding-bottom:12px}
.tiles-3 .tile-body h3::after{content:"";position:absolute;left:50%;bottom:0;
  width:0;height:2px;border-radius:2px;transform:translateX(-50%);
  background:linear-gradient(90deg,var(--cyan),var(--blue));
  transition:width .36s var(--ease)}
.tiles-3 .tile:hover h3::after{width:40px}

/* ==========================================================================
   SECTION HEAD with an optional action on the right
   ========================================================================== */
.band-head{display:flex;align-items:flex-end;justify-content:space-between;gap:30px;
  flex-wrap:wrap;margin-bottom:30px}
.band-head .h2{margin:0}
.band-head p{margin-top:10px;font-size:14.5px;color:var(--slate-500);max-width:62ch}
.band-head .link-arrow{padding-bottom:3px}

/* ==========================================================================
   SOLUTION CARDS — photo, eyebrow, title, blurb, corner action
   ========================================================================== */
.solcards{display:grid;grid-template-columns:repeat(3,1fr);gap:22px}
.solcard-lg{
  position:relative;display:block;overflow:hidden;border-radius:var(--r);
  aspect-ratio:16/12.4;background:var(--ink-800);isolation:isolate;
}
.solcard-lg>img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  transition:transform .7s var(--ease)}
/* Weighted to the lower half: that is where the copy lands, and the top stays
   open so the photograph still reads. */
.solcard-lg::after{content:"";position:absolute;inset:0;
  background:linear-gradient(180deg,
    rgba(5,11,26,.34) 0%,rgba(5,11,26,.4) 26%,rgba(5,11,26,.78) 56%,rgba(3,8,20,.95) 100%);
  transition:background .32s}
.solcard-lg:hover>img{transform:scale(1.055)}
.solcard-lg:hover::after{
  background:linear-gradient(180deg,
    rgba(5,11,26,.3) 0%,rgba(6,14,32,.44) 26%,rgba(4,10,24,.82) 56%,rgba(2,6,16,.97) 100%)}
/* the ring used to sit under the copy; without it the block needs its own
   breathing room at the foot of the card */
.solcard-lg-body{position:absolute;inset:auto 0 0 0;z-index:1;padding:26px 26px 30px;
  display:flex;flex-direction:column;align-items:flex-start}
.solcard-lg-eyebrow{font-size:11px;font-weight:700;letter-spacing:.15em;text-transform:uppercase;
  color:rgba(255,255,255,.5);margin-bottom:10px}
.solcard-lg h3{font-size:20px;font-weight:800;color:#fff;line-height:1.28;letter-spacing:-.005em;
  text-shadow:0 1px 2px rgba(5,11,26,.5)}
:lang(zh) .solcard-lg h3{letter-spacing:.02em}
.solcard-lg p{margin-top:9px;font-size:13px;line-height:1.65;color:rgba(255,255,255,.7);
  max-width:32ch}
/* What's actually in this family — collapsed until you hover. Animating
   grid-template-rows lets it expand from nothing to its natural height
   without hard-coding a value; the body is bottom-anchored so the copy grows
   upward and the ring below never moves. */
.solcard-lg-more{
  display:grid;grid-template-rows:0fr;opacity:0;
  transition:grid-template-rows .34s var(--ease),opacity .26s var(--ease);
}
.solcard-lg-more>span{
  overflow:hidden;font-size:12px;line-height:1.5;letter-spacing:.01em;
  color:var(--cyan);
}
.solcard-lg:hover .solcard-lg-more,
.solcard-lg:focus-visible .solcard-lg-more{grid-template-rows:1fr;opacity:1;padding-top:10px}

/* ==========================================================================
   PRODUCT MATRIX — tabbed
   ========================================================================== */
.tablist{display:flex;gap:4px;border-bottom:1px solid var(--line);margin-bottom:26px;
  overflow-x:auto;scrollbar-width:none}
.tablist::-webkit-scrollbar{display:none}
.tab{
  position:relative;padding:12px 18px 14px;font-size:14.5px;font-weight:600;
  color:var(--slate-500);white-space:nowrap;background:none;border:0;cursor:pointer;
  transition:color .2s;
}
.tab::after{content:"";position:absolute;left:14px;right:14px;bottom:-1px;height:2px;
  background:var(--blue);border-radius:2px;transform:scaleX(0);
  transition:transform .26s cubic-bezier(.32,0,.24,1)}
.tab:hover{color:var(--ink-800)}
.tab[aria-selected="true"]{color:var(--blue)}
.tab[aria-selected="true"]::after{transform:scaleX(1)}

/* Always four tracks, whatever the panel holds. A family with three products
   leaves the last cell empty rather than stretching its cards — otherwise the
   tiles resize every time you switch tabs. */

.mcard{display:flex;flex-direction:column;background:#fff;border:1px solid var(--line);
  border-radius:var(--r);overflow:hidden;
  transition:border-color .22s,box-shadow .26s,transform .26s var(--ease)}
.mcard:hover{border-color:#c9d6ea;box-shadow:var(--shadow);transform:translateY(-4px)}
.mcard-fig{display:grid;place-items:center;aspect-ratio:4/3;padding:14px;position:relative;
  overflow:hidden;background:linear-gradient(160deg,#f8fafc,#eef2f8)}
.mcard-fig::before{content:"";position:absolute;inset:0;opacity:.5;
  background-image:linear-gradient(rgba(49,61,149,.05) 1px,transparent 1px),
                   linear-gradient(90deg,rgba(49,61,149,.05) 1px,transparent 1px);
  background-size:22px 22px}
.mcard-fig img{position:relative;width:100%;height:100%;object-fit:contain;
  transition:transform .5s var(--ease)}
.mcard:hover .mcard-fig img{transform:scale(1.05)}
.mcard-body{padding:15px 16px 17px;border-top:1px solid var(--line-soft);
  display:flex;flex-direction:column;gap:4px;position:relative}
/* an arrow that slides in from the edge, rather than sitting there unused */
.mcard-body::after{content:"";position:absolute;right:16px;bottom:18px;
  width:14px;height:14px;opacity:0;transform:translateX(-6px);
  background:currentColor;color:var(--blue);
  -webkit-mask:var(--arrow) center/contain no-repeat;mask:var(--arrow) center/contain no-repeat;
  transition:opacity .22s,transform .26s var(--ease)}
.mcard:hover .mcard-body::after{opacity:1;transform:none}
/* Reserve two lines for the name. Some families run to two-line titles and
   others don't; without this the cards change height as you switch tabs. */
.mcard-body b{font-size:14.5px;font-weight:700;line-height:1.35;color:var(--ink-800);
  min-height:2.7em}
/* keep the last line clear of the arrow that appears on hover */
.mcard-body em{font-style:normal;font-size:12.5px;line-height:1.5;color:var(--slate-500);
  padding-right:22px}

/* panels fade in when their tab is chosen */
.tabpanel[hidden]{display:none}
.tabpanel.is-switching .matrix{animation:panelIn .3s var(--ease)}
@keyframes panelIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){
  .tabpanel.is-switching .matrix{animation:none}
}

/* ==========================================================================
   PRODUCT CARDS
   ========================================================================== */
.pcards{display:grid;grid-template-columns:repeat(4,1fr);gap:22px}
.pcard{
  display:flex;flex-direction:column;background:#fff;border:1px solid var(--line);
  border-radius:var(--r);overflow:hidden;transition:border-color .22s,box-shadow .28s,transform .28s var(--ease);
}
.pcard:hover{border-color:#c9d6ea;box-shadow:var(--shadow);transform:translateY(-4px)}
.pcard-fig{aspect-ratio:4/3.15;background:linear-gradient(160deg,#f7f9fc 0%,#eef2f8 100%);
  display:grid;place-items:center;padding:16px;position:relative;overflow:hidden}
.pcard-fig::before{content:"";position:absolute;inset:0;opacity:.5;
  background-image:linear-gradient(rgba(49,61,149,.05) 1px,transparent 1px),
                   linear-gradient(90deg,rgba(49,61,149,.05) 1px,transparent 1px);
  background-size:26px 26px}
.pcard-fig img{position:relative;width:100%;height:100%;object-fit:contain;
  transition:transform .5s var(--ease)}
.pcard:hover .pcard-fig img{transform:scale(1.05)}
.pcard-body{display:flex;align-items:center;justify-content:space-between;gap:14px;
  padding:18px 20px;border-top:1px solid var(--line-soft);margin-top:auto}
.pcard-body h3{font-size:14px;font-weight:700;text-transform:uppercase;letter-spacing:.015em;line-height:1.35}
:lang(zh) .pcard-body h3{text-transform:none;font-size:15px}
.pcard:hover .chip-dark{border-color:var(--blue);color:var(--blue)}

/* ==========================================================================
   CAPABILITY BAND (dark, home)
   ========================================================================== */
.cap{color:#fff;position:relative;overflow:hidden;isolation:isolate;background:#040a18}
/* The artwork is a 2.9:1 banner built for exactly this slot — rack centred,
   dark field either side — so it runs full bleed rather than sitting in the
   middle as an object. No feathering needed: the plate is dark to its edges. */
.cap-media{position:absolute;inset:0;z-index:-2;pointer-events:none}
.cap-media img{width:100%;height:100%;object-fit:cover;object-position:center}
/* Weighted to the outer thirds, where the copy sits. The middle is left almost
   untouched so the rack keeps its contrast. */
.cap-media::after{
  content:"";position:absolute;inset:0;
  background:
    linear-gradient(90deg,
      rgba(4,10,26,.9) 0%,   rgba(5,12,32,.74) 20%,
      rgba(6,16,42,.22) 36%, rgba(6,16,42,.06) 50%,
      rgba(6,16,42,.24) 64%, rgba(5,12,32,.72) 80%,
      rgba(4,10,26,.9) 100%),
    linear-gradient(0deg,rgba(4,10,26,.5),rgba(4,10,26,0) 22%,rgba(4,10,26,0) 80%,rgba(4,10,26,.4) 100%);
}
/* The empty middle column is the machine's breathing room. The side columns
   are sized so their inner edges stop clear of the chassis. min-height tracks
   the plate's 2.9:1 ratio, so cover crops it as little as possible. */
.cap-in{position:relative;display:grid;
  grid-template-columns:minmax(0,.92fr) minmax(0,1.16fr) minmax(0,.78fr);
  gap:44px;align-items:center;padding-block:84px;min-height:min(34vw,520px)}
.cap-copy{grid-column:1}
.cap-specs{grid-column:3}
/* The English is broken by hand into two balanced lines, so no width cap is
   needed; the Chinese runs as a single line. Slightly smaller and tighter than
   the default h2: a stacked display line wants less leading than a single-line
   heading, or the lines drift apart. */
.cap h2{color:#fff;max-width:none;
  font-size:clamp(23px,2.35vw,30px);line-height:1.16;letter-spacing:-.018em;
  text-wrap:balance}
:lang(zh) .cap h2{line-height:1.32;letter-spacing:.01em}
/* Static by intent — these three read as a list, not as controls. Only the
   entrance stagger applies; there is nothing here to hover. */
.cap-feats{margin-top:32px;display:flex;flex-direction:column;gap:22px}
.cap-feat{display:flex;gap:15px;align-items:flex-start}
.cap-feat .ic{width:38px;height:38px;flex:none;border-radius:50%;display:grid;place-items:center;
  background:var(--blue);box-shadow:0 4px 14px rgba(30,95,214,.4)}
.cap-feat .ic svg{width:18px;height:18px;color:#fff}
.cap-feat b{display:block;font-size:13px;font-weight:800;letter-spacing:.09em;text-transform:uppercase}
:lang(zh) .cap-feat b{letter-spacing:.02em;text-transform:none;font-size:15px}
.cap-feat p{font-size:13.5px;line-height:1.6;color:rgba(255,255,255,.62);margin-top:5px}
/* Spec list reads as an instrument panel: the rule at the left is the handle,
   and hovering runs it top-to-bottom like a channel being selected. No glow,
   no scaling — the response is a short, mechanical shift. */
.cap-specs{display:flex;flex-direction:column;gap:24px}
/* Shifts on padding, not transform: these rows are also reveal targets, and
   transform is already spoken for by the entrance animation. Declaring the
   reveal's own properties here too keeps this rule from replacing them. */
.cap-specs div{
  position:relative;padding-left:18px;
  transition:padding-left .2s cubic-bezier(.32,0,.24,1),
             opacity .66s var(--ease),
             transform .66s var(--ease);
}
.cap-specs div::before,
.cap-specs div::after{content:"";position:absolute;left:0;top:0;bottom:0;width:2px}
.cap-specs div::before{background:rgba(255,255,255,.15)}
.cap-specs div::after{
  background:var(--blue-400);
  transform:scaleY(0);transform-origin:top;
  transition:transform .26s cubic-bezier(.32,0,.24,1);
}
.cap-specs dt{font-size:12.5px;color:rgba(255,255,255,.55);margin-bottom:4px;
  transition:color .2s}
.cap-specs dd{margin:0;font-size:23px;font-weight:800;color:var(--blue-400);letter-spacing:-.02em;
  font-variant-numeric:tabular-nums;transition:color .2s}
/* the unit sits a size down and a shade back, so the figure carries the line */
.cap-specs .u{font-style:normal;font-size:.66em;font-weight:700;
  opacity:.6;margin-left:.14em;letter-spacing:.01em}

.cap-specs div:hover{padding-left:24px}
.cap-specs div:hover::after{transform:scaleY(1)}
.cap-specs div:hover dt{color:rgba(255,255,255,.85)}
.cap-specs div:hover dd{color:#7ba8ff}

@media (prefers-reduced-motion:reduce){
  .cap-specs div:hover{padding-left:18px}
  .cap-specs div::after{transition:none}
}

/* ==========================================================================
   CTA BANDS
   ========================================================================== */
.cta{position:relative;overflow:hidden;background:linear-gradient(100deg,#1b4fc4 0%,#2560dd 46%,#1747b0 100%);
  color:#fff}
.cta-in{position:relative;display:flex;align-items:center;justify-content:space-between;gap:40px;
  padding-block:56px;flex-wrap:wrap}

/* ---------- video variant ----------
   The loop runs behind the copy. It is decoration, not content: muted, no
   controls, hidden from assistive tech, and it falls back to its own poster
   frame whenever autoplay is refused. */
/* Base and wash are pulled toward the logo indigo rather than a neutral navy,
   so the band, the button and the mark all sit on the same hue. */
.cta-video{background:#070a24;isolation:isolate}
.catnav~.cta-video{scroll-margin-top:calc(var(--header-h) + 56px)}
.cta-media{position:absolute;inset:0;z-index:-2}
/* Taller band means cover crops less, so the framing can sit near centre and
   keep the whole unit rather than a slice of it. */
.cta-media video{width:100%;height:100%;object-fit:cover;object-position:center 50%;display:block}
/* Dark on the left where the type sits, open on the right where the unit is.
   Top and bottom edges fade so the band meets its neighbours cleanly. */
.cta-media::after{
  content:"";position:absolute;inset:0;
  background:
    linear-gradient(100deg,
      rgba(8,11,38,.94) 0%,  rgba(11,15,48,.88) 26%,
      rgba(16,22,66,.55) 46%, rgba(18,26,74,.16) 64%,
      rgba(18,26,74,.1) 100%),
    linear-gradient(0deg,rgba(7,10,32,.55),rgba(7,10,32,0) 24%,rgba(7,10,32,0) 78%,rgba(7,10,32,.45) 100%);
}
/* Headline · strapline · action, stacked hard against the left edge.
   Still flex, but a single column — the copy block centres in the band.
   Height tracks the viewport so the loop keeps more of its frame on wide
   screens instead of being cropped to a letterbox. */
.cta-video .cta-in{flex-direction:column;align-items:flex-start;justify-content:center;
  gap:0;padding-block:104px;min-height:min(40vw,580px)}
/* Hard stop before the machine, which starts around 45% of the frame. The
   room available is actually 0.45·vw − ((vw−1240)/2 + 32): because `.wrap`
   centres, a wider viewport pushes the copy right faster than it pushes the
   machine, so the gap narrows as the screen grows. A fixed cap is therefore
   safer than a percentage, and the type is sized to fit inside it on two
   lines rather than spilling to three. */
.cta-copy{max-width:min(480px,100%)}
.cta-video h2{
  font-size:clamp(32px,3.6vw,52px);font-weight:800;line-height:1.05;
  letter-spacing:-.024em;text-transform:uppercase;color:#fff;
  text-shadow:0 2px 24px rgba(3,8,22,.5);
}
:lang(zh) .cta-video h2{text-transform:none;letter-spacing:.012em;line-height:1.18}
.cta-sub{margin-top:20px;font-size:clamp(14.5px,1.25vw,18px);line-height:1.6;
  color:rgba(255,255,255,.75);font-weight:400}
:lang(zh) .cta-sub{letter-spacing:.02em}

/* ---------- action ----------
   White block carrying the logo indigo. On hover the pair inverts to indigo
   on white — the brand colour does the work, so no glow is needed to make it
   read against the video. */
.cta-btn{
  margin-top:42px;padding:19px 34px;gap:18px;
  background:#fff;color:var(--brand);
  font-size:15px;font-weight:800;letter-spacing:.07em;text-transform:uppercase;
  box-shadow:0 6px 22px rgba(3,9,24,.34);
  transition:background .22s,color .22s,box-shadow .24s,transform .22s var(--ease);
}
:lang(zh) .cta-btn{letter-spacing:.1em;text-transform:none;font-size:16px}
.cta-btn svg{width:17px;height:17px}
.cta-btn:hover{background:var(--brand);color:#fff;
  box-shadow:0 12px 30px rgba(49,61,149,.5)}
.cta-btn:focus-visible{outline:2px solid #fff;outline-offset:4px}

/* The buttons now sit inside .cta-copy rather than opposite it, because the
   video band stacks its column instead of splitting left and right. The row
   carries the top margin so a lone .cta-btn does not add its own on top of it,
   and wraps rather than pushing a second button off the copy width. */
.cta-copy .cta-acts{margin-top:42px;align-items:center}
.cta-copy .cta-acts .cta-btn{margin-top:0}

/* Below this the machine would sit under the copy. Pan the frame right and
   deepen the wash so the stack keeps its contrast. */
@media (max-width:900px){
  /* stacked now, nothing beside the copy to avoid — let it use the width */
  .cta-copy{max-width:min(80%,560px)}
  .cta-video .cta-in{padding-block:76px;min-height:430px}
  .cta-media video{object-position:72% 52%}
  .cta-media::after{
    background:
      linear-gradient(100deg,rgba(8,11,38,.95) 0%,rgba(11,15,48,.9) 38%,rgba(16,22,66,.55) 72%,rgba(18,26,74,.3) 100%),
      linear-gradient(0deg,rgba(7,10,32,.6),rgba(7,10,32,0) 30%,rgba(7,10,32,.4) 100%);
  }
}
@media (max-width:560px){
  .cta-copy{max-width:100%}
  .cta-btn{margin-top:28px;padding:16px 22px;font-size:13.5px;letter-spacing:.05em;
    width:100%;justify-content:space-between}
  :lang(zh) .cta-btn{font-size:14.5px;letter-spacing:.08em}
}
.cta h2{color:#fff;max-width:22ch}
.cta p{margin-top:10px;color:rgba(255,255,255,.82);font-size:15.5px;max-width:44ch}
.cta-acts{display:flex;gap:14px;flex-wrap:wrap}
.cta-art{position:absolute;right:-20px;top:50%;transform:translateY(-50%);width:min(430px,40%);
  opacity:.3;pointer-events:none}
.cta-art svg{width:100%;height:auto}
.cta-dark{background:linear-gradient(115deg,#08132c 0%,#0e2049 55%,#0a1734 100%);border-radius:var(--r-lg)}
.cta-dark .cta-in{padding-inline:52px}
.cta-dark .cta-art{opacity:.42;width:min(360px,36%)}

/* ==========================================================================
   FOOTER
   ========================================================================== */
.footer{background:var(--ink-900);color:rgba(255,255,255,.62);font-size:13.5px}

/* ---------- reveal-on-scroll ----------
   The footer is pinned to the bottom of the viewport underneath the page, and
   <main> slides up off it — so the last band lifts away like a card being
   pulled back rather than the footer scrolling into place.

   Two things make it work: <main> needs an opaque background (any transparent
   section would let the pinned footer show through mid-page), and it needs a
   bottom margin exactly as tall as the footer, which is the gap the footer
   shows through at the end. The class and the height are both set by script,
   because the footer's height depends on how its columns wrap. */
.footer-reveal main{
  position:relative;z-index:1;background:var(--white);
  margin-bottom:var(--footer-h,0px);
  /* the lifting edge casts onto the footer, which sells the layering */
  box-shadow:0 24px 54px rgba(0,0,0,.5);
}
/* Fill the screen exactly. A footer shorter than the viewport leaves the tail
   of the band above it still showing once you reach the bottom — a stripe of
   the video peeking over the edge. Stretching it to 100vh means the last
   screenful is footer and nothing else, and the spare height goes into the
   gap around the closing statement rather than into dead padding. */
.footer-reveal .footer{position:fixed;left:0;right:0;bottom:0;z-index:0;min-height:100vh}
.footer-reveal .footer>.wrap{min-height:100vh;display:flex;flex-direction:column}
.footer-reveal .footer-mark{margin-top:auto;margin-bottom:auto}
/* The last column is the contact block, and its widest line is the email
   address. On equal 1fr columns that came to 151px against the 228px the row
   needs, so the address broke at its hyphens and stacked three deep.

   fit-content rather than a measured px value: the column takes exactly the
   width its content asks for, capped so a longer address later can never
   crush the four nav columns beside it. The nav columns absorb the difference
   — they hold four-character headings and short links, and had room to give. */
.footer-in{display:grid;
  grid-template-columns:minmax(230px,1.5fr) repeat(4,minmax(0,1fr)) fit-content(264px);
  gap:38px;padding-block:60px 44px}
.footer-brand img{height:26px;width:auto;filter:brightness(0) invert(1)}
.footer-brand p{margin-top:18px;line-height:1.72;max-width:30ch}
/* Solid white tiles rather than tinted circles: on a near-black footer the
   filled shape is what carries, and each mark stays in its own recognisable
   silhouette instead of dissolving into the background. */
.social{display:flex;gap:12px;margin-top:24px}
.social a{
  width:44px;height:44px;border-radius:10px;display:grid;place-items:center;
  background:#fff;
  transition:background .22s,transform .22s var(--ease),box-shadow .22s;
}
.social svg{width:20px;height:20px;color:var(--ink-900);transition:color .22s}
/* inverts to the logo indigo — the same swap the primary button makes */
.social a:hover{background:var(--brand);transform:translateY(-3px);
  box-shadow:0 8px 20px rgba(49,61,149,.45)}
.social a:hover svg{color:#fff}
.social a:focus-visible{outline:2px solid #fff;outline-offset:3px}
.footer h4{color:#fff;font-size:14px;font-weight:700;margin-bottom:16px}
.footer nav li+li{margin-top:9px}
/* The link steps to the right and brightens — no underline. Reads as the item
   being picked out of the column rather than decorated. */
.footer nav a,.footer address a{display:inline-block;
  transition:color .18s,transform .24s cubic-bezier(.32,0,.24,1)}
.footer nav a:hover,.footer address a:hover{color:#fff;transform:translateX(9px)}
/* ---------- footer groups ----------
   <details> so the phone can fold them, but on anything wider it has to be
   invisible machinery: no marker, nothing clickable, and the panel simply
   open. The element ships with `open` set, so a reader without JavaScript
   gets the footer exactly as it always was. */
.foot-group{border:0}
.foot-group>summary{list-style:none;cursor:default}
.foot-group>summary::-webkit-details-marker{display:none}
.foot-group>summary::marker{content:""}

.footer address{font-style:normal;display:flex;flex-direction:column;gap:9px}
.footer address span{display:flex;gap:9px;align-items:flex-start;line-height:1.55}
/* ---------- footer QR ----------
   The white plate is not decoration: the footer is near-black and a scanner
   needs the code's light quiet zone as much as its dark modules. Sized in
   pixels rather than percent so it never shrinks below what a phone camera
   can resolve from arm's length. */
.footer-qr{margin-top:22px;display:flex;flex-direction:column;gap:8px;align-items:flex-start}
.footer-qr img{
  width:104px;height:104px;display:block;
  background:#fff;border-radius:var(--r);padding:7px
}
.footer-qr span{font-size:12px;color:rgba(255,255,255,.55);letter-spacing:.02em}
.footer address svg{width:14px;height:14px;flex:none;margin-top:3px;opacity:.75}
/* ---------- closing statement ----------
   Deliberately the biggest type anywhere on the site. The reference sets it
   in a condensed grotesque; without that face, tight tracking and a short
   leading get close to the same density. */
/* Sized against viewport height as well as width. This statement is what
   makes the footer tall, and the footer only gets pinned behind the page
   while it still fits on screen — so on a short display it has to give way
   rather than cost the reveal. */
.footer-mark{
  margin:44px 0 38px;color:#fff;
  /* Halved against the stacked version: one line is roughly twice as long, so
     the same vw would run straight past the wrap and nowrap would let it. */
  font-size:clamp(15px,min(3.9vw,7vh),62px);font-weight:800;line-height:1.02;
  letter-spacing:-.035em;text-transform:uppercase;
}
/* One line, with the two halves as inline spans and a word space between —
   the fields stay separate (an editor sets them independently) but the mark
   reads as one sentence. Measured before committing to it: Chinese wants
   634px of the 1176px available, English 1053px, so both fit with room over.

   nowrap is what makes it a promise rather than a hope. Without it the line
   would still break at the space the moment a narrower viewport or a longer
   phrase came along, and it would break in the same place — leaving exactly
   the two stacked lines this replaced. The size clamps below shrink the type
   instead, which is the behaviour worth having.

   Negative tracking pulls the last glyph tight against the edge of the box —
   at 375px the longest line was within 28px of being clipped. The trailing
   pad gives that final letter its sidebearing back. */
.footer-mark{white-space:nowrap}
.footer-mark span{display:inline;padding-right:.08em}
.footer-mark span + span{margin-left:.26em}
:lang(zh) .footer-mark{text-transform:none;letter-spacing:.01em;line-height:1.12;
  font-size:clamp(17px,min(4.6vw,8.5vh),74px)}
:lang(zh) .footer-mark span{padding-right:0}
:lang(zh) .footer-mark span + span{margin-left:.34em}

/* On a short display the statement is what tips the footer past the height
   where pinning still makes sense. Rein it in there rather than lose the
   reveal — a smaller closing line costs less than a footer that cannot be
   uncovered. */
@media (max-height:820px){
  .footer-mark{margin:26px 0 22px;font-size:clamp(15px,min(3.2vw,5.4vh),52px)}
  :lang(zh) .footer-mark{font-size:clamp(17px,min(3.8vw,4.9vh),58px)}
}

.footer-bar{border-top:1px solid rgba(255,255,255,.09);padding-block:20px;
  display:flex;align-items:center;justify-content:space-between;gap:24px;flex-wrap:wrap;font-size:12.5px}
.footer-bar .certs{display:flex;gap:10px;align-items:center;opacity:.55}
.footer-bar .certs span{border:1px solid rgba(255,255,255,.3);border-radius:50%;
  width:30px;height:30px;display:grid;place-items:center;font-size:8.5px;font-weight:700;
  letter-spacing:.02em;color:#fff}

/* ==========================================================================
   PROJECTS PAGE
   ========================================================================== */
.catcards{display:grid;grid-template-columns:repeat(4,1fr);gap:22px}
.catcard{background:#fff;border:1px solid var(--line);border-radius:var(--r);overflow:hidden;
  display:flex;flex-direction:column;transition:box-shadow .28s,transform .28s var(--ease),border-color .22s}
.catcard:hover{box-shadow:var(--shadow);transform:translateY(-4px);border-color:#c9d6ea}
.catcard-fig{position:relative;aspect-ratio:16/9.6;overflow:hidden;background:var(--ink-700)}
.catcard-fig img{width:100%;height:100%;object-fit:cover;transition:transform .65s var(--ease)}
.catcard:hover .catcard-fig img{transform:scale(1.06)}
.catcard-fig::after{content:"";position:absolute;inset:0;background:rgba(8,17,40,.28)}
.catcard .badge{position:absolute;left:20px;bottom:-19px;z-index:1;width:38px;height:38px;border-radius:50%;
  background:var(--blue);display:grid;place-items:center;box-shadow:0 5px 16px rgba(30,95,214,.42)}
.catcard .badge svg{width:18px;height:18px;color:#fff}
.catcard-body{padding:32px 22px 24px;display:flex;flex-direction:column;gap:10px;flex:1}
.catcard-body h3{font-size:16px;font-weight:800;text-transform:uppercase;line-height:1.3}
:lang(zh) .catcard-body h3{text-transform:none;font-size:17px}
.catcard-body p{font-size:13.5px;line-height:1.65;color:var(--slate-500);flex:1}
.catcard-body .link-arrow{margin-top:6px}

/* featured project rows */
.projrow{display:grid;grid-template-columns:270px minmax(0,1.25fr) minmax(0,1fr) minmax(0,1fr) 46px;
  gap:0;background:#fff;border:1px solid var(--line);border-radius:var(--r);overflow:hidden;
  transition:box-shadow .28s,border-color .22s}
.projrow+.projrow{margin-top:18px}
.projrow:hover{box-shadow:var(--shadow);border-color:#c9d6ea}
.projrow-fig{overflow:hidden;background:var(--ink-700);min-height:196px}
.projrow-fig img{width:100%;height:100%;object-fit:cover;transition:transform .65s var(--ease)}
.projrow:hover .projrow-fig img{transform:scale(1.06)}
.projrow-main{padding:26px 28px;display:flex;flex-direction:column;gap:8px}
.tag{font-size:11px;font-weight:800;letter-spacing:.13em;text-transform:uppercase;color:var(--blue)}
.projrow-main h3{font-size:19px;font-weight:800}
.projrow-loc{display:flex;align-items:center;gap:6px;font-size:13px;color:var(--slate-500)}
.projrow-loc svg{width:13px;height:13px;color:var(--blue)}
.projrow-main p{font-size:13.5px;line-height:1.65;color:var(--slate-500);margin-top:4px}
.projrow-col{padding:26px 22px;display:flex;flex-direction:column;gap:20px;border-left:1px solid var(--line-soft)}
.projrow-col h4{font-size:12.5px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;
  color:var(--ink-800);margin-bottom:7px}
:lang(zh) .projrow-col h4{letter-spacing:.01em;text-transform:none;font-size:13.5px}
.projrow-col li,.projrow-col p{font-size:13px;line-height:1.6;color:var(--slate-500)}
.projrow-col li{padding-left:12px;position:relative}
.projrow-col li+li{margin-top:4px}
.projrow-col li::before{content:"";position:absolute;left:0;top:8.5px;width:4px;height:4px;
  border-radius:50%;background:var(--blue-400)}
.projrow-go{display:grid;place-items:center;border-left:1px solid var(--line-soft);color:var(--slate-400);
  transition:background .22s,color .22s}
.projrow-go svg{width:17px;height:17px;transition:transform .24s var(--ease)}
.projrow:hover .projrow-go{background:var(--blue);color:#fff}
.projrow:hover .projrow-go svg{transform:translateX(3px)}

/* why-partner strip */
.whystrip{background:linear-gradient(115deg,#08132c,#0e2049 55%,#0a1734);color:#fff;position:relative;overflow:hidden}
.whystrip::before{content:"";position:absolute;inset:0;opacity:.4;
  background:radial-gradient(600px 300px at 20% 0%,rgba(30,95,214,.3),transparent 60%)}
.whystrip .wrap{position:relative}
.whystrip h2{color:#fff;margin-bottom:34px}
.whygrid{display:grid;grid-template-columns:repeat(5,1fr);gap:26px}
.whygrid .ic{width:36px;height:36px;border-radius:var(--r-sm);display:grid;place-items:center;
  background:rgba(255,255,255,.09);border:1px solid rgba(255,255,255,.14);margin-bottom:14px}
.whygrid .ic svg{width:18px;height:18px;color:var(--blue-400)}
.whygrid b{display:block;font-size:14.5px;font-weight:700;color:#fff}
.whygrid p{font-size:12.5px;line-height:1.6;color:rgba(255,255,255,.6);margin-top:6px}

/* world map */
.mapsec{display:grid;grid-template-columns:minmax(0,.72fr) minmax(0,1.6fr);gap:52px;align-items:center}
.worldmap{position:relative}
.worldmap svg{width:100%;height:auto}
/* keep region labels legible where they cross the dot field */
.worldmap .pin text{paint-order:stroke;stroke:#fff;stroke-width:3.2px;stroke-linejoin:round}
.pin{fill:var(--blue);animation:pin 2.6s var(--ease) infinite}
.pin:nth-of-type(2){animation-delay:.35s}.pin:nth-of-type(3){animation-delay:.7s}
.pin:nth-of-type(4){animation-delay:1.05s}.pin:nth-of-type(5){animation-delay:1.4s}
.pin:nth-of-type(6){animation-delay:1.75s}
@keyframes pin{0%,72%,100%{opacity:1;transform:translateY(0)}82%{opacity:.65;transform:translateY(-3px)}}

/* ==========================================================================
   SOLUTIONS PAGE
   ========================================================================== */
.solblock{display:grid;grid-template-columns:270px minmax(0,1fr);background:#fff;
  border-radius:var(--r);box-shadow:var(--shadow-sm);overflow:hidden;border:1px solid var(--line)}
.solblock+.solblock{margin-top:26px}
.solside{background:linear-gradient(160deg,#0a1631 0%,#0e2049 100%);color:#fff;padding:38px 30px;
  display:flex;flex-direction:column}
.solside .num{font-size:13px;font-weight:800;color:var(--blue-400);letter-spacing:.1em}
.solside h2{color:#fff;font-size:23px;margin-top:8px;line-height:1.2}
.solside .rule{background:var(--blue-400);margin-block:16px 18px}
.solside p{font-size:13.5px;line-height:1.68;color:rgba(255,255,255,.66);flex:1}
.solside .link-arrow{color:var(--blue-400);margin-top:20px}
.solmain{padding:26px 26px 22px}
.solgrid{display:grid;gap:16px}
.solgrid[data-n="5"]{grid-template-columns:repeat(5,1fr)}
.solgrid[data-n="4"]{grid-template-columns:repeat(4,1fr)}
.solgrid[data-n="3"]{grid-template-columns:repeat(3,1fr)}
.solcard{display:flex;flex-direction:column;border-radius:var(--r);overflow:hidden;background:#fff;
  border:1px solid var(--line-soft);transition:border-color .22s,box-shadow .26s,transform .26s var(--ease)}
.solcard:hover{border-color:#c9d6ea;box-shadow:var(--shadow-sm);transform:translateY(-3px)}
.solcard-fig{position:relative;aspect-ratio:4/3;background:linear-gradient(160deg,#f7f9fc,#e9eef6);
  display:grid;place-items:center;padding:10px;overflow:hidden}
.solcard-fig img{width:100%;height:100%;object-fit:contain;transition:transform .5s var(--ease)}
.solcard:hover .solcard-fig img{transform:scale(1.06)}
.solcard .badge{position:absolute;right:14px;bottom:14px;z-index:1;width:34px;height:34px;border-radius:10px;
  background:#fff;border:1px solid #dbe6f4;color:var(--blue);display:grid;place-items:center;
  box-shadow:0 2px 8px rgba(17,43,78,.06);pointer-events:none;
  transition:background .2s,border-color .2s,color .2s}
.solcard .badge svg{display:block;width:17px;height:17px;color:currentColor}
.solcard:is(:hover,:focus-visible) .badge{background:var(--blue);border-color:var(--blue);color:#fff}
.solcard:focus-visible{outline:3px solid var(--blue);outline-offset:3px}
.solcard-body{padding:24px 14px 16px}
.solcard-body h3{font-size:13.5px;font-weight:700;line-height:1.35}
.solcard-body p{font-size:12px;line-height:1.5;color:var(--slate-500);margin-top:5px}
.keyapps{display:flex;align-items:center;gap:16px;flex-wrap:wrap;margin-top:22px;padding-top:18px;
  border-top:1px solid var(--line-soft)}
.keyapps>b{font-size:12.5px;font-weight:700;color:var(--blue);flex:none}
.keyapps ul{display:flex;gap:18px;flex-wrap:wrap}
.keyapps li{display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--slate-500);line-height:1.35}
.keyapps li svg{width:19px;height:19px;color:var(--blue);flex:none;opacity:.85}

/* ==========================================================================
   PRODUCTS PAGE / DETAIL
   ========================================================================== */
.catnav{position:sticky;top:var(--header-h);z-index:60;background:rgba(255,255,255,.94);
  backdrop-filter:blur(10px);border-bottom:1px solid var(--line-soft)}
.catnav ul{display:flex;gap:6px;overflow-x:auto;padding-block:12px;scrollbar-width:none}
.catnav ul::-webkit-scrollbar{display:none}
.catnav a{padding:9px 16px;border-radius:100px;font-size:13.5px;font-weight:600;color:var(--slate-500);
  white-space:nowrap;transition:background .2s,color .2s}
.catnav a:hover{background:var(--paper);color:var(--ink-800)}
.catnav a.is-on{background:var(--brand);color:#fff}

.catband{display:flex;align-items:flex-end;justify-content:space-between;gap:30px;flex-wrap:wrap;
  padding-bottom:22px;margin-bottom:32px;border-bottom:2px solid var(--line)}
.catband .num{font-size:12.5px;font-weight:800;letter-spacing:.16em;color:var(--blue)}
.catband h2{margin-top:8px}
.catband p{margin-top:10px;font-size:15px;color:var(--slate-500);max-width:60ch;text-wrap:pretty}

/* ---------- card rails ----------
   Four to a screen and the rest a swipe away, rather than a fifth card
   dropped onto a row of its own. `--per` is written per instance from the
   card count, so a rail holding three or four fills the width and has
   nothing to scroll — it looks exactly as it did.

   overflow-x:auto computes overflow-y to auto as well, which would clip the
   4px hover lift and its shadow. The rail is padded to leave that room and
   pulled back by the same amount, so the surrounding rhythm is unchanged. */
.prodgrid,.matrix{
  display:flex;
  gap:var(--rail-gap);
  overflow-x:auto;
  overscroll-behavior-x:contain;
  scroll-snap-type:x proximity;
  padding:6px 2px 16px;
  margin:-6px -2px -16px;
  scrollbar-width:thin;
  scrollbar-color:var(--line) transparent
}
.prodgrid{--rail-gap:22px;--per-max:4}
.matrix{--rail-gap:18px;--per-max:4}
/* Two numbers, and the smaller wins. The inline --per says how many cards the
   tab actually holds; --per-max says how many this screen can show. They have
   to meet in the stylesheet rather than by cascade: --per is written inline by
   the template, and an inline custom property outranks any media query, so a
   breakpoint that simply reassigned --per was ignored — four cards stayed four
   and each one came out 67px wide on a phone. */
.prodgrid>*,.matrix>*{
  --per-now:min(var(--per,4),var(--per-max));
  flex:0 0 calc((100% - (var(--per-now) - 1) * var(--rail-gap)) / var(--per-now));
  scroll-snap-align:start;
  min-width:0
}
.prodgrid::-webkit-scrollbar,.matrix::-webkit-scrollbar{height:6px}
.prodgrid::-webkit-scrollbar-track,.matrix::-webkit-scrollbar-track{background:transparent}
.prodgrid::-webkit-scrollbar-thumb,.matrix::-webkit-scrollbar-thumb{background:var(--line);border-radius:99px}
/* The right edge fades while there is more to reach. Only script can tell
   whether a rail actually overflows, so site.js owns these two classes. */
.prodgrid.is-rail:not(.at-end),.matrix.is-rail:not(.at-end){
  -webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 64px),transparent);
          mask-image:linear-gradient(90deg,#000 calc(100% - 64px),transparent)
}
.prodgrid:focus-visible,.matrix:focus-visible{outline:2px solid var(--brand);outline-offset:3px}
/* Grab and pull. The hand only appears on a rail that has somewhere to go,
   and only for a pointer that can hover — a touch screen pans natively and
   would otherwise be told it can grab something it never needs to. */
@media (hover:hover){
  .prodgrid.is-rail,.matrix.is-rail{cursor:grab}
  .prodgrid.is-drag,.matrix.is-drag{cursor:grabbing}
  .prodgrid.is-drag *,.matrix.is-drag *{cursor:grabbing}
}
/* Snapping fights the drag, so it is switched off for the duration and picks
   the nearest card back up on release. */
.prodgrid.is-drag,.matrix.is-drag{scroll-snap-type:none;user-select:none;-webkit-user-select:none}
@media (prefers-reduced-motion:reduce){.prodgrid,.matrix{scroll-behavior:auto}}
.prodcard{display:flex;flex-direction:column;background:#fff;border:1px solid var(--line);
  border-radius:var(--r);overflow:hidden;transition:border-color .22s,box-shadow .28s,transform .28s var(--ease)}
.prodcard:hover{border-color:#c9d6ea;box-shadow:var(--shadow);transform:translateY(-4px)}
.prodcard-fig{aspect-ratio:4/3;background:linear-gradient(160deg,#f7f9fc,#eaeff7);position:relative;
  overflow:hidden;padding:14px;display:grid;place-items:center}
.prodcard-fig::before{content:"";position:absolute;inset:0;opacity:.55;
  background-image:linear-gradient(rgba(49,61,149,.05) 1px,transparent 1px),
                   linear-gradient(90deg,rgba(49,61,149,.05) 1px,transparent 1px);
  background-size:24px 24px}
.prodcard-fig img{position:relative;width:100%;height:100%;object-fit:contain;transition:transform .5s var(--ease)}
.prodcard:hover .prodcard-fig img{transform:scale(1.05)}
/* A second machine from the same range, cross-faded in on hover. The card keeps
   one image worth of height — .alt is taken out of flow and laid over the first,
   so nothing reflows and the swap costs no layout. Pointer-only: on touch there
   is no hover to trigger it, and the detail page shows both anyway. */
.prodcard-fig.has-alt .alt{position:absolute;inset:14px;width:calc(100% - 28px);height:calc(100% - 28px);
  opacity:0;transition:opacity .45s var(--ease),transform .5s var(--ease)}
@media (hover:hover){
  .prodcard:hover .prodcard-fig.has-alt img:not(.alt){opacity:0}
  .prodcard:hover .prodcard-fig.has-alt .alt{opacity:1}
}
.prodcard-fig.has-alt img:not(.alt){transition:opacity .45s var(--ease),transform .5s var(--ease)}
.prodcard-body{padding:20px;display:flex;flex-direction:column;gap:8px;flex:1}
.prodcard-body h3{font-size:16px;font-weight:700;line-height:1.3}
.prodcard-body .sub{font-size:12.5px;color:var(--blue);font-weight:600}
.prodcard-body p{font-size:13px;line-height:1.6;color:var(--slate-500);flex:1}
.prodcard-body .link-arrow{margin-top:4px;font-size:13px}

/* detail */
.pdetail{display:grid;grid-template-columns:minmax(0,1.02fr) minmax(0,1fr);gap:56px;align-items:start}

/* ---------- product gallery ----------
   One frame, a row of thumbnails under it. The frame keeps its own height
   whatever is showing — the shots are stacked and cross-faded rather than
   swapped, so picking a view never reflows the column beside it and never
   flashes white while a file decodes. */
.pgal{display:grid;gap:14px}
.pgal-stage{
  position:relative;aspect-ratio:4/3;overflow:hidden;padding:34px;
  background:linear-gradient(160deg,#f7f9fc,#e9eef6);
  border:1px solid var(--line);border-radius:var(--r-lg)
}
/* The same graph paper the cards use, so the frame reads as one family. */
.pgal-stage::before{
  content:"";position:absolute;inset:0;opacity:.6;pointer-events:none;
  background-image:linear-gradient(rgba(49,61,149,.055) 1px,transparent 1px),
                   linear-gradient(90deg,rgba(49,61,149,.055) 1px,transparent 1px);
  background-size:30px 30px
}
.pgal-shot{
  position:absolute;inset:34px;width:calc(100% - 68px);height:calc(100% - 68px);
  object-fit:contain;opacity:0;transform:scale(.985);
  transition:opacity .38s var(--ease),transform .5s var(--ease)
}
.pgal-shot.is-on{opacity:1;transform:none}

.pgal-thumbs{display:flex;flex-wrap:wrap;gap:10px}
.pgal-thumb{
  appearance:none;padding:6px;width:84px;aspect-ratio:4/3;cursor:pointer;
  background:var(--white);border:1px solid var(--line);border-radius:var(--r);
  transition:border-color .2s,box-shadow .24s,transform .24s var(--ease)
}
.pgal-thumb img{width:100%;height:100%;object-fit:contain;display:block}
.pgal-thumb:hover{border-color:#c9d6ea;transform:translateY(-2px);box-shadow:var(--shadow-sm)}
.pgal-thumb:focus-visible{outline:2px solid var(--blue);outline-offset:2px}
/* The chosen one is stated twice — border and ring — because a 1px border
   change alone is easy to miss on a row of near-identical cutouts. */
.pgal-thumb.is-on{border-color:var(--blue);box-shadow:0 0 0 1px var(--blue)}

.pgal-cap{margin:0;font-size:13px;color:var(--slate-500)}
.pgal-single .pgal-cap{display:none}
@media (prefers-reduced-motion:reduce){
  .pgal-shot{transition:opacity .01ms}
  .pgal-thumb{transition:none}
}
/* Two views of the same range, stacked. The right-hand column runs long — lede,
   buttons and four features — so a single figure left a tall gap beside it; the
   second machine fills that rather than padding it. */







.pdetail .tag{margin-bottom:10px;display:block}
.pdetail h1{font-size:clamp(26px,3.1vw,36px)}
.pdetail .lede{margin-top:16px}
.pdetail-acts{display:flex;gap:12px;margin-top:26px;flex-wrap:wrap}
.featlist{margin-top:32px;display:grid;gap:16px}
/* The short-label variant lays itself out across the width instead of running
   down one edge of it. On a 1176px row the widest entry here is 123px of text,
   so a single column spends 317px of height to use a tenth of the width and
   leaves the rest blank. auto-fit rather than a fixed count because these
   lists are four or five entries depending on the solution, and the same rule
   then holds from a wide desktop down to the phone breakpoint that overrides
   it. 200px is the floor at which 「洁净空气 精确控制」 still sits on one line
   beside its tick. */
.featlist-tight{grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:20px 24px}
.featlist li{display:flex;gap:13px;align-items:flex-start}
.featlist .ic{width:30px;height:30px;flex:none;border-radius:var(--r-sm);display:grid;place-items:center;
  background:rgba(30,95,214,.09)}
.featlist .ic svg{width:15px;height:15px;color:var(--blue)}
.featlist b{display:block;font-size:14.5px;font-weight:700}
.featlist p{font-size:13.5px;line-height:1.6;color:var(--slate-500);margin-top:3px}
/* Product detail feature lists carry whole sentences — two columns whenever
   the container can give each ~250px so they never wrap into stubs, one column
   below that (auto-fit, no breakpoint needed). */
.featlist-cols{grid-template-columns:repeat(auto-fit,minmax(min(100%,250px),1fr));gap:18px 26px}

/* Related products as a compact list card beside the spec table — fills the
   column instead of leaving a sparse shelf of small cards on full width. */
.rellist{border:1px solid var(--line);border-radius:var(--r);background:#fff;padding:6px 14px}
.relitem{display:flex;align-items:center;gap:14px;padding:10px 0;color:inherit}
.relitem+.relitem{border-top:1px solid var(--line-soft)}
.relitem-fig{width:56px;height:56px;flex:none;border-radius:10px;display:grid;place-items:center;
  background:linear-gradient(160deg,#f7f9fc,#eef2f8)}
.relitem-fig img{width:44px;height:44px;object-fit:contain}
.relitem-body{flex:1;min-width:0}
.relitem-body b{display:block;font-size:13.5px;font-weight:700;line-height:1.35}
.relitem-body small{display:block;font-size:12px;color:var(--slate-500);margin-top:2px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.relitem>svg{width:16px;height:16px;color:var(--slate-400);flex:none;transition:transform .2s,color .2s}
.relitem:hover>svg{transform:translateX(3px);color:var(--blue)}
.relmore{display:flex;align-items:center;gap:6px;padding:12px 0 8px;margin-top:4px;
  border-top:1px solid var(--line-soft);font-size:13px;font-weight:600;color:var(--blue)}
.relmore svg{width:14px;height:14px}
.product-specs-layout{display:grid;grid-template-columns:minmax(0,1.1fr) minmax(0,.9fr);gap:52px;align-items:start}
.product-specs-layout>*{min-width:0}
.product-specs-layout .spectable{table-layout:fixed;overflow-wrap:anywhere}
.specsplit{display:grid;grid-template-columns:1fr 1fr;gap:18px;align-items:start}
.specsplit .spectable th{width:38%;padding:12px 14px}
.specsplit .spectable td{padding:12px 14px;font-size:13px}
@media(max-width:560px){.specsplit{grid-template-columns:1fr}}
@media(max-width:767px){.product-specs-layout{grid-template-columns:1fr;gap:32px}}
.spectable{border:1px solid var(--line);border-radius:var(--r);overflow:hidden;background:#fff}
.spectable tr+tr{border-top:1px solid var(--line-soft)}
.spectable th,.spectable td{padding:14px 20px;text-align:left;font-size:14px;vertical-align:top}
.spectable th{width:42%;font-weight:600;color:var(--slate-500);background:var(--paper)}
.spectable td{font-weight:600;font-variant-numeric:tabular-nums}
.chips{display:flex;flex-wrap:wrap;gap:9px}
.chips span{padding:7px 14px;border:1px solid var(--line);border-radius:100px;font-size:13px;
  color:var(--slate-700);background:#fff}

/* ==========================================================================
   ABOUT PAGE
   ========================================================================== */
.about-split{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.05fr);gap:56px;align-items:center}
.about-split p+p{margin-top:16px}
.about-fig{border-radius:var(--r);overflow:hidden;box-shadow:var(--shadow);aspect-ratio:16/10.5;background:var(--ink-700)}
.about-fig img{width:100%;height:100%;object-fit:cover}

.mvv{background:linear-gradient(115deg,#08132c,#0e2049 55%,#0a1734);color:#fff;position:relative;overflow:hidden}
.mvv::before{content:"";position:absolute;inset:0;opacity:.4;
  background:radial-gradient(700px 340px at 50% 0%,rgba(30,95,214,.3),transparent 62%)}
.mvv .wrap{position:relative}
.mvvgrid{display:grid;grid-template-columns:repeat(4,1fr);gap:36px;text-align:center}
.mvvgrid .ic{width:56px;height:56px;margin:0 auto 18px;border-radius:50%;display:grid;place-items:center;
  border:1.5px solid rgba(255,255,255,.2);background:rgba(255,255,255,.05)}
.mvvgrid .ic svg{width:25px;height:25px;color:var(--blue-400)}
.mvvgrid h3{font-size:15px;font-weight:800;letter-spacing:.09em;text-transform:uppercase;color:#fff}
:lang(zh) .mvvgrid h3{letter-spacing:.02em;text-transform:none;font-size:17px}
.mvvgrid p{font-size:13px;line-height:1.65;color:rgba(255,255,255,.66);margin-top:12px}

/* timeline */
.timeline{position:relative;padding-top:14px}
.timeline::before{content:"";position:absolute;left:0;right:0;top:20px;height:2px;background:var(--line)}
.tl-items{display:grid;grid-template-columns:repeat(7,1fr);gap:12px}
.tl-item{position:relative;padding-top:38px;text-align:center}
.tl-item::before{content:"";position:absolute;left:50%;top:13px;transform:translateX(-50%);
  width:15px;height:15px;border-radius:50%;background:var(--blue);border:3px solid #fff;
  box-shadow:0 0 0 2px var(--blue);transition:transform .22s var(--ease)}
.tl-item:hover::before{transform:translateX(-50%) scale(1.22)}
.tl-item b{display:block;font-size:19px;font-weight:800;color:var(--ink-800);letter-spacing:-.01em}
.tl-item p{font-size:12.5px;line-height:1.6;color:var(--slate-500);margin-top:7px}

/* manufacturing tiles */
.mfg{display:grid;grid-template-columns:repeat(4,1fr);gap:20px}
.mfg .tile{aspect-ratio:4/3.05;border-radius:var(--r)}
.mfg .tile-body{align-items:flex-start;text-align:left;padding:22px;gap:10px}
.mfg .tile-body .ic{width:32px;height:32px;border-radius:50%;background:rgba(255,255,255,.16);
  border:1px solid rgba(255,255,255,.24);display:grid;place-items:center}
.mfg .tile-body .ic svg{width:15px;height:15px;color:#fff}
.mfg .tile-body h3{font-size:15px;max-width:none}
.mfg .tile-body p{font-size:12.5px;line-height:1.55;color:rgba(255,255,255,.78)}

/* application fields (products page) */
.fieldgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:20px}
.fieldgrid .tile{aspect-ratio:4/3}
.fieldgrid .tile-body{align-items:flex-start;text-align:left;padding:22px;gap:6px}
.fieldgrid .tile-body h3{font-size:16px;max-width:none}
.fieldgrid .tile-body p{font-size:12.5px;line-height:1.55;color:rgba(255,255,255,.78)}

/* production equipment (about page) */
.eqgrid{display:grid;grid-template-columns:repeat(4,1fr);gap:18px}
.eqcard{margin:0;background:#fff;border:1px solid var(--line);border-radius:var(--r);
  overflow:hidden;transition:border-color .22s,box-shadow .26s,transform .26s var(--ease)}
.eqcard:hover{border-color:#c9d6ea;box-shadow:var(--shadow-sm);transform:translateY(-3px)}
.eqcard img{width:100%;aspect-ratio:4/2.8;object-fit:cover;transition:transform .55s var(--ease)}
.eqcard:hover img{transform:scale(1.05)}
.eqcard figcaption{padding:15px 16px 17px;display:flex;flex-direction:column;gap:5px}
.eqcard b{font-size:14px;font-weight:700;line-height:1.3}
.eqcard span{font-size:12.5px;line-height:1.55;color:var(--slate-500)}

/* certifications */
.certgrid{display:grid;grid-template-columns:repeat(6,1fr);gap:18px}
.certcard{border:1px solid var(--line);border-radius:var(--r);background:#fff;padding:22px 14px;
  display:flex;flex-direction:column;align-items:center;gap:14px;text-align:center;
  transition:border-color .22s,box-shadow .26s,transform .26s var(--ease)}
.certcard:hover{border-color:#c9d6ea;box-shadow:var(--shadow-sm);transform:translateY(-3px)}
.certcard .mark{height:56px;display:grid;place-items:center;color:var(--brand)}
.certcard .mark svg{height:52px;width:auto}
.certcard span{font-size:12.5px;color:var(--slate-500);line-height:1.4}

.certshelf{margin:26px 0 0;border:1px solid var(--line);border-radius:var(--r);background:#fff;
  padding:22px 22px 16px;text-align:center}
.certshelf img{width:100%;border-radius:var(--r-sm)}
.certshelf figcaption{margin-top:14px;font-size:12.5px;color:var(--slate-500)}

/* customer logos — five across so ten marks fill two even rows */
.logogrid{display:grid;grid-template-columns:repeat(5,1fr);gap:14px}
.logocell{border:1px solid var(--line);border-radius:var(--r);background:#fff;height:96px;
  display:grid;place-items:center;padding:14px;transition:border-color .22s,box-shadow .24s}
.logocell:hover{border-color:#c9d6ea;box-shadow:var(--shadow-sm)}
/* --ohb is this grid's own optical height, worked out in data.js against the
   96px cell — not the strip's figure scaled up, so retuning one cannot move
   the other. */
.logocell img{width:auto;height:auto;max-width:min(158px,100%);max-height:var(--ohb,52px);
  filter:grayscale(1) opacity(.6);transition:filter .3s var(--ease)}
.logocell:hover img{filter:none}

/* ==========================================================================
   CONTACT PAGE
   ========================================================================== */
/* Narrow column first: the contact details lead and the brief follows. The
   track widths swap with them — the form still needs the wider half, it is
   just on the right now. Order lives in the markup rather than in an `order`
   property so the single-column phone layout stacks the same way round. */
.contact-split{display:grid;grid-template-columns:minmax(0,.8fr) minmax(0,1.25fr);gap:44px;align-items:start}

/* The contact column is 400px against a 978px form, so it used to leave 578px
   of nothing beside the second half of the brief. Sticking it turns that gap
   into the point of it: the phone number and address stay in view while
   someone is still working through the technical fields — which is exactly
   when a visitor decides it is easier to just call.

   Only where there is room to move: below the breakpoint this is a single
   column and a sticky block would pin itself over the form. */
@media (min-width:1025px){
  .contact-split > aside{
    position:sticky;
    /* clear of the fixed header, plus a little air */
    top:calc(var(--header-h) + 28px);
  }
}
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px}
.field{display:flex;flex-direction:column}
.field.full{grid-column:1/-1}
.field input,.field textarea{
  width:100%;padding:11px 14px;border:1px solid var(--line);border-radius:var(--r-sm);
  background:#fff;font-size:14px;transition:border-color .2s,box-shadow .2s;
}
.field textarea{min-height:112px;resize:vertical}
.field input::placeholder,.field textarea::placeholder{color:var(--slate-400)}

/* Controls, not paragraphs. Left alone, every input inherits the body's 1.65
   reading line-height, which on its own puts 10px of dead air inside each box —
   a 54px-tall field to type a two-digit number into. Pinning the line-height
   hands the decision back to the padding, where it belongs. */
.field input,.field textarea{line-height:1.4}

/* ---------- the project brief ----------
   Three numbered groups rather than one long column of inputs: the number and
   the rule under each heading tell a reader how much is left before they start
   filling anything in, which is what stops a technical form from feeling like
   a wall. */
.brief{margin-top:26px}
.brief-step{padding-top:20px;border-top:1px solid var(--line-soft)}
.brief-step:first-child{padding-top:0;border-top:0}
.brief-step + .brief-step{margin-top:22px}
.brief-step > h3{
  display:flex;align-items:baseline;gap:10px;margin:0 0 14px;
  font-size:14.5px;font-weight:800;color:var(--ink-800)
}
.brief-n{
  font-size:12px;font-weight:800;letter-spacing:.12em;
  color:var(--blue);font-variant-numeric:tabular-nums
}

/* A label above the field, not a placeholder inside it: these three carry a
   unit and a number, and a placeholder that vanishes on focus takes the only
   statement of what the number means with it. */
.field > label:not(.sr-only){
  font-size:12.5px;font-weight:600;color:var(--slate-500);margin-bottom:6px
}

/* Radios drawn as chips. The input stays a real radio — keyboard, name/value
   and validation all come free; only its appearance is replaced. */
.pickset{border:0;margin:0 0 16px;padding:0}
.pickset:last-child{margin-bottom:0}
.pickset legend{
  padding:0;font-size:12.5px;font-weight:600;color:var(--slate-500);margin-bottom:8px
}
.picks{display:flex;flex-wrap:wrap;gap:7px}
.pick{position:relative;display:inline-flex}
.pick input{position:absolute;inset:0;opacity:0;cursor:pointer;margin:0}
.pick span{
  display:inline-block;padding:7px 13px;border:1px solid var(--line);
  border-radius:var(--r-sm);background:#fff;font-size:13px;line-height:1.4;color:var(--slate-700);
  cursor:pointer;transition:border-color .18s,color .18s,background .18s
}
.pick:hover span{border-color:#c9d6ea;color:var(--ink-800)}
.pick input:checked + span{
  border-color:var(--blue);background:rgba(30,95,214,.06);color:var(--blue);font-weight:600
}
.pick input:focus-visible + span{outline:2px solid var(--blue);outline-offset:2px}

/* The unit sits inside the field rather than after it, so the three read as
   one row of measurements instead of six alternating boxes and words. */
/* Capped, not stretched. Full-width thirds gave each of these a 221px box to
   hold "50" in, and a box that wide reads as a request for a sentence. Narrow
   fields beside the full-width description say which answers are short. */
.numgrid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;max-width:456px}
.numwrap{position:relative;display:block}
.numwrap input{padding-right:40px}
.numunit{
  position:absolute;right:12px;top:50%;transform:translateY(-50%);
  font-size:12px;color:var(--slate-400);pointer-events:none
}

.brief-send{display:flex;flex-wrap:wrap;align-items:center;gap:16px;margin-top:22px}
.brief-sla{font-size:12.5px;color:var(--slate-500)}
.brief-send .form-note,.brief-send .form-ok{flex:1 0 100%}

/* ---------- location figures ---------- */
.office-stats{
  display:flex;flex-wrap:wrap;gap:18px 26px;margin-top:14px;
  padding-top:14px;border-top:1px solid var(--line-soft)
}
.office-stat b{
  display:block;font-size:17px;font-weight:800;color:var(--ink-800);
  letter-spacing:-.01em;font-variant-numeric:tabular-nums
}
.office-stat span{display:block;font-size:12px;color:var(--slate-500);margin-top:2px}
.field input:focus,.field textarea:focus{outline:none;border-color:var(--blue);
  box-shadow:0 0 0 3px rgba(30,95,214,.12)}
.field .err{font-size:12.5px;color:#d92d20;margin-top:6px;display:none}
.field.is-bad input,.field.is-bad textarea{border-color:#d92d20}
.field.is-bad .err{display:block}
.form-note{margin-top:16px;font-size:12.5px;color:var(--slate-400);line-height:1.6}
.form-ok{display:none;margin-top:18px;padding:14px 18px;border-radius:var(--r-sm);
  background:rgba(16,185,129,.1);border:1px solid rgba(16,185,129,.3);color:#047857;font-size:14px}
.form-ok.is-on{display:block}

.infocard{background:var(--paper);border-radius:var(--r);padding:34px 30px}
.infocard h2{font-size:21px}
.inforow{display:flex;gap:15px;align-items:flex-start;margin-top:26px}
.inforow .ic{width:38px;height:38px;flex:none;border-radius:50%;background:#fff;display:grid;
  place-items:center;box-shadow:var(--shadow-sm)}
.inforow .ic svg{width:17px;height:17px;color:var(--blue)}
.inforow b{display:block;font-size:14.5px;font-weight:700}
.inforow p,.inforow a{font-size:13.5px;line-height:1.6;color:var(--slate-500);margin-top:4px;display:block}
.inforow a:hover{color:var(--blue)}

.offices{display:grid;grid-template-columns:repeat(4,1fr);gap:22px}
.office{background:#fff;border:1px solid var(--line);border-radius:var(--r);overflow:hidden;
  display:flex;flex-direction:column;transition:box-shadow .26s,transform .26s var(--ease),border-color .22s}
.office:hover{box-shadow:var(--shadow);transform:translateY(-4px);border-color:#c9d6ea}
.office-fig{aspect-ratio:16/9.4;overflow:hidden;background:var(--ink-700)}
.office-fig img{width:100%;height:100%;object-fit:cover;transition:transform .6s var(--ease)}
.office:hover .office-fig img{transform:scale(1.06)}
.office-body{padding:20px;display:flex;flex-direction:column;gap:7px;font-size:13.5px;color:var(--slate-500)}
.office-body h3{font-size:15px;font-weight:800;color:var(--blue);text-transform:uppercase;letter-spacing:.02em}
:lang(zh) .office-body h3{text-transform:none}
.office-body .role{font-size:13px;font-weight:600;color:var(--ink-800)}
.office-body a:hover{color:var(--blue)}

/* ==========================================================================
   REVEAL / MOTION
   ========================================================================== */
/* Short travel and a brisk curve: the point is a sense of arrival, not a
   performance. A long slow rise on every block is what makes a page feel
   padded out. */
[data-reveal]{opacity:0;transform:translateY(14px);
  transition:opacity .52s var(--ease),transform .52s var(--ease)}
[data-reveal].is-in{opacity:1;transform:none}
[data-reveal][data-d="1"]{transition-delay:.07s}
[data-reveal][data-d="2"]{transition-delay:.14s}
[data-reveal][data-d="3"]{transition-delay:.21s}
[data-reveal][data-d="4"]{transition-delay:.28s}
[data-reveal][data-d="5"]{transition-delay:.35s}
[data-reveal][data-d="6"]{transition-delay:.42s}
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;
    transition-duration:.001ms!important;scroll-behavior:auto!important}
  [data-reveal]{opacity:1;transform:none}
}

/* ==========================================================================
   RESPONSIVE
   ========================================================================== */
@media (max-width:1180px){
  /* Four content-sized items need about 883px between them; below that they
     start squeezing the email address, which is the widest and the one worth
     protecting. Two-up gives each half the row and every value its own line
     again. The 760px rule below then takes it to one column.

     Without this the strip fell straight from four columns to two at 760px,
     leaving everything between 760 and roughly 950 to cram four columns into
     space for three — the email broke across three lines there. */
  .phero-contact{grid-template-columns:repeat(2,auto);gap:22px 40px}

  /* Five links across a tablet gives each about 130px, which is narrower than
     the two-line names inside them. Three keeps the reading order intact and
     lets the row wrap onto a second line. */
  .lcchain{grid-template-columns:repeat(3,minmax(0,1fr));gap:14px}
  .lcchain > li{padding:0}
  .lcchain > li + li::before{display:none}
  .lcsplit{grid-template-columns:1fr;gap:32px}
  .lcunit-body{grid-template-columns:1fr;gap:28px}

  .footer-in{grid-template-columns:minmax(200px,1.4fr) repeat(3,1fr)}
  /* Below the wide layout there is no column wide enough for the email, and a
     narrow track just puts the hyphen breaks back. The contact block spans the
     row instead — it reads as a band under the nav rather than a cramped fifth
     column, and the address gets the full width to sit on one line. */
  .footer-in > div:has(address){grid-column:1/-1}
  /* No room for a clear lane down the middle. The render fades back to a
     texture behind the copy; the machine is the subject only on wide screens. */
  .cap-in{grid-template-columns:minmax(0,1fr) minmax(0,.82fr);gap:40px;padding-block:72px}
  .cap-copy{grid-column:1}
  .cap-specs{grid-column:2}
  /* Cover crops hard into the rack at this width and it collides with the
     copy, so the plate steps back to a texture and legibility wins. */
  .cap-media img{opacity:.4}

  /* Same collision as on a phone, one breakpoint up: 2.35vw puts the heading
     at 24px here while the figures stay at 23px, so the claim and its evidence
     end up the same size. Step the figures down rather than inflate the
     heading — they are supporting a sentence, not competing with it.

     The heading gets its own floor too. 2.35vw bottoms out at 23px below a
     979px viewport, which leaves 760–979 — a whole tablet's worth of widths —
     with barely a fifth between heading and figure. 3.1vw keeps it opening
     until it meets the phone rule's 24px. */
  .cap h2{font-size:clamp(24px,3.1vw,30px)}
  .cap-specs dd{font-size:20px}
  .cap-media::after{
    background:
      linear-gradient(90deg,rgba(4,10,26,.94) 0%,rgba(5,12,32,.9) 52%,rgba(6,16,42,.82) 100%),
      linear-gradient(0deg,rgba(4,10,26,.5),rgba(4,10,26,.2) 50%,rgba(4,10,26,.45) 100%);
  }
  .projrow{grid-template-columns:220px minmax(0,1.2fr) minmax(0,1fr) minmax(0,1fr) 42px}
  .certgrid{grid-template-columns:repeat(3,1fr)}
  .eqgrid{grid-template-columns:repeat(3,1fr)}
  .logogrid{grid-template-columns:repeat(4,1fr)}
  /* the hairline pattern is tied to the column count, so it is restated
     wherever the grid changes */
  /* fewer marks visible at once, so shorten the loop to keep the pace even */
  .trust-track{animation-duration:38s}
  .trust-track li{padding:16px 28px}
  .tl-items{grid-template-columns:repeat(4,1fr);row-gap:30px}
  .timeline::before{display:none}
  .tl-item{padding-top:34px}
  .tl-item::before{top:6px}
}
@media (max-width:1024px){
  :root{--gut:24px}
  .nav{display:none;position:absolute;left:0;right:0;top:var(--header-h);flex-direction:column;
    align-items:stretch;gap:0;background:#fff;border-bottom:1px solid var(--line);
    box-shadow:0 14px 34px rgba(10,22,49,.12);padding:10px 16px 18px}
  .nav.is-open{display:flex}
  .nav a{padding:14px 10px;border-radius:0;font-size:15.5px}
  .nav a+a{border-top:1px solid var(--line-soft)}
  .nav a::after{display:none}
  /* No hover to open it with, and the collapsed menu already occupies the
     space it would drop into. Products is reachable by tapping the nav item. */
  .mega{display:none}
  .nav-mega .cv{display:none}
  /* the header CTA is too wide for a phone header bar — show it in the menu */
  .header-cta{display:none}
  .nav-cta{
    display:flex;align-items:center;justify-content:center;gap:8px;
    margin-top:14px;padding:14px 18px;border:0;border-radius:var(--r-sm);
    background:var(--blue);color:#fff;font-size:15px;font-weight:700;
  }
  .nav-cta svg{width:15px;height:15px}
  .nav a.nav-cta{color:#fff}
  .burger{display:grid}

  /* On the desktop it is .nav{margin-inline:auto} that holds the two ends of
     the bar apart. Below this the nav drops out of the row and becomes an
     absolutely positioned panel, so that margin goes with it and the actions
     collapse back against the logo — logo, EN, burger all bunched left with
     the whole right half of the bar empty. Push them back to the edge. */
  .header-act{margin-left:auto}
  .pcards,.catcards,.offices,.mfg,.fieldgrid,.eqgrid{grid-template-columns:repeat(2,1fr)}
  .prodgrid{--per-max:2}
  /* Same pinned behaviour, stacked: the card on top, the photograph beneath
     it. Both shrink so the pair still clears the viewport. */
  .story-stage{padding-block:24px}
  .story-grid{grid-template-columns:1fr;grid-template-rows:auto 1fr;gap:0;
    max-height:none}
  .story-card{padding:30px 28px 34px;border-radius:10px 10px 0 0}
  .story-card h2{margin-top:16px;font-size:clamp(24px,3.4vw,30px)}
  .story-quote{margin-top:16px;padding-top:18px}
  .story-quote p{font-size:15.5px}
  :lang(zh) .story-quote p{font-size:16px}
  .story-by{margin-top:16px}
  .story-more{margin-top:18px}
  .story-media{border-radius:0 0 10px 10px;min-height:180px}
  .solcards{grid-template-columns:1fr;gap:18px}
  .solcard-lg{aspect-ratio:16/6.4}
  .solcard-lg p{max-width:52ch}
  .matrix{--per-max:3}
  .tiles-3{grid-template-columns:1fr}
  .solblock{grid-template-columns:1fr}
  .solgrid[data-n="5"],.solgrid[data-n="4"]{grid-template-columns:repeat(3,1fr)}
  .whygrid{grid-template-columns:repeat(3,1fr);row-gap:30px}
  .mvvgrid{grid-template-columns:repeat(2,1fr);row-gap:38px}
  .mapsec,.about-split,.contact-split,.pdetail{grid-template-columns:1fr;gap:36px}
  .pgal-stage{padding:22px}
  .pgal-shot{inset:22px;width:calc(100% - 44px);height:calc(100% - 44px)}
  /* Once the detail page is one column the two figures sit above the copy, so
     lay them side by side — stacked they push the heading most of a screen down. */
  .projrow{grid-template-columns:1fr 1fr;grid-template-areas:"fig fig" "main main" "c1 c2"}
  .projrow-fig{grid-area:fig;aspect-ratio:16/6.5;min-height:0}
  .projrow-main{grid-area:main}
  .projrow-col{border-left:0;border-top:1px solid var(--line-soft)}
  .projrow-col:nth-of-type(3){grid-area:c1}
  .projrow-col:nth-of-type(4){grid-area:c2;border-left:1px solid var(--line-soft)}
  .projrow-go{display:none}
  .cta-art{display:none}
  .cta-dark .cta-in{padding-inline:34px}
}
@media (max-width:760px){
  :root{--gut:20px}
  .sec{padding-block:56px}
  .sec-sm{padding-block:44px}
  .hero{min-height:0}
  .hero-in{padding-block:64px 72px}
  /* Portrait shows barely a third of a 16:9 frame, so the crop is horizontal
     and each photograph needs its own x — a single value would centre one
     scene on its unit and the next on an empty wall. */
  .hero-slide{object-position:var(--pos-m,50% center)}
  /* White type over a white machine loses contrast, and portrait pushes the
     copy right on top of the subject. The wash goes heavier across the whole
     frame to buy it back. */
  .hero-media::after{
    background:
      linear-gradient(100deg,rgba(5,11,26,.9) 0%,rgba(6,14,32,.78) 42%,rgba(8,18,40,.52) 100%),
      linear-gradient(0deg,rgba(5,11,26,.66),rgba(5,11,26,0) 46%),
      rgba(6,14,32,.18);
  }
  /* Left-aligned under the copy rather than off in the far corner, and the
     ticks shorten so nine of them still fit a 360px screen. */
  /* The nine tick marks come off on a phone. They are a progress strip more
     than a control — on a pointer they show how long is left and can be
     reached for; on a touch screen they read as a row of dashes under the
     readout, and the rotation says what they say anyway. The rotator itself is
     untouched: its clock is heroTimer, which has nothing to do with whether
     these are painted. */
  .hero-nav{display:none}
  .hero-tele{margin-top:40px}
  .telemetry{grid-template-columns:repeat(3,minmax(0,1fr));gap:0}
  .phero{padding-block:44px 46px}
  .phero-feats,.phero-contact{grid-template-columns:1fr 1fr;gap:22px}

  /* ---------- the four figures ----------
     Icon above number above label, four of those stacked, came to 479px —
     more than half a screen of hero before the page begins. The icon moves
     beside the number instead of above it, which is where it reads anyway
     (it labels the figure, it is not a heading over it), and two across
     rather than one. 479 → about 150px, and the headline gets the room. */
  /* ---------- the solution checklists ----------
     Five items down a single column came to 317px in each of three sections —
     roughly a screen and a half of the page spent on fifteen short pairs. Two
     across, and the icon shrinks: at 30px it was sized for a column that had
     the whole width to spend. The longest pair here is 112px of text against
     117px of room, so it is close — hence min-width:0 on the text cell, which
     lets a stubborn one wrap inside its own column instead of pushing the
     grid wider.

     Scoped to the modifier, not to .featlist: the product pages use the same
     list for whole sentences, and two columns turned those into three lines of
     seven characters each. */
  .featlist-tight{grid-template-columns:1fr 1fr;gap:16px 14px;margin-top:26px}
  .featlist-tight li{gap:9px}
  .featlist-tight li>div{min-width:0}
  .featlist-tight .ic{width:24px;height:24px}
  .featlist-tight .ic svg{width:13px;height:13px}
  .featlist-tight b{font-size:14px}
  .featlist-tight p{font-size:12.5px;line-height:1.5;margin-top:2px}

  .phero-stats{grid-template-columns:1fr 1fr;gap:18px 16px;margin-top:30px}
  /* The icon, the figure and its label are three siblings with no wrapper, so
     a flex row puts all three side by side and the figure gets squeezed until
     「−40 °C」 breaks after the number. A two-by-two grid instead: the icon
     spans both rows down the left, the figure and its label stack to its
     right — the arrangement the markup implies without adding an element to
     say so. */
  /* .phero-contact already lays out this way at every width; only .phero-stats
     needs converting here, and both want a smaller disc on a phone. */
  .phero-stats>div{
    display:grid;grid-template-columns:auto minmax(0,1fr);
    column-gap:11px;align-items:center
  }
  .phero-stats .ic,.phero-contact .ic{width:30px;height:30px;flex:none;grid-row:1 / span 2;align-self:center}
  .phero-stats b,.phero-stats span{grid-column:2}
  .phero-contact .ic svg{width:15px;height:15px}
  .phero-contact span{font-size:14px}
  .phero-stats .ic svg{width:15px;height:15px}
  .phero-stats b{font-size:21px}
  .phero-stats span{font-size:12px;margin-top:1px}
  .tiles-2,.pcards,.catcards,.offices,.mfg,.form-grid{grid-template-columns:1fr}

  /* ---------- touch targets ----------
     iOS zooms the whole page in when a focused field's text is under 16px.
     The field is the same size either way; only the type grows, and only
     where a touch keyboard exists. */
  /* ---------- hero proportions ----------
     4.4vw floors out at 30px below a 682px viewport, so the headline is the
     same size on a 360px phone as on a 680px tablet — it stops answering to
     the screen exactly where the screen varies most. This slope picks that
     range back up, and stops at 34px so it meets the desktop curve without a
     step at the breakpoint. At 440px it takes the Chinese headline to two
     lines, which is what gives it any weight against the buttons below. */
  .h-display{font-size:clamp(30px,7.8vw,34px)}

  /* Two full-width buttons stacked came to 122px — more than three times the
     headline's own height, so the pair read as the loudest thing in the hero.
     Side by side they stay comfortably tappable and give that back.
     grid-auto-flow:column rather than two fixed tracks, so a band with a
     single button still fills the width. */
  /* Stacked. width:100% because .cta-in is a column flex with
     align-items:flex-start — without it the row shrinks to its buttons. */
  .hero-cta,.cta-acts{display:grid;gap:10px;width:100%}
  .cta-acts .btn{width:100%;justify-content:center}

  /* The hero's pair sits left and only as wide as it needs to be. Full width
     made two bars the width of the headline above them, which put more weight
     on "browse products" than on the sentence that says why. A single
     max-content column rather than letting each shrink on its own: both take
     the width of the wider label, so the stack keeps one edge down each side
     instead of stepping. Height stays at 54px — that is the thumb, and it is
     not what was too big. */
  .hero-cta{grid-template-columns:max-content;justify-content:start}
  .hero-cta .btn{width:auto;justify-content:center}

  /* btn-ghost is a near-solid white plate. At full width over a photograph it
     carries as much weight as the primary, so the pair reads as two equal
     slabs and neither leads. Translucent with a hairline keeps it legible on
     the darkest frames while staying clearly second. */
  .hero-cta .btn-ghost{
    background:rgba(255,255,255,.10);color:#fff;
    border-color:rgba(255,255,255,.45);
    -webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);
    box-shadow:none
  }
  .hero-cta .btn-ghost:hover{background:rgba(255,255,255,.18)}

  /* ---------- scene tiles as a moving rail ----------
     Three full-width tiles stacked is nearly three screens of scrolling for
     what is one idea: here is where these systems run. On a phone they become
     one rail that advances on its own, the way the customer wall does — except
     these are links, so it has to stay swipeable and tappable. site.js pauses
     the moment a finger lands on it, and again whenever the section is off
     screen. 1.15 to a screen so the next tile is visibly waiting. */
  .tiles-3{
    display:flex;gap:14px;
    overflow-x:auto;overscroll-behavior-x:contain;
    /* proximity, not mandatory: mandatory snapping fights a smooth programmatic
       scroll — the animation starts, the snap engine sees it near the previous
       point and pulls it straight back, so the rail never moves at all. */
    scroll-snap-type:x proximity;
    scrollbar-width:none;
    padding-bottom:2px;
    scroll-behavior:smooth
  }
  .tiles-3::-webkit-scrollbar{display:none}
  .tiles-3 .tile{
    flex:0 0 calc((100% - 1.15 * 14px) / 1.15);
    scroll-snap-align:start;
    aspect-ratio:16/11.5
  }

  /* The three solution cards get the same treatment. They carry more copy than
     a scene tile — an eyebrow, two lines of heading and a sentence — so they
     are taller and the peek is narrower: enough to say there is another one,
     not so much that the card in hand loses the width its own text needs. */
  .solcards{
    display:flex;gap:14px;
    overflow-x:auto;overscroll-behavior-x:contain;
    scroll-snap-type:x proximity;
    scroll-behavior:smooth;
    scrollbar-width:none;
    padding-bottom:2px
  }
  .solcards::-webkit-scrollbar{display:none}

  /* 「产品已在服务的领域」同样成轨。这些是 4:3 的实拍，标题和一句话压在
     图上，比场景瓦片更方，露边给到 1.12 就够指明后面还有。 */
  .fieldgrid{
    display:flex;gap:14px;
    overflow-x:auto;overscroll-behavior-x:contain;
    scroll-snap-type:x proximity;
    scroll-behavior:smooth;
    scrollbar-width:none;
    padding-bottom:2px
  }
  .fieldgrid::-webkit-scrollbar{display:none}
  .fieldgrid .tile{
    flex:0 0 calc((100% - 1.12 * 14px) / 1.12);
    scroll-snap-align:start
  }
  @media (prefers-reduced-motion:reduce){
    .fieldgrid{scroll-behavior:auto}
  }
  .solcards .solcard-lg{
    flex:0 0 calc((100% - 1.08 * 14px) / 1.08);
    scroll-snap-align:start;
    aspect-ratio:16/13
  }
  @media (prefers-reduced-motion:reduce){
    .solcards{scroll-behavior:auto}
  }
  @media (prefers-reduced-motion:reduce){
    .tiles-3{scroll-behavior:auto}
  }

  /* Narrower cards and a shorter cycle: at 190px a phone shows two and a bit,
     and the same 64s over a shorter track crawls. */
  /* Stacked, and the arrow turns to point down — the chain is a sequence, and
     losing the direction indicator would leave five unrelated cards. */
  .lcchain{grid-template-columns:1fr;gap:10px}
  .lcchain a{flex-direction:row;text-align:left;align-items:center;gap:14px;padding:14px 16px}
  .lcchain-ic{width:38px;height:38px}
  .lcchain a > b{flex:none;min-width:96px}
  .lcchain > li + li::before{
    display:block;left:50%;top:-9px;translate:-50% 0;width:14px;height:14px;rotate:90deg;
  }
  .lcchain-note{text-align:left}
  .lcunit-body-2{grid-template-columns:1fr}
  .lcadvice{padding:14px 16px}

  .prun-sec{padding-block:42px 32px}
  .prun-track > li{width:148px;padding:0 8px}
  .prun-track{animation-duration:calc(var(--prun-dur,64s)*.78)}
  .prun-name{font-size:12px}

  .field input,.field textarea,.field select{font-size:16px}

  /* The tightened chip is 34px tall — right under a cursor, under the 44px a
     thumb needs. The padding grows; the label stays the size it is drawn. */
  .pick span{padding:12px 16px}

  /* 44px is the smallest square a thumb hits reliably. The burger was 40 and
     the hero's tick marks were 28×24 — the marks stay the size they are drawn,
     the button around them grows. */
  .burger{width:44px;height:44px}

  /* Footer links sat at 24px line boxes with 9px between them — three of them
     fit inside one thumb. Padding rather than font-size, so the column keeps
     its proportions. */
  /* Each group folded behind its own heading. Five open lists ran the footer
     to nearly two screens; a reader who has scrolled this far is usually after
     one thing, and passing twenty-five links to reach the phone number is the
     opposite of helping. Contact stays open — it is the group people come down
     here for.

     The two columns stay. Folded, each group is a single heading row, so two
     across is four rows rather than eight — the fold is what buys the height,
     and taking the columns away would spend it again. */
  .foot-group{border-top:1px solid rgba(255,255,255,.09)}
  .foot-group>summary{
    cursor:pointer;display:flex;align-items:center;justify-content:space-between;
    padding:15px 0;-webkit-tap-highlight-color:transparent
  }
  .foot-group>summary h4{margin:0}
  /* Chevron drawn in CSS rather than another inline SVG in every group. */
  .foot-group>summary::after{
    content:"";width:8px;height:8px;flex:none;margin-left:14px;
    border-right:2px solid rgba(255,255,255,.5);
    border-bottom:2px solid rgba(255,255,255,.5);
    transform:rotate(45deg) translate(-2px,-2px);
    transition:transform .22s var(--ease)
  }
  .foot-group[open]>summary::after{transform:rotate(-135deg) translate(-2px,-2px)}
  .foot-group>nav{padding-bottom:12px}

  .footer nav a,.footer address a{padding-block:7px}
  .footer nav ul{gap:2px}
  .prodgrid{--per-max:1.15}
  .solcard-lg{aspect-ratio:16/11}
  .solcard-lg-body{padding:22px}
  .solcard-lg h3{font-size:18px}
  .matrix{--per-max:2.15}
  .tab{padding:11px 14px 13px;font-size:13.5px}
  .tab::after{left:10px;right:10px}
  .solgrid[data-n="5"],.solgrid[data-n="4"],.solgrid[data-n="3"]{grid-template-columns:1fr 1fr}
  .whygrid{grid-template-columns:1fr 1fr}
  .certgrid,.logogrid,.eqgrid{grid-template-columns:repeat(2,1fr)}
  .trust{padding-block:36px 22px}   /* items carry 14px, so 22+14 matches the 36 above */
  .trust-head{margin-bottom:18px;font-size:11.5px;letter-spacing:.1em}
  .trust-track{animation-duration:28s}
  .trust-track li{padding:14px 24px}
  .trust-track img{max-width:104px;max-height:calc(var(--oh,30px) * .88)}
  .tl-items{grid-template-columns:1fr 1fr}
  .cap-in{grid-template-columns:1fr;padding-block:34px 56px;gap:34px}
  .cap-copy,.cap-specs{grid-column:1}

  /* ---------- the render, lifted out of the background ----------
     This band exists to show one thing: a liquid-cooled rack lit blue on the
     cold side and red on the hot. It is a 2.9:1 render, and as a full-bleed
     backdrop behind a one-column phone layout it gets cover-cropped to a
     vertical sliver and then dimmed to 32% — the machine is not visible at
     all, which leaves a tall black panel with a list on it. So on a phone it
     stops being wallpaper and gets a frame of its own above the copy. 16:10
     crops in on the machine rather than showing the empty hall around it. */
  .cap{display:flex;flex-direction:column}
  .cap-media{position:static;inset:auto;z-index:0;order:-1;aspect-ratio:16/10}
  .cap-media img{opacity:1}
  .cap-media::after{
    /* only enough wash at the foot to carry the eye into the copy below */
    background:linear-gradient(0deg,rgba(4,10,26,.95) 0,rgba(4,10,26,.3) 30%,transparent 58%)
  }

  /* One column, not two. Two halves the height but leaves 159px for values
     like 「−40 °C – +45 °C」, and the range breaks across lines with the dash
     dangling and the closing unit orphaned on its own row. The figure is the
     thing being read here, so it gets the width. */
  .cap-specs{display:flex;flex-direction:column;gap:20px}

  /* ---------- type inside the band ----------
     The four figures were set at 23px — the same size as a section heading —
     so on a phone they shouted exactly as loudly as the claim they exist to
     support. On the desktop the heading is 30px and the step is there; here
     both floor out and the hierarchy goes flat. The heading joins the 24px
     every other section on the page uses, and the figures step below it.

     The small type was on 1.8 leading throughout, which is a paragraph
     setting applied to one-line labels: each label floated midway between its
     own icon and its sentence instead of belonging to either. */
  .cap h2{font-size:24px}
  .cap-feats{margin-top:26px;gap:24px}
  .cap-feat b{line-height:1.35}
  .cap-feat p{font-size:14px;line-height:1.7;margin-top:6px}
  /* ---------- the figures as a datasheet ----------
     Stacked label-over-figure, four of these ran 250px — a quarter of the
     band for four short facts. Put the label and its figure on one line and
     they become a table an eye runs down instead of four separate blocks.
     The figures right-align so they share an edge; a column of numbers that
     do not line up is just four numbers in a row. The left accent bar goes —
     it marked where each block started, and with a rule between rows there is
     nothing left to mark. */
  .cap-specs{gap:0}
  .cap-specs div{
    display:flex;align-items:baseline;justify-content:space-between;gap:16px;
    padding:12px 0;border-bottom:1px solid rgba(255,255,255,.09)
  }
  .cap-specs div:first-child{padding-top:0}
  .cap-specs div:last-child{padding-bottom:0;border-bottom:0}
  .cap-specs div,.cap-specs div:hover{padding-left:0}
  .cap-specs div::before,.cap-specs div::after{display:none}
  .cap-specs dt{font-size:12.5px;line-height:1.4;margin-bottom:0;flex:none}
  .cap-specs dd{font-size:20px;line-height:1.35;text-align:right}
  .projrow{grid-template-columns:1fr;grid-template-areas:"fig" "main" "c1" "c2"}
  .projrow-col:nth-of-type(4){border-left:0}
  .cta-in{flex-direction:column;align-items:flex-start;gap:26px}
  .cta-dark .cta-in{padding-inline:26px}
  .footer-in{grid-template-columns:1fr 1fr;gap:30px}
  .footer-brand{grid-column:1/-1}
  .spectable th,.spectable td{padding:12px 14px;font-size:13.5px}
}
@media (max-width:460px){
  .solgrid[data-n="5"],.solgrid[data-n="4"],.solgrid[data-n="3"]{grid-template-columns:1fr}
  .whygrid,.certgrid,.logogrid,.tl-items{grid-template-columns:1fr}

  /* Two columns leave about nine Chinese characters to a line — short enough
     that the eye returns more often than it reads. One column at this width. */
  .mvvgrid{grid-template-columns:1fr;row-gap:30px}

  /* ---------- timeline as a rail ----------
     Stacked and centred, each entry became an island: a lone dot, a year, a
     sentence, then 100px of nothing — eight of them read as eight unrelated
     notes rather than one line through time, and the whole thing ran 2,000px.
     A rail down the left restores the through-line, puts the years on a
     common edge where they scan as a sequence, and gives the text the full
     width instead of a centred column inside it. */
  .timeline{padding-left:24px}
  .timeline::before{
    display:block;left:5px;right:auto;top:8px;bottom:8px;
    width:2px;height:auto
  }
  .tl-items{row-gap:20px}
  .tl-item{padding-top:0;text-align:left}
  .tl-item::before{left:-24px;top:5px;transform:none;width:12px;height:12px}
  .tl-item:hover::before{transform:scale(1.22)}
  /* 联系方式保持单列：邮箱和工作时间比一个数字长得多，两列会把它们折断。
     紧凑排布（图标左置）仍然套用，光这一项就省掉一半高度。 */
  .phero-feats,.phero-contact{grid-template-columns:1fr}

}

/* ---------- print ---------- */
@media print{
  .header,.footer,.cta,.catnav,.skip{display:none}
  body{color:#000}
  .phero,.hero{background:#fff;color:#000;min-height:0;padding-block:20px}
  .phero-media,.hero-media,.hero-beams,.hero-nav{display:none}
  .phero h1,.hero-head .hero-say{color:#000}
  /* eight of the nine are hidden on screen; on paper print only the h1 */
  .hero-head .hero-say:not(.is-on){display:none}
}

/* ---------- product matrix ----------
   Tabs are buttons and panels are plain divs; site.js does the switching, and
   without it every panel is simply visible in sequence, which still reads as a
   catalogue rather than as a broken widget. */
.matrix{margin-top:8px}
.matrix-tabs{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:18px}
.matrix-tab{appearance:none;border:1px solid var(--line);background:#fff;color:var(--slate-500);
  border-radius:99px;padding:9px 18px;font-size:13.5px;font-weight:600;cursor:pointer;
  transition:border-color .2s,color .2s,background .2s}
.matrix-tab:hover{border-color:#c9d6ea;color:var(--ink)}
.matrix-tab.is-on{background:var(--blue);border-color:var(--blue);color:#fff}
.matrix-panel[hidden]{display:none}
.matrix-table{width:100%}
.matrix-table th[scope="col"]{font-size:12px;text-transform:uppercase;letter-spacing:.05em;
  color:var(--slate-500);font-weight:600}
:lang(zh) .matrix-table th[scope="col"]{text-transform:none;letter-spacing:.02em;font-size:12.5px}
.matrix-table th[scope="row"]{font-weight:600}
.matrix-table th[scope="row"] a{color:var(--ink);text-decoration:none}
.matrix-table th[scope="row"] a:hover{color:var(--blue)}
@media (max-width:640px){
  /* Three columns will not fit; the type column is the one a buyer can infer. */
  .matrix-table thead th:nth-child(2),.matrix-table tbody td:nth-child(1){display:none}
}
