shortdrama-render.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. // ==============================================================================
  2. // AI短视频起号变现 · VOC 深度洞察报告 · 主渲染器
  3. // ==============================================================================
  4. // 01 · Cover
  5. // 02 · Agenda
  6. // 03-05 · Ch1 诘问起点(3 子章)
  7. // 06-09 · Ch2 六大赛道VOC验证(4 子章)
  8. // 10-12 · Ch3 选品与变现路径VOC(3 子章)
  9. // 13-15 · Ch4 人设信任度VOC(3 子章)
  10. // 16-18 · Ch5 平台政策与风险VOC(3 子章)
  11. // 19-21 · Ch6 投放ROI与运营VOC(3 子章)
  12. // 22-25 · Ch7 30天起号SOP(4 子章)
  13. // 26-27 · Ch8 矩阵化与规模化路径(2 子章)
  14. // 总计 25 slides(初始占位版)
  15. // ==============================================================================
  16. const STYLES = require('./voc-styles.js');
  17. const SCRIPTS = require('./voc-scripts.js');
  18. const LA = require('./shortdrama-analyze.js');
  19. const C = require('./lactic-components.js');
  20. const { BRAND, esc, fmtLikes } = C;
  21. const CHAPTERS = [
  22. { id: 1, num: '1', key: 'challenge', title: '诘问起点', en: 'THE CHALLENGE', tone: 'rose', color: BRAND.CORAL, icon: '🎯',
  23. summary: 'AI短视频工具爆发 · 工具平权时代 · "起号变现"才是真正的瓶颈' },
  24. { id: 2, num: '2', key: 'track_voc', title: 'AI视频赛道全图谱 VOC', en: 'TRACK SPECTRUM VOC', tone: 'blue', color: BRAND.TECH_BLUE, icon: '📊',
  25. summary: '20+AI视频赛道全景扫描·流量型/变现型/壁垒型/热点型四象限定位·真实起号数据反向验证' },
  26. { id: 3, num: '3', key: 'product_voc', title: '选品与变现路径 VOC', en: 'PRODUCT & MONETIZE', tone: 'amber', color: BRAND.SUNNY_YELLOW, icon: '🛒',
  27. summary: '什么品好卖?什么变现路径最靠谱?挂车带货/知识付费/私域引流/代运营真实收入验证' },
  28. { id: 4, num: '4', key: 'persona_voc', title: '人设信任度 VOC', en: 'PERSONA & TRUST', tone: 'purple', color: BRAND.PURPLE, icon: '🎭',
  29. summary: 'AI数字人 vs 真人出镜——信任度差距到底多大?AI+真人混合的最优解是什么?' },
  30. { id: 5, num: '5', key: 'policy_voc', title: '平台政策与风险 VOC', en: 'PLATFORM POLICY', tone: 'red', color: BRAND.WINE_RED, icon: '⚠️',
  31. summary: '限流/封号/搬运判定——抖音快手对AI内容的真实红线在哪里?' },
  32. { id: 6, num: '6', key: 'roi_voc', title: '投放 ROI 与运营 VOC', en: 'ROI & OPERATIONS', tone: 'orange', color: BRAND.WARM_ORANGE, icon: '📈',
  33. summary: '抖+/千川投AI短视频的ROI真实数据 · 不同阶段投放策略与预算' },
  34. { id: 7, num: '7', key: 'sop_voc', title: '30 天起号 SOP', en: '30-DAY SOP', tone: 'green', color: BRAND.JIANG_GREEN, icon: '🗓',
  35. summary: '从 0 到变现的四阶段执行计划:冷启动→千粉→变现→稳定 · 每天发什么、投多少、盯什么数据' },
  36. { id: 8, num: '8', key: 'matrix_voc', title: '矩阵化与规模化路径', en: 'MATRIX & SCALE', tone: 'green', color: BRAND.LACTIC_MINT, icon: '🚀',
  37. summary: '单号跑通 → 多号矩阵 → 代运营/培训变现 · 从月入5k到月入50k的规模化路径' },
  38. ];
  39. const CHAPTER_SUBSECTIONS = {
  40. challenge: [
  41. '残酷现实·80%AI账号在1000粉前放弃',
  42. '工具平权时代·成功者与失败者的根本区别',
  43. 'VOC反向验证·从真实创作者的社媒声音中找答案',
  44. ],
  45. track_voc: [
  46. '流量型赛道·萌娃/萌宠/搞笑/情感/风景/音乐——涨粉快但变现需策略',
  47. '变现型赛道·数字人带货/好物测评/穿搭种草——离钱最近但信任门槛高',
  48. '壁垒型赛道·知识科普/微短剧/技能教学/行业IP——门槛高但长线价值大',
  49. '赛道四象限模型·如何根据自身资源匹配最优赛道',
  50. ],
  51. product_voc: [
  52. '带货型选品·食品/百货/服装/母婴/家居的AI带货数据',
  53. '服务型变现·知识付费/私域引流/代运营的真实价格与需求',
  54. '变现路径ROI对比·挂车vs知识付费vs私域vs星图vs代运营',
  55. ],
  56. persona_voc: [
  57. '五类人设信任度对比·真实人物/数字人/萌娃/虚拟场景/AI+真人混合',
  58. 'AI痕迹接受度边界·用户什么时候会因为AI而划走',
  59. '人设一致性×粉丝粘性·频繁切换AI风格的影响',
  60. ],
  61. policy_voc: [
  62. '抖音AI内容政策·限流触发条件×AI标签的真实影响',
  63. '多号矩阵搬运判定·AI去重技术的有效边界',
  64. '封号案例库·真实VOC中的封号/限流经验汇总',
  65. ],
  66. roi_voc: [
  67. '抖+投放ROI·AI短视频不同账号类型的投放数据',
  68. '千川投放策略·数字人带货vs真人带货的ROI对比',
  69. '冷启动投放预算·0-1000粉阶段的最优投放策略',
  70. ],
  71. sop_voc: [
  72. '冷启动期 Day1-7·破0粉×测方向×高密度测试',
  73. '起号期 Day8-15·冲刺千粉×放大模型×投放测试',
  74. '变现期 Day16-25·开通橱窗×选品挂车×转化优化',
  75. '稳定期 Day26-30·数据复盘×矩阵复制×规模化',
  76. ],
  77. matrix_voc: [
  78. '单号→多号矩阵·内容复用×去重策略×账号管理',
  79. '规模化变现·代运营/培训/素材售卖的三条增长曲线',
  80. ],
  81. };
  82. const CHAPTER_HERO_LINES = {
  83. challenge: `当前AI短视频工具(<strong style="color:${BRAND.CORAL};">即梦/可灵/火山引擎</strong>)已高度成熟,单条视频生产成本降至 <strong style="color:${BRAND.CORAL};">5-24 元</strong>,技术门槛趋近于零。但残酷现实是:<strong style="color:${BRAND.CORAL};">80%的AI短视频账号在1000粉之前放弃</strong>。成功起号者与失败者的根本区别不在工具,而在于是否做对了<strong style="color:${BRAND.LACTIC_MINT};">赛道选择 × 选品匹配 × 人设定位 × 投放节奏</strong>这四步。本章直面根本困境:<strong>方向困境 / 流量困境 / 变现困境 / 合规困境 / 信息过载</strong>。`,
  84. track_voc: `AI短视频赛道远不止6种——从<strong style="color:${BRAND.TECH_BLUE};">流量型</strong>(萌娃/萌宠/搞笑/情感/风景/音乐)到<strong style="color:${BRAND.TECH_BLUE};">变现型</strong>(数字人带货/好物测评/穿搭种草/探店打卡)再到<strong style="color:${BRAND.TECH_BLUE};">壁垒型</strong>(知识科普/微短剧/技能教学/行业IP/历史解说),每个赛道都有不同的起号逻辑、涨粉曲线和变现天花板。本章建立一套<strong style="color:${BRAND.LACTIC_MINT};">"流量-变现-壁垒"三维归类框架</strong>,帮助你根据自身资源(时间/预算/技能/行业背景)科学匹配最优赛道,而非盲目跟风。</p><p style="color:var(--text-2); font-size:0.92rem; line-height:1.7; margin-top:8px;">核心方法论:<strong style="color:${BRAND.CORAL};">不是"哪个赛道好"的问题,而是"哪个赛道适合你"的问题</strong>。本章基于<strong>抖音+小红书</strong>真实VOC数据,逐赛道验证起号成功率、涨粉速度、变现效率和竞争激烈度。`,
  85. product_voc: `不同账号类型对应完全不同的选品逻辑和变现路径。<strong style="color:${BRAND.SUNNY_YELLOW};">数字人带货</strong>适合低价快消(食品/日用9.9-69元),<strong style="color:${BRAND.SUNNY_YELLOW};">AI换装</strong>适合服装美妆(49-199元),<strong style="color:${BRAND.SUNNY_YELLOW};">AI萌娃</strong>适合母婴用品和知识付费。变现路径的优先级排序——<strong>挂车带货 > 知识付费 > 私域引流 > 代运营 > 星图广告</strong>——需要VOC验证真实收入数据和转化效率。`,
  86. persona_voc: `AI短视频的人设是<strong style="color:${BRAND.PURPLE};">信任的锚点</strong>。五种人设类型——<strong>真实人物出镜 / 数字人拟真 / AI萌娃萌宠 / AI虚拟场景 / AI+真人混合</strong>——在信任度、涨粉速度和变现效率上差异巨大。核心VOC问题:<strong style="color:${BRAND.PURPLE};">AI痕迹的接受度边界在哪里?</strong>用户什么时候会因为"一看就是AI做的"而划走/不信任/举报?`,
  87. policy_voc: `<strong style="color:${BRAND.WINE_RED};">平台政策是AI短视频最大的不确定变量</strong>。抖音/快手对纯AI内容的限流/标签政策持续收紧,"如何合规地用AI"比"如何用AI做爆款"更关键。核心问题:<strong style="color:${BRAND.WINE_RED};">AI标签究竟是加分还是减分?多号矩阵化会被判定搬运吗?限流的真实触发条件是什么?</strong>本章基于真实VOC中的封号/限流案例,绘制平台政策风险地图。`,
  88. roi_voc: `AI短视频的投放不是"有钱就烧"——不同账号类型、不同阶段的投放策略完全不同。<strong style="color:${BRAND.WARM_ORANGE};">冷启动期</strong>不投流、纯靠自然流量验证内容;<strong style="color:${BRAND.WARM_ORANGE};">起号期</strong>抖+测试投放单条30-50元;<strong style="color:${BRAND.WARM_ORANGE};">变现期</strong>千川精准投放ROI>1.5才放量。本章基于真实VOC中的投放经验数据,验证各阶段的预算、ROI和最优策略。`,
  89. sop_voc: `基于VOC数据反向验证的<strong style="color:${BRAND.JIANG_GREEN};">30天起号变现SOP</strong>:<strong>冷启动期 Day1-7</strong>(破0粉,测方向,不投流)→ <strong>起号期 Day8-15</strong>(冲刺千粉,放大已验证模型,抖+测试投放)→ <strong>变现期 Day16-25</strong>(开通橱窗/挂车,选品匹配,千川精准投放)→ <strong>稳定期 Day26-30</strong>(数据复盘,矩阵复制,规模化)。每阶段的<strong>目标/内容策略/投放预算/关键指标/常见坑</strong>全部基于VOC实证。`,
  90. blueprint: `当单号模型跑通后,<strong style="color:${BRAND.LACTIC_MINT};">矩阵化</strong>是放大收益的最短路径:一个爆款内容模板 → AI批量变体 → 多号分发 → 放大10倍流量。<strong style="color:${BRAND.LACTIC_MINT};">规模化变现</strong>的三条增长曲线:① 矩阵带货(月入3-10万)→ ② 代运营服务(月入5-20万)→ ③ 培训/知识付费(月入10-50万)。每个阶段都有真实的VOC案例和量化阈值。`,
  91. };
  92. function sn(n) { return String(n).padStart(2, '0'); }
  93. function renderNav() {
  94. const dots = [
  95. { tip: '封面', href: '#s0' },
  96. { tip: '目录', href: '#agenda' },
  97. ...CHAPTERS.map((c) => ({ tip: `${c.num} ${c.title}`, href: `#${c.key}` })),
  98. ];
  99. return `<nav class="nav-rail" aria-label="章节导航">
  100. ${dots.map((d, i) => `<a class="nav-dot${i === 0 ? ' active' : ''}" href="${d.href}" data-tip="${esc(d.tip)}" data-idx="${i}"></a>`).join('')}
  101. </nav>
  102. <div class="top-bar">
  103. <div class="brand-logo">AI短视频起号变现<span class="dot">·</span>VOC 深度洞察报告 v1.0</div>
  104. <div class="text-mono text-xs" style="color:var(--text-3);">全赛道矩阵 / 8 假设 / 30天 SOP</div>
  105. </div>`;
  106. }
  107. function renderKbdHint() {
  108. return `<div class="kbd-hint" id="kbdHint">
  109. <span>键盘切换</span>
  110. <kbd>↑</kbd><kbd>↓</kbd>
  111. <span>或</span>
  112. <kbd>PgUp</kbd><kbd>PgDn</kbd>
  113. </div>`;
  114. }
  115. function renderCover({ product, collectedAt, slideNum, slideTotal, meta }) {
  116. const totalComments = meta?.comments || 0;
  117. const platformCount = Object.values(meta?.platforms || {}).filter((n) => n > 0).length;
  118. const trackCount = meta?.productsCount || 0;
  119. const stage = meta?.stage || 'skeleton';
  120. const tier = meta?.sourceTier || 'unknown';
  121. const isReal = tier === 'real-collected';
  122. const sourceBadge = isReal
  123. ? { label: '真实采集 · 抖音 + 小红书评论颗粒度', color: BRAND.JIANG_GREEN, icon: '✓' }
  124. : { label: '骨架版 · 待 VOC 数据加载', color: BRAND.CORAL, icon: '○' };
  125. const commentsDisplay = totalComments.toLocaleString();
  126. return `<section class="report-section" id="s0" data-tone="amber" data-slide-num="${slideNum}/${slideTotal}" style="min-height:100vh;padding:0;">
  127. <div class="cover-hero">
  128. <div class="aurora" aria-hidden="true"><span></span><span></span><span></span></div>
  129. <div class="cover-grid-bg"></div>
  130. <div class="cover-content">
  131. <div class="cover-meta rise-in" style="display:flex; justify-content:space-between; align-items:center; gap:16px; flex-wrap:wrap;">
  132. <span>AI短视频 · VOC 深度洞察 · 2026</span>
  133. <span style="display:inline-flex; align-items:center; gap:6px; padding:4px 12px; background:${sourceBadge.color}15; color:${sourceBadge.color}; border:1px solid ${sourceBadge.color}55; border-radius:16px; font-family:var(--font-mono); font-size:0.72rem; letter-spacing:0.12em; font-weight:600;">
  134. <span style="font-size:0.9rem;">${sourceBadge.icon}</span>${esc(sourceBadge.label)}
  135. </span>
  136. </div>
  137. <div class="cover-title-stack">
  138. <p class="text-mono rise-in" style="color:${BRAND.LACTIC_MINT}; font-size:0.9rem;letter-spacing:0.2em;margin-bottom:18px;">全赛道矩阵 × 8 假设 × 30天 SOP · 四维 VOC 验证</p>
  139. <h1 class="cover-title rise-in">${esc(product)}<br/><span style="color:${BRAND.LACTIC_MINT}; -webkit-text-fill-color:${BRAND.LACTIC_MINT}; background:none; font-size:0.66em;">VOC 深度洞察报告</span></h1>
  140. <p class="text-poster rise-in" style="margin-top:20px;color:var(--text-2); font-size:1.42rem;">
  141. 从 <strong style="color:${BRAND.CORAL};">80%放弃率</strong> 到 <strong style="color:${BRAND.LACTIC_MINT};">可复制的起号变现SOP</strong>
  142. </p>
  143. </div>
  144. <p class="cover-sub fade-up">
  145. 以 <strong style="color:${BRAND.LACTIC_MINT};">${commentsDisplay}</strong> 条真实多平台 VOC 评论为证据底座,
  146. 反向验证 <strong style="color:${BRAND.TECH_BLUE};">20+ AI赛道类型</strong>的起号难度与变现效率,
  147. 覆盖 <strong style="color:${BRAND.SUNNY_YELLOW};">12 个选品品类</strong> × <strong style="color:${BRAND.PURPLE};">5 种人设类型</strong> × <strong style="color:${BRAND.WINE_RED};">平台政策红线</strong> × <strong style="color:${BRAND.WARM_ORANGE};">投放ROI</strong>,
  148. 输出从 <strong style="color:${BRAND.CORAL};">0 粉</strong> 到 <strong style="color:${BRAND.JIANG_GREEN};">稳定变现</strong> 的 <strong style="color:${BRAND.JIANG_GREEN};">30天可执行SOP</strong>。
  149. </p>
  150. <div class="cover-stats fade-up" style="grid-template-columns:repeat(6, 1fr); max-width:1100px;">
  151. <div class="cover-stat"><div class="num">${commentsDisplay}</div><div class="lbl">真实 VOC</div></div>
  152. <div class="cover-stat"><div class="num">${platformCount}</div><div class="lbl">采集平台</div></div>
  153. <div class="cover-stat"><div class="num">20+</div><div class="lbl">AI赛道</div></div>
  154. <div class="cover-stat"><div class="num">8</div><div class="lbl">核心假设</div></div>
  155. <div class="cover-stat"><div class="num">${trackCount}</div><div class="lbl">关键词维度</div></div>
  156. <div class="cover-stat"><div class="num">${slideTotal}</div><div class="lbl">slides</div></div>
  157. </div>
  158. <div class="fade-up" style="margin-top:32px; padding:14px 20px; background:${BRAND.LACTIC_MINT}08; border-left:3px solid ${BRAND.LACTIC_MINT}; border-radius:4px; max-width:920px; margin-left:auto; margin-right:auto; text-align:left;">
  159. <div class="text-mono text-xs" style="color:${BRAND.LACTIC_MINT}; letter-spacing:0.14em; margin-bottom:6px; font-weight:700;">DATA FOUNDATION · 数据底座</div>
  160. <p style="color:var(--text-2); font-size:0.82rem; line-height:1.6; margin:0;">
  161. 本报告基于 <strong style="color:${BRAND.LACTIC_MINT};">docs/短剧变现/raw/_merged.json</strong> 真实多平台采集:
  162. 抖音 <strong>${meta?.platforms?.douyin || '待采集'}</strong> 条 + 小红书 <strong>${meta?.platforms?.xhs || '待采集'}</strong> 条。
  163. 每条 VOC 带 <code style="background:${BRAND.JIANG_GREEN}15; padding:1px 5px; border-radius:3px; color:${BRAND.JIANG_GREEN};">source=real-collected</code> 明确标识。
  164. </p>
  165. </div>
  166. <p class="text-mono text-xs fade-up" style="margin-top:40px; color:var(--text-3);">SCROLL DOWN · 采集截止 ${esc(collectedAt || '')} · ${commentsDisplay} 条真实 VOC</p>
  167. </div>
  168. </div>
  169. </section>`;
  170. }
  171. function renderAgenda({ slideNum, slideTotal }) {
  172. return `<section class="report-section" id="agenda" data-tone="amber" data-slide-num="${slideNum}/${slideTotal}">
  173. <div class="section-inner">
  174. <div class="section-head rise-in">
  175. <div class="section-eyebrow" style="color:${BRAND.LACTIC_MINT};">CONTENTS · 8 章认知递进</div>
  176. <h2 class="text-h1"><span class="headline-anim">从诘问到蓝图 · AI短视频起号变现全链路 VOC 实证</span></h2>
  177. <p class="section-sub">按<strong style="color:${BRAND.LACTIC_MINT};">认知深度递进</strong>:① 诘问 → ② 赛道VOC → ③ 选品变现 → ④ 人设信任 → ⑤ 平台风险 → ⑥ 投放ROI → ⑦ 30天SOP → ⑧ 矩阵规模化</p>
  178. </div>
  179. <div class="grid" style="grid-template-columns:repeat(2, 1fr); gap:22px; margin-top:20px;">
  180. ${CHAPTERS.map((c, i) => `
  181. <a href="#${c.key}" class="fade-up" style="text-decoration:none; display:block; background:var(--bg-1); border:1px solid var(--border); border-left:4px solid ${c.color}; border-radius:var(--radius-md); padding:24px; transition:all .35s cubic-bezier(.2,.7,.2,1); position:relative; overflow:hidden;" onmouseover="this.style.transform='translateY(-4px)'; this.style.borderColor='${c.color}'; this.style.boxShadow='0 20px 60px -20px ${c.color}55'" onmouseout="this.style.transform='translateY(0)'; this.style.borderColor='var(--border)'; this.style.boxShadow='none'">
  182. <div style="position:absolute; top:-18px; right:-10px; font-family:var(--font-head); font-weight:900; font-size:8rem; line-height:1; color:${c.color}; opacity:.08; letter-spacing:-0.06em; pointer-events:none;">${c.num}</div>
  183. <div style="display:flex; justify-content:space-between; align-items:start; margin-bottom:14px;">
  184. <div class="text-mono text-xs" style="color:${c.color}; letter-spacing:0.18em; font-weight:700;">CH ${c.num} · ${c.en}</div>
  185. <div style="font-size:1.6rem; filter:grayscale(20%);">${c.icon}</div>
  186. </div>
  187. <h3 class="text-h2" style="margin:0 0 10px; color:var(--text-1); font-size:1.38rem;">${esc(c.title)}</h3>
  188. <p style="color:var(--text-2); font-size:0.88rem; line-height:1.6; margin-bottom:12px;">${esc(c.summary)}</p>
  189. <div style="display:flex; flex-wrap:wrap; gap:5px; margin-bottom:12px;">
  190. ${(CHAPTER_SUBSECTIONS[c.key] || []).slice(0, 5).map((sub, j) => `<span style="padding:3px 8px; background:${c.color}15; color:${c.color}; border-radius:3px; font-size:0.66rem; font-weight:600;">${c.num}.${j + 1} ${esc(sub.split('·')[0])}</span>`).join('')}
  191. </div>
  192. <div style="padding-top:12px; border-top:1px solid var(--border); display:flex; justify-content:space-between; align-items:center;">
  193. <span class="text-xs text-mono" style="color:var(--text-3); letter-spacing:0.12em;">→ 跳转章节</span>
  194. <span class="text-mono text-xs" style="color:${c.color};">${i + 1} / ${CHAPTERS.length}</span>
  195. </div>
  196. </a>`).join('')}
  197. </div>
  198. <div class="fade-up" style="margin-top:44px; padding:22px 26px; background:linear-gradient(135deg, ${BRAND.LACTIC_MINT}10, ${BRAND.JIANG_GREEN}06); border:1px solid ${BRAND.LACTIC_MINT}22; border-radius:var(--radius-md);">
  199. <div class="text-mono text-xs" style="color:${BRAND.LACTIC_MINT}; letter-spacing:0.15em; margin-bottom:10px;">METHODOLOGY · 方法论自律</div>
  200. <div style="color:var(--text-1); font-size:0.98rem; line-height:1.72;">
  201. 本报告严格遵循 《<strong style="color:${BRAND.LACTIC_MINT};">docs/短剧变现/0.基础调研.md + 1.场景的补充.md</strong>》反向验证方法论:<br/>
  202. <strong style="color:${BRAND.CORAL};">❌ 不做</strong>:理论推导、"我觉得什么赛道好"、脱离VOC的拍脑袋建议;<br/>
  203. <strong style="color:${BRAND.JIANG_GREEN};">✅ 做</strong>:8条核心假设 H1-H8 × 多平台VOC实证 × 6大赛道 × 12选品品类 × 5人设类型 × 投放ROI × 30天可执行SOP;<br/>
  204. 每一个结论都附 <strong style="color:${BRAND.LACTIC_MINT};">真实用户原声</strong>(昵称+平台+点赞数)+ <strong style="color:${BRAND.TECH_BLUE};">量化数据</strong> + <strong style="color:${BRAND.SAGE};">可执行决策项</strong> 三重验证。
  205. </div>
  206. </div>
  207. </div>
  208. </section>`;
  209. }
  210. function renderChapterDivider({ chapter, slideNum, slideTotal }) {
  211. const c = chapter;
  212. const heroLine = CHAPTER_HERO_LINES[c.key] || c.summary;
  213. const subs = CHAPTER_SUBSECTIONS[c.key] || [];
  214. return `<section class="report-section" id="${c.key}" data-tone="${c.tone}" data-slide-num="${slideNum}/${slideTotal}" style="justify-content:center;">
  215. <div class="section-inner">
  216. <div class="rise-in" style="text-align:center; margin-bottom:40px;">
  217. <div style="font-family:var(--font-head); font-weight:900; font-size:clamp(5rem, 14vw, 11rem); line-height:0.9; color:${c.color}; opacity:0.12; letter-spacing:-0.06em;">0${c.num}</div>
  218. <div class="text-mono text-xs" style="color:${c.color}; letter-spacing:0.3em; margin-top:-2rem; font-weight:700;">CHAPTER ${c.num} · ${c.en}</div>
  219. </div>
  220. <div class="rise-in" style="text-align:center; margin-bottom:36px;">
  221. <div style="font-size:3.2rem; margin-bottom:14px;">${c.icon}</div>
  222. <h2 class="text-poster" style="font-size:clamp(2.4rem, 5.2vw, 4.2rem); color:var(--text-1); letter-spacing:-0.025em;">${esc(c.title)}</h2>
  223. <p style="color:${c.color}; font-family:var(--font-mono); font-size:0.92rem; letter-spacing:0.18em; margin-top:12px; font-weight:600;">${c.en}</p>
  224. </div>
  225. <div class="fade-up" style="max-width:960px; margin:0 auto; text-align:center; padding:28px 32px; background:linear-gradient(135deg, ${c.color}0C, transparent); border-left:3px solid ${c.color}; border-radius:var(--radius-md);">
  226. <p style="color:var(--text-1); font-size:1.08rem; line-height:1.82;">${heroLine}</p>
  227. </div>
  228. <div class="fade-up" style="margin-top:40px; display:grid; grid-template-columns:repeat(${Math.min(subs.length, 5)}, 1fr); gap:14px; max-width:1100px; margin-left:auto; margin-right:auto;">
  229. ${subs.map((sub, i) => `
  230. <div style="padding:16px 14px; background:var(--bg-1); border:1px solid ${c.color}22; border-radius:var(--radius-sm); text-align:center;">
  231. <div class="text-mono text-xs" style="color:${c.color}; letter-spacing:0.15em; margin-bottom:6px; font-weight:700;">${c.num}.${i + 1}</div>
  232. <div style="color:var(--text-1); font-size:0.86rem; line-height:1.5;">${esc(sub)}</div>
  233. </div>`).join('')}
  234. </div>
  235. <div class="fade-up" style="margin-top:48px; text-align:center;">
  236. <div class="text-mono text-xs" style="color:var(--text-3); letter-spacing:0.3em;">SCROLL DOWN · ${subs.length} 个子章 · VOC 实证 × 数据 × 决策</div>
  237. </div>
  238. </div>
  239. </section>`;
  240. }
  241. function renderComingSoon({ chapter, subIdx, sub, slideNum, slideTotal, targetBatch, meta }) {
  242. const c = chapter;
  243. const hs = LA.CHAPTER_HYPOTHESIS_MAP[c.key] || [];
  244. const hsCount = hs.reduce((s, h) => s + (meta?.hypotheses?.[h] || 0), 0);
  245. return `<section class="report-section" id="${c.key}-${subIdx}" data-tone="${c.tone}" data-slide-num="${slideNum}/${slideTotal}">
  246. <div class="section-inner">
  247. <div class="section-head rise-in">
  248. <div class="section-eyebrow" style="color:${c.color};">${c.num}.${subIdx} · ${esc((sub || '').split('·')[0].toUpperCase())}</div>
  249. <h2 class="text-h1"><span class="headline-anim">${esc(sub)}</span></h2>
  250. <p class="section-sub">此子章将在 <strong style="color:${c.color};">Batch ${targetBatch}</strong> 填充 VOC 证据卡 + 理论映射 + 决策项</p>
  251. </div>
  252. <div class="fade-up" style="margin-top:40px; padding:40px; background:${c.color}08; border:1px dashed ${c.color}55; border-radius:var(--radius-lg); text-align:center;">
  253. <div style="font-size:3.4rem; margin-bottom:18px;">🚧</div>
  254. <div class="text-h2" style="color:${c.color}; margin-bottom:12px;">Coming in Batch ${targetBatch}</div>
  255. ${hsCount ? `
  256. <div style="display:inline-flex; align-items:center; gap:10px; padding:8px 16px; background:${BRAND.JIANG_GREEN}12; color:${BRAND.JIANG_GREEN}; border:1px solid ${BRAND.JIANG_GREEN}44; border-radius:20px; margin-bottom:20px; font-family:var(--font-mono); font-size:0.82rem; font-weight:600;">
  257. <span>✓</span> 数据就绪 · ${hsCount} 条 ${hs.join('/')} 相关真实 VOC
  258. </div><br/>
  259. <p style="color:var(--text-2); font-size:0.92rem; max-width:680px; margin:0 auto; line-height:1.7;">
  260. docs/短剧变现/raw/_merged.json 已就绪 <strong style="color:${BRAND.LACTIC_MINT};">${meta?.comments || 0} 条真实 VOC</strong>,<br/>
  261. Batch ${targetBatch} 将基于本假设的 <strong style="color:${c.color};">${hsCount} 条</strong> 证据 + 理论工具,输出 <strong>证据卡 × 理论映射 × 决策项</strong>。
  262. </p>` : `
  263. <p style="color:var(--text-2); font-size:0.95rem; max-width:600px; margin:0 auto; line-height:1.7;">
  264. Batch ${targetBatch} 将基于已采集 VOC 数据填充本子章内容。
  265. </p>`}
  266. </div>
  267. </div>
  268. </section>`;
  269. }
  270. const SUB_TO_BATCH = {
  271. 'challenge': 2,
  272. 'track_voc': 2,
  273. 'product_voc': 2,
  274. 'persona_voc': 3,
  275. 'policy_voc': 3,
  276. 'roi_voc': 4,
  277. 'sop_voc': 4,
  278. 'matrix_voc': 5,
  279. };
  280. let SLIDE_RENDERERS = {};
  281. function tryRequire(modPath) {
  282. try { return require(modPath); } catch (e) { return null; }
  283. }
  284. const CH1 = tryRequire('./shortdrama-ch1-challenge.js');
  285. const CH2 = tryRequire('./shortdrama-ch2-track-voc.js');
  286. const CH3 = tryRequire('./shortdrama-ch3-product-voc.js');
  287. const CH4 = tryRequire('./shortdrama-ch4-persona-voc.js');
  288. const CH5 = tryRequire('./shortdrama-ch5-policy-voc.js');
  289. const CH6 = tryRequire('./shortdrama-ch6-roi-voc.js');
  290. const CH7 = tryRequire('./shortdrama-ch7-sop-voc.js');
  291. const CH8 = tryRequire('./shortdrama-ch8-matrix-voc.js');
  292. SLIDE_RENDERERS = {
  293. ...(CH1 || {}),
  294. ...(CH2 || {}),
  295. ...(CH3 || {}),
  296. ...(CH4 || {}),
  297. ...(CH5 || {}),
  298. ...(CH6 || {}),
  299. ...(CH7 || {}),
  300. ...(CH8 || {}),
  301. };
  302. function buildHTML({ product, collectedAt } = {}) {
  303. product = product || 'AI短视频起号变现';
  304. collectedAt = collectedAt || new Date().toISOString().slice(0, 10);
  305. const data = LA.loadMerged();
  306. const meta = LA.getMeta(data);
  307. let slideTotal = 2;
  308. for (const c of CHAPTERS) {
  309. slideTotal += 1 + (CHAPTER_SUBSECTIONS[c.key] || []).length;
  310. }
  311. const slides = [];
  312. let num = 1;
  313. slides.push(renderCover({ product, collectedAt, slideNum: sn(num), slideTotal: sn(slideTotal), meta }));
  314. num++;
  315. slides.push(renderAgenda({ slideNum: sn(num), slideTotal: sn(slideTotal) }));
  316. num++;
  317. for (const c of CHAPTERS) {
  318. slides.push(renderChapterDivider({ chapter: c, slideNum: sn(num), slideTotal: sn(slideTotal) }));
  319. num++;
  320. const subs = CHAPTER_SUBSECTIONS[c.key] || [];
  321. for (let i = 0; i < subs.length; i++) {
  322. const subIdx = i + 1;
  323. const subKey = `${c.key}-${subIdx}`;
  324. const renderer = SLIDE_RENDERERS[subKey];
  325. const slideCtx = {
  326. chapter: c,
  327. subIdx,
  328. sub: subs[i],
  329. slideNum: sn(num),
  330. slideTotal: sn(slideTotal),
  331. data,
  332. meta,
  333. };
  334. if (renderer) {
  335. slides.push(renderer(slideCtx));
  336. } else {
  337. slides.push(renderComingSoon({ ...slideCtx, targetBatch: SUB_TO_BATCH[c.key] || 2, meta }));
  338. }
  339. num++;
  340. }
  341. }
  342. return `<!DOCTYPE html>
  343. <html lang="zh-CN">
  344. <head>
  345. <meta charset="UTF-8"/>
  346. <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  347. <title>${esc(product)} · VOC 深度洞察报告</title>
  348. ${STYLES}
  349. </head>
  350. <body>
  351. ${renderNav()}
  352. ${renderKbdHint()}
  353. <div class="progress-line" id="progressLine"></div>
  354. <main class="deck">
  355. ${slides.join('\n')}
  356. </main>
  357. <script>${SCRIPTS}</script>
  358. </body>
  359. </html>`;
  360. }
  361. module.exports = {
  362. buildHTML,
  363. BRAND,
  364. CHAPTERS,
  365. CHAPTER_SUBSECTIONS,
  366. CHAPTER_HERO_LINES,
  367. SUB_TO_BATCH,
  368. renderCover,
  369. renderAgenda,
  370. renderChapterDivider,
  371. renderComingSoon,
  372. };