@litfamily/lithermes 1.0.29 → 1.0.30

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.
Files changed (165) hide show
  1. package/README.md +12 -9
  2. package/README_Ko-KR.md +12 -9
  3. package/assets/lithermes-plugin/README.md +15 -2
  4. package/assets/lithermes-plugin/__init__.py +49 -10
  5. package/assets/lithermes-plugin/adhd_stack.py +228 -0
  6. package/assets/lithermes-plugin/core.py +30 -4
  7. package/assets/lithermes-plugin/core_commands.py +99 -0
  8. package/assets/lithermes-plugin/core_contexts.py +54 -4
  9. package/assets/lithermes-plugin/core_contract.py +2 -2
  10. package/assets/lithermes-plugin/core_routing.py +126 -0
  11. package/assets/lithermes-plugin/diagnostics.py +3 -2
  12. package/assets/lithermes-plugin/lit_mark.py +75 -0
  13. package/assets/lithermes-plugin/payload-version.json +470 -58
  14. package/assets/lithermes-plugin/plugin.yaml +1 -1
  15. package/assets/lithermes-plugin/scientific_visualization.py +23 -0
  16. package/assets/lithermes-plugin/skills/frontend-ui-ux/SKILL.md +7 -7
  17. package/assets/lithermes-plugin/skills/frontend-ui-ux/kit/components.css +414 -0
  18. package/assets/lithermes-plugin/skills/frontend-ui-ux/kit/fonts.json +42 -0
  19. package/assets/lithermes-plugin/skills/frontend-ui-ux/kit/layouts/landing.html +164 -0
  20. package/assets/lithermes-plugin/skills/frontend-ui-ux/kit/layouts/settings.html +114 -0
  21. package/assets/lithermes-plugin/skills/frontend-ui-ux/kit/layouts/workspace.html +115 -0
  22. package/assets/lithermes-plugin/skills/frontend-ui-ux/kit/styles/computational-brutalism.json +227 -0
  23. package/assets/lithermes-plugin/skills/frontend-ui-ux/kit/styles/field-notes.json +205 -0
  24. package/assets/lithermes-plugin/skills/frontend-ui-ux/kit/styles/graphite.json +200 -0
  25. package/assets/lithermes-plugin/skills/frontend-ui-ux/kit/styles/meadow.json +199 -0
  26. package/assets/lithermes-plugin/skills/frontend-ui-ux/kit/styles/neo-brutalism.json +222 -0
  27. package/assets/lithermes-plugin/skills/frontend-ui-ux/kit/ui.js +226 -0
  28. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/README.md +2 -4
  29. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/complete-contract.md +4 -4
  30. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/craft-floor.md +10 -0
  31. package/assets/lithermes-plugin/skills/frontend-ui-ux/scripts/design-lock.mjs +400 -0
  32. package/assets/lithermes-plugin/skills/frontend-ui-ux/scripts/probe-rules.json +6 -1
  33. package/assets/lithermes-plugin/skills/frontend-ui-ux/scripts/probe.mjs +76 -4
  34. package/assets/lithermes-plugin/skills/lit-adhd/SKILL.md +194 -0
  35. package/assets/lithermes-plugin/skills/lit-adhd/references/brief-contract.md +55 -0
  36. package/assets/lithermes-plugin/skills/lit-adhd/references/card-examples.md +86 -0
  37. package/assets/lithermes-plugin/skills/lit-adhd/references/status-taxonomy.md +40 -0
  38. package/assets/lithermes-plugin/skills/lit-adhd/scripts/adhd.mjs +11 -0
  39. package/assets/lithermes-plugin/skills/lit-adhd/scripts/lib/collect.mjs +554 -0
  40. package/assets/lithermes-plugin/skills/lit-adhd/scripts/lib/gauge.mjs +89 -0
  41. package/assets/lithermes-plugin/skills/lit-adhd/scripts/lib/hermes-sessions.mjs +219 -0
  42. package/assets/lithermes-plugin/skills/lit-adhd/scripts/lib/next-step.mjs +101 -0
  43. package/assets/lithermes-plugin/skills/lit-adhd/scripts/lib/plan-task-rows.mjs +32 -0
  44. package/assets/lithermes-plugin/skills/lit-adhd/scripts/lib/redact.mjs +42 -0
  45. package/assets/lithermes-plugin/skills/lit-adhd/scripts/lib/secret-shapes.mjs +59 -0
  46. package/assets/lithermes-plugin/skills/lit-diagram-drawer/SKILL.md +7 -4
  47. package/assets/lithermes-plugin/skills/lit-diagram-drawer/references/style-guide.md +20 -11
  48. package/assets/lithermes-plugin/skills/lit-diagram-drawer/references/type-architecture.md +3 -3
  49. package/assets/lithermes-plugin/skills/lit-diagram-drawer/references/type-dependency.md +1 -1
  50. package/assets/lithermes-plugin/skills/lit-diagram-drawer/references/type-tree.md +3 -3
  51. package/assets/lithermes-plugin/skills/lit-diagram-drawer/references/type-wardley.md +1 -1
  52. package/assets/lithermes-plugin/skills/lit-diagram-drawer/references/verifier-guide.md +5 -0
  53. package/assets/lithermes-plugin/skills/lit-diagram-drawer/scripts/diagram_quality.py +4 -4
  54. package/assets/lithermes-plugin/skills/lit-docx/SKILL.md +1 -1
  55. package/assets/lithermes-plugin/skills/lit-hijack/SKILL.md +239 -0
  56. package/assets/lithermes-plugin/skills/lit-hijack/references/capture-and-provenance.md +123 -0
  57. package/assets/lithermes-plugin/skills/lit-hijack/references/clean-room-build.md +128 -0
  58. package/assets/lithermes-plugin/skills/lit-hijack/references/distil-and-licence.md +128 -0
  59. package/assets/lithermes-plugin/skills/lit-hijack/references/gap-and-port-design.md +152 -0
  60. package/assets/lithermes-plugin/skills/lit-hijack/references/parity-and-report.md +131 -0
  61. package/assets/lithermes-plugin/skills/lit-hijack/scripts/hijack.mjs +995 -0
  62. package/assets/lithermes-plugin/skills/lit-plan/SKILL.md +1 -0
  63. package/assets/lithermes-plugin/skills/lit-pptx/SKILL.md +11 -6
  64. package/assets/lithermes-plugin/skills/lit-pptx/fonts/jetbrains-mono/JetBrainsMono-Bold.ttf +0 -0
  65. package/assets/lithermes-plugin/skills/lit-pptx/fonts/jetbrains-mono/JetBrainsMono-Regular.ttf +0 -0
  66. package/assets/lithermes-plugin/skills/lit-pptx/fonts/jetbrains-mono/OFL.txt +93 -0
  67. package/assets/lithermes-plugin/skills/lit-pptx/fonts/provenance.json +1 -0
  68. package/assets/lithermes-plugin/skills/lit-pptx/references/anti-slop-checklist.md +3 -0
  69. package/assets/lithermes-plugin/skills/lit-pptx/references/authoring-guide.md +1 -1
  70. package/assets/lithermes-plugin/skills/lit-pptx/references/consensus-protocol.md +1 -1
  71. package/assets/lithermes-plugin/skills/lit-pptx/references/direction-step.md +8 -2
  72. package/assets/lithermes-plugin/skills/lit-pptx/references/examples/13-engineering-review-computational-brutalism-ko.md +175 -0
  73. package/assets/lithermes-plugin/skills/lit-pptx/references/examples/14-launch-pitch-neo-brutalism-ko.md +156 -0
  74. package/assets/lithermes-plugin/skills/lit-pptx/references/examples/15-design-lecture-bauhaus-ko.md +141 -0
  75. package/assets/lithermes-plugin/skills/lit-pptx/references/examples/16-community-programme-riso-ko.md +160 -0
  76. package/assets/lithermes-plugin/skills/lit-pptx/references/examples/17-public-data-briefing-newsroom-ko.md +183 -0
  77. package/assets/lithermes-plugin/skills/lit-pptx/references/layout-families.md +1 -1
  78. package/assets/lithermes-plugin/skills/lit-pptx/references/tonalities/bauhaus.md +105 -0
  79. package/assets/lithermes-plugin/skills/lit-pptx/references/tonalities/computational-brutalism.md +111 -0
  80. package/assets/lithermes-plugin/skills/lit-pptx/references/tonalities/neo-brutalism.md +114 -0
  81. package/assets/lithermes-plugin/skills/lit-pptx/references/tonalities/newsroom.md +106 -0
  82. package/assets/lithermes-plugin/skills/lit-pptx/references/tonalities/riso.md +101 -0
  83. package/assets/lithermes-plugin/skills/lit-pptx/scripts/craft_extras.py +49 -0
  84. package/assets/lithermes-plugin/skills/lit-pptx/scripts/deck_output.py +4 -2
  85. package/assets/lithermes-plugin/skills/lit-pptx/scripts/lib/render-pack.js +96 -10
  86. package/assets/lithermes-plugin/skills/lit-pptx/scripts/lib/template-registry.js +50 -6
  87. package/assets/lithermes-plugin/skills/lit-pptx/templates/tonalities/bauhaus/pack.yaml +103 -0
  88. package/assets/lithermes-plugin/skills/lit-pptx/templates/tonalities/computational-brutalism/pack.yaml +99 -0
  89. package/assets/lithermes-plugin/skills/lit-pptx/templates/tonalities/neo-brutalism/pack.yaml +107 -0
  90. package/assets/lithermes-plugin/skills/lit-pptx/templates/tonalities/newsroom/pack.yaml +99 -0
  91. package/assets/lithermes-plugin/skills/lit-pptx/templates/tonalities/riso/pack.yaml +99 -0
  92. package/assets/lithermes-plugin/skills/lit-recap/SKILL.md +1 -1
  93. package/assets/lithermes-plugin/skills/lit-schematic/SKILL.md +144 -0
  94. package/assets/lithermes-plugin/skills/lit-schematic/examples/iso-heat-exchanger/build.mjs +38 -0
  95. package/assets/lithermes-plugin/skills/lit-schematic/examples/iso-heat-exchanger/scene.mjs +133 -0
  96. package/assets/lithermes-plugin/skills/lit-schematic/examples/journal-adsorption-rig/build.mjs +87 -0
  97. package/assets/lithermes-plugin/skills/lit-schematic/examples/pid-dosing-skid/build.mjs +85 -0
  98. package/assets/lithermes-plugin/skills/lit-schematic/references/cutaway-outputs.md +70 -0
  99. package/assets/lithermes-plugin/skills/lit-schematic/references/isometric-craft.md +101 -0
  100. package/assets/lithermes-plugin/skills/lit-schematic/references/journal-figures.md +72 -0
  101. package/assets/lithermes-plugin/skills/lit-schematic/references/kit-api.md +134 -0
  102. package/assets/lithermes-plugin/skills/lit-schematic/references/pfd-and-layout.md +53 -0
  103. package/assets/lithermes-plugin/skills/lit-schematic/references/pid-drafting.md +103 -0
  104. package/assets/lithermes-plugin/skills/lit-schematic/references/pid-symbols.md +163 -0
  105. package/assets/lithermes-plugin/skills/lit-schematic/references/verification.md +100 -0
  106. package/assets/lithermes-plugin/skills/lit-schematic/scripts/doctor.mjs +57 -0
  107. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/figure.mjs +81 -0
  108. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/fonts.mjs +27 -0
  109. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/geom.mjs +113 -0
  110. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/interactive.mjs +292 -0
  111. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/isa.mjs +115 -0
  112. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/iso.mjs +719 -0
  113. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/legend-sheet.mjs +162 -0
  114. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/pid.mjs +937 -0
  115. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/png.mjs +132 -0
  116. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/qa-core.mjs +658 -0
  117. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/route.mjs +218 -0
  118. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/sheet.mjs +890 -0
  119. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/svg.mjs +82 -0
  120. package/assets/lithermes-plugin/skills/lit-schematic/scripts/lib/theme.mjs +115 -0
  121. package/assets/lithermes-plugin/skills/lit-schematic/scripts/motion.mjs +129 -0
  122. package/assets/lithermes-plugin/skills/lit-schematic/scripts/qa.mjs +92 -0
  123. package/assets/lithermes-plugin/skills/lit-schematic/scripts/render.mjs +233 -0
  124. package/assets/lithermes-plugin/skills/lit-schematic/scripts/scaffold.mjs +60 -0
  125. package/assets/lithermes-plugin/skills/lit-scientific-visualization/SKILL.md +19 -1
  126. package/assets/lithermes-plugin/skills/lit-typographic-motion/SKILL.md +12 -11
  127. package/assets/lithermes-plugin/skills/lit-typographic-motion/bin/motion.mjs +56 -2
  128. package/assets/lithermes-plugin/skills/lit-typographic-motion/bin/runtime.mjs +129 -4
  129. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/THIRD_PARTY_NOTICES +9 -0
  130. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/blender/runner.py +218 -0
  131. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/blender.mjs +154 -0
  132. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/board.mjs +132 -0
  133. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/completion.mjs +48 -5
  134. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/gate.mjs +13 -0
  135. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/house.mjs +44 -0
  136. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/scaffold.mjs +238 -0
  137. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/score.mjs +643 -0
  138. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/sound-dsp.mjs +21 -0
  139. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/sound.mjs +171 -0
  140. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/stage-qa.mjs +23 -3
  141. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/stage.mjs +124 -17
  142. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/styles.mjs +240 -0
  143. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/treatment.mjs +152 -10
  144. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/vendor/three/LICENSE +21 -0
  145. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/vendor/three/addons/environments/RoomEnvironment.js +144 -0
  146. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/vendor/three/addons/geometries/RoundedBoxGeometry.js +155 -0
  147. package/assets/lithermes-plugin/skills/lit-typographic-motion/engine/vendor/three/three.module.js +54571 -0
  148. package/assets/lithermes-plugin/skills/lit-typographic-motion/recipes/hero-3d/expected.json +67 -0
  149. package/assets/lithermes-plugin/skills/lit-typographic-motion/recipes/hero-3d/index.html +139 -0
  150. package/assets/lithermes-plugin/skills/lit-typographic-motion/references/depth.md +74 -0
  151. package/assets/lithermes-plugin/skills/lit-typographic-motion/references/studio.md +87 -0
  152. package/assets/lithermes-plugin/skills/lit-typographic-motion/references/style-bibles.md +9 -2
  153. package/assets/lithermes-plugin/skills/lit-typographic-motion/references/treatment.md +24 -8
  154. package/assets/lithermes-plugin/skills/lit-typographic-motion/styles/paper.json +41 -0
  155. package/assets/lithermes-plugin/skills/lit-typographic-motion/styles/swiss-signal.json +41 -0
  156. package/assets/lithermes-plugin/skills/lit-typographic-motion/styles/terminalcore.json +42 -0
  157. package/assets/lithermes-plugin/skills/lit-typographic-motion/styles/tidal.json +41 -0
  158. package/assets/lithermes-plugin/skills/litresearch/SKILL.md +4 -0
  159. package/assets/lithermes-plugin/skills/readme-studio/SKILL.md +1 -1
  160. package/assets/lithermes-plugin/skills/review-work/SKILL.md +2 -0
  161. package/assets/lithermes-plugin/skills/start-work/SKILL.md +2 -1
  162. package/assets/lithermes-plugin/skills/visual-qa/references/complete-contract.md +3 -3
  163. package/package.json +1 -1
  164. package/readme-assets/badge-version.svg +1 -1
  165. package/src/lib/skillPayload.js +1 -1
@@ -0,0 +1,238 @@
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
3
+ // the shot's type zone and an empty subject group in its subject zone, entrances and hand-offs
4
+ // on the house style's tokens, and one DRAW function per shot where the drawing goes. The page
5
+ // passes the style rules as written; the drawing is the model's. A 3D film (`depth: "3d"`) also
6
+ // gets the three.js world: an import map onto the vendored build, a full-frame canvas behind the
7
+ // shots, one persistent protagonist group lit and shadowed in the style's palette, and one WORLD
8
+ // function per shot. It never overwrites an existing page.
9
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
10
+ import path from 'node:path';
11
+ import { blenderVersion, findBlender } from './blender.mjs';
12
+ import { FORMATS } from './treatment.mjs';
13
+
14
+ const HANGUL = /[ㄱ-ㆎ가-힣]/u;
15
+
16
+ /**
17
+ * Which copy lines each shot shows: the treatment's `lines` when given; otherwise spread in order
18
+ * over the shots that have a type zone, the first line on the first and the last line on the last.
19
+ */
20
+ export function linesPerShot(treatment) {
21
+ const beats = treatment.beats;
22
+ if (beats.some((beat) => Array.isArray(beat.lines))) return beats.map((beat) => (Array.isArray(beat.lines) ? beat.lines : []));
23
+ const lines = treatment.copy.lines.map((_, index) => index);
24
+ const textShots = beats.map((beat, index) => (beat.layout === 'full' ? null : index)).filter((index) => index !== null);
25
+ const out = beats.map(() => []);
26
+ if (!textShots.length) return out;
27
+ lines.forEach((line, key) => {
28
+ const at = lines.length > 1 ? Math.round((key * (textShots.length - 1)) / (lines.length - 1)) : 0;
29
+ out[textShots[at]].push(line);
30
+ });
31
+ return out;
32
+ }
33
+
34
+ /** The scaffold page for a stage treatment. */
35
+ export function scaffoldHtml(treatment) {
36
+ const [width, height] = FORMATS[treatment.format];
37
+ const perShot = linesPerShot(treatment);
38
+ const lang = treatment.copy.lines.some((line) => HANGUL.test(line)) ? 'ko' : 'en';
39
+ const sections = treatment.beats.map((beat, index) => {
40
+ const lines = perShot[index];
41
+ const roleOf = (key) => (key > 0 ? 'lit-body lit-muted' : beat.layout === 'title' ? 'lit-display lit-ink' : 'lit-headline lit-ink');
42
+ const type = beat.layout === 'full' || !lines.length ? '' : `\n <div class="type" data-zone="type">${lines.map((line, key) => `\n <p class="${roleOf(key)}" data-copy="${line}"></p>`).join('')}\n </div>`;
43
+ return ` <!-- shot ${index + 1}: ${String(beat.purpose).replace(/--/gu, '-')} -->\n <section class="shot" data-shot="${index}">\n <svg class="subject" data-zone="subject"></svg>${type}\n </section>`;
44
+ }).join('\n');
45
+ const draws = treatment.beats.map((beat, index) => ` // shot ${index + 1} (${beat.layout}): ${String(beat.hero).replace(/\*\//gu, '* /')}\n (s, svg, t) => {},`).join('\n');
46
+ const solid = treatment.depth === '3d';
47
+ const worlds = treatment.beats.map((beat, index) => ` // shot ${index + 1} (${beat.layout})\n (s, hero, cam, t) => {},`).join('\n');
48
+ const head3d = solid ? `
49
+ <script type="importmap">{ "imports": { "three": "/lit/three/three.module.js", "three/addons/": "/lit/three/addons/" } }</script>` : '';
50
+ const canvas3d = solid ? `<canvas id="world" width="${width}" height="${height}"></canvas>\n` : '';
51
+ const style3d = solid ? `
52
+ #world { position: absolute; left: 0; top: 0; width: ${width}px; height: ${height}px; }` : '';
53
+ const world3d = solid ? `
54
+ // The world: one scene for the whole film, drawn on the GPU rung (software when it is missing). The
55
+ // protagonist lives in HERO from the first frame to the last; each shot's WORLD function poses it,
56
+ // the camera and anything else as a function of the shot's progress. The camera is aimed so the
57
+ // world origin sits at the centre of the shot's subject zone. More parts of the vendored build:
58
+ // three/addons/geometries/RoundedBoxGeometry.js.
59
+ const PAL = LitStyle.palette;
60
+ const renderer = new THREE.WebGLRenderer({ canvas: document.getElementById("world"), antialias: true, alpha: true, preserveDrawingBuffer: true });
61
+ renderer.setPixelRatio(1);
62
+ renderer.setSize(${width}, ${height}, false);
63
+ renderer.setClearColor(0x000000, 0);
64
+ renderer.outputColorSpace = THREE.SRGBColorSpace;
65
+ renderer.toneMapping = THREE.ACESFilmicToneMapping;
66
+ renderer.shadowMap.enabled = true;
67
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
68
+ const scene = new THREE.Scene();
69
+ scene.environment = new THREE.PMREMGenerator(renderer).fromScene(new RoomEnvironment(), 0.04).texture;
70
+ const cam = new THREE.PerspectiveCamera(32, ${width} / ${height}, 0.1, 100);
71
+ const key = new THREE.DirectionalLight(0xffffff, 2.2);
72
+ key.position.set(3, 6, 4);
73
+ key.castShadow = true;
74
+ key.shadow.mapSize.set(2048, 2048);
75
+ key.shadow.radius = 4;
76
+ key.shadow.bias = -0.0004; // without these a turning object's shadow acne flickers as flashes
77
+ key.shadow.normalBias = 0.03;
78
+ scene.add(key, new THREE.HemisphereLight(PAL.surface ?? PAL.ground, PAL.ground, 0.6));
79
+ const floor = new THREE.Mesh(new THREE.PlaneGeometry(40, 40), new THREE.ShadowMaterial({ opacity: 0.18 }));
80
+ floor.rotation.x = -Math.PI / 2;
81
+ floor.receiveShadow = true;
82
+ scene.add(floor);
83
+ const HERO = new THREE.Group();
84
+ scene.add(HERO);
85
+
86
+ // Aim the camera so the origin lands on the centre of the shot's subject zone.
87
+ function aim(layout) {
88
+ const z = LitStyle.zones[layout].subject;
89
+ cam.setViewOffset(${width}, ${height}, ${width} / 2 - (z.x + z.w / 2), ${height} / 2 - (z.y + z.h / 2), ${width}, ${height});
90
+ return z.h / ${height};
91
+ }
92
+
93
+ const WORLD = [
94
+ ${worlds}
95
+ ];
96
+
97
+ function drawWorld(s, t) {
98
+ cam.position.set(0, 1.6, 7);
99
+ cam.lookAt(0, 0.6, 0);
100
+ aim(s.beat.layout);
101
+ WORLD[s.index](s, HERO, cam, t);
102
+ renderer.render(scene, cam);
103
+ }
104
+ ` : '';
105
+ const scriptOpen = solid ? `<script type="module">
106
+ import * as THREE from "three";
107
+ import { RoomEnvironment } from "three/addons/environments/RoomEnvironment.js";
108
+ ` : `<script>
109
+ `;
110
+ return `<!doctype html>
111
+ <html lang="${lang}">
112
+ <head>
113
+ <meta charset="utf-8">
114
+ <link rel="stylesheet" href="/lit/fonts.css">
115
+ <link rel="stylesheet" href="/lit/style.css">
116
+ <script src="/lit/style.js"></script>
117
+ <script src="/lit/stage-kit.js"></script>${head3d}
118
+ <style>
119
+ html, body { width: ${width}px; height: ${height}px; }
120
+ .shot { position: absolute; left: 0; top: 0; width: ${width}px; height: ${height}px; visibility: hidden; }
121
+ .subject { position: absolute; overflow: visible; }
122
+ .type { position: absolute; display: flex; flex-direction: column; justify-content: center; gap: 0.4em; }${style3d}
123
+ </style>
124
+ </head>
125
+ <body>
126
+ ${canvas3d}${sections}
127
+ ${scriptOpen}
128
+ // 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, text, bezier, clipInset, clipCircle } = LitStage;
132
+ // The drawing helpers the shots use, from the house tokens, the pixel zones
133
+ // and the shot clock (LitStyle, LitTimeline): the page writes no helpers of
134
+ // its own for these, it reads them here.
135
+ const clamp01 = (x) => (x < 0 ? 0 : x > 1 ? 1 : x);
136
+ const tokens = () => ({
137
+ dur: LitStyle.motion.dur,
138
+ clamp: clamp01,
139
+ ease: { move: bezier(...LitStyle.motion.ease.move) },
140
+ });
141
+ const place = (el, layout, kind) => {
142
+ const z = LitStyle.zones[layout]?.[kind];
143
+ if (el && z) Object.assign(el.style, { left: z.x + "px", top: z.y + "px", width: z.w + "px", height: z.h + "px" });
144
+ };
145
+ const shot = (t) => {
146
+ let index = LitTimeline.beats.findIndex((b) => t < b.t1);
147
+ if (index < 0) index = LitTimeline.beats.length - 1;
148
+ const beat = LitTimeline.beats[index];
149
+ return { index, beat, t, p: (t - beat.t0) / Math.max(1e-6, beat.t1 - beat.t0), enter: (delay = 0, dur = 0.45) => clamp01((t - beat.t0 - delay) / dur) };
150
+ };
151
+ const T = tokens();
152
+ const BEATS = LitTimeline.beats;
153
+ const SHOTS = [...document.querySelectorAll(".shot")];
154
+ SHOTS.forEach((el, i) => {
155
+ place(el.querySelector('[data-zone="subject"]'), BEATS[i].layout, "subject");
156
+ const type = el.querySelector('[data-zone="type"]');
157
+ if (type) place(type, BEATS[i].layout, "type");
158
+ });
159
+ for (const el of document.querySelectorAll("[data-copy]")) {
160
+ el.textContent = COPY[Number(el.dataset.copy)];
161
+ text(el);
162
+ }
163
+
164
+ // One function per shot: draw the subject into its svg (the subject zone) as a function of the
165
+ // shot's progress. s is shot(t): s.p (0..1), s.enter(delay, dur).
166
+ const DRAW = [
167
+ ${draws}
168
+ ];
169
+
170
+ // How a shot hands off to the next, over the style's exit duration before the cut.
171
+ const HANDOFF = T.dur.exit;
172
+ const handoff = { wipe: (el, p) => clipInset(el, p, "left"), iris: (el, p) => clipCircle(el, p), dissolve: (el, p) => { el.style.opacity = String(p); } };
173
+
174
+ function paint(i, s, t) {
175
+ const el = SHOTS[i];
176
+ el.style.visibility = "visible";
177
+ const type = el.querySelector('[data-zone="type"]');
178
+ if (type) {
179
+ const e = s.enter(0.1);
180
+ type.style.opacity = String(e);
181
+ type.style.transform = \`translateY(\${(1 - e) * 24}px)\`;
182
+ }
183
+ DRAW[i](s, el.querySelector('[data-zone="subject"]'), t);
184
+ }
185
+
186
+ ${world3d}define({ width: ${width}, height: ${height}, fps: ${treatment.fps ?? 60}, duration: ${treatment.durationSec}, render(t) {
187
+ for (const el of SHOTS) Object.assign(el.style, { visibility: "hidden", opacity: "", clipPath: "", transform: "", zIndex: "" });
188
+ const s = shot(t);
189
+ paint(s.index, s, t);${solid ? "\n drawWorld(s, t);" : ""}
190
+ const next = SHOTS[s.index + 1];
191
+ const into = T.clamp((t - (s.beat.t1 - HANDOFF)) / HANDOFF);
192
+ if (next && into > 0) {
193
+ const p = T.ease.move(into);
194
+ const kind = s.beat.transition;
195
+ if (handoff[kind]) {
196
+ next.style.zIndex = "1";
197
+ paint(s.index + 1, shot(BEATS[s.index + 1].t0), t);
198
+ handoff[kind](next, p);
199
+ } else if (kind === "push") {
200
+ SHOTS[s.index].style.transform = \`translateX(\${-p * ${width}}px)\`;
201
+ paint(s.index + 1, shot(BEATS[s.index + 1].t0), t);
202
+ next.style.transform = \`translateX(\${(1 - p) * ${width}}px)\`;
203
+ }
204
+ }
205
+ } });
206
+ </script>
207
+ </body>
208
+ </html>
209
+ `;
210
+ }
211
+
212
+ /** The note a 3D treatment's scaffold carries: Blender here or not, preview before final, compare
213
+ * with the GPU rung, and the seed only when the treatment pins one.
214
+ */
215
+ export function rungNote(out, treatment) {
216
+ if (treatment.depth !== '3d' || treatment.path !== 'stage') return null;
217
+ if (treatment.render3d === 'blender') {
218
+ const version = blenderVersion();
219
+ const lines = version
220
+ ? [`Blender is here (${version}). Write stage/scene.py with the \`lit\` helpers in scope (the base plate renders in stage/plate-preview), then blender --out ${out}, then blender --out ${out} --final.`]
221
+ : [`Blender is not installed here (${findBlender() ? 'unreachable' : 'LITHERMES_BLENDER, /Applications/Blender.app, blender on PATH'}). Write stage/scene.py anyway so the film is ready, but set "render3d": "three" to render with three.js on the GPU rung (engine/blender/runner.py needs Blender 4.2+).`];
222
+ if (treatment.seed !== undefined) lines.push(`Seed ${treatment.seed >>> 0}: the plate renders the same bytes again.`);
223
+ return lines.join('\n');
224
+ }
225
+ return [
226
+ 'A depth: 3d film defaults to the GPU rung.',
227
+ 'Compare with what the offline Blender rung would build (render3d: blender), keep the cheaper honest one; a Blender film that shows its path-traced plate is checked by its receipt, not by the text checks above.',
228
+ ].join('\n');
229
+ }
230
+
231
+ /** Write `<out>/stage/index.html` from the treatment; refuses to replace a page. Returns { file, note }. */
232
+ export function writeScaffold(out, treatment) {
233
+ const file = path.join(out, 'stage', 'index.html');
234
+ if (existsSync(file)) throw new Error(`scaffold: ${file} already exists; edit the page instead of scaffolding over it`);
235
+ mkdirSync(path.join(out, 'stage'), { recursive: true });
236
+ writeFileSync(file, `${scaffoldHtml(treatment)}`);
237
+ return { file, note: rungNote(out, treatment) };
238
+ }