@classic-homes/slides 0.2.1 → 0.3.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.
@@ -0,0 +1,107 @@
1
+ import {
2
+ DeckSchema,
3
+ LAYOUTS,
4
+ LAYOUT_CATEGORIES,
5
+ baseSlide,
6
+ getLayout
7
+ } from "./chunk-GXGDPBAR.js";
8
+
9
+ // src/catalog.ts
10
+ import { z } from "zod";
11
+ import { zodToJsonSchema } from "zod-to-json-schema";
12
+ function listLayouts() {
13
+ return LAYOUTS.map((l) => ({
14
+ name: l.name,
15
+ category: LAYOUT_CATEGORIES[l.name],
16
+ description: l.description,
17
+ whenToUse: l.whenToUse
18
+ }));
19
+ }
20
+ function describeLayout(name) {
21
+ const layout = getLayout(name);
22
+ if (!layout) return void 0;
23
+ const schema = z.object({ layout: z.literal(layout.name), ...layout.shape, ...baseSlide });
24
+ return {
25
+ name: layout.name,
26
+ description: layout.description,
27
+ whenToUse: layout.whenToUse,
28
+ defaults: layout.defaults ?? {},
29
+ schema: zodToJsonSchema(schema, { $refStrategy: "none" }),
30
+ example: layout.example
31
+ };
32
+ }
33
+ function deckJsonSchema() {
34
+ return zodToJsonSchema(DeckSchema, { name: "ClassicDeck", $refStrategy: "none" });
35
+ }
36
+ var AUTHORING_GUIDE = `# Writing a Classic Homes deck
37
+
38
+ A deck is a YAML (or JSON) file: deck-level fields plus a list of slides. Each slide
39
+ picks a **layout** and fills its fields. Layouts own all styling \u2014 there is no way to
40
+ set colours, fonts or sizes directly, which is what keeps every deck on-brand.
41
+
42
+ ## Principles
43
+ - **One idea per slide.** The headline states the point. Detail goes in \`notes\`.
44
+ - **Short copy.** Titles under ~70 characters, bullets a few words, at most 6 bullets.
45
+ \`validate\` warns when a slide gets crowded \u2014 split it rather than shrinking text.
46
+ - **Speaker notes on every content slide.** Presenters press N (or P for presenter view).
47
+ - **Real photos over decoration.** Every image needs \`alt\` text.
48
+ - **Tracks** colour the accent: \`value\` (red, default) for the homeowner/value story,
49
+ \`build\` (teal) for construction/process, \`neutral\` (navy) for framing slides.
50
+ - **Backgrounds**: \`paper\` (default) for content; solid \`navy\`/\`red\`/\`teal\`/\`plum\`/\`grey\`/\`stone\`
51
+ for dividers. \`backgroundImage\` + \`overlay\` puts a photo behind title/section/statement slides.
52
+
53
+ ## Picking a layout
54
+ - **Numbers:** \`stats\` (2\u20136 headline figures, optional trend + sparkline), \`chart\` (a real chart \u2014
55
+ put the takeaway in the title), \`table\` (exact figures people compare).
56
+ - **Processes & structure:** \`flow\` (ordered steps with arrows), \`timeline\` (dated milestones, phases),
57
+ \`cycle\` (things that repeat), \`hierarchy\` (org chart / tree), \`matrix\` (2\xD72), \`tiers\`
58
+ (funnel or pyramid), \`walkthrough\` (photo per stage), \`journey\` (deck agenda).
59
+ - **Media:** \`split\` (text + photos), \`photos\`, \`image\` (with numbered \`hotspots\` for floor plans
60
+ and site maps), \`compare\` (before/after slider), \`video\` (YouTube/Vimeo), \`embed\` (any https page:
61
+ maps, dashboards, 3D tours, forms).
62
+ - **Words:** \`statement\`, \`bullets\`, \`cards\`, \`comparison\`, \`quote\`.
63
+ - **Wrap-up:** \`qr\` (send people somewhere), \`contacts\`, closing \`statement\`.
64
+
65
+ ## Charts
66
+ \`chart.labels\` are the categories; each series has one value per label. Pie, donut, funnel and
67
+ gauge use the first series; gauge needs \`max\`; scatter uses two series as x and y. Use
68
+ \`highlight\` to emphasise one category and \`format\`/\`unit\` instead of typing symbols into numbers.
69
+
70
+ ## Video & embeds
71
+ They need internet while presenting. Offline, in print and in PDF they show a poster (or
72
+ \`fallback\` screenshot) with a QR code. Some sites refuse to be framed \u2014 the "Open \u2197" link
73
+ always works; add a \`fallback\` image for anything critical.
74
+
75
+ ## Brand
76
+ \`brand.profile\` picks who the deck speaks for (default \`classic-homes\`). You may override
77
+ \`name\`, \`tagline\`, \`logo\`, \`logoOnDark\`, \`favicon\` and \`footer\`; colours come from the profile.
78
+ \`settings\` controls slide numbers, logo placement, a confidential label and kiosk auto-advance.
79
+
80
+ ## Presenting
81
+ Arrow keys / Space advance (and step through \`build: true\` reveals). N notes \xB7 P presenter window
82
+ (current + next slide, notes, timer) \xB7 O overview \xB7 F fullscreen \xB7 \`?kiosk=15\` on the URL loops
83
+ unattended. \`hidden: true\` skips a slide without deleting it.
84
+
85
+ ## Inline text
86
+ \`**bold**\`, \`*highlight*\` (accent colour), \`\\\`code\\\`\`, \`[label](https://\u2026)\`. No HTML.
87
+
88
+ ## Journey decks
89
+ Set \`journey.steps\` at deck level to get a numbered agenda. Then:
90
+ - a \`journey\` slide shows the full map,
91
+ - \`step: 3\` on any slide draws the vertical STEP rail,
92
+ - \`showJourney: true\` adds the compact "you are here" strip (great on \`section\` slides).
93
+
94
+ ## Typical shape
95
+ 1. \`title\`
96
+ 2. \`statement\` or \`journey\` (why we're here / the agenda)
97
+ 3. per part: \`section\` \u2192 2\u20135 content slides
98
+ 4. \`qr\` (where to go next) \u2192 \`contacts\` \u2192 closing \`statement\`
99
+ `;
100
+
101
+ export {
102
+ listLayouts,
103
+ describeLayout,
104
+ deckJsonSchema,
105
+ AUTHORING_GUIDE
106
+ };
107
+ //# sourceMappingURL=chunk-YI7A4EWX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/catalog.ts"],"sourcesContent":["import { z } from 'zod';\nimport { zodToJsonSchema } from 'zod-to-json-schema';\nimport {\n LAYOUT_CATEGORIES,\n LAYOUTS,\n getLayout,\n type LayoutCategory,\n type LayoutName,\n} from './layouts/index.js';\nimport { baseSlide } from './schema/common.js';\nimport { DeckSchema } from './schema/deck.js';\n\nexport interface LayoutSummary {\n name: string;\n category: LayoutCategory;\n description: string;\n whenToUse: string;\n}\n\nexport function listLayouts(): LayoutSummary[] {\n return LAYOUTS.map((l) => ({\n name: l.name,\n category: LAYOUT_CATEGORIES[l.name as LayoutName],\n description: l.description,\n whenToUse: l.whenToUse,\n }));\n}\n\n/** Full description of one layout: fields as JSON Schema plus a working example. */\nexport function describeLayout(name: string) {\n const layout = getLayout(name);\n if (!layout) return undefined;\n const schema = z.object({ layout: z.literal(layout.name), ...layout.shape, ...baseSlide });\n return {\n name: layout.name,\n description: layout.description,\n whenToUse: layout.whenToUse,\n defaults: layout.defaults ?? {},\n schema: zodToJsonSchema(schema, { $refStrategy: 'none' }),\n example: layout.example,\n };\n}\n\n/** JSON Schema for a whole deck (editor tooling, `$schema` in deck files). */\nexport function deckJsonSchema() {\n return zodToJsonSchema(DeckSchema, { name: 'ClassicDeck', $refStrategy: 'none' });\n}\n\n/** Authoring guidance shared by the CLI, MCP server and docs. */\nexport const AUTHORING_GUIDE = `# Writing a Classic Homes deck\n\nA deck is a YAML (or JSON) file: deck-level fields plus a list of slides. Each slide\npicks a **layout** and fills its fields. Layouts own all styling — there is no way to\nset colours, fonts or sizes directly, which is what keeps every deck on-brand.\n\n## Principles\n- **One idea per slide.** The headline states the point. Detail goes in \\`notes\\`.\n- **Short copy.** Titles under ~70 characters, bullets a few words, at most 6 bullets.\n \\`validate\\` warns when a slide gets crowded — split it rather than shrinking text.\n- **Speaker notes on every content slide.** Presenters press N (or P for presenter view).\n- **Real photos over decoration.** Every image needs \\`alt\\` text.\n- **Tracks** colour the accent: \\`value\\` (red, default) for the homeowner/value story,\n \\`build\\` (teal) for construction/process, \\`neutral\\` (navy) for framing slides.\n- **Backgrounds**: \\`paper\\` (default) for content; solid \\`navy\\`/\\`red\\`/\\`teal\\`/\\`plum\\`/\\`grey\\`/\\`stone\\`\n for dividers. \\`backgroundImage\\` + \\`overlay\\` puts a photo behind title/section/statement slides.\n\n## Picking a layout\n- **Numbers:** \\`stats\\` (2–6 headline figures, optional trend + sparkline), \\`chart\\` (a real chart —\n put the takeaway in the title), \\`table\\` (exact figures people compare).\n- **Processes & structure:** \\`flow\\` (ordered steps with arrows), \\`timeline\\` (dated milestones, phases),\n \\`cycle\\` (things that repeat), \\`hierarchy\\` (org chart / tree), \\`matrix\\` (2×2), \\`tiers\\`\n (funnel or pyramid), \\`walkthrough\\` (photo per stage), \\`journey\\` (deck agenda).\n- **Media:** \\`split\\` (text + photos), \\`photos\\`, \\`image\\` (with numbered \\`hotspots\\` for floor plans\n and site maps), \\`compare\\` (before/after slider), \\`video\\` (YouTube/Vimeo), \\`embed\\` (any https page:\n maps, dashboards, 3D tours, forms).\n- **Words:** \\`statement\\`, \\`bullets\\`, \\`cards\\`, \\`comparison\\`, \\`quote\\`.\n- **Wrap-up:** \\`qr\\` (send people somewhere), \\`contacts\\`, closing \\`statement\\`.\n\n## Charts\n\\`chart.labels\\` are the categories; each series has one value per label. Pie, donut, funnel and\ngauge use the first series; gauge needs \\`max\\`; scatter uses two series as x and y. Use\n\\`highlight\\` to emphasise one category and \\`format\\`/\\`unit\\` instead of typing symbols into numbers.\n\n## Video & embeds\nThey need internet while presenting. Offline, in print and in PDF they show a poster (or\n\\`fallback\\` screenshot) with a QR code. Some sites refuse to be framed — the \"Open ↗\" link\nalways works; add a \\`fallback\\` image for anything critical.\n\n## Brand\n\\`brand.profile\\` picks who the deck speaks for (default \\`classic-homes\\`). You may override\n\\`name\\`, \\`tagline\\`, \\`logo\\`, \\`logoOnDark\\`, \\`favicon\\` and \\`footer\\`; colours come from the profile.\n\\`settings\\` controls slide numbers, logo placement, a confidential label and kiosk auto-advance.\n\n## Presenting\nArrow keys / Space advance (and step through \\`build: true\\` reveals). N notes · P presenter window\n(current + next slide, notes, timer) · O overview · F fullscreen · \\`?kiosk=15\\` on the URL loops\nunattended. \\`hidden: true\\` skips a slide without deleting it.\n\n## Inline text\n\\`**bold**\\`, \\`*highlight*\\` (accent colour), \\`\\\\\\`code\\\\\\`\\`, \\`[label](https://…)\\`. No HTML.\n\n## Journey decks\nSet \\`journey.steps\\` at deck level to get a numbered agenda. Then:\n- a \\`journey\\` slide shows the full map,\n- \\`step: 3\\` on any slide draws the vertical STEP rail,\n- \\`showJourney: true\\` adds the compact \"you are here\" strip (great on \\`section\\` slides).\n\n## Typical shape\n1. \\`title\\`\n2. \\`statement\\` or \\`journey\\` (why we're here / the agenda)\n3. per part: \\`section\\` → 2–5 content slides\n4. \\`qr\\` (where to go next) → \\`contacts\\` → closing \\`statement\\`\n`;\n"],"mappings":";;;;;;;;;AAAA,SAAS,SAAS;AAClB,SAAS,uBAAuB;AAkBzB,SAAS,cAA+B;AAC7C,SAAO,QAAQ,IAAI,CAAC,OAAO;AAAA,IACzB,MAAM,EAAE;AAAA,IACR,UAAU,kBAAkB,EAAE,IAAkB;AAAA,IAChD,aAAa,EAAE;AAAA,IACf,WAAW,EAAE;AAAA,EACf,EAAE;AACJ;AAGO,SAAS,eAAe,MAAc;AAC3C,QAAM,SAAS,UAAU,IAAI;AAC7B,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,OAAO,IAAI,GAAG,GAAG,OAAO,OAAO,GAAG,UAAU,CAAC;AACzF,SAAO;AAAA,IACL,MAAM,OAAO;AAAA,IACb,aAAa,OAAO;AAAA,IACpB,WAAW,OAAO;AAAA,IAClB,UAAU,OAAO,YAAY,CAAC;AAAA,IAC9B,QAAQ,gBAAgB,QAAQ,EAAE,cAAc,OAAO,CAAC;AAAA,IACxD,SAAS,OAAO;AAAA,EAClB;AACF;AAGO,SAAS,iBAAiB;AAC/B,SAAO,gBAAgB,YAAY,EAAE,MAAM,eAAe,cAAc,OAAO,CAAC;AAClF;AAGO,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;","names":[]}
package/dist/cli.js CHANGED
@@ -4,18 +4,18 @@ import {
4
4
  deckJsonSchema,
5
5
  describeLayout,
6
6
  listLayouts
7
- } from "./chunk-33EPZMT6.js";
7
+ } from "./chunk-YI7A4EWX.js";
8
8
  import {
9
9
  buildDeckFile,
10
10
  fileImageResolver,
11
11
  loadDeckFile
12
- } from "./chunk-MYY5GZC2.js";
12
+ } from "./chunk-2IPLZ3SA.js";
13
13
  import {
14
14
  DeckError,
15
15
  formatIssues,
16
16
  renderDeck,
17
17
  validateDeck
18
- } from "./chunk-7FROOJCY.js";
18
+ } from "./chunk-GXGDPBAR.js";
19
19
 
20
20
  // src/node/cli.ts
21
21
  import { watch } from "fs";
@@ -107,7 +107,7 @@ ${"".padEnd(12)} Use for: ${l.whenToUse}
107
107
  case "mcp": {
108
108
  let mcp;
109
109
  try {
110
- mcp = await import("./mcp-DGMQTIQI.js");
110
+ mcp = await import("./mcp-PUA45IS7.js");
111
111
  } catch (e) {
112
112
  if (e.code === "ERR_MODULE_NOT_FOUND") {
113
113
  throw new Error(
@@ -0,0 +1,50 @@
1
+ // src/echarts-render.ts
2
+ import { lightTheme } from "@classic-homes/charts-core/theme";
3
+ import {
4
+ BarChart,
5
+ FunnelChart,
6
+ GaugeChart,
7
+ LineChart,
8
+ PieChart,
9
+ RadarChart,
10
+ ScatterChart
11
+ } from "echarts/charts";
12
+ import { GridComponent, LegendComponent, RadarComponent } from "echarts/components";
13
+ import * as echarts from "echarts/core";
14
+ import { SVGRenderer } from "echarts/renderers";
15
+ echarts.use([
16
+ BarChart,
17
+ FunnelChart,
18
+ GaugeChart,
19
+ LineChart,
20
+ PieChart,
21
+ RadarChart,
22
+ ScatterChart,
23
+ GridComponent,
24
+ LegendComponent,
25
+ RadarComponent,
26
+ SVGRenderer
27
+ ]);
28
+ echarts.registerTheme("classic-slides", lightTheme);
29
+ function createRenderer() {
30
+ return {
31
+ render(option, width, height) {
32
+ const chart = echarts.init(null, "classic-slides", {
33
+ renderer: "svg",
34
+ ssr: true,
35
+ width,
36
+ height
37
+ });
38
+ try {
39
+ chart.setOption(option);
40
+ return chart.renderToSVGString();
41
+ } finally {
42
+ chart.dispose();
43
+ }
44
+ }
45
+ };
46
+ }
47
+ export {
48
+ createRenderer
49
+ };
50
+ //# sourceMappingURL=echarts-render-2QVSBZ6V.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/echarts-render.ts"],"sourcesContent":["/** ECharts server-side SVG renderer. Loaded lazily by charts.ts. */\nimport { lightTheme } from '@classic-homes/charts-core/theme';\nimport {\n BarChart,\n FunnelChart,\n GaugeChart,\n LineChart,\n PieChart,\n RadarChart,\n ScatterChart,\n} from 'echarts/charts';\nimport { GridComponent, LegendComponent, RadarComponent } from 'echarts/components';\nimport * as echarts from 'echarts/core';\nimport { SVGRenderer } from 'echarts/renderers';\nimport type { ChartRenderer } from './charts.js';\n\necharts.use([\n BarChart,\n FunnelChart,\n GaugeChart,\n LineChart,\n PieChart,\n RadarChart,\n ScatterChart,\n GridComponent,\n LegendComponent,\n RadarComponent,\n SVGRenderer,\n]);\necharts.registerTheme('classic-slides', lightTheme as unknown as object);\n\nexport function createRenderer(): ChartRenderer {\n return {\n render(option, width, height) {\n const chart = echarts.init(null, 'classic-slides', {\n renderer: 'svg',\n ssr: true,\n width,\n height,\n });\n try {\n chart.setOption(option);\n return chart.renderToSVGString();\n } finally {\n chart.dispose();\n }\n },\n };\n}\n"],"mappings":";AACA,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,eAAe,iBAAiB,sBAAsB;AAC/D,YAAY,aAAa;AACzB,SAAS,mBAAmB;AAGpB,YAAI;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AACO,sBAAc,kBAAkB,UAA+B;AAEhE,SAAS,iBAAgC;AAC9C,SAAO;AAAA,IACL,OAAO,QAAQ,OAAO,QAAQ;AAC5B,YAAM,QAAgB,aAAK,MAAM,kBAAkB;AAAA,QACjD,UAAU;AAAA,QACV,KAAK;AAAA,QACL;AAAA,QACA;AAAA,MACF,CAAC;AACD,UAAI;AACF,cAAM,UAAU,MAAM;AACtB,eAAO,MAAM,kBAAkB;AAAA,MACjC,UAAE;AACA,cAAM,QAAQ;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,9 +1,11 @@
1
- export { D as Deck, a as DeckError, b as DeckInput, c as DeckSchema, I as ImageResolver, L as LAYOUTS, d as LayoutDef, e as LayoutIssue, f as LayoutName, P as PREVIEW_SIZE, R as RenderOptions, g as RenderResult, S as SlideData, h as SlideInput, i as SlideSchema, V as ValidationResult, j as collectImageSources, k as extractDeckSource, l as formatIssues, m as formatPath, n as getLayout, r as renderDeck, v as validateDeck } from './render-DTz7tLKM.js';
1
+ import { L as LayoutCategory, B as BrandPalette } from './render-DcyKuBRD.js';
2
+ export { a as BrandPaletteSchema, b as BrandProfile, c as BrandProfileInput, d as BrandProfileSchema, C as CLASSIC_HOMES_BRAND, e as CLASSIC_HOMES_PALETTE, D as Deck, f as DeckBrand, g as DeckBrandSchema, h as DeckError, i as DeckInput, j as DeckSchema, k as DeckSettings, l as DeckSettingsSchema, I as ImageInput, m as ImageResolver, n as ImageValue, o as ItemValue, p as LAYOUTS, q as LAYOUT_CATEGORIES, r as LAYOUT_CATEGORY_ORDER, s as LayoutDef, t as LayoutIssue, u as LayoutName, P as PREVIEW_SIZE, R as RenderOptions, v as RenderResult, w as ResolvedBrand, S as SlideData, x as SlideInput, y as SlideSchema, V as ValidateOptions, z as ValidationResult, A as checkPalette, E as collectImageSources, F as extractDeckSource, G as formatIssues, H as formatPath, J as getLayout, K as renderDeck, M as resolveBrand, N as validateBrandProfile, O as validateDeck } from './render-DcyKuBRD.js';
2
3
  import * as zod_to_json_schema from 'zod-to-json-schema';
3
- import 'zod';
4
+ import { z } from 'zod';
4
5
 
5
6
  interface LayoutSummary {
6
7
  name: string;
8
+ category: LayoutCategory;
7
9
  description: string;
8
10
  whenToUse: string;
9
11
  }
@@ -33,7 +35,7 @@ declare function deckJsonSchema(): zod_to_json_schema.JsonSchema7Type & {
33
35
  } | undefined;
34
36
  };
35
37
  /** Authoring guidance shared by the CLI, MCP server and docs. */
36
- declare const AUTHORING_GUIDE = "# Writing a Classic Homes deck\n\nA deck is a YAML (or JSON) file: deck-level fields plus a list of slides. Each slide\npicks a **layout** and fills its fields. Layouts own all styling \u2014 there is no way to\nset colours, fonts or sizes directly, which is what keeps every deck on-brand.\n\n## Principles\n- **One idea per slide.** The headline states the point. Detail goes in `notes`.\n- **Short copy.** Titles under ~70 characters, bullets a few words, at most 6 bullets.\n `validate` warns when a slide gets crowded \u2014 split it rather than shrinking text.\n- **Speaker notes on every content slide.** Presenters press N to see them.\n- **Real photos over decoration.** Every image needs `alt` text.\n- **Tracks** colour the accent: `value` (red, default) for the homeowner/value story,\n `build` (teal) for construction/process, `neutral` (navy) for framing slides.\n- **Backgrounds**: `paper` (default) for content; solid `navy`/`red`/`teal`/`plum`/`grey`/`stone`\n for section dividers and moments of emphasis. Don't put dense content on solid grounds.\n\n## Inline text\n`**bold**`, `*highlight*` (accent colour), `\\`code\\``, `[label](https://\u2026)`. No HTML.\n\n## Journey decks\nSet `journey.steps` at deck level to get a numbered agenda. Then:\n- a `journey` slide shows the full map,\n- `step: 3` on any slide draws the vertical STEP rail,\n- `showJourney: true` adds the compact \"you are here\" strip (great on `section` slides).\n\n## Typical shape\n1. `title`\n2. `statement` or `journey` (why we're here / the agenda)\n3. per part: `section` \u2192 2\u20135 content slides (`bullets`, `split`, `cards`, `stats`, `photos`, `walkthrough`)\n4. `qr` (where to go next) \u2192 `contacts` \u2192 closing `statement`\n";
38
+ declare const AUTHORING_GUIDE = "# Writing a Classic Homes deck\n\nA deck is a YAML (or JSON) file: deck-level fields plus a list of slides. Each slide\npicks a **layout** and fills its fields. Layouts own all styling \u2014 there is no way to\nset colours, fonts or sizes directly, which is what keeps every deck on-brand.\n\n## Principles\n- **One idea per slide.** The headline states the point. Detail goes in `notes`.\n- **Short copy.** Titles under ~70 characters, bullets a few words, at most 6 bullets.\n `validate` warns when a slide gets crowded \u2014 split it rather than shrinking text.\n- **Speaker notes on every content slide.** Presenters press N (or P for presenter view).\n- **Real photos over decoration.** Every image needs `alt` text.\n- **Tracks** colour the accent: `value` (red, default) for the homeowner/value story,\n `build` (teal) for construction/process, `neutral` (navy) for framing slides.\n- **Backgrounds**: `paper` (default) for content; solid `navy`/`red`/`teal`/`plum`/`grey`/`stone`\n for dividers. `backgroundImage` + `overlay` puts a photo behind title/section/statement slides.\n\n## Picking a layout\n- **Numbers:** `stats` (2\u20136 headline figures, optional trend + sparkline), `chart` (a real chart \u2014\n put the takeaway in the title), `table` (exact figures people compare).\n- **Processes & structure:** `flow` (ordered steps with arrows), `timeline` (dated milestones, phases),\n `cycle` (things that repeat), `hierarchy` (org chart / tree), `matrix` (2\u00D72), `tiers`\n (funnel or pyramid), `walkthrough` (photo per stage), `journey` (deck agenda).\n- **Media:** `split` (text + photos), `photos`, `image` (with numbered `hotspots` for floor plans\n and site maps), `compare` (before/after slider), `video` (YouTube/Vimeo), `embed` (any https page:\n maps, dashboards, 3D tours, forms).\n- **Words:** `statement`, `bullets`, `cards`, `comparison`, `quote`.\n- **Wrap-up:** `qr` (send people somewhere), `contacts`, closing `statement`.\n\n## Charts\n`chart.labels` are the categories; each series has one value per label. Pie, donut, funnel and\ngauge use the first series; gauge needs `max`; scatter uses two series as x and y. Use\n`highlight` to emphasise one category and `format`/`unit` instead of typing symbols into numbers.\n\n## Video & embeds\nThey need internet while presenting. Offline, in print and in PDF they show a poster (or\n`fallback` screenshot) with a QR code. Some sites refuse to be framed \u2014 the \"Open \u2197\" link\nalways works; add a `fallback` image for anything critical.\n\n## Brand\n`brand.profile` picks who the deck speaks for (default `classic-homes`). You may override\n`name`, `tagline`, `logo`, `logoOnDark`, `favicon` and `footer`; colours come from the profile.\n`settings` controls slide numbers, logo placement, a confidential label and kiosk auto-advance.\n\n## Presenting\nArrow keys / Space advance (and step through `build: true` reveals). N notes \u00B7 P presenter window\n(current + next slide, notes, timer) \u00B7 O overview \u00B7 F fullscreen \u00B7 `?kiosk=15` on the URL loops\nunattended. `hidden: true` skips a slide without deleting it.\n\n## Inline text\n`**bold**`, `*highlight*` (accent colour), `\\`code\\``, `[label](https://\u2026)`. No HTML.\n\n## Journey decks\nSet `journey.steps` at deck level to get a numbered agenda. Then:\n- a `journey` slide shows the full map,\n- `step: 3` on any slide draws the vertical STEP rail,\n- `showJourney: true` adds the compact \"you are here\" strip (great on `section` slides).\n\n## Typical shape\n1. `title`\n2. `statement` or `journey` (why we're here / the agenda)\n3. per part: `section` \u2192 2\u20135 content slides\n4. `qr` (where to go next) \u2192 `contacts` \u2192 closing `statement`\n";
37
39
 
38
40
  /** Parses deck source text. YAML is a superset of JSON, so both work. */
39
41
  declare function parseDeckSource(text: string): unknown;
@@ -82,6 +84,12 @@ declare const PALETTE: {
82
84
  };
83
85
  /** Horizontal Classic Homes lockup (mark + wordmark), mirrors LogoMain in theme-svelte. Colours follow CSS. */
84
86
  declare const LOGO_SVG: string;
87
+ /**
88
+ * Logo lockup from a name. Classic Homes brands (divisions, studios) keep the
89
+ * flower mark; partner brands without a logo file get a clean wordmark so
90
+ * they are never shown under the Classic Homes mark.
91
+ */
92
+ declare function logoLockup(name: string, withMark?: boolean): string;
85
93
 
86
94
  /**
87
95
  * Slide-level edits on a deck object. Pure functions shared by the MCP
@@ -95,6 +103,18 @@ interface EditableDeck {
95
103
  /** A slide reference: its `id`, or its 1-based position. */
96
104
  type SlideRef = string | number;
97
105
  declare function findSlide(deck: EditableDeck, ref: SlideRef): number;
106
+ /** A short random slide id such as "s-k3f9x2" (valid for the slide `id` field). */
107
+ declare function newSlideId(taken?: ReadonlySet<string>): string;
108
+ /**
109
+ * Gives every slide without an `id` a stable random one, so comments and
110
+ * analytics can anchor to slides across edits and reorders. Idempotent;
111
+ * returns how many ids were added.
112
+ */
113
+ declare function ensureSlideIds(deck: EditableDeck): number;
114
+ /** Stable key for a slide: its id, or "#<position>" for decks saved before ids existed. */
115
+ declare function slideKey(slide: {
116
+ id?: unknown;
117
+ }, index: number): string;
98
118
  /** Inserts a slide. `position` is 1-based; omitted appends. Returns the new slide's position. */
99
119
  declare function addSlide(deck: EditableDeck, slide: Record<string, unknown>, position?: number): number;
100
120
  /** Shallow-merges fields into a slide. A field set to null is removed. Changing `layout` replaces the slide. */
@@ -127,9 +147,23 @@ interface FieldBase<K extends string> {
127
147
  default?: unknown;
128
148
  /** Suggest a textarea rather than a one-line input. */
129
149
  multiline?: boolean;
150
+ /**
151
+ * Suggests a richer widget; editors that don't know a hint can fall back to `kind`.
152
+ * - url: https link · video-url: YouTube/Vimeo link
153
+ * - chart: ChartSpec group (edit labels × series as a grid)
154
+ * - grid: list of rows of cells (table layout)
155
+ * - point: {x, y} in % of an image (hotspot picker)
156
+ */
157
+ hint?: 'url' | 'video-url' | 'chart' | 'grid' | 'point';
130
158
  }
159
+ /** Deck-level fields for a settings panel: brand overrides and presentation settings. */
160
+ declare function deckEditorFields(): {
161
+ brand: EditorField[];
162
+ settings: EditorField[];
163
+ };
131
164
  interface LayoutEditorInfo {
132
165
  name: string;
166
+ category: LayoutCategory;
133
167
  description: string;
134
168
  whenToUse: string;
135
169
  defaults: {
@@ -158,4 +192,103 @@ declare function isPlaceholderImage(src: string | undefined): boolean;
158
192
  */
159
193
  declare function blankSlide(name: string): Record<string, unknown> | undefined;
160
194
 
161
- export { AUTHORING_GUIDE, type EditableDeck, type EditorField, LOGO_SVG, type LayoutEditorInfo, PALETTE, PLACEHOLDER_IMAGE, type SlideRef, addSlide, allLayoutEditorInfo, blankSlide, commonSlideFields, deckJsonSchema, describeLayout, escapeHtml, findSlide, inline, isPlaceholderImage, layoutEditorInfo, listLayouts, moveSlide, outline, parseDeckSource, prose, removeSlide, stringifyDeck, updateSlide };
195
+ /**
196
+ * Chart specs for the `chart` layout. Authors describe data, never styling;
197
+ * this module turns a spec into an ECharts option themed from the brand, and
198
+ * renders it to static SVG (server-side rendering, no runtime JS) so charts
199
+ * work offline, in print, in Workers and in the browser alike.
200
+ *
201
+ * ECharts is imported lazily (echarts-render.ts) so decks without charts
202
+ * never load it.
203
+ */
204
+
205
+ declare const CHART_TYPES: readonly ["column", "bar", "stacked-column", "stacked-bar", "line", "area", "pie", "donut", "funnel", "gauge", "scatter", "radar"];
206
+ type ChartType = (typeof CHART_TYPES)[number];
207
+ declare const ChartSpec: z.ZodObject<{
208
+ type: z.ZodEnum<["column", "bar", "stacked-column", "stacked-bar", "line", "area", "pie", "donut", "funnel", "gauge", "scatter", "radar"]>;
209
+ labels: z.ZodArray<z.ZodString, "many">;
210
+ series: z.ZodArray<z.ZodObject<{
211
+ name: z.ZodString;
212
+ values: z.ZodArray<z.ZodNumber, "many">;
213
+ }, "strip", z.ZodTypeAny, {
214
+ values: number[];
215
+ name: string;
216
+ }, {
217
+ values: number[];
218
+ name: string;
219
+ }>, "many">;
220
+ format: z.ZodDefault<z.ZodEnum<["number", "percent", "currency", "compact"]>>;
221
+ decimals: z.ZodDefault<z.ZodNumber>;
222
+ unit: z.ZodOptional<z.ZodString>;
223
+ legend: z.ZodDefault<z.ZodEnum<["auto", "show", "hide"]>>;
224
+ labelsOnData: z.ZodDefault<z.ZodBoolean>;
225
+ highlight: z.ZodOptional<z.ZodString>;
226
+ max: z.ZodOptional<z.ZodNumber>;
227
+ }, "strict", z.ZodTypeAny, {
228
+ type: "column" | "bar" | "stacked-column" | "stacked-bar" | "line" | "area" | "pie" | "donut" | "funnel" | "gauge" | "scatter" | "radar";
229
+ labels: string[];
230
+ series: {
231
+ values: number[];
232
+ name: string;
233
+ }[];
234
+ format: "number" | "percent" | "currency" | "compact";
235
+ decimals: number;
236
+ legend: "auto" | "show" | "hide";
237
+ labelsOnData: boolean;
238
+ unit?: string | undefined;
239
+ highlight?: string | undefined;
240
+ max?: number | undefined;
241
+ }, {
242
+ type: "column" | "bar" | "stacked-column" | "stacked-bar" | "line" | "area" | "pie" | "donut" | "funnel" | "gauge" | "scatter" | "radar";
243
+ labels: string[];
244
+ series: {
245
+ values: number[];
246
+ name: string;
247
+ }[];
248
+ format?: "number" | "percent" | "currency" | "compact" | undefined;
249
+ decimals?: number | undefined;
250
+ unit?: string | undefined;
251
+ legend?: "auto" | "show" | "hide" | undefined;
252
+ labelsOnData?: boolean | undefined;
253
+ highlight?: string | undefined;
254
+ max?: number | undefined;
255
+ }>;
256
+ type ChartSpecInput = z.input<typeof ChartSpec>;
257
+ type ChartSpecValue = z.output<typeof ChartSpec>;
258
+ declare function formatValue(v: number, spec: Pick<ChartSpecValue, 'format' | 'decimals' | 'unit'>): string;
259
+ /** Builds the ECharts option for a spec. Pure; exported for tests and custom renderers. */
260
+ declare function chartOption(spec: ChartSpecValue, palette?: BrandPalette): Record<string, unknown>;
261
+ /** Renders one chart spec to an accessible, responsive SVG string. */
262
+ declare function renderChartSvg(spec: ChartSpecValue, opts?: {
263
+ width?: number;
264
+ height?: number;
265
+ palette?: BrandPalette;
266
+ title?: string;
267
+ }): Promise<string>;
268
+ /** Plain-language summary for screen readers. */
269
+ declare function describeChart(spec: ChartSpecValue): string;
270
+
271
+ /** YouTube / Vimeo URL parsing for the video layout. */
272
+ interface VideoRef {
273
+ provider: 'youtube' | 'vimeo';
274
+ id: string;
275
+ /** Canonical watch URL (used for QR codes and "open" links). */
276
+ watchUrl: string;
277
+ /** Poster image URL, when the provider exposes a predictable one. */
278
+ poster?: string;
279
+ }
280
+ declare function parseVideoUrl(url: string): VideoRef | undefined;
281
+ interface EmbedOptions {
282
+ start?: number;
283
+ end?: number;
284
+ muted?: boolean;
285
+ captions?: boolean;
286
+ autoplay?: boolean;
287
+ }
288
+ /** Privacy-friendly player URL (youtube-nocookie, Vimeo with dnt). */
289
+ declare function videoEmbedUrl(ref: VideoRef, o: EmbedOptions): string;
290
+
291
+ /** CSP source for the inline presenter runtime: script-src '<hash>'. */
292
+ declare const RUNTIME_SCRIPT_HASH = "sha256-oKFaVecqTMnaoeBzefA8f7poigISWe5hotztD1XgJP8=";
293
+
294
+ export { AUTHORING_GUIDE, BrandPalette, CHART_TYPES, ChartSpec, type ChartSpecInput, type ChartSpecValue, type ChartType, type EditableDeck, type EditorField, LOGO_SVG, LayoutCategory, type LayoutEditorInfo, PALETTE, PLACEHOLDER_IMAGE, RUNTIME_SCRIPT_HASH, type SlideRef, type VideoRef, addSlide, allLayoutEditorInfo, blankSlide, chartOption, commonSlideFields, deckEditorFields, deckJsonSchema, describeChart, describeLayout, ensureSlideIds, escapeHtml, findSlide, formatValue, inline, isPlaceholderImage, layoutEditorInfo, listLayouts, logoLockup, moveSlide, newSlideId, outline, parseDeckSource, parseVideoUrl, prose, removeSlide, renderChartSvg, slideKey, stringifyDeck, updateSlide, videoEmbedUrl };
package/dist/index.js CHANGED
@@ -1,79 +1,129 @@
1
- import {
2
- addSlide,
3
- findSlide,
4
- moveSlide,
5
- outline,
6
- removeSlide,
7
- updateSlide
8
- } from "./chunk-VONVGYRL.js";
9
1
  import {
10
2
  AUTHORING_GUIDE,
11
3
  deckJsonSchema,
12
4
  describeLayout,
13
5
  listLayouts
14
- } from "./chunk-33EPZMT6.js";
6
+ } from "./chunk-YI7A4EWX.js";
15
7
  import {
8
+ BrandPaletteSchema,
9
+ BrandProfileSchema,
10
+ CHART_TYPES,
11
+ CLASSIC_HOMES_BRAND,
12
+ CLASSIC_HOMES_PALETTE,
13
+ ChartSpec,
14
+ DeckBrandSchema,
16
15
  DeckError,
17
16
  DeckSchema,
17
+ DeckSettingsSchema,
18
18
  LAYOUTS,
19
+ LAYOUT_CATEGORIES,
20
+ LAYOUT_CATEGORY_ORDER,
19
21
  LOGO_SVG,
20
22
  PALETTE,
21
23
  PLACEHOLDER_IMAGE,
22
24
  PREVIEW_SIZE,
23
25
  SlideSchema,
26
+ addSlide,
24
27
  allLayoutEditorInfo,
25
28
  blankSlide,
29
+ chartOption,
30
+ checkPalette,
26
31
  collectImageSources,
27
32
  commonSlideFields,
33
+ deckEditorFields,
34
+ describeChart,
35
+ ensureSlideIds,
28
36
  escapeHtml,
29
37
  extractDeckSource,
38
+ findSlide,
30
39
  formatIssues,
31
40
  formatPath,
41
+ formatValue,
32
42
  getLayout,
33
43
  inline,
34
44
  isPlaceholderImage,
35
45
  layoutEditorInfo,
46
+ logoLockup,
47
+ moveSlide,
48
+ newSlideId,
49
+ outline,
36
50
  parseDeckSource,
51
+ parseVideoUrl,
37
52
  prose,
53
+ removeSlide,
54
+ renderChartSvg,
38
55
  renderDeck,
56
+ resolveBrand,
57
+ slideKey,
39
58
  stringifyDeck,
40
- validateDeck
41
- } from "./chunk-7FROOJCY.js";
59
+ updateSlide,
60
+ validateBrandProfile,
61
+ validateDeck,
62
+ videoEmbedUrl
63
+ } from "./chunk-GXGDPBAR.js";
64
+
65
+ // src/generated/runtime-hash.ts
66
+ var RUNTIME_SCRIPT_HASH = "sha256-oKFaVecqTMnaoeBzefA8f7poigISWe5hotztD1XgJP8=";
42
67
  export {
43
68
  AUTHORING_GUIDE,
69
+ BrandPaletteSchema,
70
+ BrandProfileSchema,
71
+ CHART_TYPES,
72
+ CLASSIC_HOMES_BRAND,
73
+ CLASSIC_HOMES_PALETTE,
74
+ ChartSpec,
75
+ DeckBrandSchema,
44
76
  DeckError,
45
77
  DeckSchema,
78
+ DeckSettingsSchema,
46
79
  LAYOUTS,
80
+ LAYOUT_CATEGORIES,
81
+ LAYOUT_CATEGORY_ORDER,
47
82
  LOGO_SVG,
48
83
  PALETTE,
49
84
  PLACEHOLDER_IMAGE,
50
85
  PREVIEW_SIZE,
86
+ RUNTIME_SCRIPT_HASH,
51
87
  SlideSchema,
52
88
  addSlide,
53
89
  allLayoutEditorInfo,
54
90
  blankSlide,
91
+ chartOption,
92
+ checkPalette,
55
93
  collectImageSources,
56
94
  commonSlideFields,
95
+ deckEditorFields,
57
96
  deckJsonSchema,
97
+ describeChart,
58
98
  describeLayout,
99
+ ensureSlideIds,
59
100
  escapeHtml,
60
101
  extractDeckSource,
61
102
  findSlide,
62
103
  formatIssues,
63
104
  formatPath,
105
+ formatValue,
64
106
  getLayout,
65
107
  inline,
66
108
  isPlaceholderImage,
67
109
  layoutEditorInfo,
68
110
  listLayouts,
111
+ logoLockup,
69
112
  moveSlide,
113
+ newSlideId,
70
114
  outline,
71
115
  parseDeckSource,
116
+ parseVideoUrl,
72
117
  prose,
73
118
  removeSlide,
119
+ renderChartSvg,
74
120
  renderDeck,
121
+ resolveBrand,
122
+ slideKey,
75
123
  stringifyDeck,
76
124
  updateSlide,
77
- validateDeck
125
+ validateBrandProfile,
126
+ validateDeck,
127
+ videoEmbedUrl
78
128
  };
79
129
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
1
+ {"version":3,"sources":["../src/generated/runtime-hash.ts"],"sourcesContent":["// Generated by scripts/embed-fonts.ts. Do not edit.\n/** CSP source for the inline presenter runtime: script-src '<hash>'. */\nexport const RUNTIME_SCRIPT_HASH = 'sha256-oKFaVecqTMnaoeBzefA8f7poigISWe5hotztD1XgJP8=';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEO,IAAM,sBAAsB;","names":[]}
@@ -1,25 +1,24 @@
1
- import {
2
- addSlide,
3
- moveSlide,
4
- outline,
5
- removeSlide,
6
- updateSlide
7
- } from "./chunk-VONVGYRL.js";
8
1
  import {
9
2
  AUTHORING_GUIDE,
10
3
  describeLayout,
11
4
  listLayouts
12
- } from "./chunk-33EPZMT6.js";
5
+ } from "./chunk-YI7A4EWX.js";
13
6
  import {
14
7
  buildDeckFile,
8
+ confinedPath,
15
9
  loadDeckFile,
16
10
  saveDeckFile
17
- } from "./chunk-MYY5GZC2.js";
11
+ } from "./chunk-2IPLZ3SA.js";
18
12
  import {
13
+ addSlide,
19
14
  formatIssues,
15
+ moveSlide,
16
+ outline,
17
+ removeSlide,
20
18
  stringifyDeck,
19
+ updateSlide,
21
20
  validateDeck
22
- } from "./chunk-7FROOJCY.js";
21
+ } from "./chunk-GXGDPBAR.js";
23
22
 
24
23
  // src/node/mcp.ts
25
24
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -41,7 +40,7 @@ function createSlidesMcpServer(rootDir = process.cwd()) {
41
40
  throw new Error(`Path "${p}" is outside the deck root ${root}.`);
42
41
  if (!/\.(ya?ml|json)$/i.test(abs))
43
42
  throw new Error("Deck files must end in .yaml, .yml or .json.");
44
- return abs;
43
+ return confinedPath(root, abs);
45
44
  }
46
45
  async function load(p) {
47
46
  const abs = safePath(p);
@@ -61,7 +60,7 @@ Fix the fields and try again.`,
61
60
  true
62
61
  );
63
62
  }
64
- await saveDeckFile(abs, deck);
63
+ await saveDeckFile(confinedPath(root, abs), deck);
65
64
  const warnings = v.issues.filter((i) => i.level === "warning");
66
65
  return text(
67
66
  `${done}
@@ -252,7 +251,11 @@ ${formatIssues(v.issues)}`,
252
251
  throw new Error("out must be an .html path inside the deck root.");
253
252
  }
254
253
  const warnings = [];
255
- const r = await buildDeckFile(abs, { out: outAbs, onWarning: (w) => warnings.push(w) });
254
+ const r = await buildDeckFile(abs, {
255
+ out: outAbs,
256
+ rootDir: root,
257
+ onWarning: (w) => warnings.push(w)
258
+ });
256
259
  return text(
257
260
  `Built ${relative(root, r.out)} \u2014 ${r.slideCount} slides, ${(r.bytes / 1024 / 1024).toFixed(2)} MB. Open it in a browser to present (N = notes, F = fullscreen).${warnings.length ? `
258
261
 
@@ -317,4 +320,4 @@ export {
317
320
  createSlidesMcpServer,
318
321
  runStdio
319
322
  };
320
- //# sourceMappingURL=mcp-DGMQTIQI.js.map
323
+ //# sourceMappingURL=mcp-PUA45IS7.js.map