/* ============================================================
   BEAUTECH — Clinical / Scientific design system
   ============================================================ */

/* ---- Fonts ----
   Loaded from a <link rel="stylesheet"> in each page's <head> (shared spec §1),
   not from an @import here: an @import inside this file cannot be discovered
   until site.css has itself downloaded and parsed, which serialises the font
   request behind it. Do not re-add it. --f-display / --f-sans / --f-mono below
   assume Inter, Noto Sans TC and JetBrains Mono come from that link. */

/* ---- Tokens ---- */
:root {
  /* palette — navy system (Doc A spec) */
  --paper:    #FFFFFF;
  --surface:  #F8FAFC;        /* soft light grey */
  --surface-2:#FFFFFF;
  --ink:      #0A192F;        /* dark navy — headlines / bold */
  --ink-soft: #334155;        /* dark grey — body / menus */
  --muted:    #64748B;        /* slate grey */
  --line:     rgba(100,116,139,0.22);
  --line-soft:rgba(100,116,139,0.11);
  --accent:   #1E3A8A;        /* navy blue — accent / labels / hover */
  --accent-d: #152C6B;
  --accent-tint:#E8EEF9;
  --brand-blue:#1E3A8A;       /* BT monogram blue */
  /* Official corporate mark (sampled from "Wall design AW.pdf" / BT-Logo-ONLY.ai).
     #871F82 is the purple of the BEAUTECH INDUSTRIES wordmark — 8.1:1 on white,
     but only 2.17:1 on the navy footer, which is why .site-footer .brand sits on
     a white plate rather than inverting the wordmark. Do not use this purple as
     a text colour on any dark band. --brand-purple is FIXED in both themes
     because the only places it prints are white plates (the footer logo tile);
     --brand-wordmark is the themed alias for the wordmark sitting on the page
     background, and lightens under the dark theme. */
  --brand-purple: #871F82;
  --brand-wordmark: #871F82;

  /* Icon accent set. The client asked for "different colors / artworks on the
     icons", but their own 9 Jul spec fixes a single navy palette site-wide.
     These four are the compromise: every hue is drawn from the corporate mark
     (navy accent, wordmark purple, monogram blue) plus one green for the GMP
     family, each paired with a tint it clears 4:1 on. Use them ONLY for icon
     chips — never for body copy. */
  --ic-navy:   #1E3A8A;  --ic-navy-bg:   #E8EEF9;
  --ic-purple: #871F82;  --ic-purple-bg: #F5E9F4;
  --ic-blue:   #0E6FA8;  --ic-blue-bg:   #E4F1FA;
  --ic-green:  #0F6B55;  --ic-green-bg:  #E3F2ED;
  --accent-on-dark:#5C8AE6;   /* light blue for labels on dark sections.
                                 site.js used to overwrite this at runtime with
                                 #5C8AE6; now that it sets no custom properties,
                                 the shipped value lives here. Do not lighten it
                                 without re-checking contrast — see PROSE note
                                 in the report / .metrics in pages/index.css. */
  --heading:  #0A192F;        /* headings in dark navy */
  --signal:   #C24E3A;        /* warm signal, used sparingly */
  --surface-dark:       #0A0C0D; /* fixed near-black bg for permanently-dark bands (.flow/.qc/.proc/.actives) under dark theme */
  --surface-dark-hover: #1F2429; /* hover state on those same dark bands */

  /* type — uniform Inter / Helvetica Neue (Doc A spec) */
  --f-display: 'Inter', 'Helvetica Neue', 'Noto Sans TC', Arial, sans-serif;
  --f-sans:    'Inter', 'Helvetica Neue', 'Noto Sans TC', Arial, sans-serif;
  --f-mono:    'JetBrains Mono', 'Noto Sans TC', monospace;

  /* accent used ONLY for the highlighted half of a headline. --accent on
     --heading is 1.70:1 — invisible as a highlight. This is 3.41:1 against
     the heading and 5.17:1 on white, and stays in the brand blue family. */
  --accent-bright: #2563EB;

  /* ---- Scale tokens -------------------------------------------------------
     One ladder per property. Before this the stylesheet carried 36 font
     sizes (13 of them between 9.5px and 16px), 8 radii and 14 tracking
     values — accumulation, not a system. Add to these only deliberately. */

  /* type scale */

  /* display scale — was 33 bespoke clamps for 34 elements, every one
     hand-tuned and used exactly once. Seven rungs replace them. */
  --ds-sm:    clamp(18px, 1.9vw, 23px);
  --ds-md:    clamp(21px, 2.3vw, 28px);
  --ds-lg:    clamp(24px, 2.7vw, 33px);
  --ds-xl:    clamp(26px, 3.2vw, 40px);
  --ds-2xl:   clamp(29px, 3.7vw, 47px);
  --ds-3xl:   clamp(33px, 4.6vw, 58px);
  --ds-4xl:   clamp(40px, 6.4vw, 92px);
  --fs-lead:  clamp(18px, 1.5vw, 22px);

  /* motion — was 17 distinct durations (.15/.16/.18 and .3/.32/.34/.35/.38
     are not humanly distinguishable). transition-delay is deliberately NOT
     tokenised: .08/.16/.24/.32 there is the reveal stagger sequence. */
  /* line-height — 20 ad-hoc decimals (1.04/1.05/1.06, 1.6/1.62/1.64) became
     seven named steps. Every shift is under 6%. */
  /* spacing rhythm — 32 distinct clamps for 48 uses became six rungs. */
  --gap-xs:   clamp(16px, 2.4vw, 28px);
  --gap-sm:   clamp(22px, 3vw, 38px);
  --gap-md:   clamp(28px, 3.8vw, 50px);
  --gap-lg:   clamp(34px, 4.8vw, 66px);
  --gap-xl:   clamp(38px, 5.6vw, 80px);
  --gap-2xl:  clamp(52px, 7.6vw, 110px);

  --lh-none:    1;
  --lh-display: 1.04;
  --lh-heading: 1.12;
  --lh-tight:   1.3;
  --lh-snug:    1.45;
  --lh-body:    1.6;
  --lh-loose:   1.72;

  --dur-fast:   .15s;
  --dur-base:   .25s;
  --dur-slow:   .35s;
  --dur-reveal: .7s;
  --ease:       cubic-bezier(.2,.7,.3,1);

  --fs-read:  17px;   /* body copy; bumped on tablet, see media query */
  --fs-nano:  10px;
  --fs-micro: 11px;
  --fs-xs:    12px;
  --fs-sm:    13px;
  --fs-base:  15px;
  --fs-md:    17px;
  --fs-lg:    20px;
  --fs-xl:    24px;
  --fs-2xl:   30px;

  /* weight — two roles: headings, and large data figures. Nothing between. */
  --fw-head:   600;
  --fw-med:    500;   /* large data figures, secondary display */
  --fw-bold:   700;   /* buttons, wordmark, emphasis */

  /* tracking */
  --track-tight: -0.02em;   /* display type */
  --track-wide:   0.06em;   /* small uppercase */
  --track-label:  0.12em;   /* mono eyebrow labels */

  /* shape */
  --radius:      3px;       /* default — sharp, clinical */
  --radius-lg:   8px;       /* photos, cards, panels */
  --radius-pill: 100px;

  /* elevation — navy-tinted. The old values were rgba(27,19,32,…), left over
     from the pre-navy purple identity, and read muddy against this palette. */
  --shadow-sm: 0 2px 10px rgba(10,25,47,.16);
  --shadow-md: 0 18px 40px -22px rgba(10,25,47,.45);
  --shadow-lg: 0 30px 70px -40px rgba(10,25,47,.55);

  /* vertical rhythm */
  --sec-y:       clamp(56px, 8vw, 112px);  /* default section rhythm */
  --sec-y-tight: clamp(40px, 6vw, 80px);   /* dense utility pages */

  /* metrics */
  --maxw: 1280px;
  --gutter: clamp(20px, 5vw, 76px);
}

:root[data-theme="dark"] {
  --paper:    #0A1220;
  --surface:  #0E1728;
  --surface-2:#121E33;
  --ink:      #EAF0F8;
  --ink-soft: #B6C2D4;
  --muted:    #7689A0;
  --line:     rgba(180,198,224,0.16);
  --line-soft:rgba(180,198,224,0.08);
  --accent:   #5C8AE6;
  --accent-d: #93B4F0;
  --accent-tint:#16233B;
  --heading:  #EAF0F8;
  --accent-on-dark:#93B4F0;
  --accent-bright: #7FA8F7;
  /* Brand purple at 8.1:1 on white drops to 1.9:1 here, so the wordmark on the
     page background lightens (7.5:1). --brand-purple itself stays the print ink
     — it is only ever used on the always-white footer logo plate. */
  --brand-wordmark: #D48CCE;
  /* Icon chips: same four hues, re-tuned for dark surfaces. */
  --ic-navy:   #93B4F0;  --ic-navy-bg:   #16233B;
  --ic-purple: #D48CCE;  --ic-purple-bg: #2A1428;
  --ic-blue:   #6FBCE8;  --ic-blue-bg:   #0E2233;
  --ic-green:  #62C6A8;  --ic-green-bg:  #0D2620;
}

/* ---- Reset ---- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; padding: 0; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  font-family: var(--f-sans);
  background: var(--paper);
  color: var(--ink);
  font-size: var(--fs-read);
  line-height: var(--lh-body);
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}
/* height:auto pairs with the explicit width/height attributes now on every
   <img> (spec §5): without it, max-width:100% shrinks the width while the
   height attribute holds, squashing the image. The three rules that
   deliberately set an img height — .brand .mark img, .foot-certs .cchip img,
   .acc-chip img — are all more specific and still win. */
img { display: block; max-width: 100%; height: auto; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; cursor: pointer; background: none; border: none; }
::selection { background: var(--accent); color: #fff; }

/* ============================================================
   ACCESSIBILITY — focus ring · skip link · sr-only
   ============================================================ */

/* One ring, two palettes. Everything reads --focus-ring; only the dark and
   always-coloured bands remap it (next rule), so component rules never have to
   know which background they are sitting on. */
:root {
  --focus-ring: var(--accent);
  --focus-ring-w: 3px;
  --focus-ring-offset: 2px;
}

/* Navy-on-navy is invisible: on the dark footer and on the permanently-dark
   bands (.flow/.qc/.proc/.actives → var(--surface-dark)), swap in the light
   blue those sections already use for their accent labels. 5.2:1 on --ink,
   5.8:1 on --surface-dark. */
.site-footer, .flow, .qc, .proc, .actives { --focus-ring: var(--accent-on-dark); }
/* .ctaband is the exception: its background IS var(--accent), so
   --accent-on-dark manages only 3.07:1 there — nominally over the 3:1 floor for
   a non-text indicator, but too thin to rely on. White is this band's
   deliberate, already-hardcoded foreground (see .ctaband at the end of this
   file) and gives 10.4:1. */
.ctaband { --focus-ring: #fff; }

/* :focus-visible only — pointer users never see a ring, keyboard users always
   do. outline (not box-shadow) so it wraps inline and wrapped elements, follows
   the element's own border-radius, and is not overpainted by a card shadow.
   [tabindex="-1"] is excluded: those are script focus targets (e.g. the skip
   link's destination), not interactive controls. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible,
[contenteditable]:focus-visible,
[tabindex]:not([tabindex="-1"]):focus-visible,
.btn:focus-visible,
.brand:focus-visible,
.nav-links a:focus-visible,
.lang-toggle button:focus-visible,
.burger:focus-visible,
.site-footer a.fl:focus-visible,
.foot-social a:focus-visible,
.chips-input button:focus-visible,
.cap-row:focus-visible,
.cap-row .go:focus-visible,
.prod-cell:focus-visible,
.skip-link:focus-visible {
  outline: var(--focus-ring-w) solid var(--focus-ring);
  outline-offset: var(--focus-ring-offset);
}

/* Inset the ring where an ancestor would clip it: .lang-toggle is
   overflow:hidden, and .cap-subnav scrolls horizontally (overflow-x:auto makes
   the block axis a scrollport too). A negative offset keeps the whole ring
   inside the element's own border box. */
.lang-toggle button:focus-visible,
.cap-subnav a:focus-visible { outline-offset: calc(-1 * var(--focus-ring-w)); }

/* Suppress the UA ring on pointer focus. The :focus-visible inside :not()
   makes the whole selector invalid — and therefore ignored — in browsers that
   do not support it, so nothing loses its ring by accident. */
a:focus:not(:focus-visible),
button:focus:not(:focus-visible),
input:focus:not(:focus-visible),
select:focus:not(:focus-visible),
textarea:focus:not(:focus-visible),
summary:focus:not(:focus-visible),
[tabindex]:focus:not(:focus-visible) { outline: none; }

/* Script focus targets (the #main a skip link points at) must never flash a
   ring around the whole page. */
[tabindex="-1"]:focus { outline: none; }

/* ---- Skip link (first child of <body>, spec §2) ----
   Parked just off the top edge rather than display:none, so it stays in the tab
   order. A skip link that renders under an open overlay is worse than none at
   all, so its z-index sits above every stacking layer the site declares. The
   whole stack, audited across site.css and assets/pages/*.css:

       1 / 2   image-slot internals (.spill, its toolbar) — shadow-root local
       2       .drawer-close — local to .drawer-wrap        (pages/products.css)
      10       image-slot[data-reframe] — host element       (assets/image-slot.js)
      40       .cap-subnav — sticky section sub-nav          (this file)
      50       .filterbar — sticky product filter bar        (pages/products.css)
      60       .ac-menu — search autocomplete dropdown       (pages/products.css)
      99       .mobile-menu                                  (this file)
     100       .site-header — sticky                         (this file)
     200       .guide-wrap — guided-picker modal             (pages/products.css)
     210       .drawer-wrap — product detail drawer          (pages/products.css)
     900       .skip-link — must clear all of the above      (this file)
    1000+      Tidio chat widget — third-party, injected at runtime

   200 was the old value: it lost to .drawer-wrap outright and only tied with
   .guide-wrap, where DOM order handed the win to the modal. 900 clears both
   with room for a future overlay and still leaves the chat widget on top.
   Deliberately a bare integer, not a --z-* token scale: two thirds of the
   layers above live in per-page stylesheets, so tokenising only this file
   would leave a half-migrated system that drifts silently. One integer plus
   this list is the honest version for a static site. */
.skip-link {
  --focus-ring: var(--accent-on-dark);
  position: fixed; top: 0; left: 0; z-index: 900;
  transform: translateY(-120%);
  background: var(--ink); color: var(--paper);
  font-family: var(--f-mono); font-size: var(--fs-xs); font-weight: var(--fw-med);
  letter-spacing: var(--track-wide); text-transform: uppercase;
  padding: 12px 20px;
  border-right: 1px solid var(--accent-on-dark);
  border-bottom: 1px solid var(--accent-on-dark);
  border-radius: 0 0 var(--radius) 0;
  box-shadow: var(--shadow-lg);
}
.skip-link:focus { transform: none; }
@media (prefers-reduced-motion: no-preference) {
  .skip-link { transition: transform var(--dur-base) var(--ease); }
}

/* ---- Screen-reader-only utility ---- */
.sr-only {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---- Language visibility ---- */
:root[data-lang="en"] .zh { display: none !important; }
:root[data-lang="zh"] .en { display: none !important; }
:root[data-lang="zh"] body { letter-spacing: 0; }

/* ---- Menu legibility: ZH glyphs read a touch larger than mono EN ---- */
.nav-links a .zh, .mobile-menu a .zh { font-size: 1.06em; }
:root[data-lang="zh"] .nav-links a { letter-spacing: 0.02em; }

/* ---- Tablet → laptop: nudge reading + UI sizes up (headers untouched) ---- */
@media (min-width: 760px) and (max-width: 1366px) {
  body { --fs-read: 18.5px; }
  .lead { --fs-lead: clamp(19px, 1.9vw, 23px); }
  .nav-links a { font-size: var(--fs-base); }
  .lang-toggle button { font-size: var(--fs-sm); }
  .btn { font-size: var(--fs-base); }
  .eyebrow { font-size: var(--fs-sm); }
}

/* ---- Layout primitives ---- */
.wrap { max-width: var(--maxw); margin: 0 auto; padding-inline: var(--gutter); }
.wrap-wide { max-width: 1480px; margin: 0 auto; padding-inline: var(--gutter); }
section { position: relative; }

/* ---- Type ---- */
.eyebrow {
  font-family: var(--f-mono);
  font-size: var(--fs-sm);
  font-weight: var(--fw-med);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--accent);
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.eyebrow .idx { color: var(--muted); }
.eyebrow.on-dark { color: var(--accent-d); }

/* keep small accent labels readable on the dark sections */
.proc .eyebrow, .actives .eyebrow, .flow .eyebrow, .qc .eyebrow,
.proc-step .ps-n, .act .an, .flow-item .fn, .qc-pipeline .qcp-gn {
  color: var(--accent-on-dark) !important;
}

/* One heading weight across the whole site. It was 300 here and 700 on the
   blog's .editorial variant — two identities. Light display type read as
   generic and undersold a precision manufacturer. */
h1,h2,h3 { font-family: var(--f-display); font-weight: var(--fw-head); line-height: var(--lh-display); letter-spacing: var(--track-tight); text-wrap: balance; color: var(--heading); }
.display {
  font-size: var(--ds-4xl);
  font-weight: var(--fw-head);
  line-height: var(--lh-display);
  letter-spacing: var(--track-tight);
}
.h-xl { font-size: var(--ds-3xl); font-weight: var(--fw-head); }
.h-lg { font-size: var(--ds-xl); }
.h-md { font-size: var(--ds-md); }
/* CJK takes the same weight; only the negative tracking is dropped, since
   Han glyphs need their full advance width. */
:root[data-lang="zh"] .display,
:root[data-lang="zh"] .h-xl { letter-spacing: 0; }

.lead {
  font-size: var(--fs-lead);
  line-height: var(--lh-body);
  color: var(--ink-soft);
  text-wrap: pretty;
  font-weight: 400;
}
.muted { color: var(--muted); }
.serif-i { font-family: var(--f-display); font-style: italic; font-weight: var(--fw-head); }
.accent-text { color: var(--accent-bright); }

/* ---- Buttons ---- */
.btn {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--f-sans);
  font-size: var(--fs-base); font-weight: var(--fw-head);
  letter-spacing: var(--track-wide); text-transform: uppercase;
  padding: 12px 24px;
  border-radius: var(--radius);
  transition: all var(--dur-base) var(--ease);
  white-space: nowrap;
}
.btn .arrow { transition: transform var(--dur-base) ease; }
.btn:hover .arrow { transform: translateX(4px); }
.btn-primary { background: var(--ink); color: var(--paper); }
.btn-primary:hover { background: var(--accent); color: #fff; }
.btn-accent { background: var(--accent); color: #fff; }
.btn-accent:hover { background: var(--accent-d); }
.btn-ghost { border: 1px solid var(--line); color: var(--ink); }
.btn-ghost:hover { border-color: var(--ink); background: var(--ink); color: var(--paper); }

/* ---- Hairline / divider ---- */
.rule { height: 1px; background: var(--line); border: 0; }
.rule-soft { height: 1px; background: var(--line-soft); border: 0; }

/* ---- Reveal animation ---- */
@media (prefers-reduced-motion: no-preference) {
  html.reveal-armed .reveal { opacity: 0; transform: translateY(18px); transition: opacity var(--dur-reveal) ease, transform var(--dur-reveal) var(--ease); }
  html.reveal-armed .reveal.in { opacity: 1; transform: none; }
  .reveal[data-d="1"] { transition-delay: .08s; }
  .reveal[data-d="2"] { transition-delay: .16s; }
  .reveal[data-d="3"] { transition-delay: .24s; }
  .reveal[data-d="4"] { transition-delay: .32s; }
}

/* ============================================================
   HEADER / NAV
   ============================================================ */
.site-header {
  position: sticky; top: 0; z-index: 100;
  background: var(--surface-2);
  border-bottom: 1px solid var(--line-soft);
}
.nav {
  display: flex; align-items: center; justify-content: space-between;
  height: 76px;
}
.brand { display: flex; align-items: center; gap: 12px; }
.brand .mark {
  width: 38px; height: 38px; position: relative; flex: none;
}
.brand .mark svg, .brand .mark img { width: 100%; height: 100%; display: block; }
.brand .name {
  font-family: var(--f-sans); font-size: var(--fs-lg); font-weight: var(--fw-bold);
  letter-spacing: 0.01em; line-height: var(--lh-none); color: var(--brand-wordmark); text-transform: uppercase;
}
.brand .name b { font-weight: var(--fw-bold); }
.brand .sub {
  font-family: var(--f-mono); font-size: var(--fs-nano); letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--muted); display: block; margin-top: 4px;
  font-weight: var(--fw-med);
}
/* "INDUSTRIES" sits under the wordmark exactly as it does in the printed mark. */
.brand .sub .ind { color: var(--brand-wordmark); }
.nav-links { display: flex; align-items: center; gap: 4px; }
.nav-links a {
  font-family: var(--f-sans); font-size: var(--fs-base); font-weight: var(--fw-head);
  letter-spacing: var(--track-wide); text-transform: uppercase;
  padding: 8px 12px; border-radius: var(--radius);
  color: var(--ink-soft); transition: color var(--dur-base), background var(--dur-base);
  position: relative;
}
.nav-links a:hover { color: var(--ink); }
.nav-links a.active { color: var(--accent); }
.nav-right { display: flex; align-items: center; gap: 8px; }
.lang-toggle {
  display: flex; align-items: center;
  border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden;
}
.lang-toggle button {
  font-family: var(--f-mono); font-size: var(--fs-sm); font-weight: var(--fw-med);
  padding: 8px 12px; letter-spacing: var(--track-wide); color: var(--muted);
  transition: all var(--dur-base);
}
.lang-toggle button.on { background: var(--ink); color: var(--paper); }
.nav-cta { display: inline-flex; font-size: var(--fs-sm); font-weight: var(--fw-bold); }
.burger { display: none; width: 40px; height: 40px; align-items: center; justify-content: center; }
.burger span { display:block; width: 20px; height: 1.5px; background: var(--ink); position: relative; }
.burger span::before, .burger span::after { content:''; position:absolute; left:0; width:20px; height:1.5px; background: var(--ink); }
.burger span::before { top: -6px; } .burger span::after { top: 6px; }

/* Open state — site.js toggles .is-open on the burger in step with
   .mobile-menu.open, but nothing ever styled it, so the bars just sat there.
   The three bars become an X: the middle bar (the <span> itself) goes
   transparent — background, not opacity or scale, because the other two bars
   are its own pseudo-elements and would fade or shrink with it — while
   ::before and ::after slide onto the centre line and cross at ±45°.
   The geometry is declared unconditionally so the shape still flips to an X
   when motion is off; only the tweening is opt-in. The transition lives here
   rather than on the base rule for the same reason. */
.burger.is-open span { background: transparent; }
.burger.is-open span::before { top: 0; transform: rotate(45deg); }
.burger.is-open span::after  { top: 0; transform: rotate(-45deg); }
@media (prefers-reduced-motion: no-preference) {
  .burger span,
  .burger span::before,
  .burger span::after { transition: var(--dur-base); }
}

/* Closed state is visibility:hidden, not just opacity:0 — a merely transparent
   element keeps every link inside it focusable and announced, so the closed
   menu used to sit in the tab order on every viewport. visibility is
   transitionable (it flips to hidden only at the END of the .3s), so the
   fade-out is unchanged and this degrades gracefully without needing
   @starting-style / transition-behavior: allow-discrete. */
.mobile-menu {
  position: fixed; inset: 76px 0 0; z-index: 99;
  background: var(--paper); padding: var(--gutter);
  transform: translateY(-8px); opacity: 0; pointer-events: none;
  visibility: hidden;
  transition: transform var(--dur-slow), opacity var(--dur-slow), visibility var(--dur-slow);
  display: flex; flex-direction: column; gap: 4px;
}
.mobile-menu.open { transform: none; opacity: 1; pointer-events: auto; visibility: visible; }
.mobile-menu a {
  font-family: var(--f-display); font-size: var(--fs-2xl); font-weight: var(--fw-med);
  padding: 12px 0; border-bottom: 1px solid var(--line-soft);
}
/* "Start a Project" inside the mobile menu — same role as .btn-primary, sized
   for the 30px menu type and opted out of the shared link hairline. */
.mobile-menu a.mm-cta {
  margin-top: 20px;
  background: var(--ink); color: var(--paper);
  border-bottom: 0; border-radius: var(--radius);
  font-family: var(--f-sans); font-size: var(--fs-md); font-weight: var(--fw-bold);
  letter-spacing: var(--track-wide); text-transform: uppercase;
  padding: 20px 20px; text-align: center;
  transition: background var(--dur-base) var(--ease), color var(--dur-base);
}
.mobile-menu a.mm-cta:hover { background: var(--accent); color: #fff; }

@media (max-width: 940px) {
  .nav-links, .nav-cta { display: none; }
  .burger { display: flex; }
}
/* Above the burger breakpoint the menu can never be opened, so hard-hide it —
   whatever class site.js happens to have left on it across a resize. */
@media (min-width: 941px) {
  .mobile-menu, .mobile-menu.open { display: none; }
}

/* ============================================================
   FOOTER
   ============================================================ */
.site-footer { background: var(--ink); color: var(--paper); padding: 88px 0 32px; margin-top: 0; }
:root[data-theme="dark"] .site-footer { background: #090B0C; }
.footer-grid { display: grid; grid-template-columns: 1.6fr 1fr 1fr 1fr; gap: 48px; }
/* The corporate mark is purple-on-white and only reaches 2.17:1 on the footer
   navy, so it sits on its own white plate — the same way the client's own
   mock-up pastes the printed logo onto the dark band. */
.site-footer .brand {
  margin-bottom: 16px; background: #fff; border-radius: var(--radius);
  padding: 12px 18px 12px 14px; align-self: start; width: fit-content;
}
.site-footer .brand .name { color: var(--brand-purple); }
.site-footer .brand .sub { color: var(--ink-soft); }
.site-footer .brand .sub .ind { color: var(--brand-purple); }
.foot-addr { color: rgba(255,255,255,.5); }
.site-footer .ftitle {
  font-family: var(--f-mono); font-size: var(--fs-micro); letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--muted); margin-bottom: 16px;
}
.site-footer a.fl { display: block; color: rgba(255,255,255,0.7); padding: 6px 0; font-size: var(--fs-base); transition: color var(--dur-base); }
.site-footer a.fl:hover { color: #fff; }
/* "Frame it like a box?" — the project CTA reads as a button, not a list item. */
.site-footer a.fl.fl-cta {
  display: inline-flex; align-items: center; gap: 8px; margin: 10px 0;
  padding: 10px 16px; border: 1px solid rgba(255,255,255,0.34); border-radius: var(--radius);
  color: #fff; font-weight: var(--fw-med);
  transition: background var(--dur-base), border-color var(--dur-base), transform var(--dur-base);
}
.site-footer a.fl.fl-cta:hover { background: #fff; border-color: #fff; color: var(--ink); transform: translateY(-1px); }
.site-footer a.fl.fl-cta .arrow { transition: transform var(--dur-base); }
.site-footer a.fl.fl-cta:hover .arrow { transform: translateX(3px); }
.footer-bottom { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 16px; margin-top: 64px; padding-top: 24px; border-top: 1px solid rgba(255,255,255,0.12); }
.footer-bottom, .footer-bottom a { font-family: var(--f-mono); font-size: var(--fs-xs); letter-spacing: var(--track-wide); color: var(--muted); text-transform: uppercase; }
.foot-desc { color: rgba(255,255,255,0.6); max-width: 34ch; font-size: var(--fs-base); line-height: var(--lh-body); }
.foot-reg { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 20px; margin: 24px 0 0; }
.foot-reg div { display: flex; flex-direction: column; }
.foot-reg dt { font-family: var(--f-mono); font-size: var(--fs-nano); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--muted); }
.foot-reg dd { font-family: var(--f-mono); font-size: var(--fs-sm); color: rgba(255,255,255,0.82); margin: 2px 0 0; letter-spacing: 0.02em; }
.foot-made { display: inline-flex; align-items: center; gap: 8px; margin-top: 20px; font-family: var(--f-mono); font-size: var(--fs-micro); letter-spacing: var(--track-label); text-transform: uppercase; color: rgba(255,255,255,0.85); }
.foot-made svg { width: 15px; height: 15px; color: var(--accent-on-dark); flex: none; }
/* Client note (18 Jul): "less white spaces to make logos bigger for these 3
   logos." Chip height 52 -> 76px and the padding tightened, so the marks
   themselves fill the plate instead of floating in it. */
.foot-certs { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 22px; }
.foot-certs .cchip { background: #fff; border-radius: var(--radius-lg); padding: 4px 8px; height: 76px; display: inline-flex; align-items: center; justify-content: center; }
.foot-certs .cchip img { height: 100%; width: auto; display: block; object-fit: contain; }
/* "Can make these 2 logos like official logos, blue and red?" — Facebook blue
   and YouTube red, on a white disc so both keep their official appearance
   against the navy footer. */
.foot-social { display: flex; gap: 12px; margin-top: 20px; }
.foot-social a { width: 40px; height: 40px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; background: #fff; transition: transform var(--dur-base), box-shadow var(--dur-base); }
.foot-social a:hover { transform: translateY(-2px); box-shadow: 0 6px 18px rgba(0,0,0,.35); }
.foot-social a.s-fb { color: #1877F2; }
.foot-social a.s-yt { color: #FF0000; }
/* Both glyphs are single filled paths whose counters knock through to the white
   disc, so `color` alone reproduces each official mark: a blue "f", and the red
   YouTube badge with its play triangle cut out. */
.foot-social svg { width: 24px; height: 24px; }
@media (max-width: 860px) { .footer-grid { grid-template-columns: 1fr 1fr; gap: 32px; } .foot-brand { grid-column: 1 / -1; } }
@media (max-width: 520px) { .footer-grid { grid-template-columns: 1fr; } }

/* ---- utility ---- */
.col { display: flex; flex-direction: column; }
.center { text-align: center; }
.tag {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--f-mono); font-size: var(--fs-micro); letter-spacing: var(--track-wide);
  text-transform: uppercase; color: var(--ink-soft);
  border: 1px solid var(--line); border-radius: var(--radius-pill); padding: 6px 12px;
}
.dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); flex: none; }

/* reveal safety net — guarantees visibility even if paint/transition stalls */
html.reveal-done .reveal { opacity: 1 !important; transform: none !important; transition: none !important; }

/* ============================================================
   MOTION — dynamic layer
   stagger · image-settle · nav-underline · hover-lift · marquee
   ============================================================ */
@media (prefers-reduced-motion: no-preference) {
  /* staggered cascade for grid / list children */
  html.reveal-armed .rise {
    opacity: 0; transform: translateY(24px);
    transition: opacity var(--dur-reveal) ease, transform var(--dur-reveal) var(--ease);
    transition-delay: calc(var(--si, 0) * 65ms);
  }
  html.reveal-armed .rise.in { opacity: 1; transform: none; }

  /* prominent images settle (scale) as their container reveals */
  html.reveal-armed .reveal image-slot {
    transform: scale(1.055); transform-origin: center;
    transition: transform var(--dur-reveal) var(--ease);
  }
  html.reveal-armed .reveal.in image-slot { transform: none; }

  /* hero spec card gently floats */
  .hero-spec { animation: hs-float 5.5s ease-in-out infinite; }
  @keyframes hs-float {
    0%, 100% { transform: translate(-14px, 14px); }
    50%      { transform: translate(-14px, 6px); }
  }
}
/* safety net — never leave motion elements stuck or scaled */
html.reveal-done .rise { opacity: 1 !important; transform: none !important; transition: none !important; }
html.reveal-done .reveal image-slot { transform: none !important; }

/* ---- Animated nav underline (hover + active) ---- */
.nav-links a::after {
  content: ''; position: absolute; left: 14px; right: 14px; bottom: 3px; height: 2px;
  background: var(--accent); border-radius: var(--radius);
  transform: scaleX(0); transform-origin: left;
  transition: transform var(--dur-slow) var(--ease);
}
.nav-links a:hover::after { transform: scaleX(1); }
.nav-links a.active::after { transform: scaleX(1); }

/* ---- Hover lift on standalone cards ---- */
.pcard, .val, .qb, .cert-item {
  transition: transform var(--dur-slow) var(--ease), box-shadow var(--dur-slow) ease, background var(--dur-slow) ease;
}
.pcard:hover, .art-card:hover { transform: translateY(-6px); box-shadow: var(--shadow-lg); }
.val:hover, .qb:hover, .cert-item:hover { transform: translateY(-4px); }

/* ---- Button press feedback ---- */
.btn:active { transform: translateY(1px) scale(.99); }

/* ---- Certifications marquee ---- */
.certbar .wrap-wide, .certbar .wrap { overflow: hidden; }
.cb-inner { display: flex; width: max-content; }
.cb-track {
  display: flex; align-items: center; flex: none;
  gap: var(--gap-lg); padding-right: var(--gap-lg);
}
@media (prefers-reduced-motion: no-preference) {
  .cb-inner.run { animation: cb-scroll 42s linear infinite; }
  .certbar:hover .cb-inner.run { animation-play-state: paused; }
}
@keyframes cb-scroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }

/* ============================================================
   SHARED PAGE COMPONENTS (moved out of per-page <style> blocks)
   ============================================================ */

/* ---- breadcrumb (all pages) ---- */
.breadcrumb { font-family: var(--f-mono); font-size: var(--fs-xs); letter-spacing: var(--track-wide); text-transform: uppercase; color: var(--muted); display: flex; gap: 8px; flex-wrap: wrap; }
.breadcrumb a:hover { color: var(--accent); }

/* ---- page hero (about/services/factory/products/contact) ---- */
.page-hero { padding: var(--gap-2xl) 0 var(--gap-md); }
.page-hero .display { margin-top: 20px; max-width: 17ch; }
.page-hero .lead { margin-top: 24px; max-width: 54ch; }

/* editorial variant (blog/news): shorter, bold headline instead of the large light .display */
.page-hero.editorial { padding-bottom: var(--gap-sm); }
.page-hero.editorial .display { margin-top: 16px; max-width: none; font-size: var(--ds-3xl); font-weight: var(--fw-head); line-height: var(--lh-heading); letter-spacing: var(--track-tight); }
.page-hero.editorial .lead { margin-top: 20px; max-width: 52ch; }

/* ---- section rhythm (about/services/factory default; index/contact/products keep their own tighter/looser override) ---- */
.sec { padding: var(--sec-y) 0; }
.sec-flush-top { padding: 0 0 var(--gap-2xl); }

/* ---- section header (title + description split) ---- */
.sec-head { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; align-items: end; margin-bottom: var(--gap-lg); }
.sec-head .h-xl { margin-top: 16px; }
.sec-head p { color: var(--ink-soft); }
@media (max-width: 760px) { .sec-head { grid-template-columns: 1fr; gap: 16px; } }

/* ---- blog article page (blog-27-tips, blog-eye-cream, blog-oem-manufacturing, blog-routine-order) ---- */
.art-hero { padding: var(--gap-xl) 0 var(--gap-sm); border-bottom: 1px solid var(--line); }
.art-cat { font-family: var(--f-mono); font-size: var(--fs-micro); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--accent); }
.art-hero h1 { font-size: var(--ds-3xl); font-weight: var(--fw-head); line-height: var(--lh-heading); letter-spacing: var(--track-tight); color: var(--ink); margin: 16px 0 0; max-width: 20ch; }
.art-meta { font-family: var(--f-mono); font-size: var(--fs-xs); color: var(--muted); margin-top: 20px; display: flex; gap: 16px; }
.art-wrap { max-width: 720px; margin: 0 auto; padding-inline: var(--gutter); }
.art-body { padding: var(--gap-lg) 0 var(--gap-xl); }
.art-body p { font-size: var(--fs-md); line-height: var(--lh-loose); color: var(--ink-soft); margin-top: 20px; }
.art-body p:first-child { margin-top: 0; }
.art-body p.stand { font-size: var(--fs-lg); line-height: var(--lh-body); color: var(--ink); }
.art-body h2 { font-size: var(--ds-lg); font-weight: var(--fw-head); color: var(--ink); margin: 40px 0 0; letter-spacing: var(--track-tight); }
.art-body h3 { font-size: var(--fs-lg); font-weight: var(--fw-head); color: var(--ink); margin: 32px 0 0; }
.art-body strong { color: var(--ink); font-weight: var(--fw-bold); }
.tip { display: grid; grid-template-columns: 54px 1fr; gap: 16px; padding: 24px 0; border-top: 1px solid var(--line); align-items: start; }
.tip .tn { font-family: var(--f-mono); font-size: var(--fs-lg); color: var(--accent); font-weight: var(--fw-med); padding-top: 2px; }
.tip h3 { margin: 0; font-size: var(--fs-md); text-transform: uppercase; letter-spacing: .02em; }
.tip p { margin-top: 8px; font-size: var(--fs-md); }
.art-cta { border-top: 1px solid var(--line); margin-top: 20px; padding: var(--gap-md) 0; display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }
.art-cta .ac-t { font-size: var(--fs-md); color: var(--ink-soft); flex: 1; min-width: 240px; }
.art-back { font-family: var(--f-mono); font-size: var(--fs-xs); letter-spacing: var(--track-wide); text-transform: uppercase; color: var(--muted); }
.art-back:hover { color: var(--accent); }

/* ---- services/factory: big service blocks ---- */
.svc { border-top: 1px solid var(--line); }
.svc-row { display: grid; grid-template-columns: 0.9fr 1.1fr; gap: var(--gap-lg); padding: var(--gap-lg) 0; border-bottom: 1px solid var(--line); align-items: start; }
.svc-row:nth-child(even) .svc-media { order: 2; }
.svc-num { font-family: var(--f-mono); font-size: var(--fs-xs); color: var(--accent); letter-spacing: var(--track-wide); }
.svc-row h2 { font-size: var(--ds-2xl); margin: 12px 0 6px; }
.svc-row .zh-sub { font-family: var(--f-display); color: var(--muted); font-size: var(--fs-md); margin-bottom: 16px; }
.svc-row p.lead { max-width: 46ch; }
.svc-feat { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 24px; margin-top: 24px; }
.svc-feat .sf { display: grid; grid-template-columns: auto 1fr; gap: 12px; align-items: start; }
.svc-feat .sf svg { width: 18px; height: 18px; color: var(--accent); flex: none; margin-top: 4px; }
.svc-feat .sf span { font-size: var(--fs-base); color: var(--ink-soft); }
.svc-media image-slot { width: 100%; height: clamp(360px,32vw,480px); aspect-ratio: auto; }
@media (max-width: 840px) { .svc-row { grid-template-columns: 1fr; } .svc-row:nth-child(even) .svc-media { order: 0; } .svc-media { max-width: 480px; } }

/* ---- services/factory: process timeline vertical (dark band) ---- */
.flow { background: var(--ink); color: var(--paper); }
:root[data-theme="dark"] .flow { background: var(--surface-dark); }
.flow .eyebrow { color: var(--accent-d); } .flow h2 { color: var(--paper); }
.flow-list { margin-top: 48px; border-top: 1px solid rgba(255,255,255,.13); }
.flow-item { display: grid; grid-template-columns: 90px 1fr 1fr; gap: 32px; padding: 32px 0; border-bottom: 1px solid rgba(255,255,255,.13); align-items: baseline; transition: var(--dur-slow); }
.flow-item:hover { background: var(--surface-dark-hover); padding-inline: 16px; }
.flow-item .fn { font-family: var(--f-display); font-size: var(--fs-2xl); font-weight: var(--fw-med); font-variant-numeric: tabular-nums; color: var(--accent-d); }
.flow-item h4 { font-family: var(--f-display); font-weight: var(--fw-head); font-size: var(--fs-xl); color: var(--paper); }
.flow-item .ft { font-family: var(--f-mono); font-size: var(--fs-micro); letter-spacing: var(--track-wide); text-transform: uppercase; color: var(--muted); margin-top: 6px; }
.flow-item p { font-size: var(--fs-base); color: rgba(255,255,255,.62); }
@media (max-width: 720px) { .flow-item { grid-template-columns: 60px 1fr; } .flow-item p { grid-column: 2; } }

/* ---- services/factory: specs strip ---- */
.specs { display: grid; grid-template-columns: repeat(4,1fr); gap: 1px; background: var(--line); border: 1px solid var(--line); }
.spec { background: var(--surface-2); padding: 32px 24px; }
.spec .sk { font-family: var(--f-mono); font-size: var(--fs-micro); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--muted); }
.spec .sv { font-family: var(--f-display); font-weight: var(--fw-med); font-variant-numeric: tabular-nums; font-size: var(--ds-lg); margin-top: 8px; }
.spec p { font-size: var(--fs-sm); color: var(--ink-soft); margin-top: 6px; }
@media (max-width: 760px) { .specs { grid-template-columns: 1fr 1fr; } }

/* ---- services/factory: faq ---- */
.faq { border-top: 1px solid var(--line); }
.faq details { border-bottom: 1px solid var(--line); }
.faq summary { list-style: none; cursor: pointer; padding: 24px 0; display: flex; justify-content: space-between; align-items: center; gap: 20px; font-family: var(--f-display); font-weight: var(--fw-head); font-size: var(--ds-sm); }
.faq summary::-webkit-details-marker { display: none; }
.faq summary .pm { font-family: var(--f-mono); color: var(--accent); font-size: var(--fs-xl); flex: none; transition: transform var(--dur-base); }
.faq details[open] summary .pm { transform: rotate(45deg); }
.faq .ans { padding: 0 0 24px; max-width: 70ch; color: var(--ink-soft); font-size: var(--fs-base); }

/* ---- services/factory: hero gallery ---- */
.gallery { display: grid; grid-template-columns: repeat(12,1fr); grid-auto-rows: 120px; gap: 1px; background: var(--line); border: 1px solid var(--line); }
.gallery image-slot { width: 100%; height: 100%; }
.g1 { grid-column: span 6; grid-row: span 3; } .g2 { grid-column: span 6; grid-row: span 2; }
.g3 { grid-column: span 3; grid-row: span 1; } .g4 { grid-column: span 3; grid-row: span 1; }
@media (max-width: 760px) { .gallery { grid-auto-rows: 100px; } .g1,.g2,.g3,.g4 { grid-column: span 6; grid-row: span 2; } }

/* ---- factory: production zones ---- */
.zones { border-top: 1px solid var(--line); }
.zone { display: grid; grid-template-columns: 80px 1fr 1.2fr; gap: 32px; padding: 32px 0; border-bottom: 1px solid var(--line); align-items: start; transition: var(--dur-slow); }
.zone:hover { background: var(--surface); padding-inline: 16px; }
.zone .zn { font-family: var(--f-mono); font-size: var(--fs-xs); color: var(--accent); letter-spacing: var(--track-wide); padding-top: 6px; }
.zone h3 { font-family: var(--f-display); font-weight: var(--fw-head); font-size: var(--ds-lg); }
.zone .zh-sub { font-family: var(--f-display); color: var(--muted); font-size: var(--fs-base); margin-top: 4px; }
.zone p { color: var(--ink-soft); font-size: var(--fs-base); }
.zone .ztags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; }
.zone .ztags span { font-family: var(--f-mono); font-size: var(--fs-micro); letter-spacing: var(--track-wide); text-transform: uppercase; color: var(--ink-soft); border: 1px solid var(--line); border-radius: var(--radius-pill); padding: 4px 12px; }
@media (max-width: 760px) { .zone { grid-template-columns: 1fr; gap: 8px; } }

/* ---- services/factory: QC band (dark) ---- */
.qc { background: var(--ink); color: var(--paper); }
:root[data-theme="dark"] .qc { background: var(--surface-dark); }
.qc .eyebrow { color: var(--accent-d); } .qc h2 { color: var(--paper); }
.qc-head { max-width: 64ch; }

/* ---- factory: QC pipeline diagram ----
   Four checkpoints as one horizontal "batch travels through gates" control.
   --ic-green (see token block) is used exactly as the icon-chip system was
   designed: as the icon/border on its own --ic-green-bg tint, never as a
   standalone text or icon colour laid directly on the .qc band — that band's
   own background differs by theme (var(--ink) light / var(--surface-dark)
   dark) and --ic-green is not vetted against either. Every other text/border
   value here reuses the rgba(255,255,255,x) scale .qc-steps already relied
   on, which is theme-invariant on this permanently-dark band. */
.qc-pipeline { margin-top: var(--gap-lg); }

.qcp-legend { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 14px; margin-bottom: 40px; }
.qcp-end { font-family: var(--f-mono); font-size: var(--fs-xs); letter-spacing: var(--track-label); text-transform: uppercase; color: rgba(255,255,255,.5); }
.qcp-end b { color: rgba(255,255,255,.92); font-weight: var(--fw-med); }
.qcp-status { display: inline-flex; align-items: center; gap: 9px; font-family: var(--f-mono); font-size: var(--fs-xs); letter-spacing: var(--track-wide); color: rgba(255,255,255,.7); }
.qcp-status .chk { width: 20px; height: 20px; border-radius: 50%; background: var(--ic-green-bg); color: var(--ic-green); display: inline-flex; align-items: center; justify-content: center; flex: none; }
.qcp-status .chk svg { width: 12px; height: 12px; stroke: currentColor; fill: none; stroke-width: 3; }
.qcp-status b { color: rgba(255,255,255,.95); font-weight: var(--fw-head); }

.qcp-track { position: relative; padding: 0 4px; }
.qcp-rail { position: relative; height: 3px; background: rgba(255,255,255,.14); border-radius: var(--radius-pill); margin: 0 40px; }
.qcp-fill { position: absolute; inset: 0; width: 0%; background: var(--ic-green); border-radius: var(--radius-pill); }
.qcp-batch { position: absolute; top: 50%; left: 0%; width: 12px; height: 12px; margin: -6px 0 0 -6px; border-radius: 50%; background: rgba(255,255,255,.95); box-shadow: 0 0 0 4px var(--ink), 0 0 0 5px var(--ic-green); }
:root[data-theme="dark"] .qcp-batch { box-shadow: 0 0 0 4px var(--surface-dark), 0 0 0 5px var(--ic-green); }
@media (prefers-reduced-motion: no-preference) {
  .qcp-fill { transition: width var(--dur-slow) var(--ease); }
  .qcp-batch { transition: left var(--dur-slow) var(--ease); }
}

.qcp-gates { display: grid; grid-template-columns: repeat(4, 1fr); margin-top: -26px; }
.qcp-gate { display: flex; flex-direction: column; align-items: center; gap: 10px; width: 100%; padding: 0 4px 6px; text-align: center; }
.qcp-ring { width: 54px; height: 54px; border-radius: 50%; background: rgba(255,255,255,.06); border: 2px solid rgba(255,255,255,.16); display: flex; align-items: center; justify-content: center; color: rgba(255,255,255,.55); flex: none; }
.qcp-ring svg { width: 24px; height: 24px; stroke: currentColor; fill: none; stroke-width: 2.2; }
.qcp-gn { font-family: var(--f-mono); font-size: var(--fs-nano); letter-spacing: var(--track-label); }
.qcp-gt { font-size: var(--fs-sm); font-weight: var(--fw-med); color: rgba(255,255,255,.82); }
.qcp-gate:hover .qcp-ring, .qcp-gate.is-pass .qcp-ring, .qcp-gate.is-active .qcp-ring { background: var(--ic-green-bg); border-color: var(--ic-green); color: var(--ic-green); }
.qcp-gate.is-active .qcp-ring { box-shadow: 0 0 0 6px var(--ic-green-bg); }
.qcp-gate.is-active .qcp-gt { color: rgba(255,255,255,.96); font-weight: var(--fw-head); }
@media (prefers-reduced-motion: no-preference) {
  .qcp-ring { transition: background var(--dur-base) var(--ease), border-color var(--dur-base) var(--ease), color var(--dur-base) var(--ease), transform var(--dur-base) var(--ease), box-shadow var(--dur-base) var(--ease); }
  .qcp-gate.is-active .qcp-ring { transform: scale(1.1); }
}

.qcp-detail { margin-top: 36px; padding-top: 30px; border-top: 1px solid rgba(255,255,255,.13); }
.qcp-panel { display: grid; grid-template-columns: auto 1fr; gap: 20px; align-items: flex-start; }
/* An author display rule beats the UA [hidden]{display:none} regardless of
   specificity (author normal always wins over UA normal) — without this,
   qc-pipeline.js's p.hidden = idx !== i does nothing visually and every
   panel renders stacked at once. */
.qcp-panel[hidden] { display: none !important; }
.qcp-dicon { width: 56px; height: 56px; border-radius: var(--radius-lg); background: var(--ic-green-bg); color: var(--ic-green); display: flex; align-items: center; justify-content: center; flex: none; }
.qcp-dicon svg { width: 26px; height: 26px; stroke: currentColor; fill: none; stroke-width: 2.2; }
.qcp-dn { font-family: var(--f-mono); font-size: var(--fs-xs); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--accent-on-dark); }
.qcp-panel h4 { font-family: var(--f-sans); font-weight: var(--fw-head); font-size: var(--fs-lg); color: rgba(255,255,255,.96); margin-top: 6px; }
.qcp-panel p { font-size: var(--fs-base); color: rgba(255,255,255,.6); margin-top: 8px; max-width: 58ch; }

@media (max-width: 620px) {
  .qcp-rail, .qcp-batch { display: none; }
  .qcp-gates { grid-template-columns: repeat(2, 1fr); row-gap: 26px; margin-top: 0; }
  .qcp-ring { width: 46px; height: 46px; }
  .qcp-ring svg { width: 20px; height: 20px; }
  .qcp-panel { grid-template-columns: 1fr; }
  .qcp-dicon { width: 44px; height: 44px; }
}

/* ---- factory: capabilities production-line stepper ----
   Replaces the former static 4-col .cap-grid (see git history / the removed
   rules in assets/pages/factory.css) with an interactive 12-stage conveyor:
   a rail + numbered nodes with an animated belt fill on desktop, autoplay
   and click-to-jump, collapsing under ~900px (12 stages need more room than
   the 4-gate .qcp- pattern this mirrors, hence the wider breakpoint than
   .qcp-'s 620px) to a 4-col tap grid — same markup, no horizontal scroll at
   any width. All EN/ZH copy lives in factory.html's .capl-node / .capl-panel
   markup already; capabilities.js only toggles state (classes, aria, hidden,
   numeral text), never translated text, so [data-lang] keeps working. */
.cap-line { margin-top: var(--gap-lg); }

.capl-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-bottom: 30px; }
.capl-play { display: inline-flex; align-items: center; gap: 9px; padding: 9px 16px 9px 13px; border: 1px solid var(--line); border-radius: var(--radius-pill); font-family: var(--f-mono); font-size: var(--fs-xs); letter-spacing: var(--track-wide); text-transform: uppercase; color: var(--ink-soft); transition: border-color var(--dur-base), color var(--dur-base); }
.capl-play:hover { border-color: var(--accent); color: var(--accent); }
.capl-play:disabled { opacity: .45; cursor: default; }
.capl-play:disabled:hover { border-color: var(--line); color: var(--ink-soft); }
.capl-play svg { width: 14px; height: 14px; stroke: currentColor; fill: none; stroke-width: 2.4; }
.capl-play .capl-ic-play { fill: currentColor; stroke: none; }
.capl-count { font-family: var(--f-mono); font-size: var(--fs-xs); color: var(--muted); font-variant-numeric: tabular-nums; }
.capl-count b { color: var(--ink); font-weight: var(--fw-head); }

.capl-track { position: relative; padding-top: 6px; }
.capl-rail { position: absolute; left: 20px; right: 20px; top: 29px; height: 3px; background: var(--line-soft); border-radius: var(--radius-pill); overflow: hidden; }
.capl-fill { position: absolute; inset: 0; width: 0%; background: linear-gradient(90deg, var(--accent-d), var(--accent-bright)); border-radius: var(--radius-pill); }
@media (prefers-reduced-motion: no-preference) {
  .capl-fill { transition: width var(--dur-slow) var(--ease); }
}
@media (min-width: 901px) and (prefers-reduced-motion: no-preference) {
  .capl-fill::after { content: ""; position: absolute; inset: 0; background-image: repeating-linear-gradient(115deg, rgba(255,255,255,.4) 0 6px, transparent 6px 14px); background-size: 26px 100%; animation: capl-belt 1s linear infinite; }
  @keyframes capl-belt { to { background-position: 26px 0; } }
}

.capl-nodes { position: relative; display: grid; grid-template-columns: repeat(12, 1fr); gap: 4px; }
.capl-node { display: flex; flex-direction: column; align-items: center; gap: 9px; padding: 0 2px 10px; text-align: center; border-radius: var(--radius); }
.capl-dot { width: 38px; height: 38px; border-radius: 50%; background: var(--surface-2); border: 2px solid var(--line); display: flex; align-items: center; justify-content: center; color: var(--muted); position: relative; z-index: 1; }
.capl-dot svg { width: 17px; height: 17px; stroke: currentColor; fill: none; stroke-width: 2.3; }
.capl-n { font-family: var(--f-mono); font-size: var(--fs-nano); letter-spacing: var(--track-wide); color: var(--muted); }
.capl-t { font-size: var(--fs-micro); font-weight: var(--fw-med); color: var(--ink-soft); line-height: 1.25; }
.capl-node:hover .capl-dot { border-color: var(--accent); color: var(--accent); }
.capl-node.is-pass .capl-dot { background: var(--accent-tint); border-color: var(--accent); color: var(--accent); }
.capl-node.is-active .capl-dot { background: var(--accent); border-color: var(--accent); color: #fff; box-shadow: 0 0 0 5px var(--accent-tint); }
.capl-node.is-active .capl-t { color: var(--ink); font-weight: var(--fw-head); }
.capl-node.is-active .capl-n { color: var(--accent); }
@media (prefers-reduced-motion: no-preference) {
  .capl-dot { transition: background var(--dur-base) var(--ease), border-color var(--dur-base) var(--ease), color var(--dur-base) var(--ease), transform var(--dur-base) var(--ease), box-shadow var(--dur-base) var(--ease); }
  .capl-node.is-active .capl-dot { transform: scale(1.12); }
}

.capl-detail { margin-top: 36px; padding-top: 30px; border-top: 1px solid var(--line); }
.capl-panel { display: flex; gap: 20px; align-items: flex-start; }
/* Same fix as .qcp-panel[hidden] above: an author display rule beats the UA
   [hidden]{display:none} regardless of specificity, so without this every
   panel stays visible (stacked) no matter which stage capabilities.js hides. */
.capl-panel[hidden] { display: none !important; }
.capl-dicon { width: 56px; height: 56px; border-radius: var(--radius-lg); background: var(--accent-tint); color: var(--accent); display: flex; align-items: center; justify-content: center; flex: none; }
.capl-dicon svg { width: 28px; height: 28px; stroke: currentColor; fill: none; stroke-width: 2.2; }
.capl-dn { font-family: var(--f-mono); font-size: var(--fs-xs); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--accent); }
.capl-panel h4 { font-family: var(--f-sans); font-weight: var(--fw-head); font-size: var(--fs-lg); color: var(--ink); margin-top: 6px; }
.capl-panel p { font-size: var(--fs-base); color: var(--ink-soft); margin-top: 8px; max-width: 58ch; }

@media (max-width: 900px) {
  .capl-track { padding-top: 0; }
  /* NOT position:static — .capl-fill (position:absolute; inset:0) is a DOM
     child of .capl-rail and needs .capl-rail to stay a positioned ancestor,
     or its containing block skips up to .capl-track and it (a) escapes this
     rule's overflow:hidden clip and (b) stretches to .capl-track's full
     height, which includes the grid below — a huge gradient blob over the
     nodes instead of a thin bar above them. position:relative keeps .capl-rail
     in normal flow (so it still renders above the grid, in DOM order) while
     remaining the containing block that clips and sizes the fill correctly. */
  .capl-rail { position: relative; left: auto; right: auto; top: auto; margin: 0 0 22px; }
  .capl-nodes { grid-template-columns: repeat(4, 1fr); gap: 18px 6px; }
  .capl-dot { width: 40px; height: 40px; }
  .capl-dot svg { width: 18px; height: 18px; }
  .capl-dicon { width: 44px; height: 44px; }
  .capl-dicon svg { width: 22px; height: 22px; }
}

/* ---- services/factory/contact: location ---- */
.loc { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gap-lg); align-items: center; }
.loc-card { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.loc-map { aspect-ratio: 16/10; background:
  linear-gradient(0deg, var(--line-soft) 1px, transparent 1px) 0 0/100% 40px,
  linear-gradient(90deg, var(--line-soft) 1px, transparent 1px) 0 0/40px 100%,
  var(--surface); position: relative; }
.loc-map iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; }
.loc-map .pin { position: absolute; left: 50%; top: 52%; transform: translate(-50%,-100%); display: flex; flex-direction: column; align-items: center; gap: 6px; }
.loc-map .pin svg { width: 34px; height: 34px; color: var(--accent); }
.loc-map .pin span { font-family: var(--f-mono); font-size: var(--fs-nano); letter-spacing: var(--track-label); text-transform: uppercase; background: var(--ink); color: var(--paper); padding: 4px 8px; border-radius: var(--radius); }
.loc-info { padding: 32px 32px; }
.loc-info .li { padding: 16px 0; border-top: 1px solid var(--line); }
.loc-info .li:first-child { border-top: 0; padding-top: 0; }
.loc-info .lk { font-family: var(--f-mono); font-size: var(--fs-micro); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--muted); }
.loc-info .lv { font-size: var(--fs-md); margin-top: 4px; }
@media (max-width: 820px) { .loc { grid-template-columns: 1fr; } }

/* ---- services/factory: sticky section sub-nav ---- */
.cap-subnav { position: sticky; top: 77px; z-index: 40; background: color-mix(in srgb,var(--paper) 90%,transparent);
  backdrop-filter: blur(14px) saturate(1.1); border-bottom: 1px solid var(--line); }
.cap-subnav .wrap-wide { display: flex; gap: 6px; overflow-x: auto; scrollbar-width: none; }
.cap-subnav .wrap-wide::-webkit-scrollbar { display: none; }
.cap-subnav a { font-family: var(--f-mono); font-size: var(--fs-xs); letter-spacing: var(--track-wide); text-transform: uppercase; color: var(--muted);
  padding: 16px 16px 12px; border-bottom: 2px solid transparent; white-space: nowrap; transition: color var(--dur-fast),border-color var(--dur-fast); }
.cap-subnav a:hover { color: var(--ink); }
.cap-subnav a.active { color: var(--accent); border-bottom-color: var(--accent); }
.cap-anchor { scroll-margin-top: 132px; }

/* ---- index/about: accreditation card ---- */
.accred { border: 1px solid var(--line); background: var(--surface); border-radius: var(--radius); padding: var(--gap-sm); display: flex; flex-direction: column; }
.accred .eyebrow { color: var(--accent); }
.accred-lead { color: var(--ink-soft); font-size: var(--fs-base); line-height: var(--lh-body); margin-top: 12px; max-width: 42ch; }
.accred-grid { display: grid; grid-template-columns: repeat(3,1fr); gap: 12px; margin-top: 24px; }
.acc-chip { background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius); display: flex; align-items: center; justify-content: center; padding: 12px; min-height: 100px; transition: border-color var(--dur-base), box-shadow var(--dur-base), transform var(--dur-base); text-align: center; }
.acc-chip:hover { border-color: var(--accent); box-shadow: var(--shadow-md); transform: translateY(-2px); }
.acc-chip img { max-height: 66px; max-width: 100%; width: auto; display: block; object-fit: contain; }
.acc-chip.txt { flex-direction: column; gap: 4px; }
.acc-chip.txt b { font-family: var(--f-sans); font-size: var(--fs-base); color: var(--accent); }
.acc-chip.txt span { font-family: var(--f-mono); font-size: var(--fs-nano); letter-spacing: var(--track-wide); color: var(--muted); line-height: var(--lh-tight); }
.accred-foot { font-family: var(--f-mono); font-size: var(--fs-micro); letter-spacing: var(--track-wide); text-transform: uppercase; color: var(--muted); margin-top: 20px; }
@media (max-width: 480px) { .accred-grid { grid-template-columns: 1fr 1fr; } }

/* ---- CTA band (index/about/services/factory/products) ---- */
/* text is deliberately hardcoded white — .ctaband's bg is always var(--accent), an always-colored band, so its foreground doesn't follow the light/dark --paper token */
.ctaband { background: var(--accent); color: #fff; }
.ctaband .wrap { padding: var(--gap-2xl) var(--gutter); display: grid; grid-template-columns: 1.4fr 1fr; gap: 40px; align-items: center; }
.ctaband h2 { font-size: var(--ds-3xl); color: #fff; }
.ctaband p { color: rgba(255,255,255,.85); margin-top: 16px; }
.ctaband .btn-ghost { border-color: rgba(255,255,255,.4); color: #fff; }
.ctaband .btn-primary { background: #fff; color: var(--accent-d); }
.ctaband .btn-primary:hover { background: var(--ink); color: #fff; }
@media (max-width: 760px) { .ctaband .wrap { grid-template-columns: 1fr; } }

/* ============================================================
   PROSE + 404 (privacy.html, 404.html)
   ============================================================ */

/* Long-form legal / policy copy. The reset zeroes list padding and the UA
   markers, so both are restored here rather than assumed. */
.prose { max-width: 70ch; padding: var(--gap-md) 0 var(--gap-2xl); }
.prose > * + * { margin-top: 20px; }
.prose h2 { font-size: var(--ds-md); font-weight: var(--fw-head); letter-spacing: var(--track-tight); color: var(--ink); margin-top: 48px; }
.prose h3 { font-size: var(--fs-md); font-weight: var(--fw-head); color: var(--ink); margin-top: 32px; }
.prose h2 + *, .prose h3 + * { margin-top: 12px; }
.prose p, .prose li { font-size: var(--fs-read); line-height: var(--lh-loose); color: var(--ink-soft); }
.prose ul, .prose ol { padding-left: 20px; }
.prose ul { list-style: disc; }
.prose ol { list-style: decimal; }
.prose li + li { margin-top: 8px; }
.prose li::marker { color: var(--muted); }
.prose strong, .prose b { color: var(--ink); font-weight: var(--fw-bold); }
/* underlined in body copy — colour alone is not a sufficient link cue */
.prose a { color: var(--accent); text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px; }
.prose a:hover { text-decoration-thickness: 2px; }
.prose .updated { font-family: var(--f-mono); font-size: var(--fs-xs); letter-spacing: var(--track-wide); text-transform: uppercase; color: var(--muted); }
.prose hr { height: 1px; background: var(--line); border: 0; margin-top: 40px; }

.page-404 {
  min-height: 58vh;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 16px; text-align: center;
  padding: var(--sec-y) 0;
}
.page-404 .code { font-family: var(--f-mono); font-size: clamp(64px,12vw,150px); font-weight: var(--fw-med); line-height: var(--lh-none); letter-spacing: var(--track-tight); color: var(--accent); }
.page-404 h1 { font-size: var(--ds-2xl); }
.page-404 p { color: var(--ink-soft); max-width: 46ch; }
.page-404 .btn-row { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; margin-top: 12px; }

/* ============================================================
   COOKIE CONSENT
   Injected by assets/consent.js. Nothing third-party loads until a
   choice is made here.
   ============================================================ */
#btc-consent {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 950;
  display: flex; align-items: center; gap: var(--gap-sm); flex-wrap: wrap;
  padding: 16px var(--gutter);
  background: var(--ink); color: var(--paper);
  border-top: 1px solid rgba(255,255,255,.14);
  box-shadow: var(--shadow-lg);
  --focus-ring: var(--accent-on-dark);
}
#btc-consent .cs-t {
  flex: 1; min-width: 280px; max-width: 78ch;
  font-size: var(--fs-sm); line-height: var(--lh-snug);
  color: rgba(255,255,255,.82);
}
#btc-consent .cs-t a { color: var(--accent-on-dark); text-decoration: underline; text-underline-offset: 3px; }
#btc-consent .cs-a { display: flex; gap: 8px; flex-shrink: 0; }
#btc-consent .cs-btn {
  font-family: var(--f-sans); font-size: var(--fs-sm); font-weight: var(--fw-head);
  letter-spacing: var(--track-wide); text-transform: uppercase;
  padding: 12px 20px; border-radius: var(--radius); cursor: pointer;
  border: 1px solid rgba(255,255,255,.3); background: none; color: var(--paper);
}
#btc-consent .cs-yes { background: var(--paper); color: var(--ink); border-color: var(--paper); }
@media (prefers-reduced-motion: no-preference) {
  #btc-consent .cs-btn { transition: background var(--dur-base), color var(--dur-base); }
}
#btc-consent .cs-no:hover { background: rgba(255,255,255,.12); }
#btc-consent .cs-yes:hover { background: var(--accent-on-dark); border-color: var(--accent-on-dark); }
@media (max-width: 620px) {
  #btc-consent { flex-direction: column; align-items: stretch; }
  #btc-consent .cs-a { justify-content: stretch; }
  #btc-consent .cs-btn { flex: 1; }
}
