/* ══════════════════════════════════════════════════════════════════════
   星图邀请码分享页 · 样式（作用域版）
   ──────────────────────────────────────────────────────────────────────
   来源：设计方交付的 xingtu-invite 源码包（styles/tokens.css + base.css +
   components.css），由 tools/scope-invite-css.py 机械转换而来，**规则内容
   一字未改**，只做了三类适配：

   ① 作用域：源站是独立页面（body 即画布），H5 里画布是 #view 内的 `.iv`
      容器。故 html/body 复位类规则整条丢弃，body 规则改写到 `.iv`，
      其余选择器统一加 `.iv ` 前缀 —— 否则 `.toast / .copy / .body / .foot`
      这些通用类名会与 H5 主样式表互相污染。
   ② 入场编排：源页用 `html.js` / `body.is-ready` 两个钩子，这里换成根元素
      上的 `.iv--js` / `.iv--ready`（JS 里加，见 app.js 的 vInvite）。
   ③ 视口 → 画布：源页按 vw/vh 定尺寸、断点按视口宽判断；H5 的画布是被钉死
      的 .phone（≤420px），两者不等价。见文件末尾「画布尺寸适配」段。

   改版时请回源码目录改，然后重跑转换脚本；**不要只改这份产物**。
   ══════════════════════════════════════════════════════════════════════ */

/* ─────────── 源文件：tokens.css ─────────── */
/* ══════════════════════════════════════════════════════════════════════
   设计令牌 · 星图科技邀请码分享页
   色值 100% 取自品牌手册《BRAND COLOR SYSTEM》，一个新增色都没有 ——
   邀请页是品牌触点，自创颜色等于稀释品牌。
   ══════════════════════════════════════════════════════════════════════ */
.iv {
  /* ── 品牌原色：改动前请先看品牌手册 ── */
  --brand-violet: #6e4fe9; /* PRIMARY VIOLET */
  --brand-black: #000000; /* DEEP BLACK */
  --brand-violet-soft: #9ba6ff; /* LIGHT VIOLET */
  --brand-lavender: #e3dff8; /* LAVENDER */
  --brand-grey: #f2f2f2; /* GREY */
  --brand-yellow: #ffd200; /* ACCENT YELLOW */

  /* ── 语义别名：组件只许用这一层 ── */
  --color-bg: var(--brand-black);
  --color-surface: #0b0a14; /* 近黑带紫偏（票卡下半区） */
  --color-text: var(--brand-lavender); /* 对黑 16.0:1 */
  --color-muted: #a9a5be; /* 次级文字，对黑 8.8:1 */
  --color-ink: #08080a; /* 浅色底上的字 */
  --color-accent: var(--brand-yellow); /* 单页唯一强调色 → 只留给"复制"这个动作 */
  --color-line: rgba(227, 223, 248, 0.14);
  --color-line-strong: rgba(227, 223, 248, 0.3);

  /* ── 字体 ──
     拉丁展示字用 Chakra Petch：切角几何体，与 XT 标记的 45° 切角同源。
     中文走系统字面（PingFang / HarmonyOS / MiSans / 思源），零下载字节 ——
     完整中文字库动辄 3~8MB，在微信里加载等于劝退，这是中文 H5 的正确工程取舍。 */
  --font-cn: "PingFang SC", "HarmonyOS Sans SC", "MiSans", "Noto Sans SC",
    "Source Han Sans SC", "Microsoft YaHei", sans-serif;
  --font-display: "Chakra Petch", var(--font-cn);
  --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Consolas, monospace;

  /* ── 字阶（液体，375 → 1440 一把梭） ── */
  --fs-hero: clamp(34px, 10.4vw, 58px);
  --fs-code: clamp(23px, 7.4vw, 34px);
  --fs-lead: clamp(14px, 3.9vw, 16px);
  --fs-body: 15px;
  --fs-label: 12px;
  --fs-micro: 11px;

  /* ── 间距 ── */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 16px;
  --sp-4: 24px;
  --sp-5: 40px;
  --sp-6: 64px;
  --sp-7: 96px;

  /* ── 首屏高度 ──
     斜切光板的几何全部由这个变量推算（见 components.css 的 .beam）。
     早先按 body 百分比算高度，结果页面总高一变、光板就漂，
     宽屏下亮部直接压到 eyebrow 上，实测对比度掉到 1.70:1。
     锚定首屏高度之后，光板与文案的相对位置在任何断点都恒定。

     窄屏从 40vh 收到 32vh：容器里只放一个 min(76vw,336px) 的标记（375 下约
     173px 高），40vh 会多留出上下各 ~76px 的空档，整页被顶到 1272px ——
     手机上得滚动两次才看得完。收到 32vh 后上下各留 ~43px，仍然透气，
     但整页能落进一张截图里。 */
  --hero-h: clamp(240px, 32vh, 320px);

  /* ── 圆角 / 描边 ── */
  --r-sm: 8px;
  --r-md: 14px;
  --r-lg: 22px;
  --r-pill: 999px;

  /* ── 阴影：三级高度，带紫环境色而不是纯黑，暗底上才有"光感" ── */
  --shadow-1: 0 2px 8px rgba(0, 0, 0, 0.5);
  --shadow-2: 0 8px 28px rgba(0, 0, 0, 0.55);
  --shadow-3: 0 24px 64px rgba(23, 10, 74, 0.72), 0 0 0 1px rgba(227, 223, 248, 0.08);

  /* ── 动效 ── */
  --dur-fast: 160ms;
  --dur-base: 320ms;
  --dur-slow: 640ms;
  --dur-scene: 760ms;
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-soft: cubic-bezier(0.32, 0.72, 0, 1);
}

/* ─────────── 源文件：base.css ─────────── */
/* ══════════════════════════════════════════════════════════════════════
   基础层：字体加载、重置、排版、焦点、动效降级
   ══════════════════════════════════════════════════════════════════════ *//* ── 自托管拉丁字体：只取真正用到的字重，latin 子集各 10~22KB ── */
@font-face{
  font-family: "Chakra Petch";
  src: url("/assets/fonts/chakra-petch-latin-500-normal.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

@font-face{
  font-family: "Chakra Petch";
  src: url("/assets/fonts/chakra-petch-latin-600-normal.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

@font-face{
  font-family: "JetBrains Mono";
  src: url("/assets/fonts/jetbrains-mono-latin-500-normal.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

@font-face{
  font-family: "JetBrains Mono";
  src: url("/assets/fonts/jetbrains-mono-latin-700-normal.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

.iv *, .iv *::before, .iv *::after {
  box-sizing: border-box;
}

.iv {
  min-height: 100dvh;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-cn);
  font-size: var(--fs-body);
  line-height: 1.75; /* 中文正文行高 ≥1.7 */
  letter-spacing: 0.02em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

.iv img {
  display: block;
  max-width: 100%;
  height: auto;
}

.iv button {
  font: inherit;
  color: inherit;
  border: 0;
  background: none;
  cursor: pointer;
}
/* 焦点圈：键盘用户必须看得见（黄色 + 2px 偏移） */
.iv :focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

.iv ::selection {
  background: var(--brand-yellow);
  color: var(--color-ink);
}
/* 无障碍：只给读屏的文本 */
.iv .sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
/* ── 动效降级：关掉全部动画，内容直接呈现 ── */
@media (prefers-reduced-motion: reduce){

.iv *, .iv *::before, .iv *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }

.iv .reveal {
    opacity: 1 !important;
    transform: none !important;
  }
}

/* ─────────── 源文件：components.css ─────────── */
/* ══════════════════════════════════════════════════════════════════════
   构成层：氛围 → 首屏 → 文案 → 邀请凭证 → 页脚 → 动效编排 → 响应式
   ══════════════════════════════════════════════════════════════════════ *//* ════════════ 1. 氛围层 ════════════
   暗底页最忌讳"纯色平铺"。这里三层叠加：
   斜切紫色光块（取自品牌手册里贯穿大屏/包装/工牌的巨型紫色斜板）
   + 两束径向辉光 + 噪点颗粒。全部 pointer-events:none，不参与交互。 */
.iv .atmosphere {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}
/* 斜切紫光板：品牌手册里贯穿大屏 / 包装 / 工牌的巨型紫色斜板。
   两条纪律：
   ① 溢出视口 —— 左右各甩出画面外，于是只剩上下两条对角长边可见，
      永远不会露出直角或圆角。硬边是对的：品牌里的斜板就是硬边，不是柔光。
   ② 尺寸锚定 --hero-h 而不是 body 百分比 —— 页面总高一变（手机 1290 / 宽屏 900），
      按百分比算的光板几何就漂，亮部会压到文案上。锚定首屏后，
      下缘中心线恒定落在 0.80 × 首屏高，叠加 ±39px 的旋转落差后仍高于正文起点。 */
.iv .beam {
  position: absolute;
  top: calc(var(--hero-h) * -0.90);
  left: -42%;
  width: 186%;
  height: calc(var(--hero-h) * 1.66);
  background: linear-gradient(152deg, #6e4fe9 0%, #5433c8 28%, #241566 64%, #0a0714 100%);
  transform: rotate(-8deg) skewX(-13deg);
  /* 基础值就是最终值：没有 JS 时这里必须已经是对的，入场动画只是把它"演"出来 */
  opacity: 0.9;
}
/* 第二道更亮的细斜带：单板太平，叠一道才有品牌手册里的"层叠透明"感。
   这条两端都渐变到全透明，所以不产生任何硬边，只贡献一道掠光。 */
.iv .beam--2 {
  top: calc(var(--hero-h) * -0.90);
  left: -42%;
  width: 186%;
  height: calc(var(--hero-h) * 1.66);
  opacity: 0.7;
  background: linear-gradient(
    152deg,
    rgba(155, 166, 255, 0) 6%,
    rgba(155, 166, 255, 0.45) 36%,
    rgba(110, 79, 233, 0) 76%
  );
  transform: rotate(-8deg) skewX(-13deg);
}

.iv .glow {
  position: absolute;
  border-radius: 50%;
  opacity: 1;
}

.iv .glow--violet {
  top: calc(var(--hero-h) * -0.52);
  left: 50%;
  width: min(150vw, 980px);
  height: calc(var(--hero-h) * 1.75);
  transform: translateX(-50%);
  background: radial-gradient(closest-side, rgba(110, 79, 233, 0.6), rgba(110, 79, 233, 0) 74%);
}

.iv .glow--soft {
  right: -24%;
  bottom: 4%;
  width: 76%;
  aspect-ratio: 1 / 1;
  background: radial-gradient(closest-side, rgba(155, 166, 255, 0.16), rgba(155, 166, 255, 0) 72%);
}
/* 星图：几个节点 + 发丝连线。名字叫星图，背景就该有一次"星图"的呼应。
   同样锚定 --hero-h：早先用 min(74vh,580px) 时，视口一高星图就往下长，
   节点圆点直接落到 eyebrow 背后 —— 实测把对比度从 8.5:1 拉到 3.5:1。
   现在它的高度跟着首屏走，遮罩也在首屏底部之前收干净，永远够不到正文。 */
.iv .constellation {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: calc(var(--hero-h) * 1.05);
  opacity: 0.55;
  -webkit-mask-image: linear-gradient(to bottom, #000 0 26%, transparent 90%);
  mask-image: linear-gradient(to bottom, #000 0 26%, transparent 90%);
}

.iv .grain {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0.05;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
/* ════════════ 2. 骨架 ════════════ */
.iv .shell {
  position: relative;
  z-index: 2;
  max-width: 1080px;
  margin-inline: auto;
  padding: 0 20px calc(34px + env(safe-area-inset-bottom, 0px));
}
/* ════════════ 3. 首屏 ════════════ */
.iv .hero {
  display: flex;
  align-items: center;
  justify-content: center;
  height: var(--hero-h);
}
/* 标记已提为透明底（见 tools/extract-alpha.py），所以能直接压在紫色光块上，
   不会出现"黑方块糊在光束中间"这类合成事故 */
.iv .hero__mark {
  width: min(76vw, 336px);
  height: auto;
}
/* ════════════ 4. 文案 ════════════ */
.iv .lede {
  text-align: center;
}

.iv .eyebrow {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  margin: 0 0 14px;
  font-family: var(--font-display);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--brand-violet-soft);
}

.iv .eyebrow::before, .iv .eyebrow::after {
  content: "";
  width: 26px;
  height: 1px;
  background: var(--color-line-strong);
}
/* 中文不吃 0.3em 的字距，单独收一档，否则字符之间会散成"标点感" */
.iv .eyebrow__cn {
  font-family: var(--font-cn);
  letter-spacing: 0.18em;
}

.iv .headline {
  margin: 0;
  font-size: var(--fs-hero);
  font-weight: 800;
  line-height: 1.06;
  letter-spacing: -0.02em;
  color: var(--brand-grey);
}
/* 两色标题：第二行用亮紫，直接呼应品牌手册手机界面里
   "Explore A Larger Universe" 那处关键词高亮的手法 */
.iv .headline__hi {
  color: var(--brand-violet-soft);
}

.iv .lead {
  margin: 12px 0 0;
  font-size: var(--fs-lead);
  line-height: 1.7;
  color: var(--color-muted);
}
/* 品牌关键词带：词直接取自品牌手册的语义清单，不是自造文案。
   只在宽屏出现 —— 手机上它插在正文与凭证之间只会添乱。 */
.iv .kicker {
  display: none;
}
/* ════════════ 5. 邀请凭证 ════════════
   一张卡承载两条加入路径：上半张浅灰放二维码（二维码必须压浅底才扫得动），
   下半张近黑放邀请码。中间那道缝上镶一枚黄菱 —— 取自 logo 里的那颗菱形。 */
.iv .ticket {
  position: relative;
  width: min(100%, 384px);
  margin: clamp(22px, 5.5vw, 38px) auto 0;
  border-radius: var(--r-lg);
  overflow: hidden;
  box-shadow: var(--shadow-3);
}

.iv .ticket__top {
  background: var(--brand-grey);
  padding: 20px 22px 18px;
}

.iv .ticket__qr {
  width: min(54vw, 172px);
  margin-inline: auto;
}

.iv .ticket__id {
  margin: 14px 0 0;
  text-align: center;
}

.iv .ticket__id-name {
  display: block;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--brand-violet); /* 对 #F2F2F2 实测 4.74:1 */
}

.iv .ticket__bottom {
  position: relative;
  background: var(--color-surface);
  padding: 20px 22px 20px;
}

.iv .ticket__seal {
  position: absolute;
  top: -8px;
  left: 50%;
  width: 16px;
  height: 16px;
  margin-left: -8px;
  background: var(--color-accent);
  transform: rotate(45deg);
}

.iv .code-label {
  margin: 0;
  text-align: center;
  font-family: var(--font-display);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--brand-violet-soft);
}

.iv .code-label span {
  font-family: var(--font-cn);
  letter-spacing: 0.16em;
}

.iv .code {
  display: block;
  /* 定高：滚动落字动画期间不能让卡片跟着抖 */
  min-height: 1.25em;
  margin: 10px 0 0;
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--fs-code);
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--color-text);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "zero" 1; /* 点零，避免 O / 0 看混 */
  user-select: all;
  -webkit-user-select: all;
}

.iv .copy {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  min-height: 48px; /* 触摸目标 ≥44px */
  margin-top: 14px;
  padding: 0 20px;
  border-radius: var(--r-pill);
  background: var(--color-accent);
  color: var(--color-ink);
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 0.06em;
  box-shadow: 0 6px 22px rgba(255, 210, 0, 0.2);
  transition: transform var(--dur-fast) ease-out, box-shadow var(--dur-fast) ease-out,
    background-color var(--dur-fast) ease-out;
}

.iv .copy:hover {
  transform: translateY(-1px);
  box-shadow: 0 10px 30px rgba(255, 210, 0, 0.3);
}

.iv .copy:active {
  transform: translateY(1px) scale(0.99);
}
/* 失败态：不引入新颜色，用描边把它降级成次级动作 */
.iv .copy[data-state="error"] {
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--brand-violet-soft);
  color: var(--brand-violet-soft);
}

.iv .copy__icon {
  flex: none;
  width: 18px;
  height: 18px;
}
/* ════════════ 6. 提示条 ════════════ */
.iv .toast {
  position: fixed;
  left: 50%;
  bottom: calc(28px + env(safe-area-inset-bottom, 0px));
  z-index: 10;
  max-width: calc(100vw - 40px);
  padding: 10px 20px;
  border-radius: var(--r-pill);
  background: rgba(11, 10, 20, 0.94);
  border: 1px solid var(--color-line-strong);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  color: var(--color-text);
  font-size: 14px;
  letter-spacing: 0.04em;
  text-align: center;
  opacity: 0;
  transform: translate(-50%, 14px);
  pointer-events: none;
  transition: opacity var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out);
}

.iv .toast.is-on {
  opacity: 1;
  transform: translate(-50%, 0);
}
/* ════════════ 7. 页脚 ════════════ */
.iv .foot {
  margin-top: clamp(30px, 7vw, 56px);
  text-align: center;
}

.iv .foot__logo {
  width: 116px;
  margin: 0 auto 14px;
  opacity: 0.92;
}

.iv .foot__rule {
  width: 56px;
  height: 1px;
  margin: 0 auto 14px;
  background: var(--color-line-strong);
}

.iv .foot__en {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--brand-violet-soft);
}

.iv .foot__cn {
  margin: 8px 0 0;
  font-size: 13px;
  letter-spacing: 0.1em;
  color: var(--color-muted);
}
/* ════════════ 8. 入场编排 ════════════
   一次编排好的序列：氛围 → 标记 → 标签 → 标题 → 副标题 → 凭证 → 页脚。
   错峰 80~120ms，整段 1.4s 内收完，不拖沓。

   注意所有"初始隐藏"都挂在 html.js 下：脚本被拦、被禁用、或者加载失败时，
   .reveal 就是普通可见元素，页面是一份完整的静态版，而不是一片黑。
   这是分享页的底线 —— 它天然要跑在各种不确定的 WebView 里。 */
.iv--js .reveal {
  opacity: 0;
  transform: translateY(22px);
}

.iv--js .beam, .iv--js .beam--2, .iv--js .glow {
  opacity: 0;
}

.iv--ready .reveal {
  opacity: 1;
  transform: none;
  transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out);
  transition-delay: var(--d, 0ms);
}

.iv--ready .beam {
  opacity: 0.9;
  transition: opacity 820ms var(--ease-out);
}

.iv--ready .beam--2 {
  opacity: 0.7;
  transition: opacity 900ms var(--ease-out) 160ms;
}

.iv--ready .glow {
  opacity: 1;
  transition: opacity 1100ms var(--ease-out) 120ms;
}

.iv .hero__mark {
  --d: 120ms;
}

.iv .eyebrow {
  --d: 240ms;
}

.iv .headline {
  --d: 340ms;
}

.iv .lead {
  --d: 420ms;
}

.iv .kicker {
  --d: 460ms;
}

.iv .ticket {
  --d: 520ms;
}

.iv .foot {
  --d: 640ms;
}
/* 辉光呼吸：只给一个元素做持续动效，且幅度必须小到"若隐若现" */
@media (prefers-reduced-motion: no-preference){

.iv--ready .glow--violet {
    animation: halo-breathe 9s var(--ease-soft) infinite 1.4s;
  }

@keyframes halo-breathe{
    0%,
    100% {
      transform: translateX(-50%) scale(1);
    }
    50% {
      transform: translateX(-50%) scale(1.05);
    }
  }
}
/* 极窄机（iPhone SE 类 320~360px）收紧留白，别让内容顶到边框 */
@media (max-width: 360px){

.iv .shell {
    padding-inline: 16px;
  }

.iv .ticket__top, .iv .ticket__bottom {
    padding-inline: 18px;
  }
}

/* ══════════════════════════════════════════════════════════════════════
   H5 容器适配（源站点没有这一段：那边 body 就是画布）
   ══════════════════════════════════════════════════════════════════════ */

/* ── 隔离兜底：主样式表里**同名**的全局类会漏进来 ──
   `.iv .hero` 这类选择器特异性(0,2,0)虽高于主表的 `.hero`(0,1,0)，但那只对
   「两边都声明过」的属性成立 —— 主表声明过、这里没声明的属性会**直接漏进来**。
   实测漏的是 `.hero`：H5 工作台那张橙黑渐变卡（background / color / padding /
   border-radius / position / overflow）把邀请页的 <header class="hero">
   刷成了一块橙色方块。逐条重置如下。
   ⚠️ 改版或新增区块后请重跑本脚本：末尾会自动重做撞名比对，有未处理的就告警。 */
.iv .hero {
  background: none;
  color: inherit;
  padding: 0;
  margin: 0;
  border: 0;
  border-radius: 0;
  position: static;
  overflow: visible;
  cursor: auto;
  opacity: 1;
  font-size: inherit;
  font-weight: inherit;
}

/* 另两个撞名类：源 CSS 的特异性已经接管，这里再显式声明一次，防将来主表加属性时静默漏进来 */
.iv .code { margin: 10px 0 0; }
.iv .toast { margin: 0; }

/* 本页是全屏深色，根容器要吃掉 #view 的内边距并铺满可用高度 */
.iv {
  position: relative;
  display: block;
  overflow: hidden;
  min-height: 100%;
}

/* 邀请页路由下：#view 去内边距、铺黑底；顶部栏跟着变深色，整页连成一体
   （与 app.js:render() 里的 view--dark / appbar--dark 类名对应） */
.view.view--dark {
  padding: 0;
  background: #000;
}

/* 二维码：源码里是一张 <img src="assets/qr.svg">，本项目换成动态生成的 SVG
   （见 app.js 的 vInvite），宽度交给容器，SVG 自己铺满 */
.iv .ticket__qr svg {
  display: block;
  width: 100%;
  height: auto;
}

.appbar.appbar--dark {
  background: #000;
  border-bottom-color: rgba(227, 223, 248, 0.14);
}

.appbar.appbar--dark .title,
.appbar.appbar--dark .back,
.appbar.appbar--dark .action {
  color: #e3dff8;
}

/* ══════════════════════════════════════════════════════════════════════
   画布尺寸适配 —— 视口宽 ≠ 画布宽（2026-09-19 用户反馈后加）
   ──────────────────────────────────────────────────────────────────────
   源站是独立页面：浏览器视口就是画布，所以整份样式按 vw/vh 定尺寸、
   断点按视口宽判断。H5 不是 —— 画布是被钉死的 `.phone` 容器
   （width:100%; max-width:420px; height:100vh; max-height:812px）。
   于是桌面宽屏下（视口 1440、画布仍 420）出现两处失真：

     · 「按视口算的字号」全部顶到 clamp 上限：标题 58px、二维码 172px、
       标记 336px …… 塞进 420px 的框里 → 标题一字一行、凭证卡只剩 154px；
     · 「按视口判断的断点」错误命中 @media (min-width:900px) 的两栏版式
       → 170px + 154px 两列 + 56px 间隙。

   修法：把画布的真实尺寸抽成 --canvas-w / --canvas-h，所有 vw/vh 派生值
   改为按它们计算。因为 画布宽 = min(视口宽, 420)、画布高 = min(视口高, 812)，
   所以视口 ≤420 时结果与源站**逐像素一致**，视口更宽时则冻结在「420 宽
   那一刻」的取值 —— 不论窗口多大，渲染结果恒等于设计稿的手机那一版。
   （阈值 >420 的 min-width 断点已在转换阶段整块丢弃，见 is_unreachable_media。）

   ⚠️ 若源包改版新增了 vw/vh 定尺寸的规则，请在这里补一条同口径覆盖，
   脚本末尾的 check_viewport_units() 会把未覆盖的列出来告警。
   ══════════════════════════════════════════════════════════════════════ */
.iv {
  --canvas-w: min(100vw, 420px); /* 420 = .phone 的 max-width，改主表须同步 */
  --canvas-h: min(100vh, 812px); /* 812 = .phone 的 max-height */
}

/* 字阶：源 --fs-hero: clamp(34px, 10.4vw, 58px) 等三条 */
.iv {
  --fs-hero: clamp(34px, calc(var(--canvas-w) * 0.104), 58px);
  --fs-code: clamp(23px, calc(var(--canvas-w) * 0.074), 34px);
  --fs-lead: clamp(14px, calc(var(--canvas-w) * 0.039), 16px);
  --hero-h: clamp(240px, calc(var(--canvas-h) * 0.32), 320px);
}

/* 首屏标记：源 min(76vw, 336px) */
.iv .hero__mark {
  width: min(calc(var(--canvas-w) * 0.76), 336px);
}

/* 紫色辉光板：源 min(150vw, 980px) */
.iv .glow--violet {
  width: min(calc(var(--canvas-w) * 1.5), 980px);
}

/* 二维码：源 min(54vw, 172px)。⚠️ 二维码要留静区，别为了塞版面把它缩太小 */
.iv .ticket__qr {
  width: min(calc(var(--canvas-w) * 0.54), 172px);
}

/* 凭证卡上边距：源 clamp(22px, 5.5vw, 38px) */
.iv .ticket {
  margin: clamp(22px, calc(var(--canvas-w) * 0.055), 38px) auto 0;
}

/* 页脚上边距：源 clamp(30px, 7vw, 56px) */
.iv .foot {
  margin-top: clamp(30px, calc(var(--canvas-w) * 0.07), 56px);
}

/* 自带提示条：源 calc(100vw - 40px) */
.iv .toast {
  max-width: calc(var(--canvas-w) - 40px);
}

/* ── 窄屏补丁（≤360px）：源包在这一档只收了 shell / 凭证卡的内边距 ──
   实测 320px（iPhone SE 一代、部分老安卓）下 eyebrow 一行需要 ~290px，
   可用宽只有 288px —— 差 2px 就折成两行（`SHARE / INVITATION`、
   `分享邀 / 请`），观感正是用户说的「元素挤在一起」。
   收紧间隙与两侧装饰线补回余量（省 28px，留 ~26px 富余抗字体差异）。
   ⚠️ 这是本项目对源码的补丁，不在交付包里；将来若拿到新版源码，
   先看它是否已自行修掉这一条，再决定是否删除本段。 */
@media (max-width: 360px) {

.iv .eyebrow {
    gap: 8px;
  }

.iv .eyebrow::before, .iv .eyebrow::after {
    width: 18px;
  }
}
