dsh-neotui 0.4.2 → 0.4.3

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/CHANGELOG.md CHANGED
@@ -1,10 +1,9 @@
1
1
  # Changelog
2
2
 
3
- ## 0.4.2 — 2026-08-23
4
3
 
5
4
  ### Added
6
5
 
7
- - 欢迎页改版:40×19 半块渲染的 DeepSeek 吉祥物 logo(真实色 255 色调色板 + 逐格上下半色,数据内嵌 `src/logo.js`,去背景、无残块、裁剪掉原图底部黑条);品牌行用 **`█▀▄` 半块像素字** 绘制(`DEEPSEEK` / `DSH NEOTUI` 两行大字标,5×7 像素字模),**每约 3.5 秒一道斜向高光扫过字标**(0.9s 扫程,纯 TUI 渲染并随主题语义色,扫过字标全宽含末尾 `I`;`DSH NEOTUI` 用深灰底保证白色高光可见);**logo 三态** `Ctrl+R` buffer:预设(内置)/ 自定义(复用 Ctrl+O 与新建工作区的 `UploadPicker` 单选 `{palette, grid}` 40×19 JSON)/ 关闭(仅标题),持久化到配置文件;版本更新提示收敛到底部(仅「可更新/失败/检查中」时显示,可点击检查,**最新则不提示**);模式选择从内联 4 列表收敛为底部提示「模式: <当前> · F9 打开模式选择(支持自定义)」——与 WebUI 鼓励自定义预设的方向一致;矮视口(<32 行或 <62 列)回退为普通文本品牌行。
6
+ - 欢迎页改版:**吉祥物 logo 默认关闭**(此前的 40×19 象限块/58×27 半块等实现保留,`Ctrl+R` 面板可随时启用预设/自定义/关闭;视觉迭代未能达成理想效果,先回退到仅标题)(真实色 255 色调色板,数据内嵌 `src/logo.js`,去背景、无残块、裁剪掉原图底部黑条);自定义 logo JSON 支持象限格式(`[mask,fg,bg]`)与旧半块格式(`[top,bottom]`);品牌行用 **`█▀▄` 半块像素字** 绘制(`DEEPSEEK` / `DSH NEOTUI` 两行大字标,5×7 像素字模),**每约 3.5 秒一道斜向高光扫过字标**(0.9s 扫程,纯 TUI 渲染并随主题语义色,扫过字标全宽含末尾 `I`;`DSH NEOTUI` 用深灰底保证白色高光可见);**logo 三态** `Ctrl+R` buffer:预设(内置)/ 自定义(复用 Ctrl+O 与新建工作区的 `UploadPicker` 单选 `{palette, grid}` 40×19 JSON)/ 关闭(仅标题),持久化到配置文件;版本更新提示收敛到底部(仅「可更新/失败/检查中」时显示,可点击检查,**最新则不提示**);模式选择从内联 4 列表收敛为底部提示「模式: <当前> · F9 打开模式选择(支持自定义)」——与 WebUI 鼓励自定义预设的方向一致;矮视口(<32 行或 <62 列)回退为普通文本品牌行。
8
7
  - 启动动画(经典游戏启动器风格,**opt-in meme**):纯黑 → 白色从屏幕中心圆形扩散至全屏(1s)→ 四行健康游戏忠告逐句淡入(2s)→ 忠告淡出的同时 `DEEPSEEK` 半块大字交叉淡入(2s)→ 底部闪烁「按任意键进入游戏」,任意键进入主界面——全程纯 ANSI(`█▀▄`),无图片协议;默认关闭,`--launcher-anime`(或 `DSH_TUI_LAUNCHER_ANIME=1`)显式启用,`DSH_TUI_NO_SPLASH=1` 强制关闭;`--script` 模式不播放,窄小终端自动跳过。
9
8
 
10
9
  ## 0.4.1 — 2026-08-22
package/README.md CHANGED
@@ -210,7 +210,7 @@ TUI 支持:
210
210
  - 工具审批、AskUser 单选/多选、Plan Review;
211
211
  - Goal、TODO、后台任务和 Subagent;
212
212
  - 独立插件清单及 `/` 筛选;
213
- - 空白会话欢迎页:40×19 半块渲染的 DeepSeek 吉祥物 logo(真实色、无背景);品牌行用 **`█▀▄` 半块像素字**绘制(`DEEPSEEK` / `DSH NEOTUI` 两行大字标),**每约 3.5 秒一道斜向高光扫过字标**(纯 TUI 渲染,随主题语义色);**logo 三态**(`Ctrl+R` 打开 buffer):预设(内置)/ 自定义(Yazi 风格文件选择器单选 JSON,格式 `{palette, grid}` 40×19)/ 关闭(仅标题),选择持久化到配置文件;版本更新提示收敛到底部(`DeepSeek Harness 可更新 v…` / `dsh-neotui 可更新 v…`,可点击检查,**最新时不显示**);模式选择为底部提示(`F9 打开模式选择(支持自定义)`);矮视口(<29 行或 <60 列)回退为普通文本品牌行;
213
+ - 空白会话欢迎页:**默认不显示吉祥物 logo**(`Ctrl+R` 打开 logo 面板:预设(40×19 象限块 80×38 真实色)/ 自定义 JSON / 关闭);品牌行用 **`█▀▄` 半块像素字**绘制(`DEEPSEEK` / `DSH NEOTUI` 两行大字标),**每约 3.5 秒一道斜向高光扫过字标**(纯 TUI 渲染,随主题语义色);**logo 三态**(`Ctrl+R` 打开 buffer):预设(内置)/ 自定义(Yazi 风格文件选择器单选 JSON,格式 `{palette, grid}` 40×19)/ 关闭(仅标题),选择持久化到配置文件;版本更新提示收敛到底部(`DeepSeek Harness 可更新 v…` / `dsh-neotui 可更新 v…`,可点击检查,**最新时不显示**);模式选择为底部提示(`F9 打开模式选择(支持自定义)`);矮视口(<29 行或 <60 列)回退为普通文本品牌行;
214
214
  - 11 套配色主题:dark、light、gruvbox、nord、solarized-dark、solarized-light、dracula、onedark、catppuccin-mocha、tokyonight、monokai;`Ctrl+D`、`/theme` 或命令面板打开**配色选择器**——每行带该方案的色板(面板/用户/强调/成功/警告/错误六色),**光标移动即对整套 TUI 即时预览**(未持久化,标题栏显示「预览: <name>」),`Enter` 或双击应用并停留继续调整,`Esc` 恢复已提交主题并关闭;选择持久化到 `$DSH_HOME/tui-theme.txt`。
215
215
  - 启动动画(经典游戏启动器风格,**默认关闭的 meme**):纯黑 → 白色从屏幕中心圆扩散到全屏(约 1s)→ 四行健康游戏忠告("抵制不良游戏,拒绝盗版游戏。"等,逐句淡入,约 2s)→ 忠告淡出同时 `DEEPSEEK` 半块大字交叉淡入(约 2s)→ 底部闪烁「按任意键进入游戏」,**按任意键进入**。全程纯 ANSI(`█▀▄`);`dsh-tui --launcher-anime`(或 `DSH_TUI_LAUNCHER_ANIME=1`)显式启用,`DSH_TUI_NO_SPLASH=1` 强制关闭,`--script` 模式不播放。
216
216
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-neotui",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Keyboard-first Vim/tmux-style terminal client for DeepSeek Harness",
5
5
  "type": "module",
6
6
  "engines": {
@@ -0,0 +1,43 @@
1
+ // ascii-logo.js — Welcome-page ASCII-art logo (52x24 character grid).
2
+ // Aspect-true for 1:2 terminal cells: 52 cols / 24 rows = 2.17, matching the
3
+ // source face (740x680 px). Each cell = [rampIndex, colorIndex] (hex byte
4
+ // each; 0,0 = empty). RAMP lightest '.' (no space keeps bright areas textured).
5
+ export const ASCII_LOGO_COLS = 52;
6
+ export const ASCII_LOGO_ROWS = 24;
7
+ export const ASCII_LOGO_RAMP = ".:-=+*#%@";
8
+ export const ASCII_LOGO_PALETTE = [
9
+ "fefeff","fefef7","fdfcfd","fdfbfd","fef9fd","fcfafe","fcfafb","fff2ed",
10
+ "ffefe8","feeee2","feecdf","feebe0","ffeae1","ffeadc","fdeadc","fceadd",
11
+ "fee9e2","ffe9de","fde9de","ffe9dd","ffe9dc","ffe9db","fce9d8","ffe8e3",
12
+ "ffe7de","fee8dd","fce8dd","ffe8dc","fee8d9","fde6da","fee5d9","fde3d8",
13
+ "ffe2d8","ffe1d9","fedacf","fed0c5","f9fefc","fafcfb","f6fdfc","fbfbfd",
14
+ "fbfbfb","fbfbf8","fbfafd","fafaf8","f6f9fd","f8f7fa","f7ede6","f8e6dc",
15
+ "f7dfd5","f9dbd1","f8d5cd","f1f1f7","e9eaf5","ece5e6","e9dbd9","d7d8e7",
16
+ "eed2ce","d9d5e5","d4d1e0","d0d5e8","d0cee0","cfcddc","cecbe2","cfcbda",
17
+ "b5e4fa","c0d7f4","b9ceeb","d0cadc","ccc9d8","c9c8da","c7c8de","c8c8d8",
18
+ "e2bcbc","e8b5b1","c8c5da","c8c3d0","bfc6e2","c5c6d8","c0bed5","abc1e9",
19
+ "a7b8e0","a5b5e1","e7b1af","ebaaaa","dab2b5","c2abbc","f4a19d","d49ea0",
20
+ "bf93a2","c58381","a3b0d6","9baddf","96aedb","95abdb","96aadc","95a9d8",
21
+ "97a6ce","9e9fc6","94a1c4","8faee1","91a7d8","8fa3d8","89a2d0","8c9bc7",
22
+ "8c96c3","839bcf","7697c3","8692c0","888ebf","8090bf","808cbf","7d8cbe",
23
+ "7d8ab7","778dc1","778bbe","718db9","8383a7","7a84b7","7685b4","7585ba",
24
+ "7188bc","7185b8","7085bb","7281b6","6e85b6","6d82b9","6885b3","6c81b5",
25
+ "6c80b6","6780b8","cc716d","9a7684","7778a5","707eb2","717ca8","72769e",
26
+ "6c7fb5","697eb9","697eb2","6b7bb1","687eb5","677eb1","677db7","687db2",
27
+ "677daf","687cb7","677cb3","677cb1","6774a6","657fb7","657fb3","667db2",
28
+ "647db6","647db3","667cb6","667cb4","657bb5","667bb2","657ab3","647ab4",
29
+ "657baf","657aaf","6679b0","6578b0","6479b0","6478ad","637db5","637cb5",
30
+ "627cb5","627bb4","607eb2","607cb3","6379b3","617ab3","6279ae","6278ae",
31
+ "6278a8","5f79ab","5b77a6","6175aa","5e74ae","5f72a7","5974a1","5d70a7",
32
+ "5c6f98","586fa5","576da4","576ba4","586a9b","586799","536ba2","53689f",
33
+ "4f689d","6b6486","586495","54659c","53628f","4e639c","4f6389","505e94",
34
+ "495e99","715861","7a4945","594d64","4c5b92","4c5781","485a8f","475084",
35
+ "455a94","445991","435891","435792","42578c","425691","43568d","41558e",
36
+ "42538f","465286","3f538d","3f5087","3c4c86","3a487e","32477e","724245",
37
+ "523e4d","3e4678","3c4184","5f3640","3f3857","552b30","391d2b","33457d",
38
+ "2e4076","313c6e","293672","2c345f","253163","252a57","221d38","1b2e61",
39
+ "11295c","152350","0c2357","0f1f56","0d1d50","121d46","0a1e53","101a48",
40
+ "0b1849","161536","071746","030e3c","080e29","090728","0c0605",
41
+ ];
42
+ export const ASCII_LOGO_GRID =
43
+ "0000000000000000000000000000000000000000000000000000000004b9002c0005002c002a0034013e036205c805c905c60147013e000105de0367025d0367002800040005002705ce000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005ce002d0029002e013d0035003507f607f505c905c9013c013807f904a6049606f00481047e024f013a00040003024c0034024c04b90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002d0003013b0006002e05c20250049e0498049a06df06df05dd05c905d404ae06e906df06dd07f1048a0372049704ab024301380145000100280006000305cc0000000000000000000000000000000000000000000000000000000000000000000000000487002c002e000200290475035f04ac049904ad06df049204a805bb04a904a7049904a704a7049b06f007f304b506eb04a70499049a049a025c07ef0144013e00340006003500000000000000000000000000000000000000000000000000000000000000000000002600290029013d0252037204a604aa0482049d049b04aa04b504a004aa04aa04aa04aa04ac04ae04a00499049904990499048204990499025e0360025c013b013e05cc05c707fb0000000000000000000000000000000000000000000000000000000000000001025601460138025d04b1036004b604a904b0049e04ae06f0049d04ad04ad04ad04ad04ae04ae04ae04ae04aa04aa04a8025f036a0497025f036004a60473048c07f8014b0256013d013800000000000000000000000000000000000000000000000000000001002b014c0138025204b104b604a504b604a20365049104a504b804b10360036b0365025c0252037203650365049b049c025c04a1036704a205d8047904b90023002106e105c3013f013e013e06e500000000000000000000000000000000000000000000000001440035013a025204a20498049d04a004ba036a04ae04a505d704b004b1036004a60366036604a2048e04ad048f049b04b504960496049904aa049907f704ac04ab06e7001900150011013a014005cd0000000000000000000000000000000000000000000005d50144013f025004b004aa04ac04aa0482049d04a80482049b07f10493048d0496048f048f047e04940481048a049e04800256047e05bf048f048f049b0492049b049a04ac04ad05c2000b0019000805cc07f2001e001105cb000f01320000000000000000000007fb014b0252049f05c904aa04aa049905db048a0492048a04b602550480047a047a0480047a047a04b1047804780491047a0255025504860478048800300031000b05cc048704a604a205c606e1001a001e000c035a0249000b024a024a00000000000000000000025204a304af05d204ae04a804a8049905d4047e05dd047b05ce06e507ef036904860370036f036f036f036e036f06ed036d01390013001202580473036e047706e101390019001f000c000906e105c200110031001b00300124024a05cb000007f7048e04b405d105c407f405cd0499049b0499048a047e05d1047905bc04c2000a000e0019048503690477036c036c036c05d00476025604750016001500150015000f025806ec05c404770480048605c205cb0022001a001505cb000c00310254025402530000000000000000000007f306f0049b05d10493048d047e047e05bc06e707fa001807ff07ff07ff0008013d04880137047703690369036205cc000f0031024a0132013907e7000f0014025804710478047804bd05bf05c006f005e0025400100015001c000f00150000000000000000000005da06f0049c05d1049e04930480047806ea013307e70140002d06e206e906d9024c0009002f03710484024906e504c2000906e006e600310132003105e006e60484036d0371048c04a605bc05c005c606e806e906e402530014001a001b00000000000007fc05da05da07f4048d05d6048a048b05c80477036c013906e0000406df02500141025000020010000f000f0017000f00120015001c00200023012301240124012401330368047605bd0486049407f405dc07f9036106ed06de06de06de06de06ea000006ed036805da05da05cf06ea025c07f505cd0252048605c3036f036c0009013301230133013200320020001200120012000900170012001300150020002100230023002106e207fe05cc05cd047c036006f006eb05c4025b0363014d024d014d024d014d06ec05da05d706e2036103610369036305ce07f107f805d104a4036605be05c506ec048707ff002101230032001f001c0015001500120012000f000f000f000f001e001e0020001e001707ef04860486047805db05d807f405d405d805d704ba05bb07f500000000000001420142014207fc0143026107f505cf05cd05d505d106df05c4047c047c036e06e1001e001f001f001e000f001506e00383045a035a04830383038300130012001200180249024a05c507fc05d905d9048c05d306eb05d404a305d804ba04b804ba05cd0000000000000000000005c007f505d505d104b804b805d805c005c4049805dc05d805d605d905c20016000b001200120015001505cb0257025702570257001a000d00120012000b06e105c105d305d70492048e049005d105d607f705d604b405d705bc04b406f004b807f7000007fc07fc0141025105c9047e04a305db05bc05db05d604b40490049304a405bc05d905c405be06e00030001300120012001200130015001200120019013105c305c005c605bf04b6047b04980490049005d105d405d607f107fc07fa048b05d507f70264025c036401430141014105c6048d049105d805c005d805d805d605dc0366048004940372047b06df04ba05bf04ba05bf05cb06e40139000a000d0139035a035a024a048804b304b704b205c0048b0366047d06e80142014d002500060006002a0001048b048607f9026404b601410141025204b004a105d205bb00010007002c024f013f013f036a0366047b047b06f004b304b304b3014d07ef02530254035a035a02540254025301380147024d04b204b20373047d047407f20147014e002c05c7003505c304b9002507fd07fc06e205c304b3025d047f047f048b047905c107f2002900290006000600060148024b06f0047d047406ea05be07fc024f0147014b014b0148014806e701470148014601460148024f024f07f6036b036b07f307f6013e00060034002a05ce05bf0027002a07fa06ee06e206e20474047407f7047f037405c405c007f6002a00290004013b013b0034014804b70252036b07f207fd06ef0147014701470146014b04750362036204880144013c07fa07fa07fd013f036e036a0491047d00340148002900030025002a000602560006002d06ec06e2";
package/src/logo.js CHANGED
@@ -1,43 +1,43 @@
1
- // logo.js — Welcome-page logo data (DeepSeek mascot face, 40x19 half-block).
2
- // Generated from the source artwork: background removed, black bar cropped out,
3
- // 1:2 terminal-cell sampling, colors quantized to 255 entries (0 = empty cell).
4
- // Each grid cell encodes [top, bottom] palette indices (one hex byte each);
5
- // rendering uses the half-block glyph: top half = fg color, bottom = bg color.
1
+ // logo.js — Welcome-page logo data (DeepSeek mascot face, 40x19 quadrant).
2
+ // 40x19 cells × 2x2 quadrant pixels = 80x38 effective resolution.
3
+ // Each cell = 4 bytes: [type, maskByte, fg, bg]; type 0 = quadrant with
4
+ // maskByte low nibble = character quads, high nibble = second-color quads.
5
+ // Ordered luminance pairs (bright = fg), near-identical pairs collapsed.
6
6
  export const LOGO_COLS = 40;
7
7
  export const LOGO_ROWS = 19;
8
8
  export const LOGO_PALETTE = [
9
- "fdfffc","fdfdfd","fdfcfd","fefbfd","fcfafb","fef5f5","fcebdd","fdeade",
10
- "fdead9","ffe9df","ffe9dc","fde9de","fde8db","fee7dd","ffe5df","fee3dd",
11
- "fde5d9","fee3d7","ffe2de","fafefa","f7fdfb","fafcfc","fbfbfc","f8fbff",
12
- "fbfafc","faf8fb","f8f9fb","f4faf7","f7f2f0","faeae1","fae6dc","f9e3d8",
13
- "f2f4f5","f4e7df","daebf3","fee1d6","fbe0d6","ffded6","fddbd0","fdd8ce",
14
- "f6dfd6","f4dbd3","e9dbdc","dbdbe0","fcd6cc","f5d6cd","fdd1c6","e3d3da",
15
- "d5d1e3","e8c7c3","d1d1e8","cfcedb","d0cce2","cfcddc","c7cfe3","c6cce0",
16
- "afdbf3","a7cdf2","cec9dc","c9c9da","cac6dc","c8c7d8","c6c8de","c0c8e6",
17
- "c5c5da","ecb9b8","d6b6bb","ebadaa","deadb0","c2c1d4","b8bedd","bfb6c7",
18
- "cdaaac","a1b5e6","adb6cf","9bb3e2","a4aed6","9bb0e2","95b1e6","92aedf",
19
- "96abda","92aad7","f8a099","e6a4a5","d6a7b0","d89190","cd8b8a","a5a5bf",
20
- "97a3cb","96a8da","a493a4","92a9d7","8ea8d4","92a5d3","90a1cf","8e9bcb",
21
- "8e94bc","849ecb","809bc8","8498c3","8793c2","7894bd","8491be","818cbb",
22
- "748dba","7f8bbc","7a87b7","7789bb","7489bc","7186b9","6e88b6","6d86b6",
23
- "7f83ac","7684b5","7583b8","7383b5","7281b2","6f83ba","6f83b6","6e81b7",
24
- "6f80b4","6b84b8","6b82b3","6b81b7","6b80b7","6883b4","6880b8","6381ab",
25
- "b36865","897c98","777d9d","6e7fb3","6d7aab","6d7593","6a7fb9","697db9",
26
- "697fb4","6a7cb2","6a78ae","677eb6","677db4","667cb5","677dae","677cb0",
27
- "687ab3","677aa9","6772a6","657eb3","637eb5","647cb1","657bb2","627bb4",
28
- "657ab3","6579ae","627ab1","6279ab","6478b0","6377ab","6278af","6276ad",
29
- "6373ae","626c9a","5e7caf","6079b1","5e77a8","5e76a2","5675a8","5e72a9",
30
- "5b71a1","5870a9","5b6fa5","5f6ca8","566da6","596c9f","586b8d","546d9d",
31
- "536ba0","506aa1","54679f","52679e","53659f","53649d","52609c","5c6797",
32
- "536697","556298","5a617d","50659e","4b649a","4a6198","4d6099","4b5e97",
33
- "4b5e8a","7e4c49","615068","694047","4d598b","4e486c","475a92","47558d",
34
- "475389","474d80","455a94","445894","445793","435791","445884","43558a",
35
- "43528c","434776","41568f","40538e","3f548b","3f547e","3a4d86","3a4a74",
36
- "38477f","3b4477","34437d","3f4366","3c3e63","304175","2d3d76","293b73",
37
- "5c353d","433348","4f2c33","482128","323568","2d2e56","322847","2d1e31",
38
- "21376c","20316b","1d2e66","232b5a","1b2b60","1c2759","222445","241f3f",
39
- "1c1a29","152859","102458","132051","131f44","0b1e51","131a41","0c1b4e",
40
- "081a52","0c1547","101432","041546","070e39","00113c","06071d",
9
+ "fefffd","fcfcfc","fcfbfb","fcfafc","fef2ea","feede3","feebde","feeae4",
10
+ "fee9de","ffe9dd","fdeae1","fdeadd","fdeadc","ffe8de","ffe8dc","fce8dc",
11
+ "fee6df","fde7da","fee5dc","fbfcfc","fbfdf9","fbfbfb","fbfafc","fbfaf5",
12
+ "fbebe2","fbe6df","f7fbfc","f7f8f9","f7f2ee","f3f4f3","eeecee","e2e9f1",
13
+ "fee3d9","fee2d7","fae2d8","fdded4","fddcd2","f6e0d7","ebe4e0","e1dfe8",
14
+ "f8d8cf","f8d2c8","ebd7d6","dfd8e1","f9d0c7","f4cac4","e7cecb","e7c8c2",
15
+ "d5d9ea","d4d2e1","d1d0dd","d0cede","ceccda","b6def8","bed4f3","cbcee2",
16
+ "c2ccec","cdcadc","cbc8d9","cac9db","c9c6d5","c7c7d7","c6c6d8","c0c8e1",
17
+ "eabbb9","dbb9c0","e4adaf","d6b0ae","c5c4d9","bfbecf","b3bbda","b5b5ce",
18
+ "b6acb7","a6b6e1","97b1e6","94aedf","a0acd4","97acdf","9aa9d2","94a9db",
19
+ "92a9d8","e9a3a3","de9896","b39da5","c78d8b","96a5d5","9ba5c3","98a0c3",
20
+ "978fab","91a6d6","8fa3d7","8ea0ca","8f9dcb","8c96c2","8c91b9","7ea1cc",
21
+ "879ac3","8498cb","8294c6","7893c2","818ebe","8590b5","7b8dbe","738db6",
22
+ "6d8cb9","7b88ba","7c85b8","7d85af","7787ba","7089bb","7386b8","7385b8",
23
+ "7084b6","6a84b4","7782ab","7282b4","7180af","6f82b5","707eb1","6b81b6",
24
+ "6b80b3","6a7eb6","6980b7","687fb7","687eb6","697fb2","6680b7","647eb6",
25
+ "af6c68","8c748a","77799a","6e7caf","6d78a8","6d6783","6a7db6","687db3",
26
+ "687cb1","6979ad","696d90","677db6","667cb6","667cb0","677bb4","667aad",
27
+ "6673a4","647db6","647cb5","637cb5","627cb4","627bb4","647cb1","617caa",
28
+ "657ab3","647ab0","637ab2","6279b2","6277b0","6278ab","6275ac","63739f",
29
+ "607aae","5f76ad","5d75ad","5d72a8","5973a8","5d70a6","5d6f98","576fa2",
30
+ "5c6b9e","576b9e","556b9f","59679c","576898","566888","526aa1","4c689e",
31
+ "5166a0","586498","5d6280","4e6497","4d619e","4d609a","4b5e96","495a94",
32
+ "525a84","4f5c71","475c93","455993","455892","445792","44588d","42587c",
33
+ "794e4d","654c51","5c506e","78403d","5f3b51","51557a","4d4e7a","4d3f5d",
34
+ "523745","45558d","42558f","41558d","45528a","40538d","415088","434572",
35
+ "44436c","423c5a","3e538c","3c5285","3d4f83","384b83","354881","394677",
36
+ "354275","30447a","393f75","3a4064","323f6d","303a6e","34395c","2b3e72",
37
+ "293a6c","29386f","583136","38324d","2f3155","263363","592527","432025",
38
+ "2d2945","2b1b33","213266","193166","1e2b5a","192c5f","1f244b","182755",
39
+ "1c1a38","132a5e","12255d","122256","0b2258","0e1e52","131e44","081c49",
40
+ "10173c","11152c","06174d","071444","0e0f2b","010c39","07061f",
41
41
  ];
42
42
  export const LOGO_GRID =
43
- "000000000000000000000000000000000000000000640016151705360503348282dcbebfbdb5ed3c2101f53c5c4e5d52038f051904033c1700bb000000000000000000000000000000000000000000000000000000000000000000000000000000210021c3050286031c354c1b7ffa7ff4bfbfb7c6eb33a1c0e0a3d79a897c8763e92c5d032c1b3501461818d61800860000000000000000000000000000000000000000000000000000000000001c14171d021702333f5062959599a4d9d78edfb28e95959598958ea78eaddb6d7676879b94958d94fa9c31fe043b0534191a00160000000000000000000000000000000000000000000000000000152317193401315c514e8d9898a4e9998e998ed78ea495989598959895a495989595959595957f7a9595967e6f51d89216fb01823e3500bf00000000000000000000000000000000000000000000bb193405331af44e645c525c89a0a16d99a999878e6d98999899999f989b98a498a598a88c6f995ca45d977c4e969a8e62e25a853e6086343635663b000000000000000000000000000000000000dcd819053b14f54c929d9f8e9d88a0a05e9fa64eedad8a8f525c5c5e5c62609a4e9a5c90978a9d8a4c9597989098f39b998e2a8b0f0fc3263be33b3b353100c100000000000000000000000000000000d947863b82627b98989898988ef29c8e9b999b8e87919d8a9095a3888d878d9794878c7d887dbe3b8c7c958795888e8d99d99899a59b29ac0b0ae40a35e1e0ba000a000d220be2200000000000000000eec0334a9a99a4a498959895cccb8e87998788e5e65b7c747674786c786db979766b776c6f6a43ca85c67dc78785c329bb247b1096e49d93bab72eb80c2e490e8128492710442e420000000000fc00ae3a9a9cd5d599a49895999588cbcb76c0736ac613dc0dd893936b6a656a676a6568657585686532090c0b45076a45726cc3c70f720b7511322007c60de10d08490c0a27c242c4c25600000000f600f900dfd0f9a199aa8e8c897d7b77cbbfe645f04907060b1e322665a26b4865686565ca60e749e71e0b0b0b0b0b0b0d20320ee62a756879737584a2b342b920e111440c0a1009c207420a0000000000000000f2c88b9dcbcc8d8d7d766e73acee32e4f12bff01ddebe3db81587107553d6b82615558ef072b2ac43022ff27ff2ee42a0a1368656b689284ada8b4b6dfdee2da07e10b420b250b2200000000005fbac9d0c88d4edfcb9c88a57d6b73f067e12a1b82d715f3233a230122070b07070b0707070b070b0b1124272d2f2f2f2f2d2d316167f6ab6a7984b7f9b8d7fe59da59d959d95fdad9c3f4005f60c8c9c9c9d7c9f24af4f8f8c7fa789eae7967ba4a752ee8282f2e2d272724120a0b0a0b1e0b09070a070c071207241228242d24612532f0b96b758451ebbfc7b3ea4dd559ee474040f540faf7dec940fe4059404737595959deed4ed0d5bcc791b67279ee7449c41e11272524110e070b0d0bc20b2e07e107810781070c0b0a0b0a0913f14379b675d1d1d1d38fcef9cdcdd3bcaad3aaaadeab000000000000000000aebff8c7becb768ab3cecef9d1d1adeac073d36bd161a2e21e0a0a0b0a090a810b535353535353530a0a0a0a0a0b1e0aafbabdf6d0d49d7c908a8deacecceaa8d5ce9eaaccf3d6abf6004a00fefeeefe3939fac0628f96d5d5b5cdd4ceb6f98a90a18abccec8d1c5b129b40ab80a200a0a0a0a110c0d0c0c0a0cc407e1b8adb2b1b39e73ab89908d4ec7f2ccccd5d3ce169a03ce9284ce4ffef44fbeae393939f4b7908f8f8ad3b302d41bf9e0d1335afc9062907c7cd5aba8aae9aaa7b0335744e45708571d5757545744e1e2b847a7a5abe07d5d517e7eedde3f333c1514172c041b02027b75d07b4ff7b0a63a524c7e8f7ad77ab3b91905191704194603373e4cf75c706e6fab9ca5a9a54b3f3f4641453c5438544945345b3c3c3c413deb47e9fd6f666f694747462cbb23ad14bdb2b0381605e6e7e6dcd2d25d6669fc8f696db9b9b0b9fb0517171b2c051a34463440dd4ca66666efef863d3cfb3dfb3df73d48ddf04bf03fe63cff3ddd3ed21cdc413e66636980f4803e191a05a61bbb211482021bef05ddfbd2d2";
43
+ "0000000000000000000000000000000000000000000000000000000000000000000000000000000000088600002e17c6000f150000cf1635000f1600001f284900af34b200ef64b6008fb5ed003f35d5001f1e34001f38c7004f51df004f516e009f40dc008f023d004f0435002f16ba0004330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c18ba000c1e00002e183200cf1c33000f1c0000cf33b400ef2875003f93fc003fa4ed004fb6e200df31b500ef31a0005fccf400cf7fd7002fa1f5000f7b00002f9df600cf2862004f28fd004f2c86008f2854001f1454001d1486008c1e6100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008ba00001f16b3000f1c00000f1600008f33ff00ef01ac00ef4b95000f8d0000cfa3e2003f8dd7001fa3d7000f9300000f9600000f9400008f94af008f8daf003fb5ec008f7ad7004fa3f3000f8000000f780000ef50ad008f32fd00cf033a00cf0433000f0200009d17b300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005f1bba000f1400002f1733006f47b9004f4ea6000f7d00008f94ec002f9cbe000f9300000f9d00000f8c00000f9500000f9500000f9500000f9500000f9600000f8000000f8000000f9200000f7f00000f9400005f4c95002f5a7e002f50fa00cf1447004f1f83002f3ce5008cb4ec000000000000000000000000000000000000000000000000000000000000000000000000000000000008c000002e0234003f0446009f2046001f4e83000f5100000f5100008f78d3001f6deb00bf639b008f99ec000f7b00000f9d00000f9c00000fa200000f9c00000f9c00007f4e9c000fa500000f7800003f5193001f5ba1000f9700006f4b9e00ef5c9a00cf64e400df4b84008f48ef000f3b00000f3a00009d38c3000000000000000000000000000000000000000000000000000000000000000000000000002a1b20000f0300003f173500ef205600ef508e000f780000df4e8d000f9d0000cf5a9e00cf509d004f9db700af4e9e000f5a00000f5100000f5100005f5090005f5a8e00cf5a9a00ef5b7800df4ea400ef6297000f880000ef5b9b008f9bbc000f8000001f5fe8004f242f003f13b3001f2cdf000f3400008f3bdc0004fe000000000000000000000000000000000000000000000000000000000000000000006e3ddd003f3a83006f66fc00ef5697000f9600000f960000af7fda000f9900000fa300000f9d00005f99e2000f880000ef6288000f8000000f7d00000f7b00000f7d00000f7800000f7b0000df7aef000f7b00000f8c00000f8d00000f8d00001fa3f2000f9300000f850000df108b004f1ae3002f2154008f2ce8008da0f6004c07c2000c0e00001f2bc5009d13c200000000000000000000000000000000005f58f300af47b900ef519b000f8d00000f9400000f8000005f87d6000f8c00000f8d00007f44b200af42ac000f7600000f7100000f7000000f7000008f71a6000f6f00000f7700000f6f0000df43c600cf73c700cf88cf00cf85c6003f08c3003f248b003f138e008f58c9000f8500000fb20000cf30b3004f102e002f23e8005f2d81009f42c400ef1a5500ef26430008f40000000000008cdafe008c5d79001f6ffe008fa8ee001f9bd3000f9600000f8d00000f7c00005f7abe008f88e2008f6fe9003f2fd0003f2bdc007f65d1008f6ade000f6500000f6500002f65dd004f6584008f75e5008f65df001f0742000f0900001f2953003f446a008f6ab7002f85e300cf1ab200cf0f83004f29e8000f1000003f12c3003f0f82000f2300000f0900002f2ac4008f41c400df415200000000000269000003f6000003fb00005fcbf6007f99da000fa200000f8c00000f7800000f7100005fb7d600af83d1005f2fae000f2600000f0b00002f2ad2008f5885005f4de5004f5e6c000f5e00003f5e6b001f426b001f1a66000f0d00000f1200000f0700000f0600002f1244003f2adc005f6fe6000f7400008f77a700df86b1001f59e300cf21c5002f2ec2000f1000002f0ac1002f29c1001f29550000000000000000000000000008fc00002fcdf4001f67b1000fb700000f8800000f8e00000f7000009f90e1008f2ae8007f3de4007f1efa003fdbdf003fd7ea005f2bc9003f06e5008f2b6b007f42b9003f425e005f46c6004f104900cf2ac1002f19ff003f25ff003f24ff00af54e8004f2681000f6600008f6ade000f7700000fa600004fb1e000afb8db002fdbe300cf2fc800cf0f43000f2300000f10000000000000000000004c5d8a00ef56cd000fcd0000af508f008fbbf5004f7eb7002f79aa004f6a91002f65c2007f25e3004f1f81009f37e6007f2060003f3760001f05df000f0900000f0c00000f0600000f0d00000f0900000f0f00000f2100000f2500000f2d00000f2d00000f2a0000ef24ae008f5ead001f74f0007f4e77008f90f3001f90f4003f4ffb003f57d8003f4fd600bf5cd9000fd800007f47c8004c5b6300cf6dcd00ef5ecd00cfcdde00efcae6001f56f8005f5db700bf85b8006f4e9f002f71ee008f77de002f6bd2004f295e000f2e00000f2900000f2500000f2200000f0a00000f0a00000f0b00000f0d00000f0c00000f0c00000f1300000f2200000f2500000f2500003f21d100bf30c3003f6aa600bf637400ef50d700afbcf6001fbff400cf58bf008f58fc000f4700008f48e600cf40fc00df47dd003f40cd003f39d8003f394f004f39fa009f394d00ef58e100ef4bf0000fbf00004fccfb007f6d8a002f74a9003f84a900df44c9000f0800000f2400000f2200000f1200000f0d0000cf0ae3004f2154008f30e800cf0c5500cf0d81000f0600000f0a00000f0900000f230000af2bd100cf6db600cf74ce001fceee003f8acc00afbeeb000fbd0000bf8fbd007f9db1000fa50000ef5bcc0004fb00000000000000000000000000006eb1f8007f4ad5001fbbfb007f9fd300af8ecf001fbdf6008fb8f4007f89bd00bf7ed500cf74cb00cf6dce00ef6cef001f19f1000f0900000f0a00000f0a00003f12c4000f5200000f5200000f5200002f2de7000f0e00000f0900000f060000cf10dc00ef86b0004fbdf3007f7dd6000f790000af8ebb004fbcfb001fcbf5005fa4cb00bf9ebd004fa3cb009f89ec000faa00008e5bf30008fc00007f36d5001f36d500af36e0007f6ed7000f9a0000efb1cc009fb1ce008fcef2005f9fb8000f8900002f88d600bf8fcb002fb5f4002fb0b3001f5fe300cf09c6004f0c2f000f1100000f0a00000f1200000f0900000f0700008f1ae700cf2682001fafc3002fafec008fabfb002f8ad5000f8900000f790000afa4e0000fbe00000fbe00007f1cdd003f149a006f7ee6008f9fd3004f4bab002f4c7700af63e0000f3600008f36e0001f67bf000f8e0000ef8ebc007f02cf003f20d3004fd3f7007f39b4004f9ef000bf628e00bf6488001f78d7005fa5eb000fa400000fa800003f4dac004f52e3001f43c100cf105500cf0855001f53e7001f4355008f42f1009f5ced000fa500002f8ed3007f4c71004f6298008f67d8003f48e6007f0145001f1535008f1649008f1c8b002f14f9000f7900001f74aa00cf4bef002fa4f800ef36aa00ef3772008f7ea8007f6ea6008fabed000f1700000f0400002f042800bf013e000f3800009f73f600cf5b68002f71bf005f98d8009f9eeb003f47d4000f4500002f45ea003f3c41003f3f52008f43f1003f3f52001f3aea000f3b0000af3f48001f58ef00cfa8f700ef5064008f69b4005f48ad007f143f006f1eae003f15af000faf00005f1baf000f1400005fe5fa007fd0e5000fd000000e6800004f72ee006f68b4009f69ad000fad0000efb4f9000f0200000f1600003f1c2c00cf1d33006f1e3500af3fad001f68ee000f6400008feffa001f38ff00cf3efd00cf3ffa00cf3cf1000f3f00004f48f1004f58ff00df3ee500cf3cfd00cf3cdf00cf3fd100cf20d1002f3aef004f6068001f98f5008f61dd009f2882001f143d003f02a7009f1b6c008f1fc3000f0400003f03fd00bf1be5000fd000";
package/src/views.js CHANGED
@@ -20,6 +20,7 @@ import {
20
20
 
21
21
  import { T, themeName, cycleTheme } from "./theme.js";
22
22
  import { LOGO_COLS, LOGO_ROWS, LOGO_PALETTE, LOGO_GRID } from "./logo.js";
23
+ import { ASCII_LOGO_COLS, ASCII_LOGO_ROWS, ASCII_LOGO_RAMP, ASCII_LOGO_PALETTE, ASCII_LOGO_GRID } from "./ascii-logo.js";
23
24
  // Live theme accessor: K.K.DIM etc. resolve against the active palette at render time.
24
25
  const K = new Proxy({}, { get(_k, key) { return T[key]; } });
25
26
 
@@ -2758,17 +2759,24 @@ export class ChatView extends Widget {
2758
2759
  // 40x19 half-block logo, per mode: preset (bundled), custom (JSON file)
2759
2760
  // or none (title only). Centered on the 40-col block.
2760
2761
  this.welcomeVersionRows = [];
2761
- const logoDrawn = this.view.w >= 48 && this.#renderWelcomeLogo(screen, cx, top);
2762
+ // the logo only fits when the view is tall enough for its own grid;
2763
+ // otherwise the welcome falls back to the plain text brand lines.
2764
+ const needRows = this.app.logoMode === "none" ? 0
2765
+ : this.app.logoStyle === "ascii" && this.app.logoMode !== "custom" ? ASCII_LOGO_ROWS
2766
+ : this.app.logoMode === "custom" ? (this.app.logoData?.grid?.length ?? LOGO_ROWS)
2767
+ : (this.app.logoStyle === "ascii" ? ASCII_LOGO_ROWS : LOGO_ROWS);
2768
+ const logoH = this.view.w >= 62 && h >= needRows + 6 ? this.#renderWelcomeLogo(screen, cx, top) : 0;
2769
+ const logoDrawn = logoH > 0;
2762
2770
  // Brand lines: TUI-drawn half-block wordmarks (like the splash), each with
2763
2771
  // its clickable version/update row on the right; the diagonal shimmer
2764
2772
  // sweeps across every ~4s. Falls back to spaced text on short/narrow views.
2765
- const brandY = top + (logoDrawn ? LOGO_ROWS + 1 : 0);
2766
- const useWordmarks = h >= 32 && this.view.w >= 62;
2773
+ const brandY = top + (logoDrawn ? logoH + 1 : 0);
2774
+ const useWordmarks = h >= (logoH > 0 ? logoH + 13 : 32) && this.view.w >= 62;
2767
2775
  if (useWordmarks) {
2768
2776
  // The band must run PAST the right edge: the final I of DSH NEOTUI sits
2769
2777
  // at x0+57 (plus the ~0.6/row diagonal offset), so phase 1 lands ~x0+72.
2770
2778
  const bandX = this.app.brandShimmer >= 0
2771
- ? (x + Math.floor((this.view.w - 58) / 2) - 8 + this.app.brandShimmer * 80)
2779
+ ? (x + Math.floor((this.view.w - 58) / 2) - 8 + this.app.brandShimmer * 88)
2772
2780
  : null;
2773
2781
  const glow = lerpColor(T.HEADING, 0xffffff, 0.18);
2774
2782
  const w1Y = brandY;
@@ -2802,12 +2810,30 @@ export class ChatView extends Widget {
2802
2810
  }
2803
2811
  }
2804
2812
 
2805
- /** The 40x19 half-block mascot for the current logo mode. */
2813
+ /** The welcome mascot for the current logo mode/style. Pixel style renders
2814
+ * 40x19 2x2 quadrant blocks (80x38 effective); ascii style renders the
2815
+ * 52x24 character grid (aspect-true for 1:2 terminal cells). Returns the
2816
+ * drawn height in rows (0 = not drawn). */
2806
2817
  #renderWelcomeLogo(screen, cx, top) {
2807
- if (this.app.logoMode === "none") return false;
2818
+ if (this.app.logoMode === "none") return 0;
2808
2819
  const hr = this.view.y + this.view.h;
2809
- let pal, grid;
2810
- if (this.app.logoMode === "custom") {
2820
+ if (this.app.logoStyle === "ascii" && this.app.logoMode !== "custom") {
2821
+ for (let r = 0; r < ASCII_LOGO_ROWS && top + r < hr; r++) {
2822
+ for (let c = 0; c < ASCII_LOGO_COLS; c++) {
2823
+ const i = (r * ASCII_LOGO_COLS + c) * 4;
2824
+ // empty cell = ramp 0 AND color 0; ramp 0 with a color = the '.' glyph
2825
+ const ci = parseInt(ASCII_LOGO_GRID.slice(i, i + 2), 16);
2826
+ const pi = parseInt(ASCII_LOGO_GRID.slice(i + 2, i + 4), 16);
2827
+ if (ci === 0 && pi === 0) continue;
2828
+ screen.put(cx - Math.floor((ASCII_LOGO_COLS - LOGO_COLS) / 2) + c, top + r,
2829
+ ASCII_LOGO_RAMP[ci], { fg: pi ? parseInt(ASCII_LOGO_PALETTE[pi - 1], 16) : T.FAINT, attrs: 1 });
2830
+ }
2831
+ }
2832
+ return ASCII_LOGO_ROWS;
2833
+ }
2834
+ const custom = this.app.logoMode === "custom";
2835
+ let pal, grid, quadrant = !custom;
2836
+ if (custom) {
2811
2837
  if (!this.app.logoData) {
2812
2838
  if (this.app.logoPath) {
2813
2839
  try { this.app.logoData = this.app.loadCustomLogo(this.app.logoPath); }
@@ -2816,33 +2842,76 @@ export class ChatView extends Widget {
2816
2842
  if (!this.app.logoData) return false;
2817
2843
  }
2818
2844
  pal = this.app.logoData.palette; grid = this.app.logoData.grid;
2845
+ quadrant = this.app.logoData.quadrant !== false;
2819
2846
  } else {
2820
2847
  pal = LOGO_PALETTE; grid = LOGO_GRID;
2821
2848
  }
2822
- for (let r = 0; r < LOGO_ROWS && top + r < hr; r++) {
2823
- for (let c = 0; c < LOGO_COLS; c++) {
2824
- let topIdx, botIdx;
2825
- if (this.app.logoMode === "custom") {
2826
- const cell = grid[r][c];
2827
- topIdx = cell[0]; botIdx = cell[1];
2849
+ // mask glyph: bits tl/tr/bl/br (quadrant blocks render width 1).
2850
+ const GL = [" ", "▘", "▝", "▀", "▖", "▌", "▞", "▛", "▗", "▚", "▐", "▜", "▄", "▙", "▟", "█"];
2851
+ const rows = custom ? grid.length : LOGO_ROWS;
2852
+ const cols = custom ? (grid[0]?.length ?? LOGO_COLS) : LOGO_COLS;
2853
+ for (let r = 0; r < rows && top + r < hr; r++) {
2854
+ for (let c = 0; c < cols; c++) {
2855
+ let kind, mask, fi, bi;
2856
+ if (quadrant) {
2857
+ const cell = custom ? grid[r]?.[c] : null;
2858
+ if (cell) { kind = cell.length === 4 ? cell[0] : 0; mask = cell.length === 4 ? cell[1] : cell[0]; fi = cell.length === 4 ? cell[2] : cell[1]; bi = cell.length === 4 ? cell[3] : cell[2]; }
2859
+ else {
2860
+ // each cell = 4 bytes (type,mask,fg,bg) = 8 hex chars; type 0 =
2861
+ // quadrant (low nibble character quads, high nibble 2nd-color
2862
+ // quads), type 1 = braille (mouth region, 2x4 dots)
2863
+ const i = (r * LOGO_COLS + c) * 8;
2864
+ kind = parseInt(grid.slice(i, i + 2), 16);
2865
+ mask = parseInt(grid.slice(i + 2, i + 4), 16);
2866
+ fi = parseInt(grid.slice(i + 4, i + 6), 16);
2867
+ bi = parseInt(grid.slice(i + 6, i + 8), 16);
2868
+ }
2869
+ if (kind === 0 && mask === 0 && fi === 0 && bi === 0) continue;
2870
+ const fgc = fi ? (custom ? pal[fi] : parseInt(pal[fi - 1], 16)) : null;
2871
+ const bgc = bi ? (custom ? pal[bi] : parseInt(pal[bi - 1], 16)) : null;
2872
+ if (kind === 1) {
2873
+ // braille mouth cell: 8 dots (2 cols x 4 rows), fg/bg pair
2874
+ screen.put(cx + c, top + r, String.fromCharCode(0x2800 | (mask & 0xff)),
2875
+ { fg: fgc ?? T.BG, bg: bgc ?? T.BG });
2876
+ continue;
2877
+ }
2878
+ if (kind === 2) {
2879
+ // half-block: one cell = one full-width pixel pair (top/bottom)
2880
+ screen.put(cx + c, top + r, "▀", { fg: fgc ?? T.BG, bg: bgc ?? T.BG });
2881
+ continue;
2882
+ }
2883
+ const fm = mask & 0x0f, cm = (mask >> 4) & 0x0f;
2884
+ if (fm === 0) continue;
2885
+ if (fm === 0x0f && bgc != null) {
2886
+ // full cell: second-color quads render as glyph bg → true 2×2.
2887
+ const glyph = GL[(~cm) & 0x0f];
2888
+ screen.put(cx + c, top + r, glyph, { fg: fgc ?? T.BG, bg: bgc });
2889
+ } else {
2890
+ // partial: character quads in fg over the transparent background.
2891
+ screen.put(cx + c, top + r, GL[fm], { fg: fgc ?? T.BG, bg: T.BG });
2892
+ }
2828
2893
  } else {
2829
- const i = (r * LOGO_COLS + c) * 4;
2830
- topIdx = parseInt(grid.slice(i, i + 2), 16);
2831
- botIdx = parseInt(grid.slice(i + 2, i + 4), 16);
2894
+ const cell = custom ? grid[r][c] : null;
2895
+ let topIdx, botIdx;
2896
+ if (cell) { topIdx = cell[0]; botIdx = cell[1]; }
2897
+ else {
2898
+ const i = (r * LOGO_COLS + c) * 4;
2899
+ topIdx = parseInt(grid.slice(i, i + 2), 16);
2900
+ botIdx = parseInt(grid.slice(i + 2, i + 4), 16);
2901
+ }
2902
+ const emptyT = custom ? topIdx < 0 : topIdx === 0;
2903
+ const emptyB = custom ? botIdx < 0 : botIdx === 0;
2904
+ if (emptyT && emptyB) continue;
2905
+ const pal32 = (pi) => (custom ? pal[pi] : parseInt(pal[pi], 16));
2906
+ const tcol = emptyT ? null : pal32(custom ? topIdx : topIdx - 1);
2907
+ const bcol = emptyB ? null : pal32(custom ? botIdx : botIdx - 1);
2908
+ const fgc = tcol ?? bcol ?? T.BG;
2909
+ const bgc = bcol ?? T.BG;
2910
+ screen.put(cx + c, top + r, tcol != null ? "▀" : "▄", { fg: fgc, bg: bgc });
2832
2911
  }
2833
- const custom = this.app.logoMode === "custom";
2834
- const emptyT = custom ? topIdx < 0 : topIdx === 0;
2835
- const emptyB = custom ? botIdx < 0 : botIdx === 0;
2836
- if (emptyT && emptyB) continue;
2837
- const pal32 = (pi) => (custom ? pal[pi] : parseInt(pal[pi], 16));
2838
- const tcol = emptyT ? null : pal32(custom ? topIdx : topIdx - 1);
2839
- const bcol = emptyB ? null : pal32(custom ? botIdx : botIdx - 1);
2840
- const fgc = tcol ?? bcol ?? T.BG;
2841
- const bgc = bcol ?? T.BG;
2842
- screen.put(cx + c, top + r, tcol != null ? "▀" : "▄", { fg: fgc, bg: bgc });
2843
2912
  }
2844
2913
  }
2845
- return true;
2914
+ return LOGO_ROWS;
2846
2915
  }
2847
2916
 
2848
2917
  /** Bottom update notices: only shown when a product has a pending update
@@ -3554,7 +3623,8 @@ export class App {
3554
3623
  // Welcome logo mode: "preset" (bundled mascot), "custom" (JSON file) or
3555
3624
  // "none" (title only). Persisted in the tui config file.
3556
3625
  const logoCfg = loadTuiConfig().logo ?? {};
3557
- this.logoMode = ["preset", "custom", "none"].includes(logoCfg.mode) ? logoCfg.mode : "preset";
3626
+ this.logoMode = ["preset", "custom", "none"].includes(logoCfg.mode) ? logoCfg.mode : "none"; // mascot off by default; Ctrl+R re-enables
3627
+ this.logoStyle = logoCfg.style === "ascii" ? "ascii" : "pixel"; // pixel = quadrant blocks
3558
3628
  this.logoPath = logoCfg.path ?? null;
3559
3629
  this.logoData = null; // decoded custom logo: { palette, grid }
3560
3630
  this.logoLoadError = null;
@@ -4465,13 +4535,19 @@ export class App {
4465
4535
  { label: `${mark("preset")}预设`, hint: "DeepSeek 吉祥物(内置)", action: "preset" },
4466
4536
  { label: `${mark("custom")}自定义…`, hint: `选择 JSON 文件(当前: ${this.logoPath ?? "—"})`, action: "custom" },
4467
4537
  { label: `${mark("none")}关闭`, hint: "不显示 logo,仅保留品牌字标", action: "none" },
4538
+ { label: `${this.logoStyle === "ascii" ? "◉" : "○"} 字符样式`, hint: this.logoStyle === "ascii" ? "当前: ASCII 52×24(回车切回像素)" : "当前: 像素 80×38(回车切换为字符画)", action: "style" },
4468
4539
  ];
4469
4540
  const pick = async (action) => {
4470
4541
  if (action === "preset" || action === "none") {
4471
4542
  this.logoMode = action;
4472
4543
  this.logoData = null;
4473
4544
  this.logoLoadError = null;
4474
- saveTuiConfig({ logo: { mode: action, path: action === "preset" ? null : this.logoPath } });
4545
+ saveTuiConfig({ logo: { mode: action, path: action === "preset" ? null : this.logoPath, style: this.logoStyle } });
4546
+ this.closeOverlay();
4547
+ this.redraw();
4548
+ } else if (action === "style") {
4549
+ this.logoStyle = this.logoStyle === "ascii" ? "pixel" : "ascii";
4550
+ saveTuiConfig({ logo: { mode: this.logoMode, path: this.logoPath, style: this.logoStyle } });
4475
4551
  this.closeOverlay();
4476
4552
  this.redraw();
4477
4553
  } else if (action === "custom") {
@@ -4505,10 +4581,12 @@ export class App {
4505
4581
  loadCustomLogo(path) {
4506
4582
  const data = JSON.parse(readFileSync(path, "utf8"));
4507
4583
  const pal = data?.palette, grid = data?.grid;
4508
- if (!Array.isArray(pal) || !Array.isArray(grid) || grid.length !== LOGO_ROWS
4509
- || grid.some((row) => !Array.isArray(row) || row.length !== LOGO_COLS)
4584
+ const rows = grid?.length, cols = grid?.[0]?.length;
4585
+ if (!Array.isArray(pal) || !Array.isArray(grid) || (rows !== LOGO_ROWS && rows !== 19)
4586
+ || grid.some((row) => !Array.isArray(row) || row.length !== cols)
4587
+ || (cols !== LOGO_COLS && cols !== 40)
4510
4588
  || pal.length < 1 || pal.length > 65535) {
4511
- throw new Error("需要 { palette:[…], grid:[[top,bottom]...] } 且为 40×19");
4589
+ throw new Error(`需要 { palette:[…], grid:[...] } 且为 58×27 或 40×19(得到 ${rows ?? "?"}×${cols ?? "?"})`);
4512
4590
  }
4513
4591
  const norm = pal.map((c) => {
4514
4592
  if (Array.isArray(c) && c.length >= 3) return ((c[0] << 16) | (c[1] << 8) | c[2]) >>> 0;
@@ -4516,15 +4594,25 @@ export class App {
4516
4594
  return NaN;
4517
4595
  });
4518
4596
  if (norm.some((c) => Number.isNaN(c) || c < 0 || c > 0xffffff)) throw new Error("调色板条目必须是 [r,g,b] 或 #rrggbb");
4597
+ // Cells: [top, bottom] = half-block (legacy) or [mask, fg, bg] = quadrant.
4598
+ const fmt = grid[0][0]?.length === 3 ? "quadrant" : "half";
4519
4599
  for (const row of grid) {
4520
4600
  for (const cell of row) {
4521
- if (!Array.isArray(cell) || cell.length !== 2 || cell[0] < -1 || cell[1] < -1
4522
- || cell[0] >= norm.length || cell[1] >= norm.length || !Number.isInteger(cell[0]) || !Number.isInteger(cell[1])) {
4523
- throw new Error("grid 必须是 [[top,bottom]...] 且索引在调色板范围内");
4601
+ if (!Array.isArray(cell) || !Number.isInteger(cell[0])) throw new Error("grid 单元必须是数组");
4602
+ if (fmt === "quadrant") {
4603
+ if (cell.length !== 3 || cell[0] < 0 || cell[0] > 0xff || cell[1] < -1 || cell[2] < -1
4604
+ || cell[1] >= norm.length || cell[2] >= norm.length) {
4605
+ throw new Error("quadrant 单元必须是 [maskByte, fg, bg] 且索引在调色板范围内(mask 低四位=角色象限,高四位=第二色象限)");
4606
+ }
4607
+ } else {
4608
+ if (cell.length !== 2 || cell[0] < -1 || cell[1] < -1
4609
+ || cell[0] >= norm.length || cell[1] >= norm.length || !Number.isInteger(cell[1])) {
4610
+ throw new Error("half 单元必须是 [top, bottom] 且索引在调色板范围内");
4611
+ }
4524
4612
  }
4525
4613
  }
4526
4614
  }
4527
- return { palette: norm, grid };
4615
+ return { palette: norm, grid, quadrant: fmt === "quadrant" };
4528
4616
  }
4529
4617
 
4530
4618
  #applyCustomLogo(path) {
@@ -4533,7 +4621,7 @@ export class App {
4533
4621
  this.logoMode = "custom";
4534
4622
  this.logoPath = path;
4535
4623
  this.logoLoadError = null;
4536
- saveTuiConfig({ logo: { mode: "custom", path } });
4624
+ saveTuiConfig({ logo: { mode: "custom", path, style: this.logoStyle } });
4537
4625
  this.closeOverlay();
4538
4626
  this.redraw();
4539
4627
  this.toast("自定义 logo 已应用");
@@ -5835,9 +5923,10 @@ export class App {
5835
5923
  * ~3.5s) while a tall blank welcome is on screen. Separated from the tick
5836
5924
  * loop so tests can drive it with fake timestamps. */
5837
5925
  tickWelcomeShimmer(now) {
5926
+ const logoH = this.logoStyle === "ascii" ? ASCII_LOGO_ROWS : this.logoMode === "none" ? 0 : LOGO_ROWS;
5838
5927
  const welcomeUp = this.chat.nodes.length === 0
5839
5928
  && (this.sessions.find((s) => s.sessionId === this.currentSession)?.blank ?? false)
5840
- && this.chat.view.h >= 32 && this.chat.view.w >= 62;
5929
+ && this.chat.view.h >= logoH + 13 && this.chat.view.w >= 62;
5841
5930
  if (!welcomeUp) {
5842
5931
  if (this.brandSweep0 >= 0 || this.brandShimmer >= 0) { this.brandSweep0 = -1; this.brandShimmer = -1; }
5843
5932
  return;