@broberg/app-shell 0.15.0 → 0.16.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.
package/README.md CHANGED
@@ -112,6 +112,34 @@ groups={[{
112
112
  - Row actions: see 0.13.0.
113
113
  - **0.15.0** — `marker: "idle"`: the same ring in grey for a finished conversation, so every row's label lines up (decorative, `aria-hidden`). `hideLabel: true` on a group: no heading row and no fold button; always open (a stored fold is ignored); `label` stays required as key + `aria-label`; testid `sidebar-group-<slug>-unlabelled`.
114
114
 
115
+ ### 0.16.0 — the app's default text size, and row tokens (F092.24)
116
+
117
+ ```tsx
118
+ <AppShell fontScale={1.2} … /> // every shell text 20 % larger — the APP's default; the user's Zoom is separate
119
+ ```
120
+
121
+ `fontScale` sets `--bas-font-scale` on the root (or set it yourself on any ancestor, e.g. `:root`); every
122
+ font size in the stylesheet is `calc(Npx * var(--bas-font-scale, 1))`. Without it: unchanged.
123
+
124
+ Sidebar rows (menu items AND history rows) read tokens — set them on your own selector to match an
125
+ approved mockup without overriding our rules. Fallbacks are the defaults:
126
+
127
+ | token | default |
128
+ |---|---|
129
+ | `--bas-item-font-size` | `calc(13px * var(--bas-font-scale, 1))` |
130
+ | `--bas-item-padding` | `7px 10px` |
131
+ | `--bas-item-gap` | `10px` |
132
+ | `--bas-item-radius` | `6px` |
133
+ | `--bas-item-icon-color` | `var(--fg-subtle)` |
134
+ | `--bas-item-active-bg` | `var(--accent-soft)` |
135
+ | `--bas-item-active-weight` | `500` |
136
+
137
+ ```css
138
+ .scout .bas-sidebar { --bas-item-font-size: 18px; --bas-item-padding: 7px 12px; --bas-item-radius: .5rem;
139
+ --bas-item-icon-color: var(--fg-muted); --bas-item-active-weight: 600; }
140
+ @media (max-width: 640px) { .scout .bas-sidebar { --bas-item-font-size: 15.5px; --bas-item-padding: 6px; } }
141
+ ```
142
+
115
143
  ## 0.11.1 — the palette list floats over the menu (F001.25)
116
144
 
117
145
  Christian 10/10: «Kunne denne ikke være en pop-over ting så dialogen ikke bliver
package/css/app-shell.css CHANGED
@@ -15,6 +15,9 @@
15
15
  */
16
16
 
17
17
  .bas-root {
18
+ /* F092.24 — --bas-font-scale = the APP's default text size (AppShell fontScale, or
19
+ * set on any ancestor); not declared here, so an app's :root value is not shadowed.
20
+ * Separate from the user's Zoom. */
18
21
  --bas-top: 56px;
19
22
  --bas-radius: var(--radius, 8px);
20
23
  --bas-ease: cubic-bezier(.4, 0, .2, 1);
@@ -90,22 +93,24 @@
90
93
  .bas-group__label {
91
94
  display: flex; align-items: center; gap: 5px; width: 100%;
92
95
  padding: 6px 4px 4px; border: 0; background: none; cursor: pointer; text-align: left;
93
- font-family: var(--font-mono, ui-monospace, monospace); font-size: 10px; letter-spacing: .08em;
96
+ font-family: var(--font-mono, ui-monospace, monospace); font-size: calc(10px * var(--bas-font-scale, 1)); letter-spacing: .08em;
94
97
  text-transform: uppercase; color: var(--fg-subtle);
95
98
  }
96
99
  .bas-group__label:hover { color: var(--fg-muted); }
97
100
  .bas-chev { display: inline-flex; transition: transform var(--bas-dur) var(--bas-ease); }
98
101
  .bas-chev.is-open { transform: rotate(90deg); }
99
102
 
103
+ /* F092.24 — row tokens: an app sets these on its own selector to match its
104
+ * approved mockup without overriding our rules. Fallbacks = the defaults. */
100
105
  .bas-item {
101
- position: relative; display: flex; align-items: center; gap: 10px; width: 100%;
102
- padding: 7px 10px; border-radius: 6px; text-decoration: none;
103
- color: var(--fg-muted); font-size: 13px;
106
+ position: relative; display: flex; align-items: center; gap: var(--bas-item-gap, 10px); width: 100%;
107
+ padding: var(--bas-item-padding, 7px 10px); border-radius: var(--bas-item-radius, 6px); text-decoration: none;
108
+ color: var(--fg-muted); font-size: var(--bas-item-font-size, calc(13px * var(--bas-font-scale, 1)));
104
109
  transition: background var(--bas-dur) var(--bas-ease), color var(--bas-dur) var(--bas-ease);
105
110
  }
106
111
  .bas-item:hover { background: var(--hover); color: var(--fg); }
107
- .bas-item.is-active { background: var(--accent-soft); color: var(--fg); font-weight: 500; }
108
- .bas-item__icon { display: inline-flex; flex: none; color: var(--fg-subtle); }
112
+ .bas-item.is-active { background: var(--bas-item-active-bg, var(--accent-soft)); color: var(--fg); font-weight: var(--bas-item-active-weight, 500); }
113
+ .bas-item__icon { display: inline-flex; flex: none; color: var(--bas-item-icon-color, var(--fg-subtle)); }
109
114
  .bas-item.is-active .bas-item__icon { color: var(--accent); }
110
115
  .bas-item__bar { position: absolute; left: -8px; top: 8px; bottom: 8px; width: 2px; border-radius: 1px; background: var(--accent); }
111
116
  .bas-item__label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@@ -114,9 +119,9 @@
114
119
  /* F092.23 — finished: the same ring, grey, so idle and working rows line up. */
115
120
  .bas-item__marker--idle { border-color: var(--border); background: transparent; }
116
121
  .bas-item__pin { display: inline-flex; flex: none; color: var(--fg-subtle); }
117
- .bas-group--plain > .bas-group__label { font-family: inherit; font-size: 12.5px; letter-spacing: 0; text-transform: none; font-weight: 500; color: var(--fg-muted); }
122
+ .bas-group--plain > .bas-group__label { font-family: inherit; font-size: calc(12.5px * var(--bas-font-scale, 1)); letter-spacing: 0; text-transform: none; font-weight: 500; color: var(--fg-muted); }
118
123
  .bas-section { margin-top: 4px; }
119
- .bas-section__label { padding: 6px 10px 2px; font-size: 11.5px; color: var(--fg-subtle); }
124
+ .bas-section__label { padding: 6px 10px 2px; font-size: calc(11.5px * var(--bas-font-scale, 1)); color: var(--fg-subtle); }
120
125
  /* F092.21 — row actions: «…» on hover/focus (always on touch), menu as a popover. */
121
126
  .bas-itemrow { position: relative; display: flex; align-items: center; }
122
127
  .bas-itemrow > .bas-item { padding-right: 34px; }
@@ -135,7 +140,7 @@
135
140
  }
136
141
  .bas-rowmenu__item, .bas-rowmenu__back {
137
142
  display: flex; align-items: center; gap: 8px; width: 100%; padding: 7px 8px; border: 0; border-radius: calc(var(--bas-radius) - 2px);
138
- background: transparent; color: var(--fg); font-size: 13px; text-align: left; cursor: pointer;
143
+ background: transparent; color: var(--fg); font-size: calc(13px * var(--bas-font-scale, 1)); text-align: left; cursor: pointer;
139
144
  }
140
145
  .bas-rowmenu__back { color: var(--fg-muted); font-weight: 500; border-bottom: 1px solid var(--border); border-radius: 0; margin-bottom: 4px; }
141
146
  .bas-rowmenu__item.is-active, .bas-rowmenu__item:hover, .bas-rowmenu__back:hover { background: var(--hover); }
@@ -144,16 +149,16 @@
144
149
  .bas-rowmenu__item.is-armed { background: var(--danger-soft, var(--hover)); font-weight: 500; }
145
150
  .bas-rowmenu__icon { display: inline-flex; color: var(--fg-subtle); }
146
151
  .bas-rowmenu__label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
147
- .bas-rowmenu__kbd { font-family: var(--font-mono, ui-monospace, monospace); font-size: 11px; color: var(--fg-subtle); }
152
+ .bas-rowmenu__kbd { font-family: var(--font-mono, ui-monospace, monospace); font-size: calc(11px * var(--bas-font-scale, 1)); color: var(--fg-subtle); }
148
153
  .bas-rowmenu__form { display: flex; gap: 6px; padding: 4px; }
149
- .bas-rowmenu__input { flex: 1; min-width: 0; padding: 6px 8px; border: 1px solid var(--border); border-radius: calc(var(--bas-radius) - 2px); background: var(--bg-sunk); color: var(--fg); font: inherit; font-size: 13px; }
150
- .bas-rowmenu__save { padding: 6px 10px; border: 0; border-radius: calc(var(--bas-radius) - 2px); background: var(--olive-btn, var(--accent)); color: var(--paper, var(--bg-card)); font-size: 12.5px; cursor: pointer; }
154
+ .bas-rowmenu__input { flex: 1; min-width: 0; padding: 6px 8px; border: 1px solid var(--border); border-radius: calc(var(--bas-radius) - 2px); background: var(--bg-sunk); color: var(--fg); font: inherit; font-size: calc(13px * var(--bas-font-scale, 1)); }
155
+ .bas-rowmenu__save { padding: 6px 10px; border: 0; border-radius: calc(var(--bas-radius) - 2px); background: var(--olive-btn, var(--accent)); color: var(--paper, var(--bg-card)); font-size: calc(12.5px * var(--bas-font-scale, 1)); cursor: pointer; }
151
156
  .bas-rowmenu__save:active { opacity: .85; }
152
157
  .bas-sidebar.is-railed .bas-section__label,
153
158
  .bas-sidebar.is-railed .bas-item__pin { display: none; }
154
159
  .bas-item__count {
155
160
  flex: none; min-width: 18px; padding: 1px 6px; border-radius: 999px; text-align: center;
156
- font-family: var(--font-mono, ui-monospace, monospace); font-size: 10px; font-weight: 500;
161
+ font-family: var(--font-mono, ui-monospace, monospace); font-size: calc(10px * var(--bas-font-scale, 1)); font-weight: 500;
157
162
  background: var(--accent-soft); color: var(--fg);
158
163
  }
159
164
 
@@ -176,7 +181,7 @@
176
181
  border: 1px solid var(--border); border-radius: var(--bas-radius);
177
182
  box-shadow: 0 12px 32px rgba(0, 0, 0, .18);
178
183
  }
179
- .bas-flyout__title { padding: 4px 8px 6px; font-size: 12px; font-weight: 500; color: var(--fg-subtle); }
184
+ .bas-flyout__title { padding: 4px 8px 6px; font-size: calc(12px * var(--bas-font-scale, 1)); font-weight: 500; color: var(--fg-subtle); }
180
185
  .bas-flyout .bas-item__label { display: block; }
181
186
 
182
187
  /* ── the rail: phone rail mode, or desktop folded with collapse="icon" ── */
@@ -221,7 +226,7 @@
221
226
  .bas-root[data-layout="inset"] .bas-sidebar__head { min-height: 56px; padding: 8px 12px; }
222
227
  .bas-contenthead__actions { display: flex; align-items: center; gap: 8px; min-width: 0; }
223
228
  .bas-contenthead__sep { flex: none; width: 1px; height: 16px; margin: 0 8px; background: var(--border); }
224
- .bas-contenthead__title { margin: 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 15px; font-weight: 500; color: var(--fg); }
229
+ .bas-contenthead__title { margin: 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: calc(15px * var(--bas-font-scale, 1)); font-weight: 500; color: var(--fg); }
225
230
 
226
231
  .bas-backdrop { display: none; }
227
232
 
@@ -265,7 +270,7 @@
265
270
  position: absolute; top: -2px; right: -2px; min-width: 17px; height: 17px; padding: 0 4px;
266
271
  border-radius: 9px; display: flex; align-items: center; justify-content: center;
267
272
  background: var(--clay-d); color: var(--paper, var(--bg-card)); border: 2px solid var(--bg);
268
- font-size: 10.5px; font-weight: 700; font-variant-numeric: tabular-nums;
273
+ font-size: calc(10.5px * var(--bas-font-scale, 1)); font-weight: 700; font-variant-numeric: tabular-nums;
269
274
  }
270
275
  .bas-overlay { position: fixed; inset: 0; z-index: 60; background: transparent; }
271
276
  .bas-overlay.is-dim { background: rgba(0, 0, 0, .34); }
@@ -280,11 +285,11 @@
280
285
  }
281
286
  .bas-panel--sheet { left: 0; right: 0; bottom: 0; max-height: 78vh; border-radius: 22px 22px 0 0; box-shadow: 0 -10px 30px rgba(0, 0, 0, .18); }
282
287
  .bas-panel__head { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid var(--border); }
283
- .bas-panel__head h3 { margin: 0; font-size: 15px; font-weight: 600; }
288
+ .bas-panel__head h3 { margin: 0; font-size: calc(15px * var(--bas-font-scale, 1)); font-weight: 600; }
284
289
  .bas-panel__list { flex: 1; overflow-y: auto; }
285
- .bas-panel__note { margin: 0; padding: 28px 16px; text-align: center; font-size: 13px; color: var(--fg-subtle); }
290
+ .bas-panel__note { margin: 0; padding: 28px 16px; text-align: center; font-size: calc(13px * var(--bas-font-scale, 1)); color: var(--fg-subtle); }
286
291
  .bas-panel__note.is-error { color: var(--danger); }
287
- .bas-link { border: 0; background: transparent; color: var(--clay-d); font-size: 12px; font-weight: 600; cursor: pointer; padding: 4px 6px; border-radius: 6px; }
292
+ .bas-link { border: 0; background: transparent; color: var(--clay-d); font-size: calc(12px * var(--bas-font-scale, 1)); font-weight: 600; cursor: pointer; padding: 4px 6px; border-radius: 6px; }
288
293
  .bas-link:hover:not(:disabled) { background: var(--hover); }
289
294
  .bas-link:disabled { color: var(--fg-faint); cursor: default; }
290
295
  .bas-note {
@@ -294,9 +299,9 @@
294
299
  .bas-note:hover { background: var(--bg-sunk); }
295
300
  .bas-note.is-read { opacity: .6; }
296
301
  .bas-note__text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
297
- .bas-note__title { font-size: 13.5px; font-weight: 600; line-height: 1.3; }
298
- .bas-note__body { font-size: 12.5px; color: var(--fg-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
299
- .bas-note__time { font-size: 11px; color: var(--fg-subtle); margin-top: 3px; }
302
+ .bas-note__title { font-size: calc(13.5px * var(--bas-font-scale, 1)); font-weight: 600; line-height: 1.3; }
303
+ .bas-note__body { font-size: calc(12.5px * var(--bas-font-scale, 1)); color: var(--fg-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
304
+ .bas-note__time { font-size: calc(11px * var(--bas-font-scale, 1)); color: var(--fg-subtle); margin-top: 3px; }
300
305
  .bas-note__dot { flex: none; align-self: center; width: 8px; height: 8px; border-radius: 50%; background: var(--clay); }
301
306
 
302
307
  /* ── user menu ── */
@@ -307,7 +312,7 @@
307
312
  transition: background var(--bas-dur) var(--bas-ease);
308
313
  }
309
314
  .bas-userbtn:hover, .bas-userbtn.is-open { background: var(--active); }
310
- .bas-userbtn__name { font-size: 13px; font-weight: 500; }
315
+ .bas-userbtn__name { font-size: calc(13px * var(--bas-font-scale, 1)); font-weight: 500; }
311
316
  .bas-avatar { border-radius: 50%; object-fit: cover; flex: none; }
312
317
  .bas-avatar--initials {
313
318
  display: inline-flex; align-items: center; justify-content: center; font-weight: 600;
@@ -320,22 +325,22 @@
320
325
  }
321
326
  .bas-menu__who { display: flex; gap: 10px; align-items: center; padding: 14px; }
322
327
  .bas-menu__whotext { min-width: 0; }
323
- .bas-menu__name { font-size: 14px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
324
- .bas-menu__email { font-family: var(--font-mono, ui-monospace, monospace); font-size: 11px; color: var(--fg-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
328
+ .bas-menu__name { font-size: calc(14px * var(--bas-font-scale, 1)); font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
329
+ .bas-menu__email { font-family: var(--font-mono, ui-monospace, monospace); font-size: calc(11px * var(--bas-font-scale, 1)); color: var(--fg-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
325
330
  .bas-sep { height: 1px; background: var(--border); margin: 4px 0; }
326
331
  .bas-mi {
327
332
  display: flex; align-items: center; gap: 10px; width: 100%; padding: 9px 14px; text-align: left;
328
- border: 0; background: transparent; color: var(--fg); font-size: 13px; text-decoration: none; cursor: pointer;
333
+ border: 0; background: transparent; color: var(--fg); font-size: calc(13px * var(--bas-font-scale, 1)); text-decoration: none; cursor: pointer;
329
334
  }
330
335
  .bas-mi:hover:not(:disabled) { background: var(--hover); }
331
336
  .bas-mi:active:not(:disabled) { background: var(--active); }
332
337
  .bas-mi:disabled { color: var(--fg-faint); cursor: progress; }
333
338
  .bas-mi.is-danger { color: var(--danger); }
334
339
  .bas-prefs { padding: 6px 14px; }
335
- .bas-pref { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 5px 0; font-size: 12.5px; color: var(--fg-muted); }
340
+ .bas-pref { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 5px 0; font-size: calc(12.5px * var(--bas-font-scale, 1)); color: var(--fg-muted); }
336
341
  .bas-seg { display: inline-flex; flex-wrap: wrap; padding: 2px; border-radius: var(--bas-radius); background: var(--bg-sunk); border: 1px solid var(--border); }
337
342
  .bas-seg > button {
338
- font-family: var(--font-mono, ui-monospace, monospace); font-size: 11px; font-weight: 500; padding: 4px 8px;
343
+ font-family: var(--font-mono, ui-monospace, monospace); font-size: calc(11px * var(--bas-font-scale, 1)); font-weight: 500; padding: 4px 8px;
339
344
  border: 0; border-radius: calc(var(--bas-radius) - 2px); background: transparent; color: var(--fg-muted); cursor: pointer; white-space: nowrap;
340
345
  }
341
346
  .bas-seg > button[aria-pressed="true"] { background: var(--olive-btn); color: var(--paper, var(--bg-card)); }
@@ -346,7 +351,7 @@
346
351
  .bas-dd { position: relative; display: inline-flex; }
347
352
  .bas-dd__btn {
348
353
  display: inline-flex; align-items: center; gap: 8px; min-width: 120px; justify-content: space-between;
349
- font-family: var(--font-mono, ui-monospace, monospace); font-size: 11px; font-weight: 500; padding: 5px 10px;
354
+ font-family: var(--font-mono, ui-monospace, monospace); font-size: calc(11px * var(--bas-font-scale, 1)); font-weight: 500; padding: 5px 10px;
350
355
  border-radius: var(--bas-radius); background: var(--bg-sunk); border: 1px solid var(--border); color: var(--fg); cursor: pointer;
351
356
  }
352
357
  .bas-dd__btn:hover { border-color: var(--fg-faint); }
@@ -358,20 +363,20 @@
358
363
  border-radius: var(--bas-radius); background: var(--bg-card); border: 1px solid var(--border);
359
364
  box-shadow: 0 12px 32px rgba(0, 0, 0, .22);
360
365
  }
361
- .bas-dd__opt { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: calc(var(--bas-radius) - 2px); font-size: 12.5px; color: var(--fg); cursor: pointer; }
366
+ .bas-dd__opt { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: calc(var(--bas-radius) - 2px); font-size: calc(12.5px * var(--bas-font-scale, 1)); color: var(--fg); cursor: pointer; }
362
367
  .bas-dd__opt.is-active { background: var(--hover); }
363
368
  .bas-dd__opt:active { background: var(--active); }
364
369
 
365
370
  /* ── page header + tabs ── */
366
371
  .bas-pagehead { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; padding: 24px 24px 12px; }
367
- .bas-pagehead__eyebrow { font-family: var(--font-mono, ui-monospace, monospace); font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase; color: var(--fg-subtle); margin-bottom: 4px; }
368
- .bas-pagehead__title { margin: 0; font-size: 24px; font-weight: 600; letter-spacing: -.01em; }
369
- .bas-pagehead__desc { margin: 6px 0 0; font-size: 13.5px; color: var(--fg-muted); }
372
+ .bas-pagehead__eyebrow { font-family: var(--font-mono, ui-monospace, monospace); font-size: calc(10.5px * var(--bas-font-scale, 1)); letter-spacing: .08em; text-transform: uppercase; color: var(--fg-subtle); margin-bottom: 4px; }
373
+ .bas-pagehead__title { margin: 0; font-size: calc(24px * var(--bas-font-scale, 1)); font-weight: 600; letter-spacing: -.01em; }
374
+ .bas-pagehead__desc { margin: 6px 0 0; font-size: calc(13.5px * var(--bas-font-scale, 1)); color: var(--fg-muted); }
370
375
  .bas-pagehead__actions { display: flex; gap: 8px; flex: none; }
371
376
  .bas-tabs { display: flex; gap: 4px; padding: 0 24px; border-bottom: 1px solid var(--border); overflow-x: auto; }
372
377
  .bas-tab {
373
378
  padding: 9px 12px; margin-bottom: -1px; border-bottom: 2px solid transparent; text-decoration: none;
374
- font-size: 13px; color: var(--fg-muted); white-space: nowrap;
379
+ font-size: calc(13px * var(--bas-font-scale, 1)); color: var(--fg-muted); white-space: nowrap;
375
380
  }
376
381
  .bas-tab:hover { color: var(--fg); }
377
382
  .bas-tab.is-active { color: var(--fg); border-bottom-color: var(--accent); font-weight: 500; }
@@ -385,19 +390,19 @@
385
390
  .bas-account__who { display: flex; flex-wrap: wrap; align-items: center; gap: 16px; min-width: 0; }
386
391
  .bas-account__avatar { display: inline-flex; flex: none; }
387
392
  .bas-account__whotext { min-width: 0; max-width: 100%; }
388
- .bas-account__name { font-size: 18px; font-weight: 600; overflow-wrap: anywhere; }
389
- .bas-account__email { font-family: var(--font-mono, ui-monospace, monospace); font-size: 12px; color: var(--fg-muted); overflow-wrap: anywhere; }
393
+ .bas-account__name { font-size: calc(18px * var(--bas-font-scale, 1)); font-weight: 600; overflow-wrap: anywhere; }
394
+ .bas-account__email { font-family: var(--font-mono, ui-monospace, monospace); font-size: calc(12px * var(--bas-font-scale, 1)); color: var(--fg-muted); overflow-wrap: anywhere; }
390
395
  .bas-account__row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; min-width: 0; max-width: 100%; }
391
396
  .bas-account__form { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
392
- .bas-account__label { font-size: 12.5px; color: var(--fg-muted); }
397
+ .bas-account__label { font-size: calc(12.5px * var(--bas-font-scale, 1)); color: var(--fg-muted); }
393
398
  .bas-account__input {
394
- flex: 1 1 220px; min-width: 0; max-width: 100%; padding: 8px 10px; font: inherit; font-size: 14px;
399
+ flex: 1 1 220px; min-width: 0; max-width: 100%; padding: 8px 10px; font: inherit; font-size: calc(14px * var(--bas-font-scale, 1));
395
400
  color: var(--fg); background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--bas-radius);
396
401
  }
397
402
  .bas-account__input:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
398
403
  .bas-account__input[readonly] { color: var(--fg-muted); background: var(--bg-sunk); }
399
404
  .bas-account__btn {
400
- display: inline-flex; align-items: center; padding: 8px 12px; font: inherit; font-size: 13px; font-weight: 500;
405
+ display: inline-flex; align-items: center; padding: 8px 12px; font: inherit; font-size: calc(13px * var(--bas-font-scale, 1)); font-weight: 500;
401
406
  border: 1px solid var(--border); border-radius: var(--bas-radius); background: transparent; color: var(--fg); cursor: pointer;
402
407
  transition: background var(--bas-dur) var(--bas-ease);
403
408
  }
@@ -408,19 +413,19 @@
408
413
  .bas-account__btn.is-primary:disabled { opacity: .55; color: var(--paper, var(--bg-card)); }
409
414
  .bas-account__btn.is-quiet { border-color: transparent; color: var(--fg-muted); }
410
415
  .bas-account__btn.is-danger { color: var(--danger); border-color: var(--danger); }
411
- .bas-account__confirm { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 8px; font-size: 13px; min-width: 0; }
412
- .bas-account__note { margin: 0; padding: 8px 12px; font-size: 13px; border-radius: var(--bas-radius); border: 1px solid var(--border); overflow-wrap: anywhere; }
416
+ .bas-account__confirm { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 8px; font-size: calc(13px * var(--bas-font-scale, 1)); min-width: 0; }
417
+ .bas-account__note { margin: 0; padding: 8px 12px; font-size: calc(13px * var(--bas-font-scale, 1)); border-radius: var(--bas-radius); border: 1px solid var(--border); overflow-wrap: anywhere; }
413
418
  .bas-account__note.is-ok { color: var(--success, var(--fg)); }
414
419
  .bas-account__note.is-error { color: var(--danger); }
415
420
  .bas-account__note.is-reauth { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; color: var(--fg); }
416
- .bas-account__muted { margin: 0; font-size: 13px; color: var(--fg-muted); }
417
- .bas-account__link { align-self: flex-start; font-size: 13px; color: var(--accent); overflow-wrap: anywhere; }
421
+ .bas-account__muted { margin: 0; font-size: calc(13px * var(--bas-font-scale, 1)); color: var(--fg-muted); }
422
+ .bas-account__link { align-self: flex-start; font-size: calc(13px * var(--bas-font-scale, 1)); color: var(--accent); overflow-wrap: anywhere; }
418
423
 
419
424
  /* ── analytics page (F097.1) ── */
420
425
  .bas-analytics { display: flex; flex-direction: column; gap: 16px; width: 100%; min-width: 0; padding: 16px 0; }
421
426
  .bas-analytics__periods { display: inline-flex; align-self: flex-start; gap: 2px; padding: 2px; border: 1px solid var(--border); border-radius: var(--bas-radius); max-width: 100%; }
422
427
  .bas-analytics__period {
423
- min-height: 36px; padding: 6px 14px; font: inherit; font-size: 13px; white-space: nowrap; color: var(--fg); background: transparent;
428
+ min-height: 36px; padding: 6px 14px; font: inherit; font-size: calc(13px * var(--bas-font-scale, 1)); white-space: nowrap; color: var(--fg); background: transparent;
424
429
  border: 0; border-radius: var(--bas-radius); cursor: pointer; transition: background var(--bas-dur) var(--bas-ease);
425
430
  }
426
431
  .bas-analytics__period:hover:not(.is-active) { background: var(--hover); }
@@ -429,19 +434,19 @@
429
434
  .bas-analytics__period:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
430
435
  .bas-analytics__tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr)); gap: 16px; }
431
436
  .bas-analytics__tile, .bas-analytics__card { min-width: 0; padding: 16px; background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--bas-radius); }
432
- .bas-analytics__label { font-size: 12.5px; color: var(--fg-muted); }
433
- .bas-analytics__value { font-size: 28px; font-weight: 600; color: var(--fg); font-variant-numeric: tabular-nums; }
434
- .bas-analytics__muted { margin: 0; font-size: 13px; color: var(--fg-muted); }
435
- .bas-analytics__note { margin: 0; padding: 12px 16px; font-size: 13px; color: var(--fg); background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--bas-radius); }
436
- .bas-analytics__h { margin: 0 0 12px; font-size: 14px; font-weight: 600; color: var(--fg); }
437
+ .bas-analytics__label { font-size: calc(12.5px * var(--bas-font-scale, 1)); color: var(--fg-muted); }
438
+ .bas-analytics__value { font-size: calc(28px * var(--bas-font-scale, 1)); font-weight: 600; color: var(--fg); font-variant-numeric: tabular-nums; }
439
+ .bas-analytics__muted { margin: 0; font-size: calc(13px * var(--bas-font-scale, 1)); color: var(--fg-muted); }
440
+ .bas-analytics__note { margin: 0; padding: 12px 16px; font-size: calc(13px * var(--bas-font-scale, 1)); color: var(--fg); background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--bas-radius); }
441
+ .bas-analytics__h { margin: 0 0 12px; font-size: calc(14px * var(--bas-font-scale, 1)); font-weight: 600; color: var(--fg); }
437
442
  /* Bars: one hue (the accent), 2px gap between them, 4px rounded top, anchored to the baseline. */
438
443
  .bas-analytics__bars { display: flex; align-items: flex-end; gap: 2px; height: 160px; min-width: 0; border-bottom: 1px solid var(--border); }
439
444
  .bas-analytics__col { flex: 1 1 0; min-width: 0; height: 100%; display: flex; align-items: flex-end; }
440
445
  .bas-analytics__col:hover .bas-analytics__bar { opacity: .75; }
441
446
  .bas-analytics__bar { width: 100%; background: var(--accent); border-radius: 4px 4px 0 0; }
442
- .bas-analytics__axis { display: flex; justify-content: space-between; margin-top: 6px; font-size: 12px; color: var(--fg-muted); }
447
+ .bas-analytics__axis { display: flex; justify-content: space-between; margin-top: 6px; font-size: calc(12px * var(--bas-font-scale, 1)); color: var(--fg-muted); }
443
448
  .bas-analytics__pages { display: flex; flex-direction: column; gap: 8px; margin: 0; padding: 0; list-style: none; }
444
- .bas-analytics__page { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; min-width: 0; font-size: 13.5px; }
449
+ .bas-analytics__page { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; min-width: 0; font-size: calc(13.5px * var(--bas-font-scale, 1)); }
445
450
  .bas-analytics__path { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--fg); }
446
451
  .bas-analytics__count { flex: none; color: var(--fg-muted); font-variant-numeric: tabular-nums; }
447
452
  .bas-analytics__sr { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
@@ -450,20 +455,20 @@
450
455
  .bas-tenant { position: relative; display: inline-flex; min-width: 0; max-width: 100%; }
451
456
  .bas-tenant__btn {
452
457
  display: inline-flex; align-items: center; gap: 6px; min-width: 0; max-width: 100%; padding: 6px 10px;
453
- font: inherit; font-size: 13px; font-weight: 500; color: var(--fg); background: transparent;
458
+ font: inherit; font-size: calc(13px * var(--bas-font-scale, 1)); font-weight: 500; color: var(--fg); background: transparent;
454
459
  border: 1px solid var(--border); border-radius: var(--bas-radius); cursor: pointer;
455
460
  }
456
461
  .bas-tenant__btn:hover, .bas-tenant__btn.is-open { background: var(--active); }
457
- .bas-tenant__btn::after { content: "▾"; font-size: 10px; color: var(--fg-muted); flex: none; }
458
- .bas-tenant__label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; font-weight: 500; }
462
+ .bas-tenant__btn::after { content: "▾"; font-size: calc(10px * var(--bas-font-scale, 1)); color: var(--fg-muted); flex: none; }
463
+ .bas-tenant__label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: calc(13px * var(--bas-font-scale, 1)); font-weight: 500; }
459
464
  .bas-tenant__menu { left: 0; right: auto; max-width: calc(100vw - 32px); }
460
465
  /* phone: the org name gives way before the page title does */
461
466
  @media (max-width: 767px) { .bas-tenant { max-width: 40vw; } }
462
467
  .bas-tenant__item { justify-content: space-between; }
463
468
  .bas-tenant__item.is-active { font-weight: 600; }
464
469
  .bas-tenant__name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
465
- .bas-tenant__role { flex: none; font-size: 11.5px; color: var(--fg-muted); }
466
- .bas-tenant__note { margin: 0; padding: 10px 14px; font-size: 12.5px; color: var(--fg-muted); }
470
+ .bas-tenant__role { flex: none; font-size: calc(11.5px * var(--bas-font-scale, 1)); color: var(--fg-muted); }
471
+ .bas-tenant__note { margin: 0; padding: 10px 14px; font-size: calc(12.5px * var(--bas-font-scale, 1)); color: var(--fg-muted); }
467
472
  .bas-tenant__note.is-error { color: var(--danger); }
468
473
 
469
474
  /* ── F092.13 confirm dialog ── */
@@ -477,9 +482,9 @@
477
482
  border: 1px solid var(--border); border-radius: var(--radius, 8px);
478
483
  box-shadow: 0 16px 48px rgba(0, 0, 0, .24);
479
484
  }
480
- .bas-confirm__title { margin: 0; font-size: 17px; font-weight: 600; }
481
- .bas-confirm__body { color: var(--fg-muted); font-size: 14px; line-height: 1.5; }
482
- .bas-confirm__error { color: var(--danger); background: var(--danger-bg); border: 1px solid var(--danger-border); border-radius: var(--radius, 8px); padding: 8px 10px; font-size: 13px; }
485
+ .bas-confirm__title { margin: 0; font-size: calc(17px * var(--bas-font-scale, 1)); font-weight: 600; }
486
+ .bas-confirm__body { color: var(--fg-muted); font-size: calc(14px * var(--bas-font-scale, 1)); line-height: 1.5; }
487
+ .bas-confirm__error { color: var(--danger); background: var(--danger-bg); border: 1px solid var(--danger-border); border-radius: var(--radius, 8px); padding: 8px 10px; font-size: calc(13px * var(--bas-font-scale, 1)); }
483
488
  .bas-confirm__actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; flex-wrap: wrap; }
484
489
  .bas-confirm__btn {
485
490
  display: inline-flex; align-items: center; gap: 8px; height: 36px; padding: 0 14px; cursor: pointer;
@@ -509,20 +514,20 @@
509
514
  }
510
515
  .bas-userbtn__text { display: flex; flex-direction: column; min-width: 0; }
511
516
  .bas-userbtn.is-sidebar .bas-userbtn__name { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
512
- .bas-userbtn__sub { font-size: 11px; color: var(--fg-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
517
+ .bas-userbtn__sub { font-size: calc(11px * var(--bas-font-scale, 1)); color: var(--fg-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
513
518
  .bas-sidebar.is-railed .bas-userbtn__text { display: none; }
514
519
  .bas-sidebar.is-railed .bas-userbtn.is-sidebar { justify-content: center; padding: 6px 0; }
515
520
  /* Positioned as position:fixed from the button by the component (the sidebar clips overflow). */
516
521
  .bas-menu.is-up { top: auto; right: auto; max-width: none; overflow-y: auto; }
517
- .bas-menu__sub { font-size: 12px; color: var(--fg-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
518
- .bas-menu__label { padding: 6px 14px 2px; font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--fg-muted); }
519
- .bas-mi__hint { margin-left: auto; font-family: var(--font-mono, ui-monospace, monospace); font-size: 11px; color: var(--fg-muted); }
522
+ .bas-menu__sub { font-size: calc(12px * var(--bas-font-scale, 1)); color: var(--fg-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
523
+ .bas-menu__label { padding: 6px 14px 2px; font-size: calc(11px * var(--bas-font-scale, 1)); font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--fg-muted); }
524
+ .bas-mi__hint { margin-left: auto; font-family: var(--font-mono, ui-monospace, monospace); font-size: calc(11px * var(--bas-font-scale, 1)); color: var(--fg-muted); }
520
525
  .bas-mi__check { width: 14px; flex: none; text-align: center; color: var(--accent); font-weight: 700; }
521
526
  .bas-mi.is-checked { font-weight: 500; }
522
527
  .bas-zoom { display: inline-flex; align-items: center; gap: 4px; border: 1px solid var(--border); border-radius: 999px; padding: 2px; }
523
528
  .bas-zoom__btn {
524
- width: 26px; height: 24px; border: 0; border-radius: 999px; background: transparent; color: var(--fg); cursor: pointer; font-size: 15px; line-height: 1;
529
+ width: 26px; height: 24px; border: 0; border-radius: 999px; background: transparent; color: var(--fg); cursor: pointer; font-size: calc(15px * var(--bas-font-scale, 1)); line-height: 1;
525
530
  }
526
531
  .bas-zoom__btn:hover:not(:disabled) { background: var(--active); }
527
532
  .bas-zoom__btn:disabled { opacity: .4; cursor: not-allowed; }
528
- .bas-zoom__value { min-width: 46px; text-align: center; font-size: 12px; font-variant-numeric: tabular-nums; }
533
+ .bas-zoom__value { min-width: 46px; text-align: center; font-size: calc(12px * var(--bas-font-scale, 1)); font-variant-numeric: tabular-nums; }
package/dist/preact.cjs CHANGED
@@ -1483,76 +1483,86 @@ function AppShell(p) {
1483
1483
  const heading = p.title ?? activeNavLabel(p.groups, p.footer, p.currentPath);
1484
1484
  const userInSidebar = p.userMenuPlacement === "sidebar-footer";
1485
1485
  const sidebarUser = userInSidebar && p.user ? /* @__PURE__ */ jsxRuntime.jsx(UserMenu, { user: p.user, ...p.userMenu, placement: "sidebar-footer", onNavigate: p.onNavigate, lang: p.lang }) : null;
1486
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { class: "bas-root", "data-testid": "app-shell", "data-mobile": mode, "data-layout": layout, children: [
1487
- inset ? null : /* @__PURE__ */ jsxRuntime.jsx(
1488
- TopBar,
1489
- {
1490
- brand: p.brand,
1491
- homeHref: p.homeHref,
1492
- onHome: () => setDrawer(false),
1493
- actions: p.actions,
1494
- notifications: p.notifications,
1495
- user: userInSidebar ? void 0 : p.user,
1496
- userMenu: p.userMenu,
1497
- onNavigate: p.onNavigate,
1498
- lang: p.lang
1499
- }
1500
- ),
1501
- /* @__PURE__ */ jsxRuntime.jsxs("div", { class: "bas-row", children: [
1502
- /* @__PURE__ */ jsxRuntime.jsx(
1503
- Sidebar,
1504
- {
1505
- groups: p.groups,
1506
- footer: p.footer,
1507
- onItemAction: p.onItemAction,
1508
- currentPath: p.currentPath,
1509
- onNavigate: p.onNavigate,
1510
- collapsed,
1511
- collapse,
1512
- brandMark: p.brandMark,
1513
- homeHref: p.homeHref,
1514
- homeLabel: p.homeLabel,
1515
- mobileOpen: drawer,
1516
- onClose: () => setDrawer(false),
1517
- mobile: mode,
1518
- header: inset || mode === "drawer" ? p.brand : void 0,
1519
- brandTestId: inset ? "brand-home" : void 0,
1520
- groupsKey: `${prefix}.groups`,
1521
- itemsKey: `${prefix}.items`,
1522
- bottom: sidebarUser,
1523
- lang: p.lang
1524
- }
1525
- ),
1526
- /* @__PURE__ */ jsxRuntime.jsxs("main", { class: "bas-main", "data-testid": "app-content", children: [
1527
- /* @__PURE__ */ jsxRuntime.jsxs("div", { class: "bas-contenthead", "data-testid": "content-header", children: [
1486
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1487
+ "div",
1488
+ {
1489
+ class: "bas-root",
1490
+ "data-testid": "app-shell",
1491
+ "data-mobile": mode,
1492
+ "data-layout": layout,
1493
+ style: p.fontScale !== void 0 ? { "--bas-font-scale": String(p.fontScale) } : void 0,
1494
+ children: [
1495
+ inset ? null : /* @__PURE__ */ jsxRuntime.jsx(
1496
+ TopBar,
1497
+ {
1498
+ brand: p.brand,
1499
+ homeHref: p.homeHref,
1500
+ onHome: () => setDrawer(false),
1501
+ actions: p.actions,
1502
+ notifications: p.notifications,
1503
+ user: userInSidebar ? void 0 : p.user,
1504
+ userMenu: p.userMenu,
1505
+ onNavigate: p.onNavigate,
1506
+ lang: p.lang
1507
+ }
1508
+ ),
1509
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { class: "bas-row", children: [
1528
1510
  /* @__PURE__ */ jsxRuntime.jsx(
1529
- "button",
1511
+ Sidebar,
1530
1512
  {
1531
- type: "button",
1532
- class: "bas-iconbtn bas-trigger",
1533
- "data-testid": "sidebar-trigger",
1534
- "aria-label": triggerLabel,
1535
- "aria-expanded": expanded,
1536
- title: triggerLabel,
1537
- onClick: onTrigger,
1538
- children: /* @__PURE__ */ jsxRuntime.jsx(IconPanelLeft, {})
1513
+ groups: p.groups,
1514
+ footer: p.footer,
1515
+ onItemAction: p.onItemAction,
1516
+ currentPath: p.currentPath,
1517
+ onNavigate: p.onNavigate,
1518
+ collapsed,
1519
+ collapse,
1520
+ brandMark: p.brandMark,
1521
+ homeHref: p.homeHref,
1522
+ homeLabel: p.homeLabel,
1523
+ mobileOpen: drawer,
1524
+ onClose: () => setDrawer(false),
1525
+ mobile: mode,
1526
+ header: inset || mode === "drawer" ? p.brand : void 0,
1527
+ brandTestId: inset ? "brand-home" : void 0,
1528
+ groupsKey: `${prefix}.groups`,
1529
+ itemsKey: `${prefix}.items`,
1530
+ bottom: sidebarUser,
1531
+ lang: p.lang
1539
1532
  }
1540
1533
  ),
1541
- heading ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1542
- /* @__PURE__ */ jsxRuntime.jsx("span", { class: "bas-contenthead__sep", "data-testid": "content-header-separator", "aria-hidden": "true" }),
1543
- /* @__PURE__ */ jsxRuntime.jsx("h1", { class: "bas-contenthead__title", "data-testid": "content-header-title", children: heading })
1544
- ] }) : null,
1545
- inset ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1546
- /* @__PURE__ */ jsxRuntime.jsx("div", { class: "bas-spacer" }),
1547
- p.actions ? /* @__PURE__ */ jsxRuntime.jsx("div", { class: "bas-contenthead__actions", children: p.actions }) : null,
1548
- /* @__PURE__ */ jsxRuntime.jsx(NotificationBell, { source: p.notifications, onNavigate: p.onNavigate, lang: p.lang }),
1549
- p.user && !userInSidebar ? /* @__PURE__ */ jsxRuntime.jsx(UserMenu, { user: p.user, ...p.userMenu, onNavigate: p.onNavigate, lang: p.lang }) : null
1550
- ] }) : null
1551
- ] }),
1552
- p.children
1553
- ] })
1554
- ] })
1555
- ] });
1534
+ /* @__PURE__ */ jsxRuntime.jsxs("main", { class: "bas-main", "data-testid": "app-content", children: [
1535
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { class: "bas-contenthead", "data-testid": "content-header", children: [
1536
+ /* @__PURE__ */ jsxRuntime.jsx(
1537
+ "button",
1538
+ {
1539
+ type: "button",
1540
+ class: "bas-iconbtn bas-trigger",
1541
+ "data-testid": "sidebar-trigger",
1542
+ "aria-label": triggerLabel,
1543
+ "aria-expanded": expanded,
1544
+ title: triggerLabel,
1545
+ onClick: onTrigger,
1546
+ children: /* @__PURE__ */ jsxRuntime.jsx(IconPanelLeft, {})
1547
+ }
1548
+ ),
1549
+ heading ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1550
+ /* @__PURE__ */ jsxRuntime.jsx("span", { class: "bas-contenthead__sep", "data-testid": "content-header-separator", "aria-hidden": "true" }),
1551
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { class: "bas-contenthead__title", "data-testid": "content-header-title", children: heading })
1552
+ ] }) : null,
1553
+ inset ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1554
+ /* @__PURE__ */ jsxRuntime.jsx("div", { class: "bas-spacer" }),
1555
+ p.actions ? /* @__PURE__ */ jsxRuntime.jsx("div", { class: "bas-contenthead__actions", children: p.actions }) : null,
1556
+ /* @__PURE__ */ jsxRuntime.jsx(NotificationBell, { source: p.notifications, onNavigate: p.onNavigate, lang: p.lang }),
1557
+ p.user && !userInSidebar ? /* @__PURE__ */ jsxRuntime.jsx(UserMenu, { user: p.user, ...p.userMenu, onNavigate: p.onNavigate, lang: p.lang }) : null
1558
+ ] }) : null
1559
+ ] }),
1560
+ p.children
1561
+ ] })
1562
+ ] })
1563
+ ]
1564
+ }
1565
+ );
1556
1566
  }
1557
1567
  function defaultReauthHref() {
1558
1568
  const here = typeof location === "undefined" ? "/" : location.pathname + location.search;