From 46ddb879ee31488c75d490ec4ee04a44d9e470ee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Mar 2026 19:25:25 +0000 Subject: [PATCH 1/4] Initial plan From 89b1677c092068aa49a0ba0c0b6fb047536068b5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Mar 2026 19:33:36 +0000 Subject: [PATCH 2/4] Add bunny app install and reskin controls Co-authored-by: DenisValeev <884686+DenisValeev@users.noreply.github.com> Agent-Logs-Url: https://github.com/DenisValeev/denisvaleev.github.io/sessions/8d4c3101-47ba-4ee5-9231-fc8fa993ada1 --- apps/enchanted-bunny/apple-touch-icon.png | Bin 0 -> 20785 bytes apps/enchanted-bunny/index.html | 473 ++++++++++++++++++++-- offline-manifest.json | 8 +- tests/enchanted-bunny.spec.js | 41 ++ tests/home-navigation.spec.js | 1 + tests/offline-manifest.spec.js | 2 +- tools/generate-offline-manifest.js | 2 +- 7 files changed, 494 insertions(+), 33 deletions(-) create mode 100644 apps/enchanted-bunny/apple-touch-icon.png create mode 100644 tests/enchanted-bunny.spec.js diff --git a/apps/enchanted-bunny/apple-touch-icon.png b/apps/enchanted-bunny/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ed75b9482937343e92099b95062870d6fe580ff3 GIT binary patch literal 20785 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 AO8@`> literal 0 HcmV?d00001 diff --git a/apps/enchanted-bunny/index.html b/apps/enchanted-bunny/index.html index 957eae6..efc6c27 100644 --- a/apps/enchanted-bunny/index.html +++ b/apps/enchanted-bunny/index.html @@ -3,6 +3,13 @@ + + + + + + + Enchanted Forest Bunny - +

Enchanted forest bunny at golden hour

+ Home +
+ + Cycle through bunny and butterfly scene styles. + +
@@ -1824,8 +2114,18 @@

Enchanted forest bunny at golden hour

(() => { const scene = document.getElementById("scene"); const canvas = document.getElementById("particle-canvas"); - const ctx = canvas.getContext("2d"); + const ctx = canvas ? canvas.getContext("2d") : null; + const skinToggle = document.getElementById("skin-toggle"); + const skinButtonValue = document.getElementById("skin-button-value"); + const skinSwatch = document.getElementById("skin-swatch"); + const skinLive = document.getElementById("skin-live"); + if (!scene || !canvas || !ctx || !skinToggle || !skinButtonValue || !skinSwatch || !skinLive) { + return; + } const clearing = scene.querySelector(".clearing"); + if (!clearing) { + return; + } const beamSvg = document.getElementById("magic-beams"); const beamSpark = document.getElementById("beam-spark"); const bunny = document.getElementById("bunny"); @@ -1837,16 +2137,102 @@

Enchanted forest bunny at golden hour

const basketPocket = document.getElementById("basket-pocket"); const basketFront = document.getElementById("basket-front"); const basketRim = document.getElementById("basket-rim"); + if (!beamSvg || !beamSpark || !bunny || !bunnyBody || !bunnyHead || !bunnyCluster || !bunnyShadow || !basket || !basketPocket || !basketFront || !basketRim) { + return; + } const tuftLeft = clearing.querySelector(".tuft-left"); const tuftRight = clearing.querySelector(".tuft-right"); + if (!tuftLeft || !tuftRight) { + return; + } const ears = { left: bunnyHead.querySelector(".ear-left"), right: bunnyHead.querySelector(".ear-right") }; + if (!ears.left || !ears.right) { + return; + } const cheeks = Array.from(bunnyHead.querySelectorAll(".cheek")); const whiskers = Array.from(bunnyHead.querySelectorAll(".whisker")); const eyes = Array.from(bunnyHead.querySelectorAll(".eye")); const irises = Array.from(bunnyHead.querySelectorAll(".iris")); + const SKIN_STORAGE_KEY = "enchanted-bunny-skin"; + const skins = [ + { + id: "golden", + label: "Golden Burrow", + description: "sunlit cream bunny with apricot butterflies", + swatch: ["#ffd59d", "#5f7d5a"] + }, + { + id: "moonlit", + label: "Moonlit Hare", + description: "cool silver fur with twilight blue wings", + swatch: ["#d9e6ff", "#6172b4"] + }, + { + id: "rose", + label: "Rose Meadow", + description: "petal-soft bunny tones with blush butterflies", + swatch: ["#ffd4dd", "#ab627e"] + }, + { + id: "snowcap", + label: "Snowcap Bunny", + description: "frost-bright fur with minty clearing highlights", + swatch: ["#eefcff", "#6bc6c9"] + } + ]; + let currentSkinIndex = 0; + + function saveSkinPreference(skinId) { + try { + window.localStorage.setItem(SKIN_STORAGE_KEY, skinId); + } catch (error) { + // Ignore storage failures in restricted contexts. + } + } + + function readSkinPreference() { + try { + return window.localStorage.getItem(SKIN_STORAGE_KEY); + } catch (error) { + return null; + } + } + + function applySkin(index, announce = false) { + currentSkinIndex = ((index % skins.length) + skins.length) % skins.length; + const skin = skins[currentSkinIndex]; + document.body.dataset.skin = skin.id; + skinButtonValue.textContent = skin.label; + skinToggle.setAttribute("aria-label", "Switch bunny scene style. Current scene: " + skin.label + "."); + skinSwatch.style.setProperty("--swatch-a", skin.swatch[0]); + skinSwatch.style.setProperty("--swatch-b", skin.swatch[1]); + skinLive.textContent = announce ? skin.label + " active — " + skin.description + "." : ""; + saveSkinPreference(skin.id); + } + + const savedSkinId = readSkinPreference(); + const savedSkinIndex = skins.findIndex((skin) => skin.id === savedSkinId); + if (savedSkinIndex >= 0) { + currentSkinIndex = savedSkinIndex; + } + applySkin(currentSkinIndex); + + skinToggle.addEventListener("click", () => { + applySkin(currentSkinIndex + 1, true); + }); + + skinToggle.addEventListener("keydown", (event) => { + if (event.key === "ArrowRight" || event.key === "ArrowDown") { + event.preventDefault(); + applySkin(currentSkinIndex + 1, true); + } else if (event.key === "ArrowLeft" || event.key === "ArrowUp") { + event.preventDefault(); + applySkin(currentSkinIndex - 1, true); + } + }); const parallaxLayers = Array.from(document.querySelectorAll("[data-depth]")); const initialWidth = window.innerWidth; const initialHeight = window.innerHeight; @@ -3236,6 +3622,38 @@

Enchanted forest bunny at golden hour

coarsePointerQuery.addListener(updatePointerPreferences); } + async function setupOfflineBundle() { + if (!("serviceWorker" in navigator) || !("caches" in window)) { + return; + } + + let manifest; + try { + const response = await fetch("../../offline-manifest.json", { cache: "no-store" }); + if (!response.ok) { + throw new Error("Manifest request failed with status " + response.status); + } + manifest = await response.json(); + } catch (error) { + console.warn("Unable to prepare the enchanted bunny offline bundle.", error); + return; + } + + try { + await navigator.serviceWorker.register("../../service-worker.js"); + const readyRegistration = await navigator.serviceWorker.ready; + const worker = readyRegistration.active || readyRegistration.waiting || readyRegistration.installing; + if (worker) { + worker.postMessage({ + type: "apply-manifest", + manifest + }); + } + } catch (error) { + console.warn("Unable to register offline support for the enchanted bunny app.", error); + } + } + carrotStates.forEach((state) => { const templateId = initialCarrotSpawnIds[state.element.id]; const template = carrotSpawnTemplates.find((item) => item.id === templateId) || carrotSpawnTemplates[state.slotIndex % carrotSpawnTemplates.length]; @@ -3249,6 +3667,7 @@

Enchanted forest bunny at golden hour

setButterflySparkle(butterfly, 0, 0); }); + setupOfflineBundle(); updateLayout(); scheduleBlink(performance.now() - 1200); earTwitch.left.next = performance.now() + rand(1000, 2600); diff --git a/offline-manifest.json b/offline-manifest.json index 0acccf1..5d98c5d 100644 --- a/offline-manifest.json +++ b/offline-manifest.json @@ -1,6 +1,6 @@ { - "version": "20260321-4e85b7806ce3-fd16c3a68980", - "generatedAt": "2026-03-21T14:45:21.961Z", + "version": "20260321-4e85b7806ce3-46ddb879ee31", + "generatedAt": "2026-03-21T19:27:30.649Z", "totalAssets": 77, "totalBytes": 6386324, "assets": [ @@ -314,8 +314,8 @@ } ], "commit": { - "hash": "fd16c3a689802b15a5dcfeb6a022778c87cb8eb8", - "short": "fd16c3a68980", + "hash": "46ddb879ee31488c75d490ec4ee04a44d9e470ee", + "short": "46ddb879ee31", "dirty": false } } diff --git a/tests/enchanted-bunny.spec.js b/tests/enchanted-bunny.spec.js new file mode 100644 index 0000000..60a8c31 --- /dev/null +++ b/tests/enchanted-bunny.spec.js @@ -0,0 +1,41 @@ +const { test, expect } = require('@playwright/test'); + +test.describe('Enchanted Forest Bunny app', () => { + test('exposes install metadata, offline registration, and reskin controls', async ({ page }) => { + await page.goto('/apps/enchanted-bunny/'); + + await expect(page.locator('.home-link')).toHaveAttribute('href', '../../'); + await expect(page.locator('link[rel="apple-touch-icon"]')).toHaveAttribute('href', 'apple-touch-icon.png'); + await expect(page.locator('meta[name="apple-mobile-web-app-capable"]')).toHaveAttribute('content', 'yes'); + + const skinButton = page.locator('#skin-toggle'); + const skinLabel = page.locator('#skin-button-value'); + + await expect(skinButton).toBeVisible(); + await expect(skinLabel).toHaveText('Golden Burrow'); + await expect(page.locator('body')).toHaveAttribute('data-skin', 'golden'); + + await skinButton.click(); + await expect(skinLabel).toHaveText('Moonlit Hare'); + await expect(page.locator('body')).toHaveAttribute('data-skin', 'moonlit'); + + const registrationState = await page.evaluate(async () => { + if (!('serviceWorker' in navigator)) { + return { supported: false, registered: false }; + } + + const registration = await navigator.serviceWorker.getRegistration('/'); + return { + supported: true, + registered: Boolean(registration), + scope: registration ? registration.scope : null, + savedSkin: window.localStorage.getItem('enchanted-bunny-skin'), + }; + }); + + expect(registrationState.supported).toBe(true); + expect(registrationState.registered).toBe(true); + expect(registrationState.scope).toContain('127.0.0.1:4173/'); + expect(registrationState.savedSkin).toBe('moonlit'); + }); +}); diff --git a/tests/home-navigation.spec.js b/tests/home-navigation.spec.js index b76decd..8881f55 100644 --- a/tests/home-navigation.spec.js +++ b/tests/home-navigation.spec.js @@ -9,6 +9,7 @@ const appPages = [ { name: 'Slang', path: '/apps/slang/' }, { name: 'Slang (All slang)', path: '/apps/slang/all-slang.html' }, { name: 'Cosine Similarity Lab', path: '/apps/similarity-report/' }, + { name: 'Enchanted Forest Bunny', path: '/apps/enchanted-bunny/' }, { name: 'Value Formatter', path: '/apps/value-formatter/' }, { name: 'Asset Observatory', path: '/apps/asset-observatory/' }, ]; diff --git a/tests/offline-manifest.spec.js b/tests/offline-manifest.spec.js index b0420fe..150af8a 100644 --- a/tests/offline-manifest.spec.js +++ b/tests/offline-manifest.spec.js @@ -6,7 +6,7 @@ const ROOT = path.join(__dirname, '..'); const MANIFEST_PATH = path.join(ROOT, 'offline-manifest.json'); const INCLUDE_DIRECTORIES = ['apps', 'data']; const INCLUDE_FILES = ['index.html', 'service-worker.js']; -const ALLOWED_EXTENSIONS = new Set(['.html', '.js', '.json', '.md', '.yaml']); +const ALLOWED_EXTENSIONS = new Set(['.html', '.js', '.json', '.md', '.png', '.yaml']); async function readManifest() { const content = await fs.readFile(MANIFEST_PATH, 'utf8'); diff --git a/tools/generate-offline-manifest.js b/tools/generate-offline-manifest.js index 7e02349..808d654 100644 --- a/tools/generate-offline-manifest.js +++ b/tools/generate-offline-manifest.js @@ -8,7 +8,7 @@ const ROOT = process.cwd(); const OUTPUT_PATH = path.join(ROOT, 'offline-manifest.json'); const INCLUDE_DIRECTORIES = ['apps', 'data']; const INCLUDE_FILES = ['index.html', 'service-worker.js']; -const ALLOWED_EXTENSIONS = new Set(['.html', '.js', '.json', '.md', '.yaml']); +const ALLOWED_EXTENSIONS = new Set(['.html', '.js', '.json', '.md', '.png', '.yaml']); const execFileAsync = promisify(execFile); async function main() { From 4991e9529677203153c28349e5b6d144790ed8b2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Mar 2026 19:34:03 +0000 Subject: [PATCH 3/4] Validate bunny app offline bundle Co-authored-by: DenisValeev <884686+DenisValeev@users.noreply.github.com> Agent-Logs-Url: https://github.com/DenisValeev/denisvaleev.github.io/sessions/8d4c3101-47ba-4ee5-9231-fc8fa993ada1 --- offline-manifest.json | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/offline-manifest.json b/offline-manifest.json index 5d98c5d..ef4903b 100644 --- a/offline-manifest.json +++ b/offline-manifest.json @@ -1,8 +1,8 @@ { - "version": "20260321-4e85b7806ce3-46ddb879ee31", - "generatedAt": "2026-03-21T19:27:30.649Z", - "totalAssets": 77, - "totalBytes": 6386324, + "version": "20260321-fe9815b3bbd3-46ddb879ee31", + "generatedAt": "2026-03-21T19:33:52.108Z", + "totalAssets": 78, + "totalBytes": 6422092, "assets": [ { "path": "apps/asset-observatory/AGENTS.md", @@ -40,9 +40,13 @@ "path": "apps/embedding-explorer/sample-embeddings.js", "bytes": 2167 }, + { + "path": "apps/enchanted-bunny/apple-touch-icon.png", + "bytes": 20785 + }, { "path": "apps/enchanted-bunny/index.html", - "bytes": 127005 + "bytes": 141988 }, { "path": "apps/jokes/AGENTS.md", From a8d7c5bcd51c8760d19187462290c5b58e753947 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Mar 2026 19:37:58 +0000 Subject: [PATCH 4/4] Polish bunny app review fixes Co-authored-by: DenisValeev <884686+DenisValeev@users.noreply.github.com> Agent-Logs-Url: https://github.com/DenisValeev/denisvaleev.github.io/sessions/8d4c3101-47ba-4ee5-9231-fc8fa993ada1 --- apps/enchanted-bunny/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/enchanted-bunny/index.html b/apps/enchanted-bunny/index.html index efc6c27..ec652f7 100644 --- a/apps/enchanted-bunny/index.html +++ b/apps/enchanted-bunny/index.html @@ -1932,7 +1932,7 @@ } - +

Enchanted forest bunny at golden hour

Home @@ -3629,7 +3629,7 @@

Enchanted forest bunny at golden hour

let manifest; try { - const response = await fetch("../../offline-manifest.json", { cache: "no-store" }); + const response = await fetch("../../offline-manifest.json", { cache: "no-cache" }); if (!response.ok) { throw new Error("Manifest request failed with status " + response.status); }