@dloizides/game-shell 0.1.1 → 0.1.2

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 CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.2
4
+
5
+ - Install: a beforeinstallprompt re-fired after Not now no longer brings the chip back in the same session.
6
+ - unlockAudio listens on pointerup / touchend / keydown (user activation) and stays armed until the context reports running.
7
+ - versionPoll stops its interval after firing and ignores a fetch that settles after dispose.
8
+ - fit dispose removes --gs-vh; Esc on the banner stops propagation; display-mode fullscreen counts as installed.
9
+ - Optional labels.close names the desktop card close button, so the card has one Not now control.
10
+ - initGameShell wires every part to the root's own window.
11
+ - yagni / yagni:ci scripts (ts-prune).
12
+
3
13
  ## 0.1.1
4
14
 
5
15
  - Desktop install card sits 60px from the bottom by default, set by `--gs-card-bottom` (clears a bottom-right HUD badge).
@@ -1,5 +1,5 @@
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-card-bottom:60px;--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}
1
+ "use strict";var GameShell=(()=>{var O=Object.defineProperty;var X=Object.getOwnPropertyDescriptor;var ee=Object.getOwnPropertyNames;var te=Object.prototype.hasOwnProperty;var ne=(e,t)=>{for(var n in t)O(e,n,{get:t[n],enumerable:!0})},oe=(e,t,n,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of ee(t))!te.call(e,o)&&o!==n&&O(e,o,{get:()=>t[o],enumerable:!(s=X(t,o))||s.enumerable});return e};var se=e=>oe(O({},"__esModule",{value:!0}),e);var we={};ne(we,{CRASH_KIND:()=>m,DEFAULT_SNOOZE_DAYS:()=>R,DESKTOP_MIN_WIDTH:()=>j,GAME_SHELL_CSS:()=>_,GS_RESIZE_EVENT:()=>P,GS_VH_VAR:()=>h,initGameShell:()=>J,injectStyles:()=>k,isDesktop:()=>x,isIosSafari:()=>I,isStandalone:()=>w,prefersReducedMotion:()=>Z,startCrash:()=>E,startFit:()=>S,startInstall:()=>C,startLifecycle:()=>D,startVersionPoll:()=>N,unlockAudio:()=>q});var m={Error:"error",Rejection:"rejection"};function a(e,t,n="",s=""){let o=e.createElement(t);return n!==""&&(o.className=n),s!==""&&(o.textContent=s),o}function y(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(y(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 H="gs-crash-title",V="gs-crash-body";function z(e){let t=a(e.doc,"div","gs gs-crash");t.setAttribute("role","alertdialog"),t.setAttribute("aria-modal","true"),t.setAttribute("aria-labelledby",H),t.setAttribute("aria-describedby",V);let n=a(e.doc,"div","gs-panel"),s=a(e.doc,"h2","gs-h",e.labels.crashTitle);s.id=H;let o=a(e.doc,"p","",e.labels.crashBody);o.id=V;let r=a(e.doc,"div","gs-acts"),i=f(e.doc,{className:"gs-btn gs-primary",text:e.labels.reload,onClick:e.onReload});return r.append(i,f(e.doc,{className:"gs-btn gs-ghost",text:e.labels.copyDetails,onClick:e.onCopy})),n.append(s,o,r),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:i}}function re(e){let t=e.error,n=t instanceof Error?t.stack:void 0,s=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:s}}function ie(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 ae(e){return typeof e.message=="string"}function le(e,t){var n,s;try{return(s=(n=e.report)==null?void 0:n.call(e,t))!=null?s:void 0}catch(o){return}}function de(e,t,n){let s=[t.message,t.source,n].filter(o=>o!==void 0&&o!=="").join(`
2
+ `);try{e.navigator.clipboard.writeText(s).catch(()=>{})}catch(o){}}function E(e){var d,c;let t=(d=e.win)!=null?d:window,n=(c=e.reload)!=null?c:(()=>t.location.reload()),s=null,o=l=>{let p=le(e,l);if(s)return;let u=z({doc:t.document,labels:e.labels,reference:p,onReload:n,onCopy:()=>de(t,l,e.appVersion)});s=u.root,t.document.body.append(s),u.reload.focus()},r=l=>{ae(l)&&o(re(l))},i=l=>o(ie(l));return t.addEventListener("error",r),t.addEventListener("unhandledrejection",i),{dispose:()=>{t.removeEventListener("error",r),t.removeEventListener("unhandledrejection",i),s==null||s.remove(),s=null}}}var P="gs:resize",h="--gs-vh";function B(e){let t=e.visualViewport;return t?{width:t.width,height:t.height}:{width:e.innerWidth,height:e.innerHeight}}function ce(e){return typeof e.requestAnimationFrame=="function"?t=>{e.requestAnimationFrame(()=>t())}:t=>{e.setTimeout(t,0)}}function S(e=window){let t=e.document.documentElement,n=ce(e),s=B(e),o=!1,r=!1;t.style.setProperty(h,`${s.height}px`);let i=()=>{o=!1;let l=B(e),p=l.width===s.width&&l.height===s.height;r||p||(s=l,t.style.setProperty(h,`${l.height}px`),e.dispatchEvent(new CustomEvent(P,{detail:l})))},d=()=>{o||(o=!0,n(i))},c=e.visualViewport;return c==null||c.addEventListener("resize",d),e.addEventListener("resize",d),e.addEventListener("orientationchange",d),{dispose:()=>{r=!0,t.style.removeProperty(h),c==null||c.removeEventListener("resize",d),e.removeEventListener("resize",d),e.removeEventListener("orientationchange",d)}}}var L=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>`,g={download:L('<path d="M12 4v11M7 10l5 5 5-5M5 20h14"/>'),add:L('<path d="M12 5v14M5 12h14"/>'),close:L('<path d="M6 6l12 12M18 6L6 18"/>'),share:L('<path d="M12 15V4M8 8l4-4 4 4M6 12v8h12v-8"/>')};var K="gs-install-steps";function G(e){let t=a(e.doc,"ol","gs-steps");t.id=K;let n=a(e.doc,"li");n.append(y(e.doc,g.share),e.doc.createTextNode(e.labels.tapShare));let s=a(e.doc,"li");return s.append(y(e.doc,g.add),e.doc.createTextNode(e.labels.tapAdd)),t.append(n,s),t}function M(e,t=e.labels.notNow){return f(e.doc,{className:"gs-x",ariaLabel:t,icon:g.close,onClick:e.onDismiss})}function pe(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:g.download,onClick:e.onInstall})),t.append(M(e)),[t];let n=G(e);n.hidden=!0;let s=f(e.doc,{className:"gs-chip",text:e.labels.addToHome,icon:g.add,onClick:()=>{n.hidden=!n.hidden,s.setAttribute("aria-expanded",String(!n.hidden))}});return s.setAttribute("aria-expanded","false"),s.setAttribute("aria-controls",K),t.append(s,M(e)),[t,n]}function ue(e){var r;let t=a(e.doc,"div","gs-card"),n=a(e.doc,"div","gs-app");n.append(a(e.doc,"i"));let s=a(e.doc,"div","gs-txt");s.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:g.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,(r=e.labels.close)!=null?r:e.labels.notNow),n,s),e.variant==="ios"&&t.append(G(e)),t.append(o),[t]}function W(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"&&(n.stopPropagation(),e.onDismiss())}),t.append(...e.layout==="card"?ue(e):pe(e)),t}var j=1024,fe=/iPad|iPhone|iPod/,ge=["standalone","fullscreen"],me=/CriOS|FxiOS|EdgiOS|OPiOS/;function w(e){return typeof e.matchMedia=="function"&&ge.some(n=>e.matchMedia(`(display-mode: ${n})`).matches)||e.navigator.standalone===!0}function I(e){let t=e.navigator.userAgent,n=t.includes("Macintosh")&&e.navigator.maxTouchPoints>1;return(fe.test(t)||n)&&!me.test(t)}function x(e){return e.innerWidth>=1024}function T(e,t){return t!=null?t:`gs-install-snooze:${e}`}function F(e,t,n,s){try{let o=e.localStorage.getItem(t);if(o===null)return!1;let r=Number(o);return Number.isFinite(r)&&s-r<n*864e5}catch(o){return!1}}function U(e,t,n){try{e.localStorage.setItem(t,String(n))}catch(s){}}var R=14,ve="accepted";function he(e,t,n){let s=T(e.appName,e.storageKey),o={deferred:null,variant:null,node:null},r=!1,i=()=>{var p;(p=o.node)==null||p.remove(),o.node=null},d=()=>{U(t,s,n()),i()},c=async()=>{let p=o.deferred;if(o.deferred=null,!!p)try{if(await p.prompt(),(await p.userChoice).outcome===ve){i();return}d()}catch(u){i()}};return{state:o,isDismissed:()=>r,mount:()=>{o.variant!==null&&(i(),o.node=W({doc:t.document,layout:x(t)?"card":"chip",variant:o.variant,appName:e.appName,labels:e.labels,onInstall:()=>{c()},onDismiss:()=>{r=!0,d()}}),t.document.body.append(o.node))},unmount:i}}function C(e){var c,l,p;let t=(c=e.win)!=null?c:window,n=(l=e.now)!=null?l:(()=>Date.now()),s=T(e.appName,e.storageKey);if(w(t)||F(t,s,(p=e.snoozeDays)!=null?p:R,n()))return{dispose:()=>{}};let o=he(e,t,n),r=u=>{u.preventDefault(),o.state.deferred=u,o.state.variant="prompt",o.isDismissed()||o.mount()},i=()=>{o.state.variant=null,o.unmount()},d=()=>{let u=o.state.node;u&&u.dataset.layout==="card"!==x(t)&&o.mount()};return t.addEventListener("beforeinstallprompt",r),t.addEventListener("appinstalled",i),t.addEventListener("resize",d),I(t)&&(o.state.variant="ios",o.mount()),{dispose:()=>{t.removeEventListener("beforeinstallprompt",r),t.removeEventListener("appinstalled",i),t.removeEventListener("resize",d),o.unmount()}}}var xe="(prefers-reduced-motion: reduce)",$=["pointerup","touchend","keydown"],Y="running";function D(e,t=document){let n=()=>{var s,o;if(t.visibilityState==="hidden"){(s=e.onHidden)==null||s.call(e);return}(o=e.onVisible)==null||o.call(e)};return t.addEventListener("visibilitychange",n),{dispose:()=>t.removeEventListener("visibilitychange",n)}}function q(e,t=window){let n=()=>{$.forEach(r=>t.removeEventListener(r,o,!0))},s=()=>{(e.state===void 0||e.state===Y)&&n()};function o(){if(e.state===Y){n();return}try{Promise.resolve(e.resume()).then(s,()=>{})}catch(r){}}return $.forEach(r=>t.addEventListener(r,o,!0)),{dispose:n}}function Z(e=window){return new URLSearchParams(e.location.search).get("reducedMotion")==="1"?!0:typeof e.matchMedia=="function"&&e.matchMedia(xe).matches}var Q="gs-styles",be=":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-card-bottom:60px;--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)}",ye=".gs-root{width:100%;height:100vh;height:100dvh;height:var(--gs-vh,100dvh)}",Ee=`.gs{font-family:var(--gs-font-text);color:var(--gs-ink);box-sizing:border-box}
3
3
  .gs *{box-sizing:border-box}
4
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
5
  .gs button:focus-visible{outline:2px solid var(--gs-focus);outline-offset:2px}
@@ -11,7 +11,7 @@
11
11
  .gs-x{width:44px;height:44px;display:grid;place-items:center;color:var(--gs-ink-dim);flex:none}
12
12
  .gs-txt{min-width:0}
13
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))}
14
+ .gs-txt span{display:block;font-size:14px;line-height:1.4;color:var(--gs-ink-dim);margin-top:3px}`,Se=`.gs-install{position:fixed;z-index:2147483000;top:calc(12px + var(--gs-safe-top));left:calc(12px + var(--gs-safe-left))}
15
15
  .gs-install[data-layout=card]{top:auto;left:auto;right:calc(24px + var(--gs-safe-right));bottom:calc(var(--gs-card-bottom,60px) + var(--gs-safe-bottom))}
16
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
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)}
@@ -28,12 +28,12 @@
28
28
  .gs-card .gs-acts{display:flex;gap:8px}
29
29
  .gs-card .gs-steps{width:auto;margin:0;border:0;padding:0;background:none}
30
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}
31
+ .gs-app i{width:13px;height:13px;border-radius:50%;background:var(--gs-accent);box-shadow:0 0 10px var(--gs-accent)}`,Le=`.gs-crash{position:fixed;inset:0;z-index:2147483001;background:var(--gs-scrim);display:grid;place-items:center;padding:20px}
32
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
33
  .gs-h{margin:0;font:400 28px/1.15 var(--gs-font-display);color:var(--gs-ink)}
34
34
  .gs-panel p{font-size:16px;line-height:1.5;color:var(--gs-ink-dim);margin:10px 0 0}
35
35
  .gs-panel .gs-acts{display:flex;flex-direction:column;gap:8px;margin-top:20px}
36
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);})();
37
+ @media (min-width:1024px){.gs-panel{max-width:460px;padding:32px}.gs-panel .gs-acts{flex-direction:row}}`,_=[be,ye,Ee,Se,Le].join(`
38
+ `);function k(e=document){if(e.getElementById(Q))return;let t=e.createElement("style");t.id=Q,t.textContent=_,e.head.append(t)}function N(e){var u,A;let t=(u=e.win)!=null?u:window,n=(A=e.fetchFn)!=null?A:((v,b)=>t.fetch(v,b)),s=null,o=!1,r=!1,i=!1,d=0,c=()=>{t.clearInterval(d),t.document.removeEventListener("visibilitychange",p)},l=async()=>{if(!(o||r||i)){r=!0;try{let v=await n(e.url,{cache:"no-store"});if(!v.ok||i)return;let b=(await v.text()).trim();if(i)return;s===null?s=b:b!==s&&(o=!0,c(),e.onNewVersion())}catch(v){}finally{r=!1}}};function p(){t.document.visibilityState==="visible"&&l()}return l(),d=t.setInterval(()=>{l()},e.intervalMs),t.document.addEventListener("visibilitychange",p),{dispose:()=>{i=!0,c()}}}function J(e){var o,r;let t=e.root.ownerDocument,n=(o=t.defaultView)!=null?o:window;k(t),e.root.classList.add("gs-root");let s=[S(n)];return e.install!==!1&&s.push(C({appName:e.appName,labels:e.labels,...e.install,win:n})),e.crash!==!1&&s.push(E({labels:e.labels,report:(r=e.crash)==null?void 0:r.report,appVersion:e.appVersion,win:n})),e.lifecycle&&s.push(D(e.lifecycle,t)),e.version&&s.push(N({...e.version,win:n})),{dispose:()=>{s.forEach(i=>i.dispose()),e.root.classList.remove("gs-root")}}}return se(we);})();
39
39
  //# sourceMappingURL=game-shell.iife.js.map
@@ -1 +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-card-bottom:60px;--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(var(--gs-card-bottom,60px) + 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"]}
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\ninterface 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\nconst CRASH_TITLE_ID = 'gs-crash-title';\nconst CRASH_BODY_ID = 'gs-crash-body';\n\ninterface CrashOverlayProps {\n doc: Document;\n labels: CrashLabels;\n reference?: string;\n onReload(): void;\n onCopy(): void;\n}\n\ninterface 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 isErrorEvent(e: Event): e is ErrorEvent {\n return typeof (e as Partial<ErrorEvent>).message === 'string';\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 (isErrorEvent(e)) {\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 root.style.removeProperty(GS_VH_VAR);\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\nconst STEPS_ID = 'gs-install-steps';\n\ninterface 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, ariaLabel: string = p.labels.notNow): HTMLButtonElement {\n return button(p.doc, { className: 'gs-x', ariaLabel, 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, p.labels.close ?? p.labels.notNow), 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 e.stopPropagation();\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 INSTALLED_DISPLAY_MODES = ['standalone', 'fullscreen'] as const;\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 =\n typeof win.matchMedia === 'function' &&\n INSTALLED_DISPLAY_MODES.some((mode) => win.matchMedia(`(display-mode: ${mode})`).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 isDismissed(): boolean;\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 let dismissed = false;\n const unmount = (): void => {\n state.node?.remove();\n state.node = null;\n };\n const snooze = (): 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 snooze();\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: () => {\n dismissed = true;\n snooze();\n },\n });\n win.document.body.append(state.node);\n };\n return { state, isDismissed: (): boolean => dismissed, 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 if (!ctl.isDismissed()) {\n ctl.mount();\n }\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 readonly state?: string;\n resume(): Promise<void> | void;\n}\n\nconst REDUCED_MOTION_QUERY = '(prefers-reduced-motion: reduce)';\nconst UNLOCK_EVENTS = ['pointerup', 'touchend', 'keydown'] as const;\nconst RUNNING = 'running';\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, onGesture, true));\n };\n const settle = (): void => {\n if (ctx.state === undefined || ctx.state === RUNNING) {\n detach();\n }\n };\n function onGesture(): void {\n if (ctx.state === RUNNING) {\n detach();\n return;\n }\n try {\n Promise.resolve(ctx.resume()).then(settle, () => 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, onGesture, 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-card-bottom:60px;--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(var(--gs-card-bottom,60px) + 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 let disposed = false;\n let timer = 0;\n const stop = (): void => {\n win.clearInterval(timer);\n win.document.removeEventListener('visibilitychange', onVisibility);\n };\n\n const check = async (): Promise<void> => {\n if (fired || busy || disposed) {\n return;\n }\n busy = true;\n try {\n const res = await fetchFn(opts.url, { cache: 'no-store' });\n if (!res.ok || disposed) {\n return;\n }\n const value = (await res.text()).trim();\n if (disposed) {\n return;\n }\n if (baseline === null) {\n baseline = value;\n } else if (value !== baseline) {\n fired = true;\n stop();\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 function onVisibility(): void {\n if (win.document.visibilityState === 'visible') {\n void check();\n }\n }\n\n void check();\n timer = win.setInterval(() => void check(), opts.intervalMs);\n win.document.addEventListener('visibilitychange', onVisibility);\n return {\n dispose: (): void => {\n disposed = true;\n stop();\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 const win = doc.defaultView ?? window;\n injectStyles(doc);\n opts.root.classList.add('gs-root');\n const parts: Disposable[] = [startFit(win)];\n if (opts.install !== false) {\n parts.push(startInstall({ appName: opts.appName, labels: opts.labels, ...opts.install, win }));\n }\n if (opts.crash !== false) {\n parts.push(startCrash({ labels: opts.labels, report: opts.crash?.report, appVersion: opts.appVersion, win }));\n }\n if (opts.lifecycle) {\n parts.push(startLifecycle(opts.lifecycle, doc));\n }\n if (opts.version) {\n parts.push(startVersionPoll({ ...opts.version, win }));\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","isErrorEvent","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","ariaLabel","button","renderChip","seal","steps","chip","renderCard","_a","card","app","txt","acts","renderBanner","wrap","e","DESKTOP_MIN_WIDTH","IOS_DEVICE_RE","INSTALLED_DISPLAY_MODES","IOS_OTHER_BROWSER_RE","isStandalone","win","mode","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","dismissed","unmount","_a","snooze","writeSnooze","install","evt","e","renderBanner","isDesktop","startInstall","_b","_c","isStandalone","isSnoozed","ctl","onPrompt","onInstalled","onResize","node","isIosSafari","REDUCED_MOTION_QUERY","UNLOCK_EVENTS","RUNNING","startLifecycle","opts","doc","onChange","_a","_b","unlockAudio","ctx","win","detach","type","onGesture","settle","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","disposed","timer","stop","onVisibility","check","res","value","e","initGameShell","opts","_a","_b","doc","win","injectStyles","parts","startFit","startInstall","startCrash","startLifecycle","startVersionPoll","part"]}
package/dist/index.d.mts CHANGED
@@ -16,6 +16,7 @@ interface GameShellLabels {
16
16
  install: string;
17
17
  addToHome: string;
18
18
  notNow: string;
19
+ close?: string;
19
20
  tapShare: string;
20
21
  tapAdd: string;
21
22
  installBody: string;
@@ -25,7 +26,7 @@ interface GameShellLabels {
25
26
  copyDetails: string;
26
27
  errorReported: string;
27
28
  }
28
- type InstallLabels = Pick<GameShellLabels, 'install' | 'addToHome' | 'notNow' | 'tapShare' | 'tapAdd' | 'installBody'>;
29
+ type InstallLabels = Pick<GameShellLabels, 'install' | 'addToHome' | 'notNow' | 'close' | 'tapShare' | 'tapAdd' | 'installBody'>;
29
30
  type CrashLabels = Pick<GameShellLabels, 'crashTitle' | 'crashBody' | 'reload' | 'copyDetails' | 'errorReported'>;
30
31
 
31
32
  interface LifecycleOptions {
@@ -33,6 +34,7 @@ interface LifecycleOptions {
33
34
  onVisible?(): void;
34
35
  }
35
36
  interface ResumableAudio {
37
+ readonly state?: string;
36
38
  resume(): Promise<void> | void;
37
39
  }
38
40
  declare function startLifecycle(opts: LifecycleOptions, doc?: Document): Disposable;
package/dist/index.d.ts CHANGED
@@ -16,6 +16,7 @@ interface GameShellLabels {
16
16
  install: string;
17
17
  addToHome: string;
18
18
  notNow: string;
19
+ close?: string;
19
20
  tapShare: string;
20
21
  tapAdd: string;
21
22
  installBody: string;
@@ -25,7 +26,7 @@ interface GameShellLabels {
25
26
  copyDetails: string;
26
27
  errorReported: string;
27
28
  }
28
- type InstallLabels = Pick<GameShellLabels, 'install' | 'addToHome' | 'notNow' | 'tapShare' | 'tapAdd' | 'installBody'>;
29
+ type InstallLabels = Pick<GameShellLabels, 'install' | 'addToHome' | 'notNow' | 'close' | 'tapShare' | 'tapAdd' | 'installBody'>;
29
30
  type CrashLabels = Pick<GameShellLabels, 'crashTitle' | 'crashBody' | 'reload' | 'copyDetails' | 'errorReported'>;
30
31
 
31
32
  interface LifecycleOptions {
@@ -33,6 +34,7 @@ interface LifecycleOptions {
33
34
  onVisible?(): void;
34
35
  }
35
36
  interface ResumableAudio {
37
+ readonly state?: string;
36
38
  resume(): Promise<void> | void;
37
39
  }
38
40
  declare function startLifecycle(opts: LifecycleOptions, doc?: Document): Disposable;
package/dist/index.js CHANGED
@@ -78,6 +78,9 @@ function fromRejection(e) {
78
78
  }
79
79
  return { kind: CRASH_KIND.Rejection, message: String(reason) };
80
80
  }
81
+ function isErrorEvent(e) {
82
+ return typeof e.message === "string";
83
+ }
81
84
  function safeReport(opts, info) {
82
85
  try {
83
86
  return opts.report?.(info) ?? void 0;
@@ -113,7 +116,7 @@ function startCrash(opts) {
113
116
  view.reload.focus();
114
117
  };
115
118
  const onError = (e) => {
116
- if (e instanceof ErrorEvent) {
119
+ if (isErrorEvent(e)) {
117
120
  handle(fromErrorEvent(e));
118
121
  }
119
122
  };
@@ -182,6 +185,7 @@ function startFit(win = window) {
182
185
  return {
183
186
  dispose: () => {
184
187
  disposed = true;
188
+ root.style.removeProperty(GS_VH_VAR);
185
189
  vv?.removeEventListener("resize", onChange);
186
190
  win.removeEventListener("resize", onChange);
187
191
  win.removeEventListener("orientationchange", onChange);
@@ -210,8 +214,8 @@ function renderSteps(p) {
210
214
  list.append(share, add);
211
215
  return list;
212
216
  }
213
- function closeButton(p) {
214
- return button(p.doc, { className: "gs-x", ariaLabel: p.labels.notNow, icon: ICONS.close, onClick: p.onDismiss });
217
+ function closeButton(p, ariaLabel = p.labels.notNow) {
218
+ return button(p.doc, { className: "gs-x", ariaLabel, icon: ICONS.close, onClick: p.onDismiss });
215
219
  }
216
220
  function renderChip(p) {
217
221
  const seal = el(p.doc, "div", "gs-seal");
@@ -247,7 +251,7 @@ function renderCard(p) {
247
251
  acts.append(button(p.doc, { className: "gs-btn gs-primary", text: p.labels.install, icon: ICONS.download, onClick: p.onInstall }));
248
252
  }
249
253
  acts.append(button(p.doc, { className: "gs-btn gs-ghost", text: p.labels.notNow, onClick: p.onDismiss }));
250
- card.append(closeButton(p), app, txt);
254
+ card.append(closeButton(p, p.labels.close ?? p.labels.notNow), app, txt);
251
255
  if (p.variant === "ios" /* Ios */) {
252
256
  card.append(renderSteps(p));
253
257
  }
@@ -262,6 +266,7 @@ function renderBanner(p) {
262
266
  wrap.setAttribute("aria-label", p.variant === "prompt" /* Prompt */ ? p.labels.install : p.labels.addToHome);
263
267
  wrap.addEventListener("keydown", (e) => {
264
268
  if (e.key === "Escape") {
269
+ e.stopPropagation();
265
270
  p.onDismiss();
266
271
  }
267
272
  });
@@ -272,9 +277,10 @@ function renderBanner(p) {
272
277
  // src/install/platform.ts
273
278
  var DESKTOP_MIN_WIDTH = 1024;
274
279
  var IOS_DEVICE_RE = /iPad|iPhone|iPod/;
280
+ var INSTALLED_DISPLAY_MODES = ["standalone", "fullscreen"];
275
281
  var IOS_OTHER_BROWSER_RE = /CriOS|FxiOS|EdgiOS|OPiOS/;
276
282
  function isStandalone(win) {
277
- const media = typeof win.matchMedia === "function" && win.matchMedia("(display-mode: standalone)").matches;
283
+ const media = typeof win.matchMedia === "function" && INSTALLED_DISPLAY_MODES.some((mode) => win.matchMedia(`(display-mode: ${mode})`).matches);
278
284
  return media || win.navigator.standalone === true;
279
285
  }
280
286
  function isIosSafari(win) {
@@ -317,11 +323,12 @@ var ACCEPTED = "accepted";
317
323
  function createController(opts, win, now) {
318
324
  const key = snoozeKey(opts.appName, opts.storageKey);
319
325
  const state = { deferred: null, variant: null, node: null };
326
+ let dismissed = false;
320
327
  const unmount = () => {
321
328
  state.node?.remove();
322
329
  state.node = null;
323
330
  };
324
- const dismiss = () => {
331
+ const snooze = () => {
325
332
  writeSnooze(win, key, now());
326
333
  unmount();
327
334
  };
@@ -338,7 +345,7 @@ function createController(opts, win, now) {
338
345
  unmount();
339
346
  return;
340
347
  }
341
- dismiss();
348
+ snooze();
342
349
  } catch {
343
350
  unmount();
344
351
  }
@@ -355,11 +362,14 @@ function createController(opts, win, now) {
355
362
  appName: opts.appName,
356
363
  labels: opts.labels,
357
364
  onInstall: () => void install(),
358
- onDismiss: dismiss
365
+ onDismiss: () => {
366
+ dismissed = true;
367
+ snooze();
368
+ }
359
369
  });
360
370
  win.document.body.append(state.node);
361
371
  };
362
- return { state, mount, unmount };
372
+ return { state, isDismissed: () => dismissed, mount, unmount };
363
373
  }
364
374
  function startInstall(opts) {
365
375
  const win = opts.win ?? window;
@@ -373,7 +383,9 @@ function startInstall(opts) {
373
383
  e.preventDefault();
374
384
  ctl.state.deferred = e;
375
385
  ctl.state.variant = "prompt" /* Prompt */;
376
- ctl.mount();
386
+ if (!ctl.isDismissed()) {
387
+ ctl.mount();
388
+ }
377
389
  };
378
390
  const onInstalled = () => {
379
391
  ctl.state.variant = null;
@@ -404,7 +416,8 @@ function startInstall(opts) {
404
416
 
405
417
  // src/lifecycle/lifecycle.ts
406
418
  var REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
407
- var UNLOCK_EVENTS = ["pointerdown", "keydown"];
419
+ var UNLOCK_EVENTS = ["pointerup", "touchend", "keydown"];
420
+ var RUNNING = "running";
408
421
  function startLifecycle(opts, doc = document) {
409
422
  const onChange = () => {
410
423
  if (doc.visibilityState === "hidden") {
@@ -418,16 +431,24 @@ function startLifecycle(opts, doc = document) {
418
431
  }
419
432
  function unlockAudio(ctx, win = window) {
420
433
  const detach = () => {
421
- UNLOCK_EVENTS.forEach((type) => win.removeEventListener(type, onFirst, true));
434
+ UNLOCK_EVENTS.forEach((type) => win.removeEventListener(type, onGesture, true));
435
+ };
436
+ const settle = () => {
437
+ if (ctx.state === void 0 || ctx.state === RUNNING) {
438
+ detach();
439
+ }
422
440
  };
423
- function onFirst() {
424
- detach();
441
+ function onGesture() {
442
+ if (ctx.state === RUNNING) {
443
+ detach();
444
+ return;
445
+ }
425
446
  try {
426
- Promise.resolve(ctx.resume()).catch(() => void 0);
447
+ Promise.resolve(ctx.resume()).then(settle, () => void 0);
427
448
  } catch {
428
449
  }
429
450
  }
430
- UNLOCK_EVENTS.forEach((type) => win.addEventListener(type, onFirst, true));
451
+ UNLOCK_EVENTS.forEach((type) => win.addEventListener(type, onGesture, true));
431
452
  return { dispose: detach };
432
453
  }
433
454
  function prefersReducedMotion(win = window) {
@@ -497,21 +518,31 @@ function startVersionPoll(opts) {
497
518
  let baseline = null;
498
519
  let fired = false;
499
520
  let busy = false;
521
+ let disposed = false;
522
+ let timer = 0;
523
+ const stop = () => {
524
+ win.clearInterval(timer);
525
+ win.document.removeEventListener("visibilitychange", onVisibility);
526
+ };
500
527
  const check = async () => {
501
- if (fired || busy) {
528
+ if (fired || busy || disposed) {
502
529
  return;
503
530
  }
504
531
  busy = true;
505
532
  try {
506
533
  const res = await fetchFn(opts.url, { cache: "no-store" });
507
- if (!res.ok) {
534
+ if (!res.ok || disposed) {
508
535
  return;
509
536
  }
510
537
  const value = (await res.text()).trim();
538
+ if (disposed) {
539
+ return;
540
+ }
511
541
  if (baseline === null) {
512
542
  baseline = value;
513
543
  } else if (value !== baseline) {
514
544
  fired = true;
545
+ stop();
515
546
  opts.onNewVersion();
516
547
  }
517
548
  } catch {
@@ -519,18 +550,18 @@ function startVersionPoll(opts) {
519
550
  busy = false;
520
551
  }
521
552
  };
522
- const onVisibility = () => {
553
+ function onVisibility() {
523
554
  if (win.document.visibilityState === "visible") {
524
555
  void check();
525
556
  }
526
- };
557
+ }
527
558
  void check();
528
- const timer = win.setInterval(() => void check(), opts.intervalMs);
559
+ timer = win.setInterval(() => void check(), opts.intervalMs);
529
560
  win.document.addEventListener("visibilitychange", onVisibility);
530
561
  return {
531
562
  dispose: () => {
532
- win.clearInterval(timer);
533
- win.document.removeEventListener("visibilitychange", onVisibility);
563
+ disposed = true;
564
+ stop();
534
565
  }
535
566
  };
536
567
  }
@@ -538,20 +569,21 @@ function startVersionPoll(opts) {
538
569
  // src/initGameShell.ts
539
570
  function initGameShell(opts) {
540
571
  const doc = opts.root.ownerDocument;
572
+ const win = doc.defaultView ?? window;
541
573
  injectStyles(doc);
542
574
  opts.root.classList.add("gs-root");
543
- const parts = [startFit()];
575
+ const parts = [startFit(win)];
544
576
  if (opts.install !== false) {
545
- parts.push(startInstall({ appName: opts.appName, labels: opts.labels, ...opts.install }));
577
+ parts.push(startInstall({ appName: opts.appName, labels: opts.labels, ...opts.install, win }));
546
578
  }
547
579
  if (opts.crash !== false) {
548
- parts.push(startCrash({ labels: opts.labels, report: opts.crash?.report, appVersion: opts.appVersion }));
580
+ parts.push(startCrash({ labels: opts.labels, report: opts.crash?.report, appVersion: opts.appVersion, win }));
549
581
  }
550
582
  if (opts.lifecycle) {
551
583
  parts.push(startLifecycle(opts.lifecycle, doc));
552
584
  }
553
585
  if (opts.version) {
554
- parts.push(startVersionPoll(opts.version));
586
+ parts.push(startVersionPoll({ ...opts.version, win }));
555
587
  }
556
588
  return {
557
589
  dispose: () => {