@echomem/mcp 1.4.21 → 1.4.22

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.
@@ -6,39 +6,24 @@ export const SETUP_PAGE_CLIENT_LIFECYCLE = String.raw ` /* ---------- scan-
6
6
  setExtractMode(false);
7
7
  setReadyMode(true);
8
8
  setHead("Local file access", "Consent");
9
- app.className = "consentStage";
9
+ app.className = "consentStage mvpConsentStage";
10
10
  app.innerHTML =
11
- '<section class="localConsent localConsentAccess">' +
12
- '<div class="consentLead">' +
13
- '<p class="consentEyebrow">Local history access / required</p>' +
14
- '<h2 class="siteHeadline">Let Echo read your <strong>local coding history?</strong></h2>' +
15
- '<p class="consentIntro">Echo needs this access to find your local Codex and Claude Code sessions, build your private usage report, and prepare conversations for memory extraction.</p>' +
16
- '<div class="consentLocalMark"><img src="/hud-assets/echo-face-cutout.png" alt="" /><span><strong>Processed on this Mac</strong><small>This scan does not send transcripts to EchoMem.</small></span></div>' +
17
- '</div>' +
18
- '<div class="consentDecision">' +
19
- '<div class="consentScopeHead"><strong>What this access does</strong><span>Local scan</span></div>' +
20
- '<div class="consentScope" aria-label="What this permission allows">' +
21
- '<div><span class="consentScopeNumber" aria-hidden="true">01</span><p><strong>Reads supported session logs</strong><small>Only local Codex and Claude Code history on this device.</small></p></div>' +
22
- '<div><span class="consentScopeNumber" aria-hidden="true">02</span><p><strong>Builds the Agent Doctor report</strong><small>Usage patterns and visualizations are computed locally.</small></p></div>' +
23
- '<div><span class="consentScopeNumber" aria-hidden="true">03</span><p><strong>Keeps transcripts on this device</strong><small>Nothing is uploaded unless you later select conversations for memory extraction.</small></p></div>' +
24
- '</div>' +
25
- '<label class="consentCheck"><input type="checkbox" id="localScanConsentCheck" /><span><strong>I understand and allow this local scan</strong><small>Required for Agent Doctor and to choose local conversations for extraction.</small></span></label>' +
26
- '<div class="consentActions">' +
27
- '<button type="button" class="primary" id="allowLocalScan" disabled>Allow local scan</button>' +
28
- '<button type="button" class="textButton" id="declineLocalScan">Not now</button>' +
29
- '</div>' +
30
- '<p class="consentFine" id="consentStatus" role="status" aria-live="polite"></p>' +
31
- '</div>' +
11
+ '<section class="mvpConsent" aria-labelledby="mvpConsentTitle">' +
12
+ '<img class="mvpConsentEcho" src="/hud-assets/echo-face-cutout.png" alt="" />' +
13
+ '<p class="mvpKicker">Before we begin</p>' +
14
+ '<h2 id="mvpConsentTitle">Echo has something for you.</h2>' +
15
+ '<p class="mvpConsentIntro">Let Echo read your local Codex and Claude Code history to build a private report. You’ll see what’s filling your context window, what keeps getting repeated, and where memory could help.</p>' +
16
+ '<p class="mvpPrivacy">The scan runs on this Mac. Nothing leaves it unless you later choose memories to extract.</p>' +
17
+ '<button type="button" class="primary mvpPrimary" id="allowLocalScan">Allow scan &amp; build my report <span aria-hidden="true">→</span></button>' +
18
+ '<button type="button" class="mvpQuiet" id="declineLocalScan">Not now</button>' +
19
+ '<p class="consentFine" id="consentStatus" role="status" aria-live="polite"></p>' +
32
20
  '</section>';
33
21
  var allow = document.getElementById("allowLocalScan");
34
22
  if (allow) allow.onclick = allowLocalScan;
35
- var consentCheck = document.getElementById("localScanConsentCheck");
36
- if (consentCheck && allow) consentCheck.onchange = function () {
37
- allow.disabled = !consentCheck.checked;
38
- if (consentCheck.checked) setConsentStatus("");
39
- };
40
23
  var decline = document.getElementById("declineLocalScan");
41
- if (decline) decline.onclick = declineLocalScan;
24
+ if (decline) decline.onclick = function () {
25
+ setConsentStatus("Echo can't continue setup without permission to scan your local history.", "required");
26
+ };
42
27
  }
43
28
  function setConsentStatus(message, tone) {
44
29
  var node = document.getElementById("consentStatus");
@@ -48,22 +33,33 @@ export const SETUP_PAGE_CLIENT_LIFECYCLE = String.raw ` /* ---------- scan-
48
33
  }
49
34
  }
50
35
  async function allowLocalScan() {
36
+ var allow = document.getElementById("allowLocalScan");
37
+ if (allow) allow.disabled = true;
51
38
  setConsentStatus("Starting local scan...");
52
39
  try {
53
- await postJson("/report-consent", { allowed: true });
40
+ await postJson("/report-consent", { allowed: true }, 5000);
54
41
  localHistoryConsentGranted = true;
55
42
  await showReport();
56
43
  } catch (error) {
44
+ // The localhost bridge may have accepted consent even if the browser loses the POST
45
+ // response while it swaps connections. Recover from the server's authoritative state
46
+ // instead of leaving the user forever on "Starting local scan...".
47
+ try {
48
+ var configResponse = await fetchWithTimeout(
49
+ "/config?nonce=" + encodeURIComponent(nonce),
50
+ { credentials: "omit", cache: "no-store" },
51
+ 3000
52
+ );
53
+ var config = configResponse.ok ? await configResponse.json() : null;
54
+ if (config && config.consentGranted === true) {
55
+ localHistoryConsentGranted = true;
56
+ await showReport();
57
+ return;
58
+ }
59
+ } catch (_) {}
57
60
  renderReportIssue("CONSENT_SAVE_FAILED", error && error.message ? error.message : "Could not start the local scan.");
58
61
  }
59
62
  }
60
- async function declineLocalScan() {
61
- localHistoryConsentGranted = false;
62
- try {
63
- await postJson("/report-consent", { allowed: false });
64
- } catch (_) {}
65
- setConsentStatus("Local history access is required to continue setup. Nothing will be scanned or uploaded unless you allow it.", "required");
66
- }
67
63
  async function showReport() {
68
64
  if (reportPollStarted) return;
69
65
  reportPollStarted = true;
@@ -136,12 +132,14 @@ export const SETUP_PAGE_CLIENT_LIFECYCLE = String.raw ` /* ---------- scan-
136
132
  if (event.origin !== location.origin) return;
137
133
  var data = event.data || {};
138
134
  if (data.type === "echomem:connected" && data.nonce === nonce) {
139
- if (!localHistoryConsentGranted) {
140
- renderLocalScanConsent();
141
- setConsentStatus("Local history access is required before you can connect EchoMem or continue setup.", "required");
135
+ if (setupFlow === "login") {
136
+ connected = true;
137
+ renderLocalLoginComplete();
142
138
  return;
143
139
  }
144
140
  connected = true;
141
+ rememberSetupPlanChoice("");
142
+ try { void refreshBillingStatus(); } catch (_) {}
145
143
  void waitForStats();
146
144
  } else if (data.type === "echomem:city-ready" && data.nonce === nonce) {
147
145
  scheduleReportToCity();
@@ -162,11 +160,18 @@ export const SETUP_PAGE_CLIENT_LIFECYCLE = String.raw ` /* ---------- scan-
162
160
  var cfg = await getJson("/config");
163
161
  configLoaded = true;
164
162
  connected = !!cfg.connected;
163
+ setupFlow = cfg.flow === "login" ? "login" : "onboarding";
165
164
  workspacePath = cfg.workspacePath || workspacePath;
166
165
  consentRequired = cfg.consentRequired !== false;
167
166
  consentGranted = cfg.consentGranted === true;
168
167
  localHistoryConsentGranted = consentGranted;
169
168
  } catch (_) {}
169
+ if (setupFlow === "login") {
170
+ if (connected) renderLocalLoginComplete();
171
+ else renderLocalLogin("");
172
+ return;
173
+ }
174
+ if (!connected) rememberSetupPlanChoice("");
170
175
  if (params.get("connected") === "1") {
171
176
  if (!configLoaded || (consentRequired && !consentGranted)) {
172
177
  renderLocalScanConsent();
@@ -185,6 +190,7 @@ export const SETUP_PAGE_CLIENT_LIFECYCLE = String.raw ` /* ---------- scan-
185
190
  setTimeout(function () { if (!window.closed) void waitForStats(); }, 1500);
186
191
  } else {
187
192
  // Same-tab fallback (popup was blocked): go straight to the extraction dashboard.
193
+ try { void refreshBillingStatus(); } catch (_) {}
188
194
  void waitForStats();
189
195
  }
190
196
  return;
@@ -717,7 +717,7 @@ export const SETUP_PAGE_CLIENT_REPORT_AUDIT = String.raw ` /* ---------- fo
717
717
  '<div class="audit-list">' + groups.map(function (group, index) { return auditRowHtml(group, index, totalWaste, slices[index]); }).join('') + '</div>' +
718
718
  '<footer class="audit-end">' +
719
719
  '<h3>Stop burning tokens on these mistakes.</h3>' +
720
- '<button class="rdo-cta" data-connect-echo>' + (connected ? 'Continue' : 'Add') +
720
+ '<button class="rdo-cta" data-connect-echo>' + (connected ? 'Continue' : 'Connect Echo') +
721
721
  '<img class="cta-face" src="/hud-assets/echo-face-cutout.png" alt="Echo" />' +
722
722
  '<svg class="cta-arrow" viewBox="0 0 64 26" aria-hidden="true">' +
723
723
  '<defs><filter id="ctaPencil" x="-20%" y="-40%" width="140%" height="180%"><feTurbulence type="fractalNoise" baseFrequency="0.05 0.12" numOctaves="3" seed="11"/><feDisplacementMap in="SourceGraphic" scale="2.6"/></filter></defs>' +
@@ -173,12 +173,23 @@ export const SETUP_PAGE_CLIENT_REPORT_CITY = String.raw ` /* ---------- ful
173
173
  var scanned = Math.max(0, Number(progress.scanned || 0));
174
174
  var total = Math.max(0, Number(progress.total || 0));
175
175
  var pct = total > 0 ? Math.min(100, Math.round((scanned / total) * 100)) : 0;
176
- var elapsed = Number(progress.stageElapsedMs || 0);
177
- var unit = progress.stage === "reading-transcripts" ? "transcript files" : "sessions";
178
- var stage = progress.label || "Starting local scan";
179
- var caption = total > 0
180
- ? stage + " " + number(scanned) + " / " + number(total) + " " + unit + (elapsed > 0 ? " · " + Math.round(elapsed / 1000) + "s" : "")
181
- : stage + "…";
176
+ if (!reportEstimateDeadline) reportEstimateDeadline = Date.now() + 30000;
177
+ function estimateCaption() {
178
+ var seconds = Math.ceil((reportEstimateDeadline - Date.now()) / 1000);
179
+ return seconds > 0
180
+ ? "About " + seconds + " seconds remaining"
181
+ : "Still working — your report will be ready soon.";
182
+ }
183
+ var caption = estimateCaption();
184
+ function updateEstimateCaption() {
185
+ var node = document.getElementById("scan-load-caption");
186
+ if (!node) return;
187
+ node.textContent = estimateCaption();
188
+ if (Date.now() >= reportEstimateDeadline && reportEstimateTimer) {
189
+ clearInterval(reportEstimateTimer);
190
+ reportEstimateTimer = null;
191
+ }
192
+ }
182
193
  var fill = document.getElementById("scan-load-fill");
183
194
  if (app.className === "scanLoading" && fill) {
184
195
  fill.style.width = pct + "%";
@@ -191,23 +202,25 @@ export const SETUP_PAGE_CLIENT_REPORT_CITY = String.raw ` /* ---------- ful
191
202
  setHead("Reading your local coding history", "Local scan");
192
203
  app.className = "scanLoading";
193
204
  app.innerHTML =
194
- '<div class="scanLoader" aria-live="polite">' +
195
- '<div class="scanLoaderLead">' +
196
- '<span class="scanMascot" aria-hidden="true"><img src="/hud-assets/echo-face-cutout.png" alt="" /></span>' +
197
- '<div class="scanLoaderCopy">' +
198
- '<p class="scanEyebrow">Local scan</p>' +
199
- '<h2 class="siteHeadline">Reading your <strong>local coding history</strong></h2>' +
200
- '<p class="scanIntro">Echo is organizing your Codex and Claude Code sessions on this Mac.</p>' +
201
- '</div>' +
202
- '</div>' +
203
- '<div class="scanProgressCard">' +
204
- '<div class="scanProgress" role="progressbar" aria-label="Local scan progress" aria-valuemin="0" aria-valuemax="100" aria-valuenow="' + pct + '">' +
205
- '<i id="scan-load-fill" class="scanProgressFill" style="width:' + pct + '%"></i>' +
206
- '</div>' +
207
- '<p id="scan-load-caption" class="scanCaption">' + esc(caption) + '</p>' +
205
+ '<section class="mvpReading" aria-live="polite">' +
206
+ '<span class="scanMascot mvpReadingAnimation" aria-hidden="true">' +
207
+ '<span class="scanMascotFrames">' +
208
+ '<img class="scanMascotFrame" src="/hud-assets/scan-loop/frame-01-ready.png" alt="" />' +
209
+ '<img class="scanMascotFrame" src="/hud-assets/scan-loop/frame-02-open.png" alt="" />' +
210
+ '<img class="scanMascotFrame" src="/hud-assets/scan-loop/frame-03-pull.png" alt="" />' +
211
+ '<img class="scanMascotFrame" src="/hud-assets/scan-loop/frame-04-read.png" alt="" />' +
212
+ '<img class="scanMascotFrame" src="/hud-assets/scan-loop/frame-04-read.png" alt="" />' +
213
+ '<img class="scanMascotFrame" src="/hud-assets/scan-loop/frame-05-file.png" alt="" />' +
214
+ '<img class="scanMascotFrame" src="/hud-assets/scan-loop/frame-06-close.png" alt="" />' +
215
+ '</span>' +
216
+ '</span>' +
217
+ '<h2>Reading your coding history…</h2>' +
218
+ '<p id="scan-load-caption" class="scanCaption">' + esc(caption) + '</p>' +
219
+ '<div class="scanProgress mvpReadingProgress" role="progressbar" aria-label="Local scan progress" aria-valuemin="0" aria-valuemax="100" aria-valuenow="' + pct + '">' +
220
+ '<i id="scan-load-fill" class="scanProgressFill" style="width:' + pct + '%"></i>' +
208
221
  '</div>' +
209
- '<p class="scanPrivacy"><span aria-hidden="true"></span>Transcripts stay on this device during the scan.</p>' +
210
- '</div>';
222
+ '</section>';
223
+ if (!reportEstimateTimer) reportEstimateTimer = setInterval(updateEstimateCaption, 1000);
211
224
  }
212
225
 
213
226
  `;
@@ -11,7 +11,7 @@ const SETUP_PAGE_BODY = String.raw `
11
11
  </div>
12
12
  <div class="pill" id="status">Local</div>
13
13
  </header>
14
- <section id="app" class="state">Preparing setup...</section>
14
+ <section id="app" class="state" aria-busy="true"></section>
15
15
  </main>
16
16
  <dialog id="echo-note" class="echo-note-dlg">
17
17
  <button class="nclose" aria-label="Close">&times;</button>
@@ -146,6 +146,263 @@ export const SETUP_PAGE_STYLES_EXTRACTION = String.raw ` /* ---- dashboard (p
146
146
  min-height: min(660px, calc(100vh - 120px));
147
147
  margin: 0 auto;
148
148
  }
149
+ .localAuthWelcomeStage {
150
+ width: min(760px, 100%);
151
+ min-height: calc(100vh - clamp(56px, 10vw, 116px));
152
+ }
153
+ body.readyMode:has(.localAuthWelcome) {
154
+ background: var(--echo-paper-canvas);
155
+ background-image: none;
156
+ }
157
+ .localAuthWelcome {
158
+ width: min(590px, 100%);
159
+ display: grid;
160
+ justify-items: center;
161
+ gap: 22px;
162
+ text-align: center;
163
+ color: var(--echo-ink-text);
164
+ }
165
+ .localAuthOrbScene {
166
+ position: relative;
167
+ display: grid;
168
+ justify-items: center;
169
+ width: 108px;
170
+ height: 142px;
171
+ margin-bottom: 2px;
172
+ }
173
+ .localAuthOrb {
174
+ position: relative;
175
+ display: block;
176
+ width: 66px;
177
+ height: 66px;
178
+ border-radius: 50%;
179
+ background:
180
+ radial-gradient(circle at 36% 30%, rgba(255,255,255,0.56) 0 4%, transparent 24%),
181
+ radial-gradient(circle at 54% 66%, #147fdc 0 22%, transparent 58%),
182
+ linear-gradient(145deg, #68d9ff 0%, #21a9f4 46%, #1172d3 100%);
183
+ box-shadow:
184
+ inset -9px -12px 18px rgba(0,74,177,0.18),
185
+ 0 12px 24px rgba(17,114,211,0.16);
186
+ animation: localAuthOrbFloat 3.2s ease-in-out infinite;
187
+ }
188
+ .localAuthOrb::before,
189
+ .localAuthOrb::after {
190
+ content: "";
191
+ position: absolute;
192
+ top: 28px;
193
+ width: 5px;
194
+ height: 8px;
195
+ border-radius: 50%;
196
+ background: #11100f;
197
+ }
198
+ .localAuthOrb::before { left: 24px; }
199
+ .localAuthOrb::after { right: 24px; }
200
+ .localAuthOrbShadow {
201
+ position: absolute;
202
+ left: 50%;
203
+ bottom: 2px;
204
+ width: 90px;
205
+ height: 10px;
206
+ border-radius: 50%;
207
+ background: rgba(65,61,54,0.12);
208
+ filter: blur(7px);
209
+ transform: translateX(-50%);
210
+ animation: localAuthOrbShadow 3.2s ease-in-out infinite;
211
+ }
212
+ @keyframes localAuthOrbFloat {
213
+ 0%, 100% { transform: translateY(0); }
214
+ 50% { transform: translateY(-10px); }
215
+ }
216
+ @keyframes localAuthOrbShadow {
217
+ 0%, 100% { opacity: 0.78; transform: translateX(-50%) scaleX(1); }
218
+ 50% { opacity: 0.42; transform: translateX(-50%) scaleX(0.72); }
219
+ }
220
+ .localAuthWelcomeCopy {
221
+ display: grid;
222
+ justify-items: center;
223
+ gap: 6px;
224
+ }
225
+ .localAuthWelcomeCopy h2 {
226
+ margin: 0;
227
+ color: var(--echo-ink-primary);
228
+ font-family: var(--echo-font-script, "Caveat", cursive);
229
+ font-size: clamp(44px, 6vw, 58px);
230
+ font-weight: 700;
231
+ line-height: 0.95;
232
+ letter-spacing: 0;
233
+ }
234
+ .localAuthWelcomeCopy p {
235
+ margin: 0;
236
+ color: #74736f;
237
+ font-size: clamp(14px, 1.7vw, 17px);
238
+ line-height: 1.45;
239
+ letter-spacing: 0.01em;
240
+ }
241
+ .localAuthWelcomeForm {
242
+ width: min(520px, 100%);
243
+ display: grid;
244
+ gap: 16px;
245
+ }
246
+ .localAuthEmailCapsule {
247
+ position: relative;
248
+ display: grid;
249
+ grid-template-columns: minmax(0, 1fr) 58px;
250
+ align-items: center;
251
+ min-height: 64px;
252
+ border: 1px solid rgba(78,78,75,0.20);
253
+ border-radius: 999px;
254
+ background: rgba(255,255,255,0.94);
255
+ padding: 5px 6px 5px 22px;
256
+ box-shadow: 0 3px 8px rgba(50,48,43,0.08);
257
+ transition: border-color 160ms ease, box-shadow 160ms ease;
258
+ }
259
+ .localAuthEmailCapsule .srOnly {
260
+ position: absolute;
261
+ width: 1px;
262
+ height: 1px;
263
+ padding: 0;
264
+ margin: -1px;
265
+ overflow: hidden;
266
+ clip: rect(0, 0, 0, 0);
267
+ white-space: nowrap;
268
+ border: 0;
269
+ }
270
+ .localAuthEmailCapsule:focus-within {
271
+ border-color: rgba(26,58,143,0.48);
272
+ box-shadow: 0 0 0 3px rgba(26,58,143,0.10), 0 3px 8px rgba(50,48,43,0.08);
273
+ }
274
+ .localAuthEmailCapsule input {
275
+ min-width: 0;
276
+ border: 0;
277
+ background: transparent;
278
+ color: var(--echo-ink-text);
279
+ font: inherit;
280
+ font-size: 17px;
281
+ outline: none;
282
+ }
283
+ .localAuthEmailCapsule input::placeholder { color: #a7a7a3; }
284
+ .localAuthEmailCapsule button {
285
+ width: 52px;
286
+ height: 52px;
287
+ display: grid;
288
+ place-items: center;
289
+ border: 0;
290
+ border-radius: 50%;
291
+ background: var(--echo-ink-primary);
292
+ color: #fff;
293
+ cursor: pointer;
294
+ font: inherit;
295
+ font-size: 27px;
296
+ line-height: 1;
297
+ transition: transform 160ms ease, background 160ms ease, opacity 160ms ease;
298
+ }
299
+ .localAuthEmailCapsule button:hover:not(:disabled) {
300
+ transform: translateX(2px);
301
+ background: #234ba9;
302
+ }
303
+ .localAuthEmailCapsule button:focus-visible {
304
+ outline: 3px solid rgba(26,58,143,0.20);
305
+ outline-offset: 3px;
306
+ }
307
+ .localAuthEmailCapsule button:disabled {
308
+ background: #ccccca;
309
+ color: #fff;
310
+ cursor: not-allowed;
311
+ opacity: 1;
312
+ }
313
+ .localAuthConsent {
314
+ width: min(470px, calc(100% - 24px));
315
+ display: grid;
316
+ grid-template-columns: 19px minmax(0, 1fr);
317
+ gap: 10px;
318
+ align-items: start;
319
+ justify-self: center;
320
+ color: #969591;
321
+ font-size: 13px;
322
+ line-height: 1.55;
323
+ text-align: left;
324
+ }
325
+ .localAuthConsent input {
326
+ width: 18px;
327
+ height: 18px;
328
+ margin: 2px 0 0;
329
+ accent-color: var(--echo-ink-primary);
330
+ }
331
+ .localAuthConsent a {
332
+ color: #62615e;
333
+ text-decoration: underline;
334
+ text-underline-offset: 2px;
335
+ }
336
+ .localAuthWelcomeForm .localAuthStatus {
337
+ width: min(470px, calc(100% - 24px));
338
+ justify-self: center;
339
+ text-align: left;
340
+ }
341
+ .localAuthOtpWelcome {
342
+ width: min(620px, 100%);
343
+ gap: 18px;
344
+ }
345
+ .localAuthOtpWelcome .localAuthOrbScene {
346
+ margin-bottom: -4px;
347
+ }
348
+ .localAuthOtpWelcome .localAuthWelcomeCopy {
349
+ gap: 10px;
350
+ }
351
+ .localAuthOtpWelcome .localAuthWelcomeCopy p strong {
352
+ color: var(--echo-ink-text);
353
+ font-family: var(--echo-font-mono);
354
+ font-weight: 700;
355
+ }
356
+ .localAuthOtpForm {
357
+ width: min(540px, 100%);
358
+ gap: 14px;
359
+ }
360
+ .localAuthOtpCells {
361
+ display: grid;
362
+ grid-template-columns: repeat(6, minmax(42px, 1fr));
363
+ gap: 10px;
364
+ width: 100%;
365
+ }
366
+ .localAuthOtpCells input {
367
+ width: 100%;
368
+ aspect-ratio: 1 / 0.82;
369
+ min-height: 58px;
370
+ border: 1.5px solid rgba(78,78,75,0.18);
371
+ border-radius: 8px;
372
+ background: rgba(255,255,255,0.95);
373
+ box-shadow: 0 3px 8px rgba(50,48,43,0.10);
374
+ color: var(--echo-ink-text);
375
+ font-family: var(--echo-font-body);
376
+ font-size: clamp(26px, 4vw, 34px);
377
+ font-weight: 650;
378
+ line-height: 1;
379
+ text-align: center;
380
+ outline: none;
381
+ transition: border-color 160ms ease, box-shadow 160ms ease;
382
+ }
383
+ .localAuthOtpCells input:focus {
384
+ border-color: rgba(26,58,143,0.72);
385
+ box-shadow: 0 0 0 4px rgba(26,58,143,0.14), 0 3px 8px rgba(50,48,43,0.10);
386
+ }
387
+ .localAuthVerifyWide,
388
+ .localAuthBackWide {
389
+ width: 100%;
390
+ min-height: 56px;
391
+ border-radius: 999px;
392
+ justify-content: center;
393
+ }
394
+ .localAuthOtpForm .localAuthStatus {
395
+ width: min(390px, 100%);
396
+ justify-self: center;
397
+ color: var(--echo-ink-primary);
398
+ font-family: var(--echo-font-mono);
399
+ font-size: 14px;
400
+ line-height: 1.45;
401
+ text-align: center;
402
+ }
403
+ .localAuthOtpForm .localAuthStatus.is-error {
404
+ color: #c7372f;
405
+ }
149
406
  .localAuthCard {
150
407
  width: min(680px, 100%);
151
408
  border: 1.5px solid rgba(26,58,143,0.16);
@@ -262,6 +519,49 @@ export const SETUP_PAGE_STYLES_EXTRACTION = String.raw ` /* ---- dashboard (p
262
519
  color: #8b2c26;
263
520
  font-weight: 650;
264
521
  }
522
+ @media (max-width: 560px) {
523
+ .localAuthWelcomeStage {
524
+ min-height: calc(100vh - 56px);
525
+ }
526
+ .localAuthWelcome {
527
+ gap: 18px;
528
+ }
529
+ .localAuthOrbScene {
530
+ width: 88px;
531
+ height: 112px;
532
+ }
533
+ .localAuthOrb {
534
+ width: 58px;
535
+ height: 58px;
536
+ }
537
+ .localAuthOrb::before,
538
+ .localAuthOrb::after {
539
+ top: 25px;
540
+ }
541
+ .localAuthOrb::before { left: 21px; }
542
+ .localAuthOrb::after { right: 21px; }
543
+ .localAuthWelcomeCopy h2 {
544
+ font-size: 44px;
545
+ }
546
+ .localAuthEmailCapsule {
547
+ min-height: 58px;
548
+ grid-template-columns: minmax(0, 1fr) 50px;
549
+ padding-left: 18px;
550
+ }
551
+ .localAuthEmailCapsule button {
552
+ width: 46px;
553
+ height: 46px;
554
+ }
555
+ .localAuthConsent {
556
+ width: calc(100% - 16px);
557
+ }
558
+ }
559
+ @media (prefers-reduced-motion: reduce) {
560
+ .localAuthOrb,
561
+ .localAuthOrbShadow {
562
+ animation: none;
563
+ }
564
+ }
265
565
  .track { height: 12px; overflow: hidden; border-radius: 999px; background: #e8e8e1; margin: 16px 0 10px; }
266
566
  .fill { height: 100%; width: 0%; background: var(--echo-ink-primary); transition: width 180ms ease; }
267
567
  /* indeterminate "we're working on it" bar for the prep phase (before job counts move) */
@@ -1789,6 +2089,36 @@ export const SETUP_PAGE_STYLES_EXTRACTION = String.raw ` /* ---- dashboard (p
1789
2089
  max-width: 650px;
1790
2090
  margin: 14px 0 0;
1791
2091
  }
2092
+ .planGateStage {
2093
+ width: 100%;
2094
+ }
2095
+ .planGateDecision {
2096
+ text-align: left;
2097
+ }
2098
+ .planGateHero {
2099
+ display: grid;
2100
+ grid-template-columns: 94px minmax(0, 1fr);
2101
+ align-items: center;
2102
+ gap: clamp(20px, 3vw, 34px);
2103
+ border-bottom: 1px solid #d7d8d3;
2104
+ padding: clamp(26px, 4vw, 42px) clamp(22px, 4vw, 34px);
2105
+ }
2106
+ .planGateHero h2 {
2107
+ max-width: 760px;
2108
+ margin: 0;
2109
+ font-size: clamp(38px, 5vw, 60px);
2110
+ line-height: 0.96;
2111
+ }
2112
+ .planGateHero p {
2113
+ max-width: 680px;
2114
+ margin: 14px 0 0;
2115
+ color: var(--echo-ink-mute);
2116
+ font-size: 16px;
2117
+ line-height: 1.5;
2118
+ }
2119
+ .planGateAccount {
2120
+ border-top: 1px solid #d7d8d3;
2121
+ }
1792
2122
  .proofStrip {
1793
2123
  width: 100%;
1794
2124
  justify-content: flex-start;