@omg-dev/pwa 0.4.24

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,726 @@
1
+ import { a as isDismissed, c as promptNativeInstall, d as subscribeInstallState, i as getServerInstallState, n as detectPlatform, o as isInIframe, r as getInstallState, u as setDismissed } from "./core-YJoFChtB.mjs";
2
+ import { useCallback, useEffect, useMemo, useState, useSyncExternalStore } from "react";
3
+ import { createPortal } from "react-dom";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/styles.ts
6
+ const STYLE_ID = "vibes-pwa-styles";
7
+ function ensureStyles() {
8
+ if (typeof document === "undefined") return;
9
+ if (document.getElementById("vibes-pwa-styles")) return;
10
+ const el = document.createElement("style");
11
+ el.id = STYLE_ID;
12
+ el.textContent = CSS;
13
+ document.head.appendChild(el);
14
+ }
15
+ const CSS = `
16
+ .vpwa {
17
+ --vpwa-bg: #ffffff;
18
+ --vpwa-fg: #18181b;
19
+ --vpwa-muted: #71717a;
20
+ --vpwa-surface: #f4f4f5;
21
+ --vpwa-border: rgba(0, 0, 0, 0.1);
22
+ --vpwa-accent: #2563eb;
23
+ --vpwa-accent-fg: #ffffff;
24
+ font-family: inherit;
25
+ box-sizing: border-box;
26
+ }
27
+ .vpwa *, .vpwa *::before, .vpwa *::after { box-sizing: border-box; }
28
+ @media (prefers-color-scheme: dark) {
29
+ .vpwa {
30
+ --vpwa-bg: #1c1c1e;
31
+ --vpwa-fg: #f4f4f5;
32
+ --vpwa-muted: #a1a1aa;
33
+ --vpwa-surface: #2c2c2e;
34
+ --vpwa-border: rgba(255, 255, 255, 0.12);
35
+ --vpwa-accent: #3b82f6;
36
+ }
37
+ }
38
+
39
+ /* ── overlay + sheet ─────────────────────────────────────────────────── */
40
+ .vpwa-overlay {
41
+ position: fixed; inset: 0; z-index: 2147483000;
42
+ display: flex; align-items: flex-end; justify-content: center;
43
+ }
44
+ .vpwa-backdrop {
45
+ position: absolute; inset: 0;
46
+ background: rgba(0, 0, 0, 0.5);
47
+ opacity: 0; transition: opacity 0.25s ease;
48
+ }
49
+ .vpwa-open .vpwa-backdrop { opacity: 1; }
50
+ .vpwa-sheet {
51
+ position: relative; width: 100%; max-width: 430px;
52
+ background: var(--card, var(--vpwa-bg));
53
+ color: var(--card-foreground, var(--vpwa-fg));
54
+ border: 1px solid var(--border, var(--vpwa-border)); border-bottom: none;
55
+ border-radius: 24px 24px 0 0;
56
+ box-shadow: 0 -8px 40px rgba(0, 0, 0, 0.2);
57
+ padding: 8px 24px calc(24px + env(safe-area-inset-bottom));
58
+ transform: translateY(100%);
59
+ transition: transform 0.3s cubic-bezier(0.32, 0.72, 0, 1);
60
+ }
61
+ .vpwa-open .vpwa-sheet { transform: translateY(0); }
62
+ @media (min-width: 640px) {
63
+ .vpwa-overlay { align-items: center; padding: 24px; }
64
+ .vpwa-sheet {
65
+ border-radius: 24px; border-bottom: 1px solid var(--border, var(--vpwa-border));
66
+ padding: 24px; box-shadow: 0 24px 60px rgba(0, 0, 0, 0.25);
67
+ transform: translateY(12px); opacity: 0;
68
+ transition: transform 0.25s ease, opacity 0.25s ease;
69
+ }
70
+ .vpwa-open .vpwa-sheet { transform: translateY(0); opacity: 1; }
71
+ .vpwa-handle { display: none; }
72
+ }
73
+ .vpwa-handle {
74
+ width: 40px; height: 4px; border-radius: 999px;
75
+ background: var(--muted-foreground, var(--vpwa-muted));
76
+ opacity: 0.25; margin: 4px auto 16px;
77
+ }
78
+ .vpwa-x {
79
+ position: absolute; top: 14px; right: 14px;
80
+ width: 32px; height: 32px; border-radius: 999px;
81
+ display: flex; align-items: center; justify-content: center;
82
+ border: none; background: transparent; cursor: pointer;
83
+ color: var(--muted-foreground, var(--vpwa-muted));
84
+ }
85
+ .vpwa-x:hover { background: var(--muted, var(--vpwa-surface)); }
86
+
87
+ .vpwa-title {
88
+ margin: 0 24px 4px 0; font-size: 17px; font-weight: 600;
89
+ letter-spacing: -0.01em; line-height: 1.3;
90
+ }
91
+ .vpwa-sub {
92
+ margin: 0 0 20px; font-size: 13px; line-height: 1.45;
93
+ color: var(--muted-foreground, var(--vpwa-muted));
94
+ }
95
+
96
+ /* ── steps ───────────────────────────────────────────────────────────── */
97
+ .vpwa-steps { display: flex; flex-direction: column; gap: 16px; margin: 0; padding: 0; list-style: none; }
98
+ .vpwa-step { display: flex; flex-direction: column; gap: 8px; }
99
+ .vpwa-stephead { display: flex; align-items: baseline; gap: 10px; }
100
+ .vpwa-stepnum {
101
+ font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums;
102
+ color: var(--muted-foreground, var(--vpwa-muted));
103
+ }
104
+ .vpwa-steptext { font-size: 14px; font-weight: 500; }
105
+ .vpwa-stepcap { font-weight: 400; color: var(--muted-foreground, var(--vpwa-muted)); }
106
+
107
+ /* ── mocks ───────────────────────────────────────────────────────────── */
108
+ .vpwa-toolbar {
109
+ display: flex; align-items: center; justify-content: space-between; gap: 16px;
110
+ background: var(--muted, var(--vpwa-surface));
111
+ border-radius: 12px; padding: 10px 16px;
112
+ color: var(--muted-foreground, var(--vpwa-muted));
113
+ }
114
+ .vpwa-toolbar svg { display: block; }
115
+ .vpwa-omnibox {
116
+ display: flex; align-items: center; gap: 10px;
117
+ background: var(--muted, var(--vpwa-surface));
118
+ border-radius: 999px; padding: 8px 14px;
119
+ color: var(--muted-foreground, var(--vpwa-muted));
120
+ font-size: 13px;
121
+ }
122
+ .vpwa-omnibox-url { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; opacity: 0.7; }
123
+ .vpwa-sheetrow {
124
+ display: flex; align-items: center; justify-content: space-between; gap: 12px;
125
+ background: var(--background, var(--vpwa-bg));
126
+ border: 1px solid var(--border, var(--vpwa-border));
127
+ border-radius: 12px; padding: 11px 14px;
128
+ font-size: 14px; font-weight: 500;
129
+ }
130
+ .vpwa-confirm {
131
+ display: flex; align-items: center; justify-content: space-between;
132
+ background: var(--muted, var(--vpwa-surface));
133
+ border-radius: 12px; padding: 11px 16px; font-size: 14px;
134
+ color: var(--muted-foreground, var(--vpwa-muted));
135
+ }
136
+ .vpwa-confirm b { color: var(--primary, var(--vpwa-accent)); font-weight: 600; }
137
+ .vpwa-target {
138
+ color: var(--primary, var(--vpwa-accent));
139
+ animation: vpwa-pulse 2s ease-in-out infinite;
140
+ }
141
+ @keyframes vpwa-pulse {
142
+ 0%, 100% { opacity: 1; }
143
+ 50% { opacity: 0.4; }
144
+ }
145
+ @media (prefers-reduced-motion: reduce) {
146
+ .vpwa-target { animation: none; }
147
+ .vpwa-sheet, .vpwa-backdrop, .vpwa-banner { transition: none; }
148
+ }
149
+
150
+ /* ── buttons ─────────────────────────────────────────────────────────── */
151
+ .vpwa-actions { display: flex; flex-direction: column; gap: 8px; margin-top: 20px; }
152
+ .vpwa-btn {
153
+ width: 100%; height: 44px; border-radius: 12px; border: none; cursor: pointer;
154
+ background: var(--primary, var(--vpwa-accent));
155
+ color: var(--primary-foreground, var(--vpwa-accent-fg));
156
+ font-size: 15px; font-weight: 600; font-family: inherit;
157
+ }
158
+ .vpwa-btn:active { opacity: 0.85; }
159
+ .vpwa-btn-ghost {
160
+ width: 100%; height: 40px; border-radius: 12px; border: none; cursor: pointer;
161
+ background: transparent; font-family: inherit;
162
+ color: var(--muted-foreground, var(--vpwa-muted));
163
+ font-size: 14px; font-weight: 500;
164
+ }
165
+ .vpwa-btn-ghost:hover { background: var(--muted, var(--vpwa-surface)); }
166
+
167
+ /* ── banner (floating / inline prompt) ───────────────────────────────── */
168
+ .vpwa-banner {
169
+ display: flex; align-items: center; gap: 12px;
170
+ background: var(--card, var(--vpwa-bg));
171
+ color: var(--card-foreground, var(--vpwa-fg));
172
+ border: 1px solid var(--border, var(--vpwa-border));
173
+ border-radius: 16px; padding: 12px 12px 12px 14px;
174
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
175
+ }
176
+ .vpwa-banner-floating {
177
+ position: fixed; left: 16px; right: 16px;
178
+ bottom: calc(16px + env(safe-area-inset-bottom));
179
+ z-index: 2147482999; max-width: 430px; margin: 0 auto;
180
+ box-shadow: 0 12px 36px rgba(0, 0, 0, 0.2);
181
+ transform: translateY(calc(100% + 32px));
182
+ transition: transform 0.35s cubic-bezier(0.32, 0.72, 0, 1);
183
+ }
184
+ .vpwa-banner-floating.vpwa-in { transform: translateY(0); }
185
+ .vpwa-chip {
186
+ width: 38px; height: 38px; border-radius: 12px; flex-shrink: 0;
187
+ display: flex; align-items: center; justify-content: center;
188
+ background: color-mix(in srgb, var(--primary, var(--vpwa-accent)) 12%, transparent);
189
+ color: var(--primary, var(--vpwa-accent));
190
+ }
191
+ .vpwa-banner-text { flex: 1; min-width: 0; }
192
+ .vpwa-banner-title {
193
+ margin: 0; font-size: 14px; font-weight: 600;
194
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
195
+ }
196
+ .vpwa-banner-cap {
197
+ margin: 0; font-size: 12px;
198
+ color: var(--muted-foreground, var(--vpwa-muted));
199
+ }
200
+ .vpwa-btn-sm {
201
+ height: 32px; padding: 0 14px; border-radius: 999px; border: none;
202
+ cursor: pointer; flex-shrink: 0; font-family: inherit;
203
+ background: var(--primary, var(--vpwa-accent));
204
+ color: var(--primary-foreground, var(--vpwa-accent-fg));
205
+ font-size: 13px; font-weight: 600;
206
+ }
207
+ .vpwa-banner .vpwa-x { position: static; width: 28px; height: 28px; flex-shrink: 0; }
208
+ `;
209
+ //#endregion
210
+ //#region src/icons.tsx
211
+ function svgProps({ size = 18, className }) {
212
+ return {
213
+ width: size,
214
+ height: size,
215
+ viewBox: "0 0 24 24",
216
+ fill: "none",
217
+ stroke: "currentColor",
218
+ strokeWidth: 2,
219
+ strokeLinecap: "round",
220
+ strokeLinejoin: "round",
221
+ className,
222
+ "aria-hidden": true
223
+ };
224
+ }
225
+ /** iOS share: square with arrow up through the top. */
226
+ function ShareIcon(p) {
227
+ return /* @__PURE__ */ jsxs("svg", {
228
+ ...svgProps(p),
229
+ children: [
230
+ /* @__PURE__ */ jsx("path", { d: "M4 12v7a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-7" }),
231
+ /* @__PURE__ */ jsx("polyline", { points: "16 6 12 2 8 6" }),
232
+ /* @__PURE__ */ jsx("line", {
233
+ x1: "12",
234
+ y1: "2",
235
+ x2: "12",
236
+ y2: "15"
237
+ })
238
+ ]
239
+ });
240
+ }
241
+ function SquarePlusIcon(p) {
242
+ return /* @__PURE__ */ jsxs("svg", {
243
+ ...svgProps(p),
244
+ children: [
245
+ /* @__PURE__ */ jsx("rect", {
246
+ width: "18",
247
+ height: "18",
248
+ x: "3",
249
+ y: "3",
250
+ rx: "3"
251
+ }),
252
+ /* @__PURE__ */ jsx("path", { d: "M8 12h8" }),
253
+ /* @__PURE__ */ jsx("path", { d: "M12 8v8" })
254
+ ]
255
+ });
256
+ }
257
+ function EllipsisVerticalIcon(p) {
258
+ return /* @__PURE__ */ jsxs("svg", {
259
+ ...svgProps(p),
260
+ children: [
261
+ /* @__PURE__ */ jsx("circle", {
262
+ cx: "12",
263
+ cy: "5",
264
+ r: "1",
265
+ fill: "currentColor"
266
+ }),
267
+ /* @__PURE__ */ jsx("circle", {
268
+ cx: "12",
269
+ cy: "12",
270
+ r: "1",
271
+ fill: "currentColor"
272
+ }),
273
+ /* @__PURE__ */ jsx("circle", {
274
+ cx: "12",
275
+ cy: "19",
276
+ r: "1",
277
+ fill: "currentColor"
278
+ })
279
+ ]
280
+ });
281
+ }
282
+ function ChevronLeftIcon(p) {
283
+ return /* @__PURE__ */ jsx("svg", {
284
+ ...svgProps(p),
285
+ children: /* @__PURE__ */ jsx("path", { d: "m15 18-6-6 6-6" })
286
+ });
287
+ }
288
+ function ChevronRightIcon(p) {
289
+ return /* @__PURE__ */ jsx("svg", {
290
+ ...svgProps(p),
291
+ children: /* @__PURE__ */ jsx("path", { d: "m9 18 6-6-6-6" })
292
+ });
293
+ }
294
+ function BookIcon(p) {
295
+ return /* @__PURE__ */ jsx("svg", {
296
+ ...svgProps(p),
297
+ children: /* @__PURE__ */ jsx("path", { d: "M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20" })
298
+ });
299
+ }
300
+ function CopySquaresIcon(p) {
301
+ return /* @__PURE__ */ jsxs("svg", {
302
+ ...svgProps(p),
303
+ children: [/* @__PURE__ */ jsx("rect", {
304
+ width: "14",
305
+ height: "14",
306
+ x: "8",
307
+ y: "8",
308
+ rx: "2"
309
+ }), /* @__PURE__ */ jsx("path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" })]
310
+ });
311
+ }
312
+ /** Chrome's omnibox install glyph: monitor with a down arrow. */
313
+ function MonitorDownIcon(p) {
314
+ return /* @__PURE__ */ jsxs("svg", {
315
+ ...svgProps(p),
316
+ children: [
317
+ /* @__PURE__ */ jsx("path", { d: "M12 13V7" }),
318
+ /* @__PURE__ */ jsx("path", { d: "m15 10-3 3-3-3" }),
319
+ /* @__PURE__ */ jsx("rect", {
320
+ width: "20",
321
+ height: "14",
322
+ x: "2",
323
+ y: "3",
324
+ rx: "2"
325
+ }),
326
+ /* @__PURE__ */ jsx("path", { d: "M12 17v4" }),
327
+ /* @__PURE__ */ jsx("path", { d: "M8 21h8" })
328
+ ]
329
+ });
330
+ }
331
+ function SmartphoneIcon(p) {
332
+ return /* @__PURE__ */ jsxs("svg", {
333
+ ...svgProps(p),
334
+ children: [/* @__PURE__ */ jsx("rect", {
335
+ width: "14",
336
+ height: "20",
337
+ x: "5",
338
+ y: "2",
339
+ rx: "2"
340
+ }), /* @__PURE__ */ jsx("path", { d: "M12 18h.01" })]
341
+ });
342
+ }
343
+ function XIcon(p) {
344
+ return /* @__PURE__ */ jsxs("svg", {
345
+ ...svgProps(p),
346
+ children: [/* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }), /* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })]
347
+ });
348
+ }
349
+ function CheckIcon(p) {
350
+ return /* @__PURE__ */ jsx("svg", {
351
+ ...svgProps(p),
352
+ children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" })
353
+ });
354
+ }
355
+ function LockIcon(p) {
356
+ return /* @__PURE__ */ jsxs("svg", {
357
+ ...svgProps(p),
358
+ children: [/* @__PURE__ */ jsx("rect", {
359
+ width: "18",
360
+ height: "11",
361
+ x: "3",
362
+ y: "11",
363
+ rx: "2"
364
+ }), /* @__PURE__ */ jsx("path", { d: "M7 11V7a5 5 0 0 1 10 0v4" })]
365
+ });
366
+ }
367
+ //#endregion
368
+ //#region src/react.tsx
369
+ function useInstallPrompt() {
370
+ const state = useSyncExternalStore(subscribeInstallState, getInstallState, getServerInstallState);
371
+ const platform = useMemo(detectPlatform, []);
372
+ return {
373
+ platform,
374
+ installed: state.installed,
375
+ /** Chromium captured `beforeinstallprompt` — one-tap install available. */
376
+ canPrompt: state.canPrompt,
377
+ supported: platform !== "unsupported" || state.canPrompt,
378
+ promptInstall: promptNativeInstall
379
+ };
380
+ }
381
+ function defaultAppName() {
382
+ if (typeof document === "undefined") return "this app";
383
+ return document.title.trim() || "this app";
384
+ }
385
+ function IosToolbarMock() {
386
+ return /* @__PURE__ */ jsxs("div", {
387
+ className: "vpwa-toolbar",
388
+ "aria-hidden": true,
389
+ children: [
390
+ /* @__PURE__ */ jsx(ChevronLeftIcon, {}),
391
+ /* @__PURE__ */ jsx(ChevronRightIcon, {}),
392
+ /* @__PURE__ */ jsx("span", {
393
+ className: "vpwa-target",
394
+ children: /* @__PURE__ */ jsx(ShareIcon, { size: 20 })
395
+ }),
396
+ /* @__PURE__ */ jsx(BookIcon, {}),
397
+ /* @__PURE__ */ jsx(CopySquaresIcon, {})
398
+ ]
399
+ });
400
+ }
401
+ function IosChromeBarMock() {
402
+ return /* @__PURE__ */ jsxs("div", {
403
+ className: "vpwa-omnibox",
404
+ "aria-hidden": true,
405
+ children: [
406
+ /* @__PURE__ */ jsx(LockIcon, { size: 14 }),
407
+ /* @__PURE__ */ jsx("span", {
408
+ className: "vpwa-omnibox-url",
409
+ children: typeof location !== "undefined" ? location.host : "example.app"
410
+ }),
411
+ /* @__PURE__ */ jsx("span", {
412
+ className: "vpwa-target",
413
+ children: /* @__PURE__ */ jsx(ShareIcon, { size: 18 })
414
+ })
415
+ ]
416
+ });
417
+ }
418
+ function AndroidBarMock() {
419
+ return /* @__PURE__ */ jsxs("div", {
420
+ className: "vpwa-omnibox",
421
+ "aria-hidden": true,
422
+ children: [
423
+ /* @__PURE__ */ jsx(LockIcon, { size: 14 }),
424
+ /* @__PURE__ */ jsx("span", {
425
+ className: "vpwa-omnibox-url",
426
+ children: typeof location !== "undefined" ? location.host : "example.app"
427
+ }),
428
+ /* @__PURE__ */ jsx("span", {
429
+ className: "vpwa-target",
430
+ children: /* @__PURE__ */ jsx(EllipsisVerticalIcon, { size: 18 })
431
+ })
432
+ ]
433
+ });
434
+ }
435
+ function OmniboxInstallMock() {
436
+ return /* @__PURE__ */ jsxs("div", {
437
+ className: "vpwa-omnibox",
438
+ "aria-hidden": true,
439
+ children: [
440
+ /* @__PURE__ */ jsx(LockIcon, { size: 14 }),
441
+ /* @__PURE__ */ jsx("span", {
442
+ className: "vpwa-omnibox-url",
443
+ children: typeof location !== "undefined" ? location.host : "example.app"
444
+ }),
445
+ /* @__PURE__ */ jsx("span", {
446
+ className: "vpwa-target",
447
+ children: /* @__PURE__ */ jsx(MonitorDownIcon, { size: 18 })
448
+ })
449
+ ]
450
+ });
451
+ }
452
+ function SheetRowMock({ label }) {
453
+ return /* @__PURE__ */ jsxs("div", {
454
+ className: "vpwa-sheetrow",
455
+ "aria-hidden": true,
456
+ children: [/* @__PURE__ */ jsx("span", { children: label }), /* @__PURE__ */ jsx("span", {
457
+ className: "vpwa-target",
458
+ children: /* @__PURE__ */ jsx(SquarePlusIcon, { size: 20 })
459
+ })]
460
+ });
461
+ }
462
+ function ConfirmMock({ confirm }) {
463
+ return /* @__PURE__ */ jsxs("div", {
464
+ className: "vpwa-confirm",
465
+ "aria-hidden": true,
466
+ children: [/* @__PURE__ */ jsx("span", { children: "Cancel" }), /* @__PURE__ */ jsx("b", { children: confirm })]
467
+ });
468
+ }
469
+ function stepsFor(platform) {
470
+ switch (platform) {
471
+ case "ios-safari": return [
472
+ {
473
+ text: "Tap Share",
474
+ caption: "in the toolbar",
475
+ mock: /* @__PURE__ */ jsx(IosToolbarMock, {})
476
+ },
477
+ {
478
+ text: "Tap Add to Home Screen",
479
+ caption: "scroll down to find it",
480
+ mock: /* @__PURE__ */ jsx(SheetRowMock, { label: "Add to Home Screen" })
481
+ },
482
+ {
483
+ text: "Tap Add",
484
+ mock: /* @__PURE__ */ jsx(ConfirmMock, { confirm: "Add" })
485
+ }
486
+ ];
487
+ case "ios-chrome": return [
488
+ {
489
+ text: "Tap Share",
490
+ caption: "next to the address bar",
491
+ mock: /* @__PURE__ */ jsx(IosChromeBarMock, {})
492
+ },
493
+ {
494
+ text: "Tap Add to Home Screen",
495
+ caption: "scroll down to find it",
496
+ mock: /* @__PURE__ */ jsx(SheetRowMock, { label: "Add to Home Screen" })
497
+ },
498
+ {
499
+ text: "Tap Add",
500
+ mock: /* @__PURE__ */ jsx(ConfirmMock, { confirm: "Add" })
501
+ }
502
+ ];
503
+ case "ios-other": return [
504
+ { text: "Open the browser menu and tap Share" },
505
+ {
506
+ text: "Tap Add to Home Screen",
507
+ mock: /* @__PURE__ */ jsx(SheetRowMock, { label: "Add to Home Screen" })
508
+ },
509
+ {
510
+ text: "Tap Add",
511
+ mock: /* @__PURE__ */ jsx(ConfirmMock, { confirm: "Add" })
512
+ }
513
+ ];
514
+ case "android": return [
515
+ {
516
+ text: "Tap the menu",
517
+ caption: "top right",
518
+ mock: /* @__PURE__ */ jsx(AndroidBarMock, {})
519
+ },
520
+ {
521
+ text: "Tap Add to Home screen",
522
+ mock: /* @__PURE__ */ jsx(SheetRowMock, { label: "Add to Home screen" })
523
+ },
524
+ {
525
+ text: "Tap Add",
526
+ mock: /* @__PURE__ */ jsx(ConfirmMock, { confirm: "Add" })
527
+ }
528
+ ];
529
+ case "desktop-chrome": return [{
530
+ text: "Click the install icon",
531
+ caption: "right side of the address bar",
532
+ mock: /* @__PURE__ */ jsx(OmniboxInstallMock, {})
533
+ }, {
534
+ text: "Click Install",
535
+ mock: /* @__PURE__ */ jsx(ConfirmMock, { confirm: "Install" })
536
+ }];
537
+ case "desktop-safari": return [{ text: "Open File in the menu bar" }, {
538
+ text: "Click Add to Dock",
539
+ mock: /* @__PURE__ */ jsx(SheetRowMock, { label: "Add to Dock" })
540
+ }];
541
+ case "unsupported": return null;
542
+ }
543
+ }
544
+ function InstallGuide({ open, onClose, appName }) {
545
+ const { platform, canPrompt, installed, promptInstall } = useInstallPrompt();
546
+ const name = appName ?? defaultAppName();
547
+ const [mounted, setMounted] = useState(open);
548
+ const [shown, setShown] = useState(false);
549
+ useEffect(() => {
550
+ ensureStyles();
551
+ if (open) {
552
+ setMounted(true);
553
+ const raf = requestAnimationFrame(() => requestAnimationFrame(() => setShown(true)));
554
+ return () => cancelAnimationFrame(raf);
555
+ }
556
+ setShown(false);
557
+ const t = setTimeout(() => setMounted(false), 300);
558
+ return () => clearTimeout(t);
559
+ }, [open]);
560
+ useEffect(() => {
561
+ if (!open) return;
562
+ const onKey = (e) => {
563
+ if (e.key === "Escape") onClose();
564
+ };
565
+ document.addEventListener("keydown", onKey);
566
+ const prevOverflow = document.body.style.overflow;
567
+ document.body.style.overflow = "hidden";
568
+ return () => {
569
+ document.removeEventListener("keydown", onKey);
570
+ document.body.style.overflow = prevOverflow;
571
+ };
572
+ }, [open, onClose]);
573
+ const onNativeInstall = useCallback(async () => {
574
+ if (await promptInstall() === "accepted") onClose();
575
+ }, [promptInstall, onClose]);
576
+ if (!mounted || typeof document === "undefined") return null;
577
+ const steps = stepsFor(platform);
578
+ let body;
579
+ if (installed) body = /* @__PURE__ */ jsxs("div", {
580
+ className: "vpwa-sheetrow",
581
+ children: [/* @__PURE__ */ jsx("span", { children: "Already installed" }), /* @__PURE__ */ jsx("span", {
582
+ className: "vpwa-target",
583
+ children: /* @__PURE__ */ jsx(CheckIcon, { size: 20 })
584
+ })]
585
+ });
586
+ else if (canPrompt) body = /* @__PURE__ */ jsxs("div", {
587
+ className: "vpwa-actions",
588
+ children: [/* @__PURE__ */ jsx("button", {
589
+ type: "button",
590
+ className: "vpwa-btn",
591
+ onClick: () => void onNativeInstall(),
592
+ children: "Install"
593
+ }), /* @__PURE__ */ jsx("button", {
594
+ type: "button",
595
+ className: "vpwa-btn-ghost",
596
+ onClick: onClose,
597
+ children: "Not now"
598
+ })]
599
+ });
600
+ else if (steps) body = /* @__PURE__ */ jsx("ol", {
601
+ className: "vpwa-steps",
602
+ children: steps.map((step, i) => /* @__PURE__ */ jsxs("li", {
603
+ className: "vpwa-step",
604
+ children: [/* @__PURE__ */ jsxs("div", {
605
+ className: "vpwa-stephead",
606
+ children: [/* @__PURE__ */ jsx("span", {
607
+ className: "vpwa-stepnum",
608
+ children: i + 1
609
+ }), /* @__PURE__ */ jsxs("span", {
610
+ className: "vpwa-steptext",
611
+ children: [step.text, step.caption ? /* @__PURE__ */ jsxs("span", {
612
+ className: "vpwa-stepcap",
613
+ children: [" — ", step.caption]
614
+ }) : null]
615
+ })]
616
+ }), step.mock]
617
+ }, step.text))
618
+ });
619
+ else body = /* @__PURE__ */ jsx("p", {
620
+ className: "vpwa-sub",
621
+ style: { margin: 0 },
622
+ children: "This browser can’t install apps. Open this page in Chrome, Edge, or Safari to install."
623
+ });
624
+ return createPortal(/* @__PURE__ */ jsxs("div", {
625
+ className: `vpwa vpwa-overlay${shown ? " vpwa-open" : ""}`,
626
+ role: "dialog",
627
+ "aria-modal": "true",
628
+ "aria-label": `Install ${name}`,
629
+ children: [/* @__PURE__ */ jsx("div", {
630
+ className: "vpwa-backdrop",
631
+ onClick: onClose
632
+ }), /* @__PURE__ */ jsxs("div", {
633
+ className: "vpwa-sheet",
634
+ children: [
635
+ /* @__PURE__ */ jsx("div", { className: "vpwa-handle" }),
636
+ /* @__PURE__ */ jsx("button", {
637
+ type: "button",
638
+ className: "vpwa-x",
639
+ onClick: onClose,
640
+ "aria-label": "Close",
641
+ children: /* @__PURE__ */ jsx(XIcon, { size: 16 })
642
+ }),
643
+ /* @__PURE__ */ jsxs("h2", {
644
+ className: "vpwa-title",
645
+ children: ["Install ", name]
646
+ }),
647
+ /* @__PURE__ */ jsx("p", {
648
+ className: "vpwa-sub",
649
+ children: "Add it to your home screen — full screen, one tap away."
650
+ }),
651
+ body
652
+ ]
653
+ })]
654
+ }), document.body);
655
+ }
656
+ function VibesInstallPrompt({ appName, variant = "inline", className }) {
657
+ const { installed, canPrompt, supported, promptInstall } = useInstallPrompt();
658
+ const [dismissed, setDismissedState] = useState(isDismissed);
659
+ const [guideOpen, setGuideOpen] = useState(false);
660
+ const [entered, setEntered] = useState(variant !== "floating");
661
+ useEffect(() => {
662
+ ensureStyles();
663
+ if (variant === "floating") {
664
+ const t = setTimeout(() => setEntered(true), 50);
665
+ return () => clearTimeout(t);
666
+ }
667
+ }, [variant]);
668
+ if (typeof window === "undefined") return null;
669
+ if (isInIframe() || installed || dismissed || !supported) return null;
670
+ const name = appName ?? defaultAppName();
671
+ const dismiss = () => {
672
+ setDismissed();
673
+ setDismissedState(true);
674
+ };
675
+ const onInstall = async () => {
676
+ if (canPrompt) {
677
+ if (await promptInstall() === "unavailable") setGuideOpen(true);
678
+ return;
679
+ }
680
+ setGuideOpen(true);
681
+ };
682
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
683
+ className: [
684
+ "vpwa",
685
+ "vpwa-banner",
686
+ variant === "floating" ? "vpwa-banner-floating" : "",
687
+ variant === "floating" && entered ? "vpwa-in" : "",
688
+ className ?? ""
689
+ ].filter(Boolean).join(" "),
690
+ children: [
691
+ /* @__PURE__ */ jsx("div", {
692
+ className: "vpwa-chip",
693
+ children: /* @__PURE__ */ jsx(SmartphoneIcon, { size: 18 })
694
+ }),
695
+ /* @__PURE__ */ jsxs("div", {
696
+ className: "vpwa-banner-text",
697
+ children: [/* @__PURE__ */ jsxs("p", {
698
+ className: "vpwa-banner-title",
699
+ children: ["Install ", name]
700
+ }), /* @__PURE__ */ jsx("p", {
701
+ className: "vpwa-banner-cap",
702
+ children: "Add to home screen"
703
+ })]
704
+ }),
705
+ /* @__PURE__ */ jsx("button", {
706
+ type: "button",
707
+ className: "vpwa-btn-sm",
708
+ onClick: () => void onInstall(),
709
+ children: "Install"
710
+ }),
711
+ /* @__PURE__ */ jsx("button", {
712
+ type: "button",
713
+ className: "vpwa-x",
714
+ onClick: dismiss,
715
+ "aria-label": "Dismiss",
716
+ children: /* @__PURE__ */ jsx(XIcon, { size: 14 })
717
+ })
718
+ ]
719
+ }), /* @__PURE__ */ jsx(InstallGuide, {
720
+ open: guideOpen,
721
+ onClose: () => setGuideOpen(false),
722
+ appName
723
+ })] });
724
+ }
725
+ //#endregion
726
+ export { useInstallPrompt as i, VibesInstallPrompt as n, defaultAppName as r, InstallGuide as t };