vibeshare-live 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -67
- package/dist/chunk-RY2ANOPC.js +1018 -0
- package/dist/cli.d.ts +33 -35
- package/dist/cli.js +393 -238
- package/dist/index.d.ts +106 -47
- package/dist/index.js +53 -22
- package/dist/manager-uhMBJXtu.d.ts +259 -0
- package/dist/mcp.d.ts +27 -38
- package/dist/mcp.js +199 -14616
- package/docs/launch-video-16x9.html +673 -0
- package/docs/launch-video-9x16.html +673 -0
- package/docs/launch-video.html +490 -0
- package/docs/prototype.html +964 -0
- package/docs/spec.md +61 -0
- package/package.json +41 -23
- package/dist/chunk-5OLI3WTU.js +0 -233
- package/dist/share-CoX608sF.d.ts +0 -188
|
@@ -0,0 +1,964 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>vibeshare</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root{
|
|
9
|
+
--bg:#0a0b0f;
|
|
10
|
+
--panel:#12141a;
|
|
11
|
+
--panel-2:#171a22;
|
|
12
|
+
--panel-3:#1d2129;
|
|
13
|
+
--border:rgba(255,255,255,.08);
|
|
14
|
+
--border-2:rgba(255,255,255,.14);
|
|
15
|
+
--text:#edeef3;
|
|
16
|
+
--dim:#9aa0b2;
|
|
17
|
+
--faint:#666c7c;
|
|
18
|
+
--cyan:#67e8f9;
|
|
19
|
+
--cyan-dim:#2a4a52;
|
|
20
|
+
--violet:#c4b5fd;
|
|
21
|
+
--violet-dim:#3a3160;
|
|
22
|
+
--amber:#f5b84e;
|
|
23
|
+
--green:#7ee787;
|
|
24
|
+
--red:#ff8b85;
|
|
25
|
+
--mono: ui-monospace, "SF Mono", "Cascadia Code", Menlo, Consolas, monospace;
|
|
26
|
+
--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
*{ box-sizing:border-box; }
|
|
30
|
+
|
|
31
|
+
html,body{
|
|
32
|
+
margin:0; padding:0;
|
|
33
|
+
background:var(--bg);
|
|
34
|
+
color:var(--text);
|
|
35
|
+
font-family:var(--sans);
|
|
36
|
+
-webkit-font-smoothing:antialiased;
|
|
37
|
+
min-height:100vh;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
body{
|
|
41
|
+
background-image:
|
|
42
|
+
radial-gradient(1100px 600px at 15% -10%, rgba(103,232,249,.05), transparent 60%),
|
|
43
|
+
radial-gradient(1000px 700px at 90% 10%, rgba(196,181,253,.045), transparent 55%);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
::selection{ background:rgba(103,232,249,.25); color:#fff; }
|
|
47
|
+
|
|
48
|
+
button{ font-family:inherit; }
|
|
49
|
+
|
|
50
|
+
.app{
|
|
51
|
+
max-width:1320px;
|
|
52
|
+
margin:0 auto;
|
|
53
|
+
padding:28px 32px 56px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* ---------- header ---------- */
|
|
57
|
+
|
|
58
|
+
.topbar{
|
|
59
|
+
display:flex;
|
|
60
|
+
align-items:center;
|
|
61
|
+
justify-content:space-between;
|
|
62
|
+
gap:16px;
|
|
63
|
+
padding-bottom:22px;
|
|
64
|
+
margin-bottom:24px;
|
|
65
|
+
border-bottom:1px solid var(--border);
|
|
66
|
+
flex-wrap:wrap;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.brand{
|
|
70
|
+
display:flex;
|
|
71
|
+
align-items:baseline;
|
|
72
|
+
gap:11px;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.brand-mark{
|
|
76
|
+
display:inline-flex;
|
|
77
|
+
align-items:center;
|
|
78
|
+
justify-content:center;
|
|
79
|
+
width:26px; height:26px;
|
|
80
|
+
border-radius:7px;
|
|
81
|
+
background:linear-gradient(160deg, var(--panel-3), var(--panel-2));
|
|
82
|
+
border:1px solid var(--border-2);
|
|
83
|
+
font-size:13px;
|
|
84
|
+
color:var(--cyan);
|
|
85
|
+
transform:translateY(1px);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.brand-name{
|
|
89
|
+
font-size:18px;
|
|
90
|
+
font-weight:650;
|
|
91
|
+
letter-spacing:-0.01em;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.brand-sub{
|
|
95
|
+
font-size:13px;
|
|
96
|
+
color:var(--faint);
|
|
97
|
+
font-weight:450;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.p2p-badge{
|
|
101
|
+
display:inline-flex;
|
|
102
|
+
align-items:center;
|
|
103
|
+
gap:8px;
|
|
104
|
+
font-family:var(--mono);
|
|
105
|
+
font-size:12.5px;
|
|
106
|
+
color:var(--dim);
|
|
107
|
+
background:var(--panel);
|
|
108
|
+
border:1px solid var(--border-2);
|
|
109
|
+
border-radius:999px;
|
|
110
|
+
padding:7px 14px 7px 11px;
|
|
111
|
+
white-space:nowrap;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.p2p-badge b{
|
|
115
|
+
color:var(--text);
|
|
116
|
+
font-weight:600;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.live-pulse{
|
|
120
|
+
width:7px; height:7px;
|
|
121
|
+
border-radius:50%;
|
|
122
|
+
background:var(--amber);
|
|
123
|
+
box-shadow:0 0 0 0 rgba(245,184,78,.55);
|
|
124
|
+
animation:pulse 2.2s cubic-bezier(.4,0,.2,1) infinite;
|
|
125
|
+
flex:none;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@keyframes pulse{
|
|
129
|
+
0% { box-shadow:0 0 0 0 rgba(245,184,78,.5); }
|
|
130
|
+
70% { box-shadow:0 0 0 8px rgba(245,184,78,0); }
|
|
131
|
+
100% { box-shadow:0 0 0 0 rgba(245,184,78,0); }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* ---------- stage layout ---------- */
|
|
135
|
+
|
|
136
|
+
.stage{
|
|
137
|
+
display:grid;
|
|
138
|
+
grid-template-columns:1fr 1fr;
|
|
139
|
+
gap:26px;
|
|
140
|
+
align-items:start;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@media (max-width:980px){
|
|
144
|
+
.stage{ grid-template-columns:1fr; }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.panel-cap{
|
|
148
|
+
font-size:11.5px;
|
|
149
|
+
letter-spacing:.02em;
|
|
150
|
+
color:var(--faint);
|
|
151
|
+
font-weight:600;
|
|
152
|
+
margin-bottom:11px;
|
|
153
|
+
padding-left:2px;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.panel-cap em{
|
|
157
|
+
font-style:normal;
|
|
158
|
+
color:var(--dim);
|
|
159
|
+
font-weight:500;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* ---------- terminal chrome (shared) ---------- */
|
|
163
|
+
|
|
164
|
+
.term{
|
|
165
|
+
background:#0d0f14;
|
|
166
|
+
border:1px solid var(--border);
|
|
167
|
+
border-radius:12px;
|
|
168
|
+
overflow:hidden;
|
|
169
|
+
box-shadow:0 24px 60px -30px rgba(0,0,0,.7), 0 2px 0 rgba(255,255,255,.02) inset;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.term-chrome{
|
|
173
|
+
display:flex;
|
|
174
|
+
align-items:center;
|
|
175
|
+
gap:6px;
|
|
176
|
+
padding:10px 12px;
|
|
177
|
+
background:var(--panel-2);
|
|
178
|
+
border-bottom:1px solid var(--border);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.term-chrome span{
|
|
182
|
+
width:9px; height:9px;
|
|
183
|
+
border-radius:50%;
|
|
184
|
+
background:#3a3f4b;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.term-chrome .path{
|
|
188
|
+
margin-left:8px;
|
|
189
|
+
font-family:var(--mono);
|
|
190
|
+
font-size:11.5px;
|
|
191
|
+
color:var(--faint);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.term-body{
|
|
195
|
+
font-family:var(--mono);
|
|
196
|
+
font-size:13px;
|
|
197
|
+
line-height:1.72;
|
|
198
|
+
padding:16px 18px;
|
|
199
|
+
color:var(--text);
|
|
200
|
+
transition:opacity .3s ease;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.term-body.fading{ opacity:0; }
|
|
204
|
+
|
|
205
|
+
.term-line{
|
|
206
|
+
white-space:pre-wrap;
|
|
207
|
+
word-break:break-word;
|
|
208
|
+
opacity:0;
|
|
209
|
+
transform:translateY(3px);
|
|
210
|
+
animation:lineIn .32s cubic-bezier(.16,1,.3,1) forwards;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
@keyframes lineIn{ to{ opacity:1; transform:none; } }
|
|
214
|
+
|
|
215
|
+
.l-prompt{ color:var(--dim); }
|
|
216
|
+
.l-prompt .ps1{ color:var(--green); margin-right:2px; }
|
|
217
|
+
.l-prompt .cmd{ color:var(--text); font-weight:600; }
|
|
218
|
+
|
|
219
|
+
.cursor{
|
|
220
|
+
display:inline-block;
|
|
221
|
+
color:var(--cyan);
|
|
222
|
+
animation:blink 1s steps(1) infinite;
|
|
223
|
+
}
|
|
224
|
+
@keyframes blink{ 50%{ opacity:0; } }
|
|
225
|
+
|
|
226
|
+
.l-hint{ color:var(--faint); margin-top:2px; }
|
|
227
|
+
.l-link{ color:var(--cyan); font-weight:650; margin-top:4px; }
|
|
228
|
+
|
|
229
|
+
.l-tool{ color:var(--dim); }
|
|
230
|
+
.l-tool .glyph{ color:var(--faint); margin-right:4px; }
|
|
231
|
+
|
|
232
|
+
.l-prose{ color:var(--text); }
|
|
233
|
+
|
|
234
|
+
.l-success{ color:var(--green); font-weight:600; }
|
|
235
|
+
|
|
236
|
+
.l-diff-add{ color:var(--green); background:rgba(126,231,135,.08); display:block; margin:0 -18px; padding:0 18px; }
|
|
237
|
+
.l-diff-del{ color:var(--red); background:rgba(255,139,133,.08); display:block; margin:0 -18px; padding:0 18px; }
|
|
238
|
+
.l-diff-ctx{ color:var(--faint); }
|
|
239
|
+
|
|
240
|
+
.spec-term .term-body{ min-height:290px; max-height:290px; overflow-y:auto; }
|
|
241
|
+
.host-term .term-body{ min-height:96px; }
|
|
242
|
+
|
|
243
|
+
/* ---------- host panel ---------- */
|
|
244
|
+
|
|
245
|
+
.panel{
|
|
246
|
+
display:flex;
|
|
247
|
+
flex-direction:column;
|
|
248
|
+
gap:16px;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.share-sheet{
|
|
252
|
+
background:var(--panel);
|
|
253
|
+
border:1px solid var(--border);
|
|
254
|
+
border-radius:12px;
|
|
255
|
+
padding:6px 18px 16px;
|
|
256
|
+
opacity:0;
|
|
257
|
+
transform:translateY(8px);
|
|
258
|
+
transition:opacity .5s cubic-bezier(.16,1,.3,1), transform .5s cubic-bezier(.16,1,.3,1);
|
|
259
|
+
}
|
|
260
|
+
.share-sheet.visible{ opacity:1; transform:none; }
|
|
261
|
+
|
|
262
|
+
.row{
|
|
263
|
+
display:flex;
|
|
264
|
+
align-items:center;
|
|
265
|
+
gap:12px;
|
|
266
|
+
padding:14px 0;
|
|
267
|
+
border-bottom:1px solid var(--border);
|
|
268
|
+
}
|
|
269
|
+
.row:last-child{ border-bottom:none; }
|
|
270
|
+
|
|
271
|
+
.row-label{
|
|
272
|
+
font-size:12.5px;
|
|
273
|
+
color:var(--faint);
|
|
274
|
+
font-weight:600;
|
|
275
|
+
width:78px;
|
|
276
|
+
flex:none;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.link-row code{
|
|
280
|
+
flex:1;
|
|
281
|
+
font-family:var(--mono);
|
|
282
|
+
font-size:14px;
|
|
283
|
+
color:var(--cyan);
|
|
284
|
+
background:var(--panel-2);
|
|
285
|
+
border:1px solid var(--border);
|
|
286
|
+
border-radius:8px;
|
|
287
|
+
padding:9px 12px;
|
|
288
|
+
overflow-x:auto;
|
|
289
|
+
white-space:nowrap;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.btn{
|
|
293
|
+
font-size:13px;
|
|
294
|
+
font-weight:600;
|
|
295
|
+
color:var(--text);
|
|
296
|
+
background:var(--panel-3);
|
|
297
|
+
border:1px solid var(--border-2);
|
|
298
|
+
border-radius:8px;
|
|
299
|
+
padding:9px 14px;
|
|
300
|
+
cursor:pointer;
|
|
301
|
+
transition:background .15s ease, border-color .15s ease, transform .1s ease;
|
|
302
|
+
flex:none;
|
|
303
|
+
}
|
|
304
|
+
.btn:hover{ background:#242933; border-color:rgba(255,255,255,.22); }
|
|
305
|
+
.btn:active{ transform:scale(.97); }
|
|
306
|
+
.btn:focus-visible{ outline:2px solid var(--cyan); outline-offset:2px; }
|
|
307
|
+
|
|
308
|
+
.btn.copied{ color:var(--green); border-color:rgba(126,231,135,.4); background:rgba(126,231,135,.08); }
|
|
309
|
+
|
|
310
|
+
/* segmented access control */
|
|
311
|
+
|
|
312
|
+
.segmented{
|
|
313
|
+
position:relative;
|
|
314
|
+
display:inline-flex;
|
|
315
|
+
background:var(--panel-2);
|
|
316
|
+
border:1px solid var(--border);
|
|
317
|
+
border-radius:9px;
|
|
318
|
+
padding:3px;
|
|
319
|
+
flex:1;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.seg-highlight{
|
|
323
|
+
position:absolute;
|
|
324
|
+
top:3px; bottom:3px; left:3px;
|
|
325
|
+
border-radius:6px;
|
|
326
|
+
background:var(--panel-3);
|
|
327
|
+
border:1px solid var(--border-2);
|
|
328
|
+
transition:transform .32s cubic-bezier(.65,0,.35,1), width .32s cubic-bezier(.65,0,.35,1), background .25s ease, border-color .25s ease;
|
|
329
|
+
z-index:0;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.seg-btn{
|
|
333
|
+
position:relative;
|
|
334
|
+
z-index:1;
|
|
335
|
+
background:transparent;
|
|
336
|
+
border:none;
|
|
337
|
+
color:var(--dim);
|
|
338
|
+
font-size:12.5px;
|
|
339
|
+
font-weight:600;
|
|
340
|
+
padding:8px 13px;
|
|
341
|
+
cursor:pointer;
|
|
342
|
+
white-space:nowrap;
|
|
343
|
+
transition:color .2s ease;
|
|
344
|
+
}
|
|
345
|
+
.seg-btn.active{ color:var(--text); }
|
|
346
|
+
.seg-btn:focus-visible{ outline:2px solid var(--cyan); outline-offset:2px; border-radius:5px; }
|
|
347
|
+
|
|
348
|
+
.segmented.mode-collab .seg-highlight{ border-color:rgba(196,181,253,.45); background:rgba(196,181,253,.12); }
|
|
349
|
+
|
|
350
|
+
.pill-group{ display:flex; gap:8px; flex:1; flex-wrap:wrap; }
|
|
351
|
+
|
|
352
|
+
.pill{
|
|
353
|
+
background:var(--panel-2);
|
|
354
|
+
border:1px solid var(--border);
|
|
355
|
+
color:var(--dim);
|
|
356
|
+
font-size:12.5px;
|
|
357
|
+
font-weight:600;
|
|
358
|
+
border-radius:7px;
|
|
359
|
+
padding:7px 12px;
|
|
360
|
+
cursor:pointer;
|
|
361
|
+
transition:all .15s ease;
|
|
362
|
+
}
|
|
363
|
+
.pill.active{ color:var(--text); background:var(--panel-3); border-color:var(--border-2); }
|
|
364
|
+
.pill:hover{ color:var(--text); }
|
|
365
|
+
.pill:focus-visible{ outline:2px solid var(--cyan); outline-offset:2px; }
|
|
366
|
+
|
|
367
|
+
/* passphrase switch */
|
|
368
|
+
|
|
369
|
+
.switch{ position:relative; display:inline-block; width:38px; height:22px; flex:none; }
|
|
370
|
+
.switch input{ opacity:0; width:0; height:0; position:absolute; }
|
|
371
|
+
.switch-track{
|
|
372
|
+
position:absolute; inset:0;
|
|
373
|
+
background:var(--panel-2);
|
|
374
|
+
border:1px solid var(--border-2);
|
|
375
|
+
border-radius:999px;
|
|
376
|
+
cursor:pointer;
|
|
377
|
+
transition:background .2s ease, border-color .2s ease;
|
|
378
|
+
}
|
|
379
|
+
.switch-track::after{
|
|
380
|
+
content:"";
|
|
381
|
+
position:absolute;
|
|
382
|
+
top:2px; left:2px;
|
|
383
|
+
width:16px; height:16px;
|
|
384
|
+
border-radius:50%;
|
|
385
|
+
background:var(--dim);
|
|
386
|
+
transition:transform .2s cubic-bezier(.65,0,.35,1), background .2s ease;
|
|
387
|
+
}
|
|
388
|
+
.switch input:checked + .switch-track{ background:rgba(245,184,78,.14); border-color:rgba(245,184,78,.45); }
|
|
389
|
+
.switch input:checked + .switch-track::after{ transform:translateX(16px); background:var(--amber); }
|
|
390
|
+
.switch input:focus-visible + .switch-track{ outline:2px solid var(--cyan); outline-offset:2px; }
|
|
391
|
+
|
|
392
|
+
.pass-input{
|
|
393
|
+
flex:1;
|
|
394
|
+
font-family:var(--mono);
|
|
395
|
+
font-size:13px;
|
|
396
|
+
color:var(--text);
|
|
397
|
+
background:var(--panel-2);
|
|
398
|
+
border:1px solid var(--border);
|
|
399
|
+
border-radius:8px;
|
|
400
|
+
padding:9px 12px;
|
|
401
|
+
transition:opacity .2s ease, border-color .2s ease;
|
|
402
|
+
}
|
|
403
|
+
.pass-input::placeholder{ color:var(--faint); }
|
|
404
|
+
.pass-input:disabled{ opacity:.45; }
|
|
405
|
+
.pass-input:focus{ outline:none; border-color:var(--amber); }
|
|
406
|
+
|
|
407
|
+
.status-row{ padding-top:14px; }
|
|
408
|
+
.live-dot{
|
|
409
|
+
width:7px; height:7px; border-radius:50%;
|
|
410
|
+
background:var(--green);
|
|
411
|
+
box-shadow:0 0 0 0 rgba(126,231,135,.5);
|
|
412
|
+
animation:pulse-g 2.4s cubic-bezier(.4,0,.2,1) infinite;
|
|
413
|
+
flex:none;
|
|
414
|
+
}
|
|
415
|
+
@keyframes pulse-g{
|
|
416
|
+
0%{ box-shadow:0 0 0 0 rgba(126,231,135,.45); }
|
|
417
|
+
70%{ box-shadow:0 0 0 7px rgba(126,231,135,0); }
|
|
418
|
+
100%{ box-shadow:0 0 0 0 rgba(126,231,135,0); }
|
|
419
|
+
}
|
|
420
|
+
#watchingText{ font-size:13px; color:var(--dim); font-family:var(--mono); }
|
|
421
|
+
#watchingText b{ color:var(--text); font-weight:650; }
|
|
422
|
+
|
|
423
|
+
/* incoming request rows */
|
|
424
|
+
|
|
425
|
+
.requests{ display:flex; flex-direction:column; }
|
|
426
|
+
|
|
427
|
+
.request-row{
|
|
428
|
+
display:flex;
|
|
429
|
+
align-items:center;
|
|
430
|
+
gap:11px;
|
|
431
|
+
padding:12px 0;
|
|
432
|
+
border-bottom:1px solid var(--border);
|
|
433
|
+
overflow:hidden;
|
|
434
|
+
max-height:60px;
|
|
435
|
+
opacity:1;
|
|
436
|
+
transform:translateX(0);
|
|
437
|
+
transition:max-height .32s cubic-bezier(.65,0,.35,1), opacity .28s ease, transform .28s ease, padding .32s ease;
|
|
438
|
+
}
|
|
439
|
+
.request-row:last-child{ border-bottom:none; }
|
|
440
|
+
.request-row.entering{ opacity:0; transform:translateX(6px); }
|
|
441
|
+
.request-row.leaving{ max-height:0; opacity:0; transform:translateX(10px); padding:0; border-color:transparent; }
|
|
442
|
+
|
|
443
|
+
.req-avatar{
|
|
444
|
+
width:26px; height:26px;
|
|
445
|
+
border-radius:50%;
|
|
446
|
+
background:var(--c, #556);
|
|
447
|
+
display:flex; align-items:center; justify-content:center;
|
|
448
|
+
font-size:11.5px; font-weight:700; color:#0a0b0f;
|
|
449
|
+
flex:none;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
.req-name{ font-size:13.5px; color:var(--text); flex:1; }
|
|
453
|
+
.req-name span{ color:var(--faint); font-weight:450; }
|
|
454
|
+
|
|
455
|
+
.req-actions{ display:flex; gap:8px; }
|
|
456
|
+
|
|
457
|
+
.req-btn{
|
|
458
|
+
font-size:12px; font-weight:650;
|
|
459
|
+
border-radius:7px;
|
|
460
|
+
padding:6px 11px;
|
|
461
|
+
cursor:pointer;
|
|
462
|
+
background:transparent;
|
|
463
|
+
transition:all .15s ease;
|
|
464
|
+
}
|
|
465
|
+
.req-approve{ border:1px solid rgba(126,231,135,.4); color:var(--green); }
|
|
466
|
+
.req-approve:hover{ background:rgba(126,231,135,.1); }
|
|
467
|
+
.req-deny{ border:1px solid rgba(255,139,133,.35); color:var(--red); }
|
|
468
|
+
.req-deny:hover{ background:rgba(255,139,133,.1); }
|
|
469
|
+
.req-btn:focus-visible{ outline:2px solid var(--cyan); outline-offset:2px; }
|
|
470
|
+
|
|
471
|
+
/* ---------- spectator panel ---------- */
|
|
472
|
+
|
|
473
|
+
.spectator-meta{
|
|
474
|
+
display:flex;
|
|
475
|
+
align-items:center;
|
|
476
|
+
gap:14px;
|
|
477
|
+
flex-wrap:wrap;
|
|
478
|
+
margin-bottom:2px;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.spec-badge{
|
|
482
|
+
display:inline-flex; align-items:center; gap:6px;
|
|
483
|
+
font-size:11.5px; font-weight:700; letter-spacing:.03em;
|
|
484
|
+
color:var(--cyan);
|
|
485
|
+
background:rgba(103,232,249,.1);
|
|
486
|
+
border:1px solid rgba(103,232,249,.3);
|
|
487
|
+
border-radius:999px;
|
|
488
|
+
padding:5px 11px;
|
|
489
|
+
transition:all .3s ease;
|
|
490
|
+
}
|
|
491
|
+
.spec-badge.collab{
|
|
492
|
+
color:var(--violet);
|
|
493
|
+
background:rgba(196,181,253,.12);
|
|
494
|
+
border-color:rgba(196,181,253,.35);
|
|
495
|
+
}
|
|
496
|
+
.spec-badge .b-dot{ width:6px; height:6px; border-radius:50%; background:currentColor; }
|
|
497
|
+
|
|
498
|
+
.viewers{ font-size:12.5px; color:var(--dim); font-family:var(--mono); }
|
|
499
|
+
.viewers b{ color:var(--text); }
|
|
500
|
+
|
|
501
|
+
.presence{ display:flex; margin-left:auto; }
|
|
502
|
+
.presence-avatar{
|
|
503
|
+
width:24px; height:24px; border-radius:50%;
|
|
504
|
+
background:var(--c,#556);
|
|
505
|
+
border:2px solid var(--bg);
|
|
506
|
+
margin-left:-8px;
|
|
507
|
+
display:flex; align-items:center; justify-content:center;
|
|
508
|
+
font-size:10px; font-weight:700; color:#0a0b0f;
|
|
509
|
+
transition:transform .25s cubic-bezier(.34,1.56,.64,1), opacity .25s ease;
|
|
510
|
+
}
|
|
511
|
+
.presence-avatar:first-child{ margin-left:0; }
|
|
512
|
+
.presence-avatar.entering{ transform:scale(0); opacity:0; }
|
|
513
|
+
|
|
514
|
+
.join-btn{
|
|
515
|
+
align-self:flex-start;
|
|
516
|
+
background:var(--violet-dim);
|
|
517
|
+
border:1px solid rgba(196,181,253,.5);
|
|
518
|
+
color:#f2eeff;
|
|
519
|
+
font-size:13.5px;
|
|
520
|
+
font-weight:650;
|
|
521
|
+
border-radius:9px;
|
|
522
|
+
padding:11px 18px;
|
|
523
|
+
cursor:pointer;
|
|
524
|
+
transition:background .15s ease, transform .1s ease, opacity .2s ease;
|
|
525
|
+
}
|
|
526
|
+
.join-btn:hover{ background:#453a78; }
|
|
527
|
+
.join-btn:active{ transform:scale(.98); }
|
|
528
|
+
.join-btn:focus-visible{ outline:2px solid var(--violet); outline-offset:2px; }
|
|
529
|
+
.join-btn:disabled{ cursor:default; opacity:.75; }
|
|
530
|
+
.join-btn.pending{ background:var(--panel-3); border-color:var(--border-2); color:var(--dim); }
|
|
531
|
+
.join-btn.joined{ background:rgba(126,231,135,.12); border-color:rgba(126,231,135,.4); color:var(--green); cursor:default; }
|
|
532
|
+
|
|
533
|
+
.handoff-note{
|
|
534
|
+
font-size:12px; color:var(--faint); font-family:var(--mono);
|
|
535
|
+
margin-top:-6px;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
@media (max-width:520px){
|
|
539
|
+
.app{ padding:20px 16px 40px; }
|
|
540
|
+
.row{ flex-wrap:wrap; }
|
|
541
|
+
.row-label{ width:auto; }
|
|
542
|
+
}
|
|
543
|
+
</style>
|
|
544
|
+
</head>
|
|
545
|
+
<body>
|
|
546
|
+
<div class="app">
|
|
547
|
+
|
|
548
|
+
<header class="topbar">
|
|
549
|
+
<div class="brand">
|
|
550
|
+
<span class="brand-mark">◆</span>
|
|
551
|
+
<span class="brand-name">vibeshare</span>
|
|
552
|
+
<span class="brand-sub">part of the Vibe Suite · built on vibelive</span>
|
|
553
|
+
</div>
|
|
554
|
+
<div class="p2p-badge"><span class="live-pulse"></span> <b>p2p</b> · nothing stored on a server</div>
|
|
555
|
+
</header>
|
|
556
|
+
|
|
557
|
+
<main class="stage">
|
|
558
|
+
|
|
559
|
+
<!-- HOST -->
|
|
560
|
+
<section class="panel">
|
|
561
|
+
<div class="panel-cap">host <em>— your machine</em></div>
|
|
562
|
+
|
|
563
|
+
<div class="term host-term">
|
|
564
|
+
<div class="term-chrome"><span></span><span></span><span></span><span class="path">zsh</span></div>
|
|
565
|
+
<div class="term-body" id="hostTermBody">
|
|
566
|
+
<div class="term-line l-prompt" style="animation-delay:0s"><span class="ps1">➜</span> <span class="cmd" id="hostCmd"></span><span class="cursor" id="hostCursor">▍</span></div>
|
|
567
|
+
</div>
|
|
568
|
+
</div>
|
|
569
|
+
|
|
570
|
+
<div class="share-sheet" id="shareSheet">
|
|
571
|
+
|
|
572
|
+
<div class="row link-row">
|
|
573
|
+
<code id="linkText">vibe.live/s/9fx2</code>
|
|
574
|
+
<button class="btn" id="copyBtn">Copy link</button>
|
|
575
|
+
</div>
|
|
576
|
+
|
|
577
|
+
<div class="row access-row">
|
|
578
|
+
<span class="row-label">Access</span>
|
|
579
|
+
<div class="segmented" id="accessToggle">
|
|
580
|
+
<span class="seg-highlight" id="segHighlight"></span>
|
|
581
|
+
<button class="seg-btn active" data-mode="spectate" id="segSpectate">Spectate read-only</button>
|
|
582
|
+
<button class="seg-btn" data-mode="collab" id="segCollab">Invite to collaborate</button>
|
|
583
|
+
</div>
|
|
584
|
+
</div>
|
|
585
|
+
|
|
586
|
+
<div class="row expiry-row">
|
|
587
|
+
<span class="row-label">Expires</span>
|
|
588
|
+
<div class="pill-group" id="expiryGroup">
|
|
589
|
+
<button class="pill" data-exp="1h">1h</button>
|
|
590
|
+
<button class="pill" data-exp="24h">24h</button>
|
|
591
|
+
<button class="pill active" data-exp="stop">Until I stop</button>
|
|
592
|
+
</div>
|
|
593
|
+
</div>
|
|
594
|
+
|
|
595
|
+
<div class="row passphrase-row">
|
|
596
|
+
<span class="row-label">Passphrase</span>
|
|
597
|
+
<label class="switch">
|
|
598
|
+
<input type="checkbox" id="passToggle">
|
|
599
|
+
<span class="switch-track"></span>
|
|
600
|
+
</label>
|
|
601
|
+
<input type="text" class="pass-input" id="passInput" placeholder="optional — none set" disabled>
|
|
602
|
+
</div>
|
|
603
|
+
|
|
604
|
+
<div class="row status-row">
|
|
605
|
+
<span class="live-dot"></span>
|
|
606
|
+
<span id="watchingText">👁 <b>3</b> watching · <b>1</b> requested to join</span>
|
|
607
|
+
</div>
|
|
608
|
+
|
|
609
|
+
<div class="requests" id="requestList"></div>
|
|
610
|
+
|
|
611
|
+
</div>
|
|
612
|
+
</section>
|
|
613
|
+
|
|
614
|
+
<!-- SPECTATOR -->
|
|
615
|
+
<section class="panel">
|
|
616
|
+
<div class="panel-cap">spectator view <em>— what opening the link shows</em></div>
|
|
617
|
+
|
|
618
|
+
<div class="spectator-meta">
|
|
619
|
+
<span class="spec-badge" id="specBadge"><span class="b-dot"></span> SPECTATING · read-only</span>
|
|
620
|
+
<span class="viewers" id="viewerText">👁 <b>3</b> watching</span>
|
|
621
|
+
<div class="presence" id="presenceDots">
|
|
622
|
+
<span class="presence-avatar" style="--c:#f2b84b" title="Maya">M</span>
|
|
623
|
+
<span class="presence-avatar" style="--c:#8ad1ff" title="Théo">T</span>
|
|
624
|
+
</div>
|
|
625
|
+
</div>
|
|
626
|
+
|
|
627
|
+
<div class="term spec-term">
|
|
628
|
+
<div class="term-chrome"><span></span><span></span><span></span><span class="path">claude — agent session</span></div>
|
|
629
|
+
<div class="term-body" id="specTermBody"></div>
|
|
630
|
+
</div>
|
|
631
|
+
|
|
632
|
+
<button class="join-btn" id="joinBtn">Request to join</button>
|
|
633
|
+
<div class="handoff-note" id="handoffNote" style="display:none"></div>
|
|
634
|
+
</section>
|
|
635
|
+
|
|
636
|
+
</main>
|
|
637
|
+
</div>
|
|
638
|
+
|
|
639
|
+
<script>
|
|
640
|
+
(function(){
|
|
641
|
+
"use strict";
|
|
642
|
+
|
|
643
|
+
/* ---------------- host: type the command, reveal link + sheet ---------------- */
|
|
644
|
+
|
|
645
|
+
const hostCmd = document.getElementById('hostCmd');
|
|
646
|
+
const hostCursor = document.getElementById('hostCursor');
|
|
647
|
+
const hostTermBody = document.getElementById('hostTermBody');
|
|
648
|
+
const shareSheet = document.getElementById('shareSheet');
|
|
649
|
+
|
|
650
|
+
function el(tag, cls, html){
|
|
651
|
+
const n = document.createElement(tag);
|
|
652
|
+
if(cls) n.className = cls;
|
|
653
|
+
if(html !== undefined) n.innerHTML = html;
|
|
654
|
+
return n;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function typeText(node, text, speed, cb){
|
|
658
|
+
let i = 0;
|
|
659
|
+
(function step(){
|
|
660
|
+
node.textContent = text.slice(0, i);
|
|
661
|
+
if(i < text.length){ i++; setTimeout(step, speed + Math.random()*30); }
|
|
662
|
+
else if(cb){ cb(); }
|
|
663
|
+
})();
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function appendHostLine(cls, html, delay){
|
|
667
|
+
const line = el('div', 'term-line ' + cls);
|
|
668
|
+
line.innerHTML = html;
|
|
669
|
+
line.style.animationDelay = '0s';
|
|
670
|
+
hostTermBody.appendChild(line);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
typeText(hostCmd, 'vibeshare', 85, function(){
|
|
674
|
+
hostCursor.style.animation = 'none';
|
|
675
|
+
hostCursor.textContent = '';
|
|
676
|
+
setTimeout(function(){
|
|
677
|
+
appendHostLine('l-hint', 'streaming this session peer-to-peer · no server storage');
|
|
678
|
+
setTimeout(function(){
|
|
679
|
+
appendHostLine('l-link', 'vibe.live/s/9fx2 ready');
|
|
680
|
+
setTimeout(function(){ shareSheet.classList.add('visible'); showFirstRequest(); }, 500);
|
|
681
|
+
}, 480);
|
|
682
|
+
}, 350);
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
/* ---------------- copy link ---------------- */
|
|
686
|
+
|
|
687
|
+
const copyBtn = document.getElementById('copyBtn');
|
|
688
|
+
const linkText = document.getElementById('linkText').textContent;
|
|
689
|
+
|
|
690
|
+
copyBtn.addEventListener('click', function(){
|
|
691
|
+
function flash(){
|
|
692
|
+
copyBtn.textContent = 'Copied ✓';
|
|
693
|
+
copyBtn.classList.add('copied');
|
|
694
|
+
setTimeout(function(){
|
|
695
|
+
copyBtn.textContent = 'Copy link';
|
|
696
|
+
copyBtn.classList.remove('copied');
|
|
697
|
+
}, 1600);
|
|
698
|
+
}
|
|
699
|
+
if(navigator.clipboard && navigator.clipboard.writeText){
|
|
700
|
+
navigator.clipboard.writeText(linkText).then(flash).catch(fallbackCopy);
|
|
701
|
+
} else {
|
|
702
|
+
fallbackCopy();
|
|
703
|
+
}
|
|
704
|
+
function fallbackCopy(){
|
|
705
|
+
const ta = document.createElement('textarea');
|
|
706
|
+
ta.value = linkText;
|
|
707
|
+
ta.style.position = 'fixed';
|
|
708
|
+
ta.style.opacity = '0';
|
|
709
|
+
document.body.appendChild(ta);
|
|
710
|
+
ta.select();
|
|
711
|
+
try{ document.execCommand('copy'); }catch(e){}
|
|
712
|
+
document.body.removeChild(ta);
|
|
713
|
+
flash();
|
|
714
|
+
}
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
/* ---------------- access segmented control ---------------- */
|
|
718
|
+
|
|
719
|
+
const segmented = document.getElementById('accessToggle');
|
|
720
|
+
const segHighlight = document.getElementById('segHighlight');
|
|
721
|
+
const segSpectate = document.getElementById('segSpectate');
|
|
722
|
+
const segCollab = document.getElementById('segCollab');
|
|
723
|
+
|
|
724
|
+
function positionHighlight(activeBtn){
|
|
725
|
+
segHighlight.style.width = activeBtn.offsetWidth + 'px';
|
|
726
|
+
segHighlight.style.transform = 'translateX(' + activeBtn.offsetLeft + 'px)';
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function setMode(mode){
|
|
730
|
+
const active = mode === 'collab' ? segCollab : segSpectate;
|
|
731
|
+
[segSpectate, segCollab].forEach(function(b){ b.classList.toggle('active', b === active); });
|
|
732
|
+
segmented.classList.toggle('mode-collab', mode === 'collab');
|
|
733
|
+
positionHighlight(active);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
segSpectate.addEventListener('click', function(){ setMode('spectate'); });
|
|
737
|
+
segCollab.addEventListener('click', function(){ setMode('collab'); });
|
|
738
|
+
window.addEventListener('resize', function(){
|
|
739
|
+
positionHighlight(segmented.querySelector('.seg-btn.active'));
|
|
740
|
+
});
|
|
741
|
+
requestAnimationFrame(function(){ setMode('spectate'); });
|
|
742
|
+
|
|
743
|
+
/* ---------------- expiry pills ---------------- */
|
|
744
|
+
|
|
745
|
+
document.getElementById('expiryGroup').addEventListener('click', function(e){
|
|
746
|
+
const btn = e.target.closest('.pill');
|
|
747
|
+
if(!btn) return;
|
|
748
|
+
this.querySelectorAll('.pill').forEach(function(p){ p.classList.remove('active'); });
|
|
749
|
+
btn.classList.add('active');
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
/* ---------------- passphrase switch ---------------- */
|
|
753
|
+
|
|
754
|
+
const passToggle = document.getElementById('passToggle');
|
|
755
|
+
const passInput = document.getElementById('passInput');
|
|
756
|
+
passToggle.addEventListener('change', function(){
|
|
757
|
+
passInput.disabled = !this.checked;
|
|
758
|
+
passInput.placeholder = this.checked ? 'e.g. river-otter-42' : 'optional — none set';
|
|
759
|
+
if(this.checked) passInput.focus();
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
/* ---------------- shared viewer/request state ---------------- */
|
|
763
|
+
|
|
764
|
+
const state = { watching: 3, pending: [] };
|
|
765
|
+
|
|
766
|
+
const watchingText = document.getElementById('watchingText');
|
|
767
|
+
const viewerText = document.getElementById('viewerText');
|
|
768
|
+
const requestList = document.getElementById('requestList');
|
|
769
|
+
const presenceDots = document.getElementById('presenceDots');
|
|
770
|
+
|
|
771
|
+
const AVATAR_COLORS = { nick: '#7ee787', you: '#c4b5fd' };
|
|
772
|
+
|
|
773
|
+
function renderCounts(){
|
|
774
|
+
watchingText.innerHTML = '👁 <b>' + state.watching + '</b> watching' +
|
|
775
|
+
(state.pending.length ? ' · <b>' + state.pending.length + '</b> requested to join' : '');
|
|
776
|
+
viewerText.innerHTML = '👁 <b>' + state.watching + '</b> watching';
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function renderRequestRow(req){
|
|
780
|
+
const row = el('div', 'request-row entering');
|
|
781
|
+
row.dataset.id = req.id;
|
|
782
|
+
row.innerHTML =
|
|
783
|
+
'<span class="req-avatar" style="--c:' + req.color + '">' + req.initial + '</span>' +
|
|
784
|
+
'<span class="req-name">' + req.name + ' <span>wants to join</span></span>' +
|
|
785
|
+
'<span class="req-actions">' +
|
|
786
|
+
'<button class="req-btn req-approve">Approve</button>' +
|
|
787
|
+
'<button class="req-btn req-deny">Deny</button>' +
|
|
788
|
+
'</span>';
|
|
789
|
+
requestList.appendChild(row);
|
|
790
|
+
requestAnimationFrame(() => requestAnimationFrame(() => row.classList.remove('entering')));
|
|
791
|
+
|
|
792
|
+
row.querySelector('.req-approve').addEventListener('click', function(){ resolveRequest(req.id, true); });
|
|
793
|
+
row.querySelector('.req-deny').addEventListener('click', function(){ resolveRequest(req.id, false); });
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function addRequest(id, name){
|
|
797
|
+
if(state.pending.some(r => r.id === id)) return;
|
|
798
|
+
const req = { id: id, name: name, initial: name[0].toUpperCase(), color: AVATAR_COLORS[id] || '#8ad1ff' };
|
|
799
|
+
state.pending.push(req);
|
|
800
|
+
renderRequestRow(req);
|
|
801
|
+
renderCounts();
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
function resolveRequest(id, approved){
|
|
805
|
+
const row = requestList.querySelector('.request-row[data-id="' + id + '"]');
|
|
806
|
+
const req = state.pending.find(r => r.id === id);
|
|
807
|
+
state.pending = state.pending.filter(r => r.id !== id);
|
|
808
|
+
if(row){
|
|
809
|
+
row.classList.add('leaving');
|
|
810
|
+
setTimeout(() => row.remove(), 340);
|
|
811
|
+
}
|
|
812
|
+
if(approved && req){
|
|
813
|
+
state.watching++;
|
|
814
|
+
addPresenceDot(req);
|
|
815
|
+
if(id === 'you') onYouJoined();
|
|
816
|
+
} else if(!approved && id === 'you'){
|
|
817
|
+
onYouDenied();
|
|
818
|
+
}
|
|
819
|
+
renderCounts();
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function addPresenceDot(req){
|
|
823
|
+
const dot = el('span', 'presence-avatar entering');
|
|
824
|
+
dot.style.setProperty('--c', req.color);
|
|
825
|
+
dot.title = req.name;
|
|
826
|
+
dot.textContent = req.initial;
|
|
827
|
+
presenceDots.appendChild(dot);
|
|
828
|
+
requestAnimationFrame(() => requestAnimationFrame(() => dot.classList.remove('entering')));
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function showFirstRequest(){
|
|
832
|
+
addRequest('nick', 'Nick');
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/* ---------------- spectator: request to join → handoff to vibelive ---------------- */
|
|
836
|
+
|
|
837
|
+
const joinBtn = document.getElementById('joinBtn');
|
|
838
|
+
const specBadge = document.getElementById('specBadge');
|
|
839
|
+
const handoffNote = document.getElementById('handoffNote');
|
|
840
|
+
|
|
841
|
+
joinBtn.addEventListener('click', function(){
|
|
842
|
+
if(joinBtn.disabled) return;
|
|
843
|
+
joinBtn.disabled = true;
|
|
844
|
+
joinBtn.classList.add('pending');
|
|
845
|
+
joinBtn.textContent = 'Waiting for host approval…';
|
|
846
|
+
addRequest('you', 'You');
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
function onYouJoined(){
|
|
850
|
+
joinBtn.classList.remove('pending');
|
|
851
|
+
joinBtn.classList.add('joined');
|
|
852
|
+
joinBtn.textContent = 'You’re in — live';
|
|
853
|
+
specBadge.classList.add('collab');
|
|
854
|
+
specBadge.innerHTML = '<span class="b-dot"></span> COLLABORATING · live';
|
|
855
|
+
handoffNote.style.display = 'block';
|
|
856
|
+
handoffNote.textContent = '→ handed off to vibelive: you now share control of this terminal.';
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function onYouDenied(){
|
|
860
|
+
joinBtn.disabled = false;
|
|
861
|
+
joinBtn.classList.remove('pending');
|
|
862
|
+
joinBtn.textContent = 'Request denied — try again';
|
|
863
|
+
setTimeout(function(){ joinBtn.textContent = 'Request to join'; }, 2200);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
/* ---------------- ambient watcher-count drift ---------------- */
|
|
867
|
+
|
|
868
|
+
setInterval(function(){
|
|
869
|
+
if(Math.random() < 0.5) return;
|
|
870
|
+
const delta = Math.random() < 0.5 ? -1 : 1;
|
|
871
|
+
state.watching = Math.max(2, Math.min(7, state.watching + delta));
|
|
872
|
+
renderCounts();
|
|
873
|
+
}, 5200);
|
|
874
|
+
|
|
875
|
+
renderCounts();
|
|
876
|
+
|
|
877
|
+
/* ---------------- spectator terminal: streaming agent session, looping ---------------- */
|
|
878
|
+
|
|
879
|
+
const specBody = document.getElementById('specTermBody');
|
|
880
|
+
|
|
881
|
+
const beats = [
|
|
882
|
+
{ type:'prose', text:'Checking how the rate limiter handles bursts before touching retry logic.' },
|
|
883
|
+
{ type:'tool', text:'Read src/lib/rateLimiter.ts' },
|
|
884
|
+
{ type:'tool', text:'Grep "maxRetries" src/' },
|
|
885
|
+
{ type:'prose', text:'Found it — the backoff multiplier keeps doubling even after a request succeeds. Fixing that.' },
|
|
886
|
+
{ type:'tool', text:'Edit src/lib/rateLimiter.ts' },
|
|
887
|
+
{ type:'diff', lines:[
|
|
888
|
+
{ t:'ctx', text:' function nextDelay(attempt, res) {' },
|
|
889
|
+
{ t:'del', text:'- if (attempt > 0) backoff *= 2' },
|
|
890
|
+
{ t:'add', text:'+ if (attempt > 0 && !res.ok) backoff *= 2' },
|
|
891
|
+
{ t:'add', text:'+ else backoff = baseDelay' },
|
|
892
|
+
{ t:'ctx', text:' }' }
|
|
893
|
+
]},
|
|
894
|
+
{ type:'tool', text:'Bash pnpm test rateLimiter' },
|
|
895
|
+
{ type:'prose', text:'12 passed, 0 failed.' },
|
|
896
|
+
{ type:'success', text:'✓ PR #42 opened · fix/rate-limiter-backoff' }
|
|
897
|
+
];
|
|
898
|
+
|
|
899
|
+
function scrollBottom(){ specBody.scrollTop = specBody.scrollHeight; }
|
|
900
|
+
|
|
901
|
+
function appendSpecLine(cls, html){
|
|
902
|
+
const line = el('div', 'term-line ' + cls);
|
|
903
|
+
line.innerHTML = html;
|
|
904
|
+
specBody.appendChild(line);
|
|
905
|
+
scrollBottom();
|
|
906
|
+
return line;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
function typeSpecLine(text, cb){
|
|
910
|
+
const line = el('div', 'term-line l-prose');
|
|
911
|
+
specBody.appendChild(line);
|
|
912
|
+
let i = 0;
|
|
913
|
+
(function step(){
|
|
914
|
+
line.innerHTML = escapeHtml(text.slice(0, i)) + (i < text.length ? '<span class="cursor">▍</span>' : '');
|
|
915
|
+
scrollBottom();
|
|
916
|
+
if(i < text.length){ i++; setTimeout(step, 16 + Math.random()*20); }
|
|
917
|
+
else if(cb){ setTimeout(cb, 450); }
|
|
918
|
+
})();
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
function escapeHtml(s){
|
|
922
|
+
return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
function runBeat(idx){
|
|
926
|
+
if(idx >= beats.length){
|
|
927
|
+
setTimeout(function(){
|
|
928
|
+
specBody.classList.add('fading');
|
|
929
|
+
setTimeout(function(){
|
|
930
|
+
specBody.innerHTML = '';
|
|
931
|
+
specBody.classList.remove('fading');
|
|
932
|
+
runBeat(0);
|
|
933
|
+
}, 320);
|
|
934
|
+
}, 2600);
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
const b = beats[idx];
|
|
938
|
+
if(b.type === 'prose'){
|
|
939
|
+
typeSpecLine(b.text, function(){ runBeat(idx + 1); });
|
|
940
|
+
} else if(b.type === 'tool'){
|
|
941
|
+
appendSpecLine('l-tool', '<span class="glyph">›</span>' + escapeHtml(b.text));
|
|
942
|
+
setTimeout(function(){ runBeat(idx + 1); }, 480);
|
|
943
|
+
} else if(b.type === 'success'){
|
|
944
|
+
appendSpecLine('l-success', escapeHtml(b.text));
|
|
945
|
+
setTimeout(function(){ runBeat(idx + 1); }, 500);
|
|
946
|
+
} else if(b.type === 'diff'){
|
|
947
|
+
let i = 0;
|
|
948
|
+
(function nextLine(){
|
|
949
|
+
if(i >= b.lines.length){ setTimeout(function(){ runBeat(idx + 1); }, 400); return; }
|
|
950
|
+
const d = b.lines[i];
|
|
951
|
+
const cls = d.t === 'add' ? 'l-diff-add' : d.t === 'del' ? 'l-diff-del' : 'l-diff-ctx';
|
|
952
|
+
appendSpecLine(cls, escapeHtml(d.text));
|
|
953
|
+
i++;
|
|
954
|
+
setTimeout(nextLine, 150);
|
|
955
|
+
})();
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
runBeat(0);
|
|
960
|
+
|
|
961
|
+
})();
|
|
962
|
+
</script>
|
|
963
|
+
</body>
|
|
964
|
+
</html>
|