loading-games 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ var o={primary:"#6366F1",background:"#0F0F0F",surface:"#1A1A2E",text:"#F8F8F8",accent:"#E94560"},c={primary:"#4F46E5",background:"#FFFFFF",surface:"#F3F4F6",text:"#111827",accent:"#E94560"},m={primary:"--lg-primary",background:"--lg-background",surface:"--lg-surface",text:"--lg-text",accent:"--lg-accent"};function d(i){return typeof window>"u"?null:getComputedStyle(document.documentElement).getPropertyValue(i).trim()||null}function u(){if(typeof window>"u")return o;try{return window.matchMedia("(prefers-color-scheme: dark)").matches?o:c}catch{return o}}function l(i){let r={...u()};for(let[n,e]of Object.entries(m)){let s=d(e);s&&(r[n]=s)}if(i)for(let[n,e]of Object.entries(i))e&&(r[n]=e);return r}var a=class{constructor(t,r){this._onScore=t;this._onGameOver=r}name="2048";bundleSize=5e3;canvas;ctx;theme;animFrameId=null;running=!1;score=0;init(t,r){this.canvas=t,this.ctx=t.getContext("2d"),this.theme=l(r);let n=window.devicePixelRatio||1,e=t.getBoundingClientRect();t.width=e.width*n,t.height=e.height*n,this.ctx.scale(n,n),t.style.width=`${e.width}px`,t.style.height=`${e.height}px`}start(){this.running=!0,this.render()}pause(){this.running=!1,this.animFrameId!==null&&(cancelAnimationFrame(this.animFrameId),this.animFrameId=null)}resume(){this.running||(this.running=!0,this.render())}destroy(){this.pause()}getScore(){return this.score}render(){let t=this.canvas.getBoundingClientRect(),r=t.width,n=t.height,{ctx:e,theme:s}=this;e.fillStyle=s.background,e.fillRect(0,0,r,n),e.fillStyle=s.text,e.font="bold 18px system-ui",e.textAlign="center",e.textBaseline="middle",e.fillText("2048",r/2,n/2-16),e.font="14px system-ui",e.globalAlpha=.6,e.fillText("Coming soon!",r/2,n/2+16),e.globalAlpha=1}};export{a as Game2048};
2
+ //# sourceMappingURL=2048.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/theme.ts","../../src/games/2048/index.ts"],"sourcesContent":["/**\r\n * Theme resolution — 3-level priority:\r\n * 1. Explicit theme prop (highest)\r\n * 2. CSS variables on :root (--lg-primary, etc.)\r\n * 3. prefers-color-scheme detection (lowest)\r\n */\r\n\r\nimport type { ThemeObject, ResolvedTheme } from './types.js'\r\n\r\nconst DARK_DEFAULTS: ResolvedTheme = {\r\n primary: '#6366F1',\r\n background: '#0F0F0F',\r\n surface: '#1A1A2E',\r\n text: '#F8F8F8',\r\n accent: '#E94560',\r\n}\r\n\r\nconst LIGHT_DEFAULTS: ResolvedTheme = {\r\n primary: '#4F46E5',\r\n background: '#FFFFFF',\r\n surface: '#F3F4F6',\r\n text: '#111827',\r\n accent: '#E94560',\r\n}\r\n\r\nconst CSS_VAR_MAP: Record<keyof ResolvedTheme, string> = {\r\n primary: '--lg-primary',\r\n background: '--lg-background',\r\n surface: '--lg-surface',\r\n text: '--lg-text',\r\n accent: '--lg-accent',\r\n}\r\n\r\n/**\r\n * Read a CSS variable from :root, returns null if not set.\r\n */\r\nfunction readCSSVar(varName: string): string | null {\r\n if (typeof window === 'undefined') return null\r\n const value = getComputedStyle(document.documentElement)\r\n .getPropertyValue(varName)\r\n .trim()\r\n return value || null\r\n}\r\n\r\n/**\r\n * Detect system color scheme preference.\r\n */\r\nfunction getSystemDefaults(): ResolvedTheme {\r\n if (typeof window === 'undefined') return DARK_DEFAULTS\r\n try {\r\n const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches\r\n return prefersDark ? DARK_DEFAULTS : LIGHT_DEFAULTS\r\n } catch {\r\n return DARK_DEFAULTS\r\n }\r\n}\r\n\r\n/**\r\n * Resolve the final theme from all sources.\r\n * Priority: explicit prop > CSS variables > system defaults.\r\n */\r\nexport function resolveTheme(theme?: ThemeObject): ResolvedTheme {\r\n const systemDefaults = getSystemDefaults()\r\n\r\n const resolved: ResolvedTheme = { ...systemDefaults }\r\n\r\n // Level 2: CSS variables\r\n for (const [key, cssVar] of Object.entries(CSS_VAR_MAP) as [keyof ResolvedTheme, string][]) {\r\n const cssValue = readCSSVar(cssVar)\r\n if (cssValue) {\r\n resolved[key] = cssValue\r\n }\r\n }\r\n\r\n // Level 1: Explicit theme prop\r\n if (theme) {\r\n for (const [key, value] of Object.entries(theme) as [keyof ThemeObject, string | undefined][]) {\r\n if (value) {\r\n resolved[key] = value\r\n }\r\n }\r\n }\r\n\r\n return resolved\r\n}\r\n\r\n/**\r\n * Apply resolved theme as CSS variables to a container element.\r\n * This allows games to reference CSS variables for dynamic theming.\r\n */\r\nexport function applyThemeToElement(element: HTMLElement, theme: ResolvedTheme): void {\r\n for (const [key, cssVar] of Object.entries(CSS_VAR_MAP) as [keyof ResolvedTheme, string][]) {\r\n element.style.setProperty(cssVar, theme[key])\r\n }\r\n}\r\n","/**\r\n * 2048 — Stub implementation.\r\n * Full implementation planned for Phase 3.\r\n * Bundle target: ~5 kB gzipped\r\n */\r\n\r\nimport type { GamePlugin, ThemeObject, ResolvedTheme } from '../../types.js'\r\nimport { resolveTheme } from '../../theme.js'\r\n\r\nexport class Game2048 implements GamePlugin {\r\n readonly name = '2048'\r\n readonly bundleSize = 5_000\r\n\r\n private canvas!: HTMLCanvasElement\r\n private ctx!: CanvasRenderingContext2D\r\n private theme!: ResolvedTheme\r\n private animFrameId: number | null = null\r\n private running = false\r\n private score = 0\r\n\r\n constructor(private _onScore?: (s: number) => void, private _onGameOver?: () => void) {}\r\n\r\n init(canvas: HTMLCanvasElement, theme: ThemeObject): void {\r\n this.canvas = canvas\r\n this.ctx = canvas.getContext('2d')!\r\n this.theme = resolveTheme(theme)\r\n const dpr = window.devicePixelRatio || 1\r\n const rect = canvas.getBoundingClientRect()\r\n canvas.width = rect.width * dpr\r\n canvas.height = rect.height * dpr\r\n this.ctx.scale(dpr, dpr)\r\n canvas.style.width = `${rect.width}px`\r\n canvas.style.height = `${rect.height}px`\r\n }\r\n\r\n start(): void { this.running = true; this.render() }\r\n pause(): void { this.running = false; if (this.animFrameId !== null) { cancelAnimationFrame(this.animFrameId); this.animFrameId = null } }\r\n resume(): void { if (!this.running) { this.running = true; this.render() } }\r\n destroy(): void { this.pause() }\r\n getScore(): number { return this.score }\r\n\r\n private render(): void {\r\n const rect = this.canvas.getBoundingClientRect()\r\n const W = rect.width, H = rect.height\r\n const { ctx, theme } = this\r\n ctx.fillStyle = theme.background; ctx.fillRect(0, 0, W, H)\r\n ctx.fillStyle = theme.text; ctx.font = 'bold 18px system-ui'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'\r\n ctx.fillText('2048', W / 2, H / 2 - 16)\r\n ctx.font = '14px system-ui'; ctx.globalAlpha = 0.6\r\n ctx.fillText('Coming soon!', W / 2, H / 2 + 16); ctx.globalAlpha = 1\r\n }\r\n}\r\n"],"mappings":"AASA,IAAMA,EAA+B,CACnC,QAAS,UACT,WAAY,UACZ,QAAS,UACT,KAAM,UACN,OAAQ,SACV,EAEMC,EAAgC,CACpC,QAAS,UACT,WAAY,UACZ,QAAS,UACT,KAAM,UACN,OAAQ,SACV,EAEMC,EAAmD,CACvD,QAAS,eACT,WAAY,kBACZ,QAAS,eACT,KAAM,YACN,OAAQ,aACV,EAKA,SAASC,EAAWC,EAAgC,CAClD,OAAI,OAAO,OAAW,IAAoB,KAC5B,iBAAiB,SAAS,eAAe,EACpD,iBAAiBA,CAAO,EACxB,KAAK,GACQ,IAClB,CAKA,SAASC,GAAmC,CAC1C,GAAI,OAAO,OAAW,IAAa,OAAOL,EAC1C,GAAI,CAEF,OADoB,OAAO,WAAW,8BAA8B,EAAE,QACjDA,EAAgBC,CACvC,MAAQ,CACN,OAAOD,CACT,CACF,CAMO,SAASM,EAAaC,EAAoC,CAG/D,IAAMC,EAA0B,CAAE,GAFXH,EAAkB,CAEW,EAGpD,OAAW,CAACI,EAAKC,CAAM,IAAK,OAAO,QAAQR,CAAW,EAAsC,CAC1F,IAAMS,EAAWR,EAAWO,CAAM,EAC9BC,IACFH,EAASC,CAAG,EAAIE,EAEpB,CAGA,GAAIJ,EACF,OAAW,CAACE,EAAKG,CAAK,IAAK,OAAO,QAAQL,CAAK,EACzCK,IACFJ,EAASC,CAAG,EAAIG,GAKtB,OAAOJ,CACT,CC3EO,IAAMK,EAAN,KAAqC,CAW1C,YAAoBC,EAAwCC,EAA0B,CAAlE,cAAAD,EAAwC,iBAAAC,CAA2B,CAV9E,KAAO,OACP,WAAa,IAEd,OACA,IACA,MACA,YAA6B,KAC7B,QAAU,GACV,MAAQ,EAIhB,KAAKC,EAA2BC,EAA0B,CACxD,KAAK,OAASD,EACd,KAAK,IAAMA,EAAO,WAAW,IAAI,EACjC,KAAK,MAAQE,EAAaD,CAAK,EAC/B,IAAME,EAAM,OAAO,kBAAoB,EACjCC,EAAOJ,EAAO,sBAAsB,EAC1CA,EAAO,MAAQI,EAAK,MAAQD,EAC5BH,EAAO,OAASI,EAAK,OAASD,EAC9B,KAAK,IAAI,MAAMA,EAAKA,CAAG,EACvBH,EAAO,MAAM,MAAQ,GAAGI,EAAK,KAAK,KAClCJ,EAAO,MAAM,OAAS,GAAGI,EAAK,MAAM,IACtC,CAEA,OAAc,CAAE,KAAK,QAAU,GAAM,KAAK,OAAO,CAAE,CACnD,OAAc,CAAE,KAAK,QAAU,GAAW,KAAK,cAAgB,OAAQ,qBAAqB,KAAK,WAAW,EAAG,KAAK,YAAc,KAAO,CACzI,QAAe,CAAO,KAAK,UAAW,KAAK,QAAU,GAAM,KAAK,OAAO,EAAI,CAC3E,SAAgB,CAAE,KAAK,MAAM,CAAE,CAC/B,UAAmB,CAAE,OAAO,KAAK,KAAM,CAE/B,QAAe,CACrB,IAAMA,EAAO,KAAK,OAAO,sBAAsB,EACzCC,EAAID,EAAK,MAAOE,EAAIF,EAAK,OACzB,CAAE,IAAAG,EAAK,MAAAN,CAAM,EAAI,KACvBM,EAAI,UAAYN,EAAM,WAAYM,EAAI,SAAS,EAAG,EAAGF,EAAGC,CAAC,EACzDC,EAAI,UAAYN,EAAM,KAAMM,EAAI,KAAO,sBAAuBA,EAAI,UAAY,SAAUA,EAAI,aAAe,SAC3GA,EAAI,SAAS,OAAQF,EAAI,EAAGC,EAAI,EAAI,EAAE,EACtCC,EAAI,KAAO,iBAAkBA,EAAI,YAAc,GAC/CA,EAAI,SAAS,eAAgBF,EAAI,EAAGC,EAAI,EAAI,EAAE,EAAGC,EAAI,YAAc,CACrE,CACF","names":["DARK_DEFAULTS","LIGHT_DEFAULTS","CSS_VAR_MAP","readCSSVar","varName","getSystemDefaults","resolveTheme","theme","resolved","key","cssVar","cssValue","value","Game2048","_onScore","_onGameOver","canvas","theme","resolveTheme","dpr","rect","W","H","ctx"]}
@@ -0,0 +1,2 @@
1
+ var o={primary:"#6366F1",background:"#0F0F0F",surface:"#1A1A2E",text:"#F8F8F8",accent:"#E94560"},c={primary:"#4F46E5",background:"#FFFFFF",surface:"#F3F4F6",text:"#111827",accent:"#E94560"},d={primary:"--lg-primary",background:"--lg-background",surface:"--lg-surface",text:"--lg-text",accent:"--lg-accent"};function m(i){return typeof window>"u"?null:getComputedStyle(document.documentElement).getPropertyValue(i).trim()||null}function u(){if(typeof window>"u")return o;try{return window.matchMedia("(prefers-color-scheme: dark)").matches?o:c}catch{return o}}function l(i){let r={...u()};for(let[n,e]of Object.entries(d)){let s=m(e);s&&(r[n]=s)}if(i)for(let[n,e]of Object.entries(i))e&&(r[n]=e);return r}var a=class{constructor(t,r){this._onScore=t;this._onGameOver=r}name="asteroids";bundleSize=8e3;canvas;ctx;theme;animFrameId=null;running=!1;score=0;init(t,r){this.canvas=t,this.ctx=t.getContext("2d"),this.theme=l(r);let n=window.devicePixelRatio||1,e=t.getBoundingClientRect();t.width=e.width*n,t.height=e.height*n,this.ctx.scale(n,n),t.style.width=`${e.width}px`,t.style.height=`${e.height}px`}start(){this.running=!0,this.render()}pause(){this.running=!1,this.animFrameId!==null&&(cancelAnimationFrame(this.animFrameId),this.animFrameId=null)}resume(){this.running||(this.running=!0,this.render())}destroy(){this.pause()}getScore(){return this.score}render(){let t=this.canvas.getBoundingClientRect(),r=t.width,n=t.height,{ctx:e,theme:s}=this;e.fillStyle=s.background,e.fillRect(0,0,r,n),e.fillStyle=s.text,e.font="bold 18px system-ui",e.textAlign="center",e.textBaseline="middle",e.fillText("Asteroids",r/2,n/2-16),e.font="14px system-ui",e.globalAlpha=.6,e.fillText("Coming soon!",r/2,n/2+16),e.globalAlpha=1}};export{a as AsteroidsGame};
2
+ //# sourceMappingURL=asteroids.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/theme.ts","../../src/games/asteroids/index.ts"],"sourcesContent":["/**\r\n * Theme resolution — 3-level priority:\r\n * 1. Explicit theme prop (highest)\r\n * 2. CSS variables on :root (--lg-primary, etc.)\r\n * 3. prefers-color-scheme detection (lowest)\r\n */\r\n\r\nimport type { ThemeObject, ResolvedTheme } from './types.js'\r\n\r\nconst DARK_DEFAULTS: ResolvedTheme = {\r\n primary: '#6366F1',\r\n background: '#0F0F0F',\r\n surface: '#1A1A2E',\r\n text: '#F8F8F8',\r\n accent: '#E94560',\r\n}\r\n\r\nconst LIGHT_DEFAULTS: ResolvedTheme = {\r\n primary: '#4F46E5',\r\n background: '#FFFFFF',\r\n surface: '#F3F4F6',\r\n text: '#111827',\r\n accent: '#E94560',\r\n}\r\n\r\nconst CSS_VAR_MAP: Record<keyof ResolvedTheme, string> = {\r\n primary: '--lg-primary',\r\n background: '--lg-background',\r\n surface: '--lg-surface',\r\n text: '--lg-text',\r\n accent: '--lg-accent',\r\n}\r\n\r\n/**\r\n * Read a CSS variable from :root, returns null if not set.\r\n */\r\nfunction readCSSVar(varName: string): string | null {\r\n if (typeof window === 'undefined') return null\r\n const value = getComputedStyle(document.documentElement)\r\n .getPropertyValue(varName)\r\n .trim()\r\n return value || null\r\n}\r\n\r\n/**\r\n * Detect system color scheme preference.\r\n */\r\nfunction getSystemDefaults(): ResolvedTheme {\r\n if (typeof window === 'undefined') return DARK_DEFAULTS\r\n try {\r\n const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches\r\n return prefersDark ? DARK_DEFAULTS : LIGHT_DEFAULTS\r\n } catch {\r\n return DARK_DEFAULTS\r\n }\r\n}\r\n\r\n/**\r\n * Resolve the final theme from all sources.\r\n * Priority: explicit prop > CSS variables > system defaults.\r\n */\r\nexport function resolveTheme(theme?: ThemeObject): ResolvedTheme {\r\n const systemDefaults = getSystemDefaults()\r\n\r\n const resolved: ResolvedTheme = { ...systemDefaults }\r\n\r\n // Level 2: CSS variables\r\n for (const [key, cssVar] of Object.entries(CSS_VAR_MAP) as [keyof ResolvedTheme, string][]) {\r\n const cssValue = readCSSVar(cssVar)\r\n if (cssValue) {\r\n resolved[key] = cssValue\r\n }\r\n }\r\n\r\n // Level 1: Explicit theme prop\r\n if (theme) {\r\n for (const [key, value] of Object.entries(theme) as [keyof ThemeObject, string | undefined][]) {\r\n if (value) {\r\n resolved[key] = value\r\n }\r\n }\r\n }\r\n\r\n return resolved\r\n}\r\n\r\n/**\r\n * Apply resolved theme as CSS variables to a container element.\r\n * This allows games to reference CSS variables for dynamic theming.\r\n */\r\nexport function applyThemeToElement(element: HTMLElement, theme: ResolvedTheme): void {\r\n for (const [key, cssVar] of Object.entries(CSS_VAR_MAP) as [keyof ResolvedTheme, string][]) {\r\n element.style.setProperty(cssVar, theme[key])\r\n }\r\n}\r\n","/**\r\n * Asteroids — Stub implementation.\r\n * Full implementation planned for Phase 3.\r\n * Bundle target: ~8 kB gzipped\r\n */\r\n\r\nimport type { GamePlugin, ThemeObject, ResolvedTheme } from '../../types.js'\r\nimport { resolveTheme } from '../../theme.js'\r\n\r\nexport class AsteroidsGame implements GamePlugin {\r\n readonly name = 'asteroids'\r\n readonly bundleSize = 8_000\r\n\r\n private canvas!: HTMLCanvasElement\r\n private ctx!: CanvasRenderingContext2D\r\n private theme!: ResolvedTheme\r\n private animFrameId: number | null = null\r\n private running = false\r\n private score = 0\r\n\r\n constructor(private _onScore?: (s: number) => void, private _onGameOver?: () => void) {}\r\n\r\n init(canvas: HTMLCanvasElement, theme: ThemeObject): void {\r\n this.canvas = canvas\r\n this.ctx = canvas.getContext('2d')!\r\n this.theme = resolveTheme(theme)\r\n const dpr = window.devicePixelRatio || 1\r\n const rect = canvas.getBoundingClientRect()\r\n canvas.width = rect.width * dpr\r\n canvas.height = rect.height * dpr\r\n this.ctx.scale(dpr, dpr)\r\n canvas.style.width = `${rect.width}px`\r\n canvas.style.height = `${rect.height}px`\r\n }\r\n\r\n start(): void { this.running = true; this.render() }\r\n pause(): void { this.running = false; if (this.animFrameId !== null) { cancelAnimationFrame(this.animFrameId); this.animFrameId = null } }\r\n resume(): void { if (!this.running) { this.running = true; this.render() } }\r\n destroy(): void { this.pause() }\r\n getScore(): number { return this.score }\r\n\r\n private render(): void {\r\n const rect = this.canvas.getBoundingClientRect()\r\n const W = rect.width, H = rect.height\r\n const { ctx, theme } = this\r\n ctx.fillStyle = theme.background; ctx.fillRect(0, 0, W, H)\r\n ctx.fillStyle = theme.text; ctx.font = 'bold 18px system-ui'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'\r\n ctx.fillText('Asteroids', W / 2, H / 2 - 16)\r\n ctx.font = '14px system-ui'; ctx.globalAlpha = 0.6\r\n ctx.fillText('Coming soon!', W / 2, H / 2 + 16); ctx.globalAlpha = 1\r\n }\r\n}\r\n"],"mappings":"AASA,IAAMA,EAA+B,CACnC,QAAS,UACT,WAAY,UACZ,QAAS,UACT,KAAM,UACN,OAAQ,SACV,EAEMC,EAAgC,CACpC,QAAS,UACT,WAAY,UACZ,QAAS,UACT,KAAM,UACN,OAAQ,SACV,EAEMC,EAAmD,CACvD,QAAS,eACT,WAAY,kBACZ,QAAS,eACT,KAAM,YACN,OAAQ,aACV,EAKA,SAASC,EAAWC,EAAgC,CAClD,OAAI,OAAO,OAAW,IAAoB,KAC5B,iBAAiB,SAAS,eAAe,EACpD,iBAAiBA,CAAO,EACxB,KAAK,GACQ,IAClB,CAKA,SAASC,GAAmC,CAC1C,GAAI,OAAO,OAAW,IAAa,OAAOL,EAC1C,GAAI,CAEF,OADoB,OAAO,WAAW,8BAA8B,EAAE,QACjDA,EAAgBC,CACvC,MAAQ,CACN,OAAOD,CACT,CACF,CAMO,SAASM,EAAaC,EAAoC,CAG/D,IAAMC,EAA0B,CAAE,GAFXH,EAAkB,CAEW,EAGpD,OAAW,CAACI,EAAKC,CAAM,IAAK,OAAO,QAAQR,CAAW,EAAsC,CAC1F,IAAMS,EAAWR,EAAWO,CAAM,EAC9BC,IACFH,EAASC,CAAG,EAAIE,EAEpB,CAGA,GAAIJ,EACF,OAAW,CAACE,EAAKG,CAAK,IAAK,OAAO,QAAQL,CAAK,EACzCK,IACFJ,EAASC,CAAG,EAAIG,GAKtB,OAAOJ,CACT,CC3EO,IAAMK,EAAN,KAA0C,CAW/C,YAAoBC,EAAwCC,EAA0B,CAAlE,cAAAD,EAAwC,iBAAAC,CAA2B,CAV9E,KAAO,YACP,WAAa,IAEd,OACA,IACA,MACA,YAA6B,KAC7B,QAAU,GACV,MAAQ,EAIhB,KAAKC,EAA2BC,EAA0B,CACxD,KAAK,OAASD,EACd,KAAK,IAAMA,EAAO,WAAW,IAAI,EACjC,KAAK,MAAQE,EAAaD,CAAK,EAC/B,IAAME,EAAM,OAAO,kBAAoB,EACjCC,EAAOJ,EAAO,sBAAsB,EAC1CA,EAAO,MAAQI,EAAK,MAAQD,EAC5BH,EAAO,OAASI,EAAK,OAASD,EAC9B,KAAK,IAAI,MAAMA,EAAKA,CAAG,EACvBH,EAAO,MAAM,MAAQ,GAAGI,EAAK,KAAK,KAClCJ,EAAO,MAAM,OAAS,GAAGI,EAAK,MAAM,IACtC,CAEA,OAAc,CAAE,KAAK,QAAU,GAAM,KAAK,OAAO,CAAE,CACnD,OAAc,CAAE,KAAK,QAAU,GAAW,KAAK,cAAgB,OAAQ,qBAAqB,KAAK,WAAW,EAAG,KAAK,YAAc,KAAO,CACzI,QAAe,CAAO,KAAK,UAAW,KAAK,QAAU,GAAM,KAAK,OAAO,EAAI,CAC3E,SAAgB,CAAE,KAAK,MAAM,CAAE,CAC/B,UAAmB,CAAE,OAAO,KAAK,KAAM,CAE/B,QAAe,CACrB,IAAMA,EAAO,KAAK,OAAO,sBAAsB,EACzCC,EAAID,EAAK,MAAOE,EAAIF,EAAK,OACzB,CAAE,IAAAG,EAAK,MAAAN,CAAM,EAAI,KACvBM,EAAI,UAAYN,EAAM,WAAYM,EAAI,SAAS,EAAG,EAAGF,EAAGC,CAAC,EACzDC,EAAI,UAAYN,EAAM,KAAMM,EAAI,KAAO,sBAAuBA,EAAI,UAAY,SAAUA,EAAI,aAAe,SAC3GA,EAAI,SAAS,YAAaF,EAAI,EAAGC,EAAI,EAAI,EAAE,EAC3CC,EAAI,KAAO,iBAAkBA,EAAI,YAAc,GAC/CA,EAAI,SAAS,eAAgBF,EAAI,EAAGC,EAAI,EAAI,EAAE,EAAGC,EAAI,YAAc,CACrE,CACF","names":["DARK_DEFAULTS","LIGHT_DEFAULTS","CSS_VAR_MAP","readCSSVar","varName","getSystemDefaults","resolveTheme","theme","resolved","key","cssVar","cssValue","value","AsteroidsGame","_onScore","_onGameOver","canvas","theme","resolveTheme","dpr","rect","W","H","ctx"]}
@@ -0,0 +1,2 @@
1
+ var o={primary:"#6366F1",background:"#0F0F0F",surface:"#1A1A2E",text:"#F8F8F8",accent:"#E94560"},c={primary:"#4F46E5",background:"#FFFFFF",surface:"#F3F4F6",text:"#111827",accent:"#E94560"},m={primary:"--lg-primary",background:"--lg-background",surface:"--lg-surface",text:"--lg-text",accent:"--lg-accent"};function d(i){return typeof window>"u"?null:getComputedStyle(document.documentElement).getPropertyValue(i).trim()||null}function u(){if(typeof window>"u")return o;try{return window.matchMedia("(prefers-color-scheme: dark)").matches?o:c}catch{return o}}function l(i){let r={...u()};for(let[n,e]of Object.entries(m)){let s=d(e);s&&(r[n]=s)}if(i)for(let[n,e]of Object.entries(i))e&&(r[n]=e);return r}var a=class{constructor(t,r){this.onScore=t;this._onGameOver=r}name="brick-breaker";bundleSize=6e3;canvas;ctx;theme;animFrameId=null;running=!1;score=0;init(t,r){this.canvas=t,this.ctx=t.getContext("2d"),this.theme=l(r);let n=window.devicePixelRatio||1,e=t.getBoundingClientRect();t.width=e.width*n,t.height=e.height*n,this.ctx.scale(n,n),t.style.width=`${e.width}px`,t.style.height=`${e.height}px`}start(){this.running=!0,this.render()}pause(){this.running=!1,this.animFrameId!==null&&(cancelAnimationFrame(this.animFrameId),this.animFrameId=null)}resume(){this.running||(this.running=!0,this.render())}destroy(){this.pause()}getScore(){return this.score}render(){let t=this.canvas.getBoundingClientRect(),r=t.width,n=t.height,{ctx:e,theme:s}=this;e.fillStyle=s.background,e.fillRect(0,0,r,n),e.fillStyle=s.text,e.font="bold 18px system-ui",e.textAlign="center",e.textBaseline="middle",e.fillText("Brick Breaker",r/2,n/2-16),e.font="14px system-ui",e.globalAlpha=.6,e.fillText("Coming soon!",r/2,n/2+16),e.globalAlpha=1}};export{a as BrickBreakerGame};
2
+ //# sourceMappingURL=brick-breaker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/theme.ts","../../src/games/brick-breaker/index.ts"],"sourcesContent":["/**\r\n * Theme resolution — 3-level priority:\r\n * 1. Explicit theme prop (highest)\r\n * 2. CSS variables on :root (--lg-primary, etc.)\r\n * 3. prefers-color-scheme detection (lowest)\r\n */\r\n\r\nimport type { ThemeObject, ResolvedTheme } from './types.js'\r\n\r\nconst DARK_DEFAULTS: ResolvedTheme = {\r\n primary: '#6366F1',\r\n background: '#0F0F0F',\r\n surface: '#1A1A2E',\r\n text: '#F8F8F8',\r\n accent: '#E94560',\r\n}\r\n\r\nconst LIGHT_DEFAULTS: ResolvedTheme = {\r\n primary: '#4F46E5',\r\n background: '#FFFFFF',\r\n surface: '#F3F4F6',\r\n text: '#111827',\r\n accent: '#E94560',\r\n}\r\n\r\nconst CSS_VAR_MAP: Record<keyof ResolvedTheme, string> = {\r\n primary: '--lg-primary',\r\n background: '--lg-background',\r\n surface: '--lg-surface',\r\n text: '--lg-text',\r\n accent: '--lg-accent',\r\n}\r\n\r\n/**\r\n * Read a CSS variable from :root, returns null if not set.\r\n */\r\nfunction readCSSVar(varName: string): string | null {\r\n if (typeof window === 'undefined') return null\r\n const value = getComputedStyle(document.documentElement)\r\n .getPropertyValue(varName)\r\n .trim()\r\n return value || null\r\n}\r\n\r\n/**\r\n * Detect system color scheme preference.\r\n */\r\nfunction getSystemDefaults(): ResolvedTheme {\r\n if (typeof window === 'undefined') return DARK_DEFAULTS\r\n try {\r\n const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches\r\n return prefersDark ? DARK_DEFAULTS : LIGHT_DEFAULTS\r\n } catch {\r\n return DARK_DEFAULTS\r\n }\r\n}\r\n\r\n/**\r\n * Resolve the final theme from all sources.\r\n * Priority: explicit prop > CSS variables > system defaults.\r\n */\r\nexport function resolveTheme(theme?: ThemeObject): ResolvedTheme {\r\n const systemDefaults = getSystemDefaults()\r\n\r\n const resolved: ResolvedTheme = { ...systemDefaults }\r\n\r\n // Level 2: CSS variables\r\n for (const [key, cssVar] of Object.entries(CSS_VAR_MAP) as [keyof ResolvedTheme, string][]) {\r\n const cssValue = readCSSVar(cssVar)\r\n if (cssValue) {\r\n resolved[key] = cssValue\r\n }\r\n }\r\n\r\n // Level 1: Explicit theme prop\r\n if (theme) {\r\n for (const [key, value] of Object.entries(theme) as [keyof ThemeObject, string | undefined][]) {\r\n if (value) {\r\n resolved[key] = value\r\n }\r\n }\r\n }\r\n\r\n return resolved\r\n}\r\n\r\n/**\r\n * Apply resolved theme as CSS variables to a container element.\r\n * This allows games to reference CSS variables for dynamic theming.\r\n */\r\nexport function applyThemeToElement(element: HTMLElement, theme: ResolvedTheme): void {\r\n for (const [key, cssVar] of Object.entries(CSS_VAR_MAP) as [keyof ResolvedTheme, string][]) {\r\n element.style.setProperty(cssVar, theme[key])\r\n }\r\n}\r\n","/**\r\n * Brick Breaker — Stub implementation.\r\n * Full implementation planned for Phase 3.\r\n * Bundle target: ~6 kB gzipped\r\n */\r\n\r\nimport type { GamePlugin, ThemeObject, ResolvedTheme } from '../../types.js'\r\nimport { resolveTheme } from '../../theme.js'\r\n\r\nexport class BrickBreakerGame implements GamePlugin {\r\n readonly name = 'brick-breaker'\r\n readonly bundleSize = 6_000\r\n\r\n private canvas!: HTMLCanvasElement\r\n private ctx!: CanvasRenderingContext2D\r\n private theme!: ResolvedTheme\r\n private animFrameId: number | null = null\r\n private running = false\r\n private score = 0\r\n\r\n constructor(private onScore?: (s: number) => void, private _onGameOver?: () => void) {}\r\n\r\n init(canvas: HTMLCanvasElement, theme: ThemeObject): void {\r\n this.canvas = canvas\r\n this.ctx = canvas.getContext('2d')!\r\n this.theme = resolveTheme(theme)\r\n\r\n const dpr = window.devicePixelRatio || 1\r\n const rect = canvas.getBoundingClientRect()\r\n canvas.width = rect.width * dpr\r\n canvas.height = rect.height * dpr\r\n this.ctx.scale(dpr, dpr)\r\n canvas.style.width = `${rect.width}px`\r\n canvas.style.height = `${rect.height}px`\r\n }\r\n\r\n start(): void {\r\n this.running = true\r\n this.render()\r\n }\r\n\r\n pause(): void {\r\n this.running = false\r\n if (this.animFrameId !== null) {\r\n cancelAnimationFrame(this.animFrameId)\r\n this.animFrameId = null\r\n }\r\n }\r\n\r\n resume(): void {\r\n if (!this.running) {\r\n this.running = true\r\n this.render()\r\n }\r\n }\r\n\r\n destroy(): void {\r\n this.pause()\r\n }\r\n\r\n getScore(): number { return this.score }\r\n\r\n private render(): void {\r\n const rect = this.canvas.getBoundingClientRect()\r\n const W = rect.width\r\n const H = rect.height\r\n const { ctx, theme } = this\r\n\r\n ctx.fillStyle = theme.background\r\n ctx.fillRect(0, 0, W, H)\r\n ctx.fillStyle = theme.text\r\n ctx.font = 'bold 18px system-ui'\r\n ctx.textAlign = 'center'\r\n ctx.textBaseline = 'middle'\r\n ctx.fillText('Brick Breaker', W / 2, H / 2 - 16)\r\n ctx.font = '14px system-ui'\r\n ctx.globalAlpha = 0.6\r\n ctx.fillText('Coming soon!', W / 2, H / 2 + 16)\r\n ctx.globalAlpha = 1\r\n }\r\n}\r\n"],"mappings":"AASA,IAAMA,EAA+B,CACnC,QAAS,UACT,WAAY,UACZ,QAAS,UACT,KAAM,UACN,OAAQ,SACV,EAEMC,EAAgC,CACpC,QAAS,UACT,WAAY,UACZ,QAAS,UACT,KAAM,UACN,OAAQ,SACV,EAEMC,EAAmD,CACvD,QAAS,eACT,WAAY,kBACZ,QAAS,eACT,KAAM,YACN,OAAQ,aACV,EAKA,SAASC,EAAWC,EAAgC,CAClD,OAAI,OAAO,OAAW,IAAoB,KAC5B,iBAAiB,SAAS,eAAe,EACpD,iBAAiBA,CAAO,EACxB,KAAK,GACQ,IAClB,CAKA,SAASC,GAAmC,CAC1C,GAAI,OAAO,OAAW,IAAa,OAAOL,EAC1C,GAAI,CAEF,OADoB,OAAO,WAAW,8BAA8B,EAAE,QACjDA,EAAgBC,CACvC,MAAQ,CACN,OAAOD,CACT,CACF,CAMO,SAASM,EAAaC,EAAoC,CAG/D,IAAMC,EAA0B,CAAE,GAFXH,EAAkB,CAEW,EAGpD,OAAW,CAACI,EAAKC,CAAM,IAAK,OAAO,QAAQR,CAAW,EAAsC,CAC1F,IAAMS,EAAWR,EAAWO,CAAM,EAC9BC,IACFH,EAASC,CAAG,EAAIE,EAEpB,CAGA,GAAIJ,EACF,OAAW,CAACE,EAAKG,CAAK,IAAK,OAAO,QAAQL,CAAK,EACzCK,IACFJ,EAASC,CAAG,EAAIG,GAKtB,OAAOJ,CACT,CC3EO,IAAMK,EAAN,KAA6C,CAWlD,YAAoBC,EAAuCC,EAA0B,CAAjE,aAAAD,EAAuC,iBAAAC,CAA2B,CAV7E,KAAO,gBACP,WAAa,IAEd,OACA,IACA,MACA,YAA6B,KAC7B,QAAU,GACV,MAAQ,EAIhB,KAAKC,EAA2BC,EAA0B,CACxD,KAAK,OAASD,EACd,KAAK,IAAMA,EAAO,WAAW,IAAI,EACjC,KAAK,MAAQE,EAAaD,CAAK,EAE/B,IAAME,EAAM,OAAO,kBAAoB,EACjCC,EAAOJ,EAAO,sBAAsB,EAC1CA,EAAO,MAAQI,EAAK,MAAQD,EAC5BH,EAAO,OAASI,EAAK,OAASD,EAC9B,KAAK,IAAI,MAAMA,EAAKA,CAAG,EACvBH,EAAO,MAAM,MAAQ,GAAGI,EAAK,KAAK,KAClCJ,EAAO,MAAM,OAAS,GAAGI,EAAK,MAAM,IACtC,CAEA,OAAc,CACZ,KAAK,QAAU,GACf,KAAK,OAAO,CACd,CAEA,OAAc,CACZ,KAAK,QAAU,GACX,KAAK,cAAgB,OACvB,qBAAqB,KAAK,WAAW,EACrC,KAAK,YAAc,KAEvB,CAEA,QAAe,CACR,KAAK,UACR,KAAK,QAAU,GACf,KAAK,OAAO,EAEhB,CAEA,SAAgB,CACd,KAAK,MAAM,CACb,CAEA,UAAmB,CAAE,OAAO,KAAK,KAAM,CAE/B,QAAe,CACrB,IAAMA,EAAO,KAAK,OAAO,sBAAsB,EACzCC,EAAID,EAAK,MACTE,EAAIF,EAAK,OACT,CAAE,IAAAG,EAAK,MAAAN,CAAM,EAAI,KAEvBM,EAAI,UAAYN,EAAM,WACtBM,EAAI,SAAS,EAAG,EAAGF,EAAGC,CAAC,EACvBC,EAAI,UAAYN,EAAM,KACtBM,EAAI,KAAO,sBACXA,EAAI,UAAY,SAChBA,EAAI,aAAe,SACnBA,EAAI,SAAS,gBAAiBF,EAAI,EAAGC,EAAI,EAAI,EAAE,EAC/CC,EAAI,KAAO,iBACXA,EAAI,YAAc,GAClBA,EAAI,SAAS,eAAgBF,EAAI,EAAGC,EAAI,EAAI,EAAE,EAC9CC,EAAI,YAAc,CACpB,CACF","names":["DARK_DEFAULTS","LIGHT_DEFAULTS","CSS_VAR_MAP","readCSSVar","varName","getSystemDefaults","resolveTheme","theme","resolved","key","cssVar","cssValue","value","BrickBreakerGame","onScore","_onGameOver","canvas","theme","resolveTheme","dpr","rect","W","H","ctx"]}
@@ -0,0 +1,2 @@
1
+ var d={primary:"#6366F1",background:"#0F0F0F",surface:"#1A1A2E",text:"#F8F8F8",accent:"#E94560"},f={primary:"#4F46E5",background:"#FFFFFF",surface:"#F3F4F6",text:"#111827",accent:"#E94560"},v={primary:"--lg-primary",background:"--lg-background",surface:"--lg-surface",text:"--lg-text",accent:"--lg-accent"};function b(a){return typeof window>"u"?null:getComputedStyle(document.documentElement).getPropertyValue(a).trim()||null}function g(){if(typeof window>"u")return d;try{return window.matchMedia("(prefers-color-scheme: dark)").matches?d:f}catch{return d}}function c(a){let t={...g()};for(let[i,s]of Object.entries(v)){let r=b(s);r&&(t[i]=r)}if(a)for(let[i,s]of Object.entries(a))s&&(t[i]=s);return t}var y=.4,m=-7,o=52,p=150,T=2.2,h=80,n=14,u=class{name="flappy";bundleSize=5e3;canvas;ctx;theme;W=0;H=0;birdY=0;birdVY=0;pipes=[];score=0;personalBest=0;animFrameId=null;running=!1;dead=!1;frameCount=0;lastTime=0;boundJump;boundKey;boundTouch;onScore;onGameOver;constructor(e,t){this.onScore=e,this.onGameOver=t,this.boundJump=this.jump.bind(this),this.boundKey=i=>{i.code==="Space"&&(i.preventDefault(),this.jump())},this.boundTouch=this.jump.bind(this)}init(e,t){this.canvas=e,this.ctx=e.getContext("2d"),this.theme=c(t);let i=window.devicePixelRatio||1,s=e.getBoundingClientRect();e.width=s.width*i,e.height=s.height*i,this.ctx.scale(i,i),e.style.width=`${s.width}px`,e.style.height=`${s.height}px`,this.W=s.width,this.H=s.height,e.setAttribute("aria-label","Flappy Bird game \u2014 loading in background"),e.setAttribute("role","img"),this.reset()}reset(){this.birdY=this.H/2,this.birdVY=0,this.pipes=[],this.score=0,this.dead=!1,this.frameCount=0,this.spawnPipe(this.W+100)}spawnPipe(e){let i=this.H-p-60,s=60+Math.random()*(i-60);this.pipes.push({x:e??this.W+50,topHeight:s,passed:!1})}start(){this.running=!0,this.lastTime=performance.now(),this.loop(performance.now()),this.canvas.addEventListener("click",this.boundJump),document.addEventListener("keydown",this.boundKey),this.canvas.addEventListener("touchstart",this.boundTouch,{passive:!0})}pause(){this.running=!1,this.animFrameId!==null&&(cancelAnimationFrame(this.animFrameId),this.animFrameId=null)}resume(){this.running||(this.running=!0,this.lastTime=performance.now(),this.loop(performance.now()))}destroy(){this.pause(),this.canvas.removeEventListener("click",this.boundJump),document.removeEventListener("keydown",this.boundKey),this.canvas.removeEventListener("touchstart",this.boundTouch)}getScore(){return this.score}jump(){if(this.dead){this.reset();return}this.birdVY=m}loop(e){if(!this.running)return;this.animFrameId=requestAnimationFrame(i=>this.loop(i));let t=Math.min((e-this.lastTime)/16.67,3);this.lastTime=e,this.dead||this.update(t),this.render()}update(e){if(this.frameCount++,this.birdVY+=y*e,this.birdY+=this.birdVY*e,this.birdY+n>=this.H||this.birdY-n<=0){this.die();return}this.frameCount%90===0&&this.spawnPipe();for(let t of this.pipes){t.x-=T*e,!t.passed&&t.x+o<h&&(t.passed=!0,this.score++,this.score>this.personalBest&&(this.personalBest=this.score),this.onScore?.(this.score));let i=h+n>t.x&&h-n<t.x+o,s=this.birdY-n<t.topHeight||this.birdY+n>t.topHeight+p;if(i&&s){this.die();return}}this.pipes=this.pipes.filter(t=>t.x+o>-10)}die(){this.dead=!0,this.birdVY=m*.5,this.onGameOver?.()}render(){let{ctx:e,W:t,H:i,theme:s}=this;e.fillStyle=s.background,e.fillRect(0,0,t,i);for(let r of this.pipes){e.fillStyle=s.primary,e.fillRect(r.x-4,r.topHeight-24,o+8,24),e.beginPath(),e.roundRect(r.x,0,o,r.topHeight-6,[0,0,6,6]),e.fill();let l=r.topHeight+p;e.fillRect(r.x-4,l,o+8,24),e.beginPath(),e.roundRect(r.x,l+6,o,i-l,[6,6,0,0]),e.fill()}e.save(),e.translate(h,this.birdY),e.rotate(Math.min(Math.max(this.birdVY*.05,-.5),1)),this.dead&&(e.globalAlpha=.6),e.fillStyle="#FFD93D",e.beginPath(),e.arc(0,0,n,0,Math.PI*2),e.fill(),e.fillStyle="#fff",e.beginPath(),e.arc(5,-4,5,0,Math.PI*2),e.fill(),e.fillStyle="#333",e.beginPath(),e.arc(6,-4,2.5,0,Math.PI*2),e.fill(),e.fillStyle="#FF6B35",e.beginPath(),e.moveTo(n-2,0),e.lineTo(n+8,-3),e.lineTo(n+8,3),e.closePath(),e.fill(),e.restore(),e.fillStyle=s.text,e.font="bold 28px monospace",e.textAlign="center",e.fillText(String(this.score),t/2,44),this.dead&&(e.fillStyle="rgba(0,0,0,0.45)",e.fillRect(0,0,t,i),e.fillStyle=s.text,e.font="bold 20px system-ui",e.textAlign="center",e.fillText("Tap to retry",t/2,i/2),e.font="14px system-ui",e.globalAlpha=.7,e.fillText(`Score: ${this.score} Best: ${this.personalBest}`,t/2,i/2+28),e.globalAlpha=1)}};export{u as FlappyGame};
2
+ //# sourceMappingURL=flappy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/theme.ts","../../src/games/flappy/index.ts"],"sourcesContent":["/**\r\n * Theme resolution — 3-level priority:\r\n * 1. Explicit theme prop (highest)\r\n * 2. CSS variables on :root (--lg-primary, etc.)\r\n * 3. prefers-color-scheme detection (lowest)\r\n */\r\n\r\nimport type { ThemeObject, ResolvedTheme } from './types.js'\r\n\r\nconst DARK_DEFAULTS: ResolvedTheme = {\r\n primary: '#6366F1',\r\n background: '#0F0F0F',\r\n surface: '#1A1A2E',\r\n text: '#F8F8F8',\r\n accent: '#E94560',\r\n}\r\n\r\nconst LIGHT_DEFAULTS: ResolvedTheme = {\r\n primary: '#4F46E5',\r\n background: '#FFFFFF',\r\n surface: '#F3F4F6',\r\n text: '#111827',\r\n accent: '#E94560',\r\n}\r\n\r\nconst CSS_VAR_MAP: Record<keyof ResolvedTheme, string> = {\r\n primary: '--lg-primary',\r\n background: '--lg-background',\r\n surface: '--lg-surface',\r\n text: '--lg-text',\r\n accent: '--lg-accent',\r\n}\r\n\r\n/**\r\n * Read a CSS variable from :root, returns null if not set.\r\n */\r\nfunction readCSSVar(varName: string): string | null {\r\n if (typeof window === 'undefined') return null\r\n const value = getComputedStyle(document.documentElement)\r\n .getPropertyValue(varName)\r\n .trim()\r\n return value || null\r\n}\r\n\r\n/**\r\n * Detect system color scheme preference.\r\n */\r\nfunction getSystemDefaults(): ResolvedTheme {\r\n if (typeof window === 'undefined') return DARK_DEFAULTS\r\n try {\r\n const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches\r\n return prefersDark ? DARK_DEFAULTS : LIGHT_DEFAULTS\r\n } catch {\r\n return DARK_DEFAULTS\r\n }\r\n}\r\n\r\n/**\r\n * Resolve the final theme from all sources.\r\n * Priority: explicit prop > CSS variables > system defaults.\r\n */\r\nexport function resolveTheme(theme?: ThemeObject): ResolvedTheme {\r\n const systemDefaults = getSystemDefaults()\r\n\r\n const resolved: ResolvedTheme = { ...systemDefaults }\r\n\r\n // Level 2: CSS variables\r\n for (const [key, cssVar] of Object.entries(CSS_VAR_MAP) as [keyof ResolvedTheme, string][]) {\r\n const cssValue = readCSSVar(cssVar)\r\n if (cssValue) {\r\n resolved[key] = cssValue\r\n }\r\n }\r\n\r\n // Level 1: Explicit theme prop\r\n if (theme) {\r\n for (const [key, value] of Object.entries(theme) as [keyof ThemeObject, string | undefined][]) {\r\n if (value) {\r\n resolved[key] = value\r\n }\r\n }\r\n }\r\n\r\n return resolved\r\n}\r\n\r\n/**\r\n * Apply resolved theme as CSS variables to a container element.\r\n * This allows games to reference CSS variables for dynamic theming.\r\n */\r\nexport function applyThemeToElement(element: HTMLElement, theme: ResolvedTheme): void {\r\n for (const [key, cssVar] of Object.entries(CSS_VAR_MAP) as [keyof ResolvedTheme, string][]) {\r\n element.style.setProperty(cssVar, theme[key])\r\n }\r\n}\r\n","/**\r\n * Flappy Bird — Infinite runner, physics-based.\r\n *\r\n * Mechanics:\r\n * - Gravity: 0.4 px/frame², jump impulse: -7 px/frame\r\n * - Gap: 150px, randomized vertical position\r\n * - Score: +1 per pipe cleared\r\n * - Pipe color: theme.primary\r\n *\r\n * Controls:\r\n * - Desktop: Spacebar or click\r\n * - Mobile: Tap\r\n *\r\n * Bundle target: ~5 kB gzipped\r\n */\r\n\r\nimport type { GamePlugin, ThemeObject, ResolvedTheme } from '../../types.js'\r\nimport { resolveTheme } from '../../theme.js'\r\n\r\nconst GRAVITY = 0.4\r\nconst JUMP_VELOCITY = -7\r\nconst PIPE_WIDTH = 52\r\nconst PIPE_GAP = 150\r\nconst PIPE_SPEED = 2.2\r\nconst BIRD_X = 80\r\nconst BIRD_RADIUS = 14\r\n\r\ninterface Pipe {\r\n x: number\r\n topHeight: number\r\n passed: boolean\r\n}\r\n\r\nexport class FlappyGame implements GamePlugin {\r\n readonly name = 'flappy'\r\n readonly bundleSize = 5_000\r\n\r\n private canvas!: HTMLCanvasElement\r\n private ctx!: CanvasRenderingContext2D\r\n private theme!: ResolvedTheme\r\n private W = 0\r\n private H = 0\r\n\r\n private birdY = 0\r\n private birdVY = 0\r\n private pipes: Pipe[] = []\r\n private score = 0\r\n private personalBest = 0\r\n private animFrameId: number | null = null\r\n private running = false\r\n private dead = false\r\n private frameCount = 0\r\n private lastTime = 0\r\n\r\n private boundJump: () => void\r\n private boundKey: (e: KeyboardEvent) => void\r\n private boundTouch: () => void\r\n\r\n private onScore?: (score: number) => void\r\n private onGameOver?: () => void\r\n\r\n constructor(onScore?: (score: number) => void, onGameOver?: () => void) {\r\n this.onScore = onScore\r\n this.onGameOver = onGameOver\r\n this.boundJump = this.jump.bind(this)\r\n this.boundKey = (e: KeyboardEvent) => { if (e.code === 'Space') { e.preventDefault(); this.jump() } }\r\n this.boundTouch = this.jump.bind(this)\r\n }\r\n\r\n init(canvas: HTMLCanvasElement, theme: ThemeObject): void {\r\n this.canvas = canvas\r\n this.ctx = canvas.getContext('2d')!\r\n this.theme = resolveTheme(theme)\r\n\r\n const dpr = window.devicePixelRatio || 1\r\n const rect = canvas.getBoundingClientRect()\r\n canvas.width = rect.width * dpr\r\n canvas.height = rect.height * dpr\r\n this.ctx.scale(dpr, dpr)\r\n canvas.style.width = `${rect.width}px`\r\n canvas.style.height = `${rect.height}px`\r\n this.W = rect.width\r\n this.H = rect.height\r\n\r\n canvas.setAttribute('aria-label', 'Flappy Bird game \\u2014 loading in background')\r\n canvas.setAttribute('role', 'img')\r\n\r\n this.reset()\r\n }\r\n\r\n private reset(): void {\r\n this.birdY = this.H / 2\r\n this.birdVY = 0\r\n this.pipes = []\r\n this.score = 0\r\n this.dead = false\r\n this.frameCount = 0\r\n this.spawnPipe(this.W + 100)\r\n }\r\n\r\n private spawnPipe(x?: number): void {\r\n const minTop = 60\r\n const maxTop = this.H - PIPE_GAP - 60\r\n const topHeight = minTop + Math.random() * (maxTop - minTop)\r\n this.pipes.push({ x: x ?? this.W + 50, topHeight, passed: false })\r\n }\r\n\r\n start(): void {\r\n this.running = true\r\n this.lastTime = performance.now()\r\n this.loop(performance.now())\r\n this.canvas.addEventListener('click', this.boundJump)\r\n document.addEventListener('keydown', this.boundKey)\r\n this.canvas.addEventListener('touchstart', this.boundTouch, { passive: true })\r\n }\r\n\r\n pause(): void {\r\n this.running = false\r\n if (this.animFrameId !== null) {\r\n cancelAnimationFrame(this.animFrameId)\r\n this.animFrameId = null\r\n }\r\n }\r\n\r\n resume(): void {\r\n if (!this.running) {\r\n this.running = true\r\n this.lastTime = performance.now()\r\n this.loop(performance.now())\r\n }\r\n }\r\n\r\n destroy(): void {\r\n this.pause()\r\n this.canvas.removeEventListener('click', this.boundJump)\r\n document.removeEventListener('keydown', this.boundKey)\r\n this.canvas.removeEventListener('touchstart', this.boundTouch)\r\n }\r\n\r\n getScore(): number { return this.score }\r\n\r\n private jump(): void {\r\n if (this.dead) { this.reset(); return }\r\n this.birdVY = JUMP_VELOCITY\r\n }\r\n\r\n private loop(now: number): void {\r\n if (!this.running) return\r\n this.animFrameId = requestAnimationFrame(t => this.loop(t))\r\n const dt = Math.min((now - this.lastTime) / 16.67, 3)\r\n this.lastTime = now\r\n if (!this.dead) this.update(dt)\r\n this.render()\r\n }\r\n\r\n private update(dt: number): void {\r\n this.frameCount++\r\n this.birdVY += GRAVITY * dt\r\n this.birdY += this.birdVY * dt\r\n\r\n if (this.birdY + BIRD_RADIUS >= this.H || this.birdY - BIRD_RADIUS <= 0) {\r\n this.die(); return\r\n }\r\n\r\n if (this.frameCount % 90 === 0) this.spawnPipe()\r\n\r\n for (const pipe of this.pipes) {\r\n pipe.x -= PIPE_SPEED * dt\r\n if (!pipe.passed && pipe.x + PIPE_WIDTH < BIRD_X) {\r\n pipe.passed = true\r\n this.score++\r\n if (this.score > this.personalBest) this.personalBest = this.score\r\n this.onScore?.(this.score)\r\n }\r\n\r\n const inX = BIRD_X + BIRD_RADIUS > pipe.x && BIRD_X - BIRD_RADIUS < pipe.x + PIPE_WIDTH\r\n const inY = this.birdY - BIRD_RADIUS < pipe.topHeight || this.birdY + BIRD_RADIUS > pipe.topHeight + PIPE_GAP\r\n if (inX && inY) { this.die(); return }\r\n }\r\n this.pipes = this.pipes.filter(p => p.x + PIPE_WIDTH > -10)\r\n }\r\n\r\n private die(): void {\r\n this.dead = true\r\n this.birdVY = JUMP_VELOCITY * 0.5\r\n this.onGameOver?.()\r\n }\r\n\r\n private render(): void {\r\n const { ctx, W, H, theme } = this\r\n ctx.fillStyle = theme.background\r\n ctx.fillRect(0, 0, W, H)\r\n\r\n for (const pipe of this.pipes) {\r\n ctx.fillStyle = theme.primary\r\n ctx.fillRect(pipe.x - 4, pipe.topHeight - 24, PIPE_WIDTH + 8, 24)\r\n ctx.beginPath(); ctx.roundRect(pipe.x, 0, PIPE_WIDTH, pipe.topHeight - 6, [0,0,6,6]); ctx.fill()\r\n const bY = pipe.topHeight + PIPE_GAP\r\n ctx.fillRect(pipe.x - 4, bY, PIPE_WIDTH + 8, 24)\r\n ctx.beginPath(); ctx.roundRect(pipe.x, bY + 6, PIPE_WIDTH, H - bY, [6,6,0,0]); ctx.fill()\r\n }\r\n\r\n ctx.save()\r\n ctx.translate(BIRD_X, this.birdY)\r\n ctx.rotate(Math.min(Math.max(this.birdVY * 0.05, -0.5), 1.0))\r\n if (this.dead) ctx.globalAlpha = 0.6\r\n ctx.fillStyle = '#FFD93D'\r\n ctx.beginPath(); ctx.arc(0, 0, BIRD_RADIUS, 0, Math.PI * 2); ctx.fill()\r\n ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(5, -4, 5, 0, Math.PI * 2); ctx.fill()\r\n ctx.fillStyle = '#333'; ctx.beginPath(); ctx.arc(6, -4, 2.5, 0, Math.PI * 2); ctx.fill()\r\n ctx.fillStyle = '#FF6B35'\r\n ctx.beginPath(); ctx.moveTo(BIRD_RADIUS - 2, 0); ctx.lineTo(BIRD_RADIUS + 8, -3); ctx.lineTo(BIRD_RADIUS + 8, 3); ctx.closePath(); ctx.fill()\r\n ctx.restore()\r\n\r\n ctx.fillStyle = theme.text\r\n ctx.font = 'bold 28px monospace'\r\n ctx.textAlign = 'center'\r\n ctx.fillText(String(this.score), W / 2, 44)\r\n\r\n if (this.dead) {\r\n ctx.fillStyle = 'rgba(0,0,0,0.45)'\r\n ctx.fillRect(0, 0, W, H)\r\n ctx.fillStyle = theme.text\r\n ctx.font = 'bold 20px system-ui'\r\n ctx.textAlign = 'center'\r\n ctx.fillText('Tap to retry', W / 2, H / 2)\r\n ctx.font = '14px system-ui'; ctx.globalAlpha = 0.7\r\n ctx.fillText(`Score: ${this.score} Best: ${this.personalBest}`, W / 2, H / 2 + 28)\r\n ctx.globalAlpha = 1\r\n }\r\n }\r\n}\r\n"],"mappings":"AASA,IAAMA,EAA+B,CACnC,QAAS,UACT,WAAY,UACZ,QAAS,UACT,KAAM,UACN,OAAQ,SACV,EAEMC,EAAgC,CACpC,QAAS,UACT,WAAY,UACZ,QAAS,UACT,KAAM,UACN,OAAQ,SACV,EAEMC,EAAmD,CACvD,QAAS,eACT,WAAY,kBACZ,QAAS,eACT,KAAM,YACN,OAAQ,aACV,EAKA,SAASC,EAAWC,EAAgC,CAClD,OAAI,OAAO,OAAW,IAAoB,KAC5B,iBAAiB,SAAS,eAAe,EACpD,iBAAiBA,CAAO,EACxB,KAAK,GACQ,IAClB,CAKA,SAASC,GAAmC,CAC1C,GAAI,OAAO,OAAW,IAAa,OAAOL,EAC1C,GAAI,CAEF,OADoB,OAAO,WAAW,8BAA8B,EAAE,QACjDA,EAAgBC,CACvC,MAAQ,CACN,OAAOD,CACT,CACF,CAMO,SAASM,EAAaC,EAAoC,CAG/D,IAAMC,EAA0B,CAAE,GAFXH,EAAkB,CAEW,EAGpD,OAAW,CAACI,EAAKC,CAAM,IAAK,OAAO,QAAQR,CAAW,EAAsC,CAC1F,IAAMS,EAAWR,EAAWO,CAAM,EAC9BC,IACFH,EAASC,CAAG,EAAIE,EAEpB,CAGA,GAAIJ,EACF,OAAW,CAACE,EAAKG,CAAK,IAAK,OAAO,QAAQL,CAAK,EACzCK,IACFJ,EAASC,CAAG,EAAIG,GAKtB,OAAOJ,CACT,CCjEA,IAAMK,EAAU,GACVC,EAAgB,GAChBC,EAAa,GACbC,EAAW,IACXC,EAAa,IACbC,EAAS,GACTC,EAAc,GAQPC,EAAN,KAAuC,CACnC,KAAO,SACP,WAAa,IAEd,OACA,IACA,MACA,EAAI,EACJ,EAAI,EAEJ,MAAQ,EACR,OAAS,EACT,MAAgB,CAAC,EACjB,MAAQ,EACR,aAAe,EACf,YAA6B,KAC7B,QAAU,GACV,KAAO,GACP,WAAa,EACb,SAAW,EAEX,UACA,SACA,WAEA,QACA,WAER,YAAYC,EAAmCC,EAAyB,CACtE,KAAK,QAAUD,EACf,KAAK,WAAaC,EAClB,KAAK,UAAY,KAAK,KAAK,KAAK,IAAI,EACpC,KAAK,SAAYC,GAAqB,CAAMA,EAAE,OAAS,UAAWA,EAAE,eAAe,EAAG,KAAK,KAAK,EAAI,EACpG,KAAK,WAAa,KAAK,KAAK,KAAK,IAAI,CACvC,CAEA,KAAKC,EAA2BC,EAA0B,CACxD,KAAK,OAASD,EACd,KAAK,IAAMA,EAAO,WAAW,IAAI,EACjC,KAAK,MAAQE,EAAaD,CAAK,EAE/B,IAAME,EAAM,OAAO,kBAAoB,EACjCC,EAAOJ,EAAO,sBAAsB,EAC1CA,EAAO,MAAQI,EAAK,MAAQD,EAC5BH,EAAO,OAASI,EAAK,OAASD,EAC9B,KAAK,IAAI,MAAMA,EAAKA,CAAG,EACvBH,EAAO,MAAM,MAAQ,GAAGI,EAAK,KAAK,KAClCJ,EAAO,MAAM,OAAS,GAAGI,EAAK,MAAM,KACpC,KAAK,EAAIA,EAAK,MACd,KAAK,EAAIA,EAAK,OAEdJ,EAAO,aAAa,aAAc,+CAA+C,EACjFA,EAAO,aAAa,OAAQ,KAAK,EAEjC,KAAK,MAAM,CACb,CAEQ,OAAc,CACpB,KAAK,MAAQ,KAAK,EAAI,EACtB,KAAK,OAAS,EACd,KAAK,MAAQ,CAAC,EACd,KAAK,MAAQ,EACb,KAAK,KAAO,GACZ,KAAK,WAAa,EAClB,KAAK,UAAU,KAAK,EAAI,GAAG,CAC7B,CAEQ,UAAUK,EAAkB,CAElC,IAAMC,EAAS,KAAK,EAAId,EAAW,GAC7Be,EAAY,GAAS,KAAK,OAAO,GAAKD,EAAS,IACrD,KAAK,MAAM,KAAK,CAAE,EAAGD,GAAK,KAAK,EAAI,GAAI,UAAAE,EAAW,OAAQ,EAAM,CAAC,CACnE,CAEA,OAAc,CACZ,KAAK,QAAU,GACf,KAAK,SAAW,YAAY,IAAI,EAChC,KAAK,KAAK,YAAY,IAAI,CAAC,EAC3B,KAAK,OAAO,iBAAiB,QAAS,KAAK,SAAS,EACpD,SAAS,iBAAiB,UAAW,KAAK,QAAQ,EAClD,KAAK,OAAO,iBAAiB,aAAc,KAAK,WAAY,CAAE,QAAS,EAAK,CAAC,CAC/E,CAEA,OAAc,CACZ,KAAK,QAAU,GACX,KAAK,cAAgB,OACvB,qBAAqB,KAAK,WAAW,EACrC,KAAK,YAAc,KAEvB,CAEA,QAAe,CACR,KAAK,UACR,KAAK,QAAU,GACf,KAAK,SAAW,YAAY,IAAI,EAChC,KAAK,KAAK,YAAY,IAAI,CAAC,EAE/B,CAEA,SAAgB,CACd,KAAK,MAAM,EACX,KAAK,OAAO,oBAAoB,QAAS,KAAK,SAAS,EACvD,SAAS,oBAAoB,UAAW,KAAK,QAAQ,EACrD,KAAK,OAAO,oBAAoB,aAAc,KAAK,UAAU,CAC/D,CAEA,UAAmB,CAAE,OAAO,KAAK,KAAM,CAE/B,MAAa,CACnB,GAAI,KAAK,KAAM,CAAE,KAAK,MAAM,EAAG,MAAO,CACtC,KAAK,OAASjB,CAChB,CAEQ,KAAKkB,EAAmB,CAC9B,GAAI,CAAC,KAAK,QAAS,OACnB,KAAK,YAAc,sBAAsBC,GAAK,KAAK,KAAKA,CAAC,CAAC,EAC1D,IAAMC,EAAK,KAAK,KAAKF,EAAM,KAAK,UAAY,MAAO,CAAC,EACpD,KAAK,SAAWA,EACX,KAAK,MAAM,KAAK,OAAOE,CAAE,EAC9B,KAAK,OAAO,CACd,CAEQ,OAAOA,EAAkB,CAK/B,GAJA,KAAK,aACL,KAAK,QAAUrB,EAAUqB,EACzB,KAAK,OAAS,KAAK,OAASA,EAExB,KAAK,MAAQf,GAAe,KAAK,GAAK,KAAK,MAAQA,GAAe,EAAG,CACvE,KAAK,IAAI,EAAG,MACd,CAEI,KAAK,WAAa,KAAO,GAAG,KAAK,UAAU,EAE/C,QAAWgB,KAAQ,KAAK,MAAO,CAC7BA,EAAK,GAAKlB,EAAaiB,EACnB,CAACC,EAAK,QAAUA,EAAK,EAAIpB,EAAaG,IACxCiB,EAAK,OAAS,GACd,KAAK,QACD,KAAK,MAAQ,KAAK,eAAc,KAAK,aAAe,KAAK,OAC7D,KAAK,UAAU,KAAK,KAAK,GAG3B,IAAMC,EAAMlB,EAASC,EAAcgB,EAAK,GAAKjB,EAASC,EAAcgB,EAAK,EAAIpB,EACvEsB,EAAM,KAAK,MAAQlB,EAAcgB,EAAK,WAAa,KAAK,MAAQhB,EAAcgB,EAAK,UAAYnB,EACrG,GAAIoB,GAAOC,EAAK,CAAE,KAAK,IAAI,EAAG,MAAO,CACvC,CACA,KAAK,MAAQ,KAAK,MAAM,OAAOC,GAAKA,EAAE,EAAIvB,EAAa,GAAG,CAC5D,CAEQ,KAAY,CAClB,KAAK,KAAO,GACZ,KAAK,OAASD,EAAgB,GAC9B,KAAK,aAAa,CACpB,CAEQ,QAAe,CACrB,GAAM,CAAE,IAAAyB,EAAK,EAAAC,EAAG,EAAAC,EAAG,MAAAhB,CAAM,EAAI,KAC7Bc,EAAI,UAAYd,EAAM,WACtBc,EAAI,SAAS,EAAG,EAAGC,EAAGC,CAAC,EAEvB,QAAWN,KAAQ,KAAK,MAAO,CAC7BI,EAAI,UAAYd,EAAM,QACtBc,EAAI,SAASJ,EAAK,EAAI,EAAGA,EAAK,UAAY,GAAIpB,EAAa,EAAG,EAAE,EAChEwB,EAAI,UAAU,EAAGA,EAAI,UAAUJ,EAAK,EAAG,EAAGpB,EAAYoB,EAAK,UAAY,EAAG,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAGI,EAAI,KAAK,EAC/F,IAAMG,EAAKP,EAAK,UAAYnB,EAC5BuB,EAAI,SAASJ,EAAK,EAAI,EAAGO,EAAI3B,EAAa,EAAG,EAAE,EAC/CwB,EAAI,UAAU,EAAGA,EAAI,UAAUJ,EAAK,EAAGO,EAAK,EAAG3B,EAAY0B,EAAIC,EAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAGH,EAAI,KAAK,CAC1F,CAEAA,EAAI,KAAK,EACTA,EAAI,UAAUrB,EAAQ,KAAK,KAAK,EAChCqB,EAAI,OAAO,KAAK,IAAI,KAAK,IAAI,KAAK,OAAS,IAAM,GAAI,EAAG,CAAG,CAAC,EACxD,KAAK,OAAMA,EAAI,YAAc,IACjCA,EAAI,UAAY,UAChBA,EAAI,UAAU,EAAGA,EAAI,IAAI,EAAG,EAAGpB,EAAa,EAAG,KAAK,GAAK,CAAC,EAAGoB,EAAI,KAAK,EACtEA,EAAI,UAAY,OAAQA,EAAI,UAAU,EAAGA,EAAI,IAAI,EAAG,GAAI,EAAG,EAAG,KAAK,GAAK,CAAC,EAAGA,EAAI,KAAK,EACrFA,EAAI,UAAY,OAAQA,EAAI,UAAU,EAAGA,EAAI,IAAI,EAAG,GAAI,IAAK,EAAG,KAAK,GAAK,CAAC,EAAGA,EAAI,KAAK,EACvFA,EAAI,UAAY,UAChBA,EAAI,UAAU,EAAGA,EAAI,OAAOpB,EAAc,EAAG,CAAC,EAAGoB,EAAI,OAAOpB,EAAc,EAAG,EAAE,EAAGoB,EAAI,OAAOpB,EAAc,EAAG,CAAC,EAAGoB,EAAI,UAAU,EAAGA,EAAI,KAAK,EAC5IA,EAAI,QAAQ,EAEZA,EAAI,UAAYd,EAAM,KACtBc,EAAI,KAAO,sBACXA,EAAI,UAAY,SAChBA,EAAI,SAAS,OAAO,KAAK,KAAK,EAAGC,EAAI,EAAG,EAAE,EAEtC,KAAK,OACPD,EAAI,UAAY,mBAChBA,EAAI,SAAS,EAAG,EAAGC,EAAGC,CAAC,EACvBF,EAAI,UAAYd,EAAM,KACtBc,EAAI,KAAO,sBACXA,EAAI,UAAY,SAChBA,EAAI,SAAS,eAAgBC,EAAI,EAAGC,EAAI,CAAC,EACzCF,EAAI,KAAO,iBAAkBA,EAAI,YAAc,GAC/CA,EAAI,SAAS,UAAU,KAAK,KAAK,WAAW,KAAK,YAAY,GAAIC,EAAI,EAAGC,EAAI,EAAI,EAAE,EAClFF,EAAI,YAAc,EAEtB,CACF","names":["DARK_DEFAULTS","LIGHT_DEFAULTS","CSS_VAR_MAP","readCSSVar","varName","getSystemDefaults","resolveTheme","theme","resolved","key","cssVar","cssValue","value","GRAVITY","JUMP_VELOCITY","PIPE_WIDTH","PIPE_GAP","PIPE_SPEED","BIRD_X","BIRD_RADIUS","FlappyGame","onScore","onGameOver","e","canvas","theme","resolveTheme","dpr","rect","x","maxTop","topHeight","now","t","dt","pipe","inX","inY","p","ctx","W","H","bY"]}
@@ -0,0 +1,2 @@
1
+ var p={primary:"#6366F1",background:"#0F0F0F",surface:"#1A1A2E",text:"#F8F8F8",accent:"#E94560"},y={primary:"#4F46E5",background:"#FFFFFF",surface:"#F3F4F6",text:"#111827",accent:"#E94560"},x={primary:"--lg-primary",background:"--lg-background",surface:"--lg-surface",text:"--lg-text",accent:"--lg-accent"};function k(a){return typeof window>"u"?null:getComputedStyle(document.documentElement).getPropertyValue(a).trim()||null}function T(){if(typeof window>"u")return p;try{return window.matchMedia("(prefers-color-scheme: dark)").matches?p:y}catch{return p}}function m(a){let t={...T()};for(let[s,i]of Object.entries(x)){let n=k(i);n&&(t[s]=n)}if(a)for(let[s,i]of Object.entries(a))i&&(t[s]=i);return t}var c=["\u{1F3AE}","\u{1F680}","\u{1F3AF}","\u{1F3B2}","\u{1F31F}","\u{1F3B8}","\u{1F984}","\u{1F355}"],d=4,u=300,f=class{name="memory-cards";canvas;ctx;theme;W=0;H=0;cards=[];flippedIdxs=[];locked=!1;score=0;matchCount=0;startTime=0;selectedIdx=0;animFrameId=null;running=!1;lastTime=0;boundClick;boundTouch;boundKeyDown;onScore;onGameOver;constructor(e,t){this.onScore=e,this.onGameOver=t,this.boundClick=this.onClick.bind(this),this.boundTouch=this.onTouch.bind(this),this.boundKeyDown=this.onKeyDown.bind(this)}init(e,t){this.canvas=e,this.ctx=e.getContext("2d"),this.theme=m(t),e.setAttribute("aria-label","Memory Cards game \u2014 loading in background"),e.setAttribute("role","img"),e.setAttribute("tabindex","0");let s=window.devicePixelRatio||1,i=e.getBoundingClientRect();e.width=i.width*s,e.height=i.height*s,this.ctx.scale(s,s),e.style.width=`${i.width}px`,e.style.height=`${i.height}px`,this.W=i.width,this.H=i.height,this.reset()}reset(){let e=[...c,...c];for(let t=e.length-1;t>0;t--){let s=Math.floor(Math.random()*(t+1));[e[t],e[s]]=[e[s],e[t]]}this.cards=e.map((t,s)=>({id:s,emoji:t,flipped:!1,matched:!1,flipProgress:0,pulsePhase:0})),this.flippedIdxs=[],this.locked=!1,this.score=0,this.matchCount=0,this.startTime=Date.now(),this.selectedIdx=0}start(){this.running=!0,this.lastTime=performance.now(),this.loop(performance.now()),this.canvas.addEventListener("click",this.boundClick),this.canvas.addEventListener("touchend",this.boundTouch,{passive:!0}),document.addEventListener("keydown",this.boundKeyDown)}pause(){this.running=!1,this.animFrameId!==null&&(cancelAnimationFrame(this.animFrameId),this.animFrameId=null)}resume(){this.running||(this.running=!0,this.lastTime=performance.now(),this.loop(performance.now()))}destroy(){this.pause(),this.canvas.removeEventListener("click",this.boundClick),this.canvas.removeEventListener("touchend",this.boundTouch),document.removeEventListener("keydown",this.boundKeyDown)}getScore(){return this.score}bounds(e){let s=d,i=d,n=(this.W-8*(s+1))/s,o=(this.H-8*(i+1))/i;return{x:8+e%s*(n+8),y:8+Math.floor(e/s)*(o+8),w:n,h:o}}cardAt(e,t){for(let s=0;s<this.cards.length;s++){let i=this.bounds(s);if(e>=i.x&&e<=i.x+i.w&&t>=i.y&&t<=i.y+i.h)return s}return-1}onClick(e){let t=this.canvas.getBoundingClientRect();this.tryFlip(e.clientX-t.left,e.clientY-t.top)}onTouch(e){let t=e.changedTouches[0];if(!t)return;let s=this.canvas.getBoundingClientRect();this.tryFlip(t.clientX-s.left,t.clientY-s.top)}onKeyDown(e){let t=this.cards.length;switch(e.key){case"Tab":e.preventDefault(),this.selectedIdx=(this.selectedIdx+(e.shiftKey?t-1:1))%t;break;case"ArrowRight":e.preventDefault(),this.selectedIdx=(this.selectedIdx+1)%t;break;case"ArrowLeft":e.preventDefault(),this.selectedIdx=(this.selectedIdx+t-1)%t;break;case"ArrowDown":e.preventDefault(),this.selectedIdx=(this.selectedIdx+d)%t;break;case"ArrowUp":e.preventDefault(),this.selectedIdx=(this.selectedIdx+t-d)%t;break;case"Enter":case" ":e.preventDefault(),this.tryFlipByIdx(this.selectedIdx);break}}tryFlipByIdx(e){if(this.locked)return;let t=this.cards[e];t.flipped||t.matched||(t.flipped=!0,this.flippedIdxs.push(e),this.checkMatch())}tryFlip(e,t){if(this.locked)return;let s=this.cardAt(e,t);if(s===-1)return;let i=this.cards[s];i.flipped||i.matched||(i.flipped=!0,this.flippedIdxs.push(s),this.selectedIdx=s,this.checkMatch())}checkMatch(){if(this.flippedIdxs.length!==2)return;this.locked=!0;let[e,t]=this.flippedIdxs;this.cards[e].emoji===this.cards[t].emoji?setTimeout(()=>{if(this.cards[e].matched=!0,this.cards[t].matched=!0,this.matchCount++,this.score+=10,this.onScore?.(this.score),this.flippedIdxs=[],this.locked=!1,this.matchCount===c.length){let s=(Date.now()-this.startTime)/1e3,i=Math.max(0,Math.round((60-s)*5));this.score+=i,this.onScore?.(this.score),this.onGameOver?.(),setTimeout(()=>this.reset(),2e3)}},u+100):setTimeout(()=>{this.cards[e].flipped=!1,this.cards[t].flipped=!1,this.flippedIdxs=[],this.locked=!1},u+600)}loop(e){if(!this.running)return;this.animFrameId=requestAnimationFrame(s=>this.loop(s));let t=(e-this.lastTime)/u;this.lastTime=e;for(let s of this.cards)s.flipped&&s.flipProgress<1?s.flipProgress=Math.min(1,s.flipProgress+t):!s.flipped&&s.flipProgress>0&&(s.flipProgress=Math.max(0,s.flipProgress-t)),s.matched&&(s.pulsePhase=(s.pulsePhase+t*.15)%(Math.PI*2));this.render()}render(){let{ctx:e,W:t,H:s,theme:i}=this;e.fillStyle=i.background,e.fillRect(0,0,t,s);for(let n=0;n<this.cards.length;n++){let o=this.cards[n],r=this.bounds(n),v=Math.abs(Math.cos(o.flipProgress*Math.PI)),b=r.x+r.w/2,g=r.y+r.h/2;if(e.save(),e.translate(b,g),e.scale(v,1),e.translate(-r.w/2,-r.h/2),e.beginPath(),e.roundRect(0,0,r.w,r.h,6),o.flipProgress>.5){if(o.matched){let l=.2+Math.sin(o.pulsePhase)*.1;e.fillStyle=i.primary+"33",e.fill(),e.strokeStyle=i.primary,e.lineWidth=3,e.globalAlpha=.6+l,e.stroke(),e.globalAlpha=1,e.shadowColor=i.primary,e.shadowBlur=8,e.stroke(),e.shadowBlur=0}else e.fillStyle=i.surface,e.fill();e.font=`${Math.min(r.w,r.h)*.45}px serif`,e.textAlign="center",e.textBaseline="middle",e.fillText(o.emoji,r.w/2,r.h/2)}else{e.fillStyle=i.primary+"99",e.fill(),e.strokeStyle=i.primary,e.lineWidth=1,e.globalAlpha=.25;for(let l=0;l<3;l++){let h=4+l*5;e.strokeRect(h,h,r.w-h*2,r.h-h*2)}e.globalAlpha=1}n===this.selectedIdx&&(e.strokeStyle=i.text,e.lineWidth=2,e.setLineDash([4,3]),e.strokeRect(0,0,r.w,r.h),e.setLineDash([])),e.restore()}e.fillStyle=i.text,e.globalAlpha=.5,e.font="11px system-ui",e.textAlign="right",e.fillText(`${this.matchCount}/${c.length} \xB7 ${this.score}pts`,t-6,s-4),e.globalAlpha=1}};export{f as MemoryCardsGame};
2
+ //# sourceMappingURL=memory-cards.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/theme.ts","../../src/games/memory-cards/index.ts"],"sourcesContent":["/**\r\n * Theme resolution — 3-level priority:\r\n * 1. Explicit theme prop (highest)\r\n * 2. CSS variables on :root (--lg-primary, etc.)\r\n * 3. prefers-color-scheme detection (lowest)\r\n */\r\n\r\nimport type { ThemeObject, ResolvedTheme } from './types.js'\r\n\r\nconst DARK_DEFAULTS: ResolvedTheme = {\r\n primary: '#6366F1',\r\n background: '#0F0F0F',\r\n surface: '#1A1A2E',\r\n text: '#F8F8F8',\r\n accent: '#E94560',\r\n}\r\n\r\nconst LIGHT_DEFAULTS: ResolvedTheme = {\r\n primary: '#4F46E5',\r\n background: '#FFFFFF',\r\n surface: '#F3F4F6',\r\n text: '#111827',\r\n accent: '#E94560',\r\n}\r\n\r\nconst CSS_VAR_MAP: Record<keyof ResolvedTheme, string> = {\r\n primary: '--lg-primary',\r\n background: '--lg-background',\r\n surface: '--lg-surface',\r\n text: '--lg-text',\r\n accent: '--lg-accent',\r\n}\r\n\r\n/**\r\n * Read a CSS variable from :root, returns null if not set.\r\n */\r\nfunction readCSSVar(varName: string): string | null {\r\n if (typeof window === 'undefined') return null\r\n const value = getComputedStyle(document.documentElement)\r\n .getPropertyValue(varName)\r\n .trim()\r\n return value || null\r\n}\r\n\r\n/**\r\n * Detect system color scheme preference.\r\n */\r\nfunction getSystemDefaults(): ResolvedTheme {\r\n if (typeof window === 'undefined') return DARK_DEFAULTS\r\n try {\r\n const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches\r\n return prefersDark ? DARK_DEFAULTS : LIGHT_DEFAULTS\r\n } catch {\r\n return DARK_DEFAULTS\r\n }\r\n}\r\n\r\n/**\r\n * Resolve the final theme from all sources.\r\n * Priority: explicit prop > CSS variables > system defaults.\r\n */\r\nexport function resolveTheme(theme?: ThemeObject): ResolvedTheme {\r\n const systemDefaults = getSystemDefaults()\r\n\r\n const resolved: ResolvedTheme = { ...systemDefaults }\r\n\r\n // Level 2: CSS variables\r\n for (const [key, cssVar] of Object.entries(CSS_VAR_MAP) as [keyof ResolvedTheme, string][]) {\r\n const cssValue = readCSSVar(cssVar)\r\n if (cssValue) {\r\n resolved[key] = cssValue\r\n }\r\n }\r\n\r\n // Level 1: Explicit theme prop\r\n if (theme) {\r\n for (const [key, value] of Object.entries(theme) as [keyof ThemeObject, string | undefined][]) {\r\n if (value) {\r\n resolved[key] = value\r\n }\r\n }\r\n }\r\n\r\n return resolved\r\n}\r\n\r\n/**\r\n * Apply resolved theme as CSS variables to a container element.\r\n * This allows games to reference CSS variables for dynamic theming.\r\n */\r\nexport function applyThemeToElement(element: HTMLElement, theme: ResolvedTheme): void {\r\n for (const [key, cssVar] of Object.entries(CSS_VAR_MAP) as [keyof ResolvedTheme, string][]) {\r\n element.style.setProperty(cssVar, theme[key])\r\n }\r\n}\r\n","/**\r\n * Memory Cards — Emoji pair matching game.\r\n * Bundle target: ~4 kB gzipped\r\n * See SPEC.md §7.7 for full spec.\r\n */\r\n\r\nimport type { GamePlugin, ThemeObject, ResolvedTheme } from '../../types.js'\r\nimport { resolveTheme } from '../../theme.js'\r\n\r\nconst EMOJIS = ['🎮','🚀','🎯','🎲','🌟','🎸','🦄','🍕']\r\nconst GRID = 4\r\nconst FLIP_MS = 300\r\n\r\ninterface Card {\r\n id: number; emoji: string; flipped: boolean; matched: boolean; flipProgress: number; pulsePhase: number\r\n}\r\n\r\nexport class MemoryCardsGame implements GamePlugin {\r\n readonly name = 'memory-cards'\r\n private canvas!: HTMLCanvasElement\r\n private ctx!: CanvasRenderingContext2D\r\n private theme!: ResolvedTheme\r\n private W = 0; private H = 0\r\n private cards: Card[] = []\r\n private flippedIdxs: number[] = []\r\n private locked = false\r\n private score = 0; private matchCount = 0; private startTime = 0\r\n private selectedIdx = 0 // keyboard navigation index\r\n private animFrameId: number | null = null\r\n private running = false; private lastTime = 0\r\n private boundClick: (e: MouseEvent) => void\r\n private boundTouch: (e: TouchEvent) => void\r\n private boundKeyDown: (e: KeyboardEvent) => void\r\n\r\n private onScore?: (s: number) => void\r\n private onGameOver?: () => void\r\n\r\n constructor(onScore?: (s: number) => void, onGameOver?: () => void) {\r\n this.onScore = onScore\r\n this.onGameOver = onGameOver\r\n this.boundClick = this.onClick.bind(this)\r\n this.boundTouch = this.onTouch.bind(this)\r\n this.boundKeyDown = this.onKeyDown.bind(this)\r\n }\r\n\r\n init(canvas: HTMLCanvasElement, theme: ThemeObject): void {\r\n this.canvas = canvas; this.ctx = canvas.getContext('2d')!; this.theme = resolveTheme(theme)\r\n\r\n canvas.setAttribute('aria-label', 'Memory Cards game \\u2014 loading in background')\r\n canvas.setAttribute('role', 'img')\r\n canvas.setAttribute('tabindex', '0')\r\n\r\n const dpr = window.devicePixelRatio || 1; const rect = canvas.getBoundingClientRect()\r\n canvas.width = rect.width * dpr; canvas.height = rect.height * dpr\r\n this.ctx.scale(dpr, dpr); canvas.style.width = `${rect.width}px`; canvas.style.height = `${rect.height}px`\r\n this.W = rect.width; this.H = rect.height; this.reset()\r\n }\r\n\r\n private reset(): void {\r\n const pairs = [...EMOJIS, ...EMOJIS]\r\n for (let i = pairs.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [pairs[i], pairs[j]] = [pairs[j]!, pairs[i]!] }\r\n this.cards = pairs.map((emoji, id) => ({ id, emoji, flipped: false, matched: false, flipProgress: 0, pulsePhase: 0 }))\r\n this.flippedIdxs = []; this.locked = false; this.score = 0; this.matchCount = 0\r\n this.startTime = Date.now(); this.selectedIdx = 0\r\n }\r\n\r\n start(): void {\r\n this.running = true; this.lastTime = performance.now(); this.loop(performance.now())\r\n this.canvas.addEventListener('click', this.boundClick)\r\n this.canvas.addEventListener('touchend', this.boundTouch, { passive: true })\r\n document.addEventListener('keydown', this.boundKeyDown)\r\n }\r\n\r\n pause(): void { this.running = false; if (this.animFrameId !== null) { cancelAnimationFrame(this.animFrameId); this.animFrameId = null } }\r\n resume(): void { if (!this.running) { this.running = true; this.lastTime = performance.now(); this.loop(performance.now()) } }\r\n\r\n destroy(): void {\r\n this.pause()\r\n this.canvas.removeEventListener('click', this.boundClick)\r\n this.canvas.removeEventListener('touchend', this.boundTouch)\r\n document.removeEventListener('keydown', this.boundKeyDown)\r\n }\r\n\r\n getScore(): number { return this.score }\r\n\r\n private bounds(i: number) {\r\n const pad = 8; const cols = GRID; const rows = GRID\r\n const w = (this.W - pad * (cols + 1)) / cols; const h = (this.H - pad * (rows + 1)) / rows\r\n return { x: pad + (i % cols) * (w + pad), y: pad + Math.floor(i / cols) * (h + pad), w, h }\r\n }\r\n\r\n private cardAt(px: number, py: number): number {\r\n for (let i = 0; i < this.cards.length; i++) { const b = this.bounds(i); if (px >= b.x && px <= b.x + b.w && py >= b.y && py <= b.y + b.h) return i }\r\n return -1\r\n }\r\n\r\n private onClick(e: MouseEvent): void { const r = this.canvas.getBoundingClientRect(); this.tryFlip(e.clientX - r.left, e.clientY - r.top) }\r\n private onTouch(e: TouchEvent): void { const t = e.changedTouches[0]; if (!t) return; const r = this.canvas.getBoundingClientRect(); this.tryFlip(t.clientX - r.left, t.clientY - r.top) }\r\n\r\n private onKeyDown(e: KeyboardEvent): void {\r\n const total = this.cards.length\r\n switch (e.key) {\r\n case 'Tab':\r\n e.preventDefault()\r\n this.selectedIdx = (this.selectedIdx + (e.shiftKey ? total - 1 : 1)) % total\r\n break\r\n case 'ArrowRight': e.preventDefault(); this.selectedIdx = (this.selectedIdx + 1) % total; break\r\n case 'ArrowLeft': e.preventDefault(); this.selectedIdx = (this.selectedIdx + total - 1) % total; break\r\n case 'ArrowDown': e.preventDefault(); this.selectedIdx = (this.selectedIdx + GRID) % total; break\r\n case 'ArrowUp': e.preventDefault(); this.selectedIdx = (this.selectedIdx + total - GRID) % total; break\r\n case 'Enter':\r\n case ' ':\r\n e.preventDefault()\r\n this.tryFlipByIdx(this.selectedIdx)\r\n break\r\n }\r\n }\r\n\r\n private tryFlipByIdx(idx: number): void {\r\n if (this.locked) return\r\n const card = this.cards[idx]!; if (card.flipped || card.matched) return\r\n card.flipped = true; this.flippedIdxs.push(idx)\r\n this.checkMatch()\r\n }\r\n\r\n private tryFlip(x: number, y: number): void {\r\n if (this.locked) return\r\n const idx = this.cardAt(x, y); if (idx === -1) return\r\n const card = this.cards[idx]!; if (card.flipped || card.matched) return\r\n card.flipped = true; this.flippedIdxs.push(idx); this.selectedIdx = idx\r\n this.checkMatch()\r\n }\r\n\r\n private checkMatch(): void {\r\n if (this.flippedIdxs.length !== 2) return\r\n this.locked = true\r\n const [a, b] = this.flippedIdxs as [number, number]\r\n if (this.cards[a]!.emoji === this.cards[b]!.emoji) {\r\n setTimeout(() => {\r\n this.cards[a]!.matched = true; this.cards[b]!.matched = true\r\n this.matchCount++; this.score += 10; this.onScore?.(this.score)\r\n this.flippedIdxs = []; this.locked = false\r\n if (this.matchCount === EMOJIS.length) {\r\n // Speed bonus: reward fast completion\r\n const elapsed = (Date.now() - this.startTime) / 1000\r\n const timeBonus = Math.max(0, Math.round((60 - elapsed) * 5))\r\n this.score += timeBonus; this.onScore?.(this.score)\r\n this.onGameOver?.()\r\n setTimeout(() => this.reset(), 2000)\r\n }\r\n }, FLIP_MS + 100)\r\n } else {\r\n setTimeout(() => {\r\n this.cards[a]!.flipped = false; this.cards[b]!.flipped = false\r\n this.flippedIdxs = []; this.locked = false\r\n }, FLIP_MS + 600)\r\n }\r\n }\r\n\r\n private loop(now: number): void {\r\n if (!this.running) return\r\n this.animFrameId = requestAnimationFrame(t => this.loop(t))\r\n const dt = (now - this.lastTime) / FLIP_MS; this.lastTime = now\r\n for (const c of this.cards) {\r\n if (c.flipped && c.flipProgress < 1) c.flipProgress = Math.min(1, c.flipProgress + dt)\r\n else if (!c.flipped && c.flipProgress > 0) c.flipProgress = Math.max(0, c.flipProgress - dt)\r\n if (c.matched) c.pulsePhase = (c.pulsePhase + dt * 0.15) % (Math.PI * 2)\r\n }\r\n this.render()\r\n }\r\n\r\n private render(): void {\r\n const { ctx, W, H, theme } = this\r\n ctx.fillStyle = theme.background; ctx.fillRect(0, 0, W, H)\r\n for (let i = 0; i < this.cards.length; i++) {\r\n const card = this.cards[i]!; const b = this.bounds(i)\r\n const scaleX = Math.abs(Math.cos(card.flipProgress * Math.PI))\r\n const cx = b.x + b.w / 2; const cy = b.y + b.h / 2\r\n ctx.save(); ctx.translate(cx, cy); ctx.scale(scaleX, 1); ctx.translate(-b.w / 2, -b.h / 2)\r\n ctx.beginPath(); ctx.roundRect(0, 0, b.w, b.h, 6)\r\n if (card.flipProgress > 0.5) {\r\n if (card.matched) {\r\n // Pulsing glow for matched cards\r\n const pulse = 0.2 + Math.sin(card.pulsePhase) * 0.1\r\n ctx.fillStyle = theme.primary + '33'; ctx.fill()\r\n ctx.strokeStyle = theme.primary; ctx.lineWidth = 3\r\n ctx.globalAlpha = 0.6 + pulse; ctx.stroke(); ctx.globalAlpha = 1\r\n // Outer glow\r\n ctx.shadowColor = theme.primary; ctx.shadowBlur = 8\r\n ctx.stroke(); ctx.shadowBlur = 0\r\n } else {\r\n ctx.fillStyle = theme.surface; ctx.fill()\r\n }\r\n ctx.font = `${Math.min(b.w, b.h) * 0.45}px serif`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'\r\n ctx.fillText(card.emoji, b.w / 2, b.h / 2)\r\n } else {\r\n ctx.fillStyle = theme.primary + '99'; ctx.fill()\r\n ctx.strokeStyle = theme.primary; ctx.lineWidth = 1; ctx.globalAlpha = 0.25\r\n for (let j = 0; j < 3; j++) { const n = 4 + j * 5; ctx.strokeRect(n, n, b.w - n * 2, b.h - n * 2) }\r\n ctx.globalAlpha = 1\r\n }\r\n // Keyboard selection highlight\r\n if (i === this.selectedIdx) {\r\n ctx.strokeStyle = theme.text; ctx.lineWidth = 2; ctx.setLineDash([4, 3])\r\n ctx.strokeRect(0, 0, b.w, b.h); ctx.setLineDash([])\r\n }\r\n ctx.restore()\r\n }\r\n ctx.fillStyle = theme.text; ctx.globalAlpha = 0.5; ctx.font = '11px system-ui'; ctx.textAlign = 'right'\r\n ctx.fillText(`${this.matchCount}/${EMOJIS.length} \\u00b7 ${this.score}pts`, W - 6, H - 4); ctx.globalAlpha = 1\r\n }\r\n}\r\n"],"mappings":"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","names":["DARK_DEFAULTS","LIGHT_DEFAULTS","CSS_VAR_MAP","readCSSVar","varName","getSystemDefaults","resolveTheme","theme","resolved","key","cssVar","cssValue","value","EMOJIS","GRID","FLIP_MS","MemoryCardsGame","onScore","onGameOver","canvas","theme","resolveTheme","dpr","rect","pairs","i","j","emoji","id","cols","rows","w","h","px","py","b","r","total","idx","card","x","y","a","elapsed","timeBonus","now","t","dt","c","ctx","W","H","scaleX","cx","cy","pulse","n"]}
@@ -0,0 +1,2 @@
1
+ var p={primary:"#6366F1",background:"#0F0F0F",surface:"#1A1A2E",text:"#F8F8F8",accent:"#E94560"},w={primary:"#4F46E5",background:"#FFFFFF",surface:"#F3F4F6",text:"#111827",accent:"#E94560"},C={primary:"--lg-primary",background:"--lg-background",surface:"--lg-surface",text:"--lg-text",accent:"--lg-accent"};function F(l){return typeof window>"u"?null:getComputedStyle(document.documentElement).getPropertyValue(l).trim()||null}function R(){if(typeof window>"u")return p;try{return window.matchMedia("(prefers-color-scheme: dark)").matches?p:w}catch{return p}}function m(l){let e={...R()};for(let[i,n]of Object.entries(C)){let s=F(n);s&&(e[i]=s)}if(l)for(let[i,n]of Object.entries(l))n&&(e[i]=n);return e}var M={up:"\u25B2",down:"\u25BC",left:"\u25C0",right:"\u25B6"},u=class{constructor(t){this.options=t;this.boundClick=this.handleClick.bind(this),this.boundTouch=this.handleTouch.bind(this)}overlay=null;boundClick;boundTouch;mount(){if(!("ontouchstart"in window)||this.overlay)return;let{parent:t,primaryColor:e,textColor:i,showFire:n}=this.options;getComputedStyle(t).position==="static"&&(t.style.position="relative");let r=document.createElement("div");r.setAttribute("data-lg-dpad",""),r.style.cssText=["position:absolute","bottom:12px","left:50%","transform:translateX(-50%)","display:grid","grid-template-columns:44px 44px 44px","grid-template-rows:44px 44px 44px","gap:2px","pointer-events:none","z-index:10"].join(";");let c=[null,"up",null,"left",n?"fire":null,"right",null,"down",null];for(let a of c){let o=document.createElement("button");a&&a!=="fire"?(o.textContent=M[a],o.setAttribute("aria-label",a),o.dataset.dir=a,o.style.cssText=this.buttonStyle(e,i)):a==="fire"?(o.textContent="\u25CF",o.setAttribute("aria-label","fire"),o.dataset.action="fire",o.style.cssText=this.buttonStyle(e,i)):o.style.cssText="visibility:hidden;",r.appendChild(o)}r.addEventListener("click",this.boundClick),r.addEventListener("touchstart",this.boundTouch,{passive:!0}),t.appendChild(r),this.overlay=r}destroy(){this.overlay&&(this.overlay.removeEventListener("click",this.boundClick),this.overlay.removeEventListener("touchstart",this.boundTouch),this.overlay.remove(),this.overlay=null)}buttonStyle(t,e){return["pointer-events:auto","width:44px","height:44px","border:none","border-radius:8px",`background:${t}55`,`color:${e}`,"font-size:16px","display:flex","align-items:center","justify-content:center","touch-action:manipulation","cursor:pointer","-webkit-tap-highlight-color:transparent"].join(";")}handleClick(t){let e=t.target.closest("[data-dir],[data-action]");e&&(e.dataset.dir?this.options.onDirection(e.dataset.dir):e.dataset.action==="fire"&&this.options.onFire?.())}handleTouch(t){let e=t.target.closest("[data-dir],[data-action]");e&&(e.dataset.dir?this.options.onDirection(e.dataset.dir):e.dataset.action==="fire"&&this.options.onFire?.())}};var h=20,f=150,A=5,L=30,y=class{name="snake";bundleSize=4e3;canvas;ctx;theme;snake=[];food={x:0,y:0};direction="right";nextDirection="right";score=0;tickMs=f;lastTick=0;animFrameId=null;running=!1;cellSize=0;boundKeyDown;boundTouchStart;boundTouchEnd;touchStartX=0;touchStartY=0;isTouchDevice=!1;dpad=null;onScoreCallback;onGameOverCallback;constructor(t,e){this.onScoreCallback=t,this.onGameOverCallback=e,this.boundKeyDown=this.handleKeyDown.bind(this),this.boundTouchStart=this.handleTouchStart.bind(this),this.boundTouchEnd=this.handleTouchEnd.bind(this)}init(t,e){this.canvas=t,this.ctx=t.getContext("2d"),this.theme=m(e),t.setAttribute("aria-label","Snake game \u2014 loading in background"),t.setAttribute("role","img");let i=window.devicePixelRatio||1,n=t.getBoundingClientRect();t.width=n.width*i,t.height=n.height*i,this.ctx.scale(i,i),t.style.width=`${n.width}px`,t.style.height=`${n.height}px`,this.cellSize=Math.floor(n.width/h),this.isTouchDevice="ontouchstart"in window,this.reset()}reset(){let t=Math.floor(h/2);this.snake=[{x:t,y:t},{x:t-1,y:t},{x:t-2,y:t}],this.direction="right",this.nextDirection="right",this.score=0,this.tickMs=f,this.placeFood()}placeFood(){let t=new Set(this.snake.map(i=>`${i.x},${i.y}`)),e;do e={x:Math.floor(Math.random()*h),y:Math.floor(Math.random()*h)};while(t.has(`${e.x},${e.y}`));this.food=e}start(){this.running=!0,this.lastTick=performance.now(),this.loop(performance.now()),this.attachEventListeners(),this.isTouchDevice&&this.mountDpad()}pause(){this.running=!1,this.animFrameId!==null&&(cancelAnimationFrame(this.animFrameId),this.animFrameId=null)}resume(){this.running||(this.running=!0,this.lastTick=performance.now(),this.loop(performance.now()))}destroy(){this.pause(),this.removeEventListeners(),this.dpad?.destroy(),this.dpad=null}getScore(){return this.score}loop(t){this.running&&(this.animFrameId=requestAnimationFrame(e=>this.loop(e)),t-this.lastTick>=this.tickMs&&(this.lastTick=t,this.tick()),this.render())}tick(){this.direction=this.nextDirection;let t=this.snake[0],e={x:(t.x+(this.direction==="right"?1:this.direction==="left"?-1:0)+h)%h,y:(t.y+(this.direction==="down"?1:this.direction==="up"?-1:0)+h)%h};if(this.snake.some(i=>i.x===e.x&&i.y===e.y)){this.onGameOverCallback?.(),this.reset();return}this.snake.unshift(e),e.x===this.food.x&&e.y===this.food.y?(this.score+=10,this.onScoreCallback?.(this.score),this.score%50===0&&(this.tickMs=Math.max(60,this.tickMs-A)),this.placeFood()):this.snake.pop()}render(){let{ctx:t,cellSize:e,theme:i}=this,n=this.canvas.getBoundingClientRect(),s=n.width,r=n.height;t.fillStyle=i.background,t.fillRect(0,0,s,r),t.strokeStyle=i.surface,t.lineWidth=.5,t.globalAlpha=.3;for(let o=0;o<=s;o+=e)t.beginPath(),t.moveTo(o,0),t.lineTo(o,r),t.stroke();for(let o=0;o<=r;o+=e)t.beginPath(),t.moveTo(0,o),t.lineTo(s,o),t.stroke();t.globalAlpha=1;let c=this.food.x*e+e/2,a=this.food.y*e+e/2;t.fillStyle=i.accent,t.beginPath(),t.arc(c,a,e*.4,0,Math.PI*2),t.fill(),this.snake.forEach((o,v)=>{let g=o.x*e,b=o.y*e,d=1;if(v===0)t.fillStyle=i.primary;else{let S=1-v/this.snake.length*.4;t.globalAlpha=S,t.fillStyle=i.primary}let T=3,x=g+d,E=b+d,D=e-d*2,k=e-d*2;t.beginPath(),t.roundRect(x,E,D,k,T),t.fill(),t.globalAlpha=1}),t.fillStyle=i.text,t.font=`bold ${Math.round(e*.7)}px monospace`,t.textAlign="right",t.fillText(`${this.score}`,s-8,e)}attachEventListeners(){document.addEventListener("keydown",this.boundKeyDown),this.canvas.addEventListener("touchstart",this.boundTouchStart,{passive:!0}),this.canvas.addEventListener("touchend",this.boundTouchEnd,{passive:!0})}removeEventListeners(){document.removeEventListener("keydown",this.boundKeyDown),this.canvas.removeEventListener("touchstart",this.boundTouchStart),this.canvas.removeEventListener("touchend",this.boundTouchEnd)}handleKeyDown(t){let i={ArrowUp:"up",ArrowDown:"down",ArrowLeft:"left",ArrowRight:"right"}[t.key];if(!i)return;({up:"down",down:"up",left:"right",right:"left"})[i]!==this.direction&&(this.nextDirection=i,t.preventDefault())}handleTouchStart(t){let e=t.touches[0];e&&(this.touchStartX=e.clientX,this.touchStartY=e.clientY)}handleTouchEnd(t){let e=t.changedTouches[0];if(!e)return;let i=e.clientX-this.touchStartX,n=e.clientY-this.touchStartY,s=Math.abs(i),r=Math.abs(n);if(Math.max(s,r)<L)return;let c;s>r?c=i>0?"right":"left":c=n>0?"down":"up",{up:"down",down:"up",left:"right",right:"left"}[c]!==this.direction&&(this.nextDirection=c)}mountDpad(){let t=this.canvas.parentElement;t&&(this.dpad=new u({parent:t,primaryColor:this.theme.primary,textColor:this.theme.text,onDirection:e=>{({up:"down",down:"up",left:"right",right:"left"})[e]!==this.direction&&(this.nextDirection=e)}}),this.dpad.mount())}};export{y as SnakeGame};
2
+ //# sourceMappingURL=snake.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/theme.ts","../../src/dpad.ts","../../src/games/snake/index.ts"],"sourcesContent":["/**\r\n * Theme resolution — 3-level priority:\r\n * 1. Explicit theme prop (highest)\r\n * 2. CSS variables on :root (--lg-primary, etc.)\r\n * 3. prefers-color-scheme detection (lowest)\r\n */\r\n\r\nimport type { ThemeObject, ResolvedTheme } from './types.js'\r\n\r\nconst DARK_DEFAULTS: ResolvedTheme = {\r\n primary: '#6366F1',\r\n background: '#0F0F0F',\r\n surface: '#1A1A2E',\r\n text: '#F8F8F8',\r\n accent: '#E94560',\r\n}\r\n\r\nconst LIGHT_DEFAULTS: ResolvedTheme = {\r\n primary: '#4F46E5',\r\n background: '#FFFFFF',\r\n surface: '#F3F4F6',\r\n text: '#111827',\r\n accent: '#E94560',\r\n}\r\n\r\nconst CSS_VAR_MAP: Record<keyof ResolvedTheme, string> = {\r\n primary: '--lg-primary',\r\n background: '--lg-background',\r\n surface: '--lg-surface',\r\n text: '--lg-text',\r\n accent: '--lg-accent',\r\n}\r\n\r\n/**\r\n * Read a CSS variable from :root, returns null if not set.\r\n */\r\nfunction readCSSVar(varName: string): string | null {\r\n if (typeof window === 'undefined') return null\r\n const value = getComputedStyle(document.documentElement)\r\n .getPropertyValue(varName)\r\n .trim()\r\n return value || null\r\n}\r\n\r\n/**\r\n * Detect system color scheme preference.\r\n */\r\nfunction getSystemDefaults(): ResolvedTheme {\r\n if (typeof window === 'undefined') return DARK_DEFAULTS\r\n try {\r\n const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches\r\n return prefersDark ? DARK_DEFAULTS : LIGHT_DEFAULTS\r\n } catch {\r\n return DARK_DEFAULTS\r\n }\r\n}\r\n\r\n/**\r\n * Resolve the final theme from all sources.\r\n * Priority: explicit prop > CSS variables > system defaults.\r\n */\r\nexport function resolveTheme(theme?: ThemeObject): ResolvedTheme {\r\n const systemDefaults = getSystemDefaults()\r\n\r\n const resolved: ResolvedTheme = { ...systemDefaults }\r\n\r\n // Level 2: CSS variables\r\n for (const [key, cssVar] of Object.entries(CSS_VAR_MAP) as [keyof ResolvedTheme, string][]) {\r\n const cssValue = readCSSVar(cssVar)\r\n if (cssValue) {\r\n resolved[key] = cssValue\r\n }\r\n }\r\n\r\n // Level 1: Explicit theme prop\r\n if (theme) {\r\n for (const [key, value] of Object.entries(theme) as [keyof ThemeObject, string | undefined][]) {\r\n if (value) {\r\n resolved[key] = value\r\n }\r\n }\r\n }\r\n\r\n return resolved\r\n}\r\n\r\n/**\r\n * Apply resolved theme as CSS variables to a container element.\r\n * This allows games to reference CSS variables for dynamic theming.\r\n */\r\nexport function applyThemeToElement(element: HTMLElement, theme: ResolvedTheme): void {\r\n for (const [key, cssVar] of Object.entries(CSS_VAR_MAP) as [keyof ResolvedTheme, string][]) {\r\n element.style.setProperty(cssVar, theme[key])\r\n }\r\n}\r\n","/**\n * Virtual D-pad — Reusable touch controller overlay.\n *\n * Renders 4 directional arrows (+ optional center/fire button) as an\n * overlay positioned at the bottom-center of a parent element.\n * Only renders when touch is available ('ontouchstart' in window).\n *\n * Each button meets the 44×44px minimum touch target requirement.\n * Semi-transparent, themed with the provided primary color.\n */\n\nexport type DpadDirection = 'up' | 'down' | 'left' | 'right'\n\nexport interface DpadOptions {\n /** Parent element to attach the overlay to. Must have position: relative/absolute/fixed. */\n parent: HTMLElement\n /** Primary theme color for button backgrounds. */\n primaryColor: string\n /** Text/icon color. */\n textColor: string\n /** Called when a direction button is pressed. */\n onDirection: (dir: DpadDirection) => void\n /** If true, render a center \"fire\" button. */\n showFire?: boolean\n /** Called when the fire button is pressed. */\n onFire?: () => void\n}\n\nconst BTN_SIZE = 44\nconst GAP = 2\n\nconst ARROWS: Record<DpadDirection, string> = {\n up: '\\u25B2',\n down: '\\u25BC',\n left: '\\u25C0',\n right: '\\u25B6',\n}\n\nexport class Dpad {\n private overlay: HTMLDivElement | null = null\n private boundClick: (e: MouseEvent) => void\n private boundTouch: (e: TouchEvent) => void\n\n constructor(private options: DpadOptions) {\n this.boundClick = this.handleClick.bind(this)\n this.boundTouch = this.handleTouch.bind(this)\n }\n\n /** Create and mount the D-pad. No-op if touch is unavailable. */\n mount(): void {\n if (!('ontouchstart' in window)) return\n if (this.overlay) return\n\n const { parent, primaryColor, textColor, showFire } = this.options\n\n // Ensure parent is positioned\n const pStyle = getComputedStyle(parent)\n if (pStyle.position === 'static') parent.style.position = 'relative'\n\n const overlay = document.createElement('div')\n overlay.setAttribute('data-lg-dpad', '')\n overlay.style.cssText = [\n 'position:absolute',\n 'bottom:12px',\n 'left:50%',\n 'transform:translateX(-50%)',\n `display:grid`,\n `grid-template-columns:${BTN_SIZE}px ${BTN_SIZE}px ${BTN_SIZE}px`,\n `grid-template-rows:${BTN_SIZE}px ${BTN_SIZE}px ${BTN_SIZE}px`,\n `gap:${GAP}px`,\n 'pointer-events:none',\n 'z-index:10',\n ].join(';')\n\n // Grid layout: [_, up, _], [left, center, right], [_, down, _]\n const cells: (DpadDirection | 'fire' | null)[] = [\n null, 'up', null,\n 'left', showFire ? 'fire' : null, 'right',\n null, 'down', null,\n ]\n\n for (const cell of cells) {\n const btn = document.createElement('button')\n if (cell && cell !== 'fire') {\n btn.textContent = ARROWS[cell]\n btn.setAttribute('aria-label', cell)\n btn.dataset.dir = cell\n btn.style.cssText = this.buttonStyle(primaryColor, textColor)\n } else if (cell === 'fire') {\n btn.textContent = '\\u25CF'\n btn.setAttribute('aria-label', 'fire')\n btn.dataset.action = 'fire'\n btn.style.cssText = this.buttonStyle(primaryColor, textColor)\n } else {\n btn.style.cssText = 'visibility:hidden;'\n }\n overlay.appendChild(btn)\n }\n\n overlay.addEventListener('click', this.boundClick)\n overlay.addEventListener('touchstart', this.boundTouch, { passive: true })\n\n parent.appendChild(overlay)\n this.overlay = overlay\n }\n\n /** Remove the D-pad from the DOM and clean up listeners. */\n destroy(): void {\n if (!this.overlay) return\n this.overlay.removeEventListener('click', this.boundClick)\n this.overlay.removeEventListener('touchstart', this.boundTouch)\n this.overlay.remove()\n this.overlay = null\n }\n\n private buttonStyle(bg: string, text: string): string {\n return [\n 'pointer-events:auto',\n `width:${BTN_SIZE}px`,\n `height:${BTN_SIZE}px`,\n 'border:none',\n 'border-radius:8px',\n `background:${bg}55`,\n `color:${text}`,\n 'font-size:16px',\n 'display:flex',\n 'align-items:center',\n 'justify-content:center',\n 'touch-action:manipulation',\n 'cursor:pointer',\n '-webkit-tap-highlight-color:transparent',\n ].join(';')\n }\n\n private handleClick(e: MouseEvent): void {\n const target = (e.target as HTMLElement).closest('[data-dir],[data-action]') as HTMLElement | null\n if (!target) return\n if (target.dataset.dir) {\n this.options.onDirection(target.dataset.dir as DpadDirection)\n } else if (target.dataset.action === 'fire') {\n this.options.onFire?.()\n }\n }\n\n private handleTouch(e: TouchEvent): void {\n const target = (e.target as HTMLElement).closest('[data-dir],[data-action]') as HTMLElement | null\n if (!target) return\n if (target.dataset.dir) {\n this.options.onDirection(target.dataset.dir as DpadDirection)\n } else if (target.dataset.action === 'fire') {\n this.options.onFire?.()\n }\n }\n}\n","/**\r\n * Snake — Classic snake game with wrapping walls.\r\n * \r\n * Controls:\r\n * - Keyboard: Arrow keys\r\n * - Touch: Swipe gestures\r\n * - Mobile: Virtual D-pad (rendered by the host component)\r\n * \r\n * Bundle target: ~4 kB gzipped\r\n */\r\n\r\nimport type { GamePlugin, ThemeObject, ResolvedTheme } from '../../types.js'\r\nimport { resolveTheme } from '../../theme.js'\r\nimport { Dpad } from '../../dpad.js'\r\n\r\ntype Direction = 'up' | 'down' | 'left' | 'right'\r\ntype Point = { x: number; y: number }\r\n\r\nconst GRID_SIZE = 20 // cells per row/column\r\nconst BASE_TICK_MS = 150 // starting tick speed\r\nconst SPEED_INCREASE = 5 // ms faster per 5 points\r\nconst SWIPE_THRESHOLD = 30 // minimum swipe distance (px)\r\n\r\nexport class SnakeGame implements GamePlugin {\r\n readonly name = 'snake'\r\n readonly bundleSize = 4_000\r\n\r\n private canvas!: HTMLCanvasElement\r\n private ctx!: CanvasRenderingContext2D\r\n private theme!: ResolvedTheme\r\n\r\n private snake: Point[] = []\r\n private food: Point = { x: 0, y: 0 }\r\n private direction: Direction = 'right'\r\n private nextDirection: Direction = 'right'\r\n private score = 0\r\n private tickMs = BASE_TICK_MS\r\n private lastTick = 0\r\n private animFrameId: number | null = null\r\n private running = false\r\n private cellSize = 0\r\n\r\n private boundKeyDown: (e: KeyboardEvent) => void\r\n private boundTouchStart: (e: TouchEvent) => void\r\n private boundTouchEnd: (e: TouchEvent) => void\r\n private touchStartX = 0\r\n private touchStartY = 0\r\n private isTouchDevice = false\r\n private dpad: Dpad | null = null\r\n\r\n private onScoreCallback?: (score: number) => void\r\n private onGameOverCallback?: () => void\r\n\r\n constructor(onScore?: (score: number) => void, onGameOver?: () => void) {\r\n this.onScoreCallback = onScore\r\n this.onGameOverCallback = onGameOver\r\n this.boundKeyDown = this.handleKeyDown.bind(this)\r\n this.boundTouchStart = this.handleTouchStart.bind(this)\r\n this.boundTouchEnd = this.handleTouchEnd.bind(this)\r\n }\r\n\r\n init(canvas: HTMLCanvasElement, theme: ThemeObject): void {\r\n this.canvas = canvas\r\n this.ctx = canvas.getContext('2d')!\r\n this.theme = resolveTheme(theme)\r\n\r\n canvas.setAttribute('aria-label', 'Snake game \\u2014 loading in background')\r\n canvas.setAttribute('role', 'img')\r\n\r\n // Scale for device pixel ratio\r\n const dpr = window.devicePixelRatio || 1\r\n const rect = canvas.getBoundingClientRect()\r\n canvas.width = rect.width * dpr\r\n canvas.height = rect.height * dpr\r\n this.ctx.scale(dpr, dpr)\r\n canvas.style.width = `${rect.width}px`\r\n canvas.style.height = `${rect.height}px`\r\n\r\n this.cellSize = Math.floor(rect.width / GRID_SIZE)\r\n this.isTouchDevice = 'ontouchstart' in window\r\n this.reset()\r\n }\r\n\r\n private reset(): void {\r\n // Start in the middle going right\r\n const mid = Math.floor(GRID_SIZE / 2)\r\n this.snake = [\r\n { x: mid, y: mid },\r\n { x: mid - 1, y: mid },\r\n { x: mid - 2, y: mid },\r\n ]\r\n this.direction = 'right'\r\n this.nextDirection = 'right'\r\n this.score = 0\r\n this.tickMs = BASE_TICK_MS\r\n this.placeFood()\r\n }\r\n\r\n private placeFood(): void {\r\n const occupied = new Set(this.snake.map(p => `${p.x},${p.y}`))\r\n let pos: Point\r\n do {\r\n pos = {\r\n x: Math.floor(Math.random() * GRID_SIZE),\r\n y: Math.floor(Math.random() * GRID_SIZE),\r\n }\r\n } while (occupied.has(`${pos.x},${pos.y}`))\r\n this.food = pos\r\n }\r\n\r\n start(): void {\r\n this.running = true\r\n this.lastTick = performance.now()\r\n this.loop(performance.now())\r\n this.attachEventListeners()\r\n if (this.isTouchDevice) this.mountDpad()\r\n }\r\n\r\n pause(): void {\r\n this.running = false\r\n if (this.animFrameId !== null) {\r\n cancelAnimationFrame(this.animFrameId)\r\n this.animFrameId = null\r\n }\r\n }\r\n\r\n resume(): void {\r\n if (!this.running) {\r\n this.running = true\r\n this.lastTick = performance.now()\r\n this.loop(performance.now())\r\n }\r\n }\r\n\r\n destroy(): void {\r\n this.pause()\r\n this.removeEventListeners()\r\n this.dpad?.destroy()\r\n this.dpad = null\r\n }\r\n\r\n getScore(): number {\r\n return this.score\r\n }\r\n\r\n // ─── Game Loop ──────────────────────────────────────────────────────────────\r\n\r\n private loop(now: number): void {\r\n if (!this.running) return\r\n\r\n this.animFrameId = requestAnimationFrame(t => this.loop(t))\r\n\r\n if (now - this.lastTick >= this.tickMs) {\r\n this.lastTick = now\r\n this.tick()\r\n }\r\n\r\n this.render()\r\n }\r\n\r\n private tick(): void {\r\n this.direction = this.nextDirection\r\n\r\n const head = this.snake[0]!\r\n const next: Point = {\r\n x: (head.x + (this.direction === 'right' ? 1 : this.direction === 'left' ? -1 : 0) + GRID_SIZE) % GRID_SIZE,\r\n y: (head.y + (this.direction === 'down' ? 1 : this.direction === 'up' ? -1 : 0) + GRID_SIZE) % GRID_SIZE,\r\n }\r\n\r\n // Collision with self\r\n if (this.snake.some(p => p.x === next.x && p.y === next.y)) {\r\n this.onGameOverCallback?.()\r\n this.reset()\r\n return\r\n }\r\n\r\n this.snake.unshift(next)\r\n\r\n if (next.x === this.food.x && next.y === this.food.y) {\r\n this.score += 10\r\n this.onScoreCallback?.(this.score)\r\n\r\n // Speed up every 5 points\r\n if (this.score % 50 === 0) {\r\n this.tickMs = Math.max(60, this.tickMs - SPEED_INCREASE)\r\n }\r\n\r\n this.placeFood()\r\n } else {\r\n this.snake.pop()\r\n }\r\n }\r\n\r\n // ─── Rendering ──────────────────────────────────────────────────────────────\r\n\r\n private render(): void {\r\n const { ctx, cellSize, theme } = this\r\n const rect = this.canvas.getBoundingClientRect()\r\n const W = rect.width\r\n const H = rect.height\r\n\r\n // Background\r\n ctx.fillStyle = theme.background\r\n ctx.fillRect(0, 0, W, H)\r\n\r\n // Grid (subtle)\r\n ctx.strokeStyle = theme.surface\r\n ctx.lineWidth = 0.5\r\n ctx.globalAlpha = 0.3\r\n for (let x = 0; x <= W; x += cellSize) {\r\n ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, H); ctx.stroke()\r\n }\r\n for (let y = 0; y <= H; y += cellSize) {\r\n ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke()\r\n }\r\n ctx.globalAlpha = 1\r\n\r\n // Food\r\n const fx = this.food.x * cellSize + cellSize / 2\r\n const fy = this.food.y * cellSize + cellSize / 2\r\n ctx.fillStyle = theme.accent\r\n ctx.beginPath()\r\n ctx.arc(fx, fy, cellSize * 0.4, 0, Math.PI * 2)\r\n ctx.fill()\r\n\r\n // Snake\r\n this.snake.forEach((p, i) => {\r\n const x = p.x * cellSize\r\n const y = p.y * cellSize\r\n const padding = 1\r\n\r\n if (i === 0) {\r\n // Head\r\n ctx.fillStyle = theme.primary\r\n } else {\r\n // Body — fade toward tail\r\n const alpha = 1 - (i / this.snake.length) * 0.4\r\n ctx.globalAlpha = alpha\r\n ctx.fillStyle = theme.primary\r\n }\r\n\r\n const r = 3\r\n const px = x + padding\r\n const py = y + padding\r\n const pw = cellSize - padding * 2\r\n const ph = cellSize - padding * 2\r\n\r\n ctx.beginPath()\r\n ctx.roundRect(px, py, pw, ph, r)\r\n ctx.fill()\r\n ctx.globalAlpha = 1\r\n })\r\n\r\n // Score\r\n ctx.fillStyle = theme.text\r\n ctx.font = `bold ${Math.round(cellSize * 0.7)}px monospace`\r\n ctx.textAlign = 'right'\r\n ctx.fillText(`${this.score}`, W - 8, cellSize)\r\n }\r\n\r\n // ─── Controls ───────────────────────────────────────────────────────────────\r\n\r\n private attachEventListeners(): void {\r\n document.addEventListener('keydown', this.boundKeyDown)\r\n this.canvas.addEventListener('touchstart', this.boundTouchStart, { passive: true })\r\n this.canvas.addEventListener('touchend', this.boundTouchEnd, { passive: true })\r\n }\r\n\r\n private removeEventListeners(): void {\r\n document.removeEventListener('keydown', this.boundKeyDown)\r\n this.canvas.removeEventListener('touchstart', this.boundTouchStart)\r\n this.canvas.removeEventListener('touchend', this.boundTouchEnd)\r\n }\r\n\r\n private handleKeyDown(e: KeyboardEvent): void {\r\n const map: Record<string, Direction> = {\r\n ArrowUp: 'up',\r\n ArrowDown: 'down',\r\n ArrowLeft: 'left',\r\n ArrowRight: 'right',\r\n }\r\n const dir = map[e.key]\r\n if (!dir) return\r\n\r\n // Prevent reverse direction\r\n const opposites: Record<Direction, Direction> = {\r\n up: 'down', down: 'up', left: 'right', right: 'left',\r\n }\r\n if (opposites[dir] !== this.direction) {\r\n this.nextDirection = dir\r\n e.preventDefault()\r\n }\r\n }\r\n\r\n private handleTouchStart(e: TouchEvent): void {\r\n const touch = e.touches[0]\r\n if (!touch) return\r\n this.touchStartX = touch.clientX\r\n this.touchStartY = touch.clientY\r\n }\r\n\r\n private handleTouchEnd(e: TouchEvent): void {\r\n const touch = e.changedTouches[0]\r\n if (!touch) return\r\n\r\n const dx = touch.clientX - this.touchStartX\r\n const dy = touch.clientY - this.touchStartY\r\n const absDx = Math.abs(dx)\r\n const absDy = Math.abs(dy)\r\n\r\n if (Math.max(absDx, absDy) < SWIPE_THRESHOLD) return // min 44px touch target\r\n\r\n let dir: Direction\r\n if (absDx > absDy) {\r\n dir = dx > 0 ? 'right' : 'left'\r\n } else {\r\n dir = dy > 0 ? 'down' : 'up'\r\n }\r\n\r\n const opposites: Record<Direction, Direction> = {\r\n up: 'down', down: 'up', left: 'right', right: 'left',\r\n }\r\n if (opposites[dir] !== this.direction) {\r\n this.nextDirection = dir\r\n }\r\n }\r\n\r\n // ─── Virtual D-pad ─────────────────────────────────────────────────────────\r\n\r\n private mountDpad(): void {\r\n const parent = this.canvas.parentElement\r\n if (!parent) return\r\n\r\n this.dpad = new Dpad({\r\n parent,\r\n primaryColor: this.theme.primary,\r\n textColor: this.theme.text,\r\n onDirection: (dir) => {\r\n const opposites: Record<Direction, Direction> = {\r\n up: 'down', down: 'up', left: 'right', right: 'left',\r\n }\r\n if (opposites[dir] !== this.direction) {\r\n this.nextDirection = dir\r\n }\r\n },\r\n })\r\n this.dpad.mount()\r\n }\r\n}\r\n"],"mappings":"AASA,IAAMA,EAA+B,CACnC,QAAS,UACT,WAAY,UACZ,QAAS,UACT,KAAM,UACN,OAAQ,SACV,EAEMC,EAAgC,CACpC,QAAS,UACT,WAAY,UACZ,QAAS,UACT,KAAM,UACN,OAAQ,SACV,EAEMC,EAAmD,CACvD,QAAS,eACT,WAAY,kBACZ,QAAS,eACT,KAAM,YACN,OAAQ,aACV,EAKA,SAASC,EAAWC,EAAgC,CAClD,OAAI,OAAO,OAAW,IAAoB,KAC5B,iBAAiB,SAAS,eAAe,EACpD,iBAAiBA,CAAO,EACxB,KAAK,GACQ,IAClB,CAKA,SAASC,GAAmC,CAC1C,GAAI,OAAO,OAAW,IAAa,OAAOL,EAC1C,GAAI,CAEF,OADoB,OAAO,WAAW,8BAA8B,EAAE,QACjDA,EAAgBC,CACvC,MAAQ,CACN,OAAOD,CACT,CACF,CAMO,SAASM,EAAaC,EAAoC,CAG/D,IAAMC,EAA0B,CAAE,GAFXH,EAAkB,CAEW,EAGpD,OAAW,CAACI,EAAKC,CAAM,IAAK,OAAO,QAAQR,CAAW,EAAsC,CAC1F,IAAMS,EAAWR,EAAWO,CAAM,EAC9BC,IACFH,EAASC,CAAG,EAAIE,EAEpB,CAGA,GAAIJ,EACF,OAAW,CAACE,EAAKG,CAAK,IAAK,OAAO,QAAQL,CAAK,EACzCK,IACFJ,EAASC,CAAG,EAAIG,GAKtB,OAAOJ,CACT,CCrDA,IAAMK,EAAwC,CAC5C,GAAI,SACJ,KAAM,SACN,KAAM,SACN,MAAO,QACT,EAEaC,EAAN,KAAW,CAKhB,YAAoBC,EAAsB,CAAtB,aAAAA,EAClB,KAAK,WAAa,KAAK,YAAY,KAAK,IAAI,EAC5C,KAAK,WAAa,KAAK,YAAY,KAAK,IAAI,CAC9C,CAPQ,QAAiC,KACjC,WACA,WAQR,OAAc,CAEZ,GADI,EAAE,iBAAkB,SACpB,KAAK,QAAS,OAElB,GAAM,CAAE,OAAAC,EAAQ,aAAAC,EAAc,UAAAC,EAAW,SAAAC,CAAS,EAAI,KAAK,QAG5C,iBAAiBH,CAAM,EAC3B,WAAa,WAAUA,EAAO,MAAM,SAAW,YAE1D,IAAMI,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,aAAa,eAAgB,EAAE,EACvCA,EAAQ,MAAM,QAAU,CACtB,oBACA,cACA,WACA,6BACA,eACA,uCACA,oCACA,UACA,sBACA,YACF,EAAE,KAAK,GAAG,EAGV,IAAMC,EAA2C,CAC/C,KAAM,KAAM,KACZ,OAAQF,EAAW,OAAS,KAAM,QAClC,KAAM,OAAQ,IAChB,EAEA,QAAWG,KAAQD,EAAO,CACxB,IAAME,EAAM,SAAS,cAAc,QAAQ,EACvCD,GAAQA,IAAS,QACnBC,EAAI,YAAcV,EAAOS,CAAI,EAC7BC,EAAI,aAAa,aAAcD,CAAI,EACnCC,EAAI,QAAQ,IAAMD,EAClBC,EAAI,MAAM,QAAU,KAAK,YAAYN,EAAcC,CAAS,GACnDI,IAAS,QAClBC,EAAI,YAAc,SAClBA,EAAI,aAAa,aAAc,MAAM,EACrCA,EAAI,QAAQ,OAAS,OACrBA,EAAI,MAAM,QAAU,KAAK,YAAYN,EAAcC,CAAS,GAE5DK,EAAI,MAAM,QAAU,qBAEtBH,EAAQ,YAAYG,CAAG,CACzB,CAEAH,EAAQ,iBAAiB,QAAS,KAAK,UAAU,EACjDA,EAAQ,iBAAiB,aAAc,KAAK,WAAY,CAAE,QAAS,EAAK,CAAC,EAEzEJ,EAAO,YAAYI,CAAO,EAC1B,KAAK,QAAUA,CACjB,CAGA,SAAgB,CACT,KAAK,UACV,KAAK,QAAQ,oBAAoB,QAAS,KAAK,UAAU,EACzD,KAAK,QAAQ,oBAAoB,aAAc,KAAK,UAAU,EAC9D,KAAK,QAAQ,OAAO,EACpB,KAAK,QAAU,KACjB,CAEQ,YAAYI,EAAYC,EAAsB,CACpD,MAAO,CACL,sBACA,aACA,cACA,cACA,oBACA,cAAcD,CAAE,KAChB,SAASC,CAAI,GACb,iBACA,eACA,qBACA,yBACA,4BACA,iBACA,yCACF,EAAE,KAAK,GAAG,CACZ,CAEQ,YAAYC,EAAqB,CACvC,IAAMC,EAAUD,EAAE,OAAuB,QAAQ,0BAA0B,EACtEC,IACDA,EAAO,QAAQ,IACjB,KAAK,QAAQ,YAAYA,EAAO,QAAQ,GAAoB,EACnDA,EAAO,QAAQ,SAAW,QACnC,KAAK,QAAQ,SAAS,EAE1B,CAEQ,YAAYD,EAAqB,CACvC,IAAMC,EAAUD,EAAE,OAAuB,QAAQ,0BAA0B,EACtEC,IACDA,EAAO,QAAQ,IACjB,KAAK,QAAQ,YAAYA,EAAO,QAAQ,GAAoB,EACnDA,EAAO,QAAQ,SAAW,QACnC,KAAK,QAAQ,SAAS,EAE1B,CACF,ECvIA,IAAMC,EAAY,GACZC,EAAe,IACfC,EAAiB,EACjBC,EAAkB,GAEXC,EAAN,KAAsC,CAClC,KAAO,QACP,WAAa,IAEd,OACA,IACA,MAEA,MAAiB,CAAC,EAClB,KAAc,CAAE,EAAG,EAAG,EAAG,CAAE,EAC3B,UAAuB,QACvB,cAA2B,QAC3B,MAAQ,EACR,OAASH,EACT,SAAW,EACX,YAA6B,KAC7B,QAAU,GACV,SAAW,EAEX,aACA,gBACA,cACA,YAAc,EACd,YAAc,EACd,cAAgB,GAChB,KAAoB,KAEpB,gBACA,mBAER,YAAYI,EAAmCC,EAAyB,CACtE,KAAK,gBAAkBD,EACvB,KAAK,mBAAqBC,EAC1B,KAAK,aAAe,KAAK,cAAc,KAAK,IAAI,EAChD,KAAK,gBAAkB,KAAK,iBAAiB,KAAK,IAAI,EACtD,KAAK,cAAgB,KAAK,eAAe,KAAK,IAAI,CACpD,CAEA,KAAKC,EAA2BC,EAA0B,CACxD,KAAK,OAASD,EACd,KAAK,IAAMA,EAAO,WAAW,IAAI,EACjC,KAAK,MAAQE,EAAaD,CAAK,EAE/BD,EAAO,aAAa,aAAc,yCAAyC,EAC3EA,EAAO,aAAa,OAAQ,KAAK,EAGjC,IAAMG,EAAM,OAAO,kBAAoB,EACjCC,EAAOJ,EAAO,sBAAsB,EAC1CA,EAAO,MAAQI,EAAK,MAAQD,EAC5BH,EAAO,OAASI,EAAK,OAASD,EAC9B,KAAK,IAAI,MAAMA,EAAKA,CAAG,EACvBH,EAAO,MAAM,MAAQ,GAAGI,EAAK,KAAK,KAClCJ,EAAO,MAAM,OAAS,GAAGI,EAAK,MAAM,KAEpC,KAAK,SAAW,KAAK,MAAMA,EAAK,MAAQX,CAAS,EACjD,KAAK,cAAgB,iBAAkB,OACvC,KAAK,MAAM,CACb,CAEQ,OAAc,CAEpB,IAAMY,EAAM,KAAK,MAAMZ,EAAY,CAAC,EACpC,KAAK,MAAQ,CACX,CAAE,EAAGY,EAAK,EAAGA,CAAI,EACjB,CAAE,EAAGA,EAAM,EAAG,EAAGA,CAAI,EACrB,CAAE,EAAGA,EAAM,EAAG,EAAGA,CAAI,CACvB,EACA,KAAK,UAAY,QACjB,KAAK,cAAgB,QACrB,KAAK,MAAQ,EACb,KAAK,OAASX,EACd,KAAK,UAAU,CACjB,CAEQ,WAAkB,CACxB,IAAMY,EAAW,IAAI,IAAI,KAAK,MAAM,IAAIC,GAAK,GAAGA,EAAE,CAAC,IAAIA,EAAE,CAAC,EAAE,CAAC,EACzDC,EACJ,GACEA,EAAM,CACJ,EAAG,KAAK,MAAM,KAAK,OAAO,EAAIf,CAAS,EACvC,EAAG,KAAK,MAAM,KAAK,OAAO,EAAIA,CAAS,CACzC,QACOa,EAAS,IAAI,GAAGE,EAAI,CAAC,IAAIA,EAAI,CAAC,EAAE,GACzC,KAAK,KAAOA,CACd,CAEA,OAAc,CACZ,KAAK,QAAU,GACf,KAAK,SAAW,YAAY,IAAI,EAChC,KAAK,KAAK,YAAY,IAAI,CAAC,EAC3B,KAAK,qBAAqB,EACtB,KAAK,eAAe,KAAK,UAAU,CACzC,CAEA,OAAc,CACZ,KAAK,QAAU,GACX,KAAK,cAAgB,OACvB,qBAAqB,KAAK,WAAW,EACrC,KAAK,YAAc,KAEvB,CAEA,QAAe,CACR,KAAK,UACR,KAAK,QAAU,GACf,KAAK,SAAW,YAAY,IAAI,EAChC,KAAK,KAAK,YAAY,IAAI,CAAC,EAE/B,CAEA,SAAgB,CACd,KAAK,MAAM,EACX,KAAK,qBAAqB,EAC1B,KAAK,MAAM,QAAQ,EACnB,KAAK,KAAO,IACd,CAEA,UAAmB,CACjB,OAAO,KAAK,KACd,CAIQ,KAAKC,EAAmB,CACzB,KAAK,UAEV,KAAK,YAAc,sBAAsBC,GAAK,KAAK,KAAKA,CAAC,CAAC,EAEtDD,EAAM,KAAK,UAAY,KAAK,SAC9B,KAAK,SAAWA,EAChB,KAAK,KAAK,GAGZ,KAAK,OAAO,EACd,CAEQ,MAAa,CACnB,KAAK,UAAY,KAAK,cAEtB,IAAME,EAAO,KAAK,MAAM,CAAC,EACnBC,EAAc,CAClB,GAAID,EAAK,GAAK,KAAK,YAAc,QAAU,EAAI,KAAK,YAAc,OAAS,GAAK,GAAKlB,GAAaA,EAClG,GAAIkB,EAAK,GAAK,KAAK,YAAc,OAAS,EAAI,KAAK,YAAc,KAAO,GAAK,GAAKlB,GAAaA,CACjG,EAGA,GAAI,KAAK,MAAM,KAAKc,GAAKA,EAAE,IAAMK,EAAK,GAAKL,EAAE,IAAMK,EAAK,CAAC,EAAG,CAC1D,KAAK,qBAAqB,EAC1B,KAAK,MAAM,EACX,MACF,CAEA,KAAK,MAAM,QAAQA,CAAI,EAEnBA,EAAK,IAAM,KAAK,KAAK,GAAKA,EAAK,IAAM,KAAK,KAAK,GACjD,KAAK,OAAS,GACd,KAAK,kBAAkB,KAAK,KAAK,EAG7B,KAAK,MAAQ,KAAO,IACtB,KAAK,OAAS,KAAK,IAAI,GAAI,KAAK,OAASjB,CAAc,GAGzD,KAAK,UAAU,GAEf,KAAK,MAAM,IAAI,CAEnB,CAIQ,QAAe,CACrB,GAAM,CAAE,IAAAkB,EAAK,SAAAC,EAAU,MAAAb,CAAM,EAAI,KAC3BG,EAAO,KAAK,OAAO,sBAAsB,EACzCW,EAAIX,EAAK,MACTY,EAAIZ,EAAK,OAGfS,EAAI,UAAYZ,EAAM,WACtBY,EAAI,SAAS,EAAG,EAAGE,EAAGC,CAAC,EAGvBH,EAAI,YAAcZ,EAAM,QACxBY,EAAI,UAAY,GAChBA,EAAI,YAAc,GAClB,QAASI,EAAI,EAAGA,GAAKF,EAAGE,GAAKH,EAC3BD,EAAI,UAAU,EAAGA,EAAI,OAAOI,EAAG,CAAC,EAAGJ,EAAI,OAAOI,EAAGD,CAAC,EAAGH,EAAI,OAAO,EAElE,QAASK,EAAI,EAAGA,GAAKF,EAAGE,GAAKJ,EAC3BD,EAAI,UAAU,EAAGA,EAAI,OAAO,EAAGK,CAAC,EAAGL,EAAI,OAAOE,EAAGG,CAAC,EAAGL,EAAI,OAAO,EAElEA,EAAI,YAAc,EAGlB,IAAMM,EAAK,KAAK,KAAK,EAAIL,EAAWA,EAAW,EACzCM,EAAK,KAAK,KAAK,EAAIN,EAAWA,EAAW,EAC/CD,EAAI,UAAYZ,EAAM,OACtBY,EAAI,UAAU,EACdA,EAAI,IAAIM,EAAIC,EAAIN,EAAW,GAAK,EAAG,KAAK,GAAK,CAAC,EAC9CD,EAAI,KAAK,EAGT,KAAK,MAAM,QAAQ,CAACN,EAAGc,IAAM,CAC3B,IAAMJ,EAAIV,EAAE,EAAIO,EACVI,EAAIX,EAAE,EAAIO,EACVQ,EAAU,EAEhB,GAAID,IAAM,EAERR,EAAI,UAAYZ,EAAM,YACjB,CAEL,IAAMsB,EAAQ,EAAKF,EAAI,KAAK,MAAM,OAAU,GAC5CR,EAAI,YAAcU,EAClBV,EAAI,UAAYZ,EAAM,OACxB,CAEA,IAAMuB,EAAI,EACJC,EAAKR,EAAIK,EACTI,EAAKR,EAAII,EACTK,EAAKb,EAAWQ,EAAU,EAC1BM,EAAKd,EAAWQ,EAAU,EAEhCT,EAAI,UAAU,EACdA,EAAI,UAAUY,EAAIC,EAAIC,EAAIC,EAAIJ,CAAC,EAC/BX,EAAI,KAAK,EACTA,EAAI,YAAc,CACpB,CAAC,EAGDA,EAAI,UAAYZ,EAAM,KACtBY,EAAI,KAAO,QAAQ,KAAK,MAAMC,EAAW,EAAG,CAAC,eAC7CD,EAAI,UAAY,QAChBA,EAAI,SAAS,GAAG,KAAK,KAAK,GAAIE,EAAI,EAAGD,CAAQ,CAC/C,CAIQ,sBAA6B,CACnC,SAAS,iBAAiB,UAAW,KAAK,YAAY,EACtD,KAAK,OAAO,iBAAiB,aAAc,KAAK,gBAAiB,CAAE,QAAS,EAAK,CAAC,EAClF,KAAK,OAAO,iBAAiB,WAAY,KAAK,cAAe,CAAE,QAAS,EAAK,CAAC,CAChF,CAEQ,sBAA6B,CACnC,SAAS,oBAAoB,UAAW,KAAK,YAAY,EACzD,KAAK,OAAO,oBAAoB,aAAc,KAAK,eAAe,EAClE,KAAK,OAAO,oBAAoB,WAAY,KAAK,aAAa,CAChE,CAEQ,cAAce,EAAwB,CAO5C,IAAMC,EANiC,CACrC,QAAS,KACT,UAAW,OACX,UAAW,OACX,WAAY,OACd,EACgBD,EAAE,GAAG,EACrB,GAAI,CAACC,EAAK,QAGsC,CAC9C,GAAI,OAAQ,KAAM,KAAM,KAAM,QAAS,MAAO,MAChD,GACcA,CAAG,IAAM,KAAK,YAC1B,KAAK,cAAgBA,EACrBD,EAAE,eAAe,EAErB,CAEQ,iBAAiBA,EAAqB,CAC5C,IAAME,EAAQF,EAAE,QAAQ,CAAC,EACpBE,IACL,KAAK,YAAcA,EAAM,QACzB,KAAK,YAAcA,EAAM,QAC3B,CAEQ,eAAeF,EAAqB,CAC1C,IAAME,EAAQF,EAAE,eAAe,CAAC,EAChC,GAAI,CAACE,EAAO,OAEZ,IAAMC,EAAKD,EAAM,QAAU,KAAK,YAC1BE,EAAKF,EAAM,QAAU,KAAK,YAC1BG,EAAQ,KAAK,IAAIF,CAAE,EACnBG,EAAQ,KAAK,IAAIF,CAAE,EAEzB,GAAI,KAAK,IAAIC,EAAOC,CAAK,EAAIvC,EAAiB,OAE9C,IAAIkC,EACAI,EAAQC,EACVL,EAAME,EAAK,EAAI,QAAU,OAEzBF,EAAMG,EAAK,EAAI,OAAS,KAGsB,CAC9C,GAAI,OAAQ,KAAM,KAAM,KAAM,QAAS,MAAO,MAChD,EACcH,CAAG,IAAM,KAAK,YAC1B,KAAK,cAAgBA,EAEzB,CAIQ,WAAkB,CACxB,IAAMM,EAAS,KAAK,OAAO,cACtBA,IAEL,KAAK,KAAO,IAAIC,EAAK,CACnB,OAAAD,EACA,aAAc,KAAK,MAAM,QACzB,UAAW,KAAK,MAAM,KACtB,YAAcN,GAAQ,EAC4B,CAC9C,GAAI,OAAQ,KAAM,KAAM,KAAM,QAAS,MAAO,MAChD,GACcA,CAAG,IAAM,KAAK,YAC1B,KAAK,cAAgBA,EAEzB,CACF,CAAC,EACD,KAAK,KAAK,MAAM,EAClB,CACF","names":["DARK_DEFAULTS","LIGHT_DEFAULTS","CSS_VAR_MAP","readCSSVar","varName","getSystemDefaults","resolveTheme","theme","resolved","key","cssVar","cssValue","value","ARROWS","Dpad","options","parent","primaryColor","textColor","showFire","overlay","cells","cell","btn","bg","text","e","target","GRID_SIZE","BASE_TICK_MS","SPEED_INCREASE","SWIPE_THRESHOLD","SnakeGame","onScore","onGameOver","canvas","theme","resolveTheme","dpr","rect","mid","occupied","p","pos","now","t","head","next","ctx","cellSize","W","H","x","y","fx","fy","i","padding","alpha","r","px","py","pw","ph","e","dir","touch","dx","dy","absDx","absDy","parent","Dpad"]}
@@ -0,0 +1,2 @@
1
+ var m={primary:"#6366F1",background:"#0F0F0F",surface:"#1A1A2E",text:"#F8F8F8",accent:"#E94560"},y={primary:"#4F46E5",background:"#FFFFFF",surface:"#F3F4F6",text:"#111827",accent:"#E94560"},T={primary:"--lg-primary",background:"--lg-background",surface:"--lg-surface",text:"--lg-text",accent:"--lg-accent"};function x(h){return typeof window>"u"?null:getComputedStyle(document.documentElement).getPropertyValue(h).trim()||null}function S(){if(typeof window>"u")return m;try{return window.matchMedia("(prefers-color-scheme: dark)").matches?m:y}catch{return m}}function p(h){let i={...S()};for(let[o,t]of Object.entries(T)){let n=x(t);n&&(i[o]=n)}if(h)for(let[o,t]of Object.entries(h))t&&(i[o]=t);return i}var u=3,c=2e4,w=u*u,v=900,P=500,A=800,M=700,g=class{name="whack-a-mole";bundleSize=4e3;canvas;ctx;theme;W=0;H=0;holes=[];score=0;timeLeft=c;roundStart=0;animFrameId=null;running=!1;lastTime=0;roundOver=!1;nextSpawnTime=0;boundClick;boundTouch;boundKeyDown;onScore;onGameOver;constructor(e,i){this.onScore=e,this.onGameOver=i,this.boundClick=this.onClick.bind(this),this.boundTouch=this.onTouch.bind(this),this.boundKeyDown=this.onKeyDown.bind(this)}init(e,i){this.canvas=e,this.ctx=e.getContext("2d"),this.theme=p(i),e.setAttribute("aria-label","Whack-a-Mole game \u2014 loading in background"),e.setAttribute("role","img");let o=window.devicePixelRatio||1,t=e.getBoundingClientRect();e.width=t.width*o,e.height=t.height*o,this.ctx.scale(o,o),e.style.width=`${t.width}px`,e.style.height=`${t.height}px`,this.W=t.width,this.H=t.height,this.reset()}reset(){this.holes=Array.from({length:w},()=>({active:!1,showProgress:0,hideAt:0,hit:!1,hitProgress:0})),this.score=0,this.timeLeft=c,this.roundOver=!1,this.nextSpawnTime=0}start(){this.running=!0,this.roundStart=performance.now(),this.lastTime=performance.now(),this.nextSpawnTime=performance.now(),this.loop(performance.now()),this.canvas.addEventListener("click",this.boundClick),this.canvas.addEventListener("touchend",this.boundTouch,{passive:!0}),document.addEventListener("keydown",this.boundKeyDown)}pause(){this.running=!1,this.animFrameId!==null&&(cancelAnimationFrame(this.animFrameId),this.animFrameId=null)}resume(){this.running||(this.running=!0,this.lastTime=performance.now(),this.nextSpawnTime=performance.now(),this.loop(performance.now()))}destroy(){this.pause(),this.canvas.removeEventListener("click",this.boundClick),this.canvas.removeEventListener("touchend",this.boundTouch),document.removeEventListener("keydown",this.boundKeyDown)}getScore(){return this.score}trySpawnMole(e){if(this.roundOver||e<this.nextSpawnTime)return;let i=this.holes.map((s,l)=>!s.active&&!s.hit?l:-1).filter(s=>s>=0);if(i.length>0){let s=i[Math.floor(Math.random()*i.length)];this.activateMole(s,e)}let o=e-this.roundStart,t=Math.min(1,o/c),n=v-t*(v-P);this.nextSpawnTime=e+n}activateMole(e,i){let o=this.holes[e];o.active=!0,o.hit=!1,o.hitProgress=0,o.hideAt=i+A+Math.random()*M}holeBounds(e){let i=u,o=u,t=24,n=24,s=(this.W-t*2)/i,l=(this.H-n*2-30)/o,r=e%i,d=Math.floor(e/i),f=t+r*s+s/2,a=n+d*l+l/2+30,b=Math.min(s,l)*.38;return{cx:f,cy:a,r:b}}onClick(e){let i=this.canvas.getBoundingClientRect();this.tryHit(e.clientX-i.left,e.clientY-i.top)}onTouch(e){let i=e.changedTouches[0];if(!i)return;let o=this.canvas.getBoundingClientRect();this.tryHit(i.clientX-o.left,i.clientY-o.top)}onKeyDown(e){let i=parseInt(e.key,10);if(i>=1&&i<=9){if(e.preventDefault(),this.roundOver){this.reset(),this.nextSpawnTime=performance.now();return}let o=i-1,t=this.holes[o];t&&t.active&&!t.hit&&(t.active=!1,t.hit=!0,t.hitProgress=0,t.hideAt=0,this.score++,this.onScore?.(this.score))}}tryHit(e,i){if(this.roundOver){this.reset(),this.nextSpawnTime=performance.now();return}for(let o=0;o<this.holes.length;o++){let t=this.holes[o];if(!t.active||t.hit)continue;let n=this.holeBounds(o);if(Math.hypot(e-n.cx,i-n.cy)<n.r*1.2){t.active=!1,t.hit=!0,t.hitProgress=0,t.hideAt=0,this.score++,this.onScore?.(this.score);return}}}loop(e){if(!this.running)return;this.animFrameId=requestAnimationFrame(o=>this.loop(o));let i=e-this.lastTime;this.lastTime=e,this.update(e,i),this.render()}update(e,i){if(this.roundOver)return;if(this.timeLeft=Math.max(0,c-(e-this.roundStart)),this.timeLeft===0){this.roundOver=!0,this.onGameOver?.();return}this.trySpawnMole(e);for(let t of this.holes)t.active&&t.hideAt>0&&e>=t.hideAt&&(t.active=!1,t.hideAt=0);let o=i/200;for(let t of this.holes)t.active?t.showProgress=Math.min(1,t.showProgress+o):t.hit?(t.hitProgress=Math.min(1,t.hitProgress+o),t.hitProgress>=1&&(t.hit=!1)):t.showProgress=Math.max(0,t.showProgress-o)}render(){let{ctx:e,W:i,H:o,theme:t}=this;e.fillStyle=t.background,e.fillRect(0,0,i,o);let n=this.timeLeft/c;e.fillStyle=t.surface,e.fillRect(16,10,i-32,12),e.fillStyle=n>.3?t.primary:t.accent,e.beginPath(),e.roundRect(16,10,(i-32)*n,12,4),e.fill(),e.fillStyle=t.text,e.font="bold 18px monospace",e.textAlign="left",e.fillText(`${this.score}`,16,o-8),e.textAlign="right",e.globalAlpha=.5,e.font="12px system-ui",e.fillText(`${Math.ceil(this.timeLeft/1e3)}s`,i-16,o-8),e.globalAlpha=1;for(let s=0;s<this.holes.length;s++){let l=this.holes[s],r=this.holeBounds(s);e.fillStyle=t.surface,e.globalAlpha=.6,e.beginPath(),e.ellipse(r.cx,r.cy+r.r*.2,r.r,r.r*.4,0,0,Math.PI*2),e.fill(),e.globalAlpha=1,e.fillStyle=t.text,e.globalAlpha=.2,e.font=`bold ${r.r*.3}px monospace`,e.textAlign="center",e.fillText(`${s+1}`,r.cx,r.cy+r.r*.55),e.globalAlpha=1;let d=(l.active||l.hit)&&l.hit?1-l.hitProgress:l.showProgress;if(d<=0)continue;let f=(1-d)*r.r*1.5,a=r.cy-f;e.save(),e.beginPath(),e.ellipse(r.cx,r.cy+r.r*.25,r.r,r.r*.45,0,0,Math.PI*2),e.clip(),e.fillStyle=l.hit?t.accent:"#8B4513",e.beginPath(),e.arc(r.cx,a,r.r*.8,0,Math.PI*2),e.fill(),e.fillStyle="#fff",e.beginPath(),e.arc(r.cx-r.r*.28,a-r.r*.15,r.r*.15,0,Math.PI*2),e.fill(),e.beginPath(),e.arc(r.cx+r.r*.28,a-r.r*.15,r.r*.15,0,Math.PI*2),e.fill(),e.fillStyle="#333",e.beginPath(),e.arc(r.cx-r.r*.25,a-r.r*.15,r.r*.08,0,Math.PI*2),e.fill(),e.beginPath(),e.arc(r.cx+r.r*.25,a-r.r*.15,r.r*.08,0,Math.PI*2),e.fill(),e.fillStyle="#FFB6C1",e.beginPath(),e.arc(r.cx,a+r.r*.05,r.r*.1,0,Math.PI*2),e.fill(),l.hit&&(e.fillStyle="#FFD700",e.font=`bold ${r.r*.6}px system-ui`,e.textAlign="center",e.fillText("\u2713",r.cx,a-r.r*.6)),e.restore()}this.roundOver&&(e.fillStyle="rgba(0,0,0,0.55)",e.fillRect(0,0,i,o),e.fillStyle=t.text,e.font="bold 24px system-ui",e.textAlign="center",e.fillText(`Score: ${this.score}`,i/2,o/2),e.font="14px system-ui",e.globalAlpha=.7,e.fillText("Tap to play again",i/2,o/2+32),e.globalAlpha=1)}};export{g as WhackAMoleGame};
2
+ //# sourceMappingURL=whack-a-mole.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/theme.ts","../../src/games/whack-a-mole/index.ts"],"sourcesContent":["/**\r\n * Theme resolution — 3-level priority:\r\n * 1. Explicit theme prop (highest)\r\n * 2. CSS variables on :root (--lg-primary, etc.)\r\n * 3. prefers-color-scheme detection (lowest)\r\n */\r\n\r\nimport type { ThemeObject, ResolvedTheme } from './types.js'\r\n\r\nconst DARK_DEFAULTS: ResolvedTheme = {\r\n primary: '#6366F1',\r\n background: '#0F0F0F',\r\n surface: '#1A1A2E',\r\n text: '#F8F8F8',\r\n accent: '#E94560',\r\n}\r\n\r\nconst LIGHT_DEFAULTS: ResolvedTheme = {\r\n primary: '#4F46E5',\r\n background: '#FFFFFF',\r\n surface: '#F3F4F6',\r\n text: '#111827',\r\n accent: '#E94560',\r\n}\r\n\r\nconst CSS_VAR_MAP: Record<keyof ResolvedTheme, string> = {\r\n primary: '--lg-primary',\r\n background: '--lg-background',\r\n surface: '--lg-surface',\r\n text: '--lg-text',\r\n accent: '--lg-accent',\r\n}\r\n\r\n/**\r\n * Read a CSS variable from :root, returns null if not set.\r\n */\r\nfunction readCSSVar(varName: string): string | null {\r\n if (typeof window === 'undefined') return null\r\n const value = getComputedStyle(document.documentElement)\r\n .getPropertyValue(varName)\r\n .trim()\r\n return value || null\r\n}\r\n\r\n/**\r\n * Detect system color scheme preference.\r\n */\r\nfunction getSystemDefaults(): ResolvedTheme {\r\n if (typeof window === 'undefined') return DARK_DEFAULTS\r\n try {\r\n const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches\r\n return prefersDark ? DARK_DEFAULTS : LIGHT_DEFAULTS\r\n } catch {\r\n return DARK_DEFAULTS\r\n }\r\n}\r\n\r\n/**\r\n * Resolve the final theme from all sources.\r\n * Priority: explicit prop > CSS variables > system defaults.\r\n */\r\nexport function resolveTheme(theme?: ThemeObject): ResolvedTheme {\r\n const systemDefaults = getSystemDefaults()\r\n\r\n const resolved: ResolvedTheme = { ...systemDefaults }\r\n\r\n // Level 2: CSS variables\r\n for (const [key, cssVar] of Object.entries(CSS_VAR_MAP) as [keyof ResolvedTheme, string][]) {\r\n const cssValue = readCSSVar(cssVar)\r\n if (cssValue) {\r\n resolved[key] = cssValue\r\n }\r\n }\r\n\r\n // Level 1: Explicit theme prop\r\n if (theme) {\r\n for (const [key, value] of Object.entries(theme) as [keyof ThemeObject, string | undefined][]) {\r\n if (value) {\r\n resolved[key] = value\r\n }\r\n }\r\n }\r\n\r\n return resolved\r\n}\r\n\r\n/**\r\n * Apply resolved theme as CSS variables to a container element.\r\n * This allows games to reference CSS variables for dynamic theming.\r\n */\r\nexport function applyThemeToElement(element: HTMLElement, theme: ResolvedTheme): void {\r\n for (const [key, cssVar] of Object.entries(CSS_VAR_MAP) as [keyof ResolvedTheme, string][]) {\r\n element.style.setProperty(cssVar, theme[key])\r\n }\r\n}\r\n","/**\r\n * Whack-a-Mole — Touch-first, 3×3 grid, 20-second rounds.\r\n *\r\n * Mechanics:\r\n * - 3×3 holes, moles appear for 800–1500ms\r\n * - 20-second timed round, spawn rate accelerates from 900ms→500ms\r\n * - +1 per hit, high score is very shareable\r\n *\r\n * Controls: Mouse click / tap / keyboard 1-9\r\n * Bundle target: ~4 kB gzipped\r\n */\r\n\r\nimport type { GamePlugin, ThemeObject, ResolvedTheme } from '../../types.js'\r\nimport { resolveTheme } from '../../theme.js'\r\n\r\nconst GRID = 3\r\nconst ROUND_DURATION = 20_000\r\nconst HOLE_COUNT = GRID * GRID\r\nconst SPAWN_START_MS = 900\r\nconst SPAWN_END_MS = 500\r\nconst MOLE_MIN_STAY = 800\r\nconst MOLE_STAY_RANGE = 700\r\n\r\ninterface Hole {\r\n active: boolean\r\n showProgress: number\r\n hideAt: number // timestamp when mole auto-hides (0 = no mole)\r\n hit: boolean\r\n hitProgress: number\r\n}\r\n\r\nexport class WhackAMoleGame implements GamePlugin {\r\n readonly name = 'whack-a-mole'\r\n readonly bundleSize = 4_000\r\n\r\n private canvas!: HTMLCanvasElement\r\n private ctx!: CanvasRenderingContext2D\r\n private theme!: ResolvedTheme\r\n private W = 0; private H = 0\r\n\r\n private holes: Hole[] = []\r\n private score = 0\r\n private timeLeft = ROUND_DURATION\r\n private roundStart = 0\r\n private animFrameId: number | null = null\r\n private running = false\r\n private lastTime = 0\r\n private roundOver = false\r\n private nextSpawnTime = 0\r\n\r\n private boundClick: (e: MouseEvent) => void\r\n private boundTouch: (e: TouchEvent) => void\r\n private boundKeyDown: (e: KeyboardEvent) => void\r\n\r\n private onScore?: (s: number) => void\r\n private onGameOver?: () => void\r\n\r\n constructor(onScore?: (s: number) => void, onGameOver?: () => void) {\r\n this.onScore = onScore\r\n this.onGameOver = onGameOver\r\n this.boundClick = this.onClick.bind(this)\r\n this.boundTouch = this.onTouch.bind(this)\r\n this.boundKeyDown = this.onKeyDown.bind(this)\r\n }\r\n\r\n init(canvas: HTMLCanvasElement, theme: ThemeObject): void {\r\n this.canvas = canvas; this.ctx = canvas.getContext('2d')!; this.theme = resolveTheme(theme)\r\n\r\n canvas.setAttribute('aria-label', 'Whack-a-Mole game \\u2014 loading in background')\r\n canvas.setAttribute('role', 'img')\r\n\r\n const dpr = window.devicePixelRatio || 1; const rect = canvas.getBoundingClientRect()\r\n canvas.width = rect.width * dpr; canvas.height = rect.height * dpr\r\n this.ctx.scale(dpr, dpr); canvas.style.width = `${rect.width}px`; canvas.style.height = `${rect.height}px`\r\n this.W = rect.width; this.H = rect.height\r\n this.reset()\r\n }\r\n\r\n private reset(): void {\r\n this.holes = Array.from({ length: HOLE_COUNT }, () => ({\r\n active: false, showProgress: 0, hideAt: 0, hit: false, hitProgress: 0,\r\n }))\r\n this.score = 0; this.timeLeft = ROUND_DURATION; this.roundOver = false\r\n this.nextSpawnTime = 0\r\n }\r\n\r\n start(): void {\r\n this.running = true; this.roundStart = performance.now(); this.lastTime = performance.now()\r\n this.nextSpawnTime = performance.now()\r\n this.loop(performance.now())\r\n this.canvas.addEventListener('click', this.boundClick)\r\n this.canvas.addEventListener('touchend', this.boundTouch, { passive: true })\r\n document.addEventListener('keydown', this.boundKeyDown)\r\n }\r\n\r\n pause(): void {\r\n this.running = false\r\n if (this.animFrameId !== null) { cancelAnimationFrame(this.animFrameId); this.animFrameId = null }\r\n }\r\n\r\n resume(): void {\r\n if (!this.running) {\r\n this.running = true; this.lastTime = performance.now()\r\n this.nextSpawnTime = performance.now()\r\n this.loop(performance.now())\r\n }\r\n }\r\n\r\n destroy(): void {\r\n this.pause()\r\n this.canvas.removeEventListener('click', this.boundClick)\r\n this.canvas.removeEventListener('touchend', this.boundTouch)\r\n document.removeEventListener('keydown', this.boundKeyDown)\r\n }\r\n\r\n getScore(): number { return this.score }\r\n\r\n // ─── RAF-based Mole Spawner ───────────────────────────────────────────────\r\n\r\n private trySpawnMole(now: number): void {\r\n if (this.roundOver || now < this.nextSpawnTime) return\r\n\r\n const available = this.holes.map((h, i) => (!h.active && !h.hit ? i : -1)).filter(i => i >= 0)\r\n if (available.length > 0) {\r\n const idx = available[Math.floor(Math.random() * available.length)]!\r\n this.activateMole(idx, now)\r\n }\r\n\r\n // Spawn rate accelerates: 900ms → 500ms over the round\r\n const elapsed = now - this.roundStart\r\n const t = Math.min(1, elapsed / ROUND_DURATION)\r\n const interval = SPAWN_START_MS - t * (SPAWN_START_MS - SPAWN_END_MS)\r\n this.nextSpawnTime = now + interval\r\n }\r\n\r\n private activateMole(idx: number, now: number): void {\r\n const hole = this.holes[idx]!\r\n hole.active = true; hole.hit = false; hole.hitProgress = 0\r\n hole.hideAt = now + MOLE_MIN_STAY + Math.random() * MOLE_STAY_RANGE\r\n }\r\n\r\n private holeBounds(i: number): { cx: number; cy: number; r: number } {\r\n const cols = GRID; const rows = GRID\r\n const padX = 24; const padY = 24\r\n const cellW = (this.W - padX * 2) / cols; const cellH = (this.H - padY * 2 - 30) / rows\r\n const col = i % cols; const row = Math.floor(i / cols)\r\n const cx = padX + col * cellW + cellW / 2\r\n const cy = padY + row * cellH + cellH / 2 + 30\r\n const r = Math.min(cellW, cellH) * 0.38\r\n return { cx, cy, r }\r\n }\r\n\r\n // ─── Input ────────────────────────────────────────────────────────────────\r\n\r\n private onClick(e: MouseEvent): void { const r = this.canvas.getBoundingClientRect(); this.tryHit(e.clientX - r.left, e.clientY - r.top) }\r\n private onTouch(e: TouchEvent): void { const t = e.changedTouches[0]; if (!t) return; const r = this.canvas.getBoundingClientRect(); this.tryHit(t.clientX - r.left, t.clientY - r.top) }\r\n\r\n private onKeyDown(e: KeyboardEvent): void {\r\n // Keys 1-9 map to holes 0-8 (top-left to bottom-right)\r\n const num = parseInt(e.key, 10)\r\n if (num >= 1 && num <= 9) {\r\n e.preventDefault()\r\n if (this.roundOver) { this.reset(); this.nextSpawnTime = performance.now(); return }\r\n const idx = num - 1\r\n const hole = this.holes[idx]\r\n if (hole && hole.active && !hole.hit) {\r\n hole.active = false; hole.hit = true; hole.hitProgress = 0; hole.hideAt = 0\r\n this.score++; this.onScore?.(this.score)\r\n }\r\n }\r\n }\r\n\r\n private tryHit(px: number, py: number): void {\r\n if (this.roundOver) { this.reset(); this.nextSpawnTime = performance.now(); return }\r\n for (let i = 0; i < this.holes.length; i++) {\r\n const hole = this.holes[i]!; if (!hole.active || hole.hit) continue\r\n const b = this.holeBounds(i)\r\n const dist = Math.hypot(px - b.cx, py - b.cy)\r\n if (dist < b.r * 1.2) {\r\n hole.active = false; hole.hit = true; hole.hitProgress = 0; hole.hideAt = 0\r\n this.score++; this.onScore?.(this.score)\r\n return\r\n }\r\n }\r\n }\r\n\r\n // ─── Loop ─────────────────────────────────────────────────────────────────\r\n\r\n private loop(now: number): void {\r\n if (!this.running) return\r\n this.animFrameId = requestAnimationFrame(t => this.loop(t))\r\n const dt = now - this.lastTime; this.lastTime = now\r\n this.update(now, dt); this.render()\r\n }\r\n\r\n private update(now: number, dt: number): void {\r\n if (this.roundOver) return\r\n\r\n this.timeLeft = Math.max(0, ROUND_DURATION - (now - this.roundStart))\r\n if (this.timeLeft === 0) { this.roundOver = true; this.onGameOver?.(); return }\r\n\r\n // RAF-based spawner (replaces setInterval)\r\n this.trySpawnMole(now)\r\n\r\n // Auto-hide moles whose time is up\r\n for (const hole of this.holes) {\r\n if (hole.active && hole.hideAt > 0 && now >= hole.hideAt) {\r\n hole.active = false; hole.hideAt = 0\r\n }\r\n }\r\n\r\n const step = dt / 200\r\n for (const hole of this.holes) {\r\n if (hole.active) hole.showProgress = Math.min(1, hole.showProgress + step)\r\n else if (hole.hit) { hole.hitProgress = Math.min(1, hole.hitProgress + step); if (hole.hitProgress >= 1) hole.hit = false }\r\n else hole.showProgress = Math.max(0, hole.showProgress - step)\r\n }\r\n }\r\n\r\n private render(): void {\r\n const { ctx, W, H, theme } = this\r\n ctx.fillStyle = theme.background; ctx.fillRect(0, 0, W, H)\r\n\r\n // Timer bar\r\n const progress = this.timeLeft / ROUND_DURATION\r\n ctx.fillStyle = theme.surface; ctx.fillRect(16, 10, W - 32, 12)\r\n ctx.fillStyle = progress > 0.3 ? theme.primary : theme.accent\r\n ctx.beginPath(); ctx.roundRect(16, 10, (W - 32) * progress, 12, 4); ctx.fill()\r\n\r\n // Score\r\n ctx.fillStyle = theme.text; ctx.font = 'bold 18px monospace'; ctx.textAlign = 'left'\r\n ctx.fillText(`${this.score}`, 16, H - 8)\r\n ctx.textAlign = 'right'; ctx.globalAlpha = 0.5; ctx.font = '12px system-ui'\r\n ctx.fillText(`${Math.ceil(this.timeLeft / 1000)}s`, W - 16, H - 8); ctx.globalAlpha = 1\r\n\r\n // Holes & moles\r\n for (let i = 0; i < this.holes.length; i++) {\r\n const hole = this.holes[i]!; const b = this.holeBounds(i)\r\n\r\n // Hole (always visible)\r\n ctx.fillStyle = theme.surface; ctx.globalAlpha = 0.6\r\n ctx.beginPath(); ctx.ellipse(b.cx, b.cy + b.r * 0.2, b.r, b.r * 0.4, 0, 0, Math.PI * 2); ctx.fill()\r\n ctx.globalAlpha = 1\r\n\r\n // Hole number label for keyboard hint\r\n ctx.fillStyle = theme.text; ctx.globalAlpha = 0.2; ctx.font = `bold ${b.r * 0.3}px monospace`\r\n ctx.textAlign = 'center'; ctx.fillText(`${i + 1}`, b.cx, b.cy + b.r * 0.55); ctx.globalAlpha = 1\r\n\r\n // Mole (pops up via showProgress or flies away on hit)\r\n const prog = hole.active || hole.hit ? (hole.hit ? 1 - hole.hitProgress : hole.showProgress) : hole.showProgress\r\n if (prog <= 0) continue\r\n\r\n const offsetY = (1 - prog) * b.r * 1.5\r\n const cy = b.cy - offsetY\r\n\r\n ctx.save()\r\n ctx.beginPath()\r\n ctx.ellipse(b.cx, b.cy + b.r * 0.25, b.r, b.r * 0.45, 0, 0, Math.PI * 2)\r\n ctx.clip()\r\n\r\n // Mole body\r\n ctx.fillStyle = hole.hit ? theme.accent : '#8B4513'\r\n ctx.beginPath(); ctx.arc(b.cx, cy, b.r * 0.8, 0, Math.PI * 2); ctx.fill()\r\n // Eyes\r\n ctx.fillStyle = '#fff'\r\n ctx.beginPath(); ctx.arc(b.cx - b.r * 0.28, cy - b.r * 0.15, b.r * 0.15, 0, Math.PI * 2); ctx.fill()\r\n ctx.beginPath(); ctx.arc(b.cx + b.r * 0.28, cy - b.r * 0.15, b.r * 0.15, 0, Math.PI * 2); ctx.fill()\r\n ctx.fillStyle = '#333'\r\n ctx.beginPath(); ctx.arc(b.cx - b.r * 0.25, cy - b.r * 0.15, b.r * 0.08, 0, Math.PI * 2); ctx.fill()\r\n ctx.beginPath(); ctx.arc(b.cx + b.r * 0.25, cy - b.r * 0.15, b.r * 0.08, 0, Math.PI * 2); ctx.fill()\r\n // Nose\r\n ctx.fillStyle = '#FFB6C1'\r\n ctx.beginPath(); ctx.arc(b.cx, cy + b.r * 0.05, b.r * 0.1, 0, Math.PI * 2); ctx.fill()\r\n\r\n if (hole.hit) {\r\n ctx.fillStyle = '#FFD700'; ctx.font = `bold ${b.r * 0.6}px system-ui`\r\n ctx.textAlign = 'center'; ctx.fillText('\\u2713', b.cx, cy - b.r * 0.6)\r\n }\r\n\r\n ctx.restore()\r\n }\r\n\r\n // Round over overlay\r\n if (this.roundOver) {\r\n ctx.fillStyle = 'rgba(0,0,0,0.55)'; ctx.fillRect(0, 0, W, H)\r\n ctx.fillStyle = theme.text; ctx.font = 'bold 24px system-ui'; ctx.textAlign = 'center'\r\n ctx.fillText(`Score: ${this.score}`, W / 2, H / 2)\r\n ctx.font = '14px system-ui'; ctx.globalAlpha = 0.7\r\n ctx.fillText('Tap to play again', W / 2, H / 2 + 32); ctx.globalAlpha = 1\r\n }\r\n }\r\n}\r\n"],"mappings":"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","names":["DARK_DEFAULTS","LIGHT_DEFAULTS","CSS_VAR_MAP","readCSSVar","varName","getSystemDefaults","resolveTheme","theme","resolved","key","cssVar","cssValue","value","GRID","ROUND_DURATION","HOLE_COUNT","SPAWN_START_MS","SPAWN_END_MS","MOLE_MIN_STAY","MOLE_STAY_RANGE","WhackAMoleGame","onScore","onGameOver","canvas","theme","resolveTheme","dpr","rect","now","available","h","i","idx","elapsed","interval","hole","cols","rows","padX","padY","cellW","cellH","col","row","cx","cy","r","t","num","px","py","b","dt","step","ctx","W","H","progress","prog","offsetY"]}
@@ -0,0 +1,2 @@
1
+ var o={primary:"#6366F1",background:"#0F0F0F",surface:"#1A1A2E",text:"#F8F8F8",accent:"#E94560"},c={primary:"#4F46E5",background:"#FFFFFF",surface:"#F3F4F6",text:"#111827",accent:"#E94560"},d={primary:"--lg-primary",background:"--lg-background",surface:"--lg-surface",text:"--lg-text",accent:"--lg-accent"};function m(i){return typeof window>"u"?null:getComputedStyle(document.documentElement).getPropertyValue(i).trim()||null}function u(){if(typeof window>"u")return o;try{return window.matchMedia("(prefers-color-scheme: dark)").matches?o:c}catch{return o}}function l(i){let r={...u()};for(let[n,e]of Object.entries(d)){let s=m(e);s&&(r[n]=s)}if(i)for(let[n,e]of Object.entries(i))e&&(r[n]=e);return r}var a=class{constructor(t,r){this._onScore=t;this._onGameOver=r}name="wordle-lite";bundleSize=7e3;canvas;ctx;theme;animFrameId=null;running=!1;score=0;init(t,r){this.canvas=t,this.ctx=t.getContext("2d"),this.theme=l(r);let n=window.devicePixelRatio||1,e=t.getBoundingClientRect();t.width=e.width*n,t.height=e.height*n,this.ctx.scale(n,n),t.style.width=`${e.width}px`,t.style.height=`${e.height}px`}start(){this.running=!0,this.render()}pause(){this.running=!1,this.animFrameId!==null&&(cancelAnimationFrame(this.animFrameId),this.animFrameId=null)}resume(){this.running||(this.running=!0,this.render())}destroy(){this.pause()}getScore(){return this.score}render(){let t=this.canvas.getBoundingClientRect(),r=t.width,n=t.height,{ctx:e,theme:s}=this;e.fillStyle=s.background,e.fillRect(0,0,r,n),e.fillStyle=s.text,e.font="bold 18px system-ui",e.textAlign="center",e.textBaseline="middle",e.fillText("Wordle-lite",r/2,n/2-16),e.font="14px system-ui",e.globalAlpha=.6,e.fillText("Coming soon!",r/2,n/2+16),e.globalAlpha=1}};export{a as WordleLiteGame};
2
+ //# sourceMappingURL=wordle-lite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/theme.ts","../../src/games/wordle-lite/index.ts"],"sourcesContent":["/**\r\n * Theme resolution — 3-level priority:\r\n * 1. Explicit theme prop (highest)\r\n * 2. CSS variables on :root (--lg-primary, etc.)\r\n * 3. prefers-color-scheme detection (lowest)\r\n */\r\n\r\nimport type { ThemeObject, ResolvedTheme } from './types.js'\r\n\r\nconst DARK_DEFAULTS: ResolvedTheme = {\r\n primary: '#6366F1',\r\n background: '#0F0F0F',\r\n surface: '#1A1A2E',\r\n text: '#F8F8F8',\r\n accent: '#E94560',\r\n}\r\n\r\nconst LIGHT_DEFAULTS: ResolvedTheme = {\r\n primary: '#4F46E5',\r\n background: '#FFFFFF',\r\n surface: '#F3F4F6',\r\n text: '#111827',\r\n accent: '#E94560',\r\n}\r\n\r\nconst CSS_VAR_MAP: Record<keyof ResolvedTheme, string> = {\r\n primary: '--lg-primary',\r\n background: '--lg-background',\r\n surface: '--lg-surface',\r\n text: '--lg-text',\r\n accent: '--lg-accent',\r\n}\r\n\r\n/**\r\n * Read a CSS variable from :root, returns null if not set.\r\n */\r\nfunction readCSSVar(varName: string): string | null {\r\n if (typeof window === 'undefined') return null\r\n const value = getComputedStyle(document.documentElement)\r\n .getPropertyValue(varName)\r\n .trim()\r\n return value || null\r\n}\r\n\r\n/**\r\n * Detect system color scheme preference.\r\n */\r\nfunction getSystemDefaults(): ResolvedTheme {\r\n if (typeof window === 'undefined') return DARK_DEFAULTS\r\n try {\r\n const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches\r\n return prefersDark ? DARK_DEFAULTS : LIGHT_DEFAULTS\r\n } catch {\r\n return DARK_DEFAULTS\r\n }\r\n}\r\n\r\n/**\r\n * Resolve the final theme from all sources.\r\n * Priority: explicit prop > CSS variables > system defaults.\r\n */\r\nexport function resolveTheme(theme?: ThemeObject): ResolvedTheme {\r\n const systemDefaults = getSystemDefaults()\r\n\r\n const resolved: ResolvedTheme = { ...systemDefaults }\r\n\r\n // Level 2: CSS variables\r\n for (const [key, cssVar] of Object.entries(CSS_VAR_MAP) as [keyof ResolvedTheme, string][]) {\r\n const cssValue = readCSSVar(cssVar)\r\n if (cssValue) {\r\n resolved[key] = cssValue\r\n }\r\n }\r\n\r\n // Level 1: Explicit theme prop\r\n if (theme) {\r\n for (const [key, value] of Object.entries(theme) as [keyof ThemeObject, string | undefined][]) {\r\n if (value) {\r\n resolved[key] = value\r\n }\r\n }\r\n }\r\n\r\n return resolved\r\n}\r\n\r\n/**\r\n * Apply resolved theme as CSS variables to a container element.\r\n * This allows games to reference CSS variables for dynamic theming.\r\n */\r\nexport function applyThemeToElement(element: HTMLElement, theme: ResolvedTheme): void {\r\n for (const [key, cssVar] of Object.entries(CSS_VAR_MAP) as [keyof ResolvedTheme, string][]) {\r\n element.style.setProperty(cssVar, theme[key])\r\n }\r\n}\r\n","/**\r\n * Wordle-lite — Stub implementation.\r\n * Full implementation planned for Phase 3.\r\n * Bundle target: ~7 kB gzipped\r\n */\r\n\r\nimport type { GamePlugin, ThemeObject, ResolvedTheme } from '../../types.js'\r\nimport { resolveTheme } from '../../theme.js'\r\n\r\nexport class WordleLiteGame implements GamePlugin {\r\n readonly name = 'wordle-lite'\r\n readonly bundleSize = 7_000\r\n\r\n private canvas!: HTMLCanvasElement\r\n private ctx!: CanvasRenderingContext2D\r\n private theme!: ResolvedTheme\r\n private animFrameId: number | null = null\r\n private running = false\r\n private score = 0\r\n\r\n constructor(private _onScore?: (s: number) => void, private _onGameOver?: () => void) {}\r\n\r\n init(canvas: HTMLCanvasElement, theme: ThemeObject): void {\r\n this.canvas = canvas\r\n this.ctx = canvas.getContext('2d')!\r\n this.theme = resolveTheme(theme)\r\n const dpr = window.devicePixelRatio || 1\r\n const rect = canvas.getBoundingClientRect()\r\n canvas.width = rect.width * dpr\r\n canvas.height = rect.height * dpr\r\n this.ctx.scale(dpr, dpr)\r\n canvas.style.width = `${rect.width}px`\r\n canvas.style.height = `${rect.height}px`\r\n }\r\n\r\n start(): void { this.running = true; this.render() }\r\n pause(): void { this.running = false; if (this.animFrameId !== null) { cancelAnimationFrame(this.animFrameId); this.animFrameId = null } }\r\n resume(): void { if (!this.running) { this.running = true; this.render() } }\r\n destroy(): void { this.pause() }\r\n getScore(): number { return this.score }\r\n\r\n private render(): void {\r\n const rect = this.canvas.getBoundingClientRect()\r\n const W = rect.width, H = rect.height\r\n const { ctx, theme } = this\r\n ctx.fillStyle = theme.background; ctx.fillRect(0, 0, W, H)\r\n ctx.fillStyle = theme.text; ctx.font = 'bold 18px system-ui'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'\r\n ctx.fillText('Wordle-lite', W / 2, H / 2 - 16)\r\n ctx.font = '14px system-ui'; ctx.globalAlpha = 0.6\r\n ctx.fillText('Coming soon!', W / 2, H / 2 + 16); ctx.globalAlpha = 1\r\n }\r\n}\r\n"],"mappings":"AASA,IAAMA,EAA+B,CACnC,QAAS,UACT,WAAY,UACZ,QAAS,UACT,KAAM,UACN,OAAQ,SACV,EAEMC,EAAgC,CACpC,QAAS,UACT,WAAY,UACZ,QAAS,UACT,KAAM,UACN,OAAQ,SACV,EAEMC,EAAmD,CACvD,QAAS,eACT,WAAY,kBACZ,QAAS,eACT,KAAM,YACN,OAAQ,aACV,EAKA,SAASC,EAAWC,EAAgC,CAClD,OAAI,OAAO,OAAW,IAAoB,KAC5B,iBAAiB,SAAS,eAAe,EACpD,iBAAiBA,CAAO,EACxB,KAAK,GACQ,IAClB,CAKA,SAASC,GAAmC,CAC1C,GAAI,OAAO,OAAW,IAAa,OAAOL,EAC1C,GAAI,CAEF,OADoB,OAAO,WAAW,8BAA8B,EAAE,QACjDA,EAAgBC,CACvC,MAAQ,CACN,OAAOD,CACT,CACF,CAMO,SAASM,EAAaC,EAAoC,CAG/D,IAAMC,EAA0B,CAAE,GAFXH,EAAkB,CAEW,EAGpD,OAAW,CAACI,EAAKC,CAAM,IAAK,OAAO,QAAQR,CAAW,EAAsC,CAC1F,IAAMS,EAAWR,EAAWO,CAAM,EAC9BC,IACFH,EAASC,CAAG,EAAIE,EAEpB,CAGA,GAAIJ,EACF,OAAW,CAACE,EAAKG,CAAK,IAAK,OAAO,QAAQL,CAAK,EACzCK,IACFJ,EAASC,CAAG,EAAIG,GAKtB,OAAOJ,CACT,CC3EO,IAAMK,EAAN,KAA2C,CAWhD,YAAoBC,EAAwCC,EAA0B,CAAlE,cAAAD,EAAwC,iBAAAC,CAA2B,CAV9E,KAAO,cACP,WAAa,IAEd,OACA,IACA,MACA,YAA6B,KAC7B,QAAU,GACV,MAAQ,EAIhB,KAAKC,EAA2BC,EAA0B,CACxD,KAAK,OAASD,EACd,KAAK,IAAMA,EAAO,WAAW,IAAI,EACjC,KAAK,MAAQE,EAAaD,CAAK,EAC/B,IAAME,EAAM,OAAO,kBAAoB,EACjCC,EAAOJ,EAAO,sBAAsB,EAC1CA,EAAO,MAAQI,EAAK,MAAQD,EAC5BH,EAAO,OAASI,EAAK,OAASD,EAC9B,KAAK,IAAI,MAAMA,EAAKA,CAAG,EACvBH,EAAO,MAAM,MAAQ,GAAGI,EAAK,KAAK,KAClCJ,EAAO,MAAM,OAAS,GAAGI,EAAK,MAAM,IACtC,CAEA,OAAc,CAAE,KAAK,QAAU,GAAM,KAAK,OAAO,CAAE,CACnD,OAAc,CAAE,KAAK,QAAU,GAAW,KAAK,cAAgB,OAAQ,qBAAqB,KAAK,WAAW,EAAG,KAAK,YAAc,KAAO,CACzI,QAAe,CAAO,KAAK,UAAW,KAAK,QAAU,GAAM,KAAK,OAAO,EAAI,CAC3E,SAAgB,CAAE,KAAK,MAAM,CAAE,CAC/B,UAAmB,CAAE,OAAO,KAAK,KAAM,CAE/B,QAAe,CACrB,IAAMA,EAAO,KAAK,OAAO,sBAAsB,EACzCC,EAAID,EAAK,MAAOE,EAAIF,EAAK,OACzB,CAAE,IAAAG,EAAK,MAAAN,CAAM,EAAI,KACvBM,EAAI,UAAYN,EAAM,WAAYM,EAAI,SAAS,EAAG,EAAGF,EAAGC,CAAC,EACzDC,EAAI,UAAYN,EAAM,KAAMM,EAAI,KAAO,sBAAuBA,EAAI,UAAY,SAAUA,EAAI,aAAe,SAC3GA,EAAI,SAAS,cAAeF,EAAI,EAAGC,EAAI,EAAI,EAAE,EAC7CC,EAAI,KAAO,iBAAkBA,EAAI,YAAc,GAC/CA,EAAI,SAAS,eAAgBF,EAAI,EAAGC,EAAI,EAAI,EAAE,EAAGC,EAAI,YAAc,CACrE,CACF","names":["DARK_DEFAULTS","LIGHT_DEFAULTS","CSS_VAR_MAP","readCSSVar","varName","getSystemDefaults","resolveTheme","theme","resolved","key","cssVar","cssValue","value","WordleLiteGame","_onScore","_onGameOver","canvas","theme","resolveTheme","dpr","rect","W","H","ctx"]}
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ 'use strict';var Te=Object.defineProperty;var p=(a,e)=>()=>(a&&(e=a(a=0)),e);var g=(a,e)=>{for(var t in e)Te(a,t,{get:e[t],enumerable:true});};function Se(a){return typeof window>"u"?null:getComputedStyle(document.documentElement).getPropertyValue(a).trim()||null}function Ee(){if(typeof window>"u")return I;try{return window.matchMedia("(prefers-color-scheme: dark)").matches?I:we}catch{return I}}function d(a){let t={...Ee()};for(let[s,i]of Object.entries(V)){let n=Se(i);n&&(t[s]=n);}if(a)for(let[s,i]of Object.entries(a))i&&(t[s]=i);return t}function G(a,e){for(let[t,s]of Object.entries(V))a.style.setProperty(s,e[t]);}var I,we,V,m=p(()=>{I={primary:"#6366F1",background:"#0F0F0F",surface:"#1A1A2E",text:"#F8F8F8",accent:"#E94560"},we={primary:"#4F46E5",background:"#FFFFFF",surface:"#F3F4F6",text:"#111827",accent:"#E94560"},V={primary:"--lg-primary",background:"--lg-background",surface:"--lg-surface",text:"--lg-text",accent:"--lg-accent"};});var Ae;exports.Dpad=void 0;var F=p(()=>{Ae={up:"\u25B2",down:"\u25BC",left:"\u25C0",right:"\u25B6"},exports.Dpad=class{constructor(e){this.options=e;this.boundClick=this.handleClick.bind(this),this.boundTouch=this.handleTouch.bind(this);}overlay=null;boundClick;boundTouch;mount(){if(!("ontouchstart"in window)||this.overlay)return;let{parent:e,primaryColor:t,textColor:s,showFire:i}=this.options;getComputedStyle(e).position==="static"&&(e.style.position="relative");let r=document.createElement("div");r.setAttribute("data-lg-dpad",""),r.style.cssText=["position:absolute","bottom:12px","left:50%","transform:translateX(-50%)","display:grid","grid-template-columns:44px 44px 44px","grid-template-rows:44px 44px 44px","gap:2px","pointer-events:none","z-index:10"].join(";");let l=[null,"up",null,"left",i?"fire":null,"right",null,"down",null];for(let o of l){let h=document.createElement("button");o&&o!=="fire"?(h.textContent=Ae[o],h.setAttribute("aria-label",o),h.dataset.dir=o,h.style.cssText=this.buttonStyle(t,s)):o==="fire"?(h.textContent="\u25CF",h.setAttribute("aria-label","fire"),h.dataset.action="fire",h.style.cssText=this.buttonStyle(t,s)):h.style.cssText="visibility:hidden;",r.appendChild(h);}r.addEventListener("click",this.boundClick),r.addEventListener("touchstart",this.boundTouch,{passive:true}),e.appendChild(r),this.overlay=r;}destroy(){this.overlay&&(this.overlay.removeEventListener("click",this.boundClick),this.overlay.removeEventListener("touchstart",this.boundTouch),this.overlay.remove(),this.overlay=null);}buttonStyle(e,t){return ["pointer-events:auto","width:44px","height:44px","border:none","border-radius:8px",`background:${e}55`,`color:${t}`,"font-size:16px","display:flex","align-items:center","justify-content:center","touch-action:manipulation","cursor:pointer","-webkit-tap-highlight-color:transparent"].join(";")}handleClick(e){let t=e.target.closest("[data-dir],[data-action]");t&&(t.dataset.dir?this.options.onDirection(t.dataset.dir):t.dataset.action==="fire"&&this.options.onFire?.());}handleTouch(e){let t=e.target.closest("[data-dir],[data-action]");t&&(t.dataset.dir?this.options.onDirection(t.dataset.dir):t.dataset.action==="fire"&&this.options.onFire?.());}};});var q={};g(q,{SnakeGame:()=>j});var y,Z,De,Pe,j,J=p(()=>{m();F();y=20,Z=150,De=5,Pe=30,j=class{name="snake";bundleSize=4e3;canvas;ctx;theme;snake=[];food={x:0,y:0};direction="right";nextDirection="right";score=0;tickMs=Z;lastTick=0;animFrameId=null;running=false;cellSize=0;boundKeyDown;boundTouchStart;boundTouchEnd;touchStartX=0;touchStartY=0;isTouchDevice=false;dpad=null;onScoreCallback;onGameOverCallback;constructor(e,t){this.onScoreCallback=e,this.onGameOverCallback=t,this.boundKeyDown=this.handleKeyDown.bind(this),this.boundTouchStart=this.handleTouchStart.bind(this),this.boundTouchEnd=this.handleTouchEnd.bind(this);}init(e,t){this.canvas=e,this.ctx=e.getContext("2d"),this.theme=d(t),e.setAttribute("aria-label","Snake game \u2014 loading in background"),e.setAttribute("role","img");let s=window.devicePixelRatio||1,i=e.getBoundingClientRect();e.width=i.width*s,e.height=i.height*s,this.ctx.scale(s,s),e.style.width=`${i.width}px`,e.style.height=`${i.height}px`,this.cellSize=Math.floor(i.width/y),this.isTouchDevice="ontouchstart"in window,this.reset();}reset(){let e=Math.floor(y/2);this.snake=[{x:e,y:e},{x:e-1,y:e},{x:e-2,y:e}],this.direction="right",this.nextDirection="right",this.score=0,this.tickMs=Z,this.placeFood();}placeFood(){let e=new Set(this.snake.map(s=>`${s.x},${s.y}`)),t;do t={x:Math.floor(Math.random()*y),y:Math.floor(Math.random()*y)};while(e.has(`${t.x},${t.y}`));this.food=t;}start(){this.running=true,this.lastTick=performance.now(),this.loop(performance.now()),this.attachEventListeners(),this.isTouchDevice&&this.mountDpad();}pause(){this.running=false,this.animFrameId!==null&&(cancelAnimationFrame(this.animFrameId),this.animFrameId=null);}resume(){this.running||(this.running=true,this.lastTick=performance.now(),this.loop(performance.now()));}destroy(){this.pause(),this.removeEventListeners(),this.dpad?.destroy(),this.dpad=null;}getScore(){return this.score}loop(e){this.running&&(this.animFrameId=requestAnimationFrame(t=>this.loop(t)),e-this.lastTick>=this.tickMs&&(this.lastTick=e,this.tick()),this.render());}tick(){this.direction=this.nextDirection;let e=this.snake[0],t={x:(e.x+(this.direction==="right"?1:this.direction==="left"?-1:0)+y)%y,y:(e.y+(this.direction==="down"?1:this.direction==="up"?-1:0)+y)%y};if(this.snake.some(s=>s.x===t.x&&s.y===t.y)){this.onGameOverCallback?.(),this.reset();return}this.snake.unshift(t),t.x===this.food.x&&t.y===this.food.y?(this.score+=10,this.onScoreCallback?.(this.score),this.score%50===0&&(this.tickMs=Math.max(60,this.tickMs-De)),this.placeFood()):this.snake.pop();}render(){let{ctx:e,cellSize:t,theme:s}=this,i=this.canvas.getBoundingClientRect(),n=i.width,r=i.height;e.fillStyle=s.background,e.fillRect(0,0,n,r),e.strokeStyle=s.surface,e.lineWidth=.5,e.globalAlpha=.3;for(let h=0;h<=n;h+=t)e.beginPath(),e.moveTo(h,0),e.lineTo(h,r),e.stroke();for(let h=0;h<=r;h+=t)e.beginPath(),e.moveTo(0,h),e.lineTo(n,h),e.stroke();e.globalAlpha=1;let l=this.food.x*t+t/2,o=this.food.y*t+t/2;e.fillStyle=s.accent,e.beginPath(),e.arc(l,o,t*.4,0,Math.PI*2),e.fill(),this.snake.forEach((h,v)=>{let c=h.x*t,f=h.y*t,k=1;if(v===0)e.fillStyle=s.primary;else {let xe=1-v/this.snake.length*.4;e.globalAlpha=xe,e.fillStyle=s.primary;}let ve=3,fe=c+k,ge=f+k,ye=t-k*2,be=t-k*2;e.beginPath(),e.roundRect(fe,ge,ye,be,ve),e.fill(),e.globalAlpha=1;}),e.fillStyle=s.text,e.font=`bold ${Math.round(t*.7)}px monospace`,e.textAlign="right",e.fillText(`${this.score}`,n-8,t);}attachEventListeners(){document.addEventListener("keydown",this.boundKeyDown),this.canvas.addEventListener("touchstart",this.boundTouchStart,{passive:true}),this.canvas.addEventListener("touchend",this.boundTouchEnd,{passive:true});}removeEventListeners(){document.removeEventListener("keydown",this.boundKeyDown),this.canvas.removeEventListener("touchstart",this.boundTouchStart),this.canvas.removeEventListener("touchend",this.boundTouchEnd);}handleKeyDown(e){let s={ArrowUp:"up",ArrowDown:"down",ArrowLeft:"left",ArrowRight:"right"}[e.key];if(!s)return;({up:"down",down:"up",left:"right",right:"left"})[s]!==this.direction&&(this.nextDirection=s,e.preventDefault());}handleTouchStart(e){let t=e.touches[0];t&&(this.touchStartX=t.clientX,this.touchStartY=t.clientY);}handleTouchEnd(e){let t=e.changedTouches[0];if(!t)return;let s=t.clientX-this.touchStartX,i=t.clientY-this.touchStartY,n=Math.abs(s),r=Math.abs(i);if(Math.max(n,r)<Pe)return;let l;n>r?l=s>0?"right":"left":l=i>0?"down":"up",{up:"down",down:"up",left:"right",right:"left"}[l]!==this.direction&&(this.nextDirection=l);}mountDpad(){let e=this.canvas.parentElement;e&&(this.dpad=new exports.Dpad({parent:e,primaryColor:this.theme.primary,textColor:this.theme.text,onDirection:t=>{({up:"down",down:"up",left:"right",right:"left"})[t]!==this.direction&&(this.nextDirection=t);}}),this.dpad.mount());}};});var Q={};g(Q,{BrickBreakerGame:()=>B});var B,ee=p(()=>{m();B=class{constructor(e,t){this.onScore=e;this._onGameOver=t;}name="brick-breaker";bundleSize=6e3;canvas;ctx;theme;animFrameId=null;running=false;score=0;init(e,t){this.canvas=e,this.ctx=e.getContext("2d"),this.theme=d(t);let s=window.devicePixelRatio||1,i=e.getBoundingClientRect();e.width=i.width*s,e.height=i.height*s,this.ctx.scale(s,s),e.style.width=`${i.width}px`,e.style.height=`${i.height}px`;}start(){this.running=true,this.render();}pause(){this.running=false,this.animFrameId!==null&&(cancelAnimationFrame(this.animFrameId),this.animFrameId=null);}resume(){this.running||(this.running=true,this.render());}destroy(){this.pause();}getScore(){return this.score}render(){let e=this.canvas.getBoundingClientRect(),t=e.width,s=e.height,{ctx:i,theme:n}=this;i.fillStyle=n.background,i.fillRect(0,0,t,s),i.fillStyle=n.text,i.font="bold 18px system-ui",i.textAlign="center",i.textBaseline="middle",i.fillText("Brick Breaker",t/2,s/2-16),i.font="14px system-ui",i.globalAlpha=.6,i.fillText("Coming soon!",t/2,s/2+16),i.globalAlpha=1;}};});var ie={};g(ie,{FlappyGame:()=>_});var Me,te,b,H,Re,D,u,_,se=p(()=>{m();Me=.4,te=-7,b=52,H=150,Re=2.2,D=80,u=14,_=class{name="flappy";bundleSize=5e3;canvas;ctx;theme;W=0;H=0;birdY=0;birdVY=0;pipes=[];score=0;personalBest=0;animFrameId=null;running=false;dead=false;frameCount=0;lastTime=0;boundJump;boundKey;boundTouch;onScore;onGameOver;constructor(e,t){this.onScore=e,this.onGameOver=t,this.boundJump=this.jump.bind(this),this.boundKey=s=>{s.code==="Space"&&(s.preventDefault(),this.jump());},this.boundTouch=this.jump.bind(this);}init(e,t){this.canvas=e,this.ctx=e.getContext("2d"),this.theme=d(t);let s=window.devicePixelRatio||1,i=e.getBoundingClientRect();e.width=i.width*s,e.height=i.height*s,this.ctx.scale(s,s),e.style.width=`${i.width}px`,e.style.height=`${i.height}px`,this.W=i.width,this.H=i.height,e.setAttribute("aria-label","Flappy Bird game \u2014 loading in background"),e.setAttribute("role","img"),this.reset();}reset(){this.birdY=this.H/2,this.birdVY=0,this.pipes=[],this.score=0,this.dead=false,this.frameCount=0,this.spawnPipe(this.W+100);}spawnPipe(e){let s=this.H-H-60,i=60+Math.random()*(s-60);this.pipes.push({x:e??this.W+50,topHeight:i,passed:false});}start(){this.running=true,this.lastTime=performance.now(),this.loop(performance.now()),this.canvas.addEventListener("click",this.boundJump),document.addEventListener("keydown",this.boundKey),this.canvas.addEventListener("touchstart",this.boundTouch,{passive:true});}pause(){this.running=false,this.animFrameId!==null&&(cancelAnimationFrame(this.animFrameId),this.animFrameId=null);}resume(){this.running||(this.running=true,this.lastTime=performance.now(),this.loop(performance.now()));}destroy(){this.pause(),this.canvas.removeEventListener("click",this.boundJump),document.removeEventListener("keydown",this.boundKey),this.canvas.removeEventListener("touchstart",this.boundTouch);}getScore(){return this.score}jump(){if(this.dead){this.reset();return}this.birdVY=te;}loop(e){if(!this.running)return;this.animFrameId=requestAnimationFrame(s=>this.loop(s));let t=Math.min((e-this.lastTime)/16.67,3);this.lastTime=e,this.dead||this.update(t),this.render();}update(e){if(this.frameCount++,this.birdVY+=Me*e,this.birdY+=this.birdVY*e,this.birdY+u>=this.H||this.birdY-u<=0){this.die();return}this.frameCount%90===0&&this.spawnPipe();for(let t of this.pipes){t.x-=Re*e,!t.passed&&t.x+b<D&&(t.passed=true,this.score++,this.score>this.personalBest&&(this.personalBest=this.score),this.onScore?.(this.score));let s=D+u>t.x&&D-u<t.x+b,i=this.birdY-u<t.topHeight||this.birdY+u>t.topHeight+H;if(s&&i){this.die();return}}this.pipes=this.pipes.filter(t=>t.x+b>-10);}die(){this.dead=true,this.birdVY=te*.5,this.onGameOver?.();}render(){let{ctx:e,W:t,H:s,theme:i}=this;e.fillStyle=i.background,e.fillRect(0,0,t,s);for(let n of this.pipes){e.fillStyle=i.primary,e.fillRect(n.x-4,n.topHeight-24,b+8,24),e.beginPath(),e.roundRect(n.x,0,b,n.topHeight-6,[0,0,6,6]),e.fill();let r=n.topHeight+H;e.fillRect(n.x-4,r,b+8,24),e.beginPath(),e.roundRect(n.x,r+6,b,s-r,[6,6,0,0]),e.fill();}e.save(),e.translate(D,this.birdY),e.rotate(Math.min(Math.max(this.birdVY*.05,-0.5),1)),this.dead&&(e.globalAlpha=.6),e.fillStyle="#FFD93D",e.beginPath(),e.arc(0,0,u,0,Math.PI*2),e.fill(),e.fillStyle="#fff",e.beginPath(),e.arc(5,-4,5,0,Math.PI*2),e.fill(),e.fillStyle="#333",e.beginPath(),e.arc(6,-4,2.5,0,Math.PI*2),e.fill(),e.fillStyle="#FF6B35",e.beginPath(),e.moveTo(u-2,0),e.lineTo(u+8,-3),e.lineTo(u+8,3),e.closePath(),e.fill(),e.restore(),e.fillStyle=i.text,e.font="bold 28px monospace",e.textAlign="center",e.fillText(String(this.score),t/2,44),this.dead&&(e.fillStyle="rgba(0,0,0,0.45)",e.fillRect(0,0,t,s),e.fillStyle=i.text,e.font="bold 20px system-ui",e.textAlign="center",e.fillText("Tap to retry",t/2,s/2),e.font="14px system-ui",e.globalAlpha=.7,e.fillText(`Score: ${this.score} Best: ${this.personalBest}`,t/2,s/2+28),e.globalAlpha=1);}};});var ne={};g(ne,{Game2048:()=>$});var $,oe=p(()=>{m();$=class{constructor(e,t){this._onScore=e;this._onGameOver=t;}name="2048";bundleSize=5e3;canvas;ctx;theme;animFrameId=null;running=false;score=0;init(e,t){this.canvas=e,this.ctx=e.getContext("2d"),this.theme=d(t);let s=window.devicePixelRatio||1,i=e.getBoundingClientRect();e.width=i.width*s,e.height=i.height*s,this.ctx.scale(s,s),e.style.width=`${i.width}px`,e.style.height=`${i.height}px`;}start(){this.running=true,this.render();}pause(){this.running=false,this.animFrameId!==null&&(cancelAnimationFrame(this.animFrameId),this.animFrameId=null);}resume(){this.running||(this.running=true,this.render());}destroy(){this.pause();}getScore(){return this.score}render(){let e=this.canvas.getBoundingClientRect(),t=e.width,s=e.height,{ctx:i,theme:n}=this;i.fillStyle=n.background,i.fillRect(0,0,t,s),i.fillStyle=n.text,i.font="bold 18px system-ui",i.textAlign="center",i.textBaseline="middle",i.fillText("2048",t/2,s/2-16),i.font="14px system-ui",i.globalAlpha=.6,i.fillText("Coming soon!",t/2,s/2+16),i.globalAlpha=1;}};});var re={};g(re,{WordleLiteGame:()=>N});var N,ae=p(()=>{m();N=class{constructor(e,t){this._onScore=e;this._onGameOver=t;}name="wordle-lite";bundleSize=7e3;canvas;ctx;theme;animFrameId=null;running=false;score=0;init(e,t){this.canvas=e,this.ctx=e.getContext("2d"),this.theme=d(t);let s=window.devicePixelRatio||1,i=e.getBoundingClientRect();e.width=i.width*s,e.height=i.height*s,this.ctx.scale(s,s),e.style.width=`${i.width}px`,e.style.height=`${i.height}px`;}start(){this.running=true,this.render();}pause(){this.running=false,this.animFrameId!==null&&(cancelAnimationFrame(this.animFrameId),this.animFrameId=null);}resume(){this.running||(this.running=true,this.render());}destroy(){this.pause();}getScore(){return this.score}render(){let e=this.canvas.getBoundingClientRect(),t=e.width,s=e.height,{ctx:i,theme:n}=this;i.fillStyle=n.background,i.fillRect(0,0,t,s),i.fillStyle=n.text,i.font="bold 18px system-ui",i.textAlign="center",i.textBaseline="middle",i.fillText("Wordle-lite",t/2,s/2-16),i.font="14px system-ui",i.globalAlpha=.6,i.fillText("Coming soon!",t/2,s/2+16),i.globalAlpha=1;}};});var le={};g(le,{AsteroidsGame:()=>W});var W,he=p(()=>{m();W=class{constructor(e,t){this._onScore=e;this._onGameOver=t;}name="asteroids";bundleSize=8e3;canvas;ctx;theme;animFrameId=null;running=false;score=0;init(e,t){this.canvas=e,this.ctx=e.getContext("2d"),this.theme=d(t);let s=window.devicePixelRatio||1,i=e.getBoundingClientRect();e.width=i.width*s,e.height=i.height*s,this.ctx.scale(s,s),e.style.width=`${i.width}px`,e.style.height=`${i.height}px`;}start(){this.running=true,this.render();}pause(){this.running=false,this.animFrameId!==null&&(cancelAnimationFrame(this.animFrameId),this.animFrameId=null);}resume(){this.running||(this.running=true,this.render());}destroy(){this.pause();}getScore(){return this.score}render(){let e=this.canvas.getBoundingClientRect(),t=e.width,s=e.height,{ctx:i,theme:n}=this;i.fillStyle=n.background,i.fillRect(0,0,t,s),i.fillStyle=n.text,i.font="bold 18px system-ui",i.textAlign="center",i.textBaseline="middle",i.fillText("Asteroids",t/2,s/2-16),i.font="14px system-ui",i.globalAlpha=.6,i.fillText("Coming soon!",t/2,s/2+16),i.globalAlpha=1;}};});var de={};g(de,{MemoryCardsGame:()=>Y});var P,M,K,Y,ce=p(()=>{m();P=["\u{1F3AE}","\u{1F680}","\u{1F3AF}","\u{1F3B2}","\u{1F31F}","\u{1F3B8}","\u{1F984}","\u{1F355}"],M=4,K=300,Y=class{name="memory-cards";canvas;ctx;theme;W=0;H=0;cards=[];flippedIdxs=[];locked=false;score=0;matchCount=0;startTime=0;selectedIdx=0;animFrameId=null;running=false;lastTime=0;boundClick;boundTouch;boundKeyDown;onScore;onGameOver;constructor(e,t){this.onScore=e,this.onGameOver=t,this.boundClick=this.onClick.bind(this),this.boundTouch=this.onTouch.bind(this),this.boundKeyDown=this.onKeyDown.bind(this);}init(e,t){this.canvas=e,this.ctx=e.getContext("2d"),this.theme=d(t),e.setAttribute("aria-label","Memory Cards game \u2014 loading in background"),e.setAttribute("role","img"),e.setAttribute("tabindex","0");let s=window.devicePixelRatio||1,i=e.getBoundingClientRect();e.width=i.width*s,e.height=i.height*s,this.ctx.scale(s,s),e.style.width=`${i.width}px`,e.style.height=`${i.height}px`,this.W=i.width,this.H=i.height,this.reset();}reset(){let e=[...P,...P];for(let t=e.length-1;t>0;t--){let s=Math.floor(Math.random()*(t+1));[e[t],e[s]]=[e[s],e[t]];}this.cards=e.map((t,s)=>({id:s,emoji:t,flipped:false,matched:false,flipProgress:0,pulsePhase:0})),this.flippedIdxs=[],this.locked=false,this.score=0,this.matchCount=0,this.startTime=Date.now(),this.selectedIdx=0;}start(){this.running=true,this.lastTime=performance.now(),this.loop(performance.now()),this.canvas.addEventListener("click",this.boundClick),this.canvas.addEventListener("touchend",this.boundTouch,{passive:true}),document.addEventListener("keydown",this.boundKeyDown);}pause(){this.running=false,this.animFrameId!==null&&(cancelAnimationFrame(this.animFrameId),this.animFrameId=null);}resume(){this.running||(this.running=true,this.lastTime=performance.now(),this.loop(performance.now()));}destroy(){this.pause(),this.canvas.removeEventListener("click",this.boundClick),this.canvas.removeEventListener("touchend",this.boundTouch),document.removeEventListener("keydown",this.boundKeyDown);}getScore(){return this.score}bounds(e){let s=M,i=M,n=(this.W-8*(s+1))/s,r=(this.H-8*(i+1))/i;return {x:8+e%s*(n+8),y:8+Math.floor(e/s)*(r+8),w:n,h:r}}cardAt(e,t){for(let s=0;s<this.cards.length;s++){let i=this.bounds(s);if(e>=i.x&&e<=i.x+i.w&&t>=i.y&&t<=i.y+i.h)return s}return -1}onClick(e){let t=this.canvas.getBoundingClientRect();this.tryFlip(e.clientX-t.left,e.clientY-t.top);}onTouch(e){let t=e.changedTouches[0];if(!t)return;let s=this.canvas.getBoundingClientRect();this.tryFlip(t.clientX-s.left,t.clientY-s.top);}onKeyDown(e){let t=this.cards.length;switch(e.key){case "Tab":e.preventDefault(),this.selectedIdx=(this.selectedIdx+(e.shiftKey?t-1:1))%t;break;case "ArrowRight":e.preventDefault(),this.selectedIdx=(this.selectedIdx+1)%t;break;case "ArrowLeft":e.preventDefault(),this.selectedIdx=(this.selectedIdx+t-1)%t;break;case "ArrowDown":e.preventDefault(),this.selectedIdx=(this.selectedIdx+M)%t;break;case "ArrowUp":e.preventDefault(),this.selectedIdx=(this.selectedIdx+t-M)%t;break;case "Enter":case " ":e.preventDefault(),this.tryFlipByIdx(this.selectedIdx);break}}tryFlipByIdx(e){if(this.locked)return;let t=this.cards[e];t.flipped||t.matched||(t.flipped=true,this.flippedIdxs.push(e),this.checkMatch());}tryFlip(e,t){if(this.locked)return;let s=this.cardAt(e,t);if(s===-1)return;let i=this.cards[s];i.flipped||i.matched||(i.flipped=true,this.flippedIdxs.push(s),this.selectedIdx=s,this.checkMatch());}checkMatch(){if(this.flippedIdxs.length!==2)return;this.locked=true;let[e,t]=this.flippedIdxs;this.cards[e].emoji===this.cards[t].emoji?setTimeout(()=>{if(this.cards[e].matched=true,this.cards[t].matched=true,this.matchCount++,this.score+=10,this.onScore?.(this.score),this.flippedIdxs=[],this.locked=false,this.matchCount===P.length){let s=(Date.now()-this.startTime)/1e3,i=Math.max(0,Math.round((60-s)*5));this.score+=i,this.onScore?.(this.score),this.onGameOver?.(),setTimeout(()=>this.reset(),2e3);}},K+100):setTimeout(()=>{this.cards[e].flipped=false,this.cards[t].flipped=false,this.flippedIdxs=[],this.locked=false;},K+600);}loop(e){if(!this.running)return;this.animFrameId=requestAnimationFrame(s=>this.loop(s));let t=(e-this.lastTime)/K;this.lastTime=e;for(let s of this.cards)s.flipped&&s.flipProgress<1?s.flipProgress=Math.min(1,s.flipProgress+t):!s.flipped&&s.flipProgress>0&&(s.flipProgress=Math.max(0,s.flipProgress-t)),s.matched&&(s.pulsePhase=(s.pulsePhase+t*.15)%(Math.PI*2));this.render();}render(){let{ctx:e,W:t,H:s,theme:i}=this;e.fillStyle=i.background,e.fillRect(0,0,t,s);for(let n=0;n<this.cards.length;n++){let r=this.cards[n],l=this.bounds(n),o=Math.abs(Math.cos(r.flipProgress*Math.PI)),h=l.x+l.w/2,v=l.y+l.h/2;if(e.save(),e.translate(h,v),e.scale(o,1),e.translate(-l.w/2,-l.h/2),e.beginPath(),e.roundRect(0,0,l.w,l.h,6),r.flipProgress>.5){if(r.matched){let c=.2+Math.sin(r.pulsePhase)*.1;e.fillStyle=i.primary+"33",e.fill(),e.strokeStyle=i.primary,e.lineWidth=3,e.globalAlpha=.6+c,e.stroke(),e.globalAlpha=1,e.shadowColor=i.primary,e.shadowBlur=8,e.stroke(),e.shadowBlur=0;}else e.fillStyle=i.surface,e.fill();e.font=`${Math.min(l.w,l.h)*.45}px serif`,e.textAlign="center",e.textBaseline="middle",e.fillText(r.emoji,l.w/2,l.h/2);}else {e.fillStyle=i.primary+"99",e.fill(),e.strokeStyle=i.primary,e.lineWidth=1,e.globalAlpha=.25;for(let c=0;c<3;c++){let f=4+c*5;e.strokeRect(f,f,l.w-f*2,l.h-f*2);}e.globalAlpha=1;}n===this.selectedIdx&&(e.strokeStyle=i.text,e.lineWidth=2,e.setLineDash([4,3]),e.strokeRect(0,0,l.w,l.h),e.setLineDash([])),e.restore();}e.fillStyle=i.text,e.globalAlpha=.5,e.font="11px system-ui",e.textAlign="right",e.fillText(`${this.matchCount}/${P.length} \xB7 ${this.score}pts`,t-6,s-4),e.globalAlpha=1;}};});var pe={};g(pe,{WhackAMoleGame:()=>z});var R,w,Ie,me,Ge,Oe,Le,z,ue=p(()=>{m();R=3,w=2e4,Ie=R*R,me=900,Ge=500,Oe=800,Le=700,z=class{name="whack-a-mole";bundleSize=4e3;canvas;ctx;theme;W=0;H=0;holes=[];score=0;timeLeft=w;roundStart=0;animFrameId=null;running=false;lastTime=0;roundOver=false;nextSpawnTime=0;boundClick;boundTouch;boundKeyDown;onScore;onGameOver;constructor(e,t){this.onScore=e,this.onGameOver=t,this.boundClick=this.onClick.bind(this),this.boundTouch=this.onTouch.bind(this),this.boundKeyDown=this.onKeyDown.bind(this);}init(e,t){this.canvas=e,this.ctx=e.getContext("2d"),this.theme=d(t),e.setAttribute("aria-label","Whack-a-Mole game \u2014 loading in background"),e.setAttribute("role","img");let s=window.devicePixelRatio||1,i=e.getBoundingClientRect();e.width=i.width*s,e.height=i.height*s,this.ctx.scale(s,s),e.style.width=`${i.width}px`,e.style.height=`${i.height}px`,this.W=i.width,this.H=i.height,this.reset();}reset(){this.holes=Array.from({length:Ie},()=>({active:false,showProgress:0,hideAt:0,hit:false,hitProgress:0})),this.score=0,this.timeLeft=w,this.roundOver=false,this.nextSpawnTime=0;}start(){this.running=true,this.roundStart=performance.now(),this.lastTime=performance.now(),this.nextSpawnTime=performance.now(),this.loop(performance.now()),this.canvas.addEventListener("click",this.boundClick),this.canvas.addEventListener("touchend",this.boundTouch,{passive:true}),document.addEventListener("keydown",this.boundKeyDown);}pause(){this.running=false,this.animFrameId!==null&&(cancelAnimationFrame(this.animFrameId),this.animFrameId=null);}resume(){this.running||(this.running=true,this.lastTime=performance.now(),this.nextSpawnTime=performance.now(),this.loop(performance.now()));}destroy(){this.pause(),this.canvas.removeEventListener("click",this.boundClick),this.canvas.removeEventListener("touchend",this.boundTouch),document.removeEventListener("keydown",this.boundKeyDown);}getScore(){return this.score}trySpawnMole(e){if(this.roundOver||e<this.nextSpawnTime)return;let t=this.holes.map((r,l)=>!r.active&&!r.hit?l:-1).filter(r=>r>=0);if(t.length>0){let r=t[Math.floor(Math.random()*t.length)];this.activateMole(r,e);}let s=e-this.roundStart,i=Math.min(1,s/w),n=me-i*(me-Ge);this.nextSpawnTime=e+n;}activateMole(e,t){let s=this.holes[e];s.active=true,s.hit=false,s.hitProgress=0,s.hideAt=t+Oe+Math.random()*Le;}holeBounds(e){let t=R,s=R,i=24,n=24,r=(this.W-i*2)/t,l=(this.H-n*2-30)/s,o=e%t,h=Math.floor(e/t),v=i+o*r+r/2,c=n+h*l+l/2+30,f=Math.min(r,l)*.38;return {cx:v,cy:c,r:f}}onClick(e){let t=this.canvas.getBoundingClientRect();this.tryHit(e.clientX-t.left,e.clientY-t.top);}onTouch(e){let t=e.changedTouches[0];if(!t)return;let s=this.canvas.getBoundingClientRect();this.tryHit(t.clientX-s.left,t.clientY-s.top);}onKeyDown(e){let t=parseInt(e.key,10);if(t>=1&&t<=9){if(e.preventDefault(),this.roundOver){this.reset(),this.nextSpawnTime=performance.now();return}let s=t-1,i=this.holes[s];i&&i.active&&!i.hit&&(i.active=false,i.hit=true,i.hitProgress=0,i.hideAt=0,this.score++,this.onScore?.(this.score));}}tryHit(e,t){if(this.roundOver){this.reset(),this.nextSpawnTime=performance.now();return}for(let s=0;s<this.holes.length;s++){let i=this.holes[s];if(!i.active||i.hit)continue;let n=this.holeBounds(s);if(Math.hypot(e-n.cx,t-n.cy)<n.r*1.2){i.active=false,i.hit=true,i.hitProgress=0,i.hideAt=0,this.score++,this.onScore?.(this.score);return}}}loop(e){if(!this.running)return;this.animFrameId=requestAnimationFrame(s=>this.loop(s));let t=e-this.lastTime;this.lastTime=e,this.update(e,t),this.render();}update(e,t){if(this.roundOver)return;if(this.timeLeft=Math.max(0,w-(e-this.roundStart)),this.timeLeft===0){this.roundOver=true,this.onGameOver?.();return}this.trySpawnMole(e);for(let i of this.holes)i.active&&i.hideAt>0&&e>=i.hideAt&&(i.active=false,i.hideAt=0);let s=t/200;for(let i of this.holes)i.active?i.showProgress=Math.min(1,i.showProgress+s):i.hit?(i.hitProgress=Math.min(1,i.hitProgress+s),i.hitProgress>=1&&(i.hit=false)):i.showProgress=Math.max(0,i.showProgress-s);}render(){let{ctx:e,W:t,H:s,theme:i}=this;e.fillStyle=i.background,e.fillRect(0,0,t,s);let n=this.timeLeft/w;e.fillStyle=i.surface,e.fillRect(16,10,t-32,12),e.fillStyle=n>.3?i.primary:i.accent,e.beginPath(),e.roundRect(16,10,(t-32)*n,12,4),e.fill(),e.fillStyle=i.text,e.font="bold 18px monospace",e.textAlign="left",e.fillText(`${this.score}`,16,s-8),e.textAlign="right",e.globalAlpha=.5,e.font="12px system-ui",e.fillText(`${Math.ceil(this.timeLeft/1e3)}s`,t-16,s-8),e.globalAlpha=1;for(let r=0;r<this.holes.length;r++){let l=this.holes[r],o=this.holeBounds(r);e.fillStyle=i.surface,e.globalAlpha=.6,e.beginPath(),e.ellipse(o.cx,o.cy+o.r*.2,o.r,o.r*.4,0,0,Math.PI*2),e.fill(),e.globalAlpha=1,e.fillStyle=i.text,e.globalAlpha=.2,e.font=`bold ${o.r*.3}px monospace`,e.textAlign="center",e.fillText(`${r+1}`,o.cx,o.cy+o.r*.55),e.globalAlpha=1;let h=(l.active||l.hit)&&l.hit?1-l.hitProgress:l.showProgress;if(h<=0)continue;let v=(1-h)*o.r*1.5,c=o.cy-v;e.save(),e.beginPath(),e.ellipse(o.cx,o.cy+o.r*.25,o.r,o.r*.45,0,0,Math.PI*2),e.clip(),e.fillStyle=l.hit?i.accent:"#8B4513",e.beginPath(),e.arc(o.cx,c,o.r*.8,0,Math.PI*2),e.fill(),e.fillStyle="#fff",e.beginPath(),e.arc(o.cx-o.r*.28,c-o.r*.15,o.r*.15,0,Math.PI*2),e.fill(),e.beginPath(),e.arc(o.cx+o.r*.28,c-o.r*.15,o.r*.15,0,Math.PI*2),e.fill(),e.fillStyle="#333",e.beginPath(),e.arc(o.cx-o.r*.25,c-o.r*.15,o.r*.08,0,Math.PI*2),e.fill(),e.beginPath(),e.arc(o.cx+o.r*.25,c-o.r*.15,o.r*.08,0,Math.PI*2),e.fill(),e.fillStyle="#FFB6C1",e.beginPath(),e.arc(o.cx,c+o.r*.05,o.r*.1,0,Math.PI*2),e.fill(),l.hit&&(e.fillStyle="#FFD700",e.font=`bold ${o.r*.6}px system-ui`,e.textAlign="center",e.fillText("\u2713",o.cx,c-o.r*.6)),e.restore();}this.roundOver&&(e.fillStyle="rgba(0,0,0,0.55)",e.fillRect(0,0,t,s),e.fillStyle=i.text,e.font="bold 24px system-ui",e.textAlign="center",e.fillText(`Score: ${this.score}`,t/2,s/2),e.font="14px system-ui",e.globalAlpha=.7,e.fillText("Tap to play again",t/2,s/2+32),e.globalAlpha=1);}};});m();var x=class{constructor(e){this.options=e;}delayTimer=null;minDisplayTimer=null;showTime=null;loadingCompletedWhileDelaying=false;isVisible=false;destroyed=false;start(){this.destroyed||(this.loadingCompletedWhileDelaying=false,this.delayTimer=setTimeout(()=>{this.delayTimer=null,!this.destroyed&&(this.loadingCompletedWhileDelaying||(this.isVisible=true,this.showTime=Date.now(),this.options.onShow()));},this.options.delay));}end(){if(this.destroyed)return;if(this.delayTimer!==null){clearTimeout(this.delayTimer),this.delayTimer=null,this.loadingCompletedWhileDelaying=true;return}if(!this.isVisible)return;let e=Date.now()-(this.showTime??0),t=this.options.minDisplay-e;t<=0?this.triggerExit():this.minDisplayTimer=setTimeout(()=>{this.destroyed||this.triggerExit();},t);}triggerExit(){this.isVisible=false,this.options.onExit();}destroy(){this.destroyed=true,this.delayTimer!==null&&(clearTimeout(this.delayTimer),this.delayTimer=null),this.minDisplayTimer!==null&&(clearTimeout(this.minDisplayTimer),this.minDisplayTimer=null);}};var O="loading-games:scores";function C(){if(typeof window>"u"||!window.localStorage)return {};try{let a=localStorage.getItem(O);return a?JSON.parse(a):{}}catch{return {}}}function X(a){if(!(typeof window>"u"||!window.localStorage))try{localStorage.setItem(O,JSON.stringify(a));}catch{}}function U(a,e){return e?`${e}:${a}`:a}function A(a,e){let t=C(),s=U(a,e);return t[s]?.personalBest??0}function L(a,e,t){let s=C(),i=U(a,t),n=s[i],r=e>(n?.personalBest??0);return s[i]={personalBest:r?e:n?.personalBest??0,lastPlayed:new Date().toISOString(),totalGames:(n?.totalGames??0)+1},X(s),r}function ke(a){let e=C();return a?Object.fromEntries(Object.entries(e).filter(([t])=>t.startsWith(`${a}:`))):e}function Ce(a){if(!a){typeof window<"u"&&window.localStorage&&localStorage.removeItem(O);return}let e=C(),t=`${a}:`,s=Object.fromEntries(Object.entries(e).filter(([i])=>!i.startsWith(t)));X(s);}var Fe={sm:{width:280,height:220},md:{width:400,height:320},lg:{width:560,height:440},full:{width:0,height:0}},S=class{hasEverActivated=false;host;callbacks;options={};state="idle";canvas=null;overlay=null;skipLink=null;currentGame=null;delayController=null;_gameStartTime=0;_currentScore=0;boundVisibilityChange;constructor(e,t){this.host=e,this.callbacks=t,this.boundVisibilityChange=this.handleVisibilityChange.bind(this),document.addEventListener("visibilitychange",this.boundVisibilityChange);}activate(e){this.hasEverActivated=true,this.options=e,this.state==="idle"&&(this.setState("delaying"),this.delayController=new x({delay:e.delay??800,minDisplay:e.minDisplay??0,onShow:()=>this.showGame(),onExit:()=>this.exitGame()}),this.delayController.start());}deactivate(e){if(this.options.exitAnimation=e.animation,this.state==="delaying"){this.delayController?.end(),this.setState("idle");return}this.state==="playing"&&(this.currentGame?.pause(),this.delayController?.end());}errorDeactivate(){this.delayController?.destroy(),this.delayController=null,this.currentGame?.destroy(),this.currentGame=null,this.teardownDOM(),this.setState("idle");}updateOptions(e){this.options={...this.options,...e};}destroy(){this.delayController?.destroy(),this.currentGame?.destroy(),this.currentGame=null,this.teardownDOM(),document.removeEventListener("visibilitychange",this.boundVisibilityChange);}async showGame(){this.setState("loading-game");let e=this.resolveGameName(this.options.game??"random");try{let t=await this.loadGameChunk(e);this.setupDOM();let s=d(this.options.theme);if(this.overlay&&G(this.overlay,s),this.canvas&&this.canvas.setAttribute("aria-label",`${e} game \u2014 loading in background`),window.matchMedia("(prefers-reduced-motion: reduce)").matches){this.showReducedMotionFallback(s);return}this.currentGame=new t(i=>this.handleScoreUpdate(i,e),()=>this.handleGameOverEvent(e)),await this.currentGame.init(this.canvas,this.options.theme??{}),this.setState("playing"),this._gameStartTime=Date.now(),this._currentScore=0,this.currentGame.start(),this.showToast("Loading in the background \u2014 play while you wait!");}catch(t){this.callbacks.onError(t instanceof Error?t:new Error(String(t))),this.teardownDOM(),this.setState("idle");}}async exitGame(){(this.state==="playing"||this.state==="min-display")&&(this.currentGame?.pause(),this.showCompletionOverlay(),await this.sleep(1500)),this.setState("exiting"),await this.animateExit(this.options.exitAnimation??"fade"),this.currentGame?.destroy(),this.currentGame=null,this.teardownDOM(),this.setState("idle"),this.callbacks.onComplete();}setupDOM(){let e=this.options.size??"md",t=Fe[e];this.overlay=document.createElement("div"),this.overlay.setAttribute("data-lg-overlay",""),Object.assign(this.overlay.style,{position:e==="full"?"fixed":"relative",inset:e==="full"?"0":"auto",width:e==="full"?"100%":`${t.width}px`,height:e==="full"?"100%":`${t.height}px`,display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",zIndex:e==="full"?"9999":"auto",opacity:"0",transition:"opacity 300ms ease, transform 300ms ease",transform:"scale(0.97)",overflow:"hidden",borderRadius:e==="full"?"0":"12px"}),this.skipLink=document.createElement("a"),this.skipLink.href="#",this.skipLink.textContent="Skip game, wait for loading",this.skipLink.setAttribute("data-lg-skip",""),Object.assign(this.skipLink.style,{position:"absolute",top:"-9999px",left:"-9999px"}),this.skipLink.addEventListener("focus",()=>{Object.assign(this.skipLink.style,{top:"8px",left:"8px"});}),this.skipLink.addEventListener("blur",()=>{Object.assign(this.skipLink.style,{top:"-9999px",left:"-9999px"});}),this.skipLink.addEventListener("click",s=>{s.preventDefault(),this.deactivate({animation:"none"});}),this.canvas=document.createElement("canvas"),this.canvas.setAttribute("role","application"),this.canvas.setAttribute("tabindex","0"),Object.assign(this.canvas.style,{width:"100%",height:"100%",display:"block"}),this.overlay.appendChild(this.skipLink),this.overlay.appendChild(this.canvas),this.host.appendChild(this.overlay),requestAnimationFrame(()=>{requestAnimationFrame(()=>{this.overlay&&(this.overlay.style.opacity="1",this.overlay.style.transform="scale(1)");});});}teardownDOM(){this.overlay?.remove(),this.overlay=null,this.canvas=null,this.skipLink=null;}async animateExit(e){!this.overlay||e==="none"||(e==="fade"?this.overlay.style.opacity="0":e==="slide"&&(this.overlay.style.transform="translateY(-20px)",this.overlay.style.opacity="0"),await this.sleep(350));}showToast(e){let t=document.createElement("div");t.textContent=e,Object.assign(t.style,{position:"absolute",bottom:"12px",left:"50%",transform:"translateX(-50%)",background:"rgba(0,0,0,0.7)",color:"#fff",padding:"6px 12px",borderRadius:"6px",fontSize:"12px",whiteSpace:"nowrap",opacity:"0",transition:"opacity 300ms ease",pointerEvents:"none"}),this.overlay?.appendChild(t),requestAnimationFrame(()=>{t.style.opacity="1";}),setTimeout(()=>{t.style.opacity="0",setTimeout(()=>t.remove(),300);},2e3);}showCompletionOverlay(){let e=document.createElement("div");e.textContent="\u2713 Done! Loading complete.",Object.assign(e.style,{position:"absolute",inset:"0",display:"flex",alignItems:"center",justifyContent:"center",background:"rgba(0,0,0,0.6)",color:"#fff",fontSize:"18px",fontWeight:"bold",fontFamily:"system-ui, sans-serif",opacity:"0",transition:"opacity 300ms ease"}),this.overlay?.appendChild(e),requestAnimationFrame(()=>{e.style.opacity="1";});}showReducedMotionFallback(e){if(!this.canvas)return;let t=this.canvas.getContext("2d"),s=this.canvas.width,i=this.canvas.height;t.fillStyle=e.background,t.fillRect(0,0,s,i),t.fillStyle=e.primary,t.font="16px system-ui",t.textAlign="center",t.fillText("Loading\u2026",s/2,i/2),this.setState("playing");}handleGameOverEvent(e){let t=Date.now()-this._gameStartTime,s=A(e,this.options.namespace);this.callbacks.onGameOver({game:e,finalScore:this._currentScore,duration:t,isNewRecord:this._currentScore>s});}handleScoreUpdate(e,t){this._currentScore=e;let s=A(t,this.options.namespace),i=false;this.options.saveScores!==false&&e>s&&(i=L(t,e,this.options.namespace)),this.callbacks.onScore({game:t,current:e,personalBest:Math.max(e,s),isNewRecord:i}),i&&this.showNewRecordBadge();}showNewRecordBadge(){let e=document.createElement("div");e.textContent="\u{1F3C6} New Record!",Object.assign(e.style,{position:"absolute",top:"12px",left:"50%",transform:"translateX(-50%) scale(0.8)",background:"gold",color:"#000",padding:"4px 10px",borderRadius:"6px",fontSize:"12px",fontWeight:"bold",fontFamily:"system-ui, sans-serif",opacity:"0",transition:"opacity 300ms ease, transform 300ms ease",pointerEvents:"none"}),this.overlay?.appendChild(e),requestAnimationFrame(()=>{e.style.opacity="1",e.style.transform="translateX(-50%) scale(1)";}),setTimeout(()=>{e.style.opacity="0",setTimeout(()=>e.remove(),300);},2500);}async loadGameChunk(e){switch(e){case "snake":return (await Promise.resolve().then(()=>(J(),q))).SnakeGame;case "brick-breaker":return (await Promise.resolve().then(()=>(ee(),Q))).BrickBreakerGame;case "flappy":return (await Promise.resolve().then(()=>(se(),ie))).FlappyGame;case "2048":return (await Promise.resolve().then(()=>(oe(),ne))).Game2048;case "wordle-lite":return (await Promise.resolve().then(()=>(ae(),re))).WordleLiteGame;case "asteroids":return (await Promise.resolve().then(()=>(he(),le))).AsteroidsGame;case "memory-cards":return (await Promise.resolve().then(()=>(ce(),de))).MemoryCardsGame;case "whack-a-mole":return (await Promise.resolve().then(()=>(ue(),pe))).WhackAMoleGame;default:throw new Error(`Unknown game: ${e}`)}}resolveGameName(e){if(e!=="random")return e;let t=["snake","brick-breaker","flappy","2048","wordle-lite","asteroids","memory-cards","whack-a-mole"];return t[Math.floor(Math.random()*t.length)]}setState(e){this.state=e;}handleVisibilityChange(){this.currentGame&&(document.visibilityState==="hidden"?this.currentGame.pause():this.state==="playing"&&this.currentGame.resume());}sleep(e){return new Promise(t=>setTimeout(t,e))}};var E=class extends HTMLElement{static observedAttributes=["game","active","size","delay","min-display","exit-animation","save-scores","namespace"];controller=null;_theme={};connectedCallback(){this.setAttribute("role","region"),this.setAttribute("aria-label","Loading game"),this.controller=new S(this,{onScore:e=>this.dispatchEvent(new CustomEvent("lg:score",{detail:e,bubbles:true})),onGameOver:e=>this.dispatchEvent(new CustomEvent("lg:gameover",{detail:e,bubbles:true})),onComplete:()=>this.dispatchEvent(new CustomEvent("lg:complete",{bubbles:true})),onError:e=>{this.controller?.errorDeactivate(),this.dispatchEvent(new CustomEvent("lg:error",{detail:e,bubbles:true}));}}),this.getAttribute("active")==="true"&&this.controller.activate(this.buildOptions());}disconnectedCallback(){this.controller?.destroy(),this.controller=null;}attributeChangedCallback(e,t,s){t===s||!this.controller||(e==="active"?s==="true"?this.controller.activate(this.buildOptions()):this.controller.deactivate({animation:this.getExitAnimation()}):this.controller.updateOptions(this.buildOptions()));}set theme(e){this._theme=e,this.controller?.updateOptions(this.buildOptions());}get theme(){return this._theme}start(){this.setAttribute("active","true");}stop(){this.removeAttribute("active");}setTheme(e){this.theme=e;}buildOptions(){let e=this.getAttribute("game")??"random",t=this.getAttribute("size")??"md",s=parseInt(this.getAttribute("delay")??"800",10),i=parseInt(this.getAttribute("min-display")??"0",10),n=this.getAttribute("exit-animation")??"fade",r=this.getAttribute("save-scores")!=="false",l=this.getAttribute("namespace")??void 0;return {game:e,size:t,delay:s,minDisplay:i,exitAnimation:n,saveScores:r,namespace:l,theme:this._theme}}getExitAnimation(){return this.getAttribute("exit-animation")??"fade"}};m();F();typeof customElements<"u"&&!customElements.get("loading-game")&&customElements.define("loading-game",E);exports.DelayController=x;exports.GameController=S;exports.LoadingGameElement=E;exports.applyThemeToElement=G;exports.clearScores=Ce;exports.getAllScores=ke;exports.getPersonalBest=A;exports.resolveTheme=d;exports.saveScore=L;//# sourceMappingURL=index.cjs.map
2
+ //# sourceMappingURL=index.cjs.map