waybetter 0.6.0 → 0.7.0

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 (190) hide show
  1. package/README.md +1 -2
  2. package/bin/waybetter.mjs +2 -5
  3. package/cli/buildenv.mjs +1 -1
  4. package/cli/release.mjs +11 -18
  5. package/cli/releasecheck.mjs +15 -0
  6. package/cli/remote.mjs +4 -4
  7. package/cli/store.mjs +80 -28
  8. package/cli/strings.mjs +2 -0
  9. package/dist/runtime/stringPackHook.js +2 -2
  10. package/dist/runtime/stringPackHook.js.map +1 -1
  11. package/dist/runtime/stringPackTranslate.d.ts +3 -1
  12. package/dist/runtime/stringPackTranslate.js +6 -3
  13. package/dist/runtime/stringPackTranslate.js.map +1 -1
  14. package/dist/runtime/stringPacks.d.ts +53 -5
  15. package/dist/runtime/stringPacks.js +161 -25
  16. package/dist/runtime/stringPacks.js.map +1 -1
  17. package/dist/runtime/themeBar.d.ts +28 -0
  18. package/dist/runtime/themeBar.js +82 -0
  19. package/dist/runtime/themeBar.js.map +1 -0
  20. package/dist/runtime/themeComponents.js +16 -3
  21. package/dist/runtime/themeComponents.js.map +1 -1
  22. package/dist/runtime/themeDecor.js +3 -1
  23. package/dist/runtime/themeDecor.js.map +1 -1
  24. package/dist/runtime/themeDerive.d.ts +69 -0
  25. package/dist/runtime/themeDerive.js +117 -0
  26. package/dist/runtime/themeDerive.js.map +1 -0
  27. package/dist/runtime/themeDoctor.d.ts +44 -3
  28. package/dist/runtime/themeDoctor.js +86 -25
  29. package/dist/runtime/themeDoctor.js.map +1 -1
  30. package/dist/runtime/themeGuards.d.ts +60 -0
  31. package/dist/runtime/themeGuards.js +235 -0
  32. package/dist/runtime/themeGuards.js.map +1 -0
  33. package/dist/runtime/themeIcons.d.ts +1 -1
  34. package/dist/runtime/themeIcons.js +13 -5
  35. package/dist/runtime/themeIcons.js.map +1 -1
  36. package/dist/runtime/themeInk.d.ts +37 -0
  37. package/dist/runtime/themeInk.js +111 -0
  38. package/dist/runtime/themeInk.js.map +1 -0
  39. package/dist/runtime/themeInvariants.d.ts +39 -0
  40. package/dist/runtime/themeInvariants.js +273 -0
  41. package/dist/runtime/themeInvariants.js.map +1 -0
  42. package/dist/runtime/themeRuntime.d.ts +15 -1
  43. package/dist/runtime/themeRuntime.js +78 -5
  44. package/dist/runtime/themeRuntime.js.map +1 -1
  45. package/dist/runtime/themeSpec.d.ts +5 -1
  46. package/dist/runtime/themeSpec.js +9 -13
  47. package/dist/runtime/themeSpec.js.map +1 -1
  48. package/dist/shell/InstallDialog.js +14 -8
  49. package/dist/shell/InstallDialog.js.map +1 -1
  50. package/dist/shell/StoreView.d.ts +12 -0
  51. package/dist/shell/StoreView.js +9 -4
  52. package/dist/shell/StoreView.js.map +1 -1
  53. package/dist/shell/frame/AppMenu.d.ts +26 -3
  54. package/dist/shell/frame/AppMenu.js +106 -27
  55. package/dist/shell/frame/AppMenu.js.map +1 -1
  56. package/dist/shell/frame/FindBar.js +1 -1
  57. package/dist/shell/frame/FindBar.js.map +1 -1
  58. package/dist/shell/frame/Shell.d.ts +1 -0
  59. package/dist/shell/frame/Shell.js +10 -2
  60. package/dist/shell/frame/Shell.js.map +1 -1
  61. package/dist/shell/frame/Titlebar.d.ts +2 -1
  62. package/dist/shell/frame/Titlebar.js +5 -3
  63. package/dist/shell/frame/Titlebar.js.map +1 -1
  64. package/dist/shell/frame/WebPanel.js +4 -1
  65. package/dist/shell/frame/WebPanel.js.map +1 -1
  66. package/dist/shell/frame/accountSwitcher.css +10 -10
  67. package/dist/shell/frame/appMenu.css +19 -13
  68. package/dist/shell/frame/boot.js +2 -0
  69. package/dist/shell/frame/boot.js.map +1 -1
  70. package/dist/shell/frame/chromeTheme.d.ts +19 -1
  71. package/dist/shell/frame/chromeTheme.js +51 -14
  72. package/dist/shell/frame/chromeTheme.js.map +1 -1
  73. package/dist/shell/frame/deck.css +30 -30
  74. package/dist/shell/frame/edges.css +159 -0
  75. package/dist/shell/frame/index.d.ts +2 -2
  76. package/dist/shell/frame/index.js +1 -1
  77. package/dist/shell/frame/index.js.map +1 -1
  78. package/dist/shell/frame/mediaModel.d.ts +13 -0
  79. package/dist/shell/frame/mediaModel.js +14 -0
  80. package/dist/shell/frame/mediaModel.js.map +1 -1
  81. package/dist/shell/frame/menuMessages.en.json +43 -1
  82. package/dist/shell/frame/menuModel.d.ts +42 -0
  83. package/dist/shell/frame/menuModel.js +8 -0
  84. package/dist/shell/frame/menuModel.js.map +1 -1
  85. package/dist/shell/frame/nativeMenu.d.ts +5 -0
  86. package/dist/shell/frame/nativeMenu.js +178 -0
  87. package/dist/shell/frame/nativeMenu.js.map +1 -0
  88. package/dist/shell/frame/nativeMenuModel.d.ts +102 -0
  89. package/dist/shell/frame/nativeMenuModel.js +186 -0
  90. package/dist/shell/frame/nativeMenuModel.js.map +1 -0
  91. package/dist/shell/frame/paletteModel.js +3 -1
  92. package/dist/shell/frame/paletteModel.js.map +1 -1
  93. package/dist/shell/frame/paneTheme.css +1 -1
  94. package/dist/shell/frame/reportModel.d.ts +1 -1
  95. package/dist/shell/frame/reportModel.js +1 -1
  96. package/dist/shell/frame/reportModel.js.map +1 -1
  97. package/dist/shell/frame/shell.css +56 -51
  98. package/dist/shell/frame/state.d.ts +2 -0
  99. package/dist/shell/frame/state.js +3 -1
  100. package/dist/shell/frame/state.js.map +1 -1
  101. package/dist/shell/frame/themeStore.js +21 -6
  102. package/dist/shell/frame/themeStore.js.map +1 -1
  103. package/dist/shell/frame/workspaces.css +12 -12
  104. package/dist/shell/installController.d.ts +16 -1
  105. package/dist/shell/installController.js +31 -5
  106. package/dist/shell/installController.js.map +1 -1
  107. package/dist/shell/panel/Appearance.js +12 -3
  108. package/dist/shell/panel/Appearance.js.map +1 -1
  109. package/dist/shell/panel/Settings.d.ts +1 -1
  110. package/dist/shell/panel/Settings.js +4 -3
  111. package/dist/shell/panel/Settings.js.map +1 -1
  112. package/dist/shell/panel/StoreSection.d.ts +2 -1
  113. package/dist/shell/panel/StoreSection.js +31 -1
  114. package/dist/shell/panel/StoreSection.js.map +1 -1
  115. package/dist/shell/panel/StringPacks.d.ts +12 -3
  116. package/dist/shell/panel/StringPacks.js +33 -8
  117. package/dist/shell/panel/StringPacks.js.map +1 -1
  118. package/dist/shell/panel/backup.css +1 -1
  119. package/dist/shell/panel/builder.css +48 -40
  120. package/dist/shell/panel/make.css +8 -7
  121. package/dist/shell/panel/panel.css +401 -401
  122. package/dist/shell/panel/panelMessages.d.ts +41 -1
  123. package/dist/shell/panel/panelMessages.js +13 -1
  124. package/dist/shell/panel/panelMessages.js.map +1 -1
  125. package/dist/shell/panel/plugins.css +5 -5
  126. package/dist/shell/panel/strings.css +1 -1
  127. package/dist/shell/store.css +34 -14
  128. package/dist/shell/storeMessages.d.ts +24 -0
  129. package/dist/shell/storeMessages.js +24 -0
  130. package/dist/shell/storeMessages.js.map +1 -1
  131. package/dist/shell/storeModel.d.ts +31 -7
  132. package/dist/shell/storeModel.js +15 -2
  133. package/dist/shell/storeModel.js.map +1 -1
  134. package/dist/shell/stringPacksModel.js +7 -2
  135. package/dist/shell/stringPacksModel.js.map +1 -1
  136. package/icons.schema.json +9 -0
  137. package/locales/ar.json +109 -2
  138. package/locales/bg.json +109 -2
  139. package/locales/bn.json +109 -2
  140. package/locales/ca.json +109 -2
  141. package/locales/cs.json +109 -2
  142. package/locales/da.json +109 -2
  143. package/locales/de.json +109 -2
  144. package/locales/el.json +109 -2
  145. package/locales/en.json +108 -2
  146. package/locales/es.json +109 -2
  147. package/locales/eu.json +109 -2
  148. package/locales/fa.json +109 -2
  149. package/locales/fi.json +109 -2
  150. package/locales/fil.json +109 -2
  151. package/locales/fr.json +109 -2
  152. package/locales/ga.json +109 -2
  153. package/locales/gl.json +109 -2
  154. package/locales/gu.json +109 -2
  155. package/locales/ha.json +109 -2
  156. package/locales/he.json +109 -2
  157. package/locales/hi.json +109 -2
  158. package/locales/hr.json +109 -2
  159. package/locales/hu.json +110 -2
  160. package/locales/id.json +109 -2
  161. package/locales/ig.json +109 -2
  162. package/locales/it.json +109 -2
  163. package/locales/ja.json +109 -2
  164. package/locales/kn.json +109 -2
  165. package/locales/ko.json +109 -2
  166. package/locales/mr.json +109 -2
  167. package/locales/ms.json +109 -2
  168. package/locales/nb.json +109 -2
  169. package/locales/nl.json +109 -2
  170. package/locales/pl.json +109 -2
  171. package/locales/pt.json +109 -2
  172. package/locales/ro.json +109 -2
  173. package/locales/ru.json +109 -2
  174. package/locales/sk.json +109 -2
  175. package/locales/sr.json +109 -2
  176. package/locales/sv.json +109 -2
  177. package/locales/ta.json +109 -2
  178. package/locales/th.json +109 -2
  179. package/locales/tr.json +109 -2
  180. package/locales/uk.json +109 -2
  181. package/locales/ur.json +109 -2
  182. package/locales/vi.json +109 -2
  183. package/locales/yo.json +109 -2
  184. package/locales/zh-Hans.json +109 -2
  185. package/locales/zh-Hant.json +109 -2
  186. package/package.json +2 -3
  187. package/store-catalog.schema.json +18 -5
  188. package/theme.schema.json +9 -0
  189. package/windows/installer.nsi +22 -2
  190. package/cli/privacy.mjs +0 -768
@@ -3,6 +3,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  // app's built-in and own themes, and removal of installed items. Native code verifies the catalog's
4
4
  // Ed25519 signature, keeps the app's items, then checks each download's sha256. Installing always
5
5
  // goes through the shell's install screen (InstallDialog), never straight from here.
6
+ import { areaFlag } from "../../runtime/featureTable.js";
6
7
  import { invoke } from "../frame/bridge.js";
7
8
  import { useT } from "../frame/hooks.js";
8
9
  import { relativeTime } from "../frame/i18n.js";
@@ -10,12 +11,28 @@ import { site } from "../frame/site.js";
10
11
  import { StoreView } from "../StoreView.js";
11
12
  import '../store.css';
12
13
  import { Badge, Button, EmptyState, SearchField, Segmented, useSettingsQuery } from "./controls.js";
14
+ import { featureTable, flagsChanged, useFlags } from "./PluginFeatures.js";
13
15
  import { panelSite, panelThemes } from "./site.js";
14
16
  import { useThemeState } from "./themes.js";
15
17
  import './plugins.css';
16
18
  const CONTROLS = { Button, SearchField, Segmented, EmptyState, Badge };
17
19
  const RELOAD_ON = ['bt-plugins-changed', 'bt-store-changed', 'bt-strings-changed'];
20
+ /** The Store with the app's area plugins as switches for its built-in items (an app without a feature table has none). */
18
21
  export function StoreSection({ notify }) {
22
+ const table = featureTable();
23
+ return table ? _jsx(WithAreas, { table: table, notify: notify }) : _jsx(Store, { notify: notify });
24
+ }
25
+ function WithAreas({ table, notify }) {
26
+ const [flags, choose] = useFlags(table, notify);
27
+ const areas = {
28
+ has: id => table.areas.some(area => area.id === id),
29
+ on: id => flags?.[areaFlag(id)] ?? false,
30
+ set: async (id, on) => { if (await choose({ [areaFlag(id)]: on }))
31
+ flagsChanged(); },
32
+ };
33
+ return _jsx(Store, { notify: notify, areas: flags ? areas : undefined });
34
+ }
35
+ function Store({ notify, areas }) {
19
36
  const t = useT();
20
37
  const { store, catalog } = panelThemes();
21
38
  const themes = useThemeState();
@@ -23,7 +40,20 @@ export function StoreSection({ notify }) {
23
40
  if (settingsQuery)
24
41
  return null;
25
42
  const userThemes = themes.choice.themes.map(theme => theme.id);
43
+ const userIcons = (themes.choice.packs ?? []).map(pack => pack.id);
26
44
  async function remove(item, view) {
45
+ if (item.kind === 'builtin')
46
+ return false;
47
+ if (item.kind === 'icons') {
48
+ const found = Object.entries(view.records.items).find(([key, value]) => key.startsWith('icons:') && value.storeId === item.id && userIcons.includes(key.slice(6)));
49
+ const id = found?.[0].slice(6);
50
+ if (!id)
51
+ return false;
52
+ const choice = themes.choice;
53
+ await store.setChoice({ ...choice, packs: (choice.packs ?? []).filter(pack => pack.id !== id), ...(choice.icons === id ? { icons: undefined } : {}) });
54
+ await invoke('install_forget', { kind: 'icons', name: id });
55
+ return true;
56
+ }
27
57
  if (item.kind === 'strings') {
28
58
  await invoke('strings_remove', { id: item.id });
29
59
  window.dispatchEvent(new CustomEvent('bt-strings-changed'));
@@ -43,6 +73,6 @@ export function StoreSection({ notify }) {
43
73
  await invoke('install_forget', { kind: 'theme', name: id });
44
74
  return true;
45
75
  }
46
- return _jsx(StoreView, { t: t, site: site().name, load: refresh => invoke('store_catalog', { refresh }), controller: panelSite().store.controller, builtinThemes: catalog.builtins.map(theme => theme.id), userThemes: userThemes, onRemove: remove, onUse: item => store.setChoice({ ...themes.choice, preset: item.id, overrides: {}, accent: null }), notify: notify, relativeTime: relativeTime, reloadOn: RELOAD_ON, controls: CONTROLS });
76
+ return _jsx(StoreView, { t: t, site: site().name, load: refresh => invoke('store_catalog', { refresh }), controller: panelSite().store.controller, builtinThemes: catalog.builtins.map(theme => theme.id), userThemes: userThemes, userIcons: userIcons, areas: areas, onRemove: remove, onUse: item => store.setChoice({ ...themes.choice, preset: item.id, overrides: {}, accent: null }), notify: notify, relativeTime: relativeTime, reloadOn: RELOAD_ON, controls: CONTROLS });
47
77
  }
48
78
  //# sourceMappingURL=StoreSection.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"StoreSection.js","sourceRoot":"","sources":["../../../../../../shell/src/panel/StoreSection.tsx"],"names":[],"mappings":"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","sourcesContent":["// The wayBetter Store section: the Store view (StoreView.tsx) with the panel's own controls, the\n// app's built-in and own themes, and removal of installed items. Native code verifies the catalog's\n// Ed25519 signature, keeps the app's items, then checks each download's sha256. Installing always\n// goes through the shell's install screen (InstallDialog), never straight from here.\nimport { invoke } from '../frame/bridge.ts';\nimport { useT } from '../frame/hooks.ts';\nimport { relativeTime } from '../frame/i18n.ts';\nimport { site } from '../frame/site.ts';\nimport type { StoreCatalogView, StoreItem } from '../storeModel.ts';\nimport { StoreView, type StoreControls } from '../StoreView.tsx';\nimport '../store.css';\nimport { Badge, Button, EmptyState, SearchField, Segmented, useSettingsQuery } from './controls.tsx';\nimport { panelSite, panelThemes, type Notify } from './site.ts';\nimport { useThemeState } from './themes.ts';\nimport './plugins.css';\n\nconst CONTROLS: Partial<StoreControls> = { Button, SearchField, Segmented, EmptyState, Badge };\nconst RELOAD_ON = ['bt-plugins-changed', 'bt-store-changed', 'bt-strings-changed'];\n\nexport function StoreSection({ notify }: { notify: Notify }) {\n const t = useT();\n const { store, catalog } = panelThemes();\n const themes = useThemeState();\n const settingsQuery = useSettingsQuery();\n if (settingsQuery) return null;\n const userThemes = themes.choice.themes.map(theme => theme.id);\n\n async function remove(item: StoreItem, view: StoreCatalogView): Promise<boolean> {\n if (item.kind === 'strings') {\n await invoke('strings_remove', { id: item.id });\n window.dispatchEvent(new CustomEvent('bt-strings-changed'));\n return true;\n }\n if (item.kind === 'plugin' && item.file) {\n await invoke('plugin_remove', { file: item.file });\n window.dispatchEvent(new CustomEvent('bt-plugins-changed'));\n return true;\n }\n const record = Object.entries(view.records.items).find(([key, value]) => key.startsWith('theme:') && value.storeId === item.id && userThemes.includes(key.slice(6)));\n const id = record?.[0].slice(6);\n if (!id) return false;\n const choice = themes.choice;\n await store.setChoice({ ...choice, themes: choice.themes.filter(theme => theme.id !== id), ...(choice.preset === id ? { preset: catalog.defaultChoice.preset, overrides: {}, accent: null } : {}) });\n await invoke('install_forget', { kind: 'theme', name: id });\n return true;\n }\n\n return <StoreView\n t={t}\n site={site().name}\n load={refresh => invoke<StoreCatalogView>('store_catalog', { refresh })}\n controller={panelSite().store!.controller}\n builtinThemes={catalog.builtins.map(theme => theme.id)}\n userThemes={userThemes}\n onRemove={remove}\n onUse={item => store.setChoice({ ...themes.choice, preset: item.id, overrides: {}, accent: null })}\n notify={notify}\n relativeTime={relativeTime}\n reloadOn={RELOAD_ON}\n controls={CONTROLS}\n />;\n}\n"]}
1
+ {"version":3,"file":"StoreSection.js","sourceRoot":"","sources":["../../../../../../shell/src/panel/StoreSection.tsx"],"names":[],"mappings":"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","sourcesContent":["// The wayBetter Store section: the Store view (StoreView.tsx) with the panel's own controls, the\n// app's built-in and own themes, and removal of installed items. Native code verifies the catalog's\n// Ed25519 signature, keeps the app's items, then checks each download's sha256. Installing always\n// goes through the shell's install screen (InstallDialog), never straight from here.\nimport { areaFlag, type FeatureTable } from '../../../runtime/src/featureTable.ts';\nimport { invoke } from '../frame/bridge.ts';\nimport { useT } from '../frame/hooks.ts';\nimport { relativeTime } from '../frame/i18n.ts';\nimport { site } from '../frame/site.ts';\nimport type { StoreCatalogView, StoreItem } from '../storeModel.ts';\nimport { StoreView, type StoreControls } from '../StoreView.tsx';\nimport '../store.css';\nimport { Badge, Button, EmptyState, SearchField, Segmented, useSettingsQuery } from './controls.tsx';\nimport { featureTable, flagsChanged, useFlags } from './PluginFeatures.tsx';\nimport { panelSite, panelThemes, type Notify } from './site.ts';\nimport { useThemeState } from './themes.ts';\nimport './plugins.css';\n\nconst CONTROLS: Partial<StoreControls> = { Button, SearchField, Segmented, EmptyState, Badge };\nconst RELOAD_ON = ['bt-plugins-changed', 'bt-store-changed', 'bt-strings-changed'];\n\ntype Areas = NonNullable<Parameters<typeof StoreView>[0]['areas']>;\n\n/** The Store with the app's area plugins as switches for its built-in items (an app without a feature table has none). */\nexport function StoreSection({ notify }: { notify: Notify }) {\n const table = featureTable();\n return table ? <WithAreas table={table} notify={notify}/> : <Store notify={notify}/>;\n}\n\nfunction WithAreas({ table, notify }: { table: FeatureTable; notify: Notify }) {\n const [flags, choose] = useFlags(table, notify);\n const areas: Areas = {\n has: id => table.areas.some(area => area.id === id),\n on: id => flags?.[areaFlag(id)] ?? false,\n set: async (id, on) => { if (await choose({ [areaFlag(id)]: on })) flagsChanged(); },\n };\n return <Store notify={notify} areas={flags ? areas : undefined}/>;\n}\n\nfunction Store({ notify, areas }: { notify: Notify; areas?: Areas }) {\n const t = useT();\n const { store, catalog } = panelThemes();\n const themes = useThemeState();\n const settingsQuery = useSettingsQuery();\n if (settingsQuery) return null;\n const userThemes = themes.choice.themes.map(theme => theme.id);\n const userIcons = (themes.choice.packs ?? []).map(pack => pack.id);\n\n async function remove(item: StoreItem, view: StoreCatalogView): Promise<boolean> {\n if (item.kind === 'builtin') return false;\n if (item.kind === 'icons') {\n const found = Object.entries(view.records.items).find(([key, value]) => key.startsWith('icons:') && value.storeId === item.id && userIcons.includes(key.slice(6)));\n const id = found?.[0].slice(6);\n if (!id) return false;\n const choice = themes.choice;\n await store.setChoice({ ...choice, packs: (choice.packs ?? []).filter(pack => pack.id !== id), ...(choice.icons === id ? { icons: undefined } : {}) });\n await invoke('install_forget', { kind: 'icons', name: id });\n return true;\n }\n if (item.kind === 'strings') {\n await invoke('strings_remove', { id: item.id });\n window.dispatchEvent(new CustomEvent('bt-strings-changed'));\n return true;\n }\n if (item.kind === 'plugin' && item.file) {\n await invoke('plugin_remove', { file: item.file });\n window.dispatchEvent(new CustomEvent('bt-plugins-changed'));\n return true;\n }\n const record = Object.entries(view.records.items).find(([key, value]) => key.startsWith('theme:') && value.storeId === item.id && userThemes.includes(key.slice(6)));\n const id = record?.[0].slice(6);\n if (!id) return false;\n const choice = themes.choice;\n await store.setChoice({ ...choice, themes: choice.themes.filter(theme => theme.id !== id), ...(choice.preset === id ? { preset: catalog.defaultChoice.preset, overrides: {}, accent: null } : {}) });\n await invoke('install_forget', { kind: 'theme', name: id });\n return true;\n }\n\n return <StoreView\n t={t}\n site={site().name}\n load={refresh => invoke<StoreCatalogView>('store_catalog', { refresh })}\n controller={panelSite().store!.controller}\n builtinThemes={catalog.builtins.map(theme => theme.id)}\n userThemes={userThemes}\n userIcons={userIcons}\n areas={areas}\n onRemove={remove}\n onUse={item => store.setChoice({ ...themes.choice, preset: item.id, overrides: {}, accent: null })}\n notify={notify}\n relativeTime={relativeTime}\n reloadOn={RELOAD_ON}\n controls={CONTROLS}\n />;\n}\n"]}
@@ -1,11 +1,20 @@
1
- import type { PageInventory } from '../../runtime/stringPacks.ts';
1
+ import { type PageInventory } from '../../runtime/stringPacks.ts';
2
2
  import { type InstalledStrings } from '../stringPacksModel.ts';
3
3
  import type { PanelSection } from './site.ts';
4
4
  import './strings.css';
5
- /** Hands the site tabs the packs that are on (in `packs` order). */
6
- export declare function applyStringPacks(inventory: PageInventory, packs?: InstalledStrings[]): Promise<void>;
5
+ /**
6
+ * Packs the app itself provides (pack documents as text), applied before the installed ones, so an
7
+ * installed pack wins where both change a word. They follow the app's own settings (the Twitter
8
+ * wording is a pack that is only here while its setting is on); they are not listed or removable.
9
+ */
10
+ export type BuiltInPacks = () => string[] | Promise<string[]>;
11
+ /** Hands the site tabs the packs that are on (built-in ones first, then `packs` in order). */
12
+ export declare function applyStringPacks(inventory: PageInventory, packs?: InstalledStrings[], builtIn?: BuiltInPacks): Promise<void>;
13
+ /** At start: hands the tabs the delivery only when it differs from the one they were last given (a tab reloads for each). */
14
+ export declare function syncStringPacks(inventory: PageInventory, builtIn?: BuiltInPacks): Promise<boolean>;
7
15
  export declare function createStringPacksSection(options: {
8
16
  inventory: PageInventory;
17
+ builtIn?: BuiltInPacks;
9
18
  id?: string;
10
19
  icon?: string;
11
20
  }): PanelSection;
@@ -4,6 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
4
  // site tabs the new delivery (`strings_apply`: the packs that are on, in order, with the inventory
5
5
  // slice their keys need); the tabs reload to show it. Packs are data only: nothing here runs code.
6
6
  import { useEffect, useState } from 'react';
7
+ import { hashText } from "../../runtime/stringPacks.js";
7
8
  import { invoke } from "../frame/bridge.js";
8
9
  import { useT } from "../frame/hooks.js";
9
10
  import { t as translate } from "../frame/i18n.js";
@@ -20,17 +21,41 @@ catch {
20
21
  return false;
21
22
  } };
22
23
  const ICON = 'M2.5 3.5h7M6 2v1.5M8 3.5c-.6 2.9-2.6 5-5.4 6.1M4.6 6.2c.8 1.2 2 2.2 3.4 2.9M9.5 14l3-7 3 7M10.6 11.8h3.8';
23
- /** Hands the site tabs the packs that are on (in `packs` order). */
24
- export async function applyStringPacks(inventory, packs) {
24
+ const DELIVERED_KEY = 'wayBetter.stringsDelivered';
25
+ /** What the tabs were last handed ("none" for no delivery), so a start-up check can skip a reload. */
26
+ const delivered = () => { try {
27
+ return localStorage.getItem(DELIVERED_KEY) ?? 'none';
28
+ }
29
+ catch {
30
+ return 'none';
31
+ } };
32
+ async function deliveryOf(inventory, packs, builtIn) {
25
33
  const list = packs ?? (await invoke('strings_list')).packs;
26
- const texts = [];
34
+ const texts = [...(await builtIn?.() ?? [])];
27
35
  for (const pack of list)
28
36
  if (pack.enabled)
29
37
  texts.push(await invoke('strings_read', { id: pack.id }));
30
- await invoke('strings_apply', { delivery: buildDelivery(texts, inventory, { translate: translating() }) });
38
+ return buildDelivery(texts, inventory, { translate: translating() });
39
+ }
40
+ /** Hands the site tabs the packs that are on (built-in ones first, then `packs` in order). */
41
+ export async function applyStringPacks(inventory, packs, builtIn) {
42
+ const delivery = await deliveryOf(inventory, packs, builtIn);
43
+ await invoke('strings_apply', { delivery });
44
+ try {
45
+ localStorage.setItem(DELIVERED_KEY, delivery === null ? 'none' : hashText(delivery));
46
+ }
47
+ catch { /* the next start-up check applies again */ }
48
+ }
49
+ /** At start: hands the tabs the delivery only when it differs from the one they were last given (a tab reloads for each). */
50
+ export async function syncStringPacks(inventory, builtIn) {
51
+ const delivery = await deliveryOf(inventory, undefined, builtIn);
52
+ if ((delivery === null ? 'none' : hashText(delivery)) === delivered())
53
+ return false;
54
+ await applyStringPacks(inventory, undefined, builtIn);
55
+ return true;
31
56
  }
32
57
  export function createStringPacksSection(options) {
33
- const { inventory } = options;
58
+ const { inventory, builtIn } = options;
34
59
  function StringPacks({ notify, openSection }) {
35
60
  const t = useT();
36
61
  const query = useSettingsQuery();
@@ -58,7 +83,7 @@ export function createStringPacksSection(options) {
58
83
  setBusy(true);
59
84
  try {
60
85
  const { packs } = await invoke('strings_set', { order: next.map(row => row.id), enabled: next.filter(row => row.enabled).map(row => row.id) });
61
- await applyStringPacks(inventory, packs);
86
+ await applyStringPacks(inventory, packs, builtIn);
62
87
  setRows(packs.map(pack => ({ ...pack, summary: next.find(row => row.id === pack.id)?.summary ?? null })));
63
88
  notify(t('wb.strings.applied', { site: name }));
64
89
  }
@@ -87,7 +112,7 @@ export function createStringPacksSection(options) {
87
112
  .filter(Boolean).join(' · ') })] }), _jsx(Button, { size: "sm", label: t('wb.strings.up'), disabled: busy || index === 0, onClick: () => move(index, -1), children: "\u2191" }), _jsx(Button, { size: "sm", label: t('wb.strings.down'), disabled: busy || index === rows.length - 1, onClick: () => move(index, 1), children: "\u2193" }), _jsx(Button, { size: "sm", tone: "danger", disabled: busy, onClick: () => {
88
113
  setBusy(true);
89
114
  void invoke('strings_remove', { id: row.id })
90
- .then(async ({ packs }) => { await applyStringPacks(inventory, packs); notify(t('wb.strings.removed', { name: row.name })); await load(); })
115
+ .then(async ({ packs }) => { await applyStringPacks(inventory, packs, builtIn); notify(t('wb.strings.removed', { name: row.name })); await load(); })
91
116
  .catch(error => notify(String(error), true))
92
117
  .finally(() => setBusy(false));
93
118
  }, children: t('wb.strings.remove') })] }, row.id)) }), on >= MAX_ON && _jsx("p", { className: "btp-hint", children: t('wb.strings.limit') }), _jsxs("div", { className: "btp-strings-row", children: [_jsx(Toggle, { label: t('wb.strings.translate'), checked: translate, disabled: busy, onChange: checked => {
@@ -97,7 +122,7 @@ export function createStringPacksSection(options) {
97
122
  catch { /* it still applies once */ }
98
123
  setTranslate(checked);
99
124
  setBusy(true);
100
- void applyStringPacks(inventory).then(() => notify(t('wb.strings.applied', { site: name }))).catch(error => notify(String(error), true)).finally(() => setBusy(false));
125
+ void applyStringPacks(inventory, undefined, builtIn).then(() => notify(t('wb.strings.applied', { site: name }))).catch(error => notify(String(error), true)).finally(() => setBusy(false));
101
126
  } }), _jsxs("div", { className: "btp-strings-text", children: [_jsx("strong", { children: t('wb.strings.translate') }), _jsx("small", { children: t('wb.strings.translateHint', { site: name }) })] })] })] });
102
127
  }
103
128
  // The nav label: the plain translator (the panel calls `label()` outside React too).
@@ -1 +1 @@
1
- {"version":3,"file":"StringPacks.js","sourceRoot":"","sources":["../../../../../../shell/src/panel/StringPacks.tsx"],"names":[],"mappings":"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","sourcesContent":["// The Languages section: the installed string packs (waybetter/docs/STRING-PACKS.md), which are on\n// and in which order, with each pack's coverage against the site's inventory. Every change hands the\n// site tabs the new delivery (`strings_apply`: the packs that are on, in order, with the inventory\n// slice their keys need); the tabs reload to show it. Packs are data only: nothing here runs code.\nimport { useEffect, useState } from 'react';\nimport type { PageInventory } from '../../../runtime/src/stringPacks.ts';\nimport { invoke } from '../frame/bridge.ts';\nimport { useT } from '../frame/hooks.ts';\nimport { t as translate } from '../frame/i18n.ts';\nimport { site } from '../frame/site.ts';\nimport { buildDelivery, coveragePercent, packSummary, type InstalledStrings, type StringsSummary } from '../stringPacksModel.ts';\nimport { Badge, Button, EmptyState, Section, Toggle, useSettingsQuery } from './controls.tsx';\nimport type { PanelSection, SectionProps } from './site.ts';\nimport './strings.css';\n\nconst MAX_ON = 8;\nconst TRANSLATE_KEY = 'wb.strings.translate';\nconst translating = () => { try { return localStorage.getItem(TRANSLATE_KEY) === '1'; } catch { return false; } };\nconst ICON = 'M2.5 3.5h7M6 2v1.5M8 3.5c-.6 2.9-2.6 5-5.4 6.1M4.6 6.2c.8 1.2 2 2.2 3.4 2.9M9.5 14l3-7 3 7M10.6 11.8h3.8';\n\ntype Row = InstalledStrings & { summary: StringsSummary | null };\n\n/** Hands the site tabs the packs that are on (in `packs` order). */\nexport async function applyStringPacks(inventory: PageInventory, packs?: InstalledStrings[]): Promise<void> {\n const list = packs ?? (await invoke<{ packs: InstalledStrings[] }>('strings_list')).packs;\n const texts: string[] = [];\n for (const pack of list) if (pack.enabled) texts.push(await invoke<string>('strings_read', { id: pack.id }));\n await invoke('strings_apply', { delivery: buildDelivery(texts, inventory, { translate: translating() }) });\n}\n\nexport function createStringPacksSection(options: { inventory: PageInventory; id?: string; icon?: string }): PanelSection {\n const { inventory } = options;\n\n function StringPacks({ notify, openSection }: SectionProps) {\n const t = useT();\n const query = useSettingsQuery();\n const name = site().name;\n const [rows, setRows] = useState<Row[] | null>(null);\n const [busy, setBusy] = useState(false);\n const [translate, setTranslate] = useState(translating);\n\n const load = async () => {\n const { packs } = await invoke<{ packs: InstalledStrings[] }>('strings_list');\n const next: Row[] = [];\n for (const pack of packs) {\n const text = await invoke<string>('strings_read', { id: pack.id }).catch(() => '');\n next.push({ ...pack, summary: text ? packSummary(text, inventory) : null });\n }\n setRows(next);\n };\n useEffect(() => {\n void load().catch(error => notify(String(error), true));\n const reload = () => void load().catch(() => {});\n window.addEventListener('bt-strings-changed', reload);\n return () => window.removeEventListener('bt-strings-changed', reload);\n }, []);\n\n /** Saves order and on/off, then hands the tabs the new delivery. */\n const save = async (next: Row[]) => {\n setBusy(true);\n try {\n const { packs } = await invoke<{ packs: InstalledStrings[] }>('strings_set', { order: next.map(row => row.id), enabled: next.filter(row => row.enabled).map(row => row.id) });\n await applyStringPacks(inventory, packs);\n setRows(packs.map(pack => ({ ...pack, summary: next.find(row => row.id === pack.id)?.summary ?? null })));\n notify(t('wb.strings.applied', { site: name }));\n } catch (error) {\n notify(String(error), true);\n } finally {\n setBusy(false);\n }\n };\n\n if (query) return null;\n if (rows === null) return null;\n const on = rows.filter(row => row.enabled).length;\n const move = (index: number, by: number) => {\n const next = [...rows];\n const [row] = next.splice(index, 1);\n next.splice(index + by, 0, row!);\n void save(next);\n };\n\n return <Section title={t('wb.strings.title')} hint={t('wb.strings.intro', { site: name })}>\n {rows.length === 0\n ? <EmptyState title={t('wb.strings.empty')} action={<Button onClick={() => openSection('store')}>{t('wb.strings.openStore')}</Button>}>{t('wb.strings.emptyHint')}</EmptyState>\n : <ul className=\"btp-strings\" aria-label={t('wb.strings.title')}>\n {rows.map((row, index) => <li key={row.id} className={`btp-strings-row${row.enabled ? ' is-on' : ''}`}>\n <Toggle label={t('wb.strings.on', { name: row.name })} checked={row.enabled} disabled={busy || (!row.enabled && on >= MAX_ON)}\n onChange={checked => void save(rows.map(item => (item.id === row.id ? { ...item, enabled: checked } : item)))}/>\n <div className=\"btp-strings-text\">\n <span className=\"btp-strings-head\"><strong>{row.name}</strong><Badge>{row.language}</Badge><Badge tone=\"ok\">{t('wb.strings.dataOnly')}</Badge></span>\n <small>\n {[row.authors.length ? t('wb.strings.by', { authors: row.authors.join(', ') }) : '', row.version && `v${row.version}`,\n row.summary && !row.summary.otherCatalog ? t('wb.install.strings.coverageValue', { translated: String(row.summary.translated), translatable: String(row.summary.translatable), site: name, percent: coveragePercent(row.summary) }) : t('wb.install.strings.count', { count: row.strings })]\n .filter(Boolean).join(' · ')}\n </small>\n </div>\n <Button size=\"sm\" label={t('wb.strings.up')} disabled={busy || index === 0} onClick={() => move(index, -1)}>↑</Button>\n <Button size=\"sm\" label={t('wb.strings.down')} disabled={busy || index === rows.length - 1} onClick={() => move(index, 1)}>↓</Button>\n <Button size=\"sm\" tone=\"danger\" disabled={busy} onClick={() => {\n setBusy(true);\n void invoke<{ packs: InstalledStrings[] }>('strings_remove', { id: row.id })\n .then(async ({ packs }) => { await applyStringPacks(inventory, packs); notify(t('wb.strings.removed', { name: row.name })); await load(); })\n .catch(error => notify(String(error), true))\n .finally(() => setBusy(false));\n }}>{t('wb.strings.remove')}</Button>\n </li>)}\n </ul>}\n {on >= MAX_ON && <p className=\"btp-hint\">{t('wb.strings.limit')}</p>}\n <div className=\"btp-strings-row\">\n <Toggle label={t('wb.strings.translate')} checked={translate} disabled={busy} onChange={checked => {\n try { localStorage.setItem(TRANSLATE_KEY, checked ? '1' : '0'); } catch { /* it still applies once */ }\n setTranslate(checked);\n setBusy(true);\n void applyStringPacks(inventory).then(() => notify(t('wb.strings.applied', { site: name }))).catch(error => notify(String(error), true)).finally(() => setBusy(false));\n }}/>\n <div className=\"btp-strings-text\"><strong>{t('wb.strings.translate')}</strong><small>{t('wb.strings.translateHint', { site: name })}</small></div>\n </div>\n </Section>;\n }\n\n // The nav label: the plain translator (the panel calls `label()` outside React too).\n return { id: options.id ?? 'languages', label: () => translate('wb.strings.section'), icon: options.icon ?? ICON, component: StringPacks };\n}\n"]}
1
+ {"version":3,"file":"StringPacks.js","sourceRoot":"","sources":["../../../../../../shell/src/panel/StringPacks.tsx"],"names":[],"mappings":"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","sourcesContent":["// The Languages section: the installed string packs (waybetter/docs/STRING-PACKS.md), which are on\n// and in which order, with each pack's coverage against the site's inventory. Every change hands the\n// site tabs the new delivery (`strings_apply`: the packs that are on, in order, with the inventory\n// slice their keys need); the tabs reload to show it. Packs are data only: nothing here runs code.\nimport { useEffect, useState } from 'react';\nimport { hashText, type PageInventory } from '../../../runtime/src/stringPacks.ts';\nimport { invoke } from '../frame/bridge.ts';\nimport { useT } from '../frame/hooks.ts';\nimport { t as translate } from '../frame/i18n.ts';\nimport { site } from '../frame/site.ts';\nimport { buildDelivery, coveragePercent, packSummary, type InstalledStrings, type StringsSummary } from '../stringPacksModel.ts';\nimport { Badge, Button, EmptyState, Section, Toggle, useSettingsQuery } from './controls.tsx';\nimport type { PanelSection, SectionProps } from './site.ts';\nimport './strings.css';\n\nconst MAX_ON = 8;\nconst TRANSLATE_KEY = 'wb.strings.translate';\nconst translating = () => { try { return localStorage.getItem(TRANSLATE_KEY) === '1'; } catch { return false; } };\nconst ICON = 'M2.5 3.5h7M6 2v1.5M8 3.5c-.6 2.9-2.6 5-5.4 6.1M4.6 6.2c.8 1.2 2 2.2 3.4 2.9M9.5 14l3-7 3 7M10.6 11.8h3.8';\n\ntype Row = InstalledStrings & { summary: StringsSummary | null };\n\n/**\n * Packs the app itself provides (pack documents as text), applied before the installed ones, so an\n * installed pack wins where both change a word. They follow the app's own settings (the Twitter\n * wording is a pack that is only here while its setting is on); they are not listed or removable.\n */\nexport type BuiltInPacks = () => string[] | Promise<string[]>;\n\nconst DELIVERED_KEY = 'wayBetter.stringsDelivered';\n/** What the tabs were last handed (\"none\" for no delivery), so a start-up check can skip a reload. */\nconst delivered = () => { try { return localStorage.getItem(DELIVERED_KEY) ?? 'none'; } catch { return 'none'; } };\n\nasync function deliveryOf(inventory: PageInventory, packs: InstalledStrings[] | undefined, builtIn: BuiltInPacks | undefined): Promise<string | null> {\n const list = packs ?? (await invoke<{ packs: InstalledStrings[] }>('strings_list')).packs;\n const texts: string[] = [...(await builtIn?.() ?? [])];\n for (const pack of list) if (pack.enabled) texts.push(await invoke<string>('strings_read', { id: pack.id }));\n return buildDelivery(texts, inventory, { translate: translating() });\n}\n\n/** Hands the site tabs the packs that are on (built-in ones first, then `packs` in order). */\nexport async function applyStringPacks(inventory: PageInventory, packs?: InstalledStrings[], builtIn?: BuiltInPacks): Promise<void> {\n const delivery = await deliveryOf(inventory, packs, builtIn);\n await invoke('strings_apply', { delivery });\n try { localStorage.setItem(DELIVERED_KEY, delivery === null ? 'none' : hashText(delivery)); } catch { /* the next start-up check applies again */ }\n}\n\n/** At start: hands the tabs the delivery only when it differs from the one they were last given (a tab reloads for each). */\nexport async function syncStringPacks(inventory: PageInventory, builtIn?: BuiltInPacks): Promise<boolean> {\n const delivery = await deliveryOf(inventory, undefined, builtIn);\n if ((delivery === null ? 'none' : hashText(delivery)) === delivered()) return false;\n await applyStringPacks(inventory, undefined, builtIn);\n return true;\n}\n\nexport function createStringPacksSection(options: { inventory: PageInventory; builtIn?: BuiltInPacks; id?: string; icon?: string }): PanelSection {\n const { inventory, builtIn } = options;\n\n function StringPacks({ notify, openSection }: SectionProps) {\n const t = useT();\n const query = useSettingsQuery();\n const name = site().name;\n const [rows, setRows] = useState<Row[] | null>(null);\n const [busy, setBusy] = useState(false);\n const [translate, setTranslate] = useState(translating);\n\n const load = async () => {\n const { packs } = await invoke<{ packs: InstalledStrings[] }>('strings_list');\n const next: Row[] = [];\n for (const pack of packs) {\n const text = await invoke<string>('strings_read', { id: pack.id }).catch(() => '');\n next.push({ ...pack, summary: text ? packSummary(text, inventory) : null });\n }\n setRows(next);\n };\n useEffect(() => {\n void load().catch(error => notify(String(error), true));\n const reload = () => void load().catch(() => {});\n window.addEventListener('bt-strings-changed', reload);\n return () => window.removeEventListener('bt-strings-changed', reload);\n }, []);\n\n /** Saves order and on/off, then hands the tabs the new delivery. */\n const save = async (next: Row[]) => {\n setBusy(true);\n try {\n const { packs } = await invoke<{ packs: InstalledStrings[] }>('strings_set', { order: next.map(row => row.id), enabled: next.filter(row => row.enabled).map(row => row.id) });\n await applyStringPacks(inventory, packs, builtIn);\n setRows(packs.map(pack => ({ ...pack, summary: next.find(row => row.id === pack.id)?.summary ?? null })));\n notify(t('wb.strings.applied', { site: name }));\n } catch (error) {\n notify(String(error), true);\n } finally {\n setBusy(false);\n }\n };\n\n if (query) return null;\n if (rows === null) return null;\n const on = rows.filter(row => row.enabled).length;\n const move = (index: number, by: number) => {\n const next = [...rows];\n const [row] = next.splice(index, 1);\n next.splice(index + by, 0, row!);\n void save(next);\n };\n\n return <Section title={t('wb.strings.title')} hint={t('wb.strings.intro', { site: name })}>\n {rows.length === 0\n ? <EmptyState title={t('wb.strings.empty')} action={<Button onClick={() => openSection('store')}>{t('wb.strings.openStore')}</Button>}>{t('wb.strings.emptyHint')}</EmptyState>\n : <ul className=\"btp-strings\" aria-label={t('wb.strings.title')}>\n {rows.map((row, index) => <li key={row.id} className={`btp-strings-row${row.enabled ? ' is-on' : ''}`}>\n <Toggle label={t('wb.strings.on', { name: row.name })} checked={row.enabled} disabled={busy || (!row.enabled && on >= MAX_ON)}\n onChange={checked => void save(rows.map(item => (item.id === row.id ? { ...item, enabled: checked } : item)))}/>\n <div className=\"btp-strings-text\">\n <span className=\"btp-strings-head\"><strong>{row.name}</strong><Badge>{row.language}</Badge><Badge tone=\"ok\">{t('wb.strings.dataOnly')}</Badge></span>\n <small>\n {[row.authors.length ? t('wb.strings.by', { authors: row.authors.join(', ') }) : '', row.version && `v${row.version}`,\n row.summary && !row.summary.otherCatalog ? t('wb.install.strings.coverageValue', { translated: String(row.summary.translated), translatable: String(row.summary.translatable), site: name, percent: coveragePercent(row.summary) }) : t('wb.install.strings.count', { count: row.strings })]\n .filter(Boolean).join(' · ')}\n </small>\n </div>\n <Button size=\"sm\" label={t('wb.strings.up')} disabled={busy || index === 0} onClick={() => move(index, -1)}>↑</Button>\n <Button size=\"sm\" label={t('wb.strings.down')} disabled={busy || index === rows.length - 1} onClick={() => move(index, 1)}>↓</Button>\n <Button size=\"sm\" tone=\"danger\" disabled={busy} onClick={() => {\n setBusy(true);\n void invoke<{ packs: InstalledStrings[] }>('strings_remove', { id: row.id })\n .then(async ({ packs }) => { await applyStringPacks(inventory, packs, builtIn); notify(t('wb.strings.removed', { name: row.name })); await load(); })\n .catch(error => notify(String(error), true))\n .finally(() => setBusy(false));\n }}>{t('wb.strings.remove')}</Button>\n </li>)}\n </ul>}\n {on >= MAX_ON && <p className=\"btp-hint\">{t('wb.strings.limit')}</p>}\n <div className=\"btp-strings-row\">\n <Toggle label={t('wb.strings.translate')} checked={translate} disabled={busy} onChange={checked => {\n try { localStorage.setItem(TRANSLATE_KEY, checked ? '1' : '0'); } catch { /* it still applies once */ }\n setTranslate(checked);\n setBusy(true);\n void applyStringPacks(inventory, undefined, builtIn).then(() => notify(t('wb.strings.applied', { site: name }))).catch(error => notify(String(error), true)).finally(() => setBusy(false));\n }}/>\n <div className=\"btp-strings-text\"><strong>{t('wb.strings.translate')}</strong><small>{t('wb.strings.translateHint', { site: name })}</small></div>\n </div>\n </Section>;\n }\n\n // The nav label: the plain translator (the panel calls `label()` outside React too).\n return { id: options.id ?? 'languages', label: () => translate('wb.strings.section'), icon: options.icon ?? ICON, component: StringPacks };\n}\n"]}
@@ -22,7 +22,7 @@
22
22
  .btb-pass .bt-input { width: 200px; }
23
23
  .btb-error { color: #ff8a80; font-size: 12px; }
24
24
  .btb-snapshots { display: flex; flex-direction: column; gap: 6px; width: 100%; }
25
- .btb-snapshot { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 9px; background: rgba(127, 127, 127, .06); }
25
+ .btb-snapshot { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: calc(9px * var(--bt-rs, 1)); background: rgba(127, 127, 127, .06); }
26
26
  .btb-snapshot.is-fresh { background: rgb(var(--c-accent-rgb) / .1); box-shadow: inset 0 0 0 1px rgb(var(--c-accent-rgb) / .3); }
27
27
  .btb-snapshot-device { font-weight: 600; font-size: 12.5px; }
28
28
  .btb-snapshot-when { flex: 1; color: var(--c-muted); font-size: 12px; font-variant-numeric: tabular-nums; }
@@ -8,16 +8,16 @@
8
8
  .btp-page:has(.wb-builder) { max-width: 1320px; }
9
9
  .wb-builder-bar {
10
10
  position: sticky; top: -18px; z-index: 12; display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px;
11
- margin: 0 -4px; padding: 10px 12px; border: 1px solid var(--c-line); border-radius: 14px;
11
+ margin: 0 -4px; padding: 10px 12px; border: 1px solid var(--c-line); border-radius: calc(14px * var(--bt-rs, 1));
12
12
  background: color-mix(in srgb, var(--c-card) 88%, transparent); backdrop-filter: blur(14px) saturate(1.2);
13
13
  box-shadow: 0 10px 30px -18px rgba(0, 0, 0, .6);
14
14
  }
15
- .wb-builder-name { flex: 1 1 200px; min-width: 0; height: 34px; padding: 0 10px; border: 1px solid transparent; border-radius: 9px; background: transparent; color: var(--c-text); font: 650 17px/1 var(--f-ui); letter-spacing: -.02em; }
15
+ .wb-builder-name { flex: 1 1 200px; min-width: 0; height: 34px; padding: 0 10px; border: 1px solid transparent; border-radius: calc(9px * var(--bt-rs, 1)); background: transparent; color: var(--c-text); font: 650 17px/1 var(--f-ui); letter-spacing: -.02em; }
16
16
  .wb-builder-name:hover { border-color: var(--c-line); }
17
17
  .wb-builder-name:focus { outline: none; border-color: var(--c-accent); background: var(--c-field); }
18
18
  .wb-builder-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
19
19
  .wb-divider { width: 1px; height: 20px; margin: 0 4px; background: var(--c-line); }
20
- .wb-icon-button { display: inline-grid; place-items: center; width: 32px; height: 32px; padding: 0; border: 1px solid var(--c-line); border-radius: 9px; background: var(--c-field); color: var(--c-text); cursor: pointer; transition: background-color .15s, border-color .15s; }
20
+ .wb-icon-button { display: inline-grid; place-items: center; width: 32px; height: 32px; padding: 0; border: 1px solid var(--c-line); border-radius: calc(9px * var(--bt-rs, 1)); background: var(--c-field); color: var(--c-text); cursor: pointer; transition: background-color .15s, border-color .15s; }
21
21
  .wb-icon-button:hover:not(:disabled) { border-color: var(--c-line-strong); background: var(--c-raise); }
22
22
  .wb-icon-button:disabled { opacity: .38; cursor: default; }
23
23
  .wb-live { display: inline-flex; align-items: center; gap: 8px; margin-inline-end: 4px; color: var(--c-muted); font-size: 12px; font-weight: 520; }
@@ -30,11 +30,11 @@
30
30
  .wb-builder-preview { order: 0; position: sticky; top: 52px; }
31
31
  }
32
32
 
33
- .wb-card { padding: 16px 16px 14px; border: 1px solid var(--c-line-soft); border-radius: 14px; background: var(--c-card); }
33
+ .wb-card { padding: 16px 16px 14px; border: 1px solid var(--c-line-soft); border-radius: calc(14px * var(--bt-rs, 1)); background: var(--c-card); }
34
34
  .wb-card > header { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 14px; }
35
35
  .wb-card h3 { margin: 0; font-size: 14px; font-weight: 640; letter-spacing: -.01em; }
36
36
  .wb-card header p { margin: 3px 0 0; max-width: 62ch; color: var(--c-muted); font-size: 12px; line-height: 1.45; }
37
- .wb-ok { display: inline-flex; align-items: center; gap: 6px; height: 26px; padding: 0 10px; border-radius: 999px; background: color-mix(in srgb, var(--c-ok) 14%, transparent); color: var(--c-ok); font-size: 12px; font-weight: 600; white-space: nowrap; }
37
+ .wb-ok { display: inline-flex; align-items: center; gap: 6px; height: 26px; padding: 0 10px; border-radius: calc(999px * var(--bt-rs, 1)); background: color-mix(in srgb, var(--c-ok) 14%, transparent); color: var(--c-ok); font-size: 12px; font-weight: 600; white-space: nowrap; }
38
38
 
39
39
  .wb-fields { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 12px 18px; margin-top: 14px; }
40
40
  .wb-field { display: flex; flex-direction: column; gap: 7px; min-width: 0; }
@@ -43,37 +43,37 @@
43
43
 
44
44
  /* Base colors */
45
45
  .wb-base { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 10px; }
46
- .wb-base-item { display: flex; flex-direction: column; gap: 9px; padding: 10px; border: 1px solid var(--c-line-soft); border-radius: 12px; background: var(--c-ground); }
47
- .wb-base-swatch { position: relative; display: flex; align-items: flex-end; justify-content: space-between; height: 62px; padding: 10px 11px; border-radius: 9px; box-shadow: inset 0 0 0 1px rgb(127 127 127 / .22); }
46
+ .wb-base-item { display: flex; flex-direction: column; gap: 9px; padding: 10px; border: 1px solid var(--c-line-soft); border-radius: calc(12px * var(--bt-rs, 1)); background: var(--c-ground); }
47
+ .wb-base-swatch { position: relative; display: flex; align-items: flex-end; justify-content: space-between; height: 62px; padding: 10px 11px; border-radius: calc(9px * var(--bt-rs, 1)); box-shadow: inset 0 0 0 1px rgb(127 127 127 / .22); }
48
48
  .wb-base-swatch span { font-size: 13px; font-weight: 650; letter-spacing: -.01em; }
49
- .wb-base-swatch code { padding: 2px 6px; border: 0; border-radius: 6px; background: rgb(0 0 0 / .32); color: #fff; font: 500 11px/1.4 var(--f-mono); }
49
+ .wb-base-swatch code { padding: 2px 6px; border: 0; border-radius: calc(6px * var(--bt-rs, 1)); background: rgb(0 0 0 / .32); color: #fff; font: 500 11px/1.4 var(--f-mono); }
50
50
  .wb-base-item .bt-color { flex-wrap: wrap; gap: 6px; }
51
51
  .wb-base-item .bt-swatch { width: 22px; height: 22px; }
52
52
 
53
53
  /* Derived palette */
54
54
  .wb-palette { margin-top: 12px; }
55
55
  .wb-palette-row { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px; }
56
- .wb-chip { display: flex; align-items: center; gap: 9px; min-width: 0; padding: 7px 9px; border: 1px solid var(--c-line-soft); border-radius: 10px; background: var(--c-ground); color: var(--c-text); text-align: start; cursor: pointer; transition: border-color .15s, background-color .15s; }
56
+ .wb-chip { display: flex; align-items: center; gap: 9px; min-width: 0; padding: 7px 9px; border: 1px solid var(--c-line-soft); border-radius: calc(10px * var(--bt-rs, 1)); background: var(--c-ground); color: var(--c-text); text-align: start; cursor: pointer; transition: border-color .15s, background-color .15s; }
57
57
  .wb-chip:hover { border-color: var(--c-line-strong); }
58
58
  .wb-chip.is-on { border-color: var(--c-accent); box-shadow: 0 0 0 1px var(--c-accent); }
59
- .wb-chip-swatch { flex: none; width: 22px; height: 22px; border-radius: 6px; box-shadow: inset 0 0 0 1px rgb(127 127 127 / .3); }
59
+ .wb-chip-swatch { flex: none; width: 22px; height: 22px; border-radius: calc(6px * var(--bt-rs, 1)); box-shadow: inset 0 0 0 1px rgb(127 127 127 / .3); }
60
60
  .wb-chip-copy { display: flex; flex-direction: column; min-width: 0; }
61
61
  .wb-chip-copy b { font-size: 12px; font-weight: 600; }
62
62
  .wb-chip-copy small { overflow: hidden; color: var(--c-muted); font: 10.5px/1.4 var(--f-mono); white-space: nowrap; text-overflow: ellipsis; }
63
- .wb-palette-edit { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 10px; padding: 10px; border-radius: 10px; background: var(--c-field); }
63
+ .wb-palette-edit { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 10px; padding: 10px; border-radius: calc(10px * var(--bt-rs, 1)); background: var(--c-field); }
64
64
  .wb-palette-edit > span { font-size: 12px; font-weight: 600; }
65
65
 
66
66
  /* Contrast */
67
67
  .wb-contrast { display: flex; flex-direction: column; gap: 4px; margin: 0; padding: 0; list-style: none; }
68
- .wb-contrast li { display: grid; grid-template-columns: 40px 1fr auto auto 76px; align-items: center; gap: 12px; padding: 6px 8px; border-radius: 9px; }
68
+ .wb-contrast li { display: grid; grid-template-columns: 40px 1fr auto auto 76px; align-items: center; gap: 12px; padding: 6px 8px; border-radius: calc(9px * var(--bt-rs, 1)); }
69
69
  .wb-contrast li:hover { background: var(--c-field); }
70
70
  .wb-contrast li.is-fail { background: color-mix(in srgb, var(--c-danger) 7%, transparent); }
71
- .wb-contrast-sample { display: grid; place-items: center; width: 40px; height: 28px; border-radius: 7px; box-shadow: inset 0 0 0 1px rgb(127 127 127 / .25); font-size: 14px; font-weight: 700; }
71
+ .wb-contrast-sample { display: grid; place-items: center; width: 40px; height: 28px; border-radius: calc(7px * var(--bt-rs, 1)); box-shadow: inset 0 0 0 1px rgb(127 127 127 / .25); font-size: 14px; font-weight: 700; }
72
72
  .wb-contrast-label { font-size: 12.5px; }
73
73
  .wb-contrast-ratio { font: 12px/1 var(--f-mono); color: var(--c-muted); }
74
74
  .wb-contrast-ratio b { color: var(--c-text); font-weight: 600; }
75
75
  .wb-contrast-need { color: var(--c-faint); font-size: 11.5px; }
76
- .wb-contrast-badge { justify-self: end; padding: 3px 7px; border-radius: 6px; font: 600 10.5px/1.2 var(--f-mono); text-align: center; }
76
+ .wb-contrast-badge { justify-self: end; padding: 3px 7px; border-radius: calc(6px * var(--bt-rs, 1)); font: 600 10.5px/1.2 var(--f-mono); text-align: center; }
77
77
  .wb-contrast-badge.is-pass { background: color-mix(in srgb, var(--c-ok) 14%, transparent); color: var(--c-ok); }
78
78
  .wb-contrast-badge.is-fail { background: color-mix(in srgb, var(--c-danger) 16%, transparent); color: var(--c-danger); }
79
79
  @container panel (max-width: 560px) {
@@ -85,48 +85,48 @@
85
85
  .wb-bg .bt-seg { margin-bottom: 2px; }
86
86
 
87
87
  /* Preview column */
88
- .wb-preview-frame { overflow: hidden; border: 1px solid var(--c-line); border-radius: 14px; background: var(--c-ground); box-shadow: 0 24px 60px -30px rgba(0, 0, 0, .7); }
88
+ .wb-preview-frame { overflow: hidden; border: 1px solid var(--c-line); border-radius: calc(14px * var(--bt-rs, 1)); background: var(--c-ground); box-shadow: 0 24px 60px -30px rgba(0, 0, 0, .7); }
89
89
  .wb-preview-frame .tp { border-radius: 0; }
90
90
  .wb-preview-note { display: flex; align-items: center; gap: 8px; margin: 0 2px; color: var(--c-muted); font-size: 12px; line-height: 1.4; }
91
91
  .wb-live-dot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--c-ok); box-shadow: 0 0 0 3px color-mix(in srgb, var(--c-ok) 22%, transparent); animation: wb-pulse 2.4s ease-in-out infinite; }
92
92
  @keyframes wb-pulse { 50% { box-shadow: 0 0 0 6px color-mix(in srgb, var(--c-ok) 0%, transparent); } }
93
93
  @media (prefers-reduced-motion: reduce) { .wb-live-dot { animation: none; } }
94
- .wb-meta { display: flex; flex-direction: column; gap: 10px; padding: 12px; border: 1px solid var(--c-line-soft); border-radius: 12px; background: var(--c-card); }
94
+ .wb-meta { display: flex; flex-direction: column; gap: 10px; padding: 12px; border: 1px solid var(--c-line-soft); border-radius: calc(12px * var(--bt-rs, 1)); background: var(--c-card); }
95
95
  .wb-meta label { display: flex; flex-direction: column; gap: 5px; }
96
96
  .wb-meta span { color: var(--c-muted); font-size: 11.5px; font-weight: 560; }
97
- .wb-meta input, .wb-meta textarea { padding: 7px 9px; border: 1px solid var(--c-line); border-radius: 8px; background: var(--c-field); color: var(--c-text); font: 13px/1.4 var(--f-ui); resize: vertical; }
97
+ .wb-meta input, .wb-meta textarea { padding: 7px 9px; border: 1px solid var(--c-line); border-radius: calc(8px * var(--bt-rs, 1)); background: var(--c-field); color: var(--c-text); font: 13px/1.4 var(--f-ui); resize: vertical; }
98
98
  .wb-meta input:focus, .wb-meta textarea:focus { outline: none; border-color: var(--c-accent); }
99
99
 
100
100
  /* The chrome in the draft (tokens straight from chromeVars, Night Ink fallbacks). */
101
- .wb-chrome-preview { padding: 7px 8px 0; background: var(--wb-c-ground, #07080c); font-family: var(--wb-f-chrome, 'Geist Variable', system-ui, sans-serif); }
101
+ .wb-chrome-preview { --bt-rs: var(--wb-c-rs, 1); padding: 7px 8px 0; background: var(--wb-c-ground, #07080c); font-family: var(--wb-f-chrome, 'Geist Variable', system-ui, sans-serif); }
102
102
  .wb-cp-tabs { display: flex; gap: 4px; }
103
- .wb-cp-tab { display: inline-flex; align-items: center; gap: 7px; height: 26px; padding: 0 12px; border-radius: 8px 8px 0 0; color: var(--wb-c-muted, #8a90a6); font-size: 11.5px; font-weight: 520; }
104
- .wb-cp-tab i { width: 10px; height: 10px; border-radius: 3px; background: var(--wb-c-line-strong, #2a3044); }
103
+ .wb-cp-tab { display: inline-flex; align-items: center; gap: 7px; height: 26px; padding: 0 12px; border-radius: calc(8px * var(--bt-rs, 1)) calc(8px * var(--bt-rs, 1)) 0 0; color: var(--wb-c-muted, #8a90a6); font-size: 11.5px; font-weight: 520; }
104
+ .wb-cp-tab i { width: 10px; height: 10px; border-radius: calc(3px * var(--bt-rs, 1)); background: var(--wb-c-line-strong, #2a3044); }
105
105
  .wb-cp-tab.is-on { background: var(--wb-c-chrome, #0d1017); color: var(--wb-c-text, #eef0f7); }
106
106
  .wb-cp-tab.is-on i { background: var(--wb-c-accent, #1d9bf0); }
107
107
  .wb-cp-bar { display: flex; align-items: center; gap: 8px; margin: 0 -8px; padding: 7px 10px; background: var(--wb-c-chrome, #0d1017); border-bottom: 1px solid var(--wb-c-line-soft, #161a26); }
108
108
  .wb-cp-dot { width: 14px; height: 14px; border-radius: 50%; background: var(--wb-c-raised, #141823); }
109
- .wb-cp-omni { flex: 1; display: flex; align-items: center; gap: 7px; height: 24px; padding: 0 9px; border-radius: 7px; background: var(--wb-c-field, #11141c); color: var(--wb-c-text, #eef0f7); font-size: 11.5px; }
110
- .wb-cp-omni b { padding: 2px 7px; border-radius: 5px; background: rgb(var(--wb-c-accent-rgb, 29 155 240) / .16); color: var(--wb-c-accent-ink, #cfe6ff); font-weight: 600; }
109
+ .wb-cp-omni { flex: 1; display: flex; align-items: center; gap: 7px; height: 24px; padding: 0 9px; border-radius: calc(7px * var(--bt-rs, 1)); background: var(--wb-c-field, #11141c); color: var(--wb-c-text, #eef0f7); font-size: 11.5px; }
110
+ .wb-cp-omni b { padding: 2px 7px; border-radius: calc(5px * var(--bt-rs, 1)); background: rgb(var(--wb-c-accent-rgb, 29 155 240) / .16); color: var(--wb-c-accent-ink, #cfe6ff); font-weight: 600; }
111
111
  .wb-cp-omni em { color: var(--wb-c-faint, #5d6379); font-style: normal; }
112
- .wb-cp-brand { width: 26px; height: 16px; border-radius: 5px; background: linear-gradient(90deg, #4f6bff, #22d3ee 55%, #a855f7); opacity: .85; }
112
+ .wb-cp-brand { width: 26px; height: 16px; border-radius: calc(5px * var(--bt-rs, 1)); background: linear-gradient(90deg, #4f6bff, #22d3ee 55%, #a855f7); opacity: .85; }
113
113
 
114
114
  /* ---------------------------------------------------------------------------------------------
115
115
  * Font roles */
116
- .wb-roles { display: flex; flex-direction: column; border: 1px solid var(--c-line-soft); border-radius: 12px; overflow: hidden; }
116
+ .wb-roles { display: flex; flex-direction: column; border: 1px solid var(--c-line-soft); border-radius: calc(12px * var(--bt-rs, 1)); overflow: hidden; }
117
117
  .wb-role + .wb-role { border-top: 1px solid var(--c-line-soft); }
118
118
  .wb-role-main { display: grid; grid-template-columns: minmax(130px, .8fr) minmax(0, 1.4fr) auto; align-items: center; gap: 12px; padding-block: 9px; padding-inline: 14px 10px; }
119
119
  .wb-role-copy { display: flex; flex-direction: column; min-width: 0; }
120
120
  .wb-role-copy strong { font-size: 13px; font-weight: 600; }
121
121
  .wb-role-copy small { color: var(--c-muted); font-size: 11.5px; line-height: 1.35; }
122
- .wb-role-font { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; min-width: 0; padding: 7px 11px; border: 1px solid var(--c-line); border-radius: 10px; background: var(--c-field); color: var(--c-text); text-align: start; cursor: pointer; transition: border-color .15s, background-color .15s; }
122
+ .wb-role-font { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; min-width: 0; padding: 7px 11px; border: 1px solid var(--c-line); border-radius: calc(10px * var(--bt-rs, 1)); background: var(--c-field); color: var(--c-text); text-align: start; cursor: pointer; transition: border-color .15s, background-color .15s; }
123
123
  .wb-role-font:hover { border-color: var(--c-line-strong); background: var(--c-raise); }
124
124
  .wb-role-font.is-inherited { border-style: dashed; background: transparent; }
125
125
  .wb-role-sample { max-width: 100%; overflow: hidden; font-size: 17px; line-height: 1.25; white-space: nowrap; text-overflow: ellipsis; }
126
126
  .wb-role-name { display: inline-flex; align-items: center; gap: 6px; color: var(--c-muted); font-size: 11px; }
127
- .wb-role-name em { padding: 1px 5px; border-radius: 4px; background: var(--c-accent-soft); color: var(--c-accent); font: 600 9.5px/1.3 var(--f-mono); font-style: normal; letter-spacing: .02em; text-transform: uppercase; }
127
+ .wb-role-name em { padding: 1px 5px; border-radius: calc(4px * var(--bt-rs, 1)); background: var(--c-accent-soft); color: var(--c-accent); font: 600 9.5px/1.3 var(--f-mono); font-style: normal; letter-spacing: .02em; text-transform: uppercase; }
128
128
  .wb-role-actions { display: flex; align-items: center; justify-content: flex-end; gap: 4px; min-width: 96px; }
129
- .wb-role-tune { display: grid; place-items: center; width: 30px; height: 30px; padding: 0; border: 1px solid transparent; border-radius: 8px; background: transparent; color: var(--c-muted); cursor: pointer; }
129
+ .wb-role-tune { display: grid; place-items: center; width: 30px; height: 30px; padding: 0; border: 1px solid transparent; border-radius: calc(8px * var(--bt-rs, 1)); background: transparent; color: var(--c-muted); cursor: pointer; }
130
130
  .wb-role-tune:hover, .wb-role-tune[aria-expanded=true] { border-color: var(--c-line); background: var(--c-field); color: var(--c-text); }
131
131
  .wb-role-tune.is-tuned { color: var(--c-accent); }
132
132
  .wb-role-tuning { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px 18px; padding: 4px 14px 14px; }
@@ -142,21 +142,21 @@
142
142
  /* ---------------------------------------------------------------------------------------------
143
143
  * Sheets (font browser): cover the panel, not the window (X panes are native webviews). */
144
144
  .wb-sheet-backdrop { position: absolute; inset: 0; z-index: 60; display: grid; place-items: center; padding: 18px; background: rgb(0 0 0 / .5); backdrop-filter: blur(4px); animation: bt-pop .16s var(--ease); }
145
- .wb-sheet { display: flex; flex-direction: column; width: min(1040px, 100%); height: min(720px, 100%); overflow: hidden; border: 1px solid var(--c-line-strong); border-radius: 16px; background: var(--c-card); box-shadow: 0 40px 100px -30px rgba(0, 0, 0, .8), 0 0 0 1px rgb(0 0 0 / .3); }
145
+ .wb-sheet { display: flex; flex-direction: column; width: min(1040px, 100%); height: min(720px, 100%); overflow: hidden; border: 1px solid var(--c-line-strong); border-radius: calc(16px * var(--bt-rs, 1)); background: var(--c-card); box-shadow: 0 40px 100px -30px rgba(0, 0, 0, .8), 0 0 0 1px rgb(0 0 0 / .3); }
146
146
  .wb-sheet-head { display: flex; align-items: center; gap: 14px; padding-block: 16px 12px; padding-inline: 20px 16px; border-bottom: 1px solid var(--c-line-soft); }
147
147
  .wb-sheet-head > div:first-child { flex: 1; min-width: 0; }
148
148
  .wb-sheet-head .bt-seg { flex: none; }
149
149
  .wb-sheet-head h2 { margin: 0; font-size: 17px; font-weight: 650; letter-spacing: -.02em; }
150
150
  .wb-sheet-head h2 span { color: var(--c-muted); font-weight: 450; }
151
151
  .wb-sheet-head p { margin: 3px 0 0; color: var(--c-muted); font-size: 12px; }
152
- .wb-sheet-close { display: grid; place-items: center; width: 32px; height: 32px; padding: 0; border: 1px solid var(--c-line); border-radius: 9px; background: var(--c-field); color: var(--c-muted); cursor: pointer; }
152
+ .wb-sheet-close { display: grid; place-items: center; width: 32px; height: 32px; padding: 0; border: 1px solid var(--c-line); border-radius: calc(9px * var(--bt-rs, 1)); background: var(--c-field); color: var(--c-muted); cursor: pointer; }
153
153
  .wb-sheet-close:hover { color: var(--c-text); border-color: var(--c-line-strong); }
154
154
  .wb-sheet-foot { padding: 10px 20px 12px; border-top: 1px solid var(--c-line-soft); color: var(--c-faint); font-size: 11px; line-height: 1.5; }
155
155
 
156
156
  .wb-fb-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; padding: 12px 20px; }
157
157
  .wb-fb-search { flex: 1 1 260px; }
158
158
  .wb-fb-cats { display: flex; flex-wrap: wrap; gap: 4px; }
159
- .wb-fb-cats button { height: 28px; padding: 0 11px; border: 1px solid var(--c-line); border-radius: 999px; background: transparent; color: var(--c-muted); font-size: 12px; font-weight: 540; cursor: pointer; transition: color .15s, background-color .15s, border-color .15s; }
159
+ .wb-fb-cats button { height: 28px; padding: 0 11px; border: 1px solid var(--c-line); border-radius: calc(999px * var(--bt-rs, 1)); background: transparent; color: var(--c-muted); font-size: 12px; font-weight: 540; cursor: pointer; transition: color .15s, background-color .15s, border-color .15s; }
160
160
  .wb-fb-cats button:hover { color: var(--c-text); border-color: var(--c-line-strong); }
161
161
  .wb-fb-cats button.is-on { background: var(--c-text); border-color: var(--c-text); color: var(--c-ground); }
162
162
  .wb-fb-cats .wb-fb-installed { margin-inline-start: 6px; }
@@ -166,13 +166,13 @@
166
166
  @container panel (max-width: 760px) { .wb-fb-body { grid-template-columns: 1fr; grid-template-rows: 1fr auto; } .wb-fb-detail { max-height: 50%; border-inline-start: 0 !important; border-top: 1px solid var(--c-line-soft); } }
167
167
  .wb-fb-list { margin: 0; padding: 6px; overflow-y: auto; list-style: none; scrollbar-width: thin; scrollbar-color: var(--c-line) transparent; outline: none; }
168
168
  .wb-fb-list:focus-visible { box-shadow: inset 0 0 0 2px var(--c-accent); }
169
- .wb-fb-list li[role=option] { display: grid; grid-template-columns: 1fr auto; grid-template-areas: 'name tags' 'meta tags'; align-items: center; column-gap: 10px; padding: 8px 12px; border-radius: 10px; cursor: pointer; }
169
+ .wb-fb-list li[role=option] { display: grid; grid-template-columns: 1fr auto; grid-template-areas: 'name tags' 'meta tags'; align-items: center; column-gap: 10px; padding: 8px 12px; border-radius: calc(10px * var(--bt-rs, 1)); cursor: pointer; }
170
170
  .wb-fb-list li.is-active { background: var(--c-field); }
171
171
  .wb-fb-list li.is-on { background: var(--c-accent-soft); box-shadow: inset 0 0 0 1px rgb(var(--c-accent-rgb) / .45); }
172
172
  .wb-fb-name { grid-area: name; overflow: hidden; font-size: 21px; line-height: 1.3; white-space: nowrap; text-overflow: ellipsis; transition: opacity .2s; }
173
173
  .wb-fb-name.is-loading { opacity: .45; }
174
174
  .wb-fb-meta { grid-area: meta; color: var(--c-muted); font-size: 11px; }
175
- .wb-fb-tag { grid-area: tags; justify-self: end; padding: 2px 7px; border-radius: 6px; background: var(--c-field); color: var(--c-muted); font: 600 10px/1.4 var(--f-mono); letter-spacing: .02em; }
175
+ .wb-fb-tag { grid-area: tags; justify-self: end; padding: 2px 7px; border-radius: calc(6px * var(--bt-rs, 1)); background: var(--c-field); color: var(--c-muted); font: 600 10px/1.4 var(--f-mono); letter-spacing: .02em; }
176
176
  .wb-fb-tag.is-accent { background: var(--c-accent-soft); color: var(--c-accent); }
177
177
  .wb-fb-tag + .wb-fb-tag { grid-row: 2; }
178
178
  .wb-fb-empty { padding: 30px 14px; color: var(--c-muted); text-align: center; }
@@ -183,34 +183,34 @@
183
183
  .wb-fb-hero { display: flex; flex-direction: column; gap: 4px; }
184
184
  .wb-fb-hero-name { font-size: 40px; line-height: 1.1; letter-spacing: -.01em; overflow-wrap: anywhere; }
185
185
  .wb-fb-hero-meta { color: var(--c-muted); font-size: 12px; }
186
- .wb-fb-sample { display: flex; flex-direction: column; gap: 8px; padding: 14px; border: 1px solid var(--c-line-soft); border-radius: 12px; background: var(--c-card); }
186
+ .wb-fb-sample { display: flex; flex-direction: column; gap: 8px; padding: 14px; border: 1px solid var(--c-line-soft); border-radius: calc(12px * var(--bt-rs, 1)); background: var(--c-card); }
187
187
  .wb-fb-sample-text { min-height: 2.6em; font-size: 24px; line-height: 1.3; overflow-wrap: anywhere; }
188
- .wb-fb-sample textarea { width: 100%; padding: 7px 9px; border: 1px solid var(--c-line); border-radius: 8px; background: var(--c-field); color: var(--c-text); font: 12.5px/1.45 var(--f-ui); resize: none; }
188
+ .wb-fb-sample textarea { width: 100%; padding: 7px 9px; border: 1px solid var(--c-line); border-radius: calc(8px * var(--bt-rs, 1)); background: var(--c-field); color: var(--c-text); font: 12.5px/1.45 var(--f-ui); resize: none; }
189
189
  .wb-fb-sample textarea:focus { outline: none; border-color: var(--c-accent); }
190
190
  .wb-fb-samples { display: flex; flex-wrap: wrap; gap: 4px; }
191
- .wb-fb-samples button { height: 24px; padding: 0 9px; border: 1px solid var(--c-line); border-radius: 999px; background: transparent; color: var(--c-muted); font-size: 11px; cursor: pointer; }
191
+ .wb-fb-samples button { height: 24px; padding: 0 9px; border: 1px solid var(--c-line); border-radius: calc(999px * var(--bt-rs, 1)); background: transparent; color: var(--c-muted); font-size: 11px; cursor: pointer; }
192
192
  .wb-fb-samples button.is-on, .wb-fb-samples button:hover { color: var(--c-text); border-color: var(--c-line-strong); }
193
193
  .wb-fb-field { display: flex; flex-direction: column; gap: 8px; }
194
194
  .wb-fb-field > span { color: var(--c-muted); font-size: 11.5px; font-weight: 600; letter-spacing: .02em; text-transform: uppercase; }
195
195
  .wb-fb-field > small { color: var(--c-faint); font-size: 11.5px; line-height: 1.45; }
196
196
  .wb-fb-range { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
197
197
  .wb-fb-weights { display: flex; flex-wrap: wrap; gap: 4px; }
198
- .wb-fb-weights button { min-width: 44px; height: 28px; padding: 0 9px; border: 1px solid var(--c-line); border-radius: 8px; background: var(--c-field); color: var(--c-muted); font: 500 11.5px/1 var(--f-mono); cursor: pointer; }
198
+ .wb-fb-weights button { min-width: 44px; height: 28px; padding: 0 9px; border: 1px solid var(--c-line); border-radius: calc(8px * var(--bt-rs, 1)); background: var(--c-field); color: var(--c-muted); font: 500 11.5px/1 var(--f-mono); cursor: pointer; }
199
199
  .wb-fb-weights button.is-on { border-color: var(--c-accent); background: var(--c-accent-soft); color: var(--c-text); }
200
200
  .wb-fb-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: auto; padding-top: 4px; }
201
- .wb-fb-error { margin: 0; padding: 8px 10px; border-radius: 8px; background: color-mix(in srgb, var(--c-danger) 12%, transparent); color: var(--c-danger); font-size: 12px; }
201
+ .wb-fb-error { margin: 0; padding: 8px 10px; border-radius: calc(8px * var(--bt-rs, 1)); background: color-mix(in srgb, var(--c-danger) 12%, transparent); color: var(--c-danger); font-size: 12px; }
202
202
 
203
203
  .wb-fb-system { flex: 1; overflow-y: auto; padding: 16px 20px; }
204
204
  .wb-fb-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 10px; margin: 0 0 18px; padding: 0; list-style: none; }
205
- .wb-fb-grid button { display: flex; flex-direction: column; gap: 4px; width: 100%; padding: 14px; border: 1px solid var(--c-line-soft); border-radius: 12px; background: var(--c-ground); color: var(--c-text); text-align: start; cursor: pointer; }
205
+ .wb-fb-grid button { display: flex; flex-direction: column; gap: 4px; width: 100%; padding: 14px; border: 1px solid var(--c-line-soft); border-radius: calc(12px * var(--bt-rs, 1)); background: var(--c-ground); color: var(--c-text); text-align: start; cursor: pointer; }
206
206
  .wb-fb-grid button:hover { border-color: var(--c-line-strong); }
207
207
  .wb-fb-grid button.is-on { border-color: var(--c-accent); box-shadow: 0 0 0 1px var(--c-accent); }
208
208
  .wb-fb-grid span { font-size: 18px; }
209
209
  .wb-fb-grid small { color: var(--c-muted); font-size: 12px; }
210
- .wb-fb-custom { display: flex; align-items: flex-end; gap: 10px; padding: 14px; border: 1px dashed var(--c-line-strong); border-radius: 12px; }
210
+ .wb-fb-custom { display: flex; align-items: flex-end; gap: 10px; padding: 14px; border: 1px dashed var(--c-line-strong); border-radius: calc(12px * var(--bt-rs, 1)); }
211
211
  .wb-fb-custom label { flex: 1; display: flex; flex-direction: column; gap: 6px; }
212
212
  .wb-fb-custom label span { color: var(--c-muted); font-size: 12px; }
213
- .wb-fb-custom input { height: 34px; padding: 0 10px; border: 1px solid var(--c-line); border-radius: 9px; background: var(--c-field); color: var(--c-text); font-size: 15px; }
213
+ .wb-fb-custom input { height: 34px; padding: 0 10px; border: 1px solid var(--c-line); border-radius: calc(9px * var(--bt-rs, 1)); background: var(--c-field); color: var(--c-text); font-size: 15px; }
214
214
  .wb-fb-custom input:focus { outline: none; border-color: var(--c-accent); }
215
215
 
216
216
  /* ---------------------------------------------------------------------------------------------
@@ -230,3 +230,11 @@
230
230
  .wb-fontlist b { font-size: 17px; font-weight: 450; }
231
231
  .wb-fontlist small { color: var(--c-muted); font: 11px/1.4 var(--f-mono); }
232
232
  .wb-fontlist ul { margin: 0; padding: 0; list-style: none; }
233
+
234
+ /* The theme doctor's full report (advice only). */
235
+ .bt-doctor { margin: 0; padding: 0; list-style: none; display: grid; gap: 2px; }
236
+ .bt-doctor-item { display: grid; gap: 3px; padding: 9px 2px; }
237
+ .bt-doctor-item + .bt-doctor-item { border-block-start: 1px solid var(--c-line-soft); }
238
+ .bt-doctor-head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
239
+ .bt-doctor-line { color: var(--c-muted); font-size: 12px; line-height: 1.45; overflow-wrap: anywhere; }
240
+ .bt-doctor-line code { font: 11.5px/1.4 var(--f-mono); color: var(--c-text); }