@jossuealcala/madre 1.0.1 → 1.0.3
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/ADDITIONAL-TERMS.md +93 -0
- package/CHANGELOG.en.md +2013 -0
- package/CHANGELOG.md +102 -1
- package/CONTRIBUTING.md +1 -1
- package/LICENSE +661 -202
- package/NOTICE +26 -14
- package/README.en.md +241 -0
- package/README.md +21 -12
- package/TRADEMARKS.en.md +50 -0
- package/TRADEMARKS.md +33 -16
- package/bin/madre.cjs +40 -0
- package/bin/madre.mjs +7 -0
- package/docs/INTERNALS.md +6 -0
- package/docs/REFERENCE.md +40 -5
- package/docs/SDK.md +4 -2
- package/docs/en/INTERNALS.md +103 -0
- package/docs/en/REFERENCE.md +365 -0
- package/docs/en/SDK.md +308 -0
- package/docs/en/training/README.md +59 -0
- package/docs/training/README.md +48 -9
- package/docs/training/train.sh +7 -2
- package/package.json +13 -5
- package/public/abduction.js +2 -0
- package/public/app.js +3154 -539
- package/public/attribution.js +114 -0
- package/public/brands.js +23 -6
- package/public/code-tools.js +143 -0
- package/public/editor.js +22 -4
- package/public/es.js +1004 -416
- package/public/hardware.js +50 -0
- package/public/i18n.js +25 -1
- package/public/index.html +18 -11
- package/public/inquiry.js +2 -0
- package/public/loader.js +2 -0
- package/public/macroverso.js +189 -96
- package/public/market-art.js +161 -0
- package/public/market-view.js +529 -0
- package/public/market.js +189 -0
- package/public/neural.js +110 -0
- package/public/nostromo-engine.js +414 -47
- package/public/power.js +100 -0
- package/public/resay.js +11 -0
- package/public/review-ask.js +2 -0
- package/public/routing.js +23 -1
- package/public/salas-mac.css +119 -18
- package/public/salas-mac.html +14 -2
- package/public/salas-mac.js +358 -56
- package/public/salas.css +2 -0
- package/public/salas.html +1 -0
- package/public/salas.js +4 -2
- package/public/styles.css +1793 -77
- package/public/syntax.js +2 -0
- package/public/touch.js +2 -0
- package/public/tour-chapters.js +2 -0
- package/public/tour.js +84 -5
- package/public/troubleshooting.js +24 -17
- package/public/wallpaper.js +275 -0
- package/src/adapters/claude.mjs +2 -0
- package/src/adapters/codex.mjs +2 -0
- package/src/adapters/cursor.mjs +96 -0
- package/src/adapters/gemini.mjs +13 -4
- package/src/adapters/madre.mjs +5 -2
- package/src/adapters/opencode.mjs +7 -3
- package/src/adapters/process.mjs +2 -0
- package/src/asking.mjs +2 -0
- package/src/auth-probe.mjs +145 -12
- package/src/bridge/devices.mjs +2 -0
- package/src/bridge/handoff.mjs +2 -0
- package/src/bridge/host.mjs +2 -0
- package/src/bridge/network.mjs +2 -0
- package/src/bridge/policy.mjs +3 -1
- package/src/bridge/qr.mjs +2 -0
- package/src/bridge/server.mjs +2 -0
- package/src/capabilities.mjs +11 -0
- package/src/chats.mjs +2 -0
- package/src/checkpoint.mjs +18 -1
- package/src/choices.mjs +2 -0
- package/src/cli-args.mjs +2 -0
- package/src/cold.mjs +2 -0
- package/src/commands.mjs +2 -0
- package/src/config.mjs +2 -0
- package/src/conversation-context.mjs +15 -1
- package/src/credentials.mjs +35 -3
- package/src/dataset.mjs +78 -4
- package/src/directives.mjs +2 -0
- package/src/distiller.mjs +2 -0
- package/src/embeddings.mjs +2 -0
- package/src/enlaces.mjs +214 -6
- package/src/event-store.mjs +2 -0
- package/src/exam.mjs +6 -4
- package/src/extensions.mjs +13 -3
- package/src/eyecat-watch.mjs +2 -0
- package/src/eyecat.mjs +2 -0
- package/src/file-ops.mjs +2 -0
- package/src/files.mjs +32 -0
- package/src/github.mjs +2 -0
- package/src/heartbeat.mjs +2 -0
- package/src/i18n.mjs +31 -1
- package/src/image-studio.mjs +2 -0
- package/src/json-file.mjs +2 -0
- package/src/launch.mjs +2 -0
- package/src/lease.mjs +5 -1
- package/src/lobby-work.mjs +2 -0
- package/src/local-ai.mjs +2 -0
- package/src/looks.mjs +127 -2
- package/src/market-key.mjs +8 -0
- package/src/market-remote.mjs +239 -0
- package/src/maturity.mjs +3 -1
- package/src/mcp/connect.mjs +345 -0
- package/src/mcp/github-server.mjs +2 -0
- package/src/mcp/image-server.mjs +2 -0
- package/src/mcp/memory-server.mjs +2 -0
- package/src/mcp/proxy.mjs +153 -0
- package/src/mcp/smtp-server.mjs +2 -0
- package/src/memory-dashboard.mjs +2 -0
- package/src/memory-tools.mjs +2 -0
- package/src/memory.mjs +2 -0
- package/src/models.mjs +49 -3
- package/src/module-scope.mjs +68 -0
- package/src/modules/ahp.mjs +104 -7
- package/src/modules/ash-policy.mjs +2 -0
- package/src/modules/ash.mjs +3 -1
- package/src/modules/bishop-exam.mjs +122 -0
- package/src/modules/bishop-plan.mjs +5 -3
- package/src/modules/bishop.mjs +84 -23
- package/src/modules/choices.mjs +2 -0
- package/src/modules/correo.mjs +2 -0
- package/src/modules/enlaces.mjs +89 -6
- package/src/modules/git-pulse.mjs +2 -0
- package/src/modules/github.mjs +2 -0
- package/src/modules/helpers.mjs +2 -0
- package/src/modules/image-studio.mjs +13 -2
- package/src/modules/index.mjs +4 -1
- package/src/modules/intents-calibration.mjs +2 -0
- package/src/modules/intents-desk.mjs +4 -2
- package/src/modules/intents-observe.mjs +2 -0
- package/src/modules/intents-policy.mjs +4 -1
- package/src/modules/intents-try.mjs +2 -0
- package/src/modules/intents.mjs +11 -7
- package/src/modules/local-models.mjs +124 -0
- package/src/modules/mcp-connect.mjs +293 -0
- package/src/modules/ollama.mjs +15 -4
- package/src/modules/playwright.mjs +88 -15
- package/src/modules/ripley.mjs +2 -0
- package/src/modules/runs.mjs +5 -3
- package/src/modules/sdk.mjs +63 -9
- package/src/modules/updates.mjs +2 -0
- package/src/mother.mjs +2 -0
- package/src/npm-launch.mjs +18 -0
- package/src/ollama.mjs +8 -5
- package/src/outbound.mjs +16 -6
- package/src/override.mjs +2 -0
- package/src/ports.mjs +6 -2
- package/src/prices.mjs +2 -0
- package/src/privacy.mjs +2 -0
- package/src/quota-monitor.mjs +2 -0
- package/src/quota-sources.mjs +2 -0
- package/src/request-guard.mjs +2 -0
- package/src/review-ask.mjs +2 -0
- package/src/room/archivist.mjs +2 -0
- package/src/room/attachments.mjs +2 -0
- package/src/room/budget.mjs +2 -0
- package/src/room/context.mjs +2 -0
- package/src/room/control.mjs +7 -1
- package/src/room/copilot.mjs +76 -0
- package/src/room/economy.mjs +2 -0
- package/src/room/escalation.mjs +2 -0
- package/src/room/ghost.mjs +2 -0
- package/src/room/guard.mjs +2 -0
- package/src/room/prompt.mjs +25 -5
- package/src/room/returns.mjs +82 -0
- package/src/room/runs.mjs +4 -0
- package/src/room/vectors.mjs +2 -0
- package/src/room.mjs +223 -35
- package/src/router.mjs +3 -1
- package/src/runs.mjs +6 -1
- package/src/runtime-detection.mjs +11 -0
- package/src/salas.mjs +28 -0
- package/src/sentinel-errors.mjs +2 -0
- package/src/server-sentinel.mjs +2 -0
- package/src/server.mjs +757 -81
- package/src/setup.mjs +3 -1
- package/src/smtp.mjs +2 -0
- package/src/tour.mjs +2 -0
- package/src/updates.mjs +2 -0
- package/src/usage-sentinel.mjs +2 -0
- package/src/vault.mjs +2 -0
- package/src/verdict.mjs +3 -1
- package/src/whats-new.mjs +2 -0
package/public/macroverso.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
|
|
1
3
|
// THE MACROVERSE: every room on this Mac as a NOSTROMO in open space (docs/MACROVERSO.md, phase 3a).
|
|
2
4
|
// It is entered from SALAS and only on a large screen; on a phone SALAS is the list and nothing more.
|
|
3
5
|
//
|
|
@@ -22,7 +24,8 @@
|
|
|
22
24
|
// for less motion gets one still frame redrawn when something changes. No line joins two universes
|
|
23
25
|
// until a link exists (phase 3c).
|
|
24
26
|
|
|
25
|
-
import { COLLAPSED, FOCUS_VEIL, MEMORY_COLORS, PITCH_LIMIT, TURN_PER_PIXEL, alongCurve, collapsedChip, createNostromo, currentAlong, dwarfChip, hexAlpha, hexMix, magmaMap, texturesReady } from './nostromo-engine.js';
|
|
27
|
+
import { COLLAPSED, FOCUS_VEIL, MEMORY_COLORS, NEURAL_AXON, NEURAL_CROSSFADE, NEURAL_IGNITION, PITCH_LIMIT, TURN_PER_PIXEL, alongCurve, collapsedChip, createNostromo, currentAlong, drawNeuralBulb, dwarfChip, hexAlpha, hexMix, magmaMap, neuralGround, neuronGlow, texturesReady } from './nostromo-engine.js';
|
|
28
|
+
import { createStrain, inviteNeural, neuralKey, paintNeuralKey } from './neural.js';
|
|
26
29
|
|
|
27
30
|
const KINDS = Object.keys(MEMORY_COLORS);
|
|
28
31
|
const TILT = 0.42; // how flat the orbits read: the plane seen a little from above
|
|
@@ -327,7 +330,7 @@ function farMemories(data) {
|
|
|
327
330
|
|
|
328
331
|
// One universe painted once: its scene placed, settled and drawn into a sprite by the engine. A
|
|
329
332
|
// room that is asleep, sealed, outdated or unreachable is MOTHER alone: cold, or caged.
|
|
330
|
-
function paintUniverse(one, dpr, shown = null) {
|
|
333
|
+
function paintUniverse(one, dpr, shown = null, neural = false) {
|
|
331
334
|
const side = Math.min(SPRITE_MAX, Math.round(Math.max(SIDE_MIN, shown ?? one.reach * 2) * dpr));
|
|
332
335
|
const css = side / dpr;
|
|
333
336
|
const canvas = canvasOf(side, side);
|
|
@@ -343,6 +346,8 @@ function paintUniverse(one, dpr, shown = null) {
|
|
|
343
346
|
}
|
|
344
347
|
scene.cam = { x: 0, y: 0, scale: (css / 2 - 4) / BODY_REACH, manual: true };
|
|
345
348
|
if (data?.sealed) scene.cage = { at: 0, closed: true };
|
|
349
|
+
// Neural mode (public/neural.js): the universe drawn as a cluster of neurons round its bulb.
|
|
350
|
+
scene.neural = neural;
|
|
346
351
|
engine.projectNostromo();
|
|
347
352
|
engine.drawNostromo(0.7);
|
|
348
353
|
if (!one.room.open) {
|
|
@@ -406,7 +411,7 @@ function paintStars(seed, count, biggest, dpr) {
|
|
|
406
411
|
return { canvas, side };
|
|
407
412
|
}
|
|
408
413
|
|
|
409
|
-
export function openMacroverso({ host, rooms, here, enlaces = [], accent = '#9bff66', accentInk = '#0a1405', bishop = null, words, actions }) {
|
|
414
|
+
export function openMacroverso({ host, rooms, here, enlaces = [], accent = '#9bff66', accentInk = '#0a1405', bishop = null, words, actions, neural: neuralStart = false }) {
|
|
410
415
|
const reduced = reducedMotion();
|
|
411
416
|
const layer = document.createElement('section');
|
|
412
417
|
layer.className = 'mv';
|
|
@@ -452,12 +457,23 @@ export function openMacroverso({ host, rooms, here, enlaces = [], accent = '#9bf
|
|
|
452
457
|
recenter.className = 'mother-close mv-recenter';
|
|
453
458
|
recenter.textContent = words.recenter ?? 'RECENTER';
|
|
454
459
|
recenter.hidden = true;
|
|
460
|
+
// Leaving: the room's close, a sunk track and the cross that turns (styles.css, .x-close), as
|
|
461
|
+
// every window closes; what it does is in its name.
|
|
462
|
+
const backTrack = document.createElement('span');
|
|
463
|
+
backTrack.className = 'surface-modes';
|
|
455
464
|
const back = document.createElement('button');
|
|
456
465
|
back.type = 'button';
|
|
457
|
-
back.className = '
|
|
458
|
-
back.
|
|
459
|
-
back.
|
|
460
|
-
|
|
466
|
+
back.className = 'surface-mode close x-close x-only mv-back';
|
|
467
|
+
back.setAttribute('aria-label', words.back);
|
|
468
|
+
back.title = words.back;
|
|
469
|
+
back.insertAdjacentHTML('beforeend', '<span class="x-icon"><svg viewBox="0 0 20 20" focusable="false" aria-hidden="true"><path d="M5.5 5.5l9 9M14.5 5.5l-9 9" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></span>'); // trusted: our own SVG
|
|
470
|
+
backTrack.append(back);
|
|
471
|
+
// Neural mode (public/neural.js): the same space drawn as neurons, chosen here or in a project.
|
|
472
|
+
const neuralButton = document.createElement('button');
|
|
473
|
+
neuralButton.type = 'button';
|
|
474
|
+
neuralButton.className = 'mother-close neural-key';
|
|
475
|
+
neuralKey(neuralButton);
|
|
476
|
+
keys.append(neuralButton, recenter, backTrack);
|
|
461
477
|
head.append(title, sub, legend, keys);
|
|
462
478
|
const card = document.createElement('aside');
|
|
463
479
|
card.className = 'mv-card';
|
|
@@ -486,6 +502,9 @@ export function openMacroverso({ host, rooms, here, enlaces = [], accent = '#9bf
|
|
|
486
502
|
let raf = 0;
|
|
487
503
|
let last = performance.now();
|
|
488
504
|
let clock = 0;
|
|
505
|
+
let neural = Boolean(neuralStart); // drawn as neurons (setNeuralMode)
|
|
506
|
+
let neuralSince = null; // when it came on, for the light running out from the centre
|
|
507
|
+
let fade = null; // the space as it was, fading over the new drawing
|
|
489
508
|
let queued = false;
|
|
490
509
|
let resizeTimer = 0;
|
|
491
510
|
const drag = { active: false, moved: false, turning: false, link: null, x: 0, y: 0, x0: 0, y0: 0 };
|
|
@@ -495,6 +514,36 @@ export function openMacroverso({ host, rooms, here, enlaces = [], accent = '#9bf
|
|
|
495
514
|
let orb = null;
|
|
496
515
|
const gaze = { x: 0, y: 0, open: 0, at: 0, jumpAt: 0, jx: 0, jy: 0, blinkAt: -1, nextBlink: 3 + Math.random() * 4, twice: false };
|
|
497
516
|
let pointer = null;
|
|
517
|
+
// Neural mode, switched from its key or because a project switched it: the space as it was fades
|
|
518
|
+
// over the new drawing, the light runs out from the centre, every universe is drawn again its new
|
|
519
|
+
// way (a sleeping one, painted once, is painted again), and the choice is kept (actions.neural).
|
|
520
|
+
paintNeuralKey(neuralButton, neural);
|
|
521
|
+
function setNeuralMode(on) {
|
|
522
|
+
if (neural === on) return;
|
|
523
|
+
if (!reduced && canvas.width && canvas.height) {
|
|
524
|
+
const held = document.createElement('canvas');
|
|
525
|
+
held.width = canvas.width;
|
|
526
|
+
held.height = canvas.height;
|
|
527
|
+
held.getContext('2d').drawImage(canvas, 0, 0);
|
|
528
|
+
fade = { canvas: held, at: clock };
|
|
529
|
+
}
|
|
530
|
+
neural = on;
|
|
531
|
+
neuralSince = on && !reduced ? clock : null;
|
|
532
|
+
paintNeuralKey(neuralButton, on);
|
|
533
|
+
layer.querySelector('.neural-invite')?.remove();
|
|
534
|
+
const now = performance.now() / 1000;
|
|
535
|
+
for (const one of universes) {
|
|
536
|
+
if (!one.sprite?.scene) continue;
|
|
537
|
+
if (one.room.open && !reducedMotion()) one.sprite.engine.setNeural(on, now);
|
|
538
|
+
else one.dirty = true;
|
|
539
|
+
}
|
|
540
|
+
live?.engine.setNeural(on, now);
|
|
541
|
+
actions.neural?.(on);
|
|
542
|
+
redraw();
|
|
543
|
+
}
|
|
544
|
+
neuralButton.addEventListener('click', () => setNeuralMode(!neural));
|
|
545
|
+
// When this machine does not give the space its frames, the key invites to try the mode.
|
|
546
|
+
const strain = createStrain({ onStrain: () => { if (!neural) inviteNeural({ key: neuralButton, host: layer, map: 'projects', onTry: () => setNeuralMode(true) }); } });
|
|
498
547
|
const motes = Array.from({ length: ORB_MOTES }, (_, i) => { const rnd = random(hash(`mote${i}`)); return { phase: rnd() * Math.PI * 2, reach: 1.3 + rnd() * 0.65, speed: 0.12 + rnd() * 0.2, tilt: (rnd() - 0.5) * 0.5, size: 0.35 + rnd() * 0.6 }; });
|
|
499
548
|
|
|
500
549
|
function layout(all) {
|
|
@@ -593,7 +642,7 @@ export function openMacroverso({ host, rooms, here, enlaces = [], accent = '#9bf
|
|
|
593
642
|
const next = [focus, ...universes].find((one) => one && one.dirty);
|
|
594
643
|
if (!next) return false;
|
|
595
644
|
if (!next.data) { if (!next.loading?.data) void load(next); return false; }
|
|
596
|
-
next.sprite = paintUniverse(next, view.dpr, next.shown ?? null);
|
|
645
|
+
next.sprite = paintUniverse(next, view.dpr, next.shown ?? null, neural);
|
|
597
646
|
next.dirty = false;
|
|
598
647
|
return true;
|
|
599
648
|
}
|
|
@@ -648,6 +697,7 @@ export function openMacroverso({ host, rooms, here, enlaces = [], accent = '#9bf
|
|
|
648
697
|
}
|
|
649
698
|
engine.placeNostromo();
|
|
650
699
|
scene.words = words;
|
|
700
|
+
scene.neural = neural;
|
|
651
701
|
if (data.sealed) scene.cage = { at: 0, closed: true };
|
|
652
702
|
// The pointer reaches it through its own canvas, which is nowhere on the page: it answers
|
|
653
703
|
// from the origin, and the pointer is moved into its frame before it is asked.
|
|
@@ -711,7 +761,9 @@ export function openMacroverso({ host, rooms, here, enlaces = [], accent = '#9bf
|
|
|
711
761
|
}
|
|
712
762
|
|
|
713
763
|
function frame(now) {
|
|
764
|
+
const started = performance.now();
|
|
714
765
|
advance(now);
|
|
766
|
+
strain.frame(performance.now() - started, now);
|
|
715
767
|
raf = reduced && !camera.to ? 0 : requestAnimationFrame(frame);
|
|
716
768
|
}
|
|
717
769
|
// One frame's worth of everything, without asking for the next: what frame() does, and what
|
|
@@ -764,10 +816,13 @@ export function openMacroverso({ host, rooms, here, enlaces = [], accent = '#9bf
|
|
|
764
816
|
ctx.setTransform(view.dpr, 0, 0, view.dpr, 0, 0);
|
|
765
817
|
ctx.globalCompositeOperation = 'source-over';
|
|
766
818
|
ctx.globalAlpha = 1;
|
|
767
|
-
|
|
819
|
+
const beat = reduced ? 0.4 : 0.5 + 0.5 * Math.sin(clock * 5.46);
|
|
820
|
+
// Neural mode: no nebula and no stars, the dark the neurons stand on.
|
|
821
|
+
if (neural) neuralGround(ctx, view.w, view.h, beat);
|
|
822
|
+
else if (nebula) ctx.drawImage(nebula, 0, 0, view.w, view.h);
|
|
768
823
|
// Stars: the near ones move with the camera, the far ones barely. The tiles repeat under any
|
|
769
824
|
// movement, so the space has no edge.
|
|
770
|
-
stars.forEach((tile, depth) => {
|
|
825
|
+
if (!neural) stars.forEach((tile, depth) => {
|
|
771
826
|
const speed = [0.04, 0.1, 0.22][depth] * camera.zoom;
|
|
772
827
|
const ox = (((-camera.x * speed - clock * (reduced ? 0 : 1.5 * (depth + 1))) % tile.side) + tile.side) % tile.side;
|
|
773
828
|
const oy = (((-camera.y * speed) % tile.side) + tile.side) % tile.side;
|
|
@@ -781,14 +836,13 @@ export function openMacroverso({ host, rooms, here, enlaces = [], accent = '#9bf
|
|
|
781
836
|
const span = Math.min(view.w, view.h * 1.7);
|
|
782
837
|
ctx.lineWidth = 1;
|
|
783
838
|
for (const orbit of [INNER, OUTER]) {
|
|
784
|
-
ctx.strokeStyle = 'rgba(242, 241, 238, .11)';
|
|
839
|
+
ctx.strokeStyle = neural ? `rgba(${NEURAL_AXON}, .1)` : 'rgba(242, 241, 238, .11)';
|
|
785
840
|
ctx.setLineDash(orbit === OUTER ? [3, 7] : []);
|
|
786
841
|
ctx.beginPath();
|
|
787
842
|
ctx.ellipse(centre.x, centre.y, span * orbit * camera.zoom, span * orbit * TILT * camera.zoom, 0, 0, Math.PI * 2);
|
|
788
843
|
ctx.stroke();
|
|
789
844
|
}
|
|
790
845
|
ctx.setLineDash([]);
|
|
791
|
-
const beat = reduced ? 0.4 : 0.5 + 0.5 * Math.sin(clock * 5.46);
|
|
792
846
|
drawVeins(ctx);
|
|
793
847
|
drawCentre(ctx, beat);
|
|
794
848
|
|
|
@@ -805,6 +859,12 @@ export function openMacroverso({ host, rooms, here, enlaces = [], accent = '#9bf
|
|
|
805
859
|
if (veil > 0.004) { ctx.fillStyle = `rgba(2, 1, 4, ${veil})`; ctx.fillRect(0, 0, view.w, view.h); }
|
|
806
860
|
const top = order.find((entry) => entry.one === lifted);
|
|
807
861
|
if (top) drawUniverse(ctx, top.one, top.at, beat);
|
|
862
|
+
// Changing the drawing: the space as it was fades over the new one.
|
|
863
|
+
if (fade) {
|
|
864
|
+
const k = (clock - fade.at) / NEURAL_CROSSFADE;
|
|
865
|
+
if (k >= 1 || k < 0) fade = null;
|
|
866
|
+
else { ctx.save(); ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.globalAlpha = 1 - k * (2 - k); ctx.drawImage(fade.canvas, 0, 0); ctx.restore(); }
|
|
867
|
+
}
|
|
808
868
|
}
|
|
809
869
|
|
|
810
870
|
// One universe on screen: MOTHER's warmth, the live scene or its own living canvas, its rings
|
|
@@ -828,7 +888,7 @@ export function openMacroverso({ host, rooms, here, enlaces = [], accent = '#9bf
|
|
|
828
888
|
const away = at.light * at.light * (0.55 + 0.45 * Math.min(1, camera.zoom));
|
|
829
889
|
const reach = r * (2.3 + 0.2 * beat);
|
|
830
890
|
const halo = ctx.createRadialGradient(p.x, p.y, r * 0.6, p.x, p.y, reach);
|
|
831
|
-
halo.addColorStop(0, awake ? `rgba(226, 40, 22, ${(0.16 + 0.05 * beat) * away})` : `rgba(120, 150, 200, ${0.08 * away})`);
|
|
891
|
+
halo.addColorStop(0, neural ? (awake ? `rgba(${NEURAL_AXON}, ${(0.12 + 0.04 * beat) * away})` : `rgba(120, 150, 200, ${0.05 * away})`) : awake ? `rgba(226, 40, 22, ${(0.16 + 0.05 * beat) * away})` : `rgba(120, 150, 200, ${0.08 * away})`);
|
|
832
892
|
halo.addColorStop(1, 'rgba(0, 0, 0, 0)');
|
|
833
893
|
ctx.fillStyle = halo;
|
|
834
894
|
ctx.beginPath(); ctx.arc(p.x, p.y, reach, 0, Math.PI * 2); ctx.fill();
|
|
@@ -842,8 +902,8 @@ export function openMacroverso({ host, rooms, here, enlaces = [], accent = '#9bf
|
|
|
842
902
|
} else {
|
|
843
903
|
// Not read yet: an ember where it will be.
|
|
844
904
|
const ember = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, r);
|
|
845
|
-
ember.addColorStop(0, awake ? '#ff7a3a' : '#56637a');
|
|
846
|
-
ember.addColorStop(1, awake ? 'rgba(168, 23, 12, 0)' : 'rgba(27, 33, 48, 0)');
|
|
905
|
+
ember.addColorStop(0, neural ? (awake ? '#9fdcff' : '#56637a') : awake ? '#ff7a3a' : '#56637a');
|
|
906
|
+
ember.addColorStop(1, neural ? 'rgba(20, 60, 110, 0)' : awake ? 'rgba(168, 23, 12, 0)' : 'rgba(27, 33, 48, 0)');
|
|
847
907
|
ctx.fillStyle = ember;
|
|
848
908
|
ctx.beginPath(); ctx.arc(p.x, p.y, r, 0, Math.PI * 2); ctx.fill();
|
|
849
909
|
}
|
|
@@ -897,85 +957,91 @@ export function openMacroverso({ host, rooms, here, enlaces = [], accent = '#9bf
|
|
|
897
957
|
const centre = toScreen(0, 0);
|
|
898
958
|
const p = { x: centre.x, y: centre.y + float * Math.max(0.6, camera.zoom) };
|
|
899
959
|
const r = 30 * clamp(camera.zoom, 0.75, 1.8);
|
|
900
|
-
core.screen = { x: p.x, y: p.y, r: r * 1.3 };
|
|
901
|
-
//
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
gaze.
|
|
918
|
-
gaze.
|
|
919
|
-
gaze.
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
orb.paintedAt = clock;
|
|
926
|
-
}
|
|
927
|
-
const life = awake ? 1 : 0.5;
|
|
928
|
-
// The atmosphere: violet into blue, breathing slowly.
|
|
929
|
-
ctx.globalCompositeOperation = 'lighter';
|
|
930
|
-
const air = ctx.createRadialGradient(p.x, p.y, r * 0.8, p.x, p.y, r * (2.7 + 0.12 * beat));
|
|
931
|
-
air.addColorStop(0, `rgba(122, 58, 238, ${0.3 * life})`);
|
|
932
|
-
air.addColorStop(0.45, `rgba(72, 112, 255, ${0.12 * life})`);
|
|
933
|
-
air.addColorStop(1, 'rgba(40, 20, 120, 0)');
|
|
934
|
-
ctx.fillStyle = air;
|
|
935
|
-
ctx.beginPath(); ctx.arc(p.x, p.y, r * (2.7 + 0.12 * beat), 0, Math.PI * 2); ctx.fill();
|
|
936
|
-
// The wisps, turning two ways at once, as gas does.
|
|
937
|
-
const span = r * orb.reach * 2;
|
|
938
|
-
for (const [speed, alpha] of [[0.05, 0.8], [-0.033, 0.5]]) {
|
|
939
|
-
ctx.save();
|
|
940
|
-
ctx.translate(p.x, p.y);
|
|
941
|
-
ctx.rotate(reduced ? 0 : clock * speed);
|
|
942
|
-
ctx.globalAlpha = alpha * life;
|
|
943
|
-
ctx.drawImage(orb.wisps, -span / 2, -span / 2, span, span);
|
|
944
|
-
ctx.restore();
|
|
945
|
-
}
|
|
946
|
-
// The motes behind it, then the sphere, then the motes in front.
|
|
947
|
-
const drawMotes = (front) => {
|
|
948
|
-
for (const mote of motes) {
|
|
949
|
-
const a = mote.phase + (reduced ? 0 : clock * mote.speed);
|
|
950
|
-
const ahead = Math.sin(a) > 0;
|
|
951
|
-
if (ahead !== front) continue;
|
|
952
|
-
const mx = p.x + Math.cos(a) * r * mote.reach;
|
|
953
|
-
const my = p.y + Math.sin(a) * r * mote.reach * 0.3 + mote.tilt * r * Math.cos(a);
|
|
954
|
-
const size = mote.size * (ahead ? 1 : 0.7) * clamp(camera.zoom, 0.7, 1.4);
|
|
955
|
-
const glow = ctx.createRadialGradient(mx, my, 0, mx, my, size * 3);
|
|
956
|
-
glow.addColorStop(0, `rgba(214, 206, 255, ${(ahead ? 0.6 : 0.25) * life})`);
|
|
957
|
-
glow.addColorStop(1, 'rgba(120, 140, 255, 0)');
|
|
958
|
-
ctx.fillStyle = glow;
|
|
959
|
-
ctx.beginPath(); ctx.arc(mx, my, size * 3, 0, Math.PI * 2); ctx.fill();
|
|
960
|
+
core.screen = { x: p.x, y: p.y, r: r * (neural ? 1.8 : 1.3) };
|
|
961
|
+
// Neural mode: the centre of the brain, its bulb, in the Coordinator's violet; no eye, no gas.
|
|
962
|
+
if (neural) {
|
|
963
|
+
const ignite = neuralSince === null ? 1 : clamp((clock - neuralSince) / NEURAL_IGNITION, 0, 1);
|
|
964
|
+
drawNeuralBulb(ctx, p.x, p.y, r * 1.6, clock, { beat, light: awake ? 1 : 0.6, ignite, tone: 'bishop', still: reduced });
|
|
965
|
+
} else {
|
|
966
|
+
// Where it looks: at the person (the pointer) first; at the universe being carried, or the
|
|
967
|
+
// chosen one, when the pointer is away; wandering slowly when nothing is there. A living eye
|
|
968
|
+
// never holds still: small jumps every second or so.
|
|
969
|
+
const seen = linking?.from ?? (focus && !focus.centre ? focus : null);
|
|
970
|
+
let target = pointer ?? seen?.screen ?? null;
|
|
971
|
+
if (!target && !reduced) target = { x: p.x + Math.cos(clock * 0.21) * 160, y: p.y + Math.sin(clock * 0.34) * 90 };
|
|
972
|
+
if (!reduced && clock > gaze.jumpAt) { gaze.jumpAt = clock + 0.6 + Math.random() * 1.2; gaze.jx = (Math.random() - 0.5) * 0.07; gaze.jy = (Math.random() - 0.5) * 0.05; }
|
|
973
|
+
const toward = target ? { x: target.x - p.x, y: target.y - p.y } : { x: 0, y: 0 };
|
|
974
|
+
const far = Math.hypot(toward.x, toward.y) + 180;
|
|
975
|
+
const step = reduced ? 1 : clamp((clock - gaze.at) * 6, 0, 1);
|
|
976
|
+
gaze.at = clock;
|
|
977
|
+
gaze.x += (clamp(toward.x / far + gaze.jx, -1, 1) - gaze.x) * step;
|
|
978
|
+
gaze.y += (clamp(toward.y / far + gaze.jy, -1, 1) - gaze.y) * step;
|
|
979
|
+
gaze.open += ((linking?.over === core ? 1 : hover === core ? 0.45 : 0) - gaze.open) * step;
|
|
980
|
+
// A blink now and then, sometimes two; the lids close fast and open a little slower.
|
|
981
|
+
if (!reduced && awake && clock > gaze.nextBlink) {
|
|
982
|
+
gaze.blinkAt = clock;
|
|
983
|
+
gaze.twice = !gaze.twice && Math.random() < 0.25;
|
|
984
|
+
gaze.nextBlink = clock + (gaze.twice ? 0.32 : 4 + Math.random() * 6);
|
|
960
985
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
986
|
+
const since = clock - gaze.blinkAt;
|
|
987
|
+
const lid = gaze.blinkAt < 0 || since > 0.26 ? 0 : since < 0.09 ? since / 0.09 : 1 - (since - 0.09) / 0.17;
|
|
988
|
+
if (clock - orb.paintedAt >= 1 / ORB_FPS || orb.paintedAt < 0 || lid > 0) {
|
|
989
|
+
orb.paint(reduced ? 0 : clock, { x: gaze.x, y: gaze.y, open: gaze.open, lid: clamp(lid, 0, 1) * 0.94, asleep: !awake });
|
|
990
|
+
orb.paintedAt = clock;
|
|
991
|
+
}
|
|
992
|
+
const life = awake ? 1 : 0.5;
|
|
993
|
+
// The atmosphere: violet into blue, breathing slowly.
|
|
994
|
+
ctx.globalCompositeOperation = 'lighter';
|
|
995
|
+
const air = ctx.createRadialGradient(p.x, p.y, r * 0.8, p.x, p.y, r * (2.7 + 0.12 * beat));
|
|
996
|
+
air.addColorStop(0, `rgba(122, 58, 238, ${0.3 * life})`);
|
|
997
|
+
air.addColorStop(0.45, `rgba(72, 112, 255, ${0.12 * life})`);
|
|
998
|
+
air.addColorStop(1, 'rgba(40, 20, 120, 0)');
|
|
999
|
+
ctx.fillStyle = air;
|
|
1000
|
+
ctx.beginPath(); ctx.arc(p.x, p.y, r * (2.7 + 0.12 * beat), 0, Math.PI * 2); ctx.fill();
|
|
1001
|
+
// The wisps, turning two ways at once, as gas does.
|
|
1002
|
+
const span = r * orb.reach * 2;
|
|
1003
|
+
for (const [speed, alpha] of [[0.05, 0.8], [-0.033, 0.5]]) {
|
|
1004
|
+
ctx.save();
|
|
1005
|
+
ctx.translate(p.x, p.y);
|
|
1006
|
+
ctx.rotate(reduced ? 0 : clock * speed);
|
|
1007
|
+
ctx.globalAlpha = alpha * life;
|
|
1008
|
+
ctx.drawImage(orb.wisps, -span / 2, -span / 2, span, span);
|
|
1009
|
+
ctx.restore();
|
|
1010
|
+
}
|
|
1011
|
+
// The motes behind it, then the sphere, then the motes in front.
|
|
1012
|
+
const drawMotes = (front) => {
|
|
1013
|
+
for (const mote of motes) {
|
|
1014
|
+
const a = mote.phase + (reduced ? 0 : clock * mote.speed);
|
|
1015
|
+
const ahead = Math.sin(a) > 0;
|
|
1016
|
+
if (ahead !== front) continue;
|
|
1017
|
+
const mx = p.x + Math.cos(a) * r * mote.reach;
|
|
1018
|
+
const my = p.y + Math.sin(a) * r * mote.reach * 0.3 + mote.tilt * r * Math.cos(a);
|
|
1019
|
+
const size = mote.size * (ahead ? 1 : 0.7) * clamp(camera.zoom, 0.7, 1.4);
|
|
1020
|
+
const glow = ctx.createRadialGradient(mx, my, 0, mx, my, size * 3);
|
|
1021
|
+
glow.addColorStop(0, `rgba(214, 206, 255, ${(ahead ? 0.6 : 0.25) * life})`);
|
|
1022
|
+
glow.addColorStop(1, 'rgba(120, 140, 255, 0)');
|
|
1023
|
+
ctx.fillStyle = glow;
|
|
1024
|
+
ctx.beginPath(); ctx.arc(mx, my, size * 3, 0, Math.PI * 2); ctx.fill();
|
|
1025
|
+
}
|
|
1026
|
+
};
|
|
1027
|
+
drawMotes(false);
|
|
1028
|
+
ctx.globalCompositeOperation = 'source-over';
|
|
1029
|
+
ctx.globalAlpha = awake ? 1 : 0.8;
|
|
1030
|
+
ctx.drawImage(orb.canvas, p.x - r, p.y - r, r * 2, r * 2);
|
|
1031
|
+
ctx.globalAlpha = 1;
|
|
1032
|
+
// Its own thin atmosphere, brighter on the side the light comes from.
|
|
1033
|
+
ctx.globalCompositeOperation = 'lighter';
|
|
1034
|
+
const limb = ctx.createRadialGradient(p.x - r * 0.1, p.y - r * 0.12, r * 0.9, p.x, p.y, r * 1.14);
|
|
1035
|
+
limb.addColorStop(0, 'rgba(120, 150, 255, 0)');
|
|
1036
|
+
limb.addColorStop(0.55, `rgba(130, 160, 255, ${0.28 * life})`);
|
|
1037
|
+
limb.addColorStop(1, 'rgba(90, 60, 220, 0)');
|
|
1038
|
+
ctx.fillStyle = limb;
|
|
1039
|
+
ctx.beginPath(); ctx.arc(p.x, p.y, r * 1.14, 0, Math.PI * 2); ctx.fill();
|
|
1040
|
+
ctx.globalCompositeOperation = 'source-over';
|
|
1041
|
+
ctx.globalCompositeOperation = 'lighter';
|
|
1042
|
+
drawMotes(true);
|
|
1043
|
+
ctx.globalCompositeOperation = 'source-over';
|
|
1044
|
+
}
|
|
979
1045
|
// A ring in the accent while a universe is carried onto it, and while it waits for the person;
|
|
980
1046
|
// a white one while it is pointed at or chosen.
|
|
981
1047
|
const target2 = linking?.over === core;
|
|
@@ -983,14 +1049,15 @@ export function openMacroverso({ host, rooms, here, enlaces = [], accent = '#9bf
|
|
|
983
1049
|
ctx.strokeStyle = accent;
|
|
984
1050
|
ctx.globalAlpha = target2 ? 0.95 : reduced ? 0.6 : 0.45 + 0.35 * Math.sin(clock * 2.4);
|
|
985
1051
|
ctx.lineWidth = 2;
|
|
986
|
-
ctx.beginPath(); ctx.arc(p.x, p.y, r * 1.5, 0, Math.PI * 2); ctx.stroke();
|
|
1052
|
+
ctx.beginPath(); ctx.arc(p.x, p.y, r * (neural ? 2.2 : 1.5), 0, Math.PI * 2); ctx.stroke();
|
|
987
1053
|
ctx.globalAlpha = 1;
|
|
988
1054
|
} else if (core === hover || core === focus) {
|
|
989
1055
|
ctx.strokeStyle = core === focus ? 'rgba(255, 255, 255, .85)' : 'rgba(255, 255, 255, .4)';
|
|
990
1056
|
ctx.lineWidth = core === focus ? 1.4 : 1;
|
|
991
|
-
ctx.beginPath(); ctx.arc(p.x, p.y, r * 1.5, 0, Math.PI * 2); ctx.stroke();
|
|
1057
|
+
ctx.beginPath(); ctx.arc(p.x, p.y, r * (neural ? 2.2 : 1.5), 0, Math.PI * 2); ctx.stroke();
|
|
992
1058
|
}
|
|
993
|
-
|
|
1059
|
+
// The bulb's stem reaches lower than the orb did: its name stands under the stem.
|
|
1060
|
+
const below = p.y + r * (neural ? 3.25 : 1.62) + 8;
|
|
994
1061
|
ctx.textAlign = 'center';
|
|
995
1062
|
ctx.textBaseline = 'top';
|
|
996
1063
|
ctx.lineJoin = 'round';
|
|
@@ -1031,6 +1098,32 @@ export function openMacroverso({ host, rooms, here, enlaces = [], accent = '#9bf
|
|
|
1031
1098
|
const from = back ? pb : pa, to = back ? pa : pb;
|
|
1032
1099
|
// MOTHER's red leaves the room that serves and the accent arrives where it is read; both
|
|
1033
1100
|
// ways, red at both ends with the accent between them.
|
|
1101
|
+
// Neural mode: an axon between two clusters, in one cold line, and what crosses it a light
|
|
1102
|
+
// with the stretch it has just run lit behind it.
|
|
1103
|
+
if (neural) {
|
|
1104
|
+
ctx.globalCompositeOperation = 'lighter';
|
|
1105
|
+
ctx.strokeStyle = `rgba(${NEURAL_AXON}, ${0.18 + 0.22 * lately})`;
|
|
1106
|
+
ctx.lineWidth = 0.9 + 0.6 * lately;
|
|
1107
|
+
ctx.beginPath(); ctx.moveTo(pa.x, pa.y); ctx.quadraticCurveTo(cx, cy, pb.x, pb.y); ctx.stroke();
|
|
1108
|
+
vein.pulses = vein.pulses.filter((pulse) => clock - pulse.born < VEIN_LIFE);
|
|
1109
|
+
for (const pulse of vein.pulses) {
|
|
1110
|
+
const u = Math.min(1, (clock - pulse.born) / VEIN_LIFE);
|
|
1111
|
+
const along = (k) => alongCurve(pa.x, pa.y, cx, cy, pb.x, pb.y, pulse.back ? 1 - k : k);
|
|
1112
|
+
let behind = along(u);
|
|
1113
|
+
const head = behind;
|
|
1114
|
+
for (let step = 1; step <= 6; step += 1) {
|
|
1115
|
+
const back = along(Math.max(0, u - step * 0.03));
|
|
1116
|
+
ctx.strokeStyle = hexAlpha(accent, 0.5 - step * 0.07);
|
|
1117
|
+
ctx.lineWidth = 2 - step * 0.22;
|
|
1118
|
+
ctx.beginPath(); ctx.moveTo(behind.x, behind.y); ctx.lineTo(back.x, back.y); ctx.stroke();
|
|
1119
|
+
behind = back;
|
|
1120
|
+
}
|
|
1121
|
+
const size = (6 + 4 * Math.sin(u * Math.PI)) * Math.max(0.7, Math.min(1.4, camera.zoom));
|
|
1122
|
+
ctx.drawImage(neuronGlow(accent), head.x - size, head.y - size, size * 2, size * 2);
|
|
1123
|
+
}
|
|
1124
|
+
ctx.globalCompositeOperation = 'source-over';
|
|
1125
|
+
continue;
|
|
1126
|
+
}
|
|
1034
1127
|
const red = '#e22816';
|
|
1035
1128
|
const arriving = enlace.direction === 'both' ? red : accent;
|
|
1036
1129
|
const head = reduced ? 0.5 : ((clock * (0.06 + 0.2 * recent) + vein.seed) % 1 + 1) % 1;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
|
|
3
|
+
// The Market's pictures (2026-10-18): drawn here, in code, not photographed. One scene for each
|
|
4
|
+
// module the carousel features — what it does, shown the way a person would see it happen — and a
|
|
5
|
+
// poster made from the icon for every other module. No words inside the drawings, so they read the
|
|
6
|
+
// same in both languages; each sits on its module's own colors (market.js, `hue`), whatever the
|
|
7
|
+
// room's light. Every id inside a drawing is made unique, because two of them can share a page.
|
|
8
|
+
|
|
9
|
+
import { cleanSvg, moduleFace, remoteEntry } from './market.js';
|
|
10
|
+
|
|
11
|
+
let drawn = 0;
|
|
12
|
+
const svg = (body, { width = 560, height = 400 } = {}) => `<svg viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">${body}</svg>`;
|
|
13
|
+
// A soft shadow under a card, and a card itself: the glass every scene is built from.
|
|
14
|
+
const shadow = (id) => `<filter id="${id}" x="-20%" y="-20%" width="140%" height="150%"><feDropShadow dx="0" dy="10" stdDeviation="12" flood-color="#000" flood-opacity=".22"/></filter>`;
|
|
15
|
+
const bar = (x, y, w, h, fill, opacity = 1) => `<rect x="${x}" y="${y}" width="${w}" height="${h}" rx="${h / 2}" fill="${fill}" fill-opacity="${opacity}"/>`;
|
|
16
|
+
|
|
17
|
+
// TEST BROWSER: a page open in a browser, the pointer pressing its button, the screenshot that
|
|
18
|
+
// comes back, and the console the agent reads.
|
|
19
|
+
function browserScene() {
|
|
20
|
+
const id = `mka${++drawn}`;
|
|
21
|
+
return svg(`<defs>${shadow(`${id}s`)}<linearGradient id="${id}h" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#bfdbfe"/><stop offset="1" stop-color="#60a5fa"/></linearGradient></defs>
|
|
22
|
+
<g filter="url(#${id}s)">
|
|
23
|
+
<rect x="60" y="44" width="440" height="296" rx="18" fill="#ffffff"/>
|
|
24
|
+
<path d="M78 44h404a18 18 0 0 1 18 18v22H60V62a18 18 0 0 1 18-18z" fill="#eef2f7"/>
|
|
25
|
+
<circle cx="84" cy="64" r="5.5" fill="#ff5f57"/><circle cx="101" cy="64" r="5.5" fill="#febc2e"/><circle cx="118" cy="64" r="5.5" fill="#28c840"/>
|
|
26
|
+
<rect x="150" y="54" width="260" height="20" rx="10" fill="#ffffff" stroke="#dbe3ee"/>
|
|
27
|
+
${bar(166, 61, 120, 6, '#94a3b8', .7)}
|
|
28
|
+
${bar(88, 108, 200, 16, '#1d4ed8', .92)}
|
|
29
|
+
${bar(88, 134, 160, 9, '#94a3b8', .55)}${bar(88, 150, 186, 9, '#94a3b8', .4)}
|
|
30
|
+
<rect x="88" y="174" width="96" height="30" rx="15" fill="#1d4ed8"/>${bar(108, 186, 56, 6, '#ffffff', .9)}
|
|
31
|
+
<rect x="316" y="104" width="156" height="104" rx="14" fill="url(#${id}h)"/>
|
|
32
|
+
<circle cx="352" cy="136" r="12" fill="#ffffff" fill-opacity=".8"/><path d="M316 192l40-36 30 26 22-18 64 48v6a14 14 0 0 1-14 14H330a14 14 0 0 1-14-14z" fill="#1d4ed8" fill-opacity=".45"/>
|
|
33
|
+
<rect x="88" y="228" width="118" height="84" rx="14" fill="#f1f5f9"/><rect x="221" y="228" width="118" height="84" rx="14" fill="#f1f5f9"/><rect x="354" y="228" width="118" height="84" rx="14" fill="#f1f5f9"/>
|
|
34
|
+
${bar(102, 244, 44, 8, '#1d4ed8', .55)}${bar(102, 262, 84, 6, '#94a3b8', .5)}${bar(102, 276, 66, 6, '#94a3b8', .4)}
|
|
35
|
+
${bar(235, 244, 44, 8, '#1d4ed8', .55)}${bar(235, 262, 84, 6, '#94a3b8', .5)}${bar(235, 276, 66, 6, '#94a3b8', .4)}
|
|
36
|
+
${bar(368, 244, 44, 8, '#1d4ed8', .55)}${bar(368, 262, 84, 6, '#94a3b8', .5)}${bar(368, 276, 66, 6, '#94a3b8', .4)}
|
|
37
|
+
</g>
|
|
38
|
+
<circle cx="160" cy="196" r="20" fill="#ffffff" fill-opacity=".35"/><circle cx="160" cy="196" r="11" fill="#ffffff" fill-opacity=".55"/>
|
|
39
|
+
<path d="M158 192l26 10-11 4-4 11z" fill="#0f172a" stroke="#ffffff" stroke-width="2.5" stroke-linejoin="round"/>
|
|
40
|
+
<g filter="url(#${id}s)" transform="rotate(-7 455 300)">
|
|
41
|
+
<rect x="380" y="250" width="150" height="104" rx="12" fill="#ffffff"/>
|
|
42
|
+
<rect x="390" y="260" width="130" height="70" rx="7" fill="#e0e7ff"/>${bar(398, 268, 60, 7, '#1d4ed8', .8)}${bar(398, 282, 44, 5, '#94a3b8', .6)}<rect x="398" y="294" width="32" height="12" rx="6" fill="#1d4ed8"/>
|
|
43
|
+
<rect x="466" y="268" width="46" height="34" rx="6" fill="url(#${id}h)"/>
|
|
44
|
+
${bar(390, 338, 70, 6, '#94a3b8', .5)}
|
|
45
|
+
</g>
|
|
46
|
+
<circle cx="523" cy="252" r="16" fill="#22c55e" stroke="#ffffff" stroke-width="3"/><path d="M516 252l5 5 9-10" fill="none" stroke="#ffffff" stroke-width="3.2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
47
|
+
<g filter="url(#${id}s)">
|
|
48
|
+
<rect x="24" y="292" width="178" height="78" rx="14" fill="#0f172a" fill-opacity=".94"/>
|
|
49
|
+
${bar(40, 310, 12, 6, '#4ade80')}${bar(58, 310, 96, 6, '#e2e8f0', .7)}
|
|
50
|
+
${bar(40, 326, 12, 6, '#4ade80')}${bar(58, 326, 120, 6, '#e2e8f0', .5)}
|
|
51
|
+
${bar(40, 342, 12, 6, '#60a5fa')}${bar(58, 342, 70, 6, '#e2e8f0', .35)}
|
|
52
|
+
</g>`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// CONTINUITY: one agent hands the work to another, and the project's state stays in .ahp/, beside
|
|
56
|
+
// the code, with what was checked.
|
|
57
|
+
function handoffScene() {
|
|
58
|
+
const id = `mka${++drawn}`;
|
|
59
|
+
const row = (y, done = true) => `<circle cx="210" cy="${y}" r="11" fill="${done ? '#10b981' : '#e2e8f0'}"/>${done ? `<path d="M205 ${y}l4 4 7-8" fill="none" stroke="#ffffff" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/>` : ''}${bar(232, y - 9, 118, 8, '#0f172a', .7)}${bar(232, y + 3, 82, 6, '#94a3b8', .5)}`;
|
|
60
|
+
return svg(`<defs>${shadow(`${id}s`)}
|
|
61
|
+
<linearGradient id="${id}a" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#fbbf24"/><stop offset="1" stop-color="#ea580c"/></linearGradient>
|
|
62
|
+
<linearGradient id="${id}b" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#818cf8"/><stop offset="1" stop-color="#7c3aed"/></linearGradient></defs>
|
|
63
|
+
<path d="M120 150C150 80 200 70 236 82" fill="none" stroke="#ffffff" stroke-opacity=".8" stroke-width="3" stroke-dasharray="2 10" stroke-linecap="round"/>
|
|
64
|
+
<path d="M376 300c40 30 70 22 92-6" fill="none" stroke="#ffffff" stroke-opacity=".8" stroke-width="3" stroke-dasharray="2 10" stroke-linecap="round"/>
|
|
65
|
+
<g filter="url(#${id}s)">
|
|
66
|
+
<path d="M176 84h66l14 16h112a20 20 0 0 1 20 20v188a20 20 0 0 1-20 20H176a20 20 0 0 1-20-20V104a20 20 0 0 1 20-20z" fill="#ffffff"/>
|
|
67
|
+
<rect x="182" y="118" width="70" height="22" rx="11" fill="#ecfdf5"/>
|
|
68
|
+
<text x="217" y="133.5" text-anchor="middle" font-family="ui-monospace, Menlo, monospace" font-size="12.5" font-weight="700" fill="#047857">.ahp/</text>
|
|
69
|
+
${row(170)}${row(214)}${row(258)}${row(302, false)}
|
|
70
|
+
</g>
|
|
71
|
+
<g filter="url(#${id}s)"><circle cx="96" cy="182" r="40" fill="url(#${id}a)"/></g>
|
|
72
|
+
<path d="M96 164l5 12.5 13 5.5-13 5.5-5 12.5-5-12.5-13-5.5 13-5.5z" fill="#ffffff"/>
|
|
73
|
+
<g filter="url(#${id}s)"><circle cx="474" cy="262" r="40" fill="url(#${id}b)"/></g>
|
|
74
|
+
<path d="M458 262l10 10 20-22" fill="none" stroke="#ffffff" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
75
|
+
<g filter="url(#${id}s)" transform="rotate(-18 432 112)"><rect x="404" y="98" width="62" height="26" rx="13" fill="#ffffff"/>${bar(418, 108, 34, 6, '#10b981', .9)}</g>
|
|
76
|
+
<circle cx="470" cy="70" r="5" fill="#ffffff" fill-opacity=".7"/><circle cx="74" cy="300" r="4" fill="#ffffff" fill-opacity=".6"/><circle cx="520" cy="170" r="3" fill="#ffffff" fill-opacity=".6"/>`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// IMAGES: what was asked, in a line, and what came back: the picture, with its other takes beside it.
|
|
80
|
+
function imagesScene() {
|
|
81
|
+
const id = `mka${++drawn}`;
|
|
82
|
+
const take = (y, stops, chosen = false) => `<g filter="url(#${id}s)"><rect x="384" y="${y}" width="128" height="68" rx="12" fill="url(#${id}${stops})"/>${chosen ? `<rect x="381" y="${y - 3}" width="134" height="74" rx="14" fill="none" stroke="#ffffff" stroke-width="3"/>` : ''}<path d="M384 ${y + 52}l30-20 22 16 18-12 58 22v2a12 12 0 0 1-12 12H396a12 12 0 0 1-12-12z" fill="#000" fill-opacity=".22"/></g>`;
|
|
83
|
+
return svg(`<defs>${shadow(`${id}s`)}
|
|
84
|
+
<linearGradient id="${id}sky" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#fde68a"/><stop offset=".5" stop-color="#fb7185"/><stop offset="1" stop-color="#7c3aed"/></linearGradient>
|
|
85
|
+
<linearGradient id="${id}t1" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#a7f3d0"/><stop offset="1" stop-color="#0ea5e9"/></linearGradient>
|
|
86
|
+
<linearGradient id="${id}t2" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#fef3c7"/><stop offset="1" stop-color="#f97316"/></linearGradient>
|
|
87
|
+
<linearGradient id="${id}t3" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#fbcfe8"/><stop offset="1" stop-color="#9333ea"/></linearGradient>
|
|
88
|
+
<linearGradient id="${id}go" x1="0" y1="0" x2="1" y2="0"><stop offset="0" stop-color="#fb7185"/><stop offset="1" stop-color="#c026d3"/></linearGradient>
|
|
89
|
+
<clipPath id="${id}c"><rect x="56" y="116" width="304" height="232" rx="18"/></clipPath></defs>
|
|
90
|
+
<g filter="url(#${id}s)">
|
|
91
|
+
<rect x="56" y="36" width="456" height="54" rx="27" fill="#ffffff"/>
|
|
92
|
+
<circle cx="84" cy="63" r="14" fill="#fdf2f8"/><path d="M84 54l2.6 6.4 6.4 2.6-6.4 2.6-2.6 6.4-2.6-6.4-6.4-2.6 6.4-2.6z" fill="#db2777"/>
|
|
93
|
+
${bar(108, 54, 170, 8, '#0f172a', .65)}${bar(108, 68, 120, 6, '#94a3b8', .55)}
|
|
94
|
+
<rect x="404" y="45" width="96" height="36" rx="18" fill="url(#${id}go)"/>${bar(426, 60, 52, 6, '#ffffff', .9)}
|
|
95
|
+
</g>
|
|
96
|
+
<g filter="url(#${id}s)">
|
|
97
|
+
<rect x="56" y="116" width="304" height="232" rx="18" fill="url(#${id}sky)"/>
|
|
98
|
+
<g clip-path="url(#${id}c)">
|
|
99
|
+
<circle cx="244" cy="206" r="40" fill="#fff7ed" fill-opacity=".92"/>
|
|
100
|
+
<path d="M56 300l70-70 50 44 46-40 70 56 68-46v104H56z" fill="#831843" fill-opacity=".72"/>
|
|
101
|
+
<path d="M56 330l92-52 64 34 58-28 90 46v18H56z" fill="#4c1d95" fill-opacity=".85"/>
|
|
102
|
+
</g>
|
|
103
|
+
<rect x="56" y="116" width="304" height="232" rx="18" fill="none" stroke="#ffffff" stroke-width="5"/>
|
|
104
|
+
</g>
|
|
105
|
+
${take(116, 't1')}${take(198, 't2', true)}${take(280, 't3')}
|
|
106
|
+
<path d="M30 150l3 7.5 7.5 3-7.5 3-3 7.5-3-7.5-7.5-3 7.5-3z" fill="#ffffff" fill-opacity=".85"/>
|
|
107
|
+
<path d="M530 116l2.2 5.4 5.4 2.2-5.4 2.2-2.2 5.4-2.2-5.4-5.4-2.2 5.4-2.2z" fill="#ffffff" fill-opacity=".8"/>
|
|
108
|
+
<path d="M352 104l1.8 4.4 4.4 1.8-4.4 1.8-1.8 4.4-1.8-4.4-4.4-1.8 4.4-1.8z" fill="#ffffff" fill-opacity=".8"/>`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// LOCAL AI: the computer itself thinking — a memory of linked notes on its screen — with no cloud
|
|
112
|
+
// above it and a lock beside it: nothing leaves.
|
|
113
|
+
function localScene() {
|
|
114
|
+
const id = `mka${++drawn}`;
|
|
115
|
+
const nodes = [[220, 132], [300, 112], [360, 160], [326, 214], [244, 206], [200, 172], [288, 166]];
|
|
116
|
+
const links = [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 0], [6, 0], [6, 1], [6, 2], [6, 3], [6, 4], [6, 5]];
|
|
117
|
+
const net = links.map(([a, b]) => `<line x1="${nodes[a][0]}" y1="${nodes[a][1]}" x2="${nodes[b][0]}" y2="${nodes[b][1]}" stroke="#86efac" stroke-opacity=".55" stroke-width="2"/>`).join('')
|
|
118
|
+
+ nodes.map(([x, y], index) => (index === 6 ? `<circle cx="${x}" cy="${y}" r="22" fill="#4ade80" fill-opacity=".22"/><circle cx="${x}" cy="${y}" r="13" fill="#4ade80"/>` : `<circle cx="${x}" cy="${y}" r="7" fill="#bbf7d0"/>`)).join('');
|
|
119
|
+
return svg(`<defs>${shadow(`${id}s`)}<radialGradient id="${id}g" cx=".5" cy=".45" r=".6"><stop offset="0" stop-color="#14532d"/><stop offset="1" stop-color="#052e16"/></radialGradient></defs>
|
|
120
|
+
<g filter="url(#${id}s)">
|
|
121
|
+
<rect x="128" y="64" width="320" height="206" rx="16" fill="#ffffff"/>
|
|
122
|
+
<rect x="140" y="76" width="296" height="182" rx="9" fill="url(#${id}g)"/>
|
|
123
|
+
<path d="M96 278h384l-26 30H122z" fill="#ffffff"/><rect x="250" y="282" width="76" height="7" rx="3.5" fill="#cbd5e1"/>
|
|
124
|
+
</g>
|
|
125
|
+
${net}
|
|
126
|
+
<g filter="url(#${id}s)">
|
|
127
|
+
<rect x="22" y="112" width="104" height="58" rx="12" fill="#ffffff"/>${bar(36, 128, 58, 7, '#15803d', .85)}${bar(36, 142, 76, 6, '#94a3b8', .55)}${bar(36, 154, 48, 6, '#94a3b8', .4)}
|
|
128
|
+
<rect x="452" y="186" width="104" height="58" rx="12" fill="#ffffff"/>${bar(466, 202, 58, 7, '#15803d', .85)}${bar(466, 216, 76, 6, '#94a3b8', .55)}${bar(466, 228, 48, 6, '#94a3b8', .4)}
|
|
129
|
+
</g>
|
|
130
|
+
<path d="M126 141c40 0 50-6 74 28" fill="none" stroke="#ffffff" stroke-opacity=".75" stroke-width="2.5" stroke-dasharray="2 8" stroke-linecap="round"/>
|
|
131
|
+
<path d="M452 215c-46 0-60 2-92-50" fill="none" stroke="#ffffff" stroke-opacity=".75" stroke-width="2.5" stroke-dasharray="2 8" stroke-linecap="round"/>
|
|
132
|
+
<g transform="translate(452 40)">
|
|
133
|
+
<path d="M22 50h52a20 20 0 0 0 2-40 28 28 0 0 0-52-6A22 22 0 0 0 22 50z" fill="#ffffff" fill-opacity=".9"/>
|
|
134
|
+
<path d="M14 8l70 52" stroke="#15803d" stroke-width="6" stroke-linecap="round"/>
|
|
135
|
+
</g>
|
|
136
|
+
<g filter="url(#${id}s)"><circle cx="80" cy="282" r="34" fill="#ffffff"/></g>
|
|
137
|
+
<rect x="66" y="280" width="28" height="22" rx="5" fill="#15803d"/><path d="M71 280v-6a9 9 0 0 1 18 0v6" fill="none" stroke="#15803d" stroke-width="4.5"/>
|
|
138
|
+
<circle cx="80" cy="290" r="3" fill="#ffffff"/>`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const SCENES = { playwright: browserScene, ahp: handoffScene, 'image-studio': imagesScene, ollama: localScene };
|
|
142
|
+
|
|
143
|
+
// Every other module: its icon, large, with the cards of what it touches floating round it.
|
|
144
|
+
function poster(item) {
|
|
145
|
+
const id = `mka${++drawn}`;
|
|
146
|
+
const face = moduleFace(item);
|
|
147
|
+
return svg(`<defs>${shadow(`${id}s`)}<radialGradient id="${id}r" cx=".5" cy=".5" r=".5"><stop offset="0" stop-color="#ffffff" stop-opacity=".35"/><stop offset="1" stop-color="#ffffff" stop-opacity="0"/></radialGradient></defs>
|
|
148
|
+
<circle cx="280" cy="200" r="190" fill="url(#${id}r)"/>
|
|
149
|
+
<g filter="url(#${id}s)">
|
|
150
|
+
<rect x="58" y="74" width="138" height="72" rx="14" fill="#ffffff" fill-opacity=".92"/>${bar(74, 92, 70, 8, '#0f172a', .6)}${bar(74, 108, 100, 6, '#94a3b8', .55)}${bar(74, 122, 64, 6, '#94a3b8', .4)}
|
|
151
|
+
<rect x="370" y="258" width="138" height="72" rx="14" fill="#ffffff" fill-opacity=".92"/>${bar(386, 276, 70, 8, '#0f172a', .6)}${bar(386, 292, 100, 6, '#94a3b8', .55)}${bar(386, 306, 64, 6, '#94a3b8', .4)}
|
|
152
|
+
<rect x="200" y="120" width="160" height="160" rx="38" fill="#ffffff" fill-opacity=".2" stroke="#ffffff" stroke-opacity=".7" stroke-width="2"/>
|
|
153
|
+
</g>
|
|
154
|
+
<g transform="translate(220 140) scale(6)" color="#ffffff">${face.icon}</g>
|
|
155
|
+
<circle cx="420" cy="110" r="6" fill="#ffffff" fill-opacity=".7"/><circle cx="140" cy="300" r="5" fill="#ffffff" fill-opacity=".6"/>`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// The picture of a module: its own scene when it has one — drawn here, or sent with the catalog
|
|
159
|
+
// (kept to the shapes a drawing needs, market.js) — and its poster otherwise.
|
|
160
|
+
export const marketArt = (item) => (SCENES[item?.id] ? SCENES[item.id]() : cleanSvg(remoteEntry(item?.id)?.art) ?? poster(item));
|
|
161
|
+
export const hasScene = (item) => Boolean(SCENES[item?.id] || remoteEntry(item?.id)?.art);
|