@docyrus/animaguru 0.20.0 → 0.23.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 (37) hide show
  1. package/NOTICE.md +25 -1
  2. package/README.md +32 -3
  3. package/dist/animaguru.js +430 -253
  4. package/package.json +1 -1
  5. package/skills/animaguru/SKILL.md +1 -1
  6. package/skills/animaguru-archive-light-table/SKILL.md +34 -0
  7. package/skills/animaguru-archive-light-table/references/recipe.md +26 -0
  8. package/skills/animaguru-briefing-board/SKILL.md +34 -0
  9. package/skills/animaguru-briefing-board/references/recipe.md +27 -0
  10. package/skills/animaguru-data-physicalization/SKILL.md +34 -0
  11. package/skills/animaguru-data-physicalization/references/recipe.md +27 -0
  12. package/skills/animaguru-exploded-teardown/SKILL.md +34 -0
  13. package/skills/animaguru-exploded-teardown/references/recipe.md +27 -0
  14. package/skills/animaguru-lidar-survey/SKILL.md +34 -0
  15. package/skills/animaguru-lidar-survey/references/recipe.md +26 -0
  16. package/skills/animaguru-machine-anatomy/LICENSE +21 -0
  17. package/skills/animaguru-machine-anatomy/SKILL.md +42 -0
  18. package/skills/animaguru-machine-anatomy/references/craft.md +97 -0
  19. package/skills/animaguru-machine-anatomy/references/recipe.md +52 -0
  20. package/skills/animaguru-microscope-story/SKILL.md +34 -0
  21. package/skills/animaguru-microscope-story/references/recipe.md +26 -0
  22. package/skills/animaguru-satellite-briefing/SKILL.md +34 -0
  23. package/skills/animaguru-satellite-briefing/references/recipe.md +27 -0
  24. package/skills/animaguru-white-model-projection/SKILL.md +34 -0
  25. package/skills/animaguru-white-model-projection/references/recipe.md +27 -0
  26. package/templates/video/README.md +1 -1
  27. package/templates/video/VIDEO-STYLE-NOTICES.txt +25 -1
  28. package/templates/video/shared/locales/en/style-studies.json +73 -0
  29. package/templates/video/src/kit/anatomy/LICENSE +21 -0
  30. package/templates/video/src/kit/anatomy/iso-kit.ts +842 -0
  31. package/templates/video/src/kit/anatomy/palette.ts +41 -0
  32. package/templates/video/src/kit/anatomy/pixi.ts +43 -0
  33. package/templates/video/src/player.ts +20 -0
  34. package/templates/video/src/studies/anatomy-plotter.ts +179 -0
  35. package/templates/video/src/studies/scenes.ts +171 -0
  36. package/templates/video/src/style.css +19 -3
  37. package/templates/video/style-source-provenance.json +7 -2
package/dist/animaguru.js CHANGED
@@ -2312,8 +2312,8 @@ var require_lib = __commonJS({
2312
2312
  if (typeof cb !== "function") {
2313
2313
  opts = cb;
2314
2314
  cb = null;
2315
- deferred = new this.Promise(function(resolve17, reject) {
2316
- deferredResolve = resolve17;
2315
+ deferred = new this.Promise(function(resolve18, reject) {
2316
+ deferredResolve = resolve18;
2317
2317
  deferredReject = reject;
2318
2318
  });
2319
2319
  }
@@ -2461,17 +2461,17 @@ var require_lib = __commonJS({
2461
2461
  if (typeof cb === "function") {
2462
2462
  fnx(cb);
2463
2463
  } else {
2464
- return new this.Promise(function(resolve17, reject) {
2464
+ return new this.Promise(function(resolve18, reject) {
2465
2465
  if (fnx.length === 1) {
2466
2466
  fnx(function(err2, ret) {
2467
2467
  if (err2) {
2468
2468
  reject(err2);
2469
2469
  } else {
2470
- resolve17(ret);
2470
+ resolve18(ret);
2471
2471
  }
2472
2472
  });
2473
2473
  } else {
2474
- resolve17(fnx());
2474
+ resolve18(fnx());
2475
2475
  }
2476
2476
  });
2477
2477
  }
@@ -6862,7 +6862,7 @@ var require_pify = __commonJS({
6862
6862
  "use strict";
6863
6863
  var processFn = (fn, options) => function(...args) {
6864
6864
  const P = options.promiseModule;
6865
- return new P((resolve17, reject) => {
6865
+ return new P((resolve18, reject) => {
6866
6866
  if (options.multiArgs) {
6867
6867
  args.push((...result) => {
6868
6868
  if (options.errorFirst) {
@@ -6870,10 +6870,10 @@ var require_pify = __commonJS({
6870
6870
  reject(result);
6871
6871
  } else {
6872
6872
  result.shift();
6873
- resolve17(result);
6873
+ resolve18(result);
6874
6874
  }
6875
6875
  } else {
6876
- resolve17(result);
6876
+ resolve18(result);
6877
6877
  }
6878
6878
  });
6879
6879
  } else if (options.errorFirst) {
@@ -6881,11 +6881,11 @@ var require_pify = __commonJS({
6881
6881
  if (error62) {
6882
6882
  reject(error62);
6883
6883
  } else {
6884
- resolve17(result);
6884
+ resolve18(result);
6885
6885
  }
6886
6886
  });
6887
6887
  } else {
6888
- args.push(resolve17);
6888
+ args.push(resolve18);
6889
6889
  }
6890
6890
  fn.apply(this, args);
6891
6891
  });
@@ -11400,7 +11400,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
11400
11400
  } catch (err22) {
11401
11401
  if (err22 === null || err22.code === "EEXIST") return;
11402
11402
  if (err22.code === "ENOENT" && attempt < retries) {
11403
- await new Promise((resolve17) => setTimeout(resolve17, 0));
11403
+ await new Promise((resolve18) => setTimeout(resolve18, 0));
11404
11404
  continue;
11405
11405
  }
11406
11406
  throw err22;
@@ -14616,9 +14616,9 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
14616
14616
  throw Error("You cannot write to a FIFO that has already been ended!");
14617
14617
  }
14618
14618
  if (this._waiting) {
14619
- const resolve17 = this._waiting;
14619
+ const resolve18 = this._waiting;
14620
14620
  this._waiting = null;
14621
- resolve17({ value: chunk });
14621
+ resolve18({ value: chunk });
14622
14622
  } else {
14623
14623
  this._queue.push(chunk);
14624
14624
  }
@@ -14626,9 +14626,9 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
14626
14626
  end() {
14627
14627
  this._ended = true;
14628
14628
  if (this._waiting) {
14629
- const resolve17 = this._waiting;
14629
+ const resolve18 = this._waiting;
14630
14630
  this._waiting = null;
14631
- resolve17({ done: true });
14631
+ resolve18({ done: true });
14632
14632
  }
14633
14633
  }
14634
14634
  destroy(err2) {
@@ -14647,8 +14647,8 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
14647
14647
  "You cannot call read until the previous call to read has returned!"
14648
14648
  );
14649
14649
  }
14650
- return new Promise((resolve17) => {
14651
- this._waiting = resolve17;
14650
+ return new Promise((resolve18) => {
14651
+ this._waiting = resolve18;
14652
14652
  });
14653
14653
  }
14654
14654
  };
@@ -14803,7 +14803,7 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
14803
14803
  const acks = [];
14804
14804
  let nak = false;
14805
14805
  let done = false;
14806
- return new Promise((resolve17, reject) => {
14806
+ return new Promise((resolve18, reject) => {
14807
14807
  forAwait2(packetlines, (data) => {
14808
14808
  const line = data.toString("utf8").trim();
14809
14809
  if (line.startsWith("shallow")) {
@@ -14830,11 +14830,11 @@ ${obj.gpgsig ? obj.gpgsig : ""}`;
14830
14830
  nak = true;
14831
14831
  }
14832
14832
  if (done) {
14833
- stream.error ? reject(stream.error) : resolve17({ shallows, unshallows, acks, nak, packfile, progress });
14833
+ stream.error ? reject(stream.error) : resolve18({ shallows, unshallows, acks, nak, packfile, progress });
14834
14834
  }
14835
14835
  }).finally(() => {
14836
14836
  if (!done) {
14837
- stream.error ? reject(stream.error) : resolve17({ shallows, unshallows, acks, nak, packfile, progress });
14837
+ stream.error ? reject(stream.error) : resolve18({ shallows, unshallows, acks, nak, packfile, progress });
14838
14838
  }
14839
14839
  });
14840
14840
  });
@@ -19761,7 +19761,7 @@ var require_ignore2 = __commonJS({
19761
19761
  // cli/package.json
19762
19762
  var package_default = {
19763
19763
  name: "@docyrus/animaguru",
19764
- version: "0.20.0",
19764
+ version: "0.23.0",
19765
19765
  description: "Agent-friendly AnimaGuru CLI built with CrustJS",
19766
19766
  type: "module",
19767
19767
  bin: {
@@ -24295,8 +24295,8 @@ var version = defineExtension(VERSION).factory((extension, value, options = {})
24295
24295
 
24296
24296
  // cli/src/commands.ts
24297
24297
  import { spawn as spawn3 } from "node:child_process";
24298
- import { mkdir as mkdir15, readFile as readFile18, writeFile as writeFile16 } from "node:fs/promises";
24299
- import { dirname as dirname14, resolve as resolve16 } from "node:path";
24298
+ import { mkdir as mkdir15, readFile as readFile19, writeFile as writeFile16 } from "node:fs/promises";
24299
+ import { dirname as dirname14, resolve as resolve17 } from "node:path";
24300
24300
  import { setTimeout as delay6 } from "node:timers/promises";
24301
24301
 
24302
24302
  // node_modules/fflate/esm/index.mjs
@@ -25339,13 +25339,14 @@ var SPRITE_IMAGE_MODEL_IDS = SPRITE_IMAGE_MODELS.map((m) => m.id);
25339
25339
  function spriteImageModel(id2) {
25340
25340
  return SPRITE_IMAGE_MODELS.find((m) => m.id === id2) ?? SPRITE_IMAGE_MODELS[0];
25341
25341
  }
25342
- var VIDEO_WORKFLOWS = ["image-to-video", "text-to-video", "extend-video", "reference-to-video", "lip-sync", "camera-controls", "insert-video", "recast", "relight", "3d-to-video", "style"];
25342
+ var VIDEO_WORKFLOWS = ["image-to-video", "text-to-video", "extend-video", "reference-to-video", "lip-sync", "camera-controls", "insert-video", "recast", "relight", "3d-to-video", "style", "keyframes-to-video"];
25343
25343
  var STUDIO_VIDEO_WORKFLOWS = ["image-to-video", "text-to-video", "extend-video"];
25344
25344
  var MEDIA_LENGTH_WORKFLOWS = ["lip-sync", "recast", "relight", "3d-to-video"];
25345
25345
  var lengthFromMedia = (workflow) => MEDIA_LENGTH_WORKFLOWS.includes(workflow);
25346
25346
  var VIDEO_STYLES = ["vhs", "retro-toon-70s", "low-poly", "hand-drawn", "16bit-pixel"];
25347
25347
  var VHS_DAMAGE_LEVELS = ["light", "medium", "heavy"];
25348
- var VIDEO_RESOLUTIONS = ["480p", "768p", "1080p", "2k"];
25348
+ var VIDEO_RESOLUTIONS = ["480p", "720p", "768p", "1080p", "2k"];
25349
+ var H3_RESOLUTIONS = ["480p", "768p", "1080p", "2k"];
25349
25350
  var VIDEO_ASPECT_RATIOS = ["21:9", "16:9", "4:3", "1:1", "3:4", "9:16"];
25350
25351
  var PROMPT_EXPANSION_MODES = ["disabled", "balanced", "quality"];
25351
25352
  var EXTEND_OUTPUTS = ["extended", "continuation"];
@@ -25359,7 +25360,10 @@ var MAX_RECAST_PEOPLE = 4;
25359
25360
  var MAX_3D_REFERENCES = 8;
25360
25361
  var MAX_CAMERA_KEYFRAMES = 12;
25361
25362
  var MAX_CAMERA_TURNS = 32;
25363
+ var MAX_IMAGE_KEYFRAMES = 10;
25364
+ var KEYFRAME_FPS = 24;
25362
25365
  var H3_MAX = "minimax/h3-max";
25366
+ var FLUX_3_MAX = "blackforestlabs/flux-3-max";
25363
25367
  var h3Endpoints = (route) => ({ "image-to-video": `${route}/image-to-video`, "text-to-video": `${route}/text-to-video`, "extend-video": `${route}/extend-video` });
25364
25368
  var SPRITE_VIDEO_MODELS = [
25365
25369
  {
@@ -25388,7 +25392,8 @@ var SPRITE_VIDEO_MODELS = [
25388
25392
  id: "fal/minimax-h3-max",
25389
25393
  label: "MiniMax H3 Max (fal.ai)",
25390
25394
  provider: "fal",
25391
- workflows: VIDEO_WORKFLOWS,
25395
+ // Every workflow but FLUX 3 Max's keyframes.
25396
+ workflows: VIDEO_WORKFLOWS.filter((w) => w !== "keyframes-to-video"),
25392
25397
  endpoints: {
25393
25398
  ...h3Endpoints(H3_MAX),
25394
25399
  "reference-to-video": `${H3_MAX}/reference-to-video`,
@@ -25402,11 +25407,11 @@ var SPRITE_VIDEO_MODELS = [
25402
25407
  },
25403
25408
  resolutions: ["480p", "768p", "1080p"],
25404
25409
  workflowOptions: {
25405
- "extend-video": { resolutions: VIDEO_RESOLUTIONS },
25406
- "lip-sync": { resolutions: VIDEO_RESOLUTIONS, seconds: { min: 5, max: 900, fractional: true } },
25410
+ "extend-video": { resolutions: H3_RESOLUTIONS },
25411
+ "lip-sync": { resolutions: H3_RESOLUTIONS, seconds: { min: 5, max: 900, fractional: true } },
25407
25412
  "insert-video": { resolutions: ["480p", "768p"], seconds: { min: 5, max: 13, fractional: true }, creditsPerSecond: { "480p": 5, "768p": 6 } },
25408
25413
  recast: { resolutions: ["768p", "1080p"], seconds: { min: 5, max: 30, fractional: true }, creditsPerSecond: { "768p": 30, "1080p": 45 } },
25409
- relight: { resolutions: VIDEO_RESOLUTIONS, seconds: { min: 2.33, max: 15, fractional: true } },
25414
+ relight: { resolutions: H3_RESOLUTIONS, seconds: { min: 2.33, max: 15, fractional: true } },
25410
25415
  "3d-to-video": { seconds: { min: 1, max: 15, fractional: true } },
25411
25416
  style: { resolutions: ["768p"], seconds: { min: 5, max: 15 } }
25412
25417
  },
@@ -25426,7 +25431,7 @@ var SPRITE_VIDEO_MODELS = [
25426
25431
  workflows: STUDIO_VIDEO_WORKFLOWS,
25427
25432
  endpoints: h3Endpoints("minimax/h3-max-turbo"),
25428
25433
  resolutions: ["480p", "768p", "1080p"],
25429
- workflowOptions: { "extend-video": { resolutions: VIDEO_RESOLUTIONS } },
25434
+ workflowOptions: { "extend-video": { resolutions: H3_RESOLUTIONS } },
25430
25435
  modes: ["speed"],
25431
25436
  lastFrame: true,
25432
25437
  endFrameOnly: true,
@@ -25435,6 +25440,21 @@ var SPRITE_VIDEO_MODELS = [
25435
25440
  audio: true,
25436
25441
  estimatedCreditsPerSecond: { "480p": { speed: 2.5 }, "768p": { speed: 4 }, "1080p": { speed: 8 }, "2k": { speed: 16 } },
25437
25442
  seconds: { min: 1, max: 15, fractional: true }
25443
+ },
25444
+ {
25445
+ id: "fal/flux-3-max",
25446
+ label: "FLUX 3 Max (fal.ai)",
25447
+ provider: "fal",
25448
+ workflows: ["image-to-video", "text-to-video", "keyframes-to-video"],
25449
+ endpoints: { "image-to-video": `${FLUX_3_MAX}/image-to-video`, "text-to-video": `${FLUX_3_MAX}/text-to-video`, "keyframes-to-video": `${FLUX_3_MAX}/keyframes-to-video` },
25450
+ falInputs: "flux",
25451
+ resolutions: ["720p", "1080p"],
25452
+ modes: ["quality"],
25453
+ lastFrame: true,
25454
+ seed: true,
25455
+ promptExpansion: ["balanced", "quality"],
25456
+ estimatedCreditsPerSecond: { "720p": { quality: 17 }, "1080p": { quality: 29 } },
25457
+ seconds: { min: 5, max: 20 }
25438
25458
  }
25439
25459
  ];
25440
25460
  var SPRITE_VIDEO_MODEL_IDS = SPRITE_VIDEO_MODELS.map((m) => m.id);
@@ -30496,7 +30516,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
30496
30516
  var NONE = 0;
30497
30517
  var ASSUMED = 1;
30498
30518
  var PROVEN = 2;
30499
- function isRecursive(inst, stack, resolve17) {
30519
+ function isRecursive(inst, stack, resolve18) {
30500
30520
  const cached2 = recursive.get(inst);
30501
30521
  if (cached2 !== void 0)
30502
30522
  return cached2 ? PROVEN : NONE;
@@ -30506,7 +30526,7 @@ function isRecursive(inst, stack, resolve17) {
30506
30526
  let result = NONE;
30507
30527
  const check2 = (child) => {
30508
30528
  if (result !== PROVEN && child?._zod) {
30509
- const answer = isRecursive(child, stack, resolve17);
30529
+ const answer = isRecursive(child, stack, resolve18);
30510
30530
  if (answer > result)
30511
30531
  result = answer;
30512
30532
  }
@@ -30517,7 +30537,7 @@ function isRecursive(inst, stack, resolve17) {
30517
30537
  const desc = Object.getOwnPropertyDescriptor(sh, key);
30518
30538
  if (spread && !desc.enumerable)
30519
30539
  continue;
30520
- const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve17) : NONE;
30540
+ const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve18) : NONE;
30521
30541
  if (child > answer)
30522
30542
  answer = child;
30523
30543
  }
@@ -30581,7 +30601,7 @@ function isRecursive(inst, stack, resolve17) {
30581
30601
  break;
30582
30602
  // `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
30583
30603
  case "lazy": {
30584
- const inner = def._cachedInner ?? (resolve17 ? inst._zod.innerType : void 0);
30604
+ const inner = def._cachedInner ?? (resolve18 ? inst._zod.innerType : void 0);
30585
30605
  merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
30586
30606
  break;
30587
30607
  }
@@ -45199,6 +45219,12 @@ async function saveWorkspace(origin, workspaceId) {
45199
45219
  credentials[origin] = { ...saved, workspaceId };
45200
45220
  await writeCredentials(credentials);
45201
45221
  }
45222
+ async function sessionToken(origin) {
45223
+ const saved = (await readCredentials())[origin];
45224
+ const token3 = process.env.ANIMAGURU_TOKEN ?? (saved && saved.expiresAt > Date.now() ? saved.token : void 0);
45225
+ if (!token3) throw new Error("Sign in with animaguru auth login, or set ANIMAGURU_TOKEN.");
45226
+ return token3;
45227
+ }
45202
45228
  var ServiceError = class extends Error {
45203
45229
  constructor(status, code, message) {
45204
45230
  super(message);
@@ -45216,12 +45242,7 @@ var Client = class {
45216
45242
  signal;
45217
45243
  origin;
45218
45244
  async request(path, options = {}) {
45219
- let token3;
45220
- if (options.authenticated !== false) {
45221
- const saved = (await readCredentials())[this.origin];
45222
- token3 = process.env.ANIMAGURU_TOKEN ?? (saved && saved.expiresAt > Date.now() ? saved.token : void 0);
45223
- if (!token3) throw new Error("Sign in with animaguru auth login, or set ANIMAGURU_TOKEN.");
45224
- }
45245
+ const token3 = options.authenticated !== false ? await sessionToken(this.origin) : void 0;
45225
45246
  const generation = options.method === "POST" && /^\/api\/v1\/sprites\/[^/]+\/(jobs|assets|avatars|avatars\/looks|avatars\/[^/]+\/consent)$/.test(path);
45226
45247
  const idempotency = generation ? { "Idempotency-Key": generationRequestId(path, options.body) } : {};
45227
45248
  const response = await fetch(`${this.origin}${path}`, {
@@ -45347,14 +45368,7 @@ var animation_styles_default = {
45347
45368
  "Masked reveals",
45348
45369
  "Speech & beat timing"
45349
45370
  ],
45350
- previewCaption: "Type takes the lead through scale, alignment and a measured reading rhythm.",
45351
- studyCopy: [
45352
- "Make",
45353
- "every",
45354
- "word",
45355
- "move.",
45356
- "TYPE / RHYTHM / EMPHASIS"
45357
- ],
45371
+ previewCaption: "Words slam, mask and split into letters on a 120 bpm beat across four color fields, then settle into a lockup. Drawn entirely in code.",
45358
45372
  family: "graphic",
45359
45373
  example: false
45360
45374
  },
@@ -45371,11 +45385,7 @@ var animation_styles_default = {
45371
45385
  "Connected transitions",
45372
45386
  "Designed easing"
45373
45387
  ],
45374
- previewCaption: "A geometric system assembles, connects and resolves into a clear composition.",
45375
- studyCopy: [
45376
- "A shape. An idea.",
45377
- "One connected system."
45378
- ],
45388
+ previewCaption: "A bouncing ball splits and morphs into hills and a sun, a skyline rises, and a cyclist rides into dusk before an iris closes. Every shape is drawn and morphed in code.",
45379
45389
  family: "graphic",
45380
45390
  example: false
45381
45391
  },
@@ -45392,13 +45402,7 @@ var animation_styles_default = {
45392
45402
  "Drawn connections",
45393
45403
  "Readable drawing holds"
45394
45404
  ],
45395
- previewCaption: "Ordered pen strokes build an idea, then hold the completed drawing for reading.",
45396
- studyCopy: [
45397
- "Draw the connection.",
45398
- "Imagine",
45399
- "Connect",
45400
- "Create"
45401
- ],
45405
+ previewCaption: "A generated hand draws an AnimaGuru-generated marker illustration stroke by stroke, colors it in, titles it and wipes the board clean.",
45402
45406
  family: "graphic",
45403
45407
  example: false
45404
45408
  },
@@ -45415,13 +45419,7 @@ var animation_styles_default = {
45415
45419
  "Travelling path markers",
45416
45420
  "Controlled camera movement"
45417
45421
  ],
45418
- previewCaption: "Platforms rise into place and a travelling marker connects their stations.",
45419
- studyCopy: [
45420
- "A world, connected.",
45421
- "Collect",
45422
- "Shape",
45423
- "Share"
45424
- ],
45422
+ previewCaption: "An isometric town assembles tile by tile: buildings extrude, trees pop, cars and a train start running and the windows light up at dusk. Built from projected geometry in code.",
45425
45423
  family: "technical",
45426
45424
  example: false
45427
45425
  },
@@ -45438,14 +45436,7 @@ var animation_styles_default = {
45438
45436
  "Counted values",
45439
45437
  "Comparison highlights"
45440
45438
  ],
45441
- previewCaption: "Illustrative data only: three sample values reveal and settle at 24, 48 and 72.",
45442
- studyCopy: [
45443
- "Make the comparison clear.",
45444
- "A",
45445
- "B",
45446
- "C",
45447
- "ILLUSTRATIVE DATA / SAMPLE VALUES"
45448
- ],
45439
+ previewCaption: "A line chart is drawn, counted and annotated, then becomes ranked bars beside a platform donut. Drawn entirely in code from sample values.",
45449
45440
  family: "technical",
45450
45441
  example: false
45451
45442
  },
@@ -45462,12 +45453,7 @@ var animation_styles_default = {
45462
45453
  "Parameter changes",
45463
45454
  "Semantic transformations"
45464
45455
  ],
45465
- previewCaption: "A changing radius connects a circle to its area formula and a corresponding plot.",
45466
- studyCopy: [
45467
- "See the relationship.",
45468
- "radius",
45469
- "area"
45470
- ],
45456
+ previewCaption: "A unit circle traces a sine wave, then becomes the largest of 90 rotating circles that draw \u03C0. Plotted from a real Fourier series.",
45471
45457
  family: "technical",
45472
45458
  example: false
45473
45459
  },
@@ -45484,19 +45470,7 @@ var animation_styles_default = {
45484
45470
  "Bounded zooms",
45485
45471
  "State transitions"
45486
45472
  ],
45487
- previewCaption: "A conceptual interface demonstrates focus, a precise click and its resulting state.",
45488
- studyCopy: [
45489
- "Focus on what matters.",
45490
- "CONCEPTUAL INTERFACE",
45491
- "Workspace",
45492
- "Overview",
45493
- "Projects",
45494
- "Library",
45495
- "New project",
45496
- "Project created",
45497
- "Create your next idea.",
45498
- "Ready to create"
45499
- ],
45473
+ previewCaption: "A cursor drives a dashboard: a live toggle, hover lift, drag-to-reorder, a modal with typing and loading, then a toast and a phone notification. Every element and interaction is coded.",
45500
45474
  family: "technical",
45501
45475
  example: false
45502
45476
  },
@@ -45513,10 +45487,7 @@ var animation_styles_default = {
45513
45487
  "Pose-to-pose timing",
45514
45488
  "Gesture & reaction"
45515
45489
  ],
45516
- previewCaption: "An original illustrated character points, pauses and reacts to a moving idea.",
45517
- studyCopy: [
45518
- "An idea comes to life."
45519
- ],
45490
+ previewCaption: "Pip, a cartoon character drawn and animated in AnimaGuru, walks a generated meadow and hops a log, with a following camera, squash and stretch, and dust on landing.",
45520
45491
  family: "character",
45521
45492
  example: false
45522
45493
  },
@@ -45533,11 +45504,7 @@ var animation_styles_default = {
45533
45504
  "Palette accents",
45534
45505
  "Pixel transitions"
45535
45506
  ],
45536
- previewCaption: "A small pixel world comes to life with grid-aligned motion and stepped poses.",
45537
- studyCopy: [
45538
- "SMALL PIXELS.",
45539
- "BIG POSSIBILITIES."
45540
- ],
45507
+ previewCaption: "Bit, a pixel-art sprite animated in AnimaGuru, runs a side-scrolling level built from generated parallax layers, with coins, sparkles and a score drawn in code.",
45541
45508
  family: "character",
45542
45509
  example: false
45543
45510
  },
@@ -45554,10 +45521,7 @@ var animation_styles_default = {
45554
45521
  "Foreground reveals",
45555
45522
  "Selective organic motion"
45556
45523
  ],
45557
- previewCaption: "An original brush-textured landscape moves through overlapping painted planes.",
45558
- studyCopy: [
45559
- "Let the picture breathe."
45560
- ],
45524
+ previewCaption: "Five AnimaGuru-generated oil-painted planes on a slow dolly, with haze between them, sun rays, drifting pollen and wind in the grass.",
45561
45525
  family: "material",
45562
45526
  example: false
45563
45527
  },
@@ -45574,13 +45538,8 @@ var animation_styles_default = {
45574
45538
  "Matched reveals",
45575
45539
  "Controlled depth"
45576
45540
  ],
45577
- studyCopy: [
45578
- "Bring the signal forward.",
45579
- "Concept stage",
45580
- "One clear focus"
45581
- ],
45582
45541
  family: "technical",
45583
- previewCaption: "Dark presentation stages, crisp components and controlled accent light with precise assembly and focused reveals. Original procedural 2.5D study.",
45542
+ previewCaption: "Glass interface panels assemble around a hero card on a reflective stage under sweeping light. Built in code with depth of field and floor reflections.",
45584
45543
  example: true
45585
45544
  },
45586
45545
  "studio-product-reveal": {
@@ -45596,13 +45555,8 @@ var animation_styles_default = {
45596
45555
  "Detail close-ups",
45597
45556
  "Layered part separation"
45598
45557
  ],
45599
- studyCopy: [
45600
- "Form. Material. Detail.",
45601
- "Illustrative product",
45602
- "Light reveals the shape"
45603
- ],
45604
45558
  family: "material",
45605
- previewCaption: "A product silhouette, material details and layered light revealed through deliberate close-ups in a controlled 2.5D studio. Original procedural 2.5D study.",
45559
+ previewCaption: "An unbranded fragrance bottle generated with AnimaGuru turns under a strip light: a macro start, a pull-back, anamorphic glints and a title lockup.",
45606
45560
  example: true
45607
45561
  },
45608
45562
  "risograph-print": {
@@ -45618,13 +45572,8 @@ var animation_styles_default = {
45618
45572
  "Registration shifts",
45619
45573
  "Held-drawing cadence"
45620
45574
  ],
45621
- studyCopy: [
45622
- "Two inks. More possibilities.",
45623
- "Separate plates",
45624
- "Meet in the overprint"
45625
- ],
45626
45575
  family: "print",
45627
- previewCaption: "Porous paper, limited spot inks and colored overprints with deliberate registration and plate reveals. Original procedural 2.5D study.",
45576
+ previewCaption: "An AnimaGuru-generated illustration separated in code into yellow, fluorescent pink and teal drums, then printed pass by pass with riso grain and slight misregistration.",
45628
45577
  example: true
45629
45578
  },
45630
45579
  "halftone-editorial": {
@@ -45640,13 +45589,8 @@ var animation_styles_default = {
45640
45589
  "Stamp impacts",
45641
45590
  "Layered panel changes"
45642
45591
  ],
45643
- studyCopy: [
45644
- "Give the idea a headline.",
45645
- "An original print study",
45646
- "Make the point readable"
45647
- ],
45648
45592
  family: "print",
45649
- previewCaption: "Bold editorial type, dot shading, graphic cutouts and stamps arranged with a strong information hierarchy. Original procedural 2.5D study.",
45593
+ previewCaption: "An AnimaGuru-generated stage photo screened in code into black and orange halftone dots at 45\xB0 and 15\xB0, pulling back from the dots to a magazine spread.",
45650
45594
  example: true
45651
45595
  },
45652
45596
  "silkscreen-poster": {
@@ -45662,13 +45606,8 @@ var animation_styles_default = {
45662
45606
  "Layer-by-layer builds",
45663
45607
  "Graphic holds"
45664
45608
  ],
45665
- studyCopy: [
45666
- "One ink. One impression.",
45667
- "Print the composition",
45668
- "Hold the finished image"
45669
- ],
45670
45609
  family: "print",
45671
- previewCaption: "Opaque ink layers, bold silhouette and stepped color bands revealed through successive printing pulls. Original procedural 2.5D study.",
45610
+ previewCaption: "AnimaGuru-generated poster art cut into five flat stencils in code and pulled screen by screen with a squeegee, ending on the finished print.",
45672
45611
  example: true
45673
45612
  },
45674
45613
  "art-deco-motion": {
@@ -45684,13 +45623,8 @@ var animation_styles_default = {
45684
45623
  "Sequenced lights",
45685
45624
  "Mechanical settling"
45686
45625
  ],
45687
- studyCopy: [
45688
- "Geometry catches the light.",
45689
- "Open from the center",
45690
- "A measured reveal"
45691
- ],
45692
45626
  family: "graphic",
45693
- previewCaption: "Geometric ornament, metallic keylines and lacquer-like planes opening through symmetry and mechanical motion. Original procedural 2.5D study.",
45627
+ previewCaption: "Gold keylines, stepped arches and a sunburst fan open symmetrically; chevrons and marquee lights run in sequence while a specular sweep crosses the metal. Drawn entirely in code.",
45694
45628
  example: true
45695
45629
  },
45696
45630
  "blueprint-drafting": {
@@ -45706,13 +45640,25 @@ var animation_styles_default = {
45706
45640
  "Exploded parts",
45707
45641
  "Anchored dimensions"
45708
45642
  ],
45709
- studyCopy: [
45710
- "See how the parts connect.",
45711
- "Illustrative drawing",
45712
- "Assemble the relationship"
45643
+ family: "technical",
45644
+ previewCaption: "A drafting point traces an AnimaGuru-generated cyanotype line by line, then dimensions, callouts and a title block are drawn in.",
45645
+ example: true
45646
+ },
45647
+ "machine-anatomy": {
45648
+ name: "Machine Anatomy",
45649
+ description: "Ideas built as working isometric machines: dozens of small shaded parts in a framed figure, with a live readout driven by the mechanism.",
45650
+ materials: [
45651
+ "Four-tone isometric solids",
45652
+ "Small, precise parts",
45653
+ "Figure card and live readout"
45654
+ ],
45655
+ motion: [
45656
+ "Mechanism-driven movement",
45657
+ "Eased travel and springs",
45658
+ "Exploded assemblies"
45713
45659
  ],
45714
45660
  family: "technical",
45715
- previewCaption: "Measured line work, construction marks, sections and dimensions on a drafting surface with progressive assembly. Original procedural 2.5D study.",
45661
+ previewCaption: "One dispensing head on a gantry fills a sixteen-well tray in a serpentine pass while the readout counts wells and milliliters. Drawn in code with the bundled Anatomy kit.",
45716
45662
  example: true
45717
45663
  },
45718
45664
  "holographic-scan": {
@@ -45728,13 +45674,8 @@ var animation_styles_default = {
45728
45674
  "Target locking",
45729
45675
  "Local part separation"
45730
45676
  ],
45731
- studyCopy: [
45732
- "Scan. Focus. Understand.",
45733
- "Illustrative part model",
45734
- "Follow the detail"
45735
- ],
45736
45677
  family: "technical",
45737
- previewCaption: "Luminous projected geometry, traveling scans and accurate part callouts with restrained depth and local explosions. Original procedural 2.5D study.",
45678
+ previewCaption: "A wireframe turbofan grows under a moving scan, locks onto its parts and separates them with callouts. Projected 3D geometry drawn in code.",
45738
45679
  example: true
45739
45680
  },
45740
45681
  "paper-popup": {
@@ -45750,13 +45691,8 @@ var animation_styles_default = {
45750
45691
  "Back-to-front arrival",
45751
45692
  "Paper-stage transitions"
45752
45693
  ],
45753
- studyCopy: [
45754
- "A flat page gains a world.",
45755
- "An original paper mechanism",
45756
- "Fold. Open. Discover."
45757
- ],
45758
45694
  family: "paper",
45759
- previewCaption: "Hinged cardstock pieces, visible fronts and backs, fold mechanisms and layered paper stages in constrained perspective. Original procedural 2.5D study.",
45695
+ previewCaption: "A book opens and AnimaGuru-generated cardstock layers hinge up from the fold with shadows and spring, while a pull tab lifts the balloon and sun.",
45760
45696
  example: true
45761
45697
  },
45762
45698
  "shadow-theatre": {
@@ -45772,13 +45708,8 @@ var animation_styles_default = {
45772
45708
  "Jointed articulation",
45773
45709
  "Lamp-distance changes"
45774
45710
  ],
45775
- studyCopy: [
45776
- "A silhouette tells a thousand things.",
45777
- "Light and carved shape",
45778
- "Move with the material"
45779
- ],
45780
45711
  family: "paper",
45781
- previewCaption: "Articulated silhouettes or translucent carved puppets on a lit screen, with distance-driven softness and scale. Original procedural 2.5D study.",
45712
+ previewCaption: "Shadow puppets generated with AnimaGuru perform behind a lit linen screen, finished in code with the cloth's weave, a breathing lamp and a warm glow.",
45782
45713
  example: true
45783
45714
  },
45784
45715
  "engraved-relief-print": {
@@ -45794,13 +45725,8 @@ var animation_styles_default = {
45794
45725
  "Progressive hatching",
45795
45726
  "Successive impressions"
45796
45727
  ],
45797
- studyCopy: [
45798
- "Every mark has a direction.",
45799
- "Carve the form",
45800
- "Pull the impression"
45801
- ],
45802
45728
  family: "print",
45803
- previewCaption: "Form-following hatch marks and carved impressions revealed through stable mark schedules and layered print treatments. Original procedural 2.5D study.",
45729
+ previewCaption: "A generated woodblock print pulled in order: hatching carved first, then two color impressions rolled into register.",
45804
45730
  example: true
45805
45731
  },
45806
45732
  "ascii-crt": {
@@ -45816,13 +45742,8 @@ var animation_styles_default = {
45816
45742
  "Grid-scale reveals",
45817
45743
  "Analytic afterglow"
45818
45744
  ],
45819
- studyCopy: [
45820
- "A picture made of characters.",
45821
- "Signal on the grid",
45822
- "Pixels become language"
45823
- ],
45824
45745
  family: "cinematic",
45825
- previewCaption: "Real glyph-grid imagery, terminal text and restrained phosphor glow with deliberate typing and screen behavior. Original procedural 2.5D study.",
45746
+ previewCaption: "An AnimaGuru-generated jellyfish clip rendered as real ASCII on a 128-column green-phosphor terminal, with curvature, glow trails and a typed command.",
45826
45747
  example: true
45827
45748
  },
45828
45749
  "analog-film-tape": {
@@ -45838,13 +45759,8 @@ var animation_styles_default = {
45838
45759
  "Deterministic weave",
45839
45760
  "Tape-inspired transitions"
45840
45761
  ],
45841
- studyCopy: [
45842
- "A different kind of time.",
45843
- "Original scene / film variant",
45844
- "Texture follows the medium"
45845
- ],
45846
45762
  family: "cinematic",
45847
- previewCaption: "Projected-film cadence or camcorder texture over original scenes, with controlled weave, exposure and medium-specific transitions. Original procedural 2.5D study.",
45763
+ previewCaption: "Two generated summer scenes projected at 18 fps, with gate weave, grain, a slipped splice and a film burn.",
45848
45764
  example: true
45849
45765
  },
45850
45766
  "glass-mosaic": {
@@ -45860,13 +45776,8 @@ var animation_styles_default = {
45860
45776
  "Moving light bands",
45861
45777
  "Identity-preserving rearrangement"
45862
45778
  ],
45863
- studyCopy: [
45864
- "Pieces become a picture.",
45865
- "Original glass-like mosaic",
45866
- "Light passes through"
45867
- ],
45868
45779
  family: "material",
45869
- previewCaption: "Colored panes or loose glass-like tiles, strong seams and moving transmitted-light treatments in 2.5D. Original procedural 2.5D study.",
45780
+ previewCaption: "An AnimaGuru-generated peacock cut in code into leaded glass pieces that slot in one by one, then lit by a low sun crossing the window.",
45870
45781
  example: true
45871
45782
  },
45872
45783
  "portal-zoom": {
@@ -45882,13 +45793,8 @@ var animation_styles_default = {
45882
45793
  "Layered parallax",
45883
45794
  "Occluded scene handoffs"
45884
45795
  ],
45885
- studyCopy: [
45886
- "Every frame opens another.",
45887
- "An original nested world",
45888
- "Keep the journey continuous"
45889
- ],
45890
45796
  family: "cinematic",
45891
- previewCaption: "Nested compositions, matched apertures and continuous scale changes through layered scenes with planned parallax. Original procedural 2.5D study.",
45797
+ previewCaption: "A continuous zoom through four AnimaGuru-generated worlds, each framed inside the last, that ends exactly where it began.",
45892
45798
  example: true
45893
45799
  },
45894
45800
  "photo-footage-editorial": {
@@ -45904,13 +45810,8 @@ var animation_styles_default = {
45904
45810
  "Media-wall selection",
45905
45811
  "Focused detail reveals"
45906
45812
  ],
45907
- studyCopy: [
45908
- "Give every image its place.",
45909
- "Original illustrated media cards",
45910
- "Frame the detail"
45911
- ],
45912
45813
  family: "cinematic",
45913
- previewCaption: "Purposeful media frames, filmstrips and contact-sheet layouts with selective focus and continuous framing relationships. Original procedural 2.5D study.",
45814
+ previewCaption: "Six generated photographs on a contact sheet. The circled frame becomes footage, then returns to its strip.",
45914
45815
  example: true
45915
45816
  },
45916
45817
  "stop-motion-objects": {
@@ -45926,13 +45827,8 @@ var animation_styles_default = {
45926
45827
  "Physical-looking assembly",
45927
45828
  "Held-drawing timing"
45928
45829
  ],
45929
- studyCopy: [
45930
- "Build it one piece at a time.",
45931
- "Illustrative tactile objects",
45932
- "Deliberate held poses"
45933
- ],
45934
45830
  family: "material",
45935
- previewCaption: "Tactile tabletop objects and deliberate held poses assembled with attached contact shadows in a 2.5D treatment. Original procedural 2.5D study.",
45831
+ previewCaption: "Clay footage generated with AnimaGuru, held on twos at 12 fps, with a nudge of the set and a flicker of the lamp between poses.",
45936
45832
  example: true
45937
45833
  },
45938
45834
  "miniature-diorama": {
@@ -45948,13 +45844,8 @@ var animation_styles_default = {
45948
45844
  "Elevated camera travel",
45949
45845
  "Optional time-lapse activity"
45950
45846
  ],
45951
- studyCopy: [
45952
- "A small world, carefully built.",
45953
- "An original 2.5D miniature",
45954
- "Let scale guide the view"
45955
- ],
45956
45847
  family: "material",
45957
- previewCaption: "Small constructed worlds seen from an elevated angle, with coherent scale, layered focus and controlled miniature activity. Original procedural 2.5D study.",
45848
+ previewCaption: "A generated harbor model with a slow elevated drift, a focus band on the quay and small time-lapsed activity.",
45958
45849
  example: true
45959
45850
  },
45960
45851
  "sand-drawing": {
@@ -45970,13 +45861,8 @@ var animation_styles_default = {
45970
45861
  "Authored reshape paths",
45971
45862
  "Material holds"
45972
45863
  ],
45973
- studyCopy: [
45974
- "A shape emerges from grains.",
45975
- "Original procedural sand study",
45976
- "Reveal the silhouette"
45977
- ],
45978
45864
  family: "material",
45979
- previewCaption: "Granular surface marks and swept negative space revealing coherent silhouettes through an original seeded 2D density recipe. Original procedural 2.5D study.",
45865
+ previewCaption: "A whale is swept into a seabird on a lit sand table, in one take. Footage generated with AnimaGuru.",
45980
45866
  example: true
45981
45867
  },
45982
45868
  "generative-light-signal": {
@@ -45992,20 +45878,150 @@ var animation_styles_default = {
45992
45878
  "Measured cue response",
45993
45879
  "Controlled pattern changes"
45994
45880
  ],
45995
- studyCopy: [
45996
- "Turn a signal into movement.",
45997
- "Original analytic field",
45998
- "Follow the rhythm"
45999
- ],
46000
45881
  family: "graphic",
46001
- previewCaption: "Luminous traces, geometric fields and seeded paths choreographed by time or precomputed audio cues. Original procedural 2.5D study.",
45882
+ previewCaption: "Seeded light particles stream along braided ribbons, flaring as timed pulses pass. A seamless loop rendered in code.",
45883
+ example: true
45884
+ },
45885
+ "satellite-briefing": {
45886
+ name: "Satellite Briefing",
45887
+ description: "Satellite imagery graded to an analyst's grey, with thin measured hairlines, dated passes and one reading per screen in one continuous zoom.",
45888
+ materials: [
45889
+ "Georeferenced imagery plates",
45890
+ "Analyst grey grade",
45891
+ "Hairline measurements"
45892
+ ],
45893
+ motion: [
45894
+ "Orbit-to-ground dive",
45895
+ "Dated scan passes",
45896
+ "Range lines that count up"
45897
+ ],
45898
+ previewCaption: "From orbit down to a drying lake: four satellite passes print their shorelines and a range line measures the retreat. Procedural terrain drawn in code, not a real place.",
45899
+ family: "evidence",
45900
+ example: true
45901
+ },
45902
+ "briefing-board": {
45903
+ name: "Engineering Briefing Board",
45904
+ description: "A filmed whiteboard wall where printed cards land with weight and marker ink in fixed colours measures every option against one limit.",
45905
+ materials: [
45906
+ "Enamel board with ghosts",
45907
+ "Three-colour marker ink",
45908
+ "Printed cards & magnets"
45909
+ ],
45910
+ motion: [
45911
+ "Riding dimension lines",
45912
+ "Weighted landings",
45913
+ "Whole-wall reveal"
45914
+ ],
45915
+ previewCaption: "Three drones are measured against one reserve line, a stamp picks the winner and the camera pulls back to the whole wall. Drawn in code with illustrative values.",
45916
+ family: "evidence",
45917
+ example: true
45918
+ },
45919
+ "lidar-survey": {
45920
+ name: "LiDAR Survey",
45921
+ description: "Measured point clouds flown through in the dark, peeled by a cutting plane and condensed into a raking-lit surface with traced features.",
45922
+ materials: [
45923
+ "Point clouds coloured by height",
45924
+ "Raking-lit relief",
45925
+ "Amber traced features"
45926
+ ],
45927
+ motion: [
45928
+ "Scan fronts",
45929
+ "Canopy peel",
45930
+ "Points condensing to ground"
45931
+ ],
45932
+ previewCaption: "A scan front paints a forest in points, a cutting plane peels the canopy and the ground condenses to reveal old field walls. A procedural cloud drawn in code, not a real survey.",
45933
+ family: "evidence",
45934
+ example: true
45935
+ },
45936
+ "white-model-projection": {
45937
+ name: "White Model Projection",
45938
+ description: "A white massing model in a dark room, where projected light paints data onto the buildings one layer at a time.",
45939
+ materials: [
45940
+ "Matte white massing",
45941
+ "Projected data light",
45942
+ "Pins & single readouts"
45943
+ ],
45944
+ motion: [
45945
+ "Lights out, lamp on",
45946
+ "Decade-by-decade switch-ons",
45947
+ "A rising level of light"
45948
+ ],
45949
+ previewCaption: "The house lights cut, each decade lights the buildings it left, a level of light climbs every facade and the tallest tower is picked out. Rendered in code; an illustrative district.",
45950
+ family: "evidence",
45951
+ example: true
45952
+ },
45953
+ "data-physicalization": {
45954
+ name: "Data Physicalization",
45955
+ description: "Statistics built as countable objects on a crafted base, lit like a studio product shot and read at the edge of one unit.",
45956
+ materials: [
45957
+ "Countable unit plates",
45958
+ "Metal, acrylic & walnut",
45959
+ "Pinned paper readings"
45960
+ ],
45961
+ motion: [
45962
+ "Pieces placed with weight",
45963
+ "Tracked growth",
45964
+ "Pull-back to the installation"
45965
+ ],
45966
+ previewCaption: "Ten years of two series become aluminium and acrylic plates on a walnut slab; the stacks cross in 2020 and the camera pulls back to the whole installation. Rendered in code with illustrative values.",
45967
+ family: "evidence",
45968
+ example: true
45969
+ },
45970
+ "exploded-teardown": {
45971
+ name: "Exploded Teardown",
45972
+ description: "A product taken apart on one axis under studio light, every part named with the numbers in its own files, then closed and dimensioned.",
45973
+ materials: [
45974
+ "Studio-lit product parts",
45975
+ "Painted circuit boards",
45976
+ "Leader labels & dimensions"
45977
+ ],
45978
+ motion: [
45979
+ "Layer-by-layer separation",
45980
+ "Lights on inside",
45981
+ "Closing and measuring"
45982
+ ],
45983
+ previewCaption: "An original pad controller lifts apart in one orbit, its LEDs light the diffuser, labels land in a column and it closes to be dimensioned. Rendered in code from its own model.",
45984
+ family: "evidence",
45985
+ example: true
45986
+ },
45987
+ "microscope-story": {
45988
+ name: "Microscope Story",
45989
+ description: "One continuous dive from a slide on the bench to a single cell, with focus breaths, true scale bars and readings counted from the image.",
45990
+ materials: [
45991
+ "Stained specimen plates",
45992
+ "Frosted instrument panels",
45993
+ "Measured rings & counts"
45994
+ ],
45995
+ motion: [
45996
+ "Log-zoom dive",
45997
+ "Focus breaths & sweeps",
45998
+ "Counting the colour"
45999
+ ],
46000
+ previewCaption: "A slide on the bench dives to 10\xD7, the blue inside one circle is counted pixel by pixel, then the camera goes on to a single nucleus. A procedural specimen drawn in code, not a real slide.",
46001
+ family: "evidence",
46002
+ example: true
46003
+ },
46004
+ "archive-light-table": {
46005
+ name: "Archive Light Table",
46006
+ description: "Archive photographs and documents on a backlit table, where a loupe turns a negative positive and catalogue records sit beside the objects.",
46007
+ materials: [
46008
+ "Backlit archive objects",
46009
+ "Loupe optics",
46010
+ "Catalogue tags & index"
46011
+ ],
46012
+ motion: [
46013
+ "Table glides",
46014
+ "Loupe landings & develop fronts",
46015
+ "Into the photograph"
46016
+ ],
46017
+ previewCaption: "Three NASA Ames photographs of one airfield on a light table; under the loupe a 1933 negative turns positive and opens into the picture. Public-domain NASA scans; the negatives are simulated.",
46018
+ family: "evidence",
46002
46019
  example: true
46003
46020
  }
46004
46021
  },
46005
46022
  browseStyles: "Browse animation styles",
46006
46023
  styleCount_one: "{{count}} visual style, guided by your brief",
46007
46024
  styleCount_other: "{{count}} visual styles, guided by your brief",
46008
- previewBrand: "ANIMAGURU / MOTION STUDIES",
46009
46025
  families: {
46010
46026
  all: "All styles",
46011
46027
  graphic: "Graphic & type",
@@ -46014,7 +46030,8 @@ var animation_styles_default = {
46014
46030
  paper: "Paper & cutout",
46015
46031
  character: "Character & pixel",
46016
46032
  material: "Materials & depth",
46017
- cinematic: "Cinema & media"
46033
+ cinematic: "Cinema & media",
46034
+ evidence: "Evidence & data"
46018
46035
  },
46019
46036
  searchLabel: "Search visual styles",
46020
46037
  searchPlaceholder: "Try paper, product, typography\u2026",
@@ -46111,6 +46128,7 @@ var VIDEO_STYLE_IDS = [
46111
46128
  "silkscreen-poster",
46112
46129
  "art-deco-motion",
46113
46130
  "blueprint-drafting",
46131
+ "machine-anatomy",
46114
46132
  "holographic-scan",
46115
46133
  "paper-popup",
46116
46134
  "shadow-theatre",
@@ -46123,7 +46141,15 @@ var VIDEO_STYLE_IDS = [
46123
46141
  "stop-motion-objects",
46124
46142
  "miniature-diorama",
46125
46143
  "sand-drawing",
46126
- "generative-light-signal"
46144
+ "generative-light-signal",
46145
+ "satellite-briefing",
46146
+ "briefing-board",
46147
+ "lidar-survey",
46148
+ "white-model-projection",
46149
+ "data-physicalization",
46150
+ "exploded-teardown",
46151
+ "microscope-story",
46152
+ "archive-light-table"
46127
46153
  ];
46128
46154
  var VIDEO_WORKFLOW_IDS = [
46129
46155
  "product-proof",
@@ -46452,6 +46478,7 @@ var cameraKeyframe = external_exports.object({
46452
46478
  /** Distance from the subject in scene units. */
46453
46479
  distance: external_exports.number().gt(0).max(100)
46454
46480
  });
46481
+ var imageKeyframe = external_exports.object({ image: blob, seconds: external_exports.number().min(0).max(20) });
46455
46482
  var videoAsset = external_exports.object({
46456
46483
  name: base.name,
46457
46484
  /** Lip-sync and relight take no prompt, and camera-controls, insert-video, recast and 3d-to-video may leave it out. */
@@ -46478,6 +46505,8 @@ var videoAsset = external_exports.object({
46478
46505
  referenceVideos: external_exports.array(blob).max(MAX_REFERENCE_VIDEOS).optional(),
46479
46506
  /** The camera's path around the reference image: 2–12 keyframes. */
46480
46507
  camera: external_exports.array(cameraKeyframe).min(2).max(MAX_CAMERA_KEYFRAMES).optional(),
46508
+ /** Keyframes-to-video: 1–10 images pinned to moments of the clip, each on a frame of its own. */
46509
+ keyframes: external_exports.array(imageKeyframe).min(1).max(MAX_IMAGE_KEYFRAMES).optional(),
46481
46510
  /** Seconds into the source where the inserted scene starts, and where the source resumes after it. */
46482
46511
  insertAt: external_exports.number().min(1.625).max(60).optional(),
46483
46512
  resumeAt: external_exports.number().gt(1.625).max(60).optional(),
@@ -46521,6 +46550,7 @@ var TAKES = {
46521
46550
  references: ["reference-to-video", "insert-video", "recast", "3d-to-video"],
46522
46551
  referenceVideos: ["reference-to-video", "insert-video"],
46523
46552
  camera: ["camera-controls"],
46553
+ keyframes: ["keyframes-to-video"],
46524
46554
  insertAt: ["insert-video"],
46525
46555
  resumeAt: ["insert-video"],
46526
46556
  colorMatch: ["insert-video"],
@@ -46531,8 +46561,8 @@ var TAKES = {
46531
46561
  generatedReferences: ["3d-to-video"],
46532
46562
  style: ["style"],
46533
46563
  damage: ["style"],
46534
- aspectRatio: ["text-to-video", "extend-video", "reference-to-video", "relight", "style"],
46535
- promptExpansion: ["image-to-video", "text-to-video", "extend-video", "reference-to-video", "camera-controls", "insert-video"],
46564
+ aspectRatio: ["text-to-video", "extend-video", "reference-to-video", "relight", "style", "keyframes-to-video"],
46565
+ promptExpansion: ["image-to-video", "text-to-video", "extend-video", "reference-to-video", "camera-controls", "insert-video", "keyframes-to-video"],
46536
46566
  seed: ALL.filter((w) => w !== "3d-to-video"),
46537
46567
  extendOutput: ["extend-video"]
46538
46568
  };
@@ -46543,7 +46573,8 @@ var NEEDS = {
46543
46573
  recast: ["source", "references", "subjectConsent"],
46544
46574
  relight: ["source", "lighting"],
46545
46575
  "3d-to-video": ["source"],
46546
- style: ["style"]
46576
+ style: ["style"],
46577
+ "keyframes-to-video": ["keyframes"]
46547
46578
  };
46548
46579
  var LABELS = {
46549
46580
  reference: "a starting image",
@@ -46556,6 +46587,7 @@ var LABELS = {
46556
46587
  references: "reference images",
46557
46588
  referenceVideos: "reference videos",
46558
46589
  camera: "camera keyframes",
46590
+ keyframes: "keyframe images",
46559
46591
  insertAt: "insertAt",
46560
46592
  resumeAt: "resumeAt",
46561
46593
  colorMatch: "colorMatch",
@@ -46641,6 +46673,11 @@ function videoChecks(p, ctx) {
46641
46673
  const travel = p.camera.slice(1).reduce((sum, k, i2) => sum + Math.abs(k.azimuth - p.camera[i2].azimuth), 0);
46642
46674
  if (travel > 360 * MAX_CAMERA_TURNS) issue2("camera", `The camera may travel at most ${MAX_CAMERA_TURNS} full turns around the subject.`);
46643
46675
  }
46676
+ if (p.keyframes) {
46677
+ const frames = p.keyframes.map((k) => Math.round(k.seconds * KEYFRAME_FPS));
46678
+ if (new Set(frames).size !== frames.length) issue2("keyframes", `Put each keyframe on a frame of its own; the video has ${KEYFRAME_FPS} frames a second.`);
46679
+ if (p.keyframes.some((k) => k.seconds > seconds2)) issue2("keyframes", "Put each keyframe within the video's length.");
46680
+ }
46644
46681
  }
46645
46682
  var defaultSeconds = (model, workflow) => {
46646
46683
  const r = videoSeconds(model, workflow);
@@ -47112,7 +47149,7 @@ var CLOUD_PART_BYTES = 8 * 1024 * 1024;
47112
47149
  var CLOUD_MANIFEST_PATH = ".animaguru-cloud.json";
47113
47150
  var CLOUD_MANIFEST_MAX_BYTES = 16 * 1024 * 1024;
47114
47151
  var CLOUD_MAX_FILES = 5e4;
47115
- var CLOUD_STORAGE_BYTES = { pro: 20 * 1024 ** 3, team: 100 * 1024 ** 3 };
47152
+ var CLOUD_STORAGE_BYTES = { starter: 5 * 1024 ** 3, pro: 20 * 1024 ** 3, team: 100 * 1024 ** 3 };
47116
47153
  var cloudProjectId = external_exports.string().regex(/^prj_[A-Za-z0-9]{8,40}$/, "Use a project ID such as prj_\u2026.");
47117
47154
  var sha256Hex = external_exports.string().regex(/^[a-f0-9]{64}$/, "Use a lowercase SHA-256 hex digest.");
47118
47155
  var gitObjectId = external_exports.string().regex(/^[a-f0-9]{40}$/, "Use a full 40-character version ID.");
@@ -47917,6 +47954,7 @@ var extras = {
47917
47954
  "middle-reference",
47918
47955
  "middle-seconds",
47919
47956
  "camera",
47957
+ "keyframes",
47920
47958
  "insert-at",
47921
47959
  "resume-at",
47922
47960
  "color-match",
@@ -47951,11 +47989,16 @@ var regions = (value) => value?.split(";").filter((region) => region.trim()).map
47951
47989
  if (rest.length || [x2, y, width, height].some((n) => !Number.isFinite(n))) throw new Error(`--mask regions are x,y,width,height in fractions of Image 1, separated by semicolons; "${region.trim()}" is not.`);
47952
47990
  return { x: x2, y, width, height };
47953
47991
  });
47954
- var keyframes = (value) => value?.split(";").filter((frame) => frame.trim()).map((frame) => {
47992
+ var cameraKeyframes = (value) => value?.split(";").filter((frame) => frame.trim()).map((frame) => {
47955
47993
  const [time3, azimuth, elevation, distance, ...rest] = frame.split(",").map((n) => n.trim() ? Number(n) : NaN);
47956
47994
  if (rest.length || [time3, azimuth, elevation, distance].some((n) => !Number.isFinite(n))) throw new Error(`--camera keyframes are time,azimuth,elevation,distance (time 0\u20131, angles in degrees), separated by semicolons; "${frame.trim()}" is not.`);
47957
47995
  return { time: time3, azimuth, elevation, distance };
47958
47996
  });
47997
+ var imageKeyframes = (value) => value?.split(";").filter((frame) => frame.trim()).map((frame) => {
47998
+ const [seconds2, image, ...rest] = frame.split(",").map((part) => part.trim());
47999
+ if (rest.length || !image || !seconds2 || !Number.isFinite(Number(seconds2))) throw new Error(`--keyframes are seconds,image SHA pairs separated by semicolons, e.g. "0,SHA;5,SHA"; "${frame.trim()}" is not.`);
48000
+ return { image, seconds: Number(seconds2) };
48001
+ });
47959
48002
  function assetFromFlags(flags) {
47960
48003
  if (flags.type === "image" && flags.model) throw new Error("Image models are managed by staff. Omit --model.");
47961
48004
  for (const name2 of Object.values(extras).flat()) {
@@ -47978,7 +48021,8 @@ function assetFromFlags(flags) {
47978
48021
  referenceVideos: shas(flags["reference-videos"]),
47979
48022
  middleReference: flags["middle-reference"],
47980
48023
  middleSeconds: flags["middle-seconds"],
47981
- camera: keyframes(flags.camera),
48024
+ camera: cameraKeyframes(flags.camera),
48025
+ keyframes: imageKeyframes(flags.keyframes),
47982
48026
  insertAt: flags["insert-at"],
47983
48027
  resumeAt: flags["resume-at"],
47984
48028
  colorMatch: flags["color-match"],
@@ -48688,8 +48732,8 @@ async function youtubeLogin(options) {
48688
48732
  let receive;
48689
48733
  let fail;
48690
48734
  let used = false;
48691
- const callback = new Promise((resolve17, reject) => {
48692
- receive = resolve17;
48735
+ const callback = new Promise((resolve18, reject) => {
48736
+ receive = resolve18;
48693
48737
  fail = reject;
48694
48738
  });
48695
48739
  void callback.catch(() => {
@@ -48727,9 +48771,9 @@ async function youtubeLogin(options) {
48727
48771
  response.end("Authorization received. Return to your AnimaGuru terminal.");
48728
48772
  receive(code);
48729
48773
  });
48730
- await new Promise((resolve17, reject) => {
48774
+ await new Promise((resolve18, reject) => {
48731
48775
  server.once("error", reject);
48732
- server.listen(0, "127.0.0.1", resolve17);
48776
+ server.listen(0, "127.0.0.1", resolve18);
48733
48777
  });
48734
48778
  const address = server.address();
48735
48779
  if (!address || typeof address === "string") throw new Error("Cannot open the local YouTube authorization listener.");
@@ -48764,7 +48808,7 @@ async function youtubeLogin(options) {
48764
48808
  clearTimeout(timer);
48765
48809
  options.signal?.removeEventListener("abort", abort);
48766
48810
  server.closeAllConnections();
48767
- await new Promise((resolve17) => server.close(() => resolve17()));
48811
+ await new Promise((resolve18) => server.close(() => resolve18()));
48768
48812
  }
48769
48813
  });
48770
48814
  }
@@ -49868,6 +49912,114 @@ async function referenceGet(client, id2, options) {
49868
49912
  return report(options.wait ? await finished(client, analysis, options.log) : analysis, options);
49869
49913
  }
49870
49914
 
49915
+ // cli/src/curl.ts
49916
+ import { createWriteStream as createWriteStream2, openAsBlob as openAsBlob2 } from "node:fs";
49917
+ import { readFile as readFile18, unlink as unlink3 } from "node:fs/promises";
49918
+ import { extname as extname4, resolve as resolve16 } from "node:path";
49919
+ import { Readable as Readable2 } from "node:stream";
49920
+ import { pipeline as pipeline2 } from "node:stream/promises";
49921
+ function curlUrl(server, target2) {
49922
+ const origin = serverUrl(server);
49923
+ const url3 = target2.startsWith("/") && !target2.startsWith("//") ? new URL(target2, origin) : URL.canParse(target2) ? new URL(target2) : null;
49924
+ if (!url3 || url3.origin !== origin || url3.username || url3.password) throw new Error(`animaguru curl calls only ${origin}. Pass a path such as /api/v1/me.`);
49925
+ return url3;
49926
+ }
49927
+ function curlHeaders(lines = []) {
49928
+ const headers = new Headers();
49929
+ for (const line of lines) {
49930
+ const colon = line.indexOf(":");
49931
+ const name2 = colon > 0 ? line.slice(0, colon).trim() : "";
49932
+ if (!name2) throw new Error(`Write headers as "Name: value", not "${line}".`);
49933
+ if (/^(authorization|host)$/i.test(name2)) throw new Error(`Leave out the ${name2} header; animaguru curl sends the signed-in session's.`);
49934
+ headers.append(name2, line.slice(colon + 1).trim());
49935
+ }
49936
+ return headers;
49937
+ }
49938
+ var UPLOAD_TYPES = {
49939
+ ".mp4": "video/mp4",
49940
+ ".webm": "video/webm",
49941
+ ".mov": "video/quicktime",
49942
+ ".jpg": "image/jpeg",
49943
+ ".jpeg": "image/jpeg",
49944
+ ".png": "image/png",
49945
+ ".webp": "image/webp",
49946
+ ".gif": "image/gif",
49947
+ ".json": "application/json",
49948
+ ".txt": "text/plain"
49949
+ };
49950
+ var isText = (type) => /^(text\/|application\/([\w.+-]*\+)?(json|xml)\b|application\/(javascript|x-www-form-urlencoded)\b)/i.test(type);
49951
+ var readStdin = async () => {
49952
+ const chunks = [];
49953
+ for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
49954
+ return Buffer.concat(chunks);
49955
+ };
49956
+ async function bodyValue(value, io) {
49957
+ if (value === "@-") return (io.stdin ?? readStdin)();
49958
+ return value.startsWith("@") ? readFile18(resolve16(value.slice(1))) : value;
49959
+ }
49960
+ var parsesAsJson = (body) => {
49961
+ try {
49962
+ JSON.parse(body.toString());
49963
+ return true;
49964
+ } catch {
49965
+ return false;
49966
+ }
49967
+ };
49968
+ async function curl(options, io) {
49969
+ const url3 = curlUrl(options.server, options.target);
49970
+ const headers = curlHeaders(options.header);
49971
+ if ([options.data, options.json, options.uploadFile].filter((value) => value !== void 0).length > 1) throw new Error("Use one of --data, --json and --upload-file.");
49972
+ let body;
49973
+ if (options.uploadFile !== void 0) {
49974
+ const file2 = resolve16(options.uploadFile);
49975
+ body = await openAsBlob2(file2);
49976
+ if (!headers.has("content-type")) headers.set("Content-Type", UPLOAD_TYPES[extname4(file2).toLowerCase()] ?? "application/octet-stream");
49977
+ } else if (options.json !== void 0) {
49978
+ body = await bodyValue(options.json, io);
49979
+ if (!headers.has("content-type")) headers.set("Content-Type", "application/json");
49980
+ if (!headers.has("accept")) headers.set("Accept", "application/json");
49981
+ } else if (options.data !== void 0) {
49982
+ body = await bodyValue(options.data, io);
49983
+ if (!headers.has("content-type")) headers.set("Content-Type", parsesAsJson(body) ? "application/json" : "application/x-www-form-urlencoded");
49984
+ }
49985
+ headers.set("Authorization", `Bearer ${await sessionToken(url3.origin)}`);
49986
+ if (!headers.has("origin")) headers.set("Origin", url3.origin);
49987
+ const method = (options.request ?? (options.uploadFile !== void 0 ? "PUT" : body !== void 0 ? "POST" : "GET")).toUpperCase();
49988
+ const timeout = AbortSignal.timeout(options.maxTime * 1e3);
49989
+ const response = await fetch(url3, { method, headers, body, redirect: "manual", signal: io.signal ? AbortSignal.any([io.signal, timeout]) : timeout });
49990
+ if (options.include) io.stdout([`HTTP ${response.status} ${response.statusText}`.trim(), ...[...response.headers].map(([name2, value]) => `${name2}: ${value}`), ""].join("\n"));
49991
+ const type = response.headers.get("content-type") ?? "";
49992
+ const failed = options.fail && response.status >= 400;
49993
+ if (options.output !== void 0 && !failed) {
49994
+ const file2 = resolve16(options.output);
49995
+ let bytes = 0;
49996
+ try {
49997
+ await pipeline2(
49998
+ response.body ? Readable2.fromWeb(response.body) : Readable2.from([]),
49999
+ async function* (chunks) {
50000
+ for await (const chunk of chunks) {
50001
+ bytes += chunk.length;
50002
+ yield chunk;
50003
+ }
50004
+ },
50005
+ createWriteStream2(file2, { flags: "wx" })
50006
+ );
50007
+ } catch (error62) {
50008
+ if (error62.code === "EEXIST") throw new Error(`${options.output} already exists; choose another --output.`);
50009
+ await unlink3(file2).catch(() => void 0);
50010
+ throw error62;
50011
+ }
50012
+ io.stdout(JSON.stringify({ status: response.status, contentType: type || null, bytes, file: file2 }));
50013
+ } else if (!response.body || isText(type) || failed) {
50014
+ const text2 = await response.text();
50015
+ if (text2) io.stdout(text2.replace(/\n$/, ""));
50016
+ } else {
50017
+ await response.body.cancel();
50018
+ throw new Error(`The response is ${type || "binary"} (HTTP ${response.status}). Save it with -o FILE.`);
50019
+ }
50020
+ if (failed) throw new Error(`HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""} from ${method} ${url3.pathname}.`);
50021
+ }
50022
+
49871
50023
  // cli/src/commands.ts
49872
50024
  var assetModelIds = [...SPRITE_VIDEO_MODEL_IDS, ...MUSIC_MODELS.map((m) => m.id), ...SPEECH_MODELS.map((m) => m.id), ...SFX_MODELS.map((m) => m.id)];
49873
50025
  var assetAuthorizationFlags = [
@@ -49918,12 +50070,12 @@ var workspaceQuery = async (client) => {
49918
50070
  async function openBrowser(url3) {
49919
50071
  const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "rundll32" : "xdg-open";
49920
50072
  const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url3] : [url3];
49921
- await new Promise((resolve17) => {
50073
+ await new Promise((resolve18) => {
49922
50074
  const child = spawn3(command, args, { stdio: "ignore", detached: true });
49923
- child.once("error", () => resolve17());
50075
+ child.once("error", () => resolve18());
49924
50076
  child.once("spawn", () => {
49925
50077
  child.unref();
49926
- resolve17();
50078
+ resolve18();
49927
50079
  });
49928
50080
  });
49929
50081
  }
@@ -50024,7 +50176,7 @@ function createCli() {
50024
50176
  ).action(async ({ args, flags, stdout, signal }) => out(stdout, await changeVideoSubtitles(new Client(flags.url, signal), args.id, flags))))).add(defineCommand("delete", { description: "Remove a video and its hosted files" }, (command) => command.args({ name: "id", type: "string", required: true }).flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" } }, { name: "yes", type: "boolean", required: true }).action(async ({ args, flags, stdout, signal }) => {
50025
50177
  if (!flags.yes) throw new Error("Pass --yes to delete the video.");
50026
50178
  out(stdout, await new Client(flags.url, signal).request(`/api/v1/videos/${encodeURIComponent(args.id)}`, { method: "DELETE" }));
50027
- })))).command("cloud", (cloud) => cloud.add(defineCommand("list", { description: "List projects with synced animations in the selected workspace, with plan and storage (Individual Pro and Team)" }, (command) => command.flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" } }).action(async ({ flags, stdout, signal }) => {
50179
+ })))).command("cloud", (cloud) => cloud.add(defineCommand("list", { description: "List projects with synced animations in the selected workspace, with plan and storage (Individual Starter, Individual Pro and Team)" }, (command) => command.flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" } }).action(async ({ flags, stdout, signal }) => {
50028
50180
  const client = new Client(flags.url, signal);
50029
50181
  out(stdout, await client.request(`/api/v1/cloud${await workspaceQuery(client)}`));
50030
50182
  }))).add(defineCommand("link", { description: "Sync this video workspace with a project's animations: --project for an existing project, or a new project in the selected workspace" }, (command) => command.flags(
@@ -50042,7 +50194,31 @@ function createCli() {
50042
50194
  }))).add(defineCommand("restore", { description: "Make the folder, or --path parts of it, match an earlier version; push afterwards to save it as the newest" }, (command) => command.args({ name: "version", type: "string", required: true, description: "Version ID or its first characters" }).flags({ name: "dir", type: "string", default: "." }, { name: "path", type: "string", multiple: true, description: "File or folder to restore; repeatable" }, { name: "yes", type: "boolean", description: "Replace unsaved local changes (copies are kept)" }).action(async ({ args, flags, stdout, stderr, signal }) => out(stdout, await restoreCloud({ dir: flags.dir, version: args.version, paths: flags.path, yes: flags.yes, log: stderr, signal }))))).add(defineCommand("clone", { description: "Download the newest version of a project's animations into a new folder" }, (command) => command.args({ name: "id", type: "string", required: true, description: "Project ID" }, { name: "directory", type: "string", description: "New folder; defaults to the project name" }).flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" } }).action(async ({ args, flags, stdout, stderr, signal }) => out(stdout, await cloneCloud(new Client(flags.url, signal), { id: args.id, dir: args.directory, log: stderr, signal }))))).add(defineCommand("unlink", { description: "Stop syncing this folder; the project's animations and versions stay" }, (command) => command.flags({ name: "dir", type: "string", default: "." }).action(async ({ flags, stdout }) => out(stdout, await unlinkCloud({ dir: flags.dir }))))).add(defineCommand("delete", { description: "Delete a project's animations with all their versions, drafts and stored files; the project and its assets stay. Requires --yes" }, (command) => command.args({ name: "id", type: "string", required: true, description: "Project ID" }).flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" } }, { name: "yes", type: "boolean", required: true }).action(async ({ args, flags, stdout, signal }) => {
50043
50195
  if (!flags.yes) throw new Error("Pass --yes to delete the project's animations and every saved version.");
50044
50196
  out(stdout, await new Client(flags.url, signal).request(`/api/v1/cloud/${encodeURIComponent(args.id)}`, { method: "DELETE" }));
50045
- })))).command("auth", (auth) => auth.add(defineCommand("login", { description: "Approve a browser sign-in and save a seven-day CLI session" }, (command) => command.flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" }, description: "Server origin" }).flags({ name: "browser", type: "boolean", default: true, description: "Open the browser; --no-browser prints the URL only" }).action(async ({ flags, stdout, stderr, signal }) => {
50197
+ })))).add(defineCommand("curl", { description: "Call an AnimaGuru API path as the signed-in user with curl's flags; the server and the session's token are added for you. Prints the response body, or saves it with -o" }, (command) => command.args({ name: "path", type: "string", required: true, description: "API path such as /api/v1/me (with any query string), or a full URL on the server" }).flags(
50198
+ { name: "request", short: "X", type: "string", description: "HTTP method; GET by default, POST with --data or --json, PUT with --upload-file" },
50199
+ { name: "header", short: "H", type: "string", multiple: true, description: '"Name: value" request header; repeatable. Authorization is added for you' },
50200
+ { name: "data", short: "d", type: "string", aliases: ["data-binary"], description: "Request body: text, @FILE or @- for standard input. JSON is sent as application/json" },
50201
+ { name: "json", type: "string", description: "JSON body (text, @FILE or @-) with JSON Content-Type and Accept headers" },
50202
+ { name: "upload-file", short: "T", type: "string", description: "Stream a file as the body (PUT unless -X); Content-Type follows its extension unless -H sets one" },
50203
+ { name: "output", short: "o", type: "string", description: "Save the response body to a new file and print a JSON summary; needed for video and images" },
50204
+ { name: "include", short: "i", type: "boolean", noNegate: true, description: "Print the status line and response headers first" },
50205
+ { name: "fail", short: "f", type: "boolean", default: true, description: "Exit non-zero on HTTP 4xx and 5xx after printing the body; --no-fail exits 0 like plain curl" },
50206
+ { name: "silent", short: "s", type: "boolean", noNegate: true, description: "Accepted for curl compatibility; nothing else is printed anyway" },
50207
+ { name: "max-time", short: "m", type: "number", default: 600, description: "Maximum seconds for the whole request" },
50208
+ { name: "server", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" }, description: "Server origin" }
50209
+ ).action(async ({ args, flags, stdout, signal }) => curl({
50210
+ server: flags.server,
50211
+ target: args.path,
50212
+ request: flags.request,
50213
+ header: flags.header,
50214
+ data: flags.data,
50215
+ json: flags.json,
50216
+ uploadFile: flags["upload-file"],
50217
+ output: flags.output,
50218
+ include: flags.include,
50219
+ fail: flags.fail,
50220
+ maxTime: flags["max-time"]
50221
+ }, { stdout, signal })))).command("auth", (auth) => auth.add(defineCommand("login", { description: "Approve a browser sign-in and save a seven-day CLI session" }, (command) => command.flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" }, description: "Server origin" }).flags({ name: "browser", type: "boolean", default: true, description: "Open the browser; --no-browser prints the URL only" }).action(async ({ flags, stdout, stderr, signal }) => {
50046
50222
  const client = new Client(flags.url, signal);
50047
50223
  const device = await client.request("/api/auth/device/code", { method: "POST", authenticated: false, body: { client_id: CLI_CLIENT_ID } });
50048
50224
  stderr(`Open ${device.verification_uri_complete}
@@ -50164,11 +50340,11 @@ Confirm code: ${device.user_code}`);
50164
50340
  { name: "name", type: "string", required: true },
50165
50341
  { name: "prompt", type: "string", description: "Description to generate, or verbatim transcript for speech; video lip-sync and relight take none, and camera-controls, insert-video, recast and 3d-to-video may leave it out" },
50166
50342
  { name: "model", type: "string", choices: assetModelIds, description: "Model for the chosen asset type" },
50167
- { name: "aspect-ratio", type: "string", choices: [.../* @__PURE__ */ new Set([...IMAGE_ASPECT_RATIOS, "auto", ...VIDEO_ASPECT_RATIOS])], description: "Image: 1:1 (default), 3:2, 2:3, 16:9 or 9:16; a masked edit keeps Image 1's shape. Video from text or a style without --reference: 21:9\u20139:16 (default 16:9). Extension, reference-to-video and relight: auto (default, the inputs' shape) or a ratio to crop to" },
50343
+ { name: "aspect-ratio", type: "string", choices: [.../* @__PURE__ */ new Set([...IMAGE_ASPECT_RATIOS, "auto", ...VIDEO_ASPECT_RATIOS])], description: "Image: 1:1 (default), 3:2, 2:3, 16:9 or 9:16; a masked edit keeps Image 1's shape. Video from text or a style without --reference: 21:9\u20139:16 (default 16:9). Extension, reference-to-video, relight and keyframes-to-video: auto (default, the inputs' shape) or a ratio to crop to" },
50168
50344
  { name: "transparent", type: "boolean", description: "Image: transparent background" },
50169
50345
  { name: "references", type: "string", description: "Comma-separated blob SHAs of this project's images or sprite art, named Image 1, Image 2\u2026 in the prompt. Image: up to 4 to follow, such as a character sheet. Video: subjects or looks to keep (reference-to-video and insert-video up to 9, 3d-to-video up to 8), or recast's new people, one photo each (up to 4)" },
50170
50346
  { name: "mask", type: "string", description: "Image: edit only these rectangles of Image 1 (the first --references image), as x,y,width,height fractions of its size from the top left, up to 8 separated by semicolons; the rest is kept" },
50171
- { name: "workflow", type: "string", choices: VIDEO_WORKFLOWS, description: "Video: image-to-video (default), text-to-video or extend-video; H3 Max also reference-to-video, lip-sync, camera-controls, insert-video, recast, relight, 3d-to-video and style, as the model's videoProfiles allow" },
50347
+ { name: "workflow", type: "string", choices: VIDEO_WORKFLOWS, description: "Video: image-to-video (default), text-to-video or extend-video; H3 Max also reference-to-video, lip-sync, camera-controls, insert-video, recast, relight, 3d-to-video and style, FLUX 3 Max keyframes-to-video, as the model's videoProfiles allow" },
50172
50348
  { name: "reference", type: "string", description: "Video: starting image blob SHA from this project's assets or sprites; the speaking face for lip-sync, the image the camera moves around for camera-controls" },
50173
50349
  { name: "end-reference", type: "string", description: "Video: ending image blob SHA; models with endFrameOnly accept it without --reference" },
50174
50350
  { name: "middle-reference", type: "string", description: "Reference-to-video: image blob SHA reached at --middle-seconds; needs --reference and --end-reference, 480p or 768p" },
@@ -50178,6 +50354,7 @@ Confirm code: ${device.user_code}`);
50178
50354
  { name: "audio-references", type: "string", description: "Extension or reference-to-video: up to 3 comma-separated audio asset blob SHAs (2\u201315 s each, 15 s total) guiding the sound; refer to them as Audio 1, Audio 2\u2026 in the prompt" },
50179
50355
  { name: "reference-videos", type: "string", description: "Reference-to-video or insert-video: up to 3 comma-separated video asset blob SHAs (2\u201315 s each, 15 s total) guiding motion, named Video 1, Video 2\u2026 in the prompt" },
50180
50356
  { name: "camera", type: "string", description: 'Camera-controls: 2\u201312 keyframes time,azimuth,elevation,distance (time 0\u20131, angles in degrees, elevation \u221290\u201390) separated by semicolons, e.g. "0,0,0,1;1,90,15,1.2"' },
50357
+ { name: "keyframes", type: "string", description: 'Keyframes-to-video (FLUX 3 Max): 1\u201310 seconds,image-SHA pairs separated by semicolons, e.g. "0,SHA;2.5,SHA;5,SHA"; each image is reached at its second, on a frame of its own of the 24 fps clip, within --seconds' },
50181
50358
  { name: "insert-at", type: "number", description: "Insert-video: seconds into --source where the new scene starts (1.625\u201360)" },
50182
50359
  { name: "resume-at", type: "number", description: "Insert-video: seconds into --source where it resumes after the scene" },
50183
50360
  { name: "color-match", type: "boolean", description: "Insert-video: match the scene's colors to the source (default on; --no-color-match turns it off)" },
@@ -50188,10 +50365,10 @@ Confirm code: ${device.user_code}`);
50188
50365
  { name: "generated-references", type: "number", description: "3d-to-video without --references: how many reference images it may draw, 1\u20138 (default 2)" },
50189
50366
  { name: "style", type: "string", choices: VIDEO_STYLES, description: "Style workflow: the preset look, at 768p for 5\u201315 whole seconds" },
50190
50367
  { name: "damage", type: "string", choices: VHS_DAMAGE_LEVELS, description: "VHS style: tape damage (fal's default medium)" },
50191
- { name: "seconds", type: "number", description: "Video or SFX duration in seconds (default 5); H3 takes fractions (6.5); extensions add this many seconds, insert-video's scene is 5\u201313; lip-sync, recast, relight and 3d-to-video take their input's length; SFX supports 0.5\u201330" },
50368
+ { name: "seconds", type: "number", description: "Video or SFX duration in seconds (default 5); H3 takes fractions (6.5), FLUX 3 Max 5\u201320 whole seconds; extensions add this many seconds, insert-video's scene is 5\u201313; lip-sync, recast, relight and 3d-to-video take their input's length; SFX supports 0.5\u201330" },
50192
50369
  { name: "resolution", type: "string", choices: VIDEO_RESOLUTIONS, description: "Video resolution supported by the selected model and workflow (default its first)" },
50193
50370
  { name: "mode", type: "string", choices: ["cost", "speed", "quality"], description: "Video generation mode supported by the selected model (default its first)" },
50194
- { name: "prompt-expansion", type: "string", choices: PROMPT_EXPANSION_MODES, description: "Video: how much the model may rewrite the prompt (default disabled); extensions treat any other value as on" },
50371
+ { name: "prompt-expansion", type: "string", choices: PROMPT_EXPANSION_MODES, description: "Video: how much the model may rewrite the prompt (default disabled); extensions treat any other value as on. FLUX 3 Max always reasons about the prompt: quality thoroughly, any other value lightly" },
50195
50372
  { name: "seed", type: "number", description: "Video: seed for repeatable results, where the model takes one" },
50196
50373
  { name: "extend-output", type: "string", choices: EXTEND_OUTPUTS, description: "Extension: extended (default, source plus new footage) or continuation (a 1.625 s overlap, then the new footage)" },
50197
50374
  { name: "voice", type: "string", description: "Gemini voice name or ElevenLabs voice ID (defaults: Kore / George)" },
@@ -50243,7 +50420,7 @@ Confirm code: ${device.user_code}`);
50243
50420
  out(stdout, await new Client(flags.url, signal).request(flags.look ? `${avatarsPath(args.id)}/looks/${encodeURIComponent(flags.look)}` : `${avatarsPath(args.id)}/${encodeURIComponent(flags.group)}`, { method: "DELETE" }));
50244
50421
  })))).command("jobs", (jobs) => jobs.add(defineCommand("list", { description: "List the latest generation jobs" }, (command) => command.flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" }, description: "Server origin" }).args(id).action(async ({ args, flags, stdout, signal }) => out(stdout, await new Client(flags.url, signal).request(`${projectPath(args.id)}/jobs`))))).add(defineCommand("submit", { description: "Submit a typed JSON job: character, animate, custom or redraw; spends credits" }, (command) => command.flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" }, description: "Server origin" }).args(id).flags(...modelFlags).flags({ name: "file", type: "string", required: true, description: "JSON job file" }, { name: "yes", type: "boolean", required: true, description: "Authorize spending credits" }).action(async ({ args, flags, stdout, signal }) => {
50245
50422
  if (!flags.yes) throw new Error("Pass --yes to authorize spending credits.");
50246
- const body = spriteJobBody.parse({ ...JSON.parse(await readFile18(flags.file, "utf8")), ...selectedModels(flags) });
50423
+ const body = spriteJobBody.parse({ ...JSON.parse(await readFile19(flags.file, "utf8")), ...selectedModels(flags) });
50247
50424
  out(stdout, await new Client(flags.url, signal).request(`${projectPath(args.id)}/jobs`, { method: "POST", body }));
50248
50425
  }))).add(defineCommand("wait", { description: "Wait for a specific job; fail if generation fails or times out" }, (command) => command.flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" }, description: "Server origin" }).args(id, { name: "job", type: "string", required: true }).flags({ name: "timeout", type: "number", default: 600, description: "Maximum seconds to wait" }).action(async ({ args, flags, stdout, signal }) => out(stdout, { job: await waitForJob(new Client(flags.url, signal), args.id, args.job, flags.timeout, signal) }))))).add(defineCommand("models", { description: "Set the default video model for future project jobs" }, (command) => command.flags({ name: "url", type: "string", default: "https://animaguru.com", env: { name: "ANIMAGURU_URL" }, description: "Server origin" }).args(id).flags(...modelFlags).action(async ({ args, flags, stdout, signal }) => {
50249
50426
  const body = selectedModels(flags);
@@ -50258,7 +50435,7 @@ Confirm code: ${device.user_code}`);
50258
50435
  const extension = flags.file.split(".").at(-1)?.toLowerCase();
50259
50436
  const type = extension === "png" ? "image/png" : extension === "jpg" || extension === "jpeg" ? "image/jpeg" : extension === "webp" ? "image/webp" : void 0;
50260
50437
  if (!type) throw new Error("Use a PNG, JPEG or WebP image.");
50261
- const bytes = await readFile18(flags.file);
50438
+ const bytes = await readFile19(flags.file);
50262
50439
  if (bytes.length > 10 * 1024 * 1024) throw new Error("Image must be at most 10 MB.");
50263
50440
  const form = new FormData();
50264
50441
  form.set("image", new Blob([bytes], { type }), `reference.${extension}`);
@@ -50274,7 +50451,7 @@ Confirm code: ${device.user_code}`);
50274
50451
  if (!file2) throw new Error(`No file at ${args.path} in this project. List them with animaguru files list.`);
50275
50452
  if (!/^[a-f0-9]{64}$/.test(file2.sha)) throw new Error("Invalid blob hash in manifest.");
50276
50453
  const response = await client.request(`/api/v1/blobs/${encodeURIComponent(manifest.ownerId)}/${file2.sha}`, { raw: true });
50277
- const path = resolve16(flags.output);
50454
+ const path = resolve17(flags.output);
50278
50455
  await mkdir15(dirname14(path), { recursive: true });
50279
50456
  const bytes = new Uint8Array(await response.arrayBuffer());
50280
50457
  await writeFile16(path, bytes, { flag: "wx" });
@@ -50289,7 +50466,7 @@ Confirm code: ${device.user_code}`);
50289
50466
  const response = await client.request(`/api/v1/blobs/${encodeURIComponent(manifest.ownerId)}/${file2.sha}`, { raw: true });
50290
50467
  entries[path2] = new Uint8Array(await response.arrayBuffer());
50291
50468
  }
50292
- const path = resolve16(flags.output);
50469
+ const path = resolve17(flags.output);
50293
50470
  await writeFile16(path, zipSync(entries, { level: 0 }), { flag: "wx" });
50294
50471
  out(stdout, { path, files: manifest.files.length });
50295
50472
  })))).extend(version(package_default.version, { format: "plain" })).extend(help()).extend(completion());