@kadal/deck 0.0.0-stage → 0.1.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 (157) hide show
  1. package/LICENSE +661 -0
  2. package/LICENSING.md +40 -0
  3. package/README.md +170 -2
  4. package/dist/_virtual/_rolldown/runtime.js +13 -0
  5. package/dist/audience/index.d.ts +14 -0
  6. package/dist/audience/index.js +680 -0
  7. package/dist/backgrounds/index.d.ts +65 -0
  8. package/dist/backgrounds/index.js +604 -0
  9. package/dist/backgrounds/shared.d.ts +7 -0
  10. package/dist/backgrounds/shared.js +89 -0
  11. package/dist/cli/bin.d.ts +1 -0
  12. package/dist/cli/bin.js +8 -0
  13. package/dist/cli/host.js +111 -0
  14. package/dist/cli/index.d.ts +4 -0
  15. package/dist/cli/index.js +140 -0
  16. package/dist/cli/login.js +56 -0
  17. package/dist/cli/publish.js +159 -0
  18. package/dist/cli/vite.js +39 -0
  19. package/dist/compiler/code-meta.d.ts +33 -0
  20. package/dist/compiler/code-meta.js +81 -0
  21. package/dist/compiler/index.d.ts +8 -0
  22. package/dist/compiler/index.js +8 -0
  23. package/dist/compiler/magic-move.d.ts +26 -0
  24. package/dist/compiler/magic-move.js +33 -0
  25. package/dist/compiler/plain-text.js +7 -0
  26. package/dist/compiler/rehype-code.d.ts +18 -0
  27. package/dist/compiler/rehype-code.js +66 -0
  28. package/dist/compiler/rehype-glossary.d.ts +35 -0
  29. package/dist/compiler/rehype-glossary.js +211 -0
  30. package/dist/compiler/slots.d.ts +3 -0
  31. package/dist/compiler/slots.js +36 -0
  32. package/dist/compiler/split.d.ts +63 -0
  33. package/dist/compiler/split.js +195 -0
  34. package/dist/components/Background.d.ts +15 -0
  35. package/dist/components/Background.js +24 -0
  36. package/dist/components/Click.d.ts +54 -0
  37. package/dist/components/Click.js +129 -0
  38. package/dist/components/Code.d.ts +27 -0
  39. package/dist/components/Code.js +124 -0
  40. package/dist/components/Motion.d.ts +25 -0
  41. package/dist/components/Motion.js +45 -0
  42. package/dist/components/Sketch.d.ts +44 -0
  43. package/dist/components/Sketch.js +141 -0
  44. package/dist/components/Term.d.ts +63 -0
  45. package/dist/components/Term.js +492 -0
  46. package/dist/components/bling.d.ts +75 -0
  47. package/dist/components/bling.js +146 -0
  48. package/dist/components/content.d.ts +232 -0
  49. package/dist/components/content.js +637 -0
  50. package/dist/components/index.d.ts +10 -0
  51. package/dist/components/index.js +88 -0
  52. package/dist/core/clicks.d.ts +45 -0
  53. package/dist/core/clicks.js +75 -0
  54. package/dist/core/hash.d.ts +33 -0
  55. package/dist/core/hash.js +49 -0
  56. package/dist/core/keyboard.d.ts +17 -0
  57. package/dist/core/keyboard.js +198 -0
  58. package/dist/core/layout.d.ts +16 -0
  59. package/dist/core/layout.js +25 -0
  60. package/dist/core/model.d.ts +259 -0
  61. package/dist/core/model.js +377 -0
  62. package/dist/core/navigation.d.ts +74 -0
  63. package/dist/core/navigation.js +244 -0
  64. package/dist/core/poster.js +30 -0
  65. package/dist/core/store.d.ts +20 -0
  66. package/dist/core/store.js +64 -0
  67. package/dist/core/touch.js +27 -0
  68. package/dist/draw/DrawingLayer.js +315 -0
  69. package/dist/draw/strokes.js +188 -0
  70. package/dist/export/index.d.ts +68 -0
  71. package/dist/export/index.js +251 -0
  72. package/dist/export/run.js +81 -0
  73. package/dist/glossary/model.d.ts +81 -0
  74. package/dist/glossary/model.js +156 -0
  75. package/dist/glossary/parse.d.ts +27 -0
  76. package/dist/glossary/parse.js +90 -0
  77. package/dist/index.d.ts +31 -0
  78. package/dist/index.js +30 -0
  79. package/dist/live/LiveProvider.d.ts +20 -0
  80. package/dist/live/LiveProvider.js +450 -0
  81. package/dist/live/analytics.d.ts +15 -0
  82. package/dist/live/analytics.js +103 -0
  83. package/dist/live/client.d.ts +51 -0
  84. package/dist/live/client.js +193 -0
  85. package/dist/live/components.d.ts +64 -0
  86. package/dist/live/components.js +471 -0
  87. package/dist/live/context.d.ts +15 -0
  88. package/dist/live/context.js +28 -0
  89. package/dist/live/index.d.ts +7 -0
  90. package/dist/live/index.js +7 -0
  91. package/dist/live/protocol.d.ts +284 -0
  92. package/dist/live/protocol.js +42 -0
  93. package/dist/live/turnstile.js +49 -0
  94. package/dist/presenter/index.d.ts +20 -0
  95. package/dist/presenter/index.js +1211 -0
  96. package/dist/react/AutoAnimate.js +92 -0
  97. package/dist/react/Backgrounds.d.ts +11 -0
  98. package/dist/react/Backgrounds.js +154 -0
  99. package/dist/react/ConfettiLayer.js +85 -0
  100. package/dist/react/Deck.d.ts +35 -0
  101. package/dist/react/Deck.js +983 -0
  102. package/dist/react/Frame.d.ts +16 -0
  103. package/dist/react/Frame.js +174 -0
  104. package/dist/react/Hints.d.ts +3 -0
  105. package/dist/react/Hints.js +201 -0
  106. package/dist/react/Lightbox.js +54 -0
  107. package/dist/react/Overview.js +93 -0
  108. package/dist/react/PrintView.js +128 -0
  109. package/dist/react/ScrollView.js +128 -0
  110. package/dist/react/SlideFrame.d.ts +51 -0
  111. package/dist/react/SlideFrame.js +323 -0
  112. package/dist/react/Stage.js +151 -0
  113. package/dist/react/ThemeMenu.d.ts +5 -0
  114. package/dist/react/ThemeMenu.js +124 -0
  115. package/dist/react/Zoom.js +23 -0
  116. package/dist/react/chrome.js +339 -0
  117. package/dist/react/context.d.ts +165 -0
  118. package/dist/react/context.js +19 -0
  119. package/dist/react/hooks.d.ts +33 -0
  120. package/dist/react/hooks.js +70 -0
  121. package/dist/react/layouts.d.ts +21 -0
  122. package/dist/react/layouts.js +104 -0
  123. package/dist/react/mdx.d.ts +4 -0
  124. package/dist/react/mdx.js +33 -0
  125. package/dist/react/terms.d.ts +6 -0
  126. package/dist/react/terms.js +66 -0
  127. package/dist/renderers/Island.d.ts +57 -0
  128. package/dist/renderers/Island.js +223 -0
  129. package/dist/renderers/contract.d.ts +173 -0
  130. package/dist/renderers/contract.js +152 -0
  131. package/dist/renderers/index.d.ts +12 -0
  132. package/dist/renderers/index.js +16 -0
  133. package/dist/renderers/react.d.ts +16 -0
  134. package/dist/renderers/react.js +32 -0
  135. package/dist/renderers/web-components.d.ts +4 -0
  136. package/dist/renderers/web-components.js +103 -0
  137. package/dist/sketch/Canvas.js +49 -0
  138. package/dist/sketch/dsl.d.ts +131 -0
  139. package/dist/sketch/dsl.js +281 -0
  140. package/dist/sketch/index.d.ts +4 -0
  141. package/dist/sketch/index.js +3 -0
  142. package/dist/sketch/palette.d.ts +21 -0
  143. package/dist/sketch/palette.js +66 -0
  144. package/dist/sketch/render.d.ts +43 -0
  145. package/dist/sketch/render.js +198 -0
  146. package/dist/themes/base.css +1704 -0
  147. package/dist/themes/bling.css +592 -0
  148. package/dist/themes/glossary.css +317 -0
  149. package/dist/themes/live.css +1557 -0
  150. package/dist/themes/minimal.css +83 -0
  151. package/dist/themes/rla.css +92 -0
  152. package/dist/themes/sketch.css +121 -0
  153. package/dist/themes/stack.css +62 -0
  154. package/dist/vite/index.d.ts +15 -0
  155. package/dist/vite/index.js +320 -0
  156. package/package.json +159 -4
  157. package/tools/verify-island.mjs +150 -0
@@ -0,0 +1,131 @@
1
+ import { SketchTone } from "./palette.js";
2
+ //#region src/sketch/dsl.d.ts
3
+ /**
4
+ * ─────────────────────────────────────────────────────────────────────────────
5
+ * SKETCH DSL — draw an Excalidraw diagram in code
6
+ * ─────────────────────────────────────────────────────────────────────────────
7
+ *
8
+ * const s = sketch({ width: 1280, height: 640 });
9
+ * s.region("edge", { x: 440, y: 40, w: 420, h: 560, title: "Cloudflare Workers", tone: "accent" });
10
+ * s.node("w", { x: 470, y: 100, w: 360, h: 90, label: "Next.js 16\nSSR + OG image", tone: "accent", region: "edge" });
11
+ * s.node("d1", { x: 470, y: 480, w: 165, h: 90, label: "D1", shape: "cylinder", tone: "pink", region: ["edge", "report"] });
12
+ * s.arrow("w", "d1", { label: "only what is on the map" });
13
+ * export const architecture = s.build();
14
+ *
15
+ * `build()` returns Excalidraw *element skeletons* (the simplified shape the package's
16
+ * `convertToExcalidrawElements` understands) plus the region of every element, so a slide can
17
+ * light one region up per click. This file imports nothing from Excalidraw: it is plain data,
18
+ * safe to ship in a deck's main bundle; the 2.7 MB package loads only when a sketch renders.
19
+ *
20
+ * Coordinates are pixels in the sketch's own space. Regions are named areas: a dashed, tinted
21
+ * rectangle with a title when they have geometry, or purely a label (`region: "report"` on
22
+ * nodes and arrows) for a path that crosses the drawn boxes.
23
+ * ─────────────────────────────────────────────────────────────────────────────
24
+ */
25
+ export type SketchShape = "box" | "pill" | "ellipse" | "diamond" | "cylinder" | "note" | "text";
26
+ export type SketchFont = "hand" | "normal" | "code";
27
+ export type SketchFill = "solid" | "hachure" | "cross-hatch" | "none";
28
+ type Side = "left" | "right" | "top" | "bottom";
29
+ export interface NodeInput {
30
+ x: number;
31
+ y: number;
32
+ w?: number;
33
+ h?: number;
34
+ label?: string;
35
+ shape?: SketchShape;
36
+ tone?: SketchTone;
37
+ /** Text colour, when not the ink. */
38
+ textTone?: SketchTone;
39
+ fill?: SketchFill;
40
+ dashed?: boolean;
41
+ font?: SketchFont;
42
+ size?: number;
43
+ /** 0 = ruler-straight, 1 = hand-drawn (default), 2 = wobbly. */
44
+ rough?: 0 | 1 | 2;
45
+ strokeWidth?: number;
46
+ region?: string | string[];
47
+ align?: "left" | "center" | "right";
48
+ opacity?: number;
49
+ }
50
+ export interface ArrowInput {
51
+ label?: string;
52
+ tone?: SketchTone;
53
+ dashed?: boolean;
54
+ dotted?: boolean;
55
+ /** A plain line instead of an arrow. */
56
+ line?: boolean;
57
+ /** Arrowheads at both ends. */
58
+ both?: boolean;
59
+ from?: Side;
60
+ to?: Side;
61
+ /** Extra corner points (absolute), for an elbow or a detour. */
62
+ via?: Array<[number, number]>;
63
+ /** Regions this arrow belongs to, on top of those of its two ends. */
64
+ region?: string | string[];
65
+ strokeWidth?: number;
66
+ size?: number;
67
+ font?: SketchFont;
68
+ /** Shift the anchor along its side, −1 … 1 (0 is the middle). */
69
+ fromShift?: number;
70
+ toShift?: number;
71
+ }
72
+ export interface RegionInput {
73
+ x?: number;
74
+ y?: number;
75
+ w?: number;
76
+ h?: number;
77
+ title?: string;
78
+ tone?: SketchTone;
79
+ dashed?: boolean;
80
+ fill?: boolean;
81
+ size?: number;
82
+ /** Where the title sits; "right" keeps it clear of arrows arriving at the left. */
83
+ titleAt?: "left" | "right";
84
+ }
85
+ export interface TextInput {
86
+ x: number;
87
+ y: number;
88
+ text: string;
89
+ size?: number;
90
+ tone?: SketchTone;
91
+ font?: SketchFont;
92
+ align?: "left" | "center" | "right";
93
+ region?: string | string[];
94
+ w?: number;
95
+ }
96
+ /** What a rendered sketch knows about a region, for the step captions and the canvas. */
97
+ export interface SketchRegionMeta {
98
+ id: string;
99
+ title?: string;
100
+ tone: SketchTone;
101
+ }
102
+ export interface SketchScene {
103
+ kind: "sketch";
104
+ width: number;
105
+ height: number;
106
+ /** Excalidraw element skeletons, in draw order. Typed loosely: the DSL never imports Excalidraw. */
107
+ elements: Array<Record<string, unknown>>;
108
+ /** Element id → the regions it belongs to. */
109
+ regions: Record<string, string[]>;
110
+ regionMeta: SketchRegionMeta[];
111
+ /** The fonts used (Excalidraw ids), so the renderer can warm them up before measuring text. */
112
+ fonts: number[];
113
+ }
114
+ export declare function sketch(opts?: {
115
+ width?: number;
116
+ height?: number;
117
+ font?: SketchFont;
118
+ size?: number;
119
+ rough?: 0 | 1 | 2;
120
+ }): {
121
+ /** A named area. With geometry it is drawn as a dashed, tinted rectangle with a title. */
122
+ region(id: string, input?: RegionInput): /*elided*/ any;
123
+ /** A shape with a label. `shape: "text"` is a bare label with no box. */
124
+ node(id: string, input: NodeInput): /*elided*/ any;
125
+ /** Free text. */
126
+ text(id: string, input: TextInput): /*elided*/ any;
127
+ /** An arrow between two nodes, anchored on the facing sides unless told otherwise. */
128
+ arrow(from: string, to: string, input?: ArrowInput): /*elided*/ any;
129
+ build(): SketchScene;
130
+ };
131
+ //#endregion
@@ -0,0 +1,281 @@
1
+ import { PALETTE } from "./palette.js";
2
+ //#region src/sketch/dsl.ts
3
+ /** Excalidraw's font ids (FONT_FAMILY in its constants). */
4
+ const FONT = {
5
+ hand: 5,
6
+ normal: 6,
7
+ code: 3
8
+ };
9
+ const list = (r) => r == null ? [] : Array.isArray(r) ? r : [r];
10
+ function sketch(opts = {}) {
11
+ const width = opts.width ?? 1280;
12
+ const height = opts.height ?? 640;
13
+ const baseFont = opts.font ?? "hand";
14
+ const baseSize = opts.size ?? 20;
15
+ const baseRough = opts.rough ?? 1;
16
+ const elements = [];
17
+ const regions = {};
18
+ const regionMeta = [];
19
+ const geoms = /* @__PURE__ */ new Map();
20
+ const fonts = /* @__PURE__ */ new Set();
21
+ let seq = 0;
22
+ const uid = (prefix) => `${prefix}-${++seq}`;
23
+ const tag = (id, r) => {
24
+ const rs = list(r);
25
+ if (rs.length) regions[id] = [.../* @__PURE__ */ new Set([...regions[id] ?? [], ...rs])];
26
+ };
27
+ const roundness = (shape) => shape === "box" || shape === "cylinder" ? { type: 3 } : shape === "pill" || shape === "ellipse" || shape === "diamond" ? { type: 2 } : null;
28
+ const api = {
29
+ /** A named area. With geometry it is drawn as a dashed, tinted rectangle with a title. */
30
+ region(id, input = {}) {
31
+ const tone = input.tone ?? "neutral";
32
+ regionMeta.push({
33
+ id,
34
+ title: input.title,
35
+ tone
36
+ });
37
+ if (input.x != null && input.y != null && input.w && input.h) {
38
+ const rect = uid("region");
39
+ elements.push({
40
+ id: rect,
41
+ type: "rectangle",
42
+ x: input.x,
43
+ y: input.y,
44
+ width: input.w,
45
+ height: input.h,
46
+ strokeColor: PALETTE[tone].stroke,
47
+ backgroundColor: input.fill === false ? "transparent" : PALETTE[tone].fill,
48
+ fillStyle: "solid",
49
+ strokeStyle: input.dashed === false ? "solid" : "dashed",
50
+ strokeWidth: 1,
51
+ roughness: baseRough,
52
+ roundness: { type: 3 },
53
+ opacity: 70
54
+ });
55
+ tag(rect, id);
56
+ if (input.title) {
57
+ const size = input.size ?? Math.round(baseSize * .8);
58
+ const t = uid("region-title");
59
+ const estimate = input.title.length * size * .56;
60
+ const right = input.titleAt === "right";
61
+ elements.push({
62
+ id: t,
63
+ type: "text",
64
+ x: right ? input.x + input.w - 16 - estimate : input.x + 16,
65
+ y: input.y + 10,
66
+ text: input.title,
67
+ fontSize: size,
68
+ fontFamily: FONT[baseFont],
69
+ strokeColor: PALETTE[tone].stroke,
70
+ textAlign: right ? "right" : "left"
71
+ });
72
+ fonts.add(FONT[baseFont]);
73
+ tag(t, id);
74
+ }
75
+ }
76
+ return api;
77
+ },
78
+ /** A shape with a label. `shape: "text"` is a bare label with no box. */
79
+ node(id, input) {
80
+ const shape = input.shape ?? "box";
81
+ const tone = input.tone ?? "ink";
82
+ const font = FONT[input.font ?? baseFont];
83
+ fonts.add(font);
84
+ const size = input.size ?? baseSize;
85
+ const w = input.w ?? 200;
86
+ const h = input.h ?? 70;
87
+ geoms.set(id, {
88
+ x: input.x,
89
+ y: input.y,
90
+ w,
91
+ h,
92
+ shape
93
+ });
94
+ const textColor = PALETTE[input.textTone ?? "ink"].stroke;
95
+ const common = {
96
+ strokeColor: PALETTE[tone].stroke,
97
+ backgroundColor: input.fill === "none" ? "transparent" : PALETTE[tone].fill,
98
+ fillStyle: input.fill && input.fill !== "none" ? input.fill : "solid",
99
+ strokeStyle: input.dashed ? "dashed" : "solid",
100
+ strokeWidth: input.strokeWidth ?? (shape === "note" ? 1 : 2),
101
+ roughness: input.rough ?? baseRough,
102
+ opacity: input.opacity ?? 100
103
+ };
104
+ if (shape === "text") {
105
+ elements.push({
106
+ id,
107
+ type: "text",
108
+ x: input.x,
109
+ y: input.y,
110
+ width: w,
111
+ text: input.label ?? "",
112
+ fontSize: size,
113
+ fontFamily: font,
114
+ strokeColor: PALETTE[input.textTone ?? tone].stroke,
115
+ textAlign: input.align ?? "center"
116
+ });
117
+ tag(id, input.region);
118
+ return api;
119
+ }
120
+ const label = input.label ? {
121
+ text: input.label,
122
+ fontSize: size,
123
+ fontFamily: font,
124
+ strokeColor: textColor,
125
+ textAlign: input.align ?? "center",
126
+ verticalAlign: "middle"
127
+ } : void 0;
128
+ if (shape === "cylinder") {
129
+ const lid = Math.max(14, Math.round(h * .22));
130
+ elements.push({
131
+ id,
132
+ type: "rectangle",
133
+ x: input.x,
134
+ y: input.y + lid / 2,
135
+ width: w,
136
+ height: h - lid / 2,
137
+ ...common,
138
+ roundness: { type: 3 },
139
+ label
140
+ });
141
+ const top = `${id}-lid`;
142
+ elements.push({
143
+ id: top,
144
+ type: "ellipse",
145
+ x: input.x,
146
+ y: input.y,
147
+ width: w,
148
+ height: lid,
149
+ ...common,
150
+ roundness: { type: 2 }
151
+ });
152
+ tag(id, input.region);
153
+ tag(top, input.region);
154
+ return api;
155
+ }
156
+ const type = shape === "ellipse" || shape === "pill" ? "ellipse" : shape === "diamond" ? "diamond" : "rectangle";
157
+ elements.push({
158
+ id,
159
+ type,
160
+ x: input.x,
161
+ y: input.y,
162
+ width: w,
163
+ height: h,
164
+ ...common,
165
+ ...shape === "note" ? {
166
+ fillStyle: "solid",
167
+ roughness: input.rough ?? 0
168
+ } : {},
169
+ roundness: shape === "note" ? null : roundness(shape),
170
+ label
171
+ });
172
+ tag(id, input.region);
173
+ return api;
174
+ },
175
+ /** Free text. */
176
+ text(id, input) {
177
+ const font = FONT[input.font ?? baseFont];
178
+ fonts.add(font);
179
+ elements.push({
180
+ id,
181
+ type: "text",
182
+ x: input.x,
183
+ y: input.y,
184
+ ...input.w ? { width: input.w } : {},
185
+ text: input.text,
186
+ fontSize: input.size ?? baseSize,
187
+ fontFamily: font,
188
+ strokeColor: PALETTE[input.tone ?? "ink"].stroke,
189
+ textAlign: input.align ?? "left"
190
+ });
191
+ tag(id, input.region);
192
+ return api;
193
+ },
194
+ /** An arrow between two nodes, anchored on the facing sides unless told otherwise. */
195
+ arrow(from, to, input = {}) {
196
+ const a = geoms.get(from);
197
+ const b = geoms.get(to);
198
+ if (!a || !b) throw new Error(`sketch: arrow ${from} → ${to} names an unknown node`);
199
+ const [fromSide, toSide] = pickSides(a, b, input.from, input.to);
200
+ const p0 = anchor(a, fromSide, input.fromShift ?? 0);
201
+ const p1 = anchor(b, toSide, input.toShift ?? 0);
202
+ const pts = [
203
+ p0,
204
+ ...input.via ?? [],
205
+ p1
206
+ ];
207
+ const x = pts[0][0];
208
+ const y = pts[0][1];
209
+ const points = pts.map(([px, py]) => [px - x, py - y]);
210
+ const id = uid("arrow");
211
+ const tone = input.tone ?? "ink";
212
+ const font = FONT[input.font ?? baseFont];
213
+ if (input.label) fonts.add(font);
214
+ elements.push({
215
+ id,
216
+ type: input.line ? "line" : "arrow",
217
+ x,
218
+ y,
219
+ points,
220
+ strokeColor: PALETTE[tone].stroke,
221
+ strokeStyle: input.dotted ? "dotted" : input.dashed ? "dashed" : "solid",
222
+ strokeWidth: input.strokeWidth ?? 2,
223
+ roughness: baseRough,
224
+ roundness: pts.length > 2 ? { type: 2 } : null,
225
+ ...input.line ? {} : {
226
+ endArrowhead: "arrow",
227
+ startArrowhead: input.both ? "arrow" : null
228
+ },
229
+ ...input.label ? { label: {
230
+ text: input.label,
231
+ fontSize: input.size ?? Math.round(baseSize * .75),
232
+ fontFamily: font,
233
+ strokeColor: PALETTE[tone === "ink" ? "muted" : tone].stroke
234
+ } } : {}
235
+ });
236
+ const rs = [
237
+ ...list(input.region),
238
+ ...regions[from] ?? [],
239
+ ...regions[to] ?? []
240
+ ];
241
+ tag(id, rs);
242
+ return api;
243
+ },
244
+ build() {
245
+ return {
246
+ kind: "sketch",
247
+ width,
248
+ height,
249
+ elements: elements.map((e) => ({ ...e })),
250
+ regions: { ...regions },
251
+ regionMeta: [...regionMeta],
252
+ fonts: [...fonts]
253
+ };
254
+ }
255
+ };
256
+ return api;
257
+ }
258
+ function pickSides(a, b, from, to) {
259
+ const acx = a.x + a.w / 2;
260
+ const acy = a.y + a.h / 2;
261
+ const bcx = b.x + b.w / 2;
262
+ const bcy = b.y + b.h / 2;
263
+ const dx = bcx - acx;
264
+ const dy = bcy - acy;
265
+ const horizontal = Math.abs(dx) > Math.abs(dy);
266
+ return [from ?? (horizontal ? dx > 0 ? "right" : "left" : dy > 0 ? "bottom" : "top"), to ?? (horizontal ? dx > 0 ? "left" : "right" : dy > 0 ? "top" : "bottom")];
267
+ }
268
+ /** A point on a node's side, a few pixels off the stroke so the arrowhead breathes. */
269
+ function anchor(g, side, shift) {
270
+ const gap = 6;
271
+ const sx = shift * g.w / 2;
272
+ const sy = shift * g.h / 2;
273
+ switch (side) {
274
+ case "left": return [g.x - gap, g.y + g.h / 2 + sy];
275
+ case "right": return [g.x + g.w + gap, g.y + g.h / 2 + sy];
276
+ case "top": return [g.x + g.w / 2 + sx, g.y - gap];
277
+ case "bottom": return [g.x + g.w / 2 + sx, g.y + g.h + gap];
278
+ }
279
+ }
280
+ //#endregion
281
+ export { sketch };
@@ -0,0 +1,4 @@
1
+ import { PALETTE, SketchTone } from "./palette.js";
2
+ import { ArrowInput, NodeInput, RegionInput, SketchFill, SketchFont, SketchRegionMeta, SketchScene, SketchShape, TextInput, sketch } from "./dsl.js";
3
+ import { ExcalidrawFile, SketchInput } from "./render.js";
4
+ export { type ArrowInput, type ExcalidrawFile, type NodeInput, PALETTE, type RegionInput, type SketchFill, type SketchFont, type SketchInput, type SketchRegionMeta, type SketchScene, type SketchShape, type SketchTone, type TextInput, sketch };
@@ -0,0 +1,3 @@
1
+ import { PALETTE } from "./palette.js";
2
+ import { sketch } from "./dsl.js";
3
+ export { PALETTE, sketch };
@@ -0,0 +1,21 @@
1
+ //#region src/sketch/palette.d.ts
2
+ /**
3
+ * ─────────────────────────────────────────────────────────────────────────────
4
+ * SKETCH PALETTE — the colours a sketch is drawn with, and what they become on a slide
5
+ * ─────────────────────────────────────────────────────────────────────────────
6
+ *
7
+ * Excalidraw bakes literal colours into its SVG. A deck has themes and a light/dark scheme, so
8
+ * every sketch is drawn with this fixed palette and, once exported, each literal is rewritten
9
+ * to the theme token it stands for (`--deck-fg`, `--tone-accent`, …). The literals are picked
10
+ * to look right on their own too: the interactive canvas shows them as they are.
11
+ *
12
+ * Every entry is unique on purpose — the rewrite is a lookup by hex.
13
+ * ─────────────────────────────────────────────────────────────────────────────
14
+ */
15
+ export type SketchTone = "ink" | "muted" | "accent" | "accent-2" | "good" | "warn" | "bad" | "info" | "neutral" | "purple" | "pink" | "amber" | "teal";
16
+ export interface TonePair {
17
+ stroke: string;
18
+ fill: string;
19
+ }
20
+ export declare const PALETTE: Record<SketchTone, TonePair>;
21
+ //#endregion
@@ -0,0 +1,66 @@
1
+ //#region src/sketch/palette.ts
2
+ const PALETTE = {
3
+ ink: {
4
+ stroke: "#1e1e1e",
5
+ fill: "#f8f9fa"
6
+ },
7
+ muted: {
8
+ stroke: "#6b7280",
9
+ fill: "#f3f4f6"
10
+ },
11
+ accent: {
12
+ stroke: "#059669",
13
+ fill: "#d1fae5"
14
+ },
15
+ "accent-2": {
16
+ stroke: "#0891b2",
17
+ fill: "#cffafe"
18
+ },
19
+ good: {
20
+ stroke: "#16a34a",
21
+ fill: "#dcfce7"
22
+ },
23
+ warn: {
24
+ stroke: "#d97706",
25
+ fill: "#fef3c7"
26
+ },
27
+ bad: {
28
+ stroke: "#dc2626",
29
+ fill: "#fee2e2"
30
+ },
31
+ info: {
32
+ stroke: "#2563eb",
33
+ fill: "#dbeafe"
34
+ },
35
+ neutral: {
36
+ stroke: "#64748b",
37
+ fill: "#f1f5f9"
38
+ },
39
+ purple: {
40
+ stroke: "#7c3aed",
41
+ fill: "#ede9fe"
42
+ },
43
+ pink: {
44
+ stroke: "#db2777",
45
+ fill: "#fce7f3"
46
+ },
47
+ amber: {
48
+ stroke: "#f59e0b",
49
+ fill: "#ffedd5"
50
+ },
51
+ teal: {
52
+ stroke: "#0d9488",
53
+ fill: "#ccfbf1"
54
+ }
55
+ };
56
+ /** The CSS a literal becomes on the slide. Strokes take the token; fills are a tint of it. */
57
+ function cssFor(tone, role) {
58
+ if (tone === "ink") return role === "stroke" ? "var(--deck-fg)" : "var(--deck-surface)";
59
+ if (tone === "muted") return role === "stroke" ? "var(--deck-muted)" : "color-mix(in oklab, var(--deck-muted) 12%, transparent)";
60
+ const token = `var(--tone-${tone})`;
61
+ return role === "stroke" ? token : `color-mix(in oklab, ${token} 16%, transparent)`;
62
+ }
63
+ /** hex (lower-case) → CSS, for the rewrite pass. */
64
+ const COLOR_MAP = new Map(Object.keys(PALETTE).flatMap((tone) => [[PALETTE[tone].stroke, cssFor(tone, "stroke")], [PALETTE[tone].fill, cssFor(tone, "fill")]]));
65
+ //#endregion
66
+ export { COLOR_MAP, PALETTE, cssFor };
@@ -0,0 +1,43 @@
1
+ import { SketchScene } from "./dsl.js";
2
+ //#region src/sketch/render.d.ts
3
+ /**
4
+ * ─────────────────────────────────────────────────────────────────────────────
5
+ * SKETCH RENDERER — Excalidraw elements in, a theme-aware SVG out
6
+ * ─────────────────────────────────────────────────────────────────────────────
7
+ *
8
+ * Three inputs, one pipeline:
9
+ *
10
+ * DSL scene skeletons ──► convertToExcalidrawElements
11
+ * .excalidraw file elements ──► restoreElements ──► exportToSvg ──► rewrite
12
+ * Mermaid text parseMermaidToExcalidraw ──► convertToExcalidrawElements
13
+ *
14
+ * The rewrite is what makes the drawing belong to the deck: every element gets a `link` before
15
+ * export (Excalidraw wraps linked elements in `<a>`, the only per-element hook its exporter
16
+ * offers), which becomes a `<g data-id data-region>` so a slide can light regions up per click;
17
+ * and every palette literal becomes the theme token it stands for, so the sketch follows the
18
+ * theme menu and the dark scheme like everything else.
19
+ *
20
+ * Fonts are self-hosted: the Vite plugin serves `dist/prod/fonts` under `<base>excalidraw/`
21
+ * and this module points `EXCALIDRAW_ASSET_PATH` there. A warm-up export registers the faces
22
+ * in `document.fonts` before any text is measured, so boxes are sized for the real font.
23
+ * ─────────────────────────────────────────────────────────────────────────────
24
+ */
25
+ export type SketchInput = SketchScene | {
26
+ kind: "file";
27
+ data: ExcalidrawFile;
28
+ } | {
29
+ kind: "url";
30
+ src: string;
31
+ } | {
32
+ kind: "mermaid";
33
+ source: string;
34
+ fontSize?: number;
35
+ };
36
+ /** The shape of a `.excalidraw` document (what excalidraw.com saves). */
37
+ export interface ExcalidrawFile {
38
+ type?: string;
39
+ elements: readonly Record<string, unknown>[];
40
+ appState?: Record<string, unknown>;
41
+ files?: Record<string, unknown>;
42
+ }
43
+ //#endregion