@marver-design/marver 0.19.1 → 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.
Files changed (35) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/README.md +9 -11
  3. package/dist/{bake-kaf5kGZ7.mjs → bake-tAb0D6Rc.mjs} +1 -1
  4. package/dist/{build-7ed5H2vT.mjs → build-DwpNPl6Z.mjs} +3 -3
  5. package/dist/cli.mjs +18 -12
  6. package/dist/{dev-D3mP2x27.mjs → dev-BjdDP69b.mjs} +3 -3
  7. package/dist/{init-BQYCS3EU.mjs → init-DKxuRxBr.mjs} +33 -32
  8. package/dist/{plugin-DI-7NAnx.mjs → plugin-y4Ch7o_A.mjs} +7 -7
  9. package/dist/{poster-DNh6N27C.mjs → poster-FCv_nXyP.mjs} +1 -1
  10. package/dist/{publish-bakes-Dp-ZFk3d.mjs → publish-bakes-XH6Bac58.mjs} +8 -2
  11. package/dist/{shot-DMDvDbeP.mjs → shot-iw3SEcpn.mjs} +7 -7
  12. package/docs/live-jam.md +1 -1
  13. package/docs/slides.md +89 -89
  14. package/docs/sticky-notes.md +14 -4
  15. package/package.json +1 -1
  16. package/src/client/const.ts +27 -9
  17. package/src/client/content/chart.tsx +8 -8
  18. package/src/client/content/index.tsx +5 -4
  19. package/src/client/content/slide.tsx +26 -201
  20. package/src/client/frame-host/main.tsx +10 -0
  21. package/src/client/shell/Comments.tsx +12 -4
  22. package/src/client/shell/Play.tsx +26 -14
  23. package/src/client/shell/Toolbar.tsx +7 -5
  24. package/src/client/shell/canvas/FrameNode.tsx +16 -2
  25. package/src/client/shell/canvas/Sticky.tsx +16 -1
  26. package/src/client/shell/notes.ts +43 -9
  27. package/src/client/shell/store.ts +17 -8
  28. package/src/client/shell/tidy.ts +12 -5
  29. package/src/client/stage/main.tsx +56 -7
  30. package/templates/AGENTS-embedded.md +3 -2
  31. package/templates/AGENTS-studio.md +3 -2
  32. package/templates/instructions/reference/deck-layouts.md +153 -199
  33. package/templates/instructions/reference/deck-story.md +6 -6
  34. package/templates/instructions/shape.md +6 -4
  35. package/templates/instructions/slides.md +275 -383
package/CHANGELOG.md CHANGED
@@ -2,6 +2,83 @@
2
2
 
3
3
  Notable changes to `@marver-design/marver`. Format follows [Keep a Changelog](https://keepachangelog.com); versions follow semver.
4
4
 
5
+ ## 0.20.0 - 2026-10-01
6
+
7
+ ### Changed
8
+
9
+ - **A slide is any frame with `slide: true` - nothing else.** The slide type keeps everything
10
+ a deck needs around the frame: the badge, the board's reading order as the deck order, slides
11
+ mode, publishing as a deck, comments and laser in play. What goes inside is the author's own
12
+ code: any layout, typeface, drawing or animation. `<Slide>` is now an optional wrapper that
13
+ fills the frame. **Breaking for decks built on it:** it no longer pads the stage, centres the
14
+ content, sets the `sl-*` type sizes, freezes animation at rest or outlines an overflow, and
15
+ the `sl-*` classes carry no styles - such a deck still plays, and needs its own styles to look
16
+ as it did.
17
+ - **The host scales the stage, not the slide.** A slide never reflows. Slides mode renders each
18
+ slide at its stage and scales the whole stage to the window, up as well as down - a projector
19
+ shows the deck at full size without the presenter picking Fill (the Slide device never went
20
+ past 100% before); Fill window now fits the same stage edge to edge, and slides mode offers no
21
+ viewport presets. A slide frame met in present or focus plays the same way - a device preset
22
+ never reflows it. A canvas node resized away from the stage shows it scaled and centred, like
23
+ a thumbnail, with comment pins mapped through the same fit; published sleep textures are
24
+ baked at the stage.
25
+ - **A deck chooses its stage.** A slide's stage is the `viewport` it declares, when the project
26
+ defines it - `viewport: 'laptop'` makes a 16:10 deck at 1280×800 - else 1280×720. The canvas,
27
+ `marver shot`, copy as image and slides mode all use it.
28
+ - **The slides guidance teaches instead of ruling.** `instructions/slides.md` drops the fixed
29
+ type roles, margins, bands, spacing scale, pacing quotas, recipe budgets and the defect
30
+ gate. In their place: how a slide plays and animates, the deck kit a strong deck is built on
31
+ (a master shell, whole-slide tones, the brand's type, hairlines and labels, a drawing
32
+ helper), ten craft habits distilled from the best decks built on marver, the method (the
33
+ answer first, a slide list that tells the argument, a `_brief.md`, an Aim / Say / Visual /
34
+ Source note per slide) and a review that squints at the contact sheet.
35
+ `reference/deck-layouts.md` becomes an idea bank, with the compositions of a strong
36
+ consulting deck described one by one. A new project's `design/slides.md` opens with a deck
37
+ look shaped the same way. Existing projects keep their `design/slides.md`; `marver init`
38
+ updates unedited shipped instructions and stages edited ones in `design/.local/latest/`.
39
+ - **Docs.** [docs/slides.md](docs/slides.md) and the README's Slides section describe the new
40
+ model (a slide is code; the host scales the stage; motion hooks; the deck kit), with a section
41
+ for decks built before 0.20. [docs/sticky-notes.md](docs/sticky-notes.md) shows a note beside a
42
+ slide as the presenter's script - Aim / Say / Visual / Source context - and that published notes
43
+ are readable by viewers.
44
+
45
+ ### Added
46
+
47
+ - **Motion hooks for any slide.** While a deck plays, the stage marks `data-sl-play` on
48
+ `<html>`, `data-sl-entered` once each slide has arrived, and `data-mv-slide` while the
49
+ mounted frame is a slide, so a slide's own CSS or JS animation runs when it arrives and the
50
+ canvas, `marver shot` and thumbnails show the finished slide. `useSlidePlay()` is exported from
51
+ `/content` for React. The `data-animate` entrance shortcuts and the `--marver-slide-tempo`
52
+ duration now work on any slide, wrapper or not, and every slide document shares one baseline
53
+ (no body margin) in every host, so its geometry never depends on what played before it.
54
+ - **`Chart` and `Img` know a slide frame without the wrapper.** Labels take the stage scale and
55
+ images skip the canvas's decoded-to-size path, so a slide scaled up on a projector stays sharp.
56
+ `Chart` also reads `--marver-slide-accent`.
57
+
58
+ ### Upgrading
59
+
60
+ - Run `npx marver init` to take the new slides guidance (unedited instruction files update in
61
+ place; edited ones are staged in `design/.local/latest/` for your agent to merge).
62
+ - A deck built on `<Slide>` and the `sl-*` classes keeps playing but loses its padding, centring
63
+ and type sizes. Ask your agent to give it a deck kit (instructions/slides.md describes one), or
64
+ stay on 0.19 until you do.
65
+ - A deck of ordinary frames becomes a slide deck by adding `slide: true` to each frame's meta -
66
+ keep its `viewport` if it was designed at one (a 1280×800 deck stays 16:10) - and setting its
67
+ publish row to `"type": "slides"`.
68
+
69
+ ## 0.19.2 - 2026-09-09
70
+
71
+ ### Fixed
72
+
73
+ - **A note longer than its frame gets its room below.** 0.19.1 reserved a note's width beside its
74
+ frame and nothing under it: a long note ran on below the card and over the next row's notes and
75
+ frames. The column's height is now measured as it renders (markdown, fonts and diagrams decide
76
+ it) and fed to the layout: a composed board's next row starts under the note, the gutter still
77
+ the card's, and a board already saved re-applies its recipe when a note lands or grows past its
78
+ frame, as it did for width. Two columns running into each other count as cramped too; the empty
79
+ canvas under a card beside a long column is free. Leaving a device view re-checks the room the
80
+ restored rows have. Folding a note keeps its room, so a fold never moves the board.
81
+
5
82
  ## 0.19.1 - 2026-09-08
6
83
 
7
84
  ### Fixed
package/README.md CHANGED
@@ -31,7 +31,7 @@ Frames appear on the canvas the moment the files land. That's the loop.
31
31
  ## Why marver
32
32
 
33
33
  - **Frames are real code.** Plain TSX/HTML files rendered from your repo's actual components and theme - zero imports from this package required. An approved design promotes into the app by moving a file, not by re-implementing a picture.
34
- - **Decks are real code too.** A slide is a frame with `slide: true`. One `Slide` primitive, your own markup inside it, a doctrine that teaches the agent to argue rather than decorate - and a stage that scales itself to any screen without the agent writing a single breakpoint. See [Slides](#slides).
34
+ - **Decks are real code too.** A slide is a frame with `slide: true` - nothing else. Any layout, typeface, drawing or animation the browser can render, a guide that teaches the agent to argue and to wear your brand rather than decorate, and a player that scales the stage to any screen without the agent writing a single breakpoint. See [Slides](#slides).
35
35
  - **Everything hot-reloads.** The agent writes, you watch it land - live.
36
36
  - **True viewports.** Each frame is a real iframe: drag its edge and your actual breakpoints fire.
37
37
  - **Your agent answers on the canvas.** Tag `@marver` in a comment and it picks up the job, edits the real source, and replies in the thread - no wiring, on by default. See [Live Jam](#live-jam).
@@ -45,23 +45,21 @@ Frames appear on the canvas the moment the files land. That's the loop.
45
45
  - **Prototype links.** `data-goto="scene/frame"` on any element links frames into a walkable prototype - across boards, too.
46
46
  - **Five ways to view a board.** The canvas (frames on a plane), the board (the same, tidy), **present** (`p`: a full-screen clickable walkthrough - `data-goto` navigates, arrows step, `[` / `]` cycle variants, laser, comments, theme and device pickers in the toolbar), **focus** (one frame as a document - the reading preset for specs), and **slides** (a deck). A published board names its landing view; a frame deep link opens straight into it.
47
47
  - **Content frames.** Specs, Mermaid diagrams, mood boards, and slides live on the same canvas as the screens - import `Doc`, `Md`, `Diagram`, `Img`, `Slide`, `Chart`, `Video` from `@marver-design/marver/content` and think a feature through before any pixels exist. Works in a repo with no app at all: idea first, design second.
48
- - **Sticky notes.** A markdown file beside a frame (`cart.note.md`) or a scene (`_note.md`) becomes a yellow note left of the frame on the canvas - what it is for, how two variations differ, an open question. Markdown, `goto:` links to frames, hand-drawn Mermaid; readers comment on a note's text like on a frame's, fold it to its corner, hide them all with `n`. The layout makes room for it; published canvases carry them.
48
+ - **Sticky notes.** A markdown file beside a frame (`cart.note.md`) or a scene (`_note.md`) becomes a yellow note left of the frame on the canvas - what it is for, how two variations differ, an open question. Markdown, `goto:` links to frames, hand-drawn Mermaid; readers comment on a note's text like on a frame's, fold it to its corner, hide them all with `n`. The layout makes room for it, beside the frame and below; published canvases carry them.
49
49
  - **Hi-fi at rest.** A frame at rest is its own live document, asleep: animations paused and every `backdrop-filter` element painted with a certified texture of its filtered backdrop (compiled by the dev server's headless Chrome), so a board of 30 glass screens pans like 30 statics and wakes pixel-identical on interact. Glass inside glass, blend modes and frames whose paint depends on random data or the clock stay live; `?awake=1` keeps every frame live for comparison.
50
50
  - **Charts and video in any frame.** `Chart` (Apache ECharts, SVG, still at rest) inherits the ink, typeface and accent of whatever frame it sits in - a Tailwind dashboard, a dark spec, a slide - sizes its type to the context and follows the layout on resize. `Video` is poster-first everywhere: click to play wherever the frame is live, `autoplay` for an ambient loop, `ratio` for vertical clips; omit the poster and marver renders one from the clip. A screen with a chart or a clip is still a screen.
51
- - **Copy as image.** Select a frame, press `i` - a 2x PNG of it lands on the clipboard, rendered by the same headless Chrome that serves `marver shot`; `⇧i` for 4x (a slide is 5120×2880). Paste into Slack, a doc, or a chat with your agent.
51
+ - **Copy as image.** Select a frame, press `i` - a 2x PNG of it lands on the clipboard, rendered by the same headless Chrome that serves `marver shot`; `⇧i` for 4x (a 1280×720 slide is 5120×2880, whatever size its node has). Paste into Slack, a doc, or a chat with your agent.
52
52
 
53
53
  ## Slides
54
54
 
55
- A deck is a scene of `slide: true` frames. On the canvas they are 1280×720 frames like any other - comment on them, laser them, fork variants, drag them to reorder the deck (the board's reading order is the play order). Press `p` on a slides board and you get slides mode: the 16:9 stage, arrows / Space / click to advance, `d` cycles the theme, devices including fill window, morphs between slides where the agent named the same element twice.
55
+ A deck is a scene of `slide: true` frames. On the canvas they are frames like any other - comment on them, laser them, fork variants, drag them to reorder the deck (the board's reading order is the play order). Press `p` on a slides board and you get slides mode: arrows / Space / click to advance, `d` cycles the theme, morphs between slides where the agent named the same element twice.
56
56
 
57
- What makes it light on the agent side, and good on every screen:
57
+ - **A slide is just code.** No component library, no wrapper, no fixed type scale: the agent builds each slide from your project's own components and styles - an intricate diagram, a full-bleed photograph, an SVG drawing that animates in - the way it builds a screen.
58
+ - **A stage, and a player that scales it.** A slide renders at 1280×720, or at the `viewport` it declares (a 16:10 deck at 1280×800). Slides mode scales the whole stage to the screen - up on a projector, down on a phone - so the composition you approved is the one everyone sees.
59
+ - **Still at rest, alive in play.** On the canvas a resting slide is asleep like any frame; while the deck plays, the stage marks `data-sl-play` and `data-sl-entered` on the document so the agent's own animations run when a slide arrives, and `view-transition-name` morphs carry elements from one slide to the next.
60
+ - **Craft, not rules.** `init` ships `design/instructions/slides.md`: the deck kit a strong deck is built on (a master, whole-slide tones, the brand's type, a drawing system), the habits that make a deck look made rather than typed, the method (the answer first, a slide list that tells the argument, notes that carry the talk track), an idea bank of compositions, and your own project-owned `design/slides.md` with a **deck look** the agent drafts from your brand.
58
61
 
59
- - **One primitive, no component library.** `Slide` owns the stage, the margins, six fixed type roles, your theme's tokens, and the motion contract. Everything inside is your project's own markup and components - a slide is built the way a screen is built.
60
- - **The fit is pure CSS.** The agent authors at exactly 1280×720; the root scales and centers itself to a phone, a laptop, a projector, or a resized canvas node. No `vw`, no media queries, no breakpoints - the composition you approved is the composition everyone sees, and a dev-only overflow marker outlines a slide whose content escapes the stage, or whose flex/grid child outgrows its parent.
61
- - **Still at rest.** Charts are final-state SVG in a lazy chunk, videos are posters, and every CSS animation under `Slide` is suspended until slides mode plays - so a 40-slide deck of the shipped primitives pans like 40 statics on the canvas. (Your own `<canvas>`, `<video>`, or JS-driven motion stays live, as in any frame.)
62
- - **A doctrine, not a template.** `init` ships `design/instructions/slides.md`: assertion-first argument, the space (three bands, the 85% rule, one spacing scale), seven silhouettes chosen before any recipe so a deck never reads as one repeated shape, 19 recipes with budgets, choreography rules, and a review gate - plus two depth references and your own project-owned `design/slides.md` with a fill-in **deck look** the agent drafts from your brand.
63
-
64
- Publish a deck with `{ "boards": { "pitch": { "max": "read", "type": "slides", "open": "slides", "lock": true } } }` in `design/publish.json` and the link opens straight into the deck, read-only, with no canvas behind it. The [slides guide](docs/slides.md) has the rest - morphs, build steps, `Chart` and `Video`, the theme tokens.
62
+ Publish a deck with `{ "boards": { "pitch": { "max": "read", "type": "slides", "open": "slides", "lock": true } } }` in `design/publish.json` and the link opens straight into the deck, read-only, with no canvas behind it. The [slides guide](docs/slides.md) has the rest - motion, build steps, `Chart` and `Video`, notes.
65
63
 
66
64
  ## Collaboration
67
65
 
@@ -1,4 +1,4 @@
1
- import { AREA, SURFACE, pool, shotConcurrency, withBrowser } from "./shot-DMDvDbeP.mjs";
1
+ import { AREA, SURFACE, pool, shotConcurrency, withBrowser } from "./shot-iw3SEcpn.mjs";
2
2
  import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, utimesSync, writeFileSync } from "node:fs";
3
3
  import { join, sep } from "node:path";
4
4
  import { createHash } from "node:crypto";
@@ -1,7 +1,7 @@
1
1
  import { a as ROUTE, r as NAME } from "./cli.mjs";
2
2
  import { a as listBoardFiles, d as flatten, f as isBoardName, n as checkBoardsDir, s as readRegistry, t as boardFields, u as buildTree } from "./boards-BmxcT3Lc.mjs";
3
3
  import { c as loadConfig, r as scanFrames, u as detectHost } from "./manifest-B01PSyDc.mjs";
4
- import { n as tailwind3Css, r as tailwind4Plugin, t as marverPlugin } from "./plugin-DI-7NAnx.mjs";
4
+ import { n as tailwind3Css, r as tailwind4Plugin, t as marverPlugin } from "./plugin-y4Ch7o_A.mjs";
5
5
  import { cpSync, existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
6
6
  import { basename, dirname, join, sep } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
@@ -683,7 +683,7 @@ async function buildSite(root, boardsFlag, allBoardsFlag, embedSeeds, textures =
683
683
  const realAssets = existsSync(assetsDir) ? realpathSync(assetsDir) : null;
684
684
  for (const r of refs) {
685
685
  if (!isLocalAssetRef(r) || !r.endsWith(".poster.png") || existsSync(join(assetsDir, r))) continue;
686
- const { ensurePoster } = await import("./poster-DNh6N27C.mjs");
686
+ const { ensurePoster } = await import("./poster-FCv_nXyP.mjs");
687
687
  const g = await ensurePoster(assetsDir, r.slice(0, -11));
688
688
  if (!g.ok) throw new Error(`design/assets/${r}: ${g.error}`);
689
689
  console.log(` poster: rendered design/assets/${r} (${g.width}×${g.height})`);
@@ -750,7 +750,7 @@ async function buildSite(root, boardsFlag, allBoardsFlag, embedSeeds, textures =
750
750
  }));
751
751
  let textureLine = " textures: skipped (--no-textures)";
752
752
  if (textures && bakeGen) {
753
- const { bakePublished } = await import("./publish-bakes-Dp-ZFk3d.mjs");
753
+ const { bakePublished } = await import("./publish-bakes-XH6Bac58.mjs");
754
754
  const pubFrameFile = new Map(pubFrames.map((f) => [f.id, f]));
755
755
  const r = await bakePublished({
756
756
  root,
package/dist/cli.mjs CHANGED
@@ -17,19 +17,25 @@ const CONTENT_WIDTH = {
17
17
  document: 760,
18
18
  wide: 1280
19
19
  };
20
- /** The slide stage (v1.5): a runtime-reserved intrinsic, deliberately NOT a
21
- * config viewport - no migration for existing projects, no deck device in
22
- * sweeps. Dependency-neutral so server (shot) and shell (store) share it. */
20
+ /** The default slide stage: 16:9 at 1280×720, deliberately NOT a config viewport - no
21
+ * migration for existing projects, no deck device in sweeps. Dependency-neutral so
22
+ * server (shot) and shell (store, play) share it. */
23
23
  const SLIDE_INTRINSIC = {
24
24
  width: 1280,
25
25
  height: 720
26
26
  };
27
- /** The one DEFAULT sizing rule for slide frames, shared by canvas and shot:
28
- * `slide: true` sets the intrinsic 1280×720 stage, over any authored viewport.
29
- * Board nodes stay resizable (the Slide root scales into whatever box it is
30
- * given); this governs defaults, shots, and stage coordinates. */
31
- function slideSize(frame) {
32
- return frame.slide ? SLIDE_INTRINSIC : null;
27
+ /** The one sizing rule for slide frames, shared by canvas, shot and slides mode: a
28
+ * `slide: true` frame's stage is its declared viewport when the project defines one
29
+ * (a 16:10 deck authored at `laptop` stays 1280×800), else the 1280×720 default. A
30
+ * slide is an ordinary frame at that size - slides mode scales the whole stage to the
31
+ * screen, so the frame never has to. */
32
+ function slideSize(frame, viewports = {}) {
33
+ if (!frame.slide) return null;
34
+ const vp = frame.viewport ? viewports[frame.viewport] : void 0;
35
+ return vp ? {
36
+ width: vp.width,
37
+ height: vp.height
38
+ } : SLIDE_INTRINSIC;
33
39
  }
34
40
  //#endregion
35
41
  //#region src/cli/name.ts
@@ -72,14 +78,14 @@ const resolve$1 = (dir) => {
72
78
  };
73
79
  const cli = cac(NAME);
74
80
  cli.command("init", "Scaffold design/ in this repo").option("--mode <mode>", "studio | embedded", { default: "studio" }).option("--no-demo", "Skip the demo scene (the demo ships unless this flag is passed)").option("--root <dir>", "Host repo root", { default: "." }).action(async (opts) => {
75
- const { init } = await import("./init-BQYCS3EU.mjs");
81
+ const { init } = await import("./init-DKxuRxBr.mjs");
76
82
  init(resolve$1(opts.root), {
77
83
  mode: opts.mode === "embedded" ? "embedded" : "studio",
78
84
  demo: opts.demo !== false
79
85
  });
80
86
  });
81
87
  for (const [name, desc] of [["dev", "Start the local canvas (everything on: hot reload, comments, Live Jam)"], ["canvas", "Start the local canvas - same as dev"]]) cli.command(name, desc).option("--root <dir>", "Host repo root", { default: "." }).option("--port <port>", "Port (default 5199)").action(async (opts) => {
82
- const { dev } = await import("./dev-D3mP2x27.mjs");
88
+ const { dev } = await import("./dev-BjdDP69b.mjs");
83
89
  let port;
84
90
  if (opts.port !== void 0) {
85
91
  const n = Number(opts.port);
@@ -89,7 +95,7 @@ for (const [name, desc] of [["dev", "Start the local canvas (everything on: hot
89
95
  await dev(resolve$1(opts.root), port);
90
96
  });
91
97
  cli.command("build", "Static export → design/.dist (what ships comes from design/publish.json - publishing is default-closed)").option("--boards <names>", "Publish only these boards (comma-separated); overrides the publish policy").option("--all-boards", "Publish every board - the loud override for the default-closed policy").option("--embed-seeds", "Copy comment history INTO the web root (identifying - every event carries its author's email)").option("--no-textures", "Skip compiling the glass textures the published frames rest under (needs Chrome; a CI without one skips on its own)").option("--root <dir>", "Host repo root", { default: "." }).action(async (opts) => {
92
- const { buildSite } = await import("./build-7ed5H2vT.mjs");
98
+ const { buildSite } = await import("./build-DwpNPl6Z.mjs");
93
99
  try {
94
100
  const boards = opts.boards === void 0 ? void 0 : typeof opts.boards === "string" ? opts.boards : "";
95
101
  await buildSite(resolve$1(opts.root), boards, opts.allBoards === true, opts.embedSeeds === true, opts.textures !== false && !process.env.MARVER_NO_TEXTURES);
@@ -1,6 +1,6 @@
1
1
  import { i as PKG, r as NAME } from "./cli.mjs";
2
2
  import { c as loadConfig, u as detectHost } from "./manifest-B01PSyDc.mjs";
3
- import { n as tailwind3Css, r as tailwind4Plugin, t as marverPlugin } from "./plugin-DI-7NAnx.mjs";
3
+ import { n as tailwind3Css, r as tailwind4Plugin, t as marverPlugin } from "./plugin-y4Ch7o_A.mjs";
4
4
  import { readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
5
5
  import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
@@ -452,12 +452,12 @@ async function dev(root, portFlag) {
452
452
  console.log(`\n ${NAME} · ${projectName} → http://localhost:${port}/\n`);
453
453
  setTimeout(async () => {
454
454
  try {
455
- await (await import("./shot-DMDvDbeP.mjs")).sweepGhosts((m) => console.log(` ${m}`));
455
+ await (await import("./shot-iw3SEcpn.mjs")).sweepGhosts((m) => console.log(` ${m}`));
456
456
  } catch {}
457
457
  }, 0);
458
458
  try {
459
459
  const { staleManagedInstructions } = await import("./managed-HwHVNI3h.mjs");
460
- const { installedVersion } = await import("./plugin-DI-7NAnx.mjs").then((n) => n.i);
460
+ const { installedVersion } = await import("./plugin-y4Ch7o_A.mjs").then((n) => n.i);
461
461
  const stale = staleManagedInstructions(root);
462
462
  if (stale.length) {
463
463
  const shown = stale.slice(0, 4).join(", ") + (stale.length > 4 ? `, +${stale.length - 4} more` : "");
@@ -153,11 +153,11 @@ function init(root, opts) {
153
153
  }
154
154
  }
155
155
  write("slides.md", [
156
- "# This project's slide layouts and house rules",
156
+ "# This project's deck look and house habits",
157
157
  "",
158
- "The agent reads BOTH the shipped doctrine (instructions/slides.md) and this",
159
- "file - and this file WINS where they disagree. Add your own layouts, house",
160
- "rules, and banned moves here; it is yours and marver never touches it.",
158
+ "The agent reads BOTH the shipped guide (instructions/slides.md) and this",
159
+ "file - and this file WINS where they disagree. Add your own deck look,",
160
+ "compositions and habits here; it is yours and marver never touches it.",
161
161
  "",
162
162
  "To grow it from examples: drop decks (PPTX, PDF, screenshots) into",
163
163
  "design/slides-inspiration/ and ask the agent to \"study my inspiration\" -",
@@ -166,44 +166,45 @@ function init(root, opts) {
166
166
  "## The deck look",
167
167
  "",
168
168
  "How a deck wears this brand. The agent drafts it from DESIGN.md and",
169
- "theme.css on the first deck (a reviewed edit, building on provisionally",
170
- "with the theme's tokens); anything it cannot settle stays `TBD` for you.",
171
- "Each line shows the shape with an example - replace the example, keep the",
172
- "shape.",
169
+ "theme.css on the first deck (a reviewed edit); anything it cannot settle",
170
+ "stays `TBD` for you. Each line shows the shape with an example - replace",
171
+ "the example, keep the shape.",
173
172
  "",
174
- "- **Tokens** (`--marver-slide-*` in theme.css, read by the Slide root):",
175
- " ground / ink / muted, each with a `-dark` twin; accent, one value for",
176
- " both themes. e.g. ground = the page background, ink = the heading",
177
- " colour, accent = the primary button.",
178
- "- **Type** (`--marver-slide-font`): one family, the theme's; the type",
179
- " roles carry their weights. e.g. Inter.",
180
- "- **The mark**: which asset, where, how big. e.g. wordmark SVG, bottom-left",
181
- " of the cover and the closing only, 120px wide - never on content slides.",
182
- "- **Colour meaning** (charts, cards, badges): hue = category, fixed across",
183
- " the deck. e.g. accent = us, slate = competitors, muted = the baseline;",
184
- " green / amber / red reserved for status.",
185
- "- **Backgrounds allowed**: e.g. the flat ground; a theme gradient on covers",
186
- " and sections only; no photos behind text without a scrim.",
187
- "- **Imagery**: e.g. product screenshots on a device-less frame, real people,",
188
- " no stock; illustrations in the product's line style only. Evidentiary",
189
- " images (a report fragment, a log line, a reasoning trace, a real",
190
- " screen) are evidence objects, not decoration - hero material when the",
191
- " message warrants it.",
192
- "- **Tempo** (`--marver-slide-tempo`): e.g. 350ms - one value, every deck.",
173
+ "- **Stage**: e.g. 1280×720 (the default); or `viewport: 'laptop'` on every",
174
+ " slide for a 16:10 deck.",
175
+ "- **The master**: what every content slide wears. e.g. the mark top left,",
176
+ " \"Prepared for <client> · Confidential · <month year>\" top right in the",
177
+ " muted colour; the cover and the closing step outside it.",
178
+ "- **Tones**: whole-slide colour sets. e.g. paper (#F1F0EA on #151616) for",
179
+ " working slides, ink for statements and turns, the brand blue once, for",
180
+ " the proof.",
181
+ "- **Type**: family, weights, tracking, the few sizes. e.g. the brand sans at",
182
+ " 400 for headlines and text, tight tracking on headlines; a display, a",
183
+ " heading, a body and a small label size - hierarchy by size and colour.",
184
+ "- **The mark**: which asset, where, how big. e.g. the lockup in the master,",
185
+ " paired with the client's mark on the cover.",
186
+ "- **Imagery**: e.g. the client's own photography, full- or half-bleed under",
187
+ " a uniform scrim; product screenshots on a device-less frame; no stock.",
188
+ "- **Drawings**: e.g. native SVG line drawings on a shared dotted grid, ink",
189
+ " with one accent per slide, light and dark versions.",
190
+ "- **Colour meaning**: e.g. accent = the point of the slide; dark red = the",
191
+ " problem loop; green = the outcome; never decoration.",
192
+ "- **Motion** (`--marver-slide-tempo` in theme.css): e.g. 350ms morphs;",
193
+ " drawings trace in when a slide arrives; nothing loops.",
193
194
  "- **Numbers**: e.g. $ and k / M (lowercase k), fiscal years as FY26,",
194
195
  " negatives in brackets, one decimal on percentages.",
195
196
  "- **Voice**: three words the deck sounds like, and the words it never",
196
197
  " uses. e.g. direct, warm, specific; never \"leverage\", \"seamless\", \"journey\".",
197
198
  "- **Terminology**: user-facing word → never-shown internal word.",
198
199
  " e.g. \"Comments\" → \"Enrichment\".",
199
- "- **End card**: yes / no. e.g. yes - the mark on the ground, no text; the",
200
- " ask lives on the slide before it.",
200
+ "- **End card**: e.g. a full-bleed photograph, the mark and a contact; the",
201
+ " ask lives on the slide before.",
201
202
  "",
202
- "## Layouts",
203
+ "## Compositions",
203
204
  "",
204
- "(none yet - the shipped recipe list and atlas apply)",
205
+ "(none yet - the shipped idea bank in instructions/reference/deck-layouts.md applies)",
205
206
  "",
206
- "## House rules",
207
+ "## House habits",
207
208
  "",
208
209
  "(none yet)",
209
210
  ""
@@ -422,7 +422,7 @@ function apiMiddleware(root, opts = {}) {
422
422
  };
423
423
  const asPng = url.searchParams.get("format") === "png";
424
424
  const origin = opts.origin?.() ?? `http://${req.headers.host ?? "localhost"}`;
425
- const { shootFrame } = await import("./shot-DMDvDbeP.mjs");
425
+ const { shootFrame } = await import("./shot-iw3SEcpn.mjs");
426
426
  const r = await shootFrame({
427
427
  root,
428
428
  viewports: opts.viewports ?? {},
@@ -472,7 +472,7 @@ function apiMiddleware(root, opts = {}) {
472
472
  if (typeof theme !== "string" || !/^[a-z0-9-]+$/i.test(theme)) return json(res, 400, { error: "invalid theme" });
473
473
  const scale = body.scale === void 0 ? void 0 : body.scale;
474
474
  if (scale !== void 0 && !(Number.isInteger(scale) && scale >= 1 && scale <= 4)) return json(res, 400, { error: "invalid scale" });
475
- const { resolveFrames, shootBatch } = await import("./shot-DMDvDbeP.mjs");
475
+ const { resolveFrames, shootBatch } = await import("./shot-iw3SEcpn.mjs");
476
476
  const sel = resolveFrames(root, body);
477
477
  if (!sel.ok) return json(res, sel.status, { error: sel.error });
478
478
  const origin = opts.origin?.() ?? `http://${req.headers.host ?? "localhost"}`;
@@ -500,7 +500,7 @@ function apiMiddleware(root, opts = {}) {
500
500
  return json(res, 400, { error: "malformed JSON" });
501
501
  }
502
502
  if (!body || typeof body !== "object" || !Array.isArray(body.asks) || !body.asks.length || body.asks.length > 200) return json(res, 400, { error: "asks must list 1-200 frames" });
503
- const { bakeBatch, ASK_MAX } = await import("./bake-kaf5kGZ7.mjs");
503
+ const { bakeBatch, ASK_MAX } = await import("./bake-tAb0D6Rc.mjs");
504
504
  let manifest = {};
505
505
  try {
506
506
  manifest = JSON.parse(readFileSync(join(root, "design", "manifest.json"), "utf8"));
@@ -544,8 +544,8 @@ function apiMiddleware(root, opts = {}) {
544
544
  if (path === "poster" && req.method === "GET") {
545
545
  if (!ownerGated(req)) return json(res, 403, { error: "forbidden" });
546
546
  const src = url.searchParams.get("src") ?? "";
547
- const { ensurePoster, isLocalClip } = await import("./poster-DNh6N27C.mjs");
548
- const { isLocalAssetRef } = await import("./build-7ed5H2vT.mjs");
547
+ const { ensurePoster, isLocalClip } = await import("./poster-FCv_nXyP.mjs");
548
+ const { isLocalAssetRef } = await import("./build-DwpNPl6Z.mjs");
549
549
  if (!isLocalAssetRef(src) || !isLocalClip(src)) return json(res, 400, { error: "src must be a clip under design/assets/" });
550
550
  const r = await ensurePoster(join(root, "design", "assets"), src);
551
551
  if (!r.ok) return json(res, r.error.includes("does not exist") ? 404 : 503, { error: r.error });
@@ -1027,7 +1027,7 @@ function marverPlugin(ctx) {
1027
1027
  };
1028
1028
  const prune = () => setTimeout(async () => {
1029
1029
  try {
1030
- (await import("./bake-kaf5kGZ7.mjs")).pruneBakes(root, bakeGen);
1030
+ (await import("./bake-tAb0D6Rc.mjs")).pruneBakes(root, bakeGen);
1031
1031
  } catch {}
1032
1032
  }, 500);
1033
1033
  prune();
@@ -1166,7 +1166,7 @@ function marverPlugin(ctx) {
1166
1166
  });
1167
1167
  return;
1168
1168
  }
1169
- const { shootFrame, shootBatch, resolveFrames } = await import("./shot-DMDvDbeP.mjs");
1169
+ const { shootFrame, shootBatch, resolveFrames } = await import("./shot-iw3SEcpn.mjs");
1170
1170
  if (ways[0] === "frame") {
1171
1171
  if (typeof spec.frame !== "string") {
1172
1172
  write({
@@ -1,4 +1,4 @@
1
- import { capture, t as findChrome } from "./shot-DMDvDbeP.mjs";
1
+ import { capture, t as findChrome } from "./shot-iw3SEcpn.mjs";
2
2
  import { constants, copyFileSync, existsSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { basename, dirname, join, sep } from "node:path";
4
4
  import { pathToFileURL } from "node:url";
@@ -1,5 +1,6 @@
1
- import { planShot, t as findChrome } from "./shot-DMDvDbeP.mjs";
2
- import { ASK_MAX, bakeBatch } from "./bake-kaf5kGZ7.mjs";
1
+ import { o as slideSize } from "./cli.mjs";
2
+ import { planShot, t as findChrome } from "./shot-iw3SEcpn.mjs";
3
+ import { ASK_MAX, bakeBatch } from "./bake-tAb0D6Rc.mjs";
3
4
  import { MIME } from "./serve-z5qtj_wJ.mjs";
4
5
  import { copyFileSync, lstatSync, mkdirSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
5
6
  import { extname, isAbsolute, join, relative, resolve } from "node:path";
@@ -43,6 +44,11 @@ function publishedAsks(boards, themes, frames, viewports, allScenes = false) {
43
44
  }
44
45
  };
45
46
  const size = (f, n) => {
47
+ const sl = slideSize(f, viewports);
48
+ if (sl) return {
49
+ w: sl.width,
50
+ h: sl.height
51
+ };
46
52
  const nw = n && typeof n.w === "number" && n.w > 0 ? n.w : void 0, nh = n && typeof n.h === "number" && n.h > 0 ? n.h : void 0;
47
53
  const p = planShot(f, viewports, {});
48
54
  if (p.fullHeight && !nh) return null;
@@ -283,16 +283,16 @@ function planShot(frame, viewports, override = {}) {
283
283
  const cw = num(frame.contentWidth);
284
284
  const vpObj = frame.viewport ? viewports[frame.viewport] : void 0;
285
285
  const ow = num(override.w), oh = num(override.h);
286
- const sl = slideSize(frame);
286
+ const fallback = viewports.mobile ?? {
287
+ width: 390,
288
+ height: 844
289
+ };
290
+ const sl = slideSize(frame, viewports);
287
291
  if (sl) return {
288
292
  width: sl.width,
289
293
  initialHeight: sl.height,
290
294
  fullHeight: false
291
295
  };
292
- const fallback = viewports.mobile ?? {
293
- width: 390,
294
- height: 844
295
- };
296
296
  if (cw) {
297
297
  const width = clamp(ow ?? vpObj?.width ?? cw, 320, 1600);
298
298
  return {
@@ -337,8 +337,8 @@ async function shootFrame(opts) {
337
337
  };
338
338
  const plan = planShot(frame, viewports, size);
339
339
  if (frame.kind !== "html") try {
340
- const { scanAssetRefs } = await import("./build-7ed5H2vT.mjs");
341
- const { ensurePoster } = await import("./poster-DNh6N27C.mjs");
340
+ const { scanAssetRefs } = await import("./build-DwpNPl6Z.mjs");
341
+ const { ensurePoster } = await import("./poster-FCv_nXyP.mjs");
342
342
  const refs = scanAssetRefs(readFileSync(join(root, frame.file), "utf8"), frame.file);
343
343
  for (const r of refs) if (r.endsWith(".poster.png")) await ensurePoster(join(root, "design", "assets"), r.slice(0, -11));
344
344
  } catch {}
package/docs/live-jam.md CHANGED
@@ -105,7 +105,7 @@ obvious one:
105
105
  scale}` for humans and shell-ful agents - the same renderer, one line. A batch is ONE
106
106
  operation: one headless browser, `MARVER_SHOT_CONCURRENCY` frames at a time inside it
107
107
  (default up to 6, sized to the machine), so a scene costs about what a frame does. Default
108
- 2x; `--scale 4` for a print-quality still (a slide comes back 5120×2880). A frame too tall
108
+ 2x; `--scale 4` for a print-quality still (a 1280×720 slide comes back 5120×2880). A frame too tall
109
109
  for the asked scale steps down and says so in `note`; the file name carries the scale
110
110
  actually used (`…@4x.png`). A frame that ran out of settle budget still ships, marked
111
111
  `unsettled` with a note.