@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,83 @@
1
+ /* minimal — the default: black on white, a wide sans serif, one accent, nothing else asking for
2
+ attention. Dark mode inverts. Fonts are system by default; set `fonts` in the headmatter to
3
+ load a Google family and point --deck-body-font at it. */
4
+ .theme-minimal {
5
+ --deck-bg: #ffffff;
6
+ --deck-fg: #0a0a0a;
7
+ --deck-muted: #737373;
8
+ --deck-accent: #0a0a0a;
9
+ --deck-accent-2: #525252;
10
+ --deck-surface: #f5f5f5;
11
+ --deck-border: #e5e5e5;
12
+ --deck-code-bg: #fafafa;
13
+ --deck-code-fg: #171717;
14
+ --deck-code-active: rgba(10, 10, 10, 0.07);
15
+ --deck-radius: 10px;
16
+ --deck-pad: 96px;
17
+ --deck-base-size: 36px;
18
+ --deck-heading-font: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
19
+ --deck-body-font: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
20
+ --deck-mono-font: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
21
+ }
22
+ .theme-minimal[data-color-scheme="dark"] {
23
+ --deck-bg: #0a0a0a;
24
+ --deck-fg: #fafafa;
25
+ --deck-muted: #a3a3a3;
26
+ --deck-accent: #fafafa;
27
+ --deck-accent-2: #a3a3a3;
28
+ --deck-surface: #171717;
29
+ --deck-border: #262626;
30
+ --deck-code-bg: #111111;
31
+ --deck-code-fg: #e5e5e5;
32
+ --deck-code-active: rgba(250, 250, 250, 0.1);
33
+ }
34
+ .theme-minimal .deck-slide h1 {
35
+ font-size: 3.2em;
36
+ font-weight: 600;
37
+ letter-spacing: -0.035em;
38
+ }
39
+ .theme-minimal .deck-slide h2 {
40
+ font-size: 2.1em;
41
+ font-weight: 600;
42
+ letter-spacing: -0.03em;
43
+ }
44
+ .theme-minimal .deck-layout-cover h1,
45
+ .theme-minimal .deck-layout-intro h1 {
46
+ font-size: 3.8em;
47
+ }
48
+ .theme-minimal .deck-layout-cover p,
49
+ .theme-minimal .deck-layout-intro p,
50
+ .theme-minimal .deck-layout-section p {
51
+ color: var(--deck-muted);
52
+ }
53
+ .theme-minimal .deck-layout-section {
54
+ align-items: flex-start;
55
+ }
56
+ .theme-minimal .deck-layout-section h1,
57
+ .theme-minimal .deck-layout-section h2 {
58
+ font-size: 3.4em;
59
+ max-width: 14ch;
60
+ }
61
+ .theme-minimal .deck-layout-section::before {
62
+ content: "";
63
+ display: block;
64
+ width: 2.5em;
65
+ height: 6px;
66
+ background: var(--deck-fg);
67
+ margin-bottom: 1em;
68
+ }
69
+ .theme-minimal .deck-slide a {
70
+ color: inherit;
71
+ text-decoration-color: var(--deck-muted);
72
+ }
73
+ .theme-minimal .deck-slide blockquote {
74
+ border-inline-start-color: var(--deck-fg);
75
+ background: transparent;
76
+ font-style: italic;
77
+ }
78
+ .theme-minimal .deck-progress span {
79
+ background: var(--deck-fg);
80
+ }
81
+ .theme-minimal .deck-controls {
82
+ --deck-controls-color: var(--deck-fg);
83
+ }
@@ -0,0 +1,92 @@
1
+ /* rla — the semester-2 look: zinc-950 with a blue→violet ramp, dot-matrix headings (Bitcount
2
+ Grid Double), a dyslexia-friendly body face, Fira Code for code, and the animated backgrounds
3
+ from the backgrounds module (`background: sparkles` / `lines` / `mesh` in frontmatter). */
4
+ @import url("https://fonts.googleapis.com/css2?family=Bitcount+Grid+Double:wght@400;700&family=Bitcount+Grid+Single&family=Fira+Code:wght@400;500;600&family=Atkinson+Hyperlegible:wght@400;700&display=swap");
5
+
6
+ .theme-rla {
7
+ --deck-bg: #09090b;
8
+ --deck-fg: #fafafa;
9
+ --deck-muted: #a1a1aa;
10
+ --deck-accent: #3b82f6;
11
+ --deck-accent-2: #8b5cf6;
12
+ --deck-surface: rgba(255, 255, 255, 0.05);
13
+ --deck-border: rgba(255, 255, 255, 0.12);
14
+ --deck-code-bg: #111114;
15
+ --deck-code-fg: #e4e4e7;
16
+ --deck-code-active: rgba(59, 130, 246, 0.18);
17
+ --deck-radius: 14px;
18
+ --deck-pad: 72px;
19
+ --deck-base-size: 34px;
20
+ --deck-heading-font: "Bitcount Grid Double", "Pixelify Sans", system-ui, sans-serif;
21
+ --deck-body-font: "Atkinson Hyperlegible", "OpenDyslexic", system-ui, sans-serif;
22
+ --deck-mono-font: "Fira Code", ui-monospace, monospace;
23
+ color-scheme: dark;
24
+ }
25
+ .theme-rla[data-color-scheme="light"] {
26
+ --deck-bg: #f4f4f5;
27
+ --deck-fg: #09090b;
28
+ --deck-muted: #52525b;
29
+ --deck-surface: rgba(0, 0, 0, 0.05);
30
+ --deck-border: rgba(0, 0, 0, 0.12);
31
+ --deck-code-bg: #ffffff;
32
+ --deck-code-fg: #18181b;
33
+ }
34
+ .theme-rla .deck-slide h1,
35
+ .theme-rla .deck-slide h2 {
36
+ font-weight: 400;
37
+ letter-spacing: 0.01em;
38
+ line-height: 1.05;
39
+ }
40
+ .theme-rla .deck-slide h1 {
41
+ font-size: 2.9em;
42
+ }
43
+ .theme-rla .deck-slide h3,
44
+ .theme-rla .deck-slide h4 {
45
+ font-family: "Bitcount Grid Single", var(--deck-heading-font);
46
+ font-weight: 400;
47
+ }
48
+ .theme-rla .gradient-text,
49
+ .theme-rla .deck-layout-cover h1 {
50
+ background: linear-gradient(90deg, #60a5fa, #a78bfa);
51
+ -webkit-background-clip: text;
52
+ background-clip: text;
53
+ color: transparent;
54
+ }
55
+ .theme-rla .deck-layout-cover h1 + p,
56
+ .theme-rla .deck-layout-cover h1 + h3 {
57
+ color: #60a5fa;
58
+ letter-spacing: 0.08em;
59
+ text-transform: uppercase;
60
+ font-size: 0.8em;
61
+ }
62
+ .theme-rla .deck-slide blockquote {
63
+ border-inline-start-color: var(--deck-accent-2);
64
+ }
65
+ .theme-rla .deck-progress span {
66
+ background: linear-gradient(90deg, #3b82f6, #8b5cf6);
67
+ }
68
+ .theme-rla .deck-controls {
69
+ --deck-controls-color: #3b82f6;
70
+ }
71
+ .theme-rla .deck-poll,
72
+ .theme-rla .deck-quiz,
73
+ .theme-rla .deck-callout {
74
+ backdrop-filter: blur(10px);
75
+ }
76
+ /* the RLA card: a bordered glassy panel with a small uppercase tag */
77
+ .theme-rla .card {
78
+ border: 1px solid var(--deck-border);
79
+ border-radius: var(--deck-radius);
80
+ background: rgba(24, 24, 27, 0.6);
81
+ padding: 0.6em 0.9em;
82
+ backdrop-filter: blur(8px);
83
+ }
84
+ .theme-rla .tag {
85
+ font: 0.45em var(--deck-mono-font);
86
+ letter-spacing: 0.12em;
87
+ text-transform: uppercase;
88
+ color: #60a5fa;
89
+ border: 1px solid rgba(96, 165, 250, 0.4);
90
+ border-radius: 6px;
91
+ padding: 0.2em 0.6em;
92
+ }
@@ -0,0 +1,121 @@
1
+ /* ─────────────────────────────────────────────────────────────────────────────
2
+ @kadal/deck — sketch: Excalidraw drawings on slides, their click tour and the canvas.
3
+ Loaded by base.css.
4
+ ───────────────────────────────────────────────────────────────────────────── */
5
+ .deck-sketch {
6
+ position: relative;
7
+ margin: 0;
8
+ width: 100%;
9
+ display: flex;
10
+ flex-direction: column;
11
+ align-items: center;
12
+ gap: 0.35em;
13
+ }
14
+ .deck-sketch-body {
15
+ width: 100%;
16
+ line-height: 0;
17
+ text-align: center;
18
+ }
19
+ /* Width follows the column; a height limit shrinks the width with it (no letterboxing). */
20
+ .deck-sketch-body svg {
21
+ width: min(100%, calc(var(--sk-max-h, 100000px) * var(--sk-ratio, 1)));
22
+ height: auto;
23
+ overflow: visible;
24
+ }
25
+ .deck-sketch .sk-el {
26
+ transition:
27
+ opacity 0.45s ease,
28
+ filter 0.45s ease;
29
+ }
30
+ .deck-sketch .sk-el.is-dim {
31
+ opacity: 0.12;
32
+ filter: saturate(0.2);
33
+ }
34
+ .deck-sketch-caption {
35
+ font-size: 0.6em;
36
+ line-height: 1.35;
37
+ color: var(--deck-muted);
38
+ text-align: center;
39
+ max-width: 60ch;
40
+ animation: deck-sketch-caption 0.4s ease both;
41
+ }
42
+ .deck-sketch-caption strong {
43
+ color: var(--deck-fg);
44
+ }
45
+ @keyframes deck-sketch-caption {
46
+ from {
47
+ opacity: 0;
48
+ transform: translateY(5px);
49
+ }
50
+ to {
51
+ opacity: 1;
52
+ transform: none;
53
+ }
54
+ }
55
+ .deck-sketch-loading,
56
+ .deck-sketch-missing {
57
+ font-size: 0.6em;
58
+ color: var(--deck-muted);
59
+ padding: 1.5em;
60
+ }
61
+ .deck-sketch-error {
62
+ font-size: 0.5em;
63
+ color: var(--tone-bad);
64
+ white-space: pre-wrap;
65
+ text-align: left;
66
+ }
67
+ .deck-sketch-open {
68
+ position: absolute;
69
+ top: 0;
70
+ left: 0;
71
+ width: 1.5em;
72
+ height: 1.5em;
73
+ display: grid;
74
+ place-items: center;
75
+ border-radius: 999px;
76
+ border: 1px solid var(--deck-border);
77
+ background: color-mix(in oklab, var(--deck-bg) 80%, transparent);
78
+ color: var(--deck-muted);
79
+ backdrop-filter: blur(6px);
80
+ cursor: pointer;
81
+ opacity: 0;
82
+ transition:
83
+ opacity 0.2s ease,
84
+ color 0.2s ease;
85
+ font-size: 0.7em;
86
+ }
87
+ .deck-sketch:hover .deck-sketch-open,
88
+ .deck-sketch.hints-on .deck-sketch-open,
89
+ .deck-sketch-open:focus-visible {
90
+ opacity: 1;
91
+ }
92
+ .deck-sketch-open:hover {
93
+ color: var(--deck-accent);
94
+ }
95
+
96
+ /* The canvas in the lightbox. */
97
+ .deck-lightbox-media[data-fit="sketch"] > * {
98
+ width: 100%;
99
+ height: 100%;
100
+ }
101
+ .deck-sketch-canvas {
102
+ width: 100%;
103
+ height: 100%;
104
+ border-radius: 12px;
105
+ overflow: hidden;
106
+ background: var(--deck-bg);
107
+ }
108
+ .deck-sketch-canvas .excalidraw {
109
+ --color-primary: var(--deck-accent);
110
+ --color-primary-darker: var(--deck-accent);
111
+ --color-primary-darkest: var(--deck-accent);
112
+ }
113
+
114
+ @media (prefers-reduced-motion: reduce) {
115
+ .deck-sketch .sk-el {
116
+ transition: none;
117
+ }
118
+ .deck-sketch-caption {
119
+ animation: none;
120
+ }
121
+ }
@@ -0,0 +1,62 @@
1
+ /* stack — the monorepo's brand: an emerald accent on warm neutrals, Inter, and the same tokens
2
+ the portals use, so a deck about the platform looks like the platform. */
3
+ @import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap");
4
+
5
+ .theme-stack {
6
+ --deck-bg: #fbfbf9;
7
+ --deck-fg: #14171a;
8
+ --deck-muted: #6b7280;
9
+ --deck-accent: #059669;
10
+ --deck-accent-2: #0d9488;
11
+ --deck-surface: #f1f2ee;
12
+ --deck-border: #e2e4dd;
13
+ --deck-code-bg: #f3f4f0;
14
+ --deck-code-fg: #1f2937;
15
+ --deck-code-active: rgba(5, 150, 105, 0.14);
16
+ --deck-radius: 12px;
17
+ --deck-pad: 88px;
18
+ --deck-base-size: 34px;
19
+ --deck-heading-font: "Inter", system-ui, sans-serif;
20
+ --deck-body-font: "Inter", system-ui, sans-serif;
21
+ --deck-mono-font: "JetBrains Mono", ui-monospace, monospace;
22
+ }
23
+ .theme-stack[data-color-scheme="dark"] {
24
+ --deck-bg: #0f1412;
25
+ --deck-fg: #f3f4f1;
26
+ --deck-muted: #9aa39e;
27
+ --deck-accent: #34d399;
28
+ --deck-accent-2: #2dd4bf;
29
+ --deck-surface: #1a2320;
30
+ --deck-border: #26332e;
31
+ --deck-code-bg: #141b18;
32
+ --deck-code-fg: #e5e7e2;
33
+ --deck-code-active: rgba(52, 211, 153, 0.16);
34
+ }
35
+ .theme-stack .deck-slide h1 {
36
+ font-weight: 800;
37
+ letter-spacing: -0.035em;
38
+ }
39
+ .theme-stack .deck-slide h2 {
40
+ font-weight: 700;
41
+ letter-spacing: -0.03em;
42
+ }
43
+ .theme-stack .deck-layout-section::before,
44
+ .theme-stack .deck-layout-cover::before {
45
+ content: "";
46
+ display: block;
47
+ width: 3em;
48
+ height: 0.35em;
49
+ border-radius: 999px;
50
+ background: linear-gradient(90deg, var(--deck-accent), var(--deck-accent-2));
51
+ margin-bottom: 0.8em;
52
+ }
53
+ .theme-stack .deck-layout-cover {
54
+ align-items: flex-start;
55
+ }
56
+ .theme-stack .deck-slide a {
57
+ color: var(--deck-accent);
58
+ }
59
+ .theme-stack .deck-badge.tone-accent {
60
+ background: color-mix(in oklab, var(--deck-accent) 15%, transparent);
61
+ border-color: transparent;
62
+ }
@@ -0,0 +1,15 @@
1
+ import { Plugin } from "vite";
2
+ //#region src/vite/index.d.ts
3
+ export interface DeckPluginOptions {
4
+ /** The deck file, relative to the Vite root. Default `deck.mdx`. */
5
+ entry?: string;
6
+ /** Shiki themes. Defaults suit the neutral theme; the deck's theme CSS can override the colours. */
7
+ shiki?: {
8
+ light?: string;
9
+ dark?: string;
10
+ langs?: string[];
11
+ };
12
+ }
13
+ export declare function deck(options?: DeckPluginOptions): Plugin;
14
+ //#endregion
15
+ export { deck as default };
@@ -0,0 +1,320 @@
1
+ import { DEFAULT_GLOSSARY, buildMatcher, glossaryOptions } from "../glossary/model.js";
2
+ import { parseCodeMeta, serialiseSteps } from "../compiler/code-meta.js";
3
+ import { levelOf, splitDeck, titleOf } from "../compiler/split.js";
4
+ import { findMagicMoves } from "../compiler/magic-move.js";
5
+ import { rehypeDeckCode } from "../compiler/rehype-code.js";
6
+ import { rehypeGlossary } from "../compiler/rehype-glossary.js";
7
+ import { parseGlossary } from "../glossary/parse.js";
8
+ import { applySlots } from "../compiler/slots.js";
9
+ import { plainText } from "../compiler/plain-text.js";
10
+ import { renderToString } from "katex";
11
+ import { compile } from "@mdx-js/mdx";
12
+ import { cpSync, createReadStream, existsSync, readFileSync, statSync } from "node:fs";
13
+ import { createRequire } from "node:module";
14
+ import { dirname, join, resolve } from "node:path";
15
+ import rehypeKatex from "rehype-katex";
16
+ import remarkGfm from "remark-gfm";
17
+ import remarkMath from "remark-math";
18
+ import { createHighlighter } from "shiki";
19
+ import { codeToKeyedTokens } from "shiki-magic-move/core";
20
+ //#region src/vite/index.ts
21
+ /**
22
+ * ─────────────────────────────────────────────────────────────────────────────
23
+ * THE VITE PLUGIN: deck.mdx → modules
24
+ * ─────────────────────────────────────────────────────────────────────────────
25
+ *
26
+ * `import deck from "virtual:deck"` gives a deck its slides. The plugin reads `deck.mdx`, cuts it
27
+ * into slides (`compiler/split.ts`), lifts the magic-move blocks, and compiles each slide's MDX
28
+ * and each slide's notes to their own module — so a change to one slide re-evaluates one module,
29
+ * code is highlighted once at build time, and the browser never loads a Markdown parser.
30
+ *
31
+ * Module ids:
32
+ * virtual:deck the manifest: config, slides (with lazy components), the glossary
33
+ * virtual:deck/slide/N slide N's compiled MDX (default export: the component)
34
+ * virtual:deck/notes/N slide N's notes, compiled MDX (default export)
35
+ * virtual:deck/magic/N slide N's precomputed magic-move steps
36
+ *
37
+ * A `glossary.yaml` beside the deck file (or the headmatter's `glossary:` path) is read here too:
38
+ * every slide's key terms are linked to it as the slide compiles (`compiler/rehype-glossary.ts`),
39
+ * and its entries ride in the manifest for the cards the runtime shows.
40
+ *
41
+ * It also self-hosts Excalidraw's fonts for `<Sketch>`: `<base>excalidraw/fonts/…` is served
42
+ * from the package in dev and copied into `dist/excalidraw/fonts` on build (all families but the
43
+ * 12 MB CJK one), so a sketch never reaches for a CDN.
44
+ * ─────────────────────────────────────────────────────────────────────────────
45
+ */
46
+ /** `node_modules/@excalidraw/excalidraw/dist/prod`, or null when the package is not installed. */
47
+ function excalidrawDist() {
48
+ try {
49
+ return dirname(createRequire(import.meta.url).resolve("@excalidraw/excalidraw"));
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+ const SKIP_FONTS = /* @__PURE__ */ new Set(["Xiaolai"]);
55
+ /** Build-time KaTeX for the maths in a definition; the browser gets finished HTML. */
56
+ const renderMath = (tex) => renderToString(tex, { throwOnError: false });
57
+ const VIRTUAL = "virtual:deck";
58
+ const RESOLVED = "\0virtual:deck";
59
+ const DEFAULT_LANGS = [
60
+ "ts",
61
+ "tsx",
62
+ "js",
63
+ "jsx",
64
+ "json",
65
+ "bash",
66
+ "sh",
67
+ "python",
68
+ "rust",
69
+ "go",
70
+ "html",
71
+ "css",
72
+ "md",
73
+ "yaml",
74
+ "sql",
75
+ "toml",
76
+ "diff",
77
+ "c",
78
+ "cpp",
79
+ "java",
80
+ "kotlin",
81
+ "swift",
82
+ "text"
83
+ ];
84
+ function deck(options = {}) {
85
+ const entry = options.entry ?? "deck.mdx";
86
+ const themes = {
87
+ light: options.shiki?.light ?? "github-light",
88
+ dark: options.shiki?.dark ?? "github-dark"
89
+ };
90
+ let root = process.cwd();
91
+ let entryPath = "";
92
+ let base = "/";
93
+ let outDir = "dist";
94
+ let building = false;
95
+ let highlighter = null;
96
+ let server = null;
97
+ let manifest = null;
98
+ const included = /* @__PURE__ */ new Set();
99
+ /** The glossary file the last build read (or looked for), watched like an included file. */
100
+ let glossaryFile = null;
101
+ const getHighlighter = async () => {
102
+ highlighter ??= await createHighlighter({
103
+ themes: [themes.light, themes.dark],
104
+ langs: options.shiki?.langs ?? DEFAULT_LANGS
105
+ });
106
+ return highlighter;
107
+ };
108
+ function build() {
109
+ included.clear();
110
+ const text = readFileSync(entryPath, "utf8");
111
+ const split = splitDeck(text, {
112
+ file: entryPath,
113
+ resolveInclude: (path, from) => {
114
+ const file = resolve(dirname(from), path);
115
+ if (!existsSync(file)) return null;
116
+ included.add(file);
117
+ return {
118
+ file,
119
+ text: readFileSync(file, "utf8")
120
+ };
121
+ }
122
+ });
123
+ const slides = split.slides.map((s) => {
124
+ const { text: content, blocks } = findMagicMoves(applySlots(s.content));
125
+ return {
126
+ ...s,
127
+ content,
128
+ magic: blocks
129
+ };
130
+ });
131
+ return {
132
+ headmatter: split.headmatter,
133
+ slides,
134
+ glossary: readGlossary(split.headmatter)
135
+ };
136
+ }
137
+ function readGlossary(headmatter) {
138
+ const opts = glossaryOptions(headmatter.glossary);
139
+ let entries = [];
140
+ glossaryFile = opts.src ? resolve(dirname(entryPath), opts.src) : null;
141
+ if (glossaryFile && existsSync(glossaryFile)) {
142
+ const parsed = parseGlossary(readFileSync(glossaryFile, "utf8"), glossaryFile, renderMath);
143
+ entries = parsed.entries;
144
+ for (const w of parsed.warnings) console.warn(`[deck] glossary: ${w}`);
145
+ server?.watcher.add(glossaryFile);
146
+ } else if (glossaryFile && opts.src !== DEFAULT_GLOSSARY.src) console.warn(`[deck] glossary: ${glossaryFile} does not exist`);
147
+ return {
148
+ options: opts,
149
+ entries,
150
+ matcher: buildMatcher(entries)
151
+ };
152
+ }
153
+ const ensure = () => {
154
+ manifest ??= build();
155
+ return manifest;
156
+ };
157
+ const invalidate = () => {
158
+ manifest = null;
159
+ if (!server) return;
160
+ const mods = [...server.moduleGraph.idToModuleMap.keys()].filter((id) => id.startsWith(RESOLVED));
161
+ for (const id of mods) {
162
+ const mod = server.moduleGraph.getModuleById(id);
163
+ if (mod) server.moduleGraph.invalidateModule(mod);
164
+ }
165
+ server.ws.send({ type: "full-reload" });
166
+ };
167
+ /** `terms` is the slide's own frontmatter switch; null compiles without the glossary (notes). */
168
+ async function compileMdx(source, file, terms = null) {
169
+ const hl = await getHighlighter();
170
+ const { options: g, matcher } = ensure().glossary;
171
+ const skipTerms = terms && typeof terms === "object" && Array.isArray(terms.skip) ? terms.skip : [];
172
+ const glossaryPass = terms === null ? [] : [[rehypeGlossary, {
173
+ matcher,
174
+ auto: terms !== false,
175
+ repeat: g.repeat,
176
+ headings: g.headings,
177
+ skip: g.skip,
178
+ exclude: new Set(skipTerms.map((t) => matcher.resolve(String(t))?.id).filter((id) => !!id)),
179
+ math: renderMath,
180
+ warn: (message) => console.warn(`[deck] ${file}: ${message}`)
181
+ }]];
182
+ const out = await compile(source, {
183
+ jsx: false,
184
+ jsxImportSource: "react",
185
+ development: false,
186
+ remarkPlugins: [remarkGfm, remarkMath],
187
+ rehypePlugins: [
188
+ rehypeKatex,
189
+ [rehypeDeckCode, {
190
+ highlighter: hl,
191
+ themes
192
+ }],
193
+ ...glossaryPass
194
+ ],
195
+ providerImportSource: "@kadal/deck/mdx-provider",
196
+ format: "mdx"
197
+ });
198
+ return `// ${file}\n${String(out)}`;
199
+ }
200
+ return {
201
+ name: "stack:deck",
202
+ enforce: "pre",
203
+ configResolved(config) {
204
+ root = config.root;
205
+ entryPath = resolve(root, entry);
206
+ base = config.base.endsWith("/") ? config.base : `${config.base}/`;
207
+ outDir = resolve(root, config.build.outDir);
208
+ building = config.command === "build";
209
+ },
210
+ configureServer(s) {
211
+ server = s;
212
+ const dist = excalidrawDist();
213
+ s.middlewares.use((req, res, next) => {
214
+ const url = (req.url ?? "").split("?")[0];
215
+ const prefix = [`${base}excalidraw/fonts/`, "/excalidraw/fonts/"].find((p) => url.startsWith(p));
216
+ if (!dist || !prefix) return next();
217
+ const rel = decodeURIComponent(url.slice(prefix.length));
218
+ const file = join(dist, "fonts", rel);
219
+ if (rel.includes("..") || !existsSync(file) || !statSync(file).isFile()) {
220
+ res.statusCode = 404;
221
+ res.end();
222
+ return;
223
+ }
224
+ res.setHeader("Content-Type", "font/woff2");
225
+ res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
226
+ res.setHeader("Access-Control-Allow-Origin", "*");
227
+ createReadStream(file).pipe(res);
228
+ });
229
+ s.watcher.add(entryPath);
230
+ s.watcher.add(resolve(dirname(entryPath), DEFAULT_GLOSSARY.src));
231
+ const changed = (file) => {
232
+ if (file === entryPath || included.has(file) || file === glossaryFile) invalidate();
233
+ };
234
+ s.watcher.on("change", changed);
235
+ s.watcher.on("add", changed);
236
+ s.watcher.on("unlink", changed);
237
+ },
238
+ writeBundle() {
239
+ if (!building) return;
240
+ const dist = excalidrawDist();
241
+ if (!dist) return;
242
+ cpSync(join(dist, "fonts"), join(outDir, "excalidraw", "fonts"), {
243
+ recursive: true,
244
+ filter: (src) => !SKIP_FONTS.has(src.split("/").pop() ?? "")
245
+ });
246
+ },
247
+ resolveId(id) {
248
+ if (id === VIRTUAL || id.startsWith(`${VIRTUAL}/`)) return `\0${id}`;
249
+ if (id === "@kadal/deck/mdx-provider") return id;
250
+ return null;
251
+ },
252
+ async load(id) {
253
+ if (id === "@kadal/deck/mdx-provider") return `export { useMDXComponents } from "@kadal/deck";`;
254
+ if (!id.startsWith(RESOLVED)) return null;
255
+ const m = ensure();
256
+ if (id === RESOLVED) {
257
+ const entries = m.slides.map((s, i) => {
258
+ const title = titleOf(s);
259
+ return ` {
260
+ index: ${i},
261
+ h: ${s.h},
262
+ v: ${s.v},
263
+ id: ${JSON.stringify(typeof s.frontmatter.id === "string" ? s.frontmatter.id : `s${i}`)},
264
+ title: ${JSON.stringify(title)},
265
+ level: ${levelOf(s)},
266
+ frontmatter: ${JSON.stringify(s.frontmatter)},
267
+ hasNotes: ${s.notes ? "true" : "false"},
268
+ text: ${JSON.stringify(plainText(s.content))},
269
+ source: ${JSON.stringify(s.source)},
270
+ load: () => import("${VIRTUAL}/slide/${i}"),
271
+ loadNotes: ${s.notes ? `() => import("${VIRTUAL}/notes/${i}")` : "null"},
272
+ loadMagic: ${s.magic.length ? `() => import("${VIRTUAL}/magic/${i}")` : "null"},
273
+ }`;
274
+ });
275
+ return `export const headmatter = ${JSON.stringify(m.headmatter)};
276
+ export const slides = [
277
+ ${entries.join(",\n")}
278
+ ];
279
+ export const glossary = ${JSON.stringify(m.glossary.entries)};
280
+ export default { headmatter, slides, glossary };
281
+ `;
282
+ }
283
+ if (!id.startsWith("\0virtual:deck/")) return null;
284
+ const slide = /^(slide|notes|magic)\/(\d+)$/.exec(id.slice(14));
285
+ if (!slide) return null;
286
+ const kind = slide[1];
287
+ const s = m.slides[Number(slide[2])];
288
+ if (!s) return `export default () => null;`;
289
+ if (kind === "slide") return compileMdx(s.content, `${s.source.file}:${s.source.line}`, s.frontmatter.terms === void 0 ? true : s.frontmatter.terms);
290
+ if (kind === "notes") return compileMdx(s.notes ?? "", `${s.source.file}:${s.source.line} (notes)`);
291
+ const hl = await getHighlighter();
292
+ const blocks = s.magic.map((block) => ({
293
+ options: block.options,
294
+ title: block.title,
295
+ steps: block.steps.map((step) => {
296
+ const lang = hl.getLoadedLanguages().includes(step.lang) ? step.lang : "text";
297
+ const meta = parseCodeMeta(step.meta);
298
+ return {
299
+ lang: step.lang,
300
+ lines: meta.lines,
301
+ highlight: meta.steps ? serialiseSteps(meta.steps) : null,
302
+ tokens: {
303
+ light: codeToKeyedTokens(hl, step.code, {
304
+ lang,
305
+ theme: themes.light
306
+ }),
307
+ dark: codeToKeyedTokens(hl, step.code, {
308
+ lang,
309
+ theme: themes.dark
310
+ })
311
+ }
312
+ };
313
+ })
314
+ }));
315
+ return `export default ${JSON.stringify(blocks)};`;
316
+ }
317
+ };
318
+ }
319
+ //#endregion
320
+ export { deck, deck as default };