shadok-ai 0.8.26 → 0.8.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/public/index.html +74 -62
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shadok-ai",
3
- "version": "0.8.26",
3
+ "version": "0.8.27",
4
4
  "main": "dist/session.js",
5
5
  "scripts": {
6
6
  "test": "node --import tsx --test test/*.ts context/*/test/*.test.mjs",
package/public/index.html CHANGED
@@ -11,7 +11,7 @@
11
11
  <title>shadok-ai — cockpit</title>
12
12
  <!-- The idle favicon: a resting Shadok. paint() swaps in the pumping / "!" / "?"
13
13
  variants once the JS runs; this is only the pre-script icon. -->
14
- <link rel="icon" id="favicon" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2032%2032%22%3E%3Crect%20width%3D%2232%22%20height%3D%2232%22%20rx%3D%227%22%20fill%3D%22%2314161d%22%2F%3E%3Cg%20fill%3D%22%23f0a848%22%3E%3Crect%20x%3D%2212%22%20y%3D%2224%22%20width%3D%221.9%22%20height%3D%227%22%20rx%3D%220.95%22%2F%3E%3Crect%20x%3D%2216.1%22%20y%3D%2224%22%20width%3D%221.9%22%20height%3D%227%22%20rx%3D%220.95%22%2F%3E%3Crect%20x%3D%229.8%22%20y%3D%2229.8%22%20width%3D%224.8%22%20height%3D%221.9%22%20rx%3D%220.95%22%2F%3E%3Crect%20x%3D%2215.4%22%20y%3D%2229.8%22%20width%3D%224.8%22%20height%3D%221.9%22%20rx%3D%220.95%22%2F%3E%3C%2Fg%3E%3Cg%3E%3Cellipse%20cx%3D%227.7%22%20cy%3D%2219.5%22%20rx%3D%222.1%22%20ry%3D%223.5%22%20fill%3D%22%23f0a848%22%20transform%3D%22rotate(-16%207.7%2019.5)%22%2F%3E%3Cellipse%20cx%3D%2222.3%22%20cy%3D%2219.5%22%20rx%3D%222.1%22%20ry%3D%223.5%22%20fill%3D%22%23f0a848%22%20transform%3D%22rotate(16%2022.3%2019.5)%22%2F%3E%3Cellipse%20cx%3D%2215%22%20cy%3D%2219.5%22%20rx%3D%227.8%22%20ry%3D%228.2%22%20fill%3D%22%23f0a848%22%2F%3E%3Crect%20x%3D%2213.4%22%20y%3D%228%22%20width%3D%223.2%22%20height%3D%225%22%20rx%3D%221.6%22%20fill%3D%22%23f0a848%22%2F%3E%3Ccircle%20cx%3D%2215%22%20cy%3D%226.2%22%20r%3D%224.3%22%20fill%3D%22%23f0a848%22%2F%3E%3Cpath%20d%3D%22M13.6%209.8%20L16.4%209.8%20L15%2011.8%20Z%22%20fill%3D%22%23f0a848%22%2F%3E%3Ccircle%20cx%3D%2213.1%22%20cy%3D%226%22%20r%3D%222%22%20fill%3D%22%23fff%22%2F%3E%3Ccircle%20cx%3D%2216.9%22%20cy%3D%226%22%20r%3D%222%22%20fill%3D%22%23fff%22%2F%3E%3Ccircle%20cx%3D%2213.1%22%20cy%3D%226.1%22%20r%3D%221.1%22%20fill%3D%22%2314161d%22%2F%3E%3Ccircle%20cx%3D%2216.9%22%20cy%3D%226.1%22%20r%3D%221.1%22%20fill%3D%22%2314161d%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E">
14
+ <link rel="icon" id="favicon" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2032%2032%22%3E%3Crect%20width%3D%2232%22%20height%3D%2232%22%20rx%3D%227%22%20fill%3D%22%2314161d%22%2F%3E%3Cline%20x1%3D%2210.8%22%20y1%3D%2223%22%20x2%3D%2210.4%22%20y2%3D%2230.6%22%20stroke%3D%22%23f0a848%22%20stroke-width%3D%221.3%22%20stroke-linecap%3D%22round%22%2F%3E%3Cline%20x1%3D%2216.400000000000002%22%20y1%3D%2223%22%20x2%3D%2216.8%22%20y2%3D%2230.6%22%20stroke%3D%22%23f0a848%22%20stroke-width%3D%221.3%22%20stroke-linecap%3D%22round%22%2F%3E%3Cline%20x1%3D%228.8%22%20y1%3D%2230.7%22%20x2%3D%2211.9%22%20y2%3D%2230.7%22%20stroke%3D%22%23f0a848%22%20stroke-width%3D%221.3%22%20stroke-linecap%3D%22round%22%2F%3E%3Cline%20x1%3D%2215.4%22%20y1%3D%2230.7%22%20x2%3D%2218.5%22%20y2%3D%2230.7%22%20stroke%3D%22%23f0a848%22%20stroke-width%3D%221.3%22%20stroke-linecap%3D%22round%22%2F%3E%3Cline%20x1%3D%2219.6%22%20y1%3D%2217.2%22%20x2%3D%2224.000000000000004%22%20y2%3D%2220.4%22%20stroke%3D%22%23f0a848%22%20stroke-width%3D%221.3%22%20stroke-linecap%3D%22round%22%2F%3E%3Cg%3E%3Cline%20x1%3D%2211.600000000000001%22%20y1%3D%225.399999999999999%22%20x2%3D%229.600000000000001%22%20y2%3D%222.8%22%20stroke%3D%22%23f0a848%22%20stroke-width%3D%220.9%22%20stroke-linecap%3D%22round%22%2F%3E%3Ccircle%20cx%3D%229.600000000000001%22%20cy%3D%222.8%22%20r%3D%221%22%20fill%3D%22%23f0a848%22%2F%3E%3Cline%20x1%3D%2216%22%20y1%3D%225.399999999999999%22%20x2%3D%2218%22%20y2%3D%222.8%22%20stroke%3D%22%23f0a848%22%20stroke-width%3D%220.9%22%20stroke-linecap%3D%22round%22%2F%3E%3Ccircle%20cx%3D%2218%22%20cy%3D%222.8%22%20r%3D%221%22%20fill%3D%22%23f0a848%22%2F%3E%3Ccircle%20cx%3D%2213.8%22%20cy%3D%2216.2%22%20r%3D%228.4%22%20fill%3D%22%23f0a848%22%2F%3E%3Cpath%20d%3D%22M12.200000000000001%2018.2%20L15.4%2018.2%20L13.8%2021.6%20Z%22%20fill%3D%22%23f0a848%22%2F%3E%3Ccircle%20cx%3D%2210.9%22%20cy%3D%227.799999999999999%22%20r%3D%222.6%22%20fill%3D%22%23fff%22%2F%3E%3Ccircle%20cx%3D%2216.7%22%20cy%3D%227.799999999999999%22%20r%3D%222.6%22%20fill%3D%22%23fff%22%2F%3E%3Ccircle%20cx%3D%2211.200000000000001%22%20cy%3D%228.1%22%20r%3D%221.3%22%20fill%3D%22%2314161d%22%2F%3E%3Ccircle%20cx%3D%2217%22%20cy%3D%228.1%22%20r%3D%221.3%22%20fill%3D%22%2314161d%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E">
15
15
  <style>
16
16
  :root {
17
17
  --bg: #14161d;
@@ -1017,17 +1017,12 @@
1017
1017
  }
1018
1018
  @keyframes tw-shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }
1019
1019
  /* The pumping Shadok: `color` feeds its `currentColor` fill so it follows the
1020
- palette. The body bobs and the arm swings — the pump — on the same beat. */
1020
+ palette. It is animated by re-drawing in JS (renderTw), not CSS — the pump's
1021
+ arm has to be re-linked to the lever each frame. */
1021
1022
  #thinking .tw-shadok { display: inline-flex; color: var(--amber); }
1022
1023
  #thinking .tw-shadok svg { width: 22px; height: 22px; overflow: visible; }
1023
- /* The whole Shadok (body + arm + handle) bobs with translate — no
1024
- transform-origin, which is a cross-browser trap on SVG — while the pump rod
1025
- and legs stay planted, so the handle strokes up and down the rod. */
1026
- #thinking .tw-shadok .sh-bob { animation: tw-bob 0.55s ease-in-out infinite; }
1027
- @keyframes tw-bob { 0%, 100% { transform: translateY(-1.8px); } 50% { transform: translateY(2.2px); } }
1028
1024
  @media (prefers-reduced-motion: reduce) {
1029
1025
  #thinking .tw-text { animation: none; color: var(--phosphor); -webkit-text-fill-color: currentColor; }
1030
- #thinking .tw-shadok .sh-bob { animation: none; }
1031
1026
  }
1032
1027
 
1033
1028
  /* Composer */
@@ -3241,50 +3236,53 @@
3241
3236
  const v = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
3242
3237
  return v || fallback;
3243
3238
  }
3244
- /** The Shadok itself — the cockpit's mascot, which famously PUMPS. Drawn
3245
- * FRONT-ON: a plump round body, a short neck, a round head with two big googly
3246
- * eyes (their signature — a side profile with a long beak read as a dinosaur),
3247
- * a tiny beak, two stub wings, two long legs. One drawing serves every use.
3248
- * `bob` poses one frame of the pump (the favicon animates by re-generating);
3249
- * `anim` instead tags the bobbing group so CSS drives it (the DOM indicator
3250
- * only — CSS animation inside a favicon data URI is unreliable). `pump` adds a
3251
- * FIXED rod on the right the Shadok works with its wing; that steady reference,
3252
- * with the planted legs, is what makes the bob read at favicon size. `sym`
3253
- * stamps a bold "!" / "?" over the belly, outlined in the ground colour so the
3254
- * accent-on-accent "?" still reads (the eyes stay clear above it). Legs (and,
3255
- * pumping, the rod) stay planted; the body/head/wings bob. The Shadok shifts a
3256
- * little left when pumping, to seat the rod on its right. Eyes are always
3257
- * white + dark, never the accent, so they read on any `fill`. */
3258
- function shadokInner(fill, { bob = 0, pump = false, anim = false, sym = null, symColor } = {}) {
3239
+ /** The Shadok itself — the cockpit's mascot, which famously PUMPS. Drawn like
3240
+ * the real thing (Rouxel): a round BALL body, two big googly eyes perched on
3241
+ * top, two antennae, a tiny beak, thin stick legs. One drawing serves every
3242
+ * state. `lever` (degrees around the fulcrum) poses the see-saw hand-pump it
3243
+ * works — pass a number to show the pump, null for the idle stub arm; `bob`
3244
+ * dips the ball with the effort. The arm is recomputed each call from the
3245
+ * (bobbed) shoulder to the lever end it grips, so the linkage is always right —
3246
+ * animating the arm with CSS instead made it swing around the pivot, backwards.
3247
+ * `sym` stamps a bold "!" / "?" over the ball, outlined in the ground colour so
3248
+ * the accent-on-accent "?" still reads (eyes stay clear above it). Legs and the
3249
+ * fulcrum stay planted. Eyes are always white + dark, never the accent, so they
3250
+ * read on any `fill` (the `currentColor` indicator included). */
3251
+ const PX = 23.2, PY = 24.4; // the pump lever's fulcrum
3252
+ function shadokInner(fill, { lever = null, bob = 0, sym = null, symColor } = {}) {
3259
3253
  const base = themeColor("--bg", "#14161d");
3260
- const hx = pump ? 13.6 : 15; // body/head centre X
3261
- const hy = 6.2; // head centre Y
3262
- const bobCls = anim ? ' class="sh-bob"' : "";
3263
- const bobTf = anim ? "" : ` transform="translate(0 ${bob})"`;
3264
- // The right wing rests low, or — pumping — angles up to work the rod's handle.
3265
- const rWing = pump
3266
- ? `<ellipse cx="${hx + 7.6}" cy="17.8" rx="2.1" ry="3.4" fill="${fill}" transform="rotate(28 ${hx + 7.6} 17.8)"/>`
3267
- : `<ellipse cx="${hx + 7.3}" cy="19.5" rx="2.1" ry="3.5" fill="${fill}" transform="rotate(16 ${hx + 7.3} 19.5)"/>`;
3268
- const rod = pump ? `<rect x="23.4" y="15.5" width="1.5" height="13.7" rx="0.6" fill="${fill}"/>` : "";
3254
+ const cx = 13.8, cy = 16.2, r = 8.4, eyeY = cy - 8.4, eyeSep = 5.8, eyeR = 2.6, pupR = 1.3;
3255
+ const shX = cx + r - 2.6, shY = cy + 1.8 + bob; // shoulder (moves with the bob)
3256
+ let fulcrum = "", leverEl = "", armEl = "";
3257
+ if (lever !== null) {
3258
+ const rad = (lever * Math.PI) / 180, nearX = PX - 6.5, farX = PX + 5;
3259
+ const ax = PX + (nearX - PX) * Math.cos(rad), ay = PY + (nearX - PX) * Math.sin(rad);
3260
+ const bx = PX + (farX - PX) * Math.cos(rad), by = PY + (farX - PX) * Math.sin(rad);
3261
+ fulcrum = `<path d="M${PX - 2.2} 29 L${PX + 2.2} 29 L${PX} ${PY} Z" fill="${fill}"/>`;
3262
+ leverEl = `<line x1="${ax}" y1="${ay}" x2="${bx}" y2="${by}" stroke="${fill}" stroke-width="1.7" stroke-linecap="round"/>`;
3263
+ armEl = `<line x1="${shX}" y1="${shY}" x2="${ax}" y2="${ay}" stroke="${fill}" stroke-width="1.3" stroke-linecap="round"/>`;
3264
+ } else {
3265
+ armEl = `<line x1="${shX}" y1="${cy + 1 + bob}" x2="${cx + r + 1.8}" y2="${cy + 4.2 + bob}" stroke="${fill}" stroke-width="1.3" stroke-linecap="round"/>`;
3266
+ }
3269
3267
  const glyph = sym
3270
- ? `<text x="${hx}" y="22" font-family="monospace" font-size="15.5" font-weight="800" fill="${symColor || fill}" stroke="${base}" stroke-width="2.6" paint-order="stroke" text-anchor="middle">${sym}</text>`
3268
+ ? `<text x="${cx}" y="${cy + 3.4 + bob}" font-family="ui-monospace, monospace" font-size="14" font-weight="800" fill="${symColor || fill}" stroke="${base}" stroke-width="2.4" paint-order="stroke" text-anchor="middle">${sym}</text>`
3271
3269
  : "";
3272
3270
  return (
3273
- rod +
3274
- // legs + feet (planted, not part of the bob)
3275
- `<g fill="${fill}">` +
3276
- `<rect x="${hx - 3}" y="24" width="1.9" height="7" rx="0.95"/><rect x="${hx + 1.1}" y="24" width="1.9" height="7" rx="0.95"/>` +
3277
- `<rect x="${hx - 5.2}" y="29.8" width="4.8" height="1.9" rx="0.95"/><rect x="${hx + 0.4}" y="29.8" width="4.8" height="1.9" rx="0.95"/>` +
3278
- `</g>` +
3279
- // wings + body + neck + head + beak + two eyes — all bob together
3280
- `<g${bobCls}${bobTf}>` +
3281
- `<ellipse cx="${hx - 7.3}" cy="19.5" rx="2.1" ry="3.5" fill="${fill}" transform="rotate(-16 ${hx - 7.3} 19.5)"/>${rWing}` +
3282
- `<ellipse cx="${hx}" cy="19.5" rx="7.8" ry="8.2" fill="${fill}"/>` +
3283
- `<rect x="${hx - 1.6}" y="8" width="3.2" height="5" rx="1.6" fill="${fill}"/>` +
3284
- `<circle cx="${hx}" cy="${hy}" r="4.3" fill="${fill}"/>` +
3285
- `<path d="M${hx - 1.4} ${hy + 3.6} L${hx + 1.4} ${hy + 3.6} L${hx} ${hy + 5.6} Z" fill="${fill}"/>` +
3286
- `<circle cx="${hx - 1.9}" cy="${hy - 0.2}" r="2" fill="#fff"/><circle cx="${hx + 1.9}" cy="${hy - 0.2}" r="2" fill="#fff"/>` +
3287
- `<circle cx="${hx - 1.9}" cy="${hy - 0.1}" r="1.1" fill="${base}"/><circle cx="${hx + 1.9}" cy="${hy - 0.1}" r="1.1" fill="${base}"/>` +
3271
+ fulcrum + leverEl +
3272
+ // thin legs + feet (planted)
3273
+ `<line x1="${cx - 3}" y1="${cy + r - 1.6}" x2="${cx - 3.4}" y2="30.6" stroke="${fill}" stroke-width="1.3" stroke-linecap="round"/>` +
3274
+ `<line x1="${cx + 2.6}" y1="${cy + r - 1.6}" x2="${cx + 3}" y2="30.6" stroke="${fill}" stroke-width="1.3" stroke-linecap="round"/>` +
3275
+ `<line x1="${cx - 5}" y1="30.7" x2="${cx - 1.9}" y2="30.7" stroke="${fill}" stroke-width="1.3" stroke-linecap="round"/>` +
3276
+ `<line x1="${cx + 1.6}" y1="30.7" x2="${cx + 4.7}" y2="30.7" stroke="${fill}" stroke-width="1.3" stroke-linecap="round"/>` +
3277
+ armEl +
3278
+ // ball body + antennae + beak + eyes — bob together with the effort
3279
+ `<g transform="translate(0 ${bob})">` +
3280
+ `<line x1="${cx - 2.2}" y1="${eyeY - 2.4}" x2="${cx - 4.2}" y2="2.8" stroke="${fill}" stroke-width="0.9" stroke-linecap="round"/><circle cx="${cx - 4.2}" cy="2.8" r="1" fill="${fill}"/>` +
3281
+ `<line x1="${cx + 2.2}" y1="${eyeY - 2.4}" x2="${cx + 4.2}" y2="2.8" stroke="${fill}" stroke-width="0.9" stroke-linecap="round"/><circle cx="${cx + 4.2}" cy="2.8" r="1" fill="${fill}"/>` +
3282
+ `<circle cx="${cx}" cy="${cy}" r="${r}" fill="${fill}"/>` +
3283
+ `<path d="M${cx - 1.6} ${cy + 2} L${cx + 1.6} ${cy + 2} L${cx} ${cy + 5.4} Z" fill="${fill}"/>` +
3284
+ `<circle cx="${cx - eyeSep / 2}" cy="${eyeY}" r="${eyeR}" fill="#fff"/><circle cx="${cx + eyeSep / 2}" cy="${eyeY}" r="${eyeR}" fill="#fff"/>` +
3285
+ `<circle cx="${cx - eyeSep / 2 + 0.3}" cy="${eyeY + 0.3}" r="${pupR}" fill="${base}"/><circle cx="${cx + eyeSep / 2 + 0.3}" cy="${eyeY + 0.3}" r="${pupR}" fill="${base}"/>` +
3288
3286
  `</g>` +
3289
3287
  glyph
3290
3288
  );
@@ -3306,14 +3304,16 @@
3306
3304
  const accent = themeColor("--amber", "#f0a848");
3307
3305
  return faviconWrap(shadokInner(accent, symbol ? { sym: symbol, symColor } : {}));
3308
3306
  }
3309
- /** The "working" favicon: the Shadok pumping. `phase` (0..PUMP_FRAMES-1) rides a
3310
- * cycle; the whole Shadok heaves up and down against the fixed rod. */
3311
- const PUMP_FRAMES = 8;
3307
+ /** The pump pose for a phase p (0..1..0): the Shadok pushes the lever DOWN (near
3308
+ * end down = negative angle) and the ball dips with the effort — shared by the
3309
+ * favicon and the in-page indicator so both pump the same way. */
3310
+ function pumpPose(p) { return { lever: 13 - p * 28, bob: +(p * 1.5).toFixed(2) }; }
3311
+ /** The "working" favicon: the Shadok pumping its lever. `phase` rides a cycle. */
3312
+ const PUMP_FRAMES = 12;
3312
3313
  function faviconPumpingSVG(phase) {
3313
3314
  const accent = themeColor("--amber", "#f0a848");
3314
3315
  const p = (Math.sin((phase / PUMP_FRAMES) * 2 * Math.PI - Math.PI / 2) + 1) / 2; // 0..1..0
3315
- const bob = +(-2 + p * 4.4).toFixed(2); // -2 (up) .. +2.4 (down): a big, visible stroke
3316
- return faviconWrap(shadokInner(accent, { bob, pump: true }));
3316
+ return faviconWrap(shadokInner(accent, pumpPose(p)));
3317
3317
  }
3318
3318
  /** What each channel is asking for, as the pure module wants it. */
3319
3319
  function attentionInput() {
@@ -3377,15 +3377,27 @@
3377
3377
  }
3378
3378
  }
3379
3379
  function refreshBadge() { paint(); }
3380
- // The in-page "pumping" Shadok, drawn once from the SAME `shadokInner` as the
3381
- // favicon. `currentColor` lets the CSS palette (var(--amber)) tint it, so it
3382
- // follows a theme change with no redraw; `anim` tags the body/arm for the CSS
3383
- // pump. The element sits earlier in the document, so it's here to fill now.
3384
- {
3385
- const twShadok = document.querySelector("#thinking .tw-shadok");
3386
- if (twShadok)
3387
- twShadok.innerHTML =
3388
- `<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">${shadokInner("currentColor", { pump: true, anim: true })}</svg>`;
3380
+ // The in-page "Shadok is pumping" indicator uses the SAME `shadokInner`, tinted
3381
+ // via `currentColor` so it follows the palette. It animates by RE-DRAWING each
3382
+ // frame (like the favicon), not CSS: the pump's arm must be re-linked to the
3383
+ // lever every frame. A single heartbeat re-draws only while the indicator is
3384
+ // visible (it shows only during a turn), so an idle cockpit pays almost nothing.
3385
+ const twShadok = document.querySelector("#thinking .tw-shadok");
3386
+ const thinkingEl = document.getElementById("thinking");
3387
+ function renderTw(p) {
3388
+ if (!twShadok) return;
3389
+ const { lever, bob } = pumpPose(p);
3390
+ twShadok.innerHTML =
3391
+ `<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">${shadokInner("currentColor", { lever, bob })}</svg>`;
3392
+ }
3393
+ renderTw(0); // populate immediately so it isn't blank on first show
3394
+ if (!(window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches)) {
3395
+ let twPhase = 0;
3396
+ setInterval(() => {
3397
+ if (!thinkingEl || !thinkingEl.classList.contains("visible")) return;
3398
+ twPhase = (twPhase + 1) % PUMP_FRAMES;
3399
+ renderTw((Math.sin((twPhase / PUMP_FRAMES) * 2 * Math.PI - Math.PI / 2) + 1) / 2);
3400
+ }, 95);
3389
3401
  }
3390
3402
  // Coming back must stop the blink at once, not at the next tick. Both signals
3391
3403
  // matter: switching tabs fires visibilitychange, switching application only