@deckio/deck-engine 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/components/BottomBar.jsx +9 -0
- package/components/BottomBar.module.css +17 -0
- package/components/Navigation.jsx +195 -0
- package/components/Navigation.module.css +210 -0
- package/components/Slide.jsx +43 -0
- package/components/exportDeckPdf.js +142 -0
- package/components/exportDeckPptx.js +127 -0
- package/context/SlideContext.jsx +190 -0
- package/index.js +5 -0
- package/instructions/AGENTS.md +26 -0
- package/instructions/deck-config.instructions.md +34 -0
- package/instructions/deck-project.instructions.md +34 -0
- package/instructions/slide-css.instructions.md +96 -0
- package/instructions/slide-jsx.instructions.md +34 -0
- package/package.json +59 -0
- package/scripts/capture-screen.mjs +127 -0
- package/scripts/export-pdf.mjs +287 -0
- package/scripts/generate-image.mjs +110 -0
- package/scripts/init-project.mjs +214 -0
- package/skills/deck-add-slide/SKILL.md +236 -0
- package/skills/deck-delete-slide/SKILL.md +51 -0
- package/skills/deck-generate-image/SKILL.md +85 -0
- package/skills/deck-inspect/SKILL.md +60 -0
- package/skills/deck-sketch/SKILL.md +91 -0
- package/skills/deck-validate-project/SKILL.md +81 -0
- package/slides/GenericThankYouSlide.jsx +31 -0
- package/styles/global.css +392 -0
- package/themes/dark.css +151 -0
- package/themes/light.css +152 -0
- package/themes/shadcn.css +212 -0
- package/themes/theme-loader.js +47 -0
- package/vite.js +67 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* deck-engine init — provisions Copilot skills and state into a deck project.
|
|
4
|
+
*
|
|
5
|
+
* Copies .github/skills/ from the engine package and bootstraps
|
|
6
|
+
* .github/memory/state.md with project metadata from deck.config.js.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node node_modules/@deckio/deck-engine/scripts/init-project.mjs
|
|
10
|
+
* npx deck-init (if bin is configured)
|
|
11
|
+
*
|
|
12
|
+
* Idempotent — safe to re-run. Updates skills, preserves state.
|
|
13
|
+
*/
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, copyFileSync, rmSync } from 'fs'
|
|
15
|
+
import { join, dirname } from 'path'
|
|
16
|
+
import { fileURLToPath } from 'url'
|
|
17
|
+
|
|
18
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
19
|
+
const engineRoot = join(__dirname, '..')
|
|
20
|
+
const projectRoot = process.cwd()
|
|
21
|
+
|
|
22
|
+
// ── Discover project metadata from deck.config.js ──
|
|
23
|
+
|
|
24
|
+
function readProjectMeta() {
|
|
25
|
+
const configPath = join(projectRoot, 'deck.config.js')
|
|
26
|
+
if (!existsSync(configPath)) {
|
|
27
|
+
console.error('❌ No deck.config.js found in', projectRoot)
|
|
28
|
+
process.exit(1)
|
|
29
|
+
}
|
|
30
|
+
const content = readFileSync(configPath, 'utf-8')
|
|
31
|
+
const str = (key) => {
|
|
32
|
+
const m = content.match(new RegExp(`${key}:\\s*['"\`]([^'"\`]+)['"\`]`))
|
|
33
|
+
return m ? m[1] : null
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
id: str('id') || 'unknown',
|
|
37
|
+
title: str('title') || 'Deck Project',
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Copy skills ──
|
|
42
|
+
|
|
43
|
+
function copySkills() {
|
|
44
|
+
const srcSkills = join(engineRoot, 'skills')
|
|
45
|
+
if (!existsSync(srcSkills)) {
|
|
46
|
+
console.warn('⚠️ No skills directory in engine package')
|
|
47
|
+
return 0
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const destSkills = join(projectRoot, '.github', 'skills')
|
|
51
|
+
let count = 0
|
|
52
|
+
|
|
53
|
+
// Collect engine skill names to detect stale skills
|
|
54
|
+
const engineSkillNames = new Set()
|
|
55
|
+
for (const entry of readdirSync(srcSkills, { withFileTypes: true })) {
|
|
56
|
+
if (!entry.isDirectory()) continue
|
|
57
|
+
const skillFile = join(srcSkills, entry.name, 'SKILL.md')
|
|
58
|
+
if (!existsSync(skillFile)) continue
|
|
59
|
+
engineSkillNames.add(entry.name)
|
|
60
|
+
|
|
61
|
+
const destDir = join(destSkills, entry.name)
|
|
62
|
+
mkdirSync(destDir, { recursive: true })
|
|
63
|
+
copyFileSync(skillFile, join(destDir, 'SKILL.md'))
|
|
64
|
+
count++
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Remove stale skills no longer in the engine
|
|
68
|
+
if (existsSync(destSkills)) {
|
|
69
|
+
for (const entry of readdirSync(destSkills, { withFileTypes: true })) {
|
|
70
|
+
if (!entry.isDirectory()) continue
|
|
71
|
+
if (!engineSkillNames.has(entry.name)) {
|
|
72
|
+
rmSync(join(destSkills, entry.name), { recursive: true, force: true })
|
|
73
|
+
console.log(` Removed stale skill: ${entry.name}`)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return count
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ── Copy instructions ──
|
|
82
|
+
|
|
83
|
+
function copyInstructions() {
|
|
84
|
+
const srcInstructions = join(engineRoot, 'instructions')
|
|
85
|
+
if (!existsSync(srcInstructions)) {
|
|
86
|
+
console.warn('⚠️ No instructions directory in engine package')
|
|
87
|
+
return 0
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const destInstructions = join(projectRoot, '.github', 'instructions')
|
|
91
|
+
mkdirSync(destInstructions, { recursive: true })
|
|
92
|
+
let count = 0
|
|
93
|
+
|
|
94
|
+
// Collect engine instruction names to detect stale instructions
|
|
95
|
+
const engineInstrNames = new Set()
|
|
96
|
+
for (const entry of readdirSync(srcInstructions, { withFileTypes: true })) {
|
|
97
|
+
if (!entry.isFile() || !entry.name.endsWith('.instructions.md')) continue
|
|
98
|
+
engineInstrNames.add(entry.name)
|
|
99
|
+
copyFileSync(
|
|
100
|
+
join(srcInstructions, entry.name),
|
|
101
|
+
join(destInstructions, entry.name)
|
|
102
|
+
)
|
|
103
|
+
count++
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Remove stale instructions no longer in the engine
|
|
107
|
+
for (const entry of readdirSync(destInstructions, { withFileTypes: true })) {
|
|
108
|
+
if (!entry.isFile() || !entry.name.endsWith('.instructions.md')) continue
|
|
109
|
+
if (!engineInstrNames.has(entry.name)) {
|
|
110
|
+
rmSync(join(destInstructions, entry.name), { force: true })
|
|
111
|
+
console.log(` Removed stale instruction: ${entry.name}`)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return count
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ── Copy AGENTS.md ──
|
|
119
|
+
|
|
120
|
+
function copyAgentsMd() {
|
|
121
|
+
const src = join(engineRoot, 'instructions', 'AGENTS.md')
|
|
122
|
+
if (!existsSync(src)) return false
|
|
123
|
+
copyFileSync(src, join(projectRoot, 'AGENTS.md'))
|
|
124
|
+
return true
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ── Bootstrap state.md ──
|
|
128
|
+
|
|
129
|
+
function bootstrapState(meta) {
|
|
130
|
+
const stateDir = join(projectRoot, '.github', 'memory')
|
|
131
|
+
const statePath = join(stateDir, 'state.md')
|
|
132
|
+
|
|
133
|
+
// Don't overwrite existing state (preserves user's port/url config)
|
|
134
|
+
if (existsSync(statePath)) {
|
|
135
|
+
console.log(' state.md already exists — preserved')
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
mkdirSync(stateDir, { recursive: true })
|
|
140
|
+
const content = `# Deck State
|
|
141
|
+
|
|
142
|
+
## Project
|
|
143
|
+
id: ${meta.id}
|
|
144
|
+
title: ${meta.title}
|
|
145
|
+
|
|
146
|
+
## Dev Server
|
|
147
|
+
port: 5173
|
|
148
|
+
url: http://localhost:5173/
|
|
149
|
+
`
|
|
150
|
+
writeFileSync(statePath, content, 'utf-8')
|
|
151
|
+
console.log(' Created .github/memory/state.md')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ── Create eyes directory ──
|
|
155
|
+
|
|
156
|
+
function createEyesDir() {
|
|
157
|
+
const eyesDir = join(projectRoot, '.github', 'eyes')
|
|
158
|
+
mkdirSync(eyesDir, { recursive: true })
|
|
159
|
+
|
|
160
|
+
// Add to .gitignore if not already there
|
|
161
|
+
const gitignorePath = join(projectRoot, '.gitignore')
|
|
162
|
+
if (existsSync(gitignorePath)) {
|
|
163
|
+
const gitignore = readFileSync(gitignorePath, 'utf-8')
|
|
164
|
+
if (!gitignore.includes('.github/eyes')) {
|
|
165
|
+
writeFileSync(gitignorePath, gitignore.trimEnd() + '\n.github/eyes/\n', 'utf-8')
|
|
166
|
+
console.log(' Added .github/eyes/ to .gitignore')
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function ensureVSCodeSettings() {
|
|
172
|
+
const vscodeDir = join(projectRoot, '.vscode')
|
|
173
|
+
const settingsPath = join(vscodeDir, 'settings.json')
|
|
174
|
+
|
|
175
|
+
mkdirSync(vscodeDir, { recursive: true })
|
|
176
|
+
|
|
177
|
+
let settings = {}
|
|
178
|
+
if (existsSync(settingsPath)) {
|
|
179
|
+
try {
|
|
180
|
+
settings = JSON.parse(readFileSync(settingsPath, 'utf-8'))
|
|
181
|
+
} catch {
|
|
182
|
+
console.warn('⚠️ Could not parse .vscode/settings.json — leaving it unchanged')
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (settings['simpleBrowser.useIntegratedBrowser'] === true) {
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
settings['simpleBrowser.useIntegratedBrowser'] = true
|
|
192
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8')
|
|
193
|
+
console.log(' Ensured .vscode/settings.json uses the integrated browser')
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ── Main ──
|
|
197
|
+
|
|
198
|
+
const meta = readProjectMeta()
|
|
199
|
+
console.log(`\n🎯 Initializing deck project: ${meta.title} (${meta.id})`)
|
|
200
|
+
|
|
201
|
+
const skillCount = copySkills()
|
|
202
|
+
console.log(` Copied ${skillCount} Copilot skills to .github/skills/`)
|
|
203
|
+
|
|
204
|
+
const instrCount = copyInstructions()
|
|
205
|
+
console.log(` Copied ${instrCount} Copilot instructions to .github/instructions/`)
|
|
206
|
+
|
|
207
|
+
const agentsCopied = copyAgentsMd()
|
|
208
|
+
if (agentsCopied) console.log(' Copied AGENTS.md to project root')
|
|
209
|
+
|
|
210
|
+
bootstrapState(meta)
|
|
211
|
+
createEyesDir()
|
|
212
|
+
ensureVSCodeSettings()
|
|
213
|
+
|
|
214
|
+
console.log(`\n✅ Done. Run \`copilot --yolo\` to start editing with Copilot skills.\n`)
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deck-add-slide
|
|
3
|
+
description: Guide for adding a new slide to a deck project. Use this when asked to create, add, or build a new slide component.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Adding a Slide to a Deck Project
|
|
7
|
+
|
|
8
|
+
## A. Slide Component Structure (mandatory skeleton)
|
|
9
|
+
|
|
10
|
+
Every slide **must** follow this structure:
|
|
11
|
+
|
|
12
|
+
```jsx
|
|
13
|
+
import { BottomBar, Slide } from '@deckio/deck-engine'
|
|
14
|
+
import styles from './MyNewSlide.module.css'
|
|
15
|
+
|
|
16
|
+
export default function MyNewSlide({ index, project }) {
|
|
17
|
+
return (
|
|
18
|
+
<Slide index={index} className={styles.myNewSlide}>
|
|
19
|
+
{/* 1. Decorative elements — always first */}
|
|
20
|
+
<div className="accent-bar" />
|
|
21
|
+
<div className={`orb ${styles.orb1}`} />
|
|
22
|
+
<div className={`orb ${styles.orb2}`} />
|
|
23
|
+
|
|
24
|
+
{/* 2. Content area — vertically centered */}
|
|
25
|
+
<div className={`${styles.body} content-frame content-gutter`}>
|
|
26
|
+
{/* Slide content */}
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
{/* 3. Footer — always last child */}
|
|
30
|
+
<BottomBar text="Project Footer Text" />
|
|
31
|
+
</Slide>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Mandatory elements (in order inside `<Slide>`):
|
|
37
|
+
|
|
38
|
+
1. **`<div className="accent-bar" />`** — Left gradient accent bar. Include on every slide.
|
|
39
|
+
2. **Orbs** — 2–4 `<div className={\`orb ${styles.orbN}\`} />` for ambient background glow.
|
|
40
|
+
3. **Content wrapper** — `<div className="content-frame content-gutter">` constrains width to `1280px` and adds `72px` horizontal padding. **All visible content goes inside this wrapper.**
|
|
41
|
+
4. **`<BottomBar text="..." />`** — Sticky footer, always the last child. The `text` must match the project's convention (check existing slides).
|
|
42
|
+
|
|
43
|
+
### Import paths (standalone project):
|
|
44
|
+
|
|
45
|
+
| Resource | Import Path |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `Slide`, `BottomBar`, `Navigation`, `SlideProvider`, `useSlides` | `'@deckio/deck-engine'` |
|
|
48
|
+
| `GenericThankYouSlide` | `'@deckio/deck-engine'` |
|
|
49
|
+
| Data / logos | `'../data/<file>'` |
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## B. CSS Module Rules
|
|
54
|
+
|
|
55
|
+
Create a companion `.module.css` file matching the JSX filename (e.g., `MyNewSlide.module.css`).
|
|
56
|
+
|
|
57
|
+
### Required root class properties
|
|
58
|
+
|
|
59
|
+
```css
|
|
60
|
+
.myNewSlide {
|
|
61
|
+
background: var(--bg-deep);
|
|
62
|
+
padding: 0 0 44px 0;
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- `background: var(--bg-deep)` — dark background on every slide
|
|
67
|
+
- `padding: 0 0 44px 0` — reserves space for the 44px BottomBar
|
|
68
|
+
|
|
69
|
+
The engine's `.slide` class provides `flex-direction: column`, `justify-content: center`, `align-items: stretch`, and `overflow: hidden` by default. It also sets `flex-grow: 0` on all direct slide children, so **content stays at its natural height and is vertically centered by default** — building from the center outward. No scrolling is allowed.
|
|
70
|
+
|
|
71
|
+
For dense slides that need top-alignment, override with `justify-content: flex-start`.
|
|
72
|
+
|
|
73
|
+
### Orb positioning (standard recipe)
|
|
74
|
+
|
|
75
|
+
```css
|
|
76
|
+
.orb1 {
|
|
77
|
+
width: 420px; height: 420px;
|
|
78
|
+
top: -100px; right: -60px;
|
|
79
|
+
background: radial-gradient(circle at 40% 40%, var(--accent), var(--blue-glow) 50%, transparent 70%);
|
|
80
|
+
}
|
|
81
|
+
.orb2 {
|
|
82
|
+
width: 320px; height: 320px;
|
|
83
|
+
bottom: -40px; right: 100px;
|
|
84
|
+
background: radial-gradient(circle at 50% 50%, var(--purple-deep), rgba(110,64,201,0.25) 60%, transparent 75%);
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Body wrapper
|
|
89
|
+
|
|
90
|
+
```css
|
|
91
|
+
.body {
|
|
92
|
+
position: relative;
|
|
93
|
+
z-index: 10;
|
|
94
|
+
display: flex;
|
|
95
|
+
flex-direction: column;
|
|
96
|
+
gap: 24px;
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
> **Do NOT add `flex: 1` or `flex-grow: 1`** to the body wrapper or any direct slide child — it stretches the wrapper to fill the slide and defeats the engine's built-in vertical centering. Inner elements within the body should also avoid `flex: 1` unless they genuinely need to fill remaining space within the body.
|
|
101
|
+
|
|
102
|
+
### Available CSS custom properties
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
--bg-deep: #080b10 --surface: #161b22 --border: #30363d
|
|
106
|
+
--text: #e6edf3 --text-muted: #8b949e --accent: #58a6ff
|
|
107
|
+
--blue-glow: #1f6feb --purple: #bc8cff --purple-deep: #6e40c9
|
|
108
|
+
--pink: #f778ba --cyan: #56d4dd --green: #3fb950
|
|
109
|
+
--orange: #d29922
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Available global CSS classes (no import needed)
|
|
113
|
+
|
|
114
|
+
| Class | Purpose |
|
|
115
|
+
|---|---|
|
|
116
|
+
| `accent-bar` | Left gradient accent bar |
|
|
117
|
+
| `orb` | Base decorative orb (absolute, rounded, blur, opacity) |
|
|
118
|
+
| `grid-dots` | Dot grid pattern (200×200px) |
|
|
119
|
+
| `content-frame` | Width constraint to `1280px`, centered |
|
|
120
|
+
| `content-gutter` | `72px` left/right padding |
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## C. Typography Conventions
|
|
125
|
+
|
|
126
|
+
| Element | Size | Weight | Spacing | Usage |
|
|
127
|
+
|---|---|---|---|---|
|
|
128
|
+
| `h1` | `clamp(42px, 5vw, 72px)` | 900 | `-2px` | Cover slides only |
|
|
129
|
+
| `h2` | `clamp(28px, 3.2vw, 36px)` | 700 | `-0.8px` | Main slide heading |
|
|
130
|
+
| `h3` | `16px–20px` | 700 | `-0.3px` | Card titles |
|
|
131
|
+
| Subtitle | `17px` | 300–400 | — | `color: var(--text-muted)`, below heading |
|
|
132
|
+
| Body text | `13px–14px` | 400 | — | `color: var(--text-muted)` |
|
|
133
|
+
| Badge/label | `10px–11px` | 600–700 | `1.5px` | Uppercase, rounded bg |
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## D. Content Layout Patterns
|
|
138
|
+
|
|
139
|
+
### Card grid
|
|
140
|
+
```css
|
|
141
|
+
.cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Standard card
|
|
145
|
+
```css
|
|
146
|
+
.card {
|
|
147
|
+
background: var(--surface);
|
|
148
|
+
border: 1px solid var(--border);
|
|
149
|
+
border-radius: 16px;
|
|
150
|
+
padding: 24px;
|
|
151
|
+
overflow: hidden;
|
|
152
|
+
transition: transform 0.3s ease, border-color 0.3s ease;
|
|
153
|
+
}
|
|
154
|
+
.card::before {
|
|
155
|
+
content: '';
|
|
156
|
+
position: absolute;
|
|
157
|
+
top: 0; left: 0; right: 0; height: 3px;
|
|
158
|
+
background: linear-gradient(90deg, var(--purple), var(--accent));
|
|
159
|
+
opacity: 0.6;
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## E. Registration in deck.config.js
|
|
166
|
+
|
|
167
|
+
After creating the slide files, register the slide in `deck.config.js`:
|
|
168
|
+
|
|
169
|
+
1. **Add an import** at the top: `import MyNewSlide from './src/slides/MyNewSlide.jsx'`
|
|
170
|
+
2. **Add the component** to the `slides` array at the desired position.
|
|
171
|
+
|
|
172
|
+
The generic `App.jsx` renders slides from this array, passing `index` as a prop automatically. **You do NOT need to manage index numbers manually** — they are assigned by array position.
|
|
173
|
+
|
|
174
|
+
### Example: adding before ThankYou
|
|
175
|
+
|
|
176
|
+
```js
|
|
177
|
+
import MyNewSlide from './src/slides/MyNewSlide.jsx'
|
|
178
|
+
// ... other imports
|
|
179
|
+
|
|
180
|
+
export default {
|
|
181
|
+
// ... metadata
|
|
182
|
+
slides: [
|
|
183
|
+
CoverSlide,
|
|
184
|
+
// ... existing slides
|
|
185
|
+
MyNewSlide, // ← insert here
|
|
186
|
+
ThankYouSlide, // stays last
|
|
187
|
+
],
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## F. Content Density Limits
|
|
194
|
+
|
|
195
|
+
Slides must never overflow the viewport. The engine shows a **red dashed border warning** in dev mode when content exceeds the slide bounds. Follow these limits:
|
|
196
|
+
|
|
197
|
+
| Layout | Max items | Notes |
|
|
198
|
+
|--------|-----------|-------|
|
|
199
|
+
| Cards (3-col grid) | 6 (2 rows) | Reduce card padding if tight |
|
|
200
|
+
| Cards (2-col grid) | 4 (2 rows) | Preferred for detailed cards |
|
|
201
|
+
| Timeline / event list | 3–4 items | Use compact card height for 4 |
|
|
202
|
+
| Bullet points | 6–8 | Depends on line length |
|
|
203
|
+
| Full-width content blocks | 2–3 | E.g. quote + detail section |
|
|
204
|
+
|
|
205
|
+
**When content exceeds limits**, split across multiple slides rather than cramming.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## G. Anti-Patterns to Avoid
|
|
210
|
+
|
|
211
|
+
1. **Missing `accent-bar`** — include on every slide.
|
|
212
|
+
2. **Missing `content-frame content-gutter`** — content will be full-width without standard margins.
|
|
213
|
+
3. **Missing `BottomBar`** — every slide needs it as the last child.
|
|
214
|
+
4. **String paths for images** — always use `import logo from '../data/...'` (Vite resolves to URL).
|
|
215
|
+
5. **Missing `padding: 0 0 44px 0`** on the slide root CSS class — content will overlap the BottomBar.
|
|
216
|
+
6. **Inconsistent `BottomBar text`** — check existing slides and match their footer text.
|
|
217
|
+
7. **Using `flex: 1` on body wrapper** — defeats vertical centering; the body should size to its content.
|
|
218
|
+
8. **Adding `flex-direction: column` on slide root** — already provided by the engine's `.slide` class.
|
|
219
|
+
9. **Overloading a slide** — if the dev server shows a red dashed border, the slide has too much content. Split into multiple slides.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## H. Complete Step-by-Step
|
|
224
|
+
|
|
225
|
+
1. **Create** `src/slides/<SlideName>Slide.jsx` following the mandatory skeleton (section A).
|
|
226
|
+
2. **Create** `src/slides/<SlideName>Slide.module.css` with required root properties (section B).
|
|
227
|
+
3. **Register** in `deck.config.js` — add import + add to `slides` array (section E).
|
|
228
|
+
4. **Verify** — the dev server hot-reloads automatically. Navigate to the new slide and check layout.
|
|
229
|
+
|
|
230
|
+
### Quick checklist
|
|
231
|
+
|
|
232
|
+
- [ ] Created `<SlideName>Slide.jsx` with Slide, accent-bar, orbs, content-frame, BottomBar
|
|
233
|
+
- [ ] Created `<SlideName>Slide.module.css` with `background: var(--bg-deep)`, `padding: 0 0 44px 0`, body wrapper (no `flex: 1`)
|
|
234
|
+
- [ ] Import added to `deck.config.js`
|
|
235
|
+
- [ ] Component added to `slides` array at correct position
|
|
236
|
+
- [ ] `BottomBar text` matches project convention
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deck-delete-slide
|
|
3
|
+
description: Guide for removing a slide from a deck project. Use this when asked to delete, remove, or drop a slide.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Removing a Slide from a Deck Project
|
|
7
|
+
|
|
8
|
+
## Step 1: Identify the slide to remove
|
|
9
|
+
|
|
10
|
+
Open `deck.config.js` and review the `slides` array to see all slides and their order. If the user didn't specify which slide to remove, list them so the user can choose.
|
|
11
|
+
|
|
12
|
+
## Step 2: Remove from deck.config.js
|
|
13
|
+
|
|
14
|
+
1. **Remove the import statement** for the slide being deleted.
|
|
15
|
+
2. **Remove the component** from the `slides` array.
|
|
16
|
+
|
|
17
|
+
No index management is needed — the generic `App.jsx` assigns indexes by array position automatically.
|
|
18
|
+
|
|
19
|
+
## Step 3: Delete the slide files
|
|
20
|
+
|
|
21
|
+
Delete from `src/slides/`:
|
|
22
|
+
|
|
23
|
+
- `<SlideName>Slide.jsx`
|
|
24
|
+
- `<SlideName>Slide.module.css`
|
|
25
|
+
|
|
26
|
+
**Do not delete** shared slides from `@deckio/deck-engine` (like `GenericThankYouSlide`) — only remove the import.
|
|
27
|
+
|
|
28
|
+
## Step 4: Check for references
|
|
29
|
+
|
|
30
|
+
Search `src/slides/` for any remaining references to the deleted slide:
|
|
31
|
+
|
|
32
|
+
- Other slides that import or reference the deleted component
|
|
33
|
+
- Data files specific to the deleted slide (if any)
|
|
34
|
+
- Remove any orphaned references
|
|
35
|
+
|
|
36
|
+
## Step 5: Verify
|
|
37
|
+
|
|
38
|
+
The dev server hot-reloads automatically. Navigate through all slides and confirm:
|
|
39
|
+
|
|
40
|
+
- The deleted slide no longer appears
|
|
41
|
+
- All remaining slides are navigable (← → arrows, keyboard)
|
|
42
|
+
- Progress bar reflects the correct new total
|
|
43
|
+
- No console errors
|
|
44
|
+
|
|
45
|
+
## Quick checklist
|
|
46
|
+
|
|
47
|
+
- [ ] Removed import from `deck.config.js`
|
|
48
|
+
- [ ] Removed component from `slides` array
|
|
49
|
+
- [ ] Deleted slide `.jsx` and `.module.css` files
|
|
50
|
+
- [ ] No orphaned references to the deleted slide
|
|
51
|
+
- [ ] No console errors, all slides navigable
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deck-generate-image
|
|
3
|
+
description: Generate images (icons, illustrations, diagrams) using chatgpt-image-latest for use in slide components. Use this when a slide needs a visual element too complex for pure HTML/CSS.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Generate Images for Slides
|
|
7
|
+
|
|
8
|
+
Generate images via the OpenAI **Image API** with `chatgpt-image-latest` for specific visual elements within slides.
|
|
9
|
+
|
|
10
|
+
> If `chatgpt-image-latest` returns a 403, pass `--model gpt-image-1.5` as a fallback.
|
|
11
|
+
|
|
12
|
+
## When to use
|
|
13
|
+
|
|
14
|
+
- A slide needs an **icon or illustration** that doesn't exist in the codebase.
|
|
15
|
+
- A sketch element is too detailed to reproduce with CSS.
|
|
16
|
+
- The user explicitly asks for a generated graphic.
|
|
17
|
+
|
|
18
|
+
## When NOT to use
|
|
19
|
+
|
|
20
|
+
- For entire slides — use **deck-add-slide** instead.
|
|
21
|
+
- For simple shapes — use CSS or inline SVG.
|
|
22
|
+
- For logos or photos — source them directly.
|
|
23
|
+
|
|
24
|
+
## Prerequisites
|
|
25
|
+
|
|
26
|
+
The `OPENAI_API_KEY` must be set. Create/edit `.env` in the project root:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
OPENAI_API_KEY=sk-proj-...
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Workflow
|
|
33
|
+
|
|
34
|
+
### Step 1 — Craft the prompt
|
|
35
|
+
|
|
36
|
+
Write a detailed prompt including:
|
|
37
|
+
|
|
38
|
+
- **Subject** — what the image depicts.
|
|
39
|
+
- **Style** — flat icon, line art, illustration, isometric, etc.
|
|
40
|
+
- **Colors** — match the design system: `#0d1117` (bg-deep), `#58a6ff` (accent), `#3fb950` (green), `#bc8cff` (purple).
|
|
41
|
+
- **Background** — almost always "transparent background".
|
|
42
|
+
- **Aspect ratio** — square (1024x1024) for icons, wide (1536x1024) for banners.
|
|
43
|
+
|
|
44
|
+
### Step 2 — Generate
|
|
45
|
+
|
|
46
|
+
Run from the project root:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
node node_modules/@deckio/deck-engine/scripts/generate-image.mjs --prompt "your prompt" --name my-icon
|
|
50
|
+
node node_modules/@deckio/deck-engine/scripts/generate-image.mjs --prompt "..." --name hero --size 1536x1024
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
| Flag | Description | Default |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| `--prompt` | Image description (required) | — |
|
|
56
|
+
| `--name` | Filename without extension (required) | — |
|
|
57
|
+
| `--size` | `1024x1024`, `1536x1024`, `1024x1536`, `auto` | `1024x1024` |
|
|
58
|
+
| `--quality` | `low`, `medium`, `high`, `auto` | `auto` |
|
|
59
|
+
| `--model` | OpenAI model | `chatgpt-image-latest` |
|
|
60
|
+
|
|
61
|
+
Images are saved to `src/data/generated/`.
|
|
62
|
+
|
|
63
|
+
### Step 3 — Use in a slide
|
|
64
|
+
|
|
65
|
+
```jsx
|
|
66
|
+
import myIcon from '../data/generated/my-icon.png'
|
|
67
|
+
|
|
68
|
+
<img src={myIcon} alt="Bridge icon" style={{ width: 120, height: 120 }} />
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Step 4 — Iterate
|
|
72
|
+
|
|
73
|
+
If the image doesn't match expectations, refine the prompt and re-run with the same `--name` to overwrite. Use **deck-eyes** to verify how it looks in the slide.
|
|
74
|
+
|
|
75
|
+
## SVG alternative
|
|
76
|
+
|
|
77
|
+
For simple icons, write SVG markup directly in JSX:
|
|
78
|
+
|
|
79
|
+
```jsx
|
|
80
|
+
<svg width="48" height="48" viewBox="0 0 48 48" fill="none">
|
|
81
|
+
<circle cx="24" cy="24" r="20" stroke="#58a6ff" strokeWidth="2" />
|
|
82
|
+
</svg>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Prefer inline SVG for geometric shapes. Use image generation for complex illustrations.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deck-inspect
|
|
3
|
+
description: Capture a screenshot of the deck app to visually inspect slides. Use this when asked to look at, see, view, inspect, check visually, or preview a slide.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Visual Inspection — Capture a Slide Screenshot
|
|
7
|
+
|
|
8
|
+
Captures a screenshot of the running deck using VS Code browser tools (Edge "Sharing with Agent").
|
|
9
|
+
|
|
10
|
+
## Deciding what to capture
|
|
11
|
+
|
|
12
|
+
1. **Slide** — resolve in this order:
|
|
13
|
+
- If the user said "slide 3" or "the cover slide" → map to a 1-based number.
|
|
14
|
+
- If you just created or edited a specific slide → use that slide's array position + 1.
|
|
15
|
+
- If not specified → capture slide 1.
|
|
16
|
+
|
|
17
|
+
## Prerequisites
|
|
18
|
+
|
|
19
|
+
The dev server must be running. Check `.github/memory/state.md` for the port. Default is `5173`.
|
|
20
|
+
|
|
21
|
+
## Workflow
|
|
22
|
+
|
|
23
|
+
### Step 1 — Open or reuse a browser page
|
|
24
|
+
|
|
25
|
+
Check the attached browser pages for an existing deck tab. If none exists, open one:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
open_browser_page → http://localhost:<port>/#/<project-id>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Read `project` and `port` from `.github/memory/state.md` if not known.
|
|
32
|
+
|
|
33
|
+
### Step 2 — Navigate to the target slide
|
|
34
|
+
|
|
35
|
+
The deck opens on slide 1. To reach slide N, press `ArrowRight` (N − 1) times:
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
// run_playwright_code on the page
|
|
39
|
+
for (let i = 0; i < N - 1; i++) {
|
|
40
|
+
await page.keyboard.press('ArrowRight');
|
|
41
|
+
await page.waitForTimeout(300);
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
If the page is already on a different slide, navigate to slide 1 first by pressing `Home`, then advance forward.
|
|
46
|
+
|
|
47
|
+
### Step 3 — Take a screenshot
|
|
48
|
+
|
|
49
|
+
Use `screenshot_page` with the page ID to capture the current view. The screenshot is returned inline — no file path needed.
|
|
50
|
+
|
|
51
|
+
### Step 4 — Inspect and report
|
|
52
|
+
|
|
53
|
+
Study the screenshot and check for:
|
|
54
|
+
- Layout alignment and spacing
|
|
55
|
+
- Typography (size, weight, color)
|
|
56
|
+
- Missing or broken elements
|
|
57
|
+
- Color and contrast issues
|
|
58
|
+
- Overflow or clipping
|
|
59
|
+
|
|
60
|
+
Report any issues found to the user.
|