@atom63/slides 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.
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { Accent, Avatar, Badge, Bleed, Body, BodyLines, Box, CONFIG_GAP_PX, Cell, Center, ClosingSlide, Collage, ColorBlock, Columns, CoverSlide, Demo, Display, Divider, FRAME_PADDING_PX, FigureMark, Fill, FullBleedCoverSlide, FullBleedGallery, FullBleedSlide, Grid, Headline, HeroBento, Highlight, Icon, Image, ImageDuoSlide, ImageSlide, ImageTrioSlide, InlineCode, Item, Label, List, Live, MediaTrio, Mono, Position, Quote, QuoteSlide, QuoteWithMedia, Reveal, Row, SLIDE_FRAME_WATERMARK_PX, Section, SectionMarker, SectionSlide, SlideCodeBlock, SlidePaddingContext, SlideRenderModeContext, SlidesPlayer, Spacer, Split, SplitHalf, SplitWithStat, Stack, StatBento, StatementSlide, Subtitle, Syllabus, SyllabusDetectContext, TYPOGRAPHY_SCALE_VALUES, TalkTrack, TextLead, TimelineBento, Title, Trio, Video, getTemplate, listTemplates, slideMdxComponents, syllabusMdxComponents, templateNames, templateRegistry, useSlideConfig, useSlidePadding, useSlideRenderMode } from './chunk-AD3ZOVWR.js';
2
+ //# sourceMappingURL=index.js.map
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
@@ -0,0 +1,81 @@
1
+ import { ReactNode, ComponentType } from 'react';
2
+
3
+ type SlideLayout = 'cover' | 'title' | 'content' | 'split' | 'media-full' | 'quote';
4
+ interface SlideDeckMeta {
5
+ date: string;
6
+ description?: string;
7
+ /**
8
+ * When `true`, deck requires a password (server env `SLIDE_PASSWORD_<SLUG>`).
9
+ * Defaults to `false`.
10
+ */
11
+ locked?: boolean;
12
+ preset?: string;
13
+ /**
14
+ * Deck theme. `'auto'` (the default) inherits the site theme so the deck
15
+ * follows the user's current setting. `'dark'` / `'light'` force the canvas
16
+ * regardless of the site theme.
17
+ */
18
+ theme?: 'auto' | 'dark' | 'light';
19
+ title: string;
20
+ }
21
+ interface SlideDeckItem {
22
+ content?: ComponentType;
23
+ meta: SlideDeckMeta;
24
+ slideCount?: number;
25
+ slug: string;
26
+ }
27
+ interface ParsedSlide {
28
+ content: ReactNode[];
29
+ layout: SlideLayout;
30
+ notes: ReactNode | null;
31
+ }
32
+
33
+ /** The kind of fillable content a slot accepts. Layout enums are out of scope. */
34
+ type SlotKind = 'text' | 'richtext' | 'media' | 'list';
35
+ /** A single fillable field — a direct template prop or a compound-slot prop. */
36
+ interface SlotDef {
37
+ /** Prop name as written in MDX/JSX. */
38
+ key: string;
39
+ kind: SlotKind;
40
+ required: boolean;
41
+ /** Human-readable label for editor UIs. */
42
+ label: string;
43
+ /**
44
+ * True when the prop is a repeated scalar value that is NOT a compound child
45
+ * (e.g. ImageTrioSlide.images, ClosingSlide.handles). Repeated compound
46
+ * children (e.g. HeroBento.Card) are modeled via SlotGroupDef, not this flag.
47
+ */
48
+ array?: boolean;
49
+ }
50
+ /** A compound child slot, e.g. `HeroBento.Card`, with cardinality + its own props. */
51
+ interface SlotGroupDef {
52
+ /** Sub-component name as written after the dot, e.g. "Card" for HeroBento.Card. */
53
+ name: string;
54
+ min: number;
55
+ max: number;
56
+ props: SlotDef[];
57
+ }
58
+ type TemplateCategory = 'cover' | 'statement' | 'quote' | 'media' | 'gallery' | 'data' | 'split' | 'closing';
59
+ /** Full machine-readable description of one author-facing template. */
60
+ interface TemplateDef {
61
+ /** Component name as written in MDX, e.g. "HeroBento". */
62
+ name: string;
63
+ label: string;
64
+ category: TemplateCategory;
65
+ /** Direct props on the template component. */
66
+ props: SlotDef[];
67
+ /** Compound child slots; empty for simple templates. */
68
+ slots: SlotGroupDef[];
69
+ }
70
+ /** Registry of every author-facing template, keyed by component name. */
71
+ declare const templateRegistry: Record<string, TemplateDef>;
72
+ /** Union of every registered template name, derived from the registry. */
73
+ type TemplateName = keyof typeof templateRegistry;
74
+ /** All registered template names. */
75
+ declare const templateNames: readonly TemplateName[];
76
+ /** Look up one template definition by component name. */
77
+ declare function getTemplate(name: string): TemplateDef | undefined;
78
+ /** Every template definition, insertion order. */
79
+ declare function listTemplates(): TemplateDef[];
80
+
81
+ export { type ParsedSlide as P, type SlideLayout as S, type TemplateCategory as T, type SlideDeckItem as a, type SlideDeckMeta as b, type SlotDef as c, type SlotGroupDef as d, type SlotKind as e, type TemplateDef as f, type TemplateName as g, getTemplate as h, templateRegistry as i, listTemplates as l, templateNames as t };
@@ -0,0 +1,30 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ interface ManifestConfig {
4
+ /** Content directory relative to project root */
5
+ contentDir: string;
6
+ /** Output manifest file path relative to project root */
7
+ outputPath: string;
8
+ }
9
+ /**
10
+ * Vite plugin that extracts slide-deck MDX frontmatter into a static manifest.
11
+ *
12
+ * Mirrors the atom63.io plugin: it decouples metadata from MDX module loading
13
+ * (so Rollup can code-split decks into lazy chunks) and — critically for os63 —
14
+ * regenerates `manifest.gen.ts` in dev when a deck is added/edited, so new decks
15
+ * actually surface in the picker. The committed manifest is the build input.
16
+ */
17
+ declare function mdxManifestPlugin(configs?: ManifestConfig[]): Plugin;
18
+
19
+ /**
20
+ * Serve raw `.mdx?raw` imports as plain strings.
21
+ *
22
+ * Vite's built-in `?raw` works for most files, but `@mdx-js/rollup` strips the
23
+ * query and compiles every `.mdx` to a component — so `?raw` would otherwise
24
+ * yield the compiled module, not the source. This `pre` plugin resolves
25
+ * `*.mdx?raw` to a virtual id ending in `.raw` (not `.mdx`), which MDX's
26
+ * extension check skips, then loads the file text for it.
27
+ */
28
+ declare function mdxRawPlugin(): Plugin;
29
+
30
+ export { type ManifestConfig, mdxManifestPlugin, mdxRawPlugin };
@@ -0,0 +1,121 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import matter from 'gray-matter';
4
+ import fs2 from 'fs/promises';
5
+
6
+ // src/vite/mdx-manifest-plugin.ts
7
+ var DEFAULT_CONFIGS = [
8
+ {
9
+ contentDir: "src/content/slides",
10
+ outputPath: "src/content/slides/manifest.gen.ts"
11
+ }
12
+ ];
13
+ function extractFrontmatter(filePath) {
14
+ try {
15
+ const content = fs.readFileSync(filePath, "utf-8");
16
+ const { data } = matter(content);
17
+ return data;
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+ function generateManifest(root, config) {
23
+ const contentDir = path.resolve(root, config.contentDir);
24
+ if (!fs.existsSync(contentDir)) {
25
+ return "// Auto-generated \u2014 no content found\nexport const manifest: Record<string, never> = {}\n";
26
+ }
27
+ const entries = [];
28
+ const topLevel = fs.readdirSync(contentDir).filter((f) => f.endsWith(".mdx"));
29
+ for (const file of topLevel) {
30
+ const filePath = path.join(contentDir, file);
31
+ if (fs.statSync(filePath).isDirectory()) continue;
32
+ const data = extractFrontmatter(filePath);
33
+ if (data) {
34
+ entries.push({ slug: file.replace(".mdx", ""), data });
35
+ }
36
+ }
37
+ const subdirs = fs.readdirSync(contentDir).filter((f) => {
38
+ const fp = path.join(contentDir, f);
39
+ return fs.statSync(fp).isDirectory();
40
+ });
41
+ for (const subdir of subdirs) {
42
+ const subdirPath = path.join(contentDir, subdir);
43
+ const subFiles = fs.readdirSync(subdirPath).filter((f) => f.endsWith(".mdx"));
44
+ for (const file of subFiles) {
45
+ const data = extractFrontmatter(path.join(subdirPath, file));
46
+ if (data) {
47
+ entries.push({ slug: file.replace(".mdx", ""), data });
48
+ }
49
+ }
50
+ }
51
+ return [
52
+ "// Auto-generated by mdx-manifest-plugin \u2014 do not edit manually",
53
+ `export const manifest = ${JSON.stringify(
54
+ Object.fromEntries(entries.map((e) => [e.slug, e.data])),
55
+ null,
56
+ 2
57
+ )} as const`,
58
+ ""
59
+ ].join("\n");
60
+ }
61
+ function mdxManifestPlugin(configs = DEFAULT_CONFIGS) {
62
+ let root = "";
63
+ return {
64
+ name: "mdx-manifest-plugin",
65
+ configResolved(config) {
66
+ root = config.root;
67
+ },
68
+ buildStart() {
69
+ for (const config of configs) {
70
+ const outputPath = path.resolve(root, config.outputPath);
71
+ const content = generateManifest(root, config);
72
+ const outputDir = path.dirname(outputPath);
73
+ if (!fs.existsSync(outputDir)) {
74
+ fs.mkdirSync(outputDir, { recursive: true });
75
+ }
76
+ fs.writeFileSync(outputPath, content, "utf-8");
77
+ }
78
+ },
79
+ // In dev, regenerate the manifest when a deck MDX file changes.
80
+ handleHotUpdate({ file }) {
81
+ if (!file.endsWith(".mdx")) return;
82
+ for (const config of configs) {
83
+ const contentDir = path.resolve(root, config.contentDir);
84
+ if (file.startsWith(contentDir)) {
85
+ const content = generateManifest(root, config);
86
+ fs.writeFileSync(path.resolve(root, config.outputPath), content, "utf-8");
87
+ }
88
+ }
89
+ }
90
+ };
91
+ }
92
+ var RAW_MDX = /\.mdx\?raw$/;
93
+ var VIRTUAL_PREFIX = "\0mdx-raw:";
94
+ var VIRTUAL_SUFFIX = ".raw";
95
+ function mdxRawPlugin() {
96
+ return {
97
+ name: "mdx-raw",
98
+ enforce: "pre",
99
+ async resolveId(id, importer) {
100
+ if (!RAW_MDX.test(id)) {
101
+ return null;
102
+ }
103
+ const clean = id.replace(/\?raw$/, "");
104
+ const resolved = await this.resolve(clean, importer, { skipSelf: true });
105
+ const filePath = resolved?.id ?? clean;
106
+ return `${VIRTUAL_PREFIX}${filePath}${VIRTUAL_SUFFIX}`;
107
+ },
108
+ async load(id) {
109
+ if (!id.startsWith(VIRTUAL_PREFIX)) {
110
+ return null;
111
+ }
112
+ const filePath = id.slice(VIRTUAL_PREFIX.length, id.length - VIRTUAL_SUFFIX.length);
113
+ const code = await fs2.readFile(filePath, "utf-8");
114
+ return { code: `export default ${JSON.stringify(code)}`, map: null };
115
+ }
116
+ };
117
+ }
118
+
119
+ export { mdxManifestPlugin, mdxRawPlugin };
120
+ //# sourceMappingURL=index.js.map
121
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/vite/mdx-manifest-plugin.ts","../../src/vite/mdx-raw-plugin.ts"],"names":["fs"],"mappings":"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","file":"index.js","sourcesContent":["import fs from 'node:fs'\nimport path from 'node:path'\nimport matter from 'gray-matter'\nimport type { Plugin } from 'vite'\n\nexport interface ManifestConfig {\n /** Content directory relative to project root */\n contentDir: string\n /** Output manifest file path relative to project root */\n outputPath: string\n}\n\nconst DEFAULT_CONFIGS: ManifestConfig[] = [\n {\n contentDir: 'src/content/slides',\n outputPath: 'src/content/slides/manifest.gen.ts',\n },\n]\n\nfunction extractFrontmatter(filePath: string): Record<string, unknown> | null {\n try {\n const content = fs.readFileSync(filePath, 'utf-8')\n const { data } = matter(content)\n return data\n } catch {\n return null\n }\n}\n\nfunction generateManifest(root: string, config: ManifestConfig): string {\n const contentDir = path.resolve(root, config.contentDir)\n\n if (!fs.existsSync(contentDir)) {\n return '// Auto-generated — no content found\\nexport const manifest: Record<string, never> = {}\\n'\n }\n\n const entries: Array<{ slug: string; data: Record<string, unknown> }> = []\n\n const topLevel = fs.readdirSync(contentDir).filter(f => f.endsWith('.mdx'))\n for (const file of topLevel) {\n const filePath = path.join(contentDir, file)\n if (fs.statSync(filePath).isDirectory()) continue\n const data = extractFrontmatter(filePath)\n if (data) {\n entries.push({ slug: file.replace('.mdx', ''), data })\n }\n }\n\n // Also scan one level of subdirectories for nested content groups.\n const subdirs = fs.readdirSync(contentDir).filter(f => {\n const fp = path.join(contentDir, f)\n return fs.statSync(fp).isDirectory()\n })\n for (const subdir of subdirs) {\n const subdirPath = path.join(contentDir, subdir)\n const subFiles = fs.readdirSync(subdirPath).filter(f => f.endsWith('.mdx'))\n for (const file of subFiles) {\n const data = extractFrontmatter(path.join(subdirPath, file))\n if (data) {\n entries.push({ slug: file.replace('.mdx', ''), data })\n }\n }\n }\n\n return [\n '// Auto-generated by mdx-manifest-plugin — do not edit manually',\n `export const manifest = ${JSON.stringify(\n Object.fromEntries(entries.map(e => [e.slug, e.data])),\n null,\n 2\n )} as const`,\n '',\n ].join('\\n')\n}\n\n/**\n * Vite plugin that extracts slide-deck MDX frontmatter into a static manifest.\n *\n * Mirrors the atom63.io plugin: it decouples metadata from MDX module loading\n * (so Rollup can code-split decks into lazy chunks) and — critically for os63 —\n * regenerates `manifest.gen.ts` in dev when a deck is added/edited, so new decks\n * actually surface in the picker. The committed manifest is the build input.\n */\nexport function mdxManifestPlugin(configs: ManifestConfig[] = DEFAULT_CONFIGS): Plugin {\n let root = ''\n\n return {\n name: 'mdx-manifest-plugin',\n\n configResolved(config) {\n root = config.root\n },\n\n buildStart() {\n for (const config of configs) {\n const outputPath = path.resolve(root, config.outputPath)\n const content = generateManifest(root, config)\n const outputDir = path.dirname(outputPath)\n if (!fs.existsSync(outputDir)) {\n fs.mkdirSync(outputDir, { recursive: true })\n }\n fs.writeFileSync(outputPath, content, 'utf-8')\n }\n },\n\n // In dev, regenerate the manifest when a deck MDX file changes.\n handleHotUpdate({ file }) {\n if (!file.endsWith('.mdx')) return\n for (const config of configs) {\n const contentDir = path.resolve(root, config.contentDir)\n if (file.startsWith(contentDir)) {\n const content = generateManifest(root, config)\n fs.writeFileSync(path.resolve(root, config.outputPath), content, 'utf-8')\n }\n }\n },\n }\n}\n","import fs from 'node:fs/promises'\nimport type { Plugin } from 'vite'\n\nconst RAW_MDX = /\\.mdx\\?raw$/\nconst VIRTUAL_PREFIX = '\\0mdx-raw:'\nconst VIRTUAL_SUFFIX = '.raw'\n\n/**\n * Serve raw `.mdx?raw` imports as plain strings.\n *\n * Vite's built-in `?raw` works for most files, but `@mdx-js/rollup` strips the\n * query and compiles every `.mdx` to a component — so `?raw` would otherwise\n * yield the compiled module, not the source. This `pre` plugin resolves\n * `*.mdx?raw` to a virtual id ending in `.raw` (not `.mdx`), which MDX's\n * extension check skips, then loads the file text for it.\n */\nexport function mdxRawPlugin(): Plugin {\n return {\n name: 'mdx-raw',\n enforce: 'pre',\n async resolveId(id, importer) {\n if (!RAW_MDX.test(id)) {\n return null\n }\n const clean = id.replace(/\\?raw$/, '')\n const resolved = await this.resolve(clean, importer, { skipSelf: true })\n const filePath = resolved?.id ?? clean\n return `${VIRTUAL_PREFIX}${filePath}${VIRTUAL_SUFFIX}`\n },\n async load(id) {\n if (!id.startsWith(VIRTUAL_PREFIX)) {\n return null\n }\n const filePath = id.slice(VIRTUAL_PREFIX.length, id.length - VIRTUAL_SUFFIX.length)\n const code = await fs.readFile(filePath, 'utf-8')\n return { code: `export default ${JSON.stringify(code)}`, map: null }\n },\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,139 @@
1
+ {
2
+ "name": "@atom63/slides",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/ATOM63/atom63-vite.git",
8
+ "directory": "packages/slides"
9
+ },
10
+ "homepage": "https://github.com/ATOM63/atom63-vite/tree/main/packages/slides#readme",
11
+ "type": "module",
12
+ "description": "Host-agnostic MDX slide presentation engine — an opinionated template grammar + token theming for building decks in MDX.",
13
+ "keywords": [
14
+ "slides",
15
+ "presentation",
16
+ "mdx",
17
+ "react",
18
+ "deck",
19
+ "slideshow",
20
+ "tailwind",
21
+ "design-system"
22
+ ],
23
+ "publishConfig": {
24
+ "access": "public",
25
+ "registry": "https://registry.npmjs.org/"
26
+ },
27
+ "main": "./dist/index.js",
28
+ "module": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "exports": {
31
+ ".": {
32
+ "typescript": "./src/index.ts",
33
+ "types": "./dist/index.d.ts",
34
+ "import": "./dist/index.js"
35
+ },
36
+ "./vite": {
37
+ "typescript": "./src/vite/index.ts",
38
+ "types": "./dist/vite/index.d.ts",
39
+ "import": "./dist/vite/index.js"
40
+ },
41
+ "./editor": {
42
+ "typescript": "./src/editor/index.ts",
43
+ "types": "./dist/editor/index.d.ts",
44
+ "import": "./dist/editor/index.js"
45
+ },
46
+ "./editor/styles": {
47
+ "types": "./src/editor/styles.d.ts",
48
+ "default": "./src/editor/styles.css"
49
+ },
50
+ "./styles": {
51
+ "types": "./src/styles/styles.d.ts",
52
+ "default": "./src/styles/slides.css"
53
+ },
54
+ "./theme-defaults": {
55
+ "types": "./src/styles/theme-defaults.d.ts",
56
+ "default": "./src/styles/theme-defaults.css"
57
+ },
58
+ "./themes/dark": {
59
+ "default": "./src/styles/themes/dark.css"
60
+ },
61
+ "./themes/terminal": {
62
+ "default": "./src/styles/themes/terminal.css"
63
+ },
64
+ "./themes/editorial": {
65
+ "default": "./src/styles/themes/editorial.css"
66
+ },
67
+ "./themes/neon": {
68
+ "default": "./src/styles/themes/neon.css"
69
+ },
70
+ "./themes/bold": {
71
+ "default": "./src/styles/themes/bold.css"
72
+ }
73
+ },
74
+ "sideEffects": [
75
+ "*.css"
76
+ ],
77
+ "files": [
78
+ "dist",
79
+ "src/styles/slides.css",
80
+ "src/styles/theme-defaults.css",
81
+ "src/styles/themes/dark.css",
82
+ "src/styles/themes/terminal.css",
83
+ "src/styles/themes/editorial.css",
84
+ "src/styles/themes/neon.css",
85
+ "src/styles/themes/bold.css",
86
+ "src/editor/styles.css",
87
+ "src/editor/styles.d.ts"
88
+ ],
89
+ "dependencies": {
90
+ "@base-ui/react": "^1.3.0",
91
+ "@iconify/react": "^6.0.2",
92
+ "class-variance-authority": "^0.7.1",
93
+ "@mdx-js/mdx": "^3.1.1",
94
+ "@mdx-js/react": "^3.1.1",
95
+ "@tanstack/react-virtual": "^3.14.2",
96
+ "clsx": "^2.1.1",
97
+ "gray-matter": "^4.0.3",
98
+ "js-yaml": "^4.1.0",
99
+ "lucide-react": "^1.18.0",
100
+ "motion": "^12.40.0",
101
+ "remark-gfm": "^4.0.1",
102
+ "shiki": "^3.23.0",
103
+ "tailwind-merge": "^3.6.0",
104
+ "zustand": "^5.0.0"
105
+ },
106
+ "peerDependencies": {
107
+ "react": "^19.1.0",
108
+ "react-dom": "^19.1.0",
109
+ "sonner": "^2.0.7",
110
+ "vite": "^5 || ^6 || ^7"
111
+ },
112
+ "peerDependenciesMeta": {
113
+ "vite": {
114
+ "optional": true
115
+ }
116
+ },
117
+ "devDependencies": {
118
+ "@biomejs/biome": "2.4.6",
119
+ "@types/js-yaml": "^4.0.9",
120
+ "@types/node": "^22.10.0",
121
+ "@types/react": "^19.1.13",
122
+ "@types/react-dom": "^19.1.9",
123
+ "react": "^19.1.1",
124
+ "react-dom": "^19.1.1",
125
+ "tsup": "^8.3.5",
126
+ "typescript": "~5.8.3",
127
+ "vite": "^7.1.5",
128
+ "vitest": "^4.1.8",
129
+ "@atom63/biome-config": "0.1.0",
130
+ "@atom63/tsconfig": "0.1.0"
131
+ },
132
+ "scripts": {
133
+ "build": "tsup",
134
+ "dev": "tsup --watch",
135
+ "test": "vitest run",
136
+ "typecheck": "tsc --noEmit",
137
+ "lint": "biome check ."
138
+ }
139
+ }
@@ -0,0 +1,206 @@
1
+ /* @atom63/slides/editor — minimal editor chrome.
2
+ *
3
+ * Layout only: a two-pane split (preview | source) with a sized preview area
4
+ * (SlidesPlayer needs an explicit height). Visual theming of the slides
5
+ * themselves comes from @atom63/slides ("/styles" + "/theme-defaults"), imported
6
+ * by the host app — NOT here.
7
+ */
8
+
9
+ .a63-editor {
10
+ display: grid;
11
+ grid-template-columns: minmax(0, 1fr) minmax(320px, 0.85fr);
12
+ gap: 0;
13
+ width: 100%;
14
+ height: 100%;
15
+ min-height: 0;
16
+ font-family:
17
+ ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
18
+ }
19
+
20
+ @media (max-width: 880px) {
21
+ .a63-editor {
22
+ grid-template-columns: minmax(0, 1fr);
23
+ grid-template-rows: minmax(240px, 1fr) minmax(0, 1fr);
24
+ }
25
+ }
26
+
27
+ /* Preview pane — must give SlidesPlayer a sized container. */
28
+ .a63-editor__preview {
29
+ position: relative;
30
+ min-width: 0;
31
+ min-height: 0;
32
+ overflow: hidden;
33
+ border-right: 1px solid rgba(127, 127, 127, 0.18);
34
+ }
35
+
36
+ .a63-editor__preview-stage {
37
+ position: absolute;
38
+ inset: 0;
39
+ }
40
+
41
+ .a63-editor__preview-empty {
42
+ display: flex;
43
+ align-items: center;
44
+ justify-content: center;
45
+ height: 100%;
46
+ padding: 2rem;
47
+ text-align: center;
48
+ font-size: 0.875rem;
49
+ opacity: 0.6;
50
+ }
51
+
52
+ /* Error banner — non-blocking; overlays the top of the preview. */
53
+ .a63-editor__error {
54
+ position: absolute;
55
+ top: 0;
56
+ left: 0;
57
+ right: 0;
58
+ z-index: 5;
59
+ display: flex;
60
+ gap: 0.5rem;
61
+ align-items: flex-start;
62
+ padding: 0.5rem 0.75rem;
63
+ background: #b91c1c;
64
+ color: #fff;
65
+ font-size: 0.78rem;
66
+ line-height: 1.35;
67
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
68
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
69
+ }
70
+
71
+ .a63-editor__error-tag {
72
+ flex: none;
73
+ font-weight: 700;
74
+ text-transform: uppercase;
75
+ letter-spacing: 0.04em;
76
+ }
77
+
78
+ .a63-editor__error-msg {
79
+ flex: 1;
80
+ min-width: 0;
81
+ white-space: pre-wrap;
82
+ word-break: break-word;
83
+ }
84
+
85
+ /* Source pane. */
86
+ .a63-editor__source {
87
+ display: flex;
88
+ flex-direction: column;
89
+ min-width: 0;
90
+ min-height: 0;
91
+ background: #f6f6f7;
92
+ color: #1a1a1a;
93
+ }
94
+
95
+ .a63-editor__toolbar {
96
+ display: flex;
97
+ align-items: center;
98
+ justify-content: space-between;
99
+ gap: 0.75rem;
100
+ padding: 0.5rem 0.75rem;
101
+ border-bottom: 1px solid rgba(127, 127, 127, 0.18);
102
+ flex: none;
103
+ }
104
+
105
+ .a63-editor__title {
106
+ font-size: 0.8rem;
107
+ font-weight: 600;
108
+ letter-spacing: 0.01em;
109
+ }
110
+
111
+ .a63-editor__theme-toggle {
112
+ display: inline-flex;
113
+ align-items: center;
114
+ gap: 0.4rem;
115
+ padding: 0.25rem 0.6rem;
116
+ border: 1px solid rgba(127, 127, 127, 0.4);
117
+ border-radius: 999px;
118
+ background: transparent;
119
+ color: inherit;
120
+ font-size: 0.72rem;
121
+ font-weight: 600;
122
+ cursor: pointer;
123
+ }
124
+
125
+ .a63-editor__theme-toggle:hover {
126
+ background: rgba(127, 127, 127, 0.12);
127
+ }
128
+
129
+ .a63-editor__textarea {
130
+ flex: 1;
131
+ min-height: 0;
132
+ width: 100%;
133
+ resize: none;
134
+ border: 0;
135
+ padding: 0.75rem;
136
+ background: transparent;
137
+ color: inherit;
138
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
139
+ font-size: 0.8rem;
140
+ line-height: 1.55;
141
+ tab-size: 2;
142
+ outline: none;
143
+ }
144
+
145
+ /* Template palette. */
146
+ .a63-editor__palette {
147
+ flex: none;
148
+ max-height: 38%;
149
+ overflow-y: auto;
150
+ border-top: 1px solid rgba(127, 127, 127, 0.18);
151
+ padding: 0.5rem 0.75rem 0.75rem;
152
+ }
153
+
154
+ .a63-editor__palette-label {
155
+ font-size: 0.7rem;
156
+ font-weight: 700;
157
+ text-transform: uppercase;
158
+ letter-spacing: 0.06em;
159
+ opacity: 0.55;
160
+ margin-bottom: 0.5rem;
161
+ }
162
+
163
+ .a63-editor__palette-grid {
164
+ display: grid;
165
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
166
+ gap: 0.4rem;
167
+ }
168
+
169
+ .a63-editor__chip {
170
+ display: flex;
171
+ flex-direction: column;
172
+ align-items: flex-start;
173
+ gap: 0.1rem;
174
+ padding: 0.4rem 0.55rem;
175
+ border: 1px solid rgba(127, 127, 127, 0.3);
176
+ border-radius: 0.5rem;
177
+ background: #fff;
178
+ color: inherit;
179
+ text-align: left;
180
+ cursor: pointer;
181
+ transition:
182
+ border-color 0.12s ease,
183
+ transform 0.12s ease;
184
+ }
185
+
186
+ .a63-editor__chip:hover {
187
+ border-color: rgba(59, 130, 246, 0.8);
188
+ transform: translateY(-1px);
189
+ }
190
+
191
+ .a63-editor__chip-name {
192
+ font-size: 0.72rem;
193
+ font-weight: 600;
194
+ }
195
+
196
+ .a63-editor__chip-meta {
197
+ font-size: 0.62rem;
198
+ opacity: 0.55;
199
+ }
200
+
201
+ .a63-editor__footnote {
202
+ margin-top: 0.6rem;
203
+ font-size: 0.62rem;
204
+ opacity: 0.5;
205
+ line-height: 1.4;
206
+ }
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,30 @@
1
+ /* =============================================================================
2
+ SLIDE REVEAL ANIMATIONS
3
+ Opt-in entrance animation for slide content. Add class `reveal` and set
4
+ `--stagger-index` inline style on any element to animate it in when the
5
+ slide becomes visible. Replays on every slide navigation.
6
+ ============================================================================= */
7
+
8
+ @keyframes slide-reveal {
9
+ from {
10
+ opacity: 0;
11
+ transform: translateY(18px);
12
+ }
13
+ to {
14
+ opacity: 1;
15
+ transform: translateY(0);
16
+ }
17
+ }
18
+
19
+ .reveal {
20
+ animation: slide-reveal 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) both;
21
+ animation-delay: calc(var(--stagger-index, 0) * 80ms);
22
+ }
23
+
24
+ @media (prefers-reduced-motion: reduce) {
25
+ .reveal {
26
+ opacity: 1;
27
+ transform: none;
28
+ animation: none;
29
+ }
30
+ }