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-mix的linear-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 →</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>