@deina-labs/deina-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +154 -0
  2. package/dist/archetype-card.d.ts +5 -0
  3. package/dist/archetype-card.d.ts.map +1 -0
  4. package/dist/archetype-card.js +239 -0
  5. package/dist/archetype-card.js.map +1 -0
  6. package/dist/archetypes.d.ts +3 -0
  7. package/dist/archetypes.d.ts.map +1 -0
  8. package/dist/archetypes.js +47 -0
  9. package/dist/archetypes.js.map +1 -0
  10. package/dist/classify.d.ts +9 -0
  11. package/dist/classify.d.ts.map +1 -0
  12. package/dist/classify.js +327 -0
  13. package/dist/classify.js.map +1 -0
  14. package/dist/coaching.d.ts +4 -0
  15. package/dist/coaching.d.ts.map +1 -0
  16. package/dist/coaching.js +107 -0
  17. package/dist/coaching.js.map +1 -0
  18. package/dist/deinas.d.ts +10 -0
  19. package/dist/deinas.d.ts.map +1 -0
  20. package/dist/deinas.js +55 -0
  21. package/dist/deinas.js.map +1 -0
  22. package/dist/fun-facts.d.ts +8 -0
  23. package/dist/fun-facts.d.ts.map +1 -0
  24. package/dist/fun-facts.js +30 -0
  25. package/dist/fun-facts.js.map +1 -0
  26. package/dist/index.d.ts +13 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +9 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/onboarding-content.d.ts +94 -0
  31. package/dist/onboarding-content.d.ts.map +1 -0
  32. package/dist/onboarding-content.js +96 -0
  33. package/dist/onboarding-content.js.map +1 -0
  34. package/dist/quiz-questions.d.ts +40 -0
  35. package/dist/quiz-questions.d.ts.map +1 -0
  36. package/dist/quiz-questions.js +336 -0
  37. package/dist/quiz-questions.js.map +1 -0
  38. package/dist/types.d.ts +23 -0
  39. package/dist/types.d.ts.map +1 -0
  40. package/dist/types.js +2 -0
  41. package/dist/types.js.map +1 -0
  42. package/package.json +33 -0
  43. package/src/archetype-card.ts +248 -0
  44. package/src/archetypes.ts +54 -0
  45. package/src/classify.ts +336 -0
  46. package/src/coaching.ts +115 -0
  47. package/src/deinas.ts +77 -0
  48. package/src/fun-facts.ts +38 -0
  49. package/src/index.ts +38 -0
  50. package/src/onboarding-content.ts +102 -0
  51. package/src/quiz-questions.ts +479 -0
  52. package/src/types.ts +24 -0
@@ -0,0 +1,248 @@
1
+ import type { DatingArchetype } from './types.js';
2
+
3
+ export function escapeHtml(str: string): string {
4
+ return str
5
+ .replace(/&/g, '&')
6
+ .replace(/</g, '&lt;')
7
+ .replace(/>/g, '&gt;')
8
+ .replace(/"/g, '&quot;')
9
+ .replace(/'/g, '&#039;');
10
+ }
11
+
12
+ export function renderArchetypePage(archetype: DatingArchetype, name?: string): string {
13
+ const displayName = archetype.name.replace(/^The /, '');
14
+ const escapedName = name ? escapeHtml(name) : '';
15
+ const escapedArchName = escapeHtml(archetype.name);
16
+ const escapedDisplayName = escapeHtml(displayName);
17
+ const escapedSignature = escapeHtml(archetype.signatureLine);
18
+ const escapedDescription = escapeHtml(archetype.description);
19
+
20
+ const pageTitle = name
21
+ ? `${escapeHtml(name)} is ${escapedArchName} — Deina`
22
+ : `I'm ${escapedArchName} — Deina`;
23
+
24
+ const descriptionParagraphs = archetype.description
25
+ .split('\n\n')
26
+ .map((p) => `<p>${escapeHtml(p)}</p>`)
27
+ .join('');
28
+
29
+ const traitItems = archetype.traits
30
+ .map((t) => `<div class="trait">${escapeHtml(t)}</div>`)
31
+ .join('');
32
+
33
+ const rarityHtml = archetype.rarityNote
34
+ ? `<div class="rarity">${escapeHtml(archetype.rarityNote)}</div>`
35
+ : '';
36
+
37
+ const nameLabel = escapedName
38
+ ? `<div class="name-label">${escapedName}</div>`
39
+ : '';
40
+
41
+ return `<!DOCTYPE html>
42
+ <html lang="en">
43
+ <head>
44
+ <meta charset="UTF-8">
45
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
46
+ <title>${pageTitle}</title>
47
+ <meta property="og:title" content="${pageTitle}">
48
+ <meta property="og:description" content="${escapedSignature}">
49
+ <meta property="og:type" content="profile">
50
+ <meta name="twitter:card" content="summary">
51
+ <meta name="twitter:title" content="${pageTitle}">
52
+ <meta name="twitter:description" content="${escapedSignature}">
53
+ <link rel="preconnect" href="https://fonts.googleapis.com">
54
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
55
+ <link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,700;0,900;1,400;1,700&family=DM+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
56
+ <style>
57
+ * { margin: 0; padding: 0; box-sizing: border-box; }
58
+ body {
59
+ font-family: 'DM Sans', sans-serif;
60
+ background: #ffffff;
61
+ display: flex;
62
+ justify-content: center;
63
+ padding: 24px 16px;
64
+ min-height: 100vh;
65
+ }
66
+ .container {
67
+ max-width: 420px;
68
+ width: 100%;
69
+ }
70
+ .card {
71
+ background: linear-gradient(180deg, #8B1A1A 0%, #5C1010 60%, #3D0A0A 100%);
72
+ border-radius: 20px;
73
+ box-shadow: 0 12px 40px rgba(116, 25, 25, 0.25);
74
+ animation: fadeIn 0.8s ease-out;
75
+ }
76
+ .card-inner {
77
+ margin: 12px;
78
+ border: 1px solid rgba(255, 255, 255, 0.15);
79
+ border-radius: 14px;
80
+ padding: 32px 24px;
81
+ text-align: center;
82
+ color: #ffffff;
83
+ }
84
+ .logo {
85
+ font-family: 'Playfair Display', serif;
86
+ font-size: 14px;
87
+ font-weight: 700;
88
+ color: rgba(255, 255, 255, 0.45);
89
+ letter-spacing: 5px;
90
+ margin-bottom: 20px;
91
+ }
92
+ .name-label {
93
+ font-size: 11px;
94
+ font-weight: 600;
95
+ text-transform: uppercase;
96
+ letter-spacing: 2.5px;
97
+ color: rgba(255, 255, 255, 0.4);
98
+ margin-bottom: 8px;
99
+ }
100
+ .archetype-name {
101
+ font-family: 'Playfair Display', serif;
102
+ font-size: 36px;
103
+ font-weight: 900;
104
+ margin-bottom: 20px;
105
+ }
106
+ .divider {
107
+ width: 36px;
108
+ height: 1px;
109
+ background: rgba(255, 255, 255, 0.15);
110
+ margin: 20px auto;
111
+ }
112
+ .trait {
113
+ font-family: 'Playfair Display', serif;
114
+ font-size: 18px;
115
+ color: rgba(255, 255, 255, 0.9);
116
+ margin-bottom: 6px;
117
+ }
118
+ .description p {
119
+ font-size: 13px;
120
+ color: rgba(255, 255, 255, 0.75);
121
+ line-height: 1.6;
122
+ margin-bottom: 12px;
123
+ }
124
+ .signature {
125
+ font-family: 'Playfair Display', serif;
126
+ font-style: italic;
127
+ font-size: 16px;
128
+ font-weight: 700;
129
+ color: #ffffff;
130
+ margin-top: 20px;
131
+ }
132
+ .rarity {
133
+ font-size: 11px;
134
+ font-style: italic;
135
+ color: rgba(255, 255, 255, 0.4);
136
+ margin-top: 12px;
137
+ }
138
+ .footer {
139
+ font-size: 9px;
140
+ font-weight: 700;
141
+ letter-spacing: 3px;
142
+ color: rgba(255, 255, 255, 0.25);
143
+ margin-top: 24px;
144
+ }
145
+ .cta {
146
+ text-align: center;
147
+ margin-top: 24px;
148
+ }
149
+ .cta a {
150
+ display: inline-block;
151
+ background: #741919;
152
+ color: #ffffff;
153
+ font-family: 'DM Sans', sans-serif;
154
+ font-size: 14px;
155
+ font-weight: 600;
156
+ padding: 12px 32px;
157
+ border-radius: 999px;
158
+ text-decoration: none;
159
+ }
160
+ @keyframes fadeIn {
161
+ from { opacity: 0; transform: translateY(12px); }
162
+ to { opacity: 1; transform: translateY(0); }
163
+ }
164
+ </style>
165
+ </head>
166
+ <body>
167
+ <div class="container">
168
+ <div class="card">
169
+ <div class="card-inner">
170
+ <div class="logo">DEINA</div>
171
+ ${nameLabel}
172
+ <div class="archetype-name">${escapedDisplayName}</div>
173
+ <div class="divider"></div>
174
+ ${traitItems}
175
+ <div class="divider"></div>
176
+ <div class="description">${descriptionParagraphs}</div>
177
+ <div class="divider"></div>
178
+ <div class="signature">${escapedSignature}</div>
179
+ ${rarityHtml}
180
+ <div class="footer">DATING ARCHETYPE</div>
181
+ </div>
182
+ </div>
183
+ <div class="cta">
184
+ <a href="https://apps.apple.com/app/deina">Discover yours</a>
185
+ </div>
186
+ </div>
187
+ </body>
188
+ </html>`;
189
+ }
190
+
191
+ export function renderNotFoundPage(): string {
192
+ return `<!DOCTYPE html>
193
+ <html lang="en">
194
+ <head>
195
+ <meta charset="UTF-8">
196
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
197
+ <title>Not Found — Deina</title>
198
+ <link rel="preconnect" href="https://fonts.googleapis.com">
199
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
200
+ <link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700;900&family=DM+Sans:wght@400;500;600&display=swap" rel="stylesheet">
201
+ <style>
202
+ * { margin: 0; padding: 0; box-sizing: border-box; }
203
+ body {
204
+ font-family: 'DM Sans', sans-serif;
205
+ background: #ffffff;
206
+ display: flex;
207
+ justify-content: center;
208
+ align-items: center;
209
+ min-height: 100vh;
210
+ padding: 24px;
211
+ text-align: center;
212
+ }
213
+ .content {
214
+ max-width: 360px;
215
+ }
216
+ h1 {
217
+ font-family: 'Playfair Display', serif;
218
+ font-size: 28px;
219
+ font-weight: 900;
220
+ color: #741919;
221
+ margin-bottom: 12px;
222
+ }
223
+ p {
224
+ font-size: 14px;
225
+ color: #666;
226
+ margin-bottom: 24px;
227
+ }
228
+ a {
229
+ display: inline-block;
230
+ background: #741919;
231
+ color: #ffffff;
232
+ font-size: 14px;
233
+ font-weight: 600;
234
+ padding: 12px 32px;
235
+ border-radius: 999px;
236
+ text-decoration: none;
237
+ }
238
+ </style>
239
+ </head>
240
+ <body>
241
+ <div class="content">
242
+ <h1>Archetype not found</h1>
243
+ <p>This link may have expired or doesn't exist.</p>
244
+ <a href="https://apps.apple.com/app/deina">Discover yours</a>
245
+ </div>
246
+ </body>
247
+ </html>`;
248
+ }
@@ -0,0 +1,54 @@
1
+ import type { DatingArchetype } from './types.js';
2
+
3
+ export const DATING_ARCHETYPES: DatingArchetype[] = [
4
+ {
5
+ id: 'the-strategic-dater',
6
+ name: 'The Strategic Dater',
7
+ traits: ['Strategic', 'Observant', 'Selective', 'Standards-Driven'],
8
+ description:
9
+ 'A Strategic Dater approaches relationships with clarity and intention. Rather than relying only on chemistry, they pay close attention to patterns — how consistently someone shows up, follows through, and invests over time. Effort, reliability, and emotional maturity matter just as much as attraction.\n\nBecause they evaluate behavior carefully before investing emotionally, Strategic Daters are less likely to stay in chaotic or ambiguous relationships. At times, however, their instinct to analyze connections quickly can make slower-building chemistry easy to overlook.',
10
+ signatureLine: 'You trust patterns more than promises.',
11
+ rarityNote: 'One of the most selective dating styles.',
12
+ },
13
+ {
14
+ id: 'the-hopeful-romantic',
15
+ name: 'The Hopeful Romantic',
16
+ traits: ['Open', 'Connection-Driven', 'Expressive', 'Optimistic'],
17
+ description:
18
+ 'Hopeful Romantics approach dating with warmth and emotional openness. They believe meaningful relationships often begin with genuine chemistry and emotional connection. When they feel excited about someone, they are naturally willing to explore where the relationship might lead.\n\nTheir ability to build connection quickly can create deeply meaningful relationships. However, because they see potential easily, Hopeful Romantics may occasionally assume long-term compatibility before consistent effort is proven.',
19
+ signatureLine: 'You believe the right connection can change everything.',
20
+ },
21
+ {
22
+ id: 'the-thoughtful-evaluator',
23
+ name: 'The Thoughtful Evaluator',
24
+ traits: ['Reflective', 'Patient', 'Observant', 'Measured'],
25
+ description:
26
+ 'Thoughtful Evaluators prefer to understand someone fully before investing emotionally. Rather than rushing into connection, they observe patterns carefully and allow compatibility to reveal itself over time.\n\nThis patience often protects them from emotionally chaotic relationships. At times, however, their cautious approach can slow the emotional momentum of promising connections.',
27
+ signatureLine: "You watch what people do — not just what they say.",
28
+ },
29
+ {
30
+ id: 'the-compassionate-partner',
31
+ name: 'The Compassionate Partner',
32
+ traits: ['Empathetic', 'Supportive', 'Emotionally Aware', 'Loyal'],
33
+ description:
34
+ 'Compassionate Partners naturally invest in building emotional connection with the people they date. Their empathy and emotional intelligence allow them to understand others deeply and create strong bonds.\n\nBecause they care about people genuinely, they often bring warmth and stability to relationships. However, their desire to understand and support others can sometimes lead them to give connections more chances than they deserve.',
35
+ signatureLine: "You see the good in people — sometimes before they've earned it.",
36
+ },
37
+ {
38
+ id: 'the-independent-chooser',
39
+ name: 'The Independent Chooser',
40
+ traits: ['Independent', 'Self-Assured', 'Selective', 'Grounded'],
41
+ description:
42
+ 'Independent Choosers approach dating from a place of stability rather than urgency. Their life already feels full and meaningful, so relationships are something they want rather than something they rely on.\n\nBecause they are not dating from scarcity, they tend to evaluate partners with clarity and strong standards. At times, however, their independence can make it harder for others to recognize when they are earning deeper emotional access.',
43
+ signatureLine: "You don't need a relationship — you choose one.",
44
+ rarityNote: 'A dating style often found among highly independent people.',
45
+ },
46
+ {
47
+ id: 'the-intentional-builder',
48
+ name: 'The Intentional Builder',
49
+ traits: ['Purpose-Driven', 'Future-Oriented', 'Committed', 'Relationship-Focused'],
50
+ description:
51
+ "Intentional Builders approach dating with a clear desire to build a meaningful partnership. They value consistency, emotional maturity, and shared vision for the future.\n\nTheir clarity about what they want often helps them identify partners who are serious about building something lasting. At times, however, their focus on long-term potential can make promising connections feel especially important early on.",
52
+ signatureLine: "You're not just dating — you're building something.",
53
+ },
54
+ ];
@@ -0,0 +1,336 @@
1
+ import type { DatingArchetype } from './types.js';
2
+ import { DATING_ARCHETYPES } from './archetypes.js';
3
+
4
+ export function classifyArchetype(answers: {
5
+ standards: Record<string, number | null>;
6
+ timelineAnswers: Record<string, string | null>;
7
+ mindsetAnswers?: Record<string, string | null>;
8
+ enforcementAnswers: Record<string, string | null>;
9
+ tradeOffAnswers: Record<string, string | null>;
10
+ }): DatingArchetype {
11
+ const scores: Record<string, number> = {};
12
+ DATING_ARCHETYPES.forEach((a) => (scores[a.id] = 0));
13
+
14
+ const { standards, timelineAnswers, mindsetAnswers, enforcementAnswers, tradeOffAnswers } =
15
+ answers;
16
+
17
+ // --- Standards signals ---
18
+ const stdValues = Object.values(standards).filter((v): v is number => v !== null);
19
+ const avgStandard =
20
+ stdValues.length > 0 ? stdValues.reduce((a, b) => a + b, 0) / stdValues.length : 3;
21
+
22
+ if (avgStandard >= 4.2) {
23
+ scores['the-strategic-dater'] += 3;
24
+ scores['the-thoughtful-evaluator'] += 2;
25
+ } else if (avgStandard >= 3.5) {
26
+ scores['the-hopeful-romantic'] += 3;
27
+ scores['the-intentional-builder'] += 1;
28
+ } else if (avgStandard <= 2.5) {
29
+ scores['the-independent-chooser'] += 3;
30
+ scores['the-compassionate-partner'] += 2;
31
+ }
32
+
33
+ const emotionalAvg =
34
+ ((standards.emotionalStability ?? 3) + (standards.emotionalAvailability ?? 3)) / 2;
35
+ if (emotionalAvg >= 4.5) {
36
+ scores['the-thoughtful-evaluator'] += 2;
37
+ }
38
+
39
+ const effortAvg =
40
+ ((standards.initiativeAndEffort ?? 3) + (standards.followThrough ?? 3)) / 2;
41
+ if (effortAvg >= 4.5) {
42
+ scores['the-intentional-builder'] += 2;
43
+ scores['the-strategic-dater'] += 1;
44
+ }
45
+
46
+ // --- Timeline signals ---
47
+ const pace = timelineAnswers?.progressionPace;
48
+ if (pace === 'Fast and intentional') {
49
+ scores['the-compassionate-partner'] += 3;
50
+ scores['the-strategic-dater'] += 1;
51
+ } else if (pace === 'Structured and steady') {
52
+ scores['the-strategic-dater'] += 3;
53
+ scores['the-intentional-builder'] += 1;
54
+ } else if (pace === 'Gradual over time') {
55
+ scores['the-hopeful-romantic'] += 2;
56
+ scores['the-thoughtful-evaluator'] += 1;
57
+ } else if (pace === 'Undefined / organic') {
58
+ scores['the-independent-chooser'] += 4;
59
+ }
60
+
61
+ const exclusivity = timelineAnswers?.exclusivityExpectation;
62
+ if (exclusivity === '3–5 dates') {
63
+ scores['the-compassionate-partner'] += 2;
64
+ scores['the-strategic-dater'] += 1;
65
+ } else if (exclusivity === 'No fixed expectation') {
66
+ scores['the-independent-chooser'] += 3;
67
+ } else if (exclusivity === '2 months' || exclusivity === '3 months') {
68
+ scores['the-thoughtful-evaluator'] += 2;
69
+ scores['the-intentional-builder'] += 1;
70
+ }
71
+
72
+ const commCadence = timelineAnswers?.communicationCadence;
73
+ if (commCadence === 'Daily') {
74
+ scores['the-compassionate-partner'] += 2;
75
+ } else if (commCadence === 'Only when planning') {
76
+ scores['the-independent-chooser'] += 2;
77
+ scores['the-thoughtful-evaluator'] += 1;
78
+ }
79
+
80
+ // --- Enforcement signals ---
81
+ const cancelDowngrade = enforcementAnswers?.cancelDowngrade;
82
+ if (cancelDowngrade === '1 time') {
83
+ scores['the-strategic-dater'] += 3;
84
+ scores['the-thoughtful-evaluator'] += 2;
85
+ } else if (cancelDowngrade === 'I rarely downgrade') {
86
+ scores['the-compassionate-partner'] += 2;
87
+ scores['the-independent-chooser'] += 2;
88
+ } else if (cancelDowngrade === 'Only if it becomes a pattern') {
89
+ scores['the-hopeful-romantic'] += 2;
90
+ }
91
+
92
+ const redFlagSpeed = enforcementAnswers?.redFlagSpeed;
93
+ if (redFlagSpeed === 'Immediately') {
94
+ scores['the-strategic-dater'] += 2;
95
+ scores['the-thoughtful-evaluator'] += 3;
96
+ } else if (redFlagSpeed === 'After confirmation') {
97
+ scores['the-intentional-builder'] += 3;
98
+ } else if (redFlagSpeed === 'Only if severe') {
99
+ scores['the-compassionate-partner'] += 2;
100
+ scores['the-independent-chooser'] += 1;
101
+ }
102
+
103
+ const disappointment = enforcementAnswers?.disappointmentResponse;
104
+ if (disappointment === 'Address directly') {
105
+ scores['the-strategic-dater'] += 2;
106
+ scores['the-hopeful-romantic'] += 1;
107
+ } else if (disappointment === 'Withdraw') {
108
+ scores['the-thoughtful-evaluator'] += 3;
109
+ } else if (disappointment === 'Increase effort') {
110
+ scores['the-compassionate-partner'] += 3;
111
+ } else if (disappointment === 'Overthink') {
112
+ scores['the-intentional-builder'] += 2;
113
+ } else if (disappointment === 'Reduce investment') {
114
+ scores['the-independent-chooser'] += 2;
115
+ }
116
+
117
+ // --- Mindset signals ---
118
+ if (mindsetAnswers) {
119
+ const stdConf = mindsetAnswers.standardsConfidence
120
+ ? Number(mindsetAnswers.standardsConfidence)
121
+ : null;
122
+ if (stdConf !== null) {
123
+ if (stdConf >= 4) {
124
+ scores['the-strategic-dater'] += 2;
125
+ scores['the-thoughtful-evaluator'] += 1;
126
+ } else if (stdConf <= 2) {
127
+ scores['the-compassionate-partner'] += 2;
128
+ scores['the-independent-chooser'] += 1;
129
+ }
130
+ }
131
+
132
+ const effort = mindsetAnswers.effortInterpretation;
133
+ if (effort === "Maybe I'm expecting too much") {
134
+ scores['the-compassionate-partner'] += 2;
135
+ } else if (effort === 'I notice the shift and start evaluating the situation') {
136
+ scores['the-intentional-builder'] += 2;
137
+ scores['the-strategic-dater'] += 1;
138
+ } else if (effort === 'I lose interest quickly') {
139
+ scores['the-thoughtful-evaluator'] += 2;
140
+ }
141
+
142
+ const behAdj = mindsetAnswers.behavioralAdjustment
143
+ ? Number(mindsetAnswers.behavioralAdjustment)
144
+ : null;
145
+ if (behAdj !== null) {
146
+ if (behAdj <= 2) {
147
+ scores['the-compassionate-partner'] += 2;
148
+ } else if (behAdj >= 4) {
149
+ scores['the-strategic-dater'] += 1;
150
+ scores['the-thoughtful-evaluator'] += 1;
151
+ }
152
+ }
153
+
154
+ const scarcity = mindsetAnswers.scarcityMindset
155
+ ? Number(mindsetAnswers.scarcityMindset)
156
+ : null;
157
+ if (scarcity !== null) {
158
+ if (scarcity <= 2) {
159
+ scores['the-compassionate-partner'] += 2;
160
+ scores['the-hopeful-romantic'] += 1;
161
+ } else if (scarcity >= 4) {
162
+ scores['the-independent-chooser'] += 2;
163
+ scores['the-strategic-dater'] += 1;
164
+ }
165
+ }
166
+
167
+ const ambiguity = mindsetAnswers.ambiguityTolerance;
168
+ if (
169
+ ambiguity ===
170
+ 'I start replaying conversations and wondering what I might have done wrong'
171
+ ) {
172
+ scores['the-compassionate-partner'] += 2;
173
+ scores['the-intentional-builder'] += 1;
174
+ } else if (
175
+ ambiguity ===
176
+ 'I assume they may be losing interest and begin worrying about where things stand'
177
+ ) {
178
+ scores['the-hopeful-romantic'] += 1;
179
+ scores['the-intentional-builder'] += 1;
180
+ } else if (
181
+ ambiguity === 'I notice the shift and start paying closer attention to their behavior'
182
+ ) {
183
+ scores['the-intentional-builder'] += 2;
184
+ } else if (
185
+ ambiguity === "I don't think about it much unless the pattern continues"
186
+ ) {
187
+ scores['the-independent-chooser'] += 2;
188
+ scores['the-thoughtful-evaluator'] += 1;
189
+ }
190
+
191
+ const investSpeed = mindsetAnswers.emotionalInvestmentSpeed;
192
+ if (investSpeed === 'Very quickly') {
193
+ scores['the-compassionate-partner'] += 3;
194
+ } else if (investSpeed === 'Somewhat quickly') {
195
+ scores['the-compassionate-partner'] += 1;
196
+ scores['the-hopeful-romantic'] += 1;
197
+ } else if (investSpeed === 'Gradually over time') {
198
+ scores['the-hopeful-romantic'] += 1;
199
+ scores['the-intentional-builder'] += 1;
200
+ } else if (investSpeed === 'Slowly and cautiously') {
201
+ scores['the-thoughtful-evaluator'] += 2;
202
+ scores['the-strategic-dater'] += 1;
203
+ }
204
+
205
+ const fearLoss = mindsetAnswers.fearOfLosingConnection
206
+ ? Number(mindsetAnswers.fearOfLosingConnection)
207
+ : null;
208
+ if (fearLoss !== null) {
209
+ if (fearLoss <= 2) {
210
+ scores['the-compassionate-partner'] += 2;
211
+ } else if (fearLoss >= 4) {
212
+ scores['the-thoughtful-evaluator'] += 1;
213
+ scores['the-independent-chooser'] += 1;
214
+ }
215
+ }
216
+
217
+ const fulfillment = mindsetAnswers.fulfillmentOutsideDating
218
+ ? Number(mindsetAnswers.fulfillmentOutsideDating)
219
+ : null;
220
+ if (fulfillment !== null) {
221
+ if (fulfillment <= 2) {
222
+ scores['the-compassionate-partner'] += 2;
223
+ } else if (fulfillment >= 4) {
224
+ scores['the-independent-chooser'] += 2;
225
+ scores['the-strategic-dater'] += 1;
226
+ }
227
+ }
228
+
229
+ const validation = mindsetAnswers.validationSensitivity
230
+ ? Number(mindsetAnswers.validationSensitivity)
231
+ : null;
232
+ if (validation !== null) {
233
+ if (validation <= 2) {
234
+ scores['the-compassionate-partner'] += 2;
235
+ scores['the-hopeful-romantic'] += 1;
236
+ } else if (validation >= 4) {
237
+ scores['the-thoughtful-evaluator'] += 1;
238
+ scores['the-strategic-dater'] += 1;
239
+ }
240
+ }
241
+
242
+ const relRole = mindsetAnswers.relationshipRole;
243
+ if (relRole === 'It would enhance a life that already feels full and satisfying') {
244
+ scores['the-strategic-dater'] += 2;
245
+ scores['the-independent-chooser'] += 1;
246
+ } else if (
247
+ relRole === 'It would add companionship and shared experiences to my life'
248
+ ) {
249
+ scores['the-hopeful-romantic'] += 2;
250
+ } else if (relRole === 'It would fill a gap I currently feel in my life') {
251
+ scores['the-compassionate-partner'] += 2;
252
+ } else if (
253
+ relRole === 'It would make me feel more secure, valued, or "chosen"'
254
+ ) {
255
+ scores['the-compassionate-partner'] += 3;
256
+ }
257
+ }
258
+
259
+ // --- Trade-off signals ---
260
+ const chemistry = tradeOffAnswers?.consistencyVsChemistry;
261
+ if (chemistry === 'Strong chemistry even if effort is inconsistent') {
262
+ scores['the-compassionate-partner'] += 3;
263
+ } else {
264
+ scores['the-strategic-dater'] += 2;
265
+ scores['the-intentional-builder'] += 1;
266
+ }
267
+
268
+ const stability = tradeOffAnswers?.stabilityVsIntensity;
269
+ if (stability === 'Deeply expressive and intense') {
270
+ scores['the-compassionate-partner'] += 2;
271
+ scores['the-hopeful-romantic'] += 1;
272
+ } else {
273
+ scores['the-thoughtful-evaluator'] += 2;
274
+ scores['the-strategic-dater'] += 1;
275
+ }
276
+
277
+ const independence = tradeOffAnswers?.independenceVsIntegration;
278
+ if (independence === 'Strong independence') {
279
+ scores['the-independent-chooser'] += 3;
280
+ scores['the-thoughtful-evaluator'] += 1;
281
+ } else {
282
+ scores['the-compassionate-partner'] += 2;
283
+ scores['the-hopeful-romantic'] += 1;
284
+ }
285
+
286
+ const pastPattern = tradeOffAnswers?.pastPattern;
287
+ if (pastPattern === 'Choosing potential over proof') {
288
+ scores['the-compassionate-partner'] += 2;
289
+ scores['the-hopeful-romantic'] += 1;
290
+ } else if (pastPattern === 'Staying too long') {
291
+ scores['the-hopeful-romantic'] += 2;
292
+ scores['the-thoughtful-evaluator'] += 1;
293
+ } else if (pastPattern === 'Leaving too early') {
294
+ scores['the-thoughtful-evaluator'] += 2;
295
+ scores['the-strategic-dater'] += 1;
296
+ } else if (pastPattern === 'Over-investing early') {
297
+ scores['the-compassionate-partner'] += 3;
298
+ }
299
+
300
+ const earlyDating = tradeOffAnswers?.earlyDatingTendency;
301
+ if (earlyDating === 'Focus on one person quickly') {
302
+ scores['the-compassionate-partner'] += 2;
303
+ } else if (earlyDating === 'Keep multiple options') {
304
+ scores['the-strategic-dater'] += 2;
305
+ scores['the-intentional-builder'] += 1;
306
+ } else if (earlyDating === 'Get attached fast') {
307
+ scores['the-compassionate-partner'] += 3;
308
+ } else if (earlyDating === 'Stay emotionally guarded') {
309
+ scores['the-thoughtful-evaluator'] += 3;
310
+ }
311
+
312
+ const attractionKiller = tradeOffAnswers?.attractionKiller;
313
+ if (attractionKiller === 'Passive energy') {
314
+ scores['the-compassionate-partner'] += 1;
315
+ scores['the-strategic-dater'] += 1;
316
+ } else if (attractionKiller === 'Hot and cold behavior') {
317
+ scores['the-intentional-builder'] += 2;
318
+ scores['the-thoughtful-evaluator'] += 1;
319
+ } else if (attractionKiller === 'Avoiding commitment talks') {
320
+ scores['the-strategic-dater'] += 2;
321
+ scores['the-hopeful-romantic'] += 1;
322
+ }
323
+
324
+ // Find highest scoring archetype
325
+ let maxScore = 0;
326
+ let archetypeId = 'the-hopeful-romantic'; // default fallback
327
+
328
+ for (const [id, score] of Object.entries(scores)) {
329
+ if (score > maxScore) {
330
+ maxScore = score;
331
+ archetypeId = id;
332
+ }
333
+ }
334
+
335
+ return DATING_ARCHETYPES.find((a) => a.id === archetypeId) ?? DATING_ARCHETYPES[1];
336
+ }