-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
708 lines (654 loc) · 33.7 KB
/
Copy pathindex.html
File metadata and controls
708 lines (654 loc) · 33.7 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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<title>관계의 결 — SRM</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Hahmlet:wght@400;500;600&family=Gothic+A1:wght@300;400;500;700&family=Gowun+Dodum&display=swap');
:root{
--bg:#fdfbf4;
--bg-soft:#f8f4e9;
--ink:#3d352e;
--ink-soft:#5f564c;
--muted:#8e8478;
--faint:#c2b8aa;
--line:#ece6d8;
--line-strong:#dfd6c3;
--card:#ffffff;
--accent:#b87b3f;
--accent-deep:#9c6429;
--accent-soft:#f1e2c9;
--accent-pale:#fbf2dd;
}
*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent;}
html,body{height:100%;}
body{
background:var(--bg);
color:var(--ink);
font-family:'Gothic A1', system-ui, sans-serif;
display:flex;justify-content:center;
background-image:radial-gradient(circle at 50% 0%, #fffef9 0%, var(--bg) 60%);
}
/* serif helper */
.serif{font-family:'Hahmlet',serif;font-weight:500;}
.app{
width:100%;max-width:440px;min-height:100vh;
padding:0 24px 56px;position:relative;display:flex;flex-direction:column;
}
/* progress line */
.progress-line{
position:fixed;top:0;left:50%;transform:translateX(-50%);
width:100%;max-width:440px;height:2px;background:transparent;z-index:10;
}
.progress-line::after{
content:'';position:absolute;left:0;top:0;bottom:0;
background:var(--accent);width:var(--pw,0%);
transition:width .5s cubic-bezier(.4,0,.2,1);opacity:.85;
}
/* quiz dots */
.dots{display:flex;gap:6px;justify-content:center;align-items:center;padding:42px 0 0;min-height:64px;}
.dot{width:5px;height:5px;border-radius:50%;background:var(--faint);transition:all .4s ease;opacity:.55;}
.dot.done{background:var(--muted);opacity:.7;}
.dot.current{background:var(--accent);transform:scale(1.8);opacity:1;}
#screen{flex:1;}
/* start */
.start{display:flex;flex-direction:column;justify-content:center;min-height:100vh;}
.eyebrow{color:var(--accent);font-size:12px;letter-spacing:.22em;font-weight:500;margin-bottom:24px;text-transform:uppercase;}
.start h1{font-family:'Hahmlet',serif;font-size:30px;line-height:1.55;font-weight:500;color:var(--ink);margin-bottom:20px;letter-spacing:-.01em;}
.start .promise{color:var(--ink-soft);font-size:15.5px;line-height:1.75;margin-bottom:44px;font-family:'Gothic A1',sans-serif;}
.start .promise b{color:var(--ink);font-weight:600;}
/* buttons */
.btn{
background:var(--accent);color:#fff;border:none;
padding:17px;border-radius:14px;font-size:15.5px;font-weight:500;
font-family:'Gothic A1';cursor:pointer;width:100%;
transition:transform .15s, background .2s;
margin-bottom:10px;letter-spacing:-.01em;
}
.btn:hover{background:var(--accent-deep);}
.btn:active{transform:scale(.98);}
.btn.ghost{
background:transparent;color:var(--ink-soft);font-weight:500;
font-size:14.5px;padding:14px;border:1px solid var(--line-strong);
}
.btn.ghost:hover{background:#fff;border-color:var(--muted);color:var(--ink);}
/* quiz */
.q-wrap{display:flex;flex-direction:column;justify-content:center;min-height:100vh;}
.chunk-label{color:var(--accent);font-size:11.5px;letter-spacing:.14em;margin-bottom:18px;font-weight:500;opacity:0;transition:opacity .4s;min-height:16px;text-transform:uppercase;}
.chunk-label.show{opacity:.9;}
.q-text{
font-family:'Hahmlet',serif;
font-size:24px;line-height:1.65;font-weight:500;
color:var(--ink);
min-height:130px;
margin-bottom:30px;
letter-spacing:-.01em;
}
/* 답 옵션: 친근한 문구가 버튼에 항상 보이는 수평 5단 */
.scale-row{
display:flex;gap:6px;align-items:stretch;
margin-top:8px;
}
.scale-row .opt{
flex:1;min-width:0;
background:var(--card);border:1px solid var(--line);
border-radius:14px;padding:14px 6px;
cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;
transition:all .2s cubic-bezier(.2,.7,.2,1);
font-family:'Hahmlet',serif;font-weight:500;
color:var(--ink-soft);
font-size:13.5px;line-height:1.35;text-align:center;
min-height:78px;letter-spacing:-.02em;
word-break:keep-all;
}
.scale-row .opt .lvl{
width:6px;height:6px;border-radius:50%;background:var(--faint);
margin-bottom:8px;transition:all .2s;
}
.scale-row .opt:hover{border-color:var(--muted);color:var(--ink);}
.scale-row .opt:hover .lvl{background:var(--muted);}
.scale-row .opt.sel{
background:var(--accent-pale);border-color:var(--accent);color:var(--accent-deep);
transform:translateY(-2px);
box-shadow:0 6px 16px -8px rgba(184,123,63,.4);
}
.scale-row .opt.sel .lvl{background:var(--accent);width:8px;height:8px;}
.back{margin-top:26px;text-align:center;}
.back button{background:none;border:none;color:var(--faint);font-size:13px;cursor:pointer;font-family:'Gothic A1';transition:color .2s;}
.back button:hover{color:var(--muted);}
/* result */
.result-wrap{padding-top:50px;}
.result-head{margin-bottom:30px;}
.result-head .stage-tag{color:var(--accent);font-size:11.5px;letter-spacing:.22em;font-weight:500;margin-bottom:10px;text-transform:uppercase;}
.result-head h2{font-family:'Hahmlet',serif;font-size:26px;font-weight:500;line-height:1.55;color:var(--ink);letter-spacing:-.01em;}
/* cards */
.card{
background:var(--card);border:1px solid var(--line);border-radius:18px;
padding:24px 22px 26px;margin-bottom:14px;
box-shadow:0 1px 0 rgba(80,60,40,.02);
}
.card .lead{
font-family:'Hahmlet',serif;font-weight:500;
font-size:17.5px;line-height:1.65;color:var(--ink);
margin-bottom:12px;letter-spacing:-.01em;
}
.card .shadow-line{color:var(--ink-soft);font-size:14.5px;line-height:1.75;font-family:'Gothic A1',sans-serif;}
.card .basis{color:var(--faint);font-size:11.5px;margin-top:16px;font-style:italic;letter-spacing:.02em;}
/* refine block (S1 secondary CTA) */
.refine-block{
margin:28px 0 12px;padding-top:24px;
border-top:1px solid var(--line);
text-align:center;
}
.refine-block .refine-q{
font-family:'Hahmlet',serif;font-size:15px;font-weight:500;
color:var(--ink-soft);margin-bottom:8px;
}
.refine-block .refine-meta{
font-size:12.5px;color:var(--muted);
margin-bottom:16px;letter-spacing:-.01em;
}
/* match screen */
.match-intro{padding-top:48px;}
.match-intro h2{font-family:'Hahmlet',serif;font-size:25px;font-weight:500;line-height:1.55;color:var(--ink);margin-bottom:14px;letter-spacing:-.01em;}
.match-intro .sub{color:var(--ink-soft);font-size:14.5px;line-height:1.7;margin-bottom:28px;}
.mbti-deck{
margin:0 -24px;padding:8px 24px 24px;
display:flex;gap:12px;overflow-x:auto;
scroll-snap-type:x mandatory;
-webkit-overflow-scrolling:touch;
scrollbar-width:none;
}
.mbti-deck::-webkit-scrollbar{display:none;}
.mbti-card{
flex:0 0 210px;scroll-snap-align:center;
background:var(--card);border:1px solid var(--line);border-radius:18px;
padding:30px 20px 26px;text-align:center;cursor:pointer;
transition:all .2s ease;
}
.mbti-card:hover{border-color:var(--accent);transform:translateY(-2px);}
.mbti-card.sel{border-color:var(--accent);background:var(--accent-pale);}
.mbti-card .code{font-family:'Hahmlet',serif;font-size:22px;font-weight:600;color:var(--accent);letter-spacing:.02em;margin-bottom:8px;}
.mbti-card .nick{font-family:'Hahmlet',serif;font-size:14.5px;font-weight:500;color:var(--ink);margin-bottom:10px;}
.mbti-card .desc{font-size:12.5px;color:var(--muted);line-height:1.55;}
.deck-hint{text-align:center;font-size:12px;color:var(--faint);margin:-8px 0 18px;letter-spacing:.05em;}
.dont-know{
background:var(--bg-soft);border:1px solid var(--line);border-radius:16px;
padding:20px 22px;margin-top:8px;
}
.dont-know .dk-q{font-family:'Hahmlet',serif;font-size:15.5px;font-weight:500;color:var(--ink);margin-bottom:12px;line-height:1.55;}
.dont-know .dk-help{font-size:13px;color:var(--ink-soft);line-height:1.7;margin-bottom:16px;}
.copy-btn{
background:#fff;color:var(--accent);
border:1px solid var(--accent);padding:13px;border-radius:12px;
font-size:14px;font-weight:500;font-family:'Gothic A1';cursor:pointer;width:100%;
transition:all .2s;
}
.copy-btn:hover{background:var(--accent);color:#fff;}
.copy-btn.copied{background:#e8f0e0;border-color:#7a9961;color:#4d6c3a;}
/* match result */
.match-result{padding-top:50px;}
.pair-glyph{text-align:center;margin-bottom:24px;font-family:'Hahmlet',serif;font-size:13px;color:var(--muted);letter-spacing:.18em;font-weight:500;}
.pair-glyph .pair-codes{font-size:22px;color:var(--accent);margin-top:6px;letter-spacing:.12em;font-weight:600;}
/* animations */
.anim-in{animation:fadeUp .5s cubic-bezier(.2,.7,.2,1);}
@keyframes fadeUp{from{opacity:0;transform:translateY(14px);}to{opacity:1;transform:translateY(0);}}
.fading{opacity:0;transform:translateY(-10px);transition:all .2s ease;}
</style>
</head>
<body>
<div class="app">
<div class="progress-line" id="pline" style="--pw:0%"></div>
<div id="dots-area"></div>
<div id="screen"></div>
</div>
<script>
// ══════════════════════════════════════════════════════════════
// DATA
// ══════════════════════════════════════════════════════════════
const SHARE_URL = 'https://hssimp.github.io/srm/';
// 답 옵션 — 항상 버튼에 표시되는 친근한 표현 (수평 5단)
const SCALE_OPTIONS = [
"전혀 아냐",
"별로",
"반반이야",
"꽤 그래",
"완전 그래"
];
const CHUNK_LABEL = ["", "절반 왔어요", "거의 끝나요"];
// 코어 10문항 (시나리오형)
const CORE = [
{id:"C1", t:"처음 만난 모임에서 분위기를 띄우는 역할을 자주 한다", dim:"E", rev:false, chunk:0},
{id:"C2", t:"4명 이상 모이면 어느 순간 내 말수가 줄어든다", dim:"E", rev:true, chunk:0},
{id:"C3", t:"친구가 슬픈 얘기를 하면 내 가슴이 같이 먹먹해진다", dim:"A", rev:false, chunk:0},
{id:"C4", t:"남의 일보다 내 일에 더 집중하는 편이다", dim:"A", rev:true, chunk:0},
{id:"C5", t:"할 일이 생기면 그날 안에 끝내야 마음이 편하다", dim:"C", rev:false, chunk:1},
{id:"C6", t:"책상 위가 정리되지 않은 채 일주일을 보낼 때가 많다", dim:"C", rev:true, chunk:1},
{id:"C7", t:"별일 아닌 메시지 한 줄에 하루가 흔들릴 때가 있다", dim:"N", rev:false, chunk:1},
{id:"C8", t:"스트레스 받을 일이 생겨도 한숨 자고 나면 거의 털어진다", dim:"N", rev:true, chunk:1},
{id:"C9", t:"잠들기 전 머릿속에서 가상의 시나리오를 자주 그려본다", dim:"O", rev:false, chunk:2},
{id:"C10",t:"추상적이거나 철학적인 토론은 금방 지루해진다", dim:"O", rev:true, chunk:2},
];
// 추가 13문항 (10 Big5 보강 + 3 H) — 단일 묶음
const DEEP = [
{id:"AE1",t:"전화 통화보다 문자가 훨씬 편하다", dim:"E", rev:true, chunk:0},
{id:"AA1",t:"누가 운다고 해서 나까지 같이 울게 되진 않는다", dim:"A", rev:true, chunk:0},
{id:"AC1",t:"여행 가기 전에 일정표를 시간 단위로 짜는 편이다", dim:"C", rev:false, chunk:0},
{id:"AN1",t:"오전엔 멀쩡했다가 오후엔 까닭없이 가라앉을 때가 있다", dim:"N", rev:false, chunk:0},
{id:"AO1",t:"은유가 많이 섞인 글은 끝까지 못 읽고 덮는다", dim:"O", rev:true, chunk:0},
{id:"AE2",t:"낯선 사람과도 5분 안에 농담을 주고받게 되는 편이다", dim:"E", rev:false, chunk:1},
{id:"AA2",t:"옆 사람이 우울해 보이면 내 일이 손에 안 잡힌다", dim:"A", rev:false, chunk:1},
{id:"AC2",t:"쓴 물건을 다음 사람을 위해 제자리에 두는 걸 자주 깜빡한다", dim:"C", rev:true, chunk:1},
{id:"AN2",t:"안 좋은 일이 있어도 자고 일어나면 거의 리셋된다", dim:"N", rev:true, chunk:1},
{id:"AO2",t:"현실에 없는 걸 상상하는 데 시간 쓰는 게 비효율적으로 느껴진다", dim:"O", rev:true,chunk:1},
{id:"H1", t:"이득을 보려고 빈말이나 아첨을 하는 게 너무 어색하다", dim:"H", rev:false, chunk:2},
{id:"H2", t:"내가 받는 대접이 나라는 사람의 가치에 못 미친다고 느낄 때가 있다", dim:"H", rev:true, chunk:2},
{id:"H3", t:"지키지 않아도 들킬 일 없는 규칙은 굳이 신경 안 써도 된다", dim:"H", rev:true, chunk:2},
];
// COPY BANK
const COPY = {
E:{
high:{lead:"사람 사이에서 에너지가 올라오는 쪽이에요.",
shadow:"혼자만의 시간이 부족하면 모르는 새 지쳐 있을 수 있어요.",
basis:"에너지 방향 관련 응답에서"},
low:{lead:"혼자 있는 시간이 진짜 충전이 되는 쪽이에요.",
shadow:"처음엔 거리감이 있어 보일 수 있지만, 무관심이 아니라 에너지를 아끼는 방식이에요.",
basis:"에너지 방향 관련 응답에서"},
},
A:{
high:{lead:"주변 사람의 감정을 자연스럽게 흡수하는 편이에요.",
shadow:"공감이 강한 만큼 상대 감정에 내가 먼저 지칠 수 있어요.",
basis:"공감·관심 관련 응답에서"},
low:{lead:"감정보다 사실과 논리를 먼저 보는 쪽이에요.",
shadow:"상대가 위로를 원할 때 해결책부터 꺼내면 차갑게 느껴질 수 있어요.",
basis:"공감·관심 관련 응답에서"},
},
C:{
high:{lead:"한 번 정하면 끝까지 가는 사람이에요.",
shadow:"상대가 즉흥적일 때 답답함이 먼저 올라올 수 있어요.",
basis:"계획·실행 관련 응답에서"},
low:{lead:"흐름에 맡기는 걸 즐기는 사람이에요. 유연하고 여유 있어요.",
shadow:"책임이 쌓일 때 처리 속도가 상대 기대에 못 미칠 수 있어요.",
basis:"계획·실행 관련 응답에서"},
},
N:{
high:{lead:"감정의 결이 풍부하고 작은 신호도 잘 잡아내요.",
shadow:"그만큼 상처가 빨리, 오래 가는 편이에요.",
basis:"감정 반응 관련 응답에서"},
low:{lead:"웬만한 일엔 흔들리지 않는 안정형이에요.",
shadow:"너무 안 흔들리면 상대가 '나만 예민한가' 느낄 수 있어요.",
basis:"감정 반응 관련 응답에서"},
},
O:{
high:{lead:"새로운 걸 먼저 시도해보고 싶어하는 쪽이에요.",
shadow:"변화와 자극을 즐기는 만큼 익숙한 루틴엔 빨리 지루해질 수 있어요.",
basis:"새로움·상상 관련 응답에서"},
low:{lead:"익숙한 것에서 안정을 찾는 사람이에요.",
shadow:"상대가 새로운 걸 제안할 때 부담이 먼저 올 수 있어요.",
basis:"새로움·상상 관련 응답에서"},
},
H:{
high:{lead:"계산보다 진심으로 움직이는 쪽이에요.",
shadow:"솔직함이 때로는 상대에게 너무 직설적으로 다가갈 수 있어요.",
basis:"정직·태도 관련 응답에서"},
low:{lead:"상황에 맞게 유연하게 움직이는 편이에요.",
shadow:"가까운 관계에선 '진심이 뭔지' 상대가 헷갈릴 수 있어요.",
basis:"정직·태도 관련 응답에서"},
},
};
const REL_CONFLICT = {
h_h:"갈등이 생기면 빠르게, 그리고 부드럽게 정리하고 넘어가요. 상대 감정을 챙기는 게 자연스러워요.",
h_l:"빨리 결론 내고 싶은데 직접적이라, 상대가 공격받는 느낌을 받을 수 있어요.",
l_h:"상대 감정을 먼저 챙기느라 정작 문제 해결이 늦어질 수 있어요.",
l_l:"갈등을 일단 덮는 쪽이에요. 상대가 답답해할 수 있어요.",
};
const REL_RHYTHM = {
h_h:"계획적이지만 새로운 시도도 반기는 균형형이에요.",
h_l:"루틴이 편한 쪽이에요. 갑작스러운 변경엔 적응 시간이 필요해요.",
l_h:"즉흥과 새로움을 즐겨요. 장기 약속이 필요할 때 갭이 생길 수 있어요.",
l_l:"큰 변화 없는 익숙한 흐름이 편안한 쪽이에요.",
};
const DIM_LABEL = {E:"에너지 방향", A:"공감 방식", C:"실행 스타일", N:"감정 결", O:"새로움 태도", H:"정직·태도"};
// SRM 독자 결 유형 — Big Five 조합 기반 12유형 (MBTI 무관)
const SRM_TYPES = [
{id:"anchor", name:"닻형", trait:"곁에 있으면 안심이 돼요",
desc:"조용하고 신뢰감 있어요. 흔들릴 때 버텨줘요",
big5:{E:2.0, O:2.5, C:4.2, A:3.8, N:2.2, H:3.8}},
{id:"classic", name:"정석형", trait:"약속은 무조건 지키는 사람이에요",
desc:"원칙과 책임감이 일관되게 느껴져요",
big5:{E:2.5, O:2.0, C:4.5, A:3.0, N:2.0, H:4.2}},
{id:"map", name:"지도형", trait:"체계적이면서도 새 길을 찾아요",
desc:"계획성과 창의성을 동시에 가지고 있어요",
big5:{E:2.0, O:4.2, C:4.2, A:3.0, N:2.5, H:3.5}},
{id:"flow", name:"흐름형", trait:"상황에 맡기는 게 자연스러워요",
desc:"유연하고 새로운 것에 열려 있어요",
big5:{E:2.0, O:4.2, C:2.5, A:3.0, N:3.0, H:3.0}},
{id:"depth", name:"그늘형", trait:"감수성이 깊고 내면이 풍부해요",
desc:"작은 것도 섬세하게 느끼는 편이에요",
big5:{E:2.0, O:3.8, C:3.0, A:3.8, N:4.2, H:3.5}},
{id:"warmth", name:"온기형", trait:"말없이 조용히 챙기는 사람이에요",
desc:"따뜻함이 행동으로 전해져요",
big5:{E:2.2, O:3.0, C:3.2, A:4.2, N:3.0, H:4.0}},
{id:"mountain", name:"산형", trait:"웬만한 일에 흔들리지 않아요",
desc:"안정감과 묵직함이 느껴지는 사람이에요",
big5:{E:2.2, O:2.2, C:4.0, A:3.0, N:1.8, H:3.5}},
{id:"compass", name:"나침반형", trait:"방향을 잡고 주도적으로 움직여요",
desc:"목표가 있고 논리적으로 이끌어요",
big5:{E:4.2, O:3.0, C:4.2, A:2.5, N:2.0, H:3.5}},
{id:"explorer", name:"탐험형", trait:"새로운 게 있으면 먼저 달려들어요",
desc:"에너지와 호기심이 넘쳐요",
big5:{E:4.2, O:4.2, C:2.5, A:3.0, N:2.5, H:3.0}},
{id:"flame", name:"불꽃형", trait:"즉흥적이고 에너지가 먼저 오는 사람",
desc:"분위기를 확 바꿔놓을 수 있어요",
big5:{E:4.5, O:4.0, C:2.0, A:3.2, N:3.5, H:3.0}},
{id:"wave", name:"파도형", trait:"감정과 관계에 에너지를 쏟는 사람",
desc:"풍부한 감정으로 주변을 물들여요",
big5:{E:4.0, O:3.8, C:2.5, A:4.2, N:3.8, H:3.2}},
{id:"mirror", name:"거울형", trait:"상대 마음을 잘 읽는 사람",
desc:"분위기 파악이 빠르고 맞춰주는 게 자연스러워요",
big5:{E:3.8, O:3.2, C:3.0, A:4.2, N:2.5, H:3.5}},
];
// ══════════════════════════════════════════════════════════════
// STATE
// ══════════════════════════════════════════════════════════════
let answers = {};
let currentItems = [];
let currentIdx = 0;
let appStage = 'start';
let scores = {};
// SCORING
function allItemsFlat(){ return [...CORE, ...DEEP]; }
function computeScores(){
const acc = {E:[],A:[],C:[],N:[],O:[],H:[]};
for(const [id,val] of Object.entries(answers)){
const it = allItemsFlat().find(x=>x.id===id);
if(!it) continue;
const s = it.rev ? (6-val) : val;
if(acc[it.dim]) acc[it.dim].push(s);
}
const out = {};
for(const [d,v] of Object.entries(acc)) out[d] = v.length ? v.reduce((a,b)=>a+b,0)/v.length : null;
return out;
}
function level(s){if(s==null) return 'u'; if(s>=3.5) return 'h'; if(s<=2.5) return 'l'; return 'm';}
function getCopy(d, s){const lv=level(s); const c=COPY[d]; if(!c||lv==='m'||lv==='u') return null; return lv==='h'?c.high:c.low;}
function sortByClarity(sc, n){
return ['E','A','C','N','O','H']
.filter(d=>sc[d]!=null)
.sort((a,b)=>Math.abs(sc[b]-3) - Math.abs(sc[a]-3))
.slice(0,n);
}
// ══════════════════════════════════════════════════════════════
// HTML helpers
// ══════════════════════════════════════════════════════════════
function cardHtml(c){
return `<div class="card">
<div class="lead">${c.lead}</div>
<div class="shadow-line">${c.shadow}</div>
<div class="basis">— ${c.basis}</div>
</div>`;
}
function neutralCardHtml(dim){
return `<div class="card">
<div class="lead">두 방향이 모두 보이는 사람이에요.</div>
<div class="shadow-line">상황에 따라 달라지는 쪽이에요. 문항을 더 답하면 더 선명해져요.</div>
<div class="basis">— ${DIM_LABEL[dim]||dim} 관련 응답에서</div>
</div>`;
}
const pline = document.getElementById('pline');
const dotsEl = document.getElementById('dots-area');
const screen = document.getElementById('screen');
function setProg(done,total){pline.style.setProperty('--pw',(total?done/total*100:0)+'%');}
function renderDots(done,total){
if(!total){dotsEl.innerHTML='';return;}
dotsEl.innerHTML = '<div class="dots">' +
Array.from({length:total},(_,i)=>{let c='dot'; if(i<done) c+=' done'; else if(i===done) c+=' current'; return `<span class="${c}"></span>`;}).join('') +
'</div>';
}
// ══════════════════════════════════════════════════════════════
// START
// ══════════════════════════════════════════════════════════════
function showStart(){
appStage='start';
setProg(0,0); dotsEl.innerHTML='';
screen.innerHTML = `
<div class="start anim-in">
<div class="eyebrow">관계 예측</div>
<h1>두 사람이 어떻게<br>맞물릴지 비춰드려요.</h1>
<p class="promise"><b>1분이면 끝나요.</b> 짧은 질문 <b>10개</b>,<br>고민 말고 느낌대로 답해주세요.</p>
<button class="btn" onclick="startQuiz()">시작하기</button>
<button class="btn ghost" onclick="showInfo()">어떻게 작동하나요?</button>
</div>`;
}
function showInfo(){
screen.innerHTML = `
<div class="start anim-in">
<div class="eyebrow">작동 방식</div>
<h1>유형명은<br>알려드리지 않아요.</h1>
<p class="promise">심리학적으로 검증된 Big Five 측정을 기반으로,<br>두 사람의 관계가 어떻게 흐를지만 비춰드려요.<br><br>"ENTJ라서" "전갈자리라서" 같은 선입견 없이,<br>실제 응답에 근거한 서술만 드려요.</p>
<button class="btn" onclick="startQuiz()">시작하기</button>
<button class="btn ghost" onclick="showStart()">뒤로</button>
</div>`;
}
// ══════════════════════════════════════════════════════════════
// QUIZ
// ══════════════════════════════════════════════════════════════
function startQuiz(){answers={}; currentItems=CORE; currentIdx=0; appStage='quiz_core'; showItem();}
function startDeep(){currentItems=DEEP; currentIdx=0; appStage='quiz_deep'; showItem();}
function showItem(){
const it = currentItems[currentIdx];
const total = currentItems.length;
const prevChunk = currentIdx>0 ? (currentItems[currentIdx-1].chunk??0) : -1;
const newChunk = (it.chunk!=null && it.chunk!==prevChunk && it.chunk>0);
const sel = answers[it.id] || null;
renderDots(currentIdx, total);
setProg(currentIdx, total);
screen.innerHTML = `
<div class="q-wrap anim-in">
<div class="chunk-label ${newChunk?'show':''}">${newChunk && it.chunk<CHUNK_LABEL.length ? CHUNK_LABEL[it.chunk] : ''}</div>
<div class="q-text">${it.t}</div>
<div class="scale-row" id="scale">
${SCALE_OPTIONS.map((label,i)=>`
<div class="opt ${sel===(i+1)?'sel':''}" data-v="${i+1}" onclick="answer(${i+1})">
<span class="lvl"></span>${label}
</div>`).join('')}
</div>
<div class="back">${currentIdx>0?'<button onclick="goBack()">← 이전</button>':''}</div>
</div>`;
}
function answer(v){
const id = currentItems[currentIdx].id;
answers[id] = v;
document.querySelectorAll('.opt').forEach(el=>{
el.classList.toggle('sel', Number(el.dataset.v)===v);
});
setTimeout(()=>{
const qw = document.querySelector('.q-wrap');
if(qw) qw.classList.add('fading');
setTimeout(()=>{
currentIdx++;
if(currentIdx >= currentItems.length) onPhaseComplete();
else showItem();
}, 220);
}, 300);
}
function goBack(){if(currentIdx>0){currentIdx--; showItem();}}
function onPhaseComplete(){
scores = computeScores();
if(appStage==='quiz_core'){ showS1(); }
else if(appStage==='quiz_deep'){ showS2(); }
}
// ══════════════════════════════════════════════════════════════
// RESULT 1 — 첫 결, 결론형
// "지금 관계 보기" 가 메인 / 그 아래 "더 정확하게 보기" 보조
// ══════════════════════════════════════════════════════════════
function showS1(){
appStage='s1';
setProg(0,0); dotsEl.innerHTML='';
const top = sortByClarity(scores, 3);
const cards = top.map(d=>{
const c = getCopy(d, scores[d]);
return c ? cardHtml(c) : neutralCardHtml(d);
}).join('');
screen.innerHTML = `
<div class="result-wrap anim-in">
<div class="result-head">
<div class="stage-tag">당신의 결</div>
<h2>이런 모습이<br>보여요.</h2>
</div>
${cards}
<button class="btn" onclick="goToMatch()">지금 관계 보기</button>
<div class="refine-block">
<div class="refine-q">더 정확하게 보고 싶다면?</div>
<div class="refine-meta">13문항을 더 답하면 관계 신호까지 보여요</div>
<button class="btn ghost" onclick="startDeep()">더 답하기</button>
</div>
</div>`;
}
// ══════════════════════════════════════════════════════════════
// RESULT 2 — 깊은 결 (관계 신호 포함)
// ══════════════════════════════════════════════════════════════
function showS2(){
appStage='s2';
setProg(0,0); dotsEl.innerHTML='';
const top = sortByClarity(scores, 4);
const cards = top.map(d=>{
const c = getCopy(d, scores[d]);
return c ? cardHtml(c) : neutralCardHtml(d);
}).join('');
const cl = level(scores.C)==='m' ? 'l' : level(scores.C);
const al = level(scores.A)==='m' ? 'l' : level(scores.A);
const ol = level(scores.O)==='m' ? 'l' : level(scores.O);
const conflict = REL_CONFLICT[`${cl}_${al}`] || "갈등 방식은 상황에 따라 달라요.";
const rhythm = REL_RHYTHM[`${cl}_${ol}`] || "생활 리듬은 상황에 따라 달라요.";
screen.innerHTML = `
<div class="result-wrap anim-in">
<div class="result-head">
<div class="stage-tag">깊은 결</div>
<h2>더 들어가서 본<br>당신의 결이에요.</h2>
</div>
${cards}
<div class="card">
<div class="lead">갈등 방식</div>
<div class="shadow-line">${conflict}</div>
<div class="basis">— 계획·공감 결합 응답에서</div>
</div>
<div class="card">
<div class="lead">생활 리듬</div>
<div class="shadow-line">${rhythm}</div>
<div class="basis">— 실행·새로움 결합 응답에서</div>
</div>
<button class="btn" onclick="goToMatch()">관계 매칭 시작하기</button>
<button class="btn ghost" onclick="showStart()">처음부터 다시</button>
</div>`;
}
// ══════════════════════════════════════════════════════════════
// MATCH
// ══════════════════════════════════════════════════════════════
function goToMatch(){
appStage='match';
setProg(0,0); dotsEl.innerHTML='';
const cards = SRM_TYPES.map(t=>`
<div class="mbti-card" data-id="${t.id}" onclick="pickType('${t.id}')">
<div class="code">${t.name}</div>
<div class="nick">${t.trait}</div>
<div class="desc">${t.desc}</div>
</div>`).join('');
screen.innerHTML = `
<div class="match-intro anim-in">
<div class="eyebrow">관계 매칭</div>
<h2>상대방은 어떤<br>결인가요?</h2>
<p class="sub">가장 비슷한 결을 골라주세요.<br>오른쪽으로 밀어서 더 볼 수 있어요.</p>
</div>
<div class="mbti-deck" id="deck">${cards}</div>
<div class="deck-hint">← 좌우로 밀어보세요 →</div>
<div class="dont-know">
<div class="dk-q">상대방의 결을 잘 모르시나요?</div>
<div class="dk-help">상대방에게 검사 링크를 보내면, 본인의 결과를 받아볼 수 있어요. 상대는 본인 유형명을 모르는 채로 답해도 됩니다.</div>
<button class="copy-btn" id="copyBtn" onclick="copyShareLink()">검사 링크 복사하기</button>
</div>
<div style="margin-top:20px"><button class="btn ghost" onclick="showStart()">처음으로</button></div>`;
}
function pickType(id){
document.querySelectorAll('.mbti-card').forEach(el=>{
el.classList.toggle('sel', el.dataset.id===id);
});
setTimeout(()=>showMatchResult(id), 320);
}
function showMatchResult(id){
const type = SRM_TYPES.find(t=>t.id===id);
const partner = type.big5;
const me = scores;
const me_E = level(me.E), me_O = level(me.O), me_C = level(me.C);
const me_A = level(me.A);
const partner_E = partner.E>=3.5?'h':'l';
const partner_O = partner.O>=3.5?'h':'l';
const partner_C = partner.C>=3.5?'h':'l';
const partner_A = partner.A>=3.5?'h':'l';
const sim_E = (me_E==='h' && partner_E==='h') || (me_E==='l' && partner_E==='l');
const diff_O = (me_O==='h' || me_O==='l') && me_O !== partner_O;
const diff_C = (me_C==='h' || me_C==='l') && me_C !== partner_C;
const energyText = sim_E
? "에너지 패턴이 비슷해요. 같이 있어도 따로 있어도 부담이 적은 조합이에요."
: "에너지 방향이 달라요. 한쪽이 모임을 원할 때 다른 쪽이 쉬고 싶을 수 있어요.";
const noveltyText = diff_O
? "새로움에 대한 태도가 달라요. 한쪽이 변화를 제안할 때 다른 쪽은 멈추고 싶어해요."
: "새로움에 대한 결이 비슷해서, 일상 결정에서 충돌이 적어요.";
const planText = diff_C
? "한 명이 계획적이고 다른 한 명이 즉흥적이에요. 보완이 되지만 갈등 포인트가 될 수도 있어요."
: "실행 스타일이 비슷해서 일을 같이 풀어가기 편해요.";
const cl2 = me_C==='m'?'l':me_C;
const al2 = me_A==='m'?'l':me_A;
const conflict = REL_CONFLICT[`${cl2}_${al2}`] || "갈등 방식은 상황에 따라 달라요.";
screen.innerHTML = `
<div class="match-result anim-in">
<div class="pair-glyph">
나 × 상대
<div class="pair-codes">● — ${type.name}</div>
</div>
<div class="result-head" style="margin-bottom:20px">
<div class="stage-tag">관계의 결</div>
<h2>두 사람은 이렇게<br>맞물려요.</h2>
</div>
<div class="card">
<div class="lead">에너지</div>
<div class="shadow-line">${energyText}</div>
</div>
<div class="card">
<div class="lead">새로움</div>
<div class="shadow-line">${noveltyText}</div>
</div>
<div class="card">
<div class="lead">계획 vs 즉흥</div>
<div class="shadow-line">${planText}</div>
</div>
<div class="card">
<div class="lead">갈등 방식</div>
<div class="shadow-line">${conflict}</div>
</div>
<div style="margin-top:20px">
<button class="btn ghost" onclick="goToMatch()">다른 결로 다시 보기</button>
<button class="btn ghost" onclick="showStart()">처음으로</button>
</div>
</div>`;
}
async function copyShareLink(){
const btn = document.getElementById('copyBtn');
try{
await navigator.clipboard.writeText(SHARE_URL);
btn.textContent = "✓ 링크가 복사됐어요";
btn.classList.add('copied');
}catch(e){
const ta = document.createElement('textarea');
ta.value = SHARE_URL; document.body.appendChild(ta); ta.select();
try{document.execCommand('copy'); btn.textContent="✓ 링크가 복사됐어요"; btn.classList.add('copied');}
catch(e2){btn.textContent="링크: " + SHARE_URL;}
document.body.removeChild(ta);
}
setTimeout(()=>{btn.textContent="검사 링크 복사하기"; btn.classList.remove('copied');}, 3200);
}
showStart();
</script>
</body>
</html>