@mevdragon/vidfarm-devcli 0.14.0 → 0.15.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/.agents/skills/embedded-captions/CATALOG.md +93 -0
- package/.agents/skills/embedded-captions/SKILL.md +286 -0
- package/.agents/skills/embedded-captions/assets/brand/CDPR-fankit-terms.txt +35 -0
- package/.agents/skills/embedded-captions/assets/brand/cyberpunk-widths.json +115 -0
- package/.agents/skills/embedded-captions/assets/fonts/char-widths.json +2211 -0
- package/.agents/skills/embedded-captions/assets/strokefonts/HersheyScript1.svg +242 -0
- package/.agents/skills/embedded-captions/assets/strokefonts/HersheyScriptMed.svg +242 -0
- package/.agents/skills/embedded-captions/dna/README.md +148 -0
- package/.agents/skills/embedded-captions/dna/chrome.json +68 -0
- package/.agents/skills/embedded-captions/dna/cream.json +69 -0
- package/.agents/skills/embedded-captions/dna/documentary.json +62 -0
- package/.agents/skills/embedded-captions/dna/editorial.json +70 -0
- package/.agents/skills/embedded-captions/dna/glitch.json +82 -0
- package/.agents/skills/embedded-captions/dna/ink.json +65 -0
- package/.agents/skills/embedded-captions/dna/keynote.json +62 -0
- package/.agents/skills/embedded-captions/dna/loud.json +75 -0
- package/.agents/skills/embedded-captions/dna/neon.json +58 -0
- package/.agents/skills/embedded-captions/dna/velocity.json +89 -0
- package/.agents/skills/embedded-captions/modes/cinematic/README.md +48 -0
- package/.agents/skills/embedded-captions/modes/cinematic/_archive/champion/spec.md +98 -0
- package/.agents/skills/embedded-captions/modes/cinematic/_archive/champion/template.html +188 -0
- package/.agents/skills/embedded-captions/modes/cinematic/_archive/memory-wall/spec.md +134 -0
- package/.agents/skills/embedded-captions/modes/cinematic/_archive/memory-wall/template.html +179 -0
- package/.agents/skills/embedded-captions/modes/cinematic/_archive/portrait-header/spec.md +81 -0
- package/.agents/skills/embedded-captions/modes/cinematic/_archive/portrait-header/template.html +170 -0
- package/.agents/skills/embedded-captions/modes/cinematic/cinematic-cream/spec.md +16 -0
- package/.agents/skills/embedded-captions/modes/cinematic/cinematic-cream/template.html +226 -0
- package/.agents/skills/embedded-captions/modes/cinematic/engine.html +355 -0
- package/.agents/skills/embedded-captions/modes/standard/_anatomy.md +233 -0
- package/.agents/skills/embedded-captions/modes/standard/_motion.md +192 -0
- package/.agents/skills/embedded-captions/modes/standard/fonts/build-fonts-css.cjs +97 -0
- package/.agents/skills/embedded-captions/references/aesthetic-principles.md +168 -0
- package/.agents/skills/embedded-captions/references/anti-patterns.md +188 -0
- package/.agents/skills/embedded-captions/references/bespoke-vs-presets.md +178 -0
- package/.agents/skills/embedded-captions/references/caption-grouping.md +130 -0
- package/.agents/skills/embedded-captions/references/composition-craft.md +522 -0
- package/.agents/skills/embedded-captions/references/direction-catalog.md +138 -0
- package/.agents/skills/embedded-captions/references/example-renders/champion.html +371 -0
- package/.agents/skills/embedded-captions/references/example-renders/memory-wall.html +334 -0
- package/.agents/skills/embedded-captions/references/failure-modes.md +145 -0
- package/.agents/skills/embedded-captions/references/layout-heuristics.md +238 -0
- package/.agents/skills/embedded-captions/references/motion-vocabulary.md +148 -0
- package/.agents/skills/embedded-captions/references/rail.md +68 -0
- package/.agents/skills/embedded-captions/references/reference-bar.md +47 -0
- package/.agents/skills/embedded-captions/references/scene-types.md +94 -0
- package/.agents/skills/embedded-captions/references/test-set.md +66 -0
- package/.agents/skills/embedded-captions/references/typographic-moves.md +266 -0
- package/.agents/skills/embedded-captions/references/typography-presets.md +63 -0
- package/.agents/skills/embedded-captions/scripts/audio-envelope.cjs +95 -0
- package/.agents/skills/embedded-captions/scripts/check-occlusion.cjs +250 -0
- package/.agents/skills/embedded-captions/scripts/check-overflow.cjs +194 -0
- package/.agents/skills/embedded-captions/scripts/check-rail-climax.cjs +233 -0
- package/.agents/skills/embedded-captions/scripts/check-timing.cjs +173 -0
- package/.agents/skills/embedded-captions/scripts/fill-timings.cjs +124 -0
- package/.agents/skills/embedded-captions/scripts/fit-fonts.cjs +158 -0
- package/.agents/skills/embedded-captions/scripts/gen-stroke-path.py +46 -0
- package/.agents/skills/embedded-captions/scripts/inject-fonts.cjs +169 -0
- package/.agents/skills/embedded-captions/scripts/lib-dna.cjs +201 -0
- package/.agents/skills/embedded-captions/scripts/make-cinematic.cjs +1205 -0
- package/.agents/skills/embedded-captions/scripts/make-composition.cjs +404 -0
- package/.agents/skills/embedded-captions/scripts/make-theme.cjs +8781 -0
- package/.agents/skills/embedded-captions/scripts/matte.cjs +294 -0
- package/.agents/skills/embedded-captions/scripts/measure-layout.cjs +293 -0
- package/.agents/skills/embedded-captions/scripts/prepare.sh +39 -0
- package/.agents/skills/embedded-captions/scripts/preview-frames.cjs +268 -0
- package/.agents/skills/embedded-captions/scripts/render-and-composite.sh +509 -0
- package/.agents/skills/embedded-captions/scripts/render-theme.sh +34 -0
- package/.agents/skills/embedded-captions/scripts/safe-zones.cjs +813 -0
- package/.agents/skills/embedded-captions/scripts/transcribe.cjs +349 -0
- package/.agents/skills/embedded-captions/themes/PORTING.md +49 -0
- package/.agents/skills/embedded-captions/themes/README.md +244 -0
- package/.agents/skills/embedded-captions/themes/anchor.json +27 -0
- package/.agents/skills/embedded-captions/themes/arcade.json +51 -0
- package/.agents/skills/embedded-captions/themes/aurora.json +41 -0
- package/.agents/skills/embedded-captions/themes/biolume.json +35 -0
- package/.agents/skills/embedded-captions/themes/brush.json +74 -0
- package/.agents/skills/embedded-captions/themes/chalkboard.json +52 -0
- package/.agents/skills/embedded-captions/themes/dossier.json +56 -0
- package/.agents/skills/embedded-captions/themes/graffiti.json +62 -0
- package/.agents/skills/embedded-captions/themes/hologram.json +41 -0
- package/.agents/skills/embedded-captions/themes/inkwater.json +43 -0
- package/.agents/skills/embedded-captions/themes/laser.json +46 -0
- package/.agents/skills/embedded-captions/themes/lastpage.json +33 -0
- package/.agents/skills/embedded-captions/themes/neonsign.json +53 -0
- package/.agents/skills/embedded-captions/themes/nightcity.json +56 -0
- package/.agents/skills/embedded-captions/themes/ordnance.json +54 -0
- package/.agents/skills/embedded-captions/themes/papercut.json +37 -0
- package/.agents/skills/embedded-captions/themes/popup.json +46 -0
- package/.agents/skills/embedded-captions/themes/ransom.json +62 -0
- package/.agents/skills/embedded-captions/themes/scoreboard.json +54 -0
- package/.agents/skills/embedded-captions/themes/spectrum.json +46 -0
- package/.agents/skills/embedded-captions/themes/stardust.json +29 -0
- package/.agents/skills/embedded-captions/themes/stomp.json +43 -0
- package/.agents/skills/embedded-captions/themes/terminal.json +49 -0
- package/.agents/skills/embedded-captions/themes/thunder.json +44 -0
- package/.agents/skills/embedded-captions/themes/transit.json +44 -0
- package/.agents/skills/embedded-captions/themes/vhs.json +47 -0
- package/.agents/skills/faceless-explainer/SKILL.md +209 -0
- package/.agents/skills/faceless-explainer/references/cut-catalog.md +215 -0
- package/.agents/skills/faceless-explainer/references/motion-language.md +156 -0
- package/.agents/skills/faceless-explainer/references/story-design.md +248 -0
- package/.agents/skills/faceless-explainer/references/visual-design.md +146 -0
- package/.agents/skills/faceless-explainer/scripts/assemble-index.mjs +570 -0
- package/.agents/skills/faceless-explainer/scripts/audio.mjs +260 -0
- package/.agents/skills/faceless-explainer/scripts/build-frame.mjs +536 -0
- package/.agents/skills/faceless-explainer/scripts/captions.mjs +508 -0
- package/.agents/skills/faceless-explainer/scripts/lib/assets.mjs +55 -0
- package/.agents/skills/faceless-explainer/scripts/lib/dimensions.mjs +45 -0
- package/.agents/skills/faceless-explainer/scripts/lib/storyboard.mjs +249 -0
- package/.agents/skills/faceless-explainer/scripts/lib/tokens.mjs +204 -0
- package/.agents/skills/faceless-explainer/scripts/lib/transition-registry.mjs +38 -0
- package/.agents/skills/faceless-explainer/scripts/lib/transitions.json +71 -0
- package/.agents/skills/faceless-explainer/scripts/transitions.mjs +320 -0
- package/.agents/skills/faceless-explainer/sub-agents/frame-worker.md +75 -0
- package/.agents/skills/flockposter/LICENSE +17 -0
- package/.agents/skills/flockposter/README.md +72 -0
- package/.agents/skills/flockposter/SKILL.md +562 -0
- package/.agents/skills/flockposter/examples/cli-instagram-reel.sh +21 -0
- package/.agents/skills/flockposter/examples/cli-multi-platform.json +69 -0
- package/.agents/skills/flockposter/examples/cli-tiktok-upload-only.sh +22 -0
- package/.agents/skills/flockposter/examples/cli-x-thread.sh +17 -0
- package/.agents/skills/flockposter/examples/cli-youtube-short.sh +21 -0
- package/.agents/skills/flockposter/examples/public-api-instagram-reel.json +28 -0
- package/.agents/skills/general-video/SKILL.md +143 -0
- package/.agents/skills/hyperframes/SKILL.md +160 -0
- package/.agents/skills/hyperframes-animation/SKILL.md +84 -0
- package/.agents/skills/hyperframes-animation/adapters/animate-text.md +64 -0
- package/.agents/skills/hyperframes-animation/adapters/animejs.md +114 -0
- package/.agents/skills/hyperframes-animation/adapters/css-animations.md +143 -0
- package/.agents/skills/hyperframes-animation/adapters/gsap-easing-and-stagger.md +118 -0
- package/.agents/skills/hyperframes-animation/adapters/gsap-timeline-and-labels.md +96 -0
- package/.agents/skills/hyperframes-animation/adapters/gsap-transforms-and-perf.md +96 -0
- package/.agents/skills/hyperframes-animation/adapters/gsap.md +105 -0
- package/.agents/skills/hyperframes-animation/adapters/html-in-canvas-patterns.md +507 -0
- package/.agents/skills/hyperframes-animation/adapters/lottie.md +117 -0
- package/.agents/skills/hyperframes-animation/adapters/three.md +131 -0
- package/.agents/skills/hyperframes-animation/adapters/typegpu.md +178 -0
- package/.agents/skills/hyperframes-animation/adapters/waapi.md +101 -0
- package/.agents/skills/hyperframes-animation/blueprints/comparison-split.md +27 -0
- package/.agents/skills/hyperframes-animation/blueprints/constellation-hub.md +44 -0
- package/.agents/skills/hyperframes-animation/blueprints/cta-morph-press.md +28 -0
- package/.agents/skills/hyperframes-animation/blueprints/cursor-ui-demo.md +57 -0
- package/.agents/skills/hyperframes-animation/blueprints/dataviz-countup.md +46 -0
- package/.agents/skills/hyperframes-animation/blueprints/device-surface-showcase.md +53 -0
- package/.agents/skills/hyperframes-animation/blueprints/grid-card-assemble.md +66 -0
- package/.agents/skills/hyperframes-animation/blueprints/kinetic-type-beats.md +78 -0
- package/.agents/skills/hyperframes-animation/blueprints/logo-assemble-lockup.md +69 -0
- package/.agents/skills/hyperframes-animation/blueprints/overwhelm-surround.md +28 -0
- package/.agents/skills/hyperframes-animation/blueprints/spatial-pan-stations.md +37 -0
- package/.agents/skills/hyperframes-animation/blueprints/ticker-takeover.md +29 -0
- package/.agents/skills/hyperframes-animation/blueprints/titlecard-reveal.md +42 -0
- package/.agents/skills/hyperframes-animation/blueprints/typewriter-reveal.md +51 -0
- package/.agents/skills/hyperframes-animation/blueprints/video-text-pivot.md +30 -0
- package/.agents/skills/hyperframes-animation/blueprints-index.md +146 -0
- package/.agents/skills/hyperframes-animation/examples/assets/avatars/02.avif +0 -0
- package/.agents/skills/hyperframes-animation/examples/assets/brands/github.avif +0 -0
- package/.agents/skills/hyperframes-animation/examples/assets/brands/nvidia.avif +0 -0
- package/.agents/skills/hyperframes-animation/examples/assets/brands/visa.avif +0 -0
- package/.agents/skills/hyperframes-animation/examples/assets/brands/zoominfo.avif +0 -0
- package/.agents/skills/hyperframes-animation/examples/brand-reveal-assemble-zoom.html +382 -0
- package/.agents/skills/hyperframes-animation/examples/comparison-split-cards.html +649 -0
- package/.agents/skills/hyperframes-animation/examples/concept-demo-decode-pan.html +520 -0
- package/.agents/skills/hyperframes-animation/examples/cta-morph-press.html +468 -0
- package/.agents/skills/hyperframes-animation/examples/cta-orbit-collapse.html +1298 -0
- package/.agents/skills/hyperframes-animation/examples/demo-page-scroll-spotlight.html +759 -0
- package/.agents/skills/hyperframes-animation/examples/hook-counter-burst.html +729 -0
- package/.agents/skills/hyperframes-animation/examples/messaging-multi-phrase.html +352 -0
- package/.agents/skills/hyperframes-animation/examples/metric-video-text-pivot.html +779 -0
- package/.agents/skills/hyperframes-animation/examples/problem-mockup-overwhelm.html +1374 -0
- package/.agents/skills/hyperframes-animation/examples/proof-logo-chain.html +861 -0
- package/.agents/skills/hyperframes-animation/examples/takeover-ticker-displace.html +347 -0
- package/.agents/skills/hyperframes-animation/examples/workflow-approve-press.html +606 -0
- package/.agents/skills/hyperframes-animation/rules/3d-page-scroll.md +227 -0
- package/.agents/skills/hyperframes-animation/rules/3d-text-depth-layers.md +297 -0
- package/.agents/skills/hyperframes-animation/rules/ai-tracking-box.md +382 -0
- package/.agents/skills/hyperframes-animation/rules/ambient-glow-bloom.md +305 -0
- package/.agents/skills/hyperframes-animation/rules/asr-keyword-glow.md +286 -0
- package/.agents/skills/hyperframes-animation/rules/avatar-cloud-network.md +371 -0
- package/.agents/skills/hyperframes-animation/rules/camera-cursor-tracking.md +246 -0
- package/.agents/skills/hyperframes-animation/rules/card-morph-anchor.md +267 -0
- package/.agents/skills/hyperframes-animation/rules/center-outward-expansion.md +227 -0
- package/.agents/skills/hyperframes-animation/rules/context-sensitive-cursor.md +257 -0
- package/.agents/skills/hyperframes-animation/rules/coordinate-target-zoom.md +332 -0
- package/.agents/skills/hyperframes-animation/rules/counting-dynamic-scale.md +283 -0
- package/.agents/skills/hyperframes-animation/rules/css-marker-patterns.md +373 -0
- package/.agents/skills/hyperframes-animation/rules/cursor-click-ripple.md +262 -0
- package/.agents/skills/hyperframes-animation/rules/depth-of-field-blur.md +313 -0
- package/.agents/skills/hyperframes-animation/rules/depth-scatter-assemble.md +303 -0
- package/.agents/skills/hyperframes-animation/rules/discrete-text-sequence.md +273 -0
- package/.agents/skills/hyperframes-animation/rules/dynamic-content-sequencing.md +307 -0
- package/.agents/skills/hyperframes-animation/rules/gsap-effects.md +299 -0
- package/.agents/skills/hyperframes-animation/rules/hacker-flip-3d.md +223 -0
- package/.agents/skills/hyperframes-animation/rules/kinetic-beat-slam.md +183 -0
- package/.agents/skills/hyperframes-animation/rules/motion-blur-streak.md +328 -0
- package/.agents/skills/hyperframes-animation/rules/multi-phase-camera.md +273 -0
- package/.agents/skills/hyperframes-animation/rules/orbit-3d-entry.md +301 -0
- package/.agents/skills/hyperframes-animation/rules/physics-press-reaction.md +350 -0
- package/.agents/skills/hyperframes-animation/rules/press-release-spring.md +296 -0
- package/.agents/skills/hyperframes-animation/rules/reactive-displacement.md +277 -0
- package/.agents/skills/hyperframes-animation/rules/scale-swap-transition.md +298 -0
- package/.agents/skills/hyperframes-animation/rules/sine-wave-loop.md +278 -0
- package/.agents/skills/hyperframes-animation/rules/split-tilt-cards.md +277 -0
- package/.agents/skills/hyperframes-animation/rules/spring-pop-entrance.md +273 -0
- package/.agents/skills/hyperframes-animation/rules/stat-bars-and-fills.md +156 -0
- package/.agents/skills/hyperframes-animation/rules/svg-icon-enrichment.md +329 -0
- package/.agents/skills/hyperframes-animation/rules/svg-path-draw.md +274 -0
- package/.agents/skills/hyperframes-animation/rules/vertical-spring-ticker.md +239 -0
- package/.agents/skills/hyperframes-animation/rules/viewport-change.md +349 -0
- package/.agents/skills/hyperframes-animation/rules-index.md +86 -0
- package/.agents/skills/hyperframes-animation/scripts/animation-map.mjs +606 -0
- package/.agents/skills/hyperframes-animation/scripts/package-loader.mjs +288 -0
- package/.agents/skills/hyperframes-animation/scripts/package-loader.test.mjs +62 -0
- package/.agents/skills/hyperframes-animation/techniques.md +507 -0
- package/.agents/skills/hyperframes-animation/transitions/TRANSITION-REGISTRY.md +167 -0
- package/.agents/skills/hyperframes-animation/transitions/catalog.md +127 -0
- package/.agents/skills/hyperframes-animation/transitions/css-3d.md +12 -0
- package/.agents/skills/hyperframes-animation/transitions/css-blur.md +51 -0
- package/.agents/skills/hyperframes-animation/transitions/css-cover.md +43 -0
- package/.agents/skills/hyperframes-animation/transitions/css-destruction.md +95 -0
- package/.agents/skills/hyperframes-animation/transitions/css-dissolve.md +66 -0
- package/.agents/skills/hyperframes-animation/transitions/css-distortion.md +45 -0
- package/.agents/skills/hyperframes-animation/transitions/css-grid.md +10 -0
- package/.agents/skills/hyperframes-animation/transitions/css-light.md +49 -0
- package/.agents/skills/hyperframes-animation/transitions/css-mechanical.md +30 -0
- package/.agents/skills/hyperframes-animation/transitions/css-other.md +25 -0
- package/.agents/skills/hyperframes-animation/transitions/css-push.md +41 -0
- package/.agents/skills/hyperframes-animation/transitions/css-radial.md +37 -0
- package/.agents/skills/hyperframes-animation/transitions/css-scale.md +24 -0
- package/.agents/skills/hyperframes-animation/transitions/overview.md +153 -0
- package/.agents/skills/hyperframes-cli/SKILL.md +113 -0
- package/.agents/skills/hyperframes-cli/references/doctor-browser.md +45 -0
- package/.agents/skills/hyperframes-cli/references/init-and-scaffold.md +55 -0
- package/.agents/skills/hyperframes-cli/references/lambda.md +132 -0
- package/.agents/skills/hyperframes-cli/references/lint-validate-inspect.md +121 -0
- package/.agents/skills/hyperframes-cli/references/preview-render.md +149 -0
- package/.agents/skills/hyperframes-cli/references/upgrade-info-misc.md +75 -0
- package/.agents/skills/hyperframes-core/SKILL.md +78 -0
- package/.agents/skills/hyperframes-core/references/composition-patterns.md +263 -0
- package/.agents/skills/hyperframes-core/references/data-attributes.md +70 -0
- package/.agents/skills/hyperframes-core/references/determinism-rules.md +68 -0
- package/.agents/skills/hyperframes-core/references/full-screen-motion.md +62 -0
- package/.agents/skills/hyperframes-core/references/minimal-composition.md +66 -0
- package/.agents/skills/hyperframes-core/references/script-format.md +49 -0
- package/.agents/skills/hyperframes-core/references/storyboard-format.md +95 -0
- package/.agents/skills/hyperframes-core/references/sub-compositions.md +237 -0
- package/.agents/skills/hyperframes-core/references/subagent-dispatch.md +41 -0
- package/.agents/skills/hyperframes-core/references/tailwind.md +125 -0
- package/.agents/skills/hyperframes-core/references/tracks-and-clips.md +76 -0
- package/.agents/skills/hyperframes-core/references/variables-and-media.md +90 -0
- package/.agents/skills/hyperframes-creative/SKILL.md +70 -0
- package/.agents/skills/hyperframes-creative/frame-presets/biennale-yellow/FRAME.md +289 -0
- package/.agents/skills/hyperframes-creative/frame-presets/biennale-yellow/caption-skin.html +221 -0
- package/.agents/skills/hyperframes-creative/frame-presets/biennale-yellow/frame-showcase.html +1214 -0
- package/.agents/skills/hyperframes-creative/frame-presets/blockframe/FRAME.md +275 -0
- package/.agents/skills/hyperframes-creative/frame-presets/blockframe/caption-skin.html +224 -0
- package/.agents/skills/hyperframes-creative/frame-presets/blockframe/frame-showcase.html +1347 -0
- package/.agents/skills/hyperframes-creative/frame-presets/blue-professional/FRAME.md +307 -0
- package/.agents/skills/hyperframes-creative/frame-presets/blue-professional/caption-skin.html +223 -0
- package/.agents/skills/hyperframes-creative/frame-presets/blue-professional/frame-showcase.html +1372 -0
- package/.agents/skills/hyperframes-creative/frame-presets/bold-poster/FRAME.md +275 -0
- package/.agents/skills/hyperframes-creative/frame-presets/bold-poster/caption-skin.html +230 -0
- package/.agents/skills/hyperframes-creative/frame-presets/bold-poster/frame-showcase.html +1140 -0
- package/.agents/skills/hyperframes-creative/frame-presets/broadside/FRAME.md +284 -0
- package/.agents/skills/hyperframes-creative/frame-presets/broadside/caption-skin.html +225 -0
- package/.agents/skills/hyperframes-creative/frame-presets/broadside/frame-showcase.html +1166 -0
- package/.agents/skills/hyperframes-creative/frame-presets/capsule/FRAME.md +284 -0
- package/.agents/skills/hyperframes-creative/frame-presets/capsule/caption-skin.html +224 -0
- package/.agents/skills/hyperframes-creative/frame-presets/capsule/frame-showcase.html +1465 -0
- package/.agents/skills/hyperframes-creative/frame-presets/cartesian/FRAME.md +278 -0
- package/.agents/skills/hyperframes-creative/frame-presets/cartesian/caption-skin.html +222 -0
- package/.agents/skills/hyperframes-creative/frame-presets/cartesian/frame-showcase.html +1263 -0
- package/.agents/skills/hyperframes-creative/frame-presets/claude/FRAME.md +277 -0
- package/.agents/skills/hyperframes-creative/frame-presets/claude/caption-skin.html +224 -0
- package/.agents/skills/hyperframes-creative/frame-presets/claude/frame-showcase.html +1439 -0
- package/.agents/skills/hyperframes-creative/frame-presets/cobalt-grid/FRAME.md +265 -0
- package/.agents/skills/hyperframes-creative/frame-presets/cobalt-grid/caption-skin.html +238 -0
- package/.agents/skills/hyperframes-creative/frame-presets/cobalt-grid/frame-showcase.html +1468 -0
- package/.agents/skills/hyperframes-creative/frame-presets/coral/FRAME.md +314 -0
- package/.agents/skills/hyperframes-creative/frame-presets/coral/caption-skin.html +226 -0
- package/.agents/skills/hyperframes-creative/frame-presets/coral/frame-showcase.html +1268 -0
- package/.agents/skills/hyperframes-creative/frame-presets/creative-mode/FRAME.md +333 -0
- package/.agents/skills/hyperframes-creative/frame-presets/creative-mode/caption-skin.html +227 -0
- package/.agents/skills/hyperframes-creative/frame-presets/creative-mode/frame-showcase.html +1330 -0
- package/.agents/skills/hyperframes-creative/frame-presets/daisy-days/FRAME.md +274 -0
- package/.agents/skills/hyperframes-creative/frame-presets/daisy-days/caption-skin.html +230 -0
- package/.agents/skills/hyperframes-creative/frame-presets/daisy-days/frame-showcase.html +1444 -0
- package/.agents/skills/hyperframes-creative/frame-presets/editorial-forest/FRAME.md +265 -0
- package/.agents/skills/hyperframes-creative/frame-presets/editorial-forest/caption-skin.html +223 -0
- package/.agents/skills/hyperframes-creative/frame-presets/editorial-forest/frame-showcase.html +1273 -0
- package/.agents/skills/hyperframes-creative/palettes/bold-energetic.md +14 -0
- package/.agents/skills/hyperframes-creative/palettes/clean-corporate.md +14 -0
- package/.agents/skills/hyperframes-creative/palettes/dark-premium.md +14 -0
- package/.agents/skills/hyperframes-creative/palettes/jewel-rich.md +14 -0
- package/.agents/skills/hyperframes-creative/palettes/monochrome.md +14 -0
- package/.agents/skills/hyperframes-creative/palettes/nature-earth.md +14 -0
- package/.agents/skills/hyperframes-creative/palettes/neon-electric.md +14 -0
- package/.agents/skills/hyperframes-creative/palettes/pastel-soft.md +14 -0
- package/.agents/skills/hyperframes-creative/palettes/warm-editorial.md +14 -0
- package/.agents/skills/hyperframes-creative/references/audio-reactive.md +76 -0
- package/.agents/skills/hyperframes-creative/references/beat-direction.md +164 -0
- package/.agents/skills/hyperframes-creative/references/composition-patterns.md +199 -0
- package/.agents/skills/hyperframes-creative/references/data-in-motion.md +19 -0
- package/.agents/skills/hyperframes-creative/references/design-adherence.md +19 -0
- package/.agents/skills/hyperframes-creative/references/design-picker.md +123 -0
- package/.agents/skills/hyperframes-creative/references/design-spec.md +55 -0
- package/.agents/skills/hyperframes-creative/references/house-style.md +73 -0
- package/.agents/skills/hyperframes-creative/references/motion-principles.md +150 -0
- package/.agents/skills/hyperframes-creative/references/narration.md +92 -0
- package/.agents/skills/hyperframes-creative/references/prompt-expansion.md +68 -0
- package/.agents/skills/hyperframes-creative/references/typography.md +218 -0
- package/.agents/skills/hyperframes-creative/references/video-composition.md +63 -0
- package/.agents/skills/hyperframes-creative/references/visual-styles.md +457 -0
- package/.agents/skills/hyperframes-creative/scripts/contrast-report.mjs +353 -0
- package/.agents/skills/hyperframes-creative/scripts/extract-audio-data.py +188 -0
- package/.agents/skills/hyperframes-creative/scripts/package-loader.mjs +288 -0
- package/.agents/skills/hyperframes-creative/scripts/package-loader.test.mjs +62 -0
- package/.agents/skills/hyperframes-creative/templates/design-picker.html +1432 -0
- package/.agents/skills/hyperframes-keyframes/SKILL.md +237 -0
- package/.agents/skills/hyperframes-keyframes/agents/openai.yaml +4 -0
- package/.agents/skills/hyperframes-keyframes/references/keyframe-patterns.md +106 -0
- package/.agents/skills/motion-graphics/SKILL.md +172 -0
- package/.agents/skills/motion-graphics/agents/builder.md +40 -0
- package/.agents/skills/motion-graphics/agents/director.md +53 -0
- package/.agents/skills/motion-graphics/agents/finalize.md +17 -0
- package/.agents/skills/motion-graphics/catalog-map.md +40 -0
- package/.agents/skills/motion-graphics/categories/asset-fusion/module.md +37 -0
- package/.agents/skills/motion-graphics/categories/charts/module.md +21 -0
- package/.agents/skills/motion-graphics/categories/kinetic-type/module.md +19 -0
- package/.agents/skills/motion-graphics/categories/logo-reveal/module.md +17 -0
- package/.agents/skills/motion-graphics/categories/lower-thirds/module.md +16 -0
- package/.agents/skills/motion-graphics/categories/maps/bake-basemap.mjs +273 -0
- package/.agents/skills/motion-graphics/categories/maps/module.md +61 -0
- package/.agents/skills/motion-graphics/categories/news/module.md +56 -0
- package/.agents/skills/motion-graphics/categories/stat/module.md +21 -0
- package/.agents/skills/motion-graphics/categories/tweet/module.md +16 -0
- package/.agents/skills/motion-graphics/categories/webpage/module.md +21 -0
- package/.agents/skills/motion-graphics/grounding/PROTOCOL.md +64 -0
- package/.agents/skills/motion-graphics/grounding/locate.mjs +227 -0
- package/.agents/skills/motion-graphics/phases/source/guide.md +21 -0
- package/.agents/skills/motion-graphics/references/builder-contract.md +38 -0
- package/.agents/skills/motion-graphics/references/motion-vocabulary.md +43 -0
- package/.agents/skills/motion-graphics/references/shot-plan-ir.md +54 -0
- package/.agents/skills/motion-graphics/samples/asset-fusion/_ref-circle-highlight.html +306 -0
- package/.agents/skills/product-launch-video/SKILL.md +208 -0
- package/.agents/skills/product-launch-video/references/cut-catalog.md +220 -0
- package/.agents/skills/product-launch-video/references/motion-language.md +156 -0
- package/.agents/skills/product-launch-video/references/story-design.md +373 -0
- package/.agents/skills/product-launch-video/references/visual-design.md +126 -0
- package/.agents/skills/product-launch-video/scripts/assemble-index.mjs +570 -0
- package/.agents/skills/product-launch-video/scripts/audio.mjs +260 -0
- package/.agents/skills/product-launch-video/scripts/build-frame.mjs +536 -0
- package/.agents/skills/product-launch-video/scripts/captions.mjs +508 -0
- package/.agents/skills/product-launch-video/scripts/lib/assets.mjs +55 -0
- package/.agents/skills/product-launch-video/scripts/lib/dimensions.mjs +45 -0
- package/.agents/skills/product-launch-video/scripts/lib/pad-frame-duration.mjs +36 -0
- package/.agents/skills/product-launch-video/scripts/lib/pad-frame-duration.test.mjs +76 -0
- package/.agents/skills/product-launch-video/scripts/lib/storyboard.mjs +249 -0
- package/.agents/skills/product-launch-video/scripts/lib/tokens.mjs +204 -0
- package/.agents/skills/product-launch-video/scripts/lib/transition-registry.mjs +38 -0
- package/.agents/skills/product-launch-video/scripts/lib/transitions.json +71 -0
- package/.agents/skills/product-launch-video/scripts/stage-assets.mjs +39 -0
- package/.agents/skills/product-launch-video/scripts/transitions.mjs +327 -0
- package/.agents/skills/product-launch-video/sub-agents/frame-worker.md +75 -0
- package/.agents/skills/slideshow/SKILL.md +550 -0
- package/.agents/skills/slideshow/references/standalone-harness.md +1020 -0
- package/.agents/skills/talking-head-recut/NOTICE.md +39 -0
- package/.agents/skills/talking-head-recut/SKILL.md +1195 -0
- package/.agents/skills/talking-head-recut/assets/vendor/gsap.min.js +10 -0
- package/.agents/skills/talking-head-recut/references/DESIGN_INDEX.md +145 -0
- package/.agents/skills/talking-head-recut/references/frames/clean.html +92 -0
- package/.agents/skills/talking-head-recut/references/frames/hairline.html +171 -0
- package/.agents/skills/talking-head-recut/references/frames/polaroid.html +131 -0
- package/.agents/skills/talking-head-recut/references/layouts/overlay.html +164 -0
- package/.agents/skills/talking-head-recut/references/layouts/pip.html +143 -0
- package/.agents/skills/talking-head-recut/references/layouts/split.html +121 -0
- package/.agents/skills/talking-head-recut/references/layouts/stack.html +109 -0
- package/.agents/skills/talking-head-recut/references/styles/academic.html +172 -0
- package/.agents/skills/talking-head-recut/references/styles/audit.html +170 -0
- package/.agents/skills/talking-head-recut/references/styles/editorial.html +189 -0
- package/.agents/skills/talking-head-recut/references/styles/geom.html +181 -0
- package/.agents/skills/talking-head-recut/references/styles/minimal.html +130 -0
- package/.agents/skills/talking-head-recut/references/styles/spotlight.html +142 -0
- package/.agents/skills/talking-head-recut/references/styles/swiss.html +188 -0
- package/.agents/skills/talking-head-recut/references/styles/terminal.html +196 -0
- package/.agents/skills/talking-head-recut/references/styles/whiteboard.html +170 -0
- package/.agents/skills/talking-head-recut/references/styles/xhs.html +206 -0
- package/.agents/skills/vidfarm-media/SKILL.md +85 -0
- package/.agents/skills/vidfarm-media/references/tts.md +54 -0
- package/.agents/skills/vidfarm-media/scripts/audio.mjs +628 -0
- package/.agents/skills/vidfarm-media/scripts/wait-bgm.mjs +31 -0
- package/.agents/skills/website-to-video/SKILL.md +145 -0
- package/.agents/skills/website-to-video/assets/sfx/CREDITS.md +35 -0
- package/.agents/skills/website-to-video/assets/sfx/manifest.json +97 -0
- package/.agents/skills/website-to-video/references/beat-builder-guide.md +263 -0
- package/.agents/skills/website-to-video/references/capabilities.md +713 -0
- package/.agents/skills/website-to-video/references/step-0-capture.md +55 -0
- package/.agents/skills/website-to-video/references/step-1-design.md +333 -0
- package/.agents/skills/website-to-video/references/step-2-brief.md +178 -0
- package/.agents/skills/website-to-video/references/step-3-storyboard.md +572 -0
- package/.agents/skills/website-to-video/references/step-4-vo.md +225 -0
- package/.agents/skills/website-to-video/references/step-5-build.md +483 -0
- package/.agents/skills/website-to-video/references/step-6-validate.md +341 -0
- package/.agents/skills/website-to-video/scripts/w2h-verify.mjs +759 -0
- package/SKILL.director.md +37 -0
- package/SKILL.platform.md +6 -1
- package/demo/dist/app.js +295 -11
- package/dist/src/app.js +317 -133
- package/dist/src/cli.js +529 -12
- package/dist/src/composition-runtime.js +262 -3
- package/dist/src/devcli/doctor.js +192 -0
- package/dist/src/devcli/hyperframes-cli.js +118 -0
- package/dist/src/devcli/skills.js +476 -0
- package/dist/src/devcli/stills.js +249 -0
- package/dist/src/editor-chat.js +255 -0
- package/dist/src/hyperframes/composition.js +5 -5
- package/dist/src/services/composition-lint.js +199 -0
- package/dist/src/services/hyperframes.js +67 -13
- package/package.json +2 -1
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// captions.mjs — build the captions sub-composition from STORYBOARD + audio_meta.
|
|
3
|
+
//
|
|
4
|
+
// One mode: `build`. Reads STORYBOARD.md (frame order + durations → cumulative
|
|
5
|
+
// frame starts) + audio_meta.json (voices[].words, frame-relative) → absolute-
|
|
6
|
+
// timed caption groups → writes:
|
|
7
|
+
// compositions/captions.html — a self-contained sub-composition the index
|
|
8
|
+
// assembler mounts on its captions track (data-composition-id="captions").
|
|
9
|
+
// caption_groups.json — the computed groups (debug / inspection / --out).
|
|
10
|
+
// caption-overrides.json — an empty `[]` shim (silences the captions runtime's
|
|
11
|
+
// validate-time fetch; only written when captions.html is).
|
|
12
|
+
// No narration / no words → legal skip: nothing written, assemble-index then omits
|
|
13
|
+
// the captions track (it keys off compositions/captions.html existence).
|
|
14
|
+
//
|
|
15
|
+
// node captions.mjs build --storyboard ./STORYBOARD.md --audio-meta ./audio_meta.json --hyperframes . --out ./caption_groups.json
|
|
16
|
+
//
|
|
17
|
+
// CAPTION LOOK — two sources, picked automatically:
|
|
18
|
+
// 1. PRESET SKIN (preferred). If a project-local `.hyperframes/caption-skin.html`
|
|
19
|
+
// exists (Step 2 copies the chosen frame-preset's skin into the project), it is
|
|
20
|
+
// the caption look.
|
|
21
|
+
// It is a brand-token-strict skin with three reserved holes; this script fills them
|
|
22
|
+
// and wraps the result in a <template> for the engine:
|
|
23
|
+
// - `var GROUPS = [];` → the computed caption groups
|
|
24
|
+
// - `var DURATION = 0;` + data-duration="0" (and data-width/height="0") → real values
|
|
25
|
+
// - `<style data-brand-tokens></style>` → :root tokens derived from the project's
|
|
26
|
+
// frame.md (colors + fonts), mapped to a fixed semantic vocab every skin shares:
|
|
27
|
+
// --cap-ink / --cap-canvas / --cap-accent / --cap-accent-2 / --font-display /
|
|
28
|
+
// --font-body, plus --cap-band-top / --cap-band-height (the keep-out band).
|
|
29
|
+
// So the brand-token overlay from Step 2 flows into the captions automatically.
|
|
30
|
+
// 2. DEFAULT (fallback). No skin file → the built-in Roboto/black pill (buildCaptionsHtml).
|
|
31
|
+
//
|
|
32
|
+
// Grouping mirrors the proven heuristics (frame boundary · sentence-end punct ·
|
|
33
|
+
// silence gap · density-aware word cap); word timings come inline from audio_meta.
|
|
34
|
+
|
|
35
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
36
|
+
import { dirname, join, resolve } from "node:path";
|
|
37
|
+
import { parseStoryboard } from "./lib/storyboard.mjs";
|
|
38
|
+
import { captionBand, parseFormat } from "./lib/dimensions.mjs";
|
|
39
|
+
import { parseColors, parseFonts, semanticColors, lum } from "./lib/tokens.mjs";
|
|
40
|
+
|
|
41
|
+
const flag = (argv, name, def) => {
|
|
42
|
+
const i = argv.indexOf(`--${name}`);
|
|
43
|
+
return i >= 0 && i + 1 < argv.length ? argv[i + 1] : def;
|
|
44
|
+
};
|
|
45
|
+
const r3 = (x) => Number(x.toFixed(3));
|
|
46
|
+
|
|
47
|
+
// ── grouping params ───────────────────────────────────────────────────────────
|
|
48
|
+
const SILENCE_GAP = 0.18; // s of silence between words → split
|
|
49
|
+
const TAIL_PAD = 0.12; // s the group lingers after its last word
|
|
50
|
+
const SENT_END = /[.?!,;:—]$/;
|
|
51
|
+
const DENSITY_WINDOW = 1.0; // s window for words/sec density
|
|
52
|
+
function wordCap(density) {
|
|
53
|
+
return density > 3.5 ? 2 : density > 2.5 ? 3 : 4;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function runBuild(argv) {
|
|
57
|
+
const skip = (reason) => {
|
|
58
|
+
console.log(`captions: skipped (${reason})`);
|
|
59
|
+
process.exit(0);
|
|
60
|
+
};
|
|
61
|
+
const die = (m) => {
|
|
62
|
+
console.error(`✗ captions build: ${m}`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const hyperframesDir = resolve(flag(argv, "hyperframes", "."));
|
|
67
|
+
const storyboardPath = resolve(flag(argv, "storyboard", join(hyperframesDir, "STORYBOARD.md")));
|
|
68
|
+
const audioMetaPath = resolve(flag(argv, "audio-meta", join(hyperframesDir, "audio_meta.json")));
|
|
69
|
+
const outPath = resolve(flag(argv, "out", join(hyperframesDir, "caption_groups.json")));
|
|
70
|
+
const htmlPath = join(hyperframesDir, "compositions/captions.html");
|
|
71
|
+
const overridesPath = join(hyperframesDir, "caption-overrides.json");
|
|
72
|
+
const skinArg = flag(argv, "skin", null);
|
|
73
|
+
const hiddenSkinPath = join(hyperframesDir, ".hyperframes", "caption-skin.html");
|
|
74
|
+
const legacySkinPath = join(hyperframesDir, "caption-skin.html");
|
|
75
|
+
const skinPath = resolve(
|
|
76
|
+
skinArg ?? (existsSync(hiddenSkinPath) ? hiddenSkinPath : legacySkinPath),
|
|
77
|
+
);
|
|
78
|
+
const framePath = resolve(flag(argv, "frame", join(hyperframesDir, "frame.md")));
|
|
79
|
+
|
|
80
|
+
if (!existsSync(storyboardPath)) die(`STORYBOARD.md not found at ${storyboardPath}`);
|
|
81
|
+
const manifest = parseStoryboard(readFileSync(storyboardPath, "utf8"));
|
|
82
|
+
const { width: W, height: H } = parseFormat(manifest.globals.format);
|
|
83
|
+
|
|
84
|
+
if (!existsSync(audioMetaPath)) skip("no audio_meta.json (silent film)");
|
|
85
|
+
const meta = JSON.parse(readFileSync(audioMetaPath, "utf8"));
|
|
86
|
+
if (!Array.isArray(meta.voices) || meta.voices.length === 0) skip("no narration");
|
|
87
|
+
|
|
88
|
+
// cumulative frame starts (by frame number) + total duration, from STORYBOARD.
|
|
89
|
+
const startByFrame = new Map();
|
|
90
|
+
let acc = 0;
|
|
91
|
+
for (const f of manifest.frames) {
|
|
92
|
+
if (f.number != null) startByFrame.set(f.number, acc);
|
|
93
|
+
acc += Number.isFinite(f.durationSeconds) ? f.durationSeconds : 0;
|
|
94
|
+
}
|
|
95
|
+
const total = r3(acc);
|
|
96
|
+
|
|
97
|
+
// absolute word stream: frame start + frame-relative word timing.
|
|
98
|
+
const words = [];
|
|
99
|
+
for (const v of meta.voices) {
|
|
100
|
+
const base = startByFrame.get(v.frame);
|
|
101
|
+
if (base == null || !Array.isArray(v.words)) continue;
|
|
102
|
+
for (const w of v.words) {
|
|
103
|
+
const text = String(w.text ?? "").trim();
|
|
104
|
+
if (!text || /^[.?!,;:—–-]+$/.test(text)) continue; // drop empties + bare punctuation
|
|
105
|
+
if (!isFinite(w.start) || !isFinite(w.end)) continue;
|
|
106
|
+
words.push({ text, start: r3(base + w.start), end: r3(base + w.end), frame: v.frame });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
words.sort((a, b) => a.start - b.start);
|
|
110
|
+
if (words.length === 0) skip("no usable words");
|
|
111
|
+
|
|
112
|
+
// density at i = words whose start falls within [w.start, w.start + WINDOW).
|
|
113
|
+
const densityAt = (i) => {
|
|
114
|
+
const t0 = words[i].start;
|
|
115
|
+
let n = 0;
|
|
116
|
+
for (let j = i; j < words.length && words[j].start < t0 + DENSITY_WINDOW; j++) n++;
|
|
117
|
+
return n / DENSITY_WINDOW;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// group: split on frame change / silence gap / word cap; always flush after a
|
|
121
|
+
// sentence-ending word.
|
|
122
|
+
const groups = [];
|
|
123
|
+
let cur = null;
|
|
124
|
+
for (let i = 0; i < words.length; i++) {
|
|
125
|
+
const w = words[i];
|
|
126
|
+
const prev = cur && cur.words[cur.words.length - 1];
|
|
127
|
+
const crossFrame = cur && w.frame !== cur.frame;
|
|
128
|
+
const gap = prev && w.start - prev.end > SILENCE_GAP;
|
|
129
|
+
const full = cur && cur.words.length >= cur.cap;
|
|
130
|
+
if (!cur || crossFrame || gap || full) {
|
|
131
|
+
if (cur) groups.push(cur);
|
|
132
|
+
cur = { frame: w.frame, cap: wordCap(densityAt(i)), words: [] };
|
|
133
|
+
}
|
|
134
|
+
cur.words.push(w);
|
|
135
|
+
if (SENT_END.test(w.text)) {
|
|
136
|
+
groups.push(cur);
|
|
137
|
+
cur = null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (cur) groups.push(cur);
|
|
141
|
+
|
|
142
|
+
// finalize: ids, start/end (tail-padded, clamped < next group's start), text.
|
|
143
|
+
const finalized = groups.map((g, gi) => {
|
|
144
|
+
const first = g.words[0];
|
|
145
|
+
const last = g.words[g.words.length - 1];
|
|
146
|
+
const next = groups[gi + 1];
|
|
147
|
+
let end = r3(last.end + TAIL_PAD);
|
|
148
|
+
if (next && next.words[0].start < end) end = r3(next.words[0].start);
|
|
149
|
+
return {
|
|
150
|
+
id: `caption-group-${gi}`,
|
|
151
|
+
frame: g.frame,
|
|
152
|
+
start: r3(first.start),
|
|
153
|
+
end,
|
|
154
|
+
text: g.words.map((w) => w.text).join(" "),
|
|
155
|
+
words: g.words.map((w, wi) => ({
|
|
156
|
+
id: `caption-word-${gi}-${wi}`,
|
|
157
|
+
text: w.text,
|
|
158
|
+
start: r3(w.start),
|
|
159
|
+
end: r3(w.end),
|
|
160
|
+
})),
|
|
161
|
+
};
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// ── write caption_groups.json ──
|
|
165
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
166
|
+
writeFileSync(
|
|
167
|
+
outPath,
|
|
168
|
+
JSON.stringify({ total_duration_s: total, width: W, height: H, groups: finalized }, null, 2),
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
// ── write compositions/captions.html (preset skin if present, else default) ──
|
|
172
|
+
mkdirSync(dirname(htmlPath), { recursive: true });
|
|
173
|
+
let source;
|
|
174
|
+
if (existsSync(skinPath)) {
|
|
175
|
+
const tokens = frameTokensCss(framePath, H);
|
|
176
|
+
const faces = brandFontFaces(framePath, hyperframesDir);
|
|
177
|
+
const fonts = existsSync(framePath) ? parseFonts(readFileSync(framePath, "utf8")) : {};
|
|
178
|
+
writeFileSync(
|
|
179
|
+
htmlPath,
|
|
180
|
+
buildFromSkin(
|
|
181
|
+
readFileSync(skinPath, "utf8"),
|
|
182
|
+
finalized,
|
|
183
|
+
total,
|
|
184
|
+
W,
|
|
185
|
+
H,
|
|
186
|
+
tokens,
|
|
187
|
+
die,
|
|
188
|
+
faces,
|
|
189
|
+
fonts,
|
|
190
|
+
),
|
|
191
|
+
);
|
|
192
|
+
source = `preset skin (${skinPath.replace(hyperframesDir + "/", "")})`;
|
|
193
|
+
} else {
|
|
194
|
+
writeFileSync(htmlPath, buildCaptionsHtml(finalized, total, W, H));
|
|
195
|
+
source = "default (built-in pill)";
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ── write caption-overrides.json shim ──
|
|
199
|
+
// Atomic create-if-absent: `wx` throws if the file already exists (which we
|
|
200
|
+
// ignore) — no existsSync→writeFileSync TOCTOU gap.
|
|
201
|
+
try {
|
|
202
|
+
writeFileSync(overridesPath, "[]\n", { flag: "wx" });
|
|
203
|
+
} catch {
|
|
204
|
+
/* overrides shim already present */
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
console.log(
|
|
208
|
+
`✓ captions build: ${finalized.length} group(s) from ${words.length} words → compositions/captions.html (total ${total}s) · skin: ${source}`,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ── preset-skin path ────────────────────────────────────────────────────────
|
|
213
|
+
// Fill the skin's three reserved holes + the root's 0-placeholders, then wrap the
|
|
214
|
+
// fragment in a <template> (the engine clones template contents only). One generic
|
|
215
|
+
// fill works for every preset's skin — no per-skin transform.
|
|
216
|
+
//
|
|
217
|
+
// Every preset's skin is authored against ITS OWN fonts/metrics (broadside→Barlow @
|
|
218
|
+
// line-height 1.02, capsule→Bodoni, …). When the project's brand font differs (it
|
|
219
|
+
// almost always does), three things must be reconciled so ANY skin renders correctly
|
|
220
|
+
// for ANY brand — done here generically, not per-project:
|
|
221
|
+
// · @font-face for the brand fonts (else the renderer can't supply them → fallback)
|
|
222
|
+
// · the skin's preset-font FALLBACK literals (var(--font-x, "Barlow")) repointed to
|
|
223
|
+
// the brand family, so no undeclared font name trips font_family_without_font_face
|
|
224
|
+
// · a metric safety net: a heavier brand font overflows a tight preset line-height,
|
|
225
|
+
// so the active-word highlight clips — a line-height floor + word padding fixes it
|
|
226
|
+
// · data-composition-id + dimensions on the <template> root (skins lead with
|
|
227
|
+
// <script>/<style>, so the root element must carry the id, not the first child)
|
|
228
|
+
function buildFromSkin(skin, groups, total, W, H, tokens, die, faces = "", fonts = {}) {
|
|
229
|
+
const fillOnce = (src, re, repl, label) => {
|
|
230
|
+
const n = (src.match(re) || []).length;
|
|
231
|
+
if (n !== 1) die(`caption-skin.html: expected exactly one ${label}, found ${n}`);
|
|
232
|
+
return src.replace(re, () => repl);
|
|
233
|
+
};
|
|
234
|
+
let out = skin;
|
|
235
|
+
// Strip HTML doc-comments first. A skin's authoring comment can contain tag-like text
|
|
236
|
+
// (broadside's literally says "<template>"), which the linter's tag scanner then picks
|
|
237
|
+
// up as the root element → false root_missing_composition_id / root_missing_dimensions.
|
|
238
|
+
// The comments are preview/authoring docs, not needed in the generated composition.
|
|
239
|
+
// Strip in a fixpoint loop, not a single global pass: removing one comment can
|
|
240
|
+
// re-form a marker from a nested/partial pair (e.g. <!--<!---->-->), which one
|
|
241
|
+
// pass misses — CodeQL flags the single replace as incomplete sanitization.
|
|
242
|
+
for (let prev = ""; prev !== out; ) {
|
|
243
|
+
prev = out;
|
|
244
|
+
out = out.replace(/<!--[\s\S]*?-->/g, "");
|
|
245
|
+
}
|
|
246
|
+
// brand :root tokens + @font-face for the brand fonts, both into the reserved hole
|
|
247
|
+
out = fillOnce(
|
|
248
|
+
out,
|
|
249
|
+
/<style data-brand-tokens>\s*<\/style>/,
|
|
250
|
+
`<style data-brand-tokens>\n${faces ? faces + "\n" : ""}${tokens}\n </style>`,
|
|
251
|
+
"<style data-brand-tokens></style> hole",
|
|
252
|
+
);
|
|
253
|
+
// Resolve the skin's font-family var()s to the brand family LITERAL. Two reasons:
|
|
254
|
+
// (1) the linter's used-font scanner naively comma-splits, so var(--x, "Brand") yields
|
|
255
|
+
// junk tokens ('var(--x', 'brand")') that never match the @font-face → a false
|
|
256
|
+
// font_family_without_font_face; a plain "Brand" literal matches the @font-face.
|
|
257
|
+
// (2) it drops the preset's own fallback name (Barlow / IBM Plex Mono / …), which has
|
|
258
|
+
// no @font-face in this project. The :root token stays for any other consumer.
|
|
259
|
+
if (fonts.display)
|
|
260
|
+
out = out.replace(/var\(\s*--font-display\s*(?:,\s*"[^"]*"\s*)?\)/g, fonts.display);
|
|
261
|
+
if (fonts.body) out = out.replace(/var\(\s*--font-body\s*(?:,\s*"[^"]*"\s*)?\)/g, fonts.body);
|
|
262
|
+
out = fillOnce(
|
|
263
|
+
out,
|
|
264
|
+
/var GROUPS = \[\];/,
|
|
265
|
+
`var GROUPS = ${JSON.stringify(groups)};`,
|
|
266
|
+
"`var GROUPS = [];` hole",
|
|
267
|
+
);
|
|
268
|
+
out = fillOnce(out, /var DURATION = 0;/, `var DURATION = ${total};`, "`var DURATION = 0;` hole");
|
|
269
|
+
out = fillOnce(out, /data-duration="0"/, `data-duration="${total}"`, '`data-duration="0"` hole');
|
|
270
|
+
out = fillOnce(out, /data-width="0"/, `data-width="${W}"`, '`data-width="0"` hole');
|
|
271
|
+
out = fillOnce(out, /data-height="0"/, `data-height="${H}"`, '`data-height="0"` hole');
|
|
272
|
+
// font-robust safety net — appended last so it wins the cascade over the skin's own
|
|
273
|
+
// (preset-font-tuned) line-height. Kept SNUG (1.1) so the plate hugs the text. NO extra
|
|
274
|
+
// word/pill padding: inspect's `text_box_overflow` on the highlight words is a cosmetic
|
|
275
|
+
// false-positive here (heavy-glyph ink slightly exceeds the line box, but there's no
|
|
276
|
+
// overflow:hidden — nothing is clipped); zeroing it would need an airy line-height that
|
|
277
|
+
// balloons the pill, which is worse. Override only if a brand font genuinely clips.
|
|
278
|
+
out += "\n<style>\n .caption-line { line-height: 1.1 !important; }\n</style>";
|
|
279
|
+
// dark-ground caption contrast (auto). The preset caption skins are tuned for a LIGHT
|
|
280
|
+
// pill (cream); on a dark brand ground the pill goes near-black, so the skin's faint
|
|
281
|
+
// upcoming-word mix and light highlight block turn unreadable. When the resolved caption
|
|
282
|
+
// canvas is dark, override the three word states: a brighter muted upcoming color + an
|
|
283
|
+
// on-brand ACCENT highlight block with light text. Light grounds are left untouched.
|
|
284
|
+
const capCanvas = (tokens.match(/--cap-canvas:\s*(#[0-9a-fA-F]{6})/) || [])[1];
|
|
285
|
+
if (capCanvas && (lum(capCanvas) ?? 255) < 90) {
|
|
286
|
+
out +=
|
|
287
|
+
"\n<style>\n" +
|
|
288
|
+
" .caption-word { color: color-mix(in srgb, var(--cap-ink) 64%, var(--cap-canvas)); }\n" +
|
|
289
|
+
" .caption-word.is-active { color: var(--cap-ink); background: var(--cap-accent); box-shadow: 0 0 0 0.06em var(--cap-accent); }\n" +
|
|
290
|
+
" .caption-word.is-spoken { color: var(--cap-ink); background: transparent; box-shadow: none; }\n" +
|
|
291
|
+
"</style>";
|
|
292
|
+
}
|
|
293
|
+
return `<template id="captions-template" data-composition-id="captions" data-width="${W}" data-height="${H}">\n${out.trim()}\n</template>\n`;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// @font-face for the brand display/body fonts, matched from the project's font dirs
|
|
297
|
+
// (staged assets/fonts first, else capture/assets/fonts) by family-name prefix, with
|
|
298
|
+
// weight parsed from the filename. Paths are relative to compositions/captions.html.
|
|
299
|
+
// Returns "" when frame.md or font files are absent (then the skin's fallback applies).
|
|
300
|
+
function brandFontFaces(framePath, hyperframesDir) {
|
|
301
|
+
if (!existsSync(framePath)) return "";
|
|
302
|
+
const { display, body } = parseFonts(readFileSync(framePath, "utf8"));
|
|
303
|
+
const families = [
|
|
304
|
+
...new Set([display, body].filter(Boolean).map((f) => f.replace(/^"|"$/g, ""))),
|
|
305
|
+
];
|
|
306
|
+
if (!families.length) return "";
|
|
307
|
+
const dirs = [
|
|
308
|
+
// ROOT-RELATIVE — compositions are served with the project root as their base URL, so a
|
|
309
|
+
// "../" prefix escapes the root (lint: invalid_parent_traversal_in_asset_path) and 404s in
|
|
310
|
+
// Studio/preview. Mirror what the frame workers use for images.
|
|
311
|
+
{ abs: join(hyperframesDir, "assets/fonts"), rel: "assets/fonts" },
|
|
312
|
+
{ abs: join(hyperframesDir, "capture/assets/fonts"), rel: "capture/assets/fonts" },
|
|
313
|
+
].filter((d) => existsSync(d.abs));
|
|
314
|
+
const weightOf = (n) => {
|
|
315
|
+
const s = n.toLowerCase();
|
|
316
|
+
if (/black|heavy|ultra|extrabold/.test(s)) return 800;
|
|
317
|
+
if (/semibold|demibold/.test(s)) return 600; // before /bold/ — "demibold" contains "bold"
|
|
318
|
+
if (/bold/.test(s)) return 700;
|
|
319
|
+
if (/medium/.test(s)) return 500;
|
|
320
|
+
if (/light|thin/.test(s)) return 300;
|
|
321
|
+
return 400; // book / regular / roman
|
|
322
|
+
};
|
|
323
|
+
const fmtOf = (f) =>
|
|
324
|
+
/\.woff2$/i.test(f)
|
|
325
|
+
? "woff2"
|
|
326
|
+
: /\.woff$/i.test(f)
|
|
327
|
+
? "woff"
|
|
328
|
+
: /\.ttf$/i.test(f)
|
|
329
|
+
? "truetype"
|
|
330
|
+
: "opentype";
|
|
331
|
+
// Normalize away ALL non-alphanumerics (spaces, underscores, hyphens) on BOTH the
|
|
332
|
+
// family name and the filename. Real font files use "_" / "-" as word separators
|
|
333
|
+
// ("TT_Norms_Pro_Bold.woff2"), so stripping only whitespace never matched them — the
|
|
334
|
+
// family key "ttnormspro" failed `startsWith` against "tt_norms_pro_bold", and the
|
|
335
|
+
// function silently returned "" → captions shipped with NO @font-face for any
|
|
336
|
+
// underscore/hyphen-named brand font (e.g. TT Norms Pro), which is exactly the
|
|
337
|
+
// font_family_without_font_face bug.
|
|
338
|
+
const norm = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
339
|
+
const faces = [];
|
|
340
|
+
const seen = new Set();
|
|
341
|
+
const claimed = new Set(); // each file is claimed by the MOST SPECIFIC family only
|
|
342
|
+
// Match the longest family key first so "TT Norms Pro" can't swallow the files that
|
|
343
|
+
// belong to "TT Norms Pro Mono" (its key is a prefix of the longer one's).
|
|
344
|
+
const ranked = [...families].sort((a, b) => norm(b).length - norm(a).length);
|
|
345
|
+
for (const fam of ranked) {
|
|
346
|
+
const key = norm(fam);
|
|
347
|
+
for (const d of dirs) {
|
|
348
|
+
let files = [];
|
|
349
|
+
try {
|
|
350
|
+
files = readdirSync(d.abs);
|
|
351
|
+
} catch {
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
for (const f of files.sort()) {
|
|
355
|
+
if (!/\.(woff2|woff|ttf|otf)$/i.test(f)) continue;
|
|
356
|
+
if (claimed.has(f)) continue; // a more specific family already took this file
|
|
357
|
+
if (!norm(f.replace(/\.(woff2|woff|ttf|otf)$/i, "")).startsWith(key)) continue;
|
|
358
|
+
const w = weightOf(f);
|
|
359
|
+
const dedup = `${fam}-${w}`;
|
|
360
|
+
if (seen.has(dedup)) continue; // one src per weight; assets/fonts wins over capture
|
|
361
|
+
seen.add(dedup);
|
|
362
|
+
claimed.add(f);
|
|
363
|
+
faces.push(
|
|
364
|
+
` @font-face { font-family: '${fam}'; src: url('${d.rel}/${f}') format('${fmtOf(f)}'); font-weight: ${w}; font-display: block; }`,
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
// Loud signal instead of a silent "". If frame.md named a brand font but no file
|
|
370
|
+
// matched, the caption text WILL fall back to a generic font in the render — surface
|
|
371
|
+
// the cause here (at build time) rather than letting it surface 2 steps later as a
|
|
372
|
+
// font_family_without_font_face lint error disconnected from its root cause.
|
|
373
|
+
if (!faces.length) {
|
|
374
|
+
const where = dirs.length
|
|
375
|
+
? dirs.map((d) => d.rel).join(" / ")
|
|
376
|
+
: "assets/fonts or capture/assets/fonts (neither exists)";
|
|
377
|
+
console.warn(
|
|
378
|
+
` ⚠ captions: frame.md names font ${families.map((f) => `"${f}"`).join(", ")} ` +
|
|
379
|
+
`but no matching .woff2/.woff/.ttf/.otf was found in ${where} — captions will fall back ` +
|
|
380
|
+
`(text may render in the wrong font). Stage a font file whose name starts with the family ` +
|
|
381
|
+
`(e.g. "TT Norms Pro" → TT_Norms_Pro_Bold.woff2) so it ships with the project.`,
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
return faces.join("\n");
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// frame.md colors:/typography: → a :root token block, mapped to the fixed semantic
|
|
388
|
+
// vocab every preset skin references. Robust to per-preset key names: colors are
|
|
389
|
+
// matched by name, then by luminance. Brand-token overlay (Step 2) flows through
|
|
390
|
+
// because the values come from the project's frame.md. No frame.md → band vars only.
|
|
391
|
+
function frameTokensCss(framePath, H) {
|
|
392
|
+
const band = captionBand(H);
|
|
393
|
+
const out = [];
|
|
394
|
+
if (existsSync(framePath)) {
|
|
395
|
+
const md = readFileSync(framePath, "utf8");
|
|
396
|
+
const colors = parseColors(md);
|
|
397
|
+
for (const [k, v] of colors) out.push(` --${k}: ${v};`); // raw, for completeness
|
|
398
|
+
const sem = semanticColors(colors);
|
|
399
|
+
if (sem.ink) out.push(` --cap-ink: ${sem.ink};`);
|
|
400
|
+
if (sem.canvas) out.push(` --cap-canvas: ${sem.canvas};`);
|
|
401
|
+
if (sem.accent) out.push(` --cap-accent: ${sem.accent};`);
|
|
402
|
+
if (sem.accent2) out.push(` --cap-accent-2: ${sem.accent2};`);
|
|
403
|
+
const { display, body } = parseFonts(md);
|
|
404
|
+
if (display) out.push(` --font-display: ${display}, system-ui, serif;`);
|
|
405
|
+
if (body) out.push(` --font-body: ${body}, system-ui, sans-serif;`);
|
|
406
|
+
}
|
|
407
|
+
out.push(` --cap-band-top: ${band.bandTopY}px;`);
|
|
408
|
+
out.push(` --cap-band-height: ${band.bandHeight}px;`);
|
|
409
|
+
return ` :root {\n${out.join("\n")}\n }`;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ── default path (no preset skin) ─────────────────────────────────────────────
|
|
413
|
+
// Self-contained captions sub-composition. The <template> holds the band container
|
|
414
|
+
// + style AND the <script> (the HyperFrames loader only executes scripts INSIDE the
|
|
415
|
+
// cloned template — a sibling <script> after </template> never runs, so the timeline
|
|
416
|
+
// never registers and captions render blank). The script builds per-word spans and a
|
|
417
|
+
// paused, seek-safe GSAP timeline (opacity for group show/hide, a quick color tween
|
|
418
|
+
// per word for the karaoke highlight — no className flips, no JS state) and ends each
|
|
419
|
+
// group with a hard tl.set kill so an exit can't get stuck. gsap is loaded via CDN
|
|
420
|
+
// inside the template (matching the frame compositions). Band = captionBand(H).
|
|
421
|
+
function buildCaptionsHtml(groups, total, W, H) {
|
|
422
|
+
const band = captionBand(H);
|
|
423
|
+
const fs = Math.round(H * 0.038);
|
|
424
|
+
const pad = Math.round(fs * 0.4);
|
|
425
|
+
return `<template id="captions-template">
|
|
426
|
+
<div
|
|
427
|
+
data-composition-id="captions"
|
|
428
|
+
data-width="${W}"
|
|
429
|
+
data-height="${H}"
|
|
430
|
+
data-duration="${total}"
|
|
431
|
+
id="captions-root"
|
|
432
|
+
>
|
|
433
|
+
<div id="cap"></div>
|
|
434
|
+
</div>
|
|
435
|
+
<style>
|
|
436
|
+
#captions-root {
|
|
437
|
+
position: absolute;
|
|
438
|
+
inset: 0;
|
|
439
|
+
pointer-events: none;
|
|
440
|
+
}
|
|
441
|
+
#cap {
|
|
442
|
+
position: absolute;
|
|
443
|
+
left: 0;
|
|
444
|
+
right: 0;
|
|
445
|
+
top: ${band.bandTopY}px;
|
|
446
|
+
height: ${band.bandHeight}px;
|
|
447
|
+
display: flex;
|
|
448
|
+
align-items: center;
|
|
449
|
+
justify-content: center;
|
|
450
|
+
}
|
|
451
|
+
.caption-group {
|
|
452
|
+
position: absolute;
|
|
453
|
+
max-width: 80%;
|
|
454
|
+
padding: ${pad}px ${Math.round(pad * 1.8)}px;
|
|
455
|
+
background: rgba(0, 0, 0, 0.72);
|
|
456
|
+
border-radius: ${Math.round(fs * 0.3)}px;
|
|
457
|
+
font-family: Roboto, sans-serif;
|
|
458
|
+
font-weight: 700;
|
|
459
|
+
font-size: ${fs}px;
|
|
460
|
+
line-height: 1.25;
|
|
461
|
+
text-align: center;
|
|
462
|
+
color: #fff;
|
|
463
|
+
opacity: 0;
|
|
464
|
+
}
|
|
465
|
+
.caption-word {
|
|
466
|
+
color: rgba(255, 255, 255, 0.55);
|
|
467
|
+
}
|
|
468
|
+
</style>
|
|
469
|
+
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js" integrity="sha384-sG0Hv1tP1lZCk9KQmrIbY/XNwi+OY84GQqhMscbnsoBFqAz8KNCil1kvfL3Hbbk2" crossorigin="anonymous"></script>
|
|
470
|
+
<script>
|
|
471
|
+
(function () {
|
|
472
|
+
var GROUPS = ${JSON.stringify(groups)};
|
|
473
|
+
var cap = document.getElementById("cap");
|
|
474
|
+
var tl = gsap.timeline({ paused: true });
|
|
475
|
+
GROUPS.forEach(function (g) {
|
|
476
|
+
var el = document.createElement("div");
|
|
477
|
+
el.className = "caption-group";
|
|
478
|
+
g.words.forEach(function (w) {
|
|
479
|
+
var s = document.createElement("span");
|
|
480
|
+
s.className = "caption-word";
|
|
481
|
+
s.textContent = w.text + " ";
|
|
482
|
+
el.appendChild(s);
|
|
483
|
+
});
|
|
484
|
+
cap.appendChild(el);
|
|
485
|
+
tl.fromTo(el, { opacity: 0 }, { opacity: 1, duration: 0.18, overwrite: "auto" }, g.start);
|
|
486
|
+
tl.to(el, { opacity: 0, duration: 0.12, overwrite: "auto" }, g.end);
|
|
487
|
+
tl.set(el, { opacity: 0, visibility: "hidden" }, g.end + 0.12); // deterministic hard kill
|
|
488
|
+
g.words.forEach(function (w, i) {
|
|
489
|
+
tl.to(el.children[i], { color: "#ffffff", duration: 0.06 }, w.start);
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
tl.to({}, { duration: ${total} }, 0); // full-span anchor
|
|
493
|
+
window.__timelines = window.__timelines || {};
|
|
494
|
+
window.__timelines["captions"] = tl;
|
|
495
|
+
})();
|
|
496
|
+
</script>
|
|
497
|
+
</template>
|
|
498
|
+
`;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const sub = process.argv[2];
|
|
502
|
+
if (sub === "build" || sub === undefined) runBuild(process.argv.slice(sub === "build" ? 3 : 2));
|
|
503
|
+
else {
|
|
504
|
+
console.error(
|
|
505
|
+
"usage: node captions.mjs build [--storyboard …] [--audio-meta …] [--hyperframes .]",
|
|
506
|
+
);
|
|
507
|
+
process.exit(2);
|
|
508
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// assets.mjs — stage frame-named capture assets into assets/.
|
|
2
|
+
// Shared by stage-assets.mjs (Step 4 close, BEFORE the frame workers run) and
|
|
3
|
+
// assemble-index.mjs (Step 5, idempotent backstop). Only assets a frame names
|
|
4
|
+
// in `asset_candidates` are staged; unnamed assets never reach the project.
|
|
5
|
+
// asset_candidates value form: "assets/<basename> — desc; assets/… — …".
|
|
6
|
+
|
|
7
|
+
import { copyFileSync, existsSync, mkdirSync } from "node:fs";
|
|
8
|
+
import { basename, join } from "node:path";
|
|
9
|
+
|
|
10
|
+
export function basenamesFromCandidates(value) {
|
|
11
|
+
if (typeof value !== "string") return [];
|
|
12
|
+
return value
|
|
13
|
+
.split(";")
|
|
14
|
+
.map((seg) => seg.split(/\s+[—–-]\s+/)[0].trim()) // strip the " — description"
|
|
15
|
+
.filter(Boolean)
|
|
16
|
+
.map((p) => basename(p.replace(/^assets\//, "")));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Copy each frame's asset_candidates from capture/{assets,assets/videos,
|
|
20
|
+
// screenshots} into assets/. Already-staged files are left as is (first-wins),
|
|
21
|
+
// so calling this twice is safe. Returns { staged, wanted, anomalies }.
|
|
22
|
+
export function stageAssets({ hyperframesDir, frames }) {
|
|
23
|
+
const wanted = new Set();
|
|
24
|
+
for (const f of frames) {
|
|
25
|
+
for (const b of basenamesFromCandidates(f.extra?.asset_candidates)) wanted.add(b);
|
|
26
|
+
}
|
|
27
|
+
const captureDirs = [
|
|
28
|
+
join(hyperframesDir, "capture/assets"),
|
|
29
|
+
join(hyperframesDir, "capture/assets/videos"), // videos download into a subdir
|
|
30
|
+
join(hyperframesDir, "capture/screenshots"),
|
|
31
|
+
];
|
|
32
|
+
const assetsDir = join(hyperframesDir, "assets");
|
|
33
|
+
const anomalies = [];
|
|
34
|
+
let staged = 0;
|
|
35
|
+
if (wanted.size > 0) {
|
|
36
|
+
mkdirSync(assetsDir, { recursive: true });
|
|
37
|
+
for (const b of wanted) {
|
|
38
|
+
const dest = join(assetsDir, b);
|
|
39
|
+
if (existsSync(dest)) {
|
|
40
|
+
staged++;
|
|
41
|
+
continue;
|
|
42
|
+
} // first-wins / already staged
|
|
43
|
+
const src = captureDirs.map((d) => join(d, b)).find((p) => existsSync(p));
|
|
44
|
+
if (src) {
|
|
45
|
+
copyFileSync(src, dest);
|
|
46
|
+
staged++;
|
|
47
|
+
} else {
|
|
48
|
+
anomalies.push(
|
|
49
|
+
`asset "${b}" named by a frame but not found under capture/ — frame will 404 it`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return { staged, wanted, anomalies };
|
|
55
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// dimensions.mjs — canvas size + caption-band geometry for the product-launch
|
|
2
|
+
// pipeline. Single source of truth = the STORYBOARD frontmatter `format` global
|
|
3
|
+
// ("1920x1080" / "1080x1920" / "1080x1080", or a named orientation). Every
|
|
4
|
+
// script and the index assembler reads the size from here; none hardcodes it.
|
|
5
|
+
|
|
6
|
+
// Named orientation presets. Square/portrait are 1080-based so they share the
|
|
7
|
+
// long-edge pixel budget with landscape (same render-cost ballpark).
|
|
8
|
+
export const ORIENTATION_PRESETS = {
|
|
9
|
+
landscape: { width: 1920, height: 1080 }, // 16:9 — default
|
|
10
|
+
portrait: { width: 1080, height: 1920 }, // 9:16 — reels / shorts / TikTok
|
|
11
|
+
square: { width: 1080, height: 1080 }, // 1:1 — feed
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const DEFAULT_DIMENSIONS = ORIENTATION_PRESETS.landscape;
|
|
15
|
+
|
|
16
|
+
function sane(w, h) {
|
|
17
|
+
return Number.isFinite(w) && Number.isFinite(h) && w >= 240 && h >= 240 && w <= 8192 && h <= 8192;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Parse a STORYBOARD `format` global into { width, height, source }. Accepts
|
|
21
|
+
// "WxH" (e.g. "1920x1080"; `x` or `×`, any inner spacing) or a named orientation;
|
|
22
|
+
// falls back to landscape so a storyboard with a missing/garbled format still
|
|
23
|
+
// renders (no behavior change vs the old landscape lock).
|
|
24
|
+
export function parseFormat(format) {
|
|
25
|
+
const s = typeof format === "string" ? format.trim().toLowerCase() : "";
|
|
26
|
+
if (ORIENTATION_PRESETS[s]) return { ...ORIENTATION_PRESETS[s], source: `orientation=${s}` };
|
|
27
|
+
const m = s.match(/^(\d+)\s*[x×]\s*(\d+)$/);
|
|
28
|
+
if (m) {
|
|
29
|
+
const w = parseInt(m[1] ?? "", 10);
|
|
30
|
+
const h = parseInt(m[2] ?? "", 10);
|
|
31
|
+
if (sane(w, h)) return { width: w, height: h, source: "format" };
|
|
32
|
+
}
|
|
33
|
+
return { ...DEFAULT_DIMENSIONS, source: "default(landscape)" };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Caption band geometry, derived from canvas height: the bottom ~16.67% (180px
|
|
37
|
+
// at h=1080). Frame content must end `safetyPx` above the band top. Holds even
|
|
38
|
+
// when captions are disabled (bottom-edge consistency).
|
|
39
|
+
export const CAPTION_BAND_FRACTION = 0.1667;
|
|
40
|
+
export function captionBand(height, safetyPx = 20) {
|
|
41
|
+
const h = Number.isFinite(height) ? height : DEFAULT_DIMENSIONS.height;
|
|
42
|
+
const bandHeight = Math.round(h * CAPTION_BAND_FRACTION);
|
|
43
|
+
const bandTopY = h - bandHeight; // foreground must end at/above this y
|
|
44
|
+
return { bandHeight, bandTopY, foregroundMaxY: bandTopY - safetyPx };
|
|
45
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// pad-frame-duration.mjs — keeps a frame's own #root/clip data-duration in
|
|
2
|
+
// sync with the padded index.html wrapper duration transitions.mjs computes.
|
|
3
|
+
//
|
|
4
|
+
// The frame's OWN internal file declares its #root/clip data-duration to the
|
|
5
|
+
// STORYBOARD's content-only length (frame-worker.md: duration is "fixed
|
|
6
|
+
// upstream"). When an outgoing transition pads the index.html WRAPPER's
|
|
7
|
+
// data-duration to cover the transition tail, the frame's own internal
|
|
8
|
+
// duration is left short — the render engine clip-gates the sub-composition's
|
|
9
|
+
// visible content at that shorter value, so content vanishes abruptly at
|
|
10
|
+
// content-end instead of fading gracefully through the wrapper's extended
|
|
11
|
+
// fade-out tween. Pad the frame's own file to match so both durations agree.
|
|
12
|
+
|
|
13
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { resolve } from "node:path";
|
|
15
|
+
|
|
16
|
+
export function padFrameInternalDuration(hyperframesDir, frameSrc, frameId, newDuration) {
|
|
17
|
+
const framePath = resolve(hyperframesDir, frameSrc);
|
|
18
|
+
let html;
|
|
19
|
+
try {
|
|
20
|
+
html = readFileSync(framePath, "utf8");
|
|
21
|
+
} catch (err) {
|
|
22
|
+
if (err?.code === "ENOENT") return;
|
|
23
|
+
throw err;
|
|
24
|
+
}
|
|
25
|
+
const tagRe = /<[a-z][\w:-]*\s[^<>]*?>/gi;
|
|
26
|
+
let m;
|
|
27
|
+
while ((m = tagRe.exec(html)) !== null) {
|
|
28
|
+
const tag = m[0];
|
|
29
|
+
if (!tag.includes(`data-composition-id="${frameId}"`)) continue;
|
|
30
|
+
if (!/data-duration="[\d.]+"/.test(tag)) continue;
|
|
31
|
+
const newTag = tag.replace(/data-duration="[\d.]+"/, `data-duration="${newDuration}"`);
|
|
32
|
+
if (newTag === tag) return;
|
|
33
|
+
writeFileSync(framePath, html.slice(0, m.index) + newTag + html.slice(m.index + tag.length));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
}
|