155 lines
5.6 KiB
JavaScript
Executable File
155 lines
5.6 KiB
JavaScript
Executable File
(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));
|