/*
Theme Name: Ti-Anode
Theme URI: https://ti-anode.com/
Author: Xinxiang Future Hydrochemistry
Description: Custom theme for ti-anode.com — MMO coated titanium anodes and salt chlorinator cells.
Version: 1.9.4
Requires at least: 6.5
Tested up to: 6.5
Requires PHP: 7.4
License: GPL-2.0-or-later
Text Domain: ti-anode
Domain Path: /languages
*/

@property --h { syntax:'<integer>'; initial-value:0; inherits:true }

:root{
  color-scheme:dark;
  --ti-900:oklch(0.185 0.006 215); --ti-850:oklch(0.215 0.006 215);
  --ti-800:oklch(0.245 0.007 215); --ti-700:oklch(0.300 0.008 215);
  --ti-600:oklch(0.375 0.009 215); --edge:oklch(0.470 0.010 215);
  --teal:oklch(0.66 0.115 195); --teal-d:oklch(0.55 0.10 195);
  --bronze:oklch(0.745 0.130 68); --bronze-d:oklch(0.665 0.130 68);
  --ink:oklch(0.955 0.004 215); --dim:oklch(0.855 0.006 215);
  --muted:oklch(0.745 0.008 215);
  --paper:oklch(0.965 0.004 215); --paper-ink:oklch(0.255 0.012 215);
  --paper-mute:oklch(0.475 0.014 215); --paper-rule:oklch(0.855 0.008 215);
  --display:'Archivo',ui-sans-serif,system-ui,sans-serif;
  --text:'IBM Plex Sans',ui-sans-serif,system-ui,sans-serif;
  --mono:'IBM Plex Mono',ui-monospace,monospace;
  --gut:clamp(20px,4vw,56px); --maxw:1240px;
  --brushed:repeating-linear-gradient(90deg,oklch(1 0 0 / .016) 0 1px,transparent 1px 3px);
}
*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{margin:0;background:var(--ti-800);color:var(--ink);
     font:400 17px/1.62 var(--text);-webkit-font-smoothing:antialiased;
     background-image:var(--brushed);background-attachment:fixed}
/* height:auto is NOT optional: the <img> width/height attributes stay authoritative
   for height once width is overridden, so width:100% alone squashes every photo
   to its attribute height. (s2-stack rendered 625x582 instead of 625x182.) */
img{display:block;max-width:100%;height:auto}
a{color:inherit;text-decoration:none}
.wrap{max-width:var(--maxw);margin-inline:auto;padding-inline:var(--gut)}
h1,h2,h3{margin:0;text-wrap:balance}
:target{scroll-margin-top:88px}

.d1{font-family:var(--display);font-weight:750;font-stretch:110%;
    font-size:clamp(38px,5.4vw,74px);line-height:1.02;letter-spacing:-.032em}
.d2{font-family:var(--display);font-weight:750;font-stretch:108%;
    font-size:clamp(28px,3.5vw,48px);line-height:1.06;letter-spacing:-.03em}
.d3{font-family:var(--display);font-weight:700;font-size:clamp(20px,2.2vw,27px);
    line-height:1.14;letter-spacing:-.022em}
em{font-style:normal;color:var(--teal)}
p{margin:0 0 1em;text-wrap:pretty}
.lede{font-size:clamp(16.5px,1.35vw,19px);color:var(--dim);max-width:52ch}

.cta{display:inline-flex;align-items:center;justify-content:center;font:600 16px/1 var(--text);
     padding:15px 26px;border-radius:2px;border:1.5px solid transparent;cursor:pointer;
     transition:background-color 140ms ease-out,border-color 140ms ease-out,color 140ms ease-out}
.cta.solid{background:var(--bronze);color:oklch(0.20 0.035 68);border-color:var(--bronze)}
.cta.line{color:var(--ink);border-color:oklch(1 0 0 / .24)}
@media(hover:hover){.cta.solid:hover{background:var(--bronze-d);border-color:var(--bronze-d)}
  .cta.line:hover{border-color:var(--teal);color:var(--teal)}}
.cta:focus-visible{outline:2.5px solid var(--teal);outline-offset:3px}

/* ---------- chrome ---------- */
/* 上面那条信息带也金属化(老板 2026-08-29)。做法与 header 同源,但【刻意做暗一档】——
   两条同亮度会读成一整块糊在一起;暗带在上、亮条在下,读作"阳极氧化暗带 + 机加工亮条"
   两块叠在一起的组件。下缘给一条实心暗线,让 header 看起来是【压在它上面】的。 */
.util{background-color:oklch(0.168 0.005 215);
  background-image:
    repeating-linear-gradient(0deg, oklch(1 0 0 / .022) 0 1px,
                                    oklch(0 0 0 / .020) 1px 2px,
                                    oklch(1 0 0 / 0) 2px 4px),
    linear-gradient(180deg, oklch(0.198 0.005 215) 0%, oklch(0.152 0.005 215) 100%);
  border-block-end:1px solid oklch(0 0 0 / .55);
  box-shadow:inset 0 1px 0 oklch(1 0 0 / .045)}
.util .wrap{display:flex;gap:24px;flex-wrap:wrap;align-items:center;padding-block:9px;
  font-family:var(--mono);font-size:12.5px;color:var(--muted)}
.util a{color:var(--ink);font-weight:500}
.util a:hover{color:var(--teal)}
.util .push{margin-inline-start:auto}
@media(max-width:720px){.util .hide-sm{display:none}.util .push{margin-inline-start:0}}

/* 工业钛的金属感(老板 2026-08-29 要求)。真实钛面是【冷灰、低饱和】的,
   所以只动明度、不动色相 —— 全部留在站内已有的 hue 215 上,一点辉光都不加:
   发光边、青色描边这类是合约 §6b 明令禁止的科幻 HUD 味道。
   三层叠加,顺序即绘制顺序(先写的在上):
     ③ 掠射高光 —— 从左上打来的一道极淡斜反射,给平面一点"被光扫过"的方向感
     ② 轧制纹   —— 沿条形【长度方向】的横向细纹(0deg ⇒ 线是横的)。
                    body 上那层 --brushed 是 90deg(竖纹),那是给大面积背景的;
                    一条横向的金属条,纹路应当顺着它的长边走。
     ① 板面明暗 —— 上缘受光、下缘落到暗处,这是"薄板"读感的主要来源。
                    1.8.6 加强:明度跨度 0.268→0.203 放大到 **0.308→0.186**,
                    并在最上面补了一条 1px 的高光棱(第 ④ 层)。
     ④ 高光棱     —— 板材上缘那条被光切到的窄边,1px。金属感的"锐"来自这条,
                    不是来自把整块调亮 —— 整块调亮只会变成"浅灰塑料"。
   ⚠️ 三层都必须带 alpha:任何一层给成不透明,下面的 backdrop-filter 就白设了。 */
header{position:sticky;top:0;z-index:50;
  background-color:oklch(0.236 0.006 215 / .94);
  background-image:
    linear-gradient(180deg, oklch(1 0 0 / .11) 0 1px, oklch(1 0 0 / 0) 2px),
    linear-gradient(104deg, oklch(1 0 0 / .085) 0 13%, oklch(1 0 0 / 0) 43%),
    repeating-linear-gradient(0deg, oklch(1 0 0 / .034) 0 1px,
                                    oklch(0 0 0 / .022) 1px 2px,
                                    oklch(1 0 0 / 0) 2px 4px),
    linear-gradient(180deg,
      oklch(0.308 0.008 215 / .94) 0%,
      oklch(0.270 0.007 215 / .94) 26%,
      oklch(0.228 0.006 215 / .94) 62%,
      oklch(0.186 0.006 215 / .94) 100%);
  backdrop-filter:blur(10px);
  border-block-end:1px solid oklch(1 0 0 / .13);
  box-shadow:inset 0 1px 0 oklch(1 0 0 / .16),
             inset 0 -1px 0 oklch(1 0 0 / .07),
             0 1px 0 oklch(0 0 0 / .48),
             0 6px 18px -8px oklch(0 0 0 / .55)}
.hd{display:flex;align-items:center;gap:30px;padding-block:14px}
.brand .nm{font-family:var(--display);font-weight:700;font-size:17px;letter-spacing:-.015em;display:block;line-height:1.1}
.brand .co{font-family:var(--mono);font-size:11px;color:var(--muted)}
@media(max-width:560px){.brand .co{display:none}}
.nav{display:flex;gap:26px;margin-inline-start:auto;font-size:15.5px;font-weight:450}
.nav a{color:oklch(0.86 0.006 215);padding-block:4px;border-block-end:2px solid transparent;
  transition:color 140ms ease-out,border-color 140ms ease-out}
.nav a:hover{color:var(--ink);border-block-end-color:var(--bronze)}
.burger{display:none;margin-inline-start:auto;background:transparent;color:var(--ink);
  border:1.5px solid oklch(1 0 0 / .24);border-radius:2px;padding:9px 12px;font:500 14px var(--mono);cursor:pointer}
#mobnav{border:none;margin:0;padding:0;inset:auto 0 0 0;width:100%;color:var(--ink);
  background:var(--ti-850);border-block-start:1px solid oklch(1 0 0 / .1)}
#mobnav ul{list-style:none;margin:0;padding:8px var(--gut) 22px}
#mobnav li{border-block-end:1px solid oklch(1 0 0 / .08)}
#mobnav a{display:block;padding:16px 2px;font-size:17px}
@media(max-width:920px){.nav{display:none}.hd .cta{display:none}.burger{display:block}}

/* ---------- S1 hook: the page IS a titanium surface ----------
   Lighting per STORYBOARD Scene 1: one hard raking light from upper-left,
   falling off to lower-right. Since 1.3.0 a full-bleed cinematic render sits
   UNDER those layers, so their intensities are roughly halved — the render now
   supplies the light and the CSS layers only supply the material. Everything
   below is LIGHT, not motion: the motion budget (COMMIT-SHEET §5) stays at
   three families. */
.s1{position:relative;overflow:clip;padding-block:clamp(70px,12vh,140px) clamp(60px,10vh,110px);
    min-block-size:clamp(520px,78svh,820px);display:flex;align-items:center;
    view-timeline:--s1 block}

/* Layer 0 — the render. Full bleed, behind everything.
   The frame is composed with the cell left-of-centre and its dark fall-off on
   the right, i.e. for a headline set on the RIGHT. This site's headline is set
   on the left (and stays there — it is the same block in all eight languages),
   so in LTR the frame is flipped: the white union cap goes to the right third
   and the type gets the render's own darkness instead of fighting its brightest
   highlight. In RTL the column swaps sides, so the frame is left alone. */
.s1-bg{position:absolute;inset:0;z-index:0;overflow:clip;pointer-events:none}
/* ⚠️ 别再试"裁紧/放大 hero 图"这条路,2026-08-26 试过并回退了。
   已量到的事实:hero 下三分之一几乎没有内容(亮度标准差 12,上/中区是 37-65),
   因为渲染图本身把仓体摆在上三分之二、底下整条是空地板;中文版尤其明显——
   中文 H1 只占 2 行(151px)而英文 3 行(226px),文字块少 106px,而 .s1 有
   min-block-size:78svh 把段落钉死,于是中文左下露出一个洞。
   试法:object-position:50% 0% + scale(1.15)。实测下三分之一 12.3→20.5,
   文字区背景亮度 27.4→28.2(可读性没坏),但【左下角 11.0→11.3,等于没动】,
   而代价是白色活接被切在画面右缘、整台仓从完整产品变成一截。放大 1.3 更差(20.1)。
   结论:裁切救不了左下角,那张图那个位置本来就没东西。真正的解法是重出一张 hero
   ——而且按"主推工业阳极"的定位,那张应该是阳极不是泳池电解槽。 */
.s1-bg img{width:100%;height:100%;object-fit:cover;object-position:50% 50%;transform:scaleX(-1)}
html[dir="rtl"] .s1-bg img{transform:none}

/* The scrim follows the render's own fall-off rather than washing the frame
   flat: it deepens the side that is already dark (where the type is) and
   releases entirely over the subject, so the cell stays a cell. The second
   gradient seats the data strip and the caption on the lower band. */
.s1-bg::after{content:"";position:absolute;inset:0;
  background:
    linear-gradient(96deg,
      oklch(0.145 0.006 215 / .88) 0%,
      oklch(0.145 0.006 215 / .70) 22%,
      oklch(0.145 0.006 215 / .34) 42%,
      oklch(0.145 0.006 215 / .10) 60%,
      transparent 74%),
    linear-gradient(to top,
      oklch(0.145 0.006 215 / .84) 0%,
      oklch(0.145 0.006 215 / .42) 14%,
      transparent 32%)}
html[dir="rtl"] .s1-bg::after{transform:scaleX(-1)}

/* Layer 1 — the metal itself: real expanded-mesh tile, over the render so the
   frame picks up the plant's own grain. Halved from .15: at full strength it
   scummed the render. */
.s1::before{content:"";position:absolute;inset:0;pointer-events:none;z-index:1;
  background:url("assets/img/ti-mesh-tile.png") repeat;background-size:420px 78px;opacity:.07;mix-blend-mode:overlay}

/* Layer 2 — the raking light. A broad anisotropic specular band (brushed metal
   scatters light along the grain, so the highlight is a smeared band, never a
   point) plus the falloff to lower-right. Drifts 60px across the scroll —
   unchanged. The alphas are roughly halved: the render brings its own key, and
   at the old strength the two lights fought. */
.s1::after{content:"";position:absolute;inset:-10% -14%;pointer-events:none;z-index:3;
  background:
    linear-gradient(104deg,
      transparent 8%,
      oklch(1 0 0 / .042) 21%,
      oklch(1 0 0 / .065) 27%,
      oklch(1 0 0 / .028) 33%,
      transparent 46%),
    linear-gradient(100deg,oklch(1 0 0 / .026) 0%,oklch(1 0 0 / .006) 26%,transparent 48%,oklch(0 0 0 / .10) 82%);
  will-change:transform}

/* Layer 3 — the machined edge. A milled chamfer catches the same light along
   the bottom boundary, so the section reads as a plate with thickness. */
.s1 > .edge{position:absolute;inset-inline:0;inset-block-end:0;height:1px;pointer-events:none;z-index:4;
  background:linear-gradient(90deg,transparent 0%,oklch(1 0 0 / .17) 18%,oklch(1 0 0 / .05) 52%,transparent 88%)}

.s1 .wrap{position:relative;z-index:2;width:100%}
.s1 .d1{max-width:16ch}
.acts{display:flex;gap:12px;flex-wrap:wrap;margin-block-start:30px}
.ds{margin-block-start:44px;border-block-start:1px solid oklch(1 0 0 / .12);padding-block-start:16px;
  display:flex;flex-wrap:wrap;gap:0 30px;font-family:var(--mono);font-size:11.5px;
  letter-spacing:.06em;color:var(--muted);text-transform:uppercase}
.ds span{padding-block:5px}
.ds b{color:var(--teal);font-weight:500}
/* The small type is the part a bright frame can actually take away, so it
   carries its own ground rather than trusting the scrim alone. */
.s1 .lede{margin-block-start:26px;text-shadow:0 1px 3px oklch(0.13 0.006 215 / .55)}
.s1 .ds{text-shadow:0 1px 3px oklch(0.13 0.006 215 / .6)}
.s1-cap{margin:22px 0 0;font-family:var(--mono);font-size:10.5px;letter-spacing:.13em;
  text-transform:uppercase;color:oklch(0.66 0.008 215);
  text-shadow:0 1px 3px oklch(0.13 0.006 215 / .7)}

/* Below 900px the 1600-wide variant is served (see the <picture> source and the
   matching preload in functions.php). The crop keeps the middle of the frame —
   the dark glass body — rather than the white cap, because on a phone the type
   covers the whole frame and the cap is the one thing it cannot sit on. */
@media(max-width:900px){
  .s1{min-block-size:clamp(480px,72svh,700px)}
  .s1-bg::after{background:
    linear-gradient(to bottom,
      oklch(0.145 0.006 215 / .86) 0%,
      oklch(0.145 0.006 215 / .62) 24%,
      oklch(0.145 0.006 215 / .40) 48%,
      oklch(0.145 0.006 215 / .58) 76%,
      oklch(0.145 0.006 215 / .86) 100%)}
  html[dir="rtl"] .s1-bg::after{transform:none}
}

/* ---------- S2 rising ---------- */
.s2{background:var(--ti-850);border-block:1px solid oklch(1 0 0 / .08);
    padding-block:clamp(48px,7vh,84px);overflow:clip}
.s2 .wrap{display:grid;grid-template-columns:1fr;gap:30px}
.s2 figure{margin:0}
.s2 img{width:100%;border:1px solid oklch(1 0 0 / .1)}
.s2 figcaption{font-family:var(--mono);font-size:12px;color:var(--muted);padding-block-start:10px}
.s2 .row{display:grid;grid-template-columns:5fr 7fr;gap:clamp(24px,4vw,56px);align-items:end}
@media(max-width:860px){.s2 .row{grid-template-columns:1fr;align-items:start}}


/* ---------- S4 data: the one bright scene, deliberate opposite register ---------- */
.s4{background:var(--paper);color:var(--paper-ink);padding-block:clamp(54px,8vh,92px)}
.s4 .d2{color:var(--paper-ink)}
.s4 .sub{color:var(--paper-mute);max-width:56ch;margin-block:12px 34px}
.tbl{overflow-x:auto;border:1px solid var(--paper-rule);background:#fff}
table{border-collapse:collapse;width:100%;min-width:760px;font-size:14.6px}
th,td{padding:12px 14px;text-align:start;border-block-end:1px solid var(--paper-rule);white-space:nowrap}
thead th{background:oklch(0.935 0.006 215);font:600 12px/1.3 var(--mono);letter-spacing:.05em;
  text-transform:uppercase;color:var(--paper-mute)}
td{font-variant-numeric:tabular-nums}
td.m{font-family:var(--mono);font-weight:500;color:oklch(0.38 0.09 195)}
tbody tr:last-child td{border-block-end:none}
.s4 .note{font-size:13.5px;color:var(--paper-mute);margin-block-start:14px}
.s4 .gap{background:oklch(0.95 0.055 78);border:1px dashed oklch(0.72 0.11 68);
  color:oklch(0.42 0.09 60);padding:12px 15px;font-size:14px;margin-block-start:18px;border-radius:3px}

.cmp2{display:grid;grid-template-columns:1fr 1fr;gap:22px;margin-block:26px 30px}
.cmp2>div{border:1px solid var(--paper-rule);padding:20px 22px;background:#fff}
.cmp2 h3{font-family:var(--display);font-weight:700;font-size:18px;margin:0 0 8px;color:var(--paper-ink)}
.cmp2 p{margin:0;font-size:14.8px;color:var(--paper-mute)}
.cmp2 strong{color:var(--paper-ink)}
@media(max-width:760px){.cmp2{grid-template-columns:1fr}}
/* ---------- S5 selector ---------- */
/* The cell selector. Was .sel on the homepage until 1.5.0; it now renders inside a
   product page, so it needs its own top margin — on the homepage the section
   above supplied it. */
/* NOTE the class is .selsec, not .sel — .sel is already taken by the two-column
   grid INSIDE this section (form | result card). Renaming the old .s5 section
   rule to .sel made both match the same element and squeezed the whole section
   into one grid column on the product page. */
.selsec{background:var(--ti-850);border-block:1px solid oklch(1 0 0 / .08);padding-block:clamp(54px,8vh,92px);margin-block-start:clamp(40px,6vh,72px)}
/* The result card used to end at the explanation line — no link, no button. This
   is the exit. */
.outact{display:flex;flex-wrap:wrap;gap:12px;margin-block-start:clamp(18px,2.6vh,26px);
  padding-block-start:clamp(16px,2.4vh,22px);border-block-start:1px solid oklch(1 0 0 / .12)}
.outact .cta{font-size:14.5px}
.sel{display:grid;grid-template-columns:1fr 1fr;gap:clamp(24px,4vw,56px);align-items:start;margin-block-start:32px}
@media(max-width:860px){.sel{grid-template-columns:1fr}}
.f label{display:block;font:600 12.5px/1 var(--mono);letter-spacing:.06em;text-transform:uppercase;
  color:var(--muted);margin-block-end:8px}
.f input,.f select{width:100%;padding:14px 15px;font:400 17px var(--text);color:var(--ink);
  background:var(--ti-800);border:1.5px solid oklch(1 0 0 / .18);border-radius:2px;margin-block-end:20px}
.f input:focus-visible,.f select:focus-visible{outline:2.5px solid var(--teal);outline-offset:2px}
.seg{display:flex;gap:0;margin-block-end:20px;border:1.5px solid oklch(1 0 0 / .18);border-radius:2px;overflow:hidden}
.seg button{flex:1;padding:13px 10px;background:transparent;color:var(--dim);border:none;
  font:500 15px var(--text);cursor:pointer;transition:background-color 140ms ease-out,color 140ms ease-out}
.seg button[aria-pressed="true"]{background:var(--teal-d);color:oklch(0.14 0.02 195);font-weight:650}
.out{background:var(--ti-800);border:1px solid oklch(1 0 0 / .12);
     box-shadow:inset 0 1px 0 oklch(1 0 0 / .07);padding:clamp(22px,3vw,32px)}
.out .k{font:600 12px/1 var(--mono);letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
.out .model{font-family:var(--display);font-weight:750;font-size:clamp(38px,6vw,64px);
  letter-spacing:-.03em;color:var(--teal);line-height:1;margin-block:10px 6px}
.out dl{display:grid;grid-template-columns:auto 1fr;gap:9px 18px;margin:22px 0 0;
  font-size:15px;border-block-start:1px solid oklch(1 0 0 / .12);padding-block-start:18px}
.out dt{font-family:var(--mono);font-size:12.5px;color:var(--muted);align-self:center}
.out dd{margin:0;font-variant-numeric:tabular-nums}
.out .why{font-size:14.5px;color:var(--muted);margin-block-start:18px}

/* ---------- S6 door ---------- */
.s6{padding-block:clamp(54px,8vh,92px)}
.s6 .wrap{display:grid;grid-template-columns:6fr 6fr;gap:clamp(28px,4vw,60px)}
@media(max-width:860px){.s6 .wrap{grid-template-columns:1fr}}
.clist{list-style:none;padding:0;margin:26px 0 0}
.clist li{display:flex;gap:16px;padding:14px 0;border-block-start:1px solid oklch(1 0 0 / .1)}
.clist .k{font:600 12px/1.5 var(--mono);letter-spacing:.07em;text-transform:uppercase;
  color:var(--muted);width:92px;flex:none}
.clist a{font-weight:600;font-size:17px}
.clist a:hover{color:var(--teal)}
footer{background:var(--ti-900);border-block-start:1px solid oklch(1 0 0 / .08);
  padding-block:30px;font-size:13.5px;color:var(--muted)}
footer .wrap{display:flex;justify-content:space-between;gap:18px;flex-wrap:wrap}

/* =============================================================================
   Inner pages (page.php / template-product.php / single.php / archive.php).
   Same tokens as the homepage — no new palette, no new fonts.
   ========================================================================== */
.crumbs{font-family:var(--mono);font-size:12px;color:var(--muted);letter-spacing:.04em;
  text-transform:uppercase;margin-block-end:20px}
.crumbs a{color:var(--muted)}
.crumbs a:hover{color:var(--teal)}
.crumbs .sep{margin-inline:8px;color:oklch(1 0 0 / .28)}

.page-hero{position:relative;overflow:hidden;padding-block:clamp(56px,9vh,110px) clamp(44px,7vh,80px);
  background:var(--ti-850);border-block-end:1px solid oklch(1 0 0 / .08)}
.page-hero::before{content:"";position:absolute;inset:0;pointer-events:none;
  background:url("assets/img/ti-mesh-tile.png") repeat;background-size:420px 78px;opacity:.10;mix-blend-mode:overlay}
.page-hero .wrap{position:relative}
.page-hero .d1{max-width:24ch}
.page-hero .lede{margin-block-start:20px}

.page-body{padding-block:clamp(48px,7vh,84px)}
.prose{max-width:78ch}
.prose h2{font-family:var(--display);font-weight:750;font-stretch:108%;font-size:clamp(24px,2.8vw,34px);
  line-height:1.1;letter-spacing:-.03em;margin:1.6em 0 .6em}
.prose h2:first-child{margin-top:0}
.prose h3{font-family:var(--display);font-weight:700;font-size:clamp(19px,2vw,24px);line-height:1.16;
  letter-spacing:-.02em;margin:1.5em 0 .5em}
.prose h4{font-family:var(--mono);font-weight:500;font-size:13px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--teal);margin:1.8em 0 .6em}
.prose p{color:var(--dim)}
.prose ul{list-style:none;margin:0 0 1.4em;padding:0}
.prose ul li{position:relative;padding-inline-start:22px;padding-block:4px;color:var(--dim)}
.prose ul li::before{content:"";position:absolute;inset-inline-start:2px;top:.72em;width:7px;height:7px;
  background:var(--bronze);transform:rotate(45deg)}
.prose strong{color:var(--ink)}
.prose a{color:var(--teal);text-decoration:underline;text-underline-offset:3px}
.prose a:hover{color:var(--bronze)}

/* Dark data tables (inner pages) */
.dtbl{overflow-x:auto;border:1px solid oklch(1 0 0 / .12);background:var(--ti-800);margin-block:1.4em}
.dtbl table{border-collapse:collapse;width:100%;min-width:760px;font-size:14.6px}
.dtbl th,.dtbl td{padding:12px 14px;text-align:start;border-block-end:1px solid oklch(1 0 0 / .1);white-space:nowrap}
.dtbl thead th{background:var(--ti-900);font:600 12px/1.3 var(--mono);letter-spacing:.05em;
  text-transform:uppercase;color:var(--muted)}
.dtbl td{color:var(--ink);font-variant-numeric:tabular-nums}
.dtbl td.m{font-family:var(--mono);font-weight:500;color:var(--teal)}
.dtbl tbody tr:last-child td{border-block-end:none}
.dtbl + .tnote{font-size:13.5px;color:var(--muted);margin-block-start:12px}

/* Product card grids */
.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:20px;margin-block:28px 8px}
@media(max-width:860px){.cards{grid-template-columns:1fr}}
.pcard{display:block;border:1px solid oklch(1 0 0 / .14);background:var(--ti-800);
  padding:24px 26px;border-radius:2px;transition:border-color 140ms ease-out}
.pcard h3{font-family:var(--display);font-weight:700;font-size:19px;margin:0 0 8px;letter-spacing:-.02em;color:var(--ink)}
.pcard p{margin:0;font-size:14.5px;color:var(--muted)}
.pcard:hover{border-color:var(--teal)}
.pcard .go{font-family:var(--mono);font-size:12px;color:var(--teal);letter-spacing:.06em;
  text-transform:uppercase;display:inline-block;margin-block-start:14px}

/* Blog index + single */
.posts{display:grid;gap:18px;margin-block-start:28px}
.post{display:block;border:1px solid oklch(1 0 0 / .12);background:var(--ti-800);
  padding:22px 26px;border-radius:2px;transition:border-color 140ms ease-out}
.post h2{font-family:var(--display);font-weight:700;font-size:21px;line-height:1.15;letter-spacing:-.02em;
  margin:0 0 6px;color:var(--ink)}
.post .meta{font-family:var(--mono);font-size:12px;color:var(--muted)}
.post .excerpt{font-size:15px;color:var(--dim);margin-block-start:10px}
.post:hover{border-color:var(--teal)}
.single-meta{font-family:var(--mono);font-size:13px;color:var(--muted);margin-block-start:20px}
.pagination{display:flex;gap:16px;margin-block-start:32px;font-family:var(--mono);font-size:14px}
.pagination a{color:var(--teal)}
.pagination a:hover{color:var(--bronze)}

/* Contact page */
.contact-grid{display:grid;grid-template-columns:5fr 7fr;gap:clamp(28px,4vw,56px);align-items:start}
@media(max-width:860px){.contact-grid{grid-template-columns:1fr}}
.cform .frow{margin-block-end:18px}
.cform label{display:block;font:600 12.5px/1 var(--mono);letter-spacing:.06em;text-transform:uppercase;
  color:var(--muted);margin-block-end:8px}
.cform input,.cform textarea{width:100%;padding:14px 15px;font:400 17px var(--text);color:var(--ink);
  background:var(--ti-800);border:1.5px solid oklch(1 0 0 / .18);border-radius:2px}
.cform input:focus-visible,.cform textarea:focus-visible{outline:2.5px solid var(--teal);outline-offset:2px}
.cform textarea{min-height:130px;resize:vertical}
.cform .cta{margin-block-start:6px}

/* ---------- i18n: language switcher (utility bar) ---------- */
.langs{display:flex;gap:12px;font:500 11.5px var(--mono);letter-spacing:.06em;margin-inline-start:18px}
.langs a{color:var(--muted)}
.langs a:hover{color:var(--teal)}
.langs .on{color:var(--teal);border-block-end:1px solid var(--teal)}
@media(max-width:720px){.langs{margin-inline-start:0;flex-wrap:wrap}}

/* ---------- i18n: CJK / Arabic type. Archivo & Plex are Latin-only; give those
   locales full stacks and drop the Latin-tuned negative tracking (connected Arabic
   script must never be letter-spaced; CJK dislikes it too). ---------- */
html:lang(zh){
  --display:'Archivo','PingFang SC','Hiragino Sans GB','Microsoft YaHei','Noto Sans SC',ui-sans-serif,sans-serif;
  --text:'IBM Plex Sans','PingFang SC','Hiragino Sans GB','Microsoft YaHei','Noto Sans SC',ui-sans-serif,sans-serif;
}
html:lang(ar){
  --display:'IBM Plex Sans Arabic','Noto Sans Arabic','Geeza Pro','Segoe UI',system-ui,sans-serif;
  --text:'IBM Plex Sans Arabic','Noto Sans Arabic','Geeza Pro','Segoe UI',system-ui,sans-serif;
}
:lang(zh) :is(.d1,.d2,.d3){letter-spacing:0}
:lang(ar) :is(.d1,.d2,.d3,.lede,h1,h2,h3,p){letter-spacing:0}


/* ---------- S1 headline reveal ----------
   Motion family 2 (heading rise + de-blur, COMMIT-SHEET §5) extended to the
   hero h1 and staggered per word, as STORYBOARD Scene 1 specifies. The words
   are wrapped by JS; with no JS the h1 renders plain and fully readable. */
.d1 .w{display:inline-block;white-space:pre-wrap}
/* Hidden only while JS has claimed the reveal and not yet delivered it. The
   flag is set by script and cleared by a timer, so a JS failure can never
   leave the headline invisible. */
.js-hero .s1 .d1{opacity:0}
.js-split .s1 .d1{opacity:1}
.js-split .d1 .w{opacity:0}

@media (prefers-reduced-motion: no-preference){
  .js-split .d1 .w{
    animation:wordRise 620ms cubic-bezier(.2,.7,.25,1) both;
    animation-delay:calc(var(--i) * 55ms + 90ms);
  }
  @keyframes wordRise{
    from{opacity:0;transform:translateY(0.42em);filter:blur(7px)}
    to  {opacity:1;transform:none;filter:blur(0)}
  }

  /* The light drifts 60px across the whole scroll — the storyboard's number.
     Declarative scroll-timeline, so no scroll listener (COMMIT-SHEET §5 ban). */
  @supports (animation-timeline: view()){
    .s1::after{animation:rake linear both;animation-timeline:--s1;
               animation-range:entry 0% exit 100%}
    @keyframes rake{from{transform:translate3d(-30px,0,0)} to{transform:translate3d(30px,0,0)}}
  }
}

/* Reduced motion: the words still arrive, by opacity alone — the art direction
   is lighter, not absent (COMMIT-SHEET §5). */
@media (prefers-reduced-motion: reduce){
  .js-split .d1 .w{animation:wordFade 320ms ease-out both;animation-delay:calc(var(--i) * 26ms)}
  @keyframes wordFade{from{opacity:0} to{opacity:1}}
}


/* =============================================================================
   CINEMATIC SCENES · 1.3.0
   One peak per page (COMMIT-SHEET §5). Each scene ships as a template part, one
   asset file enqueued only on its own page, and the block below.

   The contract every scene follows:
     · the readable cut is the DEFAULT — captioned frames in normal flow, all
       copy present. No JS, no canvas, no WebGL and no scroll-driven animations
       all land here.
     · the scene's JS adds data-live to its track ONLY once it has confirmed it
       can drive the scrub and reduced motion is off. Every pinned rule hangs off
       that attribute, so a script that throws leaves the static cut standing.
     · it also adds .ti-pin to <html>, which is what lets <header> step out of a
       film — a header animation must never reference a timeline that may not
       exist on the page.
     · no scroll / wheel / touchmove listeners anywhere: IntersectionObserver,
       ResizeObserver, rAF and CSS scroll-timelines only.
   ========================================================================== */

@property --p  { syntax:'<number>'; initial-value:0; inherits:true  }  /* scrub 0→1     */
@property --q  { syntax:'<number>'; initial-value:0; inherits:true  }  /* coverage 0→1  */
@property --v  { syntax:'<number>'; initial-value:1; inherits:false }  /* cut visibility */
/* --fl MUST inherit: the polarity flip is read by ::before/::after, and a
   registered property with inherits:false resolves to its initial value on a
   pseudo-element rather than to the value computed on the originating <b>. */
@property --fl { syntax:'<number>'; initial-value:0; inherits:true  }  /* polarity flip */


/* =============================================================================
   SCENE · coating — HOMEPAGE S3 PEAK. Bare titanium sweeps to fully coated.
   ========================================================================== */
/* vh first, svh second: an engine without svh drops the second declaration and
   keeps a working height instead of an invalid one, which would collapse the
   pinned stage to nothing. */
.coat{position:relative;background:var(--ti-900);
  --stg:100vh;--band-t:35vh;--band-h:38vh;
  --stg:100svh;--band-t:35svh;--band-h:38svh}
.co-track{position:relative}
.co-stage{position:static;overflow:clip;background:var(--ti-900);padding-block:clamp(40px,6vh,72px)}

/* ---- the readable cut: the labelled pair, side by side, in flow ---- */
.co-media{display:grid;gap:22px;grid-template-columns:1fr 1fr;
  padding-inline:var(--gut);max-width:var(--maxw);margin-inline:auto}
.co-gl{display:none;width:100%;height:100%}
.co-fr{margin:0}
.co-fr img{width:100%;height:auto;border:1px solid oklch(1 0 0 / .10)}
.co-fr figcaption{margin-block-start:10px;font-family:var(--mono);font-size:11px;
  line-height:1.62;letter-spacing:.03em;color:var(--muted)}
.co-fr figcaption strong{color:var(--bronze);font-weight:500;letter-spacing:.12em;margin-inline-end:6px}

.co-hud{display:block;padding:clamp(34px,5vh,56px) var(--gut) 0;
  max-width:var(--maxw);margin-inline:auto}
.co-cuts{position:relative;max-width:70ch}
.co-cut{margin-block-end:30px}
.co-cuts .d3{font-size:clamp(22px,2.5vw,33px);line-height:1.1;letter-spacing:-.026em}
.co-cut p{margin:14px 0 0;font-size:clamp(15.5px,1.15vw,17px);color:var(--dim);max-width:44ch}
.co-tag{display:inline-block;margin-block-end:13px;font-family:var(--mono);font-size:11px;
  letter-spacing:.14em;text-transform:uppercase;color:var(--bronze)}
.co-tag.on{color:var(--teal)}

.co-foot{display:flex;align-items:flex-end;justify-content:space-between;gap:28px;
  flex-wrap:wrap;margin-block-start:8px}
.co-hon{font-family:var(--mono);font-size:10.5px;line-height:1.66;color:var(--muted);
  max-width:64ch;margin:0}
.co-hon strong{color:var(--dim);font-weight:500}
.co-hon em{color:var(--bronze)}
.co-read{text-align:end;flex:0 0 auto}
.co-rule{position:relative;height:1px;width:clamp(160px,20vw,280px);
  margin-block:0 14px;margin-inline:auto 0;   /* logical: follows text-align:end in RTL */
  background:oklch(1 0 0 / .16)}
.co-rule i{position:absolute;inset-block:0;inset-inline-start:0;width:100%;background:var(--bronze)}
.co-count{font-family:var(--mono);font-weight:500;font-variant-numeric:tabular-nums;
  font-size:clamp(34px,5vw,66px);line-height:.94;letter-spacing:-.03em;color:var(--ink);display:block}
.co-count u{text-decoration:none;color:var(--dim);font-size:.30em;margin-inline-start:.5em;letter-spacing:.06em}
.co-rlab{display:block;margin-block-start:9px;font-family:var(--mono);font-size:10.5px;
  letter-spacing:.16em;text-transform:uppercase;color:var(--dim)}
.co-rsub{display:block;margin-block-start:5px;font-family:var(--mono);font-size:10.5px;
  letter-spacing:.04em;color:var(--muted)}

/* ---- the pinned film, claimed by coating.js ---- */
@keyframes coat{from{--p:0}to{--p:1}}
.co-track[data-live]{height:calc(3.2 * var(--stg))}
.co-track[data-live] .co-stage{position:sticky;top:0;height:var(--stg);padding:0}
/* The whole animation lives behind @supports, and that guard is load-bearing —
   not tidiness. `animation-duration:auto` is only meaningful for a timeline-
   driven animation; an engine that has no scroll-driven animations drops it,
   falls back to 0s, and a fill:both animation then finishes instantly and pins
   --p at 1 AT ANIMATION PRIORITY, which outranks the inline value coating.js
   writes. The copy would be frozen on the last cut for exactly the browsers the
   JS fallback exists to serve. Behind the guard, those engines have no
   animation at all and the inline --p governs. */
@supports (animation-timeline: view()){
  .co-track[data-live]{view-timeline-name:--coat;view-timeline-axis:block}
  .co-track[data-live] .co-stage{animation-name:coat;animation-duration:auto;
    animation-timing-function:linear;animation-fill-mode:both;
    animation-timeline:--coat;animation-range:contain 0% contain 100%}
}
.co-track[data-live] .co-media{position:absolute;inset:0;display:block;padding:0;max-width:none}
.co-track[data-live] .co-media.gl .co-gl{display:block}
.co-track[data-live] .co-fr{position:absolute;inset:0}
.co-track[data-live] .co-fr img{width:100%;height:100%;object-fit:cover;border:none}
.co-track[data-live] .co-fr figcaption{display:none}
/* WebGL unavailable → the same pin, cross-faded on coverage */
.co-track[data-live] .co-fr.co-b{opacity:var(--q)}
.co-track[data-live] .co-media.gl .co-fr{opacity:0}

/* Grade. Unlike a wear treatment this shot starts BRIGHT and ends dark, so the
   ground under the type is heaviest at --q 0 and relaxes as the layer lands. */
.co-track[data-live] .co-hud{position:absolute;inset:0;display:flex;flex-direction:column;
  justify-content:space-between;max-width:none;
  padding:clamp(76px,11vh,126px) var(--gut) clamp(24px,4.5vh,46px)}
.co-track[data-live] .co-hud::before{content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(96deg,oklch(0.12 0.006 215 / .95) 0%,oklch(0.12 0.006 215 / .74) 24%,
             oklch(0.12 0.006 215 / .30) 42%,transparent 60%);
  opacity:calc(1 - var(--q) * 0.60)}
.co-track[data-live] .co-hud::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(0deg,oklch(0.12 0.006 215 / .95) 0%,oklch(0.12 0.006 215 / .62) 13%,
             oklch(0.12 0.006 215 / .22) 26%,transparent 42%);
  opacity:calc(1 - var(--q) * 0.46)}
.co-track[data-live] .co-cuts{max-width:min(41ch,44vw)}
.co-track[data-live] .co-cut{position:absolute;inset-block-start:0;inset-inline:0;margin:0;
  pointer-events:none;opacity:var(--v);transform:translateY(calc((1 - var(--v)) * 14px));
  filter:blur(calc((1 - var(--v)) * 5px))}
.co-track[data-live] .co-cut1{--v:clamp(0, calc((0.30 - var(--p)) / 0.06), 1)}
.co-track[data-live] .co-cut2{--v:min( clamp(0, calc((var(--p) - 0.30) / 0.06), 1),
                                       clamp(0, calc((0.72 - var(--p)) / 0.06), 1) )}
.co-track[data-live] .co-cut3{--v:clamp(0, calc((var(--p) - 0.72) / 0.06), 1)}
.co-track[data-live] .co-foot{position:relative;margin:0}
/* the caption and the read-out sit over near-white metal at --q 0, so both
   carry their own ground rather than relying on the grade */
.co-track[data-live] .co-hon{color:var(--dim);max-width:min(64ch,42vw);
  text-shadow:0 1px 2px oklch(0.12 0.006 215 / .94),0 0 9px oklch(0.12 0.006 215 / .74)}
.co-track[data-live] .co-read{text-shadow:0 1px 2px oklch(0.12 0.006 215 / .90),0 0 10px oklch(0.12 0.006 215 / .70)}
.co-track[data-live] .co-rule i{width:calc(var(--q) * 100%)}

/* 390 — a 16:9 render cropped to a phone viewport throws away the plate
   silhouette, and the silhouette is what makes the coating front legible. So
   the peak becomes a held band with the copy above it and the read-out below. */
@media(max-width:860px){
  .co-media{grid-template-columns:1fr}
  .co-cut p{margin-block-start:11px;max-width:none}
  .co-read{text-align:start}
  .co-rule{margin-inline:0 auto}
  .co-track[data-live] .co-media{inset:var(--band-t) 0 auto;height:var(--band-h);
    border-block:1px solid oklch(1 0 0 / .09)}
  .co-track[data-live] .co-hud{padding:clamp(56px,8vh,84px) var(--gut) clamp(16px,2.6vh,28px)}
  .co-track[data-live] .co-hud::before,.co-track[data-live] .co-hud::after{display:none}
  .co-track[data-live] .co-cuts{max-width:none}
  .co-track[data-live] .co-foot{flex-direction:column;align-items:flex-start;gap:14px}
  .co-track[data-live] .co-hon{max-width:none;font-size:10px;line-height:1.72}
}
@media(max-width:420px){ .co-count{font-size:42px} .co-rule{margin-block-end:11px} }

/* Reduced motion — a lighter cut, not a dead page. coating.js never claims the
   pin under reduce; this block is the belt for the braces, so flipping the OS
   setting mid-session cannot leave a 3.2-screen stage nothing is driving. */
@media(prefers-reduced-motion:reduce){
  .co-track[data-live]{height:auto;view-timeline-name:none}
  .co-track[data-live] .co-stage{position:static;height:auto;animation:none;
    padding-block:clamp(40px,6vh,72px)}
  .co-track[data-live] .co-media{position:static;display:grid;gap:22px;grid-template-columns:1fr 1fr;
    padding-inline:var(--gut);max-width:var(--maxw);margin-inline:auto;inset:auto;height:auto;border:none}
  .co-track[data-live] .co-media.gl .co-gl{display:none}
  .co-track[data-live] .co-fr,.co-track[data-live] .co-media.gl .co-fr{position:static;opacity:1}
  .co-track[data-live] .co-fr img{height:auto;object-fit:fill;border:1px solid oklch(1 0 0 / .10)}
  .co-track[data-live] .co-fr figcaption{display:block}
  .co-track[data-live] .co-hud{position:static;display:block;
    padding:clamp(34px,5vh,56px) var(--gut) 0;max-width:var(--maxw);margin-inline:auto}
  .co-track[data-live] .co-hud::before,.co-track[data-live] .co-hud::after{display:none}
  .co-track[data-live] .co-cuts{max-width:70ch}
  .co-track[data-live] .co-cut{position:static;opacity:1;transform:none;filter:none;margin-block-end:30px}
  .co-track[data-live] .co-foot{position:static;flex-wrap:wrap;margin-block-start:8px}
  .co-track[data-live] .co-hon{color:var(--muted);text-shadow:none;max-width:52ch}
  .co-track[data-live] .co-rule i{width:100%}
  @media(max-width:860px){ .co-track[data-live] .co-media{grid-template-columns:1fr} }
}


/* =============================================================================
   SCENE · selfclean — RP CHLORINATOR CELL. One polarity cycle.
   The ONLY thing that changes across the twelve frames is the calcium sitting
   ON the coating. The coating itself is identical in every frame — that is the
   argument, so nothing here may be captioned as coating wear.
   ========================================================================== */
.sc{position:relative;background:var(--ti-900);
  --stg:100vh;--band-t:33vh;--band-h:36vh;
  --stg:100svh;--band-t:33svh;--band-h:36svh}
.sc-track{position:relative}
.sc-stage{position:relative;background:var(--ti-900);padding-block:clamp(40px,6vh,72px)}

/* ---- the readable cut: the three key frames, captioned, in flow ---- */
.sc-media{position:relative;display:grid;gap:22px;grid-template-columns:repeat(3,1fr);
  max-width:var(--maxw);margin-inline:auto;padding-inline:var(--gut)}
.sc-fr{margin:0;position:relative}
.sc-fr img{width:100%;height:auto}
.sc-fr:not(.sc-key){display:none}
.sc-fr figcaption{margin-block-start:10px;font-family:var(--mono);font-size:11px;
  line-height:1.62;letter-spacing:.04em;color:var(--muted)}
.sc-fr figcaption strong{color:var(--bronze);font-weight:500;letter-spacing:.12em;margin-inline-end:6px}

.sc-hud{position:relative;max-width:var(--maxw);margin-inline:auto;
  padding:clamp(34px,5vh,58px) var(--gut) 0}
.sc-cuts{position:relative;max-width:72ch}
.sc-cut{margin-block-end:30px}
.sc-tag{display:inline-block;margin-block-end:13px;font-family:var(--mono);font-size:11px;
  letter-spacing:.14em;text-transform:uppercase;color:var(--bronze)}
.sc-cut .d3{font-stretch:104%;font-size:clamp(21px,2.35vw,31px);letter-spacing:-.024em;line-height:1.12}
.sc-cut p{margin:14px 0 0;font-size:clamp(15.5px,1.15vw,17px);color:var(--dim);max-width:40ch}

.sc-foot{position:relative;margin-block-start:6px;display:flex;flex-wrap:wrap;gap:22px 40px;
  align-items:flex-end;justify-content:space-between}
.sc-hon{font-family:var(--mono);font-size:10.5px;line-height:1.66;color:var(--muted);
  max-width:52ch;margin:0}
.sc-hon strong{color:var(--dim);font-weight:500}
.sc-read{flex:0 0 auto;text-align:end}
.sc-rule{position:relative;height:1px;width:clamp(150px,19vw,264px);
  margin-block:0 15px;margin-inline:auto 0;
  background:oklch(1 0 0 / .16)}
.sc-rule i{position:absolute;inset-block:0;inset-inline-start:0;width:100%;background:var(--bronze)}

.sc-pol{display:flex;justify-content:flex-end;gap:clamp(6px,.9vw,13px);
  font-family:var(--mono);font-weight:500;font-size:clamp(24px,2.7vw,37px);line-height:1}
.sc-pol b{position:relative;display:block;width:1ch;text-align:center;font-weight:500}
.sc-pol b::before,.sc-pol b::after{display:block}
.sc-pol b::after{position:absolute;inset:0;opacity:0}
.sc-pol b.pa::before{content:"+";color:var(--bronze)}
.sc-pol b.pa::after {content:"\2212";color:var(--muted)}
.sc-pol b.pb::before{content:"\2212";color:var(--muted)}
.sc-pol b.pb::after {content:"+";color:var(--bronze)}
.sc-rlab{display:block;margin-block-start:11px;font-family:var(--mono);font-size:10.5px;
  letter-spacing:.15em;text-transform:uppercase;color:var(--muted)}
.sc-tally{display:block;margin-block-start:14px;font-family:var(--mono);font-size:11px;
  letter-spacing:.13em;text-transform:uppercase;color:var(--muted)}
.sc-tally u{text-decoration:none;color:var(--teal);font-weight:500;
  font-variant-numeric:tabular-nums;margin-inline-start:8px}

/* ---- the pinned film, claimed by selfclean.js ---- */
@keyframes sc-scrub{from{--p:0}to{--p:1}}
.sc-track[data-live]{height:calc(3.6 * var(--stg))}
.sc-track[data-live] .sc-stage{position:sticky;top:0;height:var(--stg);overflow:clip;padding:0}
/* see the note on .co-stage: the guard keeps --p out of animation priority on
   engines that cannot drive it, so selfclean.js's inline value can win */
@supports (animation-timeline: view()){
  .sc-track[data-live]{view-timeline-name:--cycle;view-timeline-axis:block}
  .sc-track[data-live] .sc-stage{animation-name:sc-scrub;animation-duration:auto;
    animation-timing-function:linear;animation-fill-mode:both;
    animation-timeline:--cycle;animation-range:contain 0% contain 100%}
}
.sc-track[data-live] .sc-media{position:absolute;inset:0;display:block;max-width:none;margin:0;padding:0;
  transform:scale(calc(1.045 - 0.045 * var(--p)));transform-origin:58% 46%}
/* :not() to out-specify the static rule that hides the nine in-between frames */
.sc-track[data-live] .sc-fr,
.sc-track[data-live] .sc-fr:not(.sc-key){position:absolute;inset:0;display:block}
.sc-track[data-live] .sc-fr img{width:100%;height:100%;object-fit:cover}
.sc-track[data-live] .sc-fr figcaption{display:none}

/* Grade: a top band for the copy and a corner slate for the read-out. The
   middle of the frame — where the deposit actually is — is never touched, or
   beat 2 loses the one thing it is about. */
.sc-track[data-live] .sc-hud{position:absolute;inset:0;z-index:3;max-width:none;margin:0;display:block;
  padding:clamp(78px,12vh,132px) var(--gut) clamp(20px,3.2vh,34px)}
.sc-track[data-live] .sc-hud::before{content:"";position:absolute;inset:0;pointer-events:none;z-index:-1;
  background:linear-gradient(to bottom,oklch(0.105 0.006 215 / .93) 0%,
    oklch(0.105 0.006 215 / .87) 12%,oklch(0.105 0.006 215 / .72) 26%,
    oklch(0.105 0.006 215 / .55) 38%,oklch(0.105 0.006 215 / .30) 48%,
    oklch(0.105 0.006 215 / .09) 55%,transparent 63%)}
.sc-track[data-live] .sc-read::before{content:"";position:absolute;z-index:-1;pointer-events:none;
  inset-block:-30px calc(-1 * clamp(20px,3.2vh,34px));
  inset-inline:-56px calc(-1 * var(--gut));
  background:radial-gradient(125% 135% at 100% 100%,oklch(0.105 0.006 215 / .88) 0%,
    oklch(0.105 0.006 215 / .52) 46%,transparent 76%)}

.sc-track[data-live] .sc-cuts{max-width:min(41ch,45vw);min-height:clamp(196px,25vh,244px)}
.sc-track[data-live] .sc-cut{position:absolute;inset-block-start:0;inset-inline:0;pointer-events:none;
  margin:0;opacity:var(--v);transform:translateY(calc((1 - var(--v)) * 13px));
  filter:blur(calc((1 - var(--v)) * 5px))}
/* the film's edit list — the same numbers selfclean.js cuts frames on.
   0.18 deposit starts · 0.44 fully fouled · 0.52 polarity flips · 0.88 clean.
   ⚠️ These MUST track the constants in selfclean.js. The scale now GROWS in
   (frames played backwards) instead of appearing on a cut, so every hand-over
   moved earlier; a stale number here desynchronises copy from picture. */
.sc-track[data-live] .sc-c1{--v:clamp(0, calc((0.175 - var(--p)) / 0.05), 1)}
.sc-track[data-live] .sc-c2{--v:min( clamp(0, calc((var(--p) - 0.185) / 0.05), 1),
                                     clamp(0, calc((0.508 - var(--p)) / 0.05), 1) )}
.sc-track[data-live] .sc-c3{--v:min( clamp(0, calc((var(--p) - 0.518) / 0.05), 1),
                                     clamp(0, calc((0.835 - var(--p)) / 0.05), 1) )}
.sc-track[data-live] .sc-c4{--v:clamp(0, calc((var(--p) - 0.865) / 0.05), 1)}
.sc-track[data-live] .sc-foot{position:static;display:block;margin:0}
.sc-track[data-live] .sc-hon{position:relative;max-width:min(64ch,44vw);color:var(--dim);
  margin-block-start:clamp(16px,2.8vh,32px);
  text-shadow:0 1px 2px oklch(0.11 0.006 215 / .92),0 0 9px oklch(0.11 0.006 215 / .7)}
.sc-track[data-live] .sc-read{position:absolute;inset-block-end:clamp(20px,3.2vh,34px);
  inset-inline-end:var(--gut);text-align:end}
.sc-track[data-live] .sc-rule i{width:calc(var(--p) * 100%)}
/* the flip sweeps across the pack rather than snapping as a block — motion
   family 1 (data landing), ~4ms of offset per plate */
.sc-track[data-live] .sc-pol b{--fl:clamp(0, calc((var(--p) - 0.505 - (var(--i) * 0.0042)) / 0.014), 1)}
.sc-track[data-live] .sc-pol b::before{opacity:calc(1 - var(--fl))}
.sc-track[data-live] .sc-pol b::after {opacity:var(--fl)}
.sc-track[data-live] .sc-tally{opacity:clamp(0, calc((var(--p) - 0.88) / 0.05), 1)}

/* ---- the landing: endurance, and the honest AC contrast ---- */
.sc-land{background:var(--ti-850);border-block:1px solid oklch(1 0 0 / .08);
  padding-block:clamp(50px,8vh,92px)}
.sc-land .d2{max-width:20ch}
.sc-land .lede{margin-block-start:16px;max-width:60ch}
.sc-cmp{margin-block-start:36px;display:grid;grid-template-columns:1fr 1fr;gap:22px}
.sc-cmp > div{border:1px solid oklch(1 0 0 / .12);background:var(--ti-800);padding:24px 26px 26px}
.sc-cmp h3{font-family:var(--display);font-weight:700;font-size:20px;letter-spacing:-.02em;
  color:var(--ink);margin-block-end:10px}
.sc-cmp h3 span{display:block;font-family:var(--mono);font-weight:400;font-size:11px;
  letter-spacing:.13em;text-transform:uppercase;margin-block-end:7px}
.sc-cmp .rp h3 span{color:var(--teal)}
.sc-cmp .ac h3 span{color:var(--bronze)}
.sc-cmp p{margin:0;font-size:15.6px;color:var(--dim)}
.sc-cmp strong{color:var(--ink);font-weight:600}
.sc-cmp .wash{margin-block-start:16px;padding-block-start:14px;
  border-block-start:1px solid oklch(1 0 0 / .12);font-family:var(--mono);font-size:11.5px;
  letter-spacing:.09em;text-transform:uppercase;color:var(--muted)}
.sc-cmp .rp .wash b{color:var(--teal);font-weight:500}
.sc-cmp .ac .wash b{color:var(--bronze);font-weight:500}
.sc-facts{margin-block-start:26px;display:grid;grid-template-columns:repeat(4,1fr);gap:1px;
  background:oklch(1 0 0 / .10);border:1px solid oklch(1 0 0 / .10)}
.sc-facts div{background:var(--ti-800);padding:20px 20px 22px}
.sc-facts .n{display:block;font-family:var(--mono);font-weight:500;font-variant-numeric:tabular-nums;
  font-size:clamp(20px,2.3vw,29px);letter-spacing:-.02em;color:var(--ink)}
.sc-facts .k{display:block;margin-block-start:9px;font-family:var(--mono);font-size:10.5px;
  letter-spacing:.11em;text-transform:uppercase;color:var(--muted)}
.sc-open{margin-block-start:26px;border-inline-start:2px solid var(--bronze-d);
  padding:2px 0 2px 18px;max-width:78ch;font-size:15px;line-height:1.7;color:var(--dim)}
.sc-open b{display:block;font-family:var(--mono);font-size:11px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--bronze);margin-block-end:8px;font-weight:500}
.sc-open strong{color:var(--ink)}

/* 390 — the peak becomes a held band. A 16:9 frame cropped to a phone viewport
   throws away the plate silhouette, and the silhouette is what makes the scale
   legible against the coating. */
@media(max-width:860px){
  .sc-media{grid-template-columns:1fr}
  .sc-cmp{grid-template-columns:1fr}
  .sc-facts{grid-template-columns:repeat(2,1fr)}
  .sc-cut p{max-width:none;margin-block-start:11px}
  .sc-track[data-live]{height:calc(2.7 * var(--stg))}
  .sc-track[data-live] .sc-media{inset:var(--band-t) 0 auto;height:var(--band-h);
    border-block:1px solid oklch(1 0 0 / .09);transform:none}
  .sc-track[data-live] .sc-hud{padding:clamp(56px,8vh,84px) var(--gut) clamp(16px,2.6vh,28px)}
  .sc-track[data-live] .sc-hud::before,.sc-track[data-live] .sc-read::before{display:none}
  .sc-track[data-live] .sc-cuts{max-width:none;min-height:0}
  .sc-track[data-live] .sc-foot{position:absolute;inset:auto 0 clamp(16px,2.6vh,28px) 0;
    padding-inline:var(--gut);display:flex;flex-direction:column;align-items:flex-start;gap:15px}
  .sc-track[data-live] .sc-hon{position:static;max-width:none;margin:0;font-size:10px;line-height:1.68}
  .sc-track[data-live] .sc-read{position:static;text-align:start;width:100%}
  .sc-track[data-live] .sc-rule{margin-inline:0 auto}
  .sc-track[data-live] .sc-pol{justify-content:flex-start}
}
@media(max-width:420px){ .sc-pol{font-size:24px} }

/* Reduced motion — the cycle shown as the three key frames it is made of,
   labelled and in order. Nothing scrubs, nothing flips. */
@media(prefers-reduced-motion:reduce){
  .sc-track[data-live]{height:auto;view-timeline-name:none}
  .sc-track[data-live] .sc-stage{position:static;height:auto;animation:none;overflow:visible;
    padding-block:clamp(40px,6vh,72px)}
  .sc-track[data-live] .sc-media{position:static;display:grid;gap:22px;grid-template-columns:repeat(3,1fr);
    inset:auto;height:auto;transform:none;border:0;max-width:var(--maxw);
    margin-inline:auto;padding-inline:var(--gut)}
  .sc-track[data-live] .sc-fr,.sc-track[data-live] .sc-fr.sc-key{position:static;display:block;opacity:1}
  .sc-track[data-live] .sc-fr:not(.sc-key){display:none}
  .sc-track[data-live] .sc-fr img{height:auto;object-fit:fill}
  .sc-track[data-live] .sc-fr figcaption{display:block}
  .sc-track[data-live] .sc-hud{position:static;display:block;
    padding:clamp(34px,5vh,58px) var(--gut) 0;max-width:var(--maxw);margin-inline:auto}
  .sc-track[data-live] .sc-hud::before,.sc-track[data-live] .sc-read::before{display:none}
  .sc-track[data-live] .sc-cuts{max-width:72ch;min-height:0}
  .sc-track[data-live] .sc-cut{position:static;opacity:1;transform:none;filter:none;margin-block-end:30px}
  .sc-track[data-live] .sc-foot{position:static;display:flex;flex-wrap:wrap;gap:22px 40px;
    margin-block-start:6px;align-items:flex-end;justify-content:space-between}
  .sc-track[data-live] .sc-hon{position:static;color:var(--muted);text-shadow:none;max-width:52ch;margin:0}
  .sc-track[data-live] .sc-read{position:static;text-align:end}
  .sc-track[data-live] .sc-rule i{width:100%}
  .sc-track[data-live] .sc-pol b::before{opacity:1}
  .sc-track[data-live] .sc-pol b::after{opacity:0}
  .sc-track[data-live] .sc-tally{opacity:1}
  @media(max-width:860px){ .sc-track[data-live] .sc-media{grid-template-columns:1fr} }
}


/* =============================================================================
   SCENE · sq — THE SCRUBBED FRAME SEQUENCE. One block, two scenes:

     AC Chlorinator Cell     scene-dolly.php    a 36-frame push-in
     Saltwater Chlorinator   scene-explode.php  a 36-frame explode

   Both are the same instrument, so they share one stylesheet and one script;
   what differs is the frame folder, the crop travel and the copy, and all of
   that rides on the track as data attributes. Named `sq` and not `dl` since
   1.4.0 for exactly that reason — the block stopped being about the dolly the
   moment the exploded cell stopped being three.js.

   The film only exists once scrub.js has claimed it; the block after it is the
   readable cut and carries the same three beats in full.
   ========================================================================== */
.sq{position:relative;background:var(--ti-900)}
.sq-track{display:none}
.sq-track[data-live]{display:block;position:relative;height:300vh;height:300svh;
  view-timeline-name:--seq;view-timeline-axis:block}
/* grid-template-rows:100% is load-bearing, not tidiness. Left implicit, the one
   row is AUTO-sized, so the canvas's block-size:100% has no definite size to
   resolve against and falls back to the natural size a <canvas> takes from its
   width/height attributes — which the scrubber then writes back from the rect
   it just measured. That loop settles at a square 1440x1440 canvas inside a
   900-tall stage: the bottom third of every frame clipped away, and W/H = 1 so
   the crop anchor picks the narrow-screen travel on a desktop. A percentage row
   against the stage's definite height breaks the loop; min-block-size:0 stops
   the grid item's automatic content-based minimum from re-inflating it.
   (Present since the dolly shipped in 1.3.0; measured and fixed 1.4.0.) */
.sq-stage{position:sticky;top:0;height:100vh;height:100svh;overflow:clip;
  background:oklch(0.185 0.006 215);   /* = --ti-900, the render's own backdrop */
  display:grid;grid-template-rows:100%}
.sq-canvas{display:block;inline-size:100%;block-size:100%;min-block-size:0;grid-area:1/1}

/* Falloff under the copy column and under the slug. The renders already darken
   to the right and bottom; by the tightest frame the plate stack has grown far
   enough right that the copy needs real ground under it, not a hint of one. */
.sq-scrim{grid-area:1/1;pointer-events:none;
  background:
    linear-gradient(180deg,oklch(0.185 0.006 215 / .60) 0%,
      oklch(0.185 0.006 215 / .16) 9%,transparent 17%),
    linear-gradient(90deg,transparent 30%,oklch(0.185 0.006 215 / .20) 46%,
      oklch(0.185 0.006 215 / .52) 57%,oklch(0.185 0.006 215 / .82) 67%,
      oklch(0.185 0.006 215 / .93) 78%,oklch(0.185 0.006 215 / .95) 100%)}

.sq-copy{grid-area:1/1;position:relative;max-width:var(--maxw);inline-size:100%;
  margin-inline:auto;padding-inline:var(--gut)}
.sq-beat{position:absolute;inset-block:0;inset-inline-end:var(--gut);inline-size:min(43ch,42%);
  display:flex;flex-direction:column;justify-content:center;
  /* a second, local layer of ground so the copy never depends on which frame
     happens to be under it */
  background:linear-gradient(90deg,transparent 0,
    oklch(0.185 0.006 215 / .34) 26%,oklch(0.185 0.006 215 / .58) 60%,
    oklch(0.185 0.006 215 / .62) 100%);
  opacity:0;visibility:hidden;transform:translateY(11px);filter:blur(6px);
  transition:opacity 380ms ease-out,
             transform 440ms cubic-bezier(.2,.7,.25,1),
             filter 440ms ease-out,
             visibility 0s linear 440ms}
.sq-beat.on{opacity:1;visibility:visible;transform:none;filter:blur(0);transition-delay:0s}
.sq-beat .sq-idx{color:var(--teal)}
.sq-beat .d2{margin-block-start:14px;text-wrap:balance}
.sq-beat p{margin-block:20px 0;color:var(--dim);font-size:clamp(15.5px,1.15vw,17px)}
.sq-beat .ds{margin-block-start:26px}
.sq-note{font-family:var(--mono);font-size:11.5px;letter-spacing:.06em;
  text-transform:uppercase;color:var(--muted)}

/* the read-out: motion family 1, data landing */
.sq-count{margin-block-start:30px;border-block-start:1px solid oklch(1 0 0 / .14);padding-block-start:16px}
.sq-count b{display:block;font-family:var(--mono);font-weight:500;
  font-size:clamp(34px,4.4vw,58px);line-height:1;letter-spacing:-.02em;
  color:var(--ink);font-variant-numeric:tabular-nums}
.sq-count b i{font-style:normal;color:var(--teal);font-size:.42em;margin-inline-start:.28em;letter-spacing:.04em}
.sq-count > span{display:block;margin-block-start:12px}

/* HUD — instrument language, not sci-fi chrome */
.sq-hud{grid-area:1/1;position:relative;pointer-events:none;
  max-width:var(--maxw);inline-size:100%;margin-inline:auto;padding:0 var(--gut)}
.sq-slug{position:absolute;inset-block-start:clamp(18px,3.2vh,34px);inset-inline-start:var(--gut);
  display:flex;flex-direction:column;gap:5px}
/* The slug can land on any frame and some frames are bright right where it
   sits. The halo is the background colour, so on dark ground it is invisible
   and on a lit plate it is the difference between legible and not. */
.sq-slug .sq-note,.sq-load{
  text-shadow:0 1px 2px oklch(0.185 0.006 215 / .95),0 0 11px oklch(0.185 0.006 215 / .85)}
.sq-honest{margin-block-start:5px;color:var(--bronze);opacity:.85}
.sq-load{position:absolute;inset-block-end:clamp(30px,5vh,50px);inset-inline-start:var(--gut);
  transition:opacity 260ms ease-out}
.sq-load.done{opacity:0}
.sq-bar{position:absolute;inset-inline:0;inset-block-end:0;block-size:1px;background:oklch(1 0 0 / .12)}
.sq-bar i{display:block;block-size:1px;background:var(--teal);
  transform:scaleX(0);transform-origin:0 50%;will-change:transform}

/* ---- the readable cut ---- */
.sq-static{padding-block:clamp(44px,7vh,84px)}
.sq-track[data-live] ~ .sq-static{display:none}
.sq-stitle{margin-block-start:14px;max-width:20ch}
.sq-plate{margin-block-start:44px;display:grid;gap:clamp(20px,3vw,44px);
  grid-template-columns:1fr;align-items:center}
.sq-plate figure{margin:0}
.sq-plate img{inline-size:100%;height:auto;background:oklch(0.185 0.006 215);
  border:1px solid oklch(1 0 0 / .10)}
.sq-plate figcaption{margin-block-start:10px}
.sq-plate .d3{margin-block-start:12px}
.sq-plate p{margin-block:16px 0;color:var(--dim)}
.sq-plate .sq-idx{color:var(--teal)}
@media(min-width:860px){
  .sq-plate{grid-template-columns:1.15fr .85fr}
  .sq-plate:nth-of-type(even) figure{order:2}
}

/* ---- the landing: the maintenance the AC series actually asks for ---- */
.sq-land{background:var(--ti-850);border-block:1px solid oklch(1 0 0 / .08);
  padding-block:clamp(44px,7vh,84px)}
.sq-cmp{display:grid;grid-template-columns:1fr 1fr;gap:22px}
.sq-cmp > div{border:1px solid oklch(1 0 0 / .12);background:var(--ti-800);padding:22px 24px 24px}
.sq-cmp h3{font-family:var(--display);font-weight:700;font-size:19px;letter-spacing:-.02em;
  color:var(--ink);margin-block-end:9px}
.sq-cmp p{margin:0;font-size:15.4px;color:var(--dim)}
.sq-cmp strong{color:var(--ink);font-weight:600}
/* the figures the render was built to — sentence case, not the HUD's uppercase */
.sq-spec{margin:0;font-family:var(--mono);font-size:12px;line-height:1.7;color:var(--dim)}
.sq-method{margin-block-start:26px;max-width:78ch;font-family:var(--mono);font-size:11px;
  line-height:1.75;color:var(--muted)}
.sq-method strong{color:var(--dim);font-weight:500}
@media(max-width:860px){ .sq-cmp{grid-template-columns:1fr} }

/* narrow — the copy moves under the frame instead of beside it */
@media(max-width:820px){
  .sq-stage{grid-template-rows:52vh 1fr;grid-template-rows:52svh 1fr}
  .sq-scrim{background:linear-gradient(180deg,
      oklch(0.185 0.006 215 / .80) 0%,oklch(0.185 0.006 215 / .34) 9%,
      oklch(0.185 0.006 215 / .08) 19%,transparent 28%,
      transparent 52%,oklch(0.185 0.006 215 / .55) 78%,
      oklch(0.185 0.006 215 / .96) 100%)}
  .sq-copy{grid-area:1/1/3/2;padding-block-end:clamp(26px,5svh,44px)}
  .sq-beat{inset-inline:var(--gut);inline-size:auto;background:none;
    inset-block:50vh 0;inset-block:50svh 0;justify-content:flex-start}
  .sq-beat p{margin-block-start:14px;font-size:15px}
  .sq-beat .ds{margin-block-start:20px;gap:0 20px}
  .sq-count{margin-block-start:18px;padding-block-start:12px}
  .sq-hud{grid-area:1/1/3/2}
  /* display:contents dissolves the slug's own box, so each mark positions
     against .sq-hud (the full stage) rather than against the slug */
  .sq-slug{display:contents}
  .sq-fig{display:none}
  .sq-fnum{position:absolute;inset-block-start:14px;inset-inline-start:var(--gut)}
  .sq-honest{position:absolute;inset-block-end:18px;inset-inline-start:var(--gut);
    margin-block-start:0;opacity:1;text-shadow:none}
  .sq-load{inset-block-end:46px}
  .sq-track[data-live]{height:280vh;height:280svh}
}
@media(max-width:820px) and (max-height:700px){
  .sq-stage{grid-template-rows:44vh 1fr;grid-template-rows:44svh 1fr}
  .sq-beat{inset-block-start:43vh;inset-block-start:43svh}
  .sq-beat p{font-size:14.5px}
}

/* Reduced motion — hand the page back to the readable cut. */
@media(prefers-reduced-motion:reduce){
  .sq-track[data-live]{display:none}
  .sq-track[data-live] ~ .sq-static{display:block}
}


/* =============================================================================
   The chrome steps out of a film, declaratively — no scroll listener.
   Gated on .ti-pin (set by the scene's own script once it has claimed the pin)
   AND on the body's scene class, so <header> can never be driven from a named
   timeline that does not exist on the page being rendered.
   ========================================================================== */
@supports (animation-timeline: view()) {
@media (prefers-reduced-motion: no-preference) {
  body.ti-scene-coating{timeline-scope:--coat}
  body.ti-scene-selfclean{timeline-scope:--cycle}
  /* one timeline name for both scrubbed sequences — they never coexist on a
     page, and the alternative is two identical rule sets */
  body.ti-scene-dolly,body.ti-scene-explode{timeline-scope:--seq}

  .ti-pin body.ti-scene-coating header{animation:ti-chrome linear both;
    animation-timeline:--coat;animation-range:contain 0% contain 100%}
  .ti-pin body.ti-scene-selfclean header{animation:ti-chrome linear both;
    animation-timeline:--cycle;animation-range:contain 0% contain 100%}
  .ti-pin body.ti-scene-dolly header,
  .ti-pin body.ti-scene-explode header{animation:ti-chrome linear both;
    animation-timeline:--seq;animation-range:contain 0% contain 100%}
  /* fill-mode holds the last keyframe once the range ends, so the chrome must
     be back at 100% — it returns late, after the read-out has cleared. */
  @keyframes ti-chrome{0%{transform:none;opacity:1}
    7%{transform:translateY(-104%);opacity:0}
    94%{transform:translateY(-104%);opacity:0}
    100%{transform:none;opacity:1}}

  /* the film's own marks leave as the chrome comes back, so the two never
     occupy the same corner */
  .ti-pin body.ti-scene-dolly .sq-hud,
  .ti-pin body.ti-scene-explode .sq-hud{animation:ti-marks linear both;
    animation-timeline:--seq;animation-range:contain 90% contain 99%}
  @keyframes ti-marks{to{opacity:0}}
}}

/* ---------- S2 core business: the three chemistries ----------
   Added 2026-08-26 with the homepage rebalance. The cell used to own 34% of the
   page and the coating business 5%; a visitor read this as a pool-cell company.
   These three cards are the identity claim, so they sit in the widest measure
   the grid allows and stay legible before any of the cell copy is reached. */
.chem{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(16px,2vw,30px);
  margin-block-start:clamp(30px,4.5vh,52px);
  border-block-start:1px solid oklch(1 0 0 / .12);padding-block-start:clamp(20px,3vh,30px)}
.chem-i b{display:block;font-family:var(--mono);font-size:clamp(19px,1.7vw,24px);
  font-weight:500;color:var(--teal);letter-spacing:-.01em}
.chem-i .k{display:block;font-family:var(--mono);font-size:11.5px;letter-spacing:.06em;
  text-transform:uppercase;color:var(--muted);margin-block-start:5px}
.chem-i p{margin:10px 0 0;font-size:clamp(14.5px,1.02vw,15.5px);color:var(--dim);line-height:1.55}
.chem-foot{margin-block-start:clamp(20px,3vh,30px);font-size:14px;color:var(--muted);
  max-width:78ch;line-height:1.6}
@media(max-width:820px){.chem{grid-template-columns:1fr;gap:22px}}

/* eyebrow above the cell section — it frames the cell as the flagship BUILT FROM
   the anodes, rather than as the company's subject */
.s4 .eyebrow{font-family:var(--mono);font-size:11.5px;letter-spacing:.07em;
  text-transform:uppercase;color:var(--bronze-d);margin:0 0 10px}

/* S6 dual entry: a replacement buyer and an industrial buyer want different things */
.ask{display:grid;grid-template-columns:1fr 1fr;gap:clamp(18px,2.4vw,34px);
  margin-block-start:clamp(20px,3vh,30px)}
.ask .k{display:block;font-family:var(--mono);font-size:11.5px;letter-spacing:.06em;
  text-transform:uppercase;color:var(--teal)}
.ask p{margin:9px 0 0;font-size:clamp(14.5px,1.02vw,15.5px);color:var(--dim);line-height:1.55}
@media(max-width:760px){.ask{grid-template-columns:1fr;gap:20px}}

/* the spec table on the homepage now carries five columns; the rest of the sheet
   lives on the product page, and this is the door to it */
.s4 .more{margin-block-start:14px;font-family:var(--mono);font-size:13.5px}
.s4 .more a{color:var(--teal-d);text-decoration:none;border-block-end:1px solid currentColor;padding-block-end:2px}
.s4 .more a:hover{color:var(--paper-ink)}


/* Mobile CTA. .hd .cta is hidden below 920px (see the nav rule above), which left
   phones with no persistent conversion entry anywhere on the site. */
#mobnav .mob-cta{margin-block-end:14px;padding-block-end:14px;border-block-end:1px solid oklch(1 0 0 / .12)}
#mobnav .mob-cta .cta{display:block;text-align:center}

/* ---------- S5b industrial anodes: how an order actually runs ----------
   Added 2026-08-26 with the selector's move to the Saltwater Chlorinator page.
   A dark ti-850 band, the same register as S2: it has to separate from the paper
   S4 above it and from the ti-800 body S6 sits on below it.
   Measured 745px at 1440x900 (probe against this sheet, self-hosted fonts).
   Class prefix is s5b- so nothing here can collide with the .sel rules the
   selector took to the product page. */
.s5b{background:var(--ti-850);border-block:1px solid oklch(1 0 0 / .08);
  padding-block:clamp(54px,8vh,92px)}
.s5b .eyebrow{font-family:var(--mono);font-size:11.5px;letter-spacing:.07em;
  text-transform:uppercase;color:var(--bronze-d);margin:0 0 10px}
.s5b .k{display:block;font-family:var(--mono);font-size:11.5px;letter-spacing:.06em;
  text-transform:uppercase;color:var(--teal)}

/* The application row. Mono tags rather than a paragraph because it is scanned,
   not read — a buyer looks for his own process and stops. Wraps freely, so
   translation growth costs a row, never an overflow. */
.s5b-tags{list-style:none;display:flex;flex-wrap:wrap;gap:7px 8px;padding:0;
  margin:clamp(12px,1.8vh,18px) 0 0}
.s5b-tags li{font-family:var(--mono);font-size:12px;line-height:1;color:var(--dim);
  border:1px solid oklch(1 0 0 / .12);border-radius:2px;padding:7px 10px}

.s5b-main{display:grid;grid-template-columns:4fr 8fr;gap:clamp(24px,4vw,56px);
  margin-block-start:clamp(16px,2.2vh,22px);
  border-block-start:1px solid oklch(1 0 0 / .12);padding-block-start:clamp(14px,2vh,20px)}

/* The route reads top to bottom, one step per line. A horizontal chain was tried
   and rejected: at eight steps it already wraps in English, and once translated
   the wrap points move and the step order stops being readable. Vertical costs
   height but is length-proof. */
.s5b-route{list-style:none;counter-reset:r;margin:10px 0 0;padding:0}
.s5b-route li{counter-increment:r;position:relative;padding-inline-start:32px;
  padding-block:1px;font-size:14.6px;color:var(--dim);line-height:1.45}
.s5b-route li::before{content:counter(r,decimal-leading-zero);position:absolute;
  inset-inline-start:0;top:4px;font-family:var(--mono);font-size:11.5px;color:var(--muted)}
.s5b-route li:first-child{color:var(--ink);font-weight:600}
.s5b-route li:first-child::before{color:var(--teal)}

/* Two columns of term rows, flowing left-to-right. Rows are flex, not a
   two-column grid, so a long translated dt pushes the dd rather than forcing a
   column width on all eight rows. */
.s5b-terms{display:grid;grid-template-columns:1fr 1fr;
  column-gap:clamp(20px,3vw,40px);margin:10px 0 0}
.s5b-terms>div{display:flex;gap:14px;align-items:baseline;justify-content:space-between;
  padding-block:7px;border-block-start:1px solid oklch(1 0 0 / .12)}
.s5b-terms dt{font-family:var(--mono);font-size:12px;letter-spacing:.04em;
  text-transform:uppercase;color:var(--muted);flex:none}
.s5b-terms dd{margin:0;font-size:14.8px;text-align:end;color:var(--dim)}
/* the two rows a buyer does not expect from a Chinese supplier */
.s5b-terms .hi dd{color:var(--teal);font-weight:600}
@media(max-width:920px){.s5b-terms{grid-template-columns:1fr}}

.s5b-band{display:grid;grid-template-columns:7fr 5fr;gap:clamp(24px,4vw,56px);
  align-items:start;margin-block-start:clamp(14px,2vh,20px);
  border-block-start:1px solid oklch(1 0 0 / .12);padding-block-start:clamp(14px,2vh,20px)}
.s5b-band h3{font-family:var(--display);font-weight:700;letter-spacing:-.015em;
  font-size:clamp(17px,1.5vw,20px);margin:8px 0 0}
.s5b-band p{margin:8px 0 0;font-size:clamp(14.5px,1.02vw,15.5px);color:var(--dim);line-height:1.55}
.s5b-acts{display:flex;flex-wrap:wrap;gap:10px;margin-block-start:14px}
.s5b-acts .cta{font-size:14.5px;padding:12px 20px}

.s5b-note{margin-block-start:clamp(12px,1.8vh,16px);font-size:12.6px;color:var(--muted);
  line-height:1.6}

@media(max-width:820px){.s5b-main,.s5b-band{grid-template-columns:1fr;gap:26px}}


/* ---------- S4 plate-pack evidence panel ----------
   A dark inset in the one paper-coloured section on the page. Two reasons it is
   dark rather than knocked out onto the paper: the renders are lit for a dark
   environment (cine/ab_render.py world_dark), and a near-black satin coating
   dropped onto white reads as a smudge — the whole point is the grazing-angle
   sheen, which needs somewhere dark to fall off into. */
.packs{margin:clamp(22px,3.2vh,34px) 0 0;background:var(--ti-850);
  border:1px solid oklch(1 0 0 / .10);padding:clamp(18px,2.4vw,28px)}
.packs-g{display:grid;grid-template-columns:1fr 1fr;gap:clamp(14px,2vw,26px)}
.packs-g img{width:100%;border:1px solid oklch(1 0 0 / .10)}
.packs-g .k{display:block;margin-block-start:11px;font-family:var(--mono);
  font-size:12px;letter-spacing:.03em;color:var(--muted)}
.packs-g .k strong{color:var(--teal);font-weight:500}
.packs figcaption{margin-block-start:clamp(14px,2vh,20px);
  padding-block-start:clamp(12px,1.6vh,16px);border-block-start:1px solid oklch(1 0 0 / .12);
  font-size:12.6px;line-height:1.6;color:var(--muted);max-width:88ch}
@media(max-width:760px){.packs-g{grid-template-columns:1fr;gap:20px}}

/* ============================================================================
   1.8.0 — 借鉴三个参考站后新增的六件东西(老板 2026-08-29 指定全做)。
   参考:cipherdigital.com / triorb.co.jp / heimdallpower.com
   逐条出处与取舍写在 wp/DEPLOY-LOG.md。
   ========================================================================= */

/* --- ① 重点色块(学 Heimdall Power) ---------------------------------------
   实心色块压在标题的一个词后面。他们用橙,我们用已有的 --bronze,不新增颜色。

   ⚠️ 用 background + box-decoration-break 实现,【不用】::before 定位色块:
      标题是 text-wrap:balance,换行位置随视口变;绝对定位的色块只会跟着第一行走,
      窄屏一换行就错位。clone 让换行后每一行各自带一块底色。
   ⚠️ 类加在 h1/h2 上、样式打到里面的 em —— ti_kses 只放行 <em>/<strong> 且不带
      属性,译文里写 class 会被剥掉。
   ⚠️ 文字压深色而非白色:--bronze 是 L=0.745 的浅色,白字压上去对比度不足。 */
.mark-em em{
  background:var(--bronze); color:var(--ti-900);
  padding:.02em .17em .06em; border-radius:1px;
  box-decoration-break:clone; -webkit-box-decoration-break:clone;
}
/* ⚠️ 亮区【不要】把字改成浅色:--bronze 是 L=0.745 的浅橙,--paper 是 L=0.965,
   浅压浅对比度不够。色块本身已经提供对比,深字在暗底亮底都成立,保持一种就行。 */

/* --- ② 工程网格 + 交点圆点(学 Cipher Digital) ----------------------------
   两层:细线网格,交点上落一个小圆点。只贴暗场区 —— Cipher 全站暗底,我们不是。

   ⚠️ 必须做成绝对定位的 ::before 层、内容提到 z-index:1,【不能】直接给 section
      加 background-image:body 上已经铺了一层 --brushed(拉丝纹),两层背景图会叠加
      成脏纹理。
   ⚠️ 上下用 mask 淡出,否则网格会在区段交界处被硬切一刀,比没有还显眼。 */
.grid-bg{position:relative; isolation:isolate}
.grid-bg::before{
  content:''; position:absolute; inset:0; z-index:0; pointer-events:none;
  --g:clamp(88px,9vw,132px);
  background-image:
    linear-gradient(to right,  oklch(1 0 0 / .040) 1px, transparent 1px),
    linear-gradient(to bottom, oklch(1 0 0 / .040) 1px, transparent 1px),
    radial-gradient(circle at 1px 1px, oklch(1 0 0 / .17) 1.5px, transparent 1.6px);
  background-size:var(--g) var(--g), var(--g) var(--g), var(--g) var(--g);
  -webkit-mask-image:linear-gradient(to bottom,transparent,#000 14%,#000 86%,transparent);
          mask-image:linear-gradient(to bottom,transparent,#000 14%,#000 86%,transparent);
}
.grid-bg>.wrap{position:relative; z-index:1}
/* ⚠️ 【只】提 .wrap。.s1-bg 是 position:absolute(style.css:114)、.edge 也另有定位,
   把它们一起写进上面那条会被 position:relative 覆盖掉,hero 的整幅背景图会当场塌掉。
   .s1 本来就有三层:.s1-bg z0 → .wrap z2(style.css:173),网格插在中间 z1 即可,
   .wrap 的 z-index 不用动。 */
.s1.grid-bg::before{z-index:1}

/* --- ③ 首页第二拍:透视(学 TriOrb) ---------------------------------------
   复用产品页的 .sq 全套样式,这里只改两件:轨道短一截(首页是过场不是深读),
   以及给按钮留出位置。 */
.xray .sq-track[data-live]{height:220vh; height:220svh}
.xray-more{margin-block-start:26px}
.xray .sq-static{display:grid; grid-template-columns:repeat(3,1fr); gap:12px;
  padding-block:clamp(40px,6vh,72px)}
.xray .sq-static figure{margin:0}
.xray .sq-static .sq-note{grid-column:1/-1; color:var(--muted)}
@media(max-width:760px){.xray .sq-static{grid-template-columns:1fr}}

/* --- ④ 产品名锁定(学 TriOrb 的【描述】+ 名称) ------------------------------ */
.lock{display:flex; align-items:baseline; gap:10px; margin:0 0 10px; flex-wrap:wrap}
.lock-d{font-family:var(--mono); font-size:11px; letter-spacing:.05em;
  text-transform:uppercase; color:var(--paper-mute);
  border:1px solid var(--paper-rule); padding:4px 8px; border-radius:2px}
.lock-n{font-family:var(--display); font-weight:750; font-stretch:110%;
  font-size:clamp(26px,2.6vw,34px); line-height:1; letter-spacing:-.03em;
  color:var(--paper-ink)}

/* --- ⑤ 带日期的条目(学 Heimdall) ------------------------------------------
   空数组时整段不渲染,样式在这里备着。 */
.dated{background:var(--ti-850); border-block:1px solid oklch(1 0 0 / .08);
  padding-block:clamp(44px,6vh,72px)}
.dated .eyebrow{font-family:var(--mono); font-size:11.5px; letter-spacing:.07em;
  text-transform:uppercase; color:var(--muted); margin:0 0 18px}
.dated-list{list-style:none; margin:0; padding:0; display:grid; gap:0}
.dated-list li{display:grid; grid-template-columns:auto 1fr; gap:18px;
  align-items:baseline; padding-block:14px;
  border-block-start:1px solid oklch(1 0 0 / .08)}
.dated-list li:last-child{border-block-end:1px solid oklch(1 0 0 / .08)}
.dated-when{font-family:var(--mono); font-size:11.5px; letter-spacing:.05em;
  text-transform:uppercase; color:var(--ti-900); background:var(--muted);
  padding:3px 7px; white-space:nowrap}
.dated-what{font-family:var(--display); font-weight:700; font-size:clamp(16px,1.5vw,20px);
  line-height:1.3; color:var(--ink)}
a.dated-what:hover{color:var(--teal)}
@media(max-width:560px){.dated-list li{grid-template-columns:1fr; gap:8px}
  .dated-when{justify-self:start}}

/* --- ⑥ 工业段转浅色(学 TriOrb 的暗→亮分区) -------------------------------
   紧接 S4(本来就是 paper),两段合成一整块亮区,把工业阳极线从泳池线里切出来。
   ⚠️ .s5b 原本整段是按暗底写的,这里逐个覆盖它的子元素颜色 —— 只改 background
      会得到浅底浅字。改动限定在 .s5b--paper 下,暗底版本仍然可用。 */
.s5b--paper{background:var(--paper); color:var(--paper-ink);
  border-block-color:var(--paper-rule)}
.s5b--paper .eyebrow{color:var(--paper-mute)}
.s5b--paper .d2{color:var(--paper-ink)}
.s5b--paper .k{color:var(--paper-mute)}
.s5b--paper .s5b-tags li{color:var(--paper-mute); border-color:var(--paper-rule);
  background:#fff}
.s5b--paper .s5b-route li{color:var(--paper-mute)}
.s5b--paper .s5b-route li::before{color:var(--paper-rule)}
.s5b--paper .s5b-route li:first-child{color:var(--paper-ink)}
.s5b--paper .s5b-route li:first-child::before{color:var(--teal-d)}
.s5b--paper .s5b-terms>div{border-color:var(--paper-rule)}
.s5b--paper .s5b-terms dt{color:var(--paper-mute)}
.s5b--paper .s5b-terms dd{color:var(--paper-ink)}
.s5b--paper .s5b-terms .hi dd{color:var(--teal-d)}
.s5b--paper .s5b-band h3{color:var(--paper-ink)}
.s5b--paper .s5b-band p{color:var(--paper-mute)}
.s5b--paper .s5b-note{color:var(--paper-mute)}
.s5b--paper .cta.line{border-color:var(--paper-rule); color:var(--paper-ink)}
.s5b--paper .cta.line:hover{border-color:var(--paper-ink)}
