@shiqyka/dsh-git-graph 0.0.0-stage → 0.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/web/app.css ADDED
@@ -0,0 +1,1194 @@
1
+ :root {
2
+ /* Sentinel read by clearBootNotice() in app.js to tell "the stylesheet
3
+ applied" apart from "it 404'd". A custom property is the only reliable
4
+ signal: the eight gradient themes paint --bg as a gradient on <html>, so
5
+ body.backgroundColor is transparent for them and any colour-based test
6
+ reports a false failure (it did — the boot notice showed up on a healthy
7
+ load in those themes). */
8
+ --gg-page-ready: 1;
9
+ --bg: #0d1117;
10
+ --bg2: #12171f;
11
+ --bg3: #1a212b;
12
+ --bg4: #222b37;
13
+ --border: #2a3542;
14
+ --text: #e8edf2;
15
+ --text2: #9aa7b4;
16
+ --text3: #6d7a88;
17
+ --accent: #58a6ff;
18
+ --green: #3fb950;
19
+ --yellow: #d29922;
20
+ --red: #f85149;
21
+ --orange: #f0883e;
22
+ }
23
+ html.gg-light {
24
+ --bg: #ffffff;
25
+ --bg2: #f7f9fb;
26
+ --bg3: #eef1f5;
27
+ --bg4: #e3e8ee;
28
+ --border: #d5dce3;
29
+ --text: #1c2430;
30
+ --text2: #55606e;
31
+ --text3: #8a94a0;
32
+ --accent: #0969da;
33
+ --green: #1a7f37;
34
+ --yellow: #9a6700;
35
+ --red: #cf222e;
36
+ --orange: #bc4c00;
37
+ }
38
+ /* ---- style presets (8 styles, ported from 拾光记) ---- */
39
+ html.gg-theme-sakura {
40
+ --bg: linear-gradient(135deg,#fff5f7,#fef0f5); --bg2: #fff8fa; --bg3: #fdeef3; --bg4: #f9e2ea;
41
+ --border: #f0cddb; --text: #4a2b36; --text2: #8a6474; --text3: #b395a2;
42
+ --accent: #ff6b9d; --green: #2e9e5b; --yellow: #b07d00; --red: #e5484d; --orange: #d2691e;
43
+ }
44
+ html.gg-theme-ocean {
45
+ --bg: linear-gradient(135deg,#e8f4fd,#f0f8ff); --bg2: #f4fafe; --bg3: #e8f3fb; --bg4: #d9eaf7;
46
+ --border: #c4ddec; --text: #1e3a52; --text2: #4f7189; --text3: #8aa5b8;
47
+ --accent: #1a6fa3; --green: #2e9e5b; --yellow: #b07d00; --red: #e5484d; --orange: #d2691e;
48
+ }
49
+ html.gg-theme-forest {
50
+ --bg: linear-gradient(135deg,#f0f7ee,#e8f5e9); --bg2: #f5faf4; --bg3: #e9f4e8; --bg4: #daecd8;
51
+ --border: #c5dfc3; --text: #2c3a2c; --text2: #5c7a5e; --text3: #8aa58c;
52
+ --accent: #2e7d32; --green: #1f7a34; --yellow: #9a8a1f; --red: #d64550; --orange: #c26a1d;
53
+ }
54
+ html.gg-theme-night {
55
+ --bg: linear-gradient(135deg,#1a1a2e,#16213e); --bg2: #20203a; --bg3: #28284a; --bg4: #30305c;
56
+ --border: #3c3c66; --text: #e9e6f7; --text2: #a9a5cf; --text3: #7d79a8;
57
+ --accent: #b39ddb; --green: #7ac98a; --yellow: #d8b64f; --red: #f07878; --orange: #e08b4d;
58
+ }
59
+ html.gg-theme-candy {
60
+ --bg: linear-gradient(135deg,#fff8e1,#fff3e0); --bg2: #fffaf0; --bg3: #fff3dc; --bg4: #ffebc8;
61
+ --border: #f2dcae; --text: #4a3820; --text2: #8a7448; --text3: #b0a080;
62
+ --accent: #a85b00; --green: #2e9e5b; --yellow: #a07d00; --red: #e5484d; --orange: #c26a1d;
63
+ }
64
+ html.gg-theme-business {
65
+ --bg: linear-gradient(135deg,#eceff1,#f5f5f5); --bg2: #f4f6f7; --bg3: #e9edef; --bg4: #dde2e5;
66
+ --border: #cfd6da; --text: #263238; --text2: #5f6c74; --text3: #92a0a9;
67
+ --accent: #00695c; --green: #1f7a34; --yellow: #9a8a1f; --red: #d64550; --orange: #bf5b1f;
68
+ }
69
+ html.gg-theme-starry {
70
+ --bg: linear-gradient(135deg,#0d0221,#150734); --bg2: #150a2e; --bg3: #1d1040; --bg4: #261a55;
71
+ --border: #392a6e; --text: #ece8fb; --text2: #b0a8dc; --text3: #8378b0;
72
+ --accent: #b39ddb; --green: #7ac98a; --yellow: #d8b64f; --red: #f07878; --orange: #e08b4d;
73
+ }
74
+ html.gg-theme-tech {
75
+ --bg: linear-gradient(135deg,#05070f,#0b1226); --bg2: #0c1428; --bg3: #121c38; --bg4: #18264a;
76
+ --border: #24355e; --text: #dce8f8; --text2: #8fa6cc; --text3: #64789e;
77
+ --accent: #00b0ff; --green: #3fb950; --yellow: #d29922; --red: #f85149; --orange: #f0883e;
78
+ }
79
+ /* ---- decorative background layer (ported from 拾光记 bg-decor) ---- */
80
+ #ggDeco { position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
81
+ #ggDeco i { position: absolute; display: block; }
82
+ /* stars: three sizes + glow breathing */
83
+ #ggDeco .star { width: 3px; height: 3px; border-radius: 50%; opacity: .8; animation: bgTwinkle 2.6s ease-in-out infinite; }
84
+ #ggDeco .star.s { width: 2px; height: 2px; animation-duration: 3.4s; }
85
+ #ggDeco .star.l { width: 5px; height: 5px; box-shadow: 0 0 8px 2px currentColor; animation-duration: 2.2s; }
86
+ #ggDeco .star.glow { width: 3px; height: 3px; box-shadow: 0 0 12px 4px rgba(255,255,255,.45); }
87
+ @keyframes bgTwinkle { 0%,100% { opacity: .15; transform: scale(.85); } 50% { opacity: 1; transform: scale(1.15); } }
88
+ /* moon: crescent + glow pulse */
89
+ #ggDeco .moon { width: 64px; height: 64px; border-radius: 50%; background: radial-gradient(circle at 38% 35%,#fffbe6,#ffe9a8 55%,#ffd76a); box-shadow: 0 0 40px 14px rgba(255,225,130,.35), inset -10px -8px 16px rgba(214,164,60,.5); animation: bgMoonGlow 6s ease-in-out infinite; }
90
+ #ggDeco .moon::after { content: ''; position: absolute; top: -4px; left: -6px; width: 70px; height: 70px; border-radius: 50%; background: transparent; box-shadow: inset 14px 6px 0 rgba(20,20,40,.18); }
91
+ @keyframes bgMoonGlow { 0%,100% { box-shadow: 0 0 30px 10px rgba(255,225,130,.25), inset -10px -8px 16px rgba(214,164,60,.5); } 50% { box-shadow: 0 0 52px 20px rgba(255,225,130,.42), inset -10px -8px 16px rgba(214,164,60,.5); } }
92
+ /* earth: CSS gradient globe + rotating cloud bands */
93
+ #ggDeco .earth { width: 62px; height: 62px; border-radius: 50%; background: radial-gradient(circle at 30% 30%,#4fc3f7 0%,#1e88e5 45%,#0d47a1 100%); box-shadow: 0 4px 16px rgba(30,136,229,.3), inset -12px -10px 20px rgba(6,32,74,.55); opacity: .55; filter: blur(1px); animation: bgEarthRot 40s linear infinite; }
94
+ #ggDeco .earth::before { content: ''; position: absolute; inset: 0; border-radius: 50%; background: radial-gradient(ellipse at 40% 60%,rgba(129,199,132,.75) 0%,rgba(129,199,132,.55) 18%,rgba(67,160,71,.35) 32%,transparent 46%), radial-gradient(ellipse at 65% 30%,rgba(165,214,167,.7) 0%,rgba(165,214,167,.4) 16%,transparent 30%), radial-gradient(ellipse at 20% 80%,rgba(174,213,129,.6) 0%,transparent 22%); animation: bgEarthRot 40s linear infinite reverse; }
95
+ #ggDeco .earth::after { content: ''; position: absolute; inset: -12%; border-radius: 50%; background: conic-gradient(from 0deg,transparent 0deg,rgba(255,255,255,.16) 24deg,transparent 50deg,transparent 130deg,rgba(255,255,255,.12) 160deg,transparent 200deg,rgba(255,255,255,.14) 240deg,transparent 280deg); animation: bgClouds 22s linear infinite; }
96
+ @keyframes bgEarthRot { from { transform: rotate(0); } to { transform: rotate(360deg); } }
97
+ @keyframes bgClouds { from { transform: rotate(0); } to { transform: rotate(360deg); } }
98
+ /* petals */
99
+ #ggDeco .petal { width: 11px; height: 15px; border-radius: 60% 0 60% 0; background: linear-gradient(135deg,var(--pc1,#ffc1d8),var(--pc2,#ff8fb3)); opacity: .8; animation: bgFallSway 9s linear infinite; }
100
+ @keyframes bgFallSway { 0% { transform: translate(0,-6vh) rotate(0); } 25% { transform: translate(14px,20vh) rotate(80deg); } 50% { transform: translate(-10px,50vh) rotate(180deg); } 75% { transform: translate(12px,78vh) rotate(270deg); } 100% { transform: translate(0,106vh) rotate(360deg); } }
101
+ @keyframes bgFall { 0% { transform: translateY(-6vh) rotate(0); } 100% { transform: translateY(106vh) rotate(360deg); } }
102
+ #ggDeco .petal.s { width: 7px; height: 10px; opacity: .5; }
103
+ #ggDeco .petal.l { width: 15px; height: 20px; opacity: .95; }
104
+ #ggDeco .petal.xl { width: 22px; height: 28px; opacity: .72; animation-duration: 11s; }
105
+ /* meteors */
106
+ #ggDeco .meteor { position: absolute; width: 86px; height: 2px; border-radius: 2px; background: linear-gradient(90deg,#fff,rgba(255,255,255,0)); transform: rotate(32deg); animation: bgMeteor 3.8s ease-in forwards; }
107
+ #ggDeco .meteor::after { content: ''; position: absolute; right: 0; top: -1.5px; width: 5px; height: 5px; border-radius: 50%; background: #fff; box-shadow: 0 0 8px 2px rgba(255,255,255,.8); }
108
+ @keyframes bgMeteor { 0% { transform: translate(0,0) rotate(32deg); opacity: 0; } 10% { opacity: 1; } 70% { opacity: .55; } 100% { transform: translate(-190px,118px) rotate(32deg); opacity: 0; } }
109
+ /* fireflies */
110
+ #ggDeco .firefly { width: 4px; height: 4px; border-radius: 50%; background: #ffe97a; box-shadow: 0 0 10px 3px rgba(255,233,122,.75); animation: bgFirefly 7s ease-in-out infinite; }
111
+ @keyframes bgFirefly { 0%,100% { opacity: .08; transform: translate(0,0); } 20% { opacity: .85; transform: translate(16px,-20px); } 45% { opacity: .4; transform: translate(-12px,-34px); } 70% { opacity: .8; transform: translate(10px,-14px); } }
112
+ /* ribbons */
113
+ #ggDeco .ribbon { width: 36px; height: 9px; border-radius: 5px; opacity: .5; animation: bgRibbon 9s linear infinite; }
114
+ @keyframes bgRibbon { 0% { transform: translateY(-9vh) rotate(0); } 100% { transform: translateY(107vh) rotate(480deg); } }
115
+ /* bubbles */
116
+ #ggDeco .bubble { width: 16px; height: 16px; border: 1.5px solid rgba(255,255,255,.6); border-radius: 50%; background: radial-gradient(circle at 30% 30%,rgba(255,255,255,.35),rgba(255,255,255,.05) 60%); animation: bgRise 10s linear infinite; }
117
+ #ggDeco .bubble.s { width: 10px; height: 10px; }
118
+ #ggDeco .bubble.l { width: 22px; height: 22px; border-width: 2px; }
119
+ @keyframes bgRise { 0% { transform: translateY(106vh) scale(.8); } 100% { transform: translateY(-8vh) scale(1.15); } }
120
+ /* waves */
121
+ #ggDeco .wave { position: absolute; left: -30%; bottom: -26px; width: 160%; height: 62px; border-radius: 48% 52% 0 0/62% 62% 0 0; background: linear-gradient(180deg,rgba(190,229,248,.4),rgba(140,205,235,.16)); animation: bgWaveDrift 8s ease-in-out infinite alternate; }
122
+ #ggDeco .wave.w2 { height: 42px; bottom: -16px; background: linear-gradient(180deg,rgba(255,255,255,.5),rgba(190,229,248,.22)); animation-duration: 6s; animation-delay: .7s; }
123
+ @keyframes bgWaveDrift { from { transform: translateX(-3%); } to { transform: translateX(3%); } }
124
+ /* ripples */
125
+ #ggDeco .ripple { width: 46px; height: 13px; border: 2px solid rgba(126,200,227,.35); border-radius: 50%; animation: bgRipple 6s ease-out infinite; }
126
+ @keyframes bgRipple { 0% { transform: translateY(0) scale(.5); opacity: .6; } 100% { transform: translateY(-30vh) scale(2.4); opacity: 0; } }
127
+ /* fish */
128
+ #ggDeco .fish { font-size: 16px; animation: bgFish 14s linear infinite; filter: drop-shadow(0 2px 4px rgba(30,136,229,.3)); }
129
+ #ggDeco .fish.s { font-size: 11px; opacity: .75; }
130
+ @keyframes bgFish { 0% { transform: translateX(-8vw) translateY(0); } 25% { transform: translateX(22vw) translateY(-8px); } 50% { transform: translateX(52vw) translateY(6px); } 75% { transform: translateX(80vw) translateY(-5px); } 100% { transform: translateX(110vw) translateY(0); } }
131
+ /* sunbeams */
132
+ #ggDeco .sunbeam { position: absolute; top: -14vh; width: 72px; height: 150%; background: linear-gradient(180deg,rgba(255,255,255,.24),rgba(255,255,255,0)); transform: rotate(18deg); animation: bgBeamPulse 7s ease-in-out infinite alternate; opacity: .5; }
133
+ @keyframes bgBeamPulse { from { opacity: .3; } to { opacity: .75; } }
134
+ /* leaves */
135
+ #ggDeco .leaf { width: 12px; height: 12px; border-radius: 80% 0 80% 0; background: linear-gradient(135deg,#c8e6c9,#81c784); opacity: .75; animation: bgFall 12s linear infinite; }
136
+ /* dots */
137
+ #ggDeco .dot { width: 11px; height: 11px; border-radius: 50%; opacity: .65; animation: bgFloat 5s ease-in-out infinite; }
138
+ #ggDeco .dot.l { width: 16px; height: 16px; opacity: .45; }
139
+ @keyframes bgFloat { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-22px); } }
140
+ /* signal rings */
141
+ #ggDeco .signal { width: 30px; height: 30px; border: 2.5px solid rgba(0,229,255,.7); border-radius: 50%; animation: bgPulse 2.4s ease-out infinite; }
142
+ #ggDeco .signal.s2 { animation-delay: .8s; border-color: rgba(0,229,255,.42); }
143
+ #ggDeco .signal.s3 { animation-delay: 1.6s; border-color: rgba(0,229,255,.26); }
144
+ @keyframes bgPulse { 0% { transform: scale(.3); opacity: 1; } 100% { transform: scale(2.8); opacity: 0; } }
145
+ /* gears */
146
+ #ggDeco .gear { font-size: 58px; line-height: 1; filter: drop-shadow(0 3px 10px rgba(120,144,255,.45)); animation: bgSpinSlow 14s linear infinite; }
147
+ #ggDeco .gear.g2 { font-size: 38px; animation-direction: reverse; animation-duration: 9s; }
148
+ @keyframes bgSpinSlow { from { transform: rotate(0); } to { transform: rotate(360deg); } }
149
+ /* circuit lines + node pulse */
150
+ #ggDeco .circuit { height: 2.5px; background: linear-gradient(90deg,transparent,rgba(0,229,255,.9),transparent); background-size: 200% 100%; animation: bgShine 3s linear infinite; border-radius: 3px; }
151
+ #ggDeco .circuit.v { width: 2.5px; height: 90px; background: linear-gradient(180deg,transparent,rgba(0,229,255,.8),transparent); background-size: 100% 200%; }
152
+ @keyframes bgShine { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
153
+ #ggDeco .circuit-node { width: 8px; height: 8px; border-radius: 50%; background: rgba(0,229,255,.9); box-shadow: 0 0 14px 4px rgba(0,229,255,.65); animation: bgNodePulse 1.8s ease-in-out infinite; }
154
+ @keyframes bgNodePulse { 0%,100% { transform: scale(1); opacity: .85; } 50% { transform: scale(1.5); opacity: 1; } }
155
+ /* business geometry */
156
+ #ggDeco .geo { width: 38px; height: 38px; border: 2px solid rgba(100,180,170,.32); border-radius: 10px; opacity: .55; animation: bgFloat 7s ease-in-out infinite; }
157
+ #ggDeco .geo.circle { border-radius: 50%; }
158
+ /* forest: tree/grass silhouettes + butterflies + mushrooms */
159
+ #ggDeco .tree { position: absolute; bottom: 0; width: 92px; height: 118px; background: linear-gradient(180deg,rgba(76,175,80,.26),rgba(46,125,50,.14)); border-radius: 60% 60% 8% 8%/85% 85% 10% 10%; opacity: .8; }
160
+ #ggDeco .tree.s { width: 54px; height: 72px; opacity: .6; }
161
+ #ggDeco .tree.xs { width: 34px; height: 46px; opacity: .5; }
162
+ #ggDeco .tree.xl { width: 128px; height: 158px; opacity: .85; }
163
+ #ggDeco .grass { position: absolute; bottom: 0; width: 130px; height: 24px; border-radius: 50% 50% 0 0; background: linear-gradient(180deg,rgba(129,199,132,.32),rgba(129,199,132,.1)); opacity: .8; }
164
+ #ggDeco .butterfly { font-size: 17px; animation: bgButterfly 12s ease-in-out infinite; filter: drop-shadow(0 2px 3px rgba(76,175,80,.3)); }
165
+ @keyframes bgButterfly { 0%,100% { transform: translate(0,0) rotate(-8deg); } 25% { transform: translate(18px,-14px) rotate(8deg); } 50% { transform: translate(-14px,-28px) rotate(-6deg); } 75% { transform: translate(12px,-10px) rotate(7deg); } }
166
+ #ggDeco .mushroom { font-size: 13px; animation: bgFloat 5s ease-in-out infinite; opacity: .85; }
167
+ /* candy: rainbow arc + lollipops */
168
+ #ggDeco .rainbow { position: absolute; top: -26px; left: 50%; transform: translateX(-50%); width: 170%; height: 104px; border-radius: 50% 50% 0 0/100% 100% 0 0; background: conic-gradient(from 180deg at 50% 100%,#ff6b6b,#ffd43b,#69db7c,#74c0fc,#b197fc,#ff8fb3,#ff6b6b); opacity: .16; animation: bgRainbow 10s ease-in-out infinite alternate; }
169
+ @keyframes bgRainbow { from { opacity: .1; } to { opacity: .26; } }
170
+ #ggDeco .lollipop { font-size: 15px; animation: bgLolli 9s linear infinite; opacity: .82; }
171
+ @keyframes bgLolli { 0% { transform: translateY(-8vh) rotate(0); } 100% { transform: translateY(106vh) rotate(360deg); } }
172
+ /* business: glass blocks + particles */
173
+ #ggDeco .glass { width: 52px; height: 52px; border-radius: 14px; background: linear-gradient(135deg,rgba(255,255,255,.5),rgba(255,255,255,.1)); border: 1px solid rgba(255,255,255,.32); backdrop-filter: blur(3px); animation: bgFloat 8s ease-in-out infinite; opacity: .62; }
174
+ #ggDeco .glass.bar { width: 112px; height: 13px; border-radius: 7px; }
175
+ #ggDeco .particle { width: 5px; height: 5px; border-radius: 50%; background: rgba(100,180,170,.42); animation: bgFloat 6s ease-in-out infinite; }
176
+ #ggDeco .particle.line { width: 46px; height: 1px; border-radius: 1px; background: linear-gradient(90deg,rgba(100,180,170,.35),rgba(100,180,170,0)); }
177
+ /* tech chip nodes */
178
+ #ggDeco .chip { width: 10px; height: 10px; border-radius: 2px; background: rgba(0,229,255,.55); box-shadow: 0 0 8px rgba(0,229,255,.5); animation: bgChipBlink 3s ease-in-out infinite; }
179
+ @keyframes bgChipBlink { 0%,100% { opacity: .4; } 50% { opacity: 1; } }
180
+ * { box-sizing: border-box; }
181
+ html, body { margin: 0; height: 100%; }
182
+ body {
183
+ background: var(--bg);
184
+ color: var(--text);
185
+ font: 13px/1.5 -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif;
186
+ display: flex;
187
+ flex-direction: column;
188
+ overflow: hidden;
189
+ }
190
+ /* ===== header ===== */
191
+ /* Three tracks: two equal flexible edges and an auto-sized centre. The equal
192
+ 1fr edges are what centres .hd-center on the header itself — a spacer-based
193
+ flex row centres the centre group on the *leftover* space, so the group
194
+ drifts as soon as the two side groups differ in width. */
195
+ header {
196
+ height: 48px; flex: none;
197
+ display: grid;
198
+ grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
199
+ align-items: center; gap: 12px;
200
+ padding: 0 12px;
201
+ border-bottom: 1px solid var(--border);
202
+ background: var(--bg2);
203
+ /* z-index 10: above #statsBar/#main (z-index 1) so the header's own
204
+ dropdowns (#filterMenu, #dateMenu) are not covered by them */
205
+ position: relative; z-index: 10;
206
+ }
207
+ .hd-left, .hd-center, .hd-right { display: flex; align-items: center; gap: 8px; min-width: 0; }
208
+ .hd-center { justify-content: center; }
209
+ .hd-right { justify-content: flex-end; }
210
+ header .title { font-weight: 700; font-size: 14px; letter-spacing: .2px; white-space: nowrap; flex: none; }
211
+ /* Icon-only control: a 30px box around a 16px stroke glyph. The verb lives in
212
+ the tooltip and the aria-label, so dropping the text loses nothing for a
213
+ hovering mouse or a screen reader — it only quiets the row. */
214
+ .icon-btn {
215
+ position: relative; flex: none;
216
+ width: 30px; height: 30px; padding: 0;
217
+ display: inline-flex; align-items: center; justify-content: center;
218
+ border: 1px solid transparent; border-radius: 9px;
219
+ background: transparent; color: var(--text2);
220
+ cursor: pointer;
221
+ transition: background .12s ease, color .12s ease, border-color .12s ease, transform .12s ease;
222
+ }
223
+ .icon-btn > svg { width: 16px; height: 16px; display: block; }
224
+ .icon-btn:hover { background: var(--bg4); color: var(--text); }
225
+ .icon-btn:active { transform: scale(.94); }
226
+ .icon-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
227
+ .icon-btn[disabled] { opacity: .45; cursor: default; }
228
+ .icon-btn[disabled]:hover { background: transparent; color: var(--text2); }
229
+ .icon-btn.busy > svg { animation: ggSpin .9s linear infinite; }
230
+ @keyframes ggSpin { to { transform: rotate(360deg); } }
231
+ /* "something behind the gear is not at its default" marker */
232
+ .icon-btn.dot::after {
233
+ content: ''; position: absolute; top: 5px; right: 5px;
234
+ width: 6px; height: 6px; border-radius: 50%;
235
+ background: var(--accent); box-shadow: 0 0 0 2px var(--bg2);
236
+ }
237
+ /* Hairline before the gear. Settings is a different kind of control from the
238
+ four repo actions to its left, and at icon size an unseparated 5px gap reads
239
+ as "another action on this repo". */
240
+ .hd-sep {
241
+ flex: none; width: 1px; height: 18px; margin: 0 1px;
242
+ background: var(--border);
243
+ }
244
+ .icon-btn.has-upd {
245
+ color: var(--accent); border-color: rgba(88,166,255,.45); background: rgba(88,166,255,.1);
246
+ }
247
+ /* labelled chips (branch filter / date): icon + text, lighter than a button */
248
+ .pill-btn {
249
+ height: 30px; padding: 0 11px;
250
+ display: inline-flex; align-items: center; gap: 6px;
251
+ border: 1px solid var(--border); border-radius: 9px;
252
+ background: var(--bg3); color: var(--text2);
253
+ cursor: pointer; font-size: 12px; white-space: nowrap;
254
+ transition: background .12s ease, color .12s ease, border-color .12s ease;
255
+ }
256
+ .pill-btn > svg { width: 14px; height: 14px; flex: none; }
257
+ .pill-btn:hover { border-color: var(--text3); background: var(--bg4); color: var(--text); }
258
+ .pill-btn.on { border-color: var(--accent); color: var(--accent); background: rgba(88,166,255,.1); }
259
+ /* stats bar under the header */
260
+ #statsBar {
261
+ position: relative; z-index: 1;
262
+ height: 26px; flex: none; display: flex; align-items: center; gap: 18px;
263
+ padding: 0 14px; border-bottom: 1px solid var(--border);
264
+ background: var(--bg2); color: var(--text3); font-size: 11.5px;
265
+ white-space: nowrap; overflow: hidden;
266
+ }
267
+ #statsBar b { color: var(--text2); font-weight: 600; }
268
+ #statsBar .st-num { color: var(--accent); font-weight: 700; }
269
+ /* The path is redundant with the switcher's own tooltip once a repo is chosen,
270
+ so it yields space first: max-width in ch keeps a long monorepo path from
271
+ pushing the centre group off centre. */
272
+ header .repo { color: var(--text3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 26ch; font-size: 11.5px; }
273
+ header select#repoSwitch {
274
+ max-width: 200px; height: 30px; flex: none;
275
+ border: 1px solid var(--border); border-radius: 9px;
276
+ background: var(--bg3); color: var(--text);
277
+ padding: 0 6px; font-size: 11.5px; outline: none; cursor: pointer;
278
+ }
279
+ header select#repoSwitch:focus { border-color: var(--accent); }
280
+ /* search: the magnifier lives inside the field so the pill reads as one control */
281
+ .searchWrap { position: relative; display: flex; align-items: center; flex: 0 1 232px; min-width: 132px; }
282
+ .searchWrap > svg {
283
+ position: absolute; left: 9px; width: 14px; height: 14px;
284
+ color: var(--text3); pointer-events: none;
285
+ }
286
+ header input#searchBox {
287
+ height: 30px; width: 100%; padding: 0 10px 0 29px;
288
+ border: 1px solid var(--border); border-radius: 9px;
289
+ background: var(--bg3); color: var(--text);
290
+ font-size: 12px; outline: none;
291
+ transition: border-color .12s ease, background .12s ease;
292
+ }
293
+ header input#searchBox:hover { border-color: var(--text3); }
294
+ header input#searchBox:focus { border-color: var(--accent); background: var(--bg); }
295
+ header input#searchBox::placeholder { color: var(--text3); }
296
+ /* date range popover */
297
+ .dateWrap { position: relative; flex: none; }
298
+ /* `fixed` with the coordinates written by positionDateMenu(): the panel is
299
+ 300 × 420px, and an absolutely anchored box runs off a phone viewport (and
300
+ off the bottom of a short window) with no way to scroll it back. */
301
+ #dateMenu {
302
+ display: none; position: fixed; left: 0; top: 0; z-index: 30;
303
+ width: 300px; padding: 12px;
304
+ background: var(--bg2); border: 1px solid var(--border); border-radius: 12px;
305
+ box-shadow: 0 16px 44px rgba(0,0,0,.5), 0 2px 8px rgba(0,0,0,.3);
306
+ }
307
+ #dateMenu.show { display: block; animation: dmIn .13s ease-out; }
308
+ @keyframes dmIn { from { opacity: 0; transform: translateY(-5px); } to { opacity: 1; transform: none; } }
309
+ #dateMenu .dm-title {
310
+ display: flex; align-items: center; gap: 7px;
311
+ font-size: 12px; font-weight: 600; color: var(--text);
312
+ margin: 0 0 11px;
313
+ }
314
+ #dateMenu .dm-title svg { width: 14px; height: 14px; color: var(--text3); flex: none; }
315
+ /* The two ends of the interval, side by side. The one with the ring is the end
316
+ the calendar below writes to — one grid drives both, so there is no need for
317
+ two separate pickers (and no second popover to position). */
318
+ #dateMenu .dm-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; }
319
+ #dateMenu .dm-chip {
320
+ display: flex; align-items: center; gap: 6px; min-width: 0;
321
+ height: 32px; padding: 0 9px;
322
+ border: 1px solid var(--border); border-radius: 8px;
323
+ background: var(--bg3); color: var(--text);
324
+ font: inherit; cursor: pointer;
325
+ transition: border-color .12s ease, background .12s ease, box-shadow .12s ease;
326
+ }
327
+ #dateMenu .dm-chip:hover { border-color: var(--text3); }
328
+ #dateMenu .dm-chip.active {
329
+ border-color: var(--accent);
330
+ box-shadow: 0 0 0 2px rgba(88,166,255,.18);
331
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 20%, transparent);
332
+ }
333
+ #dateMenu .dm-chip-k { flex: none; font-size: 11px; color: var(--text3); }
334
+ #dateMenu .dm-chip-v {
335
+ flex: 1; min-width: 0; text-align: right;
336
+ font-size: 12px; font-variant-numeric: tabular-nums;
337
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
338
+ }
339
+ #dateMenu .dm-chip.empty .dm-chip-v { color: var(--text3); }
340
+ /* A set end reads bold at any time; the accent colour is reserved for the end
341
+ the calendar is currently writing to. Bolding the "不限" placeholder made an
342
+ empty end look chosen. */
343
+ #dateMenu .dm-chip:not(.empty) .dm-chip-v { font-weight: 600; }
344
+ #dateMenu .dm-chip.active:not(.empty) .dm-chip-v { color: var(--accent); }
345
+ /* calendar */
346
+ #dateMenu .dm-cal { margin-top: 10px; }
347
+ #dateMenu .dm-cal-head { display: flex; align-items: center; gap: 3px; height: 26px; }
348
+ #dateMenu .dm-cal-title {
349
+ flex: 1; text-align: center;
350
+ font-size: 12px; font-weight: 600; color: var(--text);
351
+ font-variant-numeric: tabular-nums;
352
+ }
353
+ #dateMenu .dm-nav, #dateMenu .dm-today {
354
+ display: inline-flex; align-items: center; justify-content: center;
355
+ border: 1px solid transparent; background: transparent; color: var(--text2);
356
+ border-radius: 7px; font: inherit; cursor: pointer;
357
+ transition: background .12s ease, color .12s ease, border-color .12s ease;
358
+ }
359
+ #dateMenu .dm-nav { width: 24px; height: 24px; padding: 0; }
360
+ #dateMenu .dm-nav svg { width: 14px; height: 14px; }
361
+ #dateMenu .dm-today { height: 24px; padding: 0 8px; font-size: 11px; border-color: var(--border); }
362
+ #dateMenu .dm-nav:hover, #dateMenu .dm-today:hover { background: var(--bg4); color: var(--text); }
363
+ #dateMenu .dm-today:hover { border-color: var(--text3); }
364
+ #dateMenu .dm-cal-wd, #dateMenu .dm-cal-grid {
365
+ display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px;
366
+ }
367
+ #dateMenu .dm-cal-wd { margin: 6px 0 3px; }
368
+ #dateMenu .dm-cal-wd span { text-align: center; font-size: 10.5px; color: var(--text3); }
369
+ #dateMenu .dm-day {
370
+ position: relative; height: 26px; padding: 0;
371
+ border: 1px solid transparent; border-radius: 7px;
372
+ background: transparent; color: var(--text);
373
+ font: inherit; font-size: 12px; font-variant-numeric: tabular-nums;
374
+ cursor: pointer;
375
+ transition: background .1s ease, color .1s ease, border-color .1s ease;
376
+ }
377
+ #dateMenu .dm-day:hover { background: var(--bg4); }
378
+ #dateMenu .dm-day.other { color: var(--text3); opacity: .5; }
379
+ /* Range band and selected ends are accent *tints*, not solid accent fills:
380
+ --accent ranges from near-white (夜空 #b39ddb) to dark (海洋 #1a6fa3) across
381
+ the 11 themes, so a solid fill would need a per-theme foreground colour. */
382
+ #dateMenu .dm-day.inrange {
383
+ background: rgba(88,166,255,.13);
384
+ background: color-mix(in srgb, var(--accent) 15%, transparent);
385
+ border-radius: 4px;
386
+ }
387
+ #dateMenu .dm-day.sel {
388
+ background: rgba(88,166,255,.16);
389
+ background: color-mix(in srgb, var(--accent) 18%, transparent);
390
+ border-color: var(--accent);
391
+ color: var(--accent); font-weight: 700;
392
+ }
393
+ #dateMenu .dm-day.today::after {
394
+ content: ""; position: absolute; left: 50%; bottom: 3px;
395
+ width: 3px; height: 3px; margin-left: -1.5px;
396
+ border-radius: 50%; background: var(--accent); opacity: .85;
397
+ }
398
+ #dateMenu .dm-day:focus-visible, #dateMenu .dm-nav:focus-visible,
399
+ #dateMenu .dm-today:focus-visible, #dateMenu .dm-chip:focus-visible {
400
+ outline: 2px solid var(--accent); outline-offset: -1px;
401
+ }
402
+ /* Relative presets: the overwhelmingly common request is "the last week", not
403
+ two hand-picked calendar days. */
404
+ #dateMenu .dm-quick {
405
+ display: flex; flex-wrap: wrap; gap: 5px;
406
+ margin: 10px 0 0; padding-top: 10px; border-top: 1px solid var(--border);
407
+ }
408
+ #dateMenu .dm-quick button {
409
+ height: 24px; padding: 0 9px;
410
+ border: 1px solid var(--border); border-radius: 7px;
411
+ background: transparent; color: var(--text2);
412
+ cursor: pointer; font-size: 11px; white-space: nowrap;
413
+ transition: background .12s ease, color .12s ease, border-color .12s ease;
414
+ }
415
+ #dateMenu .dm-quick button:hover { background: var(--bg4); color: var(--text); border-color: var(--text3); }
416
+ #dateMenu .dm-hint { font-size: 10.5px; color: var(--text3); margin: 9px 0 0; line-height: 1.7; }
417
+ #dateMenu .dm-foot { display: flex; gap: 7px; margin-top: 11px; }
418
+ #dateMenu .dm-foot button {
419
+ flex: 1; height: 28px; padding: 0 12px;
420
+ border: 1px solid var(--border); border-radius: 8px;
421
+ background: var(--bg3); color: var(--text); cursor: pointer; font-size: 12px;
422
+ transition: background .12s ease, border-color .12s ease;
423
+ }
424
+ #dateMenu .dm-foot button:hover { border-color: var(--text3); background: var(--bg4); }
425
+ #dateMenu .dm-foot button.primary { background: var(--accent); border-color: var(--accent); color: #04260f; font-weight: 600; }
426
+ #dateMenu .dm-foot button.primary:hover { filter: brightness(1.08); }
427
+ /* filter menu lives in .filterWrap; the old select rule is removed */
428
+ .filterWrap { position: relative; flex: none; }
429
+ #filterMenu {
430
+ display: none; position: absolute; top: 32px; right: 0; z-index: 30;
431
+ min-width: 200px; max-height: 340px; overflow: auto;
432
+ background: var(--bg2); border: 1px solid var(--border); border-radius: 10px;
433
+ padding: 6px; box-shadow: 0 10px 34px rgba(0,0,0,.45);
434
+ }
435
+ #filterMenu.show { display: block; }
436
+ #filterMenu .fm-all {
437
+ display: flex; align-items: center; gap: 7px;
438
+ padding: 5px 8px 7px; margin-bottom: 4px;
439
+ border-bottom: 1px solid var(--border);
440
+ font-size: 12.5px; font-weight: 600; cursor: pointer;
441
+ }
442
+ #filterMenu .fm-group { font-size: 10.5px; color: var(--text3); font-weight: 700; padding: 6px 8px 2px; letter-spacing: .4px; }
443
+ #filterMenu label {
444
+ display: flex; align-items: center; gap: 7px;
445
+ padding: 4px 8px; border-radius: 6px; cursor: pointer;
446
+ font-size: 12.5px; white-space: nowrap;
447
+ }
448
+ #filterMenu label:hover { background: var(--bg4); }
449
+ #filterMenu label input { accent-color: var(--accent); cursor: pointer; }
450
+ #filterMenu label .fm-dot { width: 9px; height: 9px; border-radius: 50%; flex: none; }
451
+ #filterMenu .fm-hint { font-size: 10.5px; color: var(--text3); padding: 4px 8px 2px; }
452
+ /* No catch-all `header button` rule: every toolbar control is either
453
+ .icon-btn or .pill-btn. A generic rule would leak the old 28px/12px-padding
454
+ box onto both and silently win on any property they do not redeclare. */
455
+ header .hint { color: var(--text3); font-size: 11px; white-space: nowrap; }
456
+ #searchCount { color: var(--text3); font-size: 11px; white-space: nowrap; }
457
+ /* --- context menu --- */
458
+ #ctxMenu {
459
+ position: fixed; z-index: 40; display: none; min-width: 190px;
460
+ background: var(--bg3); border: 1px solid var(--border); border-radius: 10px;
461
+ padding: 5px; box-shadow: 0 8px 30px rgba(0,0,0,.5);
462
+ }
463
+ #ctxMenu.show { display: block; }
464
+ #ctxMenu .cm-title {
465
+ padding: 4px 10px 6px; font-size: 10.5px; color: var(--text3);
466
+ border-bottom: 1px solid var(--border); margin-bottom: 4px;
467
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 240px;
468
+ }
469
+ #ctxMenu .cm-item {
470
+ padding: 6px 10px; border-radius: 6px; cursor: pointer;
471
+ font-size: 12.5px; display: flex; align-items: center; gap: 8px;
472
+ }
473
+ #ctxMenu .cm-item:hover { background: var(--bg4); }
474
+ #ctxMenu .cm-item .cm-key { margin-left: auto; color: var(--text3); font-size: 10.5px; }
475
+ /* ===== main ===== */
476
+ #main { flex: 1; display: flex; min-height: 0; position: relative; z-index: 1; }
477
+ /* Draggable splitter between the graph and the detail panel.
478
+ Only occupies space (and hit area) while the detail panel is open. */
479
+ #detailGrip {
480
+ display: none; flex: none; width: 6px; margin-right: -1px;
481
+ cursor: col-resize; position: relative; z-index: 6;
482
+ background: transparent; touch-action: none;
483
+ }
484
+ #main.detail-open #detailGrip { display: block; }
485
+ #detailGrip::before {
486
+ content: ''; position: absolute; left: 50%; top: 0; bottom: 0;
487
+ width: 2px; transform: translateX(-50%); border-radius: 2px;
488
+ background: var(--border); transition: background .12s ease, width .12s ease;
489
+ }
490
+ #detailGrip:hover::before, #detailGrip.dragging::before { background: var(--accent); width: 3px; }
491
+ body.gg-col-resize, body.gg-col-resize * { cursor: col-resize !important; user-select: none !important; }
492
+ /* Narrow container (docked right column): details overlays the graph */
493
+ @media (max-width: 760px) {
494
+ #detail.open {
495
+ position: absolute; right: 0; top: 0; bottom: 0;
496
+ width: var(--gg-detail-w, 430px); max-width: 86%;
497
+ z-index: 5;
498
+ box-shadow: -10px 0 28px rgba(0,0,0,.4);
499
+ }
500
+ /* overlay mode: the splitter would float over the graph, so hide it */
501
+ #main.detail-open #detailGrip { display: none; }
502
+ #legend { display: none; }
503
+ header .hint { display: none; }
504
+ .gg-sub { display: none; }
505
+ /* Drop the redundant path first, then shrink the search field; the icon
506
+ group and the centred filters keep their hit areas. */
507
+ header .repo { display: none; }
508
+ .searchWrap { flex-basis: 150px; min-width: 96px; }
509
+ header { gap: 8px; padding: 0 8px; }
510
+ }
511
+ @media (max-width: 560px) {
512
+ /* Below this width three groups cannot coexist on one line. The centre
513
+ group wraps to a second row so the search field never collapses to the
514
+ width of its own icon. */
515
+ header {
516
+ height: auto; padding: 6px 8px 8px;
517
+ grid-template-columns: minmax(0, 1fr) auto;
518
+ grid-template-areas: "left right" "center center";
519
+ row-gap: 6px;
520
+ }
521
+ .hd-left { grid-area: left; }
522
+ .hd-right { grid-area: right; }
523
+ .hd-center { grid-area: center; justify-content: flex-start; }
524
+ .searchWrap { flex: 1 1 auto; }
525
+ }
526
+ /* --- commit list (GitHub-style) --- */
527
+ /* transparent: the themed background + #ggDeco ambient layer show through */
528
+ #graphWrap { flex: 1; overflow: auto; position: relative; }
529
+ /* The left/right inset must match #wdPanel's 12px margin, otherwise the branch
530
+ cards below sit 12px wider on each side than the uncommitted-changes panel
531
+ above them and the two columns look misaligned. Kept in a variable because
532
+ the lane SVG has to compensate for the same amount (see #laneSvg). */
533
+ #commitList {
534
+ --gg-list-pad-x: 12px;
535
+ padding: 10px var(--gg-list-pad-x) 24px;
536
+ min-width: 520px; position: relative;
537
+ }
538
+ .cl-row {
539
+ display: flex; align-items: stretch;
540
+ padding: 0; cursor: pointer; position: relative;
541
+ }
542
+ .cl-row:hover { background: var(--bg2); }
543
+ /* One selection style for both input methods: the highlight says "this is the
544
+ open commit", the dashed box says "↑/↓ will continue from here". They are
545
+ intentionally the same element and the same state — a row that is open but
546
+ carries no outline would leave the keyboard's "where am I" question
547
+ unanswered, which is exactly what happened when these were two classes.
548
+ `outline-offset: -1px` keeps the box inside the row so it cannot shift the
549
+ layout or be clipped by neighbouring rows at the list edges. */
550
+ .cl-row.sel {
551
+ background: rgba(88,166,255,.1);
552
+ outline: 1px dashed var(--accent);
553
+ outline-offset: -1px;
554
+ border-radius: 3px;
555
+ }
556
+ .cl-row.searchHit .cl-dot { outline: 2px solid #f0b429; outline-offset: 1px; }
557
+ /* searchHit is no longer used to mark matches (search now filters rows
558
+ instead of tinting them); the rule stays for the compare/highlight path. */
559
+ /* Graph gutter: the lane SVG is drawn once for the whole list (not per row),
560
+ so `cl-graph` is just a spacer that reserves the horizontal room. */
561
+ .cl-graph { flex: none; position: relative; }
562
+ #laneSvg {
563
+ /* Absolute positioning resolves against the padding box, i.e. the OUTER edge
564
+ of #commitList's padding. The rows live inside that padding, so without
565
+ this offset the lanes would sit 12px to the left of their own rows.
566
+ Shared variable so the two can never drift apart. */
567
+ position: absolute; left: var(--gg-list-pad-x); top: 0;
568
+ pointer-events: none; z-index: 1; overflow: visible;
569
+ }
570
+ #laneSvg .lane-line { fill: none; stroke-width: 2.4; stroke-linecap: round; }
571
+ #laneSvg .lane-shadow { fill: none; stroke: var(--bg); stroke-width: 5; stroke-linecap: round; opacity: .95; }
572
+ #laneSvg .lane-node { stroke: var(--bg); stroke-width: 2.5; }
573
+ #laneSvg .lane-node.head { stroke: var(--green); stroke-width: 2.5; }
574
+ /* Selected lane node: same dashed idiom as the row outline, so the marker and
575
+ its row read as one selection instead of two unrelated highlights. */
576
+ #laneSvg .lane-node.sel { stroke: #f0b429; stroke-width: 3; stroke-dasharray: 3 2.5; }
577
+ #laneSvg .lane-hit { fill: transparent; cursor: pointer; pointer-events: all; }
578
+ #laneSvg .lane-hit:hover + .lane-node { stroke-width: 3.4; }
579
+ .cl-dot {
580
+ position: absolute; left: 14px; top: 50%; transform: translateY(-50%);
581
+ width: 12px; height: 12px; border-radius: 50%;
582
+ border: 2px solid var(--bg); box-shadow: 0 0 0 1px var(--border);
583
+ }
584
+ .cl-head-dot { width: 12px; height: 12px; border-radius: 50%; flex: none; }
585
+ .cl-body { flex: 1; min-width: 0; padding: 7px 12px 7px 0; }
586
+ .cl-line1 { display: flex; align-items: center; gap: 8px; min-width: 0; }
587
+ .cl-msg {
588
+ font-size: 13px; font-weight: 600; color: var(--text);
589
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
590
+ /* Takes the remaining width so a long subject still ellipsises after the
591
+ ref pills, which are now rendered first and must keep their size. */
592
+ flex: 1 1 auto; min-width: 0;
593
+ }
594
+ .cl-refs { display: inline-flex; gap: 5px; flex: none; }
595
+ .cl-ref {
596
+ font-size: 10px; font-weight: 700; padding: 1px 7px; border-radius: 99px;
597
+ border: 1px solid var(--border); white-space: nowrap;
598
+ }
599
+ .cl-ref.head { color: var(--green); border-color: rgba(63,185,80,.4); background: rgba(63,185,80,.12); }
600
+ .cl-ref.branch { color: var(--accent); border-color: rgba(88,166,255,.35); background: rgba(88,166,255,.1); }
601
+ .cl-ref.tag { color: var(--yellow); border-color: rgba(210,153,34,.4); background: rgba(210,153,34,.1); }
602
+ .cl-line2 {
603
+ margin-top: 2px; font-size: 11px; color: var(--text3);
604
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
605
+ }
606
+ .cl-line2 .cl-author { color: var(--text2); }
607
+ .cl-line2 .cl-hash { font-family: Consolas, monospace; color: var(--text3); }
608
+ /* branch group header */
609
+ .cl-head {
610
+ display: flex; align-items: center; gap: 9px;
611
+ margin: 10px 0 2px; padding: 7px 12px;
612
+ background: var(--bg2); border: 1px solid var(--border); border-radius: 8px;
613
+ cursor: pointer; user-select: none;
614
+ }
615
+ .cl-head:hover { background: var(--bg3); }
616
+ .cl-head .cl-caret { color: var(--text3); font-size: 10px; flex: none; transition: transform .15s ease; }
617
+ .cl-head.open .cl-caret { transform: rotate(90deg); }
618
+ .cl-head .cl-head-name { font-size: 13px; font-weight: 700; color: var(--text); flex: none; }
619
+ .cl-head .cl-head-badge {
620
+ flex: none; font-size: 9.5px; font-weight: 700;
621
+ padding: 1px 6px; border-radius: 5px;
622
+ background: var(--green); color: #04260f;
623
+ }
624
+ .cl-head .cl-head-count { font-size: 11.5px; color: var(--text3); flex: none; }
625
+ .cl-head .cl-head-count.cl-synced { color: var(--green); }
626
+ .cl-head .cl-head-tip {
627
+ flex: 1; min-width: 0; text-align: right;
628
+ font-size: 11.5px; color: var(--text2);
629
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
630
+ }
631
+ .cl-more { text-align: center; padding: 10px 0 18px; }
632
+ .cl-more button {
633
+ height: 30px; padding: 0 20px; border: 1px solid var(--border); border-radius: 8px;
634
+ background: var(--bg3); color: var(--text2); cursor: pointer; font-size: 12px;
635
+ }
636
+ .cl-more button:hover { border-color: var(--accent); color: var(--accent); }
637
+ .cl-more button:disabled { opacity: .6; cursor: wait; }
638
+ #legend {
639
+ position: sticky; top: 8px; left: 8px;
640
+ display: flex; flex-wrap: wrap; gap: 4px 10px;
641
+ padding: 6px 10px; margin: 8px;
642
+ width: fit-content;
643
+ background: rgba(18,23,31,.82); border: 1px solid var(--border); border-radius: 8px;
644
+ font-size: 11px; color: var(--text2);
645
+ z-index: 3; pointer-events: none;
646
+ }
647
+ #legend .lg { display: inline-flex; align-items: center; gap: 5px; }
648
+ #legend .lg.lg-on { outline: 1px solid var(--accent); border-radius: 6px; padding: 1px 5px; margin: -1px -5px; }
649
+ #legend .lg i { width: 9px; height: 9px; border-radius: 50%; display: inline-block; }
650
+ /* --- detail --- */
651
+ #detail {
652
+ width: 0; flex: none;
653
+ border-left: 1px solid var(--border);
654
+ background: var(--bg2);
655
+ overflow: auto;
656
+ transition: width .16s ease;
657
+ }
658
+ #detail.open { width: var(--gg-detail-w, 430px); }
659
+ /* while dragging the splitter the width transition would lag behind the cursor */
660
+ #detail.no-anim { transition: none; }
661
+ #detail .dhead {
662
+ position: sticky; top: 0;
663
+ background: var(--bg2);
664
+ padding: 10px 14px;
665
+ border-bottom: 1px solid var(--border);
666
+ display: flex; align-items: center; gap: 8px;
667
+ z-index: 2;
668
+ }
669
+ #detail .dhead .dtitle { font-weight: 700; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; }
670
+ #detail .dclose { cursor: pointer; color: var(--text2); border: 0; background: none; font-size: 16px; padding: 2px 6px; border-radius: 6px; }
671
+ #detail .dclose:hover { background: var(--bg4); }
672
+ #detail .dmeta {
673
+ padding: 12px 14px; border-bottom: 1px solid var(--border);
674
+ font-size: 12px; color: var(--text2);
675
+ display: grid; grid-template-columns: 52px 1fr; gap: 4px 10px;
676
+ }
677
+ #detail .dmeta b { color: var(--text3); font-weight: 600; }
678
+ #detail .dmeta .hash { color: var(--accent); cursor: pointer; font-family: Consolas, monospace; font-size: 11px; word-break: break-all; }
679
+ #detail .dbody { padding: 12px 14px; font-size: 12.5px; }
680
+ #detail .dbody .msg { margin-bottom: 10px; white-space: pre-wrap; word-break: break-word; }
681
+ #detail .dbody .msg .subj { font-weight: 700; color: var(--text); font-size: 13.5px; }
682
+ #detail pre.stat, #detail pre.diff {
683
+ margin: 0 0 10px; padding: 10px 12px;
684
+ background: var(--bg);
685
+ border: 1px solid var(--border);
686
+ border-radius: 8px;
687
+ overflow: auto;
688
+ font: 11.5px/1.6 Consolas, "Courier New", monospace;
689
+ color: var(--text);
690
+ white-space: pre;
691
+ max-height: 280px;
692
+ }
693
+ #detail pre.diff { max-height: 420px; }
694
+ #detail .section {
695
+ font-weight: 700; margin: 16px 0 8px; color: var(--text2); font-size: 11.5px;
696
+ letter-spacing: .3px; text-transform: uppercase;
697
+ }
698
+ #detail .empty { color: var(--text3); font-size: 12px; }
699
+ .stat-files { margin-bottom: 8px; }
700
+ .stat-files .sf-row { display: flex; gap: 8px; padding: 1px 0; font-size: 12px; white-space: pre; }
701
+ .stat-files .sf-file { color: var(--accent); cursor: pointer; white-space: pre; }
702
+ .stat-files .sf-file:hover { text-decoration: underline; }
703
+ .stat-files .sf-rest { color: var(--text2); white-space: pre; }
704
+ .stat-files .sf-loading { opacity: .5; }
705
+ .sf-diff-block { border: 1px solid var(--border); border-radius: 8px; overflow: hidden; margin-bottom: 10px; }
706
+ .sf-diff-head {
707
+ display: flex; align-items: center; gap: 8px;
708
+ padding: 6px 12px; background: var(--bg3);
709
+ font-size: 12px; font-weight: 600;
710
+ }
711
+ .sf-diff-head span:first-child { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
712
+ .sf-diff-close { cursor: pointer; color: var(--text2); }
713
+ .sf-diff-close:hover { color: var(--text); }
714
+ .sf-diff-block pre.diff { border: 0; border-radius: 0; margin: 0; max-height: 340px; }
715
+ .sf-hist { margin-left: 6px; font-size: 12px; }
716
+ .sf-hist-row {
717
+ display: flex; align-items: baseline; gap: 10px;
718
+ padding: 5px 12px; cursor: pointer; font-size: 12.5px;
719
+ border-bottom: 1px solid var(--border);
720
+ }
721
+ .sf-hist-row:hover { background: var(--bg4); }
722
+ .sf-hist-row .sf-hist-msg { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
723
+ .sf-hist-row .sf-hist-meta { flex: none; color: var(--text3); font-size: 11px; white-space: nowrap; }
724
+ /* --- workdir panel (VSCode-style changes, persistent at the top of the graph) --- */
725
+ #wdPanel {
726
+ margin: 8px 12px 0; border: 1px solid var(--border); border-radius: 10px;
727
+ background: var(--bg2);
728
+ /* no overflow:hidden here: the ☑ 分组 dropdown is absolutely positioned and
729
+ must stay visible even when the panel body is short/collapsed */
730
+ }
731
+ #wdPanel .wd-top {
732
+ display: flex; align-items: center; gap: 10px;
733
+ padding: 7px 12px; border-bottom: 1px solid var(--border);
734
+ background: var(--bg3); cursor: pointer; user-select: none;
735
+ border-radius: 9px 9px 0 0;
736
+ }
737
+ #wdPanel.folded .wd-top { border-radius: 9px; }
738
+ #wdPanel .wd-top .wd-title { font-weight: 700; font-size: 12.5px; display: inline-flex; align-items: center; gap: 7px; }
739
+ #wdPanel .wd-caret { color: var(--text3); font-size: 10px; flex: none; transition: transform .15s ease; }
740
+ #wdPanel .wd-top .wd-caret { transform: rotate(90deg); }
741
+ #wdPanel.folded .wd-top .wd-caret { transform: rotate(0deg); }
742
+ #wdPanel.folded .wd-body { display: none; }
743
+ /* The file list scrolls on its own once it holds more than 10 rows
744
+ (MAX_WD_ROWS in app.js, which sets the inline max-height from real row
745
+ geometry). Scrolling the body instead of the whole panel keeps the
746
+ action bar — 全部暂存 / 提交 / 分组 / 刷新 — and the batch bar pinned. */
747
+ #wdPanel .wd-body.scrolls { overflow-y: auto; overscroll-behavior: contain; }
748
+ #wdPanel .wd-body.scrolls > .wd-group:last-child { padding-bottom: 2px; }
749
+ #wdPanel .wd-top .wd-counts { font-weight: 600; font-size: 11px; color: var(--text2); }
750
+ #wdPanel .wd-top .sp { flex: 1; }
751
+ #wdPanel .wd-top button {
752
+ height: 24px; padding: 0 10px; border: 1px solid var(--border);
753
+ background: var(--bg2); color: var(--text); border-radius: 6px;
754
+ cursor: pointer; font-size: 11.5px;
755
+ }
756
+ #wdPanel .wd-top button:hover { border-color: var(--text3); background: var(--bg4); }
757
+ #wdPanel .wd-top button:disabled { opacity: .45; cursor: not-allowed; }
758
+ #wdPanel .wd-top button:disabled:hover { border-color: var(--border); background: var(--bg2); }
759
+ #wdPanel .wd-filterWrap { position: relative; }
760
+ #wdPanel .wd-menu {
761
+ display: none; position: absolute; top: 27px; right: 0; z-index: 30;
762
+ min-width: 158px; background: var(--bg2); border: 1px solid var(--border);
763
+ border-radius: 8px; padding: 5px; box-shadow: 0 8px 24px rgba(0,0,0,.45);
764
+ }
765
+ #wdPanel .wd-menu.show { display: block; }
766
+ #wdPanel .wd-menu label {
767
+ display: flex; align-items: center; gap: 7px;
768
+ padding: 4px 8px; border-radius: 6px; cursor: pointer;
769
+ font-size: 12.5px; white-space: nowrap;
770
+ }
771
+ #wdPanel .wd-menu label:hover { background: var(--bg4); }
772
+ #wdPanel .wd-menu input { accent-color: var(--accent); cursor: pointer; }
773
+ #wdPanel .wd-clean { padding: 7px 12px; font-size: 12px; color: var(--green); }
774
+ #wdPanel .wd-check { flex: none; accent-color: var(--accent); cursor: pointer; margin: 0; }
775
+ /* batch action bar (multi-select) */
776
+ #wdPanel .wd-batch {
777
+ display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
778
+ padding: 7px 12px; border-bottom: 1px solid var(--border);
779
+ background: rgba(88,166,255,.07);
780
+ }
781
+ #wdPanel .wd-batch .wd-batch-info { font-size: 11.5px; color: var(--accent); font-weight: 700; }
782
+ #wdPanel .wd-batch button {
783
+ height: 24px; padding: 0 10px; border: 1px solid var(--border);
784
+ background: var(--bg3); color: var(--text); border-radius: 6px;
785
+ cursor: pointer; font-size: 11.5px;
786
+ }
787
+ #wdPanel .wd-batch button:hover { border-color: var(--text3); background: var(--bg4); }
788
+ #wdPanel .wd-batch button.danger:hover { color: var(--red); border-color: var(--red); }
789
+ #wdPanel .wd-head {
790
+ display: flex; align-items: center; gap: 8px;
791
+ padding: 6px 12px 2px; font-size: 11px; font-weight: 700;
792
+ color: var(--text2); text-transform: uppercase; letter-spacing: .3px;
793
+ cursor: pointer; user-select: none;
794
+ }
795
+ #wdPanel .wd-head:hover { color: var(--text); }
796
+ #wdPanel .wd-head .wd-caret { transform: rotate(90deg); }
797
+ #wdPanel .wd-group.folded .wd-caret { transform: rotate(0deg); }
798
+ #wdPanel .wd-group.folded .wd-row { display: none; }
799
+ #wdPanel .wd-row {
800
+ display: flex; align-items: center; gap: 8px;
801
+ padding: 3px 12px 3px 16px; border-radius: 6px; cursor: pointer;
802
+ /* The diff block lives inside the row, so the row must wrap: without this the
803
+ open diff is laid out as one more flex item and gets squeezed to the right
804
+ of the file name instead of sitting underneath it. */
805
+ flex-wrap: wrap;
806
+ }
807
+ #wdPanel .wd-row:hover { background: var(--bg4); }
808
+ #wdPanel .wd-row.open { background: var(--bg3); }
809
+ #wdPanel .wd-badge {
810
+ flex: none; width: 20px; height: 20px; border-radius: 5px;
811
+ display: inline-flex; align-items: center; justify-content: center;
812
+ font-size: 10.5px; font-weight: 800; font-family: Consolas, monospace;
813
+ }
814
+ #wdPanel .wd-badge.A { background: rgba(63,185,80,.18); color: var(--green); }
815
+ #wdPanel .wd-badge.M { background: rgba(240,136,62,.16); color: var(--orange); }
816
+ #wdPanel .wd-badge.D { background: rgba(248,81,73,.16); color: var(--red); }
817
+ #wdPanel .wd-badge.R { background: rgba(188,140,255,.16); color: #bc8cff; }
818
+ #wdPanel .wd-badge.U { background: rgba(210,153,34,.16); color: var(--yellow); }
819
+ #wdPanel .wd-badge.q { background: rgba(154,167,180,.16); color: var(--text2); }
820
+ #wdPanel .wd-path {
821
+ flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
822
+ font-family: Consolas, monospace; font-size: 12px;
823
+ }
824
+ #wdPanel .wd-old { color: var(--text3); text-decoration: line-through; }
825
+ #wdPanel .wd-counts { flex: none; font-size: 11px; font-family: Consolas, monospace; }
826
+ #wdPanel .wd-counts .add { color: var(--green); }
827
+ #wdPanel .wd-counts .del { color: var(--red); }
828
+ #wdPanel .wd-binary { flex: none; font-size: 10px; color: var(--text3); }
829
+ #wdPanel .wd-diff { display: none; }
830
+ #wdPanel .wd-row.open .wd-diff { display: block; }
831
+ /* Full width of the row, on its own line: the row wraps, and basis 100% forces
832
+ the diff onto the next flex line instead of competing for space with the
833
+ file name. */
834
+ #wdPanel .wd-row.open .wd-diff { flex: 1 0 100%; min-width: 0; }
835
+ #wdPanel .wd-diff pre {
836
+ margin: 6px 0 4px; padding: 10px 12px;
837
+ background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
838
+ overflow: auto; font: 11.5px/1.6 Consolas, "Courier New", monospace;
839
+ color: var(--text); white-space: pre; max-height: 420px;
840
+ }
841
+ /* row action buttons (stage / unstage / discard) */
842
+ #wdPanel .wd-acts { flex: none; display: inline-flex; gap: 4px; opacity: 0; }
843
+ #wdPanel .wd-row:hover .wd-acts, #wdPanel .wd-row.open .wd-acts { opacity: 1; }
844
+ #wdPanel .wd-acts button {
845
+ height: 20px; padding: 0 7px; border: 1px solid var(--border);
846
+ background: var(--bg3); color: var(--text2); border-radius: 5px;
847
+ cursor: pointer; font-size: 10.5px; white-space: nowrap;
848
+ }
849
+ #wdPanel .wd-acts button:hover { color: var(--text); border-color: var(--text3); }
850
+ #wdPanel .wd-acts button.danger:hover { color: var(--red); border-color: var(--red); }
851
+ /* diff syntax highlighting */
852
+ pre.diff .df-head, #wdPanel pre .df-head { color: var(--accent); font-weight: 700; }
853
+ pre.diff .df-hunk, #wdPanel pre .df-hunk { color: var(--text2); background: rgba(88,166,255,.08); }
854
+ pre.diff .df-add, #wdPanel pre .df-add { background: rgba(63,185,80,.14); color: var(--green); }
855
+ pre.diff .df-del, #wdPanel pre .df-del { background: rgba(248,81,73,.14); color: var(--red); }
856
+ pre.diff .df-pos, #wdPanel pre .df-pos { background: rgba(63,185,80,.08); color: var(--green); }
857
+ pre.diff .df-neg, #wdPanel pre .df-neg { background: rgba(248,81,73,.08); color: var(--red); }
858
+ /* dialogs */
859
+ #ggDlg, #commitDlg, #tagDlg, #branchDlg, #shortcutDlg {
860
+ position: fixed; inset: 0; z-index: 60; display: none;
861
+ background: rgba(0,0,10,.55); align-items: center; justify-content: center;
862
+ }
863
+ #ggDlg.show, #commitDlg.show, #tagDlg.show, #branchDlg.show, #shortcutDlg.show { display: flex; }
864
+ #ggDlg .dlg-box, #commitDlg .dlg-box, #tagDlg .dlg-box, #branchDlg .dlg-box, #shortcutDlg .dlg-box {
865
+ width: min(480px, 90%); background: var(--bg2);
866
+ border: 1px solid var(--border); border-radius: 12px;
867
+ box-shadow: 0 14px 48px rgba(0,0,0,.55); overflow: hidden;
868
+ }
869
+ .dlg-head { padding: 10px 16px; border-bottom: 1px solid var(--border); font-weight: 700; font-size: 13.5px; display: flex; align-items: center; gap: 8px; }
870
+ .dlg-head .dlg-ico { font-size: 15px; }
871
+ .dlg-body { padding: 14px 16px; font-size: 12.5px; color: var(--text2); max-height: 50vh; overflow: auto; }
872
+ .dlg-body b { color: var(--text); }
873
+ .dlg-body .dlg-list { margin-top: 8px; font-family: Consolas, monospace; font-size: 11.5px; color: var(--text2); white-space: pre-wrap; word-break: break-all; }
874
+ .dlg-body textarea {
875
+ width: 100%; min-height: 64px; margin-top: 10px; padding: 8px 10px;
876
+ border: 1px solid var(--border); border-radius: 7px;
877
+ background: var(--bg); color: var(--text); font-size: 12.5px;
878
+ font-family: inherit; outline: none; resize: vertical;
879
+ }
880
+ .dlg-body textarea:focus { border-color: var(--accent); }
881
+ .dlg-body .ai-row { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
882
+ .dlg-body .ai-btn {
883
+ height: 26px; padding: 0 12px; border: 1px solid var(--border);
884
+ background: var(--bg3); color: var(--text); border-radius: 7px;
885
+ cursor: pointer; font-size: 12px; white-space: nowrap;
886
+ }
887
+ .dlg-body .ai-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); background: rgba(88,166,255,.1); }
888
+ .dlg-body .ai-btn:disabled { opacity: .55; cursor: default; }
889
+ .dlg-body .ai-tip { font-size: 11.5px; color: var(--text3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
890
+ .dlg-body label.ck { display: flex; align-items: center; gap: 7px; margin-top: 10px; font-size: 12px; cursor: pointer; }
891
+ .dlg-body label.ck input { accent-color: var(--accent); }
892
+ /* tag management list inside the tag dialog */
893
+ .tag-list { margin-top: 10px; display: flex; flex-direction: column; gap: 5px; }
894
+ .tag-none { font-size: 11.5px; color: var(--text3); }
895
+ .tag-row {
896
+ display: flex; align-items: center; gap: 8px;
897
+ padding: 5px 8px; border: 1px solid var(--border); border-radius: 7px;
898
+ background: var(--bg); font-size: 12px;
899
+ }
900
+ .tag-row .tag-name { flex: 1; font-family: Consolas, monospace; color: var(--accent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
901
+ .tag-row .tag-acts { display: flex; gap: 5px; flex: none; }
902
+ .tag-row .tag-acts button {
903
+ height: 22px; min-width: 24px; padding: 0 7px; border: 1px solid var(--border);
904
+ background: var(--bg3); color: var(--text2); border-radius: 5px;
905
+ cursor: pointer; font-size: 11px;
906
+ }
907
+ .tag-row .tag-acts button:hover { border-color: var(--accent); color: var(--accent); }
908
+ .tag-row .tag-acts button:last-child:hover { border-color: var(--red); color: var(--red); }
909
+ .tag-rename-hint { margin-top: 7px; font-size: 11.5px; color: var(--orange); }
910
+ .dlg-foot { padding: 10px 16px; border-top: 1px solid var(--border); display: flex; justify-content: flex-end; gap: 8px; }
911
+ .dlg-foot button {
912
+ height: 30px; padding: 0 16px; border: 1px solid var(--border);
913
+ background: var(--bg3); color: var(--text); border-radius: 7px;
914
+ cursor: pointer; font-size: 12.5px;
915
+ }
916
+ .dlg-foot button:hover { border-color: var(--text3); background: var(--bg4); }
917
+ .dlg-foot button.primary { border-color: var(--accent); color: var(--accent); background: rgba(88,166,255,.1); }
918
+ .dlg-foot button.danger { border-color: var(--red); color: var(--red); background: rgba(248,81,73,.1); }
919
+ /* shortcut panel */
920
+ #shortcutDlg .sc-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 5px 2px; font-size: 12.5px; }
921
+ #shortcutDlg .sc-row .sc-key {
922
+ flex: none; font-family: Consolas, monospace; font-size: 11px; color: var(--accent);
923
+ border: 1px solid var(--border); border-radius: 5px; padding: 1px 7px; background: var(--bg3);
924
+ }
925
+ /* ---- settings (second-level page behind the gear) ---- */
926
+ #settingsDlg {
927
+ position: fixed; inset: 0; z-index: 60; display: none;
928
+ background: rgba(0,0,10,.55); align-items: center; justify-content: center;
929
+ }
930
+ #settingsDlg.show { display: flex; }
931
+ #settingsDlg .dlg-box {
932
+ width: min(540px, 92%); background: var(--bg2);
933
+ border: 1px solid var(--border); border-radius: 12px;
934
+ box-shadow: 0 14px 48px rgba(0,0,0,.55); overflow: hidden;
935
+ display: flex; flex-direction: column; max-height: 86vh;
936
+ }
937
+ #settingsDlg .dlg-body { max-height: none; flex: 1; overflow: auto; padding: 4px 0 12px; }
938
+ .st-section { padding: 12px 18px 4px; }
939
+ .st-label {
940
+ font-size: 11px; font-weight: 700; letter-spacing: .5px;
941
+ color: var(--text3); margin-bottom: 8px;
942
+ }
943
+ .st-note { font-size: 11.5px; color: var(--text3); margin-top: 7px; line-height: 1.6; }
944
+ /* segmented control: two mutually exclusive views, both visible at once so the
945
+ choice reads as a choice rather than as a toggle whose other side is hidden */
946
+ .st-seg { display: flex; gap: 6px; }
947
+ .st-seg button {
948
+ flex: 1; min-width: 0; height: 52px; padding: 0 10px;
949
+ display: flex; flex-direction: column; align-items: flex-start; justify-content: center; gap: 1px;
950
+ border: 1px solid var(--border); border-radius: 9px;
951
+ background: var(--bg3); color: var(--text2);
952
+ cursor: pointer; text-align: left;
953
+ transition: border-color .12s ease, background .12s ease, color .12s ease;
954
+ }
955
+ .st-seg button:hover { border-color: var(--text3); background: var(--bg4); }
956
+ .st-seg button.on {
957
+ border-color: var(--accent); color: var(--text);
958
+ background: rgba(88,166,255,.1);
959
+ }
960
+ .st-seg .se-t { font-size: 12.5px; font-weight: 600; color: var(--text); }
961
+ .st-seg .se-d { font-size: 10.5px; color: var(--text3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
962
+ /* theme grid: the swatch *is* the preview, so the picker shows what it does */
963
+ .st-themes { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; }
964
+ .st-theme {
965
+ display: flex; align-items: center; gap: 8px;
966
+ height: 38px; padding: 0 10px; min-width: 0;
967
+ border: 1px solid var(--border); border-radius: 9px;
968
+ background: var(--bg3); color: var(--text2);
969
+ cursor: pointer; font-size: 12px; text-align: left;
970
+ transition: border-color .12s ease, background .12s ease, color .12s ease;
971
+ }
972
+ .st-theme:hover { border-color: var(--text3); background: var(--bg4); }
973
+ .st-theme.on { border-color: var(--accent); color: var(--text); background: rgba(88,166,255,.1); }
974
+ .st-theme .sw {
975
+ flex: none; width: 14px; height: 14px; border-radius: 50%;
976
+ border: 1px solid rgba(127,127,127,.45);
977
+ }
978
+ /* Selection ring on the card, not inside the chip: an inset shadow on the
979
+ swatch turned every chip into a donut, which read as an icon rather than a
980
+ colour. */
981
+ .st-theme.on .sw { border-color: var(--accent); }
982
+ .st-theme .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
983
+ @media (max-width: 520px) {
984
+ .st-themes { grid-template-columns: repeat(2, minmax(0, 1fr)); }
985
+ .st-seg { flex-direction: column; }
986
+ .st-seg button { height: auto; padding: 9px 10px; }
987
+ }
988
+ /* folder picker ("打开文件夹": browse the disk and open any folder as a repo) */
989
+ #pickdirDlg {
990
+ position: fixed; inset: 0; z-index: 60; display: none;
991
+ background: rgba(0,0,10,.55); align-items: center; justify-content: center;
992
+ }
993
+ #pickdirDlg.show { display: flex; }
994
+ #pickdirDlg .pd-box { width: min(620px, 92%); display: flex; flex-direction: column; }
995
+ #pickdirDlg .dlg-body { display: flex; flex-direction: column; }
996
+ .pd-path {
997
+ flex: 1; text-align: right; font-weight: 400; font-size: 11px; color: var(--text3);
998
+ font-family: Consolas, monospace; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
999
+ }
1000
+ .pd-bar { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
1001
+ .pd-bar button {
1002
+ height: 28px; padding: 0 12px; border: 1px solid var(--border);
1003
+ background: var(--bg3); color: var(--text); border-radius: 7px;
1004
+ cursor: pointer; font-size: 12px; white-space: nowrap;
1005
+ }
1006
+ .pd-bar button:hover:not(:disabled) { border-color: var(--text3); background: var(--bg4); }
1007
+ .pd-bar button:disabled { opacity: .5; cursor: default; }
1008
+ .pd-bar button.primary { border-color: var(--accent); color: var(--accent); background: rgba(88,166,255,.1); }
1009
+ .pd-bar button.primary:hover:not(:disabled) { background: rgba(88,166,255,.2); }
1010
+ .pd-spacer { flex: 1; }
1011
+ #pdHint { font-size: 11px; color: var(--text3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 40%; }
1012
+ .pd-list {
1013
+ flex: 1; min-height: 220px; max-height: 46vh; overflow: auto;
1014
+ border: 1px solid var(--border); border-radius: 8px; background: var(--bg); padding: 4px;
1015
+ }
1016
+ .pd-item { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 6px; cursor: pointer; font-size: 12.5px; color: var(--text); }
1017
+ .pd-item:hover { background: var(--bg3); }
1018
+ .pd-item .pd-ico { flex: none; font-size: 13px; }
1019
+ .pd-item .pd-name { flex: 1; font-family: Consolas, monospace; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1020
+ .pd-item.pd-hidden .pd-name { color: var(--text3); }
1021
+ .pd-item .pd-git {
1022
+ flex: none; font-size: 10px; font-weight: 600; padding: 1px 6px; border-radius: 4px;
1023
+ background: rgba(63,185,80,.14); color: var(--green); border: 1px solid rgba(63,185,80,.35);
1024
+ }
1025
+ .pd-item .pd-in {
1026
+ flex: none; font-size: 10px; padding: 1px 6px; border-radius: 4px;
1027
+ background: rgba(88,166,255,.12); color: var(--accent);
1028
+ }
1029
+ .pd-empty { padding: 22px; text-align: center; color: var(--text3); font-size: 12.5px; }
1030
+ /* --- status line --- */
1031
+ #statusLine {
1032
+ height: 32px; flex: none;
1033
+ display: flex; align-items: center; gap: 12px;
1034
+ padding: 0 14px;
1035
+ border-top: 1px solid var(--border);
1036
+ background: var(--bg2);
1037
+ color: var(--text2); font-size: 12px;
1038
+ white-space: nowrap; overflow: hidden;
1039
+ position: relative; z-index: 1;
1040
+ }
1041
+ #statusLine .branch { font-weight: 600; color: var(--text); }
1042
+ #statusLine .dirty { color: var(--orange); cursor: pointer; }
1043
+ #statusLine .dirty:hover { text-decoration: underline; }
1044
+ #statusLine .clean { color: var(--green); }
1045
+ /* plugin version badge pinned to the right end of the status bar */
1046
+ #statusLine .gg-version {
1047
+ margin-left: auto; flex: none;
1048
+ font-size: 11px; color: var(--text3);
1049
+ cursor: pointer; user-select: none;
1050
+ border: 1px solid var(--border); border-radius: 5px;
1051
+ padding: 1px 7px; background: var(--bg3);
1052
+ }
1053
+ #statusLine .gg-version:hover { color: var(--accent); border-color: var(--accent); }
1054
+ /* --- graph elements --- */
1055
+ .gg-node { cursor: pointer; }
1056
+ .gg-node circle { stroke: var(--bg); stroke-width: 2; }
1057
+ .gg-node:hover circle { stroke-width: 3; }
1058
+ .gg-node.selected circle { stroke: #f0b429; stroke-width: 2.6; }
1059
+ .gg-node.searchHit circle { stroke: #f0b429; stroke-dasharray: 3 2; stroke-width: 2; }
1060
+ .gg-node.kbd circle { stroke: var(--accent); stroke-width: 2.2; stroke-dasharray: 4 3; }
1061
+ .gg-node.ghost { opacity: .18; pointer-events: none; }
1062
+ .gg-node.ghost text { display: none; }
1063
+ .gg-ref { font-size: 10px; font-weight: 600; fill: var(--text2); }
1064
+ .gg-ref rect { rx: 4; }
1065
+ .gg-ref.branch rect { fill: rgba(88,166,255,.17); }
1066
+ .gg-ref.branch text { fill: var(--accent); }
1067
+ .gg-ref.tag rect { fill: rgba(210,153,34,.16); }
1068
+ .gg-ref.tag text { fill: var(--yellow); }
1069
+ .gg-ref.head rect { fill: rgba(63,185,80,.2); }
1070
+ .gg-ref.head text { fill: var(--green); }
1071
+ .gg-msg { font-size: 12px; font-weight: 600; fill: var(--text); }
1072
+ .gg-sub { font-size: 10.5px; fill: var(--text3); }
1073
+ .gg-edge { fill: none; opacity: .55; }
1074
+ .gg-edge.mainline { opacity: .95; }
1075
+ .gg-wip {
1076
+ cursor: pointer;
1077
+ }
1078
+ .gg-wip rect { fill: rgba(240,136,62,.1); stroke: var(--orange); stroke-width: 1.2; rx: 5; }
1079
+ .gg-wip text { fill: var(--orange); font-size: 11px; font-weight: 600; }
1080
+ .tooltip {
1081
+ position: fixed; z-index: 30; pointer-events: none;
1082
+ background: var(--bg3); border: 1px solid var(--border); border-radius: 8px;
1083
+ padding: 8px 12px; font-size: 12px; color: var(--text);
1084
+ max-width: 460px; box-shadow: 0 6px 24px rgba(0,0,0,.45);
1085
+ }
1086
+ .tooltip .tt-sub { color: var(--text2); font-size: 11px; margin-top: 2px; }
1087
+ #loading {
1088
+ position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
1089
+ color: var(--text2); font-size: 14px; background: var(--bg); z-index: 10;
1090
+ }
1091
+ #loading.hidden { display: none; }
1092
+ #emptyState {
1093
+ position: absolute; inset: 0; z-index: 9; display: flex; flex-direction: column;
1094
+ align-items: center; justify-content: center; gap: 10px; text-align: center;
1095
+ padding: 28px; color: var(--text2); background: var(--bg);
1096
+ }
1097
+ #emptyState.hidden { display: none; }
1098
+ .es-ico { font-size: 46px; opacity: .85; }
1099
+ .es-title { font-size: 15px; font-weight: 600; color: var(--text); }
1100
+ .es-sub { font-size: 12.5px; line-height: 1.8; max-width: 420px; }
1101
+ .es-open {
1102
+ margin-top: 8px; height: 32px; padding: 0 18px;
1103
+ border: 1px solid var(--accent); color: var(--accent);
1104
+ background: rgba(88,166,255,.12); border-radius: 8px;
1105
+ cursor: pointer; font-size: 12.5px;
1106
+ }
1107
+ .es-open:hover { background: rgba(88,166,255,.2); }
1108
+ .es-open.es-scan { border-style: dashed; margin-left: 8px; }
1109
+ .es-scan-list { margin-top: 10px; display: flex; flex-direction: column; gap: 4px; max-width: 480px; max-height: 180px; overflow: auto; }
1110
+ .es-scan-item {
1111
+ text-align: left; padding: 5px 10px; font-size: 12px; color: var(--text);
1112
+ background: rgba(88,166,255,.08); border: 1px solid var(--bg4); border-radius: 6px;
1113
+ cursor: pointer; word-break: break-all;
1114
+ }
1115
+ .es-scan-item:hover { background: rgba(88,166,255,.2); border-color: var(--accent); }
1116
+ .es-scan-none { font-size: 12px; color: var(--text2); opacity: .8; }
1117
+ .es-path { font-size: 11.5px; color: var(--text2); word-break: break-all; max-width: 460px; opacity: .85; }
1118
+ ::-webkit-scrollbar { width: 10px; height: 10px; }
1119
+ /* --bg4 is nearly identical to --bg3 in several themes (dark: #222b37 vs
1120
+ #1a212b), which made the thumb invisible on the panels that sit on --bg3 —
1121
+ exactly the ones that now scroll. --text3 is a real foreground colour in
1122
+ every theme, so the thumb stays visible without a per-theme override. */
1123
+ ::-webkit-scrollbar-thumb {
1124
+ background: color-mix(in srgb, var(--text3) 45%, transparent);
1125
+ border-radius: 6px; border: 2px solid transparent; background-clip: content-box;
1126
+ }
1127
+ ::-webkit-scrollbar-thumb:hover {
1128
+ background: color-mix(in srgb, var(--text2) 70%, transparent);
1129
+ background-clip: content-box;
1130
+ }
1131
+ ::-webkit-scrollbar-track { background: transparent; }
1132
+ /* The workdir list is a scroll region the user meets often; give its thumb a
1133
+ little more presence than the page-level default. */
1134
+ #wdPanel .wd-body.scrolls { scrollbar-width: thin; scrollbar-color: var(--text3) transparent; }
1135
+ /* ---- utility: hidden without an inline style attribute ----------------
1136
+ * A strict `style-src 'self'` blocks style="display:none" (the attribute is
1137
+ * an inline style). Elements start hidden by carrying .gg-none and the few
1138
+ * places that show/hide them toggle that class instead of touching .style.
1139
+ * `!important` keeps this independent of source order: .icon-btn sets
1140
+ * display:inline-flex with the same specificity. */
1141
+ .gg-none { display: none !important; }
1142
+
1143
+ /* footnote under the folder picker (was an inline-styled <div>) */
1144
+ .pd-tip { font-size: 11px; color: var(--text3); margin-top: 8px; }
1145
+
1146
+ /* ---- boot failure notice ------------------------------------------------
1147
+ * Covers "page is newer than the running server": index.html is read from disk
1148
+ * on every request, but the routes for app.css/app.js only exist in the newer
1149
+ * index.js. Before a restart both assets 404, so this rule never applies and
1150
+ * the notice stays visible in the markup's own plain styling — which is the
1151
+ * point.
1152
+ *
1153
+ * The delay is what keeps it out of the normal path. app.js removes the notice
1154
+ * at startup; with `opacity: 0` plus a 1.2s delayed reveal, a healthy load
1155
+ * removes it long before it could ever become visible, so there is no flash
1156
+ * while the 4000-line script parses. If the script never arrives (CSS loaded,
1157
+ * app.js 404) the notice fades in on its own.
1158
+ *
1159
+ * `position: fixed` is not cosmetic. An opacity-0 element still occupies its
1160
+ * box, so an in-flow notice pushed `header` down for the whole script-parse
1161
+ * window and snapped it back on removal — a layout jump on every healthy load.
1162
+ * Out of flow, the header never moves: not while the notice waits to be
1163
+ * removed, and not while it fades in over a real failure. */
1164
+ #bootFail {
1165
+ position: fixed;
1166
+ top: 16px;
1167
+ left: 50%;
1168
+ transform: translateX(-50%);
1169
+ z-index: 9999;
1170
+ width: min(620px, calc(100vw - 32px));
1171
+ box-sizing: border-box;
1172
+ padding: 20px 22px;
1173
+ font: 13px/1.7 system-ui, "Segoe UI", sans-serif;
1174
+ color: #e8edf2;
1175
+ background: #161d26;
1176
+ border: 1px solid #2a3542;
1177
+ border-left: 3px solid #d29922;
1178
+ border-radius: 8px;
1179
+ opacity: 0;
1180
+ animation: bootReveal 0s linear 1.2s forwards;
1181
+ }
1182
+ @keyframes bootReveal { to { opacity: 1; } }
1183
+ #bootFail b { display: block; margin-bottom: 8px; font-size: 14.5px; }
1184
+ #bootFail p { margin: 0 0 8px; color: #9aa7b4; }
1185
+ #bootFail p:last-child { margin-bottom: 0; }
1186
+ #bootFail code {
1187
+ padding: 1px 5px;
1188
+ font: 12px ui-monospace, Consolas, monospace;
1189
+ color: #58a6ff;
1190
+ background: #1a212b;
1191
+ border-radius: 4px;
1192
+ }
1193
+
1194
+ .pd-tip-git { color: var(--green); }