@impractical-ai/motion 0.1.5 → 0.1.7
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/README.md +19 -0
- package/assets/engine.d.ts +793 -5
- package/assets/impractical/SKILL.md +53 -6
- package/assets/kits/actions.ts +803 -0
- package/assets/kits/ensemble.ts +483 -0
- package/assets/kits/highlight.ts +374 -0
- package/assets/kits/impact.ts +103 -0
- package/assets/kits/kits.ts +343 -0
- package/assets/kits/looks.ts +935 -0
- package/assets/kits/morphkit.ts +438 -0
- package/assets/kits/reveal.ts +1364 -0
- package/assets/kits/sentence.ts +218 -0
- package/assets/kits/streamkit.ts +195 -0
- package/dist/{api-KD6BNKBQ.js → api-DR3NJ7TI.js} +5 -2
- package/dist/chunk-77HVPD4G.js +43 -0
- package/dist/{chunk-DMAQPSA4.js → chunk-M7SHFM25.js} +222 -27
- package/dist/{chunk-7724464Z.js → chunk-MQKW7NFI.js} +65 -14
- package/dist/chunk-Z74SLKEU.js +67 -0
- package/dist/{commands-Y35XPDVH.js → commands-R3LJU2BW.js} +29 -4
- package/dist/index.js +6 -5
- package/dist/main-BEONOBG5.js +2218 -0
- package/dist/{stdio-ZFPBM6Z6.js → stdio-XUVQKDCB.js} +3169 -431
- package/package.json +4 -3
- package/dist/chunk-JDN3QHCX.js +0 -98
package/assets/engine.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Impractical Motion — public engine surface v1.
|
|
1
|
+
// Impractical Motion — public engine surface v1.1.
|
|
2
2
|
//
|
|
3
3
|
// A scene is two files:
|
|
4
4
|
// App.tsx the stage markup (plain React + CSS; this is your product UI)
|
|
@@ -506,10 +506,113 @@ export type ThreeLayerOpts = {
|
|
|
506
506
|
depth?: 'back' | 'front'
|
|
507
507
|
}
|
|
508
508
|
|
|
509
|
+
export type ThreeCameraAngle =
|
|
510
|
+
| 'front'
|
|
511
|
+
| 'three-quarter-left'
|
|
512
|
+
| 'three-quarter-right'
|
|
513
|
+
| 'high'
|
|
514
|
+
|
|
515
|
+
export type ThreeCameraPose = {
|
|
516
|
+
position: { x: number; y: number; z: number }
|
|
517
|
+
target: { x: number; y: number; z: number }
|
|
518
|
+
roll?: number
|
|
519
|
+
near?: number
|
|
520
|
+
far?: number
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
export type ThreeFrameOpts = {
|
|
524
|
+
/** Projected fraction of frame height occupied by the subject. Default 0.7. */
|
|
525
|
+
height?: number
|
|
526
|
+
/** `contain` also enforces maxWidth; `height` may crop horizontally. */
|
|
527
|
+
mode?: 'contain' | 'height'
|
|
528
|
+
maxWidth?: number
|
|
529
|
+
angle?: ThreeCameraAngle
|
|
530
|
+
/** Subject-to-camera direction. Overrides angle. */
|
|
531
|
+
direction?: { x: number; y: number; z: number }
|
|
532
|
+
roll?: number
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
export type ThreeMoveOpts = {
|
|
536
|
+
duration?: number
|
|
537
|
+
/** Camera travel ease. Defaults to the house dolly (`expo.inOut`), same as
|
|
538
|
+
* the 2D camera; pass one only when the user asks for a different feel. */
|
|
539
|
+
ease?: string
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
export type ThreeOrbitOpts = ThreeFrameOpts &
|
|
543
|
+
ThreeMoveOpts & {
|
|
544
|
+
/** Horizontal orbit delta in degrees. */
|
|
545
|
+
azimuth?: number
|
|
546
|
+
/** Vertical orbit delta in degrees. */
|
|
547
|
+
elevation?: number
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
export type ThreeLightingPreset =
|
|
551
|
+
| 'ui-flat'
|
|
552
|
+
| 'product-soft'
|
|
553
|
+
| 'dramatic-rim'
|
|
554
|
+
| 'studio-neutral'
|
|
555
|
+
|
|
556
|
+
export type ThreeLightingOpts = {
|
|
557
|
+
/** Camera-relative stays flattering through a move; world-locked reveals form. */
|
|
558
|
+
mode?: 'camera-relative' | 'world-locked'
|
|
559
|
+
intensity?: number
|
|
560
|
+
exposure?: number
|
|
561
|
+
rimColor?: THREE.ColorRepresentation
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
export type ThreeLightRig = {
|
|
565
|
+
ambient: THREE.AmbientLight
|
|
566
|
+
key: THREE.DirectionalLight
|
|
567
|
+
fill: THREE.DirectionalLight
|
|
568
|
+
rim: THREE.DirectionalLight
|
|
569
|
+
update: () => void
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export type ThreeCameraRig = {
|
|
573
|
+
/** Solve a pose whose projected subject bounds meet the framing constraint. */
|
|
574
|
+
frame: (subject: THREE.Object3D, opts?: ThreeFrameOpts) => ThreeCameraPose
|
|
575
|
+
/** Immediate deterministic camera cut to a resolved pose. */
|
|
576
|
+
set: (pose: ThreeCameraPose) => void
|
|
577
|
+
/** Tween XYZ, target, roll, and clipping planes while keeping the target locked. */
|
|
578
|
+
moveTo: (
|
|
579
|
+
pose: ThreeCameraPose,
|
|
580
|
+
opts?: ThreeMoveOpts,
|
|
581
|
+
position?: gsap.Position
|
|
582
|
+
) => { duration: number }
|
|
583
|
+
/** Solve and dolly to a subject. */
|
|
584
|
+
focus: (
|
|
585
|
+
subject: THREE.Object3D,
|
|
586
|
+
frame?: ThreeFrameOpts,
|
|
587
|
+
move?: ThreeMoveOpts,
|
|
588
|
+
position?: gsap.Position
|
|
589
|
+
) => { duration: number; pose: ThreeCameraPose }
|
|
590
|
+
/** Orbit in camera-local spherical coordinates and re-solve framing. */
|
|
591
|
+
orbit: (
|
|
592
|
+
subject: THREE.Object3D,
|
|
593
|
+
opts?: ThreeOrbitOpts,
|
|
594
|
+
position?: gsap.Position
|
|
595
|
+
) => { duration: number; pose: ThreeCameraPose }
|
|
596
|
+
/** Current planned pose, including moves not yet reached by the playhead. */
|
|
597
|
+
pose: () => ThreeCameraPose
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
export type ThreeLightingRig = {
|
|
601
|
+
apply: (
|
|
602
|
+
subject: THREE.Object3D,
|
|
603
|
+
preset?: ThreeLightingPreset,
|
|
604
|
+
opts?: ThreeLightingOpts
|
|
605
|
+
) => ThreeLightRig
|
|
606
|
+
}
|
|
607
|
+
|
|
509
608
|
export type ThreeLayer = {
|
|
510
609
|
/** The scene graph — add meshes, lights, groups. */
|
|
511
610
|
scene: THREE.Scene
|
|
512
611
|
camera: THREE.PerspectiveCamera
|
|
612
|
+
/** Subject-first XYZ camera control: exact projected framing, dolly, and orbit. */
|
|
613
|
+
rig: ThreeCameraRig
|
|
614
|
+
/** Deterministic key/fill/rim presets bound to a 3D subject. */
|
|
615
|
+
lighting: ThreeLightingRig
|
|
513
616
|
/** The canvas element, for tweening opacity/filters like any element. */
|
|
514
617
|
canvas: HTMLCanvasElement
|
|
515
618
|
/** Drive the layer over [position, position+duration]. `update` runs every
|
|
@@ -530,6 +633,463 @@ export type ThreeKit = {
|
|
|
530
633
|
layer: (opts?: ThreeLayerOpts) => ThreeLayer
|
|
531
634
|
}
|
|
532
635
|
|
|
636
|
+
// ─── Actions — stateful cursor actors, press, scroll, drag ───────────────
|
|
637
|
+
|
|
638
|
+
/** Built-in cursor icons. `collab` is the Figma-style colored arrow + name
|
|
639
|
+
* chip for multi-user scenes. Custom art passes `{ svg, hotspot }` — the
|
|
640
|
+
* hotspot (0..1 of the icon box, where the tip sits) is part of the contract,
|
|
641
|
+
* so brand cursors cannot reopen the misaligned-click bug. */
|
|
642
|
+
export type CursorIconName =
|
|
643
|
+
| 'arrow'
|
|
644
|
+
| 'pointer'
|
|
645
|
+
| 'ibeam'
|
|
646
|
+
| 'grab'
|
|
647
|
+
| 'grabbing'
|
|
648
|
+
| 'crosshair'
|
|
649
|
+
| 'collab'
|
|
650
|
+
|
|
651
|
+
export type CursorIcon = CursorIconName | { svg: string; hotspot: { x: number; y: number } }
|
|
652
|
+
|
|
653
|
+
export type CursorActorOpts = {
|
|
654
|
+
/** Icon set. Default 'arrow'; named collaborators usually want 'collab'. */
|
|
655
|
+
icon?: CursorIcon
|
|
656
|
+
/** Tint for the collab arrow and label chip. */
|
|
657
|
+
color?: string
|
|
658
|
+
/** Figma-style name tag riding the cursor. */
|
|
659
|
+
label?: string
|
|
660
|
+
/** Icon box size in scene px. Default 56 (at 1080p) — marketing videos
|
|
661
|
+
* oversize the cursor so the action reads at a glance. */
|
|
662
|
+
size?: number
|
|
663
|
+
/** 'still' (default) or 'human' — sub-pixel drift while parked between
|
|
664
|
+
* actions, so a resting cursor never reads as a freeze-frame. */
|
|
665
|
+
idle?: 'still' | 'human'
|
|
666
|
+
/** Auto-morph arrow → pointer over the last stretch of a click travel.
|
|
667
|
+
* Default true. */
|
|
668
|
+
morph?: boolean
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
export type CursorMoveOpts = {
|
|
672
|
+
/** Travel time in seconds. Default 0.8. */
|
|
673
|
+
duration?: number
|
|
674
|
+
/** Default 'ease-in-out-cubic' — every action eases in-out. */
|
|
675
|
+
ease?: string
|
|
676
|
+
/** Where on the target to land, as fractions of its box. Default
|
|
677
|
+
* `{ x: 0.5, y: 0.52 }` — the corpus's observed natural click point. */
|
|
678
|
+
anchor?: { x?: number; y?: number }
|
|
679
|
+
/** Extra scene-px nudge after the anchor. */
|
|
680
|
+
offset?: { x?: number; y?: number }
|
|
681
|
+
/** First-action entrance: 'edge' (default — slides in from the nearest
|
|
682
|
+
* frame edge) or 'fade' (materializes just short of the target, for tight
|
|
683
|
+
* close-ups where the edge is far away). Ignored while already visible. */
|
|
684
|
+
enter?: 'edge' | 'fade'
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/** What the cursor does after the action — the continuity contract.
|
|
688
|
+
* 'stay' (default) parks it where it is so the NEXT action continues from
|
|
689
|
+
* there; 'exit' leaves via the nearest edge; 'park' steps aside just below
|
|
690
|
+
* the target (clears it for typing or a reveal); a selector travels to that
|
|
691
|
+
* element and parks there. */
|
|
692
|
+
export type CursorThen = 'stay' | 'exit' | 'park' | string
|
|
693
|
+
|
|
694
|
+
export type CursorClickOpts = CursorMoveOpts & {
|
|
695
|
+
/** Target squash scale. 'auto' (default) derives from the element's size —
|
|
696
|
+
* big surfaces squash less. A number (e.g. 0.9) is used verbatim. */
|
|
697
|
+
press?: number | 'auto'
|
|
698
|
+
/** Squash time each way. Default 0.09. */
|
|
699
|
+
pressDuration?: number
|
|
700
|
+
then?: CursorThen
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
export type CursorDragOpts = CursorMoveOpts & {
|
|
704
|
+
/** Subtle scale lift on the payload while it is held. Default true. */
|
|
705
|
+
lift?: boolean
|
|
706
|
+
then?: CursorThen
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
export type CursorExitOpts = {
|
|
710
|
+
duration?: number
|
|
711
|
+
ease?: string
|
|
712
|
+
/** 'edge' (default) or 'fade'. */
|
|
713
|
+
via?: 'edge' | 'fade'
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
export type CursorActor = {
|
|
717
|
+
/** Move to an element (or continue there from the last position). */
|
|
718
|
+
to: (
|
|
719
|
+
target: string | Element,
|
|
720
|
+
opts?: CursorMoveOpts,
|
|
721
|
+
position?: gsap.Position
|
|
722
|
+
) => { duration: number }
|
|
723
|
+
/** Travel + press: squashes the cursor AND the target, ordered so the
|
|
724
|
+
* click visibly precedes any result the direction schedules after it. */
|
|
725
|
+
click: (
|
|
726
|
+
target: string | Element,
|
|
727
|
+
opts?: CursorClickOpts,
|
|
728
|
+
position?: gsap.Position
|
|
729
|
+
) => { duration: number }
|
|
730
|
+
/** Grab a payload and carry it: cursor and payload move as ONE tween pair
|
|
731
|
+
* with identical timing, so they cannot desync. `to` is an element/selector
|
|
732
|
+
* (payload centers on it) or a scene-px delta `{ x, y }`. */
|
|
733
|
+
drag: (
|
|
734
|
+
payload: string | Element,
|
|
735
|
+
to: string | Element | { x: number; y: number },
|
|
736
|
+
opts?: CursorDragOpts,
|
|
737
|
+
position?: gsap.Position
|
|
738
|
+
) => { duration: number }
|
|
739
|
+
/** Leave the frame. The next action re-enters fresh. */
|
|
740
|
+
exit: (opts?: CursorExitOpts, position?: gsap.Position) => { duration: number }
|
|
741
|
+
/** Update the collab name chip on the timeline. */
|
|
742
|
+
label: (text: string, position?: gsap.Position) => void
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
export type CursorKit = CursorActor & {
|
|
746
|
+
/** Declare a named cursor — an independent actor with its own icon, color,
|
|
747
|
+
* label chip, and continuity state. Two actors animate concurrently just by
|
|
748
|
+
* overlapping their positions. */
|
|
749
|
+
actor: (name: string, opts?: CursorActorOpts) => CursorActor
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
export type PressOpts = {
|
|
753
|
+
/** Squash scale; 'auto' (default) derives from element size. */
|
|
754
|
+
amount?: number | 'auto'
|
|
755
|
+
/** Each way. Default 0.09. */
|
|
756
|
+
duration?: number
|
|
757
|
+
ease?: string
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
export type ScrollOpts = {
|
|
761
|
+
duration?: number
|
|
762
|
+
ease?: string
|
|
763
|
+
/** Where the target settles in the container: 0 top, 0.5 center (default),
|
|
764
|
+
* 1 bottom. */
|
|
765
|
+
align?: number
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
export type UiChange = {
|
|
769
|
+
/** Replace the element's text content. */
|
|
770
|
+
text?: string
|
|
771
|
+
/** Set an input's value. */
|
|
772
|
+
value?: string
|
|
773
|
+
/** Swap an image/video source. */
|
|
774
|
+
src?: string
|
|
775
|
+
/** Set attributes verbatim. */
|
|
776
|
+
attrs?: Record<string, string>
|
|
777
|
+
/** Add/remove classes (resolved against the authored class list). */
|
|
778
|
+
classes?: { add?: string[]; remove?: string[] }
|
|
779
|
+
/** Brief brightness pulse after the change, so the state change reads as an
|
|
780
|
+
* event rather than a glitch. */
|
|
781
|
+
flash?: boolean
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
export type ActionsKit = {
|
|
785
|
+
cursor: CursorKit
|
|
786
|
+
/** Squash without a cursor — keyboard sends, programmatic actions. */
|
|
787
|
+
press: (
|
|
788
|
+
target: string | Element,
|
|
789
|
+
opts?: PressOpts,
|
|
790
|
+
position?: gsap.Position
|
|
791
|
+
) => { duration: number }
|
|
792
|
+
/** Animated scroll of a container until `to` (element, selector, or an
|
|
793
|
+
* absolute scrollTop) is in view. `duration: 0` sets it instantly for
|
|
794
|
+
* initial state. */
|
|
795
|
+
scroll: (
|
|
796
|
+
container: string | Element,
|
|
797
|
+
to: string | Element | number,
|
|
798
|
+
opts?: ScrollOpts,
|
|
799
|
+
position?: gsap.Position
|
|
800
|
+
) => { duration: number }
|
|
801
|
+
/** Declarative hard UI state change on the timeline (seek-reversible).
|
|
802
|
+
* Instant by design — a cut is hard; `flash` adds the one animated accent. */
|
|
803
|
+
ui: {
|
|
804
|
+
set: (
|
|
805
|
+
target: string | Element,
|
|
806
|
+
change: UiChange,
|
|
807
|
+
position?: gsap.Position
|
|
808
|
+
) => { duration: number }
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// ─── Highlight — annotations that derive from their target ───────────────
|
|
813
|
+
|
|
814
|
+
export type HighlightRelease = 'fade' | 'undraw' | 'hold'
|
|
815
|
+
|
|
816
|
+
export type HighlightOpts = {
|
|
817
|
+
/** Stroke/fill color. Each primitive has its own sensible default. */
|
|
818
|
+
color?: string
|
|
819
|
+
/** Draw-on time in seconds. */
|
|
820
|
+
duration?: number
|
|
821
|
+
/** Default 'ease-in-out-cubic'. */
|
|
822
|
+
ease?: string
|
|
823
|
+
/** Seconds the highlight holds after drawing, or 'hold' to stay to the end.
|
|
824
|
+
* Default 'hold' — releasing is a deliberate choice. */
|
|
825
|
+
persist?: number | 'hold'
|
|
826
|
+
/** How it leaves when persist is a number: 'fade' (default) or 'undraw'. */
|
|
827
|
+
release?: Exclude<HighlightRelease, 'hold'>
|
|
828
|
+
/** Extra space around the target in scene px. Default derives from size. */
|
|
829
|
+
padding?: number
|
|
830
|
+
/** Stroke thickness in px. */
|
|
831
|
+
thickness?: number
|
|
832
|
+
/** What to fit: 'text' hugs the glyphs (a block element's box spans its
|
|
833
|
+
* full column — a marker on "99.99%" must not swallow the empty half of
|
|
834
|
+
* the card); 'box' takes the element box. Marker, circle, and underline
|
|
835
|
+
* default to 'text'; ring, glow, and spotlight default to 'box'. */
|
|
836
|
+
fit?: 'text' | 'box'
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
export type SpotlightOpts = {
|
|
840
|
+
/** How dark the rest of the frame goes (0..1). Default 0.55. */
|
|
841
|
+
dim?: number
|
|
842
|
+
/** Defocus everything OUTSIDE the cutout by this many px (the target stays
|
|
843
|
+
* sharp). 0 (default) dims only. Engine ceiling: 20. */
|
|
844
|
+
blur?: number
|
|
845
|
+
/** Gaussian softness of the cutout edge in px. Default 26. */
|
|
846
|
+
feather?: number
|
|
847
|
+
/** Breathing room between the element's edge and the cutout, px. Default 16. */
|
|
848
|
+
padding?: number
|
|
849
|
+
duration?: number
|
|
850
|
+
ease?: string
|
|
851
|
+
persist?: number | 'hold'
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
export type HighlightKit = {
|
|
855
|
+
/** Rounded ring draws itself around the element, following its corner
|
|
856
|
+
* radius. The workhorse product-tour annotation. */
|
|
857
|
+
ring: (target: string | Element, opts?: HighlightOpts, position?: gsap.Position) => { duration: number }
|
|
858
|
+
/** Hand-drawn ellipse loop with overshoot — red-pen annotation energy. */
|
|
859
|
+
circle: (target: string | Element, opts?: HighlightOpts, position?: gsap.Position) => { duration: number }
|
|
860
|
+
/** Highlighter-pen swipe behind the glyphs: a skewed band wipes across in
|
|
861
|
+
* multiply blend, sized from the line height. */
|
|
862
|
+
marker: (target: string | Element, opts?: HighlightOpts, position?: gsap.Position) => { duration: number }
|
|
863
|
+
/** Underline draw. Styles: 'straight' | 'marker' | 'sketch'. */
|
|
864
|
+
underline: (
|
|
865
|
+
target: string | Element,
|
|
866
|
+
style?: 'straight' | 'marker' | 'sketch',
|
|
867
|
+
opts?: HighlightOpts,
|
|
868
|
+
position?: gsap.Position
|
|
869
|
+
) => { duration: number }
|
|
870
|
+
/** One soft glow pulse as a beat lands — light, not opacity. */
|
|
871
|
+
glow: (target: string | Element, opts?: HighlightOpts, position?: gsap.Position) => { duration: number }
|
|
872
|
+
/** Inverse move: everything EXCEPT the target dims behind a feathered
|
|
873
|
+
* cutout. The strongest "look here" in the vocabulary. */
|
|
874
|
+
spotlight: (target: string | Element, opts?: SpotlightOpts, position?: gsap.Position) => { duration: number }
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// ─── Looks — scene light, shading, backdrops, hero shot, track ───────────
|
|
878
|
+
|
|
879
|
+
export type LightState = {
|
|
880
|
+
/** Direction the light COMES FROM, degrees clockwise from 12 o'clock.
|
|
881
|
+
* 135 = upper-left key (the portrait default). */
|
|
882
|
+
angle: number
|
|
883
|
+
/** 0 cool … 1 warm. Tints sweeps and rims. */
|
|
884
|
+
warmth: number
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
export type LightKit = {
|
|
888
|
+
/** Declare the scene light. Call once, early; sweeps, rims, and cast
|
|
889
|
+
* shadows derive their direction from it. */
|
|
890
|
+
set: (state: Partial<LightState>) => void
|
|
891
|
+
/** A glint sweeps across the element, angled with the scene light — the
|
|
892
|
+
* `logo.shine` move generalized to any surface. */
|
|
893
|
+
sweep: (
|
|
894
|
+
target: string | Element,
|
|
895
|
+
opts?: { duration?: number; intensity?: number; width?: number; ease?: string },
|
|
896
|
+
position?: gsap.Position
|
|
897
|
+
) => { duration: number }
|
|
898
|
+
/** Edge light on the side facing the scene light — the premium product-
|
|
899
|
+
* shot rim. Fades in and holds. */
|
|
900
|
+
rim: (
|
|
901
|
+
target: string | Element,
|
|
902
|
+
opts?: { color?: string; intensity?: number; duration?: number },
|
|
903
|
+
position?: gsap.Position
|
|
904
|
+
) => { duration: number }
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
export type ShadeKit = {
|
|
908
|
+
/** Soft shadow under the element, offset along the scene light. Replaces
|
|
909
|
+
* hand-tuned boxShadow strings and always agrees with every other shadow. */
|
|
910
|
+
cast: (
|
|
911
|
+
target: string | Element,
|
|
912
|
+
opts?: { distance?: number; softness?: number; opacity?: number; duration?: number },
|
|
913
|
+
position?: gsap.Position
|
|
914
|
+
) => { duration: number }
|
|
915
|
+
/** Frame vignette, faded in once and held (like the grain plate — never
|
|
916
|
+
* cut, so it doesn't blink at hard cuts). */
|
|
917
|
+
vignette: (
|
|
918
|
+
opts?: { strength?: number; duration?: number },
|
|
919
|
+
position?: gsap.Position
|
|
920
|
+
) => { duration: number }
|
|
921
|
+
/** Named color grade on the scene content. 'warm-product' | 'cool-saas' |
|
|
922
|
+
* 'noir'. Applied to the scene root, so overlays (cursors, highlights)
|
|
923
|
+
* stay ungraded and legible. */
|
|
924
|
+
grade: (
|
|
925
|
+
preset: 'warm-product' | 'cool-saas' | 'noir',
|
|
926
|
+
opts?: { duration?: number },
|
|
927
|
+
position?: gsap.Position
|
|
928
|
+
) => { duration: number }
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
export type BackdropStyle =
|
|
932
|
+
| 'gradient-drift'
|
|
933
|
+
| 'orbs'
|
|
934
|
+
| 'dot-grid'
|
|
935
|
+
| 'paper'
|
|
936
|
+
// Designed grounds mined from web heroes (references/cards/web-hero-grounds.md):
|
|
937
|
+
| 'aurora' // Stripe — mesh gradient ribbons on a diagonal, drifting slowly
|
|
938
|
+
| 'threads' // Mintlify — fanned fine line strands sweeping the corners
|
|
939
|
+
| 'dot-matrix' // AgentMail — shimmering dot field, radial falloff off content
|
|
940
|
+
| 'grid-glow'
|
|
941
|
+
|
|
942
|
+
export type BackdropOpts = {
|
|
943
|
+
/** 0..1 — scales amplitude, speed, and opacity together. The default 0.3
|
|
944
|
+
* keeps the preset inside the sub-salient budget. */
|
|
945
|
+
energy?: number
|
|
946
|
+
/** Colors for the soft forms. Keep them near the scene's background
|
|
947
|
+
* luminance — the preset caps opacity, but hue contrast is yours. */
|
|
948
|
+
palette?: string[]
|
|
949
|
+
/** Base fill the backdrop paints under its forms. The backdrop mounts
|
|
950
|
+
* BEHIND the scene root, so a root that goes transparent needs the
|
|
951
|
+
* backdrop to own the ground color. */
|
|
952
|
+
base?: string
|
|
953
|
+
/** FIXED BACKPLATE (default true): mount outside the camera-carrying
|
|
954
|
+
* stage, so zooms and pans move the UI plane while the ground stays put —
|
|
955
|
+
* the studio-cyclorama behavior of reference launch films. Set false to
|
|
956
|
+
* make the backdrop part of the world the camera moves through. */
|
|
957
|
+
fixed?: boolean
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
export type BackdropHandle = {
|
|
961
|
+
el: HTMLElement
|
|
962
|
+
/** Duck the backdrop under a dense foreground beat. */
|
|
963
|
+
calm: (position?: gsap.Position) => void
|
|
964
|
+
/** Bring it back up during a hold. */
|
|
965
|
+
lift: (position?: gsap.Position) => void
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
export type HeroAnchor =
|
|
969
|
+
| 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'center'
|
|
970
|
+
| { x: number; y: number }
|
|
971
|
+
|
|
972
|
+
export type HeroOpts = {
|
|
973
|
+
/** Which region of the target the camera pushes toward. Default
|
|
974
|
+
* 'bottom-right' — the submit-button corner. */
|
|
975
|
+
anchor?: HeroAnchor
|
|
976
|
+
/** How far in: scale ≈ (frame width / target width) × zoom. Default 1.35 —
|
|
977
|
+
* the target overflows the frame, close-up territory. */
|
|
978
|
+
zoom?: number
|
|
979
|
+
/** Perspective tilt in degrees. Defaults derive from the anchor (the panel
|
|
980
|
+
* leans its far corner away): e.g. bottom-right → pitch 7, yaw -9. */
|
|
981
|
+
pitch?: number
|
|
982
|
+
yaw?: number
|
|
983
|
+
/** Push-in time. Default 0.9. */
|
|
984
|
+
duration?: number
|
|
985
|
+
/** Seconds held at the angle before returning. Default 1.2. */
|
|
986
|
+
hold?: number
|
|
987
|
+
/** Return time. Default 0.8; `settle: false` stays in the pose. */
|
|
988
|
+
returnDuration?: number
|
|
989
|
+
settle?: boolean
|
|
990
|
+
/** Sheen sweep across the surface during the push. Default true. */
|
|
991
|
+
sheen?: boolean
|
|
992
|
+
/** Shadow deepens as the panel lifts. Default true. */
|
|
993
|
+
shadow?: boolean
|
|
994
|
+
ease?: string
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
export type LooksKit = {
|
|
998
|
+
light: LightKit
|
|
999
|
+
shade: ShadeKit
|
|
1000
|
+
/** Mount an ambient animated backdrop behind the scene root. */
|
|
1001
|
+
backdrop: (style: BackdropStyle, opts?: BackdropOpts) => BackdropHandle
|
|
1002
|
+
/** The angle-zoom product shot: camera pushes toward one corner of the
|
|
1003
|
+
* target while it tilts in perspective, sheen and shadow responding.
|
|
1004
|
+
* A full round trip by default. */
|
|
1005
|
+
hero: (target: string | Element, opts?: HeroOpts, position?: gsap.Position) => { duration: number }
|
|
1006
|
+
/** Standalone perspective pose: tilt an element (not the stage) and hold.
|
|
1007
|
+
* Tilt back with pitch/yaw 0. */
|
|
1008
|
+
tilt: (
|
|
1009
|
+
target: string | Element,
|
|
1010
|
+
opts?: { pitch?: number; yaw?: number; duration?: number; ease?: string; perspective?: number },
|
|
1011
|
+
position?: gsap.Position
|
|
1012
|
+
) => { duration: number }
|
|
1013
|
+
/** Camera FOLLOWS an element while it moves: per-frame the tracked
|
|
1014
|
+
* element's live position re-centers the camera at a locked occupancy.
|
|
1015
|
+
* Seek-deterministic — each frame is a pure function of timeline position
|
|
1016
|
+
* (the catch-up from `from` is an eased blend, not stateful smoothing).
|
|
1017
|
+
* AUTHOR IT AFTER the motion it follows, so it renders after the movers. */
|
|
1018
|
+
track: (
|
|
1019
|
+
target: string | Element,
|
|
1020
|
+
opts?: {
|
|
1021
|
+
/** Tracking window in seconds. Default 2. */
|
|
1022
|
+
duration?: number
|
|
1023
|
+
/** Target occupancy as fraction of frame height. Default 0.5. */
|
|
1024
|
+
height?: number
|
|
1025
|
+
/** Fraction of the window spent easing from `from` onto the target.
|
|
1026
|
+
* Default 0.25. */
|
|
1027
|
+
attach?: number
|
|
1028
|
+
/** Camera pose the track blends FROM (what the camera shows when the
|
|
1029
|
+
* window opens). Default full frame. */
|
|
1030
|
+
from?: { scale: number; x: number; y: number }
|
|
1031
|
+
/** After the window: 'hold' keeps the last framing (a later camera call
|
|
1032
|
+
* takes over); 'full' returns to the full frame. Default 'full'. */
|
|
1033
|
+
release?: 'hold' | 'full'
|
|
1034
|
+
},
|
|
1035
|
+
position?: gsap.Position
|
|
1036
|
+
) => { duration: number }
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// ─── Sentence — a sentence as a stateful object ──────────────────────────
|
|
1040
|
+
|
|
1041
|
+
export type SentenceHandle = {
|
|
1042
|
+
el: HTMLElement
|
|
1043
|
+
append: (text: string, opts?: { stagger?: number; duration?: number; recenter?: boolean; stress?: boolean }, position?: Position) => { duration: number }
|
|
1044
|
+
swap: (slot: string, alternatives: string[], opts?: { interval?: number; stress?: boolean; align?: 'anchor' | 'recenter' }, position?: Position) => { duration: number }
|
|
1045
|
+
stress: (word: string, opts?: { color?: string; duration?: number }, position?: Position) => { duration: number }
|
|
1046
|
+
morphWord: (from: string, to: string, opts?: { duration?: number }, position?: Position) => { duration: number }
|
|
1047
|
+
chip: (word: string, media: HTMLElement, opts?: { height?: number; radius?: number }, position?: Position) => HTMLElement
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// ─── Morph — any element becomes any other element ───────────────────────
|
|
1051
|
+
|
|
1052
|
+
export type MorphOpts = {
|
|
1053
|
+
/** 'apple' = expo-out flight (~90% of the change in the first ~15% of the
|
|
1054
|
+
* time, long soft landing). 'spring' = the reference bounce: expo-out
|
|
1055
|
+
* flight, then a LIGHT damped sine rides the same path — calibrated from
|
|
1056
|
+
* the dnyxstudios AE expression (amp .04·v, freq 1.8Hz, decay e^-3t,
|
|
1057
|
+
* eased envelope). One motion, no delayed settle, ~one visible overshoot. */
|
|
1058
|
+
feel?: 'apple' | 'spring'
|
|
1059
|
+
/** Tune the 'spring' bounce. `amp` = first overshoot as a fraction of the
|
|
1060
|
+
* travel (default 0.025 — light), `freq` Hz (default 1.8), `decay` per
|
|
1061
|
+
* second (default 3), `flight` = fraction of `duration` spent on the main
|
|
1062
|
+
* move before the bounce tail (default 0.55). */
|
|
1063
|
+
bounce?: { amp?: number; freq?: number; decay?: number; flight?: number }
|
|
1064
|
+
duration?: number
|
|
1065
|
+
/** Flight curve — ANY GSAP ease works: house names, 'elastic.out(1, 0.4)',
|
|
1066
|
+
* 'back.out(1.8)'… Overshoots extrapolate past the target (kept renderable). */
|
|
1067
|
+
ease?: string
|
|
1068
|
+
/** 0..1 — center of the A→B crossfade along the flight. */
|
|
1069
|
+
handoff?: number
|
|
1070
|
+
/** Width of the crossfade as a fraction of the flight (default 0.3 —
|
|
1071
|
+
* tight; each side shows only near its natural shape). */
|
|
1072
|
+
crossfade?: number
|
|
1073
|
+
/** Arc lift in px (0 disables). Default: auto from travel distance. */
|
|
1074
|
+
arc?: number
|
|
1075
|
+
/** Part choreography (default 'stagger'): the target's direct children
|
|
1076
|
+
* arrive individually inside the landing box; the source's children are
|
|
1077
|
+
* absorbed as it leaves. `false` = containers only. */
|
|
1078
|
+
parts?: 'stagger' | false
|
|
1079
|
+
/** Sub-component matching (default 'auto'): elements tagged
|
|
1080
|
+
* `data-morph="name"` on BOTH sides (any depth) morph INTO each other —
|
|
1081
|
+
* each pair rides its own interpolated box inside the flight, so the
|
|
1082
|
+
* source's sub-component visibly becomes the target's. Untagged direct
|
|
1083
|
+
* children are greedily auto-paired by text overlap. `false` disables. */
|
|
1084
|
+
match?: 'auto' | false
|
|
1085
|
+
/** Landing overshoot on top of a non-spring flight. `true`/default object
|
|
1086
|
+
* for the soft house settle, `{ scale, duration, ease }` to tune (elastic
|
|
1087
|
+
* landings live here), `false` for none. 'spring' feel ignores this — its
|
|
1088
|
+
* ring is already in the flight. */
|
|
1089
|
+
settle?: boolean | { scale?: number; duration?: number; ease?: string }
|
|
1090
|
+
zIndex?: number
|
|
1091
|
+
}
|
|
1092
|
+
|
|
533
1093
|
// ─── Engine — the direction context you write against ────────────────────
|
|
534
1094
|
|
|
535
1095
|
/** Render directives a scene (or a kit, during the direction) hands to the
|
|
@@ -598,18 +1158,45 @@ export type AudioKit = {
|
|
|
598
1158
|
}
|
|
599
1159
|
|
|
600
1160
|
export type Output = {
|
|
1161
|
+
/** Delivery raster dimensions. They do not change scene layout. */
|
|
601
1162
|
width: number
|
|
602
1163
|
height: number
|
|
603
1164
|
fps: number
|
|
604
1165
|
render?: RenderConfig
|
|
605
1166
|
}
|
|
606
1167
|
|
|
1168
|
+
/** Resolved renderer contract. `width`/`height` are always logical scene
|
|
1169
|
+
* coordinates; only the exporter consumes `delivery` as raster dimensions. */
|
|
1170
|
+
export type StageConfig = Output & {
|
|
1171
|
+
composition: { width: number; height: number }
|
|
1172
|
+
delivery: { width: number; height: number }
|
|
1173
|
+
}
|
|
1174
|
+
|
|
607
1175
|
export type Rect = { left: number; top: number; width: number; height: number }
|
|
608
1176
|
|
|
609
1177
|
export type FramingTarget = string | Element | Rect | FramingTarget[]
|
|
610
1178
|
|
|
611
1179
|
export type Framing = { scale: number; x: number; y: number }
|
|
612
1180
|
|
|
1181
|
+
export type CameraFocusOpts = {
|
|
1182
|
+
/** Fraction of the composition height occupied by the subject. Default 0.7. */
|
|
1183
|
+
height?: number
|
|
1184
|
+
/** `contain` also caps width; `height` guarantees height and may crop horizontally. */
|
|
1185
|
+
mode?: 'contain' | 'height'
|
|
1186
|
+
/** Width cap used by `contain`. Default 0.9. */
|
|
1187
|
+
maxWidth?: number
|
|
1188
|
+
/** Optional safety ceiling. When hit, exact occupancy cannot be guaranteed. */
|
|
1189
|
+
maxScale?: number
|
|
1190
|
+
offsetX?: number
|
|
1191
|
+
offsetY?: number
|
|
1192
|
+
clamp?: boolean
|
|
1193
|
+
/** Camera travel time. Default 0.8 seconds. */
|
|
1194
|
+
duration?: number
|
|
1195
|
+
/** Camera travel ease. Defaults to the house dolly (`expo.inOut`); pass one
|
|
1196
|
+
* only when the user explicitly asks for a different camera feel. */
|
|
1197
|
+
ease?: string
|
|
1198
|
+
}
|
|
1199
|
+
|
|
613
1200
|
export type FramingOpts = {
|
|
614
1201
|
scale?: number // explicit zoom level; skips fitting
|
|
615
1202
|
fitWidth?: number // target occupies this fraction of the frame width
|
|
@@ -623,6 +1210,13 @@ export type FramingOpts = {
|
|
|
623
1210
|
export type TypeOpts = {
|
|
624
1211
|
cps?: number // characters per second
|
|
625
1212
|
caret?: boolean
|
|
1213
|
+
/** Opt-in invisible tracking head that glides smoothly along the typed
|
|
1214
|
+
* text — constant speed within each visual line, a quick eased drop at
|
|
1215
|
+
* every wrap. Hand it to `track()` or a camera framing; the caret itself
|
|
1216
|
+
* steps per character and wraps, so never track the caret directly.
|
|
1217
|
+
* `width` is the head's scene-px width (default 300); `height` is a
|
|
1218
|
+
* multiple of the line height (default 2.9). */
|
|
1219
|
+
head?: boolean | { width?: number; height?: number }
|
|
626
1220
|
}
|
|
627
1221
|
|
|
628
1222
|
export type TweenOverride = {
|
|
@@ -642,6 +1236,9 @@ export type TextStyleOverride = {
|
|
|
642
1236
|
color?: string
|
|
643
1237
|
x?: number
|
|
644
1238
|
y?: number
|
|
1239
|
+
/** Wrapping width in scene px — the side bars reflow text instead of
|
|
1240
|
+
* scale-squeezing it. Applied as inline-block + width. */
|
|
1241
|
+
width?: number
|
|
645
1242
|
hidden?: boolean
|
|
646
1243
|
}
|
|
647
1244
|
|
|
@@ -652,6 +1249,14 @@ export type ObjectPositionOverride = {
|
|
|
652
1249
|
scaleY?: number
|
|
653
1250
|
rotation?: number
|
|
654
1251
|
hidden?: boolean
|
|
1252
|
+
/** Plain CSS declarations the studio applies to the object's element —
|
|
1253
|
+
* border-radius, background-color, box-shadow and friends. Applied at
|
|
1254
|
+
* stage prepare, so they ride the structural rebuild route. */
|
|
1255
|
+
styles?: Record<string, string>
|
|
1256
|
+
/** Replacement source for the image or video the object carries (the
|
|
1257
|
+
* element itself or its first img/video descendant), usually a data URI so
|
|
1258
|
+
* renders never depend on the network. */
|
|
1259
|
+
src?: string
|
|
655
1260
|
}
|
|
656
1261
|
|
|
657
1262
|
export type AddedTextLayer = {
|
|
@@ -668,6 +1273,11 @@ export type SceneOverrides = {
|
|
|
668
1273
|
textStyles?: Record<string, TextStyleOverride>
|
|
669
1274
|
addedText?: Record<string, AddedTextLayer>
|
|
670
1275
|
objects?: Record<string, ObjectPositionOverride> // stable UI wrapper id -> scene-pixel offset
|
|
1276
|
+
/** Explicit paint order for editable siblings. Keys include the editable
|
|
1277
|
+
* kind (`object:…` / `text:…`) so independently generated ids cannot
|
|
1278
|
+
* collide. Values are normalized by the studio whenever an item moves in
|
|
1279
|
+
* the stack. */
|
|
1280
|
+
layerOrder?: Record<string, number>
|
|
671
1281
|
}
|
|
672
1282
|
|
|
673
1283
|
export type TweenKind =
|
|
@@ -698,12 +1308,16 @@ export type TweenInfo = {
|
|
|
698
1308
|
stagger?: number
|
|
699
1309
|
vars: Record<string, number | string>
|
|
700
1310
|
overridden: boolean
|
|
1311
|
+
/** The editable layer this tween animates, when its first Element target
|
|
1312
|
+
* sits inside one — lets the studio show a layer's choreography. */
|
|
1313
|
+
targetObjectId?: string
|
|
1314
|
+
targetTextId?: string
|
|
701
1315
|
}
|
|
702
1316
|
|
|
703
1317
|
export type CameraSetInfo = Framing & { id: number; overridden: boolean }
|
|
704
1318
|
|
|
705
1319
|
export type SceneInspection = {
|
|
706
|
-
config:
|
|
1320
|
+
config: StageConfig
|
|
707
1321
|
duration: number
|
|
708
1322
|
tweens: TweenInfo[]
|
|
709
1323
|
cameraSets: CameraSetInfo[]
|
|
@@ -720,26 +1334,60 @@ export type DirectionContext = {
|
|
|
720
1334
|
gsap: typeof gsap
|
|
721
1335
|
tl: gsap.core.Timeline // the master timeline (created paused)
|
|
722
1336
|
stage: HTMLElement // scene wrapper the camera transforms
|
|
723
|
-
config:
|
|
1337
|
+
config: StageConfig
|
|
724
1338
|
q: <T extends HTMLElement>(selector: string) => T
|
|
725
1339
|
qa: (selector: string) => HTMLElement[]
|
|
726
1340
|
camera: {
|
|
727
1341
|
framing: (target: FramingTarget, opts?: FramingOpts) => Framing
|
|
1342
|
+
focus: (
|
|
1343
|
+
target: FramingTarget,
|
|
1344
|
+
opts?: CameraFocusOpts,
|
|
1345
|
+
position?: gsap.Position
|
|
1346
|
+
) => Framing
|
|
728
1347
|
full: () => Framing
|
|
1348
|
+
/** CROP framing: the reference resting shot — the target deliberately
|
|
1349
|
+
* OVERFLOWS the frame (`fill` × cover, default 1.4) and one side hugs a
|
|
1350
|
+
* frame edge (`anchor`: 'left' | 'right' | 'top' | 'bottom' | corners |
|
|
1351
|
+
* {x,y} fraction), the rest bleeding off. Returns a Framing: compose
|
|
1352
|
+
* with camera.set for the CUT-TO-CROP edit (wide→detail is a cut) or
|
|
1353
|
+
* camera.move for a motivated push. A full-frame centered app is an
|
|
1354
|
+
* establishing shot, used once — proof shots default to crop. */
|
|
1355
|
+
crop: (
|
|
1356
|
+
target: FramingTarget,
|
|
1357
|
+
opts?: {
|
|
1358
|
+
fill?: number
|
|
1359
|
+
anchor?:
|
|
1360
|
+
| 'left' | 'right' | 'top' | 'bottom'
|
|
1361
|
+
| 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
|
|
1362
|
+
| 'center'
|
|
1363
|
+
| { x: number; y: number }
|
|
1364
|
+
}
|
|
1365
|
+
) => Framing
|
|
729
1366
|
set: (framing: Framing) => void
|
|
1367
|
+
/** The house dolly. DEFAULTS: `duration: 0.8`, `ease: 'expo.inOut'` —
|
|
1368
|
+
* a long dwell at both ends and a confident glide through the middle.
|
|
1369
|
+
* These defaults ARE the recommended camera physics: do NOT pass ease
|
|
1370
|
+
* or duration here unless the user explicitly asks for a different
|
|
1371
|
+
* camera feel. The two sanctioned exceptions: a slow ambient drift
|
|
1372
|
+
* (`duration: 2–5, ease: 'none'`) and an instant reframe behind a
|
|
1373
|
+
* covering element (`duration: 0.01`). Anything between 0.05–0.4s is
|
|
1374
|
+
* neither a move nor a cut — avoid it. `ease-in-out-cubic` is for
|
|
1375
|
+
* ELEMENT travel, never the camera. */
|
|
730
1376
|
move: (
|
|
731
1377
|
framing: Framing,
|
|
732
1378
|
vars?: gsap.TweenVars,
|
|
733
1379
|
position?: gsap.Position
|
|
734
1380
|
) => void
|
|
735
1381
|
}
|
|
736
|
-
/** Deterministic, seek-driven typing with a fake caret.
|
|
1382
|
+
/** Deterministic, seek-driven typing with a fake caret. The caret follows
|
|
1383
|
+
* real layout (mirror-probed), so it wraps with the text in a textarea.
|
|
1384
|
+
* `head` (opt-in) is a smooth glide marker for camera tracking. */
|
|
737
1385
|
typeInto: (
|
|
738
1386
|
target: string | HTMLTextAreaElement | HTMLInputElement,
|
|
739
1387
|
text: string,
|
|
740
1388
|
opts?: TypeOpts,
|
|
741
1389
|
position?: gsap.Position
|
|
742
|
-
) => { duration: number; caret: HTMLElement | null }
|
|
1390
|
+
) => { duration: number; caret: HTMLElement | null; head: HTMLElement | null }
|
|
743
1391
|
/** Rect the finished text will occupy in `target` (for framing the close-up). */
|
|
744
1392
|
measureTypedLine: (
|
|
745
1393
|
target: string | HTMLTextAreaElement | HTMLInputElement,
|
|
@@ -868,6 +1516,138 @@ export type DirectionContext = {
|
|
|
868
1516
|
* `tl.to(stage, { filter: 'blur(12px)' })` tweens, which produces two
|
|
869
1517
|
* unsynced rows in the Tweak panel. */
|
|
870
1518
|
fx: FilterKit
|
|
1519
|
+
/** Interaction actions — the cursor is a STATEFUL ACTOR, not a per-call
|
|
1520
|
+
* sprite. The engine tracks where every cursor is and whether it is on
|
|
1521
|
+
* screen: continuation from the last resting position is the default, and
|
|
1522
|
+
* entry/exit are explicit moments. First action enters from the nearest
|
|
1523
|
+
* frame edge (`enter: 'fade'` materializes near the target instead); the
|
|
1524
|
+
* next action continues from wherever the last one parked; `cursor.exit()`
|
|
1525
|
+
* leaves via the nearest edge. `cursor.click(el)` travels, auto-morphs
|
|
1526
|
+
* arrow→pointer on approach, and squashes cursor AND target (squash derived
|
|
1527
|
+
* from target size). `cursor.drag(payload, to)` carries the payload locked
|
|
1528
|
+
* to the cursor in one tween pair. `then: 'stay'|'exit'|'park'|selector`
|
|
1529
|
+
* says where the cursor goes after an action. `cursor.actor(name, { icon,
|
|
1530
|
+
* color, label })` declares independent named cursors (multi-user scenes —
|
|
1531
|
+
* 'collab' icon carries a Figma-style name chip); two actors animate
|
|
1532
|
+
* concurrently just by overlapping positions. Icons carry REGISTERED
|
|
1533
|
+
* hotspots, so click math cannot drift. Every action defaults to
|
|
1534
|
+
* `ease-in-out-cubic` and takes `duration`. */
|
|
1535
|
+
cursor: CursorKit
|
|
1536
|
+
/** Squash without a cursor — keyboard sends, programmatic actions. Amount
|
|
1537
|
+
* defaults from the element's size (big surfaces squash less). */
|
|
1538
|
+
press: ActionsKit['press']
|
|
1539
|
+
/** Animated scroll of a container until a target is in view (`duration: 0`
|
|
1540
|
+
* sets it instantly for initial state). */
|
|
1541
|
+
scroll: ActionsKit['scroll']
|
|
1542
|
+
/** Declarative hard UI state change on the timeline, seek-reversible:
|
|
1543
|
+
* `ui.set(el, { text?, value?, src?, attrs?, classes?, flash? })`. Instant
|
|
1544
|
+
* by design — a cut is hard; `flash` adds the one animated accent. */
|
|
1545
|
+
ui: ActionsKit['ui']
|
|
1546
|
+
/** Annotation primitives for pre-existing components — the "look here"
|
|
1547
|
+
* vocabulary. `highlight.ring(el)` draws a rounded ring around the element
|
|
1548
|
+
* (follows its corner radius); `circle` is the hand-drawn red-pen loop with
|
|
1549
|
+
* overshoot; `marker` is a highlighter swipe behind the glyphs (multiply
|
|
1550
|
+
* blend, sized from line height); `underline(el, 'sketch'|'straight'|
|
|
1551
|
+
* 'marker')`; `glow` is one soft halo pulse; `spotlight(el)` dims
|
|
1552
|
+
* everything EXCEPT the target behind a feathered cutout — the strongest
|
|
1553
|
+
* "look here" available. All geometry derives from the target; every
|
|
1554
|
+
* highlight takes `persist: 'hold' | seconds` and `release: 'fade' |
|
|
1555
|
+
* 'undraw'`, so nothing loiters. Pairs with the cursor kit: cursor.click +
|
|
1556
|
+
* highlight.ring is the product-tour grammar. */
|
|
1557
|
+
highlight: HighlightKit
|
|
1558
|
+
/** Scene lighting. `light.set({ angle, warmth })` declares THE light once —
|
|
1559
|
+
* sweeps, rims, and cast shadows all derive their direction from it, so
|
|
1560
|
+
* shadows can never disagree. `light.sweep(el)` runs a glint across any
|
|
1561
|
+
* surface; `light.rim(el)` is the premium edge light. */
|
|
1562
|
+
light: LooksKit['light']
|
|
1563
|
+
/** Shading and grade. `shade.cast(el)` — soft shadow offset along the
|
|
1564
|
+
* declared scene light; `shade.vignette()` — one held frame vignette;
|
|
1565
|
+
* `shade.grade('warm-product'|'cool-saas'|'noir')` — named color grade on
|
|
1566
|
+
* the scene content (overlays stay ungraded and legible). */
|
|
1567
|
+
shade: LooksKit['shade']
|
|
1568
|
+
/** Ambient animated backdrop mounted BEHIND the scene root (the scene's
|
|
1569
|
+
* own background must let it show through). Styles: 'gradient-drift',
|
|
1570
|
+
* 'orbs', 'dot-grid'. Presets are tuned to stay alive but sub-salient
|
|
1571
|
+
* (~0.5–2% pixel change per interval); `energy` scales within the budget.
|
|
1572
|
+
* The returned handle's `calm()`/`lift()` duck the backdrop under dense
|
|
1573
|
+
* foreground beats — audio ducking for pixels. */
|
|
1574
|
+
backdrop: LooksKit['backdrop']
|
|
1575
|
+
/** The angle-zoom product shot: the camera pushes toward one corner of the
|
|
1576
|
+
* target (`anchor: 'bottom-right'` = the submit-button corner) WHILE the
|
|
1577
|
+
* panel tilts a few degrees in perspective, a sheen sweeps the surface as
|
|
1578
|
+
* the angle changes, and the shadow deepens. Full round trip by default
|
|
1579
|
+
* (in → hold → settle); `settle: false` stays in the pose. Perspective is
|
|
1580
|
+
* applied to the target only, never the stage. */
|
|
1581
|
+
hero: LooksKit['hero']
|
|
1582
|
+
/** Standalone perspective pose for an element: `tilt(el, { pitch, yaw })`,
|
|
1583
|
+
* tilt back to 0 to flatten. */
|
|
1584
|
+
tilt: LooksKit['tilt']
|
|
1585
|
+
/** Camera FOLLOWS a moving element — per-frame re-centering at a locked
|
|
1586
|
+
* occupancy, seek-deterministic (the catch-up is an eased blend from a
|
|
1587
|
+
* declared start pose, not stateful smoothing). AUTHOR IT AFTER the motion
|
|
1588
|
+
* it follows. `track(el, { duration, height, release: 'full'|'hold' })`. */
|
|
1589
|
+
track: LooksKit['track']
|
|
1590
|
+
/** A sentence as a stateful object. `sentence(el)` takes over the element's
|
|
1591
|
+
* text (word spans) and returns a handle: `.append(words)` — words land
|
|
1592
|
+
* into the held line; `.swap(slot, [...])` — one word cycles in place;
|
|
1593
|
+
* `.stress(word)` — the one-accent-color semantic emphasis; `.morphWord`
|
|
1594
|
+
* ("it!" → "it?"); `.chip(word, media)` — a word slot holds a media chip.
|
|
1595
|
+
* New words are mounted hidden at build (layout stays stable); `recenter`
|
|
1596
|
+
* eases the container instead of live reflow. */
|
|
1597
|
+
sentence: SentenceKit['sentence']
|
|
1598
|
+
/** Universal shape morph: any element becomes any other element, a rect, or
|
|
1599
|
+
* `'frame'`. FLIP box + interpolated rounded-rect clip-path silhouette +
|
|
1600
|
+
* content-clone crossfade (`handoff` sets where). Author the TARGET hidden
|
|
1601
|
+
* (`autoAlpha: 0`); the morph reveals it at arrival. `fromPath`/`toPath`
|
|
1602
|
+
* accept real SVG paths for logo endcards. Export-worker safe. */
|
|
1603
|
+
morph: MorphKit['morph']
|
|
1604
|
+
/** The chip⇄full-bleed round trip: grows the element to cover the frame,
|
|
1605
|
+
* holds, returns home (`back: false` to stay). Scale IS the transition —
|
|
1606
|
+
* the object persists (Apple News+ grammar). */
|
|
1607
|
+
expand: MorphKit['expand']
|
|
1608
|
+
/** Elements arrive from BEHIND THE CAMERA onto the layout: each starts
|
|
1609
|
+
* oversized and offset radially (the whole UI plane blown up past the
|
|
1610
|
+
* lens), soft and translucent, then flies into its exact layout slot,
|
|
1611
|
+
* staggered. `dropIn('[data-panel] > *', { from: 3.2, stagger: 0.1 })`.
|
|
1612
|
+
* The container should already be visible; its CHILDREN drop in. */
|
|
1613
|
+
dropIn: EnsembleKit['dropIn']
|
|
1614
|
+
/** Elements fly in from scattered planar directions ('edges' | 'center' |
|
|
1615
|
+
* 'above') and settle into their exact layout slots — the tile-wall /
|
|
1616
|
+
* place-on-canvas grammar. Ends at exact identity. */
|
|
1617
|
+
assemble: EnsembleKit['assemble']
|
|
1618
|
+
/** Inverse of assemble: elements explode radially out of their layout and
|
|
1619
|
+
* fade. Layout is left clean (hidden, transforms reset). */
|
|
1620
|
+
scatter: EnsembleKit['scatter']
|
|
1621
|
+
/** SHOWPIECE — elements fly ballistically through the frame: tossed 'up'
|
|
1622
|
+
* and falling back out, raining 'down', or 'burst'. Items EXIT. Use on a
|
|
1623
|
+
* story payoff (something launched/generated/released), at most 1–2
|
|
1624
|
+
* showpieces per film — on an ambient beat this is noise. */
|
|
1625
|
+
fountain: EnsembleKit['fountain']
|
|
1626
|
+
/** SHOWPIECE — generated celebration burst (no authored elements):
|
|
1627
|
+
* particles with deterministic ballistics, spin, flutter, fade.
|
|
1628
|
+
* `confetti({ at: '[data-submit]' })`. ONE per film, ONLY on a genuine
|
|
1629
|
+
* payoff beat (shipped / delivered / won). */
|
|
1630
|
+
confetti: EnsembleKit['confetti']
|
|
1631
|
+
/** Gallery belt: the container's row of items scrolls continuously through
|
|
1632
|
+
* a clipped viewport (children duplicated once; the wrap is seamless).
|
|
1633
|
+
* A showcase beat — logos, covers, results — never ambient filler. */
|
|
1634
|
+
river: EnsembleKit['river']
|
|
1635
|
+
/** Bento reflow: tiles in one grid continuously trade space — spans change
|
|
1636
|
+
* and every tile FLIPs to its measured new slot, the grid staying full
|
|
1637
|
+
* while cells resize around each other. Ends on the authored layout. */
|
|
1638
|
+
bento: EnsembleKit['bento']
|
|
1639
|
+
/** Choreographed waiting — the beat AI products actually live in.
|
|
1640
|
+
* `stream.answer(el, text)` arrives word by word (machine writing, no
|
|
1641
|
+
* caret); `stream.skeleton(el)` shimmers placeholders then resolves into
|
|
1642
|
+
* the real content; `stream.status(el, ['Analyzing', 'Searching 3 sources'])`
|
|
1643
|
+
* names the work. All seek-deterministic. */
|
|
1644
|
+
stream: StreamKit
|
|
1645
|
+
/** The impact move. `slam(el)` lands type/cards hard from a perspective
|
|
1646
|
+
* turn; `slam(el, { direction: 'away' })` tips them back out of frame.
|
|
1647
|
+
* DISTORTION BELONGS TO MOTION: 'in' ends at exact identity, 'away' ends
|
|
1648
|
+
* invisible — an element must never come to rest sheared. Pair with
|
|
1649
|
+
* `blur()` across the fast window so the speed reads as smear, not a pop. */
|
|
1650
|
+
slam: ImpactKit['slam']
|
|
871
1651
|
/** Masks, mattes, and blend composition — the things `clip-path` cannot do.
|
|
872
1652
|
*
|
|
873
1653
|
* `mask.reveal(el, shape, opts?, position?)` and `mask.hide(...)` animate
|
|
@@ -897,6 +1677,14 @@ export type DirectionContext = {
|
|
|
897
1677
|
|
|
898
1678
|
export type Direction = (ctx: DirectionContext) => void | Promise<void>
|
|
899
1679
|
|
|
1680
|
+
export type BuiltScene = {
|
|
1681
|
+
timeline: gsap.core.Timeline
|
|
1682
|
+
runtime: DeterministicStageRuntime
|
|
1683
|
+
scene: SceneInspection
|
|
1684
|
+
config: StageConfig
|
|
1685
|
+
previewOverrides: (overrides: SceneOverrides) => boolean
|
|
1686
|
+
}
|
|
1687
|
+
|
|
900
1688
|
// ─── Named eases ──────────────────────────────────────────────────────
|
|
901
1689
|
|
|
902
1690
|
// ─── The promo easing set ─────────────────────────────────────────────────
|