@honeydeck/honeydeck 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 (144) hide show
  1. package/AGENTS.md +25 -0
  2. package/DEVELOPMENT.md +522 -0
  3. package/LICENSE +21 -0
  4. package/Readme.md +49 -0
  5. package/SPEC.md +88 -0
  6. package/docs/components.md +63 -0
  7. package/docs/configuration.md +91 -0
  8. package/docs/getting-started.md +116 -0
  9. package/docs/kit-authoring.md +207 -0
  10. package/docs/kits.md +387 -0
  11. package/docs/local-development.md +95 -0
  12. package/docs/mermaid.md +198 -0
  13. package/docs/mobile.md +108 -0
  14. package/docs/navigation.md +93 -0
  15. package/docs/next-steps.md +377 -0
  16. package/docs/pdf-export.md +91 -0
  17. package/docs/presenter-mode.md +104 -0
  18. package/docs/slides.md +130 -0
  19. package/docs/slidev-migration.md +42 -0
  20. package/docs/steps-and-reveals.md +171 -0
  21. package/package.json +134 -0
  22. package/skills/SPEC.md +21 -0
  23. package/skills/honeydeck/SKILL.md +65 -0
  24. package/skills/presentation-writing/SKILL.md +75 -0
  25. package/skills/slidev-migration/SKILL.md +153 -0
  26. package/src/SPEC.md +89 -0
  27. package/src/assets.d.ts +30 -0
  28. package/src/cli/SPEC.md +230 -0
  29. package/src/cli/args.ts +3 -0
  30. package/src/cli/banner.ts +9 -0
  31. package/src/cli/bin.js +5 -0
  32. package/src/cli/build.ts +229 -0
  33. package/src/cli/deck-path.ts +32 -0
  34. package/src/cli/dev.ts +263 -0
  35. package/src/cli/index.ts +126 -0
  36. package/src/cli/init.ts +369 -0
  37. package/src/cli/pdf.ts +923 -0
  38. package/src/cli/skill.ts +75 -0
  39. package/src/cli/templates/SPEC.md +70 -0
  40. package/src/cli/templates/deck-mdx.ts +15 -0
  41. package/src/cli/templates/package-json.ts +36 -0
  42. package/src/cli/templates/sparkle-button.ts +15 -0
  43. package/src/cli/templates/starter/components/SparkleButton.tsx +84 -0
  44. package/src/cli/templates/starter/deck.mdx +153 -0
  45. package/src/cli/templates/starter/styles.css +14 -0
  46. package/src/cli/templates/styles-css.ts +14 -0
  47. package/src/defaults.ts +1 -0
  48. package/src/layouts/ColorModeImage.tsx +55 -0
  49. package/src/layouts/SPEC.md +393 -0
  50. package/src/layouts/SlideFrame.tsx +48 -0
  51. package/src/layouts/bee/Blank.tsx +12 -0
  52. package/src/layouts/bee/Cover.tsx +70 -0
  53. package/src/layouts/bee/Default.tsx +42 -0
  54. package/src/layouts/bee/Image/Image.tsx +151 -0
  55. package/src/layouts/bee/Image/placeholder-dark.webp +0 -0
  56. package/src/layouts/bee/Image/placeholder-vertical-dark.webp +0 -0
  57. package/src/layouts/bee/Image/placeholder-vertical.webp +0 -0
  58. package/src/layouts/bee/Image/placeholder.webp +0 -0
  59. package/src/layouts/bee/ImageLeft.tsx +27 -0
  60. package/src/layouts/bee/ImageRight.tsx +27 -0
  61. package/src/layouts/bee/ImageSide.tsx +107 -0
  62. package/src/layouts/bee/Section.tsx +40 -0
  63. package/src/layouts/bee/TwoCol.tsx +108 -0
  64. package/src/layouts/bee/index.ts +40 -0
  65. package/src/layouts/clean/Blank.tsx +12 -0
  66. package/src/layouts/clean/Cover.tsx +58 -0
  67. package/src/layouts/clean/Default.tsx +33 -0
  68. package/src/layouts/clean/Image/Image.tsx +103 -0
  69. package/src/layouts/clean/ImageLeft.tsx +27 -0
  70. package/src/layouts/clean/ImageRight.tsx +27 -0
  71. package/src/layouts/clean/ImageSide.tsx +113 -0
  72. package/src/layouts/clean/Section.tsx +35 -0
  73. package/src/layouts/clean/TwoCol.tsx +63 -0
  74. package/src/layouts/clean/index.ts +40 -0
  75. package/src/layouts/index.ts +60 -0
  76. package/src/layouts/placeholders.ts +9 -0
  77. package/src/layouts/utils.ts +13 -0
  78. package/src/remark/SPEC.md +49 -0
  79. package/src/remark/h1-extract.ts +124 -0
  80. package/src/remark/index.ts +4 -0
  81. package/src/remark/shiki-code-blocks.ts +325 -0
  82. package/src/remark/step-numbering.ts +412 -0
  83. package/src/runtime/Deck.tsx +533 -0
  84. package/src/runtime/SPEC.md +256 -0
  85. package/src/runtime/SlideCanvas.tsx +95 -0
  86. package/src/runtime/TimelineContext.tsx +122 -0
  87. package/src/runtime/app-shell/index.html +31 -0
  88. package/src/runtime/app-shell/main.tsx +42 -0
  89. package/src/runtime/aspectRatio.ts +34 -0
  90. package/src/runtime/colorMode.ts +23 -0
  91. package/src/runtime/components/BrowserFrame.tsx +233 -0
  92. package/src/runtime/components/Button.tsx +57 -0
  93. package/src/runtime/components/CodeBlock.tsx +210 -0
  94. package/src/runtime/components/ColorModeCycleButton.tsx +59 -0
  95. package/src/runtime/components/ErrorBoundary.tsx +125 -0
  96. package/src/runtime/components/Keyboard.tsx +87 -0
  97. package/src/runtime/components/ListStyle.tsx +203 -0
  98. package/src/runtime/components/NavBar.tsx +223 -0
  99. package/src/runtime/components/NavBarButton.tsx +47 -0
  100. package/src/runtime/components/NavBarDivider.tsx +3 -0
  101. package/src/runtime/components/Notes.tsx +171 -0
  102. package/src/runtime/components/Reveal.tsx +82 -0
  103. package/src/runtime/components/RevealGroup.tsx +193 -0
  104. package/src/runtime/components/SPEC.md +263 -0
  105. package/src/runtime/components/SlideNumberBadge.tsx +11 -0
  106. package/src/runtime/components/TimelineSteps.tsx +115 -0
  107. package/src/runtime/components/index.ts +55 -0
  108. package/src/runtime/index.ts +42 -0
  109. package/src/runtime/inputOwnership.ts +68 -0
  110. package/src/runtime/keyboardTarget.ts +7 -0
  111. package/src/runtime/lastSlideRoute.ts +56 -0
  112. package/src/runtime/navigation.ts +211 -0
  113. package/src/runtime/router.ts +157 -0
  114. package/src/runtime/slideData.ts +137 -0
  115. package/src/runtime/sync.ts +267 -0
  116. package/src/runtime/types.ts +182 -0
  117. package/src/runtime/useKeyboardNav.ts +138 -0
  118. package/src/runtime/useSwipeNav.ts +257 -0
  119. package/src/runtime/views/DocsView.tsx +74 -0
  120. package/src/runtime/views/OverviewView.tsx +386 -0
  121. package/src/runtime/views/PresenterNotesPanel.tsx +76 -0
  122. package/src/runtime/views/PresenterView.tsx +340 -0
  123. package/src/runtime/views/SPEC.md +152 -0
  124. package/src/runtime/views/docs/ComponentsTab.tsx +178 -0
  125. package/src/runtime/views/docs/DocsHeader.tsx +101 -0
  126. package/src/runtime/views/docs/Intro.tsx +20 -0
  127. package/src/runtime/views/docs/LayoutsTab.tsx +324 -0
  128. package/src/runtime/views/docs/ThemeTab.tsx +110 -0
  129. package/src/runtime/views/index.ts +7 -0
  130. package/src/runtime/views/overviewGrid.ts +106 -0
  131. package/src/runtime/views/presenterPreview.ts +27 -0
  132. package/src/runtime/virtual-modules.d.ts +98 -0
  133. package/src/theme/SPEC.md +179 -0
  134. package/src/theme/base.css +623 -0
  135. package/src/theme/bee.css +35 -0
  136. package/src/theme/clean.css +38 -0
  137. package/src/vite-plugin/SPEC.md +114 -0
  138. package/src/vite-plugin/component-doc-crawler.ts +350 -0
  139. package/src/vite-plugin/deck-loader.ts +148 -0
  140. package/src/vite-plugin/index.ts +373 -0
  141. package/src/vite-plugin/layout-demo-crawler.ts +802 -0
  142. package/src/vite-plugin/splitter.ts +353 -0
  143. package/src/vite-plugin/token-manifest.ts +163 -0
  144. package/src/vite-plugin/virtual-modules.ts +587 -0
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Clean Image layout — image stage with minimal framing.
3
+ */
4
+
5
+ import type { ReactNode } from "react";
6
+ import type { LayoutDemo, LayoutProps } from "../../../runtime/types.ts";
7
+ import {
8
+ ColorModeImage,
9
+ type DarkModeImageFrontmatter,
10
+ } from "../../ColorModeImage.tsx";
11
+ import { imagePlaceholder, imagePlaceholderDark } from "../../placeholders.ts";
12
+ import CleanDefaultLayout from "../Default.tsx";
13
+
14
+ type ImageFrontmatter = DarkModeImageFrontmatter & {
15
+ /** Accessible text for the image. */
16
+ alt?: string;
17
+ };
18
+
19
+ function ImageFrame({ children }: { children: ReactNode }) {
20
+ return (
21
+ <div className="max-h-full overflow-hidden rounded-honeydeck border border-border bg-surface">
22
+ {children}
23
+ </div>
24
+ );
25
+ }
26
+
27
+ function PlaceholderBox() {
28
+ return (
29
+ <div className="flex max-h-full min-h-0 flex-col items-center gap-8">
30
+ <ImageFrame>
31
+ <ColorModeImage
32
+ src={imagePlaceholder}
33
+ darkSrc={imagePlaceholderDark}
34
+ alt=""
35
+ className="max-h-full max-w-full object-contain"
36
+ aria-hidden="true"
37
+ />
38
+ </ImageFrame>
39
+ <p className="text-center text-[length:var(--honeydeck-font-size-small)] leading-snug text-surface-foreground opacity-60">
40
+ Add{" "}
41
+ <code className="rounded-honeydeck bg-background px-4 py-1 font-mono">
42
+ image: /path/to/image.png
43
+ </code>{" "}
44
+ to frontmatter
45
+ </p>
46
+ </div>
47
+ );
48
+ }
49
+
50
+ export default function CleanImageLayout({
51
+ title,
52
+ children,
53
+ frontmatter,
54
+ rawChildren,
55
+ }: LayoutProps<ImageFrontmatter>) {
56
+ const { image, darkImage, alt = "" } = frontmatter;
57
+ const hasImage = Boolean(image || darkImage);
58
+ const hasCaption = Boolean(children);
59
+
60
+ return (
61
+ <CleanDefaultLayout
62
+ title={title}
63
+ frontmatter={frontmatter}
64
+ rawChildren={rawChildren}
65
+ >
66
+ <figure className="m-0 flex h-full flex-col gap-8 p-0">
67
+ <div className="flex min-h-0 flex-1 flex-col items-center justify-center">
68
+ {hasImage ? (
69
+ <ImageFrame>
70
+ <ColorModeImage
71
+ src={image || imagePlaceholder}
72
+ darkSrc={darkImage}
73
+ alt={alt}
74
+ className="max-h-full max-w-full object-contain"
75
+ />
76
+ </ImageFrame>
77
+ ) : (
78
+ <PlaceholderBox />
79
+ )}
80
+ </div>
81
+
82
+ {hasCaption && (
83
+ <figcaption className="flex-shrink-0 pt-5 text-[length:var(--honeydeck-font-size-small)] leading-snug text-surface-foreground">
84
+ {children}
85
+ </figcaption>
86
+ )}
87
+ </figure>
88
+ </CleanDefaultLayout>
89
+ );
90
+ }
91
+
92
+ export const demo: LayoutDemo<ImageFrontmatter> = {
93
+ mdx: `---
94
+ layout: Image
95
+ image: ""
96
+ darkImage: ""
97
+ alt: Architecture diagram
98
+ ---
99
+
100
+ # System Architecture
101
+
102
+ Our distributed system in production.`,
103
+ };
@@ -0,0 +1,27 @@
1
+ import type { LayoutDemo, LayoutProps } from "../../runtime/types.ts";
2
+ import type { DarkModeImageFrontmatter } from "../ColorModeImage.tsx";
3
+ import { CleanImageSideLayout } from "./ImageSide.tsx";
4
+
5
+ type ImageLeftFrontmatter = DarkModeImageFrontmatter & {
6
+ /** Accessible text for the image. */
7
+ alt?: string;
8
+ };
9
+
10
+ export default function CleanImageLeftLayout(
11
+ props: LayoutProps<ImageLeftFrontmatter>,
12
+ ) {
13
+ return <CleanImageSideLayout {...props} side="left" />;
14
+ }
15
+
16
+ export const demo: LayoutDemo<ImageLeftFrontmatter> = {
17
+ mdx: `---
18
+ layout: ImageLeft
19
+ image: ""
20
+ darkImage: ""
21
+ alt: Product detail
22
+ ---
23
+
24
+ # Product Detail
25
+
26
+ Use the body area for supporting context, bullets, or a short narrative beside the image.`,
27
+ };
@@ -0,0 +1,27 @@
1
+ import type { LayoutDemo, LayoutProps } from "../../runtime/types.ts";
2
+ import type { DarkModeImageFrontmatter } from "../ColorModeImage.tsx";
3
+ import { CleanImageSideLayout } from "./ImageSide.tsx";
4
+
5
+ type ImageRightFrontmatter = DarkModeImageFrontmatter & {
6
+ /** Accessible text for the image. */
7
+ alt?: string;
8
+ };
9
+
10
+ export default function CleanImageRightLayout(
11
+ props: LayoutProps<ImageRightFrontmatter>,
12
+ ) {
13
+ return <CleanImageSideLayout {...props} side="right" />;
14
+ }
15
+
16
+ export const demo: LayoutDemo<ImageRightFrontmatter> = {
17
+ mdx: `---
18
+ layout: ImageRight
19
+ image: ""
20
+ darkImage: ""
21
+ alt: Launch moment
22
+ ---
23
+
24
+ # Launch Moment
25
+
26
+ Pair a flush image with focused supporting content.`,
27
+ };
@@ -0,0 +1,113 @@
1
+ import type { ReactNode } from "react";
2
+ import type { LayoutProps } from "../../runtime/types.ts";
3
+ import {
4
+ ColorModeImage,
5
+ type DarkModeImageFrontmatter,
6
+ } from "../ColorModeImage.tsx";
7
+ import {
8
+ verticalImagePlaceholder,
9
+ verticalImagePlaceholderDark,
10
+ } from "../placeholders.ts";
11
+ import { SlideFrame } from "../SlideFrame.tsx";
12
+ import { hasTitle } from "../utils.ts";
13
+
14
+ type CleanImageSideFrontmatter = DarkModeImageFrontmatter & {
15
+ alt?: string;
16
+ };
17
+
18
+ type ImageSide = "left" | "right";
19
+
20
+ type CleanImageSideLayoutProps = LayoutProps<CleanImageSideFrontmatter> & {
21
+ side: ImageSide;
22
+ };
23
+
24
+ function CleanSideImage({
25
+ image,
26
+ darkImage,
27
+ alt,
28
+ }: {
29
+ image?: string;
30
+ darkImage?: string;
31
+ alt: string;
32
+ }) {
33
+ const hasImage = Boolean(image || darkImage);
34
+
35
+ return (
36
+ <div className="h-full min-h-0 overflow-hidden">
37
+ <ColorModeImage
38
+ src={image || verticalImagePlaceholder}
39
+ darkSrc={
40
+ darkImage || (hasImage ? undefined : verticalImagePlaceholderDark)
41
+ }
42
+ alt={hasImage ? alt : ""}
43
+ className="size-full object-cover"
44
+ aria-hidden={hasImage ? undefined : "true"}
45
+ />
46
+ </div>
47
+ );
48
+ }
49
+
50
+ function CleanSideContent({
51
+ title,
52
+ children,
53
+ side,
54
+ }: {
55
+ title: ReactNode | null;
56
+ children: ReactNode;
57
+ side: ImageSide;
58
+ }) {
59
+ return (
60
+ <div
61
+ className={`flex min-h-0 flex-col overflow-hidden ${
62
+ side === "left"
63
+ ? "py-[var(--honeydeck-slide-padding)] pr-[var(--honeydeck-slide-padding)]"
64
+ : "py-[var(--honeydeck-slide-padding)] pl-[var(--honeydeck-slide-padding)]"
65
+ }`}
66
+ >
67
+ {hasTitle(title) && (
68
+ <header className="mb-8 flex-shrink-0">
69
+ <h1 className="font-heading text-[length:var(--honeydeck-font-size-h2)] font-semibold leading-tight text-foreground">
70
+ {title}
71
+ </h1>
72
+ </header>
73
+ )}
74
+
75
+ <div className="min-h-0 flex-1 overflow-hidden">{children}</div>
76
+ </div>
77
+ );
78
+ }
79
+
80
+ export function CleanImageSideLayout({
81
+ title,
82
+ children,
83
+ frontmatter,
84
+ side,
85
+ }: CleanImageSideLayoutProps) {
86
+ const { image, darkImage, alt = "" } = frontmatter;
87
+ const imagePane = (
88
+ <CleanSideImage image={image} darkImage={darkImage} alt={alt} />
89
+ );
90
+ const contentPane = (
91
+ <CleanSideContent title={title} side={side}>
92
+ {children}
93
+ </CleanSideContent>
94
+ );
95
+
96
+ return (
97
+ <SlideFrame padded={false}>
98
+ <div className="grid size-full grid-cols-2 gap-10">
99
+ {side === "left" ? (
100
+ <>
101
+ {imagePane}
102
+ {contentPane}
103
+ </>
104
+ ) : (
105
+ <>
106
+ {contentPane}
107
+ {imagePane}
108
+ </>
109
+ )}
110
+ </div>
111
+ </SlideFrame>
112
+ );
113
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Clean Section layout — simple centered section divider.
3
+ */
4
+
5
+ import type { LayoutDemo, LayoutProps } from "../../runtime/types.ts";
6
+ import { SlideFrame } from "../SlideFrame.tsx";
7
+ import { hasTitle } from "../utils.ts";
8
+
9
+ export default function CleanSectionLayout({ title, children }: LayoutProps) {
10
+ return (
11
+ <SlideFrame className="items-center justify-center text-center">
12
+ {hasTitle(title) && (
13
+ <h1 className="mb-7 max-w-5xl font-heading text-[length:var(--honeydeck-font-size-display)] font-semibold leading-none text-foreground">
14
+ {title}
15
+ </h1>
16
+ )}
17
+
18
+ {children && (
19
+ <div className="max-w-3xl text-[length:var(--honeydeck-font-size-body)] leading-snug text-surface-foreground">
20
+ {children}
21
+ </div>
22
+ )}
23
+ </SlideFrame>
24
+ );
25
+ }
26
+
27
+ export const demo: LayoutDemo = {
28
+ mdx: `---
29
+ layout: Section
30
+ ---
31
+
32
+ # Section Title
33
+
34
+ An optional section subtitle or brief description.`,
35
+ };
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Clean TwoCol layout — two equal plain columns.
3
+ */
4
+
5
+ import type { ReactNode } from "react";
6
+ import type { LayoutDemo, LayoutProps } from "../../runtime/types.ts";
7
+ import CleanDefaultLayout from "./Default.tsx";
8
+
9
+ export function Left({ children }: { children?: ReactNode }) {
10
+ return (
11
+ <div data-honeydeck-slot="left" className="col-start-1 overflow-hidden">
12
+ {children}
13
+ </div>
14
+ );
15
+ }
16
+
17
+ export function Right({ children }: { children?: ReactNode }) {
18
+ return (
19
+ <div data-honeydeck-slot="right" className="col-start-2 overflow-hidden">
20
+ {children}
21
+ </div>
22
+ );
23
+ }
24
+
25
+ export default function CleanTwoColLayout({
26
+ title,
27
+ children,
28
+ frontmatter,
29
+ rawChildren,
30
+ }: LayoutProps) {
31
+ return (
32
+ <CleanDefaultLayout
33
+ title={title}
34
+ frontmatter={frontmatter}
35
+ rawChildren={rawChildren}
36
+ >
37
+ <div className="grid h-full grid-cols-2 gap-10">{children}</div>
38
+ </CleanDefaultLayout>
39
+ );
40
+ }
41
+
42
+ export const demo: LayoutDemo = {
43
+ mdx: `---
44
+ layout: TwoCol
45
+ ---
46
+
47
+ import { Left, Right } from '@honeydeck/honeydeck/layouts/TwoCol'
48
+
49
+ # Pros vs Cons
50
+
51
+ <Left>
52
+ ## Pros
53
+
54
+ - Fast
55
+ - Simple
56
+ </Left>
57
+
58
+ <Right>
59
+ ## Cons
60
+
61
+ - Limited
62
+ </Right>`,
63
+ };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Clean Honeydeck layout map.
3
+ *
4
+ * Same layout names and slot API as `@honeydeck/honeydeck/layouts`, with an explicit
5
+ * import path for decks that want to name the clean kit directly.
6
+ */
7
+
8
+ import type { LayoutMap } from "../../runtime/types.ts";
9
+ import BlankLayout from "./Blank.tsx";
10
+ import CoverLayout from "./Cover.tsx";
11
+ import DefaultLayout from "./Default.tsx";
12
+ import ImageLayout from "./Image/Image.tsx";
13
+ import ImageLeftLayout from "./ImageLeft.tsx";
14
+ import ImageRightLayout from "./ImageRight.tsx";
15
+ import SectionLayout from "./Section.tsx";
16
+ import TwoColLayout from "./TwoCol.tsx";
17
+
18
+ const cleanLayouts: LayoutMap = {
19
+ Default: DefaultLayout,
20
+ Blank: BlankLayout,
21
+ Cover: CoverLayout,
22
+ Section: SectionLayout,
23
+ TwoCol: TwoColLayout,
24
+ Image: ImageLayout,
25
+ ImageLeft: ImageLeftLayout,
26
+ ImageRight: ImageRightLayout,
27
+ };
28
+
29
+ export default cleanLayouts;
30
+
31
+ export {
32
+ BlankLayout,
33
+ CoverLayout,
34
+ DefaultLayout,
35
+ ImageLayout,
36
+ ImageLeftLayout,
37
+ ImageRightLayout,
38
+ SectionLayout,
39
+ TwoColLayout,
40
+ };
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Default Honeydeck layout map.
3
+ *
4
+ * Exported when no custom `layouts:` path is specified in deck frontmatter.
5
+ * Defaults are intentionally clean and minimal. Use
6
+ * `@honeydeck/honeydeck/layouts/bee` for the original playful Bee layouts.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * // layouts/index.ts — extend the default map
11
+ * import defaultLayouts from '@honeydeck/honeydeck/layouts'
12
+ * import { MyBrandCover } from './BrandCover'
13
+ *
14
+ * export default {
15
+ * ...defaultLayouts,
16
+ * Cover: MyBrandCover,
17
+ * } satisfies LayoutMap
18
+ * ```
19
+ */
20
+
21
+ import type { LayoutMap } from "../runtime/types.ts";
22
+ import { ColorModeImage } from "./ColorModeImage.tsx";
23
+ import BlankLayout from "./clean/Blank.tsx";
24
+ import CoverLayout from "./clean/Cover.tsx";
25
+ import DefaultLayout from "./clean/Default.tsx";
26
+ import ImageLayout from "./clean/Image/Image.tsx";
27
+ import ImageLeftLayout from "./clean/ImageLeft.tsx";
28
+ import ImageRightLayout from "./clean/ImageRight.tsx";
29
+ import SectionLayout from "./clean/Section.tsx";
30
+ import TwoColLayout from "./clean/TwoCol.tsx";
31
+
32
+ const defaultLayouts: LayoutMap = {
33
+ Default: DefaultLayout,
34
+ Blank: BlankLayout,
35
+ Cover: CoverLayout,
36
+ Section: SectionLayout,
37
+ TwoCol: TwoColLayout,
38
+ Image: ImageLayout,
39
+ ImageLeft: ImageLeftLayout,
40
+ ImageRight: ImageRightLayout,
41
+ };
42
+
43
+ export default defaultLayouts;
44
+
45
+ export type {
46
+ ColorModeImageProps,
47
+ DarkModeImageFrontmatter,
48
+ } from "./ColorModeImage.tsx";
49
+ // Re-export individual layouts for named imports
50
+ export {
51
+ BlankLayout,
52
+ ColorModeImage,
53
+ CoverLayout,
54
+ DefaultLayout,
55
+ ImageLayout,
56
+ ImageLeftLayout,
57
+ ImageRightLayout,
58
+ SectionLayout,
59
+ TwoColLayout,
60
+ };
@@ -0,0 +1,9 @@
1
+ import imagePlaceholderUrl from "./bee/Image/placeholder.webp";
2
+ import imagePlaceholderDarkUrl from "./bee/Image/placeholder-dark.webp";
3
+ import verticalImagePlaceholderUrl from "./bee/Image/placeholder-vertical.webp";
4
+ import verticalImagePlaceholderDarkUrl from "./bee/Image/placeholder-vertical-dark.webp";
5
+
6
+ export const imagePlaceholder = imagePlaceholderUrl;
7
+ export const imagePlaceholderDark = imagePlaceholderDarkUrl;
8
+ export const verticalImagePlaceholder = verticalImagePlaceholderUrl;
9
+ export const verticalImagePlaceholderDark = verticalImagePlaceholderDarkUrl;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Shared utility helpers for Honeydeck layout components.
3
+ */
4
+
5
+ /** Joins class names, filtering falsy values. No external dependencies. */
6
+ export function cn(...classes: (string | undefined | null | false)[]): string {
7
+ return classes.filter(Boolean).join(" ");
8
+ }
9
+
10
+ /** Returns true when `title` is a non-empty string. */
11
+ export function hasTitle(title: unknown): title is string {
12
+ return title != null && title !== "";
13
+ }
@@ -0,0 +1,49 @@
1
+ # Honeydeck Remark Transform Specification
2
+
3
+ > Observable behavior for code highlighting transforms.
4
+
5
+ ## Code Highlighting
6
+
7
+ ### Syntax Highlighting
8
+
9
+ Honeydeck highlights fenced code blocks without author configuration. Unsupported or failed languages render as plain code using Honeydeck CSS tokens.
10
+
11
+ ### Step-Through Syntax
12
+
13
+ ````mdx
14
+ ```ts {2|4|all}
15
+ const a = 1
16
+ const b = 2
17
+ console.log(a + b)
18
+ ```
19
+ ````
20
+
21
+ Syntax:
22
+
23
+ - `{2}` — highlight line 2 immediately; adds 0 timeline steps
24
+ - `{2-3}` — highlight lines 2 through 3 immediately; adds 0 timeline steps
25
+ - `{1,3}` — highlight lines 1 and 3 immediately; adds 0 timeline steps
26
+ - `{2-3|5|all}` — start with lines 2-3 active, then step to line 5, then all
27
+ - `|` separates code highlight groups; every group after the first adds one timeline step
28
+
29
+ ### Behavior
30
+
31
+ - Stepped code blocks show the first metadata group immediately, including at slide step 0 when the block is visible
32
+ - Each later code group adds one timeline step
33
+ - `{1|3-4|6-7|all}` starts with line 1 active, then steps to lines 3-4, then lines 6-7, then all lines
34
+ - Each active code group highlights only the specified lines (non-cumulative)
35
+ - Non-highlighted lines are dimmed (`--honeydeck-code-line-dim-opacity`)
36
+ - Dimming is applied by Honeydeck runtime markup/styles independently of Tailwind utility generation
37
+ - Code steps participate in the same slide timeline as `Reveal` components
38
+ - Unsupported/failed languages render as plain code using Honeydeck CSS tokens
39
+ - Code blocks expose a hover/focus copy control that copies the original fenced code text, not the highlighted HTML
40
+
41
+ ### Visual Style
42
+
43
+ - Highlighted lines: normal brightness
44
+ - Non-highlighted lines: dimmed (reduced opacity)
45
+ - No background stripe
46
+
47
+ ### Theme
48
+
49
+ Code highlighting colors come from Honeydeck's built-in light/dark syntax themes, not from Honeydeck CSS tokens. Honeydeck does not expose syntax theme configuration. The `--honeydeck-code-line-dim-opacity` token controls dimming of non-highlighted lines.
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Remark plugin: extract the first h1 from each slide and parse YAML frontmatter.
3
+ *
4
+ * ### What it does
5
+ * 1. **Frontmatter extraction** — visits the `yaml` node created by
6
+ * `remark-frontmatter`, parses the YAML string into a plain object, and
7
+ * stores it as `vfile.data.frontmatter`. This makes per-slide frontmatter
8
+ * (e.g. `layout: Cover`) available to the virtual-modules plugin without
9
+ * an extra YAML parsing library.
10
+ *
11
+ * 2. **H1 extraction** — finds the first `heading[depth=1]` node in the AST,
12
+ * reads its plain-text content via `mdast-util-to-string`, removes the node
13
+ * from the tree, and stores the text as `vfile.data.title`. Layouts can
14
+ * then render the title independently from the body, keeping its position
15
+ * stable regardless of how many steps have been revealed.
16
+ *
17
+ * ### Plugin ordering
18
+ * This plugin must run AFTER `remark-frontmatter` (which creates the `yaml`
19
+ * node) and BEFORE any plugins that rely on a cleaned-up AST.
20
+ *
21
+ * Recommended order:
22
+ * ```ts
23
+ * remarkPlugins: [remarkFrontmatter, remarkH1Extract, remarkStepNumbering]
24
+ * ```
25
+ */
26
+
27
+ import type { Heading, Parent, Root } from "mdast";
28
+ import { toString as mdastToString } from "mdast-util-to-string";
29
+ import type { Plugin } from "unified";
30
+ import { visit } from "unist-util-visit";
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Simple YAML parser — handles flat key: value pairs.
34
+ // Coerces booleans and numbers; everything else stays as a string.
35
+ // ---------------------------------------------------------------------------
36
+
37
+ function parseFlatYaml(yaml: string): Record<string, unknown> {
38
+ const result: Record<string, unknown> = {};
39
+
40
+ for (const line of yaml.split("\n")) {
41
+ const colonIdx = line.indexOf(":");
42
+ if (colonIdx === -1) continue;
43
+
44
+ const key = line.slice(0, colonIdx).trim();
45
+ const raw = line.slice(colonIdx + 1).trim();
46
+
47
+ if (!key) continue;
48
+
49
+ if (raw === "true") {
50
+ result[key] = true;
51
+ } else if (raw === "false") {
52
+ result[key] = false;
53
+ } else if (raw !== "" && !Number.isNaN(Number(raw))) {
54
+ result[key] = Number(raw);
55
+ } else if (
56
+ (raw.startsWith('"') && raw.endsWith('"')) ||
57
+ (raw.startsWith("'") && raw.endsWith("'"))
58
+ ) {
59
+ result[key] = raw.slice(1, -1);
60
+ } else {
61
+ result[key] = raw;
62
+ }
63
+ }
64
+
65
+ return result;
66
+ }
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // Plugin
70
+ // ---------------------------------------------------------------------------
71
+
72
+ /**
73
+ * Remark plugin that:
74
+ * 1. Parses `yaml` nodes (from remark-frontmatter) into `vfile.data.frontmatter`
75
+ * 2. Removes the first `h1` from the tree and stores its text in `vfile.data.title`
76
+ */
77
+ export const remarkH1Extract: Plugin<[], Root> = () => (tree, vfile) => {
78
+ // ── Step 1: parse YAML frontmatter ──────────────────────────────────────
79
+ visit(tree, "yaml", (node) => {
80
+ const yamlNode = node as unknown as { value: string };
81
+ vfile.data.frontmatter = parseFlatYaml(yamlNode.value);
82
+ });
83
+
84
+ // ── Step 2: find and remove first h1 ────────────────────────────────────
85
+ // We collect the location during the visit and mutate after, to avoid
86
+ // iterator invalidation while unist-util-visit walks the tree.
87
+
88
+ type H1Location = {
89
+ index: number;
90
+ parent: Parent;
91
+ text: string;
92
+ };
93
+
94
+ let firstH1: H1Location | null = null;
95
+
96
+ visit(tree, "heading", (node, index, parent) => {
97
+ if (firstH1) return; // already found one — stop collecting
98
+ const heading = node as unknown as Heading;
99
+ if (
100
+ heading.depth === 1 &&
101
+ parent &&
102
+ index !== null &&
103
+ index !== undefined
104
+ ) {
105
+ firstH1 = {
106
+ index: index as number,
107
+ parent: parent as unknown as Parent,
108
+ text: mdastToString(heading),
109
+ };
110
+ }
111
+ });
112
+
113
+ if (firstH1) {
114
+ // Remove the h1 node from its parent
115
+ (firstH1 as H1Location).parent.children.splice(
116
+ (firstH1 as H1Location).index,
117
+ 1,
118
+ );
119
+ vfile.data.title = (firstH1 as H1Location).text;
120
+ } else {
121
+ // No h1 found — title is empty string
122
+ vfile.data.title = "";
123
+ }
124
+ };
@@ -0,0 +1,4 @@
1
+ export { remarkH1Extract } from "./h1-extract.ts";
2
+ export type { StepGroup } from "./shiki-code-blocks.ts";
3
+ export { remarkShikiCodeBlocks } from "./shiki-code-blocks.ts";
4
+ export { remarkStepNumbering } from "./step-numbering.ts";