@naniteninja/profile-comparison-lib 1.0.3 → 1.0.4

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.
@@ -1,24 +1,54 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, Injectable, Optional, Inject, EventEmitter, ViewChild, Output, Input, Component, NgModule } from '@angular/core';
3
- import { switchMap, map, catchError, shareReplay, tap as tap$1 } from 'rxjs/operators';
4
- import { from, throwError, of, forkJoin, defer, switchMap as switchMap$1, map as map$1, Observable, BehaviorSubject, tap, retryWhen, concatMap, timer, catchError as catchError$1 } from 'rxjs';
5
- import * as use from '@tensorflow-models/universal-sentence-encoder';
6
- import '@tensorflow/tfjs';
2
+ import { InjectionToken, Injectable, Optional, Inject, inject, EventEmitter, ViewChild, Output, Input, Component, NgModule } from '@angular/core';
3
+ import { switchMap as switchMap$1, map as map$1, catchError, tap as tap$1 } from 'rxjs/operators';
4
+ import { from, switchMap, map, of, Observable, BehaviorSubject, throwError, tap, retryWhen, concatMap, timer, forkJoin, catchError as catchError$1 } from 'rxjs';
7
5
  import * as i1 from '@angular/common/http';
8
- import { HttpHeaders, HttpClientModule } from '@angular/common/http';
6
+ import { HttpHeaders, HttpClient, HttpClientModule } from '@angular/common/http';
9
7
  import * as i2 from '@angular/common';
10
8
  import { CommonModule } from '@angular/common';
11
9
  import { FormsModule, ReactiveFormsModule } from '@angular/forms';
12
10
 
11
+ /** Backend mode for the profile comparison component. */
12
+ var BackendMode;
13
+ (function (BackendMode) {
14
+ /** Use the real deployed backend (default). */
15
+ BackendMode[BackendMode["Real"] = 0] = "Real";
16
+ /** Use a local mock server at localhost:3000. */
17
+ BackendMode[BackendMode["Mock"] = 1] = "Mock";
18
+ })(BackendMode || (BackendMode = {}));
19
+ const BACKEND_MODE_URLS = {
20
+ [BackendMode.Real]: 'https://api.test.thecyrano.app/api/profile-comparison/api',
21
+ [BackendMode.Mock]: 'http://localhost:3000/api',
22
+ };
13
23
  /**
14
- * When provided, ProfileService and OpenAIEmbeddingService call this backend base URL
15
- * (e.g. 'http://localhost:3000/api') for all profile/embedding endpoints instead of
16
- * calling external APIs directly with keys. Can be a string or a getter function for runtime toggle.
17
- * When not provided or getter returns null, the lib uses the direct path with keys.
24
+ * Legacy injection token for the backend base URL.
25
+ * Prefer using the [backendMode] or [backendUrl] @Input on the component instead.
26
+ * Kept for backward compatibility lowest priority when resolving the URL.
18
27
  */
19
28
  const PROFILE_COMPARISON_API_BASE_URL = new InjectionToken('PROFILE_COMPARISON_API_BASE_URL');
20
29
  /** Optional getter: when true, lib and backend service log steps to console for diagnosis. */
21
30
  const PROFILE_COMPARISON_VERBOSE_LOGGING = new InjectionToken('PROFILE_COMPARISON_VERBOSE_LOGGING');
31
+ /**
32
+ * Resolves the active backend URL from inputs, with fallback to injection token.
33
+ *
34
+ * Priority:
35
+ * 1. Explicit backendUrl (custom URL string)
36
+ * 2. BackendMode enum (Real or Mock)
37
+ * 3. Legacy PROFILE_COMPARISON_API_BASE_URL token
38
+ */
39
+ function resolveBackendUrl(backendUrl, backendMode, legacyTokenUrl) {
40
+ if (backendUrl != null && backendUrl.trim().length > 0) {
41
+ return backendUrl.trim();
42
+ }
43
+ const modeUrl = BACKEND_MODE_URLS[backendMode];
44
+ if (modeUrl) {
45
+ return modeUrl;
46
+ }
47
+ if (legacyTokenUrl != null && legacyTokenUrl.trim().length > 0) {
48
+ return legacyTokenUrl.trim();
49
+ }
50
+ return null;
51
+ }
22
52
 
23
53
  const DB_NAME = 'profile-comparison-cache';
24
54
  const DB_VERSION = 1;
@@ -180,780 +210,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
180
210
  args: [{ providedIn: 'root' }]
181
211
  }] });
182
212
 
183
- class EmbeddingService {
184
- model$ = null;
185
- // Get embedding for a single text (returns Observable)
186
- getEmbedding(text) {
187
- return this.loadModel().pipe(switchMap((model) => from(model.embed([text]))), switchMap((embeddings) => {
188
- const t2d = embeddings;
189
- return from(t2d.array()).pipe(map((arr) => {
190
- t2d.dispose();
191
- return Float32Array.from(arr[0]);
192
- }));
193
- }), catchError((err) => throwError(() => new Error('Model failed to load or embed: ' + err))));
194
- }
195
- /**
196
- * Group-align two lists based on semantic similarity, allowing one-to-many relationships.
197
- *
198
- * Design goals:
199
- * - Semantic relevance takes priority over original order or spacing.
200
- * - Each term in listB is assigned to the single most similar anchor in listA (if above threshold).
201
- * - For an anchor in listA with multiple highly related listB terms, they are emitted as consecutive rows:
202
- * [A, B1], ['-', B2], ['-', B3], ... so they render adjacently and remain visually connected.
203
- * - Low-similarity/unassigned terms from either side are appended at the end and never break groups.
204
- */
205
- groupAlignLists(listA, listB, threshold = 0.25, preserveLeftOrder = true, exclusivityMargin = 0.06, leftCohesion = 0.23) {
206
- // Short-circuit simple cases
207
- if ((!listA || listA.length === 0) && (!listB || listB.length === 0)) {
208
- return of([]);
209
- }
210
- if (!listA || listA.length === 0) {
211
- return of(listB.map((b) => ({ left: '-', right: b, score: 0 })));
212
- }
213
- if (!listB || listB.length === 0) {
214
- return of(listA.map((a) => ({ left: a, right: '-', score: 0 })));
215
- }
216
- return this.loadModel().pipe(switchMap((model) => forkJoin([
217
- from(model.embed(listA)),
218
- from(model.embed(listB))
219
- ]).pipe(switchMap(([tA, tB]) => {
220
- return forkJoin([from(tA.array()), from(tB.array())]).pipe(map((tensorArrays) => {
221
- const [arrA, arrB] = tensorArrays;
222
- // Dispose tensors to avoid memory leaks
223
- tA.dispose();
224
- tB.dispose();
225
- const aCount = listA.length;
226
- const bCount = listB.length;
227
- // Pre-convert to Float32Array for faster cosine computations
228
- const vecA = new Array(aCount);
229
- const vecB = new Array(bCount);
230
- for (let i = 0; i < aCount; i++)
231
- vecA[i] = Float32Array.from(arrA[i]);
232
- for (let j = 0; j < bCount; j++)
233
- vecB[j] = Float32Array.from(arrB[j]);
234
- // Build full cross-list similarity matrix S[i][j] = sim(A_i, B_j)
235
- const S = new Array(aCount);
236
- for (let i = 0; i < aCount; i++) {
237
- const row = new Array(bCount);
238
- for (let j = 0; j < bCount; j++) {
239
- const emb = this.cosineSimilarity(vecA[i], vecB[j]);
240
- const lex = this.computeLexicalBoost(listA[i], listB[j]);
241
- // Apply domain gating to prevent mismatched category-vs-activity alignments
242
- const cap = this.domainCompatibilityCap(listA[i], listB[j]);
243
- row[j] = Math.min(Math.max(emb, lex), cap);
244
- }
245
- S[i] = row;
246
- }
247
- // Build within-list similarity for A (A↔A) to allow left-side extras to attach to cohesive anchors.
248
- // We use the same hybrid semantic/lexical metric.
249
- const SAA = new Array(aCount);
250
- for (let i = 0; i < aCount; i++) {
251
- const rowAA = new Array(aCount);
252
- for (let k = 0; k < aCount; k++) {
253
- if (i === k) {
254
- rowAA[k] = 1; // self
255
- }
256
- else {
257
- const embAA = this.cosineSimilarity(vecA[i], vecA[k]);
258
- const lexAA = this.computeLexicalBoost(listA[i], listA[k]);
259
- rowAA[k] = Math.max(embAA, lexAA);
260
- }
261
- }
262
- SAA[i] = rowAA;
263
- }
264
- // Build thresholded neighborhoods
265
- const neighborsA = new Map();
266
- const neighborsB = new Map();
267
- for (let i = 0; i < aCount; i++) {
268
- const row = [];
269
- for (let j = 0; j < bCount; j++) {
270
- const s = S[i][j];
271
- if (s >= threshold) {
272
- row.push({ j, score: s });
273
- const rev = neighborsB.get(j) || [];
274
- rev.push({ i, score: s });
275
- neighborsB.set(j, rev);
276
- }
277
- }
278
- // sort descending for deterministic preference lists
279
- row.sort((a, b) => b.score - a.score);
280
- if (row.length)
281
- neighborsA.set(i, row);
282
- }
283
- // Identify exclusive vs ambiguous B nodes
284
- const exclusiveForA = new Map();
285
- const ambiguousB = [];
286
- const unassignedB = [];
287
- for (let j = 0; j < bCount; j++) {
288
- const list = (neighborsB.get(j) || []).sort((a, b) => b.score - a.score);
289
- if (list.length === 0) {
290
- unassignedB.push(j);
291
- }
292
- else if (list.length === 1) {
293
- const i = list[0].i;
294
- const arr = exclusiveForA.get(i) || [];
295
- arr.push({ j, score: list[0].score });
296
- exclusiveForA.set(i, arr);
297
- }
298
- else {
299
- // If best is significantly better than second-best, treat as exclusive to best
300
- const best = list[0];
301
- const second = list[1];
302
- if (best.score - second.score >= exclusivityMargin) {
303
- const arr = exclusiveForA.get(best.i) || [];
304
- arr.push({ j, score: best.score });
305
- exclusiveForA.set(best.i, arr);
306
- }
307
- else {
308
- ambiguousB.push(j);
309
- }
310
- }
311
- }
312
- // A-side exclusivity: if an anchor A has a clearly dominant best B, prefer that exclusive pairing.
313
- const bOwner = new Map();
314
- for (const [i, arr] of exclusiveForA.entries()) {
315
- for (const x of arr) {
316
- const ex = bOwner.get(x.j);
317
- if (!ex || x.score > ex.score)
318
- bOwner.set(x.j, { i, score: x.score });
319
- }
320
- }
321
- const ambSetForA = new Set(ambiguousB);
322
- for (let i = 0; i < aCount; i++) {
323
- const list = (neighborsA.get(i) || [])
324
- .slice()
325
- .sort((a, b) => b.score - a.score);
326
- if (!list.length)
327
- continue;
328
- const top = list[0];
329
- const second = list[1];
330
- const hasClearLead = !second || top.score - second.score >= exclusivityMargin;
331
- if (top.score >= threshold && hasClearLead) {
332
- const current = bOwner.get(top.j);
333
- if (!current || top.score > current.score + 1e-6) {
334
- // Reassign this B exclusively to A=i
335
- // Remove from any previous exclusiveForA groups
336
- for (const [ai, arr] of exclusiveForA.entries()) {
337
- exclusiveForA.set(ai, arr.filter((x) => x.j !== top.j));
338
- }
339
- const arr = exclusiveForA.get(i) || [];
340
- if (!arr.some((x) => x.j === top.j))
341
- arr.push({ j: top.j, score: top.score });
342
- arr.sort((u, v) => v.score - u.score);
343
- exclusiveForA.set(i, arr);
344
- bOwner.set(top.j, { i, score: top.score });
345
- ambSetForA.delete(top.j);
346
- }
347
- }
348
- }
349
- ambiguousB.splice(0, ambiguousB.length, ...Array.from(ambSetForA.values()));
350
- // Mutual-best pairing stage (within small tolerance) to prioritize exclusive high-similarity pairs
351
- const mutualTolerance = 0.03;
352
- const bestBForA = new Array(aCount);
353
- for (let i = 0; i < aCount; i++) {
354
- let bBest = -1;
355
- let bBestScore = -Infinity;
356
- for (let j = 0; j < bCount; j++) {
357
- const s = S[i][j];
358
- if (s > bBestScore) {
359
- bBestScore = s;
360
- bBest = j;
361
- }
362
- }
363
- bestBForA[i] = { j: bBest, score: bBestScore };
364
- }
365
- const bestAForB = new Array(bCount);
366
- for (let j = 0; j < bCount; j++) {
367
- let aBest = -1;
368
- let aBestScore = -Infinity;
369
- for (let i = 0; i < aCount; i++) {
370
- const s = S[i][j];
371
- if (s > aBestScore) {
372
- aBestScore = s;
373
- aBest = i;
374
- }
375
- }
376
- bestAForB[j] = { i: aBest, score: aBestScore };
377
- }
378
- // Promote mutual-best pairs to exclusive, removing from ambiguous list if present
379
- const ambSet = new Set(ambiguousB);
380
- for (let i = 0; i < aCount; i++) {
381
- const b = bestBForA[i].j;
382
- if (b >= 0 && S[i][b] >= threshold) {
383
- const a = bestAForB[b].i;
384
- const score = S[i][b];
385
- // within tolerance of best on both sides
386
- const isWithinA = score >= bestBForA[i].score - mutualTolerance;
387
- const isWithinB = score >= bestAForB[b].score - mutualTolerance;
388
- if (a === i && isWithinA && isWithinB) {
389
- // Remove from any previous exclusive lists under other anchors
390
- for (const [ai, arr] of exclusiveForA.entries()) {
391
- exclusiveForA.set(ai, arr.filter((x) => x.j !== b));
392
- }
393
- // Add as exclusive to this anchor
394
- const arr = exclusiveForA.get(i) || [];
395
- // Avoid duplicate insert
396
- if (!arr.some((x) => x.j === b)) {
397
- arr.push({ j: b, score });
398
- arr.sort((u, v) => v.score - u.score);
399
- exclusiveForA.set(i, arr);
400
- }
401
- // Remove from ambiguous set
402
- ambSet.delete(b);
403
- }
404
- }
405
- }
406
- ambiguousB.splice(0, ambiguousB.length, ...Array.from(ambSet.values()));
407
- // Build symmetric anchor heads from mutual-best pairs (above threshold)
408
- const anchorHeads = new Map(); // A_i -> head B_j
409
- for (let i = 0; i < aCount; i++) {
410
- const b = bestBForA[i].j;
411
- if (b >= 0) {
412
- const score = S[i][b];
413
- const isWithinA = score >= bestBForA[i].score - mutualTolerance;
414
- const isWithinB = score >= bestAForB[b].score - mutualTolerance;
415
- if (bestAForB[b].i === i &&
416
- score >= threshold &&
417
- isWithinA &&
418
- isWithinB) {
419
- anchorHeads.set(i, b);
420
- }
421
- }
422
- }
423
- // Resolve ambiguous many-to-many by B-centric greedy assignment:
424
- // each ambiguous B chooses its single most similar A (enables one-to-many on A side).
425
- const matchedAmbigForA = new Map();
426
- for (const j of ambiguousB) {
427
- let bestI = -1;
428
- let bestScore = -Infinity;
429
- for (let i = 0; i < aCount; i++) {
430
- const s = S[i][j];
431
- if (s > bestScore) {
432
- bestScore = s;
433
- bestI = i;
434
- }
435
- }
436
- if (bestI >= 0 && bestScore >= threshold) {
437
- const arr = matchedAmbigForA.get(bestI) || [];
438
- arr.push({ j, score: bestScore });
439
- matchedAmbigForA.set(bestI, arr);
440
- }
441
- }
442
- // Merge exclusive and matched ambiguous into final assignments per A
443
- const assignedToA = new Map();
444
- for (let i = 0; i < aCount; i++) {
445
- const list = [];
446
- const ex = exclusiveForA.get(i) || [];
447
- for (const x of ex)
448
- list.push(x);
449
- const amb = matchedAmbigForA.get(i) || [];
450
- for (const x of amb)
451
- list.push(x);
452
- if (list.length) {
453
- list.sort((a, b) => b.score - a.score);
454
- assignedToA.set(i, list);
455
- }
456
- }
457
- // Promote anchors for left items that have strong assigned matches even if
458
- // they were not mutual-best. This guarantees group creation around
459
- // semantically coherent anchors such as Fish, so items like Clams attach there.
460
- for (const [i, lst] of assignedToA.entries()) {
461
- if (!anchorHeads.has(i) && lst.length) {
462
- anchorHeads.set(i, lst[0].j); // head = top assigned right item
463
- }
464
- }
465
- // Track which B have been assigned (exclusive or ambiguous)
466
- const assignedBSet = new Set();
467
- for (const arr of assignedToA.values()) {
468
- for (const x of arr)
469
- assignedBSet.add(x.j);
470
- }
471
- // Also mark mutual-best anchor head B's as assigned so they are not appended as leftovers
472
- // even if they weren't already in assignedToA for their group.
473
- for (const [ai, headJ] of anchorHeads.entries()) {
474
- assignedBSet.add(headJ);
475
- }
476
- // Any B not assigned (and not a head) goes to the bottom unassigned list
477
- const leftoverB = [];
478
- for (let j = 0; j < bCount; j++) {
479
- if (!assignedBSet.has(j))
480
- leftoverB.push(j);
481
- }
482
- // Determine ranking for each A_i.
483
- // Priority 1: anchors with at least one assigned B (highly related) go to the TOP.
484
- // Priority 2: among them, sort by descending top assigned similarity.
485
- // Anchors with NO assignment are pushed to the BOTTOM and sorted by their best potential similarity.
486
- const rankA = [];
487
- for (let i = 0; i < aCount; i++) {
488
- const assigned = assignedToA.get(i) || [];
489
- let top = 0;
490
- if (assigned.length > 0) {
491
- top = assigned.reduce((m, x) => x.score > m ? x.score : m, 0);
492
- }
493
- else {
494
- // If nothing assigned, consider the best potential similarity (even if below threshold)
495
- let best = 0;
496
- for (let j = 0; j < bCount; j++)
497
- best = Math.max(best, S[i][j]);
498
- top = best;
499
- }
500
- rankA.push({
501
- i,
502
- score: top,
503
- hasAssignment: assigned.length > 0,
504
- });
505
- }
506
- rankA.sort((x, y) => Number(y.hasAssignment) - Number(x.hasAssignment) ||
507
- y.score - x.score);
508
- // Within each group, sort assigned B by descending similarity to the anchor
509
- for (const [i, list] of assignedToA.entries()) {
510
- list.sort((a, b) => b.score - a.score);
511
- assignedToA.set(i, list);
512
- }
513
- // Determine anchor groups strictly from symmetric mutual-best pairs
514
- const anchorSet = new Set(anchorHeads.keys());
515
- // Early-out: if no anchors (no cross-list matches above threshold),
516
- // fall back to simple left-only rows followed by right-only rows
517
- if (anchorSet.size === 0) {
518
- const rowsNoAnchors = [];
519
- for (let i = 0; i < aCount; i++)
520
- rowsNoAnchors.push({
521
- left: listA[i],
522
- right: '-',
523
- score: 0,
524
- });
525
- for (const j of leftoverB)
526
- rowsNoAnchors.push({
527
- left: '-',
528
- right: listB[j],
529
- score: 0,
530
- });
531
- return rowsNoAnchors;
532
- }
533
- // Map each B to its owning anchor A for quick lookup (based on assignedToA)
534
- const ownerOfB = new Array(bCount).fill(-1);
535
- for (const [ai, lst] of assignedToA.entries()) {
536
- for (const x of lst)
537
- ownerOfB[x.j] = ai;
538
- }
539
- const leftUsed = new Set();
540
- // Mark anchors as used so they won't be re-assigned as extras
541
- for (const ai of anchorSet.values())
542
- leftUsed.add(ai);
543
- // Pre-bucket extras: group -> (right j within that group) -> extras[]
544
- const extrasByGroup = new Map();
545
- for (let k = 0; k < aCount; k++) {
546
- if (leftUsed.has(k))
547
- continue; // skip anchors
548
- let bestGroup = -1;
549
- let bestRightJ = -1;
550
- let bestScore = -Infinity;
551
- // Evaluate affinity against each anchor group
552
- for (const ai of anchorSet.values()) {
553
- const groupList = assignedToA.get(ai) || [];
554
- // Ensure the head B is present in the consideration list
555
- const headJ = anchorHeads.get(ai);
556
- const listWithHead = groupList.some((x) => x.j === headJ)
557
- ? groupList
558
- : [{ j: headJ, score: S[ai][headJ] }, ...groupList];
559
- // Best cross-list similarity to any right item under this anchor
560
- let localBestJ = -1;
561
- let localBestCross = -Infinity;
562
- for (const x of listWithHead) {
563
- const s = S[k][x.j];
564
- if (s > localBestCross) {
565
- localBestCross = s;
566
- localBestJ = x.j;
567
- }
568
- }
569
- // Within-list cohesion to the anchor left word
570
- const withinAA = SAA[k][ai];
571
- const combined = Math.max(localBestCross, withinAA);
572
- if (combined > bestScore) {
573
- bestScore = combined;
574
- bestGroup = ai;
575
- // Prefer the concrete right target when cross-list is stronger;
576
- // otherwise fall back to the group's top right assignment.
577
- bestRightJ =
578
- localBestCross >= withinAA ? localBestJ : headJ;
579
- }
580
- }
581
- // Attach as extra if cohesive enough to some group
582
- if (bestGroup >= 0 &&
583
- bestRightJ >= 0 &&
584
- bestScore >= leftCohesion) {
585
- let byRight = extrasByGroup.get(bestGroup);
586
- if (!byRight) {
587
- byRight = new Map();
588
- extrasByGroup.set(bestGroup, byRight);
589
- }
590
- const arr = byRight.get(bestRightJ) || [];
591
- arr.push({ k, jPref: bestRightJ, score: bestScore });
592
- byRight.set(bestRightJ, arr);
593
- leftUsed.add(k);
594
- }
595
- }
596
- // Build grouped rows with left extras rendered directly under the specific right item
597
- // they most strongly match. This ensures highly related words appear exactly below their
598
- // most similar counterpart on the other side.
599
- const rows = [];
600
- const anchorIndices = Array.from(anchorSet.values());
601
- const orderIndices = preserveLeftOrder
602
- ? anchorIndices.sort((x, y) => x - y)
603
- : anchorIndices.sort((i1, i2) => {
604
- const j1 = anchorHeads.get(i1);
605
- const j2 = anchorHeads.get(i2);
606
- const diff = S[i2][j2] - S[i1][j1];
607
- if (Math.abs(diff) > 1e-9)
608
- return diff;
609
- // Side-invariant tie-breaker using symmetric pair key
610
- const key1 = [
611
- listA[i1].toLowerCase(),
612
- listB[j1].toLowerCase(),
613
- ]
614
- .sort()
615
- .join('|');
616
- const key2 = [
617
- listA[i2].toLowerCase(),
618
- listB[j2].toLowerCase(),
619
- ]
620
- .sort()
621
- .join('|');
622
- return key1.localeCompare(key2);
623
- });
624
- for (const i of orderIndices) {
625
- const groupOrig = assignedToA.get(i) || [];
626
- const headJ = anchorHeads.get(i);
627
- // Build group with head first, then others by score
628
- const others = groupOrig
629
- .filter((x) => x.j !== headJ)
630
- .sort((a, b) => {
631
- const d = b.score - a.score;
632
- if (Math.abs(d) > 1e-9)
633
- return d;
634
- return listB[a.j]
635
- .toLowerCase()
636
- .localeCompare(listB[b.j].toLowerCase());
637
- });
638
- const group = [{ j: headJ, score: S[i][headJ] }, ...others];
639
- // First row uses the anchor head pair
640
- const first = group[0];
641
- rows.push({
642
- left: listA[i],
643
- right: listB[first.j],
644
- score: first.score,
645
- });
646
- // Render left extras that prefer this first right item
647
- const byRight = extrasByGroup.get(i);
648
- const extrasForFirst = (byRight && byRight.get(first.j)) || [];
649
- if (extrasForFirst.length) {
650
- extrasForFirst.sort((e1, e2) => {
651
- const s1 = Math.max(S[e1.k][first.j], SAA[e1.k][i]);
652
- const s2 = Math.max(S[e2.k][first.j], SAA[e2.k][i]);
653
- const d = s2 - s1;
654
- if (Math.abs(d) > 1e-9)
655
- return d;
656
- return listA[e1.k]
657
- .toLowerCase()
658
- .localeCompare(listA[e2.k].toLowerCase());
659
- });
660
- for (const ex of extrasForFirst) {
661
- rows.push({
662
- left: listA[ex.k],
663
- right: '-',
664
- score: Math.max(S[ex.k][first.j], SAA[ex.k][i]),
665
- });
666
- }
667
- }
668
- // Render remaining right items with their own left extras directly beneath
669
- for (let idx = 1; idx < group.length; idx++) {
670
- const g = group[idx];
671
- rows.push({ left: '-', right: listB[g.j], score: g.score });
672
- const extrasForJ = (byRight && byRight.get(g.j)) || [];
673
- if (extrasForJ.length) {
674
- extrasForJ.sort((e1, e2) => {
675
- const s1 = Math.max(S[e1.k][g.j], SAA[e1.k][i]);
676
- const s2 = Math.max(S[e2.k][g.j], SAA[e2.k][i]);
677
- const d = s2 - s1;
678
- if (Math.abs(d) > 1e-9)
679
- return d;
680
- return listA[e1.k]
681
- .toLowerCase()
682
- .localeCompare(listA[e2.k].toLowerCase());
683
- });
684
- for (const ex of extrasForJ) {
685
- rows.push({
686
- left: listA[ex.k],
687
- right: '-',
688
- score: Math.max(S[ex.k][g.j], SAA[ex.k][i]),
689
- });
690
- }
691
- }
692
- }
693
- }
694
- // Append leftover left items (not anchors and not attached as extras)
695
- for (let i = 0; i < aCount; i++) {
696
- if (!anchorSet.has(i) && !leftUsed.has(i)) {
697
- rows.push({ left: listA[i], right: '-', score: 0 });
698
- }
699
- }
700
- // Append unassigned B at the end so they do not disrupt highly related groups
701
- for (const j of leftoverB) {
702
- rows.push({ left: '-', right: listB[j], score: 0 });
703
- }
704
- return rows;
705
- }));
706
- }))), catchError((err) => throwError(() => new Error('Failed to create grouped alignment: ' + err))));
707
- }
708
- // Cosine similarity between two vectors
709
- cosineSimilarity(a, b) {
710
- let dot = 0, na = 0, nb = 0;
711
- for (let i = 0; i < a.length; i++) {
712
- dot += a[i] * b[i];
713
- na += a[i] * a[i];
714
- nb += b[i] * b[i];
715
- }
716
- return dot / (Math.sqrt(na) * Math.sqrt(nb) + 1e-12);
717
- }
718
- /**
719
- * Align words in listA with their most semantically similar words in listB
720
- *
721
- * Algorithm:
722
- * 1. Generate embeddings for all words in both lists using Universal Sentence Encoder
723
- * 2. For each word in listA, compare its embedding with all words in listB
724
- * 3. Find the word in listB with highest cosine similarity score
725
- * 4. Return alignment results with similarity scores
726
- *
727
- * @param listA - Source list of words to align
728
- * @param listB - Target list of words to find matches in
729
- * @returns Observable of alignment results with word, alignedWith, score, and index
730
- */
731
- alignLists(listA, listB) {
732
- return this.loadModel().pipe(
733
- // Step 1: Generate embeddings for both word lists simultaneously
734
- switchMap((model) => forkJoin([
735
- from(model.embed(listA)),
736
- from(model.embed(listB))
737
- ]).pipe(
738
- // Step 2: Convert tensor embeddings to JavaScript arrays
739
- switchMap(([tA, tB]) => {
740
- return forkJoin([from(tA.array()), from(tB.array())]).pipe(map((tensorArrays) => {
741
- const [arrA, arrB] = tensorArrays;
742
- // Clean up TensorFlow tensors to prevent memory leaks
743
- tA.dispose();
744
- tB.dispose();
745
- // For each word in listA, find its best match in listB
746
- const results = listA.map((wordA, i) => {
747
- // Get the embedding vector for current word from listA
748
- const vecA = Float32Array.from(arrA[i]);
749
- // Initialize variables to track the best match
750
- let bestScore = -Infinity;
751
- let bestMatch = null;
752
- let bestIndex = -1;
753
- // Compare current word from listA with all words in listB
754
- for (let j = 0; j < listB.length; j++) {
755
- // Get the embedding vector for current word from listB
756
- const vecB = Float32Array.from(arrB[j]);
757
- // Calculate semantic similarity using cosine similarity
758
- const score = this.cosineSimilarity(vecA, vecB);
759
- // Update best match if this similarity is higher
760
- if (score > bestScore) {
761
- bestScore = score;
762
- bestMatch = listB[j];
763
- bestIndex = j;
764
- }
765
- }
766
- // Return alignment result for this word
767
- return {
768
- word: wordA, // Original word from listA
769
- alignedWith: bestMatch, // Best matching word from listB
770
- score: bestScore, // Similarity score (0-1, higher is better)
771
- index: bestIndex, // Index of best match in listB
772
- };
773
- });
774
- // Return all alignment results
775
- return results;
776
- }));
777
- }))), catchError((err) => throwError(() => new Error('Model not loaded or embedding failed: ' + err))));
778
- }
779
- /**
780
- * Find best matching pairs between two lists above a similarity threshold
781
- *
782
- * @param listA - Source list of words
783
- * @param listB - Target list of words to match against
784
- * @param threshold - Minimum similarity score (0-1, default: 0.15)
785
- * @returns Observable of word pairs with similarity scores
786
- */
787
- findBestMatchingPairs(listA, listB, threshold = 0.15) {
788
- return this.alignLists(listA, listB).pipe(map((alignments) => {
789
- const usedB = new Set();
790
- const pairs = [];
791
- // Sort by similarity score (highest first)
792
- alignments.sort((a, b) => b.score - a.score);
793
- // Debug logging for travel/journey matching
794
- console.log('Embedding Service - All alignments:', alignments.map((alignment) => ({
795
- word: alignment.word,
796
- alignedWith: alignment.alignedWith,
797
- score: alignment.score.toFixed(3),
798
- })));
799
- for (const alignment of alignments) {
800
- if (alignment.score >= threshold && !usedB.has(alignment.index)) {
801
- pairs.push({
802
- wordA: alignment.word,
803
- wordB: alignment.alignedWith,
804
- score: alignment.score,
805
- });
806
- usedB.add(alignment.index);
807
- // Special logging for travel-related matches
808
- if (alignment.word.toLowerCase().includes('travel') ||
809
- alignment.word.toLowerCase().includes('journey') ||
810
- alignment.alignedWith.toLowerCase().includes('travel') ||
811
- alignment.alignedWith.toLowerCase().includes('journey')) {
812
- console.log('🎯 Travel/Journey Match Found:', {
813
- wordA: alignment.word,
814
- wordB: alignment.alignedWith,
815
- score: alignment.score.toFixed(3),
816
- threshold: threshold,
817
- });
818
- }
819
- }
820
- }
821
- return pairs;
822
- }), catchError((err) => throwError(() => new Error('Failed to find best matching pairs: ' + err))));
823
- }
824
- // Calculate similarity between two texts (returns Observable)
825
- calculateSimilarity(primaryText, comparisonText) {
826
- return this.getEmbedding(primaryText).pipe(switchMap((emb1) => this.getEmbedding(comparisonText).pipe(map((emb2) => {
827
- const similarity = this.cosineSimilarity(emb1, emb2);
828
- // Debug logging for travel/journey similarity
829
- if (primaryText.toLowerCase().includes('travel') ||
830
- primaryText.toLowerCase().includes('journey') ||
831
- comparisonText.toLowerCase().includes('travel') ||
832
- comparisonText.toLowerCase().includes('journey')) {
833
- console.log('🔍 Travel/Journey Similarity:', {
834
- primaryText,
835
- comparisonText,
836
- similarity: similarity.toFixed(3),
837
- });
838
- }
839
- return similarity;
840
- }))), catchError((err) => throwError(() => new Error('Failed to calculate similarity: ' + err))));
841
- }
842
- // Test method to check specific word similarities (Observable only)
843
- testWordSimilarity(word1, word2) {
844
- return this.calculateSimilarity(word1, word2).pipe(map((similarity) => {
845
- console.log(`📊 Similarity between "${word1}" and "${word2}": ${(similarity * 100).toFixed(1)}%`);
846
- }), catchError((error) => {
847
- console.error('Error testing word similarity:', error);
848
- return of();
849
- }));
850
- }
851
- // Private methods
852
- // Domain compatibility cap to avoid mismatched category-versus-activity pairings.
853
- // Example: "Fish" (food/animal noun) vs "Bird watching" (activity) should not align.
854
- // Returns a cap in [0,1] representing the maximum allowed similarity for the pair.
855
- // Values below the groupAlignLists() threshold (default 0.25) effectively block alignment.
856
- domainCompatibilityCap(a, b) {
857
- const norm = (s) => s
858
- .toLowerCase()
859
- .replace(/[_-]+/g, ' ')
860
- .replace(/[^a-z\s]/g, ' ')
861
- .replace(/\s+/g, ' ')
862
- .trim();
863
- const A = norm(a);
864
- const B = norm(b);
865
- const watching = /\b[a-z]+\s*watching\b|\bbirdwatching\b/;
866
- const genericActivities = /\b(hiking|reading|painting|photography|gardening|blogging|writing|gaming|chess|puzzles|travel(?:ing|ling)?|programming|coding|language learning|pottery|clay\s*model(?:ing|ling))\b/;
867
- const isActivity = (s) => watching.test(s) || genericActivities.test(s);
868
- const foodAnimal = /\b(fish|sea\s*food|seafood|shell\s*fish|shellfish|clam|clams|mussel|mussels|oyster|oysters|scallop|scallops|shrimp|prawn|prawns|lobster|crab|meat|beef|pork|chicken|egg|eggs)\b/;
869
- const isFoodAnimal = (s) => foodAnimal.test(s);
870
- const aAct = isActivity(A);
871
- const bAct = isActivity(B);
872
- const aFood = isFoodAnimal(A);
873
- const bFood = isFoodAnimal(B);
874
- if ((aAct && bFood) || (bAct && aFood)) {
875
- // Cap below default threshold so these pairs never form groups
876
- return 0.2;
877
- }
878
- return 1.0;
879
- }
880
- // Lightweight lexical similarity booster to catch cases like "AI/ML" ↔ "AI machine learning"
881
- // Uses token normalization + targeted synonym/expansion rules and token Jaccard.
882
- computeLexicalBoost(a, b) {
883
- const expand = (s) => {
884
- let x = s.toLowerCase();
885
- // Normalize separators
886
- x = x.replace(/[_-]+/g, ' ');
887
- // Targeted expansions
888
- x = x.replace(/ai\s*\/\s*ml|ai\s*[- ]?ml/g, 'ai machine learning');
889
- x = x.replace(/\bml\b/g, 'machine learning');
890
- x = x.replace(/web\s*dev(elopment)?/g, 'web development');
891
- // Domain expansions – culinary/food categories (to help cases like Fish ↔ Clams)
892
- x = x.replace(/\bsea\s*food\b/g, 'seafood fish shellfish');
893
- x = x.replace(/\bshell\s*fish\b/g, 'shellfish clams mussels oysters scallops seafood');
894
- x = x.replace(/\bfish(es)?\b/g, 'fish seafood');
895
- x = x.replace(/\bclam(s)?\b/g, 'clams shellfish seafood');
896
- x = x.replace(/\bmussel(s)?\b/g, 'mussels shellfish seafood');
897
- x = x.replace(/\boyster(s)?\b/g, 'oysters shellfish seafood');
898
- x = x.replace(/\bscallop(s)?\b/g, 'scallops shellfish seafood');
899
- x = x.replace(/\bshrimp(s)?\b/g, 'shrimp seafood');
900
- x = x.replace(/\bprawn(s)?\b/g, 'prawns shrimp seafood');
901
- x = x.replace(/\blobster(s)?\b/g, 'lobster seafood');
902
- x = x.replace(/\bcrab(s)?\b/g, 'crab seafood');
903
- x = x.replace(/ui\s*\/\s*ux|ui\s*[- ]?ux/g, 'ui ux design');
904
- return x;
905
- };
906
- const normalize = (s) => {
907
- const x = expand(s)
908
- .replace(/[^a-z0-9\s]/g, ' ')
909
- .replace(/\s+/g, ' ')
910
- .trim();
911
- return x;
912
- };
913
- const A = normalize(a);
914
- const B = normalize(b);
915
- if (!A || !B)
916
- return 0;
917
- // Strong containment boost (e.g., "ai machine learning" contains "machine learning")
918
- if (A.includes(B) || B.includes(A)) {
919
- return Math.max(0.75, Math.min(0.95, Math.max(A.length, B.length) > 12 ? 0.85 : 0.78));
920
- }
921
- const toTokens = (s) => s.split(' ').filter(Boolean);
922
- const setFrom = (arr) => new Set(arr);
923
- const tA = toTokens(A);
924
- const tB = toTokens(B);
925
- if (!tA.length || !tB.length)
926
- return 0;
927
- const sA = setFrom(tA);
928
- const sB = setFrom(tB);
929
- let inter = 0;
930
- for (const t of sA)
931
- if (sB.has(t))
932
- inter++;
933
- const union = sA.size + sB.size - inter;
934
- const jaccard = union > 0 ? inter / union : 0;
935
- // Small extra for sharing big tokens like "learning"/"development" with qualifiers
936
- const longTokenBonus = Math.min(0.1, [...sA].filter((t) => t.length >= 6 && sB.has(t)).length * 0.03);
937
- const score = Math.min(0.95, jaccard + longTokenBonus);
938
- return score;
939
- }
940
- // Lazy load the model only once, as observable
941
- loadModel() {
942
- if (!this.model$) {
943
- this.model$ = defer(() => from(use.load())).pipe(shareReplay(1));
944
- }
945
- return this.model$;
946
- }
947
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: EmbeddingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
948
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: EmbeddingService, providedIn: 'root' });
949
- }
950
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: EmbeddingService, decorators: [{
951
- type: Injectable,
952
- args: [{
953
- providedIn: 'root',
954
- }]
955
- }] });
956
-
957
213
  class FileConversionService {
958
214
  static MIME_TYPE_JPEG = 'image/jpeg';
959
215
  static MIME_TYPE_PNG = 'image/png';
@@ -963,7 +219,7 @@ class FileConversionService {
963
219
  static FILE_EXTENSION_PNG = '.png';
964
220
  static FILE_EXTENSION_WEBP = '.webp';
965
221
  urlToFile(url, fileName, mimeType) {
966
- return from(fetch(url)).pipe(switchMap$1(response => from(response.blob())), map$1(blob => new File([blob], fileName, { type: mimeType })));
222
+ return from(fetch(url)).pipe(switchMap(response => from(response.blob())), map(blob => new File([blob], fileName, { type: mimeType })));
967
223
  }
968
224
  isDataUrl(str) {
969
225
  return typeof str === 'string' && str.startsWith('data:');
@@ -1008,7 +264,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
1008
264
  class ImageCompressionService {
1009
265
  compressImageFile(file, config, overrides = {}) {
1010
266
  const cfg = { ...config, ...overrides };
1011
- return this.loadImageFromFile(file).pipe(switchMap$1((img) => {
267
+ return this.loadImageFromFile(file).pipe(switchMap((img) => {
1012
268
  let width = img.naturalWidth || img.width;
1013
269
  let height = img.naturalHeight || img.height;
1014
270
  const scale = Math.min(cfg.maxWidth / width, cfg.maxHeight / height, 1);
@@ -1024,7 +280,7 @@ class ImageCompressionService {
1024
280
  };
1025
281
  const process = (w, h, q, attempts) => {
1026
282
  render(w, h);
1027
- return this.canvasToBlob(canvas, cfg.format, q).pipe(switchMap$1((blob) => {
283
+ return this.canvasToBlob(canvas, cfg.format, q).pipe(switchMap((blob) => {
1028
284
  if (blob.size > cfg.maxBytes && attempts < 25) {
1029
285
  if (q > cfg.qualityMin + 0.005) {
1030
286
  return process(w, h, Math.max(cfg.qualityMin, q - cfg.qualityStep), attempts + 1);
@@ -1040,10 +296,10 @@ class ImageCompressionService {
1040
296
  return of(blob);
1041
297
  }));
1042
298
  };
1043
- return process(width, height, cfg.qualityStart, 0).pipe(switchMap$1((finalBlob) => {
299
+ return process(width, height, cfg.qualityStart, 0).pipe(switchMap((finalBlob) => {
1044
300
  const newName = this.renameFileForFormat(file.name, cfg.format);
1045
301
  const compressedFile = new File([finalBlob], newName, { type: cfg.format, lastModified: Date.now() });
1046
- return this.blobToDataURL(finalBlob).pipe(map$1((dataUrl) => ({ file: compressedFile, dataUrl })));
302
+ return this.blobToDataURL(finalBlob).pipe(map((dataUrl) => ({ file: compressedFile, dataUrl })));
1047
303
  }));
1048
304
  }));
1049
305
  }
@@ -1228,7 +484,7 @@ class OpenAIEmbeddingService {
1228
484
  if (validA.length === 0 && validB.length === 0)
1229
485
  return of({ listA: [], listB: [] });
1230
486
  const key = JSON.stringify({ a: validA, b: validB, v: 'spacer-v35' });
1231
- return from(this.loadSpacerCache()).pipe(switchMap(() => {
487
+ return from(this.loadSpacerCache()).pipe(switchMap$1(() => {
1232
488
  if (this.spacerAlignmentCache.has(key)) {
1233
489
  console.log('Alignment cache hit');
1234
490
  return of(this.spacerAlignmentCache.get(key));
@@ -1240,7 +496,7 @@ class OpenAIEmbeddingService {
1240
496
  .post(url, { listA: validA, listB: validB }, {
1241
497
  headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
1242
498
  })
1243
- .pipe(map((result) => {
499
+ .pipe(map$1((result) => {
1244
500
  const finalResult = { listA: result.listA || [], listB: result.listB || [] };
1245
501
  this.spacerAlignmentCache.set(key, finalResult);
1246
502
  this.persistence.set(CachePersistenceService.STORE_OPENAI_SPACER, key, finalResult);
@@ -1362,7 +618,7 @@ class OpenAIEmbeddingService {
1362
618
  temperature: 0.0, // Zero temperature for maximum determinism
1363
619
  seed: 42 // Fixed seed for reproducibility
1364
620
  };
1365
- return this.http.post(this.API_URL, body, { headers }).pipe(map(response => {
621
+ return this.http.post(this.API_URL, body, { headers }).pipe(map$1(response => {
1366
622
  try {
1367
623
  const content = response.choices[0].message.content;
1368
624
  console.log('OpenAI LLM Output (getAlignedLists):', content);
@@ -1520,7 +776,7 @@ class OpenAIEmbeddingService {
1520
776
  return of([]);
1521
777
  const keyDirect = JSON.stringify({ a: validA, b: validB });
1522
778
  const keyReverse = JSON.stringify({ a: validB, b: validA });
1523
- return from(this.loadAlignmentCache()).pipe(switchMap(() => {
779
+ return from(this.loadAlignmentCache()).pipe(switchMap$1(() => {
1524
780
  if (this.alignmentCache.has(keyDirect)) {
1525
781
  console.log('Alignment cache hit (Direct)');
1526
782
  return of(this.alignmentCache.get(keyDirect).rows);
@@ -1589,7 +845,7 @@ class OpenAIEmbeddingService {
1589
845
  temperature: 0.2
1590
846
  };
1591
847
  console.log('Sending single alignment request to OpenAI...');
1592
- return this.http.post(this.API_URL, body, { headers }).pipe(map(response => {
848
+ return this.http.post(this.API_URL, body, { headers }).pipe(map$1(response => {
1593
849
  try {
1594
850
  const content = response.choices[0].message.content;
1595
851
  console.log('OpenAI LLM Output (groupAlignLists):', content);
@@ -1767,7 +1023,7 @@ class OpenAIEmbeddingService {
1767
1023
  return of(0);
1768
1024
  }
1769
1025
  const simKey = [a, b].sort().join('\0');
1770
- return from(this.loadSimilarityCache()).pipe(switchMap(() => {
1026
+ return from(this.loadSimilarityCache()).pipe(switchMap$1(() => {
1771
1027
  if (this.similarityCache.has(simKey)) {
1772
1028
  return of(this.similarityCache.get(simKey));
1773
1029
  }
@@ -1778,7 +1034,7 @@ class OpenAIEmbeddingService {
1778
1034
  .post(url, { textA: a, textB: b }, {
1779
1035
  headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
1780
1036
  })
1781
- .pipe(map((res) => {
1037
+ .pipe(map$1((res) => {
1782
1038
  const similarity = res.similarity ?? 0;
1783
1039
  this.similarityCache.set(simKey, similarity);
1784
1040
  this.persistence.set(CachePersistenceService.STORE_OPENAI_SIMILARITY, simKey, similarity);
@@ -1797,7 +1053,7 @@ class OpenAIEmbeddingService {
1797
1053
  model: 'text-embedding-3-small',
1798
1054
  input: [a, b]
1799
1055
  };
1800
- return this.http.post(EMBEDDINGS_URL, body, { headers }).pipe(map(res => {
1056
+ return this.http.post(EMBEDDINGS_URL, body, { headers }).pipe(map$1(res => {
1801
1057
  const data = res?.data;
1802
1058
  if (!Array.isArray(data) || data.length < 2) {
1803
1059
  throw new Error('Invalid embeddings response from OpenAI');
@@ -1824,7 +1080,7 @@ class OpenAIEmbeddingService {
1824
1080
  if (!texts || texts.length === 0)
1825
1081
  return of([]);
1826
1082
  const embKey = JSON.stringify(texts);
1827
- return from(this.loadEmbeddingsCache()).pipe(switchMap(() => {
1083
+ return from(this.loadEmbeddingsCache()).pipe(switchMap$1(() => {
1828
1084
  if (this.embeddingsCache.has(embKey)) {
1829
1085
  return of(this.embeddingsCache.get(embKey));
1830
1086
  }
@@ -1835,7 +1091,7 @@ class OpenAIEmbeddingService {
1835
1091
  .post(url, { input: texts }, {
1836
1092
  headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
1837
1093
  })
1838
- .pipe(map((res) => {
1094
+ .pipe(map$1((res) => {
1839
1095
  const result = (res.data || []).map((item) => item.embedding || []);
1840
1096
  this.embeddingsCache.set(embKey, result);
1841
1097
  this.persistence.set(CachePersistenceService.STORE_OPENAI_EMBEDDINGS, embKey, result);
@@ -1854,7 +1110,7 @@ class OpenAIEmbeddingService {
1854
1110
  model: 'text-embedding-3-small',
1855
1111
  input: texts
1856
1112
  };
1857
- return this.http.post(EMBEDDINGS_URL, body, { headers }).pipe(map(res => {
1113
+ return this.http.post(EMBEDDINGS_URL, body, { headers }).pipe(map$1(res => {
1858
1114
  const data = res?.data;
1859
1115
  if (!Array.isArray(data)) {
1860
1116
  throw new Error('Invalid embeddings response from OpenAI');
@@ -2023,7 +1279,7 @@ class ProfileService {
2023
1279
  const a = (text_1 ?? '').trim();
2024
1280
  const b = (text_2 ?? '').trim();
2025
1281
  const cacheKey = a && b ? [a, b].sort().join('\0') : '';
2026
- return from(this.loadCompareCache()).pipe(switchMap(() => {
1282
+ return from(this.loadCompareCache()).pipe(switchMap$1(() => {
2027
1283
  if (cacheKey && this.compareInterestsCache.has(cacheKey)) {
2028
1284
  return of(this.compareInterestsCache.get(cacheKey));
2029
1285
  }
@@ -2054,7 +1310,7 @@ class ProfileService {
2054
1310
  detectFace(image, creds, options) {
2055
1311
  const rawKey = options?.cacheKey ?? (typeof image === 'string' ? image : undefined);
2056
1312
  const cacheKey = ProfileService.faceCacheKey(rawKey);
2057
- return from(this.loadFaceCache()).pipe(switchMap(() => {
1313
+ return from(this.loadFaceCache()).pipe(switchMap$1(() => {
2058
1314
  if (cacheKey && this.faceCache.has(cacheKey)) {
2059
1315
  console.log('Face detection cache hit');
2060
1316
  return of(this.faceCache.get(cacheKey));
@@ -2126,7 +1382,7 @@ class ProfileService {
2126
1382
  }
2127
1383
  // Propagate other errors (or second failure) to the caller
2128
1384
  return throwError(() => error);
2129
- }))), map$1((raw) => {
1385
+ }))), map((raw) => {
2130
1386
  // Face++ sometimes returns HTTP 200 with an error payload containing `error_message`
2131
1387
  // e.g. "AUTHENTICATION_ERROR: api_key and api_secret does not match." or
2132
1388
  // "AUTHORIZATION_ERROR:<reason>" or "CONCURRENCY_LIMIT_EXCEEDED".
@@ -2206,27 +1462,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
2206
1462
  * The lib does not call third-party APIs; the backend does.
2207
1463
  */
2208
1464
  class ProfileComparisonBackendService {
2209
- http;
2210
- apiBaseUrl;
2211
- getVerboseLogging;
2212
- constructor(http, apiBaseUrl, getVerboseLogging) {
2213
- this.http = http;
2214
- this.apiBaseUrl = apiBaseUrl;
2215
- this.getVerboseLogging = getVerboseLogging;
1465
+ http = inject(HttpClient);
1466
+ apiBaseUrl = inject(PROFILE_COMPARISON_API_BASE_URL, { optional: true });
1467
+ getVerboseLogging = inject(PROFILE_COMPARISON_VERBOSE_LOGGING, { optional: true });
1468
+ constructor() { }
1469
+ static extractErrorMessage(err) {
1470
+ if (err == null || typeof err !== 'object')
1471
+ return 'Unknown error';
1472
+ const e = err;
1473
+ return e.error?.message || e.message || 'Unknown error';
2216
1474
  }
2217
1475
  log(...args) {
2218
1476
  if (this.getVerboseLogging?.() === true) {
2219
1477
  console.log('[ProfileComparisonBackend]', ...args);
2220
1478
  }
2221
1479
  }
2222
- getBaseUrl() {
1480
+ /** Returns the legacy token URL (for backward compat fallback). */
1481
+ getLegacyTokenUrl() {
2223
1482
  const u = this.apiBaseUrl;
2224
1483
  if (u == null)
2225
1484
  return null;
2226
1485
  return typeof u === 'function' ? u() : u;
2227
1486
  }
2228
- getComparison(config) {
2229
- const base = this.getBaseUrl();
1487
+ /** @deprecated Use getLegacyTokenUrl(). Kept for backward compat. */
1488
+ getBaseUrl() {
1489
+ return this.getLegacyTokenUrl();
1490
+ }
1491
+ getComparison(config, baseUrl) {
1492
+ const base = baseUrl ?? this.getLegacyTokenUrl();
2230
1493
  this.log('getComparison', 'baseUrl', base ?? '(null)');
2231
1494
  if (!base || !base.trim()) {
2232
1495
  this.log('getComparison', 'returning error: backend not configured');
@@ -2236,29 +1499,17 @@ class ProfileComparisonBackendService {
2236
1499
  this.log('getComparison', 'POST', url);
2237
1500
  return this.http.post(url, { config }).pipe(tap$1(() => this.log('getComparison', 'POST success')), catchError((err) => {
2238
1501
  this.log('getComparison', 'POST error', err);
2239
- const msg = err?.error?.message ?? err?.message ?? err?.statusText ?? String(err);
2240
- const status = err?.status ?? err?.statusCode;
2241
- console.error('[ProfileComparisonBackend] getComparison failed', status, msg);
1502
+ console.error('[ProfileComparisonBackend] getComparison failed', err?.status, ProfileComparisonBackendService.extractErrorMessage(err));
2242
1503
  return throwError(() => err);
2243
1504
  }));
2244
1505
  }
2245
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ProfileComparisonBackendService, deps: [{ token: i1.HttpClient }, { token: PROFILE_COMPARISON_API_BASE_URL, optional: true }, { token: PROFILE_COMPARISON_VERBOSE_LOGGING, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
1506
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ProfileComparisonBackendService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2246
1507
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ProfileComparisonBackendService, providedIn: 'root' });
2247
1508
  }
2248
1509
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ProfileComparisonBackendService, decorators: [{
2249
1510
  type: Injectable,
2250
1511
  args: [{ providedIn: 'root' }]
2251
- }], ctorParameters: () => [{ type: i1.HttpClient }, { type: undefined, decorators: [{
2252
- type: Optional
2253
- }, {
2254
- type: Inject,
2255
- args: [PROFILE_COMPARISON_API_BASE_URL]
2256
- }] }, { type: undefined, decorators: [{
2257
- type: Optional
2258
- }, {
2259
- type: Inject,
2260
- args: [PROFILE_COMPARISON_VERBOSE_LOGGING]
2261
- }] }] });
1512
+ }], ctorParameters: () => [] });
2262
1513
 
2263
1514
  class ProfileComparisonLibComponent {
2264
1515
  backendService;
@@ -2306,7 +1557,9 @@ class ProfileComparisonLibComponent {
2306
1557
  user1Image: '',
2307
1558
  user2Image: '',
2308
1559
  };
2309
- fadeAllEdges = false;
1560
+ backendMode = BackendMode.Real;
1561
+ backendUrl = null;
1562
+ fadeAllEdges = true;
2310
1563
  matrixDataChange = new EventEmitter();
2311
1564
  rawLLMOutputChange = new EventEmitter();
2312
1565
  viewProfileClick = new EventEmitter();
@@ -2358,6 +1611,8 @@ class ProfileComparisonLibComponent {
2358
1611
  matrixData = null;
2359
1612
  /** When false, backend URL is not provided — show "Configure backend". */
2360
1613
  backendConfigured = false;
1614
+ /** The resolved backend URL computed from inputs and legacy token. */
1615
+ resolvedBackendUrl = null;
2361
1616
  compressionConfig = {
2362
1617
  maxWidth: ProfileComparisonLibComponent.DEFAULT_MAX_WIDTH,
2363
1618
  maxHeight: ProfileComparisonLibComponent.DEFAULT_MAX_HEIGHT,
@@ -2411,9 +1666,9 @@ class ProfileComparisonLibComponent {
2411
1666
  });
2412
1667
  this.baseConfig = JSON.parse(JSON.stringify(this.config));
2413
1668
  this.updateConfigProperties();
2414
- const base = this.backendService.getBaseUrl();
2415
- this.backendConfigured = !!(base && base.trim());
2416
- this.log('ngOnInit', 'backendConfigured', this.backendConfigured, 'base', base ?? '(null)');
1669
+ this.resolvedBackendUrl = resolveBackendUrl(this.backendUrl, this.backendMode, this.backendService.getLegacyTokenUrl());
1670
+ this.backendConfigured = !!this.resolvedBackendUrl;
1671
+ this.log('ngOnInit', 'backendConfigured', this.backendConfigured, 'resolvedUrl', this.resolvedBackendUrl ?? '(null)');
2417
1672
  if (!this.backendConfigured) {
2418
1673
  this.log('ngOnInit', 'exiting early: backend not configured');
2419
1674
  this.user1Transform = 'translateY(0px)';
@@ -2441,6 +1696,11 @@ class ProfileComparisonLibComponent {
2441
1696
  this.waitForImagesAndInitDrag();
2442
1697
  }
2443
1698
  ngOnChanges(changes) {
1699
+ if (changes['backendMode'] || changes['backendUrl']) {
1700
+ this.resolvedBackendUrl = resolveBackendUrl(this.backendUrl, this.backendMode, this.backendService.getLegacyTokenUrl());
1701
+ this.backendConfigured = !!this.resolvedBackendUrl;
1702
+ this.log('ngOnChanges', 'backend inputs changed', 'resolvedUrl', this.resolvedBackendUrl ?? '(null)');
1703
+ }
2444
1704
  if (changes['config']) {
2445
1705
  const first = changes['config'].firstChange;
2446
1706
  this.log('ngOnChanges', 'config changed', 'firstChange', first, 'backendConfigured', this.backendConfigured);
@@ -2502,7 +1762,7 @@ class ProfileComparisonLibComponent {
2502
1762
  user1ImageLen: (configToSend.user1Image || '').length,
2503
1763
  user2ImageLen: (configToSend.user2Image || '').length,
2504
1764
  });
2505
- this.computeSub = this.backendService.getComparison(configToSend).subscribe({
1765
+ this.computeSub = this.backendService.getComparison(configToSend, this.resolvedBackendUrl).subscribe({
2506
1766
  next: (payload) => {
2507
1767
  if (requestId !== this.fetchRequestId) {
2508
1768
  this.log('fetchComparison', 'ignoring stale success', { requestId, current: this.fetchRequestId });
@@ -2886,16 +2146,16 @@ class ProfileComparisonLibComponent {
2886
2146
  const tasks = [];
2887
2147
  if (this.user1Image) {
2888
2148
  tasks.push(this.compressImageStringToDataUrl(this.user1Image, 'ConfigUser1').pipe(tap$1((dataUrl) => { if (dataUrl)
2889
- this.user1Image = dataUrl; }), map$1(() => void 0)));
2149
+ this.user1Image = dataUrl; }), map(() => void 0)));
2890
2150
  }
2891
2151
  if (this.user2Image) {
2892
2152
  tasks.push(this.compressImageStringToDataUrl(this.user2Image, 'ConfigUser2').pipe(tap$1((dataUrl) => { if (dataUrl)
2893
- this.user2Image = dataUrl; }), map$1(() => void 0)));
2153
+ this.user2Image = dataUrl; }), map(() => void 0)));
2894
2154
  }
2895
- return tasks.length > 0 ? forkJoin(tasks).pipe(map$1(() => void 0)) : of(void 0);
2155
+ return tasks.length > 0 ? forkJoin(tasks).pipe(map(() => void 0)) : of(void 0);
2896
2156
  }
2897
2157
  compressImageStringToDataUrl(imageStr, baseName) {
2898
- return this.fileConversionService.getFileForImageString(imageStr, `${baseName}.jpg`).pipe(switchMap$1(file => this.imageCompressionService.compressImageFile(file, this.compressionConfig)), map$1(res => res.dataUrl), catchError$1(() => of(null)));
2158
+ return this.fileConversionService.getFileForImageString(imageStr, `${baseName}.jpg`).pipe(switchMap(file => this.imageCompressionService.compressImageFile(file, this.compressionConfig)), map(res => res.dataUrl), catchError$1(() => of(null)));
2899
2159
  }
2900
2160
  getEyeCoordinatesFromBBox(bbox) {
2901
2161
  const eyeY = bbox.y + bbox.height * 0.38;
@@ -3018,11 +2278,11 @@ class ProfileComparisonLibComponent {
3018
2278
  return !!face && typeof face.x === 'number' && face.width > 0 && face.height > 0;
3019
2279
  }
3020
2280
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ProfileComparisonLibComponent, deps: [{ token: ProfileComparisonBackendService }, { token: i0.Renderer2 }, { token: FileConversionService }, { token: ImageCompressionService }, { token: i0.ChangeDetectorRef }, { token: PROFILE_COMPARISON_VERBOSE_LOGGING, optional: true }], target: i0.ɵɵFactoryTarget.Component });
3021
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: ProfileComparisonLibComponent, isStandalone: false, selector: "lib-profile-comparison", inputs: { config: "config", fadeAllEdges: "fadeAllEdges" }, outputs: { matrixDataChange: "matrixDataChange", rawLLMOutputChange: "rawLLMOutputChange", viewProfileClick: "viewProfileClick" }, viewQueries: [{ propertyName: "leftContainer", first: true, predicate: ["leftContainer"], descendants: true }, { propertyName: "rightContainer", first: true, predicate: ["rightContainer"], descendants: true }, { propertyName: "profileFlex", first: true, predicate: ["profileFlex"], descendants: true }, { propertyName: "profileImgLeft", first: true, predicate: ["profileImgLeft"], descendants: true }, { propertyName: "profileImgRight", first: true, predicate: ["profileImgRight"], descendants: true }, { propertyName: "shapeContainer", first: true, predicate: ["shapeContainer"], descendants: true }, { propertyName: "shapeBg", first: true, predicate: ["shapeBg"], descendants: true }, { propertyName: "shapeBg1", first: true, predicate: ["shapeBg1"], descendants: true }, { propertyName: "shapeBg2", first: true, predicate: ["shapeBg2"], descendants: true }, { propertyName: "shapeTextLeft", first: true, predicate: ["shapeTextLeft"], descendants: true }, { propertyName: "shapeTextRight", first: true, predicate: ["shapeTextRight"], descendants: true }, { propertyName: "shapeTextCenter", first: true, predicate: ["shapeTextCenter"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"configure-backend-message\" *ngIf=\"!backendConfigured\">\n Configure backend\n</div>\n\n<div class=\"profile-screen\" *ngIf=\"backendConfigured\">\n <div class=\"backend-error-message\" *ngIf=\"backendError\">{{ backendError }}</div>\n <div #profileFlex class=\"profile-flex\" [class.fade-all]=\"fadeAllEdges\">\n <div #profileImgLeft class=\"profile-img left\" [class.fade-all]=\"fadeAllEdges\">\n <img\n [src]=\"user1Image\"\n alt=\"User 1\"\n [style.transform]=\"user1Transform\"\n [style.object-position]=\"user1ObjectPosition\"\n (load)=\"onUserImageLoad(1, $event)\"\n />\n </div>\n <div #profileImgRight class=\"profile-img right\" [class.fade-all]=\"fadeAllEdges\">\n <img\n [src]=\"user2Image\"\n alt=\"User 2\"\n [style.transform]=\"user2Transform\"\n [style.object-position]=\"user2ObjectPosition\"\n (load)=\"onUserImageLoad(2, $event)\"\n />\n <!-- [style.object-fit]=\"'cover'\" -->\n </div>\n </div>\n\n <div #shapeContainer class=\"shape\">\n <div #shapeBg class=\"shape-bg\" [class.fade-all]=\"fadeAllEdges\">\n <svg width=\"250\" height=\"350\" viewBox=\"0 0 250 350\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" preserveAspectRatio=\"xMaxYMid slice\" style=\"height: 350px;\" class=\"shape-bg1\" #shapeBg1>\n<g filter=\"url(#filter0_i_4_4137)\">\n<path d=\"M215.177 44.4924L-18.823 19.3104C-25.3264 18.6106 -31 23.7064 -31 30.2473V283.169C-31 289.914 -24.9854 295.07 -18.3196 294.04L215.68 257.869C221.043 257.04 225 252.407 225 246.981V55.4497C225 49.8302 220.764 45.0937 215.177 44.4924Z\" fill=\"#00CFE6\" fill-opacity=\"0.07\"/>\n</g>\n<path d=\"M215.177 44.4924L-18.823 19.3104C-25.3264 18.6106 -31 23.7064 -31 30.2473V283.169C-31 289.914 -24.9854 295.07 -18.3196 294.04L215.68 257.869C221.043 257.04 225 252.407 225 246.981V55.4497C225 49.8302 220.764 45.0937 215.177 44.4924Z\" fill=\"url(#paint0_radial_4_4137)\" fill-opacity=\"0.07\" stroke=\"url(#paint1_linear_4_4137)\" stroke-width=\"2\" stroke-miterlimit=\"3.99393\" stroke-linejoin=\"round\"/>\n<g filter=\"url(#filter1_ddddd_4_4137)\">\n<path d=\"M219.081 43.0755L-18.9193 17.2045C-24.8351 16.5614 -30 21.1953 -30 27.1459V287.318C-30 293.455 -24.5217 298.145 -18.4573 297.198L219.543 260.038C224.411 259.278 228 255.075 228 250.148V53.0303C228 47.9257 224.155 43.6271 219.081 43.0755Z\" stroke=\"#61C2AB\" stroke-opacity=\"0.3\" stroke-linejoin=\"round\" shape-rendering=\"crispEdges\"/>\n</g>\n<defs>\n<filter id=\"filter0_i_4_4137\" x=\"-31\" y=\"19.2461\" width=\"256\" height=\"274.925\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"BackgroundImageFix\" result=\"shape\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.95\"/>\n<feComposite in2=\"hardAlpha\" operator=\"arithmetic\" k2=\"-1\" k3=\"1\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0.516667 0 0 0 0 1 0 0 0 1 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"shape\" result=\"effect1_innerShadow_4_4137\"/>\n</filter>\n<filter id=\"filter1_ddddd_4_4137\" x=\"-46.2\" y=\"0.944715\" width=\"290.4\" height=\"312.575\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.95\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"BackgroundImageFix\" result=\"effect1_dropShadow_4_4137\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect1_dropShadow_4_4137\" result=\"effect2_dropShadow_4_4137\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect2_dropShadow_4_4137\" result=\"effect3_dropShadow_4_4137\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect3_dropShadow_4_4137\" result=\"effect4_dropShadow_4_4137\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect4_dropShadow_4_4137\" result=\"effect5_dropShadow_4_4137\"/>\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"effect5_dropShadow_4_4137\" result=\"shape\"/>\n</filter>\n<radialGradient id=\"paint0_radial_4_4137\" cx=\"0\" cy=\"0\" r=\"1\" gradientTransform=\"matrix(65.1034 -81.3964 74.955 49.415 97 157)\" gradientUnits=\"userSpaceOnUse\">\n<stop stop-color=\"#0051E6\" stop-opacity=\"0\"/>\n<stop offset=\"0.615385\" stop-color=\"#0051E6\" stop-opacity=\"0\"/>\n<stop offset=\"1\" stop-color=\"white\"/>\n</radialGradient>\n<linearGradient id=\"paint1_linear_4_4137\" x1=\"97\" y1=\"18\" x2=\"97\" y2=\"296\" gradientUnits=\"userSpaceOnUse\">\n<stop offset=\"0.380829\" stop-color=\"#52FFEB\"/>\n<stop offset=\"1\" stop-color=\"#319990\"/>\n</linearGradient>\n</defs>\n</svg>\n <svg width=\"250\" height=\"350\" viewBox=\"0 0 250 350\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" preserveAspectRatio=\"xMinYMid slice\" style=\"height: 350px;\" class=\"shape-bg2\" #shapeBg2>\n<g filter=\"url(#filter0_i_4_4141)\">\n<path d=\"M29.5864 44.9454L263.826 19.8065C270.329 19.1087 276 24.2041 276 30.7437V283.174C276 289.917 269.988 295.073 263.324 294.046L29.0843 257.937C23.7196 257.11 19.7602 252.476 19.7602 247.048V55.9032C19.7602 50.2824 23.9977 45.5452 29.5864 44.9454Z\" fill=\"#7B00E6\" fill-opacity=\"0.07\"/>\n</g>\n<path d=\"M29.5864 44.9454L263.826 19.8065C270.329 19.1087 276 24.2041 276 30.7437V283.174C276 289.917 269.988 295.073 263.324 294.046L29.0843 257.937C23.7196 257.11 19.7602 252.476 19.7602 247.048V55.9032C19.7602 50.2824 23.9977 45.5452 29.5864 44.9454Z\" fill=\"url(#paint0_radial_4_4141)\" fill-opacity=\"0.07\" stroke=\"url(#paint1_linear_4_4141)\" stroke-width=\"2\" stroke-miterlimit=\"3.99393\" stroke-linejoin=\"round\"/>\n<g filter=\"url(#filter1_ddddd_4_4141)\">\n<path d=\"M25.9212 43.077L264.37 17.2022C270.285 16.5603 275.449 21.194 275.449 27.1438V287.321C275.449 293.457 269.972 298.146 263.909 297.201L25.46 260.036C20.5905 259.277 17 255.074 17 250.146V53.032C17 47.9267 20.8457 43.6277 25.9212 43.077Z\" stroke=\"#C37DFF\" stroke-opacity=\"0.3\" stroke-linejoin=\"round\" shape-rendering=\"crispEdges\"/>\n</g>\n<defs>\n<filter id=\"filter0_i_4_4141\" x=\"19.7602\" y=\"19.7425\" width=\"256.24\" height=\"274.434\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"BackgroundImageFix\" result=\"shape\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.95\"/>\n<feComposite in2=\"hardAlpha\" operator=\"arithmetic\" k2=\"-1\" k3=\"1\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0.482353 0 0 0 0 0 0 0 0 0 0.901961 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"shape\" result=\"effect1_innerShadow_4_4141\"/>\n</filter>\n<filter id=\"filter1_ddddd_4_4141\" x=\"0.8\" y=\"0.942639\" width=\"290.849\" height=\"312.58\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"BackgroundImageFix\" result=\"effect1_dropShadow_4_4141\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect1_dropShadow_4_4141\" result=\"effect2_dropShadow_4_4141\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect2_dropShadow_4_4141\" result=\"effect3_dropShadow_4_4141\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect3_dropShadow_4_4141\" result=\"effect4_dropShadow_4_4141\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect4_dropShadow_4_4141\" result=\"effect5_dropShadow_4_4137\"/>\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"effect5_dropShadow_4_4137\" result=\"shape\"/>\n</filter>\n<radialGradient id=\"paint0_radial_4_4137\" cx=\"0\" cy=\"0\" r=\"1\" gradientTransform=\"matrix(65.1034 -81.3964 74.955 49.415 97 157)\" gradientUnits=\"userSpaceOnUse\">\n<stop stop-color=\"#0051E6\" stop-opacity=\"0\"/>\n<stop offset=\"0.615385\" stop-color=\"#0051E6\" stop-opacity=\"0\"/>\n<stop offset=\"1\" stop-color=\"white\"/>\n</radialGradient>\n<linearGradient id=\"paint1_linear_4_4137\" x1=\"97\" y1=\"18\" x2=\"97\" y2=\"296\" gradientUnits=\"userSpaceOnUse\">\n<stop offset=\"0.380829\" stop-color=\"#52FFEB\"/>\n<stop offset=\"1\" stop-color=\"#319990\"/>\n</linearGradient>\n</defs>\n</svg>\n </div>\n\n <div class=\"shape-text\">\n <div #shapeTextLeft class=\"shape-text-left\">\n <div class=\"scroll-container\" #leftContainer>\n <ng-container\n *ngFor=\"\n let interest of alignedPerson1Interests.length > 0\n ? alignedPerson1Interests\n : displayPerson1Interests.length > 0\n ? displayPerson1Interests\n : person1Interests;\n let i = index\n \"\n >\n <p\n class=\"shape-p\"\n *ngIf=\"!isInCenter(interest)\"\n [ngClass]=\"{'dash-item': interest === '-', 'spacer-item': interest === '----', 'aligned-item': interest !== '-' && interest !== '----'}\"\n >\n {{ interest === \"-\" ? \"\u2014\" : interest }}\n </p>\n </ng-container>\n </div>\n <h2 class=\"shape-h2\" (click)=\"onViewProfile('left')\">View Profile</h2>\n </div>\n\n <div #shapeTextCenter class=\"shape-text-center\">\n <ng-container *ngFor=\"let item of centerItem\">\n <p class=\"shape-p-center\">{{ item }}</p>\n </ng-container>\n </div>\n <div #shapeTextRight class=\"shape-text-right\">\n <div class=\"scroll-container\" #rightContainer>\n <ng-container\n *ngFor=\"\n let interest of alignedPerson2Interests.length > 0\n ? alignedPerson2Interests\n : displayPerson2Interests.length > 0\n ? displayPerson2Interests\n : person2Interests;\n let i = index\n \"\n >\n <p\n class=\"shape-p-right\"\n *ngIf=\"!isInCenter(interest)\"\n [ngClass]=\"{'dash-item': interest === '-', 'spacer-item': interest === '----', 'aligned-item': interest !== '-' && interest !== '----'}\"\n >\n {{ interest === \"-\" ? \"\u2014\" : interest }}\n </p>\n </ng-container>\n </div>\n <h2 class=\"shape-h2-right\" (click)=\"onViewProfile('right')\">View Profile</h2>\n </div>\n </div>\n </div>\n\n <!-- Loading indicator for alignment process -->\n <div *ngIf=\"isAligning\" class=\"loading-indicator\">\n <div class=\"loading-spinner\"></div>\n </div>\n</div>\n\n\n", styles: [".profile-img.left{-webkit-mask-image:linear-gradient(to right,rgb(0,0,0) 0%,rgb(0,0,0) calc(100% - var(--overlap-size, 40px)),rgba(0,0,0,0) 100%);-webkit-mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-image:linear-gradient(to right,#fff 0% calc(100% - var(--overlap-size, 40px)),#fff0);mask-size:100% 100%;mask-repeat:no-repeat;margin-right:calc(-.5 * var(--overlap-size, 40px))}.profile-img.right{-webkit-mask-image:linear-gradient(to left,rgb(0,0,0) 0%,rgb(0,0,0) calc(100% - var(--overlap-size, 40px)),rgba(0,0,0,0) 100%);-webkit-mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-image:linear-gradient(to left,#fff 0% calc(100% - var(--overlap-size, 40px)),#fff0);mask-size:100% 100%;mask-repeat:no-repeat;margin-left:calc(-.5 * var(--overlap-size, 40px))}.profile-img.fade-all{-webkit-mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent,black 15%,black 85%,transparent);-webkit-mask-composite:source-in;mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent,black 15%,black 85%,transparent);mask-composite:intersect}.profile-img.fade-all.left{-webkit-mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%);mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%)}.profile-img.fade-all.right{-webkit-mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to left,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%);mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to left,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%)}.configure-backend-message{padding:1rem;text-align:center;color:#bdc3c7;background:#34495e;border-radius:8px}.backend-error-message{padding:.75rem 1rem;text-align:center;color:#fef3c7;background:#b4530940;border:1px solid rgba(245,158,11,.5);border-radius:8px;margin-bottom:.5rem}.profile-screen{width:320px;max-width:100%;margin:0 auto;overflow:hidden;position:relative}.profile-flex{display:flex;width:100%;height:400px;overflow:hidden;background:linear-gradient(135deg,#3a3a3a,#2a2a2a);gap:0;margin:0;padding:0;position:relative;box-shadow:none;border:none;--overlap-size: 40px}.profile-flex.fade-all{-webkit-mask-image:linear-gradient(to right,transparent 0%,black 15%,black 85%,transparent 100%),linear-gradient(to bottom,transparent 0%,black 15%,black 85%,transparent 100%);-webkit-mask-composite:intersect;mask-image:linear-gradient(to right,transparent 0%,black 15%,black 85%,transparent 100%),linear-gradient(to bottom,transparent 0%,black 15%,black 85%,transparent 100%);mask-composite:intersect}.shape-bg1,.shape-bg2{position:absolute;max-width:250px;opacity:.5;transition:transform .2s ease-out}.shape-bg1{z-index:1;left:0}.shape-bg2{right:0}.shape-bg.fade-all .shape-bg1{-webkit-mask-image:linear-gradient(to right,transparent 0%,black 20%,black 100%);mask-image:linear-gradient(to right,transparent 0%,black 20%,black 100%)}.shape-bg.fade-all .shape-bg2{-webkit-mask-image:linear-gradient(to left,transparent 0%,black 20%,black 100%);mask-image:linear-gradient(to left,transparent 0%,black 20%,black 100%)}.shape-bg svg{cursor:grab}.shape-bg svg:active{cursor:grabbing}.profile-img{width:180px;height:550px;position:relative;flex-shrink:0;background:linear-gradient(135deg,#3a3a3a,#2a2a2a);margin:0;padding:0;left:0;top:-75px;border:none;box-sizing:border-box;overflow:hidden;box-shadow:none;outline:none}.profile-img img{transition:transform .3s ease;width:100%;height:100%;object-fit:cover;display:block;object-position:center center;opacity:.6;transform-origin:center center;border:none;outline:none;box-shadow:none;position:relative;top:0;left:0;filter:contrast(1.2) brightness(1.1) saturate(1.1);image-rendering:auto}.shape{position:absolute;top:40px;width:100%}.shape-bg{position:relative}.shape-text{position:absolute;top:0;left:0;right:0;width:100%;height:100%;z-index:1;padding:0 1.25rem;box-sizing:border-box;pointer-events:none}.shape-text-left{margin-top:40px;text-align:left;position:absolute;top:0;bottom:0;right:calc(50% + 75px);z-index:2;pointer-events:auto;display:flex;flex-direction:column;align-items:flex-end}.shape-text-left .scroll-container{width:100%}.shape-text-center{position:absolute;left:0;right:0;margin:0 auto;width:fit-content;font-family:Gilroy,sans-serif;font-weight:700;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;text-align:center;display:flex;justify-content:center;align-items:center;flex-direction:column;text-shadow:2px 2px 4px rgba(0,0,0,.3);z-index:10;transition:transform .3s ease;pointer-events:none;top:50%;transform:translateY(-50%);padding:0 .625rem;margin-top:3.8125rem}.shape-text-right{margin-top:40px;text-align:right;position:absolute;top:0;bottom:0;left:calc(50% + 70px);z-index:2;pointer-events:auto;display:flex;flex-direction:column;align-items:flex-start}.shape-text-right .scroll-container{width:100%}.shape-p{font-family:Gilroy,sans-serif;font-weight:400;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;padding-bottom:.125rem;text-align:right;text-shadow:2px 2px 4px rgba(0,0,0,.3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;direction:rtl;-webkit-user-select:none;user-select:none;margin:0}.shape-p-center{font-family:Gilroy,sans-serif;font-weight:700;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;margin:0;text-align:center;display:flex;justify-content:center;align-items:center;text-shadow:2px 2px 4px rgba(0,0,0,.3)}.shape-p-right{font-family:Gilroy,sans-serif;font-weight:400;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;padding-bottom:.125rem;text-align:left;text-shadow:2px 2px 4px rgba(0,0,0,.3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin:0;-webkit-user-select:none;user-select:none}.dash-item{color:#ffffff4d!important;font-size:.625rem!important;height:.625rem}.spacer-item{height:auto!important;color:#fffc!important;font-weight:700;font-size:.75rem!important;letter-spacing:2px}.shape-h2{font-family:Calistoga,serif;font-weight:400;font-size:.625rem;letter-spacing:0%;color:#ffffff80;text-shadow:2px 2px 4px rgba(0,0,0,.3);cursor:pointer;transition:all .3s ease;border:none;outline:none;white-space:nowrap;pointer-events:auto;z-index:100;position:relative;margin-left:.5rem}.shape-h2:hover{opacity:.8;transform:translateY(-2px)}.shape-h2-right{font-family:Calistoga,serif;font-weight:400;font-size:.625rem;letter-spacing:0%;color:#ffffff80;text-shadow:2px 2px 4px rgba(0,0,0,.3);cursor:pointer;transition:all .3s ease;border:none;outline:none;white-space:nowrap;pointer-events:auto;z-index:100;position:relative;margin-right:3.75rem}.shape-h2-right:hover{opacity:.8;transform:translateY(-2px)}.scroll-when-long{display:inline-block;max-width:4.5625rem;white-space:nowrap;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch}.scroll-when-long::-webkit-scrollbar{height:2px}.scroll-when-long::-webkit-scrollbar-track{background:#eee;border-radius:.625rem}.scroll-when-long::-webkit-scrollbar-thumb{background:#888;border-radius:.625rem}.scroll-when-long::-webkit-scrollbar-thumb:hover{background:#555}.shape-text p{cursor:default;-webkit-user-select:none;user-select:none}.shape-text p:active{cursor:default}#drag-preview{position:absolute;pointer-events:none;display:none;background:#000000b3;color:#fff;padding:5px 10px;border-radius:5px;z-index:9999;max-width:200px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.shape-text-container{width:70px;position:relative;overflow:hidden}.draggable{width:70px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:grab;-webkit-user-select:none;user-select:none;position:relative;display:inline-block}.dragging{width:auto;text-overflow:clip;cursor:grabbing;position:absolute;z-index:1000}.p-wrapper{width:70px;overflow-x:hidden}.shape-text-center{display:flex;justify-content:space-between;flex-direction:column;gap:10px;height:100%;margin-top:55px}.loading-indicator{position:fixed;top:25%;left:50%;transform:translate(-50%,-50%);background:transparent;color:#fff;padding:20px 30px;border-radius:10px;text-align:center;z-index:1000;display:flex;flex-direction:column;align-items:center;gap:8px}.loading-spinner{width:40px;height:40px;border:4px solid rgba(255,255,255,.3);border-top:4px solid #667eea;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.loading-indicator p{margin:0;font-size:16px;font-weight:500}.api-key-modal-overlay{position:fixed;inset:0;background:#0f172a99;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;z-index:2000}.api-key-modal{width:min(640px,92vw);background:#0f172a;color:#e2e8f0;border:1px solid #334155;border-radius:12px;box-shadow:0 20px 50px #00000073;padding:16px 18px}.api-key-modal-header{display:flex;align-items:center;justify-content:space-between;gap:12px}.api-key-modal-header h2{margin:0;font-size:20px;font-weight:700;color:#e2e8f0}.warning-icon{color:#fbbf24}.api-key-modal .close-btn{background:#0b1220;border:1px solid #334155;color:#94a3b8;border-radius:8px;padding:6px;cursor:pointer;line-height:0}.api-key-modal-body{margin-top:12px;display:flex;flex-direction:column;gap:10px}.modal-message,.modal-instruction{margin:0;color:#cbd5e1}.modal-message strong{color:#e5e7eb}.api-key-input-group{display:flex;align-items:center;gap:10px}.api-key-input{flex:1 1 auto;background:#0a1020;color:#e2e8f0;border:1px solid #334155;border-radius:8px;padding:10px 12px}.api-key-input:focus{outline:none;border-color:#2563eb;box-shadow:0 0 0 3px #2563eb33}.api-key-load-btn{background:#2563eb;color:#fff;border:none;border-radius:8px;padding:10px 12px;cursor:pointer;font-weight:600}.api-key-load-btn:hover{background:#1d4ed8}.modal-hint{display:flex;align-items:center;gap:8px;color:#94a3b8}.modal-hint a{color:#93c5fd}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
2281
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: ProfileComparisonLibComponent, isStandalone: false, selector: "lib-profile-comparison", inputs: { config: "config", backendMode: "backendMode", backendUrl: "backendUrl", fadeAllEdges: "fadeAllEdges" }, outputs: { matrixDataChange: "matrixDataChange", rawLLMOutputChange: "rawLLMOutputChange", viewProfileClick: "viewProfileClick" }, viewQueries: [{ propertyName: "leftContainer", first: true, predicate: ["leftContainer"], descendants: true }, { propertyName: "rightContainer", first: true, predicate: ["rightContainer"], descendants: true }, { propertyName: "profileFlex", first: true, predicate: ["profileFlex"], descendants: true }, { propertyName: "profileImgLeft", first: true, predicate: ["profileImgLeft"], descendants: true }, { propertyName: "profileImgRight", first: true, predicate: ["profileImgRight"], descendants: true }, { propertyName: "shapeContainer", first: true, predicate: ["shapeContainer"], descendants: true }, { propertyName: "shapeBg", first: true, predicate: ["shapeBg"], descendants: true }, { propertyName: "shapeBg1", first: true, predicate: ["shapeBg1"], descendants: true }, { propertyName: "shapeBg2", first: true, predicate: ["shapeBg2"], descendants: true }, { propertyName: "shapeTextLeft", first: true, predicate: ["shapeTextLeft"], descendants: true }, { propertyName: "shapeTextRight", first: true, predicate: ["shapeTextRight"], descendants: true }, { propertyName: "shapeTextCenter", first: true, predicate: ["shapeTextCenter"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"configure-backend-message\" *ngIf=\"!backendConfigured\">\r\n Configure backend\r\n</div>\r\n\r\n<div class=\"profile-screen\" *ngIf=\"backendConfigured\">\r\n <div class=\"backend-error-message\" *ngIf=\"backendError\">{{ backendError }}</div>\r\n <div #profileFlex class=\"profile-flex\" [class.fade-all]=\"fadeAllEdges\">\r\n <div #profileImgLeft class=\"profile-img left\" [class.fade-all]=\"fadeAllEdges\">\r\n <img\r\n [src]=\"user1Image\"\r\n alt=\"User 1\"\r\n [style.transform]=\"user1Transform\"\r\n [style.object-position]=\"user1ObjectPosition\"\r\n (load)=\"onUserImageLoad(1, $event)\"\r\n />\r\n </div>\r\n <div #profileImgRight class=\"profile-img right\" [class.fade-all]=\"fadeAllEdges\">\r\n <img\r\n [src]=\"user2Image\"\r\n alt=\"User 2\"\r\n [style.transform]=\"user2Transform\"\r\n [style.object-position]=\"user2ObjectPosition\"\r\n (load)=\"onUserImageLoad(2, $event)\"\r\n />\r\n <!-- [style.object-fit]=\"'cover'\" -->\r\n </div>\r\n </div>\r\n\r\n <div #shapeContainer class=\"shape\">\r\n <div #shapeBg class=\"shape-bg\" [class.fade-all]=\"fadeAllEdges\">\r\n <svg width=\"250\" height=\"350\" viewBox=\"0 0 250 350\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" preserveAspectRatio=\"xMaxYMid slice\" style=\"height: 350px;\" class=\"shape-bg1\" #shapeBg1>\r\n<g filter=\"url(#filter0_i_4_4137)\">\r\n<path d=\"M215.177 44.4924L-18.823 19.3104C-25.3264 18.6106 -31 23.7064 -31 30.2473V283.169C-31 289.914 -24.9854 295.07 -18.3196 294.04L215.68 257.869C221.043 257.04 225 252.407 225 246.981V55.4497C225 49.8302 220.764 45.0937 215.177 44.4924Z\" fill=\"#00CFE6\" fill-opacity=\"0.2\"/>\r\n</g>\r\n<path d=\"M215.177 44.4924L-18.823 19.3104C-25.3264 18.6106 -31 23.7064 -31 30.2473V283.169C-31 289.914 -24.9854 295.07 -18.3196 294.04L215.68 257.869C221.043 257.04 225 252.407 225 246.981V55.4497C225 49.8302 220.764 45.0937 215.177 44.4924Z\" fill=\"url(#paint0_radial_4_4137)\" fill-opacity=\"0.2\" stroke=\"url(#paint1_linear_4_4137)\" stroke-width=\"2\" stroke-miterlimit=\"3.99393\" stroke-linejoin=\"round\"/>\r\n<g filter=\"url(#filter1_ddddd_4_4137)\">\r\n<path d=\"M219.081 43.0755L-18.9193 17.2045C-24.8351 16.5614 -30 21.1953 -30 27.1459V287.318C-30 293.455 -24.5217 298.145 -18.4573 297.198L219.543 260.038C224.411 259.278 228 255.075 228 250.148V53.0303C228 47.9257 224.155 43.6271 219.081 43.0755Z\" stroke=\"#61C2AB\" stroke-opacity=\"0.8\" stroke-linejoin=\"round\" shape-rendering=\"crispEdges\"/>\r\n</g>\r\n<defs>\r\n<filter id=\"filter0_i_4_4137\" x=\"-31\" y=\"19.2461\" width=\"256\" height=\"274.925\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\r\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\r\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"BackgroundImageFix\" result=\"shape\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.95\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"arithmetic\" k2=\"-1\" k3=\"1\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0.516667 0 0 0 0 1 0 0 0 1 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"shape\" result=\"effect1_innerShadow_4_4137\"/>\r\n</filter>\r\n<filter id=\"filter1_ddddd_4_4137\" x=\"-46.2\" y=\"0.944715\" width=\"290.4\" height=\"312.575\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\r\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.95\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"BackgroundImageFix\" result=\"effect1_dropShadow_4_4137\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect1_dropShadow_4_4137\" result=\"effect2_dropShadow_4_4137\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect2_dropShadow_4_4137\" result=\"effect3_dropShadow_4_4137\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect3_dropShadow_4_4137\" result=\"effect4_dropShadow_4_4137\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect4_dropShadow_4_4137\" result=\"effect5_dropShadow_4_4137\"/>\r\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"effect5_dropShadow_4_4137\" result=\"shape\"/>\r\n</filter>\r\n<radialGradient id=\"paint0_radial_4_4137\" cx=\"0\" cy=\"0\" r=\"1\" gradientTransform=\"matrix(65.1034 -81.3964 74.955 49.415 97 157)\" gradientUnits=\"userSpaceOnUse\">\r\n<stop stop-color=\"#0051E6\" stop-opacity=\"0\"/>\r\n<stop offset=\"0.615385\" stop-color=\"#0051E6\" stop-opacity=\"0\"/>\r\n<stop offset=\"1\" stop-color=\"white\"/>\r\n</radialGradient>\r\n<linearGradient id=\"paint1_linear_4_4137\" x1=\"97\" y1=\"18\" x2=\"97\" y2=\"296\" gradientUnits=\"userSpaceOnUse\">\r\n<stop offset=\"0.380829\" stop-color=\"#52FFEB\"/>\r\n<stop offset=\"1\" stop-color=\"#319990\"/>\r\n</linearGradient>\r\n</defs>\r\n</svg>\r\n <svg width=\"250\" height=\"350\" viewBox=\"0 0 250 350\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" preserveAspectRatio=\"xMinYMid slice\" style=\"height: 350px;\" class=\"shape-bg2\" #shapeBg2>\r\n<g filter=\"url(#filter0_i_4_4141)\">\r\n<path d=\"M29.5864 44.9454L263.826 19.8065C270.329 19.1087 276 24.2041 276 30.7437V283.174C276 289.917 269.988 295.073 263.324 294.046L29.0843 257.937C23.7196 257.11 19.7602 252.476 19.7602 247.048V55.9032C19.7602 50.2824 23.9977 45.5452 29.5864 44.9454Z\" fill=\"#7B00E6\" fill-opacity=\"0.2\"/>\r\n</g>\r\n<path d=\"M29.5864 44.9454L263.826 19.8065C270.329 19.1087 276 24.2041 276 30.7437V283.174C276 289.917 269.988 295.073 263.324 294.046L29.0843 257.937C23.7196 257.11 19.7602 252.476 19.7602 247.048V55.9032C19.7602 50.2824 23.9977 45.5452 29.5864 44.9454Z\" fill=\"url(#paint0_radial_4_4141)\" fill-opacity=\"0.2\" stroke=\"url(#paint1_linear_4_4141)\" stroke-width=\"2\" stroke-miterlimit=\"3.99393\" stroke-linejoin=\"round\"/>\r\n<g filter=\"url(#filter1_ddddd_4_4141)\">\r\n<path d=\"M25.9212 43.077L264.37 17.2022C270.285 16.5603 275.449 21.194 275.449 27.1438V287.321C275.449 293.457 269.972 298.146 263.909 297.201L25.46 260.036C20.5905 259.277 17 255.074 17 250.146V53.032C17 47.9267 20.8457 43.6277 25.9212 43.077Z\" stroke=\"#C37DFF\" stroke-opacity=\"0.8\" stroke-linejoin=\"round\" shape-rendering=\"crispEdges\"/>\r\n</g>\r\n<defs>\r\n<filter id=\"filter0_i_4_4141\" x=\"19.7602\" y=\"19.7425\" width=\"256.24\" height=\"274.434\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\r\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\r\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"BackgroundImageFix\" result=\"shape\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.95\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"arithmetic\" k2=\"-1\" k3=\"1\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0.482353 0 0 0 0 0 0 0 0 0 0.901961 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"shape\" result=\"effect1_innerShadow_4_4141\"/>\r\n</filter>\r\n<filter id=\"filter1_ddddd_4_4141\" x=\"0.8\" y=\"0.942639\" width=\"290.849\" height=\"312.58\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\r\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"BackgroundImageFix\" result=\"effect1_dropShadow_4_4141\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect1_dropShadow_4_4141\" result=\"effect2_dropShadow_4_4141\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect2_dropShadow_4_4141\" result=\"effect3_dropShadow_4_4141\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect3_dropShadow_4_4141\" result=\"effect4_dropShadow_4_4141\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect4_dropShadow_4_4141\" result=\"effect5_dropShadow_4_4137\"/>\r\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"effect5_dropShadow_4_4137\" result=\"shape\"/>\r\n</filter>\r\n<radialGradient id=\"paint0_radial_4_4137\" cx=\"0\" cy=\"0\" r=\"1\" gradientTransform=\"matrix(65.1034 -81.3964 74.955 49.415 97 157)\" gradientUnits=\"userSpaceOnUse\">\r\n<stop stop-color=\"#0051E6\" stop-opacity=\"0\"/>\r\n<stop offset=\"0.615385\" stop-color=\"#0051E6\" stop-opacity=\"0\"/>\r\n<stop offset=\"1\" stop-color=\"white\"/>\r\n</radialGradient>\r\n<linearGradient id=\"paint1_linear_4_4137\" x1=\"97\" y1=\"18\" x2=\"97\" y2=\"296\" gradientUnits=\"userSpaceOnUse\">\r\n<stop offset=\"0.380829\" stop-color=\"#52FFEB\"/>\r\n<stop offset=\"1\" stop-color=\"#319990\"/>\r\n</linearGradient>\r\n</defs>\r\n</svg>\r\n </div>\r\n\r\n <div class=\"shape-text\">\r\n <div #shapeTextLeft class=\"shape-text-left\">\r\n <div class=\"scroll-container\" #leftContainer>\r\n <ng-container\r\n *ngFor=\"\r\n let interest of alignedPerson1Interests.length > 0\r\n ? alignedPerson1Interests\r\n : displayPerson1Interests.length > 0\r\n ? displayPerson1Interests\r\n : person1Interests;\r\n let i = index\r\n \"\r\n >\r\n <p\r\n class=\"shape-p\"\r\n *ngIf=\"!isInCenter(interest)\"\r\n [ngClass]=\"{'dash-item': interest === '-', 'spacer-item': interest === '----', 'aligned-item': interest !== '-' && interest !== '----'}\"\r\n >\r\n {{ interest === \"-\" ? \"\u2014\" : interest }}\r\n </p>\r\n </ng-container>\r\n </div>\r\n <h2 class=\"shape-h2\" (click)=\"onViewProfile('left')\">View Profile</h2>\r\n </div>\r\n\r\n <div #shapeTextCenter class=\"shape-text-center\">\r\n <ng-container *ngFor=\"let item of centerItem\">\r\n <p class=\"shape-p-center\">{{ item }}</p>\r\n </ng-container>\r\n </div>\r\n <div #shapeTextRight class=\"shape-text-right\">\r\n <div class=\"scroll-container\" #rightContainer>\r\n <ng-container\r\n *ngFor=\"\r\n let interest of alignedPerson2Interests.length > 0\r\n ? alignedPerson2Interests\r\n : displayPerson2Interests.length > 0\r\n ? displayPerson2Interests\r\n : person2Interests;\r\n let i = index\r\n \"\r\n >\r\n <p\r\n class=\"shape-p-right\"\r\n *ngIf=\"!isInCenter(interest)\"\r\n [ngClass]=\"{'dash-item': interest === '-', 'spacer-item': interest === '----', 'aligned-item': interest !== '-' && interest !== '----'}\"\r\n >\r\n {{ interest === \"-\" ? \"\u2014\" : interest }}\r\n </p>\r\n </ng-container>\r\n </div>\r\n <h2 class=\"shape-h2-right\" (click)=\"onViewProfile('right')\">View Profile</h2>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Loading indicator for alignment process -->\r\n <div *ngIf=\"isAligning\" class=\"loading-indicator\">\r\n <div class=\"loading-spinner\"></div>\r\n </div>\r\n</div>\r\n\r\n\r\n", styles: [".profile-img.left{-webkit-mask-image:linear-gradient(to right,rgb(0,0,0) 0%,rgb(0,0,0) calc(100% - var(--overlap-size, 40px)),rgba(0,0,0,0) 100%);-webkit-mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-image:linear-gradient(to right,#fff 0% calc(100% - var(--overlap-size, 40px)),#fff0);mask-size:100% 100%;mask-repeat:no-repeat;margin-right:calc(-.5 * var(--overlap-size, 40px))}.profile-img.right{-webkit-mask-image:linear-gradient(to left,rgb(0,0,0) 0%,rgb(0,0,0) calc(100% - var(--overlap-size, 40px)),rgba(0,0,0,0) 100%);-webkit-mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-image:linear-gradient(to left,#fff 0% calc(100% - var(--overlap-size, 40px)),#fff0);mask-size:100% 100%;mask-repeat:no-repeat;margin-left:calc(-.5 * var(--overlap-size, 40px))}.profile-img.fade-all{-webkit-mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent,black 15%,black 85%,transparent);-webkit-mask-composite:source-in;mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent,black 15%,black 85%,transparent);mask-composite:intersect}.profile-img.fade-all.left{-webkit-mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%);mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%)}.profile-img.fade-all.right{-webkit-mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to left,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%);mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to left,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%)}.configure-backend-message{padding:1rem;text-align:center;color:#bdc3c7;background:#34495e;border-radius:8px}.backend-error-message{padding:.75rem 1rem;text-align:center;color:#fef3c7;background:#b4530940;border:1px solid rgba(245,158,11,.5);border-radius:8px;margin-bottom:.5rem}.profile-screen{width:320px;max-width:100%;margin:0 auto;overflow:hidden;position:relative}.profile-flex{display:flex;width:100%;height:400px;overflow:hidden;background:linear-gradient(135deg,#1a1a1a,#0a0a0a);gap:0;margin:0;padding:0;position:relative;box-shadow:none;border:none;--overlap-size: 40px}.profile-flex.fade-all{-webkit-mask-image:linear-gradient(to right,transparent 0%,black 15%,black 85%,transparent 100%),linear-gradient(to bottom,transparent 0%,black 15%,black 85%,transparent 100%);-webkit-mask-composite:intersect;mask-image:linear-gradient(to right,transparent 0%,black 15%,black 85%,transparent 100%),linear-gradient(to bottom,transparent 0%,black 15%,black 85%,transparent 100%);mask-composite:intersect}.shape-bg1,.shape-bg2{position:absolute;max-width:250px;opacity:1;transition:transform .2s ease-out}.shape-bg1{z-index:1;left:0}.shape-bg2{right:0}.shape-bg.fade-all .shape-bg1{-webkit-mask-image:linear-gradient(to right,transparent 0%,black 20%,black 100%);mask-image:linear-gradient(to right,transparent 0%,black 20%,black 100%)}.shape-bg.fade-all .shape-bg2{-webkit-mask-image:linear-gradient(to left,transparent 0%,black 20%,black 100%);mask-image:linear-gradient(to left,transparent 0%,black 20%,black 100%)}.shape-bg svg{cursor:grab}.shape-bg svg:active{cursor:grabbing}.profile-img{width:180px;height:550px;position:relative;flex-shrink:0;background:linear-gradient(135deg,#1a1a1a,#0a0a0a);margin:0;padding:0;left:0;top:-75px;border:none;box-sizing:border-box;overflow:hidden;box-shadow:none;outline:none}.profile-img img{transition:transform .3s ease;width:100%;height:100%;object-fit:cover;display:block;object-position:center center;opacity:.75;transform-origin:center center;border:none;outline:none;box-shadow:none;position:relative;top:0;left:0;filter:contrast(1.2) brightness(1.1) saturate(1.1);image-rendering:auto}.shape{position:absolute;top:40px;width:100%}.shape-bg{position:relative}.shape-text{position:absolute;top:0;left:0;right:0;width:100%;height:100%;z-index:1;padding:0 1.25rem;box-sizing:border-box;pointer-events:none}.shape-text-left{margin-top:40px;text-align:left;position:absolute;top:0;bottom:0;right:calc(50% + 75px);z-index:2;pointer-events:auto;display:flex;flex-direction:column;align-items:flex-end}.shape-text-left .scroll-container{width:100%}.shape-text-center{position:absolute;left:0;right:0;margin:0 auto;width:fit-content;font-family:Gilroy,sans-serif;font-weight:700;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;text-align:center;display:flex;justify-content:center;align-items:center;flex-direction:column;text-shadow:2px 2px 4px rgba(0,0,0,.3);z-index:10;transition:transform .3s ease;pointer-events:none;top:50%;transform:translateY(-50%);padding:0 .625rem;margin-top:3.8125rem}.shape-text-right{margin-top:40px;text-align:right;position:absolute;top:0;bottom:0;left:calc(50% + 70px);z-index:2;pointer-events:auto;display:flex;flex-direction:column;align-items:flex-start}.shape-text-right .scroll-container{width:100%}.shape-p{font-family:Gilroy,sans-serif;font-weight:400;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;padding-bottom:.125rem;text-align:right;text-shadow:2px 2px 4px rgba(0,0,0,.3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;direction:rtl;-webkit-user-select:none;user-select:none;margin:0}.shape-p-center{font-family:Gilroy,sans-serif;font-weight:700;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;margin:0;text-align:center;display:flex;justify-content:center;align-items:center;text-shadow:2px 2px 4px rgba(0,0,0,.3)}.shape-p-right{font-family:Gilroy,sans-serif;font-weight:400;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;padding-bottom:.125rem;text-align:left;text-shadow:2px 2px 4px rgba(0,0,0,.3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin:0;-webkit-user-select:none;user-select:none}.dash-item{color:#ffffff4d!important;font-size:.625rem!important;height:.625rem}.spacer-item{height:auto!important;color:#fffc!important;font-weight:700;font-size:.75rem!important;letter-spacing:2px}.shape-h2{font-family:Calistoga,serif;font-weight:400;font-size:.625rem;letter-spacing:0%;color:#fff;text-shadow:2px 2px 4px rgba(0,0,0,.3);cursor:pointer;transition:all .3s ease;border:none;outline:none;white-space:nowrap;pointer-events:auto;z-index:100;position:relative;margin-left:.5rem}.shape-h2:hover{opacity:.8;transform:translateY(-2px)}.shape-h2-right{font-family:Calistoga,serif;font-weight:400;font-size:.625rem;letter-spacing:0%;color:#fff;text-shadow:2px 2px 4px rgba(0,0,0,.3);cursor:pointer;transition:all .3s ease;border:none;outline:none;white-space:nowrap;pointer-events:auto;z-index:100;position:relative;margin-right:3.75rem}.shape-h2-right:hover{opacity:.8;transform:translateY(-2px)}.scroll-when-long{display:inline-block;max-width:4.5625rem;white-space:nowrap;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch}.scroll-when-long::-webkit-scrollbar{height:2px}.scroll-when-long::-webkit-scrollbar-track{background:#eee;border-radius:.625rem}.scroll-when-long::-webkit-scrollbar-thumb{background:#888;border-radius:.625rem}.scroll-when-long::-webkit-scrollbar-thumb:hover{background:#555}.shape-text p{cursor:default;-webkit-user-select:none;user-select:none}.shape-text p:active{cursor:default}#drag-preview{position:absolute;pointer-events:none;display:none;background:#000000b3;color:#fff;padding:5px 10px;border-radius:5px;z-index:9999;max-width:200px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.shape-text-container{width:70px;position:relative;overflow:hidden}.draggable{width:70px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:grab;-webkit-user-select:none;user-select:none;position:relative;display:inline-block}.dragging{width:auto;text-overflow:clip;cursor:grabbing;position:absolute;z-index:1000}.p-wrapper{width:70px;overflow-x:hidden}.shape-text-center{display:flex;justify-content:space-between;flex-direction:column;gap:10px;height:100%;margin-top:55px}.loading-indicator{position:fixed;top:25%;left:50%;transform:translate(-50%,-50%);background:transparent;color:#fff;padding:20px 30px;border-radius:10px;text-align:center;z-index:1000;display:flex;flex-direction:column;align-items:center;gap:8px}.loading-spinner{width:40px;height:40px;border:4px solid rgba(255,255,255,.3);border-top:4px solid #667eea;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.loading-indicator p{margin:0;font-size:16px;font-weight:500}.api-key-modal-overlay{position:fixed;inset:0;background:#0f172a99;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;z-index:2000}.api-key-modal{width:min(640px,92vw);background:#0f172a;color:#e2e8f0;border:1px solid #334155;border-radius:12px;box-shadow:0 20px 50px #00000073;padding:16px 18px}.api-key-modal-header{display:flex;align-items:center;justify-content:space-between;gap:12px}.api-key-modal-header h2{margin:0;font-size:20px;font-weight:700;color:#e2e8f0}.warning-icon{color:#fbbf24}.api-key-modal .close-btn{background:#0b1220;border:1px solid #334155;color:#94a3b8;border-radius:8px;padding:6px;cursor:pointer;line-height:0}.api-key-modal-body{margin-top:12px;display:flex;flex-direction:column;gap:10px}.modal-message,.modal-instruction{margin:0;color:#cbd5e1}.modal-message strong{color:#e5e7eb}.api-key-input-group{display:flex;align-items:center;gap:10px}.api-key-input{flex:1 1 auto;background:#0a1020;color:#e2e8f0;border:1px solid #334155;border-radius:8px;padding:10px 12px}.api-key-input:focus{outline:none;border-color:#2563eb;box-shadow:0 0 0 3px #2563eb33}.api-key-load-btn{background:#2563eb;color:#fff;border:none;border-radius:8px;padding:10px 12px;cursor:pointer;font-weight:600}.api-key-load-btn:hover{background:#1d4ed8}.modal-hint{display:flex;align-items:center;gap:8px;color:#94a3b8}.modal-hint a{color:#93c5fd}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
3022
2282
  }
3023
2283
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ProfileComparisonLibComponent, decorators: [{
3024
2284
  type: Component,
3025
- args: [{ selector: 'lib-profile-comparison', standalone: false, template: "<div class=\"configure-backend-message\" *ngIf=\"!backendConfigured\">\n Configure backend\n</div>\n\n<div class=\"profile-screen\" *ngIf=\"backendConfigured\">\n <div class=\"backend-error-message\" *ngIf=\"backendError\">{{ backendError }}</div>\n <div #profileFlex class=\"profile-flex\" [class.fade-all]=\"fadeAllEdges\">\n <div #profileImgLeft class=\"profile-img left\" [class.fade-all]=\"fadeAllEdges\">\n <img\n [src]=\"user1Image\"\n alt=\"User 1\"\n [style.transform]=\"user1Transform\"\n [style.object-position]=\"user1ObjectPosition\"\n (load)=\"onUserImageLoad(1, $event)\"\n />\n </div>\n <div #profileImgRight class=\"profile-img right\" [class.fade-all]=\"fadeAllEdges\">\n <img\n [src]=\"user2Image\"\n alt=\"User 2\"\n [style.transform]=\"user2Transform\"\n [style.object-position]=\"user2ObjectPosition\"\n (load)=\"onUserImageLoad(2, $event)\"\n />\n <!-- [style.object-fit]=\"'cover'\" -->\n </div>\n </div>\n\n <div #shapeContainer class=\"shape\">\n <div #shapeBg class=\"shape-bg\" [class.fade-all]=\"fadeAllEdges\">\n <svg width=\"250\" height=\"350\" viewBox=\"0 0 250 350\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" preserveAspectRatio=\"xMaxYMid slice\" style=\"height: 350px;\" class=\"shape-bg1\" #shapeBg1>\n<g filter=\"url(#filter0_i_4_4137)\">\n<path d=\"M215.177 44.4924L-18.823 19.3104C-25.3264 18.6106 -31 23.7064 -31 30.2473V283.169C-31 289.914 -24.9854 295.07 -18.3196 294.04L215.68 257.869C221.043 257.04 225 252.407 225 246.981V55.4497C225 49.8302 220.764 45.0937 215.177 44.4924Z\" fill=\"#00CFE6\" fill-opacity=\"0.07\"/>\n</g>\n<path d=\"M215.177 44.4924L-18.823 19.3104C-25.3264 18.6106 -31 23.7064 -31 30.2473V283.169C-31 289.914 -24.9854 295.07 -18.3196 294.04L215.68 257.869C221.043 257.04 225 252.407 225 246.981V55.4497C225 49.8302 220.764 45.0937 215.177 44.4924Z\" fill=\"url(#paint0_radial_4_4137)\" fill-opacity=\"0.07\" stroke=\"url(#paint1_linear_4_4137)\" stroke-width=\"2\" stroke-miterlimit=\"3.99393\" stroke-linejoin=\"round\"/>\n<g filter=\"url(#filter1_ddddd_4_4137)\">\n<path d=\"M219.081 43.0755L-18.9193 17.2045C-24.8351 16.5614 -30 21.1953 -30 27.1459V287.318C-30 293.455 -24.5217 298.145 -18.4573 297.198L219.543 260.038C224.411 259.278 228 255.075 228 250.148V53.0303C228 47.9257 224.155 43.6271 219.081 43.0755Z\" stroke=\"#61C2AB\" stroke-opacity=\"0.3\" stroke-linejoin=\"round\" shape-rendering=\"crispEdges\"/>\n</g>\n<defs>\n<filter id=\"filter0_i_4_4137\" x=\"-31\" y=\"19.2461\" width=\"256\" height=\"274.925\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"BackgroundImageFix\" result=\"shape\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.95\"/>\n<feComposite in2=\"hardAlpha\" operator=\"arithmetic\" k2=\"-1\" k3=\"1\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0.516667 0 0 0 0 1 0 0 0 1 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"shape\" result=\"effect1_innerShadow_4_4137\"/>\n</filter>\n<filter id=\"filter1_ddddd_4_4137\" x=\"-46.2\" y=\"0.944715\" width=\"290.4\" height=\"312.575\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.95\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"BackgroundImageFix\" result=\"effect1_dropShadow_4_4137\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect1_dropShadow_4_4137\" result=\"effect2_dropShadow_4_4137\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect2_dropShadow_4_4137\" result=\"effect3_dropShadow_4_4137\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect3_dropShadow_4_4137\" result=\"effect4_dropShadow_4_4137\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect4_dropShadow_4_4137\" result=\"effect5_dropShadow_4_4137\"/>\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"effect5_dropShadow_4_4137\" result=\"shape\"/>\n</filter>\n<radialGradient id=\"paint0_radial_4_4137\" cx=\"0\" cy=\"0\" r=\"1\" gradientTransform=\"matrix(65.1034 -81.3964 74.955 49.415 97 157)\" gradientUnits=\"userSpaceOnUse\">\n<stop stop-color=\"#0051E6\" stop-opacity=\"0\"/>\n<stop offset=\"0.615385\" stop-color=\"#0051E6\" stop-opacity=\"0\"/>\n<stop offset=\"1\" stop-color=\"white\"/>\n</radialGradient>\n<linearGradient id=\"paint1_linear_4_4137\" x1=\"97\" y1=\"18\" x2=\"97\" y2=\"296\" gradientUnits=\"userSpaceOnUse\">\n<stop offset=\"0.380829\" stop-color=\"#52FFEB\"/>\n<stop offset=\"1\" stop-color=\"#319990\"/>\n</linearGradient>\n</defs>\n</svg>\n <svg width=\"250\" height=\"350\" viewBox=\"0 0 250 350\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" preserveAspectRatio=\"xMinYMid slice\" style=\"height: 350px;\" class=\"shape-bg2\" #shapeBg2>\n<g filter=\"url(#filter0_i_4_4141)\">\n<path d=\"M29.5864 44.9454L263.826 19.8065C270.329 19.1087 276 24.2041 276 30.7437V283.174C276 289.917 269.988 295.073 263.324 294.046L29.0843 257.937C23.7196 257.11 19.7602 252.476 19.7602 247.048V55.9032C19.7602 50.2824 23.9977 45.5452 29.5864 44.9454Z\" fill=\"#7B00E6\" fill-opacity=\"0.07\"/>\n</g>\n<path d=\"M29.5864 44.9454L263.826 19.8065C270.329 19.1087 276 24.2041 276 30.7437V283.174C276 289.917 269.988 295.073 263.324 294.046L29.0843 257.937C23.7196 257.11 19.7602 252.476 19.7602 247.048V55.9032C19.7602 50.2824 23.9977 45.5452 29.5864 44.9454Z\" fill=\"url(#paint0_radial_4_4141)\" fill-opacity=\"0.07\" stroke=\"url(#paint1_linear_4_4141)\" stroke-width=\"2\" stroke-miterlimit=\"3.99393\" stroke-linejoin=\"round\"/>\n<g filter=\"url(#filter1_ddddd_4_4141)\">\n<path d=\"M25.9212 43.077L264.37 17.2022C270.285 16.5603 275.449 21.194 275.449 27.1438V287.321C275.449 293.457 269.972 298.146 263.909 297.201L25.46 260.036C20.5905 259.277 17 255.074 17 250.146V53.032C17 47.9267 20.8457 43.6277 25.9212 43.077Z\" stroke=\"#C37DFF\" stroke-opacity=\"0.3\" stroke-linejoin=\"round\" shape-rendering=\"crispEdges\"/>\n</g>\n<defs>\n<filter id=\"filter0_i_4_4141\" x=\"19.7602\" y=\"19.7425\" width=\"256.24\" height=\"274.434\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"BackgroundImageFix\" result=\"shape\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.95\"/>\n<feComposite in2=\"hardAlpha\" operator=\"arithmetic\" k2=\"-1\" k3=\"1\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0.482353 0 0 0 0 0 0 0 0 0 0.901961 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"shape\" result=\"effect1_innerShadow_4_4141\"/>\n</filter>\n<filter id=\"filter1_ddddd_4_4141\" x=\"0.8\" y=\"0.942639\" width=\"290.849\" height=\"312.58\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"BackgroundImageFix\" result=\"effect1_dropShadow_4_4141\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect1_dropShadow_4_4141\" result=\"effect2_dropShadow_4_4141\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect2_dropShadow_4_4141\" result=\"effect3_dropShadow_4_4141\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect3_dropShadow_4_4141\" result=\"effect4_dropShadow_4_4141\"/>\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n<feOffset/>\n<feGaussianBlur stdDeviation=\"7.85\"/>\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\n<feBlend mode=\"normal\" in2=\"effect4_dropShadow_4_4141\" result=\"effect5_dropShadow_4_4137\"/>\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"effect5_dropShadow_4_4137\" result=\"shape\"/>\n</filter>\n<radialGradient id=\"paint0_radial_4_4137\" cx=\"0\" cy=\"0\" r=\"1\" gradientTransform=\"matrix(65.1034 -81.3964 74.955 49.415 97 157)\" gradientUnits=\"userSpaceOnUse\">\n<stop stop-color=\"#0051E6\" stop-opacity=\"0\"/>\n<stop offset=\"0.615385\" stop-color=\"#0051E6\" stop-opacity=\"0\"/>\n<stop offset=\"1\" stop-color=\"white\"/>\n</radialGradient>\n<linearGradient id=\"paint1_linear_4_4137\" x1=\"97\" y1=\"18\" x2=\"97\" y2=\"296\" gradientUnits=\"userSpaceOnUse\">\n<stop offset=\"0.380829\" stop-color=\"#52FFEB\"/>\n<stop offset=\"1\" stop-color=\"#319990\"/>\n</linearGradient>\n</defs>\n</svg>\n </div>\n\n <div class=\"shape-text\">\n <div #shapeTextLeft class=\"shape-text-left\">\n <div class=\"scroll-container\" #leftContainer>\n <ng-container\n *ngFor=\"\n let interest of alignedPerson1Interests.length > 0\n ? alignedPerson1Interests\n : displayPerson1Interests.length > 0\n ? displayPerson1Interests\n : person1Interests;\n let i = index\n \"\n >\n <p\n class=\"shape-p\"\n *ngIf=\"!isInCenter(interest)\"\n [ngClass]=\"{'dash-item': interest === '-', 'spacer-item': interest === '----', 'aligned-item': interest !== '-' && interest !== '----'}\"\n >\n {{ interest === \"-\" ? \"\u2014\" : interest }}\n </p>\n </ng-container>\n </div>\n <h2 class=\"shape-h2\" (click)=\"onViewProfile('left')\">View Profile</h2>\n </div>\n\n <div #shapeTextCenter class=\"shape-text-center\">\n <ng-container *ngFor=\"let item of centerItem\">\n <p class=\"shape-p-center\">{{ item }}</p>\n </ng-container>\n </div>\n <div #shapeTextRight class=\"shape-text-right\">\n <div class=\"scroll-container\" #rightContainer>\n <ng-container\n *ngFor=\"\n let interest of alignedPerson2Interests.length > 0\n ? alignedPerson2Interests\n : displayPerson2Interests.length > 0\n ? displayPerson2Interests\n : person2Interests;\n let i = index\n \"\n >\n <p\n class=\"shape-p-right\"\n *ngIf=\"!isInCenter(interest)\"\n [ngClass]=\"{'dash-item': interest === '-', 'spacer-item': interest === '----', 'aligned-item': interest !== '-' && interest !== '----'}\"\n >\n {{ interest === \"-\" ? \"\u2014\" : interest }}\n </p>\n </ng-container>\n </div>\n <h2 class=\"shape-h2-right\" (click)=\"onViewProfile('right')\">View Profile</h2>\n </div>\n </div>\n </div>\n\n <!-- Loading indicator for alignment process -->\n <div *ngIf=\"isAligning\" class=\"loading-indicator\">\n <div class=\"loading-spinner\"></div>\n </div>\n</div>\n\n\n", styles: [".profile-img.left{-webkit-mask-image:linear-gradient(to right,rgb(0,0,0) 0%,rgb(0,0,0) calc(100% - var(--overlap-size, 40px)),rgba(0,0,0,0) 100%);-webkit-mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-image:linear-gradient(to right,#fff 0% calc(100% - var(--overlap-size, 40px)),#fff0);mask-size:100% 100%;mask-repeat:no-repeat;margin-right:calc(-.5 * var(--overlap-size, 40px))}.profile-img.right{-webkit-mask-image:linear-gradient(to left,rgb(0,0,0) 0%,rgb(0,0,0) calc(100% - var(--overlap-size, 40px)),rgba(0,0,0,0) 100%);-webkit-mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-image:linear-gradient(to left,#fff 0% calc(100% - var(--overlap-size, 40px)),#fff0);mask-size:100% 100%;mask-repeat:no-repeat;margin-left:calc(-.5 * var(--overlap-size, 40px))}.profile-img.fade-all{-webkit-mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent,black 15%,black 85%,transparent);-webkit-mask-composite:source-in;mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent,black 15%,black 85%,transparent);mask-composite:intersect}.profile-img.fade-all.left{-webkit-mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%);mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%)}.profile-img.fade-all.right{-webkit-mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to left,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%);mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to left,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%)}.configure-backend-message{padding:1rem;text-align:center;color:#bdc3c7;background:#34495e;border-radius:8px}.backend-error-message{padding:.75rem 1rem;text-align:center;color:#fef3c7;background:#b4530940;border:1px solid rgba(245,158,11,.5);border-radius:8px;margin-bottom:.5rem}.profile-screen{width:320px;max-width:100%;margin:0 auto;overflow:hidden;position:relative}.profile-flex{display:flex;width:100%;height:400px;overflow:hidden;background:linear-gradient(135deg,#3a3a3a,#2a2a2a);gap:0;margin:0;padding:0;position:relative;box-shadow:none;border:none;--overlap-size: 40px}.profile-flex.fade-all{-webkit-mask-image:linear-gradient(to right,transparent 0%,black 15%,black 85%,transparent 100%),linear-gradient(to bottom,transparent 0%,black 15%,black 85%,transparent 100%);-webkit-mask-composite:intersect;mask-image:linear-gradient(to right,transparent 0%,black 15%,black 85%,transparent 100%),linear-gradient(to bottom,transparent 0%,black 15%,black 85%,transparent 100%);mask-composite:intersect}.shape-bg1,.shape-bg2{position:absolute;max-width:250px;opacity:.5;transition:transform .2s ease-out}.shape-bg1{z-index:1;left:0}.shape-bg2{right:0}.shape-bg.fade-all .shape-bg1{-webkit-mask-image:linear-gradient(to right,transparent 0%,black 20%,black 100%);mask-image:linear-gradient(to right,transparent 0%,black 20%,black 100%)}.shape-bg.fade-all .shape-bg2{-webkit-mask-image:linear-gradient(to left,transparent 0%,black 20%,black 100%);mask-image:linear-gradient(to left,transparent 0%,black 20%,black 100%)}.shape-bg svg{cursor:grab}.shape-bg svg:active{cursor:grabbing}.profile-img{width:180px;height:550px;position:relative;flex-shrink:0;background:linear-gradient(135deg,#3a3a3a,#2a2a2a);margin:0;padding:0;left:0;top:-75px;border:none;box-sizing:border-box;overflow:hidden;box-shadow:none;outline:none}.profile-img img{transition:transform .3s ease;width:100%;height:100%;object-fit:cover;display:block;object-position:center center;opacity:.6;transform-origin:center center;border:none;outline:none;box-shadow:none;position:relative;top:0;left:0;filter:contrast(1.2) brightness(1.1) saturate(1.1);image-rendering:auto}.shape{position:absolute;top:40px;width:100%}.shape-bg{position:relative}.shape-text{position:absolute;top:0;left:0;right:0;width:100%;height:100%;z-index:1;padding:0 1.25rem;box-sizing:border-box;pointer-events:none}.shape-text-left{margin-top:40px;text-align:left;position:absolute;top:0;bottom:0;right:calc(50% + 75px);z-index:2;pointer-events:auto;display:flex;flex-direction:column;align-items:flex-end}.shape-text-left .scroll-container{width:100%}.shape-text-center{position:absolute;left:0;right:0;margin:0 auto;width:fit-content;font-family:Gilroy,sans-serif;font-weight:700;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;text-align:center;display:flex;justify-content:center;align-items:center;flex-direction:column;text-shadow:2px 2px 4px rgba(0,0,0,.3);z-index:10;transition:transform .3s ease;pointer-events:none;top:50%;transform:translateY(-50%);padding:0 .625rem;margin-top:3.8125rem}.shape-text-right{margin-top:40px;text-align:right;position:absolute;top:0;bottom:0;left:calc(50% + 70px);z-index:2;pointer-events:auto;display:flex;flex-direction:column;align-items:flex-start}.shape-text-right .scroll-container{width:100%}.shape-p{font-family:Gilroy,sans-serif;font-weight:400;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;padding-bottom:.125rem;text-align:right;text-shadow:2px 2px 4px rgba(0,0,0,.3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;direction:rtl;-webkit-user-select:none;user-select:none;margin:0}.shape-p-center{font-family:Gilroy,sans-serif;font-weight:700;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;margin:0;text-align:center;display:flex;justify-content:center;align-items:center;text-shadow:2px 2px 4px rgba(0,0,0,.3)}.shape-p-right{font-family:Gilroy,sans-serif;font-weight:400;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;padding-bottom:.125rem;text-align:left;text-shadow:2px 2px 4px rgba(0,0,0,.3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin:0;-webkit-user-select:none;user-select:none}.dash-item{color:#ffffff4d!important;font-size:.625rem!important;height:.625rem}.spacer-item{height:auto!important;color:#fffc!important;font-weight:700;font-size:.75rem!important;letter-spacing:2px}.shape-h2{font-family:Calistoga,serif;font-weight:400;font-size:.625rem;letter-spacing:0%;color:#ffffff80;text-shadow:2px 2px 4px rgba(0,0,0,.3);cursor:pointer;transition:all .3s ease;border:none;outline:none;white-space:nowrap;pointer-events:auto;z-index:100;position:relative;margin-left:.5rem}.shape-h2:hover{opacity:.8;transform:translateY(-2px)}.shape-h2-right{font-family:Calistoga,serif;font-weight:400;font-size:.625rem;letter-spacing:0%;color:#ffffff80;text-shadow:2px 2px 4px rgba(0,0,0,.3);cursor:pointer;transition:all .3s ease;border:none;outline:none;white-space:nowrap;pointer-events:auto;z-index:100;position:relative;margin-right:3.75rem}.shape-h2-right:hover{opacity:.8;transform:translateY(-2px)}.scroll-when-long{display:inline-block;max-width:4.5625rem;white-space:nowrap;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch}.scroll-when-long::-webkit-scrollbar{height:2px}.scroll-when-long::-webkit-scrollbar-track{background:#eee;border-radius:.625rem}.scroll-when-long::-webkit-scrollbar-thumb{background:#888;border-radius:.625rem}.scroll-when-long::-webkit-scrollbar-thumb:hover{background:#555}.shape-text p{cursor:default;-webkit-user-select:none;user-select:none}.shape-text p:active{cursor:default}#drag-preview{position:absolute;pointer-events:none;display:none;background:#000000b3;color:#fff;padding:5px 10px;border-radius:5px;z-index:9999;max-width:200px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.shape-text-container{width:70px;position:relative;overflow:hidden}.draggable{width:70px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:grab;-webkit-user-select:none;user-select:none;position:relative;display:inline-block}.dragging{width:auto;text-overflow:clip;cursor:grabbing;position:absolute;z-index:1000}.p-wrapper{width:70px;overflow-x:hidden}.shape-text-center{display:flex;justify-content:space-between;flex-direction:column;gap:10px;height:100%;margin-top:55px}.loading-indicator{position:fixed;top:25%;left:50%;transform:translate(-50%,-50%);background:transparent;color:#fff;padding:20px 30px;border-radius:10px;text-align:center;z-index:1000;display:flex;flex-direction:column;align-items:center;gap:8px}.loading-spinner{width:40px;height:40px;border:4px solid rgba(255,255,255,.3);border-top:4px solid #667eea;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.loading-indicator p{margin:0;font-size:16px;font-weight:500}.api-key-modal-overlay{position:fixed;inset:0;background:#0f172a99;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;z-index:2000}.api-key-modal{width:min(640px,92vw);background:#0f172a;color:#e2e8f0;border:1px solid #334155;border-radius:12px;box-shadow:0 20px 50px #00000073;padding:16px 18px}.api-key-modal-header{display:flex;align-items:center;justify-content:space-between;gap:12px}.api-key-modal-header h2{margin:0;font-size:20px;font-weight:700;color:#e2e8f0}.warning-icon{color:#fbbf24}.api-key-modal .close-btn{background:#0b1220;border:1px solid #334155;color:#94a3b8;border-radius:8px;padding:6px;cursor:pointer;line-height:0}.api-key-modal-body{margin-top:12px;display:flex;flex-direction:column;gap:10px}.modal-message,.modal-instruction{margin:0;color:#cbd5e1}.modal-message strong{color:#e5e7eb}.api-key-input-group{display:flex;align-items:center;gap:10px}.api-key-input{flex:1 1 auto;background:#0a1020;color:#e2e8f0;border:1px solid #334155;border-radius:8px;padding:10px 12px}.api-key-input:focus{outline:none;border-color:#2563eb;box-shadow:0 0 0 3px #2563eb33}.api-key-load-btn{background:#2563eb;color:#fff;border:none;border-radius:8px;padding:10px 12px;cursor:pointer;font-weight:600}.api-key-load-btn:hover{background:#1d4ed8}.modal-hint{display:flex;align-items:center;gap:8px;color:#94a3b8}.modal-hint a{color:#93c5fd}\n"] }]
2285
+ args: [{ selector: 'lib-profile-comparison', standalone: false, template: "<div class=\"configure-backend-message\" *ngIf=\"!backendConfigured\">\r\n Configure backend\r\n</div>\r\n\r\n<div class=\"profile-screen\" *ngIf=\"backendConfigured\">\r\n <div class=\"backend-error-message\" *ngIf=\"backendError\">{{ backendError }}</div>\r\n <div #profileFlex class=\"profile-flex\" [class.fade-all]=\"fadeAllEdges\">\r\n <div #profileImgLeft class=\"profile-img left\" [class.fade-all]=\"fadeAllEdges\">\r\n <img\r\n [src]=\"user1Image\"\r\n alt=\"User 1\"\r\n [style.transform]=\"user1Transform\"\r\n [style.object-position]=\"user1ObjectPosition\"\r\n (load)=\"onUserImageLoad(1, $event)\"\r\n />\r\n </div>\r\n <div #profileImgRight class=\"profile-img right\" [class.fade-all]=\"fadeAllEdges\">\r\n <img\r\n [src]=\"user2Image\"\r\n alt=\"User 2\"\r\n [style.transform]=\"user2Transform\"\r\n [style.object-position]=\"user2ObjectPosition\"\r\n (load)=\"onUserImageLoad(2, $event)\"\r\n />\r\n <!-- [style.object-fit]=\"'cover'\" -->\r\n </div>\r\n </div>\r\n\r\n <div #shapeContainer class=\"shape\">\r\n <div #shapeBg class=\"shape-bg\" [class.fade-all]=\"fadeAllEdges\">\r\n <svg width=\"250\" height=\"350\" viewBox=\"0 0 250 350\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" preserveAspectRatio=\"xMaxYMid slice\" style=\"height: 350px;\" class=\"shape-bg1\" #shapeBg1>\r\n<g filter=\"url(#filter0_i_4_4137)\">\r\n<path d=\"M215.177 44.4924L-18.823 19.3104C-25.3264 18.6106 -31 23.7064 -31 30.2473V283.169C-31 289.914 -24.9854 295.07 -18.3196 294.04L215.68 257.869C221.043 257.04 225 252.407 225 246.981V55.4497C225 49.8302 220.764 45.0937 215.177 44.4924Z\" fill=\"#00CFE6\" fill-opacity=\"0.2\"/>\r\n</g>\r\n<path d=\"M215.177 44.4924L-18.823 19.3104C-25.3264 18.6106 -31 23.7064 -31 30.2473V283.169C-31 289.914 -24.9854 295.07 -18.3196 294.04L215.68 257.869C221.043 257.04 225 252.407 225 246.981V55.4497C225 49.8302 220.764 45.0937 215.177 44.4924Z\" fill=\"url(#paint0_radial_4_4137)\" fill-opacity=\"0.2\" stroke=\"url(#paint1_linear_4_4137)\" stroke-width=\"2\" stroke-miterlimit=\"3.99393\" stroke-linejoin=\"round\"/>\r\n<g filter=\"url(#filter1_ddddd_4_4137)\">\r\n<path d=\"M219.081 43.0755L-18.9193 17.2045C-24.8351 16.5614 -30 21.1953 -30 27.1459V287.318C-30 293.455 -24.5217 298.145 -18.4573 297.198L219.543 260.038C224.411 259.278 228 255.075 228 250.148V53.0303C228 47.9257 224.155 43.6271 219.081 43.0755Z\" stroke=\"#61C2AB\" stroke-opacity=\"0.8\" stroke-linejoin=\"round\" shape-rendering=\"crispEdges\"/>\r\n</g>\r\n<defs>\r\n<filter id=\"filter0_i_4_4137\" x=\"-31\" y=\"19.2461\" width=\"256\" height=\"274.925\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\r\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\r\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"BackgroundImageFix\" result=\"shape\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.95\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"arithmetic\" k2=\"-1\" k3=\"1\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0.516667 0 0 0 0 1 0 0 0 1 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"shape\" result=\"effect1_innerShadow_4_4137\"/>\r\n</filter>\r\n<filter id=\"filter1_ddddd_4_4137\" x=\"-46.2\" y=\"0.944715\" width=\"290.4\" height=\"312.575\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\r\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.95\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"BackgroundImageFix\" result=\"effect1_dropShadow_4_4137\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect1_dropShadow_4_4137\" result=\"effect2_dropShadow_4_4137\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect2_dropShadow_4_4137\" result=\"effect3_dropShadow_4_4137\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect3_dropShadow_4_4137\" result=\"effect4_dropShadow_4_4137\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect4_dropShadow_4_4137\" result=\"effect5_dropShadow_4_4137\"/>\r\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"effect5_dropShadow_4_4137\" result=\"shape\"/>\r\n</filter>\r\n<radialGradient id=\"paint0_radial_4_4137\" cx=\"0\" cy=\"0\" r=\"1\" gradientTransform=\"matrix(65.1034 -81.3964 74.955 49.415 97 157)\" gradientUnits=\"userSpaceOnUse\">\r\n<stop stop-color=\"#0051E6\" stop-opacity=\"0\"/>\r\n<stop offset=\"0.615385\" stop-color=\"#0051E6\" stop-opacity=\"0\"/>\r\n<stop offset=\"1\" stop-color=\"white\"/>\r\n</radialGradient>\r\n<linearGradient id=\"paint1_linear_4_4137\" x1=\"97\" y1=\"18\" x2=\"97\" y2=\"296\" gradientUnits=\"userSpaceOnUse\">\r\n<stop offset=\"0.380829\" stop-color=\"#52FFEB\"/>\r\n<stop offset=\"1\" stop-color=\"#319990\"/>\r\n</linearGradient>\r\n</defs>\r\n</svg>\r\n <svg width=\"250\" height=\"350\" viewBox=\"0 0 250 350\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" preserveAspectRatio=\"xMinYMid slice\" style=\"height: 350px;\" class=\"shape-bg2\" #shapeBg2>\r\n<g filter=\"url(#filter0_i_4_4141)\">\r\n<path d=\"M29.5864 44.9454L263.826 19.8065C270.329 19.1087 276 24.2041 276 30.7437V283.174C276 289.917 269.988 295.073 263.324 294.046L29.0843 257.937C23.7196 257.11 19.7602 252.476 19.7602 247.048V55.9032C19.7602 50.2824 23.9977 45.5452 29.5864 44.9454Z\" fill=\"#7B00E6\" fill-opacity=\"0.2\"/>\r\n</g>\r\n<path d=\"M29.5864 44.9454L263.826 19.8065C270.329 19.1087 276 24.2041 276 30.7437V283.174C276 289.917 269.988 295.073 263.324 294.046L29.0843 257.937C23.7196 257.11 19.7602 252.476 19.7602 247.048V55.9032C19.7602 50.2824 23.9977 45.5452 29.5864 44.9454Z\" fill=\"url(#paint0_radial_4_4141)\" fill-opacity=\"0.2\" stroke=\"url(#paint1_linear_4_4141)\" stroke-width=\"2\" stroke-miterlimit=\"3.99393\" stroke-linejoin=\"round\"/>\r\n<g filter=\"url(#filter1_ddddd_4_4141)\">\r\n<path d=\"M25.9212 43.077L264.37 17.2022C270.285 16.5603 275.449 21.194 275.449 27.1438V287.321C275.449 293.457 269.972 298.146 263.909 297.201L25.46 260.036C20.5905 259.277 17 255.074 17 250.146V53.032C17 47.9267 20.8457 43.6277 25.9212 43.077Z\" stroke=\"#C37DFF\" stroke-opacity=\"0.8\" stroke-linejoin=\"round\" shape-rendering=\"crispEdges\"/>\r\n</g>\r\n<defs>\r\n<filter id=\"filter0_i_4_4141\" x=\"19.7602\" y=\"19.7425\" width=\"256.24\" height=\"274.434\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\r\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\r\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"BackgroundImageFix\" result=\"shape\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.95\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"arithmetic\" k2=\"-1\" k3=\"1\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0.482353 0 0 0 0 0 0 0 0 0 0.901961 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"shape\" result=\"effect1_innerShadow_4_4141\"/>\r\n</filter>\r\n<filter id=\"filter1_ddddd_4_4141\" x=\"0.8\" y=\"0.942639\" width=\"290.849\" height=\"312.58\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\r\n<feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"BackgroundImageFix\" result=\"effect1_dropShadow_4_4141\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect1_dropShadow_4_4141\" result=\"effect2_dropShadow_4_4141\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect2_dropShadow_4_4141\" result=\"effect3_dropShadow_4_4141\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect3_dropShadow_4_4141\" result=\"effect4_dropShadow_4_4141\"/>\r\n<feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\r\n<feOffset/>\r\n<feGaussianBlur stdDeviation=\"7.85\"/>\r\n<feComposite in2=\"hardAlpha\" operator=\"out\"/>\r\n<feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0\"/>\r\n<feBlend mode=\"normal\" in2=\"effect4_dropShadow_4_4141\" result=\"effect5_dropShadow_4_4137\"/>\r\n<feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"effect5_dropShadow_4_4137\" result=\"shape\"/>\r\n</filter>\r\n<radialGradient id=\"paint0_radial_4_4137\" cx=\"0\" cy=\"0\" r=\"1\" gradientTransform=\"matrix(65.1034 -81.3964 74.955 49.415 97 157)\" gradientUnits=\"userSpaceOnUse\">\r\n<stop stop-color=\"#0051E6\" stop-opacity=\"0\"/>\r\n<stop offset=\"0.615385\" stop-color=\"#0051E6\" stop-opacity=\"0\"/>\r\n<stop offset=\"1\" stop-color=\"white\"/>\r\n</radialGradient>\r\n<linearGradient id=\"paint1_linear_4_4137\" x1=\"97\" y1=\"18\" x2=\"97\" y2=\"296\" gradientUnits=\"userSpaceOnUse\">\r\n<stop offset=\"0.380829\" stop-color=\"#52FFEB\"/>\r\n<stop offset=\"1\" stop-color=\"#319990\"/>\r\n</linearGradient>\r\n</defs>\r\n</svg>\r\n </div>\r\n\r\n <div class=\"shape-text\">\r\n <div #shapeTextLeft class=\"shape-text-left\">\r\n <div class=\"scroll-container\" #leftContainer>\r\n <ng-container\r\n *ngFor=\"\r\n let interest of alignedPerson1Interests.length > 0\r\n ? alignedPerson1Interests\r\n : displayPerson1Interests.length > 0\r\n ? displayPerson1Interests\r\n : person1Interests;\r\n let i = index\r\n \"\r\n >\r\n <p\r\n class=\"shape-p\"\r\n *ngIf=\"!isInCenter(interest)\"\r\n [ngClass]=\"{'dash-item': interest === '-', 'spacer-item': interest === '----', 'aligned-item': interest !== '-' && interest !== '----'}\"\r\n >\r\n {{ interest === \"-\" ? \"\u2014\" : interest }}\r\n </p>\r\n </ng-container>\r\n </div>\r\n <h2 class=\"shape-h2\" (click)=\"onViewProfile('left')\">View Profile</h2>\r\n </div>\r\n\r\n <div #shapeTextCenter class=\"shape-text-center\">\r\n <ng-container *ngFor=\"let item of centerItem\">\r\n <p class=\"shape-p-center\">{{ item }}</p>\r\n </ng-container>\r\n </div>\r\n <div #shapeTextRight class=\"shape-text-right\">\r\n <div class=\"scroll-container\" #rightContainer>\r\n <ng-container\r\n *ngFor=\"\r\n let interest of alignedPerson2Interests.length > 0\r\n ? alignedPerson2Interests\r\n : displayPerson2Interests.length > 0\r\n ? displayPerson2Interests\r\n : person2Interests;\r\n let i = index\r\n \"\r\n >\r\n <p\r\n class=\"shape-p-right\"\r\n *ngIf=\"!isInCenter(interest)\"\r\n [ngClass]=\"{'dash-item': interest === '-', 'spacer-item': interest === '----', 'aligned-item': interest !== '-' && interest !== '----'}\"\r\n >\r\n {{ interest === \"-\" ? \"\u2014\" : interest }}\r\n </p>\r\n </ng-container>\r\n </div>\r\n <h2 class=\"shape-h2-right\" (click)=\"onViewProfile('right')\">View Profile</h2>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Loading indicator for alignment process -->\r\n <div *ngIf=\"isAligning\" class=\"loading-indicator\">\r\n <div class=\"loading-spinner\"></div>\r\n </div>\r\n</div>\r\n\r\n\r\n", styles: [".profile-img.left{-webkit-mask-image:linear-gradient(to right,rgb(0,0,0) 0%,rgb(0,0,0) calc(100% - var(--overlap-size, 40px)),rgba(0,0,0,0) 100%);-webkit-mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-image:linear-gradient(to right,#fff 0% calc(100% - var(--overlap-size, 40px)),#fff0);mask-size:100% 100%;mask-repeat:no-repeat;margin-right:calc(-.5 * var(--overlap-size, 40px))}.profile-img.right{-webkit-mask-image:linear-gradient(to left,rgb(0,0,0) 0%,rgb(0,0,0) calc(100% - var(--overlap-size, 40px)),rgba(0,0,0,0) 100%);-webkit-mask-size:100% 100%;-webkit-mask-repeat:no-repeat;mask-image:linear-gradient(to left,#fff 0% calc(100% - var(--overlap-size, 40px)),#fff0);mask-size:100% 100%;mask-repeat:no-repeat;margin-left:calc(-.5 * var(--overlap-size, 40px))}.profile-img.fade-all{-webkit-mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent,black 15%,black 85%,transparent);-webkit-mask-composite:source-in;mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent,black 15%,black 85%,transparent);mask-composite:intersect}.profile-img.fade-all.left{-webkit-mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%);mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to right,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%)}.profile-img.fade-all.right{-webkit-mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to left,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%);mask-image:linear-gradient(to bottom,transparent 75px,black 125px,black 425px,transparent 475px),linear-gradient(to left,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%)}.configure-backend-message{padding:1rem;text-align:center;color:#bdc3c7;background:#34495e;border-radius:8px}.backend-error-message{padding:.75rem 1rem;text-align:center;color:#fef3c7;background:#b4530940;border:1px solid rgba(245,158,11,.5);border-radius:8px;margin-bottom:.5rem}.profile-screen{width:320px;max-width:100%;margin:0 auto;overflow:hidden;position:relative}.profile-flex{display:flex;width:100%;height:400px;overflow:hidden;background:linear-gradient(135deg,#1a1a1a,#0a0a0a);gap:0;margin:0;padding:0;position:relative;box-shadow:none;border:none;--overlap-size: 40px}.profile-flex.fade-all{-webkit-mask-image:linear-gradient(to right,transparent 0%,black 15%,black 85%,transparent 100%),linear-gradient(to bottom,transparent 0%,black 15%,black 85%,transparent 100%);-webkit-mask-composite:intersect;mask-image:linear-gradient(to right,transparent 0%,black 15%,black 85%,transparent 100%),linear-gradient(to bottom,transparent 0%,black 15%,black 85%,transparent 100%);mask-composite:intersect}.shape-bg1,.shape-bg2{position:absolute;max-width:250px;opacity:1;transition:transform .2s ease-out}.shape-bg1{z-index:1;left:0}.shape-bg2{right:0}.shape-bg.fade-all .shape-bg1{-webkit-mask-image:linear-gradient(to right,transparent 0%,black 20%,black 100%);mask-image:linear-gradient(to right,transparent 0%,black 20%,black 100%)}.shape-bg.fade-all .shape-bg2{-webkit-mask-image:linear-gradient(to left,transparent 0%,black 20%,black 100%);mask-image:linear-gradient(to left,transparent 0%,black 20%,black 100%)}.shape-bg svg{cursor:grab}.shape-bg svg:active{cursor:grabbing}.profile-img{width:180px;height:550px;position:relative;flex-shrink:0;background:linear-gradient(135deg,#1a1a1a,#0a0a0a);margin:0;padding:0;left:0;top:-75px;border:none;box-sizing:border-box;overflow:hidden;box-shadow:none;outline:none}.profile-img img{transition:transform .3s ease;width:100%;height:100%;object-fit:cover;display:block;object-position:center center;opacity:.75;transform-origin:center center;border:none;outline:none;box-shadow:none;position:relative;top:0;left:0;filter:contrast(1.2) brightness(1.1) saturate(1.1);image-rendering:auto}.shape{position:absolute;top:40px;width:100%}.shape-bg{position:relative}.shape-text{position:absolute;top:0;left:0;right:0;width:100%;height:100%;z-index:1;padding:0 1.25rem;box-sizing:border-box;pointer-events:none}.shape-text-left{margin-top:40px;text-align:left;position:absolute;top:0;bottom:0;right:calc(50% + 75px);z-index:2;pointer-events:auto;display:flex;flex-direction:column;align-items:flex-end}.shape-text-left .scroll-container{width:100%}.shape-text-center{position:absolute;left:0;right:0;margin:0 auto;width:fit-content;font-family:Gilroy,sans-serif;font-weight:700;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;text-align:center;display:flex;justify-content:center;align-items:center;flex-direction:column;text-shadow:2px 2px 4px rgba(0,0,0,.3);z-index:10;transition:transform .3s ease;pointer-events:none;top:50%;transform:translateY(-50%);padding:0 .625rem;margin-top:3.8125rem}.shape-text-right{margin-top:40px;text-align:right;position:absolute;top:0;bottom:0;left:calc(50% + 70px);z-index:2;pointer-events:auto;display:flex;flex-direction:column;align-items:flex-start}.shape-text-right .scroll-container{width:100%}.shape-p{font-family:Gilroy,sans-serif;font-weight:400;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;padding-bottom:.125rem;text-align:right;text-shadow:2px 2px 4px rgba(0,0,0,.3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;direction:rtl;-webkit-user-select:none;user-select:none;margin:0}.shape-p-center{font-family:Gilroy,sans-serif;font-weight:700;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;margin:0;text-align:center;display:flex;justify-content:center;align-items:center;text-shadow:2px 2px 4px rgba(0,0,0,.3)}.shape-p-right{font-family:Gilroy,sans-serif;font-weight:400;font-size:.75rem;line-height:100%;letter-spacing:0%;color:#fff;padding-bottom:.125rem;text-align:left;text-shadow:2px 2px 4px rgba(0,0,0,.3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin:0;-webkit-user-select:none;user-select:none}.dash-item{color:#ffffff4d!important;font-size:.625rem!important;height:.625rem}.spacer-item{height:auto!important;color:#fffc!important;font-weight:700;font-size:.75rem!important;letter-spacing:2px}.shape-h2{font-family:Calistoga,serif;font-weight:400;font-size:.625rem;letter-spacing:0%;color:#fff;text-shadow:2px 2px 4px rgba(0,0,0,.3);cursor:pointer;transition:all .3s ease;border:none;outline:none;white-space:nowrap;pointer-events:auto;z-index:100;position:relative;margin-left:.5rem}.shape-h2:hover{opacity:.8;transform:translateY(-2px)}.shape-h2-right{font-family:Calistoga,serif;font-weight:400;font-size:.625rem;letter-spacing:0%;color:#fff;text-shadow:2px 2px 4px rgba(0,0,0,.3);cursor:pointer;transition:all .3s ease;border:none;outline:none;white-space:nowrap;pointer-events:auto;z-index:100;position:relative;margin-right:3.75rem}.shape-h2-right:hover{opacity:.8;transform:translateY(-2px)}.scroll-when-long{display:inline-block;max-width:4.5625rem;white-space:nowrap;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch}.scroll-when-long::-webkit-scrollbar{height:2px}.scroll-when-long::-webkit-scrollbar-track{background:#eee;border-radius:.625rem}.scroll-when-long::-webkit-scrollbar-thumb{background:#888;border-radius:.625rem}.scroll-when-long::-webkit-scrollbar-thumb:hover{background:#555}.shape-text p{cursor:default;-webkit-user-select:none;user-select:none}.shape-text p:active{cursor:default}#drag-preview{position:absolute;pointer-events:none;display:none;background:#000000b3;color:#fff;padding:5px 10px;border-radius:5px;z-index:9999;max-width:200px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.shape-text-container{width:70px;position:relative;overflow:hidden}.draggable{width:70px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:grab;-webkit-user-select:none;user-select:none;position:relative;display:inline-block}.dragging{width:auto;text-overflow:clip;cursor:grabbing;position:absolute;z-index:1000}.p-wrapper{width:70px;overflow-x:hidden}.shape-text-center{display:flex;justify-content:space-between;flex-direction:column;gap:10px;height:100%;margin-top:55px}.loading-indicator{position:fixed;top:25%;left:50%;transform:translate(-50%,-50%);background:transparent;color:#fff;padding:20px 30px;border-radius:10px;text-align:center;z-index:1000;display:flex;flex-direction:column;align-items:center;gap:8px}.loading-spinner{width:40px;height:40px;border:4px solid rgba(255,255,255,.3);border-top:4px solid #667eea;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.loading-indicator p{margin:0;font-size:16px;font-weight:500}.api-key-modal-overlay{position:fixed;inset:0;background:#0f172a99;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;z-index:2000}.api-key-modal{width:min(640px,92vw);background:#0f172a;color:#e2e8f0;border:1px solid #334155;border-radius:12px;box-shadow:0 20px 50px #00000073;padding:16px 18px}.api-key-modal-header{display:flex;align-items:center;justify-content:space-between;gap:12px}.api-key-modal-header h2{margin:0;font-size:20px;font-weight:700;color:#e2e8f0}.warning-icon{color:#fbbf24}.api-key-modal .close-btn{background:#0b1220;border:1px solid #334155;color:#94a3b8;border-radius:8px;padding:6px;cursor:pointer;line-height:0}.api-key-modal-body{margin-top:12px;display:flex;flex-direction:column;gap:10px}.modal-message,.modal-instruction{margin:0;color:#cbd5e1}.modal-message strong{color:#e5e7eb}.api-key-input-group{display:flex;align-items:center;gap:10px}.api-key-input{flex:1 1 auto;background:#0a1020;color:#e2e8f0;border:1px solid #334155;border-radius:8px;padding:10px 12px}.api-key-input:focus{outline:none;border-color:#2563eb;box-shadow:0 0 0 3px #2563eb33}.api-key-load-btn{background:#2563eb;color:#fff;border:none;border-radius:8px;padding:10px 12px;cursor:pointer;font-weight:600}.api-key-load-btn:hover{background:#1d4ed8}.modal-hint{display:flex;align-items:center;gap:8px;color:#94a3b8}.modal-hint a{color:#93c5fd}\n"] }]
3026
2286
  }], ctorParameters: () => [{ type: ProfileComparisonBackendService }, { type: i0.Renderer2 }, { type: FileConversionService }, { type: ImageCompressionService }, { type: i0.ChangeDetectorRef }, { type: undefined, decorators: [{
3027
2287
  type: Optional
3028
2288
  }, {
@@ -3030,6 +2290,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
3030
2290
  args: [PROFILE_COMPARISON_VERBOSE_LOGGING]
3031
2291
  }] }], propDecorators: { config: [{
3032
2292
  type: Input
2293
+ }], backendMode: [{
2294
+ type: Input
2295
+ }], backendUrl: [{
2296
+ type: Input
3033
2297
  }], fadeAllEdges: [{
3034
2298
  type: Input
3035
2299
  }], matrixDataChange: [{
@@ -3083,9 +2347,7 @@ class ProfileComparisonLibModule {
3083
2347
  FormsModule,
3084
2348
  ReactiveFormsModule], exports: [ProfileComparisonLibComponent] });
3085
2349
  static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ProfileComparisonLibModule, providers: [
3086
- ProfileComparisonBackendService,
3087
- EmbeddingService,
3088
- OpenAIEmbeddingService
2350
+ ProfileComparisonBackendService
3089
2351
  ], imports: [CommonModule,
3090
2352
  HttpClientModule,
3091
2353
  FormsModule,
@@ -3107,9 +2369,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
3107
2369
  ProfileComparisonLibComponent
3108
2370
  ],
3109
2371
  providers: [
3110
- ProfileComparisonBackendService,
3111
- EmbeddingService,
3112
- OpenAIEmbeddingService
2372
+ ProfileComparisonBackendService
3113
2373
  ]
3114
2374
  }]
3115
2375
  }] });
@@ -3122,5 +2382,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
3122
2382
  * Generated bundle index. Do not edit.
3123
2383
  */
3124
2384
 
3125
- export { CachePersistenceService, EmbeddingService, FileConversionService, ImageCompressionService, OpenAIEmbeddingService, PROFILE_COMPARISON_API_BASE_URL, PROFILE_COMPARISON_VERBOSE_LOGGING, ProfileComparisonBackendService, ProfileComparisonLibComponent, ProfileComparisonLibModule, ProfileComparisonLibService, ProfileService };
2385
+ export { BACKEND_MODE_URLS, BackendMode, CachePersistenceService, FileConversionService, ImageCompressionService, OpenAIEmbeddingService, PROFILE_COMPARISON_API_BASE_URL, PROFILE_COMPARISON_VERBOSE_LOGGING, ProfileComparisonBackendService, ProfileComparisonLibComponent, ProfileComparisonLibModule, ProfileComparisonLibService, ProfileService, resolveBackendUrl };
3126
2386
  //# sourceMappingURL=naniteninja-profile-comparison-lib.mjs.map