跳转至

Syzygy 克莱因蓝星穹

一个「本地优先剪贴板工具」的官网首屏:纯 CSS 深空星象 hero(一 div 巨行星 + JS 星点 + 光斑), 经「大气层渐变」落进纸白正文的蓝图特性卡。整站颜色全是 data-brand-theme 令牌,右下角按钮 一键从克莱因蓝切到琥珀烈日。吸纳自 en.syzygysync.com,纯手写零依赖复刻。

要点

  • 把所有颜色收进 :root[data-brand-theme="…"] 下的设计令牌,组件只引用变量名,换肤只改 html 一个属性(原站定义了 17 套主题)
  • 深浅色不手写:用 color-mix(in oklab, var(--brand) 72%, white / black) 从品牌色派生受光面和暗面,换主题自动跟随
  • 巨行星只用一个 div:radial-gradient 定光源方向,inset 阴影压出球体明暗,外发光当大气辉光
  • 行星半径故意大过视口、只露一段弧(right:-600px),才有「贴脸飞过」的天体尺度感
  • 一明一暗两颗天体对角错开(一右下一左上),点题 syzygy「天体连珠」
  • hero 落地用「大气层渐变」:嵌套 color-mixlinear-gradient,让夜空经品牌色过渡层落进纸白正文,两个世界无缝衔接
  • 星点用 JS 撒 110 个 <i>(位置 / 大小 / 闪烁相位随机,两成是 2px 亮星),CSS 只管圆点形状和 twinkle 动画
  • 暗角用中心透、四角黑的径向罩压出景深,左侧再垫一团极淡品牌色薄雾防死黑
  • 夜空上的控件一律玻璃拟态:半透白底 + 半透白边 + backdrop-filter: blur() saturate()
  • 眉标做「乱码落定」解码动画:从左到右逐字符落定、未落定位随机跳字符,等宽字体 + min-width 定宽防抖,每 8s 重播一次
  • 巨字标题用特细字重(font-weight:250)+ clamp() 响应式字号,巨而不压人
  • 特性卡右侧「蓝图舞台」:1px 点阵网格平铺 + 品牌色径向晕聚光,上面浮一枚等距立方体 SVG(品牌色填充 + 白描边 + 品牌色 drop-shadow 辉光 + 缓浮动画)
  • 换主题时渐变硬切、纯色给 .6s 过渡 —— 渐变不可过渡只能接受硬切,原站同样取舍

源码(折叠)

syzygy-astral-demo.html(自包含单页,含内联 style + script)
<!DOCTYPE html>
<html lang="zh" data-brand-theme="astrolix">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Syzygy 克莱因蓝星穹 demo</title>
<!--
  syzygy-astral-demo.html —— 「前端风格收集」的 Syzygy 克莱因蓝星穹演示真身。

  吸纳自 https://en.syzygysync.com/(Syzygy:本地优先的跨设备剪贴板工具官网,
  2026-07-09 抓取分析)。原站是 Next.js + Tailwind,视觉核心全部在两处:
  一张深空星象 hero(行星球体 + 星点 + 光斑 + 大气层渐变落地),和一份
  brand-themes.css —— :root[data-brand-theme="…"] 定义 17 套、每套上百个
  oklch / color-mix 设计令牌,整站换肤只改一个 html 属性。

  本 demo 按 wiki 自包含规矩(skills/writing/mkdocs-wiki/index.md 第 9 节)
  纯手写复刻:零依赖、零 CDN、零构建。收录其中 2 套主题(astrolix 克莱因蓝 /
  amber-blaze 琥珀烈日),令牌命名与关键渐变配方照抄原站,可对照溯源。
  右下角按钮可切主题 —— 注意渐变(天空/行星/地面)不可过渡会硬切,
  纯色(文字/边框/芯片)有 .6s 过渡,原站同样如此取舍。
-->
<style>
  /* ══ 1. 品牌主题令牌 ══════════════════════════════════════════
     原站的核心工程:所有颜色都是 :root[data-brand-theme] 下的变量,
     组件只引用变量名。换主题 = 换 html 的 data-brand-theme 属性值。
     --hero-ground-fade 是点睛配方(照抄原站):用嵌套 color-mix 让
     夜空 hero 经「品牌色大气层」渐变落进纸白正文,两个世界无缝衔接。 */
  :root[data-brand-theme="astrolix"] {
    --background: #faf9f5;            /* 纸白正文底(原站 archive-surface) */
    --background-raised: #ffffff;
    --panel: #f2f1ec;
    --foreground: #1e2230;            /* 原站 oklch(0.19 0.01 255):藏青黑 */
    --foreground-soft: #555b6e;
    --foreground-muted: #8a8f9e;
    --brand: #0000ff;                 /* 克莱因蓝,整套风格的灵魂 */
    --brand-strong: #0000cc;
    --brand-deep: #000099;            /* 行星暗部 / 星空锚点 */
    --accent: #2c2cff;
    --primary-fg: #ffffff;            /* 蓝底上的按钮字色 */
    --border: rgba(30, 34, 48, .08);
    --border-strong: rgba(30, 34, 48, .14);
    --sky: rgb(3 6 12);               /* 原站 starfield-canvas-base */
    --planet-hi: #6b74ff;             /* 行星受光面 → 本体 → 暗部 */
    --planet-lo: #0000ee;
    --moon-hi: #303c52;               /* 左上暗行星(岩石灰蓝) */
    --moon-lo: #10141d;
    --flare-warm: rgba(255, 236, 170, .85);   /* 第二枚光斑偏暖,画面不单调 */
    --hero-overlay: radial-gradient(circle at center,
        rgba(18,31,60,.12) 0%, rgba(2,4,9,.18) 44%, rgba(0,0,0,.78) 100%);
    --hero-ground-fade: linear-gradient(to top,
        var(--background) 0%,
        color-mix(in oklab, var(--background) 50%, color-mix(in oklab, var(--brand) 20%, transparent)) 22%,
        color-mix(in oklab, var(--brand) 40%, transparent) 44%,
        color-mix(in oklab, var(--brand) 15%, transparent) 72%,
        transparent 100%);
    --hero-eyebrow-glow: 0 0 20px rgba(0, 0, 255, .7);
    --glass: rgba(255, 255, 255, .06); /* 夜空上的玻璃按钮三件套 */
    --glass-border: rgba(255, 255, 255, .12);
    --glass-hover: rgba(255, 255, 255, .12);
    --stage-glow: rgba(0, 0, 255, .1); /* 插画舞台的点阵格与蓝晕 */
    --stage-dot: rgba(30, 34, 48, .16);
  }
  :root[data-brand-theme="amber-blaze"] {
    --background: #fffcf5;
    --background-raised: #ffffff;
    --panel: #fff3dd;
    --foreground: #2b2114;
    --foreground-soft: #6b5a40;
    --foreground-muted: #9a8a6e;
    --brand: #ffbe0b;
    --brand-strong: #fb5607;
    --brand-deep: #7a3c00;
    --accent: #fb5607;
    --primary-fg: #1a1200;            /* 琥珀底亮,按钮字换深色 */
    --border: rgba(43, 33, 20, .08);
    --border-strong: rgba(43, 33, 20, .14);
    --sky: rgb(12 6 2);               /* 夜空跟着主题变暖 */
    --planet-hi: #ffd968;
    --planet-lo: #f5a300;
    --moon-hi: #4a3826;
    --moon-lo: #1c130a;
    --flare-warm: rgba(255, 190, 120, .9);
    --hero-overlay: radial-gradient(circle at center,
        rgba(60,31,8,.12) 0%, rgba(9,4,1,.18) 44%, rgba(0,0,0,.78) 100%);
    --hero-ground-fade: linear-gradient(to top,
        var(--background) 0%,
        color-mix(in oklab, var(--background) 50%, color-mix(in oklab, var(--brand) 20%, transparent)) 22%,
        color-mix(in oklab, var(--brand) 40%, transparent) 44%,
        color-mix(in oklab, var(--brand) 15%, transparent) 72%,
        transparent 100%);
    --hero-eyebrow-glow: 0 0 20px rgba(255, 190, 11, .7);
    --glass: rgba(255, 255, 255, .06);
    --glass-border: rgba(255, 255, 255, .12);
    --glass-hover: rgba(255, 255, 255, .12);
    --stage-glow: rgba(251, 86, 7, .1);
    --stage-dot: rgba(43, 33, 20, .16);
  }

  /* ══ 2. 基础 ══════════════════════════════════════════════════
     两条字体链就是全部:正文系统无衬线;眉标/标签/芯片一律等宽 +
     全大写 + 宽字距(原站 IBM Plex Mono,这里走本地等宽链)。 */
  :root {
    --sans: system-ui, -apple-system, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
    --mono: ui-monospace, 'SF Mono', Consolas, 'Liberation Mono', monospace;
  }
  * { box-sizing: border-box; margin: 0; }
  body {
    font-family: var(--sans);
    background: var(--background);
    color: var(--foreground);
    -webkit-font-smoothing: antialiased;
    transition: background-color .6s, color .6s;
  }
  a { color: inherit; text-decoration: none; }
  button { font: inherit; cursor: pointer; }
  .mono { font-family: var(--mono); text-transform: uppercase; letter-spacing: .18em; }

  /* ══ 3. 星穹 hero ═════════════════════════════════════════════
     纯 CSS 叠层,从后到前:夜空底色 → 星点 → 两颗行星 → 光斑 →
     暗角 vignette → 大气层落地渐变 → 文案。没有一张图片。 */
  .hero {
    position: relative;
    min-height: 700px; height: 94vh;
    overflow: hidden;
    background: var(--sky);
  }
  .hero > * { position: absolute; }

  /* 星点由 JS 撒出(位置/大小/闪烁相位随机),CSS 只管形状和闪烁 */
  .stars { inset: 0; z-index: 1; }
  .stars i {
    position: absolute;
    background: #fff; border-radius: 50%;
    animation: subtle-twinkle 4s ease-in-out infinite;  /* 命名取自原站 keyframes */
  }
  @keyframes subtle-twinkle { 0%, 100% { opacity: .35; } 50% { opacity: .9; } }

  /* 克莱因蓝巨行星:一个 div。radial-gradient 定光源方向(左上受光),
     inset 阴影压出球体暗部,外发光是大气辉光。半径故意大过视口 ——
     只露出一段弧,才有"贴脸飞过"的天体尺度感 */
  .planet {
    width: 1400px; height: 1400px; border-radius: 50%;
    right: -600px; top: 56px;                     /* 左缘 ≈ 视口宽 - 800px:只露一段弧 */
    background: radial-gradient(circle at 26% 20%,
        var(--planet-hi) 0%, var(--brand) 42%, var(--planet-lo) 68%, var(--brand-deep) 100%);
    box-shadow:
        0 0 140px 24px color-mix(in oklab, var(--brand) 42%, transparent),  /* 大气辉光 */
        inset 60px 80px 180px rgba(255, 255, 255, .22),                     /* 受光面提亮 */
        inset -120px -160px 300px rgba(0, 0, 30, .5);                       /* 背光面压暗 */
    z-index: 2;
  }
  /* 左上角的暗伴星:同一招,调暗调灰,只露一角。两颗天体一明一暗、
     一右一左错开 —— syzygy(天体连珠)这个词就是整套视觉的题眼 */
  .moon {
    width: 900px; height: 900px; border-radius: 50%;
    left: -300px; top: -560px;
    background: radial-gradient(circle at 68% 82%, var(--moon-hi) 0%, var(--moon-lo) 70%);
    box-shadow: inset -20px -30px 90px rgba(0,0,0,.6),
        inset 0 -2px 0 rgba(255,255,255,.14);     /* 下缘一线冷光,黑底上勾出轮廓 */
    z-index: 2;
  }
  /* 星云薄雾:文案后方一团极淡的品牌色光,左半边不至于死黑 */
  .haze {
    inset: 0; z-index: 1; pointer-events: none;
    background: radial-gradient(ellipse 60% 50% at 28% 55%,
        color-mix(in oklab, var(--brand) 16%, transparent), transparent 70%);
  }
  /* 光斑:模糊的白色/暖色圆晕浮在行星前,像镜头拾到的恒星耀斑 */
  .flare { border-radius: 50%; filter: blur(4px); z-index: 3; pointer-events: none; }
  .flare.f1 {
    width: 120px; height: 90px; right: 32%; top: 44%;
    background: radial-gradient(closest-side, rgba(255,255,255,.95), transparent 70%);
  }
  .flare.f2 {
    width: 100px; height: 80px; right: 18%; top: 60%;
    background: radial-gradient(closest-side, var(--flare-warm), transparent 70%);
  }
  /* 暗角:中心透、四角黑的径向罩,把星空压出景深,文字区自然变干净 */
  .vignette { inset: 0; background: var(--hero-overlay); z-index: 4; pointer-events: none; }
  /* 大气层落地:hero 底部 1/3 高的渐变层,配方见令牌注释 */
  .ground { left: 0; right: 0; bottom: 0; height: 36%; background: var(--hero-ground-fade); z-index: 5; pointer-events: none; }

  /* ── 顶栏:夜空上全走"玻璃"(半透白底 + 半透白边 + backdrop blur)── */
  .topbar {
    left: 0; right: 0; top: 0; z-index: 7;
    display: flex; align-items: center; gap: 28px;
    padding: 18px 32px;
    color: rgba(255,255,255,.94);
  }
  .brand { display: flex; align-items: baseline; gap: 10px; font-weight: 700; }
  .brand .tag { font-size: .68rem; color: rgba(255,255,255,.5); font-weight: 400; }
  .topbar nav { display: flex; gap: 22px; margin: 0 auto; font-size: .88rem; }
  .topbar nav a { color: rgba(255,255,255,.72); transition: color .2s; }
  .topbar nav a:hover { color: #fff; }
  .ghost-btn {
    padding: 6px 12px; border-radius: 4px;
    background: var(--glass); border: 1px solid var(--glass-border);
    color: rgba(255,255,255,.82); font-size: .78rem;
    backdrop-filter: blur(18px) saturate(145%);   /* 原站 topbar-backdrop-filter */
    transition: background .2s;
  }
  .ghost-btn:hover { background: var(--glass-hover); }
  .solid-btn {
    padding: 7px 16px; border-radius: 4px; border: none;
    background: var(--brand); color: var(--primary-fg);
    font-size: .82rem; font-weight: 600;
    transition: opacity .2s, background-color .6s, color .6s;
  }
  .solid-btn:hover { opacity: .92; }

  /* ── 文案区:乱码眉标 + 特细巨字 ── */
  .hero-inner {
    left: 0; right: 0; top: 0; bottom: 0; z-index: 6;
    display: flex; flex-direction: column; justify-content: center;
    padding: 0 8%; max-width: 1100px;
  }
  .eyebrow {
    align-self: flex-start;
    padding: 7px 16px; border-radius: 999px;
    background: rgba(255,255,255,.06);
    border: 1px solid rgba(255,255,255,.1);
    color: rgba(255,255,255,.78);
    font-size: .7rem;
    box-shadow: var(--hero-eyebrow-glow);         /* 品牌色光晕描出胶囊轮廓 */
    margin-bottom: 28px;
    min-width: 21ch;                              /* 等宽字符定宽,乱码滚动时不抖 */
  }
  .hero h1 {
    font-size: clamp(3rem, 7.5vw, 5rem);
    font-weight: 250;                             /* 特细字重是巨字不压人的关键 */
    line-height: 1.05; letter-spacing: -.01em;
    color: rgba(238, 240, 255, .96);
  }
  .hero .sub { margin-top: 26px; font-size: 1.1rem; max-width: 30rem; color: rgba(255,255,255,.76); }
  .cta-row { display: flex; gap: 14px; margin-top: 34px; }
  .cta-row .primary {
    padding: 13px 28px; border-radius: 999px; border: none;
    background: var(--brand); color: var(--primary-fg); font-weight: 600;
    box-shadow: 0 0 34px color-mix(in oklab, var(--brand) 55%, transparent);
    transition: opacity .2s, background-color .6s, color .6s;
  }
  .cta-row .primary:hover { opacity: .9; }
  .cta-row .secondary {
    padding: 13px 24px; border-radius: 999px;
    background: var(--glass); border: 1px solid var(--glass-border);
    color: rgba(255,255,255,.82); backdrop-filter: blur(12px);
    transition: background .2s;
  }
  .cta-row .secondary:hover { background: var(--glass-hover); }
  .hero .meta { margin-top: 30px; font-size: .66rem; color: rgba(255,255,255,.44); }
  .hero .meta b { color: rgba(255,255,255,.7); font-weight: 500; }

  /* ══ 4. 纸白正文:蓝点眉标 + 细字大标题 + 蓝图特性卡 ══════════ */
  .features { padding: 90px 8% 100px; max-width: 1160px; margin: 0 auto; }
  .features > .eyebrow2 { display: flex; justify-content: center; }
  .eyebrow2 span {
    display: inline-flex; align-items: center; gap: 8px;
    padding: 6px 14px; border-radius: 999px;
    border: 1px solid var(--border-strong);
    color: var(--accent); font-size: .68rem;
  }
  .eyebrow2 i { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }
  .features h2 {
    margin-top: 26px; text-align: center;
    font-size: clamp(2.2rem, 4.6vw, 3.4rem); font-weight: 280; line-height: 1.15;
  }
  .features .lead { margin-top: 18px; text-align: center; color: var(--foreground-soft); }

  /* 特性卡:左文右画的分栏大卡。右侧"舞台"= 点阵网格 + 品牌色晕 +
     等距蓝图插画,是原站辨识度最高的插画语言 */
  .feature-card {
    margin-top: 56px; display: grid; grid-template-columns: 5fr 7fr;
    border: 1px solid var(--border); border-radius: 14px; overflow: hidden;
    background: var(--background-raised);
    box-shadow: 0 4px 20px rgba(27,28,26,.04), 0 10px 40px rgba(27,28,26,.06);
  }
  .feature-text { padding: 44px 40px; background: linear-gradient(160deg, var(--panel), var(--background-raised)); }
  .feature-text .label { font-size: .66rem; color: var(--foreground-muted); }
  .feature-text h3 { margin-top: 14px; font-size: 1.7rem; font-weight: 650; }
  .feature-text p { margin-top: 14px; color: var(--foreground-soft); line-height: 1.7; font-size: .95rem; }
  .chips { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 26px; }
  .chips span {
    display: inline-flex; align-items: center; gap: 8px;
    padding: 8px 14px; border-radius: 6px;
    background: var(--panel); border: 1px solid var(--border-strong);
    font-size: .68rem; color: var(--foreground-soft);
    transition: background-color .6s, border-color .6s;
  }
  .chips i { width: 5px; height: 5px; border-radius: 50%; background: var(--accent); }
  .feature-stage {
    position: relative; min-height: 340px;
    display: flex; align-items: center; justify-content: center;
    /* 点阵网格:1px 圆点平铺;再垫一层品牌色径向晕当"聚光" */
    background:
        radial-gradient(circle at 55% 45%, var(--stage-glow) 0%, transparent 60%),
        radial-gradient(var(--stage-dot) 1px, transparent 1.5px);
    background-size: auto, 22px 22px;
  }
  /* 等距蓝图插画:纯色品牌填充 + 白描边 + 品牌色 drop-shadow 辉光。
     面的明暗不换色,靠叠白/叠黑半透明层做(换主题自动跟随) */
  .isocon { animation: float-soft 6s ease-in-out infinite; }
  .isocon .face { fill: var(--brand); stroke: #fff; stroke-width: 2.5; stroke-linejoin: round; }
  .isocon .lit  { fill: color-mix(in oklab, var(--brand) 72%, white); }
  .isocon .dim  { fill: color-mix(in oklab, var(--brand) 72%, black); }
  .isocon svg { filter: drop-shadow(0 14px 30px color-mix(in oklab, var(--brand) 45%, transparent)); }
  @keyframes float-soft { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-8px); } }

  /* ══ 5. 主题切换按钮(demo 自加,非原站控件)══════════════════ */
  .theme-switch {
    position: fixed; right: 18px; bottom: 18px; z-index: 9;
    padding: 9px 16px; border-radius: 999px;
    background: var(--background-raised); border: 1px solid var(--border-strong);
    color: var(--foreground); font-size: .68rem;
    box-shadow: 0 4px 20px rgba(0,0,0,.15);
    transition: background-color .6s, color .6s;
  }
  .theme-switch b { color: var(--accent); font-weight: 700; }

  @media (max-width: 820px) {
    .topbar nav { display: none; }
    .feature-card { grid-template-columns: 1fr; }
    .feature-stage { min-height: 280px; }
  }
</style>
</head>
<body>

<header class="hero">
  <div class="stars" id="stars"></div>
  <div class="haze"></div>
  <div class="moon"></div>
  <div class="planet"></div>
  <span class="flare f1"></span>
  <span class="flare f2"></span>
  <div class="vignette"></div>
  <div class="ground"></div>

  <div class="topbar">
    <div class="brand">Syzygy <span class="tag mono">local-first clipboard</span></div>
    <nav>
      <a href="#">Product</a><a href="#">System</a><a href="#">Download</a><a href="#">Pricing</a>
    </nav>
    <button class="ghost-btn mono">zh</button>
    <button class="solid-btn">Download</button>
  </div>

  <div class="hero-inner">
    <!-- 眉标文字由 JS 做"乱码落定"解码动画,等宽 + 宽字距是它成立的前提 -->
    <span class="eyebrow mono" id="scramble" data-text="Local-first clipboard sync"></span>
    <h1>SYZYGY<br>Cross-Device<br>Clipboard<br>Workspace</h1>
    <p class="sub">Your clipboard, searchable, previewable, and ready on trusted devices.</p>
    <div class="cta-row">
      <button class="primary">Create account&ensp;</button>
      <button class="secondary">Download</button>
    </div>
    <p class="meta mono"><b>X25519</b> · <b>ChaCha20</b> · end-to-end · zero cloud copy</p>
  </div>
</header>

<section class="features">
  <div class="eyebrow2"><span class="mono"><i></i>What it does</span></div>
  <h2>One clipboard.<br>Three layers of value.</h2>
  <p class="lead">History, previews, OCR, translation, and sync rules stay in one clear product.</p>

  <div class="feature-card">
    <div class="feature-text">
      <div class="label mono">Security boundary</div>
      <h3>Encrypted where it counts</h3>
      <p>Authenticated pairing, encrypted transport, and local controls protect
         sensitive material. Devices exchange keys directly — the relay never
         sees plaintext.</p>
      <div class="chips mono">
        <span><i></i>X25519</span>
        <span><i></i>ChaCha20-Poly1305</span>
      </div>
    </div>
    <div class="feature-stage">
      <!-- 等距立方体两枚:手算的 2:1 isometric 坐标(顶面菱形 + 左右两面),
           大小错落 + float-soft 浮动,配点阵舞台就是原站的"蓝图立体图标" -->
      <div class="isocon">
        <svg width="240" height="210" viewBox="0 0 240 210" aria-hidden="true">
          <g transform="translate(96 96)">
            <polygon class="face dim" points="-56,0 0,32 0,96 -56,64"/>
            <polygon class="face"     points="0,32 56,0 56,64 0,96"/>
            <polygon class="face lit" points="0,-32 56,0 0,32 -56,0"/>
          </g>
          <g transform="translate(186 48)">
            <polygon class="face dim" points="-26,0 0,15 0,45 -26,30"/>
            <polygon class="face"     points="0,15 26,0 26,30 0,45"/>
            <polygon class="face lit" points="0,-15 26,0 0,15 -26,0"/>
          </g>
        </svg>
      </div>
    </div>
  </div>
</section>

<button class="theme-switch mono" id="themeBtn">theme: <b>astrolix</b></button>

<script>
  /* ── 撒星星:位置/大小/闪烁相位随机,两成是 2px 的"亮星" ── */
  (function () {
    const box = document.getElementById('stars');
    let html = '';
    for (let i = 0; i < 110; i++) {
      const big = Math.random() < .2;
      html += `<i style="left:${(Math.random() * 100).toFixed(2)}%;` +
              `top:${(Math.random() * 100).toFixed(2)}%;` +
              `width:${big ? 2 : 1}px;height:${big ? 2 : 1}px;` +
              `animation-delay:${(Math.random() * 4).toFixed(2)}s;` +
              `animation-duration:${(3 + Math.random() * 3).toFixed(2)}s"></i>`;
    }
    box.innerHTML = html;
  })();

  /* ── 乱码落定:从左到右逐字符"解码",未落定位随机跳字符。
        原站眉标就是这个效果(截图时常能抓到中间态乱码)。 ── */
  const el = document.getElementById('scramble');
  const TARGET = el.dataset.text.toUpperCase();
  const POOL = '!<>-_/[]{}=+*^?#0123456789ABCDEF';
  function scramble() {
    let frame = 0;
    clearInterval(el._t);
    el._t = setInterval(() => {
      const fixed = Math.floor(frame / 2);           // 每 2 帧落定 1 个字符
      el.textContent = TARGET.split('').map((ch, i) =>
        ch === ' ' || i < fixed ? ch : POOL[Math.random() * POOL.length | 0]
      ).join('');
      if (fixed >= TARGET.length) clearInterval(el._t);
      frame++;
    }, 40);
  }
  scramble();
  setInterval(scramble, 8000);                       // 周期性重播,页面一直"活着"

  /* ── 主题切换:只改 html 的 data-brand-theme,令牌接管一切 ── */
  const THEMES = ['astrolix', 'amber-blaze'];
  const btn = document.getElementById('themeBtn');
  btn.onclick = () => {
    const root = document.documentElement;
    const next = THEMES[(THEMES.indexOf(root.dataset.brandTheme) + 1) % THEMES.length];
    root.dataset.brandTheme = next;
    btn.innerHTML = `theme: <b>${next}</b>`;
    scramble();                                      // 顺手重播解码,强化"换台"感
  };
</script>
</body>
</html>