@jossuealcala/madre 0.5.2 → 0.7.0

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.
Files changed (47) hide show
  1. package/CHANGELOG.md +265 -1
  2. package/README.md +33 -1
  3. package/docs/SDK.md +173 -6
  4. package/docs/sdk/connector-module.mjs +186 -0
  5. package/package.json +6 -2
  6. package/public/abduction.js +384 -0
  7. package/public/app.js +3450 -540
  8. package/public/assets/abduction-cat-blink.png +0 -0
  9. package/public/assets/abduction-cat-control-left.png +0 -0
  10. package/public/assets/abduction-cat-control-right.png +0 -0
  11. package/public/assets/abduction-cat.png +0 -0
  12. package/public/es.js +354 -54
  13. package/public/index.html +65 -19
  14. package/public/routing.js +74 -0
  15. package/public/styles.css +788 -77
  16. package/src/adapters/claude.mjs +6 -2
  17. package/src/adapters/gemini.mjs +26 -5
  18. package/src/adapters/madre.mjs +5 -0
  19. package/src/choices.mjs +64 -0
  20. package/src/dataset.mjs +11 -6
  21. package/src/distiller.mjs +12 -1
  22. package/src/file-ops.mjs +45 -0
  23. package/src/lease.mjs +37 -2
  24. package/src/mcp/smtp-server.mjs +189 -0
  25. package/src/memory-dashboard.mjs +69 -0
  26. package/src/memory.mjs +222 -18
  27. package/src/modules/ahp.mjs +1 -1
  28. package/src/modules/ash-policy.mjs +67 -0
  29. package/src/modules/ash.mjs +1 -1
  30. package/src/modules/choices.mjs +34 -0
  31. package/src/modules/correo.mjs +107 -0
  32. package/src/modules/index.mjs +48 -4
  33. package/src/modules/playwright.mjs +5 -1
  34. package/src/modules/ripley.mjs +13 -1
  35. package/src/modules/sdk.mjs +44 -9
  36. package/src/outbound.mjs +37 -3
  37. package/src/ports.mjs +91 -0
  38. package/src/request-guard.mjs +26 -0
  39. package/src/room/archivist.mjs +2 -1
  40. package/src/room/economy.mjs +83 -1
  41. package/src/room/prompt.mjs +14 -2
  42. package/src/room.mjs +150 -18
  43. package/src/sentinel-errors.mjs +5 -1
  44. package/src/server.mjs +188 -8
  45. package/src/smtp.mjs +122 -0
  46. package/src/vault.mjs +100 -0
  47. package/src/whats-new.mjs +140 -0
package/public/styles.css CHANGED
@@ -29,7 +29,7 @@
29
29
  --user-ink: var(--phosphor-ink);
30
30
 
31
31
  --danger: #ff7b72;
32
- --warn: #ffd166;
32
+ --warn: #ffd166; --warn-ink: #1a1403;
33
33
  --crit: #ff8a7a;
34
34
  --terror: #ff2a1f; /* MOTHER, when she stops being polite */
35
35
  --shadow: 0 1px 0 rgba(255,255,255,.03) inset, 0 12px 40px rgba(0,0,0,.45);
@@ -70,15 +70,15 @@
70
70
  --line-2: rgba(0, 0, 0, .13);
71
71
  --text: #121512;
72
72
  --text-2: #5a615a;
73
- --text-3: #8b928a;
73
+ --text-3: #767d75;
74
74
  --phosphor: #2f8a1c;
75
75
  --brew: #178f0c;
76
76
  --brew-ink: #ffffff;
77
77
  --phosphor-ink: #ffffff;
78
- --bubble: #e9ebe6;
78
+ --bubble: #dde0d7;
79
79
  --bubble-ink: #121512;
80
80
  --danger: #b3261e;
81
- --warn: #8a6a00;
81
+ --warn: #8a6a00; --warn-ink: #ffffff;
82
82
  --crit: #a1301f;
83
83
  --shadow: 0 12px 40px rgba(0,0,0,.08);
84
84
  --mode-0: #6f786e; --mode-0-ink: #ffffff;
@@ -103,15 +103,15 @@
103
103
  --line-2: rgba(0, 0, 0, .13);
104
104
  --text: #121512;
105
105
  --text-2: #5a615a;
106
- --text-3: #8b928a;
106
+ --text-3: #767d75;
107
107
  --phosphor: #2f8a1c;
108
108
  --brew: #178f0c;
109
109
  --brew-ink: #ffffff;
110
110
  --phosphor-ink: #ffffff;
111
- --bubble: #e9ebe6;
111
+ --bubble: #dde0d7;
112
112
  --bubble-ink: #121512;
113
113
  --danger: #b3261e;
114
- --warn: #8a6a00;
114
+ --warn: #8a6a00; --warn-ink: #ffffff;
115
115
  --crit: #a1301f;
116
116
  --shadow: 0 12px 40px rgba(0,0,0,.08);
117
117
  }
@@ -140,13 +140,13 @@ body {
140
140
  .bar, .onboarding, .composer, .safety, .thread-inner { width: var(--column); margin-left: auto; margin-right: auto; }
141
141
  /* The bar is the ship's console: wider than the thread so the project root, the crew and the alerts share one line. */
142
142
  .shell > .bar { width: min(1280px, 100% - 40px); }
143
- .shell > .bar { grid-row: 1; } .shell > .onboarding { grid-row: 2; } .shell > .thread { grid-row: 3; } .shell > .composer { grid-row: 4; } .shell > .safety { grid-row: 5; }
143
+ .shell > .bar { grid-row: 1; } .shell > .onboarding { grid-row: 2; } .shell > .thread { grid-row: 3; } .shell > .composer { grid-row: 4; } .shell > .safety { max-width: min(100%, 150ch); margin-inline: auto; grid-row: 5; }
144
144
  .shell > .bar, .shell > .onboarding, .shell > .thread, .shell > .composer, .shell > .safety, .shell > .tree, .shell > .chats-button { grid-column: 1; }
145
145
  .visually-hidden { position: absolute !important; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
146
146
 
147
147
  /* ================= bar ================= */
148
148
  .bar {
149
- display: flex; align-items: center; gap: 18px;
149
+ display: flex; align-items: center; gap: 18px; min-width: 0;
150
150
  padding: 18px 0 14px;
151
151
  border-bottom: 1px solid var(--line);
152
152
  font-family: var(--mono); font-size: 11px; letter-spacing: .04em;
@@ -189,7 +189,10 @@ body {
189
189
  .train-steps li.note { list-style: none; margin-left: -18px; }
190
190
  .train-step-label { font-family: var(--mono); font-size: 9px; letter-spacing: .16em; text-transform: uppercase; color: var(--text-3); }
191
191
  .project::before { content: "/ "; }
192
- .agents { display: flex; gap: 8px; margin-left: 0; flex: none; }
192
+ /* 3px of ring on each side plus air between them: anything under 10px and the neighbours clip
193
+ each other's arc. The padding is the same 3px, so the row's box contains the rings it draws —
194
+ without it the first and last ring hang outside and collide with whatever sits beside them. */
195
+ .agents { display: flex; gap: 12px; padding: 0 3px; margin-left: 0; flex: none; }
193
196
  .connection { display: inline-flex; flex: 0 0 auto; white-space: nowrap; align-items: center; gap: 6px; color: var(--text-3); text-transform: uppercase; font-size: 10px; }
194
197
  .connection-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--text-3); }
195
198
  .connection[data-state="live"] .connection-dot { background: var(--phosphor); box-shadow: 0 0 10px var(--phosphor); }
@@ -201,16 +204,22 @@ body {
201
204
  --size: 28px; --pct: 0;
202
205
  position: relative; display: inline-grid; place-items: center;
203
206
  width: var(--size); height: var(--size); border-radius: 50%;
204
- color: var(--agent); background: color-mix(in srgb, var(--agent) 14%, transparent);
207
+ color: var(--agent); background: color-mix(in srgb, var(--agent) 17%, transparent);
205
208
  flex: none;
206
209
  }
207
210
  .avatar svg { width: 58%; height: 58%; display: block; }
211
+ /* The ring is the agent's quota, drawn around it. Two things were wrong with it: it sits 3px
212
+ outside the avatar's own box, so with a 6px gap the rings of neighbours overlapped and each
213
+ arc came out flattened where the next one cut into it — the "cut bubbles" on screen. And the
214
+ unused part of the arc was drawn in --line-2, a full-strength grey that outweighed the colour
215
+ it was supposed to frame, so every agent read as a grey circle with a tint inside.
216
+
217
+ Now the track is a whisper and the used arc is the only thing with weight. */
208
218
  .avatar.ring::before {
209
219
  content: ""; position: absolute; inset: -3px; border-radius: 50%;
210
- background: conic-gradient(var(--agent) calc(var(--pct) * 1%), var(--line-2) 0);
211
- -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 2px), #000 calc(100% - 1.5px));
212
- mask: radial-gradient(farthest-side, transparent calc(100% - 2px), #000 calc(100% - 1.5px));
213
- opacity: .9;
220
+ background: conic-gradient(var(--agent) calc(var(--pct) * 1%), color-mix(in srgb, var(--text-3) 22%, transparent) 0);
221
+ -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 2.5px), #000 calc(100% - 2px));
222
+ mask: radial-gradient(farthest-side, transparent calc(100% - 2.5px), #000 calc(100% - 2px));
214
223
  }
215
224
  .avatar.ring.hot::before { background: conic-gradient(var(--crit) calc(var(--pct) * 1%), var(--line-2) 0); }
216
225
  .avatar.offline { color: var(--text-3); background: transparent; border: 1px dashed var(--line-2); }
@@ -219,6 +228,22 @@ body {
219
228
  .avatar.gradient { background: var(--agent-gradient, var(--agent)); color: #fff; }
220
229
  .agents .avatar { cursor: default; }
221
230
 
231
+ /* The spheres, in light mode. A tint of the agent's colour works on a near-black page because the
232
+ colour is the only light in the circle; over a near-white page the same mix just walks back to
233
+ the page. Measured: at 17% over #f4f5f2 the brightest agents came out at 1.06:1 and 1.03:1 —
234
+ the disc was not faint, it was absent. Raising the percentage does not fix it either, because
235
+ mixing a bright yellow into white stays white (40% only reached 1.17:1).
236
+
237
+ So on light the disc keeps its tint and gains an edge and a glyph in the agent's colour taken
238
+ DOWN instead of up. One colour, two directions, and every agent reads at its own hue. */
239
+ :root:not([data-theme="dark"]) .avatar:not(.gradient):not(.offline):not(.detected),
240
+ :root[data-theme="light"] .avatar:not(.gradient):not(.offline):not(.detected) {
241
+ --agent-ink: color-mix(in srgb, var(--agent) 58%, #11210f);
242
+ background: color-mix(in srgb, var(--agent) 22%, transparent);
243
+ box-shadow: inset 0 0 0 1.5px var(--agent-ink);
244
+ color: var(--agent-ink);
245
+ }
246
+
222
247
  /* ================= thread ================= */
223
248
  .thread {
224
249
  overflow-y: auto;
@@ -304,19 +329,106 @@ code { font-family: var(--mono); font-size: .9em; color: var(--phosphor); backgr
304
329
  .row.thinking .tokens { font-family: var(--mono); font-size: 10px; letter-spacing: .08em; color: var(--text-3); margin-left: 8px; opacity: .75; }
305
330
  /* What a reply cost, on the same line as everything else above the bubble: no pill, no wrap. */
306
331
  .who .spent { flex: none; margin-left: auto; padding: 0; border: 0; background: none; font-family: var(--mono); font-size: 10px; letter-spacing: .04em; color: var(--text-3); white-space: nowrap; }
332
+ .who .spent.ash { color: var(--brew); }
333
+ .stamp .uncharged { color: var(--brew); }
334
+ .ash-observed { margin-top: 12px; }
335
+ .economy-headline { font-family: var(--mono); font-size: clamp(38px, 5vw, 56px); line-height: 1.1; letter-spacing: -.01em; color: var(--ph); margin: 2px 0 12px; }
336
+ /* The panel is a screen, not a column. Sections pair up once there is room for two, and the
337
+ ones that carry a table or a full-width strip keep the whole width to themselves. */
338
+ #economy-body { display: grid; grid-template-columns: 1fr; gap: 0 56px; align-content: start; }
339
+ @media (min-width: 1180px) { #economy-body { grid-template-columns: 1fr 1fr; gap: 16px; } #economy-body > .wide, #economy-body > .economy-strip { grid-column: 1 / -1; } }
340
+ .economy .card-block { display: grid; gap: 10px; align-content: start; }
341
+ .economy .card-block {
342
+ margin: 0; padding: 20px 22px 22px;
343
+ border: 1px solid var(--ph-faint); border-radius: 12px;
344
+ background: color-mix(in srgb, var(--ph) 3%, transparent);
345
+ }
346
+ .economy .card-block:first-of-type { margin: 0; }
347
+ .economy .card-head { display: flex; align-items: center; gap: 8px; margin: 0 0 4px; }
348
+ .card-icon { width: 12px; height: 12px; opacity: .85; }
349
+ /* The unit, said once per card instead of stamped on every row. */
350
+ .economy .unit-note { margin: -2px 0 6px; font-family: var(--mono); font-size: 9px; letter-spacing: .14em; text-transform: uppercase; color: var(--ph-dim); }
351
+
352
+ /* The room in six figures, read across before anything is read down. */
353
+ .economy-strip { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; margin: 0 0 4px; padding: 0; border: 0; }
354
+ .economy-stat {
355
+ display: grid; gap: 5px; align-content: start; min-width: 0;
356
+ padding: 14px 16px; border: 1px solid var(--ph-faint); border-radius: 10px;
357
+ background: color-mix(in srgb, var(--ph) 4%, transparent);
358
+ }
359
+ .economy-stat b { font-family: var(--mono); font-size: 22px; font-weight: 600; line-height: 1.1; color: var(--ph); }
360
+ .economy-stat span { font-family: var(--mono); font-size: 8.5px; letter-spacing: .18em; color: var(--ph-dim); }
361
+
362
+ /* A table is read by looking one row up, so the grid stays recessive and the numbers line up. */
363
+ .economy-table { width: 100%; border-collapse: collapse; font-family: var(--mono); font-size: 11px; }
364
+ .economy-table th, .economy-table td { padding: 7px 10px; text-align: right; border-bottom: 1px solid var(--ph-faint); white-space: nowrap; }
365
+ .economy-table thead th { font-size: 8.5px; font-weight: 400; letter-spacing: .18em; color: var(--ph-dim); border-bottom-color: var(--ph-dim); }
366
+ .economy-table tbody th { text-align: left; font-weight: 600; letter-spacing: .14em; color: var(--ph-dim); }
367
+ .economy-table td { color: var(--ph); }
368
+ .economy-table tbody tr:last-child th, .economy-table tbody tr:last-child td { border-bottom: 0; }
369
+ .economy .card-block h5 { margin: 0; font-size: 10px; font-weight: 400; letter-spacing: .28em; color: var(--ph-dim); }
370
+ .economy .metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 230px)); justify-content: start; gap: 24px 40px; }
371
+ .economy .metric { display: grid; gap: 6px; align-content: start; min-width: 0; }
372
+ .economy .metric b { font-family: var(--mono); font-size: 26px; font-weight: 600; line-height: 1.1; color: var(--ph); }
373
+ .economy .metric span { font-family: var(--mono); font-size: 9px; line-height: 1.5; letter-spacing: .18em; color: var(--ph-dim); }
374
+ /* Prose is read, so it gets a measure and a size: a 60-character column, sentence case, never the
375
+ full width of a panel at heading scale. */
376
+ .economy .note { margin: 8px 0 0; max-width: 640px; font-size: 11px; line-height: 1.85; letter-spacing: .06em; color: var(--ph-dim); text-shadow: none; }
377
+ .economy .ash-block { grid-template-columns: 120px minmax(80px, 560px) auto; gap: 16px; }
378
+ .economy .ash-block b, .economy .ash-block span { font-size: 10px; }
379
+ .economy .ash-block i { height: 7px; }
380
+
381
+ /* One agent, two bars, one scale: the shorter bar is the shorter answer. */
382
+ .ash-compare { display: grid; gap: 20px; }
383
+ .ash-agent { display: grid; gap: 6px; }
384
+ .ash-agent > b { font-family: var(--mono); font-size: 10px; font-weight: 600; letter-spacing: .2em; color: var(--ph-dim); text-shadow: none; }
385
+ .ash-bar { display: grid; grid-template-columns: 76px minmax(80px, 560px) auto; gap: 14px; align-items: center; }
386
+ .ash-bar .ash-side { font-family: var(--mono); font-size: 10px; letter-spacing: .12em; color: var(--ph-dim); text-shadow: none; }
387
+ .ash-bar .ash-figure { font-family: var(--mono); font-size: 10px; letter-spacing: .08em; color: var(--ph-dim); white-space: nowrap; text-shadow: none; }
388
+ .ash-bar i { height: 9px; background: var(--ph-faint); position: relative; }
389
+ .ash-bar i::after { content: ''; position: absolute; inset: 0 auto 0 0; width: var(--fill, 0%); background: color-mix(in srgb, var(--ph) 30%, transparent); }
390
+ .ash-bar.on i::after { background: var(--ph); }
391
+ .ash-bar.on .ash-figure, .ash-bar.on .ash-side { color: var(--ph); }
392
+ .ash-verdict { margin: 4px 0 0; font-family: var(--mono); font-size: 10px; line-height: 1.5; letter-spacing: .06em; color: var(--ph); text-shadow: none; }
393
+ .ash-verdict.noise { color: var(--ph-dim); }
394
+ .ash-waiting { margin: 0; font-family: var(--mono); font-size: 10px; letter-spacing: .08em; color: var(--ph-dim); text-shadow: none; }
395
+ @media (max-width: 720px) { .ash-bar { grid-template-columns: 1fr; } .ash-bar i { display: none; } }
396
+ .ash-observed h5 { margin: 0 0 6px; }
307
397
  .row.thinking .elapsed.late { color: var(--warn); }
308
398
  .row.thinking i { width: 7px; height: 7px; border-radius: 50%; background: var(--agent, var(--text-2)); animation: bounce 1.2s infinite; opacity: .5; }
309
399
  .row.thinking i:nth-child(2) { animation-delay: .18s; } .row.thinking i:nth-child(3) { animation-delay: .36s; }
310
400
  @keyframes bounce { 0%, 80%, 100% { transform: translateY(0); opacity: .35; } 40% { transform: translateY(-4px); opacity: 1; } }
311
401
 
312
402
  /* system lines: handoffs, sentinel, failures — MOTHER speaks in small caps */
403
+ /* A line of the room talking about itself. Three things keep it orderly:
404
+
405
+ · a measure. Running the full column turns one sentence into a ragged staircase, and these are
406
+ read at a glance or not at all.
407
+ · balanced wrapping, so two lines come out the same length instead of a long one and a word.
408
+ · the label on its own line. CONTROL, MEMORY, MODULES name what follows; leaving them inline
409
+ let the break land anywhere and the eye lost which part was the heading. */
313
410
  .system {
314
- align-self: center; text-align: center; max-width: 92%;
315
- overflow-wrap: anywhere;
411
+ /* A definite width, not shrink-to-fit. Sized to its content, the box is as wide as its widest
412
+ line, so the heading centred in one box and the body wrapped inside another — which is why
413
+ the agent's name never lined up with the sentence under it. */
414
+ align-self: center; text-align: center; width: min(92%, 58ch);
415
+ /* `pretty`, not `balance`: balance evens the line lengths and in doing so kept pushing the
416
+ agent's name onto a line of its own, away from the sentence it begins. `pretty` only avoids
417
+ orphans, which is the actual problem these rows had. */
418
+ overflow-wrap: anywhere; text-wrap: pretty;
316
419
  margin: 22px 0 8px;
317
420
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase;
318
- color: var(--text-3); line-height: 1.6;
421
+ color: var(--text-3); line-height: 1.7;
319
422
  }
423
+ .system > b:first-child:not(.inline) { display: block; margin-bottom: 3px; color: var(--text-3); letter-spacing: .2em; }
424
+ /* Everything after the heading is one sentence and has to wrap as one: a code chip that becomes
425
+ a block breaks the line in front of it and leaves the agent's name stranded. */
426
+ .system code { display: inline-block; vertical-align: baseline; }
427
+ /* `.who` belongs to a message's header, where it is a flex row with a left margin. Inside a
428
+ system line it is a word in a sentence, and inheriting that rule is what put every agent's
429
+ name alone on a line, indented, away from the clause it opens. */
430
+ .system .who { display: inline; margin: 0; white-space: nowrap; }
431
+ .system .who { color: var(--agent, var(--text-2)); }
320
432
  .system b { font-weight: 600; color: var(--text-2); }
321
433
  .system .from { color: var(--from, var(--text-2)); } .system .to { color: var(--to, var(--text-2)); }
322
434
  .system.warn { color: var(--warn); }
@@ -565,11 +677,31 @@ pre.core-egress-log { margin: 0; max-height: 34vh; overflow: auto; padding: 10px
565
677
  .memory-hint .more { font-family: var(--mono); font-size: 9.5px; color: var(--text-3); }
566
678
  /* Memory used is not memory saved: saving is an event, using happens on most turns. Same shape so
567
679
  there is one thing to learn, quieter so it never competes with the answer it helped write. */
568
- .memory-hint.used { margin-top: 4px; opacity: .78; animation: none; }
569
- .memory-hint.used:hover { opacity: 1; }
570
- .memory-hint.used .lead { color: var(--text-3); }
571
- .memory-hint.used .pill { animation: none; box-shadow: none; font-size: 10.5px; padding: 2px 9px; }
572
- .memory-hint.used .pill.carried b::after { content: ' ⇢'; }
680
+ /* What the archive handed this turn: one indicator per line under its heading.
681
+ Legibility first — opacity on the whole group used to fade text that was already pale, and the
682
+ five kind colours belong to NOSTROMO's dark map, so here the colour is a dot and the text is
683
+ the room's own. */
684
+ .memory-hint.used { flex-direction: column; align-items: flex-start; margin-top: 4px; gap: 5px; animation: none; }
685
+ .memory-hint.used .lead { font-size: 9px; letter-spacing: .14em; color: var(--text-3); }
686
+ .memory-hint.used .pill {
687
+ gap: 7px; max-width: min(340px, 100%); padding: 3px 10px 3px 8px;
688
+ border: 1px solid color-mix(in srgb, var(--text-3) 26%, transparent);
689
+ background: color-mix(in srgb, var(--text-3) 7%, transparent);
690
+ color: var(--text-2); font-size: 11px; line-height: 1.45;
691
+ animation: none; box-shadow: none;
692
+ }
693
+ .memory-hint.used .pill .dot {
694
+ flex: none; width: 7px; height: 7px; border-radius: 999px; background: var(--kind);
695
+ /* The ring is what keeps a near-white kind visible on a near-white page. */
696
+ box-shadow: 0 0 0 1px color-mix(in srgb, var(--text-3) 45%, transparent);
697
+ }
698
+ .memory-hint.used .pill .what { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
699
+ .memory-hint.used .pill:hover { background: color-mix(in srgb, var(--text-3) 14%, transparent); color: var(--text); border-color: color-mix(in srgb, var(--text-3) 42%, transparent); }
700
+ /* Carried along rather than matched: the dot is hollow, the way an indicator says "inherited". */
701
+ .memory-hint.used .pill.carried .dot { background: transparent; box-shadow: inset 0 0 0 2px var(--kind), 0 0 0 1px color-mix(in srgb, var(--text-3) 45%, transparent); }
702
+ .memory-hint.used .last { display: flex; align-items: center; gap: 6px; max-width: 100%; }
703
+ .memory-hint.used .last .pill { min-width: 0; }
704
+ .memory-hint.used .more { font-size: 9.5px; }
573
705
  @keyframes hint-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
574
706
  @keyframes hint-glow { 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--kind) 55%, transparent); } 40% { box-shadow: 0 0 0 6px color-mix(in srgb, var(--kind) 0%, transparent), 0 0 18px color-mix(in srgb, var(--kind) 45%, transparent); } 100% { box-shadow: 0 0 0 0 transparent; } }
575
707
  @media (prefers-reduced-motion: reduce) { .memory-hint, .memory-hint .pill { animation: none; } }
@@ -625,7 +757,15 @@ textarea::placeholder { color: var(--text-3); }
625
757
  .field button[type="submit"]:hover { filter: brightness(1.06); }
626
758
  .field button[type="submit"]:disabled { opacity: .4; cursor: default; box-shadow: none; }
627
759
  .pick-label .pick-note { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; font-family: var(--mono); font-size: 9px; letter-spacing: .14em; text-transform: uppercase; color: var(--text-3); white-space: nowrap; }
628
- .safety { margin: 14px auto 18px; color: var(--text-3); font-family: var(--mono); font-size: 9.5px; letter-spacing: .08em; text-transform: uppercase; text-align: center; }
760
+ .safety { max-width: min(100%, 150ch); margin: 8px auto 4px; color: var(--text-2); font-family: var(--mono); font-size: 8.5px; line-height: 1.5; letter-spacing: .06em; text-transform: uppercase; text-align: center; opacity: .9; }
761
+ /* The signature, in the corner it belongs in: last in the page's order, and out of the way of
762
+ everything that is actually used. */
763
+ .colophon { position: fixed; right: 14px; bottom: 10px; z-index: 2; font-family: var(--mono); font-size: 8.5px; letter-spacing: .1em; color: var(--brew); opacity: .68; pointer-events: none; user-select: none; transition: opacity .2s; }
764
+ .colophon:hover { opacity: 1; }
765
+ .colophon-back { pointer-events: auto; color: inherit; text-decoration: none; border-bottom: 1px solid currentColor; padding-bottom: 1px; }
766
+ .colophon-back:hover { color: var(--brew); opacity: 1; }
767
+ @media (max-width: 720px) { .colophon { display: none; } }
768
+ .shell.with-browser .colophon { display: none; }
629
769
 
630
770
  /* toast */
631
771
  .toast { position: fixed; left: var(--composer-center, 50%); bottom: calc(100vh - var(--composer-top, 70vh) + 22px); transform: translateX(-50%); text-align: center; padding: 9px 14px; animation: toast-in .22s ease; z-index: 40; border-radius: 12px; border: 1px solid color-mix(in srgb, var(--danger) 40%, transparent); background: var(--panel); color: var(--danger); font-size: 13px; box-shadow: var(--shadow); max-width: min(560px, calc(100% - 32px)); }
@@ -667,7 +807,9 @@ textarea::placeholder { color: var(--text-3); }
667
807
 
668
808
  .lang-button { font-family: var(--mono); font-size: 9.5px; font-weight: 600; letter-spacing: .16em; padding: 0 9px; }
669
809
  .mother-glyph { width: 8px; height: 8px; border: 1px solid currentColor; }
670
- .mother-count { min-width: 16px; height: 16px; padding: 0 4px; border-radius: 8px; display: inline-grid; place-items: center; background: var(--danger); color: #fff; font-size: 9px; letter-spacing: 0; }
810
+ /* Every count in the bar wears the same green. Red on a counter reads as an alarm, and the
811
+ number of conditions MU/TH/UR is holding is not one. */
812
+ .mother-count { min-width: 16px; height: 16px; padding: 0 4px; border-radius: 8px; display: inline-grid; place-items: center; background: var(--phosphor); color: var(--phosphor-ink); font-size: 9px; letter-spacing: 0; }
671
813
 
672
814
  dialog.mother { border: 0; padding: 0; margin: 0; width: 100vw; max-width: 100vw; height: 100dvh; max-height: 100dvh; background: transparent; color: var(--brew); }
673
815
  dialog.mother::backdrop { background: rgba(0, 0, 0, .82); backdrop-filter: blur(6px); }
@@ -704,20 +846,35 @@ dialog.mother::backdrop { background: rgba(0, 0, 0, .82); backdrop-filter: blur(
704
846
  .mother-section { margin-top: 34px; }
705
847
  .mother-section h3:not(:first-child) { margin-top: 40px; padding-top: 22px; border-top: 1px solid var(--ph-faint); }
706
848
  .mother-section h3.toggle:not(:first-child) { margin-top: 40px; }
707
- .settings .room-form, .settings .conn-grid, .settings .mother-grid { margin-bottom: 8px; }
849
+ :is(.settings, .memory-panel) .room-form, .settings .conn-grid, .settings .mother-grid { margin-bottom: 8px; }
708
850
  .mother-section h3::before { content: "▌ "; color: var(--ph); }
851
+ /* A folding block carries its own glyph, so the generic bar would be a second mark saying the
852
+ same thing. And the rule above it goes with it: a line and an icon are two ways of announcing
853
+ «a new block starts here», and one of them is enough. */
854
+ .mother-section .fold > summary h3::before { content: none; }
855
+ .mother-section .fold > summary h3 { font-size: 11px; letter-spacing: .22em; color: var(--ph); }
856
+ .mother-section .fold[open] > summary h3 { color: var(--ph); }
857
+ /* No frame around it: a badge was a second box saying what the drawing already says. Crisp edges
858
+ because a pixel that gets antialiased into a curve stops being a pixel. */
859
+ .pixel-icon { flex: none; display: block; line-height: 0; color: var(--ph); }
860
+ .pixel-icon svg { display: block; width: 100%; height: 100%; shape-rendering: crispEdges; }
861
+ .fold-icon { width: 13px; height: 13px; opacity: .8; transition: opacity .15s; }
862
+ .mother-section .fold > summary:hover .fold-icon { opacity: 1; }
863
+ .mother-section .fold { padding-top: 26px; margin-top: 10px; border-top: 1px solid var(--ph-faint); }
864
+ .mother-section .fold:first-of-type { border-top: 0; padding-top: 0; }
709
865
 
710
866
  /* A section that folds. The summary carries the same header it always had, plus a mark on the
711
867
  right when there is something behind it worth opening. */
712
868
  .mother-section .fold { margin: 0; }
713
869
  .mother-section .fold + .fold { margin-top: 26px; }
714
- .mother-section .fold > summary { display: flex; align-items: baseline; gap: 10px; cursor: pointer; list-style: none; }
870
+ .mother-section .fold > summary { display: flex; align-items: center; gap: 9px; cursor: pointer; list-style: none; }
715
871
  .mother-section .fold > summary::-webkit-details-marker { display: none; }
716
872
  /* The button is a word, not a glyph: the panel already said EXPAND and COLLAPSE elsewhere. */
717
873
  .mother-section .fold > summary .caret { margin-left: auto; font-family: var(--mono); font-size: 9px; letter-spacing: .18em; color: var(--ph); white-space: nowrap; }
718
874
  .mother-section .fold > summary:hover h3 { color: var(--ph); }
719
- .mother-section .fold > summary h3 { margin: 0 0 12px; }
875
+ .mother-section .fold > summary h3 { margin: 0; }
720
876
  .mother-section .fold > summary h3:not(:first-child) { margin-top: 0; padding-top: 0; border-top: 0; }
877
+ .mother-section .fold > .fold-body { padding-top: 14px; }
721
878
  .fold-badge { font-family: var(--mono); font-size: 9px; letter-spacing: .12em; padding: 2px 7px; border-radius: 999px; border: 1px solid var(--ph-dim); color: var(--ph-dim); white-space: nowrap; }
722
879
  /* Red, and only red, when there is something new: that is the whole message. */
723
880
  .fold-badge.urgent { border-color: var(--danger); background: var(--danger); color: #fff; }
@@ -898,22 +1055,22 @@ dialog.mother::backdrop { background: rgba(0, 0, 0, .82); backdrop-filter: blur(
898
1055
  .conn-card .session.busy { color: #ffd166; border-color: #ffd166; animation: blink 1s infinite; }
899
1056
  .conn-card .meta { font-size: 11px; line-height: 1.6; color: rgba(210, 255, 190, .8); text-shadow: none; }
900
1057
  .conn-card .row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
901
- .conn-card button, .settings .room-form button { border: 1px solid var(--ph-dim); background: transparent; color: var(--ph); font: inherit; font-size: 9.5px; letter-spacing: .16em; padding: 6px 10px; cursor: pointer; }
902
- .conn-card button.primary, .settings .room-form button.primary { background: var(--ph); color: #041004; text-shadow: none; border-color: var(--ph); }
903
- .conn-card button:disabled, .settings .room-form button:disabled { opacity: .4; cursor: default; }
904
- .conn-card label, .settings .room-form label { display: grid; gap: 4px; font-size: 9.5px; letter-spacing: .16em; color: var(--ph-dim); }
1058
+ .conn-card button, :is(.settings, .memory-panel) .room-form button { border: 1px solid var(--ph-dim); background: transparent; color: var(--ph); font: inherit; font-size: 9.5px; letter-spacing: .16em; padding: 6px 10px; cursor: pointer; }
1059
+ .conn-card button.primary, :is(.settings, .memory-panel) .room-form button.primary { background: var(--ph); color: #041004; text-shadow: none; border-color: var(--ph); }
1060
+ .conn-card button:disabled, :is(.settings, .memory-panel) .room-form button:disabled { opacity: .4; cursor: default; }
1061
+ .conn-card label, :is(.settings, .memory-panel) .room-form label { display: grid; gap: 4px; font-size: 9.5px; letter-spacing: .16em; color: var(--ph-dim); }
905
1062
  /* A textarea is a field like any other here. It was left out of this rule, so what you typed
906
1063
  into it was drawn in the page's own colour: invisible on this ground. */
907
- .conn-card input, .conn-card select, .settings .room-form input, .settings .room-form select, .settings .room-form textarea { border: 1px solid var(--ph-dim); background: #000; color: var(--ph); font: inherit; font-size: 12px; padding: 6px 8px; letter-spacing: .04em; min-width: 0; }
908
- .settings .room-form textarea::placeholder { color: var(--ph-dim); }
909
- .conn-card input:focus, .settings .room-form input:focus, .settings .room-form textarea:focus { outline: 1px solid var(--ph); }
1064
+ .conn-card input, .conn-card select, :is(.settings, .memory-panel) .room-form input, :is(.settings, .memory-panel) .room-form select, :is(.settings, .memory-panel) .room-form textarea { border: 1px solid var(--ph-dim); background: #000; color: var(--ph); font: inherit; font-size: 12px; padding: 6px 8px; letter-spacing: .04em; min-width: 0; }
1065
+ :is(.settings, .memory-panel) .room-form textarea::placeholder { color: var(--ph-dim); }
1066
+ .conn-card input:focus, :is(.settings, .memory-panel) .room-form input:focus, :is(.settings, .memory-panel) .room-form textarea:focus { outline: 1px solid var(--ph); }
910
1067
  .conn-card .login-log { max-height: 140px; overflow-y: auto; border: 1px solid var(--ph-faint); background: #000; padding: 8px 10px; font-size: 11px; line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; }
911
1068
  .conn-card .login-log a { color: var(--ph); }
912
- .settings .room-form { border: 1px solid var(--ph-dim); padding: 14px 16px; display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); align-items: end; }
913
- .settings .room-form .full { grid-column: 1 / -1; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
914
- .settings .room-form .toggle { display: flex; align-items: center; gap: 8px; font-size: 10px; letter-spacing: .16em; color: var(--ph); cursor: pointer; }
915
- .settings .room-form .toggle input { width: auto; accent-color: var(--brew); }
916
- .settings .note { font-size: 10px; letter-spacing: .1em; color: var(--ph-dim); }
1069
+ :is(.settings, .memory-panel) .room-form { border: 1px solid var(--ph-dim); padding: 14px 16px; display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); align-items: end; }
1070
+ :is(.settings, .memory-panel) .room-form .full { grid-column: 1 / -1; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
1071
+ :is(.settings, .memory-panel) .room-form .toggle { display: flex; align-items: center; gap: 8px; font-size: 10px; letter-spacing: .16em; color: var(--ph); cursor: pointer; }
1072
+ :is(.settings, .memory-panel) .room-form .toggle input { width: auto; accent-color: var(--brew); }
1073
+ :is(.settings, .memory-panel) .note { font-size: 10px; letter-spacing: .1em; color: var(--ph-dim); }
917
1074
  .system.login { color: var(--phosphor); }
918
1075
  .system.login.failed { color: var(--danger); }
919
1076
 
@@ -1047,6 +1204,160 @@ dialog.override[open] { display: grid; place-items: center; }
1047
1204
  .bubble img.inline { max-width: 100%; max-height: 420px; border-radius: 12px; display: block; margin: 6px 0; cursor: zoom-in; }
1048
1205
  .bubble a.file-link { color: var(--agent, var(--phosphor)); text-decoration: none; border-bottom: 1px dashed currentColor; font-family: var(--mono); font-size: .92em; cursor: pointer; }
1049
1206
  dialog.viewer { border: 0; padding: 0; width: min(1100px, 96vw); max-width: 96vw; height: min(88vh, 900px); background: var(--panel); color: var(--text); border-radius: 16px; box-shadow: var(--shadow); }
1207
+ /* With the browser docked the conversation lives in half a screen, and a bar built for a wide one
1208
+ piles its pieces on top of each other.
1209
+
1210
+ The breakpoints are the BAR'S OWN WIDTH, not the window's. That distinction is the whole fix:
1211
+ the window stays 2000px wide while the column shrinks to 500, so every @media rule written
1212
+ against the viewport sat there never firing while the pieces overlapped. A container query asks
1213
+ the element how much room IT has, which is the only question that was ever relevant.
1214
+
1215
+ And a flex child with min-width:auto refuses to shrink below its content and pushes its
1216
+ neighbour out of the row instead. That is what put MADRE on top of the agent dots. */
1217
+ .bar { container-type: inline-size; container-name: bar; }
1218
+ /* Only what can truncate is allowed to shrink. A button given min-width:0 shrinks below its own
1219
+ label and lets it spill past the border onto the next button — which looks exactly like two
1220
+ controls overlapping, because they are. Buttons hold their size and lose their words instead,
1221
+ at the widths below. */
1222
+ .bar > * { flex: none; }
1223
+ /* Everything but the names holds its size. The names are the only thing in the bar that can lose
1224
+ characters without losing meaning, so they are the only thing allowed to give — and the group
1225
+ that holds them has to be allowed to give too, or its children never get the chance. */
1226
+ .bar > .bar-left { flex: 0 1 auto; min-width: 0; overflow: hidden; }
1227
+ /* The project's name holds; the room's title is the one that gives. A project is where you are
1228
+ and there is one of them; a room title is a sentence somebody typed and there are seven. */
1229
+ .bar .project { flex: none; white-space: nowrap; }
1230
+ .bar .chat-here { min-width: 0; flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1231
+ /* No overflow:hidden here. The quota ring is drawn 3px outside each avatar's own box, so hiding
1232
+ the overflow of the row that holds them is exactly what shaved a flat edge off every sphere. */
1233
+ .bar .agents { flex-wrap: nowrap; }
1234
+
1235
+ /* The order of loss. Nothing disappears: everything that had a word keeps its glyph, so the bar
1236
+ stops being a row of labels and becomes a row of buttons. What a glyph cannot carry — the name
1237
+ of the project, the name of the room — is what falls last.
1238
+
1239
+ The crew is the exception that needs markup: five spheres cannot be compacted by CSS into one
1240
+ button, so there are two elements and the container query picks which one is on screen. */
1241
+ .bar-icon { display: inline-flex; align-items: center; justify-content: center; gap: 5px; width: auto; padding: 0 8px 0 6px; height: 28px; padding: 0; border-radius: 8px; border: 1px solid var(--line-2); background: transparent; color: var(--text-2); cursor: pointer; }
1242
+ .bar-icon svg { width: 17px; height: 17px; }
1243
+ .bar-icon:hover { color: var(--phosphor); border-color: var(--phosphor); }
1244
+ .bar-count { min-width: 16px; height: 16px; padding: 0 4px; border-radius: 8px; display: inline-grid; place-items: center; background: var(--phosphor); color: var(--phosphor-ink); font-family: var(--mono); font-size: 9px; letter-spacing: 0; }
1245
+ .bar-glyph { display: none; }
1246
+ .agents-compact { display: none; }
1247
+
1248
+ @container bar (max-width: 1040px) {
1249
+ .bar { gap: 9px; }
1250
+ /* The dot already says live in colour; the word only repeats it. */
1251
+ .connection-label { display: none; }
1252
+ .brand-word { display: none; }
1253
+ }
1254
+ /* The crew collapses before the names do. Five spheres are 178px; at the width where the bar is
1255
+ tight, giving those back is what buys the project its name — and a name is information a glyph
1256
+ cannot carry, while five spheres say the same thing one button says with a number. */
1257
+ @container bar (max-width: 980px) {
1258
+ /* The crew becomes one button: how many are ready, and the whole list in its title. */
1259
+ .agents { display: none; }
1260
+ .agents-compact { display: inline-flex; }
1261
+ /* A label on a button whose glyph already names it is a label spending room it does not own. */
1262
+ /* The word goes and a real glyph takes its place: a puzzle piece for the modules, a pulse for
1263
+ MU/TH/UR. The decorative mark each button already carried is not an icon — it is a bullet —
1264
+ so leaving only that left two empty outlines on screen. */
1265
+ /* Icon and number side by side, inside the button. Pulling the badge out with position:absolute
1266
+ made it escape 16-23px past the right edge into an 18px gap, which is exactly the collision
1267
+ that was on screen. A count belongs inside the thing it counts. */
1268
+ .mother-button { width: auto; height: 28px; padding: 0 9px 0 7px; gap: 5px; font-size: 0; justify-content: center; }
1269
+ .mother-button .mother-glyph { display: none; }
1270
+ .mother-button .bar-glyph { display: block; width: 17px; height: 17px; }
1271
+ .mother-button .modules-count, .mother-button .mother-count { font-size: 9px; }
1272
+ .stop-all { width: 30px; height: 28px; padding: 0; gap: 0; font-size: 0; justify-content: center; }
1273
+ .stop-all .stop-all-glyph { font-size: 12px; }
1274
+ }
1275
+ @container bar (max-width: 640px) {
1276
+ /* The room's name is the first thing that cannot be drawn, so it becomes the mark that says
1277
+ there is one: three dots, with the whole title still in the tooltip. */
1278
+ .chat-here { font-size: 0; }
1279
+ .chat-here::after { content: '…'; font-size: 11px; letter-spacing: .1em; }
1280
+ }
1281
+ @container bar (max-width: 460px) {
1282
+ .project:not(.chat-here) { font-size: 0; }
1283
+ .project:not(.chat-here)::after { content: '/'; font-size: 11px; }
1284
+ }
1285
+
1286
+ /* The composer has the same disease: its parts have to be allowed to give. */
1287
+ .composer .field { min-width: 0; }
1288
+ .composer .editor { min-width: 0; }
1289
+ /* The field grows with the text to a workable page and then scrolls on its own, so a long brief
1290
+ is written in one piece instead of through a three-line window — and the thread above it never
1291
+ disappears, because what is being written is usually written against it. */
1292
+ .composer .editor textarea { scrollbar-width: thin; }
1293
+ .composer .crew-label { flex: none; }
1294
+
1295
+ /* The browser docks beside the conversation; it does not take the screen from it. The point of
1296
+ looking at a server while a room full of agents works on it is seeing both, so the thread keeps
1297
+ its column and simply narrows. One rule does it: the shell grows a second column, and every
1298
+ existing child is already pinned to the first. */
1299
+ .shell.with-browser { grid-template-columns: minmax(0, 1fr) var(--dock, 46vw); }
1300
+ .shell.with-browser > .browser-dock { grid-column: 2; grid-row: 1 / -1; }
1301
+ /* No height of its own: it already spans every row of the shell, and asking for 100dvh on top of
1302
+ that made the grid satisfy it by starving the rows in the other column — which is how the
1303
+ conversations panel ended up two pixels tall and the thread lost its end. */
1304
+ .browser-dock { position: relative; display: grid; grid-template-rows: auto auto minmax(0, 1fr); min-width: 0; min-height: 0; background: var(--panel); border-left: 1px solid var(--line); }
1305
+ /* The dock is a fixed grid track, so anything inside it that refuses to shrink pushes the whole
1306
+ page wider and hands the human a horizontal scrollbar for a layout that was supposed to fit.
1307
+ Its own container query decides what the header can afford. */
1308
+ .browser-dock { container-type: inline-size; container-name: dock; overflow: hidden; }
1309
+ .browser-dock .viewer-head { padding: 10px 12px; min-width: 0; }
1310
+ .browser-dock .viewer-head > * { flex: none; }
1311
+ .browser-dock .browser-where { flex: 1 1 auto; min-width: 40px; }
1312
+ .browser-dock .viewer-body { height: auto; min-height: 0; }
1313
+ .browser-tabs { min-width: 0; }
1314
+ @container dock (max-width: 620px) {
1315
+ /* The widths are a convenience; the address is not. */
1316
+ .browser-dock .viewer-sizes { display: none; }
1317
+ }
1318
+ @container dock (max-width: 460px) {
1319
+ .browser-dock #browser-close { font-size: 0; padding: 6px 8px; }
1320
+ .browser-dock #browser-close::after { content: '×'; font-size: 13px; }
1321
+ .browser-dock #browser-servers { padding: 0 7px; }
1322
+ .browser-dock #browser-servers > span { display: none; }
1323
+ }
1324
+ /* The edge between the two: wide enough to grab, invisible until wanted. */
1325
+ .dock-grip { position: absolute; left: -3px; top: 0; bottom: 0; width: 8px; cursor: ew-resize; z-index: 5; }
1326
+ .dock-grip::after { content: ''; position: absolute; left: 3px; top: 50%; transform: translateY(-50%); width: 2px; height: 48px; border-radius: 999px; background: var(--line-2); opacity: 0; transition: opacity .15s; }
1327
+ .dock-grip:hover::after, .shell.dragging-dock .dock-grip::after { opacity: 1; background: var(--phosphor); }
1328
+ .shell.dragging-dock .browser-dock iframe { pointer-events: none; }
1329
+ @media (max-width: 900px) { .shell.with-browser { grid-template-columns: 1fr; } .shell.with-browser > .browser-dock { grid-column: 1; grid-row: 3 / 4; height: auto; border-left: 0; border-top: 1px solid var(--line); } }
1330
+
1331
+ /* The browser is its own window on the rail, beside the conversations. */
1332
+
1333
+ .browser-head { flex-wrap: nowrap; }
1334
+ .browser-head .viewer-address { flex: 1; max-width: none; }
1335
+ .browser-tabs { display: flex; align-items: stretch; gap: 4px; padding: 10px 12px; border-top: 1px solid var(--line); overflow-x: auto; scrollbar-width: thin; }
1336
+ .browser-tab { display: inline-flex; align-items: center; gap: 8px; max-width: 220px; padding: 6px 10px; border: 1px solid var(--line-2); border-radius: 8px; background: transparent; color: var(--text-3); font-family: var(--mono); font-size: 11px; cursor: pointer; }
1337
+ .browser-tab .tab-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1338
+ .browser-tab .tab-close { opacity: .45; font-size: 13px; line-height: 1; }
1339
+ .browser-tab .tab-close:hover { opacity: 1; color: var(--danger); }
1340
+ .browser-tab.on { color: var(--phosphor); border-color: var(--phosphor); background: color-mix(in srgb, var(--phosphor) 10%, transparent); }
1341
+ .browser-newtab { flex: none; width: 28px; border: 1px dashed var(--line-2); border-radius: 8px; background: transparent; color: var(--text-3); font-size: 14px; cursor: pointer; }
1342
+ .browser-newtab:hover { color: var(--phosphor); border-color: var(--phosphor); }
1343
+
1344
+ /* Browsing a live server is not reading a file: the page under the glass was built for a window,
1345
+ so it gets one. Full bleed, no rounded corners pretending this is a card, and a stage the frame
1346
+ can be made narrower inside — because checking a layout at phone width is most of what a human
1347
+ opens their own app to do. */
1348
+ dialog.viewer.browsing { width: 100vw; max-width: 100vw; height: 100vh; max-height: 100vh; border-radius: 0; }
1349
+ .viewer-body.browsing { padding: 0; display: grid; grid-template-rows: minmax(0, 1fr); place-items: stretch; background: var(--bg); overflow: hidden; }
1350
+ .viewer-stage { position: relative; display: grid; grid-template-rows: minmax(0, 1fr); justify-content: center; width: 100%; min-height: 0; overflow: hidden; }
1351
+ .viewer-stage iframe.ripley { width: var(--stage, 100%); max-width: 100%; height: 100%; min-height: 0; border-radius: 0; box-shadow: none; }
1352
+ .viewer-stage[data-sized="true"] iframe.ripley { box-shadow: 0 0 0 1px var(--line-2); }
1353
+ /* The edge you pull. Wide enough to grab, invisible until it matters. */
1354
+ .stage-grip { position: absolute; top: 0; bottom: 0; width: 14px; cursor: ew-resize; display: grid; place-items: center; }
1355
+ .stage-grip::after { content: ''; width: 3px; height: 46px; border-radius: 999px; background: var(--line-2); opacity: 0; transition: opacity .15s; }
1356
+ .stage-grip:hover::after, .viewer-stage.dragging .stage-grip::after { opacity: 1; background: var(--phosphor); }
1357
+ .viewer-stage.dragging iframe.ripley { pointer-events: none; }
1358
+ .viewer-sizes { display: inline-flex; gap: 2px; border: 1px solid var(--line-2); border-radius: 999px; padding: 2px; }
1359
+ .viewer-sizes button { border: 0; background: transparent; color: var(--text-3); font-family: var(--mono); font-size: 9.5px; letter-spacing: .08em; padding: 4px 9px; border-radius: 999px; cursor: pointer; }
1360
+ .viewer-sizes button[aria-pressed="true"] { background: color-mix(in srgb, var(--phosphor) 18%, transparent); color: var(--phosphor); }
1050
1361
  dialog.viewer::backdrop { background: rgba(0,0,0,.7); backdrop-filter: blur(4px); }
1051
1362
  .viewer-head { display: flex; align-items: center; gap: 12px; padding: 12px 16px; border-bottom: 1px solid var(--line); font-family: var(--mono); font-size: 11px; }
1052
1363
  .viewer-path { color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
@@ -1355,10 +1666,107 @@ dialog.viewer::backdrop { background: rgba(0,0,0,.7); backdrop-filter: blur(4px)
1355
1666
  .reply-quote .said { min-width: 0; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
1356
1667
  .reply-quote .drop { flex: none; margin-left: auto; width: 18px; height: 18px; display: grid; place-items: center; border: 0; border-radius: 50%; background: transparent; color: var(--text-3); font-size: 14px; line-height: 1; cursor: pointer; }
1357
1668
  .reply-quote .drop:hover { background: color-mix(in srgb, var(--text-3) 14%, transparent); color: var(--text); }
1358
- .slash-menu { position: absolute; left: -12px; bottom: calc(100% + 14px); z-index: 30; min-width: 280px; max-width: min(460px, 90vw); border: 1px solid var(--line-2); background: var(--panel); border-radius: 14px; box-shadow: 0 14px 40px rgba(0, 0, 0, .35); padding: 6px; display: grid; gap: 2px; }
1359
- .slash-menu .item { display: grid; grid-template-columns: auto 1fr; gap: 10px; align-items: baseline; padding: 7px 10px; border-radius: 9px; cursor: pointer; border: 0; background: transparent; color: var(--text); text-align: left; font: inherit; }
1669
+ /* What the project already settled, at the head of the conversation: the same indicator language
1670
+ as the memory chips, so the room has one way of showing a note and not three. */
1671
+ .settled { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 0 12px 14px; padding-bottom: 12px; border-bottom: 1px solid var(--line); }
1672
+ .settled-lead { font-family: var(--mono); font-size: 9px; letter-spacing: .14em; text-transform: uppercase; color: var(--text-3); }
1673
+ .settled-note { display: inline-flex; align-items: center; gap: 7px; max-width: min(380px, 100%); padding: 3px 10px 3px 8px; border: 1px solid color-mix(in srgb, var(--text-3) 26%, transparent); background: color-mix(in srgb, var(--text-3) 7%, transparent); color: var(--text-2); font: inherit; font-size: 11px; line-height: 1.45; border-radius: 999px; cursor: pointer; text-align: left; }
1674
+ .settled-note .dot { flex: none; width: 7px; height: 7px; border-radius: 999px; background: #ffdc3c; box-shadow: 0 0 0 1px color-mix(in srgb, var(--text-3) 45%, transparent); }
1675
+ .settled-note .what { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1676
+ .settled-note:hover { background: color-mix(in srgb, var(--text-3) 14%, transparent); color: var(--text); }
1677
+
1678
+ /* Where to go on the server on screen: the host is printed, the path is typed. */
1679
+ .viewer-address { display: flex; align-items: center; gap: 0; min-width: 0; flex: 1; max-width: 520px; border: 1px solid var(--line-2); border-radius: 999px; padding: 0 4px 0 11px; background: color-mix(in srgb, var(--text-3) 6%, transparent); }
1680
+ .viewer-address .host { flex: none; font-family: var(--mono); font-size: 10.5px; letter-spacing: .04em; color: var(--text-3); }
1681
+ .viewer-address input { flex: 1; min-width: 0; border: 0; outline: 0; background: transparent; color: var(--text); font-family: var(--mono); font-size: 11.5px; padding: 6px 8px; }
1682
+ .viewer-address input::placeholder { color: var(--text-3); }
1683
+
1684
+ /* Which server is on screen. A label, not a field: the panel opens what the SERVERS list offers
1685
+ and nothing else, so there is nothing here to type. */
1686
+ .browser-where { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: var(--mono); font-size: 11px; letter-spacing: .04em; color: var(--text-2); padding: 0 4px; }
1687
+
1688
+ /* A page that will not be framed, explained where the page would have been. */
1689
+ .browser-refused { display: grid; align-content: center; justify-items: center; gap: 12px; height: 100%; padding: 32px; text-align: center; }
1690
+ .browser-refused h4 { margin: 0; font-family: var(--mono); font-size: 13px; letter-spacing: .1em; color: var(--text); }
1691
+ .browser-refused p { margin: 0; max-width: 46ch; font-size: 12px; line-height: 1.7; color: var(--text-2); }
1692
+ .browser-refused-row { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
1693
+ .browser-refused button { border: 1px solid var(--line-2); background: transparent; color: var(--text); font-family: var(--mono); font-size: 10px; letter-spacing: .12em; padding: 8px 14px; border-radius: 999px; cursor: pointer; }
1694
+ .browser-refused .browser-go { border-color: var(--phosphor); color: var(--phosphor); }
1695
+ .browser-refused button:hover { background: color-mix(in srgb, var(--phosphor) 12%, transparent); }
1696
+
1697
+ /* The servers answering on this computer, listed from RIPLEY's bar. */
1698
+ /* The one control in the header that opens something, so it is built like the size pills rather
1699
+ than like the arrows: a round track, a lamp that says whether anything is listening, and a
1700
+ caret that turns when the list is down. */
1701
+ .viewer-nav .nav.ports { width: auto; gap: 7px; display: inline-flex; align-items: center; justify-content: center; padding: 0 9px 0 8px; border-radius: 999px; font-family: var(--mono); font-size: 9.5px; letter-spacing: .12em; color: var(--text-2); }
1702
+ .ports-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--text-3); flex: none; transition: background .2s, box-shadow .2s; }
1703
+ .nav.ports[data-live="yes"] .ports-dot { background: var(--phosphor); box-shadow: 0 0 6px var(--phosphor); }
1704
+ .ports-caret { width: 0; height: 0; flex: none; border-left: 3.5px solid transparent; border-right: 3.5px solid transparent; border-top: 4px solid currentColor; opacity: .55; transition: transform .18s, opacity .18s; }
1705
+ .nav.ports[aria-expanded="true"] { background: color-mix(in srgb, var(--phosphor) 14%, transparent); border-color: color-mix(in srgb, var(--phosphor) 45%, transparent); color: var(--phosphor); }
1706
+ .nav.ports[aria-expanded="true"] .ports-caret { transform: rotate(180deg); opacity: 1; }
1707
+ /* Keyboard focus is worth a ring; a mouse click is not. Without this the clicked button keeps a
1708
+ heavy dark box the arrows beside it never wear. */
1709
+ .viewer-nav .nav:focus { outline: none; }
1710
+ .viewer-nav .nav:focus-visible { outline: 2px solid var(--phosphor); outline-offset: 2px; }
1711
+ .ports-menu { position: absolute; top: 46px; left: 12px; z-index: 40; min-width: 0; width: min(420px, calc(100cqw - 24px)); max-width: none; border: 1px solid var(--line-2); background: var(--panel); border-radius: 12px; box-shadow: 0 14px 40px rgba(0, 0, 0, .35); padding: 6px; display: grid; gap: 2px; }
1712
+ .ports-row { display: grid; grid-template-columns: auto 1fr; gap: 10px; align-items: baseline; padding: 7px 10px; border: 0; border-radius: 8px; background: transparent; color: var(--text); font: inherit; text-align: left; cursor: pointer; }
1713
+ .ports-row b { font-family: var(--mono); font-size: 12.5px; color: var(--phosphor); }
1714
+ .ports-row span { font-size: 11.5px; color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1715
+ .ports-row:hover { background: color-mix(in srgb, var(--phosphor) 12%, transparent); }
1716
+ /* Found and not offered: it is there, and it is not something this room will frame. */
1717
+ .ports-row.wider { cursor: default; opacity: .62; }
1718
+ .ports-row.wider:hover { background: transparent; }
1719
+ .ports-row.wider b { color: var(--text-3); }
1720
+ .ports-note { padding: 6px 10px 7px; font-family: var(--mono); font-size: 9px; line-height: 1.6; letter-spacing: .1em; color: var(--text-3); }
1721
+
1722
+ /* The way up, beside the agent asking to climb it. Quiet: it is a door, not an alarm. */
1723
+ /* An agent asking for a rung. The box used to be drawn around the whole thing — a dashed frame
1724
+ and a tinted ground behind a sentence that is not clickable — so the eye read a panel and had
1725
+ to find the button inside it. The sentence is a sentence now; only the thing you can press
1726
+ looks pressable, and it looks it properly: filled, not outlined. */
1727
+ .mode-ask { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin: 10px 12px 2px; padding: 0; border: 0; background: none; }
1728
+ .mode-what { flex: 1; min-width: 0; font-family: var(--mono); font-size: 10.5px; letter-spacing: .06em; color: var(--text-2); }
1729
+ .mode-go {
1730
+ flex: none; border: 0; border-radius: 7px; cursor: pointer;
1731
+ background: var(--warn, #e2a022); color: var(--warn-ink, #1a1403);
1732
+ font-family: var(--mono); font-size: 10px; font-weight: 700; letter-spacing: .12em; padding: 6px 13px;
1733
+ box-shadow: 0 1px 0 color-mix(in srgb, #000 22%, transparent);
1734
+ }
1735
+ .mode-go:hover { background: color-mix(in srgb, var(--warn, #e2a022) 86%, #fff); }
1736
+ .mode-go:active { transform: translateY(1px); box-shadow: none; }
1737
+
1738
+ /* A decision handed back, in the agent's colour: read at a glance, answered with one press. */
1739
+ .choice { margin: 10px 12px 2px; padding: 12px 14px; border: 1px solid color-mix(in srgb, var(--agent, var(--text-3)) 32%, transparent); border-radius: 14px; background: color-mix(in srgb, var(--agent, var(--text-3)) 5%, transparent); }
1740
+ .choice-q { margin: 0 0 10px; font-size: 13px; line-height: 1.5; color: var(--text); }
1741
+ .choice-row { display: flex; flex-wrap: wrap; gap: 8px; }
1742
+ .choice-option { border: 1px solid color-mix(in srgb, var(--agent, var(--text-3)) 45%, transparent); background: var(--panel); color: var(--text); font: inherit; font-size: 12.5px; line-height: 1.4; text-align: left; padding: 8px 14px; border-radius: 999px; cursor: pointer; transition: background .15s, border-color .15s; }
1743
+ .choice-option:hover { background: color-mix(in srgb, var(--agent, var(--text-3)) 14%, transparent); border-color: var(--agent, var(--text-3)); }
1744
+
1745
+ /* What the room still has no answer to. Quieter than the free-turn offer above it: that one is
1746
+ about to save money, this one is only wondering. */
1747
+ /* The label on its own line over a single row of pills. Wrapping put the third question on a
1748
+ second line and walked the composer down the page; a sideways rail keeps the height fixed
1749
+ however many the room is carrying. */
1750
+ .openq { display: grid; justify-items: center; gap: 5px; margin: 0 0 8px; min-width: 0; }
1751
+ .openq-lead { font-family: var(--mono); font-size: 9px; letter-spacing: .14em; text-transform: uppercase; color: var(--text-3); }
1752
+ .openq-rail { display: flex; gap: 6px; max-width: 100%; overflow-x: auto; scrollbar-width: none; padding: 1px 2px; }
1753
+ .openq-rail::-webkit-scrollbar { display: none; }
1754
+ .openq-rail > * { flex: 0 0 auto; }
1755
+ .openq-ask { max-width: min(340px, 100%); padding: 4px 11px; border: 1px solid color-mix(in srgb, var(--text-3) 24%, transparent); background: transparent; color: var(--text-2); font: inherit; font-size: 11px; line-height: 1.45; border-radius: 999px; cursor: pointer; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1756
+ .openq-ask:hover { background: color-mix(in srgb, var(--text-3) 10%, transparent); color: var(--text); border-color: color-mix(in srgb, var(--text-3) 40%, transparent); }
1757
+
1758
+ /* The free-turn offer: one line above the field, never in the way of typing. */
1759
+ .free-offer { display: flex; align-items: center; gap: 12px; margin: 0 0 8px; padding: 8px 12px; border: 1px dashed var(--brew); border-radius: 10px; background: color-mix(in srgb, var(--brew) 7%, transparent); }
1760
+ .free-offer .free-what { flex: 1; min-width: 0; font-family: var(--mono); font-size: 11px; line-height: 1.5; letter-spacing: .04em; color: var(--text-2); }
1761
+ .free-offer .free-go { flex: none; border: 1px solid var(--brew); background: transparent; color: var(--brew); font-family: var(--mono); font-size: 10px; font-weight: 600; letter-spacing: .12em; padding: 6px 12px; border-radius: 7px; cursor: pointer; }
1762
+ .free-offer .free-go:hover { background: color-mix(in srgb, var(--brew) 16%, transparent); }
1763
+ @media (max-width: 720px) { .free-offer { flex-wrap: wrap; } }
1764
+ .slash-menu { position: absolute; left: -12px; bottom: calc(100% + 14px); z-index: 30; min-width: 320px; max-width: min(580px, 92vw); border: 1px solid var(--line-2); background: var(--panel); border-radius: 14px; box-shadow: 0 14px 40px rgba(0, 0, 0, .35); padding: 6px; display: grid; gap: 2px; }
1765
+ .slash-menu .item { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 2px 10px; align-items: baseline; padding: 8px 10px; border-radius: 9px; cursor: pointer; border: 0; background: transparent; color: var(--text); text-align: left; font: inherit; }
1360
1766
  .slash-menu .item .key { font-family: var(--mono); font-size: 12.5px; color: var(--agent, var(--phosphor)); font-weight: 600; white-space: nowrap; }
1361
- .slash-menu .item .what { font-size: 12.5px; color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1767
+ .slash-menu .item .what { font-size: 12.5px; line-height: 1.45; color: var(--text-2); display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; }
1768
+ /* The argument syntax on its own line, under both, free to wrap. */
1769
+ .slash-menu .item .args { grid-column: 1 / -1; font-family: var(--mono); font-size: 11px; line-height: 1.5; color: var(--text-3); overflow-wrap: anywhere; }
1362
1770
  .slash-menu .item.off .key { color: var(--text-3); text-decoration: line-through; }
1363
1771
  .slash-menu .item[aria-selected="true"], .slash-menu .item:hover { background: color-mix(in srgb, var(--agent, var(--phosphor)) 14%, transparent); }
1364
1772
  .slash-menu .hint { padding: 4px 10px 6px; font-family: var(--mono); font-size: 10px; letter-spacing: .12em; color: var(--text-3); }
@@ -1395,8 +1803,50 @@ dialog.viewer::backdrop { background: rgba(0,0,0,.7); backdrop-filter: blur(4px)
1395
1803
  .chats-button:active { transform: translateY(1px); }
1396
1804
  @media (max-width: 1100px) { .chats-button { display: none; } }
1397
1805
 
1398
- .tree { grid-row: 3; justify-self: end; align-self: stretch; z-index: 5; width: clamp(240px, calc((100% - var(--column)) / 2 - 36px), 420px); margin: 18px 20px 10px 0; min-height: 0; display: grid; grid-template-rows: auto minmax(0, 1fr); border: 1px solid var(--line-2); border-radius: 18px; background: var(--panel); box-shadow: 0 10px 30px rgba(0, 0, 0, .06); overflow: hidden; animation: tree-in .22s ease; }
1806
+ .tree { position: relative; grid-row: 3; justify-self: end; align-self: stretch; z-index: 5; width: clamp(240px, calc((100% - var(--column)) / 2 - 36px), 420px); margin: 18px 20px 10px 0; min-height: 0; display: grid; grid-template-rows: auto minmax(0, 1fr); border: 1px solid var(--line-2); border-radius: 18px; background: var(--panel); box-shadow: 0 10px 30px rgba(0, 0, 0, .06); overflow: hidden; animation: tree-in .22s ease; }
1807
+ /* The finder's own row of tools: a search, and what is waiting to be pasted. */
1808
+ .tree { grid-template-rows: auto auto auto minmax(0, 1fr); grid-template-columns: minmax(0, 1fr); }
1809
+ .tree-search { padding: 8px 12px 6px; }
1810
+ .tree-search input { width: 100%; border: 1px solid var(--line-2); border-radius: 8px; background: color-mix(in srgb, var(--text-3) 6%, transparent); color: var(--text); font-family: var(--mono); font-size: 11px; padding: 6px 10px; outline: 0; }
1811
+ .tree-search input:focus { border-color: var(--phosphor); }
1812
+ .tree-clip { display: flex; min-width: 0; align-items: center; gap: 8px; margin: 0 12px 6px; padding: 6px 10px; border: 1px dashed var(--phosphor); border-radius: 8px; font-family: var(--mono); font-size: 10px; color: var(--text-2); }
1813
+ .tree-clip .what { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1814
+ .tree-clip button { border: 0; background: transparent; color: var(--phosphor); font: inherit; font-size: 10px; letter-spacing: .1em; cursor: pointer; }
1815
+ .tree-ask { display: flex; align-items: center; gap: 6px; flex: 1; min-width: 0; }
1816
+ .tree-ask input { flex: 1; min-width: 0; border: 0; border-bottom: 1px solid var(--line-2); background: transparent; color: var(--text); font: inherit; font-size: 11px; padding: 2px 1px; outline: none; }
1817
+ .tree-ask input:focus { border-bottom-color: var(--phosphor); }
1818
+ .tree-ask .go { color: var(--phosphor); }
1819
+ .tree-menu { position: absolute; z-index: 20; min-width: 170px; border: 1px solid var(--line-2); border-radius: 10px; background: var(--panel); box-shadow: 0 12px 30px rgba(0,0,0,.35); padding: 5px; display: grid; gap: 1px; }
1820
+ .tree-menu button { border: 0; background: transparent; color: var(--text); font: inherit; font-size: 12px; text-align: left; padding: 7px 10px; border-radius: 7px; cursor: pointer; }
1821
+ .tree-menu button:hover { background: color-mix(in srgb, var(--phosphor) 12%, transparent); }
1822
+ .tree-menu .guarded { color: var(--warn); }
1823
+ /* A zone is a state, not a category: the one that still travels wears the room's own green, the
1824
+ one that does not is dimmed to the colour of everything the archive keeps and never serves. */
1825
+ .nostromo-card .zone { font-family: var(--mono); font-size: 10px; letter-spacing: .12em; }
1826
+ .nostromo-card .zone.underway { color: var(--phosphor); }
1827
+ .nostromo-card .zone.archived { color: var(--text-3); opacity: .75; }
1828
+ .nostromo-card .zone.archived::after { content: ' ·'; }
1829
+
1830
+ /* The brake for one answer, in the row of the answer it stops. Quiet until the turn is worth
1831
+ stopping — which is when the human is already looking at it. */
1832
+ .stop-turn {
1833
+ margin-left: 10px; padding: 2px 9px; border: 1px solid var(--line-2); border-radius: 999px;
1834
+ background: transparent; color: var(--text-3);
1835
+ font-family: var(--mono); font-size: 9px; letter-spacing: .14em; cursor: pointer;
1836
+ opacity: .5; transition: opacity .15s, color .15s, border-color .15s;
1837
+ }
1838
+ .row.thinking:hover .stop-turn, .stop-turn:focus-visible { opacity: 1; }
1839
+ .stop-turn:hover { color: var(--terror); border-color: var(--terror); }
1840
+ .stop-turn:disabled { opacity: .7; cursor: default; color: var(--text-3); border-color: var(--line-2); }
1841
+
1842
+ .tree-menu .note { padding: 5px 10px 6px; font-family: var(--mono); font-size: 8.5px; letter-spacing: .1em; color: var(--text-3); }
1843
+
1399
1844
  @keyframes tree-in { from { opacity: 0; transform: translateX(8px); } to { opacity: 1; transform: none; } }
1845
+ /* Conversations open where their handle is. The files panel is anchored right because its button
1846
+ lives in the bar's right end; this one's handle sits at the left of the thread, so a panel that
1847
+ appeared on the far side — behind the browser, once there is one — read as not opening at all. */
1848
+ .chats { justify-self: start; margin-left: 20px; margin-right: 0; z-index: 7; animation: chats-in .22s ease; }
1849
+ @keyframes chats-in { from { opacity: 0; transform: translateX(-8px); } to { opacity: 1; transform: none; } }
1400
1850
  .tree-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 12px 12px 10px 16px; border-bottom: 1px solid var(--line); font-family: var(--mono); font-size: 10px; letter-spacing: .18em; color: var(--text-2); }
1401
1851
  .tree-title { display: flex; gap: 8px; align-items: baseline; min-width: 0; }
1402
1852
  .tree-root { color: var(--text-3); letter-spacing: .06em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@@ -1455,6 +1905,10 @@ dialog.viewer::backdrop { background: rgba(0,0,0,.7); backdrop-filter: blur(4px)
1455
1905
  .bubble-actions .act:hover { color: var(--text); background: color-mix(in srgb, var(--text) 8%, transparent); }
1456
1906
  .bubble-actions .act.good.on { color: var(--phosphor); }
1457
1907
  .bubble-actions .act.bad.on { color: var(--terror); }
1908
+ .bubble-actions .act.prefer.on { color: var(--phosphor); }
1909
+ .bubble-actions .act.never.on { color: var(--terror); }
1910
+ /* Shown only once the verdict they escalate is in: at rest the bubble carries no extra chrome. */
1911
+ .bubble-actions .act.prefer[hidden], .bubble-actions .act.never[hidden] { display: none; }
1458
1912
  .bubble-actions .act.done { color: var(--phosphor); }
1459
1913
  .reply-menu { width: min(280px, calc(100vw - 24px)); padding: 8px; }
1460
1914
  .reply-menu .model-menu-title { margin: 4px 8px 6px; }
@@ -1494,10 +1948,10 @@ dialog.viewer::backdrop { background: rgba(0,0,0,.7); backdrop-filter: blur(4px)
1494
1948
  .mother-section.sentinel button, .mother-section.update button { border: 1px solid var(--ph-dim); background: transparent; color: var(--ph); font: inherit; font-size: 9.5px; letter-spacing: .16em; padding: 6px 10px; cursor: pointer; }
1495
1949
  .mother-section.sentinel button:hover, .mother-section.update button:hover { background: var(--ph-faint); }
1496
1950
  .mother-section.sentinel .sentinel-report.sent { opacity: .65; }
1497
- .settings .memory-form .toggle { display: inline-flex; align-items: center; gap: 6px; margin-right: 14px; font-size: 9.5px; letter-spacing: .14em; color: var(--ph-dim); cursor: pointer; text-transform: uppercase; }
1498
- .settings .memory-form .toggle input { accent-color: var(--brew); width: auto; }
1499
- .settings .memory-form .full .note { margin-right: 12px; }
1500
- .settings .memory-form .dataset-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding-top: 8px; border-top: 1px solid var(--ph-faint); }
1951
+ :is(.settings, .memory-panel) .memory-form .toggle { display: inline-flex; align-items: center; gap: 6px; margin-right: 14px; font-size: 9.5px; letter-spacing: .14em; color: var(--ph-dim); cursor: pointer; text-transform: uppercase; }
1952
+ :is(.settings, .memory-panel) .memory-form .toggle input { accent-color: var(--brew); width: auto; }
1953
+ :is(.settings, .memory-panel) .memory-form .full .note { margin-right: 12px; }
1954
+ :is(.settings, .memory-panel) .memory-form .dataset-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding-top: 8px; border-top: 1px solid var(--ph-faint); }
1501
1955
  .module-card .ollama-status { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 3px 12px; font-size: 10px; letter-spacing: .1em; }
1502
1956
  .module-card .ollama-status dt { color: var(--ph-dim); } .module-card .ollama-status dd { margin: 0; color: rgba(210, 255, 190, .85); overflow-wrap: anywhere; }
1503
1957
  .module-card .actions .toggle { display: inline-flex; align-items: center; gap: 6px; font-size: 9.5px; letter-spacing: .14em; color: var(--ph-dim); cursor: pointer; }
@@ -1520,16 +1974,6 @@ dialog.viewer::backdrop { background: rgba(0,0,0,.7); backdrop-filter: blur(4px)
1520
1974
  .dev-row a.mother-close { text-decoration: none; display: inline-flex; align-items: center; }
1521
1975
  .dev-fail { color: var(--warn); } .dev-fail code { color: var(--warn); background: color-mix(in srgb, var(--warn) 12%, transparent); padding: 1px 5px; }
1522
1976
  .tour-button { min-width: 30px; padding: 6px 0; text-align: center; font-weight: 700; }
1523
- .tour-frame { width: min(620px, 92vw); padding: 26px 30px 22px; }
1524
- .tour-body { min-height: 190px; padding: 12px 0 6px; }
1525
- .tour-step h3 { margin: 6px 0 14px; font-size: 11px; letter-spacing: .28em; color: var(--ph); font-weight: 700; }
1526
- .tour-step p { margin: 0 0 10px; font-size: 12.5px; line-height: 1.7; letter-spacing: .03em; color: var(--ph); text-transform: none; }
1527
- .tour-foot { display: flex; align-items: center; justify-content: space-between; gap: 14px; padding-top: 14px; border-top: 1px solid var(--ph-dim); }
1528
- .tour-dots { display: inline-flex; gap: 8px; }
1529
- .tour-dots .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--ph-faint); border: 1px solid var(--ph-dim); transition: background-color .22s ease; }
1530
- .tour-dots .dot.on { background: var(--ph); box-shadow: 0 0 8px rgba(155, 255, 102, .5); }
1531
- .tour-nav { display: inline-flex; gap: 8px; }
1532
- .tour .mother-close.primary { background: var(--ph-faint); }
1533
1977
 
1534
1978
  /* DEV modules and proposals */
1535
1979
  .module-card .dev-tag { font-family: var(--mono); font-size: 8.5px; letter-spacing: .18em; color: var(--warn); border: 1px solid color-mix(in srgb, var(--warn) 55%, transparent); padding: 2px 7px; margin-left: 8px; align-self: center; }
@@ -1694,26 +2138,7 @@ dialog.viewer::backdrop { background: rgba(0,0,0,.7); backdrop-filter: blur(4px)
1694
2138
  .exams button.primary { background: var(--ph); color: #041004; text-shadow: none; }
1695
2139
  .exams button:disabled { opacity: .4; cursor: default; }
1696
2140
 
1697
- .maturity { display: grid; gap: 10px; margin: 12px 0 4px; }
1698
- .maturity > .card-fold { border-top: 1px solid var(--ph-faint); padding-top: 10px; }
1699
- .maturity .maturity-next { margin: 0; font-family: var(--mono); font-size: 10.5px; line-height: 1.8; letter-spacing: .1em; text-transform: uppercase; color: #eaffdf; text-shadow: 0 0 6px rgba(155, 255, 102, .18); padding: 11px 13px; border: 1px solid var(--ph); border-left-width: 4px; background: var(--ph-faint); }
1700
- .maturity .maturity-next b { font-size: 9px; letter-spacing: .22em; color: var(--ph); margin-right: 10px; }
1701
- .maturity .maturity-next .where { font-size: 9px; letter-spacing: .16em; color: var(--ph); white-space: nowrap; }
1702
- .maturity-head { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
1703
- .maturity-head b { font-family: var(--mono); font-size: 11px; letter-spacing: .24em; }
1704
- .maturity-head span { font-family: var(--mono); font-size: 9.5px; letter-spacing: .12em; line-height: 1.7; text-transform: uppercase; color: var(--ph-dim); }
1705
- .maturity-head b.stage.empty, .maturity-head b.stage.sparse { color: var(--ph-dim); }
1706
- .maturity-head b.stage.forming { color: var(--ph-dim); }
1707
- .maturity-head b.stage.working, .maturity-head b.stage.mature { color: var(--ph); }
1708
- .maturity-row { display: grid; grid-template-columns: 210px 90px 1fr; gap: 12px; align-items: center; }
1709
- .maturity-row b { font-family: var(--mono); font-size: 9px; letter-spacing: .14em; color: var(--ph-dim); font-weight: 400; }
1710
- .maturity-row i { height: 5px; background: var(--ph-faint); position: relative; }
1711
- .maturity-row i::after { content: ''; position: absolute; inset: 0 auto 0 0; width: var(--fill, 0%); background: var(--ph-dim); }
1712
- .maturity-row.weakest i::after { background: var(--ph); }
1713
- .maturity-row.weakest b { color: var(--ph); }
1714
- .maturity-row span { font-family: var(--mono); font-size: 9px; letter-spacing: .08em; color: var(--ph-dim); opacity: .8; }
1715
2141
  .maturity-next { color: var(--ph); }
1716
- @media (max-width: 860px) { .maturity-row { grid-template-columns: 1fr; gap: 2px; } .maturity-row i { display: none; } }
1717
2142
 
1718
2143
  /* A record you have dealt with: the way out sits at the end of its own row. */
1719
2144
  .mother-record .drop { justify-self: end; background: none; border: 0; color: var(--ph-faint); cursor: pointer; font: inherit; font-size: 14px; padding: 0; line-height: 1; }
@@ -1738,3 +2163,289 @@ dialog.viewer::backdrop { background: rgba(0,0,0,.7); backdrop-filter: blur(4px)
1738
2163
  .viewer .code .t-literal { color: var(--syn-literal); }
1739
2164
  .viewer .code .t-regex { color: var(--syn-regex); }
1740
2165
  .viewer-lang { color: var(--ph-dim); font-size: 9.5px; letter-spacing: .16em; text-transform: uppercase; }
2166
+
2167
+ /* A connector's key: a password field that is never filled back in, what MADRE knows about it
2168
+ (its length, never its value), and the way to take it out again. */
2169
+ .module-card .card-secret { display: flex; flex-wrap: wrap; gap: 10px 14px; align-items: center; }
2170
+ .module-card .card-secret .control { display: inline-flex; align-items: center; gap: 8px; font-size: 9.5px; letter-spacing: .14em; color: var(--ph-dim); }
2171
+ .module-card .card-secret .control input { border: 1px solid var(--ph-dim); background: #000; color: var(--ph); font: inherit; font-size: 10.5px; letter-spacing: .18em; padding: 5px 8px; min-width: 190px; }
2172
+ .module-card .card-secret button.ghost { background: none; border: 1px solid var(--ph-dim); color: var(--ph-dim); font: inherit; font-size: 8.5px; letter-spacing: .18em; padding: 5px 9px; cursor: pointer; }
2173
+ .module-card .card-secret button.ghost:hover { color: var(--ph); border-color: var(--ph); }
2174
+ .module-card .card-block > .note { margin: 0; font-size: 10px; line-height: 1.7; letter-spacing: .08em; color: var(--ph-dim); }
2175
+ .module-card .card-block > a.note { justify-self: start; color: var(--brew); text-decoration: none; letter-spacing: .18em; font-size: 8.5px; }
2176
+ .module-card .card-block > a.note:hover { text-decoration: underline; }
2177
+
2178
+ /* A starter under /module: an instruction to edit, not a command to run. Dimmer key, so the
2179
+ command above it still reads as the command. */
2180
+ .slash-menu .item.starter .key { color: var(--text-3); font-size: 11px; }
2181
+ .slash-menu .item.starter .what { color: var(--text-2); }
2182
+ .dev-card .dev-ask { margin: 0; font-size: 12px; line-height: 1.7; color: var(--ph-dim); }
2183
+ .dev-card .dev-ask code { font-family: var(--mono); font-size: 11.5px; color: var(--brew); }
2184
+
2185
+ /* EXPAND ALL, taken out of the bar. The bar is a row of destinations — CONNECTIONS, MEMORY,
2186
+ NOSTROMO — and this is not one: it acts on the canvas underneath it. A frame made it a rival
2187
+ of those buttons, so it has none: it speaks like the ▸ OPEN of each fold, dim, at the same right
2188
+ edge, over the column of carets it moves all at once. */
2189
+ .fold-all-row { display: flex; justify-content: flex-end; margin: 14px 0 6px; }
2190
+ .fold-all {
2191
+ display: inline-flex; align-items: center; gap: 7px;
2192
+ border: 0; border-radius: 0; background: transparent; color: var(--ph-dim);
2193
+ font-family: var(--mono); font-size: 9px; letter-spacing: .18em; padding: 4px 0; cursor: pointer;
2194
+ }
2195
+ .fold-all:hover, .fold-all:focus-visible { color: var(--ph); }
2196
+ .fold-all:focus-visible { outline: 1px solid var(--ph-dim); outline-offset: 3px; }
2197
+ .fold-all .pixel-icon { width: 9px; height: 9px; display: block; color: currentColor; }
2198
+ .fold-all .pixel-icon svg { width: 100%; height: 100%; shape-rendering: crispEdges; display: block; }
2199
+
2200
+ /* ---------- MEMORY · the dashboard ----------
2201
+ The same phosphor as the rest of MU/TH/UR: hairline frames, one green, the brighter ink only
2202
+ for the figures. A chart here is boxes with heights, so it stays crisp at any width. */
2203
+ .mem-dash { display: grid; gap: 14px; margin-bottom: 8px; transition: opacity .2s; }
2204
+ .mem-dash.loading { opacity: .45; }
2205
+ .mem-dash-head { display: flex; align-items: baseline; justify-content: flex-end; gap: 14px; flex-wrap: wrap; margin-top: -4px; }
2206
+ .mem-dash-head .when { font-size: 9px; letter-spacing: .16em; color: var(--ph-dim); }
2207
+ .mem-dash button { border: 1px solid var(--ph-dim); background: transparent; color: var(--ph); font: inherit; font-size: 9.5px; letter-spacing: .16em; padding: 6px 10px; cursor: pointer; }
2208
+ .mem-dash button:hover { background: var(--ph-faint); }
2209
+ .mem-dash button.primary { background: var(--ph); color: #041004; text-shadow: none; border-color: var(--ph); }
2210
+ .mem-dash button:disabled { opacity: .4; cursor: default; }
2211
+ .mem-dash .note { margin: 0; font-size: 9.5px; letter-spacing: .12em; line-height: 1.7; color: var(--ph-dim); }
2212
+
2213
+ .mem-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 10px; }
2214
+ .mem-tile { display: grid; align-content: start; gap: 6px; padding: 14px 16px 12px; border: 1px solid var(--ph-faint); background: rgba(40, 254, 20, .025); min-width: 0; }
2215
+ .mem-tile .label { font-size: 9px; letter-spacing: .2em; color: var(--ph-dim); }
2216
+ .mem-tile .value { font-size: clamp(26px, 3vw, 34px); font-weight: 700; letter-spacing: .04em; line-height: 1.05; color: #eaffdf; text-shadow: 0 0 10px rgba(155, 255, 102, .35); }
2217
+ .mem-tile .sub { font-size: 9px; letter-spacing: .14em; line-height: 1.6; color: var(--ph); text-transform: uppercase; }
2218
+ .mem-tile .mem-cols.spark { height: 22px; margin-top: 4px; }
2219
+ .mem-tile .mem-meter { margin-top: 8px; }
2220
+
2221
+ .mem-verdict { display: grid; gap: 8px; }
2222
+ .mem-verdict .says { margin: 0; font-size: 10.5px; letter-spacing: .1em; line-height: 1.7; text-transform: uppercase; color: rgba(210, 255, 190, .9); }
2223
+ .mem-verdict .says b { font-size: 10px; letter-spacing: .24em; color: var(--ph); margin-right: 8px; }
2224
+ .mem-verdict .maturity-next { margin: 0; font-family: var(--mono); font-size: 10.5px; line-height: 1.8; letter-spacing: .1em; text-transform: uppercase; color: #eaffdf; text-shadow: 0 0 6px rgba(155, 255, 102, .18); padding: 11px 13px; border: 1px solid var(--ph); background: var(--ph-faint); }
2225
+ .mem-verdict .maturity-next b { font-size: 9px; letter-spacing: .22em; color: var(--ph); margin-right: 10px; }
2226
+ .mem-verdict .maturity-next .where { font-size: 9px; letter-spacing: .16em; color: var(--ph); white-space: nowrap; }
2227
+
2228
+ .mem-grid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 14px; }
2229
+ .mem-card { grid-column: span 6; display: grid; align-content: start; gap: 12px; padding: 14px 16px 16px; border: 1px solid var(--ph-faint); min-width: 0; }
2230
+ .mem-card.span-3 { grid-column: span 3; }
2231
+ .mem-card.span-5 { grid-column: span 5; }
2232
+ .mem-card.span-7 { grid-column: span 7; }
2233
+ .mem-card.span-12 { grid-column: 1 / -1; }
2234
+ .mem-card > h4 { margin: 0; font-size: 9.5px; font-weight: 400; letter-spacing: .24em; color: var(--ph); }
2235
+ .mem-card .detail { font-size: 8.5px; letter-spacing: .1em; line-height: 1.6; color: var(--ph-dim); text-transform: uppercase; }
2236
+ @media (max-width: 1180px) { .mem-card.span-3 { grid-column: span 6; } .mem-card.span-5, .mem-card.span-7 { grid-column: 1 / -1; } }
2237
+ @media (max-width: 640px) { .mem-card.span-3 { grid-column: 1 / -1; } }
2238
+
2239
+ /* Columns from one baseline: 4px rounded ends, square at the floor, 2px of black between them. */
2240
+ .mem-cols { display: flex; align-items: flex-end; gap: 2px; height: 46px; border-bottom: 1px solid var(--ph-faint); }
2241
+ .mem-cols i { flex: 1; min-width: 0; max-width: 24px; height: var(--h, 0); background: var(--ph-dim); border-radius: 2px 2px 0 0; }
2242
+ .mem-cols i.zero { height: 1px; background: var(--ph-faint); border-radius: 0; }
2243
+ .mem-cols i.now { background: var(--ph); }
2244
+ .mem-cols i.on { background: #eaffdf; box-shadow: 0 0 8px rgba(155, 255, 102, .5); }
2245
+ .mem-cols.big i { max-width: none; border-radius: 4px 4px 0 0; }
2246
+ .mem-cols.spark { border-bottom: 0; }
2247
+ .mem-cols.spark i { border-radius: 1px 1px 0 0; }
2248
+
2249
+ .mem-activity { position: relative; display: grid; gap: 12px; outline: none; cursor: crosshair; }
2250
+ .mem-activity:focus-visible { outline: 1px solid var(--ph-dim); outline-offset: 6px; }
2251
+ .mem-strip { display: grid; gap: 6px; }
2252
+ .mem-strip-head { display: flex; align-items: baseline; gap: 10px; }
2253
+ .mem-strip-head b { font-size: 9px; font-weight: 400; letter-spacing: .18em; color: rgba(210, 255, 190, .9); }
2254
+ .mem-strip-head span { margin-left: auto; font-size: 8.5px; letter-spacing: .14em; color: var(--ph-dim); }
2255
+ .mem-axis { display: flex; justify-content: space-between; font-size: 8.5px; letter-spacing: .14em; color: var(--ph-dim); margin-top: -4px; }
2256
+ .mem-cross { position: absolute; top: 0; bottom: 14px; width: 1px; background: var(--ph-dim); pointer-events: none; }
2257
+ .mem-tip { position: absolute; top: 18px; z-index: 2; display: grid; gap: 4px; min-width: 150px; padding: 9px 11px; background: #020402; border: 1px solid var(--ph); pointer-events: none; box-shadow: 0 6px 24px rgba(0, 0, 0, .6); }
2258
+ .mem-tip .when { font-size: 9px; font-weight: 400; letter-spacing: .2em; color: var(--ph-dim); }
2259
+ .mem-tip div { display: flex; align-items: baseline; gap: 8px; }
2260
+ .mem-tip strong { min-width: 28px; font-size: 13px; color: #eaffdf; font-variant-numeric: tabular-nums; }
2261
+ .mem-tip span { font-size: 8.5px; letter-spacing: .14em; color: var(--ph); }
2262
+
2263
+ .mem-table { width: 100%; border-collapse: collapse; font-size: 9.5px; letter-spacing: .1em; }
2264
+ .mem-table th { text-align: right; font-weight: 400; font-size: 8.5px; letter-spacing: .16em; color: var(--ph-dim); padding: 4px 6px; border-bottom: 1px solid var(--ph-faint); }
2265
+ .mem-table td { text-align: right; padding: 4px 6px; color: var(--ph); font-variant-numeric: tabular-nums; border-bottom: 1px solid rgba(40, 254, 20, .06); }
2266
+ .mem-table th:first-child, .mem-table td:first-child { text-align: left; }
2267
+
2268
+ /* A meter: the fill against its own track, with an optional mark where it has to reach. */
2269
+ .mem-meter { position: relative; display: block; height: 6px; background: var(--ph-faint); }
2270
+ .mem-meter > i { position: absolute; inset: 0 auto 0 0; background: var(--ph-dim); border-radius: 0 3px 3px 0; }
2271
+ .mem-meter > .mark { position: absolute; top: -4px; bottom: -4px; width: 2px; margin-left: -1px; background: #eaffdf; }
2272
+ .mem-tile .mem-meter > i, .mem-signal.weakest .mem-meter > i { background: var(--ph); }
2273
+ .mem-signal { display: grid; gap: 5px; }
2274
+ .mem-signal .top { display: flex; align-items: baseline; gap: 10px; }
2275
+ .mem-signal .top b { font-size: 9px; font-weight: 400; letter-spacing: .14em; color: rgba(210, 255, 190, .9); }
2276
+ .mem-signal .top span { margin-left: auto; font-size: 10px; letter-spacing: .08em; color: #eaffdf; font-variant-numeric: tabular-nums; }
2277
+ .mem-signal.weakest .top b { color: var(--ph); }
2278
+ .mem-signal.weakest .top b::after { content: " ◂"; }
2279
+
2280
+ .mem-bars { display: grid; gap: 9px; }
2281
+ .mem-bar { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 4px 10px; align-items: center; }
2282
+ .mem-bar .name { display: flex; align-items: center; gap: 7px; font-size: 9px; letter-spacing: .14em; color: rgba(210, 255, 190, .9); min-width: 0; overflow-wrap: anywhere; }
2283
+ .mem-bar .swatch { flex: none; width: 8px; height: 8px; box-shadow: 0 0 6px currentColor; }
2284
+ .mem-bar .value { font-size: 9px; letter-spacing: .08em; color: var(--ph-dim); font-variant-numeric: tabular-nums; white-space: nowrap; }
2285
+ .mem-bar .track { grid-column: 1 / -1; display: block; height: 8px; }
2286
+ .mem-bar .track i { display: block; height: 100%; min-width: 2px; background: var(--ph-dim); border-radius: 0 4px 4px 0; }
2287
+ .mem-bar:first-child .track i { background: var(--ph); }
2288
+
2289
+ .mem-split { display: flex; gap: 2px; height: 12px; }
2290
+ .mem-split i { flex-basis: 0; min-width: 2px; }
2291
+ .mem-split i.search { background: var(--ph); border-radius: 0; }
2292
+ .mem-split i.cascade { background: var(--ph-dim); border-radius: 0 4px 4px 0; }
2293
+ .mem-legend { display: flex; flex-wrap: wrap; gap: 6px 16px; }
2294
+ .mem-legend .k { display: inline-flex; align-items: center; gap: 6px; font-size: 9px; letter-spacing: .14em; color: rgba(210, 255, 190, .9); }
2295
+ .mem-legend .k i { width: 10px; height: 10px; }
2296
+ .mem-legend .k.search i { background: var(--ph); }
2297
+ .mem-legend .k.cascade i { background: var(--ph-dim); }
2298
+ .mem-legend .k b { font-weight: 400; color: var(--ph-dim); font-variant-numeric: tabular-nums; }
2299
+
2300
+ /* The three tests, side by side on a wide screen, each with its rate against the line it has to reach. */
2301
+ .mem-dash .exams { margin: 0; grid-template-columns: repeat(3, minmax(0, 1fr)); align-items: start; }
2302
+ @media (max-width: 900px) { .mem-dash .exams { grid-template-columns: minmax(0, 1fr); } }
2303
+ .mem-dash .exams > .note, .mem-dash .exams > button { grid-column: 1 / -1; justify-self: start; }
2304
+ /* One hairline all round: the verdict and the bar already say passed or not, so no coloured edge. */
2305
+ .mem-dash .exams .exam, .mem-dash .exams .exam.passed, .mem-dash .exams .exam.failed { align-content: start; height: 100%; box-sizing: border-box; border: 1px solid var(--ph-faint); }
2306
+ .mem-dash .exams .said { font-family: var(--mono); font-size: 11px; letter-spacing: .04em; line-height: 1.65; }
2307
+ .exam-bullet { display: grid; gap: 6px; margin: 2px 0; }
2308
+ .exam-bullet .mem-meter { height: 8px; }
2309
+ .exam-bullet .mem-meter > i { background: var(--ph); }
2310
+ .exams .exam.failed .exam-bullet .mem-meter > i { background: var(--warn); }
2311
+ .exam-bullet span { font-size: 9px; letter-spacing: .14em; color: var(--ph-dim); }
2312
+ .exams .exam .actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
2313
+
2314
+ /* The two folds under the dashboard: same frame as everywhere else in MU/TH/UR. */
2315
+ .memory-panel > .fold { border-top: 1px solid var(--ph-faint); padding-top: 22px; }
2316
+ .memory-panel .memory-form .train-card { border-top: 1px solid var(--ph-faint); padding-top: 12px; }
2317
+ .memory-panel .train-head { color: var(--ph); }
2318
+ .memory-panel .train-step-label { color: var(--ph-dim); }
2319
+ .memory-panel .update-command { border: 1px solid var(--ph-faint); border-radius: 0; background: rgba(0, 0, 0, .4); }
2320
+ .memory-panel .update-command code { color: var(--ph); }
2321
+ .memory-panel .update-command button { border-color: var(--ph-dim); color: var(--ph); background: transparent; border-radius: 0; }
2322
+ /* A command is one long line that scrolls inside its own box; it must never widen the screen. */
2323
+ .memory-panel .memory-form .train-card { display: block; min-width: 0; }
2324
+ .memory-panel .train-steps, .memory-panel .train-steps li, .memory-panel .update-command { min-width: 0; }
2325
+
2326
+ /* ---------- What's new ----------
2327
+ The sheet an app shows after it updates: the room's own ground and type, not MU/TH/UR's
2328
+ terminal, because this is the product talking to its person and not the console. One column,
2329
+ a big plain title, each thing with its 8-bit mark in the room's green, and one button. */
2330
+ /* The window scrolls, not the sheet: a long sheet keeps its rounded edges whole and the scrollbar
2331
+ sits at the edge of the screen, outside the card, where a scrollbar belongs. */
2332
+ dialog.whats-new { position: fixed; inset: 0; width: 100vw; height: 100dvh; max-width: none; max-height: none; margin: 0; border: 0; padding: 24px 16px; box-sizing: border-box; background: transparent; color: var(--text); overflow-y: auto; }
2333
+ dialog.whats-new[open] { display: flex; flex-direction: column; align-items: center; }
2334
+ dialog.whats-new::backdrop { background: rgba(0, 0, 0, .55); backdrop-filter: blur(10px); }
2335
+ .wn-sheet {
2336
+ width: min(560px, 100%); margin: auto 0; flex: none; box-sizing: border-box;
2337
+ padding: 40px 40px 28px; border-radius: 26px; background: var(--panel); border: 1px solid var(--line-2);
2338
+ box-shadow: 0 30px 90px rgba(0, 0, 0, .45); animation: wn-in .45s cubic-bezier(.2, .8, .2, 1);
2339
+ }
2340
+ @keyframes wn-in { from { opacity: 0; transform: translateY(18px) scale(.98); } to { opacity: 1; transform: none; } }
2341
+ @media (prefers-reduced-motion: reduce) { .wn-sheet { animation: none; } }
2342
+ .wn-head { text-align: center; margin-bottom: 26px; }
2343
+ .wn-badge { display: inline-block; font-family: var(--mono); font-size: 10px; letter-spacing: .2em; color: var(--text-2); padding: 4px 10px; border: 1px solid var(--line-2); border-radius: 999px; }
2344
+ .wn-head h2 { margin: 14px 0 10px; font-size: clamp(26px, 5vw, 34px); font-weight: 700; letter-spacing: -.01em; line-height: 1.1; }
2345
+ .wn-intro { margin: 0 auto; max-width: 42ch; font-size: 14.5px; line-height: 1.55; color: var(--text-2); }
2346
+ .wn-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 20px; }
2347
+ .wn-item, .wn-more { display: grid; grid-template-columns: 40px minmax(0, 1fr); gap: 16px; align-items: start; }
2348
+ .wn-icon { width: 30px; height: 30px; margin: 3px 5px 0; color: var(--brew); filter: drop-shadow(0 0 6px color-mix(in srgb, var(--brew) 45%, transparent)); }
2349
+ :root[data-scheme="light"] .wn-icon { color: #178f0c; filter: none; }
2350
+ .wn-words h3 { margin: 0 0 4px; font-size: 15.5px; font-weight: 650; line-height: 1.3; }
2351
+ .wn-words p { margin: 0; font-size: 14px; line-height: 1.55; color: var(--text-2); }
2352
+ .wn-words .wn-where { margin-top: 6px; font-family: var(--mono); font-size: 11px; letter-spacing: .04em; color: var(--text-2); }
2353
+ .wn-where .k { font-size: 9.5px; letter-spacing: .18em; text-transform: uppercase; color: var(--text-3); margin-right: 4px; }
2354
+ .wn-more { margin-top: 26px; padding: 18px 18px 18px 14px; border-radius: 16px; border: 1px dashed var(--line-2); background: color-mix(in srgb, var(--bg) 40%, transparent); }
2355
+ .wn-more .wn-icon { color: var(--module-electric); filter: drop-shadow(0 0 6px color-mix(in srgb, var(--module-electric) 50%, transparent)); }
2356
+ :root[data-scheme="light"] .wn-more .wn-icon { color: var(--module-electric); filter: none; }
2357
+ .wn-more h3 { font-style: italic; }
2358
+ .wn-foot { display: grid; justify-items: center; gap: 14px; margin-top: 30px; }
2359
+ .wn-continue { width: min(320px, 100%); padding: 13px 18px; border: 0; border-radius: 14px; background: var(--accent); color: var(--accent-ink); font: inherit; font-size: 15px; font-weight: 650; cursor: pointer; }
2360
+ .wn-continue:hover { filter: brightness(1.06); }
2361
+ .wn-continue:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
2362
+ .wn-changes { font-size: 13px; color: var(--text-2); text-decoration: none; }
2363
+ .wn-changes:hover { color: var(--text); text-decoration: underline; }
2364
+ @media (max-width: 520px) { .wn-sheet { padding: 30px 22px 22px; border-radius: 22px; } }
2365
+
2366
+ /* ---------- First contact: the tour on the room itself ----------
2367
+ The dialog covers the window and is transparent; the spot is a hole of light in a dark veil,
2368
+ drawn as the spot's own shadow, and it glides from one stop to the next. The card is the same
2369
+ sheet as What's new: the product speaking, in the room's own ground and type. */
2370
+ dialog.tour { position: fixed; inset: 0; width: 100vw; height: 100dvh; max-width: none; max-height: none; margin: 0; padding: 0; border: 0; background: transparent; overflow: hidden; color: var(--text); }
2371
+ dialog.tour::backdrop { background: transparent; }
2372
+ .tour-spot {
2373
+ position: absolute; border-radius: 16px; pointer-events: none;
2374
+ box-shadow: 0 0 0 9999px rgba(0, 0, 0, .62), 0 0 0 2px var(--accent), 0 0 24px color-mix(in srgb, var(--accent) 45%, transparent);
2375
+ transition: left .45s cubic-bezier(.2, .8, .2, 1), top .45s cubic-bezier(.2, .8, .2, 1), width .45s cubic-bezier(.2, .8, .2, 1), height .45s cubic-bezier(.2, .8, .2, 1), opacity .25s;
2376
+ }
2377
+ .tour-spot.none { left: 50% !important; top: 50% !important; width: 0 !important; height: 0 !important; box-shadow: 0 0 0 9999px rgba(0, 0, 0, .62); }
2378
+ .tour-card {
2379
+ position: absolute; width: min(360px, calc(100vw - 32px)); box-sizing: border-box; padding: 22px 22px 16px;
2380
+ border-radius: 20px; background: var(--panel); border: 1px solid var(--line-2); box-shadow: 0 24px 70px rgba(0, 0, 0, .45);
2381
+ transition: left .45s cubic-bezier(.2, .8, .2, 1), top .45s cubic-bezier(.2, .8, .2, 1);
2382
+ }
2383
+ @media (prefers-reduced-motion: reduce) { .tour-spot, .tour-card { transition: none; } }
2384
+ .tour-count { font-family: var(--mono); font-size: 10px; letter-spacing: .2em; color: var(--text-3); text-transform: uppercase; }
2385
+ .tour-card h3 { margin: 8px 0 8px; font-size: 18px; font-weight: 700; line-height: 1.25; }
2386
+ .tour-card p { margin: 0; font-size: 14px; line-height: 1.55; color: var(--text-2); }
2387
+ .tour-card .tour-note { margin-top: 10px; font-size: 12.5px; color: var(--text-3); }
2388
+ .tour-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 18px; }
2389
+ .tour-nav { display: inline-flex; gap: 8px; }
2390
+ .tour-card button { font: inherit; font-size: 13.5px; border-radius: 11px; cursor: pointer; padding: 8px 14px; }
2391
+ .tour-skip { border: 0; background: transparent; color: var(--text-3); padding-left: 0 !important; }
2392
+ .tour-skip:hover { color: var(--text); }
2393
+ .tour-back { border: 1px solid var(--line-2); background: transparent; color: var(--text); }
2394
+ .tour-next { border: 0; background: var(--accent); color: var(--accent-ink); font-weight: 650 !important; }
2395
+ .tour-next:focus-visible, .tour-back:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
2396
+
2397
+ /* The modes, in order and in their own colours: the same chip the composer wears. */
2398
+ .tour-modes { list-style: none; margin: 12px 0 0; padding: 0; display: grid; gap: 8px; }
2399
+ .tour-modes li { display: grid; grid-template-columns: 104px minmax(0, 1fr); gap: 10px; align-items: start; }
2400
+ .tour-mode { display: inline-flex; align-items: center; gap: 5px; justify-self: start; padding: 3px 8px; border-radius: 999px; font-family: var(--mono); font-size: 10.5px; letter-spacing: .08em; white-space: nowrap; }
2401
+ .tour-mode b { font-weight: 700; }
2402
+ .tour-mode.m0 { background: var(--mode-0); color: var(--mode-0-ink); }
2403
+ .tour-mode.m1 { background: var(--mode-1); color: var(--mode-1-ink); }
2404
+ .tour-mode.m2 { background: var(--mode-2); color: var(--mode-2-ink); }
2405
+ .tour-mode.m3 { background: var(--mode-3); color: var(--mode-3-ink); }
2406
+ .tour-mode.m4 { background: var(--mode-4); color: var(--mode-4-ink); }
2407
+ .tour-modes .what { font-size: 12.5px; line-height: 1.45; color: var(--text-2); }
2408
+ .tour-modes li.start .what { color: var(--text); }
2409
+
2410
+ /* First touch: a card under a button that has never been pressed, saying what it opens. */
2411
+ .hint-card {
2412
+ position: fixed; z-index: 2147483000; width: min(320px, calc(100vw - 24px)); box-sizing: border-box; padding: 16px 18px 14px;
2413
+ border-radius: 16px; background: var(--panel); color: var(--text); border: 1px solid var(--line-2);
2414
+ box-shadow: 0 18px 50px rgba(0, 0, 0, .4); animation: wn-in .3s cubic-bezier(.2, .8, .2, 1);
2415
+ }
2416
+ .hint-card.beside::before { content: none; }
2417
+ .hint-card::before {
2418
+ content: ''; position: absolute; top: -7px; left: calc(var(--arrow, 24px) - 6px); width: 12px; height: 12px;
2419
+ background: var(--panel); border-left: 1px solid var(--line-2); border-top: 1px solid var(--line-2); transform: rotate(45deg);
2420
+ }
2421
+ @media (prefers-reduced-motion: reduce) { .hint-card { animation: none; } }
2422
+ .hint-card h3 { margin: 0 0 6px; font-size: 15px; font-weight: 700; }
2423
+ .hint-card p { margin: 0; font-size: 13.5px; line-height: 1.5; color: var(--text-2); }
2424
+ .hint-ok { margin-top: 12px; border: 0; border-radius: 10px; padding: 7px 14px; background: var(--accent); color: var(--accent-ink); font: inherit; font-size: 13px; font-weight: 650; cursor: pointer; }
2425
+ .hint-ok:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
2426
+
2427
+ /* A link inside a fold's header: text in the header's own voice, a step dimmer, that does
2428
+ something without opening or closing the fold. */
2429
+ .mother-section .fold > summary .fold-link { border: 0; background: transparent; padding: 2px 0 2px 10px; margin-left: 2px; border-left: 1px solid var(--ph-faint); font-family: var(--mono); font-size: 10px; letter-spacing: .2em; color: var(--ph-dim); cursor: pointer; }
2430
+ .mother-section .fold > summary .fold-link:hover, .mother-section .fold > summary .fold-link:focus-visible { color: var(--ph); }
2431
+
2432
+ /* /module's ideas: a grid of small cards under the command, each with its mark and the shape of
2433
+ module it would be, in that shape's colour. The command row and the hint keep the full width. */
2434
+ .slash-menu.with-starters { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; min-width: min(560px, 92vw); }
2435
+ .slash-menu.with-starters > .item:not(.starter), .slash-menu.with-starters > .hint { grid-column: 1 / -1; }
2436
+ .slash-menu .item.starter {
2437
+ display: grid; grid-template-columns: 22px minmax(0, 1fr); grid-template-rows: auto auto; gap: 6px 10px; align-items: start;
2438
+ padding: 10px 12px; border-radius: 12px; border: 1px solid var(--line); background: color-mix(in srgb, var(--bg) 35%, transparent);
2439
+ --shape: var(--phosphor);
2440
+ }
2441
+ .slash-menu .item.starter[aria-selected="true"], .slash-menu .item.starter:hover { border-color: color-mix(in srgb, var(--shape) 55%, transparent); background: color-mix(in srgb, var(--shape) 10%, transparent); }
2442
+ .slash-menu .item.starter .starter-icon { grid-row: 1 / 3; width: 18px; height: 18px; margin-top: 2px; color: var(--shape); }
2443
+ .slash-menu .item.starter .idea { font-size: 12.5px; line-height: 1.4; color: var(--text); text-align: left; }
2444
+ .slash-menu .item.starter .shape { justify-self: start; font-family: var(--mono); font-size: 9.5px; letter-spacing: .12em; text-transform: uppercase; padding: 2px 7px; border-radius: 999px; color: var(--shape); border: 1px solid color-mix(in srgb, var(--shape) 45%, transparent); }
2445
+ .slash-menu .item.starter:has(.shape.connector) { --shape: var(--module-electric); }
2446
+ .slash-menu .item.starter:has(.shape.sends) { --shape: var(--mode-4); }
2447
+ .slash-menu .item.starter:has(.shape.card) { --shape: var(--mode-2); }
2448
+ :root[data-scheme="light"] .slash-menu .item.starter:has(.shape.command) { --shape: #178f0c; }
2449
+ :root[data-scheme="light"] .slash-menu .item.starter:has(.shape.sends) { --shape: #1f6f9e; }
2450
+ :root[data-scheme="light"] .slash-menu .item.starter:has(.shape.card) { --shape: #9a6400; }
2451
+ @media (max-width: 560px) { .slash-menu.with-starters { grid-template-columns: minmax(0, 1fr); } }