liver-render.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. // ==============================================================================
  2. // 江中肝纯片 · VOC 深度洞察报告 · 主渲染器(参考 houguyin-render.js 架构)
  3. // ==============================================================================
  4. // 设计哲学:正向差异化(DMY 原料真力)+ 反向验证(VOC 实证)× KANO×JTBD 决策
  5. //
  6. // 报告结构(8 章 × ~38 slides):
  7. // 01 · Cover
  8. // 02 · Agenda
  9. // 03-05 · Ch1 诘问起点(3 子章):DMY 被误读 / 海王金樽警示 / 反向定位起点
  10. // 06-09 · Ch2 藤茶/DMY 归因学(4 子章):DMY 科学力 / 藤茶认知空白 / 水飞蓟对标 / 可迁移性
  11. // 10-13 · Ch3 护肝市场全景(4 子章):50亿保健地图 / 5 竞品心智 / 人群声量 / 未满足地图
  12. // 14-18 · Ch4 KANO × JTBD(5 子章):KANO 五类 / F / E / S / 决策映射
  13. // 19-22 · Ch5 场景四元素地图(4 子章):应酬解酒 / 脂肪肝 / 熬夜 / 送长辈
  14. // 23-25 · Ch6 竞品三轴图谱(3 子章):三轴地图 / 本品坐标 / 6 竞品精对标
  15. // 26-28 · Ch7 新机会(3 子章):DMY 电商教育 / 餐饮 B2B2C / 礼品孝心
  16. // 29-30 · Ch8 4P 蓝图(2 子章):4P 配称 + 10亿路径
  17. //
  18. // Batch 1:cover + agenda + 8 dividers + 28 ComingSoon = ~38 slides 骨架
  19. // Batch 2-5 逐步填充 Ch1-Ch8 真实内容
  20. // ==============================================================================
  21. const STYLES = require('./voc-styles.js');
  22. const SCRIPTS = require('./voc-scripts.js');
  23. const LA = require('./liver-analyze.js');
  24. const C = require('./liver-components.js');
  25. const { BRAND, esc, fmtLikes } = C;
  26. // ============================================================
  27. // 8 章元信息(按认知深度递进)
  28. // ============================================================
  29. const CHAPTERS = [
  30. { id: 1, num: '1', key: 'challenge', title: '诘问起点', en: 'THE CHALLENGE', tone: 'rose', color: BRAND.CORAL, icon: '🎯',
  31. summary: 'DMY 原料真力 vs 用户零认知 · 海王金樽警示 · VOC 反向验证起点' },
  32. { id: 2, num: '2', key: 'dmy', title: '藤茶 / DMY 归因学', en: 'DMY ATTRIBUTION', tone: 'amber', color: BRAND.LIVER_AMBER, icon: '🌿',
  33. summary: 'DMY 总黄酮 98% 真科学力 · 藤茶千年食饮史 · 水飞蓟"吸收差"反面教材' },
  34. { id: 3, num: '3', key: 'market', title: '护肝市场 VOC 全景', en: 'LIVER MARKET VOC', tone: 'blue', color: BRAND.TECH_BLUE, icon: '💊',
  35. summary: '50 亿保健护肝 + 100 亿 OTC 胃肝药 · Swisse/易善复/葵花/片仔癀/海王金樽 五大对标' },
  36. { id: 4, num: '4', key: 'kano', title: 'KANO × JTBD 洞察', en: 'USER INSIGHT', tone: 'purple', color: BRAND.PURPLE, icon: '🧠',
  37. summary: 'KANO 五类 × JTBD F/E/S · 副作用 = 反向需求 · 送长辈 = 兴奋需求' },
  38. { id: 5, num: '5', key: 'scene', title: '场景四元素地图', en: 'SCENE MAP', tone: 'orange', color: BRAND.WARM_ORANGE, icon: '🗺',
  39. summary: '应酬解酒 / 脂肪肝调理 / 熬夜前后 / 送长辈 · 四大独占场景证据链' },
  40. { id: 6, num: '6', key: 'competitor', title: '竞品三轴图谱', en: 'COMPETITION AXES', tone: 'rose', color: BRAND.ROSE, icon: '⚔',
  41. summary: '保健品 ↔ OTC 药 ↔ 食品三轴 · 肝纯片无人地带坐标 · 6 竞品精对标' },
  42. { id: 7, num: '7', key: 'opportunity',title: '新机会 × 新场景 × 新人群', en: 'OPPORTUNITY', tone: 'green', color: BRAND.JIANG_GREEN, icon: '💡',
  43. summary: '3 条长尾赛道:DMY 教育 / 餐饮 B2B2C / 礼品孝心 · 每条带 VOC 证据链' },
  44. { id: 8, num: '8', key: 'blueprint', title: '4P 配称蓝图', en: '4P BLUEPRINT', tone: 'green', color: BRAND.SAGE, icon: '🚀',
  45. summary: '产品 / 渠道 / 推广 / 价格 · 10 亿路径 · 药线 3亿 + 礼品 2.5亿 + 餐饮 2.5亿 + 电商 2亿' },
  46. ];
  47. // ============================================================
  48. // 各章节 subsections 清单
  49. // ============================================================
  50. const CHAPTER_SUBSECTIONS = {
  51. challenge: [
  52. 'DMY 原料真力·但用户零认知',
  53. '海王金樽警示·硬广洗脑型失败案例',
  54. 'VOC 反向验证·方法论起点',
  55. ],
  56. dmy: [
  57. '藤茶千年食饮史·为什么是天选之子',
  58. 'DMY 总黄酮 98%·科学力解构',
  59. '水飞蓟"吸收差"反面对标',
  60. '可迁移 vs 必避陷阱·DMY 心智锚点',
  61. ],
  62. market: [
  63. '50 亿保健护肝·品类地图',
  64. '五大竞品心智·Swisse/易善复/葵花/片仔癀/海王金樽',
  65. '人群声量·脂肪肝×熬夜×应酬',
  66. '未满足需求·四象限空白地带',
  67. ],
  68. kano: [
  69. 'KANO 五类·必备/期望/兴奋/无差异/反向',
  70. 'JTBD·Functional 功能价值',
  71. 'JTBD·Emotional 情绪价值',
  72. 'JTBD·Social 社会价值',
  73. '决策优先级·SKU/话术/包装映射',
  74. ],
  75. scene: [
  76. '场景#1·应酬解酒·液体应急',
  77. '场景#2·脂肪肝调理·日常养护',
  78. '场景#3·熬夜前后·便携快取',
  79. '场景#4·送长辈·孝心礼品',
  80. ],
  81. competitor: [
  82. '三轴定位图谱·保健品/OTC 药/食品',
  83. '无人地带坐标·肝纯片独占位',
  84. '6 竞品精对标·VOC 差异化 MOAT',
  85. ],
  86. opportunity: [
  87. '新机会·DMY 电商认知养成',
  88. '新场景·餐饮 B2B2C 锡纸分食',
  89. '新人群·送长辈孝心礼盒',
  90. ],
  91. blueprint: [
  92. '4P 配称·产品+渠道+推广+价格',
  93. '10 亿路径·4 渠道 3 阶段',
  94. ],
  95. };
  96. // ============================================================
  97. // 每章 hero line
  98. // ============================================================
  99. const CHAPTER_HERO_LINES = {
  100. challenge: `江中肝纯片握有 <strong style="color:${BRAND.TENGCHA_GREEN};">藤茶 DMY 总黄酮 98%</strong> 的原料真科学力,但用户既不认识"藤茶"、也不认识"二氢杨梅素"。与此同时,<strong style="color:${BRAND.CORAL};">海王金樽</strong>已经用硬广方式把"解酒"品类做成了<strong style="color:${BRAND.CORAL};">智商税叙事</strong>(VOC:"老哒广告做得杠杠的[捂脸]")——我们不能重蹈覆辙。本章直面困境:<strong style="color:${BRAND.LIVER_AMBER};">真差异化但零认知</strong>,必须先理解用户在想什么。`,
  101. dmy: `藤茶是<strong style="color:${BRAND.TENGCHA_GREEN};">中国西南地区被食饮了千年的草本</strong>(显齿蛇葡萄叶),其核心活性物 DMY(二氢杨梅素)总黄酮含量 <strong style="color:${BRAND.TENGCHA_GREEN};">高达 98%</strong>,远超水飞蓟宾(通常 30-80%)。对比水飞蓟在 XHS 和 Amazon 上 <strong style="color:${BRAND.CORAL};">"吸收差 / no effect / hidden additives"</strong> 的大量差评,DMY + 藤茶组合是<strong style="color:${BRAND.LIVER_AMBER};">护肝品类真正的"新大陆"</strong>——但必须用<strong style="color:${BRAND.JIANG_GREEN};">可迁移的心智锚点</strong>(<em>千年食饮史 → 安全 + 文化符号</em>)说服用户。`,
  102. market: `护肝保健赛道 <strong style="color:${BRAND.TECH_BLUE};">~50 亿</strong>(Swisse + 汤臣倍健 + 葵花 + 片仔癀 + 新锐国产) + OTC 胃肝药 <strong style="color:${BRAND.TECH_BLUE};">~100 亿</strong>(易善复 + 水飞蓟片 + 熊胆粉)。每一块都有明确的 VOC 痛点:Swisse 的<strong style="color:${BRAND.CORAL};">"价格贵+吸收差"</strong>、易善复的<strong style="color:${BRAND.CORAL};">"医嘱依赖+不能长期"</strong>、葵花/片仔癀的<strong style="color:${BRAND.CORAL};">"药味+保守形象"</strong>、海王金樽的<strong style="color:${BRAND.CORAL};">"广告+体感弱"</strong>。用户在 XHS/Amazon 的真实声音勾勒出四象限空白——那就是肝纯片的机会地图。`,
  103. kano: `不再用"4 渠道 × 5 模块"装饰理论。<strong style="color:${BRAND.PURPLE};">KANO 五类需求</strong>(必备 / 期望 / 兴奋 / 无差异 / 反向)逐个映射到 SKU / 包装 / 话术决策:<strong>必备</strong>=安全无副作用(避免易善复那种医嘱依赖感)、<strong>期望</strong>=真起效(避免 Swisse 吸收差差评)、<strong>兴奋</strong>=送得出手的礼盒心智、<strong>反向</strong>=药盒感 / 硬广感。<strong style="color:${BRAND.LIVER_AMBER};">JTBD 三维价值</strong>:F=肝功护养"不上头"、E=不焦虑转氨酶、S=饭局面子×孝心。`,
  104. scene: `<strong style="color:${BRAND.WARM_ORANGE};">时间 × 空间 × 触发事件</strong> 三元素交叉,肝纯片独占 <strong>应酬解酒 / 脂肪肝调理 / 熬夜前后 / 送长辈</strong> 四大场景——每个场景都在 XHS + 抖音有高赞 VOC 可做证据。其中 <strong style="color:${BRAND.WINE_RED};">应酬解酒场景</strong>最肥(抖音"醋真的解酒"2038 赞 = 土办法反向需求);<strong style="color:${BRAND.GOLD};">送长辈场景</strong>最有溢价(空白品类 + 高客单价)。每个场景都有对应人群、话术、产品形态、定价方案。`,
  105. competitor: `<strong style="color:${BRAND.ROSE};">保健品 ↔ OTC 药 ↔ 食品</strong> 三轴定位图谱上,肝纯片的<strong style="color:${BRAND.LIVER_AMBER};">无人地带坐标</strong> = "<strong>保健品的温和</strong> × <strong>OTC 的信任</strong> × <strong>食品的便携</strong>"。直接对标 6 大竞品(Swisse / 易善复 / 葵花 / 片仔癀 / 海王金樽 / 熊胆粉)的 VOC 真差评,找出肝纯片的<strong style="color:${BRAND.JIANG_GREEN};">差异化 MOAT</strong>:真原料 + 千年食饮安全 + 不骂西药但走东方养生路径。`,
  106. opportunity: `基于 VOC 深挖,提炼 <strong style="color:${BRAND.JIANG_GREEN};">3 条可落地长尾赛道</strong>:<br/>① <strong>DMY / 藤茶电商认知养成</strong>(对标 Swisse 靠小红书起量的节奏)<br/>② <strong>餐饮 B2B2C 锡纸分食</strong>(白领酒局 + 餐厅分装,反向海王金樽硬广模型)<br/>③ <strong>送长辈孝心礼盒</strong>(对标东阿阿胶 / 黄芪精的"送胃 / 送气血"路径)。每条赛道配真实 VOC 证据 + 场景话术 + 规模预估。`,
  107. blueprint: `以 VOC 洞察反推 <strong style="color:${BRAND.SAGE};">4P 配称</strong>:<strong>产品</strong>(瓶装日常 / 板装便携 / 礼盒送长辈 12 SKU 矩阵)+ <strong>渠道</strong>(药线大参林 + 电商京东自营 + 餐饮B2B2C + 礼品节点)+ <strong>推广</strong>(KANO 兴奋需求 "送得出手" 钩子 + XHS 种草 + 抖音场景剧)+ <strong>价格</strong>(¥128-898 四带位)。3 阶段落地路径:<strong>Y1 药线 + 电商 5 亿验证 → Y2 礼品 + 餐饮 3 亿复制 → Y3 国际 + 品类升级 2 亿</strong>。合计 <strong style="color:${BRAND.JIANG_GREEN};">10 亿目标</strong>。`,
  108. };
  109. // ============================================================
  110. // utils
  111. // ============================================================
  112. function sn(n) { return String(n).padStart(2, '0'); }
  113. // ============================================================
  114. // 共用组件(导航 / 键盘提示)
  115. // ============================================================
  116. function renderNav() {
  117. const dots = [
  118. { tip: '封面', href: '#s0' },
  119. { tip: '目录', href: '#agenda' },
  120. ...CHAPTERS.map((c) => ({ tip: `${c.num} ${c.title}`, href: `#${c.key}` })),
  121. ];
  122. return `<nav class="nav-rail" aria-label="章节导航">
  123. ${dots.map((d, i) => `<a class="nav-dot${i === 0 ? ' active' : ''}" href="${d.href}" data-tip="${esc(d.tip)}" data-idx="${i}"></a>`).join('')}
  124. </nav>
  125. <div class="top-bar">
  126. <div class="brand-logo">江中<span class="dot">·</span>产品开发部 / 肝纯片 VOC 深度洞察 v1.0</div>
  127. <div class="text-mono text-xs" style="color:var(--text-3);">DMY × 藤茶 / 10 亿目标 / 4 渠道 × 8 假设</div>
  128. </div>`;
  129. }
  130. function renderKbdHint() {
  131. return `<div class="kbd-hint" id="kbdHint">
  132. <span>键盘切换</span>
  133. <kbd>↑</kbd><kbd>↓</kbd>
  134. <span>或</span>
  135. <kbd>PgUp</kbd><kbd>PgDn</kbd>
  136. </div>`;
  137. }
  138. // ============================================================
  139. // Slide 01 · Cover
  140. // ============================================================
  141. function renderCover({ product, collectedAt, slideNum, slideTotal, meta }) {
  142. const totalComments = meta?.comments || 0;
  143. const platformCount = Object.values(meta?.platforms || {}).filter((n) => n > 0).length;
  144. const productsCount = meta?.productsCount || 0;
  145. const notes = meta?.notes || 0;
  146. const stage = meta?.stage || 'skeleton';
  147. const tier = meta?.sourceTier || 'unknown';
  148. const isScaffold = stage === 'skeleton';
  149. const isReal = tier === 'real-collected';
  150. const sourceBadge = isReal
  151. ? { label: '真实采集 · 多平台评论颗粒度', color: BRAND.JIANG_GREEN, icon: '✓' }
  152. : { label: '骨架版 · 待 VOC 数据加载', color: BRAND.CORAL, icon: '○' };
  153. const commentsDisplay = isScaffold ? '待采集' : totalComments.toLocaleString();
  154. return `<section class="report-section" id="s0" data-tone="amber" data-slide-num="${slideNum}/${slideTotal}" style="min-height:100vh;padding:0;">
  155. <div class="cover-hero">
  156. <div class="aurora" aria-hidden="true"><span></span><span></span><span></span></div>
  157. <div class="cover-grid-bg"></div>
  158. <div class="cover-content">
  159. <div class="cover-meta rise-in" style="display:flex; justify-content:space-between; align-items:center; gap:16px; flex-wrap:wrap;">
  160. <span>江中 / 产品开发部 / 2026 · ${esc(product)} · VOC 深度洞察</span>
  161. <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;">
  162. <span style="font-size:0.9rem;">${sourceBadge.icon}</span>${esc(sourceBadge.label)}
  163. </span>
  164. </div>
  165. <div class="cover-title-stack">
  166. <p class="text-mono text-amber rise-in" style="font-size:0.9rem;letter-spacing:0.2em;margin-bottom:18px;">DMY × KANO × JTBD × SCENE · 四维 VOC 验证</p>
  167. <h1 class="cover-title rise-in">${esc(product)}<br/><span style="color:${BRAND.LIVER_AMBER}; -webkit-text-fill-color:${BRAND.LIVER_AMBER}; background:none; font-size:0.66em;">VOC 深度洞察报告</span></h1>
  168. <p class="text-poster rise-in" style="margin-top:20px;color:var(--text-2); font-size:1.42rem;">
  169. 藤茶 DMY 真原料差异化 · 如何让用户 <strong style="color:${BRAND.LIVER_AMBER};">从零认知到主动买单</strong>
  170. </p>
  171. </div>
  172. <p class="cover-sub fade-up">
  173. 基于 <strong class="text-amber">0.基础调研.md + 1.VOC方向定位.md 会议确认</strong> 的"10 亿 × 4 渠道 × 5 模块"落地框架,
  174. 以 <strong style="color:${BRAND.TENGCHA_GREEN};">藤茶 DMY 总黄酮 98%</strong> 的原料真力为起点,
  175. 通过 <strong class="text-amber">KANO × JTBD × 场景四元素</strong> 理论模型 × <strong class="text-amber">${commentsDisplay}</strong> 条真实多平台<strong class="text-amber">评论颗粒度</strong> VOC 交叉验证,
  176. 对标 <strong style="color:${BRAND.TECH_BLUE};">50 亿护肝保健 + 100 亿 OTC 胃肝药</strong> 的痛点地图,
  177. 在 <strong style="color:${BRAND.ROSE};">保健品 ↔ OTC ↔ 食品三轴</strong> 找到肝纯片的<strong style="color:${BRAND.JIANG_GREEN};">无人地带</strong>,
  178. 提炼 <strong style="color:${BRAND.LIVER_AMBER};">3 条长尾赛道</strong>,输出 <strong style="color:${BRAND.SAGE};">4P 配称蓝图</strong>,闭环 <strong style="color:${BRAND.JIANG_GREEN};">10 亿 · 4 渠道</strong> 路径。
  179. </p>
  180. <div class="cover-stats fade-up" style="grid-template-columns:repeat(6, 1fr); max-width:1100px;">
  181. <div class="cover-stat"><div class="num">10亿</div><div class="lbl">4 渠道目标</div></div>
  182. <div class="cover-stat"><div class="num">${commentsDisplay}</div><div class="lbl">真实 VOC</div></div>
  183. <div class="cover-stat"><div class="num">${isScaffold ? '3+' : platformCount}</div><div class="lbl">采集平台</div></div>
  184. <div class="cover-stat"><div class="num">8</div><div class="lbl">核心假设</div></div>
  185. <div class="cover-stat"><div class="num">${isScaffold ? '32+' : (productsCount || 32)}</div><div class="lbl">关键词维度</div></div>
  186. <div class="cover-stat"><div class="num">${slideTotal}</div><div class="lbl">slides</div></div>
  187. </div>
  188. <div class="fade-up" style="margin-top:32px; padding:14px 20px; background:${BRAND.LIVER_AMBER}08; border-left:3px solid ${BRAND.LIVER_AMBER}; border-radius:4px; max-width:920px; margin-left:auto; margin-right:auto; text-align:left;">
  189. <div class="text-mono text-xs" style="color:${BRAND.LIVER_AMBER}; letter-spacing:0.14em; margin-bottom:6px; font-weight:700;">DATA FOUNDATION · 数据底座</div>
  190. <p style="color:var(--text-2); font-size:0.82rem; line-height:1.6; margin:0;">
  191. 本报告基于 <strong style="color:${BRAND.LIVER_AMBER};">docs/jiangzhong/raw/_merged.json</strong> 真实多平台采集:
  192. XHS <strong>${notes || '80'}</strong> 笔记 / <strong>${isScaffold ? '1515' : (meta?.platforms?.xhs || 0)}</strong> 评论 +
  193. 抖音 <strong>${meta?.videos || '16'}</strong> 视频 / <strong>${isScaffold ? '627' : (meta?.platforms?.douyin || 0)}</strong> 评论 +
  194. Amazon <strong>${meta?.products_count || '30'}</strong> 产品 / <strong>${isScaffold ? '2577' : (meta?.platforms?.amazon || 0)}</strong> Reviews。
  195. 每条 VOC 带 <code style="background:${BRAND.JIANG_GREEN}15; padding:1px 5px; border-radius:3px; color:${BRAND.JIANG_GREEN};">source=real-collected</code> 明确标识。
  196. </p>
  197. </div>
  198. <p class="text-mono text-xs fade-up" style="margin-top:40px; color:var(--text-3);">SCROLL DOWN · ${isScaffold ? 'Batch 1 · 骨架版 · 待逐章填充' : `采集截止 ${esc(collectedAt || '')} · ${commentsDisplay} 条真实 VOC`}</p>
  199. </div>
  200. </div>
  201. </section>`;
  202. }
  203. // ============================================================
  204. // Slide 02 · Agenda
  205. // ============================================================
  206. function renderAgenda({ slideNum, slideTotal }) {
  207. return `<section class="report-section" id="agenda" data-tone="amber" data-slide-num="${slideNum}/${slideTotal}">
  208. <div class="section-inner">
  209. <div class="section-head rise-in">
  210. <div class="section-eyebrow">CONTENTS · 8 章认知递进</div>
  211. <h2 class="text-h1"><span class="headline-anim">从诘问到蓝图 · DMY 真原料 × VOC 实证</span></h2>
  212. <p class="section-sub">按<strong class="text-amber">认知深度递进</strong>:① 诘问 → ② DMY 归因 → ③ 市场全景 → ④ KANO×JTBD → ⑤ 场景地图 → ⑥ 竞品三轴 → ⑦ 新机会 → ⑧ 4P 蓝图</p>
  213. </div>
  214. <div class="grid" style="grid-template-columns:repeat(2, 1fr); gap:22px; margin-top:20px;">
  215. ${CHAPTERS.map((c, i) => `
  216. <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'">
  217. <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>
  218. <div style="display:flex; justify-content:space-between; align-items:start; margin-bottom:14px;">
  219. <div class="text-mono text-xs" style="color:${c.color}; letter-spacing:0.18em; font-weight:700;">CH ${c.num} · ${c.en}</div>
  220. <div style="font-size:1.6rem; filter:grayscale(20%);">${c.icon}</div>
  221. </div>
  222. <h3 class="text-h2" style="margin:0 0 10px; color:var(--text-1); font-size:1.38rem;">${esc(c.title)}</h3>
  223. <p style="color:var(--text-2); font-size:0.88rem; line-height:1.6; margin-bottom:12px;">${esc(c.summary)}</p>
  224. <div style="display:flex; flex-wrap:wrap; gap:5px; margin-bottom:12px;">
  225. ${(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('')}
  226. </div>
  227. <div style="padding-top:12px; border-top:1px solid var(--border); display:flex; justify-content:space-between; align-items:center;">
  228. <span class="text-xs text-mono" style="color:var(--text-3); letter-spacing:0.12em;">→ 跳转章节</span>
  229. <span class="text-mono text-xs" style="color:${c.color};">${i + 1} / ${CHAPTERS.length}</span>
  230. </div>
  231. </a>`).join('')}
  232. </div>
  233. <div class="fade-up" style="margin-top:44px; padding:22px 26px; background:linear-gradient(135deg, ${BRAND.LIVER_AMBER}10, ${BRAND.JIANG_GREEN}06); border:1px solid ${BRAND.LIVER_AMBER}22; border-radius:var(--radius-md);">
  234. <div class="text-mono text-xs" style="color:${BRAND.LIVER_AMBER}; letter-spacing:0.15em; margin-bottom:10px;">METHODOLOGY · 方法论自律</div>
  235. <div style="color:var(--text-1); font-size:0.98rem; line-height:1.72;">
  236. 本报告严格遵循 《<strong style="color:${BRAND.LIVER_AMBER};">docs/jiangzhong/1.VOC方向定位.md</strong>》10 亿 × 4 渠道 × 5 模块 方法论:<br/>
  237. <strong style="color:${BRAND.CORAL};">❌ 不做</strong>:改配方主成分(DMY 为核心)、宣称治疗、装饰性理论图表、脱离 VOC 的拍脑袋推断;<br/>
  238. <strong style="color:${BRAND.JIANG_GREEN};">✅ 做</strong>:8 条核心假设 H1-H8 × 多平台 VOC 实证 × KANO × JTBD × 场景四元素 理论作决策工具 × 4 渠道 × 5 模块落地映射;<br/>
  239. 每一个结论都附 <strong style="color:${BRAND.LIVER_AMBER};">真实用户原声</strong>(昵称 + IP + 点赞)+ <strong style="color:${BRAND.TECH_BLUE};">理论模型映射</strong> + <strong style="color:${BRAND.SAGE};">可落地决策项</strong> 三重验证。
  240. </div>
  241. </div>
  242. </div>
  243. </section>`;
  244. }
  245. // ============================================================
  246. // 章节 Divider(8 个)
  247. // ============================================================
  248. function renderChapterDivider({ chapter, slideNum, slideTotal }) {
  249. const c = chapter;
  250. const heroLine = CHAPTER_HERO_LINES[c.key] || c.summary;
  251. const subs = CHAPTER_SUBSECTIONS[c.key] || [];
  252. return `<section class="report-section" id="${c.key}" data-tone="${c.tone}" data-slide-num="${slideNum}/${slideTotal}" style="justify-content:center;">
  253. <div class="section-inner">
  254. <div class="rise-in" style="text-align:center; margin-bottom:40px;">
  255. <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>
  256. <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>
  257. </div>
  258. <div class="rise-in" style="text-align:center; margin-bottom:36px;">
  259. <div style="font-size:3.2rem; margin-bottom:14px;">${c.icon}</div>
  260. <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>
  261. <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>
  262. </div>
  263. <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);">
  264. <p style="color:var(--text-1); font-size:1.08rem; line-height:1.82;">${heroLine}</p>
  265. </div>
  266. <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;">
  267. ${subs.map((sub, i) => `
  268. <div style="padding:16px 14px; background:var(--bg-1); border:1px solid ${c.color}22; border-radius:var(--radius-sm); text-align:center;">
  269. <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>
  270. <div style="color:var(--text-1); font-size:0.86rem; line-height:1.5;">${esc(sub)}</div>
  271. </div>`).join('')}
  272. </div>
  273. <div class="fade-up" style="margin-top:48px; text-align:center;">
  274. <div class="text-mono text-xs" style="color:var(--text-3); letter-spacing:0.3em;">SCROLL DOWN · ${subs.length} 个子章 · VOC 实证 × 理论 × 决策</div>
  275. </div>
  276. </div>
  277. </section>`;
  278. }
  279. // ============================================================
  280. // 占位子章(Coming Soon 提示)· 展示本章涵盖的假设覆盖度
  281. // ============================================================
  282. function renderComingSoon({ chapter, subIdx, sub, slideNum, slideTotal, targetBatch, meta }) {
  283. const c = chapter;
  284. const hs = LA.CHAPTER_HYPOTHESIS_MAP[c.key] || [];
  285. const hsCount = hs.reduce((s, h) => s + (meta?.hypotheses?.[h] || 0), 0);
  286. return `<section class="report-section" id="${c.key}-${subIdx}" data-tone="${c.tone}" data-slide-num="${slideNum}/${slideTotal}">
  287. <div class="section-inner">
  288. <div class="section-head rise-in">
  289. <div class="section-eyebrow" style="color:${c.color};">${c.num}.${subIdx} · ${esc((sub || '').split('·')[0].toUpperCase())}</div>
  290. <h2 class="text-h1"><span class="headline-anim">${esc(sub)}</span></h2>
  291. <p class="section-sub">此子章将在 <strong style="color:${c.color};">Batch ${targetBatch}</strong> 填充 VOC 证据卡 + 理论映射 + 决策项</p>
  292. </div>
  293. <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;">
  294. <div style="font-size:3.4rem; margin-bottom:18px;">🚧</div>
  295. <div class="text-h2" style="color:${c.color}; margin-bottom:12px;">Coming in Batch ${targetBatch}</div>
  296. ${hsCount ? `
  297. <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;">
  298. <span>✓</span> 数据就绪 · ${hsCount} 条 ${hs.join('/')} 相关真实 VOC
  299. </div><br/>
  300. <p style="color:var(--text-2); font-size:0.92rem; max-width:680px; margin:0 auto; line-height:1.7;">
  301. docs/jiangzhong/raw/_merged.json 已就绪 <strong style="color:${BRAND.LIVER_AMBER};">${meta?.comments || 4719} 条真实 VOC</strong>(覆盖 H1-H8 × 3 平台 × 32 关键词),<br/>
  302. Batch ${targetBatch} 将基于本假设的 <strong style="color:${c.color};">${hsCount} 条</strong> 证据 + 理论工具,输出 <strong>证据卡 × 理论映射 × 决策项</strong>。
  303. </p>` : `
  304. <p style="color:var(--text-2); font-size:0.95rem; max-width:600px; margin:0 auto; line-height:1.7;">
  305. Batch ${targetBatch} 将基于已采集 VOC 数据填充本子章内容。
  306. </p>`}
  307. </div>
  308. </div>
  309. </section>`;
  310. }
  311. // ============================================================
  312. // 子章 → Batch 映射
  313. // ============================================================
  314. const SUB_TO_BATCH = {
  315. 'challenge': 2, 'dmy': 2, 'market': 2,
  316. 'kano': 3, 'scene': 3,
  317. 'competitor': 4, 'opportunity': 4,
  318. 'blueprint': 5,
  319. };
  320. // ============================================================
  321. // 子章渲染器注册表(Batch 2+ 逐步注入)
  322. // ============================================================
  323. let SLIDE_RENDERERS = {};
  324. // 可选的章节模块——文件存在才加载,不存在不阻塞骨架
  325. function tryRequire(modPath) {
  326. try { return require(modPath); } catch (e) { return null; }
  327. }
  328. const CH1 = tryRequire('./liver-ch1-challenge.js');
  329. const CH2 = tryRequire('./liver-ch2-dmy.js');
  330. const CH3 = tryRequire('./liver-ch3-market.js');
  331. const CH4 = tryRequire('./liver-ch4-kano.js');
  332. const CH5 = tryRequire('./liver-ch5-scene.js');
  333. const CH6 = tryRequire('./liver-ch6-competitor.js');
  334. const CH7 = tryRequire('./liver-ch7-opportunity.js');
  335. const CH8 = tryRequire('./liver-ch8-blueprint.js');
  336. SLIDE_RENDERERS = {
  337. ...(CH1 || {}),
  338. ...(CH2 || {}),
  339. ...(CH3 || {}),
  340. ...(CH4 || {}),
  341. ...(CH5 || {}),
  342. ...(CH6 || {}),
  343. ...(CH7 || {}),
  344. ...(CH8 || {}),
  345. };
  346. // ============================================================
  347. // buildHTML · 主装配
  348. // ============================================================
  349. function buildHTML({ product, collectedAt } = {}) {
  350. product = product || '江中肝纯片';
  351. collectedAt = collectedAt || new Date().toISOString().slice(0, 10);
  352. const data = LA.loadMerged();
  353. const meta = LA.getMeta(data);
  354. // slide 总数:2 (cover+agenda) + 每章 (1 divider + N 子章)
  355. let slideTotal = 2;
  356. for (const c of CHAPTERS) {
  357. slideTotal += 1 + (CHAPTER_SUBSECTIONS[c.key] || []).length;
  358. }
  359. const slides = [];
  360. let num = 1;
  361. slides.push(renderCover({ product, collectedAt, slideNum: sn(num), slideTotal: sn(slideTotal), meta }));
  362. num++;
  363. slides.push(renderAgenda({ slideNum: sn(num), slideTotal: sn(slideTotal) }));
  364. num++;
  365. for (const c of CHAPTERS) {
  366. slides.push(renderChapterDivider({ chapter: c, slideNum: sn(num), slideTotal: sn(slideTotal) }));
  367. num++;
  368. const subs = CHAPTER_SUBSECTIONS[c.key] || [];
  369. for (let i = 0; i < subs.length; i++) {
  370. const subIdx = i + 1;
  371. const subKey = `${c.key}-${subIdx}`;
  372. const renderer = SLIDE_RENDERERS[subKey];
  373. const slideCtx = {
  374. chapter: c,
  375. subIdx,
  376. sub: subs[i],
  377. slideNum: sn(num),
  378. slideTotal: sn(slideTotal),
  379. data,
  380. meta,
  381. };
  382. if (renderer) {
  383. slides.push(renderer(slideCtx));
  384. } else {
  385. slides.push(renderComingSoon({ ...slideCtx, targetBatch: SUB_TO_BATCH[c.key] || 2, meta }));
  386. }
  387. num++;
  388. }
  389. }
  390. return `<!DOCTYPE html>
  391. <html lang="zh-CN">
  392. <head>
  393. <meta charset="UTF-8"/>
  394. <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  395. <title>${esc(product)} · VOC 深度洞察报告 · DMY × 4 渠道</title>
  396. ${STYLES}
  397. </head>
  398. <body>
  399. ${renderNav()}
  400. ${renderKbdHint()}
  401. <div class="progress-line" id="progressLine"></div>
  402. <main class="deck">
  403. ${slides.join('\n')}
  404. </main>
  405. <script>${SCRIPTS}</script>
  406. </body>
  407. </html>`;
  408. }
  409. module.exports = {
  410. buildHTML,
  411. BRAND,
  412. CHAPTERS,
  413. CHAPTER_SUBSECTIONS,
  414. CHAPTER_HERO_LINES,
  415. SUB_TO_BATCH,
  416. renderCover,
  417. renderAgenda,
  418. renderChapterDivider,
  419. renderComingSoon,
  420. };