@deckio/deck-engine 1.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,9 @@
1
+ import styles from './BottomBar.module.css'
2
+
3
+ export default function BottomBar({ text }) {
4
+ return (
5
+ <div className={styles.bar}>
6
+ <span>{text || <>GitHub Copilot &nbsp;&middot;&nbsp; Reimagine Software Development</>}</span>
7
+ </div>
8
+ )
9
+ }
@@ -0,0 +1,17 @@
1
+ .bar {
2
+ position: absolute;
3
+ bottom: 0; left: 0; right: 0;
4
+ height: 44px;
5
+ background: rgba(13,17,23,0.85);
6
+ backdrop-filter: blur(12px);
7
+ border-top: 1px solid var(--border);
8
+ display: flex;
9
+ align-items: center;
10
+ justify-content: space-between;
11
+ padding: 0 40px;
12
+ font-size: 11px;
13
+ letter-spacing: 2.5px;
14
+ text-transform: uppercase;
15
+ color: var(--text-muted);
16
+ z-index: 100;
17
+ }
@@ -0,0 +1,106 @@
1
+ import { useSlides } from '../context/SlideContext'
2
+ import styles from './Navigation.module.css'
3
+ import { useState, useEffect, useRef } from 'react'
4
+
5
+ function resolveProp(value, context) {
6
+ return typeof value === 'function' ? value(context) : value
7
+ }
8
+
9
+ export default function Navigation({ pdfPath = null, pdfLabel = 'Deck PDF' }) {
10
+ const { current, totalSlides, go, goTo, selectedCustomer, project } = useSlides()
11
+ const [hintVisible, setHintVisible] = useState(true)
12
+ const [idle, setIdle] = useState(false)
13
+ const timerRef = useRef(null)
14
+
15
+ useEffect(() => {
16
+ const t = setTimeout(() => setHintVisible(false), 5000)
17
+ return () => clearTimeout(t)
18
+ }, [])
19
+
20
+ useEffect(() => {
21
+ const resetIdle = () => {
22
+ setIdle(false)
23
+ clearTimeout(timerRef.current)
24
+ timerRef.current = setTimeout(() => setIdle(true), 2000)
25
+ }
26
+ resetIdle()
27
+ window.addEventListener('mousemove', resetIdle)
28
+ window.addEventListener('mousedown', resetIdle)
29
+ return () => {
30
+ window.removeEventListener('mousemove', resetIdle)
31
+ window.removeEventListener('mousedown', resetIdle)
32
+ clearTimeout(timerRef.current)
33
+ }
34
+ }, [])
35
+
36
+ const progress = ((current + 1) / totalSlides) * 100
37
+ const navigationState = { current, totalSlides, selectedCustomer, project }
38
+ const resolvedPdfPath = resolveProp(pdfPath, navigationState)
39
+ const resolvedPdfLabel = resolveProp(pdfLabel, navigationState) || 'Deck PDF'
40
+
41
+ return (
42
+ <div className={`${styles.navWrapper} ${idle ? styles.navHidden : ''}`}>
43
+ <div className={styles.progressTrack}>
44
+ <div className={styles.progressFill} style={{ width: `${progress}%` }} />
45
+ </div>
46
+
47
+ {current !== 0 && (
48
+ <button
49
+ className={styles.homeBtn}
50
+ onClick={() => goTo(0)}
51
+ title="Back to home"
52
+ >
53
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
54
+ <path d="M3 12l9-9 9 9" />
55
+ <path d="M9 21V9h6v12" />
56
+ </svg>
57
+ </button>
58
+ )}
59
+
60
+ {resolvedPdfPath && (
61
+ <a
62
+ className={styles.exportBtn}
63
+ href={resolvedPdfPath}
64
+ target="_blank"
65
+ rel="noopener noreferrer"
66
+ title={resolvedPdfLabel}
67
+ >
68
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
69
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
70
+ <polyline points="14 2 14 8 20 8" />
71
+ <line x1="16" y1="13" x2="8" y2="13" />
72
+ <line x1="16" y1="17" x2="8" y2="17" />
73
+ <polyline points="10 9 9 9 8 9" />
74
+ </svg>
75
+ <span className={styles.exportLabel}>PDF</span>
76
+ </a>
77
+ )}
78
+
79
+ <button
80
+ className={`${styles.navBtn} ${styles.prev}`}
81
+ disabled={current === 0}
82
+ onClick={() => go(-1)}
83
+ >
84
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
85
+ <polyline points="15 18 9 12 15 6" />
86
+ </svg>
87
+ </button>
88
+ <button
89
+ className={`${styles.navBtn} ${styles.next}`}
90
+ disabled={current === totalSlides - 1}
91
+ onClick={() => go(1)}
92
+ >
93
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
94
+ <polyline points="9 6 15 12 9 18" />
95
+ </svg>
96
+ </button>
97
+
98
+ {hintVisible && (
99
+ <div className={styles.keyHint}>
100
+ <span className={styles.kbd}>&larr;</span>
101
+ <span className={styles.kbd}>&rarr;</span> or click arrows to navigate
102
+ </div>
103
+ )}
104
+ </div>
105
+ )
106
+ }
@@ -0,0 +1,145 @@
1
+ .navWrapper {
2
+ transition: opacity 0.4s ease;
3
+ opacity: 1;
4
+ }
5
+
6
+ .navHidden {
7
+ opacity: 0;
8
+ pointer-events: none;
9
+ }
10
+
11
+ .progressTrack {
12
+ position: fixed;
13
+ top: 0; left: 0; right: 0;
14
+ height: 3px;
15
+ background: rgba(48,54,61,0.6);
16
+ z-index: 300;
17
+ }
18
+
19
+ .progressFill {
20
+ height: 100%;
21
+ background: linear-gradient(90deg, var(--purple), var(--accent), var(--cyan));
22
+ transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
23
+ border-radius: 0 2px 2px 0;
24
+ box-shadow: 0 0 12px var(--accent);
25
+ }
26
+
27
+ .homeBtn {
28
+ position: fixed;
29
+ top: 16px;
30
+ left: 20px;
31
+ z-index: 200;
32
+ width: 36px; height: 36px;
33
+ border-radius: 10px;
34
+ border: 1px solid var(--border);
35
+ background: rgba(22,27,34,0.8);
36
+ backdrop-filter: blur(8px);
37
+ color: var(--text);
38
+ cursor: pointer;
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: center;
42
+ transition: all 0.25s ease;
43
+ opacity: 0.45;
44
+ }
45
+ .homeBtn:hover {
46
+ opacity: 1;
47
+ border-color: var(--accent);
48
+ background: rgba(31,111,235,0.2);
49
+ }
50
+ .homeBtn svg {
51
+ width: 16px; height: 16px;
52
+ }
53
+
54
+ .exportBtn {
55
+ position: fixed;
56
+ top: 16px;
57
+ right: 20px;
58
+ z-index: 200;
59
+ height: 36px;
60
+ min-width: 36px;
61
+ padding: 0 12px;
62
+ border-radius: 10px;
63
+ border: 1px solid var(--border);
64
+ background: rgba(22,27,34,0.8);
65
+ backdrop-filter: blur(8px);
66
+ color: var(--text);
67
+ cursor: pointer;
68
+ display: flex;
69
+ align-items: center;
70
+ justify-content: center;
71
+ gap: 6px;
72
+ transition: all 0.25s ease;
73
+ opacity: 0.45;
74
+ text-decoration: none;
75
+ font-family: inherit;
76
+ }
77
+ .exportBtn:hover {
78
+ opacity: 1;
79
+ border-color: var(--accent);
80
+ background: rgba(31,111,235,0.2);
81
+ }
82
+ .exportBtn svg {
83
+ width: 16px; height: 16px;
84
+ }
85
+ .exportLabel {
86
+ font-size: 11px;
87
+ font-weight: 600;
88
+ letter-spacing: 0.04em;
89
+ text-transform: uppercase;
90
+ }
91
+
92
+ .navBtn {
93
+ position: fixed;
94
+ top: 50%;
95
+ transform: translateY(-50%);
96
+ z-index: 200;
97
+ width: 48px; height: 48px;
98
+ border-radius: 50%;
99
+ border: 1px solid var(--border);
100
+ background: rgba(22,27,34,0.8);
101
+ backdrop-filter: blur(8px);
102
+ color: var(--text);
103
+ cursor: pointer;
104
+ display: flex;
105
+ align-items: center;
106
+ justify-content: center;
107
+ transition: all 0.25s ease;
108
+ opacity: 0.5;
109
+ }
110
+ .navBtn:hover { opacity: 1; border-color: var(--accent); background: rgba(31,111,235,0.2); }
111
+ .prev { left: 20px; }
112
+ .next { right: 20px; }
113
+ .navBtn svg { width: 20px; height: 20px; }
114
+ .navBtn:disabled { opacity: 0.15; cursor: default; pointer-events: none; }
115
+
116
+ .keyHint {
117
+ position: fixed;
118
+ bottom: 56px; right: 40px;
119
+ z-index: 200;
120
+ display: flex;
121
+ align-items: center;
122
+ gap: 6px;
123
+ font-size: 11px;
124
+ color: var(--text-muted);
125
+ opacity: 0.5;
126
+ animation: fadeOut 3s 5s forwards;
127
+ }
128
+
129
+ @keyframes fadeOut {
130
+ to { opacity: 0; }
131
+ }
132
+
133
+ .kbd {
134
+ display: inline-flex;
135
+ align-items: center;
136
+ justify-content: center;
137
+ min-width: 22px; height: 22px;
138
+ padding: 0 5px;
139
+ border: 1px solid var(--border);
140
+ border-radius: 4px;
141
+ background: var(--surface);
142
+ font-size: 10px;
143
+ font-weight: 600;
144
+ font-family: inherit;
145
+ }
@@ -0,0 +1,15 @@
1
+ import { useSlides } from '../context/SlideContext'
2
+
3
+ export default function Slide({ index, className = '', children }) {
4
+ const { current } = useSlides()
5
+
6
+ let stateClass = ''
7
+ if (index === current) stateClass = 'active'
8
+ else if (index < current) stateClass = 'exit-left'
9
+
10
+ return (
11
+ <div className={`slide ${stateClass} ${className}`} data-slide={index}>
12
+ {children}
13
+ </div>
14
+ )
15
+ }
@@ -0,0 +1,171 @@
1
+ import {
2
+ createContext,
3
+ useContext,
4
+ useState,
5
+ useCallback,
6
+ useEffect,
7
+ } from 'react'
8
+
9
+ /* ╔══════════════════════════════════════════════════════════════╗
10
+ * ║ ║
11
+ * ║ ▂▃▅▇█ S L I D E C O N T E X T █▇▅▃▂ ║
12
+ * ║ ║
13
+ * ║ Central state for slide navigation, persistence, ║
14
+ * ║ keyboard / touch input, and customer selection. ║
15
+ * ║ ║
16
+ * ╚══════════════════════════════════════════════════════════════╝ */
17
+
18
+ const SlideContext = createContext()
19
+
20
+ /* ┌─────────────────────────────────────────────────────────────┐
21
+ * │ ◆ H E L P E R S │
22
+ * └─────────────────────────────────────────────────────────────┘ */
23
+
24
+ /**
25
+ * Recover the last-viewed slide from sessionStorage.
26
+ * Survives Vite HMR so you stay on the same slide during dev.
27
+ *
28
+ * sessionStorage key format: slide:<project>
29
+ * returns 0 when nothing stored or value is out of range.
30
+ */
31
+ function getStoredSlide(project, totalSlides) {
32
+ try {
33
+ const idx = parseInt(sessionStorage.getItem(`slide:${project}`), 10)
34
+ return Number.isFinite(idx) && idx >= 0 && idx < totalSlides ? idx : 0
35
+ } catch {
36
+ return 0
37
+ }
38
+ }
39
+
40
+ /* ╭──────────────────────────────────────────────────────────────╮
41
+ * │ ◈ P R O V I D E R │
42
+ * ╰──────────────────────────────────────────────────────────────╯ */
43
+
44
+ export function SlideProvider({ children, totalSlides, project, slides }) {
45
+ const [current, setCurrent] = useState(() =>
46
+ getStoredSlide(project, totalSlides),
47
+ )
48
+ const [selectedCustomer, setSelectedCustomer] = useState(null)
49
+
50
+ /* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
51
+ * ░ Persist slide index ─ HMR keeps position ░
52
+ * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ */
53
+
54
+ useEffect(() => {
55
+ try {
56
+ sessionStorage.setItem(`slide:${project}`, current)
57
+ } catch {
58
+ /* storage full / unavailable – ignore */
59
+ }
60
+ }, [current, project])
61
+
62
+ /* 📡 ─────────────────────────────────────────────
63
+ * │ Notify parent window of slide changes │
64
+ * │ (used by deck-launcher to provide context) │
65
+ * ───────────────────────────────────────── 📡 */
66
+
67
+ useEffect(() => {
68
+ try {
69
+ if (window.parent && window.parent !== window) {
70
+ const slideName = slides?.[current]?.displayName || slides?.[current]?.name || ''
71
+ window.parent.postMessage({
72
+ type: 'deck:slide',
73
+ project,
74
+ slideIndex: current,
75
+ slideName,
76
+ totalSlides,
77
+ }, '*')
78
+ }
79
+ } catch {
80
+ /* cross-origin or non-iframe – ignore */
81
+ }
82
+ }, [current, project, totalSlides, slides])
83
+
84
+ /* ▸ ▸ ▸ Navigation helpers ◂ ◂ ◂ */
85
+
86
+ const go = useCallback(
87
+ (dir) => {
88
+ setCurrent((prev) => {
89
+ const next = prev + dir
90
+ return next < 0 || next >= totalSlides ? prev : next
91
+ })
92
+ },
93
+ [totalSlides],
94
+ )
95
+
96
+ const goTo = useCallback(
97
+ (idx) => {
98
+ if (idx >= 0 && idx < totalSlides) setCurrent(idx)
99
+ },
100
+ [totalSlides],
101
+ )
102
+
103
+ /* ⌨ ─────────────────────────────────────────────────────
104
+ * │ Keyboard → ← Space PageDown PageUp Enter │
105
+ * ───────────────────────────────────────────────── ⌨ */
106
+
107
+ useEffect(() => {
108
+ const handler = (e) => {
109
+ if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'PageDown' || e.key === 'Enter') {
110
+ e.preventDefault()
111
+ go(1)
112
+ }
113
+ if (e.key === 'ArrowLeft' || e.key === 'PageUp') {
114
+ e.preventDefault()
115
+ go(-1)
116
+ }
117
+ }
118
+
119
+ document.addEventListener('keydown', handler)
120
+ return () => document.removeEventListener('keydown', handler)
121
+ }, [go])
122
+
123
+ /* 👆 ─────────────────────────────────
124
+ * │ Touch / swipe (threshold 50px) │
125
+ * ───────────────────────────── 👆 */
126
+
127
+ useEffect(() => {
128
+ let touchX = 0
129
+
130
+ const onStart = (e) => {
131
+ touchX = e.changedTouches[0].screenX
132
+ }
133
+ const onEnd = (e) => {
134
+ const diff = touchX - e.changedTouches[0].screenX
135
+ if (Math.abs(diff) > 50) go(diff > 0 ? 1 : -1)
136
+ }
137
+
138
+ document.addEventListener('touchstart', onStart)
139
+ document.addEventListener('touchend', onEnd)
140
+ return () => {
141
+ document.removeEventListener('touchstart', onStart)
142
+ document.removeEventListener('touchend', onEnd)
143
+ }
144
+ }, [go])
145
+
146
+ /* ◇─────────────── render ───────────────◇ */
147
+
148
+ return (
149
+ <SlideContext.Provider
150
+ value={{
151
+ current,
152
+ totalSlides,
153
+ go,
154
+ goTo,
155
+ selectedCustomer,
156
+ setSelectedCustomer,
157
+ project,
158
+ }}
159
+ >
160
+ {children}
161
+ </SlideContext.Provider>
162
+ )
163
+ }
164
+
165
+ /* ┌─────────────────────────────────────────────────────────────┐
166
+ * │ ◆ H O O K │
167
+ * └─────────────────────────────────────────────────────────────┘ */
168
+
169
+ export function useSlides() {
170
+ return useContext(SlideContext)
171
+ }
package/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { SlideProvider, useSlides } from './context/SlideContext.jsx'
2
+ export { default as Slide } from './components/Slide.jsx'
3
+ export { default as Navigation } from './components/Navigation.jsx'
4
+ export { default as BottomBar } from './components/BottomBar.jsx'
5
+ export { default as GenericThankYouSlide } from './slides/GenericThankYouSlide.jsx'
@@ -0,0 +1,26 @@
1
+ # Deck Project
2
+
3
+ This is a presentation deck built with `@deckio/deck-engine`.
4
+
5
+ ## Purpose
6
+
7
+ Create and maintain slide-based presentations. Each project is a self-contained deck with its own theme, data, and slides.
8
+
9
+ ## What to do
10
+
11
+ - Create, edit, and delete slides in `src/slides/`
12
+ - Manage project data in `src/data/`
13
+ - Register and reorder slides in `deck.config.js`
14
+
15
+ ## What NOT to do
16
+
17
+ - Do not modify `App.jsx`, `main.jsx`, `vite.config.js`, `package.json`, or `index.html` — these are scaffolding files driven by the engine
18
+ - Do not modify anything in `node_modules/` or the engine itself
19
+ - Do not add dependencies without being asked
20
+
21
+ ## Stack
22
+
23
+ - React 19, Vite, CSS Modules
24
+ - `@deckio/deck-engine` provides: `Slide`, `BottomBar`, `Navigation`, `SlideProvider`, `useSlides`, `GenericThankYouSlide`
25
+ - See `.github/instructions/` for detailed conventions on slide JSX, CSS modules, and deck.config.js
26
+ - See `.github/skills/` for step-by-step workflows (e.g., adding a slide)
@@ -0,0 +1,34 @@
1
+ ---
2
+ description: "Use when editing deck.config.js to register slides or modify project configuration."
3
+ applyTo: "**/deck.config.js"
4
+ ---
5
+
6
+ # deck.config.js Conventions
7
+
8
+ ## Structure
9
+
10
+ ```js
11
+ import CoverSlide from './src/slides/CoverSlide.jsx'
12
+ import MySlide from './src/slides/MySlide.jsx'
13
+
14
+ export default {
15
+ id: 'my-project', // lowercase-hyphens, unique slug
16
+ title: 'Project Title',
17
+ subtitle: 'Tagline',
18
+ description: 'Metadata',
19
+ icon: '🚀', // emoji for launcher
20
+ accent: '#7c3aed', // CSS color, sets --accent
21
+ order: 1, // sort position in launcher
22
+ slides: [
23
+ CoverSlide,
24
+ MySlide,
25
+ // ThankYouSlide is typically last
26
+ ],
27
+ }
28
+ ```
29
+
30
+ ## Registering a new slide
31
+
32
+ 1. Add an import at the top: `import NewSlide from './src/slides/NewSlide.jsx'`
33
+ 2. Insert the component in the `slides` array at the desired position
34
+ 3. Indices are assigned by array position — do not manage them manually
@@ -0,0 +1,34 @@
1
+ ---
2
+ description: "Use when working in any deck-project folder. Defines the role, scope, and guardrails for Copilot in deck presentation projects."
3
+ applyTo: "deck-project-*/**"
4
+ ---
5
+
6
+ # Deck Project Scope
7
+
8
+ You are a **slide builder** for a presentation deck built with `@deckio/deck-engine`.
9
+
10
+ ## Your role
11
+
12
+ - Create new slide components (JSX + CSS module pairs)
13
+ - Edit existing slides
14
+ - Manage data files in `src/data/`
15
+ - Register / reorder slides in `deck.config.js`
16
+
17
+ ## Out of scope — do NOT modify
18
+
19
+ - `App.jsx`, `main.jsx` — these are generic, engine-driven, identical across projects
20
+ - `vite.config.js`, `package.json`, `index.html` — project scaffolding, don't touch
21
+ - Anything in `node_modules/` or the deck-engine package itself
22
+
23
+ ## Project architecture
24
+
25
+ - `deck.config.js` — single source of truth: metadata + slide array
26
+ - `src/slides/` — one `PascalCase.jsx` + matching `.module.css` per slide
27
+ - `src/data/` — ESM exports for logos, speakers, opportunity data, governance
28
+ - The engine (`@deckio/deck-engine`) provides: `Slide`, `BottomBar`, `Navigation`, `SlideProvider`, `useSlides`, `GenericThankYouSlide`
29
+
30
+ ## Data conventions
31
+
32
+ - Always use ESM imports for images: `import logo from '../data/logos/acme.png'`
33
+ - Export data as named exports or default objects
34
+ - Keep data files focused — one concern per file
@@ -0,0 +1,91 @@
1
+ ---
2
+ description: "Use when creating or editing slide CSS modules in a deck project. Enforces required properties, orb positioning, and theme variables."
3
+ applyTo: "**/slides/**/*.module.css"
4
+ ---
5
+
6
+ # Slide CSS Module Conventions
7
+
8
+ ## Required root class
9
+
10
+ ```css
11
+ .mySlide {
12
+ background: var(--bg-deep);
13
+ flex-direction: column;
14
+ padding: 0 0 44px 0; /* reserve BottomBar height */
15
+ }
16
+ ```
17
+
18
+ Add `justify-content: center` for cover or thank-you slides.
19
+
20
+ ## Orb positioning recipe
21
+
22
+ ```css
23
+ .orb1 {
24
+ width: 420px; height: 420px;
25
+ top: -100px; right: -60px;
26
+ background: radial-gradient(circle at 40% 40%, var(--accent), var(--blue-glow) 50%, transparent 70%);
27
+ }
28
+ .orb2 {
29
+ width: 320px; height: 320px;
30
+ bottom: -40px; right: 100px;
31
+ background: radial-gradient(circle at 50% 50%, var(--purple-deep), rgba(110,64,201,0.25) 60%, transparent 75%);
32
+ }
33
+ ```
34
+
35
+ ## Body wrapper
36
+
37
+ ```css
38
+ .body {
39
+ position: relative;
40
+ z-index: 10;
41
+ display: flex;
42
+ flex-direction: column;
43
+ justify-content: center;
44
+ flex: 1;
45
+ min-height: 0;
46
+ }
47
+ ```
48
+
49
+ ## Theme variables (always use these, never hard-code colors)
50
+
51
+ | Variable | Value |
52
+ |----------|-------|
53
+ | `--bg-deep` | `#080b10` |
54
+ | `--surface` | `#161b22` |
55
+ | `--border` | `#30363d` |
56
+ | `--text` | `#e6edf3` |
57
+ | `--text-muted` | `#8b949e` |
58
+ | `--accent` | project-specific |
59
+ | `--blue-glow` | `#1f6feb` |
60
+ | `--purple` | `#bc8cff` |
61
+ | `--purple-deep` | `#6e40c9` |
62
+ | `--pink` | `#f778ba` |
63
+ | `--cyan` | `#56d4dd` |
64
+ | `--green` | `#3fb950` |
65
+ | `--orange` | `#d29922` |
66
+
67
+ ## Global classes (no import needed)
68
+
69
+ `accent-bar`, `orb`, `grid-dots`, `content-frame`, `content-gutter`
70
+
71
+ ## Card pattern
72
+
73
+ ```css
74
+ .card {
75
+ background: var(--surface);
76
+ border: 1px solid var(--border);
77
+ border-radius: 16px;
78
+ padding: 24px;
79
+ }
80
+ ```
81
+
82
+ ## Typography
83
+
84
+ | Element | Size | Weight | Spacing |
85
+ |---------|------|--------|---------|
86
+ | h1 | `clamp(42px, 5vw, 72px)` | 900 | `-2px` |
87
+ | h2 | `clamp(28px, 3.2vw, 36px)` | 700 | `-0.8px` |
88
+ | h3 | `16px–20px` | 700 | `-0.3px` |
89
+ | Subtitle | `17px` | 300–400 | — |
90
+ | Body | `13px–14px` | 400 | — |
91
+ | Badge | `10px–11px` | 600–700 | `1.5px` |
@@ -0,0 +1,34 @@
1
+ ---
2
+ description: "Use when creating, editing, or reviewing slide JSX components in a deck project. Enforces the mandatory slide skeleton, imports, and anti-patterns."
3
+ applyTo: "**/slides/**/*.jsx"
4
+ ---
5
+
6
+ # Slide JSX Conventions
7
+
8
+ ## Imports
9
+
10
+ ```jsx
11
+ import { BottomBar, Slide } from '@deckio/deck-engine'
12
+ import styles from './MySlide.module.css'
13
+ ```
14
+
15
+ ## Mandatory skeleton (in order inside `<Slide>`)
16
+
17
+ 1. `<div className="accent-bar" />` — always first
18
+ 2. 2–4 orbs: `<div className={\`orb ${styles.orbN}\`} />`
19
+ 3. Content wrapper: `<div className={\`${styles.body} content-frame content-gutter\`}>` — all visible content here
20
+ 4. `<BottomBar text="..." />` — always last child
21
+
22
+ ## Props
23
+
24
+ Every slide receives `{ index, project, title, subtitle }`. Pass `index` to `<Slide>`.
25
+
26
+ ## Available engine exports
27
+
28
+ `Slide`, `BottomBar`, `Navigation`, `SlideProvider`, `useSlides`, `GenericThankYouSlide`
29
+
30
+ ## Anti-patterns
31
+
32
+ - Never omit `accent-bar`, `content-frame content-gutter`, or `BottomBar`
33
+ - Never use string paths for images — always `import logo from '../data/...'`
34
+ - Never hardcode slide indices — use `useSlides().goTo()` for navigation