@bitpub/cli 2.0.3 → 2.0.5

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,574 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Welcome to BitPub</title>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <style>
8
+ /* Welcome app — written by `bitpub welcome` during install. This is the
9
+ first thing every BitPub user sees. The deliberate design choices:
10
+ - one narrative arc, not a dashboard
11
+ - generous whitespace; nothing crowded
12
+ - one accent colour (BitPub orange) carrying signal
13
+ - three actions, each with copy-to-clipboard
14
+ - a meta reveal: this page is itself a slice in your namespace
15
+ The whole document lives inside a sandboxed iframe in the BitPub
16
+ Browser — no network, no storage, inline scripts/styles only. */
17
+ :root {
18
+ --bg: #faf6f5;
19
+ --card: #ffffff;
20
+ --inset: #f0e9e6;
21
+ --inset-2: #f7f1ee;
22
+ --line: rgba(33, 29, 28, .08);
23
+ --line-2: rgba(33, 29, 28, .14);
24
+ --text: #211d1c;
25
+ --muted: #4d4645;
26
+ --subtle: #8a8281;
27
+ --faint: #b8b1ae;
28
+ --accent: #ff471a;
29
+ --accent-2: #ff6b41;
30
+ --accent-bg: rgba(255, 71, 26, .08);
31
+ --accent-bg-2: rgba(255, 71, 26, .14);
32
+ --private: #7c5cff;
33
+ --private-bg:rgba(124, 92, 255, .10);
34
+ --ok: #2a9d6e;
35
+ --ok-bg: rgba(42, 157, 110, .12);
36
+ --radius: 16px;
37
+ --radius-sm: 10px;
38
+ --radius-xs: 7px;
39
+ --font: -apple-system, BlinkMacSystemFont, "SF Pro Text",
40
+ "Segoe UI", Inter, system-ui, sans-serif;
41
+ --display: -apple-system, BlinkMacSystemFont, "SF Pro Display",
42
+ "Segoe UI", Inter, system-ui, sans-serif;
43
+ --mono: ui-monospace, SFMono-Regular, "JetBrains Mono",
44
+ Menlo, Consolas, monospace;
45
+ --shadow-1: 0 1px 0 rgba(33,29,28,.04), 0 2px 4px rgba(33,29,28,.04);
46
+ --shadow-2: 0 1px 0 rgba(33,29,28,.04), 0 8px 22px rgba(33,29,28,.08);
47
+ }
48
+ * { box-sizing: border-box; margin: 0; padding: 0; }
49
+ html, body {
50
+ background: var(--bg); color: var(--text);
51
+ font-family: var(--font); font-size: 14px; line-height: 1.55;
52
+ -webkit-font-smoothing: antialiased;
53
+ text-rendering: optimizeLegibility;
54
+ }
55
+ body {
56
+ padding: 56px 28px 64px;
57
+ max-width: 920px;
58
+ margin: 0 auto;
59
+ }
60
+ code, pre, .mono { font-family: var(--mono); }
61
+
62
+ /* ─── Hero ─────────────────────────────────────────── */
63
+ .hero {
64
+ margin-bottom: 56px;
65
+ padding-bottom: 4px;
66
+ }
67
+ .mark {
68
+ width: 38px; height: 38px;
69
+ border-radius: 9px;
70
+ background:
71
+ radial-gradient(circle at 32% 30%, #ff8a5b 0%, var(--accent) 55%, #c9320a 105%);
72
+ box-shadow:
73
+ inset 0 0 0 1px rgba(0,0,0,.05),
74
+ 0 6px 18px rgba(255, 71, 26, .25);
75
+ margin-bottom: 28px;
76
+ position: relative;
77
+ }
78
+ .mark::after {
79
+ content: '';
80
+ position: absolute;
81
+ inset: 8px;
82
+ border-radius: 4px;
83
+ background: rgba(255,255,255,.18);
84
+ backdrop-filter: blur(2px);
85
+ }
86
+ h1.title {
87
+ font-family: var(--display);
88
+ font-size: 48px;
89
+ font-weight: 600;
90
+ letter-spacing: -.025em;
91
+ line-height: 1.05;
92
+ color: var(--text);
93
+ margin-bottom: 14px;
94
+ }
95
+ h1.title .accent { color: var(--accent); }
96
+ p.lede {
97
+ font-size: 18px;
98
+ color: var(--muted);
99
+ max-width: 38ch;
100
+ line-height: 1.5;
101
+ margin-bottom: 28px;
102
+ }
103
+ p.lede b { color: var(--text); font-weight: 600; }
104
+
105
+ .you-are-here {
106
+ display: flex;
107
+ gap: 14px;
108
+ padding: 16px 18px;
109
+ background: var(--card);
110
+ border: 1px solid var(--line);
111
+ border-radius: var(--radius-sm);
112
+ max-width: 620px;
113
+ box-shadow: var(--shadow-1);
114
+ }
115
+ .you-are-here .dot {
116
+ flex-shrink: 0;
117
+ width: 8px; height: 8px;
118
+ margin-top: 7px;
119
+ border-radius: 50%;
120
+ background: var(--accent);
121
+ box-shadow: 0 0 0 0 rgba(255, 71, 26, .55);
122
+ animation: pulse 2.2s ease-out infinite;
123
+ }
124
+ @keyframes pulse {
125
+ 0% { box-shadow: 0 0 0 0 rgba(255, 71, 26, .55); }
126
+ 70% { box-shadow: 0 0 0 10px rgba(255, 71, 26, 0); }
127
+ 100% { box-shadow: 0 0 0 0 rgba(255, 71, 26, 0); }
128
+ }
129
+ .you-are-here .text {
130
+ font-size: 13.5px;
131
+ color: var(--muted);
132
+ line-height: 1.55;
133
+ }
134
+ .you-are-here .text b { color: var(--text); font-weight: 600; }
135
+ .you-are-here .text .addr {
136
+ display: inline-block;
137
+ margin-top: 4px;
138
+ padding: 2px 7px;
139
+ background: var(--accent-bg);
140
+ color: var(--accent);
141
+ border-radius: 4px;
142
+ font-family: var(--mono);
143
+ font-size: 12px;
144
+ letter-spacing: -.01em;
145
+ word-break: break-all;
146
+ }
147
+
148
+ /* ─── Section heading ─────────────────────────────────────────── */
149
+ .section-h {
150
+ display: flex;
151
+ align-items: baseline;
152
+ gap: 12px;
153
+ margin: 4px 4px 22px;
154
+ }
155
+ .section-h h2 {
156
+ font-family: var(--display);
157
+ font-size: 22px;
158
+ font-weight: 600;
159
+ letter-spacing: -.015em;
160
+ color: var(--text);
161
+ }
162
+ .section-h .meta {
163
+ font-size: 12.5px;
164
+ color: var(--subtle);
165
+ }
166
+
167
+ /* ─── Action grid ─────────────────────────────────────────── */
168
+ .actions {
169
+ display: grid;
170
+ grid-template-columns: 1fr 1fr 1fr;
171
+ gap: 18px;
172
+ }
173
+ .action {
174
+ background: var(--card);
175
+ border: 1px solid var(--line);
176
+ border-radius: var(--radius);
177
+ padding: 22px 22px 18px;
178
+ box-shadow: var(--shadow-1);
179
+ display: flex;
180
+ flex-direction: column;
181
+ min-height: 260px;
182
+ transition: box-shadow .2s ease, transform .2s ease;
183
+ }
184
+ .action:hover {
185
+ box-shadow: var(--shadow-2);
186
+ transform: translateY(-1px);
187
+ }
188
+ .action-num {
189
+ width: 28px; height: 28px;
190
+ border-radius: 8px;
191
+ background: var(--accent-bg);
192
+ color: var(--accent);
193
+ display: flex; align-items: center; justify-content: center;
194
+ font-size: 14px;
195
+ font-weight: 600;
196
+ margin-bottom: 14px;
197
+ }
198
+ .action h3 {
199
+ font-family: var(--display);
200
+ font-size: 16px;
201
+ font-weight: 600;
202
+ letter-spacing: -.005em;
203
+ line-height: 1.3;
204
+ color: var(--text);
205
+ margin-bottom: 8px;
206
+ }
207
+ .action p {
208
+ font-size: 13px;
209
+ color: var(--muted);
210
+ line-height: 1.55;
211
+ margin-bottom: 16px;
212
+ flex: 1;
213
+ }
214
+
215
+ /* Copy block + button */
216
+ .copy-block {
217
+ background: var(--inset-2);
218
+ border: 1px solid var(--line);
219
+ border-radius: var(--radius-xs);
220
+ padding: 0;
221
+ overflow: hidden;
222
+ margin-top: auto;
223
+ }
224
+ .copy-block .cmd {
225
+ font-family: var(--mono);
226
+ font-size: 12px;
227
+ color: var(--text);
228
+ padding: 10px 12px 10px;
229
+ line-height: 1.5;
230
+ white-space: pre-wrap;
231
+ word-break: break-word;
232
+ max-height: 96px;
233
+ overflow-y: auto;
234
+ }
235
+ .copy-block .cmd .prompt { color: var(--accent); user-select: none; }
236
+ .copy-btn {
237
+ display: flex;
238
+ align-items: center;
239
+ justify-content: space-between;
240
+ gap: 8px;
241
+ width: 100%;
242
+ padding: 8px 12px;
243
+ background: var(--card);
244
+ border: 0;
245
+ border-top: 1px solid var(--line);
246
+ font: inherit;
247
+ font-size: 12px;
248
+ font-weight: 500;
249
+ color: var(--muted);
250
+ cursor: pointer;
251
+ transition: background .15s ease, color .15s ease;
252
+ }
253
+ .copy-btn:hover { background: var(--inset); color: var(--text); }
254
+ .copy-btn.copied { color: var(--ok); background: var(--ok-bg); }
255
+ .copy-btn .label { display: inline-flex; align-items: center; gap: 6px; }
256
+ .copy-btn svg { width: 12px; height: 12px; }
257
+
258
+ .action .followup {
259
+ font-size: 11.5px;
260
+ color: var(--subtle);
261
+ margin-top: 12px;
262
+ line-height: 1.45;
263
+ }
264
+ .action .followup b { color: var(--muted); font-weight: 500; }
265
+
266
+ /* ─── Meta reveal ─────────────────────────────────────────── */
267
+ .meta-card {
268
+ margin-top: 56px;
269
+ background: linear-gradient(180deg, #ffffff 0%, #fbf6f4 100%);
270
+ border: 1px solid var(--line);
271
+ border-radius: var(--radius);
272
+ padding: 24px 26px;
273
+ box-shadow: var(--shadow-1);
274
+ }
275
+ .meta-card .meta-hd {
276
+ display: flex; align-items: baseline; gap: 12px;
277
+ margin-bottom: 10px;
278
+ }
279
+ .meta-card h3 {
280
+ font-family: var(--display);
281
+ font-size: 16px;
282
+ font-weight: 600;
283
+ color: var(--text);
284
+ letter-spacing: -.005em;
285
+ }
286
+ .meta-card .meta-tag {
287
+ font-size: 10.5px;
288
+ font-weight: 500;
289
+ padding: 2px 8px;
290
+ border-radius: 999px;
291
+ background: var(--accent-bg);
292
+ color: var(--accent);
293
+ letter-spacing: .02em;
294
+ }
295
+ .meta-card p {
296
+ font-size: 13.5px;
297
+ color: var(--muted);
298
+ line-height: 1.6;
299
+ max-width: 70ch;
300
+ }
301
+ .meta-card p b { color: var(--text); font-weight: 600; }
302
+ .meta-card p + p { margin-top: 10px; }
303
+
304
+ details.recipe {
305
+ margin-top: 18px;
306
+ padding-top: 16px;
307
+ border-top: 1px solid var(--line);
308
+ }
309
+ details.recipe summary {
310
+ cursor: pointer;
311
+ list-style: none;
312
+ font-size: 13px;
313
+ color: var(--muted);
314
+ display: flex; align-items: center; gap: 8px;
315
+ user-select: none;
316
+ font-weight: 500;
317
+ }
318
+ details.recipe summary::-webkit-details-marker { display: none; }
319
+ details.recipe summary .chev {
320
+ display: inline-block;
321
+ transition: transform .15s ease;
322
+ color: var(--faint);
323
+ }
324
+ details.recipe[open] summary .chev { transform: rotate(90deg); }
325
+ details.recipe pre {
326
+ margin-top: 14px;
327
+ padding: 16px 18px;
328
+ background: #1e1a19;
329
+ color: #e8e2dc;
330
+ border-radius: var(--radius-sm);
331
+ font-size: 12px;
332
+ line-height: 1.65;
333
+ overflow-x: auto;
334
+ font-family: var(--mono);
335
+ box-shadow: var(--shadow-1);
336
+ }
337
+ details.recipe pre .c { color: #6c6660; font-style: italic; }
338
+ details.recipe pre .k { color: #ff8a5b; }
339
+ details.recipe pre .s { color: #c8b58a; }
340
+ details.recipe pre .p { color: #7c9d7a; }
341
+ details.recipe pre .n { color: #b5a5ff; }
342
+
343
+ /* ─── Footnote ─────────────────────────────────────────── */
344
+ .footnote {
345
+ margin-top: 44px;
346
+ padding-top: 22px;
347
+ border-top: 1px solid var(--line);
348
+ color: var(--subtle);
349
+ font-size: 12.5px;
350
+ line-height: 1.7;
351
+ max-width: 70ch;
352
+ }
353
+ .footnote b { color: var(--muted); font-weight: 500; }
354
+ .footnote .br { display: block; margin-top: 6px; }
355
+
356
+ /* ─── Responsive ─────────────────────────────────────────── */
357
+ @media (max-width: 820px) {
358
+ body { padding: 36px 20px 48px; }
359
+ h1.title { font-size: 36px; }
360
+ .actions { grid-template-columns: 1fr; }
361
+ .action { min-height: 0; }
362
+ }
363
+
364
+ /* Entrance animation — small lift to feel alive. We start at the
365
+ final visible state and animate IN from a slightly translated, half-
366
+ transparent state. (Earlier version of this animation started with
367
+ `opacity:0` as the initial value, which left content invisible if
368
+ `animation` failed to fire — paranoia worth carrying given how many
369
+ browser quirks land on sandboxed iframes.) */
370
+ @keyframes rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
371
+ body > * { animation: rise .6s ease both; }
372
+ body > *:nth-child(1) { animation-delay: .00s; }
373
+ body > *:nth-child(2) { animation-delay: .10s; }
374
+ body > *:nth-child(3) { animation-delay: .18s; }
375
+ body > *:nth-child(4) { animation-delay: .28s; }
376
+ body > *:nth-child(5) { animation-delay: .36s; }
377
+ </style>
378
+ </head>
379
+ <body>
380
+
381
+ <!-- ═══════ Hero ═══════ -->
382
+ <div class="hero">
383
+ <div class="mark" aria-hidden="true"></div>
384
+ <h1 class="title">Welcome.<br><span class="accent">You're inside.</span></h1>
385
+ <p class="lede">
386
+ BitPub is a <b>browser for the agent web</b>. The page you're reading is itself a slice in your private namespace &mdash; written to your laptop by the install script a moment ago.
387
+ </p>
388
+
389
+ <div class="you-are-here">
390
+ <span class="dot" aria-hidden="true"></span>
391
+ <div class="text">
392
+ <b>You are here.</b> The address bar above shows where this page lives.<br>
393
+ <span class="addr">bitpub://private:__OWNER__/Welcome</span>
394
+ </div>
395
+ </div>
396
+ </div>
397
+
398
+ <!-- ═══════ Actions ═══════ -->
399
+ <div class="section-h">
400
+ <h2>Three things to try</h2>
401
+ <span class="meta">each takes under a minute &middot; click to copy</span>
402
+ </div>
403
+
404
+ <div class="actions">
405
+
406
+ <!-- ── Card 1: look at your namespace ── -->
407
+ <div class="action">
408
+ <div class="action-num">1</div>
409
+ <h3>Look at your namespace</h3>
410
+ <p>
411
+ You already have one. The tree on the left of this window is your private namespace &mdash; encrypted, local-first, yours. Click around. Everything you save lands somewhere in here.
412
+ </p>
413
+ <div class="copy-block">
414
+ <pre class="cmd"><span class="prompt">$ </span>bitpub list</pre>
415
+ <button class="copy-btn" type="button" data-copy="bitpub list">
416
+ <span class="label">
417
+ <svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="7" height="7" rx="1"/><path d="M3 2h6"/></svg>
418
+ Copy command
419
+ </span>
420
+ <span class="hint" aria-hidden="true">⌘V in terminal</span>
421
+ </button>
422
+ </div>
423
+ <p class="followup">
424
+ Or just click any folder in the sidebar &mdash; same thing, visually.
425
+ </p>
426
+ </div>
427
+
428
+ <!-- ── Card 2: create a group ── -->
429
+ <div class="action">
430
+ <div class="action-num">2</div>
431
+ <h3>Create a group with your team</h3>
432
+ <p>
433
+ Groups are shared namespaces. Members can read and write together. You'll get a shareable invite link to send your teammates &mdash; no GitHub, no email-invites, no PRs.
434
+ </p>
435
+ <div class="copy-block">
436
+ <pre class="cmd"><span class="prompt">$ </span>bitpub group create yourcompany</pre>
437
+ <button class="copy-btn" type="button" data-copy="bitpub group create yourcompany">
438
+ <span class="label">
439
+ <svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="7" height="7" rx="1"/><path d="M3 2h6"/></svg>
440
+ Copy command
441
+ </span>
442
+ <span class="hint" aria-hidden="true">⌘V in terminal</span>
443
+ </button>
444
+ </div>
445
+ <p class="followup">
446
+ Then share the printed link. Teammates run <code>bitpub join &lt;link&gt;</code> and are in.
447
+ </p>
448
+ </div>
449
+
450
+ <!-- ── Card 3: build an app with the agent you already have ── -->
451
+ <div class="action">
452
+ <div class="action-num">3</div>
453
+ <h3>Build an app with the agent you already have</h3>
454
+ <p>
455
+ Paste the prompt below into Claude Code, Cursor, or Codex. Your agent will write you a small HTML app and save it to your namespace. It'll render here, in this same window.
456
+ </p>
457
+ <div class="copy-block">
458
+ <pre class="cmd">Save me a personal todo list as an HTML app at bitpub://private:__OWNER__/Apps/todo.html. Use the BitPub palette (warm cream, orange accent). Make it look like a real product, not a demo.</pre>
459
+ <button class="copy-btn" type="button" data-copy="Save me a personal todo list as an HTML app at bitpub://private:__OWNER__/Apps/todo.html. Use the BitPub palette (warm cream, orange accent). Make it look like a real product, not a demo.">
460
+ <span class="label">
461
+ <svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="7" height="7" rx="1"/><path d="M3 2h6"/></svg>
462
+ Copy prompt
463
+ </span>
464
+ <span class="hint" aria-hidden="true">paste to your agent</span>
465
+ </button>
466
+ </div>
467
+ <p class="followup">
468
+ Refresh this tab when it's done. The new app will be at <code>Apps/todo.html</code>.
469
+ </p>
470
+ </div>
471
+
472
+ </div>
473
+
474
+ <!-- ═══════ Meta reveal ═══════ -->
475
+ <div class="meta-card">
476
+ <div class="meta-hd">
477
+ <h3>What you're actually looking at</h3>
478
+ <span class="meta-tag">explicit by design</span>
479
+ </div>
480
+ <p>
481
+ <b>This page is a slice.</b> A small HTML document, encrypted on your laptop, sitting at the URL in your address bar. The BitPub Browser fetched it from your local cache, decrypted it with your key, and rendered it in a <b>sandboxed iframe</b> &mdash; no network access, no storage, no way to leak.
482
+ </p>
483
+ <p>
484
+ Everything you'll build on BitPub looks like this. Skills, agents, dashboards, apps &mdash; they're all just slices at URLs. <b>The address is the deploy step.</b> Share the URL, the recipient sees the same app rendered against <em>their</em> namespace.
485
+ </p>
486
+
487
+ <details class="recipe">
488
+ <summary>
489
+ <span class="chev">▸</span>
490
+ See what this slice looks like under the hood
491
+ </summary>
492
+ <pre><span class="c"># address (what you see in the bar above)</span>
493
+ <span class="k">slice</span>: <span class="p">bitpub://private:__OWNER__/Welcome</span>
494
+
495
+ <span class="c"># written during install — once per machine</span>
496
+ <span class="k">written</span>: <span class="s">bitpub welcome</span> <span class="c"># the CLI command</span>
497
+ <span class="k">format</span>: <span class="s">text/html</span>
498
+ <span class="k">size</span>: <span class="n">~28 KB</span>
499
+
500
+ <span class="c"># where it executes</span>
501
+ <span class="k">runtime</span>: <span class="n">local</span> <span class="c"># renders in your browser, from your laptop</span>
502
+ <span class="k">network</span>: <span class="n">none</span> <span class="c"># the iframe sandbox blocks fetch, XHR, sockets</span>
503
+ <span class="k">storage</span>: <span class="n">none</span> <span class="c"># no cookies, no localStorage, no IndexedDB</span>
504
+
505
+ <span class="c"># what it can do</span>
506
+ <span class="k">writes_to</span>: <span class="p">—</span> <span class="c"># v1: render-only. write-bridge is opt-in per Pack.</span>
507
+ <span class="k">reads</span>: <span class="p">—</span> <span class="c"># same</span>
508
+ </pre>
509
+ </details>
510
+ </div>
511
+
512
+ <!-- ═══════ Footnote ═══════ -->
513
+ <p class="footnote">
514
+ <b>You can close this tab whenever you want.</b>
515
+ <span class="br"></span>
516
+ The BitPub Browser is here so you can <em>see</em> what your agents are doing. The real interface is the agent on your laptop &mdash; Claude Code, Cursor, Codex, anything that speaks MCP. They'll keep reading and writing to your namespace whether this tab is open or not.
517
+ </p>
518
+
519
+ <script>
520
+ /* Sandboxed-iframe copy-to-clipboard.
521
+ navigator.clipboard.writeText is granted by user gesture in this
522
+ sandbox; fall back to a textarea-select + execCommand trick if the
523
+ host context refuses (rare but worth handling — some browsers gate
524
+ clipboard on top-level origin only). */
525
+ document.querySelectorAll('.copy-btn').forEach(btn => {
526
+ btn.addEventListener('click', () => {
527
+ const text = btn.getAttribute('data-copy') || '';
528
+ copyToClipboard(text).then(ok => flashCopied(btn, ok));
529
+ });
530
+ });
531
+
532
+ function copyToClipboard(text) {
533
+ if (navigator.clipboard && navigator.clipboard.writeText) {
534
+ return navigator.clipboard.writeText(text)
535
+ .then(() => true)
536
+ .catch(() => fallbackCopy(text));
537
+ }
538
+ return Promise.resolve(fallbackCopy(text));
539
+ }
540
+
541
+ function fallbackCopy(text) {
542
+ try {
543
+ const ta = document.createElement('textarea');
544
+ ta.value = text;
545
+ ta.setAttribute('readonly', '');
546
+ ta.style.position = 'fixed';
547
+ ta.style.top = '0'; ta.style.left = '0';
548
+ ta.style.opacity = '0';
549
+ document.body.appendChild(ta);
550
+ ta.select();
551
+ const ok = document.execCommand('copy');
552
+ document.body.removeChild(ta);
553
+ return ok;
554
+ } catch (_) {
555
+ return false;
556
+ }
557
+ }
558
+
559
+ function flashCopied(btn, ok) {
560
+ const labelEl = btn.querySelector('.label');
561
+ if (!labelEl) return;
562
+ const original = labelEl.innerHTML;
563
+ labelEl.innerHTML = ok
564
+ ? '<svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M2 6.5L5 9.5l5-6"/></svg> Copied'
565
+ : '<svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M2 2l8 8M10 2l-8 8"/></svg> Press ⌘C';
566
+ btn.classList.add('copied');
567
+ setTimeout(() => {
568
+ btn.classList.remove('copied');
569
+ labelEl.innerHTML = original;
570
+ }, 1600);
571
+ }
572
+ </script>
573
+ </body>
574
+ </html>