Ethscriptions commited on
Commit
9a75c3f
·
verified ·
1 Parent(s): e69fc2f

gemini-stars v2: 星座文化文案改为温和正向表达;新增尾声荣耀时刻(金色桂冠徽章、荣誉铭牌、为你点亮);旁白/音轨/字体/多平台文案同步更新

Browse files
gemini-stars/README.md CHANGED
@@ -23,7 +23,7 @@
23
  | 封面 | Canvas 渲染:小红书 4:3 & 3:4、抖音 4:3 & 3:4、B 站 4:3 & 16:9、视频号 3:4 & 4:3 |
24
  | 文案 | 各平台标题 / 描述 / 话题,自动校验字数与话题数上限,一键复制 |
25
 
26
- ## 2. 内容大纲(约 2.5 分钟)
27
 
28
  1. **序章 · 冬夜的两颗星**:星空推近,两颗亮星并肩出现,标题浮现。
29
  2. **怎样找到它**:冬季夜空,从猎户座腰带向东北方延伸,找到北河二与北河三;双子座连线逐笔描出两个“小人”。
@@ -32,8 +32,10 @@
32
  5. **科学 · 北河三**:橙色巨星,约 34 光年,比“哥哥”更亮,还拥有一颗已确认的系外行星。
33
  6. **双子座流星雨**:每年 12 月中旬极大,母体是小行星 3200 法厄同,峰值每小时可达一百多颗。
34
  7. **星空里的发现**:1781 年赫歇尔在双子座附近发现天王星;1930 年汤博在双子座天区发现冥王星。
35
- 8. **黄道与星座文化**:太阳每年 6 月下旬至 7 月中旬穿过双子座天区;占星里的双子座(5/21–6/21)象征好奇、沟通与多面(文化趣味,非科学)。
36
- 9. **尾声**:两颗星的约定——下一个晴朗的冬夜,抬头去找它们。
 
 
37
 
38
  ## 3. 技术架构
39
 
@@ -46,7 +48,7 @@ gemini-stars/
46
  ├── audio.js # 配乐 / 音效 / 旁白混音(OfflineAudioContext,确定性)
47
  ├── timeline.js # 由 tools/make_voice.py 生成:每句旁白起止时间与字幕
48
  ├── copy.js # 小红书 / 抖音 / 哔哩哔哩 / 视频号 标题、描述、话题与封面配置
49
- ├── audio/v01–v29.mp3 # 29 句中文 AI 旁白
50
  ├── audio/soundtrack.mp3 # 由 audio.js 预渲染的完整混音(秒开;加载失败时浏览器内实时合成)
51
  ├── fonts/*.woff2 # Noto Sans/Serif SC 子集(OFL),国内网络也能正确显示
52
  ├── vendor/mp4-muxer.mjs # MIT 许可 MP4 封装库(本地化)
@@ -73,8 +75,8 @@ gemini-stars/
73
  - [x] Step 7:自检、修正与最终推送
74
 
75
  ### 自检记录
76
- - 横屏 + 竖屏全片逐帧扫描(24fps × 214s × 2 = 10,288 帧):0 报错。
77
- - 无头 Chromium 实测完整导出:24fps / 1280×720 / 3:34,约 2.5 分钟完成,得到含视频与音频的 MP4(该浏览器无 H.264 编码器,自动回退为 VP9 + Opus)。
78
  - 手机视口(390×844)自动切换竖屏,播放、字幕、控制条正常。
79
  - 音轨:整体响度 -13.6 LUFS,峰值 -1 dB。
80
 
 
23
  | 封面 | Canvas 渲染:小红书 4:3 & 3:4、抖音 4:3 & 3:4、B 站 4:3 & 16:9、视频号 3:4 & 4:3 |
24
  | 文案 | 各平台标题 / 描述 / 话题,自动校验字数与话题数上限,一键复制 |
25
 
26
+ ## 2. 内容大纲(约 4 分 09 秒)
27
 
28
  1. **序章 · 冬夜的两颗星**:星空推近,两颗亮星并肩出现,标题浮现。
29
  2. **怎样找到它**:冬季夜空,从猎户座腰带向东北方延伸,找到北河二与北河三;双子座连线逐笔描出两个“小人”。
 
32
  5. **科学 · 北河三**:橙色巨星,约 34 光年,比“哥哥”更亮,还拥有一颗已确认的系外行星。
33
  6. **双子座流星雨**:每年 12 月中旬极大,母体是小行星 3200 法厄同,峰值每小时可达一百多颗。
34
  7. **星空里的发现**:1781 年赫歇尔在双子座附近发现天王星;1930 年汤博在双子座天区发现冥王星。
35
+ 8. **黄道与流传千年的星座文化**:太阳每年 6 月下旬至 7 月下旬穿过双子座天区;在星座文化里,5/21–6/21 出生的人是双子座——心中住着“好奇”与“温柔”两颗星,风象星座、水星守护,灵动、聪慧、妙语连珠。
36
+ 9. **尾声 · 致双子座(荣耀时刻)**:北河二与北河三“为你点亮”;金色桂冠徽章「GEMINI · 双子座」在万丈光芒中升起,三块荣誉铭牌——好奇·探索世界的勇气、多面·丰盈闪耀的灵魂、陪伴·永不分离的守候;终章大字「属于你的光 · 致每一位双子座」。
37
+
38
+ > v2 文案调整:星座文化部分改为温和、正向的表达,以“流传千年的文化与祝福”呈现,强调观众的归属感、认可感与荣耀感。
39
 
40
  ## 3. 技术架构
41
 
 
48
  ├── audio.js # 配乐 / 音效 / 旁白混音(OfflineAudioContext,确定性)
49
  ├── timeline.js # 由 tools/make_voice.py 生成:每句旁白起止时间与字幕
50
  ├── copy.js # 小红书 / 抖音 / 哔哩哔哩 / 视频号 标题、描述、话题与封面配置
51
+ ├── audio/v01–v31.mp3 # 31 句中文 AI 旁白
52
  ├── audio/soundtrack.mp3 # 由 audio.js 预渲染的完整混音(秒开;加载失败时浏览器内实时合成)
53
  ├── fonts/*.woff2 # Noto Sans/Serif SC 子集(OFL),国内网络也能正确显示
54
  ├── vendor/mp4-muxer.mjs # MIT 许可 MP4 封装库(本地化)
 
75
  - [x] Step 7:自检、修正与最终推送
76
 
77
  ### 自检记录
78
+ - 横屏 + 竖屏全片逐帧扫描(v2:24fps × 249s × 2 ≈ 11,960 帧):0 报错。
79
+ - 无头 Chromium 实测完整导出(v1):24fps / 1280×720 / 3:34,约 2.5 分钟完成,得到含视频与音频的 MP4(该浏览器无 H.264 编码器,自动回退为 VP9 + Opus)。
80
  - 手机视口(390×844)自动切换竖屏,播放、字幕、控制条正常。
81
  - 音轨:整体响度 -13.6 LUFS,峰值 -1 dB。
82
 
gemini-stars/audio.js CHANGED
@@ -18,8 +18,8 @@ const MOOD = {
18
  pollux: { prog: 'major', arp: .8, pad: .8, bell: .4 },
19
  meteor: { prog: 'dream', arp: 1, pad: .8, bell: .5 },
20
  discover: { prog: 'major', arp: .7, pad: .75, bell: .3 },
21
- zodiac: { prog: 'dream', arp: .6, pad: .85, bell: .5 },
22
- outro: { prog: 'major', arp: .5, pad: 1, bell: .7 },
23
  };
24
  const BEAT = .6, CHORD = BEAT * 8;
25
 
@@ -141,8 +141,10 @@ export async function buildSoundtrack(onStatus = () => { }, baseUrl = '') {
141
  });
142
  // 尾声终止和弦
143
  const outro = S[S.length - 1];
144
- const fin = outro.lines[2].start - .3;
145
- [62, 66, 69, 74, 78].forEach((m, i) => bell(fin + i * .12, m, .1));
 
 
146
 
147
  /* ---------- 音效(与画面时间对齐) ---------- */
148
  onStatus('布置音效…');
@@ -162,8 +164,14 @@ export async function buildSoundtrack(onStatus = () => { }, baseUrl = '') {
162
  { const sc = at('meteor'), L = Ls(sc); shimmer(L[1] + .2, .08, 10, 91); whoosh(L[2] + 1.2, .3); ping(L[2] + 2.2, 76, .12); }
163
  { const sc = at('discover'), L = Ls(sc); ping(L[1] + .2, 79, .12); shimmer(L[1] + .8, .07, 8); ping(L[2] + .2, 76, .12);
164
  for (let k = 0; k < 8; k++) ping(L[2] + k / 2.2, 96, .03); shimmer(L[2] + 1.4, .07, 8); }
165
- { const sc = at('zodiac'), L = Ls(sc); ping(sc.start + .5, 74, .1); shimmer(L[1] - .2, .1, 12, 86); for (let k = 0; k < 3; k++) ping(L[2] + 1 + k * .7, 86 + k * 2, .08); }
166
- { const sc = at('outro'), L = Ls(sc); shimmer(sc.start + .5, .08, 10); for (let k = 0; k < 3; k++) ping(sc.start + 1 + k * .9, 84 + k * 3, .08); }
 
 
 
 
 
 
167
 
168
  /* ---------- 旁白 ---------- */
169
  const lines = S.flatMap(s => s.lines);
 
18
  pollux: { prog: 'major', arp: .8, pad: .8, bell: .4 },
19
  meteor: { prog: 'dream', arp: 1, pad: .8, bell: .5 },
20
  discover: { prog: 'major', arp: .7, pad: .75, bell: .3 },
21
+ zodiac: { prog: 'dream', arp: .7, pad: .9, bell: .6 },
22
+ outro: { prog: 'major', arp: .75, pad: 1.1, bell: .8 },
23
  };
24
  const BEAT = .6, CHORD = BEAT * 8;
25
 
 
141
  });
142
  // 尾声终止和弦
143
  const outro = S[S.length - 1];
144
+ const fin = outro.lines[outro.lines.length - 1].start - .3;
145
+ [62, 66, 69, 74, 78, 81, 86].forEach((m, i) => bell(fin + i * .12, m, .1));
146
+ const crown = outro.lines[2].start - 1;
147
+ [50, 57, 62, 66, 69, 74].forEach((m, i) => bell(crown + i * .09, m + 12, .09));
148
 
149
  /* ---------- 音效(与画面时间对齐) ---------- */
150
  onStatus('布置音效…');
 
164
  { const sc = at('meteor'), L = Ls(sc); shimmer(L[1] + .2, .08, 10, 91); whoosh(L[2] + 1.2, .3); ping(L[2] + 2.2, 76, .12); }
165
  { const sc = at('discover'), L = Ls(sc); ping(L[1] + .2, 79, .12); shimmer(L[1] + .8, .07, 8); ping(L[2] + .2, 76, .12);
166
  for (let k = 0; k < 8; k++) ping(L[2] + k / 2.2, 96, .03); shimmer(L[2] + 1.4, .07, 8); }
167
+ { const sc = at('zodiac'), L = Ls(sc); ping(sc.start + .5, 74, .1); shimmer(L[1] - .2, .12, 14, 86); boom(L[1] - .3, .3);
168
+ ping(L[2] + .4, 88, .1); ping(L[2] + 4, 91, .1); ping(L[2] + 7.5, 86, .1);
169
+ for (let k = 0; k < 3; k++) ping(L[3] + 2.2 + k * .8, 86 + k * 2, .09); }
170
+ { const sc = at('outro'), L = Ls(sc); shimmer(sc.start + .5, .08, 10); for (let k = 0; k < 3; k++) ping(sc.start + 1 + k * .9, 84 + k * 3, .08);
171
+ shimmer(L[1] + .5, .08, 10, 88); ping(L[1] + 3, 86, .1);
172
+ boom(L[2] - 1, .55); whoosh(L[2] - 1.6, .4); shimmer(L[2] - .8, .12, 18, 86);
173
+ for (let k = 0; k < 3; k++) { ping(L[2] + 1.5 + k * 2.6, 81 + k * 4, .12); shimmer(L[2] + 1.6 + k * 2.6, .05, 6, 93); }
174
+ shimmer(L[4] - .3, .12, 20, 86); boom(L[4] - .3, .4); }
175
 
176
  /* ---------- 旁白 ---------- */
177
  const lines = S.flatMap(s => s.lines);
gemini-stars/audio/soundtrack.mp3 CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:e83a0eeef43bb12cc2679d08fcef858a2e4a1ead72b7ef54f03121e5bd2e4b9d
3
- size 5144876
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e1f107a5f7ccfab06fe967466b91caa306b46c0df5af01a50f49becee1f08da3
3
+ size 5983532
gemini-stars/audio/v01.mp3 CHANGED
Binary files a/gemini-stars/audio/v01.mp3 and b/gemini-stars/audio/v01.mp3 differ
 
gemini-stars/audio/v02.mp3 CHANGED
Binary files a/gemini-stars/audio/v02.mp3 and b/gemini-stars/audio/v02.mp3 differ
 
gemini-stars/audio/v03.mp3 CHANGED
Binary files a/gemini-stars/audio/v03.mp3 and b/gemini-stars/audio/v03.mp3 differ
 
gemini-stars/audio/v04.mp3 CHANGED
Binary files a/gemini-stars/audio/v04.mp3 and b/gemini-stars/audio/v04.mp3 differ
 
gemini-stars/audio/v05.mp3 CHANGED
Binary files a/gemini-stars/audio/v05.mp3 and b/gemini-stars/audio/v05.mp3 differ
 
gemini-stars/audio/v06.mp3 CHANGED
Binary files a/gemini-stars/audio/v06.mp3 and b/gemini-stars/audio/v06.mp3 differ
 
gemini-stars/audio/v07.mp3 CHANGED
Binary files a/gemini-stars/audio/v07.mp3 and b/gemini-stars/audio/v07.mp3 differ
 
gemini-stars/audio/v08.mp3 CHANGED
Binary files a/gemini-stars/audio/v08.mp3 and b/gemini-stars/audio/v08.mp3 differ
 
gemini-stars/audio/v09.mp3 CHANGED
Binary files a/gemini-stars/audio/v09.mp3 and b/gemini-stars/audio/v09.mp3 differ
 
gemini-stars/audio/v10.mp3 CHANGED
Binary files a/gemini-stars/audio/v10.mp3 and b/gemini-stars/audio/v10.mp3 differ
 
gemini-stars/audio/v11.mp3 CHANGED
Binary files a/gemini-stars/audio/v11.mp3 and b/gemini-stars/audio/v11.mp3 differ
 
gemini-stars/audio/v12.mp3 CHANGED
Binary files a/gemini-stars/audio/v12.mp3 and b/gemini-stars/audio/v12.mp3 differ
 
gemini-stars/audio/v13.mp3 CHANGED
Binary files a/gemini-stars/audio/v13.mp3 and b/gemini-stars/audio/v13.mp3 differ
 
gemini-stars/audio/v14.mp3 CHANGED
Binary files a/gemini-stars/audio/v14.mp3 and b/gemini-stars/audio/v14.mp3 differ
 
gemini-stars/audio/v15.mp3 CHANGED
Binary files a/gemini-stars/audio/v15.mp3 and b/gemini-stars/audio/v15.mp3 differ
 
gemini-stars/audio/v16.mp3 CHANGED
Binary files a/gemini-stars/audio/v16.mp3 and b/gemini-stars/audio/v16.mp3 differ
 
gemini-stars/audio/v17.mp3 CHANGED
Binary files a/gemini-stars/audio/v17.mp3 and b/gemini-stars/audio/v17.mp3 differ
 
gemini-stars/audio/v18.mp3 CHANGED
Binary files a/gemini-stars/audio/v18.mp3 and b/gemini-stars/audio/v18.mp3 differ
 
gemini-stars/audio/v19.mp3 CHANGED
Binary files a/gemini-stars/audio/v19.mp3 and b/gemini-stars/audio/v19.mp3 differ
 
gemini-stars/audio/v20.mp3 CHANGED
Binary files a/gemini-stars/audio/v20.mp3 and b/gemini-stars/audio/v20.mp3 differ
 
gemini-stars/audio/v21.mp3 CHANGED
Binary files a/gemini-stars/audio/v21.mp3 and b/gemini-stars/audio/v21.mp3 differ
 
gemini-stars/audio/v22.mp3 CHANGED
Binary files a/gemini-stars/audio/v22.mp3 and b/gemini-stars/audio/v22.mp3 differ
 
gemini-stars/audio/v23.mp3 CHANGED
Binary files a/gemini-stars/audio/v23.mp3 and b/gemini-stars/audio/v23.mp3 differ
 
gemini-stars/audio/v24.mp3 CHANGED
Binary files a/gemini-stars/audio/v24.mp3 and b/gemini-stars/audio/v24.mp3 differ
 
gemini-stars/audio/v25.mp3 CHANGED
Binary files a/gemini-stars/audio/v25.mp3 and b/gemini-stars/audio/v25.mp3 differ
 
gemini-stars/audio/v26.mp3 CHANGED
Binary files a/gemini-stars/audio/v26.mp3 and b/gemini-stars/audio/v26.mp3 differ
 
gemini-stars/audio/v27.mp3 CHANGED
Binary files a/gemini-stars/audio/v27.mp3 and b/gemini-stars/audio/v27.mp3 differ
 
gemini-stars/audio/v28.mp3 CHANGED
Binary files a/gemini-stars/audio/v28.mp3 and b/gemini-stars/audio/v28.mp3 differ
 
gemini-stars/audio/v29.mp3 CHANGED
Binary files a/gemini-stars/audio/v29.mp3 and b/gemini-stars/audio/v29.mp3 differ
 
gemini-stars/audio/v30.mp3 ADDED
Binary file (59.3 kB). View file
 
gemini-stars/audio/v31.mp3 ADDED
Binary file (30.2 kB). View file
 
gemini-stars/audio/v32.mp3 ADDED
Binary file (22.3 kB). View file
 
gemini-stars/copy.js CHANGED
@@ -2,7 +2,7 @@
2
  export const PLATFORMS = [
3
  {
4
  id: 'xhs', name: '小红书', titleMax: 20, tagMax: 10, tagStyle: 'hash',
5
- title: '双子座:天上最浪漫的一对兄弟✨',
6
  desc: `冬夜抬头,东方那两颗肩并肩的亮星,就是双子座的“头”🌟
7
  🔭 怎么找:猎户座腰带 → 往东北方看
8
  🏛 神话:凡人哥哥卡斯托尔 × 不朽弟弟波吕克斯,弟弟把永生分了一半给哥哥
@@ -10,27 +10,28 @@ export const PLATFORMS = [
10
  🧡 北河三:橙色巨星,比“二号”哥哥还亮,身边还有一颗行星
11
  ☄️ 双子座流星雨:12 月中旬,母体竟是小行星法厄同
12
  🪐 天王星、冥王星的发现都和它有关
13
- 用 3 分半的动画讲给你听,下一个晴夜记得抬头找找它们~`,
14
- tags: ['双子座', '星座', '天文科普', '星空', '希腊神话', '双子座流星雨', '冬季星空', '观星', '涨知识', '科普动画'],
 
15
  covers: [
16
- { ratio: '4:3', w: 1440, h: 1080, label: '横封面 4:3', hook: '天上最浪漫的一对兄弟', tags: ['神话', '六星家族', '流星雨'] },
17
- { ratio: '3:4', w: 1080, h: 1440, label: '竖封面 3:4', hook: '天上最浪漫的一对兄弟', tags: ['神话', '六星家族', '流星雨'] },
18
  ],
19
  },
20
  {
21
  id: 'douyin', name: '抖音', titleMax: 30, tagMax: 5, tagStyle: 'hash',
22
- title: '一颗星藏着6颗恒星?3分半看懂双子座的浪漫与秘密',
23
- desc: '肉眼看是一颗星,望远镜里却是 6 颗恒星!双子座的兄弟神话、橙色巨星北河三、母体是小行星的流星雨,一次讲清楚。',
24
- tags: ['双子座', '天文科普', '星空', '双子座流星雨', '涨知识'],
25
  covers: [
26
- { ratio: '4:3', w: 1440, h: 1080, label: '横封面 4:3', hook: '一颗星藏着 6 颗恒星?', tags: ['北河二', '北河三', '流星雨'] },
27
- { ratio: '3:4', w: 1080, h: 1440, label: '竖封面 3:4', hook: '一颗星藏着 6 颗恒星?', tags: ['北河二', '北河三', '流星雨'] },
28
  ],
29
  },
30
  {
31
  id: 'bili', name: '哔哩哔哩', titleMax: 80, tagMax: 10, tagStyle: 'plain',
32
- title: '【星空动画】双子座有多神奇?一颗星竟是6颗恒星,流星雨母体是小行星,还见证了天王星和冥王星的发现',
33
- desc: `全程手工编程的星空动画,带你认识双子座:
34
  00:00 序章 · 冬夜的两颗星
35
  00:15 怎样找到它(猎户座腰带 → 东北方)
36
  00:40 神话 · 卡斯托尔与波吕克斯
@@ -38,23 +39,23 @@ export const PLATFORMS = [
38
  01:45 北河三 · 橙色巨星与它的行星(约 34 光年)
39
  02:06 双子座流星雨 · 母体为小行星 3200 法厄同
40
  02:28 星空里的发现 · 1781 天王星 / 1930 冥王星
41
- 02:46 黄道与星座文化(占星部分仅为文化趣味)
42
- 03:12 尾声
43
- 数据为常见天文资料的近似值,欢迎在评论区补充指正~`,
44
  tags: ['双子座', '天文', '科普', '星座', '希腊神话', '流星雨', '星空', '宇宙', '动画', '知识分享'],
45
  covers: [
46
- { ratio: '4:3', w: 1440, h: 1080, label: '视频封面 4:3', hook: '双子座到底有多神奇?', tags: ['神话', '六星家族', '流星雨'], badge: '星空动画' },
47
- { ratio: '16:9', w: 1920, h: 1080, label: '个人空间封面 16:9', hook: '双子座到底有多神奇?', tags: ['神话', '六星家族', '流星雨'], badge: '星空动画' },
48
  ],
49
  },
50
  {
51
  id: 'wx', name: '微信视频号', titleMax: 16, tagMax: Infinity, tagStyle: 'hash',
52
- title: '双子座:并肩千年的两颗星',
53
- desc: '冬夜东方的两颗亮星,是一对兄弟的约定。3 分半动画,带你读懂双子座的神话与科学。',
54
- tags: ['双子座', '星座', '天文科普', '星空', '希腊神话', '流星雨', '冬季星空', '观星', '科普', '宇宙'],
55
  covers: [
56
- { ratio: '3:4', w: 1080, h: 1440, label: '个人主页卡片 3:4', hook: '并肩千年的两颗星', tags: ['神话', '科学', '流星雨'] },
57
- { ratio: '4:3', w: 1440, h: 1080, label: '分享卡片 4:3', hook: '并肩千年的两颗星', tags: ['神话', '科学', '流星雨'] },
58
  ],
59
  },
60
  ];
 
2
  export const PLATFORMS = [
3
  {
4
  id: 'xhs', name: '小红书', titleMax: 20, tagMax: 10, tagStyle: 'hash',
5
+ title: '致双子座:你是两束并肩的光✨',
6
  desc: `冬夜抬头,东方那两颗肩并肩的亮星,就是双子座的“头”🌟
7
  🔭 怎么找:猎户座腰带 → 往东北方看
8
  🏛 神话:凡人哥哥卡斯托尔 × 不朽弟弟波吕克斯,弟弟把永生分了一半给哥哥
 
10
  🧡 北河三:橙色巨星,比“二号”哥哥还亮,身边还有一颗行星
11
  ☄️ 双子座流星雨:12 月中旬,母体竟是小行星法厄同
12
  🪐 天王星、冥王星的发现都和它有关
13
+ 🌟 人们说,双子座心里住着两颗星:一颗是好奇,一颗是温柔
14
+ 4 分钟星空动画,献给每一位双子座——那是属于你的星座,也是属于你的光✨`,
15
+ tags: ['双子座', '星座', '双子座女生', '双子座男生', '星空', '希腊神话', '双子座流星雨', '天文科普', '星座文化', '治愈系'],
16
  covers: [
17
+ { ratio: '4:3', w: 1440, h: 1080, label: '横封面 4:3', hook: '致双子座:你是两束并肩的光', tags: ['好奇', '温柔', '闪耀'] },
18
+ { ratio: '3:4', w: 1080, h: 1440, label: '竖封面 3:4', hook: '你是两束并肩的光', tags: ['好奇', '温柔', '闪耀'] },
19
  ],
20
  },
21
  {
22
  id: 'douyin', name: '抖音', titleMax: 30, tagMax: 5, tagStyle: 'hash',
23
+ title: '双子座看过来!一颗星藏着6颗恒星,这是属于你的星空',
24
+ desc: '肉眼看是一颗星,望远镜里却是 6 颗恒星!兄弟神话、橙色巨星、冬夜流星雨……双子座的星空,比你想象的更闪耀。你的好奇是勇气,你的多面是丰盈——致每一位双子座✨',
25
+ tags: ['双子座', '星座', '星空', '双子座流星雨', '天文科普'],
26
  covers: [
27
+ { ratio: '4:3', w: 1440, h: 1080, label: '横封面 4:3', hook: '这是属于你的星空', tags: ['神话', '六星家族', '流星雨'] },
28
+ { ratio: '3:4', w: 1080, h: 1440, label: '竖封面 3:4', hook: '这是属于你的星空', tags: ['神话', '六星家族', '流星雨'] },
29
  ],
30
  },
31
  {
32
  id: 'bili', name: '哔哩哔哩', titleMax: 80, tagMax: 10, tagStyle: 'plain',
33
+ title: '【星空动画】致每一位双子座:一颗星竟藏着6颗恒星,流星雨母体是小行星,还见证了天王星与冥王星的发现',
34
+ desc: `全程手工编程的星空动画,献给每一位双子座:
35
  00:00 序章 · 冬夜的两颗星
36
  00:15 怎样找到它(猎户座腰带 → 东北方)
37
  00:40 神话 · 卡斯托尔与波吕克斯
 
39
  01:45 北河三 · 橙色巨星与它的行星(约 34 光年)
40
  02:06 双子座流星雨 · 母体为小行星 3200 法厄同
41
  02:28 星空里的发现 · 1781 天王星 / 1930 冥王星
42
+ 02:46 黄道与流传千年的星座文化 · 心中的两颗星
43
+ 03:26 尾声 · 致双子座:属于你的光
44
+ 天文数据为常见资料的近似值,欢迎在评论区和大家分享你身边的双子座~`,
45
  tags: ['双子座', '天文', '科普', '星座', '希腊神话', '流星雨', '星空', '宇宙', '动画', '知识分享'],
46
  covers: [
47
+ { ratio: '4:3', w: 1440, h: 1080, label: '视频封面 4:3', hook: '致每一位双子座', tags: ['神话', '六星家族', '流星雨'], badge: '星空动画' },
48
+ { ratio: '16:9', w: 1920, h: 1080, label: '个人空间封面 16:9', hook: '致每一位双子座', tags: ['神话', '六星家族', '流星雨'], badge: '星空动画' },
49
  ],
50
  },
51
  {
52
  id: 'wx', name: '微信视频号', titleMax: 16, tagMax: Infinity, tagStyle: 'hash',
53
+ title: '致双子座:属于你的光',
54
+ desc: '冬夜东方的两颗亮星,是一对兄弟永不分离的约定。4 分钟星空动画,带你读懂双子座的神话、星空与千年文化——转给你身边的双子座吧✨',
55
+ tags: ['双子座', '星座', '星座文化', '星空', '希腊神话', '流星雨', '冬季星空', '天文科普', '治愈', '宇宙'],
56
  covers: [
57
+ { ratio: '3:4', w: 1080, h: 1440, label: '个人主页卡片 3:4', hook: '属于你的光', tags: ['好奇', '温柔', '闪耀'] },
58
+ { ratio: '4:3', w: 1440, h: 1080, label: '分享卡片 4:3', hook: '属于你的光', tags: ['好奇', '温柔', '闪耀'] },
59
  ],
60
  },
61
  ];
gemini-stars/covers/cover-bili-16x9.jpg CHANGED

Git LFS Details

  • SHA256: ba50cc8e5c3179abc6b163da61812a17ba67244f5639e553a4b2bb793303820a
  • Pointer size: 131 Bytes
  • Size of remote file: 118 kB

Git LFS Details

  • SHA256: b23fe9b4ef95c86633f924a02adefecb82756e06e08f6b48d73f8b2966c2fd4d
  • Pointer size: 131 Bytes
  • Size of remote file: 114 kB
gemini-stars/fonts/gemini-sans.woff2 CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:93582b984a341bb481a349295ad3940731aa1295f9249f16aaed5f8e0a189dd7
3
- size 218016
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a38144aa87625221063ebd8a4e96b7e39d61d9fc4fa96f561cf42ca731ecdeaf
3
+ size 228268
gemini-stars/fonts/gemini-serif.woff2 CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:dc92f188e2544138a4defe3c38b1e77be2a22516ac2fa68d34d375b84e33f120
3
- size 285380
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7c98a450689b239651c10b554f560ef04af74e925c9bf9a1b31c9f695d082709
3
+ size 299188
gemini-stars/scenes.js CHANGED
@@ -794,69 +794,214 @@ SC.zodiac = (ctx, lt, D, W, H, L, t) => {
794
  chip(ctx, '太阳在双子座天区:约 6/21 – 7/20', cx, port ? H * .72 : H * .1, 28 * u, ring * sstep(2, 3, lt));
795
  }
796
  if (culture > 0) {
797
- // 大 ♊ 符号
798
- const gx = port ? W / 2 : W * .32, gy = port ? H * .3 : H * .46, s = (port ? 300 : 340) * u;
799
- ctx.save(); ctx.globalAlpha = culture; ctx.font = `700 ${s}px ${SERIF}`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
800
- ctx.shadowColor = 'rgba(255,190,90,.8)'; ctx.shadowBlur = 40 * u; ctx.fillStyle = goldGrad(ctx, gx - s / 2, gy - s / 2, gx + s / 2, gy + s / 2);
801
- // 用线条画 ♊(避免字体缺字)
802
- ctx.strokeStyle = ctx.fillStyle; ctx.lineWidth = s * .07; ctx.lineCap = 'round';
803
- const w = s * .5, h2 = s * .55;
804
- ctx.beginPath(); ctx.moveTo(gx - w * .62, gy - h2 * .55); ctx.quadraticCurveTo(gx, gy - h2 * .35, gx + w * .62, gy - h2 * .55);
805
- ctx.moveTo(gx - w * .62, gy + h2 * .55); ctx.quadraticCurveTo(gx, gy + h2 * .35, gx + w * .62, gy + h2 * .55);
806
- ctx.moveTo(gx - w * .25, gy - h2 * .45); ctx.lineTo(gx - w * .25, gy + h2 * .45);
807
- ctx.moveTo(gx + w * .25, gy - h2 * .45); ctx.lineTo(gx + w * .25, gy + h2 * .45); ctx.stroke();
808
- ctx.restore();
809
- text(ctx, '5.21 – 6.21', gx, gy + s * .48, 40 * u, '#ffe6b0', 'center', culture, SERIF, 800);
810
- const tx = port ? W / 2 : W * .68, ty = port ? H * .56 : H * .32;
811
- text(ctx, '占星文化里的双子座', tx, ty, 34 * u, '#fff', 'center', culture, SERIF, 700);
812
- text(ctx, '风象星座 · 守护星:水星', tx, ty + 50 * u, 24 * u, '#c9d6ff', 'center', culture * sstep(L[1] + 1.5, L[1] + 2.5, lt));
813
- const words = [['好奇', '#9fe0ff'], ['健谈', '#ffd08a'], ['灵动', '#ffb3e0']];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
814
  words.forEach(([w, col], i) => {
815
- const a = sstep(L[2] + .8 + i * .7, L[2] + 1.6 + i * .7, lt);
816
- const x = tx + (i - 1) * (port ? 200 : 180) * u, y = ty + (port ? 140 : 160) * u + Math.sin(lt * 1.5 + i * 2) * 10 * u;
817
- ctx.save(); ctx.globalAlpha = a; ctx.translate(x, y); ctx.scale(backOut(a), backOut(a));
818
- ctx.beginPath(); ctx.arc(0, 0, 70 * u, 0, TAU); ctx.fillStyle = hexA(col, .15); ctx.fill(); ctx.strokeStyle = col; ctx.lineWidth = 2 * u; ctx.stroke(); ctx.restore();
819
- text(ctx, w, x, y, 38 * u, col, 'center', a, SERIF, 800);
 
820
  });
821
- chip(ctx, '※ 文化趣味,不是科学结论', tx, ty + (port ? 270 : 300) * u, 22 * u, sstep(L[2] + 4, L[2] + 5, lt), '#d6dcff', 'rgba(20,20,40,.7)', 'rgba(180,190,255,.4)');
 
822
  }
823
  };
824
 
825
- // 9. 尾声
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
826
  SC.outro = (ctx, lt, D, W, H, L, t) => {
827
  const { u, port } = layout(W, H);
828
- const view = { cx: 1, cy: -1.5, s: lerp(22, 27, easeIO(clamp(lt / D))) * u * (port ? 1.15 : 1), ox: W / 2, oy: H * (port ? .34 : .40) };
829
- const a = sstep(0, 1.5, lt);
 
830
  drawLines(ctx, view, GEM_LINES, 1, 'rgba(255,190,110,.22)', 24 * u, a * .6);
831
  drawLines(ctx, view, GEM_LINES, sstep(0, 3, lt), 'rgba(255,230,180,.95)', 2.6 * u, a);
832
- drawStarSet(ctx, view, GEM_KEYS, t, u, a, { castor: 1.4, pollux: 1.5 });
833
- // 回顾图标
834
- const icons = [['兄弟', '#ffe1a0'], ['流星雨', '#cfe0ff'], ['六星家族', '#ffd0a0']];
835
  icons.forEach(([s, col], i) => chip(ctx, s, W / 2 + (i - 1) * (port ? 250 : 260) * u, H * (port ? .62 : .74), 28 * u, sstep(.8 + i * .9, 1.8 + i * .9, lt) * (1 - sstep(L[1] - .5, L[1] + .5, lt)), col));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
836
  // 流星
837
  const r = rng(3);
838
- for (let i = 0; i < 6; i++) {
839
- const t0 = 2 + i * 3.1 + r(), age = lt - t0; if (age < 0 || age > .8) continue;
840
- const x = W * (.2 + r() * .6), y = H * (.08 + r() * .2), k = age / .8, len = 220 * u;
841
  const x1 = x + k * 300 * u, y1 = y + k * 150 * u;
842
- const g = ctx.createLinearGradient(x1 - len, y1 - len / 2, x1, y1); g.addColorStop(0, 'rgba(255,255,255,0)'); g.addColorStop(1, `rgba(255,255,255,${Math.sin(k * Math.PI)})`);
843
  ctx.strokeStyle = g; ctx.lineWidth = 2.5 * u; ctx.beginPath(); ctx.moveTo(x1 - len * .9, y1 - len * .45); ctx.lineTo(x1, y1); ctx.stroke();
844
  }
845
- // 结尾标题
846
- const e = sstep(L[2] - .5, L[2] + 1.5, lt);
847
  if (e > 0) {
848
- const ty = H * (port ? .66 : .78), size = (port ? 110 : 100) * u;
849
  ctx.save(); ctx.globalAlpha = e; ctx.font = `900 ${size}px ${SERIF}`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
850
- ctx.shadowColor = 'rgba(255,190,90,.7)'; ctx.shadowBlur = 30 * u; ctx.fillStyle = goldGrad(ctx, W / 2 - size * 2, ty, W / 2 + size * 2, ty);
851
- ctx.fillText('抬头,找到它们', W / 2, ty); ctx.restore();
852
- text(ctx, '双子座 · GEMINI', W / 2, ty + size * .85, 30 * u, '#cdd8ff', 'center', e, SANS, 400);
853
  }
854
  // 最后淡出
855
  const out = sstep(D - 2.2, D, lt);
856
  if (out > 0) { ctx.fillStyle = `rgba(0,0,0,${out})`; ctx.fillRect(0, 0, W, H); }
857
  };
858
 
859
- const CHAPTERS = { intro: '序章', find: '怎样找到它', myth: '神话 · 孪生兄弟', castor: '北河二 · 六星家族', pollux: '北河三 · 橙色巨星', meteor: '双子座流星雨', discover: '星空里的发现', zodiac: '黄道与星座文化', outro: '尾声' };
860
  const CAMS = { intro: [0, 0], find: [1, .4], myth: [2, .8], castor: [3, 1.1], pollux: [4, 1.5], meteor: [5, 2.1], discover: [6, 2.5], zodiac: [7, 2.9], outro: [8, 3.3] };
861
 
862
  /* ---------------- 主渲染 ---------------- */
 
794
  chip(ctx, '太阳在双子座天区:约 6/21 – 7/20', cx, port ? H * .72 : H * .1, 28 * u, ring * sstep(2, 3, lt));
795
  }
796
  if (culture > 0) {
797
+ // 背景金色光芒
798
+ const gx = port ? W / 2 : W * .3, gy = port ? H * .27 : H * .45, s = (port ? 280 : 320) * u;
799
+ godRays(ctx, gx, gy, s * 2.2, lt * .05, culture * .55, 18);
800
+ drawStar(ctx, gx, gy, s * .35, '#ffcf7a', culture * .35);
801
+ drawGlyph(ctx, gx, gy, s, culture, u);
802
+ text(ctx, '5.21 – 6.21', gx, gy + s * .5, 40 * u, '#ffe6b0', 'center', culture, SERIF, 800, 'rgba(255,190,90,.6)');
803
+ // 两颗心星:好奇 / 温柔(环绕符号)
804
+ const two = sstep(L[2] + .3, L[2] + 1.5, lt);
805
+ if (two > 0) {
806
+ const R = s * .78;
807
+ [['好奇', '#9fe0ff', 0, L[2] + 4], ['温柔', '#ffc7a0', Math.PI, L[2] + 7.5]].forEach(([w, col, ph, tt]) => {
808
+ const ang = ph + lt * .35 - .4, x = gx + Math.cos(ang) * R, y = gy + Math.sin(ang) * R * .42;
809
+ const hl = 1 + .5 * sstep(tt - .5, tt + .5, lt) * (1 - sstep(tt + 2.5, tt + 3.5, lt));
810
+ drawStar(ctx, x, y, 14 * u * hl, col, two, .9);
811
+ text(ctx, w, x, y - 44 * u, 34 * u * Math.min(hl, 1.2), col, 'center', two, SERIF, 900, col);
812
+ });
813
+ ctx.save(); ctx.globalAlpha = two * .35; ctx.strokeStyle = '#ffe1a0'; ctx.lineWidth = 1.5 * u; ctx.setLineDash([3 * u, 8 * u]);
814
+ ctx.beginPath(); ctx.ellipse(gx, gy, R, R * .42, 0, 0, TAU); ctx.stroke(); ctx.restore();
815
+ }
816
+ // 右侧文字
817
+ const tx = port ? W / 2 : W * .68, ty = port ? H * .55 : H * .28;
818
+ text(ctx, '流传千年的星座文化', tx, ty, 40 * u, '#fff4dc', 'center', culture, SERIF, 800, 'rgba(255,200,120,.5)');
819
+ ctx.save(); ctx.globalAlpha = culture; ctx.strokeStyle = goldGrad(ctx, tx - 200 * u, 0, tx + 200 * u, 0); ctx.lineWidth = 1.5 * u;
820
+ ctx.beginPath(); ctx.moveTo(tx - 200 * u * culture, ty + 34 * u); ctx.lineTo(tx + 200 * u * culture, ty + 34 * u); ctx.stroke(); ctx.restore();
821
+ const lines2 = [['心中两颗星', '好奇 · 温柔', L[2] + .6], ['风象星座', '水星守护', L[3] + .2]];
822
+ lines2.forEach(([k, v, tt], i) => {
823
+ const a = sstep(tt, tt + 1, lt), y = ty + (80 + i * 64) * u;
824
+ text(ctx, k, tx - 20 * u, y, 28 * u, '#c9d6ff', 'right', a, SANS, 500);
825
+ text(ctx, v, tx + 20 * u, y, 32 * u, '#ffe1a0', 'left', a, SERIF, 800);
826
+ });
827
+ const words = [['灵动', '#9fe0ff'], ['聪慧', '#ffd08a'], ['妙语连珠', '#ffb3e0']];
828
  words.forEach(([w, col], i) => {
829
+ const a = sstep(L[3] + 2.2 + i * .8, L[3] + 3 + i * .8, lt);
830
+ const x = tx + (i - 1) * (port ? 260 : 240) * u, y = ty + (port ? 290 : 300) * u + Math.sin(lt * 1.5 + i * 2) * 8 * u;
831
+ ctx.save(); ctx.globalAlpha = a; ctx.translate(x, y); const k = backOut(a); ctx.scale(k, k);
832
+ const bw = (w.length > 2 ? 190 : 140) * u;
833
+ rrect(ctx, -bw / 2, -38 * u, bw, 76 * u, 38 * u); ctx.fillStyle = hexA(col, .14); ctx.fill(); ctx.strokeStyle = col; ctx.lineWidth = 2 * u; ctx.shadowColor = col; ctx.shadowBlur = 16 * u; ctx.stroke(); ctx.restore();
834
+ text(ctx, w, x, y, 34 * u, col, 'center', a, SERIF, 800);
835
  });
836
+ text(ctx, '—— 千百年来,人们写给双子座的赞美', tx, ty + (port ? 380 : 390) * u, 24 * u, '#d9c9a6', 'center', sstep(L[3] + 5, L[3] + 6, lt), SERIF, 600);
837
+ goldDust(ctx, W, H, lt, culture * .8, u, 40, 7);
838
  }
839
  };
840
 
841
+ /* ---------------- 荣耀元素 ---------------- */
842
+ function drawGlyph(ctx, gx, gy, s, alpha, u) {
843
+ if (alpha <= .003) return;
844
+ ctx.save(); ctx.globalAlpha = alpha; ctx.shadowColor = 'rgba(255,190,90,.85)'; ctx.shadowBlur = 40 * u;
845
+ ctx.strokeStyle = goldGrad(ctx, gx - s / 2, gy - s / 2, gx + s / 2, gy + s / 2); ctx.lineWidth = s * .07; ctx.lineCap = 'round';
846
+ const w = s * .5, h2 = s * .55;
847
+ ctx.beginPath(); ctx.moveTo(gx - w * .62, gy - h2 * .55); ctx.quadraticCurveTo(gx, gy - h2 * .35, gx + w * .62, gy - h2 * .55);
848
+ ctx.moveTo(gx - w * .62, gy + h2 * .55); ctx.quadraticCurveTo(gx, gy + h2 * .35, gx + w * .62, gy + h2 * .55);
849
+ ctx.moveTo(gx - w * .25, gy - h2 * .45); ctx.lineTo(gx - w * .25, gy + h2 * .45);
850
+ ctx.moveTo(gx + w * .25, gy - h2 * .45); ctx.lineTo(gx + w * .25, gy + h2 * .45); ctx.stroke();
851
+ ctx.restore();
852
+ }
853
+ function godRays(ctx, x, y, R, rot, alpha, n = 24) {
854
+ if (alpha <= .003) return;
855
+ ctx.save(); ctx.globalAlpha = alpha; ctx.globalCompositeOperation = 'lighter'; ctx.translate(x, y); ctx.rotate(rot);
856
+ const g = ctx.createRadialGradient(0, 0, 0, 0, 0, R); g.addColorStop(0, 'rgba(255,214,140,.55)'); g.addColorStop(.5, 'rgba(255,190,90,.12)'); g.addColorStop(1, 'rgba(255,190,90,0)');
857
+ ctx.fillStyle = g;
858
+ for (let i = 0; i < n; i++) { const a = i / n * TAU, w = (i % 2 ? .035 : .07); ctx.beginPath(); ctx.moveTo(0, 0); ctx.arc(0, 0, R * (i % 2 ? .8 : 1), a - w, a + w); ctx.closePath(); ctx.fill(); }
859
+ ctx.restore();
860
+ }
861
+ function goldDust(ctx, W, H, t, alpha, u, n = 60, seed = 11) {
862
+ if (alpha <= .003) return;
863
+ const r = rng(seed);
864
+ for (let i = 0; i < n; i++) {
865
+ const x0 = r() * W, sp = .03 + r() * .06, ph = r(), y = H * (1.05 - ((ph + t * sp) % 1) * 1.1), x = x0 + Math.sin(t * .8 + i) * 20 * u;
866
+ drawStar(ctx, x, y, (1.5 + r() * 3) * u, r() > .5 ? '#ffd98e' : '#fff1c8', alpha * (.4 + .6 * Math.sin(t * 3 + i) ** 2));
867
+ }
868
+ }
869
+ function laurel(ctx, cx, cy, R, alpha, u, grow = 1) {
870
+ if (alpha <= .003) return;
871
+ ctx.save(); ctx.globalAlpha = alpha; ctx.fillStyle = goldGrad(ctx, cx - R, cy - R, cx + R, cy + R); ctx.shadowColor = 'rgba(255,190,90,.6)'; ctx.shadowBlur = 10 * u;
872
+ for (const side of [-1, 1]) {
873
+ const N = 11;
874
+ for (let i = 0; i < N * grow; i++) {
875
+ const a = Math.PI / 2 + side * (.35 + i / N * 2.0), x = cx + Math.cos(a) * R, y = cy + Math.sin(a) * R;
876
+ for (const off of [-1, 1]) {
877
+ ctx.save(); ctx.translate(x, y); ctx.rotate(a + side * Math.PI / 2 + off * .55 * side);
878
+ ctx.beginPath(); ctx.ellipse(0, off * R * .05, R * .075, R * .03, 0, 0, TAU); ctx.fill(); ctx.restore();
879
+ }
880
+ }
881
+ }
882
+ ctx.restore();
883
+ }
884
+ function drawEmblem(ctx, cx, cy, R, lt, a, u, t) {
885
+ if (a <= .003) return;
886
+ godRays(ctx, cx, cy, R * 3, lt * .06, a * .7, 28);
887
+ drawStar(ctx, cx, cy, R * .9, '#ffbf63', a * .45);
888
+ ctx.save(); ctx.globalAlpha = a;
889
+ // 底盘
890
+ const bg = ctx.createRadialGradient(cx, cy - R * .3, R * .1, cx, cy, R);
891
+ bg.addColorStop(0, '#26306b'); bg.addColorStop(.7, '#121638'); bg.addColorStop(1, '#0a0c22');
892
+ ctx.fillStyle = bg; ctx.beginPath(); ctx.arc(cx, cy, R, 0, TAU); ctx.fill();
893
+ // 双金环
894
+ const gg = goldGrad(ctx, cx - R, cy - R, cx + R, cy + R);
895
+ ctx.strokeStyle = gg; ctx.shadowColor = 'rgba(255,190,90,.8)'; ctx.shadowBlur = 20 * u;
896
+ ctx.lineWidth = R * .06; ctx.beginPath(); ctx.arc(cx, cy, R, 0, TAU); ctx.stroke();
897
+ ctx.lineWidth = R * .015; ctx.beginPath(); ctx.arc(cx, cy, R * .86, 0, TAU); ctx.stroke();
898
+ // 刻度(反向旋转)
899
+ ctx.shadowBlur = 0; ctx.lineWidth = R * .012;
900
+ for (let i = 0; i < 72; i++) { const ang = i / 72 * TAU - lt * .05, l = i % 6 === 0 ? .08 : .04; ctx.beginPath(); ctx.moveTo(cx + Math.cos(ang) * R * .9, cy + Math.sin(ang) * R * .9); ctx.lineTo(cx + Math.cos(ang) * R * (.9 + l * .7), cy + Math.sin(ang) * R * (.9 + l * .7)); ctx.stroke(); }
901
+ ctx.restore();
902
+ // 内部星座
903
+ const view = { cx: 1, cy: -1.5, s: R * .03, ox: cx, oy: cy - R * .02 };
904
+ ctx.save(); ctx.beginPath(); ctx.arc(cx, cy, R * .84, 0, TAU); ctx.clip();
905
+ drawGlyph(ctx, cx, cy, R * 1.1, a * .16, u);
906
+ drawLines(ctx, view, GEM_LINES, 1, 'rgba(255,224,170,.95)', R * .012, a);
907
+ drawStarSet(ctx, view, GEM_KEYS, t, u * R / 300, a, { castor: 1.5, pollux: 1.6 });
908
+ ctx.restore();
909
+ // 环绕星光
910
+ for (let i = 0; i < 2; i++) { const ang = lt * .6 + i * Math.PI; drawStar(ctx, cx + Math.cos(ang) * R, cy + Math.sin(ang) * R, R * .045, i ? '#ffc985' : '#e3ecff', a, 1); }
911
+ // 桂冠
912
+ laurel(ctx, cx, cy, R * 1.16, a, u, clamp(a * 1.2));
913
+ // 绶带
914
+ const by = cy + R * 1.02, bw = R * 1.5, bh = R * .26;
915
+ ctx.save(); ctx.globalAlpha = a;
916
+ ctx.fillStyle = '#7a1f2b';
917
+ for (const sd of [-1, 1]) { ctx.beginPath(); ctx.moveTo(cx + sd * bw * .42, by - bh * .3); ctx.lineTo(cx + sd * bw * .66, by - bh * .1); ctx.lineTo(cx + sd * bw * .58, by + bh * .35); ctx.lineTo(cx + sd * bw * .66, by + bh * .8); ctx.lineTo(cx + sd * bw * .38, by + bh * .6); ctx.closePath(); ctx.fill(); }
918
+ const rg = ctx.createLinearGradient(0, by - bh / 2, 0, by + bh / 2); rg.addColorStop(0, '#c23a4a'); rg.addColorStop(1, '#8e2232');
919
+ ctx.fillStyle = rg; ctx.beginPath(); ctx.moveTo(cx - bw / 2, by - bh / 2); ctx.quadraticCurveTo(cx, by - bh * .9, cx + bw / 2, by - bh / 2); ctx.lineTo(cx + bw / 2, by + bh / 2); ctx.quadraticCurveTo(cx, by + bh * .1, cx - bw / 2, by + bh / 2); ctx.closePath(); ctx.fill();
920
+ ctx.strokeStyle = goldGrad(ctx, cx - bw / 2, by, cx + bw / 2, by); ctx.lineWidth = 2.5 * u; ctx.stroke();
921
+ ctx.restore();
922
+ text(ctx, 'GEMINI · 双 子 座', cx, by - bh * .12, bh * .42, '#fff0c8', 'center', a, SERIF, 900);
923
+ }
924
+
925
+ // 9. 尾声 · 荣耀时刻
926
  SC.outro = (ctx, lt, D, W, H, L, t) => {
927
  const { u, port } = layout(W, H);
928
+ const emb = sstep(L[2] - 1, L[2] + 1.2, lt);
929
+ const view = { cx: 1, cy: -1.5, s: lerp(22, 26, easeIO(clamp(lt / L[2]))) * u * (port ? 1.15 : 1), ox: W / 2, oy: H * (port ? .34 : .40) - emb * H * .1 };
930
+ const a = sstep(0, 1.5, lt) * (1 - emb);
931
  drawLines(ctx, view, GEM_LINES, 1, 'rgba(255,190,110,.22)', 24 * u, a * .6);
932
  drawLines(ctx, view, GEM_LINES, sstep(0, 3, lt), 'rgba(255,230,180,.95)', 2.6 * u, a);
933
+ drawStarSet(ctx, view, GEM_KEYS, t, u, a, { castor: 1.4 + sstep(L[1], L[1] + 1, lt) * .8, pollux: 1.5 + sstep(L[1], L[1] + 1, lt) * .8 });
934
+ // 回顾标签
935
+ const icons = [['兄弟情', '#ffe1a0'], ['流星雨', '#cfe0ff'], ['六星家族', '#ffd0a0']];
936
  icons.forEach(([s, col], i) => chip(ctx, s, W / 2 + (i - 1) * (port ? 250 : 260) * u, H * (port ? .62 : .74), 28 * u, sstep(.8 + i * .9, 1.8 + i * .9, lt) * (1 - sstep(L[1] - .5, L[1] + .5, lt)), col));
937
+ // 为你点亮:星光洒向仰望者
938
+ const you = sstep(L[1], L[1] + 1.2, lt) * (1 - emb);
939
+ if (you > 0) {
940
+ const hy = H * (port ? .7 : .8), px = W / 2, py = hy - 10 * u;
941
+ ctx.save(); ctx.globalAlpha = you; const hg = ctx.createLinearGradient(0, hy - 40 * u, 0, H); hg.addColorStop(0, 'rgba(10,12,30,0)'); hg.addColorStop(.3, '#070816'); hg.addColorStop(1, '#030308');
942
+ ctx.fillStyle = hg; ctx.beginPath(); ctx.moveTo(0, H); for (let x = 0; x <= W; x += 20 * u) ctx.lineTo(x, hy + Math.sin(x / W * 4 + 1) * 18 * u); ctx.lineTo(W, H); ctx.fill(); ctx.restore();
943
+ ['castor', 'pollux'].forEach((k, i) => {
944
+ const [sx, sy] = P(view, k); const pr = easeOut(sstep(L[1] + .5 + i * .4, L[1] + 3 + i * .4, lt));
945
+ const g = ctx.createLinearGradient(sx, sy, px, py); const col = i ? '255,201,133' : '227,236,255';
946
+ g.addColorStop(0, `rgba(${col},.8)`); g.addColorStop(1, `rgba(${col},0)`);
947
+ ctx.save(); ctx.globalAlpha = you * .8; ctx.globalCompositeOperation = 'lighter'; ctx.strokeStyle = g; ctx.lineWidth = 3 * u; ctx.beginPath(); ctx.moveTo(sx, sy); ctx.lineTo(lerp(sx, px, pr), lerp(sy, py - 60 * u, pr)); ctx.stroke(); ctx.restore();
948
+ });
949
+ drawStar(ctx, px, py - 40 * u, 40 * u, '#ffd98e', you * sstep(L[1] + 2.5, L[1] + 3.5, lt) * .7);
950
+ figure(ctx, px, py + 30 * u, 90 * u, { color: '#ffe6b0', alpha: you, arm: .7, glow: .8, fill: 'rgba(255,214,140,.15)', time: t });
951
+ chip(ctx, '为你点亮', px, py + 80 * u, 26 * u, you * sstep(L[1] + 3, L[1] + 4, lt));
952
+ }
953
+ // 荣耀徽章
954
+ if (emb > 0) {
955
+ const R = (port ? 250 : 220) * u * lerp(.6, 1, easeOut(emb));
956
+ const cx = W / 2, cy = H * (port ? .32 : .37);
957
+ const ex = port ? cx : lerp(W / 2, W * .32, sstep(L[2] + 1, L[2] + 2.5, lt) * (1 - sstep(L[3] - .5, L[3] + 1, lt)));
958
+ drawEmblem(ctx, ex, cy, R, lt, emb, u, t);
959
+ goldDust(ctx, W, H, lt, emb, u, 70, 21);
960
+ // 三块荣誉铭牌
961
+ const plaques = [['好奇', '探索世界的勇气'], ['多面', '丰盈闪耀的灵魂'], ['陪伴', '永不分离的守候']];
962
+ const pv = 1 - sstep(L[3] - .5, L[3] + .8, lt);
963
+ plaques.forEach(([k, v], i) => {
964
+ const tt = L[2] + 1.5 + i * 2.6, pa = sstep(tt, tt + .8, lt) * pv;
965
+ if (pa <= 0) return;
966
+ const pw = (port ? 640 : 420) * u, ph = (port ? 88 : 96) * u;
967
+ const xx = port ? W / 2 - pw / 2 : W * .72 - pw / 2, y = port ? H * .535 + i * ph * 1.22 : H * (.24 + i * .15);
968
+ const w2 = pw;
969
+ ctx.save(); ctx.globalAlpha = pa; ctx.translate((1 - pa) * 60 * u, 0);
970
+ rrect(ctx, xx, y, w2, ph, 18 * u); const pg = ctx.createLinearGradient(xx, y, xx + w2, y + ph); pg.addColorStop(0, 'rgba(60,40,20,.85)'); pg.addColorStop(1, 'rgba(25,20,50,.85)');
971
+ ctx.fillStyle = pg; ctx.fill(); ctx.strokeStyle = goldGrad(ctx, xx, y, xx + w2, y + ph); ctx.lineWidth = 2 * u; ctx.shadowColor = 'rgba(255,190,90,.6)'; ctx.shadowBlur = 18 * u; ctx.stroke(); ctx.restore();
972
+ if (port) {
973
+ text(ctx, k, xx + 40 * u + (1 - pa) * 60 * u, y + ph / 2, 42 * u, '#ffe1a0', 'left', pa, SERIF, 900, 'rgba(255,190,90,.6)');
974
+ text(ctx, v, xx + 160 * u + (1 - pa) * 60 * u, y + ph / 2, 30 * u, '#f1e6cc', 'left', pa, SANS, 500);
975
+ } else {
976
+ text(ctx, k, xx + 36 * u + (1 - pa) * 60 * u, y + ph / 2, 44 * u, '#ffe1a0', 'left', pa, SERIF, 900, 'rgba(255,190,90,.6)');
977
+ text(ctx, v, xx + 150 * u + (1 - pa) * 60 * u, y + ph / 2, 28 * u, '#f1e6cc', 'left', pa, SANS, 500);
978
+ }
979
+ });
980
+ }
981
  // 流星
982
  const r = rng(3);
983
+ for (let i = 0; i < 10; i++) {
984
+ const t0 = 2 + i * 3.4 + r(), age = lt - t0; if (age < 0 || age > .8) continue;
985
+ const x = W * (.1 + r() * .6), y = H * (.05 + r() * .15), k = age / .8, len = 220 * u;
986
  const x1 = x + k * 300 * u, y1 = y + k * 150 * u;
987
+ const g = ctx.createLinearGradient(x1 - len, y1 - len / 2, x1, y1); g.addColorStop(0, 'rgba(255,255,255,0)'); g.addColorStop(1, `rgba(255,240,210,${Math.sin(k * Math.PI)})`);
988
  ctx.strokeStyle = g; ctx.lineWidth = 2.5 * u; ctx.beginPath(); ctx.moveTo(x1 - len * .9, y1 - len * .45); ctx.lineTo(x1, y1); ctx.stroke();
989
  }
990
+ // 结尾大字
991
+ const e = sstep(L[4] - .3, L[4] + 1.5, lt);
992
  if (e > 0) {
993
+ const ty = H * (port ? .68 : .75), size = (port ? 96 : 92) * u;
994
  ctx.save(); ctx.globalAlpha = e; ctx.font = `900 ${size}px ${SERIF}`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
995
+ ctx.shadowColor = 'rgba(255,170,70,.9)'; ctx.shadowBlur = 36 * u; ctx.fillStyle = goldGrad(ctx, W / 2 - size * 3, ty, W / 2 + size * 3, ty);
996
+ ctx.fillText('属于你的光', W / 2, ty); ctx.restore();
997
+ text(ctx, '致 每 一 位 双 子 座', W / 2, ty + size * .85, 32 * u, '#ffe6b0', 'center', e, SERIF, 700);
998
  }
999
  // 最后淡出
1000
  const out = sstep(D - 2.2, D, lt);
1001
  if (out > 0) { ctx.fillStyle = `rgba(0,0,0,${out})`; ctx.fillRect(0, 0, W, H); }
1002
  };
1003
 
1004
+ const CHAPTERS = { intro: '序章', find: '怎样找到它', myth: '神话 · 孪生兄弟', castor: '北河二 · 六星家族', pollux: '北河三 · 橙色巨星', meteor: '双子座流星雨', discover: '星空里的发现', zodiac: '黄道与星座文化', outro: '尾声 · 致双子座' };
1005
  const CAMS = { intro: [0, 0], find: [1, .4], myth: [2, .8], castor: [3, 1.1], pollux: [4, 1.5], meteor: [5, 2.1], discover: [6, 2.5], zodiac: [7, 2.9], outro: [8, 3.3] };
1006
 
1007
  /* ---------------- 主渲染 ---------------- */
gemini-stars/timeline.js CHANGED
@@ -1,6 +1,6 @@
1
  // 由 tools/make_voice.py 自动生成,请勿手改
2
  export const TIMELINE = {
3
- "duration": 214.32,
4
  "scenes": [
5
  {
6
  "id": "intro",
@@ -201,43 +201,61 @@ export const TIMELINE = {
201
  },
202
  {
203
  "file": "audio/v25.mp3",
204
- "text": "而在占星文化里,五月二十一日到六月二十一日出生的人被称作双子座,",
205
  "start": 175.95,
206
- "end": 183.27
207
  },
208
  {
209
  "file": "audio/v26.mp3",
210
- "text": "人们常用好奇、健谈、灵动来形容他们——当然,这只是一种有趣的文化想象。",
211
- "start": 183.65,
212
- "end": 191.33
 
 
 
 
 
 
213
  }
214
  ],
215
- "end": 192.19
216
  },
217
  {
218
  "id": "outro",
219
- "start": 192.19,
220
  "lines": [
221
  {
222
- "file": "audio/v27.mp3",
223
  "text": "两颗星,一段兄弟情,一场流星雨,和一个装满秘密的六星家族。",
224
- "start": 192.91,
225
- "end": 199.63
226
  },
227
  {
228
- "file": "audio/v28.mp3",
229
- "text": "下一个晴朗的冬夜,记得抬头,找一找那对并肩的星星。",
230
- "start": 200.01,
231
- "end": 205.51
232
  },
233
  {
234
- "file": "audio/v29.mp3",
235
- "text": "它们已经在那里,等了你很久很久。",
236
- "start": 205.89,
237
- "end": 209.44
 
 
 
 
 
 
 
 
 
 
 
 
238
  }
239
  ],
240
- "end": 214.32
241
  }
242
  ]
243
  };
 
1
  // 由 tools/make_voice.py 自动生成,请勿手改
2
  export const TIMELINE = {
3
+ "duration": 249.25,
4
  "scenes": [
5
  {
6
  "id": "intro",
 
201
  },
202
  {
203
  "file": "audio/v25.mp3",
204
+ "text": "而在流传千年的星座文化里,五月二十一日到六月二十一日出生的人,被称作双子座。",
205
  "start": 175.95,
206
+ "end": 184.18
207
  },
208
  {
209
  "file": "audio/v26.mp3",
210
+ "text": "人们说,双子座的心里住着两颗星:一颗是好奇,让你永远对世界充满热情;一颗是温柔,让你懂得倾听与陪伴。",
211
+ "start": 184.56,
212
+ "end": 195.96
213
+ },
214
+ {
215
+ "file": "audio/v27.mp3",
216
+ "text": "风象星座,水星守护。灵动、聪慧、妙语连珠——这是千百年来,人们写给双子座的赞美。",
217
+ "start": 196.34,
218
+ "end": 205.92
219
  }
220
  ],
221
+ "end": 206.86
222
  },
223
  {
224
  "id": "outro",
225
+ "start": 206.86,
226
  "lines": [
227
  {
228
+ "file": "audio/v28.mp3",
229
  "text": "两颗星,一段兄弟情,一场流星雨,和一个装满秘密的六星家族。",
230
+ "start": 207.7,
231
+ "end": 214.42
232
  },
233
  {
234
+ "file": "audio/v29.mp3",
235
+ "text": "如果你是双子座,请记得:北河二与北河三,从远古起,就在冬夜里为你点亮。",
236
+ "start": 214.8,
237
+ "end": 222.58
238
  },
239
  {
240
+ "file": "audio/v30.mp3",
241
+ "text": "你的好奇,是探索世界的勇气;你的多面,是丰盈闪耀的灵魂;你的陪伴,就像那对永不分离的兄弟。",
242
+ "start": 222.96,
243
+ "end": 232.85
244
+ },
245
+ {
246
+ "file": "audio/v31.mp3",
247
+ "text": "下一个晴朗的冬夜,抬头找一找那对并肩的星星。",
248
+ "start": 233.23,
249
+ "end": 238.27
250
+ },
251
+ {
252
+ "file": "audio/v32.mp3",
253
+ "text": "那是属于你的星座,也是属于你的光。",
254
+ "start": 238.65,
255
+ "end": 242.37
256
  }
257
  ],
258
+ "end": 249.25
259
  }
260
  ]
261
  };
gemini-stars/tools/make_voice.py CHANGED
@@ -43,15 +43,18 @@ SCENES = [
43
  "一九三零年,汤博在双子座天区的照片里,找到了冥王星。"]),
44
  ("zodiac", 1.2, [
45
  "双子座也是黄道星座之一。每年六月下旬到七月下旬,太阳会从它的天区穿过。",
46
- "而在占星文化里,五月二十一日到六月二十一日出生的人被称作双子座,",
47
- "人们常用好奇、健谈、灵动来形容他们——当然,这只是一种有趣的文化想象。"]),
48
- ("outro", 1.2, [
 
49
  "两颗星,一段兄弟情,一场流星雨,和一个装满秘密的六星家族。",
50
- "下一个晴朗的冬夜,记得抬头,找一找那对并肩的星星。",
51
- "它们已经在那里,等了你很久很久。"]),
 
 
52
  ]
53
  GAP = 0.38
54
- TAIL = 4.5
55
  FF = imageio_ffmpeg.get_ffmpeg_exe()
56
 
57
  def dur(path):
 
43
  "一九三零年,汤博在双子座天区的照片里,找到了冥王星。"]),
44
  ("zodiac", 1.2, [
45
  "双子座也是黄道星座之一。每年六月下旬到七月下旬,太阳会从它的天区穿过。",
46
+ "而在流传千年的星座文化里,五月二十一日到六月二十一日出生的人,被称作双子座。",
47
+ "人们说,双子座的心里住着两颗星:一颗是好奇,让你永远对世界充满热情;一颗是温柔,让你懂得倾听与陪伴。",
48
+ "风象星座,水星守护。灵动、聪慧、妙语连珠——这是千百年来,人们写给双子座的赞美。"]),
49
+ ("outro", 1.4, [
50
  "两颗星,一段兄弟情,一场流星雨,和一个装满秘密的六星家族。",
51
+ "如果你是双子座,请记得:北河二与北河三,从远古起,就在冬夜里为你点亮。",
52
+ "你的好奇,是探索世界的勇气;你的多面,是丰盈闪耀的灵魂;你的陪伴,就像那对永不分离的兄弟。",
53
+ "下一个晴朗的冬夜,抬头找一找那对并肩的星星。",
54
+ "那是属于你的星座,也是属于你的光。"]),
55
  ]
56
  GAP = 0.38
57
+ TAIL = 6.5
58
  FF = imageio_ffmpeg.get_ffmpeg_exe()
59
 
60
  def dur(path):
index.html CHANGED
@@ -12,7 +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="gemini-stars/index.html"><img src="gemini-stars/covers/cover-bili-16x9.jpg" alt="双子座 · 星河里的一对兄弟" onerror="this.style.visibility='hidden'"><div><b>♊ 双子座:星河里的一对兄弟</b><span>星座动画 · 全新制作 · 深空金线星图 · 约 3 分 34 秒 · 配乐音效旁白 · 横竖屏 · 24/30/60fps 导出 · 多平台封面文案</span></div></a>
16
  <a class="card" href="taurus/index.html"><img src="taurus/covers/cover-bili-16x9.jpg" alt="金牛座 · 慢慢来,也能抵达美好" onerror="this.style.visibility='hidden'"><div><b>♉ 金牛座:慢慢来,也能抵达美好</b><span>星座动画 · 全新制作 · 新艺术鎏金花卉 · 约 4 分 10 秒 · 鎏金珍藏卡 · 横竖屏 · 24/30/60fps 导出</span></div></a>
17
  <a class="card" href="aries/index.html"><img src="aries/covers/cover-bilibili-16x9.jpg" alt="白羊座 · 春天从这里出发" onerror="this.style.visibility='hidden'"><div><b>♈ 白羊座:春天从这里出发</b><span>星座动画 · 全新制作 · 烈焰金红 · 约 3 分 33 秒 · 专属荣耀铭牌 · 横竖屏 · 24/30/60fps 导出</span></div></a>
18
  <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 分 40 秒 · 横竖屏 · 24/30/60fps 导出</span></div></a>
 
12
  </style></head><body>
13
  <h1>知识动画工作室</h1><p>网页即播 · 浏览器本地导出 MP4 · 横屏 B站 / 竖屏抖音</p>
14
  <div class="grid">
15
+ <a class="card" href="gemini-stars/index.html"><img src="gemini-stars/covers/cover-bili-16x9.jpg" alt="双子座 · 星河里的一对兄弟" onerror="this.style.visibility='hidden'"><div><b>♊ 双子座:星河里的一对兄弟</b><span>星座动画 · 全新制作 · 深空金线星图 · 约 4 分 09 秒 · 双子荣耀徽章 · 配乐音效旁白 · 横竖屏 · 24/30/60fps 导出 · 多平台封面文案</span></div></a>
16
  <a class="card" href="taurus/index.html"><img src="taurus/covers/cover-bili-16x9.jpg" alt="金牛座 · 慢慢来,也能抵达美好" onerror="this.style.visibility='hidden'"><div><b>♉ 金牛座:慢慢来,也能抵达美好</b><span>星座动画 · 全新制作 · 新艺术鎏金花卉 · 约 4 分 10 秒 · 鎏金珍藏卡 · 横竖屏 · 24/30/60fps 导出</span></div></a>
17
  <a class="card" href="aries/index.html"><img src="aries/covers/cover-bilibili-16x9.jpg" alt="白羊座 · 春天从这里出发" onerror="this.style.visibility='hidden'"><div><b>♈ 白羊座:春天从这里出发</b><span>星座动画 · 全新制作 · 烈焰金红 · 约 3 分 33 秒 · 专属荣耀铭牌 · 横竖屏 · 24/30/60fps 导出</span></div></a>
18
  <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 分 40 秒 · 横竖屏 · 24/30/60fps 导出</span></div></a>