import { episode, episodeSampleDescription, youtubeDraftMetadata } from "../episode";
import type { AnimationRouteSampleForVisual } from "../sample-episode-visual";
import "./concept-episode-2-5d.css";
export interface ConceptEpisode2_5DOptions {
readonly sampleAt: (time: number) => AnimationRouteSampleForVisual;
readonly duration: number;
readonly fixedTime?: number;
}
export interface ConceptEpisode2_5DProof {
readonly contractId: string;
readonly episodeId: string;
readonly title: string;
readonly route: string;
readonly mode: "2.5d-parallax-concept";
readonly sourceImage: string;
readonly notTrue3D: true;
readonly screenshotTarget: "#concept-episode-2-5d";
readonly sample: AnimationRouteSampleForVisual;
readonly parallax: number;
readonly layers: readonly {
readonly id: string;
readonly role: string;
readonly depth: number;
readonly source: "same-concept-frame";
}[];
readonly aura3dRole: readonly string[];
readonly limitations: readonly string[];
}
const sourceImage = "/aura-assets/moon-garden-feature-frame.png";
const layers = [
{ id: "far-background", role: "moonlit sky and set backdrop", depth: 0.12, source: "same-concept-frame" },
{ id: "midground-set", role: "garden stage and central character space", depth: 0.42, source: "same-concept-frame" },
{ id: "character-plane", role: "painted character/action plane", depth: 0.72, source: "same-concept-frame" },
{ id: "foreground-garden", role: "front flowers/path edge", depth: 1, source: "same-concept-frame" }
] as const;
export function installConceptEpisode2_5D(options: ConceptEpisode2_5DOptions) {
document.body.dataset.animationView = "concept-2-5d";
const root = document.createElement("section");
root.id = "concept-episode-2-5d";
root.className = "concept-2-5d";
root.setAttribute("aria-label", "Aura3D 2.5D animation concept episode preview");
root.innerHTML = `
2.5D concept frame
Shot 1 / 3
`;
document.body.appendChild(root);
const hostSlot = root.querySelector("[data-aura-host-slot]");
const appHost = document.querySelector("#app");
if (hostSlot && appHost) {
hostSlot.replaceWith(appHost);
}
const caption = root.querySelector("[data-concept-caption]");
const shotLabel = root.querySelector("[data-concept-shot-label]");
const progress = root.querySelector("[data-concept-progress]");
const query = new URLSearchParams(window.location.search);
const queryTime = query.get("sampleTime");
const queryParallax = query.get("parallax");
const animateParallax = query.get("animateParallax") === "1";
const fixedTime = options.fixedTime ?? (queryTime ? Number(queryTime) : undefined);
const fixedParallax = queryParallax ? Number(queryParallax) : undefined;
const start = performance.now();
function render(time: number, parallax: number) {
const clampedParallax = Math.max(-1, Math.min(1, Number.isFinite(parallax) ? parallax : 0));
const sample = options.sampleAt(clampTime(time, options.duration));
const shotIndex = Math.max(0, episode.shotTimeline.shots.findIndex((shot) => shot.shotId === sample.shotId));
const progressPercent = Math.max(0, Math.min(100, (sample.time / options.duration) * 100));
root.dataset.shotId = sample.shotId ?? "";
root.dataset.captionId = sample.captionId ?? "";
root.dataset.conceptEpisode = "moon-garden-cleanup";
root.style.setProperty("--parallax", String(clampedParallax));
if (caption) {
caption.textContent = sample.captionText ?? "The image is treated as layered source art, not a true 3D mesh.";
}
if (shotLabel) {
shotLabel.textContent = `Shot ${shotIndex + 1} / ${episode.shotTimeline.shots.length}`;
}
if (progress) {
progress.style.setProperty("--progress", `${progressPercent}%`);
}
window.__AURA3D_ANIMATION_2_5D_PROOF__ = {
contractId: episodeSampleDescription.contractId,
episodeId: episode.episodePlan.episodeId,
title: episode.episodePlan.title,
route: "/?view=concept-2-5d",
mode: "2.5d-parallax-concept",
sourceImage,
notTrue3D: true,
screenshotTarget: "#concept-episode-2-5d",
sample,
parallax: clampedParallax,
layers,
aura3dRole: [
"episode contract and shot timing",
"caption timing",
"camera/parallax plan",
"proof metadata",
"optional foreground Aura3D scene graph"
],
limitations: [
"This is not mesh reconstruction from a still image.",
"The camera can pan, push, and create depth parallax, but it cannot orbit behind the painted characters.",
"Production-quality 2.5D needs actual masks/depth maps or separated PSD-style layers."
]
};
}
if (Number.isFinite(fixedTime) && !animateParallax) {
render(Number(fixedTime), Number.isFinite(fixedParallax) ? Number(fixedParallax) : 0);
return root;
}
const tick = () => {
const elapsed = (performance.now() - start) / 1000;
const sampleTime = Number.isFinite(fixedTime) ? Number(fixedTime) : elapsed % options.duration;
render(sampleTime, Math.sin(elapsed * 0.82));
requestAnimationFrame(tick);
};
tick();
return root;
}
declare global {
interface Window {
__AURA3D_ANIMATION_2_5D_PROOF__?: ConceptEpisode2_5DProof;
}
}
function clampTime(time: number, duration: number) {
if (!Number.isFinite(time)) return 0;
return Math.max(0, Math.min(duration - 1 / 30, time));
}