Repository navigation
Expand file tree
/
Copy pathapp.js
More file actions
57 lines (57 loc) · 42.2 KB
/
Copy pathapp.js
File metadata and controls
57 lines (57 loc) · 42.2 KB
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
'use strict';
// Each option has an independent five-dimensional vector (0..4); null = not measured.
// Scores are theoretical-range normalizations, not population percentiles.
const N=null, names=['注意力占有','社交排他','信息与控制','嫉妒威胁敏感','边界干预'], shortNames=['注意力占有','社交排他','信息控制','威胁敏感','边界干预'];
const o=(text,v,r)=>({text,v,r});
const Q=[
{type:'s',tag:'约会与优先级',text:'你们交往半年,终于有空一起吃晚饭。饭吃到一半,TA 的朋友约你们去附近聚会。TA 问:“吃完一起过去,还是今晚就我们两个?”你原本期待独处,但没有提前说过。你最可能怎么回应?',opts:[o('一起过去,我也想认识一下 TA 的朋友。',[0,N,N,0,0]),o('说今晚更想两个人待着,请 TA 和朋友另约时间。',[4,N,N,1,2]),o('先问都有谁、准备待多久,再决定。',[1,1,3,1,1]),o('再单独待一小时,然后 TA 去聚会,我做自己的事。',[2,N,N,0,1]),o('让 TA 自己选,看看没有明确要求时 TA 会怎么安排。',[3,N,N,2,0])]},
{type:'d',tag:'新的社交连接',level:1,text:'你们关系稳定。TA 最近在兴趣活动中认识了一个新朋友,对方属于 TA 可能产生恋爱吸引的性别。TA 晚饭时随口提起:“今天认识了一个挺聊得来的人。”目前没有其他变化。',feel:['有一点好奇,想知道是什么样的人。','稍微警觉,脑中闪过“会不会发展得很近”。','很自然地替 TA 高兴,没有特别的波动。','心里明显一紧,开始担心自己被比较。','有点酸,但目前还不至于打乱心情。'],opts:[o('顺着聊聊活动和这个新朋友。',[N,0,1,N,0]),o('等 TA 多提几次,再决定要不要聊自己的感受。',[N,1,0,N,0]),o('问他们之后会不会单独约活动。',[N,2,3,N,1]),o('提议提前聊清楚与新朋友相处的边界。',[N,3,1,N,2]),o('希望 TA 以后邀约这个人时,也带上我。',[2,3,2,N,3])]},
{type:'s',tag:'单独吃饭',mirror:'dinner',role:'partner',text:'TA 和一位认识多年的朋友出差到同一座城市,对方属于 TA 可能产生恋爱吸引的性别。他们打算单独吃晚饭,之前没有暧昧记录,TA 主动告诉了你。你更接近哪种安排?',opts:[o('照常吃饭,结束后有空再聊。',[N,0,0,0,0],0),o('先说一下在哪里、预计几点结束,临时变动也说一声。',[N,1,3,1,2],2),o('吃饭可以,但避免安排成特别有约会感的形式。',[N,2,1,2,2],2),o('更希望叫上其他同行的人一起吃。',[N,4,1,3,3],4),o('先了解这次见面对双方意味着什么,再商量。',[N,2,2,2,1],1)]},
{type:'b',tag:'工作与消息',text:'TA 今天在赶项目。你下午发了一件让自己开心的小事,四小时后仍未回复,但你看到 TA 刚在工作群里发了消息。TA 之前说过今天会很忙。',opts:[o('等 TA 忙完再聊,现在不补发。',[0,N,0,1,0]),o('发一句“忙完想听听你的声音”,约个晚点聊天的时间。',[2,N,0,1,1]),o('问是不是看到了我的消息,想确认我没有被漏掉。',[3,N,2,2,1]),o('把原本想分享的内容收回来,今晚先做自己的事。',[3,N,0,3,0]),o('告诉 TA 忙也希望有个简短回应,以后约定一个方式。',[4,N,1,2,3])]},
{type:'s',tag:'行程与定位',mirror:'location',role:'partner',text:'TA 周末和朋友去徒步,出发前告诉了你路线。你发现平时开启的共享定位现在关了。TA 说想省电,山里信号也不好。你觉得这次适合怎么安排?',opts:[o('到目的地和回程各发一条消息就够了。',[N,N,1,0,1],1),o('不用额外安排,方便时联系。',[N,N,0,0,0],0),o('出于安全考虑,尽量保持定位,没电前提醒一下。',[N,N,4,1,3],4),o('问清关定位的时间和原因,再决定是否需要保持开启。',[N,N,3,3,2],3),o('约定几个联系时间,定位由 TA 自己决定。',[1,N,2,1,2],2)]},
{type:'m',tag:'数字隐私',text:'你们刚开始尝试同居,在家看电影,TA 的手机连续弹出消息。TA 看了一眼,把手机反扣,说是朋友群,随后继续看电影。你会出现哪些反应?',opts:[o('注意到动作变化,之后会留意类似情况是否重复。',[N,N,1,2,0]),o('顺口问是什么群、大家在聊什么。',[N,N,2,1,1]),o('希望 TA 简单展示一下,免得我自己乱想。',[N,N,4,3,3]),o('提出看电影时两个人都把手机收起来。',[2,N,0,0,2])]},
{type:'d',tag:'频繁聊天',level:2,text:'过了一阵子,TA 和兴趣活动中新认识的那位朋友几乎每天都会聊天。内容多是活动、照片和日常趣事。TA 仍和你照常约会,也没有刻意隐藏聊天。',feel:['有一点在意,但还想看看这个频率会不会自然下降。','开始觉得这段联系占据了一个特别的位置。','没有明显不适,多了朋友很正常。','明显焦躁,反复想象他们会不会越走越近。','有点吃醋,希望自己也被这样主动分享。'],opts:[o('问问他们聊的共同兴趣,我也参与一下话题。',[1,0,1,N,0]),o('先观察我们的相处质量有没有变化。',[1,1,0,N,0]),o('告诉 TA 我有点被分走注意力的感觉,约一段专心相处的时间。',[3,1,0,N,1]),o('商量每天聊天的大致时间,避免影响两个人的相处。',[3,2,2,N,2]),o('希望减少与这个人的日常聊天,把联系集中在兴趣活动。',[2,4,2,N,4])]},
{type:'s',tag:'自己的单独吃饭',mirror:'dinner',role:'self',text:'换个角度:你和一位认识多年的朋友出差到同一座城市,对方属于你可能产生恋爱吸引的性别。你们打算单独吃晚饭,之前没有暧昧记录,你主动告诉了伴侣。你认为自己适合怎么安排?',opts:[o('更适合叫上其他同行的人一起吃。',[N,4,1,N,3],4),o('照常吃饭,结束后有空再聊。',[N,0,0,N,0],0),o('先说在哪里、预计几点结束,临时变动也说一声。',[N,1,3,N,2],2),o('先聊这次见面对我意味着什么,再商量。',[N,2,2,N,1],1),o('吃饭可以,但避免安排成特别有约会感的形式。',[N,2,1,N,2],2)]},
{type:'d',tag:'前任重新联系',text:'TA 的前任突然发消息,说在整理东西时找到了以前借走的一本书,想归还。TA 主动告诉你,打算在咖啡店见十分钟取书。你们事先没有讨论过前任联系的边界。',feel:['有一点别扭,但这次理由能理解。','担心这会成为重新联系的入口。','感觉这是一件收尾的小事。','明显不安,开始回想 TA 是否还放不下。','会吃醋,尤其在意 TA 对见面的期待。'],opts:[o('问一下 TA 现在如何看待这段旧关系。',[N,2,2,N,1]),o('建议用快递或托朋友转交,更省事。',[N,3,1,N,3]),o('让 TA 自己处理,回来想聊时再聊。',[N,0,0,N,0]),o('协商只处理归还物品,不顺势续聊往事。',[N,3,1,N,2]),o('问能否一起去,取完书我们再去别处。',[1,4,2,N,3])]},
{type:'s',tag:'向朋友倾诉',mirror:'comfort',role:'partner',text:'TA 最近工作受挫,向一位可能产生恋爱吸引的朋友倾诉了一小时,说对方有类似经历,给了很实用的建议。TA 也跟你聊过这件事,但你没有相关经验。你更接受哪种相处方式?',opts:[o('可以继续找对方聊,我也用自己擅长的方式支持 TA。',[0,0,0,0,0],0),o('聊建议可以,最脆弱的感受更希望优先留在我们之间。',[4,3,0,2,2],3),o('想了解这段支持关系的分量,再一起确定界限。',[2,2,2,2,1],1),o('如果聊天经常很深入,希望也拓展其他支持来源。',[1,3,1,2,3],3),o('可以聊,但避开深夜和涉及我们关系的私人细节。',[2,3,1,2,3],3)]},
{type:'b',tag:'兴趣占用时间',text:'TA 最近迷上一款游戏,连续三晚把原本可聊天的时间用来和队友组队。你们没有正式约定这几晚要一起做什么,但你觉得交流变少了。',opts:[o('约定每周固定两晚留给我们,其他时间各自安排。',[3,N,1,1,2]),o('先做自己的事,等 TA 这阵兴致过去。',[0,N,0,1,0]),o('加入一局或了解游戏,寻找可以一起做的部分。',[2,N,0,0,0]),o('说自己有点失落,希望这周先减少组队次数。',[4,N,1,2,3]),o('问游戏结束的时间,想每天至少有一段稳定的交流。',[3,N,2,1,2])]},
{type:'s',tag:'自己的行程与定位',mirror:'location',role:'self',text:'换个角度:你周末和朋友去徒步,出发前告诉了伴侣路线。你想关闭平时开启的共享定位,因为要省电,山里信号也不好。你认为自己适合怎么安排?',opts:[o('约定几个联系时间,定位由我自己决定。',[1,N,2,N,2],2),o('出于安全考虑,尽量保持定位,没电前提醒一下。',[N,N,4,N,3],4),o('不用额外安排,方便时联系。',[N,N,0,N,0],0),o('到目的地和回程各发一条消息就够了。',[N,N,1,N,1],1),o('先解释关闭时间和原因,再商量是否需要保持开启。',[N,N,3,N,2],3)]},
{type:'m',tag:'前任照片与生日',text:'TA 在找旅行照片时翻到几张和前任的旧合照,没有专门收藏,也没删除。同一天,共同朋友圈有人提醒前任生日,TA 说可能在群里跟大家一起发一句祝福。你会有哪些真实反应?',opts:[o('对照片有点介意,想知道保留它们对 TA 的意义。',[N,1,2,2,1]),o('群里祝福可以,但不太希望另开私聊。',[N,3,1,2,3]),o('希望把旧合照单独归档,避免经常在我们面前出现。',[1,2,1,1,2]),o('会留意 TA 是否借生日继续谈以前的感情。',[N,1,1,3,0])]},
{type:'s',tag:'聚会拥抱',mirror:'hug',role:'partner',text:'在朋友聚会上,TA 和一位很久没见的朋友重逢。对方属于 TA 可能产生恋爱吸引的性别。他们开心地拥抱了一下,附近其他朋友也这样打招呼。你更希望今后怎样处理?',opts:[o('这种短暂的社交拥抱可以照常。',[N,0,N,0,0],0),o('会有点酸,但不用为此改打招呼方式。',[N,1,N,2,0],0),o('希望改成握手或挥手,减少身体接触。',[N,4,N,3,3],4),o('聊清哪些接触是社交、哪些更亲密,再共同约定。',[N,2,1,1,2],2),o('普通拥抱可以,避免长时间贴近或单独亲昵。',[N,3,N,2,2],2)]},
{type:'d',tag:'公开夸奖与比较',text:'你们参加聚会,有人当众夸 TA 很有魅力,还半开玩笑说“要是早认识你就好了”。TA 笑着说谢谢,并介绍你是自己的伴侣。对方在你很在意的一项能力上也表现出色。',feel:['稍微被比较的感觉,但介绍让我安心了一些。','仍然有点警惕,想知道 TA 是否也被对方吸引。','觉得被夸很自然,心情没有明显变化。','心里强烈不舒服,之后很难融入聊天。','有些吃醋,也会不由自主地比较自己。'],opts:[o('自然加入聊天,了解一下这个人。',[N,0,1,N,0]),o('靠近 TA 一点,希望两个人一起参与聚会。',[2,1,0,N,1]),o('结束后告诉 TA 刚才让我有些不舒服。',[N,2,0,N,1]),o('希望 TA 今后遇到类似玩笑时回应得更明确。',[N,3,1,N,2]),o('之后希望 TA 减少和对方单独接触。',[N,4,1,N,4])]},
{type:'d',tag:'特别懂我的人',level:3,text:'TA 和兴趣活动中的那位新朋友已经熟悉了几个月。TA 最近说:“有些事 TA 真的很懂我,解释一句就明白。”你们仍照常相处,但你发现 TA 提起这个人的次数明显增加。',feel:['有点失落,想知道有哪些部分我还没理解。','担心自己在 TA 心里的特别位置正在变小。','不觉得不同的人理解不同部分会有冲突。','明显慌张,会反复比较自己和这个人。','有些嫉妒,希望 TA 也看到我们之间的默契。'],opts:[o('问问具体哪些事被理解,试着更了解 TA。',[2,0,1,N,0]),o('暂时不改变他们的联系,先看我们的亲密是否受影响。',[1,1,0,N,0]),o('表达自己有被替代的感觉,约时间认真聊。',[3,2,1,N,1]),o('协商私人倾诉的范围,以及我们之间需要保留的空间。',[3,3,2,N,2]),o('希望 TA 少向这个人倾诉特别私人的感受。',[3,4,2,N,4])]},
{type:'s',tag:'自己的倾诉对象',mirror:'comfort',role:'self',text:'换个角度:你工作受挫,向一位可能产生恋爱吸引的朋友倾诉了一小时。对方有类似经历,给了很实用的建议。你也跟伴侣聊过,但伴侣没有相关经验。你认为自己适合怎样继续?',opts:[o('可以聊,但避开深夜和涉及伴侣关系的私人细节。',[2,3,1,N,3],3),o('如果聊天经常很深入,也拓展其他支持来源。',[1,3,1,N,3],3),o('可以继续找对方聊,也接受伴侣用其他方式支持我。',[0,0,0,N,0],0),o('想解释这段支持关系的分量,再一起确定界限。',[2,2,2,N,1],1),o('聊建议可以,最脆弱的感受优先留在伴侣之间。',[4,3,0,N,2],3)]},
{type:'b',tag:'异地旅行',text:'TA 独自旅行一周,说想减少看手机。第一天发了几张照片,第二天只发一句“今天很累,明天聊”。你们之前没有约定每天联系多久。你最可能和最不可能怎么做?',opts:[o('商量每天一个简短联系时段,其余时间自由安排。',[3,N,2,1,2]),o('等 TA 想分享时再聊,我安排好自己的这一周。',[0,N,0,0,0]),o('希望至少知道每天去哪里、和谁一起活动。',[2,1,4,2,3]),o('告诉 TA 我想念 TA,希望隔天通一次电话。',[2,N,1,1,1]),o('追问今天为什么这么累、发生了什么,确认没有别的变化。',[2,1,3,3,2])]},
{type:'d',tag:'被搭讪与联系方式',text:'TA 在活动上被一个陌生人搭讪。TA 说自己有伴侣,但对方说也想交流共同兴趣,提出加社交账号。TA 加了,并在回家后主动跟你提起。',feel:['有点在意,但想先了解后来聊了什么。','觉得“有伴侣”之后还加联系方式,界限有些模糊。','只交换账号还没有让我明显不舒服。','明显紧张,担心对方会持续追求。','会吃醋,对 TA 愿意保留联系感到别扭。'],opts:[o('先了解 TA 为什么觉得这段联系值得保留。',[N,1,2,N,1]),o('希望 TA 后续一旦出现暧昧就明确结束话题。',[N,2,1,N,2]),o('建议这类兴趣交流尽量放在公开群聊里。',[N,3,1,N,3]),o('让 TA 自己判断,出现新情况再聊。',[N,0,0,N,0]),o('希望 TA 删除这次加的账号,避免持续接触。',[N,4,2,N,4])]},
{type:'b',tag:'密码变化',text:'TA 最近更换了手机密码,说旧密码曾在朋友面前输入过。你们过去偶尔会用对方手机查地图,但没有约定共享密码。TA 愿意帮你打开地图,却没有主动告诉你新密码。',opts:[o('需要用时让 TA 打开就好。',[N,N,0,0,0]),o('问以后借手机怎么方便些,找一个双方能接受的方式。',[N,N,1,1,1]),o('问为什么这次不告诉我,想了解这是不是关系变化。',[N,N,3,3,1]),o('希望继续互相知道密码,觉得这样更安心。',[N,N,4,2,3]),o('先不问,但之后会留意 TA 对手机的态度。',[N,N,1,3,0])]},
{type:'m',tag:'社交媒体与夸奖',text:'你们刚确认恋爱关系一个月。TA 这周多次给同一位朋友的动态点赞,还当着你的面说:“TA 做事真的很厉害,我很欣赏。”那个人与你年龄相近,也有你想提升的能力。没有其他暧昧信息。你会有哪些反应?',opts:[o('把自己和对方比较,想知道 TA 欣赏的具体是什么。',[N,N,1,3,0]),o('问 TA 最近是不是和对方交流比较多。',[N,1,2,2,1]),o('希望 TA 也更具体地表达对我的欣赏。',[3,N,0,2,1]),o('商量公开互动哪些形式容易让彼此误会。',[N,2,1,1,2])]},
{type:'s',tag:'自己的聚会拥抱',mirror:'hug',role:'self',text:'换个角度:你在朋友聚会上遇见一位很久没见的朋友,对方属于你可能产生恋爱吸引的性别。你们开心地拥抱了一下,附近其他朋友也这样打招呼。你认为自己今后适合怎样处理?',opts:[o('聊清哪些接触是社交、哪些更亲密,再共同约定。',[N,2,1,N,2],2),o('普通拥抱可以,避免长时间贴近或单独亲昵。',[N,3,N,N,2],2),o('这种短暂的社交拥抱可以照常。',[N,0,N,N,0],0),o('即使伴侣有点酸,也可以保留这种打招呼方式。',[N,1,N,N,0],0),o('改成握手或挥手,减少身体接触。',[N,4,N,N,3],4)]},
{type:'s',tag:'稳定相处与兴趣',text:'又过了几周,TA 的兴趣活动每周占三晚,你们最近聊天变少,但周末约会照常。TA 问你下个月是否继续这样安排。你最可能怎么回应?',opts:[o('希望减少到一两晚,给关系留更多时间。',[4,N,1,1,3]),o('保留兴趣安排,但固定两晚认真相处。',[3,N,1,1,2]),o('先照常安排,我也有想投入的事情。',[0,N,0,0,0]),o('问每晚什么时候结束,想每天有稳定的交流。',[3,N,2,1,2]),o('试着一起参与一部分,看看能不能多些共同体验。',[2,N,0,0,0])]},
{type:'d',tag:'喝醉与照顾',text:'TA 参加没有你的朋友聚会,喝得有些醉。一位可能产生恋爱吸引的朋友扶着 TA 上车,又陪车送 TA 回家。TA 第二天主动告诉你,朋友也在群里说了这件事。',feel:['有些不自在,但有人安全送回让我松了口气。','担心醉酒时的身体接触和独处难以把握。','主要是庆幸 TA 平安回家。','明显不安,想反复确认路上有没有其他事情。','会吃醋,觉得自己错过了一个亲近的时刻。'],opts:[o('问身体好些没有,也谢谢朋友的照顾。',[N,0,0,N,0]),o('了解路上发生了什么,确认 TA 的记忆和安全。',[N,1,2,N,1]),o('协商以后喝多时优先联系我或固定的接送方式。',[2,2,2,N,2]),o('希望以后由多人陪同,减少醉酒时单独相处。',[N,3,2,N,3]),o('希望 TA 以后在这类聚会控制饮酒并提早回来。',[1,3,2,N,4])]},
{type:'d',tag:'凌晨的私人聊天',level:4,text:'某晚凌晨两点,你醒来发现 TA 还在和兴趣活动中的那位朋友聊天。TA 说对方心情不好,正在谈私人感受。最近 TA 已几次因为和这个人聊到很晚,缩短了你们睡前相处的时间。',feel:['有点难受,更想弄清这是偶发还是常态。','担心这段联系已经挤占了我们重要的亲密空间。','暂时没有明显不安,想等白天再了解。','非常不安,觉得自己的关系位置受到明显威胁。','强烈吃醋,也觉得自己的需要被放在后面。'],opts:[o('先休息,第二天了解情况再谈。',[1,1,1,N,0]),o('现在告诉 TA 我难受,希望今晚先结束聊天。',[3,2,1,N,2]),o('第二天协商深夜倾诉的边界和我们的睡前时间。',[3,3,2,N,2]),o('希望减少与这个人的私人倾诉,把支持分散给其他朋友。',[3,4,2,N,3]),o('希望停止这种一对一深夜聊天,并约定之后如何处理。',[4,4,3,N,4])]},
{type:'m',tag:'旅行住宿边界',text:'TA 和几位朋友外出旅行,原订住宿临时取消。剩下一间多人套房,有独立床位但共用客厅,其中有可能产生恋爱吸引的朋友。换酒店需要更远路程和额外费用。TA 打电话与你商量。你会有哪些反应?',opts:[o('了解床位、房门和同行人员,确认实际安排。',[N,1,3,1,1]),o('觉得同住不太自在,更希望多花一些钱换住处。',[N,4,1,3,3]),o('商量睡前简单联系一下,帮助我安心。',[2,N,2,1,2]),o('提出大家明确私人空间和身体接触的边界。',[N,2,1,1,2])]},
{type:'b',tag:'临时取消与修复',text:'TA 因为临时加班取消了你期待很久的约会,说自己也很失望,提出下周补上。这是这个月第二次,你知道工作压力确实很大。',opts:[o('先接受补约,等忙季结束再一起安排时间。',[0,N,0,1,0]),o('告诉 TA 我理解原因,但连续取消让我觉得不被优先考虑。',[3,N,0,2,1]),o('希望这周留一小段通话时间,并确定补约日期。',[3,N,1,1,2]),o('提议以后约会被取消时,当天就一起确认替代安排。',[4,N,2,1,3]),o('暂时不安排新的约会,等 TA 主动拿出可行的时间。',[3,N,0,3,0])]},
{type:'s',tag:'边界意见不同',text:'你们认真讨论一次让你不舒服的社交事件。TA 说:“我能理解你难受,但我不想放弃这个朋友。我们能不能换一种办法?”没有发现欺骗,你们对于亲密友谊的界限确实不同。你最可能如何继续?',opts:[o('把具体让自己介意的行为说清,寻找双方都能实行的安排。',[N,2,1,1,2]),o('先暂停争论,分别想想哪些是需要、哪些可以调整。',[N,1,0,1,0]),o('希望先按我的底线减少联系,再看之后如何恢复。',[N,4,2,3,4]),o('允许继续来往,但需要更清楚的时间和相处信息。',[N,2,4,2,3]),o('说明这可能是相处方式的差异,再评估彼此能否接受。',[N,3,0,2,1])]}
];
let answers=Array(Q.length).fill(null),index=0,started=false,reportText='';
const app=document.getElementById('app'), letters='ABCDE';
function introduction(){app.innerHTML=`<section class="intro"><div><div class="eyebrow">28 个情景 · 一份关于你的关系画像</div><h1>恋爱关系中的<br>占有欲五维情景测试</h1><div class="intro-text"><p>这是一个关于恋爱关系中占有欲与边界反应的情景测试。共 28 题,大约需要 15–20 分钟。</p><p class="muted">没有正确答案。请选择你真实最可能出现的反应,而不是你认为“最成熟”的答案。</p><p class="muted">测试过程中我不会解释每个选项代表什么,全部完成后再给你完整分析。</p></div><div class="facts"><span class="pill">每次一道题</span><span class="pill">四种回答方式</span><span class="pill">完成后查看报告</span></div><button class="button" id="start">开始测试</button></div><div class="art" aria-hidden="true"><strong>28</strong></div></section>`;document.getElementById('start').onclick=()=>{started=true;renderQuestion()}}
function choiceHTML(opts,group,selected,multi=false){return `<div class="options">${opts.map((op,i)=>`<label class="option"><input type="${multi?'checkbox':'radio'}" name="${group}" value="${i}" ${(multi?selected?.includes(i):selected===i)?'checked':''}><span class="letter">${letters[i]}</span><span>${typeof op==='string'?op:op.text}</span></label>`).join('')}</div>`}
function renderQuestion(){const q=Q[index],a=answers[index];let controls='',hint='请选择一个最接近你真实反应的选项。';if(q.type==='s')controls=choiceHTML(q.opts,'single',a?.choice);if(q.type==='m'){hint='请选择所有符合你真实反应的选项(0–4 项);一项都不符合时可直接继续。';controls=choiceHTML(q.opts,'multi',a?.choices,true)}if(q.type==='b'){hint='分别选择最可能和最不可能的反应,两项不能相同。';controls=`<fieldset class="dual-block"><legend>你最可能怎么做?</legend>${choiceHTML(q.opts,'best',a?.best)}</fieldset><fieldset class="dual-block"><legend>你最不可能怎么做?</legend>${choiceHTML(q.opts,'worst',a?.worst)}</fieldset>`}if(q.type==='d'){hint='第一瞬间的感受与实际行动可以不同,请分别选择。';controls=`<fieldset class="dual-block"><legend>第一瞬间,你的感受最接近什么?</legend>${choiceHTML(q.feel,'feel',a?.feel)}</fieldset><fieldset class="dual-block"><legend>但实际上,你最可能怎么做?</legend>${choiceHTML(q.opts,'action',a?.action)}</fieldset>`}app.innerHTML=`<section class="question"><div class="progress-meta"><span>第 ${index+1} 题 / 28</span><span>${Math.round(index/28*100)}% 已完成</span></div><div class="progress"><div style="width:${index/28*100}%"></div></div><h1 style="font-size:24px">第 ${index+1} 题</h1><p class="scenario">${q.text}</p><p class="instruction">${hint}</p><form id="qform">${controls}<div class="error" id="error" aria-live="polite"></div><div class="actions"><button type="button" class="button secondary" id="back" ${index===0?'disabled':''}>上一题</button><span class="compact">按自己的实际反应选择</span><button type="submit" class="button">${index===27?'生成我的报告':'下一题'}</button></div></form></section>`;document.getElementById('back').onclick=()=>{saveCurrent(false);index--;renderQuestion();window.scrollTo(0,0)};document.getElementById('qform').onsubmit=e=>{e.preventDefault();if(!saveCurrent(true))return;if(index===27)renderReport();else{index++;renderQuestion();window.scrollTo(0,0)}}}
function saveCurrent(validate){const q=Q[index],form=document.getElementById('qform'),get=n=>{const e=form.querySelector(`input[name="${n}"]:checked`);return e?Number(e.value):null};let a;if(q.type==='s')a={choice:get('single')};if(q.type==='m')a={choices:[...form.querySelectorAll('input[name="multi"]:checked')].map(e=>Number(e.value))};if(q.type==='b')a={best:get('best'),worst:get('worst')};if(q.type==='d')a={feel:get('feel'),action:get('action')};const incomplete=Object.values(a).some(v=>v===null),same=q.type==='b'&&a.best!==null&&a.best===a.worst;if(validate&&(incomplete||same)){document.getElementById('error').textContent=same?'最可能与最不可能,请选择不同的选项。':'请完成本题的选择后继续。';return false}answers[index]=a;return true}
const feelValues=[1,3,0,4,2];
function vector(q,a){const measured=d=>q.type==='d'&&d===3||q.opts.some(op=>op.v[d]!==N),v=op=>names.map((_,d)=>measured(d)?op.v[d]??0:N);if(q.type==='s')return v(q.opts[a.choice]);if(q.type==='d'){const result=v(q.opts[a.action]);result[3]=feelValues[a.feel];return result}if(q.type==='m')return names.map((_,d)=>measured(d)?q.opts.reduce((sum,op,i)=>sum+(a.choices.includes(i)?op.v[d]||0:0),0)/q.opts.length:N);return names.map((_,d)=>measured(d)?.75*(q.opts[a.best].v[d]??0)+.25*(4-(q.opts[a.worst].v[d]??0)):N)}
function legalAnswers(q){if(q.type==='s')return q.opts.map((_,choice)=>({choice}));if(q.type==='d')return q.feel.flatMap((_,feel)=>q.opts.map((_,action)=>({feel,action})));if(q.type==='b')return q.opts.flatMap((_,best)=>q.opts.map((_,worst)=>({best,worst})).filter(a=>a.best!==a.worst));return Array.from({length:1<<q.opts.length},(_,mask)=>({choices:q.opts.map((_,i)=>i).filter(i=>mask&(1<<i))}))}
const bounds=Q.map(q=>{const vs=legalAnswers(q).map(a=>vector(q,a));return names.map((_,d)=>vs[0][d]===N?N:{min:Math.min(...vs.map(v=>v[d])),max:Math.max(...vs.map(v=>v[d]))})});
function calculate(as){if(as.length!==28||as.some(a=>a===null))throw Error('Incomplete questionnaire');const vectors=Q.map((q,i)=>vector(q,as[i]));const scores=names.map((_,d)=>{let sum=0,low=0,high=0;vectors.forEach((v,i)=>{if(v[d]!==N){const w=Q[i].type==='d'&&d===3?1.4:Q[i].mirror&&Q[i].role==='self'?.65:1;sum+=v[d]*w;low+=bounds[i][d].min*w;high+=bounds[i][d].max*w}});return Math.round((sum-low)/(high-low)*100)});const pairs=['dinner','location','comfort','hug'].map(key=>{const p=Q.findIndex(q=>q.mirror===key&&q.role==='partner'),s=Q.findIndex(q=>q.mirror===key&&q.role==='self');return {key,p,s,partner:Q[p].opts[as[p].choice].r,self:Q[s].opts[as[s].choice].r}});const symmetry=Math.round(100-pairs.reduce((n,p)=>n+Math.abs(p.partner-p.self),0)/16*100);const escalation=Q.map((q,i)=>q.level?{level:q.level,i,emotion:vectors[i][3]*25,behavior:vectors[i][4]*25}:null).filter(Boolean);const first=escalation.find(s=>s.emotion>=50||s.behavior>=50);const threshold=first?['低阈值','中低阈值','中等阈值','中高阈值'][first.level-1]:'高阈值';const gap=scores[3]-scores[4];const consistencyDiffs=[Math.abs(vectors[10][0]-vectors[22][0])/4,Math.abs(vectors[5][2]-vectors[19][2])/4,pairs.reduce((n,p)=>n+Math.abs(p.partner-p.self),0)/16,escalation.slice(1).reduce((n,s,i)=>n+Math.max(0,escalation[i].emotion-s.emotion)/100,0)/3];const ci=consistencyDiffs.reduce((n,v)=>n+v,0)/4;const consistency=ci<.24?'较高':ci<.48?'中等':'较低';const composite=Math.round(scores.reduce((n,s)=>n+s,0)/5);return {scores,composite,vectors,pairs,symmetry,escalation,threshold,gap,consistency}}
function selectedText(q,a){if(q.type==='s')return q.opts[a.choice].text;if(q.type==='m')return a.choices.length?a.choices.map(i=>q.opts[i].text).join(';'):'这些反应都不符合我';if(q.type==='b')return `最可能:${q.opts[a.best].text} 最不可能:${q.opts[a.worst].text}`;return `感受:${q.feel[a.feel]} 行为:${q.opts[a.action].text}`}
function radar(scores){const cx=210,cy=205,r=135,point=(i,v)=>[cx+Math.sin(i*Math.PI*2/5)*r*v,cy-Math.cos(i*Math.PI*2/5)*r*v],poly=v=>scores.map((_,i)=>point(i,v).join(',')).join(' ');return `<svg class="radar" viewBox="0 0 420 420" role="img" aria-label="五维雷达图:${names.map((n,i)=>n+' '+scores[i]).join(',')}"><title>五维占有倾向,刻度 0–100</title>${[.25,.5,.75,1].map(v=>`<polygon points="${poly(v)}" fill="none" stroke="var(--line)"/>`).join('')}${scores.map((_,i)=>{const [x,y]=point(i,1);return `<line x1="${cx}" y1="${cy}" x2="${x}" y2="${y}" stroke="var(--line)"/>`}).join('')}<polygon points="${scores.map((s,i)=>point(i,s/100).join(',')).join(' ')}" fill="var(--accent)" fill-opacity=".22" stroke="var(--accent)" stroke-width="2"/>${scores.map((s,i)=>{const [x,y]=point(i,s/100),[tx,ty]=point(i,1.28);return `<circle cx="${x}" cy="${y}" r="4" fill="var(--accent)"/><text x="${tx}" y="${ty}" text-anchor="middle" fill="var(--text)" font-size="14">${shortNames[i]}</text>`}).join('')}<text x="218" y="78" fill="var(--muted)" font-size="12">100</text><text x="218" y="145" fill="var(--muted)" font-size="12">50</text></svg>`}
function card(title,body,cls=''){return `<section class="card ${cls}"><h2>${title}</h2>${body}</section>`}
function renderReport(){const r=calculate(answers),s=r.scores,rank=s.map((score,i)=>({score,i})).sort((a,b)=>b.score-a.score),lead=rank[0].i;const concerns=['注意力和优先级是否被持续分走','可能发展为浪漫或情绪亲密的联系','信息不透明时能否获得确定性','自己在关系中的位置是否受到威胁','双方约定的边界能否落实'];const drivers=['情感独占与稳定回应需求','亲密关系的排他需求','关系确定性与信息需求','竞争线索与关系替代敏感','可执行边界与秩序需求'];const gapText=r.gap>=15?'你的内部威胁反应比实际干预更突出。在这些情景中,即使感到不安,你也未必马上限制伴侣的行为。':r.gap<=-15?'你的规则与干预倾向比主观威胁感更突出。部分安排可能源于对可预期相处方式的需要,而不只是当下的嫉妒。':'你的威胁感与干预倾向在总分上较接近,但情绪和行动仍不能直接等同。具体情景中的处理方式更值得关注。';const style=s[1]>=65&&s[4]>=55?'高排他边界':s[2]>=55&&s[4]>=50?'明确规则型边界':s[4]>=30?'协商型边界':'宽松或观察型边界';const styleExplain={'高排他边界':'你可能希望某些私人倾诉、深夜联系或亲近方式主要保留在伴侣之间。与习惯亲密友谊的伴侣相处时,需具体讨论“哪些行为算越界”。','明确规则型边界':'你更可能通过联系安排、透明信息或明确约定减少模糊性。与重视自主安排和隐私的伴侣相处时,对规则范围与自愿程度的理解可能不同。','协商型边界':'你更可能把不适带到对话中,尝试形成可共同执行的安排。若伴侣希望完全自由,或期待固定且严格的限制,你们可能需要更多时间厘清期待。','宽松或观察型边界':'你在本测试中较少直接要求改变行为,可能倾向观察、各自安排或先了解背景。若伴侣期待更明确的优先级表达或排他约定,这种处理可能被误读为不在意。'}[style];const triggers=r.vectors.map((v,i)=>({i,value:(v[3]??0)*.5+(v[4]??0)*.3+(Math.max(v[0]??0,v[1]??0,v[2]??0))*.2})).filter(t=>Q[t.i].role!=='self').sort((a,b)=>b.value-a.value);const top=[];for(const t of triggers){if(!top.some(x=>Q[x.i].tag===Q[t.i].tag))top.push(t);if(top.length===5)break}const chosenFull=selectedText(Q[top[0].i],answers[top[0].i]);const chosenSummary=chosenFull.length>48?chosenFull.slice(0,48)+"…":chosenFull;const core=`在这组情景里,你最突出的关注是${concerns[lead]}。你的反应还受到事件频率、已知背景和原有约定影响。你在“${Q[top[0].i].tag}”中选择了“${chosenSummary}”,这提供了一条具体线索。${gapText}这一模式不代表你在所有关系里都会如此。讨论自己需要什么、双方愿意接受什么约定,比单看强弱标签更有帮助。`;
const symmetryText=r.symmetry>=80?'基本对称':r.symmetry>=55?'部分情境有差异':'多处情境有差异';const diff=r.pairs.filter(p=>p.partner!==p.self);const symDetails=diff.length?diff.map(p=>`<p>在“${Q[p.p].tag}”中,你对伴侣选择“${selectedText(Q[p.p],answers[p.p])}”;对自己选择“${selectedText(Q[p.s],answers[p.s])}”。按本题的限制范围编码,你${p.partner>p.self?'对伴侣的安排更严格':'对自己的安排更严格'}。这也可能涉及你对双方信任、风险或可承担责任的不同理解,不能直接解释为道德问题。</p>`).join(''):'<p>四组对应情景中,你对自己和伴侣所选安排的限制范围基本相同。相同编码不表示所有细节相同,也不能代表现实中每个情景都完全对称。</p>';
const moments=[top[0].i,...r.escalation.slice().sort((a,b)=>Math.abs(b.emotion-b.behavior)-Math.abs(a.emotion-a.behavior)).map(e=>e.i),...r.pairs.slice().sort((a,b)=>Math.abs(b.partner-b.self)-Math.abs(a.partner-a.self)).map(p=>p.p)].filter((v,i,a)=>a.indexOf(v)===i).slice(0,3);const interpretation=i=>{const v=r.vectors[i];if(Q[i].type==='d'){const g=(v[3]-v[4])*25;return g>=25?'这道题里,第一瞬间的不适比你选择的干预更强,可能意味着你会为情绪留出缓冲。':g<=-25?'这道题里,你选择的行为约束比即时不适更突出,可能更看重事先确定相处方式。':'这道题里,感受与行动相互接近,但仍需要看具体约定和沟通背景。'}if(Q[i].mirror)return '这道题涉及你认为什么样的边界安排可以接受,与对应的自身情景一起看,比单看一题更能了解你的规则。';return `${names[v.indexOf(Math.max(...v.map(x=>x??-1)))]}在这一题的反应较突出,可能说明你对这类线索比较在意;它不是对动机的证明。`};const thresholdExplain=r.escalation.map(e=>`阶段 ${e.level}(${Q[e.i].tag}):第一瞬间不适 ${e.emotion}/100,行为干预 ${e.behavior}/100`).join(';');const first=r.escalation.find(e=>e.emotion>=50||e.behavior>=50);const path=`${first?Q[first.i].tag:'出现持续占用亲密时间的联系'} → ${first?Q[first.i].feel[answers[first.i].feel]:'本组升级情景仍未出现中等强度反应'} → 可能开始关注${concerns[lead]} → ${selectedText(Q[24],answers[24])}`;
const oneLine=`你更在意${concerns[lead]},${r.gap>=15?'但通常为情绪与行动留出距离':r.gap<=-15?'并倾向用明确安排让关系更可预期':'行动会随着具体情境和边界需要调整'}。`;
const prediction=[{title:'低压力:认识一位新朋友',i:1,meaning:'这段联系目前是否会影响已有相处,可能是你最先观察的线索。'},{title:'中等威胁:对方成为“特别懂我的人”',i:15,meaning:`你可能开始把事件与${concerns[lead]}联系起来;这是一种解释假设。`},{title:'高压力:反复深夜倾诉并挤占相处',i:24,meaning:'你可能更关注这段联系是否已经改变伴侣之间的时间分配或私人边界。'}];
app.innerHTML=`<div class="report-top"><div><div class="eyebrow">测试已完成 · 28 / 28</div><h1>你的恋爱关系<br>占有欲画像</h1></div><div><div class="muted">综合占有倾向</div><div class="score">${r.composite}<small> / 100</small></div></div></div><p class="note">综合分是五维描述分的平均,不等同于健康程度,也不能单独判断一段关系是否健康。本题库未经常模、信度或效度验证,分数不是人群排名。</p><div class="report-grid">${card('五维雷达图',radar(s)+'<p class="compact">越靠外表示该维度在本题库中的反应越突出。</p>')}${card('五个维度',`<table><thead><tr><th>维度</th><th>描述分 / 100</th></tr></thead><tbody>${names.map((n,i)=>`<tr><td>${n}</td><td>${s[i]}</td></tr>`).join('')}</tbody></table><p class="compact" style="margin-top:16px">注意力需求、浪漫排他、信息需求、嫉妒与行为干预分别观察;高分不自动意味着控制欲或关系问题。</p>`)}${card('核心画像',`<p>${core}</p>`,'full')}${card('占有欲来源结构',rank.slice(0,3).map(({i,score})=>`<p><strong>${drivers[i]}</strong>(${score}/100)<br>${i===0?'更关注稳定回应和相处时间;高亲密需求与限制行为需分开看。':i===1?'更关注关系排他范围。应分别讨论浪漫/性边界与普通社交,不能把二者混成一个禁令。':i===2?'更关注信息是否足够清楚。想了解与认为自己有权获取隐私并不相同,可结合下方实际选择判断。':i===3?'对关系竞争、比较或被替代的线索较敏感,情绪本身并不等于行为控制。':'更关注可执行的相处安排。表达、协商与要求停止联系,涉及不同程度的行为改变。'}</p>`).join('')+'<p class="compact">这些是基于选择的关注结构,不推断人格障碍、依恋障碍或隐藏经历。</p>','full')}${card('触发器 TOP 5',top.map((t,k)=>`<div class="trigger"><strong>${k+1}. ${Q[t.i].tag}</strong><p>你的选择:${selectedText(Q[t.i],answers[t.i])}</p><p class="muted">${interpretation(t.i)}与本次其他情景相比,这里的回答更突出,因此进入相对排序${t.value<1?';不过绝对反应仍然较轻,不能据此称为强触发器':''}。</p></div>`).join(''),'full')}${card('“我介意什么”与“我会做什么”',`<p>威胁敏感 ${s[3]}/100 · 边界干预 ${s[4]}/100</p><p>内外差:${r.gap>0?'+':''}${r.gap} 分(威胁敏感 − 边界干预)。</p><p>${gapText}</p><p class="muted">总分差是一个粗略对照。双层题里最高差异的具体选择,见“最典型的三个测试瞬间”。边界干预还包括表达与协商,不能将整个分数称为强制控制。</p>`)}${card('关系边界风格',`<p class="quote">${style}</p><p>${styleExplain}</p><p class="muted">这只是本次问卷的相处倾向,不存在自动适合所有人的最佳风格。</p>`)}${card('规则对称与差异',`<p>边界对称度:${r.symmetry}/100 · ${symmetryText}</p>${symDetails}`,'full')}${card('占有欲触发阈值与升级路径',`<p class="quote">${r.threshold}</p><p>在四个关联情景中,第一次出现中等或以上不适/干预的是${first?'阶段 '+first.level+':“'+Q[first.i].tag+'”':'尚未出现'}。</p><p class="compact">${thresholdExplain}。这里只按本题库的操作性界线划分,不是经验证的心理阈值,也不是好坏排名。</p><p>${path}</p><p class="muted">路径中的感受与行为来自你的选择;中间的解释是可能理解,不是直接测得的动机。若反应没有逐步增强,应按各题背景理解,而不是强行归为递增。</p>`,'full')}${card('最典型的三个测试瞬间',moments.map(i=>`<div class="moment"><h3>「${Q[i].tag}」</h3><p>${Q[i].text}</p><p><strong>你的选择:</strong>${selectedText(Q[i],answers[i])}</p><p><strong>可能反映:</strong>${interpretation(i)}</p><p class="muted"><strong>为什么这一题重要:</strong>${Q[i].type==='d'?'它把即时感受和实际行动分开,让高嫉妒与高干预不被混为一谈。':Q[i].mirror?'它能与相同背景的自身情景比较,观察规则是否对称。':'它在本次答案中提供了较突出的情景线索,让总分有实际选择作为依据。'}</p></div>`).join(''),'full')}${card('回答一致性',`<p>${r.consistency}</p><p class="muted">${r.consistency==='较低'?'你在不同情境下的边界可能高度依赖具体背景,因此本次结果的不确定性更高。':r.consistency==='中等'?'部分相近情景中的选择有差异,可能受到关系背景、行为细节或当时需要影响。':'相近情景、镜像安排与升级情景的反应整体较连贯;这不证明真实性,也不消除题库本身的测量限制。'}</p><p class="compact">一致性是轻量描述,参考兴趣时间、数字隐私、规则对称与升级反应;它不用于识别撒谎。</p>`,'full')}${card('恋爱占有欲画像',`<p>综合倾向:<strong>${r.composite} / 100</strong></p>${shortNames.map((n,i)=>`<div>${n} <strong style="float:right">${s[i]}</strong></div><div class="bar"><div style="width:${s[i]}%"></div></div>`).join('')}<p><strong>主要触发器:</strong>${top.slice(0,3).map(t=>Q[t.i].tag).join('、')}</p><p><strong>内部占有感:</strong>${s[3]>=60?'本次威胁线索下的情绪反应较突出':s[3]>=35?'对部分情景有明显感受':'本次情景中的即时不适整体较轻'}</p><p><strong>外部控制倾向:</strong>${s[4]>=60?'改变相处行为的倾向较突出,需结合协商与限制的具体选项区分':s[4]>=35?'会在部分情景中表达或调整边界':'较少直接改变伴侣行为,更多观察或了解背景'}</p><p><strong>触发阈值:</strong>${r.threshold}<br><strong>边界对称度:</strong>${symmetryText}(${r.symmetry}/100)<br><strong>回答一致性:</strong>${r.consistency}</p><p class="quote">「${oneLine}」</p>`,'summary full')}${card('关系互动预测',`<p class="muted">以下根据问卷模式推测可能反应,不是对未来行为的确定预测。相似事件也可能因信任基础、已知信息和共同约定而有不同结果。</p>${prediction.map(p=>`<div class="moment"><h3>${p.title}</h3><p><strong>首先可能感受到:</strong>${Q[p.i].feel[answers[p.i].feel]}</p><p><strong>可能如何理解:</strong>${p.meaning}</p><p><strong>最可能采取的行为:</strong>${Q[p.i].opts[answers[p.i].action].text}</p></div>`).join('')}`,'full')}</div><div class="actions no-print"><button class="button" id="download">下载完整报告</button><button class="button secondary" id="print">打印 / 保存 PDF</button><button class="button secondary" id="restart">重新测试</button></div>`;
reportText=app.innerText+'\n\n五维数据(可复制绘图):\n'+names.map((n,i)=>n+','+s[i]).join('\n')+'\n\n完整回答记录:\n'+Q.map((q,i)=>`${i+1}. ${q.text}\n${selectedText(q,answers[i])}`).join('\n\n');document.getElementById('download').onclick=()=>{const blob=new Blob(['\uFEFF'+reportText],{type:'text/plain;charset=utf-8'}),url=URL.createObjectURL(blob),a=document.createElement('a');a.href=url;a.download='恋爱关系占有欲画像.txt';a.click();setTimeout(()=>URL.revokeObjectURL(url),1000)};document.getElementById('print').onclick=()=>window.print();document.getElementById('restart').onclick=()=>{if(window.confirm('重新测试会清除本次答案。继续吗?')){answers=Array(28).fill(null);index=0;introduction();window.scrollTo(0,0)}};window.scrollTo(0,0)}
introduction();