@mingxy/opencode-mascot 0.4.25 → 0.4.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mingxy/opencode-mascot",
3
- "version": "0.4.25",
3
+ "version": "0.4.27",
4
4
  "description": "OpenCode TUI mascot plugin framework - customizable ASCII mascots for your terminal",
5
5
  "author": "mingxy",
6
6
  "license": "MIT",
@@ -4,7 +4,7 @@ import { createSignal } from "solid-js";
4
4
  import type { JSX } from "@opentui/solid";
5
5
  import type { MascotPack } from "../core/types";
6
6
  import { createAnimatedRenderer } from "../core/ascii-renderer";
7
- import { onCelebrate, onVersion } from "../core/celebration-bus";
7
+ import { onCelebrate, onVersion, onScatter } from "../core/celebration-bus";
8
8
 
9
9
  interface HomeMascotProps {
10
10
  mascots: Record<string, MascotPack>;
@@ -55,6 +55,10 @@ export function HomeMascot(props: HomeMascotProps): JSX.Element {
55
55
  setTimeout(() => setZBoost(false), 3500);
56
56
  });
57
57
 
58
+ onScatter(() => {
59
+ renderers[currentName()].scatterIn();
60
+ });
61
+
58
62
  const stopDrag = () => {
59
63
  isDragging = false;
60
64
  renderers[currentName()].setDragging(false);
@@ -4,7 +4,7 @@ import { createSignal, onCleanup } from "solid-js";
4
4
  import type { JSX } from "@opentui/solid";
5
5
  import type { MascotPack, MascotState } from "../core/types";
6
6
  import { createAnimatedRenderer } from "../core/ascii-renderer";
7
- import { onCelebrate, onVersion } from "../core/celebration-bus";
7
+ import { onCelebrate, onVersion, onScatter } from "../core/celebration-bus";
8
8
 
9
9
  interface SidebarMascotProps {
10
10
  mascots: Record<string, MascotPack>;
@@ -185,6 +185,10 @@ export function SidebarMascot(props: SidebarMascotProps): JSX.Element {
185
185
  setTimeout(() => setZBoost(false), 3500);
186
186
  });
187
187
 
188
+ onScatter(() => {
189
+ renderers[currentName()].scatterIn();
190
+ });
191
+
188
192
  return (
189
193
  <box
190
194
  position="absolute"
@@ -52,6 +52,7 @@ export function createAnimatedRenderer(pack: MascotPack): {
52
52
  celebrateUpdate: (newVersion: string) => void;
53
53
  bounce: () => void;
54
54
  showVersion: (version: string) => void;
55
+ scatterIn: () => void;
55
56
  } {
56
57
  const anim = { ...DEFAULT_ANIM, ...pack.animations };
57
58
  const fg = pack.colors?.defaultFg || undefined;
@@ -68,10 +69,12 @@ export function createAnimatedRenderer(pack: MascotPack): {
68
69
  const [flashColor, setFlashColor] = createSignal<string | null>(null);
69
70
  const [dragMsg, setDragMsg] = createSignal<string | null>(null);
70
71
  const [zzz, setZzz] = createSignal<string | null>(null);
72
+ const [scatter, setScatter] = createSignal<{ dx: number; dy: number }[] | null>(null);
71
73
 
72
74
  let flashTimer: ReturnType<typeof setInterval> | null = null;
73
75
  let dragMsgTimer: ReturnType<typeof setInterval> | null = null;
74
76
  let zzzTimer: ReturnType<typeof setInterval> | null = null;
77
+ let scatterTimer: ReturnType<typeof setInterval> | null = null;
75
78
  let bounceTimers: ReturnType<typeof setTimeout>[] = [];
76
79
  let celebrateTimers: ReturnType<typeof setTimeout>[] = [];
77
80
  let versionTimer: ReturnType<typeof setTimeout> | null = null;
@@ -95,6 +98,10 @@ export function createAnimatedRenderer(pack: MascotPack): {
95
98
  if (versionTimer) { clearTimeout(versionTimer); versionTimer = null; }
96
99
  setCelebrate(null);
97
100
  };
101
+ const stopScatter = () => {
102
+ if (scatterTimer) { clearInterval(scatterTimer); scatterTimer = null; }
103
+ setScatter(null);
104
+ };
98
105
 
99
106
  const stopAllAnimations = () => {
100
107
  stopFlash();
@@ -266,6 +273,7 @@ export function createAnimatedRenderer(pack: MascotPack): {
266
273
  stopBounce();
267
274
  stopCelebrate();
268
275
  stopVersion();
276
+ stopScatter();
269
277
  if (zzzTimer) { clearInterval(zzzTimer); zzzTimer = null; }
270
278
  });
271
279
 
@@ -281,6 +289,7 @@ export function createAnimatedRenderer(pack: MascotPack): {
281
289
  flashColor();
282
290
  dragMsg();
283
291
  zzz();
292
+ scatter();
284
293
 
285
294
  for (const [, [get]] of extraSignals) {
286
295
  get();
@@ -317,14 +326,15 @@ export function createAnimatedRenderer(pack: MascotPack): {
317
326
  const left = offset > 0 ? offset : 0;
318
327
  const cel = celebrate();
319
328
  const dm = dragMsg();
329
+ const sc = scatter();
320
330
 
321
331
  return (
322
332
  <box flexDirection="column" alignItems="flex-start" left={left} top={top}>
323
333
  {cel ? <box position="absolute" top={-1} left={0}><text fg={flashColor() ?? fg}>{cel.text}</text></box> : null}
324
334
  {dm ? <box position="absolute" top={-1} left={0}><text fg="#FF4081">{dm}</text></box> : null}
325
335
  {zzz() ? <box position="absolute" top={-1} left={0}><text fg={flashColor() ?? fg}>{zzz()}</text></box> : null}
326
- {lines.map((line: string) => (
327
- <text fg={flashColor() ?? fg}>{line}</text>
336
+ {lines.map((line: string, i: number) => (
337
+ <text fg={flashColor() ?? fg} left={sc?.[i]?.dx ?? 0} top={sc?.[i]?.dy ?? 0}>{line}</text>
328
338
  ))}
329
339
  </box>
330
340
  );
@@ -355,10 +365,10 @@ export function createAnimatedRenderer(pack: MascotPack): {
355
365
 
356
366
  if (s === "sleeping") {
357
367
  let phase = 1;
358
- setZzz("");
368
+ setZzz("zᶻ...");
359
369
  zzzTimer = setInterval(() => {
360
370
  phase = (phase % 3) + 1;
361
- setZzz("ᶻ".repeat(phase));
371
+ setZzz("z" + "ᶻ".repeat(phase) + "...");
362
372
  }, 1500);
363
373
  } else {
364
374
  if (zzzTimer) { clearInterval(zzzTimer); zzzTimer = null; }
@@ -433,7 +443,25 @@ export function createAnimatedRenderer(pack: MascotPack): {
433
443
  setJumpOffset(-3);
434
444
  bounceTimers.push(setTimeout(() => setJumpOffset(-2), 150));
435
445
  bounceTimers.push(setTimeout(() => setJumpOffset(-1), 300));
436
- bounceTimers.push(setTimeout(() => { setJumpOffset(0); bounceTimers = []; }, 450));
446
+ bounceTimers.push(setTimeout(() => {
447
+ setJumpOffset(0);
448
+ bounceTimers = [];
449
+ if (Math.random() < 0.3) {
450
+ fallApart();
451
+ }
452
+ }, 450));
453
+ };
454
+
455
+ const fallApart = () => {
456
+ const lineCount = getFrameLines(pack, "default").length;
457
+ const offsets = Array.from({ length: lineCount }, (_, i) => ({
458
+ dx: Math.floor((Math.random() - 0.3) * 20),
459
+ dy: i === 0 ? 2 : Math.floor(Math.random() * 3) + 1,
460
+ }));
461
+ setScatter(offsets);
462
+ bounceTimers.push(setTimeout(() => {
463
+ scatterIn();
464
+ }, 1500));
437
465
  };
438
466
 
439
467
  const showVersion = (version: string) => {
@@ -442,5 +470,31 @@ export function createAnimatedRenderer(pack: MascotPack): {
442
470
  versionTimer = setTimeout(() => { setCelebrate(null); versionTimer = null; }, 3000);
443
471
  };
444
472
 
445
- return { element, setState, toggleWalk, setDragging, celebrateUpdate, bounce, showVersion };
473
+ const scatterIn = () => {
474
+ stopScatter();
475
+ const lineCount = getFrameLines(pack, "default").length;
476
+ const offsets = Array.from({ length: lineCount }, () => ({
477
+ dx: Math.floor((Math.random() - 0.5) * 30),
478
+ dy: Math.floor((Math.random() - 0.5) * 12),
479
+ }));
480
+ setScatter(offsets);
481
+
482
+ let ticks = 0;
483
+ const MAX_TICKS = 15;
484
+ scatterTimer = setInterval(() => {
485
+ ticks++;
486
+ if (ticks >= MAX_TICKS) {
487
+ setScatter(offsets.map(() => ({ dx: 0, dy: 0 })));
488
+ stopScatter();
489
+ return;
490
+ }
491
+ const t = ticks / MAX_TICKS;
492
+ setScatter(offsets.map(o => ({
493
+ dx: Math.round(o.dx * (1 - t)),
494
+ dy: Math.round(o.dy * (1 - t)),
495
+ })));
496
+ }, 80);
497
+ };
498
+
499
+ return { element, setState, toggleWalk, setDragging, celebrateUpdate, bounce, showVersion, scatterIn };
446
500
  }
@@ -2,6 +2,7 @@ const bus = new EventTarget();
2
2
 
3
3
  const CELEBRATE_EVENT = "mascot:celebrate";
4
4
  const VERSION_EVENT = "mascot:version";
5
+ const SCATTER_EVENT = "mascot:scatter";
5
6
 
6
7
  export function emitCelebrate(newVersion: string): void {
7
8
  bus.dispatchEvent(new CustomEvent(CELEBRATE_EVENT, { detail: { newVersion } }));
@@ -28,3 +29,13 @@ export function onVersion(handler: (version: string) => void): () => void {
28
29
  bus.addEventListener(VERSION_EVENT, listener);
29
30
  return () => bus.removeEventListener(VERSION_EVENT, listener);
30
31
  }
32
+
33
+ export function emitScatter(): void {
34
+ bus.dispatchEvent(new CustomEvent(SCATTER_EVENT));
35
+ }
36
+
37
+ export function onScatter(handler: () => void): () => void {
38
+ const listener = () => { handler(); };
39
+ bus.addEventListener(SCATTER_EVENT, listener);
40
+ return () => bus.removeEventListener(SCATTER_EVENT, listener);
41
+ }
package/tui.tsx CHANGED
@@ -7,7 +7,7 @@ import { loadAllMascots } from "./src/core/mascot-loader"
7
7
  import { SidebarMascot } from "./src/components/sidebar-mascot"
8
8
  import { HomeMascot } from "./src/components/home-mascot"
9
9
  import { checkAndUpdate } from "./src/core/updater"
10
- import { emitCelebrate, emitVersion } from "./src/core/celebration-bus"
10
+ import { emitCelebrate, emitVersion, emitScatter } from "./src/core/celebration-bus"
11
11
 
12
12
  const __filename = fileURLToPath(import.meta.url);
13
13
  const __dirname = dirname(__filename);
@@ -38,7 +38,8 @@ const tui: TuiPlugin = async (api, _options) => {
38
38
  emitCelebrate(newVersion);
39
39
  }).catch(() => {});
40
40
 
41
- setTimeout(() => emitVersion(pluginVersion), 1500);
41
+ setTimeout(() => emitScatter(), 100);
42
+ setTimeout(() => emitVersion(pluginVersion), 2000);
42
43
  }
43
44
 
44
45
  const plugin: TuiPluginModule = {