@dloizides/game-shell 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Initial release: fit, install, crash, lifecycle, versionPoll, initGameShell, game-shell.css, IIFE bundle.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 dloizides
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # @dloizides/game-shell
2
+
3
+ The browser-side shell every EisaiPollis web game shares: it sizes the game to the height the player can actually see, offers install, shows a crash screen, pauses on hide, unlocks audio and notices a new deploy. Plain DOM + TypeScript, no runtime dependencies.
4
+
5
+ ```ts
6
+ import { initGameShell, GS_RESIZE_EVENT } from '@dloizides/game-shell';
7
+
8
+ const shell = initGameShell({
9
+ root: document.getElementById('game-container')!,
10
+ appName: 'Aurora',
11
+ appVersion: BUILD_VERSION,
12
+ labels: { install, addToHome, notNow, tapShare, tapAdd, installBody,
13
+ crashTitle, crashBody, reload, copyDetails, errorReported },
14
+ crash: { report: (e) => sendToSink(e) }, // return a short ref to show it
15
+ lifecycle: { onHidden: pauseAudio, onVisible: resumeAudio },
16
+ version: { url: '/version.txt', intervalMs: 300_000, onNewVersion: () => location.reload() },
17
+ });
18
+ window.addEventListener(GS_RESIZE_EVENT, () => game.scale.refresh());
19
+ ```
20
+
21
+ Unity / Godot / vanilla pages: load `dist/game-shell.iife.js` (global `GameShell`) and `dist/game-shell.css`.
22
+
23
+ | Module | What it does |
24
+ |---|---|
25
+ | `startFit` | `--gs-vh` on `:root` = `visualViewport.height` (else `innerHeight`), re-measured on viewport resize / resize / orientationchange, one `gs:resize` event per change. `.gs-root` sizes a container from it. Never size a game with `100vh`. |
26
+ | `startInstall` | Top-left chip on phones, bottom-right card at ≥1024 px, iOS Safari "Share → Add to Home Screen" steps. Hidden when standalone; "Not now" snoozes 14 days in `localStorage` (`gs-install-snooze:<appName>`). No scrim, no focus trap, Esc = Not now. |
27
+ | `startCrash` | `error` (ErrorEvent only) + `unhandledrejection` → `report()` every time, overlay once: Reload (focused) and Copy error details. |
28
+ | `startLifecycle` · `unlockAudio` · `prefersReducedMotion` | Visibility callbacks; resume an AudioContext on the first pointerdown/keydown; OS reduce-motion or `?reducedMotion=1`. |
29
+ | `startVersionPoll` | Fetches a URL (`no-store`) on an interval and when the tab is shown; calls `onNewVersion` once when the body changes. |
30
+
31
+ ## Theme
32
+
33
+ Every overlay reads only these CSS variables; defaults are a neutral dark theme. Set them on `:root` in the game:
34
+ `--gs-font-display --gs-font-text --gs-surface --gs-surface-solid --gs-edge --gs-ink --gs-ink-dim --gs-accent --gs-on-accent --gs-scrim --gs-radius --gs-radius-pill --gs-focus`.
@@ -0,0 +1,40 @@
1
+ :where(:root){--gs-font-display:system-ui,'Segoe UI',sans-serif;--gs-font-text:system-ui,'Segoe UI',sans-serif;--gs-surface:rgba(24,26,29,.95);--gs-surface-solid:#181a1d;--gs-edge:#41464d;--gs-ink:#f3f3f1;--gs-ink-dim:#b9bdc2;--gs-accent:#f3f3f1;--gs-on-accent:#181a1d;--gs-scrim:rgba(0,0,0,.72);--gs-radius:12px;--gs-radius-pill:12px;--gs-focus:#9cc2ff;--gs-safe-top:env(safe-area-inset-top,0px);--gs-safe-right:env(safe-area-inset-right,0px);--gs-safe-bottom:env(safe-area-inset-bottom,0px);--gs-safe-left:env(safe-area-inset-left,0px)}
2
+ .gs-root{width:100%;height:100vh;height:100dvh;height:var(--gs-vh,100dvh)}
3
+ .gs{font-family:var(--gs-font-text);color:var(--gs-ink);box-sizing:border-box}
4
+ .gs *{box-sizing:border-box}
5
+ .gs button{min-width:44px;min-height:44px;margin:0;border:0;background:none;color:inherit;font:inherit;cursor:pointer;-webkit-tap-highlight-color:transparent}
6
+ .gs button:focus-visible{outline:2px solid var(--gs-focus);outline-offset:2px}
7
+ .gs svg{width:20px;height:20px;flex:none}
8
+ .gs-ico{display:inline-flex}
9
+ .gs-btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:0 18px;border-radius:var(--gs-radius);font:700 16px/1 var(--gs-font-text);white-space:nowrap}
10
+ .gs-primary{background:var(--gs-accent);color:var(--gs-on-accent)}
11
+ .gs .gs-ghost{color:var(--gs-ink);border:1px solid var(--gs-edge)}
12
+ .gs-x{width:44px;height:44px;display:grid;place-items:center;color:var(--gs-ink-dim);flex:none}
13
+ .gs-txt{min-width:0}
14
+ .gs-txt b{display:block;font:400 18px/1.2 var(--gs-font-display);color:var(--gs-ink)}
15
+ .gs-txt span{display:block;font-size:14px;line-height:1.4;color:var(--gs-ink-dim);margin-top:3px}
16
+ .gs-install{position:fixed;z-index:2147483000;top:calc(12px + var(--gs-safe-top));left:calc(12px + var(--gs-safe-left))}
17
+ .gs-install[data-layout=card]{top:auto;left:auto;right:calc(24px + var(--gs-safe-right));bottom:calc(24px + var(--gs-safe-bottom))}
18
+ .gs-seal{display:flex;align-items:center;background:var(--gs-surface);border:1px solid var(--gs-edge);border-radius:var(--gs-radius-pill)}
19
+ .gs-chip{display:flex;align-items:center;gap:8px;padding:0 6px 0 14px;font:700 15px var(--gs-font-text);color:var(--gs-ink)}
20
+ .gs-chip svg{color:var(--gs-accent)}
21
+ .gs-seal .gs-x{border-left:1px solid var(--gs-edge)}
22
+ .gs-steps{margin:8px 0 0;width:262px;padding:10px 14px;list-style:none;background:var(--gs-surface);border:1px solid var(--gs-edge);border-radius:var(--gs-radius);display:grid;gap:2px;counter-reset:s}
23
+ .gs-steps[hidden]{display:none}
24
+ .gs-steps li{display:flex;align-items:center;gap:10px;min-height:36px;font-size:15px;color:var(--gs-ink);counter-increment:s}
25
+ .gs-steps li::before{content:counter(s);font:400 15px var(--gs-font-display);color:var(--gs-ink-dim);width:12px}
26
+ .gs-steps svg{width:17px;height:17px;color:var(--gs-accent)}
27
+ .gs-card{position:relative;width:344px;max-width:calc(100vw - 48px);padding:18px;border-radius:var(--gs-radius);background:var(--gs-surface);border:1px solid var(--gs-edge);display:grid;gap:12px}
28
+ .gs-card .gs-x{position:absolute;top:4px;right:4px}
29
+ .gs-card .gs-txt{padding-right:36px}
30
+ .gs-card .gs-acts{display:flex;gap:8px}
31
+ .gs-card .gs-steps{width:auto;margin:0;border:0;padding:0;background:none}
32
+ .gs-app{width:40px;height:40px;border-radius:var(--gs-radius);background:var(--gs-surface-solid);border:1px solid var(--gs-edge);display:grid;place-items:center}
33
+ .gs-app i{width:13px;height:13px;border-radius:50%;background:var(--gs-accent);box-shadow:0 0 10px var(--gs-accent)}
34
+ .gs-crash{position:fixed;inset:0;z-index:2147483001;background:var(--gs-scrim);display:grid;place-items:center;padding:20px}
35
+ .gs-panel{max-width:340px;background:var(--gs-surface-solid);border:1px solid var(--gs-edge);border-radius:var(--gs-radius);padding:24px}
36
+ .gs-h{margin:0;font:400 28px/1.15 var(--gs-font-display);color:var(--gs-ink)}
37
+ .gs-panel p{font-size:16px;line-height:1.5;color:var(--gs-ink-dim);margin:10px 0 0}
38
+ .gs-panel .gs-acts{display:flex;flex-direction:column;gap:8px;margin-top:20px}
39
+ .gs-panel .gs-ref{font:13px ui-monospace,monospace}
40
+ @media (min-width:1024px){.gs-panel{max-width:460px;padding:32px}.gs-panel .gs-acts{flex-direction:row}}
@@ -0,0 +1,39 @@
1
+ "use strict";var GameShell=(()=>{var N=Object.defineProperty;var Z=Object.getOwnPropertyDescriptor;var Q=Object.getOwnPropertyNames;var J=Object.prototype.hasOwnProperty;var X=(e,t)=>{for(var n in t)N(e,n,{get:t[n],enumerable:!0})},ee=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of Q(t))!J.call(e,o)&&o!==n&&N(e,o,{get:()=>t[o],enumerable:!(r=Z(t,o))||r.enumerable});return e};var te=e=>ee(N({},"__esModule",{value:!0}),e);var be={};X(be,{CRASH_KIND:()=>m,DEFAULT_SNOOZE_DAYS:()=>P,DESKTOP_MIN_WIDTH:()=>W,GAME_SHELL_CSS:()=>R,GS_RESIZE_EVENT:()=>O,GS_VH_VAR:()=>y,initGameShell:()=>q,injectStyles:()=>D,isDesktop:()=>v,isIosSafari:()=>I,isStandalone:()=>L,prefersReducedMotion:()=>$,startCrash:()=>b,startFit:()=>E,startInstall:()=>w,startLifecycle:()=>C,startVersionPoll:()=>k,unlockAudio:()=>U});var m={Error:"error",Rejection:"rejection"};function a(e,t,n="",r=""){let o=e.createElement(t);return n!==""&&(o.className=n),r!==""&&(o.textContent=r),o}function x(e,t){let n=a(e,"span","gs-ico");return n.innerHTML=t,n}function f(e,t){let n=a(e,"button",t.className);return n.type="button",t.icon!==void 0&&n.append(x(e,t.icon)),t.text!==void 0&&n.append(e.createTextNode(t.text)),t.ariaLabel!==void 0&&n.setAttribute("aria-label",t.ariaLabel),n.addEventListener("click",()=>t.onClick()),n}var _="gs-crash-title",A="gs-crash-body";function H(e){let t=a(e.doc,"div","gs gs-crash");t.setAttribute("role","alertdialog"),t.setAttribute("aria-modal","true"),t.setAttribute("aria-labelledby",_),t.setAttribute("aria-describedby",A);let n=a(e.doc,"div","gs-panel"),r=a(e.doc,"h2","gs-h",e.labels.crashTitle);r.id=_;let o=a(e.doc,"p","",e.labels.crashBody);o.id=A;let i=a(e.doc,"div","gs-acts"),l=f(e.doc,{className:"gs-btn gs-primary",text:e.labels.reload,onClick:e.onReload});return i.append(l,f(e.doc,{className:"gs-btn gs-ghost",text:e.labels.copyDetails,onClick:e.onCopy})),n.append(r,o,i),e.reference!==void 0&&e.reference!==""&&n.append(a(e.doc,"p","gs-ref",`${e.labels.errorReported} ${e.reference}`)),t.append(n),{root:t,reload:l}}function ne(e){let t=e.error,n=t instanceof Error?t.stack:void 0,r=e.filename!==""?`${e.filename}:${e.lineno}:${e.colno}`:void 0,o=e.message!==""?e.message:String(t);return{kind:m.Error,message:o,source:n!=null?n:r}}function oe(e){let t=e.reason;return t instanceof Error?{kind:m.Rejection,message:t.message,source:t.stack}:{kind:m.Rejection,message:String(t)}}function re(e,t){var n,r;try{return(r=(n=e.report)==null?void 0:n.call(e,t))!=null?r:void 0}catch(o){return}}function se(e,t,n){let r=[t.message,t.source,n].filter(o=>o!==void 0&&o!=="").join(`
2
+ `);try{e.navigator.clipboard.writeText(r).catch(()=>{})}catch(o){}}function b(e){var d,c;let t=(d=e.win)!=null?d:window,n=(c=e.reload)!=null?c:(()=>t.location.reload()),r=null,o=s=>{let g=re(e,s);if(r)return;let p=H({doc:t.document,labels:e.labels,reference:g,onReload:n,onCopy:()=>se(t,s,e.appVersion)});r=p.root,t.document.body.append(r),p.reload.focus()},i=s=>{s instanceof ErrorEvent&&o(ne(s))},l=s=>o(oe(s));return t.addEventListener("error",i),t.addEventListener("unhandledrejection",l),{dispose:()=>{t.removeEventListener("error",i),t.removeEventListener("unhandledrejection",l),r==null||r.remove(),r=null}}}var O="gs:resize",y="--gs-vh";function V(e){let t=e.visualViewport;return t?{width:t.width,height:t.height}:{width:e.innerWidth,height:e.innerHeight}}function ie(e){return typeof e.requestAnimationFrame=="function"?t=>{e.requestAnimationFrame(()=>t())}:t=>{e.setTimeout(t,0)}}function E(e=window){let t=e.document.documentElement,n=ie(e),r=V(e),o=!1,i=!1;t.style.setProperty(y,`${r.height}px`);let l=()=>{o=!1;let s=V(e),g=s.width===r.width&&s.height===r.height;i||g||(r=s,t.style.setProperty(y,`${s.height}px`),e.dispatchEvent(new CustomEvent(O,{detail:s})))},d=()=>{o||(o=!0,n(l))},c=e.visualViewport;return c==null||c.addEventListener("resize",d),e.addEventListener("resize",d),e.addEventListener("orientationchange",d),{dispose:()=>{i=!0,c==null||c.removeEventListener("resize",d),e.removeEventListener("resize",d),e.removeEventListener("orientationchange",d)}}}var S=e=>`<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${e}</svg>`,u={download:S('<path d="M12 4v11M7 10l5 5 5-5M5 20h14"/>'),add:S('<path d="M12 5v14M5 12h14"/>'),close:S('<path d="M6 6l12 12M18 6L6 18"/>'),share:S('<path d="M12 15V4M8 8l4-4 4 4M6 12v8h12v-8"/>')};var z="gs-install-steps";function B(e){let t=a(e.doc,"ol","gs-steps");t.id=z;let n=a(e.doc,"li");n.append(x(e.doc,u.share),e.doc.createTextNode(e.labels.tapShare));let r=a(e.doc,"li");return r.append(x(e.doc,u.add),e.doc.createTextNode(e.labels.tapAdd)),t.append(n,r),t}function M(e){return f(e.doc,{className:"gs-x",ariaLabel:e.labels.notNow,icon:u.close,onClick:e.onDismiss})}function ae(e){let t=a(e.doc,"div","gs-seal");if(e.variant==="prompt")return t.append(f(e.doc,{className:"gs-chip",text:e.labels.install,icon:u.download,onClick:e.onInstall})),t.append(M(e)),[t];let n=B(e);n.hidden=!0;let r=f(e.doc,{className:"gs-chip",text:e.labels.addToHome,icon:u.add,onClick:()=>{n.hidden=!n.hidden,r.setAttribute("aria-expanded",String(!n.hidden))}});return r.setAttribute("aria-expanded","false"),r.setAttribute("aria-controls",z),t.append(r,M(e)),[t,n]}function le(e){let t=a(e.doc,"div","gs-card"),n=a(e.doc,"div","gs-app");n.append(a(e.doc,"i"));let r=a(e.doc,"div","gs-txt");r.append(a(e.doc,"b","",e.appName),a(e.doc,"span","",e.labels.installBody));let o=a(e.doc,"div","gs-acts");return e.variant==="prompt"&&o.append(f(e.doc,{className:"gs-btn gs-primary",text:e.labels.install,icon:u.download,onClick:e.onInstall})),o.append(f(e.doc,{className:"gs-btn gs-ghost",text:e.labels.notNow,onClick:e.onDismiss})),t.append(M(e),n,r),e.variant==="ios"&&t.append(B(e)),t.append(o),[t]}function K(e){let t=a(e.doc,"div","gs gs-install");return t.dataset.layout=e.layout,t.dataset.variant=e.variant,t.setAttribute("role","region"),t.setAttribute("aria-label",e.variant==="prompt"?e.labels.install:e.labels.addToHome),t.addEventListener("keydown",n=>{n.key==="Escape"&&e.onDismiss()}),t.append(...e.layout==="card"?le(e):ae(e)),t}var W=1024,de=/iPad|iPhone|iPod/,ce=/CriOS|FxiOS|EdgiOS|OPiOS/;function L(e){return typeof e.matchMedia=="function"&&e.matchMedia("(display-mode: standalone)").matches||e.navigator.standalone===!0}function I(e){let t=e.navigator.userAgent,n=t.includes("Macintosh")&&e.navigator.maxTouchPoints>1;return(de.test(t)||n)&&!ce.test(t)}function v(e){return e.innerWidth>=1024}function T(e,t){return t!=null?t:`gs-install-snooze:${e}`}function G(e,t,n,r){try{let o=e.localStorage.getItem(t);if(o===null)return!1;let i=Number(o);return Number.isFinite(i)&&r-i<n*864e5}catch(o){return!1}}function j(e,t,n){try{e.localStorage.setItem(t,String(n))}catch(r){}}var P=14,pe="accepted";function ge(e,t,n){let r=T(e.appName,e.storageKey),o={deferred:null,variant:null,node:null},i=()=>{var s;(s=o.node)==null||s.remove(),o.node=null},l=()=>{j(t,r,n()),i()},d=async()=>{let s=o.deferred;if(o.deferred=null,!!s)try{if(await s.prompt(),(await s.userChoice).outcome===pe){i();return}l()}catch(g){i()}};return{state:o,mount:()=>{o.variant!==null&&(i(),o.node=K({doc:t.document,layout:v(t)?"card":"chip",variant:o.variant,appName:e.appName,labels:e.labels,onInstall:()=>{d()},onDismiss:l}),t.document.body.append(o.node))},unmount:i}}function w(e){var c,s,g;let t=(c=e.win)!=null?c:window,n=(s=e.now)!=null?s:(()=>Date.now()),r=T(e.appName,e.storageKey);if(L(t)||G(t,r,(g=e.snoozeDays)!=null?g:P,n()))return{dispose:()=>{}};let o=ge(e,t,n),i=p=>{p.preventDefault(),o.state.deferred=p,o.state.variant="prompt",o.mount()},l=()=>{o.state.variant=null,o.unmount()},d=()=>{let p=o.state.node;p&&p.dataset.layout==="card"!==v(t)&&o.mount()};return t.addEventListener("beforeinstallprompt",i),t.addEventListener("appinstalled",l),t.addEventListener("resize",d),I(t)&&(o.state.variant="ios",o.mount()),{dispose:()=>{t.removeEventListener("beforeinstallprompt",i),t.removeEventListener("appinstalled",l),t.removeEventListener("resize",d),o.unmount()}}}var fe="(prefers-reduced-motion: reduce)",F=["pointerdown","keydown"];function C(e,t=document){let n=()=>{var r,o;if(t.visibilityState==="hidden"){(r=e.onHidden)==null||r.call(e);return}(o=e.onVisible)==null||o.call(e)};return t.addEventListener("visibilitychange",n),{dispose:()=>t.removeEventListener("visibilitychange",n)}}function U(e,t=window){let n=()=>{F.forEach(o=>t.removeEventListener(o,r,!0))};function r(){n();try{Promise.resolve(e.resume()).catch(()=>{})}catch(o){}}return F.forEach(o=>t.addEventListener(o,r,!0)),{dispose:n}}function $(e=window){return new URLSearchParams(e.location.search).get("reducedMotion")==="1"?!0:typeof e.matchMedia=="function"&&e.matchMedia(fe).matches}var Y="gs-styles",ue=":where(:root){--gs-font-display:system-ui,'Segoe UI',sans-serif;--gs-font-text:system-ui,'Segoe UI',sans-serif;--gs-surface:rgba(24,26,29,.95);--gs-surface-solid:#181a1d;--gs-edge:#41464d;--gs-ink:#f3f3f1;--gs-ink-dim:#b9bdc2;--gs-accent:#f3f3f1;--gs-on-accent:#181a1d;--gs-scrim:rgba(0,0,0,.72);--gs-radius:12px;--gs-radius-pill:12px;--gs-focus:#9cc2ff;--gs-safe-top:env(safe-area-inset-top,0px);--gs-safe-right:env(safe-area-inset-right,0px);--gs-safe-bottom:env(safe-area-inset-bottom,0px);--gs-safe-left:env(safe-area-inset-left,0px)}",me=".gs-root{width:100%;height:100vh;height:100dvh;height:var(--gs-vh,100dvh)}",ve=`.gs{font-family:var(--gs-font-text);color:var(--gs-ink);box-sizing:border-box}
3
+ .gs *{box-sizing:border-box}
4
+ .gs button{min-width:44px;min-height:44px;margin:0;border:0;background:none;color:inherit;font:inherit;cursor:pointer;-webkit-tap-highlight-color:transparent}
5
+ .gs button:focus-visible{outline:2px solid var(--gs-focus);outline-offset:2px}
6
+ .gs svg{width:20px;height:20px;flex:none}
7
+ .gs-ico{display:inline-flex}
8
+ .gs-btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:0 18px;border-radius:var(--gs-radius);font:700 16px/1 var(--gs-font-text);white-space:nowrap}
9
+ .gs-primary{background:var(--gs-accent);color:var(--gs-on-accent)}
10
+ .gs .gs-ghost{color:var(--gs-ink);border:1px solid var(--gs-edge)}
11
+ .gs-x{width:44px;height:44px;display:grid;place-items:center;color:var(--gs-ink-dim);flex:none}
12
+ .gs-txt{min-width:0}
13
+ .gs-txt b{display:block;font:400 18px/1.2 var(--gs-font-display);color:var(--gs-ink)}
14
+ .gs-txt span{display:block;font-size:14px;line-height:1.4;color:var(--gs-ink-dim);margin-top:3px}`,he=`.gs-install{position:fixed;z-index:2147483000;top:calc(12px + var(--gs-safe-top));left:calc(12px + var(--gs-safe-left))}
15
+ .gs-install[data-layout=card]{top:auto;left:auto;right:calc(24px + var(--gs-safe-right));bottom:calc(24px + var(--gs-safe-bottom))}
16
+ .gs-seal{display:flex;align-items:center;background:var(--gs-surface);border:1px solid var(--gs-edge);border-radius:var(--gs-radius-pill)}
17
+ .gs-chip{display:flex;align-items:center;gap:8px;padding:0 6px 0 14px;font:700 15px var(--gs-font-text);color:var(--gs-ink)}
18
+ .gs-chip svg{color:var(--gs-accent)}
19
+ .gs-seal .gs-x{border-left:1px solid var(--gs-edge)}
20
+ .gs-steps{margin:8px 0 0;width:262px;padding:10px 14px;list-style:none;background:var(--gs-surface);border:1px solid var(--gs-edge);border-radius:var(--gs-radius);display:grid;gap:2px;counter-reset:s}
21
+ .gs-steps[hidden]{display:none}
22
+ .gs-steps li{display:flex;align-items:center;gap:10px;min-height:36px;font-size:15px;color:var(--gs-ink);counter-increment:s}
23
+ .gs-steps li::before{content:counter(s);font:400 15px var(--gs-font-display);color:var(--gs-ink-dim);width:12px}
24
+ .gs-steps svg{width:17px;height:17px;color:var(--gs-accent)}
25
+ .gs-card{position:relative;width:344px;max-width:calc(100vw - 48px);padding:18px;border-radius:var(--gs-radius);background:var(--gs-surface);border:1px solid var(--gs-edge);display:grid;gap:12px}
26
+ .gs-card .gs-x{position:absolute;top:4px;right:4px}
27
+ .gs-card .gs-txt{padding-right:36px}
28
+ .gs-card .gs-acts{display:flex;gap:8px}
29
+ .gs-card .gs-steps{width:auto;margin:0;border:0;padding:0;background:none}
30
+ .gs-app{width:40px;height:40px;border-radius:var(--gs-radius);background:var(--gs-surface-solid);border:1px solid var(--gs-edge);display:grid;place-items:center}
31
+ .gs-app i{width:13px;height:13px;border-radius:50%;background:var(--gs-accent);box-shadow:0 0 10px var(--gs-accent)}`,xe=`.gs-crash{position:fixed;inset:0;z-index:2147483001;background:var(--gs-scrim);display:grid;place-items:center;padding:20px}
32
+ .gs-panel{max-width:340px;background:var(--gs-surface-solid);border:1px solid var(--gs-edge);border-radius:var(--gs-radius);padding:24px}
33
+ .gs-h{margin:0;font:400 28px/1.15 var(--gs-font-display);color:var(--gs-ink)}
34
+ .gs-panel p{font-size:16px;line-height:1.5;color:var(--gs-ink-dim);margin:10px 0 0}
35
+ .gs-panel .gs-acts{display:flex;flex-direction:column;gap:8px;margin-top:20px}
36
+ .gs-panel .gs-ref{font:13px ui-monospace,monospace}
37
+ @media (min-width:1024px){.gs-panel{max-width:460px;padding:32px}.gs-panel .gs-acts{flex-direction:row}}`,R=[ue,me,ve,he,xe].join(`
38
+ `);function D(e=document){if(e.getElementById(Y))return;let t=e.createElement("style");t.id=Y,t.textContent=R,e.head.append(t)}function k(e){var s,g;let t=(s=e.win)!=null?s:window,n=(g=e.fetchFn)!=null?g:((p,h)=>t.fetch(p,h)),r=null,o=!1,i=!1,l=async()=>{if(!(o||i)){i=!0;try{let p=await n(e.url,{cache:"no-store"});if(!p.ok)return;let h=(await p.text()).trim();r===null?r=h:h!==r&&(o=!0,e.onNewVersion())}catch(p){}finally{i=!1}}},d=()=>{t.document.visibilityState==="visible"&&l()};l();let c=t.setInterval(()=>{l()},e.intervalMs);return t.document.addEventListener("visibilitychange",d),{dispose:()=>{t.clearInterval(c),t.document.removeEventListener("visibilitychange",d)}}}function q(e){var r;let t=e.root.ownerDocument;D(t),e.root.classList.add("gs-root");let n=[E()];return e.install!==!1&&n.push(w({appName:e.appName,labels:e.labels,...e.install})),e.crash!==!1&&n.push(b({labels:e.labels,report:(r=e.crash)==null?void 0:r.report,appVersion:e.appVersion})),e.lifecycle&&n.push(C(e.lifecycle,t)),e.version&&n.push(k(e.version)),{dispose:()=>{n.forEach(o=>o.dispose()),e.root.classList.remove("gs-root")}}}return te(be);})();
39
+ //# sourceMappingURL=game-shell.iife.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/crash/crashKind.ts","../src/dom.ts","../src/crash/crashOverlay.ts","../src/crash/crash.ts","../src/fit/fit.ts","../src/icons.ts","../src/install/banner.ts","../src/install/platform.ts","../src/install/snooze.ts","../src/install/install.ts","../src/lifecycle/lifecycle.ts","../src/styles.ts","../src/version/versionPoll.ts","../src/initGameShell.ts"],"sourcesContent":["export { initGameShell } from './initGameShell';\nexport type { GameShellOptions } from './initGameShell';\nexport { startFit, GS_RESIZE_EVENT, GS_VH_VAR } from './fit/fit';\nexport type { GsResizeDetail } from './fit/fit';\nexport { startInstall, DEFAULT_SNOOZE_DAYS } from './install/install';\nexport type { InstallOptions, BeforeInstallPromptEvent } from './install/install';\nexport { isStandalone, isIosSafari, isDesktop, DESKTOP_MIN_WIDTH } from './install/platform';\nexport { startCrash } from './crash/crash';\nexport type { CrashOptions } from './crash/crash';\nexport { CRASH_KIND } from './crash/crashKind';\nexport type { CrashInfo, CrashKind } from './crash/crashKind';\nexport { startLifecycle, unlockAudio, prefersReducedMotion } from './lifecycle/lifecycle';\nexport type { LifecycleOptions, ResumableAudio } from './lifecycle/lifecycle';\nexport { startVersionPoll } from './version/versionPoll';\nexport type { VersionPollOptions } from './version/versionPoll';\nexport { GAME_SHELL_CSS, injectStyles } from './styles';\nexport type { Disposable, GameShellLabels, InstallLabels, CrashLabels } from './types';\n","export const CRASH_KIND = {\n Error: 'error',\n Rejection: 'rejection',\n} as const;\n\nexport type CrashKind = (typeof CRASH_KIND)[keyof typeof CRASH_KIND];\n\nexport interface CrashInfo {\n kind: CrashKind;\n message: string;\n source?: string;\n}\n","export function el<K extends keyof HTMLElementTagNameMap>(\n doc: Document,\n tag: K,\n className = '',\n text = '',\n): HTMLElementTagNameMap[K] {\n const node = doc.createElement(tag);\n if (className !== '') {\n node.className = className;\n }\n if (text !== '') {\n node.textContent = text;\n }\n return node;\n}\n\nexport function iconSpan(doc: Document, svg: string): HTMLSpanElement {\n const span = el(doc, 'span', 'gs-ico');\n span.innerHTML = svg;\n return span;\n}\n\nexport interface ButtonSpec {\n className: string;\n text?: string;\n ariaLabel?: string;\n icon?: string;\n onClick(): void;\n}\n\nexport function button(doc: Document, spec: ButtonSpec): HTMLButtonElement {\n const btn = el(doc, 'button', spec.className);\n btn.type = 'button';\n if (spec.icon !== undefined) {\n btn.append(iconSpan(doc, spec.icon));\n }\n if (spec.text !== undefined) {\n btn.append(doc.createTextNode(spec.text));\n }\n if (spec.ariaLabel !== undefined) {\n btn.setAttribute('aria-label', spec.ariaLabel);\n }\n btn.addEventListener('click', () => spec.onClick());\n return btn;\n}\n","import { button, el } from '../dom';\nimport type { CrashLabels } from '../types';\n\nexport const CRASH_TITLE_ID = 'gs-crash-title';\nconst CRASH_BODY_ID = 'gs-crash-body';\n\nexport interface CrashOverlayProps {\n doc: Document;\n labels: CrashLabels;\n reference?: string;\n onReload(): void;\n onCopy(): void;\n}\n\nexport interface CrashOverlayView {\n root: HTMLElement;\n reload: HTMLButtonElement;\n}\n\nexport function renderCrashOverlay(p: CrashOverlayProps): CrashOverlayView {\n const root = el(p.doc, 'div', 'gs gs-crash');\n root.setAttribute('role', 'alertdialog');\n root.setAttribute('aria-modal', 'true');\n root.setAttribute('aria-labelledby', CRASH_TITLE_ID);\n root.setAttribute('aria-describedby', CRASH_BODY_ID);\n const panel = el(p.doc, 'div', 'gs-panel');\n const title = el(p.doc, 'h2', 'gs-h', p.labels.crashTitle);\n title.id = CRASH_TITLE_ID;\n const body = el(p.doc, 'p', '', p.labels.crashBody);\n body.id = CRASH_BODY_ID;\n const acts = el(p.doc, 'div', 'gs-acts');\n const reload = button(p.doc, { className: 'gs-btn gs-primary', text: p.labels.reload, onClick: p.onReload });\n acts.append(reload, button(p.doc, { className: 'gs-btn gs-ghost', text: p.labels.copyDetails, onClick: p.onCopy }));\n panel.append(title, body, acts);\n if (p.reference !== undefined && p.reference !== '') {\n panel.append(el(p.doc, 'p', 'gs-ref', `${p.labels.errorReported} ${p.reference}`));\n }\n root.append(panel);\n return { root, reload };\n}\n","import type { CrashLabels, Disposable } from '../types';\nimport { CRASH_KIND, type CrashInfo } from './crashKind';\nimport { renderCrashOverlay } from './crashOverlay';\n\nexport interface CrashOptions {\n labels: CrashLabels;\n report?: (info: CrashInfo) => string | void;\n appVersion?: string;\n win?: Window;\n reload?: () => void;\n}\n\nfunction fromErrorEvent(e: ErrorEvent): CrashInfo {\n const err: unknown = e.error;\n const stack = err instanceof Error ? err.stack : undefined;\n const location = e.filename !== '' ? `${e.filename}:${e.lineno}:${e.colno}` : undefined;\n const message = e.message !== '' ? e.message : String(err);\n return { kind: CRASH_KIND.Error, message, source: stack ?? location };\n}\n\nfunction fromRejection(e: Event): CrashInfo {\n const reason: unknown = (e as PromiseRejectionEvent).reason;\n if (reason instanceof Error) {\n return { kind: CRASH_KIND.Rejection, message: reason.message, source: reason.stack };\n }\n return { kind: CRASH_KIND.Rejection, message: String(reason) };\n}\n\nfunction safeReport(opts: CrashOptions, info: CrashInfo): string | undefined {\n try {\n return opts.report?.(info) ?? undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction copyDetails(win: Window, info: CrashInfo, appVersion?: string): void {\n const text = [info.message, info.source, appVersion]\n .filter((part): part is string => part !== undefined && part !== '')\n .join('\\n');\n try {\n win.navigator.clipboard.writeText(text).catch(() => undefined);\n } catch {\n // No clipboard API (insecure context): nothing to copy into.\n }\n}\n\nexport function startCrash(opts: CrashOptions): Disposable {\n const win = opts.win ?? window;\n const reload = opts.reload ?? ((): void => win.location.reload());\n let overlay: HTMLElement | null = null;\n\n const handle = (info: CrashInfo): void => {\n const reference = safeReport(opts, info);\n if (overlay) {\n return;\n }\n const view = renderCrashOverlay({\n doc: win.document,\n labels: opts.labels,\n reference,\n onReload: reload,\n onCopy: () => copyDetails(win, info, opts.appVersion),\n });\n overlay = view.root;\n win.document.body.append(overlay);\n view.reload.focus();\n };\n const onError = (e: Event): void => {\n if (e instanceof ErrorEvent) {\n handle(fromErrorEvent(e));\n }\n };\n const onRejection = (e: Event): void => handle(fromRejection(e));\n\n win.addEventListener('error', onError);\n win.addEventListener('unhandledrejection', onRejection);\n return {\n dispose: (): void => {\n win.removeEventListener('error', onError);\n win.removeEventListener('unhandledrejection', onRejection);\n overlay?.remove();\n overlay = null;\n },\n };\n}\n","import type { Disposable } from '../types';\n\nexport const GS_RESIZE_EVENT = 'gs:resize';\nexport const GS_VH_VAR = '--gs-vh';\n\nexport interface GsResizeDetail {\n width: number;\n height: number;\n}\n\nfunction measure(win: Window): GsResizeDetail {\n const vv = win.visualViewport;\n if (vv) {\n return { width: vv.width, height: vv.height };\n }\n return { width: win.innerWidth, height: win.innerHeight };\n}\n\nfunction scheduler(win: Window): (cb: () => void) => void {\n if (typeof win.requestAnimationFrame === 'function') {\n return (cb) => {\n win.requestAnimationFrame(() => cb());\n };\n }\n return (cb) => {\n win.setTimeout(cb, 0);\n };\n}\n\nexport function startFit(win: Window = window): Disposable {\n const root = win.document.documentElement;\n const schedule = scheduler(win);\n let last = measure(win);\n let pending = false;\n let disposed = false;\n root.style.setProperty(GS_VH_VAR, `${last.height}px`);\n\n const apply = (): void => {\n pending = false;\n const next = measure(win);\n const unchanged = next.width === last.width && next.height === last.height;\n if (disposed || unchanged) {\n return;\n }\n last = next;\n root.style.setProperty(GS_VH_VAR, `${next.height}px`);\n win.dispatchEvent(new CustomEvent<GsResizeDetail>(GS_RESIZE_EVENT, { detail: next }));\n };\n const onChange = (): void => {\n if (pending) {\n return;\n }\n pending = true;\n schedule(apply);\n };\n\n const vv = win.visualViewport;\n vv?.addEventListener('resize', onChange);\n win.addEventListener('resize', onChange);\n win.addEventListener('orientationchange', onChange);\n return {\n dispose: (): void => {\n disposed = true;\n vv?.removeEventListener('resize', onChange);\n win.removeEventListener('resize', onChange);\n win.removeEventListener('orientationchange', onChange);\n },\n };\n}\n","const svg = (paths: string): string =>\n `<svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" focusable=\"false\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">${paths}</svg>`;\n\nexport const ICONS = {\n download: svg('<path d=\"M12 4v11M7 10l5 5 5-5M5 20h14\"/>'),\n add: svg('<path d=\"M12 5v14M5 12h14\"/>'),\n close: svg('<path d=\"M6 6l12 12M18 6L6 18\"/>'),\n share: svg('<path d=\"M12 15V4M8 8l4-4 4 4M6 12v8h12v-8\"/>'),\n} as const;\n","import { button, el, iconSpan } from '../dom';\nimport { ICONS } from '../icons';\nimport type { InstallLabels } from '../types';\nimport { BannerLayout } from './BannerLayout';\nimport { InstallVariant } from './InstallVariant';\n\nexport const STEPS_ID = 'gs-install-steps';\n\nexport interface BannerProps {\n doc: Document;\n layout: BannerLayout;\n variant: InstallVariant;\n appName: string;\n labels: InstallLabels;\n onInstall(): void;\n onDismiss(): void;\n}\n\nfunction renderSteps(p: BannerProps): HTMLOListElement {\n const list = el(p.doc, 'ol', 'gs-steps');\n list.id = STEPS_ID;\n const share = el(p.doc, 'li');\n share.append(iconSpan(p.doc, ICONS.share), p.doc.createTextNode(p.labels.tapShare));\n const add = el(p.doc, 'li');\n add.append(iconSpan(p.doc, ICONS.add), p.doc.createTextNode(p.labels.tapAdd));\n list.append(share, add);\n return list;\n}\n\nfunction closeButton(p: BannerProps): HTMLButtonElement {\n return button(p.doc, { className: 'gs-x', ariaLabel: p.labels.notNow, icon: ICONS.close, onClick: p.onDismiss });\n}\n\nfunction renderChip(p: BannerProps): HTMLElement[] {\n const seal = el(p.doc, 'div', 'gs-seal');\n if (p.variant === InstallVariant.Prompt) {\n seal.append(button(p.doc, { className: 'gs-chip', text: p.labels.install, icon: ICONS.download, onClick: p.onInstall }));\n seal.append(closeButton(p));\n return [seal];\n }\n const steps = renderSteps(p);\n steps.hidden = true;\n const chip = button(p.doc, {\n className: 'gs-chip',\n text: p.labels.addToHome,\n icon: ICONS.add,\n onClick: () => {\n steps.hidden = !steps.hidden;\n chip.setAttribute('aria-expanded', String(!steps.hidden));\n },\n });\n chip.setAttribute('aria-expanded', 'false');\n chip.setAttribute('aria-controls', STEPS_ID);\n seal.append(chip, closeButton(p));\n return [seal, steps];\n}\n\nfunction renderCard(p: BannerProps): HTMLElement[] {\n const card = el(p.doc, 'div', 'gs-card');\n const app = el(p.doc, 'div', 'gs-app');\n app.append(el(p.doc, 'i'));\n const txt = el(p.doc, 'div', 'gs-txt');\n txt.append(el(p.doc, 'b', '', p.appName), el(p.doc, 'span', '', p.labels.installBody));\n const acts = el(p.doc, 'div', 'gs-acts');\n if (p.variant === InstallVariant.Prompt) {\n acts.append(button(p.doc, { className: 'gs-btn gs-primary', text: p.labels.install, icon: ICONS.download, onClick: p.onInstall }));\n }\n acts.append(button(p.doc, { className: 'gs-btn gs-ghost', text: p.labels.notNow, onClick: p.onDismiss }));\n card.append(closeButton(p), app, txt);\n if (p.variant === InstallVariant.Ios) {\n card.append(renderSteps(p));\n }\n card.append(acts);\n return [card];\n}\n\nexport function renderBanner(p: BannerProps): HTMLElement {\n const wrap = el(p.doc, 'div', 'gs gs-install');\n wrap.dataset.layout = p.layout;\n wrap.dataset.variant = p.variant;\n wrap.setAttribute('role', 'region');\n wrap.setAttribute('aria-label', p.variant === InstallVariant.Prompt ? p.labels.install : p.labels.addToHome);\n wrap.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') {\n p.onDismiss();\n }\n });\n wrap.append(...(p.layout === BannerLayout.Card ? renderCard(p) : renderChip(p)));\n return wrap;\n}\n","export const DESKTOP_MIN_WIDTH = 1024;\nconst IOS_DEVICE_RE = /iPad|iPhone|iPod/;\nconst IOS_OTHER_BROWSER_RE = /CriOS|FxiOS|EdgiOS|OPiOS/;\n\ninterface IosNavigator extends Navigator {\n standalone?: boolean;\n}\n\nexport function isStandalone(win: Window): boolean {\n const media = typeof win.matchMedia === 'function' && win.matchMedia('(display-mode: standalone)').matches;\n return media || (win.navigator as IosNavigator).standalone === true;\n}\n\nexport function isIosSafari(win: Window): boolean {\n const ua = win.navigator.userAgent;\n const iPadAsMac = ua.includes('Macintosh') && win.navigator.maxTouchPoints > 1;\n const iosDevice = IOS_DEVICE_RE.test(ua) || iPadAsMac;\n return iosDevice && !IOS_OTHER_BROWSER_RE.test(ua);\n}\n\nexport function isDesktop(win: Window): boolean {\n return win.innerWidth >= DESKTOP_MIN_WIDTH;\n}\n","const DAY_MS = 86_400_000;\n\nexport function snoozeKey(appName: string, override?: string): string {\n return override ?? `gs-install-snooze:${appName}`;\n}\n\nexport function isSnoozed(win: Window, key: string, days: number, now: number): boolean {\n try {\n const raw = win.localStorage.getItem(key);\n if (raw === null) {\n return false;\n }\n const at = Number(raw);\n return Number.isFinite(at) && now - at < days * DAY_MS;\n } catch {\n return false;\n }\n}\n\nexport function writeSnooze(win: Window, key: string, now: number): void {\n try {\n win.localStorage.setItem(key, String(now));\n } catch {\n // Storage blocked (private mode, quota): the banner simply shows again next load.\n }\n}\n","import type { Disposable, InstallLabels } from '../types';\nimport { renderBanner } from './banner';\nimport { BannerLayout } from './BannerLayout';\nimport { InstallVariant } from './InstallVariant';\nimport { isDesktop, isIosSafari, isStandalone } from './platform';\nimport { isSnoozed, snoozeKey, writeSnooze } from './snooze';\n\nexport const DEFAULT_SNOOZE_DAYS = 14;\nconst ACCEPTED = 'accepted';\n\nexport interface BeforeInstallPromptEvent extends Event {\n prompt(): Promise<void>;\n userChoice: Promise<{ outcome: string }>;\n}\n\nexport interface InstallOptions {\n appName: string;\n labels: InstallLabels;\n snoozeDays?: number;\n storageKey?: string;\n win?: Window;\n now?: () => number;\n}\n\ninterface InstallState {\n deferred: BeforeInstallPromptEvent | null;\n variant: InstallVariant | null;\n node: HTMLElement | null;\n}\n\nfunction createController(opts: InstallOptions, win: Window, now: () => number): {\n state: InstallState;\n mount(): void;\n unmount(): void;\n} {\n const key = snoozeKey(opts.appName, opts.storageKey);\n const state: InstallState = { deferred: null, variant: null, node: null };\n const unmount = (): void => {\n state.node?.remove();\n state.node = null;\n };\n const dismiss = (): void => {\n writeSnooze(win, key, now());\n unmount();\n };\n const install = async (): Promise<void> => {\n const evt = state.deferred;\n state.deferred = null;\n if (!evt) {\n return;\n }\n try {\n await evt.prompt();\n const choice = await evt.userChoice;\n if (choice.outcome === ACCEPTED) {\n unmount();\n return;\n }\n dismiss();\n } catch {\n unmount();\n }\n };\n const mount = (): void => {\n if (state.variant === null) {\n return;\n }\n unmount();\n state.node = renderBanner({\n doc: win.document,\n layout: isDesktop(win) ? BannerLayout.Card : BannerLayout.Chip,\n variant: state.variant,\n appName: opts.appName,\n labels: opts.labels,\n onInstall: () => void install(),\n onDismiss: dismiss,\n });\n win.document.body.append(state.node);\n };\n return { state, mount, unmount };\n}\n\nexport function startInstall(opts: InstallOptions): Disposable {\n const win = opts.win ?? window;\n const now = opts.now ?? ((): number => Date.now());\n const key = snoozeKey(opts.appName, opts.storageKey);\n if (isStandalone(win) || isSnoozed(win, key, opts.snoozeDays ?? DEFAULT_SNOOZE_DAYS, now())) {\n return { dispose: (): void => undefined };\n }\n const ctl = createController(opts, win, now);\n const onPrompt = (e: Event): void => {\n e.preventDefault();\n ctl.state.deferred = e as BeforeInstallPromptEvent;\n ctl.state.variant = InstallVariant.Prompt;\n ctl.mount();\n };\n const onInstalled = (): void => {\n ctl.state.variant = null;\n ctl.unmount();\n };\n const onResize = (): void => {\n const node = ctl.state.node;\n if (node && (node.dataset.layout === BannerLayout.Card) !== isDesktop(win)) {\n ctl.mount();\n }\n };\n win.addEventListener('beforeinstallprompt', onPrompt);\n win.addEventListener('appinstalled', onInstalled);\n win.addEventListener('resize', onResize);\n if (isIosSafari(win)) {\n ctl.state.variant = InstallVariant.Ios;\n ctl.mount();\n }\n return {\n dispose: (): void => {\n win.removeEventListener('beforeinstallprompt', onPrompt);\n win.removeEventListener('appinstalled', onInstalled);\n win.removeEventListener('resize', onResize);\n ctl.unmount();\n },\n };\n}\n","import type { Disposable } from '../types';\n\nexport interface LifecycleOptions {\n onHidden?(): void;\n onVisible?(): void;\n}\n\nexport interface ResumableAudio {\n resume(): Promise<void> | void;\n}\n\nconst REDUCED_MOTION_QUERY = '(prefers-reduced-motion: reduce)';\nconst UNLOCK_EVENTS = ['pointerdown', 'keydown'] as const;\n\nexport function startLifecycle(opts: LifecycleOptions, doc: Document = document): Disposable {\n const onChange = (): void => {\n if (doc.visibilityState === 'hidden') {\n opts.onHidden?.();\n return;\n }\n opts.onVisible?.();\n };\n doc.addEventListener('visibilitychange', onChange);\n return { dispose: (): void => doc.removeEventListener('visibilitychange', onChange) };\n}\n\nexport function unlockAudio(ctx: ResumableAudio, win: Window = window): Disposable {\n const detach = (): void => {\n UNLOCK_EVENTS.forEach((type) => win.removeEventListener(type, onFirst, true));\n };\n function onFirst(): void {\n detach();\n try {\n Promise.resolve(ctx.resume()).catch(() => undefined);\n } catch {\n // A browser that refuses resume() leaves the game silent, never crashed.\n }\n }\n UNLOCK_EVENTS.forEach((type) => win.addEventListener(type, onFirst, true));\n return { dispose: detach };\n}\n\nexport function prefersReducedMotion(win: Window = window): boolean {\n if (new URLSearchParams(win.location.search).get('reducedMotion') === '1') {\n return true;\n }\n return typeof win.matchMedia === 'function' && win.matchMedia(REDUCED_MOTION_QUERY).matches;\n}\n","const STYLE_ID = 'gs-styles';\n\nconst THEME = `:where(:root){--gs-font-display:system-ui,'Segoe UI',sans-serif;--gs-font-text:system-ui,'Segoe UI',sans-serif;--gs-surface:rgba(24,26,29,.95);--gs-surface-solid:#181a1d;--gs-edge:#41464d;--gs-ink:#f3f3f1;--gs-ink-dim:#b9bdc2;--gs-accent:#f3f3f1;--gs-on-accent:#181a1d;--gs-scrim:rgba(0,0,0,.72);--gs-radius:12px;--gs-radius-pill:12px;--gs-focus:#9cc2ff;--gs-safe-top:env(safe-area-inset-top,0px);--gs-safe-right:env(safe-area-inset-right,0px);--gs-safe-bottom:env(safe-area-inset-bottom,0px);--gs-safe-left:env(safe-area-inset-left,0px)}`;\n\nconst ROOT = `.gs-root{width:100%;height:100vh;height:100dvh;height:var(--gs-vh,100dvh)}`;\n\nconst BASE = `.gs{font-family:var(--gs-font-text);color:var(--gs-ink);box-sizing:border-box}\n.gs *{box-sizing:border-box}\n.gs button{min-width:44px;min-height:44px;margin:0;border:0;background:none;color:inherit;font:inherit;cursor:pointer;-webkit-tap-highlight-color:transparent}\n.gs button:focus-visible{outline:2px solid var(--gs-focus);outline-offset:2px}\n.gs svg{width:20px;height:20px;flex:none}\n.gs-ico{display:inline-flex}\n.gs-btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:0 18px;border-radius:var(--gs-radius);font:700 16px/1 var(--gs-font-text);white-space:nowrap}\n.gs-primary{background:var(--gs-accent);color:var(--gs-on-accent)}\n.gs .gs-ghost{color:var(--gs-ink);border:1px solid var(--gs-edge)}\n.gs-x{width:44px;height:44px;display:grid;place-items:center;color:var(--gs-ink-dim);flex:none}\n.gs-txt{min-width:0}\n.gs-txt b{display:block;font:400 18px/1.2 var(--gs-font-display);color:var(--gs-ink)}\n.gs-txt span{display:block;font-size:14px;line-height:1.4;color:var(--gs-ink-dim);margin-top:3px}`;\n\nconst INSTALL = `.gs-install{position:fixed;z-index:2147483000;top:calc(12px + var(--gs-safe-top));left:calc(12px + var(--gs-safe-left))}\n.gs-install[data-layout=card]{top:auto;left:auto;right:calc(24px + var(--gs-safe-right));bottom:calc(24px + var(--gs-safe-bottom))}\n.gs-seal{display:flex;align-items:center;background:var(--gs-surface);border:1px solid var(--gs-edge);border-radius:var(--gs-radius-pill)}\n.gs-chip{display:flex;align-items:center;gap:8px;padding:0 6px 0 14px;font:700 15px var(--gs-font-text);color:var(--gs-ink)}\n.gs-chip svg{color:var(--gs-accent)}\n.gs-seal .gs-x{border-left:1px solid var(--gs-edge)}\n.gs-steps{margin:8px 0 0;width:262px;padding:10px 14px;list-style:none;background:var(--gs-surface);border:1px solid var(--gs-edge);border-radius:var(--gs-radius);display:grid;gap:2px;counter-reset:s}\n.gs-steps[hidden]{display:none}\n.gs-steps li{display:flex;align-items:center;gap:10px;min-height:36px;font-size:15px;color:var(--gs-ink);counter-increment:s}\n.gs-steps li::before{content:counter(s);font:400 15px var(--gs-font-display);color:var(--gs-ink-dim);width:12px}\n.gs-steps svg{width:17px;height:17px;color:var(--gs-accent)}\n.gs-card{position:relative;width:344px;max-width:calc(100vw - 48px);padding:18px;border-radius:var(--gs-radius);background:var(--gs-surface);border:1px solid var(--gs-edge);display:grid;gap:12px}\n.gs-card .gs-x{position:absolute;top:4px;right:4px}\n.gs-card .gs-txt{padding-right:36px}\n.gs-card .gs-acts{display:flex;gap:8px}\n.gs-card .gs-steps{width:auto;margin:0;border:0;padding:0;background:none}\n.gs-app{width:40px;height:40px;border-radius:var(--gs-radius);background:var(--gs-surface-solid);border:1px solid var(--gs-edge);display:grid;place-items:center}\n.gs-app i{width:13px;height:13px;border-radius:50%;background:var(--gs-accent);box-shadow:0 0 10px var(--gs-accent)}`;\n\nconst CRASH = `.gs-crash{position:fixed;inset:0;z-index:2147483001;background:var(--gs-scrim);display:grid;place-items:center;padding:20px}\n.gs-panel{max-width:340px;background:var(--gs-surface-solid);border:1px solid var(--gs-edge);border-radius:var(--gs-radius);padding:24px}\n.gs-h{margin:0;font:400 28px/1.15 var(--gs-font-display);color:var(--gs-ink)}\n.gs-panel p{font-size:16px;line-height:1.5;color:var(--gs-ink-dim);margin:10px 0 0}\n.gs-panel .gs-acts{display:flex;flex-direction:column;gap:8px;margin-top:20px}\n.gs-panel .gs-ref{font:13px ui-monospace,monospace}\n@media (min-width:1024px){.gs-panel{max-width:460px;padding:32px}.gs-panel .gs-acts{flex-direction:row}}`;\n\nexport const GAME_SHELL_CSS = [THEME, ROOT, BASE, INSTALL, CRASH].join('\\n');\n\nexport function injectStyles(doc: Document = document): void {\n if (doc.getElementById(STYLE_ID)) {\n return;\n }\n const style = doc.createElement('style');\n style.id = STYLE_ID;\n style.textContent = GAME_SHELL_CSS;\n doc.head.append(style);\n}\n","import type { Disposable } from '../types';\n\nexport interface VersionPollOptions {\n url: string;\n intervalMs: number;\n onNewVersion(): void;\n fetchFn?: typeof fetch;\n win?: Window;\n}\n\nexport function startVersionPoll(opts: VersionPollOptions): Disposable {\n const win = opts.win ?? window;\n const fetchFn = opts.fetchFn ?? ((input: RequestInfo | URL, init?: RequestInit): Promise<Response> => win.fetch(input, init));\n let baseline: string | null = null;\n let fired = false;\n let busy = false;\n\n const check = async (): Promise<void> => {\n if (fired || busy) {\n return;\n }\n busy = true;\n try {\n const res = await fetchFn(opts.url, { cache: 'no-store' });\n if (!res.ok) {\n return;\n }\n const value = (await res.text()).trim();\n if (baseline === null) {\n baseline = value;\n } else if (value !== baseline) {\n fired = true;\n opts.onNewVersion();\n }\n } catch {\n // Offline or a deploy mid-flight: the next tick tries again.\n } finally {\n busy = false;\n }\n };\n const onVisibility = (): void => {\n if (win.document.visibilityState === 'visible') {\n void check();\n }\n };\n\n void check();\n const timer = win.setInterval(() => void check(), opts.intervalMs);\n win.document.addEventListener('visibilitychange', onVisibility);\n return {\n dispose: (): void => {\n win.clearInterval(timer);\n win.document.removeEventListener('visibilitychange', onVisibility);\n },\n };\n}\n","import { startCrash } from './crash/crash';\nimport type { CrashInfo } from './crash/crashKind';\nimport { startFit } from './fit/fit';\nimport { startInstall } from './install/install';\nimport { startLifecycle, type LifecycleOptions } from './lifecycle/lifecycle';\nimport { injectStyles } from './styles';\nimport type { Disposable, GameShellLabels } from './types';\nimport { startVersionPoll } from './version/versionPoll';\n\nexport interface GameShellOptions {\n root: HTMLElement;\n appName: string;\n labels: GameShellLabels;\n appVersion?: string;\n install?: { snoozeDays?: number; storageKey?: string } | false;\n version?: { url: string; intervalMs: number; onNewVersion(): void };\n crash?: { report?(info: CrashInfo): string | void } | false;\n lifecycle?: LifecycleOptions;\n}\n\nexport function initGameShell(opts: GameShellOptions): Disposable {\n const doc = opts.root.ownerDocument;\n injectStyles(doc);\n opts.root.classList.add('gs-root');\n const parts: Disposable[] = [startFit()];\n if (opts.install !== false) {\n parts.push(startInstall({ appName: opts.appName, labels: opts.labels, ...opts.install }));\n }\n if (opts.crash !== false) {\n parts.push(startCrash({ labels: opts.labels, report: opts.crash?.report, appVersion: opts.appVersion }));\n }\n if (opts.lifecycle) {\n parts.push(startLifecycle(opts.lifecycle, doc));\n }\n if (opts.version) {\n parts.push(startVersionPoll(opts.version));\n }\n return {\n dispose: (): void => {\n parts.forEach((part) => part.dispose());\n opts.root.classList.remove('gs-root');\n },\n };\n}\n"],"mappings":"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","names":["src_exports","__export","CRASH_KIND","DEFAULT_SNOOZE_DAYS","DESKTOP_MIN_WIDTH","GAME_SHELL_CSS","GS_RESIZE_EVENT","GS_VH_VAR","initGameShell","injectStyles","isDesktop","isIosSafari","isStandalone","prefersReducedMotion","startCrash","startFit","startInstall","startLifecycle","startVersionPoll","unlockAudio","CRASH_KIND","el","doc","tag","className","text","node","iconSpan","svg","span","button","spec","btn","CRASH_TITLE_ID","CRASH_BODY_ID","renderCrashOverlay","p","root","el","panel","title","body","acts","reload","button","fromErrorEvent","err","stack","location","message","CRASH_KIND","fromRejection","reason","safeReport","opts","info","_a","_b","e","copyDetails","win","appVersion","text","part","startCrash","reload","overlay","handle","reference","view","renderCrashOverlay","onError","onRejection","GS_RESIZE_EVENT","GS_VH_VAR","measure","win","vv","scheduler","cb","startFit","root","schedule","last","pending","disposed","apply","next","unchanged","onChange","svg","paths","ICONS","STEPS_ID","renderSteps","p","list","el","share","iconSpan","ICONS","add","closeButton","button","renderChip","seal","steps","chip","renderCard","card","app","txt","acts","renderBanner","wrap","e","DESKTOP_MIN_WIDTH","IOS_DEVICE_RE","IOS_OTHER_BROWSER_RE","isStandalone","win","isIosSafari","ua","iPadAsMac","isDesktop","snoozeKey","appName","override","isSnoozed","win","key","days","now","raw","at","e","writeSnooze","DEFAULT_SNOOZE_DAYS","ACCEPTED","createController","opts","win","now","key","snoozeKey","state","unmount","_a","dismiss","writeSnooze","install","evt","e","renderBanner","isDesktop","startInstall","_b","_c","isStandalone","isSnoozed","ctl","onPrompt","onInstalled","onResize","node","isIosSafari","REDUCED_MOTION_QUERY","UNLOCK_EVENTS","startLifecycle","opts","doc","onChange","_a","_b","unlockAudio","ctx","win","detach","type","onFirst","e","prefersReducedMotion","STYLE_ID","THEME","ROOT","BASE","INSTALL","CRASH","GAME_SHELL_CSS","injectStyles","doc","style","startVersionPoll","opts","_a","_b","win","fetchFn","input","init","baseline","fired","busy","check","res","value","e","onVisibility","timer","initGameShell","opts","_a","doc","injectStyles","parts","startFit","startInstall","startCrash","startLifecycle","startVersionPoll","part"]}
@@ -0,0 +1,114 @@
1
+ declare const CRASH_KIND: {
2
+ readonly Error: "error";
3
+ readonly Rejection: "rejection";
4
+ };
5
+ type CrashKind = (typeof CRASH_KIND)[keyof typeof CRASH_KIND];
6
+ interface CrashInfo {
7
+ kind: CrashKind;
8
+ message: string;
9
+ source?: string;
10
+ }
11
+
12
+ interface Disposable {
13
+ dispose(): void;
14
+ }
15
+ interface GameShellLabels {
16
+ install: string;
17
+ addToHome: string;
18
+ notNow: string;
19
+ tapShare: string;
20
+ tapAdd: string;
21
+ installBody: string;
22
+ crashTitle: string;
23
+ crashBody: string;
24
+ reload: string;
25
+ copyDetails: string;
26
+ errorReported: string;
27
+ }
28
+ type InstallLabels = Pick<GameShellLabels, 'install' | 'addToHome' | 'notNow' | 'tapShare' | 'tapAdd' | 'installBody'>;
29
+ type CrashLabels = Pick<GameShellLabels, 'crashTitle' | 'crashBody' | 'reload' | 'copyDetails' | 'errorReported'>;
30
+
31
+ interface LifecycleOptions {
32
+ onHidden?(): void;
33
+ onVisible?(): void;
34
+ }
35
+ interface ResumableAudio {
36
+ resume(): Promise<void> | void;
37
+ }
38
+ declare function startLifecycle(opts: LifecycleOptions, doc?: Document): Disposable;
39
+ declare function unlockAudio(ctx: ResumableAudio, win?: Window): Disposable;
40
+ declare function prefersReducedMotion(win?: Window): boolean;
41
+
42
+ interface GameShellOptions {
43
+ root: HTMLElement;
44
+ appName: string;
45
+ labels: GameShellLabels;
46
+ appVersion?: string;
47
+ install?: {
48
+ snoozeDays?: number;
49
+ storageKey?: string;
50
+ } | false;
51
+ version?: {
52
+ url: string;
53
+ intervalMs: number;
54
+ onNewVersion(): void;
55
+ };
56
+ crash?: {
57
+ report?(info: CrashInfo): string | void;
58
+ } | false;
59
+ lifecycle?: LifecycleOptions;
60
+ }
61
+ declare function initGameShell(opts: GameShellOptions): Disposable;
62
+
63
+ declare const GS_RESIZE_EVENT = "gs:resize";
64
+ declare const GS_VH_VAR = "--gs-vh";
65
+ interface GsResizeDetail {
66
+ width: number;
67
+ height: number;
68
+ }
69
+ declare function startFit(win?: Window): Disposable;
70
+
71
+ declare const DEFAULT_SNOOZE_DAYS = 14;
72
+ interface BeforeInstallPromptEvent extends Event {
73
+ prompt(): Promise<void>;
74
+ userChoice: Promise<{
75
+ outcome: string;
76
+ }>;
77
+ }
78
+ interface InstallOptions {
79
+ appName: string;
80
+ labels: InstallLabels;
81
+ snoozeDays?: number;
82
+ storageKey?: string;
83
+ win?: Window;
84
+ now?: () => number;
85
+ }
86
+ declare function startInstall(opts: InstallOptions): Disposable;
87
+
88
+ declare const DESKTOP_MIN_WIDTH = 1024;
89
+ declare function isStandalone(win: Window): boolean;
90
+ declare function isIosSafari(win: Window): boolean;
91
+ declare function isDesktop(win: Window): boolean;
92
+
93
+ interface CrashOptions {
94
+ labels: CrashLabels;
95
+ report?: (info: CrashInfo) => string | void;
96
+ appVersion?: string;
97
+ win?: Window;
98
+ reload?: () => void;
99
+ }
100
+ declare function startCrash(opts: CrashOptions): Disposable;
101
+
102
+ interface VersionPollOptions {
103
+ url: string;
104
+ intervalMs: number;
105
+ onNewVersion(): void;
106
+ fetchFn?: typeof fetch;
107
+ win?: Window;
108
+ }
109
+ declare function startVersionPoll(opts: VersionPollOptions): Disposable;
110
+
111
+ declare const GAME_SHELL_CSS: string;
112
+ declare function injectStyles(doc?: Document): void;
113
+
114
+ export { type BeforeInstallPromptEvent, CRASH_KIND, type CrashInfo, type CrashKind, type CrashLabels, type CrashOptions, DEFAULT_SNOOZE_DAYS, DESKTOP_MIN_WIDTH, type Disposable, GAME_SHELL_CSS, GS_RESIZE_EVENT, GS_VH_VAR, type GameShellLabels, type GameShellOptions, type GsResizeDetail, type InstallLabels, type InstallOptions, type LifecycleOptions, type ResumableAudio, type VersionPollOptions, initGameShell, injectStyles, isDesktop, isIosSafari, isStandalone, prefersReducedMotion, startCrash, startFit, startInstall, startLifecycle, startVersionPoll, unlockAudio };
@@ -0,0 +1,114 @@
1
+ declare const CRASH_KIND: {
2
+ readonly Error: "error";
3
+ readonly Rejection: "rejection";
4
+ };
5
+ type CrashKind = (typeof CRASH_KIND)[keyof typeof CRASH_KIND];
6
+ interface CrashInfo {
7
+ kind: CrashKind;
8
+ message: string;
9
+ source?: string;
10
+ }
11
+
12
+ interface Disposable {
13
+ dispose(): void;
14
+ }
15
+ interface GameShellLabels {
16
+ install: string;
17
+ addToHome: string;
18
+ notNow: string;
19
+ tapShare: string;
20
+ tapAdd: string;
21
+ installBody: string;
22
+ crashTitle: string;
23
+ crashBody: string;
24
+ reload: string;
25
+ copyDetails: string;
26
+ errorReported: string;
27
+ }
28
+ type InstallLabels = Pick<GameShellLabels, 'install' | 'addToHome' | 'notNow' | 'tapShare' | 'tapAdd' | 'installBody'>;
29
+ type CrashLabels = Pick<GameShellLabels, 'crashTitle' | 'crashBody' | 'reload' | 'copyDetails' | 'errorReported'>;
30
+
31
+ interface LifecycleOptions {
32
+ onHidden?(): void;
33
+ onVisible?(): void;
34
+ }
35
+ interface ResumableAudio {
36
+ resume(): Promise<void> | void;
37
+ }
38
+ declare function startLifecycle(opts: LifecycleOptions, doc?: Document): Disposable;
39
+ declare function unlockAudio(ctx: ResumableAudio, win?: Window): Disposable;
40
+ declare function prefersReducedMotion(win?: Window): boolean;
41
+
42
+ interface GameShellOptions {
43
+ root: HTMLElement;
44
+ appName: string;
45
+ labels: GameShellLabels;
46
+ appVersion?: string;
47
+ install?: {
48
+ snoozeDays?: number;
49
+ storageKey?: string;
50
+ } | false;
51
+ version?: {
52
+ url: string;
53
+ intervalMs: number;
54
+ onNewVersion(): void;
55
+ };
56
+ crash?: {
57
+ report?(info: CrashInfo): string | void;
58
+ } | false;
59
+ lifecycle?: LifecycleOptions;
60
+ }
61
+ declare function initGameShell(opts: GameShellOptions): Disposable;
62
+
63
+ declare const GS_RESIZE_EVENT = "gs:resize";
64
+ declare const GS_VH_VAR = "--gs-vh";
65
+ interface GsResizeDetail {
66
+ width: number;
67
+ height: number;
68
+ }
69
+ declare function startFit(win?: Window): Disposable;
70
+
71
+ declare const DEFAULT_SNOOZE_DAYS = 14;
72
+ interface BeforeInstallPromptEvent extends Event {
73
+ prompt(): Promise<void>;
74
+ userChoice: Promise<{
75
+ outcome: string;
76
+ }>;
77
+ }
78
+ interface InstallOptions {
79
+ appName: string;
80
+ labels: InstallLabels;
81
+ snoozeDays?: number;
82
+ storageKey?: string;
83
+ win?: Window;
84
+ now?: () => number;
85
+ }
86
+ declare function startInstall(opts: InstallOptions): Disposable;
87
+
88
+ declare const DESKTOP_MIN_WIDTH = 1024;
89
+ declare function isStandalone(win: Window): boolean;
90
+ declare function isIosSafari(win: Window): boolean;
91
+ declare function isDesktop(win: Window): boolean;
92
+
93
+ interface CrashOptions {
94
+ labels: CrashLabels;
95
+ report?: (info: CrashInfo) => string | void;
96
+ appVersion?: string;
97
+ win?: Window;
98
+ reload?: () => void;
99
+ }
100
+ declare function startCrash(opts: CrashOptions): Disposable;
101
+
102
+ interface VersionPollOptions {
103
+ url: string;
104
+ intervalMs: number;
105
+ onNewVersion(): void;
106
+ fetchFn?: typeof fetch;
107
+ win?: Window;
108
+ }
109
+ declare function startVersionPoll(opts: VersionPollOptions): Disposable;
110
+
111
+ declare const GAME_SHELL_CSS: string;
112
+ declare function injectStyles(doc?: Document): void;
113
+
114
+ export { type BeforeInstallPromptEvent, CRASH_KIND, type CrashInfo, type CrashKind, type CrashLabels, type CrashOptions, DEFAULT_SNOOZE_DAYS, DESKTOP_MIN_WIDTH, type Disposable, GAME_SHELL_CSS, GS_RESIZE_EVENT, GS_VH_VAR, type GameShellLabels, type GameShellOptions, type GsResizeDetail, type InstallLabels, type InstallOptions, type LifecycleOptions, type ResumableAudio, type VersionPollOptions, initGameShell, injectStyles, isDesktop, isIosSafari, isStandalone, prefersReducedMotion, startCrash, startFit, startInstall, startLifecycle, startVersionPoll, unlockAudio };