lazy-skill 0.5.0 → 0.7.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.
@@ -6,10 +6,10 @@ import { THEMES, style } from "../ui/theme.js";
6
6
  import { boxWidth, visibleWidth } from "../ui/layout.js";
7
7
  import { renderQr } from "../ui/qr.js";
8
8
  import { blank, box, centre, fail, hint, line, muted, ok, status } from "../ui/layout.js";
9
- import { banner, mascot } from "../ui/banner.js";
9
+ import { banner } from "../ui/banner.js";
10
10
  import { messages } from "../ui/messages.js";
11
11
  import { Spinner } from "../ui/spinner.js";
12
- const VERSION = "0.5.0";
12
+ const VERSION = "0.7.0";
13
13
  function friendlyPlatform() {
14
14
  const p = platform();
15
15
  return p === "darwin" || p === "win32" || p === "linux" ? p : "unknown";
@@ -71,14 +71,6 @@ export async function connectCommand(options) {
71
71
  const plate = await renderQr(session.pairUrl);
72
72
  const width = plate.length ? visibleWidth(plate[0]) + 4 : boxWidth();
73
73
  box(theme, plate.map((row) => centre(row, width)), width);
74
- // Bit sits under the code rather than over it: anything overlapping the
75
- // module area would cost scan reliability for decoration.
76
- blank();
77
- const bit = mascot(theme, false);
78
- for (let i = 0; i < bit.length; i++) {
79
- const caption = i === 2 ? ` ${style.gray("waiting for your phone")}` : "";
80
- console.log(` ${centre(bit[i], width - 18)}${caption}`);
81
- }
82
74
  blank();
83
75
  muted(session.pairUrl);
84
76
  blank();
@@ -1,6 +1,6 @@
1
1
  import { readConfig, updateConfig } from "../lib/config.js";
2
2
  import { THEMES, THEME_IDS, rgb, style } from "../ui/theme.js";
3
- import { banner, mascot } from "../ui/banner.js";
3
+ import { banner } from "../ui/banner.js";
4
4
  import { pointer, select } from "../ui/select.js";
5
5
  import { blank, muted, ok } from "../ui/layout.js";
6
6
  /**
@@ -30,7 +30,6 @@ export async function themeCommand(requested, version) {
30
30
  initial: THEME_IDS.indexOf(current),
31
31
  render: (index) => {
32
32
  const theme = THEMES[THEME_IDS[index]];
33
- const bit = mascot(theme, true);
34
33
  const out = [""];
35
34
  out.push(...banner(theme, version));
36
35
  out.push("");
@@ -42,8 +41,7 @@ export async function themeCommand(requested, version) {
42
41
  // padding a coloured string leaves the column ragged.
43
42
  const padded = t.label.padEnd(14);
44
43
  const label = active ? style.bold(padded) : style.gray(padded);
45
- const beside = i >= 1 && i <= 5 ? " " + bit[i - 1] : "";
46
- out.push(` ${pointer(active)} ${mark} ${rgb(t.accent, "████")} ${label}${beside}`);
44
+ out.push(` ${pointer(active)} ${mark} ${rgb(t.accent, "████")} ${label}`);
47
45
  });
48
46
  out.push("");
49
47
  out.push(` ${style.gray("↑ ↓ to move · enter to choose")}`);
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import { installCommand } from "./commands/install.js";
10
10
  import { themeCommand } from "./commands/theme.js";
11
11
  import { listenCommand } from "./commands/listen.js";
12
12
  import { warnIfOutdated } from "./lib/version-check.js";
13
- const VERSION = "0.5.0";
13
+ const VERSION = "0.7.0";
14
14
  function help() {
15
15
  const theme = THEMES[readConfig().theme];
16
16
  const cmd = (name) => rgb(theme.accent, name.padEnd(26));
package/dist/ui/banner.js CHANGED
@@ -41,21 +41,34 @@ export function banner(theme, version) {
41
41
  return lines;
42
42
  }
43
43
  /**
44
- * Bit, in blocks, for sitting beside things.
44
+ * Bit, hand-drawn.
45
45
  *
46
- * A deliberately small silhouette the hood, the closed eyes, the muzzle.
47
- * Anything more detailed turns to mush at terminal resolution.
46
+ * The generated 32x32 sprite is right for the phone, where there is
47
+ * resolution to spare. In a terminal it turned to mush: ellipses at that
48
+ * scale produce a cream blob with dark smears, and no amount of nudging the
49
+ * shapes fixed it. This is drawn character by character instead — a hood, a
50
+ * face, two closed eyes, a nose and a smile — which is far fewer pixels but
51
+ * reads instantly, which is the only thing that matters here.
52
+ *
53
+ * Every row is exactly 13 cells wide. Ragged rows shear the silhouette, and
54
+ * the colour codes make that impossible to spot by eye.
48
55
  */
56
+ const FACE = [227, 201, 164];
57
+ const FEATURE = [70, 48, 35];
49
58
  export function mascot(theme, awake = false) {
50
59
  const hood = (t) => rgb(theme.accent, t);
51
- // Every row is exactly ten cells wide. Ragged rows shear the silhouette,
52
- // and the colour codes make it impossible to eyeball.
53
- const eyes = awake ? `${style.bold("o")} ${style.bold("o")}` : "- -";
60
+ const face = (t) => rgb(FACE, t);
61
+ const mark = (t) => rgb(FEATURE, t);
62
+ const zzz = (t) => rgb(theme.support, t);
63
+ // Open eyes are dots; closed eyes are the lower half of a block, which
64
+ // reads as a lid rather than a gap.
65
+ const eyes = awake ? mark("●●") : mark("▄▄");
54
66
  return [
55
- hood(" ▄████▄ "),
56
- hood(" ██") + style.gray("‾‾‾‾") + hood("██ "),
57
- hood(" █ ") + eyes + hood(" █ "),
58
- hood(" ") + style.gray("‿‿") + hood(" "),
59
- hood(" ▀████▀ "),
67
+ hood(" ▄▄▄▄▄▄▄ "),
68
+ hood("") + face("███████") + hood(""),
69
+ hood(" █") + face("") + eyes + face("█") + eyes + face("█") + hood("█ ") + (awake ? "" : zzz("z")),
70
+ hood(" █") + face("███") + mark("▾") + face("███") + hood(" ") + (awake ? "" : zzz("z")),
71
+ hood(" █") + face("██") + mark("‿‿‿") + face("██") + hood("█ "),
72
+ hood(" ▀▀▀▀▀▀▀ "),
60
73
  ];
61
74
  }
@@ -1,5 +1,5 @@
1
1
  import { THEMES, THEME_IDS, rgb, style } from "./theme.js";
2
- import { banner, mascot } from "./banner.js";
2
+ import { banner } from "./banner.js";
3
3
  import { pointer, select } from "./select.js";
4
4
  /**
5
5
  * First-run theme choice, with live preview.
@@ -14,7 +14,6 @@ export async function chooseTheme(version) {
14
14
  const rows = (index) => {
15
15
  const id = THEME_IDS[index];
16
16
  const theme = THEMES[id];
17
- const bit = mascot(theme, true);
18
17
  const out = [""];
19
18
  out.push(...banner(theme, version));
20
19
  out.push("");
@@ -29,8 +28,7 @@ export async function chooseTheme(version) {
29
28
  const padded = t.label.padEnd(14);
30
29
  const label = active ? style.bold(padded) : style.gray(padded);
31
30
  // The mascot sits beside the list, aligned to its middle rows.
32
- const beside = i >= 1 && i <= 5 ? " " + bit[i - 1] : "";
33
- out.push(` ${pointer(active)} ${swatch} ${label}${beside}`);
31
+ out.push(` ${pointer(active)} ${swatch} ${label}`);
34
32
  });
35
33
  out.push("");
36
34
  out.push(` ${style.gray("↑ ↓ to move · enter to choose")}`);
@@ -0,0 +1,273 @@
1
+ // GENERATED by scripts/build-sprite.mjs — do not edit by hand.
2
+ // Run `pnpm sprite` after changing scripts/gen-sloth.mjs.
3
+ /**
4
+ * BIT — the Lazy Skill mascot. A sleepy sloth in a hoodie.
5
+ *
6
+ * Stored as a 32x32 character grid rather than SVG paths so one source
7
+ * scales crisply to any size, retints with the active theme, and swaps
8
+ * expression without a second asset.
9
+ *
10
+ * Legend: . transparent | o outline | H hood | h hood shadow | L hood light
11
+ * F fur | M muzzle | P eye patch | e eye | n nose | w eye white
12
+ */
13
+ export const SPRITE_SIZE = 32;
14
+ /** Palette keys resolve against CSS custom properties, so themes apply free. */
15
+ /** Terminal colours are literal RGB — no CSS variables to resolve. The hood
16
+ * is substituted per theme at render time; everything else is fixed. */
17
+ export const PX = {
18
+ ".": null,
19
+ o: "#120d1c",
20
+ H: "var(--ls-accent)",
21
+ h: "var(--ls-accent-lo)",
22
+ L: "var(--ls-accent-hi)",
23
+ F: "#e3c9a4",
24
+ M: "#f6e6cd",
25
+ P: "#5b4130",
26
+ e: "#2c1e16",
27
+ n: "#3d2b20",
28
+ w: "#fdf6ea",
29
+ };
30
+ export const EXPRESSIONS = ["idle", "curious", "waiting", "happy", "working", "excited", "annoyed"];
31
+ export const SPRITES = {
32
+ idle: [
33
+ "................................",
34
+ "................................",
35
+ "................o...............",
36
+ "............ooooHoooo...........",
37
+ "..........ooLLLLLLhhhoo.........",
38
+ "........ooLLLLLLLhhhhhhoo.......",
39
+ ".......oLLLLLLLLhhhhhhhhho......",
40
+ "......oLLLLLLLLhhhhhhhhhhho.....",
41
+ "......oHLLLLLLFFFFFhhhhhhho.....",
42
+ ".....oHHHHHFFFFFFFFFFFhhhhho....",
43
+ ".....oHHHHFFFFFFFFFFFFFhhhho....",
44
+ ".....oHHHFFFFFFFFFFFFFFFhhho....",
45
+ ".....oHHFFFFFFFFFFFFFFFFFhho....",
46
+ "....oHHHFFPPPPFFFFFPPPPFFhhho...",
47
+ "....oHHHFPwwwwFFFFFwwwwPFhho....",
48
+ "....oHHFFPeeeeFFFFFeeeePFFho....",
49
+ "....oHHFFPPPPPFFFFFPPPPPFFho....",
50
+ "....oHFFPPPFFFFFFFFFFFPPPFFo....",
51
+ "....oHFFFPPFFFnnnnMFFFPPFFFo....",
52
+ "....oHHHFFFFMMnnnnMMMFFFFhho....",
53
+ "....oHFFFFFFMMMnnMMMMFFFFFFo....",
54
+ "....oHFFFFFFMnMMMMnMMFFFFFFo....",
55
+ "....oHHHHHFFMMnnnnMMMFFhhhho....",
56
+ "....oHHHHHHFFFMMMMMFFFhhhhho....",
57
+ "....oHHHHHHHHHFFFFFhhhhhhhho....",
58
+ "....ooHHHHHHHHHHHhhhhhhhhhoo....",
59
+ "......oHHHHHHHHHHHhhhhhhho......",
60
+ "....ooHHHHHHHHHHHHHHHHhHHHoo....",
61
+ "...oHHHHHHHHHHHHHHHHHHHHHHHHo...",
62
+ "...oHHHHHHHHHHHHHHHHHHHHHHHHo...",
63
+ "...oHHHHHHHHHHHHHHHHHHHHHHHHo...",
64
+ "...oooooooooooooooooooooooooo...",
65
+ ],
66
+ curious: [
67
+ "................................",
68
+ "................................",
69
+ "................o...............",
70
+ "............ooooHoooo...........",
71
+ "..........ooLLLLLLhhhoo.........",
72
+ "........ooLLLLLLLhhhhhhoo.......",
73
+ ".......oLLLLLLLLhhhhhhhhho......",
74
+ "......oLLLLLLLLhhhhhhhhhhho.....",
75
+ "......oHLLLLLLFFFFFhhhhhhho.....",
76
+ ".....oHHHHHFFFFFFFFFFFhhhhho....",
77
+ ".....oHHHHFFFFFFFFFFFFFhhhho....",
78
+ ".....oHHHFFFFFFFFFFFFFFFhhho....",
79
+ ".....oHHFFFFFFFFFFFFFFFFFhho....",
80
+ "....oHHHFFPwwPFFFFFPwwPFFhhho...",
81
+ "....oHHHFPwwewFFFFFwwewPFhho....",
82
+ "....oHHFFPweewFFFFFweewPFFho....",
83
+ "....oHHFFPPwwPFFFFFPwwPPFFho....",
84
+ "....oHFFPPPFFFFFFFFFFFPPPFFo....",
85
+ "....oHFFFPPFFFnnnnMFFFPPFFFo....",
86
+ "....oHHHFFFFMMnnnnMMMFFFFhho....",
87
+ "....oHFFFFFFMMMnnMMMMFFFFFFo....",
88
+ "....oHFFFFFFMnMMMMnMMFFFFFFo....",
89
+ "....oHHHHHFFMMnnnnMMMFFhhhho....",
90
+ "....oHHHHHHFFFMMMMMFFFhhhhho....",
91
+ "....oHHHHHHHHHFFFFFhhhhhhhho....",
92
+ "....ooHHHHHHHHHHHhhhhhhhhhoo....",
93
+ "......oHHHHHHHHHHHhhhhhhho......",
94
+ "....ooHHHHHHHHHHHHHHHHhHHHoo....",
95
+ "...oHHHHHHHHHHHHHHHHHHHHHHHHo...",
96
+ "...oHHHHHHHHHHHHHHHHHHHHHHHHo...",
97
+ "...oHHHHHHHHHHHHHHHHHHHHHHHHo...",
98
+ "...oooooooooooooooooooooooooo...",
99
+ ],
100
+ waiting: [
101
+ "................................",
102
+ "................................",
103
+ "................o...............",
104
+ "............ooooHoooo...........",
105
+ "..........ooLLLLLLhhhoo.........",
106
+ "........ooLLLLLLLhhhhhhoo.......",
107
+ ".......oLLLLLLLLhhhhhhhhho......",
108
+ "......oLLLLLLLLhhhhhhhhhhho.....",
109
+ "......oHLLLLLLFFFFFhhhhhhho.....",
110
+ ".....oHHHHHFFFFFFFFFFFhhhhho....",
111
+ ".....oHHHHFFFFFFFFFFFFFhhhho....",
112
+ ".....oHHHFFFFFFFFFFFFFFFhhho....",
113
+ ".....oHHFFFFFFFFFFFFFFFFFhho....",
114
+ "....oHHHFFPPPPFFFFFPPPPFFhhho...",
115
+ "....oHHHFPeeeeFFFFFwwwwPFhho....",
116
+ "....oHHFFPeeeeFFFFFeeeePFFho....",
117
+ "....oHHFFPPPPPFFFFFPPPPPFFho....",
118
+ "....oHFFPPPFFFFFFFFFFFPPPFFo....",
119
+ "....oHFFFPPFFFnnnnMFFFPPFFFo....",
120
+ "....oHHHFFFFMMnnnnMMMFFFFhho....",
121
+ "....oHFFFFFFMMMnnMMMMFFFFFFo....",
122
+ "....oHFFFFFFMnMMMMnMMFFFFFFo....",
123
+ "....oHHHHHFFMMnnnnMMMFFhhhho....",
124
+ "....oHHHHHHFFFMMMMMFFFhhhhho....",
125
+ "....oHHHHHHHHHFFFFFhhhhhhhho....",
126
+ "....ooHHHHHHHHHHHhhhhhhhhhoo....",
127
+ "......oHHHHHHHHHHHhhhhhhho......",
128
+ "....ooHHHHHHHHHHHHHHHHhHHHoo....",
129
+ "...oHHHHHHHHHHHHHHHHHHHHHHHHo...",
130
+ "...oHHHHHHHHHHHHHHHHHHHHHHHHo...",
131
+ "...oHHHHHHHHHHHHHHHHHHHHHHHHo...",
132
+ "...oooooooooooooooooooooooooo...",
133
+ ],
134
+ happy: [
135
+ "................................",
136
+ "................................",
137
+ "................o...............",
138
+ "............ooooHoooo...........",
139
+ "..........ooLLLLLLhhhoo.........",
140
+ "........ooLLLLLLLhhhhhhoo.......",
141
+ ".......oLLLLLLLLhhhhhhhhho......",
142
+ "......oLLLLLLLLhhhhhhhhhhho.....",
143
+ "......oHLLLLLLFFFFFhhhhhhho.....",
144
+ ".....oHHHHHFFFFFFFFFFFhhhhho....",
145
+ ".....oHHHHFFFFFFFFFFFFFhhhho....",
146
+ ".....oHHHFFFFFFFFFFFFFFFhhho....",
147
+ ".....oHHFFFFFFFFFFFFFFFFFhho....",
148
+ "....oHHHFFPPPPFFFFFPPPPFFhhho...",
149
+ "....oHHHFPPwwPFFFFFPwePPFhho....",
150
+ "....oHHFFPweePFFFFFPeewPFFho....",
151
+ "....oHHFFPPwPPFFFFFPPwPPFFho....",
152
+ "....oHFFPPPFFFFFFFFFFFPPPFFo....",
153
+ "....oHFFFPPFFFnnnnMFFFPPFFFo....",
154
+ "....oHHHFFFFMMnnnnMMMFFFFhho....",
155
+ "....oHFFFFFFMMMnnMMMMFFFFFFo....",
156
+ "....oHFFFFFFMnMMMMnMMFFFFFFo....",
157
+ "....oHHHHHFFMMnnnnMMMFFhhhho....",
158
+ "....oHHHHHHFFFMMMMMFFFhhhhho....",
159
+ "....oHHHHHHHHHFFFFFhhhhhhhho....",
160
+ "....ooHHHHHHHHHHHhhhhhhhhhoo....",
161
+ "......oHHHHHHHHHHHhhhhhhho......",
162
+ "....ooHHHHHHHHHHHHHHHHhHHHoo....",
163
+ "...oHHHHHHHHHHHHHHHHHHHHHHHHo...",
164
+ "...oHHHHHHHHHHHHHHHHHHHHHHHHo...",
165
+ "...oHHHHHHHHHHHHHHHHHHHHHHHHo...",
166
+ "...oooooooooooooooooooooooooo...",
167
+ ],
168
+ working: [
169
+ "................................",
170
+ "................................",
171
+ "................o...............",
172
+ "............ooooHoooo...........",
173
+ "..........ooLLLLLLhhhoo.........",
174
+ "........ooLLLLLLLhhhhhhoo.......",
175
+ ".......oLLLLLLLLhhhhhhhhho......",
176
+ "......oLLLLLLLLhhhhhhhhhhho.....",
177
+ "......oHLLLLLLFFFFFhhhhhhho.....",
178
+ ".....oHHHHHFFFFFFFFFFFhhhhho....",
179
+ ".....oHHHHFFFFFFFFFFFFFhhhho....",
180
+ ".....oHHHFFFFFFFFFFFFFFFhhho....",
181
+ ".....oHHFFFFFFFFFFFFFFFFFhho....",
182
+ "....oHHHFFPPPPFFFFFPPPPFFhhho...",
183
+ "....oHHHFPwwwwFFFFFwwwwPFhho....",
184
+ "....oHHFFPPPPPFFFFFPPPPPFFho....",
185
+ "....oHHFFPPPPPFFFFFPPPPPFFho....",
186
+ "....oHFFPPPFFFFFFFFFFFPPPFFo....",
187
+ "....oHFFFPPFFFnnnnMFFFPPFFFo....",
188
+ "....oHHHFFFFMMnnnnMMMFFFFhho....",
189
+ "....oHFFFFFFMMMnnMMMMFFFFFFo....",
190
+ "....oHFFFFFFMnMMMMnMMFFFFFFo....",
191
+ "....oHHHHHFFMMnnnnMMMFFhhhho....",
192
+ "....oHHHHHHFFFMMMMMFFFhhhhho....",
193
+ "....oHHHHHHHHHFFFFFhhhhhhhho....",
194
+ "....ooHHHHHHHHHHHhhhhhhhhhoo....",
195
+ "......oHHHHHHHHHHHhhhhhhho......",
196
+ "....ooHHHHHHHHHHHHHHHHhHHHoo....",
197
+ "...oHHHHHHHHHHHHHHHHHHHHHHHHo...",
198
+ "...oHHHHHHHHHHHHHHHHHHHHHHHHo...",
199
+ "...oHHHHHHHHHHHHHHHHHHHHHHHHo...",
200
+ "...oooooooooooooooooooooooooo...",
201
+ ],
202
+ excited: [
203
+ "................................",
204
+ "................................",
205
+ "................o...............",
206
+ "............ooooHoooo...........",
207
+ "..........ooLLLLLLhhhoo.........",
208
+ "........ooLLLLLLLhhhhhhoo.......",
209
+ ".......oLLLLLLLLhhhhhhhhho......",
210
+ "......oLLLLLLLLhhhhhhhhhhho.....",
211
+ "......oHLLLLLLFFFFFhhhhhhho.....",
212
+ ".....oHHHHHFFFFFFFFFFFhhhhho....",
213
+ ".....oHHHHFFFFFFFFFFFFFhhhho....",
214
+ ".....oHHHFFFFFFFFFFFFFFFhhho....",
215
+ ".....oHHFFFFFFFFFFFFFFFFFhho....",
216
+ "....oHHHFFPPPPFFFFFPPPPFFhhho...",
217
+ "....oHHHFPPwwPFFFFFPwePPFhho....",
218
+ "....oHHFFPweePFFFFFPeewPFFho....",
219
+ "....oHHFFPPwPPFFFFFPPwPPFFho....",
220
+ "....oHFFPPPFFFFFFFFFFFPPPFFo....",
221
+ "....oHFFFPPFFFnnnnMFFFPPFFFo....",
222
+ "....oHHHFFFFMMnnnnMMMFFFFhho....",
223
+ "....oHFFFFFFMMMnnMMMMFFFFFFo....",
224
+ "....oHFFFFFFMnMMMMnMMFFFFFFo....",
225
+ "....oHHHHHFFMMnnnnMMMFFhhhho....",
226
+ "....oHHHHHHFFFMMMMMFFFhhhhho....",
227
+ "....oHHHHHHHHHFFFFFhhhhhhhho....",
228
+ "....ooHHHHHHHHHHHhhhhhhhhhoo....",
229
+ "......oHHHHHHHHHHHhhhhhhho......",
230
+ "....ooHHHHHHHHHHHHHHHHhHHHoo....",
231
+ "...oHHHHHHHHHHHHHHHHHHHHHHHHo...",
232
+ "...oHHHHHHHHHHHHHHHHHHHHHHHHo...",
233
+ "...oHHHHHHHHHHHHHHHHHHHHHHHHo...",
234
+ "...oooooooooooooooooooooooooo...",
235
+ ],
236
+ annoyed: [
237
+ "................................",
238
+ "................................",
239
+ "................o...............",
240
+ "............ooooHoooo...........",
241
+ "..........ooLLLLLLhhhoo.........",
242
+ "........ooLLLLLLLhhhhhhoo.......",
243
+ ".......oLLLLLLLLhhhhhhhhho......",
244
+ "......oLLLLLLLLhhhhhhhhhhho.....",
245
+ "......oHLLLLLLFFFFFhhhhhhho.....",
246
+ ".....oHHHHHFFFFFFFFFFFhhhhho....",
247
+ ".....oHHHHFFFFFFFFFFFFFhhhho....",
248
+ ".....oHHHFFFFFFFFFFFFFFFhhho....",
249
+ ".....oHHFFFFFFFFFFFFFFFFFhho....",
250
+ "....oHHHFFPPPPFFFFFPPPPFFhhho...",
251
+ "....oHHHFPwwwwFFFFFwwwwPFhho....",
252
+ "....oHHFFPPPPPFFFFFPPPPPFFho....",
253
+ "....oHHFFPPPPPFFFFFPPPPPFFho....",
254
+ "....oHFFPPPFFFFFFFFFFFPPPFFo....",
255
+ "....oHFFFPPFFFnnnnMFFFPPFFFo....",
256
+ "....oHHHFFFFMMnnnnMMMFFFFhho....",
257
+ "....oHFFFFFFMMMnnMMMMFFFFFFo....",
258
+ "....oHFFFFFFMnMMMMnMMFFFFFFo....",
259
+ "....oHHHHHFFMMnnnnMMMFFhhhho....",
260
+ "....oHHHHHHFFFMMMMMFFFhhhhho....",
261
+ "....oHHHHHHHHHFFFFFhhhhhhhho....",
262
+ "....ooHHHHHHHHHHHhhhhhhhhhoo....",
263
+ "......oHHHHHHHHHHHhhhhhhho......",
264
+ "....ooHHHHHHHHHHHHHHHHhHHHoo....",
265
+ "...oHHHHHHHHHHHHHHHHHHHHHHHHo...",
266
+ "...oHHHHHHHHHHHHHHHHHHHHHHHHo...",
267
+ "...oHHHHHHHHHHHHHHHHHHHHHHHHo...",
268
+ "...oooooooooooooooooooooooooo...",
269
+ ],
270
+ };
271
+ export function spriteRows(expression) {
272
+ return SPRITES[expression] ?? SPRITES.idle;
273
+ }
@@ -0,0 +1,74 @@
1
+ import { hasColor } from "./theme.js";
2
+ import { PX, SPRITE_SIZE, spriteRows } from "./sprite-data.js";
3
+ function parseHex(hex) {
4
+ const m = /^#([0-9a-f]{6})$/i.exec(hex.trim());
5
+ if (!m)
6
+ return null;
7
+ const n = Number.parseInt(m[1], 16);
8
+ return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
9
+ }
10
+ /** Palette key -> literal colour, with the hood taking the active theme. */
11
+ function colourFor(key, theme) {
12
+ if (key === "." || key === undefined)
13
+ return null;
14
+ // The hood is the only themed part. Its highlight and shadow are derived
15
+ // from the accent rather than reusing the support hue — teal highlights on
16
+ // a purple hood read as a different material, not a lit edge.
17
+ if (key === "H")
18
+ return theme.accent;
19
+ if (key === "L") {
20
+ const [r, g, b] = theme.accent;
21
+ const lift = (c) => Math.round(c + (255 - c) * 0.42);
22
+ return [lift(r), lift(g), lift(b)];
23
+ }
24
+ if (key === "h") {
25
+ const [r, g, b] = theme.accent;
26
+ return [Math.round(r * 0.52), Math.round(g * 0.52), Math.round(b * 0.52)];
27
+ }
28
+ const value = PX[key];
29
+ return typeof value === "string" ? parseHex(value) : null;
30
+ }
31
+ const fg = ([r, g, b]) => `\x1b[38;2;${r};${g};${b}m`;
32
+ const bg = ([r, g, b]) => `\x1b[48;2;${r};${g};${b}m`;
33
+ const RESET = "\x1b[0m";
34
+ export function sprite(theme, expression = "idle", { small = false } = {}) {
35
+ const grid = spriteRows(expression);
36
+ const step = small ? 2 : 1;
37
+ const size = SPRITE_SIZE;
38
+ // Without colour there is nothing to draw with — two block characters would
39
+ // be an unreadable smear, so the sprite is simply omitted.
40
+ if (!hasColor)
41
+ return [];
42
+ const lines = [];
43
+ // Two pixel rows per output line.
44
+ for (let y = 0; y < size; y += step * 2) {
45
+ let out = "";
46
+ let openBg = false;
47
+ for (let x = 0; x < size; x += step) {
48
+ const top = colourFor(grid[y]?.[x], theme);
49
+ const bottom = colourFor(grid[y + step]?.[x], theme);
50
+ if (!top && !bottom) {
51
+ if (openBg) {
52
+ out += RESET;
53
+ openBg = false;
54
+ }
55
+ out += " ";
56
+ continue;
57
+ }
58
+ if (top && bottom) {
59
+ out += `${bg(bottom)}${fg(top)}▀${RESET}`;
60
+ openBg = false;
61
+ }
62
+ else if (top) {
63
+ out += `${fg(top)}▀${RESET}`;
64
+ }
65
+ else if (bottom) {
66
+ out += `${fg(bottom)}▄${RESET}`;
67
+ }
68
+ }
69
+ if (openBg)
70
+ out += RESET;
71
+ lines.push(out);
72
+ }
73
+ return lines;
74
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lazy-skill",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Found an AI skill while scrolling? Install it to your computer from your phone.",
5
5
  "type": "module",
6
6
  "bin": {