pi-namespace-patch 1.0.0-namespace.2 → 1.0.2-namespace.2

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 (185) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +22 -4
  3. package/dist/bundle/chunks/{anthropic-messages-UID2PALB.js → anthropic-messages-MVG2FL56.js} +3 -3
  4. package/dist/bundle/chunks/azure-openai-responses-7AMFUIWJ.js +2 -0
  5. package/dist/bundle/chunks/bedrock-converse-stream.js +2 -2
  6. package/dist/bundle/chunks/chunk-2KAZSACP.js +2 -0
  7. package/dist/bundle/chunks/{chunk-2KTBZM5G.js → chunk-6FX7UEPL.js} +5 -5
  8. package/dist/bundle/chunks/{chunk-2VNGUWBO.js → chunk-BYB6ZAAQ.js} +1 -1
  9. package/dist/bundle/chunks/chunk-DN3QEIXS.js +2 -0
  10. package/dist/bundle/chunks/chunk-F5ZABL6Z.js +20 -0
  11. package/dist/bundle/chunks/{chunk-3LH3ZQKY.js → chunk-G7EDXK4D.js} +1 -1
  12. package/dist/bundle/chunks/chunk-IKZJX7ZT.js +2 -0
  13. package/dist/bundle/chunks/{chunk-4N5K4RKC.js → chunk-OLBCNJCL.js} +31 -32
  14. package/dist/bundle/chunks/{chunk-PRD6WYBT.js → chunk-OYBSLV7Y.js} +1 -1
  15. package/dist/bundle/chunks/{chunk-IBCPYVFK.js → chunk-PDFMCAOZ.js} +4 -4
  16. package/dist/bundle/chunks/{chunk-LTXEMVMA.js → chunk-SYF2TF6W.js} +2 -2
  17. package/dist/bundle/chunks/chunk-WCQZGZ6I.js +82 -0
  18. package/dist/bundle/chunks/{chunk-KDN6RCEQ.js → chunk-ZSI3BL5Q.js} +2 -2
  19. package/dist/bundle/chunks/{cli-QOMC26IJ.js → cli-KILDDGW3.js} +2 -2
  20. package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-NPQN263E.js → cloudflare-workers-ai-system-one-X5PVWWYJ.js} +1 -1
  21. package/dist/bundle/chunks/codemode-worker.js +24 -3
  22. package/dist/bundle/chunks/easter-egg-3d-T5FFWIBR.js +2 -0
  23. package/dist/bundle/chunks/{execute-GVRSEGHL.js → execute-RYVK27GT.js} +25 -4
  24. package/dist/bundle/chunks/{google-generative-ai-APTPZOQL.js → google-generative-ai-ZOK6CV7D.js} +1 -1
  25. package/dist/bundle/chunks/{google-vertex-MXCOSOY3.js → google-vertex-VKWZQ3AY.js} +1 -1
  26. package/dist/bundle/chunks/{mistral-conversations-DKVFNQL6.js → mistral-conversations-7DAUEE4N.js} +1 -1
  27. package/dist/bundle/chunks/node-N5BSDOA5.js +16 -0
  28. package/dist/bundle/chunks/openai-chatgpt.js +1 -1
  29. package/dist/bundle/chunks/{openai-codex-responses-GK67DQQJ.js → openai-codex-responses-TC3OK7B4.js} +1 -1
  30. package/dist/bundle/chunks/openai-completions-GWHGMQ3T.js +7 -0
  31. package/dist/bundle/chunks/openai-responses-EZ7FJ2KL.js +3 -0
  32. package/dist/bundle/chunks/{runtime-2SO2JDWM.js → runtime-D5I2NTIF.js} +1 -1
  33. package/dist/bundle/chunks/virtual-modules-J2M3WAR2.js +2 -0
  34. package/dist/bundle/cli-runtime.js +1 -1
  35. package/dist/bundle/index.js +1 -1
  36. package/dist/bundle/rpc-entry.js +1 -1
  37. package/dist/cli/args.d.ts.map +1 -1
  38. package/dist/cli/args.js +4 -1
  39. package/dist/cli/args.js.map +1 -1
  40. package/dist/config.d.ts.map +1 -1
  41. package/dist/config.js +3 -0
  42. package/dist/config.js.map +1 -1
  43. package/dist/core/agent-session.d.ts.map +1 -1
  44. package/dist/core/agent-session.js +1 -1
  45. package/dist/core/agent-session.js.map +1 -1
  46. package/dist/core/export-html/tool-renderer.d.ts +3 -3
  47. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  48. package/dist/core/export-html/tool-renderer.js +3 -3
  49. package/dist/core/export-html/tool-renderer.js.map +1 -1
  50. package/dist/core/extensions/index.d.ts +1 -1
  51. package/dist/core/extensions/index.d.ts.map +1 -1
  52. package/dist/core/extensions/index.js.map +1 -1
  53. package/dist/core/extensions/loader.d.ts.map +1 -1
  54. package/dist/core/extensions/loader.js +5 -0
  55. package/dist/core/extensions/loader.js.map +1 -1
  56. package/dist/core/extensions/runner.d.ts +3 -1
  57. package/dist/core/extensions/runner.d.ts.map +1 -1
  58. package/dist/core/extensions/runner.js +6 -0
  59. package/dist/core/extensions/runner.js.map +1 -1
  60. package/dist/core/extensions/types.d.ts +9 -0
  61. package/dist/core/extensions/types.d.ts.map +1 -1
  62. package/dist/core/extensions/types.js.map +1 -1
  63. package/dist/core/mcp-servers.d.ts +6 -0
  64. package/dist/core/mcp-servers.d.ts.map +1 -1
  65. package/dist/core/mcp-servers.js +11 -0
  66. package/dist/core/mcp-servers.js.map +1 -1
  67. package/dist/core/model-config.d.ts +36 -0
  68. package/dist/core/model-config.d.ts.map +1 -1
  69. package/dist/core/model-config.js +14 -2
  70. package/dist/core/model-config.js.map +1 -1
  71. package/dist/core/model-resolver.js +1 -1
  72. package/dist/core/model-resolver.js.map +1 -1
  73. package/dist/core/provider-composer.d.ts +2 -1
  74. package/dist/core/provider-composer.d.ts.map +1 -1
  75. package/dist/core/provider-composer.js +13 -0
  76. package/dist/core/provider-composer.js.map +1 -1
  77. package/dist/core/tools/renderers/index.d.ts +2 -2
  78. package/dist/core/tools/renderers/index.d.ts.map +1 -1
  79. package/dist/core/tools/renderers/index.js.map +1 -1
  80. package/dist/extensions/mcp/cli.d.ts.map +1 -1
  81. package/dist/extensions/mcp/cli.js +3 -0
  82. package/dist/extensions/mcp/cli.js.map +1 -1
  83. package/dist/extensions/mcp/config.d.ts +15 -3
  84. package/dist/extensions/mcp/config.d.ts.map +1 -1
  85. package/dist/extensions/mcp/config.js +46 -10
  86. package/dist/extensions/mcp/config.js.map +1 -1
  87. package/dist/extensions/mcp/index.d.ts +4 -1
  88. package/dist/extensions/mcp/index.d.ts.map +1 -1
  89. package/dist/extensions/mcp/index.js +80 -45
  90. package/dist/extensions/mcp/index.js.map +1 -1
  91. package/dist/extensions/mcp/oauth.d.ts +4 -1
  92. package/dist/extensions/mcp/oauth.d.ts.map +1 -1
  93. package/dist/extensions/mcp/oauth.js +54 -10
  94. package/dist/extensions/mcp/oauth.js.map +1 -1
  95. package/dist/extensions/mcp/runtime.d.ts.map +1 -1
  96. package/dist/extensions/mcp/runtime.js +1 -0
  97. package/dist/extensions/mcp/runtime.js.map +1 -1
  98. package/dist/extensions/mcp/tools.d.ts +3 -1
  99. package/dist/extensions/mcp/tools.d.ts.map +1 -1
  100. package/dist/extensions/mcp/tools.js +19 -13
  101. package/dist/extensions/mcp/tools.js.map +1 -1
  102. package/dist/extensions/mcp/ui.d.ts.map +1 -1
  103. package/dist/extensions/mcp/ui.js +8 -4
  104. package/dist/extensions/mcp/ui.js.map +1 -1
  105. package/dist/index.d.ts +1 -1
  106. package/dist/index.d.ts.map +1 -1
  107. package/dist/index.js.map +1 -1
  108. package/dist/modes/interactive/components/armin.d.ts +3 -0
  109. package/dist/modes/interactive/components/armin.d.ts.map +1 -1
  110. package/dist/modes/interactive/components/armin.js +7 -5
  111. package/dist/modes/interactive/components/armin.js.map +1 -1
  112. package/dist/modes/interactive/components/auth-url.d.ts +14 -0
  113. package/dist/modes/interactive/components/auth-url.d.ts.map +1 -0
  114. package/dist/modes/interactive/components/auth-url.js +37 -0
  115. package/dist/modes/interactive/components/auth-url.js.map +1 -0
  116. package/dist/modes/interactive/components/easter-egg-3d.d.ts +104 -0
  117. package/dist/modes/interactive/components/easter-egg-3d.d.ts.map +1 -0
  118. package/dist/modes/interactive/components/{pi-logo-animation.js → easter-egg-3d.js} +127 -74
  119. package/dist/modes/interactive/components/easter-egg-3d.js.map +1 -0
  120. package/dist/modes/interactive/components/easter-egg-3d.lazy.d.ts +6 -0
  121. package/dist/modes/interactive/components/easter-egg-3d.lazy.d.ts.map +1 -0
  122. package/dist/modes/interactive/components/easter-egg-3d.lazy.js +23 -0
  123. package/dist/modes/interactive/components/easter-egg-3d.lazy.js.map +1 -0
  124. package/dist/modes/interactive/components/index.d.ts +0 -1
  125. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  126. package/dist/modes/interactive/components/index.js +0 -1
  127. package/dist/modes/interactive/components/index.js.map +1 -1
  128. package/dist/modes/interactive/components/login-dialog.d.ts +2 -0
  129. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  130. package/dist/modes/interactive/components/login-dialog.js +11 -5
  131. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  132. package/dist/modes/interactive/components/tool-execution.d.ts +6 -18
  133. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  134. package/dist/modes/interactive/components/tool-execution.js +22 -45
  135. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  136. package/dist/modes/interactive/interactive-mode.d.ts +2 -2
  137. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  138. package/dist/modes/interactive/interactive-mode.js +15 -17
  139. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  140. package/dist/package-manager-cli.d.ts.map +1 -1
  141. package/dist/package-manager-cli.js +12 -0
  142. package/dist/package-manager-cli.js.map +1 -1
  143. package/dist/utils/image-convert.d.ts +12 -0
  144. package/dist/utils/image-convert.d.ts.map +1 -1
  145. package/dist/utils/image-convert.js +45 -6
  146. package/dist/utils/image-convert.js.map +1 -1
  147. package/docs/cli.md +2 -0
  148. package/docs/codemode.md +1 -1
  149. package/docs/extensions.md +4 -0
  150. package/docs/keybindings.md +1 -1
  151. package/docs/mcp.md +23 -1
  152. package/docs/models.md +28 -2
  153. package/docs/quickstart.md +16 -2
  154. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  155. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  156. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  157. package/examples/extensions/gondolin/package-lock.json +2 -2
  158. package/examples/extensions/gondolin/package.json +1 -1
  159. package/examples/extensions/sandbox/package-lock.json +2 -2
  160. package/examples/extensions/sandbox/package.json +1 -1
  161. package/examples/extensions/with-deps/package-lock.json +2 -2
  162. package/examples/extensions/with-deps/package.json +1 -1
  163. package/package.json +13 -14
  164. package/dist/bundle/chunks/azure-openai-responses-AFMVM6NP.js +0 -2
  165. package/dist/bundle/chunks/chunk-6D7LHLKG.js +0 -2
  166. package/dist/bundle/chunks/chunk-7FH3BDRG.js +0 -82
  167. package/dist/bundle/chunks/chunk-DTD7JQ7Y.js +0 -42
  168. package/dist/bundle/chunks/chunk-MJOHEBV7.js +0 -2
  169. package/dist/bundle/chunks/node-O3RIBJRH.js +0 -16
  170. package/dist/bundle/chunks/openai-completions-JXDDPZ23.js +0 -7
  171. package/dist/bundle/chunks/openai-responses-CQDDLDQF.js +0 -3
  172. package/dist/bundle/chunks/pi-logo-animation-MPKZE4EK.js +0 -2
  173. package/dist/bundle/chunks/virtual-modules-3THEGRQQ.js +0 -2
  174. package/dist/modes/interactive/components/daxnuts.d.ts +0 -23
  175. package/dist/modes/interactive/components/daxnuts.d.ts.map +0 -1
  176. package/dist/modes/interactive/components/daxnuts.js +0 -140
  177. package/dist/modes/interactive/components/daxnuts.js.map +0 -1
  178. package/dist/modes/interactive/components/pi-logo-animation.d.ts +0 -68
  179. package/dist/modes/interactive/components/pi-logo-animation.d.ts.map +0 -1
  180. package/dist/modes/interactive/components/pi-logo-animation.js.map +0 -1
  181. package/dist/modes/interactive/components/pi-logo-animation.lazy.d.ts +0 -7
  182. package/dist/modes/interactive/components/pi-logo-animation.lazy.d.ts.map +0 -1
  183. package/dist/modes/interactive/components/pi-logo-animation.lazy.js +0 -12
  184. package/dist/modes/interactive/components/pi-logo-animation.lazy.js.map +0 -1
  185. package/npm-shrinkwrap.json +0 -1952
@@ -0,0 +1,14 @@
1
+ import { Container, type TUI } from "@earendil-works/pi-tui";
2
+ /**
3
+ * A sign-in URL with a click hint and a copy hint. Hosts call `copy()` when `app.message.copy` is
4
+ * pressed, since a long URL wraps and often cannot be selected or clicked as a whole (SSH, tmux).
5
+ */
6
+ export declare class AuthUrlComponent extends Container {
7
+ readonly url: string;
8
+ private readonly tui;
9
+ private readonly hint;
10
+ constructor(tui: TUI, url: string);
11
+ private setHint;
12
+ copy(): Promise<void>;
13
+ }
14
+ //# sourceMappingURL=auth-url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-url.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/auth-url.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAmB,KAAK,GAAG,EAAE,MAAM,wBAAwB,CAAC;AAK9E;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,SAAS;IAC9C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAM;IAC1B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAO;IAE5B,YAAY,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAQhC;IAED,OAAO,CAAC,OAAO;IAMT,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAO1B;CACD"}
@@ -0,0 +1,37 @@
1
+ import { Container, hyperlink, Text } from "@earendil-works/pi-tui";
2
+ import { copyToClipboard } from "../../../utils/clipboard.js";
3
+ import { theme } from "../theme/theme.js";
4
+ import { keyHint } from "./keybinding-hints.js";
5
+ /**
6
+ * A sign-in URL with a click hint and a copy hint. Hosts call `copy()` when `app.message.copy` is
7
+ * pressed, since a long URL wraps and often cannot be selected or clicked as a whole (SSH, tmux).
8
+ */
9
+ export class AuthUrlComponent extends Container {
10
+ url;
11
+ tui;
12
+ hint;
13
+ constructor(tui, url) {
14
+ super();
15
+ this.tui = tui;
16
+ this.url = url;
17
+ this.addChild(new Text(theme.fg("accent", hyperlink(url, url)), 1, 0));
18
+ this.hint = new Text("", 1, 0);
19
+ this.addChild(this.hint);
20
+ this.setHint(keyHint("app.message.copy", "to copy"));
21
+ }
22
+ setHint(suffix) {
23
+ const clickHint = process.platform === "darwin" ? "Cmd+click to open" : "Ctrl+click to open";
24
+ this.hint.setText(`${theme.fg("dim", hyperlink(clickHint, this.url))} ${theme.fg("dim", "•")} ${suffix}`);
25
+ this.tui.requestRender();
26
+ }
27
+ async copy() {
28
+ try {
29
+ await copyToClipboard(this.url);
30
+ this.setHint(theme.fg("success", "Copied URL to clipboard"));
31
+ }
32
+ catch (error) {
33
+ this.setHint(theme.fg("error", error instanceof Error ? error.message : String(error)));
34
+ }
35
+ }
36
+ }
37
+ //# sourceMappingURL=auth-url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-url.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/auth-url.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAY,MAAM,wBAAwB,CAAC;AAC9E,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAEhD;;;GAGG;AACH,MAAM,OAAO,gBAAiB,SAAQ,SAAS;IACrC,GAAG,CAAS;IACJ,GAAG,CAAM;IACT,IAAI,CAAO;IAE5B,YAAY,GAAQ,EAAE,GAAW;QAChC,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACvE,IAAI,CAAC,IAAI,GAAG,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAC/B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzB,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC,CAAC;IACtD,CAAC;IAEO,OAAO,CAAC,MAAc;QAC7B,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,oBAAoB,CAAC;QAC7F,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,MAAM,EAAE,CAAC,CAAC;QAC1G,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,IAAI;QACT,IAAI,CAAC;YACJ,MAAM,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAChC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,yBAAyB,CAAC,CAAC,CAAC;QAC9D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACzF,CAAC;IACF,CAAC;CACD","sourcesContent":["import { Container, hyperlink, Text, type TUI } from \"@earendil-works/pi-tui\";\nimport { copyToClipboard } from \"../../../utils/clipboard.ts\";\nimport { theme } from \"../theme/theme.ts\";\nimport { keyHint } from \"./keybinding-hints.ts\";\n\n/**\n * A sign-in URL with a click hint and a copy hint. Hosts call `copy()` when `app.message.copy` is\n * pressed, since a long URL wraps and often cannot be selected or clicked as a whole (SSH, tmux).\n */\nexport class AuthUrlComponent extends Container {\n\treadonly url: string;\n\tprivate readonly tui: TUI;\n\tprivate readonly hint: Text;\n\n\tconstructor(tui: TUI, url: string) {\n\t\tsuper();\n\t\tthis.tui = tui;\n\t\tthis.url = url;\n\t\tthis.addChild(new Text(theme.fg(\"accent\", hyperlink(url, url)), 1, 0));\n\t\tthis.hint = new Text(\"\", 1, 0);\n\t\tthis.addChild(this.hint);\n\t\tthis.setHint(keyHint(\"app.message.copy\", \"to copy\"));\n\t}\n\n\tprivate setHint(suffix: string): void {\n\t\tconst clickHint = process.platform === \"darwin\" ? \"Cmd+click to open\" : \"Ctrl+click to open\";\n\t\tthis.hint.setText(`${theme.fg(\"dim\", hyperlink(clickHint, this.url))} ${theme.fg(\"dim\", \"•\")} ${suffix}`);\n\t\tthis.tui.requestRender();\n\t}\n\n\tasync copy(): Promise<void> {\n\t\ttry {\n\t\t\tawait copyToClipboard(this.url);\n\t\t\tthis.setHint(theme.fg(\"success\", \"Copied URL to clipboard\"));\n\t\t} catch (error) {\n\t\t\tthis.setHint(theme.fg(\"error\", error instanceof Error ? error.message : String(error)));\n\t\t}\n\t}\n}\n"]}
@@ -0,0 +1,104 @@
1
+ import { type Component, type TUI, type TuiMouseEvent, type TuiMouseEventResult } from "@earendil-works/pi-tui";
2
+ /**
3
+ * Fullscreen 3D easter eggs: the pi logo (header logo click) and Armin (/arminsayshi). Both are bitmaps built from
4
+ * one block per pixel. The current screen dissolves into braille dust while the model spins in the center and its
5
+ * blocks play a sliding puzzle. Leaving plays the same timeline backwards, so the screen reassembles.
6
+ *
7
+ * The pi logo lifts off the header, flies to the center, and grows; the dust spreads out from the header logo.
8
+ * Armin grows out of a speck at the center; the dust spreads out from the center.
9
+ *
10
+ * The blocks are ray cast per braille dot. A braille cell holds 2x4 roughly square dots, so one pixel of a
11
+ * half-block bitmap is exactly 2x2 dots, and the header logo (4x2 cells) is 8x8 dots when it lifts off.
12
+ */
13
+ type Rgb = readonly [number, number, number];
14
+ /** Grid position of a block: column and row in the bitmap, and layer (-1, 0, 1) in depth. */
15
+ type Cell3 = readonly [number, number, number];
16
+ /** A bitmap built from one block per foreground pixel. Each is a block that the puzzle slides around. */
17
+ interface Model {
18
+ /** Bitmap size in pixels. */
19
+ columns: number;
20
+ rows: number;
21
+ blocks: Array<{
22
+ home: Cell3;
23
+ color: Rgb;
24
+ }>;
25
+ /** Camera distance from the model's center, in pixels. */
26
+ cameraDistance: number;
27
+ /** Farthest distance of any block corner from the center, with blocks on the outer depth layers. */
28
+ radius: number;
29
+ /** Largest share of the screen width the spinning model covers. */
30
+ widthShare: number;
31
+ /** Number of blocks the puzzle moves per step, given a random number from 0 to 1. */
32
+ puzzleMoves: (random: number) => number;
33
+ /**
34
+ * Top-left cell of the model's half-block rendering on screen. The model lifts off from there and replaces it.
35
+ * Undefined to grow out of the center.
36
+ */
37
+ origin: {
38
+ column: number;
39
+ row: number;
40
+ } | undefined;
41
+ }
42
+ /**
43
+ * Which easter egg to play. The pi logo lifts off the header logo, whose top-left cell is at `column`, `row`.
44
+ */
45
+ export type EasterEgg3d = {
46
+ kind: "pi-logo";
47
+ column: number;
48
+ row: number;
49
+ } | {
50
+ kind: "armin";
51
+ };
52
+ /**
53
+ * Show the animation as a fullscreen overlay until it is dismissed. The overlay takes focus and mouse input and
54
+ * returns focus when hidden, so the rest of the UI keeps running underneath untouched.
55
+ */
56
+ export declare function playEasterEgg3d(tui: TUI, screen: readonly string[], egg: EasterEgg3d): Promise<void>;
57
+ export declare class EasterEgg3dAnimation implements Component {
58
+ private readonly tui;
59
+ /** The screen to dissolve, as rendered lines. */
60
+ private readonly screen;
61
+ private readonly model;
62
+ private readonly foreground;
63
+ private readonly background;
64
+ private readonly onDone;
65
+ private readonly startTime;
66
+ private lastRender;
67
+ private timer;
68
+ private exit;
69
+ private shuffle;
70
+ private screenWidth;
71
+ private screenHeight;
72
+ /** Star per cell index, or undefined. */
73
+ private stars;
74
+ private cells;
75
+ private readonly ansiCache;
76
+ private readonly raster;
77
+ constructor(tui: TUI, screen: readonly string[], model: Model, colors: {
78
+ foreground: Rgb;
79
+ background: Rgb;
80
+ }, onDone: () => void);
81
+ /** Play the exit animation. A second call skips it. */
82
+ close(): void;
83
+ handleInput(data: string): void;
84
+ handleMouse(event: TuiMouseEvent): TuiMouseEventResult;
85
+ invalidate(): void;
86
+ render(width: number): string[];
87
+ private elapsed;
88
+ private exitProgress;
89
+ private finish;
90
+ /** Each block's displacement from its place in the model at `time`, in grid units. */
91
+ private blockOffsets;
92
+ private flyProgress;
93
+ private starAlpha;
94
+ private hintAlpha;
95
+ private pose;
96
+ private hint;
97
+ /** A sparse, deterministic starfield: one braille dot in about STAR_DENSITY of all cells. */
98
+ private prepareStars;
99
+ private prepareCells;
100
+ private pack;
101
+ private ansi;
102
+ }
103
+ export {};
104
+ //# sourceMappingURL=easter-egg-3d.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"easter-egg-3d.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/easter-egg-3d.ts"],"names":[],"mappings":"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"}
@@ -1,23 +1,56 @@
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. Each pixel is a block that the puzzle slides around.
12
- const LOGO_PIXELS = ["ccc.", "b.c.", "bb.y", "b..y"];
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 YELLOW = [234, 182, 93];
33
+ const PI_LOGO_PIXELS = ["ccc.", "b.c.", "bb.y", "b..y"];
34
+ const PI_LOGO_COLORS = { c: CORAL, b: BLUE, y: YELLOW };
35
+ function piLogoModel(origin) {
36
+ return createModel(4, 4, (column, row) => PI_LOGO_COLORS[PI_LOGO_PIXELS[row][column]], {
37
+ cameraDistance: 10,
38
+ widthShare: 0.35,
39
+ puzzleMoves: (random) => (random < 0.4 ? 2 : 1),
40
+ origin,
41
+ });
42
+ }
43
+ function arminModel(color) {
44
+ return createModel(ARMIN_WIDTH, ARMIN_HEIGHT, (column, row) => (isArminPixel(column, row) ? color : undefined), {
45
+ // Far enough that the perspective stays mild for a figure about 36 blocks tall.
46
+ cameraDistance: 80,
47
+ widthShare: 0.45,
48
+ puzzleMoves: (random, blockCount) => Math.round(blockCount * (0.03 + random * 0.04)),
49
+ origin: undefined,
50
+ });
51
+ }
52
+ // The sliding puzzle: after the model spun for a while, blocks slide into free neighboring cells, a few at a time.
53
+ // Each cycle shuffles, flies every block back home, and holds the model briefly.
21
54
  const PUZZLE_START = FLY_START + FLY_DURATION + 3;
22
55
  const PUZZLE_STEP = 0.3;
23
56
  const PUZZLE_STEPS = 12;
@@ -37,18 +70,19 @@ const PUZZLE_MOVES = [
37
70
  [0, 0, -1],
38
71
  ];
39
72
  /** 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);
73
+ function shuffleSteps(model, cycle) {
74
+ const { columns, rows } = model;
75
+ let positions = model.blocks.map((block) => block.home);
42
76
  const steps = [positions];
43
77
  const lastMoved = new Set();
44
78
  let random = cycle * 7_919 + 1;
45
79
  const next = () => hash(random++);
46
- const key = ([x, y, z]) => (z + 1) * 16 + y * 4 + x;
80
+ const key = ([x, y, z]) => ((z + 1) * rows + y) * columns + x;
47
81
  for (let step = 0; step < PUZZLE_STEPS; step++) {
48
82
  const occupied = new Set(positions.map(key));
49
83
  const nextPositions = [...positions];
50
84
  const moved = new Set();
51
- const moveCount = next() < 0.4 ? 2 : 1;
85
+ const moveCount = model.puzzleMoves(next());
52
86
  for (let move = 0; move < moveCount; move++) {
53
87
  const candidates = [];
54
88
  for (let block = 0; block < positions.length; block++) {
@@ -57,7 +91,7 @@ function shuffleSteps(cycle) {
57
91
  const [x, y, z] = positions[block];
58
92
  for (const [dx, dy, dz] of PUZZLE_MOVES) {
59
93
  const target = [x + dx, y + dy, z + dz];
60
- if (target[0] < 0 || target[0] > 3 || target[1] < 0 || target[1] > 3)
94
+ if (target[0] < 0 || target[0] >= columns || target[1] < 0 || target[1] >= rows)
61
95
  continue;
62
96
  if (target[2] < -1 || target[2] > 1 || occupied.has(key(target)))
63
97
  continue;
@@ -335,11 +369,11 @@ function spinAngle(time) {
335
369
  return phase - SPIN_LINGER * Math.sin(phase);
336
370
  }
337
371
  /**
338
- * Renders the logo blocks into braille cells. Only faces that point at the camera and are not covered by a
372
+ * Renders the model's blocks into braille cells. Only faces that point at the camera and are not covered by a
339
373
  * touching block are drawn. Each face is rasterized over its projected bounds by intersecting each dot's ray
340
374
  * with the face's plane, with a depth buffer resolving overlaps. Buffers are reused between frames.
341
375
  */
342
- class LogoRaster {
376
+ class BlockRaster {
343
377
  /** Braille dot bits per cell. */
344
378
  bits = new Uint8Array(0);
345
379
  /** Lit dots per cell. */
@@ -355,11 +389,15 @@ class LogoRaster {
355
389
  /** Ray parameter of the nearest hit per dot; smaller is nearer. */
356
390
  depth = new Float32Array(0);
357
391
  /** Face index + 1 of the nearest hit per dot, 0 for none. */
358
- faceIds = new Uint8Array(0);
392
+ faceIds = new Uint16Array(0);
359
393
  /** Cells written by the previous frame, cleared before the next one. */
360
394
  dirty;
361
395
  /** Cells with a halo from the previous frame, cleared before the next one. */
362
396
  haloDirty;
397
+ cameraDistance;
398
+ constructor(cameraDistance) {
399
+ this.cameraDistance = cameraDistance;
400
+ }
363
401
  render(width, height, pose, boxes, background, haloStrength) {
364
402
  const dotWidth = width * 2;
365
403
  const dotHeight = height * 4;
@@ -373,7 +411,7 @@ class LogoRaster {
373
411
  this.haloRgb = new Float32Array(width * height * 3);
374
412
  this.haloDirty = undefined;
375
413
  this.depth = new Float32Array(dotWidth * dotHeight).fill(Infinity);
376
- this.faceIds = new Uint8Array(dotWidth * dotHeight);
414
+ this.faceIds = new Uint16Array(dotWidth * dotHeight);
377
415
  this.dirty = undefined;
378
416
  }
379
417
  else if (this.dirty) {
@@ -395,8 +433,9 @@ class LogoRaster {
395
433
  }
396
434
  const m = rotation(pose.yaw, pose.pitch, pose.roll);
397
435
  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];
436
+ const cameraDistance = this.cameraDistance;
437
+ // The camera sits at (0, 0, cameraDistance) in camera space; object space is the transpose rotation.
438
+ const origin = [m[6] * cameraDistance, m[7] * cameraDistance, m[8] * cameraDistance];
400
439
  const light = isLight(background);
401
440
  const faces = this.visibleFaces(m, origin, pose, boxes, dotWidth, dotHeight, light);
402
441
  if (faces.length === 0)
@@ -430,9 +469,9 @@ class LogoRaster {
430
469
  const sx = (face.minX + 0.5 - centerX) / scale;
431
470
  for (let dotY = face.minY; dotY <= face.maxY; dotY++) {
432
471
  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;
472
+ let directionA = m[a] * sx + m[3 + a] * sy - m[6 + a] * cameraDistance;
473
+ let directionU = m[u] * sx + m[3 + u] * sy - m[6 + u] * cameraDistance;
474
+ let directionV = m[v] * sx + m[3 + v] * sy - m[6 + v] * cameraDistance;
436
475
  let index = dotY * dotWidth + face.minX;
437
476
  for (let dotX = face.minX; dotX <= face.maxX; dotX++, index++) {
438
477
  const t = (face.plane - originA) / directionA;
@@ -466,7 +505,7 @@ class LogoRaster {
466
505
  continue;
467
506
  const face = faces[id - 1];
468
507
  // 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);
508
+ const fog = clamp01(0.15 - cameraDistance * (1 - depth[index]) * 0.12) * (light ? 0.5 : 1);
470
509
  faceIds[index] = 0;
471
510
  depth[index] = Infinity;
472
511
  const cellX = dotX >> 1;
@@ -586,6 +625,7 @@ class LogoRaster {
586
625
  }
587
626
  visibleFaces(m, origin, pose, boxes, dotWidth, dotHeight, lightBackground) {
588
627
  const faces = [];
628
+ const cameraDistance = this.cameraDistance;
589
629
  for (const box of boxes) {
590
630
  for (let a = 0; a < 3; a++) {
591
631
  const u = (a + 1) % 3;
@@ -617,7 +657,7 @@ class LogoRaster {
617
657
  const x = m[0] * corner[0] + m[1] * corner[1] + m[2] * corner[2];
618
658
  const y = m[3] * corner[0] + m[4] * corner[1] + m[5] * corner[2];
619
659
  const z = m[6] * corner[0] + m[7] * corner[1] + m[8] * corner[2];
620
- const perspective = (pose.scale * CAMERA_DISTANCE) / (CAMERA_DISTANCE - z);
660
+ const perspective = (pose.scale * cameraDistance) / (cameraDistance - z);
621
661
  const screenX = pose.centerX + x * perspective;
622
662
  const screenY = pose.centerY + y * perspective;
623
663
  minX = Math.min(minX, screenX);
@@ -672,7 +712,7 @@ let playing = false;
672
712
  * Show the animation as a fullscreen overlay until it is dismissed. The overlay takes focus and mouse input and
673
713
  * returns focus when hidden, so the rest of the UI keeps running underneath untouched.
674
714
  */
675
- export async function playPiLogoAnimation(tui, options) {
715
+ export async function playEasterEgg3d(tui, screen, egg) {
676
716
  if (playing)
677
717
  return;
678
718
  playing = true;
@@ -688,15 +728,18 @@ export async function playPiLogoAnimation(tui, options) {
688
728
  playing = false;
689
729
  return;
690
730
  }
691
- const animation = new PiLogoAnimation(tui, options, colors, () => {
731
+ const model = egg.kind === "armin" ? arminModel(toRgb(theme.colors.accent)) : piLogoModel({ column: egg.column, row: egg.row });
732
+ const animation = new EasterEgg3dAnimation(tui, screen, model, colors, () => {
692
733
  playing = false;
693
734
  overlay.hide();
694
735
  });
695
736
  const overlay = tui.showOverlay(animation, { anchor: "top-left", width: "100%", maxHeight: "100%" });
696
737
  }
697
- export class PiLogoAnimation {
738
+ export class EasterEgg3dAnimation {
698
739
  tui;
699
- options;
740
+ /** The screen to dissolve, as rendered lines. */
741
+ screen;
742
+ model;
700
743
  foreground;
701
744
  background;
702
745
  onDone;
@@ -711,10 +754,12 @@ export class PiLogoAnimation {
711
754
  stars = [];
712
755
  cells = [];
713
756
  ansiCache = new Map();
714
- logo = new LogoRaster();
715
- constructor(tui, options, colors, onDone) {
757
+ raster;
758
+ constructor(tui, screen, model, colors, onDone) {
716
759
  this.tui = tui;
717
- this.options = options;
760
+ this.screen = screen;
761
+ this.model = model;
762
+ this.raster = new BlockRaster(model.cameraDistance);
718
763
  this.foreground = colors.foreground;
719
764
  this.background = colors.background;
720
765
  this.onDone = onDone;
@@ -799,19 +844,20 @@ export class PiLogoAnimation {
799
844
  starAlpha = this.starAlpha(time);
800
845
  offsets = this.blockOffsets(time);
801
846
  }
802
- const boxes = BLOCKS.map((block, index) => {
847
+ const { columns, rows } = this.model;
848
+ const boxes = this.model.blocks.map((block, index) => {
803
849
  const [dx, dy, dz] = offsets[index];
804
- const x = block.home[0] - 2 + dx;
805
- const y = block.home[1] - 2 + dy;
850
+ const x = block.home[0] - columns / 2 + dx;
851
+ const y = block.home[1] - rows / 2 + dy;
806
852
  return { min: [x, y, dz - DEPTH / 2], max: [x + 1, y + 1, dz + DEPTH / 2], color: block.color };
807
853
  });
808
- const logo = this.logo;
809
- logo.render(width, height, pose, boxes, background, isLight(background) ? LIGHT_HALO_STRENGTH : 0);
854
+ const raster = this.raster;
855
+ raster.render(width, height, pose, boxes, background, isLight(background) ? LIGHT_HALO_STRENGTH : 0);
810
856
  const halo = (index, base) => {
811
- const amount = logo.haloAmount[index];
857
+ const amount = raster.haloAmount[index];
812
858
  if (amount <= 0)
813
859
  return base;
814
- const color = logo.haloRgb;
860
+ const color = raster.haloRgb;
815
861
  return mix(base ?? background, [color[index * 3], color[index * 3 + 1], color[index * 3 + 2]], amount);
816
862
  };
817
863
  const backgroundFade = smooth(dissolveTime / BACKGROUND_FADE);
@@ -865,11 +911,11 @@ export class PiLogoAnimation {
865
911
  emit(hint.text[offset], offset < hint.keyLength ? hint.keyColor : hint.color, cellBg);
866
912
  continue;
867
913
  }
868
- const dots = logo.bits[index];
914
+ const dots = raster.bits[index];
869
915
  if (dots) {
870
916
  // 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;
917
+ const step = LOGO_COLOR_STEP * raster.counts[index];
918
+ const rgb = raster.rgb;
873
919
  const color = [
874
920
  Math.round(rgb[index * 3] / step) * LOGO_COLOR_STEP,
875
921
  Math.round(rgb[index * 3 + 1] / step) * LOGO_COLOR_STEP,
@@ -884,7 +930,7 @@ export class PiLogoAnimation {
884
930
  continue;
885
931
  }
886
932
  const dust = (dissolveTime - cell.delay) / DUST_DURATION;
887
- if (dust < 0 && cell.width === 2 && !logo.bits[index + 1]) {
933
+ if (dust < 0 && cell.width === 2 && !raster.bits[index + 1]) {
888
934
  emit(cell.text, mix(cell.fg, background, textFade), cellBg);
889
935
  column++;
890
936
  continue;
@@ -929,18 +975,19 @@ export class PiLogoAnimation {
929
975
  this.timer = undefined;
930
976
  this.onDone();
931
977
  }
932
- /** Each block's displacement from its place in the logo at `time`, in grid units. */
978
+ /** Each block's displacement from its place in the model at `time`, in grid units. */
933
979
  blockOffsets(time) {
934
- const home = () => BLOCKS.map(() => [0, 0, 0]);
980
+ const blocks = this.model.blocks;
981
+ const home = () => blocks.map(() => [0, 0, 0]);
935
982
  if (time < PUZZLE_START)
936
983
  return home();
937
984
  const cycle = Math.floor((time - PUZZLE_START) / PUZZLE_CYCLE);
938
985
  const local = time - PUZZLE_START - cycle * PUZZLE_CYCLE;
939
986
  if (this.shuffle?.cycle !== cycle)
940
- this.shuffle = { cycle, steps: shuffleSteps(cycle) };
987
+ this.shuffle = { cycle, steps: shuffleSteps(this.model, cycle) };
941
988
  const steps = this.shuffle.steps;
942
989
  const offset = (position, index) => {
943
- const block = BLOCKS[index].home;
990
+ const block = blocks[index].home;
944
991
  return [position[0] - block[0], position[1] - block[1], position[2] - block[2]];
945
992
  };
946
993
  const shuffleEnd = PUZZLE_STEP * PUZZLE_STEPS;
@@ -975,14 +1022,16 @@ export class PiLogoAnimation {
975
1022
  return smooth((time - FLY_START - FLY_DURATION) / HINT_FADE);
976
1023
  }
977
1024
  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;
1025
+ const { columns, rows, cameraDistance, radius, widthShare, origin } = this.model;
1026
+ const reach = radius * (cameraDistance / (cameraDistance - radius));
1027
+ // Lifting off, the front face must cover exactly the half-block cells (2x2 dots per pixel) despite the
1028
+ // perspective.
1029
+ const startScale = origin ? (2 * (cameraDistance - DEPTH / 2)) / cameraDistance : START_SCALE;
1030
+ const endScale = Math.max(startScale, Math.min(width * 2 * widthShare, (height * 4 - 8) * 0.48) / reach);
984
1031
  const endX = width;
985
1032
  const endY = height * 2 - 2;
1033
+ const startX = origin ? origin.column * 2 + columns : endX;
1034
+ const startY = origin ? origin.row * 4 + rows : endY;
986
1035
  return {
987
1036
  centerX: startX + (endX - startX) * progress,
988
1037
  centerY: startY + (endY - startY) * progress,
@@ -1028,10 +1077,12 @@ export class PiLogoAnimation {
1028
1077
  return stars;
1029
1078
  }
1030
1079
  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;
1080
+ const cells = parseScreen(this.screen, width, foreground, this.background);
1034
1081
  const height = Math.max(1, this.tui.terminal.rows);
1082
+ const { columns, rows, origin } = this.model;
1083
+ // The dust spreads out from where the model starts.
1084
+ const centerX = origin ? origin.column + columns / 2 : width / 2;
1085
+ const centerY = origin ? origin.row + rows / 4 : height / 2;
1035
1086
  const farthest = Math.hypot(Math.max(centerX, width - centerX), Math.max(centerY, height - centerY) * 2);
1036
1087
  for (let row = 0; row < cells.length; row++) {
1037
1088
  const line = cells[row];
@@ -1051,18 +1102,20 @@ export class PiLogoAnimation {
1051
1102
  cell.ink = glyphInk(owner.text, seed);
1052
1103
  }
1053
1104
  }
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;
1105
+ // The 3D model replaces its half-block rendering on screen.
1106
+ if (origin) {
1107
+ for (let row = origin.row; row < origin.row + Math.ceil(rows / 2); row++) {
1108
+ const line = cells[row];
1109
+ if (!line)
1110
+ continue;
1111
+ for (let column = origin.column; column < origin.column + columns; column++) {
1112
+ const cell = line[column];
1113
+ if (cell) {
1114
+ cell.text = " ";
1115
+ cell.width = 1;
1116
+ cell.ink = 0;
1117
+ cell.bg = undefined;
1118
+ }
1066
1119
  }
1067
1120
  }
1068
1121
  }
@@ -1083,4 +1136,4 @@ export class PiLogoAnimation {
1083
1136
  return value;
1084
1137
  }
1085
1138
  }
1086
- //# sourceMappingURL=pi-logo-animation.js.map
1139
+ //# sourceMappingURL=easter-egg-3d.js.map