@marver-design/marver 0.19.2 → 0.21.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 (39) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/README.md +8 -10
  3. package/dist/{bake-kaf5kGZ7.mjs → bake-BID6mo-N.mjs} +1 -1
  4. package/dist/{boards-BmxcT3Lc.mjs → boards-BwiDAmPf.mjs} +95 -48
  5. package/dist/{boards-PuVzw5Wp.mjs → boards-DnLewfj8.mjs} +20 -11
  6. package/dist/{build-7ed5H2vT.mjs → build-C7MqQ7hq.mjs} +18 -8
  7. package/dist/cli.mjs +19 -13
  8. package/dist/{daemon-DbHvLQUL.mjs → daemon-CRZFpl6K.mjs} +1 -1
  9. package/dist/{dev-D3mP2x27.mjs → dev-BNZF4Mup.mjs} +5 -5
  10. package/dist/{init-BQYCS3EU.mjs → init-C34BY3R4.mjs} +34 -33
  11. package/dist/{manifest-B01PSyDc.mjs → manifest-mMfUhPtL.mjs} +8 -5
  12. package/dist/{plugin-DI-7NAnx.mjs → plugin-omHLCn91.mjs} +34 -26
  13. package/dist/{poster-DNh6N27C.mjs → poster-BvxiAzy1.mjs} +1 -1
  14. package/dist/{publish-bakes-Dp-ZFk3d.mjs → publish-bakes-BqzAAa3w.mjs} +8 -2
  15. package/dist/{shot-DMDvDbeP.mjs → shot-DswS4iRK.mjs} +7 -7
  16. package/docs/live-jam.md +1 -1
  17. package/docs/slides.md +89 -89
  18. package/docs/sticky-notes.md +9 -0
  19. package/package.json +1 -1
  20. package/src/client/const.ts +27 -9
  21. package/src/client/content/chart.tsx +8 -8
  22. package/src/client/content/index.tsx +5 -4
  23. package/src/client/content/slide.tsx +26 -201
  24. package/src/client/frame-host/main.tsx +10 -0
  25. package/src/client/shell/BoardList.tsx +111 -52
  26. package/src/client/shell/Comments.tsx +12 -4
  27. package/src/client/shell/Play.tsx +26 -14
  28. package/src/client/shell/Toolbar.tsx +7 -5
  29. package/src/client/shell/canvas/FrameNode.tsx +16 -2
  30. package/src/client/shell/store.ts +8 -8
  31. package/src/client/shell/styles.css +10 -9
  32. package/src/client/stage/main.tsx +56 -7
  33. package/src/shared/board-tree.ts +271 -140
  34. package/templates/AGENTS-embedded.md +3 -3
  35. package/templates/AGENTS-studio.md +3 -3
  36. package/templates/instructions/boards.md +47 -22
  37. package/templates/instructions/reference/deck-layouts.md +153 -199
  38. package/templates/instructions/reference/deck-story.md +6 -6
  39. package/templates/instructions/slides.md +275 -383
@@ -14,6 +14,7 @@ import { Component, createElement, useEffect, useRef, useState, type ComponentTy
14
14
  import { flushSync } from 'react-dom'
15
15
  import { createRoot } from 'react-dom/client'
16
16
  import { frameFile, frames, layoutChain, layouts, providers } from '../frame-host/registry.ts'
17
+ import { SLIDE_DOC_CSS } from '../const.ts'
17
18
  import { createInspect } from '../frame-host/inspect.js'
18
19
 
19
20
  const params = new URLSearchParams(location.search)
@@ -21,13 +22,54 @@ const params = new URLSearchParams(location.search)
21
22
  // token systems, .dark for class-keyed ones (Tailwind/shadcn). Missing the class made
22
23
  // play render class-keyed apps light while the canvas showed them dark.
23
24
  const bootTheme = params.get('theme') ?? 'light'
24
- // slides mode (v1.5): the shell says so in the URL; the stage stamps the ONE
25
- // attribute the content primitives observe (data-sl-play lifts the rest-state
26
- // motion reset; data-sl-entered arms the entrance presets after each swap
27
- // settles). `tr=none` and prefers-reduced-motion both skip view transitions.
25
+ // slides mode: the shell says so in the URL; the stage stamps the attributes every slide
26
+ // can key its own motion off - data-sl-play (the deck is playing) on <html> for the whole
27
+ // show, data-sl-entered once each swap settles, data-mv-slide while the mounted frame is a
28
+ // `slide: true` frame. `tr=none` and prefers-reduced-motion both skip view transitions.
28
29
  const slidesMode = params.get('slides') === '1'
29
30
  const deckTransition = params.get('tr') ?? 'fade'
30
31
  if (slidesMode) document.documentElement.setAttribute('data-sl-play', '')
32
+
33
+ /** The deck's playback CSS - player behaviour, so it lives with the player and serves every
34
+ * slide, whatever its markup: one tempo (--marver-slide-tempo) for the morphs between slides
35
+ * and the optional entrance presets (data-animate + data-animate-delay), which stay inert
36
+ * until the slide has arrived. Anything beyond these is the author's own CSS or JS. */
37
+ const DECK_CSS = `
38
+ ::view-transition-group(*), ::view-transition-old(root), ::view-transition-new(root) {
39
+ animation-duration: var(--marver-slide-tempo, 350ms);
40
+ }
41
+ [data-sl-play] [data-animate] { opacity: 0 }
42
+ [data-sl-play][data-sl-entered] [data-animate] {
43
+ opacity: 1; animation-duration: var(--marver-slide-tempo, 350ms); animation-timing-function: cubic-bezier(.2, .7, .2, 1);
44
+ animation-fill-mode: both;
45
+ }
46
+ [data-sl-play][data-sl-entered] [data-animate="fade-up"] { animation-name: sl-fade-up }
47
+ [data-sl-play][data-sl-entered] [data-animate="fade"] { animation-name: sl-fade }
48
+ [data-sl-play][data-sl-entered] [data-animate="scale-in"] { animation-name: sl-scale-in }
49
+ [data-sl-play][data-sl-entered] [data-animate-delay="1"] { animation-delay: 80ms }
50
+ [data-sl-play][data-sl-entered] [data-animate-delay="2"] { animation-delay: 160ms }
51
+ [data-sl-play][data-sl-entered] [data-animate-delay="3"] { animation-delay: 240ms }
52
+ @keyframes sl-fade-up { from { opacity: 0; transform: translateY(18px) } to { opacity: 1; transform: none } }
53
+ @keyframes sl-fade { from { opacity: 0 } to { opacity: 1 } }
54
+ @keyframes sl-scale-in { from { opacity: 0; transform: scale(.94) } to { opacity: 1; transform: none } }
55
+ @media (prefers-reduced-motion: reduce) {
56
+ [data-sl-play] [data-animate] { opacity: 1; animation: none !important }
57
+ }
58
+ `
59
+ if (slidesMode) {
60
+ const el = document.createElement('style')
61
+ el.setAttribute('data-mv-deck', '')
62
+ el.textContent = DECK_CSS
63
+ document.head.appendChild(el)
64
+ }
65
+ // the slide document baseline, keyed on data-mv-slide (set per mounted frame): any mode can
66
+ // show a slide, and its geometry must not depend on what the stage showed before
67
+ {
68
+ const base = document.createElement('style')
69
+ base.setAttribute('data-mv-slide-doc', '')
70
+ base.textContent = SLIDE_DOC_CSS
71
+ document.head.appendChild(base)
72
+ }
31
73
  const reducedMotion = typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches
32
74
  document.documentElement.dataset.theme = bootTheme
33
75
  document.documentElement.classList.toggle('dark', bootTheme === 'dark')
@@ -45,7 +87,7 @@ document.addEventListener('gesturestart', (e) => e.preventDefault())
45
87
  // B0.2: a nested scroll container hitting its boundary must not chain into the shell page
46
88
  document.documentElement.style.overscrollBehavior = 'contain'
47
89
 
48
- interface Mounted { id: string; Frame: ComponentType; wrappers: ComponentType[] }
90
+ interface Mounted { id: string; Frame: ComponentType; wrappers: ComponentType[]; slide: boolean }
49
91
 
50
92
  /** Resolve a frame id to its component + wrapper chain. Modules are import()-cached by
51
93
  * Vite, so re-resolving a chain yields the SAME component references - React keeps
@@ -59,7 +101,7 @@ async function resolve(id: string): Promise<Mounted> {
59
101
  const providerKey = Object.keys(providers)[0]
60
102
  if (providerKey) wrappers.push((await providers[providerKey]() as any).default)
61
103
  for (const lk of layoutChain(fileKey)) wrappers.push((await layouts[lk]() as any).default)
62
- return { id, Frame: mod.default, wrappers: wrappers.filter((w) => w != null) }
104
+ return { id, Frame: mod.default, wrappers: wrappers.filter((w) => w != null), slide: mod.meta?.slide === true }
63
105
  }
64
106
 
65
107
  class Boundary extends Component<{ resetKey: string; children?: ReactNode }, { err: Error | null }> {
@@ -119,7 +161,14 @@ function Stage() {
119
161
  for (const el of document.querySelectorAll('[data-animate]'))
120
162
  if (getComputedStyle(el).viewTransitionName !== 'none') el.removeAttribute('data-animate')
121
163
  }
122
- const apply = () => { if (seq === swapSeq.current) { flushSync(() => { setErr(null); setMounted(next) }); current.current = id; disarm() } }
164
+ const apply = () => {
165
+ if (seq !== swapSeq.current) return
166
+ // the slide mark first: content primitives read it while they render
167
+ document.documentElement.toggleAttribute('data-mv-slide', next.slide)
168
+ flushSync(() => { setErr(null); setMounted(next) })
169
+ current.current = id
170
+ disarm()
171
+ }
123
172
  const entered = () => {
124
173
  if (!slidesMode || seq !== swapSeq.current) return
125
174
  document.documentElement.setAttribute('data-sl-entered', '')