@mingxy/opencode-mascot 0.5.9 → 0.6.0

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 CHANGED
@@ -6,16 +6,25 @@ Customizable ASCII mascots that breathe, walk, sleep, get launched across the sc
6
6
 
7
7
  [English](./README.md) | [简体中文](./README_zh-CN.md)
8
8
 
9
+ ## 🏠 Home Page
10
+
11
+ ![home demo](./assets/demo-home.gif)
12
+
13
+ ## 💼 Work Page
14
+
15
+ ![work demo](./assets/demo-work.gif)
16
+
9
17
  ---
10
18
 
11
19
  ## ✨ Features
12
20
 
13
- ### 🎭 Built-in Characters (2)
21
+ ### 🎭 Built-in Characters (3)
14
22
 
15
23
  | Character | Description | Color |
16
24
  |-----------|-------------|-------|
17
25
  | **yueer** | Purple-haired girl with an ahoge, tsundere style, default mascot | `#8B7EB8` lavender |
18
26
  | **baozi** | A steaming hot bun, warm and cozy | `#D4885A` warm orange |
27
+ | **cat** | Orange tabby cat — purring, kneading, tail-swishing | `#FFA500` orange |
19
28
 
20
29
  Each character includes **5 expression frames**: default / blink / happy / thinking / sleeping
21
30
 
@@ -54,6 +63,17 @@ Each character includes **5 expression frames**: default / blink / happy / think
54
63
  | 15 | Alien text bubble | busy/thinking | 12 alien-text phrases rotate |
55
64
  | 16 | Drag panic | While dragging | `( °□° )` |
56
65
 
66
+ **cat exclusive:**
67
+
68
+ | # | Animation | Trigger | Effect |
69
+ |---|-----------|---------|--------|
70
+ | 17 | Tail swish | Every 0.6s | Tail `|_)` ↔ `|~)` |
71
+ | 18 | Ear twitch | Random (20% / 2.5s) | Ear `/\` → `/╲` |
72
+ | 19 | Pupil dilate | Random (15% / 4s, idle) | Eyes `o.o` → `@.@` |
73
+ | 20 | Kneading | idle state | Paws alternate `/|| |\` ↔ `/| ||\` |
74
+ | 21 | Purr bubble | busy/thinking | 12 purr phrases rotate (purrr~/mrrrow~/nyaa~) |
75
+ | 22 | Drag bristle | While dragging | Ears bristle `/╲╱╲` + shocked face `>.<` |
76
+
57
77
  ---
58
78
 
59
79
  ### 🖱️ Interactions (5)
@@ -157,7 +177,8 @@ opencode-mascot/
157
177
  │ │ └── sidebar-mascot.tsx # Work page mascot (peek-a-boo)
158
178
  │ └── builtins/
159
179
  │ ├── yueer/ # yueer (frames + effects)
160
- └── baozi/ # baozi (frames + effects)
180
+ ├── baozi/ # baozi (frames + effects)
181
+ │ └── cat/ # cat (frames + effects)
161
182
  ```
162
183
 
163
184
  ## 🎨 Custom Characters
@@ -197,16 +218,16 @@ All built-in animations (blink/breath/walk/jump/sleep/drag/color-flash/bomb/fall
197
218
 
198
219
  | Category | Count |
199
220
  |----------|-------|
200
- | Built-in characters | 2 |
221
+ | Built-in characters | 3 |
201
222
  | Expression frames | 5 / character |
202
- | Auto animations | 16 |
223
+ | Auto animations | 22 |
203
224
  | Interactions | 5 |
204
225
  | Peek-a-boo behaviors | 3 |
205
226
  | Random events | 3 |
206
227
  | Alien text phrases | 24 (12/character) |
207
228
  | Flash colors | 8 |
208
229
  | Drag alien text | 6 |
209
- | **Total** | **33+** |
230
+ | **Total** | **39+** |
210
231
 
211
232
  ## 📄 License
212
233
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mingxy/opencode-mascot",
3
- "version": "0.5.9",
3
+ "version": "0.6.0",
4
4
  "description": "OpenCode TUI mascot plugin framework - customizable ASCII mascots for your terminal",
5
5
  "author": "mingxy",
6
6
  "license": "MIT",
@@ -0,0 +1,48 @@
1
+ // 0123456789
2
+ const defaultFrame = [
3
+ " /\\_/\\ ",
4
+ " ( o.o ) ",
5
+ " > ^ < ",
6
+ " /| |\\ ",
7
+ "(_| |_) ",
8
+ ];
9
+
10
+ const blinkFrame = [
11
+ " /\\_/\\ ",
12
+ " ( -.- ) ",
13
+ " > ^ < ",
14
+ " /| |\\ ",
15
+ "(_| |_) ",
16
+ ];
17
+
18
+ const happyFrame = [
19
+ " /\\_/\\ ",
20
+ " ( ^ω^ ) ",
21
+ " > ω < ",
22
+ " /| |\\ ",
23
+ "(_| |_) ",
24
+ ];
25
+
26
+ const thinkingFrame = [
27
+ " /\\_/\\ ",
28
+ " ( o_o ) ",
29
+ " > ? < ",
30
+ " /| |\\ ",
31
+ "(_| |_) ",
32
+ ];
33
+
34
+ const sleepingFrame = [
35
+ " /\\_/\\ ",
36
+ " ( -.- ) ",
37
+ " > z < ",
38
+ " /| |\\ ",
39
+ "(_| |_) ",
40
+ ];
41
+
42
+ export const frames = {
43
+ default: defaultFrame,
44
+ blink: blinkFrame,
45
+ happy: happyFrame,
46
+ thinking: thinkingFrame,
47
+ sleeping: sleepingFrame,
48
+ };
@@ -0,0 +1,167 @@
1
+ import type { MascotPack, EffectRenderCtx } from "../../core/types";
2
+ import { frames } from "./frames";
3
+
4
+ const PURR_TEXTS = [
5
+ "ᵖᵘʳʳʳ~", "ᵐʳʳᵒʷ~", "ᵐʷᵃᵃ~", "ⁿʸᵃᵃ~",
6
+ "ᵖᵘʳʳ..", "ᵐʸᵃ~..", "ᶠᵘʳʳ~", "ᵐⁱᵃᵒ~",
7
+ "ᶜʰᵘʳʳ..", "ᵉᵘⁿ~..", "ˢʰᵖᵘʳʳ~", "ⁿʸᵒ~",
8
+ ];
9
+ const THINKING_FACES = ["o_o", "O_O", ">_o", "o_<", "⊙_⊙", "◑_◑"];
10
+
11
+ const catEffects: MascotPack["effects"] = {
12
+ signals: [
13
+ { name: "tailAlt", initial: false },
14
+ { name: "earTwitch", initial: false },
15
+ { name: "pupilWide", initial: false },
16
+ { name: "kneadAlt", initial: false },
17
+ { name: "purrIdx", initial: 0 },
18
+ { name: "thinkingFaceIdx", initial: 0 },
19
+ { name: "thinkingCountdown", initial: 0 },
20
+ ],
21
+
22
+ timers: [
23
+ {
24
+ interval: 600,
25
+ update(ctx) {
26
+ ctx.set("tailAlt", !(ctx.get("tailAlt") as boolean));
27
+ },
28
+ },
29
+ {
30
+ interval: 2500,
31
+ update(ctx) {
32
+ if (Math.random() < 0.2) {
33
+ ctx.set("earTwitch", true);
34
+ setTimeout(() => ctx.set("earTwitch", false), 250);
35
+ }
36
+ },
37
+ },
38
+ {
39
+ interval: 4000,
40
+ update(ctx) {
41
+ if (ctx.state === "idle" && Math.random() < 0.15) {
42
+ ctx.set("pupilWide", true);
43
+ setTimeout(() => ctx.set("pupilWide", false), 1000);
44
+ }
45
+ },
46
+ },
47
+ {
48
+ interval: 400,
49
+ update(ctx) {
50
+ if (ctx.state === "idle") {
51
+ ctx.set("kneadAlt", !(ctx.get("kneadAlt") as boolean));
52
+ }
53
+ },
54
+ },
55
+ {
56
+ interval: 2000,
57
+ update(ctx) {
58
+ if (ctx.state === "busy" || ctx.state === "thinking") {
59
+ ctx.set("purrIdx", ((ctx.get("purrIdx") as number) + 1) % PURR_TEXTS.length);
60
+ }
61
+ },
62
+ },
63
+ {
64
+ interval: 1000,
65
+ update(ctx) {
66
+ if (ctx.state === "thinking" || ctx.state === "busy") {
67
+ let cd = ctx.get("thinkingCountdown") as number;
68
+ if (!cd || cd <= 0) cd = 5 + Math.floor(Math.random() * 11);
69
+ cd--;
70
+ if (cd <= 0) {
71
+ ctx.set("thinkingFaceIdx", ((ctx.get("thinkingFaceIdx") as number) + 1) % THINKING_FACES.length);
72
+ }
73
+ ctx.set("thinkingCountdown", cd);
74
+ } else {
75
+ ctx.set("thinkingFaceIdx", 0);
76
+ ctx.set("thinkingCountdown", 0);
77
+ }
78
+ },
79
+ },
80
+ ],
81
+
82
+ render(lines: string[], ctx: EffectRenderCtx): string[] {
83
+ const { state, breathPhase, dragging, get } = ctx;
84
+ let result = [...lines];
85
+
86
+ const tailAlt = get("tailAlt") as boolean;
87
+ const earTwitch = get("earTwitch") as boolean;
88
+ const pupilWide = get("pupilWide") as boolean;
89
+ const kneadAlt = get("kneadAlt") as boolean;
90
+
91
+ if (dragging) {
92
+ result[0] = " /╲╱╲\\ ";
93
+ result[1] = " ( >.< ) ";
94
+ result[2] = " > ! < ";
95
+ result[3] = " /| |\\ ";
96
+ result[4] = "(_| |_) ";
97
+ return result;
98
+ }
99
+
100
+ if (!breathPhase) {
101
+ result = result.map((l) => l.replace(/\^/g, "'"));
102
+ }
103
+
104
+ if (earTwitch) {
105
+ result[0] = result[0].replace("/\\_/\\", "/╲_/\\");
106
+ }
107
+
108
+ if (tailAlt) {
109
+ result[4] = "(_| |~) ";
110
+ } else {
111
+ result[4] = "(_| |_) ";
112
+ }
113
+
114
+ if (pupilWide) {
115
+ const faceLine = result.findIndex((l) => /\(.*\)/.test(l));
116
+ if (faceLine >= 0) {
117
+ result[faceLine] = result[faceLine].replace("o.o", "@.@");
118
+ }
119
+ }
120
+
121
+ if (state === "idle") {
122
+ const armLine = result.findIndex((l) => l.includes("/| |\\"));
123
+ if (armLine >= 0) {
124
+ result[armLine] = kneadAlt ? " /|| |\\ " : " /| ||\\ ";
125
+ }
126
+ }
127
+
128
+ if (state === "thinking" || state === "busy") {
129
+ const faceIdx = get("thinkingFaceIdx") as number;
130
+ const faceLine = result.findIndex((l) => /\(.*\)/.test(l));
131
+ if (faceLine >= 0) {
132
+ result[faceLine] = result[faceLine].replace(/\(.*?\)/, `( ${THINKING_FACES[faceIdx]} )`);
133
+ }
134
+ }
135
+
136
+ if (state === "busy" || state === "thinking") {
137
+ const idx = get("purrIdx") as number;
138
+ const earLine = 0;
139
+ result[earLine] = result[earLine].trimEnd() + " " + PURR_TEXTS[idx];
140
+ }
141
+
142
+ return result;
143
+ },
144
+ };
145
+
146
+ export const catPack: MascotPack = {
147
+ name: "@mingxy/mascot-cat",
148
+ displayName: "小猫",
149
+ version: "0.1.0",
150
+ author: "mingxy",
151
+ description: "Orange tabby cat — purring, kneading, and knocking things off your terminal.",
152
+
153
+ frames,
154
+
155
+ colors: {
156
+ defaultFg: "#FFA500",
157
+ },
158
+
159
+ animations: {
160
+ blinkInterval: 2500,
161
+ blinkChance: 0.3,
162
+ expressionInterval: 8000,
163
+ idleTimeout: 90000,
164
+ },
165
+
166
+ effects: catEffects,
167
+ };
@@ -1,10 +1,12 @@
1
1
  import type { MascotPack } from "./types"
2
2
  import { yueerPack } from "../builtins/yueer"
3
3
  import { baoziPack } from "../builtins/baozi"
4
+ import { catPack } from "../builtins/cat"
4
5
 
5
6
  const BUILTINS: Record<string, MascotPack> = {
6
7
  yueer: yueerPack,
7
8
  baozi: baoziPack,
9
+ cat: catPack,
8
10
  }
9
11
 
10
12
  const ALL_NAMES = Object.keys(BUILTINS)