@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,
|
|
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,
|
|
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
|
|
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.
|
|
208
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
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.
|
|
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.
|
|
257
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
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.
|
|
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.
|
|
376
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
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.
|
|
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.
|
|
1158
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
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.
|
|
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.
|
|
1193
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
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.
|
|
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.
|
|
1450
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
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.
|
|
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.
|
|
1509
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
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.
|
|
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
|
|
1545
|
-
static
|
|
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
|
-
|
|
1582
|
-
|
|
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
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
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.
|
|
1715
|
-
this.
|
|
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', '
|
|
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', '
|
|
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
|
|
1753
|
-
this.
|
|
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
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
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
|
-
|
|
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.
|
|
1972
|
+
this.user1ObjectPosition = ProfileComparisonLibComponent.DEFAULT_OBJECT_POSITION;
|
|
1922
1973
|
else
|
|
1923
|
-
this.user2ObjectPosition = ProfileComparisonLibComponent.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2037
|
-
const
|
|
2038
|
-
if (!
|
|
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
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
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
|
|
2077
|
-
const
|
|
2078
|
-
const
|
|
2079
|
-
const
|
|
2080
|
-
|
|
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
|
-
//
|
|
2084
|
-
const
|
|
2085
|
-
const
|
|
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
|
|
2089
|
-
let
|
|
2090
|
-
|
|
2091
|
-
|
|
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 =
|
|
2097
|
-
|
|
2098
|
-
let
|
|
2099
|
-
if (
|
|
2100
|
-
|
|
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
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
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
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
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
|
|
2183
|
+
const resetWidths = () => {
|
|
2222
2184
|
isDragging = false;
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
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',
|
|
2285
|
-
document.removeEventListener('mouseleave',
|
|
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
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
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
|
-
|
|
2313
|
-
|
|
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
|
-
|
|
2322
|
-
activeFrame = frame; // Track which frame is active
|
|
2217
|
+
activeImg = img;
|
|
2323
2218
|
dragStartX = e.clientX;
|
|
2324
|
-
|
|
2325
|
-
|
|
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',
|
|
2347
|
-
document.addEventListener('mouseleave',
|
|
2222
|
+
document.addEventListener('mouseup', resetWidths);
|
|
2223
|
+
document.addEventListener('mouseleave', resetWidths);
|
|
2348
2224
|
});
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
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
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
const
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
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
|
-
|
|
2396
|
-
|
|
2397
|
-
this.
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
this.
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
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
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
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.
|
|
2507
|
-
const rightContainerEl = this.
|
|
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
|
|
2531
|
-
const tCenter2 = this.user2NaturalHeight ? -((anchor2Y - this.user2NaturalHeight / 2) * scale2
|
|
2532
|
-
|
|
2533
|
-
|
|
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.
|
|
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\"> </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.
|
|
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\"> </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
|
-
}],
|
|
2581
|
-
type: ViewChild,
|
|
2582
|
-
args: ['leftFrame']
|
|
2583
|
-
}], rightFrame: [{
|
|
2590
|
+
}], profileFlex: [{
|
|
2584
2591
|
type: ViewChild,
|
|
2585
|
-
args: ['
|
|
2586
|
-
}],
|
|
2592
|
+
args: ['profileFlex']
|
|
2593
|
+
}], profileImgLeft: [{
|
|
2587
2594
|
type: ViewChild,
|
|
2588
|
-
args: ['
|
|
2589
|
-
}],
|
|
2595
|
+
args: ['profileImgLeft']
|
|
2596
|
+
}], profileImgRight: [{
|
|
2590
2597
|
type: ViewChild,
|
|
2591
|
-
args: ['
|
|
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.
|
|
2602
|
-
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.
|
|
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
|
-
|
|
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.
|
|
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
|