flexdesk 0.1.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 (124) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +195 -0
  3. package/THIRD_PARTY_LICENSES.md +53 -0
  4. package/css/base.css +3439 -0
  5. package/css/flexdesk.css +5797 -0
  6. package/css/overrides.css +2349 -0
  7. package/css/reset.css +46 -0
  8. package/css/tokens.css +137 -0
  9. package/dist/charts.js +780 -0
  10. package/dist/charts.js.map +7 -0
  11. package/dist/chunk-3PHCPZHT.js +222 -0
  12. package/dist/chunk-3PHCPZHT.js.map +7 -0
  13. package/dist/chunk-CT4YXXLP.js +1770 -0
  14. package/dist/chunk-CT4YXXLP.js.map +7 -0
  15. package/dist/chunk-DRYCDMEG.js +2217 -0
  16. package/dist/chunk-DRYCDMEG.js.map +7 -0
  17. package/dist/chunk-DVU44T77.js +855 -0
  18. package/dist/chunk-DVU44T77.js.map +7 -0
  19. package/dist/chunk-FL5KFNQH.js +640 -0
  20. package/dist/chunk-FL5KFNQH.js.map +7 -0
  21. package/dist/chunk-FOOS3T5L.js +117 -0
  22. package/dist/chunk-FOOS3T5L.js.map +7 -0
  23. package/dist/chunk-JYWURG5T.js +32 -0
  24. package/dist/chunk-JYWURG5T.js.map +7 -0
  25. package/dist/chunk-TLZUUFOE.js +276 -0
  26. package/dist/chunk-TLZUUFOE.js.map +7 -0
  27. package/dist/chunk-UCJ2WD4D.js +625 -0
  28. package/dist/chunk-UCJ2WD4D.js.map +7 -0
  29. package/dist/chunk-WVFGV5FT.js +205 -0
  30. package/dist/chunk-WVFGV5FT.js.map +7 -0
  31. package/dist/core.js +1098 -0
  32. package/dist/core.js.map +7 -0
  33. package/dist/editor.js +1661 -0
  34. package/dist/editor.js.map +7 -0
  35. package/dist/flexdesk.css +5797 -0
  36. package/dist/host.js +132 -0
  37. package/dist/host.js.map +7 -0
  38. package/dist/reset.css +46 -0
  39. package/dist/tile_registry-6WZPTUZV.js +28 -0
  40. package/dist/tile_registry-6WZPTUZV.js.map +7 -0
  41. package/dist/tiles.js +2068 -0
  42. package/dist/tiles.js.map +7 -0
  43. package/dist/tokens.css +137 -0
  44. package/dist/widgets.js +5438 -0
  45. package/dist/widgets.js.map +7 -0
  46. package/dist/wm.js +4830 -0
  47. package/dist/wm.js.map +7 -0
  48. package/package.json +80 -0
  49. package/src/charts/chart_types.js +699 -0
  50. package/src/charts/data_series_plot_window.js +761 -0
  51. package/src/charts/downsample.js +119 -0
  52. package/src/charts/plot_config.js +176 -0
  53. package/src/charts/plot_popout_window.js +1560 -0
  54. package/src/charts/plotly_wrapper.js +432 -0
  55. package/src/core/event_bus.js +230 -0
  56. package/src/core/logging.js +300 -0
  57. package/src/core/settings.js +813 -0
  58. package/src/core/state_guard.js +382 -0
  59. package/src/core/state_guard_config.js +309 -0
  60. package/src/core/state_machine.js +258 -0
  61. package/src/editor/editor_pane.js +264 -0
  62. package/src/editor/monaco_editor_factory.js +449 -0
  63. package/src/editor/monaco_loader.js +74 -0
  64. package/src/editor/notebook_search.js +460 -0
  65. package/src/editor/notebook_tab_bar.js +456 -0
  66. package/src/editor/notebook_undo_manager.js +108 -0
  67. package/src/editor/split_pane_container.js +197 -0
  68. package/src/help/help_modal.js +409 -0
  69. package/src/help/help_registry.js +65 -0
  70. package/src/host/host.js +69 -0
  71. package/src/host/pywebview_host.js +133 -0
  72. package/src/tiles/config_schema.js +256 -0
  73. package/src/tiles/layout_persistence.js +662 -0
  74. package/src/tiles/tile_base.js +785 -0
  75. package/src/tiles/tile_grid.js +905 -0
  76. package/src/tiles/tile_registry.js +136 -0
  77. package/src/tiling/command_palette.js +328 -0
  78. package/src/tiling/content_registry.js +110 -0
  79. package/src/tiling/desktops.js +133 -0
  80. package/src/tiling/entity_sources.js +179 -0
  81. package/src/tiling/keymap.js +156 -0
  82. package/src/tiling/kind_taxonomy.js +172 -0
  83. package/src/tiling/landing_table.js +717 -0
  84. package/src/tiling/loading_overlay.js +33 -0
  85. package/src/tiling/nav_panel.js +142 -0
  86. package/src/tiling/page_factory.js +215 -0
  87. package/src/tiling/panel_keys.js +145 -0
  88. package/src/tiling/shell.js +437 -0
  89. package/src/tiling/tile_breadcrumb.js +222 -0
  90. package/src/tiling/tile_renderer.js +459 -0
  91. package/src/tiling/tile_tab_menu.js +340 -0
  92. package/src/tiling/tile_tree.js +880 -0
  93. package/src/tiling/wm.js +1352 -0
  94. package/src/ui/base/component_base.js +30 -0
  95. package/src/ui/base/controller_base.js +48 -0
  96. package/src/ui/base/page_base.js +102 -0
  97. package/src/ui/components/about_dialog.js +224 -0
  98. package/src/ui/components/action_dropdown.js +389 -0
  99. package/src/ui/components/attribute_list_editor.js +652 -0
  100. package/src/ui/components/autocomplete_field.js +732 -0
  101. package/src/ui/components/computing_status_window.js +81 -0
  102. package/src/ui/components/confirm_dialog.js +307 -0
  103. package/src/ui/components/context_menu.js +87 -0
  104. package/src/ui/components/data_table.js +2228 -0
  105. package/src/ui/components/detail_header.js +177 -0
  106. package/src/ui/components/drag_reorder.js +0 -0
  107. package/src/ui/components/gallery_picker.js +147 -0
  108. package/src/ui/components/inline_renamer.js +129 -0
  109. package/src/ui/components/managed_window.js +804 -0
  110. package/src/ui/components/modal.js +512 -0
  111. package/src/ui/components/notification_history.js +264 -0
  112. package/src/ui/components/slide_out_panel.js +185 -0
  113. package/src/ui/components/slider_field.js +122 -0
  114. package/src/ui/components/sortable_list.js +309 -0
  115. package/src/ui/components/table_state_store.js +81 -0
  116. package/src/ui/components/toast.js +59 -0
  117. package/src/ui/components/tree_view.js +460 -0
  118. package/src/ui/components/window_taskbar.js +143 -0
  119. package/src/ui/controllers/panel_state_machine.js +634 -0
  120. package/src/ui/controllers/window_chrome_controller.js +659 -0
  121. package/src/ui/notification_center.js +576 -0
  122. package/src/ui/utils/overlay_scrollbar.js +492 -0
  123. package/src/ui/utils/raf_resize_observer.js +31 -0
  124. package/src/ui/utils/tooltip_service.js +406 -0
@@ -0,0 +1,2349 @@
1
+ /* twm — framework rules that must WIN. Loads LAST: these deliberately beat a vendor or embedder rule they tie with on specificity.
2
+ *
3
+ * GENERATED by scripts/css_extract.py — do not edit by hand.
4
+ */
5
+
6
+ /* ── from ecoagent_extra_modes.css ───────────────────────── */
7
+ /* SortableList — EcoAgent tiling shell skin. Narrower row padding,
8
+ * darker recessed bg, square corners. Scoped to .twm-page-shell so the
9
+ * notebook test editor and other contexts keep their original look. */
10
+ .twm-page-shell .twm-sortable-list { gap: 4px; }
11
+ .twm-page-shell .twm-sortable-list__items { gap: 0; }
12
+ .twm-page-shell .twm-sortable-list__row {
13
+ background: var(--ea-bg-content, #212121);
14
+ border: 1px solid var(--ea-border, #333);
15
+ border-radius: 0;
16
+ border-bottom-width: 0;
17
+ padding: 3px 6px;
18
+ gap: 6px;
19
+ }
20
+ .twm-page-shell .twm-sortable-list__row:last-child {
21
+ border-bottom-width: 1px;
22
+ }
23
+ .twm-page-shell .twm-sortable-list__row:hover { border-color: var(--ea-border, #555); }
24
+ .twm-page-shell .twm-sortable-list__handle { padding: 0; opacity: 0.5; }
25
+ .twm-page-shell .twm-sortable-list__remove { padding: 2px; border-radius: 0; }
26
+ .twm-page-shell .twm-sortable-list__add {
27
+ margin-top: 4px;
28
+ border-radius: 0;
29
+ border-color: var(--ea-border, #333);
30
+ background: var(--ea-bg-content, #212121);
31
+ padding: 4px 8px;
32
+ font-size: 11px;
33
+ }
34
+ .twm-page-shell .twm-sortable-list__empty {
35
+ border-radius: 0;
36
+ border-color: var(--ea-border, #333);
37
+ background: transparent;
38
+ padding: 8px;
39
+ font-size: 11px;
40
+ }
41
+ .twm-page-shell .twm-sortable-list__input {
42
+ border-radius: 0;
43
+ background: var(--ea-bg-well, #0d0d0d);
44
+ border-color: var(--ea-border, #333);
45
+ padding: 3px 6px;
46
+ }
47
+ .twm-entity-header__spacer { flex: 1; }
48
+ /* Inline field in the detail header (sector tab's country picker,
49
+ * agent tab's role badges, etc.). Density-pass v3: aligned to the
50
+ * shared `--ea-row-h` height so every control in every tab header
51
+ * is exactly the same vertical size — no per-page drift between
52
+ * sector / agent / market / scenario headers. */
53
+ .twm-entity-header__field {
54
+ display: inline-flex;
55
+ align-items: center;
56
+ gap: var(--ea-pad-tight);
57
+ font-size: 10.5px;
58
+ color: var(--ea-text-muted);
59
+ text-transform: uppercase;
60
+ letter-spacing: 0.04em;
61
+ margin-left: var(--ea-pad-snug);
62
+ }
63
+ .twm-entity-header__field span { line-height: 1; }
64
+ .twm-entity-header__select {
65
+ background: var(--ea-bg-well);
66
+ color: var(--ea-text);
67
+ border: 1px solid var(--ea-border);
68
+ border-radius: 2px;
69
+ padding: 0 var(--ea-pad-snug);
70
+ height: var(--ea-row-h);
71
+ font-size: var(--ea-font-body);
72
+ text-transform: none;
73
+ letter-spacing: 0;
74
+ min-width: 110px;
75
+ line-height: 1;
76
+ }
77
+ .twm-entity-header__select:focus {
78
+ outline: none;
79
+ border-color: var(--ea-accent);
80
+ }
81
+ .twm-entity-header__currency {
82
+ color: var(--ea-text-muted);
83
+ font-size: var(--ea-font-body);
84
+ margin-left: var(--ea-pad-snug);
85
+ font-variant-numeric: tabular-nums;
86
+ }
87
+ /* ─── Buttons ──────────────────────────────────────────────────────
88
+ * One canonical button across the app:
89
+ * - Single height (--ea-row-h, 18 px) so toolbars line up with
90
+ * table rows and chrome strips.
91
+ * - 2 px radius — squared off, "tool" feel rather than "website".
92
+ * - Variants change colour, not size: --primary, --danger, --icon.
93
+ * - --small only nudges font/padding; height stays the row token.
94
+ */
95
+ .twm-btn {
96
+ display: inline-flex;
97
+ align-items: center;
98
+ justify-content: center;
99
+ gap: var(--ea-pad-tight);
100
+ background: #2e2e2e;
101
+ color: var(--ea-text);
102
+ border: 1px solid var(--ea-border);
103
+ padding: 0 var(--ea-pad-snug);
104
+ border-radius: 2px;
105
+ cursor: pointer;
106
+ font: inherit;
107
+ font-size: var(--ea-font-body);
108
+ line-height: 1;
109
+ min-height: var(--ea-row-h);
110
+ white-space: nowrap;
111
+ transition: background var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
112
+ }
113
+ .twm-btn:hover:not(:disabled) {
114
+ background: #3a3a3a;
115
+ border-color: #444;
116
+ }
117
+ .twm-btn:active:not(:disabled) { background: #262626; }
118
+ .twm-btn:focus-visible {
119
+ outline: none;
120
+ border-color: var(--ea-accent);
121
+ box-shadow: 0 0 0 1px var(--ea-accent);
122
+ }
123
+ .twm-btn:disabled {
124
+ opacity: 0.4;
125
+ cursor: not-allowed;
126
+ }
127
+ .twm-btn--primary {
128
+ background: var(--ea-accent);
129
+ border-color: var(--ea-accent);
130
+ color: var(--text-bright);
131
+ }
132
+ .twm-btn--primary:hover:not(:disabled) {
133
+ background: #1486cf;
134
+ border-color: #1486cf;
135
+ }
136
+ /* Context menu separator (matches Ecosim's .twm-context-menu look). */
137
+ .twm-context-menu__separator {
138
+ height: 1px;
139
+ background: var(--surface-3);
140
+ margin: var(--space-xs) 0;
141
+ }
142
+ /* Project sidebar — the sidebar only carries a single "Files"
143
+ * section, and the redundant collapsible header above the tree was
144
+ * pure chrome with no information. Hide it so the tree starts at
145
+ * the top of the panel. The section box still wraps the tree (so
146
+ * `.sidebar-body--accordion`'s flex layout keeps working) — only
147
+ * its header is hidden.
148
+ *
149
+ * NOTE: `buildBrowserPanel` renders the section box with class
150
+ * `tree-category dynamic-sheet-section` (not `collapsible-box`); an
151
+ * earlier attempt used the wrong parent class and didn't take. */
152
+ [data-panel-content="project"] .twm-tree-category > .twm-collapsible-header {
153
+ display: none !important;
154
+ }
155
+ [data-panel-content="project"] .twm-tree-category > .twm-collapsible-content {
156
+ /* Force-visible since the toggle header is gone — would
157
+ * otherwise stay collapsed if persisted state said so. */
158
+ display: block !important;
159
+ visibility: visible !important;
160
+ }
161
+ [data-panel-content="project"] .twm-tree-category {
162
+ border: none;
163
+ margin: 0;
164
+ }
165
+ /* Danger / icon-only variants */
166
+ .twm-btn--danger {
167
+ background: #4a2424;
168
+ border-color: #6a2e2e;
169
+ color: #e8b0b0;
170
+ }
171
+ .twm-btn--danger:hover:not(:disabled) {
172
+ background: #6a2e2e;
173
+ color: var(--text-bright);
174
+ }
175
+ /* ════════════════════════════════════════════════════════════════════
176
+ * ManagedWindow modal — TUI / console input-mask aesthetic.
177
+ *
178
+ * Reads as three deliberate horizontal zones:
179
+ * ┌─ title strip (chrome bg, marker + uppercase title, 26 px) ────┐
180
+ * │ body (content bg, fielded form: label : input grid) │
181
+ * └─ action strip (chrome bg, buttons right-aligned, 32 px) ──────┘
182
+ *
183
+ * Outer frame: 1 px hairline + 1 px inner accent line via box-shadow
184
+ * inset, giving it a Borland-drawn-box feel without literal 2px borders.
185
+ * ════════════════════════════════════════════════════════════════════ */
186
+
187
+ .twm-managed-window.twm-managed-window--modal {
188
+ background: var(--ea-bg-content);
189
+ border: 1px solid #4a4a4a;
190
+ border-radius: 0;
191
+ box-shadow:
192
+ 0 1px 0 rgba(255, 255, 255, 0.04) inset,
193
+ 0 0 0 1px rgba(0, 0, 0, 0.6),
194
+ 0 18px 48px rgba(0, 0, 0, 0.55),
195
+ 0 4px 16px rgba(0, 0, 0, 0.45);
196
+ overflow: hidden;
197
+ color: var(--ea-text);
198
+ }
199
+ /* Title strip — like the title bar of a Borland Turbo Vision dialog:
200
+ * solid dark band, title in bold uppercase with a small accent marker
201
+ * (▸) so it reads as "active". The strip is taller than the body's
202
+ * usual 18 px row to feel like a band, not a line. */
203
+ .twm-managed-window--modal .twm-managed-window__topbar {
204
+ background:
205
+ linear-gradient(180deg,
206
+ rgba(255, 255, 255, 0.025) 0%,
207
+ rgba(0, 0, 0, 0) 70%),
208
+ var(--ea-bg-chrome);
209
+ border-bottom: 1px solid #4a4a4a;
210
+ border-radius: 0;
211
+ min-height: 26px;
212
+ padding: 0 var(--ea-pad-loose) 0 calc(var(--ea-pad-loose) - 4px);
213
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.6);
214
+ }
215
+ .twm-managed-window--modal .twm-managed-window__icon {
216
+ color: var(--ea-accent);
217
+ font-size: 14px;
218
+ margin-right: 6px;
219
+ }
220
+ .twm-managed-window--modal .twm-managed-window__title {
221
+ color: var(--ea-text);
222
+ font-size: var(--ea-font-body);
223
+ font-weight: 700;
224
+ letter-spacing: 0.08em;
225
+ text-transform: uppercase;
226
+ }
227
+ .twm-managed-window--modal .twm-managed-window__title::before {
228
+ content: '▸ ';
229
+ color: var(--ea-accent);
230
+ margin-right: 2px;
231
+ }
232
+ .twm-managed-window--modal .twm-managed-window__btn--close {
233
+ width: 22px;
234
+ height: 22px;
235
+ color: var(--ea-text-muted);
236
+ background: transparent;
237
+ border-radius: 0;
238
+ }
239
+ .twm-managed-window--modal .twm-managed-window__btn--close:hover {
240
+ background: #b03a3a;
241
+ color: #fff;
242
+ }
243
+ /* Content area — drop the host padding so the body host controls its
244
+ * own zones (form rows + flush-bottom action strip). */
245
+ .twm-managed-window--modal .twm-managed-window__content {
246
+ background: var(--ea-bg-content);
247
+ padding: 0;
248
+ overflow: auto;
249
+ }
250
+ .twm-managed-window--modal .twm-modal__body-host {
251
+ display: flex;
252
+ flex-direction: column;
253
+ min-height: 100%;
254
+ }
255
+ /* Form sits inside the body host as a flex column so the actions row
256
+ * gets pushed to the bottom via margin-top:auto. */
257
+ .twm-managed-window--modal .twm-modal__form {
258
+ display: flex;
259
+ flex-direction: column;
260
+ flex: 1 1 auto;
261
+ padding: 0;
262
+ }
263
+ /* Field rows get inset margins so the action strip can run edge-to-edge
264
+ * as a true bottom band. */
265
+ .twm-managed-window--modal .twm-modal__form > .twm-modal__row,
266
+ .twm-managed-window--modal .twm-modal__form > .twm-modal__section,
267
+ .twm-managed-window--modal .twm-modal__form > .twm-modal__error,
268
+ .twm-managed-window--modal .twm-modal__body {
269
+ margin-left: 14px;
270
+ margin-right: 14px;
271
+ }
272
+ .twm-managed-window--modal .twm-modal__form > .twm-modal__row:first-of-type,
273
+ .twm-managed-window--modal .twm-modal__form > .twm-modal__section:first-child,
274
+ .twm-managed-window--modal .twm-modal__body:first-child {
275
+ margin-top: 14px;
276
+ }
277
+ /* Rich-content body (openModal). Fills the available height between
278
+ * the title strip and the action footer; the caller's content sits
279
+ * inside and inherits the scroll. */
280
+ .twm-managed-window--modal .twm-modal__body--rich {
281
+ margin: 0 14px 0;
282
+ padding: 0;
283
+ flex: 1 1 auto;
284
+ min-height: 0;
285
+ overflow: auto;
286
+ display: flex;
287
+ flex-direction: column;
288
+ }
289
+ .twm-managed-window--modal .twm-modal__body--rich > * {
290
+ flex: 1 1 auto;
291
+ min-height: 0;
292
+ }
293
+ /* Fielded input mask: label right-aligned with a colon suffix, input
294
+ * occupying the remaining width. Mirrors how curses/dialog renders a
295
+ * form — labels read down the left edge, values down the right. */
296
+ .twm-managed-window--modal .twm-modal__row {
297
+ display: grid;
298
+ grid-template-columns: 130px 1fr;
299
+ align-items: center;
300
+ column-gap: 10px;
301
+ row-gap: 0;
302
+ margin-bottom: 10px;
303
+ }
304
+ .twm-managed-window--modal .twm-modal__row--multiline {
305
+ align-items: start;
306
+ }
307
+ .twm-managed-window--modal .twm-modal__row > span:first-child {
308
+ color: var(--ea-text-dim);
309
+ font-size: var(--ea-font-body);
310
+ text-transform: uppercase;
311
+ letter-spacing: 0.05em;
312
+ text-align: right;
313
+ justify-self: end;
314
+ line-height: var(--ea-row-h);
315
+ }
316
+ /* Add the trailing colon via CSS so callers don't have to. Suppress on
317
+ * checkbox rows (their left cell is intentionally empty). */
318
+ .twm-managed-window--modal .twm-modal__row > span:first-child:not(:empty)::after {
319
+ content: ' :';
320
+ color: var(--ea-text-muted);
321
+ margin-left: 1px;
322
+ }
323
+ .twm-managed-window--modal .twm-modal__row.twm-is-invalid > span:first-child {
324
+ color: #f0a8a8;
325
+ }
326
+ /* Input wells — deeper than the body so the field reads as "the place
327
+ * you type" against the panel. Sharp 1 px border with a 1 px inner
328
+ * shadow that simulates being inset. */
329
+ .twm-managed-window--modal .twm-modal__row input[type="text"],
330
+ .twm-managed-window--modal .twm-modal__row input[type="number"],
331
+ .twm-managed-window--modal .twm-modal__row input[type="password"],
332
+ .twm-managed-window--modal .twm-modal__row input[type="search"],
333
+ .twm-managed-window--modal .twm-modal__row input[type="email"],
334
+ .twm-managed-window--modal .twm-modal__row select,
335
+ .twm-managed-window--modal .twm-modal__row textarea {
336
+ background: #0a0a0a;
337
+ color: var(--ea-text);
338
+ border: 1px solid #3a3a3a;
339
+ border-radius: 0;
340
+ /* Shared input padding (same token as `.ea-tin`). Was `0 8px` with a
341
+ * line-height fudge for vertical centering — real padding instead. */
342
+ padding: var(--ea-input-pad);
343
+ min-height: 22px;
344
+ line-height: normal;
345
+ font: inherit;
346
+ font-size: var(--ea-font-body);
347
+ width: 100%;
348
+ box-sizing: border-box;
349
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.4) inset;
350
+ }
351
+ .twm-managed-window--modal .twm-modal__row textarea {
352
+ line-height: 1.45;
353
+ padding: 6px 8px;
354
+ min-height: calc(var(--ea-row-h) * 4);
355
+ resize: vertical;
356
+ }
357
+ .twm-managed-window--modal .twm-modal__row input:focus,
358
+ .twm-managed-window--modal .twm-modal__row select:focus,
359
+ .twm-managed-window--modal .twm-modal__row textarea:focus {
360
+ outline: none;
361
+ border-color: var(--ea-accent);
362
+ box-shadow:
363
+ 0 1px 0 rgba(0, 0, 0, 0.4) inset,
364
+ 0 0 0 1px var(--ea-accent);
365
+ }
366
+ .twm-managed-window--modal .twm-modal__row.twm-is-invalid input,
367
+ .twm-managed-window--modal .twm-modal__row.twm-is-invalid select,
368
+ .twm-managed-window--modal .twm-modal__row.twm-is-invalid textarea {
369
+ border-color: #b03a3a;
370
+ }
371
+ /* Hint + per-field error — small dim line that sits *under* the input,
372
+ * spanning only the input column so it aligns with the field above. */
373
+ .twm-managed-window--modal .twm-modal__hint--field {
374
+ grid-column: 2;
375
+ color: var(--ea-text-muted);
376
+ font-size: 10.5px;
377
+ margin-top: 3px;
378
+ line-height: 1.4;
379
+ font-style: italic;
380
+ }
381
+ .twm-managed-window--modal .twm-modal__field-error {
382
+ grid-column: 2;
383
+ color: #f0a8a8;
384
+ font-size: 10.5px;
385
+ margin-top: 3px;
386
+ line-height: 1.4;
387
+ }
388
+ /* Section divider — small uppercase heading with a long em-dash style
389
+ * underline that runs to the right edge of the field column. Reads
390
+ * like the section breaks in a dialog/ncurses form. */
391
+ .twm-managed-window--modal .twm-modal__section {
392
+ grid-column: 1 / -1;
393
+ display: flex;
394
+ align-items: center;
395
+ gap: 8px;
396
+ margin: 14px 0 8px;
397
+ padding: 0;
398
+ font-size: 10.5px;
399
+ font-weight: 700;
400
+ text-transform: uppercase;
401
+ letter-spacing: 0.08em;
402
+ color: var(--ea-accent);
403
+ }
404
+ .twm-managed-window--modal .twm-modal__section::after {
405
+ content: '';
406
+ flex: 1;
407
+ height: 1px;
408
+ background: #3a3a3a;
409
+ }
410
+ /* Inline checkbox row — left cell empty (kept for grid alignment), the
411
+ * checkbox + its own label live in column 2. */
412
+ .twm-managed-window--modal .twm-modal__check {
413
+ display: flex;
414
+ align-items: center;
415
+ gap: 8px;
416
+ color: var(--ea-text);
417
+ }
418
+ .twm-managed-window--modal .twm-modal__check input[type="checkbox"] {
419
+ width: 14px;
420
+ height: 14px;
421
+ margin: 0;
422
+ accent-color: var(--ea-accent);
423
+ }
424
+ /* Combobox = input + datalist + a small "↳ will create X" hint. The
425
+ * hint sits below the input, dim accent color. */
426
+ .twm-managed-window--modal .twm-combobox {
427
+ display: flex;
428
+ flex-direction: column;
429
+ gap: 2px;
430
+ }
431
+ .twm-managed-window--modal .twm-combobox__hint {
432
+ font-size: 10.5px;
433
+ color: var(--ea-accent);
434
+ font-style: italic;
435
+ }
436
+ /* Action strip — true footer band, flush against the modal edges,
437
+ * darker chrome bg, top border + 1px highlight above. Right-aligned
438
+ * buttons, primary stays rightmost. */
439
+ .twm-managed-window--modal .twm-modal__actions {
440
+ margin-top: auto;
441
+ display: flex;
442
+ justify-content: flex-end;
443
+ align-items: center;
444
+ gap: 6px;
445
+ background: var(--ea-bg-chrome);
446
+ border-top: 1px solid #4a4a4a;
447
+ box-shadow: 0 -1px 0 rgba(255, 255, 255, 0.03) inset;
448
+ padding: 6px 14px;
449
+ min-height: 32px;
450
+ }
451
+ .twm-managed-window--modal .twm-modal__actions .twm-btn {
452
+ border-radius: 0;
453
+ min-height: 22px;
454
+ padding: 0 14px;
455
+ font-size: var(--ea-font-body);
456
+ letter-spacing: 0.04em;
457
+ text-transform: uppercase;
458
+ }
459
+ .twm-managed-window--modal .twm-modal__actions .twm-btn--primary {
460
+ background: var(--ea-accent);
461
+ border-color: var(--ea-accent);
462
+ color: #fff;
463
+ font-weight: 600;
464
+ }
465
+ .twm-managed-window--modal .twm-modal__actions .twm-btn--primary:hover:not(:disabled) {
466
+ background: #1683c4;
467
+ border-color: #1683c4;
468
+ }
469
+ .twm-managed-window--modal .twm-modal__actions .twm-btn--danger {
470
+ background: #8b2a2a;
471
+ border-color: #8b2a2a;
472
+ color: #fff;
473
+ }
474
+ .twm-managed-window--modal .twm-modal__actions .twm-btn--danger:hover:not(:disabled) {
475
+ background: #a13434;
476
+ border-color: #a13434;
477
+ }
478
+ /* openConfirm uses .twm-modal__body for the message paragraph. */
479
+ .twm-managed-window--modal .twm-modal__body {
480
+ padding: 0;
481
+ line-height: 1.5;
482
+ color: var(--ea-text);
483
+ flex: 1 1 auto;
484
+ }
485
+ /* Form-level error banner — left accent stripe, dim red panel. */
486
+ .twm-managed-window--modal .twm-modal__error {
487
+ margin-top: 8px;
488
+ padding: 6px 10px;
489
+ background: rgba(176, 58, 58, 0.12);
490
+ color: #f0a8a8;
491
+ border: 1px solid #6a2828;
492
+ border-left: 3px solid #b03a3a;
493
+ font-size: 10.5px;
494
+ line-height: 1.4;
495
+ }
496
+ .twm-modal__form,
497
+ .twm-modal__body {
498
+ padding: var(--ea-pad-loose);
499
+ overflow: auto;
500
+ }
501
+ .twm-modal__row {
502
+ display: grid;
503
+ grid-template-columns: minmax(120px, 32%) 1fr;
504
+ align-items: center;
505
+ gap: var(--ea-pad-loose);
506
+ margin-bottom: var(--ea-pad-snug);
507
+ font-size: var(--ea-font-body);
508
+ color: var(--ea-text);
509
+ }
510
+ .twm-modal__row--multiline {
511
+ align-items: start;
512
+ }
513
+ .twm-modal__row > span {
514
+ color: var(--ea-text-dim);
515
+ font-size: var(--ea-font-body);
516
+ padding-top: 1px;
517
+ }
518
+ .twm-modal__row input[type="text"],
519
+ .twm-modal__row input[type="number"],
520
+ .twm-modal__row input[type="password"],
521
+ .twm-modal__row input[type="search"],
522
+ .twm-modal__row input[type="email"],
523
+ .twm-modal__row select,
524
+ .twm-modal__row textarea {
525
+ background: var(--ea-bg-well);
526
+ color: var(--ea-text);
527
+ border: 1px solid var(--ea-border);
528
+ padding: 0 var(--ea-pad-snug);
529
+ border-radius: 0;
530
+ font: inherit;
531
+ font-size: var(--ea-font-body);
532
+ width: 100%;
533
+ box-sizing: border-box;
534
+ }
535
+ .twm-modal__row textarea {
536
+ padding: var(--ea-pad-snug);
537
+ min-height: calc(var(--ea-row-h) * 3);
538
+ line-height: 1.45;
539
+ resize: vertical;
540
+ font-family: inherit;
541
+ }
542
+ .twm-modal__row input:focus,
543
+ .twm-modal__row select:focus,
544
+ .twm-modal__row textarea:focus {
545
+ outline: none;
546
+ border-color: var(--ea-accent);
547
+ box-shadow: 0 0 0 1px var(--ea-accent);
548
+ }
549
+ .twm-modal__row.twm-is-invalid input,
550
+ .twm-modal__row.twm-is-invalid select,
551
+ .twm-modal__row.twm-is-invalid textarea {
552
+ border-color: #b03a3a;
553
+ }
554
+ /* Per-field help text — small dim line that sits under the row,
555
+ * spanning only the input column so it aligns with the field. */
556
+ .twm-modal__hint--field {
557
+ grid-column: 2;
558
+ color: var(--ea-text-dim);
559
+ font-size: var(--ea-font-small, 11px);
560
+ margin-top: 2px;
561
+ margin-bottom: var(--ea-pad-snug);
562
+ line-height: 1.4;
563
+ }
564
+ .twm-modal__field-error {
565
+ grid-column: 2;
566
+ color: #f0a8a8;
567
+ font-size: var(--ea-font-small, 11px);
568
+ margin-top: 2px;
569
+ margin-bottom: var(--ea-pad-snug);
570
+ line-height: 1.4;
571
+ }
572
+ /* Section divider — small uppercase label with a thin top border, used
573
+ * to group fields inside a longer form. */
574
+ .twm-modal__section {
575
+ grid-column: 1 / -1;
576
+ margin: var(--ea-pad-loose) 0 var(--ea-pad-snug);
577
+ padding-top: var(--ea-pad-snug);
578
+ border-top: 1px solid var(--ea-border);
579
+ font-size: var(--ea-font-small, 11px);
580
+ text-transform: uppercase;
581
+ letter-spacing: 0.06em;
582
+ color: var(--ea-text-muted);
583
+ }
584
+ .twm-modal__section:first-child {
585
+ margin-top: 0;
586
+ padding-top: 0;
587
+ border-top: none;
588
+ }
589
+ /* Inline checkbox — label sits on the right of the box so the row
590
+ * reads naturally. The empty <span> in column 1 keeps grid alignment
591
+ * with text-input rows. */
592
+ .twm-modal__check {
593
+ display: flex;
594
+ align-items: center;
595
+ gap: var(--ea-pad-snug);
596
+ color: var(--ea-text);
597
+ }
598
+ .twm-modal__check input[type="checkbox"] {
599
+ width: 14px;
600
+ height: 14px;
601
+ margin: 0;
602
+ accent-color: var(--ea-accent);
603
+ }
604
+ /* Dependent-property pick-or-create — a type-ahead combobox. The user
605
+ picks an existing option or types a new value (created on submit).
606
+ With no options it's just a text field — the "nothing defined yet"
607
+ case needs no special handling. */
608
+ .twm-combobox {
609
+ display: flex;
610
+ flex-direction: column;
611
+ gap: 3px;
612
+ }
613
+ .twm-combobox input { width: 100%; box-sizing: border-box; }
614
+ .twm-combobox__hint {
615
+ font-size: var(--ea-font-small, 11px);
616
+ color: var(--ea-accent);
617
+ }
618
+ .twm-modal__actions {
619
+ display: flex;
620
+ justify-content: flex-end;
621
+ align-items: center;
622
+ gap: var(--ea-pad-snug);
623
+ background: var(--ea-bg-chrome);
624
+ }
625
+ .twm-modal__actions .twm-btn {
626
+ border-radius: 0;
627
+ min-height: calc(var(--ea-row-h) + 4px);
628
+ padding: 0 var(--ea-pad-loose);
629
+ }
630
+ .twm-modal__error {
631
+ margin: 0 var(--ea-pad-loose) var(--ea-pad-snug);
632
+ padding: var(--ea-pad-snug) var(--ea-pad-loose);
633
+ border-radius: 0;
634
+ background: #3a1f1f;
635
+ color: #f0a8a8;
636
+ border: 1px solid #5a2a2a;
637
+ font-size: var(--ea-font-body);
638
+ }
639
+ .twm-modal__body {
640
+ padding: var(--ea-pad-loose);
641
+ }
642
+ .twm-slide-out-panel__body .twm-autocomplete-field input,
643
+ .twm-slide-out-panel__body .twm-autocomplete-field select,
644
+ .twm-slide-out-panel__body .twm-autocomplete-field button {
645
+ height: var(--ea-row-h);
646
+ min-height: var(--ea-row-h);
647
+ font-size: var(--ea-font-body);
648
+ padding: 0 var(--ea-pad-snug);
649
+ background: var(--ea-bg-well);
650
+ color: var(--ea-text);
651
+ border: 1px solid var(--ea-border);
652
+ border-radius: 2px;
653
+ line-height: 1;
654
+ }
655
+ /* ══════════════════════════════════════════════════════════════════════
656
+ * SFC mode overview — dense two-table layout (Sectors + Flows always
657
+ * visible). Header strip on top with summary stats; two stacked
658
+ * DataTable panes below, each independently scrollable. Both tables
659
+ * are keyboard-navigable; rows are click-to-open.
660
+ *
661
+ * No cards, no pills, no rounded boxes — matches the dense aesthetic
662
+ * of sector / market / agent tabs (.twm-entity-header + .ea-table).
663
+ * ══════════════════════════════════════════════════════════════════════ */
664
+ .twm-landing {
665
+ display: flex;
666
+ flex-direction: column;
667
+ height: 100%;
668
+ min-height: 0;
669
+ overflow: hidden; /* keep the actions strip anchored at the bottom */
670
+ background: var(--ea-bg-chrome);
671
+ color: var(--ea-text);
672
+ font-size: var(--ea-font-body);
673
+ }
674
+ .twm-landing__split {
675
+ flex: 1 1 0; /* shrink-fit so footer always shows */
676
+ min-height: 0;
677
+ display: flex;
678
+ flex-direction: column;
679
+ overflow: hidden;
680
+ }
681
+ .twm-landing__pane {
682
+ flex: 1 1 50%;
683
+ min-height: 0;
684
+ display: flex;
685
+ flex-direction: column;
686
+ border-top: 1px solid var(--ea-border);
687
+ }
688
+ .twm-landing__pane:first-child { border-top: none; }
689
+ /* Single-pane landings (Assets / Markets / Archetypes / Agents /
690
+ * Scenarios / KPIs / …) — the lone pane should fill 100% of the
691
+ * available vertical space inside the split. Without this the
692
+ * `flex: 1 1 50%` default leaves the pane sized to its content. */
693
+ .twm-landing__split--single > .twm-landing__pane {
694
+ flex: 1 1 auto;
695
+ }
696
+ .twm-landing__split--single > .twm-landing__pane
697
+ > .twm-landing__table {
698
+ flex: 1 1 0;
699
+ min-height: 0;
700
+ }
701
+ .twm-landing__pane-head {
702
+ display: flex;
703
+ align-items: center;
704
+ gap: var(--ea-pad-snug);
705
+ padding: 0 var(--ea-pad-snug);
706
+ height: 22px;
707
+ min-height: 22px;
708
+ background: var(--ea-bg-chrome);
709
+ border-bottom: 1px solid var(--ea-border-soft);
710
+ flex-shrink: 0;
711
+ }
712
+ .twm-landing__pane-title {
713
+ font-size: 10.5px;
714
+ text-transform: uppercase;
715
+ letter-spacing: 0.04em;
716
+ font-weight: 600;
717
+ color: var(--ea-text);
718
+ }
719
+ .twm-landing__pane-hint {
720
+ margin-left: auto;
721
+ color: var(--ea-text-dim);
722
+ font-size: 10px;
723
+ }
724
+ .twm-landing__pane--focused .twm-landing__pane-head {
725
+ background: rgba(17, 119, 179, 0.10);
726
+ }
727
+ .twm-landing__pane--focused .twm-landing__pane-title {
728
+ color: var(--ea-accent);
729
+ }
730
+ .twm-landing__table {
731
+ flex: 1;
732
+ min-height: 0;
733
+ overflow: auto;
734
+ background: var(--ea-bg-content);
735
+ }
736
+ /* Kind-filter dropdown in the Archetypes landing header. Same dark
737
+ * styling as the rest of the inputs — the bare browser default reads
738
+ * as light grey. */
739
+ .twm-entity-header__field select {
740
+ background-color: var(--ea-input-bg, #1e1e1e);
741
+ color: var(--ea-text, #ddd);
742
+ border: 1px solid var(--ea-border, #444);
743
+ padding: 0.2em 1.6em 0.2em 0.5em;
744
+ font: inherit;
745
+ font-size: 0.85em;
746
+ -webkit-appearance: none;
747
+ -moz-appearance: none;
748
+ appearance: none;
749
+ background-image: linear-gradient(45deg, transparent 50%, var(--ea-text-muted, #888) 50%),
750
+ linear-gradient(135deg, var(--ea-text-muted, #888) 50%, transparent 50%);
751
+ background-position: calc(100% - 12px) 50%, calc(100% - 8px) 50%;
752
+ background-size: 4px 4px, 4px 4px;
753
+ background-repeat: no-repeat;
754
+ cursor: pointer;
755
+ }
756
+ .twm-entity-header__field select:focus {
757
+ outline: none;
758
+ border-color: var(--ea-accent, #5aa3e8);
759
+ }
760
+ .twm-entity-header__field select option { background: var(--ea-input-bg, #1e1e1e); color: var(--ea-text, #ddd); }
761
+ .twm-landing__table table {
762
+ width: 100%;
763
+ }
764
+ .twm-landing__table thead th {
765
+ position: sticky;
766
+ top: 0;
767
+ background: var(--ea-bg-chrome);
768
+ z-index: 1;
769
+ }
770
+ .twm-landing__table tbody tr:hover {
771
+ background: rgba(255, 255, 255, 0.04);
772
+ }
773
+ /* Keyboard cursor highlight — shared between every overview's DataTable
774
+ so the same arrow-key affordance reads identically across modes. */
775
+ .twm-row-cursor {
776
+ box-shadow: inset 2px 0 0 var(--ea-accent);
777
+ background: rgba(17, 119, 179, 0.12) !important;
778
+ }
779
+ /* Quick-action strip — horizontal bar below the tables. Same chrome
780
+ tone as `.twm-entity-header` so the page reads as a single stack:
781
+ header / two tables / actions. Buttons are flat (no border-radius
782
+ beyond 2px), icon + short label, keyboard-shortcut hint on the right.
783
+ Reused across every mode overview that wants a quick-action row. */
784
+ .twm-landing__actions {
785
+ display: flex;
786
+ align-items: center;
787
+ gap: var(--ea-pad-snug);
788
+ height: 30px;
789
+ min-height: 30px;
790
+ padding: 0 var(--ea-pad-snug);
791
+ background: var(--ea-bg-well);
792
+ border-top: 1px solid var(--ea-border);
793
+ flex: 0 0 30px;
794
+ position: relative;
795
+ z-index: 2;
796
+ }
797
+ .twm-landing__actions-spacer { flex: 1; }
798
+ .twm-landing__actions-hint {
799
+ color: var(--ea-text-dim);
800
+ font-size: 10px;
801
+ }
802
+ .twm-action-btn {
803
+ display: inline-flex;
804
+ align-items: center;
805
+ gap: 4px;
806
+ height: 20px;
807
+ padding: 0 var(--ea-pad-snug);
808
+ background: transparent;
809
+ border: 1px solid var(--ea-border-soft);
810
+ border-radius: 2px;
811
+ color: var(--ea-text);
812
+ font-size: var(--ea-font-body);
813
+ cursor: pointer;
814
+ }
815
+ .twm-action-btn:hover {
816
+ border-color: var(--ea-accent);
817
+ background: rgba(17, 119, 179, 0.08);
818
+ }
819
+ .twm-action-btn:disabled,
820
+ .twm-action-btn[aria-disabled="true"] {
821
+ color: var(--ea-text-dim);
822
+ cursor: not-allowed;
823
+ }
824
+ /* Inline keyboard-shortcut chip rendered inside an .twm-action-btn —
825
+ * the visible "[N]" that tells the user which letter fires the action.
826
+ * mountLandingActions appends it after the label and `landing_table.js`
827
+ * keeps the keydown handler in sync, so the chip and the hotkey can
828
+ * never drift apart. */
829
+ .twm-action-btn__kbd {
830
+ margin-left: 4px;
831
+ padding: 0 4px;
832
+ font-family: var(--ea-mono, monospace);
833
+ font-size: 10px;
834
+ line-height: 14px;
835
+ color: var(--ea-text-dim);
836
+ border: 1px solid var(--ea-border-soft);
837
+ border-radius: 2px;
838
+ background: var(--ea-bg-strip, transparent);
839
+ }
840
+ .twm-action-btn:hover .twm-action-btn__kbd {
841
+ color: var(--ea-text);
842
+ border-color: var(--ea-accent);
843
+ }
844
+ /* ── Starter-gallery picker (G1.3) ─────────────────────────────────
845
+ * Card grid used by every "New X" modal to choose a starter shape.
846
+ * Replaces the legacy template <select> dropdowns. Cards stack
847
+ * naturally into rows of 2–3 depending on modal width; selecting a
848
+ * card simply toggles `.is-selected`. */
849
+ .twm-gallery-picker {
850
+ display: grid;
851
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
852
+ gap: 8px;
853
+ align-content: start;
854
+ max-height: 320px;
855
+ overflow-y: auto;
856
+ padding: 4px;
857
+ border: 1px solid var(--ea-border, #2a2a2a);
858
+ background: var(--ea-input-bg, #1e1e1e);
859
+ }
860
+ .twm-gallery-picker__empty {
861
+ grid-column: 1 / -1;
862
+ padding: 16px;
863
+ color: var(--ea-text-dim, #777);
864
+ font-style: italic;
865
+ text-align: center;
866
+ }
867
+ .twm-gallery-card {
868
+ display: flex;
869
+ flex-direction: column;
870
+ gap: 4px;
871
+ align-items: flex-start;
872
+ text-align: left;
873
+ padding: 10px 12px;
874
+ background: var(--ea-bg-content, #181818);
875
+ border: 1px solid var(--ea-border, #2a2a2a);
876
+ color: var(--ea-text, #ddd);
877
+ cursor: pointer;
878
+ font-family: inherit;
879
+ font-size: 13px;
880
+ transition: background-color 0.08s ease,
881
+ border-color 0.08s ease;
882
+ }
883
+ .twm-gallery-card:hover {
884
+ background: rgba(255, 255, 255, 0.04);
885
+ border-color: var(--ea-border-soft, #3a3a3a);
886
+ }
887
+ .twm-gallery-card__label {
888
+ font-weight: 600;
889
+ line-height: 1.2;
890
+ }
891
+ .twm-gallery-card__desc {
892
+ margin: 0;
893
+ color: var(--ea-text-muted, #999);
894
+ font-size: 11.5px;
895
+ line-height: 1.35;
896
+ }
897
+ /* Resource-kind picker — verdict-driven dropdown row tinting + the
898
+ * Requirements panel below the picker. Same colour family as the
899
+ * signature panel (match=green, override/partial=amber, missing=red)
900
+ * so the two views read consistently. */
901
+ .twm-autocomplete-field__item[data-type="ok"] { /* default */ }
902
+ .twm-autocomplete-field__item[data-type="partial"] {
903
+ background: rgba(204, 120, 65, 0.08);
904
+ }
905
+ .twm-autocomplete-field__item[data-type="missing"] {
906
+ background: rgba(220, 80, 80, 0.10);
907
+ color: var(--ea-text-dim, rgba(255, 255, 255, 0.55));
908
+ }
909
+ .twm-autocomplete-field__item[data-type="ok"] .twm-autocomplete-field__badge { color: #69f0ae; }
910
+ .twm-autocomplete-field__item[data-type="partial"] .twm-autocomplete-field__badge { color: #ffd180; }
911
+ .twm-autocomplete-field__item[data-type="missing"] .twm-autocomplete-field__badge { color: #f48fb1; }
912
+
913
+ /* ── from tiling/tile.css ───────────────────────── */
914
+ /* tile.css — WM containers, leaves, chrome, splitters. */
915
+
916
+ .twm-shell__main {
917
+ overflow: hidden;
918
+ position: relative;
919
+ }
920
+ .twm-root {
921
+ display: flex;
922
+ height: 100%;
923
+ width: 100%;
924
+ background: var(--surface-app);
925
+ }
926
+ .twm-empty {
927
+ display: flex;
928
+ height: 100%;
929
+ width: 100%;
930
+ align-items: center;
931
+ justify-content: center;
932
+ color: var(--text-muted);
933
+ font-size: var(--font-size-xs);
934
+ }
935
+ /* Split + slot — flex containers along one axis. */
936
+ .twm-split {
937
+ display: flex;
938
+ flex: 1 1 auto;
939
+ overflow: hidden;
940
+ min-width: 0;
941
+ min-height: 0;
942
+ }
943
+ .twm-split--h { flex-direction: row; }
944
+ .twm-split--v { flex-direction: column; }
945
+ .twm-slot {
946
+ display: flex;
947
+ overflow: hidden;
948
+ min-width: 0;
949
+ min-height: 0;
950
+ }
951
+ .twm-slot > .twm-leaf,
952
+ .twm-slot > .twm-split { flex: 1 1 auto; min-width: 0; min-height: 0; }
953
+ /* Splitter handle between siblings. */
954
+ .twm-splitter {
955
+ flex: 0 0 4px;
956
+ background: transparent;
957
+ position: relative;
958
+ z-index: 1;
959
+ }
960
+ .twm-splitter:hover,
961
+ body.twm-dragging .twm-splitter { background: var(--accent-bright); opacity: 0.5; }
962
+ .twm-splitter--h { cursor: ew-resize; }
963
+ .twm-splitter--v { cursor: ns-resize; height: 4px; flex: 0 0 4px; }
964
+ body.twm-dragging * { user-select: none; }
965
+ /* Leaf — chrome + body. */
966
+ .twm-leaf {
967
+ display: flex;
968
+ flex-direction: column;
969
+ flex: 1 1 auto;
970
+ background: var(--surface-0);
971
+ border: 1px solid var(--border-subtle);
972
+ border-radius: 0;
973
+ margin: 0;
974
+ overflow: hidden;
975
+ min-width: 0;
976
+ min-height: 0;
977
+ outline: none;
978
+ }
979
+ .twm-leaf--focused {
980
+ border-color: var(--accent-bright);
981
+ box-shadow: 0 0 0 1px rgba(0, 122, 204, 0.25) inset;
982
+ }
983
+ .twm-leaf__chrome {
984
+ display: flex;
985
+ align-items: center;
986
+ justify-content: space-between;
987
+ gap: var(--space-md);
988
+ padding: 0 var(--space-sm);
989
+ height: 22px;
990
+ /* Pin the chrome like its sibling .twm-leaf__tabbar. Without
991
+ * flex-shrink:0 the chrome inherits the default `0 1 auto` and is the
992
+ * only fixed-height bar the flex column may shrink: a tab whose content
993
+ * has a tall max-content height (e.g. Watch — rows min-height:60px +
994
+ * Monaco editor + hits + help aside) inflates .twm-leaf__body's
995
+ * flex-basis:auto, and the negative free space gets distributed onto
996
+ * the chrome too, visibly changing its height. The body (overflow:auto)
997
+ * is what should absorb the overflow, never the chrome. */
998
+ flex: 0 0 auto;
999
+ background: var(--surface-1);
1000
+ border-bottom: 1px solid var(--border-subtle);
1001
+ user-select: none;
1002
+ font-size: var(--font-size-xs);
1003
+ }
1004
+ .twm-leaf--focused .twm-leaf__chrome {
1005
+ background: var(--surface-2);
1006
+ color: var(--text-primary);
1007
+ }
1008
+ .twm-leaf__title {
1009
+ color: var(--text-secondary);
1010
+ white-space: nowrap;
1011
+ text-overflow: ellipsis;
1012
+ overflow: hidden;
1013
+ text-transform: lowercase;
1014
+ letter-spacing: 0.02em;
1015
+ }
1016
+ .twm-leaf__actions { display: inline-flex; gap: 2px; }
1017
+ .twm-leaf__btn {
1018
+ display: inline-flex;
1019
+ align-items: center;
1020
+ justify-content: center;
1021
+ width: 18px;
1022
+ height: 18px;
1023
+ padding: 0;
1024
+ background: transparent;
1025
+ border: none;
1026
+ color: var(--text-muted);
1027
+ cursor: pointer;
1028
+ border-radius: 0;
1029
+ }
1030
+ .twm-leaf__btn:hover { background: var(--state-hover); color: var(--text-primary); }
1031
+ .twm-leaf__body {
1032
+ flex: 1 1 auto;
1033
+ overflow: auto;
1034
+ min-height: 0;
1035
+ background: var(--surface-0);
1036
+ }
1037
+ /* Bottom tab strip — appears only when the leaf carries >1 tab.
1038
+ * Trapezoidal/spreadsheet-style tabs (slanted top corners) sit on a
1039
+ * thin bar at the bottom of the tile, sharing the visual language of
1040
+ * the bottom panel's chrome (surface-1, square corners, 11 px label).
1041
+ *
1042
+ * The hamburger button on the left opens the per-tile "browse this
1043
+ * page's content" menu (`wm.ctx.onTileTabMenu`). */
1044
+ .twm-leaf__tabbar {
1045
+ flex: 0 0 auto;
1046
+ display: flex;
1047
+ align-items: stretch;
1048
+ height: 22px;
1049
+ background: var(--surface-1);
1050
+ border-top: 1px solid var(--border-default);
1051
+ padding: 0 4px;
1052
+ overflow: hidden;
1053
+ user-select: none;
1054
+ font-size: var(--font-size-xs);
1055
+ }
1056
+ .twm-leaf__tabbar--hidden { display: none; }
1057
+ .twm-leaf__tab-hamburger {
1058
+ flex: 0 0 auto;
1059
+ display: inline-flex;
1060
+ align-items: center;
1061
+ justify-content: center;
1062
+ width: 22px;
1063
+ height: 22px;
1064
+ margin-right: 4px;
1065
+ padding: 0;
1066
+ background: transparent;
1067
+ border: none;
1068
+ color: var(--text-muted);
1069
+ cursor: pointer;
1070
+ border-radius: 0;
1071
+ }
1072
+ .twm-leaf__tab-hamburger:hover {
1073
+ color: var(--text-primary);
1074
+ background: var(--state-hover);
1075
+ }
1076
+ .twm-leaf__tabs {
1077
+ list-style: none;
1078
+ padding: 0;
1079
+ margin: 0;
1080
+ display: flex;
1081
+ align-items: stretch;
1082
+ flex: 1 1 auto;
1083
+ min-width: 0;
1084
+ overflow-x: auto;
1085
+ scrollbar-width: none;
1086
+ }
1087
+ .twm-leaf__tabs::-webkit-scrollbar { display: none; }
1088
+ .twm-leaf__tab {
1089
+ /* Spreadsheet-style PARALLELOGRAM: both left and right edges
1090
+ * slanted by ~8 px in the same direction (top-right / bottom-left
1091
+ * pulled inward), giving each tab a uniform slant. Reads as the
1092
+ * old Excel/Lotus sheet-tab look without the trapezoid's bottom
1093
+ * flare. */
1094
+ position: relative;
1095
+ display: inline-flex;
1096
+ align-items: center;
1097
+ gap: 4px;
1098
+ padding: 2px 14px 2px 14px;
1099
+ /* Tabs sit flush against each other — no spacing between them.
1100
+ * The parallelogram clip-path provides the visual separation on
1101
+ * its own (each tab's left edge starts at 8 px in, the previous
1102
+ * tab's right edge ends 8 px in; they interlock without overlap). */
1103
+ margin-right: 0;
1104
+ background: var(--surface-2);
1105
+ color: var(--text-muted);
1106
+ border: none;
1107
+ cursor: pointer;
1108
+ line-height: 1;
1109
+ white-space: nowrap;
1110
+ clip-path: polygon(8px 0, 100% 0, calc(100% - 8px) 100%, 0 100%);
1111
+ }
1112
+ .twm-leaf__tab:hover {
1113
+ background: var(--surface-3);
1114
+ color: var(--text-primary);
1115
+ }
1116
+ .twm-leaf__tab--on {
1117
+ background: var(--surface-0); /* matches the body — looks lifted */
1118
+ color: var(--text-bright);
1119
+ /* Active tab gets a subtle border on the slanted edge so it
1120
+ * separates from the surface-1 bar. Done via a layered ::before
1121
+ * because clip-path drops the actual border. */
1122
+ }
1123
+ .twm-leaf__tab--on::before {
1124
+ content: '';
1125
+ position: absolute;
1126
+ inset: 0;
1127
+ pointer-events: none;
1128
+ border-top: 1px solid var(--accent-bright, #1683c4);
1129
+ clip-path: polygon(8px 0, 100% 0, calc(100% - 8px) 100%, 0 100%);
1130
+ }
1131
+ .twm-leaf__tab-label {
1132
+ overflow: hidden;
1133
+ text-overflow: ellipsis;
1134
+ max-width: 160px;
1135
+ text-transform: lowercase;
1136
+ letter-spacing: 0.02em;
1137
+ }
1138
+ .twm-leaf__tab-close {
1139
+ display: inline-flex;
1140
+ align-items: center;
1141
+ justify-content: center;
1142
+ width: 14px;
1143
+ height: 14px;
1144
+ margin-left: 2px;
1145
+ padding: 0;
1146
+ background: transparent;
1147
+ border: none;
1148
+ color: inherit;
1149
+ opacity: 0.6;
1150
+ cursor: pointer;
1151
+ border-radius: 0;
1152
+ }
1153
+ .twm-leaf__tab-close:hover {
1154
+ opacity: 1;
1155
+ color: var(--text-bright);
1156
+ }
1157
+ /* ── Tile tab menu (hamburger pop-out) ────────────────────────────
1158
+ * Anchored above the hamburger button. Lists every entity that the
1159
+ * tile's home page would show, grouped by source, with search +
1160
+ * pagination. Click a row → opens in a new tab on the originating
1161
+ * tile (see install._tileTabMenu). */
1162
+ .twm-tile-tabmenu-overlay {
1163
+ position: fixed;
1164
+ inset: 0;
1165
+ z-index: var(--z-modal);
1166
+ pointer-events: none;
1167
+ }
1168
+ .twm-tile-tabmenu {
1169
+ pointer-events: auto;
1170
+ width: 360px;
1171
+ max-height: 480px;
1172
+ display: flex;
1173
+ flex-direction: column;
1174
+ background: var(--surface-1);
1175
+ border: 1px solid var(--border-default);
1176
+ box-shadow: var(--shadow-lg);
1177
+ }
1178
+ .twm-tile-tabmenu__head {
1179
+ display: flex;
1180
+ align-items: center;
1181
+ gap: var(--space-sm);
1182
+ padding: var(--space-sm) var(--space-md);
1183
+ background: var(--surface-2);
1184
+ border-bottom: 1px solid var(--border-subtle);
1185
+ font-size: var(--font-size-xs);
1186
+ text-transform: uppercase;
1187
+ letter-spacing: 0.04em;
1188
+ color: var(--text-secondary);
1189
+ }
1190
+ .twm-tile-tabmenu__head-icon { font-size: 14px; }
1191
+ .twm-tile-tabmenu__head-label { flex: 1 1 auto; }
1192
+ .twm-tile-tabmenu__close {
1193
+ background: transparent;
1194
+ border: none;
1195
+ color: var(--text-muted);
1196
+ cursor: pointer;
1197
+ padding: 0 4px;
1198
+ font-size: 14px;
1199
+ }
1200
+ .twm-tile-tabmenu__close:hover { color: var(--text-primary); }
1201
+ .twm-tile-tabmenu__search {
1202
+ display: flex;
1203
+ align-items: center;
1204
+ gap: var(--space-xs);
1205
+ padding: 4px 8px;
1206
+ border-bottom: 1px solid var(--border-subtle);
1207
+ background: var(--surface-1);
1208
+ }
1209
+ .twm-tile-tabmenu__search input {
1210
+ flex: 1 1 auto;
1211
+ background: transparent;
1212
+ border: none;
1213
+ outline: none;
1214
+ color: var(--text-primary);
1215
+ font-size: var(--font-size-sm);
1216
+ }
1217
+ .twm-tile-tabmenu__body {
1218
+ flex: 1 1 auto;
1219
+ overflow-y: auto;
1220
+ padding: 4px 0;
1221
+ }
1222
+ .twm-tile-tabmenu__loading {
1223
+ padding: var(--space-md);
1224
+ color: var(--text-muted);
1225
+ font-size: var(--font-size-xs);
1226
+ text-align: center;
1227
+ }
1228
+ .twm-tile-tabmenu__section {
1229
+ padding: 0 0 4px;
1230
+ border-bottom: 1px solid var(--border-subtle);
1231
+ }
1232
+ .twm-tile-tabmenu__section:last-child { border-bottom: none; }
1233
+ .twm-tile-tabmenu__section-head {
1234
+ display: flex;
1235
+ align-items: center;
1236
+ gap: var(--space-xs);
1237
+ padding: 4px 8px;
1238
+ color: var(--text-muted);
1239
+ font-size: 10px;
1240
+ text-transform: uppercase;
1241
+ letter-spacing: 0.05em;
1242
+ }
1243
+ .twm-tile-tabmenu__section-label { flex: 1 1 auto; }
1244
+ .twm-tile-tabmenu__section-count {
1245
+ color: var(--text-muted);
1246
+ background: var(--surface-2);
1247
+ padding: 0 4px;
1248
+ font-size: 9px;
1249
+ }
1250
+ .twm-tile-tabmenu__list {
1251
+ list-style: none;
1252
+ margin: 0;
1253
+ padding: 0;
1254
+ }
1255
+ .twm-tile-tabmenu__item {
1256
+ display: grid;
1257
+ grid-template-columns: 1fr auto;
1258
+ align-items: center;
1259
+ gap: var(--space-sm);
1260
+ padding: 3px 12px;
1261
+ cursor: pointer;
1262
+ color: var(--text-secondary);
1263
+ font-size: var(--font-size-sm);
1264
+ }
1265
+ .twm-tile-tabmenu__item:hover,
1266
+ .twm-tile-tabmenu__item--cursor {
1267
+ background: var(--state-selected);
1268
+ color: var(--text-bright);
1269
+ }
1270
+ .twm-tile-tabmenu__item--cursor {
1271
+ /* Keyboard cursor reads slightly heavier than hover so a
1272
+ * mouse-cursor-on-a-different-row state is still legible. */
1273
+ outline: 1px solid var(--accent-bright, #1683c4);
1274
+ outline-offset: -1px;
1275
+ }
1276
+ .twm-tile-tabmenu__item-hint {
1277
+ color: var(--text-muted);
1278
+ font-size: var(--font-size-xs);
1279
+ }
1280
+ .twm-tile-tabmenu__empty {
1281
+ padding: 6px 12px;
1282
+ color: var(--text-muted);
1283
+ font-size: var(--font-size-xs);
1284
+ }
1285
+ .twm-tile-tabmenu__pager {
1286
+ display: flex;
1287
+ align-items: center;
1288
+ justify-content: center;
1289
+ gap: var(--space-md);
1290
+ padding: 4px 8px;
1291
+ color: var(--text-muted);
1292
+ font-size: var(--font-size-xs);
1293
+ }
1294
+ .twm-tile-tabmenu__pager button {
1295
+ background: transparent;
1296
+ border: 1px solid var(--border-subtle);
1297
+ color: inherit;
1298
+ padding: 1px 6px;
1299
+ cursor: pointer;
1300
+ border-radius: 0;
1301
+ }
1302
+ .twm-tile-tabmenu__pager button:disabled {
1303
+ opacity: 0.4;
1304
+ cursor: not-allowed;
1305
+ }
1306
+
1307
+ /* ── from tiling/command_palette.css ───────────────────────── */
1308
+ /* command_palette.css — Ctrl+K popup. */
1309
+
1310
+ .twm-cmdpal-overlay {
1311
+ position: fixed;
1312
+ inset: 0;
1313
+ z-index: var(--z-modal);
1314
+ background: rgba(0, 0, 0, 0.55);
1315
+ display: flex;
1316
+ align-items: flex-start;
1317
+ justify-content: center;
1318
+ padding-top: 12vh;
1319
+ }
1320
+ .twm-cmdpal {
1321
+ width: min(640px, 90vw);
1322
+ background: var(--surface-1);
1323
+ border: 1px solid var(--border-default);
1324
+ border-radius: 0;
1325
+ box-shadow: var(--shadow-lg);
1326
+ overflow: hidden;
1327
+ display: flex;
1328
+ flex-direction: column;
1329
+ }
1330
+ /* Input row hosts the optional type-filter chip + the actual text
1331
+ * input. The chip sits to the left of the caret; typing `<prefix>:`
1332
+ * promotes the prefix into the chip and strips it from the input
1333
+ * (see command_palette.js `tryConsumePrefix`). */
1334
+ .twm-cmdpal__inputrow {
1335
+ display: flex;
1336
+ align-items: center;
1337
+ gap: var(--space-sm);
1338
+ padding: var(--space-md) var(--space-lg);
1339
+ background: var(--surface-1);
1340
+ border-bottom: 1px solid var(--border-subtle);
1341
+ }
1342
+ .twm-cmdpal__type-chip-slot:empty {
1343
+ /* Empty slot collapses entirely so the input flushes against the
1344
+ * row's left padding when no filter is set. */
1345
+ display: none;
1346
+ }
1347
+ .twm-cmdpal__type-chip {
1348
+ display: inline-flex;
1349
+ align-items: center;
1350
+ gap: var(--space-xs);
1351
+ padding: 2px 4px 2px 8px;
1352
+ background: var(--state-selected, rgba(17, 119, 179, 0.18));
1353
+ border: 1px solid var(--accent-bright, #1683c4);
1354
+ border-radius: 2px;
1355
+ color: var(--text-bright, #fff);
1356
+ font-size: var(--font-size-xs);
1357
+ white-space: nowrap;
1358
+ user-select: none;
1359
+ }
1360
+ .twm-cmdpal__type-chip-icon {
1361
+ font-size: 14px;
1362
+ }
1363
+ .twm-cmdpal__type-chip-label {
1364
+ text-transform: lowercase;
1365
+ letter-spacing: 0.02em;
1366
+ }
1367
+ .twm-cmdpal__type-chip-x {
1368
+ display: inline-flex;
1369
+ align-items: center;
1370
+ justify-content: center;
1371
+ width: 16px;
1372
+ height: 16px;
1373
+ margin-left: 2px;
1374
+ background: transparent;
1375
+ border: none;
1376
+ color: inherit;
1377
+ font-size: 14px;
1378
+ line-height: 1;
1379
+ cursor: pointer;
1380
+ border-radius: 0;
1381
+ }
1382
+ .twm-cmdpal__type-chip-x:hover {
1383
+ background: rgba(255, 255, 255, 0.12);
1384
+ }
1385
+ .twm-cmdpal__input {
1386
+ flex: 1 1 auto;
1387
+ min-width: 0;
1388
+ padding: 0;
1389
+ background: transparent;
1390
+ border: none;
1391
+ color: var(--text-primary);
1392
+ font-size: var(--font-size-lg);
1393
+ outline: none;
1394
+ }
1395
+ .twm-cmdpal__chips,
1396
+ .twm-cmdpal__toggles {
1397
+ display: flex;
1398
+ flex-wrap: wrap;
1399
+ gap: var(--space-xs);
1400
+ padding: var(--space-sm) var(--space-md);
1401
+ border-bottom: 1px solid var(--border-subtle);
1402
+ }
1403
+ .twm-chip {
1404
+ display: inline-flex;
1405
+ align-items: center;
1406
+ gap: var(--space-xs);
1407
+ padding: var(--space-xs) var(--space-sm);
1408
+ background: var(--surface-2);
1409
+ border: 1px solid var(--border-subtle);
1410
+ border-radius: 0;
1411
+ color: var(--text-secondary);
1412
+ font-size: var(--font-size-xs);
1413
+ cursor: pointer;
1414
+ }
1415
+ .twm-chip:hover {
1416
+ background: var(--surface-3);
1417
+ color: var(--text-primary);
1418
+ }
1419
+ .twm-chip--on {
1420
+ background: var(--state-selected);
1421
+ border-color: var(--accent-bright);
1422
+ color: var(--text-bright);
1423
+ }
1424
+ .twm-cmdpal__list {
1425
+ max-height: 40vh;
1426
+ overflow: auto;
1427
+ }
1428
+ .twm-cmdpal__item {
1429
+ display: grid;
1430
+ grid-template-columns: 20px 1fr auto auto;
1431
+ align-items: center;
1432
+ gap: var(--space-sm);
1433
+ padding: var(--space-xs) var(--space-md);
1434
+ cursor: pointer;
1435
+ color: var(--text-secondary);
1436
+ font-size: var(--font-size-sm);
1437
+ }
1438
+ .twm-cmdpal__item:hover,
1439
+ .twm-cmdpal__item--active {
1440
+ background: var(--state-selected);
1441
+ color: var(--text-bright);
1442
+ }
1443
+ .twm-cmdpal__icon { color: var(--text-muted); font-size: 16px; }
1444
+ .twm-cmdpal__label { font-weight: var(--font-weight-medium); }
1445
+ .twm-cmdpal__kind,
1446
+ .twm-cmdpal__hint {
1447
+ color: var(--text-muted);
1448
+ font-size: var(--font-size-xs);
1449
+ }
1450
+ .twm-cmdpal__footer {
1451
+ display: flex;
1452
+ gap: var(--space-md);
1453
+ padding: var(--space-xs) var(--space-md);
1454
+ background: var(--surface-0);
1455
+ border-top: 1px solid var(--border-subtle);
1456
+ color: var(--text-muted);
1457
+ font-size: var(--font-size-xs);
1458
+ }
1459
+ .twm-cmdpal__footer kbd {
1460
+ display: inline-block;
1461
+ padding: 1px 4px;
1462
+ background: var(--surface-2);
1463
+ border: 1px solid var(--border-subtle);
1464
+ border-radius: 0;
1465
+ font-family: var(--font-family-mono);
1466
+ font-size: 10px;
1467
+ margin: 0 1px;
1468
+ }
1469
+
1470
+ /* ── from tiling/nav.css ───────────────────────── */
1471
+ /* nav.css — left-nav panel-tile (project / files / git). */
1472
+
1473
+ .twm-nav {
1474
+ display: flex;
1475
+ flex-direction: column;
1476
+ height: 100%;
1477
+ background: var(--surface-0);
1478
+ overflow: hidden;
1479
+ }
1480
+ .twm-nav__modes {
1481
+ display: flex;
1482
+ gap: 0;
1483
+ padding: 0;
1484
+ background: var(--surface-1);
1485
+ border-bottom: 1px solid var(--border-subtle);
1486
+ }
1487
+ .twm-nav__mode {
1488
+ flex: 1 1 0;
1489
+ display: inline-flex;
1490
+ align-items: center;
1491
+ justify-content: center;
1492
+ gap: 4px;
1493
+ padding: 6px 8px;
1494
+ background: transparent;
1495
+ border: none;
1496
+ border-right: 1px solid var(--border-subtle);
1497
+ border-bottom: 2px solid transparent;
1498
+ border-radius: 0;
1499
+ color: var(--text-secondary);
1500
+ font-size: var(--font-size-xs);
1501
+ cursor: pointer;
1502
+ }
1503
+ .twm-nav__mode:last-child { border-right: none; }
1504
+ .twm-nav__mode:hover { background: var(--state-hover); }
1505
+ .twm-nav__mode--on {
1506
+ background: var(--surface-0);
1507
+ color: var(--text-bright);
1508
+ border-bottom-color: var(--accent-bright);
1509
+ }
1510
+ /* Filter strip — inherits look from sidebar.css's .twm-sidebar-controls
1511
+ * primitive. Just tweak the bottom border to match our pane chrome. */
1512
+ .twm-nav .twm-sidebar-controls.twm-nav__filter {
1513
+ border-bottom: 1px solid var(--border-subtle);
1514
+ }
1515
+ .twm-nav__body {
1516
+ flex: 1 1 auto;
1517
+ overflow: hidden;
1518
+ display: flex;
1519
+ min-height: 0;
1520
+ }
1521
+ /* Each mode pane fills the body when visible. Inline style.display
1522
+ * flips between 'flex' and 'none'. */
1523
+ .twm-nav__pane {
1524
+ flex: 1 1 auto;
1525
+ width: 100%;
1526
+ min-width: 0;
1527
+ min-height: 0;
1528
+ flex-direction: column;
1529
+ overflow: hidden;
1530
+ }
1531
+ .twm-nav__table {
1532
+ flex: 1 1 auto;
1533
+ width: 100%;
1534
+ overflow: auto;
1535
+ min-height: 0;
1536
+ }
1537
+ /* Force the file-tree section's content to be expanded since there is
1538
+ * no longer a header to toggle it. */
1539
+ .twm-nav__pane--files .twm-collapsible-content {
1540
+ display: block !important;
1541
+ }
1542
+ /* Action row above the file list — hosts the single "New file" button. */
1543
+ .twm-nav__files-actions {
1544
+ display: flex;
1545
+ gap: 4px;
1546
+ padding: 6px 6px 4px;
1547
+ border-bottom: 1px solid var(--border-subtle);
1548
+ flex: 0 0 auto;
1549
+ }
1550
+ .twm-nav__files-mount {
1551
+ flex: 1 1 auto;
1552
+ min-height: 0;
1553
+ overflow: hidden;
1554
+ display: flex;
1555
+ flex-direction: column;
1556
+ }
1557
+ /* Reset <ul.twm-tree-view> browser defaults so list items render at the
1558
+ * left edge of the body instead of with the 40px UA padding. */
1559
+ .twm-nav__pane ul.twm-tree-view {
1560
+ list-style: none;
1561
+ margin: 0;
1562
+ padding: 0;
1563
+ }
1564
+ .twm-nav__pane .twm-tree-item {
1565
+ /* keep the .tree-item padding from main_new.css for nested chevron */
1566
+ }
1567
+ .twm-nav__icon {
1568
+ font-size: 14px;
1569
+ color: var(--text-muted);
1570
+ vertical-align: middle;
1571
+ }
1572
+ .twm-nav__error {
1573
+ padding: 12px;
1574
+ color: var(--color-danger);
1575
+ font-size: var(--font-size-xs);
1576
+ }
1577
+
1578
+ /* ── from tiling/host.css ───────────────────────── */
1579
+ /* host.css — body layout for the tiling shell.
1580
+ *
1581
+ * The Ecosim bootstrap builds .twm-global-top-bar + .container + .twm-global-bottom-bar.
1582
+ * Our installer removes .container and inserts .twm-host between the
1583
+ * top and bottom bars; this stylesheet makes the body a flex column so
1584
+ * the WM host fills the remaining space.
1585
+ */
1586
+
1587
+ /* Body is a flex column: top bar, WM host (fill), bottom bar. We
1588
+ * override the legacy `position: fixed` on both bars so they become
1589
+ * deterministic flex items and the host's height is exactly the
1590
+ * leftover space — no math, no overlap, no off-by-N pixels. Their
1591
+ * `z-index` stays put for dropdown stacking. */
1592
+ body.twm-body {
1593
+ display: flex;
1594
+ flex-direction: column;
1595
+ margin: 0;
1596
+ height: 100vh;
1597
+ width: 100vw;
1598
+ overflow: hidden;
1599
+ }
1600
+ body.twm-body > .twm-global-top-bar,
1601
+ body.twm-body > .twm-global-bottom-bar {
1602
+ position: relative !important;
1603
+ inset: auto !important;
1604
+ flex: 0 0 auto;
1605
+ }
1606
+ body.twm-body > .twm-host {
1607
+ flex: 1 1 auto;
1608
+ min-height: 0;
1609
+ min-width: 0;
1610
+ display: flex;
1611
+ overflow: hidden;
1612
+ background: var(--surface-app);
1613
+ }
1614
+ /* Sim controls relocated into .twm-global-top-bar .twm-bar-right — they came
1615
+ * from .workspace-top-bar where the layout was different. Strip the
1616
+ * extra padding so they sit next to the panel-toggles cleanly. */
1617
+ .twm-global-top-bar .twm-sim-controls {
1618
+ display: inline-flex;
1619
+ align-items: center;
1620
+ gap: 4px;
1621
+ margin-right: 8px;
1622
+ padding: 0;
1623
+ }
1624
+ .twm-global-top-bar .twm-sim-controls .twm-menu-bar {
1625
+ display: inline-flex;
1626
+ align-items: center;
1627
+ gap: 2px;
1628
+ padding: 0;
1629
+ background: transparent;
1630
+ border: none;
1631
+ }
1632
+ .twm-global-top-bar .twm-sim-controls button {
1633
+ background: transparent;
1634
+ border: 1px solid transparent;
1635
+ border-radius: 0;
1636
+ color: var(--text-secondary);
1637
+ }
1638
+ .twm-global-top-bar .twm-sim-controls button:hover {
1639
+ background: var(--state-hover);
1640
+ color: var(--text-primary);
1641
+ }
1642
+ /* Desktop bar (mounted in global-bottom-bar .twm-bar-right). */
1643
+ .twm-desktops {
1644
+ display: inline-flex;
1645
+ align-items: center;
1646
+ gap: 2px;
1647
+ margin: 0 8px;
1648
+ }
1649
+ .twm-desk__btn {
1650
+ display: inline-flex;
1651
+ align-items: center;
1652
+ justify-content: center;
1653
+ min-width: 22px;
1654
+ height: 18px;
1655
+ padding: 0 6px;
1656
+ background: transparent;
1657
+ border: 1px solid var(--border-subtle);
1658
+ border-radius: 0;
1659
+ color: var(--text-secondary);
1660
+ font-family: var(--font-family-mono);
1661
+ font-size: var(--font-size-xs);
1662
+ cursor: pointer;
1663
+ }
1664
+ .twm-desk__btn:hover { background: var(--state-hover); }
1665
+ .twm-desk__btn--on {
1666
+ background: var(--state-selected);
1667
+ color: var(--text-bright);
1668
+ border-color: var(--accent-bright);
1669
+ }
1670
+ .twm-desk__btn--add { color: var(--text-muted); }
1671
+ .twm-panel-toggle-btn--on {
1672
+ background: var(--state-selected) !important;
1673
+ color: var(--text-bright) !important;
1674
+ }
1675
+ /* Hamburger button — left of the EcoAgent brand. Collapses File/Edit/
1676
+ * Help into a single popup so the top bar reads as one row of icons
1677
+ * instead of three text dropdowns competing with the page shortcuts. */
1678
+ .twm-hamburger-btn {
1679
+ display: inline-flex;
1680
+ align-items: center;
1681
+ justify-content: center;
1682
+ width: 35px;
1683
+ height: 35px;
1684
+ padding: 0;
1685
+ background: transparent;
1686
+ border: none;
1687
+ border-radius: 0;
1688
+ color: var(--text-secondary);
1689
+ cursor: pointer;
1690
+ }
1691
+ .twm-hamburger-btn:hover {
1692
+ background: var(--state-hover);
1693
+ color: var(--text-primary);
1694
+ }
1695
+ /* Hide the original File/Edit/Help dropdowns — they live inside the
1696
+ * hamburger menu now. Their `.menu-entry` buttons stay in the DOM so
1697
+ * the hamburger can trigger them via .click(). */
1698
+ .twm-global-top-bar .twm-menu-item.twm-hidden-menu-item {
1699
+ display: none !important;
1700
+ }
1701
+ /* The agent + flow tabs each shipped their own "Pop out" button into a
1702
+ * floating slide-out panel — superseded by the WM's Alt+F / promote-
1703
+ * to-window. Hide them so the chrome doesn't carry two parallel
1704
+ * affordances for the same action. */
1705
+ /* Catch-all: any pop-out trigger inside any page tile is hidden. The
1706
+ * tiling shell handles "pop a tile out into a floating window" via
1707
+ * Alt+F / the chrome promote button, so per-page pop-out buttons are
1708
+ * always redundant. */
1709
+ .twm-page-shell [data-action="popout"],
1710
+ .twm-page-shell #ea-archetype-popout,
1711
+ .twm-page-shell #ea-sector-popout {
1712
+ display: none !important;
1713
+ }
1714
+ /* Inline row-action buttons (edit / delete) on navigation tables. */
1715
+ .twm-row-action {
1716
+ display: inline-flex;
1717
+ align-items: center;
1718
+ justify-content: center;
1719
+ width: 22px;
1720
+ height: 22px;
1721
+ padding: 0;
1722
+ margin: 0 1px;
1723
+ background: transparent;
1724
+ border: 1px solid transparent;
1725
+ border-radius: 0;
1726
+ color: var(--text-muted);
1727
+ cursor: pointer;
1728
+ opacity: 0;
1729
+ transition: opacity 80ms ease, color 80ms ease, background 80ms ease;
1730
+ }
1731
+ tr:hover .twm-row-action { opacity: 1; }
1732
+ .twm-row-action:hover {
1733
+ color: var(--text-primary);
1734
+ background: var(--state-hover);
1735
+ }
1736
+ .twm-row-action--danger:hover {
1737
+ color: var(--text-bright);
1738
+ background: var(--color-danger);
1739
+ }
1740
+ .twm-row-actions-cell { white-space: nowrap; text-align: right; }
1741
+ /* Hierarchical landing rows (Markets, Agents) — archetype rows carry
1742
+ * a chevron + entity icon; instance rows are indented underneath. */
1743
+ .twm-market-row {
1744
+ display: inline-flex;
1745
+ align-items: center;
1746
+ gap: 6px;
1747
+ width: 100%;
1748
+ }
1749
+ .twm-market-row__icon {
1750
+ font-size: 14px;
1751
+ color: var(--text-muted);
1752
+ }
1753
+ .twm-market-row--archetype .twm-market-row__icon {
1754
+ color: var(--text-primary);
1755
+ }
1756
+ .twm-market-row__label {
1757
+ flex: 1 1 auto;
1758
+ min-width: 0;
1759
+ overflow: hidden;
1760
+ text-overflow: ellipsis;
1761
+ white-space: nowrap;
1762
+ }
1763
+ .twm-market-chevron {
1764
+ display: inline-flex;
1765
+ align-items: center;
1766
+ justify-content: center;
1767
+ width: 18px;
1768
+ height: 18px;
1769
+ padding: 0;
1770
+ background: transparent;
1771
+ border: none;
1772
+ color: var(--text-muted);
1773
+ cursor: pointer;
1774
+ }
1775
+ .twm-market-chevron:hover { color: var(--text-primary); }
1776
+ .twm-market-row--instance .twm-market-row__indent {
1777
+ display: inline-block;
1778
+ width: 22px;
1779
+ flex: 0 0 22px;
1780
+ }
1781
+ .twm-market-row--instance .twm-market-row__label {
1782
+ color: var(--text-secondary);
1783
+ }
1784
+ .twm-market-row__count {
1785
+ display: inline-block;
1786
+ margin-left: 6px;
1787
+ padding: 0 5px;
1788
+ color: var(--text-muted);
1789
+ font-size: 11px;
1790
+ border: 1px solid var(--border-subtle);
1791
+ line-height: 1.5;
1792
+ }
1793
+ .twm-market-chevron:disabled {
1794
+ opacity: 0.25;
1795
+ cursor: default;
1796
+ }
1797
+ /* DataTable filter strip stickiness — the legacy rule for this lived
1798
+ * only inside `.twm-preview-table-wrap--wizard`; generalize it so every
1799
+ * DataTable (landings, instance lists, …) keeps its filter row + column
1800
+ * headers visible when the body scrolls. Both rows need opaque
1801
+ * backgrounds + z-index above the data cells so rows can't bleed
1802
+ * through. */
1803
+ /* Structural split: thead lives in its own non-scrolling header wrap
1804
+ * above the scrollable body wrap. The header gets opaque chrome so it
1805
+ * reads as a single bar with the body; no `position: sticky` is
1806
+ * needed anymore since the header literally doesn't scroll. */
1807
+ .twm-data-table-component .twm-preview-table-header-wrap {
1808
+ background: var(--surface-1);
1809
+ border-bottom: 1px solid var(--border-strong);
1810
+ }
1811
+ .twm-data-table-component .twm-preview-table-header-wrap .twm-preview-table thead th {
1812
+ background: var(--surface-1);
1813
+ }
1814
+ .twm-data-table-component .twm-preview-table-header-wrap .twm-preview-table .twm-data-table__filter-row th {
1815
+ background: var(--surface-0);
1816
+ }
1817
+ /* Compact mode (P2) — used by the bottom-panel Registries tab.
1818
+ * Tighter padding + smaller font matches the surrounding bottom-panel
1819
+ * chrome (run console, bus, query) and gets ~30% more rows visible
1820
+ * in the same vertical space. Opt-in via `mode: 'compact'`. */
1821
+ .twm-data-table-component--compact {
1822
+ font-size: 11px;
1823
+ }
1824
+ .twm-data-table-component--compact .twm-preview-table th,
1825
+ .twm-data-table-component--compact .twm-preview-table td {
1826
+ padding: 2px 6px;
1827
+ line-height: 1.35;
1828
+ }
1829
+ .twm-data-table-component--compact .twm-pagination-controls {
1830
+ padding: 1px 6px !important;
1831
+ font-size: 10px !important;
1832
+ /* Tighter gap so pager reads as one strip, like the category /
1833
+ * schema filter bar above the table — not as a spaced toolbar. */
1834
+ gap: 4px !important;
1835
+ background: #181818;
1836
+ border-top: 1px solid var(--ea-border, #2a2a2a);
1837
+ border-bottom: 1px solid var(--ea-border, #2a2a2a);
1838
+ }
1839
+ .twm-data-table-component--compact .twm-data-table__filter-input {
1840
+ font-size: 10px;
1841
+ padding: 1px 4px;
1842
+ }
1843
+ /* Match the narrow chrome of the filter strip (.ea-bp-browser__cat):
1844
+ * 20px height, border-no-radius, transparent background, uppercase
1845
+ * typography. So the pager reads as a sibling of the category /
1846
+ * schema bar instead of a heavier toolbar.
1847
+ *
1848
+ * `!important` is used liberally below because DataTable.js still
1849
+ * sets per-button / per-input chrome via inline `style.cssText` —
1850
+ * inline beats CSS without `!important`. Once those inline styles
1851
+ * migrate to CSS classes we can drop the `!important`s. */
1852
+ .twm-data-table-component--compact .twm-pagination-controls .twm-pagination-btn {
1853
+ min-width: 22px !important;
1854
+ height: 20px !important;
1855
+ padding: 0 6px !important;
1856
+ background: transparent !important;
1857
+ border: 1px solid var(--ea-border, #2a2a2a) !important;
1858
+ border-radius: 0 !important;
1859
+ color: var(--ea-text-dim, #aaa) !important;
1860
+ font-size: 10px !important;
1861
+ text-transform: uppercase;
1862
+ letter-spacing: 0.04em;
1863
+ }
1864
+ .twm-data-table-component--compact .twm-pagination-controls .twm-pagination-btn:hover:not(:disabled) {
1865
+ color: var(--ea-text, #ddd) !important;
1866
+ border-color: var(--ea-accent, #1177b3) !important;
1867
+ }
1868
+ /* Text labels inside the pager — the "Rows X–Y of Z" and "Page X of
1869
+ * Y" spans don't have classes (just inline style), so target every
1870
+ * direct-span child of the controls to neutralise their inline color
1871
+ * + font-size + add the uppercase look from the filter strip. */
1872
+ /* Loading spinner — `setLoading(true)` (or returning a Promise from
1873
+ * onPageChange/onSort) overlays a translucent layer + a CSS spinner
1874
+ * over the whole table. Wrapper gets `position: relative` set
1875
+ * inline by the JS so the absolute overlay anchors correctly. */
1876
+ .twm-data-table__loading {
1877
+ position: absolute;
1878
+ inset: 0;
1879
+ z-index: 10;
1880
+ display: flex;
1881
+ align-items: center;
1882
+ justify-content: center;
1883
+ /* Translucent so the user still sees the stale rows underneath
1884
+ * and can recognise the table — the spinner says "fresh data
1885
+ * coming", not "blank". */
1886
+ background: rgba(20, 20, 20, 0.45);
1887
+ backdrop-filter: blur(1px);
1888
+ pointer-events: all; /* swallow clicks while in-flight */
1889
+ cursor: progress;
1890
+ }
1891
+ .twm-data-table__spinner {
1892
+ width: 28px;
1893
+ height: 28px;
1894
+ border: 2px solid rgba(255, 255, 255, 0.18);
1895
+ border-top-color: var(--ea-accent, #1177b3);
1896
+ border-radius: 50%;
1897
+ animation: data-table__spin 0.7s linear infinite;
1898
+ }
1899
+ .twm-data-table-component--compact .twm-pagination-controls > span {
1900
+ color: var(--ea-text-dim, #aaa) !important;
1901
+ font-size: 9.5px !important;
1902
+ text-transform: uppercase;
1903
+ letter-spacing: 0.04em;
1904
+ }
1905
+ /* Sticky-cell rules removed — header now lives in its own non-
1906
+ * scrolling container after the structural split. */
1907
+
1908
+ /* The legacy `.twm-landing__table` host has its own `overflow:auto`,
1909
+ * which fights with DataTable's inner `.twm-preview-table-wrap` scroll
1910
+ * container. When the outer scroll wins, the sticky filter row is
1911
+ * computed against the wrong ancestor and stops sticking — rows
1912
+ * scroll past the filter strip visually. Defer all scrolling to the
1913
+ * inner wrap. Same fix for the navigator's table host. */
1914
+ .twm-landing__table,
1915
+ .twm-nav__table {
1916
+ overflow: hidden !important;
1917
+ }
1918
+ .twm-landing__table > .twm-data-table-component,
1919
+ .twm-nav__table > .twm-data-table-component {
1920
+ flex: 1 1 auto;
1921
+ min-height: 0;
1922
+ }
1923
+ /* main_new.css caps every `.twm-preview-table-wrap` at 300px so the legacy
1924
+ * pre-tiling pages stayed compact. Inside the tiling shell, the wrap is
1925
+ * the SOLE scrolling surface for the landing table — capping it at
1926
+ * 300px clips the list to ~8 rows. Force the wrap to fill its flex
1927
+ * parent and rely on its own `overflow: auto` (set inline by DataTable)
1928
+ * for scrolling. */
1929
+ .twm-landing__table .twm-preview-table-wrap,
1930
+ .twm-nav__table .twm-preview-table-wrap {
1931
+ max-height: none !important;
1932
+ height: auto;
1933
+ flex: 1 1 0;
1934
+ min-height: 0;
1935
+ }
1936
+ /* Filter-row sticky rule removed — the row lives inside the non-
1937
+ * scrolling header wrap after the structural split, so no sticky
1938
+ * positioning is needed and the `top: 32px` magic-offset (which
1939
+ * broke in compact mode) is gone. */
1940
+
1941
+ /* Navigation-table density — tighten row padding for landings + the
1942
+ * navigator. The default DataTable padding of `var(--space-sm) 8px` is
1943
+ * comfortable for data-viewing tables; nav tables benefit from being
1944
+ * denser so more items fit. */
1945
+ .twm-landing .twm-data-table-component .twm-preview-table td,
1946
+ .twm-landing .twm-data-table-component .twm-preview-table th,
1947
+ .twm-nav__table .twm-data-table-component .twm-preview-table td,
1948
+ .twm-nav__table .twm-data-table-component .twm-preview-table th {
1949
+ padding: 2px 8px;
1950
+ }
1951
+ .twm-landing .twm-data-table-component .twm-preview-table .twm-data-table__filter-row th,
1952
+ .twm-nav__table .twm-data-table-component .twm-preview-table .twm-data-table__filter-row th {
1953
+ padding: 3px 4px;
1954
+ /* Column header in these tables is ~22 px tall (font + 2×2 padding
1955
+ * + 1 px border). The default filter-row `top: 32px` left a ~10 px
1956
+ * gap rows scrolled through. Snap the filter row right up against
1957
+ * the shorter header. */
1958
+ top: 22px;
1959
+ }
1960
+ /* Breadcrumb strip — vertically center content. */
1961
+ .twm-page-shell__breadcrumb {
1962
+ display: flex;
1963
+ align-items: center;
1964
+ }
1965
+ .twm-page-shell__breadcrumb .twm-tile-breadcrumb {
1966
+ flex: 1 1 auto;
1967
+ min-width: 0;
1968
+ line-height: 1;
1969
+ }
1970
+ .twm-page-shell__breadcrumb .twm-topbar-breadcrumb__list {
1971
+ display: inline-flex;
1972
+ align-items: center;
1973
+ margin: 0;
1974
+ padding: 0;
1975
+ list-style: none;
1976
+ gap: 4px;
1977
+ }
1978
+ .twm-page-shell__breadcrumb .twm-topbar-breadcrumb__item,
1979
+ .twm-page-shell__breadcrumb .twm-topbar-breadcrumb__link {
1980
+ display: inline-flex;
1981
+ align-items: center;
1982
+ gap: 4px;
1983
+ }
1984
+ /* Modal dialog styling lives in ecoagent_extra_modes.css under
1985
+ * `.twm-managed-window--modal`. Removed legacy in-host block — was a
1986
+ * competing `display: flex; flex-direction: column` that stacked
1987
+ * labels above fields and fought the canonical grid layout. */
1988
+
1989
+ /* The Object Explorer's host expects `.panel.right .right-panel-body`
1990
+ * — we recreate that shell inside the panel:right tile so the existing
1991
+ * module mounts unchanged. The legacy `.panel.right` layout rules
1992
+ * (fixed width, position, etc.) would fight the tile sizing, so we
1993
+ * reset to a plain stretchy column. */
1994
+ .twm-leaf__body .twm-panel-right {
1995
+ position: static;
1996
+ width: 100%;
1997
+ height: 100%;
1998
+ flex: 1 1 auto;
1999
+ min-width: 0;
2000
+ min-height: 0;
2001
+ display: flex;
2002
+ flex-direction: column;
2003
+ background: transparent;
2004
+ box-shadow: none;
2005
+ overflow: hidden;
2006
+ }
2007
+ /* Page-shell: every content tile gets a breadcrumb strip on top and a
2008
+ * scrollable content area below. */
2009
+ .twm-page-shell {
2010
+ display: flex;
2011
+ flex-direction: column;
2012
+ height: 100%;
2013
+ overflow: hidden;
2014
+ }
2015
+ /* Lock the breadcrumb strip to a constant height across every page
2016
+ * so the chrome doesn't visibly shift when switching between kinds
2017
+ * with different segment counts. */
2018
+ .twm-page-shell__breadcrumb {
2019
+ flex: 0 0 26px;
2020
+ min-height: 26px;
2021
+ max-height: 26px;
2022
+ border-bottom: 1px solid var(--border-subtle);
2023
+ background: var(--surface-1);
2024
+ overflow: hidden;
2025
+ }
2026
+ .twm-page-shell__breadcrumb .twm-tile-breadcrumb {
2027
+ display: flex;
2028
+ align-items: center;
2029
+ gap: 6px;
2030
+ padding: 2px 8px;
2031
+ line-height: 22px;
2032
+ }
2033
+ .twm-page-shell__content {
2034
+ flex: 1 1 auto;
2035
+ min-height: 0;
2036
+ min-width: 0;
2037
+ /* No `overflow: auto` here — each mounted page owns its own
2038
+ * scroll region (e.g. .ea-agent-editor's `.nb-structured-editor__content`).
2039
+ * Allowing the wrapper to scroll on top of that produced double
2040
+ * scrollbars + a shimmy on sub-tab switches inside agent / market
2041
+ * pages (Settings vs code). */
2042
+ overflow: hidden;
2043
+ display: flex;
2044
+ flex-direction: column;
2045
+ /* Children that need to layer over the content slot (the loading
2046
+ * overlay) anchor against this. */
2047
+ position: relative;
2048
+ }
2049
+ /* Loading overlay — shown while a page's async mount() is in flight.
2050
+ * `--pending` is the pre-show state set for the first 80ms so cache
2051
+ * hits / instant renders never strobe a spinner. After that the
2052
+ * overlay becomes visible until the mount promise resolves. */
2053
+ .twm-tile-loading {
2054
+ position: absolute;
2055
+ inset: 0;
2056
+ display: flex;
2057
+ align-items: center;
2058
+ justify-content: center;
2059
+ background: var(--ea-bg-content, #1a1a1a);
2060
+ z-index: 5;
2061
+ opacity: 1;
2062
+ transition: opacity 120ms ease-out;
2063
+ }
2064
+ .twm-tile-loading--pending {
2065
+ opacity: 0;
2066
+ pointer-events: none;
2067
+ }
2068
+ .twm-tile-loading__spinner {
2069
+ width: 18px;
2070
+ height: 18px;
2071
+ border: 2px solid var(--ea-border, #333);
2072
+ border-top-color: var(--accent, #4a90e2);
2073
+ border-radius: 50%;
2074
+ animation: twm-tile-loading-spin 0.7s linear infinite;
2075
+ }
2076
+ /* Merged home page — detail header row, then the project-setup body
2077
+ * fills the rest of the tile. */
2078
+ .twm-home-page {
2079
+ display: flex;
2080
+ flex-direction: column;
2081
+ height: 100%;
2082
+ overflow: hidden;
2083
+ }
2084
+ .twm-home-page__header {
2085
+ flex: 0 0 auto;
2086
+ }
2087
+ .twm-home-page__body {
2088
+ flex: 1 1 auto;
2089
+ min-height: 0;
2090
+ overflow: auto;
2091
+ display: flex;
2092
+ flex-direction: column;
2093
+ }
2094
+ /* Detail header — uniform height + padding across every tab. */
2095
+ .twm-page-shell__content .twm-entity-header {
2096
+ min-height: 32px;
2097
+ padding: 4px 12px !important;
2098
+ margin: 0 !important;
2099
+ background: var(--surface-1);
2100
+ border-bottom: 1px solid var(--border-subtle);
2101
+ }
2102
+ /* Bottom panel mounted as a tile — reset the legacy fixed/absolute
2103
+ * layout `.left-bottom` carries from main_new.css so it stretches to
2104
+ * fill the tile body. */
2105
+ .twm-leaf__body .twm-panel-bottom {
2106
+ position: static;
2107
+ height: 100%;
2108
+ width: 100%;
2109
+ display: flex;
2110
+ flex-direction: column;
2111
+ overflow: hidden;
2112
+ }
2113
+ .twm-leaf__body .twm-panel-bottom #bottom-tabs-content {
2114
+ flex: 1 1 auto;
2115
+ min-height: 0;
2116
+ overflow: auto;
2117
+ }
2118
+ .twm-bp-loading {
2119
+ padding: 14px;
2120
+ color: var(--text-muted);
2121
+ font-size: var(--font-size-xs);
2122
+ }
2123
+ .twm-bp-loading--err { color: var(--color-danger); }
2124
+ /* Self-contained bottom panel — sole owner of the panel:bottom tile.
2125
+ * Tab strip on top, body below; both painted synchronously by the
2126
+ * tiling shell so the panel can never end up visually empty. */
2127
+ .twm-bp {
2128
+ display: flex;
2129
+ flex-direction: column;
2130
+ height: 100%;
2131
+ width: 100%;
2132
+ overflow: hidden;
2133
+ background: var(--surface-0);
2134
+ color: var(--text-body);
2135
+ }
2136
+ /* Tab strip matches the legacy `.bottom-tabs-header` look: light row
2137
+ * with no per-tab background, uppercase labels spaced by left margin,
2138
+ * white underline on the active tab. No boxy buttons. */
2139
+ .twm-bp__tabs {
2140
+ flex: 0 0 auto;
2141
+ display: flex;
2142
+ align-items: center;
2143
+ gap: 0;
2144
+ padding: 0 0 0 4px;
2145
+ background: var(--surface-0);
2146
+ border-bottom: 1px solid #292929;
2147
+ overflow-x: auto;
2148
+ overflow-y: hidden;
2149
+ scrollbar-width: none;
2150
+ white-space: nowrap;
2151
+ }
2152
+ .twm-bp__tabs::-webkit-scrollbar { width: 0; height: 0; display: none; }
2153
+ .twm-bp__tab {
2154
+ display: inline-flex;
2155
+ align-items: center;
2156
+ gap: 0;
2157
+ height: 30px;
2158
+ line-height: 30px;
2159
+ margin-left: 10px;
2160
+ padding: 0;
2161
+ background: transparent;
2162
+ border: none;
2163
+ border-bottom: 1px solid transparent;
2164
+ color: inherit;
2165
+ text-transform: uppercase;
2166
+ font-size: var(--font-size-xs);
2167
+ cursor: pointer;
2168
+ user-select: none;
2169
+ }
2170
+ .twm-bp__tab:hover { background: transparent; color: var(--text-bright); }
2171
+ .twm-bp__tab--on {
2172
+ color: var(--text-bright);
2173
+ border-bottom-color: #fff;
2174
+ }
2175
+ .twm-bp__body {
2176
+ flex: 1 1 auto;
2177
+ min-height: 0;
2178
+ overflow: auto;
2179
+ }
2180
+ .twm-bp-pane {
2181
+ padding: 6px 10px;
2182
+ }
2183
+ .twm-bp-pane__placeholder {
2184
+ padding: 12px;
2185
+ color: var(--text-muted);
2186
+ font-size: var(--font-size-xs);
2187
+ }
2188
+ .twm-bp-log {
2189
+ width: 100%;
2190
+ border-collapse: collapse;
2191
+ font-family: var(--font-family-mono);
2192
+ font-size: var(--font-size-xs);
2193
+ }
2194
+ .twm-bp-log thead th {
2195
+ text-align: left;
2196
+ color: var(--text-muted);
2197
+ padding: 4px 8px;
2198
+ border-bottom: 1px solid var(--border-subtle);
2199
+ text-transform: uppercase;
2200
+ letter-spacing: 0.04em;
2201
+ font-size: 10px;
2202
+ font-weight: 600;
2203
+ }
2204
+ .twm-bp-log td {
2205
+ padding: 2px 8px;
2206
+ border-bottom: 1px solid var(--border-subtle);
2207
+ vertical-align: top;
2208
+ }
2209
+ .twm-bp-log__tick { color: var(--text-muted); white-space: nowrap; }
2210
+ .twm-bp-log__level {
2211
+ color: var(--text-secondary);
2212
+ text-transform: lowercase;
2213
+ white-space: nowrap;
2214
+ }
2215
+ .twm-bp-log__row--warn .twm-bp-log__level,
2216
+ .twm-bp-log__row--warn .twm-bp-log__msg { color: var(--color-warning); }
2217
+ .twm-bp-log__row--error .twm-bp-log__level,
2218
+ .twm-bp-log__row--error .twm-bp-log__msg { color: var(--color-danger); }
2219
+ .twm-bp-log__msg { color: var(--text-body); white-space: pre-wrap; word-break: break-word; }
2220
+ /* Context menu — override main_new.css's .twm-context-menu to the tighter,
2221
+ * grid-style look (icon | label) with a flat surface-1 background and
2222
+ * square corners. All callers (sidebars, tile chrome, desktop bar, …)
2223
+ * inherit this look — one implementation, one style. */
2224
+ .twm-context-menu {
2225
+ background: var(--surface-1);
2226
+ border: 1px solid var(--border-default);
2227
+ border-radius: 0;
2228
+ box-shadow: var(--shadow-md);
2229
+ padding: 4px 0;
2230
+ min-width: 200px;
2231
+ backdrop-filter: none;
2232
+ }
2233
+ .twm-context-menu-item {
2234
+ display: grid;
2235
+ grid-template-columns: 22px 1fr;
2236
+ align-items: center;
2237
+ gap: 8px;
2238
+ padding: 4px 10px;
2239
+ font-family: var(--font-family-sans);
2240
+ font-size: var(--font-size-sm);
2241
+ color: var(--text-primary);
2242
+ }
2243
+ .twm-context-menu-item.twm-delete-node {
2244
+ color: var(--color-danger);
2245
+ }
2246
+ .twm-context-menu__separator {
2247
+ height: 1px;
2248
+ margin: 4px 0;
2249
+ background: var(--border-subtle);
2250
+ }
2251
+ /* Window-placeholder body — shown in the leaf while its content is in
2252
+ * a managed window. */
2253
+ .twm-window-placeholder {
2254
+ height: 100%;
2255
+ display: flex;
2256
+ flex-direction: column;
2257
+ align-items: center;
2258
+ justify-content: center;
2259
+ gap: 8px;
2260
+ padding: 24px;
2261
+ color: var(--text-muted);
2262
+ text-align: center;
2263
+ }
2264
+ .twm-window-placeholder__icon {
2265
+ font-size: 32px;
2266
+ opacity: 0.5;
2267
+ }
2268
+ .twm-window-placeholder__title {
2269
+ color: var(--text-primary);
2270
+ font-weight: var(--font-weight-semibold);
2271
+ }
2272
+ .twm-window-placeholder__hint {
2273
+ font-size: var(--font-size-xs);
2274
+ }
2275
+ .twm-window-placeholder__btn {
2276
+ margin-top: 8px;
2277
+ padding: 4px 12px;
2278
+ background: var(--surface-2);
2279
+ border: 1px solid var(--border-subtle);
2280
+ border-radius: 0;
2281
+ color: var(--text-primary);
2282
+ cursor: pointer;
2283
+ }
2284
+ .twm-window-placeholder__btn:hover {
2285
+ background: var(--state-selected);
2286
+ color: var(--text-bright);
2287
+ border-color: var(--accent-bright);
2288
+ }
2289
+ /* Anchor bar-center next to bar-left (instead of centred). bar-right
2290
+ * gets pushed against the right edge with margin-left:auto so the
2291
+ * existing justify-content: space-between still reads correctly. */
2292
+ .twm-global-top-bar .twm-bar-left { flex: 0 0 auto; }
2293
+ .twm-global-top-bar .twm-bar-right { flex: 0 0 auto; margin-left: auto; }
2294
+ /* Top-nav buttons in .twm-bar-center — archaic console look: full-height,
2295
+ * monospace label, square, dark active-bg with white text. Each
2296
+ * button is only as wide as its content; the strip takes only as
2297
+ * much horizontal space as required. */
2298
+ .twm-global-top-bar .twm-bar-center.twm-top-nav {
2299
+ display: flex;
2300
+ align-items: stretch;
2301
+ align-self: stretch;
2302
+ gap: 0;
2303
+ padding: 0;
2304
+ flex: 0 0 auto;
2305
+ height: 100%;
2306
+ min-width: 0;
2307
+ }
2308
+ .twm-top-nav__btn {
2309
+ display: inline-flex;
2310
+ align-items: center;
2311
+ justify-content: center;
2312
+ gap: 6px;
2313
+ flex: 0 0 auto;
2314
+ min-width: 0;
2315
+ height: 100%;
2316
+ padding: 0 14px;
2317
+ background: transparent;
2318
+ border: none;
2319
+ border-right: 1px solid var(--surface-0);
2320
+ border-radius: 0;
2321
+ color: var(--text-muted);
2322
+ font-family: var(--font-family-mono);
2323
+ font-size: 11px;
2324
+ font-weight: var(--font-weight-medium);
2325
+ text-transform: uppercase;
2326
+ letter-spacing: 0.08em;
2327
+ cursor: pointer;
2328
+ transition: color 80ms ease, background 80ms ease;
2329
+ }
2330
+ .twm-top-nav__btn:last-child { border-right: none; }
2331
+ .twm-top-nav__btn:hover {
2332
+ background: rgba(255, 255, 255, 0.04);
2333
+ color: var(--text-primary);
2334
+ }
2335
+ .twm-top-nav__btn--on {
2336
+ color: var(--text-bright);
2337
+ background: var(--surface-well);
2338
+ }
2339
+ .twm-top-nav__btn--on:hover {
2340
+ background: #000;
2341
+ color: var(--text-bright);
2342
+ }
2343
+ .twm-top-nav__icon {
2344
+ font-size: 16px;
2345
+ font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20;
2346
+ }
2347
+ .twm-top-nav__label {
2348
+ /* hidden on narrow viewports; icon alone is enough */
2349
+ }