@polycode-projects/the-mechanical-code-talker 4.0.0 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +176 -8
- package/corpus/reference/index.json.gz +0 -0
- package/corpus/reference/manifest.json +8 -8
- package/corpus/reference/shards/ref-00.jsonl.gz +0 -0
- package/corpus/sprites/src/sprite-facts.jsonl +34 -0
- package/corpus/worlds/index.json.gz +0 -0
- package/corpus/worlds/manifest.json +49 -9
- package/corpus/worlds/shards/greyvale-museum.jsonl.gz +0 -0
- package/corpus/worlds/shards/lantern-cottage.jsonl.gz +0 -0
- package/corpus/worlds/shards/mud-hollow.jsonl.gz +0 -0
- package/corpus/worlds/shards/mud-warren.jsonl.gz +0 -0
- package/corpus/worlds/shards/spider-fly.jsonl.gz +0 -0
- package/corpus/worlds/src/greyvale-museum.jsonl +136 -0
- package/corpus/worlds/src/lantern-cottage.jsonl +60 -0
- package/corpus/worlds/src/mud-hollow.jsonl +82 -0
- package/corpus/worlds/src/mud-warren.jsonl +124 -0
- package/corpus/worlds/src/spider-fly.jsonl +1 -1
- package/data/sprites/animal-icon.toml +11 -5
- package/data/sprites/book-icon.toml +9 -5
- package/data/sprites/cabinet-icon.toml +10 -5
- package/data/sprites/cellar-icon.toml +16 -6
- package/data/sprites/container-icon.toml +7 -3
- package/data/sprites/desk-icon.toml +8 -5
- package/data/sprites/dog-icon.toml +8 -5
- package/data/sprites/dog-with-colour-icon.toml +13 -12
- package/data/sprites/drawing-room-icon.toml +14 -5
- package/data/sprites/egg-icon.toml +6 -3
- package/data/sprites/fly-icon.toml +10 -5
- package/data/sprites/furniture-icon.toml +5 -3
- package/data/sprites/garden-icon.toml +10 -3
- package/data/sprites/key-icon.toml +3 -1
- package/data/sprites/kitchen-icon.toml +15 -6
- package/data/sprites/lamp-icon.toml +9 -4
- package/data/sprites/letter-icon.toml +7 -4
- package/data/sprites/library-icon.toml +14 -3
- package/data/sprites/pan-icon.toml +7 -3
- package/data/sprites/person-icon.toml +6 -2
- package/data/sprites/poodle-icon.toml +1 -1
- package/data/sprites/portable-icon.toml +6 -4
- package/data/sprites/portrait-icon.toml +7 -4
- package/data/sprites/room-icon.toml +11 -2
- package/data/sprites/spider-icon.toml +9 -3
- package/data/sprites/study-icon.toml +10 -2
- package/package.json +5 -4
- package/src/domain/grammar/ace.mjs +40 -4
- package/src/domain/grammar/lexicon-core.json +2 -1
- package/src/domain/grammar/lexicon.mjs +18 -0
- package/src/domain/reference-pack.mjs +31 -7
- package/src/domain/spider-fly-world.mjs +1 -1
- package/src/domain/sprite-templates.mjs +8 -6
- package/src/services/adventure-viz.mjs +130 -47
- package/src/services/adventure.mjs +440 -367
- package/src/services/chat-page-viz.mjs +37 -9
- package/src/services/chat.mjs +99 -18
- package/src/services/code-explorer-viz.mjs +52 -14
- package/src/services/extract-facts.mjs +4 -7
- package/src/services/ingest-viz.mjs +54 -26
- package/src/services/ledger-viz.mjs +105 -56
- package/src/services/memory-panel-viz.mjs +24 -0
- package/src/services/mud-viz.mjs +940 -21
- package/src/services/plan-viz.mjs +119 -61
- package/src/services/research-viz.mjs +151 -57
- package/src/services/spider-fly-turn.mjs +1 -1
- package/src/services/spider-fly-viz.mjs +67 -43
- package/src/services/sprite-catalog-viz.mjs +175 -51
- package/src/services/viz-theme.mjs +15 -0
- package/src/surfaces/web/memory-ask-browser.bundle.js +107 -107
- package/src/surfaces/web/mud-browser-entry.mjs +44 -2
|
@@ -265,42 +265,48 @@ ${THEME_TOKENS_CSS}
|
|
|
265
265
|
@media (prefers-color-scheme: dark) { :root { --fly: #D9A94B; } }
|
|
266
266
|
:root[data-theme="dark"] { --fly: #D9A94B; }
|
|
267
267
|
:root[data-theme="light"] { --fly: #A6791F; }
|
|
268
|
-
/* The
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
268
|
+
/* The console chrome, in this page's own gothic-whimsical register (a
|
|
269
|
+
Charles Addams cartoon: india-ink line work over aged paper, spooky-cute
|
|
270
|
+
rather than scary) WRAPPING the window-corner board scene above, never
|
|
271
|
+
touching it — every rule below this block styles
|
|
272
|
+
.side/.controls-row/.tuning/.status only, all of which are hidden in
|
|
273
|
+
?preview=1 mode alongside .side itself, so none of this bleeds into the
|
|
274
|
+
embedded home-page hero. --chrome-face is the panel's aged-ivory (night
|
|
275
|
+
slate in dark) face, textured by --chrome-hatch, a two-direction ink
|
|
276
|
+
cross-hatch; --chrome-edge-hi/-lo are its raised-bevel light/shadow
|
|
277
|
+
edges; --chrome-accent is the ink-plum accent (borders, active/hover
|
|
278
|
+
states); --chrome-well/-well-ink are the near-black readout well with
|
|
279
|
+
bone-pale numerals, deliberately dark in both themes (a lit readout
|
|
280
|
+
reads dark-with-a-glow whether the room around it is light or dark).
|
|
281
|
+
--chrome-shadow-raised/-inset compose the two bevel directions once so
|
|
282
|
+
every panel/button/well below just references one of them. */
|
|
281
283
|
:root {
|
|
282
|
-
--chrome-face: #
|
|
283
|
-
--chrome-
|
|
284
|
-
--chrome-well: #
|
|
284
|
+
--chrome-face: #E7E1D3; --chrome-face-hi: #F2EDE0; --chrome-edge-hi: #FAF6EA; --chrome-edge-lo: #6E6A5E;
|
|
285
|
+
--chrome-accent: #5C4B66; --chrome-accent-soft: rgba(92, 75, 102, .14);
|
|
286
|
+
--chrome-well: #1A171C; --chrome-well-ink: #E4DDCB;
|
|
287
|
+
--chrome-hatch: repeating-linear-gradient(55deg, rgba(28, 23, 32, .05) 0 1px, transparent 1px 6px), repeating-linear-gradient(-35deg, rgba(28, 23, 32, .04) 0 1px, transparent 1px 9px);
|
|
285
288
|
--chrome-shadow-raised: inset 0 1px 0 var(--chrome-edge-hi), inset 0 -1px 0 var(--chrome-edge-lo), 0 1px 2px rgba(0, 0, 0, .18);
|
|
286
289
|
--chrome-shadow-inset: inset 0 1px 3px var(--chrome-edge-lo), inset 0 -1px 0 var(--chrome-edge-hi);
|
|
287
290
|
}
|
|
288
291
|
@media (prefers-color-scheme: dark) {
|
|
289
|
-
:root { --chrome-face: #
|
|
290
|
-
--chrome-
|
|
291
|
-
--chrome-well: #
|
|
292
|
+
:root { --chrome-face: #262230; --chrome-face-hi: #302B3D; --chrome-edge-hi: #453E57; --chrome-edge-lo: #121016;
|
|
293
|
+
--chrome-accent: #A995BD; --chrome-accent-soft: rgba(169, 149, 189, .16);
|
|
294
|
+
--chrome-well: #0C0A10; --chrome-well-ink: #E0D8C6;
|
|
295
|
+
--chrome-hatch: repeating-linear-gradient(55deg, rgba(240, 235, 220, .04) 0 1px, transparent 1px 6px), repeating-linear-gradient(-35deg, rgba(240, 235, 220, .03) 0 1px, transparent 1px 9px); }
|
|
292
296
|
}
|
|
293
|
-
:root[data-theme="dark"] { --chrome-face: #
|
|
294
|
-
--chrome-
|
|
295
|
-
--chrome-well: #
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
--chrome-
|
|
297
|
+
:root[data-theme="dark"] { --chrome-face: #262230; --chrome-face-hi: #302B3D; --chrome-edge-hi: #453E57; --chrome-edge-lo: #121016;
|
|
298
|
+
--chrome-accent: #A995BD; --chrome-accent-soft: rgba(169, 149, 189, .16);
|
|
299
|
+
--chrome-well: #0C0A10; --chrome-well-ink: #E0D8C6;
|
|
300
|
+
--chrome-hatch: repeating-linear-gradient(55deg, rgba(240, 235, 220, .04) 0 1px, transparent 1px 6px), repeating-linear-gradient(-35deg, rgba(240, 235, 220, .03) 0 1px, transparent 1px 9px); }
|
|
301
|
+
:root[data-theme="light"] { --chrome-face: #E7E1D3; --chrome-face-hi: #F2EDE0; --chrome-edge-hi: #FAF6EA; --chrome-edge-lo: #6E6A5E;
|
|
302
|
+
--chrome-accent: #5C4B66; --chrome-accent-soft: rgba(92, 75, 102, .14);
|
|
303
|
+
--chrome-well: #1A171C; --chrome-well-ink: #E4DDCB;
|
|
304
|
+
--chrome-hatch: repeating-linear-gradient(55deg, rgba(28, 23, 32, .05) 0 1px, transparent 1px 6px), repeating-linear-gradient(-35deg, rgba(28, 23, 32, .04) 0 1px, transparent 1px 9px); }
|
|
299
305
|
html { background: var(--bg); }
|
|
300
306
|
body { margin: 0; background: var(--bg); color: var(--ink); font-family: ${SERIF_STACK}; font-size: 16px; line-height: 1.5; }
|
|
301
307
|
.mono { font-family: ${MONO_STACK}; }
|
|
302
308
|
main { max-width: 1120px; margin: 0 auto; padding: 1.4rem 1.2rem 2.2rem; }
|
|
303
|
-
.eyebrow { font-family: ${MONO_STACK}; font-size: .7rem; letter-spacing: .08em; text-transform: uppercase; color: var(--chrome-
|
|
309
|
+
.eyebrow { font-family: ${MONO_STACK}; font-size: .7rem; letter-spacing: .08em; text-transform: uppercase; color: var(--chrome-accent); }
|
|
304
310
|
h1 { font-size: 1.4rem; margin: .3rem 0 .9rem; text-wrap: balance; }
|
|
305
311
|
button { font: inherit; color: inherit; background: none; cursor: pointer; }
|
|
306
312
|
button:focus-visible, input:focus-visible, .sprite:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
|
|
@@ -310,7 +316,7 @@ ${THEME_TOKENS_CSS}
|
|
|
310
316
|
needing to sum to a full width. */
|
|
311
317
|
.stage { display: grid; grid-template-columns: minmax(0, 8fr) minmax(280px, 3fr); gap: 1.2rem; align-items: start; }
|
|
312
318
|
@media (max-width: 760px) { .stage { grid-template-columns: minmax(0, 1fr); } }
|
|
313
|
-
/* A
|
|
319
|
+
/* A moonlit window corner: a cool pale glow near the top-left (WEB_HOME
|
|
314
320
|
already sits near that corner — spider-fly-world.mjs's own header
|
|
315
321
|
comment), and a faint diagonal weave standing in for dust/silk caught
|
|
316
322
|
in the light. Decoration only — the 10x10 game grid itself is drawn by
|
|
@@ -319,20 +325,26 @@ ${THEME_TOKENS_CSS}
|
|
|
319
325
|
margin: 0 auto;
|
|
320
326
|
position: relative; width: ${BOARD_PX}px; max-width: 100%; aspect-ratio: 1 / 1;
|
|
321
327
|
background:
|
|
322
|
-
radial-gradient(140% 140% at 6% 6%, rgba(
|
|
323
|
-
repeating-linear-gradient(115deg, rgba(
|
|
328
|
+
radial-gradient(140% 140% at 6% 6%, rgba(224, 224, 240, .55), transparent 52%),
|
|
329
|
+
repeating-linear-gradient(115deg, rgba(100, 95, 110, .06) 0 1px, transparent 1px 30px),
|
|
324
330
|
var(--card);
|
|
325
331
|
border: 1px solid var(--line);
|
|
326
332
|
box-shadow: inset 0 0 0 6px var(--bg), inset 0 0 0 7px var(--line);
|
|
327
333
|
}
|
|
328
|
-
@media (prefers-color-scheme: dark) { .board-frame { background: radial-gradient(140% 140% at 6% 6%, rgba(
|
|
329
|
-
:root[data-theme="dark"] .board-frame { background: radial-gradient(140% 140% at 6% 6%, rgba(
|
|
334
|
+
@media (prefers-color-scheme: dark) { .board-frame { background: radial-gradient(140% 140% at 6% 6%, rgba(126, 120, 158, .30), transparent 52%), repeating-linear-gradient(115deg, rgba(255, 255, 255, .045) 0 1px, transparent 1px 30px), var(--card); } }
|
|
335
|
+
:root[data-theme="dark"] .board-frame { background: radial-gradient(140% 140% at 6% 6%, rgba(126, 120, 158, .30), transparent 52%), repeating-linear-gradient(115deg, rgba(255, 255, 255, .045) 0 1px, transparent 1px 30px), var(--card); }
|
|
330
336
|
.board-frame canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
|
|
337
|
+
/* The cobweb in the window corner — pure decoration over the moonlit
|
|
338
|
+
glow (never a game element; the ACTIVE web block is still drawn by
|
|
339
|
+
drawBoard() on the canvas). It sits under the sprite layer so agents
|
|
340
|
+
always pass in front of it. */
|
|
341
|
+
.cobweb { position: absolute; top: 7px; left: 7px; width: 36%; height: 36%; color: var(--ink); opacity: .26; pointer-events: none; }
|
|
342
|
+
.cobweb svg { width: 100%; height: 100%; display: block; }
|
|
331
343
|
.sprite-layer { position: absolute; inset: 0; }
|
|
332
344
|
.sprite { position: absolute; width: 7.6%; height: 7.6%; transform: translate(-50%, -50%); transition: left .25s ease, top .25s ease; }
|
|
333
345
|
@media (prefers-reduced-motion: reduce) { .sprite, .sprite-face { transition: none !important; } }
|
|
334
346
|
.sprite-face { width: 100%; height: 100%; transition: transform .25s ease; }
|
|
335
|
-
.sprite-face svg { width: 100%; height: 100%; display: block; }
|
|
347
|
+
.sprite-face svg { width: 100%; height: 100%; display: block; filter: var(--sprite-pop); }
|
|
336
348
|
.sprite[data-cls="spider"] { color: var(--taught); }
|
|
337
349
|
.sprite[data-cls="fly"] { color: var(--fly); }
|
|
338
350
|
.sprite[data-cls="egg"] { color: var(--muted); }
|
|
@@ -350,7 +362,7 @@ ${THEME_TOKENS_CSS}
|
|
|
350
362
|
nameplate header below bleeds to this same panel's own edges via a
|
|
351
363
|
negative margin equal to this padding, so it reads as one welded unit,
|
|
352
364
|
not a label floating inside a box. */
|
|
353
|
-
.hud, .chat, .tuning { background: var(--chrome-face); border: 1px solid var(--chrome-edge-lo); box-shadow: var(--chrome-shadow-raised); border-radius: 2px; padding: .6rem .75rem; }
|
|
365
|
+
.hud, .chat, .tuning { background: var(--chrome-hatch), var(--chrome-face); border: 1px solid var(--chrome-edge-lo); box-shadow: var(--chrome-shadow-raised); border-radius: 2px; padding: .6rem .75rem; }
|
|
354
366
|
/* Both the controls strip above the board and the tuning strip below it
|
|
355
367
|
match the board's own width (not the wider stage-left column they sit
|
|
356
368
|
in), so all three line up as one visual stack. */
|
|
@@ -380,12 +392,12 @@ ${THEME_TOKENS_CSS}
|
|
|
380
392
|
.hud-row { display: flex; align-items: flex-start; gap: .6rem; padding: .4rem 0; border-top: 1px solid var(--chrome-edge-lo); box-shadow: inset 0 1px 0 var(--chrome-edge-hi); }
|
|
381
393
|
.hud-row:first-of-type { border-top: none; box-shadow: none; }
|
|
382
394
|
.hud-row.clickable { cursor: pointer; }
|
|
383
|
-
.hud-row.clickable:hover, .hud-row.clickable:focus-visible { background: var(--chrome-
|
|
395
|
+
.hud-row.clickable:hover, .hud-row.clickable:focus-visible { background: var(--chrome-accent-soft); }
|
|
384
396
|
.hud-main { display: flex; flex-direction: column; gap: .1rem; flex: 1 1 auto; min-width: 0; }
|
|
385
397
|
.hud-id { font-family: ${MONO_STACK}; font-size: .74rem; font-weight: 600; }
|
|
386
398
|
.hud-id.spider { color: var(--taught); } .hud-id.fly { color: var(--fly); } .hud-id.egg { color: var(--muted); }
|
|
387
399
|
.hud-goal { font-size: .85rem; }
|
|
388
|
-
.hud-plan, .hud-belief { font-family: ${MONO_STACK}; font-size: .66rem; color: var(--muted); margin-top: .25rem; line-height: 1.4; padding-left: .5rem; border-left: 2px solid var(--chrome-
|
|
400
|
+
.hud-plan, .hud-belief { font-family: ${MONO_STACK}; font-size: .66rem; color: var(--muted); margin-top: .25rem; line-height: 1.4; padding-left: .5rem; border-left: 2px solid var(--chrome-accent); }
|
|
389
401
|
/* the click-expand facts panel (§28): beside the clicked spider/fly's own
|
|
390
402
|
row, never a separate popover or a second panel elsewhere on the page —
|
|
391
403
|
the same believedCellOf/beliefSnapshotFor read path spider-fly.mjs
|
|
@@ -417,7 +429,7 @@ ${THEME_TOKENS_CSS}
|
|
|
417
429
|
.chatask input:disabled { opacity: .5; }
|
|
418
430
|
.chatpills { display: flex; flex-wrap: wrap; gap: .3rem; margin-top: .5rem; }
|
|
419
431
|
.pill { font-family: ${MONO_STACK}; font-size: .68rem; padding: .2rem .6rem; border: 1px solid var(--chrome-edge-lo); border-radius: 99px; background: var(--chrome-face); color: var(--ink); box-shadow: inset 0 1px 0 var(--chrome-edge-hi); white-space: nowrap; }
|
|
420
|
-
.pill:hover:not(:disabled) { border-color: var(--chrome-
|
|
432
|
+
.pill:hover:not(:disabled) { border-color: var(--chrome-accent); }
|
|
421
433
|
.pill:disabled { opacity: .45; cursor: default; }
|
|
422
434
|
.pill[data-role="addr"].active { border-color: var(--taught); color: var(--taught); }
|
|
423
435
|
/* The dynamic deception-pill rail (§A.2.4): a true/false tag shown ONLY
|
|
@@ -437,7 +449,7 @@ ${THEME_TOKENS_CSS}
|
|
|
437
449
|
.tuning-col.spider h3 { color: var(--taught); } .tuning-col.fly h3 { color: var(--fly); }
|
|
438
450
|
.tuning-col label { display: block; font-size: .68rem; color: var(--muted); margin-bottom: .6rem; }
|
|
439
451
|
/* Every live number on this page (this readout, the turn plaque below)
|
|
440
|
-
shares the same
|
|
452
|
+
shares the same bone-on-ink readout treatment — one readout language,
|
|
441
453
|
not a one-off flourish. */
|
|
442
454
|
.tuning-col .tuning-val { font-family: ${MONO_STACK}; font-variant-numeric: tabular-nums; color: var(--chrome-well-ink); background: var(--chrome-well); border: 1px solid var(--chrome-edge-lo); box-shadow: var(--chrome-shadow-inset); border-radius: 2px; padding: 0 .35rem; float: right; }
|
|
443
455
|
.tuning-col input[type="range"] {
|
|
@@ -446,7 +458,7 @@ ${THEME_TOKENS_CSS}
|
|
|
446
458
|
-webkit-appearance: none; appearance: none;
|
|
447
459
|
}
|
|
448
460
|
.tuning-col.fly input[type="range"] { accent-color: var(--fly); }
|
|
449
|
-
/* A lever-look thumb (a molded
|
|
461
|
+
/* A lever-look thumb (a molded, ink-edged cap) — appearance: none above
|
|
450
462
|
drops the OS's own native slider look in browsers that honor it;
|
|
451
463
|
accent-color just above is the fallback for any that don't. */
|
|
452
464
|
.tuning-col input[type="range"]::-webkit-slider-thumb {
|
|
@@ -469,14 +481,14 @@ ${THEME_TOKENS_CSS}
|
|
|
469
481
|
control panel", and it costs nothing under reduced-motion since neither
|
|
470
482
|
state involves a transition, just an instant bevel swap. */
|
|
471
483
|
.controls-row button { font-family: ${MONO_STACK}; font-size: .78rem; letter-spacing: .03em; text-transform: uppercase; padding: .4rem .85rem; border-radius: 2px; border: 1px solid var(--chrome-edge-lo); background: var(--chrome-face); box-shadow: var(--chrome-shadow-raised); color: var(--ink); }
|
|
472
|
-
.controls-row button:hover:not(:disabled) { border-color: var(--chrome-
|
|
484
|
+
.controls-row button:hover:not(:disabled) { border-color: var(--chrome-accent); }
|
|
473
485
|
.controls-row button:active:not(:disabled) { box-shadow: var(--chrome-shadow-inset); }
|
|
474
486
|
.controls-row button:disabled { opacity: .4; cursor: default; box-shadow: none; }
|
|
475
|
-
/* The
|
|
487
|
+
/* The turn counter plaque: an accent-framed readout, the same
|
|
476
488
|
inset-well readout language as every stat above it — the one thing this
|
|
477
489
|
page is remembered by. */
|
|
478
|
-
.controls-row .turn { margin-left: auto; font-family: ${MONO_STACK}; font-size: .82rem; letter-spacing: .05em; text-transform: uppercase; font-variant-numeric: tabular-nums; color: var(--chrome-well-ink); background: var(--chrome-well); border: 1px solid var(--chrome-
|
|
479
|
-
.status { font-family: ${MONO_STACK}; font-size: .74rem; color: var(--muted); margin-top: .5rem; padding-left: .5rem; border-left: 2px solid var(--chrome-
|
|
490
|
+
.controls-row .turn { margin-left: auto; font-family: ${MONO_STACK}; font-size: .82rem; letter-spacing: .05em; text-transform: uppercase; font-variant-numeric: tabular-nums; color: var(--chrome-well-ink); background: var(--chrome-well); border: 1px solid var(--chrome-accent); box-shadow: var(--chrome-shadow-inset); border-radius: 2px; padding: .3rem .7rem; }
|
|
491
|
+
.status { font-family: ${MONO_STACK}; font-size: .74rem; color: var(--muted); margin-top: .5rem; padding-left: .5rem; border-left: 2px solid var(--chrome-accent); }
|
|
480
492
|
body.preview .side, body.preview .controls-panel, body.preview .tuning { display: none; }
|
|
481
493
|
body.preview main { padding: 0; max-width: none; }
|
|
482
494
|
body.preview .stage, body.preview .stage-left { display: block; }
|
|
@@ -507,11 +519,23 @@ ${THEME_TOKENS_CSS}
|
|
|
507
519
|
<div class="board-frame" id="boardFrame">
|
|
508
520
|
<canvas id="board" width="${BOARD_PX}" height="${BOARD_PX}" aria-label="the 10x10 board"></canvas>
|
|
509
521
|
<canvas id="pov" width="${BOARD_PX}" height="${BOARD_PX}" aria-hidden="true"></canvas>
|
|
522
|
+
<div class="cobweb" aria-hidden="true"><svg viewBox="0 0 100 100" fill="none" stroke="currentColor" stroke-linecap="round">
|
|
523
|
+
<g stroke-width="0.7">
|
|
524
|
+
<path d="M0 0 L73.8 5.2"/><path d="M0 0 L68.6 27.7"/><path d="M0 0 L52.3 52.3"/><path d="M0 0 L27.7 68.6"/><path d="M0 0 L5.2 73.8"/>
|
|
525
|
+
</g>
|
|
526
|
+
<g stroke-width="0.55">
|
|
527
|
+
<path d="M20 1.4 Q16.8 3.9 18.5 7.5 Q14.3 9.5 14.1 14.1 Q9.5 14.3 7.5 18.5 Q3.9 16.8 1.4 20"/>
|
|
528
|
+
<path d="M33.9 2.4 Q28.4 6.6 31.5 12.7 Q24.4 16.1 24 24 Q16.1 24.4 12.7 31.5 Q6.6 28.4 2.4 33.9"/>
|
|
529
|
+
<path d="M49.9 3.5 Q41.9 9.7 46.4 18.7 Q35.9 23.7 35.4 35.4 Q23.7 35.9 18.7 46.4 Q9.7 41.9 3.5 49.9"/>
|
|
530
|
+
<path d="M67.8 4.8 Q57 13.2 63 25.5 Q48.8 32.3 48.1 48.1 Q32.3 48.8 25.5 63 Q13.2 57 4.8 67.8"/>
|
|
531
|
+
</g>
|
|
532
|
+
<path d="M48.1 48.1 Q49.5 54 48.6 59.5" stroke-width="0.5"/>
|
|
533
|
+
</svg></div>
|
|
510
534
|
<div class="sprite-layer" id="spriteLayer"></div>
|
|
511
535
|
<div class="thread-tip" id="threadTip"></div>
|
|
512
536
|
</div>
|
|
513
537
|
<div class="tuning" id="tuning">
|
|
514
|
-
<h2>live tuning &
|
|
538
|
+
<h2>live tuning · mass loss, spawn rate, vision, per class</h2>
|
|
515
539
|
<div class="tuning-grid">
|
|
516
540
|
<div class="tuning-col spider">
|
|
517
541
|
<h3>spider</h3>
|
|
@@ -165,10 +165,10 @@ export const GROUP_OBJECT = "object";
|
|
|
165
165
|
export const GROUP_EMOJI = "emoji";
|
|
166
166
|
|
|
167
167
|
export const CATALOG_GROUPS = Object.freeze([
|
|
168
|
-
Object.freeze({ id: GROUP_ADVENTURE, label: "Ashcombe Hall's own adventure props", note: "the icon tier's
|
|
168
|
+
Object.freeze({ id: GROUP_ADVENTURE, label: "Ashcombe Hall's own adventure props", note: "the icon tier's named cast and furniture. Each has its own 44px sprite." }),
|
|
169
169
|
Object.freeze({ id: GROUP_PERSON, label: "Person roles" }),
|
|
170
170
|
Object.freeze({ id: GROUP_OBJECT, label: "Physical objects, creatures & places" }),
|
|
171
|
-
Object.freeze({ id: GROUP_EMOJI, label: "Emotions & events", note: "abstract concepts with no
|
|
171
|
+
Object.freeze({ id: GROUP_EMOJI, label: "Emotions & events", note: "abstract concepts with no single physical picture, drawn as the familiar emoji instead" }),
|
|
172
172
|
]);
|
|
173
173
|
|
|
174
174
|
/** Which catalog section `cls` belongs in. Pure. `isIconTierClass`/`isEmoji`
|
|
@@ -181,6 +181,33 @@ export function groupForClass(cls, { isIconTierClass, isEmoji }) {
|
|
|
181
181
|
return GROUP_OBJECT;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
/** Cluster one presentation group's entries under each class's nearest
|
|
185
|
+
* ILLUSTRATED ancestor — the first term after the class itself, walking its
|
|
186
|
+
* own real chain outward, that carries a sprite template at either tier. A
|
|
187
|
+
* new leaf attached under that ancestor later resolves to the ancestor's
|
|
188
|
+
* own sprite through the same walk, so these headings show exactly where
|
|
189
|
+
* the graph can grow with no new art needed. A cluster needs two members
|
|
190
|
+
* to earn a heading; singletons (and classes whose chain reaches no
|
|
191
|
+
* illustrated ancestor) fold into one trailing `ancestor: null` bucket so
|
|
192
|
+
* the section never shreds into one-card headings. Pure. */
|
|
193
|
+
export function clusterEntriesByAncestor(entries, spritedClasses) {
|
|
194
|
+
const byKey = new Map();
|
|
195
|
+
for (const e of entries || []) {
|
|
196
|
+
const key = (e.chain || []).slice(1).find((t) => spritedClasses.has(t)) || "";
|
|
197
|
+
if (!byKey.has(key)) byKey.set(key, []);
|
|
198
|
+
byKey.get(key).push(e);
|
|
199
|
+
}
|
|
200
|
+
const clusters = [];
|
|
201
|
+
const rest = [];
|
|
202
|
+
for (const [key, list] of byKey) {
|
|
203
|
+
if (key && list.length >= 2) clusters.push({ ancestor: key, entries: list });
|
|
204
|
+
else rest.push(...list);
|
|
205
|
+
}
|
|
206
|
+
clusters.sort((a, b) => b.entries.length - a.entries.length || a.ancestor.localeCompare(b.ancestor));
|
|
207
|
+
if (rest.length) clusters.push({ ancestor: null, entries: [...rest].sort((a, b) => a.className.localeCompare(b.className)) });
|
|
208
|
+
return clusters;
|
|
209
|
+
}
|
|
210
|
+
|
|
184
211
|
function templatesForClass(cls, templates) {
|
|
185
212
|
return (templates || []).filter((t) => Array.isArray(t?.classes) && t.classes.includes(cls));
|
|
186
213
|
}
|
|
@@ -418,7 +445,8 @@ function swatchHtml(s) {
|
|
|
418
445
|
if (s.treatment) parts.push(`<span class="swatch-treat">→ ${escapeHtml(s.treatment)} treatment</span>`);
|
|
419
446
|
const title = s.property ? `${s.property} = ${s.label}` : s.label;
|
|
420
447
|
const cls = ["swatch", s.tier, s.kind].filter(Boolean).join(" ");
|
|
421
|
-
|
|
448
|
+
const property = s.property ? ` data-property="${escapeHtml(s.property)}"` : "";
|
|
449
|
+
return `<div class="${cls}"${property} title="${escapeHtml(title)}"><div class="swatch-img">${s.svg}</div><div class="swatch-caption">${parts.join("")}</div></div>`;
|
|
422
450
|
}
|
|
423
451
|
|
|
424
452
|
function tierRowHtml(tierName, swatches) {
|
|
@@ -438,14 +466,32 @@ function cardHtml(entry) {
|
|
|
438
466
|
</article>`;
|
|
439
467
|
}
|
|
440
468
|
|
|
441
|
-
|
|
469
|
+
/** One ancestor cluster inside a section: a heading that carries the
|
|
470
|
+
* ancestor's OWN resolved sprite (the proof the fallback has art to land
|
|
471
|
+
* on), then that cluster's cards. `ancestorSvg` is null for the trailing
|
|
472
|
+
* no-illustrated-ancestor bucket. */
|
|
473
|
+
function clusterHtml(cluster, ancestorSvg) {
|
|
474
|
+
const name = cluster.ancestor || "everything else";
|
|
475
|
+
const chip = ancestorSvg ? `<span class="cluster-chip">${ancestorSvg}</span>` : "";
|
|
476
|
+
return `<div class="cluster">
|
|
477
|
+
<h3 class="cluster-head">${chip}<span class="cluster-name">${escapeHtml(name)}</span><span class="count">${cluster.entries.length}</span></h3>
|
|
478
|
+
<div class="cards">${cluster.entries.map(cardHtml).join("")}</div>
|
|
479
|
+
</div>`;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function sectionHtml(group, entries, { clusterBy = null } = {}) {
|
|
442
483
|
const rows = entries.filter((e) => e.group === group.id);
|
|
443
484
|
if (!rows.length) return "";
|
|
444
485
|
const note = group.note ? `<p class="section-note">${escapeHtml(group.note)}</p>` : "";
|
|
486
|
+
const body = clusterBy
|
|
487
|
+
? clusterEntriesByAncestor(rows, clusterBy.spritedClasses)
|
|
488
|
+
.map((c) => clusterHtml(c, c.ancestor ? clusterBy.resolveChip(c.ancestor) : null))
|
|
489
|
+
.join("")
|
|
490
|
+
: `<div class="cards">${rows.map(cardHtml).join("")}</div>`;
|
|
445
491
|
return `<section class="group" id="g-${group.id}" aria-label="${escapeHtml(group.label)}">
|
|
446
492
|
<h2>${escapeHtml(group.label)} <span class="count">${rows.length}</span></h2>
|
|
447
493
|
${note}
|
|
448
|
-
|
|
494
|
+
${body}
|
|
449
495
|
</section>`;
|
|
450
496
|
}
|
|
451
497
|
|
|
@@ -471,6 +517,15 @@ export function renderSpriteCatalogHtml({ title = DEFAULT_TITLE, iconTemplates =
|
|
|
471
517
|
const entries = buildSpriteCatalogEntries({ iconTemplates, largeTemplates, factRows });
|
|
472
518
|
const totalSwatches = entries.reduce((n, e) => n + e.iconSwatches.length + e.largeSwatches.length, 0);
|
|
473
519
|
const pageData = embedJson({ classCount: entries.length, swatchCount: totalSwatches });
|
|
520
|
+
// Ancestor clustering for the two big sections: the heading chip is the
|
|
521
|
+
// ancestor's OWN resolved sprite — visible proof that a new subclass
|
|
522
|
+
// attached there already has fallback art waiting for it.
|
|
523
|
+
const spritedClasses = new Set([...iconTemplates, ...largeTemplates].flatMap((t) => t?.classes || []));
|
|
524
|
+
const clusterBy = {
|
|
525
|
+
spritedClasses,
|
|
526
|
+
resolveChip: (ancestor) =>
|
|
527
|
+
resolveSpriteAsset(ancestor, [], [], largeTemplates, SPRITE_REGISTRY, { instanceKey: `cluster-${ancestor}` }),
|
|
528
|
+
};
|
|
474
529
|
const dockRows = spritesBundleAvailable ? spriteFactRows({ iconTemplates, largeTemplates }) : [];
|
|
475
530
|
const navHtml = CATALOG_GROUPS
|
|
476
531
|
.map((g) => `<a class="jump" href="#g-${g.id}">${escapeHtml(g.label)} <span class="count">${entries.filter((e) => e.group === g.id).length}</span></a>`)
|
|
@@ -478,8 +533,6 @@ export function renderSpriteCatalogHtml({ title = DEFAULT_TITLE, iconTemplates =
|
|
|
478
533
|
|
|
479
534
|
const dockCss = !spritesBundleAvailable ? "" : `
|
|
480
535
|
.dockwrap { margin: .2rem 0 1.3rem; }
|
|
481
|
-
.dockwrap .panel { background: var(--card); border: 1px solid var(--line); border-top: 2px solid var(--taught); padding: .75rem .85rem; }
|
|
482
|
-
.dockwrap h2 { font-family: ${SERIF_STACK}; font-variant: small-caps; font-size: .82rem; letter-spacing: .04em; color: var(--muted); font-weight: 600; margin: 0 0 .55rem; }
|
|
483
536
|
.dock-note { color: var(--muted); font-size: .8rem; margin: 0 0 .6rem; max-width: 72ch; }
|
|
484
537
|
.docklog { display: flex; flex-direction: column; gap: .4rem; max-height: 240px; overflow-y: auto; margin-bottom: .5rem; }
|
|
485
538
|
.docklog:empty { display: none; margin-bottom: 0; }
|
|
@@ -490,8 +543,8 @@ export function renderSpriteCatalogHtml({ title = DEFAULT_TITLE, iconTemplates =
|
|
|
490
543
|
.docklog .a.grounded { border-left: 2px solid var(--taught); padding-left: .5rem; }
|
|
491
544
|
.dockask { display: flex; align-items: center; gap: .5rem; }
|
|
492
545
|
.dockask .prompt { color: var(--taught); font-size: .78rem; }
|
|
493
|
-
.dockask input { flex: 1; font-family: ${MONO_STACK}; font-size: .82rem; background: var(--
|
|
494
|
-
.dockask input:focus-visible { outline: 2px solid var(--
|
|
546
|
+
.dockask input { flex: 1; font-family: ${MONO_STACK}; font-size: .82rem; background: var(--ai-panel-hi); color: var(--ink); border: 1px solid var(--ai-edge); border-radius: 3px; padding: .38rem .6rem; min-width: 0; }
|
|
547
|
+
.dockask input:focus-visible { outline: 2px solid var(--corpus); outline-offset: 2px; }
|
|
495
548
|
.dockask input:disabled { opacity: .5; }
|
|
496
549
|
.dock-status { font-size: .72rem; color: var(--muted); margin-top: .55rem; }
|
|
497
550
|
`;
|
|
@@ -499,7 +552,7 @@ export function renderSpriteCatalogHtml({ title = DEFAULT_TITLE, iconTemplates =
|
|
|
499
552
|
const dockHtml = !spritesBundleAvailable ? "" : `<div class="dockwrap">
|
|
500
553
|
<section class="panel" aria-label="Ask about the sprite catalog">
|
|
501
554
|
<h2>ask the catalog</h2>
|
|
502
|
-
<p class="dock-note">every answer is read from the sprite templates’ own facts
|
|
555
|
+
<p class="dock-note">every answer is read from the sprite templates’ own facts. A question they can’t ground gets a refusal, never a guess.</p>
|
|
503
556
|
<div class="docklog" id="dockLog" aria-live="polite"></div>
|
|
504
557
|
<form class="dockask" id="dockForm">
|
|
505
558
|
<span class="prompt mono">tmct></span>
|
|
@@ -575,7 +628,7 @@ const SPRITE_CHAT = ${embedJson({ rows: dockRows })};
|
|
|
575
628
|
addDockLine("u", q);
|
|
576
629
|
const local = answerSpriteQuestion(q, SPRITE_CHAT.rows);
|
|
577
630
|
if (local) { addDockLine("a grounded", local.answer); return; }
|
|
578
|
-
if (!session) { addDockLine("a miss", "the engine is still loading
|
|
631
|
+
if (!session) { addDockLine("a miss", "the engine is still loading. Try again in a moment."); return; }
|
|
579
632
|
withLock(async () => {
|
|
580
633
|
const result = await session.turn(q);
|
|
581
634
|
addDockLine(result.record && result.record.miss ? "a miss" : "a", result.answer);
|
|
@@ -594,9 +647,9 @@ const SPRITE_CHAT = ${embedJson({ rows: dockRows })};
|
|
|
594
647
|
await tryLoadWink();
|
|
595
648
|
session = await tmctSprites.createSpriteCatalogSession({ factRows: SPRITE_CHAT.rows });
|
|
596
649
|
dockqEl.disabled = false;
|
|
597
|
-
dockStatusEl.textContent = SPRITE_CHAT.rows.length + " sprite facts on record
|
|
650
|
+
dockStatusEl.textContent = SPRITE_CHAT.rows.length + " sprite facts on record. Ask away, or use a quick question.";
|
|
598
651
|
} catch (err) {
|
|
599
|
-
dockStatusEl.textContent = "the chat engine failed to load
|
|
652
|
+
dockStatusEl.textContent = "the chat engine failed to load. The catalog below still works.";
|
|
600
653
|
console.error("tmct sprites: dock boot failed", err);
|
|
601
654
|
}
|
|
602
655
|
})();
|
|
@@ -614,49 +667,67 @@ ${spritesBundleAvailable ? `<link rel="icon" href="./favicon.svg" type="image/sv
|
|
|
614
667
|
<link rel="apple-touch-icon" href="./apple-touch-icon.png">` : ""}
|
|
615
668
|
<style>
|
|
616
669
|
${THEME_TOKENS_CSS}
|
|
617
|
-
html { background: var(--
|
|
618
|
-
|
|
670
|
+
html { background: var(--ai-canvas); }
|
|
671
|
+
/* Creative-app chrome: a dark document bar in BOTH themes (the app-window
|
|
672
|
+
read), panels and a recessed canvas that follow the theme, and a
|
|
673
|
+
transparency checker under every drawn swatch so each sprite proves it
|
|
674
|
+
holds its own boundary with nothing behind it. */
|
|
675
|
+
:root {
|
|
676
|
+
--ai-bar: #26272B; --ai-bar-ink: #E7E5DF;
|
|
677
|
+
--ai-panel: #EDECE8; --ai-panel-hi: #F4F3F0; --ai-canvas: #DFDEDA; --ai-edge: #C8C6C0;
|
|
678
|
+
--checker: rgba(0, 0, 0, .07);
|
|
679
|
+
}
|
|
680
|
+
@media (prefers-color-scheme: dark) { :root { --ai-panel: #26272B; --ai-panel-hi: #2D2E33; --ai-canvas: #1A1B1F; --ai-edge: #34363C; --checker: rgba(255, 255, 255, .055); } }
|
|
681
|
+
:root[data-theme="dark"] { --ai-panel: #26272B; --ai-panel-hi: #2D2E33; --ai-canvas: #1A1B1F; --ai-edge: #34363C; --checker: rgba(255, 255, 255, .055); }
|
|
682
|
+
:root[data-theme="light"] { --ai-panel: #EDECE8; --ai-panel-hi: #F4F3F0; --ai-canvas: #DFDEDA; --ai-edge: #C8C6C0; --checker: rgba(0, 0, 0, .07); }
|
|
683
|
+
body { margin: 0; background: var(--ai-canvas); color: var(--ink); font-family: ${SERIF_STACK}; font-size: 16px; line-height: 1.5; }
|
|
619
684
|
.mono { font-family: ${MONO_STACK}; }
|
|
620
|
-
main { max-width:
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
.
|
|
685
|
+
main { max-width: 1240px; margin: 0 auto; padding: 0 1.2rem 3rem; }
|
|
686
|
+
button:focus-visible, input:focus-visible, a:focus-visible { outline: 2px solid var(--corpus); outline-offset: 2px; }
|
|
687
|
+
.appbar { display: flex; align-items: flex-end; gap: 1rem; margin: 0 -1.2rem; padding: .6rem 1.2rem 0; background: var(--ai-bar); }
|
|
688
|
+
.appbar h1 { font-family: ${MONO_STACK}; font-size: .84rem; font-weight: 600; letter-spacing: .02em; margin: 0; padding: .32rem .8rem .38rem; background: var(--ai-panel); color: var(--ink); border-radius: 4px 4px 0 0; }
|
|
689
|
+
.appbar .doc-sub { font-family: ${MONO_STACK}; font-size: .66rem; letter-spacing: .07em; text-transform: uppercase; color: color-mix(in srgb, var(--ai-bar-ink) 65%, transparent); padding-bottom: .5rem; }
|
|
690
|
+
.topbar { position: sticky; top: 0; z-index: 2; display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .9rem; background: var(--ai-panel); border-bottom: 1px solid var(--ai-edge); margin: 0 -1.2rem 1.4rem; padding: .5rem 1.2rem; }
|
|
691
|
+
.jump { font-family: ${MONO_STACK}; font-size: .7rem; padding: .2rem .6rem; border: 1px solid var(--ai-edge); border-radius: 3px; background: transparent; color: var(--ink); text-decoration: none; }
|
|
692
|
+
.jump:hover { border-color: var(--corpus); color: var(--corpus); }
|
|
693
|
+
.jump .count { color: var(--muted); }
|
|
694
|
+
.filter { margin-left: auto; display: flex; align-items: center; gap: .4rem; }
|
|
695
|
+
.filter input { font-family: ${MONO_STACK}; font-size: .78rem; background: var(--ai-panel-hi); color: var(--ink); border: 1px solid var(--ai-edge); border-radius: 3px; padding: .3rem .6rem; width: 200px; }
|
|
696
|
+
.filter .n { font-family: ${MONO_STACK}; font-size: .7rem; color: var(--muted); white-space: nowrap; }
|
|
697
|
+
.composer { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin: 1.2rem 0 1.3rem; }
|
|
625
698
|
@media (max-width: 700px) { .composer { grid-template-columns: 1fr; } }
|
|
626
|
-
.composer .panel { background: var(--
|
|
627
|
-
.composer h2 { font-family: ${
|
|
699
|
+
.composer .panel, .dockwrap .panel { background: var(--ai-panel); border: 1px solid var(--ai-edge); border-radius: 4px; padding: .75rem .85rem; min-width: 0; box-shadow: 0 1px 2px rgba(0, 0, 0, .08); }
|
|
700
|
+
.composer h2, .dockwrap h2 { font-family: ${MONO_STACK}; font-size: .66rem; letter-spacing: .09em; text-transform: uppercase; color: var(--muted); font-weight: 600; margin: -.75rem -.85rem .6rem; padding: .42rem .85rem; background: var(--ai-panel-hi); border-bottom: 1px solid var(--ai-edge); border-radius: 4px 4px 0 0; }
|
|
628
701
|
.composeform { display: flex; align-items: center; gap: .5rem; }
|
|
629
702
|
.composeform .prompt { color: var(--taught); font-size: .8rem; white-space: nowrap; }
|
|
630
|
-
.composeform input { flex: 1; font-family: ${MONO_STACK}; font-size: .82rem; background: var(--
|
|
631
|
-
.composeform input:focus-visible { outline: 2px solid var(--taught); outline-offset: 2px; }
|
|
703
|
+
.composeform input { flex: 1; font-family: ${MONO_STACK}; font-size: .82rem; background: var(--ai-panel-hi); color: var(--ink); border: 1px solid var(--ai-edge); border-radius: 3px; padding: .38rem .6rem; min-width: 0; }
|
|
632
704
|
.pills { display: flex; flex-wrap: wrap; gap: .35rem; margin-top: .6rem; }
|
|
633
|
-
.pill { font-family: ${MONO_STACK}; font-size: .7rem; padding: .22rem .55rem; border: 1px solid var(--
|
|
634
|
-
.pill:hover { border-color: var(--
|
|
635
|
-
.pill:focus-visible { outline: 2px solid var(--taught); outline-offset: 2px; }
|
|
705
|
+
.pill { font-family: ${MONO_STACK}; font-size: .7rem; padding: .22rem .55rem; border: 1px solid var(--ai-edge); border-radius: 999px; background: var(--ai-panel-hi); color: var(--ink); cursor: pointer; }
|
|
706
|
+
.pill:hover { border-color: var(--corpus); color: var(--corpus); }
|
|
636
707
|
.scene-frame { min-height: 5.6rem; display: flex; align-items: center; }
|
|
637
708
|
.scene-row { display: flex; flex-wrap: wrap; gap: .8rem; align-items: flex-start; width: 100%; }
|
|
709
|
+
.scene-row:empty { display: none; }
|
|
638
710
|
.scene-card { display: flex; flex-direction: column; align-items: center; width: 74px; }
|
|
639
|
-
.scene-sprite { width: 60px; height: 60px; border-radius:
|
|
640
|
-
.scene-sprite svg { width: 100%; height: 100%; display: block; }
|
|
711
|
+
.scene-sprite { width: 60px; height: 60px; border-radius: 3px; border: 1px solid var(--ai-edge); display: flex; align-items: center; justify-content: center; padding: 6px; box-sizing: border-box; background-image: linear-gradient(45deg, var(--checker) 25%, transparent 25% 75%, var(--checker) 75%), linear-gradient(45deg, var(--checker) 25%, transparent 25% 75%, var(--checker) 75%); background-position: 0 0, 5px 5px; background-size: 10px 10px; background-color: var(--card); }
|
|
712
|
+
.scene-sprite svg { width: 100%; height: 100%; display: block; filter: var(--sprite-pop); }
|
|
641
713
|
.scene-label { font-size: .7rem; text-align: center; color: var(--ink); margin-top: .3rem; line-height: 1.2; }
|
|
642
714
|
.empty-note { font-family: ${MONO_STACK}; font-size: .78rem; color: var(--muted); }
|
|
643
|
-
.
|
|
644
|
-
.
|
|
645
|
-
.
|
|
646
|
-
.jump .count { color: var(--muted); }
|
|
647
|
-
.filter { margin-left: auto; display: flex; align-items: center; gap: .4rem; }
|
|
648
|
-
.filter input { font-family: ${MONO_STACK}; font-size: .8rem; background: var(--card); color: var(--ink); border: 1px solid var(--line); border-radius: 6px; padding: .32rem .6rem; width: 200px; }
|
|
649
|
-
.filter .n { font-family: ${MONO_STACK}; font-size: .7rem; color: var(--muted); white-space: nowrap; }
|
|
650
|
-
.group { margin: 2rem 0; content-visibility: auto; contain-intrinsic-size: 800px; }
|
|
651
|
-
.group h2 { font-size: 1.05rem; margin: 0 0 .2rem; display: flex; align-items: baseline; gap: .5rem; }
|
|
652
|
-
.group h2 .count { font-family: ${MONO_STACK}; font-size: .72rem; color: var(--muted); font-weight: 400; }
|
|
715
|
+
.group { margin: 2.2rem 0; content-visibility: auto; contain-intrinsic-size: 800px; }
|
|
716
|
+
.group h2 { font-family: ${MONO_STACK}; font-size: .78rem; text-transform: uppercase; letter-spacing: .09em; margin: 0 0 .2rem; display: flex; align-items: baseline; gap: .5rem; }
|
|
717
|
+
.group h2 .count { font-size: .7rem; color: var(--muted); font-weight: 400; }
|
|
653
718
|
.section-note { color: var(--muted); font-size: .82rem; margin: 0 0 .8rem; max-width: 68ch; }
|
|
719
|
+
.cluster { margin: 1rem 0 1.5rem; }
|
|
720
|
+
.cluster-head { display: flex; align-items: center; gap: .45rem; font-family: ${MONO_STACK}; font-size: .7rem; font-weight: 600; text-transform: uppercase; letter-spacing: .07em; color: var(--muted); margin: 0 0 .55rem; }
|
|
721
|
+
.cluster-head .count { font-weight: 400; opacity: .75; }
|
|
722
|
+
.cluster-head::after { content: ""; flex: 1; height: 1px; background: var(--ai-edge); }
|
|
723
|
+
.cluster-chip { width: 26px; height: 26px; display: inline-flex; padding: 2px; box-sizing: border-box; border: 1px solid var(--ai-edge); border-radius: 3px; background-image: linear-gradient(45deg, var(--checker) 25%, transparent 25% 75%, var(--checker) 75%), linear-gradient(45deg, var(--checker) 25%, transparent 25% 75%, var(--checker) 75%); background-position: 0 0, 4px 4px; background-size: 8px 8px; background-color: var(--card); }
|
|
724
|
+
.cluster-chip svg { width: 100%; height: 100%; display: block; filter: var(--sprite-pop); }
|
|
654
725
|
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: .7rem; }
|
|
655
|
-
.card { background: var(--card); border: 1px solid var(--
|
|
726
|
+
.card { background: var(--card); border: 1px solid var(--ai-edge); border-radius: 4px; padding: .55rem .7rem .7rem; box-shadow: 0 1px 2px rgba(0, 0, 0, .08); content-visibility: auto; contain-intrinsic-size: 220px; }
|
|
656
727
|
.card[hidden] { display: none; }
|
|
657
|
-
.card-name { font-
|
|
728
|
+
.card-name { font-family: ${MONO_STACK}; font-size: .7rem; font-weight: 600; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); margin: 0 0 .35rem; }
|
|
658
729
|
.chain { font-family: ${MONO_STACK}; font-size: .68rem; color: var(--muted); margin-bottom: .5rem; display: flex; flex-wrap: wrap; align-items: center; gap: .15rem; }
|
|
659
|
-
.chain-link { padding: .04rem .35rem; border: 1px solid var(--
|
|
730
|
+
.chain-link { padding: .04rem .35rem; border: 1px solid var(--ai-edge); border-radius: 99px; }
|
|
660
731
|
.chain-link.own { border-color: var(--taught); color: var(--taught); }
|
|
661
732
|
.chain-arrow { color: var(--muted); opacity: .6; }
|
|
662
733
|
.chain-more { font-style: italic; opacity: .75; }
|
|
@@ -665,21 +736,27 @@ ${THEME_TOKENS_CSS}
|
|
|
665
736
|
.tier-label { font-family: ${MONO_STACK}; font-size: .62rem; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
|
|
666
737
|
.swatches { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .25rem; }
|
|
667
738
|
.swatch { width: 64px; text-align: center; }
|
|
739
|
+
.swatch-img { border: 1px solid var(--ai-edge); border-radius: 3px; box-sizing: border-box; padding: 3px; background-image: linear-gradient(45deg, var(--checker) 25%, transparent 25% 75%, var(--checker) 75%), linear-gradient(45deg, var(--checker) 25%, transparent 25% 75%, var(--checker) 75%); background-position: 0 0, 5px 5px; background-size: 10px 10px; background-color: var(--card); }
|
|
668
740
|
.swatch.large .swatch-img { width: 64px; height: 64px; }
|
|
669
|
-
.swatch.icon .swatch-img { width:
|
|
670
|
-
.swatch-img svg { width: 100%; height: 100%; display: block; }
|
|
741
|
+
.swatch.icon .swatch-img { width: 36px; height: 36px; margin: 0 auto; }
|
|
742
|
+
.swatch-img svg { width: 100%; height: 100%; display: block; filter: var(--sprite-pop); }
|
|
671
743
|
.swatch.fallback { opacity: .55; }
|
|
672
|
-
.swatch.fallback .swatch-img {
|
|
744
|
+
.swatch.fallback .swatch-img { border-style: dashed; }
|
|
673
745
|
.swatch-caption { font-family: ${MONO_STACK}; font-size: .58rem; color: var(--muted); line-height: 1.25; margin-top: .15rem; word-break: break-word; }
|
|
674
746
|
.swatch-treat { display: block; opacity: .8; }
|
|
675
|
-
|
|
676
|
-
|
|
747
|
+
.swatch.cycle .swatch-img { border-color: var(--taught); cursor: pointer; display: block; }
|
|
748
|
+
.swatch.cycle .swatch-img:hover { border-color: var(--corpus); }
|
|
749
|
+
.swatch.cycle .swatch-caption { color: var(--taught); }
|
|
750
|
+
footer.page { max-width: 74ch; margin: 2.5rem 0 0; padding-top: 1rem; border-top: 1px solid var(--ai-edge); font-family: ${MONO_STACK}; font-size: .74rem; color: var(--muted); }
|
|
751
|
+
@media (prefers-reduced-motion: no-preference) { .jump, .swatch, .pill { transition: border-color .12s ease, color .12s ease, opacity .12s ease; } }
|
|
677
752
|
${dockCss}</style>
|
|
678
753
|
</head>
|
|
679
754
|
<body>
|
|
680
755
|
<main>
|
|
681
|
-
<
|
|
682
|
-
|
|
756
|
+
<header class="appbar">
|
|
757
|
+
<h1>Sprites</h1>
|
|
758
|
+
<span class="doc-sub">tmct · the sprite library</span>
|
|
759
|
+
</header>
|
|
683
760
|
<div class="composer">
|
|
684
761
|
<section class="panel compose-panel" aria-label="Describe a scene">
|
|
685
762
|
<h2>describe a scene</h2>
|
|
@@ -702,7 +779,7 @@ ${dockCss}</style>
|
|
|
702
779
|
<h2>the scene</h2>
|
|
703
780
|
<div class="scene-frame" id="sceneFrame">
|
|
704
781
|
<div class="scene-row" id="sceneRow" aria-live="polite"></div>
|
|
705
|
-
<span class="empty-note" id="sceneEmpty">nothing recognized yet
|
|
782
|
+
<span class="empty-note" id="sceneEmpty">nothing recognized yet. Try “a doctor with a hat, and a cabinet”.</span>
|
|
706
783
|
</div>
|
|
707
784
|
</section>
|
|
708
785
|
</div>
|
|
@@ -714,7 +791,7 @@ ${dockCss}</style>
|
|
|
714
791
|
<span class="n mono" id="qcount"></span>
|
|
715
792
|
</div>
|
|
716
793
|
</div>
|
|
717
|
-
${CATALOG_GROUPS.map((g) => sectionHtml(g, entries)).join("")}
|
|
794
|
+
${CATALOG_GROUPS.map((g) => sectionHtml(g, entries, { clusterBy: g.id === GROUP_PERSON || g.id === GROUP_OBJECT ? clusterBy : null })).join("")}
|
|
718
795
|
<footer class="page">${entries.length} classes · ${totalSwatches} swatches · icon tier 44px, sprite tier 400px</footer>
|
|
719
796
|
</main>
|
|
720
797
|
<script>
|
|
@@ -813,6 +890,53 @@ const SPRITE_CATALOG = ${pageData};
|
|
|
813
890
|
renderScene(composeqEl.value);
|
|
814
891
|
});
|
|
815
892
|
renderScene("");
|
|
893
|
+
|
|
894
|
+
// ---- the variant cycle — a card whose large tier carries expression
|
|
895
|
+
// (mgx:feels) or direction (mgx:faces) variants gains one extra swatch
|
|
896
|
+
// that steps through them: plain, then each direction, then each face.
|
|
897
|
+
// Frames are the card's own already-rendered swatches (the same
|
|
898
|
+
// read-the-DOM posture classIndex takes above — never a second embedded
|
|
899
|
+
// copy), and one shared interval steps every cycle in sync. Reduced
|
|
900
|
+
// motion disables the auto-step only; the frame itself is a button, so a
|
|
901
|
+
// click or Enter always advances one frame by hand. Injected AFTER
|
|
902
|
+
// buildClassIndexFromDom ran, so a cycle frame never leaks into the
|
|
903
|
+
// composer's material index.
|
|
904
|
+
const CYCLE_PROPERTIES = ["mgx:faces", "mgx:feels"];
|
|
905
|
+
const cycleSteppers = [];
|
|
906
|
+
for (const card of cards) {
|
|
907
|
+
const swatchRow = card.querySelector('.tier-row[data-tier="large"] .swatches');
|
|
908
|
+
if (!swatchRow) continue;
|
|
909
|
+
const frames = [];
|
|
910
|
+
for (const swatch of swatchRow.querySelectorAll(".swatch")) {
|
|
911
|
+
if (CYCLE_PROPERTIES.indexOf(swatch.dataset.property || "") === -1) continue;
|
|
912
|
+
const img = swatch.querySelector(".swatch-img");
|
|
913
|
+
const label = swatch.querySelector(".swatch-label");
|
|
914
|
+
if (img && label) frames.push({ svg: img.innerHTML, label: label.textContent.trim() });
|
|
915
|
+
}
|
|
916
|
+
if (frames.length < 2) continue;
|
|
917
|
+
const baseImg = swatchRow.querySelector(".swatch.plain .swatch-img, .swatch.fallback .swatch-img");
|
|
918
|
+
if (baseImg) frames.unshift({ svg: baseImg.innerHTML, label: card.dataset.cls });
|
|
919
|
+
const holder = document.createElement("div");
|
|
920
|
+
holder.className = "swatch large cycle";
|
|
921
|
+
holder.innerHTML = '<button type="button" class="swatch-img" aria-label="step through the '
|
|
922
|
+
+ esc(card.dataset.cls) + ' variants"></button><div class="swatch-caption"><span class="swatch-label"></span></div>';
|
|
923
|
+
const frameImgEl = holder.querySelector(".swatch-img");
|
|
924
|
+
const frameLabelEl = holder.querySelector(".swatch-label");
|
|
925
|
+
let frameIndex = 0;
|
|
926
|
+
const showFrame = () => {
|
|
927
|
+
frameImgEl.innerHTML = frames[frameIndex].svg;
|
|
928
|
+
frameLabelEl.textContent = frames[frameIndex].label;
|
|
929
|
+
};
|
|
930
|
+
const stepFrame = () => { frameIndex = (frameIndex + 1) % frames.length; showFrame(); };
|
|
931
|
+
frameImgEl.addEventListener("click", stepFrame);
|
|
932
|
+
showFrame();
|
|
933
|
+
swatchRow.insertBefore(holder, swatchRow.firstChild);
|
|
934
|
+
cycleSteppers.push(stepFrame);
|
|
935
|
+
}
|
|
936
|
+
const reducedMotion = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
937
|
+
if (cycleSteppers.length && !reducedMotion) {
|
|
938
|
+
setInterval(() => { for (const stepFrame of cycleSteppers) stepFrame(); }, 800);
|
|
939
|
+
}
|
|
816
940
|
})();
|
|
817
941
|
</script>
|
|
818
942
|
${dockScripts}
|