@yewdiel21/motion-kit 0.0.0-stage → 0.9.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/NOTICE +38 -0
- package/README.md +80 -2
- package/bin/motion-kit.mjs +242 -0
- package/css.d.ts +2 -0
- package/dist/auto/index.d.ts +63 -0
- package/dist/auto/index.js +207 -0
- package/dist/auto/index.js.map +1 -0
- package/dist/chunk-2BQGFOA7.js +177 -0
- package/dist/chunk-2BQGFOA7.js.map +1 -0
- package/dist/chunk-34XYOXHG.js +2374 -0
- package/dist/chunk-34XYOXHG.js.map +1 -0
- package/dist/chunk-6OJAP6ZL.js +832 -0
- package/dist/chunk-6OJAP6ZL.js.map +1 -0
- package/dist/chunk-ABNRDDO7.js +28 -0
- package/dist/chunk-ABNRDDO7.js.map +1 -0
- package/dist/chunk-FS2HJVVY.js +3320 -0
- package/dist/chunk-FS2HJVVY.js.map +1 -0
- package/dist/chunk-J27KH2A2.js +157 -0
- package/dist/chunk-J27KH2A2.js.map +1 -0
- package/dist/chunk-KLY5BRRI.js +18 -0
- package/dist/chunk-KLY5BRRI.js.map +1 -0
- package/dist/chunk-Q4LVFNLN.js +1617 -0
- package/dist/chunk-Q4LVFNLN.js.map +1 -0
- package/dist/chunk-RFDIZYFX.js +304 -0
- package/dist/chunk-RFDIZYFX.js.map +1 -0
- package/dist/chunk-TGV27ZWM.js +17 -0
- package/dist/chunk-TGV27ZWM.js.map +1 -0
- package/dist/confetti-BSX1CLLV.d.ts +41 -0
- package/dist/css/components/chat-background.css +87 -0
- package/dist/css/components/chat.css +237 -0
- package/dist/css/components/cursor.css +178 -0
- package/dist/css/components/dialog.css +259 -0
- package/dist/css/components/island.css +301 -0
- package/dist/css/components/menu.css +105 -0
- package/dist/css/components/nav.css +1054 -0
- package/dist/css/components/steps.css +50 -0
- package/dist/css/components/theme-picker.css +115 -0
- package/dist/css/components/toast.css +115 -0
- package/dist/css/controls.css +950 -0
- package/dist/css/effects.css +587 -0
- package/dist/css/fonts.css +3 -0
- package/dist/css/themes/black-green.css +126 -0
- package/dist/css/themes/black.css +126 -0
- package/dist/css/themes/blue.css +126 -0
- package/dist/css/themes/brown.css +126 -0
- package/dist/css/themes/cold-blue.css +126 -0
- package/dist/css/themes/funny.css +126 -0
- package/dist/css/themes/gray.css +126 -0
- package/dist/css/themes/green.css +126 -0
- package/dist/css/themes/modern-1.css +126 -0
- package/dist/css/themes/neogreen.css +126 -0
- package/dist/css/themes/new.css +126 -0
- package/dist/css/themes/new2.css +126 -0
- package/dist/css/themes/oled.css +126 -0
- package/dist/css/themes/orange.css +126 -0
- package/dist/css/themes/pastel-pink.css +126 -0
- package/dist/css/themes/pink.css +126 -0
- package/dist/css/themes/purple.css +126 -0
- package/dist/css/themes/red.css +126 -0
- package/dist/css/themes/super-blue.css +126 -0
- package/dist/css/themes.css +2431 -0
- package/dist/css/tokens.css +452 -0
- package/dist/elements/index.d.ts +183 -0
- package/dist/elements/index.js +472 -0
- package/dist/elements/index.js.map +1 -0
- package/dist/engine-CUr9QijM.d.ts +46 -0
- package/dist/gl/index.d.ts +61 -0
- package/dist/gl/index.js +18 -0
- package/dist/gl/index.js.map +1 -0
- package/dist/index.css +5296 -0
- package/dist/index.d.ts +440 -0
- package/dist/index.js +390 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.d.ts +476 -0
- package/dist/react/index.js +969 -0
- package/dist/react/index.js.map +1 -0
- package/dist/reduced-BM4Ls0g5.d.ts +566 -0
- package/dist/reveal-WFUWDrei.d.ts +416 -0
- package/dist/store-Bqy_obnD.d.ts +195 -0
- package/dist/theme/generate.d.ts +18 -0
- package/dist/theme/generate.js +64 -0
- package/dist/theme/generate.js.map +1 -0
- package/dist/theme/index.d.ts +17 -0
- package/dist/theme/index.js +32 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/theme-BFMoCzi6.d.ts +158 -0
- package/dist/types-BkS_raL8.d.ts +7 -0
- package/package.json +114 -3
- package/registry.json +469 -0
- package/skills/motion-kit/SKILL.md +189 -0
- package/skills/motion-kit/reference/components.md +86 -0
- package/skills/motion-kit/reference/design.md +92 -0
- package/skills/motion-kit/reference/recipes.md +172 -0
- package/src/auto/auto-init.ts +151 -0
- package/src/auto/index.ts +3 -0
- package/src/auto/parse-options.ts +68 -0
- package/src/components/chat/README.md +90 -0
- package/src/components/chat/chat.css +236 -0
- package/src/components/chat/element.ts +98 -0
- package/src/components/chat/engine.ts +295 -0
- package/src/components/chat/react.tsx +196 -0
- package/src/components/chat-background/README.md +50 -0
- package/src/components/chat-background/audio.ts +56 -0
- package/src/components/chat-background/chat-background.css +86 -0
- package/src/components/chat-background/element.ts +31 -0
- package/src/components/chat-background/engine.ts +108 -0
- package/src/components/chat-background/react.tsx +36 -0
- package/src/components/cursor/README.md +53 -0
- package/src/components/cursor/cursor.css +177 -0
- package/src/components/cursor/element.ts +23 -0
- package/src/components/cursor/engine.ts +254 -0
- package/src/components/cursor/react.tsx +26 -0
- package/src/components/dialog/README.md +106 -0
- package/src/components/dialog/dialog.css +258 -0
- package/src/components/dialog/element.ts +118 -0
- package/src/components/dialog/engine.ts +432 -0
- package/src/components/dialog/react.tsx +200 -0
- package/src/components/icon.tsx +10 -0
- package/src/components/icons.ts +24 -0
- package/src/components/island/README.md +91 -0
- package/src/components/island/element.ts +45 -0
- package/src/components/island/engine.ts +200 -0
- package/src/components/island/island.css +300 -0
- package/src/components/island/react.tsx +115 -0
- package/src/components/menu/README.md +67 -0
- package/src/components/menu/element.ts +55 -0
- package/src/components/menu/engine.ts +142 -0
- package/src/components/menu/menu.css +104 -0
- package/src/components/menu/react.tsx +82 -0
- package/src/components/nav/README.md +85 -0
- package/src/components/nav/element.ts +39 -0
- package/src/components/nav/engine.ts +164 -0
- package/src/components/nav/nav.css +1053 -0
- package/src/components/nav/react.tsx +90 -0
- package/src/components/steps/README.md +70 -0
- package/src/components/steps/element.ts +43 -0
- package/src/components/steps/engine.ts +185 -0
- package/src/components/steps/react.tsx +91 -0
- package/src/components/steps/steps.css +49 -0
- package/src/components/theme-picker/README.md +57 -0
- package/src/components/theme-picker/element.ts +20 -0
- package/src/components/theme-picker/engine.ts +91 -0
- package/src/components/theme-picker/react.tsx +31 -0
- package/src/components/theme-picker/theme-picker.css +114 -0
- package/src/components/toast/README.md +60 -0
- package/src/components/toast/element.ts +25 -0
- package/src/components/toast/engine.ts +483 -0
- package/src/components/toast/react.tsx +22 -0
- package/src/components/toast/store.ts +122 -0
- package/src/components/toast/toast.css +114 -0
- package/src/core/channels.ts +165 -0
- package/src/core/curves.ts +35 -0
- package/src/core/frame.ts +12 -0
- package/src/core/gsap.ts +65 -0
- package/src/core/loop.ts +44 -0
- package/src/core/math.ts +9 -0
- package/src/core/physics-spring.ts +59 -0
- package/src/core/pointer.ts +63 -0
- package/src/core/reduced.ts +21 -0
- package/src/core/scope.ts +70 -0
- package/src/core/scroll-track.ts +106 -0
- package/src/core/split.ts +68 -0
- package/src/core/spring.ts +22 -0
- package/src/core/styles.ts +31 -0
- package/src/core/timing.ts +204 -0
- package/src/core/types.ts +20 -0
- package/src/core/visible.ts +19 -0
- package/src/css/base.css +142 -0
- package/src/css/controls/README.md +122 -0
- package/src/css/controls/burger.css +67 -0
- package/src/css/controls/button.css +226 -0
- package/src/css/controls/checkbox.css +53 -0
- package/src/css/controls/chip.css +85 -0
- package/src/css/controls/field.css +124 -0
- package/src/css/controls/glass.css +220 -0
- package/src/css/controls/icon.css +26 -0
- package/src/css/controls/segmented.css +60 -0
- package/src/css/controls/switch.css +72 -0
- package/src/css/default-theme.css +123 -0
- package/src/css/effects/README.md +134 -0
- package/src/css/effects/anim.css +224 -0
- package/src/css/effects/fade.css +15 -0
- package/src/css/effects/float.css +64 -0
- package/src/css/effects/frost.css +62 -0
- package/src/css/effects/grain.css +58 -0
- package/src/css/effects/loading.css +60 -0
- package/src/css/effects/success.css +22 -0
- package/src/css/effects/text.css +67 -0
- package/src/css/fonts.css +3 -0
- package/src/css/states.css +51 -0
- package/src/css/themes/black-green.css +126 -0
- package/src/css/themes/black.css +126 -0
- package/src/css/themes/blue.css +126 -0
- package/src/css/themes/brown.css +126 -0
- package/src/css/themes/cold-blue.css +126 -0
- package/src/css/themes/funny.css +126 -0
- package/src/css/themes/gray.css +126 -0
- package/src/css/themes/green.css +126 -0
- package/src/css/themes/modern-1.css +126 -0
- package/src/css/themes/neogreen.css +126 -0
- package/src/css/themes/new.css +126 -0
- package/src/css/themes/new2.css +126 -0
- package/src/css/themes/oled.css +126 -0
- package/src/css/themes/orange.css +126 -0
- package/src/css/themes/pastel-pink.css +126 -0
- package/src/css/themes/pink.css +126 -0
- package/src/css/themes/purple.css +126 -0
- package/src/css/themes/red.css +126 -0
- package/src/css/themes/super-blue.css +126 -0
- package/src/css/tokens.css +182 -0
- package/src/elements/base.ts +76 -0
- package/src/elements/index.ts +48 -0
- package/src/gl/index.ts +15 -0
- package/src/gl/liquid-blend/README.md +23 -0
- package/src/gl/liquid-blend/create-liquid-blend.ts +7 -0
- package/src/gl/liquid-blend/shader.ts +9 -0
- package/src/gl/matcap/README.md +23 -0
- package/src/gl/matcap/create-matcap.ts +7 -0
- package/src/gl/matcap/shader.ts +16 -0
- package/src/gl/particle-hero/README.md +41 -0
- package/src/gl/particle-hero/create-particle-hero.ts +544 -0
- package/src/gl/particle-hero/particle-hero.css +19 -0
- package/src/gl/particle-hero/shaders.ts +84 -0
- package/src/gl/shader-blur/README.md +23 -0
- package/src/gl/shader-blur/create-shader-blur.ts +7 -0
- package/src/gl/shader-blur/shader.ts +11 -0
- package/src/gl/shader-grain/README.md +23 -0
- package/src/gl/shader-grain/create-shader-grain.ts +7 -0
- package/src/gl/shader-grain/shader.ts +6 -0
- package/src/gl/shared/create-shader-effect.ts +186 -0
- package/src/gl/shared/shader.css +3 -0
- package/src/gl/shared/shaders.ts +30 -0
- package/src/index.ts +177 -0
- package/src/motion/anchor.ts +45 -0
- package/src/motion/chat.ts +97 -0
- package/src/motion/confetti.ts +130 -0
- package/src/motion/count.ts +95 -0
- package/src/motion/details.ts +70 -0
- package/src/motion/drag-dismiss.ts +231 -0
- package/src/motion/effects.ts +81 -0
- package/src/motion/entrances.ts +27 -0
- package/src/motion/floating.ts +389 -0
- package/src/motion/flow.ts +115 -0
- package/src/motion/indicator.ts +44 -0
- package/src/motion/layout.ts +39 -0
- package/src/motion/liquid.ts +227 -0
- package/src/motion/morph.ts +540 -0
- package/src/motion/reveal.ts +112 -0
- package/src/motion/sheet.ts +65 -0
- package/src/motion/surface.ts +108 -0
- package/src/react/animated-number.tsx +30 -0
- package/src/react/flip-group.tsx +44 -0
- package/src/react/frost.tsx +33 -0
- package/src/react/index.ts +80 -0
- package/src/react/slot-text.tsx +44 -0
- package/src/react/theme-provider.tsx +42 -0
- package/src/react/use-card-dock.ts +9 -0
- package/src/react/use-cascade.ts +9 -0
- package/src/react/use-contrast.ts +9 -0
- package/src/react/use-counter.ts +8 -0
- package/src/react/use-curved-carousel.ts +10 -0
- package/src/react/use-depth-grid.ts +9 -0
- package/src/react/use-details.ts +7 -0
- package/src/react/use-dock-pill.ts +10 -0
- package/src/react/use-drag-ruler.ts +10 -0
- package/src/react/use-fill-text.ts +9 -0
- package/src/react/use-fit-text.ts +8 -0
- package/src/react/use-flow.ts +8 -0
- package/src/react/use-frame-zoom.ts +9 -0
- package/src/react/use-infinite-reel.ts +8 -0
- package/src/react/use-letters.ts +9 -0
- package/src/react/use-liquid-blend.ts +8 -0
- package/src/react/use-marquee.ts +9 -0
- package/src/react/use-mask-text.ts +9 -0
- package/src/react/use-matcap.ts +8 -0
- package/src/react/use-parallax.ts +9 -0
- package/src/react/use-particle-hero.ts +9 -0
- package/src/react/use-pile.ts +8 -0
- package/src/react/use-pop.ts +8 -0
- package/src/react/use-radial-menu.ts +10 -0
- package/src/react/use-recipe.ts +38 -0
- package/src/react/use-reflow.ts +9 -0
- package/src/react/use-repel-bento.ts +8 -0
- package/src/react/use-reveal.ts +8 -0
- package/src/react/use-rotator.ts +10 -0
- package/src/react/use-shader-blur.ts +8 -0
- package/src/react/use-shader-grain.ts +8 -0
- package/src/react/use-slot-text.ts +17 -0
- package/src/react/use-snap-strip.ts +9 -0
- package/src/react/use-sortable.ts +14 -0
- package/src/react/use-spring-cards.ts +8 -0
- package/src/react/use-stretch-footer.ts +20 -0
- package/src/react/use-tabs.ts +10 -0
- package/src/react/use-text-roll.ts +9 -0
- package/src/react/use-ticket-menu.ts +15 -0
- package/src/react/use-words.ts +8 -0
- package/src/recipes/card-dock/README.md +36 -0
- package/src/recipes/card-dock/card-dock.css +178 -0
- package/src/recipes/card-dock/create-card-dock.ts +97 -0
- package/src/recipes/cascade/README.md +33 -0
- package/src/recipes/cascade/create-cascade.ts +111 -0
- package/src/recipes/contrast/README.md +29 -0
- package/src/recipes/contrast/create-contrast.ts +68 -0
- package/src/recipes/counter/README.md +42 -0
- package/src/recipes/counter/create-counter.ts +84 -0
- package/src/recipes/curved-carousel/README.md +50 -0
- package/src/recipes/curved-carousel/carousel-math.ts +20 -0
- package/src/recipes/curved-carousel/create-curved-carousel.ts +257 -0
- package/src/recipes/curved-carousel/curved-carousel.css +50 -0
- package/src/recipes/depth-grid/README.md +30 -0
- package/src/recipes/depth-grid/create-depth-grid.ts +120 -0
- package/src/recipes/details/README.md +31 -0
- package/src/recipes/details/create-details.ts +27 -0
- package/src/recipes/dock-pill/README.md +23 -0
- package/src/recipes/dock-pill/create-dock-pill.ts +68 -0
- package/src/recipes/dock-pill/dock-pill.css +5 -0
- package/src/recipes/drag-ruler/README.md +21 -0
- package/src/recipes/drag-ruler/create-drag-ruler.ts +95 -0
- package/src/recipes/drag-ruler/drag-ruler.css +16 -0
- package/src/recipes/fill-text/README.md +27 -0
- package/src/recipes/fill-text/create-fill-text.ts +93 -0
- package/src/recipes/fit-text/README.md +37 -0
- package/src/recipes/fit-text/create-fit-text.ts +55 -0
- package/src/recipes/fit-text/fit-text.css +6 -0
- package/src/recipes/flow/README.md +46 -0
- package/src/recipes/flow/create-flow.ts +55 -0
- package/src/recipes/frame-zoom/README.md +29 -0
- package/src/recipes/frame-zoom/create-frame-zoom.ts +91 -0
- package/src/recipes/infinite-reel/README.md +23 -0
- package/src/recipes/infinite-reel/create-infinite-reel.ts +140 -0
- package/src/recipes/infinite-reel/infinite-reel.css +8 -0
- package/src/recipes/letters/README.md +30 -0
- package/src/recipes/letters/create-letters.ts +113 -0
- package/src/recipes/marquee/README.md +32 -0
- package/src/recipes/marquee/create-marquee.ts +74 -0
- package/src/recipes/marquee/marquee.css +41 -0
- package/src/recipes/mask-text/README.md +39 -0
- package/src/recipes/mask-text/create-mask-text.ts +134 -0
- package/src/recipes/parallax/README.md +31 -0
- package/src/recipes/parallax/create-parallax.ts +74 -0
- package/src/recipes/pile/README.md +48 -0
- package/src/recipes/pile/create-pile.ts +50 -0
- package/src/recipes/pile/pile-math.ts +27 -0
- package/src/recipes/pile/pile.css +63 -0
- package/src/recipes/pill/README.md +30 -0
- package/src/recipes/pill/pill.css +48 -0
- package/src/recipes/pop/README.md +36 -0
- package/src/recipes/pop/create-pop.ts +52 -0
- package/src/recipes/radial-menu/README.md +44 -0
- package/src/recipes/radial-menu/create-radial-menu.ts +96 -0
- package/src/recipes/radial-menu/radial-menu.css +45 -0
- package/src/recipes/reflow/README.md +47 -0
- package/src/recipes/reflow/create-reflow.ts +49 -0
- package/src/recipes/repel-bento/README.md +21 -0
- package/src/recipes/repel-bento/create-repel-bento.ts +65 -0
- package/src/recipes/repel-bento/repel-bento.css +3 -0
- package/src/recipes/reveal/README.md +36 -0
- package/src/recipes/reveal/create-reveal.ts +48 -0
- package/src/recipes/rotator/README.md +21 -0
- package/src/recipes/rotator/create-rotator.ts +66 -0
- package/src/recipes/rotator/rotator.css +3 -0
- package/src/recipes/slot-text/README.md +44 -0
- package/src/recipes/slot-text/create-slot-text.ts +244 -0
- package/src/recipes/slot-text/slot-text.css +42 -0
- package/src/recipes/snap-strip/README.md +31 -0
- package/src/recipes/snap-strip/create-snap-strip.ts +133 -0
- package/src/recipes/snap-strip/snap-strip.css +23 -0
- package/src/recipes/sortable/README.md +88 -0
- package/src/recipes/sortable/create-sortable.ts +863 -0
- package/src/recipes/sortable/sortable.css +40 -0
- package/src/recipes/spring-cards/README.md +48 -0
- package/src/recipes/spring-cards/create-spring-cards.ts +140 -0
- package/src/recipes/spring-cards/spring-cards.css +23 -0
- package/src/recipes/stretch-footer/README.md +44 -0
- package/src/recipes/stretch-footer/create-stretch-footer.ts +69 -0
- package/src/recipes/stretch-footer/stretch-footer.css +28 -0
- package/src/recipes/tabs/README.md +39 -0
- package/src/recipes/tabs/create-tabs.ts +91 -0
- package/src/recipes/tabs/tabs.css +19 -0
- package/src/recipes/text-roll/README.md +35 -0
- package/src/recipes/text-roll/create-text-roll.ts +137 -0
- package/src/recipes/text-roll/text-roll.css +21 -0
- package/src/recipes/ticket-menu/README.md +24 -0
- package/src/recipes/ticket-menu/create-ticket-menu.ts +6 -0
- package/src/recipes/words/README.md +43 -0
- package/src/recipes/words/create-words.ts +114 -0
- package/src/theme/css-format.ts +32 -0
- package/src/theme/generate.ts +72 -0
- package/src/theme/index.ts +15 -0
- package/src/theme/presets.ts +79 -0
- package/src/theme/theme.ts +119 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# shader-grain
|
|
2
|
+
|
|
3
|
+
Grano de película animado por shader, intensidad .07 (Siena).
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { createShaderGrain } from '@yewdiel21/motion-kit/gl';
|
|
7
|
+
import { useShaderGrain } from '@yewdiel21/motion-kit/react';
|
|
8
|
+
import '@yewdiel21/motion-kit/css';
|
|
9
|
+
const effect = createShaderGrain(root);
|
|
10
|
+
effect.setProgress(.5);
|
|
11
|
+
effect.setAmount(.07);
|
|
12
|
+
effect.destroy();
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Opciones compartidas: `images?: HTMLImageElement[]` (por defecto las dos primeras imágenes de root; una se repite), `progress` 0–1, `amount`, `maxDpr` 1.5. Las imágenes pueden terminar de cargar después de crear la instancia. Recursos del mismo origen o con CORS configurado antes de cargar la imagen. El kit no descarga imágenes. Root debe tener dimensiones; el canvas decorativo cubre su caja y deja el HTML accesible debajo.
|
|
16
|
+
|
|
17
|
+
Controles: `setProgress(0..1)`, `setAmount(0..100)`, `pause()`, `resume()`, `mode: 'webgl' | 'none'`, `destroy()`. React: `useShaderGrain(ref, options, deps)`, ref a la instancia. No usa `autoInit`: importar explícitamente desde `/gl` conserva la separación de GPU del paquete principal.
|
|
18
|
+
|
|
19
|
+
Pausa fuera de pantalla y con pestaña oculta. Movimiento reducido: frame fijo, con controles manuales. Sin WebGL, error de textura o contexto perdido: canvas oculto, HTML original disponible. Contexto restaurado: recompila y vuelve a cargar texturas. Destroy libera texturas, buffer, shaders y programa, desconecta observers y restaura la clase original.
|
|
20
|
+
|
|
21
|
+
Siena `siena.webgl.post`: intensidad .07. Ruido propio con semilla temporal a 24 Hz; amount 0 conserva la imagen. No reproduce el pipeline de postprocesado del sitio completo.
|
|
22
|
+
|
|
23
|
+
Desde 0.9.0. Fuentes de parámetros: `REA - Dousan OFFFICE Siena/`. Shaders originales, sin redistribuir código ni assets de terceros.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { createShaderEffect, type ShaderEffectInstance, type ShaderEffectOptions } from '../shared/create-shader-effect';
|
|
2
|
+
import { grainFragment } from './shader';
|
|
3
|
+
|
|
4
|
+
export type ShaderGrainOptions = ShaderEffectOptions;
|
|
5
|
+
export type ShaderGrainInstance = ShaderEffectInstance;
|
|
6
|
+
export const createShaderGrain = (root: HTMLElement | null, options: ShaderGrainOptions = {}): ShaderGrainInstance =>
|
|
7
|
+
createShaderEffect(root, grainFragment, { amount: 0.07, ...options });
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { whileVisible } from '../../core/loop';
|
|
2
|
+
import { clamp } from '../../core/math';
|
|
3
|
+
import { reduced } from '../../core/reduced';
|
|
4
|
+
import { createScope } from '../../core/scope';
|
|
5
|
+
import { inert, type Instance } from '../../core/types';
|
|
6
|
+
import { fragmentHeader, fullscreenVertex } from './shaders';
|
|
7
|
+
|
|
8
|
+
export interface ShaderEffectOptions {
|
|
9
|
+
images?: HTMLImageElement[];
|
|
10
|
+
progress?: number;
|
|
11
|
+
amount?: number;
|
|
12
|
+
maxDpr?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface ShaderEffectInstance extends Instance {
|
|
15
|
+
readonly mode: 'webgl' | 'none';
|
|
16
|
+
setProgress(progress: number): void;
|
|
17
|
+
setAmount(amount: number): void;
|
|
18
|
+
pause(): void;
|
|
19
|
+
resume(): void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface Renderer {
|
|
23
|
+
draw(time: number, progress: number, amount: number): void;
|
|
24
|
+
upload(): boolean;
|
|
25
|
+
destroy(): void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function createRenderer(canvas: HTMLCanvasElement, fragment: string, images: HTMLImageElement[]): Renderer {
|
|
29
|
+
const gl = canvas.getContext('webgl', { alpha: true, antialias: false, premultipliedAlpha: false });
|
|
30
|
+
if (!gl) throw new Error('WebGL unavailable');
|
|
31
|
+
const releases: Array<() => void> = [];
|
|
32
|
+
const destroy = () => { releases.splice(0).reverse().forEach((release) => release()); };
|
|
33
|
+
try {
|
|
34
|
+
const compile = (type: number, source: string) => {
|
|
35
|
+
const shader = gl.createShader(type);
|
|
36
|
+
if (!shader) throw new Error('Shader allocation failed');
|
|
37
|
+
releases.push(() => gl.deleteShader(shader));
|
|
38
|
+
gl.shaderSource(shader, source);
|
|
39
|
+
gl.compileShader(shader);
|
|
40
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(shader) ?? 'Shader failed');
|
|
41
|
+
return shader;
|
|
42
|
+
};
|
|
43
|
+
const vertex = compile(gl.VERTEX_SHADER, fullscreenVertex);
|
|
44
|
+
const pixel = compile(gl.FRAGMENT_SHADER, fragmentHeader + fragment);
|
|
45
|
+
const program = gl.createProgram();
|
|
46
|
+
if (!program) throw new Error('Program allocation failed');
|
|
47
|
+
releases.push(() => gl.deleteProgram(program));
|
|
48
|
+
gl.attachShader(program, vertex);
|
|
49
|
+
gl.attachShader(program, pixel);
|
|
50
|
+
gl.linkProgram(program);
|
|
51
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) throw new Error(gl.getProgramInfoLog(program) ?? 'Link failed');
|
|
52
|
+
gl.useProgram(program);
|
|
53
|
+
const buffer = gl.createBuffer();
|
|
54
|
+
if (!buffer) throw new Error('Buffer allocation failed');
|
|
55
|
+
releases.push(() => gl.deleteBuffer(buffer));
|
|
56
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
57
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
|
|
58
|
+
const attribute = gl.getAttribLocation(program, 'position');
|
|
59
|
+
gl.enableVertexAttribArray(attribute);
|
|
60
|
+
gl.vertexAttribPointer(attribute, 2, gl.FLOAT, false, 0, 0);
|
|
61
|
+
const names = ['uSize', 'uImageSize0', 'uImageSize1', 'uTime', 'uProgress', 'uAmount', 'uImage0', 'uImage1', 'uHasImage'] as const;
|
|
62
|
+
const uniforms = Object.fromEntries(names.map((name) => [name, gl.getUniformLocation(program, name)]));
|
|
63
|
+
const textures = [0, 1].map((index) => {
|
|
64
|
+
const texture = gl.createTexture();
|
|
65
|
+
if (!texture) throw new Error('Texture allocation failed');
|
|
66
|
+
releases.push(() => gl.deleteTexture(texture));
|
|
67
|
+
gl.activeTexture(gl.TEXTURE0 + index);
|
|
68
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
69
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
70
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
71
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
72
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
73
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([26, 40, 55, 255]));
|
|
74
|
+
gl.uniform1i(uniforms[`uImage${index}`]!, index);
|
|
75
|
+
return texture;
|
|
76
|
+
});
|
|
77
|
+
const upload = () => {
|
|
78
|
+
if (images.some((image) => !image.complete || !image.naturalWidth)) return false;
|
|
79
|
+
gl.useProgram(program);
|
|
80
|
+
images.slice(0, 2).forEach((image, index) => {
|
|
81
|
+
gl.activeTexture(gl.TEXTURE0 + index);
|
|
82
|
+
gl.bindTexture(gl.TEXTURE_2D, textures[index]!);
|
|
83
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
|
|
84
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
|
|
85
|
+
});
|
|
86
|
+
return true;
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
89
|
+
upload,
|
|
90
|
+
draw(time, progress, amount) {
|
|
91
|
+
gl.viewport(0, 0, canvas.width, canvas.height);
|
|
92
|
+
gl.useProgram(program);
|
|
93
|
+
gl.uniform2f(uniforms.uSize!, canvas.width, canvas.height);
|
|
94
|
+
[0, 1].forEach((index) => gl.uniform2f(uniforms[`uImageSize${index}`]!, images[index]?.naturalWidth || 1, images[index]?.naturalHeight || 1));
|
|
95
|
+
gl.uniform1f(uniforms.uTime!, time);
|
|
96
|
+
gl.uniform1f(uniforms.uProgress!, progress);
|
|
97
|
+
gl.uniform1f(uniforms.uAmount!, amount);
|
|
98
|
+
gl.uniform1f(uniforms.uHasImage!, images.length ? 1 : 0);
|
|
99
|
+
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
100
|
+
},
|
|
101
|
+
destroy,
|
|
102
|
+
};
|
|
103
|
+
} catch (error) {
|
|
104
|
+
destroy();
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function createShaderEffect(root: HTMLElement | null, fragment: string, options: ShaderEffectOptions = {}): ShaderEffectInstance {
|
|
110
|
+
if (!root) return inert({ mode: 'none' as const, setProgress() {}, setAmount() {}, pause() {}, resume() {} });
|
|
111
|
+
const scope = createScope();
|
|
112
|
+
const canvas = document.createElement('canvas');
|
|
113
|
+
canvas.className = 'mk-shader__canvas';
|
|
114
|
+
canvas.setAttribute('aria-hidden', 'true');
|
|
115
|
+
canvas.hidden = true;
|
|
116
|
+
const images = (options.images ?? [...root.querySelectorAll('img')]).slice(0, 2);
|
|
117
|
+
if (images.length === 1) images.push(images[0]!);
|
|
118
|
+
let renderer: Renderer | null = null;
|
|
119
|
+
let mode: ShaderEffectInstance['mode'] = 'none';
|
|
120
|
+
let progress = clamp(options.progress ?? 0, 0, 1);
|
|
121
|
+
let amount = clamp(options.amount ?? 1, 0, 100);
|
|
122
|
+
let time = 0;
|
|
123
|
+
let paused = false;
|
|
124
|
+
let ready = false;
|
|
125
|
+
const draw = () => {
|
|
126
|
+
if (!scope.disposed && ready && renderer && mode === 'webgl') renderer.draw(time, progress, amount);
|
|
127
|
+
};
|
|
128
|
+
const fallback = () => {
|
|
129
|
+
mode = 'none';
|
|
130
|
+
ready = false;
|
|
131
|
+
canvas.hidden = true;
|
|
132
|
+
};
|
|
133
|
+
const load = () => {
|
|
134
|
+
if (scope.disposed || !renderer) return;
|
|
135
|
+
try {
|
|
136
|
+
ready = renderer.upload();
|
|
137
|
+
canvas.hidden = !ready;
|
|
138
|
+
if (ready) mode = 'webgl';
|
|
139
|
+
draw();
|
|
140
|
+
} catch { fallback(); }
|
|
141
|
+
};
|
|
142
|
+
const resize = (render = true) => {
|
|
143
|
+
if (scope.disposed) return;
|
|
144
|
+
const rect = root.getBoundingClientRect();
|
|
145
|
+
const dpr = Math.min(typeof devicePixelRatio === 'number' ? devicePixelRatio : 1, Math.max(0.5, options.maxDpr ?? 1.5));
|
|
146
|
+
canvas.width = Math.max(1, Math.round(rect.width * dpr));
|
|
147
|
+
canvas.height = Math.max(1, Math.round(rect.height * dpr));
|
|
148
|
+
if (render) draw();
|
|
149
|
+
};
|
|
150
|
+
const initialize = () => {
|
|
151
|
+
renderer?.destroy();
|
|
152
|
+
renderer = null;
|
|
153
|
+
try {
|
|
154
|
+
renderer = createRenderer(canvas, fragment, images);
|
|
155
|
+
mode = 'webgl';
|
|
156
|
+
resize(false);
|
|
157
|
+
load();
|
|
158
|
+
return true;
|
|
159
|
+
} catch { fallback(); return false; }
|
|
160
|
+
};
|
|
161
|
+
if (!initialize()) { scope.dispose(); return inert({ mode: 'none' as const, setProgress() {}, setAmount() {}, pause() {}, resume() {} }); }
|
|
162
|
+
scope.attr(root, 'class', `${root.className} mk-shader`.trim());
|
|
163
|
+
root.append(canvas);
|
|
164
|
+
scope.add(() => { renderer?.destroy(); renderer = null; canvas.remove(); mode = 'none'; });
|
|
165
|
+
images.forEach((image) => {
|
|
166
|
+
scope.on(image, 'load', load);
|
|
167
|
+
scope.on(image, 'error', fallback);
|
|
168
|
+
});
|
|
169
|
+
scope.on(canvas, 'webglcontextlost', (event) => { event.preventDefault(); fallback(); });
|
|
170
|
+
scope.on(canvas, 'webglcontextrestored', initialize);
|
|
171
|
+
if (typeof ResizeObserver === 'function') scope.observe(new ResizeObserver(() => resize())).observe(root);
|
|
172
|
+
scope.on(window, 'resize', () => resize());
|
|
173
|
+
if (!reduced()) scope.add(whileVisible(root, (dt) => {
|
|
174
|
+
if (paused || document.hidden || mode === 'none') return;
|
|
175
|
+
time += dt / 60;
|
|
176
|
+
draw();
|
|
177
|
+
}));
|
|
178
|
+
return {
|
|
179
|
+
get mode() { return mode; },
|
|
180
|
+
setProgress(value) { if (scope.disposed || !Number.isFinite(value)) return; progress = clamp(value, 0, 1); draw(); },
|
|
181
|
+
setAmount(value) { if (scope.disposed || !Number.isFinite(value)) return; amount = clamp(value, 0, 100); draw(); },
|
|
182
|
+
pause() { paused = true; },
|
|
183
|
+
resume() { paused = false; },
|
|
184
|
+
destroy: () => scope.dispose(),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const fullscreenVertex = `
|
|
2
|
+
attribute vec2 position;
|
|
3
|
+
varying vec2 vUv;
|
|
4
|
+
void main() {
|
|
5
|
+
vUv = position * .5 + .5;
|
|
6
|
+
gl_Position = vec4(position, 0., 1.);
|
|
7
|
+
}`;
|
|
8
|
+
|
|
9
|
+
export const fragmentHeader = `
|
|
10
|
+
precision highp float;
|
|
11
|
+
varying vec2 vUv;
|
|
12
|
+
uniform sampler2D uImage0;
|
|
13
|
+
uniform sampler2D uImage1;
|
|
14
|
+
uniform vec2 uSize;
|
|
15
|
+
uniform vec2 uImageSize0;
|
|
16
|
+
uniform vec2 uImageSize1;
|
|
17
|
+
uniform float uTime;
|
|
18
|
+
uniform float uProgress;
|
|
19
|
+
uniform float uAmount;
|
|
20
|
+
uniform float uHasImage;
|
|
21
|
+
vec2 cover(vec2 uv, vec2 imageSize) {
|
|
22
|
+
float aspect = uSize.x / uSize.y;
|
|
23
|
+
float imageAspect = imageSize.x / imageSize.y;
|
|
24
|
+
vec2 scale = vec2(min(aspect / imageAspect, 1.), min(imageAspect / aspect, 1.));
|
|
25
|
+
return (uv - .5) * scale + .5;
|
|
26
|
+
}
|
|
27
|
+
float noise(vec2 point) {
|
|
28
|
+
return fract(sin(dot(point, vec2(12.9898, 78.233))) * 43758.5453);
|
|
29
|
+
}
|
|
30
|
+
`;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// Core
|
|
2
|
+
export { registerMotion, gsap, ScrollTrigger, SplitText, CustomEase, Flip, ELASTIC, EASE } from './core/gsap';
|
|
3
|
+
export { PATHS, springPath, SIGNATURE_LINEAR } from './core/curves';
|
|
4
|
+
export { TIMING } from './core/timing';
|
|
5
|
+
export {
|
|
6
|
+
channels,
|
|
7
|
+
stepChannel,
|
|
8
|
+
createChannelState,
|
|
9
|
+
spring,
|
|
10
|
+
eased,
|
|
11
|
+
bezier,
|
|
12
|
+
easeOut,
|
|
13
|
+
easeOutCubic,
|
|
14
|
+
easeInCubic,
|
|
15
|
+
springEase,
|
|
16
|
+
type Channel,
|
|
17
|
+
type ChannelState,
|
|
18
|
+
} from './core/channels';
|
|
19
|
+
export { splitWords, splitGraphemes, type Split, type SplitWordsOptions } from './core/split';
|
|
20
|
+
export { requestFrame, nextFrame } from './core/frame';
|
|
21
|
+
export { clearStyles, MORPH_STYLES } from './core/styles';
|
|
22
|
+
export { motionSpring, springDuration, springLinear, type MotionSpring } from './core/physics-spring';
|
|
23
|
+
export { trackScroll, type ScrollTrack, type ScrollTrackOptions } from './core/scroll-track';
|
|
24
|
+
|
|
25
|
+
// Motion functions (no instance)
|
|
26
|
+
export { flowIn, flowOut, stepChange, type FlowInOptions, type FlowOutOptions } from './motion/flow';
|
|
27
|
+
export { flowInMenu, flowInPalette, flowInForm, flowInLanding, flowInContent } from './motion/entrances';
|
|
28
|
+
export { revealFrom, type RevealOptions, type RevealHandle, type RevealSpeed } from './motion/reveal';
|
|
29
|
+
export { toggleDetails, installDetailsMotion } from './motion/details';
|
|
30
|
+
export { slideIndicator, type IndicatorOptions } from './motion/indicator';
|
|
31
|
+
export { countTo, revealCounter, tweenNumber, type CountOptions, type NumberFormat } from './motion/count';
|
|
32
|
+
export { captureLayout, reflowLayout, type CapturedLayout } from './motion/layout';
|
|
33
|
+
export { enterMessages, fitComposer, scrollToLatest, type EnterMessagesOptions } from './motion/chat';
|
|
34
|
+
export { animate, leave, ANIMATIONS, type AnimationName, type ExitName, type AnimateOptions, type LeaveOptions } from './motion/effects';
|
|
35
|
+
export { confetti, type ConfettiOptions, type ConfettiHandle } from './motion/confetti';
|
|
36
|
+
export { reduced, entrances, MK_HEAD_SCRIPT } from './core/reduced';
|
|
37
|
+
export { subscribe, whileVisible, type Tick } from './core/loop';
|
|
38
|
+
export { createSpring, type Spring, type Vec } from './core/spring';
|
|
39
|
+
export { clamp, lerp, damp, wrap, mapRange } from './core/math';
|
|
40
|
+
export { bindPointer, pointer, takeMovement } from './core/pointer';
|
|
41
|
+
export { createScope, type Scope } from './core/scope';
|
|
42
|
+
export type { Instance, Cleanup } from './core/types';
|
|
43
|
+
|
|
44
|
+
// Recipes
|
|
45
|
+
export { createSpringCards, type SpringCardsOptions, type SpringCardsInstance } from './recipes/spring-cards/create-spring-cards';
|
|
46
|
+
export {
|
|
47
|
+
createCurvedCarousel,
|
|
48
|
+
type CurvedCarouselOptions,
|
|
49
|
+
type CurvedCarouselInstance,
|
|
50
|
+
} from './recipes/curved-carousel/create-curved-carousel';
|
|
51
|
+
export { createPile, pileLayers, pileStyle, type PileOptions, type PileInstance, type PileLayer } from './recipes/pile/create-pile';
|
|
52
|
+
export { createWords, type WordsOptions, type WordsInstance } from './recipes/words/create-words';
|
|
53
|
+
export { createFlow, type FlowOptions, type FlowInstance } from './recipes/flow/create-flow';
|
|
54
|
+
export { createPop, type PopOptions, type PopInstance } from './recipes/pop/create-pop';
|
|
55
|
+
export { createFitText, type FitTextOptions, type FitTextInstance } from './recipes/fit-text/create-fit-text';
|
|
56
|
+
export { createStretchFooter, type StretchFooterOptions, type StretchFooterInstance } from './recipes/stretch-footer/create-stretch-footer';
|
|
57
|
+
export { createRadialMenu, type RadialMenuOptions, type RadialMenuInstance } from './recipes/radial-menu/create-radial-menu';
|
|
58
|
+
export { createTabs, type TabsOptions, type TabsInstance } from './recipes/tabs/create-tabs';
|
|
59
|
+
export { createCounter, type CounterOptions, type CounterInstance } from './recipes/counter/create-counter';
|
|
60
|
+
export { createDetails, type DetailsInstance } from './recipes/details/create-details';
|
|
61
|
+
export { createReveal, type RevealRecipeOptions, type RevealInstance } from './recipes/reveal/create-reveal';
|
|
62
|
+
export { createReflow, type ReflowOptions, type ReflowInstance } from './recipes/reflow/create-reflow';
|
|
63
|
+
export { createSlotText, type SlotTextOptions, type SlotTextInstance } from './recipes/slot-text/create-slot-text';
|
|
64
|
+
export {
|
|
65
|
+
createSortable,
|
|
66
|
+
moveItem,
|
|
67
|
+
type SortableOptions,
|
|
68
|
+
type SortableInstance,
|
|
69
|
+
type SortableLabels,
|
|
70
|
+
type ReorderDetail,
|
|
71
|
+
} from './recipes/sortable/create-sortable';
|
|
72
|
+
export { createMaskText, type MaskTextOptions, type MaskTextInstance } from './recipes/mask-text/create-mask-text';
|
|
73
|
+
export { createCascade, type CascadeOptions, type CascadeInstance } from './recipes/cascade/create-cascade';
|
|
74
|
+
export { createTextRoll, type TextRollOptions, type TextRollInstance } from './recipes/text-roll/create-text-roll';
|
|
75
|
+
export { createLetters, type LettersOptions, type LettersInstance } from './recipes/letters/create-letters';
|
|
76
|
+
export { createParallax, type ParallaxOptions, type ParallaxInstance } from './recipes/parallax/create-parallax';
|
|
77
|
+
export { createDepthGrid, type DepthGridOptions, type DepthGridInstance } from './recipes/depth-grid/create-depth-grid';
|
|
78
|
+
export { createFrameZoom, type FrameZoomOptions, type FrameZoomInstance } from './recipes/frame-zoom/create-frame-zoom';
|
|
79
|
+
export { createContrast, type ContrastOptions, type ContrastInstance } from './recipes/contrast/create-contrast';
|
|
80
|
+
export { createMarquee, type MarqueeOptions, type MarqueeInstance } from './recipes/marquee/create-marquee';
|
|
81
|
+
export { createCardDock, type CardDockOptions, type CardDockInstance } from './recipes/card-dock/create-card-dock';
|
|
82
|
+
export { createSnapStrip, type SnapStripOptions, type SnapStripInstance } from './recipes/snap-strip/create-snap-strip';
|
|
83
|
+
export { createFillText, type FillTextOptions, type FillTextInstance } from './recipes/fill-text/create-fill-text';
|
|
84
|
+
export { createTicketMenu, type TicketMenuOptions, type TicketMenuInstance } from './recipes/ticket-menu/create-ticket-menu';
|
|
85
|
+
export { createInfiniteReel, type InfiniteReelOptions, type InfiniteReelInstance } from './recipes/infinite-reel/create-infinite-reel';
|
|
86
|
+
export { createRepelBento, type RepelBentoOptions, type RepelBentoInstance } from './recipes/repel-bento/create-repel-bento';
|
|
87
|
+
export { createRotator, type RotatorOptions, type RotatorInstance } from './recipes/rotator/create-rotator';
|
|
88
|
+
export { createDragRuler, type DragRulerOptions, type DragRulerInstance } from './recipes/drag-ruler/create-drag-ruler';
|
|
89
|
+
export { createDockPill, type DockPillOptions, type DockPillInstance } from './recipes/dock-pill/create-dock-pill';
|
|
90
|
+
|
|
91
|
+
// Components (engines; custom elements in /elements, React components in /react)
|
|
92
|
+
export { createThemePicker, type ThemePickerOptions, type ThemePickerInstance } from './components/theme-picker/engine';
|
|
93
|
+
|
|
94
|
+
// Surface engines (dialogs, menus, toasts build on these)
|
|
95
|
+
export { openModal, closeModal, fadeBackdrop, ghostOf, backdropVeil, transparentColor } from './motion/surface';
|
|
96
|
+
export {
|
|
97
|
+
morphOpen,
|
|
98
|
+
morphClose,
|
|
99
|
+
morphCloseWithGhost,
|
|
100
|
+
showOrigin,
|
|
101
|
+
releaseShared,
|
|
102
|
+
shareInto,
|
|
103
|
+
shareBack,
|
|
104
|
+
isMorphing,
|
|
105
|
+
stopMorph,
|
|
106
|
+
type MorphOpenOptions,
|
|
107
|
+
type MorphCloseOptions,
|
|
108
|
+
} from './motion/morph';
|
|
109
|
+
export { liquidOpen, liquidClose } from './motion/liquid';
|
|
110
|
+
export { sheetOpen, sheetClose, pushUnderlay, releaseUnderlay } from './motion/sheet';
|
|
111
|
+
export { attachDragDismiss, type DragDismissOptions } from './motion/drag-dismiss';
|
|
112
|
+
export {
|
|
113
|
+
attachFloating,
|
|
114
|
+
liftWindow,
|
|
115
|
+
dockWindow,
|
|
116
|
+
savedFloat,
|
|
117
|
+
forgetFloat,
|
|
118
|
+
floatingAvailable,
|
|
119
|
+
clampRect,
|
|
120
|
+
type FloatingOptions,
|
|
121
|
+
type Rect,
|
|
122
|
+
} from './motion/floating';
|
|
123
|
+
export { anchorSurface, clearAnchor, type Viewport } from './motion/anchor';
|
|
124
|
+
export {
|
|
125
|
+
createDialog,
|
|
126
|
+
type DialogOptions,
|
|
127
|
+
type DialogInstance,
|
|
128
|
+
type DialogVariant,
|
|
129
|
+
type DialogPlacement,
|
|
130
|
+
type DialogLabels,
|
|
131
|
+
} from './components/dialog/engine';
|
|
132
|
+
export { createMenu, type MenuOptions, type MenuInstance } from './components/menu/engine';
|
|
133
|
+
export { createToaster, type ToasterOptions, type ToasterLabels, type ToasterInstance } from './components/toast/engine';
|
|
134
|
+
export {
|
|
135
|
+
createChatBackground,
|
|
136
|
+
chatBackgroundOf,
|
|
137
|
+
type ChatBackgroundOptions,
|
|
138
|
+
type ChatBackgroundInstance,
|
|
139
|
+
} from './components/chat-background/engine';
|
|
140
|
+
export { audioEnergy, type AudioEnergy } from './components/chat-background/audio';
|
|
141
|
+
export {
|
|
142
|
+
createChat,
|
|
143
|
+
chatMessage,
|
|
144
|
+
capturePending,
|
|
145
|
+
morphPendingToReply,
|
|
146
|
+
PENDING_ID,
|
|
147
|
+
type ChatOptions,
|
|
148
|
+
type ChatInstance,
|
|
149
|
+
type ChatLabels,
|
|
150
|
+
type ChatRole,
|
|
151
|
+
} from './components/chat/engine';
|
|
152
|
+
export {
|
|
153
|
+
createNav,
|
|
154
|
+
revealShell,
|
|
155
|
+
NAV_STYLES,
|
|
156
|
+
type NavOptions,
|
|
157
|
+
type NavInstance,
|
|
158
|
+
type NavStyle,
|
|
159
|
+
type NavigateDetail,
|
|
160
|
+
} from './components/nav/engine';
|
|
161
|
+
export { createSteps, type StepsOptions, type StepsInstance, type StepDetail } from './components/steps/engine';
|
|
162
|
+
export { createCursor, type CursorOptions, type CursorInstance, type CursorVariant } from './components/cursor/engine';
|
|
163
|
+
export {
|
|
164
|
+
toast,
|
|
165
|
+
dismissToast,
|
|
166
|
+
removeToast,
|
|
167
|
+
clearToasts,
|
|
168
|
+
getToasts,
|
|
169
|
+
subscribeToasts,
|
|
170
|
+
toastTimers,
|
|
171
|
+
TOAST_LIMIT,
|
|
172
|
+
type ToastOptions,
|
|
173
|
+
type ToastItem,
|
|
174
|
+
type ToastTone,
|
|
175
|
+
type ToastAction,
|
|
176
|
+
} from './components/toast/store';
|
|
177
|
+
export { createIsland, type IslandOptions, type IslandInstance } from './components/island/engine';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Place an anchored surface (menu, anchored dialog) under its opener, left
|
|
3
|
+
* aligned with it, inside the viewport: above when there is no room below,
|
|
4
|
+
* beside it when neither fits (never covering it), else as low as it can go.
|
|
5
|
+
* Under 680 px the stylesheet's full-screen sheet wins, so the inline place is cleared.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const EDGE = 16;
|
|
9
|
+
const GAP = 8;
|
|
10
|
+
const PHONE = 680;
|
|
11
|
+
/** Shorthands and longhands: some engines keep the longhands of a removed shorthand. */
|
|
12
|
+
const PLACED = ['top', 'left', 'right', 'bottom', 'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left'];
|
|
13
|
+
|
|
14
|
+
export interface Viewport {
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function anchorSurface(surface: HTMLElement, origin: DOMRect | null | undefined, viewport?: Viewport) {
|
|
20
|
+
const view = viewport ?? { width: innerWidth, height: innerHeight };
|
|
21
|
+
if (!origin || view.width < PHONE) {
|
|
22
|
+
for (const side of PLACED) surface.style.removeProperty(side);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const box = surface.getBoundingClientRect();
|
|
26
|
+
const clampLeft = (value: number) => Math.max(EDGE, Math.min(value, view.width - box.width - EDGE));
|
|
27
|
+
const below = origin.bottom + GAP;
|
|
28
|
+
const above = origin.top - GAP - box.height;
|
|
29
|
+
const lowest = Math.max(EDGE, view.height - box.height - EDGE);
|
|
30
|
+
const besideRight = origin.right + GAP;
|
|
31
|
+
const besideLeft = origin.left - GAP - box.width;
|
|
32
|
+
let top: number;
|
|
33
|
+
let left: number;
|
|
34
|
+
if (below <= lowest) [top, left] = [below, clampLeft(origin.left)];
|
|
35
|
+
else if (above >= EDGE) [top, left] = [above, clampLeft(origin.left)];
|
|
36
|
+
else if (besideRight + box.width <= view.width - EDGE) [top, left] = [Math.min(Math.max(EDGE, origin.top), lowest), besideRight];
|
|
37
|
+
else if (besideLeft >= EDGE) [top, left] = [Math.min(Math.max(EDGE, origin.top), lowest), besideLeft];
|
|
38
|
+
else [top, left] = [lowest, clampLeft(origin.left)];
|
|
39
|
+
Object.assign(surface.style, { margin: '0', top: `${Math.round(top)}px`, left: `${Math.round(left)}px`, right: 'auto', bottom: 'auto' });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Remove the inline place anchorSurface wrote. */
|
|
43
|
+
export function clearAnchor(surface: HTMLElement) {
|
|
44
|
+
for (const side of PLACED) surface.style.removeProperty(side);
|
|
45
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { gsap, EASE, registerMotion } from '../core/gsap';
|
|
2
|
+
import { reduced } from '../core/reduced';
|
|
3
|
+
import { TIMING } from '../core/timing';
|
|
4
|
+
import { splitWords } from '../core/split';
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* Stepbro's chat (PrettyChat): new bubbles grow from their speaker's side
|
|
8
|
+
* (scale .5, 24 px blur, 0.7 s, 60 ms apart) while their words rise into place
|
|
9
|
+
* (16 px, 6 px blur, 0.25 s, 50 ms apart, back.out). Words are split only for
|
|
10
|
+
* the animation; tables and code stay whole; if splitting would reflow the text
|
|
11
|
+
* by more than half a line, the text block moves as one.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export interface EnterMessagesOptions {
|
|
15
|
+
/** Text inside each bubble whose words rise. */
|
|
16
|
+
textSelector?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const isUser = (bubble: HTMLElement) => bubble.dataset.mkRole === 'user' || bubble.classList.contains('user');
|
|
20
|
+
|
|
21
|
+
export function enterMessages(bubbles: HTMLElement[], { textSelector = '[data-mk-text], .message-text' }: EnterMessagesOptions = {}) {
|
|
22
|
+
if (!bubbles.length || reduced()) return null;
|
|
23
|
+
registerMotion();
|
|
24
|
+
const reverts: Array<() => void> = [];
|
|
25
|
+
const cleanup = () => {
|
|
26
|
+
for (const revert of reverts.splice(0)) revert();
|
|
27
|
+
};
|
|
28
|
+
const timeline = gsap.timeline({ onComplete: cleanup, onInterrupt: cleanup });
|
|
29
|
+
bubbles.forEach((bubble, index) => {
|
|
30
|
+
gsap.set(bubble, { transformOrigin: isUser(bubble) ? '100% 50%' : '0% 50%' });
|
|
31
|
+
timeline.fromTo(
|
|
32
|
+
bubble,
|
|
33
|
+
{ scale: 0.5, autoAlpha: 0, filter: 'blur(24px)' },
|
|
34
|
+
{ scale: 1, autoAlpha: 1, filter: 'blur(0px)', duration: TIMING.bubble, ease: EASE.signature, clearProps: 'transform,opacity,visibility,filter' },
|
|
35
|
+
index * TIMING.bubbleStagger,
|
|
36
|
+
);
|
|
37
|
+
const text = bubble.querySelector<HTMLElement>(textSelector);
|
|
38
|
+
if (!text) return;
|
|
39
|
+
const before = text.offsetHeight;
|
|
40
|
+
const split = splitWords(text);
|
|
41
|
+
const lineHeight = parseFloat(getComputedStyle(text).lineHeight) || 0;
|
|
42
|
+
let targets: HTMLElement[] = split.parts;
|
|
43
|
+
if (!split.parts.length || text.offsetHeight - before > lineHeight / 2) {
|
|
44
|
+
split.revert();
|
|
45
|
+
targets = [text];
|
|
46
|
+
} else reverts.push(() => split.revert());
|
|
47
|
+
timeline.fromTo(
|
|
48
|
+
targets,
|
|
49
|
+
{ y: 16, autoAlpha: 0, filter: 'blur(6px)' },
|
|
50
|
+
{
|
|
51
|
+
y: 0,
|
|
52
|
+
autoAlpha: 1,
|
|
53
|
+
filter: 'blur(0px)',
|
|
54
|
+
duration: TIMING.word,
|
|
55
|
+
ease: 'back.out(1)',
|
|
56
|
+
stagger: TIMING.wordStagger,
|
|
57
|
+
clearProps: 'transform,opacity,visibility,filter',
|
|
58
|
+
},
|
|
59
|
+
index * TIMING.bubbleStagger,
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
return timeline;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** A message box grows or shrinks to its text (0.18 s, power2.out), from `min` px. */
|
|
66
|
+
export function fitComposer(field: HTMLTextAreaElement, min = 64) {
|
|
67
|
+
const from = field.offsetHeight;
|
|
68
|
+
field.style.height = 'auto';
|
|
69
|
+
const to = Math.max(min, field.scrollHeight);
|
|
70
|
+
field.style.height = `${from}px`;
|
|
71
|
+
if (from === to || reduced()) {
|
|
72
|
+
field.style.height = `${to}px`;
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
registerMotion();
|
|
76
|
+
return gsap.to(field, { height: to, duration: TIMING.composer, ease: 'power2.out', overwrite: 'auto' });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Scroll a conversation to its newest message (0.35 s, Stepbro's chat scroll curve). */
|
|
80
|
+
export function scrollToLatest(scroller: HTMLElement) {
|
|
81
|
+
const top = scroller.scrollHeight - scroller.clientHeight;
|
|
82
|
+
if (reduced()) {
|
|
83
|
+
scroller.scrollTop = top;
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
registerMotion();
|
|
87
|
+
const state = { top: scroller.scrollTop };
|
|
88
|
+
return gsap.to(state, {
|
|
89
|
+
top,
|
|
90
|
+
duration: TIMING.chatScroll,
|
|
91
|
+
ease: EASE.chatScroll,
|
|
92
|
+
overwrite: 'auto',
|
|
93
|
+
onUpdate: () => {
|
|
94
|
+
scroller.scrollTop = state.top;
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|