ks-render-v7.js 66 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871
  1. #!/usr/bin/env node
  2. const fs = require('fs');
  3. const path = require('path');
  4. const ROOT = path.resolve(__dirname, '..', '..', '..');
  5. const OUT_FILE = path.join(ROOT, 'reports', 'ks-lanlingqiuzhi', 'ks-voc-insight-report-v7.html');
  6. const COVER_STATS = [
  7. { num: '7,902', lbl: 'VOC总量' },
  8. { num: '380', lbl: '视频数' },
  9. { num: '19', lbl: '关键词数' },
  10. { num: '9,455万', lbl: '总播放量' },
  11. ];
  12. const KPIS = [
  13. { label: 'VOC总量', value: '7,902', sub: '高质量评论', color: 'var(--amber)' },
  14. { label: '视频覆盖', value: '380', sub: '条视频内容', color: 'var(--green)' },
  15. { label: '情绪方向', value: '负向主导', sub: '风险焦虑最强', color: 'var(--rose)' },
  16. { label: '核心关键词', value: '骗/坑/黑中介', sub: '热度最高', color: 'var(--amber)' },
  17. ];
  18. const KUAIKUAI_DATA = [
  19. { label: '月度服务求职者', value: '超2.5亿人次', note: '截至2024年底' },
  20. { label: '平台蓝领用户规模', value: '约2.5亿人', note: '2024年快聘峰会' },
  21. { label: '全年直播带岗场次', value: '超500万场', note: '2024年全年' },
  22. { label: '日收简历峰值', value: '突破5万份', note: '2025年2月' },
  23. { label: '带动就业机会', value: '4,320万个', note: '截至2024年底' },
  24. ];
  25. const COMPETITORS = [
  26. { name: '快手快聘', type: '直播带岗', users: '2.5亿', trend: '快速增长', color: 'var(--green)', strength: '用户渗透率极高', weak: '信息审核薄弱', barColor: '#00DC82' },
  27. { name: 'BOSS直聘', type: '算法匹配', users: '约3000万', trend: '稳步增长', color: 'var(--blue)', strength: '匹配准确率高', weak: '情绪获得感弱', barColor: '#3B82F6' },
  28. { name: '58同城', type: '分类信息', users: '规模下滑', trend: '持续萎缩', color: 'var(--text-3)', strength: '传统市场份额', weak: '虚假信息泛滥', barColor: '#6B6B6B' },
  29. ];
  30. const RADAR_DATA = [
  31. { label: '信息真实度', ks: 3, boss: 4, wu: 2 },
  32. { label: '薪资透明度', ks: 4, boss: 4, wu: 2 },
  33. { label: '权益保障', ks: 2, boss: 3, wu: 2 },
  34. { label: '匹配准确率', ks: 3, boss: 5, wu: 3 },
  35. { label: '求职低门槛', ks: 5, boss: 4, wu: 3 },
  36. { label: '沟通即时性', ks: 5, boss: 4, wu: 1 },
  37. { label: '信任建立', ks: 4, boss: 3, wu: 1 },
  38. { label: '职业发展', ks: 2, boss: 3, wu: 1 },
  39. { label: '平台审核', ks: 2, boss: 4, wu: 3 },
  40. { label: '情绪获得感', ks: 5, boss: 2, wu: 0 },
  41. ];
  42. const RISK_SIGNALS = [
  43. { label: '骗', value: 252, heat: 514788, color: 'var(--rose)' },
  44. { label: '黑中介', value: 109, heat: 393762, color: 'var(--rose)' },
  45. { label: '坑', value: 92, heat: 375264, color: 'var(--rose)' },
  46. { label: '套路', value: 45, heat: 144842, color: 'var(--orange)' },
  47. { label: '黑厂', value: 43, heat: 123531, color: 'var(--rose)' },
  48. { label: '押金', value: 105, heat: 95543, color: 'var(--amber)' },
  49. { label: '不退', value: 70, heat: 84106, color: 'var(--amber)' },
  50. { label: '陷阱', value: 21, heat: 74090, color: 'var(--orange)' },
  51. ];
  52. const CARE_FACTORS = [
  53. { label: '包吃包住', value: 31, heat: 89190, color: 'var(--green)' },
  54. { label: '工资', value: 186, heat: 89081, color: 'var(--green)' },
  55. { label: '加班费', value: 20, heat: 43986, color: 'var(--blue)' },
  56. { label: '吃住', value: 21, heat: 34060, color: 'var(--green)' },
  57. { label: '住宿', value: 15, heat: 25224, color: 'var(--blue)' },
  58. { label: '当天入职', value: 7, heat: 19264, color: 'var(--purple)' },
  59. { label: '日结', value: 30, heat: 12651, color: 'var(--purple)' },
  60. { label: '发工资', value: 29, heat: 4773, color: 'var(--amber)' },
  61. ];
  62. const CARE_SIGNALS = [
  63. { label: '信息真实不被骗', pct: 85, color: 'var(--rose)' },
  64. { label: '工资按时发放', pct: 72, color: 'var(--green)' },
  65. { label: '包吃包住', pct: 65, color: 'var(--green)' },
  66. { label: '当天入职', pct: 55, color: 'var(--purple)' },
  67. { label: '眼见为实', pct: 48, color: 'var(--blue)' },
  68. ];
  69. const HIGH_LIKED = [
  70. { rank: 1, likes: 6276, expr: '22年差点被骗去当咯咯哒 说什么模特走台下面的人送花就有提成', kw: '入职被坑', hot: true },
  71. { rank: 2, likes: 2209, expr: '去买几个鸡蛋,打碎,放在沙发下面...', kw: '押金不退', hot: true },
  72. { rank: 3, likes: 808, expr: '我在广西就遇到过房东死活不退押金...', kw: '押金不退', hot: false },
  73. { rank: 4, likes: 533, expr: '做的不错,就该那样!有些房东太不像话了!押金不退,还有理由了!', kw: '押金不退', hot: false },
  74. { rank: 5, likes: 491, expr: '无论怎么样,反正黑中介是赚了', kw: '黑中介', hot: false },
  75. { rank: 6, likes: 422, expr: '那暑假工能做什么啊', kw: '入职被坑', hot: false },
  76. { rank: 7, likes: 403, expr: '干的漂亮,支持小伙子不应该判刑...', kw: '黑中介', hot: false },
  77. { rank: 8, likes: 312, expr: '都绑起来了,都投降了,那人还要去喷那个喷雾', kw: '黑中介', hot: false },
  78. { rank: 9, likes: 311, expr: '为什么南方的房东都不退押金', kw: '押金不退', hot: false },
  79. { rank: 10, likes: 228, expr: '只要收身份证的都是,正常的面试只需要填表就行', kw: '黑中介', hot: false },
  80. ];
  81. const SALARY_VOC = [
  82. { likes: 38195, expr: '不给加班费就不加班合理吗?#记录餐饮人生活每一天 #餐饮创业 #现场实拍', kw: '加班费' },
  83. { likes: 21817, expr: '#打工妹 #工资条 #打工妹的日常生活 #电子厂', kw: '发工资' },
  84. { likes: 51, expr: '确实是像你这样说的,跑外卖只要多劳多得,工资不会少发。我坚持了两个月,第3个月的时候我已经摆烂了', kw: '工资靠谱' },
  85. ];
  86. const FOOD_VOC = [
  87. { likes: 84, expr: '我年前就是在洗碗又累又脏,基本上10个小时没得休息,只有吃销10多分钟,我都不想干了', kw: '包吃包住' },
  88. { likes: 17397, expr: '#不体检不收费当天安排 #当天来当天入职安排住宿 #岗位轻松干活不累想工作就来 #包吃包住当天安排住宿', kw: '当天入职' },
  89. { likes: 15768, expr: '距你0.9km #找工作 #包吃包住 #勤杂工 #大龄工', kw: '包吃包住' },
  90. ];
  91. const HYPOTHESES = [
  92. { h: 'H1', title: '快聘是蓝领求职主阵地', count: 596, color: '#F5A623' },
  93. { h: 'H2', title: '直播带岗降低信息不对称', count: 1930, color: '#00DC82' },
  94. { h: 'H3', title: '薪资透明度是核心关切', count: 1842, color: '#8B5CF6' },
  95. { h: 'H4', title: '食宿条件影响求职决策', count: 2194, color: '#FF4D8D' },
  96. { h: 'H5', title: '入职流程便捷性', count: 595, color: '#3B82F6' },
  97. { h: 'H6', title: '求职被骗是普遍焦虑', count: 4435, color: '#EF4444' },
  98. { h: 'H7', title: '黑中介是核心痛点', count: 3705, color: '#F97316' },
  99. { h: 'H8', title: '工资结算方式差异', count: 2417, color: '#6B6B6B' },
  100. ];
  101. const EMOTION_KEYWORDS = [
  102. { word: '"眼见为实"', desc: '消解信息不对称带来的不安全感', emoji: '👁️', color: 'var(--amber)' },
  103. { word: '"老铁推荐"', desc: '以熟人身份降低求职者心理防备', emoji: '🤝', color: 'var(--green)' },
  104. { word: '"来了就是兄弟"', desc: '强调归属感和人情味', emoji: '🏠', color: 'var(--purple)' },
  105. { word: '"有尊严地赚钱"', desc: '回应蓝领的身份焦虑与社会认同诉求', emoji: '💪', color: 'var(--blue)' },
  106. { word: '"不骗人、不套路"', desc: '针对虚假宣传痛点', emoji: '🛡️', color: 'var(--rose)' },
  107. { word: '"独门绝技"', desc: '放大蓝领专业技能价值', emoji: '⚡', color: 'var(--orange)' },
  108. ];
  109. const STRATEGY_RECS = [
  110. { num: '1', title: '建立分级审核体系', desc: '对招聘主播实行分级管理,对粉丝量级大的头部主播实行更审慎的审核标准', color: 'var(--amber)' },
  111. { num: '2', title: '强化"信任基建"投入', desc: '将信息审核能力作为快聘业务的核心基建来建设,建立招聘方信用评级和黑名单机制', color: 'var(--green)' },
  112. { num: '3', title: '深化企业直招占比', desc: '减少对中介端的依赖,提高B端企业直招占比,从根本上降低信息失真风险', color: 'var(--blue)' },
  113. { num: '4', title: '放大"眼见为实"差异化优势', desc: '继续强化直播展示真实工作环境的核心卖点,将车间、宿舍、食堂直播打造为标准化招聘流程', color: 'var(--purple)' },
  114. { num: '5', title: '差异化提升情绪价值', desc: '利用"老铁信任"和"眼见为实"的天然优势,将情绪价值从"内容传播手段"升级为"用户留存壁垒"', color: 'var(--orange)' },
  115. ];
  116. const RISKS = [
  117. { type: '信任透支', desc: '虚假招聘信息泛滥,透支"老铁信任"', prob: '高', impact: '高', strategy: '审核体系升级+信用评级', color: 'var(--rose)' },
  118. { type: '监管压力', desc: '五部门行政指导要求强化审核', prob: '高', impact: '高', strategy: '主动对标"五个到位"', color: 'var(--rose)' },
  119. { type: '竞争跟进', desc: 'BOSS直聘加码蓝领,58同城转型', prob: '中', impact: '中', strategy: '差异化壁垒+效率提升', color: 'var(--amber)' },
  120. { type: '匹配效率低', desc: '流量大但精准度不如算法平台', prob: '中', impact: '中', strategy: '引入精准匹配算法', color: 'var(--amber)' },
  121. ];
  122. const FOUR_P = {
  123. product: {
  124. title: '产品策略:直播带岗 × 信任基建',
  125. points: ['核心产品:快手快聘直播带岗服务', '差异化卖点:眼见为实 / 老铁信任 / 零门槛求职', '产品升级方向:标准化直播带岗流程(车间+宿舍+食堂三景展示)'],
  126. tags: ['直播带岗', '老铁信任', '零门槛求职'],
  127. },
  128. price: {
  129. title: '价格策略:求职者免费 × 企业端付费',
  130. points: ['求职者端:完全免费(零门槛核心优势)', '企业端:按效果付费(CPM/CPC/简历投递量)', '主播端:带岗主播抽佣机制'],
  131. tags: ['零门槛', '效果付费', 'C端免费'],
  132. },
  133. place: {
  134. title: '渠道策略:B端入驻 + C端自然渗透',
  135. points: ['B端:24万家入驻用人单位,提高企业直招占比', '知名企业合作(创维/顺丰/伯恩光学)', 'C端:2.5亿蓝领月活用户自然覆盖', '40万+专职带岗主播'],
  136. tags: ['24万企业', '2.5亿用户', '40万主播'],
  137. },
  138. promotion: {
  139. title: '促销策略:情绪价值 × 信任传播',
  140. points: ['品牌传播:"10个蓝领9个用快手" / "直播带岗,眼见为实"', '内容营销:《来活了老铁!》综艺IP+短剧营销', '政策背书:直播招聘师认证+人社部认可', '老铁文化:"来了就是兄弟"+"有尊严地赚钱"'],
  141. tags: ['综艺IP', '老铁文化', '政策背书'],
  142. },
  143. };
  144. function esc(s) { return String(s ?? '').replace(/[&<>"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c])); }
  145. function fmtLikes(n) { if (n == null) return '0'; if (n >= 10000) return (n / 10000).toFixed(1).replace(/\.0$/, '') + 'w'; if (n >= 1000) return (n / 1000).toFixed(1).replace(/\.0$/, '') + 'k'; return String(n); }
  146. function barChart(rows, accent, maxW = 900) {
  147. const max = Math.max(...rows.map(r => r.heat), 1);
  148. const barH = 36, gap = 14, padL = 120, padR = 120, padV = 16, W = maxW;
  149. const totalH = rows.length * (barH + gap) + padV * 2;
  150. return `<div style="background:var(--bg-1);border:1px solid var(--border);border-radius:var(--radius-md);padding:20px 24px;margin-bottom:24px;">
  151. <svg viewBox="0 0 ${W} ${totalH}" style="width:100%;height:auto;" preserveAspectRatio="xMidYMid meet">
  152. ${rows.map((r, i) => {
  153. const y = padV + i * (barH + gap);
  154. const w = Math.max((r.heat / max) * (W - padL - padR), 6);
  155. return `<text x="${padL - 10}" y="${y + barH / 2 + 5}" text-anchor="end" font-size="13" fill="var(--text-2)">${esc(r.label)}</text>
  156. <rect x="${padL}" y="${y}" width="${w}" height="${barH}" rx="4" fill="${r.color || accent}" opacity="0.85"/>
  157. <text x="${padL + w + 10}" y="${y + barH / 2 + 5}" font-size="13" font-weight="700" fill="${r.color || accent}" font-family="monospace">${esc(r.heat.toLocaleString())}</text>`;
  158. }).join('')}
  159. </svg>
  160. </div>`;
  161. }
  162. function careBarChart(rows, maxW = 800) {
  163. const max = Math.max(...rows.map(r => r.pct), 1);
  164. const barH = 36, gap = 14, padL = 140, padR = 100, padV = 16, W = maxW;
  165. const totalH = rows.length * (barH + gap) + padV * 2;
  166. return `<div style="background:var(--bg-1);border:1px solid var(--border);border-radius:var(--radius-md);padding:20px 24px;">
  167. <svg viewBox="0 0 ${W} ${totalH}" style="width:100%;height:auto;" preserveAspectRatio="xMidYMid meet">
  168. ${rows.map((r, i) => {
  169. const y = padV + i * (barH + gap);
  170. const w = Math.max((r.pct / 100) * (W - padL - padR), 8);
  171. return `<text x="${padL - 10}" y="${y + barH / 2 + 5}" text-anchor="end" font-size="13" fill="var(--text-2)">${esc(r.label)}</text>
  172. <rect x="${padL}" y="${y}" width="${w}" height="${barH}" rx="4" fill="${r.color}" opacity="0.85"/>
  173. <text x="${padL + w + 10}" y="${y + barH / 2 + 5}" font-size="13" font-weight="700" fill="${r.color}" font-family="monospace">${r.pct}%</text>`;
  174. }).join('')}
  175. </svg>
  176. </div>`;
  177. }
  178. function competitorBarChart() {
  179. const W = 800, barH = 48, gap = 20, padV = 20;
  180. const rows = COMPETITORS;
  181. const max = 25000;
  182. const totalH = rows.length * (barH + gap) + padV * 2;
  183. return `<div style="background:var(--bg-1);border:1px solid var(--border);border-radius:var(--radius-md);padding:20px 24px;margin-bottom:24px;">
  184. <svg viewBox="0 0 ${W} ${totalH}" style="width:100%;height:auto;" preserveAspectRatio="xMidYMid meet">
  185. ${rows.map((r, i) => {
  186. const y = padV + i * (barH + gap);
  187. const barW = r.users === '规模下滑' || r.users === '约3000万' ? (r.users === '规模下滑' ? 60 : (3000 / max) * (W - 160 - 120)) : (25000 / max) * (W - 160 - 120);
  188. return `<text x="150" y="${y + barH / 2 + 5}" text-anchor="end" font-size="14" font-weight="600" fill="${r.color}">${esc(r.name)}</text>
  189. <rect x="160" y="${y}" width="${Math.max(barW, 8)}" height="${barH}" rx="4" fill="${r.barColor}" opacity="0.85"/>
  190. <text x="${160 + Math.max(barW, 8) + 10}" y="${y + barH / 2 + 5}" font-size="13" font-weight="700" fill="${r.barColor}" font-family="monospace">${esc(r.users)}</text>
  191. <text x="160" y="${y + barH + 14}" font-size="11" fill="var(--text-3)">${esc(r.type)} · ${esc(r.trend)}</text>`;
  192. }).join('')}
  193. </svg>
  194. </div>`;
  195. }
  196. function radarChart() {
  197. const cx = 200, cy = 190, r = 130;
  198. const labels = RADAR_DATA.map(d => d.label);
  199. const ksVals = RADAR_DATA.map(d => d.ks);
  200. const bossVals = RADAR_DATA.map(d => d.boss);
  201. const wuVals = RADAR_DATA.map(d => d.wu);
  202. const n = labels.length;
  203. const step = 2 * Math.PI / n;
  204. const toXY = (v, i, R = r) => ({ x: cx + R * v * Math.cos(i * step - Math.PI / 2), y: cy + R * v * Math.sin(i * step - Math.PI / 2) });
  205. const ksPath = ksVals.map((v, i) => toXY(v / 5, i)).map((p, i) => (i === 0 ? 'M' : 'L') + p.x + ',' + p.y).join('') + 'Z';
  206. const bossPath = bossVals.map((v, i) => toXY(v / 5, i)).map((p, i) => (i === 0 ? 'M' : 'L') + p.x + ',' + p.y).join('') + 'Z';
  207. const wuPath = wuVals.map((v, i) => toXY(v / 5, i)).map((p, i) => (i === 0 ? 'M' : 'L') + p.x + ',' + p.y).join('') + 'Z';
  208. const gridPath = [1, 0.5, 0.25].map(s => [0, 1, 2, 3, 4].map(i => toXY(s, i)).map((p, i) => (i === 0 ? 'M' : 'L') + p.x + ',' + p.y).join('') + 'Z').join('');
  209. let labelHTML = labels.map((l, i) => {
  210. const p = toXY(1.15, i);
  211. return `<text x="${p.x}" y="${p.y}" text-anchor="middle" dominant-baseline="middle" font-size="10" fill="var(--text-2)">${esc(l)}</text>`;
  212. }).join('');
  213. return `<div style="background:var(--bg-1);border:1px solid var(--border);border-radius:var(--radius-md);padding:20px;">
  214. <svg viewBox="0 0 400 380" style="width:100%;height:auto;">
  215. <circle cx="${cx}" cy="${cy}" r="${r}" fill="none" stroke="var(--border)" stroke-width="1"/>
  216. <path d="${gridPath}" fill="none" stroke="var(--border)" stroke-width="0.5" opacity="0.5"/>
  217. <path d="${wuPath}" fill="rgba(107,107,107,0.15)" stroke="#6B6B6B" stroke-width="2"/>
  218. <path d="${bossPath}" fill="rgba(59,130,246,0.15)" stroke="#3B82F6" stroke-width="2"/>
  219. <path d="${ksPath}" fill="rgba(0,220,130,0.2)" stroke="#00DC82" stroke-width="2.5"/>
  220. ${labelHTML}
  221. <g transform="translate(${cx + r + 20}, 20)">
  222. <rect x="0" y="0" width="12" height="12" fill="#00DC82"/><text x="18" y="10" font-size="11" fill="var(--text-2)">快手</text>
  223. <rect x="0" y="25" width="12" height="12" fill="#3B82F6"/><text x="18" y="35" font-size="11" fill="var(--text-2)">BOSS直聘</text>
  224. <rect x="0" y="50" width="12" height="12" fill="#6B6B6B"/><text x="18" y="60" font-size="11" fill="var(--text-2)">58同城</text>
  225. </g>
  226. </svg>
  227. </div>`;
  228. }
  229. function voiceCard(comment, isHot = false) {
  230. const borderColor = isHot ? 'var(--amber)' : 'var(--rose)';
  231. const tagBg = isHot ? 'rgba(245,166,35,0.1)' : 'rgba(255,77,141,0.1)';
  232. const tagColor = isHot ? 'var(--amber)' : 'var(--rose)';
  233. return `<div class="voice-card ${isHot ? 'hot' : ''}" style="border-left:3px solid ${borderColor};">
  234. <div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
  235. <span style="width:22px;height:22px;border-radius:50%;background:var(--bg-2);color:var(--text-1);font-size:0.6rem;font-weight:700;display:flex;align-items:center;justify-content:center;">快</span>
  236. <span style="font-size:0.8rem;color:var(--text-3);">快手用户</span>
  237. <span style="margin-left:auto;font-size:0.78rem;font-weight:600;color:${isHot ? 'var(--amber)' : 'var(--rose)'};">♥ ${fmtLikes(comment.likes)}</span>
  238. </div>
  239. <div class="voice-content">"${esc(comment.expr)}"</div>
  240. <div class="voice-meta">
  241. <span class="voice-tag" style="background:${tagBg};color:${tagColor};">${esc(comment.kw)}</span>
  242. </div>
  243. </div>
  244. `;}
  245. function insightBanner(num, title, body, dataPoints) {
  246. const dps = dataPoints.map(dp => `<div class="dp"><div class="num">${esc(dp.num)}</div><div class="lbl">${esc(dp.lbl)}</div></div>`).join('');
  247. return `<div class="insight-banner" data-num="${num}">
  248. <h3>${title}</h3>
  249. <p class="insight-body">${body}</p>
  250. <div class="insight-data">${dps}</div>
  251. </div>`;
  252. }
  253. function hypothesisBarChart() {
  254. const max = Math.max(...HYPOTHESES.map(h => h.count));
  255. const barH = 28, gap = 12, padL = 100, padR = 100, padV = 16, W = 800;
  256. const totalH = HYPOTHESES.length * (barH + gap) + padV * 2;
  257. return `<div style="background:var(--bg-1);border:1px solid var(--border);border-radius:var(--radius-md);padding:20px 24px;">
  258. <div style="min-width:520px;">
  259. <svg viewBox="0 0 ${W} ${totalH}" style="width:100%;height:auto;" preserveAspectRatio="xMidYMid meet">
  260. ${HYPOTHESES.map((h, i) => {
  261. const y = padV + i * (barH + gap);
  262. const w = Math.max((h.count / max) * (W - padL - padR), 8);
  263. return `<text x="${padL - 10}" y="${y + barH / 2 + 5}" text-anchor="end" font-size="11" fill="var(--text-2)">${esc(h.h)}</text>
  264. <rect x="${padL}" y="${y}" width="${w}" height="${barH}" rx="4" fill="${h.color}" opacity="0.85"/>
  265. <text x="${padL + w + 8}" y="${y + barH / 2 + 5}" font-size="11" font-weight="700" fill="${h.color}">${h.count.toLocaleString()}</text>`;
  266. }).join('')}
  267. </svg>
  268. </div>
  269. </div>`;
  270. }
  271. function fourPGrid() {
  272. const p = FOUR_P;
  273. const items = [
  274. { title: p.product.title, points: p.product.points, tags: p.product.tags, color: 'var(--blue)' },
  275. { title: p.price.title, points: p.price.points, tags: p.price.tags, color: 'var(--green)' },
  276. { title: p.place.title, points: p.place.points, tags: p.place.tags, color: 'var(--amber)' },
  277. { title: p.promotion.title, points: p.promotion.points, tags: p.promotion.tags, color: 'var(--purple)' },
  278. ];
  279. return `<div class="grid grid-2">${items.map(item => `
  280. <div class="card" style="border-top:3px solid ${item.color};">
  281. <div class="text-h3" style="color:${item.color};margin-bottom:14px;">${esc(item.title)}</div>
  282. <div style="font-size:0.85rem;color:var(--text-2);line-height:1.8;margin-bottom:14px;">
  283. ${item.points.map(pt => `<div style="margin-bottom:6px;">• ${esc(pt)}</div>`).join('')}
  284. </div>
  285. <div style="display:flex;gap:8px;flex-wrap:wrap;">
  286. ${item.tags.map(t => `<span class="tag" style="background:${item.color}22;color:${item.color};border-color:${item.color}44;">${esc(t)}</span>`).join('')}
  287. </div>
  288. </div>`).join('')}</div>`;
  289. }
  290. const html = `<!DOCTYPE html>
  291. <html lang="zh-CN">
  292. <head>
  293. <meta charset="UTF-8">
  294. <meta name="viewport" content="width=device-width,initial-scale=1.0">
  295. <title>快手快聘 · VOC 深度洞察报告 v7</title>
  296. <style>
  297. :root{--bg-0:#0A0A0A;--bg-1:#111111;--bg-2:#1A1A1A;--bg-3:#252525;--border:rgba(255,255,255,0.08);--border-strong:rgba(255,255,255,0.16);--text-1:#F5F5F5;--text-2:#A8A8A8;--text-3:#6B6B6B;--amber:#F5A623;--amber-glow:rgba(245,166,35,0.15);--green:#00DC82;--purple:#8B5CF6;--rose:#FF4D8D;--blue:#3B82F6;--red:#EF4444;--orange:#F97316;--font-head:'Inter','Noto Sans SC','PingFang SC','Microsoft YaHei',sans-serif;--font-body:'Noto Sans SC','Inter','PingFang SC','Microsoft YaHei',sans-serif;--font-mono:'JetBrains Mono','SF Mono','Fira Code',monospace;--max-w:1280px;--radius-sm:8px;--radius-md:12px;--radius-lg:20px;}
  298. *{margin:0;padding:0;box-sizing:border-box}
  299. html{scroll-behavior:smooth;font-size:16px;scroll-snap-type:y proximity;}
  300. body{font-family:var(--font-body);background:var(--bg-0);color:var(--text-1);line-height:1.65;-webkit-font-smoothing:antialiased;overflow-x:hidden}
  301. body::before{content:'';position:fixed;inset:0;pointer-events:none;z-index:0;background:radial-gradient(ellipse 900px 700px at var(--amb-x,20%) var(--amb-y,25%),rgba(245,166,35,0.10),transparent 60%),radial-gradient(ellipse 800px 600px at calc(100% - var(--amb-x,20%)) calc(100% - var(--amb-y,25%)),rgba(139,92,246,0.07),transparent 60%);transition:background 1.4s cubic-bezier(.2,.7,.2,1);}
  302. body::after{content:'';position:fixed;inset:0;pointer-events:none;z-index:0;background-image:linear-gradient(rgba(255,255,255,0.015) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,0.015) 1px,transparent 1px);background-size:80px 80px;mask-image:radial-gradient(ellipse 90% 95% at 50% 50%,black 0%,transparent 85%);-webkit-mask-image:radial-gradient(ellipse 90% 95% at 50% 50%,black 0%,transparent 85%);}
  303. main.deck{position:relative;z-index:1}
  304. ::selection{background:var(--amber);color:#000}
  305. .text-hero{font-family:var(--font-head);font-weight:900;letter-spacing:-0.04em;line-height:0.95;font-size:clamp(4rem,12vw,10rem)}
  306. .text-poster{font-family:var(--font-head);font-weight:800;letter-spacing:-0.03em;line-height:1.05;font-size:clamp(2.5rem,6vw,5rem)}
  307. .text-h1{font-family:var(--font-head);font-weight:700;letter-spacing:-0.02em;line-height:1.1;font-size:clamp(2rem,4vw,3.5rem)}
  308. .text-h2{font-family:var(--font-head);font-weight:700;letter-spacing:-0.015em;line-height:1.2;font-size:clamp(1.4rem,2.5vw,2rem)}
  309. .text-h3{font-weight:600;font-size:clamp(1.05rem,1.6vw,1.3rem);letter-spacing:-0.01em}
  310. .text-body{font-size:clamp(0.92rem,1.1vw,1rem);line-height:1.75;color:var(--text-2)}
  311. .text-sm{font-size:0.85rem;color:var(--text-2)}
  312. .text-xs{font-size:0.72rem;color:var(--text-3);letter-spacing:0.02em}
  313. .text-mono{font-family:var(--font-mono)}
  314. .text-amber{color:var(--amber)}.text-green{color:var(--green)}.text-purple{color:var(--purple)}.text-rose{color:var(--rose)}.text-blue{color:var(--blue)}.text-red{color:var(--red)}.text-orange{color:var(--orange)}
  315. section.report-section{min-height:100vh;padding:clamp(60px,10vh,120px) clamp(24px,6vw,80px);position:relative;border-bottom:1px solid var(--border);display:flex;flex-direction:column;justify-content:center;scroll-snap-align:start;scroll-snap-stop:always;overflow:hidden;isolation:isolate}
  316. section.report-section::before{content:attr(data-slide-num);position:absolute;top:clamp(20px,3vh,36px);right:clamp(24px,4vw,60px);font-family:var(--font-mono);font-weight:600;font-size:clamp(0.68rem,0.75vw,0.78rem);letter-spacing:0.25em;color:var(--text-3);z-index:4;pointer-events:none}
  317. section.report-section::after{content:'';position:absolute;inset:0;pointer-events:none;z-index:-1;background:radial-gradient(ellipse 70% 60% at 85% 15%,var(--slide-glow-1,rgba(245,166,35,0.06)),transparent 55%),radial-gradient(ellipse 60% 50% at 15% 85%,var(--slide-glow-2,rgba(139,92,246,0.04)),transparent 55%);opacity:0;transition:opacity 1s cubic-bezier(.2,.7,.2,1)}
  318. section.report-section.in-view::after{opacity:1}
  319. section.report-section[data-tone="amber"]{--slide-glow-1:rgba(245,166,35,0.10);--slide-glow-2:rgba(245,166,35,0.04)}
  320. section.report-section[data-tone="rose"]{--slide-glow-1:rgba(255,77,141,0.09);--slide-glow-2:rgba(255,77,141,0.04)}
  321. section.report-section[data-tone="green"]{--slide-glow-1:rgba(0,220,130,0.08);--slide-glow-2:rgba(0,220,130,0.03)}
  322. section.report-section[data-tone="purple"]{--slide-glow-1:rgba(139,92,246,0.10);--slide-glow-2:rgba(139,92,246,0.04)}
  323. section.report-section[data-tone="blue"]{--slide-glow-1:rgba(59,130,246,0.08);--slide-glow-2:rgba(59,130,246,0.03)}
  324. section.report-section[data-tone="orange"]{--slide-glow-1:rgba(249,115,22,0.10);--slide-glow-2:rgba(249,115,22,0.04)}
  325. .cover-hero{position:absolute;inset:0;display:flex;flex-direction:column;justify-content:center;padding:clamp(40px,8vh,100px) clamp(24px,6vw,80px);background:radial-gradient(ellipse 60% 80% at 30% 30%,rgba(245,166,35,0.08) 0%,transparent 70%),radial-gradient(ellipse 40% 60% at 80% 70%,rgba(139,92,246,0.05) 0%,transparent 70%),var(--bg-0)}
  326. .cover-grid-bg{position:absolute;inset:0;background-image:linear-gradient(rgba(255,255,255,0.025) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,0.025) 1px,transparent 1px);background-size:60px 60px;mask-image:radial-gradient(ellipse 80% 80% at 50% 50%,black 0%,transparent 70%);-webkit-mask-image:radial-gradient(ellipse 80% 80% at 50% 50%,black 0%,transparent 70%)}
  327. .cover-content{position:relative;z-index:2;max-width:var(--max-w)}
  328. .cover-meta{display:flex;align-items:center;gap:16px;font-family:var(--font-mono);font-size:0.78rem;color:var(--text-3);letter-spacing:0.18em;text-transform:uppercase;margin-bottom:32px}
  329. .cover-meta::before{content:'';width:32px;height:2px;background:var(--amber)}
  330. .cover-title-stack{display:flex;flex-direction:column;gap:4px}
  331. .cover-title{font-family:var(--font-head);font-weight:900;letter-spacing:-0.04em;line-height:0.92;font-size:clamp(3.5rem,11vw,9rem);color:#fff;background:linear-gradient(135deg,#fff 30%,rgba(245,166,35,0.9) 100%);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
  332. .cover-sub{font-size:clamp(1rem,1.6vw,1.4rem);color:var(--text-2);max-width:780px;margin-top:32px;line-height:1.7}
  333. .cover-stats{display:grid;grid-template-columns:repeat(4,1fr);gap:clamp(12px,2vw,28px);margin-top:clamp(48px,8vh,96px);padding-top:clamp(24px,4vh,48px);border-top:1px solid var(--border);max-width:900px}
  334. .cover-stat .num{font-family:var(--font-head);font-weight:800;font-size:clamp(1.6rem,2.8vw,2.4rem);letter-spacing:-0.02em;color:var(--amber)}
  335. .cover-stat .lbl{font-size:0.78rem;color:var(--text-3);margin-top:4px;font-family:var(--font-mono);letter-spacing:0.05em;text-transform:uppercase}
  336. @media(max-width:720px){.cover-stats{grid-template-columns:repeat(2,1fr)}}
  337. .nav-rail{position:fixed;left:clamp(16px,2vw,32px);top:50%;transform:translateY(-50%);z-index:100;display:flex;flex-direction:column;gap:12px}
  338. .nav-dot{width:32px;height:3px;border-radius:2px;background:rgba(255,255,255,0.14);cursor:pointer;transition:all 0.5s cubic-bezier(.2,.7,.2,1);position:relative;display:flex;align-items:center}
  339. .nav-dot.active{background:var(--amber);width:48px;box-shadow:0 0 12px rgba(245,166,35,0.4)}
  340. .nav-dot:hover{background:rgba(255,255,255,0.3)}
  341. .nav-dot::after{content:attr(data-tip);position:absolute;left:calc(100% + 16px);top:50%;transform:translateY(-50%) translateX(-6px);background:rgba(20,20,20,0.92);backdrop-filter:blur(8px);border:1px solid var(--border);padding:6px 12px;border-radius:6px;font-size:0.72rem;color:var(--text-1);white-space:nowrap;opacity:0;pointer-events:none;transition:opacity 0.25s ease,transform 0.25s ease;font-family:var(--font-mono);letter-spacing:0.04em}
  342. .nav-dot:hover::after,.nav-dot.active::after{opacity:1;transform:translateY(-50%) translateX(0)}
  343. .progress-line{position:fixed;top:0;left:0;height:2px;background:linear-gradient(90deg,var(--amber),var(--rose));z-index:1000;width:0%;transition:width 0.3s ease}
  344. .top-bar{position:fixed;top:0;left:0;right:0;display:flex;justify-content:space-between;align-items:center;padding:18px clamp(24px,4vw,48px);z-index:99;pointer-events:none;background:linear-gradient(180deg,rgba(10,10,10,0.85) 0%,transparent 100%)}
  345. .brand-logo{font-family:var(--font-head);font-weight:900;font-size:0.88rem;letter-spacing:0.2em}
  346. .brand-logo .dot{color:var(--amber)}
  347. .slide-counter{position:fixed;left:clamp(16px,2vw,28px);bottom:clamp(16px,2vh,28px);z-index:100;display:flex;align-items:center;gap:8px;padding:8px 14px;border-radius:999px;background:rgba(20,20,20,0.85);backdrop-filter:blur(16px);border:1px solid var(--border-strong);font-family:var(--font-mono);font-size:0.74rem;letter-spacing:0.1em;color:var(--text-2)}
  348. .slide-counter .now{color:var(--amber);font-weight:700}
  349. .slide-counter .bar{width:60px;height:2px;background:rgba(255,255,255,0.1);border-radius:2px;overflow:hidden}
  350. .slide-counter .bar::after{content:'';display:block;height:100%;width:var(--pct,0%);background:linear-gradient(90deg,var(--amber),var(--rose));transition:width 0.6s cubic-bezier(.2,.7,.2,1)}
  351. .kbd-hint{position:fixed;right:clamp(16px,2vw,28px);bottom:clamp(16px,2vh,28px);z-index:100;display:flex;align-items:center;gap:10px;padding:10px 16px;border-radius:999px;background:rgba(20,20,20,0.85);backdrop-filter:blur(16px);border:1px solid var(--border-strong);font-family:var(--font-mono);font-size:0.72rem;color:var(--text-2);letter-spacing:0.04em;transition:opacity 0.6s ease,transform 0.6s ease}
  352. .kbd-hint.fade{opacity:0;transform:translateY(12px);pointer-events:none}
  353. .kbd-hint kbd{display:inline-flex;align-items:center;justify-content:center;min-width:22px;height:22px;padding:0 6px;background:var(--bg-3);border:1px solid var(--border-strong);border-radius:4px;font-size:0.7rem;font-family:var(--font-mono);color:var(--text-1);box-shadow:0 1px 0 rgba(0,0,0,0.4)}
  354. .section-inner{width:100%;max-width:var(--max-w);margin:0 auto;position:relative}
  355. .section-head{margin-bottom:clamp(32px,5vh,64px)}
  356. .section-eyebrow{display:inline-flex;align-items:center;gap:10px;font-family:var(--font-mono);font-size:0.72rem;letter-spacing:0.18em;text-transform:uppercase;color:var(--amber);font-weight:500;margin-bottom:18px}
  357. .section-eyebrow::before{content:'';display:inline-block;width:24px;height:1px;background:var(--amber)}
  358. .section-sub{color:var(--text-2);font-size:1.05rem;max-width:720px;margin-top:14px;line-height:1.8}
  359. .grid{display:grid;gap:clamp(14px,1.6vw,22px)}
  360. .grid-2{grid-template-columns:repeat(auto-fit,minmax(320px,1fr))}
  361. .grid-3{grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}
  362. .grid-4{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}
  363. .card{background:linear-gradient(180deg,rgba(255,255,255,0.025) 0%,rgba(255,255,255,0.008) 100%),var(--bg-1);border:1px solid var(--border);border-radius:var(--radius-md);padding:clamp(20px,2.2vw,32px);position:relative;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);transition:transform 0.4s cubic-bezier(.2,.7,.2,1),border-color 0.3s ease,box-shadow 0.4s ease}
  364. .card:hover{border-color:var(--border-strong);transform:translateY(-3px);box-shadow:0 12px 40px rgba(0,0,0,0.35),0 0 0 1px rgba(245,166,35,0.08)}
  365. .voice-card:hover{transform:translateY(-2px);box-shadow:0 8px 24px rgba(0,0,0,0.3);border-color:var(--border-strong);}
  366. .card.card-glow{box-shadow:0 0 60px rgba(245,166,35,0.12),0 0 120px rgba(245,166,35,0.06)}
  367. .card.card-glow-green{box-shadow:0 0 60px rgba(0,220,130,0.12),0 0 120px rgba(0,220,130,0.06)}
  368. .card.card-glow-rose{box-shadow:0 0 60px rgba(255,77,141,0.12),0 0 120px rgba(255,77,141,0.06)}
  369. .card-accent{border-left:3px solid var(--amber);border-top:1px solid var(--border);border-right:1px solid var(--border);border-bottom:1px solid var(--border)}
  370. .metric{background:var(--bg-1);border:1px solid var(--border);border-radius:var(--radius-md);padding:clamp(24px,2.5vw,36px);position:relative;overflow:hidden}
  371. .metric::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,var(--amber),transparent);opacity:0.6}
  372. .metric .metric-label{font-family:var(--font-mono);font-size:0.7rem;letter-spacing:0.15em;text-transform:uppercase;color:var(--text-3);margin-bottom:10px}
  373. .metric .metric-value{font-family:var(--font-head);font-weight:800;font-size:clamp(2.2rem,4vw,3.5rem);letter-spacing:-0.03em;line-height:1;color:var(--text-1);margin-bottom:8px}
  374. .metric .metric-unit{font-family:var(--font-mono);font-size:0.85rem;color:var(--amber);font-weight:500}
  375. .metric .metric-sub{margin-top:14px;font-size:0.82rem;color:var(--text-2);line-height:1.5}
  376. .voice-card{background:var(--bg-1);border:1px solid var(--border);border-radius:var(--radius-md);padding:20px 24px;margin-bottom:14px;transition:all 0.3s ease;position:relative;overflow:visible}
  377. .voice-card::before{content:'';position:absolute;top:0;bottom:0;left:0;width:3px;height:auto;background:var(--rose);opacity:0.6;transition:opacity 0.3s;border-radius:var(--radius-md) 0 0 var(--radius-md);}
  378. .voice-card.hot{border-color:rgba(245,166,35,0.3)}
  379. .voice-card.hot::before{opacity:1;background:var(--amber)}
  380. .voice-card:hover{border-color:var(--border-strong);transform:translateY(-2px);box-shadow:0 8px 24px rgba(0,0,0,0.3)}
  381. .voice-card .voice-content{font-size:0.96rem;line-height:1.7;color:var(--text-1);margin:12px 0;font-weight:400}
  382. .voice-card .voice-meta{display:flex;gap:8px;flex-wrap:wrap}
  383. .voice-tag{display:inline-flex;padding:3px 10px;background:rgba(255,77,141,0.1);color:var(--rose);border-radius:4px;font-size:0.72rem;font-weight:500}
  384. .voice-card.hot .voice-tag{background:rgba(245,166,35,0.1);color:var(--amber)}
  385. .tag{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:4px;font-size:0.72rem;font-weight:500;letter-spacing:0.02em;font-family:var(--font-head);background:rgba(245,166,35,0.1);color:var(--amber);border:1px solid rgba(245,166,35,0.22)}
  386. .tag.green{background:rgba(0,220,130,0.08);color:var(--green);border-color:rgba(0,220,130,0.2)}
  387. .tag.purple{background:rgba(139,92,246,0.08);color:var(--purple);border-color:rgba(139,92,246,0.2)}
  388. .tag.rose{background:rgba(255,77,141,0.08);color:var(--rose);border-color:rgba(255,77,141,0.2)}
  389. .tag.blue{background:rgba(59,130,246,0.08);color:var(--blue);border-color:rgba(59,130,246,0.2)}
  390. .tag.orange{background:rgba(249,115,22,0.08);color:var(--orange);border-color:rgba(249,115,22,0.2)}
  391. .tag.red{background:rgba(239,68,68,0.08);color:var(--red);border-color:rgba(239,68,68,0.2)}
  392. .cover-hero .aurora{position:absolute;inset:0;pointer-events:none;overflow:hidden;z-index:1}
  393. .cover-hero .aurora span{position:absolute;border-radius:50%;filter:blur(80px);opacity:0.5}
  394. .cover-hero .aurora span:nth-child(1){width:500px;height:500px;background:rgba(245,166,35,0.35);top:-10%;left:10%;animation:aurora-drift-1 18s ease-in-out infinite alternate}
  395. .cover-hero .aurora span:nth-child(2){width:420px;height:420px;background:rgba(139,92,246,0.30);bottom:-8%;right:10%;animation:aurora-drift-2 22s ease-in-out infinite alternate}
  396. .cover-hero .aurora span:nth-child(3){width:300px;height:300px;background:rgba(0,220,130,0.22);top:40%;left:55%;animation:aurora-drift-3 26s ease-in-out infinite alternate}
  397. @keyframes aurora-drift-1{from{transform:translate(0,0) scale(1)}to{transform:translate(60px,80px) scale(1.2)}}
  398. @keyframes aurora-drift-2{from{transform:translate(0,0) scale(1)}to{transform:translate(-80px,-60px) scale(1.15)}}
  399. @keyframes aurora-drift-3{from{transform:translate(0,0) scale(1)}to{transform:translate(-100px,40px) scale(1.3)}}
  400. @media(prefers-reduced-motion:reduce){.cover-hero .aurora span{animation:none}}
  401. .insight-banner{min-height:260px;background:var(--bg-1);border:1px solid var(--border);border-radius:var(--radius-md);padding:clamp(32px,4vw,56px);display:flex;flex-direction:column;justify-content:space-between;gap:32px;position:relative;overflow:hidden;margin-bottom:28px}
  402. .insight-banner::before{content:attr(data-num);position:absolute;top:16px;right:24px;font-family:var(--font-head);font-weight:900;font-size:clamp(4rem,9vw,7rem);line-height:1;color:var(--bg-3);letter-spacing:-0.05em;pointer-events:none}
  403. .insight-banner h3{font-family:var(--font-head);font-weight:800;font-size:clamp(1.5rem,2.8vw,2.2rem);letter-spacing:-0.02em;line-height:1.25;color:var(--text-1);max-width:85%}
  404. .insight-banner h3 strong,.insight-banner h3 span{color:var(--amber);font-weight:900}
  405. .insight-banner .insight-body{color:var(--text-2);font-size:1rem;line-height:1.75;max-width:85%}
  406. .insight-banner .insight-data{display:flex;gap:24px;margin-top:16px;padding-top:20px;border-top:1px solid var(--border)}
  407. .insight-banner .insight-data .dp{display:flex;flex-direction:column;gap:2px}
  408. .insight-banner .insight-data .num{font-family:var(--font-mono);font-size:1.3rem;font-weight:700;color:var(--amber)}
  409. .insight-banner .insight-data .lbl{font-size:0.72rem;color:var(--text-3);text-transform:uppercase;letter-spacing:0.1em}
  410. .data-tbl{width:100%;border-collapse:collapse;font-size:0.88rem}
  411. .data-tbl thead th{text-align:left;padding:14px 16px;background:var(--bg-1);border-bottom:1px solid var(--border);font-family:var(--font-mono);font-weight:500;font-size:0.72rem;letter-spacing:0.08em;text-transform:uppercase;color:var(--text-3)}
  412. .data-tbl tbody td{padding:14px 16px;border-bottom:1px solid var(--border);color:var(--text-1);vertical-align:middle}
  413. .data-tbl tbody tr:last-child td{border-bottom:none}
  414. .data-tbl tbody tr:hover{background:rgba(255,255,255,0.02)}
  415. .data-tbl .tb-num{font-family:var(--font-mono);font-weight:600;color:var(--amber)}
  416. .table-wrap{overflow-x:auto;border-radius:var(--radius-md);border:1px solid var(--border)}
  417. .theme-poster{background:var(--bg-1);border:1px solid var(--border);border-radius:var(--radius-md);padding:28px;position:relative;overflow:hidden}
  418. .theme-poster.pain{border-top:3px solid var(--rose)}
  419. .theme-poster.bright{border-top:3px solid var(--green)}
  420. .theme-poster.scene{border-top:3px solid var(--blue)}
  421. .theme-poster h4{font-family:var(--font-head);font-weight:700;font-size:1.3rem;margin-bottom:8px}
  422. .theme-poster .count{font-family:var(--font-mono);font-size:0.78rem;color:var(--text-3);margin-bottom:16px}
  423. .theme-poster .count .num{font-size:1.4rem;font-weight:700;color:var(--text-1);margin-right:4px}
  424. .theme-poster .examples{list-style:none;display:flex;flex-direction:column;gap:10px;margin-top:12px}
  425. .theme-poster .examples li{padding:10px 14px;background:var(--bg-2);border-left:2px solid var(--border-strong);border-radius:4px;font-size:0.85rem;line-height:1.55;color:var(--text-2)}
  426. .theme-poster.pain .examples li{border-left-color:var(--rose)}
  427. .theme-poster.bright .examples li{border-left-color:var(--green)}
  428. .theme-poster.scene .examples li{border-left-color:var(--blue)}
  429. .fade-up{opacity:1;transform:none;transition:opacity 0.9s cubic-bezier(.16,.84,.33,1),transform 0.9s cubic-bezier(.16,.84,.33,1)}
  430. html.js .fade-up{opacity:0;transform:translateY(32px);will-change:opacity,transform}
  431. html.js .fade-up.is-visible{opacity:1;transform:translateY(0)}
  432. footer{text-align:center;padding:48px clamp(24px,6vw,80px);border-top:1px solid var(--border);color:var(--text-3);font-size:0.8rem;background:var(--bg-0)}
  433. footer .meta{font-family:var(--font-mono);letter-spacing:0.12em;text-transform:uppercase;margin-top:8px;font-size:0.7rem}
  434. @media(max-width:900px){.nav-rail{display:none}}
  435. </style>
  436. </head>
  437. <body>
  438. <div class="progress-line" id="progressLine"></div>
  439. <header class="top-bar"><div class="brand-logo">快手快聘<span class="dot">·</span>VOC</div></header>
  440. <nav class="nav-rail" id="navRail"></nav>
  441. <div class="slide-counter"><span class="now" id="slideNow">01</span><div class="bar"><div id="slideBar"></div></div><span id="slideTotal">09</span></div>
  442. <div class="kbd-hint"><kbd>↓</kbd><kbd>↑</kbd> 滚动切换</div>
  443. <main class="deck">
  444. <!-- 封面 -->
  445. <section class="report-section cover-hero" data-tone="amber" style="min-height:100vh;">
  446. <div class="cover-grid-bg"></div>
  447. <div class="aurora"><span></span><span></span><span></span></div>
  448. <div class="cover-content">
  449. <div class="cover-meta">VOC 深度洞察报告 · v7</div>
  450. <div class="cover-title-stack">
  451. <div class="cover-title">快手快聘</div>
  452. </div>
  453. <p class="cover-sub">"10个蓝领,9个都用快手"——蓝领求职生态与快聘战略洞察</p>
  454. <div class="cover-stats">
  455. ${COVER_STATS.map(s => `<div class="cover-stat"><div class="num">${esc(s.num)}</div><div class="lbl">${esc(s.lbl)}</div></div>`).join('')}
  456. </div>
  457. </div>
  458. </section>
  459. <!-- Ch1: 执行摘要 -->
  460. <section class="report-section" data-slide-num="01" data-tone="amber" id="ch1">
  461. <div class="section-inner">
  462. <div class="section-head">
  463. <div class="section-eyebrow">CHAPTER 01</div>
  464. <h2 class="text-h1">执行摘要</h2>
  465. <p class="section-sub">核心问题与机会分析</p>
  466. </div>
  467. <div class="grid grid-4" style="margin-bottom:32px;">
  468. ${KPIS.map(k => `<div class="metric"><div class="metric-label">${esc(k.label)}</div><div class="metric-value" style="color:${k.color};">${esc(k.value)}</div><div class="metric-sub">${esc(k.sub)}</div></div>`).join('')}
  469. </div>
  470. <div class="card card-glow card-accent" style="margin-bottom:28px;padding:28px 32px;">
  471. <div style="font-family:var(--font-mono);font-size:0.72rem;letter-spacing:0.15em;color:var(--amber);margin-bottom:14px;font-weight:600;">核心洞察</div>
  472. <div class="text-h2" style="margin-bottom:12px;">蓝领求职最担心什么?最看重什么?</div>
  473. <p class="text-body">最强焦虑不是"工资低",而是"怕被骗"——信息不真实、被坑、黑中介、押金不退、工资被克扣</p>
  474. </div>
  475. <div class="grid grid-3">
  476. <div class="card" style="border-left:3px solid var(--rose);">
  477. <div style="font-size:1.6rem;margin-bottom:12px;">🛡️</div>
  478. <div style="font-weight:700;margin-bottom:8px;">最强焦虑:被骗</div>
  479. <p class="text-sm">"骗/黑中介/坑"相关风险词热度突破100万,VOC占比最高</p>
  480. </div>
  481. <div class="card" style="border-left:3px solid var(--green);">
  482. <div style="font-size:1.6rem;margin-bottom:12px;">💰</div>
  483. <div style="font-weight:700;margin-bottom:8px;">最强诉求:确定性</div>
  484. <p class="text-sm">工资真实、发薪稳定、包吃包住、当天入职、能快速安顿</p>
  485. </div>
  486. <div class="card" style="border-left:3px solid var(--purple);">
  487. <div style="font-size:1.6rem;margin-bottom:12px;">🤝</div>
  488. <div style="font-weight:700;margin-bottom:8px;">情绪核心:信任</div>
  489. <p class="text-sm">"老铁文化"+"眼见为实"是快聘最难被竞品复制的竞争壁垒</p>
  490. </div>
  491. </div>
  492. </div>
  493. </section>
  494. <!-- Ch2: 快聘生态现状 -->
  495. <section class="report-section" data-slide-num="02" data-tone="orange" id="ch2">
  496. <div class="section-inner">
  497. <div class="section-head">
  498. <div class="section-eyebrow" style="color:var(--orange);">CHAPTER 02</div>
  499. <h2 class="text-h1">快聘生态现状</h2>
  500. <p class="section-sub">市场规模、竞争格局与战略位势</p>
  501. </div>
  502. <div class="grid grid-4" style="margin-bottom:32px;">
  503. ${KUAIKUAI_DATA.map(d => `<div class="metric"><div class="metric-label">${esc(d.label)}</div><div class="metric-value" style="color:var(--green);">${esc(d.value)}</div><div class="metric-sub">${esc(d.note)}</div></div>`).join('')}
  504. </div>
  505. <div class="card card-glow" style="border-left:3px solid var(--green);margin-bottom:28px;padding:28px 32px;">
  506. <div style="font-family:var(--font-mono);font-size:0.72rem;letter-spacing:0.15em;color:var(--green);margin-bottom:14px;font-weight:600;">战略定位</div>
  507. <div class="text-h2" style="margin-bottom:12px;">"10个蓝领9个用快手——直播带岗,眼见为实"</div>
  508. <p class="text-body">快聘日活几十万简历,内部仅次于电商;2.5亿蓝领月活是最大壁垒;但信息审核弱是最大短板</p>
  509. </div>
  510. <div class="grid grid-3" style="margin-bottom:24px;">
  511. ${COMPETITORS.map(c => `
  512. <div class="card" style="border-top:3px solid ${c.color};">
  513. <div style="font-weight:700;color:${c.color};margin-bottom:10px;">${esc(c.name)}</div>
  514. <div style="font-size:0.82rem;color:var(--text-2);line-height:1.7;">
  515. <div style="margin-bottom:6px;"><span style="color:var(--green);">✓</span> ${esc(c.strength)}</div>
  516. <div style="margin-bottom:6px;"><span style="color:var(--green);">✓</span> 用户:${esc(c.users)} · ${esc(c.type)}</div>
  517. <div><span style="color:var(--rose);">⚠</span> ${esc(c.weak)}</div>
  518. </div>
  519. </div>`).join('')}
  520. </div>
  521. <div class="fade-up">
  522. <div style="font-family:var(--font-mono);font-size:0.72rem;letter-spacing:0.15em;color:var(--orange);margin-bottom:14px;font-weight:600;">📊 平台竞争要素雷达图</div>
  523. ${radarChart()}
  524. </div>
  525. </div>
  526. </section>
  527. <!-- Ch3: 蓝领求职心理洞察 -->
  528. <section class="report-section" data-slide-num="03" data-tone="rose" id="ch3">
  529. <div class="section-inner">
  530. <div class="section-head">
  531. <div class="section-eyebrow" style="color:var(--rose);">CHAPTER 03</div>
  532. <h2 class="text-h1">蓝领求职心理洞察</h2>
  533. <p class="section-sub">最担心什么?最看重什么?</p>
  534. </div>
  535. <div class="grid grid-2" style="margin-bottom:28px;">
  536. <div>
  537. <div style="font-family:var(--font-mono);font-size:0.72rem;letter-spacing:0.15em;color:var(--rose);margin-bottom:16px;font-weight:600;">TOP 焦虑来源(按热度)</div>
  538. ${barChart(RISK_SIGNALS.slice(0, 6), 'var(--rose)')}
  539. </div>
  540. <div>
  541. <div style="font-family:var(--font-mono);font-size:0.72rem;letter-spacing:0.15em;color:var(--green);margin-bottom:16px;font-weight:600;">TOP 诉求来源(按热度)</div>
  542. ${barChart(CARE_FACTORS.slice(0, 6), 'var(--green)')}
  543. </div>
  544. </div>
  545. ${insightBanner('01', '蓝领求职者最需要的是<strong>确定性</strong>——确定工作是真的、<strong>工资会发</strong>、<strong>不会被坑</strong>、能快速安顿下来', '情绪价值的核心不是娱乐,而是"确定性":靠谱、真实、放心、少踩坑。', [
  546. { num: '85%', lbl: '担心信息不真实' },
  547. { num: '72%', lbl: '关注工资按时发放' },
  548. { num: '65%', lbl: '关注包吃包住' },
  549. ])}
  550. <div class="grid grid-3">
  551. <div class="card" style="border-left:3px solid var(--rose);">
  552. <div style="font-size:1.6rem;margin-bottom:12px;">🛡️</div>
  553. <div style="font-weight:700;color:var(--rose);margin-bottom:8px;">焦虑1:信息虚假</div>
  554. <p class="text-sm">承诺月薪近万,实际到手4000多;薪资货不对版是头号痛点</p>
  555. </div>
  556. <div class="card" style="border-left:3px solid var(--amber);">
  557. <div style="font-size:1.6rem;margin-bottom:12px;">💰</div>
  558. <div style="font-weight:700;color:var(--amber);margin-bottom:8px;">焦虑2:押金陷阱</div>
  559. <p class="text-sm">预缴押金/培训费后失联,押金不退是高频次焦虑</p>
  560. </div>
  561. <div class="card" style="border-left:3px solid var(--purple);">
  562. <div style="font-size:1.6rem;margin-bottom:12px;">🤝</div>
  563. <div style="font-weight:700;color:var(--purple);margin-bottom:8px;">焦虑3:权益保障</div>
  564. <p class="text-sm">不签合同、不上保险,维权举证难、责任界定难</p>
  565. </div>
  566. </div>
  567. </div>
  568. </section>
  569. <!-- Ch4: 核心关切:薪资与权益 -->
  570. <section class="report-section" data-slide-num="04" data-tone="amber" id="ch4">
  571. <div class="section-inner">
  572. <div class="section-head">
  573. <div class="section-eyebrow">CHAPTER 04</div>
  574. <h2 class="text-h1">核心关切:薪资与权益</h2>
  575. <p class="section-sub">工资、食宿、入职便捷性</p>
  576. </div>
  577. <div class="grid grid-2" style="margin-bottom:28px;">
  578. <div>
  579. <div style="font-family:var(--font-mono);font-size:0.72rem;letter-spacing:0.15em;color:var(--amber);margin-bottom:14px;font-weight:600;">薪资权益关键词热度</div>
  580. ${barChart(CARE_FACTORS, 'var(--amber)')}
  581. </div>
  582. <div>
  583. <div style="font-family:var(--font-mono);font-size:0.72rem;letter-spacing:0.15em;color:var(--green);margin-bottom:14px;font-weight:600;">高赞薪资VOC</div>
  584. ${SALARY_VOC.map(v => voiceCard(v, false)).join('\n')}
  585. </div>
  586. </div>
  587. ${insightBanner('02', '薪资透明度是<strong>核心关切</strong>,但最核心的购买点是"确定性"——<strong>包吃包住+当天入职+不发愁</strong>', '蓝领求职者决策链极短,追求"今天看、明天就上班"。快聘能快速集结大批求职者,春节高峰期效果尤为显著。', [
  588. { num: '89,190', lbl: '"包吃包住"热度' },
  589. { num: '89,081', lbl: '"工资"热度' },
  590. { num: '19,264', lbl: '"当天入职"热度' },
  591. ])}
  592. <div class="grid grid-2">
  593. <div>
  594. <div style="font-family:var(--font-mono);font-size:0.72rem;letter-spacing:0.15em;color:var(--green);margin-bottom:14px;font-weight:600;">食宿条件VOC</div>
  595. ${FOOD_VOC.map(v => voiceCard(v, false)).join('\n')}
  596. </div>
  597. <div>
  598. <div style="font-family:var(--font-mono);font-size:0.72rem;letter-spacing:0.15em;color:var(--rose);margin-bottom:14px;font-weight:600;">TOP10 高赞评论</div>
  599. ${HIGH_LIKED.slice(0, 5).map(v => voiceCard(v, v.hot)).join('\n')}
  600. </div>
  601. </div>
  602. </div>
  603. </section>
  604. <!-- Ch5: 风险感知:被骗焦虑 -->
  605. <section class="report-section" data-slide-num="05" data-tone="rose" id="ch5">
  606. <div class="section-inner">
  607. <div class="section-head">
  608. <div class="section-eyebrow" style="color:var(--rose);">CHAPTER 05</div>
  609. <h2 class="text-h1">风险感知:被骗焦虑</h2>
  610. <p class="section-sub">黑中介、押金陷阱、入职被套路</p>
  611. </div>
  612. <div class="grid grid-3" style="margin-bottom:28px;">
  613. <div class="theme-poster pain">
  614. <h4>黑中介 · 最强风险信号</h4>
  615. <div class="count">热度 <span class="num">393,762</span> · 109条命中</div>
  616. <ul class="examples">
  617. <li>"万恶之源 #黑中介的噩梦,只要我不努力,你就过不上你想要的生活" 热度203,219</li>
  618. <li>"#严厉打击黑中介 #黑中介骗了多少中国同胞" 热度61,984</li>
  619. <li>"第一次进厂是通过直播,就是黑中介..." ♥226</li>
  620. </ul>
  621. </div>
  622. <div class="theme-poster pain">
  623. <h4>押金陷阱 · 高频次焦虑</h4>
  624. <div class="count">热度 <span class="num">95,543</span> · 105条命中</div>
  625. <ul class="examples">
  626. <li>"不退租客押金你是心高气傲,被租客报复你是生死难料" 热度55,223</li>
  627. <li>"去买几个鸡蛋,打碎,放在沙发下面..." ♥2,209(TOP2高赞)</li>
  628. <li>"我在广西就遇到过房东死活不退押金" ♥808</li>
  629. </ul>
  630. </div>
  631. <div class="theme-poster pain">
  632. <h4>入职被坑 · 薪资货不对版</h4>
  633. <div class="count">热度 <span class="num">375,264</span> · 92条命中</div>
  634. <ul class="examples">
  635. <li>"刚开始签18块/时,干了1个月只拿6块/时,只拿了1200" 热度42,121</li>
  636. <li>"只要收身份证的都是,正常的面试只需要填表就行" ♥228</li>
  637. <li>"地铁安检我知道,找中介还不如直接去地铁口问" ♥224</li>
  638. </ul>
  639. </div>
  640. </div>
  641. <div class="grid grid-2">
  642. <div>
  643. <div style="font-family:var(--font-mono);font-size:0.72rem;letter-spacing:0.15em;color:var(--rose);margin-bottom:14px;font-weight:600;">TOP10 高赞风险评论</div>
  644. ${HIGH_LIKED.map(v => voiceCard(v, v.hot)).join('\n')}
  645. </div>
  646. <div>
  647. <div style="font-family:var(--font-mono);font-size:0.72rem;letter-spacing:0.15em;color:var(--rose);margin-bottom:14px;font-weight:600;">风险关键词完整排行</div>
  648. ${barChart(RISK_SIGNALS, 'var(--rose)')}
  649. </div>
  650. </div>
  651. </div>
  652. </section>
  653. <!-- Ch6: 平台信任机制 -->
  654. <section class="report-section" data-slide-num="06" data-tone="purple" id="ch6">
  655. <div class="section-inner">
  656. <div class="section-head">
  657. <div class="section-eyebrow" style="color:var(--purple);">CHAPTER 06</div>
  658. <h2 class="text-h1">平台信任机制</h2>
  659. <p class="section-sub">老铁文化、直播带岗、情绪关键词体系</p>
  660. </div>
  661. <div class="grid grid-3" style="margin-bottom:32px;">
  662. ${EMOTION_KEYWORDS.map(e => `
  663. <div class="card" style="border-top:3px solid ${e.color};">
  664. <div style="font-size:2rem;margin-bottom:12px;">${e.emoji}</div>
  665. <div class="text-h3" style="color:${e.color};margin-bottom:8px;">${esc(e.word)}</div>
  666. <p class="text-sm">${esc(e.desc)}</p>
  667. </div>`).join('')}
  668. </div>
  669. ${insightBanner('06', '情绪价值是<strong>双刃剑</strong>——真实故事获得关注,但<strong>虚假摆拍</strong>正在侵蚀信任基础', '外卖骑手、快递小哥、网约车司机因"接地气""高共鸣"属性,成为短视频平台虚假摆拍重灾区。平台算法无差别推荐,使虚假内容获得杠杆式传播。', [
  670. { num: '★★★★★', lbl: '情绪获得感优势' },
  671. { num: '⚠️高风险', lbl: '虚假摆拍泛滥' },
  672. { num: '五部门', lbl: '行政指导监管' },
  673. ])}
  674. <div class="card card-glow" style="border-left:3px solid var(--purple);padding:28px 32px;margin-bottom:28px;">
  675. <div style="font-family:var(--font-mono);font-size:0.72rem;letter-spacing:0.15em;color:var(--purple);margin-bottom:14px;font-weight:600;">标杆案例 · 快手《来活了老铁!》</div>
  676. <div class="text-h2" style="margin-bottom:12px;">快手联合山东卫视推出的首档蓝领求职综艺IP</div>
  677. <p class="text-body">25个热搜榜单(微博主榜6个),最高总榜TOP21,完成度625%。站外曝光量原定2.5亿,实际完成3.26亿,完成度130%。围绕"技能向+娱乐向+社会向"三个维度展开。</p>
  678. <div style="margin-top:14px;display:flex;gap:10px;">
  679. <span class="tag purple">技能向</span>
  680. <span class="tag purple">娱乐向</span>
  681. <span class="tag purple">社会向</span>
  682. </div>
  683. </div>
  684. <div class="grid grid-2">
  685. <div class="card" style="border-top:3px solid var(--green);">
  686. <div style="font-weight:700;color:var(--green);margin-bottom:14px;">老铁信任体系</div>
  687. <div style="font-size:0.85rem;color:var(--text-2);line-height:1.8;">
  688. <div style="margin-bottom:8px;">• 熟人推荐式信任</div>
  689. <div style="margin-bottom:8px;">• 主播与粉丝长期关系积累</div>
  690. <div style="margin-bottom:8px;">• 评论区实时互动答疑</div>
  691. <div>• "来了就是兄弟"文化</div>
  692. </div>
  693. </div>
  694. <div class="card" style="border-top:3px solid var(--blue);">
  695. <div style="font-weight:700;color:var(--blue);margin-bottom:14px;">直播带岗信任机制</div>
  696. <div style="font-size:0.85rem;color:var(--text-2);line-height:1.8;">
  697. <div style="margin-bottom:8px;">• 车间直播展示</div>
  698. <div style="margin-bottom:8px;">• 宿舍直播展示</div>
  699. <div style="margin-bottom:8px;">• 食堂直播展示</div>
  700. <div>• 眼见为实消除信息不对称</div>
  701. </div>
  702. </div>
  703. </div>
  704. </div>
  705. </section>
  706. <!-- Ch7: 机会与建议 -->
  707. <section class="report-section" data-slide-num="07" data-tone="green" id="ch7">
  708. <div class="section-inner">
  709. <div class="section-head">
  710. <div class="section-eyebrow" style="color:var(--green);">CHAPTER 07</div>
  711. <h2 class="text-h1">机会与建议</h2>
  712. <p class="section-sub">快聘优势强化、风险应对、战略方向</p>
  713. </div>
  714. <div class="grid grid-2" style="margin-bottom:28px;">
  715. ${STRATEGY_RECS.slice(0, 4).map(r => `
  716. <div class="card" style="border-top:3px solid ${r.color};">
  717. <div style="display:flex;align-items:center;gap:12px;margin-bottom:14px;">
  718. <div style="width:32px;height:32px;border-radius:50%;background:${r.color};color:#000;font-weight:800;display:flex;align-items:center;justify-content:center;font-size:0.9rem;">${esc(r.num)}</div>
  719. <div class="text-h3">${esc(r.title)}</div>
  720. </div>
  721. <p class="text-sm">${esc(r.desc)}</p>
  722. </div>`).join('')}
  723. </div>
  724. <div class="card" style="border-top:3px solid var(--rose);margin-bottom:28px;">
  725. <div style="font-family:var(--font-mono);font-size:0.72rem;letter-spacing:0.15em;color:var(--rose);margin-bottom:14px;font-weight:600;">⚠️ 风险矩阵</div>
  726. <div class="table-wrap">
  727. <table class="data-tbl">
  728. <thead><tr><th>风险类型</th><th>描述</th><th>概率</th><th>影响</th><th>应对策略</th></tr></thead>
  729. <tbody>
  730. ${RISKS.map(r => `<tr style="background:${r.color}11;">
  731. <td><strong style="color:${r.color};">${esc(r.type)}</strong></td>
  732. <td style="color:var(--text-2);font-size:0.85rem;">${esc(r.desc)}</td>
  733. <td style="color:${r.color};font-weight:600;">${esc(r.prob)}</td>
  734. <td style="color:${r.color};font-weight:600;">${esc(r.impact)}</td>
  735. <td style="color:var(--text-2);font-size:0.82rem;">${esc(r.strategy)}</td>
  736. </tr>`).join('')}
  737. </tbody>
  738. </table>
  739. </div>
  740. </div>
  741. <div class="card" style="border-top:3px solid var(--green);padding:28px 32px;">
  742. <div style="font-family:var(--font-mono);font-size:0.72rem;letter-spacing:0.15em;color:var(--green);margin-bottom:14px;font-weight:600;">📋 五部门行政指导要求</div>
  743. <div class="grid grid-2">
  744. <div style="font-size:0.85rem;color:var(--text-2);line-height:1.8;">
  745. <div style="margin-bottom:8px;">① <strong style="color:var(--text-1);">资质审核到位</strong> — 对招聘方资质进行核验</div>
  746. <div style="margin-bottom:8px;">② <strong style="color:var(--text-1);">信息明示到位</strong> — 招聘信息完整透明</div>
  747. <div>③ <strong style="color:var(--text-1);">内容审核到位</strong> — 虚假信息严格过滤</div>
  748. </div>
  749. <div style="font-size:0.85rem;color:var(--text-2);line-height:1.8;">
  750. <div style="margin-bottom:8px;">④ <strong style="color:var(--text-1);">风险防范到位</strong> — 建立风险预警机制</div>
  751. <div>⑤ <strong style="color:var(--text-1);">处置管控到位</strong> — 违规内容快速处置</div>
  752. </div>
  753. </div>
  754. </div>
  755. </div>
  756. </section>
  757. <!-- Ch8: 快聘4P营销策略 -->
  758. <section class="report-section" data-slide-num="08" data-tone="blue" id="ch8">
  759. <div class="section-inner">
  760. <div class="section-head">
  761. <div class="section-eyebrow" style="color:var(--blue);">CHAPTER 08</div>
  762. <h2 class="text-h1">快聘 4P 营销策略</h2>
  763. <p class="section-sub">产品 / 价格 / 渠道 / 促销</p>
  764. </div>
  765. ${fourPGrid()}
  766. ${insightBanner('08', '基于VOC的内容策略:围绕<strong>"确定性"</strong>构建传播矩阵', '最强购买点:工资真实、发薪稳定、包吃包住,当天入职。最强焦虑:被骗、被坑、黑中介、押金不退。内容方向应围绕"真实岗位展示+主播真实推荐+快速入职承诺"。', [
  767. { num: '20+', lbl: '情绪关键词体系' },
  768. { num: '3', lbl: '核心传播维度' },
  769. { num: '1个', lbl: '综艺IP标杆' },
  770. ])}
  771. <div style="font-family:var(--font-mono);font-size:0.72rem;letter-spacing:0.15em;color:var(--blue);margin-bottom:14px;font-weight:600;">📊 H1-H8 假设验证</div>
  772. ${hypothesisBarChart()}
  773. </div>
  774. </section>
  775. <!-- Ch9: VOC证据锚点 -->
  776. <section class="report-section" data-slide-num="09" data-tone="amber" id="ch9">
  777. <div class="section-inner">
  778. <div class="section-head">
  779. <div class="section-eyebrow">CHAPTER 09</div>
  780. <h2 class="text-h1">VOC 证据锚点</h2>
  781. <p class="section-sub">可直接引用的用户原声</p>
  782. </div>
  783. <div class="grid grid-2">
  784. <div>
  785. <div style="font-family:var(--font-mono);font-size:0.72rem;letter-spacing:0.15em;color:var(--rose);margin-bottom:14px;font-weight:600;">🔥 薪资焦虑类高赞VOC</div>
  786. ${SALARY_VOC.map(v => voiceCard(v, false)).join('\n')}
  787. </div>
  788. <div>
  789. <div style="font-family:var(--font-mono);font-size:0.72rem;letter-spacing:0.15em;color:var(--rose);margin-bottom:14px;font-weight:600;">🔥 风险担忧类高赞VOC TOP10</div>
  790. ${HIGH_LIKED.map(v => voiceCard(v, v.hot)).join('\n')}
  791. </div>
  792. </div>
  793. <div class="card card-glow" style="border-left:3px solid var(--green);margin-top:28px;padding:28px 32px;">
  794. <div style="font-family:var(--font-mono);font-size:0.72rem;letter-spacing:0.15em;color:var(--green);margin-bottom:14px;font-weight:600;">💡 核心洞察</div>
  795. <div class="text-h3" style="margin-bottom:12px;">情绪价值的核心不是娱乐,而是"确定性"</div>
  796. <p class="text-body">快手在蓝领求职场景中的优势,来自内容触达、直播解释、即时互动和高频决策场景的天然匹配。需求高频、风险敏感、决策链短、转化诉求强。</p>
  797. </div>
  798. </div>
  799. </section>
  800. </main>
  801. <footer>
  802. <div>快手快聘 · VOC 深度洞察报告</div>
  803. <div class="meta">数据来源:TikHub API 快手评论采集 | VOC 7,902条 | 视频380条 | 19个关键词 | 播放量9,455万</div>
  804. <div class="meta">数据截至:2026年4月 | 报告版本:v7</div>
  805. </footer>
  806. <script>
  807. (function(){
  808. const slides=document.querySelectorAll('.report-section');
  809. const nav=document.getElementById('navRail');
  810. const now=document.getElementById('slideNow');
  811. const bar=document.getElementById('slideBar');
  812. const total=document.getElementById('slideTotal');
  813. const progress=document.getElementById('progressLine');
  814. const tips=['封面','执行摘要','快聘生态','心理洞察','薪资权益','风险焦虑','信任机制','机会建议','4P策略','VOC锚点'];
  815. let current=0;
  816. tips.forEach((t,i)=>{const d=document.createElement('div');d.className='nav-dot';d.setAttribute('data-tip',t);d.addEventListener('click',()=>slides[i]&&slides[i].scrollIntoView({behavior:'smooth'}));nav.appendChild(d);});
  817. const update=()=>{const idx=Math.round(window.scrollY/window.innerHeight);current=Math.max(0,Math.min(idx,slides.length-1));now.textContent=String(current+1).padStart(2,'0');bar.style.setProperty('--pct',((current+1)/slides.length*100)+'%');progress.style.width=((current+1)/slides.length*100)+'%';document.querySelectorAll('.nav-dot').forEach((d,i)=>{d.classList.toggle('active',i===current);});};
  818. window.addEventListener('scroll',update,{passive:true});
  819. document.addEventListener('keydown',e=>{if(e.key==='ArrowDown'||e.key==='PageDown'){e.preventDefault();slides[current+1]&&slides[current+1].scrollIntoView({behavior:'smooth'});}else if(e.key==='ArrowUp'||e.key==='PageUp'){e.preventDefault();slides[current-1]&&slides[current-1].scrollIntoView({behavior:'smooth'});}});
  820. document.querySelectorAll('.kbd-hint')[0]&&document.querySelectorAll('.kbd-hint')[0].classList.add('fade');
  821. window.addEventListener('scroll',()=>{document.querySelectorAll('.kbd-hint')[0]&&document.querySelectorAll('.kbd-hint')[0].classList.add('fade');},{passive:true});
  822. total.textContent=String(slides.length).padStart(2,'0');
  823. update();
  824. document.documentElement.classList.add('js');
  825. const observer=new IntersectionObserver(entries=>{entries.forEach(e=>{if(e.isIntersecting){e.target.classList.add('in-view');const fadeups=e.target.querySelectorAll('.fade-up');fadeups.forEach((el,i)=>setTimeout(()=>el.classList.add('is-visible'),i*100));}}});slides.forEach(s=>observer.observe(s));
  826. })();
  827. </script>
  828. </body>
  829. </html>`;
  830. fs.writeFileSync(OUT_FILE, html, 'utf-8');
  831. console.log('HTML report generated:', OUT_FILE);
  832. console.log('File size:', (fs.statSync(OUT_FILE).size / 1024).toFixed(1), 'KB');