@yewdiel21/motion-kit 0.9.1 → 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.
Files changed (129) hide show
  1. package/README.md +93 -83
  2. package/dist/auto/index.d.ts +22 -3
  3. package/dist/auto/index.js +41 -3
  4. package/dist/auto/index.js.map +1 -1
  5. package/dist/{chunk-BFC27F2G.js → chunk-IKQ5FODN.js} +2 -2
  6. package/dist/{chunk-JQMJQSXD.js → chunk-TFNRIZHU.js} +1614 -11
  7. package/dist/chunk-TFNRIZHU.js.map +1 -0
  8. package/dist/{chunk-XDWBVFHQ.js → chunk-XAKC2QY7.js} +1202 -3
  9. package/dist/chunk-XAKC2QY7.js.map +1 -0
  10. package/dist/css/components/calendar.css +159 -0
  11. package/dist/css/components/hover-card.css +14 -0
  12. package/dist/css/components/input-otp.css +10 -0
  13. package/dist/css/components/pagination.css +11 -0
  14. package/dist/css/components/quantity.css +16 -0
  15. package/dist/css/components/slot-picker.css +126 -0
  16. package/dist/css/components/studio-dock.css +35 -0
  17. package/dist/css/components/trailer.css +37 -0
  18. package/dist/css/controls.css +65 -0
  19. package/dist/elements/index.d.ts +72 -4
  20. package/dist/elements/index.js +170 -4
  21. package/dist/elements/index.js.map +1 -1
  22. package/dist/index.css +1017 -0
  23. package/dist/index.d.ts +3 -3
  24. package/dist/index.js +45 -3
  25. package/dist/index.js.map +1 -1
  26. package/dist/react/index.d.ts +105 -6
  27. package/dist/react/index.js +350 -3
  28. package/dist/react/index.js.map +1 -1
  29. package/dist/{reduced-B98f8LPs.d.ts → reduced-BmBzKH94.d.ts} +199 -2
  30. package/dist/{reveal-C0hCDcB_.d.ts → reveal-jsX-uwUz.d.ts} +211 -1
  31. package/dist/{store-pHiUvxP8.d.ts → store-DYb3tTsZ.d.ts} +1 -1
  32. package/package.json +1 -1
  33. package/registry.json +178 -104
  34. package/skills/motion-kit/SKILL.md +209 -189
  35. package/src/auto/auto-init.ts +38 -0
  36. package/src/components/calendar/README.md +82 -0
  37. package/src/components/calendar/calendar.css +158 -0
  38. package/src/components/calendar/element.ts +27 -0
  39. package/src/components/calendar/engine.ts +412 -0
  40. package/src/components/calendar/react.tsx +69 -0
  41. package/src/components/hover-card/README.md +16 -0
  42. package/src/components/hover-card/element.ts +8 -0
  43. package/src/components/hover-card/engine.ts +69 -0
  44. package/src/components/hover-card/hover-card.css +13 -0
  45. package/src/components/hover-card/react.tsx +19 -0
  46. package/src/components/input-otp/README.md +18 -0
  47. package/src/components/input-otp/element.ts +14 -0
  48. package/src/components/input-otp/engine.ts +106 -0
  49. package/src/components/input-otp/input-otp.css +9 -0
  50. package/src/components/input-otp/react.tsx +21 -0
  51. package/src/components/pagination/README.md +17 -0
  52. package/src/components/pagination/element.ts +14 -0
  53. package/src/components/pagination/engine.ts +102 -0
  54. package/src/components/pagination/pagination.css +10 -0
  55. package/src/components/pagination/react.tsx +22 -0
  56. package/src/components/quantity/README.md +20 -0
  57. package/src/components/quantity/element.ts +14 -0
  58. package/src/components/quantity/engine.ts +85 -0
  59. package/src/components/quantity/quantity.css +15 -0
  60. package/src/components/quantity/react.tsx +21 -0
  61. package/src/components/slot-picker/README.md +68 -0
  62. package/src/components/slot-picker/element.ts +27 -0
  63. package/src/components/slot-picker/engine.ts +412 -0
  64. package/src/components/slot-picker/react.tsx +76 -0
  65. package/src/components/slot-picker/slot-picker.css +125 -0
  66. package/src/components/studio-dock/README.md +44 -0
  67. package/src/components/studio-dock/element.ts +11 -0
  68. package/src/components/studio-dock/engine.ts +212 -0
  69. package/src/components/studio-dock/react.tsx +23 -0
  70. package/src/components/studio-dock/studio-dock.css +34 -0
  71. package/src/components/trailer/README.md +28 -0
  72. package/src/components/trailer/element.ts +13 -0
  73. package/src/components/trailer/engine.ts +189 -0
  74. package/src/components/trailer/react.tsx +16 -0
  75. package/src/components/trailer/trailer.css +36 -0
  76. package/src/core/dates.ts +98 -0
  77. package/src/css/controls/README.md +14 -0
  78. package/src/css/controls/status.css +63 -0
  79. package/src/elements/index.ts +24 -0
  80. package/src/index.ts +25 -0
  81. package/src/react/index.ts +21 -0
  82. package/src/react/use-action-button.ts +6 -0
  83. package/src/react/use-editorial-reveal.ts +7 -0
  84. package/src/react/use-film-hero.ts +6 -0
  85. package/src/react/use-font-cycle.ts +7 -0
  86. package/src/react/use-image-zoom.ts +5 -0
  87. package/src/react/use-joined-cta.ts +5 -0
  88. package/src/react/use-local-clock.ts +5 -0
  89. package/src/react/use-scroll-dock.ts +5 -0
  90. package/src/react/use-sticker.ts +5 -0
  91. package/src/react/use-studio-dock.ts +8 -0
  92. package/src/react/use-thumbnail-nav.ts +7 -0
  93. package/src/react/use-trailer.ts +6 -0
  94. package/src/react/use-weight-hover.ts +5 -0
  95. package/src/recipes/action-button/README.md +31 -0
  96. package/src/recipes/action-button/action-button.css +113 -0
  97. package/src/recipes/action-button/create-action-button.ts +56 -0
  98. package/src/recipes/editorial-reveal/README.md +35 -0
  99. package/src/recipes/editorial-reveal/create-editorial-reveal.ts +91 -0
  100. package/src/recipes/editorial-reveal/editorial-reveal.css +17 -0
  101. package/src/recipes/film-hero/README.md +23 -0
  102. package/src/recipes/film-hero/create-film-hero.ts +65 -0
  103. package/src/recipes/film-hero/film-hero.css +29 -0
  104. package/src/recipes/font-cycle/README.md +11 -0
  105. package/src/recipes/font-cycle/create-font-cycle.ts +126 -0
  106. package/src/recipes/font-cycle/font-cycle.css +36 -0
  107. package/src/recipes/image-zoom/README.md +16 -0
  108. package/src/recipes/image-zoom/create-image-zoom.ts +27 -0
  109. package/src/recipes/image-zoom/image-zoom.css +7 -0
  110. package/src/recipes/joined-cta/README.md +13 -0
  111. package/src/recipes/joined-cta/create-joined-cta.ts +22 -0
  112. package/src/recipes/joined-cta/joined-cta.css +17 -0
  113. package/src/recipes/local-clock/README.md +31 -0
  114. package/src/recipes/local-clock/create-local-clock.ts +61 -0
  115. package/src/recipes/scroll-dock/README.md +46 -0
  116. package/src/recipes/scroll-dock/create-scroll-dock.ts +72 -0
  117. package/src/recipes/scroll-dock/scroll-dock.css +70 -0
  118. package/src/recipes/sticker/README.md +59 -0
  119. package/src/recipes/sticker/create-sticker.ts +490 -0
  120. package/src/recipes/sticker/sticker.css +112 -0
  121. package/src/recipes/thumbnail-nav/README.md +16 -0
  122. package/src/recipes/thumbnail-nav/create-thumbnail-nav.ts +165 -0
  123. package/src/recipes/thumbnail-nav/thumbnail-nav.css +108 -0
  124. package/src/recipes/weight-hover/README.md +37 -0
  125. package/src/recipes/weight-hover/create-weight-hover.ts +136 -0
  126. package/src/recipes/weight-hover/weight-hover.css +7 -0
  127. package/dist/chunk-JQMJQSXD.js.map +0 -1
  128. package/dist/chunk-XDWBVFHQ.js.map +0 -1
  129. /package/dist/{chunk-BFC27F2G.js.map → chunk-IKQ5FODN.js.map} +0 -0
@@ -1,189 +1,209 @@
1
- ---
2
- name: motion-kit
3
- description: Use motion-kit components and animation recipes in vanilla JavaScript, Astro and React. Includes dialogs, menus, toasts, chat, navigation, onboarding, sortable lists, text and scroll effects, collections, themes, CSS controls and optional WebGL. Use for component installation, configuration, composition and motion-kit API examples.
4
- ---
5
-
6
- # motion-kit
7
-
8
- Motion components for vanilla JavaScript, Astro and React. Entry points:
9
-
10
- | Layer | Import | What |
11
- |---|---|---|
12
- | Core + motion functions | `@yewdiel21/motion-kit` | `registerMotion`, `EASE`, `TIMING`, `flowIn`, `revealFrom`, `slideIndicator`, `countTo`, `captureLayout`/`reflowLayout`, `enterMessages`… |
13
- | Recipes | same + `/auto` + `/react` | `createX(root, options) → { destroy, …controls }`; `data-mk="x"` + `autoInit()`; `useX(ref, options)` |
14
- | Components | engine in `.`, `<mk-x>` in `/elements`, `<X>` in `/react` | own markup + styles: `dialog`, `menu`, `toast`, `theme-picker`, `chat`, `chat-background`, `nav`, `steps`, `cursor`, `island` |
15
- | WebGL (optional) | `@yewdiel21/motion-kit/gl` (+ matching hooks in `/react`) | `createParticleHero`, `createLiquidBlend`, `createMatcap`, `createShaderGrain`, `createShaderBlur`; reduced motion and fallback |
16
- | Effects | `/css/effects` (in `/css`) + `.` / `/react` | `.mk-anim-*`, `.mk-loading`, `.mk-text-gradient`, `.mk-frost`, `.mk-float`, `.mk-grain`, `.mk-success`; `animate(el, name)`, `leave(el, exit)`, `confetti(origin)`, `<Frost />` |
17
- | Design line | `/css`, `/css/themes`, `/css/fonts`, `/theme`, `/theme/generate` | tokens, 19 themes, controls, theme runtime and generator |
18
-
19
- ## 1. Install (pick one)
20
-
21
- **Package** (public npm registry):
22
-
23
- ```sh
24
- npm i @yewdiel21/motion-kit gsap
25
- npm i -D @material/material-color-utilities@^0.3.0 # only for the theme generator
26
- ```
27
-
28
- **Copy the code** (editable, no runtime dependency on the package):
29
-
30
- ```sh
31
- npx @yewdiel21/motion-kit list
32
- npx @yewdiel21/motion-kit add tabs slot-text theme-picker --react --elements --dir src/motion-kit
33
- npx @yewdiel21/motion-kit add theme neogreen oled
34
- npx @yewdiel21/motion-kit add controls
35
- npx @yewdiel21/motion-kit theme brand --seed "#ff6a3d" --out src/styles
36
- ```
37
-
38
- `add` copies each entry, what it `requires`, only the core files it imports, the base CSS (tokens, default theme, base, entrance states) and, with `--react` / `--elements`, the hook/component and custom element. Imports are relative, so nothing needs rewriting. `gsap` must be installed.
39
-
40
- ## 2. Wire it once
41
-
42
- 1. CSS: `import '@yewdiel21/motion-kit/css'` (tokens, default theme, controls, recipe and component styles). Add `/css/themes` (all 19) or `/css/themes/<id>` and, for fonts, `/css/fonts`.
43
- 2. Head, inline, in this order:
44
- - `MK_THEME_HEAD_SCRIPT` (`/theme`): applies the stored theme/mode before the first paint.
45
- - `MK_HEAD_SCRIPT` (`/auto` or `.`): adds `.mk-js` so entrances start hidden, `.mk-failed` if no recipe ran within 2.5 s.
46
- 3. `<body class="mk-app">` gives the page the theme's background, text colour and DM Sans.
47
- 4. Start things:
48
- - **Astro / HTML**: `initTheme()`; `data-mk="<recipe>"` + `autoInit()`; `defineElements()` for `<mk-…>` components.
49
- - **React / Next.js** (entries are `'use client'`): `<ThemeProvider>` at the root, hooks (`useTabs(ref)`), components (`<ThemePicker />`, `<SlotText text />`, `<AnimatedNumber value />`, `<FlipGroup>`). Keep `data-mk="<recipe>"` on recipe markup so the hidden entrance state applies before hydration.
50
-
51
- ## 3. Design line
52
-
53
- - Colour: 37 Material 3 roles `--md-sys-color-*` per theme; components use the semantic layer `--mk-bg`, `--mk-surface`, `--mk-surface-1…5` (container lowest→highest), `--mk-text`, `--mk-muted`, `--mk-accent`, `--mk-on-accent`, `--mk-accent-soft`, `--mk-secondary-soft`, `--mk-outline(-soft)`, `--mk-error(-soft)`, `--mk-scrim`, `--mk-shadow-color`. Never hard-code colours in components: use these.
54
- - Themes: `<html data-mk-theme="<id>" data-mk-mode="system|light|dark">`. Ids: blue, purple, neogreen (default), red, pink, pastel-pink, brown, super-blue, gray, oled (the 10 visible themes) + black, green, orange, cold-blue, modern-1, funny, new, new2, black-green. `setTheme(id)`, `setMode(mode)`, `getTheme()`, event `mk:theme`. Nested `[data-mk-theme]` sections work.
55
- - Own brand colour: `applyTheme('#ff6a3d', { id: 'brand' })` at runtime, or `motion-kit theme brand --seed …` for a static file.
56
- - Type: `--mk-font` (DM Sans), `--mk-font-mono` (Geist Mono), icons `.mk-icon` (Material Symbols Rounded). Scale classes `.mk-type-{display|headline|title|body|label}-{large|medium|small}`.
57
- - Shape: capsules. `--mk-radius-{xs 8, sm 12, md 16, lg 24, xl 32, 2xl 40, 3xl 48, 4xl 64, full}`; bubble 32, dialog 40, chat 48, dock 64. Spacing `--mk-space-1…16` (4 px). Breakpoint 680 px.
58
- - Controls (CSS only): `.mk-glass` (glass on any button/chip; `<span class="mk-glass__shadow">` inside for its shadow), `.mk-burger` (three lines → X with `aria-expanded`), `.mk-button--roll` (`.mk-roll[data-text]` for the label and `.mk-roll__arrow` for the arrow), `.mk-button` (`--primary`, `--tonal`, `--outlined`, `--text`, `--icon`, `--sm`, `--lg`; squash on pointer focus), `.mk-field` (+ `__label`, `__hint`, `aria-invalid`), `.mk-switch` (`<input type=checkbox role=switch>`), `.mk-checkbox`, `.mk-chip` (`--filter[aria-pressed]`, `--input`), `.mk-segmented` (with the `tabs` recipe), `.mk-icon`.
59
- - Full table: [reference/design.md](reference/design.md).
60
-
61
- ## 4. Recipes
62
-
63
- | Recipe | Markup | Key options (defaults) | Controls |
64
- |---|---|---|---|
65
- | `tabs` | root with `[data-mk-tab]` buttons and a `[data-mk-indicator]` (use `.mk-segmented`) | `orientation 'horizontal'`, `duration .45`, `onSelect(i, tab)` (event `mk:tab`) | `select(i)`, `index` |
66
- | `counter` | element with a number | `to` (text), `from 0`, `duration .6`, `decimals`, `locale`, `prefix`, `suffix`, `trigger 'scroll'` | `play()`, `set(n)` |
67
- | `details` | container of `<details>` (chevron `[data-mk-chevron]` in the summary) | — | — |
68
- | `reveal` | element to uncover | `speed 'fast'\|'normal'\|'slow'\|'slower'`, `origin` (selector), `trigger 'manual'\|'load'` | `play(origin?)` |
69
- | `reflow` | list with `[data-mk-item][data-flip-id]` | `duration .62` | `capture()` before the DOM change, `play()` after |
70
- | `slot-text` | text element | `duration 300` ms, `stagger 45`, `maxTilt 5` | `set(text)` |
71
- | `spring-cards` | root with `[data-mk-card]` children, each with `.mk-card__shadow` + image | `push .4`, `falloff 6`, `initialOffset 100`, `stiffness .05`, `friction .8`, `stagger .08`, `delay .4` | — |
72
- | `curved-carousel` | root with `[data-mk-slide]`; optional `.mk-carousel__media`, `.mk-carousel__title` | `friction .85`, `throwBoost 2`, `wheel .2`, `startIndex 0`, `onSelect(i, slide)` (auto: `mk:select` event) | `next()`, `prev()`, `goTo(i)`, `index` |
73
- | `pile` | `.mk-pile > .mk-pile__stack > .mk-pile__layer > .mk-pile__media`; back layers styled with `pileStyle(pileLayers(n, seed)[i])` | `threshold .25` | — |
74
- | `words` | any text element | `by 'words'\|'chars'`, `trigger 'scroll'\|'load'\|'manual'`, `duration .8`, `stagger .1` | `reveal()`, `replace(mutate)` |
75
- | `flow` | container; its children animate | `y 20`, `blur 16`, `duration .5`, `stagger .05`, `style 1\|2`, `trigger` | `play()` |
76
- | `pop` | any element | `scale .7`, `duration 1.1`, `trigger` | `play()` |
77
- | `fit-text` | one-line element inside a parent | `safety .995` | `refit()` |
78
- | `stretch-footer` | `footer[data-mk="stretch-footer"]` containing `[data-mk-footer-title]`, plus a `[data-mk-phantom]` spacer after `<main>` | `lambda .9` | — |
79
- | `radial-menu` | root with `[data-mk-toggle]` and `[data-mk-item]` direct children | `radius '3.6rem'`, `startAngle 1.5708`, `spacing .9` | `open()`, `close()`, `toggle()`, `isOpen` |
80
- | `pill` | `.mk-pill` button, `.mk-stretch` link | CSS only | — |
81
- | `mask-text` | any text | `by 'lines'|'words'|'chars'`, `y 100`, `duration 1.2`, `delay .1`, `stagger .02`, `trigger 'scroll'|'load'|'manual'`, `once` | `play()`, `hide()`, `split` |
82
- | `cascade` | block with `[data-mk-cascade="rule|fade|rise"]` children (`rise` inside a clip) | `delay` (`data-mk-delay`), `step .03`, `duration 1.2`, `once`, `trigger` | `play()`, `reset()` |
83
- | `text-roll` | a title | `trigger 'load'|'scroll'|'hover'|'manual'`, `delay`, `interval`, `duration 2`, `stagger .04` | `play()` |
84
- | `letters` | a word | `effect 'scale'|'blink'`, `colors`, `autoplay` | `play()` |
85
- | `parallax` | clipped frame with a picture (`[data-mk-parallax-target]`) | `amount 1`, `lerp 1`, `scroll` | `seek(p)` |
86
- | `depth-grid` | grid container | `start 'top center'`, `end 'bottom bottom'`, `lerp 1`, `scroll` | `seek(p)` |
87
- | `frame-zoom` | frame with one picture/video child | `start 'top bottom'`, `end 'bottom top'`, `lerp 1` | `seek(p)`, `open(on)` |
88
- | `contrast` | header; sections with `data-mk-tone="light|dark"` | `sections`, `attribute 'data-mk-on'` | `tone` |
89
- | `marquee` | strip whose first child is the group | `axis 'x'|'y'`, `speed 2` (s per item), `reverse` | `refresh()` |
90
- | `card-dock` | `.mk-card-dock` with `.mk-card-dock__item` (> `a.mk-card-dock__card` + `.mk-card-dock__title`), optional `.mk-card-dock__cta`; `mk-card-dock--fixed` | `intro 1600` (ms spread on load, 0 skips) | `expand()`, `collapse()`, `expanded` |
91
- | `snap-strip` | `.mk-snap-strip` with the cards as children | `autoplay`, `interval 5000`, `resume 2000`, `delay 500` (`data-mk-delay`) | `next()`, `previous()`, `index` |
92
- | `fill-text` | any text | `from .2`, `start 'top 90%'`, `end 'center 40%'`, `scroll` | `seek(p)`, `progress` |
93
- | `sortable` | root with `[data-mk-item][data-mk-id]` (optional `[data-mk-sortable-group="key"]` lists, `data-mk-sortable-locked`, `[data-mk-sort-handle]`) | `mutate true` (`false` in `useSortable`), `handle`, `autoScroll true`, `follow {1100, 38}`, `onReorder(detail)` (event `mk:reorder`, cancelable) | `cancel()`, `dragging` |
94
-
95
- React hooks: `useCardDock`, `useSnapStrip`, `useFillText`, `useParticleHero`, `useMaskText`, `useCascade`, `useTextRoll`, `useLetters`, `useParallax`, `useDepthGrid`, `useFrameZoom`, `useContrast`, `useMarquee`, `useSortable`, `useTabs`, `useCounter`, `useDetails`, `useReveal`, `useReflow`, `useSlotText`, `useSpringCards`, `useCurvedCarousel`, `usePile`, `useWords`, `useFlow`, `usePop`, `useFitText`, `useStretchFooter`, `useRadialMenu`. Markup and options: each recipe's `README.md` (`src/recipes/<name>/`). Parameters and options: [reference/recipes.md](reference/recipes.md).
96
-
97
- ## 4b. Effects (v0.5)
98
-
99
- | Name (`animate` / class `.mk-anim-<name>`) | Motion | Time |
100
- |---|---|---|
101
- | `out` / `out-left` / `out-bottom` | up 20 / left 64 / down 24 px, blur 16, fade | 0.5 s |
102
- | `collapse` | folds height, padding and margin while blurring | 0.25 s |
103
- | `highlight` | 4 px inner ring in primary-container | 1 s |
104
- | `shake` / `shake-strong` | ±4 px / ±10 px | 0.5 s / 0.4 s |
105
- | `fade`, `wipe`, `progress-in` | appear; a surface veil leaves to the right; appear late | 0.3 / 0.7 / 1 s |
106
-
107
- `await animate(el, name)` replays even if already playing; `await leave(row, 'out-left' | 'collapse', { remove })` animates then removes (or hides). In React lists: `leave(item, 'out', { remove: false })`, then drop it from state. Classes: `.mk-loading` (+`--soft`), `.mk-shimmer`, `.mk-text-gradient` (theme), `.mk-text-rainbow`, `.mk-frost` (+ five `<i>`; `--top`, `--fixed`; `<Frost />`), `.mk-float` / `.mk-float-group`, `.mk-grain` (`--mk-grain`), `.mk-success` + `confetti(button)`. Reduced motion: loops stop, exits jump to the end, confetti does not draw.
108
-
109
- ## 5. Motion functions (no instance)
110
-
111
- Use them inside your own components; they all honour reduced motion and return the tween (or `null`).
112
-
113
- | Function | use |
114
- |---|---|
115
- | `flowIn(targets, { style, reverse, target })` / `flowOut(targets)` | lists, steps, settings entering / leaving |
116
- | `stepChange(container, () => render next)` | wizard / onboarding steps (flow out → swap → flow in) |
117
- | `flowInMenu`, `flowInPalette`, `flowInForm`, `flowInLanding`, `flowInContent` | menu options, swatches, form cards, landing, articles |
118
- | `revealFrom(el, { origin, speed })` | a surface uncovered like a wave from the control that opened it |
119
- | `toggleDetails(details, open)`, `installDetailsMotion(root)` | disclosures |
120
- | `slideIndicator(indicator, target)` | any sliding selection indicator |
121
- | `countTo(el, n)`, `revealCounter(el, n)`, `tweenNumber(a, b, cb)` | changing numbers, balances |
122
- | `captureLayout` + `reflowLayout` | reorder / filter / add in lists |
123
- | `enterMessages(bubbles)`, `fitComposer(textarea)`, `scrollToLatest(scroller)` | chat / comments |
124
- | `capturePending(el)` + `morphPendingToReply(state, reply, list)` | a "thinking" placeholder turning into the answer (other frameworks) |
125
- | `audioEnergy(stream, onEnergy)` | microphone level 0…1 for `chat-background` (never asks for the mic itself) |
126
- | `channels(start, onChange)` + `spring()` / `eased()` | multi-property spring motion (liquid surfaces) |
127
- | `trackScroll(el, { start, end, lerp, onUpdate })` | an element's 0→1 progress through the viewport (follows the page; `lerp < 1` smooths it) — build your own scroll effects |
128
- | `motionSpring(stiffness, damping, mass?)` → `{ ease, duration }`, `springLinear(…)` | Motion/Framer springs as a GSAP ease or a CSS `linear()` |
129
- | `sheetOpen`, `sheetClose`, `pushUnderlay`, `releaseUnderlay` | a sheet rising from the bottom while the page behind sinks |
130
- | `morphOpen(surface, opener)`, `morphClose`, `morphCloseWithGhost`, `liquidOpen/Close` | any surface growing out of / folding into a control |
131
- | `attachDragDismiss(surface, { onDismiss })`, `attachFloating`, `anchorSurface` | drag to dismiss, floating windows, anchored panels |
132
- | `fadeBackdrop(dialog, 'in')`, `ghostOf`, `clearStyles` | backdrop tint, inert close copies, cleaning without GSAP measuring hidden nodes |
133
-
134
- Curves: `EASE.signature`, `EASE.flow`, `EASE.fadeAway`, `EASE.blurAway`, `EASE.layoutReflow`…; CSS: `var(--mk-ease-signature)`, `--mk-ease-flow`, `--mk-ease-close`. Times: `TIMING.*`.
135
-
136
- ## 6. Components
137
-
138
- | Component | HTML / Astro | React |
139
- |---|---|---|
140
- | `dialog` | `<button data-mk-open="crear">` + `<mk-dialog id="crear" heading="…" [description] [variant="liquid|sheet"] [placement="anchor\|side"] [size="sm\|lg\|xl"] [float="key"] [shared="id"] [no-backdrop] [persistent] [no-drag]>` (children → body; `data-mk-part="footer"` stays below) | `<Dialog.Root variant placement float…><Dialog.Trigger/><Dialog.Content size><Dialog.Header><Dialog.Title/><Dialog.Close/></Dialog.Header><Dialog.Body/><Dialog.Footer/></Dialog.Content></Dialog.Root>` |
141
- | `menu` | `<button data-mk-open="m">` + `<mk-menu id="m" label="…">` with `<button data-mk-item data-mk-value>`; listen `mk:select` | `<Menu.Root onSelect><Menu.Trigger/><Menu.Content><Menu.Item value/><Menu.Separator/></Menu.Content></Menu.Root>` |
142
- | `toast` | `<mk-toaster>` once + `toast('Copiado', { origin: button, tone, action })` | `<Toaster />` once + `toast(…)` |
143
- | `theme-picker` | `<mk-theme-picker [show-hidden] [themes="a,b"]>` | `<ThemePicker themes showHidden onChange />` inside `<ThemeProvider>` |
144
- | `chat` | `<mk-chat [background] [mic] placeholder="…">` (history `[data-mk-message][data-mk-role]` inside); `mk:send` → `el.add('user', t)`, `el.pending()`, `el.reply(answer)` | `<Chat.Root onSend><ChatBackground /><Chat.Messages pending>{<Chat.Message role/>}</Chat.Messages><Chat.Confirm/><Chat.Composer/></Chat.Root>`; set the reply and `pending=false` in the same render |
145
- | `chat-background` | `<mk-chat-background energy scale>` first inside a positioned box; `el.busy(true)` | `<ChatBackground energy scale />` |
146
- | `nav` | `<div class="mk-shell"><main class="mk-shell__view">…</main><mk-nav class="mk-nav mk-nav--bonita" nav-style="bonita" [accent] [reveal] [storage-key]><div class="mk-nav__buttons"><a class="mk-nav__button" href aria-current="page">…</a></div></mk-nav></div>`; `mk:navigate` | `<Nav navStyle items active onSelect first actions accent reveal />` inside `.mk-shell` |
147
- | `cursor` | `<mk-cursor>` once (chip over `[data-mk-cursor="Label"]`, `data-mk-cursor-shape="circle"`, `data-mk-cursor-icon`) · `<mk-cursor variant="dot" text arrows>` inside a zone (`data-mk-cursor-hide` hides it) | `<Cursor />` · `<Cursor variant="dot" text arrows zone />` |
148
- | `island` | `<mk-island [class="mk-island--contained"] [entrance="false|manual"]>` with `.mk-island__backdrop[data-mk-island-close]`, `.mk-island__shell` (glass, header, `[data-mk-island-toggle="menu"]` buttons, `.mk-island__cta`), `.mk-island__content > div > [data-mk-island-panel="menu"]` with `[data-mk-island-item]` rows and `.mk-island-card`s; `mk:island` | `<Island brand cta panels={[{ name, label, icon, light, content }]} contained onChange />` |
149
- | `steps` | `<mk-steps class="mk-steps">` + `[data-mk-step]` sections, `[data-mk-flow-item]`, `[data-mk-counter]`, `[data-mk-step-next/prev/goto]`; `el.busy(on)`, `mk:step` | `<Steps index onChange busy><ChatBackground /><Steps.Counter /><Steps.Step>…</Steps.Step></Steps>` |
150
-
151
- Register elements with `defineElements()` (or a list of tag names). Engines for your own markup: `createDialog(dialogEl, opts)`, `createMenu(dialogEl, opts)`, `createToaster(hostEl)`, `createThemePicker(el)`, `createChat(el)`, `createChatBackground(el)`, `createNav(navEl, { style })`, `createSteps(el)`.
152
-
153
- Dialog behaviour: the opener turns into the dialog (0.7 s signature, colour handover 0.3 s, content from an 8 px blur) and it folds back on close (0.5 s, content blur 32 + radius 400, via an inert ghost so focus returns at once); without an opener it scales from .92. `data-mk-shared="id"` on a piece of the opener and of the dialog flies that text between them. Auto-resize 0.45 s; drag down > 100 px dismisses (also on phones); `float` lifts it into a movable, resizable window on desktop; full screen with radius 0 under 680 px. Events: `mk:open`, `mk:close` (cancelable), `mk:closed`. `[data-mk-close]` inside closes it. `open(null)` opens with no opener (scale-in).
154
-
155
- Chat: bubbles grow from their speaker's side (scale .5, blur 24, 0.7 s) while words rise; "Pensando…" only after 500 ms, then it morphs (Flip 0.7 s) into the reply; Enter sends, Shift+Enter breaks the line; the composer grows from 64 px (0.18 s). Nav styles: `clasica`, `bonita`, `dock`, `guapa`, `tasks`, `melon`, `tunnel` (`NAV_STYLES`), one glass bar under 680 px; `accent` highlights the active page. Steps: flow out → swap → flow in (0.5 s), counter rolls, focus goes to the step's title. Sortable: lift 0.18 s, spring 1100/38, tilt up to 14°, neighbours slide 0.62 s, land 0.42 s; touch drags only from the handle; Space/arrows/Escape on the keyboard.
156
-
157
- **Adding a component** (in the motion-kit repo): `pnpm new component <name>` scaffolds `src/components/<name>/{engine.ts, <name>.css, element.ts, react.tsx, README.md}`, a test, playground demos, the registry entry and the exports. Fill the engine (works on existing markup, defaults from `TIMING`, reduced motion, `destroy()` restores the DOM), then `pnpm test`. See [reference/components.md](reference/components.md).
158
-
159
- ## 7. Rules
160
-
161
- - Call `destroy()` when the element leaves (hooks, components and `autoInit()`'s returned function do it). Never leave instances running on removed nodes.
162
- - Respect reduced motion: everything here already shows the final state under `prefers-reduced-motion: reduce`; custom motion must check `reduced()` too.
163
- - Flip contract: measure before the DOM change, animate after it (`capture()`/`play()`, `<FlipGroup signature>`).
164
- - Text that changes: `words.replace(() => …)`, `fit.refit()`, `slot.set(text)` — don't re-create.
165
- - Elements inside a closed `<dialog>`: create `words` with `trigger: 'manual'` and call `reveal()` after `showModal()`.
166
- - `stretch-footer` needs the page above it: `main { position: relative; z-index: 1; background: var(--mk-bg) }`.
167
- - Theme: style with `--mk-*` tokens and controls; switch with `setTheme`/`setMode` (or `useTheme()`), never by swapping stylesheets.
168
- - Override look with CSS variables (`--mk-button-bg`, `--mk-field-radius`, `--mk-swatch-size`, `--mk-tabs-indicator`, `--mk-shadow`…), not by editing package code.
169
-
170
- ## 8. Choosing
171
-
172
- - New app with look → `/css` + `/css/themes` + `/css/fonts`, `.mk-app`, controls, `theme-picker` in settings.
173
- - Segmented filters / view switch → `.mk-segmented` + `tabs`. Totals → `counter` / `<AnimatedNumber>`.
174
- - Lists that reorder → `reflow` / `<FlipGroup>`; lists the user reorders by dragging (tasks, kanban) → `sortable`; lists that appear → `flow` (or `flowIn`).
175
- - Editorial headlines → `mask-text` (lines; `y 120` for film-title feel); credits / spec lists → `cascade`; a hero title that keeps alive → `text-roll` with `interval`; a playful word → `letters`.
176
- - Photos in long pages → `parallax`; a gallery section → `depth-grid`; a video section → `frame-zoom`; header over dark/light sections → `contrast`; logos / festivals → `marquee` (+ `.mk-fade-x`).
177
- - Site header for a product / landing → `island` (menu + cart share it); a product switcher pinned to a corner → `card-dock` (`--fixed`); phone carousels → `snap-strip`; a manifesto paragraph → `fill-text`; a hero with a living background → `particle-hero` from `/gl` (keep the text in HTML).
178
- - Portfolio cards → `cursor` chip (`data-mk-cursor="View project"`); a draggable reel or full-bleed hero → `cursor` dot.
179
- - Glass actions or tags over photos / colour → `.mk-glass` (+ `.mk-float-group` for floating tags); bottom fade over scrolling content → `.mk-frost`; a cinematic hero → `.mk-grain`.
180
- - Removing rows → `leave(row, 'out-left' | 'collapse')`; form error → `aria-invalid="true"` (vibrates) or `animate(el, 'shake')`; success → `.mk-success` + `confetti(button)`; loading cards → `.mk-loading`; AI / premium labels → `.mk-text-gradient`.
181
- - Project detail over the page → `dialog` with `variant="sheet"` and `data-mk-underlay` on the page; mobile menu toggle → `.mk-burger`; "Explore" CTA → `.mk-button--roll`.
182
- - App frame → `.mk-shell` + `nav` (let the user pick the style; `storageKey` remembers it). Assistant / support chat → `chat` + `chat-background`. First-run setup → `steps` (with `ChatBackground` and `busy` while saving).
183
- - FAQ / settings groups → `details`. Date/time popovers → `revealFrom` from the field.
184
- - Language switch on a title → `slot-text`.
185
- - Portfolio hero → `spring-cards` + a giant `fit-text words` title; featured projects → `curved-carousel`; archive → `pile`; last screen → `stretch-footer`; corner quick actions → `radial-menu`.
186
- - Film navigation → `ticket-menu` or `<Menu.Root ticket>` / `<mk-menu ticket>`; preserves menu focus, selection and keyboard handling.
187
- - Circular archive → `infinite-reel` (wheel, drag, keyboard, axis x/y); interactive card grid → `repel-bento`; testimonials → `rotator` (8 s, pause on focus/hover).
188
- - Numeric experience/time selector → `drag-ruler` (role slider, 60 px/unit, step .1); a shared active indicator in navigation → `dock-pill` (spring 380/30). Navigation remains the app's responsibility.
189
- - GPU image transitions → `createLiquidBlend`; metal study → `createMatcap`; film processing → `createShaderGrain` / `createShaderBlur`, all from `/gl`. React hooks `useLiquidBlend`, `useMatcap`, `useShaderGrain`, `useShaderBlur` are in `/react`. Import explicitly: these are not autoInit recipes. Retain accessible HTML images and set dimensions on the host. See [reference/recipes.md](reference/recipes.md) and each recipe README for the parameters and controls.
1
+ ---
2
+ name: motion-kit
3
+ description: Use motion-kit components and animation recipes in vanilla JavaScript, Astro and React. Includes dialogs, menus, toasts, chat, navigation, onboarding, sortable lists, text and scroll effects, collections, themes, CSS controls and optional WebGL. Use for component installation, configuration, composition and motion-kit API examples.
4
+ ---
5
+
6
+ # motion-kit
7
+
8
+ Motion components for vanilla JavaScript, Astro and React. Entry points:
9
+
10
+ | Layer | Import | What |
11
+ |---|---|---|
12
+ | Core + motion functions | `@yewdiel21/motion-kit` | `registerMotion`, `EASE`, `TIMING`, `flowIn`, `revealFrom`, `slideIndicator`, `countTo`, `captureLayout`/`reflowLayout`, `enterMessages`… |
13
+ | Recipes | same + `/auto` + `/react` | `createX(root, options) → { destroy, …controls }`; `data-mk="x"` + `autoInit()`; `useX(ref, options)` |
14
+ | Components | engine in `.`, `<mk-x>` in `/elements`, `<X>` in `/react` | own markup + styles: `dialog`, `menu`, `toast`, `theme-picker`, `chat`, `chat-background`, `nav`, `steps`, `cursor`, `island` |
15
+ | WebGL (optional) | `@yewdiel21/motion-kit/gl` (+ matching hooks in `/react`) | `createParticleHero`, `createLiquidBlend`, `createMatcap`, `createShaderGrain`, `createShaderBlur`; reduced motion and fallback |
16
+ | Effects | `/css/effects` (in `/css`) + `.` / `/react` | `.mk-anim-*`, `.mk-loading`, `.mk-text-gradient`, `.mk-frost`, `.mk-float`, `.mk-grain`, `.mk-success`; `animate(el, name)`, `leave(el, exit)`, `confetti(origin)`, `<Frost />` |
17
+ | Design line | `/css`, `/css/themes`, `/css/fonts`, `/theme`, `/theme/generate` | tokens, 19 themes, controls, theme runtime and generator |
18
+
19
+ ## 1. Install (pick one)
20
+
21
+ **Package** (public npm registry):
22
+
23
+ ```sh
24
+ npm i @yewdiel21/motion-kit gsap
25
+ npm i -D @material/material-color-utilities@^0.3.0 # only for the theme generator
26
+ ```
27
+
28
+ **Copy the code** (editable, no runtime dependency on the package):
29
+
30
+ ```sh
31
+ npx @yewdiel21/motion-kit list
32
+ npx @yewdiel21/motion-kit add tabs slot-text theme-picker --react --elements --dir src/motion-kit
33
+ npx @yewdiel21/motion-kit add theme neogreen oled
34
+ npx @yewdiel21/motion-kit add controls
35
+ npx @yewdiel21/motion-kit theme brand --seed "#ff6a3d" --out src/styles
36
+ ```
37
+
38
+ `add` copies each entry, what it `requires`, only the core files it imports, the base CSS (tokens, default theme, base, entrance states) and, with `--react` / `--elements`, the hook/component and custom element. Imports are relative, so nothing needs rewriting. `gsap` must be installed.
39
+
40
+ ## 2. Wire it once
41
+
42
+ 1. CSS: `import '@yewdiel21/motion-kit/css'` (tokens, default theme, controls, recipe and component styles). Add `/css/themes` (all 19) or `/css/themes/<id>` and, for fonts, `/css/fonts`.
43
+ 2. Head, inline, in this order:
44
+ - `MK_THEME_HEAD_SCRIPT` (`/theme`): applies the stored theme/mode before the first paint.
45
+ - `MK_HEAD_SCRIPT` (`/auto` or `.`): adds `.mk-js` so entrances start hidden, `.mk-failed` if no recipe ran within 2.5 s.
46
+ 3. `<body class="mk-app">` gives the page the theme's background, text colour and DM Sans.
47
+ 4. Start things:
48
+ - **Astro / HTML**: `initTheme()`; `data-mk="<recipe>"` + `autoInit()`; `defineElements()` for `<mk-…>` components.
49
+ - **React / Next.js** (entries are `'use client'`): `<ThemeProvider>` at the root, hooks (`useTabs(ref)`), components (`<ThemePicker />`, `<SlotText text />`, `<AnimatedNumber value />`, `<FlipGroup>`). Keep `data-mk="<recipe>"` on recipe markup so the hidden entrance state applies before hydration.
50
+
51
+ ## 3. Design line
52
+
53
+ - Colour: 37 Material 3 roles `--md-sys-color-*` per theme; components use the semantic layer `--mk-bg`, `--mk-surface`, `--mk-surface-1…5` (container lowest→highest), `--mk-text`, `--mk-muted`, `--mk-accent`, `--mk-on-accent`, `--mk-accent-soft`, `--mk-secondary-soft`, `--mk-outline(-soft)`, `--mk-error(-soft)`, `--mk-scrim`, `--mk-shadow-color`. Never hard-code colours in components: use these.
54
+ - Themes: `<html data-mk-theme="<id>" data-mk-mode="system|light|dark">`. Ids: blue, purple, neogreen (default), red, pink, pastel-pink, brown, super-blue, gray, oled (the 10 visible themes) + black, green, orange, cold-blue, modern-1, funny, new, new2, black-green. `setTheme(id)`, `setMode(mode)`, `getTheme()`, event `mk:theme`. Nested `[data-mk-theme]` sections work.
55
+ - Own brand colour: `applyTheme('#ff6a3d', { id: 'brand' })` at runtime, or `motion-kit theme brand --seed …` for a static file.
56
+ - Type: `--mk-font` (DM Sans), `--mk-font-mono` (Geist Mono), icons `.mk-icon` (Material Symbols Rounded). Scale classes `.mk-type-{display|headline|title|body|label}-{large|medium|small}`.
57
+ - Shape: capsules. `--mk-radius-{xs 8, sm 12, md 16, lg 24, xl 32, 2xl 40, 3xl 48, 4xl 64, full}`; bubble 32, dialog 40, chat 48, dock 64. Spacing `--mk-space-1…16` (4 px). Breakpoint 680 px.
58
+ - Controls (CSS only): `.mk-glass` (glass on any button/chip; `<span class="mk-glass__shadow">` inside for its shadow), `.mk-burger` (three lines → X with `aria-expanded`), `.mk-button--roll` (`.mk-roll[data-text]` for the label and `.mk-roll__arrow` for the arrow), `.mk-button` (`--primary`, `--tonal`, `--outlined`, `--text`, `--icon`, `--sm`, `--lg`; squash on pointer focus), `.mk-field` (+ `__label`, `__hint`, `aria-invalid`), `.mk-switch` (`<input type=checkbox role=switch>`), `.mk-checkbox`, `.mk-chip` (`--filter[aria-pressed]`, `--input`), `.mk-segmented` (with the `tabs` recipe), `.mk-icon`.
59
+ - Full table: [reference/design.md](reference/design.md).
60
+
61
+ ## 4. Recipes
62
+
63
+ | Recipe | Markup | Key options (defaults) | Controls |
64
+ |---|---|---|---|
65
+ | `tabs` | root with `[data-mk-tab]` buttons and a `[data-mk-indicator]` (use `.mk-segmented`) | `orientation 'horizontal'`, `duration .45`, `onSelect(i, tab)` (event `mk:tab`) | `select(i)`, `index` |
66
+ | `counter` | element with a number | `to` (text), `from 0`, `duration .6`, `decimals`, `locale`, `prefix`, `suffix`, `trigger 'scroll'` | `play()`, `set(n)` |
67
+ | `details` | container of `<details>` (chevron `[data-mk-chevron]` in the summary) | — | — |
68
+ | `reveal` | element to uncover | `speed 'fast'\|'normal'\|'slow'\|'slower'`, `origin` (selector), `trigger 'manual'\|'load'` | `play(origin?)` |
69
+ | `reflow` | list with `[data-mk-item][data-flip-id]` | `duration .62` | `capture()` before the DOM change, `play()` after |
70
+ | `slot-text` | text element | `duration 300` ms, `stagger 45`, `maxTilt 5` | `set(text)` |
71
+ | `spring-cards` | root with `[data-mk-card]` children, each with `.mk-card__shadow` + image | `push .4`, `falloff 6`, `initialOffset 100`, `stiffness .05`, `friction .8`, `stagger .08`, `delay .4` | — |
72
+ | `curved-carousel` | root with `[data-mk-slide]`; optional `.mk-carousel__media`, `.mk-carousel__title` | `friction .85`, `throwBoost 2`, `wheel .2`, `startIndex 0`, `onSelect(i, slide)` (auto: `mk:select` event) | `next()`, `prev()`, `goTo(i)`, `index` |
73
+ | `pile` | `.mk-pile > .mk-pile__stack > .mk-pile__layer > .mk-pile__media`; back layers styled with `pileStyle(pileLayers(n, seed)[i])` | `threshold .25` | — |
74
+ | `words` | any text element | `by 'words'\|'chars'`, `trigger 'scroll'\|'load'\|'manual'`, `duration .8`, `stagger .1` | `reveal()`, `replace(mutate)` |
75
+ | `flow` | container; its children animate | `y 20`, `blur 16`, `duration .5`, `stagger .05`, `style 1\|2`, `trigger` | `play()` |
76
+ | `pop` | any element | `scale .7`, `duration 1.1`, `trigger` | `play()` |
77
+ | `fit-text` | one-line element inside a parent | `safety .995` | `refit()` |
78
+ | `stretch-footer` | `footer[data-mk="stretch-footer"]` containing `[data-mk-footer-title]`, plus a `[data-mk-phantom]` spacer after `<main>` | `lambda .9` | — |
79
+ | `radial-menu` | root with `[data-mk-toggle]` and `[data-mk-item]` direct children | `radius '3.6rem'`, `startAngle 1.5708`, `spacing .9` | `open()`, `close()`, `toggle()`, `isOpen` |
80
+ | `pill` | `.mk-pill` button, `.mk-stretch` link | CSS only | — |
81
+ | `mask-text` | any text | `by 'lines'|'words'|'chars'`, `y 100`, `duration 1.2`, `delay .1`, `stagger .02`, `trigger 'scroll'|'load'|'manual'`, `once` | `play()`, `hide()`, `split` |
82
+ | `cascade` | block with `[data-mk-cascade="rule|fade|rise"]` children (`rise` inside a clip) | `delay` (`data-mk-delay`), `step .03`, `duration 1.2`, `once`, `trigger` | `play()`, `reset()` |
83
+ | `text-roll` | a title | `trigger 'load'|'scroll'|'hover'|'manual'`, `delay`, `interval`, `duration 2`, `stagger .04` | `play()` |
84
+ | `letters` | a word | `effect 'scale'|'blink'`, `colors`, `autoplay` | `play()` |
85
+ | `parallax` | clipped frame with a picture (`[data-mk-parallax-target]`) | `amount 1`, `lerp 1`, `scroll` | `seek(p)` |
86
+ | `depth-grid` | grid container | `start 'top center'`, `end 'bottom bottom'`, `lerp 1`, `scroll` | `seek(p)` |
87
+ | `frame-zoom` | frame with one picture/video child | `start 'top bottom'`, `end 'bottom top'`, `lerp 1` | `seek(p)`, `open(on)` |
88
+ | `contrast` | header; sections with `data-mk-tone="light|dark"` | `sections`, `attribute 'data-mk-on'` | `tone` |
89
+ | `marquee` | strip whose first child is the group | `axis 'x'|'y'`, `speed 2` (s per item), `reverse` | `refresh()` |
90
+ | `card-dock` | `.mk-card-dock` with `.mk-card-dock__item` (> `a.mk-card-dock__card` + `.mk-card-dock__title`), optional `.mk-card-dock__cta`; `mk-card-dock--fixed` | `intro 1600` (ms spread on load, 0 skips) | `expand()`, `collapse()`, `expanded` |
91
+ | `snap-strip` | `.mk-snap-strip` with the cards as children | `autoplay`, `interval 5000`, `resume 2000`, `delay 500` (`data-mk-delay`) | `next()`, `previous()`, `index` |
92
+ | `fill-text` | any text | `from .2`, `start 'top 90%'`, `end 'center 40%'`, `scroll` | `seek(p)`, `progress` |
93
+ | `sortable` | root with `[data-mk-item][data-mk-id]` (optional `[data-mk-sortable-group="key"]` lists, `data-mk-sortable-locked`, `[data-mk-sort-handle]`) | `mutate true` (`false` in `useSortable`), `handle`, `autoScroll true`, `follow {1100, 38}`, `onReorder(detail)` (event `mk:reorder`, cancelable) | `cancel()`, `dragging` |
94
+
95
+ React hooks: `useCardDock`, `useSnapStrip`, `useFillText`, `useParticleHero`, `useMaskText`, `useCascade`, `useTextRoll`, `useLetters`, `useParallax`, `useDepthGrid`, `useFrameZoom`, `useContrast`, `useMarquee`, `useSortable`, `useTabs`, `useCounter`, `useDetails`, `useReveal`, `useReflow`, `useSlotText`, `useSpringCards`, `useCurvedCarousel`, `usePile`, `useWords`, `useFlow`, `usePop`, `useFitText`, `useStretchFooter`, `useRadialMenu`. Markup and options: each recipe's `README.md` (`src/recipes/<name>/`). Parameters and options: [reference/recipes.md](reference/recipes.md).
96
+
97
+ ## 4b. Effects (v0.5)
98
+
99
+ | Name (`animate` / class `.mk-anim-<name>`) | Motion | Time |
100
+ |---|---|---|
101
+ | `out` / `out-left` / `out-bottom` | up 20 / left 64 / down 24 px, blur 16, fade | 0.5 s |
102
+ | `collapse` | folds height, padding and margin while blurring | 0.25 s |
103
+ | `highlight` | 4 px inner ring in primary-container | 1 s |
104
+ | `shake` / `shake-strong` | ±4 px / ±10 px | 0.5 s / 0.4 s |
105
+ | `fade`, `wipe`, `progress-in` | appear; a surface veil leaves to the right; appear late | 0.3 / 0.7 / 1 s |
106
+
107
+ `await animate(el, name)` replays even if already playing; `await leave(row, 'out-left' | 'collapse', { remove })` animates then removes (or hides). In React lists: `leave(item, 'out', { remove: false })`, then drop it from state. Classes: `.mk-loading` (+`--soft`), `.mk-shimmer`, `.mk-text-gradient` (theme), `.mk-text-rainbow`, `.mk-frost` (+ five `<i>`; `--top`, `--fixed`; `<Frost />`), `.mk-float` / `.mk-float-group`, `.mk-grain` (`--mk-grain`), `.mk-success` + `confetti(button)`. Reduced motion: loops stop, exits jump to the end, confetti does not draw.
108
+
109
+ ## 5. Motion functions (no instance)
110
+
111
+ Use them inside your own components; they all honour reduced motion and return the tween (or `null`).
112
+
113
+ | Function | use |
114
+ |---|---|
115
+ | `flowIn(targets, { style, reverse, target })` / `flowOut(targets)` | lists, steps, settings entering / leaving |
116
+ | `stepChange(container, () => render next)` | wizard / onboarding steps (flow out → swap → flow in) |
117
+ | `flowInMenu`, `flowInPalette`, `flowInForm`, `flowInLanding`, `flowInContent` | menu options, swatches, form cards, landing, articles |
118
+ | `revealFrom(el, { origin, speed })` | a surface uncovered like a wave from the control that opened it |
119
+ | `toggleDetails(details, open)`, `installDetailsMotion(root)` | disclosures |
120
+ | `slideIndicator(indicator, target)` | any sliding selection indicator |
121
+ | `countTo(el, n)`, `revealCounter(el, n)`, `tweenNumber(a, b, cb)` | changing numbers, balances |
122
+ | `captureLayout` + `reflowLayout` | reorder / filter / add in lists |
123
+ | `enterMessages(bubbles)`, `fitComposer(textarea)`, `scrollToLatest(scroller)` | chat / comments |
124
+ | `capturePending(el)` + `morphPendingToReply(state, reply, list)` | a "thinking" placeholder turning into the answer (other frameworks) |
125
+ | `audioEnergy(stream, onEnergy)` | microphone level 0…1 for `chat-background` (never asks for the mic itself) |
126
+ | `channels(start, onChange)` + `spring()` / `eased()` | multi-property spring motion (liquid surfaces) |
127
+ | `trackScroll(el, { start, end, lerp, onUpdate })` | an element's 0→1 progress through the viewport (follows the page; `lerp < 1` smooths it) — build your own scroll effects |
128
+ | `motionSpring(stiffness, damping, mass?)` → `{ ease, duration }`, `springLinear(…)` | Motion/Framer springs as a GSAP ease or a CSS `linear()` |
129
+ | `sheetOpen`, `sheetClose`, `pushUnderlay`, `releaseUnderlay` | a sheet rising from the bottom while the page behind sinks |
130
+ | `morphOpen(surface, opener)`, `morphClose`, `morphCloseWithGhost`, `liquidOpen/Close` | any surface growing out of / folding into a control |
131
+ | `attachDragDismiss(surface, { onDismiss })`, `attachFloating`, `anchorSurface` | drag to dismiss, floating windows, anchored panels |
132
+ | `fadeBackdrop(dialog, 'in')`, `ghostOf`, `clearStyles` | backdrop tint, inert close copies, cleaning without GSAP measuring hidden nodes |
133
+
134
+ Curves: `EASE.signature`, `EASE.flow`, `EASE.fadeAway`, `EASE.blurAway`, `EASE.layoutReflow`…; CSS: `var(--mk-ease-signature)`, `--mk-ease-flow`, `--mk-ease-close`. Times: `TIMING.*`.
135
+
136
+ ## 6. Components
137
+
138
+ | Component | HTML / Astro | React |
139
+ |---|---|---|
140
+ | `dialog` | `<button data-mk-open="crear">` + `<mk-dialog id="crear" heading="…" [description] [variant="liquid|sheet"] [placement="anchor\|side"] [size="sm\|lg\|xl"] [float="key"] [shared="id"] [no-backdrop] [persistent] [no-drag]>` (children → body; `data-mk-part="footer"` stays below) | `<Dialog.Root variant placement float…><Dialog.Trigger/><Dialog.Content size><Dialog.Header><Dialog.Title/><Dialog.Close/></Dialog.Header><Dialog.Body/><Dialog.Footer/></Dialog.Content></Dialog.Root>` |
141
+ | `menu` | `<button data-mk-open="m">` + `<mk-menu id="m" label="…">` with `<button data-mk-item data-mk-value>`; listen `mk:select` | `<Menu.Root onSelect><Menu.Trigger/><Menu.Content><Menu.Item value/><Menu.Separator/></Menu.Content></Menu.Root>` |
142
+ | `toast` | `<mk-toaster>` once + `toast('Copiado', { origin: button, tone, action })` | `<Toaster />` once + `toast(…)` |
143
+ | `theme-picker` | `<mk-theme-picker [show-hidden] [themes="a,b"]>` | `<ThemePicker themes showHidden onChange />` inside `<ThemeProvider>` |
144
+ | `chat` | `<mk-chat [background] [mic] placeholder="…">` (history `[data-mk-message][data-mk-role]` inside); `mk:send` → `el.add('user', t)`, `el.pending()`, `el.reply(answer)` | `<Chat.Root onSend><ChatBackground /><Chat.Messages pending>{<Chat.Message role/>}</Chat.Messages><Chat.Confirm/><Chat.Composer/></Chat.Root>`; set the reply and `pending=false` in the same render |
145
+ | `chat-background` | `<mk-chat-background energy scale>` first inside a positioned box; `el.busy(true)` | `<ChatBackground energy scale />` |
146
+ | `nav` | `<div class="mk-shell"><main class="mk-shell__view">…</main><mk-nav class="mk-nav mk-nav--bonita" nav-style="bonita" [accent] [reveal] [storage-key]><div class="mk-nav__buttons"><a class="mk-nav__button" href aria-current="page">…</a></div></mk-nav></div>`; `mk:navigate` | `<Nav navStyle items active onSelect first actions accent reveal />` inside `.mk-shell` |
147
+ | `cursor` | `<mk-cursor>` once (chip over `[data-mk-cursor="Label"]`, `data-mk-cursor-shape="circle"`, `data-mk-cursor-icon`) · `<mk-cursor variant="dot" text arrows>` inside a zone (`data-mk-cursor-hide` hides it) | `<Cursor />` · `<Cursor variant="dot" text arrows zone />` |
148
+ | `island` | `<mk-island [class="mk-island--contained"] [entrance="false|manual"]>` with `.mk-island__backdrop[data-mk-island-close]`, `.mk-island__shell` (glass, header, `[data-mk-island-toggle="menu"]` buttons, `.mk-island__cta`), `.mk-island__content > div > [data-mk-island-panel="menu"]` with `[data-mk-island-item]` rows and `.mk-island-card`s; `mk:island` | `<Island brand cta panels={[{ name, label, icon, light, content }]} contained onChange />` |
149
+ | `steps` | `<mk-steps class="mk-steps">` + `[data-mk-step]` sections, `[data-mk-flow-item]`, `[data-mk-counter]`, `[data-mk-step-next/prev/goto]`; `el.busy(on)`, `mk:step` | `<Steps index onChange busy><ChatBackground /><Steps.Counter /><Steps.Step>…</Steps.Step></Steps>` |
150
+
151
+ Register elements with `defineElements()` (or a list of tag names). Engines for your own markup: `createDialog(dialogEl, opts)`, `createMenu(dialogEl, opts)`, `createToaster(hostEl)`, `createThemePicker(el)`, `createChat(el)`, `createChatBackground(el)`, `createNav(navEl, { style })`, `createSteps(el)`.
152
+
153
+ Dialog behaviour: the opener turns into the dialog (0.7 s signature, colour handover 0.3 s, content from an 8 px blur) and it folds back on close (0.5 s, content blur 32 + radius 400, via an inert ghost so focus returns at once); without an opener it scales from .92. `data-mk-shared="id"` on a piece of the opener and of the dialog flies that text between them. Auto-resize 0.45 s; drag down > 100 px dismisses (also on phones); `float` lifts it into a movable, resizable window on desktop; full screen with radius 0 under 680 px. Events: `mk:open`, `mk:close` (cancelable), `mk:closed`. `[data-mk-close]` inside closes it. `open(null)` opens with no opener (scale-in).
154
+
155
+ Chat: bubbles grow from their speaker's side (scale .5, blur 24, 0.7 s) while words rise; "Pensando…" only after 500 ms, then it morphs (Flip 0.7 s) into the reply; Enter sends, Shift+Enter breaks the line; the composer grows from 64 px (0.18 s). Nav styles: `clasica`, `bonita`, `dock`, `guapa`, `tasks`, `melon`, `tunnel` (`NAV_STYLES`), one glass bar under 680 px; `accent` highlights the active page. Steps: flow out → swap → flow in (0.5 s), counter rolls, focus goes to the step's title. Sortable: lift 0.18 s, spring 1100/38, tilt up to 14°, neighbours slide 0.62 s, land 0.42 s; touch drags only from the handle; Space/arrows/Escape on the keyboard.
156
+
157
+ **Adding a component** (in the motion-kit repo): `pnpm new component <name>` scaffolds `src/components/<name>/{engine.ts, <name>.css, element.ts, react.tsx, README.md}`, a test, playground demos, the registry entry and the exports. Fill the engine (works on existing markup, defaults from `TIMING`, reduced motion, `destroy()` restores the DOM), then `pnpm test`. See [reference/components.md](reference/components.md).
158
+
159
+ ## 7. Rules
160
+
161
+ - Call `destroy()` when the element leaves (hooks, components and `autoInit()`'s returned function do it). Never leave instances running on removed nodes.
162
+ - Respect reduced motion: everything here already shows the final state under `prefers-reduced-motion: reduce`; custom motion must check `reduced()` too.
163
+ - Flip contract: measure before the DOM change, animate after it (`capture()`/`play()`, `<FlipGroup signature>`).
164
+ - Text that changes: `words.replace(() => …)`, `fit.refit()`, `slot.set(text)` — don't re-create.
165
+ - Elements inside a closed `<dialog>`: create `words` with `trigger: 'manual'` and call `reveal()` after `showModal()`.
166
+ - `stretch-footer` needs the page above it: `main { position: relative; z-index: 1; background: var(--mk-bg) }`.
167
+ - Theme: style with `--mk-*` tokens and controls; switch with `setTheme`/`setMode` (or `useTheme()`), never by swapping stylesheets.
168
+ - Override look with CSS variables (`--mk-button-bg`, `--mk-field-radius`, `--mk-swatch-size`, `--mk-tabs-indicator`, `--mk-shadow`…), not by editing package code.
169
+
170
+ ## 8. Choosing
171
+
172
+ - New app with look → `/css` + `/css/themes` + `/css/fonts`, `.mk-app`, controls, `theme-picker` in settings.
173
+ - Segmented filters / view switch → `.mk-segmented` + `tabs`. Totals → `counter` / `<AnimatedNumber>`.
174
+ - Lists that reorder → `reflow` / `<FlipGroup>`; lists the user reorders by dragging (tasks, kanban) → `sortable`; lists that appear → `flow` (or `flowIn`).
175
+ - Editorial headlines → `mask-text` (lines; `y 120` for film-title feel); credits / spec lists → `cascade`; a hero title that keeps alive → `text-roll` with `interval`; a playful word → `letters`.
176
+ - Photos in long pages → `parallax`; a gallery section → `depth-grid`; a video section → `frame-zoom`; header over dark/light sections → `contrast`; logos / festivals → `marquee` (+ `.mk-fade-x`).
177
+ - Site header for a product / landing → `island` (menu + cart share it); a product switcher pinned to a corner → `card-dock` (`--fixed`); phone carousels → `snap-strip`; a manifesto paragraph → `fill-text`; a hero with a living background → `particle-hero` from `/gl` (keep the text in HTML).
178
+ - Portfolio cards → `cursor` chip (`data-mk-cursor="View project"`); a draggable reel or full-bleed hero → `cursor` dot.
179
+ - Glass actions or tags over photos / colour → `.mk-glass` (+ `.mk-float-group` for floating tags); bottom fade over scrolling content → `.mk-frost`; a cinematic hero → `.mk-grain`.
180
+ - Removing rows → `leave(row, 'out-left' | 'collapse')`; form error → `aria-invalid="true"` (vibrates) or `animate(el, 'shake')`; success → `.mk-success` + `confetti(button)`; loading cards → `.mk-loading`; AI / premium labels → `.mk-text-gradient`.
181
+ - Project detail over the page → `dialog` with `variant="sheet"` and `data-mk-underlay` on the page; mobile menu toggle → `.mk-burger`; "Explore" CTA → `.mk-button--roll`.
182
+ - App frame → `.mk-shell` + `nav` (let the user pick the style; `storageKey` remembers it). Assistant / support chat → `chat` + `chat-background`. First-run setup → `steps` (with `ChatBackground` and `busy` while saving).
183
+ - FAQ / settings groups → `details`. Date/time popovers → `revealFrom` from the field.
184
+ - Language switch on a title → `slot-text`.
185
+ - Portfolio hero → `spring-cards` + a giant `fit-text words` title; featured projects → `curved-carousel`; archive → `pile`; last screen → `stretch-footer`; corner quick actions → `radial-menu`.
186
+ - Film navigation → `ticket-menu` or `<Menu.Root ticket>` / `<mk-menu ticket>`; preserves menu focus, selection and keyboard handling.
187
+ - Circular archive → `infinite-reel` (wheel, drag, keyboard, axis x/y); interactive card grid → `repel-bento`; testimonials → `rotator` (8 s, pause on focus/hover).
188
+ - Numeric experience/time selector → `drag-ruler` (role slider, 60 px/unit, step .1); a shared active indicator in navigation → `dock-pill` (spring 380/30). Navigation remains the app's responsibility.
189
+ - GPU image transitions → `createLiquidBlend`; metal study → `createMatcap`; film processing → `createShaderGrain` / `createShaderBlur`, all from `/gl`. React hooks `useLiquidBlend`, `useMatcap`, `useShaderGrain`, `useShaderBlur` are in `/react`. Import explicitly: these are not autoInit recipes. Retain accessible HTML images and set dimensions on the host. See [reference/recipes.md](reference/recipes.md) and each recipe README for the parameters and controls.
190
+
191
+ ## Referencias y básicos (próxima versión)
192
+
193
+ - Contact CTA en tres piezas: `createJoinedCta`, `useJoinedCta`, `data-mk="joined-cta"`; tres spans dentro de `.mk-joined-cta`.
194
+ - Media contrazoom: `createImageZoom`, `useImageZoom`, `data-mk="image-zoom"`; `[data-mk-zoom-frame]` con imagen dentro.
195
+ - Fuente alternada/rodillo y overlay propio opcional: `createFontCycle`/`useFontCycle` (`font-cycle`); navegación de categorías con miniaturas: `createThumbnailNav`/`useThumbnailNav` (`thumbnail-nav`).
196
+ - Botones de presión y etiqueta film: `createActionButton`/`useActionButton` (`action-button`); hero editorial de cine: `createFilmHero`/`useFilmHero` (`film-hero`).
197
+ - Hero/tarjetas/contacto: `createEditorialReveal`/`useEditorialReveal` (`editorial-reveal`), compone mask-text/cascade/parallax/contrast.
198
+ - Dock bento: `createStudioDock`, `<StudioDock>`, `<mk-studio-dock>`, `useStudioDock`; páginas, contacto y reel.
199
+ - Video modal nativo: `createTrailer`, `<Trailer>`, `<mk-trailer>`, `useTrailer`; video suministrado por la app; fullscreen se aplica a su stage.
200
+ - Básicos: `createQuantity`/`<Quantity>`/`<mk-quantity>`; `createPagination`/`<Pagination>`/`<mk-pagination>`; `createHoverCard`/`<HoverCard>`/`<mk-hover-card>`; `createInputOtp`/`<InputOtp>`/`<mk-input-otp>`.
201
+ - Quantity limita min/max/step; pagination limita total/page y mantiene foco; hover-card soporta hover/foco/tap/Escape; OTP usa un input nativo con slots decorativos para pegado/autofill/ceros iniciales.
202
+ - En React, props value/page opcionales controlan el estado; al omitirlas los controles conservan edición/selección entre cambios de opciones.
203
+ - Calendario de citas: `createCalendar`/`<Calendar>`/`<mk-calendar>` (`calendar`); eventos `{id,title,start,end?,status?,color?}`, `setEvents`, `mk:select`/`mk:event`/`mk:navigate`; requiere `[data-mk-calendar-grid]`.
204
+ - Reservas/citas: `createSlotPicker`/`<SlotPicker>`/`<mk-slot-picker>` (`slot-picker`); `slots` lista, mapa por día o función/promesa; valor `YYYY-MM-DDTHH:mm` en `[data-mk-slot-value]` y `mk:change`.
205
+ - Titular que adelgaza bajo el puntero: `createWeightHover`/`useWeightHover` (`weight-hover`), fuente variable con eje `wght`.
206
+ - Stickers holográficos que se despegan: `createSticker`/`useSticker` (`sticker`), arte `.mk-sticker__pill`/`__badge`/`__art`, `--mk-sticker-rot`, `aria-hidden` si es decoración.
207
+ - Navegación que aparece con el scroll: `createScrollDock`/`useScrollDock` (`scroll-dock`), `hideWhen` para el footer, `.mk-scroll-dock--sticky` dentro de contenedores con scroll.
208
+ - Hora de otra zona: `createLocalClock`/`useLocalClock` (`local-clock`); píldora de disponibilidad `.mk-status` + `.mk-status__dot` (controles).
209
+ - Cada carpeta src/recipes/<name>/README.md o src/components/<name>/README.md define marcado, valores, eventos y controles. No reconstruir estas APIs por intuición.
@@ -1,3 +1,22 @@
1
+ import { createQuantity } from '../components/quantity/engine';
2
+ import { createPagination } from '../components/pagination/engine';
3
+ import { createHoverCard } from '../components/hover-card/engine';
4
+ import { createInputOtp } from '../components/input-otp/engine';
5
+ import { createJoinedCta } from '../recipes/joined-cta/create-joined-cta';
6
+ import { createImageZoom } from '../recipes/image-zoom/create-image-zoom';
7
+ import { createFontCycle } from '../recipes/font-cycle/create-font-cycle';
8
+ import { createThumbnailNav } from '../recipes/thumbnail-nav/create-thumbnail-nav';
9
+ import { createActionButton } from '../recipes/action-button/create-action-button';
10
+ import { createFilmHero } from '../recipes/film-hero/create-film-hero';
11
+ import { createEditorialReveal } from '../recipes/editorial-reveal/create-editorial-reveal';
12
+ import { createStudioDock } from '../components/studio-dock/engine';
13
+ import { createCalendar } from '../components/calendar/engine';
14
+ import { createSlotPicker } from '../components/slot-picker/engine';
15
+ import { createWeightHover } from '../recipes/weight-hover/create-weight-hover';
16
+ import { createSticker } from '../recipes/sticker/create-sticker';
17
+ import { createScrollDock } from '../recipes/scroll-dock/create-scroll-dock';
18
+ import { createLocalClock } from '../recipes/local-clock/create-local-clock';
19
+ import { createTrailer } from '../components/trailer/engine';
1
20
  import { ScrollTrigger } from '../core/gsap';
2
21
  import { warnOnce, type Instance } from '../core/types';
3
22
  import { createSpringCards } from '../recipes/spring-cards/create-spring-cards';
@@ -39,6 +58,25 @@ import { createDockPill } from '../recipes/dock-pill/create-dock-pill';
39
58
  type Factory = (el: HTMLElement, options: Record<string, unknown>) => Instance;
40
59
 
41
60
  const factories = {
61
+ 'joined-cta': createJoinedCta,
62
+ 'image-zoom': createImageZoom,
63
+ 'font-cycle': createFontCycle,
64
+ 'thumbnail-nav': createThumbnailNav,
65
+ 'action-button': createActionButton,
66
+ 'film-hero': createFilmHero,
67
+ 'editorial-reveal': createEditorialReveal,
68
+ 'studio-dock': createStudioDock,
69
+ 'trailer': createTrailer,
70
+ 'quantity': createQuantity,
71
+ 'pagination': createPagination,
72
+ 'hover-card': createHoverCard,
73
+ 'input-otp': createInputOtp,
74
+ 'calendar': createCalendar,
75
+ 'slot-picker': createSlotPicker,
76
+ 'weight-hover': createWeightHover,
77
+ 'sticker': createSticker,
78
+ 'scroll-dock': createScrollDock,
79
+ 'local-clock': createLocalClock,
42
80
  'spring-cards': createSpringCards,
43
81
  'curved-carousel': (el, options) =>
44
82
  createCurvedCarousel(el, {
@@ -0,0 +1,82 @@
1
+ # calendar
2
+
3
+ El calendario de Melon Mind. Tiene vista de mes y de semana, día de hoy marcado y eventos como chips con estado (pendiente, completado o cancelado), hora y color propio.
4
+
5
+ - **Al cargar:** los días entran desde arriba con blur (y −20 px, blur 16 → 0, 0.5 s), 5 ms uno tras otro, y después los eventos, 20 ms uno tras otro. Es el `flowChilds` del `CalendarComponent` original.
6
+ - **Cambiar de mes o semana:** la rejilla vieja sale 30 px hacia un lado (0.12 s) y, cuando ya casi no se ve, la nueva entra 30 px desde el otro (0.25 s `ease-out`), como los horarios de su selector de citas. Van en secuencia, sin fundido cruzado, para que no se mezclen dos meses llenos de eventos. Luego vuelven a entrar los eventos.
7
+ - **Hover:** el fondo del día cambia en 0.3 s y el número tiene resorte de radio y escala (`cubic-bezier(.38,.49,0,2)`).
8
+
9
+ Origen: patrón observado en la app de Melon Mind.
10
+
11
+ ## HTML / Astro
12
+
13
+ ```html
14
+ <mk-calendar class="mk-calendar" locale="es" first-day="0"
15
+ events='[{"id":1,"title":"Ana","start":"2026-10-23T10:00","status":"pending"}]'>
16
+ <div class="mk-calendar__header">
17
+ <h3 class="mk-calendar__title" data-mk-calendar-title>Calendario</h3>
18
+ <div class="mk-calendar__views" role="group" aria-label="Vista">
19
+ <button type="button" data-mk-calendar-view="month">Mes</button>
20
+ <button type="button" data-mk-calendar-view="week">Semana</button>
21
+ </div>
22
+ <button type="button" class="mk-calendar__nav" data-mk-calendar-prev aria-label="Anterior">←</button>
23
+ <button type="button" class="mk-calendar__nav" data-mk-calendar-today>Hoy</button>
24
+ <button type="button" class="mk-calendar__nav" data-mk-calendar-next aria-label="Siguiente">→</button>
25
+ </div>
26
+ <div data-mk-calendar-grid></div>
27
+ </mk-calendar>
28
+ <script>
29
+ import { defineElements } from '@yewdiel21/motion-kit/elements';
30
+ defineElements(['mk-calendar']);
31
+ </script>
32
+ ```
33
+
34
+ Sólo `[data-mk-calendar-grid]` es obligatorio. Las demás piezas del header son opcionales. Si cambias el atributo `events`, los eventos se actualizan sin perder el mes ni el foco. Sin custom element: `createCalendar(root, opciones)`.
35
+
36
+ ## React
37
+
38
+ ```tsx
39
+ <Calendar
40
+ events={citas}
41
+ onSelect={(day) => abrirDia(day)}
42
+ onEventClick={(event) => abrirCita(event.id)}
43
+ />
44
+ ```
45
+
46
+ `events` y `selected` se actualizan en el sitio. `date` y `view` son iniciales: el usuario navega libremente.
47
+
48
+ ## Opciones
49
+
50
+ | Opción | Atributo | Por defecto | Qué hace |
51
+ |---|---|---|---|
52
+ | `date` | `date` | hoy | Mes o semana inicial (`Date` o `"2026-10-23"`) |
53
+ | `view` | `view` | `'month'` | `'month'` o `'week'` |
54
+ | `firstDay` | `first-day` | `0` | Primer día de la semana (0 = domingo) |
55
+ | `locale` | `locale` | `'es'` | Nombres de mes y día, formato de hora |
56
+ | `events` | `events` (JSON) | `[]` | `{ id, title, start, end?, allDay?, status?, color? }` |
57
+ | `maxEvents` | `max-events` | `3` | Chips por día en la vista de mes antes de «+N más» |
58
+ | `selected` | `selected` | — | Día seleccionado |
59
+ | `today` | `today` | — | Fija «hoy» (demos, pruebas) |
60
+ | `labels` | — | español | `more(n)`, `events(n)`, `status` |
61
+ | `onSelect(date)`, `onEventClick(event)`, `onNavigate({start, end, view})` | — | — | Callbacks |
62
+
63
+ API: `next()`, `prev()`, `goToday()`, `setDate(d)`, `setView(v)`, `setEvents(list)`, `select(d | null, notify)`, `date`, `view`, `selected`, `range`, `destroy()`. Eventos DOM: `mk:select` `{date, key}`, `mk:event` `{event}` y `mk:navigate` `{start, end, view}`.
64
+
65
+ Un evento con `end` en otro día aparece en cada día que cubre, hasta dos meses.
66
+
67
+ ## Variables CSS
68
+
69
+ `--mk-calendar-gap`, `--mk-calendar-radius`, `--mk-calendar-day-height` y `--mk-calendar-event-color` (por evento, con `color`).
70
+
71
+ ## Accesibilidad
72
+
73
+ Rejilla ARIA (`grid` / `row` / `columnheader` / `gridcell`) con un solo botón de día en el orden de tabulación:
74
+
75
+ | Tecla | Acción |
76
+ |---|---|
77
+ | Flechas | Día anterior/siguiente, misma semana anterior/siguiente |
78
+ | Inicio / Fin | Principio/fin de semana |
79
+ | RePág / AvPág | Mes anterior/siguiente (con Mayúsculas, año) |
80
+ | Enter / Espacio | Seleccionar el día |
81
+
82
+ Cada día anuncia la fecha completa y cuántos eventos tiene. Hoy lleva `aria-current="date"`. Los chips son botones con título, hora y estado. En móvil (< 680 px) los chips del mes se reducen a puntos de color. Con movimiento reducido no hay entradas, deslizamiento ni transiciones.