@deckio/deck-engine 1.7.5 → 1.7.7
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 -9
- package/components/BottomBar.module.css +17 -17
- package/components/Navigation.jsx +155 -106
- package/components/Navigation.module.css +154 -145
- package/components/Slide.jsx +15 -15
- package/components/exportDeckPdf.js +134 -0
- package/context/SlideContext.jsx +171 -171
- package/index.js +5 -5
- package/instructions/AGENTS.md +26 -26
- package/instructions/deck-config.instructions.md +34 -34
- package/instructions/deck-project.instructions.md +34 -34
- package/instructions/slide-css.instructions.md +91 -91
- package/instructions/slide-jsx.instructions.md +34 -34
- package/package.json +49 -45
- package/scripts/capture-screen.mjs +110 -110
- package/scripts/export-pdf.mjs +287 -287
- package/scripts/generate-image.mjs +110 -110
- package/scripts/init-project.mjs +214 -188
- package/skills/deck-add-slide/SKILL.md +217 -217
- package/skills/deck-delete-slide/SKILL.md +51 -51
- package/skills/deck-generate-image/SKILL.md +85 -85
- package/skills/deck-inspect/SKILL.md +60 -60
- package/skills/deck-sketch/SKILL.md +91 -91
- package/skills/deck-validate-project/SKILL.md +80 -80
- package/slides/GenericThankYouSlide.jsx +31 -31
- package/slides/ThankYouSlide.module.css +131 -131
- package/styles/global.css +191 -191
- package/vite.js +26 -26
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
const DEFAULT_PAGE_WIDTH = 1920
|
|
2
|
+
const DEFAULT_PAGE_HEIGHT = 1080
|
|
3
|
+
const EXPORT_TRANSITION_MS = 750
|
|
4
|
+
|
|
5
|
+
function wait(ms) {
|
|
6
|
+
return new Promise((resolve) => {
|
|
7
|
+
window.setTimeout(resolve, ms)
|
|
8
|
+
})
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function waitForPaint() {
|
|
12
|
+
await new Promise((resolve) => requestAnimationFrame(() => resolve()))
|
|
13
|
+
await new Promise((resolve) => requestAnimationFrame(() => resolve()))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function waitForFonts() {
|
|
17
|
+
if (document.fonts?.ready) {
|
|
18
|
+
await document.fonts.ready
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function sanitizeFileName(value) {
|
|
23
|
+
return String(value || 'deck')
|
|
24
|
+
.trim()
|
|
25
|
+
.toLowerCase()
|
|
26
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
27
|
+
.replace(/^-|-$/g, '') || 'deck'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getDeckElement() {
|
|
31
|
+
return document.querySelector('.deck')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getSlideNodes(deckElement) {
|
|
35
|
+
return Array.from(deckElement?.querySelectorAll('.slide') || [])
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getExportFileName({ project, selectedCustomer }) {
|
|
39
|
+
const baseName = selectedCustomer
|
|
40
|
+
? `${selectedCustomer} ${document.title || project || 'deck'}`
|
|
41
|
+
: document.title || project || 'deck'
|
|
42
|
+
|
|
43
|
+
return `${sanitizeFileName(baseName)}.pdf`
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function triggerDownload(blob, fileName) {
|
|
47
|
+
const blobUrl = URL.createObjectURL(blob)
|
|
48
|
+
const link = document.createElement('a')
|
|
49
|
+
link.href = blobUrl
|
|
50
|
+
link.download = fileName
|
|
51
|
+
document.body.append(link)
|
|
52
|
+
link.click()
|
|
53
|
+
link.remove()
|
|
54
|
+
window.setTimeout(() => URL.revokeObjectURL(blobUrl), 1000)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function exportDeckPdf({
|
|
58
|
+
current,
|
|
59
|
+
goTo,
|
|
60
|
+
project,
|
|
61
|
+
selectedCustomer,
|
|
62
|
+
totalSlides,
|
|
63
|
+
onProgress,
|
|
64
|
+
}) {
|
|
65
|
+
const deckElement = getDeckElement()
|
|
66
|
+
const slideNodes = getSlideNodes(deckElement)
|
|
67
|
+
|
|
68
|
+
if (!deckElement || slideNodes.length === 0) {
|
|
69
|
+
throw new Error('No slides found to export')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const [{ default: html2canvas }, { jsPDF }] = await Promise.all([
|
|
73
|
+
import('html2canvas'),
|
|
74
|
+
import('jspdf'),
|
|
75
|
+
])
|
|
76
|
+
|
|
77
|
+
const pageWidth = deckElement.clientWidth || DEFAULT_PAGE_WIDTH
|
|
78
|
+
const pageHeight = deckElement.clientHeight || DEFAULT_PAGE_HEIGHT
|
|
79
|
+
const backgroundColor = getComputedStyle(document.documentElement)
|
|
80
|
+
.getPropertyValue('--bg-deep')
|
|
81
|
+
.trim() || '#080b10'
|
|
82
|
+
const scale = Math.min(window.devicePixelRatio || 1, 2)
|
|
83
|
+
const pdf = new jsPDF({
|
|
84
|
+
orientation: 'landscape',
|
|
85
|
+
unit: 'px',
|
|
86
|
+
format: [DEFAULT_PAGE_WIDTH, DEFAULT_PAGE_HEIGHT],
|
|
87
|
+
compress: true,
|
|
88
|
+
hotfixes: ['px_scaling'],
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
await waitForFonts()
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
for (let index = 0; index < totalSlides; index += 1) {
|
|
95
|
+
onProgress?.({ current: index + 1, total: totalSlides })
|
|
96
|
+
goTo(index)
|
|
97
|
+
await waitForPaint()
|
|
98
|
+
await wait(EXPORT_TRANSITION_MS)
|
|
99
|
+
|
|
100
|
+
const activeSlide = document.querySelector('.slide.active') || slideNodes[index]
|
|
101
|
+
if (!activeSlide) {
|
|
102
|
+
throw new Error(`Slide ${index + 1} could not be rendered`)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const canvas = await html2canvas(activeSlide, {
|
|
106
|
+
backgroundColor,
|
|
107
|
+
useCORS: true,
|
|
108
|
+
allowTaint: true,
|
|
109
|
+
logging: false,
|
|
110
|
+
scale,
|
|
111
|
+
width: pageWidth,
|
|
112
|
+
height: pageHeight,
|
|
113
|
+
windowWidth: window.innerWidth,
|
|
114
|
+
windowHeight: window.innerHeight,
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
const imageData = canvas.toDataURL('image/png')
|
|
118
|
+
if (index > 0) {
|
|
119
|
+
pdf.addPage([DEFAULT_PAGE_WIDTH, DEFAULT_PAGE_HEIGHT], 'landscape')
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
pdf.addImage(imageData, 'PNG', 0, 0, DEFAULT_PAGE_WIDTH, DEFAULT_PAGE_HEIGHT, undefined, 'FAST')
|
|
123
|
+
}
|
|
124
|
+
} finally {
|
|
125
|
+
goTo(current)
|
|
126
|
+
await waitForPaint()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const blob = pdf.output('blob')
|
|
130
|
+
const fileName = getExportFileName({ project, selectedCustomer })
|
|
131
|
+
triggerDownload(blob, fileName)
|
|
132
|
+
|
|
133
|
+
return { fileName }
|
|
134
|
+
}
|
package/context/SlideContext.jsx
CHANGED
|
@@ -1,171 +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
|
-
}
|
|
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
CHANGED
|
@@ -1,5 +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'
|
|
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'
|
package/instructions/AGENTS.md
CHANGED
|
@@ -1,26 +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)
|
|
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)
|
|
@@ -1,34 +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
|
|
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
|