@mingxy/opencode-mascot 0.7.8 → 0.7.9

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.7.8",
3
+ "version": "0.7.9",
4
4
  "description": "OpenCode TUI mascot plugin framework - customizable ASCII mascots for your terminal",
5
5
  "author": "mingxy",
6
6
  "license": "MIT",
@@ -1,19 +1,6 @@
1
1
  import type { PropPack } from "../../core/types";
2
2
 
3
- /**
4
- * Pad 道具 — busy 状态变体道具
5
- *
6
- * 尺寸: 18 宽 × 9 高(双层框+.•摄像头+底部◉Home键)
7
- * 屏幕区: 12 宽 × 4 高
8
- * 叙事: 月儿小人玩游戏,又菜又爱玩
9
- * 贪吃蛇 6帧: 开局→走→撞墙→哭→不服→重开
10
- * 俄罗斯方块 4帧: 开局→堆积→满→over
11
- * 2048 4帧: 开局→合并→128→over
12
- * 切换 1帧: next!
13
- * 月儿小人: ☆ 呆毛 + (^-^) 圆脸 + ┃█┃ 身体(迷你版)
14
- */
15
-
16
- const W = 12; // 屏幕内容区宽
3
+ const W = 12;
17
4
 
18
5
  const OUTER_TOP = "╭.•" + "─".repeat(14) + "╮";
19
6
  const INNER_TOP = "│ ┌" + "─".repeat(W) + "┐ │";
@@ -25,24 +12,20 @@ const scr = (rows: string[]) =>
25
12
  rows.map((r) => "│ │" + r.padEnd(W) + "│ │");
26
13
 
27
14
  const games: string[][] = [
28
- // 贪吃蛇 6帧
29
15
  [" ☆ ᵇᵉᵍⁱⁿ ", "(^-^) ◯→ ●", " ┃█┃ ˢ:0 ", " "],
30
16
  [" ☆ ", "(^-^) ◯◯→ ●", " ┃█┃ ˢ:1 ", " "],
31
17
  [" ☆ ", "(×_×) ◯◯💥 ", " ┃█┃ ᵍᵍ! ", " "],
32
18
  [" ☆ ", "(╥_╥) ᵒᵛᵉʳ ", " ┃█┃ ˢ:1 ", " "],
33
19
  [" ☆ ", "(¬_¬) ᵃᵍᵃⁱⁿ", " ┃█┃ ", " "],
34
20
  [" ☆ ᵍᵒ! ", "(^-^) ◯→ ●", " ┃█┃ ˢ:0 ", " "],
35
- // 俄罗斯方块 4帧
36
21
  [" ☆ ᵇᵉᵍⁱⁿ ", "(^-^) ▣ ", " ┃█┃ ", " "],
37
22
  [" ☆ ", "(^-^) ▣▣ ", " ┃█┃ ▣▣▣▣ ", " "],
38
23
  [" ☆ ", "(×_×) ", " ┃█┃ ▣▣▣▣▣▣", " ᶠᵘˡˡ! "],
39
24
  [" ☆ ", "(╥_╥) ᵒᵛᵉʳ ", " ┃█┃ ", " "],
40
- // 2048 4帧
41
25
  [" ☆ ᵇᵉᵍⁱⁿ ", "(^-^) [ 2 ]", " ┃█┃ ", " "],
42
26
  [" ☆ ", "(^-^) [ 4 ]", " ┃█┃ ᵒᵒⁿ ", " "],
43
27
  [" ☆ ", "(⊙_⊙) [128]", " ┃█┃ ʷᵒʷ ", " "],
44
28
  [" ☆ ", "(╥_╥) ᵒᵛᵉʳ ", " ┃█┃ ", " "],
45
- // 切换
46
29
  [" ☆ ", "(^-^) ⁿᵉˣᵗ!", " ┃█┃ ", " "],
47
30
  ];
48
31
 
@@ -157,7 +157,19 @@ export function HomeMascot(props: HomeMascotProps): JSX.Element {
157
157
  onMouseUp={() => { stopDrag(); }}
158
158
  onMouseDragEnd={() => { stopDrag(); }}
159
159
  >
160
- {renderers[currentName()]?.element() ?? null}
160
+ {renderers[currentName()]?.propElement() ? (
161
+ <box
162
+ position="absolute"
163
+ zIndex={50}
164
+ left={renderers[currentName()].getPropPosition() === "side-left" ? -16 : renderers[currentName()].getPropPosition() === "side-right" ? 12 : 0}
165
+ top={0}
166
+ >
167
+ {renderers[currentName()].propElement()}
168
+ </box>
169
+ ) : null}
170
+ <box zIndex={100}>
171
+ {renderers[currentName()]?.element() ?? null}
172
+ </box>
161
173
  </box>
162
174
  );
163
175
  }
@@ -159,16 +159,8 @@ export function SidebarMascot(props: SidebarMascotProps): JSX.Element {
159
159
  if (statusType === "busy" || statusType === "retry") {
160
160
  if (hideSide) returnToView();
161
161
  renderers[currentName()].setState("busy");
162
- // 先显示箱子"打开"动画300ms,再切换到 busy 道具(道具从箱子里掉出来)
163
162
  const busyProp = pickPropByTrigger("busy");
164
- if (busyProp) {
165
- renderers[currentName()].setProp(getProp("box") ?? null);
166
- setTimeout(() => {
167
- renderers[currentName()].setProp(busyProp);
168
- }, 300);
169
- } else {
170
- renderers[currentName()].setProp(null);
171
- }
163
+ renderers[currentName()].setProp(busyProp);
172
164
  } else {
173
165
  setStateWithSwitch("idle");
174
166
  renderers[currentName()].setProp(null);
@@ -279,7 +271,19 @@ export function SidebarMascot(props: SidebarMascotProps): JSX.Element {
279
271
  checkEdge();
280
272
  }}
281
273
  >
282
- {renderers[currentName()]?.element() ?? null}
274
+ {renderers[currentName()]?.propElement() ? (
275
+ <box
276
+ position="absolute"
277
+ zIndex={50}
278
+ left={renderers[currentName()].getPropPosition() === "side-left" ? -16 : renderers[currentName()].getPropPosition() === "side-right" ? 12 : 0}
279
+ top={0}
280
+ >
281
+ {renderers[currentName()].propElement()}
282
+ </box>
283
+ ) : null}
284
+ <box zIndex={100}>
285
+ {renderers[currentName()]?.element() ?? null}
286
+ </box>
283
287
  </box>
284
288
  );
285
289
  }
@@ -46,6 +46,8 @@ function getFrameLines(pack: MascotPack, frameName: string): string[] {
46
46
 
47
47
  export function createAnimatedRenderer(pack: MascotPack): {
48
48
  element: () => JSX.Element;
49
+ propElement: () => JSX.Element | null;
50
+ getPropPosition: () => PropPosition | null;
49
51
  getState: () => MascotState;
50
52
  setState: (s: MascotState) => void;
51
53
  toggleWalk: () => void;
@@ -331,9 +333,6 @@ export function createAnimatedRenderer(pack: MascotPack): {
331
333
  scatter();
332
334
  bomb();
333
335
  versionMsg();
334
- activeProp();
335
- propFrameIdx();
336
- propPosition();
337
336
 
338
337
  for (const [, [get]] of extraSignals) {
339
338
  get();
@@ -368,47 +367,6 @@ export function createAnimatedRenderer(pack: MascotPack): {
368
367
  lines = effects.render(lines, renderCtx);
369
368
  }
370
369
 
371
- // ─── Prop overlay ───
372
- const prop = activeProp();
373
- if (prop) {
374
- const propFramesRaw = Array.isArray(prop.frames[0])
375
- ? (prop.frames as string[][])
376
- : [prop.frames as string[]];
377
- const propLines = propFramesRaw[propFrameIdx() % propFramesRaw.length] ?? propFramesRaw[0];
378
-
379
- if (propLines.length > 0) {
380
- const pos = propPosition();
381
- if (pos === 'front') {
382
- const overlayCount = Math.min(propLines.length, lines.length);
383
- const startRow = Math.floor((lines.length - overlayCount) / 2);
384
- for (let i = 0; i < overlayCount; i++) {
385
- lines[startRow + i] = propLines[i];
386
- }
387
- } else {
388
- const charWidth = lines[0]?.length ?? 0;
389
- const propWidth = propLines[0]?.length ?? 0;
390
- const charHeight = lines.length;
391
- const propHeight = propLines.length;
392
- const maxLines = Math.max(charHeight, propHeight);
393
- const charPad = Math.floor((maxLines - charHeight) / 2);
394
- const propPad = Math.floor((maxLines - propHeight) / 2);
395
- const sep = " ";
396
-
397
- const merged: string[] = [];
398
- for (let i = 0; i < maxLines; i++) {
399
- const cLine = (i >= charPad && i < charPad + charHeight)
400
- ? lines[i - charPad]
401
- : " ".repeat(charWidth);
402
- const pLine = (i >= propPad && i < propPad + propHeight)
403
- ? propLines[i - propPad]
404
- : " ".repeat(propWidth);
405
- merged.push(pos === 'side-right' ? cLine + sep + pLine : pLine + sep + cLine);
406
- }
407
- lines = merged;
408
- }
409
- }
410
- }
411
-
412
370
  const top = jumpOffset();
413
371
  const left = offset > 0 ? offset : 0;
414
372
  const cel = celebrate();
@@ -435,6 +393,26 @@ export function createAnimatedRenderer(pack: MascotPack): {
435
393
  );
436
394
  };
437
395
 
396
+ const propElement = () => {
397
+ activeProp();
398
+ propFrameIdx();
399
+ const prop = activeProp();
400
+ if (!prop) return null;
401
+
402
+ const propFramesRaw = Array.isArray(prop.frames[0])
403
+ ? (prop.frames as string[][])
404
+ : [prop.frames as string[]];
405
+ const propLines = propFramesRaw[propFrameIdx() % propFramesRaw.length] ?? propFramesRaw[0];
406
+
407
+ return (
408
+ <box flexDirection="column" alignItems="flex-start">
409
+ {propLines.map((line: string) => (
410
+ <text fg={fg}>{line}</text>
411
+ ))}
412
+ </box>
413
+ );
414
+ };
415
+
438
416
  // ─── State control ───
439
417
  const setState = (s: MascotState) => {
440
418
  setCurrentState(s);
@@ -658,5 +636,5 @@ export function createAnimatedRenderer(pack: MascotPack): {
658
636
 
659
637
  const getProp = () => activeProp();
660
638
 
661
- return { element, getState: currentState, setState, toggleWalk, setDragging, setCharacterHidden, celebrateUpdate, bounce, showVersion, scatterIn, explode, setProp, getProp };
639
+ return { element, propElement, getPropPosition: () => propPosition(), getState: currentState, setState, toggleWalk, setDragging, setCharacterHidden, celebrateUpdate, bounce, showVersion, scatterIn, explode, setProp, getProp };
662
640
  }