@echomem/mcp 1.4.27 → 1.4.28

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.
@@ -169,8 +169,111 @@ export const SETUP_PAGE_CLIENT_REPORT_CITY = String.raw ` /* ---------- ful
169
169
  setScanMode(false);
170
170
  setCityMode(false);
171
171
  }
172
+ function formatReportEta(seconds) {
173
+ seconds = Math.max(1, Math.round(seconds));
174
+ if (seconds >= 3600) {
175
+ var hours = Math.floor(seconds / 3600);
176
+ var minutesAfterHours = Math.ceil((seconds % 3600) / 60);
177
+ return "About " + hours + "h" + (minutesAfterHours ? " " + minutesAfterHours + "m" : "") + " remaining";
178
+ }
179
+ if (seconds >= 60) {
180
+ var minutes = Math.floor(seconds / 60);
181
+ var secondsAfterMinutes = seconds % 60;
182
+ return "About " + minutes + "m " + secondsAfterMinutes + "s remaining";
183
+ }
184
+ return "About " + seconds + "s remaining";
185
+ }
186
+ function estimateReportRemaining(progress, overall, stage) {
187
+ void overall;
188
+ var now = Date.now();
189
+ var stageElapsed = Math.max(0, Number(progress.stageElapsedMs || 0) / 1000);
190
+ var stageDone = Math.max(0, Number(progress.stageDone || 0));
191
+ var stageTotal = Math.max(0, Number(progress.stageTotal || 0));
192
+ function tickExistingEstimate() {
193
+ if (reportEtaDisplaySeconds <= 0) {
194
+ return { seconds: 0, overdue: false, calibrating: true };
195
+ }
196
+ var sinceLast = reportEtaUpdatedAt ? Math.max(0, (now - reportEtaUpdatedAt) / 1000) : 0;
197
+ reportEtaDisplaySeconds = Math.max(0, reportEtaDisplaySeconds - sinceLast);
198
+ reportEtaUpdatedAt = now;
199
+ return {
200
+ seconds: reportEtaDisplaySeconds,
201
+ overdue: now >= reportEtaDeadline || reportEtaDisplaySeconds <= 0,
202
+ calibrating: false,
203
+ };
204
+ }
205
+
206
+ // The visual progress bands describe work completed, not wall-clock cost. In particular,
207
+ // reaching the 75% classification band does NOT mean only 25% of the time remains.
208
+ // Calibrate only from movement inside the expensive classification stage.
209
+ if (stage !== "classifying-repeated-context") {
210
+ return tickExistingEstimate();
211
+ }
212
+ if (reportEtaStage !== stage) {
213
+ reportEtaStage = stage;
214
+ reportEtaStageDone = stageDone;
215
+ reportEtaStageElapsed = stageElapsed;
216
+ reportEtaObservedRate = 0;
217
+ return { seconds: 0, overdue: false, calibrating: true };
218
+ }
219
+
220
+ // The first canonical completions can be cache hits and arrive almost instantly. Treat the
221
+ // first observation as a baseline, then measure only later movement over real elapsed time.
222
+ if (stageDone > reportEtaStageDone && stageElapsed > reportEtaStageElapsed) {
223
+ var sampleElapsed = stageElapsed - reportEtaStageElapsed;
224
+ var sampleDone = stageDone - reportEtaStageDone;
225
+ if (sampleElapsed >= 2 && sampleDone > 0) {
226
+ var sampleRate = sampleDone / sampleElapsed;
227
+ reportEtaObservedRate = reportEtaObservedRate > 0
228
+ ? reportEtaObservedRate * 0.65 + sampleRate * 0.35
229
+ : sampleRate;
230
+ reportEtaStageDone = stageDone;
231
+ reportEtaStageElapsed = stageElapsed;
232
+ }
233
+ }
234
+ if (
235
+ reportEtaObservedRate <= 0 ||
236
+ !isFinite(reportEtaObservedRate)
237
+ ) {
238
+ return { seconds: 0, overdue: false, calibrating: true };
239
+ }
240
+ if (stageTotal <= stageDone) return tickExistingEstimate();
241
+
242
+ // Add a safety margin for the tail of slower sessions plus final report reconciliation.
243
+ // This intentionally prefers a slightly early completion over a fake three-second promise.
244
+ var observedRemaining = ((stageTotal - stageDone) / reportEtaObservedRate) * 1.35 + 5;
245
+ observedRemaining = Math.min(15 * 60, Math.max(8, observedRemaining));
246
+
247
+ if (!reportEtaDeadline) {
248
+ reportEtaDeadline = now + observedRemaining * 1000;
249
+ reportEtaDisplaySeconds = observedRemaining;
250
+ } else {
251
+ var sinceLast = reportEtaUpdatedAt ? Math.max(0, (now - reportEtaUpdatedAt) / 1000) : 0;
252
+ var clockRemaining = Math.max(0, reportEtaDisplaySeconds - sinceLast);
253
+ // A faster observed rate may shorten the estimate. A slower sample never pushes the
254
+ // countdown back upward; passing the first deadline changes the copy to "Finishing up".
255
+ reportEtaDisplaySeconds = Math.min(clockRemaining, observedRemaining);
256
+ }
257
+ reportEtaUpdatedAt = now;
258
+ return {
259
+ seconds: Math.max(0, reportEtaDisplaySeconds),
260
+ overdue: now >= reportEtaDeadline || reportEtaDisplaySeconds <= 0,
261
+ calibrating: false,
262
+ };
263
+ }
172
264
  function renderLoading() {
173
265
  var progress = reportEnvelope && reportEnvelope.kind === "scanning" ? (reportEnvelope.progress || {}) : {};
266
+ var scanId = String(reportEnvelope && reportEnvelope.scanId || "");
267
+ if (scanId !== reportEtaScanId) {
268
+ reportEtaScanId = scanId;
269
+ reportEtaDeadline = 0;
270
+ reportEtaDisplaySeconds = 0;
271
+ reportEtaUpdatedAt = 0;
272
+ reportEtaStage = "";
273
+ reportEtaStageDone = 0;
274
+ reportEtaStageElapsed = 0;
275
+ reportEtaObservedRate = 0;
276
+ }
174
277
  var scannedValue = Number(progress.scanned);
175
278
  var totalValue = Number(progress.total);
176
279
  var scanned = isFinite(scannedValue) ? Math.max(0, scannedValue) : 0;
@@ -182,26 +285,48 @@ export const SETUP_PAGE_CLIENT_REPORT_CITY = String.raw ` /* ---------- ful
182
285
  var overall = isFinite(overallValue) ? Math.min(1, Math.max(0, overallValue)) : 0;
183
286
  var pct = Math.round(overall * 100);
184
287
  var stage = String(progress.stage || "starting");
288
+ var stageDone = Math.max(0, Number(progress.stageDone || 0));
289
+ var stageTotal = Math.max(0, Number(progress.stageTotal || 0));
290
+ var stageElapsed = Math.max(0, Number(progress.stageElapsedMs || 0));
291
+ var beat = Math.floor(stageElapsed / 4500);
185
292
  var progressText = "";
186
293
  if (total > 0 && stage === "reading-transcripts") {
187
294
  progressText = "Reading " + number(visibleScanned) + " of " + number(total) + " sessions…";
188
- } else if (stage === "building-summary" || stage === "classifying-repeated-context") {
189
- progressText = total > 0
190
- ? "Analyzing repeated context across " + number(total) + " sessions…"
191
- : "Analyzing repeated context…";
295
+ } else if (stage === "building-summary") {
296
+ var summaryBeats = [
297
+ "Organizing what Echo found…",
298
+ "Building the report summary…"
299
+ ];
300
+ progressText = summaryBeats[beat % summaryBeats.length];
301
+ } else if (stage === "classifying-repeated-context") {
302
+ var analysisCount = stageTotal > 0
303
+ ? " · " + number(Math.min(stageDone, stageTotal)) + " of " + number(stageTotal)
304
+ : "";
305
+ var analysisBeats = [
306
+ "Comparing context across sessions" + analysisCount + "…",
307
+ "Finding repeated context" + analysisCount + "…",
308
+ "Separating useful context from noise" + analysisCount + "…"
309
+ ];
310
+ progressText = analysisBeats[beat % analysisBeats.length];
192
311
  } else if (stage === "finalizing-report") {
193
- progressText = "Preparing your report…";
312
+ progressText = beat % 2 === 0 ? "Checking the report totals…" : "Preparing your report…";
194
313
  }
314
+ var etaState = estimateReportRemaining(progress, overall, stage);
315
+ var etaText = etaState.overdue
316
+ ? "Taking a little longer than estimated…"
317
+ : (etaState.seconds > 0 ? formatReportEta(etaState.seconds) : "Estimating time remaining…");
195
318
  var fill = document.getElementById("scan-load-fill");
196
319
  var caption = document.getElementById("scan-load-caption");
320
+ var eta = document.getElementById("scan-load-eta");
197
321
  if (app.className === "scanLoading" && fill) {
198
322
  fill.style.width = pct + "%";
199
323
  fill.parentElement.setAttribute("aria-valuenow", String(pct));
200
- fill.parentElement.setAttribute("aria-valuetext", progressText || "Preparing session count");
324
+ fill.parentElement.setAttribute("aria-valuetext", (progressText || "Preparing session count") + " " + etaText);
201
325
  if (caption) {
202
326
  caption.textContent = progressText;
203
327
  caption.hidden = !progressText;
204
328
  }
329
+ if (eta) eta.textContent = etaText;
205
330
  return;
206
331
  }
207
332
  setScanMode(true);
@@ -210,10 +335,19 @@ export const SETUP_PAGE_CLIENT_REPORT_CITY = String.raw ` /* ---------- ful
210
335
  app.innerHTML =
211
336
  setupJourney(1) +
212
337
  '<section class="mvpReading" aria-labelledby="scanLoadingTitle">' +
213
- '<img class="mvpReadingMascot" src="/hud-assets/scan-loop/frame-04-read.png" alt="" aria-hidden="true" />' +
338
+ '<span class="mvpReadingMascot scanMascotFrames" aria-hidden="true">' +
339
+ '<img class="scanMascotFrame" src="/hud-assets/scan-loop/frame-01-ready.png" alt="" />' +
340
+ '<img class="scanMascotFrame" src="/hud-assets/scan-loop/frame-02-open.png" alt="" />' +
341
+ '<img class="scanMascotFrame" src="/hud-assets/scan-loop/frame-03-pull.png" alt="" />' +
342
+ '<img class="scanMascotFrame" src="/hud-assets/scan-loop/frame-04-read.png" alt="" />' +
343
+ '<img class="scanMascotFrame" src="/hud-assets/scan-loop/frame-04-read.png" alt="" />' +
344
+ '<img class="scanMascotFrame" src="/hud-assets/scan-loop/frame-05-file.png" alt="" />' +
345
+ '<img class="scanMascotFrame" src="/hud-assets/scan-loop/frame-06-close.png" alt="" />' +
346
+ '</span>' +
214
347
  '<h2 id="scanLoadingTitle">Building your report…</h2>' +
215
348
  '<p class="mvpReadingCount" id="scan-load-caption" aria-live="polite"' + (progressText ? "" : " hidden") + '>' + esc(progressText) + '</p>' +
216
- '<div class="scanProgress mvpReadingProgress" role="progressbar" aria-labelledby="scanLoadingTitle" aria-valuemin="0" aria-valuemax="100" aria-valuenow="' + pct + '" aria-valuetext="' + esc(progressText || "Preparing session count") + '">' +
349
+ '<p class="mvpReadingEta" id="scan-load-eta">' + esc(etaText) + '</p>' +
350
+ '<div class="scanProgress mvpReadingProgress" role="progressbar" aria-labelledby="scanLoadingTitle" aria-valuemin="0" aria-valuemax="100" aria-valuenow="' + pct + '" aria-valuetext="' + esc((progressText || "Preparing session count") + " " + etaText) + '">' +
217
351
  '<i id="scan-load-fill" class="scanProgressFill" style="width:' + pct + '%"></i>' +
218
352
  '</div>' +
219
353
  '</section>';
@@ -1462,6 +1462,43 @@ export const SETUP_PAGE_STYLES_EXTRACTION = String.raw ` /* ---- dashboard (p
1462
1462
  .savedHeading strong {
1463
1463
  font-variant-numeric: tabular-nums;
1464
1464
  }
1465
+ .firstRecallStage {
1466
+ display: flex;
1467
+ flex-direction: column;
1468
+ gap: clamp(22px, 3vw, 34px);
1469
+ }
1470
+ .firstRecallStage .resultTop h2 {
1471
+ max-width: none;
1472
+ margin: 0;
1473
+ color: var(--echo-ink-text);
1474
+ font-family: var(--echo-font-brand);
1475
+ font-size: clamp(42px, 6vw, 72px);
1476
+ font-weight: 700;
1477
+ line-height: 0.96;
1478
+ letter-spacing: -0.045em;
1479
+ }
1480
+ .firstRecallStage .savedHeading {
1481
+ display: block;
1482
+ }
1483
+ .firstRecallStage .savedHeading .savedMain {
1484
+ color: var(--echo-ink-text);
1485
+ }
1486
+ .firstRecallStage .savedHeading strong {
1487
+ color: var(--echo-ink-primary);
1488
+ font-weight: 900;
1489
+ }
1490
+ .firstRecallStage .tryCard {
1491
+ margin-top: 0;
1492
+ }
1493
+ .firstRecallStage .tryCard > .tryTitle {
1494
+ margin-bottom: 0;
1495
+ color: var(--echo-ink-primary);
1496
+ font-size: clamp(26px, 3vw, 38px);
1497
+ line-height: 1.04;
1498
+ }
1499
+ .firstRecallStage .recallPromptPrimary {
1500
+ margin-top: 18px;
1501
+ }
1465
1502
  .viewEchoLink {
1466
1503
  color: var(--echo-ink-mute);
1467
1504
  font-style: normal;
@@ -14,7 +14,8 @@ export const SETUP_PAGE_STYLES_MVP = String.raw `
14
14
  width: 100%;
15
15
  min-height: 100vh;
16
16
  margin: 0;
17
- padding: clamp(16px, 2.4vw, 34px) clamp(18px, 5vw, 64px) clamp(38px, 6vw, 72px);
17
+ padding: clamp(12px, 1.8vw, 24px) clamp(18px, 5vw, 64px) clamp(38px, 6vw, 72px);
18
+ display: block;
18
19
  }
19
20
  main > header { display: none; }
20
21
  #app.state { color: transparent; }
@@ -24,11 +25,17 @@ export const SETUP_PAGE_STYLES_MVP = String.raw `
24
25
  width: 100%;
25
26
  min-height: calc(100vh - clamp(56px, 10vw, 128px));
26
27
  }
28
+ body.readyMode #app:has(> .setupJourney),
29
+ body.scanMode #app:has(> .setupJourney),
30
+ body.extractMode #app:has(> .setupJourney) {
31
+ padding-top: 0 !important;
32
+ }
27
33
 
28
34
  /* One calm map across local scan, account/Stripe, import, and first value. */
29
35
  .setupJourney {
30
36
  width: min(760px, 100%);
31
- margin: 0 auto clamp(16px, 2.4vw, 26px);
37
+ flex: 0 0 auto;
38
+ margin: 0 auto 14px;
32
39
  color: var(--echo-ink-faint);
33
40
  }
34
41
  .setupJourney ol {
@@ -93,12 +100,13 @@ export const SETUP_PAGE_STYLES_MVP = String.raw `
93
100
  color: white;
94
101
  }
95
102
  body.cityMode .setupJourney {
96
- margin-top: 26px;
103
+ margin-top: 12px;
97
104
  margin-bottom: 8px;
98
105
  padding-inline: 18px;
99
106
  }
100
- body.readyMode #app:has(.readyDecision.extractionReady) {
101
- padding-top: 124px;
107
+ #setupJourneySlot {
108
+ width: 100%;
109
+ flex: 0 0 auto;
102
110
  }
103
111
 
104
112
  /* Permission ------------------------------------------------------------ */
@@ -481,8 +489,13 @@ export const SETUP_PAGE_STYLES_MVP = String.raw `
481
489
 
482
490
  /* Reading --------------------------------------------------------------- */
483
491
  .scanLoading {
484
- display: grid !important;
485
- place-items: center;
492
+ display: flex !important;
493
+ flex-direction: column;
494
+ align-items: center;
495
+ justify-content: flex-start;
496
+ }
497
+ .scanLoading .mvpReading {
498
+ margin-block: auto;
486
499
  }
487
500
  .mvpReading {
488
501
  display: flex;
@@ -515,12 +528,45 @@ export const SETUP_PAGE_STYLES_MVP = String.raw `
515
528
  display: block;
516
529
  visibility: hidden;
517
530
  }
531
+ .mvpReadingEta {
532
+ min-height: 17px;
533
+ margin: 5px 0 0;
534
+ color: var(--echo-ink-faint);
535
+ font: 600 12px/1.4 var(--echo-font-mono);
536
+ font-variant-numeric: tabular-nums;
537
+ }
518
538
  .mvpReadingProgress {
519
539
  width: min(260px, 100%);
520
540
  height: 3px;
521
541
  margin: 8px 0 0;
522
542
  background: rgba(26,58,143,0.12);
523
543
  }
544
+ .discoveryLoader {
545
+ width: 100%;
546
+ min-height: min(62vh, 620px);
547
+ display: grid;
548
+ place-items: center;
549
+ }
550
+ .discoverySpinner span {
551
+ display: block;
552
+ width: 26px;
553
+ height: 26px;
554
+ border: 3px solid rgba(26,58,143,0.16);
555
+ border-top-color: var(--echo-ink-primary);
556
+ border-radius: 50%;
557
+ animation: localAuthSpin 700ms linear infinite;
558
+ }
559
+ .extractionStartStage {
560
+ display: flex !important;
561
+ flex-direction: column;
562
+ align-items: center;
563
+ justify-content: flex-start;
564
+ }
565
+ .extractionStartStage .discoverySpinner {
566
+ display: grid;
567
+ flex: 1 1 auto;
568
+ place-items: center;
569
+ }
524
570
  .reportMessageStage {
525
571
  display: flex !important;
526
572
  justify-content: center;
@@ -890,6 +936,112 @@ export const SETUP_PAGE_STYLES_MVP = String.raw `
890
936
  text-transform: uppercase;
891
937
  }
892
938
  .planGateAccount .setupAccountChange { min-height: 34px; }
939
+ .readyUtilityNav {
940
+ position: fixed;
941
+ top: 20px;
942
+ left: 22px;
943
+ z-index: 20;
944
+ display: grid;
945
+ width: min(310px, calc(100vw - 44px));
946
+ gap: 2px;
947
+ border: 1px solid rgba(26,58,143,0.10);
948
+ border-radius: 24px;
949
+ background: rgba(255,255,255,0.78);
950
+ padding: 6px;
951
+ box-shadow: 0 8px 24px rgba(26,58,143,0.08);
952
+ backdrop-filter: blur(12px);
953
+ }
954
+ .readyUtilityNav .planGateAccount {
955
+ position: static;
956
+ top: auto;
957
+ left: auto;
958
+ z-index: auto;
959
+ width: 100%;
960
+ max-width: 100%;
961
+ }
962
+ .readyUtilityNav .planGateAccount .setupAccountCard {
963
+ display: grid;
964
+ width: 100%;
965
+ min-height: 44px;
966
+ grid-template-columns: 70px minmax(0, 1fr) auto;
967
+ align-items: center;
968
+ gap: 10px;
969
+ border: 0;
970
+ border-radius: 16px;
971
+ background: transparent;
972
+ padding: 5px 8px;
973
+ box-shadow: none;
974
+ backdrop-filter: none;
975
+ }
976
+ .readyUtilityNav .setupAccountCopy { display: contents; }
977
+ .readyUtilityNav .setupAccountCopy small {
978
+ grid-column: 1;
979
+ margin: 0;
980
+ align-self: center;
981
+ }
982
+ .readyUtilityNav .setupAccountCopy strong {
983
+ grid-column: 2;
984
+ overflow: hidden;
985
+ align-self: center;
986
+ text-overflow: ellipsis;
987
+ white-space: nowrap;
988
+ }
989
+ .readyUtilityNav .setupAccountChange { grid-column: 3; }
990
+ .readyPlanNav {
991
+ display: grid;
992
+ width: 100%;
993
+ min-height: 44px;
994
+ grid-template-columns: 70px minmax(0, 1fr);
995
+ align-items: center;
996
+ gap: 10px;
997
+ border: 0;
998
+ border-radius: 16px;
999
+ background: transparent;
1000
+ padding: 5px 8px;
1001
+ box-shadow: none;
1002
+ backdrop-filter: none;
1003
+ }
1004
+ .readyPlanNav:has(.setupPlanSlot.is-hidden) { display: none; }
1005
+ .readyPlanNavLabel {
1006
+ grid-column: 1;
1007
+ color: var(--echo-ink-faint);
1008
+ font: 700 8px/1 var(--echo-font-mono);
1009
+ letter-spacing: 0.1em;
1010
+ text-transform: uppercase;
1011
+ }
1012
+ .readyPlanNav .setupPlanSlot {
1013
+ grid-column: 2;
1014
+ min-width: 0;
1015
+ }
1016
+ .readyPlanNav .setupPlanConfirmed {
1017
+ display: grid;
1018
+ min-width: 0;
1019
+ grid-template-columns: minmax(0, 1fr) auto;
1020
+ margin: 0;
1021
+ align-items: center;
1022
+ gap: 8px;
1023
+ border: 0;
1024
+ background: transparent;
1025
+ padding: 0;
1026
+ box-shadow: none;
1027
+ }
1028
+ .readyPlanNav .setupPlanConfirmedIcon,
1029
+ .readyPlanNav .setupPlanConfirmedCopy > span { display: none; }
1030
+ .readyPlanNav .setupPlanConfirmedCopy { min-width: 0; }
1031
+ .readyPlanNav .setupPlanConfirmedCopy strong {
1032
+ display: block;
1033
+ overflow: hidden;
1034
+ color: var(--echo-ink-mute);
1035
+ font-size: 13px;
1036
+ font-weight: 500;
1037
+ text-overflow: ellipsis;
1038
+ white-space: nowrap;
1039
+ }
1040
+ .readyPlanNav .textButton {
1041
+ min-height: 34px;
1042
+ padding: 0 9px;
1043
+ font-size: 11px;
1044
+ }
893
1045
 
894
1046
  /* Found sessions: one message, one next action -------------------------- */
895
1047
  .readyDecision.extractionReady {
@@ -951,6 +1103,7 @@ export const SETUP_PAGE_STYLES_MVP = String.raw `
951
1103
  line-height: 1.45;
952
1104
  text-align: left;
953
1105
  }
1106
+ .extractionReady .readyHero .exSub:empty { display: none; }
954
1107
  .extractionReady .proofStrip {
955
1108
  order: 1;
956
1109
  width: fit-content;
@@ -996,6 +1149,7 @@ export const SETUP_PAGE_STYLES_MVP = String.raw `
996
1149
  color: var(--echo-ink-faint);
997
1150
  font-size: 11px;
998
1151
  }
1152
+ .extractionReady .ctaMeta:empty { display: none; }
999
1153
  .extractionReady .ctaMeta .dataHandlingLink { font-size: inherit; }
1000
1154
  .extractionReady .readyFoot { order: 4; }
1001
1155
  .billingCommitmentSlot {
@@ -1257,6 +1411,34 @@ export const SETUP_PAGE_STYLES_MVP = String.raw `
1257
1411
  letter-spacing: -0.06em;
1258
1412
  }
1259
1413
  .sessionPickerHead p { margin-top: 12px; font-size: 14px; }
1414
+ .sessionPickerSummary {
1415
+ display: flex;
1416
+ flex-wrap: wrap;
1417
+ align-items: center;
1418
+ gap: 8px;
1419
+ }
1420
+ .sessionPickerQuota strong {
1421
+ color: var(--echo-ink-text);
1422
+ font-weight: 750;
1423
+ }
1424
+ .sessionPickerUpgrade {
1425
+ display: inline-flex;
1426
+ min-height: 30px;
1427
+ align-items: center;
1428
+ justify-content: center;
1429
+ border: 1px solid rgba(26,58,143,0.22);
1430
+ border-radius: 999px;
1431
+ background: rgba(255,255,255,0.78);
1432
+ padding: 0 12px;
1433
+ color: var(--echo-ink-primary);
1434
+ font-size: 12px;
1435
+ font-weight: 750;
1436
+ line-height: 1;
1437
+ text-decoration: none;
1438
+ box-shadow: 0 4px 12px rgba(26,58,143,0.07);
1439
+ }
1440
+ .sessionPickerUpgrade:hover { background: var(--echo-paper-white); }
1441
+ .sessionPickerOrder { color: var(--echo-ink-faint); }
1260
1442
  .sessionPickerClose {
1261
1443
  border: 0;
1262
1444
  background: rgba(255,255,255,0.72);
@@ -162,7 +162,12 @@ export const SETUP_PAGE_STYLES_WEBSITE_ALIGNMENT = String.raw `
162
162
  filter: drop-shadow(0 12px 14px rgba(26,58,143,0.16));
163
163
  animation: scanMascotFrame 4.2s linear infinite;
164
164
  }
165
- .scanMascotFrame:nth-child(1) { animation-delay: 0s; }
165
+ /* Keep the ready pose mounted underneath every animated frame. This prevents
166
+ a transparent flash at loop boundaries or while a later PNG is decoding. */
167
+ .scanMascotFrame:nth-child(1) {
168
+ opacity: 1;
169
+ animation: none;
170
+ }
166
171
  .scanMascotFrame:nth-child(2) { animation-delay: 0.6s; }
167
172
  .scanMascotFrame:nth-child(3) { animation-delay: 1.2s; }
168
173
  .scanMascotFrame:nth-child(4) { animation-delay: 1.8s; }
@@ -170,8 +175,8 @@ export const SETUP_PAGE_STYLES_WEBSITE_ALIGNMENT = String.raw `
170
175
  .scanMascotFrame:nth-child(6) { animation-delay: 3s; }
171
176
  .scanMascotFrame:nth-child(7) { animation-delay: 3.6s; }
172
177
  @keyframes scanMascotFrame {
173
- 0%, 14.2% { opacity: 1; }
174
- 14.3%, 100% { opacity: 0; }
178
+ 0%, 14.4% { opacity: 1; }
179
+ 14.5%, 100% { opacity: 0; }
175
180
  }
176
181
  .scanLoaderCopy { min-width: 0; }
177
182
  .scanEyebrow {