From 4e70741f5530f29fb1d85703a894522985d65ace Mon Sep 17 00:00:00 2001 From: JoeAllen <2494136784@qq.com> Date: Mon, 28 Sep 2026 22:49:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Linear=20=E9=A3=8E=E6=A0=BC=20UI=20?= =?UTF-8?q?=E4=B8=BB=E9=A2=98=EF=BC=88=E8=BF=91=E9=BB=91/=E7=BA=AF?= =?UTF-8?q?=E7=99=BD=E6=89=81=E5=B9=B3=E8=B0=83=E8=89=B2=E6=9D=BF=E3=80=81?= =?UTF-8?q?=E9=9D=9B=E8=93=9D=E4=B8=BB=E8=89=B2=E3=80=8114px=20=E5=AF=86?= =?UTF-8?q?=E5=BA=A6=E4=B8=8E=206px=20=E5=9C=86=E8=A7=92=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- gpui-app/src/ui/app.rs | 25 +- gpui-app/src/ui/linear-theme.json | 423 ++++++++++++++++++++++++++++++ scripts/gen_linear_theme.py | 202 ++++++++++++++ 4 files changed, 650 insertions(+), 2 deletions(-) create mode 100644 gpui-app/src/ui/linear-theme.json create mode 100644 scripts/gen_linear_theme.py diff --git a/README.md b/README.md index cf6a52a..eff0569 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ - **连接测试** — 表单内握手测试(超时可配置),保存前验证连通性 - **日志面板** — 连接级事件日志(CONNACK/SUBACK/PUBACK/错误/重连等),按日落盘 `mqttx-YYYY-MM-DD.log`,设置内一键打开日志目录 - **快捷键** — `Ctrl+N` 新建连接、`Ctrl+,` 设置、`Ctrl+Shift+E` 导出、`Ctrl+Shift+I` 导入、`Ctrl+Enter` 发送消息 -- **主题** — 浅色 / 深色 / 跟随系统 +- **主题** — Linear 风格配色(近黑/纯白扁平底 + 靛蓝 #5e6ad2 主色,14px 紧凑排版、6px 圆角),浅色 / 深色 / 跟随系统 - **设置** — 自动检查更新开关、消息缓存条数、时间戳毫秒、数据目录展示与打开、关于页(版本 / 许可) - **本地持久化** — 连接、订阅、预设、变量、设置以 JSON 存于系统数据目录 diff --git a/gpui-app/src/ui/app.rs b/gpui-app/src/ui/app.rs index aec82e8..496fbcd 100644 --- a/gpui-app/src/ui/app.rs +++ b/gpui-app/src/ui/app.rs @@ -11,7 +11,7 @@ use gpui_kit::component::menu::{DropdownMenu as _, PopupMenuItem}; use gpui_kit::component::tab::{Tab, TabBar}; use gpui_kit::component::{ h_flex, notification::Notification, v_flex, ActiveTheme as _, Selectable as _, Sizable as _, - StyledExt as _, Theme, ThemeMode, TitleBar, WindowExt as _, + StyledExt as _, Theme, ThemeMode, ThemeRegistry, TitleBar, WindowExt as _, }; use gpui_kit::prelude::FluentBuilder as _; use gpui_kit::{ @@ -816,7 +816,14 @@ impl MqttXApp { /// 应用主题偏好。 /// /// 「跟随系统」读取 gpui 的窗口外观(`App::window_appearance`);系统深浅色变化由 +/// Linear 风格主题(内嵌 JSON,模式与 gpui-component 默认主题同构)。 +const LINEAR_THEME: &str = include_str!("linear-theme.json"); + /// `MqttXApp::new` 里注册的 `Window::observe_window_appearance` 监听并重应用本函数。 +/// +/// 流程:先 `change` 确保 Theme 全局已建立(注册表 observer 依赖它)→ 注册 +/// Linear Light/Dark(重名跳过,幂等)→ 把当前明暗对指向 Linear → 再 `change` +/// 应用其颜色/字号/圆角。 pub fn apply_theme(pref: ThemeModePref, cx: &mut App) { let mode = match pref { ThemeModePref::Light => ThemeMode::Light, @@ -824,6 +831,22 @@ pub fn apply_theme(pref: ThemeModePref, cx: &mut App) { ThemeModePref::System => ThemeMode::from(cx.window_appearance()), }; Theme::change(mode, None, cx); + if let Err(e) = ThemeRegistry::global_mut(cx).load_themes_from_str(LINEAR_THEME) { + eprintln!("加载 Linear 主题失败,回退默认主题: {e}"); + } + { + let reg = ThemeRegistry::global(cx); + let light = reg.themes().get("Linear Light").cloned(); + let dark = reg.themes().get("Linear Dark").cloned(); + let theme = Theme::global_mut(cx); + if let Some(l) = light { + theme.light_theme = l; + } + if let Some(d) = dark { + theme.dark_theme = d; + } + } + Theme::change(mode, None, cx); } // ─── 渲染 ──────────────────────────────────────────────────────────────────── diff --git a/gpui-app/src/ui/linear-theme.json b/gpui-app/src/ui/linear-theme.json new file mode 100644 index 0000000..f5b6966 --- /dev/null +++ b/gpui-app/src/ui/linear-theme.json @@ -0,0 +1,423 @@ +{ + "themes": [ + { + "name": "Linear Light", + "mode": "light", + "font_size": 14, + "radius": 6, + "radius_lg": 10, + "colors": { + "accent.background": "#eeeef0", + "accent.foreground": "#0d0e10", + "accordion.background": "#f7f7f8", + "background": "#ffffff", + "border": "#e5e5e7", + "group_box.background": "#ffffff", + "group_box.foreground": "#0d0e10", + "caret": "#0d0e10", + "chart.1": "#93c5fd", + "chart.2": "#3b82f6", + "chart.3": "#2563eb", + "chart.4": "#1d4ed8", + "chart.5": "#1e40af", + "chart.bullish": "green-600", + "chart.bearish": "red-600", + "danger.background": "#e5484d", + "danger.foreground": "#ffffff", + "description_list_label.foreground": "#6f7076", + "drag_border": "#5e6ad2", + "drop_target.background": "#5e6ad219", + "foreground": "#0d0e10", + "info.background": "#5e6ad2", + "info.foreground": "#ffffff", + "input.border": "#e5e5e7", + "link.foreground": "#0d0e10", + "link.active.foreground": "#3d3e44", + "link.hover.foreground": "#000000", + "list.background": "#ffffff", + "list.active.background": "#5e6ad233", + "list.active.border": "#5e6ad2", + "list.even.background": "#f1f1f266", + "list.head.background": "#f1f1f266", + "list.hover.background": "#f5f5f5", + "muted.background": "#f1f1f2", + "muted.foreground": "#6f7076", + "popover.background": "#ffffff", + "popover.foreground": "#0d0e10", + "primary.background": "#5e6ad2", + "primary.active.background": "#5661c4", + "primary.foreground": "#ffffff", + "primary.hover.background": "#6b76d4", + "progress_bar.background": "#5e6ad2", + "ring": "#5e6ad2", + "scrollbar.background": "#ffffff00", + "scrollbar.thumb.background": "#c9c9cee6", + "scrollbar.thumb.hover.background": "#b5b5bb", + "secondary.background": "#f7f7f8", + "secondary.active.background": "#e7e7e9", + "secondary.foreground": "#0d0e10", + "secondary.hover.background": "#eeeef0", + "selection.background": "#5e6ad2", + "sidebar.background": "#fbfbfb", + "sidebar.accent.background": "#eeeef0", + "sidebar.accent.foreground": "#0d0e10", + "sidebar.border": "#e5e5e7", + "sidebar.foreground": "#0d0e10", + "sidebar.primary.background": "#0d0e10", + "sidebar.primary.foreground": "#ffffff", + "skeleton.background": "#eeeef0", + "slider.bar.background": "#0d0e10", + "slider.thumb.background": "#ffffff", + "success.background": "#4cb782", + "success.foreground": "#ffffff", + "switch.background": "#d7d7db", + "tab.background": "#00000000", + "tab.active.background": "#ffffff", + "tab.active.foreground": "#0d0e10", + "tab_bar.background": "#ffffff", + "tab_bar.segmented.background": "#f1f1f2", + "tab.foreground": "#6f7076", + "table.background": "#ffffff", + "table.active.background": "#bfdbfe33", + "table.active.border": "#60a5fa", + "table.even.background": "#fafafa", + "table.head.background": "#fafafa", + "table.head.foreground": "#6f7076", + "table.hover.background": "#f5f5f5", + "table.row.border": "#ebebed", + "title_bar.background": "#ffffff", + "title_bar.border": "#e5e5e7", + "status_bar.background": "#ffffff", + "status_bar.border": "#e5e5e7", + "warning.background": "#f2a93b", + "warning.foreground": "#3a2504", + "overlay": "#00000033", + "window.border": "#e5e5e7", + "base.red": "red-600", + "base.red.light": "red-400", + "base.green": "green-600", + "base.green.light": "green-400", + "base.blue": "blue-600", + "base.blue.light": "blue-400", + "base.yellow": "yellow-600", + "base.yellow.light": "yellow-400", + "base.magenta": "purple-600", + "base.magenta.light": "purple-400", + "base.cyan": "cyan-600", + "base.cyan.light": "cyan-400", + "description_list_label.background": "#f7f7f8" + }, + "highlight": { + "editor.foreground": "#000000", + "editor.background": "#ffffff", + "editor.active_line.background": "#F5F5F5", + "editor.line_number": "#929292", + "editor.active_line_number": "#000000", + "editor.invisible": "#73737366", + "conflict": "#C5060B", + "created": "#1642FF", + "hidden": "#6D6D6D", + "hint": "#9e5dff", + "modified": "#9e7008", + "predictive": "#A4ABB6", + "warning": "#C99401", + "syntax": { + "attribute": { + "color": "#957931" + }, + "boolean": { + "color": "#C5060B" + }, + "comment": { + "color": "#007fff" + }, + "comment.doc": { + "color": "#007fff" + }, + "constant": { + "color": "#C5060B" + }, + "constructor": { + "color": "#0433ff" + }, + "embedded": { + "color": "#333333" + }, + "emphasis": { + "font_style": "italic" + }, + "emphasis.strong": { + "font_weight": 700 + }, + "function": { + "color": "#0000A2" + }, + "keyword": { + "color": "#0433ff" + }, + "link_text": { + "color": "#0000A2", + "font_style": "normal" + }, + "link_uri": { + "color": "#6A7293", + "font_style": "italic" + }, + "number": { + "color": "#0433ff" + }, + "string": { + "color": "#036A07" + }, + "string.escape": { + "color": "#036A07" + }, + "string.regex": { + "color": "#036A07" + }, + "string.special": { + "color": "#d21f07" + }, + "string.special.symbol": { + "color": "#d21f07" + }, + "tag": { + "color": "#0433ff" + }, + "text.literal": { + "color": "#6F42C1" + }, + "text.code.span": { + "color": "#6F42C1" + }, + "title": { + "color": "#0433FF" + }, + "type": { + "color": "#6f42c1" + }, + "property": { + "color": "#333333" + }, + "variable": { + "color": "#333333" + }, + "variable.special": { + "color": "#C5060B" + } + } + } + }, + { + "name": "Linear Dark", + "mode": "dark", + "font_size": 14, + "radius": 6, + "radius_lg": 10, + "colors": { + "accent.background": "#1c1d1f", + "accent.foreground": "#f7f8f8", + "accordion.background": "#101012", + "background": "#08090a", + "border": "#2b2c31", + "group_box.background": "#08090a", + "group_box.foreground": "#f7f8f8", + "caret": "#f7f8f8", + "chart.1": "#93c5fd", + "chart.2": "#3b82f6", + "chart.3": "#2563eb", + "chart.4": "#1d4ed8", + "chart.5": "#1e40af", + "chart.bullish": "green-600", + "chart.bearish": "red-600", + "danger.background": "#e5484d", + "danger.foreground": "#ffe9e9", + "description_list_label.background": "#101012", + "description_list_label.foreground": "#8a8f98", + "drag_border": "#5e6ad2", + "drop_target.background": "#5e6ad219", + "foreground": "#f7f8f8", + "info.background": "#5e6ad2", + "info.foreground": "#e0e3ff", + "input.border": "#2b2c31", + "link.foreground": "#f7f8f8", + "link.active.foreground": "#d5d7de", + "link.hover.foreground": "#ffffff", + "list.background": "#08090a", + "list.active.background": "#5e6ad233", + "list.active.border": "#5e6ad2", + "list.even.background": "#10101266", + "list.head.background": "#10101266", + "muted.background": "#1d1e22", + "muted.foreground": "#8a8f98", + "popover.background": "#101012", + "popover.foreground": "#f7f8f8", + "primary.background": "#5e6ad2", + "primary.active.background": "#5661c4", + "primary.foreground": "#ffffff", + "primary.hover.background": "#6e79d6", + "progress_bar.background": "#5e6ad2", + "ring": "#5e6ad2", + "scrollbar.background": "#08090a00", + "scrollbar.thumb.background": "#3a3b42e6", + "scrollbar.thumb.hover.background": "#4a4b54", + "secondary.background": "#101012", + "secondary.active.background": "#232429", + "secondary.foreground": "#f7f8f8", + "secondary.hover.background": "#1c1d1f", + "selection.background": "#5e6ad2", + "sidebar.background": "#08090a", + "sidebar.accent.background": "#1c1d1f", + "sidebar.accent.foreground": "#f7f8f8", + "sidebar.border": "#1f2024", + "sidebar.foreground": "#f7f8f8", + "sidebar.primary.background": "#f7f8f8", + "sidebar.primary.foreground": "#08090a", + "skeleton.background": "#1c1d1f", + "slider.bar.background": "#f7f8f8", + "slider.thumb.background": "#08090a", + "success.background": "#4cb782", + "success.foreground": "#0b3a26", + "switch.background": "#2b2c31", + "tab.background": "#00000000", + "tab.active.background": "#101012", + "tab.active.foreground": "#f7f8f8", + "tab_bar.background": "#08090a", + "tab_bar.segmented.background": "#101012", + "tab.foreground": "#8a8f98", + "table.background": "#08090a", + "table.head.foreground": "#8a8f98", + "table.row.border": "#1f2024b3", + "title_bar.background": "#08090a", + "title_bar.border": "#1f2024", + "status_bar.background": "#08090a", + "status_bar.border": "#1f2024", + "warning.background": "#f2a93b", + "warning.foreground": "#3a2504", + "overlay": "#00000066", + "window.border": "#1f2024", + "base.red": "red-400", + "base.red.light": "red-300", + "base.green": "green-400", + "base.green.light": "green-300", + "base.blue": "blue-400", + "base.blue.light": "blue-300", + "base.yellow": "yellow-400", + "base.yellow.light": "yellow-300", + "base.magenta": "purple-400", + "base.magenta.light": "purple-300", + "base.cyan": "cyan-400", + "base.cyan.light": "cyan-300" + }, + "highlight": { + "editor.foreground": "#CACCCA", + "editor.background": "#0a0a0a", + "editor.active_line.background": "#171717", + "editor.line_number": "#8F8F8F", + "editor.active_line_number": "#DDDDDD", + "editor.invisible": "#73737366", + "conflict": "#D2602D", + "created": "#3f72e2", + "created.background": "#0C4619", + "deleted.background": "#46190C", + "error.background": "#46190C", + "error.border": "#E44A4F", + "hidden": "#9E9E9E", + "hint": "#b283f8", + "hint.background": "#250c4b", + "hint.border": "#3f0891", + "info.background": "#0059D1", + "info.border": "#0059D1", + "modified": "#B0A878", + "modified.background": "#3A310E", + "predictive": "#5D5945", + "success.background": "#0C4619", + "warning.background": "#3A310E", + "warning.border": "#7B6508", + "syntax": { + "attribute": { + "color": "#7FAEF9" + }, + "boolean": { + "color": "#CC9E00" + }, + "comment": { + "color": "#9D9D9D" + }, + "comment.doc": { + "color": "#9D9D9D" + }, + "constant": { + "color": "#CC9E00" + }, + "constructor": { + "color": "#CBA6F7" + }, + "embedded": { + "color": "#CACCCA" + }, + "emphasis": { + "font_style": "italic" + }, + "emphasis.strong": { + "font_weight": 700 + }, + "function": { + "color": "#B3C5F3" + }, + "keyword": { + "color": "#87B1F6" + }, + "link_text": { + "color": "#419CFF", + "font_style": "normal" + }, + "link_uri": { + "color": "#7faef9", + "font_style": "italic" + }, + "number": { + "color": "#CC9E00" + }, + "string": { + "color": "#A3E09F" + }, + "string.escape": { + "color": "#68DC7C" + }, + "string.regex": { + "color": "#68DC7C" + }, + "string.special": { + "color": "#A3E09F" + }, + "string.special.symbol": { + "color": "#A3E09F" + }, + "tag": { + "color": "#419CFF" + }, + "text.literal": { + "color": "#A3E09F" + }, + "text.code.span": { + "color": "#A3E09F" + }, + "title": { + "color": "#CC9E00", + "font_weight": 600 + }, + "type": { + "color": "#CBA6F7" + }, + "property": { + "color": "#BCC4E0" + }, + "variable.special": { + "color": "#419CFF" + } + } + } + } + ] +} \ No newline at end of file diff --git a/scripts/gen_linear_theme.py b/scripts/gen_linear_theme.py new file mode 100644 index 0000000..62f9f5f --- /dev/null +++ b/scripts/gen_linear_theme.py @@ -0,0 +1,202 @@ +# -*- coding: utf-8 -*- +"""从 gpui-component 默认主题生成 Linear 风格主题 linear-theme.json。 +映射规则见各表;未列出的键(chart/base 等)保留默认值。""" +import json, sys, collections + +SRC = sys.argv[1] +DST = sys.argv[2] + +with open(SRC, "r", encoding="utf-8") as f: + base = json.load(f) + +# ── Linear Dark ──────────────────────────────────────────────────────────── +DARK = { + "background": "#08090a", + "foreground": "#f7f8f8", + "accordion.background": "#101012", + "border": "#2b2c31", + "group_box.background": "#08090a", + "group_box.foreground": "#f7f8f8", + "caret": "#f7f8f8", + "accent.background": "#1c1d1f", + "accent.foreground": "#f7f8f8", + "danger.background": "#e5484d", + "danger.foreground": "#ffe9e9", + "description_list_label.background": "#101012", + "description_list_label.foreground": "#8a8f98", + "drag_border": "#5e6ad2", + "drop_target.background": "#5e6ad219", + "input.border": "#2b2c31", + "link.foreground": "#f7f8f8", + "link.active.foreground": "#d5d7de", + "link.hover.foreground": "#ffffff", + "list.background": "#08090a", + "list.active.background": "#5e6ad233", + "list.active.border": "#5e6ad2", + "list.even.background": "#10101266", + "list.head.background": "#10101266", + "muted.background": "#1d1e22", + "muted.foreground": "#8a8f98", + "popover.background": "#101012", + "popover.foreground": "#f7f8f8", + "primary.background": "#5e6ad2", + "primary.hover.background": "#6e79d6", + "primary.active.background": "#5661c4", + "primary.foreground": "#ffffff", + "progress_bar.background": "#5e6ad2", + "ring": "#5e6ad2", + "scrollbar.background": "#08090a00", + "scrollbar.thumb.background": "#3a3b42e6", + "scrollbar.thumb.hover.background": "#4a4b54", + "secondary.background": "#101012", + "secondary.hover.background": "#1c1d1f", + "secondary.active.background": "#232429", + "secondary.foreground": "#f7f8f8", + "selection.background": "#5e6ad2", + "sidebar.background": "#08090a", + "sidebar.accent.background": "#1c1d1f", + "sidebar.accent.foreground": "#f7f8f8", + "sidebar.border": "#1f2024", + "sidebar.foreground": "#f7f8f8", + "sidebar.primary.background": "#f7f8f8", + "sidebar.primary.foreground": "#08090a", + "skeleton.background": "#1c1d1f", + "slider.bar.background": "#f7f8f8", + "slider.thumb.background": "#08090a", + "success.background": "#4cb782", + "success.foreground": "#0b3a26", + "switch.background": "#2b2c31", + "tab.background": "#00000000", + "tab.active.background": "#101012", + "tab.active.foreground": "#f7f8f8", + "tab.foreground": "#8a8f98", + "tab_bar.background": "#08090a", + "tab_bar.segmented.background": "#101012", + "table.background": "#08090a", + "table.head.foreground": "#8a8f98", + "table.row.border": "#1f2024b3", + "title_bar.background": "#08090a", + "title_bar.border": "#1f2024", + "status_bar.background": "#08090a", + "status_bar.border": "#1f2024", + "warning.background": "#f2a93b", + "warning.foreground": "#3a2504", + "info.background": "#5e6ad2", + "info.foreground": "#e0e3ff", + "overlay": "#00000066", + "window.border": "#1f2024", +} + +# ── Linear Light ─────────────────────────────────────────────────────────── +LIGHT = { + "background": "#ffffff", + "foreground": "#0d0e10", + "accordion.background": "#f7f7f8", + "border": "#e5e5e7", + "group_box.background": "#ffffff", + "group_box.foreground": "#0d0e10", + "caret": "#0d0e10", + "accent.background": "#eeeef0", + "accent.foreground": "#0d0e10", + "danger.background": "#e5484d", + "danger.foreground": "#ffffff", + "description_list_label.background": "#f7f7f8", + "description_list_label.foreground": "#6f7076", + "drag_border": "#5e6ad2", + "drop_target.background": "#5e6ad219", + "input.border": "#e5e5e7", + "link.foreground": "#0d0e10", + "link.active.foreground": "#3d3e44", + "link.hover.foreground": "#000000", + "list.background": "#ffffff", + "list.active.background": "#5e6ad233", + "list.active.border": "#5e6ad2", + "list.even.background": "#f1f1f266", + "list.head.background": "#f1f1f266", + "muted.background": "#f1f1f2", + "muted.foreground": "#6f7076", + "popover.background": "#ffffff", + "popover.foreground": "#0d0e10", + "primary.background": "#5e6ad2", + "primary.hover.background": "#6b76d4", + "primary.active.background": "#5661c4", + "primary.foreground": "#ffffff", + "progress_bar.background": "#5e6ad2", + "ring": "#5e6ad2", + "scrollbar.background": "#ffffff00", + "scrollbar.thumb.background": "#c9c9cee6", + "scrollbar.thumb.hover.background": "#b5b5bb", + "secondary.background": "#f7f7f8", + "secondary.hover.background": "#eeeef0", + "secondary.active.background": "#e7e7e9", + "secondary.foreground": "#0d0e10", + "selection.background": "#5e6ad2", + "sidebar.background": "#fbfbfb", + "sidebar.accent.background": "#eeeef0", + "sidebar.accent.foreground": "#0d0e10", + "sidebar.border": "#e5e5e7", + "sidebar.foreground": "#0d0e10", + "sidebar.primary.background": "#0d0e10", + "sidebar.primary.foreground": "#ffffff", + "skeleton.background": "#eeeef0", + "slider.bar.background": "#0d0e10", + "slider.thumb.background": "#ffffff", + "success.background": "#4cb782", + "success.foreground": "#ffffff", + "switch.background": "#d7d7db", + "tab.background": "#00000000", + "tab.active.background": "#ffffff", + "tab.active.foreground": "#0d0e10", + "tab.foreground": "#6f7076", + "tab_bar.background": "#ffffff", + "tab_bar.segmented.background": "#f1f1f2", + "table.background": "#ffffff", + "table.head.foreground": "#6f7076", + "table.row.border": "#ebebed", + "title_bar.background": "#ffffff", + "title_bar.border": "#e5e5e7", + "status_bar.background": "#ffffff", + "status_bar.border": "#e5e5e7", + "warning.background": "#f2a93b", + "warning.foreground": "#3a2504", + "info.background": "#5e6ad2", + "info.foreground": "#ffffff", + "overlay": "#00000033", + "window.border": "#e5e5e7", +} + + +def build(theme, overrides, name): + colors = dict(theme.get("colors") or {}) + missing = [k for k in overrides if k not in colors] + if missing: + print(f"WARN {name}: 目标主题缺少键 {missing}", file=sys.stderr) + colors.update(overrides) + out = collections.OrderedDict() + # 键序跟随默认主题,新键追加 + for k in (theme.get("colors") or {}): + out[k] = colors[k] + for k in overrides: + if k not in out: + out[k] = overrides[k] + cfg = collections.OrderedDict() + cfg["name"] = name + cfg["mode"] = theme["mode"] + cfg["font_size"] = 14 + cfg["radius"] = 6 + cfg["radius_lg"] = 10 + cfg["colors"] = out + if theme.get("highlight"): + cfg["highlight"] = theme["highlight"] + return cfg + + +by_mode = {t["mode"]: t for t in base["themes"]} +themes = [ + build(by_mode["light"], LIGHT, "Linear Light"), + build(by_mode["dark"], DARK, "Linear Dark"), +] +doc = {"themes": themes} +with open(DST, "w", encoding="utf-8") as f: + json.dump(doc, f, ensure_ascii=False, indent=2) +print(f"OK -> {DST} ({len(themes)} themes)")