gm-copilot-cli 2.0.538 → 2.0.540
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/copilot-profile.md +1 -1
- package/index.html +997 -529
- package/manifest.yml +1 -1
- package/package.json +1 -1
- package/tools.json +1 -1
package/index.html
CHANGED
|
@@ -1,531 +1,999 @@
|
|
|
1
|
-
<!
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en"><head>
|
|
3
|
+
<meta charset="utf-8">
|
|
4
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
5
|
+
<title>Copilot CLI · gm · 247420</title>
|
|
6
|
+
<meta name="description" content="State machine agent with hooks, skills, and automated git enforcement">
|
|
7
|
+
<meta name="color-scheme" content="light dark">
|
|
8
|
+
<meta name="theme-color" content="#181a1f" media="(prefers-color-scheme: dark)">
|
|
9
|
+
<meta name="theme-color" content="#f4f5f7" media="(prefers-color-scheme: light)">
|
|
10
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
11
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
12
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
13
|
+
<style>
|
|
14
|
+
|
|
15
|
+
/* Rows: no separators, hover surface tint instead */
|
|
16
|
+
.row { background: transparent; transition: background var(--dur-snap) var(--ease); }
|
|
17
|
+
.row:hover { background: var(--surface-2); }
|
|
18
|
+
[data-theme="ink"] .row:hover { background: var(--surface-ink-2); }
|
|
19
|
+
|
|
20
|
+
body {
|
|
21
|
+
background: var(--panel-0);
|
|
22
|
+
color: var(--panel-text);
|
|
23
|
+
font-family: 'Inter', 'Segoe UI', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
|
24
|
+
font-size: 13px;
|
|
25
|
+
line-height: 1.5;
|
|
26
|
+
-webkit-font-smoothing: antialiased;
|
|
27
|
+
-moz-osx-font-smoothing: grayscale;
|
|
28
|
+
text-rendering: optimizeLegibility;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* ============================================================
|
|
32
|
+
Semantic type classes
|
|
33
|
+
============================================================ */
|
|
34
|
+
|
|
35
|
+
/* Hero — project name, once per page. Archivo Black territory. */
|
|
36
|
+
.t-hero, h1.hero {
|
|
37
|
+
font-family: var(--ff-display);
|
|
38
|
+
font-size: clamp(64px, 12vw, var(--fs-hero));
|
|
39
|
+
line-height: var(--lh-tight);
|
|
40
|
+
letter-spacing: var(--tr-tight);
|
|
41
|
+
font-weight: 800;
|
|
42
|
+
margin: 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* h1 — page title */
|
|
46
|
+
.t-h1, h1 {
|
|
47
|
+
font-family: var(--ff-display);
|
|
48
|
+
font-size: clamp(40px, 6vw, var(--fs-h1));
|
|
49
|
+
line-height: var(--lh-tight);
|
|
50
|
+
letter-spacing: var(--tr-tight);
|
|
51
|
+
font-weight: 700;
|
|
52
|
+
margin: 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* h2 — section */
|
|
56
|
+
.t-h2, h2 {
|
|
57
|
+
font-family: var(--ff-display);
|
|
58
|
+
font-size: var(--fs-h2);
|
|
59
|
+
line-height: var(--lh-snug);
|
|
60
|
+
letter-spacing: var(--tr-tight);
|
|
61
|
+
font-weight: 700;
|
|
62
|
+
margin: 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* h3 — subsection (mono, colorful-eligible) */
|
|
66
|
+
.t-h3, h3 {
|
|
67
|
+
font-family: var(--ff-mono);
|
|
68
|
+
font-size: var(--fs-h3);
|
|
69
|
+
line-height: var(--lh-snug);
|
|
70
|
+
letter-spacing: 0;
|
|
71
|
+
font-weight: 600;
|
|
72
|
+
margin: 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* h4 */
|
|
76
|
+
.t-h4, h4 {
|
|
77
|
+
font-family: var(--ff-mono);
|
|
78
|
+
font-size: var(--fs-h4);
|
|
79
|
+
line-height: var(--lh-snug);
|
|
80
|
+
font-weight: 500;
|
|
81
|
+
margin: 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* Body UI — mono, the default UI voice */
|
|
85
|
+
.t-body {
|
|
86
|
+
font-family: var(--ff-mono);
|
|
87
|
+
font-size: var(--fs-body);
|
|
88
|
+
line-height: var(--lh-base);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Long-form prose — SANS, not serif. Space Grotesk. */
|
|
92
|
+
.t-prose, .prose p {
|
|
93
|
+
font-family: var(--ff-body);
|
|
94
|
+
font-size: var(--fs-lg);
|
|
95
|
+
line-height: var(--lh-long);
|
|
96
|
+
max-width: var(--measure);
|
|
97
|
+
text-wrap: pretty;
|
|
98
|
+
font-weight: 400;
|
|
99
|
+
}
|
|
100
|
+
.prose p { margin: 0 0 var(--space-4) 0; }
|
|
101
|
+
|
|
102
|
+
/* Label — mono caps, terminal voice */
|
|
103
|
+
.t-label {
|
|
104
|
+
font-family: var(--ff-mono);
|
|
105
|
+
font-size: var(--fs-xs);
|
|
106
|
+
text-transform: uppercase;
|
|
107
|
+
letter-spacing: var(--tr-label);
|
|
108
|
+
font-weight: 500;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* Meta — mono, small, dim */
|
|
112
|
+
.t-meta {
|
|
113
|
+
font-family: var(--ff-mono);
|
|
114
|
+
font-size: var(--fs-xs);
|
|
115
|
+
color: var(--fg-3);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* Micro — smallest, datelines */
|
|
119
|
+
.t-micro {
|
|
120
|
+
font-family: var(--ff-mono);
|
|
121
|
+
font-size: var(--fs-micro);
|
|
122
|
+
letter-spacing: var(--tr-label);
|
|
123
|
+
text-transform: uppercase;
|
|
124
|
+
color: var(--fg-3);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* Pull — display, for section intros and callouts */
|
|
128
|
+
.t-pull {
|
|
129
|
+
font-family: var(--ff-display);
|
|
130
|
+
font-size: var(--fs-h2);
|
|
131
|
+
line-height: var(--lh-snug);
|
|
132
|
+
font-weight: 600;
|
|
133
|
+
max-width: 20ch;
|
|
134
|
+
letter-spacing: var(--tr-tight);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* Code */
|
|
138
|
+
code, .t-code {
|
|
139
|
+
font-family: var(--ff-mono);
|
|
140
|
+
font-size: 0.9em;
|
|
141
|
+
background: var(--bg-2);
|
|
142
|
+
padding: 0.15em 0.5em;
|
|
8
143
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
144
|
+
border-radius: var(--r-1);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* ============================================================
|
|
148
|
+
COLORFUL MONO — the signature treatment
|
|
149
|
+
Mono is the UI voice. We don't leave it grey. Every mono block
|
|
150
|
+
is allowed — encouraged — to carry a canonical hue per token.
|
|
151
|
+
============================================================ */
|
|
152
|
+
|
|
153
|
+
/* Hue utilities — apply to mono runs to tint them */
|
|
154
|
+
.mono-green { color: var(--mono-1); }
|
|
155
|
+
.mono-purple { color: var(--mono-2); }
|
|
156
|
+
.mono-mascot { color: var(--mono-3); }
|
|
157
|
+
.mono-sun { color: var(--mono-4); }
|
|
158
|
+
.mono-flame { color: var(--mono-5); }
|
|
159
|
+
.mono-sky { color: var(--mono-6); }
|
|
160
|
+
.mono-ink { color: var(--fg); }
|
|
161
|
+
.mono-dim { color: var(--fg-3); }
|
|
162
|
+
|
|
163
|
+
/* Highlight block — a mono run on a tinted bg with dark text */
|
|
164
|
+
.hl-green { background: var(--green); color: var(--green-fg); padding: 0.08em 0.4em; border-radius: var(--r-1); }
|
|
165
|
+
.hl-purple { background: var(--purple); color: var(--purple-fg); padding: 0.08em 0.4em; border-radius: var(--r-1); }
|
|
166
|
+
.hl-mascot { background: var(--mascot); color: var(--mascot-fg); padding: 0.08em 0.4em; border-radius: var(--r-1); }
|
|
167
|
+
.hl-sun { background: var(--sun); color: var(--ink); padding: 0.08em 0.4em; border-radius: var(--r-1); }
|
|
168
|
+
.hl-flame { background: var(--flame); color: var(--paper); padding: 0.08em 0.4em; border-radius: var(--r-1); }
|
|
169
|
+
.hl-sky { background: var(--sky); color: var(--paper); padding: 0.08em 0.4em; border-radius: var(--r-1); }
|
|
170
|
+
|
|
171
|
+
/* Role-colored mono spans — semantic, self-documenting */
|
|
172
|
+
.m-k { color: var(--mono-2); font-weight: 500; } /* keyword */
|
|
173
|
+
.m-s { color: var(--mono-4); } /* string */
|
|
174
|
+
.m-n { color: var(--mono-1); } /* number / live */
|
|
175
|
+
.m-c { color: var(--fg-3); font-style: italic; } /* comment */
|
|
176
|
+
.m-tag { color: var(--mono-3); } /* tag / mention */
|
|
177
|
+
.m-op { color: var(--mono-5); } /* operator / punct */
|
|
178
|
+
.m-ref { color: var(--mono-6); text-decoration: underline; text-underline-offset: 3px; }
|
|
179
|
+
|
|
180
|
+
/* Links in prose */
|
|
181
|
+
.prose a, a.t-link {
|
|
182
|
+
color: var(--link);
|
|
183
|
+
text-decoration: underline;
|
|
184
|
+
text-underline-offset: 3px;
|
|
185
|
+
text-decoration-thickness: 1px;
|
|
186
|
+
}
|
|
187
|
+
.prose a:hover, a.t-link:hover {
|
|
188
|
+
background: var(--link);
|
|
189
|
+
color: var(--paper);
|
|
190
|
+
text-decoration: none;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/* ============================================================
|
|
194
|
+
Primitives — rules, stamps, buttons
|
|
195
|
+
============================================================ */
|
|
196
|
+
|
|
197
|
+
.rule { margin: 0; }
|
|
198
|
+
.rule-double{ height: var(--bw-rule); margin: 0; }
|
|
199
|
+
.rule-dotted{ margin: 0; }
|
|
200
|
+
.rule-green { margin: 0; }
|
|
201
|
+
.rule-purple{ margin: 0; }
|
|
202
|
+
.rule-mascot{ margin: 0; }
|
|
203
|
+
|
|
204
|
+
/* The 247420 stamp */
|
|
205
|
+
.stamp {
|
|
206
|
+
display: inline-block;
|
|
207
|
+
padding: var(--space-1) var(--space-3);
|
|
208
|
+
|
|
209
|
+
border-radius: var(--r-pill);
|
|
210
|
+
color: var(--fg);
|
|
211
|
+
background: transparent;
|
|
212
|
+
font-family: var(--ff-mono);
|
|
213
|
+
font-size: var(--fs-tiny);
|
|
214
|
+
letter-spacing: var(--tr-label);
|
|
215
|
+
text-transform: uppercase;
|
|
216
|
+
font-weight: 600;
|
|
217
|
+
transform: rotate(-2deg);
|
|
218
|
+
}
|
|
219
|
+
.stamp.ink { background: var(--fg); color: var(--bg); }
|
|
220
|
+
.stamp.green { background: var(--green); color: var(--green-fg); }
|
|
221
|
+
.stamp.purple { background: var(--purple); color: var(--purple-fg); }
|
|
222
|
+
.stamp.mascot { background: var(--mascot); color: var(--mascot-fg); }
|
|
223
|
+
.stamp.sun { background: var(--sun); color: var(--ink); }
|
|
224
|
+
.stamp.flame { background: var(--flame); color: var(--paper); }
|
|
225
|
+
|
|
226
|
+
/* Legacy alias — .stamp.acid now means green */
|
|
227
|
+
.stamp.acid { background: var(--green); color: var(--green-fg); }
|
|
228
|
+
|
|
229
|
+
/* The "stamp button" */
|
|
230
|
+
.btn-stamp {
|
|
231
|
+
display: inline-flex;
|
|
232
|
+
align-items: center;
|
|
233
|
+
gap: var(--space-2);
|
|
234
|
+
padding: var(--space-3) var(--space-4);
|
|
235
|
+
background: var(--fg);
|
|
236
|
+
color: var(--bg);
|
|
237
|
+
|
|
238
|
+
border-radius: var(--r-2);
|
|
239
|
+
box-shadow: 4px 4px 0 var(--fg);
|
|
240
|
+
font-family: var(--ff-mono);
|
|
241
|
+
font-weight: 600;
|
|
242
|
+
font-size: var(--fs-sm);
|
|
243
|
+
text-transform: uppercase;
|
|
244
|
+
letter-spacing: var(--tr-caps);
|
|
245
|
+
text-decoration: none;
|
|
246
|
+
cursor: pointer;
|
|
247
|
+
transition: transform var(--dur-snap) var(--ease), box-shadow var(--dur-snap) var(--ease);
|
|
248
|
+
}
|
|
249
|
+
.btn-stamp:hover { transform: translate(1px, 1px); box-shadow: 3px 3px 0 var(--fg); }
|
|
250
|
+
.btn-stamp:active { transform: translate(4px, 4px); box-shadow: 0 0 0 var(--fg); }
|
|
251
|
+
|
|
252
|
+
.btn-stamp.green { background: var(--green); color: var(--green-fg); box-shadow: 4px 4px 0 var(--ink); }
|
|
253
|
+
.btn-stamp.purple { background: var(--purple); color: var(--purple-fg); box-shadow: 4px 4px 0 var(--ink); }
|
|
254
|
+
.btn-stamp.mascot { background: var(--mascot); color: var(--mascot-fg); box-shadow: 4px 4px 0 var(--ink); }
|
|
255
|
+
.btn-stamp.sun { background: var(--sun); color: var(--ink); box-shadow: 4px 4px 0 var(--ink); }
|
|
256
|
+
.btn-stamp.flame { background: var(--flame); color: var(--paper); box-shadow: 4px 4px 0 var(--ink); }
|
|
257
|
+
.btn-stamp.acid { background: var(--green); color: var(--green-fg); box-shadow: 4px 4px 0 var(--ink); } /* legacy */
|
|
258
|
+
|
|
259
|
+
/* Plain button — default */
|
|
260
|
+
.btn {
|
|
261
|
+
display: inline-flex;
|
|
262
|
+
align-items: center;
|
|
263
|
+
gap: var(--space-2);
|
|
264
|
+
padding: var(--space-3) var(--space-4);
|
|
265
|
+
background: transparent;
|
|
266
|
+
color: var(--fg);
|
|
267
|
+
|
|
268
|
+
border-radius: var(--r-2);
|
|
269
|
+
font-family: var(--ff-mono);
|
|
270
|
+
font-weight: 500;
|
|
271
|
+
font-size: var(--fs-sm);
|
|
272
|
+
text-transform: uppercase;
|
|
273
|
+
letter-spacing: var(--tr-caps);
|
|
274
|
+
text-decoration: none;
|
|
275
|
+
cursor: pointer;
|
|
276
|
+
transition: background var(--dur-snap) var(--ease), color var(--dur-snap) var(--ease);
|
|
277
|
+
}
|
|
278
|
+
.btn:hover { background: var(--fg); color: var(--bg); }
|
|
279
|
+
.btn:active { background: var(--green); color: var(--green-fg); }
|
|
280
|
+
|
|
281
|
+
/* Ghost/link button */
|
|
282
|
+
.btn-ghost {
|
|
283
|
+
background: transparent;
|
|
284
|
+
|
|
285
|
+
color: var(--fg);
|
|
286
|
+
font-family: var(--ff-mono);
|
|
287
|
+
font-size: var(--fs-sm);
|
|
288
|
+
text-transform: uppercase;
|
|
289
|
+
letter-spacing: var(--tr-caps);
|
|
290
|
+
padding: var(--space-2) 0;
|
|
291
|
+
cursor: pointer;
|
|
292
|
+
text-decoration: underline;
|
|
293
|
+
text-underline-offset: 4px;
|
|
294
|
+
}
|
|
295
|
+
.btn-ghost:hover { color: var(--green-fg); background: var(--green); }
|
|
296
|
+
|
|
297
|
+
/* Input — softened: full border, rounded, 2px default */
|
|
298
|
+
.input {
|
|
299
|
+
width: 100%;
|
|
300
|
+
background: var(--bg);
|
|
301
|
+
|
|
302
|
+
border-radius: var(--r-2);
|
|
303
|
+
padding: var(--space-3) var(--space-3);
|
|
304
|
+
font-family: var(--ff-mono);
|
|
305
|
+
font-size: var(--fs-body);
|
|
306
|
+
color: var(--fg);
|
|
307
|
+
|
|
308
|
+
transition: border-color var(--dur-snap) var(--ease), box-shadow var(--dur-snap) var(--ease);
|
|
309
|
+
}
|
|
310
|
+
.input:focus {
|
|
311
|
+
|
|
312
|
+
box-shadow: 0 0 0 4px color-mix(in oklab, var(--green) 25%, transparent);
|
|
313
|
+
}
|
|
314
|
+
.input::placeholder { color: var(--fg-3); }
|
|
315
|
+
|
|
316
|
+
/* ============================================================
|
|
317
|
+
Layout scaffolds
|
|
318
|
+
============================================================ */
|
|
319
|
+
|
|
320
|
+
.page { min-height: 100vh; padding: var(--space-5); }
|
|
321
|
+
|
|
322
|
+
.dateline {
|
|
323
|
+
display: flex;
|
|
324
|
+
justify-content: space-between;
|
|
325
|
+
align-items: baseline;
|
|
326
|
+
gap: var(--space-4);
|
|
327
|
+
padding-bottom: var(--space-2);
|
|
328
|
+
|
|
329
|
+
font-family: var(--ff-mono);
|
|
330
|
+
font-size: var(--fs-xs);
|
|
331
|
+
text-transform: uppercase;
|
|
332
|
+
letter-spacing: var(--tr-label);
|
|
333
|
+
}
|
|
334
|
+
.dateline > * { white-space: nowrap; }
|
|
335
|
+
.dateline .spread { flex: 1; align-self: center; }
|
|
336
|
+
|
|
337
|
+
/* Index-card row — soft card with breathing room */
|
|
338
|
+
.row {
|
|
339
|
+
display: grid;
|
|
340
|
+
grid-template-columns: 80px 1fr auto;
|
|
341
|
+
gap: var(--space-4);
|
|
342
|
+
padding: var(--space-4);
|
|
343
|
+
|
|
344
|
+
border-radius: var(--r-3);
|
|
345
|
+
background: var(--bg);
|
|
346
|
+
align-items: baseline;
|
|
347
|
+
margin-bottom: calc(var(--bw-hair) * -1);
|
|
348
|
+
transition: background var(--dur-snap) var(--ease), color var(--dur-snap) var(--ease), transform var(--dur-snap) var(--ease);
|
|
349
|
+
}
|
|
350
|
+
.row + .row { margin-top: var(--space-2); margin-bottom: 0; }
|
|
351
|
+
.row .row-code { font-family: var(--ff-mono); font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: var(--tr-label); color: var(--mono-1); }
|
|
352
|
+
.row .row-title { font-family: var(--ff-display); font-weight: 700; font-size: var(--fs-h3); line-height: var(--lh-snug); letter-spacing: var(--tr-tight); }
|
|
353
|
+
.row .row-meta { font-family: var(--ff-mono); font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: var(--tr-label); color: var(--fg-3); }
|
|
354
|
+
.row:hover { background: var(--green); color: var(--green-fg); transform: translate(-2px, -2px); box-shadow: 4px 4px 0 var(--ink); }
|
|
355
|
+
.row:hover .row-code { color: var(--sun); }
|
|
356
|
+
.row:hover .row-meta { color: var(--green-fg); }
|
|
357
|
+
|
|
358
|
+
/* Soft card — the generic container */
|
|
359
|
+
.card {
|
|
360
|
+
|
|
361
|
+
border-radius: var(--r-3);
|
|
362
|
+
background: var(--bg);
|
|
363
|
+
padding: var(--space-5);
|
|
364
|
+
}
|
|
365
|
+
.card.chunk { }
|
|
366
|
+
|
|
367
|
+
/* ============================================================
|
|
368
|
+
Selection
|
|
369
|
+
============================================================ */
|
|
370
|
+
::selection { background: var(--mascot); color: var(--mascot-fg); }
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
/* ============================================================
|
|
374
|
+
Panel surface tokens — VS-Code-inspired layered chrome
|
|
375
|
+
Light default. Dark via prefers-color-scheme or [data-theme="dark"].
|
|
376
|
+
============================================================ */
|
|
377
|
+
:root {
|
|
378
|
+
--panel-0: #f4f5f7; /* canvas */
|
|
379
|
+
--panel-1: #ffffff; /* elevated card */
|
|
380
|
+
--panel-2: #e7e9ec; /* chrome (topbar, tabbar) */
|
|
381
|
+
--panel-3: #d8dbe0; /* deepest */
|
|
382
|
+
--panel-hover: #ecedf0;
|
|
383
|
+
--panel-select: #dbe3f5;
|
|
384
|
+
--panel-text: #1f2328;
|
|
385
|
+
--panel-text-2: #59636e;
|
|
386
|
+
--panel-text-3: #8b949e;
|
|
387
|
+
--panel-accent: var(--green);
|
|
388
|
+
--panel-accent-2:var(--green-deep);
|
|
389
|
+
--panel-shadow: 0 1px 2px rgba(0,0,0,0.04), 0 1px 4px rgba(0,0,0,0.04);
|
|
390
|
+
--panel-shadow-top: 0 1px 0 rgba(0,0,0,0.05);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
@media (prefers-color-scheme: dark) {
|
|
394
|
+
:root:not([data-theme="light"]) {
|
|
395
|
+
--panel-0: #181a1f;
|
|
396
|
+
--panel-1: #21252b;
|
|
397
|
+
--panel-2: #2c313a;
|
|
398
|
+
--panel-3: #383e48;
|
|
399
|
+
--panel-hover: #2a3038;
|
|
400
|
+
--panel-select: #3a4050;
|
|
401
|
+
--panel-text: #e4e6eb;
|
|
402
|
+
--panel-text-2: #9aa3b2;
|
|
403
|
+
--panel-text-3: #6d7685;
|
|
404
|
+
--panel-accent: var(--green-2);
|
|
405
|
+
--panel-accent-2:var(--green-2);
|
|
406
|
+
--panel-shadow: 0 1px 2px rgba(0,0,0,0.3), 0 1px 6px rgba(0,0,0,0.25);
|
|
407
|
+
--panel-shadow-top: 0 1px 0 rgba(255,255,255,0.04);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
[data-theme="dark"] {
|
|
412
|
+
--panel-0: #181a1f;
|
|
413
|
+
--panel-1: #21252b;
|
|
414
|
+
--panel-2: #2c313a;
|
|
415
|
+
--panel-3: #383e48;
|
|
416
|
+
--panel-hover: #2a3038;
|
|
417
|
+
--panel-select: #3a4050;
|
|
418
|
+
--panel-text: #e4e6eb;
|
|
419
|
+
--panel-text-2: #9aa3b2;
|
|
420
|
+
--panel-text-3: #6d7685;
|
|
421
|
+
--panel-accent: var(--green-2);
|
|
422
|
+
--panel-accent-2:var(--green-2);
|
|
423
|
+
--panel-shadow: 0 1px 2px rgba(0,0,0,0.3), 0 1px 6px rgba(0,0,0,0.25);
|
|
424
|
+
--panel-shadow-top: 0 1px 0 rgba(255,255,255,0.04);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
[data-theme="light"] {
|
|
428
|
+
--panel-0: #f4f5f7;
|
|
429
|
+
--panel-1: #ffffff;
|
|
430
|
+
--panel-2: #e7e9ec;
|
|
431
|
+
--panel-3: #d8dbe0;
|
|
432
|
+
--panel-hover: #ecedf0;
|
|
433
|
+
--panel-select: #dbe3f5;
|
|
434
|
+
--panel-text: #1f2328;
|
|
435
|
+
--panel-text-2: #59636e;
|
|
436
|
+
--panel-text-3: #8b949e;
|
|
437
|
+
--panel-accent: var(--green);
|
|
438
|
+
--panel-accent-2:var(--green-deep);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/* Universal reset — no borders, no outlines except :focus-visible */
|
|
442
|
+
*, *::before, *::after { box-sizing: border-box; border: 0 !important; outline: 0 !important; }
|
|
443
|
+
:focus-visible { outline: 2px solid var(--panel-accent) !important; outline-offset: 2px; }
|
|
444
|
+
html, body { margin: 0; padding: 0; }
|
|
445
|
+
|
|
446
|
+
/* ============================================================
|
|
447
|
+
App shell — IDE-modern density patterns
|
|
448
|
+
Consumed by landing, ui_kits/*, docs, blog, slides
|
|
449
|
+
Light default · dark via prefers-color-scheme or [data-theme=dark]
|
|
450
|
+
============================================================ */
|
|
451
|
+
|
|
452
|
+
html, body {
|
|
453
|
+
background: var(--panel-0);
|
|
454
|
+
color: var(--panel-text);
|
|
455
|
+
font-family: 'Inter', 'Segoe UI', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
|
456
|
+
font-size: 13px;
|
|
457
|
+
line-height: 1.5;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
.app {
|
|
461
|
+
min-height: 100vh;
|
|
462
|
+
display: flex;
|
|
463
|
+
flex-direction: column;
|
|
464
|
+
background: var(--panel-0);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/* Top bar — sticky chrome, elevated via shadow so content doesn't bleed under */
|
|
468
|
+
.app-topbar {
|
|
469
|
+
position: sticky;
|
|
470
|
+
top: 0;
|
|
471
|
+
z-index: 100;
|
|
472
|
+
height: 40px;
|
|
473
|
+
background: var(--panel-2);
|
|
474
|
+
color: var(--panel-text);
|
|
475
|
+
display: flex;
|
|
476
|
+
align-items: center;
|
|
477
|
+
padding: 0 16px;
|
|
478
|
+
gap: 16px;
|
|
479
|
+
font-size: 13px;
|
|
480
|
+
flex-shrink: 0;
|
|
481
|
+
box-shadow: 0 1px 0 rgba(0,0,0,0.06), var(--panel-shadow);
|
|
482
|
+
}
|
|
483
|
+
.app-topbar .brand {
|
|
484
|
+
font-weight: 600;
|
|
485
|
+
color: var(--panel-text);
|
|
486
|
+
display: inline-flex;
|
|
487
|
+
align-items: center;
|
|
488
|
+
}
|
|
489
|
+
.app-topbar .brand .slash { color: var(--panel-text-3); margin: 0 6px; font-weight: 400; }
|
|
490
|
+
.app-topbar nav {
|
|
491
|
+
display: flex;
|
|
492
|
+
gap: 2px;
|
|
493
|
+
margin-left: auto;
|
|
494
|
+
align-items: center;
|
|
495
|
+
}
|
|
496
|
+
.app-topbar nav a {
|
|
497
|
+
color: var(--panel-text-2);
|
|
498
|
+
text-decoration: none;
|
|
499
|
+
padding: 7px 12px;
|
|
500
|
+
font-size: 13px;
|
|
501
|
+
border-radius: 6px;
|
|
502
|
+
line-height: 1;
|
|
503
|
+
}
|
|
504
|
+
.app-topbar nav a:hover { background: var(--panel-hover); color: var(--panel-text); }
|
|
505
|
+
.app-topbar nav a.active { color: #ffffff; background: var(--panel-accent); }
|
|
506
|
+
|
|
507
|
+
/* Breadcrumb — under topbar */
|
|
508
|
+
.app-crumb {
|
|
509
|
+
position: sticky;
|
|
510
|
+
top: 40px;
|
|
511
|
+
z-index: 99;
|
|
512
|
+
background: var(--panel-1);
|
|
513
|
+
padding: 8px 20px;
|
|
514
|
+
font-family: var(--ff-mono);
|
|
515
|
+
font-size: 12px;
|
|
516
|
+
color: var(--panel-text-2);
|
|
517
|
+
display: flex;
|
|
518
|
+
gap: 6px;
|
|
519
|
+
align-items: center;
|
|
520
|
+
flex-shrink: 0;
|
|
521
|
+
box-shadow: 0 1px 0 rgba(0,0,0,0.04);
|
|
522
|
+
}
|
|
523
|
+
.app-crumb .sep { color: var(--panel-text-3); }
|
|
524
|
+
.app-crumb .leaf { color: var(--panel-text); }
|
|
525
|
+
|
|
526
|
+
/* Shell body — optional sidebar + main */
|
|
527
|
+
.app-body {
|
|
528
|
+
display: grid;
|
|
529
|
+
grid-template-columns: 240px 1fr;
|
|
530
|
+
flex: 1;
|
|
531
|
+
min-height: 0;
|
|
532
|
+
background: var(--panel-0);
|
|
533
|
+
}
|
|
534
|
+
.app-body.no-side { grid-template-columns: 1fr; }
|
|
535
|
+
|
|
536
|
+
/* Sidebar */
|
|
537
|
+
.app-side {
|
|
538
|
+
background: var(--panel-1);
|
|
539
|
+
padding: 12px 0;
|
|
540
|
+
overflow-y: auto;
|
|
541
|
+
box-shadow: inset -1px 0 0 rgba(0,0,0,0.05);
|
|
542
|
+
}
|
|
543
|
+
.app-side .group {
|
|
544
|
+
padding: 12px 16px 4px 16px;
|
|
545
|
+
font-family: var(--ff-mono);
|
|
546
|
+
font-size: 11px;
|
|
547
|
+
text-transform: uppercase;
|
|
548
|
+
letter-spacing: 0.08em;
|
|
549
|
+
color: var(--panel-text-2);
|
|
550
|
+
font-weight: 600;
|
|
551
|
+
}
|
|
552
|
+
.app-side a {
|
|
553
|
+
display: flex;
|
|
554
|
+
align-items: center;
|
|
555
|
+
gap: 8px;
|
|
556
|
+
padding: 5px 16px 5px 24px;
|
|
557
|
+
color: var(--panel-text);
|
|
558
|
+
text-decoration: none;
|
|
559
|
+
font-size: 13px;
|
|
560
|
+
}
|
|
561
|
+
.app-side a:hover { background: var(--panel-hover); }
|
|
562
|
+
.app-side a.active { background: var(--panel-select); color: var(--panel-text); font-weight: 500; }
|
|
563
|
+
.app-side a .glyph { color: var(--panel-text-2); font-family: var(--ff-mono); width: 14px; flex-shrink: 0; }
|
|
564
|
+
.app-side a.active .glyph { color: var(--panel-accent); }
|
|
565
|
+
|
|
566
|
+
/* Main content area */
|
|
567
|
+
.app-main {
|
|
568
|
+
background: var(--panel-0);
|
|
569
|
+
padding: 24px 32px 64px 32px;
|
|
570
|
+
overflow-y: auto;
|
|
571
|
+
}
|
|
572
|
+
.app-main.narrow { max-width: 820px; }
|
|
573
|
+
.app-main.centered { margin: 0 auto; }
|
|
574
|
+
|
|
575
|
+
.app-main h1 {
|
|
576
|
+
font-size: 28px;
|
|
577
|
+
font-weight: 600;
|
|
578
|
+
margin: 4px 0 4px 0;
|
|
579
|
+
color: var(--panel-text);
|
|
580
|
+
letter-spacing: -0.01em;
|
|
581
|
+
line-height: 1.2;
|
|
582
|
+
}
|
|
583
|
+
.app-main h2 {
|
|
584
|
+
font-size: 18px;
|
|
585
|
+
font-weight: 600;
|
|
586
|
+
margin: 32px 0 8px 0;
|
|
587
|
+
color: var(--panel-text);
|
|
588
|
+
letter-spacing: -0.005em;
|
|
589
|
+
}
|
|
590
|
+
.app-main h3 {
|
|
591
|
+
font-family: var(--ff-mono);
|
|
592
|
+
font-size: 12px;
|
|
593
|
+
font-weight: 600;
|
|
594
|
+
margin: 24px 0 8px 0;
|
|
595
|
+
color: var(--panel-accent);
|
|
596
|
+
text-transform: uppercase;
|
|
597
|
+
letter-spacing: 0.08em;
|
|
598
|
+
}
|
|
599
|
+
.app-main p {
|
|
600
|
+
margin: 0 0 12px 0;
|
|
601
|
+
max-width: 72ch;
|
|
602
|
+
font-size: 14px;
|
|
603
|
+
line-height: 1.6;
|
|
604
|
+
color: var(--panel-text);
|
|
605
|
+
}
|
|
606
|
+
.app-main .lede {
|
|
607
|
+
font-size: 15px;
|
|
608
|
+
color: var(--panel-text-2);
|
|
609
|
+
margin: 0 0 24px 0;
|
|
610
|
+
max-width: 60ch;
|
|
611
|
+
}
|
|
612
|
+
.app-main a {
|
|
613
|
+
color: var(--panel-accent);
|
|
614
|
+
text-decoration: none;
|
|
615
|
+
}
|
|
616
|
+
.app-main a:hover { text-decoration: underline; text-underline-offset: 2px; }
|
|
617
|
+
.app-main code {
|
|
618
|
+
font-family: var(--ff-mono);
|
|
619
|
+
font-size: 12px;
|
|
620
|
+
background: var(--panel-1);
|
|
621
|
+
padding: 1px 6px;
|
|
622
|
+
border-radius: 6px;
|
|
623
|
+
color: var(--panel-text);
|
|
624
|
+
box-shadow: var(--panel-shadow);
|
|
625
|
+
}
|
|
626
|
+
.app-main pre {
|
|
627
|
+
background: var(--panel-1);
|
|
628
|
+
padding: 14px 16px;
|
|
629
|
+
font-family: var(--ff-mono);
|
|
630
|
+
font-size: 12px;
|
|
631
|
+
line-height: 1.6;
|
|
632
|
+
color: var(--panel-text);
|
|
633
|
+
overflow-x: auto;
|
|
634
|
+
margin: 12px 0;
|
|
635
|
+
border-radius: 8px;
|
|
636
|
+
box-shadow: var(--panel-shadow);
|
|
637
|
+
}
|
|
638
|
+
.app-main pre code { background: transparent; padding: 0; box-shadow: none; }
|
|
639
|
+
.app-main pre .c { color: var(--panel-text-2); }
|
|
640
|
+
.app-main pre .k { color: var(--panel-accent); }
|
|
641
|
+
.app-main pre .s { color: #ce9178; }
|
|
642
|
+
|
|
643
|
+
/* Panel — grouped content block, elevated */
|
|
644
|
+
.panel {
|
|
645
|
+
background: var(--panel-1);
|
|
646
|
+
border-radius: 8px;
|
|
647
|
+
overflow: hidden;
|
|
648
|
+
margin: 12px 0;
|
|
649
|
+
box-shadow: var(--panel-shadow);
|
|
650
|
+
}
|
|
651
|
+
.panel-head {
|
|
652
|
+
padding: 10px 16px;
|
|
653
|
+
background: var(--panel-2);
|
|
654
|
+
font-family: var(--ff-mono);
|
|
655
|
+
font-size: 11px;
|
|
656
|
+
text-transform: uppercase;
|
|
657
|
+
letter-spacing: 0.08em;
|
|
658
|
+
color: var(--panel-text-2);
|
|
659
|
+
display: flex;
|
|
660
|
+
justify-content: space-between;
|
|
661
|
+
align-items: center;
|
|
662
|
+
gap: 12px;
|
|
663
|
+
box-shadow: inset 0 -1px 0 rgba(0,0,0,0.05);
|
|
664
|
+
}
|
|
665
|
+
.panel-body { padding: 0; background: var(--panel-1); }
|
|
666
|
+
|
|
667
|
+
/* Row — dense list item */
|
|
668
|
+
.row {
|
|
669
|
+
display: grid;
|
|
670
|
+
grid-template-columns: 80px 1fr auto;
|
|
671
|
+
gap: 12px;
|
|
672
|
+
padding: 8px 16px;
|
|
673
|
+
align-items: baseline;
|
|
674
|
+
color: var(--panel-text);
|
|
675
|
+
text-decoration: none;
|
|
676
|
+
font-size: 13px;
|
|
677
|
+
cursor: pointer;
|
|
678
|
+
background: transparent;
|
|
679
|
+
}
|
|
680
|
+
.row:hover { background: var(--panel-hover); }
|
|
681
|
+
.row.active { background: var(--panel-select); }
|
|
682
|
+
.row .code {
|
|
683
|
+
font-family: var(--ff-mono);
|
|
684
|
+
font-size: 11px;
|
|
685
|
+
color: var(--panel-text-2);
|
|
686
|
+
letter-spacing: 0.04em;
|
|
687
|
+
}
|
|
688
|
+
.row .title { color: var(--panel-text); font-weight: 500; }
|
|
689
|
+
.row .sub { color: var(--panel-text-2); font-size: 12px; margin-left: 8px; font-weight: 400; }
|
|
690
|
+
.row .meta { color: var(--panel-text-2); font-size: 11px; text-align: right; font-family: var(--ff-mono); }
|
|
691
|
+
|
|
692
|
+
/* CLI install block */
|
|
693
|
+
.cli {
|
|
694
|
+
display: flex;
|
|
695
|
+
align-items: stretch;
|
|
696
|
+
background: var(--panel-1);
|
|
697
|
+
border-radius: 8px;
|
|
698
|
+
overflow: hidden;
|
|
699
|
+
font-family: var(--ff-mono);
|
|
700
|
+
font-size: 13px;
|
|
701
|
+
max-width: 640px;
|
|
702
|
+
margin: 12px 0;
|
|
703
|
+
box-shadow: var(--panel-shadow);
|
|
704
|
+
}
|
|
705
|
+
.cli .prompt { padding: 10px 14px; color: var(--panel-accent); font-weight: 600; }
|
|
706
|
+
.cli .cmd { flex: 1; padding: 10px 0; color: var(--panel-text); }
|
|
707
|
+
.cli .copy {
|
|
708
|
+
padding: 0 16px;
|
|
709
|
+
background: var(--panel-2);
|
|
710
|
+
color: var(--panel-text-2);
|
|
711
|
+
cursor: pointer;
|
|
712
|
+
font-size: 11px;
|
|
713
|
+
text-transform: uppercase;
|
|
714
|
+
letter-spacing: 0.08em;
|
|
715
|
+
display: flex;
|
|
716
|
+
align-items: center;
|
|
717
|
+
}
|
|
718
|
+
.cli .copy:hover { background: var(--panel-3); color: var(--panel-text); }
|
|
719
|
+
|
|
720
|
+
/* Buttons */
|
|
721
|
+
.btn {
|
|
722
|
+
display: inline-flex;
|
|
723
|
+
align-items: center;
|
|
724
|
+
gap: 6px;
|
|
725
|
+
padding: 7px 14px;
|
|
726
|
+
background: var(--panel-2);
|
|
727
|
+
color: var(--panel-text);
|
|
728
|
+
font-family: inherit;
|
|
729
|
+
font-size: 13px;
|
|
730
|
+
font-weight: 500;
|
|
731
|
+
border-radius: 6px;
|
|
732
|
+
cursor: pointer;
|
|
733
|
+
text-decoration: none;
|
|
734
|
+
transition: background 80ms ease;
|
|
735
|
+
box-shadow: var(--panel-shadow);
|
|
736
|
+
}
|
|
737
|
+
.btn:hover { background: var(--panel-3); }
|
|
738
|
+
.btn:active { background: var(--panel-hover); }
|
|
739
|
+
|
|
740
|
+
.btn-primary {
|
|
741
|
+
display: inline-flex;
|
|
742
|
+
align-items: center;
|
|
743
|
+
gap: 6px;
|
|
744
|
+
padding: 7px 14px;
|
|
745
|
+
background: var(--panel-accent);
|
|
746
|
+
color: #ffffff;
|
|
747
|
+
font-family: inherit;
|
|
748
|
+
font-size: 13px;
|
|
749
|
+
font-weight: 500;
|
|
750
|
+
border-radius: 6px;
|
|
751
|
+
cursor: pointer;
|
|
752
|
+
text-decoration: none;
|
|
753
|
+
transition: filter 80ms ease;
|
|
754
|
+
box-shadow: var(--panel-shadow);
|
|
755
|
+
}
|
|
756
|
+
.btn-primary:hover { filter: brightness(1.08); }
|
|
757
|
+
.btn-primary:active { filter: brightness(0.94); }
|
|
758
|
+
|
|
759
|
+
.btn-ghost {
|
|
760
|
+
display: inline-flex;
|
|
761
|
+
align-items: center;
|
|
762
|
+
gap: 6px;
|
|
763
|
+
padding: 7px 14px;
|
|
764
|
+
background: transparent;
|
|
765
|
+
color: var(--panel-text-2);
|
|
766
|
+
font-family: inherit;
|
|
767
|
+
font-size: 13px;
|
|
768
|
+
cursor: pointer;
|
|
769
|
+
text-decoration: none;
|
|
770
|
+
border-radius: 6px;
|
|
771
|
+
transition: background 80ms ease;
|
|
772
|
+
}
|
|
773
|
+
.btn-ghost:hover { background: var(--panel-hover); color: var(--panel-text); }
|
|
774
|
+
|
|
775
|
+
/* Tag / chip */
|
|
776
|
+
.chip {
|
|
777
|
+
display: inline-flex;
|
|
778
|
+
align-items: center;
|
|
779
|
+
padding: 2px 8px;
|
|
780
|
+
border-radius: 6px;
|
|
781
|
+
font-family: var(--ff-mono);
|
|
782
|
+
font-size: 11px;
|
|
783
|
+
font-weight: 500;
|
|
784
|
+
background: var(--panel-2);
|
|
785
|
+
color: var(--panel-text);
|
|
786
|
+
}
|
|
787
|
+
.chip.accent { background: var(--panel-accent); color: #ffffff; }
|
|
788
|
+
.chip.dim { color: var(--panel-text-2); background: var(--panel-1); }
|
|
789
|
+
|
|
790
|
+
/* Status bar — accent-colored footer */
|
|
791
|
+
.app-status {
|
|
792
|
+
height: 24px;
|
|
793
|
+
background: var(--panel-accent);
|
|
794
|
+
color: #ffffff;
|
|
795
|
+
display: flex;
|
|
796
|
+
align-items: center;
|
|
797
|
+
padding: 0 14px;
|
|
798
|
+
gap: 14px;
|
|
799
|
+
font-family: var(--ff-mono);
|
|
800
|
+
font-size: 11px;
|
|
801
|
+
flex-shrink: 0;
|
|
802
|
+
}
|
|
803
|
+
.app-status .spread { flex: 1; }
|
|
804
|
+
.app-status .item { display: flex; align-items: center; gap: 4px; }
|
|
805
|
+
.app-status a { color: #ffffff; text-decoration: none; }
|
|
806
|
+
|
|
807
|
+
/* Key-value table */
|
|
808
|
+
.kv {
|
|
809
|
+
width: 100%;
|
|
810
|
+
max-width: 640px;
|
|
811
|
+
background: var(--panel-1);
|
|
812
|
+
border-radius: 8px;
|
|
813
|
+
overflow: hidden;
|
|
814
|
+
border-collapse: collapse;
|
|
815
|
+
box-shadow: var(--panel-shadow);
|
|
816
|
+
margin: 12px 0;
|
|
817
|
+
}
|
|
818
|
+
.kv td { padding: 8px 16px; font-size: 13px; }
|
|
819
|
+
.kv tr:nth-child(even) { background: var(--panel-2); }
|
|
820
|
+
.kv td:first-child {
|
|
821
|
+
color: var(--panel-text-2);
|
|
822
|
+
font-family: var(--ff-mono);
|
|
823
|
+
font-size: 11px;
|
|
824
|
+
text-transform: uppercase;
|
|
825
|
+
letter-spacing: 0.08em;
|
|
826
|
+
width: 160px;
|
|
827
|
+
}
|
|
828
|
+
.kv td:last-child { color: var(--panel-text); }
|
|
829
|
+
|
|
830
|
+
/* Card grid — landing index */
|
|
831
|
+
.cards {
|
|
832
|
+
display: grid;
|
|
833
|
+
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
|
834
|
+
gap: 8px;
|
|
835
|
+
margin: 12px 0 24px 0;
|
|
836
|
+
}
|
|
837
|
+
.card-item {
|
|
838
|
+
display: grid;
|
|
839
|
+
grid-template-columns: 44px 1fr;
|
|
840
|
+
gap: 10px;
|
|
841
|
+
padding: 14px 16px;
|
|
842
|
+
background: var(--panel-1);
|
|
843
|
+
color: var(--panel-text);
|
|
844
|
+
text-decoration: none;
|
|
845
|
+
align-items: baseline;
|
|
846
|
+
border-radius: 8px;
|
|
847
|
+
box-shadow: var(--panel-shadow);
|
|
848
|
+
transition: background 80ms ease, transform 80ms ease;
|
|
849
|
+
}
|
|
850
|
+
.card-item:hover { background: var(--panel-hover); transform: translateY(-1px); }
|
|
851
|
+
.card-item .code {
|
|
852
|
+
font-family: var(--ff-mono);
|
|
853
|
+
font-size: 11px;
|
|
854
|
+
color: var(--panel-accent);
|
|
855
|
+
letter-spacing: 0.04em;
|
|
856
|
+
font-weight: 600;
|
|
857
|
+
}
|
|
858
|
+
.card-item .name {
|
|
859
|
+
font-size: 13px;
|
|
860
|
+
font-weight: 500;
|
|
861
|
+
color: var(--panel-text);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/* Scrollbars */
|
|
865
|
+
::-webkit-scrollbar { width: 10px; height: 10px; }
|
|
866
|
+
::-webkit-scrollbar-thumb { background: var(--panel-3); border-radius: 5px; }
|
|
867
|
+
::-webkit-scrollbar-thumb:hover { background: var(--panel-hover); }
|
|
868
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
body { display: flex; flex-direction: column; min-height: 100vh; }
|
|
872
|
+
.app-main { max-width: 1100px; margin: 0 auto; width: 100%; padding: 24px 32px 64px 32px; }
|
|
873
|
+
.gm-hero { padding: 32px 0 24px 0; }
|
|
874
|
+
.gm-hero h1 { font-size: 36px; font-weight: 600; margin: 0 0 6px 0; color: var(--panel-text); letter-spacing: -0.01em; line-height: 1.15; }
|
|
875
|
+
.gm-hero .lede { font-size: 14px; line-height: 1.55; color: var(--panel-text-2); max-width: 64ch; margin: 0 0 20px 0; }
|
|
876
|
+
.gm-hero .actions { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
877
|
+
.gm-btn { display: inline-flex; align-items: center; gap: 6px; padding: 8px 14px; background: var(--panel-accent); color: var(--panel-1); border-radius: 6px; font-size: 13px; font-weight: 500; text-decoration: none; }
|
|
878
|
+
.gm-btn:hover { background: var(--panel-accent-2); text-decoration: none; }
|
|
879
|
+
.gm-btn.ghost { background: transparent; color: var(--panel-text); box-shadow: inset 0 0 0 1px var(--panel-3); }
|
|
880
|
+
.gm-btn.ghost:hover { background: var(--panel-hover); }
|
|
881
|
+
.gm-section-label { font-family: var(--ff-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--panel-text-2); margin: 32px 0 8px 0; }
|
|
882
|
+
.gm-section-label .slash { color: var(--panel-text-3); margin-right: 6px; }
|
|
883
|
+
.panel-row-link { display: grid; grid-template-columns: 80px 1fr auto; gap: 12px; padding: 8px 16px; align-items: baseline; color: var(--panel-text); text-decoration: none; font-size: 13px; }
|
|
884
|
+
.panel-row-link:hover { background: var(--panel-hover); text-decoration: none; }
|
|
885
|
+
.panel-row-link .code { font-family: var(--ff-mono); font-size: 11px; color: var(--panel-text-2); letter-spacing: 0.04em; }
|
|
886
|
+
.panel-row-link .title { color: var(--panel-text); font-weight: 500; }
|
|
887
|
+
.panel-row-link .sub { color: var(--panel-text-2); font-size: 12px; margin-left: 8px; font-weight: 400; }
|
|
888
|
+
.panel-row-link .meta { color: var(--panel-text-2); font-size: 11px; text-align: right; font-family: var(--ff-mono); }
|
|
889
|
+
.gm-install { background: var(--panel-1); border-radius: 8px; margin: 12px 0; box-shadow: var(--panel-shadow); overflow: hidden; }
|
|
890
|
+
.gm-install .head { padding: 10px 16px; background: var(--panel-2); font-family: var(--ff-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--panel-text-2); display: flex; justify-content: space-between; }
|
|
891
|
+
.gm-install-step { display: grid; grid-template-columns: 40px 1fr; gap: 12px; padding: 10px 16px; font-family: var(--ff-mono); font-size: 13px; color: var(--panel-text); }
|
|
892
|
+
.gm-install-step:not(:last-child) { box-shadow: inset 0 -1px 0 rgba(0,0,0,0.04); }
|
|
893
|
+
.gm-install-step .n { font-size: 11px; color: var(--panel-text-2); padding-top: 1px; }
|
|
894
|
+
.gm-install-step .d { color: var(--panel-text); font-family: 'Inter','Segoe UI',sans-serif; }
|
|
895
|
+
.gm-install-step pre { margin: 6px 0 0 0; padding: 6px 10px; background: var(--panel-2); border-radius: 4px; font-size: 12px; overflow-x: auto; }
|
|
896
|
+
.gm-install-step pre code { color: var(--panel-accent); font-family: var(--ff-mono); }
|
|
897
|
+
.gm-cards { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
|
898
|
+
@media (max-width: 720px) { .gm-cards { grid-template-columns: 1fr; } }
|
|
899
|
+
.gm-card { background: var(--panel-1); border-radius: 6px; padding: 14px 16px; box-shadow: var(--panel-shadow); }
|
|
900
|
+
.gm-card .t { font-weight: 600; font-size: 13px; color: var(--panel-text); margin-bottom: 4px; }
|
|
901
|
+
.gm-card .d { font-size: 12px; color: var(--panel-text-2); line-height: 1.55; }
|
|
902
|
+
.gm-footer { padding: 12px 20px; font-family: var(--ff-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--panel-text-3); display: flex; justify-content: space-between; background: var(--panel-1); box-shadow: inset 0 1px 0 rgba(0,0,0,0.04); flex-wrap: wrap; gap: 12px; }
|
|
903
|
+
.gm-footer a { color: var(--panel-text-2); }
|
|
904
|
+
.gm-footer a:hover { color: var(--panel-text); }
|
|
905
|
+
</style>
|
|
16
906
|
</head>
|
|
17
|
-
<body>
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
continue;
|
|
111
|
-
if (key.startsWith("on") && typeof value === "function") {
|
|
112
|
-
const eventName = key.substring(2).toLowerCase();
|
|
113
|
-
updateEventListener(el, eventName, value, el.__webjsx_listeners?.[eventName]);
|
|
114
|
-
}
|
|
115
|
-
else if (value !== oldProps[key]) {
|
|
116
|
-
updatePropOrAttr(el, key, value);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
// Handle dangerouslySetInnerHTML
|
|
120
|
-
if ("dangerouslySetInnerHTML" in newProps) {
|
|
121
|
-
const html = newProps.dangerouslySetInnerHTML.__html || "";
|
|
122
|
-
el.innerHTML = html;
|
|
123
|
-
}
|
|
124
|
-
else if ("dangerouslySetInnerHTML" in oldProps) {
|
|
125
|
-
el.innerHTML = "";
|
|
126
|
-
}
|
|
127
|
-
// If this is a fresh set (no oldProps), remove any attributes not in newProps
|
|
128
|
-
if (Object.keys(oldProps).length === 0) {
|
|
129
|
-
const currentAttrs = Array.from(el.attributes).map((attr) => attr.name);
|
|
130
|
-
for (const attr of currentAttrs) {
|
|
131
|
-
if (!(attr in newProps) && !attr.startsWith("on")) {
|
|
132
|
-
el.removeAttribute(attr);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
// Remove old props/attributes
|
|
137
|
-
for (const key of Object.keys(oldProps)) {
|
|
138
|
-
if (!(key in newProps) &&
|
|
139
|
-
key !== "children" &&
|
|
140
|
-
key !== "key" &&
|
|
141
|
-
key !== "dangerouslySetInnerHTML") {
|
|
142
|
-
if (key.startsWith("on")) {
|
|
143
|
-
const eventName = key.substring(2).toLowerCase();
|
|
144
|
-
const existingListener = el.__webjsx_listeners?.[eventName];
|
|
145
|
-
if (existingListener) {
|
|
146
|
-
el.removeEventListener(eventName, existingListener);
|
|
147
|
-
delete el.__webjsx_listeners[eventName];
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
else if (key in el) {
|
|
151
|
-
el[key] = undefined;
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
el.removeAttribute(key);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
// Store current props for future updates
|
|
159
|
-
el.__webjsx_props = newProps;
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Sets attributes and properties on a DOM element based on the provided props.
|
|
163
|
-
* If the property exists on the element, it sets it as a property.
|
|
164
|
-
* Otherwise, it sets it as an attribute or property based on the value type.
|
|
165
|
-
*
|
|
166
|
-
* @param el - The DOM element to update.
|
|
167
|
-
* @param props - The new properties to apply.
|
|
168
|
-
*/
|
|
169
|
-
function setAttributes(el, props) {
|
|
170
|
-
withRenderSuspension(el, () => {
|
|
171
|
-
updateAttributesCore(el, props);
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* Updates attributes and properties on a DOM element based on the new and old props.
|
|
176
|
-
*
|
|
177
|
-
* @param el - The DOM element to update.
|
|
178
|
-
* @param newProps - The new properties to apply.
|
|
179
|
-
* @param oldProps - The old properties to compare against.
|
|
180
|
-
*/
|
|
181
|
-
function updateAttributes(el, newProps, oldProps) {
|
|
182
|
-
withRenderSuspension(el, () => {
|
|
183
|
-
updateAttributesCore(el, newProps, oldProps);
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function isFragment(type) {
|
|
188
|
-
return type === Fragment;
|
|
189
|
-
}
|
|
190
|
-
function createNode(vnode, parentNamespaceURI) {
|
|
191
|
-
if (typeof vnode === "string" ||
|
|
192
|
-
typeof vnode === "number" ||
|
|
193
|
-
typeof vnode === "boolean") {
|
|
194
|
-
return document.createTextNode(String(vnode));
|
|
195
|
-
}
|
|
196
|
-
else if (isFragment(vnode.type)) {
|
|
197
|
-
const fragment = document.createDocumentFragment();
|
|
198
|
-
if (vnode.props.children) {
|
|
199
|
-
const children = vnode.props.children;
|
|
200
|
-
children.forEach((child) => {
|
|
201
|
-
fragment.appendChild(createNode(child, undefined));
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
return fragment;
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
207
|
-
const namespaceURI = vnode.props.xmlns !== undefined
|
|
208
|
-
? vnode.props.xmlns
|
|
209
|
-
: vnode.type === "svg"
|
|
210
|
-
? SVG_NAMESPACE
|
|
211
|
-
: parentNamespaceURI ?? undefined;
|
|
212
|
-
const el = vnode.props.is !== undefined
|
|
213
|
-
? namespaceURI !== undefined
|
|
214
|
-
? document.createElementNS(namespaceURI, vnode.type, {
|
|
215
|
-
is: vnode.props.is,
|
|
216
|
-
})
|
|
217
|
-
: document.createElement(vnode.type, {
|
|
218
|
-
is: vnode.props.is,
|
|
219
|
-
})
|
|
220
|
-
: namespaceURI !== undefined
|
|
221
|
-
? document.createElementNS(namespaceURI, vnode.type)
|
|
222
|
-
: document.createElement(vnode.type);
|
|
223
|
-
if (vnode.props) {
|
|
224
|
-
setAttributes(el, vnode.props);
|
|
225
|
-
}
|
|
226
|
-
if (vnode.props.key != null) {
|
|
227
|
-
el.__webjsx_key = vnode.props.key;
|
|
228
|
-
el.setAttribute("data-key", String(vnode.props.key));
|
|
229
|
-
}
|
|
230
|
-
if (vnode.props.ref) {
|
|
231
|
-
assignRef(el, vnode.props.ref);
|
|
232
|
-
}
|
|
233
|
-
if (vnode.props.children && !vnode.props.dangerouslySetInnerHTML) {
|
|
234
|
-
const children = vnode.props.children;
|
|
235
|
-
children.forEach((child) => {
|
|
236
|
-
el.appendChild(createNode(child, namespaceURI));
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
return el;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
/**
|
|
243
|
-
* Assigns a ref to a node.
|
|
244
|
-
* @param node The DOM node.
|
|
245
|
-
* @param ref The ref to assign.
|
|
246
|
-
*/
|
|
247
|
-
function assignRef(node, ref) {
|
|
248
|
-
const currentRef = node.__webjsx_assignedRef;
|
|
249
|
-
// Only assign the ref if it's different
|
|
250
|
-
if (currentRef !== ref) {
|
|
251
|
-
if (typeof ref === "function") {
|
|
252
|
-
ref(node);
|
|
253
|
-
}
|
|
254
|
-
else if (ref && typeof ref === "object") {
|
|
255
|
-
ref.current = node;
|
|
256
|
-
}
|
|
257
|
-
// Store the assigned ref
|
|
258
|
-
node.__webjsx_assignedRef = ref;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
function createElement(type, props, ...children) {
|
|
263
|
-
const normalizedProps = props ? { ...props } : {};
|
|
264
|
-
const flatChildren = [];
|
|
265
|
-
const flatten = (child) => {
|
|
266
|
-
if (Array.isArray(child)) {
|
|
267
|
-
child.forEach(flatten);
|
|
268
|
-
}
|
|
269
|
-
else if (typeof child === "string" || typeof child === "number") {
|
|
270
|
-
flatChildren.push(child);
|
|
271
|
-
}
|
|
272
|
-
else if (child === null ||
|
|
273
|
-
child === undefined ||
|
|
274
|
-
typeof child === "boolean") {
|
|
275
|
-
// Ignore null or undefined children
|
|
276
|
-
}
|
|
277
|
-
else {
|
|
278
|
-
flatChildren.push(child);
|
|
279
|
-
}
|
|
280
|
-
};
|
|
281
|
-
children.forEach(flatten);
|
|
282
|
-
if (flatChildren.length > 0) {
|
|
283
|
-
// Only set children if dangerouslySetInnerHTML is not present
|
|
284
|
-
if (!normalizedProps.dangerouslySetInnerHTML) {
|
|
285
|
-
normalizedProps.children = flatChildren;
|
|
286
|
-
}
|
|
287
|
-
else {
|
|
288
|
-
console.warn("WebJSX: Ignoring children since dangerouslySetInnerHTML is set.");
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
return {
|
|
292
|
-
type,
|
|
293
|
-
props: normalizedProps,
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Applies the differences between the new virtual node(s) and the existing DOM.
|
|
299
|
-
* @param parent The parent DOM node where the virtual nodes will be applied.
|
|
300
|
-
* @param newVirtualNode A single virtual node or an array of virtual nodes.
|
|
301
|
-
*/
|
|
302
|
-
function applyDiff(parent, newVirtualNode) {
|
|
303
|
-
const newVNodes = Array.isArray(newVirtualNode)
|
|
304
|
-
? newVirtualNode
|
|
305
|
-
: [newVirtualNode];
|
|
306
|
-
diffChildren(parent, newVNodes);
|
|
307
|
-
}
|
|
308
|
-
/**
|
|
309
|
-
* Flattens the list of virtual nodes by replacing Fragments with their children.
|
|
310
|
-
* @param vnodes The array of virtual nodes to flatten.
|
|
311
|
-
* @returns A new array of virtual nodes with Fragments flattened.
|
|
312
|
-
*/
|
|
313
|
-
function flattenVNodes(vnodes) {
|
|
314
|
-
const flat = [];
|
|
315
|
-
const arrayVNodes = vnodes;
|
|
316
|
-
arrayVNodes.forEach((vnode) => {
|
|
317
|
-
if (isFragment(vnode)) {
|
|
318
|
-
const children = vnode.props.children ? vnode.props.children : [];
|
|
319
|
-
flat.push(...children);
|
|
320
|
-
}
|
|
321
|
-
else {
|
|
322
|
-
flat.push(vnode);
|
|
323
|
-
}
|
|
324
|
-
});
|
|
325
|
-
return flat;
|
|
326
|
-
}
|
|
327
|
-
/**
|
|
328
|
-
* Type guard to check if a VNode is a Fragment.
|
|
329
|
-
* @param vnode The virtual node to check.
|
|
330
|
-
* @returns True if vnode is a Fragment, false otherwise.
|
|
331
|
-
*/
|
|
332
|
-
function isFragment(vnode) {
|
|
333
|
-
return typeof vnode === "object" && vnode !== null && vnode.type === Fragment;
|
|
334
|
-
}
|
|
335
|
-
/**
|
|
336
|
-
* Diffs and updates the children of a DOM node based on the new virtual nodes.
|
|
337
|
-
* @param parent The parent DOM node whose children will be diffed.
|
|
338
|
-
* @param newVNodes An array of new virtual nodes.
|
|
339
|
-
*/
|
|
340
|
-
function diffChildren(parent, newVNodes) {
|
|
341
|
-
const flattenedVNodes = flattenVNodes(newVNodes);
|
|
342
|
-
const existingNodes = Array.from(parent.childNodes);
|
|
343
|
-
const keyedMap = new Map();
|
|
344
|
-
// Populate keyedMap with existing keyed nodes
|
|
345
|
-
existingNodes.forEach((node) => {
|
|
346
|
-
const key = node.__webjsx_key;
|
|
347
|
-
if (key != null) {
|
|
348
|
-
keyedMap.set(key, node);
|
|
349
|
-
}
|
|
350
|
-
});
|
|
351
|
-
const newKeys = flattenedVNodes
|
|
352
|
-
.filter(isVElementWithKey)
|
|
353
|
-
.map((vnode) => vnode.props.key);
|
|
354
|
-
existingNodes.forEach((node) => {
|
|
355
|
-
const key = node.__webjsx_key;
|
|
356
|
-
if (key != null && !newKeys.includes(key)) {
|
|
357
|
-
parent.removeChild(node);
|
|
358
|
-
}
|
|
359
|
-
});
|
|
360
|
-
flattenedVNodes.forEach((newVNode, i) => {
|
|
361
|
-
const newKey = isVElement(newVNode) ? newVNode.props.key : undefined;
|
|
362
|
-
let existingNode = null;
|
|
363
|
-
if (newKey != null) {
|
|
364
|
-
existingNode = keyedMap.get(newKey) || null;
|
|
365
|
-
}
|
|
366
|
-
if (!existingNode && newKey == null) {
|
|
367
|
-
existingNode = parent.childNodes[i] || null;
|
|
368
|
-
}
|
|
369
|
-
if (existingNode) {
|
|
370
|
-
if (existingNode !== parent.childNodes[i]) {
|
|
371
|
-
parent.insertBefore(existingNode, parent.childNodes[i] || null);
|
|
372
|
-
}
|
|
373
|
-
updateNode(existingNode, newVNode);
|
|
374
|
-
}
|
|
375
|
-
else {
|
|
376
|
-
const newDomNode = createNode(newVNode, getNamespaceURI(parent));
|
|
377
|
-
if (isVElement(newVNode) && newVNode.props.key != null) {
|
|
378
|
-
newDomNode.__webjsx_key = newVNode.props.key;
|
|
379
|
-
newDomNode.setAttribute("data-key", String(newVNode.props.key));
|
|
380
|
-
}
|
|
381
|
-
parent.insertBefore(newDomNode, parent.childNodes[i] || null);
|
|
382
|
-
}
|
|
383
|
-
});
|
|
384
|
-
const updatedChildNodes = Array.from(parent.childNodes);
|
|
385
|
-
const newUnkeyed = flattenedVNodes.filter((vnode) => !isVElementWithKey(vnode));
|
|
386
|
-
const existingUnkeyed = updatedChildNodes.filter((node) => !node.__webjsx_key);
|
|
387
|
-
if (newUnkeyed.length < existingUnkeyed.length) {
|
|
388
|
-
for (let i = newUnkeyed.length; i < existingUnkeyed.length; i++) {
|
|
389
|
-
parent.removeChild(existingUnkeyed[i]);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
/**
|
|
394
|
-
* Updates a DOM node to match the new virtual node.
|
|
395
|
-
* @param domNode The existing DOM node to be updated.
|
|
396
|
-
* @param newVNode The new virtual node to apply.
|
|
397
|
-
*/
|
|
398
|
-
function updateNode(domNode, newVNode) {
|
|
399
|
-
if (typeof newVNode === "string" ||
|
|
400
|
-
typeof newVNode === "number" ||
|
|
401
|
-
typeof newVNode === "boolean") {
|
|
402
|
-
if (domNode.nodeType !== Node.TEXT_NODE ||
|
|
403
|
-
domNode.textContent !== String(newVNode)) {
|
|
404
|
-
const newTextNode = document.createTextNode(String(newVNode));
|
|
405
|
-
domNode.parentNode?.replaceChild(newTextNode, domNode);
|
|
406
|
-
}
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
|
-
if (newVNode.type === Fragment) {
|
|
410
|
-
if (domNode instanceof DocumentFragment) {
|
|
411
|
-
diffChildren(domNode, newVNode.props.children ? newVNode.props.children : []);
|
|
412
|
-
}
|
|
413
|
-
else {
|
|
414
|
-
const fragment = document.createDocumentFragment();
|
|
415
|
-
const children = newVNode.props.children ? newVNode.props.children : [];
|
|
416
|
-
children.forEach((child) => {
|
|
417
|
-
fragment.appendChild(createNode(child, undefined));
|
|
418
|
-
});
|
|
419
|
-
domNode.parentNode?.replaceChild(fragment, domNode);
|
|
420
|
-
}
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
if (domNode instanceof HTMLElement &&
|
|
424
|
-
domNode.tagName.toLowerCase() === newVNode.type.toLowerCase()) {
|
|
425
|
-
const oldProps = domNode.__webjsx_props || {};
|
|
426
|
-
const newProps = newVNode.props || {};
|
|
427
|
-
updateAttributes(domNode, newProps, oldProps);
|
|
428
|
-
if (isVElement(newVNode) && newVNode.props.key != null) {
|
|
429
|
-
domNode.__webjsx_key = newVNode.props.key;
|
|
430
|
-
domNode.setAttribute("data-key", String(newVNode.props.key));
|
|
431
|
-
}
|
|
432
|
-
else {
|
|
433
|
-
delete domNode.__webjsx_key;
|
|
434
|
-
domNode.removeAttribute("data-key");
|
|
435
|
-
}
|
|
436
|
-
if (newProps.ref) {
|
|
437
|
-
assignRef(domNode, newProps.ref);
|
|
438
|
-
}
|
|
439
|
-
if (!newProps.dangerouslySetInnerHTML && newProps.children != null) {
|
|
440
|
-
diffChildren(domNode, newProps.children);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
else {
|
|
444
|
-
const newDomNode = createNode(newVNode, domNode.parentNode ? getNamespaceURI(domNode.parentNode) : undefined);
|
|
445
|
-
if (isVElement(newVNode) && newVNode.props.key != null) {
|
|
446
|
-
newDomNode.__webjsx_key = newVNode.props.key;
|
|
447
|
-
newDomNode.setAttribute("data-key", String(newVNode.props.key));
|
|
448
|
-
}
|
|
449
|
-
if (isVElement(newVNode) && newVNode.props.ref) {
|
|
450
|
-
assignRef(newDomNode, newVNode.props.ref);
|
|
451
|
-
}
|
|
452
|
-
domNode.parentNode?.replaceChild(newDomNode, domNode);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
/**
|
|
456
|
-
* Assigns a ref to a node.
|
|
457
|
-
* @param node The DOM node.
|
|
458
|
-
* @param ref The ref to assign.
|
|
459
|
-
*/
|
|
460
|
-
function assignRef(node, ref) {
|
|
461
|
-
const currentRef = node.__webjsx_assignedRef;
|
|
462
|
-
// Only assign the ref if it's different
|
|
463
|
-
if (currentRef !== ref) {
|
|
464
|
-
if (typeof ref === "function") {
|
|
465
|
-
ref(node);
|
|
466
|
-
}
|
|
467
|
-
else if (ref && typeof ref === "object") {
|
|
468
|
-
ref.current = node;
|
|
469
|
-
}
|
|
470
|
-
// Store the assigned ref
|
|
471
|
-
node.__webjsx_assignedRef = ref;
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
/**
|
|
475
|
-
* Type guard to check if a VNode is a VElement.
|
|
476
|
-
* @param vnode The virtual node to check.
|
|
477
|
-
* @returns True if vnode is a VElement, false otherwise.
|
|
478
|
-
*/
|
|
479
|
-
function isVElement(vnode) {
|
|
480
|
-
return typeof vnode === "object" && vnode !== null && "props" in vnode;
|
|
481
|
-
}
|
|
482
|
-
/**
|
|
483
|
-
* Type guard to check if a VNode is a VElement with a key.
|
|
484
|
-
* @param vnode The virtual node to check.
|
|
485
|
-
* @returns True if vnode is a VElement with a key, false otherwise.
|
|
486
|
-
*/
|
|
487
|
-
function isVElementWithKey(vnode) {
|
|
488
|
-
return isVElement(vnode) && vnode.props.key != null;
|
|
489
|
-
}
|
|
490
|
-
function getNamespaceURI(node) {
|
|
491
|
-
return node instanceof Element && node.namespaceURI !== HTML_NAMESPACE
|
|
492
|
-
? node.namespaceURI ?? undefined
|
|
493
|
-
: undefined;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
const h = createElement;
|
|
497
|
-
|
|
498
|
-
const PLATFORM_NAME="Copilot CLI",PLATFORM_TYPE="CLI Tool",PLATFORM_TYPE_COLOR="#3b82f6";
|
|
499
|
-
const DESCRIPTION="State machine agent with hooks, skills, and automated git enforcement",VERSION="2.0.538";
|
|
500
|
-
const GITHUB_URL="https://github.com/AnEntrypoint/gm-copilot-cli",BADGE_LABEL="copilot-cli";
|
|
501
|
-
const FEATURES=[{"title":"State Machine","desc":"Immutable PLAN→EXECUTE→EMIT→VERIFY→COMPLETE phases with full mutable tracking"},{"title":"Semantic Search","desc":"Natural language codebase exploration via codesearch skill — no grep needed"},{"title":"Hooks","desc":"Pre-tool, session-start, prompt-submit, and stop hooks for full lifecycle control"},{"title":"Agents","desc":"gm, codesearch, and websearch agents pre-configured and ready to use"},{"title":"MCP Integration","desc":"Model Context Protocol server support built in"},{"title":"Auto-Recovery","desc":"Supervisor hierarchy ensures the system never crashes"}],INSTALL_STEPS=[{"desc":"Install via GitHub CLI","cmd":"gh extension install AnEntrypoint/gm-copilot-cli"},{"desc":"Restart your terminal — activates automatically"}];
|
|
502
|
-
const CURRENT_PLATFORM="gm-copilot-cli";
|
|
503
|
-
const ALL_PLATFORMS=[
|
|
504
|
-
{id:'gm-cc',label:'Claude Code',type:'cli'},{id:'gm-gc',label:'Gemini CLI',type:'cli'},
|
|
505
|
-
{id:'gm-oc',label:'OpenCode',type:'cli'},{id:'gm-kilo',label:'Kilo Code',type:'cli'},
|
|
506
|
-
{id:'gm-codex',label:'Codex',type:'cli'},{id:'gm-copilot-cli',label:'Copilot CLI',type:'cli'},
|
|
507
|
-
{id:'gm-qwen',label:'Qwen Code',type:'cli'},{id:'gm-hermes',label:'Hermes Agent',type:'cli'},
|
|
508
|
-
{id:'gm-vscode',label:'VS Code',type:'ide'},{id:'gm-cursor',label:'Cursor',type:'ide'},
|
|
509
|
-
{id:'gm-zed',label:'Zed',type:'ide'},{id:'gm-jetbrains',label:'JetBrains',type:'ide'},
|
|
510
|
-
];
|
|
511
|
-
const GH_ICON='M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z';
|
|
512
|
-
function NavBar(){return h('nav',{class:'border-b border-gray-800 bg-gray-950/80 backdrop-blur sticky top-0 z-10'},h('div',{class:'max-w-5xl mx-auto px-4 py-3 flex items-center justify-between'},h('div',{class:'flex items-center gap-3'},h('a',{href:'https://anentrypoint.github.io/gm',class:'text-white font-bold text-lg hover:text-indigo-400 transition-colors'},'gm'),h('span',{class:'text-gray-500'},'/'),h('span',{class:'text-gray-300 font-medium'},BADGE_LABEL)),h('a',{href:GITHUB_URL,target:'_blank',rel:'noopener',class:'flex items-center gap-2 text-gray-400 hover:text-white transition-colors text-sm'},h('svg',{viewBox:'0 0 16 16',class:'w-5 h-5 fill-current','aria-hidden':'true'},h('path',{d:GH_ICON})),'GitHub')));}
|
|
513
|
-
function Badge(label,color){return h('span',{class:'inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold text-white',style:`background-color:${color}`},label);}
|
|
514
|
-
function Hero(){return h('section',{class:'gradient-hero py-20 px-4'},h('div',{class:'max-w-5xl mx-auto text-center'},h('div',{class:'flex justify-center gap-2 mb-6'},Badge(PLATFORM_TYPE,PLATFORM_TYPE_COLOR),Badge('v'+VERSION,'#374151')),h('h1',{class:'text-4xl md:text-5xl font-bold text-white mb-4'},PLATFORM_NAME),h('p',{class:'text-lg text-gray-300 max-w-2xl mx-auto mb-8'},DESCRIPTION),h('a',{href:GITHUB_URL,target:'_blank',rel:'noopener',class:'inline-flex items-center gap-2 bg-indigo-600 hover:bg-indigo-500 text-white font-semibold px-6 py-3 rounded-lg transition-colors'},'View on GitHub')));}
|
|
515
|
-
function FeatureCard(title,desc){return h('div',{class:'card-hover bg-gray-900 border border-gray-800 rounded-xl p-5'},h('h3',{class:'font-semibold text-white mb-2'},title),h('p',{class:'text-gray-400 text-sm leading-relaxed'},desc));}
|
|
516
|
-
function FeaturesSection(){return h('section',{class:'py-16 px-4'},h('div',{class:'max-w-5xl mx-auto'},h('h2',{class:'text-2xl font-bold text-white mb-8 text-center'},'Features'),h('div',{class:'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4'},...FEATURES.map(f=>FeatureCard(f.title,f.desc)))));}
|
|
517
|
-
function InstallStep(step,index){return h('div',{class:'flex gap-4 items-start'},h('div',{class:'flex-shrink-0 w-7 h-7 rounded-full bg-indigo-600 flex items-center justify-center text-xs font-bold text-white'},String(index+1)),h('div',{class:'flex-1'},h('p',{class:'text-gray-300 text-sm mb-1'},step.desc),step.cmd?h('pre',{class:'bg-gray-950 border border-gray-700 rounded-lg px-4 py-2 text-sm text-green-400 overflow-x-auto mt-1'},step.cmd):null));}
|
|
518
|
-
function InstallSection(){if(!INSTALL_STEPS.length)return null;return h('section',{class:'py-16 px-4 bg-gray-900/50'},h('div',{class:'max-w-2xl mx-auto'},h('h2',{class:'text-2xl font-bold text-white mb-8 text-center'},'Installation'),h('div',{class:'space-y-6'},...INSTALL_STEPS.map((step,i)=>InstallStep(step,i)))));}
|
|
519
|
-
function PlatformLink(p){const isCurrent=p.id===CURRENT_PLATFORM;return h('a',{href:isCurrent?'#':`https://anentrypoint.github.io/${p.id}`,class:`px-3 py-1.5 rounded-lg text-sm font-medium transition-colors ${isCurrent?'bg-indigo-600 text-white cursor-default':'bg-gray-800 text-gray-300 hover:bg-gray-700 hover:text-white'}`},p.label);}
|
|
520
|
-
const SM_PHASES=[{name:'PLAN',desc:'Write .prd with every unknown named before any work begins'},{name:'EXECUTE',desc:'Prove every hypothesis via witnessed execution, import real modules'},{name:'EMIT',desc:'Write files only after all tests pass — pre and post-emit gates'},{name:'VERIFY',desc:'End-to-end execution confirms all changes work in real context'},{name:'COMPLETE',desc:'.prd empty, git clean, all output pushed'}];
|
|
521
|
-
const HOOK_ITEMS=[{title:'Tool interception',desc:'exec:<lang> commands run code directly; forbidden tools redirected to code-search'},{title:'System injection',desc:'gm.md rules prepended to every system prompt'},{title:'Context injection',desc:'session-start injects codebase analysis and pending work reminder'},{title:'Completion gate',desc:'session end blocked until .prd is empty and git is clean'}];
|
|
522
|
-
function PhaseCard(phase,index,total){return h('div',{class:'card-hover bg-gray-900 border border-gray-800 rounded-xl p-4 flex-1 min-w-0'},h('div',{class:'flex items-center gap-2 mb-2'},h('span',{class:'text-indigo-400 font-bold text-sm font-mono'},phase.name),index<total-1?h('span',{class:'text-gray-600 text-xs'},'→'):null),h('p',{class:'text-gray-400 text-xs leading-relaxed'},phase.desc));}
|
|
523
|
-
function StateMachineSection(){return h('section',{class:'py-16 px-4 bg-gray-900/40'},h('div',{class:'max-w-5xl mx-auto'},h('h2',{class:'text-2xl font-bold text-white mb-2 text-center'},'State Machine'),h('p',{class:'text-gray-400 text-sm text-center mb-8'},'Every task follows the same 5-phase cycle — no skipping, no shortcuts'),h('div',{class:'flex flex-col sm:flex-row gap-3'},...SM_PHASES.map((phase,i)=>PhaseCard(phase,i,SM_PHASES.length)))));}
|
|
524
|
-
function HookCard(item){return h('div',{class:'card-hover bg-gray-900 border border-gray-800 rounded-xl p-5'},h('h3',{class:'font-semibold text-white mb-2'},item.title),h('p',{class:'text-gray-400 text-sm leading-relaxed'},item.desc));}
|
|
525
|
-
function HooksSection(){return h('section',{class:'py-16 px-4'},h('div',{class:'max-w-5xl mx-auto'},h('h2',{class:'text-2xl font-bold text-white mb-2 text-center'},'What the Hooks Enforce'),h('p',{class:'text-gray-400 text-sm text-center mb-8'},'Lifecycle hooks wrap every interaction to keep the agent on rails'),h('div',{class:'grid grid-cols-1 sm:grid-cols-2 gap-4'},...HOOK_ITEMS.map(item=>HookCard(item)))));}
|
|
526
|
-
function AlsoAvailableSection(){const cli=ALL_PLATFORMS.filter(p=>p.type==='cli'),ide=ALL_PLATFORMS.filter(p=>p.type==='ide');return h('section',{class:'py-16 px-4 bg-gray-900/30'},h('div',{class:'max-w-5xl mx-auto'},h('h2',{class:'text-2xl font-bold text-white mb-8 text-center'},'Also Available For'),h('div',{class:'space-y-6'},h('div',null,h('p',{class:'text-xs font-semibold text-blue-400 uppercase tracking-wider mb-3'},'CLI Tools'),h('div',{class:'flex flex-wrap gap-2'},...cli.map(p=>PlatformLink(p)))),h('div',null,h('p',{class:'text-xs font-semibold text-purple-400 uppercase tracking-wider mb-3'},'IDE Extensions'),h('div',{class:'flex flex-wrap gap-2'},...ide.map(p=>PlatformLink(p))))),h('div',{class:'mt-8 text-center'},h('a',{href:'https://anentrypoint.github.io/gm',class:'text-sm text-gray-400 hover:text-indigo-300 transition-colors'},'← Back to gm hub'))));}
|
|
527
|
-
function Footer(){return h('footer',{class:'border-t border-gray-800 py-8 px-4 text-center text-gray-500 text-sm'},h('p',null,'Generated by ',h('a',{href:'https://github.com/AnEntrypoint/gm',class:'text-indigo-400 hover:text-indigo-300'},'gm'),' — convention-driven multi-platform plugin generator'));}
|
|
528
|
-
applyDiff(document.body,[NavBar(),Hero(),FeaturesSection(),StateMachineSection(),HooksSection(),InstallSection(),AlsoAvailableSection(),Footer()]);
|
|
529
|
-
</script>
|
|
530
|
-
</body>
|
|
531
|
-
</html>
|
|
907
|
+
<body data-screen-label="gm / copilot cli">
|
|
908
|
+
<header class="app-topbar">
|
|
909
|
+
<span class="brand">247420<span class="slash"> / </span>gm<span class="slash"> / </span>copilot cli</span>
|
|
910
|
+
<nav>
|
|
911
|
+
<a href="https://anentrypoint.github.io/gm/">← all platforms</a>
|
|
912
|
+
<a href="https://github.com/AnEntrypoint/gm-copilot-cli" target="_blank" rel="noopener">source ↗</a>
|
|
913
|
+
</nav>
|
|
914
|
+
</header>
|
|
915
|
+
<div class="app-crumb">
|
|
916
|
+
<span>247420</span><span class="sep">›</span>
|
|
917
|
+
<span>gm</span><span class="sep">›</span>
|
|
918
|
+
<span class="leaf">copilot cli</span>
|
|
919
|
+
</div>
|
|
920
|
+
<main class="app-main">
|
|
921
|
+
<section class="gm-hero">
|
|
922
|
+
<h1>gm · copilot cli</h1>
|
|
923
|
+
<p class="lede">State machine agent with hooks, skills, and automated git enforcement</p>
|
|
924
|
+
<div class="actions">
|
|
925
|
+
<a class="gm-btn" href="https://github.com/AnEntrypoint/gm-copilot-cli" target="_blank" rel="noopener">view on github</a>
|
|
926
|
+
<a class="gm-btn ghost" href="#install">install ↓</a>
|
|
927
|
+
</div>
|
|
928
|
+
</section>
|
|
929
|
+
<section>
|
|
930
|
+
<div class="gm-section-label"><span class="slash">//</span>status</div>
|
|
931
|
+
<div class="panel">
|
|
932
|
+
<div class="panel-head"><span>release · v2.0.540</span><span>probably emerging</span></div>
|
|
933
|
+
<div class="panel-body">
|
|
934
|
+
<div class="row">
|
|
935
|
+
<span class="code"><span style="color:var(--panel-accent)">●</span></span>
|
|
936
|
+
<span class="title">Copilot CLI<span class="sub">part of the gm family</span></span>
|
|
937
|
+
<span class="meta">live</span>
|
|
938
|
+
</div>
|
|
939
|
+
</div>
|
|
940
|
+
</div>
|
|
941
|
+
</section>
|
|
942
|
+
<section id="install">
|
|
943
|
+
<div class="gm-section-label"><span class="slash">//</span>install</div>
|
|
944
|
+
<div class="gm-install">
|
|
945
|
+
<div class="head"><span>install · 2 steps</span><span>shell</span></div>
|
|
946
|
+
<div class="gm-install-step"><span class="n">01</span><div><div class="d">install via GitHub CLI</div><pre><code>gh extension install AnEntrypoint/gm-copilot-cli</code></pre></div></div>
|
|
947
|
+
<div class="gm-install-step"><span class="n">02</span><div><div class="d">restart your terminal</div></div></div>
|
|
948
|
+
</div>
|
|
949
|
+
</section>
|
|
950
|
+
<section>
|
|
951
|
+
<div class="gm-section-label"><span class="slash">//</span>features</div>
|
|
952
|
+
<div class="gm-cards">
|
|
953
|
+
<div class="gm-card"><div class="t">state machine</div><div class="d">immutable PLAN→EXECUTE→EMIT→VERIFY→COMPLETE phases with full mutable tracking</div></div>
|
|
954
|
+
<div class="gm-card"><div class="t">semantic search</div><div class="d">natural language codebase exploration via codesearch skill</div></div>
|
|
955
|
+
<div class="gm-card"><div class="t">hooks</div><div class="d">pre-tool, session-start, prompt-submit, and stop hooks for full lifecycle control</div></div>
|
|
956
|
+
<div class="gm-card"><div class="t">agents</div><div class="d">gm, codesearch, and websearch agents pre-configured</div></div>
|
|
957
|
+
<div class="gm-card"><div class="t">mcp integration</div><div class="d">model context protocol server support built in</div></div>
|
|
958
|
+
<div class="gm-card"><div class="t">auto-recovery</div><div class="d">supervisor hierarchy ensures the system never crashes</div></div>
|
|
959
|
+
</div>
|
|
960
|
+
</section>
|
|
961
|
+
<section>
|
|
962
|
+
<div class="gm-section-label"><span class="slash">//</span>state machine</div>
|
|
963
|
+
<div class="panel">
|
|
964
|
+
<div class="panel-head"><span>5 phases · any new unknown → plan</span><span>strict order</span></div>
|
|
965
|
+
<div class="panel-body">
|
|
966
|
+
<div class="row"><span class="code">001</span><span class="title">plan<span class="sub">write the unknowns down</span></span><span class="meta">.gm/prd.yml</span></div>
|
|
967
|
+
<div class="row"><span class="code">002</span><span class="title">execute<span class="sub">run code against real services</span></span><span class="meta">exec:<lang></span></div>
|
|
968
|
+
<div class="row"><span class="code">003</span><span class="title">emit<span class="sub">write files after checks pass</span></span><span class="meta">pre + post gates</span></div>
|
|
969
|
+
<div class="row"><span class="code">004</span><span class="title">verify<span class="sub">end-to-end, real data</span></span><span class="meta">no mocks</span></div>
|
|
970
|
+
<div class="row"><span class="code">005</span><span class="title">complete<span class="sub">.prd empty, git clean, pushed</span></span><span class="meta">test.js</span></div>
|
|
971
|
+
</div>
|
|
972
|
+
</div>
|
|
973
|
+
</section>
|
|
974
|
+
<section>
|
|
975
|
+
<div class="gm-section-label"><span class="slash">//</span>also available</div>
|
|
976
|
+
<div class="panel">
|
|
977
|
+
<div class="panel-head"><span>platforms · 12</span><span>one state machine</span></div>
|
|
978
|
+
<div class="panel-body">
|
|
979
|
+
<a class="panel-row-link" href="https://anentrypoint.github.io/gm-cc" target="_blank" rel="noopener"><span class="code">001</span><span class="title">claude code<span class="sub">gm-cc</span></span><span class="meta">cli · live</span></a>
|
|
980
|
+
<a class="panel-row-link" href="https://anentrypoint.github.io/gm-gc" target="_blank" rel="noopener"><span class="code">002</span><span class="title">gemini cli<span class="sub">gm-gc</span></span><span class="meta">cli · live</span></a>
|
|
981
|
+
<a class="panel-row-link" href="https://anentrypoint.github.io/gm-oc" target="_blank" rel="noopener"><span class="code">003</span><span class="title">opencode<span class="sub">gm-oc</span></span><span class="meta">cli · live</span></a>
|
|
982
|
+
<a class="panel-row-link" href="https://anentrypoint.github.io/gm-kilo" target="_blank" rel="noopener"><span class="code">004</span><span class="title">kilo code<span class="sub">gm-kilo</span></span><span class="meta">cli · live</span></a>
|
|
983
|
+
<a class="panel-row-link" href="https://anentrypoint.github.io/gm-codex" target="_blank" rel="noopener"><span class="code">005</span><span class="title">codex<span class="sub">gm-codex</span></span><span class="meta">cli · live</span></a>
|
|
984
|
+
<a class="panel-row-link" href="#"><span class="code">006</span><span class="title">copilot cli<span class="sub">gm-copilot-cli</span></span><span class="meta">cli · here</span></a>
|
|
985
|
+
<a class="panel-row-link" href="https://anentrypoint.github.io/gm-vscode" target="_blank" rel="noopener"><span class="code">007</span><span class="title">vs code<span class="sub">gm-vscode</span></span><span class="meta">ide · live</span></a>
|
|
986
|
+
<a class="panel-row-link" href="https://anentrypoint.github.io/gm-cursor" target="_blank" rel="noopener"><span class="code">008</span><span class="title">cursor<span class="sub">gm-cursor</span></span><span class="meta">ide · live</span></a>
|
|
987
|
+
<a class="panel-row-link" href="https://anentrypoint.github.io/gm-zed" target="_blank" rel="noopener"><span class="code">009</span><span class="title">zed<span class="sub">gm-zed</span></span><span class="meta">ide · live</span></a>
|
|
988
|
+
<a class="panel-row-link" href="https://anentrypoint.github.io/gm-jetbrains" target="_blank" rel="noopener"><span class="code">010</span><span class="title">jetbrains<span class="sub">gm-jetbrains</span></span><span class="meta">ide · live</span></a>
|
|
989
|
+
<a class="panel-row-link" href="https://anentrypoint.github.io/gm-qwen" target="_blank" rel="noopener"><span class="code">011</span><span class="title">qwen code<span class="sub">gm-qwen</span></span><span class="meta">cli · live</span></a>
|
|
990
|
+
<a class="panel-row-link" href="https://anentrypoint.github.io/gm-hermes" target="_blank" rel="noopener"><span class="code">012</span><span class="title">hermes agent<span class="sub">gm-hermes</span></span><span class="meta">cli · live</span></a>
|
|
991
|
+
</div>
|
|
992
|
+
</div>
|
|
993
|
+
</section>
|
|
994
|
+
</main>
|
|
995
|
+
<footer class="gm-footer">
|
|
996
|
+
<span>copilot cli · part of <a href="https://anentrypoint.github.io/gm/">gm</a> · <a href="https://github.com/AnEntrypoint" target="_blank" rel="noopener">anentrypoint</a></span>
|
|
997
|
+
<span>probably emerging 🌀</span>
|
|
998
|
+
</footer>
|
|
999
|
+
</body></html>
|