@young1lin/dsh-ui-gitworkbench 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1512 @@
1
+ /* Change inspector.
2
+ *
3
+ * The session-header card lives in dsh chrome and keeps dsh's own --dsw-* tokens,
4
+ * so it is themed by the app and needs nothing here. The DRAWER paints its own
5
+ * surface — a diff needs an add/delete/word-level/syntax palette dsh does not
6
+ * define — so it carries a self-contained palette instead.
7
+ *
8
+ * Every drawer colour resolves through a --gs-* token. The palettes below are the
9
+ * only place a literal colour appears; each theme sets the same token names, so a
10
+ * theme is a palette swap and nothing else. `data-gs-theme` on the overlay picks
11
+ * one, and the panel computes it from the theme setting (`system` follows dsh's
12
+ * `body[data-ds-dark-theme]`; light/dark pin the drawer).
13
+ *
14
+ * One exception is deliberate: the mode picker's own light/dark chips are fixed
15
+ * white and near-black, because those buttons name colours rather than wear them.
16
+ *
17
+ * `data-gs-part` attributes on the overlay, card, header, tabs and three panes are
18
+ * the stable hooks user CSS targets — class names are hashed by CSS Modules and
19
+ * change on every build, so they are not addressable from outside.
20
+ */
21
+
22
+ /* ---- environment card (lives in dsh chrome — keep dsw tokens) ---- */
23
+ .card {
24
+ display: inline-flex;
25
+ align-items: center;
26
+ gap: 7px;
27
+ min-height: 28px;
28
+ padding: 3px 10px;
29
+ border: 1px solid var(--dsw-alias-border-l2);
30
+ border-radius: 8px;
31
+ background: transparent;
32
+ color: var(--dsw-alias-label-secondary);
33
+ font-size: 12px;
34
+ line-height: 18px;
35
+ font-variant-numeric: tabular-nums;
36
+ cursor: pointer;
37
+ transition: background 120ms ease, border-color 120ms ease;
38
+ }
39
+ .card:hover, .card:focus-visible {
40
+ background: var(--dsw-alias-interactive-bg-hover);
41
+ border-color: var(--dsw-alias-border-l3);
42
+ }
43
+ .cardBranch {
44
+ display: inline-flex;
45
+ align-items: center;
46
+ gap: 4px;
47
+ max-width: 220px;
48
+ overflow: hidden;
49
+ color: var(--dsw-alias-label-primary);
50
+ font-weight: 550;
51
+ }
52
+ .cardBranchName { min-width: 0; overflow: hidden; }
53
+ .cardGlyph { flex: none; opacity: 0.7; }
54
+ .cardDetached {
55
+ padding: 0 6px;
56
+ border-radius: 999px;
57
+ background: var(--dsw-alias-state-warning-bg, rgba(187, 128, 9, 0.15));
58
+ color: var(--dsw-alias-state-warning-primary, #d29922);
59
+ font-size: 10px;
60
+ line-height: 16px;
61
+ letter-spacing: 0.02em;
62
+ }
63
+ /* Bound-worktree badge: the session is attached to an agent worktree. */
64
+ .cardWt {
65
+ display: inline-flex;
66
+ align-items: center;
67
+ gap: 3px;
68
+ max-width: 150px;
69
+ overflow: hidden;
70
+ padding: 0 6px;
71
+ border-radius: 999px;
72
+ background: var(--dsw-alias-state-business-bg, rgba(56, 139, 253, 0.16));
73
+ color: var(--dsw-alias-state-business-primary, #58a6ff);
74
+ font-size: 10px;
75
+ line-height: 16px;
76
+ letter-spacing: 0.02em;
77
+ white-space: nowrap;
78
+ text-overflow: ellipsis;
79
+ }
80
+ .cardAhead, .cardBehind {
81
+ color: var(--dsw-alias-label-tertiary);
82
+ font-size: 11px;
83
+ }
84
+ .cardAhead { color: var(--dsw-alias-state-success-primary); }
85
+ .cardBehind { color: var(--dsw-alias-state-attention-primary, #d29922); }
86
+ .cardSep { width: 1px; height: 14px; background: var(--dsw-alias-border-l2); }
87
+ .cardAdded { color: var(--dsw-alias-state-success-primary); }
88
+ .cardDeleted { color: var(--dsw-alias-state-error-primary); }
89
+ .cardFiles { color: var(--dsw-alias-label-tertiary); }
90
+
91
+ /* ============================ theme palettes ============================ */
92
+
93
+ /* GitHub — the default, and the reference the other palettes are balanced against. */
94
+ .overlay[data-gs-theme='github-dark'] {
95
+ --gs-bg: #0d1117; --gs-panel: #161b22; --gs-raise: #21262d;
96
+ --gs-border: #30363d; --gs-border-soft: #21262d;
97
+ --gs-fg: #e6edf3; --gs-fg-muted: #c9d1d9; --gs-fg-dim: #8b949e; --gs-fg-faint: #6e7681; --gs-fg-fainter: #484f58;
98
+ --gs-accent: #58a6ff; --gs-accent-bg: rgba(56, 139, 253, 0.14); --gs-accent-border: rgba(56, 139, 253, 0.55);
99
+ --gs-add: #3fb950; --gs-del: #f85149; --gs-warn: #d29922; --gs-info: #79c0ff;
100
+ --gs-add-bg: rgba(46, 160, 67, 0.16); --gs-del-bg: rgba(248, 81, 73, 0.16);
101
+ --gs-warn-bg: rgba(187, 128, 9, 0.16); --gs-info-bg: rgba(56, 139, 253, 0.16);
102
+ /* Primer's own diffBlob tokens, flattened over --gs-bg (#0d1117). Opaque
103
+ rather than the alpha hexes Primer ships, because here the number cells
104
+ paint ON TOP of the row tint — two alphas would composite to roughly twice
105
+ GitHub's strength. Line 15% / num 30% / word 40% of the base hue. */
106
+ --gs-add-line: #12261e; --gs-del-line: #25181c;
107
+ --gs-add-word: #1a4a29; --gs-del-word: #6b2b2b;
108
+ --gs-add-num: #1c4428; --gs-del-num: #542426;
109
+ --gs-hunk: #111d2e; --gs-hunk-num: #0c2d6b;
110
+ --gs-neutral-bg: rgba(110, 118, 129, 0.18);
111
+ --gs-backdrop: rgba(1, 4, 9, 0.6); --gs-shadow: rgba(1, 4, 9, 0.55);
112
+ }
113
+ .overlay[data-gs-theme='github-light'] {
114
+ --gs-bg: #ffffff; --gs-panel: #f6f8fa; --gs-raise: #eaeef2;
115
+ --gs-border: #d0d7de; --gs-border-soft: #eaeef2;
116
+ --gs-fg: #1f2328; --gs-fg-muted: #32383f; --gs-fg-dim: #656d76; --gs-fg-faint: #8c959f; --gs-fg-fainter: #afb8c1;
117
+ --gs-accent: #0969da; --gs-accent-bg: rgba(9, 105, 218, 0.1); --gs-accent-border: rgba(9, 105, 218, 0.45);
118
+ --gs-add: #1a7f37; --gs-del: #cf222e; --gs-warn: #9a6700; --gs-info: #0550ae;
119
+ --gs-add-bg: rgba(26, 127, 55, 0.12); --gs-del-bg: rgba(207, 34, 46, 0.12);
120
+ --gs-warn-bg: rgba(154, 103, 0, 0.12); --gs-info-bg: rgba(9, 105, 218, 0.12);
121
+ /* Primer's light diffBlob tokens, which are already opaque upstream. */
122
+ --gs-add-line: #dafbe1; --gs-del-line: #ffebe9;
123
+ --gs-add-word: #aceebb; --gs-del-word: #ffcecb;
124
+ --gs-add-num: #aceebb; --gs-del-num: #ffcecb;
125
+ --gs-hunk: #ddf4ff; --gs-hunk-num: #b6e3ff;
126
+ --gs-neutral-bg: rgba(110, 118, 129, 0.12);
127
+ --gs-backdrop: rgba(31, 35, 40, 0.28); --gs-shadow: rgba(31, 35, 40, 0.22);
128
+ }
129
+
130
+ /* IntelliJ IDEA — the New UI (2022.3+, the default since 2024.1), not Darcula.
131
+ Its greys are the giveaway: the New UI dropped Darcula's warm #3C3F41 chrome
132
+ for the cooler #1E1F22 editor over a #2B2D30 tool window, and moved the
133
+ accent to #3574F0.
134
+
135
+ The semantic four are the IntelliJ platform's own VCS colours (the keys the
136
+ log and the diff gutter paint with), except that dark's green is the New UI's
137
+ brighter #5FAD65 rather than the platform #499C54 — the latter is a fill
138
+ colour there, and as 12px TEXT on #1E1F22 it lands under 4:1.
139
+
140
+ Unlike the GitHub palettes below, these are not transcribed from a published
141
+ token file, so there is no test pinning them to a source of truth; they are
142
+ read off the New UI's own chrome and diff. */
143
+ .overlay[data-gs-theme='idea-dark'] {
144
+ --gs-bg: #1e1f22; --gs-panel: #2b2d30; --gs-raise: #393b40;
145
+ --gs-border: #393b40; --gs-border-soft: #2b2d30;
146
+ --gs-fg: #dfe1e5; --gs-fg-muted: #ced0d6; --gs-fg-dim: #9da0a8; --gs-fg-faint: #6f737a; --gs-fg-fainter: #4e5157;
147
+ --gs-accent: #548af7; --gs-accent-bg: rgba(84, 138, 247, 0.16); --gs-accent-border: rgba(84, 138, 247, 0.55);
148
+ --gs-add: #5fad65; --gs-del: #e26e6e; --gs-warn: #f0a732; --gs-info: #3592c4;
149
+ --gs-add-bg: rgba(95, 173, 101, 0.16); --gs-del-bg: rgba(226, 110, 110, 0.16);
150
+ --gs-warn-bg: rgba(240, 167, 50, 0.16); --gs-info-bg: rgba(53, 146, 196, 0.16);
151
+ /* IDEA paints diff as solid blocks rather than a wash, and the word-level
152
+ highlight as a distinctly stronger block over it — so these are opaque,
153
+ like GitHub's, instead of the alpha tints the other families use. */
154
+ --gs-add-line: #293c2e; --gs-del-line: #3d2b2b;
155
+ --gs-add-word: #3d6640; --gs-del-word: #6b3838;
156
+ --gs-hunk: rgba(84, 138, 247, 0.10); --gs-neutral-bg: rgba(157, 160, 168, 0.18);
157
+ --gs-backdrop: rgba(16, 17, 19, 0.6); --gs-shadow: rgba(0, 0, 0, 0.5);
158
+ }
159
+ .overlay[data-gs-theme='idea-light'] {
160
+ --gs-bg: #ffffff; --gs-panel: #f7f8fa; --gs-raise: #ebecf0;
161
+ --gs-border: #d3d5db; --gs-border-soft: #ebecf0;
162
+ --gs-fg: #1e1f22; --gs-fg-muted: #3c3f44; --gs-fg-dim: #6c707e; --gs-fg-faint: #818594; --gs-fg-fainter: #a8adbd;
163
+ --gs-accent: #3574f0; --gs-accent-bg: rgba(53, 116, 240, 0.1); --gs-accent-border: rgba(53, 116, 240, 0.45);
164
+ --gs-add: #398a4a; --gs-del: #cc4a4a; --gs-warn: #a8760b; --gs-info: #2b7fb8;
165
+ --gs-add-bg: rgba(57, 138, 74, 0.12); --gs-del-bg: rgba(204, 74, 74, 0.12);
166
+ --gs-warn-bg: rgba(168, 118, 11, 0.12); --gs-info-bg: rgba(43, 127, 184, 0.12);
167
+ --gs-add-line: #e6f5e9; --gs-del-line: #fbe9ea;
168
+ --gs-add-word: #c2e5c9; --gs-del-word: #f5c9cc;
169
+ --gs-hunk: rgba(53, 116, 240, 0.07); --gs-neutral-bg: rgba(108, 112, 126, 0.12);
170
+ --gs-backdrop: rgba(30, 31, 34, 0.26); --gs-shadow: rgba(30, 31, 34, 0.18);
171
+ }
172
+
173
+ /* VS Code — Dark+ / Light+, the default editor themes. */
174
+ .overlay[data-gs-theme='vscode-dark'] {
175
+ --gs-bg: #1e1e1e; --gs-panel: #252526; --gs-raise: #2d2d30;
176
+ --gs-border: #3e3e42; --gs-border-soft: #2d2d30;
177
+ --gs-fg: #d4d4d4; --gs-fg-muted: #cccccc; --gs-fg-dim: #9d9d9d; --gs-fg-faint: #808080; --gs-fg-fainter: #5a5a5a;
178
+ --gs-accent: #0098ff; --gs-accent-bg: rgba(0, 122, 204, 0.24); --gs-accent-border: rgba(0, 152, 255, 0.6);
179
+ --gs-add: #89d185; --gs-del: #f14c4c; --gs-warn: #cca700; --gs-info: #75beff;
180
+ --gs-add-bg: rgba(137, 209, 133, 0.18); --gs-del-bg: rgba(241, 76, 76, 0.18);
181
+ --gs-warn-bg: rgba(204, 167, 0, 0.18); --gs-info-bg: rgba(117, 190, 255, 0.18);
182
+ --gs-add-line: rgba(155, 185, 85, 0.18); --gs-del-line: rgba(255, 0, 0, 0.16);
183
+ --gs-add-word: rgba(155, 185, 85, 0.4); --gs-del-word: rgba(255, 0, 0, 0.36);
184
+ --gs-hunk: rgba(0, 122, 204, 0.14); --gs-neutral-bg: rgba(190, 190, 190, 0.14);
185
+ --gs-backdrop: rgba(0, 0, 0, 0.55); --gs-shadow: rgba(0, 0, 0, 0.6);
186
+ }
187
+ .overlay[data-gs-theme='vscode-light'] {
188
+ --gs-bg: #ffffff; --gs-panel: #f3f3f3; --gs-raise: #e8e8e8;
189
+ --gs-border: #cecece; --gs-border-soft: #e8e8e8;
190
+ --gs-fg: #1e1e1e; --gs-fg-muted: #333333; --gs-fg-dim: #616161; --gs-fg-faint: #767676; --gs-fg-fainter: #a0a0a0;
191
+ --gs-accent: #005fb8; --gs-accent-bg: rgba(0, 95, 184, 0.1); --gs-accent-border: rgba(0, 95, 184, 0.45);
192
+ --gs-add: #10793f; --gs-del: #b5200d; --gs-warn: #855b00; --gs-info: #005fb8;
193
+ --gs-add-bg: rgba(16, 121, 63, 0.12); --gs-del-bg: rgba(181, 32, 13, 0.12);
194
+ --gs-warn-bg: rgba(133, 91, 0, 0.12); --gs-info-bg: rgba(0, 95, 184, 0.12);
195
+ --gs-add-line: rgba(155, 185, 85, 0.22); --gs-del-line: rgba(255, 0, 0, 0.13);
196
+ --gs-add-word: rgba(155, 185, 85, 0.45); --gs-del-word: rgba(255, 0, 0, 0.3);
197
+ --gs-hunk: rgba(0, 95, 184, 0.07); --gs-neutral-bg: rgba(97, 97, 97, 0.12);
198
+ --gs-backdrop: rgba(0, 0, 0, 0.25); --gs-shadow: rgba(0, 0, 0, 0.18);
199
+ }
200
+
201
+ /* Cyberpunk — neon on deep violet; the light variant keeps the magenta/cyan pair
202
+ over a near-white ground so the theme survives a bright room. */
203
+ .overlay[data-gs-theme='cyberpunk-dark'] {
204
+ --gs-bg: #0b0417; --gs-panel: #14082a; --gs-raise: #1f0d3d;
205
+ --gs-border: #3a1f6b; --gs-border-soft: #26124a;
206
+ --gs-fg: #f0e6ff; --gs-fg-muted: #d5c2f5; --gs-fg-dim: #a98fd6; --gs-fg-faint: #7d64a8; --gs-fg-fainter: #56427a;
207
+ --gs-accent: #00f0ff; --gs-on-accent: #0b0417; --gs-accent-bg: rgba(0, 240, 255, 0.14); --gs-accent-border: rgba(0, 240, 255, 0.5);
208
+ --gs-add: #39ff88; --gs-del: #ff2e88; --gs-warn: #ffcc00; --gs-info: #00f0ff;
209
+ --gs-add-bg: rgba(57, 255, 136, 0.14); --gs-del-bg: rgba(255, 46, 136, 0.14);
210
+ --gs-warn-bg: rgba(255, 204, 0, 0.14); --gs-info-bg: rgba(0, 240, 255, 0.14);
211
+ --gs-add-line: rgba(57, 255, 136, 0.12); --gs-del-line: rgba(255, 46, 136, 0.12);
212
+ --gs-add-word: rgba(57, 255, 136, 0.34); --gs-del-word: rgba(255, 46, 136, 0.34);
213
+ --gs-hunk: rgba(0, 240, 255, 0.1); --gs-neutral-bg: rgba(169, 143, 214, 0.16);
214
+ --gs-backdrop: rgba(6, 2, 14, 0.68); --gs-shadow: rgba(0, 240, 255, 0.14);
215
+ }
216
+ .overlay[data-gs-theme='cyberpunk-light'] {
217
+ --gs-bg: #fdfbff; --gs-panel: #f4eeff; --gs-raise: #e9dfff;
218
+ --gs-border: #c9b3f0; --gs-border-soft: #e0d2f7;
219
+ --gs-fg: #1a0b2e; --gs-fg-muted: #33195c; --gs-fg-dim: #6b4ba0; --gs-fg-faint: #8f74bd; --gs-fg-fainter: #b9a5d6;
220
+ --gs-accent: #00a6b8; --gs-accent-bg: rgba(0, 166, 184, 0.12); --gs-accent-border: rgba(0, 166, 184, 0.5);
221
+ --gs-add: #00875a; --gs-del: #d6006e; --gs-warn: #b37a00; --gs-info: #00a6b8;
222
+ --gs-add-bg: rgba(0, 135, 90, 0.12); --gs-del-bg: rgba(214, 0, 110, 0.12);
223
+ --gs-warn-bg: rgba(179, 122, 0, 0.12); --gs-info-bg: rgba(0, 166, 184, 0.12);
224
+ --gs-add-line: rgba(0, 200, 130, 0.14); --gs-del-line: rgba(255, 46, 136, 0.12);
225
+ --gs-add-word: rgba(0, 200, 130, 0.34); --gs-del-word: rgba(255, 46, 136, 0.3);
226
+ --gs-hunk: rgba(0, 166, 184, 0.08); --gs-neutral-bg: rgba(107, 75, 160, 0.12);
227
+ --gs-backdrop: rgba(26, 11, 46, 0.28); --gs-shadow: rgba(26, 11, 46, 0.16);
228
+ }
229
+
230
+ /* One — Atom's One Dark / One Light. */
231
+ .overlay[data-gs-theme='one-dark'] {
232
+ --gs-bg: #282c34; --gs-panel: #21252b; --gs-raise: #2c313a;
233
+ --gs-border: #3e4451; --gs-border-soft: #2c313a;
234
+ --gs-fg: #abb2bf; --gs-fg-muted: #b6bdca; --gs-fg-dim: #7f848e; --gs-fg-faint: #636d83; --gs-fg-fainter: #4b5263;
235
+ --gs-accent: #61afef; --gs-accent-bg: rgba(97, 175, 239, 0.14); --gs-accent-border: rgba(97, 175, 239, 0.55);
236
+ --gs-add: #98c379; --gs-del: #e06c75; --gs-warn: #e5c07b; --gs-info: #56b6c2;
237
+ --gs-add-bg: rgba(152, 195, 121, 0.16); --gs-del-bg: rgba(224, 108, 117, 0.16);
238
+ --gs-warn-bg: rgba(229, 192, 123, 0.16); --gs-info-bg: rgba(86, 182, 194, 0.16);
239
+ --gs-add-line: rgba(152, 195, 121, 0.14); --gs-del-line: rgba(224, 108, 117, 0.14);
240
+ --gs-add-word: rgba(152, 195, 121, 0.38); --gs-del-word: rgba(224, 108, 117, 0.38);
241
+ --gs-hunk: rgba(97, 175, 239, 0.1); --gs-neutral-bg: rgba(127, 132, 142, 0.18);
242
+ --gs-backdrop: rgba(23, 26, 33, 0.6); --gs-shadow: rgba(15, 17, 21, 0.55);
243
+ }
244
+ .overlay[data-gs-theme='one-light'] {
245
+ --gs-bg: #fafafa; --gs-panel: #f0f0f0; --gs-raise: #e5e5e6;
246
+ --gs-border: #d4d4d4; --gs-border-soft: #e5e5e6;
247
+ --gs-fg: #383a42; --gs-fg-muted: #4a4c53; --gs-fg-dim: #696c77; --gs-fg-faint: #909196; --gs-fg-fainter: #b8b9bd;
248
+ --gs-accent: #4078f2; --gs-accent-bg: rgba(64, 120, 242, 0.1); --gs-accent-border: rgba(64, 120, 242, 0.45);
249
+ --gs-add: #50a14f; --gs-del: #e45649; --gs-warn: #c18401; --gs-info: #0184bc;
250
+ --gs-add-bg: rgba(80, 161, 79, 0.12); --gs-del-bg: rgba(228, 86, 73, 0.12);
251
+ --gs-warn-bg: rgba(193, 132, 1, 0.12); --gs-info-bg: rgba(1, 132, 188, 0.12);
252
+ --gs-add-line: rgba(80, 161, 79, 0.14); --gs-del-line: rgba(228, 86, 73, 0.13);
253
+ --gs-add-word: rgba(80, 161, 79, 0.34); --gs-del-word: rgba(228, 86, 73, 0.32);
254
+ --gs-hunk: rgba(64, 120, 242, 0.07); --gs-neutral-bg: rgba(105, 108, 119, 0.12);
255
+ --gs-backdrop: rgba(56, 58, 66, 0.26); --gs-shadow: rgba(56, 58, 66, 0.18);
256
+ }
257
+
258
+ /* Solarized — Ethan Schoonover's fixed sixteen, both official grounds. */
259
+ .overlay[data-gs-theme='solarized-dark'] {
260
+ --gs-bg: #002b36; --gs-panel: #073642; --gs-raise: #0a4553;
261
+ --gs-border: #0f5666; --gs-border-soft: #073642;
262
+ --gs-fg: #93a1a1; --gs-fg-muted: #839496; --gs-fg-dim: #657b83; --gs-fg-faint: #586e75; --gs-fg-fainter: #45636b;
263
+ --gs-accent: #268bd2; --gs-accent-bg: rgba(38, 139, 210, 0.16); --gs-accent-border: rgba(38, 139, 210, 0.55);
264
+ --gs-add: #859900; --gs-del: #dc322f; --gs-warn: #b58900; --gs-info: #2aa198;
265
+ --gs-add-bg: rgba(133, 153, 0, 0.2); --gs-del-bg: rgba(220, 50, 47, 0.18);
266
+ --gs-warn-bg: rgba(181, 137, 0, 0.18); --gs-info-bg: rgba(42, 161, 152, 0.18);
267
+ --gs-add-line: rgba(133, 153, 0, 0.16); --gs-del-line: rgba(220, 50, 47, 0.15);
268
+ --gs-add-word: rgba(133, 153, 0, 0.4); --gs-del-word: rgba(220, 50, 47, 0.38);
269
+ --gs-hunk: rgba(38, 139, 210, 0.12); --gs-neutral-bg: rgba(101, 123, 131, 0.2);
270
+ --gs-backdrop: rgba(0, 20, 26, 0.62); --gs-shadow: rgba(0, 20, 26, 0.55);
271
+ }
272
+ .overlay[data-gs-theme='solarized-light'] {
273
+ --gs-bg: #fdf6e3; --gs-panel: #eee8d5; --gs-raise: #e3ddca;
274
+ --gs-border: #d5cfbb; --gs-border-soft: #eee8d5;
275
+ --gs-fg: #586e75; --gs-fg-muted: #657b83; --gs-fg-dim: #839496; --gs-fg-faint: #93a1a1; --gs-fg-fainter: #b5b0a0;
276
+ --gs-accent: #268bd2; --gs-accent-bg: rgba(38, 139, 210, 0.12); --gs-accent-border: rgba(38, 139, 210, 0.45);
277
+ --gs-add: #6c7c00; --gs-del: #cb2825; --gs-warn: #9a7400; --gs-info: #21867f;
278
+ --gs-add-bg: rgba(133, 153, 0, 0.18); --gs-del-bg: rgba(220, 50, 47, 0.14);
279
+ --gs-warn-bg: rgba(181, 137, 0, 0.16); --gs-info-bg: rgba(42, 161, 152, 0.14);
280
+ --gs-add-line: rgba(133, 153, 0, 0.16); --gs-del-line: rgba(220, 50, 47, 0.12);
281
+ --gs-add-word: rgba(133, 153, 0, 0.38); --gs-del-word: rgba(220, 50, 47, 0.3);
282
+ --gs-hunk: rgba(38, 139, 210, 0.08); --gs-neutral-bg: rgba(88, 110, 117, 0.14);
283
+ --gs-backdrop: rgba(88, 110, 117, 0.26); --gs-shadow: rgba(88, 110, 117, 0.2);
284
+ }
285
+
286
+ /* Nord — Polar Night for dark, Snow Storm for light. Nord's aurora greens and
287
+ reds are tuned for a dark ground, so the light variant darkens the diff pair
288
+ rather than washing it out. */
289
+ .overlay[data-gs-theme='nord-dark'] {
290
+ --gs-bg: #2e3440; --gs-panel: #3b4252; --gs-raise: #434c5e;
291
+ --gs-border: #4c566a; --gs-border-soft: #3b4252;
292
+ --gs-fg: #eceff4; --gs-fg-muted: #d8dee9; --gs-fg-dim: #9aa5b8; --gs-fg-faint: #7b88a1; --gs-fg-fainter: #5d6a82;
293
+ --gs-accent: #88c0d0; --gs-on-accent: #2e3440; --gs-accent-bg: rgba(136, 192, 208, 0.14); --gs-accent-border: rgba(136, 192, 208, 0.55);
294
+ --gs-add: #a3be8c; --gs-del: #bf616a; --gs-warn: #ebcb8b; --gs-info: #81a1c1;
295
+ --gs-add-bg: rgba(163, 190, 140, 0.16); --gs-del-bg: rgba(191, 97, 106, 0.18);
296
+ --gs-warn-bg: rgba(235, 203, 139, 0.16); --gs-info-bg: rgba(129, 161, 193, 0.16);
297
+ --gs-add-line: rgba(163, 190, 140, 0.14); --gs-del-line: rgba(191, 97, 106, 0.15);
298
+ --gs-add-word: rgba(163, 190, 140, 0.38); --gs-del-word: rgba(191, 97, 106, 0.4);
299
+ --gs-hunk: rgba(136, 192, 208, 0.1); --gs-neutral-bg: rgba(154, 165, 184, 0.16);
300
+ --gs-backdrop: rgba(29, 35, 45, 0.62); --gs-shadow: rgba(20, 25, 33, 0.55);
301
+ }
302
+ .overlay[data-gs-theme='nord-light'] {
303
+ --gs-bg: #eceff4; --gs-panel: #e5e9f0; --gs-raise: #d8dee9;
304
+ --gs-border: #c3cbd8; --gs-border-soft: #dfe4ec;
305
+ --gs-fg: #2e3440; --gs-fg-muted: #3b4252; --gs-fg-dim: #4c566a; --gs-fg-faint: #6b7689; --gs-fg-fainter: #9aa4b5;
306
+ --gs-accent: #5e81ac; --gs-accent-bg: rgba(94, 129, 172, 0.12); --gs-accent-border: rgba(94, 129, 172, 0.5);
307
+ --gs-add: #5e7d47; --gs-del: #a5424c; --gs-warn: #97701f; --gs-info: #4c7a8c;
308
+ --gs-add-bg: rgba(94, 125, 71, 0.14); --gs-del-bg: rgba(165, 66, 76, 0.13);
309
+ --gs-warn-bg: rgba(151, 112, 31, 0.14); --gs-info-bg: rgba(76, 122, 140, 0.13);
310
+ --gs-add-line: rgba(94, 125, 71, 0.14); --gs-del-line: rgba(165, 66, 76, 0.12);
311
+ --gs-add-word: rgba(94, 125, 71, 0.34); --gs-del-word: rgba(165, 66, 76, 0.32);
312
+ --gs-hunk: rgba(94, 129, 172, 0.09); --gs-neutral-bg: rgba(76, 86, 106, 0.13);
313
+ --gs-backdrop: rgba(46, 52, 64, 0.26); --gs-shadow: rgba(46, 52, 64, 0.18);
314
+ }
315
+
316
+ /* ============================ drawer shell ============================ */
317
+
318
+ /* ---- geometry and type scale ----
319
+ *
320
+ * The drawer is a plugin surface inside dsh, so it borrows dsh's own control
321
+ * vocabulary rather than inventing a second one. The numbers below are measured
322
+ * from the running app, not guessed: dsh settles on a 28px control height, a
323
+ * 12px radius for its rows and panes, a full pill for its dropdown triggers,
324
+ * and the `--dsw-font-*` ladder for type.
325
+ *
326
+ * Every control in this stylesheet resolves its height, radius, padding and
327
+ * size through these. That is the whole mechanism keeping the drawer coherent —
328
+ * before it, eight classes each spelled out their own padding and radius, and
329
+ * the same "small secondary button" rendered at four different heights.
330
+ */
331
+ .overlay {
332
+ --gs-inset: 14px;
333
+
334
+ /* Control geometry — dsh's own. */
335
+ --gs-h-control: 28px; /* chrome: header actions, chips, ref pickers */
336
+ --gs-h-compact: 24px; /* in-pane tools, where vertical space belongs to the diff */
337
+ --gs-pad-control: 0 12px;
338
+ --gs-pad-compact: 0 10px;
339
+
340
+ /* Readable against a solid --gs-accent fill. White for every palette whose
341
+ accent is a mid-to-dark hue; the two palettes with a light accent override
342
+ it, because white on #00f0ff is not a colour anyone can read. */
343
+ --gs-on-accent: #ffffff;
344
+
345
+ /* Commit-graph lanes. Deliberately NOT per-palette: a lane's colour carries no
346
+ meaning beyond "not the same as the lane beside it", so it has nothing to
347
+ follow the theme for. What it does need is to stay legible on every ground
348
+ the drawer can paint, from #0d1117 to Solarized's cream — these are mid
349
+ hues chosen to hold on both ends rather than tuned to either. */
350
+ --gs-graph-0: #58a6ff;
351
+ --gs-graph-1: #3fb950;
352
+ --gs-graph-2: #d29922;
353
+ --gs-graph-3: #bc8cff;
354
+ --gs-graph-4: #f778ba;
355
+ --gs-graph-5: #39c5cf;
356
+
357
+ /* Radii. Four values, each with one job; nothing else may invent a fifth. */
358
+ --gs-r-pill: 999px; /* drawer chrome: header actions, ref chips, triggers */
359
+ --gs-r-control: 8px; /* controls inside a panel or pane; inputs; badges */
360
+ --gs-r-surface: 12px; /* rows, popovers, panes */
361
+ --gs-r-drawer: 14px; /* the drawer and its companion card */
362
+
363
+ /* Type — dsh's --dsw-font-* ladder, which tops out at 11px for meta text. */
364
+ --gs-t-meta: 11px; /* --dsw-font-xxxs-11: counters, captions */
365
+ --gs-t-dense: 12px; /* --dsw-font-xxs-12: dense list rows */
366
+ --gs-t-ui: 13px; /* --dsw-font-xs-13: controls, tabs, body */
367
+
368
+ /* Two gutters, one per container. Chrome bars that span the drawer — header,
369
+ tabs, worktree chips, path line, compare bar — share `--gs-gutter`; they
370
+ used to sit at 20/20/14/20/20 and the drawer's left edge read as ragged.
371
+ Inside a pane the gutter is tighter, because a pane is already inset by its
372
+ divider and its rows carry indentation of their own. */
373
+ --gs-gutter: 16px;
374
+ --gs-gutter-pane: 12px;
375
+
376
+ position: fixed; inset: 0; z-index: 1000;
377
+ display: flex; justify-content: flex-end; align-items: stretch; gap: 12px;
378
+ padding: var(--gs-inset);
379
+ box-sizing: border-box;
380
+ background: var(--gs-backdrop);
381
+ animation: gsFade 160ms ease-out;
382
+ }
383
+ .drawer {
384
+ position: relative;
385
+ display: flex; flex-direction: column; box-sizing: border-box;
386
+ width: min(1600px, 94vw); height: 100%;
387
+ color: var(--gs-fg);
388
+ background: var(--gs-bg);
389
+ border: 1px solid var(--gs-border);
390
+ border-radius: var(--gs-r-drawer);
391
+ /* Rounded corners only hold if the panes cannot paint past them. This also
392
+ clips the scaled-up background layer below. */
393
+ overflow: hidden;
394
+ box-shadow: 0 24px 64px var(--gs-shadow);
395
+ font-family: var(--dsw-font-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif);
396
+ animation: gsSlide 180ms cubic-bezier(0.22, 1, 0.36, 1);
397
+ /* Surfaces the panes paint with. Identical to the palette's own colours until
398
+ a background image is set, at which point they thin out to let it show. Every
399
+ large filled area uses these; small chips and hovers keep the solid tokens,
400
+ since a translucent 20px pill only reads as muddy. */
401
+ --gs-surface: var(--gs-bg);
402
+ --gs-surface-2: var(--gs-panel);
403
+ }
404
+ /* `--gs-bg-image`, `--gs-bg-blur` and `--gs-veil` are set inline by the panel
405
+ from the winning style scope; the attribute is what switches the surfaces. */
406
+ .drawer[data-gs-bg] {
407
+ --gs-surface: color-mix(in srgb, var(--gs-bg) var(--gs-veil), transparent);
408
+ --gs-surface-2: color-mix(in srgb, var(--gs-panel) var(--gs-veil), transparent);
409
+ }
410
+ /* Background image layer. Scaled past the edges because a blur samples beyond
411
+ its own box and would otherwise fade to transparent at the border. */
412
+ .drawer[data-gs-bg]::before {
413
+ content: '';
414
+ position: absolute; inset: 0; z-index: 0;
415
+ background-image: var(--gs-bg-image);
416
+ background-size: cover; background-position: center;
417
+ filter: blur(var(--gs-bg-blur));
418
+ transform: scale(1.12);
419
+ pointer-events: none;
420
+ }
421
+ /* Everything real sits above that layer. The drag handle is excluded because it
422
+ is absolutely positioned and carries its own stacking order; matching it here
423
+ would leave the two rules deciding by source order.
424
+ Chrome that hosts a popover must sit ABOVE `.body`: later siblings with the
425
+ same z-index paint on top, which buried Theme and Branch menus under the
426
+ panes. A menu that renders but cannot be clicked looks like a dead control.
427
+ Popovers also drop DOWNWARD, so a bar's list can reach past every bar below
428
+ it — which means the ranks must strictly DECREASE down the DOM, or a lower
429
+ bar at the same z-index absorbs the rows (the worktree picker's list was
430
+ unclickable behind the sync bar until they were separated).
431
+ STRICTLY decreasing, including between the header and the tabs. Those two
432
+ shared rank 23 for as long as the header's only menu was Appearance, which
433
+ portals to the rail outside the drawer and so never dropped through anything.
434
+ The worktree picker does drop, and the tab bar — a later sibling at the same
435
+ rank — swallowed its filter box on the first try. */
436
+ .drawer > *:not(.resizer) { position: relative; z-index: 1; }
437
+ .drawer > .header { z-index: 24; }
438
+ .drawer > .tabs { z-index: 23; }
439
+ .drawer > .compareBar { z-index: 22; }
440
+ .drawer > .syncBar { z-index: 20; }
441
+ /* Maximized drops the inset and the rounding: at full bleed they would only
442
+ waste edge pixels the diff wants. */
443
+ .overlayMax { padding: 0; gap: 0; }
444
+ .overlayMax .drawer { flex: 1 1 auto; width: auto; min-width: 0; border-radius: 0; border-width: 0 0 0 1px; }
445
+ @keyframes gsFade { from { opacity: 0; } }
446
+ @keyframes gsSlide { from { transform: translateX(24px); opacity: 0; } }
447
+
448
+ /* Drag handle on the leading edge: a 10px hit area that is easy to grab, showing
449
+ a short centred grip in the card's own border colour. A full-height accent bar
450
+ was read as a second scrollbar and pulled the eye off the diff. */
451
+ .resizer {
452
+ position: absolute; top: 0; bottom: 0; left: 0; z-index: 30;
453
+ display: flex; align-items: center; justify-content: center;
454
+ width: 10px;
455
+ cursor: col-resize;
456
+ touch-action: none;
457
+ }
458
+ .resizer::after {
459
+ content: '';
460
+ width: 3px; height: 32px;
461
+ border-radius: 999px;
462
+ background: var(--gs-fg-fainter);
463
+ opacity: 0;
464
+ transition: opacity 140ms ease, height 140ms ease;
465
+ }
466
+ .resizer:hover::after { opacity: 0.6; }
467
+ .resizerActive::after { opacity: 0.9; height: 56px; }
468
+
469
+ /* Divider between two panes. It carries the 1px rule the panes used to draw as
470
+ their own border, so the visible line and the drag target are one element and
471
+ cannot drift apart. */
472
+ .paneDivider {
473
+ position: relative; z-index: 5;
474
+ flex: none;
475
+ display: flex; align-items: center; justify-content: center;
476
+ width: 7px;
477
+ cursor: col-resize;
478
+ touch-action: none;
479
+ }
480
+ .paneDivider::before {
481
+ content: '';
482
+ position: absolute; top: 0; bottom: 0; left: 3px;
483
+ width: 1px;
484
+ background: var(--gs-border-soft);
485
+ }
486
+ .paneDivider::after {
487
+ content: '';
488
+ position: relative;
489
+ width: 3px; height: 28px;
490
+ border-radius: 999px;
491
+ background: var(--gs-fg-fainter);
492
+ opacity: 0;
493
+ transition: opacity 140ms ease, height 140ms ease;
494
+ }
495
+ .paneDivider:hover::after { opacity: 0.6; }
496
+ .paneDividerActive::after { opacity: 0.9; height: 48px; }
497
+
498
+ .header {
499
+ display: flex; align-items: center; justify-content: space-between; gap: 12px;
500
+ flex: none;
501
+ padding: 10px var(--gs-gutter);
502
+ border-bottom: 1px solid var(--gs-border);
503
+ background: var(--gs-surface-2);
504
+ font-family: var(--dsw-font-ui, system-ui, sans-serif);
505
+ font-size: var(--gs-t-ui);
506
+ }
507
+ .headerLeft { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; min-width: 0; }
508
+ /* The subject of the drawer: which worktree. Worn by the picker's trigger when
509
+ there is a choice (`.headerPicker` below) and by a plain span when there is
510
+ not, so a one-worktree repository still states its identity in the same
511
+ place, at the same weight. */
512
+ .headerBranch {
513
+ display: inline-flex; align-items: center; gap: 6px;
514
+ padding: 2px 10px;
515
+ border: 1px solid var(--gs-accent-border);
516
+ border-radius: 999px;
517
+ background: var(--gs-accent-bg);
518
+ color: var(--gs-accent);
519
+ font-weight: 600;
520
+ }
521
+ /* Colour only — the geometry stays the button vocabulary's, so the trigger is
522
+ the same pill as every other menu in the drawer and only its role differs.
523
+ Qualified with `.refButton` deliberately: the shared vocabulary is declared
524
+ at the FOOT of this file, so a bare `.headerPicker` up here loses every
525
+ property they both set to source order, and the chip came out grey. */
526
+ .refButton.headerPicker {
527
+ /* The drawer's subject, in a row with 1400px to spare — the 260px the small
528
+ pickers share clipped `feature/nested/deep/scheduler` at a point where the
529
+ name needed 205. Still capped, so one absurd branch cannot push the totals
530
+ off the row. */
531
+ max-width: 420px;
532
+ border-color: var(--gs-accent-border);
533
+ background: var(--gs-accent-bg);
534
+ color: var(--gs-accent);
535
+ font-weight: 600;
536
+ }
537
+ .refButton.headerPicker:hover { background: var(--gs-accent-bg); color: var(--gs-accent); border-color: var(--gs-accent); }
538
+
539
+ /* Slash-separated names — paths and branches alike — that give up their HEAD
540
+ when the row runs short. Plain `text-overflow: ellipsis` eats the leaf, and
541
+ the leaf is the half that answers "which one": `…/worktrees/fixture-07` vs
542
+ `…/worktrees/fixture-14`, `feature/deep/parser` vs `feature/deep/lexer`.
543
+
544
+ The shrink is WEIGHTED rather than pinned. `flex: none` on the tail read
545
+ correctly for a path, but a name with no head at all — a bare
546
+ `some-very-long-branch-name` — would then refuse to shrink and simply
547
+ overflow its row. At 999 the head absorbs effectively all of it, and the
548
+ tail only starts giving ground once the head has nothing left. */
549
+ .elide { display: inline-flex; align-items: baseline; flex: 0 1 auto; min-width: 0; }
550
+ .elideHead {
551
+ flex: 0 999 auto; min-width: 0;
552
+ overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
553
+ }
554
+ .elideTail {
555
+ flex: 0 1 auto; min-width: 0;
556
+ overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
557
+ }
558
+ /* The header's path: quiet, with the leaf a shade brighter than its directory,
559
+ and free to take whatever the totals beside it leave. */
560
+ .headerPathMain { color: var(--gs-fg-faint); font-size: var(--gs-t-meta); }
561
+ .headerPathMain .elideTail { color: var(--gs-fg-dim); }
562
+ /* Either end of the compare pill, so a long branch elides there too rather
563
+ than stretching the pill across the header. */
564
+ .headerViewRef { max-width: 160px; }
565
+
566
+ /* What the view is about when it is NOT the working tree: a commit hash, or a
567
+ comparison's two ends. Quieter than the worktree chip — the worktree is where
568
+ you are, this is only what you are looking at inside it. */
569
+ .headerView {
570
+ flex: none;
571
+ padding: 1px 8px;
572
+ border-radius: 999px;
573
+ background: var(--gs-neutral-bg);
574
+ color: var(--gs-fg-muted);
575
+ font-size: var(--gs-t-meta);
576
+ font-variant-numeric: tabular-nums;
577
+ }
578
+ .headerDetached {
579
+ padding: 1px 8px;
580
+ border-radius: 999px;
581
+ background: var(--gs-warn-bg);
582
+ color: var(--gs-warn);
583
+ font-size: var(--gs-t-meta);
584
+ letter-spacing: 0.02em;
585
+ }
586
+ .headerTotals { font-variant-numeric: tabular-nums; }
587
+ .headerTotalsAdd { color: var(--gs-add); font-weight: 600; }
588
+ .headerTotalsDel { color: var(--gs-del); font-weight: 600; }
589
+ .headerTotalsDim { color: var(--gs-fg-dim); font-size: var(--gs-t-dense); }
590
+ /* The gap is the rule, not each button's margin. `.btn` used to carry its own
591
+ `margin-left`, which meant the spacing lived in the button and vanished the
592
+ moment the button's geometry moved into the shared vocabulary. */
593
+ .headerRight { display: flex; flex: none; gap: 8px; position: relative; }
594
+
595
+ /* Theme button in the header. The panel is a companion card in the overlay,
596
+ to the LEFT of the drawer — not a column stolen from the diff, and not a
597
+ popover painted over the code. */
598
+ .theme { position: relative; display: inline-flex; }
599
+ /* Settings hangs from its own gear like every other menu in the drawer, right
600
+ edge aligned to the button because the button is the last thing in the row
601
+ and a left-aligned card would run off the drawer. Wider than a ref list: it
602
+ holds a palette grid, an image preview and a CSS editor, none of which fit a
603
+ menu-width column. */
604
+ .refPop.settingsPop {
605
+ top: calc(100% + 6px); right: 0; left: auto;
606
+ width: 320px; max-width: calc(100vw - 32px);
607
+ max-height: min(560px, calc(100vh - 120px));
608
+ }
609
+ .themeRail {
610
+ flex: 1; min-height: 0;
611
+ display: flex; flex-direction: column; gap: 14px;
612
+ padding: 12px 14px 16px;
613
+ overflow-y: auto;
614
+ font-family: var(--dsw-font-ui, system-ui, sans-serif);
615
+ }
616
+ .themeGroup { display: flex; flex-direction: column; gap: 6px; }
617
+ .themeLabel {
618
+ color: var(--gs-fg-faint);
619
+ font-size: var(--gs-t-meta); font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase;
620
+ }
621
+ .themeRowSplit { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
622
+
623
+ /* Mode picker. Each option is a painted swatch over its label, and the swatch
624
+ colours are FIXED rather than palette-derived: "light" and "dark" name the
625
+ colours themselves, so a dark-palette button rendering the light option in
626
+ dark grey would be telling the user the opposite of what it does. */
627
+ /* The mode picker is the one control that is taller than the scale: it stacks a
628
+ painted swatch over its label, because "light" and "dark" are better shown
629
+ than named. Everything except the height comes from the vocabulary. */
630
+ .segmented { display: flex; gap: 6px; }
631
+ .segment {
632
+ flex: 1 1 0;
633
+ display: flex; flex-direction: column; gap: 4px; align-items: stretch;
634
+ box-sizing: border-box;
635
+ padding: 4px;
636
+ border: 1px solid var(--gs-border); border-radius: var(--gs-r-control);
637
+ background: transparent; color: var(--gs-fg-dim);
638
+ font-family: inherit; font-size: var(--gs-t-meta);
639
+ cursor: pointer;
640
+ transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
641
+ }
642
+ .segment:hover { background: var(--gs-raise); border-color: var(--gs-fg-fainter); color: var(--gs-fg); }
643
+ .segment:focus-visible { outline: 2px solid var(--gs-accent); outline-offset: 1px; }
644
+ .segmentChip {
645
+ height: 20px;
646
+ border: 1px solid rgba(128, 128, 128, 0.45);
647
+ border-radius: calc(var(--gs-r-control) - 4px);
648
+ }
649
+ .chipLight { background: #ffffff; }
650
+ .chipDark { background: #0b0b0d; }
651
+ /* System takes both halves, which is how every OS appearance control shows it. */
652
+ .chipSystem { background: linear-gradient(105deg, #ffffff 0 50%, #0b0b0d 50% 100%); }
653
+
654
+ .paletteRow {
655
+ display: flex; align-items: center; gap: 8px;
656
+ width: 100%; box-sizing: border-box;
657
+ padding: 5px 8px;
658
+ border: 0; border-radius: var(--gs-r-control);
659
+ background: transparent; color: var(--gs-fg-muted);
660
+ font-family: inherit; font-size: var(--gs-t-dense);
661
+ text-align: left; cursor: pointer;
662
+ }
663
+ .paletteRow:hover { background: var(--gs-raise); color: var(--gs-fg); }
664
+ /* .paletteRowActive: see the shared selected-state rule at the foot of the file. */
665
+ /* Three-stop swatch: ground, accent, and the add colour the diff leans on. */
666
+ .swatch {
667
+ flex: none;
668
+ display: inline-flex; overflow: hidden;
669
+ width: 26px; height: 12px;
670
+ border: 1px solid var(--gs-border); border-radius: 3px;
671
+ }
672
+ .swatch span { flex: 1; }
673
+
674
+ /* Scope switch: which of the two stores an edit lands in. */
675
+ .scopeRow { display: flex; gap: 6px; }
676
+ .scopeHint { color: var(--gs-fg-faint); font-size: var(--gs-t-meta); overflow-wrap: anywhere; }
677
+
678
+ .bgPreview {
679
+ height: 64px;
680
+ border: 1px solid var(--gs-border); border-radius: var(--gs-r-control);
681
+ background-color: var(--gs-bg);
682
+ background-size: cover; background-position: center;
683
+ }
684
+ .bgEmpty {
685
+ display: flex; align-items: center; justify-content: center;
686
+ color: var(--gs-fg-faint); font-size: var(--gs-t-meta);
687
+ }
688
+ .sliderRow { display: flex; align-items: center; gap: 8px; font-size: var(--gs-t-meta); color: var(--gs-fg-dim); }
689
+ .sliderRow input { flex: 1; min-width: 0; accent-color: var(--gs-accent); }
690
+ .sliderValue { flex: none; width: 38px; text-align: right; font-variant-numeric: tabular-nums; }
691
+
692
+ .cssArea {
693
+ width: 100%; box-sizing: border-box;
694
+ min-height: 96px; max-height: 220px;
695
+ padding: 7px 8px;
696
+ border: 1px solid var(--gs-border); border-radius: var(--gs-r-control);
697
+ background: var(--gs-bg); color: var(--gs-fg);
698
+ font-family: var(--dsw-font-mono, ui-monospace, Consolas, monospace); font-size: var(--gs-t-meta);
699
+ resize: vertical;
700
+ outline: none;
701
+ }
702
+ .cssArea:focus { border-color: var(--gs-accent-border); }
703
+ .themeNote { color: var(--gs-warn); font-size: var(--gs-t-meta); }
704
+ .themeDirty { color: var(--gs-accent); font-size: var(--gs-t-meta); }
705
+
706
+ /* Worktree rows give their whole width to the branch; `.refRowName` already
707
+ flexes and ellipsises, so nothing else here competes for it. The path is on
708
+ the row's `title`. */
709
+ /* Marks the session's OWN worktree, independent of which one is being viewed. */
710
+ .wtCurrent { color: var(--gs-add); font-size: 8px; line-height: 1; }
711
+
712
+ .commitCopy { margin-left: auto; }
713
+
714
+ .commitPop {
715
+ position: fixed; z-index: 40;
716
+ box-sizing: border-box;
717
+ width: min(380px, calc(100vw - 24px));
718
+ display: flex; flex-direction: column; gap: 6px;
719
+ padding: 10px 12px 12px;
720
+ overflow: auto;
721
+ border: 1px solid var(--gs-border); border-radius: var(--gs-r-surface);
722
+ background: var(--gs-panel); color: var(--gs-fg);
723
+ box-shadow: 0 16px 40px var(--gs-shadow);
724
+ font-family: var(--dsw-font-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif);
725
+ font-size: var(--gs-t-dense);
726
+ }
727
+ .commitPopTop { display: flex; align-items: center; gap: 8px; }
728
+ .commitPopTop .commitWhen { margin-right: auto; }
729
+ .commitPopSubject {
730
+ color: var(--gs-fg);
731
+ font-size: var(--gs-t-ui); font-weight: 600; line-height: 18px;
732
+ white-space: pre-wrap; overflow-wrap: anywhere;
733
+ user-select: text;
734
+ }
735
+ .commitPopBody {
736
+ margin: 0;
737
+ color: var(--gs-fg-muted);
738
+ font-family: inherit;
739
+ font-size: var(--gs-t-dense); line-height: 18px;
740
+ white-space: pre-wrap; overflow-wrap: anywhere;
741
+ user-select: text;
742
+ }
743
+
744
+ /* Drawer tabs — mirrors the app's own tab bar: 13/16 text at weight 500, a
745
+ tertiary label, the selected one riding the accent over a 2px bar. */
746
+ .tabs {
747
+ display: flex; gap: 24px;
748
+ flex: none;
749
+ padding: 0 var(--gs-gutter);
750
+ border-bottom: 1px solid var(--gs-border-soft);
751
+ background: var(--gs-surface-2);
752
+ font-family: inherit;
753
+ }
754
+ .tab {
755
+ position: relative;
756
+ padding: 8px 0 9px;
757
+ border: none;
758
+ background: transparent;
759
+ font-family: inherit;
760
+ font-size: var(--gs-t-ui); line-height: 16px; font-weight: 500;
761
+ color: var(--gs-fg-dim);
762
+ cursor: pointer;
763
+ }
764
+ .tab::after {
765
+ content: '';
766
+ position: absolute; right: 0; bottom: 1px; left: 0;
767
+ height: 2px; border-radius: 2px;
768
+ background: transparent;
769
+ }
770
+ .tabActive { color: var(--gs-accent); }
771
+ .tabActive::after { background: var(--gs-accent); }
772
+
773
+ /* Compare tab: the base/head ref pickers. */
774
+ .compareBar {
775
+ display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
776
+ flex: none;
777
+ padding: 8px var(--gs-gutter);
778
+ border-bottom: 1px solid var(--gs-border-soft);
779
+ background: var(--gs-surface-2);
780
+ color: var(--gs-fg-dim);
781
+ font-family: var(--dsw-font-ui, system-ui, sans-serif);
782
+ font-size: var(--gs-t-dense);
783
+ }
784
+ .compareArrow { color: var(--gs-fg-faint); }
785
+
786
+ /* Ref picker: a button that opens a filter box over a scrolling list. Sized for
787
+ a repository with hundreds of branches, where a chip row or a plain select
788
+ stops being usable. */
789
+ .refPicker { position: relative; display: inline-flex; align-items: center; gap: 6px; }
790
+ .refLabel { flex: none; color: var(--gs-fg-faint); }
791
+ .refButton { max-width: 260px; justify-content: space-between; }
792
+ .refValue { min-width: 0; overflow: hidden; }
793
+ .refCaret { flex: none; color: var(--gs-fg-faint); font-size: 9px; }
794
+ .refPop {
795
+ position: absolute; top: calc(100% + 4px); left: 0; z-index: 10;
796
+ display: flex; flex-direction: column;
797
+ width: 300px; max-width: 80vw;
798
+ border: 1px solid var(--gs-border); border-radius: var(--gs-r-control);
799
+ background: var(--gs-panel);
800
+ box-shadow: 0 12px 32px var(--gs-shadow);
801
+ overflow: hidden;
802
+ }
803
+ /* A short fixed menu rather than a branch list: no search box, no footer, and
804
+ sized to its own labels instead of the 300px a few hundred refs need. Anchored
805
+ right because the pickers that use it sit in a bar's right-hand cluster, where
806
+ a left-anchored popover would open off the drawer. */
807
+ .menuPop {
808
+ width: auto; min-width: 100%;
809
+ left: auto; right: 0;
810
+ padding: 4px;
811
+ }
812
+ .refSearch {
813
+ flex: none;
814
+ padding: 7px 10px;
815
+ border: 0; border-bottom: 1px solid var(--gs-border-soft);
816
+ background: var(--gs-bg); color: var(--gs-fg);
817
+ font-family: inherit; font-size: var(--gs-t-dense);
818
+ outline: none;
819
+ }
820
+ .refSearch::placeholder { color: var(--gs-fg-faint); }
821
+ /* Bounded so the popover never runs off the drawer, whatever the branch count. */
822
+ .refList { flex: 1; max-height: 280px; overflow-y: auto; padding: 4px; }
823
+ .refGroup {
824
+ padding: 6px 8px 3px;
825
+ color: var(--gs-fg-faint);
826
+ font-size: var(--gs-t-meta); font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase;
827
+ }
828
+ .refRow {
829
+ display: flex; align-items: center; gap: 6px;
830
+ box-sizing: border-box; width: 100%;
831
+ padding: 5px 8px;
832
+ border: 0; border-radius: var(--gs-r-control);
833
+ background: transparent; color: var(--gs-fg-muted);
834
+ font-family: inherit; font-size: var(--gs-t-dense);
835
+ text-align: left; cursor: pointer;
836
+ }
837
+ .refRow:hover { background: var(--gs-raise); color: var(--gs-fg); }
838
+ /* .refRowActive: see the shared selected-state rule at the foot of the file. */
839
+ /* Keeps a branch without a worktree aligned with one that has the glyph. */
840
+ .refRowSpacer { flex: none; width: 12px; }
841
+ .refRowName { flex: 1 1 auto; min-width: 0; overflow: hidden; }
842
+ .refEmpty { padding: 14px 10px; color: var(--gs-fg-faint); font-size: var(--gs-t-dense); }
843
+ .refFoot {
844
+ flex: none;
845
+ padding: 5px 10px;
846
+ border-top: 1px solid var(--gs-border-soft);
847
+ color: var(--gs-fg-faint); font-size: var(--gs-t-meta);
848
+ }
849
+
850
+ /* Commit log — its own full-height pane beside the file tree. Rows are stacked
851
+ so each subject gets the pane's full width instead of a 340px ellipsis. */
852
+ .commitsPane {
853
+ /* Shrinkable, with the floor as a min-width rather than a JS clamp alone: a
854
+ width dragged in one tab must not squeeze the diff to nothing when a third
855
+ pane appears in another. The floors come from the panel's own constants, so
856
+ the clamp and the stylesheet cannot drift apart. */
857
+ flex: 0 1 auto; box-sizing: border-box;
858
+ display: flex; flex-direction: column; min-height: 0;
859
+ /* Capped, so every pixel past the cap goes to the diff — the pane whose
860
+ content cannot wrap. */
861
+ width: 26%; min-width: var(--gs-min-commits); max-width: 340px;
862
+ background: var(--gs-surface);
863
+ }
864
+ /* Shared header bar for the commit and tree panes. Its own height comes from
865
+ the compact control it hosts, so a pane with tools and a pane without one
866
+ still line up across the divider. */
867
+ .paneHead {
868
+ display: flex; align-items: center; gap: 8px;
869
+ flex: none;
870
+ min-height: 37px; box-sizing: border-box;
871
+ padding: 6px var(--gs-gutter-pane);
872
+ border-bottom: 1px solid var(--gs-border-soft);
873
+ color: var(--gs-fg-dim);
874
+ font-family: var(--dsw-font-ui, system-ui, sans-serif);
875
+ font-size: var(--gs-t-dense);
876
+ }
877
+ .paneTitle { font-weight: 600; }
878
+ /* Zero-height probe below the last row; scrolling it into view fetches the next
879
+ page. It carries no pixels of its own so it cannot disturb the list. */
880
+ .commitsSentinel { flex: none; height: 1px; }
881
+ .commitsFoot {
882
+ flex: none;
883
+ padding: 8px;
884
+ color: var(--gs-fg-faint);
885
+ font-family: var(--dsw-font-ui, system-ui, sans-serif);
886
+ font-size: var(--gs-t-meta); text-align: center;
887
+ /* Reserved whether or not there is text, so arriving at the end of the list
888
+ does not shift the rows above it. */
889
+ min-height: 16px;
890
+ }
891
+
892
+ /* `flex: 1` with `min-height: 0` gives this scroller a DEFINITE height from the
893
+ pane. A percentage max-height cannot: its parent is sized by content, so the
894
+ percentage has nothing to resolve against and the list grows without bound —
895
+ which is what pushed the file tree off screen and the footer out of reach. */
896
+ /* No gap between lines and no left padding: the graph column runs down the left
897
+ edge and a lane has to reach the next row to stay one line. The rows' own
898
+ breathing room is inside `.commit`, which keeps its inset and rounding. */
899
+ .commits {
900
+ display: flex; flex-direction: column;
901
+ flex: 1; min-height: 0;
902
+ overflow-y: auto;
903
+ padding: 4px 6px 4px 0;
904
+ }
905
+ /* One list line: the graph slice, then the row. Fixed height, because the SVG
906
+ is drawn at exactly this height and the lanes only join if they agree. */
907
+ .commitLine {
908
+ display: flex; align-items: stretch;
909
+ flex: none;
910
+ height: 48px;
911
+ }
912
+ .graphCell { flex: none; display: block; }
913
+ /* An inset row, like dsh's own session rows: 12px radius, filled when active. */
914
+ .commit {
915
+ display: flex; flex-direction: column; justify-content: center; gap: 2px;
916
+ flex: 1 1 auto; min-width: 0; box-sizing: border-box;
917
+ height: 100%;
918
+ margin: 1px 0;
919
+ padding: 4px 8px;
920
+ border: 1px solid transparent;
921
+ border-radius: var(--gs-r-surface);
922
+ background: transparent;
923
+ font-family: inherit;
924
+ text-align: left;
925
+ cursor: pointer;
926
+ font-size: var(--gs-t-dense);
927
+ overflow: hidden;
928
+ transition: background 120ms ease, border-color 120ms ease;
929
+ }
930
+ .commit:hover { background: var(--gs-raise); }
931
+ .commitActive { border-color: var(--gs-accent-border); background: var(--gs-accent-bg); }
932
+ .commitTop { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
933
+ .commitHash { color: var(--gs-info); }
934
+ .commitSubjectRow {
935
+ display: flex; align-items: baseline; gap: 4px;
936
+ min-width: 0;
937
+ }
938
+ .commitSubject {
939
+ flex: 1; min-width: 0;
940
+ overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
941
+ color: var(--gs-fg-muted);
942
+ }
943
+ .commitHasBody { flex: none; color: var(--gs-fg-faint); letter-spacing: 0.04em; }
944
+ /* Branch and tag names pointing at this commit. Capped and ellipsised: a release
945
+ commit can carry a dozen refs, and none of them is worth the subject's room. */
946
+ .commitRef {
947
+ flex: none;
948
+ max-width: 96px;
949
+ overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
950
+ padding: 0 5px;
951
+ border: 1px solid var(--gs-accent-border); border-radius: var(--gs-r-pill);
952
+ background: var(--gs-accent-bg);
953
+ color: var(--gs-accent);
954
+ font-size: var(--gs-t-meta); line-height: 15px;
955
+ }
956
+ .commitWhen { flex: none; color: var(--gs-fg-faint); }
957
+
958
+ .body { display: flex; flex: 1; min-height: 0; z-index: 1; }
959
+
960
+ /* File tree column. A peer of the commit pane and the diff, never their parent:
961
+ each of the three scrolls on its own and keeps its full height. */
962
+ .treeCol {
963
+ flex: 0 1 auto; box-sizing: border-box;
964
+ display: flex; flex-direction: column; min-height: 0;
965
+ width: 28%; min-width: var(--gs-min-tree); max-width: 400px;
966
+ background: var(--gs-surface);
967
+ }
968
+
969
+ /* file tree */
970
+ .treeWrap {
971
+ flex: 1; box-sizing: border-box;
972
+ display: flex; flex-direction: column;
973
+ min-width: 0; min-height: 0;
974
+ }
975
+ /* Two items, not five: the count and one group of buttons. The pane drags down
976
+ to 170px, so the row does have to wrap — but it wraps between the count and
977
+ the group, never inside it. Wrapping the buttons individually is what left a
978
+ lone "Collapse all" stranded on a second row under a right-aligned
979
+ "Expand all". `space-between` then aligns whichever way the row broke. */
980
+ .treeTools {
981
+ display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
982
+ justify-content: space-between;
983
+ flex: none;
984
+ min-height: 37px; box-sizing: border-box;
985
+ padding: 6px var(--gs-gutter-pane);
986
+ border-bottom: 1px solid var(--gs-border-soft);
987
+ }
988
+ /* Wraps only when the group alone cannot fit the pane — five controls do not
989
+ go into 170px by any arrangement, and a clipped button is worse than a
990
+ second row. Above ~250px this never fires. */
991
+ .treeActions { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
992
+ /* The root tick sits in the same column the rows read down, so it is pulled out
993
+ of the toolbar's gutter to the pane edge rather than aligned to the label. */
994
+ .treeLead { display: flex; align-items: center; gap: 4px; min-width: 0; }
995
+ .treeLabel {
996
+ flex: none;
997
+ white-space: nowrap;
998
+ color: var(--gs-fg-dim); font-size: var(--gs-t-dense);
999
+ }
1000
+ .tree {
1001
+ flex: 1; min-height: 0;
1002
+ margin: 0; padding: 6px 0 16px;
1003
+ overflow: auto; list-style: none;
1004
+ }
1005
+ /* The list's stand-in while the view's file list is in flight: centred and
1006
+ quiet, never a claim about how many changes exist. */
1007
+ .treeEmpty {
1008
+ flex: 1; min-height: 0;
1009
+ display: flex; align-items: center; justify-content: center;
1010
+ color: var(--gs-fg-faint); font-size: var(--gs-t-ui);
1011
+ }
1012
+ /* Nested lists are NOT .tree: only the root list scrolls, otherwise every level
1013
+ would grow its own scrollbar. */
1014
+ .treeSub {
1015
+ position: relative;
1016
+ margin: 0; padding: 0;
1017
+ list-style: none;
1018
+ }
1019
+ /* One hairline per nesting level at the parent's chevron column, so a deep tree
1020
+ reads as a tree rather than a ragged left edge. The offset comes from the row
1021
+ that owns the level (see TREE_RAIL_OFFSET). */
1022
+ .treeSub::before {
1023
+ content: '';
1024
+ position: absolute;
1025
+ top: 0; bottom: 0; left: var(--gs-rail, 0);
1026
+ width: 1px;
1027
+ background: var(--gs-border-soft);
1028
+ }
1029
+ .treeDirLi { position: relative; }
1030
+ .treeDir {
1031
+ display: flex; align-items: center; gap: 6px;
1032
+ box-sizing: border-box; width: 100%;
1033
+ padding: 4px 12px 4px 8px;
1034
+ border: 0;
1035
+ background: transparent;
1036
+ color: var(--gs-fg-dim);
1037
+ font-family: inherit;
1038
+ font-size: var(--gs-t-dense); font-weight: 600;
1039
+ text-align: left;
1040
+ cursor: pointer;
1041
+ }
1042
+ .treeDir:hover { background: var(--gs-panel); color: var(--gs-fg-muted); }
1043
+ .treeDirActive { color: var(--gs-fg-muted); }
1044
+ .chevron {
1045
+ flex: none;
1046
+ display: inline-block;
1047
+ width: 10px;
1048
+ color: var(--gs-fg-fainter);
1049
+ font-size: var(--gs-t-meta);
1050
+ transition: transform 120ms ease;
1051
+ }
1052
+ .chevronOpen { transform: rotate(90deg); }
1053
+ .treeDirName {
1054
+ flex: 1; min-width: 0;
1055
+ overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
1056
+ }
1057
+ .treeDirCount {
1058
+ flex: none;
1059
+ padding: 0 6px;
1060
+ border-radius: 999px;
1061
+ background: var(--gs-neutral-bg);
1062
+ color: var(--gs-fg-dim);
1063
+ font-size: var(--gs-t-meta); font-weight: 600; line-height: 16px;
1064
+ }
1065
+ .treeDirCounts { flex: none; font-size: var(--gs-t-meta); font-variant-numeric: tabular-nums; }
1066
+ .file {
1067
+ display: flex; align-items: center; gap: 8px;
1068
+ box-sizing: border-box; width: 100%;
1069
+ padding: 5px 14px;
1070
+ border: 0;
1071
+ border-left: 2px solid transparent;
1072
+ background: transparent;
1073
+ color: var(--gs-fg-muted);
1074
+ font-family: inherit;
1075
+ font-size: var(--gs-t-dense);
1076
+ text-align: left;
1077
+ cursor: pointer;
1078
+ }
1079
+ .file:hover { background: var(--gs-panel); }
1080
+ .fileActive {
1081
+ background: var(--gs-panel);
1082
+ border-left-color: var(--gs-accent);
1083
+ color: var(--gs-fg);
1084
+ }
1085
+
1086
+ .fileStatus {
1087
+ flex: none;
1088
+ display: inline-flex; align-items: center; justify-content: center;
1089
+ width: 18px; height: 18px;
1090
+ border-radius: 5px;
1091
+ font-weight: 700; font-size: var(--gs-t-meta); line-height: 1;
1092
+ }
1093
+ .stAdded, .stUntracked { color: var(--gs-add); background: var(--gs-add-bg); }
1094
+ .stDeleted { color: var(--gs-del); background: var(--gs-del-bg); }
1095
+ .stModified { color: var(--gs-warn); background: var(--gs-warn-bg); }
1096
+ .stRenamed { color: var(--gs-info); background: var(--gs-info-bg); }
1097
+
1098
+ .filePath { flex: 1; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
1099
+ .fileBinary {
1100
+ flex: none;
1101
+ padding: 0 5px;
1102
+ border-radius: 4px;
1103
+ background: var(--gs-neutral-bg);
1104
+ color: var(--gs-fg-dim);
1105
+ font-size: 9px; font-weight: 700; letter-spacing: 0.06em;
1106
+ }
1107
+ .fileCounts { flex: none; font-size: var(--gs-t-meta); font-variant-numeric: tabular-nums; }
1108
+ .fileCountAdd { color: var(--gs-add); }
1109
+ .fileCountDel { color: var(--gs-del); }
1110
+
1111
+ /* diff pane */
1112
+ .diffPane { flex: 1 1 0; min-width: var(--gs-min-diff); overflow: auto; background: var(--gs-surface); }
1113
+ /* The pre is as wide as its longest line (`max-content`) and at least the pane
1114
+ (`min-width: 100%`). Each row then stretches to that width, so add/delete
1115
+ tints survive horizontal scroll instead of stopping at the viewport edge. */
1116
+ .diffPre {
1117
+ display: flex; flex-direction: column;
1118
+ margin: 0; padding: 8px 0 16px;
1119
+ min-width: 100%; width: max-content;
1120
+ /* GitHub's blob stack — Cascadia ligatures and a 16px leading were why this
1121
+ read as a terminal dump instead of a pull-request diff. */
1122
+ font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
1123
+ font-size: var(--gs-t-dense); line-height: 20px; font-weight: 400;
1124
+ font-variant-ligatures: none;
1125
+ font-feature-settings: 'liga' 0, 'calt' 0;
1126
+ tab-size: 4;
1127
+ }
1128
+ .renameLine {
1129
+ padding: 8px 16px;
1130
+ border-bottom: 1px solid var(--gs-border-soft);
1131
+ color: var(--gs-info);
1132
+ font-size: var(--gs-t-dense);
1133
+ }
1134
+ /* The old path is literal content, not prose: give it the body colour so it
1135
+ reads louder than the sentence around it. */
1136
+ .renameLine code { color: var(--gs-fg); }
1137
+
1138
+ /* Each line: [old#][new#][sign][code]. Stretch, don't baseline — empty line-number
1139
+ cells plus nested syntax spans used to inflate the row into prose leading. */
1140
+ .line {
1141
+ display: flex; align-items: stretch;
1142
+ width: max-content; min-width: 100%;
1143
+ min-height: 20px; line-height: 20px;
1144
+ white-space: nowrap;
1145
+ }
1146
+ .lnOld, .lnNew {
1147
+ flex: none;
1148
+ box-sizing: border-box;
1149
+ width: 3.2em;
1150
+ padding: 0 8px;
1151
+ text-align: right;
1152
+ color: var(--gs-fg-fainter);
1153
+ user-select: none;
1154
+ font-variant-numeric: tabular-nums;
1155
+ }
1156
+ .line:hover .lnOld, .line:hover .lnNew { color: var(--gs-fg-dim); }
1157
+ /* No rule down the right edge: GitHub carries the +/- as the first glyph of the
1158
+ code cell and draws nothing between the columns. The rule that used to be
1159
+ here cut every tinted row in half, which is most of why a full-file addition
1160
+ read as two stacked green bands rather than one block of new code. */
1161
+ .gutter {
1162
+ flex: none;
1163
+ width: 1.4em;
1164
+ text-align: center;
1165
+ user-select: none;
1166
+ }
1167
+ .code { flex: 1 0 auto; white-space: pre; padding: 0 16px 0 10px; }
1168
+
1169
+ .lineAdd { background: var(--gs-add-line); }
1170
+ .lineDel { background: var(--gs-del-line); }
1171
+ .lineAdd .lnOld, .lineAdd .lnNew, .lineAdd .gutter { background: var(--gs-add-num, var(--gs-add-line)); }
1172
+ .lineDel .lnOld, .lineDel .lnNew, .lineDel .gutter { background: var(--gs-del-num, var(--gs-del-line)); }
1173
+ .lineContext { color: var(--gs-fg-muted); }
1174
+ /* GitHub's hunk header is quiet: a faint accent wash on the row, muted grey
1175
+ text, and the saturated blue confined to the number cells. It used to run the
1176
+ accent colour across the whole line, which made every hunk boundary shout
1177
+ louder than the code it introduces. */
1178
+ .lineHunk { background: var(--gs-hunk); }
1179
+ .lineHunk .code { color: var(--gs-fg-dim); }
1180
+ .lineHunk .lnOld, .lineHunk .lnNew, .lineHunk .gutter { background: var(--gs-hunk-num, var(--gs-hunk)); }
1181
+ .signAdd { color: var(--gs-add); }
1182
+ .signDel { color: var(--gs-del); }
1183
+
1184
+ .wordAdd { background: var(--gs-add-word); border-radius: 2px; }
1185
+ .wordDel { background: var(--gs-del-word); border-radius: 2px; }
1186
+
1187
+ /* No syntax-tint classes here: Shiki resolves a real TextMate theme and emits a
1188
+ hex per token, so the panel writes colour inline. A four-class approximation
1189
+ painted identifiers, types and punctuation all one colour, which is what made
1190
+ the diff look unhighlighted next to GitHub's. */
1191
+
1192
+ .empty { padding: 32px 24px; color: var(--gs-fg-faint); font-size: var(--gs-t-ui); }
1193
+
1194
+ /* ============================ write operations ============================ */
1195
+
1196
+ /* Sync bar: fetch / pull / push, with the divergence they act on. A chrome bar
1197
+ like the others, so it takes the drawer gutter and not a gutter of its own. */
1198
+ .syncBar {
1199
+ display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
1200
+ flex: none;
1201
+ padding: 8px var(--gs-gutter);
1202
+ border-bottom: 1px solid var(--gs-border-soft);
1203
+ background: var(--gs-surface-2);
1204
+ font-size: var(--gs-t-dense);
1205
+ color: var(--gs-fg-dim);
1206
+ }
1207
+ .syncUpstream {
1208
+ max-width: 260px;
1209
+ overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
1210
+ color: var(--gs-fg-muted);
1211
+ font-variant-numeric: tabular-nums;
1212
+ }
1213
+ .syncLevel { color: var(--gs-fg-faint); }
1214
+ .syncSpacer { flex: 1 1 auto; }
1215
+ .btn:disabled, .treeIcon:disabled, .miniBtn:disabled, .refButton:disabled {
1216
+ opacity: 0.45;
1217
+ cursor: default;
1218
+ }
1219
+ /* A control disabled ONLY by an operation too young to report keeps its full
1220
+ strength. Every tick stages through git, and fading the whole sync row for
1221
+ the ~150ms that takes was a blink, not feedback — nobody can act inside it.
1222
+ The click is still refused from the first frame; see `op-feedback.ts`. */
1223
+ .btn[data-quiet]:disabled, .refButton[data-quiet]:disabled { opacity: 1; }
1224
+
1225
+ /* Outcome of the last operation. Pre-wrap because git's own message rides along
1226
+ under the explanation, and it arrives with its newlines. */
1227
+ .opBanner {
1228
+ flex: none;
1229
+ padding: 8px var(--gs-gutter);
1230
+ border-bottom: 1px solid var(--gs-border-soft);
1231
+ font-size: var(--gs-t-dense); line-height: 18px;
1232
+ white-space: pre-wrap; overflow-wrap: anywhere;
1233
+ user-select: text;
1234
+ }
1235
+ .opBannerOk { background: var(--gs-add-bg); color: var(--gs-add); }
1236
+ .opBannerBad { background: var(--gs-del-bg); color: var(--gs-del); }
1237
+
1238
+ /* A file row and its stage toggle. The row keeps its own indent padding, so the
1239
+ toggle trails it rather than displacing the tree's left edge. */
1240
+ .fileLi { display: flex; align-items: stretch; }
1241
+ .fileLi .file { flex: 1 1 auto; min-width: 0; }
1242
+ /* Directory rows need the same two-slot row as files, but their subtree list
1243
+ has to stay a child of the <li> — hence a wrapper around just the row. */
1244
+ .treeRow { display: flex; align-items: stretch; }
1245
+ .treeRow .treeDir { flex: 1 1 auto; min-width: 0; }
1246
+
1247
+ /* ---- the commit tick ----
1248
+ *
1249
+ * The drawer presents a commit the way IDEA does: a set of ticks, not a staging
1250
+ * area. A tick is `git add` the moment it is clicked, so the tree and
1251
+ * `git status` can never disagree.
1252
+ *
1253
+ * The column does NOT indent with the tree. Depth already costs room in a pane
1254
+ * that drags to 170px, and a straight column down the left edge is what makes
1255
+ * "what am I about to commit" readable in one glance — which is why the ticks
1256
+ * exist. Its width is TREE_CHECK_W; the row indents start after it. */
1257
+ .checkBox {
1258
+ flex: none;
1259
+ display: flex; align-items: center; justify-content: center;
1260
+ width: 22px;
1261
+ padding: 0;
1262
+ border: 0;
1263
+ background: transparent;
1264
+ cursor: pointer;
1265
+ }
1266
+ .checkMark {
1267
+ display: flex; align-items: center; justify-content: center;
1268
+ box-sizing: border-box;
1269
+ width: 14px; height: 14px;
1270
+ border: 1px solid var(--gs-fg-faint); border-radius: 3px;
1271
+ background: var(--gs-bg);
1272
+ color: transparent;
1273
+ font-size: 10px; font-weight: 700; line-height: 1;
1274
+ transition: background 120ms ease, border-color 120ms ease;
1275
+ }
1276
+ .checkBox:hover .checkMark { border-color: var(--gs-accent); }
1277
+ .checkBox:focus-visible { outline: none; }
1278
+ .checkBox:focus-visible .checkMark { outline: 2px solid var(--gs-accent); outline-offset: 1px; }
1279
+ /* Ticked and half-ticked share a filled box: both mean "some of this is going
1280
+ into the commit", and the glyph inside says which. */
1281
+ .checkMarkOn, .checkMarkPartial {
1282
+ border-color: var(--gs-accent);
1283
+ background: var(--gs-accent);
1284
+ color: var(--gs-on-accent);
1285
+ }
1286
+
1287
+ /* Commit box: pinned under the tree, so the message stays reachable however far
1288
+ the file list scrolls. */
1289
+ .commitBox {
1290
+ flex: none;
1291
+ display: flex; flex-direction: column; gap: 6px;
1292
+ padding: 8px var(--gs-gutter-pane) 10px;
1293
+ border-top: 1px solid var(--gs-border);
1294
+ background: var(--gs-surface-2);
1295
+ }
1296
+ .commitMessage {
1297
+ width: 100%; box-sizing: border-box;
1298
+ min-height: 46px; max-height: 160px;
1299
+ padding: 6px 8px;
1300
+ border: 1px solid var(--gs-border); border-radius: var(--gs-r-control);
1301
+ background: var(--gs-bg); color: var(--gs-fg);
1302
+ font-family: inherit; font-size: var(--gs-t-dense); line-height: 18px;
1303
+ resize: vertical;
1304
+ outline: none;
1305
+ }
1306
+ .commitMessage:focus { border-color: var(--gs-accent-border); }
1307
+ .commitMessage::placeholder { color: var(--gs-fg-faint); }
1308
+ .commitRow { display: flex; align-items: center; gap: 8px; }
1309
+ .commitAmend {
1310
+ display: inline-flex; align-items: center; gap: 4px;
1311
+ color: var(--gs-fg-dim); font-size: var(--gs-t-meta);
1312
+ cursor: pointer;
1313
+ }
1314
+ .commitAmend input { accent-color: var(--gs-accent); cursor: pointer; }
1315
+ .commitStaged { margin-left: auto; color: var(--gs-fg-faint); font-size: var(--gs-t-meta); }
1316
+ /* The commit box's one standing instruction: tick to stage, then commit. A
1317
+ constant quiet line at the top — the blocker banner that appeared only when
1318
+ the index was empty read as an error, and arrived after the confusion it
1319
+ explains. */
1320
+ .commitLead { margin: 0; color: var(--gs-fg-faint); font-size: var(--gs-t-meta); line-height: 15px; }
1321
+
1322
+ /* Commit is the one PRIMARY action in the drawer, and the only control that
1323
+ gets a solid fill. As a quiet outlined button it sat at the same weight as
1324
+ Expand all and disappeared into the row — the button that writes history
1325
+ should not look like the button that folds a directory. */
1326
+ .commitBtn {
1327
+ flex: none;
1328
+ box-sizing: border-box;
1329
+ display: inline-flex; align-items: center; justify-content: center;
1330
+ min-width: 92px;
1331
+ height: var(--gs-h-control);
1332
+ padding: 0 16px;
1333
+ border: 1px solid transparent; border-radius: var(--gs-r-control);
1334
+ background: var(--gs-accent); color: var(--gs-on-accent);
1335
+ font-family: inherit; font-size: var(--gs-t-ui); font-weight: 600; line-height: 1;
1336
+ white-space: nowrap;
1337
+ cursor: pointer;
1338
+ transition: filter 120ms ease, opacity 120ms ease;
1339
+ }
1340
+ .commitBtn:hover:not(:disabled) { filter: brightness(1.1); }
1341
+ .commitBtn:focus-visible { outline: 2px solid var(--gs-accent); outline-offset: 2px; }
1342
+ /* Disabled keeps the shape and loses the weight: the button stays findable, so
1343
+ "nothing is staged yet" reads as a state rather than as a missing feature. */
1344
+ .commitBtn:disabled {
1345
+ background: var(--gs-raise); color: var(--gs-fg-faint);
1346
+ border-color: var(--gs-border);
1347
+ cursor: default;
1348
+ }
1349
+
1350
+ /* ============================ one button vocabulary ============================
1351
+ *
1352
+ * Every clickable control in the drawer is one of two shapes, and which one it
1353
+ * gets is decided by WHERE IT LIVES, never by what it does: drawer-level chrome
1354
+ * (header actions, ref chips, menu triggers) is a PILL, and anything inside a
1355
+ * panel or pane is a ROUNDED RECT. They differ in shape and nothing else — same
1356
+ * height, same type size, same hover, same focus ring, same transition.
1357
+ *
1358
+ * Grouping the selectors is what enforces that. Each class used to spell out
1359
+ * its own padding, radius and font-size, and the same "small secondary button"
1360
+ * had drifted to four heights and three radii across one screen.
1361
+ */
1362
+ .btn, .miniBtn, .treeIcon, .commitCopy, .scopeBtn, .refButton {
1363
+ display: inline-flex; align-items: center; justify-content: center; gap: 6px;
1364
+ box-sizing: border-box;
1365
+ flex: none;
1366
+ height: var(--gs-h-control);
1367
+ padding: var(--gs-pad-control);
1368
+ border: 1px solid var(--gs-border);
1369
+ border-radius: var(--gs-r-control);
1370
+ background: transparent;
1371
+ color: var(--gs-fg-dim);
1372
+ font-family: inherit;
1373
+ font-size: var(--gs-t-ui);
1374
+ line-height: 1;
1375
+ white-space: nowrap;
1376
+ cursor: pointer;
1377
+ transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
1378
+ }
1379
+ .btn:hover, .miniBtn:hover, .treeIcon:hover, .commitCopy:hover,
1380
+ .scopeBtn:hover, .refButton:hover {
1381
+ background: var(--gs-raise);
1382
+ color: var(--gs-fg);
1383
+ border-color: var(--gs-fg-fainter);
1384
+ }
1385
+ .btn:focus-visible, .miniBtn:focus-visible, .treeIcon:focus-visible,
1386
+ .commitCopy:focus-visible, .scopeBtn:focus-visible, .refButton:focus-visible {
1387
+ outline: 2px solid var(--gs-accent);
1388
+ outline-offset: 1px;
1389
+ }
1390
+
1391
+ /* Pills are for drawer-level chrome: the header's actions, the ref chips, and
1392
+ anything that opens a menu — dsh spells its own dropdown triggers
1393
+ ("Code mode ⌄", "glm-5.3 ⌄") as pills, so a trigger that is not one reads as
1394
+ foreign. Controls that live INSIDE a panel or pane stay 8px rects. */
1395
+ .btn, .refButton { border-radius: var(--gs-r-pill); }
1396
+
1397
+ /* Pane tools give their vertical space back to the diff. */
1398
+ .treeIcon, .commitCopy {
1399
+ height: var(--gs-h-compact);
1400
+ padding: var(--gs-pad-compact);
1401
+ font-size: var(--gs-t-dense);
1402
+ }
1403
+ /* Expand / collapse all are the tree's own chevron at its two angles: the
1404
+ button shows the state it puts every row into. Icon-only because at the 170px
1405
+ the pane drags down to, two more text buttons cannot share a row with the
1406
+ count — and these two are the pair a tree header always carries. */
1407
+ .treeIcon { width: var(--gs-h-compact); padding: 0; }
1408
+ .treeIconGlyph { display: block; font-size: var(--gs-t-meta); line-height: 1; }
1409
+ .treeIconDown { transform: rotate(90deg); }
1410
+
1411
+ /* The scope toggle splits its row in two equal halves, so it opts out of the
1412
+ vocabulary's `flex: none`. Declared here rather than beside `.scopeRow`
1413
+ because it has to win against the grouped rule above by source order. */
1414
+ .scopeBtn { flex: 1 1 0; }
1415
+
1416
+ /* The single "this one is selected" idiom. Three different ones — accent chip,
1417
+ bordered panel, left accent bar — used to mean the same thing in one drawer. */
1418
+ .scopeBtnActive, .miniBtnPrimary, .segmentActive, .refRowActive, .paletteRowActive {
1419
+ border-color: var(--gs-accent-border);
1420
+ background: var(--gs-accent-bg);
1421
+ color: var(--gs-accent);
1422
+ font-weight: 600;
1423
+ }
1424
+
1425
+ /* Icon-only chrome: square, so four of them read as a control cluster rather
1426
+ than four pills of drifting width. Height, border, radius and hover still
1427
+ come from the vocabulary — only the box changes shape. */
1428
+ .btnIcon { width: var(--gs-h-control); padding: 0; }
1429
+ .btnIcon svg { display: block; }
1430
+ /* Close is the one destructive control in the row, and every window on the
1431
+ machine says so with red on hover. Quiet until then: a permanently red X in
1432
+ a header would read as an error state. */
1433
+ .btnClose:hover:not(:disabled) {
1434
+ border-color: var(--gs-del); background: var(--gs-del-bg); color: var(--gs-del);
1435
+ }
1436
+
1437
+ /* Sync actions that have something to report.
1438
+ *
1439
+ * These three variants set COLOUR ONLY — height, padding, radius and type size
1440
+ * still come from the vocabulary above, so a loud Push is the same object as a
1441
+ * quiet one and not a second button that happens to look similar.
1442
+ *
1443
+ * Fetch deliberately has no variant. It writes nothing, so it has no state to
1444
+ * announce, and leaving one of the three permanently quiet is what makes the
1445
+ * other two register as signal rather than decoration. */
1446
+ .btnBehind {
1447
+ border-color: var(--gs-warn); background: var(--gs-warn-bg); color: var(--gs-warn);
1448
+ font-weight: 600;
1449
+ }
1450
+ .btnBehind:hover:not(:disabled) { background: var(--gs-warn-bg); color: var(--gs-warn); border-color: var(--gs-warn); }
1451
+ .btnAhead {
1452
+ border-color: var(--gs-add); background: var(--gs-add-bg); color: var(--gs-add);
1453
+ font-weight: 600;
1454
+ }
1455
+ .btnAhead:hover:not(:disabled) { background: var(--gs-add-bg); color: var(--gs-add); border-color: var(--gs-add); }
1456
+ /* Publishing a branch that exists nowhere else is the one moment this bar has a
1457
+ primary action, so it gets the solid fill Commit uses — and only then. */
1458
+ .btnPrimary {
1459
+ border-color: var(--gs-accent); background: var(--gs-accent); color: var(--gs-on-accent, #fff);
1460
+ font-weight: 600;
1461
+ }
1462
+ .btnPrimary:hover:not(:disabled) { background: var(--gs-accent); color: var(--gs-on-accent, #fff); border-color: var(--gs-accent); }
1463
+
1464
+ /* The count riding inside Pull / Push. Reads as part of the label rather than a
1465
+ badge stuck on it: same colour, tabular, just set apart by a hairline. */
1466
+ .btnCount {
1467
+ margin-left: 1px; padding-left: 6px;
1468
+ border-left: 1px solid currentColor;
1469
+ font-variant-numeric: tabular-nums;
1470
+ opacity: 0.85;
1471
+ }
1472
+
1473
+ /* Pull and its strategy are one control. The picker is Pull's argument, not a
1474
+ third action, so the two are welded: shared border, one outer radius. */
1475
+ .pullGroup { display: inline-flex; flex: none; align-items: center; }
1476
+ .pullGroup > .refPicker > .refButton {
1477
+ border-top-right-radius: 0; border-bottom-right-radius: 0;
1478
+ border-right-color: transparent;
1479
+ }
1480
+ .pullGroup > .btn {
1481
+ margin-left: -1px;
1482
+ border-top-left-radius: 0; border-bottom-left-radius: 0;
1483
+ }
1484
+
1485
+ /* Scrollbars inside the drawer.
1486
+ Listing the scrolling panes by name is what let the file tree keep the
1487
+ platform's own wide light scrollbar after that pane was renamed, so this
1488
+ matches every descendant instead: nothing to keep in step as panes change.
1489
+ `scrollbar-*` inherit, which covers Firefox from the same declaration. */
1490
+ .drawer { scrollbar-width: thin; scrollbar-color: var(--gs-border) transparent; }
1491
+ .drawer ::-webkit-scrollbar { width: 10px; height: 10px; }
1492
+ .drawer ::-webkit-scrollbar-track, .drawer ::-webkit-scrollbar-corner { background: transparent; }
1493
+ .drawer ::-webkit-scrollbar-thumb {
1494
+ /* Painted inside a transparent border so the thumb reads as a floating pill
1495
+ rather than a bar welded to the pane edge. */
1496
+ background: var(--gs-border);
1497
+ background-clip: padding-box;
1498
+ border: 3px solid transparent;
1499
+ border-radius: 999px;
1500
+ }
1501
+ .drawer ::-webkit-scrollbar-thumb:hover { background: var(--gs-fg-faint); background-clip: padding-box; }
1502
+
1503
+ /* Overflow counter for a commit carrying more refs than the row can show. */
1504
+ .commitRefMore {
1505
+ flex: none;
1506
+ padding: 0 5px;
1507
+ border-radius: var(--gs-r-pill);
1508
+ background: var(--gs-neutral-bg);
1509
+ color: var(--gs-fg-dim);
1510
+ font-size: var(--gs-t-meta); line-height: 15px;
1511
+ cursor: default;
1512
+ }