@bakapiano/ccsm 0.6.0 → 0.8.3

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 (67) hide show
  1. package/README.md +172 -38
  2. package/bin/ccsm.js +194 -0
  3. package/lib/favorites.js +23 -45
  4. package/lib/jsonStore.js +60 -0
  5. package/lib/labels.js +21 -41
  6. package/lib/webTerminal.js +173 -0
  7. package/package.json +11 -3
  8. package/public/css/base.css +82 -0
  9. package/public/css/cards.css +149 -0
  10. package/public/css/feedback.css +219 -0
  11. package/public/css/forms.css +282 -0
  12. package/public/css/layout.css +107 -0
  13. package/public/css/modal.css +169 -0
  14. package/public/css/responsive.css +10 -0
  15. package/public/css/sidebar.css +165 -0
  16. package/public/css/tables.css +266 -0
  17. package/public/css/terminals.css +112 -0
  18. package/public/css/tokens.css +63 -0
  19. package/public/css/wco.css +70 -0
  20. package/public/css/widgets.css +204 -0
  21. package/public/favicon.svg +1 -1
  22. package/public/index.html +52 -490
  23. package/public/js/actions.js +87 -0
  24. package/public/js/api.js +103 -0
  25. package/public/js/backend.js +28 -0
  26. package/public/js/components/App.js +45 -0
  27. package/public/js/components/Card.js +24 -0
  28. package/public/js/components/DialogHost.js +45 -0
  29. package/public/js/components/Fab.js +11 -0
  30. package/public/js/components/FavoritesTable.js +81 -0
  31. package/public/js/components/Footer.js +12 -0
  32. package/public/js/components/NewSessionModal.js +142 -0
  33. package/public/js/components/OfflineBanner.js +52 -0
  34. package/public/js/components/PageHead.js +33 -0
  35. package/public/js/components/Pagination.js +27 -0
  36. package/public/js/components/ProgressList.js +32 -0
  37. package/public/js/components/RecentTable.js +68 -0
  38. package/public/js/components/RepoPicker.js +40 -0
  39. package/public/js/components/ReposEditor.js +74 -0
  40. package/public/js/components/ServerStatus.js +18 -0
  41. package/public/js/components/SessionsTable.js +71 -0
  42. package/public/js/components/Sidebar.js +52 -0
  43. package/public/js/components/SnapshotPanel.js +77 -0
  44. package/public/js/components/TerminalView.js +108 -0
  45. package/public/js/components/TitleCell.js +40 -0
  46. package/public/js/components/Toast.js +8 -0
  47. package/public/js/components/WorkspacePicker.js +19 -0
  48. package/public/js/components/WorkspacesGrid.js +41 -0
  49. package/public/js/dialog.js +59 -0
  50. package/public/js/html.js +6 -0
  51. package/public/js/icons.js +114 -0
  52. package/public/js/main.js +81 -0
  53. package/public/js/pages/AboutPage.js +85 -0
  54. package/public/js/pages/ConfigurePage.js +194 -0
  55. package/public/js/pages/LaunchPage.js +117 -0
  56. package/public/js/pages/SessionsPage.js +47 -0
  57. package/public/js/pages/TerminalsPage.js +74 -0
  58. package/public/js/state.js +87 -0
  59. package/public/js/streaming.js +96 -0
  60. package/public/js/toast.js +14 -0
  61. package/public/js/util.js +24 -0
  62. package/public/manifest.webmanifest +14 -0
  63. package/scripts/install.js +111 -0
  64. package/scripts/uninstall.js +56 -0
  65. package/server.js +286 -30
  66. package/public/app.js +0 -1353
  67. package/public/styles.css +0 -1639
@@ -0,0 +1,165 @@
1
+ /* Left collapsible sidebar nav · brand mark · util items · collapse toggle */
2
+
3
+ .sidebar {
4
+ position: sticky;
5
+ top: 0;
6
+ height: 100vh;
7
+ background: var(--sidebar-bg);
8
+ border-right: 1px solid var(--border);
9
+ display: flex;
10
+ flex-direction: column;
11
+ padding: var(--s-4) var(--s-3);
12
+ overflow: hidden;
13
+ transition: padding .25s cubic-bezier(.4, 0, .2, 1);
14
+ }
15
+ .sidebar[data-collapsed="true"] {
16
+ padding: var(--s-4) var(--s-2);
17
+ }
18
+
19
+ .sidebar-brand {
20
+ display: flex;
21
+ align-items: center;
22
+ gap: var(--s-2);
23
+ /* Top padding matches .main's so the brand mark sits on the same
24
+ horizontal band as the page-title. */
25
+ padding: var(--s-4) var(--s-2);
26
+ min-height: 56px;
27
+ }
28
+ .brand-mark {
29
+ display: inline-flex;
30
+ align-items: center;
31
+ justify-content: center;
32
+ width: 32px;
33
+ height: 32px;
34
+ flex: 0 0 32px;
35
+ background: transparent;
36
+ }
37
+ .brand-mark svg { display: block; }
38
+ .brand-name {
39
+ font-size: 19px;
40
+ font-weight: 600;
41
+ letter-spacing: -0.02em;
42
+ color: var(--ink);
43
+ white-space: nowrap;
44
+ opacity: 1;
45
+ transition: opacity .15s ease;
46
+ }
47
+ .brand-dot { color: var(--accent); }
48
+ .sidebar[data-collapsed="true"] .brand-name { opacity: 0; pointer-events: none; }
49
+
50
+ .sidebar-nav {
51
+ display: flex;
52
+ flex-direction: column;
53
+ gap: 2px;
54
+ flex: 0 0 auto;
55
+ }
56
+
57
+ .nav-item, .util-item {
58
+ appearance: none;
59
+ background: transparent;
60
+ border: 0;
61
+ display: flex;
62
+ align-items: center;
63
+ gap: var(--s-3);
64
+ width: 100%;
65
+ padding: 0 12px;
66
+ min-height: 42px;
67
+ border-radius: var(--r-sm);
68
+ cursor: pointer;
69
+ color: var(--ink-mid);
70
+ font-family: var(--body);
71
+ font-size: 14.5px;
72
+ font-weight: 500;
73
+ text-align: left;
74
+ transition: background .12s ease, color .12s ease;
75
+ position: relative;
76
+ }
77
+ .nav-item:hover, .util-item:hover {
78
+ background: var(--sidebar-hover);
79
+ color: var(--ink);
80
+ }
81
+ .nav-item[aria-selected="true"] {
82
+ background: var(--sidebar-active);
83
+ color: var(--ink);
84
+ }
85
+
86
+ /* Unsaved-changes dot next to nav label */
87
+ .nav-item.has-changes::after {
88
+ content: "";
89
+ position: absolute;
90
+ right: 10px;
91
+ top: 50%;
92
+ transform: translateY(-50%);
93
+ width: 7px;
94
+ height: 7px;
95
+ border-radius: 50%;
96
+ background: var(--ink);
97
+ box-shadow: 0 0 0 0 rgba(26, 24, 21, 0.30);
98
+ animation: dirty-pulse 2s ease-in-out infinite;
99
+ }
100
+ @keyframes dirty-pulse {
101
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(26, 24, 21, 0.22); }
102
+ 50% { box-shadow: 0 0 0 4px rgba(26, 24, 21, 0); }
103
+ }
104
+ .sidebar[data-collapsed="true"] .nav-item.has-changes::after {
105
+ right: auto;
106
+ top: 6px;
107
+ left: 28px;
108
+ }
109
+
110
+ .nav-icon {
111
+ display: inline-flex;
112
+ width: 20px;
113
+ height: 20px;
114
+ flex: 0 0 20px;
115
+ color: currentColor;
116
+ }
117
+ .nav-icon svg { width: 100%; height: 100%; }
118
+ .nav-label {
119
+ white-space: nowrap;
120
+ opacity: 1;
121
+ transition: opacity .15s ease;
122
+ flex: 1;
123
+ }
124
+ .sidebar[data-collapsed="true"] .nav-label { opacity: 0; pointer-events: none; }
125
+
126
+ .nav-badge {
127
+ font-family: var(--mono);
128
+ font-size: 10.5px;
129
+ background: var(--border-soft);
130
+ color: var(--ink-muted);
131
+ padding: 1px 6px;
132
+ border-radius: 4px;
133
+ font-variant-numeric: tabular-nums;
134
+ opacity: 1;
135
+ transition: opacity .15s ease;
136
+ }
137
+ .sidebar[data-collapsed="true"] .nav-badge { opacity: 0; }
138
+ .nav-item[aria-selected="true"] .nav-badge {
139
+ background: var(--bg-elev);
140
+ color: var(--ink-mid);
141
+ }
142
+
143
+ .sidebar-divider {
144
+ margin: var(--s-3) var(--s-2);
145
+ border-top: 1px solid var(--border);
146
+ }
147
+
148
+ .sidebar-utility {
149
+ display: flex;
150
+ flex-direction: column;
151
+ gap: 2px;
152
+ }
153
+
154
+ .sidebar-foot {
155
+ margin-top: auto;
156
+ padding-top: var(--s-2);
157
+ }
158
+
159
+ /* chevron flips when the sidebar is collapsed; the rest is util-item styling */
160
+ .collapse-toggle .nav-icon {
161
+ transition: transform .25s cubic-bezier(.4, 0, .2, 1);
162
+ }
163
+ .sidebar[data-collapsed="true"] .collapse-toggle .nav-icon {
164
+ transform: rotate(180deg);
165
+ }
@@ -0,0 +1,266 @@
1
+ /* Data tables · sticky actions column · status mark · title/path cells ·
2
+ star + rename inline buttons · row stagger animation */
3
+
4
+ .table-scroll {
5
+ overflow-x: auto;
6
+ /* tactile: scroll-padding so the actions column edges aren't flush
7
+ against the viewport when you scroll right to reach the buttons */
8
+ scroll-padding-right: var(--s-6);
9
+ }
10
+ .table-scroll::-webkit-scrollbar { height: 8px; }
11
+ .table-scroll .data {
12
+ min-width: 960px;
13
+ table-layout: auto;
14
+ }
15
+
16
+ .data {
17
+ width: 100%;
18
+ border-collapse: collapse;
19
+ font-family: var(--body);
20
+ font-size: 13.5px;
21
+ }
22
+
23
+ .data thead th {
24
+ padding: 10px var(--s-5) 10px;
25
+ text-align: left;
26
+ font-family: var(--body);
27
+ font-size: 11px;
28
+ font-weight: 600;
29
+ letter-spacing: 0.04em;
30
+ color: var(--ink-muted);
31
+ border-bottom: 1px solid var(--border);
32
+ background: var(--bg);
33
+ vertical-align: bottom;
34
+ white-space: nowrap;
35
+ }
36
+ .data thead th.num { text-align: right; }
37
+ /* shrink-to-fit pattern: width:1% with nowrap → the cell takes only
38
+ what it needs; title / path cells get the remainder */
39
+ .data thead th.col-mark { width: 28px; padding-left: 0; padding-right: 0; }
40
+ .data thead th.col-star { width: 28px; padding-left: 0; padding-right: 0; }
41
+ .data thead th.col-actions { width: 1%; white-space: nowrap; }
42
+ .data thead th.num { width: 1%; white-space: nowrap; }
43
+ .data tbody td.num { white-space: nowrap; }
44
+ .data tbody td:last-child { white-space: nowrap; }
45
+ .data thead th:first-child { padding-left: var(--s-6); }
46
+ .data thead th:last-child { padding-right: var(--s-6); }
47
+
48
+ /* Last column pins to the right edge of the .table-scroll container during
49
+ horizontal scroll. Backgrounds must be solid because sticky cells overlay
50
+ the content scrolling beneath them. */
51
+ .data thead th:last-child,
52
+ .data tbody td:last-child {
53
+ position: sticky;
54
+ right: 0;
55
+ z-index: 1;
56
+ box-shadow: -10px 0 10px -8px rgba(26, 24, 21, 0.08);
57
+ }
58
+ .data thead th:last-child { background: var(--bg); }
59
+ .data tbody td:last-child { background: var(--bg-elev); }
60
+ .data tbody tr:hover td:last-child { background: var(--bg); }
61
+
62
+ .data tbody tr {
63
+ border-bottom: 1px solid var(--border-soft);
64
+ transition: background .12s ease;
65
+ }
66
+ .data tbody tr:last-child { border-bottom: 0; }
67
+ .data tbody tr:hover { background: var(--bg); }
68
+
69
+ .data tbody td {
70
+ padding: 12px var(--s-5);
71
+ vertical-align: middle;
72
+ color: var(--ink);
73
+ }
74
+ .data tbody td:first-child { padding-left: var(--s-6); }
75
+ .data tbody td:last-child { padding-right: var(--s-6); }
76
+ .data tbody td.num {
77
+ text-align: right;
78
+ font-family: var(--mono);
79
+ font-size: 11.5px;
80
+ color: var(--ink-mid);
81
+ }
82
+
83
+ /* JS adds .no-anim after the first render so 5s auto-refresh doesn't
84
+ restage the stagger and strobe. */
85
+ .data tbody:not(.no-anim) tr { animation: row-in .3s ease-out backwards; }
86
+ .data tbody:not(.no-anim) tr:nth-child(1) { animation-delay: 0ms; }
87
+ .data tbody:not(.no-anim) tr:nth-child(2) { animation-delay: 20ms; }
88
+ .data tbody:not(.no-anim) tr:nth-child(3) { animation-delay: 40ms; }
89
+ .data tbody:not(.no-anim) tr:nth-child(4) { animation-delay: 60ms; }
90
+ .data tbody:not(.no-anim) tr:nth-child(5) { animation-delay: 80ms; }
91
+ .data tbody:not(.no-anim) tr:nth-child(6) { animation-delay: 100ms; }
92
+ .data tbody:not(.no-anim) tr:nth-child(7) { animation-delay: 120ms; }
93
+ .data tbody:not(.no-anim) tr:nth-child(8) { animation-delay: 140ms; }
94
+ .data tbody:not(.no-anim) tr:nth-child(n+9) { animation-delay: 160ms; }
95
+ @keyframes row-in {
96
+ from { opacity: 0; transform: translateY(3px); }
97
+ to { opacity: 1; transform: translateY(0); }
98
+ }
99
+
100
+ /* Status indicator dot — claude-like. Base color is the muted gray so any
101
+ unrecognized status string Claude writes ("compacting", "thinking", a
102
+ transient state, etc.) falls back to "unknown-looking" rather than a
103
+ stark black dot. .busy / .idle override. */
104
+ .status-mark {
105
+ display: inline-block;
106
+ width: 8px;
107
+ height: 8px;
108
+ border-radius: 50%;
109
+ background: currentColor;
110
+ vertical-align: middle;
111
+ position: relative;
112
+ color: var(--ink-faint);
113
+ }
114
+ .status-mark.busy {
115
+ color: var(--blue);
116
+ box-shadow: 0 0 0 0 currentColor;
117
+ animation: pulse 1.5s ease-in-out infinite;
118
+ }
119
+ .status-mark.idle { color: var(--green); }
120
+ .status-mark.unknown { color: var(--ink-faint); }
121
+ @keyframes pulse {
122
+ 0% { box-shadow: 0 0 0 0 rgba(74, 115, 165, 0.4); }
123
+ 70% { box-shadow: 0 0 0 7px rgba(74, 115, 165, 0); }
124
+ 100% { box-shadow: 0 0 0 0 rgba(74, 115, 165, 0); }
125
+ }
126
+
127
+ /* Title and path are fixed-width with overflow ellipsis on the inner
128
+ spans so the sticky actions column is always visible at typical widths;
129
+ long values truncate, the title attr shows the full value on hover. */
130
+ .title-cell {
131
+ width: 300px;
132
+ max-width: 300px;
133
+ min-width: 0;
134
+ }
135
+ .title-cell .title-row {
136
+ display: flex;
137
+ align-items: center;
138
+ gap: 6px;
139
+ min-width: 0;
140
+ width: 100%;
141
+ }
142
+ .title-cell .primary {
143
+ color: var(--ink);
144
+ font-weight: 500;
145
+ white-space: nowrap;
146
+ overflow: hidden;
147
+ text-overflow: ellipsis;
148
+ font-size: 13.5px;
149
+ min-width: 0;
150
+ flex: 0 1 auto;
151
+ }
152
+ .title-cell .secondary {
153
+ font-family: var(--mono);
154
+ font-size: 10.5px;
155
+ color: var(--ink-muted);
156
+ letter-spacing: 0.02em;
157
+ margin-top: 2px;
158
+ white-space: nowrap;
159
+ overflow: hidden;
160
+ text-overflow: ellipsis;
161
+ }
162
+
163
+ .path-cell {
164
+ font-family: var(--mono);
165
+ font-size: 11.5px;
166
+ color: var(--ink-mid);
167
+ width: 260px;
168
+ max-width: 260px;
169
+ min-width: 0;
170
+ white-space: nowrap;
171
+ overflow: hidden;
172
+ text-overflow: ellipsis;
173
+ }
174
+
175
+ .branch-tag {
176
+ display: inline-block;
177
+ font-family: var(--mono);
178
+ font-size: 11px;
179
+ color: var(--ink-mid);
180
+ background: var(--bg);
181
+ padding: 2px 7px;
182
+ border-radius: 4px;
183
+ border: 1px solid var(--border-soft);
184
+ }
185
+
186
+ .row-actions {
187
+ display: flex;
188
+ gap: var(--s-2);
189
+ justify-content: flex-end;
190
+ }
191
+ /* Equalised width so Focus / Continue / Resume align cleanly down the
192
+ actions column (last column is sticky, so width drift is visible).
193
+ .action is inline-flex with justify-content:center, so the icon+text
194
+ cluster sits in the middle. */
195
+ .row-actions .action.small {
196
+ min-width: 96px;
197
+ }
198
+
199
+ /* Star toggle — inline next to the title text. Outline by default;
200
+ filled in ink when pinned. */
201
+ .star-btn {
202
+ appearance: none;
203
+ background: transparent;
204
+ border: 0;
205
+ padding: 2px;
206
+ margin: 0;
207
+ cursor: pointer;
208
+ color: var(--ink-muted);
209
+ display: inline-flex;
210
+ align-items: center;
211
+ justify-content: center;
212
+ border-radius: 4px;
213
+ transition: color .12s ease, background .12s ease, transform .15s ease;
214
+ line-height: 0;
215
+ flex: 0 0 auto;
216
+ }
217
+ .data tbody tr:hover .star-btn { color: var(--ink-mid); }
218
+ .star-btn:hover {
219
+ color: #e3b341 !important;
220
+ background: var(--bg);
221
+ }
222
+ .star-btn:active { transform: scale(0.88); }
223
+ .star-btn.is-fav { color: #e3b341; }
224
+ .star-btn svg { display: block; }
225
+
226
+ /* Rename (pencil) — only visible on row hover so it doesn't compete with
227
+ the star. When a label is set (.has-label), it stays subtly visible to
228
+ indicate the title is overridden. */
229
+ .rename-btn {
230
+ appearance: none;
231
+ background: transparent;
232
+ border: 0;
233
+ padding: 2px;
234
+ margin: 0;
235
+ cursor: pointer;
236
+ color: var(--ink-faint);
237
+ opacity: 0;
238
+ display: inline-flex;
239
+ align-items: center;
240
+ justify-content: center;
241
+ border-radius: 4px;
242
+ transition: opacity .12s ease, color .12s ease, background .12s ease, transform .15s ease;
243
+ line-height: 0;
244
+ flex: 0 0 auto;
245
+ }
246
+ .data tbody tr:hover .rename-btn {
247
+ opacity: 1;
248
+ color: var(--ink-muted);
249
+ }
250
+ .rename-btn:hover {
251
+ color: var(--ink) !important;
252
+ background: var(--bg);
253
+ }
254
+ .rename-btn:active { transform: scale(0.88); }
255
+ .rename-btn.has-label {
256
+ opacity: 0.8;
257
+ color: var(--ink);
258
+ }
259
+ .rename-btn svg { display: block; }
260
+
261
+ #favoritesEmpty {
262
+ padding: var(--s-6) var(--s-6);
263
+ text-align: center;
264
+ font-size: 12.5px;
265
+ color: var(--ink-muted);
266
+ }
@@ -0,0 +1,112 @@
1
+ /* Terminals tab · left rail (active sessions) + right pane (xterm host) */
2
+
3
+ .terminals-layout {
4
+ display: grid;
5
+ grid-template-columns: 240px 1fr;
6
+ gap: var(--s-4);
7
+ /* viewport minus page header + footer + padding; lets xterm fill height */
8
+ height: calc(100vh - 220px);
9
+ min-height: 480px;
10
+ }
11
+
12
+ .terminals-rail {
13
+ background: var(--bg-elev);
14
+ border: 1px solid var(--border);
15
+ border-radius: var(--r-md);
16
+ padding: var(--s-2);
17
+ display: flex;
18
+ flex-direction: column;
19
+ gap: 2px;
20
+ overflow-y: auto;
21
+ }
22
+ .terminals-rail-head {
23
+ display: flex;
24
+ justify-content: space-between;
25
+ align-items: center;
26
+ padding: var(--s-2) var(--s-3);
27
+ font-size: 11px;
28
+ text-transform: uppercase;
29
+ letter-spacing: 0.06em;
30
+ color: var(--ink-muted);
31
+ border-bottom: 1px solid var(--border-soft);
32
+ margin-bottom: var(--s-2);
33
+ }
34
+
35
+ /* button row showing one terminal in the rail */
36
+ .terminal-row {
37
+ appearance: none;
38
+ background: transparent;
39
+ border: 0;
40
+ width: 100%;
41
+ text-align: left;
42
+ padding: 8px 10px;
43
+ border-radius: var(--r-sm);
44
+ cursor: pointer;
45
+ display: grid;
46
+ grid-template-columns: 10px 1fr auto auto;
47
+ align-items: center;
48
+ gap: 8px;
49
+ color: var(--ink-mid);
50
+ font-family: var(--body);
51
+ font-size: 13px;
52
+ transition: background .12s ease, color .12s ease;
53
+ }
54
+ .terminal-row:hover { background: var(--bg); color: var(--ink); }
55
+ .terminal-row.is-active {
56
+ background: var(--sidebar-active);
57
+ color: var(--ink);
58
+ }
59
+ .terminal-row-title {
60
+ white-space: nowrap;
61
+ overflow: hidden;
62
+ text-overflow: ellipsis;
63
+ min-width: 0;
64
+ font-weight: 500;
65
+ }
66
+ .terminal-row-meta {
67
+ font-family: var(--mono);
68
+ font-size: 10.5px;
69
+ color: var(--ink-muted);
70
+ font-variant-numeric: tabular-nums;
71
+ }
72
+ .terminal-row-actions { display: inline-flex; }
73
+ .terminal-row .action.tiny.danger {
74
+ padding: 1px 7px;
75
+ font-size: 11px;
76
+ min-width: 0;
77
+ }
78
+
79
+ /* right pane — full-height xterm host */
80
+ .terminals-main {
81
+ background: var(--bg);
82
+ border: 1px solid var(--border);
83
+ border-radius: var(--r-md);
84
+ padding: var(--s-3);
85
+ overflow: hidden;
86
+ min-width: 0;
87
+ display: flex;
88
+ flex-direction: column;
89
+ }
90
+ .terminal-host {
91
+ flex: 1;
92
+ min-height: 0;
93
+ width: 100%;
94
+ }
95
+ /* Don't override xterm's background — its renderer (canvas/WebGL) assumes
96
+ an opaque surface and ghosts on scroll if we force transparent. The
97
+ theme object in TerminalView.js already paints #faf9f5 to match the
98
+ surrounding card. */
99
+ .terminal-host .xterm-viewport {
100
+ scrollbar-width: thin;
101
+ }
102
+
103
+ .terminal-empty {
104
+ height: 100%;
105
+ display: flex;
106
+ align-items: center;
107
+ justify-content: center;
108
+ font-size: 13px;
109
+ color: var(--ink-muted);
110
+ }
111
+
112
+ .terminal-empty-page { width: 100%; }
@@ -0,0 +1,63 @@
1
+ /* ccsm · design tokens · v0.6 */
2
+
3
+ :root {
4
+ /* Surfaces — cream with neutral whites. Sidebar shares the page bg
5
+ so they read as one continuous surface; separation is just the
6
+ hairline border on .sidebar. */
7
+ --bg: #faf9f5;
8
+ --bg-elev: #ffffff;
9
+ --sidebar-bg: #faf9f5;
10
+ --sidebar-hover: #f0ece0;
11
+ --sidebar-active: #e8e3d5;
12
+
13
+ /* Borders & rules */
14
+ --border: #e8e3d5;
15
+ --border-soft: #ece8da;
16
+ --border-strong: #d4cdb8;
17
+
18
+ /* Ink */
19
+ --ink: #1a1815;
20
+ --ink-mid: #534e44;
21
+ --ink-muted: #8a8475;
22
+ --ink-faint: #b5af9d;
23
+
24
+ /* Accent — Claude warm copper. Slightly desaturated from raw #c45f3f so
25
+ solid-fill buttons feel calm against the cream surfaces, while still
26
+ reading as the same brand hue in star icons, focus rings, etc. */
27
+ --accent: #b3614a;
28
+ --accent-deep: #8f4a36;
29
+ --accent-soft: rgba(179, 97, 74, 0.10);
30
+ --accent-softer: rgba(179, 97, 74, 0.04);
31
+
32
+ /* Status */
33
+ --green: #4a8a4a;
34
+ --yellow: #c4892b;
35
+ --red: #b73f3f;
36
+ --blue: #4a73a5;
37
+
38
+ /* Type */
39
+ --body: "Geist", -apple-system, "Segoe UI", system-ui, sans-serif;
40
+ --mono: "JetBrains Mono", "Cascadia Mono", "Consolas", monospace;
41
+
42
+ /* Scale */
43
+ --s-1: 4px; --s-2: 8px; --s-3: 12px; --s-4: 16px;
44
+ --s-5: 20px; --s-6: 24px; --s-8: 32px; --s-10: 40px;
45
+ --s-12: 48px; --s-16: 64px;
46
+
47
+ /* Radius */
48
+ --r-sm: 6px;
49
+ --r: 8px;
50
+ --r-md: 10px;
51
+ --r-lg: 14px;
52
+
53
+ /* Shadow — soft, like print on cream */
54
+ --shadow-sm: 0 1px 0 rgba(26, 24, 21, 0.04);
55
+ --shadow: 0 1px 2px rgba(26, 24, 21, 0.04),
56
+ 0 1px 0 rgba(26, 24, 21, 0.03);
57
+ --shadow-md: 0 4px 12px -2px rgba(26, 24, 21, 0.08),
58
+ 0 1px 0 rgba(26, 24, 21, 0.04);
59
+
60
+ /* Sidebar geometry */
61
+ --sidebar-w: 232px;
62
+ --sidebar-w-collapsed: 60px;
63
+ }
@@ -0,0 +1,70 @@
1
+ /* PWA / --app= window drag regions.
2
+ *
3
+ * `-webkit-app-region: drag` is a Chromium-only property; it has effect in
4
+ * Electron, installed PWA windows, AND in `--app=` chromeless mode where
5
+ * the manifest's display_override unlocks WCO-ish behaviour. In a plain
6
+ * browser tab it's silently ignored. We therefore apply it *unconditionally*
7
+ * (no `@media (display-mode: …)` gate) so any surface that does honour it
8
+ * gets the drag handles — particularly the case where Edge `--app=` reads
9
+ * our manifest, hides its own OS title bar, but still reports
10
+ * display-mode: browser. Without unconditional rules, that window has
11
+ * neither a native title bar to grab NOR app-region drag → undraggable.
12
+ */
13
+
14
+ .sidebar-brand,
15
+ .page-head,
16
+ .page-head-inner,
17
+ .page-title,
18
+ .page-subtitle {
19
+ -webkit-app-region: drag;
20
+ app-region: drag;
21
+ }
22
+
23
+ /* Any interactive element opts out of drag so click / focus / scroll
24
+ still reach the page. */
25
+ button, a, input, select, textarea, label,
26
+ .nav-item, .util-item, .collapse-toggle,
27
+ .action, .server-status,
28
+ .star-btn, .rename-btn, .card-fold, .modal-close,
29
+ .chip, .radio, .fab,
30
+ .ph-stat, .ph-divider {
31
+ -webkit-app-region: no-drag;
32
+ app-region: no-drag;
33
+ }
34
+
35
+ /* `user-select: none` on the drag-target text is needed so clicking the
36
+ page title doesn't start a text selection instead of a window drag. But
37
+ we only want this in PWA-like contexts — in a regular browser tab the
38
+ user should still be able to select the title text. Runtime JS adds
39
+ `body.is-app` when not in display-mode:browser; see main.js. */
40
+ body.is-app .sidebar-brand,
41
+ body.is-app .page-head,
42
+ body.is-app .page-head-inner,
43
+ body.is-app .page-title,
44
+ body.is-app .page-subtitle {
45
+ user-select: none;
46
+ -webkit-user-select: none;
47
+ }
48
+
49
+ /* In an app window, .main and .sidebar's own top padding is "blank space"
50
+ that has no drag region — clicking the topmost 32px does nothing. Move
51
+ that padding into the draggable child elements (.sidebar-brand and
52
+ .page-head) so the windowtop is grabbable all the way to y=0. Visually
53
+ nothing shifts; behaviourally the OS-title-bar zone now responds. */
54
+ body.is-app .sidebar { padding-top: 0; }
55
+ body.is-app .main { padding-top: 0; }
56
+ body.is-app .sidebar-brand {
57
+ padding-top: calc(var(--s-4) + var(--s-4)); /* 32px = old sidebar.pt + old brand.pt */
58
+ }
59
+ body.is-app .page-head {
60
+ padding-top: var(--s-8); /* 32px = old main.pt */
61
+ }
62
+
63
+ /* WCO-only · the OS title bar is fully gone, so right-pad the page-head
64
+ so the meta cluster doesn't slide under the floating close/max/min
65
+ controls. `.main`'s own 32px top padding already clears their height. */
66
+ @media (display-mode: window-controls-overlay) {
67
+ .page-head {
68
+ padding-right: calc(var(--s-10) + 100vw - env(titlebar-area-width, 100vw));
69
+ }
70
+ }