@sienklogic/plan-build-run 2.15.0 → 2.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dashboard/package.json +3 -1
  3. package/dashboard/public/css/layout.css +237 -82
  4. package/dashboard/public/css/tokens.css +59 -0
  5. package/dashboard/public/js/sidebar-toggle.js +21 -7
  6. package/dashboard/public/js/sse-client.js +99 -0
  7. package/dashboard/public/js/theme-toggle.js +46 -0
  8. package/dashboard/src/app.js +4 -0
  9. package/dashboard/src/middleware/current-phase.js +24 -0
  10. package/dashboard/src/routes/events.routes.js +5 -0
  11. package/dashboard/src/routes/index.routes.js +2 -1
  12. package/dashboard/src/routes/pages.routes.js +94 -6
  13. package/dashboard/src/services/analytics.service.js +143 -0
  14. package/dashboard/src/services/milestone.service.js +50 -4
  15. package/dashboard/src/services/roadmap.service.js +73 -0
  16. package/dashboard/src/services/todo.service.js +11 -2
  17. package/dashboard/src/services/watcher.service.js +1 -1
  18. package/dashboard/src/utils/cache.js +55 -0
  19. package/dashboard/src/views/analytics.ejs +5 -0
  20. package/dashboard/src/views/dependencies.ejs +5 -0
  21. package/dashboard/src/views/error.ejs +16 -9
  22. package/dashboard/src/views/partials/analytics-content.ejs +71 -0
  23. package/dashboard/src/views/partials/breadcrumbs.ejs +14 -0
  24. package/dashboard/src/views/partials/dashboard-content.ejs +1 -0
  25. package/dashboard/src/views/partials/dependencies-content.ejs +16 -0
  26. package/dashboard/src/views/partials/empty-state.ejs +7 -0
  27. package/dashboard/src/views/partials/head.ejs +4 -1
  28. package/dashboard/src/views/partials/header.ejs +9 -0
  29. package/dashboard/src/views/partials/layout-bottom.ejs +1 -10
  30. package/dashboard/src/views/partials/layout-top.ejs +7 -0
  31. package/dashboard/src/views/partials/milestone-detail-content.ejs +1 -0
  32. package/dashboard/src/views/partials/milestones-content.ejs +55 -19
  33. package/dashboard/src/views/partials/phase-content.ejs +1 -0
  34. package/dashboard/src/views/partials/phase-doc-content.ejs +1 -1
  35. package/dashboard/src/views/partials/phases-content.ejs +1 -0
  36. package/dashboard/src/views/partials/roadmap-content.ejs +1 -0
  37. package/dashboard/src/views/partials/sidebar.ejs +88 -43
  38. package/dashboard/src/views/partials/todo-create-content.ejs +1 -0
  39. package/dashboard/src/views/partials/todo-detail-content.ejs +5 -1
  40. package/dashboard/src/views/partials/todos-content.ejs +44 -3
  41. package/package.json +1 -1
  42. package/plugins/copilot-pbr/plugin.json +1 -1
  43. package/plugins/cursor-pbr/.cursor-plugin/plugin.json +1 -1
  44. package/plugins/pbr/.claude-plugin/plugin.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,43 @@ All notable changes to Plan-Build-Run will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.16.0](https://github.com/SienkLogic/plan-build-run/compare/plan-build-run-v2.15.0...plan-build-run-v2.16.0) (2026-02-22)
9
+
10
+
11
+ ### Features
12
+
13
+ * **11-01:** create tokens.css with dual-mode design tokens ([53afcc1](https://github.com/SienkLogic/plan-build-run/commit/53afcc1cc84e56d3d98b66a5758591f79af5c725))
14
+ * **11-01:** refactor layout.css to use semantic design tokens ([f0fa53c](https://github.com/SienkLogic/plan-build-run/commit/f0fa53c88ac4345b135033e14dc37b937a65d5dd))
15
+ * **11-02:** add theme toggle button with localStorage persistence ([f84576e](https://github.com/SienkLogic/plan-build-run/commit/f84576ec4596dedc9643b7cb76e55d14865ebffb))
16
+ * **11-02:** pin Pico.css CDN to v2.0.6 ([1e46ef6](https://github.com/SienkLogic/plan-build-run/commit/1e46ef6bd56e12222d735590d9dea3b3c7f1f5b6))
17
+ * **12-01:** add current-phase middleware for sidebar context ([d7d0e16](https://github.com/SienkLogic/plan-build-run/commit/d7d0e16419f692aa9883d9262b119dc514f7e9b0))
18
+ * **12-01:** implement mobile overlay sidebar with backdrop ([3cc59ac](https://github.com/SienkLogic/plan-build-run/commit/3cc59acf55ab90568d4f11c11df43c7a07ca43f4))
19
+ * **12-01:** redesign sidebar with collapsible sections and current phase card ([0b6aa07](https://github.com/SienkLogic/plan-build-run/commit/0b6aa076bfea6fdac3c7c855a6ad522f6d8f50ce))
20
+ * **12-02:** add breadcrumb data to routes and include partial in content templates ([a90684e](https://github.com/SienkLogic/plan-build-run/commit/a90684e7752fcf232bcaee223e392e1dd4a9d52e))
21
+ * **12-02:** create breadcrumbs partial and CSS styles ([82ba448](https://github.com/SienkLogic/plan-build-run/commit/82ba44833b09398d5abec88bf6e6a60032694412))
22
+ * **13-01:** add milestone history expandable table with stats and deliverables ([ccc82d5](https://github.com/SienkLogic/plan-build-run/commit/ccc82d59f28e67b8ab5cb0db803d228e35af8339))
23
+ * **13-01:** add todo filtering by priority, status, and search with bulk complete ([0abbf4c](https://github.com/SienkLogic/plan-build-run/commit/0abbf4c173395bfeda4b5c73f312a3820e86ad67))
24
+ * **13-02:** add dependency graph route, views, and sidebar link ([d7224c2](https://github.com/SienkLogic/plan-build-run/commit/d7224c297dcbd07ac3659e95c4047448e5c6292b))
25
+ * **13-02:** add Mermaid dependency graph generation to roadmap service ([662056b](https://github.com/SienkLogic/plan-build-run/commit/662056b703f4ef4d7910d7afb1f79444be6e11d1))
26
+ * **13-03:** add analytics route, views, and sidebar link ([750562b](https://github.com/SienkLogic/plan-build-run/commit/750562b94287f151d89d7160ab124e2dc3279abc))
27
+ * **13-03:** create analytics service with git-based metrics and TTL cache ([41eb38a](https://github.com/SienkLogic/plan-build-run/commit/41eb38aebf930ec085edaee416913c2319a6d216))
28
+ * **14-01:** add Last-Event-ID state recovery to SSE endpoint ([b41c8dd](https://github.com/SienkLogic/plan-build-run/commit/b41c8ddc0638cd1bd103110ed6aa74a1512ca137))
29
+ * **14-01:** create custom SSE client with exponential backoff ([8d75876](https://github.com/SienkLogic/plan-build-run/commit/8d758765f0465820560a72fdad745481e091f04b))
30
+ * **14-01:** reduce chokidar stability threshold to 500ms ([1ef4dc4](https://github.com/SienkLogic/plan-build-run/commit/1ef4dc49604d98f5b6d9847f6ce835ee909b519f))
31
+ * **14-02:** add hx-indicator spinners to todo complete actions ([a166d94](https://github.com/SienkLogic/plan-build-run/commit/a166d946323255696f46f0550892878eda622ebd))
32
+ * **14-02:** add TTL cache utility and integrate into analytics and milestone services ([123c2d2](https://github.com/SienkLogic/plan-build-run/commit/123c2d25ea7050515e685c05231401de76d4cd7c))
33
+ * **15-01:** add error-card styling, loading indicator, and favicon ([6f3c550](https://github.com/SienkLogic/plan-build-run/commit/6f3c550f79885010af59377a95c7dfd2e53038c3))
34
+ * **15-01:** add skip-to-content link, focus-visible styles, and ARIA labels ([47c3c9b](https://github.com/SienkLogic/plan-build-run/commit/47c3c9bcaf8f2a5e143fe1b5a8ed18e5da9b20ba))
35
+ * **15-01:** create reusable empty-state partial and integrate into views ([48c6807](https://github.com/SienkLogic/plan-build-run/commit/48c6807b9f19fcd4abc8582820155c5ccc73a244))
36
+ * **15-02:** GREEN - analytics, cache, SSE tests pass against existing code ([1f6e3c2](https://github.com/SienkLogic/plan-build-run/commit/1f6e3c2feef7ba601afe66c68e41401aa44568be))
37
+ * **15-02:** GREEN - dependencies and breadcrumbs tests pass ([8fd48fc](https://github.com/SienkLogic/plan-build-run/commit/8fd48fc63e5414cf93bec4d7afadf86b463a6f32))
38
+
39
+
40
+ ### Bug Fixes
41
+
42
+ * **14-01:** add missing #sse-status element to header ([b831104](https://github.com/SienkLogic/plan-build-run/commit/b831104feaee62a5f6768ec8060aa2387e82322c))
43
+ * **14-02:** clear milestone cache between tests to prevent stale data ([192b53c](https://github.com/SienkLogic/plan-build-run/commit/192b53cbb4777350950ed8bd0d4993d3229f1630))
44
+
8
45
  ## [2.15.0](https://github.com/SienkLogic/plan-build-run/compare/plan-build-run-v2.14.0...plan-build-run-v2.15.0) (2026-02-22)
9
46
 
10
47
 
@@ -9,7 +9,8 @@
9
9
  "scripts": {
10
10
  "start": "node bin/cli.js",
11
11
  "dev": "node --watch bin/cli.js",
12
- "test": "vitest run"
12
+ "test": "vitest run",
13
+ "test:coverage": "vitest run --coverage"
13
14
  },
14
15
  "keywords": [
15
16
  "plan-build-run",
@@ -28,6 +29,7 @@
28
29
  "marked": "^17.0.1"
29
30
  },
30
31
  "devDependencies": {
32
+ "@vitest/coverage-v8": "^4.0.18",
31
33
  "memfs": "^4.56.10",
32
34
  "supertest": "^7.2.2",
33
35
  "vitest": "^3.1.0"
@@ -4,23 +4,7 @@
4
4
 
5
5
  /* --- Custom Properties --- */
6
6
  :root {
7
- --sidebar-width: 220px;
8
- --header-height: 3.5rem;
9
7
  --content-max-width: 960px;
10
- --font-sans: 'Inter', system-ui, -apple-system, sans-serif;
11
- --font-mono: 'JetBrains Mono', ui-monospace, 'Cascadia Code', 'Fira Code', monospace;
12
- --space-xs: 0.25rem;
13
- --space-sm: 0.5rem;
14
- --space-md: 1rem;
15
- --space-lg: 1.5rem;
16
- --space-xl: 2rem;
17
- --space-2xl: 3rem;
18
- --radius-sm: 0.375rem;
19
- --radius-md: 0.5rem;
20
- --border-subtle: rgba(255, 255, 255, 0.08);
21
- --bg-surface: rgba(255, 255, 255, 0.03);
22
- --bg-surface-hover: rgba(255, 255, 255, 0.06);
23
- --text-dim: rgba(255, 255, 255, 0.5);
24
8
  }
25
9
 
26
10
  /* --- Typography Override --- */
@@ -41,14 +25,14 @@ h3 { font-size: 1.1rem; font-weight: 600; }
41
25
 
42
26
  p { line-height: 1.65; }
43
27
 
44
- small { font-size: 0.8125rem; color: var(--text-dim); }
28
+ small { font-size: 0.8125rem; color: var(--color-text-dim); }
45
29
 
46
30
  /* --- Page Grid Layout --- */
47
31
  .page-wrapper {
48
32
  display: grid;
49
33
  gap: 0;
50
- grid-template-columns: var(--sidebar-width) 1fr;
51
- grid-template-rows: var(--header-height) 1fr auto;
34
+ grid-template-columns: var(--size-sidebar) 1fr;
35
+ grid-template-rows: var(--size-header) 1fr auto;
52
36
  grid-template-areas:
53
37
  "header header"
54
38
  "sidebar content"
@@ -62,8 +46,8 @@ small { font-size: 0.8125rem; color: var(--text-dim); }
62
46
  display: flex;
63
47
  align-items: center;
64
48
  padding: 0 var(--space-xl);
65
- border-bottom: 1px solid var(--border-subtle);
66
- background: var(--bg-surface);
49
+ border-bottom: 1px solid var(--color-border);
50
+ background: var(--color-surface-raised);
67
51
  backdrop-filter: blur(8px);
68
52
  position: sticky;
69
53
  top: 0;
@@ -96,11 +80,11 @@ small { font-size: 0.8125rem; color: var(--text-dim); }
96
80
  .page-wrapper > aside.sidebar {
97
81
  grid-area: sidebar;
98
82
  padding: var(--space-lg) 0;
99
- border-right: 1px solid var(--border-subtle);
100
- background: var(--bg-surface);
83
+ border-right: 1px solid var(--color-border);
84
+ background: var(--color-surface-raised);
101
85
  position: sticky;
102
- top: var(--header-height);
103
- height: calc(100vh - var(--header-height));
86
+ top: var(--size-header);
87
+ height: calc(100vh - var(--size-header));
104
88
  overflow-y: auto;
105
89
  }
106
90
 
@@ -123,21 +107,86 @@ aside.sidebar nav a {
123
107
  border-left: 3px solid transparent;
124
108
  font-size: 0.9rem;
125
109
  font-weight: 500;
126
- color: var(--text-dim);
110
+ color: var(--color-text-dim);
127
111
  transition: all 0.15s ease;
128
112
  }
129
113
 
130
114
  aside.sidebar nav a:hover {
131
115
  color: var(--pico-color);
132
- background: var(--bg-surface-hover);
133
- border-left-color: rgba(255, 255, 255, 0.15);
116
+ background: var(--color-surface-hover);
117
+ border-left-color: var(--color-border);
134
118
  }
135
119
 
136
120
  aside.sidebar nav a[aria-current="page"] {
137
121
  font-weight: 600;
138
- color: var(--pico-primary);
139
- border-left-color: var(--pico-primary);
140
- background: var(--bg-surface-hover);
122
+ color: var(--color-accent);
123
+ border-left-color: var(--color-accent);
124
+ background: var(--color-surface-hover);
125
+ }
126
+
127
+ /* --- Sidebar: Current Phase Card --- */
128
+ .sidebar-current-phase {
129
+ padding: var(--space-md) var(--space-lg);
130
+ border-bottom: 1px solid var(--color-border);
131
+ margin-bottom: var(--space-sm);
132
+ }
133
+
134
+ .sidebar-current-phase small {
135
+ text-transform: uppercase;
136
+ letter-spacing: 0.04em;
137
+ font-size: 0.6875rem;
138
+ color: var(--color-text-dim);
139
+ }
140
+
141
+ .sidebar-current-phase a {
142
+ display: block;
143
+ text-decoration: none;
144
+ color: inherit;
145
+ margin-top: var(--space-xs);
146
+ line-height: 1.4;
147
+ }
148
+
149
+ .sidebar-current-phase a strong {
150
+ font-size: 0.875rem;
151
+ display: block;
152
+ }
153
+
154
+ .sidebar-current-phase a span {
155
+ display: block;
156
+ font-size: 0.8125rem;
157
+ color: var(--color-text-dim);
158
+ }
159
+
160
+ .sidebar-current-phase .phase-status {
161
+ font-size: 0.75rem;
162
+ text-transform: capitalize;
163
+ color: var(--color-accent);
164
+ margin-top: 2px;
165
+ }
166
+
167
+ /* --- Sidebar: Section Details --- */
168
+ aside.sidebar details {
169
+ border: none;
170
+ margin: 0;
171
+ padding: 0;
172
+ }
173
+
174
+ aside.sidebar details summary {
175
+ text-transform: uppercase;
176
+ font-size: 0.75rem;
177
+ font-weight: 600;
178
+ letter-spacing: 0.04em;
179
+ color: var(--color-text-dim);
180
+ padding: var(--space-sm) var(--space-lg);
181
+ cursor: pointer;
182
+ }
183
+
184
+ aside.sidebar details[open] summary {
185
+ color: var(--color-accent);
186
+ }
187
+
188
+ aside.sidebar details ul {
189
+ margin: 0;
141
190
  }
142
191
 
143
192
  /* --- Main Content --- */
@@ -152,27 +201,27 @@ aside.sidebar nav a[aria-current="page"] {
152
201
  .page-wrapper > footer {
153
202
  grid-area: footer;
154
203
  padding: var(--space-md) var(--space-xl);
155
- border-top: 1px solid var(--border-subtle);
204
+ border-top: 1px solid var(--color-border);
156
205
  text-align: center;
157
206
  }
158
207
 
159
208
  .page-wrapper > footer small {
160
209
  font-size: 0.75rem;
161
- color: var(--text-dim);
210
+ color: var(--color-text-dim);
162
211
  }
163
212
 
164
213
  /* --- Article Cards --- */
165
214
  article {
166
- border: 1px solid var(--border-subtle);
215
+ border: 1px solid var(--color-border);
167
216
  border-radius: var(--radius-md);
168
- background: var(--bg-surface);
217
+ background: var(--color-surface-raised);
169
218
  margin-bottom: var(--space-lg);
170
219
  }
171
220
 
172
221
  article > header {
173
- border-bottom: 1px solid var(--border-subtle);
222
+ border-bottom: 1px solid var(--color-border);
174
223
  padding: var(--space-md) var(--space-lg);
175
- background: rgba(255, 255, 255, 0.02);
224
+ background: var(--color-surface-raised);
176
225
  border-radius: var(--radius-md) var(--radius-md) 0 0;
177
226
  }
178
227
 
@@ -200,7 +249,7 @@ th {
200
249
  font-size: 0.8125rem;
201
250
  text-transform: uppercase;
202
251
  letter-spacing: 0.04em;
203
- color: var(--text-dim);
252
+ color: var(--color-text-dim);
204
253
  white-space: nowrap;
205
254
  }
206
255
 
@@ -216,18 +265,18 @@ progress {
216
265
  }
217
266
 
218
267
  progress::-webkit-progress-bar {
219
- background: rgba(255, 255, 255, 0.08);
268
+ background: var(--color-border);
220
269
  border-radius: 999px;
221
270
  }
222
271
 
223
272
  progress::-webkit-progress-value {
224
- background: var(--pico-primary);
273
+ background: var(--color-accent);
225
274
  border-radius: 999px;
226
275
  transition: width 0.4s ease;
227
276
  }
228
277
 
229
278
  progress::-moz-progress-bar {
230
- background: var(--pico-primary);
279
+ background: var(--color-accent);
231
280
  border-radius: 999px;
232
281
  }
233
282
 
@@ -242,7 +291,7 @@ details summary {
242
291
  font-weight: 500;
243
292
  cursor: pointer;
244
293
  padding: var(--space-xs) 0;
245
- color: var(--pico-primary);
294
+ color: var(--color-accent);
246
295
  }
247
296
 
248
297
  details summary:hover {
@@ -264,7 +313,7 @@ details li {
264
313
  font-size: 0.8125em;
265
314
  padding: 0.15em 0.4em;
266
315
  border-radius: var(--radius-sm);
267
- background: rgba(255, 255, 255, 0.06);
316
+ background: var(--color-surface-hover);
268
317
  }
269
318
 
270
319
  /* --- Markdown Body (rendered markdown content) --- */
@@ -281,13 +330,13 @@ details li {
281
330
 
282
331
  .markdown-body th,
283
332
  .markdown-body td {
284
- border: 1px solid var(--border-subtle);
333
+ border: 1px solid var(--color-border);
285
334
  padding: var(--space-xs) var(--space-sm);
286
335
  text-align: left;
287
336
  }
288
337
 
289
338
  .markdown-body th {
290
- background: rgba(255, 255, 255, 0.04);
339
+ background: var(--color-surface-hover);
291
340
  }
292
341
 
293
342
  .markdown-body pre {
@@ -305,8 +354,8 @@ details li {
305
354
  }
306
355
 
307
356
  .markdown-body blockquote {
308
- border-left: 3px solid var(--pico-primary);
309
- background: rgba(255, 255, 255, 0.02);
357
+ border-left: 3px solid var(--color-accent);
358
+ background: var(--color-surface-raised);
310
359
  margin: var(--space-md) 0;
311
360
  padding: var(--space-sm) var(--space-md);
312
361
  }
@@ -329,19 +378,49 @@ details li {
329
378
 
330
379
  .markdown-body hr {
331
380
  border: none;
332
- border-top: 1px solid var(--border-subtle);
381
+ border-top: 1px solid var(--color-border);
333
382
  margin: var(--space-lg) 0;
334
383
  }
335
384
 
385
+ /* --- Breadcrumbs --- */
386
+ .breadcrumbs {
387
+ display: flex;
388
+ list-style: none;
389
+ padding: 0;
390
+ margin: 0 0 var(--space-md) 0;
391
+ font-size: 0.85rem;
392
+ gap: var(--space-xs);
393
+ }
394
+
395
+ .breadcrumbs li + li::before {
396
+ content: ">";
397
+ margin-right: var(--space-xs);
398
+ color: var(--color-text-dim);
399
+ }
400
+
401
+ .breadcrumbs a {
402
+ color: var(--color-accent);
403
+ text-decoration: none;
404
+ }
405
+
406
+ .breadcrumbs a:hover {
407
+ text-decoration: underline;
408
+ }
409
+
410
+ .breadcrumbs [aria-current="page"] {
411
+ color: var(--color-text-dim);
412
+ font-weight: 500;
413
+ }
414
+
336
415
  /* --- Back Link --- */
337
416
  main > p:first-of-type > a[href="/"] {
338
417
  font-size: 0.875rem;
339
- color: var(--text-dim);
418
+ color: var(--color-text-dim);
340
419
  text-decoration: none;
341
420
  }
342
421
 
343
422
  main > p:first-of-type > a[href="/"]:hover {
344
- color: var(--pico-primary);
423
+ color: var(--color-accent);
345
424
  }
346
425
 
347
426
  /* --- SSE Connection Status --- */
@@ -363,11 +442,91 @@ main > p:first-of-type > a[href="/"]:hover {
363
442
  background-color: var(--status-not-started);
364
443
  }
365
444
 
445
+ /* --- Skip Link (Accessibility) --- */
446
+ .skip-link {
447
+ position: absolute;
448
+ left: -9999px;
449
+ top: 0;
450
+ z-index: 1000;
451
+ padding: var(--space-sm) var(--space-md);
452
+ background: var(--pico-primary);
453
+ color: var(--pico-primary-inverse);
454
+ text-decoration: none;
455
+ font-weight: 600;
456
+ }
457
+
458
+ .skip-link:focus {
459
+ position: static;
460
+ display: block;
461
+ text-align: center;
462
+ }
463
+
464
+ /* --- Focus Visible --- */
465
+ :focus-visible {
466
+ outline: 2px solid var(--pico-primary);
467
+ outline-offset: 2px;
468
+ }
469
+
470
+ /* --- Empty State --- */
471
+ .empty-state {
472
+ text-align: center;
473
+ padding: var(--space-2xl) var(--space-lg);
474
+ }
475
+
476
+ .empty-state h3 {
477
+ color: var(--color-text-dim);
478
+ font-weight: 500;
479
+ }
480
+
481
+ /* --- Error Card --- */
482
+ .error-card {
483
+ border-left: 4px solid var(--pico-del-color);
484
+ padding: var(--space-lg);
485
+ margin: var(--space-lg) 0;
486
+ background: var(--color-surface-raised);
487
+ border-radius: var(--radius-md);
488
+ }
489
+
490
+ .error-card__icon {
491
+ font-size: 1.5rem;
492
+ margin-bottom: var(--space-sm);
493
+ }
494
+
495
+ .error-card__message {
496
+ margin-bottom: var(--space-md);
497
+ }
498
+
499
+ .error-card__action a {
500
+ font-size: 0.875rem;
501
+ }
502
+
503
+ /* --- Loading Bar --- */
504
+ .loading-bar {
505
+ position: fixed;
506
+ top: 0;
507
+ left: 0;
508
+ width: 100%;
509
+ height: 3px;
510
+ z-index: 100;
511
+ display: none;
512
+ }
513
+
514
+ .htmx-request .loading-bar {
515
+ display: block;
516
+ background: var(--pico-primary);
517
+ animation: loading-shimmer 1.2s ease-in-out infinite;
518
+ }
519
+
520
+ @keyframes loading-shimmer {
521
+ 0% { transform: translateX(-100%); }
522
+ 100% { transform: translateX(100%); }
523
+ }
524
+
366
525
  /* --- Mobile hamburger toggle --- */
367
526
  .sidebar-toggle {
368
527
  display: none;
369
528
  background: none;
370
- border: 1px solid var(--border-subtle);
529
+ border: 1px solid var(--color-border);
371
530
  border-radius: var(--radius-sm);
372
531
  color: var(--pico-color);
373
532
  font-size: 1.25rem;
@@ -383,7 +542,7 @@ main > p:first-of-type > a[href="/"]:hover {
383
542
  /* Tablet: narrower sidebar */
384
543
  @media (max-width: 1024px) {
385
544
  :root {
386
- --sidebar-width: 180px;
545
+ --size-sidebar: 180px;
387
546
  }
388
547
 
389
548
  .page-wrapper > main {
@@ -391,14 +550,13 @@ main > p:first-of-type > a[href="/"]:hover {
391
550
  }
392
551
  }
393
552
 
394
- /* Mobile: sidebar collapses to top nav */
553
+ /* Mobile: sidebar overlays content */
395
554
  @media (max-width: 768px) {
396
555
  .page-wrapper {
397
556
  grid-template-columns: 1fr;
398
- grid-template-rows: var(--header-height) auto 1fr auto;
557
+ grid-template-rows: var(--size-header) 1fr auto;
399
558
  grid-template-areas:
400
559
  "header"
401
- "sidebar"
402
560
  "content"
403
561
  "footer";
404
562
  }
@@ -408,40 +566,37 @@ main > p:first-of-type > a[href="/"]:hover {
408
566
  }
409
567
 
410
568
  .page-wrapper > aside.sidebar {
411
- position: static;
412
- height: auto;
413
- border-right: none;
414
- border-bottom: 1px solid var(--border-subtle);
415
- padding: 0;
416
- overflow: visible;
417
- display: none;
418
- }
419
-
420
- .page-wrapper > aside.sidebar.open {
569
+ position: fixed;
570
+ top: var(--size-header);
571
+ left: 0;
572
+ width: 260px;
573
+ height: calc(100vh - var(--size-header));
574
+ z-index: 20;
575
+ transform: translateX(-100%);
576
+ transition: transform 0.25s ease;
421
577
  display: block;
578
+ border-right: 1px solid var(--color-border);
579
+ border-bottom: none;
580
+ padding: var(--space-lg) 0;
581
+ overflow-y: auto;
582
+ background: var(--color-surface-raised);
422
583
  }
423
584
 
424
- aside.sidebar nav ul {
425
- display: flex;
426
- flex-wrap: wrap;
427
- gap: 0;
428
- padding: var(--space-sm) var(--space-md);
429
- }
430
-
431
- aside.sidebar nav a {
432
- border-left: none;
433
- border-bottom: 2px solid transparent;
434
- padding: var(--space-sm) var(--space-md);
435
- font-size: 0.85rem;
585
+ .page-wrapper > aside.sidebar.open {
586
+ transform: translateX(0);
436
587
  }
437
588
 
438
- aside.sidebar nav a[aria-current="page"] {
439
- border-left: none;
440
- border-bottom-color: var(--pico-primary);
589
+ .sidebar-backdrop {
590
+ display: none;
591
+ position: fixed;
592
+ inset: 0;
593
+ top: var(--size-header);
594
+ background: rgba(0, 0, 0, 0.4);
595
+ z-index: 19;
441
596
  }
442
597
 
443
- aside.sidebar nav a:hover {
444
- border-left: none;
598
+ .sidebar-backdrop.open {
599
+ display: block;
445
600
  }
446
601
 
447
602
  .page-wrapper > main {
@@ -0,0 +1,59 @@
1
+ /* ============================================
2
+ Towline Dashboard — Design Tokens
3
+ Single source of truth for colors, spacing,
4
+ typography, and sizing across all CSS files.
5
+ ============================================ */
6
+
7
+ /* --- Light Mode (Default) --- */
8
+ :root {
9
+ /* Colors */
10
+ --color-surface: #ffffff;
11
+ --color-surface-raised: #f8f9fa;
12
+ --color-surface-hover: rgba(0, 0, 0, 0.04);
13
+ --color-border: rgba(0, 0, 0, 0.1);
14
+ --color-text-dim: rgba(0, 0, 0, 0.5);
15
+ --color-text: #1a1a2e;
16
+ --color-accent: var(--pico-primary);
17
+
18
+ /* Spacing */
19
+ --space-xs: 0.25rem;
20
+ --space-sm: 0.5rem;
21
+ --space-md: 1rem;
22
+ --space-lg: 1.5rem;
23
+ --space-xl: 2rem;
24
+ --space-2xl: 3rem;
25
+
26
+ /* Radii */
27
+ --radius-sm: 0.375rem;
28
+ --radius-md: 0.5rem;
29
+
30
+ /* Typography */
31
+ --font-sans: 'Inter', system-ui, -apple-system, sans-serif;
32
+ --font-mono: 'JetBrains Mono', ui-monospace, 'Cascadia Code', 'Fira Code', monospace;
33
+
34
+ /* Sizing */
35
+ --size-sidebar: 220px;
36
+ --size-header: 3.5rem;
37
+ }
38
+
39
+ /* --- Dark Mode (explicit attribute) --- */
40
+ [data-theme="dark"] {
41
+ --color-surface: #13131a;
42
+ --color-surface-raised: rgba(255, 255, 255, 0.03);
43
+ --color-surface-hover: rgba(255, 255, 255, 0.06);
44
+ --color-border: rgba(255, 255, 255, 0.08);
45
+ --color-text-dim: rgba(255, 255, 255, 0.5);
46
+ --color-text: #e8e8f0;
47
+ }
48
+
49
+ /* --- Dark Mode (system preference, unless light forced) --- */
50
+ @media (prefers-color-scheme: dark) {
51
+ :root:not([data-theme="light"]) {
52
+ --color-surface: #13131a;
53
+ --color-surface-raised: rgba(255, 255, 255, 0.03);
54
+ --color-surface-hover: rgba(255, 255, 255, 0.06);
55
+ --color-border: rgba(255, 255, 255, 0.08);
56
+ --color-text-dim: rgba(255, 255, 255, 0.5);
57
+ --color-text: #e8e8f0;
58
+ }
59
+ }
@@ -1,19 +1,33 @@
1
- // Mobile sidebar toggle
1
+ // Mobile sidebar toggle with backdrop overlay
2
2
  document.addEventListener('DOMContentLoaded', function() {
3
3
  var toggle = document.querySelector('.sidebar-toggle');
4
4
  var sidebar = document.querySelector('.sidebar');
5
- if (toggle && sidebar) {
6
- toggle.addEventListener('click', function() {
7
- sidebar.classList.toggle('open');
8
- });
5
+ if (!toggle || !sidebar) return;
6
+
7
+ // Create backdrop element
8
+ var backdrop = document.createElement('div');
9
+ backdrop.className = 'sidebar-backdrop';
10
+ document.body.appendChild(backdrop);
11
+
12
+ function closeSidebar() {
13
+ sidebar.classList.remove('open');
14
+ backdrop.classList.remove('open');
9
15
  }
10
16
 
17
+ function toggleSidebar() {
18
+ sidebar.classList.toggle('open');
19
+ backdrop.classList.toggle('open');
20
+ }
21
+
22
+ toggle.addEventListener('click', toggleSidebar);
23
+ backdrop.addEventListener('click', closeSidebar);
24
+
11
25
  // Close sidebar when a nav link is clicked (mobile)
12
- var navLinks = sidebar ? sidebar.querySelectorAll('a') : [];
26
+ var navLinks = sidebar.querySelectorAll('a');
13
27
  navLinks.forEach(function(link) {
14
28
  link.addEventListener('click', function() {
15
29
  if (window.innerWidth <= 768) {
16
- sidebar.classList.remove('open');
30
+ closeSidebar();
17
31
  }
18
32
  });
19
33
  });