@vibeframe/cli 0.57.3 → 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 +494 -33
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -25611,6 +25611,70 @@ function buildEmptyRootHtml(opts) {
|
|
|
25611
25611
|
</html>
|
|
25612
25612
|
`;
|
|
25613
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
|
+
}
|
|
25614
25678
|
function buildProjectClaudeMd(name) {
|
|
25615
25679
|
return `# ${name} \u2014 Scene Authoring Project
|
|
25616
25680
|
|
|
@@ -25618,6 +25682,16 @@ This project is **bilingual**: it works with both VibeFrame (\`vibe\`) and
|
|
|
25618
25682
|
HeyGen Hyperframes (\`hyperframes\`). You can run either CLI inside this
|
|
25619
25683
|
directory.
|
|
25620
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
|
+
|
|
25621
25695
|
## Skills \u2014 USE THESE FIRST
|
|
25622
25696
|
|
|
25623
25697
|
**Always invoke the relevant skill before authoring scenes.** Skills encode
|
|
@@ -25626,14 +25700,23 @@ semantics, VibeFrame pipeline conventions) that are NOT in generic web docs.
|
|
|
25626
25700
|
|
|
25627
25701
|
| Skill | Command | When to use |
|
|
25628
25702
|
| ----------------- | ---------------- | ------------------------------------------------------------------------------------- |
|
|
25629
|
-
| **
|
|
25630
|
-
| **
|
|
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 |
|
|
25631
25705
|
| **gsap** | \`/gsap\` | GSAP tweens, timelines, easing |
|
|
25632
25706
|
|
|
25633
|
-
|
|
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.
|
|
25634
25716
|
|
|
25635
25717
|
## Project structure
|
|
25636
25718
|
|
|
25719
|
+
- \`DESIGN.md\` \u2014 visual identity contract (palette, type, motion, transitions)
|
|
25637
25720
|
- \`index.html\` \u2014 root composition (timeline)
|
|
25638
25721
|
- \`compositions/scene-*.html\` \u2014 per-scene HTML authored by you or the agent
|
|
25639
25722
|
- \`assets/\` \u2014 shared media (narration audio, images, video)
|
|
@@ -25751,6 +25834,17 @@ async function scaffoldSceneProject(opts) {
|
|
|
25751
25834
|
await writeFile4(claudePath, buildProjectClaudeMd(name), "utf-8");
|
|
25752
25835
|
created.push(claudePath);
|
|
25753
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
|
+
}
|
|
25754
25848
|
const gitignorePath = resolve5(dir, ".gitignore");
|
|
25755
25849
|
if (await pathExists(gitignorePath)) {
|
|
25756
25850
|
skipped.push(gitignorePath);
|
|
@@ -25774,6 +25868,180 @@ var init_scene_project = __esm({
|
|
|
25774
25868
|
}
|
|
25775
25869
|
});
|
|
25776
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
|
+
|
|
25777
26045
|
// src/commands/_shared/scene-html-emit.ts
|
|
25778
26046
|
function esc(text) {
|
|
25779
26047
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -25799,6 +26067,52 @@ function buildTranscriptTweens(transcript, targetSelector) {
|
|
|
25799
26067
|
return `tl.fromTo('${sel}', { opacity: 0, y: 10 }, { opacity: 1, y: 0, duration: 0.18, ease: 'power2.out' }, ${start});`;
|
|
25800
26068
|
}).join("\n ");
|
|
25801
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
|
+
}
|
|
25802
26116
|
function buildPreset(input) {
|
|
25803
26117
|
const id = input.id;
|
|
25804
26118
|
const scope = `[data-composition-id="${id}"]`;
|
|
@@ -25835,7 +26149,8 @@ function buildPreset(input) {
|
|
|
25835
26149
|
const captionInner = useWordSync ? renderTranscriptSpans(transcript) : esc(captionText);
|
|
25836
26150
|
const wordCss = useWordSync ? `
|
|
25837
26151
|
${scope} .caption .word { display: inline-block; opacity: 0; }` : "";
|
|
25838
|
-
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);`;
|
|
25839
26154
|
return {
|
|
25840
26155
|
css: `${scope} {
|
|
25841
26156
|
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
@@ -25843,21 +26158,27 @@ function buildPreset(input) {
|
|
|
25843
26158
|
color: #fff; overflow: hidden; background: #000;
|
|
25844
26159
|
}
|
|
25845
26160
|
${backdrop}
|
|
26161
|
+
${scope} .backdrop { transform-origin: center; }
|
|
25846
26162
|
${scope} .caption {
|
|
25847
26163
|
position: absolute;
|
|
25848
26164
|
left: 8%; right: 8%; bottom: 12%;
|
|
25849
26165
|
text-align: center;
|
|
25850
|
-
font-size:
|
|
26166
|
+
font-size: ${captionFontPx}px;
|
|
25851
26167
|
font-weight: 700;
|
|
25852
26168
|
line-height: 1.2;
|
|
26169
|
+
overflow-wrap: break-word;
|
|
25853
26170
|
text-shadow: 0 4px 20px rgba(0,0,0,0.65);
|
|
25854
26171
|
}${wordCss}`,
|
|
25855
26172
|
body: `${backdropMarkup}
|
|
25856
26173
|
<div class="caption" id="caption">${captionInner}</div>`,
|
|
25857
|
-
timeline
|
|
26174
|
+
timeline: `${crossfadeTweens(scope, dur)}
|
|
26175
|
+
${kenBurnsTween(scope, dur)}
|
|
26176
|
+
${captionTween}
|
|
26177
|
+
${idleHeroPulse(scope, "#caption", 1, dur)}`
|
|
25858
26178
|
};
|
|
25859
26179
|
}
|
|
25860
26180
|
case "announcement": {
|
|
26181
|
+
const fontSize = fitFontSize(headline, 160, 72, 22);
|
|
25861
26182
|
return {
|
|
25862
26183
|
css: `${scope} {
|
|
25863
26184
|
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
@@ -25865,6 +26186,7 @@ function buildPreset(input) {
|
|
|
25865
26186
|
color: #fff; overflow: hidden; background: #000;
|
|
25866
26187
|
}
|
|
25867
26188
|
${backdrop}
|
|
26189
|
+
${scope} .backdrop { transform-origin: center; }
|
|
25868
26190
|
${scope} .announce {
|
|
25869
26191
|
position: absolute; inset: 0;
|
|
25870
26192
|
display: flex; align-items: center; justify-content: center;
|
|
@@ -25873,10 +26195,11 @@ function buildPreset(input) {
|
|
|
25873
26195
|
}
|
|
25874
26196
|
${scope} .announce h1 {
|
|
25875
26197
|
margin: 0;
|
|
25876
|
-
font-size:
|
|
26198
|
+
font-size: ${fontSize}px;
|
|
25877
26199
|
font-weight: 900;
|
|
25878
26200
|
letter-spacing: -4px;
|
|
25879
|
-
line-height: 1;
|
|
26201
|
+
line-height: 1.05;
|
|
26202
|
+
overflow-wrap: break-word;
|
|
25880
26203
|
background: linear-gradient(90deg, #8e2de2, #00c9ff);
|
|
25881
26204
|
-webkit-background-clip: text; background-clip: text; color: transparent;
|
|
25882
26205
|
text-shadow: 0 8px 40px rgba(142,45,226,0.35);
|
|
@@ -25886,8 +26209,12 @@ function buildPreset(input) {
|
|
|
25886
26209
|
// Faster, snappier entrance and no tail fade-out — see the
|
|
25887
26210
|
// matching note in the `simple` case above. The headline stays
|
|
25888
26211
|
// on screen until the producer cuts to the next clip; the next
|
|
25889
|
-
// scene's own fade-in handles the visual transition.
|
|
25890
|
-
|
|
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)}`
|
|
25891
26218
|
};
|
|
25892
26219
|
}
|
|
25893
26220
|
case "explainer": {
|
|
@@ -25899,6 +26226,7 @@ function buildPreset(input) {
|
|
|
25899
26226
|
const wordCss = useWordSync ? `
|
|
25900
26227
|
${scope} #subtitle .word { display: inline-block; opacity: 0; }` : "";
|
|
25901
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);
|
|
25902
26230
|
return {
|
|
25903
26231
|
css: `${scope} {
|
|
25904
26232
|
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
@@ -25906,6 +26234,7 @@ function buildPreset(input) {
|
|
|
25906
26234
|
color: #fff; overflow: hidden; background: #000;
|
|
25907
26235
|
}
|
|
25908
26236
|
${backdrop}
|
|
26237
|
+
${scope} .backdrop { transform-origin: center; }
|
|
25909
26238
|
${scope} .stage {
|
|
25910
26239
|
position: absolute; inset: 0;
|
|
25911
26240
|
display: flex; flex-direction: column; justify-content: center;
|
|
@@ -25916,11 +26245,13 @@ function buildPreset(input) {
|
|
|
25916
26245
|
color: #00c9ff; font-weight: 600;
|
|
25917
26246
|
}
|
|
25918
26247
|
${scope} .title {
|
|
25919
|
-
font-size:
|
|
26248
|
+
font-size: ${titleFontPx}px; font-weight: 800; letter-spacing: -2px;
|
|
25920
26249
|
line-height: 1.05; margin: 0;
|
|
26250
|
+
overflow-wrap: break-word;
|
|
25921
26251
|
}
|
|
25922
26252
|
${scope} .subtitle {
|
|
25923
26253
|
font-size: 38px; font-weight: 300; color: #c0c0d0; max-width: 80%;
|
|
26254
|
+
overflow-wrap: break-word;
|
|
25924
26255
|
}${wordCss}`,
|
|
25925
26256
|
body: `${backdropMarkup}
|
|
25926
26257
|
<div class="stage">
|
|
@@ -25928,9 +26259,12 @@ function buildPreset(input) {
|
|
|
25928
26259
|
<h1 class="title" id="title">${esc(headline)}</h1>${sub ? `
|
|
25929
26260
|
<div class="subtitle" id="subtitle">${subtitleInner}</div>` : ""}
|
|
25930
26261
|
</div>`,
|
|
25931
|
-
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);
|
|
25932
26265
|
tl.from('${scope} #title', { opacity: 0, y: 60, duration: 0.7, ease: 'power3.out' }, 0.25);
|
|
25933
|
-
${subtitleTween}
|
|
26266
|
+
${subtitleTween}
|
|
26267
|
+
${idleHeroPulse(scope, "#title", 1.2, dur)}`
|
|
25934
26268
|
};
|
|
25935
26269
|
}
|
|
25936
26270
|
case "kinetic-type": {
|
|
@@ -25946,6 +26280,9 @@ function buildPreset(input) {
|
|
|
25946
26280
|
const start = (0.05 + i * stagger).toFixed(2);
|
|
25947
26281
|
return `tl.from('${scope} #w-${i}', { opacity: 0, y: 80, scale: 0.8, duration: 0.45, ease: 'back.out(1.8)' }, ${start});`;
|
|
25948
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)));
|
|
25949
26286
|
return {
|
|
25950
26287
|
css: `${scope} {
|
|
25951
26288
|
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
@@ -25953,21 +26290,28 @@ function buildPreset(input) {
|
|
|
25953
26290
|
color: #fff; overflow: hidden; background: #000;
|
|
25954
26291
|
}
|
|
25955
26292
|
${backdrop}
|
|
26293
|
+
${scope} .backdrop { transform-origin: center; }
|
|
25956
26294
|
${scope} .kinetic {
|
|
25957
26295
|
position: absolute; inset: 0;
|
|
25958
26296
|
display: flex; align-items: center; justify-content: center;
|
|
26297
|
+
flex-wrap: wrap; gap: 8px 16px;
|
|
25959
26298
|
text-align: center; padding: 0 6%;
|
|
25960
|
-
font-size:
|
|
26299
|
+
font-size: ${kineticFontPx}px; font-weight: 900; letter-spacing: -6px;
|
|
25961
26300
|
line-height: 1; text-shadow: 0 6px 30px rgba(0,0,0,0.6);
|
|
26301
|
+
overflow-wrap: break-word;
|
|
25962
26302
|
}
|
|
25963
26303
|
${scope} .kinetic .word { display: inline-block; margin: 0 12px; }`,
|
|
25964
26304
|
body: `${backdropMarkup}
|
|
25965
26305
|
<div class="kinetic">${wordSpans}</div>`,
|
|
25966
|
-
timeline:
|
|
26306
|
+
timeline: `${crossfadeTweens(scope, dur)}
|
|
26307
|
+
${kenBurnsTween(scope, dur)}
|
|
26308
|
+
${tweens}
|
|
26309
|
+
${kineticWordBobs(scope, words.length, idleStart, dur)}`
|
|
25967
26310
|
};
|
|
25968
26311
|
}
|
|
25969
26312
|
case "product-shot": {
|
|
25970
26313
|
const label = kicker || humanise(id);
|
|
26314
|
+
const headlineFontPx = fitFontSize(headline, 72, 40, 50);
|
|
25971
26315
|
return {
|
|
25972
26316
|
css: `${scope} {
|
|
25973
26317
|
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
@@ -25985,22 +26329,26 @@ function buildPreset(input) {
|
|
|
25985
26329
|
}
|
|
25986
26330
|
${scope} .product-headline {
|
|
25987
26331
|
position: absolute; left: 8%; right: 8%; bottom: 14%;
|
|
25988
|
-
font-size:
|
|
25989
|
-
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);
|
|
25990
26335
|
}${subhead ? `
|
|
25991
26336
|
${scope} .product-sub {
|
|
25992
26337
|
position: absolute; left: 8%; right: 8%; bottom: 8%;
|
|
25993
26338
|
font-size: 28px; font-weight: 400; color: #d0d0e0;
|
|
26339
|
+
overflow-wrap: break-word;
|
|
25994
26340
|
text-shadow: 0 2px 10px rgba(0,0,0,0.7);
|
|
25995
26341
|
}` : ""}`,
|
|
25996
26342
|
body: `${backdropMarkup}
|
|
25997
26343
|
<div class="label" id="label">${esc(label)}</div>
|
|
25998
26344
|
<div class="product-headline" id="headline">${esc(headline)}</div>${subhead ? `
|
|
25999
26345
|
<div class="product-sub" id="subhead">${esc(subhead)}</div>` : ""}`,
|
|
26000
|
-
timeline:
|
|
26346
|
+
timeline: `${crossfadeTweens(scope, dur)}
|
|
26347
|
+
tl.fromTo('${scope} .backdrop', { scale: 1.0 }, { scale: 1.12, duration: ${dur.toFixed(2)}, ease: 'none' }, 0);
|
|
26001
26348
|
tl.from('${scope} #label', { opacity: 0, x: -30, duration: 0.5, ease: 'power3.out' }, 0.2);
|
|
26002
26349
|
tl.from('${scope} #headline', { opacity: 0, y: 40, duration: 0.6, ease: 'power3.out' }, 0.4);${subhead ? `
|
|
26003
|
-
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)}`
|
|
26004
26352
|
};
|
|
26005
26353
|
}
|
|
26006
26354
|
}
|
|
@@ -26046,7 +26394,7 @@ ${audioBlock}
|
|
|
26046
26394
|
</template>
|
|
26047
26395
|
`;
|
|
26048
26396
|
}
|
|
26049
|
-
function nextSceneStart(rootHtml) {
|
|
26397
|
+
function nextSceneStart(rootHtml, overlap = 0) {
|
|
26050
26398
|
const clipRegex = /<div\s+class="clip"[^>]*?\sdata-start="([\d.]+)"[^>]*?\sdata-duration="([\d.]+)"/gi;
|
|
26051
26399
|
let maxEnd = 0;
|
|
26052
26400
|
let match;
|
|
@@ -26054,14 +26402,15 @@ function nextSceneStart(rootHtml) {
|
|
|
26054
26402
|
const end = parseFloat(match[1]) + parseFloat(match[2]);
|
|
26055
26403
|
if (Number.isFinite(end) && end > maxEnd) maxEnd = end;
|
|
26056
26404
|
}
|
|
26057
|
-
return maxEnd;
|
|
26405
|
+
return Math.max(0, maxEnd - overlap);
|
|
26058
26406
|
}
|
|
26059
26407
|
function buildClipReference(opts) {
|
|
26060
26408
|
const start = Number(opts.start.toFixed(3));
|
|
26061
26409
|
const duration = Number(opts.duration.toFixed(3));
|
|
26062
26410
|
const track = opts.trackIndex ?? 1;
|
|
26063
26411
|
const src = opts.src ?? `compositions/scene-${opts.id}.html`;
|
|
26064
|
-
|
|
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>`;
|
|
26065
26414
|
}
|
|
26066
26415
|
function insertClipIntoRoot(rootHtml, clip) {
|
|
26067
26416
|
const clipDiv = buildClipReference(clip);
|
|
@@ -26094,7 +26443,7 @@ function readRootDims(rootHtml) {
|
|
|
26094
26443
|
if (!widthMatch || !heightMatch) return null;
|
|
26095
26444
|
return { width: parseInt(widthMatch[1], 10), height: parseInt(heightMatch[1], 10) };
|
|
26096
26445
|
}
|
|
26097
|
-
var SCENE_PRESETS, GSAP_CDN;
|
|
26446
|
+
var SCENE_PRESETS, GSAP_CDN, SCENE_OVERLAP_SECONDS;
|
|
26098
26447
|
var init_scene_html_emit = __esm({
|
|
26099
26448
|
"src/commands/_shared/scene-html-emit.ts"() {
|
|
26100
26449
|
"use strict";
|
|
@@ -26106,6 +26455,7 @@ var init_scene_html_emit = __esm({
|
|
|
26106
26455
|
"product-shot"
|
|
26107
26456
|
];
|
|
26108
26457
|
GSAP_CDN = "https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js";
|
|
26458
|
+
SCENE_OVERLAP_SECONDS = 0.4;
|
|
26109
26459
|
}
|
|
26110
26460
|
});
|
|
26111
26461
|
|
|
@@ -26789,6 +27139,18 @@ function validatePreset(value) {
|
|
|
26789
27139
|
}
|
|
26790
27140
|
return value;
|
|
26791
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
|
+
}
|
|
26792
27154
|
async function pathExists2(p) {
|
|
26793
27155
|
try {
|
|
26794
27156
|
await access4(p);
|
|
@@ -27002,7 +27364,20 @@ async function executeSceneAdd(opts) {
|
|
|
27002
27364
|
}
|
|
27003
27365
|
const cfg = await loadVibeProjectConfig(projectDir);
|
|
27004
27366
|
const fallback2 = cfg?.defaultSceneDuration ?? 5;
|
|
27005
|
-
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));
|
|
27006
27381
|
opts.onProgress?.("Emitting scene HTML...");
|
|
27007
27382
|
const sceneHtml = emitSceneHtml({
|
|
27008
27383
|
id,
|
|
@@ -27020,8 +27395,10 @@ async function executeSceneAdd(opts) {
|
|
|
27020
27395
|
await mkdir4(dirname3(scenePath), { recursive: true });
|
|
27021
27396
|
await writeFile6(scenePath, sceneHtml, "utf-8");
|
|
27022
27397
|
opts.onProgress?.("Updating root composition...");
|
|
27023
|
-
const start = nextSceneStart(rootHtmlBefore);
|
|
27024
|
-
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 });
|
|
27025
27402
|
await writeFile6(rootPath, updated, "utf-8");
|
|
27026
27403
|
const transcriptAbsPath = transcriptRelPath ? resolve10(projectDir, transcriptRelPath) : void 0;
|
|
27027
27404
|
return {
|
|
@@ -27080,6 +27457,7 @@ var init_scene = __esm({
|
|
|
27080
27457
|
init_dist2();
|
|
27081
27458
|
init_tts_resolve();
|
|
27082
27459
|
init_scene_project();
|
|
27460
|
+
init_visual_styles();
|
|
27083
27461
|
init_scene_html_emit();
|
|
27084
27462
|
init_scene_lint();
|
|
27085
27463
|
init_scene_render();
|
|
@@ -27104,21 +27482,28 @@ Examples:
|
|
|
27104
27482
|
|
|
27105
27483
|
A scene project is bilingual: it works with both \`vibe\` and \`npx hyperframes\`.
|
|
27106
27484
|
Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
27107
|
-
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) => {
|
|
27108
27486
|
const aspect = validateAspect(options.ratio);
|
|
27109
27487
|
const duration = validateDuration(options.duration);
|
|
27110
27488
|
const name = options.name ?? basename4(dir.replace(/\/+$/, ""));
|
|
27489
|
+
const visualStyle = options.visualStyle ? validateVisualStyle(options.visualStyle) : void 0;
|
|
27111
27490
|
if (options.dryRun) {
|
|
27112
27491
|
outputResult({
|
|
27113
27492
|
dryRun: true,
|
|
27114
27493
|
command: "scene init",
|
|
27115
|
-
params: {
|
|
27494
|
+
params: {
|
|
27495
|
+
dir,
|
|
27496
|
+
name,
|
|
27497
|
+
aspect,
|
|
27498
|
+
duration,
|
|
27499
|
+
visualStyle: visualStyle?.name ?? null
|
|
27500
|
+
}
|
|
27116
27501
|
});
|
|
27117
27502
|
return;
|
|
27118
27503
|
}
|
|
27119
27504
|
const spinner2 = isJsonMode() ? null : ora(`Scaffolding scene project at ${dir}...`).start();
|
|
27120
27505
|
try {
|
|
27121
|
-
const result = await scaffoldSceneProject({ dir, name, aspect, duration });
|
|
27506
|
+
const result = await scaffoldSceneProject({ dir, name, aspect, duration, visualStyle });
|
|
27122
27507
|
if (isJsonMode()) {
|
|
27123
27508
|
outputResult({
|
|
27124
27509
|
success: true,
|
|
@@ -27127,6 +27512,7 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
|
27127
27512
|
name,
|
|
27128
27513
|
aspect,
|
|
27129
27514
|
duration,
|
|
27515
|
+
visualStyle: visualStyle?.name ?? null,
|
|
27130
27516
|
created: result.created,
|
|
27131
27517
|
merged: result.merged,
|
|
27132
27518
|
skipped: result.skipped
|
|
@@ -27143,7 +27529,13 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
|
27143
27529
|
console.log();
|
|
27144
27530
|
console.log(source_default.bold.cyan("Next steps"));
|
|
27145
27531
|
console.log(source_default.dim("\u2500".repeat(60)));
|
|
27146
|
-
|
|
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")}`);
|
|
27147
27539
|
console.log(` ${source_default.cyan("vibe scene lint")} ${source_default.dim("# validate HTML")}`);
|
|
27148
27540
|
console.log(` ${source_default.cyan("vibe scene render")} ${source_default.dim("# render to MP4")}`);
|
|
27149
27541
|
} catch (error) {
|
|
@@ -27152,6 +27544,69 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
|
27152
27544
|
exitWithError(generalError(`Failed to scaffold: ${msg}`));
|
|
27153
27545
|
}
|
|
27154
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
|
+
});
|
|
27155
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) => {
|
|
27156
27611
|
if (options.style) options.style = validatePreset(options.style);
|
|
27157
27612
|
if (options.duration !== void 0) options.duration = validateDuration(options.duration);
|
|
@@ -30822,7 +31277,12 @@ async function executeSegmentsToScenes(opts) {
|
|
|
30822
31277
|
const dims = aspectToDims(aspect);
|
|
30823
31278
|
const preset = opts.scenePreset ?? DEFAULT_PRESET;
|
|
30824
31279
|
const projectName = opts.projectName ?? basename7(outputDir);
|
|
30825
|
-
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
|
+
})();
|
|
30826
31286
|
const baseError = (error) => ({
|
|
30827
31287
|
success: false,
|
|
30828
31288
|
outputDir,
|
|
@@ -30895,9 +31355,10 @@ async function executeSegmentsToScenes(opts) {
|
|
|
30895
31355
|
id,
|
|
30896
31356
|
start: clipStart,
|
|
30897
31357
|
duration,
|
|
31358
|
+
trackIndex: i % 2 + 1,
|
|
30898
31359
|
src: `compositions/${id}.html`
|
|
30899
31360
|
});
|
|
30900
|
-
clipStart += duration;
|
|
31361
|
+
clipStart += duration - SCENE_OVERLAP_SECONDS;
|
|
30901
31362
|
}
|
|
30902
31363
|
await writeFile10(rootAbs, rootHtml, "utf-8");
|
|
30903
31364
|
let lintResult;
|
|
@@ -57538,7 +57999,7 @@ init_source();
|
|
|
57538
57999
|
// package.json
|
|
57539
58000
|
var package_default = {
|
|
57540
58001
|
name: "@vibeframe/cli",
|
|
57541
|
-
version: "0.
|
|
58002
|
+
version: "0.58.0",
|
|
57542
58003
|
description: "VibeFrame CLI - natural language video editing from the terminal",
|
|
57543
58004
|
private: false,
|
|
57544
58005
|
license: "MIT",
|
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"
|