Ethscriptions commited on
Commit
94cbaf5
·
verified ·
1 Parent(s): b058fd5

libra-scales: 第5-7步 MP4导出(24/30/60fps 横竖屏)+四平台文案+8张封面+首页入口+移动端适配

Browse files
.gitattributes CHANGED
@@ -20,3 +20,12 @@ libra-scales/audio/seg-02.mp3 filter=lfs diff=lfs merge=lfs -text
20
  libra-scales/audio/seg-01.mp3 filter=lfs diff=lfs merge=lfs -text
21
  libra-scales/audio/seg-00.mp3 filter=lfs diff=lfs merge=lfs -text
22
  libra-scales/audio/music.mp3 filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
20
  libra-scales/audio/seg-01.mp3 filter=lfs diff=lfs merge=lfs -text
21
  libra-scales/audio/seg-00.mp3 filter=lfs diff=lfs merge=lfs -text
22
  libra-scales/audio/music.mp3 filter=lfs diff=lfs merge=lfs -text
23
+ libra-scales/covers/cover-xhs-3x4.jpg filter=lfs diff=lfs merge=lfs -text
24
+ libra-scales/covers/cover-xhs-4x3.jpg filter=lfs diff=lfs merge=lfs -text
25
+ libra-scales/covers/cover-landscape.jpg filter=lfs diff=lfs merge=lfs -text
26
+ libra-scales/covers/cover-bilibili-16x9-space.jpg filter=lfs diff=lfs merge=lfs -text
27
+ libra-scales/covers/cover-wechat-3x4.jpg filter=lfs diff=lfs merge=lfs -text
28
+ libra-scales/covers/cover-wechat-4x3.jpg filter=lfs diff=lfs merge=lfs -text
29
+ libra-scales/covers/cover-bilibili-4x3.jpg filter=lfs diff=lfs merge=lfs -text
30
+ libra-scales/covers/cover-douyin-4x3.jpg filter=lfs diff=lfs merge=lfs -text
31
+ libra-scales/covers/cover-douyin-3x4.jpg filter=lfs diff=lfs merge=lfs -text
index.html CHANGED
@@ -12,6 +12,7 @@ a.card div{padding:14px 16px}a.card b{font-size:19px}a.card span{display:block;o
12
  </style></head><body>
13
  <h1>知识动画工作室</h1><p>网页即播 · 浏览器本地导出 MP4 · 横屏 B站 / 竖屏抖音</p>
14
  <div class="grid">
 
15
  <a class="card" href="solana-clock/index.html"><img src="solana-clock/covers/cover-landscape.png" alt="SOLANA 给区块链装上时钟的人" onerror="this.style.visibility='hidden'"><div><b>⏱️ SOLANA:给区块链装上时钟的人</b><span>2017 → 2026 · 全新制作 · 时间之链霓虹风 · 约 7 分钟</span></div></a>
16
  <a class="card" href="ethereum/index.html"><img src="ethereum/covers/cover-landscape.png" alt="以太坊简史" onerror="this.style.visibility='hidden'"><div><b>⟠ 以太坊简史:从一份白皮书到世界计算机</b><span>2013 → 2026 · 深空光之晶体风格 · 约 7 分钟</span></div></a>
17
  <a class="card" href="guozu/index.html"><img src="guozu/covers/cover-landscape.png" alt="国足投注模拟器" onerror="this.style.visibility='hidden'"><div><b>⚽ 国足投注模拟器:每场押 ¥100,23 年后还剩多少?</b><span>2002 → 2026.10 · 149 场正式比赛 + 8 场友谊赛彩蛋 · 8-bit 像素街机风 · 约 5 分 50 秒 · 数据模拟,远离赌博</span></div></a>
 
12
  </style></head><body>
13
  <h1>知识动画工作室</h1><p>网页即播 · 浏览器本地导出 MP4 · 横屏 B站 / 竖屏抖音</p>
14
  <div class="grid">
15
+ <a class="card" href="libra-scales/index.html"><img src="libra-scales/covers/cover-landscape.jpg" alt="天秤座 · 星空中的一杆秤" onerror="this.style.visibility='hidden'"><div><b>♎ 天秤座:星空中的一杆秤</b><span>星座动画解说 · 全新制作 · 金色星图风 · 约 3 分 37 秒 · 横竖屏 · 24/30/60fps 导出</span></div></a>
16
  <a class="card" href="solana-clock/index.html"><img src="solana-clock/covers/cover-landscape.png" alt="SOLANA 给区块链装上时钟的人" onerror="this.style.visibility='hidden'"><div><b>⏱️ SOLANA:给区块链装上时钟的人</b><span>2017 → 2026 · 全新制作 · 时间之链霓虹风 · 约 7 分钟</span></div></a>
17
  <a class="card" href="ethereum/index.html"><img src="ethereum/covers/cover-landscape.png" alt="以太坊简史" onerror="this.style.visibility='hidden'"><div><b>⟠ 以太坊简史:从一份白皮书到世界计算机</b><span>2013 → 2026 · 深空光之晶体风格 · 约 7 分钟</span></div></a>
18
  <a class="card" href="guozu/index.html"><img src="guozu/covers/cover-landscape.png" alt="国足投注模拟器" onerror="this.style.visibility='hidden'"><div><b>⚽ 国足投注模拟器:每场押 ¥100,23 年后还剩多少?</b><span>2002 → 2026.10 · 149 场正式比赛 + 8 场友谊赛彩蛋 · 8-bit 像素街机风 · 约 5 分 50 秒 · 数据模拟,远离赌博</span></div></a>
libra-scales/README.md CHANGED
@@ -17,9 +17,9 @@
17
  | 2 | 旁白文案定稿(10 幕)+ TTS 配音(总长 217 秒)+ 时间轴 timeline.js | ✅ 完成 |
18
  | 3 | 动画引擎 engine.js + 10 个场景 scenes.js(横屏 16:9 / 竖屏 9:16 自适应,逐场景截图自检) | ✅ 完成 |
19
  | 4 | 程序化音乐 + 40 余处音效(WebAudio)+ 旁白闪避;预渲染 music.mp3 加速加载,缺失时现场合成 | ✅ 完成 |
20
- | 5 | 浏览器本地导出 MP4(WebCodecs + mp4-muxer,24/30/60fps,横/竖) | ⏳ |
21
- | 6 | 四平台标题/描述/标签 + 8 张封面生成与下载 | ⏳ |
22
- | 7 | 首页入口、自检、最终推送 | ⏳ |
23
 
24
  ---
25
 
@@ -102,3 +102,19 @@ libra-scales/
102
  - 星图使用真实赤经/赤纬:天秤座 α/β/γ/σ/υ/τ/θ,室女座(含角宿一),天蝎座(含心宿二)。
103
  - **audio.js**:D 大调 Pad(Dmaj9 – Bm11 – Gmaj7♯11 – A6sus)+ 竖琴式琶音(带延迟)+ 低音 + 高音星光;音效:嗖声转场、低频轰鸣、钟铃、星光琶音、钳子咔嚓、印章闷响、计数滴答、上升噪声;旁白出现时音乐自动压低。
104
  - 合成整轨约需十几秒,所以用 `tools/render_music.js` 把**同一套代码**离线渲染成 `audio/music.mp3`,页面优先加载它,加载失败时自动回退为浏览器现场合成。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  | 2 | 旁白文案定稿(10 幕)+ TTS 配音(总长 217 秒)+ 时间轴 timeline.js | ✅ 完成 |
18
  | 3 | 动画引擎 engine.js + 10 个场景 scenes.js(横屏 16:9 / 竖屏 9:16 自适应,逐场景截图自检) | ✅ 完成 |
19
  | 4 | 程序化音乐 + 40 余处音效(WebAudio)+ 旁白闪避;预渲染 music.mp3 加速加载,缺失时现场合成 | ✅ 完成 |
20
+ | 5 | 浏览器本地导出 MP4(WebCodecs H.264 + AAC/Opus + mp4-muxer,24/30/60fps,横/竖,1080p/720p,进度/取消;无 WebCodecs 时降级实时录制) | ✅ 完成(无头 Chrome 实测 3 种帧率 × 横竖屏导出成功) |
21
+ | 6 | 四平台标题/描述/话题(实时字数校验 + 一键复制)+ 8 张封面实时生成/下载(另存 covers/*.jpg) | ✅ 完成 |
22
+ | 7 | 首页卡片入口、全片逐帧扫描无报错、手机竖屏适配、最终推送 | ✅ 完成 |
23
 
24
  ---
25
 
 
102
  - 星图使用真实赤经/赤纬:天秤座 α/β/γ/σ/υ/τ/θ,室女座(含角宿一),天蝎座(含心宿二)。
103
  - **audio.js**:D 大调 Pad(Dmaj9 – Bm11 – Gmaj7♯11 – A6sus)+ 竖琴式琶音(带延迟)+ 低音 + 高音星光;音效:嗖声转场、低频轰鸣、钟铃、星光琶音、钳子咔嚓、印章闷响、计数滴答、上升噪声;旁白出现时音乐自动压低。
104
  - 合成整轨约需十几秒,所以用 `tools/render_music.js` 把**同一套代码**离线渲染成 `audio/music.mp3`,页面优先加载它,加载失败时自动回退为浏览器现场合成。
105
+
106
+ ## 7. 使用说明
107
+
108
+ 1. 打开 `libra-scales/index.html`(需通过 http(s) 访问,如 Hugging Face Space 或 `python3 -m http.server`)。
109
+ 2. **播放**:点击画面或 ▶;空格暂停,←/→ 快退快进 5 秒;下方章节按钮可跳转。手机竖屏会自动切换为 9:16 竖版,也可手动选择“横屏 16:9 / 竖屏 9:16”。
110
+ 3. **导出 MP4**:选择帧率(24 / 30 / 60 fps)、画幅(横屏 1920×1080 / 竖屏 1080×1920)、清晰度(1080p / 720p)→ “开始导出”。
111
+ 全程在浏览器本地逐帧渲染 + 编码,旁白、配乐、音效一起混入。推荐桌面版 Chrome / Edge(AAC 音轨);Firefox 等会用 Opus 音轨,仍是 .mp4。
112
+ 4. **发布文案**:四个平台的标题 / 描述 / 话题,带字数与话题数校验,可分别复制或一键复制全部。
113
+ 5. **封面**:8 张平台封面在页面实时绘制,可单张或全部下载 PNG;`covers/` 下另存了一份 JPG。
114
+
115
+ | 平台 | 封面 | 标题(字数) | 话题数 |
116
+ |---|---|---|---|
117
+ | 小红书 | 横 4:3 1440×1080 / 竖 3:4 1080×1440 | 天秤座为什么是一杆秤?3分钟动画讲透(18/20) | 10/10 |
118
+ | 抖音 | 横 4:3 / 竖 3:4 | 天秤座竟是唯一的“器物”星座?3分钟动画看懂它的秘密(26/30) | 5/5 |
119
+ | 哔哩哔哩 | 视频封面 4:3 / 个人空间 16:9 1920×1080 | 【星座动画】天秤座:星空中的一杆秤|…(56/80) | 10/10 |
120
+ | 微信视频号 | 主页卡片 3:4 / 分享卡片 4:3 | 天秤座:星空里的一杆秤(11/16) | 8(不限) |
libra-scales/covers/cover-bilibili-16x9-space.jpg ADDED

Git LFS Details

  • SHA256: 96da3dee5eed716de88279ef71413562e856b3602893e9272addfff696ab6096
  • Pointer size: 131 Bytes
  • Size of remote file: 309 kB
libra-scales/covers/cover-bilibili-4x3.jpg ADDED

Git LFS Details

  • SHA256: ae99447f4fd90c19b357ad28d8e613da22720cd94510d30e6b3c8118e0b2773b
  • Pointer size: 131 Bytes
  • Size of remote file: 234 kB
libra-scales/covers/cover-douyin-3x4.jpg ADDED

Git LFS Details

  • SHA256: 89cbbc287bc8ce30bfeb1ece8e9760e40762f07db3d2c590b1f38ee2b31ce755
  • Pointer size: 131 Bytes
  • Size of remote file: 239 kB
libra-scales/covers/cover-douyin-4x3.jpg ADDED

Git LFS Details

  • SHA256: 6455e572fcd3ab982ff8ba11df71670f163d3bfda55ec8b43e798cde85ec1658
  • Pointer size: 131 Bytes
  • Size of remote file: 234 kB
libra-scales/covers/cover-landscape.jpg ADDED

Git LFS Details

  • SHA256: ca6d87dd2bb81e4813e7eb306c641b0958f552b845d32f959687351142d78dcd
  • Pointer size: 131 Bytes
  • Size of remote file: 110 kB
libra-scales/covers/cover-wechat-3x4.jpg ADDED

Git LFS Details

  • SHA256: 9934801fffc75abbd50ae867f9104e031c6d85d46495d31b685eab3145a46db2
  • Pointer size: 131 Bytes
  • Size of remote file: 238 kB
libra-scales/covers/cover-wechat-4x3.jpg ADDED

Git LFS Details

  • SHA256: 75b99d5ae3f6d9e274a959d547044e9b4e8824eef7e3405388ea33cb7f4444db
  • Pointer size: 131 Bytes
  • Size of remote file: 234 kB
libra-scales/covers/cover-xhs-3x4.jpg ADDED

Git LFS Details

  • SHA256: b077d2a8ea4fcf5af6dafcfedba26545dd99b93d6b05aeec2703e29b374773e4
  • Pointer size: 131 Bytes
  • Size of remote file: 239 kB
libra-scales/covers/cover-xhs-4x3.jpg ADDED

Git LFS Details

  • SHA256: f1655af2ffe00ff6879704478028d1f9a5978b6eec88ca9d2233d0bc66906bd0
  • Pointer size: 131 Bytes
  • Size of remote file: 235 kB
libra-scales/index.html CHANGED
@@ -32,7 +32,7 @@
32
  </div>
33
  <label class="tg"><input id="tglVoice" type="checkbox" checked> 旁白</label>
34
  <label class="tg"><input id="tglMusic" type="checkbox" checked> 音乐/音效</label>
35
- <button id="btnFull" class="pbtn" aria-label="全屏">⛶</button>
36
  </div>
37
  <div id="chapters" class="chapters"></div>
38
  </section>
 
32
  </div>
33
  <label class="tg"><input id="tglVoice" type="checkbox" checked> 旁白</label>
34
  <label class="tg"><input id="tglMusic" type="checkbox" checked> 音乐/音效</label>
35
+ <button id="btnFull" class="pbtn" aria-label="全屏" title="全屏"><svg viewBox="0 0 24 24"><path d="M4 9V4h5M20 9V4h-5M4 15v5h5M20 15v5h-5"/></svg></button>
36
  </div>
37
  <div id="chapters" class="chapters"></div>
38
  </section>
libra-scales/js/meta.js CHANGED
@@ -33,7 +33,8 @@
33
  E.background(ctx, 12, W, H, { nebula: 1 });
34
  E.meteors(ctx, 2.4, W, H, 5, .9, 1);
35
  // 主光
36
- const sx = P ? W / 2 : W * (wide ? .3 : .32), sy = P ? H * .32 : H * .46;
 
37
  E.glow(ctx, sx, sy, 640 * S, 'rgba(246,180,110,1)', .55);
38
  E.glow(ctx, sx, sy, 300 * S, 'rgba(255,220,170,1)', .35);
39
  // 天秤座星图暗纹
@@ -41,17 +42,17 @@
41
  const pr = view(W, H, 15.25, -19, Math.min(W, H) / 34, P ? 0 : W * .22, P ? H * .1 : 0);
42
  ctx.save(); ctx.globalAlpha = .55; drawFigure(ctx, LIB, LIB_LINES, pr, 1, 'rgba(246,213,142,.6)', 2.4 * S, 1, 'rgba(255,225,160,1)'); ctx.restore();
43
  // 天平
44
- E.scales(ctx, sx, sy - 120 * S * (P ? .95 : 1.05), (P ? .95 : 1.05) * S, { tilt: -.12,
45
  leftLoad: (c, x, y, s) => E.glow(c, x, y - 12 * s, 60 * s, 'rgba(255,235,200,1)', 1),
46
  rightLoad: (c, x, y, s) => E.glow(c, x, y - 12 * s, 60 * s, 'rgba(255,180,210,1)', 1) });
47
  // 文本
48
- const tx = P ? W / 2 : W * (wide ? .69 : .68), al = 'center';
49
  let ty = P ? H * .66 : H * .3;
50
  E.libraGlyph(ctx, tx, ty - 40 * S, .55 * S, 1);
51
  ty += 110 * S;
52
- const big = (P ? 190 : wide ? 175 : 160) * S;
53
  E.text(ctx, '天秤座', tx, ty, { size: big, align: al, weight: 900, gradient: ['#fff6dc', '#f6d58e', '#f4a9c4'], glow: 'rgba(246,190,110,.6)', blur: 40 * S, spacing: 14 * S });
54
- ty += big * .78;
55
  E.text(ctx, '星空中的一杆秤', tx, ty, { size: 64 * S, align: al, color: '#fff3dc', weight: 700, spacing: 8 * S, glow: 'rgba(0,0,0,.7)', blur: 16 * S });
56
  ty += 92 * S;
57
  // 钩子胶囊
 
33
  E.background(ctx, 12, W, H, { nebula: 1 });
34
  E.meteors(ctx, 2.4, W, H, 5, .9, 1);
35
  // 主光
36
+ const sx = P ? W / 2 : W * (wide ? .27 : .25), sy = P ? H * .32 : H * .44;
37
+ const ks = P ? .95 : wide ? .92 : .78;
38
  E.glow(ctx, sx, sy, 640 * S, 'rgba(246,180,110,1)', .55);
39
  E.glow(ctx, sx, sy, 300 * S, 'rgba(255,220,170,1)', .35);
40
  // 天秤座星图暗纹
 
42
  const pr = view(W, H, 15.25, -19, Math.min(W, H) / 34, P ? 0 : W * .22, P ? H * .1 : 0);
43
  ctx.save(); ctx.globalAlpha = .55; drawFigure(ctx, LIB, LIB_LINES, pr, 1, 'rgba(246,213,142,.6)', 2.4 * S, 1, 'rgba(255,225,160,1)'); ctx.restore();
44
  // 天平
45
+ E.scales(ctx, sx, sy - 120 * S * ks, ks * S, { tilt: -.12,
46
  leftLoad: (c, x, y, s) => E.glow(c, x, y - 12 * s, 60 * s, 'rgba(255,235,200,1)', 1),
47
  rightLoad: (c, x, y, s) => E.glow(c, x, y - 12 * s, 60 * s, 'rgba(255,180,210,1)', 1) });
48
  // 文本
49
+ const tx = P ? W / 2 : W * (wide ? .68 : .69), al = 'center';
50
  let ty = P ? H * .66 : H * .3;
51
  E.libraGlyph(ctx, tx, ty - 40 * S, .55 * S, 1);
52
  ty += 110 * S;
53
+ const big = (P ? 190 : wide ? 175 : 150) * S;
54
  E.text(ctx, '天秤座', tx, ty, { size: big, align: al, weight: 900, gradient: ['#fff6dc', '#f6d58e', '#f4a9c4'], glow: 'rgba(246,190,110,.6)', blur: 40 * S, spacing: 14 * S });
55
+ ty += big * .84;
56
  E.text(ctx, '星空中的一杆秤', tx, ty, { size: 64 * S, align: al, color: '#fff3dc', weight: 700, spacing: 8 * S, glow: 'rgba(0,0,0,.7)', blur: 16 * S });
57
  ty += 92 * S;
58
  // 钩子胶囊
libra-scales/js/player.js CHANGED
@@ -61,7 +61,14 @@
61
  document.querySelectorAll('[data-orient]').forEach(b => b.onclick = () => { st.orient = b.dataset.orient; document.querySelectorAll('[data-orient]').forEach(x => x.classList.toggle('on', x === b)); resize(); });
62
  $('#tglMusic').onchange = e => { st.vol.music = e.target.checked ? 1 : 0; if (st.gM) st.gM.gain.value = st.vol.music; };
63
  $('#tglVoice').onchange = e => { st.vol.voice = e.target.checked ? 1 : 0; if (st.gV) st.gV.gain.value = st.vol.voice * 1.05; };
64
- $('#btnFull').onclick = () => { const el = stage; (document.fullscreenElement ? document.exitFullscreen() : (el.requestFullscreen || el.webkitRequestFullscreen).call(el)); };
 
 
 
 
 
 
 
65
  const chaps = $('#chapters');
66
  TL.scenes.forEach((s, i) => { const b = document.createElement('button'); b.className = 'chap'; b.textContent = (i + 1) + ' · ' + s.title; b.onclick = () => seek(s.start + .01); chaps.appendChild(b); });
67
  window.addEventListener('keydown', e => { if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT') return;
 
61
  document.querySelectorAll('[data-orient]').forEach(b => b.onclick = () => { st.orient = b.dataset.orient; document.querySelectorAll('[data-orient]').forEach(x => x.classList.toggle('on', x === b)); resize(); });
62
  $('#tglMusic').onchange = e => { st.vol.music = e.target.checked ? 1 : 0; if (st.gM) st.gM.gain.value = st.vol.music; };
63
  $('#tglVoice').onchange = e => { st.vol.voice = e.target.checked ? 1 : 0; if (st.gV) st.gV.gain.value = st.vol.voice * 1.05; };
64
+ $('#btnFull').onclick = () => {
65
+ const el = stage, req = el.requestFullscreen || el.webkitRequestFullscreen;
66
+ if (document.fullscreenElement || document.webkitFullscreenElement) { (document.exitFullscreen || document.webkitExitFullscreen).call(document); return; }
67
+ if (stage.classList.contains('fs')) { stage.classList.remove('fs'); resize(); return; }
68
+ if (req) { try { const r = req.call(el); if (r && r.catch) r.catch(() => { stage.classList.add('fs'); resize(); }); } catch (e) { stage.classList.add('fs'); resize(); } }
69
+ else { stage.classList.add('fs'); resize(); } // iOS Safari:伪全屏
70
+ };
71
+ document.addEventListener('keydown', e => { if (e.key === 'Escape' && stage.classList.contains('fs')) { stage.classList.remove('fs'); resize(); } });
72
  const chaps = $('#chapters');
73
  TL.scenes.forEach((s, i) => { const b = document.createElement('button'); b.className = 'chap'; b.textContent = (i + 1) + ' · ' + s.title; b.onclick = () => seek(s.start + .01); chaps.appendChild(b); });
74
  window.addEventListener('keydown', e => { if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT') return;
libra-scales/style.css CHANGED
@@ -1,6 +1,7 @@
1
  :root{--gold:#f6d58e;--rose:#f4a9c4;--ink:#070a1e;--card:rgba(255,255,255,.05);--line:rgba(246,213,142,.22)}
2
  *{box-sizing:border-box}
3
- html,body{margin:0;background:radial-gradient(1200px 800px at 75% -10%,#2a1a5c 0,#0b1030 45%,#05060f 100%) fixed;color:#efeaff;font-family:"Noto Sans SC","PingFang SC","Microsoft YaHei",sans-serif}
 
4
  button,select,input,textarea{font:inherit;color:inherit}
5
  .top{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:18px clamp(12px,3vw,36px)}
6
  .brand{display:flex;align-items:center;gap:14px}
@@ -12,6 +13,7 @@ main{max-width:1280px;margin:0 auto;padding:0 clamp(8px,2vw,24px) 40px}
12
  .player{margin-bottom:22px}
13
  .stage{position:relative;width:100%;aspect-ratio:16/9;background:#03040c;border-radius:18px;overflow:hidden;box-shadow:0 30px 90px rgba(0,0,0,.6),0 0 0 1px var(--line);margin:0 auto}
14
  .stage.portrait{aspect-ratio:9/16;width:min(100%,calc((100vh - 170px) * 9 / 16));min-width:280px}
 
15
  .stage:fullscreen{border-radius:0;width:100vw;height:100vh;aspect-ratio:auto}
16
  #screen{width:100%;height:100%;display:block;cursor:pointer}
17
  .loader{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;background:rgba(3,4,14,.72);gap:14px;transition:opacity .5s}
@@ -62,8 +64,10 @@ button:disabled{opacity:.4;cursor:not-allowed}
62
  .cover button{background:rgba(246,213,142,.14);border:1px solid var(--line);border-radius:8px;padding:7px;cursor:pointer;color:var(--gold)}
63
  footer{text-align:center;opacity:.5;font-size:12px;line-height:1.8;margin-top:26px}
64
  @media (max-width:640px){
65
- .top{padding:12px}.glyph{font-size:32px}.back{font-size:12px}
66
  .stage{border-radius:12px}
67
  .stage.portrait{width:100%;min-width:0}
68
  .controls{gap:8px;padding:8px}.time{min-width:auto}
69
  }
 
 
 
1
  :root{--gold:#f6d58e;--rose:#f4a9c4;--ink:#070a1e;--card:rgba(255,255,255,.05);--line:rgba(246,213,142,.22)}
2
  *{box-sizing:border-box}
3
+ html{background:#05060f}
4
+ body{margin:0;min-height:100vh;background:radial-gradient(1200px 800px at 75% -10%,#2a1a5c 0,#0b1030 45%,#05060f 100%) no-repeat,#05060f;color:#efeaff;font-family:"Noto Sans SC","PingFang SC","Microsoft YaHei",sans-serif}
5
  button,select,input,textarea{font:inherit;color:inherit}
6
  .top{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:18px clamp(12px,3vw,36px)}
7
  .brand{display:flex;align-items:center;gap:14px}
 
13
  .player{margin-bottom:22px}
14
  .stage{position:relative;width:100%;aspect-ratio:16/9;background:#03040c;border-radius:18px;overflow:hidden;box-shadow:0 30px 90px rgba(0,0,0,.6),0 0 0 1px var(--line);margin:0 auto}
15
  .stage.portrait{aspect-ratio:9/16;width:min(100%,calc((100vh - 170px) * 9 / 16));min-width:280px}
16
+ .stage.fs{position:fixed;inset:0;z-index:50;border-radius:0;width:100vw!important;height:100vh;height:100dvh;aspect-ratio:auto;min-width:0}
17
  .stage:fullscreen{border-radius:0;width:100vw;height:100vh;aspect-ratio:auto}
18
  #screen{width:100%;height:100%;display:block;cursor:pointer}
19
  .loader{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;background:rgba(3,4,14,.72);gap:14px;transition:opacity .5s}
 
64
  .cover button{background:rgba(246,213,142,.14);border:1px solid var(--line);border-radius:8px;padding:7px;cursor:pointer;color:var(--gold)}
65
  footer{text-align:center;opacity:.5;font-size:12px;line-height:1.8;margin-top:26px}
66
  @media (max-width:640px){
67
+ .top{padding:12px;align-items:flex-start}.glyph{font-size:30px}.back{font-size:12px}.top h1{font-size:18px;letter-spacing:1px}.top p{font-size:11px}
68
  .stage{border-radius:12px}
69
  .stage.portrait{width:100%;min-width:0}
70
  .controls{gap:8px;padding:8px}.time{min-width:auto}
71
  }
72
+
73
+ .pbtn svg{width:18px;height:18px;display:block;margin:auto;fill:none;stroke:currentColor;stroke-width:2}