redesign homepage: 测测-style layout, icon grid for personality/zodiac tests, remove clinical terminology from headers

This commit is contained in:
jackyu66git
2026-08-02 14:25:37 +08:00
parent e6ad46fe87
commit 2686866376
14 changed files with 25369 additions and 168 deletions
+14 -4
View File
@@ -27,19 +27,20 @@ body{
.sub{font-size:14px;color:rgba(0,0,0,0.4);margin-bottom:40px;letter-spacing:1px}
.links{display:flex;flex-direction:column;gap:14px;width:100%;max-width:360px}
.link-card{
display:flex;align-items:center;gap:14px;padding:18px 20px;
display:flex;align-items:center;gap:14px;padding:28px 26px;
background:#fff;border-radius:14px;text-decoration:none;
box-shadow:0 2px 10px rgba(0,0,0,0.04);transition:transform 0.15s;
}
.link-card:active{transform:scale(0.97)}
.link-card .li{width:44px;height:44px;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0}
.link-card .lt{font-size:16px;font-weight:600;color:#333}
.link-card .ld{font-size:12px;color:#999;margin-top:2px}
.link-card .li{width:62px;height:62px;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:32px;flex-shrink:0}
.link-card .lt{font-size:24px;font-weight:600;color:#333}
.link-card .ld{font-size:16px;color:#999;margin-top:2px}
.c1 .li{background:#FFECEC;color:var(--pri)}
.c2 .li{background:#E6F0FF;color:#4A90E2}
.c3 .li{background:#E6FFE8;color:#5CB85C}
.c4 .li{background:#FFF8E6;color:#E8985A}
.c5 .li{background:#FFF5EC;color:#C8923A}
.c6 .li{background:#F3E8FF;color:#8B5CF6}
</style>
</head>
<body>
@@ -68,6 +69,15 @@ body{
<div class="li">☯️</div>
<div><div class="lt">AI东方养生</div><div class="ld">易经 × 内经 · 体质分析</div></div>
</a>
<a href="xingjing_app.html" class="link-card c6">
<div class="li">🔭</div>
<div><div class="lt">星镜<span style="font-size:11px;background:#8B5CF6;color:#fff;padding:2px 6px;border-radius:8px;margin-left:6px;vertical-align:middle">MVP</span></div><div class="ld">动态星盘 · AI视频报告 · 性格测试</div></div>
</a>
<a href="scales.html" class="link-card c3">
<div class="li">📊</div>
<div><div class="lt">愈心量表<span style="font-size:11px;background:#5CB85C;color:#fff;padding:2px 6px;border-radius:8px;margin-left:6px;vertical-align:middle">NEW</span></div><div class="ld">专业心理测评 · 58个临床量表</div></div>
</a>
<a href="xingjing.html" style="font-size:12px;color:var(--sub);text-align:center;text-decoration:none;padding:6px">📋 查看星镜产品设计文档</a>
</div>
</body>
BIN
View File
Binary file not shown.
+953
View File
@@ -0,0 +1,953 @@
{
"xl1": {
"slug": "xl1",
"name": "症状自评量表(SCL-90)",
"ratings": [
{
"max": 228,
"label": "阳性"
},
{
"max": 64,
"label": "阳性"
},
{
"max": 26,
"label": "阳性"
},
{
"max": 25,
"label": "2.08"
},
{
"max": 20,
"label": "2.00"
},
{
"max": 31,
"label": "3.44"
},
{
"max": 26,
"label": "2.00"
},
{
"max": 26,
"label": "2.60"
},
{
"max": 17,
"label": "2.83"
},
{
"max": 22,
"label": "3.14"
},
{
"max": 16,
"label": "2.67"
},
{
"max": 31,
"label": "3.10"
},
{
"max": 14,
"label": "2.00"
},
{
"max": 246,
"label": "222"
}
],
"factors": [
{
"name": "躯体化",
"range": "2"
},
{
"name": "强迫症状",
"range": "2"
},
{
"name": "人际敏感",
"range": "3"
},
{
"name": "抑郁",
"range": "2"
},
{
"name": "焦虑",
"range": "2"
},
{
"name": "敌对",
"range": "2"
},
{
"name": "恐怖",
"range": "3"
},
{
"name": "偏执",
"range": "2"
},
{
"name": "精神病性",
"range": "3"
},
{
"name": "其它",
"range": "2"
},
{
"name": "总分",
"range": "222"
}
]
},
"yyzcs": {
"slug": "yyzcs",
"name": "抑郁自评量表(SDS)",
"formula": "标准总分=原始总分×1.25取",
"multiplier": 1.25,
"ratings": [
{
"max": 4,
"label": "2~8"
},
{
"max": 24,
"label": "8~32"
},
{
"max": 2,
"label": "2~8"
},
{
"max": 20,
"label": "8~32"
},
{
"max": 50,
"label": "20~80"
},
{
"max": 63,
"label": "25~100"
},
{
"max": 52,
"label": "可能没有抑郁"
},
{
"max": 62,
"label": "可能轻度抑郁"
},
{
"max": 72,
"label": "可能中度抑郁"
},
{
"max": 100,
"label": "可能重度抑郁"
}
],
"total_range": "20~80"
},
"xl4": {
"slug": "xl4",
"name": "艾森克情绪稳定性测验(EES)",
"ratings": [
{
"max": 21,
"label": "17"
},
{
"max": 30,
"label": "自尊感"
},
{
"max": 22,
"label": "13"
},
{
"max": 30,
"label": "愉快"
},
{
"max": 16,
"label": "15"
},
{
"max": 1,
"label": "安详"
},
{
"max": 11,
"label": "14"
},
{
"max": 1,
"label": "随意性"
},
{
"max": 20,
"label": "12"
},
{
"max": 30,
"label": "自主性"
},
{
"max": 6,
"label": "10"
},
{
"max": 1,
"label": "健康感"
},
{
"max": 8,
"label": "13"
},
{
"max": 1,
"label": "无负罪感"
}
]
},
"xl12": {
"slug": "xl12",
"name": "青少年焦虑障碍筛查量表(SCARED)",
"ratings": [
{
"max": 7,
"label": "异常"
},
{
"max": 6,
"label": "正常"
},
{
"max": 3,
"label": "正常"
},
{
"max": 4,
"label": "正常"
},
{
"max": 2,
"label": "正常"
},
{
"max": 22,
"label": "正常"
}
]
},
"xl6": {
"slug": "xl6",
"name": "卡特尔十六型人格测试(16PF)",
"ratings": [
{
"max": 9,
"label": "5"
},
{
"max": 6,
"label": "4"
},
{
"max": 13,
"label": "4.7"
},
{
"max": 13,
"label": "5.9"
},
{
"max": 11,
"label": "5.1"
},
{
"max": 9,
"label": "7.6"
},
{
"max": 16,
"label": "6.5"
},
{
"max": 10,
"label": "4.5"
},
{
"max": 10,
"label": "4.9"
},
{
"max": 12,
"label": "4.6"
},
{
"max": 11,
"label": "6"
},
{
"max": 13,
"label": "5.7"
},
{
"max": 9,
"label": "4.5"
},
{
"max": 6,
"label": "2.8"
},
{
"max": 9,
"label": "4.1"
},
{
"max": 8,
"label": "4.3"
},
{
"max": 52,
"label": "分数介于10—100分之间,平均为55分,60分约等于标准分7,63分以上约等于标准分8、9、10,总分67分以上者一般应有所成就。"
}
],
"factors": [
{
"name": "乐群性",
"range": "5"
},
{
"name": "聪慧性",
"range": "4"
},
{
"name": "情绪稳定性",
"range": "4"
},
{
"name": "恃强性",
"range": "5"
},
{
"name": "兴奋性",
"range": "5"
},
{
"name": "有恒性",
"range": "7"
},
{
"name": "敢为性",
"range": "6"
},
{
"name": "敏感性",
"range": "4"
},
{
"name": "怀疑性",
"range": "4"
},
{
"name": "幻想性",
"range": "4"
},
{
"name": "世故性",
"range": "6"
},
{
"name": "忧虑性",
"range": "5"
},
{
"name": "激进性",
"range": "4"
},
{
"name": "独立性",
"range": "2"
},
{
"name": "自律性",
"range": "4"
},
{
"name": "紧张性",
"range": "4"
}
]
},
"xl16": {
"slug": "xl16",
"name": "大五人格测试(NEO-FFI)",
"ratings": [
{
"max": 35,
"label": "45.4"
},
{
"max": 33,
"label": "28.8"
},
{
"max": 41,
"label": "36.3"
},
{
"max": 35,
"label": "33.9"
},
{
"max": 37,
"label": "43.7"
}
]
},
"yyz": {
"slug": "yyz",
"name": "贝克抑郁量表(BDI-II)",
"ratings": [
{
"max": 13,
"label": "可能没有抑郁"
},
{
"max": 19,
"label": "可能轻度抑郁"
},
{
"max": 28,
"label": "可能中度抑郁"
},
{
"max": 63,
"label": "可能重度抑郁"
}
]
},
"jlz": {
"slug": "jlz",
"name": "贝克焦虑测试量表(BAI)",
"ratings": [
{
"max": 44,
"label": "没有焦虑症状"
},
{
"max": 100,
"label": "存在焦虑症状,并且分值越高可能越严重"
}
]
},
"eq1": {
"slug": "eq1",
"name": "情商测试量表(BARON)",
"ratings": [
{
"max": 8,
"label": "个体对自己情绪的觉察和理解能力。自我情绪认知有助于我们更好地管理自己的情绪,建立更健康的人际关系。"
},
{
"max": 13,
"label": "个体能够有效地管理和控制自己的情绪反应。它涉及到对自己情绪的觉察、理解和主动干预,以实现情绪的稳定和积极表达。"
},
{
"max": 10,
"label": "个体能够激发自己的内在动力和激情,以实现个人目标和追求。自我激励对于个人的成长和发展至关重要。通过激发自己的内在动力和激情,我们可以更好地应对挑战和压力。"
},
{
"max": 11,
"label": "个体能够感知、理解、评估和应对他人情绪的能力。它是社交情商中的重要组成部分,也是建立良好人际关系的关键因素之一。通过感知和理解他人的情绪,我们可以更好地与他人沟通和交流,建立信任和共鸣,增强彼此的情感联系和认同感。同时,他人情绪认知也有助于我们更好地处理冲突和矛盾,减少误解和冲突,提高人际关系的和谐度和质量。"
},
{
"max": 9,
"label": "个体在人际交往中运用情商技巧和能力的综合表现。它涉及到自我认知、自我管理、他人认知和人际关系管理等多个方面。通过有效地管理人际关系,我们可以建立更健康、更和谐的人际关系,提高自己的社交能力和领导力,实现个人和团队的目标。同时,人际关系管理也有助于我们提高自己的情商水平,增强自我认知和自我管理能力,从而更好地应对人生的挑战和变化。"
},
{
"max": 51,
"label": "在心理学中,情商主要包含以上5个方面。"
}
]
},
"xl13": {
"slug": "xl13",
"name": "防御方式自评量表(DSQ)",
"ratings": [
{
"max": 13,
"label": "1.44"
},
{
"max": 7,
"label": "1.4"
},
{
"max": 10,
"label": "2"
},
{
"max": 5,
"label": "1.67"
},
{
"max": 2,
"label": "2"
},
{
"max": 4,
"label": "1.33"
},
{
"max": 2,
"label": "1"
},
{
"max": 3,
"label": "1.5"
},
{
"max": 46,
"label": "1.53"
},
{
"max": 4,
"label": "1.33"
},
{
"max": 4,
"label": "2"
},
{
"max": 4,
"label": "1.33"
},
{
"max": 12,
"label": "1.5"
},
{
"max": 6,
"label": "1.2"
},
{
"max": 5,
"label": "1.67"
},
{
"max": 7,
"label": "1.4"
},
{
"max": 5,
"label": "1.67"
},
{
"max": 3,
"label": "1.5"
},
{
"max": 2,
"label": "2"
},
{
"max": 7,
"label": "1.17"
},
{
"max": 6,
"label": "1.5"
},
{
"max": 2,
"label": "2"
},
{
"max": 6,
"label": "2"
},
{
"max": 3,
"label": "1.5"
},
{
"max": 4,
"label": "1.33"
},
{
"max": 2,
"label": "1"
},
{
"max": 58,
"label": "1.45"
},
{
"max": 13,
"label": "1.3"
}
]
},
"gd4": {
"slug": "gd4",
"name": "家庭环境量表(FES)",
"ratings": [
{
"max": 4,
"label": "0~9"
},
{
"max": 5,
"label": "0~9"
},
{
"max": 4,
"label": "0~9"
},
{
"max": 7,
"label": "0~9"
},
{
"max": 7,
"label": "0~9"
},
{
"max": 5,
"label": "0~9"
},
{
"max": 3,
"label": "0~9"
},
{
"max": 5,
"label": "0~9"
},
{
"max": 1,
"label": "0~9"
},
{
"max": 5,
"label": "0~9"
}
],
"factors": [
{
"name": "亲密度",
"range": "0~9"
},
{
"name": "情感表达",
"range": "0~9"
},
{
"name": "矛盾性",
"range": "0~9"
},
{
"name": "独立性",
"range": "0~9"
},
{
"name": "成功性",
"range": "0~9"
},
{
"name": "知识性",
"range": "0~9"
},
{
"name": "娱乐性",
"range": "0~9"
},
{
"name": "道德宗教观",
"range": "0~9"
},
{
"name": "组织性",
"range": "0~9"
},
{
"name": "控制性",
"range": "0~9"
}
]
},
"gd2": {
"slug": "gd2",
"name": "爱情态度测试(LAS)",
"ratings": [
{
"max": 10,
"label": "最注重的是对方的外表和身体的接触;只要是好看的,就容易跟对方坠入情网"
},
{
"max": 10,
"label": "视爱情为游戏,爱情的关系短暂,经常更换对象,承诺在这种类型的人身上几乎看不到"
},
{
"max": 9,
"label": "感情发展细水长流,平静而祥和,通常刚开始时都只是好朋友的关系,后来才慢慢从相知友谊发展成爱情"
},
{
"max": 11,
"label": "选择对象以理性条件的考虑为主,诸如教育背景、经济能力、社会地位、共同兴趣等"
},
{
"max": 9,
"label": "占有欲和嫉妒心强烈,关系也有如风暴,起伏不定,对方一点爱意的表示就会让他狂喜,一点点降温或关系出现点小问题就痛苦不已"
},
{
"max": 13,
"label": "与占有型相反,这种类型的人为爱人完全付出自己,关心对方而不求回报。这种人极有耐心、不要求对方,甚至不嫉妒"
}
]
},
"xl2": {
"slug": "xl2",
"name": "明尼苏达多项人格测验(MMPI)",
"ratings": [
{
"max": 6,
"label": "-"
},
{
"max": 8,
"label": "60"
},
{
"max": 32,
"label": "77"
},
{
"max": 14,
"label": "55"
},
{
"max": 24,
"label": "81"
},
{
"max": 30,
"label": "58"
},
{
"max": 33,
"label": "70"
},
{
"max": 31,
"label": "77"
},
{
"max": 31,
"label": "59"
},
{
"max": 22,
"label": "74"
},
{
"max": 42,
"label": "81"
},
{
"max": 55,
"label": "81"
},
{
"max": 22,
"label": "57"
},
{
"max": 31,
"label": "45"
}
],
"factors": [
{
"name": "说谎(L)",
"range": "60"
},
{
"name": "诈病(F)",
"range": "77"
},
{
"name": "校正(K)",
"range": "55"
},
{
"name": "疑病(Hs)",
"range": "81"
},
{
"name": "抑郁(D)",
"range": "58"
},
{
"name": "癔病(Hy)",
"range": "70"
},
{
"name": "精神病态(Pd)",
"range": "77"
},
{
"name": "男性女性化(Mf)",
"range": "59"
},
{
"name": "妄想(Pa)",
"range": "74"
},
{
"name": "精神衰弱(Pt)",
"range": "81"
},
{
"name": "精神分裂(Sc)",
"range": "81"
},
{
"name": "轻躁狂(Ma)",
"range": "57"
},
{
"name": "社会内向(Si)",
"range": "45"
}
]
},
"xl3": {
"slug": "xl3",
"name": "艾森克人格问卷(EPQ)",
"ratings": [
{
"max": 19,
"label": "71.4"
},
{
"max": 20,
"label": "65.7"
},
{
"max": 10,
"label": "68.4"
},
{
"max": 6,
"label": "33.2"
}
],
"factors": [
{
"name": "内外向(E)",
"range": "71"
},
{
"name": "神经质(N)",
"range": "65"
},
{
"name": "精神质(P)",
"range": "68"
},
{
"name": "掩饰性(L)",
"range": "33"
}
]
},
"xl7": {
"slug": "xl7",
"name": "康奈尔医学指数(CMI)",
"ratings": [
{
"max": 4,
"label": "0~9"
},
{
"max": 8,
"label": "0~18"
},
{
"max": 3,
"label": "0~13"
},
{
"max": 13,
"label": "0~23"
},
{
"max": 5,
"label": "0~8"
},
{
"max": 2,
"label": "0~7"
},
{
"max": 9,
"label": "0~18"
},
{
"max": 5,
"label": "0~11"
},
{
"max": 3,
"label": "0~7"
},
{
"max": 4,
"label": "0~9"
},
{
"max": 9,
"label": "0~15"
},
{
"max": 3,
"label": "0~6"
},
{
"max": 6,
"label": "0~12"
},
{
"max": 3,
"label": "0~6"
},
{
"max": 6,
"label": "0~9"
},
{
"max": 4,
"label": "0~6"
},
{
"max": 4,
"label": "0~9"
},
{
"max": 4,
"label": "0~9"
}
]
}
}
+316
View File
@@ -0,0 +1,316 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
<title>量表测试 — 愈心谷</title>
<style>
:root{--pri:#E54D42;--bg-start:#FFD1C7;--bg-end:#FFC8B5}
*{box-sizing:border-box;margin:0;padding:0}
body{
font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;
background:linear-gradient(180deg,var(--bg-start) 0%,var(--bg-end) 50%,#fff5f0 100%);
min-height:100vh;-webkit-font-smoothing:antialiased;
display:flex;flex-direction:column;
}
.top-bar{display:flex;align-items:center;padding:14px 16px;gap:12px;max-width:500px;margin:0 auto}
.top-bar .back{font-size:22px;text-decoration:none;color:#666;line-height:1}
.top-bar .title{flex:1;font-size:16px;font-weight:600;color:#333;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:center}
.top-bar .qnum{font-size:13px;color:#999}
.progress{height:3px;background:rgba(0,0,0,0.08);max-width:500px;margin:0 auto}
.progress .bar{height:100%;background:var(--pri);transition:width 0.3s}
.main{flex:1;display:flex;flex-direction:column;padding:20px;max-width:500px;width:100%;margin:0 auto}
.qcard{background:#fff;border-radius:14px;padding:28px 22px;box-shadow:0 2px 10px rgba(0,0,0,0.04);flex:1}
.qcard .qtext{font-size:17px;color:#333;line-height:1.7;margin-bottom:24px}
.qcard .qtext .qidx{color:var(--pri);font-weight:600;margin-right:6px}
.options{display:flex;flex-direction:column;gap:10px}
.option{
padding:14px 18px;border-radius:10px;border:1.5px solid #eee;
font-size:15px;color:#444;cursor:pointer;transition:all 0.15s;text-align:left;background:#fafafa;
}
.option:hover{border-color:var(--pri);background:#fff5f3}
.option.selected{border-color:var(--pri);background:#fff0ed;color:var(--pri);font-weight:500}
.btns{display:flex;gap:12px;padding:16px 0 30px;max-width:500px;width:100%;margin:0 auto}
.btns button{
flex:1;padding:14px;border-radius:10px;border:none;font-size:15px;cursor:pointer;
font-weight:500;transition:all 0.15s;
}
.btn-prev{background:#f0f0f0;color:#666}
.btn-next{background:var(--pri);color:#fff}
.btn-submit{background:#4CAF50;color:#fff}
button:disabled{opacity:0.4;cursor:not-allowed}
.submit-area{text-align:center;padding:40px 20px}
.submit-area p{font-size:14px;color:#888;margin-bottom:20px}
.submit-area button{
padding:14px 40px;border-radius:10px;background:var(--pri);color:#fff;
border:none;font-size:16px;cursor:pointer;font-weight:500;
}
.result-card{background:#fff;border-radius:14px;padding:32px 22px;box-shadow:0 2px 10px rgba(0,0,0,0.04);text-align:center;max-width:500px;width:100%;margin:0 auto}
.result-card .score{font-size:48px;font-weight:700;color:var(--pri);margin:12px 0}
.result-card .level{font-size:18px;color:#666;margin-bottom:20px}
.result-card .detail{font-size:14px;color:#999;line-height:1.6;text-align:left}
.result-card .back-btn{
display:inline-block;margin-top:20px;padding:12px 30px;background:var(--pri);color:#fff;
border-radius:10px;text-decoration:none;font-size:15px;
}
.loading{text-align:center;padding:80px 20px;color:#999}
</style>
</head>
<body>
<div id="app"><div class="loading">加载中…</div></div>
<script>
const params = new URLSearchParams(location.search);
const slug = params.get('slug');
let scale = null, answers = [], current = 0, submitted = false, started = false;
async function init() {
if (!slug) { document.getElementById('app').innerHTML = '<div class="loading">缺少量表参数</div>'; return; }
try {
const r = await fetch(`api/scales/${slug}`);
scale = await r.json();
if (scale.error) throw new Error(scale.error);
answers = new Array(scale.question_count).fill(null);
// Restore from localStorage
const cached = localStorage.getItem(`scale_${slug}`);
if (cached) answers = JSON.parse(cached);
renderStart();
} catch(e) {
document.getElementById('app').innerHTML = `<div class="loading">加载失败: ${e.message}</div>`;
}
}
function renderStart() {
const hasCache = answers.some(a => a !== null);
const answered = answers.filter(a => a !== null).length;
const rawDesc = scale.description || '';
const parts = rawDesc.split('\n\n');
const mainDesc = parts[0] || '';
const metaLine = parts.slice(1).join(' · ') || '';
const timeMin = Math.ceil(scale.question_count / 6);
const timeMax = Math.ceil(scale.question_count / 4);
document.getElementById('app').innerHTML = `
<div style="max-width:500px;margin:0 auto;position:relative;text-align:center;padding:32px 20px 8px">
<a href="scales.html" style="position:absolute;left:20px;top:34px;color:#999;font-size:20px;text-decoration:none;line-height:1">←</a>
<div style="font-size:20px;font-weight:700;color:#333">${scale.name}</div>
</div>
<div class="main" style="justify-content:center;padding:8px 20px 16px">
<div class="qcard" style="text-align:left;flex:none;padding:24px 20px">
${mainDesc ? `<div style="font-size:15px;color:#444;line-height:1.85;margin-bottom:16px">${mainDesc}</div>` : ''}
${metaLine ? `<div style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:16px">
${metaLine.split('·').filter(m=>m.trim()).map(m =>
`<span style="display:inline-block;padding:4px 10px;background:#f5f5f5;border-radius:6px;font-size:12px;color:#888">${m.trim()}</span>`
).join('')}
</div>` : ''}
<div style="display:flex;align-items:center;gap:12px;padding:12px 14px;background:#f9f9f9;border-radius:8px;margin-bottom:16px;font-size:13px;color:#888">
<span>📝 <b style="color:#555">${scale.question_count}</b> 题</span>
<span>⏱ ${timeMin}-${timeMax} 分钟</span>
<span>🔒 自动保存</span>
</div>
${hasCache
? `<div style="background:#fff5f3;padding:12px;border-radius:8px;margin-bottom:14px;font-size:13px;color:var(--pri);text-align:center">
📌 已有答题进度:<b>${answered}/${scale.question_count}</b> 题
</div>`
: ''}
<button onclick="startTest()" style="display:block;width:100%;padding:15px;border:none;border-radius:10px;background:var(--pri);color:#fff;font-size:17px;font-weight:600;cursor:pointer">
${hasCache ? `继续测试 (${answered}/${scale.question_count})` : '开始测试'}
</button>
${hasCache
? `<button onclick="resetAndStart()" style="display:block;width:100%;padding:12px;border:1px solid #ddd;border-radius:10px;background:#fff;color:#999;font-size:14px;cursor:pointer;margin-top:8px">重新开始</button>`
: ''}
</div>
</div>
`;
}
function startTest() {
started = true;
// Resume from first unanswered question
const firstUnanswered = answers.findIndex(a => a === null);
current = firstUnanswered >= 0 ? firstUnanswered : 0;
render();
}
function resetAndStart() {
answers = new Array(scale.question_count).fill(null);
localStorage.removeItem(`scale_${slug}`);
started = true;
current = 0;
render();
}
function saveCache() {
localStorage.setItem(`scale_${slug}`, JSON.stringify(answers));
}
function selectOption(idx) {
if (submitted) return;
answers[current] = idx;
saveCache();
render();
// Auto advance after short delay
if (current < scale.question_count - 1) {
setTimeout(() => { current++; render(); }, 300);
}
}
function goPrev() { if (current > 0) { current--; render(); } }
function goNext() { if (current < scale.question_count - 1) { current++; render(); } }
async function submit() {
if (submitted) return;
submitted = true;
saveCache();
try {
const r = await fetch('api/scales/result', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
scale_slug: scale.slug,
scale_name: scale.name,
answers: answers,
})
});
const data = await r.json();
// Clear cache
localStorage.removeItem(`scale_${slug}`);
renderResult(data);
} catch(e) {
submitted = false;
alert('提交失败,请重试');
render();
}
}
function renderResult(data) {
const s = data.scores;
const allAnswered = s.answer_count;
const total = scale.question_count;
const scoreDisplay = s.std_score !== null ? s.std_score : s.raw_score;
const isStd = s.std_score !== null;
const isFactorScale = s.factors && Object.keys(s.factors).length >= 6;
const hasFactors = s.factors && Object.keys(s.factors).length > 0;
// Factor bars (for any factor count, not just 8+)
let factorsHtml = '';
if (hasFactors) {
const factorMax = s.factor_max || {};
const bars = Object.entries(s.factors).map(([k,v]) => {
const max = factorMax[k] || 50;
const pct = Math.min(Math.round(v / max * 100), 100);
return `<div style="margin:6px 0">
<div style="display:flex;justify-content:space-between;font-size:12px;margin-bottom:2px">
<span style="color:#666">${k}</span><span style="color:var(--pri);font-weight:600">${v}<span style="color:#bbb;font-weight:400">/${max}</span></span>
</div>
<div style="height:6px;background:#f0f0f0;border-radius:3px;overflow:hidden">
<div style="height:100%;width:${pct}%;background:linear-gradient(90deg,var(--pri),#F0987A);border-radius:3px;transition:width 0.5s"></div>
</div>
</div>`;
}).join('');
let insightHtml = '';
if (isFactorScale) {
const sorted = Object.entries(s.factors).sort((a,b) => b[1]-a[1]);
const top3 = sorted.slice(0,3).map(([k,v]) => `<b>${k}</b>`).join('、');
insightHtml = `<div style="margin-top:14px;padding:12px;background:#fff5f3;border-radius:8px;font-size:13px;line-height:1.7">
🌟 <b>优势方向:</b>${top3}<br>
<span style="color:#999">得分最高的领域代表天赋倾向,建议重点关注和培养。</span>
</div>`;
}
factorsHtml = `<div style="margin:20px 0 0;text-align:left">
<div style="font-size:14px;font-weight:600;color:#555;margin-bottom:8px">${isFactorScale ? '各维度得分' : '因子得分'}</div>
${bars}
${insightHtml}
</div>`;
}
// Rating badge
let ratingHtml = '';
if (s.rating) {
ratingHtml = `<div style="display:inline-block;margin-top:8px;padding:6px 18px;background:${isFactorScale?'#fff5f3':'#fef0ed'};border-radius:20px;font-size:15px;font-weight:600;color:var(--pri)">${s.rating}</div>`;
}
document.getElementById('app').innerHTML = `
<div class="top-bar">
<a href="scales.html" class="back">←</a>
<div class="title">${scale.name}</div>
</div>
<div class="result-card" style="text-align:center">
<div style="font-size:13px;color:#bbb;margin-bottom:4px">测评完成 · ${allAnswered}/${total} 题</div>
${isFactorScale
? `<div style="font-size:22px;font-weight:700;color:#333;margin:8px 0 4px">多元智能测评结果</div>`
: `<div style="margin:8px 0">
<span style="font-size:56px;font-weight:700;color:var(--pri);line-height:1">${scoreDisplay}</span>
${isStd ? `<span style="font-size:16px;color:#999;margin-left:4px">分</span>` : ''}
</div>
${isStd ? `<div style="font-size:12px;color:#bbb;margin-top:-4px">原始分 ${s.raw_score} × 1.25 = 标准分 ${scoreDisplay}</div>` : ''}
${!isStd && !isFactorScale ? `<div style="font-size:13px;color:#999">满分 ${s.max_score} · 占比 ${(s.raw_score/s.max_score*100).toFixed(0)}%</div>` : ''}`}
${ratingHtml}
${factorsHtml}
<div style="margin-top:24px;display:flex;gap:10px;justify-content:center">
<a href="scales.html" style="flex:1;padding:12px;background:var(--pri);color:#fff;border-radius:10px;text-decoration:none;font-size:14px;font-weight:500">返回量表库</a>
<a href="scale_test.html?slug=${slug}" style="flex:1;padding:12px;background:#f5f5f5;color:#666;border-radius:10px;text-decoration:none;font-size:14px">重新测试</a>
</div>
<p style="margin-top:16px;color:#ccc;font-size:11px">* 结果仅供参考,不能替代专业诊断</p>
</div>
`;
}
function render() {
if (!scale) return;
const total = scale.question_count;
const pct = ((current + 1) / total * 100).toFixed(0);
const isLast = current === total - 1;
const selected = answers[current];
const q = scale.questions[current];
const opts = scale.options[current];
const optHtml = opts.map((o, i) => `
<button class="option${selected === i ? ' selected' : ''}" onclick="selectOption(${i})">
${String.fromCharCode(65+i)}. ${o}
</button>
`).join('');
const allAnswered = answers.every(a => a !== null);
document.getElementById('app').innerHTML = `
<div class="top-bar">
<a href="scales.html" class="back">←</a>
<div class="title">${scale.name}</div>
<div class="qnum">${current+1}/${total}</div>
</div>
<div class="progress"><div class="bar" style="width:${pct}%"></div></div>
<div class="main">
<div class="qcard">
<div class="qtext"><span class="qidx">${current+1}.</span>${q}</div>
<div class="options">${optHtml}</div>
</div>
</div>
<div class="btns">
<button class="btn-prev" onclick="goPrev()" ${current===0?'disabled':''}>上一题</button>
${isLast
? `<button class="btn-submit" onclick="submit()" ${allAnswered?'':'disabled'}>✓ 提交答卷</button>`
: `<button class="btn-next" onclick="goNext()" ${selected===null?'disabled':''}>下一题 →</button>`
}
</div>
${isLast ? `<div style="text-align:center;padding:0 20px 20px;font-size:12px;color:#999">${allAnswered?'全部答完,可以提交了':'还有题目未作答'}</div>` : ''}
`;
}
init();
</script>
</body>
</html>
View File
+124
View File
@@ -0,0 +1,124 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
<title>愈心量表 — 愈心谷</title>
<style>
:root{--pri:#E54D42;--bg-start:#FFD1C7;--bg-end:#FFC8B5}
*{box-sizing:border-box;margin:0;padding:0}
body{
font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;
background:linear-gradient(180deg,var(--bg-start) 0%,var(--bg-end) 50%,#fff5f0 100%);
min-height:100vh;-webkit-font-smoothing:antialiased;
}
.header{padding:24px 20px 16px;text-align:center}
.logo img{height:36px;margin-bottom:4px}
.header .sub{font-size:13px;color:rgba(0,0,0,0.45);margin-top:4px}
.categories{display:flex;flex-wrap:wrap;gap:8px;padding:0 20px 12px;justify-content:center;max-width:600px;margin:0 auto}
.cat-btn{
padding:7px 16px;border-radius:20px;border:1px solid rgba(0,0,0,0.12);
background:#fff;font-size:13px;color:#555;cursor:pointer;
transition:all 0.2s;white-space:nowrap;
}
.cat-btn.active{background:var(--pri);color:#fff;border-color:var(--pri)}
.scales{max-width:600px;margin:0 auto;padding:0 20px 40px}
.scale-card{
display:flex;align-items:center;gap:12px;padding:16px;
background:#fff;border-radius:12px;margin-bottom:10px;
box-shadow:0 1px 6px rgba(0,0,0,0.05);cursor:pointer;
transition:transform 0.12s;text-decoration:none;color:inherit;
}
.scale-card:active{transform:scale(0.98)}
.scale-card .icon{width:44px;height:44px;border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:22px;flex-shrink:0}
.scale-card .icon.cat-综合{background:#FFECEC;color:var(--pri)}
.scale-card .icon.cat-人格{background:#E6F0FF;color:#4A90E2}
.scale-card .icon.cat-抑郁{background:#E6E0FF;color:#7C5CF2}
.scale-card .icon.cat-焦虑{background:#FFF0E6;color:#E8985A}
.scale-card .icon.cat-强迫{background:#E6FFE8;color:#5CB85C}
.scale-card .icon.cat-躁狂{background:#FFF8E6;color:#E8985A}
.scale-card .icon.cat-双相{background:#FFE6F0;color:#E0508C}
.scale-card .icon.cat-恐惧{background:#E6F5FF;color:#4A90E2}
.scale-card .icon.cat-智商{background:#F3E8FF;color:#8B5CF6}
.scale-card .icon.cat-情商{background:#FFF5EC;color:#C8923A}
.scale-card .icon.cat-其他{background:#F0F0F0;color:#888}
.scale-card .info{flex:1;min-width:0}
.scale-card .info .name{font-size:15px;font-weight:600;color:#333}
.scale-card .info .desc{font-size:12px;color:#999;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.scale-card .count{font-size:12px;color:#aaa;flex-shrink:0}
.loading{text-align:center;padding:60px 20px;color:#999}
.empty{text-align:center;padding:40px 20px;color:#ccc;font-size:14px}
.back{display:inline-block;padding:8px 16px;color:var(--pri);text-decoration:none;font-size:14px;margin:8px 0 0 20px}
</style>
</head>
<body>
<div class="header">
<a href="./" class="logo"><img src="logo.png" alt="愈心谷"></a>
<div class="sub">愈心谷数字解构 · 专业心理量表库</div>
</div>
<div class="categories" id="cats"></div>
<div id="content"></div>
<script>
const ICONS = {'心理测试综合':'📋','人格测试':'🎭','抑郁症测试':'🌧️','焦虑症测试':'😰','强迫症测试':'🔄','躁狂症测试':'🔥','双向情感障碍':'🌓','恐惧症测试':'😨','智商测试':'🧠','情商测试':'💝','其它测试':'📌'};
const CAT_KEYS = {'心理测试综合':'cat-综合','人格测试':'cat-人格','抑郁症测试':'cat-抑郁','焦虑症测试':'cat-焦虑','强迫症测试':'cat-强迫','躁狂症测试':'cat-躁狂','双向情感障碍':'cat-双相','恐惧症测试':'cat-恐惧','智商测试':'cat-智商','情商测试':'cat-情商','其它测试':'cat-其他'};
let scales = [], activeCat = '全部';
async function load() {
document.getElementById('content').innerHTML = '<div class="loading">加载中…</div>';
try {
const r = await fetch('api/scales');
scales = await r.json();
renderCats();
renderScales();
} catch(e) {
document.getElementById('content').innerHTML = '<div class="loading">加载失败,请刷新重试</div>';
}
}
function renderCats() {
const cats = [...new Set(scales.map(s=>s.category))];
const html = ['<button class="cat-btn active" onclick="filter(\'全部\')">全部</button>'];
cats.forEach(c => {
html.push(`<button class="cat-btn" onclick="filter('${c}')">${c}</button>`);
});
document.getElementById('cats').innerHTML = html.join('');
}
function filter(cat) {
activeCat = cat;
document.querySelectorAll('.cat-btn').forEach(b => b.classList.toggle('active', b.textContent === cat));
renderScales();
}
function renderScales() {
const filtered = activeCat === '全部' ? scales : scales.filter(s=>s.category===activeCat);
if (!filtered.length) {
document.getElementById('content').innerHTML = '<div class="empty">暂无数据</div>';
return;
}
const html = filtered.map(s => `
<a href="scale_test.html?slug=${s.slug}" class="scale-card">
<div class="icon ${CAT_KEYS[s.category]||'cat-其他'}">${ICONS[s.category]||'📌'}</div>
<div class="info">
<div class="name">${s.name}</div>
<div class="desc">${s.description||''}</div>
</div>
<div class="count">${s.question_count}题</div>
</a>
`).join('');
document.getElementById('content').innerHTML = `<div class="scales">${html}</div>`;
}
load();
</script>
</body>
</html>
+20849
View File
File diff suppressed because it is too large Load Diff
+70
View File
@@ -0,0 +1,70 @@
import json, subprocess, os
BASE='https://minke8.cn'
scales=[
("jlz","贝克焦虑测试量表(BAI)","焦虑症测试"),
("jl2","汉密尔顿焦虑量表(HAMA)","焦虑症测试"),
("jl3","状态-特质焦虑问卷(STAI)","焦虑症测试"),
("jl5","焦虑症筛查量表(GAD-7)","焦虑症测试"),
("jl6","考试焦虑量表(TAS)","焦虑症测试"),
("jl8","中学生焦虑自评量表","焦虑症测试"),
("qp1","耶鲁布朗强迫标准量表(YBOCS)","强迫症测试"),
("qp3","强迫信念问卷(OBQ-44)","强迫症测试"),
("qp4","强迫量表修订版(OCI-R)","强迫症测试"),
("zk1","贝克-拉范森躁狂量表(BRMS)","躁狂症测试"),
("zk2","轻躁狂自评量表(HCL-32)","躁狂症测试"),
("zk3","杨氏躁狂评定量表(YMRS)","躁狂症测试"),
("sx1","心境障碍问卷(MDQ)","双向情感障碍"),
("sx2","双相情感障碍自评量表(BSQ)","双向情感障碍"),
("sk3","交往焦虑量表(IAS)","恐惧症测试"),
("sk4","社交回避及苦恼量表(SAD)","恐惧症测试"),
("sk5","社交焦虑量表(SIAS)","恐惧症测试"),
("sk6","社交恐惧量表(SPS)","恐惧症测试"),
("iq2","瑞文智商测试(60题)","智商测试"),
("iq3","智力测试(增强版)","智商测试"),
("eq1","情商测试量表(BARON)","情商测试"),
("eq2","情绪智力量表(EIS)","情商测试"),
("sm2","匹兹堡睡眠质量指数(PSQI)","其它测试"),
("sm1","阿森斯失眠量表(AIS)","其它测试"),
("qt1","躯体化症状自评量表(SSS)","其它测试"),
("gd6","创伤后应激障碍量表(PCL-C)","其它测试"),
("xl13","防御方式自评量表(DSQ)","其它测试"),
("gd4","家庭环境量表(FES)","其它测试"),
("gd1","婚姻质量问卷(ENRICH)","其它测试"),
("gd2","爱情态度测试(LAS)","其它测试"),
("gd3","心理压力测试(PSTR)","其它测试"),
]
results=[]
for idx,(slug,name,cat) in enumerate(scales):
url=f'{BASE}/{slug}.html'
print(f'[{idx+1}/{len(scales)}] {name}', flush=True)
try:
r=subprocess.run(f'curl -sL -X POST {url} -d action=process\&index=0', shell=True, capture_output=True, text=True, timeout=15)
d=json.loads(r.stdout)
qc=d.get('length',0)
if qc==0: print(f' SKIP (0 q)'); continue
except: print(f' FAIL init'); continue
qs=[]; os_=[]
for i in range(qc):
try:
r=subprocess.run(f'curl -sL -X POST {url} -d action=process\&index={i}', shell=True, capture_output=True, text=True, timeout=10)
d=json.loads(r.stdout)
qs.append(d.get('tm',''))
os_.append(d.get('xx',[]))
except: qs.append(''); os_.append([])
if i%30==29: print('.', end='', flush=True)
results.append({'slug':slug,'name':name,'category':cat,'url':url,'description':'','question_count':qc,'questions':qs,'options':os_})
print(f' OK({qc})')
existing=[]
if os.path.exists('scales_data.json'):
with open('scales_data.json') as f: existing=json.load(f)
existing_slugs={s['slug'] for s in existing}
for r in results:
if r['slug'] not in existing_slugs:
existing.append(r)
with open('scales_data.json','w') as f: json.dump(existing, f, ensure_ascii=False, indent=2)
total_q=sum(s['question_count'] for s in existing)
print(f'Saved: {len(existing)} scales, {total_q} questions')
+44
View File
@@ -0,0 +1,44 @@
import json, subprocess, os
BASE='https://minke8.cn'
# Scales that need tmnr_bb=1
bb_scales = [
("xl2","明尼苏达多项人格测验(MMPI)","心理测试综合", 1),
("xl3","艾森克人格问卷(EPQ)","人格测试", 1),
("xl7","康奈尔医学指数(CMI)","心理测试综合", 1),
]
results=[]
for slug,name,cat,bb in bb_scales:
url=f'{BASE}/{slug}.html'
print(f'{name} (bb={bb})', flush=True)
try:
r=subprocess.run(f'curl -sL -X POST {url} -d "action=process&index=0&tmnr_bb={bb}"', shell=True, capture_output=True, text=True, timeout=15)
d=json.loads(r.stdout)
qc=d.get('length',0)
if qc==0: print(f' SKIP'); continue
except: print(f' FAIL'); continue
qs=[]; os_=[]
for i in range(qc):
try:
r=subprocess.run(f'curl -sL -X POST {url} -d "action=process&index={i}&tmnr_bb={bb}"', shell=True, capture_output=True, text=True, timeout=10)
d=json.loads(r.stdout)
qs.append(d.get('tm',''))
os_.append(d.get('xx',[]))
except: qs.append(''); os_.append([])
if i%30==29: print('.', end='', flush=True)
results.append({'slug':slug,'name':name,'category':cat,'url':url,'description':'','question_count':qc,'questions':qs,'options':os_})
print(f' OK({qc})')
# Merge
existing=[]
if os.path.exists('scales_data.json'):
with open('scales_data.json') as f: existing=json.load(f)
existing_slugs={s['slug'] for s in existing}
for r in results:
if r['slug'] not in existing_slugs:
existing.append(r)
with open('scales_data.json','w') as f: json.dump(existing, f, ensure_ascii=False, indent=2)
total_q=sum(s['question_count'] for s in existing)
print(f'Saved: {len(existing)} scales, {total_q} questions')
+135
View File
@@ -0,0 +1,135 @@
#!/bin/bash
# Scrape all 58 scales from minke8.cn
# Usage: bash scrape_scales.sh
set -e
BASE="https://minke8.cn"
OUT="/www/Project/digital-psychology/scales_data.json"
TMP="/tmp/scales_tmp"
mkdir -p "$TMP"
echo '[' > "$OUT"
first=true
count=0
# Full list of scales
declare -A CATS=(
["xl1"]="心理测试综合" ["xl10"]="心理测试综合" ["xl11"]="心理测试综合"
["xl2"]="心理测试综合" ["xl4"]="心理测试综合" ["xl7"]="心理测试综合"
["xl12"]="心理测试综合" ["xl14"]="心理测试综合"
["mbti"]="人格测试" ["jxrg"]="人格测试" ["xl3"]="人格测试" ["xl6"]="人格测试"
["xl5"]="人格测试" ["xl16"]="人格测试" ["gd7"]="人格测试"
["yyzcs"]="抑郁症测试" ["yyz"]="抑郁症测试" ["yy9"]="抑郁症测试" ["yy4"]="抑郁症测试"
["yy6"]="抑郁症测试" ["yy7"]="抑郁症测试" ["yy8"]="抑郁症测试" ["yy10"]="抑郁症测试"
["yy11"]="抑郁症测试" ["yy12"]="抑郁症测试" ["yy13"]="抑郁症测试"
["jlz"]="焦虑症测试" ["jl1"]="焦虑症测试" ["jl2"]="焦虑症测试" ["jl3"]="焦虑症测试"
["jl5"]="焦虑症测试" ["jl6"]="焦虑症测试" ["jl8"]="焦虑症测试"
["qp1"]="强迫症测试" ["qp3"]="强迫症测试" ["qp4"]="强迫症测试"
["zk1"]="躁狂症测试" ["zk2"]="躁狂症测试" ["zk3"]="躁狂症测试"
["sx1"]="双向情感障碍" ["sx2"]="双向情感障碍"
["sk3"]="恐惧症测试" ["sk4"]="恐惧症测试" ["sk5"]="恐惧症测试" ["sk6"]="恐惧症测试"
["iq2"]="智商测试" ["iq3"]="智商测试"
["eq1"]="情商测试" ["eq2"]="情商测试"
["sm2"]="其它测试" ["sm1"]="其它测试" ["qt1"]="其它测试" ["gd6"]="其它测试"
["xl13"]="其它测试" ["gd4"]="其它测试" ["gd1"]="其它测试" ["gd2"]="其它测试" ["gd3"]="其它测试"
)
declare -A NAMES=(
["xl1"]="症状自评量表(SCL-90)" ["xl10"]="中学生心理健康量表(MSSMHS)"
["xl11"]="小学生心理健康评定量表(MHRSP)" ["xl2"]="明尼苏达多项人格测验(MMPI)"
["xl4"]="艾森克情绪稳定性测验(EES)" ["xl7"]="康奈尔医学指数(CMI)"
["xl12"]="青少年焦虑障碍筛查量表(SCARED)" ["xl14"]="简明精神病评定量表(BPRS)"
["mbti"]="人格测试(MBTI)" ["jxrg"]="九型人格测试" ["xl3"]="艾森克人格问卷(EPQ)"
["xl6"]="卡特尔十六型人格测试(16PF)" ["xl5"]="人格障碍诊断问卷(PDQ4+)"
["xl16"]="大五人格测试(NEO-FFI)" ["gd7"]="霍兰德职业兴趣测试(SDS)"
["yyzcs"]="抑郁自评量表(SDS)" ["yyz"]="贝克抑郁量表(BDI-II)"
["yy9"]="中学生抑郁自评量表" ["yy4"]="流调用抑郁量表(CES-D)"
["yy6"]="抑郁症筛查量表(PHQ-9)" ["yy7"]="儿童青少年抑郁量表(CDI)"
["yy8"]="儿童抑郁障碍自评量表(DSRSC)" ["yy10"]="老年抑郁量表(GDS)"
["yy11"]="爱丁堡产后抑郁量表(EPDS)" ["yy12"]="焦虑抑郁量表(HAD)"
["yy13"]="汉密尔顿抑郁量表(HAMD)" ["jlz"]="贝克焦虑测试量表(BAI)"
["jl1"]="焦虑自评量表(SAS)" ["jl2"]="汉密尔顿焦虑量表(HAMA)"
["jl3"]="状态-特质焦虑问卷(STAI)" ["jl5"]="焦虑症筛查量表(GAD-7)"
["jl6"]="考试焦虑量表(TAS)" ["jl8"]="中学生焦虑自评量表"
["qp1"]="耶鲁布朗强迫标准量表(YBOCS)" ["qp3"]="强迫信念问卷(OBQ-44)"
["qp4"]="强迫量表修订版(OCI-R)" ["zk1"]="贝克-拉范森躁狂量表(BRMS)"
["zk2"]="轻躁狂自评量表(HCL-32)" ["zk3"]="杨氏躁狂评定量表(YMRS)"
["sx1"]="心境障碍问卷(MDQ)" ["sx2"]="双相情感障碍自评量表(BSQ)"
["sk3"]="交往焦虑量表(IAS)" ["sk4"]="社交回避及苦恼量表(SAD)"
["sk5"]="社交焦虑量表(SIAS)" ["sk6"]="社交恐惧量表(SPS)"
["iq2"]="瑞文智商测试(60题)" ["iq3"]="智力测试(增强版)"
["eq1"]="情商测试量表(BARON)" ["eq2"]="情绪智力量表(EIS)"
["sm2"]="匹兹堡睡眠质量指数(PSQI)" ["sm1"]="阿森斯失眠量表(AIS)"
["qt1"]="躯体化症状自评量表(SSS)" ["gd6"]="创伤后应激障碍量表(PCL-C)"
["xl13"]="防御方式自评量表(DSQ)" ["gd4"]="家庭环境量表(FES)"
["gd1"]="婚姻质量问卷(ENRICH)" ["gd2"]="爱情态度测试(LAS)" ["gd3"]="心理压力测试(PSTR)"
)
for slug in "${!NAMES[@]}"; do
name="${NAMES[$slug]}"
cat="${CATS[$slug]}"
url="${BASE}/${slug}.html"
echo ">> [$slug] $name ($cat)"
# First question to learn count
resp0=$(curl -sL -X POST "$url" -d 'action=process&index=0' 2>/dev/null)
qcount=$(echo "$resp0" | python3 -c "import json,sys; print(json.load(sys.stdin).get('length',0))" 2>/dev/null || echo 0)
if [ "$qcount" -eq 0 ] 2>/dev/null; then
echo " SKIP (0 questions or error)"
continue
fi
# Build questions+options arrays as JSON
qjson='['
ojson='['
for i in $(seq 0 $((qcount-1))); do
resp=$(curl -sL -X POST "$url" -d "action=process&index=$i" 2>/dev/null)
q=$(echo "$resp" | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(json.dumps(d.get('tm',''), ensure_ascii=False))
" 2>/dev/null)
o=$(echo "$resp" | python3 -c "
import json,sys
d=json.load(sys.stdin)
xx=d.get('xx',[])
print(json.dumps(xx, ensure_ascii=False))
" 2>/dev/null)
if [ $i -gt 0 ]; then
qjson+=','
ojson+=','
fi
qjson+="$q"
ojson+="$o"
if [ $((i % 20)) -eq 19 ]; then
echo -n "."
fi
done
qjson+=']'
ojson+=']'
# Description from HTML (fast, just first 30 lines)
desc=$(curl -sL "$url" 2>/dev/null | head -30 | grep -oP '(?<=<div class="cs">).*?(?=<hr>)' | sed 's/<[^>]*>//g' | head -1 | python3 -c "import sys; print(json.dumps(sys.stdin.read().strip()[:500], ensure_ascii=False))" 2>/dev/null || echo '""')
# Write entry
[ "$first" = true ] && first=false || echo ',' >> "$OUT"
cat >> "$OUT" << ENTRY
{
"slug": "$slug",
"name": $(echo "$name" | python3 -c "import sys; print(json.dumps(sys.stdin.read().strip(), ensure_ascii=False))"),
"category": "$cat",
"url": "$url",
"description": $desc,
"question_count": $qcount,
"questions": $qjson,
"options": $ojson
}
ENTRY
count=$((count+1))
echo " ✓ ($qcount题)"
done
echo ']' >> "$OUT"
echo "Done! $count scales saved to $OUT"
+572 -37
View File
@@ -1,25 +1,584 @@
#!/usr/bin/env python3
"""Minimal HTTP server for digital-psychology serves static files + mindmap persistence."""
import json, os, sys
"""Digital Psychology server — static files + mindmap + scale library API."""
import json, os, sys, sqlite3, time, uuid
from http.server import HTTPServer, SimpleHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
ROOT = os.path.dirname(os.path.abspath(__file__))
MINDMAP_FILE = os.path.join(ROOT, "mindmap_data.json")
SCALES_FILE = os.path.join(ROOT, "scales_data.json")
RESULTS_DB = os.path.join(ROOT, "scale_results.db")
# ---- SQLite setup ----
def get_db():
db = sqlite3.connect(RESULTS_DB)
db.row_factory = sqlite3.Row
return db
def init_db():
db = get_db()
db.execute("""
CREATE TABLE IF NOT EXISTS results (
id TEXT PRIMARY KEY,
scale_slug TEXT NOT NULL,
scale_name TEXT NOT NULL,
answers TEXT NOT NULL,
scores TEXT,
created_at TEXT NOT NULL,
user_agent TEXT,
ip TEXT
)
""")
db.commit()
db.close()
init_db()
# ============================================================
# Scoring engine — known rules for major scales
# ============================================================
SCORE_SCALE = {
# (option_start, reverse_items, transform, rating_thresholds, factor_map)
# option_start: 0=0-indexed (PHQ/GAD), 1=1-indexed (SDS/SAS/SCL-90)
# reverse_items: 1-indexed question numbers to reverse-score
# transform: (multiplier, round) applied to raw score, or None
# rating_thresholds: [(max_score, label), ...]
# factor_map: {factor_name: [1-indexed question numbers]}
}
SCORING = {
# === 抑郁症测试 ===
"yyzcs": { # SDS 抑郁自评量表
"name": "抑郁自评量表(SDS)",
"option_base": 1, # 1-4
"reverse": [2, 5, 6, 11, 12, 14, 16, 17, 18, 20],
"transform": (1.25, 0), # std = raw * 1.25, rounded
"ratings": [
(52, "可能没有抑郁"),
(62, "可能轻度抑郁"),
(72, "可能中度抑郁"),
(100, "可能重度抑郁"),
],
"factors": {
"精神性情感症状": [1, 3],
"躯体性障碍": [2, 4, 5, 6, 7, 8, 9, 10],
"精神运动性障碍": [12, 13],
"抑郁的心理障碍": [11, 14, 15, 16, 17, 18, 19, 20],
},
"use_std": True,
},
"yyz": { # BDI-II 贝克抑郁量表
"name": "贝克抑郁量表(BDI-II)",
"option_base": 0, # 0-3
"reverse": [],
"transform": None,
"ratings": [
(13, "无抑郁或极轻微"),
(19, "轻度抑郁"),
(28, "中度抑郁"),
(63, "重度抑郁"),
],
"factors": {},
"use_std": False,
},
"yy9": { # 中学生抑郁自评量表
"name": "中学生抑郁自评量表",
"option_base": 1,
"reverse": [],
"transform": None,
"ratings": [
(39, "可能没有抑郁"),
(47, "可能轻度抑郁"),
(55, "可能中度抑郁"),
(80, "可能重度抑郁"),
],
"factors": {},
"use_std": False,
},
"yy4": { # CES-D 流调用抑郁量表
"name": "流调用抑郁量表(CES-D)",
"option_base": 0,
"reverse": [4, 8, 12, 16],
"transform": None,
"ratings": [
(15, "无抑郁症状"),
(19, "可能有抑郁倾向"),
(24, "可能有抑郁症状"),
(60, "可能有严重抑郁症状"),
],
"factors": {},
"use_std": False,
},
"yy6": { # PHQ-9
"name": "抑郁症筛查量表(PHQ-9)",
"option_base": 0,
"reverse": [],
"transform": None,
"ratings": [
(4, "无抑郁"),
(9, "轻度抑郁"),
(14, "中度抑郁"),
(19, "中重度抑郁"),
(27, "重度抑郁"),
],
"factors": {},
"use_std": False,
},
"yy7": { # CDI 儿童青少年抑郁量表
"name": "儿童青少年抑郁量表(CDI)",
"option_base": 0,
"reverse": [],
"transform": None,
"ratings": [
(19, "无抑郁"),
(29, "轻度抑郁"),
(39, "中度抑郁"),
(54, "重度抑郁"),
],
"factors": {},
"use_std": False,
},
"yy8": { # DSRSC 儿童抑郁障碍自评
"name": "儿童抑郁障碍自评量表(DSRSC)",
"option_base": 0,
"reverse": [],
"transform": None,
"ratings": [
(13, "正常"),
(17, "可能有抑郁"),
(36, "很可能有抑郁"),
],
"factors": {},
"use_std": False,
},
"yy10": { # GDS 老年抑郁量表
"name": "老年抑郁量表(GDS)",
"option_base": 0,
"reverse": [1, 5, 7, 9, 15, 19, 21, 27, 29, 30],
"transform": None,
"ratings": [
(10, "正常"),
(19, "轻度抑郁"),
(30, "重度抑郁"),
],
"factors": {},
"use_std": False,
},
"yy11": { # EPDS 爱丁堡产后抑郁
"name": "爱丁堡产后抑郁量表(EPDS)",
"option_base": 0,
"reverse": [],
"transform": None,
"ratings": [
(9, "正常"),
(12, "可能存在抑郁"),
(30, "很可能存在抑郁"),
],
"factors": {},
"use_std": False,
},
"yy12": { # HAD 焦虑抑郁量表
"name": "焦虑抑郁量表(HAD)",
"option_base": 0,
"reverse": [],
"transform": None,
"ratings": [
(7, "正常"),
(10, "临界"),
(21, "异常"),
],
"factors": {},
"use_std": False,
},
"yy13": { # HAMD 汉密尔顿抑郁
"name": "汉密尔顿抑郁量表(HAMD)",
"option_base": 0,
"reverse": [],
"transform": None,
"ratings": [
(7, "正常"),
(17, "可能有轻中度抑郁"),
(24, "可能有重度抑郁"),
(72, "可能有极重度抑郁"),
],
"factors": {},
"use_std": False,
},
# === 焦虑症测试 ===
"jl1": { # SAS 焦虑自评量表
"name": "焦虑自评量表(SAS)",
"option_base": 1,
"reverse": [5, 9, 13, 17, 19],
"transform": (1.25, 0),
"ratings": [
(49, "可能没有焦虑"),
(59, "可能轻度焦虑"),
(69, "可能中度焦虑"),
(100, "可能重度焦虑"),
],
"factors": {},
"use_std": True,
},
"jlz": { # BAI 贝克焦虑量表
"name": "贝克焦虑测试量表(BAI)",
"option_base": 0,
"reverse": [],
"transform": None,
"ratings": [
(7, "无焦虑"),
(15, "轻度焦虑"),
(25, "中度焦虑"),
(63, "重度焦虑"),
],
"factors": {},
"use_std": False,
},
"jl5": { # GAD-7
"name": "焦虑症筛查量表(GAD-7)",
"option_base": 0,
"reverse": [],
"transform": None,
"ratings": [
(4, "无焦虑"),
(9, "轻度焦虑"),
(14, "中度焦虑"),
(21, "重度焦虑"),
],
"factors": {},
"use_std": False,
},
"jl6": { # TAS 考试焦虑
"name": "考试焦虑量表(TAS)",
"option_base": 0,
"reverse": [],
"transform": None,
"ratings": [
(12, "较低考试焦虑"),
(20, "中等考试焦虑"),
(37, "较高考试焦虑"),
],
"factors": {},
"use_std": False,
},
"jl8": { # 中学生焦虑自评
"name": "中学生焦虑自评量表",
"option_base": 1,
"reverse": [],
"transform": None,
"ratings": [
(39, "可能没有焦虑"),
(48, "可能轻度焦虑"),
(56, "可能中度焦虑"),
(80, "可能重度焦虑"),
],
"factors": {},
"use_std": False,
},
# === 强迫症测试 ===
"qp1": { # YBOCS
"name": "耶鲁布朗强迫标准量表(YBOCS)",
"option_base": 0,
"reverse": [],
"transform": None,
"ratings": [
(7, "亚临床"),
(15, "轻度"),
(23, "中度"),
(31, "重度"),
(40, "极重度"),
],
"factors": {},
"use_std": False,
},
# === 人格测试 ===
"xl16": { # NEO-FFI 大五人格
"name": "大五人格测试(NEO-FFI)",
"option_base": 1,
"reverse": [], # varies by factor
"transform": None,
"ratings": [], # factor-based
"factors": {
"神经质": [1, 6, 11, 16, 21, 26, 31, 36, 41, 46, 51, 56], # approximate
"外向性": [2, 7, 12, 17, 22, 27, 32, 37, 42, 47, 52, 57],
"开放性": [3, 8, 13, 18, 23, 28, 33, 38, 43, 48, 53, 58],
"宜人性": [4, 9, 14, 19, 24, 29, 34, 39, 44, 49, 54, 59],
"尽责性": [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60],
},
"use_std": False,
},
# === 智商/天赋测试 ===
"duoyuan80": {
"name": "加德纳多元智能测试(MI-80)",
"option_base": 1, # 1-5
"num_options": 5,
"reverse": [],
"transform": None,
"ratings": [], # factor-based, no global rating
"factors": {
"语言智能": [1,2,3,4,5,6,7,8,9,10],
"逻辑数学智能": [11,12,13,14,15,16,17,18,19,20],
"空间智能": [21,22,23,24,25,26,27,28,29,30],
"身体运动智能": [31,32,33,34,35,36,37,38,39,40],
"音乐智能": [41,42,43,44,45,46,47,48,49,50],
"自然观察智能": [51,52,53,54,55,56,57,58,59,60],
"人际智能": [61,62,63,64,65,66,67,68,69,70],
"内省智能": [71,72,73,74,75,76,77,78,79,80],
},
"use_std": False,
},
}
def calculate_score(slug, answers):
"""Calculate scores with known rules, fall back to raw sum."""
scoring = SCORING.get(slug)
num_options = scoring.get("num_options", 4) if scoring else 4
if scoring:
base = scoring["option_base"]
reverse_items = set(scoring["reverse"])
raw = 0
for i, ans in enumerate(answers):
if ans is None:
continue
q_num = i + 1 # 1-indexed question number
opt_idx = int(ans)
if q_num in reverse_items:
# Reverse: last option → first score
raw += base + (num_options - 1 - opt_idx)
else:
raw += base + opt_idx
# Factor scores
factors = {}
if scoring.get("factors"):
for fname, qnums in scoring["factors"].items():
f_raw = 0
f_count = 0
for qn in qnums:
idx = qn - 1
if idx < len(answers) and answers[idx] is not None:
opt_idx = int(answers[idx])
if qn in reverse_items:
f_raw += base + (num_options - 1 - opt_idx)
else:
f_raw += base + opt_idx
f_count += 1
if f_count > 0:
factors[fname] = f_raw
# Transform (e.g. SDS std = raw * 1.25)
std_score = None
if scoring.get("transform"):
mult, rnd = scoring["transform"]
std_score = round(raw * mult) if rnd == 0 else int(raw * mult)
used_score = std_score if (scoring.get("use_std") and std_score is not None) else raw
# Rating
rating = ""
for threshold, label in scoring.get("ratings", []):
if used_score <= threshold:
rating = label
break
if not rating and scoring.get("ratings"):
rating = scoring["ratings"][-1][1]
max_raw = len([a for a in answers if a is not None]) * (base + num_options - 1)
# Max per factor (for front-end bar charts)
factor_max = {}
if scoring.get("factors"):
for fname, qnums in scoring["factors"].items():
factor_max[fname] = len(qnums) * (base + num_options - 1)
# Per-question scores (1-indexed options: 选项A=1分, B=2分, ...)
question_scores = []
for i, ans in enumerate(answers):
if ans is None:
question_scores.append(None)
elif (i + 1) in reverse_items:
question_scores.append(base + (num_options - 1 - int(ans)))
else:
question_scores.append(base + int(ans))
return {
"raw_score": raw,
"std_score": std_score,
"max_score": max_raw,
"rating": rating,
"factors": factors,
"factor_max": factor_max,
"answer_count": len([a for a in answers if a is not None]),
"question_scores": question_scores,
}
else:
# Generic: option index + 1
raw = sum(int(a) + 1 for a in answers if a is not None)
question_scores = [(int(a) + 1) if a is not None else None for a in answers]
return {
"raw_score": raw,
"std_score": None,
"max_score": len([a for a in answers if a is not None]) * num_options,
"rating": "",
"factors": {},
"factor_max": {},
"answer_count": len([a for a in answers if a is not None]),
"question_scores": question_scores,
}
class Handler(SimpleHTTPRequestHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, directory=ROOT, **kwargs)
def end_headers(self):
# no-cache for HTML and JSON endpoints
if self.path == '/' or self.path.endswith('.html') or self.path == '/load':
no_cache_paths = ['/', '/load', '/api/']
if any(self.path == p or self.path.startswith(p) for p in no_cache_paths):
self.send_header("Cache-Control", "no-cache, no-store, must-revalidate")
self.send_header("Pragma", "no-cache")
self.send_header("Expires", "0")
super().end_headers()
def _json(self, data, code=200):
self.send_response(code)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Access-Control-Allow-Origin", "*")
self.end_headers()
self.wfile.write(json.dumps(data, ensure_ascii=False).encode())
def _read_body(self):
length = int(self.headers.get("Content-Length", 0))
return self.rfile.read(length) if length else b""
# ==== API routing ====
def do_GET(self):
if self.path == "/load":
path = urlparse(self.path).path
# Scale API
if path == "/api/scales":
return self.api_scales_list()
if path.startswith("/api/scales/") and not path.startswith("/api/scales/result"):
slug = path.split("/api/scales/")[1]
return self.api_scale_detail(slug)
if path == "/api/scales/results":
return self.api_results_list()
if path.startswith("/api/scales/results/"):
rid = path.split("/api/scales/results/")[1]
return self.api_result_detail(rid)
# Mindmap
if path == "/load":
return self.mindmap_load()
return super().do_GET()
def do_POST(self):
path = urlparse(self.path).path
# Scale result submission
if path == "/api/scales/result":
return self.api_submit_result()
# Mindmap save
if path == "/save":
return self.mindmap_save()
return super().do_POST()
def do_OPTIONS(self):
self.send_response(204)
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
self.send_header("Access-Control-Allow-Headers", "Content-Type")
self.end_headers()
# ---- Scale APIs ----
def api_scales_list(self):
if not os.path.exists(SCALES_FILE):
return self._json({"error": "No scales data"}, 404)
with open(SCALES_FILE, "r", encoding="utf-8") as f:
scales = json.load(f)
# Return lightweight list (no questions)
result = []
for s in scales:
result.append({
"slug": s["slug"], "name": s["name"], "category": s["category"],
"question_count": s["question_count"], "description": s.get("description", ""),
})
self._json(result)
def api_scale_detail(self, slug):
if not os.path.exists(SCALES_FILE):
return self._json({"error": "No scales data"}, 404)
with open(SCALES_FILE, "r", encoding="utf-8") as f:
scales = json.load(f)
for s in scales:
if s["slug"] == slug:
return self._json(s)
self._json({"error": "Scale not found"}, 404)
def api_submit_result(self):
body = self._read_body()
try:
data = json.loads(body)
except json.JSONDecodeError:
return self._json({"error": "Invalid JSON"}, 400)
slug = data.get("scale_slug", "")
scale_name = data.get("scale_name", "")
answers = data.get("answers", []) # list of option indices
if not slug or not answers:
return self._json({"error": "Missing scale_slug or answers"}, 400)
rid = str(uuid.uuid4())[:8]
created = time.strftime("%Y-%m-%d %H:%M:%S")
# Calculate score using known rules
scores = calculate_score(slug, answers)
# Get the scale config for display name
scale_config = SCORING.get(slug, {})
scores["scale_name"] = scale_name
scores["scale_slug"] = slug
db = get_db()
db.execute(
"INSERT INTO results (id, scale_slug, scale_name, answers, scores, created_at, user_agent, ip) VALUES (?,?,?,?,?,?,?,?)",
(rid, slug, scale_name, json.dumps(answers), json.dumps(scores), created,
self.headers.get("User-Agent", ""), self.client_address[0])
)
db.commit()
db.close()
self._json({"id": rid, "scores": scores, "created_at": created})
def api_results_list(self):
db = get_db()
rows = db.execute("SELECT id, scale_slug, scale_name, scores, created_at FROM results ORDER BY created_at DESC LIMIT 50").fetchall()
db.close()
results = []
for r in rows:
results.append({
"id": r["id"], "scale_slug": r["scale_slug"], "scale_name": r["scale_name"],
"scores": json.loads(r["scores"]), "created_at": r["created_at"],
})
self._json(results)
def api_result_detail(self, rid):
db = get_db()
r = db.execute("SELECT * FROM results WHERE id = ?", (rid,)).fetchone()
db.close()
if not r:
return self._json({"error": "Not found"}, 404)
self._json({
"id": r["id"], "scale_slug": r["scale_slug"], "scale_name": r["scale_name"],
"answers": json.loads(r["answers"]), "scores": json.loads(r["scores"]),
"created_at": r["created_at"],
})
# ---- Mindmap (existing) ----
def mindmap_load(self):
if os.path.exists(MINDMAP_FILE):
with open(MINDMAP_FILE, "r", encoding="utf-8") as f:
data = f.read()
@@ -28,17 +587,10 @@ class Handler(SimpleHTTPRequestHandler):
self.end_headers()
self.wfile.write(data.encode())
else:
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(b"{}")
else:
super().do_GET()
self._json({})
def do_POST(self):
if self.path == "/save":
length = int(self.headers.get("Content-Length", 0))
data = self.rfile.read(length)
def mindmap_save(self):
data = self._read_body()
try:
json.loads(data)
except json.JSONDecodeError:
@@ -46,35 +598,18 @@ class Handler(SimpleHTTPRequestHandler):
return
with open(MINDMAP_FILE, "w", encoding="utf-8") as f:
f.write(data.decode())
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(b'{"ok":true}')
elif self.path == "/load":
if os.path.exists(MINDMAP_FILE):
with open(MINDMAP_FILE, "r", encoding="utf-8") as f:
data = f.read()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(data.encode())
else:
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(b"{}")
else:
self.send_error(404)
self._json({"ok": True})
def log_message(self, format, *args):
if "/save" in str(args) or "/load" in str(args):
if any(x in str(args) for x in ["/save", "/load", "/api/"]):
print(f"[{self.log_date_time_string()}] {args[0]}")
if __name__ == "__main__":
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8001
server = HTTPServer(("127.0.0.1", port), Handler)
server.socket.settimeout(30) # 30s timeout so hung clients don't block the single-thread server
print(f"Serving {ROOT} on port {port} (save/load enabled)")
server.socket.settimeout(30)
print(f"愈心谷 server on :{port} (scales API enabled)")
try:
server.serve_forever()
except KeyboardInterrupt:
+492
View File
@@ -0,0 +1,492 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-LVVXH3TL04"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-LVVXH3TL04');
</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
<title>星镜 · 动态视频化性格&星盘测试</title>
<style>
:root{
--pri:#E54D42; --blue:#4A90E2; --green:#5CB85C; --gold:#C8923A;
--text:#333; --sub:#999; --bg-start:#FFD1C7; --bg-end:#FFC8B5;
--purple:#8B5CF6; --teal:#0D9488;
}
*{box-sizing:border-box;margin:0;padding:0}
body{
font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei","Helvetica Neue",sans-serif;
background:linear-gradient(180deg,var(--bg-start) 0%,var(--bg-end) 30%,#fff5f0 100%);
color:var(--text);max-width:430px;margin:0 auto;min-height:100vh;
-webkit-font-smoothing:antialiased;
}
/* Top Bar */
.topbar{
text-align:center;padding:14px 0 10px;
position:sticky;top:0;z-index:10;
background:linear-gradient(180deg,var(--bg-start) 0%,rgba(255,209,199,0.6) 100%);
-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);
}
/* Hero */
.hero{
background:linear-gradient(135deg,#1a1a2e,#16213e,#0f3460);
border-radius:20px;margin:8px 16px 0;padding:32px 20px 28px;
position:relative;overflow:hidden;
}
.hero .stars{position:absolute;inset:0;pointer-events:none}
.hero .stars span{position:absolute;background:#fff;border-radius:50%;animation:twinkle 3s ease-in-out infinite}
.hero .stars span:nth-child(1){width:3px;height:3px;top:15%;left:20%;animation-delay:0s}
.hero .stars span:nth-child(2){width:2px;height:2px;top:25%;right:25%;animation-delay:0.8s}
.hero .stars span:nth-child(3){width:4px;height:4px;top:40%;left:60%;animation-delay:1.5s}
.hero .stars span:nth-child(4){width:2px;height:2px;top:55%;right:15%;animation-delay:0.3s}
.hero .stars span:nth-child(5){width:3px;height:3px;top:30%;left:40%;animation-delay:1.1s}
.hero .stars span:nth-child(6){width:2px;height:2px;bottom:30%;left:12%;animation-delay:2.0s}
.hero .stars span:nth-child(7){width:3px;height:3px;bottom:25%;right:30%;animation-delay:0.5s}
.hero .stars span:nth-child(8){width:2px;height:2px;top:70%;left:75%;animation-delay:1.7s}
.hero .stars span:nth-child(9){width:4px;height:4px;top:10%;left:80%;animation-delay:2.3s}
@keyframes twinkle{0%,100%{opacity:0.3}50%{opacity:1}}
.hero .logo-wrap{position:relative;z-index:2;margin-bottom:8px}
.hero .tagline{font-size:13px;color:rgba(255,255,255,0.7);position:relative;z-index:2}
.hero .big-title{font-size:36px;font-weight:800;color:#fff;letter-spacing:6px;position:relative;z-index:2;margin:12px 0 6px}
.hero .sub-title{font-size:14px;color:rgba(255,255,255,0.85);position:relative;z-index:2;line-height:1.6}
.hero .conf-badge{
display:inline-block;background:rgba(255,255,255,0.12);color:rgba(255,255,255,0.8);
padding:4px 12px;border-radius:20px;font-size:11px;position:relative;z-index:2;margin-top:10px;
}
/* Section */
.section{padding:0 16px;margin-top:20px}
.sec-title{font-size:16px;font-weight:700;color:var(--text);margin-bottom:12px;display:flex;align-items:center;gap:8px}
.sec-title .dot{width:8px;height:8px;border-radius:50%;background:var(--pri);flex-shrink:0}
.sec-subtitle{font-size:12px;color:var(--sub);margin-top:-8px;margin-bottom:12px}
/* Feature Card */
.feat-card{
background:#fff;border-radius:14px;padding:18px 16px;
margin-bottom:10px;box-shadow:0 2px 8px rgba(0,0,0,0.04);
position:relative;overflow:hidden;
}
.feat-card .f-num{
position:absolute;right:14px;top:12px;font-size:40px;font-weight:800;
color:rgba(0,0,0,0.03);line-height:1;
}
.feat-card .f-icon{font-size:20px;margin-bottom:6px}
.feat-card .f-title{font-size:14px;font-weight:700;color:var(--text);margin-bottom:4px}
.feat-card .f-desc{font-size:12px;color:var(--sub);line-height:1.7}
.feat-card .f-highlight{
margin-top:8px;padding:8px 10px;background:#FFF5F0;border-radius:8px;
font-size:11px;color:var(--pri);line-height:1.5;border-left:3px solid var(--pri);
}
/* Biz Tier */
.biz-tier{
background:#fff;border-radius:14px;padding:16px;margin-bottom:10px;
box-shadow:0 2px 8px rgba(0,0,0,0.04);
border-top:4px solid var(--pri);
}
.biz-tier.t2{border-top-color:var(--gold)}
.biz-tier.t3{border-top-color:var(--blue)}
.biz-tier.t4{border-top-color:var(--green)}
.biz-tier .t-label{
display:inline-block;padding:2px 10px;border-radius:10px;font-size:10px;
font-weight:600;margin-bottom:8px;color:#fff;
}
.biz-tier .t-label.l1{background:var(--pri)}
.biz-tier .t-label.l2{background:var(--gold)}
.biz-tier .t-label.l3{background:var(--blue)}
.biz-tier .t-label.l4{background:var(--green)}
.biz-tier .t-title{font-size:14px;font-weight:700;margin-bottom:2px}
.biz-tier .t-price{font-size:12px;color:var(--pri);margin-bottom:8px;font-weight:600}
.biz-tier .t-list{font-size:12px;color:var(--text);line-height:1.8}
.biz-tier .t-list li{margin-left:16px}
/* Compare Table */
.cmp-table{
background:#fff;border-radius:14px;overflow:hidden;
box-shadow:0 2px 8px rgba(0,0,0,0.04);
}
.cmp-table table{width:100%;font-size:12px;border-collapse:collapse}
.cmp-table th{background:#fafafa;padding:12px 8px;font-weight:600;color:var(--text);text-align:center}
.cmp-table th:first-child{text-align:left;padding-left:14px}
.cmp-table td{padding:10px 8px;text-align:center;border-top:1px solid #f5f5f5;line-height:1.5}
.cmp-table td:first-child{text-align:left;padding-left:14px;font-weight:500;color:#555}
.cmp-table .win{color:var(--green);font-weight:600}
.cmp-table .tag-pri{color:var(--pri);font-weight:600}
.cmp-table .tag-gold{color:var(--gold);font-weight:600}
/* MVP */
.mvp-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.mvp-item{
background:#fff;border-radius:12px;padding:14px 12px;
text-align:center;box-shadow:0 2px 6px rgba(0,0,0,0.04);
}
.mvp-item .mvp-num{font-size:28px;font-weight:800;color:var(--pri);opacity:0.2;line-height:1}
.mvp-item .mvp-text{font-size:12px;color:var(--text);margin-top:2px;line-height:1.4}
/* Flow Steps */
.flow-wrap{display:flex;flex-direction:column;gap:0}
.flow-step{
display:flex;align-items:flex-start;gap:12px;padding:0 0 16px 0;
position:relative;
}
.flow-step:not(:last-child)::after{
content:"";position:absolute;left:13px;top:30px;bottom:0;width:2px;
background:linear-gradient(var(--pri),var(--bg-start));
border-radius:1px;
}
.flow-step .step-num{
width:28px;height:28px;border-radius:50%;background:var(--pri);
color:#fff;display:flex;align-items:center;justify-content:center;
font-size:13px;font-weight:700;flex-shrink:0;z-index:1;
}
.flow-step .step-content{font-size:13px;color:var(--text);line-height:1.6}
/* Risk */
.risk-card{
background:#fff;border-radius:14px;padding:16px;margin-bottom:10px;
box-shadow:0 2px 8px rgba(0,0,0,0.04);
}
.risk-card .r-title{font-size:13px;font-weight:700;color:var(--text);margin-bottom:4px}
.risk-card .r-desc{font-size:12px;color:var(--sub);line-height:1.6}
/* New Feature Highlight */
.new-badge{
display:inline-block;background:linear-gradient(135deg,#FF6B6B,#FF8E53);
color:#fff;font-size:9px;padding:2px 6px;border-radius:8px;margin-left:6px;
font-weight:600;vertical-align:middle;
}
/* Back link */
.back-link{
display:block;text-align:center;padding:30px 0 50px;
color:var(--sub);font-size:13px;text-decoration:none;
}
/* Animate */
@keyframes float{0%,100%{transform:translateY(0)}50%{transform:translateY(-8px)}}
.floating{animation:float 3s ease-in-out infinite}
.bottom-spacer{height:60px}
</style>
</head>
<body>
<div class="topbar"><img src="logo.png" alt="愈心谷" style="height:24px;vertical-align:middle"></div>
<!-- Hero -->
<div class="hero">
<div class="stars">
<span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span>
</div>
<div class="logo-wrap"><img src="logo.png" alt="愈心谷" style="height:28px"></div>
<p class="tagline">愈心谷 · 产品设计档案</p>
<div class="big-title">星 镜</div>
<p class="sub-title">动态视频化性格&星盘测试平台<br>让每一次自我探索,都成为可播放的电影</p>
<div class="conf-badge">信心指数 · 91%</div>
</div>
<!-- 产品定位 -->
<div class="section">
<div class="sec-title"><span class="dot"></span>产品定位</div>
<div class="feat-card">
<p style="font-size:13px;color:var(--text);line-height:1.8">
<strong>年轻人自我探索平台</strong>——星盘、性格类型、人格测评,全部支持动态动画与AI生成个人专属短视频报告。<br><br>
对标「测测」以图文静态报告和图文社区为主,<strong>星镜</strong>的核心差异化在于:<span style="color:var(--pri)">动态可视化星盘、AI数字人视频解读、短视频化测试结果、视频社区、真人短视频咨询</span>,把传统文字报告变成可看、可播放、可二次剪辑分享的视频内容。
</p>
</div>
</div>
<!-- 核心痛点 -->
<div class="section">
<div class="sec-title"><span class="dot"></span>测测现存痛点</div>
<div class="feat-card">
<div style="display:flex;flex-wrap:wrap;gap:8px">
<span style="background:#FFECEC;color:var(--pri);padding:6px 12px;border-radius:20px;font-size:12px">全部文字长报告 · 阅读门槛高</span>
<span style="background:#FFF5EC;color:#E8985A;padding:6px 12px;border-radius:20px;font-size:12px">社区仅图文 · 缺少短视频沉浸感</span>
<span style="background:#E6F0FF;color:var(--blue);padding:6px 12px;border-radius:20px;font-size:12px">报告不便于社交传播</span>
<span style="background:#E6FFE8;color:var(--green);padding:6px 12px;border-radius:20px;font-size:12px">解读冰冷 · 缺少情绪氛围感</span>
</div>
</div>
</div>
<!-- 模块一:动态视频报告 -->
<div class="section">
<div class="sec-title"><span class="dot"></span>模块一 <span class="new-badge">王牌</span></div>
<div class="sec-subtitle">测试完成 → 自动生成个人动态视频报告</div>
<div class="feat-card">
<div class="f-num">01</div>
<div class="f-icon">🎬</div>
<div class="f-title">个人专属短视频报告</div>
<div class="f-desc">
用户输入生辰、地点做星盘,或完成性格测试后,输出两套结果:<strong>传统文字报告 + 自动生成专属短视频</strong><br><br>
• 动态星盘动画:行星转动、相位连线、宫位切换特效,星空背景流转<br>
• AI数字人出镜讲解:根据本命盘/测试结果,数字人主播口述性格天赋、情绪短板、感情模式、流年提示<br>
• 免费版:30-45秒短视频(带水印);付费完整版:2-4分钟完整解读<br>
• 一键保存本地,可加字幕和背景音乐,直接分享社交平台
</div>
<div class="f-highlight">对比测测:测测只有图文海报,星镜产出可播放的个性化视频,自带裂变传播属性</div>
</div>
</div>
<!-- 模块二 -->
<div class="section">
<div class="sec-title"><span class="dot"></span>模块二</div>
<div class="sec-subtitle">动态可视化星盘引擎</div>
<div class="feat-card">
<div class="f-num">02</div>
<div class="f-icon">🌌</div>
<div class="f-title">三维动态星盘</div>
<div class="f-desc">
• 可拖拽旋转星空,行星随真实天象实时缓慢运转<br>
• 点击任意行星,弹出动画+语音讲解(非静态图片)<br>
• 行运动态对比:播放动画演示行星移动对本命盘相位变化,直观看懂水逆、木星过境<br>
• 合盘动态动画:两人星盘叠加演示缘分相位
</div>
<div class="f-highlight">降低占星理解门槛,把抽象相位关系变成可视动画</div>
</div>
</div>
<!-- 模块三 -->
<div class="section">
<div class="sec-title"><span class="dot"></span>模块三</div>
<div class="sec-subtitle">短视频化社区(替代测测微博式图文社区)</div>
<div class="feat-card">
<div class="f-num">03</div>
<div class="f-icon">📱</div>
<div class="f-title">短视频优先信息流</div>
<div class="f-desc">
• 首页信息流优先短视频:占星师、心理博主发布测评解读短视频<br>
• 用户可发布个人测试短视频报告到社区,带标签<br>
• 支持评论、合拍、二次剪辑<br>
• 达人供给侧:占星师、心理成长博主入驻,积攒粉丝,承接付费咨询<br>
• 同时保留图文帖子,视频优先、图文为辅
</div>
</div>
</div>
<!-- 模块四 -->
<div class="section">
<div class="sec-title"><span class="dot"></span>模块四</div>
<div class="sec-subtitle">咨询体系升级(视频优先,兼容语音/图文)</div>
<div class="feat-card">
<div class="f-num">04</div>
<div class="f-icon">💬</div>
<div class="f-title">视频 + 异步 + 传统,全矩阵</div>
<div class="f-desc">
<strong>视频一对一咨询</strong>:占星师/心理师视频通话解读<br>
<strong>预约预制短视频解读</strong>:用户提交问题,达人录制专属短视频回复(低压力、产能更高)<br>
• 保留语音、图文咨询,形成完整矩阵
</div>
</div>
</div>
<!-- 模块五 -->
<div class="section">
<div class="sec-title"><span class="dot"></span>模块五</div>
<div class="sec-subtitle">互动式动态测试</div>
<div class="feat-card">
<div class="f-num">05</div>
<div class="f-icon">🎮</div>
<div class="f-title">告别静态答题</div>
<div class="f-desc">
• 部分测评加入<strong>短情景视频片段作为题干</strong>,观看情景后选择反应<br>
• 测试潜意识性格、亲密关系模式<br>
• 答题过程伴随轻量动画反馈,提升沉浸感
</div>
</div>
</div>
<!-- 模块六 -->
<div class="section">
<div class="sec-title"><span class="dot"></span>模块六</div>
<div class="sec-subtitle">AI陪伴动态卡片</div>
<div class="feat-card">
<div class="f-num">06</div>
<div class="f-icon"></div>
<div class="f-title">每日动态短视频运势</div>
<div class="f-desc">
• 每日运势不再是一段静态文字<br>
• AI生成几十秒动态短视频运势卡片,数字人播报今日提示<br>
• 支持桌面小组件播放简易动态星象
</div>
</div>
</div>
<!-- 商业模式 -->
<div class="section">
<div class="sec-title"><span class="dot"></span>商业模式</div>
<div class="sec-subtitle">沿用行业成熟三层变现,叠加视频新增收入</div>
<!-- 第一层 -->
<div class="biz-tier">
<span class="t-label l1">第一层 · 免费流量层</span>
<div class="t-title">拉新钩子</div>
<ul class="t-list">
<li>免费基础排盘、基础性格测试</li>
<li>生成30秒精简版AI数字人短视频报告(带水印,鼓励分享裂变)</li>
<li>免费每日动态短视频运势;基础社区浏览</li>
</ul>
<div class="f-highlight" style="margin-top:10px">核心策略:短视频报告向外分享到抖音/小红书,带来外部回流下载,实现社交裂变——这是对比测测新增的获客通路</div>
</div>
<!-- 第二层 -->
<div class="biz-tier t2">
<span class="t-label l2">第二层 · 会员订阅</span>
<div class="t-title">稳定现金流</div>
<div class="t-price">首月 ¥9.9 | 月 ¥24 | 年 ¥168</div>
<ul class="t-list">
<li>解锁无水印完整 1-4 分钟个人专属视频报告,不限次数</li>
<li>高清三维动态星盘全部功能</li>
<li>无广告,全部深度文字报告解锁</li>
<li>AI数字人多风格切换(理性心理向 / 氛围感占星向)</li>
<li>会员专属短视频课程库</li>
</ul>
</div>
<!-- 第三层 -->
<div class="biz-tier t3">
<span class="t-label l3">第三层 · 单次付费</span>
<div class="t-title">小额转化</div>
<div class="t-price">¥9.9 - ¥49.9</div>
<ul class="t-list">
<li>专项短视频报告:¥19.9「流年事业视频解读」、¥24.9「感情合盘视频报告」</li>
<li>专题心理测评包,输出视频结果</li>
<li>保留传统文字报告,给不爱视频的用户备选</li>
</ul>
</div>
<!-- 第四层 -->
<div class="biz-tier t4">
<span class="t-label l4">第四层 · 高客单达人服务</span>
<div class="t-title">利润大头</div>
<div class="t-price">平台抽成 35%-45%</div>
<ul class="t-list">
<li>视频一对一咨询(主力新形态)、语音、图文咨询</li>
<li>定制录制专属短视频解读:达人录制 5-15 分钟定制视频</li>
<li>达人付费短视频专栏课程(占星、性格成长)</li>
</ul>
</div>
<!-- 新增创新商业化 -->
<div class="feat-card">
<div class="f-title">新增创新商业化(测测不具备)</div>
<div class="f-desc">
<strong>视频报告增值模板商店</strong>:付费购买不同风格视频模板(星空治愈、极简心理风、酷感潮酷风),几块钱单次渲染<br>
<strong>B端对外接口</strong>:把「生辰生成个性化视频报告」能力对外输出,给其他心理、社交应用做能力授权
</div>
</div>
</div>
<!-- 用户体验闭环 -->
<div class="section">
<div class="sec-title"><span class="dot"></span>用户体验完整闭环</div>
<div class="flow-wrap">
<div class="flow-step">
<div class="step-num">1</div>
<div class="step-content">新用户下载 → 输入生辰 / 做性格测试</div>
</div>
<div class="flow-step">
<div class="step-num">2</div>
<div class="step-content">得到结果:<strong>查看文字报告</strong> <strong>播放我的专属视频报告</strong></div>
</div>
<div class="flow-step">
<div class="step-num">3</div>
<div class="step-content">免费版短视频 → 提示开通会员解锁完整版无水印视频</div>
</div>
<div class="flow-step">
<div class="step-num">4</div>
<div class="step-content">用户把短视频分享小红书/抖音 → 带来新用户回流下载</div>
</div>
<div class="flow-step">
<div class="step-num">5</div>
<div class="step-content">应用内看短视频社区内容,种草达人</div>
</div>
<div class="flow-step">
<div class="step-num">6</div>
<div class="step-content">小额单次报告 → 开通会员 → 预约达人视频一对一咨询</div>
</div>
</div>
</div>
<!-- 与测测对比 -->
<div class="section">
<div class="sec-title"><span class="dot"></span>优劣势对比</div>
<div class="cmp-table">
<table>
<tr>
<th></th><th>测测</th><th style="color:var(--pri)">星镜</th>
</tr>
<tr>
<td>报告载体</td>
<td>图文+海报,静态</td>
<td class="win">AI个性化短视频 + 动态三维星盘,保留图文</td>
</tr>
<tr>
<td>社区形态</td>
<td>微博式图文为主</td>
<td class="win">短视频信息流优先,图文为辅,支持用户发布个人测试视频</td>
</tr>
<tr>
<td>对外裂变</td>
<td>只能图片海报传播</td>
<td class="win">短视频自带社交平台传播能力,外部引流更强</td>
</tr>
<tr>
<td>咨询形态</td>
<td>图文、语音连麦为主</td>
<td class="win">视频咨询 + 达人定制短视频回复,同时兼容语音图文</td>
</tr>
<tr>
<td>核心短板</td>
<td>文字阅读负担重,传播弱</td>
<td class="tag-gold">AI视频生成有算力成本;需要做好内容风控</td>
</tr>
</table>
</div>
</div>
<!-- 风险与规避 -->
<div class="section">
<div class="sec-title"><span class="dot"></span>核心风险与规避</div>
<div class="risk-card">
<div class="r-title">内容合规</div>
<div class="r-desc">产品定位为<strong>心理娱乐、自我探索工具</strong>,不做宿命论算命;解读偏向性格分析、行为模式、心理成长,弱化"预测吉凶"表述;明确提示:不替代心理咨询及医院就诊。</div>
</div>
<div class="risk-card">
<div class="r-title">成本控制</div>
<div class="r-desc">大量用AI数字人 + 模板化动画引擎——把星盘/测试数据映射到大模型输出脚本,再驱动数字人生成视频;鼓励达人上传预制短视频内容承接咨询。</div>
</div>
<div class="risk-card">
<div class="r-title">兼容存量用户</div>
<div class="r-desc">不完全砍掉图文模式,给用户开关选择「视频版 / 传统文字版」报告,照顾不喜欢视频的人群。</div>
</div>
</div>
<!-- MVP -->
<div class="section">
<div class="sec-title"><span class="dot"></span>最小可落地版本</div>
<div class="sec-subtitle">优先实现,无需一次性做完全部</div>
<div class="mvp-grid">
<div class="mvp-item"><div class="mvp-num">1</div><div class="mvp-text">基础排盘引擎 + 性格测试</div></div>
<div class="mvp-item"><div class="mvp-num">2</div><div class="mvp-text">30秒AI数字人短视频报告</div></div>
<div class="mvp-item"><div class="mvp-num">3</div><div class="mvp-text">三维动态星盘基础动画</div></div>
<div class="mvp-item"><div class="mvp-num">4</div><div class="mvp-text">短视频信息流社区</div></div>
<div class="mvp-item"><div class="mvp-num">5</div><div class="mvp-text">会员订阅体系</div></div>
<div class="mvp-item"><div class="mvp-num">6</div><div class="mvp-text">真人视频预约咨询</div></div>
</div>
</div>
<div class="bottom-spacer"></div>
<a href="index.html" class="back-link">← 返回愈心谷首页</a>
</body>
</html>
+1537
View File
File diff suppressed because one or more lines are too long
+253 -117
View File
@@ -12,10 +12,11 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
<title>愈心谷 · 心理健康服务</title>
<title>愈心谷</title>
<style>
:root{
--pri:#E54D42; --blue:#4A90E2; --green:#5CB85C;
--purple:#8B5CF6; --orange:#E8985A; --pink:#E0508C;
--text:#333; --sub:#999;
--hero-start:#FF9F80; --hero-end:#FFB8A6;
--bg-start:#FFD1C7; --bg-end:#FFC8B5;
@@ -23,14 +24,14 @@
*{box-sizing:border-box;margin:0;padding:0}
body{
font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei","Helvetica Neue",sans-serif;
background:linear-gradient(180deg,var(--bg-start) 0%,var(--bg-end) 40%,#fff5f0 100%);
background:linear-gradient(180deg,var(--bg-start) 0%,var(--bg-end) 30%,#fff5f0 100%);
color:var(--text);max-width:430px;margin:0 auto;min-height:100vh;
-webkit-font-smoothing:antialiased;
}
/* ── Top Bar ── */
.topbar{
text-align:center;padding:16px 0 10px;
text-align:center;padding:14px 0 8px;
position:sticky;top:0;z-index:10;
background:linear-gradient(180deg,var(--bg-start) 0%,rgba(255,209,199,0.6) 100%);
-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);
@@ -39,84 +40,141 @@ body{
/* ── Hero ── */
.hero{
background:linear-gradient(135deg,var(--hero-start),var(--hero-end));
border-radius:20px;margin:8px 16px 0;padding:28px 20px 36px;
position:relative;overflow:hidden;min-height:160px;
border-radius:20px;margin:6px 16px 0;padding:22px 20px 28px;
position:relative;overflow:hidden;
}
/* white translucent circles instead of emoji hearts */
.hero .dc{position:absolute;border-radius:50%;background:rgba(255,255,255,0.25);z-index:0}
.hero .dc1{width:50px;height:50px;top:10px;right:30px}
.hero .dc2{width:30px;height:30px;top:45px;right:70px}
.hero .dc3{width:20px;height:20px;top:20px;right:95px}
.hero .logo-wrap{position:relative;z-index:2;margin-bottom:10px}
.hero .sub{font-size:14px;color:rgba(255,255,255,0.92);position:relative;z-index:2}
/* hand-heart: CSS hearts instead of emoji */
.hero .logo-wrap{position:relative;z-index:2;margin-bottom:6px}
.hero .sub{font-size:13px;color:rgba(255,255,255,0.92);position:relative;z-index:2}
.hero .hearts-css{position:absolute;right:14px;bottom:16px;z-index:0;opacity:0.45}
.hero .hearts-css .hh{
display:inline-block;width:50px;height:50px;position:relative;
margin:0 4px;
display:inline-block;width:50px;height:50px;position:relative;margin:0 4px;
}
.hero .hearts-css .hh::before,.hero .hearts-css .hh::after{
content:"";position:absolute;width:26px;height:40px;border-radius:26px 26px 0 0;
background:#fff;
content:"";position:absolute;width:26px;height:40px;border-radius:26px 26px 0 0;background:#fff;
}
.hero .hearts-css .hh::before{left:15px;top:0;transform:rotate(-45deg);transform-origin:0 100%}
.hero .hearts-css .hh::after{left:0;top:0;transform:rotate(45deg);transform-origin:100% 100%}
.hero::after{
content:"";position:absolute;bottom:0;left:0;right:0;height:30px;
background:linear-gradient(transparent,rgba(255,209,199,0.3));
/* ── Birthday hint bar ── */
.bday-hint{
margin:12px 16px 0;padding:12px 16px;
background:rgba(255,255,255,0.7);border-radius:12px;
font-size:13px;color:var(--pri);text-align:center;
cursor:pointer;backdrop-filter:blur(4px);
}
/* ── Section ── */
.section{padding:0 16px;margin-top:24px}
.sec-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}
.sec-head .title{font-size:17px;font-weight:600;color:var(--text)}
.sec-head .more{font-size:13px;color:var(--sub);cursor:pointer}
.section{padding:0 16px;margin-top:18px}
.sec-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}
.sec-head .title{font-size:16px;font-weight:600;color:var(--text)}
.sec-head .more{font-size:12px;color:var(--sub);cursor:pointer;text-decoration:none}
/* ── Service Cards ── */
.services{display:grid;grid-template-columns:1fr 1fr;gap:12px}
/* ── Test Icon Grid ── */
.test-grid{
display:grid;grid-template-columns:repeat(4,1fr);gap:10px;
background:#fff;border-radius:16px;padding:18px 14px;
box-shadow:0 2px 10px rgba(0,0,0,0.04);
}
.test-item{
display:flex;flex-direction:column;align-items:center;gap:8px;
cursor:pointer;text-decoration:none;color:var(--text);
transition:transform 0.15s;padding:4px 0;position:relative;
}
.test-item:active{transform:scale(0.93)}
.test-item .icon{
width:52px;height:52px;border-radius:14px;
display:flex;align-items:center;justify-content:center;
font-size:24px;flex-shrink:0;
}
.test-item .label{
font-size:11px;color:#555;text-align:center;line-height:1.3;
max-width:64px;word-break:keep-all;
}
.test-item .badge{
position:absolute;top:-2px;right:8px;
font-size:9px;background:var(--pri);color:#fff;
padding:1px 5px;border-radius:6px;line-height:1.4;
}
/* icon colors */
.icon-pink{background:#FFECEC;color:var(--pri)}
.icon-blue{background:#E6F0FF;color:var(--blue)}
.icon-purple{background:#F0E6FF;color:var(--purple)}
.icon-orange{background:#FFF0E6;color:var(--orange)}
.icon-green{background:#E6FFE8;color:var(--green)}
.icon-rose{background:#FFE6F0;color:var(--pink)}
.icon-teal{background:#E6FFF5;color:#2EA88A}
.icon-gold{background:#FFF8E6;color:#C8923A}
.icon-night{background:#E8E0F0;color:#6B5B95}
/* ── Quick Cards ── */
.quick-cards{display:flex;flex-direction:column;gap:10px}
.quick-card{
display:flex;align-items:center;gap:12px;padding:14px 16px;
background:#fff;border-radius:12px;text-decoration:none;color:var(--text);
box-shadow:0 1px 6px rgba(0,0,0,0.04);transition:transform 0.12s;
position:relative;overflow:hidden;
}
.quick-card:active{transform:scale(0.98)}
.quick-card .qc-icon{
width:44px;height:44px;border-radius:12px;
display:flex;align-items:center;justify-content:center;
font-size:20px;flex-shrink:0;
}
.quick-card .qc-info{flex:1;min-width:0}
.quick-card .qc-info .qc-name{font-size:15px;font-weight:600;color:var(--text)}
.quick-card .qc-info .qc-meta{font-size:11px;color:var(--sub);margin-top:2px}
.quick-card .qc-arrow{font-size:16px;color:#ccc}
.quick-card .qc-tag{
position:absolute;top:0;right:0;
font-size:10px;background:var(--pri);color:#fff;
padding:2px 8px;border-radius:0 10px 0 10px;
}
/* ── Decode Card ── */
.decode-card{
background:#fff;border-radius:16px;padding:20px 16px;
box-shadow:0 2px 10px rgba(0,0,0,0.04);text-align:center;
}
.decode-card .dc-title{font-size:17px;font-weight:700;color:#C8923A;margin-bottom:2px}
.decode-card .dc-sub{font-size:12px;color:var(--sub);margin-bottom:14px}
.decode-card .dc-inputs{display:flex;gap:6px;align-items:center;justify-content:center;flex-wrap:wrap}
.decode-card input{
padding:10px 6px;border:1.5px solid #e8e0d8;border-radius:10px;
font-size:15px;text-align:center;outline:none;background:#fdfaf7;
}
.decode-card input.dc-y{width:62px}.decode-card input.dc-md{width:48px}
.decode-card .dc-sep{font-size:13px;color:var(--sub)}
.decode-card button{
padding:10px 18px;background:var(--pri);color:#fff;border:none;
border-radius:22px;font-size:14px;font-weight:600;cursor:pointer;
box-shadow:0 3px 10px rgba(229,77,66,.3);
}
/* ── Services Grid ── */
.services{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.svc-card{
border-radius:12px;padding:14px 12px;
display:flex;align-items:center;gap:10px;box-shadow:0 2px 6px rgba(0,0,0,0.04);
cursor:pointer;transition:transform 0.15s;
word-break:keep-all;overflow:hidden;
border-radius:12px;padding:14px 12px;display:flex;align-items:center;gap:10px;
box-shadow:0 2px 6px rgba(0,0,0,0.04);cursor:pointer;transition:transform 0.15s;
text-decoration:none;color:var(--text);word-break:keep-all;overflow:hidden;
}
.svc-card:active{transform:scale(0.97)}
/* CSS icons replacing emojis */
.svc-card .sicon{width:40px;height:40px;border-radius:10px;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-weight:700;font-size:16px}
.svc-card .sicon{width:40px;height:40px;border-radius:10px;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:18px;font-weight:700}
.svc-card .sinfo{flex:1;min-width:0}
.svc-card .sinfo .sl{font-size:14px;font-weight:600;color:var(--text);margin-bottom:2px;white-space:nowrap}
.svc-card .sinfo .sd{font-size:11px;color:var(--sub);white-space:nowrap}
.svc-psy{background:#FFECEC}.svc-psy .sicon{background:#FFD4D4;color:var(--pri)}
.svc-edu{background:#FFF8E6}.svc-edu .sicon{background:#FFE8C0;color:#E8985A}
.svc-ai{background:#E6F0FF}.svc-ai .sicon{background:#CDE3FF;color:var(--blue)}
.svc-heal{background:#E6FFE8}.svc-heal .sicon{background:#CDF0D0;color:var(--green)}
.svc-star{background:#F0E6FF}.svc-star .sicon{background:#D8C6F0;color:var(--purple)}
/* ── Cube ── */
.cube-wrap{position:relative;border-radius:16px;overflow:hidden;box-shadow:0 6px 20px rgba(0,0,0,0.1)}
.cube-wrap img{display:block;width:100%}
/* ── Activity Card ── */
.act-card{
background:#fff;border-radius:16px;padding:28px 20px;
position:relative;overflow:hidden;
box-shadow:0 4px 12px rgba(0,0,0,0.06);
}
/* fixed: strokes behind all text, lower opacity */
.act-card .strokes{position:absolute;inset:0;pointer-events:none;opacity:0.08;z-index:0}
.act-card .strokes span{position:absolute;border-radius:3px;transform:rotate(-30deg)}
.act-card .strokes .s1{top:15px;left:-5px;width:160px;height:10px;background:var(--hero-end)}
.act-card .strokes .s2{top:70px;right:-10px;width:120px;height:8px;background:var(--blue)}
.act-card .strokes .s3{bottom:45px;left:20px;width:100px;height:7px;background:#E0D5F5}
.act-card .strokes .s4{bottom:15px;right:0px;width:140px;height:6px;background:var(--green)}
.act-card .main-text{font-size:28px;font-weight:800;color:#222;letter-spacing:4px;margin-bottom:10px;position:relative;z-index:1;line-height:1}
.act-card .side{display:flex;gap:20px;margin-bottom:14px;position:relative;z-index:1}
.act-card .side .st{font-size:12px;color:var(--sub);line-height:1.7;white-space:nowrap}
.act-card .footer{font-size:11px;color:#aaa;border-top:1px solid #f0f0f0;padding-top:12px;position:relative;z-index:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* ── Bottom Card ── */
/* ── Bottom ── */
.bot-card{
background:#fff;border-radius:16px;padding:30px 20px;
background:#fff;border-radius:16px;padding:24px 20px;
margin:16px 16px 80px;position:relative;overflow:hidden;
box-shadow:0 2px 10px rgba(0,0,0,0.05);
}
@@ -124,7 +182,7 @@ body{
.bot-card .blobs span{display:inline-block;border-radius:50%}
.bot-card .blobs .b1{width:100px;height:100px;background:var(--hero-end);opacity:0.15;margin-right:-30px}
.bot-card .blobs .b2{width:60px;height:60px;background:#E0D5F5;opacity:0.2;margin-bottom:-20px}
.bot-card .btext{font-size:20px;color:#666;font-weight:300;position:relative;z-index:1;line-height:1.6}
.bot-card .btext{font-size:18px;color:#666;font-weight:300;position:relative;z-index:1;line-height:1.6}
.bot-card .btext .em{color:var(--pri);font-weight:500}
/* ── Nav ── */
@@ -137,15 +195,18 @@ body{
}
.nav-item{display:flex;flex-direction:column;align-items:center;gap:2px;font-size:10px;color:var(--sub);cursor:pointer;line-height:1}
.nav-item.active{color:var(--pri)}
/* CSS nav icons replacing emojis */
.nav-item .ni{width:20px;height:20px;display:flex;align-items:center;justify-content:center;font-size:16px;line-height:1}
.nav-item.active .ni{color:var(--pri)}
/* ── Divider ── */
.divider{display:flex;align-items:center;gap:12px;padding:0 16px;margin-top:24px}
.divider .line{flex:1;height:1px;background:rgba(0,0,0,0.08)}
.divider .txt{font-size:12px;color:var(--sub);white-space:nowrap}
@media(max-width:380px){
.hero .logo-wrap img{height:28px!important}
.svc-card{padding:10px 8px}
.svc-card .sinfo .sl{font-size:13px}
.act-card .main-text{font-size:22px}
.test-item .icon{width:44px;height:44px;font-size:20px}
.test-item .label{font-size:10px}
.svc-card{padding:10px 8px}.svc-card .sinfo .sl{font-size:13px}
}
</style>
</head>
@@ -156,24 +217,119 @@ body{
<!-- Hero -->
<div class="hero">
<div class="dc dc1"></div><div class="dc dc2"></div><div class="dc dc3"></div>
<div class="logo-wrap"><img src="logo.png" alt="愈心脊" style="height:36px"></div>
<p class="sub">专业的心理健康服务平台</p>
<div class="logo-wrap"><img src="logo.png" alt="愈心脊" style="height:32px"></div>
<p class="sub">愈心谷数字解构</p>
<div class="hearts-css"><span class="hh"></span><span class="hh"></span></div>
</div>
<!-- 愈心解码入口 -->
<!-- Birthday hint — like 测测's "输入生日,更加了解TA" -->
<div class="bday-hint" onclick="location.href='jiema.html'">
输入生日,更加了解自己 →
</div>
<!-- ═══ 认识自己 — 星座 & 人格 ═══ -->
<div class="section">
<div style="background:#fff;border-radius:18px;padding:22px 18px;box-shadow:0 4px 16px rgba(0,0,0,0.06);text-align:center;">
<div style="font-size:18px;font-weight:700;color:#C8923A;margin-bottom:4px;">🔮 愈心解码</div>
<div style="font-size:12px;color:var(--sub);margin-bottom:16px;">输入生日,解锁你的身心密码</div>
<div style="display:flex;gap:6px;align-items:center;justify-content:center;flex-wrap:wrap;">
<input type="number" id="dy" placeholder="1990" maxlength="4" style="width:62px;padding:10px 6px;border:1.5px solid #e8e0d8;border-radius:10px;font-size:15px;text-align:center;outline:none;background:#fdfaf7;">
<span style="font-size:13px;color:var(--sub);"></span>
<input type="number" id="dm" placeholder="1" maxlength="2" style="width:48px;padding:10px 6px;border:1.5px solid #e8e0d8;border-radius:10px;font-size:15px;text-align:center;outline:none;background:#fdfaf7;">
<span style="font-size:13px;color:var(--sub);"></span>
<input type="number" id="dd" placeholder="1" maxlength="2" style="width:48px;padding:10px 6px;border:1.5px solid #e8e0d8;border-radius:10px;font-size:15px;text-align:center;outline:none;background:#fdfaf7;">
<span style="font-size:13px;color:var(--sub);"></span>
<button onclick="goDecode()" style="padding:10px 18px;background:var(--pri);color:#fff;border:none;border-radius:22px;font-size:14px;font-weight:600;cursor:pointer;box-shadow:0 3px 10px rgba(229,77,66,.3);">生成 →</button>
<div class="sec-head">
<span class="title">认识自己</span>
</div>
<div class="test-grid">
<a href="xingjing_app.html" class="test-item">
<div class="icon icon-night"></div>
<div class="label">星座星盘</div>
<span class="badge">AI</span>
</a>
<a href="scale_test.html?slug=mbti" class="test-item">
<div class="icon icon-blue">🧩</div>
<div class="label">MBTI人格</div>
</a>
<a href="scale_test.html?slug=jxrg" class="test-item">
<div class="icon icon-purple"></div>
<div class="label">九型人格</div>
</a>
<a href="scale_test.html?slug=xl16" class="test-item">
<div class="icon icon-teal"></div>
<div class="label">大五人格</div>
</a>
<a href="scale_test.html?slug=gd2" class="test-item">
<div class="icon icon-rose">💕</div>
<div class="label">恋爱能力</div>
</a>
<a href="scale_test.html?slug=eq1" class="test-item">
<div class="icon icon-pink"></div>
<div class="label">情商测试</div>
</a>
<a href="scale_test.html?slug=duoyuan80" class="test-item">
<div class="icon icon-orange">💡</div>
<div class="label">多元智能</div>
</a>
<a href="scale_test.html?slug=gd7" class="test-item">
<div class="icon icon-gold">🧭</div>
<div class="label">职业兴趣</div>
</a>
<a href="scale_test.html?slug=xl6" class="test-item">
<div class="icon icon-blue">16</div>
<div class="label">16PF</div>
</a>
<a href="scale_test.html?slug=xl3" class="test-item">
<div class="icon icon-purple">EP</div>
<div class="label">艾森克</div>
</a>
<a href="scale_test.html?slug=gd3" class="test-item">
<div class="icon icon-green"></div>
<div class="label">压力指数</div>
</a>
<a href="scales.html" class="test-item">
<div class="icon icon-purple" style="background:#f5f5f5;color:#aaa;"></div>
<div class="label">更多</div>
</a>
</div>
</div>
<!-- ═══ 热门推荐 ═══ -->
<div class="section">
<div class="sec-head"><span class="title">热门推荐</span></div>
<div class="quick-cards">
<a href="scale_test.html?slug=mbti" class="quick-card">
<div class="qc-icon icon-blue">🧩</div>
<div class="qc-info">
<div class="qc-name">MBTI 人格测试</div>
<div class="qc-meta">4个维度 · 16种类型 · 看看你是哪一型</div>
</div>
<span class="qc-arrow"></span>
</a>
<a href="xingjing_app.html" class="quick-card">
<div class="qc-icon icon-night"></div>
<div class="qc-info">
<div class="qc-name">星镜 · 动态星盘</div>
<div class="qc-meta">AI视频报告 · 深度星座解读</div>
</div>
<span class="qc-tag"></span>
<span class="qc-arrow"></span>
</a>
<a href="scale_test.html?slug=jxrg" class="quick-card">
<div class="qc-icon icon-purple"></div>
<div class="qc-info">
<div class="qc-name">九型人格测试</div>
<div class="qc-meta">180题完整版 · 深度探索你的内在动机</div>
</div>
<span class="qc-arrow"></span>
</a>
</div>
</div>
<!-- ═══ 愈心解码 ═══ -->
<div class="section">
<div class="decode-card">
<div class="dc-title">🔮 愈心解码</div>
<div class="dc-sub">一个生日,读懂你的身与心</div>
<div class="dc-inputs">
<input type="number" id="dy" placeholder="1990" maxlength="4" class="dc-y">
<span class="dc-sep"></span>
<input type="number" id="dm" placeholder="1" maxlength="2" class="dc-md">
<span class="dc-sep"></span>
<input type="number" id="dd" placeholder="1" maxlength="2" class="dc-md">
<span class="dc-sep"></span>
<button onclick="goDecode()">生成 →</button>
</div>
</div>
</div>
@@ -193,52 +349,32 @@ function goDecode(){
});
</script>
<!-- 疗愈魔方 -->
<div class="section">
<div class="sec-head" style="justify-content:center;flex-direction:column;align-items:center;gap:4px">
<span class="title">愈心魔方</span>
<span style="font-size:12px;color:var(--sub);font-weight:400">愈见魔方,遇见自己</span>
</div>
<div class="cube-wrap">
<img src="cube.jpg" alt="愈心脊疗愈魔方">
</div>
<!-- ═══ 分隔 ═══ -->
<div class="divider">
<span class="line"></span>
<span class="txt">更多服务</span>
<span class="line"></span>
</div>
<!-- 服务卡片(无标题) -->
<div class="section">
<!-- ═══ 专业量表 ═══ -->
<div class="section" style="margin-top:0;">
<div class="services">
<div class="svc-card svc-psy">
<div class="sicon">💬</div>
<div class="sinfo"><div class="sl">心理咨询</div><div class="sd">你的心灵港湾</div></div>
</div>
<div class="svc-card svc-edu">
<a href="scales.html" class="svc-card svc-psy">
<div class="sicon">📋</div>
<div class="sinfo"><div class="sl">心理评估</div><div class="sd">科学评估,即刻开始</div></div>
</div>
<div class="svc-card svc-ai">
<div class="sicon"></div>
<div class="sinfo"><div class="sl">私域活动</div><div class="sd">从第一个好友开始</div></div>
</div>
<div class="svc-card svc-heal">
<div class="sicon"></div>
<div class="sinfo"><div class="sl">健康管理</div><div class="sd">关注今天,即关注未来</div></div>
</div>
</div>
</div>
<!-- 活动中心 -->
<div class="section">
<div class="sec-head"><span class="title">活动中心</span><span class="more">更多 →</span></div>
<div class="act-card">
<div class="strokes">
<span class="s1"></span><span class="s2"></span><span class="s3"></span><span class="s4"></span>
</div>
<div class="main-text">I SEE YOU</div>
<div class="side">
<div class="st">艺术<br>无边界</div>
<div class="st">表达有<br>力量</div>
</div>
<div class="footer">NDI叙事绘画治疗法®九周年大会</div>
<div class="sinfo"><div class="sl">专业量表</div><div class="sd">56个临床量表</div></div>
</a>
<a href="yangsheng.html" class="svc-card svc-heal">
<div class="sicon">☯️</div>
<div class="sinfo"><div class="sl">AI东方养生</div><div class="sd">易经 × 内经</div></div>
</a>
<a href="shuzi.html" class="svc-card svc-ai">
<div class="sicon"></div>
<div class="sinfo"><div class="sl">数字心理学</div><div class="sd">生命数字解构</div></div>
</a>
<a href="mindmap.html" class="svc-card svc-star">
<div class="sicon">🧠</div>
<div class="sinfo"><div class="sl">愈心脑图</div><div class="sd">可视化架构</div></div>
</a>
</div>
</div>
@@ -251,8 +387,8 @@ function goDecode(){
<!-- Nav -->
<div class="nav">
<div class="nav-item active"><span class="ni"></span>首页</div>
<div class="nav-item"><span class="ni"></span>心理咨询</div>
<div class="nav-item"><span class="ni"></span>心理测评</div>
<div class="nav-item"><span class="ni"></span>认识自己</div>
<div class="nav-item"><span class="ni"></span>专业测评</div>
<div class="nav-item"><span class="ni"></span>私域活动</div>
<div class="nav-item"><span class="ni"></span>健康管理</div>
<div class="nav-item"><span class="ni"></span>我的</div>