diff --git a/README.md b/README.md index cffbeb8..78aeebd 100755 --- a/README.md +++ b/README.md @@ -274,7 +274,7 @@ The published HTML is static, but some browser modules rely on same-origin APIs: | Notes board | `pages/notes.js` | `/api/notes` | | Wird tracker | `pages/wird-tracker.js` | `/api/wird`, `/api/wird/motalah`, `/api/calibre/books` | | Competency board | `pages/competency-status-board.js` | `/api/competencies/items...` | -| RPG saves | `pages/play.js`, `pages/ash-below-lake.js` | `/api/play/rpg/save/:slot`, with local-storage fallback | +| Archive World progress | `pages/archive-world.js` | Device-local `localStorage` state | These APIs are not implemented here. Production routing must serve `output/` and proxy `/api/` to the appropriate backend. When changing an endpoint contract, coordinate the static client and its external service. diff --git a/assets/audio/archive-world/ambient.ogg b/assets/audio/archive-world/ambient.ogg new file mode 100644 index 0000000..cddef7d Binary files /dev/null and b/assets/audio/archive-world/ambient.ogg differ diff --git a/assets/audio/archive-world/discover.wav b/assets/audio/archive-world/discover.wav new file mode 100644 index 0000000..e9958c0 Binary files /dev/null and b/assets/audio/archive-world/discover.wav differ diff --git a/assets/audio/archive-world/portal.wav b/assets/audio/archive-world/portal.wav new file mode 100644 index 0000000..1bf1cff Binary files /dev/null and b/assets/audio/archive-world/portal.wav differ diff --git a/assets/audio/archive-world/step.wav b/assets/audio/archive-world/step.wav new file mode 100644 index 0000000..c64b62a Binary files /dev/null and b/assets/audio/archive-world/step.wav differ diff --git a/assets/images/play/archive-world/archive-town.webp b/assets/images/play/archive-world/archive-town.webp new file mode 100644 index 0000000..57b5988 Binary files /dev/null and b/assets/images/play/archive-world/archive-town.webp differ diff --git a/assets/images/play/archive-world/traveler.png b/assets/images/play/archive-world/traveler.png new file mode 100644 index 0000000..f488664 Binary files /dev/null and b/assets/images/play/archive-world/traveler.png differ diff --git a/assets/scripts/ash-below-lake.js b/assets/scripts/ash-below-lake.js deleted file mode 100755 index cd3483c..0000000 --- a/assets/scripts/ash-below-lake.js +++ /dev/null @@ -1,7 +0,0 @@ -(function () { - "use strict"; - var script = document.createElement("script"); - script.src = "/assets/scripts/pages/ash-below-lake.js"; - script.defer = true; - document.head.appendChild(script); -}()); diff --git a/assets/scripts/pages/archive-world-state.js b/assets/scripts/pages/archive-world-state.js new file mode 100644 index 0000000..e326bd8 --- /dev/null +++ b/assets/scripts/pages/archive-world-state.js @@ -0,0 +1,114 @@ +(function (root, factory) { + "use strict"; + + const api = factory(); + if (typeof module === "object" && module.exports) module.exports = api; + root.ArchiveWorldState = api; +}(typeof globalThis !== "undefined" ? globalThis : this, function () { + "use strict"; + + const VERSION = 1; + const STORAGE_KEY = "zxh_archive_world_v1"; + const PALETTES = Object.freeze(["brass", "moss", "berry"]); + const LANDMARK_IDS = Object.freeze([ + "gate", + "library", + "study", + "inn", + "workshop", + "playroom", + "museum", + "garden" + ]); + const SPAWN = Object.freeze({ x: 640, y: 830 }); + + function validateName(value) { + const name = String(value || "").trim().replace(/\s+/g, " "); + return name.length >= 1 && name.length <= 20 ? name : null; + } + + function validatePalette(value) { + return PALETTES.includes(value) ? value : null; + } + + function fresh(name, palette) { + return { + version: VERSION, + name: validateName(name) || "", + palette: validatePalette(palette) || "brass", + discovered: [], + complete: false, + position: { x: SPAWN.x, y: SPAWN.y }, + soundEnabled: false + }; + } + + function clamp(value, min, max, fallback) { + const number = Number(value); + return Number.isFinite(number) ? Math.min(max, Math.max(min, number)) : fallback; + } + + function normalize(candidate) { + if (!candidate || candidate.version !== VERSION) return null; + const name = validateName(candidate.name); + const palette = validatePalette(candidate.palette); + if (!name || !palette) return null; + + const discovered = Array.isArray(candidate.discovered) + ? Array.from(new Set(candidate.discovered.filter((id) => LANDMARK_IDS.includes(id)))) + : []; + + return { + version: VERSION, + name, + palette, + discovered, + complete: LANDMARK_IDS.every((id) => discovered.includes(id)), + position: { + x: clamp(candidate.position && candidate.position.x, 36, 1244, SPAWN.x), + y: clamp(candidate.position && candidate.position.y, 36, 924, SPAWN.y) + }, + soundEnabled: candidate.soundEnabled === true + }; + } + + function parse(raw) { + if (!raw) return null; + try { + return normalize(JSON.parse(raw)); + } catch (_error) { + return null; + } + } + + function discover(state, landmarkId) { + const current = normalize(state); + if (!current || !LANDMARK_IDS.includes(landmarkId)) return current; + if (!current.discovered.includes(landmarkId)) current.discovered.push(landmarkId); + current.complete = LANDMARK_IDS.every((id) => current.discovered.includes(id)); + return current; + } + + function withPosition(state, x, y) { + const current = normalize(state); + if (!current) return null; + current.position.x = clamp(x, 36, 1244, SPAWN.x); + current.position.y = clamp(y, 36, 924, SPAWN.y); + return current; + } + + return Object.freeze({ + VERSION, + STORAGE_KEY, + PALETTES, + LANDMARK_IDS, + SPAWN, + validateName, + validatePalette, + fresh, + normalize, + parse, + discover, + withPosition + }); +})); diff --git a/assets/scripts/pages/archive-world.js b/assets/scripts/pages/archive-world.js new file mode 100644 index 0000000..94f2347 --- /dev/null +++ b/assets/scripts/pages/archive-world.js @@ -0,0 +1,420 @@ +(function () { + "use strict"; + + const State = window.ArchiveWorldState; + const LANDMARKS = Object.freeze({ + gate: landmark("Town Gate", "Arrivals", 640, 830, + "The old gate remembers every arrival. Begin at the dashboard or follow the newest paths.", + [["Home", "/"], ["Recently updated", "/recently-updated.html"], ["Contact", "/home/contact.html"]]), + library: landmark("Grand Library", "Knowledge", 260, 285, + "Long-lived explanations, categories, and learning notes fill the blue-roofed library.", + [["Posts", "/posts/posts-list.html"], ["Categories", "/home/categories.html"], ["Posts introduction", "/posts/posts-intro.html"]]), + study: landmark("Guild Study", "Work", 640, 250, + "Engineering notes, professional lessons, and active competencies cover every desk.", + [["Career", "/posts/career/career-list.html"], ["Competency status", "/home/status.html"], ["Probation objectives", "/posts/career/probation-objectives.html"]]), + inn: landmark("Kitchen Inn", "Daily life", 1005, 270, + "Weekly reviews and ordinary days stay warm beside the inn's oven.", + [["Blogs", "/blogs/blogs-list.html"], ["Weekly reviews", "/tags/review.html"], ["Blogs introduction", "/blogs/blogs-intro.html"]]), + workshop: landmark("Workshop", "Living systems", 1030, 535, + "Trackers, services, plans, and practical machinery keep the town moving.", + [["Services", "/home/services.html"], ["Wird tracker", "/home/wird-tracker.html"], ["Countdowns", "/home/countdown.html"], ["Backlog", "/home/backlog.html"]]), + playroom: landmark("Playroom", "Experiments", 1005, 765, + "Games, generators, and stranger little mechanisms glow behind the bright windows.", + [["Play hub", "/play/play.html"], ["The Rain Index", "/play/the-rain-index.html"], ["House of Pages", "/play/house.html"]]), + museum: landmark("Lima Museum", "Keepsakes", 275, 765, + "Personal fragments, older writing, and small memories rest beneath the glass roof.", + [["Lima archive", "/lima/index.html"], ["Older writing", "/blogs/2025/2025-list.html"], ["Memory Cabinet", "/play/memory.html"]]), + garden: landmark("Notes Garden", "Connections", 220, 515, + "Loose notes, paths between topics, and recently tended pages grow beyond the trellis.", + [["Notes wall", "/home/notes.html"], ["Categories", "/home/categories.html"], ["Recently updated", "/recently-updated.html"], ["Sitemap", "/sitemap.html"]]) + }); + + const PALETTE_TINTS = Object.freeze({ + brass: 0xf1c46f, + moss: 0x91c788, + berry: 0xd894ad + }); + + const controller = { + state: null, + scene: null, + lock: false, + move: { up: false, down: false, left: false, right: false }, + reducedMotion: window.matchMedia("(prefers-reduced-motion: reduce)").matches, + saveTimer: 0 + }; + + function landmark(title, eyebrow, x, y, description, links) { + return Object.freeze({ title, eyebrow, x, y, description, links: Object.freeze(links) }); + } + + document.addEventListener("DOMContentLoaded", init); + + function init() { + const root = document.querySelector('.archive-world[data-play-page="archive-world"]'); + if (!root || !State) return; + + const stored = State.parse(localStorage.getItem(State.STORAGE_KEY)); + controller.state = stored; + bindInterface(root); + + if (!window.Phaser) { + setStatus(root, "The world engine could not load. Every destination remains available in the plain directory."); + return; + } + + if (stored) { + startGame(root); + } else { + openSetup(root); + } + } + + function bindInterface(root) { + const setup = root.querySelector("[data-world-setup]"); + const setupForm = root.querySelector("[data-world-setup-form]"); + const preview = root.querySelector("[data-world-preview]"); + const journal = root.querySelector("[data-world-journal]"); + + setup.addEventListener("cancel", (event) => event.preventDefault()); + setupForm.addEventListener("submit", (event) => { + event.preventDefault(); + const data = new FormData(setupForm); + const name = State.validateName(data.get("name")); + const palette = State.validatePalette(data.get("palette")); + const error = root.querySelector("[data-world-setup-error]"); + if (!name || !palette) { + error.textContent = "Choose a palette and enter a name between 1 and 20 characters."; + return; + } + controller.state = State.fresh(name, palette); + persist(); + setup.close(); + startGame(root); + }); + + root.querySelector("[data-world-preview-close]").addEventListener("click", () => preview.close()); + preview.addEventListener("close", () => { + controller.lock = false; + root.querySelector("#archive-world-game").focus(); + }); + + root.querySelector("[data-world-journal-open]").addEventListener("click", () => { + renderJournal(root); + controller.lock = true; + journal.showModal(); + }); + root.querySelector("[data-world-journal-close]").addEventListener("click", () => journal.close()); + journal.addEventListener("close", () => { + controller.lock = false; + root.querySelector("[data-world-journal-open]").focus(); + }); + + root.querySelector("[data-world-sound]").addEventListener("click", () => toggleSound(root)); + root.querySelector("[data-world-reset]").addEventListener("click", () => { + if (!window.confirm("Reset your traveler, discoveries, and saved position?")) return; + localStorage.removeItem(State.STORAGE_KEY); + window.location.reload(); + }); + + root.querySelectorAll("[data-world-move]").forEach((button) => { + const direction = button.dataset.worldMove; + const down = (event) => { + event.preventDefault(); + controller.move[direction] = true; + }; + const up = (event) => { + event.preventDefault(); + controller.move[direction] = false; + }; + button.addEventListener("pointerdown", down); + button.addEventListener("pointerup", up); + button.addEventListener("pointercancel", up); + button.addEventListener("pointerleave", up); + }); + root.querySelector("[data-world-interact]").addEventListener("click", () => { + if (controller.scene) controller.scene.interact(); + }); + + window.addEventListener("pagehide", savePosition); + document.addEventListener("visibilitychange", () => { + if (document.hidden) savePosition(); + }); + } + + function openSetup(root) { + const dialog = root.querySelector("[data-world-setup]"); + window.setTimeout(() => { + dialog.showModal(); + dialog.querySelector("input[name=name]").focus(); + }, 0); + } + + function startGame(root) { + renderHud(root); + const game = new Phaser.Game({ + type: Phaser.AUTO, + parent: "archive-world-game", + width: 960, + height: 640, + backgroundColor: "#17251e", + pixelArt: true, + roundPixels: true, + physics: { + default: "arcade", + arcade: { debug: false } + }, + scale: { + mode: Phaser.Scale.FIT, + autoCenter: Phaser.Scale.CENTER_BOTH + }, + scene: ArchiveTownScene, + input: { + keyboard: true, + mouse: true, + touch: true + } + }); + root.dataset.gameReady = "true"; + root.querySelector("#archive-world-game").focus(); + window.archiveWorldGame = game; + } + + class ArchiveTownScene extends Phaser.Scene { + constructor() { + super("ArchiveTown"); + this.nearest = null; + this.lastStepAt = 0; + this.wasMoving = false; + } + + preload() { + this.load.image("archive-town", "/assets/images/play/archive-world/archive-town.webp"); + this.load.image("traveler", "/assets/images/play/archive-world/traveler.png"); + this.load.audio("ambient", "/assets/audio/archive-world/ambient.ogg"); + this.load.audio("step", "/assets/audio/archive-world/step.wav"); + this.load.audio("discover", "/assets/audio/archive-world/discover.wav"); + this.load.audio("portal", "/assets/audio/archive-world/portal.wav"); + } + + create() { + controller.scene = this; + this.add.image(0, 0, "archive-town").setOrigin(0).setDepth(0); + this.physics.world.setBounds(0, 0, 1280, 960); + + this.markers = {}; + Object.entries(LANDMARKS).forEach(([id, item]) => { + const marker = this.add.circle(item.x, item.y, 22, 0xf4d98a, 0.16) + .setStrokeStyle(3, 0xf8e6a7, 0.9) + .setDepth(2); + marker.setData("landmarkId", id); + this.markers[id] = marker; + if (!controller.reducedMotion) { + this.tweens.add({ + targets: marker, + scale: 1.18, + alpha: 0.58, + duration: 900, + yoyo: true, + repeat: -1, + delay: Object.keys(LANDMARKS).indexOf(id) * 90 + }); + } + }); + + this.player = this.physics.add.sprite( + controller.state.position.x, + controller.state.position.y, + "traveler" + ).setDepth(4).setTint(PALETTE_TINTS[controller.state.palette]); + this.player.setCollideWorldBounds(true); + this.player.body.setSize(28, 34).setOffset(8, 58); + + this.cameras.main.setBounds(0, 0, 1280, 960); + this.cameras.main.startFollow(this.player, true, controller.reducedMotion ? 1 : 0.12, controller.reducedMotion ? 1 : 0.12); + + this.cursors = this.input.keyboard.createCursorKeys(); + this.keys = this.input.keyboard.addKeys({ + up: Phaser.Input.Keyboard.KeyCodes.W, + down: Phaser.Input.Keyboard.KeyCodes.S, + left: Phaser.Input.Keyboard.KeyCodes.A, + right: Phaser.Input.Keyboard.KeyCodes.D, + interact: Phaser.Input.Keyboard.KeyCodes.E, + enter: Phaser.Input.Keyboard.KeyCodes.ENTER + }); + + this.ambient = this.sound.add("ambient", { loop: true, volume: 0.24 }); + this.stepSound = this.sound.add("step", { volume: 0.22 }); + this.discoverSound = this.sound.add("discover", { volume: 0.35 }); + this.portalSound = this.sound.add("portal", { volume: 0.3 }); + if (controller.state.soundEnabled) this.enableSound(); + + setStatus(document.querySelector(".archive-world"), `Welcome, ${controller.state.name}. Walk near a glowing landmark and press E, Space, or Enter.`); + this.updateNearest(); + } + + update(time) { + if (!this.player) return; + let dx = 0; + let dy = 0; + if (!controller.lock) { + if (this.cursors.left.isDown || this.keys.left.isDown || controller.move.left) dx -= 1; + if (this.cursors.right.isDown || this.keys.right.isDown || controller.move.right) dx += 1; + if (this.cursors.up.isDown || this.keys.up.isDown || controller.move.up) dy -= 1; + if (this.cursors.down.isDown || this.keys.down.isDown || controller.move.down) dy += 1; + } + + const moving = dx !== 0 || dy !== 0; + if (moving) { + const vector = new Phaser.Math.Vector2(dx, dy).normalize().scale(175); + this.player.setVelocity(vector.x, vector.y); + if (dx) this.player.setFlipX(dx < 0); + if (!controller.reducedMotion) { + this.player.rotation = Math.sin(time / 80) * 0.025; + this.player.setScale(1, 0.98 + Math.abs(Math.sin(time / 95)) * 0.04); + } + if (controller.state.soundEnabled && time - this.lastStepAt > 340) { + this.stepSound.play(); + this.lastStepAt = time; + } + if (time - controller.saveTimer > 700) { + controller.saveTimer = time; + savePosition(); + } + } else { + this.player.setVelocity(0, 0); + this.player.rotation = 0; + this.player.setScale(1); + } + this.wasMoving = moving; + this.updateNearest(); + + if (!controller.lock && ( + Phaser.Input.Keyboard.JustDown(this.keys.interact) || + Phaser.Input.Keyboard.JustDown(this.keys.enter) || + Phaser.Input.Keyboard.JustDown(this.cursors.space) + )) this.interact(); + } + + updateNearest() { + let nearest = null; + let distance = Infinity; + Object.entries(LANDMARKS).forEach(([id, item]) => { + const nextDistance = Phaser.Math.Distance.Between(this.player.x, this.player.y, item.x, item.y); + if (nextDistance < distance) { + nearest = id; + distance = nextDistance; + } + this.markers[id].setStrokeStyle(nextDistance < 95 ? 5 : 3, nextDistance < 95 ? 0xffffff : 0xf8e6a7, 0.95); + }); + const next = distance < 95 ? nearest : null; + if (next !== this.nearest) { + this.nearest = next; + const root = document.querySelector(".archive-world"); + setStatus(root, next + ? `${LANDMARKS[next].title} is nearby. Press E, Space, Enter, or Explore.` + : "Follow the stone paths toward a glowing landmark."); + } + } + + interact() { + if (controller.lock) return; + if (!this.nearest) { + setStatus(document.querySelector(".archive-world"), "Move closer to one of the glowing landmark circles."); + return; + } + openPreview(document.querySelector(".archive-world"), this.nearest); + } + + enableSound() { + if (this.sound.locked && this.sound.unlock) this.sound.unlock(); + this.sound.mute = false; + if (!this.ambient.isPlaying) this.ambient.play(); + } + + disableSound() { + this.sound.mute = true; + if (this.ambient.isPlaying) this.ambient.pause(); + } + } + + function openPreview(root, id) { + const item = LANDMARKS[id]; + if (!item) return; + const wasDiscovered = controller.state.discovered.includes(id); + controller.state = State.discover(controller.state, id); + persist(); + controller.lock = true; + + root.querySelector("[data-world-preview-eyebrow]").textContent = item.eyebrow; + root.querySelector("[data-world-preview-title]").textContent = item.title; + root.querySelector("[data-world-preview-description]").textContent = item.description; + const links = root.querySelector("[data-world-preview-links]"); + links.replaceChildren(); + item.links.forEach(([label, href]) => { + const anchor = document.createElement("a"); + anchor.href = href; + anchor.textContent = label; + anchor.addEventListener("click", () => { + if (controller.scene && controller.state.soundEnabled) controller.scene.portalSound.play(); + savePosition(); + }); + links.appendChild(anchor); + }); + + renderHud(root); + if (!wasDiscovered && controller.scene && controller.state.soundEnabled) controller.scene.discoverSound.play(); + root.querySelector("[data-world-preview]").showModal(); + setStatus(root, wasDiscovered + ? `${item.title} is already recorded in your journal.` + : `${item.title} was added to your discovery journal.`); + } + + function toggleSound(root) { + if (!controller.state || !controller.scene) return; + controller.state.soundEnabled = !controller.state.soundEnabled; + if (controller.state.soundEnabled) controller.scene.enableSound(); + else controller.scene.disableSound(); + persist(); + renderHud(root); + setStatus(root, controller.state.soundEnabled ? "Ambient sound is on." : "Sound is muted."); + } + + function savePosition() { + if (!controller.state || !controller.scene || !controller.scene.player) return; + controller.state = State.withPosition(controller.state, controller.scene.player.x, controller.scene.player.y); + persist(); + } + + function persist() { + if (controller.state) localStorage.setItem(State.STORAGE_KEY, JSON.stringify(controller.state)); + } + + function renderHud(root) { + if (!controller.state) return; + root.querySelector("[data-world-player]").textContent = controller.state.name; + root.querySelector("[data-world-discovery-count]").textContent = `${controller.state.discovered.length} / ${State.LANDMARK_IDS.length}`; + root.querySelector("[data-world-sound]").textContent = controller.state.soundEnabled ? "Sound: on" : "Sound: muted"; + const badge = root.querySelector("[data-world-badge]"); + badge.hidden = !controller.state.complete; + renderJournal(root); + } + + function renderJournal(root) { + if (!controller.state) return; + const list = root.querySelector("[data-world-journal-list]"); + list.replaceChildren(); + State.LANDMARK_IDS.forEach((id) => { + const row = document.createElement("li"); + const found = controller.state.discovered.includes(id); + row.className = found ? "is-discovered" : ""; + row.textContent = `${found ? "Discovered" : "Unexplored"} — ${LANDMARKS[id].title}`; + list.appendChild(row); + }); + } + + function setStatus(root, message) { + const status = root && root.querySelector("[data-world-status]"); + if (status) status.textContent = message; + } +}()); diff --git a/assets/scripts/pages/ash-below-lake.js b/assets/scripts/pages/ash-below-lake.js deleted file mode 100755 index 5aa74cd..0000000 --- a/assets/scripts/pages/ash-below-lake.js +++ /dev/null @@ -1,1008 +0,0 @@ -(function () { - const slot = "ash-below-the-lake"; - const fallbackKey = "play:rpg:ash-below-the-lake"; - const tile = 32; - const cols = 30; - const rows = 15; - const worldH = tile * rows; - const uiY = worldH; - const actions = ["Confront", "Listen", "Forget"]; - const areas = { - harbor: { - name: "Harbor Row", - act: "Act 1", - track: "harbor", - floor: ["#222c36", "#263541", "#304650"], - accent: "#e0a451", - walls: rects([[0, 0, 30, 1], [0, 14, 30, 1], [0, 0, 1, 15], [29, 0, 1, 15], [3, 3, 5, 2], [12, 2, 4, 3], [20, 3, 6, 2], [5, 11, 7, 1], [17, 10, 5, 1]]), - exits: [{ x: 28, y: 7, to: "red", px: 1, py: 7 }], - props: [{ kind: "lake", x: 1, y: 11, w: 28, h: 3 }, { kind: "stall", x: 9, y: 5 }, { kind: "flowers", x: 23, y: 9 }], - start: { x: 3, y: 8 } - }, - red: { - name: "The Red Streets", - act: "Act 1", - track: "red", - floor: ["#2f1f2c", "#442237", "#5b283f"], - accent: "#db4f62", - walls: rects([[0, 0, 30, 1], [0, 14, 30, 1], [0, 0, 1, 6], [0, 9, 1, 6], [29, 0, 1, 15], [4, 3, 4, 3], [12, 4, 3, 8], [20, 2, 5, 3], [22, 9, 4, 2]]), - exits: [{ x: 0, y: 7, to: "harbor", px: 28, py: 7 }, { x: 28, y: 12, to: "roots", px: 1, py: 12 }], - props: [{ kind: "sign", x: 9, y: 3 }, { kind: "stage", x: 17, y: 6 }, { kind: "poster", x: 26, y: 4 }], - start: { x: 2, y: 7 } - }, - roots: { - name: "The Roots", - act: "Act 2", - track: "roots", - floor: ["#182725", "#203832", "#2e493e"], - accent: "#66c7a2", - walls: rects([[0, 0, 30, 1], [0, 14, 30, 1], [0, 0, 1, 11], [0, 13, 1, 2], [29, 0, 1, 15], [5, 2, 2, 10], [11, 5, 7, 1], [15, 8, 2, 5], [22, 3, 3, 9]]), - exits: [{ x: 0, y: 12, to: "red", px: 28, py: 12 }, { x: 28, y: 4, to: "chapel", px: 1, py: 4 }], - props: [{ kind: "archive", x: 8, y: 8 }, { kind: "root", x: 18, y: 3 }, { kind: "home", x: 25, y: 11 }], - start: { x: 2, y: 12 } - }, - chapel: { - name: "The Drowned Chapel", - act: "Act 3", - track: "chapel", - floor: ["#172232", "#1c3043", "#263b4f"], - accent: "#8bd3ff", - walls: rects([[0, 0, 30, 1], [0, 14, 30, 1], [0, 0, 1, 3], [0, 6, 1, 9], [29, 0, 1, 15], [6, 4, 3, 7], [13, 2, 4, 3], [19, 4, 3, 7], [10, 12, 10, 1]]), - exits: [{ x: 0, y: 4, to: "roots", px: 28, py: 4 }, { x: 28, y: 10, to: "shore", px: 1, py: 10 }], - props: [{ kind: "pew", x: 10, y: 7 }, { kind: "pew", x: 16, y: 7 }, { kind: "glass", x: 14, y: 5 }], - start: { x: 2, y: 4 } - }, - shore: { - name: "The Hollow Shore", - act: "Final Act", - track: "shore", - floor: ["#111827", "#1b2440", "#211c33"], - accent: "#f1f5f9", - walls: rects([[0, 0, 30, 1], [0, 14, 30, 1], [0, 0, 1, 9], [0, 12, 1, 3], [29, 0, 1, 15], [7, 3, 3, 2], [16, 2, 2, 10], [22, 7, 4, 1]]), - exits: [{ x: 0, y: 10, to: "chapel", px: 28, py: 10 }], - props: [{ kind: "lake", x: 2, y: 10, w: 27, h: 4 }, { kind: "flowers", x: 5, y: 6 }, { kind: "rift", x: 24, y: 4 }], - start: { x: 2, y: 10 } - } - }; - const characters = { - ilyas: { name: "Ilyas", colors: ["#3d2b2f", "#e3b27f", "#334b7a", "#f2f0e6"] }, - mira: { name: "Mira", colors: ["#21191e", "#d98d70", "#b73550", "#f6d36f"] }, - ferryman: { name: "Ferryman", colors: ["#151719", "#dad9cd", "#56616d", "#0b0d10"] }, - cinder: { name: "Brother Cinder", colors: ["#1d1512", "#d59667", "#6b2e2f", "#f4c35c"] }, - sibling: { name: "Your Sibling", colors: ["#1d2531", "#bec8d8", "#52616f", "#dbeafe"] }, - vendor: { name: "Maribel", colors: ["#2a2023", "#d5a06f", "#3d7b65", "#ffd166"] }, - performer: { name: "Velvet Noon", colors: ["#2a1724", "#f2bd93", "#8f244d", "#ffe6a8"] }, - archivist: { name: "Memory Archivist", colors: ["#151f1d", "#b9ddc9", "#386b5c", "#dff7eb"] } - }; - const npcs = { - mira: { character: "mira", area: "harbor", x: 10, y: 8 }, - ferryman: { character: "ferryman", area: "harbor", x: 25, y: 10 }, - vendor: { character: "vendor", area: "harbor", x: 9, y: 6 }, - performer: { character: "performer", area: "red", x: 17, y: 8 }, - archivist: { character: "archivist", area: "roots", x: 9, y: 9 }, - cinder: { character: "cinder", area: "chapel", x: 15, y: 6 }, - sibling: { character: "sibling", area: "shore", x: 24, y: 8 } - }; - const items = { - letter: { name: "Wet Letter", area: "harbor", x: 5, y: 10 }, - bell: { name: "Brass Bell", area: "red", x: 27, y: 5 }, - photo: { name: "Blurred Photo", area: "roots", x: 13, y: 9 }, - cinderKey: { name: "Chapel Key", area: "chapel", x: 22, y: 12 } - }; - const encounters = { - first_erased: { - title: "First Erased Person", - area: "red", - x: 6, - y: 9, - hp: 18, - text: "A person-shaped absence blocks the alley. Their outline mouths a name no one else can hear.", - bullets: "drift" - }, - root_husk: { - title: "Root Husk", - area: "roots", - x: 21, - y: 9, - hp: 24, - text: "A house nobody remembers learned to walk. It wants its rooms back.", - bullets: "rain" - }, - chapel_memory: { - title: "Choir of Ash", - area: "chapel", - x: 11, - y: 10, - hp: 30, - text: "Voices rise from the drowned pews. Every note sounds like an apology.", - bullets: "spiral" - } - }; - - window.AshBelowLakeRpg = function init(root) { - const canvas = root.querySelector("[data-rpg-canvas]"); - const ctx = canvas.getContext("2d"); - const els = { - speaker: root.querySelector("[data-rpg-speaker]"), - line: root.querySelector("[data-rpg-line]"), - area: root.querySelector("[data-rpg-room]"), - route: root.querySelector("[data-rpg-route]"), - resolve: root.querySelector("[data-rpg-hearts]"), - journal: root.querySelector("[data-rpg-quests]"), - inventory: root.querySelector("[data-rpg-inventory]"), - status: root.querySelector("[data-rpg-status]") - }; - const ui = buildUi(root); - const audio = makeAudio(); - let state = freshState(); - let keys = new Set(); - let last = performance.now(); - let saveBusy = false; - let backendAvailable = true; - - bind(); - loadLocal(); - requestAnimationFrame(frame); - - function freshState() { - return { - started: false, - mode: "title", - area: "harbor", - player: { x: 3, y: 8, facing: "down", step: 0 }, - route: "Undecided", - resolve: 20, - toldName: null, - flags: {}, - inventory: [], - memories: [ - { id: "letter", text: "Your sibling's letter says: Don't let the lake remember your name.", stable: true }, - { id: "town", text: "Morrow's End smiles like a town in a postcard someone tried to burn.", stable: true } - ], - forgets: 0, - listens: 0, - confronts: 0, - companion: false, - battle: null, - ending: null, - dialogue: { speaker: "The Letter", line: "Don't let the lake remember your name." } - }; - } - - function buildUi(rootEl) { - const panel = rootEl.querySelector(".rpg-panel"); - const actionsWrap = document.createElement("div"); - actionsWrap.className = "ash-choicebar"; - actionsWrap.innerHTML = actions.map((name) => ``).join(""); - panel.insertBefore(actionsWrap, panel.querySelector(".rpg-list")); - const meter = document.createElement("div"); - meter.className = "ash-memory-meter"; - meter.innerHTML = "Lake Memory"; - panel.insertBefore(meter, actionsWrap); - const nameBox = document.createElement("form"); - nameBox.className = "ash-name-form"; - nameBox.innerHTML = ""; - panel.insertBefore(nameBox, meter); - return { - actionButtons: Array.from(actionsWrap.querySelectorAll("button")), - memoryFill: meter.querySelector("[data-ash-memory-fill]"), - nameForm: nameBox, - nameInput: nameBox.querySelector("[data-ash-name]") - }; - } - - function bind() { - root.querySelector("[data-rpg-start]").addEventListener("click", start); - root.querySelector("[data-rpg-save]").addEventListener("click", save); - root.querySelector("[data-rpg-load]").addEventListener("click", () => load(true)); - root.querySelector("[data-rpg-reset]").addEventListener("click", newGame); - root.querySelector("[data-rpg-sound]").addEventListener("click", () => { - audio.start(); - audio.setTrack(state.started ? areas[state.area].track : "title"); - els.status.textContent = audio.supported() ? "Sound on." : "This browser does not expose Web Audio."; - }); - root.querySelector("[data-rpg-act]").addEventListener("click", act); - root.querySelectorAll("[data-rpg-move]").forEach((button) => { - button.addEventListener("click", () => move(button.dataset.rpgMove)); - }); - ui.actionButtons.forEach((button) => button.addEventListener("click", () => chooseBattle(button.dataset.ashAction))); - ui.nameForm.addEventListener("submit", (event) => { - event.preventDefault(); - tellName(ui.nameInput.value.trim() || "Ilyas"); - }); - window.addEventListener("keydown", (event) => { - if (!root.isConnected || ["INPUT", "TEXTAREA"].includes(event.target.tagName)) return; - if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "w", "a", "s", "d", " ", "Enter", "Escape"].includes(event.key)) event.preventDefault(); - keys.add(event.key.toLowerCase()); - audio.start(); - if (event.key === " " || event.key === "Enter") act(); - if (event.key === "Escape" && state.mode === "battle") leaveBattle("You step back from the memory and let it breathe."); - }); - window.addEventListener("keyup", (event) => keys.delete(event.key.toLowerCase())); - } - - function start() { - audio.start(); - state.started = true; - state.mode = "world"; - state.dialogue = { speaker: "Ilyas", line: "The letter is damp, but the ink has not run. Morrow's End waits beyond the harbor lamps." }; - audio.setTrack(areas[state.area].track); - save(); - } - - async function newGame() { - state = freshState(); - localStorage.removeItem(fallbackKey); - if (backendAvailable) { - try { - const response = await fetch(`/api/play/rpg/save/${slot}`, { method: "DELETE" }); - if (response.status === 404) els.status.textContent = "New game started locally. There was no backend save to clear."; - } catch (_error) { - backendAvailable = false; - } - } - audio.setTrack("title"); - if (!els.status.textContent.includes("backend save to clear")) { - els.status.textContent = backendAvailable ? "New game started and local save cleared." : "New game started locally. Backend save endpoint is not reachable."; - } - renderHud(); - } - - async function save() { - localStorage.setItem(fallbackKey, JSON.stringify(state)); - if (saveBusy || !backendAvailable) { - els.status.textContent = backendAvailable ? "Save already running." : "Saved locally. Backend save endpoint is not available."; - return; - } - saveBusy = true; - try { - const response = await fetch(`/api/play/rpg/save/${slot}`, { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ payload: state }) - }); - if (response.status === 404) { - backendAvailable = false; - els.status.textContent = "Saved locally. Backend save endpoint is not deployed yet."; - } else { - els.status.textContent = response.ok ? "Saved to backend." : "Saved locally. Backend rejected the save."; - } - } catch (_error) { - backendAvailable = false; - els.status.textContent = "Saved locally. Backend save API was not reachable."; - } finally { - saveBusy = false; - } - } - - async function load(allowBackend) { - if (loadLocal("Loaded local save.")) return; - if (!allowBackend || !backendAvailable) { - els.status.textContent = backendAvailable ? "No local save found." : "No local save found. Backend save endpoint is not available."; - return; - } - try { - const response = await fetch(`/api/play/rpg/save/${slot}`); - if (response.status === 404) { - els.status.textContent = "No save file exists yet. Press Save after starting a game to create one."; - return; - } - if (!response.ok) { - els.status.textContent = "No local save found. Backend did not return a save."; - return; - } - const data = await response.json(); - state = normalize(data.payload); - localStorage.setItem(fallbackKey, JSON.stringify(state)); - els.status.textContent = "Loaded from backend."; - audio.setTrack(state.started ? areas[state.area].track : "title"); - } catch (_error) { - backendAvailable = false; - els.status.textContent = "No local save found. Backend save API was not reachable."; - } - } - - function loadLocal(message) { - const local = localStorage.getItem(fallbackKey); - if (local) { - state = normalize(JSON.parse(local)); - if (message) els.status.textContent = message; - audio.setTrack(state.started ? areas[state.area].track : "title"); - return true; - } - audio.setTrack(state.started ? areas[state.area].track : "title"); - return false; - } - - function normalize(candidate) { - const base = freshState(); - const next = Object.assign(base, candidate || {}); - next.player = Object.assign(base.player, (candidate && candidate.player) || {}); - next.flags = Object.assign({}, (candidate && candidate.flags) || {}); - next.inventory = Array.isArray(next.inventory) ? next.inventory.filter((id) => items[id]) : []; - next.memories = Array.isArray(next.memories) ? next.memories : base.memories; - next.area = areas[next.area] ? next.area : "harbor"; - next.dialogue = Object.assign(base.dialogue, (candidate && candidate.dialogue) || {}); - return next; - } - - function frame(now) { - const dt = Math.min(48, now - last); - last = now; - update(dt); - draw(now); - renderHud(); - requestAnimationFrame(frame); - } - - function update(dt) { - if (state.mode !== "world") return; - const dir = keyDirection(); - if (!dir) return; - state.player.step += dt; - if (state.player.step < 115) return; - state.player.step = 0; - move(dir); - } - - function keyDirection() { - if (keys.has("arrowup") || keys.has("w")) return "up"; - if (keys.has("arrowdown") || keys.has("s")) return "down"; - if (keys.has("arrowleft") || keys.has("a")) return "left"; - if (keys.has("arrowright") || keys.has("d")) return "right"; - return null; - } - - function move(direction) { - if (state.mode !== "world" || !state.started || state.ending) return; - const delta = { up: [0, -1], down: [0, 1], left: [-1, 0], right: [1, 0] }[direction]; - state.player.facing = direction; - const nx = state.player.x + delta[0]; - const ny = state.player.y + delta[1]; - const area = areas[state.area]; - const exit = area.exits.find((item) => item.x === nx && item.y === ny); - if (exit && canEnter(exit.to)) { - state.area = exit.to; - state.player.x = exit.px; - state.player.y = exit.py; - audio.setTrack(areas[state.area].track); - remember(`Entered ${areas[state.area].name}.`, true); - say("Morrow's End", areaEnterLine(state.area)); - save(); - return; - } - if (area.walls.has(`${nx},${ny}`) || npcAt(nx, ny) || nx < 0 || nx >= cols || ny < 0 || ny >= rows) return; - state.player.x = nx; - state.player.y = ny; - const found = itemAt(nx, ny); - if (found) take(found); - const encounter = encounterAt(nx, ny); - if (encounter && !state.flags[`encounter_${encounter}`]) startBattle(encounter); - } - - function canEnter(to) { - if (to === "chapel" && !state.flags.rootsTruth && state.forgets < 2) { - say("Ferryman", "The chapel is under the lake. It only opens for truth, or for forgetting."); - return false; - } - if (to === "shore" && !state.flags.cinderMet) { - say("Brother Cinder", "The shore is not a place. It is a decision. Hear me first."); - return false; - } - return true; - } - - function act() { - audio.start(); - if (!state.started) return start(); - if (state.mode === "battle") return chooseBattle("listen"); - if (state.ending) return; - const front = inFront(); - const npc = npcAt(front.x, front.y) || npcAt(state.player.x, state.player.y); - if (npc) return talk(npc); - const found = itemAt(state.player.x, state.player.y); - if (found) return take(found); - const special = propAt(front.x, front.y) || propAt(state.player.x, state.player.y); - if (special) return inspectProp(special); - if (state.area === "shore" && state.player.x > 22 && state.player.y > 8) return finalChoice(); - say("Ilyas", "The town holds its breath. Somewhere, water knocks against stone."); - } - - function inFront() { - const d = { up: [0, -1], down: [0, 1], left: [-1, 0], right: [1, 0] }[state.player.facing] || [0, 1]; - return { x: state.player.x + d[0], y: state.player.y + d[1] }; - } - - function npcAt(x, y) { - return Object.keys(npcs).find((id) => { - const npc = npcs[id]; - return npc.area === state.area && npc.x === x && npc.y === y && !forgottenNpc(id); - }); - } - - function itemAt(x, y) { - return Object.keys(items).find((id) => { - const item = items[id]; - return item.area === state.area && item.x === x && item.y === y && !state.inventory.includes(id); - }); - } - - function encounterAt(x, y) { - return Object.keys(encounters).find((id) => { - const e = encounters[id]; - return e.area === state.area && e.x === x && e.y === y; - }); - } - - function propAt(x, y) { - return areas[state.area].props.find((prop) => x >= prop.x && x < prop.x + (prop.w || 1) && y >= prop.y && y < prop.y + (prop.h || 1)); - } - - function take(id) { - state.inventory.push(id); - audio.sfx("item"); - if (id === "letter") remember("The wet letter refuses to dry.", true); - if (id === "photo") remember("A photo shows you beside someone whose face keeps changing.", true); - if (id === "cinderKey") state.flags.chapelKey = true; - say("Found", `${items[id].name} added to your bag.`); - save(); - } - - function talk(id) { - audio.sfx("talk"); - if (id === "mira") { - state.companion = true; - state.flags.metMira = true; - remember("Mira remembers what other people lose.", true); - say("Mira", state.forgets > 1 ? "You keep looking through people like windows. Don't do that to me, okay?" : "You look lost. Good. Everyone interesting does."); - } - if (id === "ferryman") { - remember("The Ferryman knows where you have been before you arrive.", state.forgets < 2); - say("Ferryman", state.toldName ? `${state.toldName} is a fine name to keep away from water.` : "Names are hooks. I would not throw yours into the lake."); - } - if (id === "vendor") say("Maribel", "Today's special: one jar of fog, two apologies, and a fish that predicts Thursday badly."); - if (id === "performer") { - remember("A performer advertised a show called YOUR MISSING YEARS.", state.forgets < 3); - say("Velvet Noon", "Clap if you remember me. Clap twice if you are pretending."); - } - if (id === "archivist") { - state.flags.rootsTruth = true; - remember("The lake feeds on suffering, but calls it mercy.", true); - say("Memory Archivist", "The archives are homes with the people removed. The lake calls that tidying."); - } - if (id === "cinder") { - state.flags.cinderMet = true; - remember("Brother Cinder believes forgetting pain is mercy.", true); - say("Brother Cinder", "I have seen grief make animals of gentle people. The lake did not begin as a hunger. It began as a kindness."); - } - if (id === "sibling") { - remember("Your sibling entered the lake willingly.", true); - say("Your Sibling", "I gave it my name because I was tired of carrying it. Please don't give it yours."); - finalChoice(); - } - save(); - } - - function inspectProp(prop) { - audio.sfx("talk"); - if (prop.kind === "lake") say("Black Lake", state.toldName ? `It almost says ${state.toldName}. Almost.` : "It reflects nothing. Not clouds. Not you."); - if (prop.kind === "stall") say("Market Sign", "Fresh eels. Older than regret. Discount if they remember your birthday."); - if (prop.kind === "flowers") say("Glowing Flowers", "They lean toward the water like they are listening."); - if (prop.kind === "sign") say("Advertisement", "TONIGHT: The Amazing Vanishing Audience. Refunds unavailable after erasure."); - if (prop.kind === "stage") say("Stage", "The curtain twitches though there is no wind indoors."); - if (prop.kind === "poster") say("Poster", "A smiling face has been scratched out. The scratch is smiling too."); - if (prop.kind === "archive") { - state.flags.rootsTruth = true; - remember("Some memory shelves are empty because nobody dared open them.", true); - say("Memory Archive", "A drawer opens by itself. Inside: houses, birthdays, funeral songs, a ticket stub from a train that never arrived."); - } - if (prop.kind === "root") say("Roots", "They pulse slowly under the town, carrying lake-water like blood."); - if (prop.kind === "home") say("Abandoned Home", "Every room is furnished except for the idea of a family."); - if (prop.kind === "pew") say("Drowned Pew", "The wood is wet. The prayer book is dry."); - if (prop.kind === "glass") say("Sunken Window", "Saints in blue glass lower their eyes toward the lake."); - if (prop.kind === "rift") finalChoice(); - save(); - } - - function tellName(name) { - state.toldName = name.slice(0, 18); - remember(`You told Morrow's End your name is ${state.toldName}.`, false); - say("Morrow's End", `The town repeats ${state.toldName} very softly. Too softly.`); - state.route = state.route === "Undecided" ? "Named" : state.route; - save(); - } - - function startBattle(id) { - const e = encounters[id]; - state.mode = "battle"; - state.battle = { - id, - hp: e.hp, - maxHp: e.hp, - soul: { x: 480, y: 382 }, - bullets: [], - t: 0, - grace: 0, - turn: 1, - message: e.text - }; - audio.setTrack("battle"); - audio.sfx("battle"); - say(e.title, e.text); - save(); - } - - function chooseBattle(action) { - if (state.mode !== "battle" || !state.battle) return; - const battle = state.battle; - if (action === "confront") { - battle.hp -= 9; - state.confronts += 1; - state.route = "Confronting"; - battle.message = "You refuse to move. The memory recoils from your shape."; - audio.sfx("hit"); - } - if (action === "listen") { - battle.hp -= 7; - state.listens += 1; - state.route = state.forgets === 0 ? "Carry" : "Listening"; - battle.message = "You listen until the ache becomes words. The attack softens."; - remember(`Listened to ${encounters[battle.id].title}.`, true); - audio.sfx("listen"); - } - if (action === "forget") { - battle.hp -= 18; - state.forgets += 1; - state.route = state.forgets >= 3 ? "Forgotten" : "Silence"; - battle.message = "You let the lake take the hard part. Something else goes with it."; - rewriteMemory(); - audio.sfx("forget"); - } - battle.turn += 1; - if (battle.hp <= 0) resolveBattle(action); - else say(encounters[battle.id].title, battle.message); - save(); - } - - function resolveBattle(action) { - const id = state.battle.id; - state.flags[`encounter_${id}`] = action; - if (id === "root_husk") state.flags.rootsTruth = true; - if (action === "forget" && state.forgets >= 3) return end("forgotten"); - const line = action === "listen" - ? "The memory steps aside, still hurting, but no longer alone." - : action === "forget" - ? "The memory becomes easy to pass. Behind you, a street sign loses a letter." - : "The memory breaks apart like ash under rain."; - leaveBattle(line); - } - - function leaveBattle(line) { - state.mode = "world"; - state.battle = null; - audio.setTrack(areas[state.area].track); - say("Afterimage", line); - save(); - } - - function finalChoice() { - if (state.forgets >= 3) return end("forgotten"); - if (state.forgets === 0 && state.listens >= 3 && state.flags.metMira) return end("rest"); - if (state.route === "Silence" || state.forgets > state.listens) return end("silence"); - end("carry"); - } - - function end(kind) { - state.ending = kind; - state.mode = "ending"; - state.route = kind === "rest" ? "True: Rest" : kind === "carry" ? "Carry" : kind === "silence" ? "Silence" : "Forgotten"; - audio.setTrack(kind === "forgotten" ? "void" : "ending"); - say("Ending", endingLine(kind)); - remember(endingLine(kind), kind !== "forgotten"); - save(); - } - - function endingLine(kind) { - if (kind === "silence") return "Silence: you join the lake. Morrow's End becomes peaceful, clean, and almost empty."; - if (kind === "carry") return "Carry: the lake breaks. Everyone remembers. The town survives with tears in every lit window."; - if (kind === "forgotten") return "Forgotten: the story continues politely around the space where Ilyas used to be."; - return "Rest: the lake is forgiven, not destroyed. Mira laughs once, real and afraid, as the shore finally reflects the sky."; - } - - function rewriteMemory() { - const mutable = state.memories.find((memory) => !memory.stable && !memory.erased) || state.memories.find((memory) => !memory.erased); - if (mutable) { - mutable.text = state.forgets > 2 ? "There was someone here. There was no one here. Both feel true." : mutable.text.replace(/[aeiou]/gi, "_"); - mutable.erased = state.forgets > 2; - } - if (state.forgets === 2) state.flags.vendorForgotten = true; - if (state.forgets >= 3) state.flags.performerForgotten = true; - } - - function forgottenNpc(id) { - return (id === "vendor" && state.flags.vendorForgotten) || (id === "performer" && state.flags.performerForgotten); - } - - function remember(text, stable) { - if (!state.memories.some((memory) => memory.text === text)) state.memories.push({ id: `m${Date.now()}`, text, stable: Boolean(stable) }); - if (state.memories.length > 8) state.memories.splice(2, 1); - } - - function say(speaker, line) { - state.dialogue = { speaker, line }; - } - - function areaEnterLine(area) { - if (area === "red") return "The Red Streets glow warmly, but the laughter arrives half a second late."; - if (area === "roots") return "The Roots smell like wet stone and bedrooms left untouched for years."; - if (area === "chapel") return "The Drowned Chapel rings with a bell under water."; - if (area === "shore") return "The Hollow Shore bends at the edges, as if the world has been folded too many times."; - return "Harbor Row keeps its lamps low and its names lower."; - } - - function draw(now) { - ctx.imageSmoothingEnabled = false; - if (!state.started || state.mode === "title") drawTitle(now); - else drawWorld(now); - if (state.mode === "battle") drawBattle(now); - if (state.mode === "ending") drawEnding(now); - drawVignette(now); - } - - function drawTitle(now) { - const g = ctx.createLinearGradient(0, 0, 0, canvas.height); - g.addColorStop(0, "#05070c"); - g.addColorStop(0.55, "#101b28"); - g.addColorStop(1, "#020306"); - ctx.fillStyle = g; - ctx.fillRect(0, 0, canvas.width, canvas.height); - drawLake(0, 345, 960, 195, now, 0.4); - ctx.fillStyle = "#f8efe0"; - ctx.font = "700 50px Georgia, serif"; - ctx.textAlign = "center"; - ctx.fillText("Ash Below the Lake", 480, 142); - ctx.font = "18px Georgia, serif"; - ctx.fillStyle = "#b9c6d4"; - ctx.fillText("A quiet town. A black lake. A name you should not give away.", 480, 178); - ctx.fillStyle = "#e0a451"; - ctx.font = "700 18px ui-monospace, monospace"; - ctx.fillText("Press Start or Space", 480, 246); - drawSprite("ilyas", 424, 288, 3, now); - drawSprite("mira", 500, 288, 3, now); - } - - function drawWorld(now) { - const area = areas[state.area]; - for (let y = 0; y < rows; y += 1) { - for (let x = 0; x < cols; x += 1) { - const wall = area.walls.has(`${x},${y}`); - const c = area.floor[(x + y) % area.floor.length]; - ctx.fillStyle = wall ? "#0b0d10" : c; - ctx.fillRect(x * tile, y * tile, tile, tile); - if (!wall) { - ctx.fillStyle = (x + y) % 2 === 0 ? "rgba(255,255,255,0.018)" : "rgba(0,0,0,0.035)"; - ctx.fillRect(x * tile, y * tile + 30, tile, 2); - } - } - } - area.exits.forEach((exit) => drawDoor(exit.x, exit.y, area.accent)); - area.props.forEach((prop) => drawProp(prop, now)); - Object.keys(items).forEach((id) => { - const item = items[id]; - if (item.area === state.area && !state.inventory.includes(id)) drawItem(id, item, now); - }); - Object.keys(encounters).forEach((id) => { - const e = encounters[id]; - if (e.area === state.area && !state.flags[`encounter_${id}`]) drawEncounter(e, now); - }); - Object.keys(npcs).forEach((id) => { - const npc = npcs[id]; - if (npc.area === state.area && !forgottenNpc(id)) drawSprite(npc.character, npc.x * tile, npc.y * tile - 12, 2, now); - }); - drawSprite("ilyas", state.player.x * tile, state.player.y * tile - 12, 2, now); - if (state.companion && state.area !== "chapel") { - const offset = state.player.facing === "left" ? 1 : -1; - drawSprite("mira", (state.player.x + offset) * tile, state.player.y * tile - 12, 2, now + 200); - } - drawTopBar(area); - } - - function drawTopBar(area) { - ctx.fillStyle = "rgba(3, 6, 10, 0.82)"; - ctx.fillRect(0, 0, canvas.width, 42); - ctx.fillStyle = area.accent; - ctx.font = "700 14px ui-monospace, monospace"; - ctx.textAlign = "left"; - ctx.fillText(`${area.act} / ${area.name}`, 18, 26); - ctx.textAlign = "right"; - ctx.fillStyle = "#f8efe0"; - ctx.fillText(`Resolve ${state.resolve} Lake ${state.forgets}`, 940, 26); - } - - function drawBattle(now) { - const battle = state.battle; - battle.t += 1; - updateBullets(battle, now); - ctx.fillStyle = "rgba(4, 5, 8, 0.88)"; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.strokeStyle = "#f8efe0"; - ctx.lineWidth = 3; - ctx.strokeRect(296, 252, 368, 190); - ctx.fillStyle = "#f8efe0"; - ctx.font = "700 24px Georgia, serif"; - ctx.textAlign = "center"; - ctx.fillText(encounters[battle.id].title, 480, 82); - ctx.font = "16px ui-monospace, monospace"; - ctx.fillText(`Memory ${Math.max(0, battle.hp)} / ${battle.maxHp}`, 480, 116); - drawBattleEnemy(battle.id, now); - ctx.fillStyle = "#f35f5f"; - ctx.beginPath(); - ctx.moveTo(battle.soul.x, battle.soul.y - 8); - ctx.bezierCurveTo(battle.soul.x - 13, battle.soul.y - 17, battle.soul.x - 20, battle.soul.y + 4, battle.soul.x, battle.soul.y + 18); - ctx.bezierCurveTo(battle.soul.x + 20, battle.soul.y + 4, battle.soul.x + 13, battle.soul.y - 17, battle.soul.x, battle.soul.y - 8); - ctx.fill(); - ctx.fillStyle = "#dbeafe"; - battle.bullets.forEach((b) => { - ctx.beginPath(); - ctx.arc(b.x, b.y, b.r, 0, Math.PI * 2); - ctx.fill(); - }); - ctx.fillStyle = "#f8efe0"; - ctx.font = "15px Georgia, serif"; - wrapText(battle.message, 148, 480, 660, 22); - } - - function updateBullets(battle) { - const speed = state.forgets > 0 ? 1.15 : 1.55; - const move = keyDirection(); - if (move === "up") battle.soul.y -= 3.3; - if (move === "down") battle.soul.y += 3.3; - if (move === "left") battle.soul.x -= 3.3; - if (move === "right") battle.soul.x += 3.3; - battle.soul.x = clamp(battle.soul.x, 312, 648); - battle.soul.y = clamp(battle.soul.y, 270, 426); - if (battle.t % 26 === 0) { - if (encounters[battle.id].bullets === "rain") battle.bullets.push({ x: 315 + Math.random() * 330, y: 256, vx: 0, vy: speed * 2.4, r: 5 }); - else if (encounters[battle.id].bullets === "spiral") { - const a = battle.t / 14; - battle.bullets.push({ x: 480, y: 346, vx: Math.cos(a) * speed * 2, vy: Math.sin(a) * speed * 2, r: 5 }); - } else battle.bullets.push({ x: Math.random() > 0.5 ? 304 : 656, y: 275 + Math.random() * 130, vx: (Math.random() > 0.5 ? -1 : 1) * speed * 2, vy: Math.sin(battle.t) * 0.6, r: 5 }); - } - battle.bullets.forEach((b) => { - b.x += b.vx; - b.y += b.vy; - if (battle.grace <= 0 && Math.hypot(b.x - battle.soul.x, b.y - battle.soul.y) < b.r + 10) { - state.resolve = Math.max(0, state.resolve - 1); - battle.grace = 24; - audio.sfx("hurt"); - if (state.resolve <= 0) end("forgotten"); - } - }); - battle.grace -= 1; - battle.bullets = battle.bullets.filter((b) => b.x > 280 && b.x < 680 && b.y > 230 && b.y < 455); - } - - function renderHud() { - root.classList.toggle("is-lake-awake", state.forgets > 1); - root.classList.toggle("is-ending", Boolean(state.ending)); - els.speaker.textContent = state.dialogue.speaker; - els.line.textContent = state.dialogue.line; - els.area.textContent = areas[state.area].name; - els.route.textContent = state.route; - els.resolve.textContent = String(state.resolve); - ui.memoryFill.style.width = `${Math.min(100, state.forgets * 34)}%`; - els.inventory.innerHTML = state.inventory.length ? state.inventory.map((id) => `
d&&w*r+b*n 1&&(o[1]=Math.floor(l/3)%3-1,p>2&&(o[2]=Math.floor(l/9)%3-1,p>3&&(o[3]=Math.floor(l/27)%3-1))),s=c(p,n,o,e,i),h=f(p,o,a,s),u){case 0:ht?r.ORIENTATION.PORTRAIT:r.ORIENTATION.LANDSCAPE}},74403(t){t.exports=function(t){var e;return""!==t&&("string"==typeof t?e=document.getElementById(t):t&&1===t.nodeType&&(e=t)),e||(e=document.body),e}},56836(t){t.exports=function(t){var e="";try{if(window.DOMParser)e=(new DOMParser).parseFromString(t,"text/xml");else(e=new ActiveXObject("Microsoft.XMLDOM")).loadXML(t)}catch(t){e=null}return e&&e.documentElement&&!e.getElementsByTagName("parsererror").length?e:null}},35846(t){t.exports=function(t){t.parentNode&&t.parentNode.removeChild(t)}},43092(t,e,i){var r=i(83419),s=i(29747),n=new r({initialize:function(){this.isRunning=!1,this.callback=s,this.isSetTimeOut=!1,this.timeOutID=null,this.delay=0;var t=this;this.step=function e(i){t.callback(i),t.isRunning&&(t.timeOutID=window.requestAnimationFrame(e))},this.stepTimeout=function e(){t.isRunning&&(t.timeOutID=window.setTimeout(e,t.delay)),t.callback(window.performance.now())}},start:function(t,e,i){this.isRunning||(this.callback=t,this.isSetTimeOut=e,this.delay=i,this.isRunning=!0,this.timeOutID=e?window.setTimeout(this.stepTimeout,0):window.requestAnimationFrame(this.step))},stop:function(){this.isRunning=!1,this.isSetTimeOut?clearTimeout(this.timeOutID):window.cancelAnimationFrame(this.timeOutID)},destroy:function(){this.stop(),this.callback=s}});t.exports=n},84902(t,e,i){var r={AddToDOM:i(40366),DOMContentLoaded:i(57264),GetInnerHeight:i(57811),GetScreenOrientation:i(45818),GetTarget:i(74403),ParseXML:i(56836),RemoveFromDOM:i(35846),RequestAnimationFrame:i(43092)};t.exports=r},47565(t,e,i){var r=i(83419),s=i(50792),n=i(37277),a=new r({Extends:s,initialize:function(){s.call(this)},shutdown:function(){this.removeAllListeners()},destroy:function(){this.removeAllListeners()}});n.register("EventEmitter",a,"events"),t.exports=a},93055(t,e,i){t.exports={EventEmitter:i(47565)}},10189(t,e,i){var r=i(83419),s=i(13045),n=new r({Extends:s,initialize:function(t,e){void 0===e&&(e=1),s.call(this,t,"FilterBarrel"),this.amount=e}});t.exports=n},16762(t,e,i){var r=i(83419),s=i(13045),n=new r({Extends:s,initialize:function(t,e,i,r,n){void 0===e&&(e="__WHITE"),void 0===i&&(i=0),void 0===r&&(r=1),void 0===n&&(n=[1,1,1,1]),s.call(this,t,"FilterBlend"),this.glTexture,this.blendMode=i,this.amount=r,this.color=n,this.setTexture(e)},setTexture:function(t){var e=this.camera.scene.sys.textures.getFrame(t);return e&&(this.glTexture=e.glTexture),this}});t.exports=n},37597(t,e,i){var r=i(83419),s=i(13045),n=new r({Extends:s,initialize:function(t,e){s.call(this,t,"FilterBlocky"),this.size={x:4,y:4},this.offset={x:0,y:0},e&&(void 0!==e.size&&("number"==typeof e.size?(this.size.x=e.size,this.size.y=e.size):(this.size.x=e.size.x,this.size.y=e.size.y)),void 0!==e.offset&&("number"==typeof e.offset?(this.offset.x=e.offset,this.offset.y=e.offset):(this.offset.x=e.offset.x,this.offset.y=e.offset.y)))}});t.exports=n},88344(t,e,i){var r=i(83419),s=i(13045),n=new r({Extends:s,initialize:function(t,e,i,r,n,a,o){void 0===e&&(e=0),void 0===i&&(i=2),void 0===r&&(r=2),void 0===n&&(n=1),void 0===o&&(o=4),s.call(this,t,"FilterBlur"),this.quality=e,this.x=i,this.y=r,this.strength=n,this.glcolor=[1,1,1],null!=a&&(this.color=a),this.steps=o},color:{get:function(){var t=this.glcolor;return(255*t[0]<<16)+(255*t[1]<<8)+(255*t[2]|0)},set:function(t){var e=this.glcolor;e[0]=(t>>16&255)/255,e[1]=(t>>8&255)/255,e[2]=(255&t)/255}},getPadding:function(){var t=this.paddingOverride;if(t)return this.currentPadding.setTo(t.x,t.y,t.width,t.height),t;var e=this.quality,i=0===e?1.333:1===e?3.2307692308:5.176470588235294,r=this.steps*this.strength*i,s=Math.ceil(this.x*r),n=Math.ceil(this.y*r);return this.currentPadding.setTo(-s,-n,2*s,2*n),this.currentPadding}});t.exports=n},47564(t,e,i){var r=i(83419),s=i(13045),n=new r({Extends:s,initialize:function(t,e,i,r,n,a,o,h){void 0===e&&(e=.5),void 0===i&&(i=1),void 0===r&&(r=.2),void 0===n&&(n=!1),void 0===a&&(a=1),void 0===o&&(o=1),void 0===h&&(h=1),s.call(this,t,"FilterBokeh"),this.radius=e,this.amount=i,this.contrast=r,this.isTiltShift=n,this.blurX=a,this.blurY=o,this.strength=h},getPadding:function(){var t=this.paddingOverride;if(t)return this.currentPadding.setTo(t.x,t.y,t.width,t.height),t;var e=Math.ceil(this.camera.height*this.radius*.021426096060426905);return this.currentPadding.setTo(-e,-e,2*e,2*e),this.currentPadding}});t.exports=n},77011(t,e,i){var r=i(83419),s=i(13045),n=i(89422),a=new r({Extends:s,initialize:function(t){s.call(this,t,"FilterColorMatrix"),this.colorMatrix=new n},destroy:function(){this.colorMatrix=null,s.prototype.destroy.call(this)}});t.exports=a},95200(t,e,i){var r=i(83419),s=i(13045),n=i(89422),a=new r({Extends:s,initialize:function(t,e){s.call(this,t,"FilterCombineColorMatrix"),this.glTexture,this.colorMatrixSelf=new n,this.colorMatrixTransfer=new n,this.additions=[1,1,1,0],this.multiplications=[0,0,0,1],this.setTexture(e||"__WHITE")},setTexture:function(t){var e=t instanceof Phaser.Textures.Texture?t:this.camera.scene.sys.textures.getFrame(t);return e&&(this.glTexture=e.glTexture),this},setupAlphaTransfer:function(t,e,i,r,s,n){var a=this.colorMatrixSelf,o=this.colorMatrixTransfer;a.reset(),o.reset(),this.additions=[1,1,1,0],this.multiplications=[0,0,0,1],t||a.black(),e||o.black(),s?a.brightnessToAlphaInverse(!0):i&&a.brightnessToAlpha(!0),n?o.brightnessToAlphaInverse(!0):r&&o.brightnessToAlpha(!0)},destroy:function(){this.colorMatrixSelf=null,this.colorMatrixTransfer=null,s.prototype.destroy.call(this)}});t.exports=a},13045(t,e,i){var r=i(83419),s=i(87841),n=new r({initialize:function(t,e){this.active=!0,this.camera=t,this.renderNode=e,this.paddingOverride=new s,this.currentPadding=new s,this.allowBaseDraw=!0,this.ignoreDestroy=!1},getPadding:function(){return this.paddingOverride||this.currentPadding},getPaddingCeil:function(){var t=this.getPadding(),e=new s(Math.ceil(t.x),Math.ceil(t.y),Math.ceil(t.width),Math.ceil(t.height));return this.currentPadding.setTo(e.x,e.y,e.width,e.height),e},setPaddingOverride:function(t,e,i,r){return null===t?(this.paddingOverride=null,this):(void 0===t&&(t=0),void 0===e&&(e=0),void 0===i&&(i=0),void 0===r&&(r=0),this.paddingOverride=new s(t,e,i-t,r-e),this)},setActive:function(t){return this.active=t,this},destroy:function(){this.active=!1,this.renderNode=null,this.camera=null}});t.exports=n},16898(t,e,i){var r=i(83419),s=i(13045),n=new r({Extends:s,initialize:function(t,e,i,r){void 0===e&&(e="__WHITE"),void 0===i&&(i=.005),void 0===r&&(r=.005),s.call(this,t,"FilterDisplacement"),this.x=i,this.y=r,this.glTexture,this.setTexture(e)},setTexture:function(t){var e=this.camera.scene.sys.textures.getFrame(t);return e&&(this.glTexture=e.glTexture),this},getPadding:function(){var t=this.paddingOverride;if(t)return this.currentPadding.setTo(t.x,t.y,t.width,t.height),t;var e=this.camera,i=Math.ceil(e.width*this.x*.5),r=Math.ceil(e.height*this.y*.5);return this.currentPadding.setTo(-i,-r,2*i,2*r),this.currentPadding}});t.exports=n},42652(t,e,i){var r=i(83419),s=i(13045),n=new r({Extends:s,initialize:function(t,e,i,r,n,a,o,h){void 0===i&&(i=4),void 0===r&&(r=0),void 0===n&&(n=1),void 0===a&&(a=!1),void 0===o&&(o=t.scene.sys.game.config.glowQuality),void 0===h&&(h=t.scene.sys.game.config.glowDistance),s.call(this,t,"FilterGlow"),this.outerStrength=i,this.innerStrength=r,this.scale=n,this.knockout=a,this._quality=Math.max(Math.round(o),1),this._distance=Math.max(Math.round(h),1),this.glcolor=[1,1,1,1],void 0!==e&&(this.color=e)},color:{get:function(){var t=this.glcolor;return(255*t[0]<<16)+(255*t[1]<<8)+(255*t[2]|0)},set:function(t){var e=this.glcolor;e[0]=(t>>16&255)/255,e[1]=(t>>8&255)/255,e[2]=(255&t)/255}},distance:{get:function(){return this._distance}},quality:{get:function(){return this._quality}},getPadding:function(){var t=this.paddingOverride;if(t)return this.currentPadding.setTo(t.x,t.y,t.width,t.height),t;var e=this.currentPadding,i=Math.ceil(this.distance*this.scale);return e.left=-i,e.top=-i,e.right=i,e.bottom=i,e}});t.exports=n},43927(t,e,i){var r=i(83419),s=i(13045),n=i(73043),a=new r({Extends:s,initialize:function(t,e){e||(e={});var i=t.scene;s.call(this,t,"FilterGradientMap");var r=e.ramp;r||(r={colorStart:0,colorEnd:16777215}),r instanceof n||(r=new n(i,r,!0)),this.ramp=r,this.dither=!!e.dither,this.color=[0,0,0,0],e.color&&(this.color[0]=e.color[0]||0,this.color[1]=e.color[1]||0,this.color[2]=e.color[2]||0,this.color[3]=e.color[3]||0),this.colorFactor=[.3,.6,.1,0],e.colorFactor&&(this.colorFactor[0]=e.colorFactor[0]||0,this.colorFactor[1]=e.colorFactor[1]||0,this.colorFactor[2]=e.colorFactor[2]||0,this.colorFactor[3]=e.colorFactor[3]||0),this.unpremultiply=void 0===e.unpremultiply||e.unpremultiply,this.alpha=void 0===e.alpha?1:e.alpha}});t.exports=a},84714(t,e,i){var r=i(83419),s=i(13045),n=i(37867),a=i(61340),o=new r({Extends:s,initialize:function(t,e){s.call(this,t,"FilterImageLight"),this.normalGlTexture,this.environmentGlTexture,this.viewMatrix=new n,this.modelRotation=e.modelRotation||0,this.modelRotationSource=e.modelRotationSource||null,this.bulge=e.bulge||0,this.colorFactor=e.colorFactor||[1,1,1],this._tempMatrix=new a,this._tempParentMatrix=new a,this.setEnvironmentMap(e.environmentMap||"__WHITE"),this.setNormalMap(e.normalMap||"__NORMAL"),e.viewMatrix&&this.viewMatrix.set(e.viewMatrix)},setEnvironmentMap:function(t){var e=t instanceof Phaser.Textures.Texture?t:this.camera.scene.sys.textures.getFrame(t);return e&&(this.environmentGlTexture=e.glTexture),this},setNormalMap:function(t){var e=t instanceof Phaser.Textures.Texture?t:this.camera.scene.sys.textures.getFrame(t);return e&&(this.normalGlTexture=e.glTexture),this},setNormalMapFromGameObject:function(t){var e=t.texture.dataSource[0];return e&&(this.normalGlTexture=e.glTexture),this},getModelRotation:function(){return this.modelRotationSource?"function"==typeof this.modelRotationSource?this.modelRotationSource():this.modelRotationSource.hasTransformComponent?this.modelRotationSource.getWorldTransformMatrix(this._tempMatrix,this._tempParentMatrix).rotationNormalized:this.modelRotation:this.modelRotation}});t.exports=o},51890(t,e,i){var r=i(83419),s=i(13045),n=i(40987),a=new r({Extends:s,initialize:function(t,e){void 0===e&&(e={}),s.call(this,t,"FilterKey"),this.color=[1,1,1,1],void 0!==e.color&&this.setColor(e.color),void 0!==e.alpha&&this.setAlpha(e.alpha),this.isolate=!1,void 0!==e.isolate&&(this.isolate=e.isolate),this.threshold=.0625,void 0!==e.threshold&&(this.threshold=e.threshold),this.feather=0,void 0!==e.feather&&(this.feather=e.feather)},setAlpha:function(t){return this.color[3]=t,this},setColor:function(t){var e=this.color[3];if("number"==typeof t){var i=n.IntegerToRGB(t);this.color=[i.r/255,i.g/255,i.b/255,e]}else if("string"==typeof t){var r=n.HexStringToColor(t);this.color=[r.redGL,r.greenGL,r.blueGL,e]}else Array.isArray(t)?this.color=[t[0],t[1],t[2],e]:t instanceof n&&(this.color=[t.redGL,t.greenGL,t.blueGL,e]);return this}});t.exports=a},97797(t,e,i){var r=i(83419),s=i(45650),n=i(13045),a=new r({Extends:n,initialize:function(t,e,i,r,s,a){void 0===e&&(e="__WHITE"),void 0===i&&(i=!1),void 0===a&&(a=1),n.call(this,t,"FilterMask"),this.glTexture,this._dynamicTexture=null,this.maskGameObject=null,this.invert=i,this.autoUpdate=!0,this.needsUpdate=!1,this.viewTransform=s||"world",this.viewCamera=r,this.scaleFactor=a,"string"==typeof e?this.setTexture(e):this.setGameObject(e)},updateDynamicTexture:function(t,e){var i=this.scaleFactor,r=t*i,n=e*i,a=this.maskGameObject;if(a){if(this._dynamicTexture)this._dynamicTexture.width!==r||this._dynamicTexture.height!==n?this._dynamicTexture.setSize(r,n,!1):this._dynamicTexture.clear();else{var o=this.camera.scene.sys.textures;this._dynamicTexture=o.addDynamicTexture(s(),r,n,!1)}this.glTexture=this._dynamicTexture.get().glTexture;var h=this.viewCamera||a.scene.renderer.currentViewCamera;this._dynamicTexture.capture(a,{transform:this.viewTransform,camera:h}),this._dynamicTexture.render(),this.needsUpdate=!1}},setGameObject:function(t){return this.maskGameObject=t,this.needsUpdate=!0,this},setTexture:function(t){var e=this.camera.scene.sys.textures.getFrame(t);return e&&(this.maskGameObject=null,this.glTexture=e.glTexture),this},destroy:function(){this._dynamicTexture&&this._dynamicTexture.destroy(),this.maskGameObject=null,this._dynamicTexture=null,n.prototype.destroy.call(this)}});t.exports=a},37911(t,e,i){var r=i(83419),s=i(13045),n=i(37867),a=i(25836),o=new r({Extends:s,initialize:function(t,e){e=e||{},s.call(this,t,"FilterNormalTools"),this._rotation=0,this.viewMatrix=new n,this.setRotation(e.rotation||0),this.rotationSource=e.rotationSource||null,this.facingPower=e.facingPower||1,this.outputRatio=e.outputRatio||!1,this.ratioVector=new a(0,0,1),e.ratioVector&&this.ratioVector.set(e.ratioVector[0],e.ratioVector[1],e.ratioVector[2]),this.ratioRadius=e.ratioRadius||1},getRotation:function(){if(this.rotationSource){if("function"==typeof this.rotationSource)return this.rotationSource();if(this.rotationSource.hasTransformComponent)return this.rotationSource.getWorldTransformMatrix().rotationNormalized}return this._rotation},setRotation:function(t){return this.viewMatrix.identity().rotateZ(t),this._rotation=t,this},updateRotation:function(){if(this.rotationSource){var t=this.getRotation();this.viewMatrix.identity().rotateZ(t),this._rotation=t}return this}});t.exports=o},6379(t,e,i){var r=i(83419),s=i(13045),n=new r({Extends:s,initialize:function(t,e){void 0===e&&(e={}),s.call(this,t,"FilterPanoramaBlur"),this.radius=e.radius||1,this.samplesX=e.samplesX||32,this.samplesY=e.samplesY||16,this.power=e.power||1}});t.exports=n},2195(t,e,i){var r=i(83419),s=i(53427),n=i(13045),a=i(16762),o=new r({Extends:n,initialize:function(t){n.call(this,t,"FilterParallelFilters"),this.top=new s(t),this.bottom=new s(t),this.blend=new a(t)}});s.prototype.addParallelFilters=function(){return this.add(new o(this.camera))},t.exports=o},29861(t,e,i){var r=i(83419),s=i(13045),n=new r({Extends:s,initialize:function(t,e){void 0===e&&(e=1),s.call(this,t,"FilterPixelate"),this.amount=e}});t.exports=n},14366(t,e,i){var r=i(83419),s=i(13045),n=new r({Extends:s,initialize:function(t,e){e||(e={}),s.call(this,t,"FilterQuantize"),this.steps=[8,8,8,8],e.steps&&(this.steps[0]=e.steps[0],this.steps[1]=e.steps[1],this.steps[2]=e.steps[2],this.steps[3]=e.steps[3]),this.gamma=[1,1,1,1],e.gamma&&(this.gamma[0]=e.gamma[0],this.gamma[1]=e.gamma[1],this.gamma[2]=e.gamma[2],this.gamma[3]=e.gamma[3]),this.offset=[0,0,0,0],e.offset&&(this.offset[0]=e.offset[0],this.offset[1]=e.offset[1],this.offset[2]=e.offset[2],this.offset[3]=e.offset[3]),this.mode=e.mode||0,this.dither=!!e.dither}});t.exports=n},63785(t,e,i){var r=i(83419),s=i(13045),n=new r({Extends:s,initialize:function(t,e,i){void 0===i&&(i=null),s.call(this,t,"FilterSampler"),this.allowBaseDraw=!1,this.callback=e,this.region=i}});t.exports=n},62229(t,e,i){var r=i(83419),s=i(13045),n=new r({Extends:s,initialize:function(t,e,i,r,n,a,o,h){void 0===e&&(e=0),void 0===i&&(i=0),void 0===r&&(r=.1),void 0===n&&(n=1),void 0===o&&(o=6),void 0===h&&(h=1),s.call(this,t,"FilterShadow"),this.x=e,this.y=i,this.decay=r,this.power=n,this.glcolor=[0,0,0,1],this.samples=o,this.intensity=h,void 0!==a&&(this.color=a)},color:{get:function(){var t=this.glcolor;return(255*t[0]<<16)+(255*t[1]<<8)+(255*t[2]|0)},set:function(t){var e=this.glcolor;e[0]=(t>>16&255)/255,e[1]=(t>>8&255)/255,e[2]=(255&t)/255}},getPadding:function(){var t=this.paddingOverride;if(t)return this.currentPadding.setTo(t.x,t.y,t.width,t.height),t;var e=this.camera,i=this.decay*this.intensity,r=Math.ceil(Math.abs(this.x)*e.width*i),s=Math.ceil(Math.abs(this.y)*e.height*i);return this.currentPadding.setTo(-r,-s,2*r,2*s),this.currentPadding}});t.exports=n},99534(t,e,i){var r=i(83419),s=i(13045),n=new r({Extends:s,initialize:function(t,e,i,r){s.call(this,t,"FilterThreshold"),this.edge1=[.5,.5,.5,.5],this.edge2=[.5,.5,.5,.5],this.invert=[!1,!1,!1,!1],this.setEdge(e,i),this.setInvert(r)},setEdge:function(t,e){void 0===t&&(t=.5),"number"==typeof t&&(t=[t,t,t,t]),this.edge1[0]=t[0],this.edge1[1]=t[1],this.edge1[2]=t[2],this.edge1[3]=t[3],void 0===e&&(e=t),"number"==typeof e&&(e=[e,e,e,e]),this.edge2[0]=e[0],this.edge2[1]=e[1],this.edge2[2]=e[2],this.edge2[3]=e[3];for(var i=0;i<4;i++)if(this.edge1[i]>this.edge2[i]){var r=this.edge1[i];this.edge1[i]=this.edge2[i],this.edge2[i]=r}return this},setInvert:function(t){return void 0===t&&(t=!1),"boolean"==typeof t&&(t=[t,t,t,t]),this.invert[0]=t[0],this.invert[1]=t[1],this.invert[2]=t[2],this.invert[3]=t[3],this}});t.exports=n},20263(t,e,i){var r=i(83419),s=i(13045),n=i(40987),a=new r({Extends:s,initialize:function(t,e,i,r,a,o,h){void 0===e&&(e=.5),void 0===i&&(i=.5),void 0===r&&(r=.5),void 0===a&&(a=.5),void 0===o&&(o=0),void 0===h&&(h=0),s.call(this,t,"FilterVignette"),this.x=e,this.y=i,this.radius=r,this.strength=a,this.color=new n,this.blendMode=h,this.setColor(o)},setColor:function(t){return"number"==typeof t?n.IntegerToColor(t,this.color):"string"==typeof t?n.HexStringToColor(t,this.color):t.setTo?this.color.setTo(t.red,t.green,t.blue,t.alpha):t?this.color.setTo(t.r||0,t.g||0,t.b||0,t.a||255):this.color.setTo(0,0,0,255),this}});t.exports=a},90002(t,e,i){var r=i(83419),s=i(13045),n=i(79237),a=new r({Extends:s,initialize:function(t,e,i,r,n,a){void 0===e&&(e=.1),s.call(this,t,"FilterWipe"),this.progress=0,this.wipeWidth=e,this.direction=i||0,this.axis=r||0,this.reveal=n||0,this.wipeTexture=null,this.setTexture(a)},setWipeWidth:function(t){return void 0===t&&(t=.1),this.wipeWidth=t,this},setLeftToRight:function(){return this.direction=0,this.axis=0,this},setRightToLeft:function(){return this.direction=1,this.axis=0,this},setTopToBottom:function(){return this.direction=1,this.axis=1,this},setBottomToTop:function(){return this.direction=0,this.axis=1,this},setWipeEffect:function(){return this.reveal=0,this.progress=0,this},setRevealEffect:function(){return this.setTexture(),this.reveal=1,this.progress=0,this},setTexture:function(t){return void 0===t&&(t="__DEFAULT"),this.wipeTexture=t instanceof n?t:this.camera.scene.sys.textures.get(t)||this.camera.scene.sys.textures.get("__DEFAULT"),this},setProgress:function(t){return this.progress=t,this}});t.exports=a},11889(t,e,i){var r={Controller:i(13045),Barrel:i(10189),Blend:i(16762),Blocky:i(37597),Blur:i(88344),Bokeh:i(47564),ColorMatrix:i(77011),CombineColorMatrix:i(95200),Displacement:i(16898),Glow:i(42652),GradientMap:i(43927),ImageLight:i(84714),Key:i(51890),Mask:i(97797),NormalTools:i(37911),PanoramaBlur:i(6379),ParallelFilters:i(2195),Pixelate:i(29861),Quantize:i(14366),Sampler:i(63785),Shadow:i(62229),Threshold:i(99534),Vignette:i(20263),Wipe:i(90002)};t.exports=r},25305(t,e,i){var r=i(10312),s=i(23568);t.exports=function(t,e,i){e.x=s(i,"x",0),e.y=s(i,"y",0),e.depth=s(i,"depth",0),e.flipX=s(i,"flipX",!1),e.flipY=s(i,"flipY",!1);var n=s(i,"scale",null);"number"==typeof n?e.setScale(n):null!==n&&(e.scaleX=s(n,"x",1),e.scaleY=s(n,"y",1));var a=s(i,"scrollFactor",null);"number"==typeof a?e.setScrollFactor(a):null!==a&&(e.scrollFactorX=s(a,"x",1),e.scrollFactorY=s(a,"y",1)),e.rotation=s(i,"rotation",0);var o=s(i,"angle",null);null!==o&&(e.angle=o),e.alpha=s(i,"alpha",1);var h=s(i,"origin",null);if("number"==typeof h)e.setOrigin(h);else if(null!==h){var l=s(h,"x",.5),u=s(h,"y",.5);e.setOrigin(l,u)}return e.blendMode=s(i,"blendMode",r.NORMAL),e.visible=s(i,"visible",!0),s(i,"add",!0)&&t.sys.displayList.add(e),e.preUpdate&&t.sys.updateList.add(e),e}},13059(t,e,i){var r=i(23568);t.exports=function(t,e){var i=r(e,"anims",null);if(null===i)return t;if("string"==typeof i)t.anims.play(i);else if("object"==typeof i){var s=t.anims,n=r(i,"key",void 0);if(n){var a=r(i,"startFrame",void 0),o=r(i,"delay",0),h=r(i,"repeat",0),l=r(i,"repeatDelay",0),u=r(i,"yoyo",!1),d=r(i,"play",!1),c=r(i,"delayedPlay",0),f={key:n,delay:o,repeat:h,repeatDelay:l,yoyo:u,startFrame:a};d?s.play(f):c>0?s.playAfterDelay(f,c):s.load(f)}}return t}},8050(t,e,i){var r=i(83419),s=i(73162),n=i(37277),a=i(51708),o=i(44594),h=i(19186),l=new r({Extends:s,initialize:function(t){s.call(this,t),this.sortChildrenFlag=!1,this.scene=t,this.systems=t.sys,this.events=t.sys.events,this.addCallback=this.addChildCallback,this.removeCallback=this.removeChildCallback,this.events.once(o.BOOT,this.boot,this),this.events.on(o.START,this.start,this)},boot:function(){this.events.once(o.DESTROY,this.destroy,this)},addChildCallback:function(t){t.displayList&&t.displayList!==this&&t.removeFromDisplayList(),t.parentContainer&&t.parentContainer.remove(t),t.displayList||(this.queueDepthSort(),t.displayList=this,t.emit(a.ADDED_TO_SCENE,t,this.scene),this.events.emit(o.ADDED_TO_SCENE,t,this.scene))},removeChildCallback:function(t){this.queueDepthSort(),t.displayList=null,t.emit(a.REMOVED_FROM_SCENE,t,this.scene),this.events.emit(o.REMOVED_FROM_SCENE,t,this.scene)},start:function(){this.events.once(o.SHUTDOWN,this.shutdown,this)},queueDepthSort:function(){this.sortChildrenFlag=!0},depthSort:function(){this.sortChildrenFlag&&(h(this.list,this.sortByDepth),this.sortChildrenFlag=!1)},sortByDepth:function(t,e){return t._depth-e._depth},getChildren:function(){return this.list},shutdown:function(){for(var t=this.list,e=t.length;e--;)t[e]&&t[e].destroy(!0);t.length=0,this.events.off(o.SHUTDOWN,this.shutdown,this)},destroy:function(){this.shutdown(),this.events.off(o.START,this.start,this),this.scene=null,this.systems=null,this.events=null}});n.register("DisplayList",l,"displayList"),t.exports=l},95643(t,e,i){var r=i(83419),s=i(31401),n=i(53774),a=i(45893),o=i(50792),h=i(51708),l=i(44594),u=new r({Extends:o,Mixins:[s.Filters,s.RenderSteps],initialize:function(t,e){o.call(this),this.scene=t,this.displayList=null,this.type=e,this.state=0,this.parentContainer=null,this.name="",this.active=!0,this.tabIndex=-1,this.data=null,this.renderFlags=15,this.cameraFilter=0,this.vertexRoundMode="safeAuto",this.input=null,this.body=null,this.ignoreDestroy=!1,this.isDestroyed=!1,this.addRenderStep&&this.addRenderStep(this.renderWebGL),this.on(h.ADDED_TO_SCENE,this.addedToScene,this),this.on(h.REMOVED_FROM_SCENE,this.removedFromScene,this),t.sys.queueDepthSort()},setActive:function(t){return this.active=t,this},setName:function(t){return this.name=t,this},setState:function(t){return this.state=t,this},setDataEnabled:function(){return this.data||(this.data=new a(this)),this},setData:function(t,e){return this.data||(this.data=new a(this)),this.data.set(t,e),this},incData:function(t,e){return this.data||(this.data=new a(this)),this.data.inc(t,e),this},toggleData:function(t){return this.data||(this.data=new a(this)),this.data.toggle(t),this},getData:function(t){return this.data||(this.data=new a(this)),this.data.get(t)},setInteractive:function(t,e,i){return this.scene.sys.input.enable(this,t,e,i),this},disableInteractive:function(t){return void 0===t&&(t=!1),this.scene.sys.input.disable(this,t),this},removeInteractive:function(t){return void 0===t&&(t=!1),this.scene.sys.input.clear(this),t&&this.scene.sys.input.resetCursor(),this.input=void 0,this},addedToScene:function(){},removedFromScene:function(){},update:function(){},toJSON:function(){return n(this)},willRender:function(t){return!(!(!this.displayList||!this.displayList.active||this.displayList.willRender(t))||u.RENDER_MASK!==this.renderFlags||0!==this.cameraFilter&&this.cameraFilter&t.id)},willRoundVertices:function(t,e){switch(this.vertexRoundMode){case"safe":return e;case"safeAuto":return e&&t.roundPixels;case"full":return!0;case"fullAuto":return t.roundPixels;default:return!1}},setVertexRoundMode:function(t){return this.vertexRoundMode=t,this},getIndexList:function(){for(var t=this,e=this.parentContainer,i=[];e&&(i.unshift(e.getIndex(t)),t=e,e.parentContainer);)e=e.parentContainer;return this.displayList?i.unshift(this.displayList.getIndex(t)):i.unshift(this.scene.sys.displayList.getIndex(t)),i},addToDisplayList:function(t){return void 0===t&&(t=this.scene.sys.displayList),this.displayList&&this.displayList!==t&&this.removeFromDisplayList(),t.exists(this)||(this.displayList=t,t.add(this,!0),t.queueDepthSort(),this.emit(h.ADDED_TO_SCENE,this,this.scene),t.events.emit(l.ADDED_TO_SCENE,this,this.scene)),this},addToUpdateList:function(){return this.scene&&this.preUpdate&&this.scene.sys.updateList.add(this),this},removeFromDisplayList:function(){var t=this.displayList||this.scene.sys.displayList;return t&&t.exists(this)&&(t.remove(this,!0),t.queueDepthSort(),this.displayList=null,this.emit(h.REMOVED_FROM_SCENE,this,this.scene),t.events.emit(l.REMOVED_FROM_SCENE,this,this.scene)),this},removeFromUpdateList:function(){return this.scene&&this.preUpdate&&this.scene.sys.updateList.remove(this),this},getDisplayList:function(){var t=null;return this.parentContainer?t=this.parentContainer.list:this.displayList&&(t=this.displayList.list),t},destroy:function(t){this.scene&&!this.ignoreDestroy&&(void 0===t&&(t=!1),this.isDestroyed=!0,this.preDestroy&&this.preDestroy.call(this),this.emit(h.DESTROY,this,t),this.removeAllListeners(),this.removeFromDisplayList(),this.removeFromUpdateList(),this.input&&(this.scene.sys.input.clear(this),this.input=void 0),this.data&&(this.data.destroy(),this.data=void 0),this.body&&(this.body.destroy(),this.body=void 0),this.filterCamera&&(this.filterCamera.destroy(),this.filterCamera=void 0),this.active=!1,this.visible=!1,this.scene=void 0,this.parentContainer=void 0)}});u.RENDER_MASK=15,t.exports=u},44603(t,e,i){var r=i(83419),s=i(37277),n=i(44594),a=new r({initialize:function(t){this.scene=t,this.systems=t.sys,this.events=t.sys.events,this.displayList,this.updateList,this.events.once(n.BOOT,this.boot,this),this.events.on(n.START,this.start,this)},boot:function(){this.displayList=this.systems.displayList,this.updateList=this.systems.updateList,this.events.once(n.DESTROY,this.destroy,this)},start:function(){this.events.once(n.SHUTDOWN,this.shutdown,this)},shutdown:function(){this.events.off(n.SHUTDOWN,this.shutdown,this)},destroy:function(){this.shutdown(),this.events.off(n.START,this.start,this),this.scene=null,this.systems=null,this.events=null,this.displayList=null,this.updateList=null}});a.register=function(t,e){a.prototype.hasOwnProperty(t)||(a.prototype[t]=e)},a.remove=function(t){a.prototype.hasOwnProperty(t)&&delete a.prototype[t]},s.register("GameObjectCreator",a,"make"),t.exports=a},39429(t,e,i){var r=i(83419),s=i(37277),n=i(44594),a=new r({initialize:function(t){this.scene=t,this.systems=t.sys,this.events=t.sys.events,this.displayList,this.updateList,this.events.once(n.BOOT,this.boot,this),this.events.on(n.START,this.start,this)},boot:function(){this.displayList=this.systems.displayList,this.updateList=this.systems.updateList,this.events.once(n.DESTROY,this.destroy,this)},start:function(){this.events.once(n.SHUTDOWN,this.shutdown,this)},existing:function(t){return(t.renderCanvas||t.renderWebGL)&&this.displayList.add(t),t.preUpdate&&this.updateList.add(t),t},shutdown:function(){this.events.off(n.SHUTDOWN,this.shutdown,this)},destroy:function(){this.shutdown(),this.events.off(n.START,this.start,this),this.scene=null,this.systems=null,this.events=null,this.displayList=null,this.updateList=null}});a.register=function(t,e){a.prototype.hasOwnProperty(t)||(a.prototype[t]=e)},a.remove=function(t){a.prototype.hasOwnProperty(t)&&delete a.prototype[t]},s.register("GameObjectFactory",a,"add"),t.exports=a},91296(t,e,i){var r=i(61340),s=new r,n=new r,a=new r,o=new r,h={camera:s,sprite:n,calc:a,cameraExternal:o};t.exports=function(t,e,i,r){return r?o.loadIdentity():o.copyFrom(e.matrixExternal),s.copyWithScrollFactorFrom(r?e.matrix:e.matrixCombined,e.scrollX,e.scrollY,t.scrollFactorX,t.scrollFactorY),a.copyFrom(s),i&&a.multiply(i),n.applyITRS(t.x,t.y,t.rotation,t.scaleX,t.scaleY),a.multiply(n),h}},45027(t,e,i){var r=i(83419),s=i(25774),n=i(37277),a=i(44594),o=new r({Extends:s,initialize:function(t){s.call(this),this.checkQueue=!0,this.scene=t,this.systems=t.sys,t.sys.events.once(a.BOOT,this.boot,this),t.sys.events.on(a.START,this.start,this)},boot:function(){this.systems.events.once(a.DESTROY,this.destroy,this)},start:function(){var t=this.systems.events;t.on(a.PRE_UPDATE,this.update,this),t.on(a.UPDATE,this.sceneUpdate,this),t.once(a.SHUTDOWN,this.shutdown,this)},sceneUpdate:function(t,e){for(var i=this._active,r=i.length,s=0;sF&&(F=I),I
h&&(o=l,h=u)}h>n&&(o-e>1&&r(t,e,o,n,a),a.push(t[o]),s-o>1&&r(t,o,s,n,a))}function s(t,e){var i=t.length-1,s=[t[0]];return r(t,0,i,e,s),s.push(t[i]),s}t.exports=function(t,i,r){void 0===i&&(i=1),void 0===r&&(r=!1);var n=t.points;if(n.length>2){var a=i*i;r||(n=function(t,i){for(var r,s=t[0],n=[s],a=1,o=t.length;an&&(n=h.y),h.y>>0)+2891336453,s=277803737*(r>>>(r>>>28)+4^r),n=(s>>>22^s)>>>0;return n/=f};t.exports=function(t,e){switch("number"==typeof t&&(t=[t]),e){case 2:return p(t,!0);case 1:return p(t);default:return c(t)}}},84854(t,e,i){var r=i(72958),s=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],n=Array(4),a=Array(4),o=Array(4),h=Array(4),l=Array(4),u=Array(4),d=function(t,e,i){var r,s,h,l,u=e.noiseMode||0,d=void 0===e.noiseSmoothing?1:e.noiseSmoothing,p=Math.max(1,Math.min(t.length,4)),g=e.noiseCells||[32,32,32,32].slice(0,p);for(r=0;r0&&u&&t.checkCollision.down&&h&&t.bottom>i&&(o=t.bottom-i)>n&&(o=0),0!==o&&(t.customSeparateY?t.overlapY=o:r(t,o)),o}},2483(t){t.exports=function(t,e){return!(e.right<=t.left||e.bottom<=t.top||e.position.x>=t.right||e.position.y>=t.bottom)}},55173(t,e,i){var r={ProcessTileCallbacks:i(96602),ProcessTileSeparationX:i(36294),ProcessTileSeparationY:i(67013),SeparateTile:i(40012),TileCheckX:i(21329),TileCheckY:i(53442),TileIntersectsBody:i(2483)};t.exports=r},44563(t,e,i){t.exports={Arcade:i(27064),Matter:i(3875)}},68174(t,e,i){var r=i(83419),s=i(26099),n=new r({initialize:function(){this.boundsCenter=new s,this.centerDiff=new s},parseBody:function(t){if(!(t=t.hasOwnProperty("body")?t.body:t).hasOwnProperty("bounds")||!t.hasOwnProperty("centerOfMass"))return!1;var e=this.boundsCenter,i=this.centerDiff,r=t.bounds.max.x-t.bounds.min.x,s=t.bounds.max.y-t.bounds.min.y,n=r*t.centerOfMass.x,a=s*t.centerOfMass.y;return e.set(r/2,s/2),i.set(n-e.x,a-e.y),!0},getTopLeft:function(t,e,i){if(void 0===e&&(e=0),void 0===i&&(i=0),this.parseBody(t)){var r=this.boundsCenter,n=this.centerDiff;return new s(e+r.x+n.x,i+r.y+n.y)}return!1},getTopCenter:function(t,e,i){if(void 0===e&&(e=0),void 0===i&&(i=0),this.parseBody(t)){var r=this.boundsCenter,n=this.centerDiff;return new s(e+n.x,i+r.y+n.y)}return!1},getTopRight:function(t,e,i){if(void 0===e&&(e=0),void 0===i&&(i=0),this.parseBody(t)){var r=this.boundsCenter,n=this.centerDiff;return new s(e-(r.x-n.x),i+r.y+n.y)}return!1},getLeftCenter:function(t,e,i){if(void 0===e&&(e=0),void 0===i&&(i=0),this.parseBody(t)){var r=this.boundsCenter,n=this.centerDiff;return new s(e+r.x+n.x,i+n.y)}return!1},getCenter:function(t,e,i){if(void 0===e&&(e=0),void 0===i&&(i=0),this.parseBody(t)){var r=this.centerDiff;return new s(e+r.x,i+r.y)}return!1},getRightCenter:function(t,e,i){if(void 0===e&&(e=0),void 0===i&&(i=0),this.parseBody(t)){var r=this.boundsCenter,n=this.centerDiff;return new s(e-(r.x-n.x),i+n.y)}return!1},getBottomLeft:function(t,e,i){if(void 0===e&&(e=0),void 0===i&&(i=0),this.parseBody(t)){var r=this.boundsCenter,n=this.centerDiff;return new s(e+r.x+n.x,i-(r.y-n.y))}return!1},getBottomCenter:function(t,e,i){if(void 0===e&&(e=0),void 0===i&&(i=0),this.parseBody(t)){var r=this.boundsCenter,n=this.centerDiff;return new s(e+n.x,i-(r.y-n.y))}return!1},getBottomRight:function(t,e,i){if(void 0===e&&(e=0),void 0===i&&(i=0),this.parseBody(t)){var r=this.boundsCenter,n=this.centerDiff;return new s(e-(r.x-n.x),i-(r.y-n.y))}return!1}});t.exports=n},19933(t,e,i){var r=i(6790);r.Body=i(22562),r.Composite=i(69351),r.World=i(4372),r.Collision=i(52284),r.Detector=i(81388),r.Pairs=i(99561),r.Pair=i(4506),r.Query=i(73296),r.Resolver=i(66272),r.Constraint=i(48140),r.Common=i(53402),r.Engine=i(48413),r.Events=i(35810),r.Sleeping=i(53614),r.Plugin=i(73832),r.Bodies=i(66280),r.Composites=i(74116),r.Axes=i(66615),r.Bounds=i(15647),r.Svg=i(74058),r.Vector=i(31725),r.Vertices=i(41598),r.World.add=r.Composite.add,r.World.remove=r.Composite.remove,r.World.addComposite=r.Composite.addComposite,r.World.addBody=r.Composite.addBody,r.World.addConstraint=r.Composite.addConstraint,r.World.clear=r.Composite.clear,t.exports=r},28137(t,e,i){var r=i(66280),s=i(83419),n=i(74116),a=i(48140),o=i(74058),h=i(75803),l=i(23181),u=i(34803),d=i(73834),c=i(19496),f=i(85791),p=i(98713),g=i(41598),m=new s({initialize:function(t){this.world=t,this.scene=t.scene,this.sys=t.scene.sys},rectangle:function(t,e,i,s,n){var a=r.rectangle(t,e,i,s,n);return this.world.add(a),a},trapezoid:function(t,e,i,s,n,a){var o=r.trapezoid(t,e,i,s,n,a);return this.world.add(o),o},circle:function(t,e,i,s,n){var a=r.circle(t,e,i,s,n);return this.world.add(a),a},polygon:function(t,e,i,s,n){var a=r.polygon(t,e,i,s,n);return this.world.add(a),a},fromVertices:function(t,e,i,s,n,a,o){"string"==typeof i&&(i=g.fromPath(i));var h=r.fromVertices(t,e,i,s,n,a,o);return this.world.add(h),h},fromPhysicsEditor:function(t,e,i,r,s){void 0===s&&(s=!0);var n=c.parseBody(t,e,i,r);return s&&!this.world.has(n)&&this.world.add(n),n},fromSVG:function(t,e,i,s,n,a){void 0===s&&(s=1),void 0===n&&(n={}),void 0===a&&(a=!0);for(var h=i.getElementsByTagName("path"),l=[],u=0;up)break;if(!(g