@echomem/mcp 1.4.8 → 1.4.9
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.
- package/README.md +23 -3
- package/assets/canonical-scorer/README.md +18 -0
- package/assets/canonical-scorer/analyze-10-problems.mjs +857 -0
- package/assets/canonical-scorer/build-session-waste-dashboard.mjs +1628 -0
- package/assets/canonical-scorer/golden_anchors.mjs +83 -0
- package/assets/canonical-scorer/optimizable_detail.mjs +633 -0
- package/dist/city/chaos-to-clarity-pencil.html +582 -0
- package/dist/city/echo-ai-city-only.html +1104 -105
- package/dist/city/echo-ai-city-only.template.html +1104 -105
- package/dist/city/pencil-pie-generator.html +883 -0
- package/dist/city/pencil-webgl-landscape.html +1239 -0
- package/dist/city/spatial-fan-story.html +479 -0
- package/dist/codex-session-files.js +283 -0
- package/dist/codex-sync.js +7 -2
- package/dist/context-analysis/canonical-golden.js +47 -0
- package/dist/context-analysis/claude-native-canonical.js +1193 -0
- package/dist/context-analysis/vendored-canonical.js +793 -0
- package/dist/context-analysis/workspace-report.js +1838 -0
- package/dist/context-metrics/calculate.js +56 -0
- package/dist/context-metrics/model-limits.js +26 -0
- package/dist/context-metrics/types.js +1 -0
- package/dist/forensics-10-problems.js +7 -6
- package/dist/forensics.js +863 -132
- package/dist/hud/adapters.js +8 -4
- package/dist/hud/metric.js +13 -4
- package/dist/hud/monitor.js +135 -16
- package/dist/hud/web.js +344 -298
- package/dist/index.js +7 -3
- package/dist/local-data-paths.js +87 -0
- package/dist/migrate.js +37 -29
- package/dist/report.js +101 -40
- package/dist/setup-page.js +3290 -196
- package/dist/setup-preview.js +245 -0
- package/dist/setup.js +432 -34
- package/package.json +5 -4
- package/templates/echomem-recall.md +2 -2
package/dist/setup-page.js
CHANGED
|
@@ -1,10 +1,44 @@
|
|
|
1
|
-
|
|
1
|
+
import { statSync } from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { renderSetupPreviewBootstrap } from "./setup-preview.js";
|
|
4
|
+
// Temporary local iteration marker. Derived from this compiled module's mtime so every rebuild
|
|
5
|
+
// updates the on-page stamp automatically — no manual edits.
|
|
6
|
+
const SETUP_PAGE_WORKSTREAM_UPDATED_AT = (() => {
|
|
7
|
+
try {
|
|
8
|
+
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
9
|
+
timeZone: "America/Los_Angeles",
|
|
10
|
+
year: "numeric",
|
|
11
|
+
month: "2-digit",
|
|
12
|
+
day: "2-digit",
|
|
13
|
+
hour: "2-digit",
|
|
14
|
+
minute: "2-digit",
|
|
15
|
+
hour12: false,
|
|
16
|
+
timeZoneName: "short",
|
|
17
|
+
}).formatToParts(statSync(fileURLToPath(import.meta.url)).mtime);
|
|
18
|
+
const get = (type) => parts.find((p) => p.type === type)?.value ?? "";
|
|
19
|
+
return `${get("year")}-${get("month")}-${get("day")} ${get("hour")}:${get("minute")} ${get("timeZoneName")}`;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return "local build";
|
|
23
|
+
}
|
|
24
|
+
})();
|
|
25
|
+
export function renderSetupPage(options = {}) {
|
|
26
|
+
const previewState = options.previewState ?? null;
|
|
27
|
+
const startupScript = previewState
|
|
28
|
+
? renderSetupPreviewBootstrap(previewState)
|
|
29
|
+
: `
|
|
30
|
+
init().catch(function () {
|
|
31
|
+
renderReportIssue("SETUP_INITIALIZATION_FAILED", "The local setup page could not start. Close this tab and run echomem-mcp init again.");
|
|
32
|
+
});`;
|
|
2
33
|
return `<!doctype html>
|
|
3
34
|
<html lang="en">
|
|
4
35
|
<head>
|
|
5
36
|
<meta charset="utf-8" />
|
|
6
37
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7
38
|
<title>EchoMem Device Setup</title>
|
|
39
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
40
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
41
|
+
<link href="https://fonts.googleapis.com/css2?family=Caveat:wght@500;700&display=swap" rel="stylesheet" />
|
|
8
42
|
<style>
|
|
9
43
|
:root {
|
|
10
44
|
color-scheme: light;
|
|
@@ -115,14 +149,14 @@ export function renderSetupPage() {
|
|
|
115
149
|
text-align: center;
|
|
116
150
|
}
|
|
117
151
|
body.cityMode {
|
|
118
|
-
background:
|
|
152
|
+
background: var(--echo-paper-canvas);
|
|
119
153
|
}
|
|
120
154
|
body.cityMode main {
|
|
121
155
|
width: 100%;
|
|
122
156
|
padding: 0;
|
|
123
157
|
margin: 0;
|
|
124
158
|
}
|
|
125
|
-
body.cityMode header {
|
|
159
|
+
body.cityMode main > header {
|
|
126
160
|
display: none;
|
|
127
161
|
}
|
|
128
162
|
body.extractMode header {
|
|
@@ -144,6 +178,9 @@ export function renderSetupPage() {
|
|
|
144
178
|
justify-items: center;
|
|
145
179
|
padding: clamp(24px, 5vw, 56px);
|
|
146
180
|
}
|
|
181
|
+
body.readyMode {
|
|
182
|
+
background: #f1f1ef;
|
|
183
|
+
}
|
|
147
184
|
body.cityMode #app {
|
|
148
185
|
display: block;
|
|
149
186
|
min-height: 100vh;
|
|
@@ -174,19 +211,420 @@ export function renderSetupPage() {
|
|
|
174
211
|
--city-display-w: min(calc(100vw - 28px), calc((100vh - 28px) * 0.7797));
|
|
175
212
|
--city-display-h: min(calc(100vh - 28px), calc((100vw - 28px) * 1.2826));
|
|
176
213
|
position: relative;
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
padding: 0;
|
|
180
|
-
|
|
181
|
-
|
|
214
|
+
width: min(880px, 100%);
|
|
215
|
+
margin: clamp(-36px, -2vw, -16px) auto 0;
|
|
216
|
+
padding: 4px 0 0;
|
|
217
|
+
background: transparent;
|
|
218
|
+
}
|
|
219
|
+
.cityFigureRow {
|
|
220
|
+
/* Mirror the body-text column geometry (.context-audit): same 880px column,
|
|
221
|
+
same inner padding, same half-gutter nudge — so cityNote's left edge sits
|
|
222
|
+
exactly on the body text's left line, and cityFig's column starts exactly
|
|
223
|
+
on the body text's right line, then bleeds to the viewport's right edge. */
|
|
224
|
+
--hero-pad: clamp(22px, 4.5vw, 64px);
|
|
225
|
+
--audit-gutter: calc(150px + clamp(16px, 2.5vw, 36px));
|
|
226
|
+
display: grid;
|
|
227
|
+
/* City column width is chosen so its CENTER sits on the pie-chart axis below
|
|
228
|
+
(pie column center = 100vw − (100vw − 880px)/2 − pad + gutter/2 − 75px) while its
|
|
229
|
+
right edge stays glued to the viewport edge. The camera centers the city in the
|
|
230
|
+
canvas, so the default view lands exactly on that shared vertical axis. */
|
|
231
|
+
grid-template-columns: minmax(0, 1fr) calc(100vw - min(880px, 100vw) + 2 * var(--hero-pad) - var(--audit-gutter) + 150px);
|
|
232
|
+
gap: 0;
|
|
233
|
+
align-items: center;
|
|
234
|
+
margin-left: calc(var(--hero-pad) - var(--audit-gutter) / 2);
|
|
235
|
+
width: calc(50vw + 50% - var(--hero-pad) + var(--audit-gutter) / 2);
|
|
236
|
+
}
|
|
237
|
+
.cityFig { margin: 0; order: 2; position: relative; }
|
|
238
|
+
.cityNote { order: 1; }
|
|
239
|
+
/* ---- fullscreen city modal (80% viewport): dirty/clean toggle + tool legend + forensic stats ---- */
|
|
240
|
+
.cityFullBtn {
|
|
241
|
+
position: absolute;
|
|
242
|
+
z-index: 6;
|
|
243
|
+
left: 50%;
|
|
244
|
+
transform: translateX(-50%);
|
|
245
|
+
bottom: clamp(16px, 2.5vh, 30px);
|
|
246
|
+
display: inline-flex;
|
|
247
|
+
align-items: center;
|
|
248
|
+
gap: 8px;
|
|
249
|
+
padding: 10px 15px;
|
|
250
|
+
border: 1px solid rgba(26, 58, 143, 0.22);
|
|
251
|
+
border-radius: 999px;
|
|
252
|
+
background: rgba(255, 255, 255, 0.94);
|
|
253
|
+
color: var(--echo-ink-primary);
|
|
254
|
+
font-family: var(--echo-font-brand);
|
|
255
|
+
font-size: 12.5px;
|
|
256
|
+
font-weight: 700;
|
|
257
|
+
line-height: 1;
|
|
258
|
+
cursor: pointer;
|
|
259
|
+
box-shadow: 0 10px 28px rgba(28, 38, 60, 0.12);
|
|
260
|
+
}
|
|
261
|
+
.cityFullBtn:hover { background: #ffffff; }
|
|
262
|
+
.cityFullBtn svg { width: 12px; height: 12px; display: block; }
|
|
263
|
+
body.cityFull .cityFullBtn { display: none; }
|
|
264
|
+
.cityFullBackdrop { position: fixed; inset: 0; z-index: 58; background: rgba(26, 26, 26, 0.45); }
|
|
265
|
+
body.cityFull .cityShell {
|
|
266
|
+
position: fixed;
|
|
267
|
+
inset: 10vh 10vw;
|
|
268
|
+
width: auto;
|
|
269
|
+
height: auto;
|
|
270
|
+
z-index: 59;
|
|
271
|
+
background: var(--echo-paper-canvas);
|
|
272
|
+
border: 1px solid var(--echo-line-soft);
|
|
273
|
+
border-radius: 18px;
|
|
274
|
+
box-shadow: 0 40px 120px rgba(20, 24, 40, 0.4);
|
|
275
|
+
-webkit-mask-image: none;
|
|
276
|
+
mask-image: none;
|
|
277
|
+
}
|
|
278
|
+
.cityFullChrome {
|
|
279
|
+
position: fixed;
|
|
280
|
+
inset: 10vh 10vw;
|
|
281
|
+
z-index: 60;
|
|
282
|
+
display: flex;
|
|
283
|
+
flex-direction: column;
|
|
284
|
+
justify-content: space-between;
|
|
285
|
+
pointer-events: none;
|
|
286
|
+
padding: 16px 18px;
|
|
287
|
+
box-sizing: border-box;
|
|
288
|
+
}
|
|
289
|
+
.cityFullChrome[hidden], .cityFullBackdrop[hidden] { display: none; }
|
|
290
|
+
.cfc-bar { display: flex; align-items: center; justify-content: space-between; gap: 14px; }
|
|
291
|
+
.cfc-left { display: flex; align-items: center; gap: 10px; }
|
|
292
|
+
/* Quiet toggles — same language as the top-nav pill: white pill, hairline border,
|
|
293
|
+
the active chip gets only a thin ink ring. No saturated fills. */
|
|
294
|
+
.cfc-toggle {
|
|
295
|
+
pointer-events: auto;
|
|
296
|
+
display: inline-flex;
|
|
297
|
+
align-items: center;
|
|
298
|
+
gap: 2px;
|
|
299
|
+
padding: 5px;
|
|
300
|
+
background: var(--echo-paper-white);
|
|
301
|
+
border: 1px solid rgba(26, 26, 26, 0.06);
|
|
302
|
+
border-radius: 999px;
|
|
303
|
+
box-shadow: 0 12px 30px rgba(28, 38, 60, 0.10);
|
|
304
|
+
}
|
|
305
|
+
.cfc-toggle button {
|
|
306
|
+
appearance: none;
|
|
307
|
+
display: inline-flex;
|
|
308
|
+
align-items: center;
|
|
309
|
+
gap: 7px;
|
|
310
|
+
border: 1px solid transparent;
|
|
311
|
+
border-radius: 999px;
|
|
312
|
+
padding: 8px 14px;
|
|
313
|
+
background: transparent;
|
|
314
|
+
font-family: var(--echo-font-brand);
|
|
315
|
+
font-size: 12.5px;
|
|
316
|
+
font-weight: 600;
|
|
317
|
+
line-height: 1;
|
|
318
|
+
color: var(--echo-ink-mute);
|
|
319
|
+
cursor: pointer;
|
|
320
|
+
transition: color 120ms linear, border-color 120ms linear;
|
|
321
|
+
}
|
|
322
|
+
.cfc-toggle button:hover { color: #1a1a1a; }
|
|
323
|
+
.cfc-toggle button.on { color: #1a1a1a; font-weight: 700; border-color: rgba(26, 26, 26, 0.16); }
|
|
324
|
+
.cfc-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; background: #c7372f; }
|
|
325
|
+
.cfc-face { width: 18px; height: 18px; border-radius: 50%; object-fit: cover; flex: none; }
|
|
326
|
+
.cfc-legend {
|
|
327
|
+
pointer-events: auto;
|
|
328
|
+
display: inline-flex;
|
|
329
|
+
align-items: center;
|
|
330
|
+
gap: 20px;
|
|
331
|
+
padding: 10px 18px;
|
|
332
|
+
background: #ffffff;
|
|
333
|
+
border: 1px solid var(--echo-line-soft);
|
|
334
|
+
border-radius: 999px;
|
|
335
|
+
box-shadow: 0 10px 26px rgba(28, 38, 60, 0.12);
|
|
336
|
+
font-family: var(--echo-font-mono);
|
|
337
|
+
font-size: 12px;
|
|
338
|
+
color: var(--echo-ink-text);
|
|
339
|
+
white-space: nowrap;
|
|
340
|
+
}
|
|
341
|
+
.cfc-legend .cfc-side { display: inline-flex; align-items: center; gap: 8px; }
|
|
342
|
+
.cfc-legend .cfc-swatch { width: 11px; height: 11px; border-radius: 3px; flex: none; }
|
|
343
|
+
.cfc-legend img { width: 18px; height: 18px; object-fit: contain; }
|
|
344
|
+
.cfc-legend b { font-size: 14.5px; font-variant-numeric: tabular-nums; }
|
|
345
|
+
.cfc-close {
|
|
346
|
+
pointer-events: auto;
|
|
347
|
+
width: 42px;
|
|
348
|
+
height: 42px;
|
|
349
|
+
flex: none;
|
|
350
|
+
border: 1px solid var(--echo-line-soft);
|
|
351
|
+
border-radius: 999px;
|
|
352
|
+
background: #ffffff;
|
|
353
|
+
color: #1a1a1a;
|
|
354
|
+
font-size: 20px;
|
|
355
|
+
line-height: 1;
|
|
356
|
+
cursor: pointer;
|
|
357
|
+
box-shadow: 0 10px 26px rgba(28, 38, 60, 0.12);
|
|
182
358
|
}
|
|
359
|
+
.cfc-close:hover { background: var(--echo-paper-soft); }
|
|
360
|
+
.cfc-stats {
|
|
361
|
+
pointer-events: auto;
|
|
362
|
+
align-self: center;
|
|
363
|
+
display: flex;
|
|
364
|
+
flex-wrap: wrap;
|
|
365
|
+
justify-content: center;
|
|
366
|
+
gap: 10px 30px;
|
|
367
|
+
padding: 13px 26px;
|
|
368
|
+
background: rgba(255, 255, 255, 0.95);
|
|
369
|
+
border: 1px solid var(--echo-line-soft);
|
|
370
|
+
border-radius: 14px;
|
|
371
|
+
box-shadow: 0 14px 40px rgba(28, 38, 60, 0.14);
|
|
372
|
+
}
|
|
373
|
+
.cfc-stat { display: grid; gap: 5px; justify-items: center; }
|
|
374
|
+
.cfc-stat span { font-family: var(--echo-font-mono); font-size: 9px; font-weight: 700; letter-spacing: 0.07em; text-transform: uppercase; color: var(--echo-ink-mute); }
|
|
375
|
+
.cfc-stat strong { font-family: var(--echo-font-mono); font-size: 15px; line-height: 1; color: var(--echo-ink-text); font-variant-numeric: tabular-nums; }
|
|
183
376
|
.cityShell {
|
|
184
377
|
position: relative;
|
|
185
|
-
width:
|
|
186
|
-
height:
|
|
378
|
+
width: 100%;
|
|
379
|
+
height: clamp(400px, 52vh, 560px);
|
|
187
380
|
border-radius: 0;
|
|
188
381
|
overflow: hidden;
|
|
382
|
+
/* Fade the LEFT edge (toward the text) and the BOTTOM edge (toward the report);
|
|
383
|
+
top and right run clean to the viewport edge. Two mask layers intersected. */
|
|
384
|
+
-webkit-mask-image:
|
|
385
|
+
linear-gradient(to right, rgba(0, 0, 0, 0) 0%, #000 16%, #000 100%),
|
|
386
|
+
linear-gradient(to top, rgba(0, 0, 0, 0) 0%, #000 16%, #000 100%);
|
|
387
|
+
-webkit-mask-composite: source-in;
|
|
388
|
+
mask-image:
|
|
389
|
+
linear-gradient(to right, rgba(0, 0, 0, 0) 0%, #000 16%, #000 100%),
|
|
390
|
+
linear-gradient(to top, rgba(0, 0, 0, 0) 0%, #000 16%, #000 100%);
|
|
391
|
+
mask-composite: intersect;
|
|
392
|
+
}
|
|
393
|
+
.reportTop {
|
|
394
|
+
position: sticky;
|
|
395
|
+
top: 0;
|
|
396
|
+
z-index: 40;
|
|
397
|
+
display: grid;
|
|
398
|
+
grid-template-columns: 1fr auto 1fr;
|
|
399
|
+
align-items: center;
|
|
400
|
+
gap: 16px;
|
|
401
|
+
width: 100%;
|
|
402
|
+
margin: 0 auto;
|
|
403
|
+
padding: 12px clamp(24px, 4vw, 56px);
|
|
404
|
+
box-sizing: border-box;
|
|
405
|
+
background: var(--echo-paper-canvas);
|
|
406
|
+
border-bottom: 0;
|
|
407
|
+
margin-bottom: 0;
|
|
408
|
+
}
|
|
409
|
+
/* Yeah! Echo wordmark — ported 1:1 from WebPageReactVersion YeahEchoMark.module.css
|
|
410
|
+
(default lockup: bubble left, tail points right toward "Echo"). */
|
|
411
|
+
.yeahLogo {
|
|
412
|
+
position: relative;
|
|
413
|
+
display: inline-flex;
|
|
414
|
+
align-items: center;
|
|
415
|
+
gap: 0.5em;
|
|
416
|
+
justify-self: start;
|
|
417
|
+
font-size: 27px;
|
|
418
|
+
line-height: 1;
|
|
419
|
+
color: var(--echo-ink-primary);
|
|
420
|
+
}
|
|
421
|
+
.yeahLogo i {
|
|
422
|
+
position: relative;
|
|
423
|
+
font-family: "Caveat", cursive;
|
|
424
|
+
font-style: normal;
|
|
425
|
+
font-weight: 700;
|
|
426
|
+
font-size: 0.55em;
|
|
427
|
+
line-height: 1;
|
|
428
|
+
color: var(--echo-ink-primary);
|
|
429
|
+
background: var(--echo-paper-white);
|
|
430
|
+
border: 0.05em solid var(--echo-ink-primary);
|
|
431
|
+
padding: 0.06em 0.55em 0.14em;
|
|
432
|
+
border-radius: 999px;
|
|
433
|
+
box-shadow: 0 0.1em 0.16em rgba(26, 58, 143, 0.10);
|
|
434
|
+
}
|
|
435
|
+
.yeahLogo i::before,
|
|
436
|
+
.yeahLogo i::after { content: ""; position: absolute; width: 0; height: 0; top: 50%; }
|
|
437
|
+
.yeahLogo i::before {
|
|
438
|
+
right: -0.25em;
|
|
439
|
+
transform: translateY(-50%);
|
|
440
|
+
border-top: 0.24em solid transparent;
|
|
441
|
+
border-bottom: 0.24em solid transparent;
|
|
442
|
+
border-left: 0.32em solid var(--echo-ink-primary);
|
|
443
|
+
}
|
|
444
|
+
.yeahLogo i::after {
|
|
445
|
+
right: -0.14em;
|
|
446
|
+
transform: translateY(-50%);
|
|
447
|
+
border-top: 0.2em solid transparent;
|
|
448
|
+
border-bottom: 0.2em solid transparent;
|
|
449
|
+
border-left: 0.28em solid var(--echo-paper-white);
|
|
450
|
+
}
|
|
451
|
+
.yeahLogo b { font-family: var(--echo-font-brand); font-weight: 700; letter-spacing: -0.02em; font-size: 1em; line-height: 1; color: var(--echo-ink-primary); }
|
|
452
|
+
/* Center nav — same pill language as the website header lockup. */
|
|
453
|
+
.navCenter { justify-self: center; display: inline-flex; align-items: center; gap: 10px; }
|
|
454
|
+
.navPill {
|
|
455
|
+
display: inline-flex;
|
|
456
|
+
align-items: center;
|
|
457
|
+
gap: 4px;
|
|
458
|
+
padding: 5px;
|
|
459
|
+
background: var(--echo-paper-white);
|
|
460
|
+
border: 1px solid rgba(26, 26, 26, 0.06);
|
|
461
|
+
border-radius: 999px;
|
|
462
|
+
box-shadow: 0 12px 30px rgba(28, 38, 60, 0.10);
|
|
463
|
+
}
|
|
464
|
+
.navTitle {
|
|
465
|
+
display: inline-flex;
|
|
466
|
+
align-items: center;
|
|
467
|
+
gap: 8px;
|
|
468
|
+
font-family: var(--echo-font-brand);
|
|
469
|
+
font-size: 13px;
|
|
470
|
+
font-weight: 700;
|
|
471
|
+
line-height: 1;
|
|
472
|
+
white-space: nowrap;
|
|
473
|
+
color: #1a1a1a;
|
|
474
|
+
padding: 6px 8px 6px 10px;
|
|
475
|
+
}
|
|
476
|
+
.navTitleFace { width: 20px; height: 20px; border-radius: 50%; object-fit: cover; flex: none; }
|
|
477
|
+
.navDates { display: inline-flex; align-items: center; gap: 8px; padding-right: 4px; }
|
|
478
|
+
.navDates:empty { display: none; }
|
|
479
|
+
/* Mini timeline axis between the two dates — same language as the story
|
|
480
|
+
timeline: thin slate line, round endpoints, NOW end in warm coral. */
|
|
481
|
+
.navAxis {
|
|
482
|
+
position: relative;
|
|
483
|
+
width: 72px;
|
|
484
|
+
height: 2px;
|
|
485
|
+
border-radius: 999px;
|
|
486
|
+
background: #a9aec9;
|
|
487
|
+
}
|
|
488
|
+
.navAxis::before,
|
|
489
|
+
.navAxis::after {
|
|
490
|
+
content: "";
|
|
491
|
+
position: absolute;
|
|
492
|
+
top: 50%;
|
|
493
|
+
transform: translateY(-50%);
|
|
494
|
+
width: 6px;
|
|
495
|
+
height: 6px;
|
|
496
|
+
border-radius: 50%;
|
|
497
|
+
}
|
|
498
|
+
.navAxis::before { left: -1px; background: #a9aec9; }
|
|
499
|
+
.navAxis::after { right: -1px; background: #e06b52; }
|
|
500
|
+
.navDates em.isNow { border-color: #e06b52; color: #b94a35; background: #fdf3f0; }
|
|
501
|
+
.navDates em {
|
|
502
|
+
font-style: normal;
|
|
503
|
+
font-family: var(--echo-font-mono);
|
|
504
|
+
font-size: 10.5px;
|
|
505
|
+
font-weight: 700;
|
|
506
|
+
letter-spacing: 0.04em;
|
|
507
|
+
text-transform: uppercase;
|
|
508
|
+
line-height: 1;
|
|
509
|
+
white-space: nowrap;
|
|
510
|
+
color: #3a3a36;
|
|
511
|
+
background: #f7f7f5;
|
|
512
|
+
border: 1px solid #9b9b97;
|
|
513
|
+
border-radius: 999px;
|
|
514
|
+
padding: 7px 11px;
|
|
515
|
+
}
|
|
516
|
+
.navDates i { font-style: normal; font-family: var(--echo-font-mono); font-size: 11px; font-weight: 700; color: var(--echo-ink-faint); }
|
|
517
|
+
.navDot { width: 7px; height: 7px; border-radius: 50%; background: var(--echo-ink-accent); flex-shrink: 0; animation: navDotPulse 1.6s ease-in-out infinite; }
|
|
518
|
+
@keyframes navDotPulse {
|
|
519
|
+
0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(78, 205, 196, 0.55); }
|
|
520
|
+
50% { opacity: 0.35; box-shadow: 0 0 0 5px rgba(78, 205, 196, 0); }
|
|
521
|
+
}
|
|
522
|
+
.navLocal {
|
|
523
|
+
display: inline-flex;
|
|
524
|
+
align-items: center;
|
|
525
|
+
gap: 7px;
|
|
526
|
+
font-family: var(--echo-font-brand);
|
|
527
|
+
font-size: 12px;
|
|
528
|
+
font-weight: 700;
|
|
529
|
+
line-height: 1;
|
|
530
|
+
white-space: nowrap;
|
|
531
|
+
color: #1a1a1a;
|
|
532
|
+
background: var(--echo-paper-white);
|
|
533
|
+
border: 1px solid rgba(26, 26, 26, 0.08);
|
|
534
|
+
border-radius: 999px;
|
|
535
|
+
padding: 9px 14px;
|
|
536
|
+
box-shadow: 0 8px 22px rgba(28, 38, 60, 0.08);
|
|
189
537
|
}
|
|
538
|
+
.navLocal svg { width: 12px; height: 12px; display: block; color: var(--echo-ink-mute); }
|
|
539
|
+
.navConnect {
|
|
540
|
+
justify-self: end;
|
|
541
|
+
display: inline-flex;
|
|
542
|
+
align-items: center;
|
|
543
|
+
gap: 8px;
|
|
544
|
+
font-family: var(--echo-font-brand);
|
|
545
|
+
font-size: 13px;
|
|
546
|
+
font-weight: 700;
|
|
547
|
+
line-height: 1;
|
|
548
|
+
white-space: nowrap;
|
|
549
|
+
color: var(--echo-ink-accent-deep);
|
|
550
|
+
background: rgba(78, 205, 196, 0.16);
|
|
551
|
+
border: 1px solid rgba(78, 205, 196, 0.5);
|
|
552
|
+
border-radius: 999px;
|
|
553
|
+
padding: 12px 16px;
|
|
554
|
+
cursor: pointer;
|
|
555
|
+
transition: background 140ms linear;
|
|
556
|
+
}
|
|
557
|
+
.navConnect:hover { background: rgba(78, 205, 196, 0.28); }
|
|
558
|
+
.navConnect svg { width: 11px; height: 11px; display: block; }
|
|
559
|
+
.cityNote { display: grid; gap: 8px; align-content: center; justify-items: start; color: #242424; }
|
|
560
|
+
/* Echo speaks the greeting: mascot on the left, manga speech bubble to his right
|
|
561
|
+
(same bubble language as the Yeah! Echo wordmark — paper white, navy ink, left tail). */
|
|
562
|
+
.cityNote .fn-speak { display: flex; align-items: center; gap: 16px; margin-bottom: 4px; }
|
|
563
|
+
.cityNote img { width: 108px; height: auto; margin-top: -24px; flex: none; }
|
|
564
|
+
.cityNote .fn-bubble {
|
|
565
|
+
position: relative;
|
|
566
|
+
margin-top: -52px;
|
|
567
|
+
background: var(--echo-paper-white);
|
|
568
|
+
border: 1.6px solid #1a3a8f;
|
|
569
|
+
border-radius: 18px;
|
|
570
|
+
padding: 12px 20px 14px;
|
|
571
|
+
box-shadow: 0 3px 6px rgba(26, 58, 143, 0.10);
|
|
572
|
+
}
|
|
573
|
+
.cityNote .fn-bubble::before,
|
|
574
|
+
.cityNote .fn-bubble::after { content: ""; position: absolute; top: 62%; width: 0; height: 0; transform: translateY(-50%); }
|
|
575
|
+
.cityNote .fn-bubble::before {
|
|
576
|
+
left: -12px;
|
|
577
|
+
border-top: 8px solid transparent;
|
|
578
|
+
border-bottom: 8px solid transparent;
|
|
579
|
+
border-right: 12px solid #1a3a8f;
|
|
580
|
+
}
|
|
581
|
+
.cityNote .fn-bubble::after {
|
|
582
|
+
left: -9px;
|
|
583
|
+
border-top: 6.5px solid transparent;
|
|
584
|
+
border-bottom: 6.5px solid transparent;
|
|
585
|
+
border-right: 10px solid var(--echo-paper-white);
|
|
586
|
+
}
|
|
587
|
+
.cityNote h2 { margin: 0; font-family: var(--echo-font-brand); font-size: clamp(24px, 2.6vw, 32px); font-weight: 900; letter-spacing: -0.02em; line-height: 1; color: #171717; text-transform: lowercase; }
|
|
588
|
+
.cityNote .fn-save { margin: 9px 0 2px; max-width: 34ch; font-family: var(--echo-font-brand); font-size: 16px; font-weight: 600; line-height: 1.5; color: #242424; }
|
|
589
|
+
.cityNote .fn-save .fn-num,
|
|
590
|
+
.cityNote .fn-save .fn-less { font-weight: 800; color: #c7372f; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
591
|
+
.cityNote h2 b { color: #1a3a8f; }
|
|
592
|
+
.cityNote .fn-dot { color: #f4a83b; }
|
|
593
|
+
.cityNote .fn-rank { margin: 0; font-family: var(--echo-font-brand); font-size: 15px; color: #666666; }
|
|
594
|
+
.cityNote .fn-rank b { font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: #242424; }
|
|
595
|
+
.fn-info {
|
|
596
|
+
/* beat the global button { min-height:44px; padding:0 16px } rule */
|
|
597
|
+
min-height: 0;
|
|
598
|
+
width: 17px;
|
|
599
|
+
height: 17px;
|
|
600
|
+
margin-left: 8px;
|
|
601
|
+
padding: 0;
|
|
602
|
+
display: inline-grid;
|
|
603
|
+
place-items: center;
|
|
604
|
+
border: 1.2px solid rgba(26, 26, 26, 0.35);
|
|
605
|
+
border-radius: 50%;
|
|
606
|
+
background: transparent;
|
|
607
|
+
color: rgba(26, 26, 26, 0.55);
|
|
608
|
+
font-family: Georgia, "Times New Roman", serif;
|
|
609
|
+
font-size: 11px;
|
|
610
|
+
font-weight: 700;
|
|
611
|
+
font-style: italic;
|
|
612
|
+
line-height: 1;
|
|
613
|
+
vertical-align: 2px;
|
|
614
|
+
cursor: pointer;
|
|
615
|
+
transition: color 120ms linear, border-color 120ms linear;
|
|
616
|
+
}
|
|
617
|
+
.fn-info:hover { color: #1a1a1a; border-color: #1a1a1a; }
|
|
618
|
+
.fn-guide-sec { margin: 16px 0 7px; font-family: var(--echo-font-mono); font-size: 10px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--echo-ink-mute); }
|
|
619
|
+
.fn-guide-sec:first-child { margin-top: 0; }
|
|
620
|
+
.fn-guide-row { margin: 0 0 7px; font-size: 13px; line-height: 1.5; color: var(--echo-ink-text); }
|
|
621
|
+
.fn-guide-row b { color: var(--echo-ink-primary); }
|
|
622
|
+
.fn-guide-row span { color: var(--echo-ink-mute); }
|
|
623
|
+
.fn-guide-row.is-you { background: var(--echo-paper-soft); border-radius: 8px; padding: 6px 10px; margin-left: -10px; }
|
|
624
|
+
.fn-guide-row.is-you i { display: inline-block; margin-left: 7px; padding: 2px 7px 3px; border-radius: 999px; background: #1a3a8f; color: #fff; font-style: normal; font-family: var(--echo-font-mono); font-size: 9px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; vertical-align: 1px; }
|
|
625
|
+
.echo-note-dlg .nb { max-height: min(60vh, 620px); overflow-y: auto; }
|
|
626
|
+
.cityNote .fn-lede { margin: 6px 0 0; max-width: 46ch; font-family: var(--echo-font-brand); font-size: 18px; line-height: 1.6; color: #242424; }
|
|
627
|
+
.cityNote .fn-lede mark { background: linear-gradient(transparent 62%, rgba(244, 168, 59, 0.45) 0); color: inherit; font-weight: 700; }
|
|
190
628
|
.cityShell iframe {
|
|
191
629
|
width: 100%;
|
|
192
630
|
height: 100%;
|
|
@@ -215,8 +653,8 @@ export function renderSetupPage() {
|
|
|
215
653
|
.detailsWrap {
|
|
216
654
|
width: 100%;
|
|
217
655
|
margin: 0;
|
|
218
|
-
padding: clamp(
|
|
219
|
-
background:
|
|
656
|
+
padding: clamp(28px, 4vw, 52px) clamp(20px, 5vw, 72px);
|
|
657
|
+
background: var(--echo-paper-canvas);
|
|
220
658
|
}
|
|
221
659
|
#setup-details { scroll-margin-top: 0; }
|
|
222
660
|
.hero { padding: 4px 0 24px; }
|
|
@@ -415,6 +853,36 @@ export function renderSetupPage() {
|
|
|
415
853
|
.seg-cold { background: var(--echo-ink-mute); }
|
|
416
854
|
.seg-cw { background: var(--echo-ink-accent); }
|
|
417
855
|
.seg-cr { background: var(--echo-ink-seal); }
|
|
856
|
+
.ctxFacts {
|
|
857
|
+
display: grid;
|
|
858
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
859
|
+
gap: 10px;
|
|
860
|
+
margin-top: 16px;
|
|
861
|
+
}
|
|
862
|
+
.ctxFacts div {
|
|
863
|
+
min-height: 58px;
|
|
864
|
+
padding: 10px 12px;
|
|
865
|
+
border: 1px solid var(--echo-line-soft);
|
|
866
|
+
border-radius: 6px;
|
|
867
|
+
background: var(--echo-paper-soft);
|
|
868
|
+
}
|
|
869
|
+
.ctxFacts span {
|
|
870
|
+
display: block;
|
|
871
|
+
font-family: var(--echo-font-mono);
|
|
872
|
+
font-size: 10px;
|
|
873
|
+
font-weight: 800;
|
|
874
|
+
letter-spacing: 0.06em;
|
|
875
|
+
text-transform: uppercase;
|
|
876
|
+
color: var(--echo-ink-mute);
|
|
877
|
+
}
|
|
878
|
+
.ctxFacts strong {
|
|
879
|
+
display: block;
|
|
880
|
+
margin-top: 5px;
|
|
881
|
+
font-family: var(--echo-font-mono);
|
|
882
|
+
font-size: 12px;
|
|
883
|
+
line-height: 1.35;
|
|
884
|
+
color: var(--echo-ink-primary);
|
|
885
|
+
}
|
|
418
886
|
.costLegend { display: grid; gap: 6px; font-family: var(--echo-font-mono); font-size: 11px; color: var(--echo-ink-mute); }
|
|
419
887
|
.modelRow {
|
|
420
888
|
display: grid; grid-template-columns: 1fr auto auto; gap: 12px; align-items: baseline;
|
|
@@ -703,7 +1171,7 @@ export function renderSetupPage() {
|
|
|
703
1171
|
.hidden { display: none; }
|
|
704
1172
|
@media (max-width: 820px) {
|
|
705
1173
|
.heroGrid, .twoCol, .statStrip, .privacy, .doctor-hero, .doctor-stat-strip,
|
|
706
|
-
.explainHero, .insightPills, .explainSections, .explainCta { grid-template-columns: 1fr; }
|
|
1174
|
+
.explainHero, .insightPills, .explainSections, .explainCta, .ctxFacts { grid-template-columns: 1fr; }
|
|
707
1175
|
.doctor-stat-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
708
1176
|
.heroCopy h2 { font-size: 34px; }
|
|
709
1177
|
.scoreCard { min-height: 220px; }
|
|
@@ -1242,10 +1710,39 @@ export function renderSetupPage() {
|
|
|
1242
1710
|
font-weight: 900;
|
|
1243
1711
|
color: var(--echo-ink-primary);
|
|
1244
1712
|
}
|
|
1245
|
-
.tryCard
|
|
1713
|
+
.tryCard > .tryTitle { font-size: clamp(30px, 3.8vw, 48px); line-height: 0.98; }
|
|
1714
|
+
.promptShell { position: relative; margin-top: 14px; }
|
|
1715
|
+
.tryCard pre { margin: 0; white-space: pre-wrap; word-break: break-word; font-family: var(--echo-font-mono); font-size: 12px; line-height: 1.5; color: var(--echo-ink-mute); background: var(--echo-paper-soft); border: 1px solid var(--echo-line-soft); border-radius: 8px; padding: 11px 118px 11px 13px; }
|
|
1716
|
+
.promptControls {
|
|
1717
|
+
position: absolute;
|
|
1718
|
+
top: 8px;
|
|
1719
|
+
right: 8px;
|
|
1720
|
+
z-index: 2;
|
|
1721
|
+
display: flex;
|
|
1722
|
+
gap: 6px;
|
|
1723
|
+
align-items: center;
|
|
1724
|
+
}
|
|
1725
|
+
.promptCopyLabel {
|
|
1726
|
+
min-height: 28px;
|
|
1727
|
+
border: 1px solid var(--echo-line-soft);
|
|
1728
|
+
border-radius: 999px;
|
|
1729
|
+
background: rgba(255,255,255,0.82);
|
|
1730
|
+
color: var(--echo-ink-primary);
|
|
1731
|
+
padding: 0 10px;
|
|
1732
|
+
font-family: var(--echo-font-mono);
|
|
1733
|
+
font-size: 10px;
|
|
1734
|
+
font-weight: 800;
|
|
1735
|
+
letter-spacing: 0.06em;
|
|
1736
|
+
text-transform: uppercase;
|
|
1737
|
+
cursor: pointer;
|
|
1738
|
+
}
|
|
1739
|
+
.promptCopyLabel:hover {
|
|
1740
|
+
border-color: rgba(26,58,143,0.35);
|
|
1741
|
+
background: var(--echo-paper-white);
|
|
1742
|
+
}
|
|
1246
1743
|
.agentLaunch {
|
|
1247
1744
|
display: grid;
|
|
1248
|
-
grid-template-columns: repeat(
|
|
1745
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1249
1746
|
gap: 10px;
|
|
1250
1747
|
margin-top: 18px;
|
|
1251
1748
|
}
|
|
@@ -1325,6 +1822,7 @@ export function renderSetupPage() {
|
|
|
1325
1822
|
font-weight: 600;
|
|
1326
1823
|
}
|
|
1327
1824
|
.launchStatus { margin: 9px 0 0; min-height: 18px; font-size: 12px; color: var(--echo-ink-mute); }
|
|
1825
|
+
.launchStatus:empty { display: none; }
|
|
1328
1826
|
.hudTip { margin: 14px 0 0; max-width: 700px; display: grid; grid-template-columns: 52px 1fr; gap: 12px; align-items: center; font-size: 13px; line-height: 1.55; color: var(--echo-ink-mute); }
|
|
1329
1827
|
.hudTip img { width: 52px; height: 52px; object-fit: contain; filter: drop-shadow(0 10px 16px rgba(26,58,143,0.18)); }
|
|
1330
1828
|
.hudTip strong { color: var(--echo-ink-primary); }
|
|
@@ -1347,6 +1845,36 @@ export function renderSetupPage() {
|
|
|
1347
1845
|
.doneNum strong { font-family: var(--echo-font-brand); font-size: clamp(52px, 9vw, 96px); line-height: 0.9; color: var(--echo-ink-primary); letter-spacing: -0.01em; }
|
|
1348
1846
|
.doneNum span { font-size: clamp(15px, 1.6vw, 19px); font-weight: 700; color: var(--echo-ink-mute); }
|
|
1349
1847
|
.doneSub { margin: 16px 0 0; max-width: 560px; font-size: clamp(15px, 1.4vw, 17px); line-height: 1.5; color: var(--echo-ink-text); }
|
|
1848
|
+
.savedHeading {
|
|
1849
|
+
max-width: 760px;
|
|
1850
|
+
display: inline-flex;
|
|
1851
|
+
align-items: baseline;
|
|
1852
|
+
flex-wrap: wrap;
|
|
1853
|
+
gap: 6px 16px;
|
|
1854
|
+
}
|
|
1855
|
+
.savedHeading .savedMain {
|
|
1856
|
+
color: var(--echo-ink-primary);
|
|
1857
|
+
}
|
|
1858
|
+
.savedHeading strong {
|
|
1859
|
+
font-variant-numeric: tabular-nums;
|
|
1860
|
+
}
|
|
1861
|
+
.viewEchoLink {
|
|
1862
|
+
color: var(--echo-ink-mute);
|
|
1863
|
+
font-style: normal;
|
|
1864
|
+
font-family: var(--echo-font-mono);
|
|
1865
|
+
font-size: clamp(12px, 1.15vw, 15px);
|
|
1866
|
+
font-weight: 800;
|
|
1867
|
+
letter-spacing: 0.06em;
|
|
1868
|
+
text-decoration-line: underline;
|
|
1869
|
+
text-decoration-thickness: 1px;
|
|
1870
|
+
text-underline-offset: 0.18em;
|
|
1871
|
+
text-transform: none;
|
|
1872
|
+
text-decoration-color: rgba(26,58,143,0.28);
|
|
1873
|
+
}
|
|
1874
|
+
.viewEchoLink:hover {
|
|
1875
|
+
color: var(--echo-ink-primary);
|
|
1876
|
+
text-decoration-color: var(--echo-ink-primary);
|
|
1877
|
+
}
|
|
1350
1878
|
/* Collapse the long warm-up prompt behind a disclosure so the buttons lead. */
|
|
1351
1879
|
.promptPeek { margin: 14px 0 0; }
|
|
1352
1880
|
.promptPeek summary { cursor: pointer; font-family: var(--echo-font-mono); font-size: 12px; color: var(--echo-ink-mute); list-style: none; }
|
|
@@ -1369,64 +1897,553 @@ export function renderSetupPage() {
|
|
|
1369
1897
|
@keyframes rdoGrow { from { transform: scaleY(0); } to { transform: scaleY(1); } }
|
|
1370
1898
|
@keyframes rdoDraw { from { transform: scaleX(0); } to { transform: scaleX(1); } }
|
|
1371
1899
|
@keyframes rdoWipe { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 0 0 0); } }
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
.
|
|
1375
|
-
.echo-
|
|
1376
|
-
.echo-
|
|
1377
|
-
.echo-
|
|
1378
|
-
.echo-
|
|
1379
|
-
.echo-
|
|
1380
|
-
.echo-
|
|
1381
|
-
.echo-
|
|
1382
|
-
.echo-
|
|
1383
|
-
.echo-
|
|
1384
|
-
.echo-
|
|
1385
|
-
.echo-
|
|
1386
|
-
.echo-
|
|
1387
|
-
.echo-
|
|
1388
|
-
.echo-
|
|
1389
|
-
.echo-
|
|
1390
|
-
.echo-
|
|
1391
|
-
.echo-
|
|
1392
|
-
.echo-
|
|
1393
|
-
.echo-
|
|
1394
|
-
.echo-
|
|
1395
|
-
.echo-
|
|
1396
|
-
.echo-
|
|
1397
|
-
.echo-
|
|
1398
|
-
.echo-
|
|
1399
|
-
.echo-
|
|
1400
|
-
.echo-
|
|
1401
|
-
.echo-
|
|
1402
|
-
.echo-
|
|
1403
|
-
.echo-
|
|
1404
|
-
.echo-
|
|
1405
|
-
.echo-
|
|
1406
|
-
.echo-
|
|
1407
|
-
.echo-
|
|
1408
|
-
.echo-
|
|
1409
|
-
.echo-
|
|
1410
|
-
.echo-
|
|
1411
|
-
.echo-
|
|
1412
|
-
.echo-
|
|
1413
|
-
.echo-
|
|
1414
|
-
.echo-
|
|
1415
|
-
.echo-
|
|
1416
|
-
.echo-
|
|
1417
|
-
.echo-
|
|
1418
|
-
.echo-
|
|
1419
|
-
.echo-
|
|
1420
|
-
.echo-
|
|
1421
|
-
.echo-
|
|
1422
|
-
.echo-
|
|
1423
|
-
.echo-
|
|
1424
|
-
.echo-
|
|
1425
|
-
.echo-
|
|
1426
|
-
.echo-
|
|
1427
|
-
.echo-
|
|
1428
|
-
.echo-
|
|
1429
|
-
.echo-
|
|
1900
|
+
/* Pull the report up toward the city figure — the canvas bottom fades out now,
|
|
1901
|
+
so the document can tuck into that empty band without touching the board. */
|
|
1902
|
+
body.cityMode .detailsWrap { background: var(--echo-paper-canvas); margin-top: clamp(-120px, -7vw, -56px); }
|
|
1903
|
+
.echo-story { width: min(1100px, 100%); margin: clamp(72px, 10vw, 132px) auto 0; color: var(--echo-ink-text); font-family: var(--echo-font-body); }
|
|
1904
|
+
.echo-story .story-shell { display: grid; grid-template-columns: 132px minmax(0, 1fr); gap: clamp(28px, 5vw, 68px); align-items: start; }
|
|
1905
|
+
.echo-story .story-nav { position: sticky; top: 24px; display: grid; gap: 4px; padding-top: 4px; }
|
|
1906
|
+
.echo-story .story-nav a { display: grid; grid-template-columns: 22px minmax(0, 1fr); gap: 9px; align-items: baseline; padding: 9px 0; border-left: 2px solid var(--echo-line-soft); color: var(--echo-ink-faint); font-family: var(--echo-font-mono); font-size: 10px; font-weight: 900; letter-spacing: 0.07em; line-height: 1.25; text-decoration: none; text-transform: uppercase; }
|
|
1907
|
+
.echo-story .story-nav a:hover { border-left-color: var(--echo-ink-primary); color: var(--echo-ink-primary); }
|
|
1908
|
+
.echo-story .story-nav b { color: var(--echo-ink-accent-deep); font-size: 11px; }
|
|
1909
|
+
.echo-story .story-nav span { overflow: hidden; text-overflow: ellipsis; }
|
|
1910
|
+
.echo-story .story-main { min-width: 0; }
|
|
1911
|
+
.echo-story .rise { opacity: 0; animation: rdoRise 0.6s var(--rdo-ease) forwards; animation-delay: var(--d, 0ms); }
|
|
1912
|
+
.echo-story .story-kicker { margin: 0 0 10px; font-family: var(--echo-font-mono); font-size: 11px; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; color: var(--echo-ink-primary); }
|
|
1913
|
+
.echo-story h2 { margin: 0; font-family: var(--echo-font-brand); font-weight: 900; color: var(--echo-ink-primary); font-size: clamp(30px, 4vw, 48px); line-height: 1.03; letter-spacing: 0; text-wrap: balance; }
|
|
1914
|
+
.echo-story h3 { margin: 4px 0 8px; font-family: var(--echo-font-brand); font-weight: 900; font-size: clamp(22px, 3vw, 30px); line-height: 1.08; color: var(--echo-ink-primary); text-wrap: balance; }
|
|
1915
|
+
.echo-story .lead { margin: 12px 0 0; max-width: 62ch; font-size: clamp(15px, 1.4vw, 18px); line-height: 1.58; }
|
|
1916
|
+
.echo-story .lead b, .echo-story .capline b { color: var(--echo-ink-primary); }
|
|
1917
|
+
.echo-story .beat { background: transparent; border: 0; border-top: 1px dashed var(--echo-line-soft); border-radius: 0; padding: clamp(28px, 5vw, 52px) 0; margin-top: 0; position: relative; overflow: visible; }
|
|
1918
|
+
.echo-story .beat:first-of-type { border-top: 0; padding-top: 0; }
|
|
1919
|
+
.echo-story .search-beat { padding-bottom: clamp(72px, 10vw, 116px); }
|
|
1920
|
+
.echo-story .search-title { max-width: 820px; font-size: clamp(34px, 5.2vw, 54px); line-height: 0.98; }
|
|
1921
|
+
.echo-story .again-rhythm { display: flex; gap: 10px; margin-top: 10px; color: var(--echo-ink-seal); }
|
|
1922
|
+
.echo-story .again-rhythm i { font-style: normal; }
|
|
1923
|
+
.echo-story .again-rhythm i:nth-child(2) { opacity: 0.68; }
|
|
1924
|
+
.echo-story .again-rhythm i:nth-child(3) { opacity: 0.38; }
|
|
1925
|
+
.echo-story .search-lead { max-width: 54ch; margin-top: 20px; }
|
|
1926
|
+
.echo-story .search-compare { margin-top: clamp(34px, 5vw, 52px); display: grid; grid-template-columns: minmax(0, 1fr) 64px minmax(0, 1fr); overflow: hidden; border: 1px solid var(--echo-line-soft); border-radius: 14px; background: #fff; box-shadow: 0 16px 44px rgba(16,33,79,0.08); }
|
|
1927
|
+
.echo-story .search-side { min-width: 0; min-height: 320px; overflow: hidden; padding: clamp(24px, 3.5vw, 34px); box-sizing: border-box; }
|
|
1928
|
+
.echo-story .search-without { background: #f3dcda; }
|
|
1929
|
+
.echo-story .search-with { background: #d9f1ed; }
|
|
1930
|
+
.echo-story .search-side-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
|
|
1931
|
+
.echo-story .search-side-head span { font-family: var(--echo-font-mono); font-size: 10px; font-weight: 900; letter-spacing: 0.12em; text-transform: uppercase; }
|
|
1932
|
+
.echo-story .search-without .search-side-head { color: #8a3732; }
|
|
1933
|
+
.echo-story .search-with .search-side-head { color: #14524b; }
|
|
1934
|
+
.echo-story .search-side-head b { font-family: var(--echo-font-brand); font-size: clamp(24px, 3vw, 32px); line-height: 1; }
|
|
1935
|
+
.echo-story .repeat-one { position: relative; margin: 44px 26px 0 0; }
|
|
1936
|
+
.echo-story .rq-ghost { position: absolute; inset: 0; border-radius: 8px; border: 1px solid #ddb1ab; background: #fbf2f0; }
|
|
1937
|
+
.echo-story .rq-ghost.g1 { transform: translate(8px, -8px) rotate(1.2deg); opacity: 0.75; }
|
|
1938
|
+
.echo-story .rq-ghost.g2 { transform: translate(16px, -16px) rotate(-1.8deg); opacity: 0.5; }
|
|
1939
|
+
.echo-story .rq-ghost.g3 { transform: translate(24px, -24px) rotate(2.4deg); opacity: 0.3; }
|
|
1940
|
+
.echo-story .repeat-main { position: relative; z-index: 2; min-width: 0; overflow: hidden; padding: 13px 15px; border-radius: 8px; border: 1px solid #d9a49d; border-left: 4px solid #b84a43; background: #fff; box-shadow: 0 4px 12px rgba(95,39,35,0.10); box-sizing: border-box; }
|
|
1941
|
+
.echo-story .repeat-main code { display: block; overflow: hidden; color: #5e2926; font-family: var(--echo-font-mono); font-size: 11.5px; font-weight: 800; text-overflow: ellipsis; white-space: nowrap; }
|
|
1942
|
+
.echo-story .repeat-main small { display: block; margin-top: 5px; color: #956762; font-family: var(--echo-font-mono); font-size: 9.5px; letter-spacing: 0.04em; text-transform: uppercase; }
|
|
1943
|
+
.echo-story .rq-stamp { position: absolute; top: -34px; right: -20px; z-index: 3; font-family: var(--echo-font-brand); font-weight: 900; font-size: 19px; color: var(--echo-ink-seal); border: 2.5px solid var(--echo-ink-seal); border-radius: 7px; padding: 2px 11px; transform: rotate(6deg); background: rgba(255,253,247,0.85); font-variant-numeric: tabular-nums; }
|
|
1944
|
+
.echo-story .search-side-note { margin: 20px 0 0; color: #80534f; font-size: 13px; line-height: 1.45; }
|
|
1945
|
+
.echo-story .search-with .search-side-note { color: #2b675f; }
|
|
1946
|
+
.echo-story .search-turn { display: grid; place-items: center; background: #fff; color: var(--echo-ink-accent-deep); font-family: var(--echo-font-brand); font-size: 38px; font-weight: 900; }
|
|
1947
|
+
.echo-story .recall-card { min-width: 0; margin-top: 28px; overflow: hidden; padding: 18px; background: rgba(255,255,255,0.82); border: 1px solid #8bcac2; border-radius: 10px; box-sizing: border-box; }
|
|
1948
|
+
.echo-story .recall-top { display: flex; align-items: center; gap: 10px; color: #14524b; font-family: var(--echo-font-mono); font-size: 10px; font-weight: 900; letter-spacing: 0.08em; text-transform: uppercase; }
|
|
1949
|
+
.echo-story .recall-top img { width: 30px; height: 30px; object-fit: contain; }
|
|
1950
|
+
.echo-story .recall-card code { display: block; margin-top: 16px; overflow: hidden; color: #123e39; font-family: var(--echo-font-mono); font-size: 12px; font-weight: 800; text-overflow: ellipsis; white-space: nowrap; }
|
|
1951
|
+
.echo-story .recall-result { margin-top: 12px; padding-top: 12px; border-top: 1px solid #a9d8d2; color: #285d56; font-size: 13px; line-height: 1.45; }
|
|
1952
|
+
.echo-story .search-proof { margin-top: 18px; display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; color: var(--echo-ink-mute); font-size: 12.5px; }
|
|
1953
|
+
.echo-story .search-proof > span { font-family: var(--echo-font-mono); font-size: 9.5px; font-weight: 900; letter-spacing: 0.1em; text-transform: uppercase; color: var(--echo-ink-faint); }
|
|
1954
|
+
.echo-story .search-proof strong { color: var(--echo-ink-seal); font-family: var(--echo-font-brand); font-size: 22px; }
|
|
1955
|
+
.echo-story .search-proof code { min-width: 0; max-width: min(100%, 56ch); overflow: hidden; color: var(--echo-ink-primary); font-family: var(--echo-font-mono); font-size: 11.5px; font-weight: 800; text-overflow: ellipsis; white-space: nowrap; background: var(--echo-paper-soft); border-radius: 5px; padding: 2px 7px; box-sizing: border-box; }
|
|
1956
|
+
.echo-story .carry-beat { padding: clamp(90px, 12vw, 132px) 0; }
|
|
1957
|
+
.echo-story .carry-title { max-width: 760px; font-size: clamp(34px, 5.2vw, 54px); line-height: 0.98; }
|
|
1958
|
+
.echo-story .carry-title span { display: block; margin-top: 10px; color: var(--echo-ink-seal); opacity: 0.7; }
|
|
1959
|
+
.echo-story .carry-lead { max-width: 56ch; margin-top: 20px; }
|
|
1960
|
+
.echo-story .carry-compare { margin-top: clamp(34px, 5vw, 52px); display: grid; grid-template-columns: minmax(0, 1fr) 64px minmax(0, 1fr); overflow: hidden; border: 1px solid var(--echo-line-soft); border-radius: 14px; background: #fff; box-shadow: 0 16px 44px rgba(16,33,79,0.08); }
|
|
1961
|
+
.echo-story .carry-side { min-height: 330px; padding: clamp(24px, 3.5vw, 34px); box-sizing: border-box; }
|
|
1962
|
+
.echo-story .carry-without { background: #f3dcda; }
|
|
1963
|
+
.echo-story .carry-with { background: #d9f1ed; }
|
|
1964
|
+
.echo-story .carry-side-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
|
|
1965
|
+
.echo-story .carry-side-head span { font-family: var(--echo-font-mono); font-size: 10px; font-weight: 900; letter-spacing: 0.12em; text-transform: uppercase; }
|
|
1966
|
+
.echo-story .carry-without .carry-side-head { color: #8a3732; }
|
|
1967
|
+
.echo-story .carry-with .carry-side-head { color: #14524b; }
|
|
1968
|
+
.echo-story .carry-side-head b { font-family: var(--echo-font-brand); font-size: clamp(24px, 3vw, 32px); line-height: 1; }
|
|
1969
|
+
.echo-story .carry-turn { display: grid; place-items: center; background: #fff; color: var(--echo-ink-accent-deep); font-family: var(--echo-font-brand); font-size: 38px; font-weight: 900; }
|
|
1970
|
+
.echo-story .carry-stack { display: grid; gap: 10px; margin-top: 28px; }
|
|
1971
|
+
.echo-story .carry-object { display: flex; align-items: center; gap: 12px; min-height: 52px; padding: 10px 12px; background: rgba(255,255,255,0.66); border-left: 4px solid #b84a43; box-sizing: border-box; }
|
|
1972
|
+
.echo-story .carry-object:nth-child(2) { margin-left: 12px; opacity: 0.76; }
|
|
1973
|
+
.echo-story .carry-object:nth-child(3) { margin-left: 24px; opacity: 0.52; }
|
|
1974
|
+
.echo-story .carry-glyph { display: grid; place-items: center; width: 32px; height: 28px; flex: none; border: 1px solid #c98781; background: #fffaf9; color: #93413b; font-family: var(--echo-font-mono); font-size: 10px; font-weight: 900; }
|
|
1975
|
+
.echo-story .carry-glyph.file { position: relative; }
|
|
1976
|
+
.echo-story .carry-glyph.file::after { content: ""; position: absolute; inset: 4px -4px -4px 4px; border: 1px solid #d5aaa5; z-index: -1; }
|
|
1977
|
+
.echo-story .carry-object b { display: block; color: #642f2b; font-family: var(--echo-font-mono); font-size: 11px; font-weight: 900; letter-spacing: 0.05em; text-transform: uppercase; }
|
|
1978
|
+
.echo-story .carry-object small { display: block; margin-top: 4px; color: #956762; font-size: 12px; line-height: 1.3; }
|
|
1979
|
+
.echo-story .checkpoint-card { margin-top: 28px; padding: 18px; background: rgba(255,255,255,0.82); border: 1px solid #8bcac2; border-radius: 10px; }
|
|
1980
|
+
.echo-story .checkpoint-top { display: flex; align-items: center; gap: 10px; color: #14524b; font-family: var(--echo-font-mono); font-size: 10px; font-weight: 900; letter-spacing: 0.08em; text-transform: uppercase; }
|
|
1981
|
+
.echo-story .checkpoint-top img { width: 30px; height: 30px; object-fit: contain; }
|
|
1982
|
+
.echo-story .checkpoint-lines { display: grid; gap: 8px; margin-top: 16px; padding-top: 14px; border-top: 1px solid #a9d8d2; }
|
|
1983
|
+
.echo-story .checkpoint-lines span { color: #285d56; font-size: 13px; }
|
|
1984
|
+
.echo-story .checkpoint-lines b { color: #14655a; font-family: var(--echo-font-mono); font-size: 11px; }
|
|
1985
|
+
.echo-story .carry-proof { margin-top: 18px; display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; color: var(--echo-ink-mute); font-size: 12.5px; }
|
|
1986
|
+
.echo-story .carry-proof > span { font-family: var(--echo-font-mono); font-size: 9.5px; font-weight: 900; letter-spacing: 0.1em; text-transform: uppercase; color: var(--echo-ink-faint); }
|
|
1987
|
+
.echo-story .carry-proof strong { color: var(--echo-ink-seal); font-family: var(--echo-font-brand); font-size: 22px; }
|
|
1988
|
+
.echo-story .payoff { margin: 0; padding: clamp(96px, 13vw, 150px) 0 clamp(72px, 9vw, 112px); background: transparent; border: 0; }
|
|
1989
|
+
.echo-story .payoff-title { max-width: 720px; font-size: clamp(34px, 5.2vw, 54px); line-height: 0.98; }
|
|
1990
|
+
.echo-story .payoff-lead { max-width: 54ch; margin-top: 20px; }
|
|
1991
|
+
.echo-story .time-payoff { margin-top: clamp(34px, 5vw, 52px); display: grid; grid-template-columns: minmax(0, 1fr) 64px minmax(0, 1fr); overflow: hidden; border: 1px solid var(--echo-line-soft); border-radius: 14px; background: #fff; box-shadow: 0 16px 44px rgba(16,33,79,0.08); }
|
|
1992
|
+
.echo-story .time-state { min-height: 246px; padding: clamp(24px, 3.5vw, 34px); box-sizing: border-box; }
|
|
1993
|
+
.echo-story .time-current { background: #f3dcda; color: #7b3732; }
|
|
1994
|
+
.echo-story .time-next { background: #d9f1ed; color: #14524b; }
|
|
1995
|
+
.echo-story .time-state > span { display: block; font-family: var(--echo-font-mono); font-size: 10px; font-weight: 900; letter-spacing: 0.12em; text-transform: uppercase; }
|
|
1996
|
+
.echo-story .time-state strong { display: block; margin-top: 18px; font-family: var(--echo-font-brand); font-size: clamp(52px, 7vw, 76px); line-height: 0.82; font-variant-numeric: tabular-nums; }
|
|
1997
|
+
.echo-story .time-state small { display: block; margin-top: 10px; font-size: 14px; line-height: 1.4; }
|
|
1998
|
+
.echo-story .time-date { display: inline-flex; align-items: baseline; gap: 7px; margin-top: 22px; padding-top: 12px; border-top: 1px solid currentColor; font-family: var(--echo-font-mono); }
|
|
1999
|
+
.echo-story .time-date b { font-size: 20px; letter-spacing: 0.03em; }
|
|
2000
|
+
.echo-story .time-date span { font-size: 9px; font-weight: 900; letter-spacing: 0.08em; text-transform: uppercase; opacity: 0.72; }
|
|
2001
|
+
.echo-story .time-arrow { display: grid; place-items: center; background: #fff; color: var(--echo-ink-accent-deep); font-family: var(--echo-font-brand); font-size: 38px; font-weight: 900; }
|
|
2002
|
+
.echo-story .time-proof { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; margin-top: 22px; color: var(--echo-ink-mute); }
|
|
2003
|
+
.echo-story .time-proof strong { color: var(--echo-ink-accent-deep); font-family: var(--echo-font-brand); font-size: 40px; line-height: 1; font-variant-numeric: tabular-nums; }
|
|
2004
|
+
.echo-story .time-proof span { max-width: 52ch; font-size: 14px; line-height: 1.45; }
|
|
2005
|
+
.echo-story .beat-label { display: flex; justify-content: space-between; gap: 12px; align-items: baseline; margin-bottom: 12px; font-family: var(--echo-font-mono); font-size: 11px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: var(--echo-ink-seal); }
|
|
2006
|
+
.echo-story .beat-label span:last-child { color: var(--echo-ink-accent-deep); }
|
|
2007
|
+
.echo-story .rlabel { margin: 26px 0 0; color: var(--echo-ink-mute); font-size: 13.5px; }
|
|
2008
|
+
.echo-story .evidence { margin-top: 10px; display: grid; gap: 0; }
|
|
2009
|
+
.echo-story .exh { display: grid; grid-template-columns: minmax(132px, 150px) minmax(0, 1fr); gap: 14px; align-items: start; padding: 15px 0; border-bottom: 1px solid var(--echo-line-soft); }
|
|
2010
|
+
.echo-story .xn { font-family: var(--echo-font-brand); font-weight: 900; font-size: clamp(24px, 2.8vw, 27px); line-height: 1; color: var(--echo-ink-seal); font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
2011
|
+
.echo-story .xn small { display: block; margin-top: 5px; font-family: var(--echo-font-mono); font-size: 9.5px; font-weight: 800; letter-spacing: 0.07em; text-transform: uppercase; color: var(--echo-ink-faint); }
|
|
2012
|
+
.echo-story .xc code { font-family: var(--echo-font-mono); font-size: 12.5px; color: var(--echo-ink-primary); font-weight: 700; word-break: break-word; background: var(--echo-paper-soft); border: 1px solid var(--echo-line-soft); border-radius: 6px; padding: 2px 7px; }
|
|
2013
|
+
.echo-story .xc small { display: block; margin-top: 7px; color: var(--echo-ink-mute); font-size: 13px; line-height: 1.45; }
|
|
2014
|
+
.echo-story .echo-compare { margin: 20px 0 0; display: grid; gap: 9px; max-width: 720px; }
|
|
2015
|
+
.echo-story .cmp-row { display: grid; grid-template-columns: 132px minmax(0, 1fr); gap: 12px; align-items: center; }
|
|
2016
|
+
.echo-story .cmp-label { color: var(--echo-ink-faint); font-family: var(--echo-font-mono); font-size: 11px; font-weight: 900; letter-spacing: 0.08em; text-align: right; text-transform: uppercase; }
|
|
2017
|
+
.echo-story .cmp-track { position: relative; min-width: 0; height: 32px; overflow: hidden; border: 1px solid var(--echo-line-soft); border-radius: 8px; background: var(--echo-paper-soft); }
|
|
2018
|
+
.echo-story .cmp-fill { position: absolute; inset: 0 auto 0 0; width: var(--w, 0%); border-radius: 7px; }
|
|
2019
|
+
.echo-story .cmp-row.bad .cmp-fill { background: #e8aaa5; }
|
|
2020
|
+
.echo-story .cmp-row.good .cmp-fill { background: #9ed8d1; min-width: 76px; max-width: 100%; }
|
|
2021
|
+
.echo-story .cmp-track strong { position: relative; z-index: 1; display: flex; align-items: center; justify-content: flex-end; height: 100%; padding: 0 12px; box-sizing: border-box; color: #78312d; font-family: var(--echo-font-mono); font-size: 12px; font-weight: 900; white-space: nowrap; }
|
|
2022
|
+
.echo-story .cmp-row.good .cmp-track strong { justify-content: flex-start; color: #14524b; }
|
|
2023
|
+
.echo-story .cmp-track.cleared { display: flex; align-items: center; padding: 0 12px; box-sizing: border-box; border-color: #7cc5bc; border-style: dashed; background: #eef9f7; color: var(--echo-ink-accent-deep); font-family: var(--echo-font-mono); font-size: 12px; font-weight: 900; }
|
|
2024
|
+
.echo-story .imgrow { display: flex; gap: 6px; margin-top: 8px; }
|
|
2025
|
+
.echo-story .imgrow i { width: 16px; height: 16px; background: #e8b7b0; border: 1px solid #d9a49d; border-radius: 3px; }
|
|
2026
|
+
.echo-story .imgrow i:nth-child(2n) { transform: rotate(6deg); }
|
|
2027
|
+
.echo-story .imgrow i:nth-child(3n) { transform: rotate(-5deg); }
|
|
2028
|
+
.echo-story .imgrow em { align-self: center; color: var(--echo-ink-faint); font-family: var(--echo-font-mono); font-size: 11px; font-style: normal; }
|
|
2029
|
+
.echo-story .shotstrip { display: flex; align-items: flex-start; gap: 5px; margin-top: 16px; flex-wrap: wrap; min-height: 92px; }
|
|
2030
|
+
.echo-story .shotframe { width: 88px; height: 64px; border: 1px solid var(--echo-line-soft); border-radius: 7px; background: #fff; display: grid; place-items: center; position: relative; box-shadow: 0 2px 6px rgba(43,42,38,0.12); }
|
|
2031
|
+
.echo-story .shotframe:first-child { border-color: var(--echo-ink-seal); }
|
|
2032
|
+
.echo-story .shotframe:nth-child(2) { transform: rotate(-4deg) translateY(4px); }
|
|
2033
|
+
.echo-story .shotframe:nth-child(3) { transform: rotate(5deg) translateY(8px); margin-left: -12px; background: #faf4e2; border-color: #ddd0ac; }
|
|
2034
|
+
.echo-story .shotframe:nth-child(4) { transform: rotate(-7deg) translateY(5px); margin-left: -9px; background: #f8efd5; border-color: #ddd0ac; }
|
|
2035
|
+
.echo-story .shotframe:nth-child(5) { transform: rotate(3deg) translateY(12px); margin-left: -14px; background: #f6e9c9; border-color: #d7c69d; }
|
|
2036
|
+
.echo-story .shotframe:nth-child(6) { transform: rotate(-6deg) translateY(9px); margin-left: -11px; background: #f1e1bd; border-color: #d1bd8f; }
|
|
2037
|
+
.echo-story .shotframe span { position: absolute; bottom: -22px; left: 50%; transform: translateX(-50%); font-family: var(--echo-font-mono); font-size: 9px; color: var(--echo-ink-faint); white-space: nowrap; }
|
|
2038
|
+
.echo-story .devshot { width: 68px; height: 47px; border-radius: 5px; background: #f8f7f2; border: 1px solid #d8d2c2; padding: 4px; box-sizing: border-box; }
|
|
2039
|
+
.echo-story .devshot i { display: block; height: 5px; border-radius: 2px; background: #1a3a8f; opacity: 0.84; }
|
|
2040
|
+
.echo-story .devshot b { display: block; width: 38px; height: 15px; border-radius: 2px; background: #e8e4d8; margin-top: 5px; box-shadow: 42px 0 0 -2px #dcd6c6, 42px 10px 0 -2px #e8e4d8; }
|
|
2041
|
+
.echo-story .devshot em { display: block; width: 50px; height: 3px; border-radius: 2px; background: #eceadf; margin-top: 8px; }
|
|
2042
|
+
.echo-story .shotframe:nth-child(n+3) .devshot { filter: sepia(0.5) opacity(0.72); }
|
|
2043
|
+
.echo-story .gitpile { position: relative; margin-top: 14px; height: 126px; max-width: 500px; overflow: hidden; }
|
|
2044
|
+
.echo-story .gitpile .gchip { position: absolute; font-family: var(--echo-font-mono); font-size: 11px; color: var(--echo-ink-primary); background: #f6f4ee; border: 1px solid #d8d2c2; border-radius: 6px; padding: 4px 10px; white-space: nowrap; box-shadow: 0 2px 6px rgba(43,42,38,0.10); }
|
|
2045
|
+
.echo-story .gitpile .pilecount { position: absolute; right: 4px; bottom: 0; font-family: var(--echo-font-brand); font-weight: 900; font-size: 24px; color: var(--echo-ink-seal); transform: rotate(-4deg); }
|
|
2046
|
+
.echo-story .sheetstack { position: relative; margin-top: 22px; height: 86px; width: min(250px, calc(100% - 34px)); max-width: 250px; }
|
|
2047
|
+
.echo-story .sheetstack.messy { height: 104px; margin-top: 26px; }
|
|
2048
|
+
.echo-story .sheet { position: absolute; inset: 0; background: var(--echo-paper-white); border: 1px solid #d8d2c2; border-radius: 8px; box-shadow: 0 2px 6px rgba(43,42,38,0.07); }
|
|
2049
|
+
.echo-story .sheet.top { padding: 12px 14px; display: flex; flex-direction: column; gap: 7px; }
|
|
2050
|
+
.echo-story .sheet .fl { height: 6px; border-radius: 3px; background: #eceadf; }
|
|
2051
|
+
.echo-story .sheet .fl.w1 { width: 82%; } .echo-story .sheet .fl.w2 { width: 64%; } .echo-story .sheet .fl.w3 { width: 74%; }
|
|
2052
|
+
.echo-story .stackcap { margin: 10px 0 0; color: var(--echo-ink-faint); font-family: var(--echo-font-mono); font-size: 11px; }
|
|
2053
|
+
.echo-story .stalestamp { position: absolute; top: -9px; right: -6px; z-index: 9; font-family: var(--echo-font-mono); font-weight: 900; font-size: 11px; letter-spacing: 0.12em; color: var(--echo-ink-seal); border: 2px solid var(--echo-ink-seal); border-radius: 4px; padding: 2px 8px; transform: rotate(-8deg); background: rgba(255,253,247,0.78); }
|
|
2054
|
+
.echo-story .fix { margin-top: 28px; border: 2px solid var(--echo-ink-primary); border-radius: 12px; padding: 22px 24px; background: linear-gradient(180deg, #f4f7fe, #fbfcff); position: relative; box-shadow: 0 4px 18px rgba(26,58,143,0.08); }
|
|
2055
|
+
.echo-story .fk { position: absolute; top: -11px; left: 20px; background: var(--echo-ink-primary); color: #fff; font-family: var(--echo-font-mono); font-size: 10px; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; padding: 3px 10px; border-radius: 4px; }
|
|
2056
|
+
.echo-story .echo-seal { position: absolute; top: -30px; right: -14px; z-index: 2; width: 92px; height: 92px; border-radius: 50%; background: #fffdf7; border: 3px solid var(--echo-ink-primary); box-shadow: 0 10px 24px rgba(16,33,79,0.24), inset 0 0 0 6px #eef5ff; display: flex; flex-direction: column; align-items: center; justify-content: center; transform: rotate(9deg); }
|
|
2057
|
+
.echo-story .echo-seal::before { content: ""; position: absolute; inset: 6px; border-radius: 50%; border: 1.5px dashed #8da0da; }
|
|
2058
|
+
.echo-story .echo-seal img { width: 38px; height: 36px; object-fit: contain; filter: drop-shadow(0 2px 4px rgba(16,33,79,0.18)); margin-top: -4px; }
|
|
2059
|
+
.echo-story .echo-seal b { font-family: var(--echo-font-mono); font-size: 12px; line-height: 1; font-weight: 900; letter-spacing: 0.08em; color: var(--echo-ink-primary); }
|
|
2060
|
+
.echo-story .echo-seal span { font-family: var(--echo-font-mono); font-size: 7px; font-weight: 800; letter-spacing: 0.14em; color: #1d5a54; }
|
|
2061
|
+
.echo-story .terminal-pair { display: grid; grid-template-columns: 1.15fr 1fr; gap: 16px; margin-top: 16px; align-items: start; }
|
|
2062
|
+
.echo-story .term { background: #21232a; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 14px rgba(16,33,79,0.18); }
|
|
2063
|
+
.echo-story .term-bar { display: flex; align-items: center; gap: 5px; padding: 7px 12px; background: #2a2d35; }
|
|
2064
|
+
.echo-story .term-bar i { width: 8px; height: 8px; border-radius: 50%; }
|
|
2065
|
+
.echo-story .term-bar i:nth-child(1) { background: #e0655a; } .echo-story .term-bar i:nth-child(2) { background: #e3b54e; } .echo-story .term-bar i:nth-child(3) { background: #6cbf62; }
|
|
2066
|
+
.echo-story .term-bar img { width: 18px; height: 18px; object-fit: contain; margin-left: 4px; }
|
|
2067
|
+
.echo-story .term-bar span { margin-left: 8px; font-family: var(--echo-font-mono); font-size: 10px; letter-spacing: 0.05em; color: #9aa0ab; text-transform: uppercase; }
|
|
2068
|
+
.echo-story .term-body { padding: 12px 14px; font-family: var(--echo-font-mono); font-size: 11.5px; line-height: 1.8; color: #c9cdd4; }
|
|
2069
|
+
.echo-story .term-body .q { color: #eceef2; }
|
|
2070
|
+
.echo-story .term-body .tl .dot { color: #6fc7b8; }
|
|
2071
|
+
.echo-story .term-body .rs { color: #7d838e; padding-left: 14px; }
|
|
2072
|
+
.echo-story .term-body .mo { color: #7d838e; }
|
|
2073
|
+
.echo-story .term-body .think { color: #e3b54e; }
|
|
2074
|
+
.echo-story .ttime { text-align: right; padding: 2px 14px 12px; font-family: var(--echo-font-mono); font-size: 17px; font-weight: 800; }
|
|
2075
|
+
.echo-story .ttime small { display: block; font-size: 9px; font-weight: 700; letter-spacing: 0.07em; text-transform: uppercase; color: #7d838e; }
|
|
2076
|
+
.echo-story .ttime.slow { color: #e08a7e; } .echo-story .ttime.fast { color: #7fd4c9; }
|
|
2077
|
+
.echo-story .saveflow { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 18px; margin-top: 18px; align-items: start; }
|
|
2078
|
+
.echo-story .savecol-title { margin: 0 0 10px; color: var(--echo-ink-accent-deep); font-family: var(--echo-font-mono); font-size: 10.5px; font-weight: 800; letter-spacing: 0.07em; text-transform: uppercase; }
|
|
2079
|
+
.echo-story .savecard { background: var(--echo-paper-white); border: 1px solid var(--echo-line-soft); border-radius: 10px; padding: 14px 16px; box-shadow: 0 2px 8px rgba(43,42,38,0.06); }
|
|
2080
|
+
.echo-story .sc-think { margin: 0 0 2px; font-family: var(--echo-font-mono); font-size: 10.5px; font-weight: 700; color: #b3924e; }
|
|
2081
|
+
.echo-story .squig { width: 100%; height: 22px; display: block; margin: 2px 0 4px; border-radius: 999px; background: repeating-linear-gradient(90deg, transparent 0 8px, rgba(201,189,151,0.68) 8px 10px); opacity: 0.75; }
|
|
2082
|
+
.echo-story .saved-memory { margin-top: 10px; border: 1px solid var(--echo-line-soft); border-left: 3px solid var(--echo-ink-primary); border-radius: 8px; background: #fbfcff; padding: 10px 12px; }
|
|
2083
|
+
.echo-story .saved-memory b { display: block; color: var(--echo-ink-primary); font-family: var(--echo-font-mono); font-size: 10px; font-weight: 900; letter-spacing: 0.08em; text-transform: uppercase; }
|
|
2084
|
+
.echo-story .saved-memory span { display: block; margin-top: 5px; color: var(--echo-ink-text); font-size: 13px; line-height: 1.4; }
|
|
2085
|
+
.echo-story .saved-memory small { display: block; margin-top: 7px; color: var(--echo-ink-faint); font-family: var(--echo-font-mono); font-size: 10px; }
|
|
2086
|
+
.echo-story .sc-arrow { text-align: center; color: var(--echo-ink-accent-deep); font-weight: 900; font-size: 17px; margin: 8px 0; }
|
|
2087
|
+
.echo-story .memchip { display: flex; align-items: center; gap: 8px; background: #e9f5f3; border: 1.5px solid #7cc5bc; border-radius: 8px; padding: 7px 11px; font-family: var(--echo-font-mono); font-size: 11.5px; color: #14524b; font-weight: 700; }
|
|
2088
|
+
.echo-story .memchip img { width: 21px; height: 21px; object-fit: contain; flex: none; }
|
|
2089
|
+
.echo-story .memchip span { min-width: 0; }
|
|
2090
|
+
.echo-story .memchip em { margin-left: auto; padding-left: 8px; color: #1d5a54; font-size: 10px; font-style: normal; letter-spacing: 0.05em; text-transform: uppercase; white-space: nowrap; }
|
|
2091
|
+
.echo-story .flow { margin-top: 18px; position: relative; padding-left: 30px; }
|
|
2092
|
+
.echo-story .flow::before { content: ""; position: absolute; left: 8px; top: 14px; bottom: 26px; width: 2px; background: var(--echo-line-soft); }
|
|
2093
|
+
.echo-story .fstep { position: relative; padding: 8px 0 20px; }
|
|
2094
|
+
.echo-story .fstep::before { content: ""; position: absolute; left: -28px; top: 13px; width: 10px; height: 10px; border-radius: 50%; background: var(--echo-paper-white); border: 3px solid var(--echo-ink-primary); z-index: 1; }
|
|
2095
|
+
.echo-story .fs-t { margin: 0 0 12px; color: var(--echo-ink-primary); font-family: var(--echo-font-mono); font-size: 11px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; }
|
|
2096
|
+
.echo-story .hudmock { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
|
|
2097
|
+
.echo-story .hudbubble.drift { border-color: var(--echo-ink-seal); }
|
|
2098
|
+
.echo-story .hudbubble.drift .hudtxt b i { color: var(--echo-ink-seal); }
|
|
2099
|
+
.echo-story .hudbubble.drift .hudtxt span { color: var(--echo-ink-seal); font-family: var(--echo-font-brand); font-size: 12px; font-weight: 800; letter-spacing: 0; text-transform: none; }
|
|
2100
|
+
.echo-story .ckpt { margin-top: 16px; background: var(--echo-paper-white); border: 1px solid var(--echo-line-soft); border-radius: 10px; padding: 15px 18px 17px; box-shadow: 0 2px 10px rgba(16,33,79,0.05); }
|
|
2101
|
+
.echo-story .ck-h { margin: 12px 0 4px; color: var(--echo-ink-primary); font-family: var(--echo-font-mono); font-size: 10.5px; font-weight: 800; letter-spacing: 0.07em; text-transform: uppercase; }
|
|
2102
|
+
.echo-story .ck-h:first-child { margin-top: 0; }
|
|
2103
|
+
.echo-story .ck-b { margin: 0; color: var(--echo-ink-mute); filter: blur(2.6px); font-size: 12.5px; line-height: 1.45; pointer-events: none; user-select: none; }
|
|
2104
|
+
.echo-story .ck-dec { margin: 5px 0; padding-left: 10px; border-left: 3px solid var(--echo-ink-accent-deep); color: var(--echo-ink-text); font-size: 13px; line-height: 1.45; }
|
|
2105
|
+
.echo-story .fresh { max-width: 340px; border: 2px solid var(--echo-ink-accent-deep); border-radius: 10px; overflow: hidden; background: var(--echo-paper-white); }
|
|
2106
|
+
.echo-story .fw-bar { padding: 6px 12px; border-bottom: 1.5px solid var(--echo-ink-accent-deep); background: #f2f9f8; color: #1d5a54; font-family: var(--echo-font-mono); font-size: 10px; font-weight: 800; letter-spacing: 0.07em; text-transform: uppercase; }
|
|
2107
|
+
.echo-story .fw-body { padding: 14px; background: linear-gradient(180deg, #f7fbfa, #eaf4f2); }
|
|
2108
|
+
.echo-story .seed { display: inline-block; color: #1d5a54; background: #fff; border: 1px solid #9fd0ca; border-radius: 5px; padding: 3px 9px; font-family: var(--echo-font-mono); font-size: 11px; }
|
|
2109
|
+
.echo-story .fw-note { display: block; margin-top: 9px; color: var(--echo-ink-mute); font-size: 12.5px; }
|
|
2110
|
+
.echo-story .wins { margin-top: 16px; background: var(--echo-paper-white); border: 1px solid var(--echo-line-soft); border-top: 3px solid var(--echo-ink-accent-deep); border-radius: 14px; padding: clamp(22px, 4vw, 30px); }
|
|
2111
|
+
.echo-story .wins .lead { max-width: 56ch; }
|
|
2112
|
+
.echo-story .hudbubble { display: flex; align-items: center; gap: 12px; background: #fffdf7; border: 2px solid #10214f; border-radius: 999px; padding: 10px 20px 10px 12px; box-shadow: 0 4px 14px rgba(16,33,79,0.10); }
|
|
2113
|
+
.echo-story .hudbubble.next { border-color: var(--echo-ink-accent-deep); box-shadow: 0 4px 14px rgba(47,157,147,0.18); }
|
|
2114
|
+
.echo-story .hudbubble img { width: 38px; height: 38px; object-fit: contain; }
|
|
2115
|
+
.echo-story .hudtxt b { display: block; font-family: var(--echo-font-brand); font-weight: 900; font-size: 16px; color: var(--echo-ink-text); line-height: 1.1; }
|
|
2116
|
+
.echo-story .hudtxt span { font-family: var(--echo-font-mono); font-size: 9px; letter-spacing: 0.09em; text-transform: uppercase; color: var(--echo-ink-faint); }
|
|
2117
|
+
.echo-story .arrow { font-size: 24px; color: var(--echo-ink-accent-deep); font-weight: 900; }
|
|
2118
|
+
.echo-story .livehud { margin-top: 20px; max-width: 520px; border: 1px solid var(--echo-line-soft); border-radius: 16px; background: #fffdf7; box-shadow: 0 18px 42px rgba(16,33,79,0.10); overflow: hidden; }
|
|
2119
|
+
.echo-story .lh-glance { display: grid; grid-template-columns: auto auto minmax(0, 1fr) auto; align-items: center; gap: 10px; padding: 12px 14px; border-bottom: 1px solid var(--echo-line-soft); background: #fff; }
|
|
2120
|
+
.echo-story .lh-drag { width: 14px; height: 14px; border-radius: 3px; background: radial-gradient(circle, #b6b8bd 2px, transparent 2.5px) 0 0 / 7px 7px; opacity: 0.8; }
|
|
2121
|
+
.echo-story .lh-mascot { width: 40px; height: 40px; border-radius: 12px; display: grid; place-items: center; background: #eef5ff; border: 1px solid #dfe7fb; }
|
|
2122
|
+
.echo-story .lh-mascot img { width: 32px; height: 32px; object-fit: contain; }
|
|
2123
|
+
.echo-story .lh-status { min-width: 0; display: flex; align-items: center; gap: 9px; border: 1px solid #d8d2c2; border-radius: 999px; padding: 8px 12px; background: #fffdf7; }
|
|
2124
|
+
.echo-story .lh-dot { width: 10px; height: 10px; border-radius: 50%; background: #d8963a; flex: none; }
|
|
2125
|
+
.echo-story .lh-dot.target { background: var(--echo-ink-accent-deep); }
|
|
2126
|
+
.echo-story .lh-copy { min-width: 0; display: grid; gap: 2px; }
|
|
2127
|
+
.echo-story .lh-copy b { color: var(--echo-ink-text); font-family: var(--echo-font-brand); font-size: 15px; line-height: 1.05; font-weight: 900; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
2128
|
+
.echo-story .lh-copy span { color: var(--echo-ink-faint); font-family: var(--echo-font-mono); font-size: 9px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; white-space: nowrap; }
|
|
2129
|
+
.echo-story .lh-mini { width: 26px; height: 26px; border-radius: 50%; border: 1px solid var(--echo-line-soft); background: var(--echo-paper-soft); color: var(--echo-ink-mute); display: grid; place-items: center; font-family: var(--echo-font-mono); font-weight: 900; }
|
|
2130
|
+
.echo-story .lh-details { padding: 14px 16px 16px; background: #fffdf7; }
|
|
2131
|
+
.echo-story .lh-head { display: flex; justify-content: space-between; gap: 12px; color: var(--echo-ink-faint); font-family: var(--echo-font-mono); font-size: 10px; font-weight: 800; letter-spacing: 0.07em; text-transform: uppercase; }
|
|
2132
|
+
.echo-story .lh-state { margin-top: 14px; display: flex; align-items: baseline; gap: 9px; }
|
|
2133
|
+
.echo-story .lh-state b { color: var(--echo-ink-primary); font-family: var(--echo-font-brand); font-size: 28px; line-height: 0.9; font-weight: 900; font-variant-numeric: tabular-nums; }
|
|
2134
|
+
.echo-story .lh-state span { color: var(--echo-ink-text); font-weight: 800; }
|
|
2135
|
+
.echo-story .lh-full { margin-top: 8px; color: var(--echo-ink-mute); font-size: 13px; }
|
|
2136
|
+
.echo-story .lh-lead { margin: 4px 0 0; color: var(--echo-ink-text); font-size: 14px; line-height: 1.45; }
|
|
2137
|
+
.echo-story .lh-evidence { margin-top: 8px; color: var(--echo-ink-faint); font-family: var(--echo-font-mono); font-size: 11px; }
|
|
2138
|
+
.echo-story .lh-bar { margin-top: 12px; height: 10px; border-radius: 999px; overflow: hidden; display: flex; background: var(--echo-paper-soft); }
|
|
2139
|
+
.echo-story .lh-bar .useful { background: var(--echo-ink-primary); width: var(--useful, 50%); }
|
|
2140
|
+
.echo-story .lh-bar .noise { background: var(--echo-ink-seal); flex: 1; opacity: 0.86; }
|
|
2141
|
+
.echo-story .lh-next { margin-top: 12px; border-top: 1px solid var(--echo-line-soft); padding-top: 11px; display: flex; align-items: center; justify-content: space-between; gap: 14px; }
|
|
2142
|
+
.echo-story .lh-next span { color: var(--echo-ink-mute); font-size: 13px; }
|
|
2143
|
+
.echo-story .lh-next .lh-dot { display: inline-block; margin-right: 6px; vertical-align: -1px; }
|
|
2144
|
+
.echo-story .lh-next b { color: var(--echo-ink-accent-deep); font-family: var(--echo-font-brand); font-size: 22px; line-height: 1; font-weight: 900; font-variant-numeric: tabular-nums; }
|
|
2145
|
+
.echo-story .story-actions { margin-top: 24px; display: flex; gap: 12px; flex-wrap: wrap; }
|
|
2146
|
+
.echo-story .story-cta { min-height: 48px; border-radius: 8px; border: 1px solid var(--echo-ink-primary); background: var(--echo-ink-primary); color: #fff; font-family: var(--echo-font-mono); font-size: 12px; font-weight: 900; letter-spacing: 0.08em; text-transform: uppercase; cursor: pointer; padding: 0 22px; }
|
|
2147
|
+
.echo-story .privacy-line { margin-top: 16px; font-family: var(--echo-font-mono); font-size: 11px; line-height: 1.6; color: var(--echo-ink-faint); }
|
|
2148
|
+
@media (max-width: 760px) { .echo-story .story-shell { grid-template-columns: 1fr; } .echo-story .story-nav { position: static; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; } .echo-story .story-nav a { grid-template-columns: 18px minmax(0, 1fr); } .echo-story .terminal-pair, .echo-story .saveflow, .echo-story .search-compare, .echo-story .carry-compare, .echo-story .time-payoff { grid-template-columns: 1fr; } .echo-story .search-turn, .echo-story .carry-turn, .echo-story .time-arrow { min-height: 54px; transform: rotate(90deg); } .echo-story .again-rhythm { flex-wrap: wrap; } .echo-story .echo-seal { top: -20px; right: -6px; width: 72px; height: 72px; } .echo-story .echo-seal img { width: 28px; height: 27px; } .echo-story .echo-seal b { font-size: 9px; } .echo-story .echo-seal span { font-size: 6px; } }
|
|
2149
|
+
@media (max-width: 620px) { .echo-story .exh, .echo-story .cmp-row { grid-template-columns: 1fr; gap: 6px; } .echo-story .cmp-label { text-align: left; } }
|
|
2150
|
+
|
|
2151
|
+
/* Context audit — a printed report sheet on the desk: serif document type, tight rhythm. */
|
|
2152
|
+
.context-audit {
|
|
2153
|
+
/* Per echo-design-system tokens.css: brand/body = Avenir Next, mono = IBM Plex Mono, script = Caveat. */
|
|
2154
|
+
--audit-serif: var(--echo-font-brand);
|
|
2155
|
+
--echo-ink-primary: #171717;
|
|
2156
|
+
--echo-ink-seal: #171717;
|
|
2157
|
+
--echo-ink-text: #242424;
|
|
2158
|
+
--echo-ink-mute: #666666;
|
|
2159
|
+
--echo-ink-faint: #929292;
|
|
2160
|
+
--echo-line-soft: #ddddda;
|
|
2161
|
+
--echo-paper-white: #ffffff;
|
|
2162
|
+
--echo-paper-soft: #f1f1ef;
|
|
2163
|
+
width: min(880px, 100%);
|
|
2164
|
+
margin: 0 auto;
|
|
2165
|
+
/* The pie gutter hangs 150px+gap off the RIGHT edge (negative margins on
|
|
2166
|
+
.audit-list-intro/.audit-row). Nudge the column left by half that so the
|
|
2167
|
+
composite block — text + pies — is what sits centered on the page. */
|
|
2168
|
+
position: relative;
|
|
2169
|
+
left: calc((150px + clamp(16px, 2.5vw, 36px)) / -2);
|
|
2170
|
+
padding: clamp(28px, 4vw, 52px) clamp(22px, 4.5vw, 64px) clamp(24px, 3.5vw, 40px);
|
|
2171
|
+
background: transparent;
|
|
2172
|
+
border: 0;
|
|
2173
|
+
box-shadow: none;
|
|
2174
|
+
color: var(--echo-ink-text);
|
|
2175
|
+
font-family: var(--audit-serif);
|
|
2176
|
+
}
|
|
2177
|
+
.context-audit * { box-sizing: border-box; }
|
|
2178
|
+
.audit-intro { padding: 0 0 12px; }
|
|
2179
|
+
.audit-eyebrow,
|
|
2180
|
+
.audit-scope,
|
|
2181
|
+
.audit-index,
|
|
2182
|
+
.audit-path,
|
|
2183
|
+
.audit-example summary,
|
|
2184
|
+
.audit-footnote {
|
|
2185
|
+
font-family: var(--echo-font-mono);
|
|
2186
|
+
font-size: 10px;
|
|
2187
|
+
font-weight: 700;
|
|
2188
|
+
letter-spacing: 0.075em;
|
|
2189
|
+
text-transform: uppercase;
|
|
2190
|
+
}
|
|
2191
|
+
.audit-eyebrow { margin: clamp(18px, 2.5vw, 28px) 0 0; color: var(--echo-ink-seal); }
|
|
2192
|
+
.audit-intro h2 {
|
|
2193
|
+
max-width: 32ch;
|
|
2194
|
+
margin: 10px 0 0;
|
|
2195
|
+
font-family: var(--echo-font-brand);
|
|
2196
|
+
font-size: clamp(24px, 3vw, 36px);
|
|
2197
|
+
font-weight: 700;
|
|
2198
|
+
line-height: 1.18;
|
|
2199
|
+
letter-spacing: -0.015em;
|
|
2200
|
+
color: var(--echo-ink-primary);
|
|
2201
|
+
}
|
|
2202
|
+
.audit-intro h2 em { color: var(--echo-ink-seal); font-style: normal; }
|
|
2203
|
+
.audit-deck,
|
|
2204
|
+
.audit-narrative {
|
|
2205
|
+
max-width: 64ch;
|
|
2206
|
+
margin: 12px 0 0;
|
|
2207
|
+
font-size: 15px;
|
|
2208
|
+
line-height: 1.58;
|
|
2209
|
+
color: var(--echo-ink-text);
|
|
2210
|
+
}
|
|
2211
|
+
.audit-method {
|
|
2212
|
+
max-width: 72ch;
|
|
2213
|
+
margin: 10px 0 0;
|
|
2214
|
+
padding-left: 12px;
|
|
2215
|
+
border-left: 2px solid #b8b8b4;
|
|
2216
|
+
font-family: var(--echo-font-mono);
|
|
2217
|
+
font-size: 10.5px;
|
|
2218
|
+
line-height: 1.55;
|
|
2219
|
+
color: var(--echo-ink-mute);
|
|
2220
|
+
}
|
|
2221
|
+
.audit-period { margin: 18px 0 0; font-family: var(--echo-font-mono); font-size: 11px; font-weight: 700; letter-spacing: 0.035em; color: var(--echo-ink-mute); }
|
|
2222
|
+
.audit-period time { color: var(--echo-ink-text); }
|
|
2223
|
+
.audit-proof { display: grid; gap: 12px; margin-top: 18px; padding: 6px 0; }
|
|
2224
|
+
.audit-narrative { max-width: 72ch; }
|
|
2225
|
+
.audit-origin { display: grid; margin-top: 16px; }
|
|
2226
|
+
.audit-origin-line { display: flex; align-items: flex-end; gap: 7px; margin: 0; flex-wrap: wrap; font-size: 15px; line-height: 1.45; }
|
|
2227
|
+
.audit-date-chip { display: inline-flex; align-items: center; min-height: 27px; padding: 2px 11px; border: 1px solid #9b9b97; border-radius: 999px; background: #f7f7f5; font-family: var(--echo-font-mono); font-size: 12px; font-weight: 700; line-height: 1; letter-spacing: 0.02em; white-space: nowrap; }
|
|
2228
|
+
.audit-repo-chip { display: inline-flex; align-items: center; gap: 6px; min-height: 27px; padding: 2px 8px 2px 6px; border: 1px solid #b8b8b4; border-radius: 5px; background: #fff; color: #171717; font-family: var(--echo-font-mono); font-size: 12px; font-weight: 700; line-height: 1; white-space: nowrap; }
|
|
2229
|
+
.audit-repo-chip svg { width: 15px; height: 15px; fill: #181717; flex: none; }
|
|
2230
|
+
.audit-sources { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
|
|
2231
|
+
.audit-source { display: inline-flex; align-items: center; gap: 7px; font-family: var(--echo-font-mono); font-size: 11px; color: var(--echo-ink-text); }
|
|
2232
|
+
.audit-source img { width: 20px; height: 20px; object-fit: contain; }
|
|
2233
|
+
.audit-source strong { font-size: 13px; }
|
|
2234
|
+
.audit-source-plus { font-family: var(--echo-font-mono); font-size: 13px; font-weight: 700; color: var(--echo-ink-faint); }
|
|
2235
|
+
.audit-source-tail { font-family: var(--echo-font-mono); font-size: 11px; color: var(--echo-ink-mute); }
|
|
2236
|
+
.audit-ledger {
|
|
2237
|
+
list-style: none;
|
|
2238
|
+
display: grid;
|
|
2239
|
+
gap: 8px;
|
|
2240
|
+
margin-top: clamp(18px, 2.5vw, 28px);
|
|
2241
|
+
padding: 0;
|
|
2242
|
+
}
|
|
2243
|
+
.audit-ledger-item { min-width: 0; display: flex; align-items: baseline; justify-content: flex-start; gap: 8px; padding: 0; }
|
|
2244
|
+
.audit-ledger-item::before { content: "•"; color: var(--echo-ink-text); font-family: var(--audit-serif); font-size: 16px; line-height: 1; }
|
|
2245
|
+
.audit-ledger-item span { font-family: var(--echo-font-mono); font-size: 13px; font-weight: 700; letter-spacing: 0.055em; text-transform: uppercase; color: var(--echo-ink-mute); }
|
|
2246
|
+
.audit-ledger-item strong { font-family: var(--echo-font-mono); font-size: 14.5px; font-weight: 700; line-height: 1; color: var(--echo-ink-primary); font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
2247
|
+
.audit-list-intro { display: grid; grid-template-columns: minmax(0, 1fr) 150px; gap: clamp(16px, 2.5vw, 36px); align-items: center; margin: 28px 0 12px; margin-right: calc(-150px - clamp(16px, 2.5vw, 36px)); }
|
|
2248
|
+
.audit-list-intro p { order: 1; margin: 0; font-family: var(--audit-serif); font-size: clamp(19px, 2vw, 23px); font-weight: 400; line-height: 1.35; color: var(--echo-ink-text); }
|
|
2249
|
+
.audit-list-intro b { font-weight: 700; }
|
|
2250
|
+
.audit-master-pie { order: 2; justify-self: center; width: 150px; height: auto; }
|
|
2251
|
+
.audit-list { border-top: 0; }
|
|
2252
|
+
.audit-row {
|
|
2253
|
+
display: grid;
|
|
2254
|
+
grid-template-columns: minmax(0, 1fr) 150px;
|
|
2255
|
+
gap: clamp(16px, 2.5vw, 36px);
|
|
2256
|
+
padding: clamp(22px, 3vw, 34px) 0;
|
|
2257
|
+
margin-right: calc(-150px - clamp(16px, 2.5vw, 36px));
|
|
2258
|
+
}
|
|
2259
|
+
.audit-list .audit-row:first-child { padding-top: 10px; }
|
|
2260
|
+
.audit-copy { order: 1; }
|
|
2261
|
+
.audit-share { order: 2; display: grid; justify-items: center; gap: 7px; align-content: start; }
|
|
2262
|
+
.audit-share-pie { width: 118px; height: auto; }
|
|
2263
|
+
.audit-share strong { font-family: var(--echo-font-mono); font-size: 12px; line-height: 1; color: var(--echo-ink-text); }
|
|
2264
|
+
.audit-share span { font-family: var(--echo-font-mono); font-size: 8px; line-height: 1; letter-spacing: 0.07em; text-transform: uppercase; color: var(--echo-ink-faint); }
|
|
2265
|
+
.audit-copy h3 {
|
|
2266
|
+
max-width: 40ch;
|
|
2267
|
+
margin: 0;
|
|
2268
|
+
font-family: var(--audit-serif);
|
|
2269
|
+
font-size: clamp(18px, 2vw, 23px);
|
|
2270
|
+
font-weight: 700;
|
|
2271
|
+
line-height: 1.25;
|
|
2272
|
+
letter-spacing: -0.005em;
|
|
2273
|
+
color: var(--echo-ink-primary);
|
|
2274
|
+
}
|
|
2275
|
+
.audit-copy > p { max-width: 64ch; margin: 9px 0 0; font-size: 14.5px; line-height: 1.58; }
|
|
2276
|
+
.audit-facts { display: grid; gap: 8px; margin-top: 14px; }
|
|
2277
|
+
.audit-fact { display: flex; align-items: baseline; gap: 8px; }
|
|
2278
|
+
.audit-fact::before { content: "•"; font-family: var(--audit-serif); font-size: 15px; line-height: 1; color: var(--echo-ink-text); }
|
|
2279
|
+
.audit-fact span { font-family: var(--echo-font-mono); font-size: 13px; color: var(--echo-ink-mute); }
|
|
2280
|
+
.audit-fact strong { font-family: var(--echo-font-mono); font-size: 14.5px; font-weight: 700; line-height: 1; color: var(--echo-ink-text); font-variant-numeric: tabular-nums; }
|
|
2281
|
+
.audit-example { margin-top: 14px; border: 0; background: transparent; }
|
|
2282
|
+
/* Sample-analysis teaser: a real session chip (agent icon + session + id + when)
|
|
2283
|
+
instead of the dashed abstract label — reads as "this is a concrete example". */
|
|
2284
|
+
.audit-example > summary {
|
|
2285
|
+
display: inline-flex;
|
|
2286
|
+
align-items: center;
|
|
2287
|
+
gap: 9px;
|
|
2288
|
+
padding: 6px 14px 6px 9px;
|
|
2289
|
+
border: 1px solid rgba(26, 26, 26, 0.14);
|
|
2290
|
+
border-radius: 999px;
|
|
2291
|
+
background: var(--echo-paper-white);
|
|
2292
|
+
color: var(--echo-ink-mute);
|
|
2293
|
+
text-transform: none;
|
|
2294
|
+
letter-spacing: 0;
|
|
2295
|
+
cursor: pointer;
|
|
2296
|
+
list-style: none;
|
|
2297
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
2298
|
+
}
|
|
2299
|
+
.audit-example > summary:hover { border-color: rgba(26, 26, 26, 0.32); box-shadow: 0 4px 14px rgba(28, 38, 60, 0.08); }
|
|
2300
|
+
.audit-example > summary::-webkit-details-marker { display: none; }
|
|
2301
|
+
.audit-example > summary::after { content: "+"; font-family: var(--audit-serif); font-size: 14px; line-height: 1; color: var(--echo-ink-mute); margin-left: 2px; }
|
|
2302
|
+
.audit-example[open] > summary::after { content: "−"; }
|
|
2303
|
+
.aet-icon { width: 17px; height: 17px; object-fit: contain; flex: none; }
|
|
2304
|
+
.aet-name { font-family: var(--echo-font-brand); font-size: 12.5px; font-weight: 700; color: var(--echo-ink-text); max-width: 34ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
2305
|
+
.aet-id, .aet-when { font-family: var(--echo-font-mono); font-size: 10px; font-weight: 700; letter-spacing: 0.04em; color: var(--echo-ink-faint); white-space: nowrap; }
|
|
2306
|
+
/* Expanded dossier hangs off a rule line so the repeated session header reads
|
|
2307
|
+
as attached detail, not stray duplication. */
|
|
2308
|
+
.audit-example-body { margin: 12px 0 4px 12px; padding: 4px 0 4px 20px; border-left: 2px solid rgba(26, 26, 26, 0.14); }
|
|
2309
|
+
.audit-session-locator { display: grid; gap: 9px; padding: 13px 0 0; }
|
|
2310
|
+
.audit-session-open { appearance: none; min-width: 0; display: flex; align-items: center; gap: 10px; padding: 0; border: 0; background: transparent; color: var(--echo-ink-text); cursor: pointer; text-align: left; justify-self: start; }
|
|
2311
|
+
.audit-session-name { min-width: 0; display: flex; align-items: center; gap: 8px; font-family: var(--audit-serif); font-size: 17px; font-weight: 700; }
|
|
2312
|
+
.audit-session-name img { width: 21px; height: 21px; object-fit: contain; flex: none; }
|
|
2313
|
+
.audit-session-name span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--echo-ink-text); text-decoration: underline; text-decoration-color: var(--session-accent, currentColor); text-decoration-thickness: 1.5px; text-underline-offset: 4px; transition: color 0.15s ease; }
|
|
2314
|
+
.audit-session-open:hover .audit-session-name span { color: var(--session-accent, currentColor); }
|
|
2315
|
+
.audit-session-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-family: var(--echo-font-mono); font-size: 10px; color: var(--echo-ink-mute); }
|
|
2316
|
+
.audit-session-repo { display: inline-flex; align-items: center; gap: 5px; color: var(--echo-ink-text); font-weight: 700; }
|
|
2317
|
+
.audit-session-repo svg { width: 13px; height: 13px; fill: currentColor; }
|
|
2318
|
+
.audit-session-status { font-family: var(--echo-font-mono); font-size: 9px; color: var(--echo-ink-mute); }
|
|
2319
|
+
.audit-session-status:empty { display: none; }
|
|
2320
|
+
.audit-turn-ledger { list-style: none; display: grid; grid-template-columns: max-content auto; column-gap: 20px; row-gap: 6px; align-items: baseline; margin: 0; padding: 10px 0 12px; font-family: var(--echo-font-mono); }
|
|
2321
|
+
.audit-turn-ledger li { display: contents; }
|
|
2322
|
+
.audit-turn-ledger span { font-size: 9px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--echo-ink-faint); }
|
|
2323
|
+
.audit-turn-ledger strong { font-size: 12px; font-variant-numeric: tabular-nums; color: var(--echo-ink-text); }
|
|
2324
|
+
.audit-turn-ledger strong em { font-style: normal; font-weight: 400; font-size: 10px; color: var(--echo-ink-mute); }
|
|
2325
|
+
.audit-example-label { display: block; margin-top: 14px; font-family: "Avenir Next", "Avenir", -apple-system, sans-serif; font-size: 12px; font-weight: 700; color: var(--echo-ink-text); }
|
|
2326
|
+
.audit-prompt { margin: 7px 0 0; padding-left: 14px; border-left: 2px solid var(--echo-ink-primary); font-family: var(--audit-serif); font-size: 14.5px; font-weight: 500; line-height: 1.55; color: var(--echo-ink-text); }
|
|
2327
|
+
.audit-prompt::before { content: "\\201C"; }
|
|
2328
|
+
.audit-retained { margin-top: 15px; }
|
|
2329
|
+
.audit-retained summary { min-height: 38px; display: flex; align-items: center; gap: 10px; padding: 0; font-family: var(--echo-font-mono); font-size: 9px; font-weight: 700; letter-spacing: 0.075em; text-transform: uppercase; color: var(--echo-ink-text); cursor: pointer; list-style: none; }
|
|
2330
|
+
.audit-retained summary::-webkit-details-marker { display: none; }
|
|
2331
|
+
.audit-retained summary::after { content: "+"; }
|
|
2332
|
+
.audit-retained[open] summary::after { content: "−"; }
|
|
2333
|
+
.audit-retained kbd { margin-left: auto; margin-right: 10px; padding: 2px 5px; border: 1px solid #cacac5; background: #fff; font-family: var(--echo-font-mono); font-size: 8px; color: var(--echo-ink-mute); }
|
|
2334
|
+
.audit-retained-item { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 6px 14px; padding: 10px 0; }
|
|
2335
|
+
.audit-retained-item b { font-family: var(--echo-font-mono); font-size: 10px; color: var(--echo-ink-text); }
|
|
2336
|
+
.audit-retained-item strong { font-family: var(--echo-font-mono); font-size: 11px; color: var(--echo-ink-text); }
|
|
2337
|
+
.audit-retained-item p { grid-column: 1 / -1; margin: 0; max-width: 62ch; font-size: 12.5px; line-height: 1.55; color: var(--echo-ink-text); }
|
|
2338
|
+
.audit-retained-item code { grid-column: 1 / -1; padding: 8px 9px; background: #f1f1ef; font-family: var(--echo-font-mono); font-size: 10px; line-height: 1.5; color: var(--echo-ink-mute); white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
2339
|
+
.audit-retained-open { grid-column: 1 / -1; justify-self: start; min-height: 0; border: 0; border-radius: 0; background: transparent; padding: 0; font-family: var(--echo-font-mono); font-size: 10px; font-weight: 700; color: var(--echo-ink-text); text-decoration: underline; text-decoration-color: var(--session-accent, currentColor); text-underline-offset: 3px; cursor: pointer; }
|
|
2340
|
+
.audit-retained-open:hover { color: var(--session-accent, currentColor); }
|
|
2341
|
+
/* Closing CTA mirrors the problem rows: text in the body column, the button out in
|
|
2342
|
+
the right pie gutter, centered on the same vertical axis as the pies above. */
|
|
2343
|
+
.audit-end {
|
|
2344
|
+
display: grid;
|
|
2345
|
+
grid-template-columns: minmax(0, 1fr) 150px;
|
|
2346
|
+
gap: clamp(16px, 2.5vw, 36px);
|
|
2347
|
+
align-items: center;
|
|
2348
|
+
margin-right: calc(-150px - clamp(16px, 2.5vw, 36px));
|
|
2349
|
+
padding: clamp(26px, 3.5vw, 40px) 0 8px;
|
|
2350
|
+
}
|
|
2351
|
+
.audit-end h3 { max-width: 34ch; margin: 0; font-family: var(--audit-serif); font-size: clamp(20px, 2.4vw, 27px); font-weight: 700; line-height: 1.2; color: var(--echo-ink-primary); }
|
|
2352
|
+
.audit-end p { max-width: 64ch; margin: 10px 0 0; font-size: 14.5px; line-height: 1.58; }
|
|
2353
|
+
.audit-end .rdo-cta { grid-column: 2; justify-self: center; }
|
|
2354
|
+
.audit-end .audit-footnote { grid-column: 1 / -1; }
|
|
2355
|
+
.audit-end .rdo-cta {
|
|
2356
|
+
margin-top: 0;
|
|
2357
|
+
min-height: 62px;
|
|
2358
|
+
display: inline-flex;
|
|
2359
|
+
align-items: center;
|
|
2360
|
+
gap: 16px;
|
|
2361
|
+
padding: 12px 34px 14px;
|
|
2362
|
+
border: 0;
|
|
2363
|
+
border-radius: 18px;
|
|
2364
|
+
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 72' preserveAspectRatio='none'%3E%3Cdefs%3E%3Cfilter id='p' x='-5%25' y='-15%25' width='110%25' height='130%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.035 0.1' numOctaves='3' seed='7'/%3E%3CfeDisplacementMap in='SourceGraphic' scale='2'/%3E%3C/filter%3E%3Cpattern id='h' width='7' height='7' patternTransform='rotate(-45)' patternUnits='userSpaceOnUse'%3E%3Cline x1='0' y1='0' x2='0' y2='7' stroke='%234c4d47' stroke-width='0.7'/%3E%3C/pattern%3E%3C/defs%3E%3Cg filter='url(%23p)'%3E%3Crect x='7' y='8' width='306' height='56' rx='15' fill='%23f1ede2'/%3E%3Crect x='7' y='8' width='306' height='56' rx='15' fill='url(%23h)' opacity='.14'/%3E%3Cg fill='none' stroke='%2322241f' stroke-linecap='round'%3E%3Crect x='7' y='8' width='306' height='56' rx='15' stroke-width='1.6'/%3E%3Crect x='7.8' y='9' width='304.6' height='54.2' rx='14.4' stroke-width='1.1' opacity='.5'/%3E%3Crect x='6.4' y='7.2' width='307.2' height='57.6' rx='15.8' stroke-width='0.9' opacity='.3'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E") center / 100% 100% no-repeat;
|
|
2365
|
+
color: var(--echo-ink-primary);
|
|
2366
|
+
font-family: var(--audit-serif);
|
|
2367
|
+
font-size: 22px;
|
|
2368
|
+
font-weight: 800;
|
|
2369
|
+
letter-spacing: 0.01em;
|
|
2370
|
+
text-transform: none;
|
|
2371
|
+
cursor: pointer;
|
|
2372
|
+
transition: transform 0.16s ease, filter 0.16s ease;
|
|
2373
|
+
}
|
|
2374
|
+
.audit-end .rdo-cta:hover { transform: translateY(-1px) scale(1.02); filter: brightness(0.98); }
|
|
2375
|
+
.audit-end .rdo-cta:active { transform: translateY(0) scale(0.99); }
|
|
2376
|
+
.audit-end .rdo-cta .cta-arrow { width: 58px; height: 24px; flex: none; transition: transform 0.16s ease; }
|
|
2377
|
+
.audit-end .rdo-cta:hover .cta-arrow { transform: translateX(5px); }
|
|
2378
|
+
/* Add [Echo] → [Codex][Claude]: full faces in rounded-square tiles, no hard corners */
|
|
2379
|
+
.audit-end .rdo-cta .cta-face { width: 38px; height: 38px; flex: none; border-radius: 11px; object-fit: contain; background: var(--echo-paper-white); border: 1.4px solid rgba(34, 36, 31, 0.75); box-shadow: 0 2px 5px rgba(28, 38, 60, 0.16); }
|
|
2380
|
+
.audit-end .rdo-cta .cta-tools { display: inline-flex; align-items: center; gap: 9px; flex: none; }
|
|
2381
|
+
.audit-end .rdo-cta .cta-tools img { width: 38px; height: 38px; padding: 6px; box-sizing: border-box; border-radius: 11px; object-fit: contain; background: var(--echo-paper-white); border: 1.4px solid rgba(34, 36, 31, 0.75); box-shadow: 0 2px 5px rgba(28, 38, 60, 0.16); display: block; }
|
|
2382
|
+
.audit-footnote { margin-top: 20px; padding-top: 12px; color: var(--echo-ink-faint); line-height: 1.7; text-transform: none; letter-spacing: 0.02em; }
|
|
2383
|
+
@media (max-width: 720px) {
|
|
2384
|
+
.context-audit { padding: 22px 16px 18px; }
|
|
2385
|
+
.audit-row { grid-template-columns: 1fr; gap: 12px; }
|
|
2386
|
+
.audit-share { justify-items: start; grid-template-columns: auto auto auto; align-items: center; }
|
|
2387
|
+
.audit-intro h2 { font-size: clamp(22px, 6vw, 28px); }
|
|
2388
|
+
.audit-meter-copy { display: grid; grid-template-columns: 1fr; gap: 4px; }
|
|
2389
|
+
.audit-facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
|
|
2390
|
+
}
|
|
2391
|
+
.echo-rdo { width: min(1280px, 100%); margin: 0 auto; color: var(--echo-ink-text); font-family: var(--echo-font-body); }
|
|
2392
|
+
.echo-rdo .rise { opacity: 0; animation: rdoRise 0.6s var(--rdo-ease) forwards; animation-delay: var(--d, 0ms); }
|
|
2393
|
+
.echo-rdo .rdo-head { border-bottom: 1px solid var(--echo-line-soft); padding-bottom: 20px; margin-bottom: 22px; }
|
|
2394
|
+
.echo-rdo .rdo-kicker { margin: 0; font-family: var(--echo-font-mono); font-size: 12px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--echo-ink-primary); }
|
|
2395
|
+
.echo-rdo .rdo-h1 { margin: 8px 0 0; font-family: var(--echo-font-brand); font-weight: 900; font-size: clamp(28px, 4vw, 46px); line-height: 1.02; letter-spacing: -0.01em; color: var(--echo-ink-primary); }
|
|
2396
|
+
.echo-rdo .rdo-lead { margin: 12px 0 0; font-size: clamp(15px, 1.3vw, 17px); line-height: 1.55; color: var(--echo-ink-text); max-width: 56ch; }
|
|
2397
|
+
.echo-rdo .rdo-lead b { color: var(--echo-ink-primary); }
|
|
2398
|
+
.echo-rdo .rdo-meta { margin-top: 14px; display: flex; flex-wrap: wrap; gap: 8px; }
|
|
2399
|
+
.echo-rdo .rdo-chip { display: inline-flex; align-items: center; gap: 7px; border: 1px solid var(--echo-line-soft); border-radius: 999px; background: var(--echo-paper-white); padding: 6px 13px; font-family: var(--echo-font-mono); font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--echo-ink-mute); }
|
|
2400
|
+
.echo-rdo .rdo-chip b { color: var(--echo-ink-primary); }
|
|
2401
|
+
.echo-rdo .rdo-chip .live { width: 7px; height: 7px; border-radius: 50%; background: var(--echo-ink-accent); }
|
|
2402
|
+
.echo-rdo .rdo-card { position: relative; background: var(--echo-paper-white); border: 1px solid var(--echo-line-soft); border-radius: 10px; padding: 18px; }
|
|
2403
|
+
.echo-rdo .clab { margin: 0; font-family: var(--echo-font-mono); font-size: 11px; font-weight: 700; letter-spacing: 0.07em; text-transform: uppercase; color: var(--echo-ink-mute); }
|
|
2404
|
+
.echo-rdo .csub { margin-top: 4px; font-family: var(--echo-font-mono); font-size: 11px; color: var(--echo-ink-faint); }
|
|
2405
|
+
.echo-rdo .csub b { color: var(--echo-ink-primary); }
|
|
2406
|
+
.echo-rdo .rdo-help { position: absolute; top: 13px; right: 13px; width: 24px; height: 24px; min-height: 0; box-sizing: border-box; border-radius: 999px; border: 1px solid var(--echo-line-soft); background: var(--echo-paper-soft); color: var(--echo-ink-faint); font-family: var(--echo-font-mono); font-size: 11px; font-weight: 800; cursor: pointer; display: grid; place-items: center; padding: 0; line-height: 1; transition: all 0.15s ease; }
|
|
2407
|
+
.echo-rdo .rdo-help:hover { color: #fff; background: var(--echo-ink-primary); border-color: var(--echo-ink-primary); }
|
|
2408
|
+
.echo-rdo .rdo-kpis { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-bottom: 14px; }
|
|
2409
|
+
.echo-rdo .rdo-kpi { min-height: 132px; display: flex; flex-direction: column; justify-content: flex-end; }
|
|
2410
|
+
.echo-rdo .rdo-kpi .num { font-family: var(--echo-font-brand); font-weight: 900; font-size: clamp(34px, 4vw, 50px); line-height: 0.9; color: var(--echo-ink-primary); font-variant-numeric: tabular-nums; }
|
|
2411
|
+
.echo-rdo .rdo-kpi.hero { border-color: rgba(199,55,47,0.30); }
|
|
2412
|
+
.echo-rdo .rdo-kpi.hero::before { content: ""; position: absolute; left: 0; top: 14px; bottom: 14px; width: 3px; border-radius: 3px; background: var(--echo-ink-seal); }
|
|
2413
|
+
.echo-rdo .rdo-kpi.hero .num { color: var(--echo-ink-seal); }
|
|
2414
|
+
.echo-rdo .rdo-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
|
|
2415
|
+
.echo-rdo .figbox { margin-top: 16px; position: relative; }
|
|
2416
|
+
.echo-rdo .rdo-histo { display: flex; align-items: flex-end; gap: 3px; height: 132px; border-bottom: 2px solid var(--echo-line-soft); }
|
|
2417
|
+
.echo-rdo .rdo-histo .bar { flex: 1; align-self: flex-end; min-height: 3px; border-radius: 3px 3px 0 0; background: var(--echo-ink-primary); transform: scaleY(0); transform-origin: bottom; animation: rdoGrow 0.5s var(--rdo-ease) forwards; animation-delay: var(--bd, 0ms); }
|
|
2418
|
+
.echo-rdo .rdo-histo .bar.night { background: var(--echo-night); }
|
|
2419
|
+
.echo-rdo .rdo-histo .bar:hover { filter: brightness(1.12); }
|
|
2420
|
+
.echo-rdo .rdo-axis { display: flex; justify-content: space-between; margin-top: 8px; font-family: var(--echo-font-mono); font-size: 10px; color: var(--echo-ink-faint); }
|
|
2421
|
+
.echo-rdo .rdo-legend { display: flex; flex-wrap: wrap; gap: 7px 16px; margin-top: 13px; font-family: var(--echo-font-mono); font-size: 11px; color: var(--echo-ink-mute); }
|
|
2422
|
+
.echo-rdo .rdo-legend i { display: inline-block; width: 11px; height: 11px; border-radius: 3px; margin-right: 6px; vertical-align: -1px; }
|
|
2423
|
+
.echo-rdo .terr { display: grid; grid-template-columns: 150px 1fr; gap: 22px; align-items: center; margin-top: 16px; }
|
|
2424
|
+
.echo-rdo .donut { width: 144px; height: 144px; position: relative; margin: 0 auto; }
|
|
2425
|
+
.echo-rdo .donut .donutSvg { width: 100%; height: 100%; display: block; opacity: 0; animation: rdoRise 0.6s var(--rdo-ease) 0.2s forwards; }
|
|
2426
|
+
.echo-rdo .donut .seg { transition: opacity 0.15s ease, stroke-width 0.15s ease; }
|
|
2427
|
+
.echo-rdo .donut.hovering .seg { opacity: 0.3; }
|
|
2428
|
+
.echo-rdo .donut.hovering .seg.on { opacity: 1; stroke-width: 7.5; }
|
|
2429
|
+
.echo-rdo .donut .seg { cursor: pointer; }
|
|
2430
|
+
.echo-rdo .donut-tip, .echo-rdo .histo-tip { position: absolute; left: 0; top: 0; pointer-events: none; transform: translate(-50%, -132%); background: var(--echo-ink-text); color: #fff; font-family: var(--echo-font-mono); font-size: 11px; font-weight: 700; padding: 5px 9px; border-radius: 6px; white-space: nowrap; z-index: 6; opacity: 0; transition: opacity 0.12s ease; box-shadow: 0 6px 16px rgba(28,38,60,0.2); }
|
|
2431
|
+
.echo-rdo .donut-tip.show, .echo-rdo .histo-tip.show { opacity: 1; }
|
|
2432
|
+
.echo-rdo .donut-tip::after, .echo-rdo .histo-tip::after { content: ""; position: absolute; left: 50%; top: 100%; transform: translateX(-50%); border: 5px solid transparent; border-top-color: var(--echo-ink-text); }
|
|
2433
|
+
.echo-rdo .donut .center { position: absolute; inset: 0; display: grid; place-items: center; text-align: center; pointer-events: none; }
|
|
2434
|
+
.echo-rdo .donut .center b { display: block; font-family: var(--echo-font-brand); font-weight: 900; font-size: 28px; color: var(--echo-ink-primary); line-height: 0.9; }
|
|
2435
|
+
.echo-rdo .donut .center span { font-family: var(--echo-font-mono); font-size: 9px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--echo-ink-mute); }
|
|
2436
|
+
.echo-rdo .rrow { display: grid; grid-template-columns: 1fr auto; gap: 10px; align-items: baseline; padding: 7px 0; }
|
|
2437
|
+
.echo-rdo .rrow .nm { font-family: var(--echo-font-mono); font-size: 12px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
2438
|
+
.echo-rdo .rrow .vl { font-family: var(--echo-font-mono); font-size: 11px; color: var(--echo-ink-text); white-space: nowrap; font-variant-numeric: tabular-nums; }
|
|
2439
|
+
.echo-rdo .rbar { grid-column: 1 / -1; height: 5px; border-radius: 999px; background: var(--echo-paper-soft); margin-top: 3px; overflow: hidden; }
|
|
2440
|
+
.echo-rdo .rbar i { display: block; height: 100%; border-radius: 999px; transform-origin: left; transform: scaleX(0); animation: rdoDraw 0.6s var(--rdo-ease) forwards; animation-delay: var(--rd, 0ms); }
|
|
2441
|
+
.echo-rdo .lblrow { font-family: var(--echo-font-mono); font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--echo-ink-mute); margin: 16px 0 7px; }
|
|
2442
|
+
.echo-rdo .stack { display: flex; height: 28px; border-radius: 7px; overflow: hidden; background: var(--echo-paper-soft); }
|
|
2443
|
+
.echo-rdo .stack .seg { min-width: 0; display: grid; place-items: center; font-family: var(--echo-font-mono); font-size: 10px; font-weight: 800; }
|
|
2444
|
+
.echo-rdo .ledgerCols { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; }
|
|
2445
|
+
.echo-rdo .ex-row { display: grid; grid-template-columns: 1fr auto; gap: 12px; align-items: baseline; padding: 11px 0; border-top: 1px solid var(--echo-line-soft); }
|
|
2446
|
+
.echo-rdo .ex-row:first-of-type { border-top: 0; padding-top: 16px; }
|
|
1430
2447
|
.echo-rdo .collapse-inner .ex-row:first-of-type { border-top: 1px solid var(--echo-line-soft); padding-top: 11px; }
|
|
1431
2448
|
.echo-rdo .ex-file { font-family: var(--echo-font-mono); font-size: 12.5px; font-weight: 700; color: var(--echo-ink-primary); }
|
|
1432
2449
|
.echo-rdo .ex-meta { margin-top: 3px; font-size: 12px; color: var(--echo-ink-mute); }
|
|
@@ -1441,6 +2458,94 @@ export function renderSetupPage() {
|
|
|
1441
2458
|
.echo-rdo .mrow2:first-child { border-top: 0; }
|
|
1442
2459
|
.echo-rdo .mrow2 b { font-size: 13px; color: var(--echo-ink-primary); }
|
|
1443
2460
|
.echo-rdo .mrow2 strong { font-family: var(--echo-font-brand); font-size: 16px; color: var(--echo-ink-text); }
|
|
2461
|
+
.echo-rdo .gs-section { margin-top: 14px; display: grid; gap: 14px; }
|
|
2462
|
+
.echo-rdo .gs-head { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 18px; align-items: start; }
|
|
2463
|
+
.echo-rdo .gs-head h3 { margin: 7px 0 0; font-family: var(--echo-font-brand); font-size: clamp(24px, 3vw, 34px); line-height: 1.05; color: var(--echo-ink-primary); }
|
|
2464
|
+
.echo-rdo .gs-lead { margin: 8px 0 0; max-width: 72ch; font-size: 14px; line-height: 1.55; color: var(--echo-ink-mute); }
|
|
2465
|
+
.echo-rdo .gs-metrics { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; min-width: min(440px, 100%); }
|
|
2466
|
+
.echo-rdo .gs-metric { background: var(--echo-paper-soft); border: 1px solid var(--echo-line-soft); border-radius: 8px; padding: 12px; }
|
|
2467
|
+
.echo-rdo .gs-metric b { display: block; font-family: var(--echo-font-brand); font-size: 24px; line-height: 1; color: var(--echo-ink-primary); font-variant-numeric: tabular-nums; }
|
|
2468
|
+
.echo-rdo .gs-metric span { display: block; margin-top: 5px; font-family: var(--echo-font-mono); font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--echo-ink-mute); }
|
|
2469
|
+
.echo-rdo .gs-focus-head { display: grid; grid-template-columns: minmax(0, 1fr) minmax(230px, 0.64fr); gap: clamp(20px, 4vw, 52px); align-items: end; }
|
|
2470
|
+
.echo-rdo .gs-focus-copy { max-width: 620px; }
|
|
2471
|
+
.echo-rdo .gs-focus-copy h3 { margin: 7px 0 0; font-family: var(--echo-font-brand); font-size: clamp(30px, 4.2vw, 52px); line-height: 0.98; letter-spacing: -0.035em; color: var(--echo-ink-primary); }
|
|
2472
|
+
.echo-rdo .gs-focus-copy h3 em { font-style: normal; color: var(--echo-ink-seal); }
|
|
2473
|
+
.echo-rdo .gs-waste-callout { position: relative; overflow: hidden; min-height: 180px; padding: 21px 22px 18px; border: 1px solid rgba(199,55,47,0.28); border-radius: 12px; background: linear-gradient(135deg, #fff8f6, var(--echo-paper-white)); box-shadow: 0 12px 28px rgba(161,51,42,0.08); }
|
|
2474
|
+
.echo-rdo .gs-waste-callout::after { content: ""; position: absolute; width: 150px; height: 150px; right: -62px; top: -64px; border: 26px solid rgba(199,55,47,0.09); border-radius: 50%; }
|
|
2475
|
+
.echo-rdo .gs-waste-callout span, .echo-rdo .gs-waste-callout b { position: relative; z-index: 1; display: block; font-family: var(--echo-font-mono); font-size: 10px; font-weight: 900; letter-spacing: 0.09em; text-transform: uppercase; color: var(--echo-ink-mute); }
|
|
2476
|
+
.echo-rdo .gs-waste-callout strong { position: relative; z-index: 1; display: block; margin: 8px 0 2px; font-family: var(--echo-font-brand); font-size: clamp(56px, 7vw, 78px); line-height: 0.8; letter-spacing: -0.06em; color: var(--echo-ink-seal); font-variant-numeric: tabular-nums; }
|
|
2477
|
+
.echo-rdo .gs-waste-callout strong small { font-size: 0.5em; letter-spacing: -0.03em; }
|
|
2478
|
+
.echo-rdo .gs-waste-callout b { color: var(--echo-ink-seal); }
|
|
2479
|
+
.echo-rdo .gs-context-meter { display: grid; gap: 8px; margin-top: 6px; }
|
|
2480
|
+
.echo-rdo .gs-context-meter-track { display: flex; height: 14px; overflow: hidden; border-radius: 999px; background: var(--echo-paper-soft); box-shadow: inset 0 0 0 1px var(--echo-line-soft); }
|
|
2481
|
+
.echo-rdo .gs-context-meter-track i { display: block; height: 100%; background: var(--echo-ink-primary); }
|
|
2482
|
+
.echo-rdo .gs-context-meter-track b { display: block; height: 100%; background: var(--echo-ink-seal); }
|
|
2483
|
+
.echo-rdo .gs-context-meter-labels { display: flex; justify-content: space-between; gap: 12px; font-family: var(--echo-font-mono); font-size: 10px; font-weight: 900; letter-spacing: 0.06em; text-transform: uppercase; color: var(--echo-ink-mute); }
|
|
2484
|
+
.echo-rdo .gs-context-meter-labels b { color: var(--echo-ink-seal); }
|
|
2485
|
+
.echo-rdo .gs-source-waste { display: grid; grid-template-columns: 1.1fr repeat(2, minmax(0, 1fr)); gap: 12px; margin-top: 4px; }
|
|
2486
|
+
.echo-rdo .gs-source-waste-card { appearance: none; min-width: 0; display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 5px 14px; align-items: baseline; min-height: 104px; padding: 17px 18px; border: 1px solid var(--echo-line-soft); border-radius: 10px; background: var(--echo-paper-white); color: inherit; font: inherit; text-align: left; cursor: pointer; transition: border-color 0.24s var(--rdo-ease), background-color 0.24s var(--rdo-ease), color 0.24s var(--rdo-ease); }
|
|
2487
|
+
.echo-rdo .gs-source-waste-card.combined { border-color: rgba(199,55,47,0.35); background: #fff8f6; }
|
|
2488
|
+
.echo-rdo .gs-source-waste-card.active { border: 2px solid var(--echo-ink-primary); padding: 16px 17px; }
|
|
2489
|
+
.echo-rdo .gs-source-waste-card.combined.active { border-color: var(--echo-ink-seal); }
|
|
2490
|
+
.echo-rdo .gs-source-waste-card:disabled { cursor: default; opacity: 0.55; }
|
|
2491
|
+
.echo-rdo .gs-source-waste-card span { font-family: var(--echo-font-mono); font-size: 10px; font-weight: 900; letter-spacing: 0.08em; text-transform: uppercase; color: var(--echo-ink-mute); }
|
|
2492
|
+
.echo-rdo .gs-source-waste-card b { font-family: var(--echo-font-brand); font-size: 28px; line-height: 0.9; color: var(--echo-ink-primary); font-variant-numeric: tabular-nums; }
|
|
2493
|
+
.echo-rdo .gs-source-waste-card.combined b { color: var(--echo-ink-seal); }
|
|
2494
|
+
.echo-rdo .gs-source-waste-card small { grid-column: 1 / -1; font-family: var(--echo-font-mono); font-size: 9px; font-weight: 800; letter-spacing: 0.06em; text-transform: uppercase; color: var(--echo-ink-faint); }
|
|
2495
|
+
.echo-rdo [data-gs-source-callout], .echo-rdo [data-gs-source-detail] { transition: opacity 0.18s var(--rdo-ease), transform 0.18s var(--rdo-ease); will-change: opacity, transform; }
|
|
2496
|
+
.echo-rdo [data-gs-source-detail] { margin-top: clamp(24px, 3vw, 36px); }
|
|
2497
|
+
.echo-rdo .gs-source-fade { opacity: 0.62; transform: translateY(3px); }
|
|
2498
|
+
.echo-rdo .gs-problem-intro { display: flex; align-items: end; justify-content: space-between; gap: 22px; margin: clamp(28px, 3vw, 42px) 0 clamp(18px, 2.5vw, 28px); padding-top: clamp(22px, 3vw, 34px); border-top: 1px solid var(--echo-line-soft); }
|
|
2499
|
+
.echo-rdo .gs-problem-intro h4 { margin: 0; font-family: var(--echo-font-brand); font-size: 22px; line-height: 1; color: var(--echo-ink-primary); }
|
|
2500
|
+
.echo-rdo .gs-problem-intro p { max-width: 58ch; margin: 0; font-size: 13px; line-height: 1.45; color: var(--echo-ink-mute); }
|
|
2501
|
+
.echo-rdo .gs-top-problems { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }
|
|
2502
|
+
.echo-rdo .gs-problem-card { min-width: 0; display: grid; grid-template-rows: auto auto auto 1fr auto; gap: 10px; padding: 18px; border: 1px solid var(--echo-line-soft); border-top: 3px solid var(--gs-accent); border-radius: 10px; background: var(--echo-paper-white); }
|
|
2503
|
+
.echo-rdo .gs-problem-card-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-family: var(--echo-font-mono); font-size: 10px; font-weight: 900; letter-spacing: 0.06em; text-transform: uppercase; color: var(--echo-ink-mute); }
|
|
2504
|
+
.echo-rdo .gs-problem-card-head b { color: var(--gs-accent); }
|
|
2505
|
+
.echo-rdo .gs-problem-card h5 { margin: 0; font-family: var(--echo-font-brand); font-size: 19px; line-height: 1.02; color: var(--echo-ink-primary); }
|
|
2506
|
+
.echo-rdo .gs-problem-card p { margin: 0; font-size: 12px; line-height: 1.45; color: var(--echo-ink-mute); }
|
|
2507
|
+
.echo-rdo .gs-example-btn { align-self: start; min-height: 32px; width: max-content; max-width: 100%; border: 1px solid var(--gs-accent); border-radius: 7px; background: color-mix(in srgb, var(--gs-accent) 9%, var(--echo-paper-white)); color: var(--gs-accent); cursor: pointer; padding: 0 11px; font-family: var(--echo-font-mono); font-size: 10px; font-weight: 900; letter-spacing: 0.06em; text-transform: uppercase; }
|
|
2508
|
+
.echo-rdo .gs-example-btn:hover { background: var(--gs-accent); color: #fff; }
|
|
2509
|
+
.echo-rdo .gs-problem-card-meter { height: 5px; overflow: hidden; border-radius: 999px; background: var(--echo-paper-soft); }
|
|
2510
|
+
.echo-rdo .gs-problem-card-meter i { display: block; height: 100%; min-width: 8%; border-radius: inherit; background: var(--gs-accent); }
|
|
2511
|
+
.echo-rdo .gs-switch { display: inline-flex; align-items: center; gap: 4px; padding: 4px; border-radius: 8px; border: 1px solid var(--echo-line-soft); background: var(--echo-paper-soft); width: max-content; max-width: 100%; overflow-x: auto; }
|
|
2512
|
+
.echo-rdo .gs-tab { min-height: 30px; border: 0; border-radius: 6px; background: transparent; padding: 0 12px; cursor: pointer; font-family: var(--echo-font-mono); font-size: 11px; font-weight: 900; letter-spacing: 0.05em; text-transform: uppercase; color: var(--echo-ink-mute); white-space: nowrap; }
|
|
2513
|
+
.echo-rdo .gs-tab.active { background: var(--echo-paper-white); color: var(--echo-ink-primary); box-shadow: 0 1px 5px rgba(28,38,60,0.08); }
|
|
2514
|
+
.echo-rdo .gs-panel { display: none; }
|
|
2515
|
+
.echo-rdo .gs-panel.active { display: grid; gap: 12px; }
|
|
2516
|
+
.echo-rdo .gs-stack { display: flex; height: 38px; border-radius: 8px; overflow: hidden; background: var(--echo-paper-soft); border: 1px solid var(--echo-line-soft); }
|
|
2517
|
+
.echo-rdo .gs-stack i { min-width: 0; display: grid; place-items: center; font-family: var(--echo-font-mono); font-size: 11px; font-weight: 900; font-style: normal; color: #fff; white-space: nowrap; }
|
|
2518
|
+
.echo-rdo .gs-stack .useful { background: var(--echo-ink-primary); }
|
|
2519
|
+
.echo-rdo .gs-stack .waste { background: var(--echo-ink-seal); }
|
|
2520
|
+
.echo-rdo .gs-body { display: grid; grid-template-columns: minmax(320px, 0.95fr) minmax(0, 1.05fr); gap: 14px; }
|
|
2521
|
+
.echo-rdo .gs-bubbles { min-height: 480px; }
|
|
2522
|
+
.echo-rdo .gs-bubble-svg { width: 100%; height: 480px; display: block; overflow: visible; }
|
|
2523
|
+
.echo-rdo .gs-bubble-svg circle { stroke: rgba(255,255,255,0.88); stroke-width: 1.3; }
|
|
2524
|
+
.echo-rdo .gs-bubble-svg text { pointer-events: none; font-family: var(--echo-font-mono); font-weight: 900; text-anchor: middle; fill: #fff; paint-order: stroke; stroke: rgba(20,30,45,0.26); stroke-width: 2px; stroke-linejoin: round; }
|
|
2525
|
+
.echo-rdo .gs-bubble-svg .sub { font-size: 8px; stroke-width: 1.5px; opacity: 0.94; }
|
|
2526
|
+
.echo-rdo .gs-bucket { border: 1px solid var(--echo-line-soft); border-radius: 8px; padding: 10px; background: var(--echo-paper-white); }
|
|
2527
|
+
.echo-rdo .gs-bucket span { display: block; font-family: var(--echo-font-mono); font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--echo-ink-mute); }
|
|
2528
|
+
.echo-rdo .gs-bucket b { display: block; margin-top: 4px; font-family: var(--echo-font-brand); font-size: 18px; color: var(--echo-ink-text); }
|
|
2529
|
+
.echo-rdo .gs-problems { display: grid; gap: 7px; }
|
|
2530
|
+
.echo-rdo .gs-problem { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 10px; align-items: center; padding: 9px 0; border-top: 1px solid var(--echo-line-soft); overflow: visible; }
|
|
2531
|
+
.echo-rdo .gs-problem:first-child { border-top: 0; }
|
|
2532
|
+
.echo-rdo .gs-id { font-family: var(--echo-font-brand); font-size: 17px; color: var(--echo-ink-primary); }
|
|
2533
|
+
.echo-rdo .gs-title { min-width: 0; overflow: visible; }
|
|
2534
|
+
.echo-rdo .gs-title-head { display: flex; align-items: center; gap: 7px; min-width: 0; overflow: visible; }
|
|
2535
|
+
.echo-rdo .gs-title b { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: var(--echo-font-mono); font-size: 12px; color: var(--echo-ink-text); }
|
|
2536
|
+
.echo-rdo .gs-title span { display: block; margin-top: 2px; font-size: 11px; color: var(--echo-ink-mute); }
|
|
2537
|
+
.echo-rdo .gs-help { position: relative; display: inline-grid; place-items: center; flex: 0 0 auto; width: 18px; height: 18px; border-radius: 50%; border: 1px solid var(--echo-line-soft); background: var(--echo-paper-soft); font-family: var(--echo-font-mono); font-size: 11px; font-weight: 900; color: var(--echo-ink-mute); cursor: help; outline: none; }
|
|
2538
|
+
.echo-rdo .gs-help:hover, .echo-rdo .gs-help:focus { color: var(--echo-ink-primary); border-color: rgba(26,58,143,0.35); background: var(--echo-paper-white); }
|
|
2539
|
+
.echo-rdo .gs-help::after { content: attr(data-tip); position: absolute; z-index: 4; right: -8px; bottom: calc(100% + 9px); width: min(310px, 72vw); padding: 10px 11px; border-radius: 8px; background: #1c263c; color: #fff; box-shadow: 0 12px 28px rgba(20,30,45,0.22); font-family: var(--echo-font-sans); font-size: 12px; font-weight: 650; line-height: 1.35; letter-spacing: 0; text-transform: none; text-align: left; opacity: 0; pointer-events: none; transform: translateY(4px); transition: opacity 0.16s ease, transform 0.16s ease; }
|
|
2540
|
+
.echo-rdo .gs-help::before { content: ""; position: absolute; z-index: 5; right: 3px; bottom: calc(100% + 4px); border: 5px solid transparent; border-top: 0; border-bottom-color: #1c263c; opacity: 0; transition: opacity 0.16s ease; }
|
|
2541
|
+
.echo-rdo .gs-help:hover::after, .echo-rdo .gs-help:focus::after, .echo-rdo .gs-help:hover::before, .echo-rdo .gs-help:focus::before { opacity: 1; transform: translateY(0); }
|
|
2542
|
+
.echo-rdo .gs-tok { font-family: var(--echo-font-brand); font-size: 18px; color: var(--echo-ink-seal); white-space: nowrap; }
|
|
2543
|
+
.echo-rdo .gs-evidence { display: grid; gap: 8px; }
|
|
2544
|
+
.echo-rdo .gs-ev { display: grid; grid-template-columns: 1fr auto; gap: 12px; padding: 9px 0; border-top: 1px solid var(--echo-line-soft); }
|
|
2545
|
+
.echo-rdo .gs-ev:first-child { border-top: 0; }
|
|
2546
|
+
.echo-rdo .gs-ev b { font-family: var(--echo-font-mono); font-size: 12px; color: var(--echo-ink-primary); }
|
|
2547
|
+
.echo-rdo .gs-ev span { display: block; margin-top: 2px; font-size: 11px; color: var(--echo-ink-mute); }
|
|
2548
|
+
.echo-rdo .gs-ev strong { font-family: var(--echo-font-brand); font-size: 18px; color: var(--echo-ink-seal); white-space: nowrap; }
|
|
1444
2549
|
.echo-rdo .outcome { margin-top: 14px; border: 1px solid rgba(78,205,196,0.45); background: linear-gradient(180deg, rgba(78,205,196,0.10), rgba(78,205,196,0.04)); border-radius: 12px; padding: clamp(20px, 3vw, 30px); display: grid; grid-template-columns: 1.5fr 1fr; gap: clamp(22px, 3vw, 44px); align-items: center; }
|
|
1445
2550
|
.echo-rdo .outcome .rdo-kicker { color: var(--echo-ink-accent-deep); }
|
|
1446
2551
|
.echo-rdo .outcome h2 { margin: 9px 0 0; font-family: var(--echo-font-brand); font-weight: 900; font-size: clamp(22px, 2.7vw, 33px); line-height: 1.05; letter-spacing: -0.01em; color: var(--echo-ink-primary); }
|
|
@@ -1465,11 +2570,12 @@ export function renderSetupPage() {
|
|
|
1465
2570
|
.echo-rdo .rdo-foot { margin-top: 22px; padding-top: 16px; border-top: 1px solid var(--echo-line-soft); display: flex; flex-wrap: wrap; gap: 12px 24px; justify-content: space-between; font-family: var(--echo-font-mono); font-size: 11px; line-height: 1.6; color: var(--echo-ink-faint); }
|
|
1466
2571
|
.echo-rdo .rdo-foot code { background: var(--echo-paper-soft); color: var(--echo-ink-primary); padding: 1px 5px; border-radius: 4px; }
|
|
1467
2572
|
.echo-rdo .rdo-foot .priv { color: var(--echo-ink-accent-deep); font-weight: 700; }
|
|
1468
|
-
@media (max-width: 860px) { .echo-rdo .rdo-kpis { grid-template-columns: repeat(2, 1fr); } .echo-rdo .rdo-grid { grid-template-columns: 1fr; } .echo-rdo .
|
|
1469
|
-
@media (max-width: 620px) { .echo-rdo .ledgerCols, .echo-rdo .terr { grid-template-columns: 1fr; } }
|
|
1470
|
-
@media (prefers-reduced-motion: reduce) { .echo-rdo .rise, .echo-rdo .rdo-histo .bar, .echo-rdo .donut .donutSvg, .echo-rdo .rbar i, .echo-rdo .tb-fill { animation: none !important; opacity: 1 !important; transform: none !important; translate: none !important; clip-path: none !important; } }
|
|
2573
|
+
@media (max-width: 860px) { .echo-rdo .rdo-kpis { grid-template-columns: repeat(2, 1fr); } .echo-rdo .rdo-grid, .echo-rdo .gs-head, .echo-rdo .gs-focus-head, .echo-rdo .gs-body, .echo-rdo .outcome { grid-template-columns: 1fr; } .echo-rdo .gs-metrics { min-width: 0; } .echo-rdo .gs-top-problems { grid-template-columns: repeat(2, minmax(0, 1fr)); } .echo-rdo .gs-source-waste { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
|
|
2574
|
+
@media (max-width: 620px) { .echo-rdo .ledgerCols, .echo-rdo .terr { grid-template-columns: 1fr; } .echo-rdo .gs-metrics { grid-template-columns: repeat(2, 1fr); } .echo-rdo .gs-bubbles, .echo-rdo .gs-bubble-svg { min-height: 420px; height: 420px; } .echo-rdo .gs-top-problems, .echo-rdo .gs-source-waste { grid-template-columns: 1fr; } .echo-rdo .gs-problem-intro { display: grid; } }
|
|
2575
|
+
@media (prefers-reduced-motion: reduce) { .echo-rdo .rise, .echo-rdo .rdo-histo .bar, .echo-rdo .donut .donutSvg, .echo-rdo .rbar i, .echo-rdo .tb-fill { animation: none !important; opacity: 1 !important; transform: none !important; translate: none !important; clip-path: none !important; } .echo-rdo [data-gs-source-callout], .echo-rdo [data-gs-source-detail] { transition: none !important; } }
|
|
1471
2576
|
/* annotation dialog */
|
|
1472
2577
|
.echo-note-dlg { border: 1px solid var(--echo-line-soft); border-radius: 14px; padding: 0; width: min(440px, calc(100vw - 32px)); background: var(--echo-paper-white); color: var(--echo-ink-text); box-shadow: 0 28px 60px -18px rgba(26,32,46,0.4); }
|
|
2578
|
+
.echo-note-dlg.example { width: min(820px, calc(100vw - 32px)); }
|
|
1473
2579
|
.echo-note-dlg[open] { animation: rdoRise 0.24s var(--rdo-ease); }
|
|
1474
2580
|
.echo-note-dlg::backdrop { background: rgba(26,32,46,0.4); }
|
|
1475
2581
|
.echo-note-dlg .nk, .echo-note-dlg .nt, .echo-note-dlg .nb { padding-left: 26px; padding-right: 26px; }
|
|
@@ -1481,11 +2587,76 @@ export function renderSetupPage() {
|
|
|
1481
2587
|
.echo-note-dlg .nb .echo { margin-top: 14px; padding: 12px 14px; border-radius: 8px; background: rgba(78,205,196,0.10); border: 1px solid rgba(78,205,196,0.3); font-size: 13px; line-height: 1.55; }
|
|
1482
2588
|
.echo-note-dlg .nb .echo b { color: var(--echo-ink-primary); }
|
|
1483
2589
|
.echo-note-dlg .nb code { background: var(--echo-paper-soft); color: var(--echo-ink-primary); padding: 1px 5px; border-radius: 4px; }
|
|
2590
|
+
.echo-note-dlg .example-list { display: grid; gap: 14px; }
|
|
2591
|
+
.echo-note-dlg .example-card { border: 1px solid var(--echo-line-soft); border-radius: 10px; background: linear-gradient(180deg, #fff 0%, var(--echo-paper-soft) 100%); padding: 16px; }
|
|
2592
|
+
.echo-note-dlg .example-meta { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 8px 12px; margin-bottom: 10px; font-family: var(--echo-font-mono); font-size: 10px; font-weight: 900; letter-spacing: 0.06em; text-transform: uppercase; color: var(--echo-ink-mute); }
|
|
2593
|
+
.echo-note-dlg .example-meta b { color: var(--echo-ink-seal); white-space: nowrap; border: 1px solid rgba(217,77,94,0.22); background: rgba(217,77,94,0.08); border-radius: 999px; padding: 5px 8px; }
|
|
2594
|
+
.echo-note-dlg .example-field { display: grid; gap: 4px; padding: 10px 0; border-top: 1px solid var(--echo-line-soft); }
|
|
2595
|
+
.echo-note-dlg .example-field:first-of-type { border-top: 0; padding-top: 0; }
|
|
2596
|
+
.echo-note-dlg .example-field b { font-family: var(--echo-font-mono); font-size: 10px; font-weight: 900; letter-spacing: 0.07em; text-transform: uppercase; color: var(--echo-ink-primary); }
|
|
2597
|
+
.echo-note-dlg .example-field span { font-size: 13px; line-height: 1.5; color: var(--echo-ink-text); overflow-wrap: anywhere; }
|
|
2598
|
+
.echo-note-dlg .example-field.missing span, .echo-note-dlg .example-field.missing .example-output { color: var(--echo-ink-faint); font-style: italic; }
|
|
2599
|
+
.echo-note-dlg .example-output { font-size: 13px; line-height: 1.58; color: var(--echo-ink-text); background: var(--echo-paper-white); border: 1px solid var(--echo-line-soft); border-radius: 8px; max-height: 300px; overflow: auto; overflow-wrap: anywhere; box-shadow: inset 0 1px 0 rgba(255,255,255,0.8); }
|
|
2600
|
+
.echo-note-dlg .example-output-text { padding: 12px 13px; }
|
|
2601
|
+
.echo-note-dlg .example-output-text p { margin: 0 0 10px; font-size: 13px; line-height: 1.58; }
|
|
2602
|
+
.echo-note-dlg .example-output-text p:last-child { margin-bottom: 0; }
|
|
2603
|
+
.echo-note-dlg .example-output-text h1, .echo-note-dlg .example-output-text h2, .echo-note-dlg .example-output-text h3 { margin: 12px 0 7px; font-family: var(--echo-font-brand); line-height: 1.15; color: var(--echo-ink-primary); }
|
|
2604
|
+
.echo-note-dlg .example-output-text h1:first-child, .echo-note-dlg .example-output-text h2:first-child, .echo-note-dlg .example-output-text h3:first-child { margin-top: 0; }
|
|
2605
|
+
.echo-note-dlg .example-output-text h1 { font-size: 18px; }
|
|
2606
|
+
.echo-note-dlg .example-output-text h2 { font-size: 16px; }
|
|
2607
|
+
.echo-note-dlg .example-output-text h3 { font-size: 14px; }
|
|
2608
|
+
.echo-note-dlg .example-output-text ul, .echo-note-dlg .example-output-text ol { margin: 0 0 10px 18px; padding: 0; }
|
|
2609
|
+
.echo-note-dlg .example-output-text li { margin: 4px 0; padding-left: 2px; }
|
|
2610
|
+
.echo-note-dlg .example-output-text blockquote { margin: 10px 0; padding: 7px 10px; border-left: 3px solid rgba(26,58,143,0.24); background: var(--echo-paper-soft); color: var(--echo-ink-mute); border-radius: 0 7px 7px 0; }
|
|
2611
|
+
.echo-note-dlg .example-output-text pre { margin: 10px 0; padding: 10px 11px; border-radius: 7px; background: #172033; color: #f7fafc; overflow: auto; white-space: pre-wrap; }
|
|
2612
|
+
.echo-note-dlg .example-output-text pre code { background: transparent; color: inherit; padding: 0; border-radius: 0; }
|
|
2613
|
+
.echo-note-dlg .example-output-text strong { color: var(--echo-ink-primary); }
|
|
2614
|
+
.echo-note-dlg .example-problem-signal { display: grid; gap: 3px; padding: 10px 13px; border-bottom: 1px solid rgba(217,77,94,0.18); background: linear-gradient(90deg, rgba(217,77,94,0.09), rgba(255,207,92,0.09)); }
|
|
2615
|
+
.echo-note-dlg .example-problem-signal small { font-family: var(--echo-font-mono); font-size: 9px; font-weight: 900; letter-spacing: 0.08em; text-transform: uppercase; color: var(--echo-ink-seal); }
|
|
2616
|
+
.echo-note-dlg .example-problem-signal span { font-size: 12.5px; line-height: 1.45; color: var(--echo-ink-primary); }
|
|
2617
|
+
.echo-note-dlg .example-output mark { background: rgba(255,207,92,0.62); color: var(--echo-ink-primary); border: 1px solid rgba(217,77,94,0.22); border-radius: 4px; padding: 0 3px; box-decoration-break: clone; -webkit-box-decoration-break: clone; }
|
|
2618
|
+
.echo-note-dlg .example-output code { font-family: var(--echo-font-mono); font-size: 12px; }
|
|
1484
2619
|
.echo-note-dlg .nclose { position: absolute; top: 13px; right: 14px; width: 30px; height: 30px; border-radius: 999px; border: 1px solid var(--echo-line-soft); background: var(--echo-paper-white); color: var(--echo-ink-mute); font-size: 18px; cursor: pointer; display: grid; place-items: center; }
|
|
1485
2620
|
.echo-note-dlg .nclose:hover { color: var(--echo-ink-seal); border-color: var(--echo-ink-seal); }
|
|
2621
|
+
.setup-workstream-marker {
|
|
2622
|
+
position: fixed;
|
|
2623
|
+
right: 14px;
|
|
2624
|
+
bottom: 14px;
|
|
2625
|
+
z-index: 2147483647;
|
|
2626
|
+
display: flex;
|
|
2627
|
+
align-items: center;
|
|
2628
|
+
gap: 9px;
|
|
2629
|
+
max-width: calc(100vw - 28px);
|
|
2630
|
+
padding: 9px 12px;
|
|
2631
|
+
border: 1px solid rgba(255,255,255,0.42);
|
|
2632
|
+
border-radius: 10px;
|
|
2633
|
+
background: rgba(26,58,143,0.94);
|
|
2634
|
+
box-shadow: 0 10px 30px rgba(16,33,79,0.24);
|
|
2635
|
+
color: #fff;
|
|
2636
|
+
font-family: var(--echo-font-mono);
|
|
2637
|
+
pointer-events: none;
|
|
2638
|
+
user-select: none;
|
|
2639
|
+
-webkit-backdrop-filter: blur(8px);
|
|
2640
|
+
backdrop-filter: blur(8px);
|
|
2641
|
+
}
|
|
2642
|
+
.setup-workstream-marker .marker-dot {
|
|
2643
|
+
width: 8px;
|
|
2644
|
+
height: 8px;
|
|
2645
|
+
flex: none;
|
|
2646
|
+
border-radius: 999px;
|
|
2647
|
+
background: var(--echo-ink-accent);
|
|
2648
|
+
box-shadow: 0 0 0 4px rgba(78,205,196,0.18);
|
|
2649
|
+
}
|
|
2650
|
+
.setup-workstream-marker .marker-copy { display: grid; gap: 2px; min-width: 0; }
|
|
2651
|
+
.setup-workstream-marker strong { font-size: 10px; line-height: 1.1; letter-spacing: 0.09em; text-transform: uppercase; }
|
|
2652
|
+
.setup-workstream-marker time { color: rgba(255,255,255,0.76); font-size: 9px; line-height: 1.2; white-space: nowrap; }
|
|
1486
2653
|
</style>
|
|
1487
2654
|
</head>
|
|
1488
2655
|
<body>
|
|
2656
|
+
<div class="setup-workstream-marker" data-testid="setup-workstream-marker" aria-label="Local setup UI workstream marker">
|
|
2657
|
+
<span class="marker-dot" aria-hidden="true"></span>
|
|
2658
|
+
<span class="marker-copy"><strong>UI workstream locked</strong><time>Updated ${SETUP_PAGE_WORKSTREAM_UPDATED_AT}</time></span>
|
|
2659
|
+
</div>
|
|
1489
2660
|
<main>
|
|
1490
2661
|
<header>
|
|
1491
2662
|
<div>
|
|
@@ -1505,11 +2676,15 @@ export function renderSetupPage() {
|
|
|
1505
2676
|
<script>
|
|
1506
2677
|
(function () {
|
|
1507
2678
|
var params = new URLSearchParams(location.search);
|
|
2679
|
+
// Preview is a server-authorized render option, never a URL-controlled client mode.
|
|
2680
|
+
var previewState = ${JSON.stringify(previewState)};
|
|
1508
2681
|
var nonce = params.get("nonce") || "";
|
|
1509
2682
|
var app = document.getElementById("app");
|
|
1510
2683
|
var title = document.getElementById("title");
|
|
1511
2684
|
var status = document.getElementById("status");
|
|
1512
2685
|
var report = null;
|
|
2686
|
+
var reportEnvelope = null;
|
|
2687
|
+
var activeScanId = "";
|
|
1513
2688
|
var stats = null;
|
|
1514
2689
|
var statsSlow = false;
|
|
1515
2690
|
var decisionMade = false;
|
|
@@ -1526,6 +2701,7 @@ export function renderSetupPage() {
|
|
|
1526
2701
|
var connectionPollStarted = false;
|
|
1527
2702
|
var statsPollStarted = false;
|
|
1528
2703
|
var reportPollStarted = false;
|
|
2704
|
+
var cityReportPushTimer = null;
|
|
1529
2705
|
var NIGHT_HOURS = { 22: 1, 23: 1, 0: 1, 1: 1, 2: 1, 3: 1 };
|
|
1530
2706
|
|
|
1531
2707
|
function esc(value) {
|
|
@@ -1630,7 +2806,7 @@ export function renderSetupPage() {
|
|
|
1630
2806
|
if (code === "FORBIDDEN_SCOPE") return "This device token cannot import history. Re-connect this device to mint an import-capable token.";
|
|
1631
2807
|
if (code === "VAULT_LOCKED") return "Your local vault is locked. Run echomem-mcp unlock, then retry.";
|
|
1632
2808
|
if (code === "IMPORT_START_TIMEOUT") return "Starting the import timed out. Retry when the connection settles.";
|
|
1633
|
-
if (code === "NOT_LOGGED_IN") return "
|
|
2809
|
+
if (code === "NOT_LOGGED_IN") return "Echo on this machine is not signed in yet. Sign in again and retry.";
|
|
1634
2810
|
return code || "Something went wrong. Retry in a moment.";
|
|
1635
2811
|
}
|
|
1636
2812
|
function closeAuthWindow() {
|
|
@@ -1676,6 +2852,223 @@ export function renderSetupPage() {
|
|
|
1676
2852
|
signin.onclick = onConnectClick;
|
|
1677
2853
|
});
|
|
1678
2854
|
}
|
|
2855
|
+
// Ported from artifacts/pencil-pie-generator.html: 3D pencil-sketch pies drawn on a
|
|
2856
|
+
// transparent canvas so the graphite sits directly on the page background.
|
|
2857
|
+
function pencilMulberry(seed) {
|
|
2858
|
+
return function () {
|
|
2859
|
+
var t = seed += 0x6D2B79F5;
|
|
2860
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
2861
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
2862
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
2863
|
+
};
|
|
2864
|
+
}
|
|
2865
|
+
function drawPencilPie(cv, slices, R) {
|
|
2866
|
+
var TILT = 0.48, u = R / 270;
|
|
2867
|
+
var TH = 58 * u, SHLEN = 154 * u, SHANG = 14 * Math.PI / 180;
|
|
2868
|
+
var density = Math.min(1.1, Math.max(0.2, u * u * 2.6));
|
|
2869
|
+
cv.width = Math.round(R * 2.62);
|
|
2870
|
+
cv.height = Math.round(R * 1.9);
|
|
2871
|
+
var cx = R * 1.14, cy = R * 0.64;
|
|
2872
|
+
var g = cv.getContext("2d");
|
|
2873
|
+
var random = pencilMulberry(2117 + Math.round(R * 7) + slices.length * 131);
|
|
2874
|
+
function ePoint(radius, angle, yOff) { return { x: cx + Math.cos(angle) * radius, y: cy + Math.sin(angle) * radius * TILT + (yOff || 0) }; }
|
|
2875
|
+
function ePath(radius) { g.beginPath(); g.ellipse(cx, cy, radius, radius * TILT, 0, 0, Math.PI * 2); }
|
|
2876
|
+
function wPath(start, sweep) {
|
|
2877
|
+
var steps = Math.max(18, Math.ceil(Math.abs(sweep) / (Math.PI / 90)));
|
|
2878
|
+
g.beginPath();
|
|
2879
|
+
g.moveTo(cx, cy);
|
|
2880
|
+
for (var i = 0; i <= steps; i++) { var p = ePoint(R, start + sweep * (i / steps), 0); g.lineTo(p.x, p.y); }
|
|
2881
|
+
g.closePath();
|
|
2882
|
+
}
|
|
2883
|
+
function pStroke(pathFn, color, width, repeats) {
|
|
2884
|
+
var rnd = pencilMulberry(808 + Math.round(width * 100) + repeats * 47);
|
|
2885
|
+
for (var i = 0; i < repeats; i++) {
|
|
2886
|
+
g.save();
|
|
2887
|
+
g.translate((rnd() - 0.5) * 1.25, (rnd() - 0.5) * 0.8);
|
|
2888
|
+
pathFn();
|
|
2889
|
+
g.strokeStyle = color;
|
|
2890
|
+
g.globalAlpha = 0.58 / Math.max(1, repeats * 0.64);
|
|
2891
|
+
g.lineWidth = width + rnd() * 0.45;
|
|
2892
|
+
g.stroke();
|
|
2893
|
+
g.restore();
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
function hatchClipped(clipFn, angle, spacing, color, alpha) {
|
|
2897
|
+
g.save();
|
|
2898
|
+
clipFn();
|
|
2899
|
+
g.clip();
|
|
2900
|
+
g.translate(cx, cy);
|
|
2901
|
+
g.rotate(angle);
|
|
2902
|
+
g.translate(-cx, -cy);
|
|
2903
|
+
g.strokeStyle = color;
|
|
2904
|
+
g.globalAlpha = alpha;
|
|
2905
|
+
g.lineWidth = 0.58;
|
|
2906
|
+
g.lineCap = "round";
|
|
2907
|
+
g.beginPath();
|
|
2908
|
+
for (var x = cx - R * 1.7; x < cx + R * 1.7; x += spacing) {
|
|
2909
|
+
var off = (random() - 0.5) * 2;
|
|
2910
|
+
g.moveTo(x + off, cy - R);
|
|
2911
|
+
g.lineTo(x + off + (random() - 0.5) * 2, cy + R);
|
|
2912
|
+
}
|
|
2913
|
+
g.stroke();
|
|
2914
|
+
g.restore();
|
|
2915
|
+
}
|
|
2916
|
+
// Graphite shadow rubbed into the page beneath the disc.
|
|
2917
|
+
var dx = Math.cos(SHANG) * SHLEN, dy = Math.sin(SHANG) * SHLEN;
|
|
2918
|
+
var shX = cx + dx * 0.46, shY = cy + TH + 12 * u + dy * 0.42;
|
|
2919
|
+
g.save();
|
|
2920
|
+
g.filter = "blur(" + Math.max(2, 6 * u * 1.6).toFixed(1) + "px)";
|
|
2921
|
+
g.fillStyle = "rgba(31,33,28,.05)";
|
|
2922
|
+
g.beginPath();
|
|
2923
|
+
g.ellipse(shX, shY, R * 1.08, R * TILT * 0.82, SHANG * 0.12, 0, Math.PI * 2);
|
|
2924
|
+
g.fill();
|
|
2925
|
+
g.restore();
|
|
2926
|
+
var L = Math.max(0.8, u * 1.9);
|
|
2927
|
+
var dust = Math.round(1380 * density), pass, i;
|
|
2928
|
+
var dustTones = ["rgba(35,37,32,.028)", "rgba(35,37,32,.055)", "rgba(35,37,32,.09)"];
|
|
2929
|
+
for (pass = 0; pass < 3; pass++) {
|
|
2930
|
+
g.beginPath();
|
|
2931
|
+
for (i = pass; i < dust; i += 3) {
|
|
2932
|
+
var ring = Math.pow(random(), 0.58 + pass * 0.16), ang = random() * Math.PI * 2, trail = Math.pow(random(), 2.1);
|
|
2933
|
+
g.rect(shX + Math.cos(ang) * R * 1.06 * ring + dx * trail * 0.48, shY + Math.sin(ang) * R * TILT * 0.76 * ring + dy * trail * 0.32, 0.35 + random() * (1.2 + pass * 0.28), (0.35 + random()) * (0.55 + random() * 0.8));
|
|
2934
|
+
}
|
|
2935
|
+
g.fillStyle = dustTones[pass];
|
|
2936
|
+
g.fill();
|
|
2937
|
+
}
|
|
2938
|
+
var strokes = Math.round(1040 * density);
|
|
2939
|
+
g.save();
|
|
2940
|
+
g.lineCap = "round";
|
|
2941
|
+
g.lineWidth = 0.72;
|
|
2942
|
+
for (pass = 0; pass < 2; pass++) {
|
|
2943
|
+
g.beginPath();
|
|
2944
|
+
var cross = pass === 0 ? -0.38 : 0.46;
|
|
2945
|
+
for (i = pass; i < strokes; i += 2) {
|
|
2946
|
+
var ring2 = Math.pow(random(), 0.62), ang2 = random() * Math.PI * 2, trail2 = Math.pow(random(), 2.05);
|
|
2947
|
+
var x0 = shX + Math.cos(ang2) * R * 1.04 * ring2 + dx * trail2 * 0.5;
|
|
2948
|
+
var y0 = shY + Math.sin(ang2) * R * TILT * 0.75 * ring2 + dy * trail2 * 0.34;
|
|
2949
|
+
var len = (3 + random() * (9 + (1 - ring2) * 14)) * L;
|
|
2950
|
+
var sa = SHANG + cross + (random() - 0.5) * 0.15;
|
|
2951
|
+
g.moveTo(x0, y0);
|
|
2952
|
+
g.lineTo(x0 + Math.cos(sa) * len, y0 + Math.sin(sa) * len * 0.62);
|
|
2953
|
+
}
|
|
2954
|
+
g.strokeStyle = pass === 0 ? "rgba(32,34,29,.14)" : "rgba(32,34,29,.11)";
|
|
2955
|
+
g.stroke();
|
|
2956
|
+
}
|
|
2957
|
+
g.restore();
|
|
2958
|
+
// Contact crevice hugging the lower ellipse.
|
|
2959
|
+
g.save();
|
|
2960
|
+
g.lineCap = "round";
|
|
2961
|
+
g.lineWidth = 0.9;
|
|
2962
|
+
var contact = Math.round(300 * density);
|
|
2963
|
+
for (pass = 0; pass < 3; pass++) {
|
|
2964
|
+
g.beginPath();
|
|
2965
|
+
for (i = pass; i < contact; i += 3) {
|
|
2966
|
+
var cAng = random() * Math.PI;
|
|
2967
|
+
var dark = 0.5 + Math.cos(cAng - SHANG) * 0.5;
|
|
2968
|
+
var depth = Math.pow(random(), 2.6) * (5 + dark * 8) * L + pass * 1.2;
|
|
2969
|
+
var cp = ePoint(R * (0.97 + random() * 0.045), cAng, TH + 1 + depth);
|
|
2970
|
+
var tx = -Math.sin(cAng), ty = Math.cos(cAng) * TILT;
|
|
2971
|
+
var norm = Math.hypot(tx, ty) || 1;
|
|
2972
|
+
var cLen = (3 + random() * (8 + dark * 11)) * L;
|
|
2973
|
+
g.moveTo(cp.x - tx / norm * cLen * 0.5, cp.y - ty / norm * cLen * 0.5);
|
|
2974
|
+
g.lineTo(cp.x + tx / norm * cLen * 0.5, cp.y + ty / norm * cLen * 0.5);
|
|
2975
|
+
}
|
|
2976
|
+
g.strokeStyle = ["rgba(27,29,25,.36)", "rgba(27,29,25,.25)", "rgba(27,29,25,.16)"][pass];
|
|
2977
|
+
g.stroke();
|
|
2978
|
+
}
|
|
2979
|
+
g.restore();
|
|
2980
|
+
// Cylinder wall.
|
|
2981
|
+
function sidePath() {
|
|
2982
|
+
var steps = 120, k;
|
|
2983
|
+
g.beginPath();
|
|
2984
|
+
for (k = 0; k <= steps; k++) { var sp = ePoint(R, Math.PI * k / steps, 0); if (k === 0) g.moveTo(sp.x, sp.y); else g.lineTo(sp.x, sp.y); }
|
|
2985
|
+
for (k = steps; k >= 0; k--) { var sp2 = ePoint(R, Math.PI * k / steps, TH); g.lineTo(sp2.x, sp2.y); }
|
|
2986
|
+
g.closePath();
|
|
2987
|
+
}
|
|
2988
|
+
sidePath();
|
|
2989
|
+
var sideTone = g.createLinearGradient(cx - Math.cos(SHANG) * R, cy - Math.sin(SHANG) * R * TILT, cx + Math.cos(SHANG) * R, cy + Math.sin(SHANG) * R * TILT);
|
|
2990
|
+
sideTone.addColorStop(0, "#e6e1d6");
|
|
2991
|
+
sideTone.addColorStop(0.47, "#d8d3c7");
|
|
2992
|
+
sideTone.addColorStop(1, "#aaa79f");
|
|
2993
|
+
g.fillStyle = sideTone;
|
|
2994
|
+
g.fill();
|
|
2995
|
+
g.strokeStyle = "rgba(39,41,35,.7)";
|
|
2996
|
+
g.lineWidth = 1.5;
|
|
2997
|
+
g.stroke();
|
|
2998
|
+
var hatchCount = Math.round(138 * Math.max(density, 0.45));
|
|
2999
|
+
g.save();
|
|
3000
|
+
g.lineCap = "round";
|
|
3001
|
+
g.lineWidth = 0.66;
|
|
3002
|
+
for (pass = 0; pass < 3; pass++) {
|
|
3003
|
+
g.beginPath();
|
|
3004
|
+
for (i = 1; i < hatchCount; i++) {
|
|
3005
|
+
var hAng = Math.PI * i / hatchCount;
|
|
3006
|
+
var hDark = 0.5 + Math.cos(hAng - SHANG) * 0.5;
|
|
3007
|
+
if (Math.min(2, Math.floor(hDark * 3)) !== pass) continue;
|
|
3008
|
+
var top = ePoint(R, hAng, 0);
|
|
3009
|
+
var jit = (random() - 0.5) * 4 * L;
|
|
3010
|
+
g.moveTo(top.x + jit * 0.25, top.y + 2 + random() * 3);
|
|
3011
|
+
g.lineTo(top.x + jit, top.y + TH - 2 - random() * 5);
|
|
3012
|
+
}
|
|
3013
|
+
g.strokeStyle = ["rgba(37,39,34,.13)", "rgba(37,39,34,.21)", "rgba(37,39,34,.31)"][pass];
|
|
3014
|
+
g.stroke();
|
|
3015
|
+
}
|
|
3016
|
+
g.restore();
|
|
3017
|
+
pStroke(function () { g.beginPath(); g.ellipse(cx, cy, R, R * TILT, 0, 0, Math.PI, false); }, "#272923", 1.05, 3);
|
|
3018
|
+
pStroke(function () { g.beginPath(); g.ellipse(cx, cy + TH, R, R * TILT, 0, 0, Math.PI, false); }, "#22241f", 1.45, 4);
|
|
3019
|
+
// Disc top.
|
|
3020
|
+
ePath(R);
|
|
3021
|
+
g.fillStyle = "#f1ede2";
|
|
3022
|
+
g.fill();
|
|
3023
|
+
hatchClipped(function () { ePath(R); }, -Math.PI / 4, Math.max(3, 8 * u * 2) / 1, "#4c4d47", 0.15);
|
|
3024
|
+
pStroke(function () { ePath(R); }, "#22241f", 1.35, 3);
|
|
3025
|
+
// Colored wedges: same clock as the CSS conic version (0% = 12 o'clock).
|
|
3026
|
+
slices.forEach(function (slice) {
|
|
3027
|
+
var pctSpan = Math.max(0, Number(slice.end || 0) - Number(slice.start || 0));
|
|
3028
|
+
if (pctSpan < 0.5) return;
|
|
3029
|
+
var start = (-90 + Number(slice.start || 0) * 3.6) * Math.PI / 180;
|
|
3030
|
+
var sweep = pctSpan * 3.6 * Math.PI / 180;
|
|
3031
|
+
wPath(start, sweep);
|
|
3032
|
+
g.fillStyle = slice.color || "#778db8";
|
|
3033
|
+
g.fill();
|
|
3034
|
+
hatchClipped(function () { wPath(start, sweep); }, Math.PI / 5, Math.max(2.4, 5.5 * u * 2), "#26354d", 0.24);
|
|
3035
|
+
pStroke(function () { wPath(start, sweep); }, "#20231f", 1.25, 3);
|
|
3036
|
+
g.save();
|
|
3037
|
+
wPath(start, sweep);
|
|
3038
|
+
g.strokeStyle = "rgba(30,32,28,.92)";
|
|
3039
|
+
g.lineWidth = 1.45;
|
|
3040
|
+
g.stroke();
|
|
3041
|
+
g.restore();
|
|
3042
|
+
});
|
|
3043
|
+
pStroke(function () { ePath(R); }, "#22241f", 1.15, 2);
|
|
3044
|
+
}
|
|
3045
|
+
function renderPencilPies() {
|
|
3046
|
+
Array.prototype.forEach.call(document.querySelectorAll("canvas[data-pencil-pie]"), function (cv) {
|
|
3047
|
+
if (cv.getAttribute("data-pie-drawn")) return;
|
|
3048
|
+
cv.setAttribute("data-pie-drawn", "1");
|
|
3049
|
+
var slices = [];
|
|
3050
|
+
try { slices = JSON.parse(cv.getAttribute("data-slices") || "[]"); } catch (_) { slices = []; }
|
|
3051
|
+
drawPencilPie(cv, slices, Number(cv.getAttribute("data-radius") || 100));
|
|
3052
|
+
});
|
|
3053
|
+
}
|
|
3054
|
+
function bindSessionLinks() {
|
|
3055
|
+
Array.prototype.forEach.call(document.querySelectorAll("[data-open-session]"), function (button) {
|
|
3056
|
+
button.onclick = async function () {
|
|
3057
|
+
var scope = button.closest(".audit-example-body");
|
|
3058
|
+
var status = scope && scope.querySelector(".audit-session-status");
|
|
3059
|
+
if (status) status.textContent = "Opening original session…";
|
|
3060
|
+
try {
|
|
3061
|
+
var result = await postJson("/open-session", {
|
|
3062
|
+
source: button.getAttribute("data-session-source"),
|
|
3063
|
+
sessionId: button.getAttribute("data-session-id")
|
|
3064
|
+
}, 8000);
|
|
3065
|
+
if (status) status.textContent = result && result.message ? result.message : "Original session opened.";
|
|
3066
|
+
} catch (error) {
|
|
3067
|
+
if (status) status.textContent = error && error.message ? error.message : "Could not open the original session.";
|
|
3068
|
+
}
|
|
3069
|
+
};
|
|
3070
|
+
});
|
|
3071
|
+
}
|
|
1679
3072
|
function bindDetailsArrow() {
|
|
1680
3073
|
var arrow = document.getElementById("details-arrow");
|
|
1681
3074
|
var target = document.getElementById("setup-details");
|
|
@@ -1683,6 +3076,203 @@ export function renderSetupPage() {
|
|
|
1683
3076
|
arrow.onclick = function () { target.scrollIntoView({ behavior: "smooth", block: "start" }); };
|
|
1684
3077
|
}
|
|
1685
3078
|
}
|
|
3079
|
+
function bindCityFieldnote() {
|
|
3080
|
+
window.addEventListener("message", function (event) {
|
|
3081
|
+
if (event.origin !== location.origin) return;
|
|
3082
|
+
var data = event.data || {};
|
|
3083
|
+
if (data.type !== "echomem:city-fieldnote") return;
|
|
3084
|
+
var note = document.getElementById("city-fieldnote");
|
|
3085
|
+
if (!note) return;
|
|
3086
|
+
var sig = JSON.stringify([data.name, data.rank, data.lede]);
|
|
3087
|
+
if (note._fnSig === sig) return;
|
|
3088
|
+
note._fnSig = sig;
|
|
3089
|
+
var save = fnSavings();
|
|
3090
|
+
var saveLine = save
|
|
3091
|
+
? '<p class="fn-save">I can help you get the same things done with ' +
|
|
3092
|
+
'<b class="fn-num" data-kind="days" data-target="' + save.days + '">0 days</b> and ' +
|
|
3093
|
+
'<b class="fn-num" data-kind="tokens" data-target="' + save.tokens + '">0 tokens</b> <b class="fn-less">less</b>.</p>'
|
|
3094
|
+
: '';
|
|
3095
|
+
note.innerHTML =
|
|
3096
|
+
'<div class="fn-speak">' +
|
|
3097
|
+
(data.avatar ? '<img src="' + esc(data.avatar) + '" alt="" />' : '') +
|
|
3098
|
+
'<div class="fn-bubble"><h2>hey, <b>' + esc(data.name || "") + '</b><span class="fn-dot">.</span></h2>' + saveLine + '</div>' +
|
|
3099
|
+
'</div>' +
|
|
3100
|
+
(data.rank ? '<p class="fn-rank">' + data.rank + (data.guide ? '<button class="fn-info" type="button" aria-label="What do these types mean?">i</button>' : '') + '</p>' : '') +
|
|
3101
|
+
(data.lede ? '<p class="fn-lede">' + data.lede + '</p>' : '');
|
|
3102
|
+
var infoBtn = note.querySelector(".fn-info");
|
|
3103
|
+
if (infoBtn) infoBtn.onclick = function () { openPersonaGuide(data.guide); };
|
|
3104
|
+
fnAnimateNums(note);
|
|
3105
|
+
});
|
|
3106
|
+
}
|
|
3107
|
+
// The taxonomy quote: every persona kind Echo can assign, with the user's own
|
|
3108
|
+
// rank/rhythm/personality highlighted. Data rides in from the city iframe.
|
|
3109
|
+
function openPersonaGuide(guide) {
|
|
3110
|
+
var dlg = document.getElementById("echo-note");
|
|
3111
|
+
if (!dlg || !guide) return;
|
|
3112
|
+
dlg.classList.remove("example");
|
|
3113
|
+
var cur = guide.current || {};
|
|
3114
|
+
function rows(list, curLabel) {
|
|
3115
|
+
return (list || []).map(function (t) {
|
|
3116
|
+
var you = t.label === curLabel;
|
|
3117
|
+
return '<div class="fn-guide-row' + (you ? ' is-you' : '') + '"><b>' + esc(t.label) + '</b>' + (you ? '<i>you</i>' : '') + '<span> — ' + esc(t.blurb) + '</span></div>';
|
|
3118
|
+
}).join("");
|
|
3119
|
+
}
|
|
3120
|
+
document.getElementById("echo-note-k").textContent = "Persona guide";
|
|
3121
|
+
document.getElementById("echo-note-t").textContent = "How Echo reads you";
|
|
3122
|
+
document.getElementById("echo-note-b").innerHTML =
|
|
3123
|
+
'<p class="fn-guide-sec">Personality · ' + (guide.traits || []).length + ' kinds</p>' + rows(guide.traits, cur.primary) +
|
|
3124
|
+
'<p class="fn-guide-sec">Rank · ' + (guide.ranks || []).length + ' levels</p>' + rows(guide.ranks, cur.rank) +
|
|
3125
|
+
'<p class="fn-guide-sec">Rhythm · ' + (guide.rhythms || []).length + ' kinds</p>' + rows(guide.rhythms, cur.rhythm);
|
|
3126
|
+
var cl = dlg.querySelector(".nclose");
|
|
3127
|
+
if (cl) cl.onclick = function () { dlg.close(); };
|
|
3128
|
+
dlg.onclick = function (e) { if (e.target === dlg) dlg.close(); };
|
|
3129
|
+
if (dlg.showModal) dlg.showModal(); else dlg.setAttribute("open", "");
|
|
3130
|
+
}
|
|
3131
|
+
// Savings projection. BOTH figures apply the canonical terminal-window noise share to the
|
|
3132
|
+
// FULL ledger, so they share one basis: days = waste share of total attention hours; tokens =
|
|
3133
|
+
// projected waste share of total provider-billed input. The absolute token figure is a
|
|
3134
|
+
// calibrated projection because intermediate requests are billed but not independently scored.
|
|
3135
|
+
function fnSavings() {
|
|
3136
|
+
var r = report || {};
|
|
3137
|
+
var gs = gsClean(r.canonicalGoldenStandard);
|
|
3138
|
+
if (!gs) return null;
|
|
3139
|
+
var pct = gsDisplayWastePct(gs);
|
|
3140
|
+
var projection = gsBillingProjection(r, gs);
|
|
3141
|
+
var tokens = Number(projection.projectedWasteTokens || 0);
|
|
3142
|
+
var wh = r.userWorkingHours || {};
|
|
3143
|
+
var hours = Number(wh.userAttentionHours || 0) * pct / 100;
|
|
3144
|
+
var perDay = Number(wh.averageDailyAttentionHours || 0);
|
|
3145
|
+
var days = Math.round(perDay > 0 ? hours / perDay : hours / 8);
|
|
3146
|
+
if (!tokens && !days) return null;
|
|
3147
|
+
return { tokens: tokens, days: days };
|
|
3148
|
+
}
|
|
3149
|
+
function fnAnimateNums(root) {
|
|
3150
|
+
var els = root.querySelectorAll(".fn-num");
|
|
3151
|
+
if (!els.length) return;
|
|
3152
|
+
var DURATION = 1500, t0 = null;
|
|
3153
|
+
function label(kind, value) {
|
|
3154
|
+
if (kind === "days") return number(value) + " days";
|
|
3155
|
+
return big(value) + " tokens";
|
|
3156
|
+
}
|
|
3157
|
+
function step(ts) {
|
|
3158
|
+
if (t0 === null) t0 = ts;
|
|
3159
|
+
var p = Math.min(1, (ts - t0) / DURATION);
|
|
3160
|
+
var eased = 1 - Math.pow(1 - p, 3);
|
|
3161
|
+
Array.prototype.forEach.call(els, function (el) {
|
|
3162
|
+
var target = Number(el.getAttribute("data-target") || 0);
|
|
3163
|
+
el.textContent = label(el.getAttribute("data-kind"), Math.round(target * eased));
|
|
3164
|
+
});
|
|
3165
|
+
if (p < 1) requestAnimationFrame(step);
|
|
3166
|
+
}
|
|
3167
|
+
requestAnimationFrame(step);
|
|
3168
|
+
}
|
|
3169
|
+
/* ---------- fullscreen city modal: 80% viewport, dirty/clean, legend, stats ---------- */
|
|
3170
|
+
function cityComparisonTo(mode) {
|
|
3171
|
+
var frame = document.getElementById("city-report-frame");
|
|
3172
|
+
if (!frame || !frame.contentWindow) return;
|
|
3173
|
+
try { frame.contentWindow.postMessage({ type: "echomem:city-comparison", mode: mode }, location.origin); } catch (_) {}
|
|
3174
|
+
}
|
|
3175
|
+
function fillCityFullData() {
|
|
3176
|
+
var legend = document.getElementById("cfc-legend");
|
|
3177
|
+
var stats = document.getElementById("cfc-stats");
|
|
3178
|
+
var r = report || {};
|
|
3179
|
+
var gs = gsClean(r.canonicalGoldenStandard);
|
|
3180
|
+
var s = (gs && gs.summary) || {};
|
|
3181
|
+
var projection = gsBillingProjection(r, gs);
|
|
3182
|
+
var cov = r.activeSessionCoverage || {};
|
|
3183
|
+
var sr = (gs && gs.sourceReports) || {};
|
|
3184
|
+
var codexShare = Number(sr.codex && sr.codex.summary && sr.codex.summary.officialInputTokens || 0);
|
|
3185
|
+
var claudeShare = Number(sr.claudeCode && sr.claudeCode.summary && sr.claudeCode.summary.officialInputTokens || 0);
|
|
3186
|
+
var basis = "of input tokens";
|
|
3187
|
+
if (!codexShare && !claudeShare) {
|
|
3188
|
+
codexShare = Number(cov.codex || 0);
|
|
3189
|
+
claudeShare = Number(cov.claudeCode || 0);
|
|
3190
|
+
basis = "of sessions";
|
|
3191
|
+
}
|
|
3192
|
+
var totalShare = codexShare + claudeShare;
|
|
3193
|
+
var codexPct = totalShare ? Math.round((codexShare / totalShare) * 100) : 0;
|
|
3194
|
+
var claudePct = totalShare ? 100 - codexPct : 0;
|
|
3195
|
+
if (legend) legend.innerHTML =
|
|
3196
|
+
'<span class="cfc-side"><i class="cfc-swatch" style="background:#10a37f"></i><img src="/hud-assets/codex.svg" alt="" />Codex <b>' + codexPct + '%</b></span>' +
|
|
3197
|
+
'<span class="cfc-side"><i class="cfc-swatch" style="background:#d97757"></i><img src="/hud-assets/claude.svg" alt="" />Claude Code <b>' + claudePct + '%</b></span>' +
|
|
3198
|
+
'<span style="color:var(--echo-ink-faint)">' + basis + '</span>';
|
|
3199
|
+
var wh = r.userWorkingHours || {};
|
|
3200
|
+
var items = [
|
|
3201
|
+
["Sessions", number(Number(s.sessionsAnalyzed || 0) || (Number(cov.codex || 0) + Number(cov.claudeCode || 0)))],
|
|
3202
|
+
["Repos analyzed", number(Number(s.reposAnalyzed || 0) || Number(r.scale && r.scale.repoCount || 0))],
|
|
3203
|
+
["Turns analyzed", number(s.turnsAnalyzed)],
|
|
3204
|
+
["Provider input", big(projection.billedInputTokens)],
|
|
3205
|
+
["Projected noise", Math.round(gs ? gsDisplayWastePct(gs) : 0) + "% · " + big(projection.projectedWasteTokens)],
|
|
3206
|
+
["Attention hours", number(Math.round(Number(wh.userAttentionHours || 0))) + "h"]
|
|
3207
|
+
];
|
|
3208
|
+
if (stats) stats.innerHTML = items.map(function (it) {
|
|
3209
|
+
return '<div class="cfc-stat"><span>' + it[0] + '</span><strong>' + it[1] + '</strong></div>';
|
|
3210
|
+
}).join("");
|
|
3211
|
+
}
|
|
3212
|
+
function bindCityFullscreen() {
|
|
3213
|
+
var btn = document.getElementById("city-full-btn");
|
|
3214
|
+
var chrome = document.getElementById("city-full-chrome");
|
|
3215
|
+
var backdrop = document.getElementById("city-full-backdrop");
|
|
3216
|
+
if (!btn || !chrome || !backdrop) return;
|
|
3217
|
+
function setState(mode) {
|
|
3218
|
+
cityComparisonTo(mode);
|
|
3219
|
+
Array.prototype.forEach.call(chrome.querySelectorAll("[data-city-state]"), function (b) {
|
|
3220
|
+
b.classList.toggle("on", b.getAttribute("data-city-state") === mode);
|
|
3221
|
+
});
|
|
3222
|
+
}
|
|
3223
|
+
function open() {
|
|
3224
|
+
fillCityFullData();
|
|
3225
|
+
document.body.classList.add("cityFull");
|
|
3226
|
+
chrome.hidden = false;
|
|
3227
|
+
backdrop.hidden = false;
|
|
3228
|
+
}
|
|
3229
|
+
function close() {
|
|
3230
|
+
document.body.classList.remove("cityFull");
|
|
3231
|
+
chrome.hidden = true;
|
|
3232
|
+
backdrop.hidden = true;
|
|
3233
|
+
setState("without");
|
|
3234
|
+
}
|
|
3235
|
+
btn.onclick = open;
|
|
3236
|
+
backdrop.onclick = close;
|
|
3237
|
+
chrome.querySelector(".cfc-close").onclick = close;
|
|
3238
|
+
Array.prototype.forEach.call(chrome.querySelectorAll("[data-city-state]"), function (b) {
|
|
3239
|
+
b.onclick = function () { setState(b.getAttribute("data-city-state")); };
|
|
3240
|
+
});
|
|
3241
|
+
Array.prototype.forEach.call(chrome.querySelectorAll("[data-city-metric]"), function (b) {
|
|
3242
|
+
b.onclick = function () {
|
|
3243
|
+
var frame = document.getElementById("city-report-frame");
|
|
3244
|
+
if (frame && frame.contentWindow) {
|
|
3245
|
+
try { frame.contentWindow.postMessage({ type: "echomem:city-metric", metric: b.getAttribute("data-city-metric") }, location.origin); } catch (_) {}
|
|
3246
|
+
}
|
|
3247
|
+
Array.prototype.forEach.call(chrome.querySelectorAll("[data-city-metric]"), function (x) {
|
|
3248
|
+
x.classList.toggle("on", x === b);
|
|
3249
|
+
});
|
|
3250
|
+
};
|
|
3251
|
+
});
|
|
3252
|
+
document.addEventListener("keydown", function (e) {
|
|
3253
|
+
if (e.key === "Escape" && document.body.classList.contains("cityFull")) close();
|
|
3254
|
+
});
|
|
3255
|
+
if (params.get("cityfull") === "1") open();
|
|
3256
|
+
}
|
|
3257
|
+
function sendReportToCity() {
|
|
3258
|
+
if (!report || !reportEnvelope || reportEnvelope.kind !== "ready") return;
|
|
3259
|
+
var frame = document.getElementById("city-report-frame");
|
|
3260
|
+
if (!frame || !frame.contentWindow) return;
|
|
3261
|
+
try {
|
|
3262
|
+
frame.contentWindow.postMessage({ type: "echomem:setup-report", nonce: nonce, envelope: reportEnvelope }, location.origin);
|
|
3263
|
+
} catch (_) {}
|
|
3264
|
+
}
|
|
3265
|
+
function scheduleReportToCity() {
|
|
3266
|
+
if (!report) return;
|
|
3267
|
+
if (cityReportPushTimer) clearTimeout(cityReportPushTimer);
|
|
3268
|
+
var attempts = 0;
|
|
3269
|
+
function push() {
|
|
3270
|
+
attempts++;
|
|
3271
|
+
sendReportToCity();
|
|
3272
|
+
if (attempts < 20) cityReportPushTimer = setTimeout(push, 300);
|
|
3273
|
+
}
|
|
3274
|
+
push();
|
|
3275
|
+
}
|
|
1686
3276
|
|
|
1687
3277
|
/* ---------- report shell (city iframe owns its own loading) ---------- */
|
|
1688
3278
|
// The report page (full-width city hero + detail sections below) is mounted ONCE. The city iframe
|
|
@@ -1692,18 +3282,83 @@ export function renderSetupPage() {
|
|
|
1692
3282
|
setCityMode(true);
|
|
1693
3283
|
app.className = "";
|
|
1694
3284
|
app.innerHTML =
|
|
1695
|
-
'<
|
|
1696
|
-
'<
|
|
1697
|
-
'<
|
|
3285
|
+
'<header class="reportTop">' +
|
|
3286
|
+
'<span class="yeahLogo"><i>Yeah!</i><b>Echo</b></span>' +
|
|
3287
|
+
'<nav class="navCenter">' +
|
|
3288
|
+
'<span class="navPill">' +
|
|
3289
|
+
'<span class="navTitle"><img class="navTitleFace" src="/hud-assets/echo-face-cutout.png" alt="" />Agent Doctor</span>' +
|
|
3290
|
+
'<span class="navDates" id="report-period"></span>' +
|
|
3291
|
+
'</span>' +
|
|
3292
|
+
'<span class="navLocal">' +
|
|
3293
|
+
'<svg viewBox="0 0 12 12" fill="none" aria-hidden="true"><rect x="2" y="5" width="8" height="5.5" rx="1.2" stroke="currentColor" stroke-width="1.3"/><path d="M4 5V3.8a2 2 0 0 1 4 0V5" stroke="currentColor" stroke-width="1.3"/></svg>' +
|
|
3294
|
+
'Local Only' +
|
|
3295
|
+
'</span>' +
|
|
3296
|
+
'</nav>' +
|
|
3297
|
+
'<button class="navConnect" data-connect-echo><span class="navDot"></span>Start Now' +
|
|
3298
|
+
'<svg viewBox="0 0 12 12" fill="none" aria-hidden="true"><path d="M3.5 8.5 8.5 3.5M4.5 3.5h4v4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>' +
|
|
3299
|
+
'</button>' +
|
|
3300
|
+
'</header>' +
|
|
3301
|
+
'<section class="cityHero"><div class="cityFigureRow">' +
|
|
3302
|
+
'<figure class="cityFig">' +
|
|
3303
|
+
'<div class="cityShell" id="city-shell">' +
|
|
3304
|
+
'<iframe id="city-report-frame" title="AI Coding City" src="/city/echo-ai-city-only.html?setup=1&figure=1&nonce=' + encodeURIComponent(nonce) + (previewState ? '&preview=1' : '') + '"></iframe>' +
|
|
3305
|
+
'</div>' +
|
|
3306
|
+
'<button class="cityFullBtn" id="city-full-btn" type="button">' +
|
|
3307
|
+
'<svg viewBox="0 0 12 12" fill="none" aria-hidden="true"><path d="M7.5 1.5h3v3M4.5 10.5h-3v-3M10.5 1.5 7 5M1.5 10.5 5 7" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>' +
|
|
3308
|
+
'Enter' +
|
|
3309
|
+
'</button>' +
|
|
3310
|
+
'</figure>' +
|
|
3311
|
+
'<div class="cityNote" id="city-fieldnote"></div>' +
|
|
1698
3312
|
'</div></section>' +
|
|
3313
|
+
'<div class="cityFullBackdrop" id="city-full-backdrop" hidden></div>' +
|
|
3314
|
+
'<div class="cityFullChrome" id="city-full-chrome" hidden>' +
|
|
3315
|
+
'<div class="cfc-bar">' +
|
|
3316
|
+
'<div class="cfc-left">' +
|
|
3317
|
+
'<div class="cfc-toggle" role="group" aria-label="City state">' +
|
|
3318
|
+
'<button type="button" data-city-state="without" class="on"><i class="cfc-dot"></i>Dirty — today</button>' +
|
|
3319
|
+
'<button type="button" data-city-state="with"><img class="cfc-face" src="/hud-assets/echo-face-cutout.png" alt="" />Clean — with Echo</button>' +
|
|
3320
|
+
'</div>' +
|
|
3321
|
+
'<div class="cfc-toggle" role="group" aria-label="City metric">' +
|
|
3322
|
+
'<button type="button" data-city-metric="time" class="on">Time</button>' +
|
|
3323
|
+
'<button type="button" data-city-metric="tokens">Tokens</button>' +
|
|
3324
|
+
'<button type="button" data-city-metric="cost">API $</button>' +
|
|
3325
|
+
'</div>' +
|
|
3326
|
+
'</div>' +
|
|
3327
|
+
'<div class="cfc-legend" id="cfc-legend"></div>' +
|
|
3328
|
+
'<button class="cfc-close" type="button" aria-label="Exit fullscreen">×</button>' +
|
|
3329
|
+
'</div>' +
|
|
3330
|
+
'<div class="cfc-stats" id="cfc-stats"></div>' +
|
|
3331
|
+
'</div>' +
|
|
1699
3332
|
'<div id="setup-details" class="detailsWrap"></div>';
|
|
1700
|
-
|
|
3333
|
+
bindCityFieldnote();
|
|
3334
|
+
bindConnect();
|
|
3335
|
+
bindCityFullscreen();
|
|
3336
|
+
var cityFrame = document.getElementById("city-report-frame");
|
|
3337
|
+
if (cityFrame) cityFrame.addEventListener("load", scheduleReportToCity);
|
|
1701
3338
|
reportMounted = true;
|
|
1702
3339
|
}
|
|
1703
|
-
function
|
|
1704
|
-
if (
|
|
1705
|
-
|
|
1706
|
-
|
|
3340
|
+
function resetReportSurface() {
|
|
3341
|
+
if (cityReportPushTimer) clearTimeout(cityReportPushTimer);
|
|
3342
|
+
cityReportPushTimer = null;
|
|
3343
|
+
reportMounted = false;
|
|
3344
|
+
document.body.classList.remove("cityFull");
|
|
3345
|
+
setCityMode(false);
|
|
3346
|
+
}
|
|
3347
|
+
function renderLoading(progress) {
|
|
3348
|
+
resetReportSurface();
|
|
3349
|
+
setHead("Reading your local coding history", "Local scan");
|
|
3350
|
+
var p = progress || {};
|
|
3351
|
+
var total = Number(p.total || 0);
|
|
3352
|
+
var scanned = Number(p.scanned || 0);
|
|
3353
|
+
var count = total > 0 ? number(scanned) + " / " + number(total) + " files" : "Checking local Codex and Claude Code history";
|
|
3354
|
+
var label = p.label || "Starting local scan";
|
|
3355
|
+
app.className = "loading-panel";
|
|
3356
|
+
app.innerHTML =
|
|
3357
|
+
'<div style="padding:24px" data-report-state="scanning">' +
|
|
3358
|
+
'<h2>Your report is being built from this machine</h2>' +
|
|
3359
|
+
'<p class="helper" style="margin-top:10px">' + esc(label) + ' · ' + esc(count) + '</p>' +
|
|
3360
|
+
'<p class="helper" style="margin-top:10px"><strong>No sample or backup data will be substituted.</strong> Personalized numbers and the city appear only after this scan passes the local-data checks.</p>' +
|
|
3361
|
+
'</div>';
|
|
1707
3362
|
}
|
|
1708
3363
|
|
|
1709
3364
|
/* ---------- forensic report (pic 1/2) ---------- */
|
|
@@ -1788,6 +3443,157 @@ export function renderSetupPage() {
|
|
|
1788
3443
|
return '<div class="stack"><div class="seg" style="width:' + ip + '%;background:var(--echo-ink-primary);color:#fff">' + ip + '% in</div><div class="seg" style="width:' + op + '%;background:var(--echo-ink-accent);color:#0c3b37">' + op + '%</div></div>' +
|
|
1789
3444
|
'<div class="rdo-legend"><span><i style="background:var(--echo-ink-primary)"></i>context loaded in</span><span><i style="background:var(--echo-ink-accent)"></i>answers written out</span></div>';
|
|
1790
3445
|
}
|
|
3446
|
+
function rdoPct(v) {
|
|
3447
|
+
return typeof v === "number" && isFinite(v) ? Math.max(0, Math.round(v * 100)) : null;
|
|
3448
|
+
}
|
|
3449
|
+
function rdoPctText(v, fallback) {
|
|
3450
|
+
var p = rdoPct(v);
|
|
3451
|
+
return p === null ? (fallback || "-") : number(p) + "%";
|
|
3452
|
+
}
|
|
3453
|
+
function rdoSnr(cm) {
|
|
3454
|
+
if (cm && typeof cm.snr === "number" && isFinite(cm.snr)) return rdoDec(cm.snr) + ":1";
|
|
3455
|
+
return "clean";
|
|
3456
|
+
}
|
|
3457
|
+
function gsClean(gs) {
|
|
3458
|
+
return gs && !gs.error && gs.summary ? gs : null;
|
|
3459
|
+
}
|
|
3460
|
+
function gsSummary(gs) {
|
|
3461
|
+
return (gsClean(gs) || {}).summary || {};
|
|
3462
|
+
}
|
|
3463
|
+
function gsBillingProjection(r, gs) {
|
|
3464
|
+
var summary = (gsClean(gs) || {}).summary || {};
|
|
3465
|
+
var supplied = r && r.billedWasteProjection;
|
|
3466
|
+
if (!supplied || supplied.method !== "canonical-window-share-over-provider-input") {
|
|
3467
|
+
throw new Error("Validated provider-billed projection is missing.");
|
|
3468
|
+
}
|
|
3469
|
+
var canonicalInput = Number(summary.officialInputTokens);
|
|
3470
|
+
var canonicalWaste = Number(summary.wasteTokens);
|
|
3471
|
+
var billedInput = Number(supplied.billedInputTokens);
|
|
3472
|
+
var multiplier = Number(supplied.billingMultiplier);
|
|
3473
|
+
var projectedWaste = Number(supplied.projectedWasteTokens);
|
|
3474
|
+
var projectedUseful = Number(supplied.projectedUsefulTokens);
|
|
3475
|
+
var tolerance = Math.max(2, billedInput * 0.000001);
|
|
3476
|
+
if (![canonicalInput, canonicalWaste, billedInput, multiplier, projectedWaste, projectedUseful].every(function (v) { return isFinite(v); }) ||
|
|
3477
|
+
canonicalInput <= 0 || canonicalWaste < 0 || canonicalWaste > canonicalInput || billedInput < canonicalInput || multiplier < 1 ||
|
|
3478
|
+
Math.abs(Number(supplied.canonicalInputTokens) - canonicalInput) > 1 ||
|
|
3479
|
+
Math.abs(Number(supplied.canonicalWasteTokens) - canonicalWaste) > 1 ||
|
|
3480
|
+
Math.abs((canonicalInput * multiplier) - billedInput) > tolerance ||
|
|
3481
|
+
Math.abs((projectedUseful + projectedWaste) - billedInput) > 1) {
|
|
3482
|
+
throw new Error("Provider and canonical token ledgers do not reconcile.");
|
|
3483
|
+
}
|
|
3484
|
+
return {
|
|
3485
|
+
billedInputTokens: billedInput,
|
|
3486
|
+
canonicalInputTokens: canonicalInput,
|
|
3487
|
+
canonicalWasteTokens: canonicalWaste,
|
|
3488
|
+
billingMultiplier: multiplier,
|
|
3489
|
+
projectedWasteTokens: projectedWaste,
|
|
3490
|
+
projectedUsefulTokens: projectedUseful
|
|
3491
|
+
};
|
|
3492
|
+
}
|
|
3493
|
+
function gsProjectedTokens(projection, canonicalTokens) {
|
|
3494
|
+
return Math.max(0, Math.round(Number(canonicalTokens || 0) * Number((projection && projection.billingMultiplier) || 1)));
|
|
3495
|
+
}
|
|
3496
|
+
function gsClampPct(value) {
|
|
3497
|
+
return Math.max(0, Math.min(100, Number(value) || 0));
|
|
3498
|
+
}
|
|
3499
|
+
function gsDisplayWastePct(gs) {
|
|
3500
|
+
return gsClampPct(((gs && gs.summary) || {}).wastePct || 0);
|
|
3501
|
+
}
|
|
3502
|
+
function gsDisplayUsefulPct(gs) {
|
|
3503
|
+
return gsClampPct(100 - gsDisplayWastePct(gs));
|
|
3504
|
+
}
|
|
3505
|
+
function gsProblemShare(report, problemId) {
|
|
3506
|
+
var summary = (report && report.summary) || {};
|
|
3507
|
+
var totalWaste = Math.max(0, Number(summary.wasteTokens || 0));
|
|
3508
|
+
if (!totalWaste) return null;
|
|
3509
|
+
if (problemId === "UNLABELED") return (Number(summary.unattributedWasteTokens || 0) / totalWaste) * 100;
|
|
3510
|
+
var problem = ((report && report.problems) || []).find(function (row) { return row.id === problemId; });
|
|
3511
|
+
if (!problem) return 0;
|
|
3512
|
+
return (Number(problem.allocatedWasteTokens || 0) / totalWaste) * 100;
|
|
3513
|
+
}
|
|
3514
|
+
function gsUnlabeledProblem(gs) {
|
|
3515
|
+
var summary = (gs && gs.summary) || {};
|
|
3516
|
+
var tokens = Math.max(0, Number(summary.unattributedWasteTokens || 0));
|
|
3517
|
+
return {
|
|
3518
|
+
id: "UNLABELED",
|
|
3519
|
+
label: "Unlabeled residue",
|
|
3520
|
+
bucket: "unattributed",
|
|
3521
|
+
category: "unattributed",
|
|
3522
|
+
allocatedWasteTokens: tokens,
|
|
3523
|
+
count: 0,
|
|
3524
|
+
sessions: Number(summary.sessionsAnalyzed || 0),
|
|
3525
|
+
};
|
|
3526
|
+
}
|
|
3527
|
+
function gsProblemRankValue(problem) {
|
|
3528
|
+
return Number.isFinite(Number(problem.displaySharePct))
|
|
3529
|
+
? Number(problem.displaySharePct || 0)
|
|
3530
|
+
: Number(problem.allocatedWasteTokens || 0);
|
|
3531
|
+
}
|
|
3532
|
+
function gsSortProblems(problems) {
|
|
3533
|
+
return problems.slice().sort(function (a, b) {
|
|
3534
|
+
return gsProblemRankValue(b) - gsProblemRankValue(a) || String(a.id || "").localeCompare(String(b.id || ""));
|
|
3535
|
+
});
|
|
3536
|
+
}
|
|
3537
|
+
function gsDisplayProblems(gs, includeUnlabeled) {
|
|
3538
|
+
var problems = ((gs && gs.problems) || []).slice();
|
|
3539
|
+
var unlabeled = includeUnlabeled ? gsUnlabeledProblem(gs) : null;
|
|
3540
|
+
if (unlabeled) problems.push(unlabeled);
|
|
3541
|
+
var sourceReports = [gs && gs.sourceReports && gs.sourceReports.codex, gs && gs.sourceReports && gs.sourceReports.claudeCode]
|
|
3542
|
+
.filter(function (rep) { return rep && rep.summary && Number(rep.summary.sessionsAnalyzed || 0) > 0; });
|
|
3543
|
+
if (sourceReports.length <= 1) return gsSortProblems(problems);
|
|
3544
|
+
return gsSortProblems(problems.map(function (problem) {
|
|
3545
|
+
var shares = sourceReports
|
|
3546
|
+
.map(function (report) { return gsProblemShare(report, problem.id); })
|
|
3547
|
+
.filter(function (share) { return share !== null && Number.isFinite(Number(share)); });
|
|
3548
|
+
var displaySharePct = shares.length
|
|
3549
|
+
? shares.reduce(function (sum, share) { return sum + Number(share || 0); }, 0) / shares.length
|
|
3550
|
+
: 0;
|
|
3551
|
+
return Object.assign({}, problem, { displaySharePct: displaySharePct });
|
|
3552
|
+
}));
|
|
3553
|
+
}
|
|
3554
|
+
function rdoScore(cm) {
|
|
3555
|
+
var score = cm && typeof cm.healthScorePct === "number" && isFinite(cm.healthScorePct) ? Math.round(cm.healthScorePct) : rdoPct(cm && cm.usefulPct);
|
|
3556
|
+
if (score === null) score = 0;
|
|
3557
|
+
return Math.max(0, Math.min(100, score));
|
|
3558
|
+
}
|
|
3559
|
+
function rdoContextStack(cm) {
|
|
3560
|
+
cm = cm || {};
|
|
3561
|
+
var noise = rdoPct(cm.noisePct);
|
|
3562
|
+
if (noise === null) noise = 0;
|
|
3563
|
+
noise = Math.max(0, Math.min(100, noise));
|
|
3564
|
+
var useful = Math.max(0, 100 - noise);
|
|
3565
|
+
return '<div class="stack"><div class="seg" style="width:' + useful + '%;background:var(--echo-ink-primary);color:#fff">' + useful + '% useful</div><div class="seg" style="width:' + noise + '%;background:var(--echo-ink-seal);color:#fff">' + noise + '% noise</div></div>' +
|
|
3566
|
+
'<div class="rdo-legend"><span><i style="background:var(--echo-ink-primary)"></i>useful resident context</span><span><i style="background:var(--echo-ink-seal)"></i>resident clutter estimate</span></div>';
|
|
3567
|
+
}
|
|
3568
|
+
function rdoContextFacts(cw, cm) {
|
|
3569
|
+
cw = cw || {}; cm = cm || {};
|
|
3570
|
+
var projected = rdoPct(cm.projectedNextFullnessPct);
|
|
3571
|
+
var limit = cm.modelContextLimitTokens ? ' / ' + big(cm.modelContextLimitTokens) + ' limit' : ' · model limit unavailable';
|
|
3572
|
+
var model = cw.model && cw.model !== "unknown" ? esc(cw.model) : "model unknown";
|
|
3573
|
+
var source = cw.modelContextLimitSource === "logged" ? "logged limit" : cw.modelContextLimitSource === "model-default" ? "model default" : "no exact limit";
|
|
3574
|
+
return '<div class="ctxFacts">' +
|
|
3575
|
+
'<div><span>latest input</span><strong>' + big(cm.latestInputTokens || 0) + limit + '</strong></div>' +
|
|
3576
|
+
'<div><span>noise</span><strong>' + big(cm.currentResidentWasteTokens || 0) + ' resident · SNR ' + esc(rdoSnr(cm)) + '</strong></div>' +
|
|
3577
|
+
'<div><span>next turn</span><strong>' + (projected === null ? big(cm.newOutputThisTurnTokens || 0) + ' new output' : number(projected) + '% projected') + '</strong></div>' +
|
|
3578
|
+
'<div><span>basis</span><strong>' + model + ' · ' + esc(source) + '</strong></div>' +
|
|
3579
|
+
'</div>';
|
|
3580
|
+
}
|
|
3581
|
+
function gsOverviewFacts(r, gs) {
|
|
3582
|
+
var rep = gsClean(gs);
|
|
3583
|
+
if (!rep) return '<div class="ctxFacts"><div><span>golden standard</span><strong>not available</strong></div></div>';
|
|
3584
|
+
var s = rep.summary || {}, b = rep.buckets || {};
|
|
3585
|
+
var projection = gsBillingProjection(r, rep);
|
|
3586
|
+
var wastePct = Math.round(gsDisplayWastePct(rep));
|
|
3587
|
+
var usefulPct = Math.round(gsDisplayUsefulPct(rep));
|
|
3588
|
+
var mix = big(gsProjectedTokens(projection, b.duplicate || 0)) + ' dup · ' + big(gsProjectedTokens(projection, b.refind || 0)) + ' re-find · ' + big(gsProjectedTokens(projection, b.dead || 0)) + ' dead';
|
|
3589
|
+
if (b.unattributed) mix += ' · ' + big(gsProjectedTokens(projection, b.unattributed || 0)) + ' unlabeled';
|
|
3590
|
+
return '<div class="ctxFacts">' +
|
|
3591
|
+
'<div><span>provider input</span><strong>' + big(projection.billedInputTokens) + '</strong></div>' +
|
|
3592
|
+
'<div><span>projected waste</span><strong>' + big(projection.projectedWasteTokens) + ' · ' + number(wastePct) + '%</strong></div>' +
|
|
3593
|
+
'<div><span>projected useful</span><strong>' + big(projection.projectedUsefulTokens) + ' · ' + number(usefulPct) + '%</strong></div>' +
|
|
3594
|
+
'<div><span>projected waste mix</span><strong>' + mix + '</strong></div>' +
|
|
3595
|
+
'</div>';
|
|
3596
|
+
}
|
|
1791
3597
|
function rdoCostStack(c) {
|
|
1792
3598
|
var t = c.total || 1; function s(col, v) { return '<div class="seg" style="width:' + Math.max(0, (v / t) * 100) + '%;background:' + col + '"></div>'; }
|
|
1793
3599
|
return '<div class="stack">' + s("var(--echo-ink-seal)", c.cacheReadCost) + s("var(--echo-ink-mute)", c.coldInputCost) + s("var(--echo-night)", c.cacheWriteCost) + s("var(--echo-ink-accent)", c.outputCost) + '</div>' +
|
|
@@ -1810,21 +3616,787 @@ export function renderSetupPage() {
|
|
|
1810
3616
|
var moreBtn = list.length > firstN ? '<button class="moreBtn" data-rdo-toggle>Show all ' + number(list.length) + ' files ↓</button>' : '';
|
|
1811
3617
|
return head + '<div class="collapse"><div class="collapse-inner">' + rest + '</div></div>' + moreBtn;
|
|
1812
3618
|
}
|
|
3619
|
+
function gsMetric(value, label) {
|
|
3620
|
+
return '<div class="gs-metric"><b>' + esc(value) + '</b><span>' + esc(label) + '</span></div>';
|
|
3621
|
+
}
|
|
3622
|
+
function gsStack(gs) {
|
|
3623
|
+
var useful = Math.round(gsDisplayUsefulPct(gs));
|
|
3624
|
+
var waste = Math.round(gsDisplayWastePct(gs));
|
|
3625
|
+
if (useful + waste > 100) useful = 100 - waste;
|
|
3626
|
+
return '<div class="gs-stack"><i class="useful" style="width:' + useful + '%">' + useful + '% useful</i><i class="waste" style="width:' + waste + '%">' + waste + '% wasteful</i></div>';
|
|
3627
|
+
}
|
|
3628
|
+
function gsColor(p, i) {
|
|
3629
|
+
return "#c7372f";
|
|
3630
|
+
}
|
|
3631
|
+
var GS_PROBLEM_DEFINITIONS = {
|
|
3632
|
+
UNLABELED: "Wasteful context that is included in the headline score but does not have enough evidence to assign to one of the 13 named problems.",
|
|
3633
|
+
P01: "Too many screenshots or images remain in context after they stop being the active reference.",
|
|
3634
|
+
P02: "The model had a known user constraint or correction signal, but the work still violated it and required repair.",
|
|
3635
|
+
P03: "Old file reads, duplicate file versions, or superseded patch drafts accumulate beside the current version.",
|
|
3636
|
+
P04: "After compaction, the model has to rediscover files, searches, or git state that it had already found.",
|
|
3637
|
+
P05: "The assistant treated work as done, but the user corrected it, so the next turn pays repair debt.",
|
|
3638
|
+
P06: "After a time gap or state gap, the model's working view no longer matches the repo or task state.",
|
|
3639
|
+
P07: "A long or aborted turn may leave file, process, or disk side effects that later turns must recover from.",
|
|
3640
|
+
P08: "Git status, git diff, or related repo-state commands are repeated without adding enough new information.",
|
|
3641
|
+
P09: "The execution path drifts and needs repeated correction, retry, validation, or reorientation.",
|
|
3642
|
+
P10: "The model repeats the same search, query, or memory lookup instead of reusing prior knowledge.",
|
|
3643
|
+
P11: "Browser/visual inspection outputs, DOM dumps, route checks, and viewport/debug metadata remain in context after serving visual QA.",
|
|
3644
|
+
P12: "Initial discovery outputs such as broad repo searches, memory lookups, first git diffs, file listings, and one-time orientation reads remain raw instead of being compressed.",
|
|
3645
|
+
P13: "Agent explanations, reconciliation notes, planning residue, and reasoning-adjacent text remain in context after they stop being operationally useful."
|
|
3646
|
+
};
|
|
3647
|
+
function gsProblemDefinition(p) {
|
|
3648
|
+
return GS_PROBLEM_DEFINITIONS[p && p.id] || "No definition is available for this problem yet.";
|
|
3649
|
+
}
|
|
3650
|
+
function gsPackBubbles(problems) {
|
|
3651
|
+
var list = (problems || []).slice(0, 13);
|
|
3652
|
+
var maxTok = Math.max.apply(null, [1].concat(list.map(function (p) { return p.allocatedWasteTokens || 0; })));
|
|
3653
|
+
var W = 320, H = 480, pad = 1.1, placed = [];
|
|
3654
|
+
list.forEach(function (p, i) {
|
|
3655
|
+
var r = (44 + Math.sqrt((p.allocatedWasteTokens || 0) / maxTok) * 70) / 2;
|
|
3656
|
+
var seed = (i + 1) * 2.399963229728653;
|
|
3657
|
+
var best = null;
|
|
3658
|
+
for (var step = 0; step < 900 && !best; step++) {
|
|
3659
|
+
var ring = Math.sqrt(step / 900);
|
|
3660
|
+
var x = W / 2 + Math.cos(seed + step * 0.62) * ring * (W / 2 - r - pad);
|
|
3661
|
+
var y = H / 2 + Math.sin(seed + step * 0.62) * ring * (H / 2 - r - pad);
|
|
3662
|
+
x = Math.max(r + pad, Math.min(W - r - pad, x));
|
|
3663
|
+
y = Math.max(r + pad, Math.min(H - r - pad, y));
|
|
3664
|
+
var ok = true;
|
|
3665
|
+
for (var j = 0; j < placed.length; j++) {
|
|
3666
|
+
var q = placed[j], dx = x - q.x, dy = y - q.y;
|
|
3667
|
+
if (Math.sqrt(dx * dx + dy * dy) < r + q.r + pad) { ok = false; break; }
|
|
3668
|
+
}
|
|
3669
|
+
if (ok) best = { p: p, x: x, y: y, r: r, i: i };
|
|
3670
|
+
}
|
|
3671
|
+
if (!best) {
|
|
3672
|
+
var col = i % 3, row = Math.floor(i / 3);
|
|
3673
|
+
best = { p: p, x: 55 + col * 105, y: 55 + row * 92, r: Math.min(r, 42), i: i };
|
|
3674
|
+
}
|
|
3675
|
+
placed.push(best);
|
|
3676
|
+
});
|
|
3677
|
+
return { W: W, H: H, placed: placed };
|
|
3678
|
+
}
|
|
3679
|
+
function gsBubbleChart(problems, totalWaste) {
|
|
3680
|
+
var packed = gsPackBubbles(problems);
|
|
3681
|
+
var circles = packed.placed.map(function (b) {
|
|
3682
|
+
var p = b.p, pct = p.allocatedWasteTokens ? Math.round((p.allocatedWasteTokens / Math.max(1, totalWaste || 1)) * 100) : 0;
|
|
3683
|
+
var tip = p.id + ' ' + p.label + ' · ' + big(p.allocatedWasteTokens || 0) + ' waste tokens · ' + gsProblemDefinition(p);
|
|
3684
|
+
return '<g tabindex="0" aria-label="' + esc(tip) + '"><title>' + esc(tip) + '</title>' +
|
|
3685
|
+
'<circle cx="' + b.x.toFixed(1) + '" cy="' + b.y.toFixed(1) + '" r="' + b.r.toFixed(1) + '" fill="' + gsColor(p, b.i) + '"></circle>' +
|
|
3686
|
+
'<text x="' + b.x.toFixed(1) + '" y="' + (b.y - 2).toFixed(1) + '" font-size="' + Math.max(11, Math.min(18, b.r * 0.46)).toFixed(1) + '">' + esc(p.id) + '</text>' +
|
|
3687
|
+
'<text class="sub" x="' + b.x.toFixed(1) + '" y="' + (b.y + 13).toFixed(1) + '">' + pct + '%</text></g>';
|
|
3688
|
+
}).join("");
|
|
3689
|
+
return '<svg class="gs-bubble-svg" viewBox="0 0 ' + packed.W + ' ' + packed.H + '" role="img" aria-label="Context problem map">' + circles + '</svg>';
|
|
3690
|
+
}
|
|
3691
|
+
function gsProblemRows(gs) {
|
|
3692
|
+
return ((gs && gs.problems) || []).slice(0, 13).map(function (p) {
|
|
3693
|
+
var definition = gsProblemDefinition(p);
|
|
3694
|
+
return '<div class="gs-problem"><span class="gs-id">' + esc(p.id) + '</span><div class="gs-title"><div class="gs-title-head"><b>' + esc(p.label) + '</b><span class="gs-help" tabindex="0" role="note" aria-label="' + esc(definition) + '" title="' + esc(definition) + '" data-tip="' + esc(definition) + '">?</span></div><span>' + esc(p.category || p.bucket) + ' · ' + number(p.count || 0) + ' findings · ' + number(p.sessions || 0) + ' sessions</span></div><strong class="gs-tok">' + big(p.allocatedWasteTokens || 0) + '</strong></div>';
|
|
3695
|
+
}).join("");
|
|
3696
|
+
}
|
|
3697
|
+
function gsEvidence(gs) {
|
|
3698
|
+
var examples = [];
|
|
3699
|
+
((gs && gs.problems) || []).forEach(function (p) {
|
|
3700
|
+
(p.examples || []).slice(0, 1).forEach(function (ex) { examples.push({ p: p, ex: ex }); });
|
|
3701
|
+
});
|
|
3702
|
+
return examples.slice(0, 5).map(function (row) {
|
|
3703
|
+
var ex = row.ex, p = row.p;
|
|
3704
|
+
return '<div class="gs-ev"><div><b>' + esc(p.id + ' · ' + p.label) + '</b><span>' + esc(ex.repo || "workspace") + ' · turn ' + number(ex.turn || 0) + ' · ' + esc(ex.evidence || "") + '</span></div><strong>' + big(ex.tokens || 0) + '</strong></div>';
|
|
3705
|
+
}).join("") || '<div class="gs-ev"><div><b>No examples captured</b><span>The analyzer produced aggregate totals but no row-level evidence.</span></div><strong>0</strong></div>';
|
|
3706
|
+
}
|
|
3707
|
+
function gsSourcePanels(gs) {
|
|
3708
|
+
var sources = [{ key: "combined", label: "Combined", report: gs }];
|
|
3709
|
+
if (gs.sourceReports && gs.sourceReports.codex) sources.push({ key: "codex", label: "Codex", report: gs.sourceReports.codex });
|
|
3710
|
+
if (gs.sourceReports && gs.sourceReports.claudeCode) sources.push({ key: "claude", label: "Claude Code", report: gs.sourceReports.claudeCode });
|
|
3711
|
+
sources = sources.filter(function (src) { return src.report && src.report.summary && src.report.summary.sessionsAnalyzed > 0; });
|
|
3712
|
+
if (!sources.length) sources = [{ key: "combined", label: "Combined", report: gs }];
|
|
3713
|
+
var tabs = '<div class="gs-switch" role="tablist" aria-label="Golden-standard source">' + sources.map(function (src, i) {
|
|
3714
|
+
return '<button class="gs-tab' + (i === 0 ? ' active' : '') + '" type="button" role="tab" aria-selected="' + (i === 0 ? 'true' : 'false') + '" data-gs-tab="' + src.key + '">' + esc(src.label) + '</button>';
|
|
3715
|
+
}).join("") + '</div>';
|
|
3716
|
+
var panels = sources.map(function (src, i) {
|
|
3717
|
+
var rep = src.report, s = rep.summary || {};
|
|
3718
|
+
var wastePct = Math.round(gsDisplayWastePct(rep));
|
|
3719
|
+
return '<div class="gs-panel' + (i === 0 ? ' active' : '') + '" data-gs-panel="' + src.key + '">' +
|
|
3720
|
+
'<div class="csub"><b>' + esc(src.label) + '</b> · ' + number(s.sessionsAnalyzed || 0) + ' sessions · ' + big(s.officialInputTokens || 0) + ' terminal input · ' + number(wastePct) + '% wasteful</div>' +
|
|
3721
|
+
gsStack(rep) +
|
|
3722
|
+
'<div class="gs-body"><div><p class="lblrow">Problem map</p><div class="gs-bubbles">' + gsBubbleChart(rep.problems || [], s.wasteTokens || 0) + '</div></div>' +
|
|
3723
|
+
'<div><p class="lblrow">All 13 problems</p><div class="gs-problems">' + gsProblemRows(rep) + '</div></div></div>' +
|
|
3724
|
+
'<div class="ledgerCols"><div><p class="lblrow">Top evidence</p><div class="gs-evidence">' + gsEvidence(rep) + '</div></div><div><p class="lblrow">Method</p><div class="gs-bucket"><span>generated from</span><b>' + esc((rep.generatedFrom || []).join(", ")) + '</b></div><div class="gs-bucket" style="margin-top:8px"><span>status</span><b>' + esc((rep.methodology || {}).status || "local") + '</b></div><p class="gs-lead">' + esc((rep.methodology || {}).note || "") + '</p></div></div>' +
|
|
3725
|
+
'</div>';
|
|
3726
|
+
}).join("");
|
|
3727
|
+
return tabs + panels;
|
|
3728
|
+
}
|
|
3729
|
+
function gsSourceList(gs) {
|
|
3730
|
+
var sourceReports = (gs && gs.sourceReports) || {};
|
|
3731
|
+
return [
|
|
3732
|
+
{ key: "combined", label: "Combined", report: gs, combined: true },
|
|
3733
|
+
{ key: "codex", label: "Codex", report: sourceReports.codex },
|
|
3734
|
+
{ key: "claude", label: "Claude Code", report: sourceReports.claudeCode },
|
|
3735
|
+
];
|
|
3736
|
+
}
|
|
3737
|
+
function gsSourceReport(gs, key) {
|
|
3738
|
+
var sources = gsSourceList(gs);
|
|
3739
|
+
for (var i = 0; i < sources.length; i++) if (sources[i].key === key) return sources[i];
|
|
3740
|
+
return sources[0];
|
|
3741
|
+
}
|
|
3742
|
+
function gsSourceWasteCards(gs) {
|
|
3743
|
+
var sources = gsSourceList(gs);
|
|
3744
|
+
return '<div class="gs-source-waste" aria-label="Wasteful context by source">' + sources.map(function (source) {
|
|
3745
|
+
var summary = (source.report && source.report.summary) || null;
|
|
3746
|
+
var pct = summary && Number.isFinite(Number(summary.wastePct))
|
|
3747
|
+
? Math.round(gsDisplayWastePct(source.report))
|
|
3748
|
+
: null;
|
|
3749
|
+
return '<button class="gs-source-waste-card' + (source.combined ? ' combined active' : '') + '" type="button" data-gs-source="' + esc(source.key) + '" aria-pressed="' + (source.combined ? 'true' : 'false') + '"' + (pct == null ? ' disabled' : '') + '><span>' + esc(source.label) + '</span><b>' + (pct == null ? '—' : number(pct) + '%') + '</b><small>' + (pct == null ? 'not scanned' : 'wasteful context') + '</small></button>';
|
|
3750
|
+
}).join("") + '</div>';
|
|
3751
|
+
}
|
|
3752
|
+
function gsSourceCallout(gs, label) {
|
|
3753
|
+
var wastePct = Math.round(gsDisplayWastePct(gs));
|
|
3754
|
+
var contextLabel = label === "Combined" ? "Of the context you use" : "Of the " + label + " context you use";
|
|
3755
|
+
return '<div class="gs-waste-callout" aria-label="' + number(wastePct) + '% of ' + esc(label) + ' context is waste"><span>' + esc(contextLabel) + '</span><strong>' + number(wastePct) + '<small>%</small></strong><b>is waste</b></div>';
|
|
3756
|
+
}
|
|
3757
|
+
function gsSourceDetail(gs, label) {
|
|
3758
|
+
var wastePct = Math.round(gsDisplayWastePct(gs));
|
|
3759
|
+
var usefulPct = Math.max(0, 100 - wastePct);
|
|
3760
|
+
var heading = label === "Combined" ? "The four biggest named sources of waste" : "The four biggest named sources of " + label + " waste";
|
|
3761
|
+
return '<div class="gs-context-meter" aria-label="' + number(wastePct) + '% waste and ' + number(usefulPct) + '% useful ' + esc(label) + ' context"><div class="gs-context-meter-track"><i style="width:' + usefulPct + '%"></i><b style="width:' + wastePct + '%"></b></div><div class="gs-context-meter-labels"><span>' + number(usefulPct) + '% useful context</span><b>' + number(wastePct) + '% waste</b></div></div>' +
|
|
3762
|
+
'<div class="gs-problem-intro"><div><p class="rdo-kicker">What is filling it</p><h4>' + esc(heading) + '</h4></div></div>' +
|
|
3763
|
+
gsTopProblems(gs);
|
|
3764
|
+
}
|
|
3765
|
+
function gsProblemExampleText(ex) {
|
|
3766
|
+
var evidence = String((ex && ex.evidence) || "").trim();
|
|
3767
|
+
if (!evidence) return "Analyzer marked this turn as evidence for the pattern.";
|
|
3768
|
+
return evidence
|
|
3769
|
+
.replace(/\\s+/g, " ")
|
|
3770
|
+
.replace(/git state command/gi, "git check")
|
|
3771
|
+
.replace(new RegExp("(^|[^A-Za-z0-9])T([0-9]+)", "g"), "$1turn $2")
|
|
3772
|
+
.slice(0, 280);
|
|
3773
|
+
}
|
|
3774
|
+
function gsExamplePreview(value, limit) {
|
|
3775
|
+
var compacted = String(value || "").replace(/\\s+/g, " ").trim();
|
|
3776
|
+
if (!compacted) return "";
|
|
3777
|
+
limit = limit || 420;
|
|
3778
|
+
return compacted.length > limit ? compacted.slice(0, limit - 1) + "…" : compacted;
|
|
3779
|
+
}
|
|
3780
|
+
function gsExampleBlockPreview(value, limit) {
|
|
3781
|
+
var compacted = String(value || "")
|
|
3782
|
+
.replace(/\\r\\n/g, "\\n")
|
|
3783
|
+
.replace(/[ \\t]+\\n/g, "\\n")
|
|
3784
|
+
.replace(/\\n{3,}/g, "\\n\\n")
|
|
3785
|
+
.trim();
|
|
3786
|
+
if (!compacted) return "";
|
|
3787
|
+
limit = limit || 900;
|
|
3788
|
+
return compacted.length > limit ? compacted.slice(0, limit - 1) + "…" : compacted;
|
|
3789
|
+
}
|
|
3790
|
+
function gsExamplePct(part, total) {
|
|
3791
|
+
part = Number(part || 0);
|
|
3792
|
+
total = Number(total || 0);
|
|
3793
|
+
return total > 0 ? Math.round((part / total) * 100) : 0;
|
|
3794
|
+
}
|
|
3795
|
+
function gsSpecificProblemText(payload, ex, stats) {
|
|
3796
|
+
var tokens = number(stats.problemTokens || 0);
|
|
3797
|
+
var problemPct = number(stats.problemPct || 0);
|
|
3798
|
+
var input = number(stats.input || 0);
|
|
3799
|
+
var usefulPct = number(stats.usefulPct || 0);
|
|
3800
|
+
var wastePct = number(stats.wastePct || 0);
|
|
3801
|
+
var command = gsExamplePreview(ex && ex.command, 120);
|
|
3802
|
+
var file = gsExamplePreview(ex && ex.file, 160);
|
|
3803
|
+
var base = stats.input
|
|
3804
|
+
? "This turn used " + input + " input tokens. About " + usefulPct + "% was useful, " + wastePct + "% was waste, and " + tokens + " tokens (" + problemPct + "% of the turn) were attributed to " + (payload.label || "this problem") + ". "
|
|
3805
|
+
: "The scanner could not recover the whole turn size, but it attributed " + tokens + " tokens to " + (payload.label || "this problem") + ". ";
|
|
3806
|
+
if (payload.id === "P13") {
|
|
3807
|
+
return base + "The wasted part was agent reasoning or note residue: internal thinking carried forward after it had stopped helping the user's request. The user sees their own prompt, but the model was also dragging along this extra scratchpad context.";
|
|
3808
|
+
}
|
|
3809
|
+
if (payload.id === "P12") {
|
|
3810
|
+
return base + "The wasted part was tool output still sitting in context after its useful signal had already been consumed." + (command ? " The captured command was '" + command + "'." : "") + " This is the kind of turn where logs become ballast instead of evidence.";
|
|
3811
|
+
}
|
|
3812
|
+
if (payload.id === "P03") {
|
|
3813
|
+
return base + "This is a ghosting-file case: old file content stayed in the working window after the current task needed fresher state." + (file ? " The file tied to the finding was '" + file + "'." : "") + " The problem is not that the file was read once; it is that stale file context kept traveling with later turns.";
|
|
3814
|
+
}
|
|
3815
|
+
if (payload.id === "P10") {
|
|
3816
|
+
return base + "The agent repeated or re-carried search results for information it had already found." + (command ? " The repeated search/tool command was '" + command + "'." : "") + " That made the turn heavier without adding new task signal.";
|
|
3817
|
+
}
|
|
3818
|
+
if (payload.id === "P08") {
|
|
3819
|
+
return base + "The waste came from repeated repo-state checks, usually git status, diff, or related bookkeeping." + (command ? " The captured command was '" + command + "'." : "") + " Those checks are useful briefly, then become stale once the repo changes.";
|
|
3820
|
+
}
|
|
3821
|
+
if (payload.id === "P11") {
|
|
3822
|
+
return base + "The waste came from visual or browser debugging output that stayed in context after the inspection had already served its purpose. Screenshots and layout probes help during verification, but they should not dominate later turns.";
|
|
3823
|
+
}
|
|
3824
|
+
if (payload.id === "P01") {
|
|
3825
|
+
return base + "The wasted context was screenshot or image evidence that no longer needed to be carried at full weight. It made the turn expensive even though only the conclusion from the image was still useful.";
|
|
3826
|
+
}
|
|
3827
|
+
if (payload.id === "P04") {
|
|
3828
|
+
return base + "The agent had to rediscover project context that should have been remembered or summarized. The cost here is repeated orientation work: reading or explaining things the session had already established.";
|
|
3829
|
+
}
|
|
3830
|
+
if (payload.id === "P06") {
|
|
3831
|
+
return base + "This is a session re-heat problem. The agent spent context reloading old state before it could move the task forward, so a large part of the turn was setup rather than progress.";
|
|
3832
|
+
}
|
|
3833
|
+
if (payload.id === "P07") {
|
|
3834
|
+
return base + "The wasted context came from leftovers after a failed or abandoned path. Keeping that dead branch in context made the next attempt pay for details that no longer guided the solution.";
|
|
3835
|
+
}
|
|
3836
|
+
if (payload.id === "P02") {
|
|
3837
|
+
return base + "The problem is instruction drift: context from earlier turns competed with what the user was asking for in this turn. The stale instruction context counted as waste because it made the agent less aligned with the current prompt.";
|
|
3838
|
+
}
|
|
3839
|
+
if (payload.id === "P05") {
|
|
3840
|
+
return base + "The waste came from premature-completion residue: context around work that looked done but still needed correction or verification. It carried closure signals instead of fresh evidence.";
|
|
3841
|
+
}
|
|
3842
|
+
if (payload.id === "P09") {
|
|
3843
|
+
return base + "The turn carried repeated fix attempts for the same issue. Some history is useful, but this much retry context means the model is paying for prior attempts instead of a compact diagnosis.";
|
|
3844
|
+
}
|
|
3845
|
+
return base + (payload.definition || "The analyzer marked this turn as the strongest example for this problem.");
|
|
3846
|
+
}
|
|
3847
|
+
function gsExampleProblemSignal(payload, ex) {
|
|
3848
|
+
var command = gsExamplePreview(ex && ex.command, 140);
|
|
3849
|
+
var file = gsExamplePreview(ex && ex.file, 180);
|
|
3850
|
+
var evidence = gsExamplePreview(ex && ex.evidence, 180);
|
|
3851
|
+
if (payload.id === "P12" && command) return "Tool output from command: " + command;
|
|
3852
|
+
if (payload.id === "P08" && command) return "Repeated repo-state command: " + command;
|
|
3853
|
+
if (payload.id === "P10" && command) return "Repeated search/tool command: " + command;
|
|
3854
|
+
if (payload.id === "P03" && file) return "Ghosting file: " + file;
|
|
3855
|
+
if (payload.id === "P13") return "Reasoning-note residue carried into this turn";
|
|
3856
|
+
if (payload.id === "P11") return "Visual/browser debug output carried past the inspection";
|
|
3857
|
+
if (payload.id === "P01") return "Screenshot or image evidence carried at full weight";
|
|
3858
|
+
return evidence || payload.definition || "";
|
|
3859
|
+
}
|
|
3860
|
+
function gsOutputHighlightCandidates(payload, ex) {
|
|
3861
|
+
var out = [];
|
|
3862
|
+
function add(value) {
|
|
3863
|
+
var v = String(value || "").replace(/\\s+/g, " ").trim();
|
|
3864
|
+
if (v.length < 4) return;
|
|
3865
|
+
if (out.indexOf(v) === -1) out.push(v);
|
|
3866
|
+
}
|
|
3867
|
+
if (payload.id === "P12" || payload.id === "P08" || payload.id === "P10") add(ex && ex.command);
|
|
3868
|
+
if (payload.id === "P03") add(ex && ex.file);
|
|
3869
|
+
add(ex && ex.evidence);
|
|
3870
|
+
add(gsExampleProblemSignal(payload, ex));
|
|
3871
|
+
return out.sort(function (a, b) { return b.length - a.length; });
|
|
3872
|
+
}
|
|
3873
|
+
function gsMarkdownInlineHtml(text) {
|
|
3874
|
+
var codeChar = String.fromCharCode(96);
|
|
3875
|
+
var html = esc(text);
|
|
3876
|
+
html = html.replace(new RegExp(codeChar + "([^" + codeChar + "]+)" + codeChar, "g"), function (_, code) {
|
|
3877
|
+
return '<code>' + code + '</code>';
|
|
3878
|
+
});
|
|
3879
|
+
html = html
|
|
3880
|
+
.replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>')
|
|
3881
|
+
.replace(/__([^_]+)__/g, '<strong>$1</strong>')
|
|
3882
|
+
.replace(/(^|\\W)\\*([^*]+)\\*(?=\\W|$)/g, '$1<em>$2</em>')
|
|
3883
|
+
.replace(/(^|\\W)_([^_]+)_(?=\\W|$)/g, '$1<em>$2</em>');
|
|
3884
|
+
return html;
|
|
3885
|
+
}
|
|
3886
|
+
function gsMarkdownHtml(text) {
|
|
3887
|
+
var fence = String.fromCharCode(96) + String.fromCharCode(96) + String.fromCharCode(96);
|
|
3888
|
+
var lines = String(text || "").replace(/\\r\\n/g, "\\n").split("\\n");
|
|
3889
|
+
var html = [];
|
|
3890
|
+
var paragraph = [];
|
|
3891
|
+
var listTag = "";
|
|
3892
|
+
var inCode = false;
|
|
3893
|
+
var codeLines = [];
|
|
3894
|
+
function flushParagraph() {
|
|
3895
|
+
if (!paragraph.length) return;
|
|
3896
|
+
html.push('<p>' + gsMarkdownInlineHtml(paragraph.join(" ")) + '</p>');
|
|
3897
|
+
paragraph = [];
|
|
3898
|
+
}
|
|
3899
|
+
function closeList() {
|
|
3900
|
+
if (!listTag) return;
|
|
3901
|
+
html.push('</' + listTag + '>');
|
|
3902
|
+
listTag = "";
|
|
3903
|
+
}
|
|
3904
|
+
function openList(tag) {
|
|
3905
|
+
if (listTag === tag) return;
|
|
3906
|
+
closeList();
|
|
3907
|
+
flushParagraph();
|
|
3908
|
+
listTag = tag;
|
|
3909
|
+
html.push('<' + tag + '>');
|
|
3910
|
+
}
|
|
3911
|
+
lines.forEach(function (line) {
|
|
3912
|
+
var trimmed = line.trim();
|
|
3913
|
+
if (trimmed.indexOf(fence) === 0) {
|
|
3914
|
+
if (inCode) {
|
|
3915
|
+
html.push('<pre><code>' + esc(codeLines.join("\\n")) + '</code></pre>');
|
|
3916
|
+
codeLines = [];
|
|
3917
|
+
inCode = false;
|
|
3918
|
+
} else {
|
|
3919
|
+
flushParagraph();
|
|
3920
|
+
closeList();
|
|
3921
|
+
inCode = true;
|
|
3922
|
+
}
|
|
3923
|
+
return;
|
|
3924
|
+
}
|
|
3925
|
+
if (inCode) {
|
|
3926
|
+
codeLines.push(line);
|
|
3927
|
+
return;
|
|
3928
|
+
}
|
|
3929
|
+
if (!trimmed) {
|
|
3930
|
+
flushParagraph();
|
|
3931
|
+
closeList();
|
|
3932
|
+
return;
|
|
3933
|
+
}
|
|
3934
|
+
var heading = /^(#{1,3})\\s+(.+)$/.exec(trimmed);
|
|
3935
|
+
if (heading) {
|
|
3936
|
+
flushParagraph();
|
|
3937
|
+
closeList();
|
|
3938
|
+
html.push('<h' + heading[1].length + '>' + gsMarkdownInlineHtml(heading[2]) + '</h' + heading[1].length + '>');
|
|
3939
|
+
return;
|
|
3940
|
+
}
|
|
3941
|
+
var quote = /^>\\s?(.+)$/.exec(trimmed);
|
|
3942
|
+
if (quote) {
|
|
3943
|
+
flushParagraph();
|
|
3944
|
+
closeList();
|
|
3945
|
+
html.push('<blockquote>' + gsMarkdownInlineHtml(quote[1]) + '</blockquote>');
|
|
3946
|
+
return;
|
|
3947
|
+
}
|
|
3948
|
+
var bullet = /^[-*]\\s+(.+)$/.exec(trimmed);
|
|
3949
|
+
if (bullet) {
|
|
3950
|
+
openList("ul");
|
|
3951
|
+
html.push('<li>' + gsMarkdownInlineHtml(bullet[1]) + '</li>');
|
|
3952
|
+
return;
|
|
3953
|
+
}
|
|
3954
|
+
var numbered = /^\\d+[.)]\\s+(.+)$/.exec(trimmed);
|
|
3955
|
+
if (numbered) {
|
|
3956
|
+
openList("ol");
|
|
3957
|
+
html.push('<li>' + gsMarkdownInlineHtml(numbered[1]) + '</li>');
|
|
3958
|
+
return;
|
|
3959
|
+
}
|
|
3960
|
+
closeList();
|
|
3961
|
+
paragraph.push(trimmed);
|
|
3962
|
+
});
|
|
3963
|
+
if (inCode) html.push('<pre><code>' + esc(codeLines.join("\\n")) + '</code></pre>');
|
|
3964
|
+
flushParagraph();
|
|
3965
|
+
closeList();
|
|
3966
|
+
return html.join("");
|
|
3967
|
+
}
|
|
3968
|
+
function gsHighlightedOutputHtml(text, candidates) {
|
|
3969
|
+
var raw = String(text || "");
|
|
3970
|
+
var lower = raw.toLowerCase();
|
|
3971
|
+
var best = null;
|
|
3972
|
+
candidates.forEach(function (candidate) {
|
|
3973
|
+
var idx = lower.indexOf(String(candidate || "").toLowerCase());
|
|
3974
|
+
if (idx < 0) return;
|
|
3975
|
+
if (!best || String(candidate).length > best.length) best = { index: idx, length: String(candidate).length };
|
|
3976
|
+
});
|
|
3977
|
+
if (!best) return { html: gsMarkdownHtml(raw), matched: false };
|
|
3978
|
+
var startMark = "\\uE000";
|
|
3979
|
+
var endMark = "\\uE001";
|
|
3980
|
+
var markedRaw = raw.slice(0, best.index) + startMark + raw.slice(best.index, best.index + best.length) + endMark + raw.slice(best.index + best.length);
|
|
3981
|
+
return {
|
|
3982
|
+
html: gsMarkdownHtml(markedRaw).replace(new RegExp(startMark, "g"), "<mark>").replace(new RegExp(endMark, "g"), "</mark>"),
|
|
3983
|
+
matched: true,
|
|
3984
|
+
};
|
|
3985
|
+
}
|
|
3986
|
+
function gsExampleTime(ms) {
|
|
3987
|
+
ms = Number(ms || 0);
|
|
3988
|
+
if (!isFinite(ms) || ms <= 0) return "time unavailable";
|
|
3989
|
+
var d = new Date(ms);
|
|
3990
|
+
return d.toLocaleString("en-US", { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" });
|
|
3991
|
+
}
|
|
3992
|
+
function gsAgentLabel(source) {
|
|
3993
|
+
source = String(source || "").toLowerCase();
|
|
3994
|
+
if (source === "claude-code" || source === "claude" || source === "claudecode") return "Claude Code";
|
|
3995
|
+
if (source === "codex") return "Codex";
|
|
3996
|
+
return "Agent unknown";
|
|
3997
|
+
}
|
|
3998
|
+
function gsExamplePayload(p, shareLabel) {
|
|
3999
|
+
return {
|
|
4000
|
+
id: p && p.id || "",
|
|
4001
|
+
label: p && p.label || "Untitled problem",
|
|
4002
|
+
category: p && (p.category || p.bucket) || "Context problem",
|
|
4003
|
+
share: shareLabel,
|
|
4004
|
+
definition: gsProblemDefinition(p),
|
|
4005
|
+
tokens: Number((p && p.allocatedWasteTokens) || 0),
|
|
4006
|
+
examples: ((p && p.examples) || []).slice(0, 1).map(function (ex) {
|
|
4007
|
+
return {
|
|
4008
|
+
repo: ex && ex.repo || "workspace",
|
|
4009
|
+
session: ex && ex.session || "",
|
|
4010
|
+
agentSource: ex && ex.agentSource || "",
|
|
4011
|
+
turn: ex && ex.turn || 0,
|
|
4012
|
+
tokens: ex && ex.tokens || 0,
|
|
4013
|
+
prompt: gsExamplePreview(ex && ex.prompt, 420),
|
|
4014
|
+
output: gsExampleBlockPreview(ex && ex.output, 900),
|
|
4015
|
+
command: ex && ex.command || "",
|
|
4016
|
+
file: ex && ex.file || "",
|
|
4017
|
+
evidence: gsProblemExampleText(ex),
|
|
4018
|
+
timestampMs: ex && ex.timestampMs || ex && ex.sessionStartedAt || null,
|
|
4019
|
+
turnInputTokens: ex && ex.turnInputTokens || 0,
|
|
4020
|
+
turnUsefulTokens: ex && ex.turnUsefulTokens || 0,
|
|
4021
|
+
turnWasteTokens: ex && ex.turnWasteTokens || 0,
|
|
4022
|
+
};
|
|
4023
|
+
}),
|
|
4024
|
+
};
|
|
4025
|
+
}
|
|
4026
|
+
function gsProblemExampleButton(p, shareLabel) {
|
|
4027
|
+
var payload = encodeURIComponent(JSON.stringify(gsExamplePayload(p, shareLabel)));
|
|
4028
|
+
return '<button class="gs-example-btn" type="button" data-gs-example="' + payload + '">See example</button>';
|
|
4029
|
+
}
|
|
4030
|
+
function exampleField(label, value, fallback) {
|
|
4031
|
+
var missing = !String(value || "").trim();
|
|
4032
|
+
return '<div class="example-field' + (missing ? ' missing' : '') + '"><b>' + esc(label) + '</b><span>' + esc(missing ? fallback : value) + '</span></div>';
|
|
4033
|
+
}
|
|
4034
|
+
function exampleBlockField(label, value, fallback, payload, ex) {
|
|
4035
|
+
var missing = !String(value || "").trim();
|
|
4036
|
+
if (missing) return '<div class="example-field missing"><b>' + esc(label) + '</b><div class="example-output"><div class="example-output-text">' + esc(fallback) + '</div></div></div>';
|
|
4037
|
+
var highlighted = gsHighlightedOutputHtml(value, gsOutputHighlightCandidates(payload || {}, ex || {}));
|
|
4038
|
+
var signal = gsExampleProblemSignal(payload || {}, ex || {});
|
|
4039
|
+
var signalHtml = signal
|
|
4040
|
+
? '<div class="example-problem-signal"><small>' + (highlighted.matched ? 'Highlighted problem span' : 'Problem signal') + '</small><span>' + esc(signal) + '</span></div>'
|
|
4041
|
+
: "";
|
|
4042
|
+
return '<div class="example-field"><b>' + esc(label) + '</b><div class="example-output">' + signalHtml + '<div class="example-output-text">' + highlighted.html + '</div></div></div>';
|
|
4043
|
+
}
|
|
4044
|
+
function rdoOpenProblemExample(encoded) {
|
|
4045
|
+
var payload = null;
|
|
4046
|
+
try { payload = JSON.parse(decodeURIComponent(encoded || "")); } catch (_) { return; }
|
|
4047
|
+
var dlg = document.getElementById("echo-note"); if (!dlg || !payload) return;
|
|
4048
|
+
var examples = payload.examples || [];
|
|
4049
|
+
document.getElementById("echo-note-k").textContent = (payload.id ? payload.id + " · " : "") + (payload.category || "Context problem");
|
|
4050
|
+
document.getElementById("echo-note-t").textContent = payload.label || "Problem example";
|
|
4051
|
+
document.getElementById("echo-note-b").innerHTML =
|
|
4052
|
+
'<p>This card uses the <b>worst turn</b> found for this problem. The red badge is <b>not the whole session</b>; it is the amount of this turn\\'s input payload attributed to <b>' + esc(payload.label || "this pattern") + '</b>.</p>' +
|
|
4053
|
+
(examples.length ? '<div class="example-list">' + examples.map(function (ex, index) {
|
|
4054
|
+
var input = Number(ex.turnInputTokens || 0);
|
|
4055
|
+
var useful = Number(ex.turnUsefulTokens || 0);
|
|
4056
|
+
var waste = Number(ex.turnWasteTokens || 0);
|
|
4057
|
+
var problemTokens = Number(ex.tokens || 0);
|
|
4058
|
+
var usefulPct = gsExamplePct(useful, input);
|
|
4059
|
+
var wastePct = gsExamplePct(waste, input);
|
|
4060
|
+
var problemPct = gsExamplePct(problemTokens, input);
|
|
4061
|
+
var stats = { input: input, useful: useful, waste: waste, problemTokens: problemTokens, usefulPct: usefulPct, wastePct: wastePct, problemPct: problemPct };
|
|
4062
|
+
var meta = gsAgentLabel(ex.agentSource) + " · " + (ex.repo || "workspace") + (ex.turn ? " · turn " + number(ex.turn) : "") + (ex.session ? " · session " + String(ex.session).slice(0, 8) : "") + " · " + gsExampleTime(ex.timestampMs);
|
|
4063
|
+
var statLine = input
|
|
4064
|
+
? number(input) + " total input tokens · " + number(usefulPct) + "% useful · " + number(wastePct) + "% waste · " + number(problemPct) + "% this problem"
|
|
4065
|
+
: "Turn token totals unavailable for this scan.";
|
|
4066
|
+
return '<div class="example-card">' +
|
|
4067
|
+
'<div class="example-meta"><span>' + esc(meta) + '</span><b>' + number(ex.tokens || 0) + ' problem tokens in this turn</b></div>' +
|
|
4068
|
+
exampleField("Turn size", statLine, "Turn token totals unavailable for this scan.") +
|
|
4069
|
+
exampleField("User prompt", ex.prompt, "This report snapshot did not capture the turn prompt preview. Re-run the scan to rebuild examples with transcript previews.") +
|
|
4070
|
+
exampleBlockField("Agent output", ex.output, "This report snapshot did not capture the agent output preview. Re-run the scan to rebuild examples with transcript previews.", payload, ex) +
|
|
4071
|
+
exampleField("Problem", gsSpecificProblemText(payload, ex, stats), "Problem definition is not available.") +
|
|
4072
|
+
exampleField("Evidence", ex.evidence, "Evidence details are not available.") +
|
|
4073
|
+
'</div>';
|
|
4074
|
+
}).join("") + '</div>' : '<div class="example-list"><div class="example-card">' + exampleField("No examples captured", "", "This scan produced aggregate totals but no row-level evidence for this card.") + '</div></div>');
|
|
4075
|
+
dlg.classList.add("example");
|
|
4076
|
+
if (dlg.showModal) dlg.showModal(); else dlg.setAttribute("open", "");
|
|
4077
|
+
}
|
|
4078
|
+
function gsTopProblems(gs) {
|
|
4079
|
+
var summary = (gs && gs.summary) || {};
|
|
4080
|
+
var totalWaste = Math.max(0, Number(summary.wasteTokens || 0));
|
|
4081
|
+
var problems = gsDisplayProblems(gs, false).slice(0, 4);
|
|
4082
|
+
if (!problems.length) return '<p class="gs-lead">No recurring waste pattern was strong enough to rank in this scan.</p>';
|
|
4083
|
+
return '<div class="gs-top-problems">' + problems.map(function (p, index) {
|
|
4084
|
+
var rawShare = Number.isFinite(Number(p.displaySharePct))
|
|
4085
|
+
? Number(p.displaySharePct || 0)
|
|
4086
|
+
: (totalWaste ? (Number(p.allocatedWasteTokens || 0) / totalWaste) * 100 : 0);
|
|
4087
|
+
var share = Math.round(rawShare);
|
|
4088
|
+
var hasTokens = Number(p.allocatedWasteTokens || 0) > 0;
|
|
4089
|
+
var shareLabel = share === 0 && hasTokens ? '<1' : number(share);
|
|
4090
|
+
var accent = gsColor(p, index);
|
|
4091
|
+
return '<article class="gs-problem-card" style="--gs-accent:' + esc(accent) + '">' +
|
|
4092
|
+
'<div class="gs-problem-card-head"><span>' + esc(String(index + 1).padStart(2, "0")) + '</span><b>' + esc(shareLabel) + '% of waste</b></div>' +
|
|
4093
|
+
'<h5>' + esc(p.label || "Untitled problem") + '</h5>' +
|
|
4094
|
+
'<p>' + esc(gsProblemDefinition(p)) + '</p>' +
|
|
4095
|
+
gsProblemExampleButton(p, shareLabel) +
|
|
4096
|
+
'<div class="gs-problem-card-meter" aria-hidden="true"><i style="width:' + Math.max(0, Math.min(100, rawShare)) + '%"></i></div>' +
|
|
4097
|
+
'</article>';
|
|
4098
|
+
}).join("") + '</div>';
|
|
4099
|
+
}
|
|
4100
|
+
|
|
4101
|
+
var AUDIT_GITHUB_MARK = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 .7a11.5 11.5 0 0 0-3.64 22.41c.58.11.79-.25.79-.56v-2.23c-3.22.7-3.9-1.37-3.9-1.37-.53-1.34-1.29-1.7-1.29-1.7-1.05-.72.08-.71.08-.71 1.17.08 1.78 1.2 1.78 1.2 1.04 1.78 2.72 1.27 3.38.97.1-.75.41-1.27.74-1.56-2.57-.29-5.27-1.29-5.27-5.68 0-1.26.45-2.28 1.19-3.09-.12-.29-.52-1.47.11-3.05 0 0 .97-.31 3.16 1.18A10.97 10.97 0 0 1 12 6.12c.98 0 1.96.13 2.88.39 2.2-1.49 3.16-1.18 3.16-1.18.63 1.58.23 2.76.11 3.05.74.81 1.19 1.83 1.19 3.09 0 4.4-2.71 5.38-5.29 5.67.42.36.79 1.06.79 2.14v3.27c0 .31.21.68.8.56A11.5 11.5 0 0 0 12 .7Z"/></svg>';
|
|
4102
|
+
var AUDIT_GROUPS = [
|
|
4103
|
+
{
|
|
4104
|
+
key: "reasoning",
|
|
4105
|
+
label: "Agent planning and reasoning never leave the context",
|
|
4106
|
+
ids: ["P13"],
|
|
4107
|
+
findingNoun: "reasoning residues",
|
|
4108
|
+
explanation: "Agents generate planning and reasoning notes to guide each turn. Turn after turn, those notes decay — plans change, assumptions get corrected — yet they all stay in the context, and the model still walks through every one of them before it can act. These notes are often very long, and they build on one another, so one stale line of reasoning keeps steering later turns and interferes with the current direction."
|
|
4109
|
+
},
|
|
4110
|
+
{
|
|
4111
|
+
key: "screenshots",
|
|
4112
|
+
label: "Every screenshot stays in the context",
|
|
4113
|
+
ids: ["P01", "P11"],
|
|
4114
|
+
findingNoun: "obsolete screenshots",
|
|
4115
|
+
explanation: "Screenshots enter the context from your uploads and from the agent’s own visual checks, and each one is heavy — a single image can cost as much as pages of text. Once its question is answered, the image should be done. Instead it stays attached, and every later turn re-processes the same obsolete pixels that no longer describe what is on screen."
|
|
4116
|
+
},
|
|
4117
|
+
{
|
|
4118
|
+
key: "tools",
|
|
4119
|
+
label: "Finished tool output stayed in the payload",
|
|
4120
|
+
ids: ["P12", "P08", "P10"],
|
|
4121
|
+
findingNoun: "retained tool outputs",
|
|
4122
|
+
explanation: "Agents run tools to answer questions: read a file, search the code, check the repository state. The answer gets used in that turn, but the raw output — full logs, long file dumps, command results — keeps riding along in every later request. The question is closed; the model still pays to re-read the evidence each turn."
|
|
4123
|
+
},
|
|
4124
|
+
{
|
|
4125
|
+
key: "dead-work",
|
|
4126
|
+
label: "Discarded work survived the decision",
|
|
4127
|
+
ids: ["P07", "P09", "P05", "P03"],
|
|
4128
|
+
findingNoun: "discarded-work residues",
|
|
4129
|
+
explanation: "Not every attempt survives: edits get reverted, drafts get replaced, whole approaches get abandoned. The decision moved on, but the discarded versions stay in the context beside the accepted one. Later turns carry both — and the model has to keep telling the dead ends apart from the work that actually counts."
|
|
4130
|
+
}
|
|
4131
|
+
];
|
|
4132
|
+
function auditGroupData(gs, spec, projection) {
|
|
4133
|
+
var rows = ((gs && gs.problems) || []).filter(function (p) { return spec.ids.indexOf(p.id) !== -1; });
|
|
4134
|
+
var canonicalTokens = rows.reduce(function (sum, p) { return sum + Number(p.allocatedWasteTokens || 0); }, 0);
|
|
4135
|
+
var tokens = gsProjectedTokens(projection, canonicalTokens);
|
|
4136
|
+
var count = rows.reduce(function (sum, p) { return sum + Number(p.count || 0); }, 0);
|
|
4137
|
+
var sessions = rows.reduce(function (set, p) {
|
|
4138
|
+
((p && p.examples) || []).forEach(function (ex) { if (ex && ex.session) set[ex.session] = 1; });
|
|
4139
|
+
return set;
|
|
4140
|
+
}, {});
|
|
4141
|
+
var candidates = [];
|
|
4142
|
+
rows.forEach(function (problem) {
|
|
4143
|
+
((problem && problem.examples) || []).forEach(function (example) {
|
|
4144
|
+
candidates.push({ problem: problem, example: example, weight: Math.max(Number(example.tokens || 0), Number(example.turnWasteTokens || 0)) });
|
|
4145
|
+
});
|
|
4146
|
+
});
|
|
4147
|
+
candidates.sort(function (a, b) { return b.weight - a.weight; });
|
|
4148
|
+
return { spec: spec, rows: rows, tokens: tokens, canonicalTokens: canonicalTokens, count: count, sessions: Object.keys(sessions).length, best: candidates[0] || null };
|
|
4149
|
+
}
|
|
4150
|
+
var sessionTurnMap = null;
|
|
4151
|
+
function sessionTurnTotal(sessionId) {
|
|
4152
|
+
if (!sessionId) return 0;
|
|
4153
|
+
if (!sessionTurnMap) {
|
|
4154
|
+
sessionTurnMap = {};
|
|
4155
|
+
var gs = gsClean(report && report.canonicalGoldenStandard);
|
|
4156
|
+
var sources = gs ? [gs, gs.sourceReports && gs.sourceReports.codex, gs.sourceReports && gs.sourceReports.claudeCode] : [];
|
|
4157
|
+
sources.forEach(function (src) {
|
|
4158
|
+
(((src || {}).sessions) || []).forEach(function (row) {
|
|
4159
|
+
if (row && row.session && Number(row.turns)) sessionTurnMap[row.session] = Number(row.turns);
|
|
4160
|
+
});
|
|
4161
|
+
});
|
|
4162
|
+
}
|
|
4163
|
+
return sessionTurnMap[sessionId] || sessionTurnMap[String(sessionId).slice(0, 8)] || 0;
|
|
4164
|
+
}
|
|
4165
|
+
function auditExampleHtml(group) {
|
|
4166
|
+
var candidate = group.best;
|
|
4167
|
+
if (!candidate) {
|
|
4168
|
+
return '<details class="audit-example"><summary>No turn-level record captured</summary></details>';
|
|
4169
|
+
}
|
|
4170
|
+
var p = candidate.problem || {}, ex = candidate.example || {};
|
|
4171
|
+
var input = Number(ex.turnInputTokens || 0);
|
|
4172
|
+
var waste = Number(ex.turnWasteTokens || 0);
|
|
4173
|
+
var problemTokens = Number(ex.tokens || 0);
|
|
4174
|
+
var wastePct = gsExamplePct(waste, input);
|
|
4175
|
+
var problemPct = gsExamplePct(problemTokens, input);
|
|
4176
|
+
var source = gsAgentLabel(ex.agentSource);
|
|
4177
|
+
var sourceKey = ex.agentSource === 'claude-code' ? 'claude-code' : 'codex';
|
|
4178
|
+
var sourceIcon = sourceKey === 'claude-code' ? '/hud-assets/claude.svg' : '/hud-assets/codex.svg';
|
|
4179
|
+
var sourceAccent = sourceKey === 'claude-code' ? '#D97757' : '#3941FF';
|
|
4180
|
+
var sessionName = ex.sessionTitle || ((ex.repo || source) + ' session');
|
|
4181
|
+
var totalTurns = Number(ex.sessionTurns || 0) || sessionTurnTotal(ex.session);
|
|
4182
|
+
var turnLabel = ex.turn ? 'Turn ' + number(ex.turn) + (totalTurns ? ' of ' + number(totalTurns) : '') : '';
|
|
4183
|
+
var eventMs = Number(ex.timestampMs || ex.sessionStartedAt || 0);
|
|
4184
|
+
var whenClock = '';
|
|
4185
|
+
var whenRel = '';
|
|
4186
|
+
if (eventMs) {
|
|
4187
|
+
var now = Date.now();
|
|
4188
|
+
var eventDate = new Date(eventMs);
|
|
4189
|
+
var sameDay = new Date(now).toDateString() === eventDate.toDateString();
|
|
4190
|
+
var daysAgo = Math.max(0, Math.floor((new Date(now).setHours(0,0,0,0) - new Date(eventMs).setHours(0,0,0,0)) / 86400000));
|
|
4191
|
+
whenClock = eventDate.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
|
|
4192
|
+
whenRel = sameDay ? 'Today' : number(daysAgo) + (daysAgo === 1 ? ' day ago' : ' days ago');
|
|
4193
|
+
}
|
|
4194
|
+
var prompt = gsExamplePreview(ex.prompt, 700) || 'The scan did not retain a prompt preview for this turn.';
|
|
4195
|
+
var signal = gsExampleProblemSignal(p, ex) || gsProblemExampleText(ex) || gsProblemDefinition(p);
|
|
4196
|
+
var retainedLabel = group.spec.findingNoun || 'retained items';
|
|
4197
|
+
var retainedExplain = ex.evidence || signal;
|
|
4198
|
+
var retainedRecord = ex.command || ex.file || '';
|
|
4199
|
+
var shortId = ex.session ? String(ex.session).slice(0, 8) : '';
|
|
4200
|
+
return '<details class="audit-example">' +
|
|
4201
|
+
'<summary>' +
|
|
4202
|
+
'<img class="aet-icon" src="' + sourceIcon + '" alt="' + esc(source) + '" title="' + esc(source) + '" />' +
|
|
4203
|
+
'<span class="aet-name">' + esc(sessionName) + '</span>' +
|
|
4204
|
+
(shortId ? '<span class="aet-id">' + esc(shortId) + '</span>' : '') +
|
|
4205
|
+
(whenRel ? '<span class="aet-when">' + esc(whenRel) + '</span>' : '') +
|
|
4206
|
+
'</summary>' +
|
|
4207
|
+
'<div class="audit-example-body">' +
|
|
4208
|
+
'<div class="audit-session-locator">' +
|
|
4209
|
+
'<button type="button" class="audit-session-open" data-open-session data-session-source="' + esc(sourceKey) + '" data-session-id="' + esc(ex.session || '') + '" style="--session-accent:' + sourceAccent + '">' +
|
|
4210
|
+
'<span class="audit-session-name"><img src="' + sourceIcon + '" alt="' + esc(source) + '" title="' + esc(source) + '" /><span>' + esc(sessionName) + '</span></span>' +
|
|
4211
|
+
'</button>' +
|
|
4212
|
+
'<div class="audit-session-meta"><span class="audit-session-repo">' + AUDIT_GITHUB_MARK + esc(ex.repo || 'workspace') + '</span>' + (turnLabel ? '<span>·</span><span>' + esc(turnLabel) + '</span>' : '') + (eventMs ? '<span>·</span><time title="' + esc(new Date(eventMs).toLocaleString()) + '">' + esc(whenRel + ' · ' + whenClock) + '</time>' : '') + '</div>' +
|
|
4213
|
+
'<div class="audit-session-status" aria-live="polite"></div>' +
|
|
4214
|
+
'</div>' +
|
|
4215
|
+
'<ul class="audit-turn-ledger">' +
|
|
4216
|
+
'<li><span>Terminal window input</span><strong>' + big(input) + '</strong></li>' +
|
|
4217
|
+
'<li><span>Noise in this window</span><strong>' + big(waste) + ' <em>· ' + number(wastePct) + '% of input</em></strong></li>' +
|
|
4218
|
+
'<li><span>Attributed in this window</span><strong>' + big(problemTokens) + ' <em>· ' + number(problemPct) + '% of input</em></strong></li>' +
|
|
4219
|
+
'</ul>' +
|
|
4220
|
+
'<span class="audit-example-label">You said</span>' +
|
|
4221
|
+
'<blockquote class="audit-prompt">' + esc(prompt) + '</blockquote>' +
|
|
4222
|
+
'<details class="audit-retained">' +
|
|
4223
|
+
'<summary>What stayed in this terminal window · ' + big(problemTokens) + ' tokens <kbd>Enter</kbd></summary>' +
|
|
4224
|
+
'<div class="audit-retained-item">' +
|
|
4225
|
+
'<b>' + esc(retainedLabel) + '</b><strong>' + big(problemTokens) + '</strong>' +
|
|
4226
|
+
'<p>' + esc(retainedExplain) + '</p>' +
|
|
4227
|
+
(retainedRecord ? '<code>' + esc(retainedRecord) + '</code>' : '') +
|
|
4228
|
+
(ex.session ? '<button type="button" class="audit-retained-open" data-open-session data-session-source="' + esc(sourceKey) + '" data-session-id="' + esc(ex.session) + '" style="--session-accent:' + sourceAccent + '">See it in the original session' + (ex.turn ? ' · turn ' + number(ex.turn) : '') + ' ↗</button>' : '') +
|
|
4229
|
+
'</div>' +
|
|
4230
|
+
'</details>' +
|
|
4231
|
+
'</div>' +
|
|
4232
|
+
'</details>';
|
|
4233
|
+
}
|
|
4234
|
+
function auditRowHtml(group, index, totalWaste, slice) {
|
|
4235
|
+
var pct = totalWaste ? Math.round((group.tokens / totalWaste) * 100) : 0;
|
|
4236
|
+
var pieSlices = [slice ? { start: slice.start, end: slice.end, color: slice.color } : { start: 0, end: pct, color: '#6d84b4' }];
|
|
4237
|
+
var noun = group.spec.findingNoun || 'events';
|
|
4238
|
+
var nounCap = noun.charAt(0).toUpperCase() + noun.slice(1);
|
|
4239
|
+
return '<section class="audit-row" id="audit-' + esc(group.spec.key) + '">' +
|
|
4240
|
+
'<div class="audit-share"><canvas class="audit-share-pie" data-pencil-pie data-radius="90" data-slices="' + esc(JSON.stringify(pieSlices)) + '" aria-label="' + esc(pct) + '% of all noise"></canvas><strong>' + esc(pct) + '%</strong><span>of noise</span></div>' +
|
|
4241
|
+
'<div class="audit-copy">' +
|
|
4242
|
+
'<h3>' + esc(group.spec.label) + '</h3>' +
|
|
4243
|
+
'<p>' + esc(group.spec.explanation) + '</p>' +
|
|
4244
|
+
'<div class="audit-facts">' +
|
|
4245
|
+
'<div class="audit-fact"><strong>' + big(group.tokens) + ' projected billed tokens</strong><span> across all API requests</span></div>' +
|
|
4246
|
+
'<div class="audit-fact"><span>Canonical terminal-window evidence: </span><strong>' + big(group.canonicalTokens) + ' tokens</strong></div>' +
|
|
4247
|
+
'<div class="audit-fact"><span>' + esc(nounCap) + ' detected: </span><strong>' + (group.count ? number(group.count) + ' pieces' : 'none counted') + '</strong><span> in all sessions</span></div>' +
|
|
4248
|
+
(group.count ? '<div class="audit-fact"><span>Average projected impact per detected piece: </span><strong>~' + big(Math.round(group.tokens / group.count)) + ' billed tokens</strong></div>' : '') +
|
|
4249
|
+
'</div>' +
|
|
4250
|
+
auditExampleHtml(group) +
|
|
4251
|
+
'</div>' +
|
|
4252
|
+
'</section>';
|
|
4253
|
+
}
|
|
4254
|
+
function gsContextAuditStream(r) {
|
|
4255
|
+
var gs = gsClean(r && r.canonicalGoldenStandard);
|
|
4256
|
+
if (!gs) return '';
|
|
4257
|
+
var s = gs.summary || {};
|
|
4258
|
+
var wastePct = Math.round(gsDisplayWastePct(gs));
|
|
4259
|
+
var projection = gsBillingProjection(r, gs);
|
|
4260
|
+
var groups = AUDIT_GROUPS.map(function (spec) { return auditGroupData(gs, spec, projection); })
|
|
4261
|
+
.sort(function (a, b) { return b.tokens - a.tokens; });
|
|
4262
|
+
var totalWaste = Math.max(1, Number(projection.projectedWasteTokens || 0));
|
|
4263
|
+
var dateLabel = function (value) {
|
|
4264
|
+
if (!value) return 'Unknown';
|
|
4265
|
+
var parsed = new Date(value.length === 10 ? value + 'T12:00:00' : value);
|
|
4266
|
+
return parsed.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
|
|
4267
|
+
};
|
|
4268
|
+
var periodDateLabel = function (value) {
|
|
4269
|
+
if (!value) return 'Unknown';
|
|
4270
|
+
var parsed = new Date(value.length === 10 ? value + 'T12:00:00' : value);
|
|
4271
|
+
var day = parsed.getDate();
|
|
4272
|
+
var mod100 = day % 100;
|
|
4273
|
+
var suffix = mod100 >= 11 && mod100 <= 13 ? 'th' : ({ 1: 'st', 2: 'nd', 3: 'rd' }[day % 10] || 'th');
|
|
4274
|
+
return parsed.toLocaleDateString('en-US', { month: 'long' }).replace(/ \d+$/, '') + ' ' + day + suffix + ', ' + parsed.getFullYear();
|
|
4275
|
+
};
|
|
4276
|
+
var first = r && r.firstSession || null;
|
|
4277
|
+
var numericDate = function (value) {
|
|
4278
|
+
if (!value) return 'Unknown';
|
|
4279
|
+
var parsed = new Date(value.length === 10 ? value + 'T12:00:00' : value);
|
|
4280
|
+
var mm = String(parsed.getMonth() + 1).padStart(2, '0');
|
|
4281
|
+
var dd = String(parsed.getDate()).padStart(2, '0');
|
|
4282
|
+
return mm + '/' + dd + '/' + parsed.getFullYear();
|
|
4283
|
+
};
|
|
4284
|
+
var periodEl = document.getElementById("report-period");
|
|
4285
|
+
if (periodEl) {
|
|
4286
|
+
var periodStart = (first && first.date) || (r && r.scanStartDate);
|
|
4287
|
+
periodEl.innerHTML = '<em>' + esc(numericDate(periodStart)) + '</em><span class="navAxis"></span><em class="isNow">' + esc(numericDate(r && r.scanEndDate)) + '</em>';
|
|
4288
|
+
}
|
|
4289
|
+
var firstSource = first && first.source === 'claude-code' ? 'Claude Code' : 'Codex';
|
|
4290
|
+
var coverage = r && r.activeSessionCoverage || {};
|
|
4291
|
+
// Prefer canonical per-source counts so the "X Codex + Y Claude" equation matches the
|
|
4292
|
+
// ledger's sessionsAnalyzed exactly (activeSessionCoverage misses archived sessions).
|
|
4293
|
+
var codexSessions = Number((gs.sourceReports && gs.sourceReports.codex && gs.sourceReports.codex.summary && gs.sourceReports.codex.summary.sessionsAnalyzed) || coverage.codex || 0);
|
|
4294
|
+
var claudeSessions = Number((gs.sourceReports && gs.sourceReports.claudeCode && gs.sourceReports.claudeCode.summary && gs.sourceReports.claudeCode.summary.sessionsAnalyzed) || coverage.claudeCode || 0);
|
|
4295
|
+
var averageSessionTokens = Number(s.sessionsAnalyzed || 0) ? Math.round(Number(s.officialInputTokens || 0) / Number(s.sessionsAnalyzed || 1)) : 0;
|
|
4296
|
+
var averageContextTokens = Number(s.turnsAnalyzed || 0) ? Math.round(Number(s.officialInputTokens || 0) / Number(s.turnsAnalyzed || 1)) : 0;
|
|
4297
|
+
var groupedNoiseTokens = groups.reduce(function (sum, group) { return sum + Number(group.tokens || 0); }, 0);
|
|
4298
|
+
var groupedNoisePct = totalWaste ? Math.round((groupedNoiseTokens / totalWaste) * 100) : 0;
|
|
4299
|
+
// One shared clock: the master pie carves the attributed sector clockwise in display order,
|
|
4300
|
+
// and each problem row lights up the same fixed slice.
|
|
4301
|
+
var SLICE_COLORS = ["#6d84b4", "#8ea0c6", "#aebcd6", "#ccd4e6"];
|
|
4302
|
+
var sliceCursor = 0;
|
|
4303
|
+
var slices = groups.map(function (group, i) {
|
|
4304
|
+
var pct = totalWaste ? Math.round((group.tokens / totalWaste) * 100) : 0;
|
|
4305
|
+
var start = sliceCursor;
|
|
4306
|
+
sliceCursor += pct;
|
|
4307
|
+
return { start: start, end: sliceCursor, color: SLICE_COLORS[i] || SLICE_COLORS[SLICE_COLORS.length - 1] };
|
|
4308
|
+
});
|
|
4309
|
+
return '<div class="context-audit">' +
|
|
4310
|
+
'<section class="audit-intro">' +
|
|
4311
|
+
'<h2><em>' + wastePct + '%</em> of provider-billed input is projected noise.</h2>' +
|
|
4312
|
+
'<div class="audit-origin">' +
|
|
4313
|
+
'<p class="audit-origin-line"><span>This analysis starts with a session in ' + esc(firstSource) + ' on</span><span class="audit-date-chip">' + esc(dateLabel(first && first.date)) + '</span><span>in</span><span class="audit-repo-chip">' + AUDIT_GITHUB_MARK + esc(first && first.repo || 'workspace') + '</span></p>' +
|
|
4314
|
+
'</div>' +
|
|
4315
|
+
'<div class="audit-proof">' +
|
|
4316
|
+
'<div class="audit-sources">' +
|
|
4317
|
+
'<span class="audit-source"><img src="/hud-assets/codex.svg" alt="Codex" /><strong>' + number(codexSessions) + '</strong> Codex sessions</span>' +
|
|
4318
|
+
'<span class="audit-source-plus">+</span>' +
|
|
4319
|
+
'<span class="audit-source"><img src="/hud-assets/claude.svg" alt="Claude Code" /><strong>' + number(claudeSessions) + '</strong> Claude Code sessions</span>' +
|
|
4320
|
+
'<span class="audit-source-tail">on this machine</span>' +
|
|
4321
|
+
'</div>' +
|
|
4322
|
+
'</div>' +
|
|
4323
|
+
'<p class="audit-narrative">The provider ledger records <b>' + big(projection.billedInputTokens) + ' input tokens</b> across every API request. Applying the ' + number(wastePct) + '% noise density estimated from the canonical terminal windows projects <b>' + big(projection.projectedWasteTokens) + ' billed input tokens</b> spent carrying old reasoning, screenshots, tool output, and discarded work.</p>' +
|
|
4324
|
+
'<p class="audit-method">Method: one terminal provider request is classified per human turn, then its waste density is expanded across the exact all-request input ledger by a dynamic ' + (Math.round(Number(projection.billingMultiplier || 1) * 100) / 100).toFixed(2) + '× token factor. Aggregate billed figures are projections; the expandable turn examples below remain unscaled terminal-window evidence.</p>' +
|
|
4325
|
+
'<ul class="audit-ledger" aria-label="Audit scope">' +
|
|
4326
|
+
'<li class="audit-ledger-item"><span>Provider input across all requests</span><strong>' + big(projection.billedInputTokens) + '</strong></li>' +
|
|
4327
|
+
'<li class="audit-ledger-item"><span>Terminal-window input analyzed</span><strong>' + big(projection.canonicalInputTokens) + '</strong></li>' +
|
|
4328
|
+
'<li class="audit-ledger-item"><span>Effective payload expansion</span><strong>' + (Math.round(Number(projection.billingMultiplier || 1) * 100) / 100).toFixed(2) + '×</strong></li>' +
|
|
4329
|
+
'<li class="audit-ledger-item"><span>Average terminal window per session</span><strong>' + big(averageSessionTokens) + '</strong></li>' +
|
|
4330
|
+
'<li class="audit-ledger-item"><span>Average terminal window per turn</span><strong>' + big(averageContextTokens) + '</strong></li>' +
|
|
4331
|
+
'<li class="audit-ledger-item"><span>Context SNR</span><strong>' + number(100 - wastePct) + '% signal</strong></li>' +
|
|
4332
|
+
'</ul>' +
|
|
4333
|
+
'<div class="audit-list-intro">' +
|
|
4334
|
+
'<canvas class="audit-master-pie" data-pencil-pie data-radius="130" data-slices="' + esc(JSON.stringify(slices)) + '" role="img" aria-label="' + number(groupedNoisePct) + '% of noise split across four problem groups"></canvas>' +
|
|
4335
|
+
'<p><b>' + number(groupedNoisePct) + '% of that projected noise</b> is concentrated in the four problem groups below.</p>' +
|
|
4336
|
+
'</div>' +
|
|
4337
|
+
'</section>' +
|
|
4338
|
+
'<div class="audit-list">' + groups.map(function (group, index) { return auditRowHtml(group, index, totalWaste, slices[index]); }).join('') + '</div>' +
|
|
4339
|
+
'<footer class="audit-end">' +
|
|
4340
|
+
'<h3>Stop burning tokens on these mistakes.</h3>' +
|
|
4341
|
+
'<button class="rdo-cta" data-connect-echo>' + (connected ? 'Continue' : 'Add') +
|
|
4342
|
+
'<img class="cta-face" src="/hud-assets/echo-face-cutout.png" alt="Echo" />' +
|
|
4343
|
+
'<svg class="cta-arrow" viewBox="0 0 64 26" aria-hidden="true">' +
|
|
4344
|
+
'<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>' +
|
|
4345
|
+
'<g filter="url(#ctaPencil)" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round">' +
|
|
4346
|
+
'<path d="M2 14 C 18 12, 40 12, 57 13"/>' +
|
|
4347
|
+
'<path d="M46 5 C 50 8.5, 54 11.5, 58 13 C 53.5 15, 49 18.5, 46 21.5"/>' +
|
|
4348
|
+
'<path d="M6 15.5 C 22 14, 40 13.5, 52 14" opacity="0.4" stroke-width="1.4"/>' +
|
|
4349
|
+
'</g>' +
|
|
4350
|
+
'</svg>' +
|
|
4351
|
+
'<span class="cta-tools">' +
|
|
4352
|
+
'<img src="/hud-assets/codex.svg" alt="Codex" title="Codex" />' +
|
|
4353
|
+
'<img src="/hud-assets/claude.svg" alt="Claude Code" title="Claude Code" />' +
|
|
4354
|
+
'</span>' +
|
|
4355
|
+
'</button>' +
|
|
4356
|
+
'<p class="audit-footnote">Compiled from <code>~/.codex/sessions</code> and <code>~/.claude/projects</code>. API-equivalent figures are list-price estimates, not subscription charges. No transcript leaves this machine.</p>' +
|
|
4357
|
+
'</footer>' +
|
|
4358
|
+
'</div>';
|
|
4359
|
+
}
|
|
4360
|
+
function gsGoldenSection(gs) {
|
|
4361
|
+
if (!gs) return "";
|
|
4362
|
+
if (gs.error) {
|
|
4363
|
+
return '<section class="gs-section rdo-card rise" style="--d:460ms"><p class="clab">Canonical Context Golden Standard</p><p class="gs-lead">Canonical analysis did not finish: ' + esc(gs.error) + '</p></section>';
|
|
4364
|
+
}
|
|
4365
|
+
var s = gs.summary || {};
|
|
4366
|
+
var wastePct = Math.round(gsDisplayWastePct(gs));
|
|
4367
|
+
var wasteStatement = wastePct >= 50
|
|
4368
|
+
? 'More than half of the context carried into your agent work is no longer helping it move forward.'
|
|
4369
|
+
: 'A meaningful share of the context carried into your agent work is no longer helping it move forward.';
|
|
4370
|
+
return '<section class="gs-section rdo-card rise" style="--d:460ms">' +
|
|
4371
|
+
'<button class="rdo-help" data-info="golden-standard">?</button>' +
|
|
4372
|
+
'<div class="gs-focus-head"><div class="gs-focus-copy"><p class="rdo-kicker">Context health</p><h3>Your context is <em>doing too much.</em></h3><p class="gs-lead">' + esc(wasteStatement) + ' Echo keeps the decision and active task state, so your next session starts with signal instead of residue.</p></div>' +
|
|
4373
|
+
'<div data-gs-source-callout>' + gsSourceCallout(gs, "Combined") + '</div></div>' +
|
|
4374
|
+
gsSourceWasteCards(gs) +
|
|
4375
|
+
'<div data-gs-source-detail>' + gsSourceDetail(gs, "Combined") + '</div>' +
|
|
4376
|
+
'</section>';
|
|
4377
|
+
}
|
|
1813
4378
|
function rdoNotes() {
|
|
1814
|
-
var r = report || {}, s = r.scale || {}, c = r.cost || {}, cl = r.cleanliness || {}, rh = r.rhythm || {}, uwh = r.userWorkingHours || {};
|
|
4379
|
+
var r = report || {}, s = r.scale || {}, c = r.cost || {}, cl = r.cleanliness || {}, rh = r.rhythm || {}, uwh = r.userWorkingHours || {}, cw = r.contextWindow || {}, cm = cw.metrics || {}, gs = gsClean(r.canonicalGoldenStandard), gss = gsSummary(r.canonicalGoldenStandard);
|
|
4380
|
+
var projection = gsBillingProjection(r, gs);
|
|
4381
|
+
var displayWastePct = Math.round(gsDisplayWastePct(gs));
|
|
4382
|
+
var displayUsefulPct = Math.round(gsDisplayUsefulPct(gs));
|
|
1815
4383
|
return {
|
|
1816
|
-
"
|
|
4384
|
+
"golden-waste": { k: "Golden Standard", t: "Wasteful context", b: '<p>' + number(displayWastePct) + '% is the token-weighted terminal-window waste score: <b>' + big(gss.wasteTokens || 0) + '</b> of <b>' + big(gss.officialInputTokens || 0) + '</b> in-window tokens. Applied to <b>' + big(projection.billedInputTokens) + '</b> provider-input tokens across all requests, that projects <b>' + big(projection.projectedWasteTokens) + '</b> billed waste tokens.</p>' },
|
|
4385
|
+
"golden-useful": { k: "Canonical Golden Standard", t: "Useful context", b: '<p>' + number(displayUsefulPct) + '% is the token-weighted useful-context complement: required overhead plus product/task context worth keeping available.</p><p>The token ledger remains available in the workspace totals.</p>' },
|
|
4386
|
+
"golden-input": { k: "Golden Standard", t: "Input analyzed", b: '<p>The golden-standard model analyzes the terminal provider request from each human turn, then aggregates those retained context windows across the workspace. Current rollup: <b>' + number(gss.sessionsAnalyzed || 0) + '</b> sessions and <b>' + number(gss.turnsAnalyzed || 0) + '</b> turns.</p>' },
|
|
1817
4387
|
"agent-attention": { k: "Activity", t: "Agent attention", b: '<p>' + number(uwh.userAttentionHours) + ' hours of active agent work across the scan. ' + rdoDec(uwh.averageDailyAttentionHours) + 'h/day average, peaking at ' + rdoDec(uwh.maxDailyAttentionHours) + 'h.</p>' },
|
|
1818
|
-
"
|
|
4388
|
+
"context-window": { k: "Golden Standard", t: "Context health", b: '<p>The setup page now uses the golden-standard workspace rollup for the report health view. It separates useful context from wasteful context and breaks waste into duplicate, re-find, and dead-work buckets.</p><p>The live HUD can still use latest-window metrics during an active session; this setup report is workspace-wide.</p>' },
|
|
1819
4389
|
"api-value": { k: "Cost", t: "API-equivalent", b: '<p>' + money(c.total) + ' at API list prices — a measure of scale, not a charge. Subscriptions were not billed this.</p>' },
|
|
1820
4390
|
"when-you-work": { k: "Rhythm", t: "When you work", b: '<p>Prompts by hour of day. The soft-indigo bars are late-night hours (10pm–4am); ' + number(rh.lateNightShare) + '% of your prompts land there.</p>' },
|
|
1821
4391
|
"time-by-repo": { k: "Focus", t: "Where your time goes", b: '<p>Agent attention hours split by repository — where your coding actually happened.</p>' },
|
|
1822
4392
|
"cost-went": { k: "Cost", t: "Where the tokens & cost go", b: '<p>Most volume is input — reloading context each turn. In cost, <b>cache read</b> + <b>cold input</b> are re-fed context.</p><div class="echo">That re-fed-context tax shrinks when agents recall instead of re-read.</div>' },
|
|
1823
|
-
"rereads": { k: "
|
|
4393
|
+
"rereads": { k: "Evidence", t: "Most re-read files", b: '<p>Files your agents kept reopening after seeing them unchanged. This is still useful evidence and often maps to P03/Old Files, but it is no longer the top-line waste percentage.</p>' },
|
|
4394
|
+
"golden-standard": { k: "Canonical Golden Standard", t: "Workspace context analysis", b: '<p>This view compares the useful context with the residue that has stayed in the working window after it stopped helping the task.</p><p>The four cards show the largest recurring sources of that residue across your local coding history.</p>' }
|
|
1824
4395
|
};
|
|
1825
4396
|
}
|
|
1826
4397
|
function rdoOpenNote(key) {
|
|
1827
4398
|
var d = rdoNotes()[key]; if (!d) return; var dlg = document.getElementById("echo-note"); if (!dlg) return;
|
|
4399
|
+
dlg.classList.remove("example");
|
|
1828
4400
|
document.getElementById("echo-note-k").textContent = d.k;
|
|
1829
4401
|
document.getElementById("echo-note-t").textContent = d.t;
|
|
1830
4402
|
document.getElementById("echo-note-b").innerHTML = d.b;
|
|
@@ -1842,10 +4414,76 @@ export function renderSetupPage() {
|
|
|
1842
4414
|
'<div class="num" data-count="' + value + '" data-fmt="' + kind + '">' + rdoFmt(value, kind) + '</div>' +
|
|
1843
4415
|
'<p class="clab" style="margin-top:11px">' + esc(label) + '</p><div class="csub">' + sub + '</div></div>';
|
|
1844
4416
|
}
|
|
4417
|
+
function gsSwapSourceContent(container, markup) {
|
|
4418
|
+
if (!container) return;
|
|
4419
|
+
if (container.innerHTML === markup) return;
|
|
4420
|
+
if (container._gsSwapTimer) clearTimeout(container._gsSwapTimer);
|
|
4421
|
+
if (rdoReduce) { container.innerHTML = markup; return; }
|
|
4422
|
+
container.classList.add("gs-source-fade");
|
|
4423
|
+
container._gsSwapTimer = setTimeout(function () {
|
|
4424
|
+
container.innerHTML = markup;
|
|
4425
|
+
requestAnimationFrame(function () {
|
|
4426
|
+
container.classList.remove("gs-source-fade");
|
|
4427
|
+
container._gsSwapTimer = null;
|
|
4428
|
+
});
|
|
4429
|
+
}, 95);
|
|
4430
|
+
}
|
|
1845
4431
|
function rdoWire() {
|
|
1846
4432
|
var root = document.getElementById("setup-details"); if (!root) return;
|
|
1847
4433
|
Array.prototype.forEach.call(root.querySelectorAll("[data-count]"), function (el, i) { setTimeout(function () { rdoCountUp(el); }, rdoReduce ? 0 : 260 + i * 60); });
|
|
1848
|
-
|
|
4434
|
+
if (!root._rdoClickDelegated) {
|
|
4435
|
+
root._rdoClickDelegated = true;
|
|
4436
|
+
root.addEventListener("click", function (event) {
|
|
4437
|
+
var target = event.target && event.target.closest ? event.target : event.target && event.target.parentElement;
|
|
4438
|
+
if (!target || !target.closest) return;
|
|
4439
|
+
var example = target.closest("[data-gs-example]");
|
|
4440
|
+
if (example && root.contains(example)) {
|
|
4441
|
+
event.preventDefault();
|
|
4442
|
+
rdoOpenProblemExample(example.getAttribute("data-gs-example"));
|
|
4443
|
+
return;
|
|
4444
|
+
}
|
|
4445
|
+
var help = target.closest(".rdo-help");
|
|
4446
|
+
if (help && root.contains(help)) {
|
|
4447
|
+
event.preventDefault();
|
|
4448
|
+
rdoOpenNote(help.getAttribute("data-info"));
|
|
4449
|
+
return;
|
|
4450
|
+
}
|
|
4451
|
+
var button = target.closest("[data-gs-source]");
|
|
4452
|
+
if (button && root.contains(button) && !button.disabled) {
|
|
4453
|
+
event.preventDefault();
|
|
4454
|
+
var key = button.getAttribute("data-gs-source");
|
|
4455
|
+
var section = button.closest(".gs-section");
|
|
4456
|
+
var canonical = gsClean(report && report.canonicalGoldenStandard);
|
|
4457
|
+
var source = canonical && key ? gsSourceReport(canonical, key) : null;
|
|
4458
|
+
if (!section || !source || !source.report || !source.report.summary) return;
|
|
4459
|
+
Array.prototype.forEach.call(section.querySelectorAll("[data-gs-source]"), function (el) {
|
|
4460
|
+
var active = el === button;
|
|
4461
|
+
el.classList.toggle("active", active);
|
|
4462
|
+
el.setAttribute("aria-pressed", active ? "true" : "false");
|
|
4463
|
+
});
|
|
4464
|
+
var callout = section.querySelector("[data-gs-source-callout]");
|
|
4465
|
+
gsSwapSourceContent(callout, gsSourceCallout(source.report, source.label));
|
|
4466
|
+
var detail = section.querySelector("[data-gs-source-detail]");
|
|
4467
|
+
gsSwapSourceContent(detail, gsSourceDetail(source.report, source.label));
|
|
4468
|
+
return;
|
|
4469
|
+
}
|
|
4470
|
+
var tab = target.closest("[data-gs-tab]");
|
|
4471
|
+
if (tab && root.contains(tab)) {
|
|
4472
|
+
event.preventDefault();
|
|
4473
|
+
var tabKey = tab.getAttribute("data-gs-tab");
|
|
4474
|
+
var tabSection = tab.closest(".gs-section");
|
|
4475
|
+
if (!tabSection || !tabKey) return;
|
|
4476
|
+
Array.prototype.forEach.call(tabSection.querySelectorAll("[data-gs-tab]"), function (el) {
|
|
4477
|
+
var active = el === tab;
|
|
4478
|
+
el.classList.toggle("active", active);
|
|
4479
|
+
el.setAttribute("aria-selected", active ? "true" : "false");
|
|
4480
|
+
});
|
|
4481
|
+
Array.prototype.forEach.call(tabSection.querySelectorAll("[data-gs-panel]"), function (panel) {
|
|
4482
|
+
panel.classList.toggle("active", panel.getAttribute("data-gs-panel") === tabKey);
|
|
4483
|
+
});
|
|
4484
|
+
}
|
|
4485
|
+
});
|
|
4486
|
+
}
|
|
1849
4487
|
var dlg = document.getElementById("echo-note");
|
|
1850
4488
|
if (dlg) { var cl = dlg.querySelector(".nclose"); if (cl) cl.onclick = function () { dlg.close(); }; dlg.onclick = function (e) { if (e.target === dlg) dlg.close(); }; }
|
|
1851
4489
|
Array.prototype.forEach.call(root.querySelectorAll("[data-rdo-toggle]"), function (mb) {
|
|
@@ -1884,12 +4522,13 @@ export function renderSetupPage() {
|
|
|
1884
4522
|
});
|
|
1885
4523
|
}
|
|
1886
4524
|
}
|
|
1887
|
-
function
|
|
4525
|
+
function renderLegacyDashboard(message) {
|
|
1888
4526
|
var r = report;
|
|
1889
4527
|
if (!r) { showReport(); return; }
|
|
1890
4528
|
if (!reportMounted || !document.getElementById("setup-details")) mountReportShell();
|
|
1891
4529
|
setCityMode(true);
|
|
1892
|
-
|
|
4530
|
+
scheduleReportToCity();
|
|
4531
|
+
var s = r.scale || {}, c = r.cost || {}, mu = r.modelUsage || {}, uwh = r.userWorkingHours || {}, rh = r.rhythm || {}, cl = r.cleanliness || {}, rf = r.rereadForensics || {}, aw = r.avoidableWait || {}, cf = r.counterfactual || {}, cw = r.contextWindow || {}, cm = cw.metrics || {};
|
|
1893
4532
|
var night = rf.nightExample;
|
|
1894
4533
|
setHead("AI Coding City", connected ? "Signed in" : "Local");
|
|
1895
4534
|
// The city iframe is already mounted by the shell and shows its own loading; just reveal the
|
|
@@ -1900,11 +4539,28 @@ export function renderSetupPage() {
|
|
|
1900
4539
|
var topRepoHours = repos.reduce(function (t, x) { return t + (x.attentionHours || 0); }, 0);
|
|
1901
4540
|
var weeks = (new Date(r.scanEndDate) - new Date(r.scanStartDate)) / (7 * 86400000);
|
|
1902
4541
|
if (!isFinite(weeks) || weeks < 1) weeks = 1;
|
|
1903
|
-
var
|
|
4542
|
+
var gs = gsClean(r.canonicalGoldenStandard);
|
|
4543
|
+
var gss = gsSummary(r.canonicalGoldenStandard);
|
|
4544
|
+
var projection = gsBillingProjection(r, gs);
|
|
4545
|
+
var sourceHtml = (Array.isArray(r.generatedFrom) && r.generatedFrom.length ? r.generatedFrom : ["$CODEX_HOME/sessions", "$CLAUDE_CONFIG_DIR/projects"])
|
|
4546
|
+
.map(function (source) { return '<code>' + esc(source) + '</code>'; })
|
|
4547
|
+
.join(' and ');
|
|
4548
|
+
var sharePct = gs ? Math.round(gsDisplayWastePct(gs)) : (cl.staleReadSharePct || 0);
|
|
1904
4549
|
var savedCost = Math.round((c.total / weeks) * (sharePct / 100));
|
|
1905
|
-
var savedTokens = (s.
|
|
1906
|
-
var
|
|
4550
|
+
var savedTokens = (gs ? projection.projectedWasteTokens : ((s.totalInputTokens || 0) * (sharePct / 100))) / weeks;
|
|
4551
|
+
var weeklyWasteTokens = savedTokens;
|
|
1907
4552
|
var targetShare = 10;
|
|
4553
|
+
var fullPct = rdoPct(cm.contextFullnessPct);
|
|
4554
|
+
var contextFullnessLabel = fullPct === null ? "fullness unknown" : number(fullPct) + "% full";
|
|
4555
|
+
var contextKpiValue = rdoScore(cm);
|
|
4556
|
+
var contextKpiKind = "pct";
|
|
4557
|
+
var contextKpiSub = contextFullnessLabel + ' · ' + rdoPctText(cm.noisePct, "0%") + ' noise';
|
|
4558
|
+
var noisePct = rdoPct(cm.noisePct) || 0;
|
|
4559
|
+
var gsWastePct = Math.round(gs ? gsDisplayWastePct(gs) : sharePct);
|
|
4560
|
+
var gsUsefulPct = Math.round(gs ? gsDisplayUsefulPct(gs) : Math.max(0, 100 - sharePct));
|
|
4561
|
+
var gsInputTokens = gs ? projection.billedInputTokens : (s.totalInputTokens || 0);
|
|
4562
|
+
var gsWasteTokens = gs ? projection.projectedWasteTokens : (gsInputTokens * (sharePct / 100));
|
|
4563
|
+
var gsUsefulTokens = gs ? projection.projectedUsefulTokens : Math.max(0, gsInputTokens - gsWasteTokens);
|
|
1908
4564
|
document.getElementById("setup-details").innerHTML =
|
|
1909
4565
|
'<div class="echo-rdo">' +
|
|
1910
4566
|
(message ? '<div class="notice" style="margin-bottom:18px">' + esc(message) + '</div>' : '') +
|
|
@@ -1915,34 +4571,33 @@ export function renderSetupPage() {
|
|
|
1915
4571
|
'<div class="rdo-meta"><span class="rdo-chip"><span class="live"></span>Filed locally</span><span class="rdo-chip"><b>' + number(s.sessionCount) + '</b> sessions</span><span class="rdo-chip"><b>' + number(s.repoCount) + '</b> repos</span><span class="rdo-chip">' + esc(r.scanStartDate) + ' – ' + esc(r.scanEndDate) + '</span></div>' +
|
|
1916
4572
|
'</div>' +
|
|
1917
4573
|
'<div class="rdo-kpis">' +
|
|
1918
|
-
'<div class="rise" style="--d:60ms">' + rdoKpi("hero",
|
|
1919
|
-
'<div class="rise" style="--d:110ms">' + rdoKpi("",
|
|
1920
|
-
'<div class="rise" style="--d:160ms">' + rdoKpi("",
|
|
1921
|
-
'<div class="rise" style="--d:210ms">' + rdoKpi("",
|
|
4574
|
+
'<div class="rise" style="--d:60ms">' + rdoKpi("hero", gsWastePct, "pct", "wasteful context", big(gsWasteTokens) + ' waste tokens', "golden-waste") + '</div>' +
|
|
4575
|
+
'<div class="rise" style="--d:110ms">' + rdoKpi("", gsUsefulPct, "pct", "useful context", big(gsUsefulTokens) + ' projected useful tokens', "golden-useful") + '</div>' +
|
|
4576
|
+
'<div class="rise" style="--d:160ms">' + rdoKpi("", gsInputTokens, "big", "input analyzed", number(gs ? (gss.turnsAnalyzed || 0) : (s.sessionCount || 0)) + (gs ? ' turns' : ' sessions'), "golden-input") + '</div>' +
|
|
4577
|
+
'<div class="rise" style="--d:210ms">' + rdoKpi("", uwh.userAttentionHours, "hours", "agent attention", rdoDec(uwh.averageDailyAttentionHours) + 'h/day avg', "agent-attention") + '</div>' +
|
|
1922
4578
|
'</div>' +
|
|
1923
4579
|
'<div class="rdo-grid">' +
|
|
1924
4580
|
'<div class="rdo-card rise" style="--d:260ms"><button class="rdo-help" data-info="when-you-work">?</button><p class="clab">When you work</p><div class="csub"><b>' + number(rh.lateNightShare) + '%</b> after 10pm · <b>' + rdoDec(uwh.averageDailyAttentionHours) + 'h</b> avg active day</div>' + rdoHisto(rh.hourHistogram) + '</div>' +
|
|
1925
|
-
'<div class="rdo-card rise" style="--d:310ms"><button class="rdo-help" data-info="
|
|
1926
|
-
'<div class="ledgerCols"><div><div class="lblrow">
|
|
1927
|
-
'<div class="collapse"><div class="collapse-inner" style="padding-top:6px">' + rdoModelRows(c.byModel) + '</div></div>' +
|
|
1928
|
-
'<button class="moreBtn" data-rdo-toggle>Break down by model ↓</button>' +
|
|
4581
|
+
'<div class="rdo-card rise" style="--d:310ms"><button class="rdo-help" data-info="context-window">?</button><p class="clab">Golden context health</p><div class="csub"><b>' + number(gsUsefulPct) + '%</b> useful · <b>' + number(gsWastePct) + '%</b> wasteful</div>' +
|
|
4582
|
+
'<div class="ledgerCols"><div><div class="lblrow">Useful vs. waste</div>' + (gs ? gsStack(gs) : rdoContextStack(cm)) + '</div><div><div class="lblrow">Workspace totals</div>' + gsOverviewFacts(r, gs) + '</div></div>' +
|
|
1929
4583
|
'</div>' +
|
|
1930
4584
|
'<div class="rdo-card rise" style="--d:360ms"><button class="rdo-help" data-info="time-by-repo">?</button><p class="clab">Where your time goes</p><div class="csub"><b>' + rdoDec(topRepoHours) + 'h</b> across ' + number(repos.length) + ' repos</div>' + rdoTerritory(repos) + '</div>' +
|
|
1931
4585
|
'<div class="rdo-card rise" style="--d:410ms"><button class="rdo-help" data-info="rereads">?</button><p class="clab">Most re-read files</p><div class="csub">top 5 of <b>' + number(files.length) + '</b> by total reads</div>' + rdoEvidence(files) + '</div>' +
|
|
1932
4586
|
'</div>' +
|
|
4587
|
+
gsGoldenSection(r.canonicalGoldenStandard) +
|
|
1933
4588
|
'<section class="outcome rise" style="--d:460ms">' +
|
|
1934
4589
|
'<div class="oc-copy">' +
|
|
1935
4590
|
'<p class="rdo-kicker">What changes next week</p>' +
|
|
1936
4591
|
'<h2>Echo remembers, so your agents stop re-reading.</h2>' +
|
|
1937
|
-
'<p class="rdo-lead" data-signin-help>Right now ' + number(sharePct) + '% of
|
|
1938
|
-
'<div class="targetbar"><div class="tb-track"><div class="tb-fill" style="width:' + sharePct + '%">' + number(sharePct) + '%
|
|
4592
|
+
'<p class="rdo-lead" data-signin-help>Right now ' + number(sharePct) + '% of analyzed context is wasteful. As Echo fills in, your agents <b>recall</b> what they have already seen instead of reloading it — and it compounds every session.</p>' +
|
|
4593
|
+
'<div class="targetbar"><div class="tb-track"><div class="tb-fill" style="width:' + sharePct + '%">' + number(sharePct) + '% wasteful today</div><div class="tb-goal" style="left:' + targetShare + '%"></div></div><div class="tb-flag" style="left:' + targetShare + '%">target ≤ ' + targetShare + '%</div></div>' +
|
|
1939
4594
|
'</div>' +
|
|
1940
4595
|
'<div class="oc-targets">' +
|
|
1941
4596
|
'<p class="ot-title">Next week’s target</p>' +
|
|
1942
|
-
'<p class="ot-note">What Echo
|
|
4597
|
+
'<p class="ot-note">What Echo can reduce in a week, once it recalls instead of re-reading:</p>' +
|
|
1943
4598
|
'<div class="ot-row"><div class="otk"><span class="k">Cost saved</span><span class="ks">API list-price value</span></div><span class="v" data-count="' + savedCost + '" data-fmt="money">' + money(savedCost) + '</span></div>' +
|
|
1944
|
-
'<div class="ot-row"><div class="otk"><span class="k">Tokens saved</span><span class="ks">context
|
|
1945
|
-
'<div class="ot-row"><div class="otk"><span class="k">
|
|
4599
|
+
'<div class="ot-row"><div class="otk"><span class="k">Tokens saved</span><span class="ks">wasteful context reduced</span></div><span class="v" data-count="' + savedTokens + '" data-fmt="big">' + big(savedTokens) + '</span></div>' +
|
|
4600
|
+
'<div class="ot-row"><div class="otk"><span class="k">Waste tokens/week</span><span class="ks">golden-standard estimate</span></div><span class="v" data-count="' + weeklyWasteTokens + '" data-fmt="big">' + big(weeklyWasteTokens) + '</span></div>' +
|
|
1946
4601
|
'<button class="rdo-cta" data-connect-echo>' + (connected ? "Continue →" : "Connect Echo →") + '</button>' +
|
|
1947
4602
|
'</div>' +
|
|
1948
4603
|
'</section>' +
|
|
@@ -1956,32 +4611,451 @@ export function renderSetupPage() {
|
|
|
1956
4611
|
bindDetailsArrow();
|
|
1957
4612
|
}
|
|
1958
4613
|
|
|
4614
|
+
function storyProblem(gs, id) {
|
|
4615
|
+
var rep = gsClean(gs);
|
|
4616
|
+
if (!rep || !Array.isArray(rep.problems)) return null;
|
|
4617
|
+
for (var i = 0; i < rep.problems.length; i++) if (rep.problems[i].id === id) return rep.problems[i];
|
|
4618
|
+
return null;
|
|
4619
|
+
}
|
|
4620
|
+
function storyProblemMeta(p) {
|
|
4621
|
+
if (!p) return "";
|
|
4622
|
+
var bits = [];
|
|
4623
|
+
if (p.sessions) bits.push(number(p.sessions) + " sessions");
|
|
4624
|
+
if (p.allocatedWasteTokens) bits.push(big(p.allocatedWasteTokens) + " waste tokens");
|
|
4625
|
+
var ex = p.examples && p.examples[0] ? p.examples[0] : null;
|
|
4626
|
+
if (ex && ex.evidence) bits.push(storyReadableEvidence(ex.evidence));
|
|
4627
|
+
return bits.join(" · ");
|
|
4628
|
+
}
|
|
4629
|
+
function storyReadableEvidence(evidence) {
|
|
4630
|
+
return String(evidence)
|
|
4631
|
+
.replace(/git state command/gi, "git check")
|
|
4632
|
+
.replace(/same search pattern replayed with no intervening edit/gi, "same search repeated before any code changed")
|
|
4633
|
+
.replace(/same discovery repeated without an intervening state change/gi, "same discovery repeated before the project changed")
|
|
4634
|
+
.replace(/same-turn transient output superseded by a later duplicate/gi, "same result repeated in the same turn")
|
|
4635
|
+
.replace(/transient output superseded before episode outcome/gi, "search output was replaced before it became memory")
|
|
4636
|
+
.replace(/transient output did not survive as episode outcome context/gi, "previous search output did not carry forward")
|
|
4637
|
+
.replace(new RegExp("visual/reference context expired before episode outcome", "gi"), "screenshots kept riding after they stopped mattering")
|
|
4638
|
+
.replace(/did not survive as episode outcome context/gi, "was never used again")
|
|
4639
|
+
.replace(/expired before episode outcome/gi, "kept riding after it stopped mattering")
|
|
4640
|
+
.replace(new RegExp("resident reasoning/self-talk in retained context", "gi"), "old thinking still in the window")
|
|
4641
|
+
.replace(new RegExp("(^|[^A-Za-z0-9])T([0-9]+)", "g"), "$1turn $2");
|
|
4642
|
+
}
|
|
4643
|
+
function storyQueryFromEvidence(evidence) {
|
|
4644
|
+
if (!evidence) return "";
|
|
4645
|
+
var firstLine = String(evidence).split(String.fromCharCode(10))[0];
|
|
4646
|
+
var lower = firstLine.toLowerCase();
|
|
4647
|
+
var tools = ["rg ", "grep ", "ripgrep ", "find ", "ag "];
|
|
4648
|
+
for (var i = 0; i < tools.length; i++) {
|
|
4649
|
+
var idx = lower.indexOf(tools[i]);
|
|
4650
|
+
if (idx >= 0) return firstLine.slice(idx).trim().slice(0, 72);
|
|
4651
|
+
}
|
|
4652
|
+
var keys = ["query:", "pattern:", "search:", "query=", "pattern=", "search="];
|
|
4653
|
+
for (var k = 0; k < keys.length; k++) {
|
|
4654
|
+
var ki = lower.indexOf(keys[k]);
|
|
4655
|
+
if (ki >= 0) return firstLine.slice(ki + keys[k].length).trim().slice(0, 72);
|
|
4656
|
+
}
|
|
4657
|
+
return "";
|
|
4658
|
+
}
|
|
4659
|
+
function storyGenericFileQuery(q) {
|
|
4660
|
+
var s = String(q || "").trim().toLowerCase();
|
|
4661
|
+
if (!s) return true;
|
|
4662
|
+
if (/^(search\\s+)?(@?[\\w.-]+\\/)*(package(-lock)?\\.json|pnpm-lock\\.yaml|yarn\\.lock|bun\\.lockb|tsconfig(\\.[\\w.-]+)?\\.json|readme(\\.md)?|\\.env(\\.[\\w.-]+)?)$/.test(s)) return true;
|
|
4663
|
+
return false;
|
|
4664
|
+
}
|
|
4665
|
+
function storyQueryCode(p) {
|
|
4666
|
+
var examples = p && Array.isArray(p.examples) ? p.examples : [];
|
|
4667
|
+
for (var i = 0; i < examples.length; i++) {
|
|
4668
|
+
var ex = examples[i] || {};
|
|
4669
|
+
var q = ex.command ? String(ex.command).trim().slice(0, 72) : "";
|
|
4670
|
+
if (!q) q = storyQueryFromEvidence(ex.evidence);
|
|
4671
|
+
if (q && !storyGenericFileQuery(q)) return q;
|
|
4672
|
+
}
|
|
4673
|
+
return "";
|
|
4674
|
+
}
|
|
4675
|
+
function storySearchCommand(q) {
|
|
4676
|
+
var s = String(q || "").trim() || '"where this is handled"';
|
|
4677
|
+
return /^(rg|grep|ripgrep|find|ag)\\b/i.test(s) ? s : "search " + s;
|
|
4678
|
+
}
|
|
4679
|
+
function storyEvidenceRows(rows) {
|
|
4680
|
+
return '<div class="evidence">' + rows.filter(Boolean).slice(0, 3).map(function (row) {
|
|
4681
|
+
return '<div class="exh"><div class="xn">' + esc(row.value) + '<small>' + esc(row.unit) + '</small></div>' +
|
|
4682
|
+
'<div class="xc">' + (row.code ? '<code>' + esc(row.code) + '</code>' : '<b>' + esc(row.title || "") + '</b>') +
|
|
4683
|
+
(row.visual || "") +
|
|
4684
|
+
(row.detail ? '<small>' + esc(row.detail || "") + '</small>' : '') + '</div></div>';
|
|
4685
|
+
}).join("") + '</div>';
|
|
4686
|
+
}
|
|
4687
|
+
function storyMeterPct(tokenPressure, wasteTokens, countFallback) {
|
|
4688
|
+
var pct = 0;
|
|
4689
|
+
if (tokenPressure && wasteTokens) pct = Math.round((tokenPressure / Math.max(1, wasteTokens)) * 100);
|
|
4690
|
+
if (!pct && countFallback) pct = Math.round((countFallback / Math.max(1, countFallback + 1)) * 100);
|
|
4691
|
+
return Math.max(6, Math.min(96, pct || 12));
|
|
4692
|
+
}
|
|
4693
|
+
function storyCompareRows(rows) {
|
|
4694
|
+
return '<div class="echo-compare" aria-label="Problem impact without Echo versus with Echo">' + rows.map(function (row) {
|
|
4695
|
+
if (row.cleared) {
|
|
4696
|
+
return '<div class="cmp-row good"><div class="cmp-label">' + esc(row.label) + '</div><div class="cmp-track cleared">' + esc(row.text) + '</div></div>';
|
|
4697
|
+
}
|
|
4698
|
+
return '<div class="cmp-row ' + esc(row.tone || "") + '"><div class="cmp-label">' + esc(row.label) + '</div><div class="cmp-track"><span class="cmp-fill" style="--w:' + number(storyMeterPct(row.pct, 100, 0)) + '%"></span><strong>' + esc(row.text) + '</strong></div></div>';
|
|
4699
|
+
}).join("") + '</div>';
|
|
4700
|
+
}
|
|
4701
|
+
function storyProblemCount(list) {
|
|
4702
|
+
return list.reduce(function (sum, p) { return sum + (p && p.count ? p.count : 0); }, 0);
|
|
4703
|
+
}
|
|
4704
|
+
function storyProblemTokens(list) {
|
|
4705
|
+
return list.reduce(function (sum, p) {
|
|
4706
|
+
if (!p) return sum;
|
|
4707
|
+
return sum + (p.allocatedWasteTokens || p.qualifiedTokenPressure || 0);
|
|
4708
|
+
}, 0);
|
|
4709
|
+
}
|
|
4710
|
+
function storyWire() {
|
|
4711
|
+
var root = document.getElementById("setup-details"); if (!root) return;
|
|
4712
|
+
storyObjects(root);
|
|
4713
|
+
bindConnect();
|
|
4714
|
+
bindDetailsArrow();
|
|
4715
|
+
}
|
|
4716
|
+
function storyObjects(root) {
|
|
4717
|
+
Array.prototype.forEach.call(root.querySelectorAll("[data-pile]"), function (gp) {
|
|
4718
|
+
var cmds = ["git status", "git diff", "git status", "git log", "git status", "git diff"];
|
|
4719
|
+
var pn = parseInt(gp.getAttribute("data-pile"), 10) || 12;
|
|
4720
|
+
for (var g = 0; g < pn; g++) {
|
|
4721
|
+
var c = document.createElement("span");
|
|
4722
|
+
c.className = "gchip";
|
|
4723
|
+
c.textContent = cmds[g % cmds.length];
|
|
4724
|
+
var col = g % 5, row = Math.floor(g / 5);
|
|
4725
|
+
c.style.left = (col * 88 + ((g * 37) % 26)) + "px";
|
|
4726
|
+
c.style.top = (row * 36 + ((g * 29) % 16)) + "px";
|
|
4727
|
+
c.style.transform = "rotate(" + (((g * 47) % 13) - 6) + "deg)";
|
|
4728
|
+
c.style.zIndex = String(g);
|
|
4729
|
+
gp.appendChild(c);
|
|
4730
|
+
}
|
|
4731
|
+
});
|
|
4732
|
+
Array.prototype.forEach.call(root.querySelectorAll("[data-sheets]"), function (ss) {
|
|
4733
|
+
var sn = parseInt(ss.getAttribute("data-sheets"), 10) || 8;
|
|
4734
|
+
for (var s = sn - 1; s >= 0; s--) {
|
|
4735
|
+
var sh = document.createElement("div");
|
|
4736
|
+
sh.className = "sheet" + (s === 0 ? " top" : "");
|
|
4737
|
+
sh.style.transform = "translate(" + (s * 4) + "px, " + (s * -3) + "px) rotate(" + (((s * 31) % 5) - 2) + "deg)";
|
|
4738
|
+
sh.style.zIndex = String(sn - s);
|
|
4739
|
+
sh.style.opacity = s === 0 ? "1" : String(0.9 - s * 0.06);
|
|
4740
|
+
if (s === 0) sh.innerHTML = '<div class="fl w1"></div><div class="fl w2"></div><div class="fl w3"></div><div class="fl w2"></div>';
|
|
4741
|
+
ss.appendChild(sh);
|
|
4742
|
+
}
|
|
4743
|
+
});
|
|
4744
|
+
}
|
|
4745
|
+
function renderForensicReport(message) {
|
|
4746
|
+
var r = report;
|
|
4747
|
+
if (!r) { showReport(); return; }
|
|
4748
|
+
if (!previewState && (!reportEnvelope || reportEnvelope.kind !== "ready" || reportEnvelope.report !== r)) {
|
|
4749
|
+
throw new Error("A validated local report envelope is required before rendering.");
|
|
4750
|
+
}
|
|
4751
|
+
if (!reportMounted || !document.getElementById("setup-details")) mountReportShell();
|
|
4752
|
+
setCityMode(true);
|
|
4753
|
+
scheduleReportToCity();
|
|
4754
|
+
setHead("AI Coding City", connected ? "Signed in" : "Local");
|
|
4755
|
+
var dArrow = document.getElementById("details-arrow");
|
|
4756
|
+
if (dArrow) dArrow.classList.remove("hidden");
|
|
4757
|
+
|
|
4758
|
+
var s = r.scale || {}, cl = r.cleanliness || {}, rf = r.rereadForensics || {}, cf = r.counterfactual || {}, gs = gsClean(r.canonicalGoldenStandard), gss = gsSummary(r.canonicalGoldenStandard);
|
|
4759
|
+
if (!gs) throw new Error("Validated canonical analysis is required before rendering.");
|
|
4760
|
+
var files = rf.topFiles || [];
|
|
4761
|
+
var topFile = files[0] || null;
|
|
4762
|
+
var usefulPct = Math.round(gs ? gsDisplayUsefulPct(gs) : Math.max(0, 100 - (cl.staleReadSharePct || 0)));
|
|
4763
|
+
var wastePct = Math.round(gs ? gsDisplayWastePct(gs) : (cl.staleReadSharePct || 0));
|
|
4764
|
+
var inputTokens = gs ? (gss.officialInputTokens || 0) : (s.totalTokens || 0);
|
|
4765
|
+
var wasteTokens = gs ? (gss.wasteTokens || 0) : ((s.totalTokens || 0) * (wastePct / 100));
|
|
4766
|
+
var usefulTokens = gs ? (gss.usefulTokens || 0) : Math.max(0, inputTokens - wasteTokens);
|
|
4767
|
+
var reportTargetScore = (typeof cf.targetScore === "number" && isFinite(cf.targetScore)) ? cf.targetScore : Math.min(100, usefulPct + 30);
|
|
4768
|
+
var targetScore = Math.round(Math.max(usefulPct, reportTargetScore));
|
|
4769
|
+
if (gs) {
|
|
4770
|
+
document.getElementById("setup-details").innerHTML =
|
|
4771
|
+
(message ? '<div class="notice" style="max-width:980px;margin:0 auto 24px">' + esc(message) + '</div>' : '') +
|
|
4772
|
+
gsContextAuditStream(r);
|
|
4773
|
+
bindConnect();
|
|
4774
|
+
bindSessionLinks();
|
|
4775
|
+
bindDetailsArrow();
|
|
4776
|
+
renderPencilPies();
|
|
4777
|
+
return;
|
|
4778
|
+
}
|
|
4779
|
+
var p01 = storyProblem(r.canonicalGoldenStandard, "P01");
|
|
4780
|
+
var p03 = storyProblem(r.canonicalGoldenStandard, "P03");
|
|
4781
|
+
var p08 = storyProblem(r.canonicalGoldenStandard, "P08");
|
|
4782
|
+
var p10 = storyProblem(r.canonicalGoldenStandard, "P10");
|
|
4783
|
+
var p13 = storyProblem(r.canonicalGoldenStandard, "P13");
|
|
4784
|
+
var buckets = (gs && gs.buckets) || {};
|
|
4785
|
+
var replayFindings = storyProblemCount([p08, p10]);
|
|
4786
|
+
var carryFindings = storyProblemCount([p01, p03, p13]);
|
|
4787
|
+
var replayTokenPressure = storyProblemTokens([p08, p10]) || (buckets.refind || 0);
|
|
4788
|
+
var carryTokens = storyProblemTokens([p01, p03, p13]) || ((buckets.dead || 0) + (buckets.duplicate || 0));
|
|
4789
|
+
|
|
4790
|
+
var repeatRows = [];
|
|
4791
|
+
if (topFile) {
|
|
4792
|
+
repeatRows.push({
|
|
4793
|
+
value: number(topFile.totalReads || 0) + "x",
|
|
4794
|
+
unit: "reads",
|
|
4795
|
+
code: topFile.file || "top re-read file",
|
|
4796
|
+
detail: (topFile.repo || "workspace") + " · " + number(topFile.sessionsTouched || 0) + " sessions · " + number(topFile.daysTouched || 0) + " days" + (topFile.everChanged ? "" : " · never changed")
|
|
4797
|
+
});
|
|
4798
|
+
}
|
|
4799
|
+
if (p08 && p08.count) repeatRows.push({
|
|
4800
|
+
value: number(p08.count),
|
|
4801
|
+
unit: "git checks",
|
|
4802
|
+
code: "git status · git diff · git log",
|
|
4803
|
+
visual: '<div class="gitpile" data-pile="13" role="img" aria-label="Repeated git commands piling up"></div>',
|
|
4804
|
+
detail: storyProblemMeta(p08)
|
|
4805
|
+
});
|
|
4806
|
+
if (p10 && p10.count) {
|
|
4807
|
+
var q10 = storyQueryCode(p10);
|
|
4808
|
+
repeatRows.push({ value: number(p10.count), unit: "repeat searches", code: q10, title: q10 ? "" : "Re-ran the same search", detail: storyProblemMeta(p10) });
|
|
4809
|
+
}
|
|
4810
|
+
for (var rfi = 1; repeatRows.length < 3 && rfi < files.length; rfi++) {
|
|
4811
|
+
var f = files[rfi];
|
|
4812
|
+
repeatRows.push({ value: number(f.totalReads || 0) + "x", unit: "reads", code: f.file || "re-read file", detail: (f.repo || "workspace") + " · " + number(f.sessionsTouched || 0) + " sessions" });
|
|
4813
|
+
}
|
|
4814
|
+
var replayFoundLabel = replayFindings ? number(replayFindings) + " repeats" : (topFile ? number(topFile.totalReads || 0) + " repeated reads" : "repeated work");
|
|
4815
|
+
var realQuery = storyQueryCode(p10);
|
|
4816
|
+
var fbObj = null;
|
|
4817
|
+
if (!realQuery) {
|
|
4818
|
+
for (var ffi = 0; ffi < files.length; ffi++) {
|
|
4819
|
+
var cand = files[ffi] && files[ffi].file ? String(files[ffi].file) : "";
|
|
4820
|
+
if (cand && !storyGenericFileQuery(cand)) { fbObj = files[ffi]; break; }
|
|
4821
|
+
}
|
|
4822
|
+
if (!fbObj && topFile && topFile.file) fbObj = topFile;
|
|
4823
|
+
}
|
|
4824
|
+
var fileFallback = fbObj && fbObj.file ? String(fbObj.file) : "";
|
|
4825
|
+
var searchQuery = realQuery || fileFallback || '"where is this handled"';
|
|
4826
|
+
var searchCommand = realQuery ? storySearchCommand(realQuery) : (fileFallback ? "read " + fileFallback : 'search "where is this handled"');
|
|
4827
|
+
var searchCardNote = realQuery || !fileFallback ? "same question, session after session" : "same file, session after session";
|
|
4828
|
+
var searchCount = replayFindings || (topFile && topFile.totalReads) || 3;
|
|
4829
|
+
var stampCount = realQuery ? ((p10 && p10.count) || searchCount) : (fbObj && fbObj.totalReads ? fbObj.totalReads : searchCount);
|
|
4830
|
+
var ex10 = p10 && p10.examples && p10.examples[0] ? p10.examples[0] : null;
|
|
4831
|
+
var searchProofDetail = (realQuery && storyProblemMeta(p10)) || (fbObj ? number(fbObj.sessionsTouched || 0) + " sessions · " + number(fbObj.daysTouched || 0) + " days" : "");
|
|
4832
|
+
if (realQuery && ex10 && ex10.repo) searchProofDetail += (searchProofDetail ? " · " : "") + "in " + ex10.repo;
|
|
4833
|
+
var searchProofReal = !!(realQuery || fileFallback);
|
|
4834
|
+
|
|
4835
|
+
var carryRows = [];
|
|
4836
|
+
if (p01 && p01.count) carryRows.push({
|
|
4837
|
+
value: number(p01.count),
|
|
4838
|
+
unit: "old screenshots",
|
|
4839
|
+
title: "Old screenshots stayed in context",
|
|
4840
|
+
visual: '<div class="shotstrip" role="img" aria-label="The same preview screenshot stays in later turns, aging but never leaving"><div class="shotframe"><div class="devshot"><i></i><b></b><em></em></div><span>first used</span></div><div class="shotframe"><div class="devshot"><i></i><b></b><em></em></div></div><div class="shotframe"><div class="devshot"><i></i><b></b><em></em></div></div><div class="shotframe"><div class="devshot"><i></i><b></b><em></em></div></div><div class="shotframe"><div class="devshot"><i></i><b></b><em></em></div></div><div class="shotframe"><div class="devshot"><i></i><b></b><em></em></div><span>still there</span></div></div>',
|
|
4841
|
+
detail: storyProblemMeta(p01)
|
|
4842
|
+
});
|
|
4843
|
+
if (p03 && p03.count) carryRows.push({
|
|
4844
|
+
value: number(p03.count),
|
|
4845
|
+
unit: "old file versions",
|
|
4846
|
+
title: "Old file versions stayed in context",
|
|
4847
|
+
visual: '<div class="sheetstack messy" data-sheets="8" role="img" aria-label="Stacked copies of old file versions, with stale copies stamped"><span class="stalestamp" aria-hidden="true">STALE ×7</span></div>',
|
|
4848
|
+
detail: storyProblemMeta(p03)
|
|
4849
|
+
});
|
|
4850
|
+
if (buckets.dead) carryRows.push({ value: big(buckets.dead), unit: "stale tokens", title: "No-longer-needed context stayed in the window", detail: "Context that kept taking space after it stopped helping" });
|
|
4851
|
+
if (buckets.duplicate && carryRows.length < 3) carryRows.push({ value: big(buckets.duplicate), unit: "duplicate tokens", title: "Repeated context stayed in the window", detail: "The same information showed up more than once across the local scan" });
|
|
4852
|
+
var carryFoundLabel = carryFindings ? number(carryFindings) + " pieces of stale context" : (carryTokens ? big(carryTokens) + " carried tokens" : number(wastePct) + "% of the window");
|
|
4853
|
+
var screenshotCarryLabel = p01 && p01.count ? number(p01.count) + " screenshot re-sends" : "old screenshots";
|
|
4854
|
+
var fileCarryLabel = p03 && p03.count ? number(p03.count) + " stale file copies" : "old file versions";
|
|
4855
|
+
var noteCarryLabel = p13 && p13.count ? number(p13.count) + " carried notes" : "old reasoning notes";
|
|
4856
|
+
var carryProofDetail = storyProblemMeta(p01) || storyProblemMeta(p03) || storyProblemMeta(p13) || "context that kept riding in the window";
|
|
4857
|
+
var replayWithoutPct = storyMeterPct(replayTokenPressure, wasteTokens, replayFindings || (topFile && topFile.totalReads) || 0);
|
|
4858
|
+
var replayWithPct = Math.max(6, Math.min(32, Math.round(replayWithoutPct / Math.max(2, replayFindings || (topFile && topFile.totalReads) || 4))));
|
|
4859
|
+
var carryWithoutPct = storyMeterPct(carryTokens, wasteTokens, carryFindings);
|
|
4860
|
+
|
|
4861
|
+
var currentNoise = Math.max(0, 100 - usefulPct);
|
|
4862
|
+
var targetNoise = Math.max(0, 100 - targetScore);
|
|
4863
|
+
var uwh = r.userWorkingHours || {};
|
|
4864
|
+
var attentionHours = Number(uwh.userAttentionHours || 0);
|
|
4865
|
+
if (!attentionHours && Array.isArray(r.repos)) attentionHours = r.repos.reduce(function (sum, repo) { return sum + Number(repo.attentionHours || 0); }, 0);
|
|
4866
|
+
attentionHours = Math.max(1, attentionHours || 1);
|
|
4867
|
+
var dailyHours = Math.max(1, Number(uwh.averageDailyAttentionHours || 3));
|
|
4868
|
+
var currentDays = Math.max(1, Math.ceil(attentionHours / dailyHours));
|
|
4869
|
+
var reclaimRate = Math.min(0.7, Math.max(0.12, wastePct / 100));
|
|
4870
|
+
var freedDays = Math.max(1, Math.round(currentDays * reclaimRate));
|
|
4871
|
+
var echoDays = Math.max(1, currentDays - freedDays);
|
|
4872
|
+
var endDate = r.scanEndDate ? new Date(r.scanEndDate + "T12:00:00") : new Date();
|
|
4873
|
+
if (!isFinite(endDate.getTime())) endDate = new Date();
|
|
4874
|
+
var echoDate = new Date(endDate.getTime() - freedDays * 86400000);
|
|
4875
|
+
var dateFormat = { month: "short", day: "numeric" };
|
|
4876
|
+
var currentDateLabel = endDate.toLocaleDateString("en-US", dateFormat);
|
|
4877
|
+
var echoDateLabel = echoDate.toLocaleDateString("en-US", dateFormat);
|
|
4878
|
+
document.getElementById("setup-details").innerHTML =
|
|
4879
|
+
'<div class="echo-rdo canonical-report">' +
|
|
4880
|
+
gsGoldenSection(r.canonicalGoldenStandard) +
|
|
4881
|
+
'</div>' +
|
|
4882
|
+
'<div class="echo-story">' +
|
|
4883
|
+
'<div class="story-shell">' +
|
|
4884
|
+
'<nav class="story-nav" aria-label="Report sections">' +
|
|
4885
|
+
'<a href="#story-refind"><b>01</b><span>Repeat search</span></a>' +
|
|
4886
|
+
'<a href="#story-carry"><b>02</b><span>Old context</span></a>' +
|
|
4887
|
+
'<a href="#story-time"><b>03</b><span>Time back</span></a>' +
|
|
4888
|
+
'</nav>' +
|
|
4889
|
+
'<div class="story-main">' +
|
|
4890
|
+
(message ? '<div class="notice" style="margin-bottom:18px">' + esc(message) + '</div>' : '') +
|
|
4891
|
+
'<section id="story-refind" class="beat search-beat rise" style="--d:0ms">' +
|
|
4892
|
+
'<p class="story-kicker">Problem 1 of 2</p>' +
|
|
4893
|
+
'<h3 class="search-title">Your agent keeps searching.<span class="again-rhythm"><i>Again.</i><i>Again.</i><i>Again.</i></span></h3>' +
|
|
4894
|
+
'<p class="lead search-lead">A fresh session remembers nothing from the last one. So your agent digs up what it already found. This scan found <b>' + esc(replayFoundLabel) + '</b> in your sessions.</p>' +
|
|
4895
|
+
'<div class="search-compare" role="img" aria-label="Without Echo, the same search repeats. With Echo, one recall returns the saved answer.">' +
|
|
4896
|
+
'<div class="search-side search-without">' +
|
|
4897
|
+
'<div class="search-side-head"><span>Without Echo</span><b>' + esc(number(searchCount)) + ' repeats</b></div>' +
|
|
4898
|
+
'<div class="repeat-one">' +
|
|
4899
|
+
'<i class="rq-ghost g3" aria-hidden="true"></i>' +
|
|
4900
|
+
'<i class="rq-ghost g2" aria-hidden="true"></i>' +
|
|
4901
|
+
'<i class="rq-ghost g1" aria-hidden="true"></i>' +
|
|
4902
|
+
'<span class="rq-stamp" aria-hidden="true">×' + esc(number(stampCount)) + '</span>' +
|
|
4903
|
+
'<div class="repeat-main"><code>> ' + esc(searchCommand) + '</code><small>' + esc(searchCardNote) + '</small></div>' +
|
|
4904
|
+
'</div>' +
|
|
4905
|
+
'<p class="search-side-note">Every repeat reloads files, searches, and tool output before work can continue.</p>' +
|
|
4906
|
+
'</div>' +
|
|
4907
|
+
'<div class="search-turn" aria-hidden="true">→</div>' +
|
|
4908
|
+
'<div class="search-side search-with">' +
|
|
4909
|
+
'<div class="search-side-head"><span>With Echo</span><b>1 recall</b></div>' +
|
|
4910
|
+
'<div class="recall-card">' +
|
|
4911
|
+
'<div class="recall-top"><img src="/hud-assets/echo-face-cutout.png" alt="" /><span>Echo memory found</span></div>' +
|
|
4912
|
+
'<code>> recall ' + esc(searchQuery) + '</code>' +
|
|
4913
|
+
'<div class="recall-result">File, decision, and next step returned. No workspace search required.</div>' +
|
|
4914
|
+
'</div>' +
|
|
4915
|
+
'<p class="search-side-note">Echo recorded what mattered while you worked, then brought it back here.</p>' +
|
|
4916
|
+
'</div>' +
|
|
4917
|
+
'</div>' +
|
|
4918
|
+
(searchProofReal ? '<div class="search-proof"><span>Found locally</span><strong>' + esc(number(stampCount)) + '×</strong><code>' + esc(searchCommand) + '</code><em>' + esc(searchProofDetail) + '</em></div>' : '') +
|
|
4919
|
+
'</section>' +
|
|
4920
|
+
|
|
4921
|
+
'<section id="story-carry" class="beat carry-beat rise" style="--d:80ms">' +
|
|
4922
|
+
'<p class="story-kicker">Problem 2 of 2</p>' +
|
|
4923
|
+
'<h3 class="carry-title">Old context keeps riding along.<span>Long after it stopped helping.</span></h3>' +
|
|
4924
|
+
'<p class="lead carry-lead">Nothing leaves the context window on its own. The screenshot it is done with. The file it already replaced. Still there, re-sent every turn. This scan found <b>' + esc(carryFoundLabel) + '</b>.</p>' +
|
|
4925
|
+
'<div class="carry-compare" role="img" aria-label="Without Echo, old context keeps riding in the window. With Echo, one checkpoint carries only the current work forward.">' +
|
|
4926
|
+
'<div class="carry-side carry-without">' +
|
|
4927
|
+
'<div class="carry-side-head"><span>Without Echo</span><b>' + number(currentNoise) + '% noise</b></div>' +
|
|
4928
|
+
'<div class="carry-stack">' +
|
|
4929
|
+
'<div class="carry-object"><span class="carry-glyph">IMG</span><div><b>' + esc(screenshotCarryLabel) + '</b><small>evidence already used, still taking space</small></div></div>' +
|
|
4930
|
+
'<div class="carry-object"><span class="carry-glyph file">FILE</span><div><b>' + esc(fileCarryLabel) + '</b><small>newer version already exists</small></div></div>' +
|
|
4931
|
+
'<div class="carry-object"><span class="carry-glyph">NOTE</span><div><b>' + esc(noteCarryLabel) + '</b><small>path abandoned, still carried forward</small></div></div>' +
|
|
4932
|
+
'</div>' +
|
|
4933
|
+
'</div>' +
|
|
4934
|
+
'<div class="carry-turn" aria-hidden="true">→</div>' +
|
|
4935
|
+
'<div class="carry-side carry-with">' +
|
|
4936
|
+
'<div class="carry-side-head"><span>With Echo</span><b>1 checkpoint</b></div>' +
|
|
4937
|
+
'<div class="checkpoint-card">' +
|
|
4938
|
+
'<div class="checkpoint-top"><img src="/hud-assets/echo-face-cutout.png" alt="" /><span>Checkpoint ready</span></div>' +
|
|
4939
|
+
'<div class="checkpoint-lines"><span><b>✓ Decision</b> · what we decided</span><span><b>✓ Current files</b> · what is still active</span><span><b>✓ Next step</b> · where to continue</span></div>' +
|
|
4940
|
+
'</div>' +
|
|
4941
|
+
'<p class="search-side-note">Only the work that still matters crosses into the fresh session.</p>' +
|
|
4942
|
+
'</div>' +
|
|
4943
|
+
'</div>' +
|
|
4944
|
+
'<div class="carry-proof"><span>Found locally</span><strong>' + esc(carryFoundLabel) + '</strong><em>' + esc(carryProofDetail) + '</em></div>' +
|
|
4945
|
+
'</section>' +
|
|
4946
|
+
|
|
4947
|
+
'<section id="story-time" class="payoff rise" style="--d:160ms">' +
|
|
4948
|
+
'<p class="story-kicker">Time back</p>' +
|
|
4949
|
+
'<h3 class="payoff-title">Get ' + number(freedDays) + ' working days back.</h3>' +
|
|
4950
|
+
'<p class="lead payoff-lead">Same project state. Fewer searches to repeat and less old context to reload.</p>' +
|
|
4951
|
+
'<div class="time-payoff" role="img" aria-label="At your current pace, Echo models ' + number(freedDays) + ' working days freed from ' + number(currentDays) + ' days of agent work.">' +
|
|
4952
|
+
'<div class="time-state time-current"><span>Without Echo</span><strong>' + number(currentDays) + ' days</strong><small>' + number(Math.round(attentionHours)) + 'h of agent work at ' + rdoDec(dailyHours) + 'h per active day</small><div class="time-date"><b>' + esc(currentDateLabel) + '</b><span>at today’s pace</span></div></div>' +
|
|
4953
|
+
'<div class="time-arrow" aria-hidden="true">→</div>' +
|
|
4954
|
+
'<div class="time-state time-next"><span>With Echo</span><strong>' + number(echoDays) + ' days</strong><small>modeled after removing ' + number(wastePct) + '% wasteful context from this scan</small><div class="time-date"><b>' + esc(echoDateLabel) + '</b><span>same state, sooner</span></div></div>' +
|
|
4955
|
+
'</div>' +
|
|
4956
|
+
'<div class="story-actions"><button class="story-cta" data-connect-echo>' + (connected ? "Continue →" : "Connect Echo →") + '</button></div>' +
|
|
4957
|
+
'<p class="privacy-line">Compiled locally from ' + sourceHtml + ' · ' + big(inputTokens) + ' terminal input tokens analyzed · ' + big(usefulTokens) + ' useful / ' + big(wasteTokens) + ' waste. No transcripts leave this machine.</p>' +
|
|
4958
|
+
'</section>' +
|
|
4959
|
+
'</div>' +
|
|
4960
|
+
'</div>' +
|
|
4961
|
+
'</div>';
|
|
4962
|
+
storyWire();
|
|
4963
|
+
rdoWire();
|
|
4964
|
+
}
|
|
4965
|
+
|
|
1959
4966
|
/* ---------- dashboard (post-auth extraction) ---------- */
|
|
1960
4967
|
function statusStrip(title, detail, kind) {
|
|
1961
4968
|
return '<div class="status-strip ' + esc(kind || "") + '"><span class="dot"></span><div><strong>' + esc(title) + '</strong><p>' + esc(detail) + '</p></div></div>';
|
|
1962
4969
|
}
|
|
1963
|
-
function
|
|
1964
|
-
|
|
1965
|
-
|
|
4970
|
+
function acceptReportScanId(envelope) {
|
|
4971
|
+
var scanId = envelope && typeof envelope.scanId === "string" ? envelope.scanId : "";
|
|
4972
|
+
if (!scanId) throw new Error("Report response is missing its scan identity.");
|
|
4973
|
+
if (activeScanId && activeScanId !== scanId) throw new Error("The local scan identity changed before the report completed.");
|
|
4974
|
+
activeScanId = scanId;
|
|
4975
|
+
}
|
|
4976
|
+
function validateProductionReportEnvelope(envelope) {
|
|
4977
|
+
if (!envelope || typeof envelope !== "object" || envelope.schemaVersion !== 1 || envelope.mode !== "production") {
|
|
4978
|
+
throw new Error("Report response has an unsupported production schema.");
|
|
4979
|
+
}
|
|
4980
|
+
acceptReportScanId(envelope);
|
|
4981
|
+
if (["scanning", "ready", "empty", "failed"].indexOf(envelope.kind) === -1) {
|
|
4982
|
+
throw new Error("Report response has an unknown state.");
|
|
4983
|
+
}
|
|
4984
|
+
if (envelope.kind === "scanning") return envelope;
|
|
4985
|
+
if (envelope.kind === "failed") {
|
|
4986
|
+
if (!envelope.error || typeof envelope.error.code !== "string" || typeof envelope.error.message !== "string") {
|
|
4987
|
+
throw new Error("Report failure response is malformed.");
|
|
4988
|
+
}
|
|
4989
|
+
return envelope;
|
|
4990
|
+
}
|
|
4991
|
+
var r = envelope.report;
|
|
4992
|
+
if (!r || typeof r !== "object" || r.schemaVersion !== 1 || r.dataOrigin !== "local-workspace-scan") {
|
|
4993
|
+
throw new Error("Report did not prove that it came from this local workspace scan.");
|
|
4994
|
+
}
|
|
4995
|
+
var scale = r.scale || {};
|
|
4996
|
+
var sessions = Number(scale.sessionCount);
|
|
4997
|
+
var gs = gsClean(r.canonicalGoldenStandard);
|
|
4998
|
+
var summary = (gs && gs.summary) || {};
|
|
4999
|
+
var canonicalSessions = Number(summary.sessionsAnalyzed || 0);
|
|
5000
|
+
var canonicalInput = Number(summary.officialInputTokens || 0);
|
|
5001
|
+
var skipped = Number((gs && gs.diagnostics && gs.diagnostics.skippedSessions) || 0);
|
|
5002
|
+
if (envelope.kind === "empty") {
|
|
5003
|
+
if (sessions !== 0 || canonicalSessions !== 0 || canonicalInput !== 0 || Number(scale.totalInputTokens || 0) !== 0) {
|
|
5004
|
+
throw new Error("An empty report contained non-empty usage data.");
|
|
5005
|
+
}
|
|
5006
|
+
return envelope;
|
|
5007
|
+
}
|
|
5008
|
+
if (!gs || !Number.isFinite(sessions) || sessions <= 0 || canonicalSessions !== sessions || canonicalInput <= 0 || skipped !== 0) {
|
|
5009
|
+
throw new Error("Canonical analysis is incomplete or does not match the provider session cohort.");
|
|
5010
|
+
}
|
|
5011
|
+
if (!Array.isArray(r.repos) || r.repos.length === 0) {
|
|
5012
|
+
throw new Error("The local report has no workspace rows to render.");
|
|
5013
|
+
}
|
|
5014
|
+
gsBillingProjection(r, gs);
|
|
5015
|
+
return envelope;
|
|
5016
|
+
}
|
|
5017
|
+
function renderReportIssue(code, message) {
|
|
5018
|
+
report = null;
|
|
5019
|
+
reportEnvelope = null;
|
|
5020
|
+
resetReportSurface();
|
|
5021
|
+
setHead("Your local report could not be shown", "Needs attention");
|
|
1966
5022
|
app.className = "loading-panel";
|
|
1967
5023
|
app.innerHTML =
|
|
1968
|
-
'<div style="padding:24px"
|
|
1969
|
-
|
|
1970
|
-
|
|
5024
|
+
'<div style="padding:24px" data-report-state="failed">' +
|
|
5025
|
+
'<h2>No backup or sample data was shown</h2>' +
|
|
5026
|
+
'<p class="helper" style="margin-top:10px">' + esc(message || "The local scan did not produce a report that passed validation.") + '</p>' +
|
|
5027
|
+
'<div class="warning" style="margin-top:16px;margin-bottom:0"><strong>Error ' + esc(code || "REPORT_FAILED") + '</strong><br />Close this tab, check the terminal, and run <code>echomem-mcp init</code> again.</div>' +
|
|
5028
|
+
'</div>';
|
|
5029
|
+
}
|
|
5030
|
+
function renderEmptyReport() {
|
|
5031
|
+
report = null;
|
|
5032
|
+
reportEnvelope = null;
|
|
5033
|
+
resetReportSurface();
|
|
5034
|
+
setHead("No local coding history found", "Local scan complete");
|
|
5035
|
+
app.className = "loading-panel";
|
|
5036
|
+
app.innerHTML =
|
|
5037
|
+
'<div style="padding:24px" data-report-state="empty">' +
|
|
5038
|
+
'<h2>There is no local Codex or Claude Code history to analyze yet</h2>' +
|
|
5039
|
+
'<p class="helper" style="margin-top:10px">Echo checked this machine and found no supported sessions with token usage. Start a coding session, then run <code>echomem-mcp init</code> again.</p>' +
|
|
5040
|
+
'<p class="helper" style="margin-top:10px"><strong>No demo data, percentages, savings, persona, or city was substituted.</strong></p>' +
|
|
5041
|
+
'</div>';
|
|
5042
|
+
}
|
|
5043
|
+
function renderBridgeIssue() {
|
|
5044
|
+
renderReportIssue("BRIDGE_UNREACHABLE", "The local bridge stopped answering before your report was ready. Your terminal may have closed or the process may have stopped.");
|
|
1971
5045
|
}
|
|
1972
5046
|
function renderLogoutPending() {
|
|
1973
5047
|
setCityMode(false);
|
|
1974
5048
|
setHead("Signing out locally", "Working");
|
|
1975
5049
|
app.className = "loading-panel";
|
|
1976
|
-
app.innerHTML = '<div style="padding:24px"><h2>
|
|
5050
|
+
app.innerHTML = '<div style="padding:24px"><h2>Signing this device out</h2><p class="helper">If Echo is scanning local files, this can take a few seconds to answer.</p></div>';
|
|
1977
5051
|
}
|
|
1978
5052
|
function renderLogoutIssue(error) {
|
|
1979
5053
|
setCityMode(false);
|
|
1980
5054
|
setHead("Sign out did not finish", "Needs attention");
|
|
1981
5055
|
app.className = "loading-panel";
|
|
1982
5056
|
app.innerHTML =
|
|
1983
|
-
'<div style="padding:24px"><h2>
|
|
1984
|
-
'<p class="helper">Keep the terminal open and retry. If the command exited, close this tab and run
|
|
5057
|
+
'<div style="padding:24px"><h2>Echo on this machine did not answer</h2>' +
|
|
5058
|
+
'<p class="helper">Keep the terminal open and retry. If the command exited, close this tab and run echomem-mcp init again.</p>' +
|
|
1985
5059
|
(error ? '<div class="warning" style="margin-top:16px;margin-bottom:0">' + esc(error) + '</div>' : '') +
|
|
1986
5060
|
'<div class="actions"><button id="logout" class="secondary">Retry sign out</button></div></div>';
|
|
1987
5061
|
var lo = document.getElementById("logout"); if (lo) lo.onclick = localLogout;
|
|
@@ -2111,7 +5185,7 @@ export function renderSetupPage() {
|
|
|
2111
5185
|
dashMounted = true;
|
|
2112
5186
|
}
|
|
2113
5187
|
document.getElementById("exBanner").innerHTML =
|
|
2114
|
-
(statsSlow && !stats ? '<div class="warning">Local counts are taking longer than expected. You can still ask
|
|
5188
|
+
(statsSlow && !stats ? '<div class="warning">Local counts are taking longer than expected. You can still ask Echo to extract anything unprocessed.</div>' : '') +
|
|
2115
5189
|
(error ? '<div class="error">' + esc(error) + '</div>' : '');
|
|
2116
5190
|
document.getElementById("exHeadline").textContent = headline;
|
|
2117
5191
|
document.getElementById("exSub").textContent = sub;
|
|
@@ -2217,7 +5291,9 @@ export function renderSetupPage() {
|
|
|
2217
5291
|
if (done || stopped) {
|
|
2218
5292
|
setHead(done ? "Memory captured" : "Extraction stopped", done ? "Done" : "Needs attention");
|
|
2219
5293
|
var memoryNoun = extracted === 1 ? "new memory" : "new memories";
|
|
5294
|
+
var savedMemoryLabel = extracted === 1 ? "memory saved" : "memories saved";
|
|
2220
5295
|
var conversationNoun = completed === 1 ? "conversation" : "conversations";
|
|
5296
|
+
var timelineUrl = "https://yeahecho.com/memories/timeline";
|
|
2221
5297
|
var stopDetail = String(progress.error) === "key-expired"
|
|
2222
5298
|
? "Your encryption key was not accepted. Re-run extraction; if it keeps failing, run <code>echomem-mcp unlock</code> first. Already-done conversations are skipped."
|
|
2223
5299
|
: esc(progress.error || "Re-run extraction to finish the rest. Already-done conversations are skipped.");
|
|
@@ -2226,27 +5302,25 @@ export function renderSetupPage() {
|
|
|
2226
5302
|
(done ? "" : '<p class="kicker-mono">Extraction stopped</p>') +
|
|
2227
5303
|
'<div class="resultTop">' +
|
|
2228
5304
|
'<div>' +
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
5305
|
+
(done
|
|
5306
|
+
? '<h2 class="savedHeading"><span class="savedMain"><strong>' + esc(number(extracted)) + '</strong> ' + esc(savedMemoryLabel) + '</span><a class="viewEchoLink" href="' + timelineUrl + '" target="_blank" rel="noopener noreferrer">(view in yeahecho)</a></h2>'
|
|
5307
|
+
: '<h2>Some conversations need another pass.</h2>' +
|
|
5308
|
+
'<p class="exSub">' + stopDetail + '</p>' +
|
|
5309
|
+
'<div class="resultNumber"><strong>' + esc(number(extracted)) + '</strong><span>' + esc(memoryNoun) + '</span></div>') +
|
|
2232
5310
|
'</div>' +
|
|
2233
5311
|
'</div>' +
|
|
2234
5312
|
(done ? tryItCard() : "") +
|
|
2235
|
-
|
|
2236
|
-
'<
|
|
2237
|
-
|
|
2238
|
-
'<div class="
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
5313
|
+
(done ? "" :
|
|
5314
|
+
'<div class="memoryReceipt">' +
|
|
5315
|
+
'<p class="receiptK">What just happened</p>' +
|
|
5316
|
+
'<div class="finishStats">' +
|
|
5317
|
+
'<div class="finishStat"><strong>' + esc(number(completed)) + '</strong><span>' + esc(conversationNoun) + ' extracted</span></div>' +
|
|
5318
|
+
'<div class="finishStat"><strong>' + esc(number(extracted)) + '</strong><span>memories ready</span></div>' +
|
|
5319
|
+
'</div>' +
|
|
5320
|
+
'</div>') +
|
|
2242
5321
|
'</div>' +
|
|
2243
5322
|
(done ? hudTip() + doneCloseNote() : "");
|
|
2244
|
-
|
|
2245
|
-
if (copyBtn) copyBtn.addEventListener("click", copyPromptOnly);
|
|
2246
|
-
var codexBtn = document.getElementById("tryCodex");
|
|
2247
|
-
if (codexBtn) codexBtn.addEventListener("click", tryInCodex);
|
|
2248
|
-
var claudeBtn = document.getElementById("tryClaudeDesktop");
|
|
2249
|
-
if (claudeBtn) claudeBtn.addEventListener("click", openClaudeDesktop);
|
|
5323
|
+
wireTryButtons();
|
|
2250
5324
|
return;
|
|
2251
5325
|
}
|
|
2252
5326
|
setHead(preparing ? "Preparing" : "Extracting", preparing ? "Preparing" : "Extracting");
|
|
@@ -2310,6 +5384,11 @@ export function renderSetupPage() {
|
|
|
2310
5384
|
}
|
|
2311
5385
|
async function copyPromptOnly() {
|
|
2312
5386
|
var copied = await copyRecallPrompt();
|
|
5387
|
+
var copyBtn = document.getElementById("tryCopy");
|
|
5388
|
+
if (copyBtn && copied) {
|
|
5389
|
+
copyBtn.textContent = "Copied";
|
|
5390
|
+
window.setTimeout(function () { copyBtn.textContent = "Copy prompt"; }, 1600);
|
|
5391
|
+
}
|
|
2313
5392
|
setLaunchStatus(copied ? "Prompt copied." : "Could not copy automatically. Select the prompt and copy it.");
|
|
2314
5393
|
}
|
|
2315
5394
|
function codexDeepLink(prompt) {
|
|
@@ -2338,17 +5417,28 @@ export function renderSetupPage() {
|
|
|
2338
5417
|
function tryItCard() {
|
|
2339
5418
|
var prompt = recallPrompt();
|
|
2340
5419
|
return '<div class="tryCard">' +
|
|
2341
|
-
'<
|
|
2342
|
-
'<
|
|
2343
|
-
|
|
5420
|
+
'<h3 class="tryTitle">Echo MCP now available to use</h3>' +
|
|
5421
|
+
'<div class="promptShell">' +
|
|
5422
|
+
'<div class="promptControls">' +
|
|
5423
|
+
'<button type="button" id="tryCopy" class="promptCopyLabel">Copy prompt</button>' +
|
|
5424
|
+
'</div>' +
|
|
5425
|
+
'<pre id="tryPrompt">' + esc(prompt) + '</pre>' +
|
|
5426
|
+
'</div>' +
|
|
2344
5427
|
'<div class="agentLaunch" aria-label="Open in a coding agent">' +
|
|
2345
5428
|
'<button type="button" id="tryCodex"><span class="agentIcon"><img src="/agent-icons/codex.png" alt="" onerror="this.style.display="none";this.nextElementSibling.style.display="grid";" /><span class="agentFallback">CX</span></span><span class="agentText"><strong>Try in Codex</strong><small>copy prompt + open session</small></span></button>' +
|
|
2346
5429
|
'<button type="button" id="tryClaudeDesktop"><span class="agentIcon"><img src="/agent-icons/claude-desktop.png" alt="" onerror="this.style.display="none";this.nextElementSibling.style.display="grid";" /><span class="agentFallback">CL</span></span><span class="agentText"><strong>Open Claude Desktop</strong><small>copy prompt + open app</small></span></button>' +
|
|
2347
|
-
'<button type="button" id="tryCopy"><span class="agentIcon"><span class="copyGlyph" aria-hidden="true"></span></span><span class="agentText"><strong>Copy prompt</strong><small>paste anywhere</small></span></button>' +
|
|
2348
5430
|
'</div>' +
|
|
2349
|
-
'<p class="launchStatus" id="launchStatus"
|
|
5431
|
+
'<p class="launchStatus" id="launchStatus"></p>' +
|
|
2350
5432
|
'</div>';
|
|
2351
5433
|
}
|
|
5434
|
+
function wireTryButtons() {
|
|
5435
|
+
var copyBtn = document.getElementById("tryCopy");
|
|
5436
|
+
if (copyBtn) copyBtn.addEventListener("click", copyPromptOnly);
|
|
5437
|
+
var codexBtn = document.getElementById("tryCodex");
|
|
5438
|
+
if (codexBtn) codexBtn.addEventListener("click", tryInCodex);
|
|
5439
|
+
var claudeBtn = document.getElementById("tryClaudeDesktop");
|
|
5440
|
+
if (claudeBtn) claudeBtn.addEventListener("click", openClaudeDesktop);
|
|
5441
|
+
}
|
|
2352
5442
|
function hudTip() {
|
|
2353
5443
|
return '<p class="hudTip"><img src="/hud-assets/echo-face-cutout.png" alt="" /><span><strong>Live HUD:</strong> Echo watches the context window while you work. When the score drifts low, start a fresh session and carry the checkpoint forward. In Codex or Claude Code, ask “<strong>open the EchoMem HUD</strong>” or run <code>echomem-hud app</code>.</span></p>';
|
|
2354
5444
|
}
|
|
@@ -2376,9 +5466,11 @@ export function renderSetupPage() {
|
|
|
2376
5466
|
'<div class="skipStage">' +
|
|
2377
5467
|
'<span class="seal">Skipped for now</span>' +
|
|
2378
5468
|
'<h2>You can come back anytime.</h2>' +
|
|
2379
|
-
'<p>Echo saved what already finished. When you are ready, run <code>echomem-mcp
|
|
5469
|
+
'<p>Echo saved what already finished. When you are ready, run <code>echomem-mcp init</code> again — it picks up right where it left off.</p>' +
|
|
5470
|
+
tryItCard() +
|
|
2380
5471
|
hudTip() +
|
|
2381
5472
|
'</div>';
|
|
5473
|
+
wireTryButtons();
|
|
2382
5474
|
}
|
|
2383
5475
|
async function endExtraction() {
|
|
2384
5476
|
if (extractionEnded) return;
|
|
@@ -2426,27 +5518,65 @@ export function renderSetupPage() {
|
|
|
2426
5518
|
async function showReport() {
|
|
2427
5519
|
if (reportPollStarted) return;
|
|
2428
5520
|
reportPollStarted = true;
|
|
2429
|
-
if (!report) renderLoading();
|
|
5521
|
+
if (!report) renderLoading({ label: "Starting local scan" });
|
|
2430
5522
|
try {
|
|
2431
5523
|
var fails = 0;
|
|
2432
5524
|
for (;;) {
|
|
2433
5525
|
if (decisionMade) return;
|
|
5526
|
+
var res;
|
|
2434
5527
|
try {
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
if (res.status === 202) {
|
|
2438
|
-
fails = 0;
|
|
2439
|
-
renderLoading();
|
|
2440
|
-
} else {
|
|
2441
|
-
throw new Error(await res.text() || ("HTTP " + res.status));
|
|
2442
|
-
}
|
|
2443
|
-
} catch (e) {
|
|
5528
|
+
res = await fetchWithTimeout("/report?nonce=" + encodeURIComponent(nonce), { credentials: "omit", cache: "no-store" }, 12000);
|
|
5529
|
+
} catch (_) {
|
|
2444
5530
|
// Only a RUN of consecutive failures is a real bridge problem. A long scan that keeps
|
|
2445
|
-
// answering 202 must never trip this
|
|
5531
|
+
// answering valid 202 envelopes must never trip this.
|
|
2446
5532
|
fails++;
|
|
2447
|
-
if (
|
|
5533
|
+
if (fails >= 8) { renderBridgeIssue(); return; }
|
|
5534
|
+
await sleep(700);
|
|
5535
|
+
continue;
|
|
5536
|
+
}
|
|
5537
|
+
if (res.status === 403) {
|
|
5538
|
+
renderReportIssue("SETUP_SESSION_INVALID", "This setup link is missing or has an invalid local session nonce.");
|
|
5539
|
+
return;
|
|
2448
5540
|
}
|
|
2449
|
-
|
|
5541
|
+
var envelope;
|
|
5542
|
+
try {
|
|
5543
|
+
envelope = await res.json();
|
|
5544
|
+
} catch (_) {
|
|
5545
|
+
renderReportIssue("REPORT_RESPONSE_INVALID", "The local bridge returned a report response that could not be decoded.");
|
|
5546
|
+
return;
|
|
5547
|
+
}
|
|
5548
|
+
try {
|
|
5549
|
+
validateProductionReportEnvelope(envelope);
|
|
5550
|
+
} catch (_) {
|
|
5551
|
+
renderReportIssue("REPORT_SCHEMA_INVALID", "The local report failed its provenance or ledger consistency checks.");
|
|
5552
|
+
return;
|
|
5553
|
+
}
|
|
5554
|
+
if (res.status === 202 && envelope.kind === "scanning") {
|
|
5555
|
+
fails = 0;
|
|
5556
|
+
renderLoading(envelope.progress || {});
|
|
5557
|
+
await sleep(700);
|
|
5558
|
+
continue;
|
|
5559
|
+
}
|
|
5560
|
+
if (res.status === 200 && envelope.kind === "empty") {
|
|
5561
|
+
renderEmptyReport();
|
|
5562
|
+
return;
|
|
5563
|
+
}
|
|
5564
|
+
if (res.status === 200 && envelope.kind === "ready") {
|
|
5565
|
+
reportEnvelope = envelope;
|
|
5566
|
+
report = envelope.report;
|
|
5567
|
+
try {
|
|
5568
|
+
renderForensicReport();
|
|
5569
|
+
} catch (_) {
|
|
5570
|
+
renderReportIssue("RENDER_FAILED", "Your validated local report could not be rendered safely.");
|
|
5571
|
+
}
|
|
5572
|
+
return;
|
|
5573
|
+
}
|
|
5574
|
+
if (envelope.kind === "failed") {
|
|
5575
|
+
renderReportIssue(envelope.error.code, envelope.error.message);
|
|
5576
|
+
return;
|
|
5577
|
+
}
|
|
5578
|
+
renderReportIssue("REPORT_STATE_MISMATCH", "The local bridge returned a report state that did not match its HTTP status.");
|
|
5579
|
+
return;
|
|
2450
5580
|
}
|
|
2451
5581
|
} finally { reportPollStarted = false; }
|
|
2452
5582
|
}
|
|
@@ -2458,12 +5588,17 @@ export function renderSetupPage() {
|
|
|
2458
5588
|
connected = true;
|
|
2459
5589
|
closeAuthWindow();
|
|
2460
5590
|
void waitForStats();
|
|
5591
|
+
} else if (data.type === "echomem:city-ready" && data.nonce === nonce) {
|
|
5592
|
+
scheduleReportToCity();
|
|
2461
5593
|
} else if (data.type === "echomem:connect-click") {
|
|
2462
5594
|
onConnectClick();
|
|
2463
5595
|
}
|
|
2464
5596
|
});
|
|
2465
5597
|
async function init() {
|
|
2466
|
-
if (!nonce) throw new Error("Missing setup nonce. Re-run echomem-mcp
|
|
5598
|
+
if (!nonce) throw new Error("Missing setup nonce. Re-run echomem-mcp init.");
|
|
5599
|
+
// Older builds cached a report under the nonce. It is never an authority now; the current
|
|
5600
|
+
// server envelope and scan identity are the only inputs allowed to unlock personalized UI.
|
|
5601
|
+
try { sessionStorage.removeItem("echomem:setup-report:" + nonce); } catch (_) {}
|
|
2467
5602
|
if (params.get("connected") === "1") {
|
|
2468
5603
|
connected = true;
|
|
2469
5604
|
if (notifyOpenerConnected()) {
|
|
@@ -2490,48 +5625,7 @@ export function renderSetupPage() {
|
|
|
2490
5625
|
// Scan-first: show the local forensic report before asking the user to connect.
|
|
2491
5626
|
await showReport();
|
|
2492
5627
|
}
|
|
2493
|
-
|
|
2494
|
-
// state directly with sample data (no server, no nonce). Lets us eyeball every state of the
|
|
2495
|
-
// first-impression flow without running a real 3000-session import.
|
|
2496
|
-
var previewState = params.get("preview");
|
|
2497
|
-
if (previewState === "extract-counting") {
|
|
2498
|
-
stats = null;
|
|
2499
|
-
renderDashboard();
|
|
2500
|
-
} else if (previewState === "extract-ready") {
|
|
2501
|
-
stats = {
|
|
2502
|
-
sessions: { total: 136, codex: 82, claudeCode: 54 },
|
|
2503
|
-
migratable: {
|
|
2504
|
-
pending: 28,
|
|
2505
|
-
pendingTotal: 28,
|
|
2506
|
-
pendingCodex: 19,
|
|
2507
|
-
pendingClaudeCode: 9,
|
|
2508
|
-
alreadyMigrated: 108,
|
|
2509
|
-
skippedActive: 0,
|
|
2510
|
-
eta: { estimatedLabel: "under 2 minutes" },
|
|
2511
|
-
estimatedLabel: "under 2 minutes",
|
|
2512
|
-
},
|
|
2513
|
-
discovery: { phase: "account", exact: false },
|
|
2514
|
-
};
|
|
2515
|
-
renderDashboard();
|
|
2516
|
-
} else if (previewState === "extract-run") {
|
|
2517
|
-
renderProgress({ status: "running", total: 28, completed: 4, extracted: 19, failed: 1, latest: "codex 2026-07-01", latestRepo: "EchoMem-Chrome" });
|
|
2518
|
-
} else if (previewState === "extract-overdue") {
|
|
2519
|
-
exStartAt = Date.now() - 200000;
|
|
2520
|
-
exDeadline = Date.now() - 1000;
|
|
2521
|
-
renderProgress({ status: "running", total: 28, completed: 21, extracted: 96, failed: 2, latest: "claude-code 2026-06-28", latestRepo: "DG-Web" });
|
|
2522
|
-
} else if (previewState === "extract-done") {
|
|
2523
|
-
renderProgress({ status: "completed", total: 28, completed: 28, extracted: 128, failed: 0 });
|
|
2524
|
-
} else if (previewState === "extract-ending") {
|
|
2525
|
-
renderEnding();
|
|
2526
|
-
} else if (previewState === "extract-skipped") {
|
|
2527
|
-
renderSkipped();
|
|
2528
|
-
} else {
|
|
2529
|
-
init().catch(function (e) {
|
|
2530
|
-
setHead("Connection issue", "Needs attention");
|
|
2531
|
-
app.className = "error";
|
|
2532
|
-
app.textContent = e && e.message ? e.message : String(e);
|
|
2533
|
-
});
|
|
2534
|
-
}
|
|
5628
|
+
${startupScript}
|
|
2535
5629
|
})();
|
|
2536
5630
|
</script>
|
|
2537
5631
|
</body>
|