@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,9 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { DependencyList, RefObject } from 'react';
|
|
4
|
+
import { useRecipe } from './use-recipe';
|
|
5
|
+
import { createParallax, type ParallaxOptions } from '../recipes/parallax/create-parallax';
|
|
6
|
+
|
|
7
|
+
/** Siena's photo parallax inside its frame. */
|
|
8
|
+
export const useParallax = (ref: RefObject<HTMLElement | null>, options?: ParallaxOptions, deps?: DependencyList) =>
|
|
9
|
+
useRecipe(ref, createParallax, options, deps);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { DependencyList, RefObject } from 'react';
|
|
4
|
+
import { useRecipe } from './use-recipe';
|
|
5
|
+
import { createParticleHero, type ParticleHeroOptions } from '../gl/particle-hero/create-particle-hero';
|
|
6
|
+
|
|
7
|
+
/** iyo.ai's particle hero (WebGL2, Canvas2D fallback). The canvas joins the ref's element. */
|
|
8
|
+
export const useParticleHero = (ref: RefObject<HTMLElement | null>, options?: ParticleHeroOptions, deps?: DependencyList) =>
|
|
9
|
+
useRecipe(ref, createParticleHero, options, deps);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { DependencyList, RefObject } from 'react';
|
|
4
|
+
import { useRecipe } from './use-recipe';
|
|
5
|
+
import { createPile, type PileOptions } from '../recipes/pile/create-pile';
|
|
6
|
+
|
|
7
|
+
export const usePile = (ref: RefObject<HTMLElement | null>, options?: PileOptions, deps?: DependencyList) =>
|
|
8
|
+
useRecipe(ref, createPile, options, deps);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { DependencyList, RefObject } from 'react';
|
|
4
|
+
import { useRecipe } from './use-recipe';
|
|
5
|
+
import { createPop, type PopOptions } from '../recipes/pop/create-pop';
|
|
6
|
+
|
|
7
|
+
export const usePop = (ref: RefObject<HTMLElement | null>, options?: PopOptions, deps?: DependencyList) =>
|
|
8
|
+
useRecipe(ref, createPop, options, deps);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { DependencyList, RefObject } from 'react';
|
|
4
|
+
import { useLatest, useRecipe } from './use-recipe';
|
|
5
|
+
import { createRadialMenu, type RadialMenuOptions } from '../recipes/radial-menu/create-radial-menu';
|
|
6
|
+
|
|
7
|
+
export function useRadialMenu(ref: RefObject<HTMLElement | null>, options: RadialMenuOptions = {}, deps?: DependencyList) {
|
|
8
|
+
const onToggle = useLatest(options.onToggle);
|
|
9
|
+
return useRecipe(ref, createRadialMenu, { ...options, onToggle: (open) => onToggle.current?.(open) }, deps);
|
|
10
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useLayoutEffect, useRef, type DependencyList, type RefObject } from 'react';
|
|
4
|
+
import type { Instance } from '../core/types';
|
|
5
|
+
|
|
6
|
+
/** useLayoutEffect in the browser (no flash), useEffect during SSR (no warning). */
|
|
7
|
+
export const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Create a recipe on `ref.current` after mount and destroy it on unmount or when
|
|
11
|
+
* `deps` change. Returns a ref holding the live instance (controls like `next()`).
|
|
12
|
+
*/
|
|
13
|
+
export function useRecipe<I extends Instance, O>(
|
|
14
|
+
ref: RefObject<HTMLElement | null>,
|
|
15
|
+
factory: (el: HTMLElement | null, options?: O) => I,
|
|
16
|
+
options?: O,
|
|
17
|
+
deps: DependencyList = [],
|
|
18
|
+
): RefObject<I | null> {
|
|
19
|
+
const instance = useRef<I | null>(null);
|
|
20
|
+
useIsomorphicLayoutEffect(() => {
|
|
21
|
+
const created = factory(ref.current, options);
|
|
22
|
+
instance.current = created;
|
|
23
|
+
return () => {
|
|
24
|
+
created.destroy();
|
|
25
|
+
instance.current = null;
|
|
26
|
+
};
|
|
27
|
+
// Options are read once per creation; pass `deps` to recreate when they change.
|
|
28
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
29
|
+
}, deps);
|
|
30
|
+
return instance;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Keep the latest callback in a ref so recipes do not restart on every render. */
|
|
34
|
+
export function useLatest<T>(value: T) {
|
|
35
|
+
const ref = useRef(value);
|
|
36
|
+
ref.current = value;
|
|
37
|
+
return ref;
|
|
38
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { DependencyList, RefObject } from 'react';
|
|
4
|
+
import { useRecipe } from './use-recipe';
|
|
5
|
+
import { createReflow, type ReflowOptions } from '../recipes/reflow/create-reflow';
|
|
6
|
+
|
|
7
|
+
/** Call `.current.capture()` before the state change and `.current.play()` in a layout effect after it. */
|
|
8
|
+
export const useReflow = (ref: RefObject<HTMLElement | null>, options?: ReflowOptions, deps?: DependencyList) =>
|
|
9
|
+
useRecipe(ref, createReflow, options, deps);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { DependencyList, RefObject } from 'react';
|
|
4
|
+
import { useRecipe } from './use-recipe';
|
|
5
|
+
import { createRepelBento, type RepelBentoOptions } from '../recipes/repel-bento/create-repel-bento';
|
|
6
|
+
|
|
7
|
+
export const useRepelBento = (ref: RefObject<HTMLElement | null>, options?: RepelBentoOptions, deps?: DependencyList) =>
|
|
8
|
+
useRecipe(ref, createRepelBento, options, deps);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { DependencyList, RefObject } from 'react';
|
|
4
|
+
import { useRecipe } from './use-recipe';
|
|
5
|
+
import { createReveal, type RevealRecipeOptions } from '../recipes/reveal/create-reveal';
|
|
6
|
+
|
|
7
|
+
export const useReveal = (ref: RefObject<HTMLElement | null>, options?: RevealRecipeOptions, deps?: DependencyList) =>
|
|
8
|
+
useRecipe(ref, createReveal, options, deps);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { DependencyList, RefObject } from 'react';
|
|
4
|
+
import { useLatest, useRecipe } from './use-recipe';
|
|
5
|
+
import { createRotator, type RotatorOptions } from '../recipes/rotator/create-rotator';
|
|
6
|
+
|
|
7
|
+
export function useRotator(ref: RefObject<HTMLElement | null>, options: RotatorOptions = {}, deps?: DependencyList) {
|
|
8
|
+
const callback = useLatest(options.onChange);
|
|
9
|
+
return useRecipe(ref, createRotator, { ...options, onChange: (index) => callback.current?.(index) }, deps);
|
|
10
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { DependencyList, RefObject } from 'react';
|
|
4
|
+
import { useRecipe } from './use-recipe';
|
|
5
|
+
import { createShaderBlur, type ShaderBlurOptions } from '../gl/shader-blur/create-shader-blur';
|
|
6
|
+
|
|
7
|
+
export const useShaderBlur = (ref: RefObject<HTMLElement | null>, options?: ShaderBlurOptions, deps?: DependencyList) =>
|
|
8
|
+
useRecipe(ref, createShaderBlur, options, deps);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { DependencyList, RefObject } from 'react';
|
|
4
|
+
import { useRecipe } from './use-recipe';
|
|
5
|
+
import { createShaderGrain, type ShaderGrainOptions } from '../gl/shader-grain/create-shader-grain';
|
|
6
|
+
|
|
7
|
+
export const useShaderGrain = (ref: RefObject<HTMLElement | null>, options?: ShaderGrainOptions, deps?: DependencyList) =>
|
|
8
|
+
useRecipe(ref, createShaderGrain, options, deps);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { RefObject } from 'react';
|
|
4
|
+
import { useIsomorphicLayoutEffect, useRecipe } from './use-recipe';
|
|
5
|
+
import { createSlotText, type SlotTextOptions } from '../recipes/slot-text/create-slot-text';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Rolls `text` into `ref` whenever it changes. The element must render no
|
|
9
|
+
* children of its own: the recipe writes the letters (see `<SlotText>`).
|
|
10
|
+
*/
|
|
11
|
+
export function useSlotText(ref: RefObject<HTMLElement | null>, text: string, options: Omit<SlotTextOptions, 'text'> = {}) {
|
|
12
|
+
const instance = useRecipe(ref, createSlotText, { ...options, text });
|
|
13
|
+
useIsomorphicLayoutEffect(() => {
|
|
14
|
+
instance.current?.set(text);
|
|
15
|
+
}, [text]);
|
|
16
|
+
return instance;
|
|
17
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { DependencyList, RefObject } from 'react';
|
|
4
|
+
import { useRecipe } from './use-recipe';
|
|
5
|
+
import { createSnapStrip, type SnapStripOptions } from '../recipes/snap-strip/create-snap-strip';
|
|
6
|
+
|
|
7
|
+
/** iyo.ai's phone cards: an autoplaying scroll-snap strip. */
|
|
8
|
+
export const useSnapStrip = (ref: RefObject<HTMLElement | null>, options?: SnapStripOptions, deps?: DependencyList) =>
|
|
9
|
+
useRecipe(ref, createSnapStrip, options, deps);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { DependencyList, RefObject } from 'react';
|
|
4
|
+
import { useLatest, useRecipe } from './use-recipe';
|
|
5
|
+
import { createSortable, type SortableOptions } from '../recipes/sortable/create-sortable';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Drag to reorder a list React renders: answer `onReorder` by updating your state
|
|
9
|
+
* (React moves the DOM; the engine waits for it and lands the item). `mutate` is off.
|
|
10
|
+
*/
|
|
11
|
+
export function useSortable(ref: RefObject<HTMLElement | null>, options: SortableOptions = {}, deps?: DependencyList) {
|
|
12
|
+
const onReorder = useLatest(options.onReorder);
|
|
13
|
+
return useRecipe(ref, createSortable, { mutate: false, ...options, onReorder: (detail) => onReorder.current?.(detail) }, deps);
|
|
14
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { DependencyList, RefObject } from 'react';
|
|
4
|
+
import { useRecipe } from './use-recipe';
|
|
5
|
+
import { createSpringCards, type SpringCardsOptions } from '../recipes/spring-cards/create-spring-cards';
|
|
6
|
+
|
|
7
|
+
export const useSpringCards = (ref: RefObject<HTMLElement | null>, options?: SpringCardsOptions, deps?: DependencyList) =>
|
|
8
|
+
useRecipe(ref, createSpringCards, options, deps);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { DependencyList, RefObject } from 'react';
|
|
4
|
+
import { useRecipe } from './use-recipe';
|
|
5
|
+
import { createStretchFooter, type StretchFooterOptions } from '../recipes/stretch-footer/create-stretch-footer';
|
|
6
|
+
|
|
7
|
+
export interface UseStretchFooterOptions extends Omit<StretchFooterOptions, 'phantom' | 'title'> {
|
|
8
|
+
phantomRef?: RefObject<HTMLElement | null>;
|
|
9
|
+
titleRef?: RefObject<HTMLElement | null>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useStretchFooter(ref: RefObject<HTMLElement | null>, options: UseStretchFooterOptions = {}, deps?: DependencyList) {
|
|
13
|
+
const { phantomRef, titleRef, ...rest } = options;
|
|
14
|
+
return useRecipe(
|
|
15
|
+
ref,
|
|
16
|
+
(el) => createStretchFooter(el, { ...rest, phantom: phantomRef?.current ?? undefined, title: titleRef?.current ?? undefined }),
|
|
17
|
+
undefined,
|
|
18
|
+
deps,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { DependencyList, RefObject } from 'react';
|
|
4
|
+
import { useLatest, useRecipe } from './use-recipe';
|
|
5
|
+
import { createTabs, type TabsOptions } from '../recipes/tabs/create-tabs';
|
|
6
|
+
|
|
7
|
+
export function useTabs(ref: RefObject<HTMLElement | null>, options: TabsOptions = {}, deps?: DependencyList) {
|
|
8
|
+
const onSelect = useLatest(options.onSelect);
|
|
9
|
+
return useRecipe(ref, createTabs, { ...options, onSelect: (index, tab) => onSelect.current?.(index, tab) }, deps);
|
|
10
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { DependencyList, RefObject } from 'react';
|
|
4
|
+
import { useRecipe } from './use-recipe';
|
|
5
|
+
import { createTextRoll, type TextRollOptions } from '../recipes/text-roll/create-text-roll';
|
|
6
|
+
|
|
7
|
+
/** Siena's slot title. `.current.play()` spins it. */
|
|
8
|
+
export const useTextRoll = (ref: RefObject<HTMLElement | null>, options?: TextRollOptions, deps?: DependencyList) =>
|
|
9
|
+
useRecipe(ref, createTextRoll, options, deps);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { DependencyList, RefObject } from 'react';
|
|
4
|
+
import { useLatest, useRecipe } from './use-recipe';
|
|
5
|
+
import { createTicketMenu, type TicketMenuOptions } from '../recipes/ticket-menu/create-ticket-menu';
|
|
6
|
+
|
|
7
|
+
export function useTicketMenu(ref: RefObject<HTMLElement | null>, options: TicketMenuOptions = {}, deps?: DependencyList) {
|
|
8
|
+
const latest = useLatest(options);
|
|
9
|
+
return useRecipe(ref, createTicketMenu, {
|
|
10
|
+
...options,
|
|
11
|
+
onSelect: (value, item) => latest.current.onSelect?.(value, item),
|
|
12
|
+
onOpen: () => latest.current.onOpen?.(),
|
|
13
|
+
onClose: () => latest.current.onClose?.(),
|
|
14
|
+
}, deps);
|
|
15
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { DependencyList, RefObject } from 'react';
|
|
4
|
+
import { useRecipe } from './use-recipe';
|
|
5
|
+
import { createWords, type WordsOptions } from '../recipes/words/create-words';
|
|
6
|
+
|
|
7
|
+
export const useWords = (ref: RefObject<HTMLElement | null>, options?: WordsOptions, deps?: DependencyList) =>
|
|
8
|
+
useRecipe(ref, createWords, options, deps);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# card-dock
|
|
2
|
+
|
|
3
|
+
Un dock de tarjetas de cristal apiladas: las que están detrás de la del frente bajan 90 % / 180 % / 270 % (360 % la quinta) y encogen a .93 / .87 / .8 (.73). Al pasar el puntero o enfocar se despliegan hacia arriba (0.8 s `cubic-bezier(.48,.01,0,1.08)`, con un pequeño rebote), la tarjeta bajo el puntero crece de 4rem a 4.5rem (0.6 s) y su nombre sale hacia la izquierda; al salir se apilan de nuevo (0.64 s). Al cargar se ven desplegadas 1.6 s y luego se apilan y aparece el CTA.
|
|
4
|
+
|
|
5
|
+
Origen: iyo.ai, dock fijo de productos de la home (`product-cards`, `PM`): cristal `blur(19px)` sobre `#38383899` (`#555555b0` al pasar), borde `#8a8a8a1a`, radio .9rem, nombres a .7rem con tracking .07em. El movimiento es CSS; la receta añade la presentación de carga, el primer toque en tabletas táctiles (768–1024 px sin hover: abre en vez de navegar), Escape y clic fuera.
|
|
6
|
+
|
|
7
|
+
## HTML / Astro
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<div data-mk="card-dock" class="mk-card-dock mk-card-dock--fixed" role="list" aria-label="Productos">
|
|
11
|
+
<div class="mk-card-dock__item" role="listitem">
|
|
12
|
+
<a class="mk-card-dock__card" href="/producto-a" aria-label="Producto A"><img src="/a.avif" alt="" /></a>
|
|
13
|
+
<span class="mk-card-dock__title" aria-hidden="true">Producto A</span>
|
|
14
|
+
</div>
|
|
15
|
+
<!-- … hasta cinco; la última es la del frente -->
|
|
16
|
+
<span class="mk-card-dock__cta" aria-hidden="true">Shop now</span>
|
|
17
|
+
</div>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`mk-card-dock--fixed` lo fija abajo a la derecha (3rem / 2rem); sin ella queda en el flujo, creciendo hacia arriba desde su sitio.
|
|
21
|
+
|
|
22
|
+
## React
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
26
|
+
const dock = useCardDock(ref, { intro: 1600 });
|
|
27
|
+
// dock.current?.expand() / collapse()
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## API
|
|
31
|
+
|
|
32
|
+
`createCardDock(root, { intro })` → `{ expand(), collapse(), expanded, destroy }`. `intro`: milisegundos desplegado al cargar (0 lo omite). Estados en la raíz: `data-mk-spread`, `data-mk-settled`, `data-mk-expanded`. Variables: `--mk-card-dock-size(-hover)`, `--mk-card-dock-surface(-hover)`, `--mk-card-dock-border`, `--mk-card-dock-fg`, `--mk-card-dock-height`, `--mk-ease-dock`, `--mk-ease-dock-size`, `--mk-ease-dock-fade`.
|
|
33
|
+
|
|
34
|
+
## Accesibilidad
|
|
35
|
+
|
|
36
|
+
El foco despliega la pila y muestra los nombres, así que el teclado ve lo mismo que el puntero; pon el nombre accesible en el enlace (`aria-label`) y deja el título visible `aria-hidden`. Con movimiento reducido la pila queda desplegada con sus nombres y sin transiciones.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* iyo.ai's product dock. Cards are .mk-card-dock__item children (the last one
|
|
3
|
+
* is the front card); the optional .mk-card-dock__cta shows next to the stack
|
|
4
|
+
* once it has settled. Without JavaScript the stack still opens on hover/focus.
|
|
5
|
+
*/
|
|
6
|
+
.mk-card-dock {
|
|
7
|
+
--mk-card-dock-size: 4rem;
|
|
8
|
+
--mk-card-dock-size-hover: 4.5rem;
|
|
9
|
+
--mk-card-dock-surface: #38383899;
|
|
10
|
+
--mk-card-dock-surface-hover: #555555b0;
|
|
11
|
+
--mk-card-dock-border: #8a8a8a1a;
|
|
12
|
+
--mk-card-dock-fg: #f1f0ed;
|
|
13
|
+
--mk-card-dock-height: 7.2vw;
|
|
14
|
+
--mk-ease-dock: cubic-bezier(0.48, 0.01, 0, 1.08);
|
|
15
|
+
--mk-ease-dock-size: cubic-bezier(0.77, 0, 0.175, 1);
|
|
16
|
+
--mk-ease-dock-fade: cubic-bezier(0.65, 0, 0.35, 1);
|
|
17
|
+
position: relative;
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
align-items: flex-end;
|
|
21
|
+
justify-content: flex-end;
|
|
22
|
+
width: var(--mk-card-dock-size-hover);
|
|
23
|
+
height: var(--mk-card-dock-height);
|
|
24
|
+
isolation: isolate;
|
|
25
|
+
color: var(--mk-card-dock-fg);
|
|
26
|
+
}
|
|
27
|
+
.mk-card-dock--fixed {
|
|
28
|
+
position: fixed;
|
|
29
|
+
z-index: 3;
|
|
30
|
+
right: 3rem;
|
|
31
|
+
bottom: 2rem;
|
|
32
|
+
}
|
|
33
|
+
.mk-card-dock__item {
|
|
34
|
+
position: relative;
|
|
35
|
+
flex: none;
|
|
36
|
+
padding: 0.18rem;
|
|
37
|
+
transition: transform 0.64s var(--mk-ease-dock);
|
|
38
|
+
}
|
|
39
|
+
.mk-card-dock__item:nth-last-child(2 of .mk-card-dock__item) {
|
|
40
|
+
transform: translateY(90%) scale(0.93);
|
|
41
|
+
}
|
|
42
|
+
.mk-card-dock__item:nth-last-child(3 of .mk-card-dock__item) {
|
|
43
|
+
transform: translateY(180%) scale(0.87);
|
|
44
|
+
}
|
|
45
|
+
.mk-card-dock__item:nth-last-child(4 of .mk-card-dock__item) {
|
|
46
|
+
transform: translateY(270%) scale(0.8);
|
|
47
|
+
}
|
|
48
|
+
.mk-card-dock__item:nth-last-child(5 of .mk-card-dock__item) {
|
|
49
|
+
transform: translateY(360%) scale(0.73);
|
|
50
|
+
}
|
|
51
|
+
.mk-card-dock__card {
|
|
52
|
+
position: relative;
|
|
53
|
+
z-index: 1;
|
|
54
|
+
display: flex;
|
|
55
|
+
box-sizing: border-box;
|
|
56
|
+
width: var(--mk-card-dock-size);
|
|
57
|
+
height: var(--mk-card-dock-size);
|
|
58
|
+
padding: 0.25rem 0.25rem 0.23rem;
|
|
59
|
+
border: 1px solid var(--mk-card-dock-border);
|
|
60
|
+
border-radius: 0.9rem;
|
|
61
|
+
background: var(--mk-card-dock-surface);
|
|
62
|
+
color: inherit;
|
|
63
|
+
-webkit-backdrop-filter: blur(19px);
|
|
64
|
+
backdrop-filter: blur(19px);
|
|
65
|
+
transition:
|
|
66
|
+
width 0.6s var(--mk-ease-dock-size),
|
|
67
|
+
height 0.6s var(--mk-ease-dock-size),
|
|
68
|
+
background-color 0.3s var(--mk-ease-dock-fade);
|
|
69
|
+
}
|
|
70
|
+
.mk-card-dock__card :is(img, video, svg) {
|
|
71
|
+
width: 100%;
|
|
72
|
+
height: 100%;
|
|
73
|
+
object-fit: contain;
|
|
74
|
+
}
|
|
75
|
+
.mk-card-dock__title {
|
|
76
|
+
position: absolute;
|
|
77
|
+
inset: 0;
|
|
78
|
+
display: flex;
|
|
79
|
+
align-items: center;
|
|
80
|
+
justify-content: flex-end;
|
|
81
|
+
font-size: 0.7rem;
|
|
82
|
+
letter-spacing: 0.07em;
|
|
83
|
+
line-height: 1.1rem;
|
|
84
|
+
text-transform: uppercase;
|
|
85
|
+
white-space: nowrap;
|
|
86
|
+
pointer-events: none;
|
|
87
|
+
opacity: 0;
|
|
88
|
+
transform: translateX(-15%);
|
|
89
|
+
transition:
|
|
90
|
+
opacity 0.5s var(--mk-ease-dock-fade),
|
|
91
|
+
transform 0.4s var(--mk-ease-dock-fade);
|
|
92
|
+
}
|
|
93
|
+
.mk-card-dock__cta {
|
|
94
|
+
position: absolute;
|
|
95
|
+
right: 0;
|
|
96
|
+
bottom: 0.18rem;
|
|
97
|
+
display: flex;
|
|
98
|
+
align-items: center;
|
|
99
|
+
justify-content: flex-end;
|
|
100
|
+
width: 9.3rem;
|
|
101
|
+
height: var(--mk-card-dock-size);
|
|
102
|
+
padding-right: calc(var(--mk-card-dock-size) + 1rem);
|
|
103
|
+
box-sizing: border-box;
|
|
104
|
+
font-size: 0.7rem;
|
|
105
|
+
letter-spacing: 0.07em;
|
|
106
|
+
text-transform: uppercase;
|
|
107
|
+
white-space: nowrap;
|
|
108
|
+
pointer-events: none;
|
|
109
|
+
opacity: 0;
|
|
110
|
+
transition: opacity 0.5s var(--mk-ease-dock-fade);
|
|
111
|
+
}
|
|
112
|
+
.mk-card-dock[data-mk-settled] .mk-card-dock__cta {
|
|
113
|
+
opacity: 1;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* Spread: the load presentation, the tap on tablets, keyboard focus. */
|
|
117
|
+
.mk-card-dock:is([data-mk-spread], [data-mk-expanded], :focus-within) {
|
|
118
|
+
height: auto;
|
|
119
|
+
}
|
|
120
|
+
.mk-card-dock:is([data-mk-spread], [data-mk-expanded], :focus-within) > .mk-card-dock__item {
|
|
121
|
+
transform: none;
|
|
122
|
+
transition-duration: 0.8s;
|
|
123
|
+
}
|
|
124
|
+
.mk-card-dock:is([data-mk-expanded], :focus-within) .mk-card-dock__title {
|
|
125
|
+
opacity: 1;
|
|
126
|
+
transform: translateX(-120%);
|
|
127
|
+
}
|
|
128
|
+
.mk-card-dock:is([data-mk-spread], [data-mk-expanded], :focus-within) .mk-card-dock__cta {
|
|
129
|
+
opacity: 0;
|
|
130
|
+
}
|
|
131
|
+
@media (hover: hover) {
|
|
132
|
+
.mk-card-dock:hover {
|
|
133
|
+
height: auto;
|
|
134
|
+
}
|
|
135
|
+
.mk-card-dock:hover > .mk-card-dock__item {
|
|
136
|
+
transform: none;
|
|
137
|
+
transition-duration: 0.8s;
|
|
138
|
+
}
|
|
139
|
+
.mk-card-dock:hover .mk-card-dock__cta {
|
|
140
|
+
opacity: 0;
|
|
141
|
+
}
|
|
142
|
+
.mk-card-dock__item:is(:hover, :focus-within) .mk-card-dock__card {
|
|
143
|
+
width: var(--mk-card-dock-size-hover);
|
|
144
|
+
height: var(--mk-card-dock-size-hover);
|
|
145
|
+
background-color: var(--mk-card-dock-surface-hover);
|
|
146
|
+
}
|
|
147
|
+
.mk-card-dock__item:is(:hover, :focus-within) .mk-card-dock__title {
|
|
148
|
+
opacity: 1;
|
|
149
|
+
transform: translateX(-120%);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
|
|
153
|
+
.mk-card-dock {
|
|
154
|
+
--mk-card-dock-surface: #383838e6;
|
|
155
|
+
--mk-card-dock-surface-hover: #555555f0;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/* Still: the stack stays open with its names. */
|
|
159
|
+
@media (prefers-reduced-motion: reduce) {
|
|
160
|
+
.mk-card-dock {
|
|
161
|
+
height: auto;
|
|
162
|
+
}
|
|
163
|
+
/* As specific as the stacked rules (:nth-last-child(n of …)), and later. */
|
|
164
|
+
.mk-card-dock > .mk-card-dock__item:nth-last-child(n of .mk-card-dock__item) {
|
|
165
|
+
transform: none;
|
|
166
|
+
}
|
|
167
|
+
.mk-card-dock .mk-card-dock__title {
|
|
168
|
+
opacity: 1;
|
|
169
|
+
transform: translateX(-120%);
|
|
170
|
+
}
|
|
171
|
+
.mk-card-dock .mk-card-dock__cta {
|
|
172
|
+
display: none;
|
|
173
|
+
}
|
|
174
|
+
.mk-card-dock,
|
|
175
|
+
.mk-card-dock * {
|
|
176
|
+
transition: none !important;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { reduced } from '../../core/reduced';
|
|
2
|
+
import { createScope } from '../../core/scope';
|
|
3
|
+
import { TIMING } from '../../core/timing';
|
|
4
|
+
import { inert, warnOnce, type Instance } from '../../core/types';
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* iyo.ai's product dock: a stack of glass cards where the ones behind the front
|
|
8
|
+
* card drop 90 % / 180 % / 270 % and shrink to .93 / .87 / .8. Hover or focus
|
|
9
|
+
* spreads them out (0.8 s, cubic-bezier(.48,.01,0,1.08)), the card under the
|
|
10
|
+
* pointer grows and its name slides out; leaving stacks them again (0.64 s).
|
|
11
|
+
* The motion is CSS (card-dock.css); this adds the 1.6 s spread on load, the
|
|
12
|
+
* first tap on touch tablets, Escape / outside click and manual control.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface CardDockOptions {
|
|
16
|
+
/** Milliseconds the cards stay spread out on load before stacking (iyo: 1600; 0 skips it). */
|
|
17
|
+
intro?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CardDockInstance extends Instance {
|
|
21
|
+
expand(): void;
|
|
22
|
+
collapse(): void;
|
|
23
|
+
readonly expanded: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const TABLET_TOUCH = '(min-width: 768px) and (max-width: 1024px) and (hover: none)';
|
|
27
|
+
|
|
28
|
+
export function createCardDock(root: HTMLElement | null, options: CardDockOptions = {}): CardDockInstance {
|
|
29
|
+
if (!root) {
|
|
30
|
+
warnOnce('card-dock: missing element');
|
|
31
|
+
return inert({ expand() {}, collapse() {}, expanded: false });
|
|
32
|
+
}
|
|
33
|
+
const t = TIMING.cardDock;
|
|
34
|
+
const intro = Math.max(0, options.intro ?? t.intro);
|
|
35
|
+
const scope = createScope();
|
|
36
|
+
let expanded = false;
|
|
37
|
+
let resizeTimer: ReturnType<typeof setTimeout> | undefined;
|
|
38
|
+
|
|
39
|
+
const set = (name: string, on: boolean) => scope.attr(root, name, on ? '' : null);
|
|
40
|
+
const expand = () => {
|
|
41
|
+
if (scope.disposed) return;
|
|
42
|
+
expanded = true;
|
|
43
|
+
set('data-mk-expanded', true);
|
|
44
|
+
};
|
|
45
|
+
const collapse = () => {
|
|
46
|
+
if (scope.disposed) return;
|
|
47
|
+
expanded = false;
|
|
48
|
+
set('data-mk-expanded', false);
|
|
49
|
+
};
|
|
50
|
+
const settle = () => {
|
|
51
|
+
set('data-mk-spread', false);
|
|
52
|
+
set('data-mk-settled', true);
|
|
53
|
+
};
|
|
54
|
+
// Checked on each tap, so a window resized into the tablet range works too.
|
|
55
|
+
const tablet = () => typeof matchMedia === 'function' && matchMedia(TABLET_TOUCH).matches;
|
|
56
|
+
|
|
57
|
+
if (intro > 0 && !reduced()) {
|
|
58
|
+
set('data-mk-spread', true);
|
|
59
|
+
scope.timeout(settle, intro);
|
|
60
|
+
} else settle();
|
|
61
|
+
|
|
62
|
+
// Touch tablets have no hover: the first tap opens the stack, the next one follows the link.
|
|
63
|
+
scope.on(
|
|
64
|
+
root,
|
|
65
|
+
'click',
|
|
66
|
+
(event) => {
|
|
67
|
+
if (expanded || !tablet()) return;
|
|
68
|
+
event.preventDefault();
|
|
69
|
+
event.stopPropagation();
|
|
70
|
+
expand();
|
|
71
|
+
},
|
|
72
|
+
true,
|
|
73
|
+
);
|
|
74
|
+
scope.on(document, 'click', (event) => {
|
|
75
|
+
if (expanded && !root.contains(event.target as Node)) collapse();
|
|
76
|
+
});
|
|
77
|
+
scope.on(document, 'keydown', (event) => {
|
|
78
|
+
if (expanded && (event as KeyboardEvent).key === 'Escape') collapse();
|
|
79
|
+
});
|
|
80
|
+
scope.on(window, 'resize', () => {
|
|
81
|
+
clearTimeout(resizeTimer);
|
|
82
|
+
resizeTimer = setTimeout(() => {
|
|
83
|
+
if (!tablet()) collapse();
|
|
84
|
+
}, t.resizeDebounce);
|
|
85
|
+
});
|
|
86
|
+
scope.add(() => clearTimeout(resizeTimer));
|
|
87
|
+
scope.attr(root, 'data-mk-ready', '');
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
expand,
|
|
91
|
+
collapse,
|
|
92
|
+
get expanded() {
|
|
93
|
+
return expanded;
|
|
94
|
+
},
|
|
95
|
+
destroy: () => scope.dispose(),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# cascade
|
|
2
|
+
|
|
3
|
+
Un bloque que entra pieza a pieza: rayas que crecen desde un lado (alternando izquierda y derecha), textos que aparecen y líneas que suben desde su recorte. 1.2 s `expo.out`, 30 ms entre piezas, cuando el 30 % del bloque se ve (o el 30 % de la pantalla si el bloque es más alto). Al salir de la pantalla todo vuelve a su estado inicial, así que entra de nuevo al volver (salvo `once`), como el `isOut` de Siena.
|
|
4
|
+
|
|
5
|
+
Origen: Siena Film Foundation, grupos `[data-a]` (`line`, `alpha`, `item`); el `fade` es también el `alpha` de OFFFICE.
|
|
6
|
+
|
|
7
|
+
## HTML / Astro
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<section data-mk="cascade" data-mk-delay="0.2">
|
|
11
|
+
<hr data-mk-cascade="rule">
|
|
12
|
+
<p data-mk-cascade="fade">Director</p>
|
|
13
|
+
<span style="overflow: hidden; display: block"><span data-mk-cascade="rise" style="display: block">Limor Pinhasov</span></span>
|
|
14
|
+
<hr data-mk-cascade="rule">
|
|
15
|
+
</section>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Los `rise` necesitan un padre que recorte (`overflow: hidden`).
|
|
19
|
+
|
|
20
|
+
## React
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
const ref = useRef<HTMLElement>(null);
|
|
24
|
+
useCascade(ref, { once: true });
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## API
|
|
28
|
+
|
|
29
|
+
`createCascade(root, { delay, duration, step, once, trigger })` → `{ play(), reset(), destroy }`. `delay` también se lee de `data-mk-delay`. `reset()` devuelve todas las piezas a su estado inicial.
|
|
30
|
+
|
|
31
|
+
## Accesibilidad
|
|
32
|
+
|
|
33
|
+
Solo transforma y hace aparecer contenido que ya está en el DOM. Con movimiento reducido todo queda visible.
|