vibeshare-live 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,673 @@
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.0">
6
+ <title>vibeshare — launch video (9:16)</title>
7
+ <!--
8
+ vibeshare launch video — 9:16 vertical master
9
+ · Fixed 1080x1920 stage, auto-scaled to any window (letterboxed). Record the window at any size.
10
+ · ~20.5s, plays on load, loops seamlessly.
11
+ · Self-contained: inline CSS/JS/SVG only. No network, no CDN, no webfonts.
12
+ · Timeline (nothing ever overlaps):
13
+ 0.0–15.1s iMessage thread types itself inside the phone
14
+ 15.1–16.0s phone exits completely (fades/blurs out, then visibility:hidden)
15
+ 16.0–19.6s end card alone: mark, wordmark, tagline, install pill, Vibe Suite
16
+ 19.6–20.5s wipe, reset, loop
17
+ · Tip: open with #t=SECONDS (e.g. ...html#t=18.5) to jump to an exact frame for stills.
18
+ -->
19
+ <style>
20
+ :root {
21
+ --blue: #0a84ff;
22
+ --gray-bubble: #303033;
23
+ --chat-bg: #000;
24
+ --green: #5be49b;
25
+ }
26
+
27
+ * { margin: 0; padding: 0; box-sizing: border-box; }
28
+
29
+ html, body { height: 100%; }
30
+
31
+ body {
32
+ font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
33
+ background: #000;
34
+ overflow: hidden;
35
+ -webkit-font-smoothing: antialiased;
36
+ }
37
+
38
+ /* ---- fixed 1080x1920 stage, scaled to fit the window ---- */
39
+ .stage {
40
+ position: fixed;
41
+ left: 50%;
42
+ top: 50%;
43
+ width: 1080px;
44
+ height: 1920px;
45
+ transform: translate(-50%, -50%);
46
+ transform-origin: center center;
47
+ background: #07070a;
48
+ overflow: hidden;
49
+ }
50
+
51
+ /* instant-frame mode (#t=N): freeze every animation/transition at its end state */
52
+ .stage.snap *, .stage.snap *::before, .stage.snap *::after {
53
+ animation-duration: 0s !important;
54
+ animation-delay: 0s !important;
55
+ transition-duration: 0s !important;
56
+ transition-delay: 0s !important;
57
+ }
58
+
59
+ /* ---- ambient backdrop ---- */
60
+ .glow {
61
+ position: absolute;
62
+ border-radius: 50%;
63
+ filter: blur(140px);
64
+ pointer-events: none;
65
+ transition: opacity 1.4s ease;
66
+ }
67
+ .glow.g1 {
68
+ width: 1100px; height: 1100px;
69
+ top: -420px; left: -420px;
70
+ background: radial-gradient(circle, #123a7a 0%, transparent 65%);
71
+ opacity: 0.35;
72
+ animation: drift1 19s ease-in-out infinite alternate;
73
+ }
74
+ .glow.g2 {
75
+ width: 1150px; height: 1150px;
76
+ bottom: -460px; right: -460px;
77
+ background: radial-gradient(circle, #3a1a6e 0%, transparent 65%);
78
+ opacity: 0.35;
79
+ animation: drift2 23s ease-in-out infinite alternate;
80
+ }
81
+ /* end-card halo: only appears once the end card owns the stage */
82
+ .glow.g3 {
83
+ width: 1250px; height: 1250px;
84
+ left: 50%; top: 50%;
85
+ margin: -625px 0 0 -625px;
86
+ background: radial-gradient(circle, rgba(47,139,255,0.5) 0%, rgba(138,92,255,0.3) 45%, transparent 70%);
87
+ opacity: 0;
88
+ }
89
+ .stage.card .g1 { opacity: 0.2; }
90
+ .stage.card .g2 { opacity: 0.24; }
91
+ .stage.card .g3 { opacity: 0.5; }
92
+ @keyframes drift1 { to { transform: translate(120px, 150px) scale(1.12); } }
93
+ @keyframes drift2 { to { transform: translate(-110px, -140px) scale(1.08); } }
94
+
95
+ .vignette {
96
+ position: absolute;
97
+ inset: 0;
98
+ background: radial-gradient(ellipse at center, transparent 52%, rgba(0,0,0,0.55) 100%);
99
+ pointer-events: none;
100
+ }
101
+
102
+ /* ---- phone ---- */
103
+ .phone-wrap {
104
+ position: absolute;
105
+ left: 50%;
106
+ top: 50%;
107
+ margin: -825px 0 0 -380px; /* half of 1650 / 760 */
108
+ animation: rise 0.75s cubic-bezier(0.22, 1, 0.36, 1) 0.15s both;
109
+ transition: transform 0.62s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.55s ease, filter 0.62s ease;
110
+ }
111
+ .phone-wrap.out {
112
+ transform: scale(0.93) translateY(36px);
113
+ opacity: 0;
114
+ filter: blur(20px);
115
+ }
116
+ .phone-wrap.gone { visibility: hidden; }
117
+ @keyframes rise {
118
+ from { opacity: 0; transform: translateY(50px) scale(0.96); }
119
+ to { opacity: 1; transform: translateY(0) scale(1); }
120
+ }
121
+
122
+ .phone {
123
+ width: 760px;
124
+ height: 1650px;
125
+ background: #141416;
126
+ border-radius: 96px;
127
+ padding: 20px;
128
+ box-shadow:
129
+ 0 0 0 1.5px rgba(255,255,255,0.09),
130
+ 0 60px 150px rgba(0,0,0,0.78),
131
+ 0 0 180px rgba(30,80,200,0.13);
132
+ animation: floaty 9s ease-in-out 1.2s infinite alternate;
133
+ }
134
+ @keyframes floaty {
135
+ from { transform: translateY(-9px); }
136
+ to { transform: translateY(9px); }
137
+ }
138
+
139
+ .screen {
140
+ width: 100%;
141
+ height: 100%;
142
+ background: var(--chat-bg);
143
+ border-radius: 78px;
144
+ overflow: hidden;
145
+ display: flex;
146
+ flex-direction: column;
147
+ }
148
+
149
+ /* ---- status bar + nav ---- */
150
+ .statusbar {
151
+ display: flex;
152
+ justify-content: space-between;
153
+ align-items: center;
154
+ padding: 34px 54px 10px;
155
+ color: #fff;
156
+ font-size: 26px;
157
+ font-weight: 600;
158
+ letter-spacing: 0.01em;
159
+ }
160
+ .statusbar .icons { display: flex; align-items: center; gap: 12px; }
161
+
162
+ .nav {
163
+ display: flex;
164
+ align-items: center;
165
+ justify-content: space-between;
166
+ padding: 10px 30px 20px;
167
+ border-bottom: 1.5px solid rgba(255,255,255,0.08);
168
+ }
169
+ .nav .side { width: 80px; display: flex; align-items: center; }
170
+ .nav .side.right { justify-content: flex-end; }
171
+ .nav .contact { display: flex; flex-direction: column; align-items: center; gap: 7px; }
172
+ .avatar {
173
+ width: 84px; height: 84px;
174
+ border-radius: 50%;
175
+ background: linear-gradient(135deg, #5e5ce6 0%, #bf5af2 100%);
176
+ display: grid;
177
+ place-items: center;
178
+ color: #fff;
179
+ font-size: 34px;
180
+ font-weight: 600;
181
+ }
182
+ .nav .name {
183
+ color: #eaeaee;
184
+ font-size: 21px;
185
+ font-weight: 500;
186
+ letter-spacing: 0.02em;
187
+ display: flex;
188
+ align-items: center;
189
+ gap: 4px;
190
+ }
191
+ .nav .name svg { opacity: 0.5; }
192
+
193
+ /* ---- thread ---- */
194
+ .ts {
195
+ text-align: center;
196
+ color: #8e8e93;
197
+ font-size: 20px;
198
+ line-height: 1.5;
199
+ padding: 20px 0 4px;
200
+ }
201
+ .ts b { color: #aeaeb2; font-weight: 600; }
202
+
203
+ .thread {
204
+ flex: 1;
205
+ display: flex;
206
+ flex-direction: column;
207
+ justify-content: flex-start;
208
+ gap: 17px;
209
+ padding: 16px 0 24px;
210
+ overflow: hidden;
211
+ }
212
+ .row { display: flex; margin: 0 26px; }
213
+ .row.a { justify-content: flex-start; }
214
+ .row.b { justify-content: flex-end; }
215
+
216
+ .bubble {
217
+ position: relative;
218
+ max-width: 75%;
219
+ padding: 16px 25px;
220
+ border-radius: 34px;
221
+ font-size: 32px;
222
+ line-height: 1.32;
223
+ letter-spacing: -0.005em;
224
+ color: #fff;
225
+ animation: pop 0.5s cubic-bezier(0.3, 1.45, 0.5, 1) both;
226
+ }
227
+ .row.a .bubble { transform-origin: left bottom; }
228
+ .row.b .bubble { transform-origin: right bottom; }
229
+ @keyframes pop {
230
+ 0% { transform: scale(0.3) translateY(26px); opacity: 0; }
231
+ 100% { transform: scale(1) translateY(0); opacity: 1; }
232
+ }
233
+
234
+ .bubble.gray {
235
+ background: var(--gray-bubble);
236
+ border-bottom-left-radius: 10px;
237
+ }
238
+ .bubble.blue {
239
+ background: var(--blue);
240
+ border-bottom-right-radius: 10px;
241
+ }
242
+ .bubble.mono {
243
+ font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
244
+ font-size: 25.5px;
245
+ line-height: 1.45;
246
+ letter-spacing: 0;
247
+ }
248
+ .bcur {
249
+ display: inline-block;
250
+ width: 13px; height: 25px;
251
+ background: rgba(255,255,255,0.92);
252
+ vertical-align: -4px;
253
+ margin-left: 3px;
254
+ animation: blink 0.9s steps(1) infinite;
255
+ }
256
+
257
+ /* tails (classic notch trick: bump + chat-colored carver) */
258
+ .bubble.gray::before,
259
+ .bubble.blue::before {
260
+ content: "";
261
+ position: absolute;
262
+ bottom: 0;
263
+ width: 30px;
264
+ height: 25px;
265
+ }
266
+ .bubble.gray::before {
267
+ left: -10px;
268
+ background: var(--gray-bubble);
269
+ border-bottom-right-radius: 24px 21px;
270
+ }
271
+ .bubble.blue::before {
272
+ right: -10px;
273
+ background: var(--blue);
274
+ border-bottom-left-radius: 24px 21px;
275
+ }
276
+ .bubble.gray::after,
277
+ .bubble.blue::after {
278
+ content: "";
279
+ position: absolute;
280
+ bottom: 0;
281
+ width: 21px;
282
+ height: 25px;
283
+ background: var(--chat-bg);
284
+ }
285
+ .bubble.gray::after { left: -21px; border-bottom-right-radius: 15px; }
286
+ .bubble.blue::after { right: -21px; border-bottom-left-radius: 15px; }
287
+
288
+ /* typing indicator */
289
+ .typing { display: flex; align-items: center; gap: 10px; height: 32px; padding: 20px 27px; }
290
+ .dot {
291
+ width: 14px; height: 14px;
292
+ border-radius: 50%;
293
+ background: #8e8e93;
294
+ animation: dot 1.1s ease-in-out infinite;
295
+ }
296
+ .bubble.blue .dot { background: rgba(255,255,255,0.9); }
297
+ .dot:nth-child(2) { animation-delay: 0.15s; }
298
+ .dot:nth-child(3) { animation-delay: 0.3s; }
299
+ @keyframes dot {
300
+ 0%, 60%, 100% { transform: translateY(0); opacity: 0.45; }
301
+ 30% { transform: translateY(-6px); opacity: 1; }
302
+ }
303
+
304
+ .receipt {
305
+ align-self: flex-end;
306
+ margin: -6px 34px 0 0;
307
+ font-size: 21px;
308
+ color: #8e8e93;
309
+ animation: fadein 0.3s ease both;
310
+ }
311
+ @keyframes fadein { from { opacity: 0; } to { opacity: 1; } }
312
+
313
+ /* ---- composer + home indicator ---- */
314
+ .composer {
315
+ display: flex;
316
+ align-items: center;
317
+ gap: 20px;
318
+ padding: 14px 26px 16px;
319
+ }
320
+ .cfield {
321
+ flex: 1;
322
+ height: 64px;
323
+ display: flex;
324
+ align-items: center;
325
+ padding: 0 26px;
326
+ border: 1.5px solid rgba(255,255,255,0.16);
327
+ border-radius: 32px;
328
+ color: #8e8e93;
329
+ font-size: 28px;
330
+ letter-spacing: -0.01em;
331
+ }
332
+ .home {
333
+ width: 240px;
334
+ height: 8px;
335
+ border-radius: 4px;
336
+ background: rgba(255,255,255,0.35);
337
+ margin: 4px auto 16px;
338
+ }
339
+
340
+ /* ---- end card (hidden until the phone has fully exited) ---- */
341
+ .ec {
342
+ position: absolute;
343
+ inset: 0;
344
+ display: flex;
345
+ flex-direction: column;
346
+ align-items: center;
347
+ justify-content: center;
348
+ text-align: center;
349
+ visibility: hidden;
350
+ pointer-events: none;
351
+ }
352
+ .ec.on { visibility: visible; }
353
+ .ec .item {
354
+ opacity: 0;
355
+ transform: translateY(36px);
356
+ transition: opacity 0.55s ease, transform 0.72s cubic-bezier(0.22, 1, 0.36, 1);
357
+ }
358
+ .ec .item.show { opacity: 1; transform: translateY(0); }
359
+
360
+ .ec .mark {
361
+ width: 150px; height: 150px;
362
+ border-radius: 42px;
363
+ background: linear-gradient(135deg, #2f8bff 0%, #8a5cff 100%);
364
+ display: grid;
365
+ place-items: center;
366
+ box-shadow: 0 26px 80px rgba(70,120,255,0.38);
367
+ margin-bottom: 44px;
368
+ }
369
+ .ec .mark span {
370
+ font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
371
+ color: #fff;
372
+ font-size: 68px;
373
+ font-weight: 600;
374
+ transform: translateY(-3px);
375
+ }
376
+ .ec .word {
377
+ font-size: 116px;
378
+ font-weight: 700;
379
+ letter-spacing: -0.045em;
380
+ line-height: 1;
381
+ background: linear-gradient(92deg, #9cc9ff 0%, #d3b8ff 100%);
382
+ -webkit-background-clip: text;
383
+ background-clip: text;
384
+ color: transparent;
385
+ filter: drop-shadow(0 10px 44px rgba(120,140,255,0.22));
386
+ margin-bottom: 26px;
387
+ }
388
+ .ec .tag {
389
+ font-size: 36px;
390
+ color: rgba(255,255,255,0.72);
391
+ letter-spacing: -0.01em;
392
+ margin-bottom: 56px;
393
+ }
394
+ .ec .pill {
395
+ font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
396
+ font-size: 27px;
397
+ color: #e8e8ea;
398
+ background: rgba(255,255,255,0.05);
399
+ border: 1.5px solid rgba(255,255,255,0.14);
400
+ border-radius: 20px;
401
+ padding: 24px 36px;
402
+ margin-bottom: 44px;
403
+ white-space: nowrap;
404
+ }
405
+ .ec .pill .ps { color: var(--green); margin-right: 14px; }
406
+ .ec .cur {
407
+ display: inline-block;
408
+ width: 13px; height: 27px;
409
+ background: var(--green);
410
+ vertical-align: -5px;
411
+ margin-left: 5px;
412
+ animation: blink 1.05s steps(1) infinite;
413
+ }
414
+ @keyframes blink { 50% { opacity: 0; } }
415
+ .ec .suite {
416
+ font-size: 23px;
417
+ color: rgba(255,255,255,0.45);
418
+ letter-spacing: 0.06em;
419
+ }
420
+
421
+ /* ---- film grain ---- */
422
+ .grain {
423
+ position: absolute;
424
+ inset: 0;
425
+ width: 100%;
426
+ height: 100%;
427
+ opacity: 0.05;
428
+ mix-blend-mode: overlay;
429
+ pointer-events: none;
430
+ }
431
+
432
+ /* ---- loop wipe ---- */
433
+ #wipe {
434
+ position: absolute;
435
+ inset: 0;
436
+ background: #07070a;
437
+ opacity: 0;
438
+ pointer-events: none;
439
+ transition: opacity 0.45s ease;
440
+ }
441
+ #wipe.on { opacity: 1; }
442
+ </style>
443
+ </head>
444
+ <body>
445
+
446
+ <div class="stage" id="stage">
447
+ <div class="glow g1"></div>
448
+ <div class="glow g2"></div>
449
+ <div class="glow g3"></div>
450
+ <div class="vignette"></div>
451
+
452
+ <div class="phone-wrap" id="phoneWrap">
453
+ <div class="phone">
454
+ <div class="screen">
455
+ <div class="statusbar">
456
+ <span>9:41</span>
457
+ <span class="icons">
458
+ <!-- cellular -->
459
+ <svg width="31" height="19" viewBox="0 0 18 11" fill="#fff"><rect x="0" y="7" width="3" height="4" rx="0.8"/><rect x="4.5" y="5" width="3" height="6" rx="0.8"/><rect x="9" y="2.5" width="3" height="8.5" rx="0.8"/><rect x="13.5" y="0" width="3" height="11" rx="0.8"/></svg>
460
+ <!-- wifi -->
461
+ <svg width="27" height="19" viewBox="0 0 16 11" fill="#fff"><path d="M8 11 5.2 8.2a4 4 0 0 1 5.6 0L8 11zM8 6.2c-1.5 0-2.9.6-4 1.6L2.6 6.4a7.3 7.3 0 0 1 10.8 0L12 7.8a5.6 5.6 0 0 0-4-1.6zM8 2C5.7 2 3.5.9 2 2.3L.6.9A10.4 10.4 0 0 1 15.4.9L14 2.3A8.3 8.3 0 0 0 8 2z" transform="translate(0 1)"/></svg>
462
+ <!-- battery -->
463
+ <svg width="43" height="20" viewBox="0 0 25 12"><rect x="0.5" y="0.5" width="21" height="11" rx="3.2" fill="none" stroke="#fff" opacity="0.45"/><rect x="2" y="2" width="16" height="8" rx="1.8" fill="#fff"/><path d="M23 4v4a2.2 2.2 0 0 0 0-4z" fill="#fff" opacity="0.45"/></svg>
464
+ </span>
465
+ </div>
466
+
467
+ <div class="nav">
468
+ <span class="side">
469
+ <svg width="22" height="38" viewBox="0 0 13 22" fill="none"><path d="M11 2 2.5 11 11 20" stroke="#0a84ff" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
470
+ </span>
471
+ <span class="contact">
472
+ <span class="avatar">S</span>
473
+ <span class="name">sam
474
+ <svg width="12" height="19" viewBox="0 0 7 11" fill="none"><path d="M1 1l4.5 4.5L1 10" stroke="#eaeaee" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
475
+ </span>
476
+ </span>
477
+ <span class="side right">
478
+ <svg width="44" height="31" viewBox="0 0 26 18" fill="#0a84ff"><rect x="0" y="1" width="16.5" height="16" rx="4.5"/><path d="M18.5 6.8 24.4 2.7c.8-.6 1.6 0 1.6.9v10.8c0 .9-.8 1.5-1.6.9l-5.9-4.1V6.8z"/></svg>
479
+ </span>
480
+ </div>
481
+
482
+ <div class="ts"><b>iMessage</b><br>Today 9:41 PM</div>
483
+
484
+ <div class="thread" id="thread"></div>
485
+
486
+ <div class="composer">
487
+ <svg width="52" height="52" viewBox="0 0 30 30"><circle cx="15" cy="15" r="14.2" fill="none" stroke="rgba(255,255,255,0.32)" stroke-width="1.6"/><path d="M15 9.5v11M9.5 15h11" stroke="rgba(255,255,255,0.75)" stroke-width="1.8" stroke-linecap="round"/></svg>
488
+ <span class="cfield">iMessage</span>
489
+ <svg width="34" height="38" viewBox="0 0 20 22" fill="none" stroke="#8e8e93" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="6.5" y="1.5" width="7" height="12" rx="3.5"/><path d="M2.8 10.5a7.2 7.2 0 0 0 14.4 0M10 17.7v2.8"/></svg>
490
+ </div>
491
+ <div class="home"></div>
492
+ </div>
493
+ </div>
494
+ </div>
495
+
496
+ <!-- end card: stays visibility:hidden until the phone is completely gone -->
497
+ <div class="ec" id="ec">
498
+ <div class="item mark"><span>&#8250;_</span></div>
499
+ <div class="item word">vibeshare</div>
500
+ <div class="item tag">share your live agent session</div>
501
+ <div class="item pill"><span class="ps">$</span><span class="cmd" id="cmd"></span><span class="cur"></span></div>
502
+ <div class="item suite">part of the Vibe Suite</div>
503
+ </div>
504
+
505
+ <svg class="grain" xmlns="http://www.w3.org/2000/svg">
506
+ <filter id="grainF"><feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="2" stitchTiles="stitch"/><feColorMatrix type="saturate" values="0"/></filter>
507
+ <rect width="100%" height="100%" filter="url(#grainF)"/>
508
+ </svg>
509
+
510
+ <div id="wipe"></div>
511
+ </div>
512
+
513
+ <script>
514
+ (function () {
515
+ "use strict";
516
+
517
+ var stage = document.getElementById("stage");
518
+ var phoneWrap = document.getElementById("phoneWrap");
519
+ var thread = document.getElementById("thread");
520
+ var wipe = document.getElementById("wipe");
521
+ var ec = document.getElementById("ec");
522
+ var cmdEl = document.getElementById("cmd");
523
+ var ecItems = Array.prototype.slice.call(ec.querySelectorAll(".item"));
524
+
525
+ var CMD = "npx vibeshare-live";
526
+
527
+ /* instant-frame mode: open with #t=SECONDS to render that exact moment */
528
+ var OFF = parseFloat((location.hash.match(/t=([\d.]+)/) || [])[1] || "0") || 0;
529
+ var INSTANT = OFF > 0;
530
+ if (INSTANT) stage.classList.add("snap");
531
+
532
+ var timers = [];
533
+ var typingRow = null;
534
+
535
+ /* ---- scale the 1080x1920 stage to the window ---- */
536
+ function fit() {
537
+ var s = Math.min(window.innerWidth / 1080, window.innerHeight / 1920);
538
+ stage.style.transform = "translate(-50%, -50%) scale(" + s + ")";
539
+ }
540
+ window.addEventListener("resize", fit);
541
+ fit();
542
+
543
+ function at(sec, fn) {
544
+ var d = sec - OFF;
545
+ if (d <= 0.02) { fn(); return; }
546
+ timers.push(setTimeout(fn, d * 1000));
547
+ }
548
+
549
+ function row(side, bubbleHtml) {
550
+ var r = document.createElement("div");
551
+ r.className = "row " + side;
552
+ r.innerHTML = bubbleHtml;
553
+ thread.appendChild(r);
554
+ return r;
555
+ }
556
+
557
+ function showTyping(side) {
558
+ var cls = side === "b" ? "blue" : "gray";
559
+ typingRow = row(side,
560
+ '<div class="bubble ' + cls + ' typing">' +
561
+ '<span class="dot"></span><span class="dot"></span><span class="dot"></span>' +
562
+ "</div>");
563
+ }
564
+
565
+ function killTyping() {
566
+ if (typingRow) { typingRow.remove(); typingRow = null; }
567
+ }
568
+
569
+ function say(side, text) {
570
+ killTyping();
571
+ var cls = side === "b" ? "blue" : "gray";
572
+ row(side, '<div class="bubble ' + cls + '">' + text + "</div>");
573
+ }
574
+
575
+ /* terminal-style bubble: pops in, then the command types itself */
576
+ function sayTyped(side, text) {
577
+ killTyping();
578
+ var r = row(side,
579
+ '<div class="bubble blue mono"><span class="tx"></span>' +
580
+ (INSTANT ? "" : '<span class="bcur"></span>') + "</div>");
581
+ var tx = r.querySelector(".tx");
582
+ if (INSTANT) { tx.textContent = text; return; }
583
+ for (var i = 1; i <= text.length; i++) {
584
+ (function (n) {
585
+ timers.push(setTimeout(function () {
586
+ tx.textContent = text.slice(0, n);
587
+ if (n === text.length) {
588
+ var c = r.querySelector(".bcur");
589
+ if (c) c.remove();
590
+ }
591
+ }, n * 46));
592
+ })(i);
593
+ }
594
+ }
595
+
596
+ function receipt(text) {
597
+ var old = thread.querySelector(".receipt");
598
+ if (old) old.remove();
599
+ var r = document.createElement("div");
600
+ r.className = "receipt";
601
+ r.textContent = text;
602
+ thread.appendChild(r);
603
+ }
604
+
605
+ function typeCmd() {
606
+ if (INSTANT) { cmdEl.textContent = CMD; return; }
607
+ for (var i = 1; i <= CMD.length; i++) {
608
+ (function (n) {
609
+ timers.push(setTimeout(function () {
610
+ cmdEl.textContent = CMD.slice(0, n);
611
+ }, n * 52));
612
+ })(i);
613
+ }
614
+ }
615
+
616
+ function reset() {
617
+ timers.forEach(clearTimeout);
618
+ timers = [];
619
+ typingRow = null;
620
+ thread.innerHTML = "";
621
+ cmdEl.textContent = "";
622
+ phoneWrap.classList.remove("out", "gone");
623
+ stage.classList.remove("card");
624
+ ec.classList.remove("on");
625
+ ecItems.forEach(function (item) { item.classList.remove("show"); });
626
+ }
627
+
628
+ function run() {
629
+ // --- phase 1: the thread (0–15.1s) ---
630
+ at(0.80, function () { showTyping("a"); });
631
+ at(1.60, function () { say("a", "yo how'd you show me that claude bug so fast"); });
632
+
633
+ at(3.00, function () { showTyping("b"); });
634
+ at(3.90, function () { say("b", "shared my terminal session, you were watching live"); });
635
+
636
+ at(5.70, function () { showTyping("a"); });
637
+ at(6.40, function () { say("a", "wait thats a thing??"); });
638
+
639
+ at(7.40, function () { showTyping("b"); });
640
+ at(8.30, function () { say("b", "vibeshare. one link. you can even jump in and drive"); });
641
+
642
+ at(10.30, function () { showTyping("a"); });
643
+ at(11.00, function () { say("a", "this is fake"); });
644
+
645
+ at(11.90, function () { showTyping("b"); });
646
+ at(12.70, function () { sayTyped("b", CMD); });
647
+
648
+ at(13.80, function () { receipt("Delivered"); });
649
+ at(14.50, function () { receipt("Read 9:41 PM"); });
650
+
651
+ // --- phase 2: the thread clears completely (15.1–16.05s) ---
652
+ at(15.10, function () { phoneWrap.classList.add("out"); });
653
+ at(15.78, function () { phoneWrap.classList.add("gone"); });
654
+
655
+ // --- phase 3: end card alone, staggered in (16.05s on) ---
656
+ at(16.05, function () { stage.classList.add("card"); ec.classList.add("on"); });
657
+ ecItems.forEach(function (item, i) {
658
+ at(16.31 + i * 0.27, function () { item.classList.add("show"); });
659
+ });
660
+ at(17.28, typeCmd);
661
+
662
+ // --- wipe + loop (~19.6s) ---
663
+ at(19.60, function () { wipe.classList.add("on"); });
664
+ at(20.05, reset);
665
+ at(20.18, function () { wipe.classList.remove("on"); });
666
+ at(20.55, run);
667
+ }
668
+
669
+ run();
670
+ })();
671
+ </script>
672
+ </body>
673
+ </html>