@vibeframe/cli 0.57.2 → 0.58.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.
- package/dist/index.js +517 -48
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -25210,6 +25210,7 @@ var init_schema = __esm({
|
|
|
25210
25210
|
elevenlabs: "ELEVENLABS_API_KEY",
|
|
25211
25211
|
runway: "RUNWAY_API_SECRET",
|
|
25212
25212
|
kling: "KLING_API_KEY",
|
|
25213
|
+
fal: "FAL_KEY",
|
|
25213
25214
|
imgbb: "IMGBB_API_KEY",
|
|
25214
25215
|
replicate: "REPLICATE_API_TOKEN",
|
|
25215
25216
|
xai: "XAI_API_KEY",
|
|
@@ -25610,6 +25611,70 @@ function buildEmptyRootHtml(opts) {
|
|
|
25610
25611
|
</html>
|
|
25611
25612
|
`;
|
|
25612
25613
|
}
|
|
25614
|
+
function buildDesignMd(opts) {
|
|
25615
|
+
const { name, style } = opts;
|
|
25616
|
+
const intro = style ? `Visual identity for **${name}**, scaffolded from the **${style.name}** style (after ${style.designer}). Customise freely \u2014 this file is the single source of truth for every scene's palette, typography, and motion.` : `Visual identity for **${name}**. Fill the sections below before authoring any scene HTML or generating any backdrop. Pick a named style with \`vibe scene styles\` if you want a credible starting point.`;
|
|
25617
|
+
const moodLine = style ? `**Mood:** ${style.mood} \xB7 **Best for:** ${style.bestFor}` : `**Mood:** _(one line \u2014 what should the viewer FEEL?)_`;
|
|
25618
|
+
const palette = style ? `${style.palette.map((c) => `- \`${c}\``).join("\n")}
|
|
25619
|
+
|
|
25620
|
+
${style.paletteNotes}` : `- _hex_ \u2014 primary
|
|
25621
|
+
- _hex_ \u2014 accent
|
|
25622
|
+
|
|
25623
|
+
_2\u20133 colours max. Declare explicit hex values; never name colours abstractly._`;
|
|
25624
|
+
const typography = style ? style.typography : `_One family, two weights. State the role of each (headline / label / body)._`;
|
|
25625
|
+
const composition = style ? style.composition : `_Grid? Centered? Layered? How does negative space behave?_`;
|
|
25626
|
+
const motion = style ? `${style.motion}
|
|
25627
|
+
|
|
25628
|
+
**GSAP signature:** ${style.gsapSignature}` : `_How fast? Snappy or fluid? Overshoot or precision?_
|
|
25629
|
+
|
|
25630
|
+
**GSAP signature:** _e.g. \`expo.out\`, \`sine.inOut\`, \`back.out(1.8)\`_`;
|
|
25631
|
+
const transition = style ? style.transition : `_Which Hyperframes shader matches the energy? (Cinematic Zoom, Cross-Warp Morph, Glitch, Domain Warp, \u2026)_`;
|
|
25632
|
+
const avoid = style ? style.avoid.map((a) => `- ${a}`).join("\n") : `- _anti-pattern 1_
|
|
25633
|
+
- _anti-pattern 2_
|
|
25634
|
+
- _anti-pattern 3_`;
|
|
25635
|
+
return `# ${name} \u2014 Design
|
|
25636
|
+
|
|
25637
|
+
> **Hard-gate.** This file defines the visual identity of every scene.
|
|
25638
|
+
> Author it before generating any HTML, backdrop image, or motion.
|
|
25639
|
+
> The Hyperframes \`hyperframes\` skill enforces this: scenes that
|
|
25640
|
+
> contradict DESIGN.md are rejected.
|
|
25641
|
+
|
|
25642
|
+
${intro}
|
|
25643
|
+
|
|
25644
|
+
## Style
|
|
25645
|
+
|
|
25646
|
+
${moodLine}
|
|
25647
|
+
|
|
25648
|
+
## Palette
|
|
25649
|
+
|
|
25650
|
+
${palette}
|
|
25651
|
+
|
|
25652
|
+
## Typography
|
|
25653
|
+
|
|
25654
|
+
${typography}
|
|
25655
|
+
|
|
25656
|
+
## Composition
|
|
25657
|
+
|
|
25658
|
+
${composition}
|
|
25659
|
+
|
|
25660
|
+
## Motion
|
|
25661
|
+
|
|
25662
|
+
${motion}
|
|
25663
|
+
|
|
25664
|
+
## Transition
|
|
25665
|
+
|
|
25666
|
+
${transition}
|
|
25667
|
+
|
|
25668
|
+
## What NOT to do
|
|
25669
|
+
|
|
25670
|
+
${avoid}
|
|
25671
|
+
|
|
25672
|
+
---
|
|
25673
|
+
|
|
25674
|
+
_Browse other named styles: \`vibe scene styles\`_
|
|
25675
|
+
${style ? `_This file was seeded by \`vibe scene init --visual-style "${style.name}"\`._` : `_Seed this file from a named style: \`vibe scene init <dir> --visual-style "<name>"\`._`}
|
|
25676
|
+
`;
|
|
25677
|
+
}
|
|
25613
25678
|
function buildProjectClaudeMd(name) {
|
|
25614
25679
|
return `# ${name} \u2014 Scene Authoring Project
|
|
25615
25680
|
|
|
@@ -25617,6 +25682,16 @@ This project is **bilingual**: it works with both VibeFrame (\`vibe\`) and
|
|
|
25617
25682
|
HeyGen Hyperframes (\`hyperframes\`). You can run either CLI inside this
|
|
25618
25683
|
directory.
|
|
25619
25684
|
|
|
25685
|
+
## Visual identity hard-gate
|
|
25686
|
+
|
|
25687
|
+
**Author \`DESIGN.md\` before any scene HTML.** It defines palette,
|
|
25688
|
+
typography, motion, and transition rules. Both the agent-driven path and
|
|
25689
|
+
the fallback emit reference it; scenes that contradict DESIGN.md are
|
|
25690
|
+
rejected by the Hyperframes \`hyperframes\` skill.
|
|
25691
|
+
|
|
25692
|
+
Browse named styles: \`vibe scene styles\`. Re-seed from one with
|
|
25693
|
+
\`vibe scene init . --visual-style "Swiss Pulse"\` (idempotent).
|
|
25694
|
+
|
|
25620
25695
|
## Skills \u2014 USE THESE FIRST
|
|
25621
25696
|
|
|
25622
25697
|
**Always invoke the relevant skill before authoring scenes.** Skills encode
|
|
@@ -25625,14 +25700,23 @@ semantics, VibeFrame pipeline conventions) that are NOT in generic web docs.
|
|
|
25625
25700
|
|
|
25626
25701
|
| Skill | Command | When to use |
|
|
25627
25702
|
| ----------------- | ---------------- | ------------------------------------------------------------------------------------- |
|
|
25628
|
-
| **
|
|
25629
|
-
| **
|
|
25703
|
+
| **hyperframes** | \`/hyperframes\` | Cinematic-quality composition \u2014 DESIGN.md hard-gate, named styles, motion principles |
|
|
25704
|
+
| **vibe-scene** | \`/vibe-scene\` | VibeFrame's authoring loop, AI assets, lint feedback, pipeline integration |
|
|
25630
25705
|
| **gsap** | \`/gsap\` | GSAP tweens, timelines, easing |
|
|
25631
25706
|
|
|
25632
|
-
|
|
25707
|
+
Install the Hyperframes skills once per machine:
|
|
25708
|
+
|
|
25709
|
+
\`\`\`bash
|
|
25710
|
+
npx skills add heygen-com/hyperframes
|
|
25711
|
+
\`\`\`
|
|
25712
|
+
|
|
25713
|
+
Restart your agent session (or reload the skill list) after installing.
|
|
25714
|
+
If skills aren't available, follow the **Key Rules** below \u2014 they cover
|
|
25715
|
+
the framework-level minimum, not the cinematic craft layer.
|
|
25633
25716
|
|
|
25634
25717
|
## Project structure
|
|
25635
25718
|
|
|
25719
|
+
- \`DESIGN.md\` \u2014 visual identity contract (palette, type, motion, transitions)
|
|
25636
25720
|
- \`index.html\` \u2014 root composition (timeline)
|
|
25637
25721
|
- \`compositions/scene-*.html\` \u2014 per-scene HTML authored by you or the agent
|
|
25638
25722
|
- \`assets/\` \u2014 shared media (narration audio, images, video)
|
|
@@ -25750,6 +25834,17 @@ async function scaffoldSceneProject(opts) {
|
|
|
25750
25834
|
await writeFile4(claudePath, buildProjectClaudeMd(name), "utf-8");
|
|
25751
25835
|
created.push(claudePath);
|
|
25752
25836
|
}
|
|
25837
|
+
const designPath = resolve5(dir, "DESIGN.md");
|
|
25838
|
+
if (await pathExists(designPath)) {
|
|
25839
|
+
skipped.push(designPath);
|
|
25840
|
+
} else {
|
|
25841
|
+
await writeFile4(
|
|
25842
|
+
designPath,
|
|
25843
|
+
buildDesignMd({ name, style: opts.visualStyle }),
|
|
25844
|
+
"utf-8"
|
|
25845
|
+
);
|
|
25846
|
+
created.push(designPath);
|
|
25847
|
+
}
|
|
25753
25848
|
const gitignorePath = resolve5(dir, ".gitignore");
|
|
25754
25849
|
if (await pathExists(gitignorePath)) {
|
|
25755
25850
|
skipped.push(gitignorePath);
|
|
@@ -25773,6 +25868,180 @@ var init_scene_project = __esm({
|
|
|
25773
25868
|
}
|
|
25774
25869
|
});
|
|
25775
25870
|
|
|
25871
|
+
// src/commands/_shared/visual-styles.ts
|
|
25872
|
+
function listVisualStyles() {
|
|
25873
|
+
return STYLES;
|
|
25874
|
+
}
|
|
25875
|
+
function getVisualStyle(query) {
|
|
25876
|
+
const q = query.trim().toLowerCase();
|
|
25877
|
+
return STYLES.find(
|
|
25878
|
+
(s) => s.name.toLowerCase() === q || s.slug === q
|
|
25879
|
+
);
|
|
25880
|
+
}
|
|
25881
|
+
function visualStyleNames() {
|
|
25882
|
+
return STYLES.map((s) => `"${s.name}"`).join(", ");
|
|
25883
|
+
}
|
|
25884
|
+
var STYLES;
|
|
25885
|
+
var init_visual_styles = __esm({
|
|
25886
|
+
"src/commands/_shared/visual-styles.ts"() {
|
|
25887
|
+
"use strict";
|
|
25888
|
+
STYLES = [
|
|
25889
|
+
{
|
|
25890
|
+
name: "Swiss Pulse",
|
|
25891
|
+
slug: "swiss-pulse",
|
|
25892
|
+
designer: "Josef M\xFCller-Brockmann",
|
|
25893
|
+
mood: "Clinical, precise",
|
|
25894
|
+
bestFor: "SaaS dashboards, developer tools, APIs, metrics",
|
|
25895
|
+
palette: ["#1a1a1a", "#ffffff", "#0066FF"],
|
|
25896
|
+
paletteNotes: "Black, white, ONE accent \u2014 electric blue (#0066FF) or amber (#FFB300). Never both accents at once.",
|
|
25897
|
+
typography: "Helvetica or Inter Bold for headlines, Regular for labels. Numbers dominate at 80\u2013120px.",
|
|
25898
|
+
composition: "Grid-locked. Every element snaps to an invisible 12-column grid. Hard cuts only \u2014 no decorative transitions.",
|
|
25899
|
+
motion: "Animated counters count up from 0. Entries are fast and snap into place. Nothing floats.",
|
|
25900
|
+
transition: "Cinematic Zoom or SDF Iris (precise, geometric)",
|
|
25901
|
+
gsapSignature: "expo.out, power4.out \u2014 fast arrivals, hard stops",
|
|
25902
|
+
avoid: [
|
|
25903
|
+
"Decorative transitions (fades, dissolves) \u2014 use hard cuts",
|
|
25904
|
+
"Two accent colours competing in one frame",
|
|
25905
|
+
"Off-grid placement or floating elements"
|
|
25906
|
+
]
|
|
25907
|
+
},
|
|
25908
|
+
{
|
|
25909
|
+
name: "Velvet Standard",
|
|
25910
|
+
slug: "velvet-standard",
|
|
25911
|
+
designer: "Massimo Vignelli",
|
|
25912
|
+
mood: "Premium, timeless",
|
|
25913
|
+
bestFor: "Luxury products, enterprise software, keynotes, investor decks",
|
|
25914
|
+
palette: ["#000000", "#ffffff", "#1a237e"],
|
|
25915
|
+
paletteNotes: "Black, white, ONE rich accent \u2014 deep navy (#1a237e) or gold (#c9a84c).",
|
|
25916
|
+
typography: "Thin sans-serif, ALL CAPS, wide letter-spacing (0.15em+). Sequential reveals only.",
|
|
25917
|
+
composition: "Generous negative space. Symmetrical, centered, architectural precision. Nothing busy.",
|
|
25918
|
+
motion: "Slow, deliberate. Sequential reveals with long holds. No frantic motion.",
|
|
25919
|
+
transition: "Cross-Warp Morph (elegant, organic flow between scenes)",
|
|
25920
|
+
gsapSignature: "sine.inOut, power1 \u2014 nothing snaps, everything glides",
|
|
25921
|
+
avoid: [
|
|
25922
|
+
"Tight letter-spacing \u2014 kills the premium register",
|
|
25923
|
+
"Bouncy or elastic easings \u2014 too playful",
|
|
25924
|
+
"Multiple elements arriving at once \u2014 break sequence"
|
|
25925
|
+
]
|
|
25926
|
+
},
|
|
25927
|
+
{
|
|
25928
|
+
name: "Deconstructed",
|
|
25929
|
+
slug: "deconstructed",
|
|
25930
|
+
designer: "Neville Brody",
|
|
25931
|
+
mood: "Industrial, raw",
|
|
25932
|
+
bestFor: "Tech news, developer launches, security products, punk-energy reveals",
|
|
25933
|
+
palette: ["#1a1a1a", "#D4501E", "#f0f0f0"],
|
|
25934
|
+
paletteNotes: "Dark grey (#1a1a1a), rust orange (#D4501E), raw white (#f0f0f0).",
|
|
25935
|
+
typography: "Type at angles, overlapping edges, escaping frames. Bold industrial weight.",
|
|
25936
|
+
composition: "Gritty textures \u2014 scan-line effects, glitch artifacts baked into the design.",
|
|
25937
|
+
motion: "Text SLAMS and SHATTERS. Letters scramble then snap to final position.",
|
|
25938
|
+
transition: "Glitch shader or Whip Pan (breaks the rules, feels aggressive)",
|
|
25939
|
+
gsapSignature: "back.out(2.5), steps(8), elastic.out(1.2, 0.4) \u2014 intentional irregularity",
|
|
25940
|
+
avoid: [
|
|
25941
|
+
"Polished, centered compositions \u2014 must feel raw",
|
|
25942
|
+
"Smooth fades \u2014 use glitch / scramble entries",
|
|
25943
|
+
"Soft easings \u2014 every motion lands hard"
|
|
25944
|
+
]
|
|
25945
|
+
},
|
|
25946
|
+
{
|
|
25947
|
+
name: "Maximalist Type",
|
|
25948
|
+
slug: "maximalist-type",
|
|
25949
|
+
designer: "Paula Scher",
|
|
25950
|
+
mood: "Loud, kinetic",
|
|
25951
|
+
bestFor: "Big product launches, milestone announcements, high-energy hype videos",
|
|
25952
|
+
palette: ["#E63946", "#FFD60A", "#000000", "#ffffff"],
|
|
25953
|
+
paletteNotes: "Bold saturated: red (#E63946), yellow (#FFD60A), black, white \u2014 maximum contrast.",
|
|
25954
|
+
typography: "Text IS the visual. Overlapping type layers at different scales and angles, filling 50\u201380% of frame.",
|
|
25955
|
+
composition: "Text layered OVER footage \u2014 never empty backgrounds. 2\u20133 second rapid-fire scenes.",
|
|
25956
|
+
motion: "Everything is kinetic \u2014 slamming, sliding, scaling. No static moments.",
|
|
25957
|
+
transition: "Ridged Burn (explosive, dramatic, impossible to ignore)",
|
|
25958
|
+
gsapSignature: "expo.out, back.out(1.8) \u2014 fast arrivals, hard stops",
|
|
25959
|
+
avoid: [
|
|
25960
|
+
"Static moments \u2014 every frame must move",
|
|
25961
|
+
"Empty backgrounds \u2014 text and footage must layer",
|
|
25962
|
+
"Single typeface at a single size \u2014 must be layered"
|
|
25963
|
+
]
|
|
25964
|
+
},
|
|
25965
|
+
{
|
|
25966
|
+
name: "Data Drift",
|
|
25967
|
+
slug: "data-drift",
|
|
25968
|
+
designer: "Refik Anadol",
|
|
25969
|
+
mood: "Futuristic, immersive",
|
|
25970
|
+
bestFor: "AI products, ML platforms, data companies, speculative tech",
|
|
25971
|
+
palette: ["#0a0a0a", "#7c3aed", "#06b6d4"],
|
|
25972
|
+
paletteNotes: "Iridescent \u2014 deep black (#0a0a0a), electric purple (#7c3aed), cyan (#06b6d4).",
|
|
25973
|
+
typography: "Thin futuristic sans-serif \u2014 floating, weightless, minimal text.",
|
|
25974
|
+
composition: "Fluid morphing compositions. Extreme scale shifts (micro \u2192 macro). Particles coalesce into numbers.",
|
|
25975
|
+
motion: "Light traces data paths through the frame. Smooth, continuous, organic \u2014 nothing hard.",
|
|
25976
|
+
transition: "Gravitational Lens or Domain Warp (otherworldly distortion)",
|
|
25977
|
+
gsapSignature: "sine.inOut, power2.out \u2014 smooth, continuous, organic",
|
|
25978
|
+
avoid: [
|
|
25979
|
+
"Hard cuts \u2014 break the immersion",
|
|
25980
|
+
"Heavy/bold typography \u2014 too grounded",
|
|
25981
|
+
"Static compositions \u2014 must feel like flow"
|
|
25982
|
+
]
|
|
25983
|
+
},
|
|
25984
|
+
{
|
|
25985
|
+
name: "Soft Signal",
|
|
25986
|
+
slug: "soft-signal",
|
|
25987
|
+
designer: "Stefan Sagmeister",
|
|
25988
|
+
mood: "Intimate, warm",
|
|
25989
|
+
bestFor: "Wellness brands, personal stories, lifestyle products, human-centered apps",
|
|
25990
|
+
palette: ["#F5A623", "#FFF8EC", "#C4A3A3", "#8FAF8C"],
|
|
25991
|
+
paletteNotes: "Warm amber (#F5A623), cream (#FFF8EC), dusty rose (#C4A3A3), sage green (#8FAF8C).",
|
|
25992
|
+
typography: "Handwritten-style or humanist serif fonts. Personal, lowercase, delicate.",
|
|
25993
|
+
composition: "Close-up framing feel \u2014 single element fills the frame. Nothing feels corporate.",
|
|
25994
|
+
motion: "Slow drifts and floats, never snaps. Soft organic motion throughout.",
|
|
25995
|
+
transition: "Thermal Distortion (warm, flowing, like heat shimmer)",
|
|
25996
|
+
gsapSignature: "sine.inOut, power1.inOut \u2014 everything breathes",
|
|
25997
|
+
avoid: [
|
|
25998
|
+
"Sharp geometric layouts \u2014 break the warmth",
|
|
25999
|
+
"Hard easings or snaps \u2014 too clinical",
|
|
26000
|
+
"Cool tones (blue/green-blue) without warm balance"
|
|
26001
|
+
]
|
|
26002
|
+
},
|
|
26003
|
+
{
|
|
26004
|
+
name: "Folk Frequency",
|
|
26005
|
+
slug: "folk-frequency",
|
|
26006
|
+
designer: "Eduardo Terrazas",
|
|
26007
|
+
mood: "Cultural, vivid",
|
|
26008
|
+
bestFor: "Consumer apps, food platforms, community products, festive launches",
|
|
26009
|
+
palette: ["#FF1493", "#0047AB", "#FFE000", "#009B77"],
|
|
26010
|
+
paletteNotes: "Vivid folk: hot pink (#FF1493), cobalt blue (#0047AB), sun yellow (#FFE000), emerald (#009B77).",
|
|
26011
|
+
typography: "Bold warm rounded type. Every frame feels handcrafted.",
|
|
26012
|
+
composition: "Pattern and repetition \u2014 folk art rhythm and density. Layered compositions with rich visual texture.",
|
|
26013
|
+
motion: "Colorful motion \u2014 elements bounce, pop, and spin into place with joy.",
|
|
26014
|
+
transition: "Swirl Vortex or Ripple Waves (hypnotic, celebratory)",
|
|
26015
|
+
gsapSignature: "back.out(1.6), elastic.out(1, 0.5) \u2014 overshoots feel intentional",
|
|
26016
|
+
avoid: [
|
|
26017
|
+
"Muted or monochrome palettes \u2014 kill the celebration",
|
|
26018
|
+
"Pure flat / minimal compositions \u2014 must feel layered",
|
|
26019
|
+
"Linear easings \u2014 motion should feel joyful"
|
|
26020
|
+
]
|
|
26021
|
+
},
|
|
26022
|
+
{
|
|
26023
|
+
name: "Shadow Cut",
|
|
26024
|
+
slug: "shadow-cut",
|
|
26025
|
+
designer: "Hans Hillmann",
|
|
26026
|
+
mood: "Dark, cinematic",
|
|
26027
|
+
bestFor: "Security products, dramatic reveals, investigative content, intense launches",
|
|
26028
|
+
palette: ["#0a0a0a", "#3a3a3a", "#ffffff", "#C1121F"],
|
|
26029
|
+
paletteNotes: "Near-monochrome \u2014 deep blacks (#0a0a0a), cold greys (#3a3a3a), stark white + ONE accent (blood red #C1121F or toxic green #39FF14).",
|
|
26030
|
+
typography: "Sharp angular text like film noir title cards. Heavy contrast, no softness.",
|
|
26031
|
+
composition: "Heavy shadow \u2014 elements emerge from darkness. The reveal IS the narrative.",
|
|
26032
|
+
motion: "Slow creeping push-ins, dramatic scale reveals. Silence before the hit matters.",
|
|
26033
|
+
transition: "Domain Warp (dissolves reality before revealing the next scene)",
|
|
26034
|
+
gsapSignature: "power4.in for exits, power3.out for dramatic reveals \u2014 pause before the hit",
|
|
26035
|
+
avoid: [
|
|
26036
|
+
"Bright or saturated palettes \u2014 kill the cinematic mood",
|
|
26037
|
+
"Bouncy easings \u2014 break the tension",
|
|
26038
|
+
"Quick cuts \u2014 let the reveal breathe"
|
|
26039
|
+
]
|
|
26040
|
+
}
|
|
26041
|
+
];
|
|
26042
|
+
}
|
|
26043
|
+
});
|
|
26044
|
+
|
|
25776
26045
|
// src/commands/_shared/scene-html-emit.ts
|
|
25777
26046
|
function esc(text) {
|
|
25778
26047
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -25798,6 +26067,52 @@ function buildTranscriptTweens(transcript, targetSelector) {
|
|
|
25798
26067
|
return `tl.fromTo('${sel}', { opacity: 0, y: 10 }, { opacity: 1, y: 0, duration: 0.18, ease: 'power2.out' }, ${start});`;
|
|
25799
26068
|
}).join("\n ");
|
|
25800
26069
|
}
|
|
26070
|
+
function crossfadeTweens(scope, dur) {
|
|
26071
|
+
return `tl.from('${scope}', { opacity: 0, duration: ${SCENE_OVERLAP_SECONDS}, ease: 'power2.out' }, 0);
|
|
26072
|
+
tl.to('${scope}', { opacity: 0, duration: ${SCENE_OVERLAP_SECONDS}, ease: 'power2.in' }, ${(dur - SCENE_OVERLAP_SECONDS).toFixed(2)});`;
|
|
26073
|
+
}
|
|
26074
|
+
function fitFontSize(text, baseMaxPx, minPx, comfortableChars) {
|
|
26075
|
+
const charCount = text.length;
|
|
26076
|
+
if (charCount <= comfortableChars) return baseMaxPx;
|
|
26077
|
+
const ratio = comfortableChars / charCount;
|
|
26078
|
+
return Math.max(minPx, Math.round(baseMaxPx * ratio));
|
|
26079
|
+
}
|
|
26080
|
+
function fitKineticFontSize(words) {
|
|
26081
|
+
const totalChars = words.join(" ").length;
|
|
26082
|
+
if (totalChars <= 18) return 180;
|
|
26083
|
+
if (totalChars <= 26) return 140;
|
|
26084
|
+
if (totalChars <= 38) return 110;
|
|
26085
|
+
if (totalChars <= 54) return 90;
|
|
26086
|
+
return 72;
|
|
26087
|
+
}
|
|
26088
|
+
function kenBurnsTween(scope, dur, endScale = 1.08) {
|
|
26089
|
+
return `tl.fromTo('${scope} .backdrop', { scale: 1.0 }, { scale: ${endScale}, duration: ${dur.toFixed(2)}, ease: 'none' }, 0);`;
|
|
26090
|
+
}
|
|
26091
|
+
function idleHeroPulse(scope, selector, idleStart, dur) {
|
|
26092
|
+
const cycleDur = 1.5;
|
|
26093
|
+
const idleEnd = dur - SCENE_OVERLAP_SECONDS;
|
|
26094
|
+
const window2 = idleEnd - idleStart;
|
|
26095
|
+
if (window2 < cycleDur) return "";
|
|
26096
|
+
const plays = Math.max(1, Math.floor(window2 / cycleDur));
|
|
26097
|
+
const repeat = plays - 1;
|
|
26098
|
+
return `tl.to('${scope} ${selector}', { scale: 1.04, y: -8, duration: ${cycleDur}, yoyo: true, repeat: ${repeat}, ease: 'sine.inOut' }, ${idleStart.toFixed(2)});`;
|
|
26099
|
+
}
|
|
26100
|
+
function kineticWordBobs(scope, wordCount, idleStart, dur) {
|
|
26101
|
+
const cycleDur = 1.8;
|
|
26102
|
+
const idleEnd = dur - SCENE_OVERLAP_SECONDS;
|
|
26103
|
+
const window2 = idleEnd - idleStart;
|
|
26104
|
+
if (window2 < cycleDur) return "";
|
|
26105
|
+
const plays = Math.max(1, Math.floor(window2 / cycleDur));
|
|
26106
|
+
const repeat = plays - 1;
|
|
26107
|
+
const lines = [];
|
|
26108
|
+
for (let i = 0; i < wordCount; i++) {
|
|
26109
|
+
const start = (idleStart + i * 0.18).toFixed(2);
|
|
26110
|
+
lines.push(
|
|
26111
|
+
`tl.to('${scope} #w-${i}', { y: -12, duration: ${cycleDur}, yoyo: true, repeat: ${repeat}, ease: 'sine.inOut' }, ${start});`
|
|
26112
|
+
);
|
|
26113
|
+
}
|
|
26114
|
+
return lines.join("\n ");
|
|
26115
|
+
}
|
|
25801
26116
|
function buildPreset(input) {
|
|
25802
26117
|
const id = input.id;
|
|
25803
26118
|
const scope = `[data-composition-id="${id}"]`;
|
|
@@ -25834,7 +26149,8 @@ function buildPreset(input) {
|
|
|
25834
26149
|
const captionInner = useWordSync ? renderTranscriptSpans(transcript) : esc(captionText);
|
|
25835
26150
|
const wordCss = useWordSync ? `
|
|
25836
26151
|
${scope} .caption .word { display: inline-block; opacity: 0; }` : "";
|
|
25837
|
-
const
|
|
26152
|
+
const captionFontPx = fitFontSize(captionText, 56, 28, 60);
|
|
26153
|
+
const captionTween = useWordSync ? buildTranscriptTweens(transcript, `${scope} .caption .word`) : `tl.from('${scope} .caption', { opacity: 0, y: 28, duration: 0.45, ease: 'power3.out' }, 0.05);`;
|
|
25838
26154
|
return {
|
|
25839
26155
|
css: `${scope} {
|
|
25840
26156
|
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
@@ -25842,21 +26158,27 @@ function buildPreset(input) {
|
|
|
25842
26158
|
color: #fff; overflow: hidden; background: #000;
|
|
25843
26159
|
}
|
|
25844
26160
|
${backdrop}
|
|
26161
|
+
${scope} .backdrop { transform-origin: center; }
|
|
25845
26162
|
${scope} .caption {
|
|
25846
26163
|
position: absolute;
|
|
25847
26164
|
left: 8%; right: 8%; bottom: 12%;
|
|
25848
26165
|
text-align: center;
|
|
25849
|
-
font-size:
|
|
26166
|
+
font-size: ${captionFontPx}px;
|
|
25850
26167
|
font-weight: 700;
|
|
25851
26168
|
line-height: 1.2;
|
|
26169
|
+
overflow-wrap: break-word;
|
|
25852
26170
|
text-shadow: 0 4px 20px rgba(0,0,0,0.65);
|
|
25853
26171
|
}${wordCss}`,
|
|
25854
26172
|
body: `${backdropMarkup}
|
|
25855
26173
|
<div class="caption" id="caption">${captionInner}</div>`,
|
|
25856
|
-
timeline
|
|
26174
|
+
timeline: `${crossfadeTweens(scope, dur)}
|
|
26175
|
+
${kenBurnsTween(scope, dur)}
|
|
26176
|
+
${captionTween}
|
|
26177
|
+
${idleHeroPulse(scope, "#caption", 1, dur)}`
|
|
25857
26178
|
};
|
|
25858
26179
|
}
|
|
25859
26180
|
case "announcement": {
|
|
26181
|
+
const fontSize = fitFontSize(headline, 160, 72, 22);
|
|
25860
26182
|
return {
|
|
25861
26183
|
css: `${scope} {
|
|
25862
26184
|
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
@@ -25864,6 +26186,7 @@ function buildPreset(input) {
|
|
|
25864
26186
|
color: #fff; overflow: hidden; background: #000;
|
|
25865
26187
|
}
|
|
25866
26188
|
${backdrop}
|
|
26189
|
+
${scope} .backdrop { transform-origin: center; }
|
|
25867
26190
|
${scope} .announce {
|
|
25868
26191
|
position: absolute; inset: 0;
|
|
25869
26192
|
display: flex; align-items: center; justify-content: center;
|
|
@@ -25872,10 +26195,11 @@ function buildPreset(input) {
|
|
|
25872
26195
|
}
|
|
25873
26196
|
${scope} .announce h1 {
|
|
25874
26197
|
margin: 0;
|
|
25875
|
-
font-size:
|
|
26198
|
+
font-size: ${fontSize}px;
|
|
25876
26199
|
font-weight: 900;
|
|
25877
26200
|
letter-spacing: -4px;
|
|
25878
|
-
line-height: 1;
|
|
26201
|
+
line-height: 1.05;
|
|
26202
|
+
overflow-wrap: break-word;
|
|
25879
26203
|
background: linear-gradient(90deg, #8e2de2, #00c9ff);
|
|
25880
26204
|
-webkit-background-clip: text; background-clip: text; color: transparent;
|
|
25881
26205
|
text-shadow: 0 8px 40px rgba(142,45,226,0.35);
|
|
@@ -25885,8 +26209,12 @@ function buildPreset(input) {
|
|
|
25885
26209
|
// Faster, snappier entrance and no tail fade-out — see the
|
|
25886
26210
|
// matching note in the `simple` case above. The headline stays
|
|
25887
26211
|
// on screen until the producer cuts to the next clip; the next
|
|
25888
|
-
// scene's own fade-in handles the visual transition.
|
|
25889
|
-
|
|
26212
|
+
// scene's own fade-in handles the visual transition. Backdrop
|
|
26213
|
+
// Ken-Burns fills what used to be 80% dead static frame.
|
|
26214
|
+
timeline: `${crossfadeTweens(scope, dur)}
|
|
26215
|
+
${kenBurnsTween(scope, dur)}
|
|
26216
|
+
tl.from('${scope} #headline', { opacity: 0, scale: 0.92, duration: 0.55, ease: 'power3.out' }, 0.05);
|
|
26217
|
+
${idleHeroPulse(scope, "#headline", 1, dur)}`
|
|
25890
26218
|
};
|
|
25891
26219
|
}
|
|
25892
26220
|
case "explainer": {
|
|
@@ -25898,6 +26226,7 @@ function buildPreset(input) {
|
|
|
25898
26226
|
const wordCss = useWordSync ? `
|
|
25899
26227
|
${scope} #subtitle .word { display: inline-block; opacity: 0; }` : "";
|
|
25900
26228
|
const subtitleTween = useWordSync ? buildTranscriptTweens(transcript, `${scope} #subtitle .word`) : sub ? `tl.from('${scope} #subtitle', { opacity: 0, y: 30, duration: 0.55, ease: 'power3.out' }, 0.55);` : "";
|
|
26229
|
+
const titleFontPx = fitFontSize(headline, 110, 56, 30);
|
|
25901
26230
|
return {
|
|
25902
26231
|
css: `${scope} {
|
|
25903
26232
|
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
@@ -25905,6 +26234,7 @@ function buildPreset(input) {
|
|
|
25905
26234
|
color: #fff; overflow: hidden; background: #000;
|
|
25906
26235
|
}
|
|
25907
26236
|
${backdrop}
|
|
26237
|
+
${scope} .backdrop { transform-origin: center; }
|
|
25908
26238
|
${scope} .stage {
|
|
25909
26239
|
position: absolute; inset: 0;
|
|
25910
26240
|
display: flex; flex-direction: column; justify-content: center;
|
|
@@ -25915,11 +26245,13 @@ function buildPreset(input) {
|
|
|
25915
26245
|
color: #00c9ff; font-weight: 600;
|
|
25916
26246
|
}
|
|
25917
26247
|
${scope} .title {
|
|
25918
|
-
font-size:
|
|
26248
|
+
font-size: ${titleFontPx}px; font-weight: 800; letter-spacing: -2px;
|
|
25919
26249
|
line-height: 1.05; margin: 0;
|
|
26250
|
+
overflow-wrap: break-word;
|
|
25920
26251
|
}
|
|
25921
26252
|
${scope} .subtitle {
|
|
25922
26253
|
font-size: 38px; font-weight: 300; color: #c0c0d0; max-width: 80%;
|
|
26254
|
+
overflow-wrap: break-word;
|
|
25923
26255
|
}${wordCss}`,
|
|
25924
26256
|
body: `${backdropMarkup}
|
|
25925
26257
|
<div class="stage">
|
|
@@ -25927,9 +26259,12 @@ function buildPreset(input) {
|
|
|
25927
26259
|
<h1 class="title" id="title">${esc(headline)}</h1>${sub ? `
|
|
25928
26260
|
<div class="subtitle" id="subtitle">${subtitleInner}</div>` : ""}
|
|
25929
26261
|
</div>`,
|
|
25930
|
-
timeline:
|
|
26262
|
+
timeline: `${crossfadeTweens(scope, dur)}
|
|
26263
|
+
${kenBurnsTween(scope, dur)}
|
|
26264
|
+
tl.from('${scope} #kicker', { opacity: 0, y: 16, duration: 0.4, ease: 'power2.out' }, 0.1);
|
|
25931
26265
|
tl.from('${scope} #title', { opacity: 0, y: 60, duration: 0.7, ease: 'power3.out' }, 0.25);
|
|
25932
|
-
${subtitleTween}
|
|
26266
|
+
${subtitleTween}
|
|
26267
|
+
${idleHeroPulse(scope, "#title", 1.2, dur)}`
|
|
25933
26268
|
};
|
|
25934
26269
|
}
|
|
25935
26270
|
case "kinetic-type": {
|
|
@@ -25945,6 +26280,9 @@ function buildPreset(input) {
|
|
|
25945
26280
|
const start = (0.05 + i * stagger).toFixed(2);
|
|
25946
26281
|
return `tl.from('${scope} #w-${i}', { opacity: 0, y: 80, scale: 0.8, duration: 0.45, ease: 'back.out(1.8)' }, ${start});`;
|
|
25947
26282
|
}).join("\n ");
|
|
26283
|
+
const kineticFontPx = fitKineticFontSize(words);
|
|
26284
|
+
const lastWordEntryEnd = useWordSync ? Math.max(...transcript.map((w) => w.end + 0.2)) : 0.05 + (words.length - 1) * stagger + 0.45 + 0.2;
|
|
26285
|
+
const idleStart = Math.max(1, Number(lastWordEntryEnd.toFixed(2)));
|
|
25948
26286
|
return {
|
|
25949
26287
|
css: `${scope} {
|
|
25950
26288
|
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
@@ -25952,21 +26290,28 @@ function buildPreset(input) {
|
|
|
25952
26290
|
color: #fff; overflow: hidden; background: #000;
|
|
25953
26291
|
}
|
|
25954
26292
|
${backdrop}
|
|
26293
|
+
${scope} .backdrop { transform-origin: center; }
|
|
25955
26294
|
${scope} .kinetic {
|
|
25956
26295
|
position: absolute; inset: 0;
|
|
25957
26296
|
display: flex; align-items: center; justify-content: center;
|
|
26297
|
+
flex-wrap: wrap; gap: 8px 16px;
|
|
25958
26298
|
text-align: center; padding: 0 6%;
|
|
25959
|
-
font-size:
|
|
26299
|
+
font-size: ${kineticFontPx}px; font-weight: 900; letter-spacing: -6px;
|
|
25960
26300
|
line-height: 1; text-shadow: 0 6px 30px rgba(0,0,0,0.6);
|
|
26301
|
+
overflow-wrap: break-word;
|
|
25961
26302
|
}
|
|
25962
26303
|
${scope} .kinetic .word { display: inline-block; margin: 0 12px; }`,
|
|
25963
26304
|
body: `${backdropMarkup}
|
|
25964
26305
|
<div class="kinetic">${wordSpans}</div>`,
|
|
25965
|
-
timeline:
|
|
26306
|
+
timeline: `${crossfadeTweens(scope, dur)}
|
|
26307
|
+
${kenBurnsTween(scope, dur)}
|
|
26308
|
+
${tweens}
|
|
26309
|
+
${kineticWordBobs(scope, words.length, idleStart, dur)}`
|
|
25966
26310
|
};
|
|
25967
26311
|
}
|
|
25968
26312
|
case "product-shot": {
|
|
25969
26313
|
const label = kicker || humanise(id);
|
|
26314
|
+
const headlineFontPx = fitFontSize(headline, 72, 40, 50);
|
|
25970
26315
|
return {
|
|
25971
26316
|
css: `${scope} {
|
|
25972
26317
|
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
@@ -25984,22 +26329,26 @@ function buildPreset(input) {
|
|
|
25984
26329
|
}
|
|
25985
26330
|
${scope} .product-headline {
|
|
25986
26331
|
position: absolute; left: 8%; right: 8%; bottom: 14%;
|
|
25987
|
-
font-size:
|
|
25988
|
-
line-height: 1.1;
|
|
26332
|
+
font-size: ${headlineFontPx}px; font-weight: 800; letter-spacing: -1px;
|
|
26333
|
+
line-height: 1.1; overflow-wrap: break-word;
|
|
26334
|
+
text-shadow: 0 4px 20px rgba(0,0,0,0.7);
|
|
25989
26335
|
}${subhead ? `
|
|
25990
26336
|
${scope} .product-sub {
|
|
25991
26337
|
position: absolute; left: 8%; right: 8%; bottom: 8%;
|
|
25992
26338
|
font-size: 28px; font-weight: 400; color: #d0d0e0;
|
|
26339
|
+
overflow-wrap: break-word;
|
|
25993
26340
|
text-shadow: 0 2px 10px rgba(0,0,0,0.7);
|
|
25994
26341
|
}` : ""}`,
|
|
25995
26342
|
body: `${backdropMarkup}
|
|
25996
26343
|
<div class="label" id="label">${esc(label)}</div>
|
|
25997
26344
|
<div class="product-headline" id="headline">${esc(headline)}</div>${subhead ? `
|
|
25998
26345
|
<div class="product-sub" id="subhead">${esc(subhead)}</div>` : ""}`,
|
|
25999
|
-
timeline:
|
|
26346
|
+
timeline: `${crossfadeTweens(scope, dur)}
|
|
26347
|
+
tl.fromTo('${scope} .backdrop', { scale: 1.0 }, { scale: 1.12, duration: ${dur.toFixed(2)}, ease: 'none' }, 0);
|
|
26000
26348
|
tl.from('${scope} #label', { opacity: 0, x: -30, duration: 0.5, ease: 'power3.out' }, 0.2);
|
|
26001
26349
|
tl.from('${scope} #headline', { opacity: 0, y: 40, duration: 0.6, ease: 'power3.out' }, 0.4);${subhead ? `
|
|
26002
|
-
tl.from('${scope} #subhead', { opacity: 0, y: 20, duration: 0.5, ease: 'power3.out' }, 0.65);` : ""}
|
|
26350
|
+
tl.from('${scope} #subhead', { opacity: 0, y: 20, duration: 0.5, ease: 'power3.out' }, 0.65);` : ""}
|
|
26351
|
+
${idleHeroPulse(scope, "#headline", 1.2, dur)}`
|
|
26003
26352
|
};
|
|
26004
26353
|
}
|
|
26005
26354
|
}
|
|
@@ -26045,7 +26394,7 @@ ${audioBlock}
|
|
|
26045
26394
|
</template>
|
|
26046
26395
|
`;
|
|
26047
26396
|
}
|
|
26048
|
-
function nextSceneStart(rootHtml) {
|
|
26397
|
+
function nextSceneStart(rootHtml, overlap = 0) {
|
|
26049
26398
|
const clipRegex = /<div\s+class="clip"[^>]*?\sdata-start="([\d.]+)"[^>]*?\sdata-duration="([\d.]+)"/gi;
|
|
26050
26399
|
let maxEnd = 0;
|
|
26051
26400
|
let match;
|
|
@@ -26053,14 +26402,15 @@ function nextSceneStart(rootHtml) {
|
|
|
26053
26402
|
const end = parseFloat(match[1]) + parseFloat(match[2]);
|
|
26054
26403
|
if (Number.isFinite(end) && end > maxEnd) maxEnd = end;
|
|
26055
26404
|
}
|
|
26056
|
-
return maxEnd;
|
|
26405
|
+
return Math.max(0, maxEnd - overlap);
|
|
26057
26406
|
}
|
|
26058
26407
|
function buildClipReference(opts) {
|
|
26059
26408
|
const start = Number(opts.start.toFixed(3));
|
|
26060
26409
|
const duration = Number(opts.duration.toFixed(3));
|
|
26061
26410
|
const track = opts.trackIndex ?? 1;
|
|
26062
26411
|
const src = opts.src ?? `compositions/scene-${opts.id}.html`;
|
|
26063
|
-
|
|
26412
|
+
const zIndex = Math.max(1, 1e6 - Math.floor(start * 1e3));
|
|
26413
|
+
return `<div class="clip" data-composition-id="${esc(opts.id)}" data-composition-src="${esc(src)}" data-start="${start}" data-duration="${duration}" data-track-index="${track}" style="z-index: ${zIndex};"></div>`;
|
|
26064
26414
|
}
|
|
26065
26415
|
function insertClipIntoRoot(rootHtml, clip) {
|
|
26066
26416
|
const clipDiv = buildClipReference(clip);
|
|
@@ -26093,7 +26443,7 @@ function readRootDims(rootHtml) {
|
|
|
26093
26443
|
if (!widthMatch || !heightMatch) return null;
|
|
26094
26444
|
return { width: parseInt(widthMatch[1], 10), height: parseInt(heightMatch[1], 10) };
|
|
26095
26445
|
}
|
|
26096
|
-
var SCENE_PRESETS, GSAP_CDN;
|
|
26446
|
+
var SCENE_PRESETS, GSAP_CDN, SCENE_OVERLAP_SECONDS;
|
|
26097
26447
|
var init_scene_html_emit = __esm({
|
|
26098
26448
|
"src/commands/_shared/scene-html-emit.ts"() {
|
|
26099
26449
|
"use strict";
|
|
@@ -26105,6 +26455,7 @@ var init_scene_html_emit = __esm({
|
|
|
26105
26455
|
"product-shot"
|
|
26106
26456
|
];
|
|
26107
26457
|
GSAP_CDN = "https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js";
|
|
26458
|
+
SCENE_OVERLAP_SECONDS = 0.4;
|
|
26108
26459
|
}
|
|
26109
26460
|
});
|
|
26110
26461
|
|
|
@@ -26788,6 +27139,18 @@ function validatePreset(value) {
|
|
|
26788
27139
|
}
|
|
26789
27140
|
return value;
|
|
26790
27141
|
}
|
|
27142
|
+
function validateVisualStyle(value) {
|
|
27143
|
+
const found = getVisualStyle(value);
|
|
27144
|
+
if (!found) {
|
|
27145
|
+
exitWithError(
|
|
27146
|
+
usageError(
|
|
27147
|
+
`Unknown visual style: ${value}`,
|
|
27148
|
+
`Valid: ${visualStyleNames()}. Browse details with \`vibe scene styles\`.`
|
|
27149
|
+
)
|
|
27150
|
+
);
|
|
27151
|
+
}
|
|
27152
|
+
return found;
|
|
27153
|
+
}
|
|
26791
27154
|
async function pathExists2(p) {
|
|
26792
27155
|
try {
|
|
26793
27156
|
await access4(p);
|
|
@@ -27001,7 +27364,20 @@ async function executeSceneAdd(opts) {
|
|
|
27001
27364
|
}
|
|
27002
27365
|
const cfg = await loadVibeProjectConfig(projectDir);
|
|
27003
27366
|
const fallback2 = cfg?.defaultSceneDuration ?? 5;
|
|
27004
|
-
const
|
|
27367
|
+
const NARRATION_TAIL_BUFFER = 0.5;
|
|
27368
|
+
const userDur = opts.duration;
|
|
27369
|
+
const audioMinDur = narrationDuration !== void 0 ? narrationDuration + SCENE_OVERLAP_SECONDS + NARRATION_TAIL_BUFFER : void 0;
|
|
27370
|
+
let duration;
|
|
27371
|
+
if (userDur !== void 0 && audioMinDur !== void 0) {
|
|
27372
|
+
duration = Math.max(userDur, audioMinDur);
|
|
27373
|
+
} else if (audioMinDur !== void 0) {
|
|
27374
|
+
duration = audioMinDur;
|
|
27375
|
+
} else if (userDur !== void 0) {
|
|
27376
|
+
duration = userDur;
|
|
27377
|
+
} else {
|
|
27378
|
+
duration = fallback2;
|
|
27379
|
+
}
|
|
27380
|
+
duration = Number(duration.toFixed(2));
|
|
27005
27381
|
opts.onProgress?.("Emitting scene HTML...");
|
|
27006
27382
|
const sceneHtml = emitSceneHtml({
|
|
27007
27383
|
id,
|
|
@@ -27019,8 +27395,10 @@ async function executeSceneAdd(opts) {
|
|
|
27019
27395
|
await mkdir4(dirname3(scenePath), { recursive: true });
|
|
27020
27396
|
await writeFile6(scenePath, sceneHtml, "utf-8");
|
|
27021
27397
|
opts.onProgress?.("Updating root composition...");
|
|
27022
|
-
const start = nextSceneStart(rootHtmlBefore);
|
|
27023
|
-
const
|
|
27398
|
+
const start = nextSceneStart(rootHtmlBefore, SCENE_OVERLAP_SECONDS);
|
|
27399
|
+
const existingClipCount = (rootHtmlBefore.match(/<div\s+class="clip"/g) || []).length;
|
|
27400
|
+
const trackIndex = existingClipCount % 2 + 1;
|
|
27401
|
+
const updated = insertClipIntoRoot(rootHtmlBefore, { id, start, duration, trackIndex });
|
|
27024
27402
|
await writeFile6(rootPath, updated, "utf-8");
|
|
27025
27403
|
const transcriptAbsPath = transcriptRelPath ? resolve10(projectDir, transcriptRelPath) : void 0;
|
|
27026
27404
|
return {
|
|
@@ -27079,6 +27457,7 @@ var init_scene = __esm({
|
|
|
27079
27457
|
init_dist2();
|
|
27080
27458
|
init_tts_resolve();
|
|
27081
27459
|
init_scene_project();
|
|
27460
|
+
init_visual_styles();
|
|
27082
27461
|
init_scene_html_emit();
|
|
27083
27462
|
init_scene_lint();
|
|
27084
27463
|
init_scene_render();
|
|
@@ -27103,21 +27482,28 @@ Examples:
|
|
|
27103
27482
|
|
|
27104
27483
|
A scene project is bilingual: it works with both \`vibe\` and \`npx hyperframes\`.
|
|
27105
27484
|
Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
27106
|
-
sceneCommand.command("init").description("Scaffold a new scene project (or safely augment an existing Hyperframes project)").argument("<dir>", "Project directory (created if it doesn't exist)").option("-n, --name <name>", "Project name (defaults to directory basename)").option("-r, --ratio <ratio>", "Aspect ratio: 16:9, 9:16, 1:1, 4:5", "16:9").option("-d, --duration <sec>", "Default root composition duration (seconds)", "10").option("--dry-run", "Preview parameters without writing files").action(async (dir, options) => {
|
|
27485
|
+
sceneCommand.command("init").description("Scaffold a new scene project (or safely augment an existing Hyperframes project)").argument("<dir>", "Project directory (created if it doesn't exist)").option("-n, --name <name>", "Project name (defaults to directory basename)").option("-r, --ratio <ratio>", "Aspect ratio: 16:9, 9:16, 1:1, 4:5", "16:9").option("-d, --duration <sec>", "Default root composition duration (seconds)", "10").option("--visual-style <name>", `Seed DESIGN.md from a named style (browse via \`vibe scene styles\`). E.g. "Swiss Pulse"`).option("--dry-run", "Preview parameters without writing files").action(async (dir, options) => {
|
|
27107
27486
|
const aspect = validateAspect(options.ratio);
|
|
27108
27487
|
const duration = validateDuration(options.duration);
|
|
27109
27488
|
const name = options.name ?? basename4(dir.replace(/\/+$/, ""));
|
|
27489
|
+
const visualStyle = options.visualStyle ? validateVisualStyle(options.visualStyle) : void 0;
|
|
27110
27490
|
if (options.dryRun) {
|
|
27111
27491
|
outputResult({
|
|
27112
27492
|
dryRun: true,
|
|
27113
27493
|
command: "scene init",
|
|
27114
|
-
params: {
|
|
27494
|
+
params: {
|
|
27495
|
+
dir,
|
|
27496
|
+
name,
|
|
27497
|
+
aspect,
|
|
27498
|
+
duration,
|
|
27499
|
+
visualStyle: visualStyle?.name ?? null
|
|
27500
|
+
}
|
|
27115
27501
|
});
|
|
27116
27502
|
return;
|
|
27117
27503
|
}
|
|
27118
27504
|
const spinner2 = isJsonMode() ? null : ora(`Scaffolding scene project at ${dir}...`).start();
|
|
27119
27505
|
try {
|
|
27120
|
-
const result = await scaffoldSceneProject({ dir, name, aspect, duration });
|
|
27506
|
+
const result = await scaffoldSceneProject({ dir, name, aspect, duration, visualStyle });
|
|
27121
27507
|
if (isJsonMode()) {
|
|
27122
27508
|
outputResult({
|
|
27123
27509
|
success: true,
|
|
@@ -27126,6 +27512,7 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
|
27126
27512
|
name,
|
|
27127
27513
|
aspect,
|
|
27128
27514
|
duration,
|
|
27515
|
+
visualStyle: visualStyle?.name ?? null,
|
|
27129
27516
|
created: result.created,
|
|
27130
27517
|
merged: result.merged,
|
|
27131
27518
|
skipped: result.skipped
|
|
@@ -27142,7 +27529,13 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
|
27142
27529
|
console.log();
|
|
27143
27530
|
console.log(source_default.bold.cyan("Next steps"));
|
|
27144
27531
|
console.log(source_default.dim("\u2500".repeat(60)));
|
|
27145
|
-
|
|
27532
|
+
if (visualStyle) {
|
|
27533
|
+
console.log(` ${source_default.dim("DESIGN.md seeded with")} ${source_default.bold(visualStyle.name)} ${source_default.dim("\u2014 review and customise.")}`);
|
|
27534
|
+
} else {
|
|
27535
|
+
console.log(` ${source_default.cyan("vibe scene styles")} ${source_default.dim("# pick a named style for DESIGN.md")}`);
|
|
27536
|
+
}
|
|
27537
|
+
console.log(` ${source_default.cyan("npx skills add heygen-com/hyperframes")} ${source_default.dim("# load the cinematic-craft skill set")}`);
|
|
27538
|
+
console.log(` ${source_default.cyan("vibe scene add")} <name> ${source_default.dim("# fallback path: 5-preset emit")}`);
|
|
27146
27539
|
console.log(` ${source_default.cyan("vibe scene lint")} ${source_default.dim("# validate HTML")}`);
|
|
27147
27540
|
console.log(` ${source_default.cyan("vibe scene render")} ${source_default.dim("# render to MP4")}`);
|
|
27148
27541
|
} catch (error) {
|
|
@@ -27151,6 +27544,69 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
|
27151
27544
|
exitWithError(generalError(`Failed to scaffold: ${msg}`));
|
|
27152
27545
|
}
|
|
27153
27546
|
});
|
|
27547
|
+
sceneCommand.command("styles").description("List vendored visual styles (or show one) for DESIGN.md seeding").argument("[name]", "Style name to inspect (omit to list all)").action((name) => {
|
|
27548
|
+
if (!name) {
|
|
27549
|
+
const all = listVisualStyles();
|
|
27550
|
+
if (isJsonMode()) {
|
|
27551
|
+
outputResult({
|
|
27552
|
+
success: true,
|
|
27553
|
+
command: "scene styles",
|
|
27554
|
+
count: all.length,
|
|
27555
|
+
styles: all.map((s) => ({
|
|
27556
|
+
name: s.name,
|
|
27557
|
+
slug: s.slug,
|
|
27558
|
+
designer: s.designer,
|
|
27559
|
+
mood: s.mood,
|
|
27560
|
+
bestFor: s.bestFor
|
|
27561
|
+
}))
|
|
27562
|
+
});
|
|
27563
|
+
return;
|
|
27564
|
+
}
|
|
27565
|
+
console.log();
|
|
27566
|
+
console.log(source_default.bold.cyan("Visual styles"));
|
|
27567
|
+
console.log(source_default.dim("\u2500".repeat(60)));
|
|
27568
|
+
for (const s of all) {
|
|
27569
|
+
console.log(
|
|
27570
|
+
` ${source_default.bold(s.name.padEnd(18))} ${source_default.dim(s.mood.padEnd(24))} ${source_default.dim(s.bestFor)}`
|
|
27571
|
+
);
|
|
27572
|
+
}
|
|
27573
|
+
console.log();
|
|
27574
|
+
console.log(source_default.dim("Show details: "), source_default.cyan('vibe scene styles "<name>"'));
|
|
27575
|
+
console.log(source_default.dim("Seed DESIGN.md:"), source_default.cyan('vibe scene init <dir> --visual-style "<name>"'));
|
|
27576
|
+
return;
|
|
27577
|
+
}
|
|
27578
|
+
const style = getVisualStyle(name);
|
|
27579
|
+
if (!style) {
|
|
27580
|
+
exitWithError(
|
|
27581
|
+
usageError(
|
|
27582
|
+
`Unknown visual style: ${name}`,
|
|
27583
|
+
`Valid: ${visualStyleNames()}.`
|
|
27584
|
+
)
|
|
27585
|
+
);
|
|
27586
|
+
return;
|
|
27587
|
+
}
|
|
27588
|
+
if (isJsonMode()) {
|
|
27589
|
+
outputResult({ success: true, command: "scene styles", style });
|
|
27590
|
+
return;
|
|
27591
|
+
}
|
|
27592
|
+
console.log();
|
|
27593
|
+
console.log(source_default.bold.cyan(style.name), source_default.dim(`\u2014 ${style.designer}`));
|
|
27594
|
+
console.log(source_default.dim("\u2500".repeat(60)));
|
|
27595
|
+
console.log(`${source_default.bold("Mood:")} ${style.mood}`);
|
|
27596
|
+
console.log(`${source_default.bold("Best for:")} ${style.bestFor}`);
|
|
27597
|
+
console.log(`${source_default.bold("Palette:")} ${style.palette.join(", ")}`);
|
|
27598
|
+
console.log(source_default.dim(" ") + style.paletteNotes);
|
|
27599
|
+
console.log(`${source_default.bold("Typography:")} ${style.typography}`);
|
|
27600
|
+
console.log(`${source_default.bold("Composition:")} ${style.composition}`);
|
|
27601
|
+
console.log(`${source_default.bold("Motion:")} ${style.motion}`);
|
|
27602
|
+
console.log(`${source_default.bold("GSAP:")} ${style.gsapSignature}`);
|
|
27603
|
+
console.log(`${source_default.bold("Transition:")} ${style.transition}`);
|
|
27604
|
+
console.log();
|
|
27605
|
+
console.log(source_default.bold("Avoid:"));
|
|
27606
|
+
for (const a of style.avoid) console.log(` ${source_default.red("\u2022")} ${a}`);
|
|
27607
|
+
console.log();
|
|
27608
|
+
console.log(source_default.dim("Seed DESIGN.md:"), source_default.cyan(`vibe scene init <dir> --visual-style "${style.name}"`));
|
|
27609
|
+
});
|
|
27154
27610
|
sceneCommand.command("add").description("Add a new scene to a project: AI narration + image + per-scene HTML").argument("<name>", "Scene name (slugified into the composition id)").option("--style <preset>", `Style preset: ${SCENE_PRESETS.join(", ")}`, "simple").option("--narration <text>", "Narration text (or path to a .txt file). Drives TTS + scene duration.").option("--narration-file <path>", "Existing narration audio file (.wav/.mp3). Skips TTS \u2014 useful with hyperframes tts, Mac say, or other external tools.").option("-d, --duration <sec>", "Explicit scene duration in seconds (overrides narration audio)").option("--visuals <prompt>", "Image prompt \u2014 generates assets/scene-<id>.png via the configured image provider").option("--headline <text>", "Visible headline (defaults to the humanised scene name)").option("--kicker <text>", "Small label above the headline (explainer / product-shot)").option("--insert-into <path>", "Root composition file to update", "index.html").option("--project <dir>", "Project directory", ".").option("--image-provider <name>", "Image provider: gemini, openai", "gemini").option("--tts <provider>", "TTS provider: auto, elevenlabs, kokoro (default auto \u2014 picks ElevenLabs when key set, else Kokoro local)", "auto").option("--voice <id>", "Voice id (ElevenLabs name/id, or Kokoro id like af_heart, am_michael)").option("--no-audio", "Skip TTS even when --narration is provided (useful for tests/agent dry runs)").option("--no-image", "Skip image generation even when --visuals is provided").option("--no-transcribe", "Skip Whisper word-level transcribe step (no transcript-<id>.json emitted)").option("--transcribe-language <code>", "BCP-47 language code passed to Whisper (e.g. en, ko)").option("--force", "Overwrite an existing compositions/scene-<id>.html").option("--dry-run", "Preview parameters without writing files or calling APIs").action(async (name, options) => {
|
|
27155
27611
|
if (options.style) options.style = validatePreset(options.style);
|
|
27156
27612
|
if (options.duration !== void 0) options.duration = validateDuration(options.duration);
|
|
@@ -30821,7 +31277,12 @@ async function executeSegmentsToScenes(opts) {
|
|
|
30821
31277
|
const dims = aspectToDims(aspect);
|
|
30822
31278
|
const preset = opts.scenePreset ?? DEFAULT_PRESET;
|
|
30823
31279
|
const projectName = opts.projectName ?? basename7(outputDir);
|
|
30824
|
-
const totalDuration =
|
|
31280
|
+
const totalDuration = (() => {
|
|
31281
|
+
const sum = opts.segments.reduce((acc, s) => acc + (s.duration || 0), 0);
|
|
31282
|
+
if (sum <= 0) return 10;
|
|
31283
|
+
const joins = Math.max(0, opts.segments.length - 1);
|
|
31284
|
+
return Math.max(0.1, sum - joins * SCENE_OVERLAP_SECONDS);
|
|
31285
|
+
})();
|
|
30825
31286
|
const baseError = (error) => ({
|
|
30826
31287
|
success: false,
|
|
30827
31288
|
outputDir,
|
|
@@ -30894,9 +31355,10 @@ async function executeSegmentsToScenes(opts) {
|
|
|
30894
31355
|
id,
|
|
30895
31356
|
start: clipStart,
|
|
30896
31357
|
duration,
|
|
31358
|
+
trackIndex: i % 2 + 1,
|
|
30897
31359
|
src: `compositions/${id}.html`
|
|
30898
31360
|
});
|
|
30899
|
-
clipStart += duration;
|
|
31361
|
+
clipStart += duration - SCENE_OVERLAP_SECONDS;
|
|
30900
31362
|
}
|
|
30901
31363
|
await writeFile10(rootAbs, rootHtml, "utf-8");
|
|
30902
31364
|
let lintResult;
|
|
@@ -57537,7 +57999,7 @@ init_source();
|
|
|
57537
57999
|
// package.json
|
|
57538
58000
|
var package_default = {
|
|
57539
58001
|
name: "@vibeframe/cli",
|
|
57540
|
-
version: "0.
|
|
58002
|
+
version: "0.58.0",
|
|
57541
58003
|
description: "VibeFrame CLI - natural language video editing from the terminal",
|
|
57542
58004
|
private: false,
|
|
57543
58005
|
license: "MIT",
|
|
@@ -65245,25 +65707,25 @@ var AI_FEATURES = [
|
|
|
65245
65707
|
{
|
|
65246
65708
|
label: "Images",
|
|
65247
65709
|
desc: "generate + edit",
|
|
65248
|
-
defaultProvider: "
|
|
65249
|
-
alsoAvailable: "
|
|
65250
|
-
keys: [{ configKey: "
|
|
65710
|
+
defaultProvider: "OpenAI gpt-image-2 (Artificial Analysis #1, since v0.56)",
|
|
65711
|
+
alsoAvailable: "Gemini Nano Banana, Grok Imagine",
|
|
65712
|
+
keys: [{ configKey: "openai", envVar: "OPENAI_API_KEY", name: "OpenAI", url: "https://platform.openai.com/api-keys", what: "gpt-image-2 image generation + editing (also Whisper, Agent)" }],
|
|
65251
65713
|
tryCommand: 'vibe generate image "a sunset over mountains" -o test.png'
|
|
65252
65714
|
},
|
|
65253
65715
|
{
|
|
65254
65716
|
label: "Videos",
|
|
65255
65717
|
desc: "generate + extend",
|
|
65256
|
-
defaultProvider: "
|
|
65257
|
-
alsoAvailable: "Kling, Runway Gen-4.5, Google Veo",
|
|
65258
|
-
keys: [{ configKey: "
|
|
65718
|
+
defaultProvider: "fal.ai Seedance 2.0 (Artificial Analysis #2 t2v + i2v, since v0.57)",
|
|
65719
|
+
alsoAvailable: "Grok Imagine, Kling, Runway Gen-4.5, Google Veo",
|
|
65720
|
+
keys: [{ configKey: "fal", envVar: "FAL_KEY", name: "fal.ai", url: "https://fal.ai/dashboard/keys", what: "ByteDance Seedance 2.0 text-to-video and image-to-video" }],
|
|
65259
65721
|
tryCommand: 'vibe generate video "ocean waves" -o waves.mp4'
|
|
65260
65722
|
},
|
|
65261
65723
|
{
|
|
65262
65724
|
label: "Audio",
|
|
65263
65725
|
desc: "TTS, SFX, music, voice clone",
|
|
65264
|
-
defaultProvider: "ElevenLabs",
|
|
65726
|
+
defaultProvider: "ElevenLabs (paid, premium quality) \u2014 falls back to local Kokoro when no key (free, since v0.54)",
|
|
65265
65727
|
alsoAvailable: "Replicate MusicGen (music only)",
|
|
65266
|
-
keys: [{ configKey: "elevenlabs", envVar: "ELEVENLABS_API_KEY", name: "ElevenLabs", url: "https://elevenlabs.io/app/settings/api-keys", what: "Text-to-speech, sound effects, music, voice cloning" }],
|
|
65728
|
+
keys: [{ configKey: "elevenlabs", envVar: "ELEVENLABS_API_KEY", name: "ElevenLabs", url: "https://elevenlabs.io/app/settings/api-keys", what: "Text-to-speech, sound effects, music, voice cloning (skip to use local Kokoro)" }],
|
|
65267
65729
|
tryCommand: 'vibe generate speech "Hello world" -o hello.mp3'
|
|
65268
65730
|
},
|
|
65269
65731
|
{
|
|
@@ -65315,9 +65777,9 @@ async function runSetupWizard(fullSetup = false) {
|
|
|
65315
65777
|
config2.llm.provider = "claude";
|
|
65316
65778
|
const pipelineKeys = [
|
|
65317
65779
|
{ configKey: "anthropic", envVar: "ANTHROPIC_API_KEY", name: "Anthropic", url: "https://console.anthropic.com/settings/keys", what: "Claude \u2014 storyboard generation + reasoning" },
|
|
65318
|
-
{ configKey: "
|
|
65319
|
-
{ configKey: "
|
|
65320
|
-
{ configKey: "
|
|
65780
|
+
{ configKey: "openai", envVar: "OPENAI_API_KEY", name: "OpenAI", url: "https://platform.openai.com/api-keys", what: "gpt-image-2 image generation (default since v0.56) + Whisper word-level transcribe" },
|
|
65781
|
+
{ configKey: "fal", envVar: "FAL_KEY", name: "fal.ai", url: "https://fal.ai/dashboard/keys", what: "Seedance 2.0 \u2014 video generation, default since v0.57" },
|
|
65782
|
+
{ configKey: "elevenlabs", envVar: "ELEVENLABS_API_KEY", name: "ElevenLabs", url: "https://elevenlabs.io/app/settings/api-keys", what: "Text-to-speech narration + music (skip to use local Kokoro)" }
|
|
65321
65783
|
];
|
|
65322
65784
|
console.log(source_default.dim("Step 2 of 2"));
|
|
65323
65785
|
console.log(source_default.bold("Pipeline requires these API keys:"));
|
|
@@ -65448,11 +65910,12 @@ async function runCustomSetup(config2) {
|
|
|
65448
65910
|
console.log(source_default.dim(" Press Enter to skip any provider you don't need."));
|
|
65449
65911
|
console.log();
|
|
65450
65912
|
const allProviders = [
|
|
65913
|
+
{ key: "openai", name: "OpenAI", env: "OPENAI_API_KEY", desc: "gpt-image-2 image gen ($, default since v0.56), Whisper transcribe, Agent" },
|
|
65451
65914
|
{ key: "anthropic", name: "Anthropic", env: "ANTHROPIC_API_KEY", desc: "Claude \u2014 storyboard, color grade, reframe, Agent ($)" },
|
|
65452
|
-
{ key: "
|
|
65915
|
+
{ key: "fal", name: "fal.ai", env: "FAL_KEY", desc: "Seedance 2.0 video gen ($$, default since v0.57)" },
|
|
65453
65916
|
{ key: "google", name: "Google", env: "GOOGLE_API_KEY", desc: "Gemini \u2014 image gen (free tier), video analysis ($), Veo ($$)" },
|
|
65454
65917
|
{ key: "xai", name: "xAI", env: "XAI_API_KEY", desc: "Grok \u2014 video gen with audio ($$), image ($), Agent" },
|
|
65455
|
-
{ key: "elevenlabs", name: "ElevenLabs", env: "ELEVENLABS_API_KEY", desc: "TTS ($), SFX, music, voice clone, dubbing" },
|
|
65918
|
+
{ key: "elevenlabs", name: "ElevenLabs", env: "ELEVENLABS_API_KEY", desc: "TTS ($), SFX, music, voice clone, dubbing \u2014 skip to use local Kokoro" },
|
|
65456
65919
|
{ key: "runway", name: "Runway", env: "RUNWAY_API_SECRET", desc: "Gen-4.5 video generation ($$)" },
|
|
65457
65920
|
{ key: "kling", name: "Kling", env: "KLING_API_KEY", desc: "v2.5/v3 video \u2014 std ($$) and pro ($$$) modes" },
|
|
65458
65921
|
{ key: "openrouter", name: "OpenRouter", env: "OPENROUTER_API_KEY", desc: "300+ models via one key \u2014 Agent only (pay per model)" },
|
|
@@ -65566,6 +66029,7 @@ async function showConfig() {
|
|
|
65566
66029
|
{ key: "openai", name: "OpenAI", env: "OPENAI_API_KEY" },
|
|
65567
66030
|
{ key: "google", name: "Google", env: "GOOGLE_API_KEY" },
|
|
65568
66031
|
{ key: "xai", name: "xAI", env: "XAI_API_KEY" },
|
|
66032
|
+
{ key: "fal", name: "fal.ai", env: "FAL_KEY" },
|
|
65569
66033
|
{ key: "elevenlabs", name: "ElevenLabs", env: "ELEVENLABS_API_KEY" },
|
|
65570
66034
|
{ key: "runway", name: "Runway", env: "RUNWAY_API_SECRET" },
|
|
65571
66035
|
{ key: "kling", name: "Kling", env: "KLING_API_KEY" },
|
|
@@ -65642,9 +66106,14 @@ var COMMAND_KEY_MAP = {
|
|
|
65642
66106
|
XAI_API_KEY: [
|
|
65643
66107
|
"agent -p xai",
|
|
65644
66108
|
"generate image -p grok",
|
|
65645
|
-
"generate video",
|
|
66109
|
+
"generate video -p grok",
|
|
65646
66110
|
"edit image -p grok"
|
|
65647
66111
|
],
|
|
66112
|
+
FAL_KEY: [
|
|
66113
|
+
"generate video -p fal (Seedance 2.0 \u2014 default since v0.57)",
|
|
66114
|
+
"generate video -p fal -m fast (lower-latency variant)",
|
|
66115
|
+
"generate video -p fal -i <image> (image-to-video)"
|
|
66116
|
+
],
|
|
65648
66117
|
ELEVENLABS_API_KEY: [
|
|
65649
66118
|
"generate speech",
|
|
65650
66119
|
"generate sound-effect",
|
|
@@ -65656,7 +66125,7 @@ var COMMAND_KEY_MAP = {
|
|
|
65656
66125
|
KLING_API_KEY: ["generate video -p kling"],
|
|
65657
66126
|
RUNWAY_API_SECRET: ["generate video -p runway"],
|
|
65658
66127
|
REPLICATE_API_TOKEN: ["generate music -p replicate"],
|
|
65659
|
-
IMGBB_API_KEY: ["generate video -p kling (image-to-video)"]
|
|
66128
|
+
IMGBB_API_KEY: ["generate video -p kling/fal (image-to-video upload host)"]
|
|
65660
66129
|
};
|
|
65661
66130
|
var FREE_COMMANDS = [
|
|
65662
66131
|
"edit silence-cut",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibeframe/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.58.0",
|
|
4
4
|
"description": "VibeFrame CLI - natural language video editing from the terminal",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
@@ -149,8 +149,8 @@
|
|
|
149
149
|
"tsx": "^4.21.0",
|
|
150
150
|
"typescript": "^5.3.3",
|
|
151
151
|
"vitest": "^1.2.2",
|
|
152
|
-
"@vibeframe/ai-providers": "0.
|
|
153
|
-
"@vibeframe/core": "0.
|
|
152
|
+
"@vibeframe/ai-providers": "0.58.0",
|
|
153
|
+
"@vibeframe/core": "0.58.0"
|
|
154
154
|
},
|
|
155
155
|
"engines": {
|
|
156
156
|
"node": ">=20"
|