@subimpact/e 0.99.5 → 0.99.7

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.
Files changed (63) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/bundle/chunks/{chunk-YGYY5CGE.js → chunk-JKK652B2.js} +20 -20
  3. package/dist/bundle/chunks/{chunk-SS3AAGL7.js → chunk-VVPSHZFU.js} +1 -1
  4. package/dist/bundle/chunks/{chunk-34ZJK2EZ.js → chunk-ZDC7MWVF.js} +1 -1
  5. package/dist/bundle/chunks/easter-egg-3d-567GSTG6.js +2 -0
  6. package/dist/bundle/chunks/{virtual-modules-GWKJPH7F.js → virtual-modules-EKUHB2H5.js} +1 -1
  7. package/dist/bundle/cli-runtime.js +1 -1
  8. package/dist/bundle/index.js +1 -1
  9. package/dist/bundle/rpc-entry.js +1 -1
  10. package/dist/cli/args.js +1 -1
  11. package/dist/cli/args.js.map +1 -1
  12. package/dist/core/agent-session.js +1 -1
  13. package/dist/core/agent-session.js.map +1 -1
  14. package/dist/core/bug-report.js +1 -1
  15. package/dist/core/bug-report.js.map +1 -1
  16. package/dist/core/settings-manager.d.ts.map +1 -1
  17. package/dist/core/settings-manager.js +6 -2
  18. package/dist/core/settings-manager.js.map +1 -1
  19. package/dist/core/slash-commands.js +1 -1
  20. package/dist/core/slash-commands.js.map +1 -1
  21. package/dist/modes/interactive/bug-report.js +1 -1
  22. package/dist/modes/interactive/bug-report.js.map +1 -1
  23. package/dist/modes/interactive/components/armin.d.ts +3 -0
  24. package/dist/modes/interactive/components/armin.d.ts.map +1 -1
  25. package/dist/modes/interactive/components/armin.js +7 -5
  26. package/dist/modes/interactive/components/armin.js.map +1 -1
  27. package/dist/modes/interactive/components/{pi-logo.d.ts → e-logo.d.ts} +2 -2
  28. package/dist/modes/interactive/components/e-logo.d.ts.map +1 -0
  29. package/dist/modes/interactive/components/{pi-logo.js → e-logo.js} +2 -2
  30. package/dist/modes/interactive/components/e-logo.js.map +1 -0
  31. package/dist/modes/interactive/components/easter-egg-3d.d.ts +104 -0
  32. package/dist/modes/interactive/components/easter-egg-3d.d.ts.map +1 -0
  33. package/dist/modes/interactive/components/{pi-logo-animation.js → easter-egg-3d.js} +126 -74
  34. package/dist/modes/interactive/components/easter-egg-3d.js.map +1 -0
  35. package/dist/modes/interactive/components/easter-egg-3d.lazy.d.ts +6 -0
  36. package/dist/modes/interactive/components/easter-egg-3d.lazy.d.ts.map +1 -0
  37. package/dist/modes/interactive/components/easter-egg-3d.lazy.js +23 -0
  38. package/dist/modes/interactive/components/easter-egg-3d.lazy.js.map +1 -0
  39. package/dist/modes/interactive/components/first-time-setup.js +1 -1
  40. package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
  41. package/dist/modes/interactive/components/settings-selector.js +1 -1
  42. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  43. package/dist/modes/interactive/external-editor.js +1 -1
  44. package/dist/modes/interactive/external-editor.js.map +1 -1
  45. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  46. package/dist/modes/interactive/interactive-mode.js +7 -5
  47. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  48. package/dist/package-manager-cli.js +3 -3
  49. package/dist/package-manager-cli.js.map +1 -1
  50. package/docs/cli.md +1 -1
  51. package/docs/settings.md +2 -2
  52. package/docs/usage.md +1 -1
  53. package/package.json +1 -1
  54. package/dist/bundle/chunks/pi-logo-animation-5NR3P2W2.js +0 -2
  55. package/dist/modes/interactive/components/pi-logo-animation.d.ts +0 -68
  56. package/dist/modes/interactive/components/pi-logo-animation.d.ts.map +0 -1
  57. package/dist/modes/interactive/components/pi-logo-animation.js.map +0 -1
  58. package/dist/modes/interactive/components/pi-logo-animation.lazy.d.ts +0 -7
  59. package/dist/modes/interactive/components/pi-logo-animation.lazy.d.ts.map +0 -1
  60. package/dist/modes/interactive/components/pi-logo-animation.lazy.js +0 -12
  61. package/dist/modes/interactive/components/pi-logo-animation.lazy.js.map +0 -1
  62. package/dist/modes/interactive/components/pi-logo.d.ts.map +0 -1
  63. package/dist/modes/interactive/components/pi-logo.js.map +0 -1
@@ -1,23 +1,55 @@
1
1
  import { backgroundAnsi, colorToRgb, foregroundAnsi, getKeybindings, indexedColor, rgbColor, visibleWidth, } from "@earendil-works/pi-tui";
2
2
  import { theme } from "../theme/theme.js";
3
+ import { ARMIN_HEIGHT, ARMIN_WIDTH, isArminPixel } from "./armin.js";
3
4
  import { formatKeyText } from "./keybinding-hints.js";
4
- const CORAL = [228, 138, 122];
5
- const BLUE = [79, 142, 179];
6
- const YELLOW = [234, 182, 93];
7
5
  const DEPTH = 0.7;
8
- const CAMERA_DISTANCE = 10;
9
6
  const FLY_START = 0.1;
10
7
  const FLY_DURATION = 1.3;
11
- // The logo's pixels on a 4x4 grid: an e (Euler's number). Each pixel is a block that the puzzle slides around.
12
- const LOGO_PIXELS = ["c..c", "cccc", "bbbc", "b..b"];
13
- const BLOCKS = LOGO_PIXELS.flatMap((line, row) => [...line].flatMap((pixel, column) => {
14
- const color = pixel === "c" ? CORAL : pixel === "b" ? BLUE : pixel === "y" ? YELLOW : undefined;
15
- return color ? [{ home: [column, row, 0], color }] : [];
16
- }));
17
- // Farthest distance of any block corner from the origin, with blocks on the outer depth layers.
18
- const LOGO_RADIUS = Math.hypot(2, 2, 1 + DEPTH / 2);
19
- // The sliding puzzle: after the logo spun for a while, blocks slide into free neighboring cells, one step at
20
- // a time. Each cycle shuffles, flies every block back home, and holds the logo briefly.
8
+ /** Braille dots per pixel when a model without an origin appears, growing from a speck at the center. */
9
+ const START_SCALE = 0.1;
10
+ function createModel(columns, rows, pixel, options) {
11
+ const blocks = [];
12
+ for (let row = 0; row < rows; row++) {
13
+ for (let column = 0; column < columns; column++) {
14
+ const color = pixel(column, row);
15
+ if (color)
16
+ blocks.push({ home: [column, row, 0], color });
17
+ }
18
+ }
19
+ return {
20
+ columns,
21
+ rows,
22
+ blocks,
23
+ cameraDistance: options.cameraDistance,
24
+ radius: Math.hypot(columns / 2, rows / 2, 1 + DEPTH / 2),
25
+ widthShare: options.widthShare,
26
+ puzzleMoves: (random) => options.puzzleMoves(random, blocks.length),
27
+ origin: options.origin,
28
+ };
29
+ }
30
+ const CORAL = [228, 138, 122];
31
+ const BLUE = [79, 142, 179];
32
+ const E_LOGO_PIXELS = ["c..c", "cccc", "bbbc", "b..b"];
33
+ const E_LOGO_COLORS = { c: CORAL, b: BLUE };
34
+ function eLogoModel(origin) {
35
+ return createModel(4, 4, (column, row) => E_LOGO_COLORS[E_LOGO_PIXELS[row][column]], {
36
+ cameraDistance: 10,
37
+ widthShare: 0.35,
38
+ puzzleMoves: (random) => (random < 0.4 ? 2 : 1),
39
+ origin,
40
+ });
41
+ }
42
+ function arminModel(color) {
43
+ return createModel(ARMIN_WIDTH, ARMIN_HEIGHT, (column, row) => (isArminPixel(column, row) ? color : undefined), {
44
+ // Far enough that the perspective stays mild for a figure about 36 blocks tall.
45
+ cameraDistance: 80,
46
+ widthShare: 0.45,
47
+ puzzleMoves: (random, blockCount) => Math.round(blockCount * (0.03 + random * 0.04)),
48
+ origin: undefined,
49
+ });
50
+ }
51
+ // The sliding puzzle: after the model spun for a while, blocks slide into free neighboring cells, a few at a time.
52
+ // Each cycle shuffles, flies every block back home, and holds the model briefly.
21
53
  const PUZZLE_START = FLY_START + FLY_DURATION + 3;
22
54
  const PUZZLE_STEP = 0.3;
23
55
  const PUZZLE_STEPS = 12;
@@ -37,18 +69,19 @@ const PUZZLE_MOVES = [
37
69
  [0, 0, -1],
38
70
  ];
39
71
  /** Block positions after each step of one shuffle cycle, starting at home. Deterministic per cycle. */
40
- function shuffleSteps(cycle) {
41
- let positions = BLOCKS.map((block) => block.home);
72
+ function shuffleSteps(model, cycle) {
73
+ const { columns, rows } = model;
74
+ let positions = model.blocks.map((block) => block.home);
42
75
  const steps = [positions];
43
76
  const lastMoved = new Set();
44
77
  let random = cycle * 7_919 + 1;
45
78
  const next = () => hash(random++);
46
- const key = ([x, y, z]) => (z + 1) * 16 + y * 4 + x;
79
+ const key = ([x, y, z]) => ((z + 1) * rows + y) * columns + x;
47
80
  for (let step = 0; step < PUZZLE_STEPS; step++) {
48
81
  const occupied = new Set(positions.map(key));
49
82
  const nextPositions = [...positions];
50
83
  const moved = new Set();
51
- const moveCount = next() < 0.4 ? 2 : 1;
84
+ const moveCount = model.puzzleMoves(next());
52
85
  for (let move = 0; move < moveCount; move++) {
53
86
  const candidates = [];
54
87
  for (let block = 0; block < positions.length; block++) {
@@ -57,7 +90,7 @@ function shuffleSteps(cycle) {
57
90
  const [x, y, z] = positions[block];
58
91
  for (const [dx, dy, dz] of PUZZLE_MOVES) {
59
92
  const target = [x + dx, y + dy, z + dz];
60
- if (target[0] < 0 || target[0] > 3 || target[1] < 0 || target[1] > 3)
93
+ if (target[0] < 0 || target[0] >= columns || target[1] < 0 || target[1] >= rows)
61
94
  continue;
62
95
  if (target[2] < -1 || target[2] > 1 || occupied.has(key(target)))
63
96
  continue;
@@ -335,11 +368,11 @@ function spinAngle(time) {
335
368
  return phase - SPIN_LINGER * Math.sin(phase);
336
369
  }
337
370
  /**
338
- * Renders the logo blocks into braille cells. Only faces that point at the camera and are not covered by a
371
+ * Renders the model's blocks into braille cells. Only faces that point at the camera and are not covered by a
339
372
  * touching block are drawn. Each face is rasterized over its projected bounds by intersecting each dot's ray
340
373
  * with the face's plane, with a depth buffer resolving overlaps. Buffers are reused between frames.
341
374
  */
342
- class LogoRaster {
375
+ class BlockRaster {
343
376
  /** Braille dot bits per cell. */
344
377
  bits = new Uint8Array(0);
345
378
  /** Lit dots per cell. */
@@ -355,11 +388,15 @@ class LogoRaster {
355
388
  /** Ray parameter of the nearest hit per dot; smaller is nearer. */
356
389
  depth = new Float32Array(0);
357
390
  /** Face index + 1 of the nearest hit per dot, 0 for none. */
358
- faceIds = new Uint8Array(0);
391
+ faceIds = new Uint16Array(0);
359
392
  /** Cells written by the previous frame, cleared before the next one. */
360
393
  dirty;
361
394
  /** Cells with a halo from the previous frame, cleared before the next one. */
362
395
  haloDirty;
396
+ cameraDistance;
397
+ constructor(cameraDistance) {
398
+ this.cameraDistance = cameraDistance;
399
+ }
363
400
  render(width, height, pose, boxes, background, haloStrength) {
364
401
  const dotWidth = width * 2;
365
402
  const dotHeight = height * 4;
@@ -373,7 +410,7 @@ class LogoRaster {
373
410
  this.haloRgb = new Float32Array(width * height * 3);
374
411
  this.haloDirty = undefined;
375
412
  this.depth = new Float32Array(dotWidth * dotHeight).fill(Infinity);
376
- this.faceIds = new Uint8Array(dotWidth * dotHeight);
413
+ this.faceIds = new Uint16Array(dotWidth * dotHeight);
377
414
  this.dirty = undefined;
378
415
  }
379
416
  else if (this.dirty) {
@@ -395,8 +432,9 @@ class LogoRaster {
395
432
  }
396
433
  const m = rotation(pose.yaw, pose.pitch, pose.roll);
397
434
  const { centerX, centerY, scale } = pose;
398
- // The camera sits at (0, 0, CAMERA_DISTANCE) in camera space; object space is the transpose rotation.
399
- const origin = [m[6] * CAMERA_DISTANCE, m[7] * CAMERA_DISTANCE, m[8] * CAMERA_DISTANCE];
435
+ const cameraDistance = this.cameraDistance;
436
+ // The camera sits at (0, 0, cameraDistance) in camera space; object space is the transpose rotation.
437
+ const origin = [m[6] * cameraDistance, m[7] * cameraDistance, m[8] * cameraDistance];
400
438
  const light = isLight(background);
401
439
  const faces = this.visibleFaces(m, origin, pose, boxes, dotWidth, dotHeight, light);
402
440
  if (faces.length === 0)
@@ -430,9 +468,9 @@ class LogoRaster {
430
468
  const sx = (face.minX + 0.5 - centerX) / scale;
431
469
  for (let dotY = face.minY; dotY <= face.maxY; dotY++) {
432
470
  const sy = (dotY + 0.5 - centerY) / scale;
433
- let directionA = m[a] * sx + m[3 + a] * sy - m[6 + a] * CAMERA_DISTANCE;
434
- let directionU = m[u] * sx + m[3 + u] * sy - m[6 + u] * CAMERA_DISTANCE;
435
- let directionV = m[v] * sx + m[3 + v] * sy - m[6 + v] * CAMERA_DISTANCE;
471
+ let directionA = m[a] * sx + m[3 + a] * sy - m[6 + a] * cameraDistance;
472
+ let directionU = m[u] * sx + m[3 + u] * sy - m[6 + u] * cameraDistance;
473
+ let directionV = m[v] * sx + m[3 + v] * sy - m[6 + v] * cameraDistance;
436
474
  let index = dotY * dotWidth + face.minX;
437
475
  for (let dotX = face.minX; dotX <= face.maxX; dotX++, index++) {
438
476
  const t = (face.plane - originA) / directionA;
@@ -466,7 +504,7 @@ class LogoRaster {
466
504
  continue;
467
505
  const face = faces[id - 1];
468
506
  // Points farther from the camera fade slightly toward the background for depth.
469
- const fog = clamp01(0.15 - CAMERA_DISTANCE * (1 - depth[index]) * 0.12) * (light ? 0.5 : 1);
507
+ const fog = clamp01(0.15 - cameraDistance * (1 - depth[index]) * 0.12) * (light ? 0.5 : 1);
470
508
  faceIds[index] = 0;
471
509
  depth[index] = Infinity;
472
510
  const cellX = dotX >> 1;
@@ -586,6 +624,7 @@ class LogoRaster {
586
624
  }
587
625
  visibleFaces(m, origin, pose, boxes, dotWidth, dotHeight, lightBackground) {
588
626
  const faces = [];
627
+ const cameraDistance = this.cameraDistance;
589
628
  for (const box of boxes) {
590
629
  for (let a = 0; a < 3; a++) {
591
630
  const u = (a + 1) % 3;
@@ -617,7 +656,7 @@ class LogoRaster {
617
656
  const x = m[0] * corner[0] + m[1] * corner[1] + m[2] * corner[2];
618
657
  const y = m[3] * corner[0] + m[4] * corner[1] + m[5] * corner[2];
619
658
  const z = m[6] * corner[0] + m[7] * corner[1] + m[8] * corner[2];
620
- const perspective = (pose.scale * CAMERA_DISTANCE) / (CAMERA_DISTANCE - z);
659
+ const perspective = (pose.scale * cameraDistance) / (cameraDistance - z);
621
660
  const screenX = pose.centerX + x * perspective;
622
661
  const screenY = pose.centerY + y * perspective;
623
662
  minX = Math.min(minX, screenX);
@@ -672,7 +711,7 @@ let playing = false;
672
711
  * Show the animation as a fullscreen overlay until it is dismissed. The overlay takes focus and mouse input and
673
712
  * returns focus when hidden, so the rest of the UI keeps running underneath untouched.
674
713
  */
675
- export async function playPiLogoAnimation(tui, options) {
714
+ export async function playEasterEgg3d(tui, screen, egg) {
676
715
  if (playing)
677
716
  return;
678
717
  playing = true;
@@ -688,15 +727,18 @@ export async function playPiLogoAnimation(tui, options) {
688
727
  playing = false;
689
728
  return;
690
729
  }
691
- const animation = new PiLogoAnimation(tui, options, colors, () => {
730
+ const model = egg.kind === "armin" ? arminModel(toRgb(theme.colors.accent)) : eLogoModel({ column: egg.column, row: egg.row });
731
+ const animation = new EasterEgg3dAnimation(tui, screen, model, colors, () => {
692
732
  playing = false;
693
733
  overlay.hide();
694
734
  });
695
735
  const overlay = tui.showOverlay(animation, { anchor: "top-left", width: "100%", maxHeight: "100%" });
696
736
  }
697
- export class PiLogoAnimation {
737
+ export class EasterEgg3dAnimation {
698
738
  tui;
699
- options;
739
+ /** The screen to dissolve, as rendered lines. */
740
+ screen;
741
+ model;
700
742
  foreground;
701
743
  background;
702
744
  onDone;
@@ -711,10 +753,12 @@ export class PiLogoAnimation {
711
753
  stars = [];
712
754
  cells = [];
713
755
  ansiCache = new Map();
714
- logo = new LogoRaster();
715
- constructor(tui, options, colors, onDone) {
756
+ raster;
757
+ constructor(tui, screen, model, colors, onDone) {
716
758
  this.tui = tui;
717
- this.options = options;
759
+ this.screen = screen;
760
+ this.model = model;
761
+ this.raster = new BlockRaster(model.cameraDistance);
718
762
  this.foreground = colors.foreground;
719
763
  this.background = colors.background;
720
764
  this.onDone = onDone;
@@ -799,19 +843,20 @@ export class PiLogoAnimation {
799
843
  starAlpha = this.starAlpha(time);
800
844
  offsets = this.blockOffsets(time);
801
845
  }
802
- const boxes = BLOCKS.map((block, index) => {
846
+ const { columns, rows } = this.model;
847
+ const boxes = this.model.blocks.map((block, index) => {
803
848
  const [dx, dy, dz] = offsets[index];
804
- const x = block.home[0] - 2 + dx;
805
- const y = block.home[1] - 2 + dy;
849
+ const x = block.home[0] - columns / 2 + dx;
850
+ const y = block.home[1] - rows / 2 + dy;
806
851
  return { min: [x, y, dz - DEPTH / 2], max: [x + 1, y + 1, dz + DEPTH / 2], color: block.color };
807
852
  });
808
- const logo = this.logo;
809
- logo.render(width, height, pose, boxes, background, isLight(background) ? LIGHT_HALO_STRENGTH : 0);
853
+ const raster = this.raster;
854
+ raster.render(width, height, pose, boxes, background, isLight(background) ? LIGHT_HALO_STRENGTH : 0);
810
855
  const halo = (index, base) => {
811
- const amount = logo.haloAmount[index];
856
+ const amount = raster.haloAmount[index];
812
857
  if (amount <= 0)
813
858
  return base;
814
- const color = logo.haloRgb;
859
+ const color = raster.haloRgb;
815
860
  return mix(base ?? background, [color[index * 3], color[index * 3 + 1], color[index * 3 + 2]], amount);
816
861
  };
817
862
  const backgroundFade = smooth(dissolveTime / BACKGROUND_FADE);
@@ -865,11 +910,11 @@ export class PiLogoAnimation {
865
910
  emit(hint.text[offset], offset < hint.keyLength ? hint.keyColor : hint.color, cellBg);
866
911
  continue;
867
912
  }
868
- const dots = logo.bits[index];
913
+ const dots = raster.bits[index];
869
914
  if (dots) {
870
915
  // Quantized so neighboring cells on one face usually share a color and its escape sequence.
871
- const step = LOGO_COLOR_STEP * logo.counts[index];
872
- const rgb = logo.rgb;
916
+ const step = LOGO_COLOR_STEP * raster.counts[index];
917
+ const rgb = raster.rgb;
873
918
  const color = [
874
919
  Math.round(rgb[index * 3] / step) * LOGO_COLOR_STEP,
875
920
  Math.round(rgb[index * 3 + 1] / step) * LOGO_COLOR_STEP,
@@ -884,7 +929,7 @@ export class PiLogoAnimation {
884
929
  continue;
885
930
  }
886
931
  const dust = (dissolveTime - cell.delay) / DUST_DURATION;
887
- if (dust < 0 && cell.width === 2 && !logo.bits[index + 1]) {
932
+ if (dust < 0 && cell.width === 2 && !raster.bits[index + 1]) {
888
933
  emit(cell.text, mix(cell.fg, background, textFade), cellBg);
889
934
  column++;
890
935
  continue;
@@ -929,18 +974,19 @@ export class PiLogoAnimation {
929
974
  this.timer = undefined;
930
975
  this.onDone();
931
976
  }
932
- /** Each block's displacement from its place in the logo at `time`, in grid units. */
977
+ /** Each block's displacement from its place in the model at `time`, in grid units. */
933
978
  blockOffsets(time) {
934
- const home = () => BLOCKS.map(() => [0, 0, 0]);
979
+ const blocks = this.model.blocks;
980
+ const home = () => blocks.map(() => [0, 0, 0]);
935
981
  if (time < PUZZLE_START)
936
982
  return home();
937
983
  const cycle = Math.floor((time - PUZZLE_START) / PUZZLE_CYCLE);
938
984
  const local = time - PUZZLE_START - cycle * PUZZLE_CYCLE;
939
985
  if (this.shuffle?.cycle !== cycle)
940
- this.shuffle = { cycle, steps: shuffleSteps(cycle) };
986
+ this.shuffle = { cycle, steps: shuffleSteps(this.model, cycle) };
941
987
  const steps = this.shuffle.steps;
942
988
  const offset = (position, index) => {
943
- const block = BLOCKS[index].home;
989
+ const block = blocks[index].home;
944
990
  return [position[0] - block[0], position[1] - block[1], position[2] - block[2]];
945
991
  };
946
992
  const shuffleEnd = PUZZLE_STEP * PUZZLE_STEPS;
@@ -975,14 +1021,16 @@ export class PiLogoAnimation {
975
1021
  return smooth((time - FLY_START - FLY_DURATION) / HINT_FADE);
976
1022
  }
977
1023
  pose(width, height, progress, time) {
978
- // At the start the front face must cover exactly the header logo's 8x8 dots despite the perspective.
979
- const startScale = (2 * (CAMERA_DISTANCE - DEPTH / 2)) / CAMERA_DISTANCE;
980
- const reach = LOGO_RADIUS * (CAMERA_DISTANCE / (CAMERA_DISTANCE - LOGO_RADIUS));
981
- const endScale = Math.max(startScale, Math.min(width * 2 * 0.35, (height * 4 - 8) * 0.48) / reach);
982
- const startX = this.options.logoColumn * 2 + 4;
983
- const startY = this.options.logoRow * 4 + 4;
1024
+ const { columns, rows, cameraDistance, radius, widthShare, origin } = this.model;
1025
+ const reach = radius * (cameraDistance / (cameraDistance - radius));
1026
+ // Lifting off, the front face must cover exactly the half-block cells (2x2 dots per pixel) despite the
1027
+ // perspective.
1028
+ const startScale = origin ? (2 * (cameraDistance - DEPTH / 2)) / cameraDistance : START_SCALE;
1029
+ const endScale = Math.max(startScale, Math.min(width * 2 * widthShare, (height * 4 - 8) * 0.48) / reach);
984
1030
  const endX = width;
985
1031
  const endY = height * 2 - 2;
1032
+ const startX = origin ? origin.column * 2 + columns : endX;
1033
+ const startY = origin ? origin.row * 4 + rows : endY;
986
1034
  return {
987
1035
  centerX: startX + (endX - startX) * progress,
988
1036
  centerY: startY + (endY - startY) * progress,
@@ -1028,10 +1076,12 @@ export class PiLogoAnimation {
1028
1076
  return stars;
1029
1077
  }
1030
1078
  prepareCells(width, foreground) {
1031
- const cells = parseScreen(this.options.screen, width, foreground, this.background);
1032
- const centerX = this.options.logoColumn + 2;
1033
- const centerY = this.options.logoRow + 1;
1079
+ const cells = parseScreen(this.screen, width, foreground, this.background);
1034
1080
  const height = Math.max(1, this.tui.terminal.rows);
1081
+ const { columns, rows, origin } = this.model;
1082
+ // The dust spreads out from where the model starts.
1083
+ const centerX = origin ? origin.column + columns / 2 : width / 2;
1084
+ const centerY = origin ? origin.row + rows / 4 : height / 2;
1035
1085
  const farthest = Math.hypot(Math.max(centerX, width - centerX), Math.max(centerY, height - centerY) * 2);
1036
1086
  for (let row = 0; row < cells.length; row++) {
1037
1087
  const line = cells[row];
@@ -1051,18 +1101,20 @@ export class PiLogoAnimation {
1051
1101
  cell.ink = glyphInk(owner.text, seed);
1052
1102
  }
1053
1103
  }
1054
- // The 3D logo replaces the header logo.
1055
- for (let row = this.options.logoRow; row < this.options.logoRow + 2; row++) {
1056
- const line = cells[row];
1057
- if (!line)
1058
- continue;
1059
- for (let column = this.options.logoColumn; column < this.options.logoColumn + 4; column++) {
1060
- const cell = line[column];
1061
- if (cell) {
1062
- cell.text = " ";
1063
- cell.width = 1;
1064
- cell.ink = 0;
1065
- cell.bg = undefined;
1104
+ // The 3D model replaces its half-block rendering on screen.
1105
+ if (origin) {
1106
+ for (let row = origin.row; row < origin.row + Math.ceil(rows / 2); row++) {
1107
+ const line = cells[row];
1108
+ if (!line)
1109
+ continue;
1110
+ for (let column = origin.column; column < origin.column + columns; column++) {
1111
+ const cell = line[column];
1112
+ if (cell) {
1113
+ cell.text = " ";
1114
+ cell.width = 1;
1115
+ cell.ink = 0;
1116
+ cell.bg = undefined;
1117
+ }
1066
1118
  }
1067
1119
  }
1068
1120
  }
@@ -1083,4 +1135,4 @@ export class PiLogoAnimation {
1083
1135
  return value;
1084
1136
  }
1085
1137
  }
1086
- //# sourceMappingURL=pi-logo-animation.js.map
1138
+ //# sourceMappingURL=easter-egg-3d.js.map