lactic-render.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. // ==============================================================================
  2. // 江中乳酸菌素片儿童版 · VOC 深度洞察报告 · 主渲染器
  3. // ==============================================================================
  4. // 01 · Cover
  5. // 02 · Agenda
  6. // 03-05 · Ch1 诘问起点(3 子章)
  7. // 06-09 · Ch2 药品益生菌 VOC 反面教材(4 子章)
  8. // 10-13 · Ch3 食品益生菌 VOC 矛盾盘(4 子章)
  9. // 14-18 · Ch4 KANO × JTBD 双视角(5 子章)
  10. // 19-22 · Ch5 场景四元素地图(4 子章)
  11. // 23-25 · Ch6 三轴图谱 × 无人地带(3 子章)
  12. // 26-28 · Ch7 新机会 × 新人群(3 子章)
  13. // 29-30 · Ch8 4P 配称蓝图(2 子章)
  14. // 总计 38 slides
  15. // ==============================================================================
  16. const STYLES = require('./voc-styles.js');
  17. const SCRIPTS = require('./voc-scripts.js');
  18. const LA = require('./lactic-analyze.js');
  19. const C = require('./lactic-components.js');
  20. const { BRAND, esc, fmtLikes } = C;
  21. // ============================================================
  22. // 8 章元信息
  23. // ============================================================
  24. const CHAPTERS = [
  25. { id: 1, num: '1', key: 'challenge', title: '诘问起点', en: 'THE CHALLENGE', tone: 'rose', color: BRAND.CORAL, icon: '🎯',
  26. summary: '6000万→10亿 · 差异化定位的起点 · 为什么只加"娃娃头"不够' },
  27. { id: 2, num: '2', key: 'drug_voc', title: '药品益生菌 VOC 反面教材', en: 'RX PROBIOTIC VOC', tone: 'blue', color: BRAND.TECH_BLUE, icon: '💊',
  28. summary: '妈咪爱/亿活/金双岐痛点全扫描 · 冷链/处方/依赖焦虑' },
  29. { id: 3, num: '3', key: 'food_voc', title: '食品益生菌 VOC 矛盾盘', en: 'FOOD PROBIOTIC VOC',tone: 'amber', color: BRAND.SUNNY_YELLOW, icon: '🍬',
  30. summary: '合生元/万益蓝/拜奥/inne 家长"怕没效果"的信任缺口' },
  31. { id: 4, num: '4', key: 'kano', title: 'KANO × JTBD 双视角', en: 'USER INSIGHT', tone: 'purple', color: BRAND.PURPLE, icon: '🧠',
  32. summary: '家长 × 儿童双决策链 · F/E/S 三维价值' },
  33. { id: 5, num: '5', key: 'scene', title: '场景四元素地图', en: 'SCENE MAP', tone: 'orange', color: BRAND.WARM_ORANGE, icon: '🗺',
  34. summary: '入园/抗生素后/挑食/换季 · 四大高频触发场景' },
  35. { id: 6, num: '6', key: 'competitor', title: '三轴图谱 × 无人地带', en: 'COMPETITION AXES', tone: 'rose', color: BRAND.ROSE, icon: '⚔',
  36. summary: '药品 / 食品 / 营养软糖 三轴 · 乳酸菌素片独占坐标' },
  37. { id: 7, num: '7', key: 'opportunity',title: '新机会 × 新人群', en: 'OPPORTUNITY', tone: 'green', color: BRAND.JIANG_GREEN, icon: '💡',
  38. summary: '分龄产品线 / 内源吸收赛道 / 包装差异化三大破局' },
  39. { id: 8, num: '8', key: 'blueprint', title: '4P 配称蓝图', en: '4P BLUEPRINT', tone: 'green', color: BRAND.LACTIC_MINT, icon: '🚀',
  40. summary: '产品/渠道/推广/价格 · 6000万→10亿 三年路径' },
  41. ];
  42. // ============================================================
  43. // 各章节 subsections 清单
  44. // ============================================================
  45. const CHAPTER_SUBSECTIONS = {
  46. challenge: [
  47. '现状困境·6000万规模 × 0 差异化',
  48. '10亿目标·药品/食品/软糖三轴破局',
  49. 'VOC 反向验证·家长 × 儿童双视角起点',
  50. ],
  51. drug_voc: [
  52. '妈咪爱·冷链活菌 × 处方门槛痛点',
  53. '亿活/金双岐·依赖焦虑 × 停药反弹',
  54. '喂药难·口感苦 × 不独立包装',
  55. '药品益生菌 GAP·乳酸菌素片切入点',
  56. ],
  57. food_voc: [
  58. '合生元·食品级 × 家长怀疑效果',
  59. '万益蓝/拜奥·菌株混乱 × 价格贵',
  60. 'inne 噗噗宝·便秘单点但单薄',
  61. '食品益生菌 GAP·OTC 信任空白',
  62. ],
  63. kano: [
  64. 'KANO 五类·必备/期望/兴奋/无差异/反向',
  65. 'JTBD·家长 Functional 功能价值',
  66. 'JTBD·家长 Emotional 情绪价值',
  67. 'JTBD·儿童 × 家长 Social 社会价值',
  68. '决策优先级·SKU / 话术 / 包装映射',
  69. ],
  70. scene: [
  71. '场景#1·入园生病·集体生活综合征',
  72. '场景#2·抗生素后·肠道修复刚需',
  73. '场景#3·挑食积食·日常消化养护',
  74. '场景#4·换季腹泻·季节性防护',
  75. ],
  76. competitor: [
  77. '三轴图谱·药品 / 食品 / 营养软糖',
  78. '无人地带坐标·乳酸菌素片独占位',
  79. '8 竞品精对标·VOC 差异化 MOAT',
  80. ],
  81. opportunity: [
  82. '新机会·分龄产品线(0-3 / 3-6 / 6-12)',
  83. '新赛道·告别瓶瓶罐罐内源吸收',
  84. '新包装·卡通 IP × 咀嚼片革命',
  85. ],
  86. blueprint: [
  87. '4P 配称·产品 + 渠道 + 推广 + 价格',
  88. '10 亿路径·3 年 × 4 渠道分阶段',
  89. ],
  90. };
  91. // ============================================================
  92. // 每章 hero line
  93. // ============================================================
  94. const CHAPTER_HERO_LINES = {
  95. challenge: `江中乳酸菌素片儿童版当前规模 <strong style="color:${BRAND.CORAL};">线下 6000 万 + 线上 400-500 万</strong>,与成人版唯一区别是<strong style="color:${BRAND.CORAL};">"包装上加了个娃娃头"</strong>。妈咪爱线下 3.6 亿 + 线上 3848 万,合生元益生菌全家桶 5 亿+。我们不能再把儿童当"成人减量版"卖。本章直面困境:<strong style="color:${BRAND.LACTIC_MINT};">没有儿童专属定位 / 没有专属场景 / 没有家长非买不可的理由</strong>,VOC 反向验证是唯一出路。`,
  96. drug_voc: `药品益生菌(<strong style="color:${BRAND.TECH_BLUE};">妈咪爱 / 亿活 / 金双岐</strong>)是家长"孩子生病时想起的那个药",但也背负着三大痛点:<strong style="color:${BRAND.CORAL};">冷链储存麻烦</strong>、<strong style="color:${BRAND.CORAL};">必须医生处方才敢用</strong>、<strong style="color:${BRAND.CORAL};">停药就反弹的依赖焦虑</strong>。本章逐一解剖妈咪爱、亿活、金双岐的 XHS / 抖音真实家长吐槽,找出乳酸菌素片<strong style="color:${BRAND.LACTIC_MINT};">"非活菌常温稳定 × OTC 无需处方 × 可日常长期"</strong>的差异化切入点。`,
  97. food_voc: `食品益生菌(<strong style="color:${BRAND.SUNNY_YELLOW};">合生元 / 万益蓝 / 拜奥 / inne / 小胖瓶 / 康萃乐</strong>)靠果味 + 卡通包装 + 颜值营销在小红书起量,但也背负着家长最大的信任焦虑:<strong style="color:${BRAND.CORAL};">"没有 OTC 药品背书 / 菌株效果不稳定 / 价格贵但不确定值不值"</strong>。本章解剖食品益生菌的 VOC 矛盾盘——家长给孩子吃得多,但心里不踏实。乳酸菌素片的 <strong style="color:${BRAND.LACTIC_MINT};">OTC 蓝帽子背书</strong> 是这个赛道里的天然信任壁垒。`,
  98. kano: `儿童产品不是单人决策,是 <strong style="color:${BRAND.PURPLE};">家长 × 儿童双引擎</strong>:家长决定买什么、孩子决定吃不吃。<strong style="color:${BRAND.PURPLE};">KANO 五类需求</strong>(必备=安全OTC / 期望=见效 / 兴奋=分龄礼盒 / 反向=药盒感)映射到 SKU/包装/话术。<strong style="color:${BRAND.LACTIC_MINT};">JTBD 三维价值</strong>:Functional(解决便秘/积食/抗生素后)、Emotional(家长不焦虑带娃)、Social(妈妈圈专业感 + 孩子被羡慕)。`,
  99. scene: `<strong style="color:${BRAND.WARM_ORANGE};">时间 × 空间 × 触发事件 × 人群</strong> 四元素交叉,乳酸菌素片独占 <strong>入园生病 / 抗生素后 / 挑食积食 / 换季腹泻</strong> 四大高频场景——每个场景都在 XHS + 抖音有高赞家长 VOC 可做证据。其中<strong style="color:${BRAND.BERRY_PINK};">入园季(9月)+ 开学季(3月)</strong>是全年两个消费高峰,带独特人群(新手妈妈 + 3-6 岁宝宝)和独家话术模板。`,
  100. competitor: `<strong style="color:${BRAND.ROSE};">药品益生菌 ↔ 食品益生菌 ↔ 营养软糖</strong> 三轴定位图谱上,乳酸菌素片的<strong style="color:${BRAND.LACTIC_MINT};">无人地带坐标</strong> = "<strong>药品级 OTC 背书</strong> × <strong>食品级日常安全</strong> × <strong>营养吸收源头改善</strong>"。直接对标 8 大竞品(妈咪爱 / 亿活 / 金双岐 / 合生元 / 万益蓝 / 拜奥 / inne / 康萃乐)的 VOC 真痛点,找出乳酸菌素片的<strong style="color:${BRAND.JIANG_GREEN};">差异化 MOAT</strong>。`,
  101. opportunity: `基于 VOC 深挖,提炼 <strong style="color:${BRAND.JIANG_GREEN};">3 条长尾赛道</strong>:<br/>① <strong>分龄产品线</strong>(0-3 颗粒 / 3-6 水果咀嚼片 / 6-12 加量片)三 SKU 矩阵<br/>② <strong>"告别瓶瓶罐罐"内源吸收</strong>赛道切入 100 亿儿童营养软糖市场<br/>③ <strong>卡通 IP × 咀嚼片革命</strong>解决喂药依从性 + 建立货架辨识度。每条赛道配真实 VOC 证据 + 场景话术 + 规模预估。`,
  102. blueprint: `以 VOC 洞察反推 <strong style="color:${BRAND.LACTIC_MINT};">4P 配称</strong>:<strong>产品</strong>(颗粒 / 咀嚼片 / 独立包装 / 分龄礼盒 8 SKU)+ <strong>渠道</strong>(药店线下 + 京东 / 天猫线上 + 母婴连锁 + 微信社群)+ <strong>推广</strong>(XHS 新手妈妈种草 + 抖音儿科医生科普 + 丁香妈妈合作)+ <strong>价格</strong>(¥30-298 三带位)。3 阶段落地:<strong>Y1 差异化升级 3 亿 → Y2 赛道突破 6 亿 → Y3 生态化 10 亿</strong>。`,
  103. };
  104. // ============================================================
  105. // utils
  106. // ============================================================
  107. function sn(n) { return String(n).padStart(2, '0'); }
  108. // ============================================================
  109. // 共用组件(导航 / 键盘提示)
  110. // ============================================================
  111. function renderNav() {
  112. const dots = [
  113. { tip: '封面', href: '#s0' },
  114. { tip: '目录', href: '#agenda' },
  115. ...CHAPTERS.map((c) => ({ tip: `${c.num} ${c.title}`, href: `#${c.key}` })),
  116. ];
  117. return `<nav class="nav-rail" aria-label="章节导航">
  118. ${dots.map((d, i) => `<a class="nav-dot${i === 0 ? ' active' : ''}" href="${d.href}" data-tip="${esc(d.tip)}" data-idx="${i}"></a>`).join('')}
  119. </nav>
  120. <div class="top-bar">
  121. <div class="brand-logo">江中<span class="dot">·</span>产品开发部 / 乳酸菌素片儿童版 VOC 深度洞察 v1.0</div>
  122. <div class="text-mono text-xs" style="color:var(--text-3);">6000万→10亿 / 3 赛道 / 8 假设</div>
  123. </div>`;
  124. }
  125. function renderKbdHint() {
  126. return `<div class="kbd-hint" id="kbdHint">
  127. <span>键盘切换</span>
  128. <kbd>↑</kbd><kbd>↓</kbd>
  129. <span>或</span>
  130. <kbd>PgUp</kbd><kbd>PgDn</kbd>
  131. </div>`;
  132. }
  133. // ============================================================
  134. // Slide 01 · Cover
  135. // ============================================================
  136. function renderCover({ product, collectedAt, slideNum, slideTotal, meta }) {
  137. const totalComments = meta?.comments || 0;
  138. const platformCount = Object.values(meta?.platforms || {}).filter((n) => n > 0).length;
  139. const productsCount = meta?.productsCount || 0;
  140. const stage = meta?.stage || 'skeleton';
  141. const tier = meta?.sourceTier || 'unknown';
  142. const isScaffold = stage === 'skeleton';
  143. const isReal = tier === 'real-collected';
  144. const sourceBadge = isReal
  145. ? { label: '真实采集 · XHS + 抖音评论颗粒度', color: BRAND.JIANG_GREEN, icon: '✓' }
  146. : { label: '骨架版 · 待 VOC 数据加载', color: BRAND.CORAL, icon: '○' };
  147. const commentsDisplay = isScaffold ? '待采集' : totalComments.toLocaleString();
  148. return `<section class="report-section" id="s0" data-tone="amber" data-slide-num="${slideNum}/${slideTotal}" style="min-height:100vh;padding:0;">
  149. <div class="cover-hero">
  150. <div class="aurora" aria-hidden="true"><span></span><span></span><span></span></div>
  151. <div class="cover-grid-bg"></div>
  152. <div class="cover-content">
  153. <div class="cover-meta rise-in" style="display:flex; justify-content:space-between; align-items:center; gap:16px; flex-wrap:wrap;">
  154. <span>江中 / 产品开发部 / 2026 · ${esc(product)} · VOC 深度洞察</span>
  155. <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;">
  156. <span style="font-size:0.9rem;">${sourceBadge.icon}</span>${esc(sourceBadge.label)}
  157. </span>
  158. </div>
  159. <div class="cover-title-stack">
  160. <p class="text-mono rise-in" style="color:${BRAND.LACTIC_MINT}; font-size:0.9rem;letter-spacing:0.2em;margin-bottom:18px;">KANO × JTBD × SCENE × COMPETITOR · 四维儿童 VOC 验证</p>
  161. <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>
  162. <p class="text-poster rise-in" style="margin-top:20px;color:var(--text-2); font-size:1.42rem;">
  163. 儿童肠道调理专属 OTC · 从 <strong style="color:${BRAND.LACTIC_MINT};">6000 万到 10 亿的真实路径</strong>
  164. </p>
  165. </div>
  166. <p class="cover-sub fade-up">
  167. 基于 <strong style="color:${BRAND.LACTIC_MINT};">0.基础调研.md + 1.场景的补充.md 会议确认</strong> 的"10 亿 × 3 赛道破局"落地框架,
  168. 以 <strong style="color:${BRAND.JIANG_GREEN};">OTC 蓝帽子非活菌 + 儿童专属剂型</strong> 为起点,
  169. 通过 <strong style="color:${BRAND.LACTIC_MINT};">KANO × JTBD × 场景四元素</strong> × <strong style="color:${BRAND.LACTIC_MINT};">${commentsDisplay}</strong> 条真实家长评论<strong style="color:${BRAND.LACTIC_MINT};">颗粒度</strong> VOC 交叉验证,
  170. 对标 <strong style="color:${BRAND.TECH_BLUE};">6 亿药品益生菌 + 20 亿食品益生菌 + 100 亿儿童营养软糖</strong> 的痛点地图,
  171. 在 <strong style="color:${BRAND.ROSE};">药品 ↔ 食品 ↔ 营养软糖三轴</strong> 找到乳酸菌素片<strong style="color:${BRAND.JIANG_GREEN};">无人地带</strong>,
  172. 提炼 <strong style="color:${BRAND.LACTIC_MINT};">3 条长尾赛道</strong>,输出 <strong style="color:${BRAND.SAGE};">4P 配称蓝图</strong>,闭环 <strong style="color:${BRAND.JIANG_GREEN};">10 亿 · 3 年</strong> 路径。
  173. </p>
  174. <div class="cover-stats fade-up" style="grid-template-columns:repeat(6, 1fr); max-width:1100px;">
  175. <div class="cover-stat"><div class="num">10亿</div><div class="lbl">3 年目标</div></div>
  176. <div class="cover-stat"><div class="num">${commentsDisplay}</div><div class="lbl">真实家长 VOC</div></div>
  177. <div class="cover-stat"><div class="num">${isScaffold ? '2' : platformCount}</div><div class="lbl">采集平台</div></div>
  178. <div class="cover-stat"><div class="num">8</div><div class="lbl">核心假设</div></div>
  179. <div class="cover-stat"><div class="num">${isScaffold ? '30' : (productsCount || 30)}</div><div class="lbl">关键词维度</div></div>
  180. <div class="cover-stat"><div class="num">${slideTotal}</div><div class="lbl">slides</div></div>
  181. </div>
  182. <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;">
  183. <div class="text-mono text-xs" style="color:${BRAND.LACTIC_MINT}; letter-spacing:0.14em; margin-bottom:6px; font-weight:700;">DATA FOUNDATION · 数据底座</div>
  184. <p style="color:var(--text-2); font-size:0.82rem; line-height:1.6; margin:0;">
  185. 本报告基于 <strong style="color:${BRAND.LACTIC_MINT};">docs/乳酸菌/raw/_merged.json</strong> 真实多平台采集:
  186. XHS <strong>${meta?.platforms?.xhs || '待采集'}</strong> 条评论 + 抖音 <strong>${meta?.platforms?.douyin || '待采集'}</strong> 条评论。
  187. 每条 VOC 带 <code style="background:${BRAND.JIANG_GREEN}15; padding:1px 5px; border-radius:3px; color:${BRAND.JIANG_GREEN};">source=real-collected</code> 明确标识。
  188. </p>
  189. </div>
  190. <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>
  191. </div>
  192. </div>
  193. </section>`;
  194. }
  195. // ============================================================
  196. // Slide 02 · Agenda
  197. // ============================================================
  198. function renderAgenda({ slideNum, slideTotal }) {
  199. return `<section class="report-section" id="agenda" data-tone="amber" data-slide-num="${slideNum}/${slideTotal}">
  200. <div class="section-inner">
  201. <div class="section-head rise-in">
  202. <div class="section-eyebrow" style="color:${BRAND.LACTIC_MINT};">CONTENTS · 8 章认知递进</div>
  203. <h2 class="text-h1"><span class="headline-anim">从诘问到蓝图 · 家长 × 儿童 双视角 VOC 实证</span></h2>
  204. <p class="section-sub">按<strong style="color:${BRAND.LACTIC_MINT};">认知深度递进</strong>:① 诘问 → ② 药品VOC → ③ 食品VOC → ④ KANO×JTBD → ⑤ 场景地图 → ⑥ 三轴定位 → ⑦ 新机会 → ⑧ 4P 蓝图</p>
  205. </div>
  206. <div class="grid" style="grid-template-columns:repeat(2, 1fr); gap:22px; margin-top:20px;">
  207. ${CHAPTERS.map((c, i) => `
  208. <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'">
  209. <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>
  210. <div style="display:flex; justify-content:space-between; align-items:start; margin-bottom:14px;">
  211. <div class="text-mono text-xs" style="color:${c.color}; letter-spacing:0.18em; font-weight:700;">CH ${c.num} · ${c.en}</div>
  212. <div style="font-size:1.6rem; filter:grayscale(20%);">${c.icon}</div>
  213. </div>
  214. <h3 class="text-h2" style="margin:0 0 10px; color:var(--text-1); font-size:1.38rem;">${esc(c.title)}</h3>
  215. <p style="color:var(--text-2); font-size:0.88rem; line-height:1.6; margin-bottom:12px;">${esc(c.summary)}</p>
  216. <div style="display:flex; flex-wrap:wrap; gap:5px; margin-bottom:12px;">
  217. ${(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('')}
  218. </div>
  219. <div style="padding-top:12px; border-top:1px solid var(--border); display:flex; justify-content:space-between; align-items:center;">
  220. <span class="text-xs text-mono" style="color:var(--text-3); letter-spacing:0.12em;">→ 跳转章节</span>
  221. <span class="text-mono text-xs" style="color:${c.color};">${i + 1} / ${CHAPTERS.length}</span>
  222. </div>
  223. </a>`).join('')}
  224. </div>
  225. <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);">
  226. <div class="text-mono text-xs" style="color:${BRAND.LACTIC_MINT}; letter-spacing:0.15em; margin-bottom:10px;">METHODOLOGY · 方法论自律</div>
  227. <div style="color:var(--text-1); font-size:0.98rem; line-height:1.72;">
  228. 本报告严格遵循 《<strong style="color:${BRAND.LACTIC_MINT};">docs/乳酸菌/0.基础调研.md + 1.场景的补充.md</strong>》10 亿 × 3 赛道破局方法论:<br/>
  229. <strong style="color:${BRAND.CORAL};">❌ 不做</strong>:改药理主成分、宣称"提升免疫力/促进生长发育"(需场景化话术替代)、装饰性图表、脱离 VOC 的拍脑袋;<br/>
  230. <strong style="color:${BRAND.JIANG_GREEN};">✅ 做</strong>:8 条核心假设 H1-H8 × 多平台 VOC 实证 × KANO × JTBD × 场景四元素 × 3 轴竞品地图 × 4P 落地映射;<br/>
  231. 每一个结论都附 <strong style="color:${BRAND.LACTIC_MINT};">真实家长原声</strong>(昵称 + IP + 点赞)+ <strong style="color:${BRAND.TECH_BLUE};">理论模型映射</strong> + <strong style="color:${BRAND.SAGE};">可落地决策项</strong> 三重验证。
  232. </div>
  233. </div>
  234. </div>
  235. </section>`;
  236. }
  237. // ============================================================
  238. // 章节 Divider(8 个)
  239. // ============================================================
  240. function renderChapterDivider({ chapter, slideNum, slideTotal }) {
  241. const c = chapter;
  242. const heroLine = CHAPTER_HERO_LINES[c.key] || c.summary;
  243. const subs = CHAPTER_SUBSECTIONS[c.key] || [];
  244. return `<section class="report-section" id="${c.key}" data-tone="${c.tone}" data-slide-num="${slideNum}/${slideTotal}" style="justify-content:center;">
  245. <div class="section-inner">
  246. <div class="rise-in" style="text-align:center; margin-bottom:40px;">
  247. <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>
  248. <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>
  249. </div>
  250. <div class="rise-in" style="text-align:center; margin-bottom:36px;">
  251. <div style="font-size:3.2rem; margin-bottom:14px;">${c.icon}</div>
  252. <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>
  253. <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>
  254. </div>
  255. <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);">
  256. <p style="color:var(--text-1); font-size:1.08rem; line-height:1.82;">${heroLine}</p>
  257. </div>
  258. <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;">
  259. ${subs.map((sub, i) => `
  260. <div style="padding:16px 14px; background:var(--bg-1); border:1px solid ${c.color}22; border-radius:var(--radius-sm); text-align:center;">
  261. <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>
  262. <div style="color:var(--text-1); font-size:0.86rem; line-height:1.5;">${esc(sub)}</div>
  263. </div>`).join('')}
  264. </div>
  265. <div class="fade-up" style="margin-top:48px; text-align:center;">
  266. <div class="text-mono text-xs" style="color:var(--text-3); letter-spacing:0.3em;">SCROLL DOWN · ${subs.length} 个子章 · VOC 实证 × 理论 × 决策</div>
  267. </div>
  268. </div>
  269. </section>`;
  270. }
  271. // ============================================================
  272. // 占位子章(Coming Soon)
  273. // ============================================================
  274. function renderComingSoon({ chapter, subIdx, sub, slideNum, slideTotal, targetBatch, meta }) {
  275. const c = chapter;
  276. const hs = LA.CHAPTER_HYPOTHESIS_MAP[c.key] || [];
  277. const hsCount = hs.reduce((s, h) => s + (meta?.hypotheses?.[h] || 0), 0);
  278. return `<section class="report-section" id="${c.key}-${subIdx}" data-tone="${c.tone}" data-slide-num="${slideNum}/${slideTotal}">
  279. <div class="section-inner">
  280. <div class="section-head rise-in">
  281. <div class="section-eyebrow" style="color:${c.color};">${c.num}.${subIdx} · ${esc((sub || '').split('·')[0].toUpperCase())}</div>
  282. <h2 class="text-h1"><span class="headline-anim">${esc(sub)}</span></h2>
  283. <p class="section-sub">此子章将在 <strong style="color:${c.color};">Batch ${targetBatch}</strong> 填充 VOC 证据卡 + 理论映射 + 决策项</p>
  284. </div>
  285. <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;">
  286. <div style="font-size:3.4rem; margin-bottom:18px;">🚧</div>
  287. <div class="text-h2" style="color:${c.color}; margin-bottom:12px;">Coming in Batch ${targetBatch}</div>
  288. ${hsCount ? `
  289. <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;">
  290. <span>✓</span> 数据就绪 · ${hsCount} 条 ${hs.join('/')} 相关真实 VOC
  291. </div><br/>
  292. <p style="color:var(--text-2); font-size:0.92rem; max-width:680px; margin:0 auto; line-height:1.7;">
  293. docs/乳酸菌/raw/_merged.json 已就绪 <strong style="color:${BRAND.LACTIC_MINT};">${meta?.comments || 0} 条真实 VOC</strong>,<br/>
  294. Batch ${targetBatch} 将基于本假设的 <strong style="color:${c.color};">${hsCount} 条</strong> 证据 + 理论工具,输出 <strong>证据卡 × 理论映射 × 决策项</strong>。
  295. </p>` : `
  296. <p style="color:var(--text-2); font-size:0.95rem; max-width:600px; margin:0 auto; line-height:1.7;">
  297. Batch ${targetBatch} 将基于已采集 VOC 数据填充本子章内容。
  298. </p>`}
  299. </div>
  300. </div>
  301. </section>`;
  302. }
  303. // ============================================================
  304. // 子章 → Batch 映射
  305. // ============================================================
  306. const SUB_TO_BATCH = {
  307. 'challenge': 2, 'drug_voc': 2, 'food_voc': 2,
  308. 'kano': 3, 'scene': 3,
  309. 'competitor': 4, 'opportunity': 4,
  310. 'blueprint': 5,
  311. };
  312. // ============================================================
  313. // 子章渲染器注册表
  314. // ============================================================
  315. let SLIDE_RENDERERS = {};
  316. function tryRequire(modPath) {
  317. try { return require(modPath); } catch (e) { return null; }
  318. }
  319. const CH1 = tryRequire('./lactic-ch1-challenge.js');
  320. const CH2 = tryRequire('./lactic-ch2-drug-voc.js');
  321. const CH3 = tryRequire('./lactic-ch3-food-voc.js');
  322. const CH4 = tryRequire('./lactic-ch4-kano.js');
  323. const CH5 = tryRequire('./lactic-ch5-scene.js');
  324. const CH6 = tryRequire('./lactic-ch6-competitor.js');
  325. const CH7 = tryRequire('./lactic-ch7-opportunity.js');
  326. const CH8 = tryRequire('./lactic-ch8-blueprint.js');
  327. SLIDE_RENDERERS = {
  328. ...(CH1 || {}),
  329. ...(CH2 || {}),
  330. ...(CH3 || {}),
  331. ...(CH4 || {}),
  332. ...(CH5 || {}),
  333. ...(CH6 || {}),
  334. ...(CH7 || {}),
  335. ...(CH8 || {}),
  336. };
  337. // ============================================================
  338. // buildHTML · 主装配
  339. // ============================================================
  340. function buildHTML({ product, collectedAt } = {}) {
  341. product = product || '江中乳酸菌素片儿童版';
  342. collectedAt = collectedAt || new Date().toISOString().slice(0, 10);
  343. const data = LA.loadMerged();
  344. const meta = LA.getMeta(data);
  345. // slide 总数:2 (cover+agenda) + 每章 (1 divider + N 子章)
  346. let slideTotal = 2;
  347. for (const c of CHAPTERS) {
  348. slideTotal += 1 + (CHAPTER_SUBSECTIONS[c.key] || []).length;
  349. }
  350. const slides = [];
  351. let num = 1;
  352. slides.push(renderCover({ product, collectedAt, slideNum: sn(num), slideTotal: sn(slideTotal), meta }));
  353. num++;
  354. slides.push(renderAgenda({ slideNum: sn(num), slideTotal: sn(slideTotal) }));
  355. num++;
  356. for (const c of CHAPTERS) {
  357. slides.push(renderChapterDivider({ chapter: c, slideNum: sn(num), slideTotal: sn(slideTotal) }));
  358. num++;
  359. const subs = CHAPTER_SUBSECTIONS[c.key] || [];
  360. for (let i = 0; i < subs.length; i++) {
  361. const subIdx = i + 1;
  362. const subKey = `${c.key}-${subIdx}`;
  363. const renderer = SLIDE_RENDERERS[subKey];
  364. const slideCtx = {
  365. chapter: c,
  366. subIdx,
  367. sub: subs[i],
  368. slideNum: sn(num),
  369. slideTotal: sn(slideTotal),
  370. data,
  371. meta,
  372. };
  373. if (renderer) {
  374. slides.push(renderer(slideCtx));
  375. } else {
  376. slides.push(renderComingSoon({ ...slideCtx, targetBatch: SUB_TO_BATCH[c.key] || 2, meta }));
  377. }
  378. num++;
  379. }
  380. }
  381. return `<!DOCTYPE html>
  382. <html lang="zh-CN">
  383. <head>
  384. <meta charset="UTF-8"/>
  385. <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  386. <title>${esc(product)} · VOC 深度洞察报告 · 6000万→10亿</title>
  387. ${STYLES}
  388. </head>
  389. <body>
  390. ${renderNav()}
  391. ${renderKbdHint()}
  392. <div class="progress-line" id="progressLine"></div>
  393. <main class="deck">
  394. ${slides.join('\n')}
  395. </main>
  396. <script>${SCRIPTS}</script>
  397. </body>
  398. </html>`;
  399. }
  400. module.exports = {
  401. buildHTML,
  402. BRAND,
  403. CHAPTERS,
  404. CHAPTER_SUBSECTIONS,
  405. CHAPTER_HERO_LINES,
  406. SUB_TO_BATCH,
  407. renderCover,
  408. renderAgenda,
  409. renderChapterDivider,
  410. renderComingSoon,
  411. };