@webship.co/workspace 0.0.0-stage → 1.0.0-alpha1

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 (311) hide show
  1. package/PLUGINS.md +249 -0
  2. package/README.md +379 -2
  3. package/WORKSPACES.md +69 -0
  4. package/actions/ai-items.js +293 -0
  5. package/actions/audio.js +48 -0
  6. package/actions/build-order.js +79 -0
  7. package/actions/checks.js +95 -0
  8. package/actions/claude-session.js +104 -0
  9. package/actions/commands-center.js +258 -0
  10. package/actions/composer.js +267 -0
  11. package/actions/ddev-addons.js +90 -0
  12. package/actions/ddev-mail.js +80 -0
  13. package/actions/ddev.js +237 -0
  14. package/actions/folders.js +165 -0
  15. package/actions/graphs.js +107 -0
  16. package/actions/images-svg.js +33 -0
  17. package/actions/images.js +145 -0
  18. package/actions/navigation.js +190 -0
  19. package/actions/node.js +222 -0
  20. package/actions/notes.js +55 -0
  21. package/actions/obsidian.js +59 -0
  22. package/actions/plugins.js +36 -0
  23. package/actions/project-make.js +28 -0
  24. package/actions/rag.js +202 -0
  25. package/actions/row-actions.js +262 -0
  26. package/actions/screens.js +270 -0
  27. package/actions/testing.js +121 -0
  28. package/actions/video-edit.js +54 -0
  29. package/ai-inventory.js +182 -0
  30. package/ai-items.js +202 -0
  31. package/assistant-cast.js +176 -0
  32. package/assistant-suggestions.js +91 -0
  33. package/assistant.js +484 -0
  34. package/audio-voice.js +528 -0
  35. package/auth-google.js +260 -0
  36. package/build-ai.js +52 -0
  37. package/build-order-page.js +191 -0
  38. package/build-order.js +313 -0
  39. package/builder-args.js +117 -0
  40. package/changelog.js +302 -0
  41. package/chat-threads.js +134 -0
  42. package/checks.js +105 -0
  43. package/claude-args.js +282 -0
  44. package/claude-sessions.js +129 -0
  45. package/command-author.js +117 -0
  46. package/command-block.js +20 -0
  47. package/commands/defaults.yml +159 -0
  48. package/commands-page.js +460 -0
  49. package/commands.js +568 -0
  50. package/cucumber.js +43 -0
  51. package/ddev-addons-page.js +124 -0
  52. package/ddev-addons.js +154 -0
  53. package/ddev-custom-commands.js +96 -0
  54. package/ddev-domains.js +84 -0
  55. package/ddev-import.js +93 -0
  56. package/ddev-mail-page.js +167 -0
  57. package/ddev-mail.js +110 -0
  58. package/ddev-options.js +145 -0
  59. package/ddev-snapshots.js +126 -0
  60. package/ddev.js +145 -0
  61. package/docs/DEVELOPER-GUIDE.md +224 -0
  62. package/docs/README.md +252 -0
  63. package/docs/REFERENCE.md +684 -0
  64. package/docs/USER-GUIDE.md +697 -0
  65. package/docs/screenshots/assistant/avatar-board.png +0 -0
  66. package/docs/screenshots/assistant/avatar-coffee.png +0 -0
  67. package/docs/screenshots/assistant/avatar-colours.png +0 -0
  68. package/docs/screenshots/assistant/avatar-icons.png +0 -0
  69. package/docs/screenshots/assistant/avatar-moves.png +0 -0
  70. package/docs/screenshots/assistant/avatar-pick-cookieman.png +0 -0
  71. package/docs/screenshots/assistant/avatar-settings-voices.png +0 -0
  72. package/docs/screenshots/assistant/avatar-status.png +0 -0
  73. package/docs/screenshots/create-card/create-card.png +0 -0
  74. package/docs/screenshots/create-card/start-ai.png +0 -0
  75. package/docs/screenshots/guide-clone-address.png +0 -0
  76. package/docs/screenshots/guide-clone-worktree-off.png +0 -0
  77. package/docs/screenshots/guide-clone-worktree-on.png +0 -0
  78. package/docs/screenshots/guide-ddev-menu.png +0 -0
  79. package/docs/screenshots/guide-domains.png +0 -0
  80. package/docs/screenshots/guide-home.png +0 -0
  81. package/docs/screenshots/guide-ide-menu.png +0 -0
  82. package/docs/screenshots/guide-remove-refusal.png +0 -0
  83. package/docs/screenshots/guide-rows-worktree.png +0 -0
  84. package/docs/screenshots/guide-screens.png +0 -0
  85. package/docs/screenshots/guide-settings.png +0 -0
  86. package/docs/screenshots/guide-workspace-page.png +0 -0
  87. package/docs/screenshots/home/card-settings.png +0 -0
  88. package/docs/screenshots/home/drag-reorder.png +0 -0
  89. package/docs/screenshots/home/edit-card.png +0 -0
  90. package/docs/screenshots/images/claude-code-image.png +0 -0
  91. package/docs/screenshots/images/edit-with-ai.png +0 -0
  92. package/docs/screenshots/images/gallery.png +0 -0
  93. package/docs/screenshots/images/generate-description.png +0 -0
  94. package/docs/screenshots/images/generate-plan.png +0 -0
  95. package/docs/screenshots/images/providers.png +0 -0
  96. package/docs/screenshots/images/svg-form.png +0 -0
  97. package/docs/screenshots/images/svg-result.png +0 -0
  98. package/docs/screenshots/images/upload.png +0 -0
  99. package/docs/screenshots/new-folder/2-form.png +0 -0
  100. package/docs/screenshots/new-folder/8-ddev-options.png +0 -0
  101. package/docs/screenshots/new-folder/files-folder.png +0 -0
  102. package/docs/screenshots/projects/ddev-custom-commands.png +0 -0
  103. package/docs/screenshots/projects/state-chip-stop.png +0 -0
  104. package/docs/screenshots/readme/dashboard-dark.png +0 -0
  105. package/docs/screenshots/readme/dashboard-light.png +0 -0
  106. package/docs/screenshots/readme/dev-dark.png +0 -0
  107. package/docs/screenshots/readme/dev-light.png +0 -0
  108. package/docs/screenshots/readme/docs-dark.png +0 -0
  109. package/docs/screenshots/readme/docs-light.png +0 -0
  110. package/docs/screenshots/readme/icons/ai.svg +4 -0
  111. package/docs/screenshots/readme/icons/code.svg +4 -0
  112. package/docs/screenshots/readme/icons/docs.svg +4 -0
  113. package/docs/screenshots/readme/icons/services.svg +4 -0
  114. package/docs/screenshots/readme/icons/sites.svg +4 -0
  115. package/docs/screenshots/readme/test-report-dark.png +0 -0
  116. package/docs/screenshots/readme/test-report-light.png +0 -0
  117. package/docs/screenshots/readme/test-scenarios.png +0 -0
  118. package/docs/screenshots/readme/test-steps.png +0 -0
  119. package/docs/screenshots/readme/videos-dark.png +0 -0
  120. package/docs/screenshots/readme/videos-light.png +0 -0
  121. package/docs/screenshots/screens-menu.png +0 -0
  122. package/docs/screenshots/screens-new.png +0 -0
  123. package/docs/screenshots/settings/auth-google-lists.png +0 -0
  124. package/docs/screenshots/settings/delete-1-row.png +0 -0
  125. package/docs/screenshots/settings/delete-2-confirm.png +0 -0
  126. package/docs/screenshots/settings/delete-3-type-name.png +0 -0
  127. package/docs/screenshots/settings/delete-4-type-yes.png +0 -0
  128. package/event-log.js +59 -0
  129. package/files-browser.js +247 -0
  130. package/folders.js +121 -0
  131. package/format.js +73 -0
  132. package/git-info.js +81 -0
  133. package/github-auth.js +62 -0
  134. package/graphs.js +104 -0
  135. package/html.js +10 -0
  136. package/icons.js +83 -0
  137. package/images-ai.js +447 -0
  138. package/images-create.js +226 -0
  139. package/images-page.js +417 -0
  140. package/images-records.js +39 -0
  141. package/images-render.js +36 -0
  142. package/images-svg-core.js +214 -0
  143. package/images-svg-sanitize.js +188 -0
  144. package/images-svg.js +241 -0
  145. package/images.js +270 -0
  146. package/jobs.js +430 -0
  147. package/linger.js +21 -0
  148. package/lists.js +292 -0
  149. package/media-info.js +136 -0
  150. package/navigation-page.js +264 -0
  151. package/navigation.js +394 -0
  152. package/notes.js +0 -0
  153. package/obsidian.js +405 -0
  154. package/package.json +47 -4
  155. package/palette-index.js +46 -0
  156. package/playwright.config.js +23 -0
  157. package/plugin-install.js +239 -0
  158. package/plugin-loader.js +448 -0
  159. package/plugin-manifest.js +83 -0
  160. package/plugin-settings.js +44 -0
  161. package/plugins/workspace-notes/index.js +59 -0
  162. package/plugins-page.js +103 -0
  163. package/project-make.js +241 -0
  164. package/project-name.js +210 -0
  165. package/public/assistant-avatar-moves.js +646 -0
  166. package/public/assistant-avatar-ui.js +1223 -0
  167. package/public/assistant-avatar.js +530 -0
  168. package/public/avatar/druplicon.vrm +0 -0
  169. package/public/css/base.css +260 -0
  170. package/public/css/components/ai-assistant.css +263 -0
  171. package/public/css/components/assistant-avatar.css +279 -0
  172. package/public/css/components/assistant-views.css +198 -0
  173. package/public/css/components/build-order.css +74 -0
  174. package/public/css/components/clone-as-a-git-worktree.css +30 -0
  175. package/public/css/components/command-palette.css +61 -0
  176. package/public/css/components/commands-center.css +139 -0
  177. package/public/css/components/confirm-dialog.css +40 -0
  178. package/public/css/components/create-card.css +8 -0
  179. package/public/css/components/ddev-menu.css +23 -0
  180. package/public/css/components/deep-chat.css +17 -0
  181. package/public/css/components/density.css +11 -0
  182. package/public/css/components/floating-assistant-workspace-pages.css +55 -0
  183. package/public/css/components/folders.css +5 -0
  184. package/public/css/components/full-height-left-assistant-sidebar-all-pages.css +39 -0
  185. package/public/css/components/full-height-right-actions-rail-pages-that-have-actions.css +267 -0
  186. package/public/css/components/grid-and-list.css +203 -0
  187. package/public/css/components/help-page.css +36 -0
  188. package/public/css/components/hero.css +16 -0
  189. package/public/css/components/images.css +70 -0
  190. package/public/css/components/layout-rhythm-and-spacing-polish.css +139 -0
  191. package/public/css/components/mic-on-any-field-that-wants-typing.css +416 -0
  192. package/public/css/components/modal-fit.css +113 -0
  193. package/public/css/components/page-width.css +23 -0
  194. package/public/css/components/plugins-page.css +9 -0
  195. package/public/css/components/project-cards.css +256 -0
  196. package/public/css/components/project-rows.css +390 -0
  197. package/public/css/components/reordering-the-workspace-cards.css +85 -0
  198. package/public/css/components/responsive-home-and-the-workspace-pages.css +457 -0
  199. package/public/css/components/running-commands-as-toasts.css +327 -0
  200. package/public/css/components/settings-editor-core-config-yml.css +828 -0
  201. package/public/css/components/short-results-as-a-toast-at-the-top.css +203 -0
  202. package/public/css/components/sound-player.css +84 -0
  203. package/public/css/components/suggest-agents-skills-commands.css +112 -0
  204. package/public/css/components/tab-modal.css +142 -0
  205. package/public/css/components/terminals.css +244 -0
  206. package/public/css/components/test-report-media-one-file-per-row-paged.css +144 -0
  207. package/public/css/components/top-toolbar-logo-breadcrumb.css +107 -0
  208. package/public/css/components/video-edit.css +6 -0
  209. package/public/css/components/web-ide-open-with.css +23 -0
  210. package/public/css/components/working-indicators.css +158 -0
  211. package/public/css/components/workspace-cards.css +79 -0
  212. package/public/favicon.ico +0 -0
  213. package/public/favicon.png +0 -0
  214. package/public/htmx.min.js +1 -0
  215. package/public/logo.png +0 -0
  216. package/public/media-player.js +215 -0
  217. package/public/mic.js +255 -0
  218. package/public/narration.js +110 -0
  219. package/public/palette.js +278 -0
  220. package/public/sortable.js +251 -0
  221. package/public/suggest.js +337 -0
  222. package/public/terminal.js +903 -0
  223. package/public/themes/default/theme.css +267 -0
  224. package/public/themes/ember/theme.css +180 -0
  225. package/public/themes/press/theme.css +290 -0
  226. package/public/themes/slate/theme.css +182 -0
  227. package/public/touch-icon.png +0 -0
  228. package/public/ui.js +2721 -0
  229. package/public/vendor/ace/LICENSE.txt +24 -0
  230. package/public/vendor/ace/ace.js +23 -0
  231. package/public/vendor/ace/ext-language_tools.js +8 -0
  232. package/public/vendor/ace/ext-searchbox.js +8 -0
  233. package/public/vendor/ace/mode-css.js +8 -0
  234. package/public/vendor/ace/mode-html.js +8 -0
  235. package/public/vendor/ace/mode-javascript.js +8 -0
  236. package/public/vendor/ace/mode-markdown.js +8 -0
  237. package/public/vendor/ace/mode-sh.js +8 -0
  238. package/public/vendor/ace/mode-yaml.js +8 -0
  239. package/public/vendor/ace/theme-textmate.js +8 -0
  240. package/public/vendor/ace/theme-tomorrow_night.js +8 -0
  241. package/public/vendor/ace/worker-yaml.js +1 -0
  242. package/public/vendor/deep-chat.LICENSE +21 -0
  243. package/public/vendor/deep-chat.bundle.js +1 -0
  244. package/public/vendor/fonts/dm-serif-text-LICENSE.txt +93 -0
  245. package/public/vendor/fonts/dm-serif-text-latin-400-italic.woff2 +0 -0
  246. package/public/vendor/fonts/dm-serif-text-latin-400-normal.woff2 +0 -0
  247. package/public/vendor/fonts/figtree-LICENSE.txt +93 -0
  248. package/public/vendor/fonts/figtree-latin-400-italic.woff2 +0 -0
  249. package/public/vendor/fonts/figtree-latin-400-normal.woff2 +0 -0
  250. package/public/vendor/fonts/figtree-latin-600-italic.woff2 +0 -0
  251. package/public/vendor/fonts/figtree-latin-600-normal.woff2 +0 -0
  252. package/public/vendor/fonts/jetbrains-mono-LICENSE.txt +93 -0
  253. package/public/vendor/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
  254. package/public/vendor/fonts/ubuntu-mono-LICENSE.txt +98 -0
  255. package/public/vendor/fonts/ubuntu-mono-latin-wght-normal.woff2 +0 -0
  256. package/public/vendor/tabler-LICENSE.txt +21 -0
  257. package/public/vendor/tabler-sprite.svg +1 -0
  258. package/public/vendor/three/LICENSE +21 -0
  259. package/public/vendor/three/addons/loaders/GLTFLoader.js +4860 -0
  260. package/public/vendor/three/addons/utils/BufferGeometryUtils.js +1501 -0
  261. package/public/vendor/three/addons/utils/SkeletonUtils.js +496 -0
  262. package/public/vendor/three/three.core.min.js +6 -0
  263. package/public/vendor/three/three.module.min.js +6 -0
  264. package/public/vendor/three-vrm/LICENSE +22 -0
  265. package/public/vendor/three-vrm/three-vrm.module.min.js +983 -0
  266. package/public/vendor/uikit-icons.min.js +1 -0
  267. package/public/vendor/uikit.min.css +1 -0
  268. package/public/vendor/uikit.min.js +1 -0
  269. package/public/vendor/webship-icons/ui/bookmark-outlined.svg +1 -0
  270. package/public/vendor/webship-icons/ui/headset.svg +1 -0
  271. package/public/vendor/webship-icons/ui/menu.svg +1 -0
  272. package/public/vendor/webship-icons/ui/pause.svg +1 -0
  273. package/public/vendor/webship-icons/ui/play.svg +1 -0
  274. package/public/vendor/webship-icons/ui/share.svg +1 -0
  275. package/public/vendor/webship-icons/ui/sound-off.svg +1 -0
  276. package/public/vendor/webship-icons/ui/sound-on.svg +1 -0
  277. package/public/vendor/webship-icons/ui/stop.svg +1 -0
  278. package/public/vendor/xterm/addon-fit.js +2 -0
  279. package/public/vendor/xterm/addon-unicode11.js +2 -0
  280. package/public/vendor/xterm/addon-web-links.js +2 -0
  281. package/public/vendor/xterm/xterm.css +218 -0
  282. package/public/vendor/xterm/xterm.js +2 -0
  283. package/rag.js +169 -0
  284. package/reader.js +132 -0
  285. package/repo-blobs.js +66 -0
  286. package/report-pdf.js +337 -0
  287. package/row-layout.js +1440 -0
  288. package/screenrc +37 -0
  289. package/screens.js +1063 -0
  290. package/scripts/code-server-start.sh +190 -0
  291. package/scripts/images-claude-finish.js +107 -0
  292. package/scripts/screen-host.sh +114 -0
  293. package/scripts/screen-runner.js +362 -0
  294. package/scripts/screen-then.js +47 -0
  295. package/scripts/update.sh +350 -0
  296. package/server.js +8140 -0
  297. package/settings-file.js +423 -0
  298. package/speech.js +297 -0
  299. package/tab-modal.js +75 -0
  300. package/test-reports.js +260 -0
  301. package/themes.js +381 -0
  302. package/transcripts.js +168 -0
  303. package/urls.js +13 -0
  304. package/validate.js +18 -0
  305. package/version-check.js +94 -0
  306. package/video-edit.js +588 -0
  307. package/waiting.js +85 -0
  308. package/workspace-card-tools.js +41 -0
  309. package/workspace-delete.js +139 -0
  310. package/workspace-types.js +85 -0
  311. package/workspaces.js +557 -0
@@ -0,0 +1,828 @@
1
+ /* ---------- Settings editor (core/config/*.yml) ---------- */
2
+
3
+ .settings-tabs { display: flex; flex-wrap: wrap; gap:0.375rem; margin-bottom:0.25rem; }
4
+ .settings-tab { border-radius:31.25rem; }
5
+
6
+ .settings-row {
7
+ display: grid;
8
+ grid-template-columns: minmax(14rem, 38%) 1fr;
9
+ align-items: start;
10
+ gap:0.75rem;
11
+ padding:0.4375rem 0;
12
+ border-bottom: 1px solid var(--line);
13
+ }
14
+
15
+ .settings-row:last-of-type { border-bottom: 0; }
16
+
17
+ /* A row with no hint still reads as one line: only the taller ones need the offset. */
18
+ .settings-row > .uk-input,
19
+ .settings-row > .image-field,
20
+ .settings-row > .settings-toggle { margin-top: 0.0625rem; }
21
+
22
+ .settings-key {
23
+ font-family: var(--font-code);
24
+ font-size: .8rem;
25
+ color: var(--text-subtle);
26
+ word-break: break-all;
27
+ }
28
+
29
+ /* A secret is marked, because the field being empty is meaningful here: it means
30
+ "unchanged", not "no value". */
31
+ .settings-key .uk-label {
32
+ background: var(--subtle-accent);
33
+ /* Not --brand-secondary: that navy does not flip, and on the dark accent tint it was 1.7:1. */
34
+ color: var(--accent-ink);
35
+ margin-left:0.25rem;
36
+ }
37
+
38
+ .settings-readonly { font-size: .8rem; }
39
+
40
+ @media (max-width: 39.9375em) {
41
+ .settings-row { grid-template-columns: 1fr; gap:0.25rem; }
42
+ }
43
+
44
+ /* The workspaces list in the settings editor: what exists, and the card order. */
45
+ .settings-row-block { align-items: start; }
46
+
47
+ .ws-list {
48
+ list-style: none;
49
+ margin:0 0 0.625rem;
50
+ padding: 0;
51
+ border: 1px solid var(--line);
52
+ border-radius:0.5rem;
53
+ overflow: hidden;
54
+ }
55
+
56
+ .ws-item {
57
+ display: flex;
58
+ align-items: center;
59
+ gap:0.5rem;
60
+ padding:0.3125rem 0.5rem;
61
+ background: var(--surface-card);
62
+ border-bottom: 1px solid var(--line);
63
+ font-size: .84rem;
64
+ }
65
+
66
+ .ws-item:last-child { border-bottom: 0; }
67
+ html.dark .ws-item { background: transparent; }
68
+
69
+ .ws-order {
70
+ min-width:1.375rem;
71
+ font-family: var(--font-code);
72
+ font-size: .72rem;
73
+ color: var(--text-subtle);
74
+ text-align: right;
75
+ }
76
+
77
+ .ws-name { font-weight: 600; }
78
+ .ws-move { margin-left: auto; display: flex; gap:0.25rem; }
79
+ .ws-move .uk-button { border-radius:0.375rem; padding:0 0.4375rem; line-height:1.5rem; }
80
+ .ws-item { flex-wrap: wrap; }
81
+ .ws-in-use { font-size: .72rem; padding: 0 0.25rem; cursor: help; align-self: center; }
82
+ .ws-move .ws-delete:not(:hover, :focus-visible) { color: var(--text-subtle); }
83
+ .ws-move .ws-delete:is(:hover, :focus-visible) { color: var(--danger); border-color: var(--danger); }
84
+ .ws-delete-step {
85
+ flex-basis: 100%;
86
+ margin: 0.25rem 0 0.25rem 1.875rem;
87
+ padding: 0.5rem 0.75rem;
88
+ border: 1px solid var(--danger);
89
+ border-radius: 0.5rem;
90
+ background: var(--danger-soft);
91
+ }
92
+ .ws-delete-step p { margin: 0 0 0.375rem; }
93
+ .ws-delete-step .ws-delete-error { color: var(--danger-text); font-weight: 600; }
94
+ .ws-delete-fields { display: flex; gap: 0.5rem; align-items: center; }
95
+ .ws-delete-fields .uk-input { max-width: 12.5rem; background: var(--surface-card); }
96
+ .ws-add { display: flex; gap:0.5rem; align-items: center; }
97
+ .ws-add .uk-input { max-width:16.25rem; }
98
+
99
+ /* Drag handle for the workspaces list. */
100
+ .ws-handle {
101
+ cursor: grab;
102
+ /* The same story as .row-handle: quiet by colour, not by fading the token that is already quiet. */
103
+ color: var(--line-strong);
104
+ padding:0 0.125rem;
105
+ }
106
+
107
+ .ws-item:hover .ws-handle { opacity: 1; color: var(--brand-primary); }
108
+ .ws-list > .uk-sortable-drag { background: var(--subtle-brand); }
109
+ .ws-list > .uk-sortable-placeholder { opacity: .35; }
110
+
111
+ /* A set, not a sequence: modules, themes, profiles, site_templates. No handles or
112
+ arrows, and a scroller once the list is long — themes: is 436 entries. */
113
+ .set-list {
114
+ list-style: none;
115
+ margin:0 0 0.625rem;
116
+ padding:0.375rem 0.5rem;
117
+ border: 1px solid var(--line);
118
+ border-radius:0.5rem;
119
+ display: flex;
120
+ flex-wrap: wrap;
121
+ gap:0.25rem 0.375rem;
122
+ }
123
+
124
+ .set-list-scroll { max-height:16.25rem; overflow-y: auto; }
125
+
126
+ .set-item {
127
+ font-family: var(--font-code);
128
+ font-size: .74rem;
129
+ background: var(--subtle-light);
130
+ border: 1px solid var(--line);
131
+ border-radius:0.25rem;
132
+ padding:0.0625rem 0.375rem;
133
+ color: var(--text-subtle);
134
+ }
135
+
136
+ html.dark .set-item { background: transparent; }
137
+
138
+ /* Icon + description for a workspace, with a live preview of the card line. */
139
+ .pres-preview {
140
+ display: flex;
141
+ align-items: center;
142
+ gap:0.625rem;
143
+ padding:0.5rem 0.625rem;
144
+ margin-bottom:0.5rem;
145
+ border: 1px solid var(--line);
146
+ border-radius:0.5rem;
147
+ background: var(--subtle-light);
148
+ }
149
+
150
+ html.dark .pres-preview { background: transparent; }
151
+
152
+ .pres-icon {
153
+ width:2.375rem;
154
+ height:2.375rem;
155
+ min-width:2.375rem;
156
+ border-radius:0.625rem;
157
+ background: var(--subtle-brand);
158
+ color: var(--brand-primary);
159
+ display: inline-flex;
160
+ align-items: center;
161
+ justify-content: center;
162
+ }
163
+
164
+ .pres-fields { display: flex; flex-wrap: wrap; gap:0.5rem; align-items: center; }
165
+ .pres-fields .uk-select { max-width:9.375rem; }
166
+ .pres-fields .uk-input { max-width:20rem; }
167
+
168
+ /* Icon chooser: the icons themselves, not their names. Radios so it works by
169
+ keyboard and needs no JavaScript — :checked + label does the highlighting. */
170
+ .icon-grid {
171
+ display: flex;
172
+ flex-wrap: wrap;
173
+ gap:0.375rem;
174
+ margin-bottom:0.625rem;
175
+ }
176
+
177
+ .icon-radio {
178
+ position: absolute;
179
+ opacity: 0;
180
+ width: 0;
181
+ height: 0;
182
+ }
183
+
184
+ .icon-choice {
185
+ width:2.125rem;
186
+ height:2.125rem;
187
+ border: 1px solid var(--line);
188
+ border-radius:0.5rem;
189
+ display: inline-flex;
190
+ align-items: center;
191
+ justify-content: center;
192
+ cursor: pointer;
193
+ color: var(--text-subtle);
194
+ background: var(--surface-card);
195
+ transition: border-color .15s, color .15s, background .15s;
196
+ }
197
+
198
+ .icon-choice:hover { border-color: var(--brand-primary); color: var(--brand-primary); }
199
+
200
+ .icon-radio:checked + .icon-choice {
201
+ border-color: var(--brand-primary);
202
+ background: var(--subtle-brand);
203
+ color: var(--brand-primary);
204
+ box-shadow: 0 0 0 2px var(--subtle-brand);
205
+ }
206
+
207
+ /* Keyboard focus has to be visible, since the input itself is off-screen. */
208
+ .icon-radio:focus-visible + .icon-choice {
209
+ outline: 2px solid var(--brand-primary);
210
+ outline-offset: 2px;
211
+ }
212
+
213
+ /* 162 icons: cap the grid and let it scroll, with the filter above it. */
214
+ .icon-filter { max-width:16.25rem; margin-bottom:0.5rem; height:2rem; font-size: .82rem; }
215
+ .icon-grid { max-height:10.5rem; overflow-y: auto; padding:0.125rem; }
216
+
217
+ /* Tabler icons come from the vendored sprite; UIKit's come from uk-icon. Both must
218
+ sit on the same baseline and take the current colour. */
219
+ .tabler-icon { display: inline-block; vertical-align: middle; }
220
+
221
+ .icon-sets { display: flex; flex-direction: column; gap:0.25rem; }
222
+
223
+ .icon-set-label {
224
+ font-size: .7rem;
225
+ font-weight: 700;
226
+ letter-spacing: .06em;
227
+ text-transform: uppercase;
228
+ color: var(--text-subtle);
229
+ }
230
+
231
+ /* The Tabler grid is 2000 tiles, so it gets its own scroller and the filter does
232
+ the real work of finding one. */
233
+ .icon-grid-tabler { max-height:13.125rem; }
234
+
235
+ /* Pager under the icon grid. */
236
+ .icon-pager {
237
+ display: flex;
238
+ align-items: center;
239
+ gap:0.375rem;
240
+ flex-wrap: wrap;
241
+ margin-top:0.375rem;
242
+ }
243
+
244
+ .icon-pager .icon-page {
245
+ border-radius:0.375rem;
246
+ padding:0 0.5625rem;
247
+ line-height:1.625rem;
248
+ min-width:1.875rem;
249
+ }
250
+
251
+ .icon-pager .uk-text-meta { font-size: .74rem; }
252
+
253
+ /* The pager on every list (items, projects, backups, commands). It renders both above
254
+ and below the rows: on a 200-row page the bottom one is where you arrive after
255
+ reading, and the top one is where the size chooser is looked for. */
256
+ .list-pager {
257
+ display: flex;
258
+ align-items: center;
259
+ gap: 0.375rem;
260
+ flex-wrap: wrap;
261
+ margin: 0.5rem 0;
262
+ padding: 0.375rem 0;
263
+ }
264
+
265
+ .list-pager .uk-text-meta { font-size: .78rem; }
266
+
267
+ /* A disabled step renders as a <span>, not a <button>: a button that cannot act still
268
+ takes a tab stop and still looks pressable. It keeps the same footprint so the row
269
+ does not shift as you move between the first and last page. */
270
+ .list-pager [disabled] { opacity: .45; cursor: default; }
271
+
272
+ .list-pager-size {
273
+ display: inline-flex;
274
+ align-items: center;
275
+ gap: 0.375rem;
276
+ margin-right: 0.25rem;
277
+ }
278
+
279
+ /* UIKit's uk-form-small select is sized for a form row; in a pager it only holds
280
+ "20"…"All", so let it shrink to its content instead of stretching the bar. */
281
+ .list-pager-size .uk-select {
282
+ width: auto;
283
+ min-width: 4.5rem;
284
+ }
285
+
286
+ /* Tabler icons come through <use> from an EXTERNAL sprite, and presentation
287
+ attributes on that file's root <svg> do NOT cross into this document — the symbol
288
+ inherits from the host <svg>, which had none, so every icon drew at the default
289
+ 1px stroke and looked thin and wrong. Stating them here fixes every use at once
290
+ and keeps the sprite free of 4736 repetitions. */
291
+ .tabler-icon {
292
+ fill: none;
293
+ stroke: currentColor;
294
+ stroke-width: 1.75;
295
+ stroke-linecap: round;
296
+ stroke-linejoin: round;
297
+ }
298
+
299
+ /* And they are brand-coloured where the old uk-icon carried uk-text-primary: the
300
+ card tile, the page heading and the chooser's preview. Dropping that class when I
301
+ moved to <svg> is why they turned grey. */
302
+ .workspace-card .icon .tabler-icon,
303
+ .page-heading-icon .tabler-icon,
304
+ .pres-icon .tabler-icon { color: var(--brand-primary); }
305
+
306
+ /* A slightly lighter stroke at large sizes: 1.75 reads heavy at 28px+. */
307
+ .workspace-card .icon .tabler-icon,
308
+ .page-heading-icon .tabler-icon { stroke-width: 1.6; }
309
+
310
+ /* Ace, where a file is code. The textarea stays in the DOM (hidden) because it is
311
+ what the form submits — Ace only mirrors into it. */
312
+ .ace-host {
313
+ width: 100%;
314
+ border: 1px solid var(--line);
315
+ border-radius:0.5rem;
316
+ font-size:0.8125rem;
317
+ line-height: 1.5;
318
+ overflow: hidden; /* keep the rounded corners */
319
+ }
320
+ /* A one-line editor (a command line) sizes itself to its line; a read-only one is code to read, with no cursor. */
321
+ .ace-host-single { padding: 0.25rem 0; }
322
+ .ace-host-readonly .ace_cursor-layer { display: none; }
323
+
324
+ /* The gutter, the active line and the rest of the editor's chrome belong to Ace's
325
+ theme — it ships a matched set per theme and overriding one piece from here breaks
326
+ the match. These were keyed on html.dark, so a dark editor forced onto a light page
327
+ (style.editor_theme: dark) got a LIGHT gutter of line numbers beside dark code. Ace
328
+ styles .ace_gutter itself; let it. */
329
+
330
+ /* Ace positions its own scrollbars; the card must not clip the search box. */
331
+ .editor-card { overflow: visible; }
332
+
333
+ /* The editor modal. Wide, because a script needs the width, and tall enough that the
334
+ editor is the page rather than a slot in it. */
335
+ #editor-modal .uk-modal-dialog { width:min(68.75rem, 94vw); }
336
+ #editor-modal .uk-modal-body { padding:1.375rem 1.625rem; }
337
+ #editor-modal .editor-card { box-shadow: none; border: 0; padding: 0; }
338
+ #editor-modal .ace-host:not(.ace-host-single) { height:min(62vh, 38.75rem) !important; }
339
+
340
+ /* The save result sits above the editor, where the eye already is, and is sticky so
341
+ it stays visible while the editor scrolls under it. */
342
+ #editor-result:not(:empty) {
343
+ position: sticky;
344
+ top: 0;
345
+ z-index: 2;
346
+ margin-bottom:0.625rem;
347
+ }
348
+
349
+ #editor-result .msg { margin-top: 0; }
350
+
351
+ /* Toolbar: the Webship wordmark from the brand tokens (Figma nodes 7989-1383
352
+ light / 7989-1385 dark — identical geometry, the wordmark ink is the only
353
+ difference, so the file is swapped rather than the artwork recoloured), then the
354
+ product name on its own. "Webship Workspace" beside a Webship wordmark said Webship
355
+ twice. */
356
+ .toolbar-logo { display: flex; align-items: center; gap:0.625rem; }
357
+
358
+ /* UIKit's .uk-logo sets font-size on the anchor, so the product name needs the extra
359
+ specificity — declared 1rem, it was rendering at UIKit's 20px. */
360
+ .toolbar .toolbar-product {
361
+ font-size:1.0625rem;
362
+ font-weight: 600;
363
+ letter-spacing: -0.01em;
364
+ color: var(--ink);
365
+ /* A hairline divider, so the product name reads as ours rather than as part of
366
+ the wordmark. */
367
+ padding-left:0.6875rem;
368
+ border-left: 1px solid var(--line);
369
+ line-height: 1.1;
370
+ }
371
+
372
+
373
+
374
+ /* The item editor's text area: code, so the code face and a tighter rhythm than body text.
375
+ * Moved here from the default theme's file, where only one of the three themes had it. */
376
+ .editor-card .editor-area {
377
+ font-family: var(--font-code);
378
+ font-size: 0.8125rem;
379
+ line-height: 1.5;
380
+ resize: vertical;
381
+ background: var(--surface-input);
382
+ color: var(--text-subtle-2);
383
+ border-color: var(--line);
384
+ }
385
+
386
+ /* Asking the assistant to change a command, and reviewing what it answered.
387
+ *
388
+ * The proposal is shown as a diff inside the editor rather than applied: a cmd-*.sh is executed by
389
+ * the dashboard and by a terminal, so nothing changes one without a person having read the change. */
390
+ .cmd-ai-row { gap: 0.5rem; }
391
+ .cmd-ai-row .uk-input { flex: 1; }
392
+
393
+ .cmd-proposal { background: var(--busy-soft); border-left: 3px solid var(--link); }
394
+
395
+ .cmd-diff {
396
+ max-height: 18rem;
397
+ overflow: auto;
398
+ margin: 0.5rem 0 0;
399
+ padding: 0.5rem 0.625rem;
400
+ background: var(--code-bg);
401
+ color: var(--code-ink);
402
+ font-family: var(--font-code);
403
+ font-size: 0.72rem;
404
+ line-height: 1.45;
405
+ border-radius: 0.375rem;
406
+ }
407
+
408
+ /* A number is a number.
409
+ *
410
+ * Sized to its content rather than stretched across the row: a millisecond value is four characters
411
+ * and a full-width box for it reads as though something longer belongs there. The unit sits beside
412
+ * it as quiet text — "1 is normal" answers the question a bare spinner leaves open, and answering it
413
+ * in the label would make every label a sentence. */
414
+ .settings-row > .settings-number {
415
+ width: 9rem;
416
+ flex: 0 0 auto;
417
+ }
418
+
419
+ .settings-unit {
420
+ align-self: center;
421
+ margin-inline-start: .6rem;
422
+ color: var(--text-subtle);
423
+ font-size: .82rem;
424
+ }
425
+
426
+ /* The list of voices this browser actually has, under the field that asks for one.
427
+ * Full row width beneath the input rather than beside it: the names are long
428
+ * ("Microsoft Zira - English (United States)") and there can be dozens. */
429
+ .settings-voices {
430
+ flex: 1 0 100%;
431
+ margin-top: .4rem;
432
+ font-size: .82rem;
433
+ line-height: 1.7;
434
+ }
435
+
436
+ .settings-voices .voice-pick {
437
+ font-size: .82rem;
438
+ text-decoration: none;
439
+ }
440
+
441
+ /* A command's own description, from its ARGPARSE_DESCRIPTION.
442
+ *
443
+ * On its own line under the file name rather than beside it: the names are long
444
+ * (cmd-webship11-0-x-project.sh) and the descriptions are sentences, so side by side
445
+ * they wrap into each other and neither is scannable. */
446
+ .cmd-id { display: block; }
447
+
448
+ /* The human name leads, because that is what tells two commands apart:
449
+ * cmd-webship11-0-x-project.sh and cmd-webship11-0-0-project.sh differ by
450
+ * one character in the middle. */
451
+ .cmd-title {
452
+ display: block;
453
+ font-weight: 600;
454
+ }
455
+
456
+ /* The file name still matters — it is what you type — so it stays, in the
457
+ * monospace it will be typed in, one step quieter than the name above it. */
458
+ /* No chip: a filled background here reads as a badge with a meaning, and this is
459
+ * only the file's name. Monospace and one step quieter is enough to mark it as
460
+ * the thing you type. */
461
+ /* A file name, anywhere it appears: settings.yml, a cmd-*.sh, a path.
462
+ *
463
+ * Set as code, because that is what it is — a string you are about to type, and
464
+ * cmd-tools-autostart.sh has more word boundaries than the body font gives it.
465
+ *
466
+ * A NEUTRAL surface, not the brand fill `code` inherits: `--chip-bg` is the
467
+ * primary blue, and a blue pill here reads as a badge with a meaning — a status,
468
+ * a count — rather than as "this is a file name". Grey with a hairline says the
469
+ * same thing quietly, and follows both themes because both are tokens. */
470
+ .fname {
471
+ padding: .05em .35em;
472
+ border: 1px solid var(--line);
473
+ border-radius: .25rem;
474
+ background: var(--surface-muted);
475
+ color: var(--text-subtle-2);
476
+ font-size: .85em;
477
+ white-space: nowrap;
478
+ }
479
+
480
+ .cmd-file {
481
+ display: inline-block;
482
+ margin-top: .2rem;
483
+ font-size: .78rem;
484
+ }
485
+
486
+ /* Inside a tab button the file name inherits the button's own colour, or it
487
+ * disappears against the primary fill when that tab is the active one. */
488
+ /* Inside a button or a heading the pill fights the control it sits in: a tab is
489
+ * already a bordered box, and an h3 does not want a box around half of itself. */
490
+ .settings-tab .fname,
491
+ h3 .fname,
492
+ button .fname {
493
+ padding: 0;
494
+ border: 0;
495
+ background: none;
496
+ color: inherit;
497
+ }
498
+
499
+ /* The instruction box, wide and two lines high: what goes in it is a sentence
500
+ * aimed at an agent, and a single line hides the start of it while you type the
501
+ * end. The mic sits inside the box, so the row stays two controls wide. */
502
+ .cmd-ai-row {
503
+ display: flex;
504
+ align-items: flex-start;
505
+ gap: .5rem;
506
+ }
507
+
508
+ .cmd-ai-input {
509
+ flex: 1 1 auto;
510
+ min-width: 0;
511
+ }
512
+
513
+ .cmd-ai-input .uk-textarea {
514
+ width: 100%;
515
+ min-height: 3.5rem;
516
+ resize: vertical;
517
+ }
518
+
519
+ .cmd-desc {
520
+ display: block;
521
+ margin-top: .15rem;
522
+ color: var(--text-subtle);
523
+ font-size: .84rem;
524
+ line-height: 1.5;
525
+ }
526
+
527
+ /* A command row reads top to bottom: what it is called, what file it is, what it
528
+ * does — then what you can do to it.
529
+ *
530
+ * Side by side, the six action buttons and a full sentence fought for the same
531
+ * line and both lost: the description wrapped into a narrow column while the
532
+ * buttons stacked two-wide. Nothing here needs to be on one line. */
533
+ .cmd-row-stack {
534
+ display: flex;
535
+ flex-direction: column;
536
+ gap: .5rem;
537
+ }
538
+
539
+ .cmd-row-stack .project-actions {
540
+ display: flex;
541
+ flex-wrap: wrap;
542
+ gap: .35rem;
543
+ }
544
+
545
+ /* Settings → Row actions: the buttons, what is inside each menu, and your own actions.
546
+ Its own class names, not .ws-list: ui.js posts a .ws-list drag straight to list-order. */
547
+ .row-actions-section { margin-bottom: 1.5rem; }
548
+ .row-list {
549
+ list-style: none;
550
+ margin: 0 0 0.625rem;
551
+ padding: 0;
552
+ border: 1px solid var(--line);
553
+ border-radius: 0.5rem;
554
+ overflow: hidden;
555
+ }
556
+ .row-item {
557
+ display: flex;
558
+ align-items: center;
559
+ gap: 0.5rem;
560
+ padding: 0.3125rem 0.5rem;
561
+ background: var(--surface-card);
562
+ border-bottom: 1px solid var(--line);
563
+ font-size: .84rem;
564
+ }
565
+ .row-item:last-child { border-bottom: 0; }
566
+ html.dark .row-item { background: transparent; }
567
+ /* Quiet by colour, not by fading a token that was already the quiet one: at 55% the handle was under
568
+ the 3:1 a control needs to be seen at all, on the row you have to find before you can drag it. */
569
+ .row-handle { cursor: grab; color: var(--line-strong); padding: 0 0.125rem; }
570
+ .row-item:hover .row-handle { opacity: 1; color: var(--brand-primary); }
571
+ .row-list > .uk-sortable-drag { background: var(--subtle-brand); }
572
+ .row-list > .uk-sortable-placeholder { opacity: .35; }
573
+ .row-item .row-line { width: auto; margin-left: auto; }
574
+ .row-item .ws-move { margin-left: 0.25rem; }
575
+ .row-group { align-items: flex-start; }
576
+ .row-group-body { flex: 1; min-width: 0; }
577
+ .row-entries { display: flex; flex-wrap: wrap; gap: 0.125rem 0.875rem; margin: 0.25rem 0 0 1.5rem; }
578
+ .row-entry { font-size: .8rem; color: var(--text-subtle); }
579
+ .row-menu { border: 1px solid var(--line); border-radius: 0.5rem; padding: 0.375rem 0.75rem; margin-bottom: 0.5rem; }
580
+ .row-menu > summary { cursor: pointer; }
581
+ .row-menu[open] > summary { margin-bottom: 0.5rem; }
582
+ .row-custom-add { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: 0.5rem; align-items: end; }
583
+ .row-custom-add .row-custom-command { grid-column: 1 / -1; }
584
+ .row-custom-table code { white-space: pre-wrap; word-break: break-all; }
585
+
586
+ /* Rows management: rows of buttons, each button opening onto its groups and commands. */
587
+ .row-actions-tabs { margin-bottom: 1rem; }
588
+ .row-block { border: 1px solid var(--line); border-radius: 0.5rem; padding: 0.5rem 0.625rem; margin-bottom: 0.75rem; }
589
+ .row-block-head { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.375rem; }
590
+ .row-block-tools { margin-left: auto; display: flex; gap: 0.25rem; }
591
+ .row-block-tools .uk-button { border-radius: 0.375rem; padding: 0 0.4375rem; line-height: 1.5rem; }
592
+ .row-block-hidden { border-style: dashed; }
593
+ .row-buttons { min-height: 2.25rem; }
594
+ .row-button { flex-wrap: wrap; align-items: flex-start; }
595
+ .row-button > .row-handle { margin-top: 0.25rem; }
596
+ .row-button-body { flex: 1 1 18rem; min-width: 0; }
597
+ .row-button-body > summary { cursor: pointer; padding: 0.125rem 0; }
598
+ .row-button-body[open] > summary { margin-bottom: 0.375rem; }
599
+ .row-button > .row-move { width: auto; }
600
+ .row-entry-list { list-style: none; margin: 0.25rem 0 0 1.25rem; padding: 0; }
601
+ .row-entry-item { display: flex; flex-wrap: nowrap; align-items: flex-start; gap: 0.25rem 0.5rem; padding: 0.0625rem 0; }
602
+ .row-entry-item > .row-handle { flex: none; }
603
+ .row-entry-item > .row-entry { flex: 1 1 auto; min-width: 0; }
604
+ /* An action of yours carries its edit form below it, so its line may wrap. */
605
+ .row-entry-item:has(> .row-edit) { flex-wrap: wrap; }
606
+ .row-entry-list > .uk-sortable-placeholder { opacity: .35; }
607
+ .row-entry-builtin { font-size: .72rem; flex: none; white-space: nowrap; }
608
+ .row-entry-yours { font-size: .68rem; flex: none; white-space: nowrap; }
609
+ .row-edit { flex-basis: 100%; margin-left: 1.5rem; }
610
+ .row-edit > summary { cursor: pointer; }
611
+ .row-add-action, .row-edit-action { border-top: 1px dashed var(--line); margin-top: 0.5rem; padding-top: 0.5rem; }
612
+ .row-add-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); gap: 0.375rem 0.5rem; align-items: end; margin: 0.25rem 0 0.5rem; }
613
+ .row-add-grid .row-add-steps { grid-column: 1 / -1; }
614
+ .row-add-grid .row-add-check { font-size: .8rem; }
615
+ .row-definition-text { font-family: var(--font-code); font-size: .8rem; }
616
+ .row-definition-buttons { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; margin: 0.5rem 0; }
617
+ .row-definition-buttons .uk-select { width: auto; }
618
+ .row-definition-help pre, .row-activity-log { max-height: 16rem; overflow: auto; font-size: .75rem; }
619
+ .row-options .row-option { display: block; margin-bottom: 0.625rem; }
620
+ .row-columns { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0.5rem; }
621
+ .row-columns-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
622
+ .row-columns-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
623
+ .row-columns-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
624
+ .row-columns > .row-groups { min-height: 2.25rem; }
625
+ .row-columns-pick { display: inline-flex; align-items: center; gap: 0.375rem; margin-bottom: 0.375rem; font-size: .8rem; }
626
+ .row-columns-pick .uk-select { width: auto; min-width: 4rem; }
627
+ @media (max-width: 40em) { .row-columns[class] { grid-template-columns: 1fr; } }
628
+ .row-group-name .uk-text-meta { white-space: nowrap; }
629
+ /* Side by side there is less room: the command list steps in less. */
630
+ .row-columns-2 .row-entry-list, .row-columns-3 .row-entry-list, .row-columns-4 .row-entry-list { margin-left: 0.5rem; }
631
+ /* The Rows form's save bar stays in view, and says when there is something to save. */
632
+ .row-save-bar { position: sticky; bottom: 0; z-index: 2; display: flex; align-items: center; justify-content: flex-end; gap: 0.75rem; padding: 0.5rem 0; background: var(--surface-card); border-top: 1px solid var(--line); }
633
+ .row-unsaved-note { display: none; font-size: .8rem; font-weight: 600; color: var(--link); }
634
+ #row-arrange-form[data-dirty="1"] .row-unsaved-note { display: inline; }
635
+ /* A command's own move arrows: small, beside its handle, so it can be reordered without dragging. */
636
+ .row-entry-item > .ws-move { flex: none; margin-left: 0; order: -1; }
637
+ .row-entry-item > .ws-move .uk-button { padding: 0 0.25rem; line-height: 1.125rem; min-height: 0; }
638
+ /* Rows tab at phone width: a command's line wraps so its name keeps a readable width (it was left about 20px,
639
+ one word per line), and a group's line wraps so its move arrows stay inside the list instead of being cut off. */
640
+ @media (max-width: 40em) {
641
+ .row-entry-list { margin-left: 0; }
642
+ .row-entry-item { flex-wrap: wrap; }
643
+ .row-entry-item > .row-entry { flex: 1 1 9rem; }
644
+ .row-group { flex-wrap: wrap; }
645
+ .row-group > .ws-move { margin-left: auto; }
646
+ }
647
+
648
+ /* The navigation topbar: entries someone moved up out of the sidebar, in the bar's three regions.
649
+ Quiet by default — the bar is a place, not a toolbar of its own — and the label is hidden under
650
+ 640px, where a wordmark plus a divider plus the theme toggle already fill the width. */
651
+ .toolbar-nav { display: flex; align-items: center; gap: 0.25rem; }
652
+ .toolbar-nav-start { margin-left: 0.5rem; padding-left: 0.5rem; border-left: 1px solid var(--line); }
653
+ .toolbar-nav .action-btn {
654
+ display: inline-flex;
655
+ align-items: center;
656
+ gap: 0.35rem;
657
+ height: 32px;
658
+ padding: 0 0.6rem;
659
+ border: 1px solid transparent;
660
+ border-radius: 999px;
661
+ background: transparent;
662
+ color: var(--text-subtle);
663
+ font-size: 0.8125rem;
664
+ line-height: 1;
665
+ white-space: nowrap;
666
+ }
667
+ .toolbar-nav .action-btn:hover,
668
+ .toolbar-nav .action-btn:focus-visible {
669
+ background: var(--subtle-brand);
670
+ border-color: var(--line);
671
+ color: var(--link);
672
+ text-decoration: none;
673
+ }
674
+ @media (max-width: 40em) {
675
+ .toolbar-nav .action-label { display: none; }
676
+ .toolbar-nav .action-btn { padding: 0 0.45rem; }
677
+ .toolbar-nav-start { margin-left: 0.35rem; padding-left: 0.35rem; }
678
+ }
679
+
680
+ /* The tiles have their own name: `settings-card` is already the class of the settings FORM wrapper,
681
+ and a grid rule on that squeezed its description into a 71px ribbon down the right. */
682
+ /* Settings, grouped and as a grid. There are 26 of these files on a full tree, and as pills they
683
+ wrapped onto three lines of identical buttons — the hub's own settings, an editor that is not a file
684
+ at all, and twenty-four workspaces, all weighted the same. */
685
+ .settings-group { margin-bottom: 1.5rem; }
686
+ .settings-group-head {
687
+ font-size: 0.75rem;
688
+ text-transform: uppercase;
689
+ letter-spacing: 1px;
690
+ font-weight: 600;
691
+ color: var(--text-subtle);
692
+ margin: 0 0 0.5rem;
693
+ }
694
+ .settings-grid {
695
+ display: grid;
696
+ grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
697
+ gap: 0.6rem;
698
+ }
699
+ .settings-tile {
700
+ display: grid;
701
+ grid-template-columns: auto 1fr;
702
+ grid-template-areas: "icon name" "icon meta";
703
+ align-items: center;
704
+ gap: 0 0.6rem;
705
+ padding: 0.65rem 0.75rem;
706
+ border: 1px solid var(--line);
707
+ border-radius: 12px;
708
+ background: var(--surface-card);
709
+ color: var(--text-default);
710
+ text-decoration: none;
711
+ min-width: 0;
712
+ }
713
+ .settings-tile:hover,
714
+ .settings-tile:focus-visible {
715
+ border-color: var(--brand-primary);
716
+ background: var(--subtle-brand);
717
+ color: var(--text-default);
718
+ text-decoration: none;
719
+ }
720
+ .settings-tile.is-current { border-color: var(--brand-primary); background: var(--subtle-brand); }
721
+ .settings-tile-icon { grid-area: icon; color: var(--brand-primary); display: flex; }
722
+ .settings-tile-name { grid-area: name; font-weight: 600; font-size: 0.875rem; }
723
+ .settings-tile-meta {
724
+ grid-area: meta;
725
+ font-size: 0.6875rem;
726
+ line-height: 1.3;
727
+ color: var(--text-subtle);
728
+ overflow: hidden;
729
+ text-overflow: ellipsis;
730
+ white-space: nowrap;
731
+ }
732
+ .settings-back { display: flex; align-items: center; gap: 0.6rem; margin: 0 0 1rem; }
733
+ .settings-back strong { font-size: 1rem; }
734
+
735
+ /* The screens status: quiet while nothing runs, brand while something does — the same state change a
736
+ project row's SCREENS button makes when that project has sessions. The count arrives on a poll, so the
737
+ button follows the badge rather than the server having to re-render it. */
738
+ .actions-screens { gap: 0.35rem; }
739
+ .actions-screens .screens-count:empty { display: none; }
740
+ .actions-screens .uk-badge {
741
+ min-width: 1.05rem;
742
+ height: 1.05rem;
743
+ line-height: 1.05rem;
744
+ font-size: 0.625rem;
745
+ font-weight: 700;
746
+ background: var(--brand-primary);
747
+ color: var(--text-inverse);
748
+ vertical-align: middle;
749
+ }
750
+ .toolbar-nav .actions-screens:has(.uk-badge) {
751
+ background: var(--subtle-brand);
752
+ border-color: var(--brand-primary);
753
+ color: var(--link);
754
+ }
755
+ .toolbar-nav .actions-screens:has(.uk-badge):hover { background: var(--subtle-brand); }
756
+ /* In the rail it is a tile like its neighbours, so the state reads as a border and a tint rather than
757
+ as a different shape. */
758
+ .actions-rail-inner .actions-screens:has(.uk-badge) {
759
+ border-color: var(--brand-primary);
760
+ background: var(--subtle-brand);
761
+ }
762
+
763
+ /* "There is a newer one." It sits beside the version rather than replacing it: what you run and what
764
+ exists are two facts, and the second is only worth room when the answer is yes. */
765
+ .version-hint:empty { display: none; }
766
+ .version-new {
767
+ display: inline-flex;
768
+ align-items: center;
769
+ gap: 0.15rem;
770
+ margin-left: 0.3rem;
771
+ padding: 0.1rem 0.4rem;
772
+ border-radius: 999px;
773
+ background: var(--ok-soft);
774
+ color: var(--ok-ink);
775
+ border: 1px solid var(--ok);
776
+ font-size: 0.6875rem;
777
+ font-weight: 600;
778
+ line-height: 1.4;
779
+ white-space: nowrap;
780
+ text-decoration: none;
781
+ }
782
+ .version-new:hover, .version-new:focus-visible { text-decoration: none; filter: brightness(1.05); }
783
+
784
+ /* Fields or raw YAML: the two ways to edit one settings file. */
785
+ .settings-modes { margin: 0 0 0.75rem; }
786
+ .settings-raw-text { min-height: 30rem; font-family: var(--font-code); font-size: 0.8125rem; }
787
+
788
+ /* ---------- the Styles page ---------- */
789
+
790
+ /* Almost nothing lives here on purpose. The cards are uk-card, the grid is uk-grid with
791
+ uk-child-width and uk-grid-match, the badge is uk-label, the form is uk-form-stacked — so what is
792
+ left is the two things UIkit has no component for: a colour swatch, and a gap between wrapped
793
+ buttons, which UIkit 3 offers no flex-gap utility for. */
794
+
795
+ .style-swatches { gap: .3rem; }
796
+ .style-swatch {
797
+ width: 1.6rem; height: 1.6rem; border-radius: .3rem;
798
+ /* Neutral rather than a token: this sits on the colour it is showing, in whichever style. */
799
+ border: 1px solid rgba(128, 128, 128, .35);
800
+ }
801
+ .style-card-actions { gap: .4rem; }
802
+
803
+ /* The style in use. An outline rather than a border, so the card does not change size when it
804
+ becomes the active one and the grid does not shift under it. */
805
+ .style-card.is-active { outline: 2px solid var(--brand-primary); outline-offset: 2px; }
806
+
807
+ /* The description of a key is the comment written above it in the file. */
808
+ .settings-desc { display: block; margin-top: 0.125rem; font-size: 0.75rem; font-weight: 400; line-height: 1.4; color: var(--text-subtle); }
809
+ /* Find a setting in the file. */
810
+ .settings-find.uk-input { max-width: 26rem; margin: 0.25rem 0 1rem; border-radius: 0.5rem; }
811
+ .settings-row[hidden], .settings-group[hidden] { display: none; }
812
+ /* On or off is a switch, and it says which. */
813
+ .settings-switch {
814
+ appearance: none; flex: none; position: relative; width: 2.25rem; height: 1.25rem; margin: 0; cursor: pointer;
815
+ border-radius: 999px; background: var(--line-strong); transition: background-color 0.15s ease;
816
+ }
817
+ .settings-switch::after {
818
+ content: ""; position: absolute; top: 0.125rem; left: 0.125rem; width: 1rem; height: 1rem; border-radius: 50%;
819
+ background: var(--surface-card); box-shadow: 0 1px 2px rgb(0 0 0 / 20%); transition: transform 0.15s ease;
820
+ }
821
+ .settings-switch:checked { background: var(--brand-primary); }
822
+ .settings-switch:checked::after { transform: translateX(1rem); }
823
+ .settings-switch:focus-visible { outline: 2px solid var(--brand-primary); outline-offset: 2px; }
824
+ .settings-toggle { display: inline-flex; align-items: center; gap: 0.5rem; }
825
+ @media (prefers-reduced-motion: reduce) { .settings-switch, .settings-switch::after { transition: none; } }
826
+ /* Saving is still one button; it says when there is something to save. */
827
+ .settings-save { display: flex; align-items: center; gap: 0.75rem; }
828
+ .settings-dirty { color: var(--warning-ink); font-weight: 600; }