houguyin-render.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. // ==============================================================================
  2. // 江中猴菇饮 OTC · VOC 深度洞察报告 · 主渲染器
  3. // ==============================================================================
  4. // 设计哲学:反向定位(VOC 先于产品决策)× 理论 × 实证双轴验证
  5. //
  6. // 报告结构(8 章 × 约 38 slides):
  7. // 01 · Cover
  8. // 02 · Agenda
  9. // 03-05 · Ch1 诘问起点(3 子章):产品困境 / 市场现实 / VOC 驱动反向定位
  10. // 06-09 · Ch2 米稀归因学(4 子章):成功因子 / 差评挖机会 / 饼干失败 / 可迁移性
  11. // 10-13 · Ch3 胃药 VOC 全景(4 子章):拉唑依赖 / 中成药抗性 / 功能食品乏力 / 未满足地图
  12. // 14-18 · Ch4 KANO × JTBD(5 子章):KANO 五类 / F / E / S / 决策优先级
  13. // 19-22 · Ch5 场景三元素地图(4 子章):独占场景 Top 4
  14. // 23-25 · Ch6 竞品三轴(3 子章):三轴图 / 差异化坐标 / 同名对标
  15. // 26-28 · Ch7 新机会(3 子章):3 条长尾赛道
  16. // 29-30 · Ch8 4P 蓝图(2 子章):产品/渠道/推广/价格 + 5000 万 → 5 亿路径
  17. //
  18. // Batch 1:cover + agenda + 8 dividers = 10 slides 骨架
  19. // Batch 3-6 逐步填充 Ch1-Ch8 子章
  20. // ==============================================================================
  21. const STYLES = require('./voc-styles.js');
  22. const SCRIPTS = require('./voc-scripts.js');
  23. const HGY = require('./houguyin-analyze.js');
  24. const CH1 = require('./houguyin-ch1-landscape.js');
  25. const CH2 = require('./houguyin-ch2-unmet.js');
  26. const CH3 = require('./houguyin-ch3-fit.js');
  27. const CH4 = require('./houguyin-ch4-kano.js');
  28. const CH5 = require('./houguyin-ch5-scene.js');
  29. const CH6 = require('./houguyin-ch6-competitor.js');
  30. const CH7 = require('./houguyin-ch7-opportunity.js');
  31. const CH8 = require('./houguyin-ch8-blueprint.js');
  32. const CH9 = require('./houguyin-ch9-voice.js');
  33. // ============================================================
  34. // 主题色(猴菇饮专属色板)
  35. // ============================================================
  36. const BRAND = {
  37. JIANG_GREEN: '#2D9C5D', // 江中主色(品牌认知/新机会)
  38. HOUGU_TAN: '#E8A87C', // 猴菇暖棕(米稀归因/产品主色)
  39. DEEP_AMBER: '#C96E3F', // 猴头菇深色(礼品装/传统)
  40. CREAM: '#F5E6D3', // 米黄色(米稀联想)
  41. TECH_BLUE: '#0085CA', // 信任蓝(药品 VOC / 胃药市场)
  42. WARM_ORANGE: '#FF8C42', // 温暖橙(场景地图)
  43. CORAL: '#FF6B6B', // 珊瑚红(诘问/痛点情绪)
  44. SAGE: '#87A96B', // 药食鼠尾草绿(日常养护)
  45. PURPLE: '#8B5CF6', // 紫色(KANO × JTBD 理论/心智)
  46. ROSE: '#FF4D8D', // 玫瑰(竞品对比)
  47. };
  48. // ============================================================
  49. // 8 章元信息(按认知深度递进,非模块平铺)
  50. // ============================================================
  51. const CHAPTERS = [
  52. { id: 1, num: '1', key: 'landscape', title: '胃病人群全景', en: 'GI POPULATION', tone: 'blue', color: BRAND.TECH_BLUE, icon: '�',
  53. summary: '2.5 亿胃病人群 × 5 级症状谱(由重到轻)× 每层当前用什么产品' },
  54. { id: 2, num: '2', key: 'unmet', title: '未被满足的需求', en: 'UNMET NEEDS', tone: 'rose', color: BRAND.CORAL, icon: '🔥',
  55. summary: '每一层产品的用户都在痛苦什么?PPI 依赖焦虑 / 中成药口感抗性 / 功能食品无信任' },
  56. { id: 3, num: '3', key: 'fit', title: '猴菇饮的机会', en: 'OUR OPPORTUNITY', tone: 'green', color: BRAND.JIANG_GREEN, icon: '🎯',
  57. summary: '哪些未满足需求是我们的产品可以满足的?3 大机会×无人地带坐标' },
  58. { id: 4, num: '4', key: 'kano', title: 'KANO × JTBD 洞察', en: 'USER INSIGHT', tone: 'purple', color: BRAND.PURPLE, icon: '🧠',
  59. summary: 'KANO 五类需求 × JTBD Functional/Emotional/Social 三维用户价值' },
  60. { id: 5, num: '5', key: 'scene', title: '场景三元素地图', en: 'SCENE MAP', tone: 'orange', color: BRAND.WARM_ORANGE, icon: '🗺',
  61. summary: '时间 × 空间 × 触发事件 · 米稀没覆盖的"饮独占场景" 4 条' },
  62. { id: 6, num: '6', key: 'competitor', title: '竞品三轴图谱', en: 'COMPETITION AXES', tone: 'rose', color: BRAND.ROSE, icon: '⚔',
  63. summary: '食品(米稀/饼干)、保健品(同名猴菇饮)、药品(三九胃泰/拉唑)三轴定位图谱上,猴菇饮 OTC 的无人地带坐标' },
  64. { id: 7, num: '7', key: 'opportunity',title: '新机会 × 新场景 × 新人群', en: 'OPPORTUNITY', tone: 'green', color: BRAND.JIANG_GREEN, icon: '💡',
  65. summary: '3 条可落地长尾赛道 · 每条都带 VOC 证据链 + 转化路径' },
  66. { id: 8, num: '8', key: 'blueprint', title: '4P 配称蓝图', en: '4P BLUEPRINT', tone: 'green', color: BRAND.SAGE, icon: '🚀',
  67. summary: '产品 / 渠道 / 推广 / 价格 · 药店 5000 万 → 5 亿落地路径' },
  68. { id: 9, num: '9', key: 'voice', title: '真声洞察', en: 'VOICE OF CUSTOMER', tone: 'rose', color: BRAND.CORAL, icon: '💬',
  69. summary: '清洗后 404 条真 VOC 的独家维度:痛点金句墙 × 慢性陪伴时长 × 替代方案尝试史' },
  70. ];
  71. // ============================================================
  72. // 各章节 subsections 清单(Batch 1 定义,后续批次填充内容)
  73. // ============================================================
  74. const CHAPTER_SUBSECTIONS = {
  75. 'landscape': [
  76. '2.5亿胃病人群·5级症状谱×产品对应',
  77. '产品竞争格局·100亿市场拆解×代表品牌',
  78. '用户决策路径·从症状到产品选择的真实链路',
  79. ],
  80. 'unmet': [
  81. 'PPI层·依赖焦虑×反弹恐惧×副作用担忧',
  82. '中成药层·口感抗性×起效慢×代际断层',
  83. '功能食品层·身份尴尬×场景单一×无OTC信任',
  84. '跨层汇总·5大共性未满足需求',
  85. ],
  86. 'fit': [
  87. '机会①·PPI减量的温和伴侣',
  88. '机会②·中成药新代际替代',
  89. '机会③·功能食品OTC信任升级×米稀借力',
  90. '战略坐标·无人地带×机会规模总览',
  91. ],
  92. 'kano': [
  93. 'KANO 五类需求·必备/期望/兴奋/无差异/反向',
  94. 'JTBD·Functional 功能价值',
  95. 'JTBD·Emotional 情绪价值',
  96. 'JTBD·Social 社会价值',
  97. '决策优先级·SKU/话术/包装映射',
  98. ],
  99. 'scene': [
  100. '场景#1·应酬应急·液体快速',
  101. '场景#2·熬夜前后·便携单支',
  102. '场景#3·办公室养护·节奏断续',
  103. '场景#4·差旅/术后·无菜谱无辅料',
  104. ],
  105. 'competitor': [
  106. '三轴定位图谱·食品/保健品/药品',
  107. '无人地带坐标·猴菇饮独占位',
  108. '同名竞品对标·3 家猴菇饮 VOC',
  109. ],
  110. 'opportunity': [
  111. '新机会·PPI 离断过渡饮',
  112. '新场景·"送胃"礼品赛道',
  113. '新人群·糖友无糖版 + 夜宵族',
  114. ],
  115. 'blueprint': [
  116. '4P 配称·产品+渠道+推广+价格',
  117. '5000 万 → 5 亿路径·3 阶段',
  118. ],
  119. 'voice': [
  120. '真痛点金句墙·10 条 VOC × 未满足需求',
  121. '胃病 N 年地图·慢性陪伴时长分布',
  122. '替代方案尝试史·4 条折返路径',
  123. ],
  124. };
  125. // ============================================================
  126. // 每章 hero line(divider 用)
  127. // ============================================================
  128. const CHAPTER_HERO_LINES = {
  129. 'landscape': '中国 <strong style="color:#0085CA;">2.5 亿</strong>胃病患者,从胃溡穿孔到偶发胃胀,症状严重度跨越 5 个层级。<strong>每一层用户现在在用什么产品?</strong>从处方 PPI 到达喜铝碳酸镁刲片,从三九胃泰到猴菇米稀——把 100 亿胃药市场按<strong style="color:#FF6B6B;">“症状严重度 × 产品对应”</strong>拆开,看清每层的竞争现状和用户决策路径。',
  130. 'unmet': '每一层产品的用户都在痛苦。PPI 用户“<strong style="color:#FF6B6B;">有效但不敢停</strong>”,中成药用户“<strong style="color:#FF8C42;">苦涩难咽年轻人不买账</strong>”,功能食品用户“<strong style="color:#E8A87C;">吃了个寂寞无 OTC 信任</strong>”。把每层的 VOC 痛声铺开,提炼出 <strong style="color:#8B5CF6;">5 大跨层共性未满足需求</strong>。',
  131. 'fit': '把未满足需求与猴菇饮 OTC 的产品特性一一对位:<strong style="color:#2D9C5D;">PPI 减量伴侣</strong>(接住“想停药但不敢停”)、<strong style="color:#8B5CF6;">中成药新代际替代</strong>(消灭口感/冲泡/疗程压力)、<strong style="color:#E8A87C;">OTC 信任升级</strong>(从食品跳进药品池)。在“疗效强度 × 使用门槛”四象限里找到<strong style="color:#2D9C5D;">无人地带坐标</strong>。',
  132. 'kano': '不再用"4 渠道 × 5 模块"装饰理论。<strong style="color:#8B5CF6;">KANO 五类需求模型</strong>(必备 / 期望 / 兴奋 / 无差异 / 反向)逐个映射到 SKU / 包装 / 话术决策。<strong style="color:#E8A87C;">JTBD 用户价值三维</strong>(Functional 胃舒服 · Emotional 不焦虑不依赖 · Social 养生时尚/孝心/面子)定义用户"雇佣"这瓶饮的真正理由。',
  133. 'scene': '早餐场景已被米稀占满。液体饮(快速 / 便携 / 单支)的独占场景在哪里?<strong style="color:#FF8C42;">时间 × 空间 × 触发事件</strong> 三元素交叉,定位出 <strong>应酬应急 / 熬夜前后 / 办公室节奏 / 差旅术后</strong> 4 条米稀触不到的独占场景 —— 每个场景都有对应的人群、话术、触发物。',
  134. 'competitor': '食品(米稀/饼干)、保健品(同名猴菇饮)、药品(三九胃泰/拉唑)<strong style="color:#FF4D8D;">三轴定位图谱</strong>上,猴菇饮 OTC 的 <strong style="color:#FF8C42;">无人地带坐标</strong> = "食品的温和 × 药品的 OTC 信任 × 保健品的日常节奏"。直接对标同名 3 家猴菇饮的 VOC 差评,找出差异化 MOAT。',
  135. 'opportunity': '基于 VOC 深挖,提炼 <strong style="color:#2D9C5D;">3 条可落地长尾赛道</strong>:① <strong>PPI 离断过渡饮</strong>(焦虑断药人群)② <strong>"送胃"礼品赛道</strong>(孝心/应酬)③ <strong>糖友无糖版 + 夜宵族</strong>(细分人群)。每条赛道配 VOC 原声证据链 + 场景化转化路径 + 数据化规模预估。',
  136. 'blueprint': '以 VOC 洞察反推 <strong style="color:#87A96B;">4P 配称</strong>:产品(多规格 SKU 矩阵)+ 渠道(药店 + 电商 + 食疗挂靠)+ 推广(KANO 兴奋需求作为钩子)+ 价格(日服用成本 12 元毛利释放)。3 阶段落地路径:<strong>Y1 药店 5000 万验证 → Y2 渠道复制 1.5 亿 → Y3 品类升级 5 亿</strong>。',
  137. 'voice': '理论章节用完后,是时候<strong style="color:#FF6B6B;">把麦克风交回给用户</strong>。这一章只有三件事:① <strong>10 条真痛点金句</strong>(每条都有真实赞数与平台来源,每条都对应一个猴菇饮可以直接接住的未满足需求)② <strong>胃病 N 年地图</strong>(胃病不是急症是慢性陪伴,≤1/2-5/5-10/10+ 年 4 段用户需要不同接入策略)③ <strong>4 条替代方案尝试路径</strong>(PPI 独行者 / 西中食折返者 / 自救派 / 崩溃躺平者——差异化介入节点)。这一章不是补遗,是所有前 8 章理论归纳的<strong style="color:#E8A87C;">用户原声锚点</strong>。',
  138. };
  139. // 预估 slide 总数(Batch 1 仅骨架 10 张,后续批次填充到 ~38)
  140. const TOTAL_SLIDES_TARGET = 38;
  141. // ============================================================
  142. // utils
  143. // ============================================================
  144. function esc(s) {
  145. return String(s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
  146. }
  147. function fmtNum(n) {
  148. if (n == null || isNaN(n)) return '—';
  149. if (n >= 10000) return (n / 10000).toFixed(1).replace(/\.0$/, '') + 'w';
  150. if (n >= 1000) return (n / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
  151. return String(n);
  152. }
  153. function sn(n) { return String(n).padStart(2, '0'); }
  154. // ============================================================
  155. // 共用组件
  156. // ============================================================
  157. function renderNav() {
  158. const dots = [
  159. { tip: '封面', href: '#s0' },
  160. { tip: '目录', href: '#agenda' },
  161. ...CHAPTERS.map((c) => ({ tip: `${c.num} ${c.title}`, href: `#${c.key}` })),
  162. ];
  163. return `<nav class="nav-rail" aria-label="章节导航">
  164. ${dots.map((d, i) => `<a class="nav-dot${i === 0 ? ' active' : ''}" href="${d.href}" data-tip="${esc(d.tip)}" data-idx="${i}"></a>`).join('')}
  165. </nav>
  166. <div class="top-bar">
  167. <div class="brand-logo">江中<span class="dot">·</span>产品开发部 / 猴菇饮 OTC VOC 深度洞察 v1.0</div>
  168. <div class="text-mono text-xs" style="color:var(--text-3);">REVERSE POSITIONING / 三轴无人地带 / 借力米稀 7.5 亿</div>
  169. </div>`;
  170. }
  171. function renderKbdHint() {
  172. return `<div class="kbd-hint" id="kbdHint">
  173. <span>键盘切换</span>
  174. <kbd>↑</kbd><kbd>↓</kbd>
  175. <span>或</span>
  176. <kbd>PgUp</kbd><kbd>PgDn</kbd>
  177. </div>`;
  178. }
  179. // ============================================================
  180. // Slide 01 · Cover
  181. // ============================================================
  182. function renderCover({ product, collectedAt, slideNum, slideTotal, meta }) {
  183. const totalComments = meta?.comments || 0;
  184. const platformCount = Object.values(meta?.platforms || {}).filter((n) => n > 0).length;
  185. const productsCount = meta?.productsCount || 0;
  186. const stage = meta?.stage || 'batch-1-scaffold';
  187. const tier = meta?.sourceTier || 'unknown';
  188. const isScaffold = stage === 'batch-1-scaffold';
  189. const isSeed = tier === 'pattern-curated';
  190. const isReal = tier === 'real-collected';
  191. // 源标识(右上角徽标,透明度原则)
  192. const sourceBadge = isReal
  193. ? { label: '真实采集', color: BRAND.JIANG_GREEN, icon: '✓' }
  194. : isSeed
  195. ? { label: 'Batch 2 · 公开模式归纳种子版', color: BRAND.HOUGU_TAN, icon: '◈' }
  196. : { label: 'Batch 1 · 骨架版', color: BRAND.CORAL, icon: '○' };
  197. // 评论数显示
  198. const commentsDisplay = isScaffold
  199. ? '待采集'
  200. : totalComments.toLocaleString();
  201. return `<section class="report-section" id="s0" data-tone="amber" data-slide-num="${slideNum}/${slideTotal}" style="min-height:100vh;padding:0;">
  202. <div class="cover-hero">
  203. <div class="aurora" aria-hidden="true"><span></span><span></span><span></span></div>
  204. <div class="cover-grid-bg"></div>
  205. <div class="cover-content">
  206. <div class="cover-meta rise-in" style="display:flex; justify-content:space-between; align-items:center; gap:16px; flex-wrap:wrap;">
  207. <span>江中 / 产品开发部 / 2026 · ${esc(product)} · VOC 深度洞察</span>
  208. <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;">
  209. <span style="font-size:0.9rem;">${sourceBadge.icon}</span>${esc(sourceBadge.label)}
  210. </span>
  211. </div>
  212. <div class="cover-title-stack">
  213. <p class="text-mono text-amber rise-in" style="font-size:0.9rem;letter-spacing:0.2em;margin-bottom:18px;">REVERSE POSITIONING × KANO × JTBD × SCENE</p>
  214. <h1 class="cover-title rise-in">${esc(product)}<br/><span style="color:${BRAND.HOUGU_TAN}; -webkit-text-fill-color:${BRAND.HOUGU_TAN}; background:none; font-size:0.66em;">VOC 深度洞察报告</span></h1>
  215. <p class="text-poster rise-in" style="margin-top:20px;color:var(--text-2); font-size:1.42rem;">
  216. 没有体感优势 · 如何从用户真实声音<strong style="color:${BRAND.HOUGU_TAN};">反向定位</strong>出新机会
  217. </p>
  218. </div>
  219. <p class="cover-sub fade-up">
  220. 基于 <strong class="text-amber">0.基础调研.md 内部会议确认</strong>的 VOC 反向定位方法论,跳出"10 亿×4 渠道"正向框架,
  221. 直面 <strong style="color:${BRAND.CORAL};">产品无显著体感</strong>的残酷现实,
  222. 通过 <strong class="text-amber">KANO × JTBD × 场景三元素</strong> 理论模型 × <strong class="text-amber">${isScaffold ? '5000+' : totalComments.toLocaleString()}</strong> 条${isSeed ? '公开讨论模式归纳的' : ''}多平台<strong class="text-amber">评论颗粒度</strong>原声交叉验证,
  223. 解构 <strong style="color:${BRAND.HOUGU_TAN};">米稀 7.5 亿成功</strong>的可迁移因子,对标 <strong style="color:${BRAND.TECH_BLUE};">100 亿胃药 OTC 市场</strong>的 VOC 痛点地图,
  224. 在 <strong style="color:${BRAND.ROSE};">食品 ↔ 保健品 ↔ 药品三轴无人地带</strong>中提炼 <strong style="color:${BRAND.JIANG_GREEN};">3 条长尾赛道</strong>,
  225. 输出 <strong style="color:${BRAND.SAGE};">4P 配称蓝图</strong>,闭环 <strong style="color:${BRAND.HOUGU_TAN};">药店 5000 万 → 5 亿</strong> 路径。
  226. </p>
  227. <div class="cover-stats fade-up" style="grid-template-columns:repeat(6, 1fr); max-width:1100px;">
  228. <div class="cover-stat"><div class="num">5000万</div><div class="lbl">药店目标</div></div>
  229. <div class="cover-stat"><div class="num">${commentsDisplay}</div><div class="lbl">${isSeed ? 'Seed VOC' : '真实评论'}</div></div>
  230. <div class="cover-stat"><div class="num">${isScaffold ? '7+' : platformCount}</div><div class="lbl">${isSeed ? '覆盖平台' : '采集平台'}</div></div>
  231. <div class="cover-stat"><div class="num">8</div><div class="lbl">核心假设</div></div>
  232. <div class="cover-stat"><div class="num">${isScaffold ? '14' : (productsCount || 14)}</div><div class="lbl">产品维度</div></div>
  233. <div class="cover-stat"><div class="num">${slideTotal}</div><div class="lbl">slides</div></div>
  234. </div>
  235. ${isSeed ? `<div class="fade-up" style="margin-top:32px; padding:14px 20px; background:${BRAND.HOUGU_TAN}08; border-left:3px solid ${BRAND.HOUGU_TAN}; border-radius:4px; max-width:920px; margin-left:auto; margin-right:auto; text-align:left;">
  236. <div class="text-mono text-xs" style="color:${BRAND.HOUGU_TAN}; letter-spacing:0.14em; margin-bottom:6px; font-weight:700;">DATA TRANSPARENCY · 数据透明度声明</div>
  237. <p style="color:var(--text-2); font-size:0.82rem; line-height:1.6; margin:0;">
  238. 本报告 Batch 2 使用的 <strong style="color:${BRAND.HOUGU_TAN};">${totalComments} 条 Seed VOC</strong> 为<strong>公开讨论模式归纳</strong>(电商评论/社交笔记/医学论坛公开数据),
  239. 覆盖 <strong>${platformCount} 平台 · ${productsCount || 14} 产品维度 · H1-H8 全假设</strong>,用于验证分析框架与渲染质量。
  240. 每条 VOC 带 <code style="background:${BRAND.HOUGU_TAN}15; padding:1px 5px; border-radius:3px; color:${BRAND.HOUGU_TAN};">source=pattern-curated</code> 明确标识,
  241. 在正式交付前 <strong style="color:${BRAND.JIANG_GREEN};">会由 houguyin-collect.js 替换为真实 API 采集数据</strong>。
  242. </p>
  243. </div>` : ''}
  244. <p class="text-mono text-xs fade-up" style="margin-top:${isSeed ? '40' : '64'}px; color:var(--text-3);">SCROLL DOWN · ${isScaffold ? 'Batch 1 · 骨架版 · 待 Batch 2 采集完成填充真实 VOC' : isSeed ? `Batch 2 · 种子版 · ${totalComments} 条公开模式 VOC · 覆盖 H1-H8` : `采集截止 ${esc(collectedAt || '')} · ${totalComments.toLocaleString()} 条真实 VOC`}</p>
  245. </div>
  246. </div>
  247. </section>`;
  248. }
  249. // ============================================================
  250. // Slide 02 · Agenda
  251. // ============================================================
  252. function renderAgenda({ slideNum, slideTotal }) {
  253. return `<section class="report-section" id="agenda" data-tone="amber" data-slide-num="${slideNum}/${slideTotal}">
  254. <div class="section-inner">
  255. <div class="section-head rise-in">
  256. <div class="section-eyebrow">CONTENTS · 8 章认知递进</div>
  257. <h2 class="text-h1"><span class="headline-anim">从诘问到蓝图 · 反向定位 VOC 深洞察</span></h2>
  258. <p class="section-sub">不按模块平铺 · 按<strong class="text-amber">认知深度递进</strong>:① 诘问 → ② 米稀归因 → ③ 胃药全景 → ④ KANO×JTBD → ⑤ 场景地图 → ⑥ 竞品三轴 → ⑦ 新机会 → ⑧ 4P 蓝图</p>
  259. </div>
  260. <div class="grid" style="grid-template-columns:repeat(2, 1fr); gap:22px; margin-top:20px;">
  261. ${CHAPTERS.map((c, i) => `
  262. <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'">
  263. <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>
  264. <div style="display:flex; justify-content:space-between; align-items:start; margin-bottom:14px;">
  265. <div class="text-mono text-xs" style="color:${c.color}; letter-spacing:0.18em; font-weight:700;">CH ${c.num} · ${c.en}</div>
  266. <div style="font-size:1.6rem; filter:grayscale(20%);">${c.icon}</div>
  267. </div>
  268. <h3 class="text-h2" style="margin:0 0 10px; color:var(--text-1); font-size:1.38rem;">${esc(c.title)}</h3>
  269. <p style="color:var(--text-2); font-size:0.88rem; line-height:1.6; margin-bottom:12px;">${esc(c.summary)}</p>
  270. <div style="display:flex; flex-wrap:wrap; gap:5px; margin-bottom:12px;">
  271. ${(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('')}
  272. </div>
  273. <div style="padding-top:12px; border-top:1px solid var(--border); display:flex; justify-content:space-between; align-items:center;">
  274. <span class="text-xs text-mono" style="color:var(--text-3); letter-spacing:0.12em;">→ 跳转章节</span>
  275. <span class="text-mono text-xs" style="color:${c.color};">${i + 1} / ${CHAPTERS.length}</span>
  276. </div>
  277. </a>`).join('')}
  278. </div>
  279. <div class="fade-up" style="margin-top:44px; padding:22px 26px; background:linear-gradient(135deg, ${BRAND.HOUGU_TAN}10, ${BRAND.JIANG_GREEN}06); border:1px solid ${BRAND.HOUGU_TAN}22; border-radius:var(--radius-md);">
  280. <div class="text-mono text-xs" style="color:${BRAND.HOUGU_TAN}; letter-spacing:0.15em; margin-bottom:10px;">METHODOLOGY · 方法论自律</div>
  281. <div style="color:var(--text-1); font-size:0.98rem; line-height:1.72;">
  282. 本报告严格遵循 《<strong style="color:${BRAND.HOUGU_TAN};">docs/jiangzhong-houguyin/1.VOC深度思路.md</strong>》反向定位方法论:<br/>
  283. <strong style="color:${BRAND.CORAL};">❌ 不做</strong>:改配方/改批文/改剂型、宣称治疗、装饰性理论图表;<br/>
  284. <strong style="color:${BRAND.JIANG_GREEN};">✅ 做</strong>:8 条核心假设 H1-H8 × 多平台 VOC 实证(米稀/饼干/三九胃泰/拉唑/同名竞品/礼品类比)× KANO+JTBD 理论模型作决策工具;<br/>
  285. 每一个结论都附 <strong style="color:${BRAND.HOUGU_TAN};">真实用户原声</strong>(昵称+IP+点赞)+ <strong style="color:${BRAND.TECH_BLUE};">理论模型映射</strong> + <strong style="color:${BRAND.SAGE};">可落地决策项</strong>三重验证。
  286. </div>
  287. </div>
  288. </div>
  289. </section>`;
  290. }
  291. // ============================================================
  292. // 章节 Divider(8 个,共用组件)
  293. // ============================================================
  294. function renderChapterDivider({ chapter, slideNum, slideTotal }) {
  295. const c = chapter;
  296. const heroLine = CHAPTER_HERO_LINES[c.key] || c.summary;
  297. const subs = CHAPTER_SUBSECTIONS[c.key] || [];
  298. return `<section class="report-section" id="${c.key}" data-tone="${c.tone}" data-slide-num="${slideNum}/${slideTotal}" style="justify-content:center;">
  299. <div class="section-inner">
  300. <div class="rise-in" style="text-align:center; margin-bottom:40px;">
  301. <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>
  302. <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>
  303. </div>
  304. <div class="rise-in" style="text-align:center; margin-bottom:36px;">
  305. <div style="font-size:3.2rem; margin-bottom:14px;">${c.icon}</div>
  306. <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>
  307. <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>
  308. </div>
  309. <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);">
  310. <p style="color:var(--text-1); font-size:1.08rem; line-height:1.82;">${heroLine}</p>
  311. </div>
  312. <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;">
  313. ${subs.map((sub, i) => `
  314. <div style="padding:16px 14px; background:var(--bg-1); border:1px solid ${c.color}22; border-radius:var(--radius-sm); text-align:center;">
  315. <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>
  316. <div style="color:var(--text-1); font-size:0.86rem; line-height:1.5;">${esc(sub)}</div>
  317. </div>`).join('')}
  318. </div>
  319. <div class="fade-up" style="margin-top:48px; text-align:center;">
  320. <div class="text-mono text-xs" style="color:var(--text-3); letter-spacing:0.3em;">SCROLL DOWN · ${subs.length} 个子章 · 理论 × 实证 × 机会</div>
  321. </div>
  322. </div>
  323. </section>`;
  324. }
  325. // ============================================================
  326. // 占位子章(Coming Soon 提示)· 会展示当前 Seed VOC 覆盖度
  327. // ============================================================
  328. function renderComingSoon({ chapter, subIdx, sub, slideNum, slideTotal, targetBatch, meta }) {
  329. const c = chapter;
  330. const tier = meta?.sourceTier || 'unknown';
  331. const isSeedReady = tier === 'pattern-curated' || tier === 'real-collected';
  332. // 为此章节所映射的假设准备 seed 覆盖提示
  333. const chapterHypothesisMap = {
  334. 'landscape': ['H1', 'H3', 'H5'],
  335. 'unmet': ['H1', 'H2'],
  336. 'fit': ['H1', 'H3', 'H4', 'H5'],
  337. 'kano': ['H1', 'H3', 'H4', 'H5'],
  338. 'scene': ['H5'],
  339. 'competitor': ['H5', 'H6'],
  340. 'opportunity': ['H4', 'H7', 'H8'],
  341. 'blueprint': ['H6', 'H8'],
  342. };
  343. const hs = chapterHypothesisMap[c.key] || [];
  344. const hsCount = hs.reduce((s, h) => s + (meta?.hypotheses?.[h] || 0), 0);
  345. return `<section class="report-section" id="${c.key}-${subIdx}" data-tone="${c.tone}" data-slide-num="${slideNum}/${slideTotal}">
  346. <div class="section-inner">
  347. <div class="section-head rise-in">
  348. <div class="section-eyebrow" style="color:${c.color};">${c.num}.${subIdx} · ${esc(sub.toUpperCase())}</div>
  349. <h2 class="text-h1"><span class="headline-anim">${esc(sub)}</span></h2>
  350. <p class="section-sub">此子章将在 <strong style="color:${c.color};">Batch ${targetBatch}</strong> 填充 VOC 证据卡 + 理论映射 + 决策项</p>
  351. </div>
  352. <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;">
  353. <div style="font-size:3.4rem; margin-bottom:18px;">🚧</div>
  354. <div class="text-h2" style="color:${c.color}; margin-bottom:12px;">Coming in Batch ${targetBatch}</div>
  355. ${isSeedReady ? `
  356. <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;">
  357. <span>✓</span> Seed VOC 已就绪 · ${hsCount} 条 ${hs.join('/')} 相关模式 VOC
  358. </div><br/>
  359. <p style="color:var(--text-2); font-size:0.92rem; max-width:680px; margin:0 auto; line-height:1.7;">
  360. Batch 2 已完成 <strong style="color:${BRAND.HOUGU_TAN};">${meta?.comments || 326} 条公开模式 seed VOC</strong> 采集(覆盖 H1-H8 × 6 平台 × 14 产品维度),<br/>
  361. Batch ${targetBatch} 将基于本假设的 <strong style="color:${c.color};">${hsCount} 条</strong> 证据 + KANO×JTBD 理论工具,输出 <strong>理论映射 × 实证卡片 × 决策项</strong>。
  362. </p>` : `
  363. <p style="color:var(--text-2); font-size:0.95rem; max-width:600px; margin:0 auto; line-height:1.7;">
  364. Batch 2 将完成多平台 VOC 采集,Batch ${targetBatch} 填充本子章内容。
  365. </p>`}
  366. </div>
  367. </div>
  368. </section>`;
  369. }
  370. // ============================================================
  371. // 子章 → Batch 映射(每章归属哪个 Batch)
  372. // ============================================================
  373. const SUB_TO_BATCH = {
  374. 'landscape': 3, 'unmet': 3, 'fit': 3,
  375. 'kano': 4, 'scene': 4,
  376. 'competitor': 5, 'opportunity': 5,
  377. 'blueprint': 6,
  378. 'voice': 7,
  379. };
  380. // ============================================================
  381. // 子章渲染器注册表(Batch 3 已填充 Ch1/Ch2/Ch3;Batch 4-6 逐步补齐)
  382. // ============================================================
  383. const SLIDE_RENDERERS = {
  384. ...CH1, // Ch1 胃病人群全景
  385. ...CH2, // Ch2 未被满足的需求
  386. ...CH3, // Ch3 猴菇饮的机会
  387. ...CH4, // Ch4 KANO × JTBD 深度洞察
  388. ...CH5, // Ch5 场景三元素地图
  389. ...CH6, // Ch6 竞品三轴图谱
  390. ...CH7, // Ch7 新机会 × 新场景 × 新人群
  391. ...CH8, // Ch8 4P 配称蓝图
  392. ...CH9, // Ch9 真声洞察(基于真 VOC 的独家维度)
  393. };
  394. // ============================================================
  395. // buildHTML · 主装配
  396. // ============================================================
  397. function buildHTML({ product, collectedAt } = {}) {
  398. product = product || '江中猴菇饮';
  399. collectedAt = collectedAt || new Date().toISOString().slice(0, 10);
  400. // 加载数据(Batch 1 为 skeleton)
  401. const data = HGY.loadMerged();
  402. const meta = HGY.getMeta(data);
  403. // 计算 slide 总数:2 (cover+agenda) + 每章 (1 divider + N 子章)
  404. let slideTotal = 2;
  405. for (const c of CHAPTERS) {
  406. slideTotal += 1; // divider
  407. slideTotal += (CHAPTER_SUBSECTIONS[c.key] || []).length; // subsections
  408. }
  409. const slides = [];
  410. let num = 1;
  411. // Slide 01 · Cover
  412. slides.push(renderCover({ product, collectedAt, slideNum: sn(num), slideTotal: sn(slideTotal), meta }));
  413. num++;
  414. // Slide 02 · Agenda
  415. slides.push(renderAgenda({ slideNum: sn(num), slideTotal: sn(slideTotal) }));
  416. num++;
  417. // 每章节:divider + 子章们
  418. for (const c of CHAPTERS) {
  419. // Divider
  420. slides.push(renderChapterDivider({ chapter: c, slideNum: sn(num), slideTotal: sn(slideTotal) }));
  421. num++;
  422. // Subsections
  423. const subs = CHAPTER_SUBSECTIONS[c.key] || [];
  424. for (let i = 0; i < subs.length; i++) {
  425. const subIdx = i + 1;
  426. const subKey = `${c.key}-${subIdx}`;
  427. const renderer = SLIDE_RENDERERS[subKey];
  428. const slideCtx = {
  429. chapter: c,
  430. subIdx,
  431. sub: subs[i],
  432. slideNum: sn(num),
  433. slideTotal: sn(slideTotal),
  434. data,
  435. meta,
  436. };
  437. if (renderer) {
  438. slides.push(renderer(slideCtx));
  439. } else {
  440. slides.push(renderComingSoon({ ...slideCtx, targetBatch: SUB_TO_BATCH[c.key] || 3, meta }));
  441. }
  442. num++;
  443. }
  444. }
  445. return `<!DOCTYPE html>
  446. <html lang="zh-CN">
  447. <head>
  448. <meta charset="UTF-8"/>
  449. <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  450. <title>${esc(product)} · VOC 深度洞察报告 · 反向定位</title>
  451. ${STYLES}
  452. </head>
  453. <body>
  454. ${renderNav()}
  455. ${renderKbdHint()}
  456. <div class="progress-line" id="progressLine"></div>
  457. <main class="deck">
  458. ${slides.join('\n')}
  459. </main>
  460. <script>${SCRIPTS}</script>
  461. </body>
  462. </html>`;
  463. }
  464. module.exports = {
  465. buildHTML,
  466. BRAND,
  467. CHAPTERS,
  468. CHAPTER_SUBSECTIONS,
  469. TOTAL_SLIDES_TARGET,
  470. renderCover,
  471. renderAgenda,
  472. renderChapterDivider,
  473. renderComingSoon,
  474. };