This commit is contained in:
2026-04-26 13:47:21 +08:00
commit 4983f34202
24 changed files with 2559 additions and 0 deletions

17
.gitignore vendored Normal file
View File

@@ -0,0 +1,17 @@
# OS
.DS_Store
Thumbs.db
# IDE
.vscode/
.idea/
# Superpowers (internal tooling)
.superpowers/
# Dependencies
node_modules/
# Environment
.env
.env.local

BIN
assets/course-card.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

BIN
assets/icons/awakener.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

17
assets/icons/awakener.svg Normal file
View File

@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" fill="none">
<!-- 觉醒者 - 眼睛/觉醒 -->
<circle cx="100" cy="100" r="90" fill="rgba(255,255,255,0.1)" stroke="white" stroke-width="2"/>
<ellipse cx="100" cy="100" rx="60" ry="35" stroke="white" stroke-width="3" fill="none"/>
<circle cx="100" cy="100" r="18" fill="white"/>
<circle cx="100" cy="100" r="8" fill="#764ba2"/>
<circle cx="94" cy="94" r="3" fill="white"/>
<!-- 光芒 -->
<line x1="100" y1="10" x2="100" y2="30" stroke="white" stroke-width="2" stroke-linecap="round"/>
<line x1="100" y1="170" x2="100" y2="190" stroke="white" stroke-width="2" stroke-linecap="round"/>
<line x1="10" y1="100" x2="30" y2="100" stroke="white" stroke-width="2" stroke-linecap="round"/>
<line x1="170" y1="100" x2="190" y2="100" stroke="white" stroke-width="2" stroke-linecap="round"/>
<line x1="36" y1="36" x2="50" y2="50" stroke="white" stroke-width="2" stroke-linecap="round"/>
<line x1="150" y1="150" x2="164" y2="164" stroke="white" stroke-width="2" stroke-linecap="round"/>
<line x1="164" y1="36" x2="150" y2="50" stroke="white" stroke-width="2" stroke-linecap="round"/>
<line x1="36" y1="164" x2="50" y2="150" stroke="white" stroke-width="2" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
assets/icons/guardian.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

17
assets/icons/guardian.svg Normal file
View File

@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" fill="none">
<!-- 传承守护者 - 盾牌/树 -->
<circle cx="100" cy="100" r="90" fill="rgba(255,255,255,0.1)" stroke="white" stroke-width="2"/>
<!-- 盾牌 -->
<path d="M100,30 L160,55 L160,110 C160,145 130,170 100,180 C70,170 40,145 40,110 L40,55 Z" stroke="white" stroke-width="2.5" fill="rgba(255,255,255,0.1)"/>
<!---->
<line x1="100" y1="100" x2="100" y2="155" stroke="white" stroke-width="3" stroke-linecap="round"/>
<line x1="100" y1="120" x2="80" y2="140" stroke="white" stroke-width="2" stroke-linecap="round"/>
<line x1="100" y1="110" x2="120" y2="130" stroke="white" stroke-width="2" stroke-linecap="round"/>
<!-- 树冠 -->
<circle cx="100" cy="80" r="25" stroke="white" stroke-width="2" fill="rgba(255,255,255,0.15)"/>
<circle cx="80" cy="90" r="18" stroke="white" stroke-width="2" fill="rgba(255,255,255,0.1)"/>
<circle cx="120" cy="90" r="18" stroke="white" stroke-width="2" fill="rgba(255,255,255,0.1)"/>
<!-- 根系 -->
<path d="M100,155 Q90,165 75,170" stroke="white" stroke-width="2" stroke-linecap="round" fill="none"/>
<path d="M100,155 Q110,165 125,170" stroke="white" stroke-width="2" stroke-linecap="round" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
assets/icons/habitual.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

21
assets/icons/habitual.svg Normal file
View File

@@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" fill="none">
<!-- 惯性行者 - 齿轮/循环 -->
<circle cx="100" cy="100" r="90" fill="rgba(255,255,255,0.1)" stroke="white" stroke-width="2"/>
<!-- 外齿轮 -->
<circle cx="100" cy="100" r="50" stroke="white" stroke-width="2" fill="rgba(255,255,255,0.1)"/>
<circle cx="100" cy="100" r="30" stroke="white" stroke-width="2" fill="rgba(255,255,255,0.1)"/>
<!-- 齿轮齿 -->
<rect x="96" y="40" width="8" height="16" rx="2" fill="white" opacity="0.7"/>
<rect x="96" y="144" width="8" height="16" rx="2" fill="white" opacity="0.7"/>
<rect x="40" y="96" width="16" height="8" rx="2" fill="white" opacity="0.7"/>
<rect x="144" y="96" width="16" height="8" rx="2" fill="white" opacity="0.7"/>
<rect x="55" y="55" width="14" height="8" rx="2" fill="white" opacity="0.7" transform="rotate(45 62 59)"/>
<rect x="131" y="55" width="14" height="8" rx="2" fill="white" opacity="0.7" transform="rotate(-45 138 59)"/>
<rect x="55" y="137" width="14" height="8" rx="2" fill="white" opacity="0.7" transform="rotate(-45 62 141)"/>
<rect x="131" y="137" width="14" height="8" rx="2" fill="white" opacity="0.7" transform="rotate(45 138 141)"/>
<!-- 循环箭头 -->
<path d="M75,65 A35,35 0 0,1 125,65" stroke="white" stroke-width="2" fill="none" stroke-linecap="round"/>
<polygon points="125,60 130,68 120,68" fill="white"/>
<path d="M125,135 A35,35 0 0,1 75,135" stroke="white" stroke-width="2" fill="none" stroke-linecap="round"/>
<polygon points="75,140 70,132 80,132" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
assets/icons/icebreaker.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" fill="none">
<!-- 破冰人 - 锤子/冰块 -->
<circle cx="100" cy="100" r="90" fill="rgba(255,255,255,0.1)" stroke="white" stroke-width="2"/>
<!-- 冰块 -->
<polygon points="100,35 155,65 155,115 100,145 45,115 45,65" stroke="white" stroke-width="2.5" fill="rgba(255,255,255,0.15)"/>
<line x1="100" y1="35" x2="100" y2="145" stroke="white" stroke-width="1.5" opacity="0.5"/>
<line x1="45" y1="65" x2="155" y2="115" stroke="white" stroke-width="1.5" opacity="0.5"/>
<line x1="155" y1="65" x2="45" y2="115" stroke="white" stroke-width="1.5" opacity="0.5"/>
<!-- 裂缝 -->
<path d="M100,55 L95,75 L105,85 L90,105 L100,115" stroke="white" stroke-width="2.5" stroke-linecap="round" fill="none"/>
<!-- 碎片飞溅 -->
<polygon points="130,50 138,45 140,55" fill="white" opacity="0.7"/>
<polygon points="70,45 65,38 75,40" fill="white" opacity="0.7"/>
<polygon points="145,85 152,80 150,92" fill="white" opacity="0.5"/>
</svg>

After

Width:  |  Height:  |  Size: 1012 B

BIN
assets/icons/pauser.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

10
assets/icons/pauser.svg Normal file
View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" fill="none">
<!-- 知止行者 - 手掌/暂停 -->
<circle cx="100" cy="100" r="90" fill="rgba(255,255,255,0.1)" stroke="white" stroke-width="2"/>
<!-- 手掌 -->
<path d="M65,140 L65,80 C65,72 73,68 78,72 L78,105 L78,60 C78,52 86,48 91,52 L91,100 L91,55 C91,47 99,43 104,47 L104,100 L104,58 C104,50 112,46 117,50 L117,105 L117,70 C117,62 125,58 130,62 L130,130 C130,155 115,170 100,170 C80,170 65,155 65,140Z" stroke="white" stroke-width="2.5" fill="rgba(255,255,255,0.15)"/>
<!-- 停止符号 -->
<circle cx="100" cy="42" r="14" stroke="white" stroke-width="2" fill="rgba(255,255,255,0.2)"/>
<rect x="93" y="35" width="5" height="14" rx="1" fill="white"/>
<rect x="102" y="35" width="5" height="14" rx="1" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 807 B

BIN
assets/icons/relational.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" fill="none">
<!-- 关系困者 - 两人/纠缠 -->
<circle cx="100" cy="100" r="90" fill="rgba(255,255,255,0.1)" stroke="white" stroke-width="2"/>
<!-- 左边的人 -->
<circle cx="72" cy="70" r="16" stroke="white" stroke-width="2" fill="rgba(255,255,255,0.15)"/>
<path d="M72,86 L72,130 M55,100 L89,100 M72,130 L55,160 M72,130 L89,160" stroke="white" stroke-width="2" stroke-linecap="round"/>
<!-- 右边的人 -->
<circle cx="128" cy="70" r="16" stroke="white" stroke-width="2" fill="rgba(255,255,255,0.15)"/>
<path d="M128,86 L128,130 M111,100 L145,100 M128,130 L111,160 M128,130 L145,160" stroke="white" stroke-width="2" stroke-linecap="round"/>
<!-- 纠缠线 -->
<path d="M88,75 Q100,65 112,75" stroke="white" stroke-width="1.5" stroke-dasharray="4,3" fill="none" opacity="0.6"/>
<path d="M85,100 Q100,110 115,100" stroke="white" stroke-width="1.5" stroke-dasharray="4,3" fill="none" opacity="0.6"/>
<!-- 问号在中间 -->
<text x="100" y="92" text-anchor="middle" fill="white" font-size="18" font-weight="bold" opacity="0.5" font-family="sans-serif">?</text>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/icons/seeker.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

19
assets/icons/seeker.svg Normal file
View File

@@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" fill="none">
<!-- 寻道者 - 指南针/探索 -->
<circle cx="100" cy="100" r="90" fill="rgba(255,255,255,0.1)" stroke="white" stroke-width="2"/>
<!-- 外圈 -->
<circle cx="100" cy="100" r="65" stroke="white" stroke-width="2" fill="none"/>
<circle cx="100" cy="100" r="55" stroke="white" stroke-width="1" fill="none" opacity="0.5"/>
<!-- 刻度 -->
<line x1="100" y1="35" x2="100" y2="45" stroke="white" stroke-width="2"/>
<line x1="100" y1="155" x2="100" y2="165" stroke="white" stroke-width="2"/>
<line x1="35" y1="100" x2="45" y2="100" stroke="white" stroke-width="2"/>
<line x1="155" y1="100" x2="165" y2="100" stroke="white" stroke-width="2"/>
<!-- 指针 -->
<polygon points="100,50 92,100 100,90 108,100" fill="white" opacity="0.9"/>
<polygon points="100,150 92,100 100,110 108,100" fill="white" opacity="0.4"/>
<!-- 中心点 -->
<circle cx="100" cy="100" r="5" fill="white"/>
<!-- 问号 -->
<text x="100" y="30" text-anchor="middle" fill="white" font-size="16" font-weight="bold" font-family="serif">?</text>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/icons/sleeper.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

16
assets/icons/sleeper.svg Normal file
View File

@@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" fill="none">
<!-- 沉睡者 - 月亮/沉睡 -->
<circle cx="100" cy="100" r="90" fill="rgba(255,255,255,0.1)" stroke="white" stroke-width="2"/>
<!-- 月亮 -->
<path d="M120,50 C80,55 55,85 60,120 C65,155 95,175 130,170 C110,180 80,175 65,155 C45,130 45,90 65,65 C80,45 105,42 120,50Z" stroke="white" stroke-width="2.5" fill="rgba(255,255,255,0.15)"/>
<!-- 星星 -->
<circle cx="140" cy="70" r="3" fill="white" opacity="0.7"/>
<circle cx="155" cy="95" r="2" fill="white" opacity="0.5"/>
<circle cx="145" cy="120" r="2.5" fill="white" opacity="0.6"/>
<circle cx="130" cy="55" r="2" fill="white" opacity="0.4"/>
<circle cx="160" cy="75" r="1.5" fill="white" opacity="0.3"/>
<!-- ZZZ -->
<text x="145" y="50" fill="white" font-size="14" font-weight="bold" opacity="0.6" font-family="sans-serif">z</text>
<text x="158" y="42" fill="white" font-size="11" font-weight="bold" opacity="0.4" font-family="sans-serif">z</text>
<text x="168" y="35" fill="white" font-size="9" font-weight="bold" opacity="0.3" font-family="sans-serif">z</text>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,64 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="200" height="200">
<rect width="200" height="200" fill="white"/>
<!-- 模拟二维码图案 -->
<rect x="10" y="10" width="50" height="50" fill="#333"/>
<rect x="15" y="15" width="40" height="40" fill="white"/>
<rect x="20" y="20" width="30" height="30" fill="#333"/>
<rect x="140" y="10" width="50" height="50" fill="#333"/>
<rect x="145" y="15" width="40" height="40" fill="white"/>
<rect x="150" y="20" width="30" height="30" fill="#333"/>
<rect x="10" y="140" width="50" height="50" fill="#333"/>
<rect x="15" y="145" width="40" height="40" fill="white"/>
<rect x="20" y="150" width="30" height="30" fill="#333"/>
<!-- 随机方块模拟 -->
<rect x="70" y="10" width="10" height="10" fill="#333"/>
<rect x="90" y="10" width="10" height="10" fill="#333"/>
<rect x="110" y="10" width="10" height="10" fill="#333"/>
<rect x="70" y="30" width="10" height="10" fill="#333"/>
<rect x="100" y="30" width="10" height="10" fill="#333"/>
<rect x="120" y="30" width="10" height="10" fill="#333"/>
<rect x="80" y="50" width="10" height="10" fill="#333"/>
<rect x="100" y="50" width="10" height="10" fill="#333"/>
<rect x="10" y="70" width="10" height="10" fill="#333"/>
<rect x="30" y="70" width="10" height="10" fill="#333"/>
<rect x="50" y="70" width="10" height="10" fill="#333"/>
<rect x="80" y="70" width="10" height="10" fill="#333"/>
<rect x="110" y="70" width="10" height="10" fill="#333"/>
<rect x="140" y="70" width="10" height="10" fill="#333"/>
<rect x="160" y="70" width="10" height="10" fill="#333"/>
<rect x="180" y="70" width="10" height="10" fill="#333"/>
<rect x="20" y="90" width="10" height="10" fill="#333"/>
<rect x="50" y="90" width="10" height="10" fill="#333"/>
<rect x="70" y="90" width="10" height="10" fill="#333"/>
<rect x="100" y="90" width="10" height="10" fill="#333"/>
<rect x="130" y="90" width="10" height="10" fill="#333"/>
<rect x="150" y="90" width="10" height="10" fill="#333"/>
<rect x="180" y="90" width="10" height="10" fill="#333"/>
<rect x="10" y="110" width="10" height="10" fill="#333"/>
<rect x="40" y="110" width="10" height="10" fill="#333"/>
<rect x="60" y="110" width="10" height="10" fill="#333"/>
<rect x="90" y="110" width="10" height="10" fill="#333"/>
<rect x="120" y="110" width="10" height="10" fill="#333"/>
<rect x="140" y="110" width="10" height="10" fill="#333"/>
<rect x="170" y="110" width="10" height="10" fill="#333"/>
<rect x="70" y="130" width="10" height="10" fill="#333"/>
<rect x="100" y="130" width="10" height="10" fill="#333"/>
<rect x="130" y="130" width="10" height="10" fill="#333"/>
<rect x="160" y="130" width="10" height="10" fill="#333"/>
<rect x="180" y="130" width="10" height="10" fill="#333"/>
<rect x="70" y="150" width="10" height="10" fill="#333"/>
<rect x="90" y="150" width="10" height="10" fill="#333"/>
<rect x="120" y="150" width="10" height="10" fill="#333"/>
<rect x="140" y="150" width="10" height="10" fill="#333"/>
<rect x="160" y="150" width="10" height="10" fill="#333"/>
<rect x="180" y="150" width="10" height="10" fill="#333"/>
<rect x="70" y="170" width="10" height="10" fill="#333"/>
<rect x="100" y="170" width="10" height="10" fill="#333"/>
<rect x="130" y="170" width="10" height="10" fill="#333"/>
<rect x="150" y="170" width="10" height="10" fill="#333"/>
<rect x="180" y="170" width="10" height="10" fill="#333"/>
<rect x="70" y="180" width="10" height="10" fill="#333"/>
<rect x="110" y="180" width="10" height="10" fill="#333"/>
<rect x="140" y="180" width="10" height="10" fill="#333"/>
<rect x="170" y="180" width="10" height="10" fill="#333"/>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
assets/qrcode-site.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

1120
css/style.css Normal file

File diff suppressed because it is too large Load Diff

218
index.html Normal file
View File

@@ -0,0 +1,218 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="description" content="传承基因测试 - 3分钟发现你的家族传承类型">
<title>传承基因测试</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;600;700;900&family=Noto+Sans+SC:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<!-- ==================== 首页 ==================== -->
<section id="home" class="page active">
<div class="bg-noise"></div>
<div class="deco-circle deco-1"></div>
<div class="deco-circle deco-2"></div>
<div class="deco-circle deco-3"></div>
<div class="deco-glow"></div>
<div class="home-content">
<div class="home-badge">HERITAGE GENE TEST</div>
<h1 class="home-title">你的传承基因<br>是什么?</h1>
<p class="home-subtitle">3 分钟测试,发现你的家族传承类型</p>
<button id="btn-start" class="btn btn-start">
<span>开始测试</span>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
</button>
</div>
<div class="home-footer">
<a href="javascript:void(0)" id="btn-privacy" class="privacy-link">隐私声明</a>
<p class="disclaimer">本测试仅供自我探索参考,不构成专业心理评估或诊断</p>
</div>
</section>
<!-- ==================== 答题页 ==================== -->
<section id="quiz" class="page">
<div class="bg-noise"></div>
<!-- 进度区域 -->
<div class="quiz-topbar">
<button id="btn-prev" class="btn-back" style="visibility:hidden;">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
</button>
<div class="progress-area">
<div class="progress-track">
<div class="progress-fill" id="progress-fill"></div>
</div>
<span class="progress-label" id="progress-text">1 / 18</span>
</div>
<div style="width:40px;"></div>
</div>
<!-- 题目区域 -->
<div class="quiz-body">
<div class="question-wrapper" id="question-wrapper">
<div class="question-inner">
<span class="question-number" id="question-number">Q1</span>
<p class="quiz-question" id="quiz-question"></p>
</div>
</div>
<div class="scale-buttons" id="scale-buttons">
<button class="btn-scale" data-value="1">
<span class="scale-emoji">😞</span>
<span class="scale-text">非常不符合</span>
</button>
<button class="btn-scale" data-value="2">
<span class="scale-emoji">😕</span>
<span class="scale-text">比较不符合</span>
</button>
<button class="btn-scale" data-value="3">
<span class="scale-emoji">😐</span>
<span class="scale-text">一般</span>
</button>
<button class="btn-scale" data-value="4">
<span class="scale-emoji">🙂</span>
<span class="scale-text">比较符合</span>
</button>
<button class="btn-scale" data-value="5">
<span class="scale-emoji">😊</span>
<span class="scale-text">非常符合</span>
</button>
</div>
<p class="quiz-disclaimer">本测试仅供自我探索参考,不构成专业心理评估或诊断</p>
</div>
</section>
<!-- ==================== 计算页 ==================== -->
<section id="loading" class="page">
<div class="bg-noise"></div>
<div class="loading-content">
<div class="loading-dna">
<div class="dna-strand">
<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>
</div>
<div class="dna-strand dna-strand-2">
<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>
</div>
</div>
<p class="loading-text">正在解码你的传承基因<span class="loading-dots"></span></p>
</div>
</section>
<!-- ==================== 结果页 ==================== -->
<section id="result" class="page">
<div class="bg-noise"></div>
<div class="result-scroll">
<!-- 类型展示区 -->
<div class="result-hero">
<div class="result-icon-ring">
<img id="result-icon" class="result-icon" src="" alt="类型图标">
</div>
<div class="result-hero-text">
<p class="result-label">你的传承类型是</p>
<h2 id="result-name" class="result-name"></h2>
<p id="result-desc" class="result-desc"></p>
</div>
</div>
<!-- 雷达图 -->
<div class="result-card">
<h3 class="card-title">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="12 2 22 8.5 22 15.5 12 22 2 15.5 2 8.5 12 2"/><line x1="12" y1="22" x2="12" y2="15.5"/><polyline points="22 8.5 12 15.5 2 8.5"/></svg>
六维能力图谱
</h3>
<canvas id="radar-canvas" width="300" height="300"></canvas>
</div>
<!-- 详细分析 -->
<div class="result-card">
<h3 class="card-title">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>
深度解析
</h3>
<p id="result-analysis" class="result-analysis"></p>
</div>
<!-- 课程推荐 -->
<div class="result-card recommend-card">
<h3 class="card-title">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>
专属成长建议
</h3>
<p id="result-recommend" class="result-recommend"></p>
<div class="course-card-wrapper">
<img src="assets/course-card.jpg" alt="卢慧-家庭关系直播课" class="course-card-img" id="course-card-img">
<p class="course-card-hint">👆 点击查看大图 · 长按识别二维码</p>
</div>
</div>
<!-- 操作按钮 -->
<div class="result-actions">
<button id="btn-share" class="btn btn-share">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>
分享我的结果
</button>
<button id="btn-retry" class="btn btn-retry">重新测试</button>
</div>
<p class="disclaimer result-disclaimer">本测试仅供自我探索参考,不构成专业心理评估或诊断</p>
</div>
</section>
<!-- ==================== 海报页 ==================== -->
<div id="poster" class="poster-container">
<div id="poster-content" class="poster-content">
<div class="poster-header">
<p class="poster-brand">传承基因测试</p>
<img id="poster-icon" class="poster-icon" src="" alt="">
<h2 id="poster-name" class="poster-name"></h2>
<p id="poster-desc" class="poster-desc"></p>
<p id="poster-quote" class="poster-quote"></p>
</div>
<div class="poster-radar-wrap">
<canvas id="poster-radar-canvas" width="400" height="400"></canvas>
</div>
<div class="poster-footer">
<p class="poster-cta">你属于哪种传承类型?</p>
<p class="poster-slogan">扫码测测你的传承基因</p>
<img src="assets/qrcode-site.png" alt="扫码测测你的传承基因" class="poster-qrcode">
</div>
</div>
<div id="poster-preview" class="poster-preview" style="display:none;">
<h3 class="poster-preview-title">长按图片保存分享</h3>
<img id="poster-image" class="poster-image" src="" alt="分享海报">
<button id="btn-poster-close" class="btn btn-retry">关闭</button>
</div>
</div>
<!-- ==================== 课程大图弹窗 ==================== -->
<div id="course-modal" class="modal-overlay" style="display:none;">
<img id="course-modal-img" class="course-modal-img" src="" alt="课程详情">
</div>
<!-- ==================== 隐私声明弹窗 ==================== -->
<div id="privacy-modal" class="modal-overlay" style="display:none;">
<div class="modal-card">
<h3 class="modal-title">隐私声明</h3>
<div class="modal-body">
<p>1. 本测试<strong>不收集</strong>任何个人信息,包括但不限于姓名、手机号、邮箱等。</p>
<p>2. 所有答题数据和计算过程均在<strong>您的浏览器本地完成</strong>,不会上传到任何服务器。</p>
<p>3. 本网站<strong>不使用Cookie</strong>,不追踪用户行为,不使用任何第三方分析工具。</p>
<p>4. 您的测试结果仅保存在本地浏览器的临时存储中,关闭浏览器后可被清除。</p>
<p>5. 我们尊重并保护每一位用户的隐私权。</p>
</div>
<button id="btn-close-privacy" class="btn btn-modal-confirm">我知道了</button>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
<script src="js/app.js"></script>
</body>
</html>

684
js/app.js Normal file
View File

@@ -0,0 +1,684 @@
/**
* 传承基因测试 - 主逻辑
* 包含:题目数据、类型数据、答题流程、计分逻辑、雷达图绘制、海报生成
*/
(function () {
'use strict';
// ========================================
// 题目数据18题6个维度每维度3题
// ========================================
const questions = [
// 维度1 - 自我觉察力题1-3
'当我感到烦躁时,我能说出自己真正在气什么,而不是只觉得"烦"',
'我经常反思自己的行为模式,而不是只看到别人的问题',
'面对冲突时,我能觉察到自己身体的变化(心跳加速、肌肉紧绷等)',
// 维度2 - 情绪驾驭力题4-6
'在和孩子吵架后,我能让自己冷静下来再去沟通,而不是冷战或翻旧账',
'当情绪上来时,我能让自己暂停几秒再回应,而不是脱口而出',
'我很少因为一时冲动说出让自己后悔的话',
// 维度3 - 关系修复力题7-9
'我发现自己和父母的相处方式,正在不知不觉中复制到我和孩子的关系上',
'当关系出现问题时,我愿意主动去修复,而不是等对方先改变',
'我能分辨哪些情绪是自己的,哪些是"替别人承担的"',
// 维度4 - 传承意识题10-12
'我想过自己的某些性格特点或反应模式,可能是从祖辈那里继承来的',
'我注意到家族里有些模式在一代代重复(比如某种情绪、某种关系模式)',
'我相信了解家族的历史和故事,对理解和教育孩子有帮助',
// 维度5 - 内在驱动力题13-15
'我清楚自己为什么要做现在做的事,而不是随大流或被外界推着走',
'我觉得自己的人生是自己主动选择的,而不是被安排好的',
'遇到困难时,我能找到继续前行的内在力量,而不是只靠外部压力',
// 维度6 - 成长信念题16-18
'我相信不管现在是什么状态,人都可以改变和成长',
'我愿意为了长远的目标,每天坚持做一点小事',
'我觉得"慢慢来"不是放弃,而是一种智慧'
];
// 维度名称(用于雷达图标签)
const dimensionNames = [
'自我觉察力', '情绪驾驭力', '关系修复力',
'传承意识', '内在驱动力', '成长信念'
];
// ========================================
// 8种类型数据
// ========================================
const types = {
awakener: {
name: '觉醒者',
icon: 'assets/icons/awakener.png',
desc: '已走在自我成长路上,觉察力和情绪力都很强',
quote: '「看见即觉察,觉察即涨心」',
analysis: '你拥有出色的自我觉察能力,能够清晰地看见自己的情绪和念头。在情绪管理方面,你已经掌握了"知止"的智慧,能在关键时刻停住并选择回应方式。你正在成为自己生活的"第一目击证人",内在诚信正在建立。继续深化修炼,从"看见"走向"止于至善",你将影响整个家族的传承模式。',
recommend: '你已经走在觉醒的路上,课程将帮你从"看见"走向"止于至善",深化修炼,让美好状态持续。'
},
icebreaker: {
name: '破冰人',
icon: 'assets/icons/icebreaker.png',
desc: '正在打破家族旧模式,传承意识和关系修复力突出',
quote: '「旧地图无法发现新大陆」',
analysis: '你对家族传承有着深刻的意识,能够看到代际之间传递的模式。你已经开始打破那些不再服务于你的共生剧本,在关系修复方面展现出勇气和行动力。你正在成为家族中"那个不一样的人"。系统学习冰山模型,将帮助你更深入地理解这些模式背后的根源。',
recommend: '你已开始打破旧模式,课程将带你系统理解冰山下的家族数据,找到真正的破冰点。'
},
pauser: {
name: '知止行者',
icon: 'assets/icons/pauser.png',
desc: '情绪驾驭力突出,能在关键时刻停住',
quote: '「在电光火石间看见自己」',
analysis: '你拥有强大的情绪驾驭能力能够在情绪升起的瞬间停住善用那0.0001秒的间隙。这是非常珍贵的能力——很多人终其一生都在被情绪带着走。不过,你还可以更进一步:从"知止"到"看见",觉察情绪背后的根源。当你能看见情绪从哪里来,你就真正掌握了选择的自由。',
recommend: '你已能在情绪中停住,课程将帮你看见情绪背后的根源,从"知止"走向"格物致知"。'
},
guardian: {
name: '传承守护者',
icon: 'assets/icons/guardian.png',
desc: '传承意识强、成长信念坚定,但行动力还不足',
quote: '「冰冻三尺非一日之寒,精诚所至金石为开」',
analysis: '你对家族传承有着深刻的理解和坚定的成长信念,你知道改变需要时间和耐心。你的内在已经有了清晰的认知,但从意识到行动之间还有一段距离。这不是你的问题——冰冻三尺非一日之寒。关键是从最小的行动开始,日拱一卒,让认知慢慢落地为每天的功课。',
recommend: '你对传承有深刻意识,课程将帮你把意识转化为每天的功课,从"知道"到"做到"。'
},
seeker: {
name: '寻道者',
icon: 'assets/icons/seeker.png',
desc: '成长信念强、内在驱动力高,但方向还不够清晰',
quote: '「通过外面生活现象,找到内在卡点」',
analysis: '你拥有强大的内在驱动力和坚定的成长信念,这是非常宝贵的品质。你已经准备好改变,也愿意为此投入时间和精力。但有时候你可能感到方向不够清晰——知道要改变,但不知道从哪里开始。这正是"格物致知"的用武之地:通过外面生活现象找到内在卡点,方向自然就会浮现。',
recommend: '你有强大的成长动力,课程将帮你找到内在卡点,明确方向,从"寻找"到"看见"。'
},
sleeper: {
name: '沉睡者',
icon: 'assets/icons/sleeper.png',
desc: '多维度偏低,但成长信念还在,是觉醒的起点',
quote: '「每个人本自具足,生来就足够好」',
analysis: '你可能觉得自己在生活的惯性中运转,很多方面都还没有找到方向。但请看到一件重要的事:你的成长信念还在——你相信人可以改变,这本身就是觉醒的种子。很多人连这颗种子都没有。觉醒的第一步,不是做更多,而是"看见"——看见自己的情绪、念头和模式。从"我看见我"开始,一切都会不同。',
recommend: '觉醒的第一步是看见。课程将从最基础的"我看见我"开始,帮你唤醒内在的力量。'
},
relational: {
name: '关系困者',
icon: 'assets/icons/relational.png',
desc: '关系是最大痛点,情绪驾驭和关系修复都较弱',
quote: '「找回足够好的自己,用它修复生活」',
analysis: '关系是你当前最大的挑战和突破口。你可能发现自己总是在重复某些关系模式——和父母的、和伴侣的、和孩子的。这些模式不是你的错,它们是家族大数据通过代际传承传递下来的"共生剧本"。好消息是,你已经意识到了问题所在,这就是改变的开始。从"我看见我的妈妈"开始,你将慢慢解开这些关系的结。',
recommend: '关系是你的突破口,课程将教你从"我看见我的妈妈"开始,修复关系,走出共生剧本。'
},
habitual: {
name: '惯性行者',
icon: 'assets/icons/habitual.png',
desc: '在惯性中运转,内在驱动力和自我觉察都较弱',
quote: '「慢慢来比较快」',
analysis: '你可能感觉自己像一台被程序化运转的机器——每天做着差不多的事,想着差不多的念头,每一念都来自过去。这不是你的问题,而是每个人都可能陷入的状态,课程中称之为"空心人"或"NPC"。但旧地图无法发现新大陆,看见即觉察,觉察即涨心。当你开始看见自己的惯性,改变就已经发生了。',
recommend: '看见即觉察,觉察即涨心。课程将帮你走出惯性,找回内在力量,从"NPC"变回自己。'
}
};
// ========================================
// 状态管理
// ========================================
let currentQuestion = 0; // 当前题目索引0-17
let answers = []; // 答题记录长度18每项1-5
// localStorage 键名
const STORAGE_KEY = 'heritage_gene_test';
// ========================================
// DOM 元素引用
// ========================================
const $home = document.getElementById('home');
const $quiz = document.getElementById('quiz');
const $loading = document.getElementById('loading');
const $result = document.getElementById('result');
const $poster = document.getElementById('poster');
const $progressFill = document.getElementById('progress-fill');
const $progressText = document.getElementById('progress-text');
const $quizQuestion = document.getElementById('quiz-question');
const $questionNumber = document.getElementById('question-number');
const $questionInner = document.getElementById('question-wrapper').querySelector('.question-inner');
const $scaleButtons = document.getElementById('scale-buttons');
const $btnPrev = document.getElementById('btn-prev');
const $resultIcon = document.getElementById('result-icon');
const $resultName = document.getElementById('result-name');
const $resultDesc = document.getElementById('result-desc');
const $resultAnalysis = document.getElementById('result-analysis');
const $resultRecommend = document.getElementById('result-recommend');
const $radarCanvas = document.getElementById('radar-canvas');
const $posterIcon = document.getElementById('poster-icon');
const $posterName = document.getElementById('poster-name');
const $posterDesc = document.getElementById('poster-desc');
const $posterQuote = document.getElementById('poster-quote');
const $posterRadarCanvas = document.getElementById('poster-radar-canvas');
const $posterContent = document.getElementById('poster-content');
const $posterPreview = document.getElementById('poster-preview');
const $posterImage = document.getElementById('poster-image');
const $privacyModal = document.getElementById('privacy-modal');
const $courseModal = document.getElementById('course-modal');
const $courseModalImg = document.getElementById('course-modal-img');
const $courseCardImg = document.getElementById('course-card-img');
// ========================================
// 初始化
// ========================================
function init() {
// 尝试从 localStorage 恢复进度
loadProgress();
// 绑定事件
document.getElementById('btn-start').addEventListener('click', startQuiz);
document.getElementById('btn-prev').addEventListener('click', prevQuestion);
document.getElementById('btn-share').addEventListener('click', generatePoster);
document.getElementById('btn-retry').addEventListener('click', retryQuiz);
document.getElementById('btn-privacy').addEventListener('click', function () {
$privacyModal.style.display = 'flex';
});
document.getElementById('btn-close-privacy').addEventListener('click', function () {
$privacyModal.style.display = 'none';
});
document.getElementById('btn-poster-close').addEventListener('click', closePoster);
// 点击遮罩关闭弹窗
$privacyModal.addEventListener('click', function (e) {
if (e.target === $privacyModal) {
$privacyModal.style.display = 'none';
}
});
// 课程卡片:点击查看大图
$courseCardImg.addEventListener('click', function () {
$courseModalImg.src = $courseCardImg.src;
$courseModal.style.display = 'flex';
});
// 课程大图:点击遮罩关闭
$courseModal.addEventListener('click', function (e) {
if (e.target === $courseModal) {
$courseModal.style.display = 'none';
}
});
// 量表按钮事件委托
$scaleButtons.addEventListener('click', function (e) {
var btn = e.target.closest('.btn-scale');
if (!btn) return;
var value = parseInt(btn.getAttribute('data-value'));
selectAnswer(value);
});
}
// ========================================
// 页面切换
// ========================================
function showPage(pageId) {
var pages = document.querySelectorAll('.page');
pages.forEach(function (p) {
p.classList.remove('active');
});
document.getElementById(pageId).classList.add('active');
// 滚动到顶部
window.scrollTo(0, 0);
}
// ========================================
// 答题流程
// ========================================
/** 开始测试 */
function startQuiz() {
currentQuestion = 0;
answers = new Array(questions.length).fill(0);
saveProgress();
showPage('quiz');
renderQuestion();
}
/** 渲染当前题目(带滑动动画) */
function renderQuestion(direction) {
// direction: 'next' | 'prev' | undefined首次
var doRender = function() {
// 更新进度
var progress = ((currentQuestion) / questions.length) * 100;
$progressFill.style.width = progress + '%';
$progressText.textContent = (currentQuestion + 1) + ' / ' + questions.length;
// 更新题目
$questionNumber.textContent = 'Q' + (currentQuestion + 1);
$quizQuestion.textContent = questions[currentQuestion];
// 更新量表按钮状态
var btns = $scaleButtons.querySelectorAll('.btn-scale');
var currentAnswer = answers[currentQuestion];
btns.forEach(function (btn) {
var val = parseInt(btn.getAttribute('data-value'));
if (val === currentAnswer) {
btn.classList.add('selected');
} else {
btn.classList.remove('selected');
}
});
// 重置选项弹入动画
resetScaleAnimation();
// 上一题按钮
$btnPrev.style.visibility = currentQuestion > 0 ? 'visible' : 'hidden';
// 滑入
if (direction === 'next') {
$questionInner.classList.remove('slide-in-right', 'slide-out-left', 'slide-in-left', 'slide-out-right');
$questionInner.classList.add('slide-in-right');
requestAnimationFrame(function() {
requestAnimationFrame(function() {
$questionInner.classList.remove('slide-in-right');
});
});
} else if (direction === 'prev') {
$questionInner.classList.remove('slide-in-right', 'slide-out-left', 'slide-in-left', 'slide-out-right');
$questionInner.classList.add('slide-in-left');
requestAnimationFrame(function() {
requestAnimationFrame(function() {
$questionInner.classList.remove('slide-in-left');
});
});
}
};
if (!direction) {
doRender();
return;
}
// 先滑出当前题目
var outClass = direction === 'next' ? 'slide-out-left' : 'slide-out-right';
$questionInner.classList.remove('slide-in-right', 'slide-out-left', 'slide-in-left', 'slide-out-right');
$questionInner.classList.add(outClass);
setTimeout(function() {
doRender();
}, 250);
}
/** 重置量表按钮的弹入动画 */
function resetScaleAnimation() {
var btns = $scaleButtons.querySelectorAll('.btn-scale');
btns.forEach(function(btn) {
btn.style.animation = 'none';
btn.offsetHeight; // 触发重排
btn.style.animation = '';
});
}
/** 选择答案 */
function selectAnswer(value) {
answers[currentQuestion] = value;
saveProgress();
// 高亮选中按钮
var btns = $scaleButtons.querySelectorAll('.btn-scale');
btns.forEach(function (btn) {
var val = parseInt(btn.getAttribute('data-value'));
if (val === value) {
btn.classList.add('selected');
} else {
btn.classList.remove('selected');
}
});
// 更新进度条到当前题的完成状态
var progress = ((currentQuestion + 1) / questions.length) * 100;
$progressFill.style.width = progress + '%';
// 0.3秒后自动跳下一题
setTimeout(function () {
if (currentQuestion < questions.length - 1) {
currentQuestion++;
renderQuestion('next');
} else {
// 最后一题,进入计算页
showLoading();
}
}, 300);
}
/** 上一题 */
function prevQuestion() {
if (currentQuestion > 0) {
currentQuestion--;
renderQuestion('prev');
}
}
// ========================================
// 计算页
// ========================================
function showLoading() {
showPage('loading');
// 2.5秒后跳转结果页
setTimeout(function () {
calculateAndShowResult();
}, 2500);
}
// ========================================
// 计分与类型匹配
// ========================================
function calculateAndShowResult() {
// 计算各维度分数每维度3题分值3-15
var dimensions = [];
for (var d = 0; d < 6; d++) {
var sum = 0;
for (var q = 0; q < 3; q++) {
sum += answers[d * 3 + q];
}
dimensions.push(sum);
}
// 维度:觉察、情绪、关系、传承、驱动、成长
var 觉察 = dimensions[0];
var 情绪 = dimensions[1];
var 关系 = dimensions[2];
var 传承 = dimensions[3];
var 驱动 = dimensions[4];
var 成长 = dimensions[5];
// 类型匹配(按优先级)
var typeKey;
if (觉察 >= 10 && 情绪 >= 10) {
typeKey = 'awakener';
} else if (传承 >= 10 && 关系 >= 10) {
typeKey = 'icebreaker';
} else if (情绪 >= 10 && 觉察 <= 9) {
typeKey = 'pauser';
} else if (传承 >= 10 && 成长 >= 10 && 驱动 <= 9) {
typeKey = 'guardian';
} else if (成长 >= 10 && 驱动 >= 10 && 觉察 <= 9) {
typeKey = 'seeker';
} else if (关系 <= 9 && 情绪 <= 9) {
typeKey = 'relational';
} else if (驱动 <= 9 && 觉察 <= 9) {
typeKey = 'habitual';
} else {
typeKey = 'sleeper';
}
var typeData = types[typeKey];
// 渲染结果页
$resultIcon.src = typeData.icon;
$resultIcon.alt = typeData.name;
$resultName.textContent = typeData.name;
$resultDesc.textContent = typeData.desc;
$resultAnalysis.textContent = typeData.analysis;
$resultRecommend.textContent = typeData.recommend;
// 绘制雷达图
drawRadarChart($radarCanvas, dimensions, 300);
// 保存结果到 localStorage
localStorage.setItem(STORAGE_KEY + '_result', JSON.stringify({
typeKey: typeKey,
dimensions: dimensions
}));
// 清除答题进度
localStorage.removeItem(STORAGE_KEY);
showPage('result');
}
// ========================================
// 雷达图绘制Canvas API
// ========================================
function drawRadarChart(canvas, dimensions, size) {
var ctx = canvas.getContext('2d');
var dpr = window.devicePixelRatio || 1;
// 高清屏适配
canvas.width = size * dpr;
canvas.height = size * dpr;
canvas.style.width = size + 'px';
canvas.style.height = size + 'px';
ctx.scale(dpr, dpr);
var centerX = size / 2;
var centerY = size / 2;
var maxRadius = size * 0.3;
var levels = 5; // 5层网格
var sides = 6; // 6个维度
var angleStep = (Math.PI * 2) / sides;
var startAngle = -Math.PI / 2; // 从顶部开始
ctx.clearRect(0, 0, size, size);
// 绘制背景网格5层
for (var level = 1; level <= levels; level++) {
var r = (maxRadius / levels) * level;
ctx.beginPath();
for (var i = 0; i <= sides; i++) {
var angle = startAngle + angleStep * i;
var x = centerX + r * Math.cos(angle);
var y = centerY + r * Math.sin(angle);
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.closePath();
ctx.strokeStyle = 'rgba(255, 255, 255, 0.08)';
ctx.lineWidth = 1;
ctx.stroke();
}
// 绘制轴线
for (var i = 0; i < sides; i++) {
var angle = startAngle + angleStep * i;
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.lineTo(
centerX + maxRadius * Math.cos(angle),
centerY + maxRadius * Math.sin(angle)
);
ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
ctx.lineWidth = 1;
ctx.stroke();
}
// 绘制数据区域
ctx.beginPath();
for (var i = 0; i < sides; i++) {
// 维度分值范围3-15映射到0-1
var value = (dimensions[i] - 3) / 12;
value = Math.max(0, Math.min(1, value));
var r = maxRadius * value;
var angle = startAngle + angleStep * i;
var x = centerX + r * Math.cos(angle);
var y = centerY + r * Math.sin(angle);
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.closePath();
// 渐变填充
var gradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, maxRadius);
gradient.addColorStop(0, 'rgba(124, 92, 191, 0.45)');
gradient.addColorStop(1, 'rgba(240, 194, 122, 0.2)');
ctx.fillStyle = gradient;
ctx.fill();
ctx.strokeStyle = 'rgba(196, 181, 253, 0.6)';
ctx.lineWidth = 2;
ctx.stroke();
// 绘制数据点
for (var i = 0; i < sides; i++) {
var value = (dimensions[i] - 3) / 12;
value = Math.max(0, Math.min(1, value));
var r = maxRadius * value;
var angle = startAngle + angleStep * i;
var x = centerX + r * Math.cos(angle);
var y = centerY + r * Math.sin(angle);
ctx.beginPath();
ctx.arc(x, y, 4, 0, Math.PI * 2);
ctx.fillStyle = '#f0c27a';
ctx.fill();
ctx.strokeStyle = 'rgba(240, 194, 122, 0.4)';
ctx.lineWidth = 6;
ctx.stroke();
}
// 绘制维度标签
ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
ctx.font = '11px "Noto Sans SC", -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
var labelRadius = maxRadius + 24;
for (var i = 0; i < sides; i++) {
var angle = startAngle + angleStep * i;
var x = centerX + labelRadius * Math.cos(angle);
var y = centerY + labelRadius * Math.sin(angle);
ctx.fillText(dimensionNames[i], x, y);
}
}
// ========================================
// 海报生成
// ========================================
function generatePoster() {
// 获取结果数据
var resultData = JSON.parse(localStorage.getItem(STORAGE_KEY + '_result') || '{}');
if (!resultData.typeKey) return;
var typeData = types[resultData.typeKey];
var dimensions = resultData.dimensions;
// 填充海报内容
$posterIcon.src = typeData.icon;
$posterName.textContent = typeData.name;
$posterDesc.textContent = typeData.desc;
$posterQuote.textContent = typeData.quote;
// 在海报canvas上重新绘制雷达图
drawRadarChart($posterRadarCanvas, dimensions, 400);
// 显示海报容器
$poster.classList.add('active');
$posterContent.style.display = 'block';
$posterPreview.style.display = 'none';
// 等待图片和canvas渲染完成后截图
setTimeout(function () {
capturePoster();
}, 500);
}
/** 使用 html2canvas 截图 */
function capturePoster() {
if (typeof html2canvas === 'undefined') {
alert('海报生成组件加载失败,请刷新页面重试');
return;
}
// 先显示加载提示
$posterPreview.style.display = 'block';
$posterImage.style.display = 'none';
var previewTitle = document.querySelector('.poster-preview-title');
if (previewTitle) previewTitle.textContent = '正在生成海报...';
html2canvas($posterContent, {
scale: 2,
useCORS: true,
allowTaint: true,
backgroundColor: '#0f0a1e',
width: 750,
height: 1334,
logging: false
}).then(function (canvas) {
$posterContent.style.display = 'none';
var dataUrl = canvas.toDataURL('image/png');
$posterImage.src = dataUrl;
$posterImage.style.display = 'block';
if (previewTitle) previewTitle.textContent = '长按图片保存分享';
}).catch(function (err) {
console.error('海报生成失败:', err);
$posterContent.style.display = 'none';
$posterPreview.style.display = 'block';
$posterImage.style.display = 'none';
if (previewTitle) previewTitle.textContent = '海报生成失败,请重试';
});
}
/** 关闭海报 */
function closePoster() {
$poster.classList.remove('active');
$posterContent.style.display = 'block';
$posterPreview.style.display = 'none';
}
// ========================================
// 重新测试
// ========================================
function retryQuiz() {
localStorage.removeItem(STORAGE_KEY);
localStorage.removeItem(STORAGE_KEY + '_result');
answers = new Array(questions.length).fill(0);
currentQuestion = 0;
showPage('home');
}
// ========================================
// localStorage 进度保存/恢复
// ========================================
function saveProgress() {
var data = {
currentQuestion: currentQuestion,
answers: answers
};
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
} catch (e) {
// localStorage 不可用时静默失败
}
}
function loadProgress() {
try {
var raw = localStorage.getItem(STORAGE_KEY);
if (raw) {
var data = JSON.parse(raw);
if (data.answers && data.answers.length === questions.length) {
answers = data.answers;
currentQuestion = data.currentQuestion || 0;
// 如果有未完成的测试,恢复到答题页
var allAnswered = answers.every(function (a) { return a > 0; });
if (allAnswered) {
// 所有题都答完了,直接计算结果
calculateAndShowResult();
} else if (currentQuestion > 0) {
// 有部分进度,恢复答题
showPage('quiz');
renderQuestion();
}
}
}
} catch (e) {
// 解析失败时忽略
}
}
// ========================================
// 启动
// ========================================
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();

View File

@@ -0,0 +1,326 @@
# 「传承基因」测试网站 — 设计方案
> 创建日期2026-04-24
> 状态:设计已完成,待开发
---
## 一、项目概述
### 1.1 项目目标
构建一个类似 MBTI 风格的静态测试网站,通过 18 道场景化测试题,分析用户在"代继传承"方面的 6 个核心维度,最终给出一个类型标签和详细分析,并推荐「代继传承」课程。
### 1.2 目标用户
- **核心人群**有孩子的中年父母30-55岁
- **用户画像**:带着各种家庭困惑而来——孩子躺平、亲子关系紧张、夫妻关系问题、婆媳矛盾、财富传承焦虑等
- **使用场景**:微信朋友圈/群分享 → 手机打开 → 3-5分钟完成测试 → 查看结果 → 分享海报
### 1.3 核心约束
- 纯静态网站HTML + CSS + JS无需后端服务
- 手机优先,同时适配电脑端
- 课程推荐以链接/二维码形式呈现
- 最终生成可分享的结果海报
---
## 二、设计风格
### 2.1 视觉风格:简约专业风
- **主色调**:蓝紫渐变(#667eea#764ba2
- **辅助色**:白色、浅灰背景
- **字体**:系统默认无衬线字体(-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif
- **设计语言**:干净利落、专业感强,类似 16personalities / MBTI 官方风格
- **圆角**按钮和卡片使用大圆角30px 按钮16px 卡片)
- **装饰**:几何圆形装饰元素,半透明,增加层次感
### 2.2 情感调性
- 专业但不冰冷
- 有深度但不晦涩
- 让用户感到"被理解"而非"被评判"
- 避免负面标签,所有类型名称都是正向或中性的
---
## 三、用户流程
```
首页(吸引点击)
↓ 点击"开始测试"
答题页18题逐题展示
↓ 最后一题提交
计算页动画过渡2-3秒
↓ 自动跳转
结果页(类型分析 + 课程推荐)
↓ 点击"分享我的结果"
海报页(生成结果海报图片)
↓ 长按保存/截图
分享到朋友圈/微信群
```
---
## 四、页面设计
### 4.1 首页
| 元素 | 内容 |
|------|------|
| 主标题 | "你的传承基因是什么?" |
| 副标题 | "3分钟测试发现你的家族传承类型" |
| 按钮 | "开始测试 →" |
| 装饰 | 蓝紫渐变背景 + 半透明几何圆形 |
| 底部 | 简短说明:"基于心理学理论,已帮助 XXX 人发现自我"(可选) |
### 4.2 答题页
| 元素 | 内容 |
|------|------|
| 顶部 | 进度条(当前题号/总题数) |
| 题目区 | 场景化描述文字 |
| 选项区 | 5级李克特量表非常不符合 / 比较不符合 / 一般 / 比较符合 / 非常符合 |
| 交互 | 选择后自动跳转下一题0.3秒延迟,给用户反馈感) |
| 返回 | 允许返回上一题修改 |
### 4.3 计算页
| 元素 | 内容 |
|------|------|
| 动画 | "正在分析你的传承基因..." + 脉冲/呼吸动画 |
| 时长 | 2-3秒营造"认真分析"的感觉 |
### 4.4 结果页
| 元素 | 内容 |
|------|------|
| 类型图标 | 每种类型配一个简笔画/图标 |
| 类型标签 | 大字展示,如"觉醒者" |
| 一句话描述 | 该类型的核心特征 |
| 雷达图 | 6个维度的得分可视化 |
| 详细分析 | 200字左右包含优势和成长方向 |
| 课程推荐 | 推荐语 + 课程链接/二维码 |
| 按钮 | "分享我的结果" / "重新测试" |
### 4.5 海报页
| 元素 | 内容 |
|------|------|
| 背景 | 蓝紫渐变 |
| 内容 | 类型图标 + 类型名称 + 一句话描述 + 雷达图(简化版) |
| 底部 | "扫码测测你的传承基因" + 网站二维码 |
| 交互 | 提示"长按保存图片"或"截图分享" |
---
## 五、测试维度设计
### 5.1 六大维度
| # | 维度名称 | 高分特征(觉醒端) | 低分特征(惯性端) | 对应课程理论 |
|---|---------|-------------------|-------------------|-------------|
| 1 | 自我觉察力 | 能看见自己的情绪和念头,对自己真实 | 习惯性压抑或忽视内在感受 | "我看见我的XX"、内在诚信 |
| 2 | 情绪驾驭力 | 能在情绪升起时停住,选择回应方式 | 容易被情绪带着走,事后后悔 | 知止、0.0001秒的间隙 |
| 3 | 关系修复力 | 能识别共生剧本,主动修复关系 | 在重复的关系模式中打转 | 亲民、共生剧本 |
| 4 | 传承意识 | 意识到家族模式在影响自己和孩子 | 没想过家族对自己的影响 | 代际传承、冰山模型 |
| 5 | 内在驱动力 | 行动来自内在力量和清晰意图 | 在惯性中生活,感觉被动 | 内驱力 vs NPC/空心人 |
| 6 | 成长信念 | 相信人可以持续改变,愿意长期投入 | 觉得"我就是这样了",难以改变 | 日拱一卒、止于至善 |
### 5.2 计分规则
- 每题 1-5 分(非常不符合=1非常符合=5
- 每个维度 3 题,维度得分 = 3题总分3-15分
- 维度得分 ≥ 10 为"觉醒端",≤ 9 为"惯性端"
- 根据6个维度的觉醒/惯性组合匹配到8种类型之一
---
## 六、8种传承类型
| # | 类型标签 | 核心特征 | 维度组合特征 | 课程推荐切入点 |
|---|---------|---------|-------------|--------------|
| 1 | **觉醒者** | 已走在自我成长路上,觉察力和情绪力都很强 | 觉察力高 + 情绪力高 | 深化修炼,止于至善 |
| 2 | **破冰人** | 正在打破家族旧模式,传承意识和关系修复力突出 | 传承意识高 + 关系修复力强 | 系统学习冰山模型 |
| 3 | **知止行者** | 情绪驾驭力突出,能在关键时刻停住,但自我觉察还需提升 | 情绪力高 + 觉察力待提升 | 从"知止"到"看见" |
| 4 | **传承守护者** | 传承意识强、成长信念坚定,但行动力还不足 | 传承意识高 + 成长信念强 + 驱动力待提升 | 从意识到行动,日拱一卒 |
| 5 | **寻道者** | 成长信念强、内在驱动力高,但方向还不够清晰 | 成长信念强 + 驱动力高 + 觉察力待提升 | 格物致知,找到内在卡点 |
| 6 | **沉睡者** | 多维度偏低,但成长信念还在,是觉醒的起点 | 多维度偏低 + 成长信念尚可 | 从最基础的"我看见我"开始 |
| 7 | **关系困者** | 关系是最大痛点,情绪驾驭和关系修复都较弱 | 关系修复力低 + 情绪力弱 | 从"我看见我的妈妈"开始 |
| 8 | **惯性行者** | 在惯性中运转,内在驱动力和自我觉察都较弱 | 驱动力低 + 觉察力弱 | 看见即觉察,觉察即涨心 |
### 6.1 类型匹配逻辑
```
IF 觉察力≥10 AND 情绪力≥10 → 觉醒者
ELSE IF 传承意识≥10 AND 关系修复力≥10 → 破冰人
ELSE IF 情绪力≥10 AND 觉察力≤9 → 知止行者
ELSE IF 传承意识≥10 AND 成长信念≥10 AND 驱动力≤9 → 传承守护者
ELSE IF 成长信念≥10 AND 驱动力≥10 AND 觉察力≤9 → 寻道者
ELSE IF 关系修复力≤9 AND 情绪力≤9 → 关系困者
ELSE IF 驱动力≤9 AND 觉察力≤9 → 惯性行者
ELSE → 沉睡者(兜底)
```
---
## 七、测试题目18题
### 维度1自我觉察力题1-3
| # | 题目 |
|---|------|
| 1 | 当我感到烦躁时,我能说出自己真正在气什么,而不是只觉得"烦" |
| 2 | 我经常反思自己的行为模式,而不是只看到别人的问题 |
| 3 | 面对冲突时,我能觉察到自己身体的变化(心跳加速、肌肉紧绷等) |
### 维度2情绪驾驭力题4-6
| # | 题目 |
|---|------|
| 4 | 在和孩子吵架后,我能让自己冷静下来再去沟通,而不是冷战或翻旧账 |
| 5 | 当情绪上来时,我能让自己暂停几秒再回应,而不是脱口而出 |
| 6 | 我很少因为一时冲动说出让自己后悔的话 |
### 维度3关系修复力题7-9
| # | 题目 |
|---|------|
| 7 | 我发现自己和父母的相处方式,正在不知不觉中复制到我和孩子的关系上 |
| 8 | 当关系出现问题时,我愿意主动去修复,而不是等对方先改变 |
| 9 | 我能分辨哪些情绪是自己的,哪些是"替别人承担的" |
### 维度4传承意识题10-12
| # | 题目 |
|---|------|
| 10 | 我想过自己的某些性格特点或反应模式,可能是从祖辈那里继承来的 |
| 11 | 我注意到家族里有些模式在一代代重复(比如某种情绪、某种关系模式) |
| 12 | 我相信了解家族的历史和故事,对理解和教育孩子有帮助 |
### 维度5内在驱动力题13-15
| # | 题目 |
|---|------|
| 13 | 我清楚自己为什么要做现在做的事,而不是随大流或被外界推着走 |
| 14 | 我觉得自己的人生是自己主动选择的,而不是被安排好的 |
| 15 | 遇到困难时,我能找到继续前行的内在力量,而不是只靠外部压力 |
### 维度6成长信念题16-18
| # | 题目 |
|---|------|
| 16 | 我相信不管现在是什么状态,人都可以改变和成长 |
| 17 | 我愿意为了长远的目标,每天坚持做一点小事 |
| 18 | 我觉得"慢慢来"不是放弃,而是一种智慧 |
---
## 八、技术方案
### 8.1 技术栈
| 项目 | 方案 |
|------|------|
| 结构 | HTML5 |
| 样式 | CSS3 + 响应式设计Mobile First |
| 交互 | 原生 JavaScript无框架依赖 |
| 图表 | Canvas 绘制雷达图 |
| 海报 | html2canvas 库生成图片 |
| 部署 | 任意静态托管GitHub Pages / Vercel / 阿里云 OSS 等) |
### 8.2 文件结构
```
/
├── index.html # 首页
├── quiz.html # 答题页
├── result.html # 结果页
├── css/
│ └── style.css # 全局样式
├── js/
│ ├── quiz.js # 答题逻辑
│ ├── result.js # 计分 + 类型匹配
│ ├── radar.js # 雷达图绘制
│ └── poster.js # 海报生成
└── assets/
└── icons/ # 8种类型的简笔画图标
```
### 8.3 响应式断点
| 断点 | 适配设备 |
|------|---------|
| < 480px | 小屏手机 |
| 480-768px | 大屏手机 |
| 768-1024px | 平板 |
| > 1024px | 电脑 |
---
## 九、课程推荐策略
### 9.1 推荐逻辑
每种类型的结果页底部,根据该类型的"薄弱维度"推荐对应的课程内容:
| 类型 | 推荐切入点 | 推荐话术方向 |
|------|-----------|-------------|
| 觉醒者 | 深化修炼 | "你已经走在觉醒的路上,课程将帮你从'看见'走向'止于至善'" |
| 破冰人 | 冰山模型 | "你已开始打破旧模式,课程将带你系统理解冰山下的家族数据" |
| 知止行者 | 从知止到看见 | "你已能在情绪中停住,课程将帮你看见情绪背后的根源" |
| 传承守护者 | 从意识到行动 | "你对传承有深刻意识,课程将帮你把意识转化为每天的功课" |
| 寻道者 | 格物致知 | "你有强大的成长动力,课程将帮你找到内在卡点,明确方向" |
| 沉睡者 | 基础功课 | "觉醒的第一步,是看见。课程将从最基础的'我看见我'开始" |
| 关系困者 | 关系修复 | "关系是你的突破口,课程将教你从'我看见我的妈妈'开始修复" |
| 惯性行者 | 看见即觉察 | "看见即觉察,觉察即涨心。课程将帮你走出惯性,找回内在力量" |
### 9.2 推荐展示形式
- 推荐语2-3句
- 课程链接按钮
- 课程二维码(图片)
---
## 十、分享海报设计
### 10.1 海报内容
```
┌─────────────────────────┐
│ │
│ [类型简笔画图标] │
│ │
│ 你的传承基因是 │
│ 「 觉醒者 」 │
│ │
│ "已走在自我成长路上, │
│ 觉察力和情绪力都很强" │
│ │
│ ┌─────────────┐ │
│ │ 简化雷达图 │ │
│ └─────────────┘ │
│ │
│ 扫码测测你的传承基因 │
│ [网站二维码] │
│ │
└─────────────────────────┘
```
### 10.2 海报规格
- 尺寸750 × 1334px适合手机屏幕分享
- 背景:蓝紫渐变
- 字体:白色为主,类型标签加大加粗
---
## 十一、后续待确认事项
- [ ] 课程链接/二维码素材
- [ ] 8种类型的简笔画图标设计可用AI生成或手绘
- [ ] 网站域名和部署方式
- [ ] 是否需要统计答题数据(如需则需接入简单后端或第三方统计)