@marver-design/marver 0.19.2 → 0.20.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/docs/slides.md CHANGED
@@ -3,67 +3,63 @@
3
3
  A slide is an ordinary frame with `slide: true`:
4
4
 
5
5
  ```tsx
6
- import { Slide } from '@marver-design/marver/content'
7
- export const meta = { title: 'Cover', slide: true }
6
+ export const meta = { title: 'Onboarding time halved', slide: true }
8
7
  export default () => (
9
- <Slide>
10
- <h1 className="sl-assertion">Churn halved after onboarding v2</h1>
11
- </Slide>
8
+ <main className="deck deck-ink">
9
+ <h1>Onboarding time halved in one quarter.</h1>
10
+ </main>
12
11
  )
13
12
  ```
14
13
 
15
- It renders 1280×720 on the canvas, wears the slide badge, and everything
16
- you know - comments, lasers, variants, promotion, Live Jam - keeps working.
17
- **The stage fits every screen**: you author at exactly 1280×720, and the
18
- slide scales and centers itself to whatever viewport plays it - fill window,
19
- a laptop, a viewer's phone - author px, Tailwind classes, and charts all
20
- scale together, so the composition you approved is the composition everyone
21
- sees. One scene = one deck; numbered files
22
- (`01-cover.tsx`) are the authoring order; **the board's reading order is the
23
- played order** - drag slides around the canvas to reorder the deck.
24
-
25
- ## Why it stays light for the agent
26
-
27
- There is no slide component library to learn. `Slide` is the ONE primitive:
28
- it owns the 1280×720 stage, the asymmetric margins, six fixed type roles
29
- (`sl-display` 160 · `sl-stat` 88 · `sl-assertion` 56 · `sl-support` 30 ·
30
- `sl-body` 24 · `sl-caption` 18), your theme's tokens, and the motion
31
- contract. Everything inside it is your project's own markup, classes, and
32
- components - the same ones the app ships - so a slide is built the way a
33
- screen is built, and an approved slide can be promoted like one.
34
-
35
- Looking good at every size costs the agent nothing extra: the fit is pure
36
- CSS on the root (a resized canvas node, a phone, a projector all get the
37
- same composition, scaled), so the doctrine forbids `vw`/`vh` and media
38
- queries inside a slide and asks for flex/grid in the stage's own
39
- proportions. A dev-only overflow marker outlines a slide whose content escapes the
40
- stage, or whose flex/grid child outgrows its parent - the agent sees the
41
- defect on the canvas, and the rule is always "cut or split, never shrink the type".
42
-
43
- The craft lives in prose, not code. `marver init` ships
44
- `design/instructions/slides.md` - the doctrine: assertion-first argument,
45
- the type roles, **the space IS the design** (three bands, the 85% rule, one
46
- px spacing scale), **seven silhouettes chosen before any recipe** (statement
47
- / hero / split / grid / stream / field / bookend) with a storyboard step
48
- and pacing rules so a deck never reads as one repeated shape, 19 core
49
- recipes with budgets and morph anchors, the choreography rules, and a
50
- review gate that squints the contact sheet. Two depth references sit
51
- beside it: `instructions/reference/deck-story.md` (intake, answer-first
52
- structure, the evidence check, audience calibration, the words) and
53
- `instructions/reference/deck-layouts.md` (the full layout atlas by job, the
54
- grid, content budgets, rebuilding an existing deck, chart craft). Your own
55
- **deck look** (tokens, type, the mark, colour meaning, numbers, voice - a
56
- fill-in template the agent drafts on the first deck), layouts, and house
57
- rules live in `design/slides.md`, which overrides the doctrine and which
58
- marver never overwrites.
14
+ That is the whole contract. Everything inside is your code - your
15
+ project's components and classes, any layout, any typeface, any image,
16
+ any SVG drawing, any CSS or JS animation. marver adds what a deck needs
17
+ around it: a stage, a player, and a place on the board.
18
+
19
+ - **The stage.** A slide renders at its stage size: 1280×720 by default,
20
+ or the `viewport` it declares (`viewport: 'laptop'` gives a 16:10 deck at
21
+ 1280×800). On the canvas it is a frame like any other - comments, laser,
22
+ variants, Live Jam - with a slide badge.
23
+ - **The deck.** One scene is one deck; numbered files (`01-cover.tsx`) are
24
+ the authoring order, and **the board's reading order is the played
25
+ order** - drag slides around the canvas to reorder the deck.
26
+ - **The host scales, not the slide.** A slide never reflows. Slides mode
27
+ renders each slide at its stage and scales the whole stage to the screen -
28
+ up on a projector, down on a laptop or a phone - and a canvas node resized
29
+ smaller shows the same stage, scaled, like a thumbnail. The composition you
30
+ approved is the one everyone sees, without breakpoints.
31
+ - **Notes.** `<slide>.note.md` beside the frame is its
32
+ [sticky note](sticky-notes.md): the aim, the talk track, the visual
33
+ intent, the sources. Notes ship with a published canvas, so keep anything
34
+ the audience must not read out of them.
35
+
36
+ ## The guidance the agent reads
37
+
38
+ `marver init` ships `design/instructions/slides.md` - not a rulebook, a
39
+ guide: how a slide plays and animates, the **deck kit** a strong deck is
40
+ built on (a master shell, whole-slide tones, the brand's type, hairlines
41
+ and labels, a drawing helper), the craft that makes a deck look made
42
+ rather than typed (the brand's own voice, one idea per slide, structure
43
+ with space instead of boxes, real images used big, a drawing system of its
44
+ own, pacing, honest numbers), the method (the answer first, a slide list
45
+ that tells the argument, a `_brief.md`, notes per slide), and a review that
46
+ squints at the contact sheet. Two references sit beside it:
47
+ `instructions/reference/deck-story.md` (intake, answer-first structure, the
48
+ evidence check, the words) and `instructions/reference/deck-layouts.md` (an
49
+ idea bank of compositions, rebuilding an existing deck, chart craft).
50
+
51
+ Your own **deck look** - master, tones, type, mark, imagery, drawing style,
52
+ colour meaning, voice - lives in `design/slides.md`, which the agent drafts
53
+ from your brand on the first deck, which overrides the shipped guide, and
54
+ which marver never overwrites.
59
55
 
60
56
  ## Playing and publishing a deck
61
57
 
62
58
  Press `p` on a board whose publish row says slides and you get slides mode:
63
- the 16:9 stage with the standard prototype toolbars (with `chrome: full`,
64
- the default) - arrows / Space / click to advance, `d` cycles the theme,
65
- devices including a 1280×720 Slide preset and fill window.
66
- Publish it with:
59
+ the stage scaled to the window, the standard prototype toolbars (with
60
+ `chrome: full`, the default), arrows / Space / click to advance, `d` cycles
61
+ the theme, and two views of the stage: Slide (fit to the window, room for
62
+ the chrome) and Fill window (edge to edge). Publish it with:
67
63
 
68
64
  ```json
69
65
  { "boards": { "pitch": { "max": "comment", "type": "slides",
@@ -72,8 +68,8 @@ Publish it with:
72
68
 
73
69
  - `transition`: `fade` (default) or `none`.
74
70
  - `chrome`: `full` (default - the standard prototype chrome: the top-right
75
- toolbar with comment, laser, theme, and devices including fill, plus the
76
- bottom-left walker; a locked deck-only share also carries the brand pill),
71
+ toolbar with comment, laser, theme, and devices, plus the bottom-left
72
+ walker; a locked deck-only share also carries the brand pill),
77
73
  `minimal` (a slim progress strip, comments, the canvas door when the
78
74
  board is not locked, and a pending-update control), or `none` (bare
79
75
  stage).
@@ -83,35 +79,44 @@ Publish it with:
83
79
 
84
80
  Viewers land straight in the deck; the URL survives refresh and back.
85
81
 
86
- ## Motion - the diff is the animation
82
+ ## Motion
87
83
 
88
- A resting slide is STILL - that is a contract, not a hope: charts render
89
- final-state SVG, videos are posters (no `<video>` element exists), and the
90
- `Slide` root suspends every CSS animation and transition under it at rest.
91
- (Your own `<canvas>`, `<video>`, or JS-driven motion is outside the contract
92
- and stays live, as in any frame.) Motion happens in slides
93
- mode, one-shot:
84
+ Motion is yours to write. While a deck plays, the stage puts
85
+ `data-sl-play` on `<html>` for the whole show, and `data-sl-entered` once
86
+ each slide has arrived (removed at each swap, set again when the new slide
87
+ settles); `useSlidePlay()` from `/content` is the playing flag in React. Key
88
+ your animations off them, so the canvas, `marver shot` and thumbnails show
89
+ the finished slide:
90
+
91
+ ```css
92
+ :root[data-sl-entered] .route { animation: draw 900ms ease-out both }
93
+ @keyframes draw { from { stroke-dashoffset: 600 } to { stroke-dashoffset: 0 } }
94
+ ```
95
+
96
+ On the canvas, a resting frame's animations are paused anyway (see sleep in
97
+ the README). marver adds three things on top:
94
98
 
95
99
  - **Morphs**: give the same `view-transition-name` to an element on two
96
- adjacent slides and it travels/grows between them. This is the house move.
97
- - **Build steps**: progressive disclosure is sibling frames (`03a-`, `03b-`)
98
- sharing morph names - every step visible and commentable on the board.
99
- - **Entrances**: `data-animate="fade-up | fade | scale-in"` +
100
- `data-animate-delay="0-3"`, run once after the transition settles. Never
101
- on an element that carries a morph name.
100
+ adjacent slides and it travels or resizes between them.
101
+ - **Build steps**: progressive disclosure is sibling frames (`03a-`,
102
+ `03b-`) sharing morph names - every step visible and commentable on the
103
+ board.
104
+ - **Entrance shortcuts**: `data-animate="fade-up | fade | scale-in"` +
105
+ `data-animate-delay="1-3"`, run once after a slide arrives. Never on an
106
+ element that carries a morph name.
102
107
 
103
- `prefers-reduced-motion` flattens marver's own motion - the morphs between
104
- slides and the entrance presets.
108
+ `--marver-slide-tempo` in your theme (default 350ms) times the morphs and
109
+ the shortcuts. `prefers-reduced-motion` flattens both.
105
110
 
106
111
  ## Charts and video
107
112
 
108
113
  - `<Chart option={...} h={420} />` - an Apache ECharts option, on a
109
114
  fixed supported surface: series bar, line, pie, scatter, radar, gauge, heatmap, funnel, treemap, sunburst, sankey, boxplot; components grid, polar, radar, tooltip, legend, title, dataset (+ transform), markLine, markPoint, markArea, visualMap, dataZoom. Anything outside
110
115
  it is dropped by ECharts without an error, so stay inside. marver
111
- supplies the house theme (colours, type, tooltip) from your
112
- `design/theme.css` tokens, strips animation at rest, and lets any
113
- styling you pass override the theme - so pass data and structure only.
114
- SVG-rendered, in a lazy chunk chart-free canvases never download.
116
+ supplies the house theme from the slide's own ink and typeface (and
117
+ `--marver-slide-accent`), sizes labels for a stage, keeps the chart
118
+ still at rest and plays its entrance in slides mode. SVG-rendered, in a
119
+ lazy chunk chart-free canvases never download.
115
120
  - `<Video src="intro.mp4" poster="intro.jpg" />` - the poster is the frame
116
121
  at rest. Omit it on a local clip and marver renders one from the clip's own
117
122
  first moments (`intro.mp4.poster.png` beside it - the dev server on first
@@ -122,19 +127,14 @@ slides and the entrance presets.
122
127
  `ratio="9 / 16"` for a vertical clip; `autoplay` for a muted ambient loop
123
128
  (that frame then stays live on the canvas). Remote https direct files work
124
129
  too.
130
+ - Images: `Img` for a framed asset, or a plain `<img>` when the slide needs
131
+ full control. Slides play scaled up, so use files at least 2x the size
132
+ they show.
133
+
134
+ ## Decks built before 0.20
125
135
 
126
- ## Theme tokens
127
-
128
- The `Slide` root reads `--marver-slide-ground / -ink / -muted` (each with a
129
- `-dark` variant), `--marver-slide-accent` (one value, both themes),
130
- `--marver-slide-font`, and `--marver-slide-tempo` (one duration that times
131
- both the entrances and the morphs between slides) from your theme and falls
132
- back to the house palette. The stage is 1280×720 (`SLIDE_W` / `SLIDE_H`, exported from `/content`)
133
- with asymmetric margins - 88px sides, 44px top and bottom, overridable in
134
- px via `--marver-slide-pad-x` / `--marver-slide-pad-y` - leaving a 1104×632
135
- content box. Morphs between slides are progressive enhancement: where
136
- `document.startViewTransition` is missing, slides crossfade at the tempo.
137
- Type roles, fixed: `sl-display` (160px, the one
138
- oversize - a hero number, a section numeral), `sl-stat` (88px, a row of
139
- figures), `sl-assertion` (56px),
140
- `sl-support` (30px), `sl-body` (24px), `sl-caption` (18px).
136
+ Earlier decks wrap each slide in `<Slide>` and use its `sl-*` type classes.
137
+ `<Slide>` still exists, as an optional wrapper that fills the frame - but it
138
+ no longer pads the stage, centres content, sizes type or freezes animation,
139
+ and the `sl-*` classes carry no styles. Such a deck still plays; give it its
140
+ own styles (a deck kit, as the guide describes) to restore the look.
@@ -44,6 +44,15 @@ own theme.
44
44
  - Comments: in comment mode (`C`) click any element of a note; the pin sits on the note, follows a
45
45
  fold onto the tab, and the thread card opens beside the column.
46
46
 
47
+ ## On a slide
48
+
49
+ Beside a slide (`slide: true`), the note is the presenter's script and the next agent's memory,
50
+ and the [slides guide](slides.md) teaches agents to write it in four short parts - **Aim** (what the
51
+ slide must do in the argument), **Say** (the talk track), **Visual** (what the image or drawing
52
+ carries) and **Source context** (where the facts come from, and their limits) - so the slide itself
53
+ can stay sparse. A note ships with a published canvas like any other: keep anything the audience
54
+ must not read out of it.
55
+
47
56
  ## What it is not
48
57
 
49
58
  A note is an aside, a screen's worth of reading at most. Specs, flows and mood boards stay content
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marver-design/marver",
3
- "version": "0.19.2",
3
+ "version": "0.20.0",
4
4
  "description": "The agent-native design canvas. A design/ folder, one command, a canvas of live frames built from your repo's real components - comment @marver and your own coding agent does the work. The tool ships no AI.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -9,15 +9,33 @@ export const ROUTE = '/__mv'
9
9
  * manifest scan (defaultSize for content frames) - one source, no drift. */
10
10
  export const CONTENT_WIDTH: Record<string, number> = { document: 760, wide: 1280 }
11
11
 
12
- /** The slide stage (v1.5): a runtime-reserved intrinsic, deliberately NOT a
13
- * config viewport - no migration for existing projects, no deck device in
14
- * sweeps. Dependency-neutral so server (shot) and shell (store) share it. */
12
+ /** The default slide stage: 16:9 at 1280×720, deliberately NOT a config viewport - no
13
+ * migration for existing projects, no deck device in sweeps. Dependency-neutral so
14
+ * server (shot) and shell (store, play) share it. */
15
15
  export const SLIDE_INTRINSIC = { width: 1280, height: 720 }
16
16
 
17
- /** The one DEFAULT sizing rule for slide frames, shared by canvas and shot:
18
- * `slide: true` sets the intrinsic 1280×720 stage, over any authored viewport.
19
- * Board nodes stay resizable (the Slide root scales into whatever box it is
20
- * given); this governs defaults, shots, and stage coordinates. */
21
- export function slideSize(frame: { slide?: boolean }): { width: number; height: number } | null {
22
- return frame.slide ? SLIDE_INTRINSIC : null
17
+ /** The one sizing rule for slide frames, shared by canvas, shot and slides mode: a
18
+ * `slide: true` frame's stage is its declared viewport when the project defines one
19
+ * (a 16:10 deck authored at `laptop` stays 1280×800), else the 1280×720 default. A
20
+ * slide is an ordinary frame at that size - slides mode scales the whole stage to the
21
+ * screen, so the frame never has to. */
22
+ export function slideSize(
23
+ frame: { slide?: boolean; viewport?: string },
24
+ viewports: Record<string, { width: number; height: number }> = {},
25
+ ): { width: number; height: number } | null {
26
+ if (!frame.slide) return null
27
+ const vp = frame.viewport ? viewports[frame.viewport] : undefined
28
+ return vp ? { width: vp.width, height: vp.height } : SLIDE_INTRINSIC
29
+ }
30
+
31
+ /** The baseline every slide document shares, in the frame host and the stage alike: the
32
+ * document IS the stage, so the browser's body margin never frames it - whatever the theme
33
+ * or the content primitives a previous slide injected. */
34
+ export const SLIDE_DOC_CSS = 'html[data-mv-slide] body { margin: 0 }'
35
+
36
+ /** How a slide's stage sits in a box of any other size: scaled uniformly to fit and centred.
37
+ * A slide never reflows - the canvas node, the player and a shot all show the same stage. */
38
+ export function stageFit(stage: { width: number; height: number }, box: { w: number; h: number }): { k: number; ox: number; oy: number } {
39
+ const k = Math.max(0.01, Math.min(box.w / stage.width, box.h / stage.height))
40
+ return { k, ox: (box.w - stage.width * k) / 2, oy: (box.h - stage.height * k) / 2 }
23
41
  }
@@ -1,11 +1,9 @@
1
1
  /**
2
2
  * Chart (v1.5) - Apache ECharts, the Diagram way: the author picks the FORM
3
3
  * (the ECharts option surface, pointed at from instructions/slides.md);
4
- * marver injects the house theme and strips author styling drift where it
5
- * breaks the deck (animation at rest, above all).
4
+ * marver injects the house theme and keeps the chart still at rest.
6
5
  *
7
- * SVG renderer ONLY - a canvas-rendered chart would pin its frame live on
8
- * the board (the lean-DOM serializer keeps <canvas> frames degraded). At
6
+ * SVG renderer ONLY - crisp at any canvas zoom and any stage scale. At
9
7
  * rest the chart renders its final state (animation force-disabled); in
10
8
  * slides mode (useSlidePlay) it plays its entrance once on mount.
11
9
  *
@@ -66,19 +64,21 @@ export function chartTheme(t: { ink: string; font: string; accent: string; groun
66
64
 
67
65
  /** The house theme, read from the frame the chart sits in, at render time. Ink and font are
68
66
  * the element's own COMPUTED color and font-family - so a chart inherits a UI screen's
69
- * Tailwind text colour and typeface, a Doc's tokens, or a Slide's, with no per-context
70
- * wiring. Accent and ground come from slide tokens, then Doc tokens, then the mode palette. */
67
+ * Tailwind text colour and typeface, a Doc's tokens, or a slide's own type, with no
68
+ * per-context wiring. Accent and ground come from slide tokens, then Doc tokens, then the
69
+ * mode palette. A slide frame (meta `slide: true`, stamped on <html> as data-mv-slide) or a
70
+ * <Slide> wrapper takes the stage label scale. */
71
71
  function houseTheme(el: HTMLElement, dark: boolean) {
72
72
  const css = getComputedStyle(el)
73
73
  const v = (...names: string[]) => { for (const n of names) { const x = css.getPropertyValue(n).trim(); if (x) return x } return '' }
74
74
  return chartTheme({
75
75
  ink: css.color || (dark ? '#F2F2F7' : '#1C1C1E'),
76
76
  font: v('--sl-font') || css.fontFamily || FONT_STACK,
77
- accent: v('--sl-accent', '--mv-accent') || (dark ? '#0091FF' : '#0088FF'),
77
+ accent: v('--sl-accent', '--marver-slide-accent', '--mv-accent') || (dark ? '#0091FF' : '#0088FF'),
78
78
  ground: v('--sl-ground', '--mv-surface', '--mv-bg') || (dark ? '#1C1C1E' : '#FFFFFF'),
79
79
  grid: v('--sl-grid') || (dark ? 'rgba(242,242,247,.12)' : 'rgba(28,28,30,.1)'),
80
80
  dark,
81
- inSlide: !!el.closest('.sl-root'),
81
+ inSlide: !!el.closest('.sl-root') || document.documentElement.hasAttribute('data-mv-slide'),
82
82
  })
83
83
  }
84
84
 
@@ -22,7 +22,7 @@ export function Diagram(props: Parameters<typeof DiagramRoot>[0]) { ensureStyles
22
22
  import { Slide as SlideRoot } from './slide.tsx'
23
23
  import { Chart as ChartRoot } from './chart.tsx'
24
24
  import { Video as VideoRoot } from './video.tsx'
25
- export { SLIDE_W, SLIDE_H } from './slide.tsx'
25
+ export { SLIDE_W, SLIDE_H, useSlidePlay } from './slide.tsx'
26
26
  // the shared stylesheet used to ride in with Doc alone; a slide composes Img,
27
27
  // Chart, and Video straight inside <Slide> with no Doc, so every public
28
28
  // primitive installs it - once per document, idempotent
@@ -98,9 +98,10 @@ export function Img({ src, caption, alt, h }: { src: string; caption?: string; a
98
98
  const url = assetUrl(src)
99
99
  const [err, setErr] = useState(false)
100
100
  const canvasRef = useRef<HTMLCanvasElement>(null)
101
- // inside a <Slide>, the LOD canvas is OFF: a resting slide must serialize to
102
- // the lean-DOM path, and a <canvas> element pins its frame live (v1.5 §7)
103
- const inSlide = useInSlide()
101
+ // on a slide the LOD canvas is OFF: slides mode scales the whole stage up to the screen,
102
+ // and a bitmap decoded for the frame's own box would blur there - the plain <img> keeps
103
+ // its full resolution. A slide frame is marked on <html> (data-mv-slide) before it renders.
104
+ const inSlide = useInSlide() || (typeof document !== 'undefined' && document.documentElement.hasAttribute('data-mv-slide'))
104
105
  // LOD: paint the image on a <canvas> decoded to its on-screen size (never the full 17MB bitmap), and
105
106
  // re-pick resolution only when the canvas settles after a zoom. See img-lod.ts. Falls back to a plain
106
107
  // <img> where createImageBitmap/bitmaprenderer isn't available (correctness over the optimization).