| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365 |
- // ==============================================================================
- // 江中乳酸菌素片儿童版 · VOC 深度洞察报告 v2 · 主渲染器
- // ==============================================================================
- // 叙事线:王总 5 步分析路径
- // Ch0 品类全景 · 肠道用药大盘(Step 1) 3 子章
- // Ch1 成人乳酸菌素片 · 品类机会(Step 2) 3 子章
- // Ch2 成人→儿童 · 机会迁移(Step 3) 3 子章
- // Ch3 儿童微生态 · 交叉验证(Step 4) 3 子章
- // Ch4 品类定位 · 里斯×特劳特(Step 5) 4 子章
- // Ch5 落地行动 3 子章
- // 总计 27 slides(cover + agenda + 6 divider + 19 子章)
- // ==============================================================================
- const STYLES = require('./voc-styles.js');
- const SCRIPTS = require('./voc-scripts.js');
- const LA = require('./lactic-analyze.js');
- const C = require('./lactic-components.js');
- const { BRAND, esc, fmtLikes, renderSectionHead, renderVocCard, renderVocGrid,
- renderMatrix2x2, renderStatStrip, renderBarChart, renderCompareTable,
- renderFindingCard, renderInsightHero, renderDecisionList, renderTagCloud,
- renderTheoryBox, renderHypothesisBadge } = C;
- // ============================================================
- // 6 章元信息(对应王总 5 步 + 落地)
- // ============================================================
- const CHAPTERS = [
- { id: 0, num: '0', key: 'category', title: '品类全景 · 肠道用药大盘', en: 'CATEGORY LANDSCAPE', tone: 'blue', color: BRAND.TECH_BLUE, icon: '🔭',
- summary: '微生态制剂 × 肠炎止泻 × 润肠通便 全量扫描 · TAM→SAM→SOM' },
- { id: 1, num: '1', key: 'adult', title: '成人乳酸菌素片 · 品类机会', en: 'ADULT OPPORTUNITY', tone: 'amber', color: BRAND.WARM_ORANGE, icon: '💊',
- summary: '成人 VOC 全景 · 品类机会点 · 竞品差异化空白' },
- { id: 2, num: '2', key: 'migrate', title: '成人→儿童 · 机会迁移', en: 'MIGRATION', tone: 'rose', color: BRAND.BERRY_PINK, icon: '🔄',
- summary: '成人 VOC 中的儿童信号 · 家长决策路径 · 可迁移机会矩阵' },
- { id: 3, num: '3', key: 'cross', title: '儿童微生态 · 交叉验证', en: 'CROSS VALIDATION', tone: 'purple', color: BRAND.PURPLE, icon: '🔬',
- summary: '儿童产品全量扫描 · 竞品空白 × 我们能接住的交叉点' },
- { id: 4, num: '4', key: 'position', title: '品类定位 · 里斯×特劳特', en: 'POSITIONING', tone: 'green', color: BRAND.JIANG_GREEN, icon: '🎯',
- summary: '里斯品类战略 · 特劳特一句话定位 · 三条机会 VOC 验证' },
- { id: 5, num: '5', key: 'action', title: '落地行动', en: 'ACTION PLAN', tone: 'green', color: BRAND.LACTIC_MINT, icon: '🚀',
- summary: '产品策略 · 渠道策略 · P0/P1 行动清单' },
- ];
- // ============================================================
- // 各章节 subsections 清单
- // ============================================================
- const CHAPTER_SUBSECTIONS = {
- 'category': [
- '微生态制剂 × 肠炎止泻 × 润肠通便 · 三大品类扫描',
- '各品类人群需求标签 × 解决问题矩阵',
- 'TAM→SAM→SOM · 市场漏斗与乳酸菌素片区间',
- ],
- 'adult': [
- '成人乳酸菌素片 VOC 全景 · 平台×情感×主题',
- '成人品类机会点 · 竞品差异化空白',
- '品类核心机会提炼 · 可向儿童迁移的信号',
- ],
- 'migrate': [
- '成人 VOC 中的儿童信号扫描 · "妈妈/小孩/儿童"',
- '家长购买决策路径 · 双决策者模型(家长买×孩子吃)',
- '可迁移 vs 不可迁移 · 机会矩阵',
- ],
- 'cross': [
- '儿童微生态制剂全量扫描 · 妈咪爱/亿活/合生元等',
- '交叉验证 · 竞品没满足 + 我们能满足的空白',
- '竞品正反面精简分析 · 药品 vs 食品 vs 营养品',
- ],
- 'position': [
- '里斯品类战略 · 我们在哪个品类?',
- '特劳特定位 · 一句话定位方案',
- '三条细分机会 VOC 验证 · 肠道调理/免疫力/营养吸收',
- '风险评估 · 功效扩展 vs 原有心智冲突',
- ],
- 'action': [
- '产品策略 · 包装/剂型/话术',
- '渠道策略 · 分年龄段打法',
- 'P0/P1 行动清单 × 时间线',
- ],
- };
- // ============================================================
- // 每章 hero line
- // ============================================================
- const CHAPTER_HERO_LINES = {
- category: `王总明确要求:<strong style="color:${BRAND.TECH_BLUE};">"从整个肠道品类入手,不是从乳酸菌素片入手"</strong>。本章从 <strong>微生态制剂(益生菌+乳酸菌素片)、肠炎止泻药、润肠通便药</strong> 三大品类出发,扫描 <strong style="color:${BRAND.LACTIC_MINT};">100+ 亿肠道市场</strong> 的人群需求、标签、解决问题,建立 TAM→SAM→SOM 漏斗。`,
- adult: `王总第二步:<strong style="color:${BRAND.WARM_ORANGE};">"聚焦到乳酸菌素片这个品类,先做成人的完整 VOC 分析"</strong>。成人乳酸菌素片的市场机会是什么?与竞品(整肠生/培菲康/妈咪爱成人版)的差异在哪里?哪些机会可以向儿童迁移?`,
- migrate: `王总第三步:<strong style="color:${BRAND.BERRY_PINK};">"从成人机会迁移到儿童"</strong>——成人 VOC 中能否扒拉出儿童相关的?成人的需求能否迁移到儿童?<strong>妈妈买给孩子用的这部分 VOC</strong> 是最关键的桥梁。`,
- cross: `王总第四步:<strong style="color:${BRAND.PURPLE};">"反向交叉验证"</strong>——用微生态制剂大品类中的"儿童"部分,与 Ch2 得到的儿童机会做交叉,找到<strong style="color:${BRAND.JIANG_GREEN};">"竞品没满足的 + 我们能满足的"</strong>机会。`,
- position: `王总第五步:<strong style="color:${BRAND.JIANG_GREEN};">"用里斯品类战略 + 特劳特定位收敛"</strong>——我在哪个品类?怎么定位?<strong>容易传播的、能写在盒子上的一句话</strong>。三条细分机会(肠道调理/免疫力/营养吸收)逐一 VOC 验证,评估功效扩展与原有心智的冲突风险。`,
- action: `基于 Ch0-Ch4 的层层收敛,输出 <strong style="color:${BRAND.LACTIC_MINT};">可落地的行动清单</strong>:产品策略(包装/剂型/话术)、渠道策略(分年龄段打法)、P0/P1 优先级时间线。从 <strong style="color:${BRAND.JIANG_GREEN};">6000 万到 10 亿的三年路径</strong>。`,
- };
- // ============================================================
- // utils
- // ============================================================
- function sn(n) { return String(n).padStart(2, '0'); }
- // ============================================================
- // 共用组件
- // ============================================================
- function renderNav() {
- const dots = [
- { tip: '封面', href: '#s0' },
- { tip: '目录', href: '#agenda' },
- ...CHAPTERS.map((c) => ({ tip: `${c.num} ${c.title}`, href: `#${c.key}` })),
- ];
- return `<nav class="nav-rail" aria-label="章节导航">
- ${dots.map((d, i) => `<a class="nav-dot${i === 0 ? ' active' : ''}" href="${d.href}" data-tip="${esc(d.tip)}" data-idx="${i}"></a>`).join('')}
- </nav>
- <div class="top-bar">
- <div class="brand-logo">江中<span class="dot">·</span>产品开发部 / 乳酸菌素片儿童版 VOC 深度洞察 v2.0</div>
- <div class="text-mono text-xs" style="color:var(--text-3);">王总 5 步路径 / 品类→成人→迁移→交叉→定位</div>
- </div>`;
- }
- function renderKbdHint() {
- return `<div class="kbd-hint" id="kbdHint">
- <span>键盘切换</span>
- <kbd>↑</kbd><kbd>↓</kbd>
- <span>或</span>
- <kbd>PgUp</kbd><kbd>PgDn</kbd>
- </div>`;
- }
- // ============================================================
- // Cover
- // ============================================================
- function renderCover({ product, collectedAt, slideNum, slideTotal, meta }) {
- const totalComments = meta?.comments || 0;
- const platformCount = Object.values(meta?.platforms || {}).filter((n) => n > 0).length;
- const commentsDisplay = totalComments ? totalComments.toLocaleString() : '待采集';
- return `<section class="report-section" id="s0" data-tone="amber" data-slide-num="${slideNum}/${slideTotal}" style="min-height:100vh;padding:0;">
- <div class="cover-hero">
- <div class="aurora" aria-hidden="true"><span></span><span></span><span></span></div>
- <div class="cover-grid-bg"></div>
- <div class="cover-content">
- <div class="cover-meta rise-in" style="display:flex; justify-content:space-between; align-items:center; gap:16px; flex-wrap:wrap;">
- <span>江中 / 产品开发部 / 2026 · ${esc(product)} · VOC 深度洞察 v2</span>
- <span style="display:inline-flex; align-items:center; gap:6px; padding:4px 12px; background:${BRAND.JIANG_GREEN}15; color:${BRAND.JIANG_GREEN}; border:1px solid ${BRAND.JIANG_GREEN}55; border-radius:16px; font-family:var(--font-mono); font-size:0.72rem; letter-spacing:0.12em; font-weight:600;">
- <span style="font-size:0.9rem;">✓</span>王总 5 步路径 · 结构重构版
- </span>
- </div>
- <div class="cover-title-stack">
- <p class="text-mono rise-in" style="color:${BRAND.LACTIC_MINT}; font-size:0.9rem;letter-spacing:0.2em;margin-bottom:18px;">品类全景 → 成人机会 → 儿童迁移 → 交叉验证 → 里斯×特劳特定位</p>
- <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 深度洞察报告 v2</span></h1>
- <p class="text-poster rise-in" style="margin-top:20px;color:var(--text-2); font-size:1.42rem;">
- 严格遵循<strong style="color:${BRAND.LACTIC_MINT};">王总 5 步分析路径</strong> · 从品类入手而非产品入手
- </p>
- </div>
- <p class="cover-sub fade-up">
- 基于 <strong style="color:${BRAND.LACTIC_MINT};">0420 会议王总核心分析路径</strong>重构:
- Step1 <strong style="color:${BRAND.TECH_BLUE};">肠道品类全景</strong> →
- Step2 <strong style="color:${BRAND.WARM_ORANGE};">成人乳酸菌素片品类机会</strong> →
- Step3 <strong style="color:${BRAND.BERRY_PINK};">成人→儿童机会迁移</strong> →
- Step4 <strong style="color:${BRAND.PURPLE};">儿童微生态交叉验证</strong> →
- Step5 <strong style="color:${BRAND.JIANG_GREEN};">里斯品类战略 × 特劳特定位收敛</strong>。
- 以 <strong style="color:${BRAND.LACTIC_MINT};">${commentsDisplay}</strong> 条真实家长 VOC 为证据底座。
- </p>
- <div class="cover-stats fade-up" style="grid-template-columns:repeat(5, 1fr); max-width:960px;">
- <div class="cover-stat"><div class="num">5 步</div><div class="lbl">王总分析路径</div></div>
- <div class="cover-stat"><div class="num">${commentsDisplay}</div><div class="lbl">真实家长 VOC</div></div>
- <div class="cover-stat"><div class="num">${platformCount}</div><div class="lbl">采集平台</div></div>
- <div class="cover-stat"><div class="num">6</div><div class="lbl">章节</div></div>
- <div class="cover-stat"><div class="num">${slideTotal}</div><div class="lbl">slides</div></div>
- </div>
- <p class="text-mono text-xs fade-up" style="margin-top:40px; color:var(--text-3);">SCROLL DOWN · 采集截止 ${esc(collectedAt || '')} · ${commentsDisplay} 条真实 VOC</p>
- </div>
- </div>
- </section>`;
- }
- // ============================================================
- // Agenda
- // ============================================================
- function renderAgenda({ slideNum, slideTotal }) {
- return `<section class="report-section" id="agenda" data-tone="amber" data-slide-num="${slideNum}/${slideTotal}">
- <div class="section-inner">
- <div class="section-head rise-in">
- <div class="section-eyebrow" style="color:${BRAND.LACTIC_MINT};">CONTENTS · 王总 5 步路径 + 落地</div>
- <h2 class="text-h1"><span class="headline-anim">从品类入手 · 层层收敛到一句话定位</span></h2>
- <p class="section-sub">严格遵循王总要求:<strong style="color:${BRAND.LACTIC_MINT};">品类全景→成人机会→儿童迁移→交叉验证→定位收敛→落地</strong></p>
- </div>
- <div class="grid" style="grid-template-columns:repeat(2, 1fr); gap:22px; margin-top:20px;">
- ${CHAPTERS.map((c, i) => `
- <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'">
- <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>
- <div style="display:flex; justify-content:space-between; align-items:start; margin-bottom:14px;">
- <div class="text-mono text-xs" style="color:${c.color}; letter-spacing:0.18em; font-weight:700;">CH ${c.num} · ${c.en} ${i < 5 ? `· STEP ${i + 1}` : ''}</div>
- <div style="font-size:1.6rem; filter:grayscale(20%);">${c.icon}</div>
- </div>
- <h3 class="text-h2" style="margin:0 0 10px; color:var(--text-1); font-size:1.38rem;">${esc(c.title)}</h3>
- <p style="color:var(--text-2); font-size:0.88rem; line-height:1.6; margin-bottom:12px;">${esc(c.summary)}</p>
- <div style="display:flex; flex-wrap:wrap; gap:5px; margin-bottom:12px;">
- ${(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('')}
- </div>
- </a>`).join('')}
- </div>
- <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);">
- <div class="text-mono text-xs" style="color:${BRAND.LACTIC_MINT}; letter-spacing:0.15em; margin-bottom:10px;">METHODOLOGY · 王总分析路径</div>
- <div style="color:var(--text-1); font-size:0.98rem; line-height:1.72;">
- 本报告 v2 版严格遵循 <strong style="color:${BRAND.LACTIC_MINT};">0420 会议王总 5 步分析路径</strong>重构,解决 v1 版"<strong style="color:${BRAND.CORAL};">太散太乱,不知道怎么看怎么用</strong>"的问题:<br/>
- ① 品类全景入手 → ② 成人品类机会 → ③ 机会迁移到儿童 → ④ 反向交叉验证 → ⑤ 里斯品类战略 + 特劳特定位收敛。<br/>
- 使用客户熟悉的 <strong style="color:${BRAND.JIANG_GREEN};">里斯 + 特劳特</strong> 框架,而非 KANO/混沌等不适用模型。
- </div>
- </div>
- </div>
- </section>`;
- }
- // ============================================================
- // Chapter Divider
- // ============================================================
- function renderChapterDivider({ chapter, slideNum, slideTotal }) {
- const c = chapter;
- const heroLine = CHAPTER_HERO_LINES[c.key] || c.summary;
- const subs = CHAPTER_SUBSECTIONS[c.key] || [];
- const stepLabel = c.id < 5 ? `STEP ${c.id + 1} · ` : '';
- return `<section class="report-section" id="${c.key}" data-tone="${c.tone}" data-slide-num="${slideNum}/${slideTotal}" style="justify-content:center;">
- <div class="section-inner">
- <div class="rise-in" style="text-align:center; margin-bottom:40px;">
- <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>
- <div class="text-mono text-xs" style="color:${c.color}; letter-spacing:0.3em; margin-top:-2rem; font-weight:700;">${stepLabel}CHAPTER ${c.num} · ${c.en}</div>
- </div>
- <div class="rise-in" style="text-align:center; margin-bottom:36px;">
- <div style="font-size:3.2rem; margin-bottom:14px;">${c.icon}</div>
- <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>
- <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>
- </div>
- <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);">
- <p style="color:var(--text-1); font-size:1.08rem; line-height:1.82;">${heroLine}</p>
- </div>
- <div class="fade-up" style="margin-top:40px; display:grid; grid-template-columns:repeat(${Math.min(subs.length, 4)}, 1fr); gap:14px; max-width:1100px; margin-left:auto; margin-right:auto;">
- ${subs.map((sub, i) => `
- <div style="padding:16px 14px; background:var(--bg-1); border:1px solid ${c.color}22; border-radius:var(--radius-sm); text-align:center;">
- <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>
- <div style="color:var(--text-1); font-size:0.86rem; line-height:1.5;">${esc(sub)}</div>
- </div>`).join('')}
- </div>
- <div class="fade-up" style="margin-top:48px; text-align:center;">
- <div class="text-mono text-xs" style="color:var(--text-3); letter-spacing:0.3em;">SCROLL DOWN · ${subs.length} 个子章</div>
- </div>
- </div>
- </section>`;
- }
- // ============================================================
- // 子章渲染器注册
- // ============================================================
- function tryRequire(modPath) {
- try { return require(modPath); } catch (e) { return null; }
- }
- const CH0 = tryRequire('./lactic-v2-ch0-category.js');
- const CH1 = tryRequire('./lactic-v2-ch1-adult.js');
- const CH2 = tryRequire('./lactic-v2-ch2-migrate.js');
- const CH3 = tryRequire('./lactic-v2-ch3-cross.js');
- const CH4 = tryRequire('./lactic-v2-ch4-position.js');
- const CH5 = tryRequire('./lactic-v2-ch5-action.js');
- // ============================================================
- // buildHTML · 主装配
- // ============================================================
- function buildHTML({ product, collectedAt } = {}) {
- product = product || '江中乳酸菌素片儿童版';
- collectedAt = collectedAt || new Date().toISOString().slice(0, 10);
- const data = LA.loadMerged();
- const meta = LA.getMeta(data);
- // slide 总数
- let slideTotal = 2; // cover + agenda
- for (const c of CHAPTERS) {
- slideTotal += 1 + (CHAPTER_SUBSECTIONS[c.key] || []).length;
- }
- const slides = [];
- let num = 1;
- slides.push(renderCover({ product, collectedAt, slideNum: sn(num), slideTotal: sn(slideTotal), meta }));
- num++;
- slides.push(renderAgenda({ slideNum: sn(num), slideTotal: sn(slideTotal) }));
- num++;
- const chModules = [CH0, CH1, CH2, CH3, CH4, CH5];
- for (let ci = 0; ci < CHAPTERS.length; ci++) {
- const c = CHAPTERS[ci];
- slides.push(renderChapterDivider({ chapter: c, slideNum: sn(num), slideTotal: sn(slideTotal) }));
- num++;
- const subs = CHAPTER_SUBSECTIONS[c.key] || [];
- const mod = chModules[ci];
- for (let i = 0; i < subs.length; i++) {
- const subIdx = i + 1;
- const renderFn = mod && mod[`renderSub${subIdx}`];
- const ctx = {
- chapter: c,
- subIdx,
- sub: subs[i],
- slideNum: sn(num),
- slideTotal: sn(slideTotal),
- data,
- meta,
- };
- if (renderFn) {
- slides.push(renderFn(ctx));
- } else {
- slides.push(renderPlaceholder(ctx));
- }
- num++;
- }
- }
- return `<!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8"/>
- <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
- <title>${esc(product)} · VOC 深度洞察报告 v2 · 王总 5 步路径</title>
- ${STYLES}
- </head>
- <body>
- ${renderNav()}
- ${renderKbdHint()}
- <div class="progress-line" id="progressLine"></div>
- <main class="deck">
- ${slides.join('\n')}
- </main>
- <script>${SCRIPTS}</script>
- </body>
- </html>`;
- }
- function renderPlaceholder(ctx) {
- const { chapter: c, subIdx, sub, slideNum, slideTotal } = ctx;
- return `<section class="report-section" id="${c.key}-${subIdx}" data-tone="${c.tone}" data-slide-num="${slideNum}/${slideTotal}">
- <div class="section-inner">
- ${renderSectionHead({
- eyebrow: `${c.num}.${subIdx} · ${c.en}`,
- title: sub || 'Coming Soon',
- subtitle: '此子章将在后续批次填充。',
- color: c.color,
- })}
- <div style="margin-top:40px; padding:40px; background:${c.color}08; border:1px dashed ${c.color}55; border-radius:var(--radius-lg); text-align:center;">
- <div style="font-size:3.4rem; margin-bottom:18px;">🚧</div>
- <div class="text-h2" style="color:${c.color};">待填充</div>
- </div>
- </div>
- </section>`;
- }
- module.exports = {
- buildHTML,
- BRAND,
- CHAPTERS,
- CHAPTER_SUBSECTIONS,
- CHAPTER_HERO_LINES,
- };
|