@naniteninja/profile-comparison-lib 1.0.19 → 1.0.21

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,13 +1,11 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, Injectable, Optional, Inject, inject, EventEmitter, ViewChild, HostListener, Output, Input, Component, NgModule } from '@angular/core';
2
+ import { InjectionToken, Injectable, Optional, Inject, inject, EventEmitter, ViewChild, Output, Input, Component, NgModule } from '@angular/core';
3
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';
4
+ import { from, switchMap, map, Observable, of, BehaviorSubject, throwError, tap, retryWhen, concatMap, timer, catchError as catchError$1, forkJoin } from 'rxjs';
5
5
  import * as i1 from '@angular/common/http';
6
6
  import { HttpHeaders, HttpClient, HttpClientModule } from '@angular/common/http';
7
- import * as i2 from '@angular/common';
7
+ import * as i2_1 from '@angular/common';
8
8
  import { CommonModule } from '@angular/common';
9
- import * as i3 from '@naniteninja/ionic-lib';
10
- import { LibMarqueeModule } from '@naniteninja/ionic-lib';
11
9
  import { FormsModule, ReactiveFormsModule } from '@angular/forms';
12
10
 
13
11
  /** Backend mode for the profile comparison component. */
@@ -204,10 +202,10 @@ class CachePersistenceService {
204
202
  static STORE_OPENAI_EMBEDDINGS = STORE_OPENAI_EMBEDDINGS;
205
203
  static STORE_PROFILE_FACE = STORE_PROFILE_FACE;
206
204
  static STORE_PROFILE_COMPARE = STORE_PROFILE_COMPARE;
207
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: CachePersistenceService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
208
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: CachePersistenceService, providedIn: 'root' });
205
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: CachePersistenceService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
206
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: CachePersistenceService, providedIn: 'root' });
209
207
  }
210
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: CachePersistenceService, decorators: [{
208
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: CachePersistenceService, decorators: [{
211
209
  type: Injectable,
212
210
  args: [{ providedIn: 'root' }]
213
211
  }] });
@@ -223,6 +221,68 @@ class FileConversionService {
223
221
  urlToFile(url, fileName, mimeType) {
224
222
  return from(fetch(url)).pipe(switchMap(response => from(response.blob())), map(blob => new File([blob], fileName, { type: mimeType })));
225
223
  }
224
+ fetchUrlToDataUrl(url) {
225
+ return new Observable(observer => {
226
+ // Try fetch first (works for CORS-enabled URLs)
227
+ fetch(url)
228
+ .then(response => response.blob())
229
+ .then(blob => {
230
+ const reader = new FileReader();
231
+ reader.onload = () => {
232
+ observer.next(String(reader.result));
233
+ observer.complete();
234
+ };
235
+ reader.onerror = () => {
236
+ // Fallback to Image + canvas if FileReader fails
237
+ this.loadImageViaCanvas(url).subscribe({
238
+ next: dataUrl => {
239
+ observer.next(dataUrl);
240
+ observer.complete();
241
+ },
242
+ error: err => observer.error(err)
243
+ });
244
+ };
245
+ reader.readAsDataURL(blob);
246
+ })
247
+ .catch(() => {
248
+ // Fetch failed (likely CORS), fallback to Image + canvas
249
+ this.loadImageViaCanvas(url).subscribe({
250
+ next: dataUrl => {
251
+ observer.next(dataUrl);
252
+ observer.complete();
253
+ },
254
+ error: err => observer.error(err)
255
+ });
256
+ });
257
+ });
258
+ }
259
+ loadImageViaCanvas(url) {
260
+ return new Observable(observer => {
261
+ const img = new Image();
262
+ img.crossOrigin = 'anonymous';
263
+ img.onload = () => {
264
+ const canvas = document.createElement('canvas');
265
+ canvas.width = img.naturalWidth;
266
+ canvas.height = img.naturalHeight;
267
+ const ctx = canvas.getContext('2d');
268
+ if (!ctx) {
269
+ observer.error(new Error('Failed to get canvas context'));
270
+ return;
271
+ }
272
+ ctx.drawImage(img, 0, 0);
273
+ try {
274
+ const dataUrl = canvas.toDataURL('image/jpeg', 0.92);
275
+ observer.next(dataUrl);
276
+ observer.complete();
277
+ }
278
+ catch (err) {
279
+ observer.error(err);
280
+ }
281
+ };
282
+ img.onerror = () => observer.error(new Error('Failed to load image'));
283
+ img.src = url;
284
+ });
285
+ }
226
286
  isDataUrl(str) {
227
287
  return typeof str === 'string' && str.startsWith('data:');
228
288
  }
@@ -253,10 +313,10 @@ class FileConversionService {
253
313
  const mime = this.getMimeFromFilename(imageStr);
254
314
  return this.urlToFile(imageStr, fileBaseName, mime);
255
315
  }
256
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FileConversionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
257
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FileConversionService, providedIn: 'root' });
316
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: FileConversionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
317
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: FileConversionService, providedIn: 'root' });
258
318
  }
259
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FileConversionService, decorators: [{
319
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: FileConversionService, decorators: [{
260
320
  type: Injectable,
261
321
  args: [{
262
322
  providedIn: 'root'
@@ -372,10 +432,10 @@ class ImageCompressionService {
372
432
  return `${base}.webp`;
373
433
  return `${base}.jpg`;
374
434
  }
375
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ImageCompressionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
376
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ImageCompressionService, providedIn: 'root' });
435
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ImageCompressionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
436
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ImageCompressionService, providedIn: 'root' });
377
437
  }
378
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ImageCompressionService, decorators: [{
438
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ImageCompressionService, decorators: [{
379
439
  type: Injectable,
380
440
  args: [{
381
441
  providedIn: 'root'
@@ -1154,10 +1214,10 @@ class OpenAIEmbeddingService {
1154
1214
  this.persistence.clear(CachePersistenceService.STORE_OPENAI_SIMILARITY);
1155
1215
  this.persistence.clear(CachePersistenceService.STORE_OPENAI_EMBEDDINGS);
1156
1216
  }
1157
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: OpenAIEmbeddingService, deps: [{ token: i1.HttpClient }, { token: PROFILE_COMPARISON_API_BASE_URL, optional: true }, { token: CachePersistenceService }], target: i0.ɵɵFactoryTarget.Injectable });
1158
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: OpenAIEmbeddingService, providedIn: 'root' });
1217
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: OpenAIEmbeddingService, deps: [{ token: i1.HttpClient }, { token: PROFILE_COMPARISON_API_BASE_URL, optional: true }, { token: CachePersistenceService }], target: i0.ɵɵFactoryTarget.Injectable });
1218
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: OpenAIEmbeddingService, providedIn: 'root' });
1159
1219
  }
1160
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: OpenAIEmbeddingService, decorators: [{
1220
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: OpenAIEmbeddingService, decorators: [{
1161
1221
  type: Injectable,
1162
1222
  args: [{
1163
1223
  providedIn: 'root'
@@ -1189,10 +1249,10 @@ class ProfileComparisonLibService {
1189
1249
  getVersion() {
1190
1250
  return '0.0.0';
1191
1251
  }
1192
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ProfileComparisonLibService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1193
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ProfileComparisonLibService, providedIn: 'root' });
1252
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ProfileComparisonLibService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1253
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ProfileComparisonLibService, providedIn: 'root' });
1194
1254
  }
1195
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ProfileComparisonLibService, decorators: [{
1255
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ProfileComparisonLibService, decorators: [{
1196
1256
  type: Injectable,
1197
1257
  args: [{
1198
1258
  providedIn: 'root'
@@ -1446,10 +1506,10 @@ class ProfileService {
1446
1506
  this.compareInterestsCache.clear();
1447
1507
  this.persistence.clear(CachePersistenceService.STORE_PROFILE_COMPARE);
1448
1508
  }
1449
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ProfileService, deps: [{ token: i1.HttpClient }, { token: PROFILE_COMPARISON_API_BASE_URL, optional: true }, { token: CachePersistenceService }], target: i0.ɵɵFactoryTarget.Injectable });
1450
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ProfileService, providedIn: 'root' });
1509
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ProfileService, deps: [{ token: i1.HttpClient }, { token: PROFILE_COMPARISON_API_BASE_URL, optional: true }, { token: CachePersistenceService }], target: i0.ɵɵFactoryTarget.Injectable });
1510
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ProfileService, providedIn: 'root' });
1451
1511
  }
1452
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ProfileService, decorators: [{
1512
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ProfileService, decorators: [{
1453
1513
  type: Injectable,
1454
1514
  args: [{ providedIn: 'root' }]
1455
1515
  }], ctorParameters: () => [{ type: i1.HttpClient }, { type: undefined, decorators: [{
@@ -1505,10 +1565,10 @@ class ProfileComparisonBackendService {
1505
1565
  return throwError(() => err);
1506
1566
  }));
1507
1567
  }
1508
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ProfileComparisonBackendService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1509
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ProfileComparisonBackendService, providedIn: 'root' });
1568
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ProfileComparisonBackendService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1569
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ProfileComparisonBackendService, providedIn: 'root' });
1510
1570
  }
1511
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ProfileComparisonBackendService, decorators: [{
1571
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ProfileComparisonBackendService, decorators: [{
1512
1572
  type: Injectable,
1513
1573
  args: [{ providedIn: 'root' }]
1514
1574
  }], ctorParameters: () => [] });
@@ -1520,6 +1580,7 @@ class ProfileComparisonLibComponent {
1520
1580
  imageCompressionService;
1521
1581
  cdr;
1522
1582
  getVerboseLogging;
1583
+ static DEFAULT_OBJECT_POSITION = '50% 50%';
1523
1584
  static DEFAULT_EYE_ALIGNMENT_BIAS_PX = 50;
1524
1585
  static DEFAULT_IMAGE_FORMAT = 'image/jpeg';
1525
1586
  static DEFAULT_IMAGE_QUALITY_START = 0.85;
@@ -1533,7 +1594,6 @@ class ProfileComparisonLibComponent {
1533
1594
  static DEFAULT_MAX_HEIGHT = 1600;
1534
1595
  static SPACER_SMALL = '-';
1535
1596
  static SPACER_LARGE = '----';
1536
- static PLACEHOLDER = '--';
1537
1597
  static CONSOLIDATION_SEPARATOR_X = '×';
1538
1598
  static CONSOLIDATION_SEPARATOR_SLASH = '/';
1539
1599
  static FACEPP_RATE_LIMIT_DELAY_MS = 5000;
@@ -1541,8 +1601,8 @@ class ProfileComparisonLibComponent {
1541
1601
  // Drag and shape constants
1542
1602
  static SHAPE_BG_HEIGHT = '350px';
1543
1603
  static SHAPE_BG_OBJECT_FIT = 'cover';
1544
- static SHAPE_BG1_BACK_POSITION = 'right center';
1545
- static SHAPE_BG2_BACK_POSITION = 'left center';
1604
+ static SHAPE_BG1_OBJECT_POSITION = 'right';
1605
+ static SHAPE_BG2_OBJECT_POSITION = 'left';
1546
1606
  static DRAG_MIN_WIDTH = 200;
1547
1607
  static DRAG_MAX_WIDTH = 400;
1548
1608
  static MAX_DRAG_DISTANCE = 100;
@@ -1558,28 +1618,33 @@ class ProfileComparisonLibComponent {
1558
1618
  person3Interests: [],
1559
1619
  user1Image: '',
1560
1620
  user2Image: '',
1561
- edgeShading: true,
1562
- shadingColor: 'rgba(44, 40, 47, 0.7)'
1563
1621
  };
1622
+ user1File = null;
1623
+ user2File = null;
1564
1624
  backendMode = BackendMode.Real;
1565
1625
  backendUrl = null;
1566
1626
  fadeAllEdges = true;
1627
+ person1Name = 'Josh';
1628
+ person2Name = 'Amy';
1629
+ imageProxyPath = '';
1567
1630
  matrixDataChange = new EventEmitter();
1568
1631
  rawLLMOutputChange = new EventEmitter();
1569
1632
  viewProfileClick = new EventEmitter();
1570
- /** Fires when the user drags a frame to its maximum displacement and
1571
- * continues pulling past the edge. The parent can use this to swipe to
1572
- * the next/previous prospect in its own swiper. */
1573
- edgeDrag = new EventEmitter();
1574
1633
  selectedFile = null;
1575
1634
  firstImageData = null;
1576
1635
  secondImageData = null;
1577
1636
  user1Transform = '';
1578
1637
  user2Transform = '';
1638
+ user1Top = '0px';
1639
+ user1Height = '100%';
1640
+ user2Top = '0px';
1641
+ user2Height = '100%';
1579
1642
  alignmentCalculated = false;
1580
1643
  isAligning = false;
1581
- user1ObjectPosition = ProfileComparisonLibComponent.SHAPE_BG1_BACK_POSITION;
1582
- user2ObjectPosition = ProfileComparisonLibComponent.SHAPE_BG2_BACK_POSITION;
1644
+ /** Set when backend returns an error (e.g. 503 API keys not configured). */
1645
+ backendError = null;
1646
+ user1ObjectPosition = ProfileComparisonLibComponent.DEFAULT_OBJECT_POSITION;
1647
+ user2ObjectPosition = ProfileComparisonLibComponent.DEFAULT_OBJECT_POSITION;
1583
1648
  user1NaturalWidth = 0;
1584
1649
  user1NaturalHeight = 0;
1585
1650
  user2NaturalWidth = 0;
@@ -1609,34 +1674,6 @@ class ProfileComparisonLibComponent {
1609
1674
  alignedPerson2Interests = [];
1610
1675
  leftProfileClicked = false;
1611
1676
  rightProfileClicked = false;
1612
- leftShapeAnimating = false;
1613
- rightShapeAnimating = false;
1614
- similarities = [];
1615
- // ── Small-screen responsive helpers ──────────────────────────────────────
1616
- get viewProfileLeftX() {
1617
- const w = window.innerWidth;
1618
- if (w <= 360)
1619
- return 85;
1620
- if (w <= 400)
1621
- return 72;
1622
- if (w <= 600)
1623
- return 67;
1624
- return 40;
1625
- }
1626
- get viewProfileRightX() {
1627
- const w = window.innerWidth;
1628
- if (w <= 360)
1629
- return 171;
1630
- if (w <= 400)
1631
- return 184;
1632
- if (w <= 600)
1633
- return 188;
1634
- return 222;
1635
- }
1636
- onResize() {
1637
- this.cdr.detectChanges();
1638
- }
1639
- // ─────────────────────────────────────────────────────────────────────────
1640
1677
  sortedA = [];
1641
1678
  sortedB = [];
1642
1679
  sortedC = [];
@@ -1647,8 +1684,6 @@ class ProfileComparisonLibComponent {
1647
1684
  backendConfigured = false;
1648
1685
  /** The resolved backend URL computed from inputs and legacy token. */
1649
1686
  resolvedBackendUrl = null;
1650
- /** Flag to prevent click animations after a drag. */
1651
- wasDragging = false;
1652
1687
  compressionConfig = {
1653
1688
  maxWidth: ProfileComparisonLibComponent.DEFAULT_MAX_WIDTH,
1654
1689
  maxHeight: ProfileComparisonLibComponent.DEFAULT_MAX_HEIGHT,
@@ -1666,11 +1701,15 @@ class ProfileComparisonLibComponent {
1666
1701
  fetchRequestId = 0;
1667
1702
  leftContainer;
1668
1703
  rightContainer;
1669
- leftFrame;
1670
- rightFrame;
1671
- leftBgFrame;
1672
- rightBgFrame;
1704
+ profileFlex;
1705
+ profileImgLeft;
1706
+ profileImgRight;
1673
1707
  shapeContainer;
1708
+ shapeBg;
1709
+ shapeBg1;
1710
+ shapeBg2;
1711
+ shapeTextLeft;
1712
+ shapeTextRight;
1674
1713
  shapeTextCenter;
1675
1714
  constructor(backendService, renderer, fileConversionService, imageCompressionService, cdr, getVerboseLogging) {
1676
1715
  this.backendService = backendService;
@@ -1711,15 +1750,19 @@ class ProfileComparisonLibComponent {
1711
1750
  this.user1Transform = 'translateY(0px)';
1712
1751
  this.user2Transform = 'translateY(0px)';
1713
1752
  this.alignmentCalculated = false;
1714
- this.log('ngOnInit', 'starting compressConfigImagesIfNeeded then fetchComparison');
1715
- this.compressConfigImagesIfNeeded().subscribe({
1753
+ this.isAligning = true;
1754
+ this.cdr.detectChanges();
1755
+ this.log('ngOnInit', 'starting compressInputImages then fetchComparison');
1756
+ this.compressInputImages().subscribe({
1716
1757
  next: () => {
1717
- this.log('ngOnInit', 'compressConfigImagesIfNeeded done, calling fetchComparison');
1758
+ this.log('ngOnInit', 'compressInputImages done, calling fetchComparison');
1759
+ this.isAligning = false;
1718
1760
  this.fetchComparison();
1719
1761
  },
1720
1762
  error: (e) => {
1721
1763
  console.warn('Config image compression failed, continuing:', e);
1722
- this.log('ngOnInit', 'compressConfigImagesIfNeeded error, calling fetchComparison anyway');
1764
+ this.log('ngOnInit', 'compressInputImages error, calling fetchComparison anyway');
1765
+ this.isAligning = false;
1723
1766
  this.fetchComparison();
1724
1767
  }
1725
1768
  });
@@ -1733,24 +1776,17 @@ class ProfileComparisonLibComponent {
1733
1776
  this.backendConfigured = !!this.resolvedBackendUrl;
1734
1777
  this.log('ngOnChanges', 'backend inputs changed', 'resolvedUrl', this.resolvedBackendUrl ?? '(null)');
1735
1778
  }
1736
- if (changes['fadeAllEdges']) {
1737
- this.config.edgeShading = this.fadeAllEdges;
1738
- }
1739
1779
  if (changes['config']) {
1740
1780
  const first = changes['config'].firstChange;
1741
1781
  this.log('ngOnChanges', 'config changed', 'firstChange', first, 'backendConfigured', this.backendConfigured);
1742
- // Sync fadeAllEdges if it was explicitly changed via Input but not in config
1743
- if (this.config.edgeShading === undefined) {
1744
- this.config.edgeShading = this.fadeAllEdges;
1745
- }
1746
1782
  this.updateConfigProperties();
1747
1783
  if (!this.backendConfigured) {
1748
1784
  this.log('ngOnChanges', 'skipping fetch: backend not configured');
1749
1785
  return;
1750
1786
  }
1751
1787
  if (!first) {
1752
- this.log('ngOnChanges', 'running compressConfigImagesIfNeeded then fetchComparison');
1753
- this.compressConfigImagesIfNeeded().subscribe({
1788
+ this.log('ngOnChanges', 'running compressInputImages then fetchComparison');
1789
+ this.compressInputImages().subscribe({
1754
1790
  next: () => {
1755
1791
  this.log('ngOnChanges', 'compress done, calling fetchComparison');
1756
1792
  this.fetchComparison();
@@ -1767,19 +1803,12 @@ class ProfileComparisonLibComponent {
1767
1803
  }
1768
1804
  }
1769
1805
  }
1770
- /**
1771
- * Returns the type of dash based on similarity score:
1772
- * - 'high': Very similar (Short single dash, 0.5 opacity)
1773
- * - 'medium': Almost similar (Standard 1 dash)
1774
- * - 'low': Very dissimilar (4 small dashes)
1775
- */
1776
- getDashType(index) {
1777
- const score = this.similarities[index] ?? 0;
1778
- if (score > 0.8)
1779
- return 'high';
1780
- if (score > 0.4)
1781
- return 'medium';
1782
- return 'low';
1806
+ /** Use for template: hide left/right item when it's a shared (center) item. Compares case-insensitively. */
1807
+ isInCenter(interest) {
1808
+ if (!interest || !this.centerItem?.length)
1809
+ return false;
1810
+ const n = String(interest).trim().toLowerCase();
1811
+ return this.centerItem.some((c) => String(c).trim().toLowerCase() === n);
1783
1812
  }
1784
1813
  fetchComparison() {
1785
1814
  this.log('fetchComparison', 'start');
@@ -1793,6 +1822,8 @@ class ProfileComparisonLibComponent {
1793
1822
  }
1794
1823
  const requestId = ++this.fetchRequestId;
1795
1824
  this.isAligning = true;
1825
+ this.backendError = null;
1826
+ this.cdr.detectChanges();
1796
1827
  const normalized = this.buildConfigWithDefaults(this.config, this.baseConfig);
1797
1828
  const configToSend = {
1798
1829
  person1Interests: normalized.person1Interests,
@@ -1815,17 +1846,32 @@ class ProfileComparisonLibComponent {
1815
1846
  }
1816
1847
  this.log('fetchComparison', 'backend response received', { aligned1: payload?.alignedPerson1Interests?.length, aligned2: payload?.alignedPerson2Interests?.length });
1817
1848
  this.isAligning = false;
1818
- const a1 = payload.alignedPerson1Interests ?? [];
1819
- const a2 = payload.alignedPerson2Interests ?? [];
1820
- this.alignedPerson1Interests = a1;
1821
- this.alignedPerson2Interests = a2;
1822
- this.centerItem = payload.centerItem ?? [];
1823
- this.similarities = payload.similarities ?? [];
1824
- this.displayPerson1Interests = this.alignedPerson1Interests;
1825
- this.displayPerson2Interests = this.alignedPerson2Interests;
1849
+ this.backendError = null;
1826
1850
  this.matrixData = payload.matrixData ?? null;
1827
1851
  if (this.matrixData)
1828
1852
  this.matrixDataChange.emit(this.matrixData);
1853
+ const a1 = payload.alignedPerson1Interests ?? [];
1854
+ const a2 = payload.alignedPerson2Interests ?? [];
1855
+ const sameLists = a1.length === a2.length && a1.every((v, i) => String(v).trim().toLowerCase() === String(a2[i]).trim().toLowerCase());
1856
+ if (sameLists && a1.length > 0) {
1857
+ this.log('fetchComparison', 'backend returned identical aligned lists; using raw lists to keep sides distinct', { len: a1.length });
1858
+ this.alignedPerson1Interests = [];
1859
+ this.alignedPerson2Interests = [];
1860
+ this.displayPerson1Interests = this.person1Interests;
1861
+ this.displayPerson2Interests = this.person2Interests;
1862
+ this.centerItem = payload.centerItem ?? [];
1863
+ }
1864
+ else {
1865
+ const rawCenter = payload.centerItem ?? [];
1866
+ const list1ToUse = this.person1Interests?.length ? this.person1Interests : a1;
1867
+ const list2ToUse = this.person2Interests?.length ? this.person2Interests : a2;
1868
+ const reordered = this.reorderInterestsByMatrix(list1ToUse, list2ToUse, rawCenter);
1869
+ this.alignedPerson1Interests = reordered.p1;
1870
+ this.alignedPerson2Interests = reordered.p2;
1871
+ this.displayPerson1Interests = reordered.p1;
1872
+ this.displayPerson2Interests = reordered.p2;
1873
+ this.centerItem = reordered.center;
1874
+ }
1829
1875
  if (payload.rawLLMOutput != null) {
1830
1876
  this.rawLLMOutput = payload.rawLLMOutput;
1831
1877
  this.rawLLMOutputChange.emit(payload.rawLLMOutput);
@@ -1851,6 +1897,7 @@ class ProfileComparisonLibComponent {
1851
1897
  }
1852
1898
  this.log('fetchComparison', 'backend error', err);
1853
1899
  const msg = err?.error?.message ?? err?.message ?? err?.statusText ?? (typeof err?.error === 'string' ? err.error : null) ?? 'Request failed';
1900
+ this.backendError = msg;
1854
1901
  console.error('[ProfileComparisonLib] fetchComparison error', err?.status ?? '', msg);
1855
1902
  this.isAligning = false;
1856
1903
  this.cdr.detectChanges();
@@ -1864,6 +1911,10 @@ class ProfileComparisonLibComponent {
1864
1911
  this.person2Interests = [...normalized.person2Interests];
1865
1912
  this.user1Image = normalized.user1Image;
1866
1913
  this.user2Image = normalized.user2Image;
1914
+ if (normalized.person1Name)
1915
+ this.person1Name = normalized.person1Name;
1916
+ if (normalized.person2Name)
1917
+ this.person2Name = normalized.person2Name;
1867
1918
  }
1868
1919
  buildConfigWithDefaults(cfg, base) {
1869
1920
  const list = (val, fb) => {
@@ -1882,13 +1933,13 @@ class ProfileComparisonLibComponent {
1882
1933
  };
1883
1934
  const safe = cfg || {};
1884
1935
  return {
1936
+ person1Name: safe.person1Name ? str(safe.person1Name, '') : undefined,
1937
+ person2Name: safe.person2Name ? str(safe.person2Name, '') : undefined,
1885
1938
  person1Interests: list(safe.person1Interests, base.person1Interests),
1886
1939
  person2Interests: list(safe.person2Interests, base.person2Interests),
1887
1940
  person3Interests: list(safe.person3Interests, base.person3Interests),
1888
1941
  user1Image: str(safe.user1Image, base.user1Image),
1889
1942
  user2Image: str(safe.user2Image, base.user2Image),
1890
- edgeShading: safe.edgeShading ?? base.edgeShading ?? true,
1891
- shadingColor: safe.shadingColor ?? base.shadingColor ?? 'rgba(44, 40, 47, 0.7)',
1892
1943
  };
1893
1944
  }
1894
1945
  onUserImageLoad(which, event) {
@@ -1918,9 +1969,9 @@ class ProfileComparisonLibComponent {
1918
1969
  const natH = which === 1 ? this.user1NaturalHeight : this.user2NaturalHeight;
1919
1970
  if (!face || !natW || !natH) {
1920
1971
  if (which === 1)
1921
- this.user1ObjectPosition = ProfileComparisonLibComponent.SHAPE_BG1_BACK_POSITION;
1972
+ this.user1ObjectPosition = ProfileComparisonLibComponent.DEFAULT_OBJECT_POSITION;
1922
1973
  else
1923
- this.user2ObjectPosition = ProfileComparisonLibComponent.SHAPE_BG2_BACK_POSITION;
1974
+ this.user2ObjectPosition = ProfileComparisonLibComponent.DEFAULT_OBJECT_POSITION;
1924
1975
  return;
1925
1976
  }
1926
1977
  const eyes = this.getEyeCoordinatesFromFace(face);
@@ -2016,7 +2067,7 @@ class ProfileComparisonLibComponent {
2016
2067
  }
2017
2068
  getProfileContainerSize(which) {
2018
2069
  try {
2019
- const el = (which === 1 ? this.leftFrame : this.rightFrame)?.nativeElement || null;
2070
+ const el = (which === 1 ? this.profileImgLeft : this.profileImgRight)?.nativeElement || null;
2020
2071
  if (!el)
2021
2072
  return { width: 0, height: 0 };
2022
2073
  const rect = el.getBoundingClientRect();
@@ -2027,396 +2078,338 @@ class ProfileComparisonLibComponent {
2027
2078
  }
2028
2079
  }
2029
2080
  getOverlapSizeCssPx() {
2030
- return 40;
2081
+ try {
2082
+ const root = this.profileFlex?.nativeElement || null;
2083
+ const fallback = 40;
2084
+ if (!root)
2085
+ return fallback;
2086
+ const val = getComputedStyle(root).getPropertyValue('--overlap-size').trim();
2087
+ const parsed = parseFloat(val);
2088
+ return isNaN(parsed) ? fallback : parsed;
2089
+ }
2090
+ catch {
2091
+ return 40;
2092
+ }
2031
2093
  }
2032
2094
  setOverlapSizeCssPx(px) {
2033
- // deprecated
2095
+ try {
2096
+ const root = this.profileFlex?.nativeElement || null;
2097
+ if (!root)
2098
+ return;
2099
+ const clamped = Math.max(8, Math.min(80, Math.round(px)));
2100
+ root.style.setProperty('--overlap-size', `${clamped}px`);
2101
+ }
2102
+ catch { }
2034
2103
  }
2035
2104
  waitForImagesAndInitDrag() {
2036
- const leftFrame = this.leftFrame?.nativeElement;
2037
- const rightFrame = this.rightFrame?.nativeElement;
2038
- if (!leftFrame || !rightFrame) {
2039
- setTimeout(() => this.waitForImagesAndInitDrag(), 100);
2105
+ const shapeBg1 = this.shapeBg1?.nativeElement;
2106
+ const shapeBg2 = this.shapeBg2?.nativeElement;
2107
+ if (!shapeBg1 || !shapeBg2) {
2040
2108
  return;
2041
2109
  }
2042
- setTimeout(() => {
2043
- this.initDrag();
2044
- // Apply initial edge mask state
2045
- setTimeout(() => this.updateEdgeMasks(), 50);
2046
- }, 100);
2047
- }
2048
- /**
2049
- * Toggles .edge-at-left-border / .edge-at-right-border classes on both
2050
- * interactive and background frames based on their outer edge position
2051
- * relative to the container boundary.
2052
- */
2053
- updateEdgeMasks() {
2054
- const container = this.shapeContainer?.nativeElement;
2055
- if (!container)
2056
- return;
2057
- const containerRect = container.getBoundingClientRect();
2058
- const frames = [
2059
- { side: 'left', el: this.leftFrame?.nativeElement, bg: this.leftBgFrame?.nativeElement },
2060
- { side: 'right', el: this.rightFrame?.nativeElement, bg: this.rightBgFrame?.nativeElement }
2061
- ];
2062
- frames.forEach(f => {
2063
- if (!f.el)
2064
- return;
2065
- const rect = f.el.getBoundingClientRect();
2066
- const className = `edge-at-${f.side}-border`;
2067
- const touches = f.side === 'left'
2068
- ? rect.left <= containerRect.left + 1
2069
- : rect.right >= containerRect.right - 1;
2070
- f.el.classList.toggle(className, touches);
2071
- if (f.bg)
2072
- f.bg.classList.toggle(className, touches);
2073
- });
2110
+ const checkImagesLoaded = () => {
2111
+ // For SVG elements, check if they're rendered instead of naturalWidth
2112
+ const img1Loaded = shapeBg1 && shapeBg1.getBoundingClientRect().width > 0;
2113
+ const img2Loaded = shapeBg2 && shapeBg2.getBoundingClientRect().width > 0;
2114
+ if (img1Loaded && img2Loaded) {
2115
+ setTimeout(() => this.initDrag(), 50);
2116
+ }
2117
+ else {
2118
+ setTimeout(checkImagesLoaded, 100);
2119
+ }
2120
+ };
2121
+ checkImagesLoaded();
2074
2122
  }
2075
2123
  initDrag() {
2076
- const leftFrame = this.leftFrame?.nativeElement;
2077
- const rightFrame = this.rightFrame?.nativeElement;
2078
- const leftBgFrame = this.leftBgFrame?.nativeElement;
2079
- const rightBgFrame = this.rightBgFrame?.nativeElement;
2080
- const shapeTextCenter = this.shapeTextCenter?.nativeElement;
2081
- if (!leftFrame || !rightFrame)
2124
+ const shapeBg1 = this.shapeBg1?.nativeElement;
2125
+ const shapeBg2 = this.shapeBg2?.nativeElement;
2126
+ const shapeTextLeft = this.shapeTextLeft?.nativeElement || null;
2127
+ const shapeTextRight = this.shapeTextRight?.nativeElement || null;
2128
+ if (!shapeBg1 || !shapeBg2 || !shapeTextLeft || !shapeTextRight)
2082
2129
  return;
2083
- // Glass SVG references for shadow fade-on-drag behaviour
2084
- const leftGlass = leftFrame.querySelector('.profile-comparison__frame-svg--glass');
2085
- const rightGlass = rightFrame.querySelector('.profile-comparison__frame-svg--glass');
2130
+ // Cast to HTMLElement to access style properties conveniently, as SVGElement style handling can be similar in this context
2131
+ const shapeBg1El = shapeBg1;
2132
+ const shapeBg2El = shapeBg2;
2133
+ shapeBg1El.style.height = ProfileComparisonLibComponent.SHAPE_BG_HEIGHT;
2134
+ // shapeBg1El.style.objectFit = ProfileComparisonLibComponent.SHAPE_BG_OBJECT_FIT;
2135
+ // shapeBg1El.style.objectPosition = ProfileComparisonLibComponent.SHAPE_BG1_OBJECT_POSITION;
2136
+ shapeBg2El.style.height = ProfileComparisonLibComponent.SHAPE_BG_HEIGHT;
2137
+ // shapeBg2El.style.objectFit = ProfileComparisonLibComponent.SHAPE_BG_OBJECT_FIT;
2138
+ // shapeBg2El.style.objectPosition = ProfileComparisonLibComponent.SHAPE_BG2_OBJECT_POSITION;
2139
+ const originalWidth1 = shapeBg1.getBoundingClientRect().width;
2140
+ const originalWidth2 = shapeBg2.getBoundingClientRect().width;
2141
+ const minWidth = ProfileComparisonLibComponent.DRAG_MIN_WIDTH;
2142
+ const maxWidth = ProfileComparisonLibComponent.DRAG_MAX_WIDTH;
2086
2143
  let isDragging = false;
2087
2144
  let dragStartX = 0;
2088
- let activeFrame = null;
2089
- let edgeDragEmitted = false; // Track whether we have emitted edgeDrag for the current gesture
2090
- const initialTranslateXLeft = -24; // starting CSS value percent
2091
- const initialTranslateXRight = 24;
2145
+ let initialWidth1 = 0;
2146
+ let initialWidth2 = 0;
2147
+ let activeImg = null;
2148
+ // Use ViewChild for center text
2149
+ const shapeTextCenter = this.shapeTextCenter?.nativeElement || null;
2092
2150
  const onMouseMove = (e) => {
2093
- if (!isDragging)
2151
+ if (!isDragging || !activeImg)
2094
2152
  return;
2095
2153
  const deltaX = e.clientX - dragStartX;
2096
- const containerWidth = leftFrame.parentElement?.clientWidth || 360;
2097
- // Current drag delta in container-relative percentage
2098
- let deltaPercent = (deltaX / containerWidth) * 100;
2099
- if (Math.abs(deltaX) > 5) {
2100
- this.wasDragging = true;
2101
- }
2102
- // Check for edge drag swiping (both mouse and touch support!)
2103
- const leftRect = leftFrame.getBoundingClientRect();
2104
- const rightRect = rightFrame.getBoundingClientRect();
2105
- const overlapPx = Math.max(0, leftRect.right - leftRect.left); // Frame width
2106
- const maxDisplacementPx = overlapPx / 3;
2107
- if (!edgeDragEmitted && Math.abs(deltaX) > maxDisplacementPx * 1.05) {
2108
- // Only trigger swipe outward:
2109
- // Left frame dragged further left (deltaX < 0) or Right frame dragged further right (deltaX > 0)
2110
- if ((activeFrame === leftFrame && deltaX < 0) || (activeFrame === rightFrame && deltaX > 0)) {
2111
- edgeDragEmitted = true;
2112
- this.edgeDrag.emit(deltaX < 0 ? 'left' : 'right');
2113
- }
2154
+ const containerWidth = this.shapeContainer?.nativeElement?.clientWidth || 0;
2155
+ let newWidth1 = initialWidth1;
2156
+ let newWidth2 = initialWidth2;
2157
+ if (activeImg === shapeBg1) {
2158
+ newWidth1 = initialWidth1 + deltaX;
2159
+ newWidth2 = initialWidth2 - deltaX;
2114
2160
  }
2115
- // Clamp delta to the pre-calculated displacement limit
2116
- deltaPercent = Math.max(-initialMaxDisplacementPercent, Math.min(initialMaxDisplacementPercent, deltaPercent));
2117
- // Calculate actual frame displacement in pixels for text coordination
2118
- // Frame width is 75% of container width
2119
- const frameMovePx = (deltaPercent / 100) * (containerWidth * 0.75);
2120
- let newLeftTranslate = initialTranslateXLeft;
2121
- let newRightTranslate = initialTranslateXRight;
2122
- // Staggered text movement references
2123
- const leftContent = leftFrame.querySelector('.profile-comparison__content--left');
2124
- const rightContent = rightFrame.querySelector('.profile-comparison__content--right');
2125
- // Mouse-controlled sequential staged text movement (no auto-animations)
2126
- const maxStageMove = Math.abs(initialMaxDisplacementPercent * 0.75 * containerWidth / 100);
2127
- const getStagedX = (current, startPct, endPct, totalDisplacementPx) => {
2128
- const absD = Math.abs(current);
2129
- const startPx = maxStageMove * startPct;
2130
- const endPx = maxStageMove * endPct;
2131
- if (absD <= startPx)
2132
- return 0;
2133
- // Linear progress [0, 1]
2134
- let progress = Math.min(1, (absD - startPx) / (endPx - startPx));
2135
- // Apply smoothstep easing (3x^2 - 2x^3) for soft start/end
2136
- progress = progress * progress * (3 - 2 * progress);
2137
- return progress * totalDisplacementPx * Math.sign(current);
2138
- };
2139
- const leadTarget = 35;
2140
- const followTarget = 40;
2141
- const centerTarget = 15;
2142
- // Use a moderate transition to smooth out mouse jitter while maintaining control
2143
- const textSmoothing = 'transform 0.4s cubic-bezier(0.2, 0, 0.2, 1)';
2144
- if (leftContent)
2145
- leftContent.style.transition = textSmoothing;
2146
- if (rightContent)
2147
- rightContent.style.transition = textSmoothing;
2148
- if (shapeTextCenter)
2149
- shapeTextCenter.style.transition = textSmoothing;
2150
- if (deltaPercent < 0) {
2151
- // Dragging left: only left shape moves further left
2152
- newLeftTranslate = initialTranslateXLeft + deltaPercent;
2153
- leftFrame.style.transform = `translateX(${newLeftTranslate}%) scale(0.86)`;
2154
- if (leftBgFrame) {
2155
- leftBgFrame.style.transform = `translateX(${newLeftTranslate}%) scale(0.86)`;
2156
- const bg = leftBgFrame.querySelector('.profile-comparison__bg');
2157
- if (bg) {
2158
- bg.style.position = 'absolute';
2159
- bg.style.left = '0%';
2160
- bg.style.right = `${deltaPercent}%`; // Expand right when dragging left
2161
- }
2162
- }
2163
- // Ensure right frame is at rest
2164
- rightFrame.style.transform = `translateX(${initialTranslateXRight}%) scale(0.86)`;
2165
- if (rightBgFrame) {
2166
- rightBgFrame.style.transform = `translateX(${initialTranslateXRight}%) scale(0.86)`;
2167
- const bg = rightBgFrame.querySelector('.profile-comparison__bg');
2168
- if (bg) {
2169
- bg.style.position = 'absolute';
2170
- bg.style.left = '0%';
2171
- bg.style.right = '0%';
2172
- bg.style.backgroundPositionX = ''; // restore CSS default (right)
2173
- }
2174
- }
2175
- // Lead = Left, Follow = Right
2176
- if (leftContent)
2177
- leftContent.style.transform = `translateX(${getStagedX(frameMovePx, 0, 0.4, -leadTarget)}px)`;
2178
- if (rightContent)
2179
- rightContent.style.transform = `translateX(${getStagedX(frameMovePx, 0.4, 0.8, followTarget)}px)`;
2180
- if (shapeTextCenter)
2181
- shapeTextCenter.style.transform = `translateX(${getStagedX(frameMovePx, 0.8, 1.0, centerTarget)}px) scale(0.86)`;
2161
+ else if (activeImg === shapeBg2) {
2162
+ newWidth1 = initialWidth1 - deltaX;
2163
+ newWidth2 = initialWidth2 + deltaX;
2182
2164
  }
2183
- else if (deltaPercent > 0) {
2184
- // Dragging right: only right shape moves further right
2185
- newRightTranslate = initialTranslateXRight + deltaPercent;
2186
- rightFrame.style.transform = `translateX(${newRightTranslate}%) scale(0.86)`;
2187
- if (rightBgFrame) {
2188
- rightBgFrame.style.transform = `translateX(${newRightTranslate}%) scale(0.86)`;
2189
- // Same formula as left bg: left:-deltaPercent% keeps image stationary.
2190
- // For right drag deltaPercent>0, so left is negative — div extends
2191
- // left of frame (clipped by overflow:hidden) while image anchor stays put.
2192
- const bg = rightBgFrame.querySelector('.profile-comparison__bg');
2193
- if (bg) {
2194
- bg.style.position = 'absolute';
2195
- bg.style.left = `${-deltaPercent}%`;
2196
- bg.style.right = '0%';
2197
- }
2198
- }
2199
- // Ensure left frame is at rest
2200
- leftFrame.style.transform = `translateX(${initialTranslateXLeft}%) scale(0.86)`;
2201
- if (leftBgFrame) {
2202
- leftBgFrame.style.transform = `translateX(${initialTranslateXLeft}%) scale(0.86)`;
2203
- const bg = leftBgFrame.querySelector('.profile-comparison__bg');
2204
- if (bg) {
2205
- bg.style.position = 'absolute';
2206
- bg.style.left = '0%';
2207
- bg.style.right = '0%';
2208
- }
2209
- }
2210
- // Lead = Right, Follow = Left
2211
- if (rightContent)
2212
- rightContent.style.transform = `translateX(${getStagedX(frameMovePx, 0, 0.4, -leadTarget)}px)`;
2213
- if (leftContent)
2214
- leftContent.style.transform = `translateX(${getStagedX(frameMovePx, 0.4, 0.8, followTarget)}px)`;
2215
- if (shapeTextCenter)
2216
- shapeTextCenter.style.transform = `translateX(${getStagedX(frameMovePx, 0.8, 1.0, centerTarget)}px) scale(0.86)`;
2165
+ newWidth1 = Math.max(minWidth, Math.min(maxWidth, newWidth1));
2166
+ newWidth2 = Math.max(minWidth, Math.min(maxWidth, newWidth2));
2167
+ shapeBg1El.style.width = newWidth1 + 'px';
2168
+ shapeBg1El.style.height = ProfileComparisonLibComponent.SHAPE_BG_HEIGHT;
2169
+ shapeBg2El.style.width = newWidth2 + 'px';
2170
+ shapeBg2El.style.height = ProfileComparisonLibComponent.SHAPE_BG_HEIGHT;
2171
+ const widthChange1 = newWidth1 - originalWidth1;
2172
+ const widthChange2 = newWidth2 - originalWidth2;
2173
+ const dragDirection = deltaX > 0 ? 1 : -1;
2174
+ const dragDistance = Math.abs(deltaX);
2175
+ const maxDragDistance = ProfileComparisonLibComponent.MAX_DRAG_DISTANCE;
2176
+ const centerMoveDistance = Math.min(dragDistance, maxDragDistance) * dragDirection * ProfileComparisonLibComponent.CENTER_MOVE_MULTIPLIER;
2177
+ shapeTextLeft.style.transform = `translateX(${widthChange1 * ProfileComparisonLibComponent.TEXT_MOVE_MULTIPLIER}px)`;
2178
+ shapeTextRight.style.transform = `translateX(${-widthChange2 * ProfileComparisonLibComponent.TEXT_MOVE_MULTIPLIER}px)`;
2179
+ if (shapeTextCenter) {
2180
+ shapeTextCenter.style.transform = `translateX(${centerMoveDistance * ProfileComparisonLibComponent.TEXT_MOVE_MULTIPLIER}px)`;
2217
2181
  }
2218
- // Update edge masks during drag
2219
- this.updateEdgeMasks();
2220
2182
  };
2221
- const resetDrag = () => {
2183
+ const resetWidths = () => {
2222
2184
  isDragging = false;
2223
- activeFrame = null;
2224
- edgeDragEmitted = false; // Reset edge drag flag on release
2225
- const leftContent = leftFrame.querySelector('.profile-comparison__content--left');
2226
- const rightContent = rightFrame.querySelector('.profile-comparison__content--right');
2227
- // Smoother rebound transition
2228
- const reboundTransition = 'transform 0.6s cubic-bezier(0.16, 1, 0.3, 1)';
2229
- [leftFrame, rightFrame, leftBgFrame, rightBgFrame, leftContent, rightContent, shapeTextCenter].forEach(el => {
2230
- if (el) {
2231
- el.style.transition = reboundTransition;
2232
- el.style.transitionDelay = '0s'; // Reset delay for simultaneous rebound
2233
- }
2234
- });
2235
- // On release: instantly flash glass to near-zero, then spring back inside the rAF
2236
- [leftGlass, rightGlass].forEach(gl => {
2237
- if (gl) {
2238
- gl.style.transition = 'none';
2239
- gl.style.opacity = '0.03';
2240
- }
2241
- });
2242
- // Use requestAnimationFrame to ensure transition is applied before position reset
2243
- requestAnimationFrame(() => {
2244
- leftFrame.style.transform = `translateX(-24%) scale(0.86)`;
2245
- rightFrame.style.transform = `translateX(24%) scale(0.86)`;
2246
- if (leftBgFrame) {
2247
- leftBgFrame.style.transform = `translateX(-24%) scale(0.86)`;
2248
- const bg = leftBgFrame.querySelector('.profile-comparison__bg');
2249
- if (bg) {
2250
- bg.style.transition = `left 0.6s cubic-bezier(0.16, 1, 0.3, 1), right 0.6s cubic-bezier(0.16, 1, 0.3, 1), ${reboundTransition}`;
2251
- bg.style.left = '0%';
2252
- bg.style.right = '0%';
2253
- }
2254
- }
2255
- if (rightBgFrame) {
2256
- rightBgFrame.style.transform = `translateX(24%) scale(0.86)`;
2257
- const bg = rightBgFrame.querySelector('.profile-comparison__bg');
2258
- if (bg) {
2259
- bg.style.transition = `left 0.6s cubic-bezier(0.16, 1, 0.3, 1), right 0.6s cubic-bezier(0.16, 1, 0.3, 1), ${reboundTransition}`;
2260
- bg.style.left = '0%';
2261
- bg.style.right = '0%';
2262
- }
2263
- }
2264
- // Force reset all transforms and clear inline styles
2265
- [leftContent, rightContent].forEach(el => {
2266
- if (el) {
2267
- el.style.transform = 'translateX(0px)';
2268
- el.style.transition = reboundTransition;
2269
- }
2270
- });
2271
- if (shapeTextCenter) {
2272
- shapeTextCenter.style.transform = 'translateX(0px) scale(0.86)';
2273
- shapeTextCenter.style.transition = reboundTransition;
2274
- }
2275
- // Restore glass shadow opacity with spring rebound
2276
- [leftGlass, rightGlass].forEach(gl => {
2277
- if (gl) {
2278
- gl.style.transition = 'opacity 0.6s cubic-bezier(0.16, 1, 0.3, 1)';
2279
- gl.style.opacity = '1';
2280
- }
2281
- });
2282
- });
2185
+ activeImg = null;
2186
+ shapeBg1El.style.transition = ProfileComparisonLibComponent.TRANSITION_WIDTH;
2187
+ shapeBg2El.style.transition = ProfileComparisonLibComponent.TRANSITION_WIDTH;
2188
+ shapeTextLeft.style.transition = ProfileComparisonLibComponent.TRANSITION_TRANSFORM;
2189
+ shapeTextRight.style.transition = ProfileComparisonLibComponent.TRANSITION_TRANSFORM;
2190
+ if (shapeTextCenter) {
2191
+ shapeTextCenter.style.transition = ProfileComparisonLibComponent.TRANSITION_TRANSFORM;
2192
+ }
2193
+ shapeBg1El.style.width = originalWidth1 + 'px';
2194
+ shapeBg2El.style.width = originalWidth2 + 'px';
2195
+ shapeTextLeft.style.transform = ProfileComparisonLibComponent.TRANSFORM_RESET;
2196
+ shapeTextRight.style.transform = ProfileComparisonLibComponent.TRANSFORM_RESET;
2197
+ if (shapeTextCenter) {
2198
+ shapeTextCenter.style.transform = ProfileComparisonLibComponent.TRANSFORM_RESET;
2199
+ }
2283
2200
  document.removeEventListener('mousemove', onMouseMove);
2284
- document.removeEventListener('mouseup', resetDrag);
2285
- document.removeEventListener('mouseleave', resetDrag);
2286
- // Re-evaluate edge masks after rebound animation completes
2287
- setTimeout(() => this.updateEdgeMasks(), 650);
2201
+ document.removeEventListener('mouseup', resetWidths);
2202
+ document.removeEventListener('mouseleave', resetWidths);
2288
2203
  setTimeout(() => {
2289
- [leftFrame, rightFrame, leftBgFrame, rightBgFrame, leftContent, rightContent, shapeTextCenter].forEach(el => {
2290
- if (el)
2291
- el.style.transition = '';
2292
- // Also clear image transitions and positions
2293
- if (el === leftBgFrame || el === rightBgFrame) {
2294
- const bg = el.querySelector('.profile-comparison__bg');
2295
- if (bg) {
2296
- bg.style.transition = '';
2297
- bg.style.left = '';
2298
- bg.style.right = '';
2299
- bg.style.backgroundPositionX = '';
2300
- }
2301
- }
2302
- });
2303
- // Clear glass opacity inline style after transition completes
2304
- [leftGlass, rightGlass].forEach(gl => {
2305
- if (gl) {
2306
- gl.style.transition = '';
2307
- gl.style.opacity = '';
2308
- }
2309
- });
2310
- }, 600);
2204
+ shapeBg1El.style.transition = '';
2205
+ shapeBg2El.style.transition = '';
2206
+ shapeTextLeft.style.transition = '';
2207
+ shapeTextRight.style.transition = '';
2208
+ if (shapeTextCenter) {
2209
+ shapeTextCenter.style.transition = '';
2210
+ }
2211
+ }, ProfileComparisonLibComponent.TRANSITION_RESET_DELAY_MS);
2311
2212
  };
2312
- let initialMaxDisplacementPercent = 0;
2313
- [leftFrame, rightFrame].forEach((frame) => {
2314
- // Ensure cursor visually indicates grab on the svg images inside the frame
2315
- const svgs = frame.querySelectorAll('svg');
2316
- svgs.forEach(s => s.style.cursor = 'grab');
2317
- frame.addEventListener('mousedown', (e) => {
2318
- // Prevent default text selection during drag
2213
+ [shapeBg1, shapeBg2].forEach((img) => {
2214
+ img.addEventListener('mousedown', (e) => {
2319
2215
  e.preventDefault();
2320
2216
  isDragging = true;
2321
- this.wasDragging = false;
2322
- activeFrame = frame; // Track which frame is active
2217
+ activeImg = img;
2323
2218
  dragStartX = e.clientX;
2324
- // Lighten shadow on grab, then slowly return to normal while holding
2325
- const activeGlass = frame === leftFrame ? leftGlass : rightGlass;
2326
- if (activeGlass) {
2327
- // Instant drop on grab
2328
- activeGlass.style.transition = 'none';
2329
- activeGlass.style.opacity = '0.15';
2330
- // Next frame: begin slow return to normal while user holds
2331
- requestAnimationFrame(() => {
2332
- if (activeGlass) {
2333
- activeGlass.style.transition = 'opacity 1.5s ease-in';
2334
- activeGlass.style.opacity = '1';
2335
- }
2336
- });
2337
- }
2338
- // Calculate initial max displacement once at the start of drag
2339
- const containerWidth = leftFrame.parentElement?.clientWidth || 360;
2340
- const leftRect = leftFrame.getBoundingClientRect();
2341
- const rightRect = rightFrame.getBoundingClientRect();
2342
- const overlapPx = Math.max(0, leftRect.right - rightRect.left);
2343
- const maxDisplacementPx = overlapPx / 3;
2344
- initialMaxDisplacementPercent = (maxDisplacementPx / containerWidth) * 100;
2219
+ initialWidth1 = shapeBg1.getBoundingClientRect().width;
2220
+ initialWidth2 = shapeBg2.getBoundingClientRect().width;
2345
2221
  document.addEventListener('mousemove', onMouseMove);
2346
- document.addEventListener('mouseup', resetDrag);
2347
- document.addEventListener('mouseleave', resetDrag);
2222
+ document.addEventListener('mouseup', resetWidths);
2223
+ document.addEventListener('mouseleave', resetWidths);
2348
2224
  });
2349
- frame.addEventListener('touchstart', (e) => {
2350
- // Prevent default to avoid scrolling/swiping
2351
- if (e.cancelable)
2352
- e.preventDefault();
2353
- // Stop propagation to prevent swiper from seeing this touch
2354
- e.stopPropagation();
2355
- isDragging = true;
2356
- this.wasDragging = false;
2357
- activeFrame = frame; // Track which frame is active
2358
- dragStartX = e.touches[0].clientX;
2359
- const activeGlass = frame === leftFrame ? leftGlass : rightGlass;
2360
- if (activeGlass) {
2361
- activeGlass.style.transition = 'none';
2362
- activeGlass.style.opacity = '0.15';
2363
- requestAnimationFrame(() => {
2364
- if (activeGlass) {
2365
- activeGlass.style.transition = 'opacity 1.5s ease-in';
2366
- activeGlass.style.opacity = '1';
2367
- }
2368
- });
2225
+ });
2226
+ }
2227
+ getSimilarity(item1, item2) {
2228
+ if (!this.matrixData || !item1 || !item2)
2229
+ return 0;
2230
+ const n1 = String(item1).trim().toLowerCase();
2231
+ const n2 = String(item2).trim().toLowerCase();
2232
+ if (n1 === n2)
2233
+ return 1.0;
2234
+ const leg1 = this.matrixData.legend?.find(l => l.full.trim().toLowerCase() === n1);
2235
+ const leg2 = this.matrixData.legend?.find(l => l.full.trim().toLowerCase() === n2);
2236
+ if (!leg1 || !leg2)
2237
+ return 0;
2238
+ const colIdx = this.matrixData.headers?.indexOf(leg2.abbr);
2239
+ if (colIdx === -1 || colIdx === undefined)
2240
+ return 0;
2241
+ const row = this.matrixData.rows?.find(r => r.label === leg1.abbr);
2242
+ if (!row || !row.values || colIdx >= row.values.length)
2243
+ return 0;
2244
+ const val = parseFloat(row.values[colIdx]);
2245
+ return isNaN(val) ? 0 : val;
2246
+ }
2247
+ reorderInterestsByMatrix(p1List, p2List, centerList) {
2248
+ if (!this.matrixData)
2249
+ return { p1: p1List, p2: p2List, center: centerList };
2250
+ const cleanCenter = (centerList || []).filter(c => c && c !== '-' && c !== '----');
2251
+ const list1 = (p1List || []).filter(item => item && item !== '-' && item !== '----');
2252
+ const list2 = (p2List || []).filter(item => item && item !== '-' && item !== '----');
2253
+ const ordered1 = [];
2254
+ const ordered2 = [];
2255
+ const used1 = new Set();
2256
+ const used2 = new Set();
2257
+ // 1. PRIORITIZE LEFT AND RIGHT SIMILARITY (Pair ordering from top to bottom)
2258
+ const remaining1 = [...list1];
2259
+ const remaining2 = [...list2];
2260
+ while (remaining1.length > 0 && remaining2.length > 0) {
2261
+ let bestPair = { i1: -1, i2: -1, score: -1 };
2262
+ for (let i = 0; i < remaining1.length; i++) {
2263
+ for (let j = 0; j < remaining2.length; j++) {
2264
+ const score = this.getSimilarity(remaining1[i], remaining2[j]);
2265
+ if (score > bestPair.score) {
2266
+ bestPair = { i1: i, i2: j, score };
2267
+ }
2369
2268
  }
2370
- const onTouchMove = (ev) => {
2371
- onMouseMove({ clientX: ev.touches[0].clientX });
2372
- };
2373
- const onTouchEnd = () => {
2374
- resetDrag();
2375
- document.removeEventListener('touchmove', onTouchMove);
2376
- document.removeEventListener('touchend', onTouchEnd);
2377
- };
2378
- document.addEventListener('touchmove', onTouchMove, { passive: false });
2379
- document.addEventListener('touchend', onTouchEnd);
2380
- document.addEventListener('touchcancel', onTouchEnd);
2381
- }, { passive: false });
2269
+ }
2270
+ if (bestPair.score >= 0.15 && bestPair.i1 !== -1 && bestPair.i2 !== -1) {
2271
+ const item1 = remaining1[bestPair.i1];
2272
+ const item2 = remaining2[bestPair.i2];
2273
+ ordered1.push(item1);
2274
+ ordered2.push(item2);
2275
+ used1.add(item1);
2276
+ used2.add(item2);
2277
+ remaining1.splice(bestPair.i1, 1);
2278
+ remaining2.splice(bestPair.i2, 1);
2279
+ }
2280
+ else {
2281
+ break;
2282
+ }
2283
+ }
2284
+ // 2. FOR REMAINING UNMATCHED LEFT & RIGHT, ORDER BY RELEVANCE TO ALREADY PLACED ITEMS
2285
+ const unplaced1 = list1.filter(item => !used1.has(item));
2286
+ const unplaced2 = list2.filter(item => !used2.has(item));
2287
+ const rankUnplaced = (items, placedContext) => {
2288
+ return items.map(item => {
2289
+ let maxSim = 0;
2290
+ placedContext.forEach(placed => {
2291
+ const s = this.getSimilarity(item, placed);
2292
+ if (s > maxSim)
2293
+ maxSim = s;
2294
+ });
2295
+ return { item, maxSim };
2296
+ }).sort((a, b) => b.maxSim - a.maxSim).map(x => x.item);
2297
+ };
2298
+ const allPlaced = [...ordered1, ...ordered2];
2299
+ const sortedUnplaced1 = rankUnplaced(unplaced1, allPlaced);
2300
+ const sortedUnplaced2 = rankUnplaced(unplaced2, allPlaced);
2301
+ sortedUnplaced1.forEach(item => {
2302
+ ordered1.push(item);
2303
+ used1.add(item);
2304
+ });
2305
+ sortedUnplaced2.forEach(item => {
2306
+ ordered2.push(item);
2307
+ used2.add(item);
2308
+ });
2309
+ // 3. ASSIGN CENTER ITEMS TO INDICES BASED ON BEST MATCH
2310
+ const maxRows = Math.max(ordered1.length, ordered2.length);
2311
+ const assignments = [];
2312
+ cleanCenter.forEach(cItem => {
2313
+ for (let k = 0; k < maxRows; k++) {
2314
+ const item1 = ordered1[k];
2315
+ const item2 = ordered2[k];
2316
+ const score1 = item1 ? this.getSimilarity(cItem, item1) : 0;
2317
+ const score2 = item2 ? this.getSimilarity(cItem, item2) : 0;
2318
+ const maxScore = Math.max(score1, score2);
2319
+ assignments.push({ centerItem: cItem, index: k, score: maxScore });
2320
+ }
2321
+ });
2322
+ assignments.sort((a, b) => b.score - a.score);
2323
+ const assignedItems = new Set();
2324
+ const assignedIndices = new Set();
2325
+ const orderedCenter = new Array(maxRows).fill('empty');
2326
+ assignments.forEach(({ centerItem, index }) => {
2327
+ if (!assignedItems.has(centerItem) && !assignedIndices.has(index)) {
2328
+ orderedCenter[index] = centerItem;
2329
+ assignedItems.add(centerItem);
2330
+ assignedIndices.add(index);
2331
+ }
2382
2332
  });
2333
+ const remainingCenter = cleanCenter.filter(c => !assignedItems.has(c));
2334
+ orderedCenter.push(...remainingCenter);
2335
+ return { p1: ordered1, p2: ordered2, center: orderedCenter };
2383
2336
  }
2384
2337
  onViewProfile(side) {
2385
- if (side === 'left') {
2386
- this.leftProfileClicked = true;
2387
- setTimeout(() => this.leftProfileClicked = false, 650);
2388
- }
2389
- else {
2390
- this.rightProfileClicked = true;
2391
- setTimeout(() => this.rightProfileClicked = false, 650);
2392
- }
2393
2338
  this.viewProfileClick.emit({ side });
2394
2339
  }
2395
- onShapeClick(side) {
2396
- if (this.wasDragging) {
2397
- this.wasDragging = false;
2398
- return;
2399
- }
2400
- if (side === 'left') {
2401
- this.leftShapeAnimating = true;
2402
- setTimeout(() => this.leftShapeAnimating = false, 550);
2403
- }
2404
- else {
2405
- this.rightShapeAnimating = true;
2406
- setTimeout(() => this.rightShapeAnimating = false, 550);
2407
- }
2408
- }
2409
- compressConfigImagesIfNeeded() {
2340
+ getPerson1UniqueInterests() {
2341
+ const raw = this.alignedPerson1Interests.length > 0
2342
+ ? this.alignedPerson1Interests
2343
+ : this.displayPerson1Interests.length > 0
2344
+ ? this.displayPerson1Interests
2345
+ : this.person1Interests;
2346
+ return raw.filter(item => item && item !== '-' && item !== '----' && !this.isInCenter(item));
2347
+ }
2348
+ getPerson2UniqueInterests() {
2349
+ const raw = this.alignedPerson2Interests.length > 0
2350
+ ? this.alignedPerson2Interests
2351
+ : this.displayPerson2Interests.length > 0
2352
+ ? this.displayPerson2Interests
2353
+ : this.person2Interests;
2354
+ return raw.filter(item => item && item !== '-' && item !== '----' && !this.isInCenter(item));
2355
+ }
2356
+ getChipOffset(index, column) {
2357
+ const leftOffsets = [0, 20, 6, 26, 12, 30, 4, 18];
2358
+ const rightOffsets = [0, 20, 6, 26, 12, 30, 4, 18];
2359
+ const centerOffsets = [0, -14, 12, -6, 16, -10, 8, -4];
2360
+ if (column === 'left')
2361
+ return leftOffsets[index % leftOffsets.length];
2362
+ if (column === 'right')
2363
+ return rightOffsets[index % rightOffsets.length];
2364
+ return centerOffsets[index % centerOffsets.length];
2365
+ }
2366
+ compressInputImages() {
2410
2367
  const tasks = [];
2411
- if (this.user1Image) {
2412
- tasks.push(this.compressImageStringToDataUrl(this.user1Image, 'ConfigUser1').pipe(tap$1((dataUrl) => { if (dataUrl)
2413
- this.user1Image = dataUrl; }), map(() => void 0)));
2414
- }
2415
- if (this.user2Image) {
2416
- tasks.push(this.compressImageStringToDataUrl(this.user2Image, 'ConfigUser2').pipe(tap$1((dataUrl) => { if (dataUrl)
2417
- this.user2Image = dataUrl; }), map(() => void 0)));
2418
- }
2419
- return tasks.length > 0 ? forkJoin(tasks).pipe(map(() => void 0)) : of(void 0);
2368
+ const processUser = (which) => {
2369
+ const file = which === 1 ? this.user1File : this.user2File;
2370
+ const imageStr = which === 1 ? this.user1Image : this.user2Image;
2371
+ const baseName = which === 1 ? 'ConfigUser1' : 'ConfigUser2';
2372
+ const updateImage = (dataUrl) => {
2373
+ if (which === 1)
2374
+ this.user1Image = dataUrl;
2375
+ else
2376
+ this.user2Image = dataUrl;
2377
+ };
2378
+ // If File object provided, compress it
2379
+ if (file) {
2380
+ return this.imageCompressionService.compressImageFile(file, this.compressionConfig).pipe(tap$1(res => { if (res.dataUrl)
2381
+ updateImage(res.dataUrl); }), map(() => void 0), catchError$1(() => of(void 0)));
2382
+ }
2383
+ if (!imageStr)
2384
+ return of(void 0);
2385
+ // If already a data URL, compress it
2386
+ if (this.fileConversionService.isDataUrl(imageStr)) {
2387
+ return this.compressImageStringToDataUrl(imageStr, baseName).pipe(tap$1(dataUrl => { if (dataUrl)
2388
+ updateImage(dataUrl); }), map(() => void 0));
2389
+ }
2390
+ // For external URLs, try to fetch and compress client-side
2391
+ // If fetch fails (e.g., CORS), keep the raw URL and let backend handle it
2392
+ let fetchUrl = imageStr;
2393
+ if (this.imageProxyPath && imageStr.startsWith('http')) {
2394
+ // Extract path from full URL for proxy
2395
+ try {
2396
+ const urlObj = new URL(imageStr);
2397
+ const pathWithoutLeadingSlash = urlObj.pathname.replace(/^\//, '');
2398
+ fetchUrl = `${this.imageProxyPath}${pathWithoutLeadingSlash}${urlObj.search}`;
2399
+ }
2400
+ catch {
2401
+ fetchUrl = imageStr;
2402
+ }
2403
+ }
2404
+ return this.fileConversionService.fetchUrlToDataUrl(fetchUrl).pipe(switchMap(dataUrl => this.imageCompressionService.compressImageFile(this.fileConversionService.dataUrlToFile(dataUrl, `${baseName}.jpg`), this.compressionConfig)), tap$1(res => { if (res.dataUrl)
2405
+ updateImage(res.dataUrl); }), map(() => void 0), catchError$1(() => {
2406
+ // Fetch failed (likely CORS), keep raw URL for backend to handle
2407
+ return of(void 0);
2408
+ }));
2409
+ };
2410
+ tasks.push(processUser(1));
2411
+ tasks.push(processUser(2));
2412
+ return forkJoin(tasks).pipe(map(() => void 0));
2420
2413
  }
2421
2414
  compressImageStringToDataUrl(imageStr, baseName) {
2422
2415
  return this.fileConversionService.getFileForImageString(imageStr, `${baseName}.jpg`).pipe(switchMap(file => this.imageCompressionService.compressImageFile(file, this.compressionConfig)), map(res => res.dataUrl), catchError$1(() => of(null)));
@@ -2503,50 +2496,62 @@ class ProfileComparisonLibComponent {
2503
2496
  const user2FaceCoords = this.getFaceCoordinatesFromBBox(face2);
2504
2497
  const user1Eyes = this.getEyeCoordinatesFromFace(face1);
2505
2498
  const user2Eyes = this.getEyeCoordinatesFromFace(face2);
2506
- const leftContainerEl = this.leftFrame?.nativeElement || null;
2507
- const rightContainerEl = this.rightFrame?.nativeElement || null;
2499
+ const leftContainerEl = this.profileImgLeft?.nativeElement || null;
2500
+ const rightContainerEl = this.profileImgRight?.nativeElement || null;
2508
2501
  const containerWidth1 = leftContainerEl?.clientWidth || 160;
2509
2502
  const containerHeight1 = leftContainerEl?.clientHeight || 550;
2510
2503
  const containerWidth2 = rightContainerEl?.clientWidth || 160;
2511
2504
  const containerHeight2 = rightContainerEl?.clientHeight || 550;
2512
2505
  const scale1 = this.user1NaturalWidth ? Math.max(containerWidth1 / this.user1NaturalWidth, containerHeight1 / this.user1NaturalHeight) : 1;
2513
2506
  const scale2 = this.user2NaturalWidth ? Math.max(containerWidth2 / this.user2NaturalWidth, containerHeight2 / this.user2NaturalHeight) : 1;
2514
- const displayedFaceHeight1 = face1.height * scale1;
2515
- const displayedFaceHeight2 = face2.height * scale2;
2516
- const ratio1 = containerHeight1 ? displayedFaceHeight1 / containerHeight1 : 0;
2517
- const ratio2 = containerHeight2 ? displayedFaceHeight2 / containerHeight2 : 0;
2518
- let finalScale1 = ratio1 > 0 && ratio1 < 0.22 ? Math.min(3, 0.32 / ratio1) : 1;
2519
- let finalScale2 = ratio2 > 0 && ratio2 < 0.22 ? Math.min(3, 0.32 / ratio2) : 1;
2520
- const effRatio1 = ratio1 * finalScale1;
2521
- const effRatio2 = ratio2 * finalScale2;
2522
- if (effRatio1 > effRatio2 * 1.35)
2523
- finalScale1 = Math.max(0.95, finalScale1 * (effRatio2 / effRatio1));
2524
- else if (effRatio2 > effRatio1 * 1.35)
2525
- finalScale2 = Math.max(0.95, finalScale2 * (effRatio1 / effRatio2));
2526
2507
  const nose1 = this.getNoseCoordinateFromFace(face1);
2527
2508
  const nose2 = this.getNoseCoordinateFromFace(face2);
2528
2509
  const anchor1Y = (Math.min(user1Eyes.leftEye.y, user1Eyes.rightEye.y) + nose1.y) / 2;
2529
2510
  const anchor2Y = (Math.min(user2Eyes.leftEye.y, user2Eyes.rightEye.y) + nose2.y) / 2;
2530
- const tCenter1 = this.user1NaturalHeight ? -((anchor1Y - this.user1NaturalHeight / 2) * scale1 * finalScale1) : 0;
2531
- const tCenter2 = this.user2NaturalHeight ? -((anchor2Y - this.user2NaturalHeight / 2) * scale2 * finalScale2) : 0;
2532
- this.user1Transform = `translateY(${tCenter1 - this.eyeAlignmentBiasPx}px) scale(${finalScale1})`;
2533
- this.user2Transform = `translateY(${tCenter2 - this.eyeAlignmentBiasPx}px) scale(${finalScale2})`;
2511
+ const tCenter1 = this.user1NaturalHeight ? -((anchor1Y - this.user1NaturalHeight / 2) * scale1) : 0;
2512
+ const tCenter2 = this.user2NaturalHeight ? -((anchor2Y - this.user2NaturalHeight / 2) * scale2) : 0;
2513
+ const shiftY1 = tCenter1 - this.eyeAlignmentBiasPx;
2514
+ const shiftY2 = tCenter2 - this.eyeAlignmentBiasPx;
2515
+ // Apply eye-to-eye vertical repositioning transforms without any scaling
2516
+ this.user1Transform = `translateY(${shiftY1}px)`;
2517
+ this.user2Transform = `translateY(${shiftY2}px)`;
2518
+ // Extend image dimensions into the adjusted area without applying any uniform zoom/scaling
2519
+ if (shiftY1 > 0) {
2520
+ this.user1Top = `-${shiftY1}px`;
2521
+ this.user1Height = `calc(100% + ${shiftY1}px)`;
2522
+ }
2523
+ else {
2524
+ this.user1Top = '0px';
2525
+ this.user1Height = `calc(100% + ${Math.abs(shiftY1)}px)`;
2526
+ }
2527
+ if (shiftY2 > 0) {
2528
+ this.user2Top = `-${shiftY2}px`;
2529
+ this.user2Height = `calc(100% + ${shiftY2}px)`;
2530
+ }
2531
+ else {
2532
+ this.user2Top = '0px';
2533
+ this.user2Height = `calc(100% + ${Math.abs(shiftY2)}px)`;
2534
+ }
2534
2535
  this.alignmentCalculated = true;
2535
2536
  }
2536
2537
  applyDefaultAlignment() {
2537
2538
  this.user1Transform = 'translateY(0px)';
2538
2539
  this.user2Transform = 'translateY(0px)';
2540
+ this.user1Top = '0px';
2541
+ this.user1Height = '100%';
2542
+ this.user2Top = '0px';
2543
+ this.user2Height = '100%';
2539
2544
  this.alignmentCalculated = true;
2540
2545
  }
2541
2546
  isValidFaceData(face) {
2542
2547
  return !!face && typeof face.x === 'number' && face.width > 0 && face.height > 0;
2543
2548
  }
2544
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", 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 });
2545
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: ProfileComparisonLibComponent, isStandalone: false, selector: "lib-profile-comparison", inputs: { config: "config", backendMode: "backendMode", backendUrl: "backendUrl", fadeAllEdges: "fadeAllEdges" }, outputs: { matrixDataChange: "matrixDataChange", rawLLMOutputChange: "rawLLMOutputChange", viewProfileClick: "viewProfileClick", edgeDrag: "edgeDrag" }, host: { listeners: { "window:resize": "onResize()" } }, viewQueries: [{ propertyName: "leftContainer", first: true, predicate: ["leftContainer"], descendants: true }, { propertyName: "rightContainer", first: true, predicate: ["rightContainer"], descendants: true }, { propertyName: "leftFrame", first: true, predicate: ["leftFrame"], descendants: true }, { propertyName: "rightFrame", first: true, predicate: ["rightFrame"], descendants: true }, { propertyName: "leftBgFrame", first: true, predicate: ["leftBgFrame"], descendants: true }, { propertyName: "rightBgFrame", first: true, predicate: ["rightBgFrame"], descendants: true }, { propertyName: "shapeContainer", first: true, predicate: ["shapeContainer"], 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 profile-comparison\" *ngIf=\"backendConfigured\"\n [class.fade-all-edges]=\"config.edgeShading !== false\"\n [style.--edge-shading-color]=\"config.shadingColor || 'rgba(44, 40, 47, 0.0)'\"\n [style.--edge-shading-display]=\"config.edgeShading !== false ? 'block' : 'none'\">\n\n <div class=\"profile-comparison__inner\" #shapeContainer>\n <!-- Backgrounds layer: structurally below everything else to fix stacking -->\n <div class=\"profile-comparison__bg-layer\">\n <div class=\"profile-comparison__frame profile-comparison__frame--left bg-frame\"\n [class.is-animating-nudge]=\"leftShapeAnimating\"\n #leftBgFrame>\n <div\n class=\"profile-comparison__bg profile-comparison__bg--left\"\n [ngStyle]=\"{ 'background-image': 'url(' + user1Image + ')', 'transform': user1Transform, 'background-position': user1ObjectPosition }\"\n ></div>\n </div>\n <div class=\"profile-comparison__frame profile-comparison__frame--right bg-frame\"\n [class.is-animating-nudge]=\"rightShapeAnimating\"\n #rightBgFrame>\n <div\n class=\"profile-comparison__bg profile-comparison__bg--right\"\n [ngStyle]=\"{ 'background-image': 'url(' + user2Image + ')', 'transform': user2Transform, 'background-position': user2ObjectPosition }\"\n ></div>\n </div>\n </div>\n\n <!-- Active interactive shapes and content layer -->\n <div class=\"profile-comparison__frame profile-comparison__frame--left swiper-no-swiping\"\n [class.is-animating-nudge]=\"leftShapeAnimating\"\n (click)=\"onShapeClick('left')\"\n #leftFrame>\n <svg\n class=\"profile-comparison__frame-svg profile-comparison__frame-svg--glass\"\n viewBox=\"0 0 256 275\"\n preserveAspectRatio=\"none\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <g filter=\"url(#filter-glass-left)\">\n <path\n d=\"M246.177 26.4924 L10 1.07 Q0 0 0 10 L0 268 Q0 278 9.8 276.5 L246.68 239.869 C252.043 239.04 256 234.407 256 228.981 V37.4497 C256 31.8302 251.764 27.0937 246.177 26.4924\"\n stroke=\"#61C2AB\"\n stroke-opacity=\"0.3\"\n stroke-width=\"0.809707\"\n stroke-linejoin=\"round\"\n shape-rendering=\"geometricPrecision\"\n />\n </g>\n <defs>\n <filter id=\"filter-glass-left\" x=\"-40\" y=\"-40\" width=\"336\" height=\"355\" 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 3.3333 0\" result=\"hardAlpha\"/>\n <feOffset/>\n <feGaussianBlur stdDeviation=\"6.3562\"/>\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\"/>\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 3.3333 0\" result=\"hardAlpha\"/>\n <feOffset/>\n <feGaussianBlur stdDeviation=\"6.3562\"/>\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\" result=\"effect2_dropShadow\"/>\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 3.3333 0\" result=\"hardAlpha\"/>\n <feOffset/>\n <feGaussianBlur stdDeviation=\"6.3562\"/>\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\" result=\"effect3_dropShadow\"/>\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 3.3333 0\" result=\"hardAlpha\"/>\n <feOffset/>\n <feGaussianBlur stdDeviation=\"6.3562\"/>\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\" result=\"effect4_dropShadow\"/>\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 3.3333 0\" result=\"hardAlpha\"/>\n <feOffset/>\n <feGaussianBlur stdDeviation=\"6.3562\"/>\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\" result=\"effect5_dropShadow\"/>\n <feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"effect5_dropShadow\" result=\"shape\"/>\n </filter>\n </defs>\n </svg>\n <svg\n class=\"profile-comparison__frame-svg profile-comparison__frame-svg--middle\"\n viewBox=\"0 0 256 276\"\n preserveAspectRatio=\"none\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <defs>\n <clipPath id=\"clip-middle-left\">\n <path d=\"M246.177 26.4924 L10 1.07 Q0 0 0 10 V268 Q0 278 9.8 276.5 L246.68 239.869 C252.043 239.04 256 234.407 256 228.981 V37.4497 C256 31.8302 251.764 27.0937 246.177 26.4924 Z\"/>\n </clipPath>\n <filter id=\"filter0_i_2126_3746\" x=\"-0.638672\" y=\"-2\" width=\"256\" height=\"279\" 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 14.2857 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 0 0 0 0 0.516667 0 0 0 0 1 0 0 0 1 0\" />\n <feBlend mode=\"normal\" in2=\"shape\" result=\"effect1_innerShadow_2126_3746\" />\n </filter>\n </defs>\n <g clip-path=\"url(#clip-middle-left)\" filter=\"url(#filter0_i_2126_3746)\">\n <path\n d=\"M245.538 25.2463 L9.36 -1.2459 Q-0.638672 -2.5 -0.638672 7.5 V266.75 Q-0.638672 276.75 9.16 275.25 L246.042 238.623 C251.404 237.794 255.361 233.161 255.361 227.735 V36.2037 C255.361 30.5841 251.126 25.8476 245.538 25.2463 Z\"\n fill=\"#00CFE6\"\n fill-opacity=\"0.1\"\n shape-rendering=\"geometricPrecision\"\n />\n </g>\n </svg>\n <svg\n class=\"profile-comparison__frame-svg profile-comparison__frame-svg--left\"\n viewBox=\"0 0 256 278\"\n preserveAspectRatio=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <defs>\n <radialGradient id=\"profile-left-fill\" cx=\"0\" cy=\"0\" r=\"1\" gradientTransform=\"matrix(65.1034 -81.3964 74.955 49.415 128 139)\" gradientUnits=\"userSpaceOnUse\">\n <stop stop-color=\"#0051E6\" stop-opacity=\"0\" />\n <stop offset=\"0.5\" stop-color=\"#0051E6\" stop-opacity=\"0.035\" />\n <stop offset=\"1\" stop-color=\"white\" />\n </radialGradient>\n <linearGradient id=\"profile-left-stroke\" x1=\"128\" y1=\"0\" x2=\"128\" y2=\"278\" gradientUnits=\"userSpaceOnUse\">\n <stop offset=\"0\" stop-color=\"#52FFEB\" />\n <stop offset=\"0.5\" stop-color=\"#41CFA1\" />\n <stop offset=\"1\" stop-color=\"#319990\" />\n </linearGradient>\n </defs>\n <!-- Fill Layer -->\n <path\n d=\"M246.177 26.4924 L10 1.07 Q0 0 0 10 V268 Q0 278 9.8 276.5 L246.68 239.869 C252.043 239.04 256 234.407 256 228.981 V37.4497 C256 31.8302 251.764 27.0937 246.177 26.4924 Z\"\n fill=\"url(#profile-left-fill)\"\n fill-opacity=\"0.07\"\n shape-rendering=\"geometricPrecision\"\n />\n <!-- Stroke Layer (Left outer border skipped) -->\n <path\n d=\"M246.177 26.4924 L10 1.07 Q0 0 0 10 L0 268 Q0 278 9.8 276.5 L246.68 239.869 C252.043 239.04 256 234.407 256 228.981 V37.4497 C256 31.8302 251.764 27.0937 246.177 26.4924\"\n fill=\"none\"\n stroke=\"url(#profile-left-stroke)\"\n stroke-width=\"2\"\n stroke-miterlimit=\"3.99393\"\n stroke-linejoin=\"round\"\n shape-rendering=\"geometricPrecision\"\n />\n <!-- Added explicit embedded SVG text anchoring tightly inside the graphical shape bounds instead of pure flexbox logic padding. -->\n <g\n style=\"cursor: pointer !important;\"\n (click)=\"onViewProfile('left'); $event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\"\n >\n <rect [attr.x]=\"viewProfileLeftX - 15\" y=\"238\" width=\"115\" height=\"40\" fill=\"transparent\" pointer-events=\"auto\" />\n <text\n [attr.x]=\"viewProfileLeftX\" y=\"253\"\n fill=\"rgba(255, 255, 255, 0.60)\"\n font-family=\"'Calistoga', serif\"\n font-size=\"9\"\n text-anchor=\"start\"\n [class.is-animating-bob]=\"leftProfileClicked\"\n style=\"text-shadow: 0 2px 4.2px #000;\"\n pointer-events=\"none\"\n >View Profile</text>\n </g>\n </svg>\n <div class=\"profile-comparison__content profile-comparison__content--left\">\n <div class=\"profile-comparison__list profile-comparison__list--left\">\n <!-- Semantic Items only -->\n <ng-container *ngFor=\"let interest of alignedPerson1Interests; let i = index\">\n <div class=\"profile-comparison__item profile-comparison__item--left\">\n <lib-marquee\n class=\"profile-comparison__text\"\n [title]=\"interest\"\n [onlyMarqueeOnHover]=\"false\"\n style=\"--marquee-font-size: 15px; --marquee-font-family: 'Gilroy', sans-serif; --marquee-font-color: white; --marquee-font-weight: 100; --marquee-animation-duration: 8s;\"\n ></lib-marquee>\n\n <!-- Dash beneath each interest -->\n <div class=\"profile-comparison__dash-container\" [ngSwitch]=\"getDashType(i)\">\n <!-- High Similarity: Short single dash, 0.3 opacity -->\n <svg *ngSwitchCase=\"'high'\" class=\"profile-comparison__dash profile-comparison__dash--high\" width=\"15\" height=\"4\" viewBox=\"0 0 15 4\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M2 2H13\" stroke=\"white\" stroke-width=\"4\" stroke-opacity=\"0.3\" stroke-linecap=\"round\"/>\n </svg>\n <!-- Medium Similarity: Standard 1 dash -->\n <svg *ngSwitchCase=\"'medium'\" class=\"profile-comparison__dash profile-comparison__dash--medium\" width=\"30\" height=\"4\" viewBox=\"0 0 30 4\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M2 2H28\" stroke=\"white\" stroke-width=\"4\" stroke-linecap=\"round\"/>\n </svg>\n <!-- Low Similarity: 4 small dashes -->\n <svg *ngSwitchCase=\"'low'\" class=\"profile-comparison__dash profile-comparison__dash--low\" width=\"30\" height=\"4\" viewBox=\"0 0 30 4\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M2 2H28\" stroke=\"white\" stroke-width=\"4\" stroke-dasharray=\"2,5\" stroke-linecap=\"round\"/>\n </svg>\n </div>\n </div>\n </ng-container>\n </div>\n </div>\n </div>\n\n <div class=\"profile-comparison__frame profile-comparison__frame--right swiper-no-swiping\"\n [class.is-animating-nudge]=\"rightShapeAnimating\"\n (click)=\"onShapeClick('right')\"\n #rightFrame>\n <svg\n class=\"profile-comparison__frame-svg profile-comparison__frame-svg--glass\"\n viewBox=\"0 0 257 275\"\n preserveAspectRatio=\"none\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <g filter=\"url(#filter-glass-right)\">\n <path\n d=\"M256.24 267.5 Q256.24 277.5 246.24 275.9 L243.564 275.546 L9.32411 239.437 C3.95944 238.61 0 233.976 0 228.548 V37.4032 C0 31.7824 4.2375 27.0452 9.8262 26.4454 L244.066 1.30651 L246.24 1.1 Q256.24 0 256.24 10 L256.24 267.5\"\n stroke=\"#AB4AFF\"\n stroke-opacity=\"0.3\"\n stroke-width=\"0.809707\"\n stroke-linejoin=\"round\"\n shape-rendering=\"geometricPrecision\"\n />\n </g>\n <defs>\n <filter id=\"filter-glass-right\" x=\"-40\" y=\"-40\" width=\"337\" height=\"355\" 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 3.3333 0\" result=\"hardAlpha\"/>\n <feOffset/>\n <feGaussianBlur stdDeviation=\"6.3562\"/>\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\"/>\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 3.3333 0\" result=\"hardAlpha\"/>\n <feOffset/>\n <feGaussianBlur stdDeviation=\"6.3562\"/>\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\" result=\"effect2_dropShadow\"/>\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 3.3333 0\" result=\"hardAlpha\"/>\n <feOffset/>\n <feGaussianBlur stdDeviation=\"6.3562\"/>\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\" result=\"effect3_dropShadow\"/>\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 3.3333 0\" result=\"hardAlpha\"/>\n <feOffset/>\n <feGaussianBlur stdDeviation=\"6.3562\"/>\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\" result=\"effect4_dropShadow\"/>\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 3.3333 0\" result=\"hardAlpha\"/>\n <feOffset/>\n <feGaussianBlur stdDeviation=\"6.3562\"/>\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\" result=\"effect5_dropShadow\"/>\n <feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"effect5_dropShadow\" result=\"shape\"/>\n </filter>\n </defs>\n </svg>\n <svg\n class=\"profile-comparison__frame-svg profile-comparison__frame-svg--middle\"\n viewBox=\"0 0 257 276\"\n preserveAspectRatio=\"none\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <defs>\n <clipPath id=\"clip-middle-right\">\n <path d=\"M9.8262 26.4454 L244.066 1.30651 L246.24 1.1 Q256.24 0 256.24 10 V267.5 Q256.24 277.5 246.24 275.9 L243.564 275.546 L9.32411 239.437 C3.95935 237.368 -9.15527e-05 232.733 -9.15527e-05 227.305 V36.1607 C-9.15527e-05 30.5399 4.23741 25.8028 9.8262 26.4454 Z\"/>\n </clipPath>\n <filter id=\"filter0_i_2126_3750\" x=\"0\" y=\"-2\" width=\"256.24\" height=\"279\" 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 14.2857 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_2126_3750\" />\n </filter>\n </defs>\n <g clip-path=\"url(#clip-middle-right)\" filter=\"url(#filter0_i_2126_3750)\">\n <path\n d=\"M9.82611 25.203 L244.066 0.0640762 L246.24 -0.05 Q256.24 -1.2424 256.24 8.75 V266.25 Q256.24 276.258 246.24 274.75 L243.564 274.304 L9.32402 238.195 C3.95935 237.368 -9.15527e-05 232.733 -9.15527e-05 227.305 V36.1607 C-9.15527e-05 30.5399 4.23741 25.8028 9.82611 25.203 Z\"\n fill=\"#7B00E6\"\n fill-opacity=\"0.1\"\n shape-rendering=\"geometricPrecision\"\n />\n </g>\n </svg>\n <svg\n class=\"profile-comparison__frame-svg profile-comparison__frame-svg--right\"\n viewBox=\"0 0 257 278\"\n preserveAspectRatio=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <defs>\n <radialGradient id=\"profile-right-fill\" cx=\"0\" cy=\"0\" r=\"1\" gradientTransform=\"matrix(-65.1644 -81.25 -75.0252 49.3261 128.12 138.75)\" gradientUnits=\"userSpaceOnUse\">\n <stop stop-color=\"#0051E6\" stop-opacity=\"0\" />\n <stop offset=\"0.5\" stop-color=\"#0051E6\" stop-opacity=\"0.035\" />\n <stop offset=\"1\" stop-color=\"white\" />\n </radialGradient>\n <linearGradient id=\"profile-right-stroke\" x1=\"128.12\" y1=\"277.5\" x2=\"128.12\" y2=\"8.50002\" gradientUnits=\"userSpaceOnUse\">\n <stop offset=\"0\" stop-color=\"#662C99\" />\n <stop offset=\"0.5\" stop-color=\"#893BD3\" />\n <stop offset=\"1\" stop-color=\"#AB4AFF\" />\n </linearGradient>\n </defs>\n <!-- Fill Layer -->\n <path\n d=\"M9.8262 26.4454 L244.066 1.30651 L246.24 1.1 Q256.24 0 256.24 10 V267.5 Q256.24 277.5 246.24 275.9 L243.564 275.546 L9.32411 239.437 C3.95944 238.61 0 233.976 0 228.548 V37.4032 C0 31.7824 4.2375 27.0452 9.8262 26.4454 Z\"\n fill=\"url(#profile-right-fill)\"\n fill-opacity=\"0.07\"\n shape-rendering=\"geometricPrecision\"\n />\n <!-- Stroke Layer (Right outer border skipped) -->\n <path\n d=\"M256.24 267.5 Q256.24 277.5 246.24 275.9 L243.564 275.546 L9.32411 239.437 C3.95944 238.61 0 233.976 0 228.548 V37.4032 C0 31.7824 4.2375 27.0452 9.8262 26.4454 L244.066 1.30651 L246.24 1.1 Q256.24 0 256.24 10 L256.24 267.5\"\n fill=\"none\"\n stroke=\"url(#profile-right-stroke)\"\n stroke-width=\"2\"\n stroke-miterlimit=\"3.99393\"\n stroke-linejoin=\"round\"\n shape-rendering=\"geometricPrecision\"\n />\n <g\n style=\"cursor: pointer !important;\"\n (click)=\"onViewProfile('right'); $event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\"\n >\n <rect [attr.x]=\"viewProfileRightX - 100\" y=\"238\" width=\"115\" height=\"40\" fill=\"transparent\" pointer-events=\"auto\" />\n <text\n [attr.x]=\"viewProfileRightX\" y=\"253\"\n fill=\"rgba(255, 255, 255, 0.60)\"\n font-family=\"'Calistoga', serif\"\n font-size=\"9\"\n text-anchor=\"end\"\n [class.is-animating-bob]=\"rightProfileClicked\"\n style=\"text-shadow: 0 2px 4.2px #000;\"\n pointer-events=\"auto\"\n >View Profile</text>\n </g>\n </svg>\n <div class=\"profile-comparison__content profile-comparison__content--right\">\n <div class=\"profile-comparison__list profile-comparison__list--right\">\n <!-- Semantic Items only -->\n <ng-container *ngFor=\"let interest of alignedPerson2Interests; let i = index\">\n <div class=\"profile-comparison__item profile-comparison__item--right\">\n <lib-marquee\n class=\"profile-comparison__text\"\n [title]=\"interest\"\n [onlyMarqueeOnHover]=\"false\"\n style=\"--marquee-font-size: 15px; --marquee-font-family: 'Gilroy', sans-serif; --marquee-font-color: white; --marquee-font-weight: 100; --marquee-animation-duration: 8s;\"\n ></lib-marquee>\n\n <!-- Dash beneath each interest -->\n <div class=\"profile-comparison__dash-container\" [ngSwitch]=\"getDashType(i)\">\n <!-- High Similarity: Short single dash, 0.5 opacity -->\n <svg *ngSwitchCase=\"'high'\" class=\"profile-comparison__dash profile-comparison__dash--high\" width=\"15\" height=\"4\" viewBox=\"0 0 15 4\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M2 2H13\" stroke=\"white\" stroke-width=\"4\" stroke-opacity=\"0.5\" stroke-linecap=\"round\"/>\n </svg>\n <!-- Medium Similarity: Standard 1 dash -->\n <svg *ngSwitchCase=\"'medium'\" class=\"profile-comparison__dash profile-comparison__dash--medium\" width=\"30\" height=\"4\" viewBox=\"0 0 30 4\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M2 2H28\" stroke=\"white\" stroke-width=\"4\" stroke-linecap=\"round\"/>\n </svg>\n <!-- Low Similarity: 4 small dashes -->\n <svg *ngSwitchCase=\"'low'\" class=\"profile-comparison__dash profile-comparison__dash--low\" width=\"30\" height=\"4\" viewBox=\"0 0 30 4\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M2 2H28\" stroke=\"white\" stroke-width=\"4\" stroke-dasharray=\"2,5\" stroke-linecap=\"round\"/>\n </svg>\n </div>\n </div>\n </ng-container>\n </div>\n </div>\n </div>\n\n <!-- Center text section -->\n <div class=\"profile-comparison__center\" #shapeTextCenter>\n <div class=\"profile-comparison__center-inner\">\n <!-- Exact Items only -->\n <div *ngFor=\"let item of centerItem\" class=\"profile-comparison__center-item\">\n <lib-marquee\n class=\"profile-comparison__center-text\"\n [title]=\"item\"\n [onlyMarqueeOnHover]=\"false\"\n style=\"--marquee-font-size: 15px; --marquee-font-family: 'Gilroy', sans-serif; --marquee-font-color: white; --marquee-font-weight: 700; --marquee-animation-duration: 8s;\"\n ></lib-marquee>\n <!-- Invisible dash as requested -->\n <div class=\"profile-comparison__center-dash-placeholder\"></div>\n </div>\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: ["@keyframes textDropIn{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}@keyframes nudgeLeft{0%,to{transform:translate(-24%) scale(.86)}50%{transform:translate(-26%) scale(.86)}}@keyframes nudgeRight{0%,to{transform:translate(24%) scale(.86)}50%{transform:translate(26%) scale(.86)}}.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 0%,black 10%,black 90%,transparent 100%),linear-gradient(to right,transparent,black 15%,black 85%,transparent);-webkit-mask-composite:source-in;mask-image:linear-gradient(to bottom,transparent 0%,black 10%,black 90%,transparent 100%),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 0%,black 10%,black 90%,transparent 100%),linear-gradient(to right,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%);mask-image:linear-gradient(to bottom,transparent 0%,black 10%,black 90%,transparent 100%),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 0%,black 10%,black 90%,transparent 100%),linear-gradient(to left,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%);mask-image:linear-gradient(to bottom,transparent 0%,black 10%,black 90%,transparent 100%),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}.profile-screen{width:100%;max-width:460px;margin:0 auto;overflow:visible;position:relative}.profile-flex{display:flex;width:100%;height:auto;align-items:center;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-bg{position:relative;pointer-events:auto;width:100%;height:100%}.shape-bg1,.shape-bg2{position:absolute;width:75%;max-width:none;height:350px;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;overflow:visible!important}.shape-bg svg:active{cursor:grabbing}.profile-img{width:50%;height:350px;position:relative;flex-shrink:0;background:linear-gradient(135deg,#1a1a1a,#0a0a0a);margin:0;padding:0;left:0;top:0;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 30%;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:32px;width:100%;left:0;z-index:2;pointer-events:none}.shape-bg{position:relative;pointer-events:auto}.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:0 .5px 1.5px #000;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:0 .5px 1.5px #000;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:0 .5px 1.5px #000}.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:0 .5px 1.5px #000;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:0 .5px 1.5px #000;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:0 .5px 1.5px #000;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}:host{display:block}.profile-comparison{width:100%;padding:0;box-sizing:border-box;display:flex;justify-content:center;align-items:center;overflow:visible}.profile-comparison__inner{position:relative;width:100%;min-width:25.75rem;max-width:100%;min-height:500px;display:grid;grid-template-columns:1fr;grid-template-rows:auto;background:transparent;overflow:visible}.profile-comparison__bg-layer{grid-area:1/1;position:relative;width:100%;height:100%;min-height:inherit;pointer-events:none;display:grid;grid-template-columns:1fr;grid-template-rows:1fr}.profile-comparison__frame{grid-area:1/1;position:relative;pointer-events:none}.profile-comparison__frame.bg-frame{width:100%;height:100%;min-height:inherit;overflow:hidden;border-radius:10px;-webkit-mask-composite:source-in;mask-composite:intersect;will-change:auto}.profile-comparison__frame.bg-frame.profile-comparison__frame--left{-webkit-mask-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 278'%3E%3Cpath d='M246.177 26.4924 L10 1.07 Q0 0 0 10 V268 Q0 278 9.8 276.5 L246.68 239.869 C252.043 239.04 256 234.407 256 228.981 V37.4497 C256 31.8302 251.764 27.0937 246.177 26.4924 Z' fill='white'/%3E%3C/svg%3E\"),linear-gradient(to top,transparent 0%,black 15px,black 100%);mask-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 278'%3E%3Cpath d='M246.177 26.4924 L10 1.07 Q0 0 0 10 V268 Q0 278 9.8 276.5 L246.68 239.869 C252.043 239.04 256 234.407 256 228.981 V37.4497 C256 31.8302 251.764 27.0937 246.177 26.4924 Z' fill='white'/%3E%3C/svg%3E\"),linear-gradient(to top,transparent 0%,black 15px,black 100%);-webkit-mask-size:100% 100%,100% 100%;mask-size:100% 100%,100% 100%;-webkit-mask-composite:source-in;mask-composite:intersect}.fade-all-edges .profile-comparison__frame.bg-frame.profile-comparison__frame--left.edge-at-left-border{-webkit-mask-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 278'%3E%3Cpath d='M246.177 26.4924 L10 1.07 Q0 0 0 10 V268 Q0 278 9.8 276.5 L246.68 239.869 C252.043 239.04 256 234.407 256 228.981 V37.4497 C256 31.8302 251.764 27.0937 246.177 26.4924 Z' fill='white'/%3E%3C/svg%3E\"),linear-gradient(to top,transparent 0%,black 15px,black 100%),linear-gradient(to right,transparent 0%,black 20px,black 100%);mask-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 278'%3E%3Cpath d='M246.177 26.4924 L10 1.07 Q0 0 0 10 V268 Q0 278 9.8 276.5 L246.68 239.869 C252.043 239.04 256 234.407 256 228.981 V37.4497 C256 31.8302 251.764 27.0937 246.177 26.4924 Z' fill='white'/%3E%3C/svg%3E\"),linear-gradient(to top,transparent 0%,black 15px,black 100%),linear-gradient(to right,transparent 0%,black 20px,black 100%);-webkit-mask-size:100% 100%,100% 100%,100% 100%;mask-size:100% 100%,100% 100%,100% 100%;-webkit-mask-composite:source-in;mask-composite:intersect}.profile-comparison__frame.bg-frame.profile-comparison__frame--right{-webkit-mask-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 257 278'%3E%3Cpath d='M9.8262 26.4454 L244.066 1.30651 L246.24 1.1 Q256.24 0 256.24 10 V267.5 Q256.24 277.5 246.24 275.9 L243.564 275.546 L9.32411 239.437 C3.95944 238.61 0 233.976 0 228.548 V37.4032 C0 31.7824 4.2375 27.0452 9.8262 26.4454 Z' fill='white'/%3E%3C/svg%3E\"),linear-gradient(to top,transparent 0%,black 15px,black 100%);mask-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 257 278'%3E%3Cpath d='M9.8262 26.4454 L244.066 1.30651 L246.24 1.1 Q256.24 0 256.24 10 V267.5 Q256.24 277.5 246.24 275.9 L243.564 275.546 L9.32411 239.437 C3.95944 238.61 0 233.976 0 228.548 V37.4032 C0 31.7824 4.2375 27.0452 9.8262 26.4454 Z' fill='white'/%3E%3C/svg%3E\"),linear-gradient(to top,transparent 0%,black 15px,black 100%);-webkit-mask-size:100% 100%,100% 100%;mask-size:100% 100%,100% 100%;-webkit-mask-composite:source-in;mask-composite:intersect}.fade-all-edges .profile-comparison__frame.bg-frame.profile-comparison__frame--right.edge-at-right-border{-webkit-mask-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 257 278'%3E%3Cpath d='M9.8262 26.4454 L244.066 1.30651 L246.24 1.1 Q256.24 0 256.24 10 V267.5 Q256.24 277.5 246.24 275.9 L243.564 275.546 L9.32411 239.437 C3.95944 238.61 0 233.976 0 228.548 V37.4032 C0 31.7824 4.2375 27.0452 9.8262 26.4454 Z' fill='white'/%3E%3C/svg%3E\"),linear-gradient(to top,transparent 0%,black 15px,black 100%),linear-gradient(to left,transparent 0%,black 20px,black 100%);mask-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 257 278'%3E%3Cpath d='M9.8262 26.4454 L244.066 1.30651 L246.24 1.1 Q256.24 0 256.24 10 V267.5 Q256.24 277.5 246.24 275.9 L243.564 275.546 L9.32411 239.437 C3.95944 238.61 0 233.976 0 228.548 V37.4032 C0 31.7824 4.2375 27.0452 9.8262 26.4454 Z' fill='white'/%3E%3C/svg%3E\"),linear-gradient(to top,transparent 0%,black 15px,black 100%),linear-gradient(to left,transparent 0%,black 20px,black 100%);-webkit-mask-size:100% 100%,100% 100%,100% 100%;mask-size:100% 100%,100% 100%,100% 100%;-webkit-mask-composite:source-in;mask-composite:intersect}.profile-comparison__frame--left{transform:translate(-24%) scale(.86)}.profile-comparison__frame--left.is-animating-nudge{animation:nudgeLeft .5s ease-out}.profile-comparison__frame--left .profile-comparison__frame-svg{-webkit-mask-image:var(--edge-mask-left, linear-gradient(to right, transparent 0%, rgba(0, 0, 0, .4) 14px, black var(--edge-shading-width, 28px)));mask-image:var(--edge-mask-left, linear-gradient(to right, transparent 0%, rgba(0, 0, 0, .4) 14px, black var(--edge-shading-width, 28px)))}.profile-comparison__frame--left .profile-comparison__frame-svg--glass{transform:scaleX(1.04) translateY(-4px);transform-origin:center;-webkit-mask-image:none;mask-image:none}.profile-comparison__frame--left .profile-comparison__frame-svg--left,.profile-comparison__frame--left .profile-comparison__frame-svg--right{-webkit-mask-image:none;mask-image:none}.profile-comparison__frame--right{transform:translate(24%) scale(.86)}.profile-comparison__frame--right.is-animating-nudge{animation:nudgeRight .5s ease-out}.profile-comparison__frame--right .profile-comparison__frame-svg{-webkit-mask-image:var(--edge-mask-right, linear-gradient(to left, transparent 0%, rgba(0, 0, 0, .4) 14px, black var(--edge-shading-width, 28px)));mask-image:var(--edge-mask-right, linear-gradient(to left, transparent, black var(--edge-shading-width, 28px)))}.profile-comparison__frame--right .profile-comparison__frame-svg--glass{transform:scaleX(1.04) translateY(-4px);transform-origin:center;-webkit-mask-image:none;mask-image:none}.profile-comparison__frame--right .profile-comparison__frame-svg--left,.profile-comparison__frame--right .profile-comparison__frame-svg--right{-webkit-mask-image:none;mask-image:none}.profile-comparison__frame--right .profile-comparison__frame-svg--bottom{width:113.2%}.profile-comparison__frame-svg{position:absolute;inset:0;width:100%;height:100%;pointer-events:auto;overflow:visible}.profile-comparison__frame-svg--glass{z-index:1}.profile-comparison__frame-svg--middle{z-index:2;mix-blend-mode:screen}.profile-comparison__frame-svg--left,.profile-comparison__frame-svg--right{z-index:3}.profile-comparison__frame-svg--bottom{width:113.7%;height:112.6%;inset:51% auto auto 50%;transform:translate(-50%,-51%)}.profile-comparison__bg{position:absolute;top:0;left:0;right:0;height:130%;background-size:cover;background-repeat:no-repeat;background-position:center 30%;pointer-events:auto;opacity:.8;transition:filter .6s ease,opacity .6s ease}.profile-comparison__bg:after{content:\"\";position:absolute;inset:0;background:#000000bf;pointer-events:none}.profile-comparison__bg--left{background-position:right center;margin-right:0%;-webkit-mask-image:linear-gradient(to right,black 50%,transparent 100%);mask-image:linear-gradient(to right,black 50%,transparent 100%);-webkit-mask-composite:source-in;mask-composite:intersect}.profile-comparison__bg--right{background-position:left center;margin-left:0%;-webkit-mask-image:linear-gradient(to left,black 50%,transparent 100%);mask-image:linear-gradient(to left,black 50%,transparent 100%);-webkit-mask-composite:source-in;mask-composite:intersect}@keyframes viewProfileBob{0%{transform:translateY(0)}50%{transform:translateY(-3px)}to{transform:translateY(0)}}.profile-comparison svg text{transition:opacity .3s ease}.profile-comparison svg text.is-animating-bob{animation:viewProfileBob .6s ease-in-out;fill:#fff!important}.profile-comparison__content{position:relative;z-index:20;padding:50px 12% 75px;min-height:100%;display:flex;flex-direction:column;justify-content:flex-start;color:#ecf0f1;font-family:Gilroy,serif;pointer-events:none}.profile-comparison__content--left{align-items:flex-end;text-align:right;padding-right:31%}.profile-comparison__content--right{align-items:flex-start;text-align:left;padding-left:31%}.profile-comparison__list{display:flex;flex-direction:column;gap:2px;width:100%;pointer-events:auto}.profile-comparison__list--left{align-items:flex-end;text-align:right;padding-right:31%}.profile-comparison__list--right{align-items:flex-start;text-align:left;padding-left:31%}.profile-comparison__item{display:flex;flex-direction:column;height:auto;width:11rem;flex-shrink:0;gap:6px;margin-bottom:6px;animation:textDropIn .4s ease-out backwards}.profile-comparison__item--left{align-items:flex-end;text-align:right}.profile-comparison__item--right{align-items:flex-start;text-align:left}.profile-comparison__text{font-family:Gilroy,sans-serif;font-size:15px;font-weight:100!important;color:#fff;width:100%;display:block;text-shadow:0 .5px 1.5px #000}.profile-comparison__text ::ng-deep *{font-weight:400!important;text-shadow:0 .5px 1.5px #000}.profile-comparison__dash-container{display:flex;width:100%;height:8px;align-items:center}.profile-comparison__item--left .profile-comparison__dash-container{justify-content:flex-end}.profile-comparison__item--right .profile-comparison__dash-container{justify-content:flex-start}.profile-comparison__dash{display:block}.profile-comparison__center-empty,.profile-comparison__center-dash-placeholder{height:8px;width:100%}.profile-comparison__center{grid-area:1/1;display:flex;align-items:flex-start;justify-content:center;z-index:15;pointer-events:none;transform:scale(.86)}.profile-comparison__center-inner{display:flex;flex-direction:column;align-items:center;text-align:center;justify-content:flex-start;padding:50px 0 75px;gap:6px;width:100%}.profile-comparison__center-item{height:auto;min-height:22px;display:flex;flex-direction:column;align-items:center;justify-content:flex-start;width:9rem;flex-shrink:0;gap:6px;margin-bottom:6px;animation:textDropIn .4s ease-out backwards}.profile-comparison__center-text{font-family:Gilroy,sans-serif;font-size:15px;font-weight:700;color:#fff;width:100%;display:block;text-shadow:0 .5px 1.5px #000}.profile-comparison__center-text--empty{color:#ecf0f180}.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.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i2.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i2.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "component", type: i3.MarqueeComponent, selector: "lib-marquee", inputs: ["title", "searchText", "padding", "onlyMarqueeOnHover"], outputs: ["marquee"] }] });
2549
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", 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 });
2550
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.25", type: ProfileComparisonLibComponent, isStandalone: false, selector: "lib-profile-comparison", inputs: { config: "config", user1File: "user1File", user2File: "user2File", backendMode: "backendMode", backendUrl: "backendUrl", fadeAllEdges: "fadeAllEdges", person1Name: "person1Name", person2Name: "person2Name", imageProxyPath: "imageProxyPath" }, 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 #profileFlex class=\"profile-flex\" [class.fade-all]=\"fadeAllEdges\">\n <svg class=\"image-noise-overlay\" style=\"position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; overflow: hidden; z-index: 0;\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 320\" fill=\"none\" preserveAspectRatio=\"none\">\n <g filter=\"url(#filter0_n_1_1753)\">\n <rect width=\"100%\" height=\"100%\" fill=\"url(#paint0_radial_1_1753)\" fill-opacity=\"0.33\"/>\n </g>\n <defs>\n <filter id=\"filter0_n_1_1753\" x=\"0%\" y=\"0%\" width=\"100%\" height=\"100%\" filterUnits=\"objectBoundingBox\" color-interpolation-filters=\"sRGB\">\n <feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\n <feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"BackgroundImageFix\" result=\"shape\"/>\n <feTurbulence type=\"fractalNoise\" baseFrequency=\"0.10869565606117249 0.10869565606117249\" stitchTiles=\"stitch\" numOctaves=\"3\" result=\"noise\" seed=\"7339\"/>\n <feColorMatrix in=\"noise\" type=\"luminanceToAlpha\" result=\"alphaNoise\"/>\n <feComponentTransfer in=\"alphaNoise\" result=\"coloredNoise1\">\n <feFuncA type=\"discrete\" tableValues=\"0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \"/>\n </feComponentTransfer>\n <feComposite operator=\"in\" in2=\"shape\" in=\"coloredNoise1\" result=\"noise1Clipped\"/>\n <feFlood flood-color=\"rgba(0, 0, 0, 0.25)\" result=\"color1Flood\"/>\n <feComposite operator=\"in\" in2=\"noise1Clipped\" in=\"color1Flood\" result=\"color1\"/>\n <feMerge result=\"effect1_noise_1_1753\">\n <feMergeNode in=\"shape\"/>\n <feMergeNode in=\"color1\"/>\n </feMerge>\n </filter>\n <radialGradient id=\"paint0_radial_1_1753\" cx=\"50%\" cy=\"50%\" r=\"50%\" fx=\"50%\" fy=\"50%\">\n <stop offset=\"0%\" stop-opacity=\"0\"/>\n <stop offset=\"100%\" stop-opacity=\"0.25\"/>\n </radialGradient>\n </defs>\n </svg>\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.top]=\"user1Top\"\n [style.height]=\"user1Height\"\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.top]=\"user2Top\"\n [style.height]=\"user2Height\"\n [style.object-position]=\"user2ObjectPosition\"\n (load)=\"onUserImageLoad(2, $event)\"\n />\n </div>\n <div class=\"image-shade-overlay\"></div>\n\n <!-- Frosted Glass Overlay Layer -->\n <div class=\"overlay-container\">\n <!-- Top Headers -->\n <div class=\"header-label left\">{{ person1Name }}</div>\n <div class=\"header-label center\">Shared</div>\n <div class=\"header-label right\">{{ person2Name }}</div>\n\n <!-- Chips Columns Grid -->\n <div class=\"chips-grid\">\n <!-- Person 1 Unique Interests -->\n <div class=\"chips-column left\">\n <ng-container *ngFor=\"let interest of getPerson1UniqueInterests(); let i = index\">\n <div class=\"frosted-chip person-a\" [style.margin-right.px]=\"getChipOffset(i, 'left')\" [style.margin-top.px]=\"(i > 0 ? 4 : 0)\">\n <span class=\"chip-label\">{{ interest }}</span>\n </div>\n </ng-container>\n </div>\n\n <!-- Shared Interests (Center Column) -->\n <div class=\"chips-column center\">\n <ng-container *ngFor=\"let interest of centerItem; let i = index\">\n <div *ngIf=\"interest !== 'empty'\" class=\"frosted-chip shared\" [style.margin-left.px]=\"getChipOffset(i, 'center')\" [style.margin-top.px]=\"(i > 0 ? 4 : 0)\">\n <span class=\"chip-label\">{{ interest }}</span>\n </div>\n <div *ngIf=\"interest === 'empty'\" class=\"frosted-chip ghost-chip shared\" [style.margin-left.px]=\"getChipOffset(i, 'center')\" [style.margin-top.px]=\"(i > 0 ? 4 : 0)\">\n <span class=\"chip-label\">&nbsp;</span>\n </div>\n </ng-container>\n </div>\n\n <!-- Person 2 Unique Interests -->\n <div class=\"chips-column right\">\n <ng-container *ngFor=\"let interest of getPerson2UniqueInterests(); let i = index\">\n <div class=\"frosted-chip person-b\" [style.margin-left.px]=\"getChipOffset(i, 'right')\" [style.margin-top.px]=\"(i > 0 ? 4 : 0)\">\n <span class=\"chip-label\">{{ interest }}</span>\n </div>\n </ng-container>\n </div>\n </div>\n\n <!-- Bottom Actions -->\n <div class=\"view-profile left\" (click)=\"onViewProfile('left')\">View Profile</div>\n <div class=\"view-profile right\" (click)=\"onViewProfile('right')\">View Profile</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: [":host{display:block;width:100%;max-width:100%;overflow-x:hidden!important;box-sizing:border-box!important}.profile-img.left{position:absolute;top:0;bottom:0;left:0;width:58%;height:100%;margin:0;z-index:1;-webkit-mask-image:linear-gradient(to right,black 0%,black 45%,transparent 92%);mask-image:linear-gradient(to right,black 0%,black 45%,transparent 92%)}.profile-img.right{position:absolute;inset:0 0 0 auto;width:58%;height:100%;margin:0;z-index:2;-webkit-mask-image:linear-gradient(to right,transparent 8%,black 55%,black 100%);mask-image:linear-gradient(to right,transparent 8%,black 55%,black 100%)}@media(min-width:768px){.profile-img.left{width:65%;-webkit-mask-image:linear-gradient(to right,black 0%,black 40%,transparent 90%);mask-image:linear-gradient(to right,black 0%,black 40%,transparent 90%)}.profile-img.right{width:65%;-webkit-mask-image:linear-gradient(to right,transparent 10%,black 60%,black 100%);mask-image:linear-gradient(to right,transparent 10%,black 60%,black 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:100%;max-width:768px;margin:0 auto;overflow:hidden;position:relative;border-radius:12px}.profile-flex{display:flex;width:100%;height:440px;overflow:hidden;background:transparent;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 18%,black 82%,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 18%,black 82%,transparent 100%),linear-gradient(to bottom,transparent 0%,black 15%,black 85%,transparent 100%);mask-composite:intersect}.profile-flex .image-shade-overlay{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:3;background:radial-gradient(ellipse at center,transparent 40%,rgba(41,37,45,.25) 100%)}.profile-flex .image-noise-overlay{position:absolute;inset:0;width:100%;height:100%;max-width:100%;max-height:100%;pointer-events:none;overflow:hidden;z-index:0}.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{height:100%;position:relative;flex-shrink:0;background:transparent;margin:0;padding:0;left:0;top:0;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:1;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}.overlay-container{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:10;-webkit-user-select:none;user-select:none;overflow:hidden;background:#29252da6;backdrop-filter:blur(.5px);-webkit-backdrop-filter:blur(.5px)}.header-label{position:absolute;top:20px;font-family:Gilroy,sans-serif;text-shadow:0 1px 8px rgba(0,0,0,.8);pointer-events:none}.header-label.left{left:5%;transform:none;font-size:14px;font-weight:400;color:#fffffff2}.header-label.center{left:50%;transform:translate(-50%);font-size:12px;font-weight:400;color:#fffc;letter-spacing:0}.header-label.right{right:5%;left:auto;transform:none;font-size:14px;font-weight:400;color:#fffffff2}.chips-grid{position:absolute;inset:45px 0;display:flex;padding:0;overflow-y:auto;overscroll-behavior:none;scrollbar-width:none;box-sizing:border-box}.chips-grid::-webkit-scrollbar{display:none}.chips-column{flex:1;display:flex;flex-direction:column;gap:8px;padding:4px 10px;box-sizing:border-box}.chips-column.left{align-items:flex-end}.chips-column.center{align-items:center}.chips-column.right{align-items:flex-start}.frosted-chip{position:relative;cursor:pointer;pointer-events:auto;transition:transform .2s ease,box-shadow .2s ease,background .2s ease;flex-shrink:0;padding:6px 18px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;border:none;background:#ffffff30;box-shadow:inset 0 1.5px 2px #ffffff59;backdrop-filter:blur(.5px);-webkit-backdrop-filter:blur(.5px)}.frosted-chip:hover{transform:scale(1.04);background:#ffffff29}.frosted-chip .chip-label{position:relative;color:#a8a8a8;text-align:center;font-family:Gilroy,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:140%;letter-spacing:-.01em;text-shadow:none;white-space:nowrap;pointer-events:none;z-index:1}.ghost-chip{visibility:hidden!important;pointer-events:none!important}.view-profile{position:absolute;bottom:20px!important;color:#ffffff80;text-shadow:0 2px 4.2px #000;font-family:Calistoga,serif;font-size:18px;font-style:normal;font-weight:400;line-height:normal;cursor:pointer;pointer-events:auto;transition:opacity .2s ease,transform .2s ease}.view-profile:hover{opacity:.85}.view-profile.left{left:5%;transform:none}.view-profile.left:hover{transform:translateY(-2px)}.view-profile.right{right:5%;left:auto;transform:none}.view-profile.right:hover{transform:translateY(-2px)}#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:absolute;inset:0;width:100%;height:100%;background:#00000040;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);color:#fff;z-index:100;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;pointer-events:auto}.loading-spinner{width:44px;height:44px;border:3.5px solid rgba(255,255,255,.25);border-top:3.5px solid #ffffff;border-radius:50%;animation:spin .8s linear infinite;box-shadow:0 0 15px #fff3}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.loading-indicator p{margin:0;font-size:14px;font-weight:500;color:#ffffffd9;letter-spacing:.02em}.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}@media(max-width:1023px){.profile-screen{max-width:640px}.profile-flex{height:400px}.profile-img{height:550px}}@media(max-width:767px){.profile-screen,.profile-flex,.overlay-container{width:100%;max-width:100%;overflow-x:hidden;box-sizing:border-box}.profile-screen{border-radius:0}.profile-flex{height:360px}.profile-img{height:510px}.chips-grid{width:100%;max-width:100%;padding:0 4px;top:50px;bottom:50px;box-sizing:border-box}.chips-column{padding:2px}.chips-column.left{align-items:flex-end}.chips-column.center{align-items:center}.chips-column.right{align-items:flex-start}.frosted-chip{padding:4px 10px;max-width:100%;box-sizing:border-box}.frosted-chip .chip-label{font-size:11px;max-width:100%;overflow:hidden;text-overflow:ellipsis}.view-profile{bottom:14px}}@media(max-width:480px){.profile-flex{height:340px}.profile-img{height:480px}.frosted-chip{padding:3px 8px}.frosted-chip .chip-label{font-size:10px}.header-label{top:14px}.header-label.left{font-size:12px}.header-label.center{font-size:10px}.header-label.right{font-size:12px}}\n"], dependencies: [{ kind: "directive", type: i2_1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2_1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
2546
2551
  }
2547
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ProfileComparisonLibComponent, decorators: [{
2552
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ProfileComparisonLibComponent, decorators: [{
2548
2553
  type: Component,
2549
- 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 profile-comparison\" *ngIf=\"backendConfigured\"\n [class.fade-all-edges]=\"config.edgeShading !== false\"\n [style.--edge-shading-color]=\"config.shadingColor || 'rgba(44, 40, 47, 0.0)'\"\n [style.--edge-shading-display]=\"config.edgeShading !== false ? 'block' : 'none'\">\n\n <div class=\"profile-comparison__inner\" #shapeContainer>\n <!-- Backgrounds layer: structurally below everything else to fix stacking -->\n <div class=\"profile-comparison__bg-layer\">\n <div class=\"profile-comparison__frame profile-comparison__frame--left bg-frame\"\n [class.is-animating-nudge]=\"leftShapeAnimating\"\n #leftBgFrame>\n <div\n class=\"profile-comparison__bg profile-comparison__bg--left\"\n [ngStyle]=\"{ 'background-image': 'url(' + user1Image + ')', 'transform': user1Transform, 'background-position': user1ObjectPosition }\"\n ></div>\n </div>\n <div class=\"profile-comparison__frame profile-comparison__frame--right bg-frame\"\n [class.is-animating-nudge]=\"rightShapeAnimating\"\n #rightBgFrame>\n <div\n class=\"profile-comparison__bg profile-comparison__bg--right\"\n [ngStyle]=\"{ 'background-image': 'url(' + user2Image + ')', 'transform': user2Transform, 'background-position': user2ObjectPosition }\"\n ></div>\n </div>\n </div>\n\n <!-- Active interactive shapes and content layer -->\n <div class=\"profile-comparison__frame profile-comparison__frame--left swiper-no-swiping\"\n [class.is-animating-nudge]=\"leftShapeAnimating\"\n (click)=\"onShapeClick('left')\"\n #leftFrame>\n <svg\n class=\"profile-comparison__frame-svg profile-comparison__frame-svg--glass\"\n viewBox=\"0 0 256 275\"\n preserveAspectRatio=\"none\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <g filter=\"url(#filter-glass-left)\">\n <path\n d=\"M246.177 26.4924 L10 1.07 Q0 0 0 10 L0 268 Q0 278 9.8 276.5 L246.68 239.869 C252.043 239.04 256 234.407 256 228.981 V37.4497 C256 31.8302 251.764 27.0937 246.177 26.4924\"\n stroke=\"#61C2AB\"\n stroke-opacity=\"0.3\"\n stroke-width=\"0.809707\"\n stroke-linejoin=\"round\"\n shape-rendering=\"geometricPrecision\"\n />\n </g>\n <defs>\n <filter id=\"filter-glass-left\" x=\"-40\" y=\"-40\" width=\"336\" height=\"355\" 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 3.3333 0\" result=\"hardAlpha\"/>\n <feOffset/>\n <feGaussianBlur stdDeviation=\"6.3562\"/>\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\"/>\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 3.3333 0\" result=\"hardAlpha\"/>\n <feOffset/>\n <feGaussianBlur stdDeviation=\"6.3562\"/>\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\" result=\"effect2_dropShadow\"/>\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 3.3333 0\" result=\"hardAlpha\"/>\n <feOffset/>\n <feGaussianBlur stdDeviation=\"6.3562\"/>\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\" result=\"effect3_dropShadow\"/>\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 3.3333 0\" result=\"hardAlpha\"/>\n <feOffset/>\n <feGaussianBlur stdDeviation=\"6.3562\"/>\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\" result=\"effect4_dropShadow\"/>\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 3.3333 0\" result=\"hardAlpha\"/>\n <feOffset/>\n <feGaussianBlur stdDeviation=\"6.3562\"/>\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\" result=\"effect5_dropShadow\"/>\n <feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"effect5_dropShadow\" result=\"shape\"/>\n </filter>\n </defs>\n </svg>\n <svg\n class=\"profile-comparison__frame-svg profile-comparison__frame-svg--middle\"\n viewBox=\"0 0 256 276\"\n preserveAspectRatio=\"none\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <defs>\n <clipPath id=\"clip-middle-left\">\n <path d=\"M246.177 26.4924 L10 1.07 Q0 0 0 10 V268 Q0 278 9.8 276.5 L246.68 239.869 C252.043 239.04 256 234.407 256 228.981 V37.4497 C256 31.8302 251.764 27.0937 246.177 26.4924 Z\"/>\n </clipPath>\n <filter id=\"filter0_i_2126_3746\" x=\"-0.638672\" y=\"-2\" width=\"256\" height=\"279\" 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 14.2857 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 0 0 0 0 0.516667 0 0 0 0 1 0 0 0 1 0\" />\n <feBlend mode=\"normal\" in2=\"shape\" result=\"effect1_innerShadow_2126_3746\" />\n </filter>\n </defs>\n <g clip-path=\"url(#clip-middle-left)\" filter=\"url(#filter0_i_2126_3746)\">\n <path\n d=\"M245.538 25.2463 L9.36 -1.2459 Q-0.638672 -2.5 -0.638672 7.5 V266.75 Q-0.638672 276.75 9.16 275.25 L246.042 238.623 C251.404 237.794 255.361 233.161 255.361 227.735 V36.2037 C255.361 30.5841 251.126 25.8476 245.538 25.2463 Z\"\n fill=\"#00CFE6\"\n fill-opacity=\"0.1\"\n shape-rendering=\"geometricPrecision\"\n />\n </g>\n </svg>\n <svg\n class=\"profile-comparison__frame-svg profile-comparison__frame-svg--left\"\n viewBox=\"0 0 256 278\"\n preserveAspectRatio=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <defs>\n <radialGradient id=\"profile-left-fill\" cx=\"0\" cy=\"0\" r=\"1\" gradientTransform=\"matrix(65.1034 -81.3964 74.955 49.415 128 139)\" gradientUnits=\"userSpaceOnUse\">\n <stop stop-color=\"#0051E6\" stop-opacity=\"0\" />\n <stop offset=\"0.5\" stop-color=\"#0051E6\" stop-opacity=\"0.035\" />\n <stop offset=\"1\" stop-color=\"white\" />\n </radialGradient>\n <linearGradient id=\"profile-left-stroke\" x1=\"128\" y1=\"0\" x2=\"128\" y2=\"278\" gradientUnits=\"userSpaceOnUse\">\n <stop offset=\"0\" stop-color=\"#52FFEB\" />\n <stop offset=\"0.5\" stop-color=\"#41CFA1\" />\n <stop offset=\"1\" stop-color=\"#319990\" />\n </linearGradient>\n </defs>\n <!-- Fill Layer -->\n <path\n d=\"M246.177 26.4924 L10 1.07 Q0 0 0 10 V268 Q0 278 9.8 276.5 L246.68 239.869 C252.043 239.04 256 234.407 256 228.981 V37.4497 C256 31.8302 251.764 27.0937 246.177 26.4924 Z\"\n fill=\"url(#profile-left-fill)\"\n fill-opacity=\"0.07\"\n shape-rendering=\"geometricPrecision\"\n />\n <!-- Stroke Layer (Left outer border skipped) -->\n <path\n d=\"M246.177 26.4924 L10 1.07 Q0 0 0 10 L0 268 Q0 278 9.8 276.5 L246.68 239.869 C252.043 239.04 256 234.407 256 228.981 V37.4497 C256 31.8302 251.764 27.0937 246.177 26.4924\"\n fill=\"none\"\n stroke=\"url(#profile-left-stroke)\"\n stroke-width=\"2\"\n stroke-miterlimit=\"3.99393\"\n stroke-linejoin=\"round\"\n shape-rendering=\"geometricPrecision\"\n />\n <!-- Added explicit embedded SVG text anchoring tightly inside the graphical shape bounds instead of pure flexbox logic padding. -->\n <g\n style=\"cursor: pointer !important;\"\n (click)=\"onViewProfile('left'); $event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\"\n >\n <rect [attr.x]=\"viewProfileLeftX - 15\" y=\"238\" width=\"115\" height=\"40\" fill=\"transparent\" pointer-events=\"auto\" />\n <text\n [attr.x]=\"viewProfileLeftX\" y=\"253\"\n fill=\"rgba(255, 255, 255, 0.60)\"\n font-family=\"'Calistoga', serif\"\n font-size=\"9\"\n text-anchor=\"start\"\n [class.is-animating-bob]=\"leftProfileClicked\"\n style=\"text-shadow: 0 2px 4.2px #000;\"\n pointer-events=\"none\"\n >View Profile</text>\n </g>\n </svg>\n <div class=\"profile-comparison__content profile-comparison__content--left\">\n <div class=\"profile-comparison__list profile-comparison__list--left\">\n <!-- Semantic Items only -->\n <ng-container *ngFor=\"let interest of alignedPerson1Interests; let i = index\">\n <div class=\"profile-comparison__item profile-comparison__item--left\">\n <lib-marquee\n class=\"profile-comparison__text\"\n [title]=\"interest\"\n [onlyMarqueeOnHover]=\"false\"\n style=\"--marquee-font-size: 15px; --marquee-font-family: 'Gilroy', sans-serif; --marquee-font-color: white; --marquee-font-weight: 100; --marquee-animation-duration: 8s;\"\n ></lib-marquee>\n\n <!-- Dash beneath each interest -->\n <div class=\"profile-comparison__dash-container\" [ngSwitch]=\"getDashType(i)\">\n <!-- High Similarity: Short single dash, 0.3 opacity -->\n <svg *ngSwitchCase=\"'high'\" class=\"profile-comparison__dash profile-comparison__dash--high\" width=\"15\" height=\"4\" viewBox=\"0 0 15 4\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M2 2H13\" stroke=\"white\" stroke-width=\"4\" stroke-opacity=\"0.3\" stroke-linecap=\"round\"/>\n </svg>\n <!-- Medium Similarity: Standard 1 dash -->\n <svg *ngSwitchCase=\"'medium'\" class=\"profile-comparison__dash profile-comparison__dash--medium\" width=\"30\" height=\"4\" viewBox=\"0 0 30 4\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M2 2H28\" stroke=\"white\" stroke-width=\"4\" stroke-linecap=\"round\"/>\n </svg>\n <!-- Low Similarity: 4 small dashes -->\n <svg *ngSwitchCase=\"'low'\" class=\"profile-comparison__dash profile-comparison__dash--low\" width=\"30\" height=\"4\" viewBox=\"0 0 30 4\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M2 2H28\" stroke=\"white\" stroke-width=\"4\" stroke-dasharray=\"2,5\" stroke-linecap=\"round\"/>\n </svg>\n </div>\n </div>\n </ng-container>\n </div>\n </div>\n </div>\n\n <div class=\"profile-comparison__frame profile-comparison__frame--right swiper-no-swiping\"\n [class.is-animating-nudge]=\"rightShapeAnimating\"\n (click)=\"onShapeClick('right')\"\n #rightFrame>\n <svg\n class=\"profile-comparison__frame-svg profile-comparison__frame-svg--glass\"\n viewBox=\"0 0 257 275\"\n preserveAspectRatio=\"none\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <g filter=\"url(#filter-glass-right)\">\n <path\n d=\"M256.24 267.5 Q256.24 277.5 246.24 275.9 L243.564 275.546 L9.32411 239.437 C3.95944 238.61 0 233.976 0 228.548 V37.4032 C0 31.7824 4.2375 27.0452 9.8262 26.4454 L244.066 1.30651 L246.24 1.1 Q256.24 0 256.24 10 L256.24 267.5\"\n stroke=\"#AB4AFF\"\n stroke-opacity=\"0.3\"\n stroke-width=\"0.809707\"\n stroke-linejoin=\"round\"\n shape-rendering=\"geometricPrecision\"\n />\n </g>\n <defs>\n <filter id=\"filter-glass-right\" x=\"-40\" y=\"-40\" width=\"337\" height=\"355\" 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 3.3333 0\" result=\"hardAlpha\"/>\n <feOffset/>\n <feGaussianBlur stdDeviation=\"6.3562\"/>\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\"/>\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 3.3333 0\" result=\"hardAlpha\"/>\n <feOffset/>\n <feGaussianBlur stdDeviation=\"6.3562\"/>\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\" result=\"effect2_dropShadow\"/>\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 3.3333 0\" result=\"hardAlpha\"/>\n <feOffset/>\n <feGaussianBlur stdDeviation=\"6.3562\"/>\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\" result=\"effect3_dropShadow\"/>\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 3.3333 0\" result=\"hardAlpha\"/>\n <feOffset/>\n <feGaussianBlur stdDeviation=\"6.3562\"/>\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\" result=\"effect4_dropShadow\"/>\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 3.3333 0\" result=\"hardAlpha\"/>\n <feOffset/>\n <feGaussianBlur stdDeviation=\"6.3562\"/>\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\" result=\"effect5_dropShadow\"/>\n <feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"effect5_dropShadow\" result=\"shape\"/>\n </filter>\n </defs>\n </svg>\n <svg\n class=\"profile-comparison__frame-svg profile-comparison__frame-svg--middle\"\n viewBox=\"0 0 257 276\"\n preserveAspectRatio=\"none\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <defs>\n <clipPath id=\"clip-middle-right\">\n <path d=\"M9.8262 26.4454 L244.066 1.30651 L246.24 1.1 Q256.24 0 256.24 10 V267.5 Q256.24 277.5 246.24 275.9 L243.564 275.546 L9.32411 239.437 C3.95935 237.368 -9.15527e-05 232.733 -9.15527e-05 227.305 V36.1607 C-9.15527e-05 30.5399 4.23741 25.8028 9.8262 26.4454 Z\"/>\n </clipPath>\n <filter id=\"filter0_i_2126_3750\" x=\"0\" y=\"-2\" width=\"256.24\" height=\"279\" 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 14.2857 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_2126_3750\" />\n </filter>\n </defs>\n <g clip-path=\"url(#clip-middle-right)\" filter=\"url(#filter0_i_2126_3750)\">\n <path\n d=\"M9.82611 25.203 L244.066 0.0640762 L246.24 -0.05 Q256.24 -1.2424 256.24 8.75 V266.25 Q256.24 276.258 246.24 274.75 L243.564 274.304 L9.32402 238.195 C3.95935 237.368 -9.15527e-05 232.733 -9.15527e-05 227.305 V36.1607 C-9.15527e-05 30.5399 4.23741 25.8028 9.82611 25.203 Z\"\n fill=\"#7B00E6\"\n fill-opacity=\"0.1\"\n shape-rendering=\"geometricPrecision\"\n />\n </g>\n </svg>\n <svg\n class=\"profile-comparison__frame-svg profile-comparison__frame-svg--right\"\n viewBox=\"0 0 257 278\"\n preserveAspectRatio=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <defs>\n <radialGradient id=\"profile-right-fill\" cx=\"0\" cy=\"0\" r=\"1\" gradientTransform=\"matrix(-65.1644 -81.25 -75.0252 49.3261 128.12 138.75)\" gradientUnits=\"userSpaceOnUse\">\n <stop stop-color=\"#0051E6\" stop-opacity=\"0\" />\n <stop offset=\"0.5\" stop-color=\"#0051E6\" stop-opacity=\"0.035\" />\n <stop offset=\"1\" stop-color=\"white\" />\n </radialGradient>\n <linearGradient id=\"profile-right-stroke\" x1=\"128.12\" y1=\"277.5\" x2=\"128.12\" y2=\"8.50002\" gradientUnits=\"userSpaceOnUse\">\n <stop offset=\"0\" stop-color=\"#662C99\" />\n <stop offset=\"0.5\" stop-color=\"#893BD3\" />\n <stop offset=\"1\" stop-color=\"#AB4AFF\" />\n </linearGradient>\n </defs>\n <!-- Fill Layer -->\n <path\n d=\"M9.8262 26.4454 L244.066 1.30651 L246.24 1.1 Q256.24 0 256.24 10 V267.5 Q256.24 277.5 246.24 275.9 L243.564 275.546 L9.32411 239.437 C3.95944 238.61 0 233.976 0 228.548 V37.4032 C0 31.7824 4.2375 27.0452 9.8262 26.4454 Z\"\n fill=\"url(#profile-right-fill)\"\n fill-opacity=\"0.07\"\n shape-rendering=\"geometricPrecision\"\n />\n <!-- Stroke Layer (Right outer border skipped) -->\n <path\n d=\"M256.24 267.5 Q256.24 277.5 246.24 275.9 L243.564 275.546 L9.32411 239.437 C3.95944 238.61 0 233.976 0 228.548 V37.4032 C0 31.7824 4.2375 27.0452 9.8262 26.4454 L244.066 1.30651 L246.24 1.1 Q256.24 0 256.24 10 L256.24 267.5\"\n fill=\"none\"\n stroke=\"url(#profile-right-stroke)\"\n stroke-width=\"2\"\n stroke-miterlimit=\"3.99393\"\n stroke-linejoin=\"round\"\n shape-rendering=\"geometricPrecision\"\n />\n <g\n style=\"cursor: pointer !important;\"\n (click)=\"onViewProfile('right'); $event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\"\n >\n <rect [attr.x]=\"viewProfileRightX - 100\" y=\"238\" width=\"115\" height=\"40\" fill=\"transparent\" pointer-events=\"auto\" />\n <text\n [attr.x]=\"viewProfileRightX\" y=\"253\"\n fill=\"rgba(255, 255, 255, 0.60)\"\n font-family=\"'Calistoga', serif\"\n font-size=\"9\"\n text-anchor=\"end\"\n [class.is-animating-bob]=\"rightProfileClicked\"\n style=\"text-shadow: 0 2px 4.2px #000;\"\n pointer-events=\"auto\"\n >View Profile</text>\n </g>\n </svg>\n <div class=\"profile-comparison__content profile-comparison__content--right\">\n <div class=\"profile-comparison__list profile-comparison__list--right\">\n <!-- Semantic Items only -->\n <ng-container *ngFor=\"let interest of alignedPerson2Interests; let i = index\">\n <div class=\"profile-comparison__item profile-comparison__item--right\">\n <lib-marquee\n class=\"profile-comparison__text\"\n [title]=\"interest\"\n [onlyMarqueeOnHover]=\"false\"\n style=\"--marquee-font-size: 15px; --marquee-font-family: 'Gilroy', sans-serif; --marquee-font-color: white; --marquee-font-weight: 100; --marquee-animation-duration: 8s;\"\n ></lib-marquee>\n\n <!-- Dash beneath each interest -->\n <div class=\"profile-comparison__dash-container\" [ngSwitch]=\"getDashType(i)\">\n <!-- High Similarity: Short single dash, 0.5 opacity -->\n <svg *ngSwitchCase=\"'high'\" class=\"profile-comparison__dash profile-comparison__dash--high\" width=\"15\" height=\"4\" viewBox=\"0 0 15 4\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M2 2H13\" stroke=\"white\" stroke-width=\"4\" stroke-opacity=\"0.5\" stroke-linecap=\"round\"/>\n </svg>\n <!-- Medium Similarity: Standard 1 dash -->\n <svg *ngSwitchCase=\"'medium'\" class=\"profile-comparison__dash profile-comparison__dash--medium\" width=\"30\" height=\"4\" viewBox=\"0 0 30 4\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M2 2H28\" stroke=\"white\" stroke-width=\"4\" stroke-linecap=\"round\"/>\n </svg>\n <!-- Low Similarity: 4 small dashes -->\n <svg *ngSwitchCase=\"'low'\" class=\"profile-comparison__dash profile-comparison__dash--low\" width=\"30\" height=\"4\" viewBox=\"0 0 30 4\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M2 2H28\" stroke=\"white\" stroke-width=\"4\" stroke-dasharray=\"2,5\" stroke-linecap=\"round\"/>\n </svg>\n </div>\n </div>\n </ng-container>\n </div>\n </div>\n </div>\n\n <!-- Center text section -->\n <div class=\"profile-comparison__center\" #shapeTextCenter>\n <div class=\"profile-comparison__center-inner\">\n <!-- Exact Items only -->\n <div *ngFor=\"let item of centerItem\" class=\"profile-comparison__center-item\">\n <lib-marquee\n class=\"profile-comparison__center-text\"\n [title]=\"item\"\n [onlyMarqueeOnHover]=\"false\"\n style=\"--marquee-font-size: 15px; --marquee-font-family: 'Gilroy', sans-serif; --marquee-font-color: white; --marquee-font-weight: 700; --marquee-animation-duration: 8s;\"\n ></lib-marquee>\n <!-- Invisible dash as requested -->\n <div class=\"profile-comparison__center-dash-placeholder\"></div>\n </div>\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: ["@keyframes textDropIn{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}@keyframes nudgeLeft{0%,to{transform:translate(-24%) scale(.86)}50%{transform:translate(-26%) scale(.86)}}@keyframes nudgeRight{0%,to{transform:translate(24%) scale(.86)}50%{transform:translate(26%) scale(.86)}}.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 0%,black 10%,black 90%,transparent 100%),linear-gradient(to right,transparent,black 15%,black 85%,transparent);-webkit-mask-composite:source-in;mask-image:linear-gradient(to bottom,transparent 0%,black 10%,black 90%,transparent 100%),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 0%,black 10%,black 90%,transparent 100%),linear-gradient(to right,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%);mask-image:linear-gradient(to bottom,transparent 0%,black 10%,black 90%,transparent 100%),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 0%,black 10%,black 90%,transparent 100%),linear-gradient(to left,transparent 0%,black 15%,black calc(100% - var(--overlap-size, 40px)),transparent 100%);mask-image:linear-gradient(to bottom,transparent 0%,black 10%,black 90%,transparent 100%),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}.profile-screen{width:100%;max-width:460px;margin:0 auto;overflow:visible;position:relative}.profile-flex{display:flex;width:100%;height:auto;align-items:center;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-bg{position:relative;pointer-events:auto;width:100%;height:100%}.shape-bg1,.shape-bg2{position:absolute;width:75%;max-width:none;height:350px;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;overflow:visible!important}.shape-bg svg:active{cursor:grabbing}.profile-img{width:50%;height:350px;position:relative;flex-shrink:0;background:linear-gradient(135deg,#1a1a1a,#0a0a0a);margin:0;padding:0;left:0;top:0;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 30%;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:32px;width:100%;left:0;z-index:2;pointer-events:none}.shape-bg{position:relative;pointer-events:auto}.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:0 .5px 1.5px #000;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:0 .5px 1.5px #000;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:0 .5px 1.5px #000}.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:0 .5px 1.5px #000;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:0 .5px 1.5px #000;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:0 .5px 1.5px #000;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}:host{display:block}.profile-comparison{width:100%;padding:0;box-sizing:border-box;display:flex;justify-content:center;align-items:center;overflow:visible}.profile-comparison__inner{position:relative;width:100%;min-width:25.75rem;max-width:100%;min-height:500px;display:grid;grid-template-columns:1fr;grid-template-rows:auto;background:transparent;overflow:visible}.profile-comparison__bg-layer{grid-area:1/1;position:relative;width:100%;height:100%;min-height:inherit;pointer-events:none;display:grid;grid-template-columns:1fr;grid-template-rows:1fr}.profile-comparison__frame{grid-area:1/1;position:relative;pointer-events:none}.profile-comparison__frame.bg-frame{width:100%;height:100%;min-height:inherit;overflow:hidden;border-radius:10px;-webkit-mask-composite:source-in;mask-composite:intersect;will-change:auto}.profile-comparison__frame.bg-frame.profile-comparison__frame--left{-webkit-mask-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 278'%3E%3Cpath d='M246.177 26.4924 L10 1.07 Q0 0 0 10 V268 Q0 278 9.8 276.5 L246.68 239.869 C252.043 239.04 256 234.407 256 228.981 V37.4497 C256 31.8302 251.764 27.0937 246.177 26.4924 Z' fill='white'/%3E%3C/svg%3E\"),linear-gradient(to top,transparent 0%,black 15px,black 100%);mask-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 278'%3E%3Cpath d='M246.177 26.4924 L10 1.07 Q0 0 0 10 V268 Q0 278 9.8 276.5 L246.68 239.869 C252.043 239.04 256 234.407 256 228.981 V37.4497 C256 31.8302 251.764 27.0937 246.177 26.4924 Z' fill='white'/%3E%3C/svg%3E\"),linear-gradient(to top,transparent 0%,black 15px,black 100%);-webkit-mask-size:100% 100%,100% 100%;mask-size:100% 100%,100% 100%;-webkit-mask-composite:source-in;mask-composite:intersect}.fade-all-edges .profile-comparison__frame.bg-frame.profile-comparison__frame--left.edge-at-left-border{-webkit-mask-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 278'%3E%3Cpath d='M246.177 26.4924 L10 1.07 Q0 0 0 10 V268 Q0 278 9.8 276.5 L246.68 239.869 C252.043 239.04 256 234.407 256 228.981 V37.4497 C256 31.8302 251.764 27.0937 246.177 26.4924 Z' fill='white'/%3E%3C/svg%3E\"),linear-gradient(to top,transparent 0%,black 15px,black 100%),linear-gradient(to right,transparent 0%,black 20px,black 100%);mask-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 278'%3E%3Cpath d='M246.177 26.4924 L10 1.07 Q0 0 0 10 V268 Q0 278 9.8 276.5 L246.68 239.869 C252.043 239.04 256 234.407 256 228.981 V37.4497 C256 31.8302 251.764 27.0937 246.177 26.4924 Z' fill='white'/%3E%3C/svg%3E\"),linear-gradient(to top,transparent 0%,black 15px,black 100%),linear-gradient(to right,transparent 0%,black 20px,black 100%);-webkit-mask-size:100% 100%,100% 100%,100% 100%;mask-size:100% 100%,100% 100%,100% 100%;-webkit-mask-composite:source-in;mask-composite:intersect}.profile-comparison__frame.bg-frame.profile-comparison__frame--right{-webkit-mask-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 257 278'%3E%3Cpath d='M9.8262 26.4454 L244.066 1.30651 L246.24 1.1 Q256.24 0 256.24 10 V267.5 Q256.24 277.5 246.24 275.9 L243.564 275.546 L9.32411 239.437 C3.95944 238.61 0 233.976 0 228.548 V37.4032 C0 31.7824 4.2375 27.0452 9.8262 26.4454 Z' fill='white'/%3E%3C/svg%3E\"),linear-gradient(to top,transparent 0%,black 15px,black 100%);mask-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 257 278'%3E%3Cpath d='M9.8262 26.4454 L244.066 1.30651 L246.24 1.1 Q256.24 0 256.24 10 V267.5 Q256.24 277.5 246.24 275.9 L243.564 275.546 L9.32411 239.437 C3.95944 238.61 0 233.976 0 228.548 V37.4032 C0 31.7824 4.2375 27.0452 9.8262 26.4454 Z' fill='white'/%3E%3C/svg%3E\"),linear-gradient(to top,transparent 0%,black 15px,black 100%);-webkit-mask-size:100% 100%,100% 100%;mask-size:100% 100%,100% 100%;-webkit-mask-composite:source-in;mask-composite:intersect}.fade-all-edges .profile-comparison__frame.bg-frame.profile-comparison__frame--right.edge-at-right-border{-webkit-mask-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 257 278'%3E%3Cpath d='M9.8262 26.4454 L244.066 1.30651 L246.24 1.1 Q256.24 0 256.24 10 V267.5 Q256.24 277.5 246.24 275.9 L243.564 275.546 L9.32411 239.437 C3.95944 238.61 0 233.976 0 228.548 V37.4032 C0 31.7824 4.2375 27.0452 9.8262 26.4454 Z' fill='white'/%3E%3C/svg%3E\"),linear-gradient(to top,transparent 0%,black 15px,black 100%),linear-gradient(to left,transparent 0%,black 20px,black 100%);mask-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 257 278'%3E%3Cpath d='M9.8262 26.4454 L244.066 1.30651 L246.24 1.1 Q256.24 0 256.24 10 V267.5 Q256.24 277.5 246.24 275.9 L243.564 275.546 L9.32411 239.437 C3.95944 238.61 0 233.976 0 228.548 V37.4032 C0 31.7824 4.2375 27.0452 9.8262 26.4454 Z' fill='white'/%3E%3C/svg%3E\"),linear-gradient(to top,transparent 0%,black 15px,black 100%),linear-gradient(to left,transparent 0%,black 20px,black 100%);-webkit-mask-size:100% 100%,100% 100%,100% 100%;mask-size:100% 100%,100% 100%,100% 100%;-webkit-mask-composite:source-in;mask-composite:intersect}.profile-comparison__frame--left{transform:translate(-24%) scale(.86)}.profile-comparison__frame--left.is-animating-nudge{animation:nudgeLeft .5s ease-out}.profile-comparison__frame--left .profile-comparison__frame-svg{-webkit-mask-image:var(--edge-mask-left, linear-gradient(to right, transparent 0%, rgba(0, 0, 0, .4) 14px, black var(--edge-shading-width, 28px)));mask-image:var(--edge-mask-left, linear-gradient(to right, transparent 0%, rgba(0, 0, 0, .4) 14px, black var(--edge-shading-width, 28px)))}.profile-comparison__frame--left .profile-comparison__frame-svg--glass{transform:scaleX(1.04) translateY(-4px);transform-origin:center;-webkit-mask-image:none;mask-image:none}.profile-comparison__frame--left .profile-comparison__frame-svg--left,.profile-comparison__frame--left .profile-comparison__frame-svg--right{-webkit-mask-image:none;mask-image:none}.profile-comparison__frame--right{transform:translate(24%) scale(.86)}.profile-comparison__frame--right.is-animating-nudge{animation:nudgeRight .5s ease-out}.profile-comparison__frame--right .profile-comparison__frame-svg{-webkit-mask-image:var(--edge-mask-right, linear-gradient(to left, transparent 0%, rgba(0, 0, 0, .4) 14px, black var(--edge-shading-width, 28px)));mask-image:var(--edge-mask-right, linear-gradient(to left, transparent, black var(--edge-shading-width, 28px)))}.profile-comparison__frame--right .profile-comparison__frame-svg--glass{transform:scaleX(1.04) translateY(-4px);transform-origin:center;-webkit-mask-image:none;mask-image:none}.profile-comparison__frame--right .profile-comparison__frame-svg--left,.profile-comparison__frame--right .profile-comparison__frame-svg--right{-webkit-mask-image:none;mask-image:none}.profile-comparison__frame--right .profile-comparison__frame-svg--bottom{width:113.2%}.profile-comparison__frame-svg{position:absolute;inset:0;width:100%;height:100%;pointer-events:auto;overflow:visible}.profile-comparison__frame-svg--glass{z-index:1}.profile-comparison__frame-svg--middle{z-index:2;mix-blend-mode:screen}.profile-comparison__frame-svg--left,.profile-comparison__frame-svg--right{z-index:3}.profile-comparison__frame-svg--bottom{width:113.7%;height:112.6%;inset:51% auto auto 50%;transform:translate(-50%,-51%)}.profile-comparison__bg{position:absolute;top:0;left:0;right:0;height:130%;background-size:cover;background-repeat:no-repeat;background-position:center 30%;pointer-events:auto;opacity:.8;transition:filter .6s ease,opacity .6s ease}.profile-comparison__bg:after{content:\"\";position:absolute;inset:0;background:#000000bf;pointer-events:none}.profile-comparison__bg--left{background-position:right center;margin-right:0%;-webkit-mask-image:linear-gradient(to right,black 50%,transparent 100%);mask-image:linear-gradient(to right,black 50%,transparent 100%);-webkit-mask-composite:source-in;mask-composite:intersect}.profile-comparison__bg--right{background-position:left center;margin-left:0%;-webkit-mask-image:linear-gradient(to left,black 50%,transparent 100%);mask-image:linear-gradient(to left,black 50%,transparent 100%);-webkit-mask-composite:source-in;mask-composite:intersect}@keyframes viewProfileBob{0%{transform:translateY(0)}50%{transform:translateY(-3px)}to{transform:translateY(0)}}.profile-comparison svg text{transition:opacity .3s ease}.profile-comparison svg text.is-animating-bob{animation:viewProfileBob .6s ease-in-out;fill:#fff!important}.profile-comparison__content{position:relative;z-index:20;padding:50px 12% 75px;min-height:100%;display:flex;flex-direction:column;justify-content:flex-start;color:#ecf0f1;font-family:Gilroy,serif;pointer-events:none}.profile-comparison__content--left{align-items:flex-end;text-align:right;padding-right:31%}.profile-comparison__content--right{align-items:flex-start;text-align:left;padding-left:31%}.profile-comparison__list{display:flex;flex-direction:column;gap:2px;width:100%;pointer-events:auto}.profile-comparison__list--left{align-items:flex-end;text-align:right;padding-right:31%}.profile-comparison__list--right{align-items:flex-start;text-align:left;padding-left:31%}.profile-comparison__item{display:flex;flex-direction:column;height:auto;width:11rem;flex-shrink:0;gap:6px;margin-bottom:6px;animation:textDropIn .4s ease-out backwards}.profile-comparison__item--left{align-items:flex-end;text-align:right}.profile-comparison__item--right{align-items:flex-start;text-align:left}.profile-comparison__text{font-family:Gilroy,sans-serif;font-size:15px;font-weight:100!important;color:#fff;width:100%;display:block;text-shadow:0 .5px 1.5px #000}.profile-comparison__text ::ng-deep *{font-weight:400!important;text-shadow:0 .5px 1.5px #000}.profile-comparison__dash-container{display:flex;width:100%;height:8px;align-items:center}.profile-comparison__item--left .profile-comparison__dash-container{justify-content:flex-end}.profile-comparison__item--right .profile-comparison__dash-container{justify-content:flex-start}.profile-comparison__dash{display:block}.profile-comparison__center-empty,.profile-comparison__center-dash-placeholder{height:8px;width:100%}.profile-comparison__center{grid-area:1/1;display:flex;align-items:flex-start;justify-content:center;z-index:15;pointer-events:none;transform:scale(.86)}.profile-comparison__center-inner{display:flex;flex-direction:column;align-items:center;text-align:center;justify-content:flex-start;padding:50px 0 75px;gap:6px;width:100%}.profile-comparison__center-item{height:auto;min-height:22px;display:flex;flex-direction:column;align-items:center;justify-content:flex-start;width:9rem;flex-shrink:0;gap:6px;margin-bottom:6px;animation:textDropIn .4s ease-out backwards}.profile-comparison__center-text{font-family:Gilroy,sans-serif;font-size:15px;font-weight:700;color:#fff;width:100%;display:block;text-shadow:0 .5px 1.5px #000}.profile-comparison__center-text--empty{color:#ecf0f180}.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"] }]
2554
+ 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 #profileFlex class=\"profile-flex\" [class.fade-all]=\"fadeAllEdges\">\n <svg class=\"image-noise-overlay\" style=\"position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; overflow: hidden; z-index: 0;\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 320\" fill=\"none\" preserveAspectRatio=\"none\">\n <g filter=\"url(#filter0_n_1_1753)\">\n <rect width=\"100%\" height=\"100%\" fill=\"url(#paint0_radial_1_1753)\" fill-opacity=\"0.33\"/>\n </g>\n <defs>\n <filter id=\"filter0_n_1_1753\" x=\"0%\" y=\"0%\" width=\"100%\" height=\"100%\" filterUnits=\"objectBoundingBox\" color-interpolation-filters=\"sRGB\">\n <feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\n <feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"BackgroundImageFix\" result=\"shape\"/>\n <feTurbulence type=\"fractalNoise\" baseFrequency=\"0.10869565606117249 0.10869565606117249\" stitchTiles=\"stitch\" numOctaves=\"3\" result=\"noise\" seed=\"7339\"/>\n <feColorMatrix in=\"noise\" type=\"luminanceToAlpha\" result=\"alphaNoise\"/>\n <feComponentTransfer in=\"alphaNoise\" result=\"coloredNoise1\">\n <feFuncA type=\"discrete\" tableValues=\"0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \"/>\n </feComponentTransfer>\n <feComposite operator=\"in\" in2=\"shape\" in=\"coloredNoise1\" result=\"noise1Clipped\"/>\n <feFlood flood-color=\"rgba(0, 0, 0, 0.25)\" result=\"color1Flood\"/>\n <feComposite operator=\"in\" in2=\"noise1Clipped\" in=\"color1Flood\" result=\"color1\"/>\n <feMerge result=\"effect1_noise_1_1753\">\n <feMergeNode in=\"shape\"/>\n <feMergeNode in=\"color1\"/>\n </feMerge>\n </filter>\n <radialGradient id=\"paint0_radial_1_1753\" cx=\"50%\" cy=\"50%\" r=\"50%\" fx=\"50%\" fy=\"50%\">\n <stop offset=\"0%\" stop-opacity=\"0\"/>\n <stop offset=\"100%\" stop-opacity=\"0.25\"/>\n </radialGradient>\n </defs>\n </svg>\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.top]=\"user1Top\"\n [style.height]=\"user1Height\"\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.top]=\"user2Top\"\n [style.height]=\"user2Height\"\n [style.object-position]=\"user2ObjectPosition\"\n (load)=\"onUserImageLoad(2, $event)\"\n />\n </div>\n <div class=\"image-shade-overlay\"></div>\n\n <!-- Frosted Glass Overlay Layer -->\n <div class=\"overlay-container\">\n <!-- Top Headers -->\n <div class=\"header-label left\">{{ person1Name }}</div>\n <div class=\"header-label center\">Shared</div>\n <div class=\"header-label right\">{{ person2Name }}</div>\n\n <!-- Chips Columns Grid -->\n <div class=\"chips-grid\">\n <!-- Person 1 Unique Interests -->\n <div class=\"chips-column left\">\n <ng-container *ngFor=\"let interest of getPerson1UniqueInterests(); let i = index\">\n <div class=\"frosted-chip person-a\" [style.margin-right.px]=\"getChipOffset(i, 'left')\" [style.margin-top.px]=\"(i > 0 ? 4 : 0)\">\n <span class=\"chip-label\">{{ interest }}</span>\n </div>\n </ng-container>\n </div>\n\n <!-- Shared Interests (Center Column) -->\n <div class=\"chips-column center\">\n <ng-container *ngFor=\"let interest of centerItem; let i = index\">\n <div *ngIf=\"interest !== 'empty'\" class=\"frosted-chip shared\" [style.margin-left.px]=\"getChipOffset(i, 'center')\" [style.margin-top.px]=\"(i > 0 ? 4 : 0)\">\n <span class=\"chip-label\">{{ interest }}</span>\n </div>\n <div *ngIf=\"interest === 'empty'\" class=\"frosted-chip ghost-chip shared\" [style.margin-left.px]=\"getChipOffset(i, 'center')\" [style.margin-top.px]=\"(i > 0 ? 4 : 0)\">\n <span class=\"chip-label\">&nbsp;</span>\n </div>\n </ng-container>\n </div>\n\n <!-- Person 2 Unique Interests -->\n <div class=\"chips-column right\">\n <ng-container *ngFor=\"let interest of getPerson2UniqueInterests(); let i = index\">\n <div class=\"frosted-chip person-b\" [style.margin-left.px]=\"getChipOffset(i, 'right')\" [style.margin-top.px]=\"(i > 0 ? 4 : 0)\">\n <span class=\"chip-label\">{{ interest }}</span>\n </div>\n </ng-container>\n </div>\n </div>\n\n <!-- Bottom Actions -->\n <div class=\"view-profile left\" (click)=\"onViewProfile('left')\">View Profile</div>\n <div class=\"view-profile right\" (click)=\"onViewProfile('right')\">View Profile</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: [":host{display:block;width:100%;max-width:100%;overflow-x:hidden!important;box-sizing:border-box!important}.profile-img.left{position:absolute;top:0;bottom:0;left:0;width:58%;height:100%;margin:0;z-index:1;-webkit-mask-image:linear-gradient(to right,black 0%,black 45%,transparent 92%);mask-image:linear-gradient(to right,black 0%,black 45%,transparent 92%)}.profile-img.right{position:absolute;inset:0 0 0 auto;width:58%;height:100%;margin:0;z-index:2;-webkit-mask-image:linear-gradient(to right,transparent 8%,black 55%,black 100%);mask-image:linear-gradient(to right,transparent 8%,black 55%,black 100%)}@media(min-width:768px){.profile-img.left{width:65%;-webkit-mask-image:linear-gradient(to right,black 0%,black 40%,transparent 90%);mask-image:linear-gradient(to right,black 0%,black 40%,transparent 90%)}.profile-img.right{width:65%;-webkit-mask-image:linear-gradient(to right,transparent 10%,black 60%,black 100%);mask-image:linear-gradient(to right,transparent 10%,black 60%,black 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:100%;max-width:768px;margin:0 auto;overflow:hidden;position:relative;border-radius:12px}.profile-flex{display:flex;width:100%;height:440px;overflow:hidden;background:transparent;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 18%,black 82%,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 18%,black 82%,transparent 100%),linear-gradient(to bottom,transparent 0%,black 15%,black 85%,transparent 100%);mask-composite:intersect}.profile-flex .image-shade-overlay{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:3;background:radial-gradient(ellipse at center,transparent 40%,rgba(41,37,45,.25) 100%)}.profile-flex .image-noise-overlay{position:absolute;inset:0;width:100%;height:100%;max-width:100%;max-height:100%;pointer-events:none;overflow:hidden;z-index:0}.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{height:100%;position:relative;flex-shrink:0;background:transparent;margin:0;padding:0;left:0;top:0;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:1;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}.overlay-container{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:10;-webkit-user-select:none;user-select:none;overflow:hidden;background:#29252da6;backdrop-filter:blur(.5px);-webkit-backdrop-filter:blur(.5px)}.header-label{position:absolute;top:20px;font-family:Gilroy,sans-serif;text-shadow:0 1px 8px rgba(0,0,0,.8);pointer-events:none}.header-label.left{left:5%;transform:none;font-size:14px;font-weight:400;color:#fffffff2}.header-label.center{left:50%;transform:translate(-50%);font-size:12px;font-weight:400;color:#fffc;letter-spacing:0}.header-label.right{right:5%;left:auto;transform:none;font-size:14px;font-weight:400;color:#fffffff2}.chips-grid{position:absolute;inset:45px 0;display:flex;padding:0;overflow-y:auto;overscroll-behavior:none;scrollbar-width:none;box-sizing:border-box}.chips-grid::-webkit-scrollbar{display:none}.chips-column{flex:1;display:flex;flex-direction:column;gap:8px;padding:4px 10px;box-sizing:border-box}.chips-column.left{align-items:flex-end}.chips-column.center{align-items:center}.chips-column.right{align-items:flex-start}.frosted-chip{position:relative;cursor:pointer;pointer-events:auto;transition:transform .2s ease,box-shadow .2s ease,background .2s ease;flex-shrink:0;padding:6px 18px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;border:none;background:#ffffff30;box-shadow:inset 0 1.5px 2px #ffffff59;backdrop-filter:blur(.5px);-webkit-backdrop-filter:blur(.5px)}.frosted-chip:hover{transform:scale(1.04);background:#ffffff29}.frosted-chip .chip-label{position:relative;color:#a8a8a8;text-align:center;font-family:Gilroy,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:140%;letter-spacing:-.01em;text-shadow:none;white-space:nowrap;pointer-events:none;z-index:1}.ghost-chip{visibility:hidden!important;pointer-events:none!important}.view-profile{position:absolute;bottom:20px!important;color:#ffffff80;text-shadow:0 2px 4.2px #000;font-family:Calistoga,serif;font-size:18px;font-style:normal;font-weight:400;line-height:normal;cursor:pointer;pointer-events:auto;transition:opacity .2s ease,transform .2s ease}.view-profile:hover{opacity:.85}.view-profile.left{left:5%;transform:none}.view-profile.left:hover{transform:translateY(-2px)}.view-profile.right{right:5%;left:auto;transform:none}.view-profile.right:hover{transform:translateY(-2px)}#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:absolute;inset:0;width:100%;height:100%;background:#00000040;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);color:#fff;z-index:100;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;pointer-events:auto}.loading-spinner{width:44px;height:44px;border:3.5px solid rgba(255,255,255,.25);border-top:3.5px solid #ffffff;border-radius:50%;animation:spin .8s linear infinite;box-shadow:0 0 15px #fff3}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.loading-indicator p{margin:0;font-size:14px;font-weight:500;color:#ffffffd9;letter-spacing:.02em}.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}@media(max-width:1023px){.profile-screen{max-width:640px}.profile-flex{height:400px}.profile-img{height:550px}}@media(max-width:767px){.profile-screen,.profile-flex,.overlay-container{width:100%;max-width:100%;overflow-x:hidden;box-sizing:border-box}.profile-screen{border-radius:0}.profile-flex{height:360px}.profile-img{height:510px}.chips-grid{width:100%;max-width:100%;padding:0 4px;top:50px;bottom:50px;box-sizing:border-box}.chips-column{padding:2px}.chips-column.left{align-items:flex-end}.chips-column.center{align-items:center}.chips-column.right{align-items:flex-start}.frosted-chip{padding:4px 10px;max-width:100%;box-sizing:border-box}.frosted-chip .chip-label{font-size:11px;max-width:100%;overflow:hidden;text-overflow:ellipsis}.view-profile{bottom:14px}}@media(max-width:480px){.profile-flex{height:340px}.profile-img{height:480px}.frosted-chip{padding:3px 8px}.frosted-chip .chip-label{font-size:10px}.header-label{top:14px}.header-label.left{font-size:12px}.header-label.center{font-size:10px}.header-label.right{font-size:12px}}\n"] }]
2550
2555
  }], ctorParameters: () => [{ type: ProfileComparisonBackendService }, { type: i0.Renderer2 }, { type: FileConversionService }, { type: ImageCompressionService }, { type: i0.ChangeDetectorRef }, { type: undefined, decorators: [{
2551
2556
  type: Optional
2552
2557
  }, {
@@ -2554,65 +2559,80 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
2554
2559
  args: [PROFILE_COMPARISON_VERBOSE_LOGGING]
2555
2560
  }] }], propDecorators: { config: [{
2556
2561
  type: Input
2562
+ }], user1File: [{
2563
+ type: Input
2564
+ }], user2File: [{
2565
+ type: Input
2557
2566
  }], backendMode: [{
2558
2567
  type: Input
2559
2568
  }], backendUrl: [{
2560
2569
  type: Input
2561
2570
  }], fadeAllEdges: [{
2562
2571
  type: Input
2572
+ }], person1Name: [{
2573
+ type: Input
2574
+ }], person2Name: [{
2575
+ type: Input
2576
+ }], imageProxyPath: [{
2577
+ type: Input
2563
2578
  }], matrixDataChange: [{
2564
2579
  type: Output
2565
2580
  }], rawLLMOutputChange: [{
2566
2581
  type: Output
2567
2582
  }], viewProfileClick: [{
2568
2583
  type: Output
2569
- }], edgeDrag: [{
2570
- type: Output
2571
- }], onResize: [{
2572
- type: HostListener,
2573
- args: ['window:resize']
2574
2584
  }], leftContainer: [{
2575
2585
  type: ViewChild,
2576
2586
  args: ['leftContainer']
2577
2587
  }], rightContainer: [{
2578
2588
  type: ViewChild,
2579
2589
  args: ['rightContainer']
2580
- }], leftFrame: [{
2581
- type: ViewChild,
2582
- args: ['leftFrame']
2583
- }], rightFrame: [{
2590
+ }], profileFlex: [{
2584
2591
  type: ViewChild,
2585
- args: ['rightFrame']
2586
- }], leftBgFrame: [{
2592
+ args: ['profileFlex']
2593
+ }], profileImgLeft: [{
2587
2594
  type: ViewChild,
2588
- args: ['leftBgFrame']
2589
- }], rightBgFrame: [{
2595
+ args: ['profileImgLeft']
2596
+ }], profileImgRight: [{
2590
2597
  type: ViewChild,
2591
- args: ['rightBgFrame']
2598
+ args: ['profileImgRight']
2592
2599
  }], shapeContainer: [{
2593
2600
  type: ViewChild,
2594
2601
  args: ['shapeContainer']
2602
+ }], shapeBg: [{
2603
+ type: ViewChild,
2604
+ args: ['shapeBg']
2605
+ }], shapeBg1: [{
2606
+ type: ViewChild,
2607
+ args: ['shapeBg1']
2608
+ }], shapeBg2: [{
2609
+ type: ViewChild,
2610
+ args: ['shapeBg2']
2611
+ }], shapeTextLeft: [{
2612
+ type: ViewChild,
2613
+ args: ['shapeTextLeft']
2614
+ }], shapeTextRight: [{
2615
+ type: ViewChild,
2616
+ args: ['shapeTextRight']
2595
2617
  }], shapeTextCenter: [{
2596
2618
  type: ViewChild,
2597
2619
  args: ['shapeTextCenter']
2598
2620
  }] } });
2599
2621
 
2600
2622
  class ProfileComparisonLibModule {
2601
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ProfileComparisonLibModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
2602
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.19", ngImport: i0, type: ProfileComparisonLibModule, declarations: [ProfileComparisonLibComponent], imports: [CommonModule,
2623
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ProfileComparisonLibModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
2624
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.25", ngImport: i0, type: ProfileComparisonLibModule, declarations: [ProfileComparisonLibComponent], imports: [CommonModule,
2603
2625
  HttpClientModule,
2604
2626
  FormsModule,
2605
- ReactiveFormsModule,
2606
- LibMarqueeModule], exports: [ProfileComparisonLibComponent] });
2607
- static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ProfileComparisonLibModule, providers: [
2627
+ ReactiveFormsModule], exports: [ProfileComparisonLibComponent] });
2628
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ProfileComparisonLibModule, providers: [
2608
2629
  ProfileComparisonBackendService
2609
2630
  ], imports: [CommonModule,
2610
2631
  HttpClientModule,
2611
2632
  FormsModule,
2612
- ReactiveFormsModule,
2613
- LibMarqueeModule] });
2633
+ ReactiveFormsModule] });
2614
2634
  }
2615
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ProfileComparisonLibModule, decorators: [{
2635
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: ProfileComparisonLibModule, decorators: [{
2616
2636
  type: NgModule,
2617
2637
  args: [{
2618
2638
  declarations: [
@@ -2622,8 +2642,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImpo
2622
2642
  CommonModule,
2623
2643
  HttpClientModule,
2624
2644
  FormsModule,
2625
- ReactiveFormsModule,
2626
- LibMarqueeModule
2645
+ ReactiveFormsModule
2627
2646
  ],
2628
2647
  exports: [
2629
2648
  ProfileComparisonLibComponent