/* ds.css · Tradveller Anson Tan 华语 lander · the one design system (v7 "测试报告").
   Loaded right after zh.css and before every sec-*.css. zh.css aliases its old tokens to these, so this is
   the single source of truth for colour, type, space and motion. Spec, contrast ratios and copy-paste
   snippets: docs/11-design-system.md (section "v7 · 测试报告"). Live sheet: webinar-lander-zh/_styleguide.html.
   Phone first (390), premium at 1440. Behaviour (count-up, stamp, lock shimmer) lives in assets/js/ds.js. */

:root {
  /* ---- grounds (three, never more). Contrast ratios below are measured on the named ground. ---- */
  --ds-ink: #0b0d10;          /* dark "data" ground: S1 S2 S4 S6 S9 footer */
  --ds-ink-2: #15181e;        /* raised surface ON ink only (card, chat panel, ticket on dark) */
  --ds-ink-3: #1e222a;        /* second raise on ink (AI bubble, input, tab track) */
  --ds-paper: #f4f1ea;        /* warm "reading" ground: S3 S5 S7 S10 */
  --ds-white: #ffffff;        /* white ground: S8, form sheet, cards on paper */

  /* ---- text on light (paper / white) ---- */
  --ds-text: #14161a;         /* 16.1 / 18.1 : 1  headlines, strong text */
  --ds-text-2: #3d424b;       /*  9.0 / 10.1 : 1  lead and body */
  --ds-mute: #5f6570;         /*  5.2 /  5.9 : 1  eyebrow, labels, fine print (#6b7280 is 4.3 on paper: fails) */
  /* ---- text on ink (ink / ink-2) ---- */
  --ds-text-d: #f4f1ea;       /* 17.3 / 15.8 : 1 */
  --ds-text-d2: #c9cdd4;      /* 12.2 / 11.2 : 1  lead and body */
  --ds-mute-d: #9ba1ab;       /*  7.5 /  6.8 : 1  eyebrow, labels, fine print */

  --ds-line: rgba(20,22,26,.12);   --ds-line-strong: #14161a;
  --ds-line-d: rgba(255,255,255,.10); --ds-line-d-strong: rgba(255,255,255,.55);

  /* ---- colour means ONE thing ----
     yellow = action only (the .btn fill, countdown digits, the 只限现场 tag). Never text on light grounds.
     red    = loss / 蚀 / 不及格 only.   green = tested / passes / ✓ only.  "Just holding" RM90,000 is NEUTRAL. */
  --ds-y: #ffd43b;  --ds-y-hover: #ffdd66;  --ds-y-press: #f5c400;
  --ds-y-deep: #9a7400;       /* yellow-family LINE on light grounds (focus ring, tag outline): 3.8 : 1 on paper */
  --ds-dn: #e5484d;           /* red. Light: 3.5 paper / 3.9 white -> big numbers & stamps only (>=24px, or >=19px bold).
                                 Ink: 5.0 ink / 4.5 ink-2 -> any size. */
  --ds-dn-t: #c62f33;         /* red small text on light: 4.8 paper / 5.5 white */
  --ds-dn-d: #e5484d;         /* red on ink (same hue, kept as a name for old rules) */
  --ds-up: #218358;           /* green on light, big numbers & stamps: 4.2 paper / 4.7 white (#30a46c is 2.8 on paper: fails) */
  --ds-up-t: #1d7a50;         /* green small text on light: 4.7 paper / 5.3 white */
  --ds-up-d: #30a46c;         /* green on ink, any size: 6.2 ink / 5.6 ink-2 */

  /* ---- type families: Noto Sans SC 400/700/900 for Chinese, Archivo (condensed, tabular) for every figure,
         JetBrains Mono 500-700 only for countdown digits, data tags and receipt lines. No others. ---- */
  --ds-zh: "Noto Sans SC", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", system-ui, sans-serif;
  --ds-num: "Archivo", "Noto Sans SC", system-ui, sans-serif;
  --ds-mono: "JetBrains Mono", "Noto Sans SC", ui-monospace, "SFMono-Regular", Menlo, monospace;   /* v9: Chinese in mono runs uses Noto (no wide CJK-mono gaps) */

  /* ---- type scale: size (390 -> 1440) ---- */
  --ds-fs-display: clamp(34px, 9.4vw, 64px);  /* H1, hero only.            390: 36.7  1440: 64 */
  --ds-fs-h2: clamp(26px, 7.2vw, 44px);       /* every section H2.         390: 28.1  1440: 44 */
  --ds-fs-h3: clamp(19px, 4.9vw, 24px);       /* item titles, pull quote.  390: 19.1  1440: 24 */
  --ds-fs-lead: clamp(17px, 4.4vw, 19px);     /* the one-line lead / sub.  390: 17.2  1440: 19 */
  --ds-fs-body: 16px;                         /* body: 16 phone, 17 at >=960 */
  --ds-fs-small: 14px;                        /* item sub-lines, labels */
  --ds-fs-fine: 13px;                         /* eyebrow, fine print, consent */
  --ds-fs-tag: 13px;                          /* mono data tags only */
  --ds-fs-num-xl: clamp(56px, 17vw, 120px);   /* BigNumber: the ONE number of a screen. 390: 66.3 */
  --ds-fs-num-l: clamp(40px, 11vw, 76px);     /* two numbers side by side.               390: 42.9 */
  --ds-fs-num-m: clamp(26px, 7vw, 40px);      /* stats row, countdown digits.            390: 27.3 */
  --ds-fs-stamp: clamp(20px, 5.6vw, 28px);    /* TestStamp; --lg variant = 1.6x */

  /* ---- line heights ---- */
  --ds-lh-display: 1.2; --ds-lh-h2: 1.2; --ds-lh-h3: 1.35; --ds-lh-lead: 1.5; --ds-lh-body: 1.5; --ds-lh-num: .92;

  /* ---- spacing scale: 4 · 8 · 16 · 24 · 40 · 64 · 96 = s1 s2 s4 s5 s6 s8 s9.
         s3 (12), s7 (48), s10 (128) are legacy v5 steps: do not use them in new v7 code. ---- */
  --ds-s1: 4px; --ds-s2: 8px; --ds-s3: 12px; --ds-s4: 16px; --ds-s5: 24px; --ds-s6: 40px; --ds-s7: 48px; --ds-s8: 64px; --ds-s9: 96px; --ds-s10: 128px;
  --ds-gutter: 20px;                           /* side gutter (16 at <=360, 32 at >=960) */
  --ds-sec-y: 64px;                            /* section padding top/bottom: 64 phone, 96 at >=960 */
  --ds-max: 1120px;                            /* desktop content max width (= .wrap, .ds-wrap content) */
  --ds-measure: 36em;                          /* max body line length */

  /* ---- shape ---- */
  --ds-r-s: 10px; --ds-r: 16px; --ds-r-l: 24px;
  --ds-shadow-lift: 0 24px 60px -30px rgba(11,13,16,.45);

  /* ---- motion: purposeful, <= 400ms (count-up 900ms is the one named exception). All off under reduced motion. ---- */
  --ds-ease: cubic-bezier(.22,.8,.24,1);       /* default out-ease */
  --ds-ease-thunk: cubic-bezier(.3,1.5,.55,1); /* stamp overshoot */
  --ds-t1: .15s; --ds-t2: .25s; --ds-t3: .4s;  /* tap feedback / state change / reveal */

  /* ---- form modal (the sheet in _src/13-sheet.html; zh.css + sec-sheet.css read these) ---- */
  --ds-sheet-bg: var(--ds-white);
  --ds-sheet-r: 24px;                          /* top corners on phone, all corners on desktop */
  --ds-sheet-w: 480px;                         /* desktop modal width */
  --ds-sheet-pad: 24px;
  --ds-sheet-shadow: 0 40px 80px -24px rgba(5,6,8,.55);
  --ds-scrim: rgba(5,6,8,.64);

  /* ---- sticky bar ---- */
  --ds-sticky-h: 64px;
  --ds-glass: rgba(11,13,16,.84);

  --ds-ground: var(--ds-paper);                /* the colour under the current element; set by .ds-sec--* and .ds-card */
}
@media (max-width: 360px) { :root { --ds-gutter: 16px; } }
@media (min-width: 960px) { :root { --ds-sec-y: 96px; --ds-gutter: 32px; --ds-fs-body: 17px; } }

/* =================== Section shell ===================
   <section id="x" class="ds-sec ds-sec--dark" data-section="x"><div class="ds-wrap"> … </div></section>
   Ground order (REBUILD_PLAN): S1 dark · S2 dark · S3 paper · S4 dark · S5 paper · S6 dark · S7 paper · S8 white · S9 dark · S10 paper · footer dark */
.ds-sec { position: relative; padding: var(--ds-sec-y) 0; background: var(--ds-paper); color: var(--ds-text); --ds-ground: var(--ds-paper); }
.ds-sec--paper { background: var(--ds-paper); color: var(--ds-text); --ds-ground: var(--ds-paper); }
.ds-sec--white { background: var(--ds-white); color: var(--ds-text); --ds-ground: var(--ds-white); }
.ds-sec--dark  { background: var(--ds-ink);  color: var(--ds-text-d); --ds-ground: var(--ds-ink); }
.ds-sec--tight { padding-top: var(--ds-s6); }                      /* continues the section above on the same ground */
.ds-wrap { width: 100%; max-width: calc(var(--ds-max) + 2 * var(--ds-gutter)); margin: 0 auto; padding: 0 var(--ds-gutter); }

/* desktop grid: head 5 cols left, content 7 cols right; or one column of 760 */
.ds-split { display: grid; gap: var(--ds-s6); }
.ds-stack { max-width: 760px; }
@media (min-width: 960px) {
  .ds-split { grid-template-columns: 5fr 7fr; gap: var(--ds-s9); align-items: start; }
  .ds-split > .ds-head { position: sticky; top: 96px; }
  .ds-split--even { grid-template-columns: 1fr 1fr; }
}

/* =================== Eyebrow -> Headline -> lead (the skim grammar) =================== */
.ds-head { max-width: 40em; }
.ds-kicker, .ds-eyebrow { display: block; margin: 0 0 var(--ds-s3); font: 700 var(--ds-fs-fine)/1.4 var(--ds-zh); letter-spacing: .08em; color: var(--ds-mute); }
.ds-sec--dark .ds-kicker, .ds-sec--dark .ds-eyebrow { color: var(--ds-mute-d); }
.ds-kicker .ds-live, .ds-eyebrow .ds-live { display: inline-block; width: 8px; height: 8px; margin-right: 8px; border-radius: 50%; background: var(--ds-dn-d); vertical-align: 1px; }

.ds-h1 { margin: 0; font: 900 var(--ds-fs-display)/var(--ds-lh-display) var(--ds-zh); letter-spacing: -.01em; text-wrap: balance; word-break: keep-all; line-break: strict; }
.ds-h1 > .ds-line { display: block; }                  /* one span per visual line (≤14 字, max 3) */
.ds-h2 { margin: 0; font: 900 var(--ds-fs-h2)/var(--ds-lh-h2) var(--ds-zh); letter-spacing: 0; text-wrap: balance; word-break: keep-all; line-break: strict; }
.ds-h2 > .ds-line { display: block; }
.ds-h1 > .l, .ds-h2 > .l, h1 > .l, h2 > .l { display: block; }   /* copy.zh.js writes headline lines as <span class="l"> (integrator, v7) */
.ds-h1 .num, .ds-h1 .ds-n, .ds-h2 .num, .ds-h2 .ds-n { font-family: var(--ds-num); font-weight: 800; font-stretch: 88%; font-size: 1.08em; letter-spacing: -.01em; font-variant-numeric: tabular-nums; }
.ds-h1 .ds-n--dn, .ds-h2 .ds-n--dn { color: var(--ds-dn); }          /* red figure inside a headline (big type: 3.5:1 ok) */
.ds-h1 .ds-n--up, .ds-h2 .ds-n--up { color: var(--ds-up); }
.ds-sec--dark .ds-n--dn { color: var(--ds-dn-d); } .ds-sec--dark .ds-n--up { color: var(--ds-up-d); }
.ds-h2 .ds-hl, .ds-h1 .ds-hl { color: inherit; box-shadow: inset 0 -.2em 0 rgba(20,22,26,.12); }  /* v7: neutral marker, NOT yellow */
.ds-sec--dark .ds-hl { box-shadow: inset 0 -.2em 0 rgba(255,255,255,.16); }
.ds-h3 { margin: 0; font: 700 var(--ds-fs-h3)/var(--ds-lh-h3) var(--ds-zh); text-wrap: balance; word-break: keep-all; }

.ds-lead { margin: var(--ds-s4) 0 0; max-width: var(--ds-measure); font: 400 var(--ds-fs-lead)/var(--ds-lh-lead) var(--ds-zh); color: var(--ds-text-2); text-wrap: pretty; }
.ds-lead--strong { font-weight: 700; color: var(--ds-text); }      /* the bold sub (S1 「你现在用的，可能就是它。」) */
.ds-sec--dark .ds-lead { color: var(--ds-text-d2); }
.ds-sec--dark .ds-lead--strong { color: var(--ds-text-d); }
.ds-body { margin: var(--ds-s6) 0 0; }
.ds-p { margin: 0; max-width: var(--ds-measure); font: 400 var(--ds-fs-body)/var(--ds-lh-body) var(--ds-zh); color: var(--ds-text-2); text-wrap: pretty; }
.ds-sec--dark .ds-p { color: var(--ds-text-d2); }
.ds-p + .ds-p { margin-top: var(--ds-s2); }
.ds-punch { margin: var(--ds-s6) 0 0; font: 900 var(--ds-fs-h3)/var(--ds-lh-h3) var(--ds-zh); text-wrap: balance; word-break: keep-all; }  /* the one punch line */
.ds-nw { white-space: nowrap; }
.ds-em { font-weight: 700; color: var(--ds-text); }          /* one per paragraph */
.ds-sec--dark .ds-em { color: #fff; }

/* =================== FinePrint + Tag =================== */
.ds-fine { margin: var(--ds-s4) 0 0; max-width: var(--ds-measure); font: 400 var(--ds-fs-fine)/1.6 var(--ds-zh); color: var(--ds-mute); }
.ds-sec--dark .ds-fine { color: var(--ds-mute-d); }
.ds-fine a { color: inherit; font-weight: 700; text-underline-offset: 3px; }
.ds-tag { display: inline-flex; align-items: center; gap: 6px; padding: 2px 8px; border: 1px solid currentColor; border-radius: 6px;
  font: 600 var(--ds-fs-tag)/1.5 var(--ds-mono); letter-spacing: .02em; color: var(--ds-mute); white-space: nowrap; vertical-align: middle; }
.ds-sec--dark .ds-tag { color: var(--ds-mute-d); }
.ds-tag svg { width: 12px; height: 12px; flex: none; }
.ds-tag--live { border: 1.5px solid var(--ds-y-deep); background: rgba(255,212,59,.28); color: var(--ds-text); font-family: var(--ds-zh); font-weight: 700; }  /* 只限现场 */
.ds-sec--dark .ds-tag--live { border-color: var(--ds-y); background: transparent; color: var(--ds-y); }

/* =================== BigNumber ===================
   <p class="ds-big ds-big--dn" data-count-from="10000" data-count-to="2600" data-count-prefix="RM">RM2,600</p>
   Neutral by default (inherits text colour). --dn red = a loss. --up green = a test result that passes. */
.ds-big { display: block; margin: 0; font-family: var(--ds-num); font-weight: 800; font-stretch: 88%; font-variant-numeric: tabular-nums lining-nums;
  font-size: var(--ds-fs-num-xl); line-height: var(--ds-lh-num); letter-spacing: -.02em; white-space: nowrap; }
.ds-big--l { font-size: var(--ds-fs-num-l); }
.ds-big--m { font-size: var(--ds-fs-num-m); letter-spacing: -.01em; }
.ds-big-pre { display: inline-block; margin-right: .06em; font-size: .42em; letter-spacing: 0; vertical-align: .95em; }  /* small top-aligned "RM" */
.ds-big--dn { color: var(--ds-dn); } .ds-big--up { color: var(--ds-up); }
.ds-sec--dark .ds-big--dn, .ds-card--dark .ds-big--dn { color: var(--ds-dn-d); } .ds-sec--dark .ds-big--up, .ds-card--dark .ds-big--up { color: var(--ds-up-d); }
.ds-big s, .ds-big-was { color: var(--ds-mute); text-decoration-thickness: .08em; text-decoration-color: var(--ds-dn); }
.ds-sec--dark .ds-big s, .ds-sec--dark .ds-big-was { color: var(--ds-mute-d); }
.ds-big-label { display: block; margin: 0 0 var(--ds-s2); font: 700 var(--ds-fs-fine)/1.4 var(--ds-zh); color: var(--ds-mute); }   /* label ABOVE */
.ds-big-note { display: block; margin: var(--ds-s2) 0 0; font: 400 var(--ds-fs-small)/1.5 var(--ds-zh); color: var(--ds-mute); }    /* note BELOW */
.ds-sec--dark .ds-big-label, .ds-sec--dark .ds-big-note { color: var(--ds-mute-d); }
.ds-big-note .ds-dn { color: var(--ds-dn-t); font-weight: 700; }
.ds-sec--dark .ds-big-note .ds-dn { color: var(--ds-dn-d); }

/* v5 number classes (still used by sections; same face as .ds-big) */
.ds-num { display: block; margin: 0; font-family: var(--ds-num); font-weight: 800; font-stretch: 88%; font-variant-numeric: tabular-nums;
  letter-spacing: -.02em; line-height: var(--ds-lh-num); font-size: var(--ds-fs-num-l); }
.ds-num--xl { font-size: var(--ds-fs-num-xl); }
.ds-num--m { font-size: var(--ds-fs-num-m); letter-spacing: -.01em; }
.ds-num--dn { color: var(--ds-dn); } .ds-num--up { color: var(--ds-up); }
.ds-sec--dark .ds-num--dn { color: var(--ds-dn-d); } .ds-sec--dark .ds-num--up { color: var(--ds-up-d); }
.ds-stat { display: grid; gap: var(--ds-s2); align-content: start; }
.ds-stat-label { font: 700 var(--ds-fs-fine)/1.4 var(--ds-zh); color: var(--ds-mute); }
.ds-stat-note  { font: 400 var(--ds-fs-small)/1.5 var(--ds-zh); color: var(--ds-mute); }
.ds-sec--dark .ds-stat-label, .ds-sec--dark .ds-stat-note { color: var(--ds-mute-d); }
.ds-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); gap: var(--ds-s5); }

/* =================== TestStamp (测试章, the signature) ===================
   <span class="ds-stamp ds-stamp--fail" data-stamp><span>不及格</span><svg …✕…/></span>
   ds.js adds .is-in when 60% visible (data-stamp="manual": the section calls DS.stamp(el) itself). */
.ds-stamp { position: relative; display: inline-flex; align-items: center; gap: .3em; padding: .22em .55em .26em; border: 3px solid currentColor; border-radius: 10px;
  box-shadow: inset 0 0 0 2px var(--ds-ground), inset 0 0 0 3.5px currentColor;
  font: 900 var(--ds-fs-stamp)/1.15 var(--ds-zh); letter-spacing: .1em; white-space: nowrap; transform: rotate(-6deg); transform-origin: 50% 60%; }
.ds-stamp svg { width: .9em; height: .9em; flex: none; }
.ds-stamp--pass { color: var(--ds-up); }   /* 及格 ✓ */
.ds-stamp--fail { color: var(--ds-dn); }   /* 不及格 ✕ */
.ds-sec--dark .ds-stamp--pass, .ds-card--dark .ds-stamp--pass { color: var(--ds-up-d); }
.ds-sec--dark .ds-stamp--fail, .ds-card--dark .ds-stamp--fail { color: var(--ds-dn-d); }
.ds-stamp--lg { font-size: calc(var(--ds-fs-stamp) * 1.6); border-width: 4px; box-shadow: inset 0 0 0 3px var(--ds-ground), inset 0 0 0 5px currentColor; }
.ds-stamp::after { content: ""; position: absolute; inset: -5px; border: 2px solid currentColor; border-radius: 14px; opacity: 0; pointer-events: none; }
.ds-motion [data-stamp]:not(.is-in) { opacity: 0; }
.ds-motion .ds-stamp.is-in { animation: ds-thunk .24s var(--ds-ease-thunk) both; }    /* 150ms land + 90ms settle */
.ds-motion .ds-stamp.is-in::after { animation: ds-ring .3s ease-out .12s both; }
@keyframes ds-thunk { 0% { opacity: 0; transform: rotate(-6deg) scale(1.6); } 62% { opacity: 1; transform: rotate(-6deg) scale(.94); } 100% { opacity: 1; transform: rotate(-6deg) scale(1); } }
@keyframes ds-ring { 0% { opacity: .45; transform: scale(.92); } 100% { opacity: 0; transform: scale(1.18); } }

/* small ✓ / ✕ marks for lists (S3 rows, S5 outcomes, S8 honest list) */
.ds-mark { display: inline-grid; place-items: center; width: 28px; height: 28px; flex: none; border-radius: 50%; color: #fff; }
.ds-mark svg { width: 16px; height: 16px; }
.ds-mark--pass { background: var(--ds-up); } .ds-mark--fail { background: var(--ds-dn-t); }
.ds-sec--dark .ds-mark--pass { background: var(--ds-up-d); color: var(--ds-ink); } .ds-sec--dark .ds-mark--fail { background: var(--ds-dn-d); color: var(--ds-ink); }
.ds-mark--off { background: transparent; color: var(--ds-mute); box-shadow: inset 0 0 0 1.5px currentColor; }
.ds-sec--dark .ds-mark--off { color: var(--ds-mute-d); }

/* =================== Card (real objects only: a product UI, a gift, a test card. Never for structure) =================== */
.ds-card { position: relative; padding: var(--ds-s5); border-radius: var(--ds-r); background: var(--ds-white); color: var(--ds-text);
  box-shadow: inset 0 0 0 1px var(--ds-line); --ds-ground: var(--ds-white); }
.ds-sec--white .ds-card { background: var(--ds-paper); --ds-ground: var(--ds-paper); }
.ds-sec--dark .ds-card, .ds-card--dark { background: var(--ds-ink-2); color: var(--ds-text-d); box-shadow: inset 0 0 0 1px var(--ds-line-d); --ds-ground: var(--ds-ink-2); }
.ds-card--lift { box-shadow: inset 0 0 0 1px var(--ds-line), var(--ds-shadow-lift); }
.ds-sec--dark .ds-card--lift { box-shadow: inset 0 0 0 1px var(--ds-line-d), 0 30px 60px -30px rgba(0,0,0,.7); }
.ds-card-tag { position: absolute; top: var(--ds-s4); right: var(--ds-s4); }           /* pin a .ds-tag 我们自己测的 to the card */

/* =================== LockCard ===================
   Fake value only (never the real one). Shimmers once when .is-in is added (ds.js, 50% visible). */
.ds-lock { position: relative; display: grid; min-height: 132px; padding: var(--ds-s4); border-radius: var(--ds-r); overflow: hidden; isolation: isolate;
  background: repeating-linear-gradient(135deg, transparent 0 8px, rgba(20,22,26,.035) 8px 16px), var(--ds-white); box-shadow: inset 0 0 0 1px var(--ds-line); color: var(--ds-text); }
.ds-sec--dark .ds-lock { background: repeating-linear-gradient(135deg, transparent 0 8px, rgba(255,255,255,.025) 8px 16px), var(--ds-ink-2); box-shadow: inset 0 0 0 1px var(--ds-line-d); color: var(--ds-text-d); }
.ds-lock-blur { filter: blur(7px); opacity: .55; user-select: none; pointer-events: none; }
.ds-lock-badge { position: absolute; inset: 0; z-index: 1; display: grid; place-content: center; justify-items: center; gap: var(--ds-s2); text-align: center;
  font: 700 var(--ds-fs-small)/1.4 var(--ds-zh); }
.ds-lock-ico { display: grid; place-items: center; width: 40px; height: 40px; border-radius: 50%; background: var(--ds-text); color: var(--ds-paper); }
.ds-sec--dark .ds-lock-ico { background: var(--ds-text-d); color: var(--ds-ink); }
.ds-lock-ico svg { width: 20px; height: 20px; }
.ds-lock::after { content: ""; position: absolute; inset: 0; z-index: 2; pointer-events: none; transform: translateX(-110%);
  background: linear-gradient(100deg, transparent 20%, rgba(255,255,255,.55) 50%, transparent 80%); }
.ds-sec--dark .ds-lock::after { background: linear-gradient(100deg, transparent 20%, rgba(255,255,255,.14) 50%, transparent 80%); }
.ds-motion .ds-lock.is-in::after { animation: ds-shimmer .4s var(--ds-ease) .1s 1 both; }
@keyframes ds-shimmer { from { transform: translateX(-110%); } to { transform: translateX(110%); } }

/* =================== ChatBubble (phone-chat look, no WhatsApp branding, no green: green = passes) =================== */
.ds-chat { display: grid; border-radius: var(--ds-r-l); overflow: hidden; background: #e9e5dc; color: var(--ds-text); box-shadow: inset 0 0 0 1px var(--ds-line); }
.ds-sec--dark .ds-chat { background: #101318; color: var(--ds-text-d); box-shadow: inset 0 0 0 1px var(--ds-line-d), 0 30px 60px -30px rgba(0,0,0,.7); }
.ds-chat-bar { display: flex; align-items: center; gap: var(--ds-s3); padding: var(--ds-s3) var(--ds-s4); border-bottom: 1px solid var(--ds-line); background: rgba(255,255,255,.5); }
.ds-sec--dark .ds-chat-bar { border-bottom-color: var(--ds-line-d); background: var(--ds-ink-2); }
.ds-chat-av { display: grid; place-items: center; width: 36px; height: 36px; flex: none; border-radius: 50%; background: var(--ds-text); color: var(--ds-paper); font: 800 13px/1 var(--ds-num); }
.ds-sec--dark .ds-chat-av { background: var(--ds-text-d); color: var(--ds-ink); }
.ds-chat-who { display: grid; min-width: 0; flex: 1; line-height: 1.3; }
.ds-chat-who b { font: 700 15px/1.3 var(--ds-zh); }
.ds-chat-who small { font-size: 12px; color: var(--ds-mute); }
.ds-sec--dark .ds-chat-who small { color: var(--ds-mute-d); }
.ds-chat-body { display: grid; gap: var(--ds-s2); padding: var(--ds-s4); }
.ds-bubble { position: relative; max-width: 86%; padding: 10px 14px 8px; border-radius: 16px; font: 400 var(--ds-fs-body)/1.5 var(--ds-zh); }
.ds-bubble p { margin: 0; }
.ds-bubble--user { justify-self: end; border-bottom-right-radius: 4px; background: #dde4ee; color: var(--ds-text); }      /* 14.2:1 */
.ds-bubble--ai { justify-self: start; border-bottom-left-radius: 4px; background: var(--ds-white); color: var(--ds-text); }
.ds-sec--dark .ds-bubble--user { background: #2b3445; color: var(--ds-text-d); }   /* 11.1:1 */
.ds-sec--dark .ds-bubble--ai { background: var(--ds-ink-3); color: var(--ds-text-d); }   /* 14.1:1 */
.ds-bubble-meta { display: flex; justify-content: flex-end; align-items: center; gap: 4px; margin-top: 2px; font: 500 11px/1.2 var(--ds-mono); color: var(--ds-mute); }
.ds-sec--dark .ds-bubble-meta { color: var(--ds-mute-d); }
.ds-bubble-meta svg { width: 16px; height: 10px; }
.ds-typing { display: inline-flex; gap: 4px; padding: 14px 16px; }
.ds-typing i { width: 7px; height: 7px; border-radius: 50%; background: currentColor; opacity: .45; }
.ds-motion .ds-typing i { animation: ds-dot 1s infinite ease-in-out; }
.ds-motion .ds-typing i:nth-child(2) { animation-delay: .15s; } .ds-motion .ds-typing i:nth-child(3) { animation-delay: .3s; }
@keyframes ds-dot { 0%, 60%, 100% { opacity: .3; transform: none; } 30% { opacity: .9; transform: translateY(-3px); } }
.ds-caret { display: inline-block; width: 2px; height: 1.05em; margin-left: 1px; vertical-align: -.15em; background: currentColor; }
.ds-motion .ds-caret { animation: ds-blink 1s steps(2) infinite; }
@keyframes ds-blink { 50% { opacity: 0; } }

/* =================== ProgressBar ===================
   <div class="ds-progress" style="--p:.6" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="60"></div>
   Set --p (0..1) from JS; the fill animates over --ds-progress-ms (default .4s; the S4 demo sets 1.5s). */
.ds-progress { position: relative; height: 8px; border-radius: 99px; overflow: hidden; background: rgba(20,22,26,.1); }
.ds-sec--dark .ds-progress, .ds-card--dark .ds-progress { background: rgba(255,255,255,.1); }
.ds-progress::after { content: ""; position: absolute; inset: 0; border-radius: inherit; background: var(--ds-text-2); transform-origin: left; transform: scaleX(var(--p, 0));
  transition: transform var(--ds-progress-ms, .4s) linear, background var(--ds-t2); }
.ds-sec--dark .ds-progress::after, .ds-card--dark .ds-progress::after { background: var(--ds-text-d2); }
.ds-progress--pass::after { background: var(--ds-up) !important; } .ds-progress--fail::after { background: var(--ds-dn) !important; }
.ds-progress-label { display: flex; justify-content: space-between; margin: var(--ds-s2) 0 0; font: 400 var(--ds-fs-small)/1.4 var(--ds-zh); color: var(--ds-mute); }
.ds-sec--dark .ds-progress-label { color: var(--ds-mute-d); }
.ds-progress-label b { font: 700 var(--ds-fs-small)/1.4 var(--ds-mono); }

/* =================== Countdown ===================
   Keeps the event.js hooks: [data-cd] + [data-cd-d|h|m|s]. Mono digits, yellow on dark, ink on light. */
.ds-cd { display: grid; gap: var(--ds-s2); }
.ds-cd-k { margin: 0; font: 700 var(--ds-fs-fine)/1.4 var(--ds-zh); color: var(--ds-mute); }
.ds-sec--dark .ds-cd-k { color: var(--ds-mute-d); }
.ds-cd-n { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 var(--ds-s2); margin: 0; }
.ds-cd-n b { font: 700 var(--ds-fs-num-m)/1 var(--ds-mono); font-variant-numeric: tabular-nums; letter-spacing: -.02em; color: var(--ds-text); }
.ds-sec--dark .ds-cd-n b { color: var(--ds-y); }
.ds-cd-n i { margin: 0 var(--ds-s1) 0 2px; font: 400 var(--ds-fs-fine)/1 var(--ds-zh); font-style: normal; color: var(--ds-mute); }
.ds-sec--dark .ds-cd-n i { color: var(--ds-mute-d); }

/* =================== Ticket (S5 agenda) ===================
   Perforated edge between head and body; notches follow any height. Ground-aware via filter shadow. */
.ds-ticket { position: relative; display: grid; grid-template-rows: auto 1fr; filter: drop-shadow(0 0 1px rgba(20,22,26,.28)) drop-shadow(0 16px 24px rgba(20,22,26,.08)); color: var(--ds-text); }
.ds-ticket > * { background: var(--ds-white); }
.ds-sec--white .ds-ticket > * { background: var(--ds-paper); }
.ds-sec--dark .ds-ticket { filter: drop-shadow(0 0 1px rgba(255,255,255,.22)); color: var(--ds-text-d); }
.ds-sec--dark .ds-ticket > * { background: var(--ds-ink-2); }
.ds-ticket-head { padding: var(--ds-s5) var(--ds-s5) var(--ds-s4); border-radius: var(--ds-r) var(--ds-r) 0 0;
  -webkit-mask: radial-gradient(circle 12px at 0 100%, #0000 97%, #000) left / 51% 100% no-repeat, radial-gradient(circle 12px at 100% 100%, #0000 97%, #000) right / 51% 100% no-repeat;
          mask: radial-gradient(circle 12px at 0 100%, #0000 97%, #000) left / 51% 100% no-repeat, radial-gradient(circle 12px at 100% 100%, #0000 97%, #000) right / 51% 100% no-repeat; }
.ds-ticket-body { position: relative; padding: var(--ds-s5); border-radius: 0 0 var(--ds-r) var(--ds-r);
  -webkit-mask: radial-gradient(circle 12px at 0 0, #0000 97%, #000) left / 51% 100% no-repeat, radial-gradient(circle 12px at 100% 0, #0000 97%, #000) right / 51% 100% no-repeat;
          mask: radial-gradient(circle 12px at 0 0, #0000 97%, #000) left / 51% 100% no-repeat, radial-gradient(circle 12px at 100% 0, #0000 97%, #000) right / 51% 100% no-repeat; }
.ds-ticket-body::before { content: ""; position: absolute; top: -1px; left: 20px; right: 20px; border-top: 2px dashed var(--ds-line); }
.ds-sec--dark .ds-ticket-body::before { border-top-color: var(--ds-line-d); }
.ds-ticket-k { margin: 0; font: 700 var(--ds-fs-fine)/1.4 var(--ds-mono); letter-spacing: .06em; color: var(--ds-mute); }   /* 第一晚 · 11月17日（二）8PM */
.ds-sec--dark .ds-ticket-k { color: var(--ds-mute-d); }
.ds-ticket-t { margin: var(--ds-s2) 0 0; font: 900 var(--ds-fs-h3)/var(--ds-lh-h3) var(--ds-zh); text-wrap: balance; word-break: keep-all; }
.ds-ticket-no { position: absolute; top: var(--ds-s5); right: var(--ds-s5); font: 800 var(--ds-fs-num-m)/1 var(--ds-num); font-stretch: 88%; color: var(--ds-line); }
.ds-sec--dark .ds-ticket-no { color: rgba(255,255,255,.14); }

/* =================== Receipt (S3 学费 block) ===================
   Blank amounts are drawn as lines (the reader fills in their own number). Zig-zag bottom edge. */
.ds-receipt { max-width: 400px; filter: drop-shadow(0 0 1px rgba(20,22,26,.22)) drop-shadow(0 18px 24px rgba(20,22,26,.1)); }
.ds-receipt-in { padding: var(--ds-s5) var(--ds-s5) calc(var(--ds-s5) + 10px); background: var(--ds-white); color: var(--ds-text);
  -webkit-mask: conic-gradient(from -45deg at bottom, #0000, #000 1deg 89deg, #0000 90deg) 50% / 16px 100%;
          mask: conic-gradient(from -45deg at bottom, #0000, #000 1deg 89deg, #0000 90deg) 50% / 16px 100%; }
.ds-receipt-head { margin: 0 0 var(--ds-s4); padding-bottom: var(--ds-s4); border-bottom: 2px dashed var(--ds-line); font: 700 var(--ds-fs-small)/1.5 var(--ds-zh); text-align: center; }
.ds-receipt-head small { display: block; font: 500 var(--ds-fs-tag)/1.6 var(--ds-mono); color: var(--ds-mute); letter-spacing: .06em; }
.ds-receipt-rows { margin: 0; padding: 0; list-style: none; display: grid; gap: var(--ds-s3); }
.ds-receipt-rows li { display: flex; align-items: baseline; gap: var(--ds-s2); font: 400 var(--ds-fs-body)/1.4 var(--ds-zh); }
.ds-receipt-rows li::after { content: ""; order: 1; flex: 1; border-bottom: 2px dotted var(--ds-line); transform: translateY(-4px); }
.ds-receipt-rows b { order: 2; min-width: 6.5em; font: 600 var(--ds-fs-body)/1.4 var(--ds-mono); text-align: right; color: var(--ds-mute); }
.ds-receipt-rows .ds-blank { display: inline-block; width: 4.5em; border-bottom: 2px solid var(--ds-text-2); vertical-align: -2px; }
.ds-receipt-total { display: flex; align-items: center; justify-content: space-between; gap: var(--ds-s4); margin: var(--ds-s5) 0 0; padding-top: var(--ds-s4); border-top: 2px solid var(--ds-text); }
.ds-receipt-total > span { font: 900 var(--ds-fs-h3)/1.3 var(--ds-zh); }
.ds-receipt .ds-stamp { --ds-ground: var(--ds-white); }

/* =================== media frame + placeholders (v5, kept) =================== */
.ds-frame { position: relative; margin: var(--ds-s6) 0 0; border-radius: var(--ds-r-l); overflow: hidden; background: var(--ds-ink-2); color: var(--ds-text-d); }
.ds-frame--light { background: var(--ds-white); color: var(--ds-text); box-shadow: inset 0 0 0 1px var(--ds-line); }
.ds-sec--dark .ds-frame { background: var(--ds-ink-2); box-shadow: inset 0 0 0 1px var(--ds-line-d); }
.ds-frame--bleed { margin-left: calc(var(--ds-gutter) * -1); margin-right: calc(var(--ds-gutter) * -1); border-radius: 0; }
@media (min-width: 720px) { .ds-frame--bleed { margin-left: 0; margin-right: 0; border-radius: var(--ds-r-l); } }
.ds-frame-tag { position: absolute; top: var(--ds-s3); right: var(--ds-s3); z-index: 1; }
.ds-frame figcaption, .ds-frame-cap { padding: var(--ds-s3) var(--ds-s4); font: 400 var(--ds-fs-fine)/1.6 var(--ds-zh); color: var(--ds-mute-d); }
.ds-ph { display: flex; align-items: center; gap: var(--ds-s3); min-height: 48px; margin: var(--ds-s4) 0 0; padding: var(--ds-s2) var(--ds-s4);
  border: 1px dashed var(--ds-line); border-radius: var(--ds-r-s); font: 400 var(--ds-fs-fine)/1.5 var(--ds-zh); color: var(--ds-mute); }
.ds-sec--dark .ds-ph { border-color: var(--ds-line-d); color: var(--ds-mute-d); }

/* =================== list row, callout, divider, split (v5, kept) =================== */
.ds-list { margin: 0; padding: 0; list-style: none; border-top: 1px solid var(--ds-line); }
.ds-sec--dark .ds-list { border-top-color: var(--ds-line-d); }
.ds-row { display: grid; grid-template-columns: 44px 1fr; gap: var(--ds-s1) var(--ds-s3); padding: var(--ds-s5) 0; border-bottom: 1px solid var(--ds-line); }
.ds-sec--dark .ds-row { border-bottom-color: var(--ds-line-d); }
.ds-row--bare { grid-template-columns: 1fr; }
.ds-row-mark { grid-row: 1 / span 2; font: 600 var(--ds-fs-small)/1.6 var(--ds-mono); color: var(--ds-mute); }
.ds-sec--dark .ds-row-mark { color: var(--ds-mute-d); }
.ds-row-title { margin: 0; font: 700 17px/1.4 var(--ds-zh); text-wrap: balance; word-break: keep-all; }
.ds-row-sub { margin: 0; font: 400 var(--ds-fs-small)/1.5 var(--ds-zh); color: var(--ds-mute); }
.ds-sec--dark .ds-row-sub { color: var(--ds-mute-d); }
@media (min-width: 960px) { .ds-row { grid-template-columns: 64px 1fr; padding: var(--ds-s6) 0; } .ds-row--bare { grid-template-columns: 1fr; } .ds-row-title { font-size: 19px; } .ds-row-sub { font-size: 15px; } }

.ds-callout { margin: var(--ds-s6) 0 0; padding: 2px 0 2px var(--ds-s4); border-left: 3px solid var(--ds-line-strong); max-width: var(--ds-measure); font: 700 var(--ds-fs-lead)/1.5 var(--ds-zh); }
.ds-sec--dark .ds-callout { border-left-color: var(--ds-line-d-strong); }
.ds-callout small { display: block; margin-top: var(--ds-s1); font-weight: 400; font-size: var(--ds-fs-small); color: var(--ds-mute); }
.ds-sec--dark .ds-callout small { color: var(--ds-mute-d); }

.ds-rule { height: 0; margin: var(--ds-s7) 0; border: 0; border-top: 1px solid var(--ds-line); }
.ds-rule--strong { border-top: 2px solid var(--ds-line-strong); }
.ds-sec--dark .ds-rule { border-top-color: var(--ds-line-d); }
.ds-sec--dark .ds-rule--strong { border-top-color: var(--ds-line-d-strong); }

.ds-vs { display: grid; grid-template-columns: 1fr 1fr; border-top: 1px solid var(--ds-line); }
.ds-vs > * { padding: var(--ds-s5) var(--ds-s4) var(--ds-s5) 0; }
.ds-vs > * + * { padding-left: var(--ds-s4); border-left: 1px solid var(--ds-line); }
.ds-sec--dark .ds-vs, .ds-sec--dark .ds-vs > * + * { border-color: var(--ds-line-d); }

/* =================== CTAButton row ===================
   <div class="ds-cta"><button class="btn" type="button" data-cta="cta_x" data-open-form>免费留位 <span class="arrow" aria-hidden="true">→</span></button><p class="ds-cta-note">…</p></div>
   .btn itself lives in zh.css (yellow, min-height 56). Full width below 960px. */
.ds-cta { display: grid; justify-items: stretch; gap: var(--ds-s3); margin: var(--ds-s6) 0 0; }
.ds-cta .btn { width: 100%; }
.ds-cta-note { margin: 0; text-align: center; font: 400 var(--ds-fs-fine)/1.5 var(--ds-zh); color: var(--ds-mute); }
.ds-sec--dark .ds-cta-note { color: var(--ds-mute-d); }
@media (min-width: 960px) {
  .ds-cta { justify-items: start; }
  .ds-cta .btn { width: auto; min-width: 320px; }
  .ds-cta-note { text-align: left; }
}

/* =================== icons (inline SVG, stroke = currentColor) =================== */
.ds-ico { display: inline-block; width: 1em; height: 1em; flex: none; vertical-align: -.125em; }

/* =================== motion off =================== */
@media (prefers-reduced-motion: reduce) {
  .ds-sec *, .ds-sec *::before, .ds-sec *::after { animation-duration: .001s !important; animation-iteration-count: 1 !important; transition-duration: .001s !important; }
  [data-stamp] { opacity: 1 !important; }
  .ds-lock::after { display: none; }
}
