Refactor platform UI and data flow across core pages
All checks were successful
Build Org Website / build (push) Successful in 50s
All checks were successful
Build Org Website / build (push) Successful in 50s
This commit is contained in:
154
assets/scripts/pages/princess-lima-intro.js
Normal file
154
assets/scripts/pages/princess-lima-intro.js
Normal file
@@ -0,0 +1,154 @@
|
||||
(function (root) {
|
||||
"use strict";
|
||||
|
||||
const BEATS = Object.freeze([
|
||||
Object.freeze({
|
||||
panel: 0, duration: 15000, voice: "intro-narration-1",
|
||||
subtitle: "Before shadow crossed the northern road, Princess Lima walked among her people—listening before she ruled, and helping before she asked."
|
||||
}),
|
||||
Object.freeze({
|
||||
panel: 1, duration: 14000, voice: "intro-narration-2", effect: "door",
|
||||
subtitle: "Then Lord Malrec descended from the Fortress of Shadows. His riders carried fear through the valleys, searching for the royal oath."
|
||||
}),
|
||||
Object.freeze({
|
||||
panel: 2, duration: 15000, voice: "intro-narration-3", effect: "attack",
|
||||
subtitle: "Lima stood between the riders and the village. Malrec could not bend her will, so he bound her in shadow and carried her beyond the mountains."
|
||||
}),
|
||||
Object.freeze({
|
||||
panel: 3, duration: 15000, voice: "intro-narration-4", effect: "quest",
|
||||
subtitle: "At dawn, a lone traveller reached the broken village. The road was dangerous, but every rescued life would become another light leading to Lima."
|
||||
}),
|
||||
Object.freeze({
|
||||
panel: 3, duration: 8000, voice: null, effect: "victory",
|
||||
subtitle: "RESCUE PRINCESS LIMA — Chapter One: The Broken Village"
|
||||
})
|
||||
]);
|
||||
|
||||
function create(container, actions) {
|
||||
const element = container.querySelector("[data-lima-intro]");
|
||||
const picture = element.querySelector("[data-lima-intro-picture]");
|
||||
const subtitle = element.querySelector("[data-lima-intro-subtitle]");
|
||||
const progress = element.querySelector("[data-lima-intro-progress]");
|
||||
const pauseButton = element.querySelector("[data-lima-intro-pause]");
|
||||
const muteButton = element.querySelector("[data-lima-intro-mute]");
|
||||
let beatIndex = 0;
|
||||
let elapsed = 0;
|
||||
let startedAt = 0;
|
||||
let timer = 0;
|
||||
let paused = false;
|
||||
let replay = false;
|
||||
let escapeStarted = 0;
|
||||
let running = false;
|
||||
|
||||
function start(options) {
|
||||
stopTimer();
|
||||
beatIndex = 0;
|
||||
elapsed = 0;
|
||||
paused = false;
|
||||
replay = Boolean(options && options.replay);
|
||||
running = true;
|
||||
element.hidden = false;
|
||||
container.classList.add("is-intro-running");
|
||||
actions.onLock(true);
|
||||
actions.onAudioUnlock();
|
||||
renderBeat();
|
||||
element.focus();
|
||||
}
|
||||
|
||||
function renderBeat() {
|
||||
const beat = BEATS[beatIndex];
|
||||
picture.dataset.panel = String(beat.panel);
|
||||
subtitle.textContent = beat.subtitle;
|
||||
subtitle.hidden = !actions.subtitlesEnabled();
|
||||
progress.style.width = `${beatIndex / BEATS.length * 100}%`;
|
||||
progress.parentElement.setAttribute("aria-valuenow", String(beatIndex + 1));
|
||||
if (beat.voice && actions.narrationEnabled()) actions.playVoice(beat.voice);
|
||||
if (beat.effect) actions.playEffect(beat.effect, 0.55);
|
||||
startedAt = Date.now();
|
||||
timer = window.setTimeout(next, beat.duration);
|
||||
pauseButton.textContent = "Pause";
|
||||
pauseButton.setAttribute("aria-pressed", "false");
|
||||
}
|
||||
|
||||
function next() {
|
||||
stopTimer();
|
||||
actions.stopVoice();
|
||||
beatIndex += 1;
|
||||
elapsed = 0;
|
||||
if (beatIndex >= BEATS.length) return finish(false);
|
||||
renderBeat();
|
||||
}
|
||||
|
||||
function togglePause() {
|
||||
if (!running) return;
|
||||
paused = !paused;
|
||||
if (paused) {
|
||||
elapsed += Date.now() - startedAt;
|
||||
stopTimer();
|
||||
actions.stopVoice();
|
||||
} else {
|
||||
const beat = BEATS[beatIndex];
|
||||
startedAt = Date.now();
|
||||
timer = window.setTimeout(next, Math.max(500, beat.duration - elapsed));
|
||||
if (beat.voice && actions.narrationEnabled()) actions.playVoice(beat.voice);
|
||||
}
|
||||
pauseButton.textContent = paused ? "Resume" : "Pause";
|
||||
pauseButton.setAttribute("aria-pressed", String(paused));
|
||||
element.classList.toggle("is-paused", paused);
|
||||
}
|
||||
|
||||
function finish(skipped) {
|
||||
if (!running) return;
|
||||
running = false;
|
||||
stopTimer();
|
||||
actions.stopVoice();
|
||||
progress.style.width = "100%";
|
||||
element.hidden = true;
|
||||
element.classList.remove("is-paused");
|
||||
container.classList.remove("is-intro-running");
|
||||
actions.onComplete({ replay, skipped });
|
||||
}
|
||||
|
||||
function stopTimer() {
|
||||
if (timer) window.clearTimeout(timer);
|
||||
timer = 0;
|
||||
}
|
||||
|
||||
function updateMute() {
|
||||
const enabled = actions.soundEnabled();
|
||||
muteButton.textContent = enabled ? "Mute" : "Enable sound";
|
||||
muteButton.setAttribute("aria-pressed", String(!enabled));
|
||||
}
|
||||
|
||||
pauseButton.addEventListener("click", togglePause);
|
||||
muteButton.addEventListener("click", () => {
|
||||
actions.toggleSound();
|
||||
updateMute();
|
||||
});
|
||||
element.querySelector("[data-lima-intro-skip]").addEventListener("click", () => finish(true));
|
||||
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (!running) return;
|
||||
if (event.key === "Escape" && !escapeStarted) {
|
||||
event.preventDefault();
|
||||
escapeStarted = Date.now();
|
||||
} else if (event.key.toLowerCase() === "p") {
|
||||
event.preventDefault();
|
||||
togglePause();
|
||||
}
|
||||
});
|
||||
document.addEventListener("keyup", (event) => {
|
||||
if (!running || event.key !== "Escape") return;
|
||||
event.preventDefault();
|
||||
if (escapeStarted && Date.now() - escapeStarted >= 900) finish(true);
|
||||
else actions.status("Hold Escape for one second to skip the introduction.");
|
||||
escapeStarted = 0;
|
||||
});
|
||||
|
||||
return Object.freeze({ start, finish, togglePause, isRunning: () => running, updateMute, BEATS });
|
||||
}
|
||||
|
||||
const api = Object.freeze({ create, BEATS });
|
||||
if (typeof module === "object" && module.exports) module.exports = api;
|
||||
root.PrincessLimaIntro = api;
|
||||
}(typeof globalThis !== "undefined" ? globalThis : this));
|
||||
Reference in New Issue
Block a user