@gonrocca/zero-pi 0.1.21 → 0.1.22

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.
@@ -119,7 +119,12 @@ function colorForPiece(piece: string, row: number, rank: number, active: boolean
119
119
  return lerpColor(base, COLORS.peak, heightWarmth * 0.18 + rankPulse);
120
120
  }
121
121
 
122
- function paintMatrixLine(matrix: Matrix, row: number, visibleCells: number): string {
122
+ function paintMatrixLine(
123
+ matrix: Matrix,
124
+ row: number,
125
+ visibleCells: number,
126
+ sparkles?: ReadonlySet<string>,
127
+ ): string {
123
128
  let out = "";
124
129
  const line = matrix.rows[row] ?? "";
125
130
  for (let col = 0; col < matrix.width; col++) {
@@ -128,16 +133,31 @@ function paintMatrixLine(matrix: Matrix, row: number, visibleCells: number): str
128
133
  out += " ";
129
134
  continue;
130
135
  }
131
- const rank = matrix.ranks.get(`${row}:${col}`) ?? Number.POSITIVE_INFINITY;
136
+ const key = `${row}:${col}`;
137
+ const rank = matrix.ranks.get(key) ?? Number.POSITIVE_INFINITY;
132
138
  if (rank >= visibleCells) {
133
139
  out += " ";
134
140
  continue;
135
141
  }
136
- out += ansiFg(colorForPiece(piece, row, rank, rank === visibleCells - 1), "[]");
142
+ // A cell glints when it is the assembly's leading cell or when this
143
+ // sparkle frame picked it.
144
+ const glint = rank === visibleCells - 1 || (sparkles?.has(key) ?? false);
145
+ out += ansiFg(colorForPiece(piece, row, rank, glint), "[]");
137
146
  }
138
147
  return out;
139
148
  }
140
149
 
150
+ /** Pick a handful of distinct settled cells to glint for one sparkle frame. */
151
+ function pickSparkles(matrix: Matrix, count: number, rand: () => number): Set<string> {
152
+ const keys = Array.from(matrix.ranks.keys());
153
+ const chosen = new Set<string>();
154
+ const target = Math.min(Math.max(0, count), keys.length);
155
+ for (let guard = 0; chosen.size < target && guard < target * 8; guard++) {
156
+ chosen.add(keys[Math.floor(rand() * keys.length)]);
157
+ }
158
+ return chosen;
159
+ }
160
+
141
161
  interface OutStream {
142
162
  write(chunk: string): unknown;
143
163
  isTTY?: boolean;
@@ -178,6 +198,10 @@ export interface RenderOptions {
178
198
  text?: string;
179
199
  frames?: number;
180
200
  frameMs?: number;
201
+ /** Number of post-settle sparkle frames (shimmer mode). `0` disables it. */
202
+ sparkleFrames?: number;
203
+ /** Delay between sparkle frames, in milliseconds. */
204
+ sparkleMs?: number;
181
205
  stream?: OutStream;
182
206
  }
183
207
 
@@ -201,6 +225,14 @@ export function renderBanner(options: RenderOptions = {}): void {
201
225
 
202
226
  stream.write("\n" + Array.from({ length: ROWS }, () => "").join("\n") + "\n");
203
227
 
228
+ // Repaint the six rows in place; optionally with this frame's sparkle set.
229
+ const paintRows = (sparkles?: ReadonlySet<string>): void => {
230
+ stream.write(`\x1b[${ROWS}A\r`);
231
+ for (let r = 0; r < ROWS; r++) {
232
+ stream.write("\x1b[2K" + paintMatrixLine(matrix, r, matrix.totalCells, sparkles) + "\n");
233
+ }
234
+ };
235
+
204
236
  if (mode === "shimmer") {
205
237
  const frames = Math.max(2, options.frames ?? 42);
206
238
  const frameMs = Math.max(1, options.frameMs ?? 18);
@@ -215,10 +247,21 @@ export function renderBanner(options: RenderOptions = {}): void {
215
247
  }
216
248
  }
217
249
 
218
- stream.write(`\x1b[${ROWS}A\r`);
219
- for (let r = 0; r < ROWS; r++) {
220
- stream.write("\x1b[2K" + paintMatrixLine(matrix, r, matrix.totalCells) + "\n");
250
+ // Settle on the completed wordmark.
251
+ paintRows();
252
+
253
+ // Sparkle pass — a few settled cells glint bright each frame, then a clean
254
+ // final settle. Shimmer only; `sparkleFrames: 0` disables it.
255
+ if (mode === "shimmer") {
256
+ const sparkleFrames = Math.max(0, options.sparkleFrames ?? 22);
257
+ const sparkleMs = Math.max(1, options.sparkleMs ?? 36);
258
+ for (let f = 0; f < sparkleFrames; f++) {
259
+ paintRows(pickSparkles(matrix, 3 + (f % 4), Math.random));
260
+ sleepSync(sparkleMs);
261
+ }
262
+ if (sparkleFrames > 0) paintRows();
221
263
  }
264
+
222
265
  stream.write("\n");
223
266
  }
224
267
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gonrocca/zero-pi",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "description": "zero-pi — an installable layer for pi (pi.dev): the zero spec-driven development workflow, skill auto-learning, and an animated ZERO startup banner. Adds capability to pi without modifying pi.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -166,6 +166,11 @@ your internal reasoning. Reference an artifact by its path; never paste its
166
166
  contents. Summarize each sub-agent's result in one short message — synthesize,
167
167
  do not relay.
168
168
 
169
+ **Formatting.** pi's chat shows a triple-backtick fenced code block with the
170
+ backticks rendered literally — never use one. Present commands, paths, and
171
+ snippets as plain lines indented two spaces, or inline with single backticks.
172
+ Keep the rest plain text; bold and single-backtick inline code render fine.
173
+
169
174
  **Approval question.** In interactive mode, after the phase summary, ask a
170
175
  single Spanish question — `¿Continuamos?` — never a bilingual one. Accept
171
176
  continue, stop, or feedback.