@yewdiel21/motion-kit 0.9.0 → 0.10.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 +3 -35
- package/README.md +93 -81
- package/dist/auto/index.d.ts +22 -3
- package/dist/auto/index.js +43 -5
- package/dist/auto/index.js.map +1 -1
- package/dist/{chunk-KLY5BRRI.js → chunk-34EZ7DF4.js} +1 -1
- package/dist/chunk-34EZ7DF4.js.map +1 -0
- package/dist/{chunk-RFDIZYFX.js → chunk-DOOMULVV.js} +20 -20
- package/dist/chunk-DOOMULVV.js.map +1 -0
- package/dist/{chunk-ABNRDDO7.js → chunk-I3DXF2KQ.js} +2 -2
- package/dist/{chunk-Q4LVFNLN.js → chunk-IKQ5FODN.js} +4 -4
- package/dist/chunk-IKQ5FODN.js.map +1 -0
- package/dist/{chunk-2BQGFOA7.js → chunk-J6OCFNGU.js} +2 -2
- package/dist/chunk-J6OCFNGU.js.map +1 -0
- package/dist/{chunk-6OJAP6ZL.js → chunk-QFBOHPWZ.js} +3 -3
- package/dist/chunk-QFBOHPWZ.js.map +1 -0
- package/dist/{chunk-J27KH2A2.js → chunk-RVSKEBIC.js} +1 -1
- package/dist/chunk-RVSKEBIC.js.map +1 -0
- package/dist/{chunk-34XYOXHG.js → chunk-TFNRIZHU.js} +1615 -12
- package/dist/chunk-TFNRIZHU.js.map +1 -0
- package/dist/{chunk-FS2HJVVY.js → chunk-XAKC2QY7.js} +1204 -5
- package/dist/chunk-XAKC2QY7.js.map +1 -0
- package/dist/css/components/calendar.css +159 -0
- package/dist/css/components/chat-background.css +2 -2
- package/dist/css/components/chat.css +1 -1
- package/dist/css/components/cursor.css +3 -3
- package/dist/css/components/dialog.css +3 -3
- package/dist/css/components/hover-card.css +14 -0
- package/dist/css/components/input-otp.css +10 -0
- package/dist/css/components/island.css +1 -1
- package/dist/css/components/menu.css +1 -1
- package/dist/css/components/nav.css +1 -5
- package/dist/css/components/pagination.css +11 -0
- package/dist/css/components/quantity.css +16 -0
- package/dist/css/components/slot-picker.css +126 -0
- package/dist/css/components/steps.css +2 -2
- package/dist/css/components/studio-dock.css +35 -0
- package/dist/css/components/theme-picker.css +1 -1
- package/dist/css/components/toast.css +1 -1
- package/dist/css/components/trailer.css +37 -0
- package/dist/css/controls.css +78 -13
- package/dist/css/effects.css +8 -8
- package/dist/css/fonts.css +1 -1
- package/dist/css/themes/black-green.css +1 -1
- package/dist/css/themes/black.css +1 -1
- package/dist/css/themes/blue.css +1 -1
- package/dist/css/themes/brown.css +1 -1
- package/dist/css/themes/cold-blue.css +1 -1
- package/dist/css/themes/funny.css +1 -1
- package/dist/css/themes/gray.css +1 -1
- package/dist/css/themes/green.css +1 -1
- package/dist/css/themes/modern-1.css +1 -1
- package/dist/css/themes/neogreen.css +1 -1
- package/dist/css/themes/new.css +1 -1
- package/dist/css/themes/new2.css +1 -1
- package/dist/css/themes/oled.css +1 -1
- package/dist/css/themes/orange.css +1 -1
- package/dist/css/themes/pastel-pink.css +1 -1
- package/dist/css/themes/pink.css +1 -1
- package/dist/css/themes/purple.css +1 -1
- package/dist/css/themes/red.css +1 -1
- package/dist/css/themes/super-blue.css +1 -1
- package/dist/css/themes.css +19 -19
- package/dist/css/tokens.css +9 -9
- package/dist/elements/index.d.ts +75 -7
- package/dist/elements/index.js +172 -6
- package/dist/elements/index.js.map +1 -1
- package/dist/{engine-CUr9QijM.d.ts → engine-GDGwQnJE.d.ts} +2 -2
- package/dist/gl/index.js +3 -3
- package/dist/index.css +1076 -63
- package/dist/index.d.ts +17 -17
- package/dist/index.js +49 -7
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.ts +139 -40
- package/dist/react/index.js +355 -8
- package/dist/react/index.js.map +1 -1
- package/dist/{reduced-BM4Ls0g5.d.ts → reduced-BmBzKH94.d.ts} +215 -18
- package/dist/{reveal-WFUWDrei.d.ts → reveal-jsX-uwUz.d.ts} +233 -24
- package/dist/{store-Bqy_obnD.d.ts → store-DYb3tTsZ.d.ts} +5 -5
- package/dist/theme/generate.d.ts +1 -1
- package/dist/theme/generate.js +2 -2
- package/dist/theme/generate.js.map +1 -1
- package/dist/theme/index.d.ts +2 -2
- package/dist/theme/index.js +2 -2
- package/dist/{theme-BFMoCzi6.d.ts → theme-Bjf9qIIy.d.ts} +1 -1
- package/package.json +2 -2
- package/registry.json +230 -156
- package/skills/motion-kit/SKILL.md +209 -189
- package/skills/motion-kit/reference/components.md +5 -5
- package/skills/motion-kit/reference/design.md +14 -14
- package/skills/motion-kit/reference/recipes.md +48 -49
- package/src/auto/auto-init.ts +38 -0
- package/src/components/calendar/README.md +82 -0
- package/src/components/calendar/calendar.css +158 -0
- package/src/components/calendar/element.ts +27 -0
- package/src/components/calendar/engine.ts +412 -0
- package/src/components/calendar/react.tsx +69 -0
- package/src/components/chat/README.md +1 -3
- package/src/components/chat/chat.css +1 -1
- package/src/components/chat/engine.ts +1 -1
- package/src/components/chat/react.tsx +1 -1
- package/src/components/chat-background/README.md +1 -3
- package/src/components/chat-background/audio.ts +1 -1
- package/src/components/chat-background/chat-background.css +2 -2
- package/src/components/chat-background/engine.ts +1 -1
- package/src/components/chat-background/react.tsx +1 -1
- package/src/components/cursor/README.md +3 -5
- package/src/components/cursor/cursor.css +3 -3
- package/src/components/cursor/element.ts +2 -2
- package/src/components/cursor/engine.ts +5 -5
- package/src/components/cursor/react.tsx +1 -1
- package/src/components/dialog/README.md +4 -8
- package/src/components/dialog/dialog.css +3 -3
- package/src/components/dialog/engine.ts +2 -2
- package/src/components/dialog/react.tsx +1 -1
- package/src/components/hover-card/README.md +16 -0
- package/src/components/hover-card/element.ts +8 -0
- package/src/components/hover-card/engine.ts +69 -0
- package/src/components/hover-card/hover-card.css +13 -0
- package/src/components/hover-card/react.tsx +19 -0
- package/src/components/input-otp/README.md +18 -0
- package/src/components/input-otp/element.ts +14 -0
- package/src/components/input-otp/engine.ts +106 -0
- package/src/components/input-otp/input-otp.css +9 -0
- package/src/components/input-otp/react.tsx +21 -0
- package/src/components/island/README.md +7 -7
- package/src/components/island/engine.ts +3 -3
- package/src/components/island/island.css +1 -1
- package/src/components/island/react.tsx +3 -3
- package/src/components/menu/README.md +2 -4
- package/src/components/menu/engine.ts +1 -1
- package/src/components/menu/menu.css +1 -1
- package/src/components/menu/react.tsx +1 -1
- package/src/components/nav/README.md +12 -14
- package/src/components/nav/engine.ts +5 -5
- package/src/components/nav/nav.css +1 -5
- package/src/components/nav/react.tsx +3 -3
- package/src/components/pagination/README.md +17 -0
- package/src/components/pagination/element.ts +14 -0
- package/src/components/pagination/engine.ts +102 -0
- package/src/components/pagination/pagination.css +10 -0
- package/src/components/pagination/react.tsx +22 -0
- package/src/components/quantity/README.md +20 -0
- package/src/components/quantity/element.ts +14 -0
- package/src/components/quantity/engine.ts +85 -0
- package/src/components/quantity/quantity.css +15 -0
- package/src/components/quantity/react.tsx +21 -0
- package/src/components/slot-picker/README.md +68 -0
- package/src/components/slot-picker/element.ts +27 -0
- package/src/components/slot-picker/engine.ts +412 -0
- package/src/components/slot-picker/react.tsx +76 -0
- package/src/components/slot-picker/slot-picker.css +125 -0
- package/src/components/steps/README.md +1 -3
- package/src/components/steps/engine.ts +3 -3
- package/src/components/steps/react.tsx +2 -2
- package/src/components/steps/steps.css +2 -2
- package/src/components/studio-dock/README.md +44 -0
- package/src/components/studio-dock/element.ts +11 -0
- package/src/components/studio-dock/engine.ts +212 -0
- package/src/components/studio-dock/react.tsx +23 -0
- package/src/components/studio-dock/studio-dock.css +34 -0
- package/src/components/theme-picker/README.md +3 -5
- package/src/components/theme-picker/engine.ts +2 -2
- package/src/components/theme-picker/react.tsx +1 -1
- package/src/components/theme-picker/theme-picker.css +1 -1
- package/src/components/toast/README.md +1 -3
- package/src/components/toast/engine.ts +2 -2
- package/src/components/toast/react.tsx +1 -1
- package/src/components/toast/store.ts +1 -2
- package/src/components/toast/toast.css +1 -1
- package/src/components/trailer/README.md +28 -0
- package/src/components/trailer/element.ts +13 -0
- package/src/components/trailer/engine.ts +189 -0
- package/src/components/trailer/react.tsx +16 -0
- package/src/components/trailer/trailer.css +36 -0
- package/src/core/channels.ts +3 -3
- package/src/core/curves.ts +3 -3
- package/src/core/dates.ts +98 -0
- package/src/core/gsap.ts +2 -2
- package/src/core/loop.ts +1 -1
- package/src/core/math.ts +1 -1
- package/src/core/physics-spring.ts +2 -2
- package/src/core/scroll-track.ts +2 -4
- package/src/core/spring.ts +1 -1
- package/src/core/timing.ts +21 -22
- package/src/css/controls/README.md +24 -20
- package/src/css/controls/burger.css +1 -1
- package/src/css/controls/button.css +4 -4
- package/src/css/controls/checkbox.css +1 -1
- package/src/css/controls/chip.css +1 -1
- package/src/css/controls/field.css +2 -2
- package/src/css/controls/glass.css +4 -4
- package/src/css/controls/status.css +63 -0
- package/src/css/default-theme.css +1 -1
- package/src/css/effects/README.md +19 -21
- package/src/css/effects/anim.css +1 -1
- package/src/css/effects/fade.css +1 -1
- package/src/css/effects/float.css +1 -1
- package/src/css/effects/frost.css +1 -1
- package/src/css/effects/grain.css +1 -1
- package/src/css/effects/loading.css +1 -1
- package/src/css/effects/success.css +1 -1
- package/src/css/effects/text.css +1 -1
- package/src/css/fonts.css +1 -1
- package/src/css/states.css +1 -1
- package/src/css/themes/black-green.css +1 -1
- package/src/css/themes/black.css +1 -1
- package/src/css/themes/blue.css +1 -1
- package/src/css/themes/brown.css +1 -1
- package/src/css/themes/cold-blue.css +1 -1
- package/src/css/themes/funny.css +1 -1
- package/src/css/themes/gray.css +1 -1
- package/src/css/themes/green.css +1 -1
- package/src/css/themes/modern-1.css +1 -1
- package/src/css/themes/neogreen.css +1 -1
- package/src/css/themes/new.css +1 -1
- package/src/css/themes/new2.css +1 -1
- package/src/css/themes/oled.css +1 -1
- package/src/css/themes/orange.css +1 -1
- package/src/css/themes/pastel-pink.css +1 -1
- package/src/css/themes/pink.css +1 -1
- package/src/css/themes/purple.css +1 -1
- package/src/css/themes/red.css +1 -1
- package/src/css/themes/super-blue.css +1 -1
- package/src/css/tokens.css +8 -8
- package/src/elements/index.ts +24 -0
- package/src/gl/liquid-blend/README.md +3 -3
- package/src/gl/matcap/README.md +3 -3
- package/src/gl/particle-hero/README.md +1 -1
- package/src/gl/particle-hero/create-particle-hero.ts +3 -5
- package/src/gl/particle-hero/shaders.ts +1 -1
- package/src/gl/shader-blur/README.md +3 -3
- package/src/gl/shader-grain/README.md +3 -3
- package/src/index.ts +25 -0
- package/src/motion/chat.ts +2 -2
- package/src/motion/confetti.ts +1 -1
- package/src/motion/count.ts +1 -1
- package/src/motion/details.ts +1 -1
- package/src/motion/drag-dismiss.ts +1 -1
- package/src/motion/effects.ts +1 -1
- package/src/motion/entrances.ts +1 -1
- package/src/motion/floating.ts +1 -1
- package/src/motion/flow.ts +2 -2
- package/src/motion/indicator.ts +1 -1
- package/src/motion/layout.ts +1 -1
- package/src/motion/liquid.ts +1 -1
- package/src/motion/morph.ts +2 -2
- package/src/motion/reveal.ts +1 -1
- package/src/motion/sheet.ts +1 -1
- package/src/motion/surface.ts +1 -1
- package/src/react/animated-number.tsx +2 -2
- package/src/react/flip-group.tsx +1 -1
- package/src/react/frost.tsx +1 -1
- package/src/react/index.ts +21 -0
- package/src/react/slot-text.tsx +1 -1
- package/src/react/use-action-button.ts +6 -0
- package/src/react/use-card-dock.ts +1 -1
- package/src/react/use-cascade.ts +1 -1
- package/src/react/use-contrast.ts +1 -1
- package/src/react/use-depth-grid.ts +1 -1
- package/src/react/use-editorial-reveal.ts +7 -0
- package/src/react/use-fill-text.ts +1 -1
- package/src/react/use-film-hero.ts +6 -0
- package/src/react/use-font-cycle.ts +7 -0
- package/src/react/use-frame-zoom.ts +1 -1
- package/src/react/use-image-zoom.ts +5 -0
- package/src/react/use-joined-cta.ts +5 -0
- package/src/react/use-letters.ts +1 -1
- package/src/react/use-local-clock.ts +5 -0
- package/src/react/use-marquee.ts +1 -1
- package/src/react/use-mask-text.ts +1 -1
- package/src/react/use-parallax.ts +1 -1
- package/src/react/use-particle-hero.ts +1 -1
- package/src/react/use-scroll-dock.ts +5 -0
- package/src/react/use-snap-strip.ts +1 -1
- package/src/react/use-sticker.ts +5 -0
- package/src/react/use-studio-dock.ts +8 -0
- package/src/react/use-text-roll.ts +1 -1
- package/src/react/use-thumbnail-nav.ts +7 -0
- package/src/react/use-trailer.ts +6 -0
- package/src/react/use-weight-hover.ts +5 -0
- package/src/recipes/action-button/README.md +31 -0
- package/src/recipes/action-button/action-button.css +113 -0
- package/src/recipes/action-button/create-action-button.ts +56 -0
- package/src/recipes/card-dock/README.md +1 -1
- package/src/recipes/card-dock/card-dock.css +1 -1
- package/src/recipes/card-dock/create-card-dock.ts +2 -2
- package/src/recipes/cascade/README.md +1 -3
- package/src/recipes/cascade/create-cascade.ts +3 -3
- package/src/recipes/contrast/README.md +0 -2
- package/src/recipes/contrast/create-contrast.ts +1 -1
- package/src/recipes/counter/README.md +0 -2
- package/src/recipes/counter/create-counter.ts +1 -1
- package/src/recipes/curved-carousel/README.md +0 -2
- package/src/recipes/curved-carousel/create-curved-carousel.ts +2 -2
- package/src/recipes/curved-carousel/curved-carousel.css +1 -1
- package/src/recipes/depth-grid/README.md +0 -2
- package/src/recipes/depth-grid/create-depth-grid.ts +3 -4
- package/src/recipes/details/README.md +0 -2
- package/src/recipes/details/create-details.ts +1 -1
- package/src/recipes/dock-pill/README.md +3 -3
- package/src/recipes/drag-ruler/README.md +3 -3
- package/src/recipes/editorial-reveal/README.md +35 -0
- package/src/recipes/editorial-reveal/create-editorial-reveal.ts +91 -0
- package/src/recipes/editorial-reveal/editorial-reveal.css +17 -0
- package/src/recipes/fill-text/README.md +1 -1
- package/src/recipes/fill-text/create-fill-text.ts +1 -1
- package/src/recipes/film-hero/README.md +23 -0
- package/src/recipes/film-hero/create-film-hero.ts +65 -0
- package/src/recipes/film-hero/film-hero.css +29 -0
- package/src/recipes/fit-text/README.md +0 -2
- package/src/recipes/flow/README.md +0 -2
- package/src/recipes/flow/create-flow.ts +1 -1
- package/src/recipes/font-cycle/README.md +11 -0
- package/src/recipes/font-cycle/create-font-cycle.ts +126 -0
- package/src/recipes/font-cycle/font-cycle.css +36 -0
- package/src/recipes/frame-zoom/README.md +1 -3
- package/src/recipes/frame-zoom/create-frame-zoom.ts +2 -2
- package/src/recipes/image-zoom/README.md +16 -0
- package/src/recipes/image-zoom/create-image-zoom.ts +27 -0
- package/src/recipes/image-zoom/image-zoom.css +7 -0
- package/src/recipes/infinite-reel/README.md +3 -3
- package/src/recipes/joined-cta/README.md +13 -0
- package/src/recipes/joined-cta/create-joined-cta.ts +22 -0
- package/src/recipes/joined-cta/joined-cta.css +17 -0
- package/src/recipes/letters/README.md +2 -4
- package/src/recipes/letters/create-letters.ts +3 -3
- package/src/recipes/local-clock/README.md +31 -0
- package/src/recipes/local-clock/create-local-clock.ts +61 -0
- package/src/recipes/marquee/README.md +0 -2
- package/src/recipes/marquee/create-marquee.ts +3 -3
- package/src/recipes/marquee/marquee.css +1 -1
- package/src/recipes/mask-text/README.md +3 -5
- package/src/recipes/mask-text/create-mask-text.ts +4 -4
- package/src/recipes/parallax/README.md +1 -3
- package/src/recipes/parallax/create-parallax.ts +1 -1
- package/src/recipes/pile/README.md +0 -2
- package/src/recipes/pile/create-pile.ts +1 -1
- package/src/recipes/pile/pile-math.ts +1 -1
- package/src/recipes/pile/pile.css +1 -1
- package/src/recipes/pill/README.md +0 -2
- package/src/recipes/pill/pill.css +2 -2
- package/src/recipes/pop/README.md +0 -2
- package/src/recipes/pop/create-pop.ts +1 -1
- package/src/recipes/radial-menu/README.md +0 -2
- package/src/recipes/radial-menu/create-radial-menu.ts +1 -1
- package/src/recipes/radial-menu/radial-menu.css +1 -1
- package/src/recipes/reflow/README.md +3 -5
- package/src/recipes/reflow/create-reflow.ts +1 -1
- package/src/recipes/repel-bento/README.md +3 -3
- package/src/recipes/reveal/README.md +0 -2
- package/src/recipes/reveal/create-reveal.ts +1 -1
- package/src/recipes/rotator/README.md +3 -3
- package/src/recipes/scroll-dock/README.md +46 -0
- package/src/recipes/scroll-dock/create-scroll-dock.ts +72 -0
- package/src/recipes/scroll-dock/scroll-dock.css +70 -0
- package/src/recipes/slot-text/README.md +0 -2
- package/src/recipes/slot-text/create-slot-text.ts +1 -1
- package/src/recipes/snap-strip/README.md +1 -1
- package/src/recipes/snap-strip/create-snap-strip.ts +5 -6
- package/src/recipes/snap-strip/snap-strip.css +1 -1
- package/src/recipes/sortable/README.md +3 -5
- package/src/recipes/sortable/create-sortable.ts +4 -4
- package/src/recipes/sortable/sortable.css +1 -1
- package/src/recipes/spring-cards/README.md +0 -2
- package/src/recipes/spring-cards/create-spring-cards.ts +1 -1
- package/src/recipes/spring-cards/spring-cards.css +1 -1
- package/src/recipes/sticker/README.md +59 -0
- package/src/recipes/sticker/create-sticker.ts +490 -0
- package/src/recipes/sticker/sticker.css +112 -0
- package/src/recipes/stretch-footer/README.md +0 -2
- package/src/recipes/stretch-footer/create-stretch-footer.ts +1 -1
- package/src/recipes/stretch-footer/stretch-footer.css +1 -1
- package/src/recipes/tabs/README.md +0 -2
- package/src/recipes/tabs/create-tabs.ts +1 -1
- package/src/recipes/text-roll/README.md +1 -3
- package/src/recipes/text-roll/create-text-roll.ts +3 -3
- package/src/recipes/text-roll/text-roll.css +1 -1
- package/src/recipes/thumbnail-nav/README.md +16 -0
- package/src/recipes/thumbnail-nav/create-thumbnail-nav.ts +165 -0
- package/src/recipes/thumbnail-nav/thumbnail-nav.css +108 -0
- package/src/recipes/ticket-menu/README.md +3 -3
- package/src/recipes/weight-hover/README.md +37 -0
- package/src/recipes/weight-hover/create-weight-hover.ts +136 -0
- package/src/recipes/weight-hover/weight-hover.css +7 -0
- package/src/recipes/words/README.md +0 -2
- package/src/recipes/words/create-words.ts +1 -1
- package/src/theme/generate.ts +1 -1
- package/src/theme/presets.ts +2 -2
- package/src/theme/theme.ts +1 -2
- package/dist/chunk-2BQGFOA7.js.map +0 -1
- package/dist/chunk-34XYOXHG.js.map +0 -1
- package/dist/chunk-6OJAP6ZL.js.map +0 -1
- package/dist/chunk-FS2HJVVY.js.map +0 -1
- package/dist/chunk-J27KH2A2.js.map +0 -1
- package/dist/chunk-KLY5BRRI.js.map +0 -1
- package/dist/chunk-Q4LVFNLN.js.map +0 -1
- package/dist/chunk-RFDIZYFX.js.map +0 -1
- /package/dist/{chunk-ABNRDDO7.js.map → chunk-I3DXF2KQ.js.map} +0 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createScope } from '../../core/scope';
|
|
2
|
+
import { inert, type Instance } from '../../core/types';
|
|
3
|
+
|
|
4
|
+
export interface JoinedCtaOptions { duration?: number; }
|
|
5
|
+
export interface JoinedCtaInstance extends Instance { expand(): void; collapse(): void; readonly expanded: boolean; }
|
|
6
|
+
|
|
7
|
+
/** Silvia's three contact capsules joining into one. Motion is entirely CSS. */
|
|
8
|
+
export function createJoinedCta(root: HTMLElement | null, options: JoinedCtaOptions = {}): JoinedCtaInstance {
|
|
9
|
+
if (!root) return inert({ expand() {}, collapse() {}, expanded: false });
|
|
10
|
+
const scope = createScope();
|
|
11
|
+
let expanded = false;
|
|
12
|
+
let pointer = false;
|
|
13
|
+
scope.attr(root, 'style', root.getAttribute('style'));
|
|
14
|
+
scope.attr(root, 'data-mk-ready', '');
|
|
15
|
+
if (Number.isFinite(options.duration)) root.style.setProperty('--mk-joined-duration', `${Math.max(0, options.duration!)}s`);
|
|
16
|
+
const set = (value: boolean) => { if (!scope.disposed) { expanded = value; scope.attr(root, 'data-mk-expanded', value ? '' : null); } };
|
|
17
|
+
scope.on(root, 'pointerenter', () => { pointer = true; set(true); });
|
|
18
|
+
scope.on(root, 'pointerleave', () => { pointer = false; if (!root.contains(document.activeElement)) set(false); });
|
|
19
|
+
scope.on(root, 'focusin', () => set(true));
|
|
20
|
+
scope.on(root, 'focusout', (event) => { if (!pointer && !root.contains((event as FocusEvent).relatedTarget as Node | null)) set(false); });
|
|
21
|
+
return { expand: () => set(true), collapse: () => set(false), get expanded() { return expanded; }, destroy: () => scope.dispose() };
|
|
22
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
.mk-joined-cta { --mk-joined-duration: .5s; --mk-joined-bg: var(--mk-text); --mk-joined-color: var(--mk-bg); position: relative; display: inline-flex; align-items: center; justify-content: center; width: 17.7rem; height: 5.9rem; max-width: 100%; border: 0; padding: 0; background: transparent; text-decoration: none; color: var(--mk-joined-color); cursor: pointer; font: inherit; isolation: isolate; border-radius: var(--mk-radius-full); }
|
|
2
|
+
.mk-joined-cta::before { content: ''; position: absolute; left: 0; width: 7.3rem; height: 5.9rem; border-radius: 100px; background: var(--mk-joined-bg); z-index: -1; transition: width var(--mk-joined-duration) ease-in-out; }
|
|
3
|
+
.mk-joined-cta > span { position: relative; display: grid; place-items: center; flex-shrink: 0; background: var(--mk-joined-bg); border-radius: 100px; height: 5.9rem; transition: left var(--mk-joined-duration) ease-in-out, height var(--mk-joined-duration) ease-in-out, transform var(--mk-joined-duration) ease-in-out, background var(--mk-joined-duration); }
|
|
4
|
+
.mk-joined-cta > span:nth-child(1) { width: 7.3rem; left: 0; }
|
|
5
|
+
.mk-joined-cta > span:nth-child(2) { width: 4.5rem; left: 0; z-index: 2; }
|
|
6
|
+
.mk-joined-cta > span:nth-child(3) { width: 5.9rem; height: 3.05rem; left: -1.45rem; transform: rotate(-90deg); }
|
|
7
|
+
.mk-joined-cta:is(:hover,:focus-visible,[data-mk-expanded])::before { width: 17.7rem; }
|
|
8
|
+
.mk-joined-cta:is(:hover,:focus-visible,[data-mk-expanded]) > span:nth-child(1) { left: 3.2rem; }
|
|
9
|
+
.mk-joined-cta:is(:hover,:focus-visible,[data-mk-expanded]) > span:nth-child(2) { left: -.75rem; background: transparent; }
|
|
10
|
+
.mk-joined-cta:is(:hover,:focus-visible,[data-mk-expanded]) > span:nth-child(3) { left: -3.65rem; height: 5.9rem; transform: rotate(0); }
|
|
11
|
+
.mk-joined-cta:focus-visible { outline: 2px solid var(--mk-accent); outline-offset: 5px; }
|
|
12
|
+
@media (hover: none), (max-width: 420px) {
|
|
13
|
+
.mk-joined-cta { width: auto; height: auto; padding: 1.25rem 2rem; gap: .3em; background: var(--mk-joined-bg); }
|
|
14
|
+
.mk-joined-cta::before { display: none; }
|
|
15
|
+
.mk-joined-cta > span:nth-child(n) { width: auto; height: auto; left: 0; transform: none; background: transparent; }
|
|
16
|
+
}
|
|
17
|
+
@media (prefers-reduced-motion: reduce) { .mk-joined-cta::before, .mk-joined-cta > span { transition: none; } }
|
|
@@ -5,13 +5,11 @@ Letras que juegan:
|
|
|
5
5
|
- **`scale`**: al pasar el ratón por la palabra cada letra crece (`scale 1.3`, sube 5 px) con un resorte con rebote (Motion 300/10), 30 ms después de la anterior; vuelve igual al salir.
|
|
6
6
|
- **`blink`**: cada letra pasa a un color pastel y vuelve (0.6 s, `ease-in-out`) con retardos desordenados (.1, .6, .2, 1, .4, 1.2, .15, .8, .3 s), dos veces. Arranca 0.6 s después de crearse y al pasar el ratón.
|
|
7
7
|
|
|
8
|
-
Origen: Dousan Miao, el titular de la portada («impactful»: `hero-letters-blink`, `hero-scale-letters`).
|
|
9
|
-
|
|
10
8
|
## HTML / Astro
|
|
11
9
|
|
|
12
10
|
```html
|
|
13
11
|
<h1>I design <span data-mk="letters" data-mk-effect="blink">impactful</span> products</h1>
|
|
14
|
-
<span data-mk="letters" data-mk-effect="scale">
|
|
12
|
+
<span data-mk="letters" data-mk-effect="scale">letters</span>
|
|
15
13
|
```
|
|
16
14
|
|
|
17
15
|
## React
|
|
@@ -23,7 +21,7 @@ useLetters(ref, { effect: 'blink', colors: ['#FFD6FF', '#C8B6FF'] });
|
|
|
23
21
|
|
|
24
22
|
## API
|
|
25
23
|
|
|
26
|
-
`createLetters(el, { effect, colors, autoplay })` → `{ play(), destroy }`. `colors` por defecto: los pastel
|
|
24
|
+
`createLetters(el, { effect, colors, autoplay })` → `{ play(), destroy }`. `colors` por defecto: los pastel (`#FFD6FF #E7C6FF #C8B6FF #B8C0FF #BBD0FF`).
|
|
27
25
|
|
|
28
26
|
## Accesibilidad
|
|
29
27
|
|
|
@@ -6,15 +6,15 @@ import { TIMING } from '../../core/timing';
|
|
|
6
6
|
import { inert, warnOnce, type Instance } from '../../core/types';
|
|
7
7
|
|
|
8
8
|
/*
|
|
9
|
-
*
|
|
9
|
+
* hero words. 'scale': on hover every letter springs up (scale 1.3,
|
|
10
10
|
* y −5 px, Motion 300/10), 30 ms after the previous one, and back on leave.
|
|
11
11
|
* 'blink': every letter flashes a pastel and returns (0.6 s, ease-in-out) with
|
|
12
|
-
*
|
|
12
|
+
* scattered delays, twice; it plays 0.6 s after start and on hover.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
export interface LettersOptions {
|
|
16
16
|
effect?: 'scale' | 'blink';
|
|
17
|
-
/** Blink colours
|
|
17
|
+
/** Blink colours. */
|
|
18
18
|
colors?: string[];
|
|
19
19
|
/** Blink once 0.6 s after start (default true). */
|
|
20
20
|
autoplay?: boolean;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# local-clock
|
|
2
|
+
|
|
3
|
+
La hora de otra zona en vivo, como el «20:48 local time» del contacto de DOVV Studio. Se actualiza justo al cambiar el minuto (o el segundo, con `seconds`). En un `<time>` también mantiene el atributo `datetime`.
|
|
4
|
+
|
|
5
|
+
## Vanilla / Astro
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<p><span class="mk-status__dot"></span> <time data-mk="local-clock" data-mk-time-zone="America/Mexico_City">--:--</time> hora local</p>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Con `autoInit()` las opciones van como `data-mk-<opción>`. Sin `autoInit`: `createLocalClock(el, opciones)`, que devuelve `text` y `destroy()`. El contenido inicial (por ejemplo `--:--`) se ve sin JavaScript y vuelve con `destroy()`.
|
|
12
|
+
|
|
13
|
+
## React
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
const ref = useRef<HTMLTimeElement>(null);
|
|
17
|
+
useLocalClock(ref, { timeZone: 'America/Argentina/Buenos_Aires' });
|
|
18
|
+
return <time ref={ref}>--:--</time>;
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Opciones
|
|
22
|
+
|
|
23
|
+
| Opción | Por defecto | Qué hace |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| `timeZone` | la del visitante | Zona IANA |
|
|
26
|
+
| `locale` | `'es'` | Idioma del formato |
|
|
27
|
+
| `hour12` | `false` | 12 h con a. m./p. m. |
|
|
28
|
+
| `seconds` | `false` | Mostrar y actualizar segundos |
|
|
29
|
+
| `format` | — | `Intl.DateTimeFormatOptions` completo |
|
|
30
|
+
|
|
31
|
+
Una zona o un idioma desconocidos usan los del visitante en lugar de fallar. No anuncia cada cambio a lectores de pantalla.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { createScope } from '../../core/scope';
|
|
2
|
+
import { inert, type Instance } from '../../core/types';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* Local clock (DOVV Studio contact: "20:48 local time"): the time in a given
|
|
6
|
+
* zone, refreshed on the minute (or the second). A <time> element also gets
|
|
7
|
+
* its datetime attribute. The text node is replaced; destroy() puts it back.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface LocalClockOptions {
|
|
11
|
+
/** IANA zone, e.g. "America/Mexico_City". The visitor's zone by default. */
|
|
12
|
+
timeZone?: string;
|
|
13
|
+
locale?: string;
|
|
14
|
+
hour12?: boolean;
|
|
15
|
+
seconds?: boolean;
|
|
16
|
+
/** Full control over the format (overrides hour12/seconds). */
|
|
17
|
+
format?: Intl.DateTimeFormatOptions;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface LocalClockInstance extends Instance {
|
|
21
|
+
/** Text currently shown. */
|
|
22
|
+
readonly text: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function createLocalClock(root: HTMLElement | null, options: LocalClockOptions = {}): LocalClockInstance {
|
|
26
|
+
if (!root) return inert({ text: '' });
|
|
27
|
+
const scope = createScope();
|
|
28
|
+
const original = [...root.childNodes];
|
|
29
|
+
scope.add(() => root.replaceChildren(...original));
|
|
30
|
+
scope.attr(root, 'data-mk-ready', '');
|
|
31
|
+
let formatter: Intl.DateTimeFormat;
|
|
32
|
+
try {
|
|
33
|
+
formatter = new Intl.DateTimeFormat(options.locale ?? 'es', options.format ?? {
|
|
34
|
+
hour: '2-digit',
|
|
35
|
+
minute: '2-digit',
|
|
36
|
+
...(options.seconds ? { second: '2-digit' } : {}),
|
|
37
|
+
hour12: options.hour12 ?? false,
|
|
38
|
+
timeZone: options.timeZone,
|
|
39
|
+
});
|
|
40
|
+
} catch {
|
|
41
|
+
// Unknown zone or locale: the visitor's own.
|
|
42
|
+
formatter = new Intl.DateTimeFormat(undefined, { hour: '2-digit', minute: '2-digit', hour12: options.hour12 ?? false });
|
|
43
|
+
}
|
|
44
|
+
let text = '';
|
|
45
|
+
const isTime = root instanceof HTMLTimeElement;
|
|
46
|
+
const render = () => {
|
|
47
|
+
const now = new Date();
|
|
48
|
+
text = formatter.format(now);
|
|
49
|
+
if (root.textContent !== text) root.textContent = text;
|
|
50
|
+
if (isTime) scope.attr(root, 'datetime', now.toISOString());
|
|
51
|
+
};
|
|
52
|
+
const step = options.seconds || options.format?.second ? 1000 : 60000;
|
|
53
|
+
const schedule = () => {
|
|
54
|
+
// Land just after the next boundary so the minute flips with the system clock.
|
|
55
|
+
const wait = step - (Date.now() % step) + 20;
|
|
56
|
+
scope.timeout(() => { if (!scope.disposed) { render(); schedule(); } }, wait);
|
|
57
|
+
};
|
|
58
|
+
render();
|
|
59
|
+
schedule();
|
|
60
|
+
return { get text() { return text; }, destroy: () => scope.dispose() };
|
|
61
|
+
}
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
Una banda infinita: el primer hijo (el grupo) se copia hasta llenar la banda más una vez y cada copia se desplaza su propio tamaño en bucle lineal, sin cortes. Velocidad: 2 s por elemento del grupo (cuenta hasta 14).
|
|
4
4
|
|
|
5
|
-
Origen: Siena Film Foundation, marquesina vertical de la página de película. Siena la usa en vertical; aquí el eje por defecto es horizontal (`axis: 'y'` para la de Siena).
|
|
6
|
-
|
|
7
5
|
## HTML / Astro
|
|
8
6
|
|
|
9
7
|
```html
|
|
@@ -4,14 +4,14 @@ import { TIMING } from '../../core/timing';
|
|
|
4
4
|
import { inert, warnOnce, type Instance } from '../../core/types';
|
|
5
5
|
|
|
6
6
|
/*
|
|
7
|
-
*
|
|
7
|
+
* marquee: the first child (the group) is copied until the strip is
|
|
8
8
|
* full, plus one; every copy slides its own length in a linear loop, so the
|
|
9
9
|
* strip never shows a seam. Speed: 2 s per item in the group (at most 14).
|
|
10
10
|
* Copies are aria-hidden and inert: readers and keyboards meet the group once.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
export interface MarqueeOptions {
|
|
14
|
-
/** 'x' (default) or 'y'
|
|
14
|
+
/** 'x' (default) or 'y'. */
|
|
15
15
|
axis?: 'x' | 'y';
|
|
16
16
|
/** Seconds per item. */
|
|
17
17
|
speed?: number;
|
|
@@ -47,7 +47,7 @@ export function createMarquee(root: HTMLElement | null, options: MarqueeOptions
|
|
|
47
47
|
if (!entrances()) return;
|
|
48
48
|
const size = axis === 'x' ? group.offsetWidth : group.offsetHeight;
|
|
49
49
|
const room = axis === 'x' ? root.clientWidth : root.clientHeight;
|
|
50
|
-
// Unmeasurable (hidden, not laid out yet): five copies
|
|
50
|
+
// Unmeasurable (hidden, not laid out yet): five copies.
|
|
51
51
|
const count = size > 0 ? Math.max(1, Math.ceil(room / size)) + 1 : 5;
|
|
52
52
|
for (let i = 0; i < count; i++) {
|
|
53
53
|
const copy = group.cloneNode(true) as HTMLElement;
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
# mask-text
|
|
2
2
|
|
|
3
|
-
El texto sale de su máscara: cada línea (o palabra, o letra) vive en un recorte y sube desde abajo (`yPercent 100 → 0`, 1.2 s `expo.out`, retardo 0.1 s, 20 ms entre piezas) cuando el 80 % del bloque está en pantalla. Al salir de la pantalla vuelve a esconderse, así que se repite cada vez que regresa (salvo `once`). SplitText lo vuelve a partir al cambiar el tamaño o al cargar la fuente
|
|
4
|
-
|
|
5
|
-
Origen: OFFFICE (offficestud.io) `masked-type`; Siena usa el mismo gesto en los títulos de película partiendo de 120 %.
|
|
3
|
+
El texto sale de su máscara: cada línea (o palabra, o letra) vive en un recorte y sube desde abajo (`yPercent 100 → 0`, 1.2 s `expo.out`, retardo 0.1 s, 20 ms entre piezas) cuando el 80 % del bloque está en pantalla. Al salir de la pantalla vuelve a esconderse, así que se repite cada vez que regresa (salvo `once`). SplitText lo vuelve a partir al cambiar el tamaño o al cargar la fuente.
|
|
6
4
|
|
|
7
5
|
## HTML / Astro
|
|
8
6
|
|
|
9
7
|
```html
|
|
10
|
-
<h2 data-mk="mask-text">
|
|
8
|
+
<h2 data-mk="mask-text">Texto que aparece desde su máscara</h2>
|
|
11
9
|
<p data-mk="mask-text" data-mk-by="words" data-mk-once>Texto que entra palabra a palabra.</p>
|
|
12
10
|
<h1 data-mk="mask-text" data-mk-y="120" data-mk-trigger="load">Savoy</h1>
|
|
13
11
|
```
|
|
@@ -32,7 +30,7 @@ const text = useMaskText(ref, { by: 'lines' });
|
|
|
32
30
|
| `trigger` | `'scroll'` | `'load'`, `'manual'` |
|
|
33
31
|
| `once` | `false` | no vuelve a esconderse al salir |
|
|
34
32
|
|
|
35
|
-
`hide()` es la salida
|
|
33
|
+
`hide()` es la salida: todas bajan a su recorte con 2 ms entre piezas.
|
|
36
34
|
|
|
37
35
|
## Accesibilidad
|
|
38
36
|
|
|
@@ -5,7 +5,7 @@ import { shows, thresholds } from '../../core/visible';
|
|
|
5
5
|
import { inert, warnOnce, type Instance } from '../../core/types';
|
|
6
6
|
|
|
7
7
|
/*
|
|
8
|
-
*
|
|
8
|
+
* masked type: the text is split into lines,
|
|
9
9
|
* words or letters, each inside its own clip, and rises from below it
|
|
10
10
|
* (yPercent 100 → 0, 1.2 s expo.out, 0.1 s delay, 20 ms apart) once 80 % of it
|
|
11
11
|
* is on screen. It hides again when it leaves the screen (unless `once`), so it
|
|
@@ -14,7 +14,7 @@ import { inert, warnOnce, type Instance } from '../../core/types';
|
|
|
14
14
|
|
|
15
15
|
export interface MaskTextOptions {
|
|
16
16
|
by?: 'lines' | 'words' | 'chars';
|
|
17
|
-
/** Start offset in % of each piece
|
|
17
|
+
/** Start offset in % of each piece. */
|
|
18
18
|
y?: number;
|
|
19
19
|
duration?: number;
|
|
20
20
|
ease?: string;
|
|
@@ -22,13 +22,13 @@ export interface MaskTextOptions {
|
|
|
22
22
|
stagger?: number;
|
|
23
23
|
/** 'scroll' (default) plays when 80 % is visible; 'load' now; 'manual' waits for play(). */
|
|
24
24
|
trigger?: 'scroll' | 'load' | 'manual';
|
|
25
|
-
/** Play once and stay
|
|
25
|
+
/** Play once and stay. */
|
|
26
26
|
once?: boolean;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export interface MaskTextInstance extends Instance {
|
|
30
30
|
play(): void;
|
|
31
|
-
/**
|
|
31
|
+
/** exit: back down into the clips, 2 ms apart. */
|
|
32
32
|
hide(): void;
|
|
33
33
|
readonly split: SplitText | null;
|
|
34
34
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# parallax
|
|
2
2
|
|
|
3
|
-
La foto se mueve dentro de su marco mientras
|
|
4
|
-
|
|
5
|
-
Origen: Siena Film Foundation, fotos de las páginas de película y About (`translateY(-10·a + p·20·a %) scale(1.2)`).
|
|
3
|
+
La foto se mueve dentro de su marco mientras cruza la pantalla: escala 1.2 (1.3 con `amount` distinto de 1 en escritorio), recorrido de −10 % a +10 % de su altura × `amount`. Sigue al scroll sin retraso adicional; `lerp: .1` permite suavizado opcional. Arriba de la página comienza en `top top`.
|
|
6
4
|
|
|
7
5
|
## HTML / Astro
|
|
8
6
|
|
|
@@ -5,7 +5,7 @@ import { TIMING } from '../../core/timing';
|
|
|
5
5
|
import { inert, warnOnce, type Instance } from '../../core/types';
|
|
6
6
|
|
|
7
7
|
/*
|
|
8
|
-
*
|
|
8
|
+
* photo parallax: the frame clips, the picture inside is scaled up
|
|
9
9
|
* (1.2, or 1.3 for a stronger amount on wide screens) and travels from −10 % to
|
|
10
10
|
* +10 % of its height × amount while the frame crosses the screen (a frame at
|
|
11
11
|
* the top of the page counts from "top top", so it starts at −10 %). Target:
|
|
@@ -6,7 +6,7 @@ import type { PileLayer } from './pile-math';
|
|
|
6
6
|
export { pileLayers, type PileLayer } from './pile-math';
|
|
7
7
|
|
|
8
8
|
/*
|
|
9
|
-
*
|
|
9
|
+
* pile: a pile of two or three rotated layers that
|
|
10
10
|
* scale in (CSS, elastic) once the pile scrolls into view. The rotations are
|
|
11
11
|
* generated once with `pileLayers()` and written into the markup with
|
|
12
12
|
* `pileStyle()`, so server and client render the same pile.
|
|
@@ -15,7 +15,7 @@ function mulberry32(seed: number) {
|
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
/**
|
|
18
|
+
/** stack transforms, seeded so SSR output is stable. Layer 0 is the back. */
|
|
19
19
|
export function pileLayers(count: number, seed: number): PileLayer[] {
|
|
20
20
|
const rand = mulberry32(seed + 1);
|
|
21
21
|
const range = (min: number, max: number) => rand() * (max - min) + min;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* pill —
|
|
1
|
+
/* pill — pill: elastic scale and shadow */
|
|
2
2
|
|
|
3
3
|
.mk-pill {
|
|
4
4
|
display: inline-flex;
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
/* Stretch on hover (
|
|
37
|
+
/* Stretch on hover (stretch-footer links) */
|
|
38
38
|
.mk-stretch {
|
|
39
39
|
display: inline-block;
|
|
40
40
|
transform-origin: 50% 100%;
|
|
@@ -2,7 +2,7 @@ import { gsap, ELASTIC, ScrollTrigger, registerMotion } from '../../core/gsap';
|
|
|
2
2
|
import { entrances } from '../../core/reduced';
|
|
3
3
|
import { inert, warnOnce, type Instance } from '../../core/types';
|
|
4
4
|
|
|
5
|
-
/* Elastic scale-in for a single element (
|
|
5
|
+
/* Elastic scale-in for a single element (scale .7 → 1). */
|
|
6
6
|
|
|
7
7
|
export interface PopOptions {
|
|
8
8
|
scale?: number;
|
|
@@ -2,7 +2,7 @@ import { createScope } from '../../core/scope';
|
|
|
2
2
|
import { inert, warnOnce, type Instance } from '../../core/types';
|
|
3
3
|
|
|
4
4
|
/*
|
|
5
|
-
*
|
|
5
|
+
* radial-menu: options fan out in an arc from a
|
|
6
6
|
* round toggle (CSS trig on --mk-i), elastic, 50 ms apart. Closed options are
|
|
7
7
|
* `inert`, so they are skipped by Tab and screen readers.
|
|
8
8
|
*/
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
Cuando una lista cambia (ordenar, filtrar, añadir), los elementos viajan a su nuevo lugar con un resorte (0.62 s, razón .52) y los nuevos crecen desde 94 %.
|
|
4
4
|
|
|
5
|
-
Origen: Stepbro — task-layout (`layoutReflow`, `FA(.52)` en el kit).
|
|
6
|
-
|
|
7
5
|
## Vanilla / Astro
|
|
8
6
|
|
|
9
7
|
```html
|
|
@@ -15,9 +13,9 @@ Origen: Stepbro — task-layout (`layoutReflow`, `FA(.52)` en el kit).
|
|
|
15
13
|
|
|
16
14
|
```js
|
|
17
15
|
const reflow = createReflow(lista);
|
|
18
|
-
reflow.capture();
|
|
19
|
-
reordenar(lista);
|
|
20
|
-
reflow.play();
|
|
16
|
+
reflow.capture(); // antes de cambiar el DOM
|
|
17
|
+
reordenar(lista); // tu cambio
|
|
18
|
+
reflow.play(); // después
|
|
21
19
|
```
|
|
22
20
|
|
|
23
21
|
`data-flip-id` estable empareja cada elemento con su posición anterior. No pongas los elementos en `position: absolute`.
|
|
@@ -4,7 +4,7 @@ import { TIMING } from '../../core/timing';
|
|
|
4
4
|
import { captureLayout, reflowLayout, type CapturedLayout } from '../../motion/layout';
|
|
5
5
|
|
|
6
6
|
/*
|
|
7
|
-
*
|
|
7
|
+
* layout reflow on a list: call `capture()` before changing the DOM
|
|
8
8
|
* and `play()` after it; moved items travel on a spring and new ones grow in.
|
|
9
9
|
* Give items a stable `data-flip-id` so moves pair up.
|
|
10
10
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# repel-bento
|
|
2
2
|
|
|
3
|
-
Bento con entrada spring y repulsión de tarjetas vecinas
|
|
3
|
+
Bento con entrada spring y repulsión de tarjetas vecinas.
|
|
4
4
|
|
|
5
5
|
```html
|
|
6
6
|
<div class="mk-repel-bento" data-mk="repel-bento"><article>Uno</article><article>Dos</article></div>
|
|
@@ -14,8 +14,8 @@ const instance = createRepelBento(root);
|
|
|
14
14
|
instance.destroy();
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
`distance` máximo 30 px; `breakpoint` 1024. Entrada con spring 60/15 y desfase .006 s; repulsión de vecinos min(5000/distancia,30) con spring 300/25. `play()` repite la entrada; `reset()` vuelve al reposo. Bajo el breakpoint, puntero grueso y movimiento reducido no hay repulsión.
|
|
17
|
+
`distance` máximo 30 px; `breakpoint` 1024. Entrada con spring 60/15 y desfase .006 s; repulsión de vecinos min(5000/distancia,30) con spring 300/25. `play()` repite la entrada; `reset()` vuelve al reposo. Bajo el breakpoint, puntero grueso y movimiento reducido no hay repulsión.
|
|
18
18
|
|
|
19
19
|
Vanilla declarativo: `autoInit(root)`. React: `useRepelBento(ref, options, deps)` devuelve una ref a la instancia; se destruye al desmontar. Con movimiento reducido no se ejecuta movimiento decorativo. `destroy()` restaura atributos y estilos y quita listeners, observers y nodos generados.
|
|
20
20
|
|
|
21
|
-
Desde 0.8.0.
|
|
21
|
+
Desde 0.8.0.
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
Un velo del color de la superficie la cubre y se abre como una onda desde un punto; luego se desvanece. El sobrepaso de 2 hace que la apertura pase la esquina más lejana a mitad de camino, y el final queda suave.
|
|
4
4
|
|
|
5
|
-
Origen: Stepbro — reveal (`reveal-wipe` en el kit; usado en la navegación, el calendario y la hora). Velocidades `fast` 0.5/0.25 s, `normal` 0.7/0.35, `slow` 1.05/0.5, `slower` 1.6/0.75 (recorrido/fundido), `power2.out` y fundido `power1.out`.
|
|
6
|
-
|
|
7
5
|
## Vanilla / Astro
|
|
8
6
|
|
|
9
7
|
```html
|
|
@@ -3,7 +3,7 @@ import { inert, warnOnce, type Instance } from '../../core/types';
|
|
|
3
3
|
import { revealFrom, type RevealHandle, type RevealSpeed } from '../../motion/reveal';
|
|
4
4
|
|
|
5
5
|
/*
|
|
6
|
-
*
|
|
6
|
+
* reveal on an element: a veil in its own colour opens like a wave
|
|
7
7
|
* from the origin (an element, else the last pointer position, else the centre).
|
|
8
8
|
*/
|
|
9
9
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# rotator
|
|
2
2
|
|
|
3
|
-
Rotador de testimonios con autoplay, pausa al enfocar y controles manuales
|
|
3
|
+
Rotador de testimonios con autoplay, pausa al enfocar y controles manuales.
|
|
4
4
|
|
|
5
5
|
```html
|
|
6
6
|
<div class="mk-rotator" data-mk="rotator"><blockquote>Uno</blockquote><blockquote>Dos</blockquote></div>
|
|
@@ -14,8 +14,8 @@ const instance = createRotator(root);
|
|
|
14
14
|
instance.destroy();
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
`interval` 8000 ms, `autoplay` true, `index` 0, `onChange(index)`. `select(index)`, `next()`, `previous()`, `index`. Pausa al señalar, enfocar o esconder la pestaña; los controles reinician el intervalo. Solo la pieza activa es visible y accesible. Entrada opacity/y 5 a 0, .25 s.
|
|
17
|
+
`interval` 8000 ms, `autoplay` true, `index` 0, `onChange(index)`. `select(index)`, `next()`, `previous()`, `index`. Pausa al señalar, enfocar o esconder la pestaña; los controles reinician el intervalo. Solo la pieza activa es visible y accesible. Entrada opacity/y 5 a 0, .25 s.
|
|
18
18
|
|
|
19
19
|
Vanilla declarativo: `autoInit(root)`. React: `useRotator(ref, options, deps)` devuelve una ref a la instancia; se destruye al desmontar. Con movimiento reducido no se ejecuta movimiento decorativo. `destroy()` restaura atributos y estilos y quita listeners, observers y nodos generados.
|
|
20
20
|
|
|
21
|
-
Desde 0.8.0.
|
|
21
|
+
Desde 0.8.0.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# scroll-dock
|
|
2
|
+
|
|
3
|
+
La navegación rápida de DOVV Studio: una cápsula invertida fija abajo y al centro. Aparece cuando el scroll pasa de 0.6 alturas de ventana y se esconde cuando el footer llega a 40 px del borde inferior. Sube desde `translateY(140%)` en 0.6 s `cubic-bezier(.32,1.25,.5,1)`, un resorte con un poco de rebote, y su opacidad cambia en 0.3 s.
|
|
4
|
+
|
|
5
|
+
## Vanilla / Astro
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<nav class="mk-scroll-dock" data-mk="scroll-dock" data-mk-hide-when="footer" aria-label="Navegación rápida">
|
|
9
|
+
<a href="#proyectos">Proyectos</a>
|
|
10
|
+
<a href="#proceso">Cómo trabajamos</a>
|
|
11
|
+
<a href="#precios">Precios</a>
|
|
12
|
+
<a href="#contacto">Contacto</a>
|
|
13
|
+
</nav>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Con `autoInit()` las opciones van como `data-mk-<opción>` (`data-mk-threshold`, `data-mk-hide-when`, `data-mk-hide-offset`). Sin `autoInit`: `createScrollDock(el, opciones)`, que devuelve `visible`, `update()` y `destroy()`. Cada cambio emite `mk:change` con `{ visible }`.
|
|
17
|
+
|
|
18
|
+
## React
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
const ref = useRef<HTMLElement>(null);
|
|
22
|
+
useScrollDock(ref, { hideWhen: 'footer' });
|
|
23
|
+
return <nav ref={ref} className="mk-scroll-dock" aria-label="Navegación rápida">…</nav>;
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Opciones
|
|
27
|
+
|
|
28
|
+
| Opción | Por defecto | Qué hace |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| `threshold` | `0.6` | Scroll mínimo, en alturas de ventana |
|
|
31
|
+
| `hideWhen` | — | Elemento o selector que la esconde al llegar abajo |
|
|
32
|
+
| `hideOffset` | `40` | px antes del borde inferior |
|
|
33
|
+
| `scroller` | `window` | Contenedor con scroll propio (elemento o selector, buscado desde la cápsula hacia arriba) |
|
|
34
|
+
| `onChange(visible)` | — | Al aparecer o esconderse |
|
|
35
|
+
|
|
36
|
+
Dentro de un contenedor con scroll propio, añade `.mk-scroll-dock--sticky` y pasa `scroller`. Así queda pegada al borde inferior del contenedor en vez de al de la ventana. Ponla como último hijo del contenido. No ocupa sitio: se superpone a la última sección, como la versión fija, así que no deja una franja vacía al final.
|
|
37
|
+
|
|
38
|
+
## Variables CSS
|
|
39
|
+
|
|
40
|
+
`--mk-scroll-dock-bottom` (24 px) y `--mk-scroll-dock-height` (58 px, 64 px en móvil; sólo la usa `--sticky`; cámbiala si cambias el alto de los botones).
|
|
41
|
+
|
|
42
|
+
En pantallas de 680 px o menos los botones pasan a pestañas (icono sobre etiqueta, 11 px), así caben cuatro o cinco en un teléfono. Si aun así no caben, la cápsula se desplaza por dentro y nunca desborda la página.
|
|
43
|
+
|
|
44
|
+
## Accesibilidad
|
|
45
|
+
|
|
46
|
+
Los enlaces siguen en el orden de tabulación. Si reciben foco con la cápsula escondida, `:focus-within` la muestra. Con movimiento reducido sólo cambia la opacidad.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createScope } from '../../core/scope';
|
|
2
|
+
import { inert, type Instance } from '../../core/types';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* Scroll dock (DOVV Studio quick navigation): a floating capsule that rises
|
|
6
|
+
* once the page is scrolled past `threshold` viewports and sinks again when
|
|
7
|
+
* `hideWhen` (the footer) comes within `hideOffset` px of the bottom edge.
|
|
8
|
+
* The rise is CSS: translateY 140 % → 0 in .6 s cubic-bezier(.32,1.25,.5,1),
|
|
9
|
+
* opacity .3 s. Keyboard focus inside shows it whatever the scroll.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export interface ScrollDockOptions {
|
|
13
|
+
/** Scroll distance before it shows, in viewport heights. */
|
|
14
|
+
threshold?: number;
|
|
15
|
+
/** Element (or selector) that sends it away when it reaches the viewport bottom. */
|
|
16
|
+
hideWhen?: Element | string | null;
|
|
17
|
+
/** px above the bottom edge at which `hideWhen` counts as reached. */
|
|
18
|
+
hideOffset?: number;
|
|
19
|
+
/** Scrolling element (or selector, looked up from the dock outwards); the window by default. */
|
|
20
|
+
scroller?: HTMLElement | string | null;
|
|
21
|
+
onChange?: (visible: boolean) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ScrollDockInstance extends Instance {
|
|
25
|
+
readonly visible: boolean;
|
|
26
|
+
/** Re-read the scroll position (after layout changes). */
|
|
27
|
+
update(): void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function createScrollDock(root: HTMLElement | null, options: ScrollDockOptions = {}): ScrollDockInstance {
|
|
31
|
+
if (!root) return inert({ visible: false, update() {} });
|
|
32
|
+
const scope = createScope();
|
|
33
|
+
const threshold = Number.isFinite(options.threshold) ? Math.max(0, options.threshold!) : 0.6;
|
|
34
|
+
const hideOffset = Number.isFinite(options.hideOffset) ? options.hideOffset! : 40;
|
|
35
|
+
const scroller = typeof options.scroller === 'string'
|
|
36
|
+
? root.closest<HTMLElement>(options.scroller) ?? document.querySelector<HTMLElement>(options.scroller)
|
|
37
|
+
: options.scroller ?? null;
|
|
38
|
+
const hideTarget = () => (typeof options.hideWhen === 'string' ? document.querySelector(options.hideWhen) : options.hideWhen ?? null);
|
|
39
|
+
let visible = false;
|
|
40
|
+
let queued = false;
|
|
41
|
+
scope.attr(root, 'data-mk-ready', '');
|
|
42
|
+
|
|
43
|
+
const update = () => {
|
|
44
|
+
queued = false;
|
|
45
|
+
if (scope.disposed) return;
|
|
46
|
+
const offset = scroller ? scroller.scrollTop : window.scrollY;
|
|
47
|
+
const viewport = scroller ? scroller.clientHeight : window.innerHeight;
|
|
48
|
+
const bottom = scroller ? scroller.getBoundingClientRect().bottom : window.innerHeight;
|
|
49
|
+
const end = hideTarget();
|
|
50
|
+
const reachedEnd = end ? end.getBoundingClientRect().top < bottom - hideOffset : false;
|
|
51
|
+
const next = offset > viewport * threshold && !reachedEnd;
|
|
52
|
+
if (next === visible) return;
|
|
53
|
+
visible = next;
|
|
54
|
+
scope.attr(root, 'data-mk-visible', next ? '' : null);
|
|
55
|
+
options.onChange?.(next);
|
|
56
|
+
root.dispatchEvent(new CustomEvent('mk:change', { bubbles: true, detail: { visible: next } }));
|
|
57
|
+
};
|
|
58
|
+
const queue = () => {
|
|
59
|
+
if (queued) return;
|
|
60
|
+
queued = true;
|
|
61
|
+
requestAnimationFrame(update);
|
|
62
|
+
};
|
|
63
|
+
scope.on(scroller ?? window, 'scroll', queue, { passive: true });
|
|
64
|
+
scope.on(window, 'resize', queue, { passive: true });
|
|
65
|
+
update();
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
get visible() { return visible; },
|
|
69
|
+
update,
|
|
70
|
+
destroy: () => scope.dispose(),
|
|
71
|
+
};
|
|
72
|
+
}
|