- timeline.js: headless clock/tracks/evaluate (transform slerp, param lerp, clip blocks with fade-out, camera cuts, undo stack). No three/DOM import. - tlui.js: canvas panel — ruler scrub, playhead, key add/drag/delete, clip blocks, camera cuts, save/load with localStorage fallback. Injects own CSS. - stagestub.js: dev Stage per PLAN §4.2 (deleted at SYNC 1). - timeline_test.mjs: node self-check, all assertions pass. - tldev.html: dev-only browser harness (deleted at SYNC 1). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
60 lines
3.1 KiB
HTML
60 lines
3.1 KiB
HTML
<!doctype html>
|
|
<!-- tldev.html — Lane B DEV-ONLY harness to exercise the timeline UI against
|
|
the stub before Lane A's index.html exists. Deleted at SYNC 1. -->
|
|
<meta charset="utf-8">
|
|
<title>SCENEGOD · timeline dev harness</title>
|
|
<style>
|
|
html,body{margin:0;height:100%;background:#0d1117;color:#c9d1d9;font:13px ui-monospace,monospace}
|
|
#top{padding:8px 12px;border-bottom:1px solid #2b323c}
|
|
#log{height:120px;overflow:auto;padding:6px 12px;color:#565f6b;font-size:11px;white-space:pre-wrap;border-bottom:1px solid #2b323c}
|
|
#timeline{position:fixed;left:0;right:0;bottom:0}
|
|
</style>
|
|
<div id="top">Lane B dev harness — stub stage. Double-click a lane to add a key,
|
|
drag keys, ▶ to play, drag the ruler to scrub. setTransform/param calls log below.</div>
|
|
<div id="log"></div>
|
|
<div id="timeline"></div>
|
|
|
|
<script type="module">
|
|
import { StageStub } from './stagestub.js';
|
|
import { Timeline } from './timeline.js';
|
|
import { TimelineUI } from './tlui.js';
|
|
|
|
// mirror stub effects into the on-page log
|
|
const logEl = document.getElementById('log');
|
|
const stage = new StageStub();
|
|
const _st = stage.setTransform.bind(stage), _sp = stage.setParam.bind(stage), _sc = stage.setActiveCamera.bind(stage);
|
|
const line = (s) => { logEl.textContent = s + '\n' + logEl.textContent.slice(0, 4000); };
|
|
stage.setTransform = (id, t) => { _st(id, t); line(`setTransform ${id} pos=[${t.pos.map(n=>n.toFixed(2))}]`); };
|
|
stage.setParam = (id, k, v) => { _sp(id, k, v); line(`setParam ${id} ${k}=${v}`); };
|
|
stage.setActiveCamera = (id) => { _sc(id); line(`activeCamera ${id}`); };
|
|
|
|
const scene = {
|
|
version: 1, name: 'dev', fps: 30, duration: 6,
|
|
entities: [
|
|
{ id: 'lady', kind: 'character', label: 'lady',
|
|
source: { type: 'assets', path: 'characters/lady.glb' },
|
|
transform: { pos: [0,0,0], rot: [0,0,0], scale: 1 },
|
|
tracks: { transform: [
|
|
{ t: 0, pos: [-3,0,0], rot: [0,0,0], scale: 1, ease: 'linear' },
|
|
{ t: 4, pos: [3,0,0], rot: [0,1.57,0], scale: 1, ease: 'inout' } ],
|
|
clips: [ { path: 'animations/walk.fbx', clipIndex: 0, start: 0.5, in: 0, out: 2.4, loop: 1, fade: 0 } ] } },
|
|
{ id: 'sun', kind: 'light', label: 'sun',
|
|
params: { type: 'key', color: '#ffcc88', intensity: 1 },
|
|
transform: { pos: [2,5,2], rot: [0,0,0], scale: 1 },
|
|
tracks: { params: [ { t: 0, key: 'intensity', value: 0.2, ease: 'inout' }, { t: 5, key: 'intensity', value: 3, ease: 'inout' } ] } },
|
|
{ id: 'camA', kind: 'camera', label: 'wide', params: { fov: 40 }, transform: { pos: [0,1.5,6], rot: [0,0,0], scale: 1 }, tracks: {} },
|
|
{ id: 'camB', kind: 'camera', label: 'close', params: { fov: 60 }, transform: { pos: [2,1.6,2], rot: [0,0,0], scale: 1 }, tracks: {} },
|
|
],
|
|
cameraCuts: [ { t: 0, camera: 'camA' }, { t: 3, camera: 'camB' } ],
|
|
audio: [],
|
|
};
|
|
|
|
await stage.applyState(scene);
|
|
const tl = new Timeline(stage);
|
|
tl.load(scene);
|
|
await tl.preload();
|
|
const ui = new TimelineUI(tl, '#timeline');
|
|
window._tl = tl; window._ui = ui; window._stage = stage; // console poking
|
|
line('ready — scene loaded');
|
|
</script>
|