@litfamily/litmuse 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +23 -1
- package/README_ko-KR.md +23 -1
- package/TRADEMARK.md +12 -0
- package/bin/litmuse.mjs +4 -0
- package/lib/stage-card.mjs +249 -6
- package/package.json +3 -2
- package/plugin/.muse-plugin/plugin.json +26 -11
- package/plugin/commands/lit-status.md +3 -1
- package/plugin/commands/lit.md +1 -1
- package/plugin/hooks/lib/digest.mjs +1 -1
- package/plugin/hooks/lib/next-step.mjs +112 -0
- package/plugin/hooks/lib/routes.mjs +59 -0
- package/plugin/hooks/lib/stage.mjs +53 -6
- package/plugin/hooks/lib/workflow-stack.mjs +336 -0
- package/plugin/hooks/post-tool-use-failure.mjs +2 -0
- package/plugin/hooks/post-tool-use.mjs +2 -0
- package/plugin/hooks/session-end.mjs +2 -1
- package/plugin/hooks/session-start.mjs +32 -2
- package/plugin/hooks/stop.mjs +23 -3
- package/plugin/hooks/user-prompt-submit.mjs +138 -7
- package/plugin/lib/capped-fetch.mjs +25 -0
- package/plugin/lib/pinned-fetch.mjs +79 -0
- package/plugin/lib/plan-task-rows.mjs +32 -0
- package/plugin/lib/render_pages.py +29 -1
- package/plugin/lib/rewind/collect.mjs +808 -0
- package/plugin/lib/rewind/gauge.mjs +72 -0
- package/plugin/lib/rewind/next-step.mjs +91 -0
- package/plugin/lib/rewind/redact.mjs +83 -0
- package/plugin/lib/rewind/transcripts.mjs +229 -0
- package/plugin/skills/frontend-ui-ux/SKILL.md +24 -9
- package/plugin/skills/frontend-ui-ux/kit/components.css +414 -0
- package/plugin/skills/frontend-ui-ux/kit/fonts.json +22 -0
- package/plugin/skills/frontend-ui-ux/kit/layouts/landing.html +164 -0
- package/plugin/skills/frontend-ui-ux/kit/layouts/settings.html +114 -0
- package/plugin/skills/frontend-ui-ux/kit/layouts/workspace.html +115 -0
- package/plugin/skills/frontend-ui-ux/kit/styles/computational-brutalism.json +227 -0
- package/plugin/skills/frontend-ui-ux/kit/styles/field-notes.json +205 -0
- package/plugin/skills/frontend-ui-ux/kit/styles/graphite.json +200 -0
- package/plugin/skills/frontend-ui-ux/kit/styles/meadow.json +199 -0
- package/plugin/skills/frontend-ui-ux/kit/styles/neo-brutalism.json +222 -0
- package/plugin/skills/frontend-ui-ux/kit/ui.js +226 -0
- package/plugin/skills/frontend-ui-ux/references/complete-contract.md +2 -1
- package/plugin/skills/frontend-ui-ux/references/craft-floor.md +9 -4
- package/plugin/skills/frontend-ui-ux/references/default-editorial-pixel.json +59 -7
- package/plugin/skills/frontend-ui-ux/references/house-styles.md +234 -0
- package/plugin/skills/frontend-ui-ux/references/motion-guide.md +3 -3
- package/plugin/skills/frontend-ui-ux/references/slop-register.md +7 -7
- package/plugin/skills/frontend-ui-ux/scripts/design-lock.mjs +400 -0
- package/plugin/skills/frontend-ui-ux/scripts/interface-probe-lock.mjs +196 -0
- package/plugin/skills/frontend-ui-ux/scripts/interface-probe-page.mjs +31 -10
- package/plugin/skills/frontend-ui-ux/scripts/interface-probe-rules.mjs +19 -7
- package/plugin/skills/frontend-ui-ux/scripts/interface-probe-static.mjs +4 -0
- package/plugin/skills/frontend-ui-ux/scripts/interface-probe.mjs +59 -7
- package/plugin/skills/frontend-ui-ux/scripts/pixel-art.mjs +60 -0
- package/plugin/skills/lit-adhd/SKILL.md +195 -0
- package/plugin/skills/lit-adhd/references/card-examples.md +121 -0
- package/plugin/skills/lit-adhd/references/status-taxonomy.md +45 -0
- package/plugin/skills/lit-adhd/scripts/rewind.mjs +11 -0
- package/plugin/skills/lit-diagram-drawer/SKILL.md +3 -3
- package/plugin/skills/lit-diagram-drawer/scripts/diagram-craft-rules.mjs +1 -2
- package/plugin/skills/lit-diagram-drawer/scripts/visual-quality.mjs +0 -1
- package/plugin/skills/lit-docx/SKILL.md +1 -1
- package/plugin/skills/lit-hijack/SKILL.md +589 -0
- package/plugin/skills/lit-hijack/references/capture-and-provenance.md +123 -0
- package/plugin/skills/lit-hijack/references/clean-room-build.md +133 -0
- package/plugin/skills/lit-hijack/references/distil-and-licence.md +128 -0
- package/plugin/skills/lit-hijack/references/gap-and-port-design.md +152 -0
- package/plugin/skills/lit-hijack/references/parity-and-report.md +133 -0
- package/plugin/skills/lit-hijack/scripts/hijack.mjs +879 -0
- package/plugin/skills/lit-plan/SKILL.md +12 -1
- package/plugin/skills/lit-plan/scripts/check-plan.mjs +71 -4
- package/plugin/skills/lit-pptx/SKILL.md +20 -9
- package/plugin/skills/lit-pptx/fonts/jetbrains-mono/JetBrainsMono-Bold.ttf +0 -0
- package/plugin/skills/lit-pptx/fonts/jetbrains-mono/JetBrainsMono-Regular.ttf +0 -0
- package/plugin/skills/lit-pptx/fonts/jetbrains-mono/OFL.txt +93 -0
- package/plugin/skills/lit-pptx/fonts/provenance.json +32 -1
- package/plugin/skills/lit-pptx/references/direction-step.md +19 -2
- package/plugin/skills/lit-pptx/references/examples/13-engineering-review-computational-brutalism-ko.md +173 -0
- package/plugin/skills/lit-pptx/references/examples/14-launch-pitch-neo-brutalism-ko.md +154 -0
- package/plugin/skills/lit-pptx/references/examples/15-design-lecture-bauhaus-ko.md +139 -0
- package/plugin/skills/lit-pptx/references/examples/16-community-programme-riso-ko.md +158 -0
- package/plugin/skills/lit-pptx/references/examples/17-public-data-briefing-newsroom-ko.md +179 -0
- package/plugin/skills/lit-pptx/references/tonalities/bauhaus.md +112 -0
- package/plugin/skills/lit-pptx/references/tonalities/computational-brutalism.md +118 -0
- package/plugin/skills/lit-pptx/references/tonalities/neo-brutalism.md +125 -0
- package/plugin/skills/lit-pptx/references/tonalities/newsroom.md +115 -0
- package/plugin/skills/lit-pptx/references/tonalities/riso.md +109 -0
- package/plugin/skills/lit-pptx/scripts/compile-deck.js +1 -1
- package/plugin/skills/lit-pptx/scripts/craft_extras.py +136 -92
- package/plugin/skills/lit-pptx/scripts/embed_fonts.py +13 -1
- package/plugin/skills/lit-pptx/scripts/lib/grid-resolver.js +105 -116
- package/plugin/skills/lit-pptx/scripts/lib/layout-resolver.js +9 -15
- package/plugin/skills/lit-pptx/scripts/lib/render-pack.js +368 -561
- package/plugin/skills/lit-pptx/scripts/lib/slide-grid.js +104 -0
- package/plugin/skills/lit-pptx/scripts/lib/template-registry.js +57 -6
- package/plugin/skills/lit-pptx/scripts/qa_deck.py +4 -4
- package/plugin/skills/lit-pptx/templates/tonalities/atlas/pack.yaml +1 -1
- package/plugin/skills/lit-pptx/templates/tonalities/bauhaus/pack.yaml +98 -0
- package/plugin/skills/lit-pptx/templates/tonalities/chalk/pack.yaml +1 -1
- package/plugin/skills/lit-pptx/templates/tonalities/computational-brutalism/pack.yaml +99 -0
- package/plugin/skills/lit-pptx/templates/tonalities/gazette/pack.yaml +1 -1
- package/plugin/skills/lit-pptx/templates/tonalities/ledger/pack.yaml +1 -1
- package/plugin/skills/lit-pptx/templates/tonalities/neo-brutalism/pack.yaml +107 -0
- package/plugin/skills/lit-pptx/templates/tonalities/newsroom/pack.yaml +99 -0
- package/plugin/skills/lit-pptx/templates/tonalities/night/pack.yaml +1 -1
- package/plugin/skills/lit-pptx/templates/tonalities/paper/pack.yaml +1 -1
- package/plugin/skills/lit-pptx/templates/tonalities/riso/pack.yaml +99 -0
- package/plugin/skills/lit-pptx/templates/tonalities/signal/pack.yaml +2 -2
- package/plugin/skills/lit-pptx/templates/tonalities/studio/pack.yaml +1 -1
- package/plugin/skills/lit-recap/SKILL.md +3 -0
- package/plugin/skills/lit-schematic/SKILL.md +241 -0
- package/plugin/skills/lit-schematic/examples/iso-heat-exchanger/build.mjs +14 -0
- package/plugin/skills/lit-schematic/examples/iso-heat-exchanger/scene.mjs +8 -0
- package/plugin/skills/lit-schematic/examples/journal-adsorption-rig/build.mjs +22 -0
- package/plugin/skills/lit-schematic/examples/pid-dosing-skid/build.mjs +73 -0
- package/plugin/skills/lit-schematic/references/cutaway-outputs.md +25 -0
- package/plugin/skills/lit-schematic/references/isometric-craft.md +24 -0
- package/plugin/skills/lit-schematic/references/journal-figures.md +19 -0
- package/plugin/skills/lit-schematic/references/kit-api.md +27 -0
- package/plugin/skills/lit-schematic/references/pfd-and-layout.md +20 -0
- package/plugin/skills/lit-schematic/references/pid-drafting.md +26 -0
- package/plugin/skills/lit-schematic/references/pid-symbols.md +27 -0
- package/plugin/skills/lit-schematic/references/verification.md +24 -0
- package/plugin/skills/lit-schematic/scripts/doctor.mjs +13 -0
- package/plugin/skills/lit-schematic/scripts/lib/figure.mjs +49 -0
- package/plugin/skills/lit-schematic/scripts/lib/isa.mjs +18 -0
- package/plugin/skills/lit-schematic/scripts/lib/iso.mjs +58 -0
- package/plugin/skills/lit-schematic/scripts/lib/legend-sheet.mjs +10 -0
- package/plugin/skills/lit-schematic/scripts/lib/qa-core.mjs +241 -0
- package/plugin/skills/lit-schematic/scripts/lib/sheet.mjs +446 -0
- package/plugin/skills/lit-schematic/scripts/lib/svg.mjs +39 -0
- package/plugin/skills/lit-schematic/scripts/lib/theme.mjs +25 -0
- package/plugin/skills/lit-schematic/scripts/motion.mjs +8 -0
- package/plugin/skills/lit-schematic/scripts/qa.mjs +21 -0
- package/plugin/skills/lit-schematic/scripts/render.mjs +38 -0
- package/plugin/skills/lit-schematic/scripts/scaffold.mjs +73 -0
- package/plugin/skills/lit-scientific-visualization/SKILL.md +2 -0
- package/plugin/skills/lit-team/scripts/state-root.mjs +5 -18
- package/plugin/skills/lit-typographic-motion/SKILL.md +12 -8
- package/plugin/skills/lit-typographic-motion/engine/THIRD_PARTY_NOTICES +11 -0
- package/plugin/skills/lit-typographic-motion/engine/blender/runner.py +218 -0
- package/plugin/skills/lit-typographic-motion/engine/core/presets.mjs +1 -1
- package/plugin/skills/lit-typographic-motion/engine/node/assets.mjs +218 -0
- package/plugin/skills/lit-typographic-motion/engine/node/blender.mjs +144 -0
- package/plugin/skills/lit-typographic-motion/engine/node/board.mjs +136 -0
- package/plugin/skills/lit-typographic-motion/engine/node/done.mjs +42 -3
- package/plugin/skills/lit-typographic-motion/engine/node/gallery.mjs +120 -0
- package/plugin/skills/lit-typographic-motion/engine/node/house.mjs +44 -0
- package/plugin/skills/lit-typographic-motion/engine/node/pipeline.mjs +18 -7
- package/plugin/skills/lit-typographic-motion/engine/node/runtime.mjs +6 -3
- package/plugin/skills/lit-typographic-motion/engine/node/scaffold.mjs +210 -0
- package/plugin/skills/lit-typographic-motion/engine/node/score.mjs +639 -0
- package/plugin/skills/lit-typographic-motion/engine/node/sound-cli.mjs +21 -4
- package/plugin/skills/lit-typographic-motion/engine/node/sound.mjs +126 -27
- package/plugin/skills/lit-typographic-motion/engine/node/stage-serve.mjs +29 -5
- package/plugin/skills/lit-typographic-motion/engine/node/stage.mjs +82 -14
- package/plugin/skills/lit-typographic-motion/engine/node/styles.mjs +214 -0
- package/plugin/skills/lit-typographic-motion/engine/node/treatment.mjs +311 -10
- package/plugin/skills/lit-typographic-motion/engine/vendor/three/LICENSE +21 -0
- package/plugin/skills/lit-typographic-motion/engine/vendor/three/addons/environments/RoomEnvironment.js +144 -0
- package/plugin/skills/lit-typographic-motion/engine/vendor/three/addons/geometries/RoundedBoxGeometry.js +155 -0
- package/plugin/skills/lit-typographic-motion/engine/vendor/three/three.module.js +54571 -0
- package/plugin/skills/lit-typographic-motion/recipes/hero-3d/expected.json +69 -0
- package/plugin/skills/lit-typographic-motion/recipes/hero-3d/index.html +139 -0
- package/plugin/skills/lit-typographic-motion/references/depth.md +122 -0
- package/plugin/skills/lit-typographic-motion/references/sound.md +74 -16
- package/plugin/skills/lit-typographic-motion/references/stage.md +29 -5
- package/plugin/skills/lit-typographic-motion/references/studio.md +245 -0
- package/plugin/skills/lit-typographic-motion/references/treatment.md +31 -8
- package/plugin/skills/lit-typographic-motion/scripts/motion.mjs +48 -7
- package/plugin/skills/lit-typographic-motion/styles/paper.json +41 -0
- package/plugin/skills/lit-typographic-motion/styles/swiss-signal.json +41 -0
- package/plugin/skills/lit-typographic-motion/styles/terminalcore.json +42 -0
- package/plugin/skills/lit-typographic-motion/styles/tidal.json +41 -0
- package/plugin/skills/readme-studio/SKILL.md +1 -1
- package/plugin/skills/start-work/SKILL.md +10 -1
|
@@ -14,6 +14,7 @@ import { homedir, tmpdir } from "node:os";
|
|
|
14
14
|
import path from "node:path";
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
16
|
import { failureDetail, pickPython, spawnNpm } from "../../../../lib/host-process.mjs";
|
|
17
|
+
import { readCapped } from "../../../../lib/capped-fetch.mjs";
|
|
17
18
|
|
|
18
19
|
export const SKILL_ROOT = fileURLToPath(new URL("../../", import.meta.url));
|
|
19
20
|
export const ENGINE_ROOT = path.join(SKILL_ROOT, "engine");
|
|
@@ -195,8 +196,10 @@ function withLock(root, fn) {
|
|
|
195
196
|
return Promise.resolve().then(fn).finally(() => rmSync(lock, { recursive: true, force: true }));
|
|
196
197
|
}
|
|
197
198
|
|
|
199
|
+
const RUNTIME_FETCH_LIMIT = 128 * 1024 * 1024;
|
|
200
|
+
|
|
198
201
|
/** Fetch bytes from an http(s) or file URL. A mirror (tests, air-gapped hosts) serves files by sha256. */
|
|
199
|
-
async function fetchBytes(url, { sha256: expected, env }) {
|
|
202
|
+
export async function fetchBytes(url, { sha256: expected, env }) {
|
|
200
203
|
const mirror = env.LITMUSE_MOTION_MIRROR;
|
|
201
204
|
const source = mirror && expected ? `${mirror.replace(/\/$/u, "")}/${expected}` : url;
|
|
202
205
|
let bytes;
|
|
@@ -204,7 +207,7 @@ async function fetchBytes(url, { sha256: expected, env }) {
|
|
|
204
207
|
else {
|
|
205
208
|
const response = await fetch(source, { signal: AbortSignal.timeout(120000) });
|
|
206
209
|
if (!response.ok) throw new Error(`HTTP ${response.status} for ${source}`);
|
|
207
|
-
bytes =
|
|
210
|
+
bytes = await readCapped(response, RUNTIME_FETCH_LIMIT, source);
|
|
208
211
|
}
|
|
209
212
|
if (expected && sha256(bytes) !== expected) throw new Error(`sha256 mismatch for ${url} (expected ${expected}, got ${sha256(bytes)})`);
|
|
210
213
|
return bytes;
|
|
@@ -274,7 +277,7 @@ export async function installNode({ env = process.env, log = () => {} } = {}) {
|
|
|
274
277
|
const integrity = Buffer.from(pkg.integrity.replace(/^sha512-/u, ""), "base64").toString("hex");
|
|
275
278
|
const mirror = env.LITMUSE_MOTION_MIRROR.replace(/\/$/u, "");
|
|
276
279
|
const source = `${mirror}/${path.basename(new URL(pkg.resolved).pathname)}`;
|
|
277
|
-
const bytes = source.startsWith("file://") ? readFileSync(fileURLToPath(source)) :
|
|
280
|
+
const bytes = source.startsWith("file://") ? readFileSync(fileURLToPath(source)) : await readCapped(await fetch(source), RUNTIME_FETCH_LIMIT, source);
|
|
278
281
|
if (createHash("sha512").update(bytes).digest("hex") !== integrity) throw new Error(`integrity mismatch for ${name}`);
|
|
279
282
|
const tarball = path.join(cache, path.basename(new URL(pkg.resolved).pathname));
|
|
280
283
|
writeAtomic(tarball, bytes);
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// `motion.mjs scaffold --out <dir>`: the starting stage page, written from the treatment, so every
|
|
2
|
+
// film has the same architecture: the COPY block, one <section> per shot with its type placed in the
|
|
3
|
+
// shot's type zone and an empty subject group in its subject zone, entrances and hand-offs on the
|
|
4
|
+
// house style's tokens, and one DRAW function per shot where the drawing goes. The page passes the
|
|
5
|
+
// style rules as written; the drawing is the model's. A 3D film (`depth: "3d"`) also gets the
|
|
6
|
+
// three.js world: an import map onto the vendored build, a full-frame canvas behind the shots, one
|
|
7
|
+
// persistent protagonist group lit and shadowed in the style's palette, and one WORLD function per
|
|
8
|
+
// shot. It never overwrites an existing page.
|
|
9
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { EXIT } from "../core/constants.mjs";
|
|
12
|
+
import { BlockedError } from "./chrome.mjs";
|
|
13
|
+
import { findBlender } from "./blender.mjs";
|
|
14
|
+
import { FORMATS, asksPathTraced, loadTreatment } from "./treatment.mjs";
|
|
15
|
+
|
|
16
|
+
const HANGUL = /[ㄱ-ㆎ가-힣]/u;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Which copy lines each shot shows: the treatment's `lines` when given; otherwise spread in order over
|
|
20
|
+
* the shots that have a type zone, the first line on the first and the last line on the last.
|
|
21
|
+
*/
|
|
22
|
+
export function linesPerShot(treatment) {
|
|
23
|
+
const beats = treatment.beats;
|
|
24
|
+
if (beats.some((b) => Array.isArray(b.lines))) return beats.map((b) => (Array.isArray(b.lines) ? b.lines : []));
|
|
25
|
+
const lines = treatment.copy.lines.map((_, i) => i);
|
|
26
|
+
const textShots = beats.map((b, i) => (b.layout === "full" ? null : i)).filter((i) => i !== null);
|
|
27
|
+
const out = beats.map(() => []);
|
|
28
|
+
if (!textShots.length) return out;
|
|
29
|
+
lines.forEach((line, k) => {
|
|
30
|
+
const at = lines.length > 1 ? Math.round((k * (textShots.length - 1)) / (lines.length - 1)) : 0;
|
|
31
|
+
out[textShots[at]].push(line);
|
|
32
|
+
});
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The scaffold page for a stage treatment. */
|
|
37
|
+
export function scaffoldHtml(treatment) {
|
|
38
|
+
const [width, height] = FORMATS[treatment.format];
|
|
39
|
+
const perShot = linesPerShot(treatment);
|
|
40
|
+
const lang = treatment.copy.lines.some((l) => HANGUL.test(l)) ? "ko" : "en";
|
|
41
|
+
const sections = treatment.beats.map((b, i) => {
|
|
42
|
+
const lines = perShot[i];
|
|
43
|
+
const roleOf = (k) => (k > 0 ? "lit-body lit-muted" : b.layout === "title" ? "lit-display lit-ink" : "lit-headline lit-ink");
|
|
44
|
+
const type = b.layout === "full" || !lines.length ? "" : `\n <div class="type" data-zone="type">${lines.map((line, k) => `\n <p class="${roleOf(k)}" data-copy="${line}"></p>`).join("")}\n </div>`;
|
|
45
|
+
return ` <!-- shot ${i + 1}: ${String(b.purpose).replace(/--/gu, "-")} -->\n <section class="shot" data-shot="${i}">\n <svg class="subject" data-zone="subject"></svg>${type}\n </section>`;
|
|
46
|
+
}).join("\n");
|
|
47
|
+
const draws = treatment.beats.map((b, i) => ` // shot ${i + 1} (${b.layout}): ${String(b.hero).replace(/\*\//gu, "* /")}\n (s, svg, t) => {},`).join("\n");
|
|
48
|
+
const solid = treatment.depth === "3d";
|
|
49
|
+
const worlds = treatment.beats.map((b, i) => ` // shot ${i + 1} (${b.layout})\n (s, hero, cam, t) => {},`).join("\n");
|
|
50
|
+
const head3d = solid ? `
|
|
51
|
+
<script type="importmap">{ "imports": { "three": "/lit/three/three.module.js", "three/addons/": "/lit/three/addons/" } }</script>` : "";
|
|
52
|
+
const canvas3d = solid ? `<canvas id="world" width="${width}" height="${height}"></canvas>\n` : "";
|
|
53
|
+
const style3d = solid ? `
|
|
54
|
+
#world { position: absolute; left: 0; top: 0; width: ${width}px; height: ${height}px; }` : "";
|
|
55
|
+
const world3d = solid ? `
|
|
56
|
+
// The world: one scene for the whole film, drawn on the GPU rung (software when it is missing). The
|
|
57
|
+
// protagonist lives in HERO from the first frame to the last; each shot's WORLD function poses it,
|
|
58
|
+
// the camera and anything else as a function of the shot's progress. The camera is aimed so the
|
|
59
|
+
// world origin sits at the centre of the shot's subject zone. More parts of the vendored build:
|
|
60
|
+
// three/addons/geometries/RoundedBoxGeometry.js.
|
|
61
|
+
const PAL = LitStyle.palette;
|
|
62
|
+
const renderer = new THREE.WebGLRenderer({ canvas: document.getElementById("world"), antialias: true, alpha: true, preserveDrawingBuffer: true });
|
|
63
|
+
renderer.setPixelRatio(1);
|
|
64
|
+
renderer.setSize(${width}, ${height}, false);
|
|
65
|
+
renderer.setClearColor(0x000000, 0);
|
|
66
|
+
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
|
67
|
+
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
68
|
+
renderer.shadowMap.enabled = true;
|
|
69
|
+
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
|
70
|
+
const scene = new THREE.Scene();
|
|
71
|
+
scene.environment = new THREE.PMREMGenerator(renderer).fromScene(new RoomEnvironment(), 0.04).texture;
|
|
72
|
+
const cam = new THREE.PerspectiveCamera(32, ${width} / ${height}, 0.1, 100);
|
|
73
|
+
const key = new THREE.DirectionalLight(0xffffff, 2.2);
|
|
74
|
+
key.position.set(3, 6, 4);
|
|
75
|
+
key.castShadow = true;
|
|
76
|
+
key.shadow.mapSize.set(2048, 2048);
|
|
77
|
+
key.shadow.radius = 4;
|
|
78
|
+
key.shadow.bias = -0.0004; // without these a turning object's shadow acne flickers as flashes
|
|
79
|
+
key.shadow.normalBias = 0.03;
|
|
80
|
+
scene.add(key, new THREE.HemisphereLight(PAL.surface ?? PAL.ground, PAL.ground, 0.6));
|
|
81
|
+
const floor = new THREE.Mesh(new THREE.PlaneGeometry(40, 40), new THREE.ShadowMaterial({ opacity: 0.18 }));
|
|
82
|
+
floor.rotation.x = -Math.PI / 2;
|
|
83
|
+
floor.receiveShadow = true;
|
|
84
|
+
scene.add(floor);
|
|
85
|
+
const HERO = new THREE.Group();
|
|
86
|
+
scene.add(HERO);
|
|
87
|
+
|
|
88
|
+
// Aim the camera so the origin lands on the centre of the shot's subject zone.
|
|
89
|
+
function aim(layout) {
|
|
90
|
+
const z = LitStage.zone(layout, "subject");
|
|
91
|
+
cam.setViewOffset(${width}, ${height}, ${width} / 2 - (z.x + z.w / 2), ${height} / 2 - (z.y + z.h / 2), ${width}, ${height});
|
|
92
|
+
return z.h / ${height};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const WORLD = [
|
|
96
|
+
${worlds}
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
function drawWorld(s, t) {
|
|
100
|
+
cam.position.set(0, 1.6, 7);
|
|
101
|
+
cam.lookAt(0, 0.6, 0);
|
|
102
|
+
aim(s.beat.layout);
|
|
103
|
+
WORLD[s.index](s, HERO, cam, t);
|
|
104
|
+
renderer.render(scene, cam);
|
|
105
|
+
}
|
|
106
|
+
` : "";
|
|
107
|
+
const scriptOpen = solid ? `<script type="module">
|
|
108
|
+
import * as THREE from "three";
|
|
109
|
+
import { RoomEnvironment } from "three/addons/environments/RoomEnvironment.js";
|
|
110
|
+
` : "<script>\n";
|
|
111
|
+
return `<!doctype html>
|
|
112
|
+
<html lang="${lang}">
|
|
113
|
+
<head>
|
|
114
|
+
<meta charset="utf-8">
|
|
115
|
+
<link rel="stylesheet" href="/lit/fonts.css">
|
|
116
|
+
<link rel="stylesheet" href="/lit/style.css">
|
|
117
|
+
<script src="/lit/style.js"></script>
|
|
118
|
+
<script src="/lit/stage-kit.js"></script>${head3d}
|
|
119
|
+
<style>
|
|
120
|
+
html, body { width: ${width}px; height: ${height}px; }
|
|
121
|
+
.shot { position: absolute; left: 0; top: 0; width: ${width}px; height: ${height}px; visibility: hidden; }
|
|
122
|
+
.subject { position: absolute; overflow: visible; }
|
|
123
|
+
.type { display: flex; flex-direction: column; justify-content: center; gap: 0.4em; }${style3d}
|
|
124
|
+
</style>
|
|
125
|
+
</head>
|
|
126
|
+
<body>
|
|
127
|
+
${canvas3d}${sections}
|
|
128
|
+
${scriptOpen} // The film's words: every copy line of treatment.json, verbatim. Edit them here.
|
|
129
|
+
const COPY = ${JSON.stringify(treatment.copy.lines, null, 2).replace(/\n/gu, "\n ")};
|
|
130
|
+
|
|
131
|
+
const { define, shot, place, tokens, text, clamp, clipRect, clipCircle } = LitStage;
|
|
132
|
+
const T = tokens();
|
|
133
|
+
const BEATS = LitTimeline.beats;
|
|
134
|
+
const SHOTS = [...document.querySelectorAll(".shot")];
|
|
135
|
+
SHOTS.forEach((el, i) => {
|
|
136
|
+
place(el.querySelector('[data-zone="subject"]'), BEATS[i].layout, "subject");
|
|
137
|
+
const type = el.querySelector('[data-zone="type"]');
|
|
138
|
+
if (type) place(type, BEATS[i].layout, "type");
|
|
139
|
+
});
|
|
140
|
+
for (const el of document.querySelectorAll("[data-copy]")) {
|
|
141
|
+
el.textContent = COPY[Number(el.dataset.copy)];
|
|
142
|
+
text(el);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// One function per shot: draw the subject into its svg (the subject zone) as a function of the
|
|
146
|
+
// shot's progress. s is LitStage.shot(t): s.p (0..1), s.local (seconds), s.enter(delay, dur).
|
|
147
|
+
const DRAW = [
|
|
148
|
+
${draws}
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
// How a shot hands off to the next, over the style's exit duration before the cut.
|
|
152
|
+
const HANDOFF = T.dur.exit;
|
|
153
|
+
const handoff = { wipe: (el, p) => clipRect(el, p, { from: "left" }), iris: (el, p) => clipCircle(el, p, { x: 50, y: 50 }), dissolve: (el, p) => { el.style.opacity = String(p); } };
|
|
154
|
+
|
|
155
|
+
function paint(i, s, t) {
|
|
156
|
+
const el = SHOTS[i];
|
|
157
|
+
el.style.visibility = "visible";
|
|
158
|
+
const type = el.querySelector('[data-zone="type"]');
|
|
159
|
+
if (type) {
|
|
160
|
+
const e = s.enter(0.1);
|
|
161
|
+
type.style.opacity = String(e);
|
|
162
|
+
type.style.transform = \`translateY(\${(1 - e) * 24}px)\`;
|
|
163
|
+
}
|
|
164
|
+
DRAW[i](s, el.querySelector('[data-zone="subject"]'), t);
|
|
165
|
+
}
|
|
166
|
+
${world3d}
|
|
167
|
+
define({ width: ${width}, height: ${height}, fps: ${treatment.fps ?? 60}, duration: ${treatment.durationSec}, render(t) {
|
|
168
|
+
for (const el of SHOTS) Object.assign(el.style, { visibility: "hidden", opacity: "", clipPath: "", transform: "", zIndex: "" });
|
|
169
|
+
const s = shot(t);
|
|
170
|
+
paint(s.index, s, t);${solid ? "\n drawWorld(s, t);" : ""}
|
|
171
|
+
const next = SHOTS[s.index + 1];
|
|
172
|
+
const into = clamp((t - (s.beat.t1 - HANDOFF)) / HANDOFF);
|
|
173
|
+
if (next && into > 0) {
|
|
174
|
+
const p = T.ease.move(into);
|
|
175
|
+
const kind = s.beat.transition;
|
|
176
|
+
if (handoff[kind]) {
|
|
177
|
+
next.style.zIndex = "1";
|
|
178
|
+
paint(s.index + 1, shot(BEATS[s.index + 1].t0), t);
|
|
179
|
+
handoff[kind](next, p);
|
|
180
|
+
} else if (kind === "push") {
|
|
181
|
+
SHOTS[s.index].style.transform = \`translateX(\${-p * ${width}}px)\`;
|
|
182
|
+
paint(s.index + 1, shot(BEATS[s.index + 1].t0), t);
|
|
183
|
+
next.style.transform = \`translateX(\${(1 - p) * ${width}}px)\`;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
} });
|
|
187
|
+
</script>
|
|
188
|
+
</body>
|
|
189
|
+
</html>
|
|
190
|
+
`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Write `<out>/stage/index.html` from the treatment; refuses to replace a page. */
|
|
194
|
+
// The scaffold's word on the 3D renderer: a Blender film draws its world in stage/scene.py; a film that
|
|
195
|
+
// asks for high-quality 3D on a machine with Blender is pointed at that rung before any drawing starts.
|
|
196
|
+
function rungNote(treatment, out, env) {
|
|
197
|
+
if (treatment.depth !== "3d") return "";
|
|
198
|
+
if (treatment.render3d === "blender") return ` This is a Blender film: write the world in ${path.join(out, "stage", "scene.py")}, look at the preview stills (blender --out ${out}), render the plate (--final), and show it under the type with LitStage.plate() in place of the three.js world (references/depth.md, The Blender rung).`;
|
|
199
|
+
const found = asksPathTraced(treatment.request) ? findBlender({ env }) : null;
|
|
200
|
+
return found ? `\nBLENDER: ${found.path} is installed and the request asks for high-quality 3D. Take the Blender rung: set render3d "blender" in the treatment, write stage/scene.py, and keep this page for the type, labels and panels over the plate (references/depth.md, The Blender rung).` : "";
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function scaffoldCommand({ out, env = process.env }) {
|
|
204
|
+
const treatment = loadTreatment(out, { pathName: "stage" });
|
|
205
|
+
const file = path.join(out, "stage", "index.html");
|
|
206
|
+
if (existsSync(file)) throw new BlockedError(EXIT.USAGE, `${file} exists; scaffold never overwrites a page (move it away to start again)`);
|
|
207
|
+
mkdirSync(path.dirname(file), { recursive: true });
|
|
208
|
+
writeFileSync(file, scaffoldHtml(treatment));
|
|
209
|
+
return { code: EXIT.OK, message: `SCAFFOLD: ${file} (style ${treatment.style}, ${treatment.beats.length} shots). Draw each shot's subject in its DRAW function, then render the board: stage --out ${out} --round 1 --stills-only${rungNote(treatment, out, env)}` };
|
|
210
|
+
}
|