@docyrus/animaguru 0.6.0

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 (198) hide show
  1. package/NOTICE.md +256 -0
  2. package/README.md +241 -0
  3. package/dist/animaguru.js +27338 -0
  4. package/package.json +37 -0
  5. package/skills/animaguru/SKILL.md +115 -0
  6. package/skills/animaguru-advertising/SKILL.md +25 -0
  7. package/skills/animaguru-advertising/references/brand-bumper.md +11 -0
  8. package/skills/animaguru-advertising/references/comparison.md +11 -0
  9. package/skills/animaguru-advertising/references/creator-testimonial.md +11 -0
  10. package/skills/animaguru-advertising/references/editorial-advertorial.md +11 -0
  11. package/skills/animaguru-advertising/references/game-reward.md +11 -0
  12. package/skills/animaguru-advertising/references/offer-promotion.md +11 -0
  13. package/skills/animaguru-advertising/references/product-launch.md +11 -0
  14. package/skills/animaguru-advertising/references/product-proof.md +11 -0
  15. package/skills/animaguru-analog-film-tape/SKILL.md +29 -0
  16. package/skills/animaguru-analog-film-tape/references/recipe.md +23 -0
  17. package/skills/animaguru-animated-data-visualization/SKILL.md +39 -0
  18. package/skills/animaguru-animated-data-visualization/references/data-contract.md +16 -0
  19. package/skills/animaguru-animated-data-visualization/references/variants.md +17 -0
  20. package/skills/animaguru-art-deco-motion/SKILL.md +29 -0
  21. package/skills/animaguru-art-deco-motion/references/recipe.md +22 -0
  22. package/skills/animaguru-ascii-crt/SKILL.md +29 -0
  23. package/skills/animaguru-ascii-crt/references/recipe.md +22 -0
  24. package/skills/animaguru-blueprint-drafting/SKILL.md +29 -0
  25. package/skills/animaguru-blueprint-drafting/references/recipe.md +22 -0
  26. package/skills/animaguru-cartoon-character-animation/SKILL.md +35 -0
  27. package/skills/animaguru-cartoon-character-animation/references/variants.md +17 -0
  28. package/skills/animaguru-cutout-animation/SKILL.md +33 -0
  29. package/skills/animaguru-cutout-animation/references/production-recipe.md +37 -0
  30. package/skills/animaguru-cutout-animation/references/variants.md +17 -0
  31. package/skills/animaguru-cutout-animation/references/visual-style.md +34 -0
  32. package/skills/animaguru-dark-tech-stage/SKILL.md +29 -0
  33. package/skills/animaguru-dark-tech-stage/references/recipe.md +23 -0
  34. package/skills/animaguru-diagram-formula-motion/SKILL.md +36 -0
  35. package/skills/animaguru-diagram-formula-motion/references/variants.md +17 -0
  36. package/skills/animaguru-engraved-relief-print/SKILL.md +29 -0
  37. package/skills/animaguru-engraved-relief-print/references/recipe.md +24 -0
  38. package/skills/animaguru-flat-vector-motion/SKILL.md +35 -0
  39. package/skills/animaguru-flat-vector-motion/references/variants.md +17 -0
  40. package/skills/animaguru-general-video/SKILL.md +20 -0
  41. package/skills/animaguru-general-video/references/ambient-loop.md +11 -0
  42. package/skills/animaguru-general-video/references/music-lyrics.md +11 -0
  43. package/skills/animaguru-general-video/references/short-film.md +11 -0
  44. package/skills/animaguru-general-video/references/titles-event.md +11 -0
  45. package/skills/animaguru-general-video/references/visual-essay.md +11 -0
  46. package/skills/animaguru-generative-light-signal/SKILL.md +29 -0
  47. package/skills/animaguru-generative-light-signal/references/recipe.md +23 -0
  48. package/skills/animaguru-glass-mosaic/SKILL.md +29 -0
  49. package/skills/animaguru-glass-mosaic/references/recipe.md +23 -0
  50. package/skills/animaguru-halftone-editorial/SKILL.md +29 -0
  51. package/skills/animaguru-halftone-editorial/references/recipe.md +23 -0
  52. package/skills/animaguru-holographic-scan/SKILL.md +29 -0
  53. package/skills/animaguru-holographic-scan/references/recipe.md +22 -0
  54. package/skills/animaguru-interface-motion/SKILL.md +36 -0
  55. package/skills/animaguru-interface-motion/references/screen-mapping.md +16 -0
  56. package/skills/animaguru-interface-motion/references/variants.md +17 -0
  57. package/skills/animaguru-isometric-worlds/SKILL.md +35 -0
  58. package/skills/animaguru-isometric-worlds/references/variants.md +17 -0
  59. package/skills/animaguru-kinetic-typography/SKILL.md +35 -0
  60. package/skills/animaguru-kinetic-typography/references/variants.md +17 -0
  61. package/skills/animaguru-miniature-diorama/SKILL.md +29 -0
  62. package/skills/animaguru-miniature-diorama/references/recipe.md +23 -0
  63. package/skills/animaguru-painterly-depth-animation/SKILL.md +35 -0
  64. package/skills/animaguru-painterly-depth-animation/references/variants.md +17 -0
  65. package/skills/animaguru-paper-popup/SKILL.md +29 -0
  66. package/skills/animaguru-paper-popup/references/recipe.md +22 -0
  67. package/skills/animaguru-photo-footage-editorial/SKILL.md +29 -0
  68. package/skills/animaguru-photo-footage-editorial/references/recipe.md +23 -0
  69. package/skills/animaguru-pixel-art-animation/SKILL.md +36 -0
  70. package/skills/animaguru-pixel-art-animation/references/variants.md +17 -0
  71. package/skills/animaguru-portal-zoom/SKILL.md +29 -0
  72. package/skills/animaguru-portal-zoom/references/recipe.md +21 -0
  73. package/skills/animaguru-risograph-print/SKILL.md +29 -0
  74. package/skills/animaguru-risograph-print/references/recipe.md +23 -0
  75. package/skills/animaguru-sand-drawing/SKILL.md +29 -0
  76. package/skills/animaguru-sand-drawing/references/recipe.md +21 -0
  77. package/skills/animaguru-shadow-theatre/SKILL.md +29 -0
  78. package/skills/animaguru-shadow-theatre/references/recipe.md +23 -0
  79. package/skills/animaguru-silkscreen-poster/SKILL.md +29 -0
  80. package/skills/animaguru-silkscreen-poster/references/recipe.md +22 -0
  81. package/skills/animaguru-stop-motion-objects/SKILL.md +29 -0
  82. package/skills/animaguru-stop-motion-objects/references/recipe.md +23 -0
  83. package/skills/animaguru-studio-product-reveal/SKILL.md +29 -0
  84. package/skills/animaguru-studio-product-reveal/references/recipe.md +23 -0
  85. package/skills/animaguru-video/SKILL.md +79 -0
  86. package/skills/animaguru-whiteboard-line-drawing/SKILL.md +34 -0
  87. package/skills/animaguru-whiteboard-line-drawing/references/variants.md +17 -0
  88. package/templates/video/AGENTS.md +34 -0
  89. package/templates/video/CLAUDE.md +19 -0
  90. package/templates/video/README.md +101 -0
  91. package/templates/video/VIDEO-STYLE-NOTICES.txt +54 -0
  92. package/templates/video/film.config.json +10 -0
  93. package/templates/video/gitignore +11 -0
  94. package/templates/video/index.html +14 -0
  95. package/templates/video/package.json +28 -0
  96. package/templates/video/public/brand/logo-mark.svg +5 -0
  97. package/templates/video/scripts/build.mjs +12 -0
  98. package/templates/video/scripts/export-html.mjs +12 -0
  99. package/templates/video/scripts/export-mp4.mjs +209 -0
  100. package/templates/video/scripts/html-export-plugin.mjs +35 -0
  101. package/templates/video/scripts/html-export.mjs +54 -0
  102. package/templates/video/scripts/install-example.mjs +32 -0
  103. package/templates/video/scripts/review-analysis.mjs +29 -0
  104. package/templates/video/scripts/review-audio.mjs +24 -0
  105. package/templates/video/scripts/review.mjs +37 -0
  106. package/templates/video/scripts/starter-film.sha256 +1 -0
  107. package/templates/video/shared/locales/en/export.json +10 -0
  108. package/templates/video/shared/locales/en/film.json +42 -0
  109. package/templates/video/shared/locales/en/player.json +31 -0
  110. package/templates/video/shared/locales/en/style-studies.json +168 -0
  111. package/templates/video/shared/locales/en/timeline.json +33 -0
  112. package/templates/video/src/assets.ts +4 -0
  113. package/templates/video/src/download.ts +27 -0
  114. package/templates/video/src/exporter.ts +70 -0
  115. package/templates/video/src/film-resources.ts +29 -0
  116. package/templates/video/src/film.ts +132 -0
  117. package/templates/video/src/main.ts +219 -0
  118. package/templates/video/src/resource-timeline.ts +193 -0
  119. package/templates/video/src/review.ts +50 -0
  120. package/templates/video/src/studies/film.ts +26 -0
  121. package/templates/video/src/studies/media.ts +46 -0
  122. package/templates/video/src/studies/model.ts +60 -0
  123. package/templates/video/src/studies/pixi-renderer.ts +121 -0
  124. package/templates/video/src/studies/scenes.ts +410 -0
  125. package/templates/video/src/studies/timing.ts +53 -0
  126. package/templates/video/src/studies/variants.ts +39 -0
  127. package/templates/video/src/style.css +111 -0
  128. package/templates/video/src/timeline-model.ts +55 -0
  129. package/templates/video/style-source-provenance.json +11 -0
  130. package/templates/video/tsconfig.json +15 -0
  131. package/templates/video/vite.config.mjs +9 -0
  132. package/vendor/pixijs-skills/LICENSE +21 -0
  133. package/vendor/pixijs-skills/provenance.json +79 -0
  134. package/vendor/pixijs-skills/skills/pixijs/SKILL.md +66 -0
  135. package/vendor/pixijs-skills/skills/pixijs/references/index.md +136 -0
  136. package/vendor/pixijs-skills/skills/pixijs-accessibility/SKILL.md +224 -0
  137. package/vendor/pixijs-skills/skills/pixijs-application/SKILL.md +293 -0
  138. package/vendor/pixijs-skills/skills/pixijs-application/references/application-options.md +314 -0
  139. package/vendor/pixijs-skills/skills/pixijs-assets/SKILL.md +303 -0
  140. package/vendor/pixijs-skills/skills/pixijs-assets/references/background.md +137 -0
  141. package/vendor/pixijs-skills/skills/pixijs-assets/references/bundles.md +122 -0
  142. package/vendor/pixijs-skills/skills/pixijs-assets/references/caching.md +143 -0
  143. package/vendor/pixijs-skills/skills/pixijs-assets/references/compressed-textures.md +120 -0
  144. package/vendor/pixijs-skills/skills/pixijs-assets/references/fonts.md +205 -0
  145. package/vendor/pixijs-skills/skills/pixijs-assets/references/gif.md +171 -0
  146. package/vendor/pixijs-skills/skills/pixijs-assets/references/manifests.md +175 -0
  147. package/vendor/pixijs-skills/skills/pixijs-assets/references/progress.md +142 -0
  148. package/vendor/pixijs-skills/skills/pixijs-assets/references/resolution.md +142 -0
  149. package/vendor/pixijs-skills/skills/pixijs-assets/references/spritesheet.md +119 -0
  150. package/vendor/pixijs-skills/skills/pixijs-assets/references/svg.md +132 -0
  151. package/vendor/pixijs-skills/skills/pixijs-assets/references/video.md +158 -0
  152. package/vendor/pixijs-skills/skills/pixijs-blend-modes/SKILL.md +215 -0
  153. package/vendor/pixijs-skills/skills/pixijs-color/SKILL.md +299 -0
  154. package/vendor/pixijs-skills/skills/pixijs-core-concepts/SKILL.md +132 -0
  155. package/vendor/pixijs-skills/skills/pixijs-core-concepts/references/render-loop.md +216 -0
  156. package/vendor/pixijs-skills/skills/pixijs-core-concepts/references/renderers.md +287 -0
  157. package/vendor/pixijs-skills/skills/pixijs-create/SKILL.md +276 -0
  158. package/vendor/pixijs-skills/skills/pixijs-custom-rendering/SKILL.md +405 -0
  159. package/vendor/pixijs-skills/skills/pixijs-custom-rendering/references/advanced-gpu.md +244 -0
  160. package/vendor/pixijs-skills/skills/pixijs-custom-rendering/references/uniform-types.md +94 -0
  161. package/vendor/pixijs-skills/skills/pixijs-environments/SKILL.md +243 -0
  162. package/vendor/pixijs-skills/skills/pixijs-events/SKILL.md +387 -0
  163. package/vendor/pixijs-skills/skills/pixijs-filters/SKILL.md +300 -0
  164. package/vendor/pixijs-skills/skills/pixijs-html-source/SKILL.md +228 -0
  165. package/vendor/pixijs-skills/skills/pixijs-math/SKILL.md +489 -0
  166. package/vendor/pixijs-skills/skills/pixijs-migration-v8/SKILL.md +415 -0
  167. package/vendor/pixijs-skills/skills/pixijs-performance/SKILL.md +489 -0
  168. package/vendor/pixijs-skills/skills/pixijs-scene-container/SKILL.md +345 -0
  169. package/vendor/pixijs-skills/skills/pixijs-scene-core-concepts/SKILL.md +243 -0
  170. package/vendor/pixijs-skills/skills/pixijs-scene-core-concepts/references/constructor-options.md +155 -0
  171. package/vendor/pixijs-skills/skills/pixijs-scene-core-concepts/references/container-hierarchy.md +232 -0
  172. package/vendor/pixijs-skills/skills/pixijs-scene-core-concepts/references/layers.md +145 -0
  173. package/vendor/pixijs-skills/skills/pixijs-scene-core-concepts/references/masking.md +129 -0
  174. package/vendor/pixijs-skills/skills/pixijs-scene-core-concepts/references/render-groups.md +140 -0
  175. package/vendor/pixijs-skills/skills/pixijs-scene-core-concepts/references/scene-management.md +169 -0
  176. package/vendor/pixijs-skills/skills/pixijs-scene-core-concepts/references/transforms.md +198 -0
  177. package/vendor/pixijs-skills/skills/pixijs-scene-dom-container/SKILL.md +193 -0
  178. package/vendor/pixijs-skills/skills/pixijs-scene-gif/SKILL.md +233 -0
  179. package/vendor/pixijs-skills/skills/pixijs-scene-graphics/SKILL.md +576 -0
  180. package/vendor/pixijs-skills/skills/pixijs-scene-mesh/SKILL.md +141 -0
  181. package/vendor/pixijs-skills/skills/pixijs-scene-mesh/references/mesh-perspective.md +189 -0
  182. package/vendor/pixijs-skills/skills/pixijs-scene-mesh/references/mesh-plane.md +159 -0
  183. package/vendor/pixijs-skills/skills/pixijs-scene-mesh/references/mesh-rope.md +176 -0
  184. package/vendor/pixijs-skills/skills/pixijs-scene-mesh/references/mesh-simple.md +144 -0
  185. package/vendor/pixijs-skills/skills/pixijs-scene-mesh/references/mesh.md +213 -0
  186. package/vendor/pixijs-skills/skills/pixijs-scene-particle-container/SKILL.md +323 -0
  187. package/vendor/pixijs-skills/skills/pixijs-scene-sprite/SKILL.md +120 -0
  188. package/vendor/pixijs-skills/skills/pixijs-scene-sprite/references/animated-sprite.md +262 -0
  189. package/vendor/pixijs-skills/skills/pixijs-scene-sprite/references/nineslice-sprite.md +202 -0
  190. package/vendor/pixijs-skills/skills/pixijs-scene-sprite/references/sprite.md +181 -0
  191. package/vendor/pixijs-skills/skills/pixijs-scene-sprite/references/tiling-sprite.md +216 -0
  192. package/vendor/pixijs-skills/skills/pixijs-scene-text/SKILL.md +136 -0
  193. package/vendor/pixijs-skills/skills/pixijs-scene-text/references/bitmap-text.md +232 -0
  194. package/vendor/pixijs-skills/skills/pixijs-scene-text/references/html-text.md +192 -0
  195. package/vendor/pixijs-skills/skills/pixijs-scene-text/references/split-bitmap-text.md +187 -0
  196. package/vendor/pixijs-skills/skills/pixijs-scene-text/references/split-text.md +168 -0
  197. package/vendor/pixijs-skills/skills/pixijs-scene-text/references/text.md +311 -0
  198. package/vendor/pixijs-skills/skills/pixijs-ticker/SKILL.md +292 -0
@@ -0,0 +1,32 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFile, writeFile, mkdir, copyFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ const root=fileURLToPath(new URL("../",import.meta.url));
7
+ const copy=JSON.parse(await readFile(path.join(root,"shared/locales/en/style-studies.json"),"utf8"));
8
+ const id=process.argv[2];
9
+ if(process.argv.length!==3 || !Object.hasOwn(copy.styles,id)) throw new Error(`Choose an installed example: npm run example -- ${Object.keys(copy.styles).join(" | ")}`);
10
+ const film=await readFile(path.join(root,"src/film.ts"),"utf8");
11
+ const expected=(await readFile(path.join(root,"scripts/starter-film.sha256"),"utf8")).trim();
12
+ const digest=createHash("sha256").update(film).digest("hex");
13
+ // Reruns also refuse edited studies; the exact last installer output is hashed.
14
+ let previous;
15
+ try { previous=JSON.parse(await readFile(path.join(root,"production/example-selection.json"),"utf8")); } catch(error) { if(error.code!=="ENOENT")throw error; }
16
+ if(digest!==expected && digest!==previous?.filmSha256) throw new Error("This film has been authored. Import the study into your timeline instead; example installation refuses to replace it.");
17
+ const backup=path.join(root,"production/example-backup");
18
+ await mkdir(backup,{recursive:true});
19
+ for(const file of ["src/film.ts","film.config.json","shared/locales/en/player.json"]) {
20
+ const destination=path.join(backup,path.basename(file));
21
+ try { await copyFile(path.join(root,file),destination,1); } catch(error) { if(error.code!=="EEXIST")throw error; }
22
+ }
23
+ const authored=`import { StyleStudyFilm, studyChapters } from "./studies/film";\nimport type { Application } from "pixi.js";\nexport const chapters = studyChapters(${JSON.stringify(id)});\nexport class Film extends StyleStudyFilm {\n constructor(app: Application) { super(app, ${JSON.stringify(id)}); }\n}\n`;
24
+ const config=JSON.parse(await readFile(path.join(root,"film.config.json"),"utf8"));
25
+ Object.assign(config,{title:copy.styles[id].name,slug:id,duration:8,audio:null,language:"en"});
26
+ const player=JSON.parse(await readFile(path.join(root,"shared/locales/en/player.json"),"utf8"));
27
+ Object.assign(player,{template:copy.styles[id].name,starter:"Original style study · adapt to your brief"});
28
+ await writeFile(path.join(root,"src/film.ts"),authored);
29
+ await writeFile(path.join(root,"film.config.json"),JSON.stringify(config,null,2)+"\n");
30
+ await writeFile(path.join(root,"shared/locales/en/player.json"),JSON.stringify(player,null,2)+"\n");
31
+ await writeFile(path.join(root,"production/example-selection.json"),JSON.stringify({id,filmSha256:createHash("sha256").update(authored).digest("hex")},null,2)+"\n");
32
+ console.log(`Installed ${copy.styles[id].name}. Run npm run dev. HTML5 and MP4 export use this film. Original starter files are in production/example-backup/.`);
@@ -0,0 +1,29 @@
1
+ /** Numerical checks complement viewing; they do not infer visual occlusion or factual truth. */
2
+ export function analyzeReview(report, config) {
3
+ const problems=[],warnings=[],runs=new Map(),active=new Map();
4
+ const finish=(key)=>{const run=active.get(key);if(run){const previous=runs.get(key);if(!previous||run.duration>previous.duration)runs.set(key,run);active.delete(key);}};
5
+ for(const sample of report.samples) {
6
+ const seen=new Set();
7
+ for(const b of sample.texts) {
8
+ const key=`${b.id}\0${b.text}`;
9
+ if(!b.text.trim())continue;
10
+ seen.add(key);
11
+ const inside=[b.x,b.y,b.width,b.height].every(Number.isFinite)&&b.x>=-.5&&b.y>=-.5&&b.x+b.width<=config.width+.5&&b.y+b.height<=config.height+.5;
12
+ if(!inside) { finish(key);problems.push({type:"text-bounds",time:sample.time,text:b.text});continue; }
13
+ const run=active.get(key)??{text:b.text,start:sample.time,duration:0};
14
+ run.duration=sample.time-run.start+.2;active.set(key,run);
15
+ }
16
+ for(const key of active.keys())if(!seen.has(key))finish(key);
17
+ }
18
+ for(const key of active.keys())finish(key);
19
+ for(const run of runs.values()) {
20
+ const need=Math.max(.8,[...run.text].filter((c)=>! /\s/.test(c)).length/25);
21
+ if(run.duration<need)warnings.push({type:"reading-hold",...run,suggested:need});
22
+ }
23
+ if(!report.samples.some((s)=>s.texts.length))warnings.push({type:"text-not-checked",message:"Expose actual visible text bounds through window.animaguru.texts."});
24
+ if(report.seekMatches.some((s)=>!s.match))problems.push({type:"seek-determinism",samples:report.seekMatches.filter((s)=>!s.match)});
25
+ for(const cue of report.cues??[]) {
26
+ if(cue.audioTime!==undefined&&Math.abs(cue.time-cue.audioTime)>1/config.fps)problems.push({type:"cue-alignment",cue});
27
+ }
28
+ return {pass:problems.length===0,problems,warnings,readingRuns:[...runs.values()]};
29
+ }
@@ -0,0 +1,24 @@
1
+ import path from "node:path";
2
+ import { execFile } from "node:child_process";
3
+ import { promisify } from "node:util";
4
+ import ffmpeg from "@ffmpeg-installer/ffmpeg";
5
+ import ffprobe from "@ffprobe-installer/ffprobe";
6
+
7
+ const run=promisify(execFile);
8
+ export async function inspectAudio(root,config) {
9
+ if(!config.audio)return {status:"silent",message:"No soundtrack configured; audio checks are not applicable."};
10
+ const publicRoot=path.resolve(root,"public"),file=path.resolve(publicRoot,config.audio);
11
+ if(!file.startsWith(publicRoot+path.sep))throw new Error("Soundtrack must be inside public/.");
12
+ const {stdout}=await run(ffprobe.path,["-v","error","-show_streams","-show_format","-of","json",file]);
13
+ const probe=JSON.parse(stdout),stream=probe.streams.find((s)=>s.codec_type==="audio");
14
+ if(!stream)throw new Error("Configured soundtrack has no audio stream.");
15
+ const {stderr}=await run(ffmpeg.path,["-hide_banner","-nostats","-i",file,"-af","loudnorm=I=-14:TP=-1.2:print_format=json","-f","null","-"],{maxBuffer:1024*1024});
16
+ const json=stderr.match(/\{\s*"input_i"[\s\S]*?\}/)?.[0];
17
+ if(!json)throw new Error("Audio loudness could not be measured.");
18
+ const levels=JSON.parse(json),duration=Number(probe.format.duration);
19
+ return {status:"measured",duration,sampleRate:Number(stream.sample_rate),integratedLufs:levels.input_i,truePeakDb:levels.input_tp,notes:[
20
+ ...(Math.abs(duration-config.duration)>.15?["Soundtrack duration differs from the film; inspect padding or truncation."]:[]),
21
+ ...(Number.isFinite(Number(levels.input_i))&&Math.abs(Number(levels.input_i)+14)>2?["Review soundtrack loudness for the intended placement."]:[]),
22
+ ...(Number(levels.input_tp)>-1?["True peak is above -1 dB; inspect clipping/headroom."]:[]),
23
+ ]};
24
+ }
@@ -0,0 +1,37 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { createServer } from "vite";
6
+ import { analyzeReview } from "./review-analysis.mjs";
7
+ import { inspectAudio } from "./review-audio.mjs";
8
+
9
+ const root=fileURLToPath(new URL("../",import.meta.url));
10
+ const config=JSON.parse(await readFile(path.join(root,"film.config.json"),"utf8"));
11
+ const token=randomUUID(),port=Number(process.env.ANIMAGURU_REVIEW_PORT??5197);
12
+ const output=path.join(root,"production/review");
13
+ const server=await createServer({configFile:false,root,clearScreen:false,server:{host:"127.0.0.1",port,strictPort:true},plugins:[{name:"animaguru-review",configureServer(vite){
14
+ vite.middlewares.use((request,response,next)=>{
15
+ if(request.url!=="/__review/report")return next();
16
+ void (async()=>{
17
+ if(request.method!=="POST"||request.headers["x-animaguru-review"]!==token||request.headers.origin!==`http://127.0.0.1:${port}`){response.writeHead(403).end("Wrong review session.");return;}
18
+ const parts=[];let length=0;
19
+ for await(const part of request){length+=part.length;if(length>32*1024*1024)throw new Error("Review report is too large.");parts.push(part);}
20
+ const report=JSON.parse(Buffer.concat(parts).toString());
21
+ if(!Array.isArray(report.samples)||!Array.isArray(report.frames)||!Array.isArray(report.seekMatches)||report.frames.length>10)throw new Error("Invalid review report.");
22
+ const analysis=analyzeReview(report,config);
23
+ const audio=await inspectAudio(root,config);
24
+ await mkdir(output,{recursive:true});
25
+ for(const [i,frame]of report.frames.entries()){
26
+ if(typeof frame.image!=="string"||!frame.image.startsWith("data:image/jpeg;base64,"))throw new Error("Expected JPEG review frame.");
27
+ await writeFile(path.join(output,`frame-${String(i).padStart(2,"0")}.jpg`),Buffer.from(frame.image.split(",")[1],"base64"));
28
+ }
29
+ await writeFile(path.join(output,"report.json"),JSON.stringify({config,samples:report.samples,seekMatches:report.seekMatches,cues:report.cues,analysis,audio},null,2)+"\n");
30
+ console.log(`${analysis.pass?"Passed":"Failed"}: seek and text-bound checks. ${analysis.warnings.length} reading/review notes. Frames and report: ${output}`);
31
+ console.log("Inspect the frames and moving playback with audio; numerical checks do not establish artistic quality or claim accuracy.");
32
+ response.writeHead(200,{"Content-Type":"application/json"}).end(JSON.stringify(analysis));
33
+ response.once("finish",()=>{void server.close();});process.exitCode=analysis.pass?0:1;
34
+ })().catch((error)=>{response.writeHead(500).end(error.message);void server.close();process.exitCode=1;});
35
+ });
36
+ }}]});
37
+ await server.listen();console.log(`Open this review page and keep it open until completion:\nhttp://127.0.0.1:${port}/?review=${token}`);
@@ -0,0 +1 @@
1
+ f82f05cc73b14a023396f06cf7589e13e3bcb9e5397323e59e2f598e1d1658d8
@@ -0,0 +1,10 @@
1
+ {
2
+ "preparing": "Preparing MP4 export…",
3
+ "rendering": "Rendering MP4 · {percent}% · {frame} / {total} frames. Keep this tab open.",
4
+ "finishing": "Finishing the MP4 and checking its video and audio…",
5
+ "saved": "MP4 saved: {path}",
6
+ "failed": "Export stopped: {error}",
7
+ "requestFailed": "Capture request failed.",
8
+ "languageChanged": "The language changed during capture. Restart the export.",
9
+ "captureFailed": "Could not capture frame {frame}."
10
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "chapters": [
3
+ {
4
+ "start": 0,
5
+ "end": 8,
6
+ "label": "The spark",
7
+ "eyebrow": "01 / EVERY STORY STARTS SOMEWHERE",
8
+ "headline": "A little spark.\nA bigger story.",
9
+ "detail": "Turn something you believe in into something people feel.",
10
+ "caption": "Every great story begins with a little spark.",
11
+ "color": "#DCE7BC",
12
+ "cardLabel": "THE FIRST SPARK",
13
+ "cardCaption": "Small idea. Big possibilities."
14
+ },
15
+ {
16
+ "start": 8,
17
+ "end": 16,
18
+ "label": "The possibility",
19
+ "eyebrow": "02 / MAKE THE IDEA TANGIBLE",
20
+ "headline": "Show the wonder.\nMake it matter.",
21
+ "detail": "Real characters. Small discoveries. A world worth exploring.",
22
+ "caption": "Bring your idea to life, one meaningful moment at a time.",
23
+ "color": "#DCCFF0",
24
+ "cardLabel": "A NEW PERSPECTIVE",
25
+ "cardCaption": "See what comes next."
26
+ },
27
+ {
28
+ "start": 16,
29
+ "end": 24,
30
+ "label": "The invitation",
31
+ "eyebrow": "03 / GIVE THEM A NEXT STEP",
32
+ "headline": "Your vision.\nReady to move.",
33
+ "detail": "Open your coding agent. Tell it the story you want to tell.",
34
+ "caption": "Your next great story starts here.",
35
+ "color": "#F3D88D",
36
+ "cardLabel": "MADE TO MOVE",
37
+ "cardCaption": "Let's tell your story."
38
+ }
39
+ ],
40
+ "brand": "ANIMAGURU / MOTION STUDIO",
41
+ "tagline": "YOUR STORY, IN MOTION"
42
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "studio": "Motion studio",
3
+ "template": "Choose category & style with your agent",
4
+ "play": "Play",
5
+ "pause": "Pause",
6
+ "replay": "Replay",
7
+ "mute": "Mute",
8
+ "unmute": "Unmute",
9
+ "noAudio": "No soundtrack yet",
10
+ "captions": "Captions",
11
+ "fullscreen": "Fullscreen",
12
+ "saveFrame": "Save frame",
13
+ "download": "Download",
14
+ "pngFormat": "PNG",
15
+ "exportHtml": "Export HTML5",
16
+ "htmlFormat": "ZIP",
17
+ "exportPreparing": "Preparing your HTML5 animation…",
18
+ "exportReady": "HTML5 download started. Unzip and upload the folder to embed your animation.",
19
+ "exportFailed": "HTML5 export failed. Check the terminal or rebuild the player, then try again.",
20
+ "timeline": "Video timeline",
21
+ "transcript": "Read the transcript",
22
+ "starter": "Starter preview · replace with your story",
23
+ "exportHint": "Ready for the big screen? Run npm run export:mp4.",
24
+ "loading": "Setting the stage…",
25
+ "failed": "The player could not load. Check the browser console and reload.",
26
+ "audioFailed": "Sound could not play. Check film.config.json and the local audio file.",
27
+ "fullscreenFailed": "Fullscreen is unavailable in this browser.",
28
+ "videoPlayer": "Video player",
29
+ "footer": "Made with AnimaGuru × PixiJS",
30
+ "footerTagline": "IMAGINE. CREATE. ANIMATE."
31
+ }
@@ -0,0 +1,168 @@
1
+ {
2
+ "brand": "ANIMAGURU / ORIGINAL MOTION STUDIES",
3
+ "resourceArt": "Study artwork",
4
+ "resourceMaterial": "Material & background",
5
+ "resourceText": "Study typography",
6
+ "styles": {
7
+ "dark-tech-stage": {
8
+ "name": "Dark Tech Stage",
9
+ "lines": [
10
+ "Bring the signal forward.",
11
+ "Concept stage",
12
+ "One clear focus"
13
+ ]
14
+ },
15
+ "studio-product-reveal": {
16
+ "name": "Studio Product Reveal",
17
+ "lines": [
18
+ "Form. Material. Detail.",
19
+ "Illustrative product",
20
+ "Light reveals the shape"
21
+ ]
22
+ },
23
+ "risograph-print": {
24
+ "name": "Risograph Print",
25
+ "lines": [
26
+ "Two inks. More possibilities.",
27
+ "Separate plates",
28
+ "Meet in the overprint"
29
+ ]
30
+ },
31
+ "halftone-editorial": {
32
+ "name": "Halftone Editorial",
33
+ "lines": [
34
+ "Give the idea a headline.",
35
+ "An original print study",
36
+ "Make the point readable"
37
+ ]
38
+ },
39
+ "silkscreen-poster": {
40
+ "name": "Silkscreen Poster",
41
+ "lines": [
42
+ "One ink. One impression.",
43
+ "Print the composition",
44
+ "Hold the finished image"
45
+ ]
46
+ },
47
+ "art-deco-motion": {
48
+ "name": "Art Deco Motion",
49
+ "lines": [
50
+ "Geometry catches the light.",
51
+ "Open from the center",
52
+ "A measured reveal"
53
+ ]
54
+ },
55
+ "blueprint-drafting": {
56
+ "name": "Blueprint Drafting",
57
+ "lines": [
58
+ "See how the parts connect.",
59
+ "Illustrative drawing",
60
+ "Assemble the relationship"
61
+ ]
62
+ },
63
+ "holographic-scan": {
64
+ "name": "Holographic Scan",
65
+ "lines": [
66
+ "Scan. Focus. Understand.",
67
+ "Illustrative part model",
68
+ "Follow the detail"
69
+ ]
70
+ },
71
+ "paper-popup": {
72
+ "name": "Paper Pop-up",
73
+ "lines": [
74
+ "A flat page gains a world.",
75
+ "An original paper mechanism",
76
+ "Fold. Open. Discover."
77
+ ]
78
+ },
79
+ "shadow-theatre": {
80
+ "name": "Shadow Theatre",
81
+ "lines": [
82
+ "A silhouette tells a thousand things.",
83
+ "Light and carved shape",
84
+ "Move with the material"
85
+ ]
86
+ },
87
+ "engraved-relief-print": {
88
+ "name": "Engraved & Relief Print",
89
+ "lines": [
90
+ "Every mark has a direction.",
91
+ "Carve the form",
92
+ "Pull the impression"
93
+ ]
94
+ },
95
+ "ascii-crt": {
96
+ "name": "ASCII & CRT",
97
+ "lines": [
98
+ "A picture made of characters.",
99
+ "Signal on the grid",
100
+ "Pixels become language"
101
+ ]
102
+ },
103
+ "analog-film-tape": {
104
+ "name": "Analog Film & Tape",
105
+ "lines": [
106
+ "A different kind of time.",
107
+ "Original scene / film variant",
108
+ "Texture follows the medium"
109
+ ]
110
+ },
111
+ "glass-mosaic": {
112
+ "name": "Glass Mosaic",
113
+ "lines": [
114
+ "Pieces become a picture.",
115
+ "Original glass-like mosaic",
116
+ "Light passes through"
117
+ ]
118
+ },
119
+ "portal-zoom": {
120
+ "name": "Portal Zoom",
121
+ "lines": [
122
+ "Every frame opens another.",
123
+ "An original nested world",
124
+ "Keep the journey continuous"
125
+ ]
126
+ },
127
+ "photo-footage-editorial": {
128
+ "name": "Photo & Footage Editorial",
129
+ "lines": [
130
+ "Give every image its place.",
131
+ "Original illustrated media cards",
132
+ "Frame the detail"
133
+ ]
134
+ },
135
+ "stop-motion-objects": {
136
+ "name": "Stop Motion Objects",
137
+ "lines": [
138
+ "Build it one piece at a time.",
139
+ "Illustrative tactile objects",
140
+ "Deliberate held poses"
141
+ ]
142
+ },
143
+ "miniature-diorama": {
144
+ "name": "Miniature Diorama",
145
+ "lines": [
146
+ "A small world, carefully built.",
147
+ "An original 2.5D miniature",
148
+ "Let scale guide the view"
149
+ ]
150
+ },
151
+ "sand-drawing": {
152
+ "name": "Sand Drawing",
153
+ "lines": [
154
+ "A shape emerges from grains.",
155
+ "Original procedural sand study",
156
+ "Reveal the silhouette"
157
+ ]
158
+ },
159
+ "generative-light-signal": {
160
+ "name": "Generative Light & Signal",
161
+ "lines": [
162
+ "Turn a signal into movement.",
163
+ "Original analytic field",
164
+ "Follow the rhythm"
165
+ ]
166
+ }
167
+ }
168
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "title": "RESOURCE TIMELINE",
3
+ "trackCount": "{tracks} rows · {clips} clips",
4
+ "resources": "Resources",
5
+ "playhead": "Timeline playhead",
6
+ "zoomIn": "Zoom in",
7
+ "zoomOut": "Zoom out",
8
+ "fit": "Fit",
9
+ "fitTitle": "Fit the whole film",
10
+ "hint": "Select a clip to inspect it. Drag the ruler to scrub.",
11
+ "selection": "{kind} · {start} — {end} · {duration}s",
12
+ "seekTo": "{label}, {start} to {end}. Seek to this clip.",
13
+ "source": "Source: {source}",
14
+ "seekFailed": "Could not seek to that frame. Check the media and try again.",
15
+ "hidden": "Hidden",
16
+ "muted": "Muted",
17
+ "graphics": "Graphics",
18
+ "image": "Image",
19
+ "video": "Video",
20
+ "text": "Text",
21
+ "captions": "Captions",
22
+ "audio": "Audio",
23
+ "paper": "Paper backdrop",
24
+ "branding": "Studio wordmark",
25
+ "tagline": "Film tagline",
26
+ "artwork": "{scene} artwork",
27
+ "headlines": "Headlines",
28
+ "details": "Supporting copy",
29
+ "eyebrows": "Scene labels",
30
+ "cardLabels": "Card labels",
31
+ "cardCaptions": "Card descriptions",
32
+ "soundtrack": "Soundtrack"
33
+ }
@@ -0,0 +1,4 @@
1
+ /** Public-relative media paths remain portable when an HTML5 export is embedded in a subfolder. */
2
+ export function publicAsset(path: string) {
3
+ return `${import.meta.env.BASE_URL}${path.replace(/^\/+/, "")}`;
4
+ }
@@ -0,0 +1,27 @@
1
+ import config from "../film.config.json";
2
+ import { publicAsset } from "./assets";
3
+
4
+ export function downloadUrl(url: string, filename: string) {
5
+ const link = document.createElement("a");
6
+ link.href = url;
7
+ link.download = filename;
8
+ document.body.append(link);
9
+ link.click();
10
+ link.remove();
11
+ }
12
+
13
+ export async function downloadHtml() {
14
+ const filename = `${config.slug}-html5.zip`;
15
+ if (!import.meta.env.DEV) {
16
+ // npm run build prepares this alongside the static player. No server API is needed.
17
+ downloadUrl(publicAsset(`downloads/${filename}`), filename);
18
+ return;
19
+ }
20
+ const response = await fetch("/__animaguru/html", {
21
+ method: "POST", headers: { "X-AnimaGuru-Export": "html5" }, signal: AbortSignal.timeout(180000),
22
+ });
23
+ if (!response.ok) throw new Error(await response.text());
24
+ const url = URL.createObjectURL(await response.blob());
25
+ downloadUrl(url, filename);
26
+ setTimeout(() => URL.revokeObjectURL(url), 60000);
27
+ }
@@ -0,0 +1,70 @@
1
+ import copy from "../shared/locales/en/export.json";
2
+
3
+ const message = (key: keyof typeof copy, values: Record<string, string | number> = {}) =>
4
+ copy[key].replace(/\{(\w+)\}/g, (_, name: string) => String(values[name] ?? `{${name}}`));
5
+
6
+ type CaptureApi = {
7
+ duration: number;
8
+ width: number;
9
+ height: number;
10
+ canvas: HTMLCanvasElement;
11
+ fps: number;
12
+ language: string;
13
+ renderFrame(frame: number, fps: number): Promise<void>;
14
+ setCaptions(enabled: boolean): void;
15
+ pause(): void;
16
+ };
17
+
18
+ export async function exportMp4(api: CaptureApi, token: string) {
19
+ const state = { frame: 0, total: Math.round(api.duration * api.fps), status: "starting", error: "", path: "" };
20
+ Object.assign(window, { animaguruExport: state });
21
+ const status = document.createElement("div");
22
+ status.setAttribute("role", "status");
23
+ status.style.cssText = "position:fixed;z-index:100;left:24px;bottom:24px;padding:16px 22px;border-radius:12px;background:#162224ed;color:white;font:15px Arial,sans-serif;box-shadow:0 8px 30px #0002;max-width:calc(100vw - 48px)";
24
+ status.textContent = copy.preparing;
25
+ document.body.append(status);
26
+ const request = async (route: string, body?: BodyInit, type = "application/json") => {
27
+ const attempts = route.startsWith("frame?") ? 3 : 1;
28
+ for (let attempt = 0; attempt < attempts; attempt++) {
29
+ try {
30
+ const response = await fetch(`/__export/${route}`, { method: "POST", headers: { "Content-Type": type, "X-AnimaGuru-Export": token }, body, signal: AbortSignal.timeout(route === "finish" ? 180000 : 45000) });
31
+ if (!response.ok) throw new Error(await response.text());
32
+ return await response.json();
33
+ } catch (error) {
34
+ const transient = error instanceof TypeError || error instanceof SyntaxError || (error instanceof DOMException && error.name === "TimeoutError");
35
+ if (!transient || attempt + 1 === attempts) throw error;
36
+ await new Promise((resolve) => window.setTimeout(resolve, 250 * (attempt + 1)));
37
+ }
38
+ }
39
+ throw new Error(copy.requestFailed);
40
+ };
41
+ try {
42
+ api.pause();
43
+ api.setCaptions(true);
44
+ const language = api.language;
45
+ const settings = await request("start", JSON.stringify({ width: api.width, height: api.height, duration: api.duration, fps: api.fps, language }));
46
+ state.total = settings.frames;
47
+ state.status = "rendering";
48
+ for (let frame = 0; frame < settings.frames; frame++) {
49
+ if (api.language !== language) throw new Error(copy.languageChanged);
50
+ await api.renderFrame(frame, settings.fps);
51
+ const image = await new Promise<Blob | null>((resolve) => api.canvas.toBlob(resolve, "image/jpeg", 0.98));
52
+ if (!image) throw new Error(message("captureFailed", { frame }));
53
+ await request(`frame?index=${frame}`, image, "image/jpeg");
54
+ state.frame = frame + 1;
55
+ status.textContent = message("rendering", { percent: Math.round(state.frame / state.total * 100), frame: state.frame.toLocaleString(), total: state.total.toLocaleString() });
56
+ }
57
+ state.status = "encoding";
58
+ status.textContent = copy.finishing;
59
+ const result = await request("finish", "{}");
60
+ state.status = "complete";
61
+ state.path = result.path;
62
+ status.textContent = message("saved", { path: result.path });
63
+ } catch (error) {
64
+ state.status = "failed";
65
+ state.error = error instanceof Error ? error.message : String(error);
66
+ status.textContent = message("failed", { error: state.error });
67
+ await request("abort", JSON.stringify({ error: state.error })).catch(() => {});
68
+ console.error(error);
69
+ }
70
+ }
@@ -0,0 +1,29 @@
1
+ import copy from "../shared/locales/en/timeline.json";
2
+ import type { ResourceKind, ResourceTrack } from "./timeline-model";
3
+
4
+ type Chapter = { start: number; end: number; label: string; headline: string; detail: string; eyebrow: string; cardLabel: string; cardCaption: string; caption: string };
5
+
6
+ /** Mirrors the resources in Film. Add a track/clip here when adding an image, video, or audio stem. */
7
+ export function filmResources(story: { chapters: Chapter[]; brand: string; tagline: string }, config: { duration: number; audio: string | null }) {
8
+ const tracks: ResourceTrack[] = story.chapters.map((chapter, index) => ({
9
+ id: `artwork-${index}`, label: copy.artwork.replace("{scene}", chapter.label), kind: "graphics",
10
+ clips: [{ id: `art-${index}`, label: chapter.cardLabel, start: chapter.start, end: chapter.end }],
11
+ }));
12
+ for (const [field, label, kind] of [
13
+ ["headline", copy.headlines, "text"], ["detail", copy.details, "text"],
14
+ ["eyebrow", copy.eyebrows, "text"], ["cardLabel", copy.cardLabels, "text"],
15
+ ["cardCaption", copy.cardCaptions, "text"], ["caption", copy.captions, "captions"],
16
+ ] as const) {
17
+ tracks.push({ id: field, label, kind, clips: story.chapters.map((chapter, index) => ({
18
+ id: `${field}-${index}`, label: chapter[field], start: chapter.start, end: chapter.end,
19
+ })) });
20
+ }
21
+ const continuous = (id: string, label: string, kind: ResourceKind, content: string, source?: string) => {
22
+ tracks.push({ id, label, kind, clips: [{ id, label: content, start: 0, end: config.duration, ...(source ? { source } : {}) }] });
23
+ };
24
+ continuous("branding", copy.branding, "text", story.brand);
25
+ continuous("tagline", copy.tagline, "text", story.tagline);
26
+ continuous("paper", copy.paper, "graphics", copy.paper);
27
+ if (config.audio) continuous("soundtrack", copy.soundtrack, "audio", config.audio.split(/[\\/]/).pop()!, config.audio);
28
+ return tracks;
29
+ }