promptslide 0.2.4 → 0.2.6

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.
@@ -4,11 +4,16 @@ import { join, basename, relative, extname } from "node:path"
4
4
  import { bold, green, cyan, red, dim } from "../utils/ansi.mjs"
5
5
  import { requireAuth } from "../utils/auth.mjs"
6
6
  import { captureSlideAsDataUri, isPlaywrightAvailable } from "../utils/export.mjs"
7
- import { publishToRegistry, registryItemExists, searchRegistry, updateLockfileItem, readLockfile, hashContent, detectPackageManager, requestUploadTokens, uploadBinaryToBlob, assetFileToSlug, detectAssetDepsInContent } from "../utils/registry.mjs"
8
- import { prompt, confirm, closePrompts } from "../utils/prompts.mjs"
7
+ import { publishToRegistry, registryItemExists, searchRegistry, updateLockfileItem, updateLockfilePublishConfig, readLockfile, hashContent, detectPackageManager, requestUploadTokens, uploadBinaryToBlob, assetFileToSlug, detectAssetDepsInContent } from "../utils/registry.mjs"
8
+ import { prompt, confirm, select, closePrompts } from "../utils/prompts.mjs"
9
9
  import { parseDeckConfig } from "../utils/deck-config.mjs"
10
10
 
11
11
  function readDeckPrefix(cwd) {
12
+ // Prefer stored prefix from lockfile (user's previous choice)
13
+ const lock = readLockfile(cwd)
14
+ if (lock.deckPrefix) return lock.deckPrefix
15
+
16
+ // Fall back to package.json name
12
17
  try {
13
18
  const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf-8"))
14
19
  return (pkg.name || "").toLowerCase()
@@ -402,22 +407,17 @@ export async function publish(args) {
402
407
  process.exit(1)
403
408
  }
404
409
 
405
- console.log(` ${bold("Available files:")}`)
406
- available.forEach((f, i) => {
407
- console.log(` ${dim(`${i + 1}.`)} ${f.target}${f.path}`)
408
- })
409
- console.log(` ${dim("─".repeat(30))}`)
410
- console.log(` ${dim(`${available.length + 1}.`)} ${bold("Entire deck")}`)
410
+ console.log(` ${bold("What do you want to publish?")}`)
411
411
  console.log()
412
412
 
413
- const choice = await prompt("Select file number:", "1")
414
- const idx = parseInt(choice, 10) - 1
413
+ const options = [
414
+ ...available.map(f => `${f.target}${f.path}`),
415
+ "Entire deck"
416
+ ]
417
+ const idx = await select(options, options.length - 1)
415
418
 
416
419
  if (idx === available.length) {
417
420
  typeOverride = "deck"
418
- } else if (idx < 0 || idx >= available.length) {
419
- console.error(` ${red("Error:")} Invalid selection.`)
420
- process.exit(1)
421
421
  } else {
422
422
  filePath = relative(cwd, available[idx].fullPath)
423
423
  }
@@ -432,10 +432,34 @@ export async function publish(args) {
432
432
  process.exit(1)
433
433
  }
434
434
 
435
- const deckPrefix = await promptDeckPrefix(cwd, true)
436
- const dirName = basename(cwd)
437
- const deckBaseSlug = dirName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")
438
- const deckSlug = `${deckPrefix}/${deckBaseSlug}`
435
+ // Check if a deck slug already exists in the lockfile
436
+ const existingLock = readLockfile(cwd)
437
+ const existingSlug = existingLock.deckSlug
438
+ let deckPrefix, deckSlug
439
+
440
+ if (existingSlug) {
441
+ console.log(` ${dim("Previously published as")} ${cyan(existingSlug)}`)
442
+ console.log()
443
+ const reuse = await confirm(` Publish to ${bold(existingSlug)}?`)
444
+
445
+ if (reuse) {
446
+ deckSlug = existingSlug
447
+ deckPrefix = existingSlug.split("/")[0]
448
+ } else {
449
+ deckPrefix = await promptDeckPrefix(cwd, true)
450
+ const dirName = basename(cwd)
451
+ const deckBaseSlug = dirName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")
452
+ deckSlug = `${deckPrefix}/${deckBaseSlug}`
453
+ }
454
+ } else {
455
+ deckPrefix = await promptDeckPrefix(cwd, true)
456
+ const dirName = basename(cwd)
457
+ const deckBaseSlug = dirName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")
458
+ deckSlug = `${deckPrefix}/${deckBaseSlug}`
459
+ }
460
+
461
+ // Persist prefix for next time (slug is saved after successful publish)
462
+ updateLockfilePublishConfig(cwd, { deckPrefix })
439
463
 
440
464
  // Walk public/ to collect assets and build reference set
441
465
  const publicDir = join(cwd, "public")
@@ -721,6 +745,7 @@ export async function publish(args) {
721
745
  const assetSlugs = publicAssets.map(a => assetFileToSlug(deckPrefix, a.relativePath))
722
746
  const allDeckDeps = [...slideSlugs, ...assetSlugs]
723
747
 
748
+ let deckItemId = null
724
749
  try {
725
750
  const result = await publishToRegistry({
726
751
  type: "deck",
@@ -736,6 +761,8 @@ export async function publish(args) {
736
761
  }, auth)
737
762
  console.log(` [${itemIndex}/${totalItems}] ${green("✓")} deck ${cyan(deckSlug)} ${dim(`v${result.version}`)}`)
738
763
  updateLockfileItem(cwd, deckSlug, result.version ?? 0, {})
764
+ updateLockfilePublishConfig(cwd, { deckSlug })
765
+ deckItemId = result.id
739
766
  published++
740
767
  } catch (err) {
741
768
  console.log(` [${itemIndex}/${totalItems}] ${red("✗")} deck ${dim(deckSlug)}: ${err.message}`)
@@ -744,6 +771,9 @@ export async function publish(args) {
744
771
 
745
772
  console.log()
746
773
  console.log(` ${bold("Done:")} ${green(`${published} published`)}${skipped ? `, ${skipped} unchanged` : ""}${failed ? `, ${red(`${failed} failed`)}` : ""}`)
774
+ if (auth.organizationSlug && deckItemId) {
775
+ console.log(` View: ${cyan(`${auth.registry}/${auth.organizationSlug}/items/${deckItemId}`)}`)
776
+ }
747
777
  console.log(` Install: ${cyan(`promptslide add ${deckSlug}`)}`)
748
778
  console.log()
749
779
  closePrompts()
@@ -782,6 +812,10 @@ export async function publish(args) {
782
812
  // Prompt for deck prefix (required)
783
813
  const deckPrefix = await promptDeckPrefix(cwd, true)
784
814
  const slug = `${deckPrefix}/${baseSlug}`
815
+
816
+ // Persist prefix for next time
817
+ updateLockfilePublishConfig(cwd, { deckPrefix })
818
+
785
819
  console.log(` Slug: ${cyan(slug)}`)
786
820
  console.log()
787
821
 
@@ -919,6 +953,9 @@ export async function publish(args) {
919
953
  const verTag = result.version ? ` v${result.version}` : ""
920
954
  console.log(` ${green("✓")} Published ${bold(slug)}${verTag} to ${auth.organizationName || "registry"}`)
921
955
  console.log(` Status: ${result.status || "published"}`)
956
+ if (auth.organizationSlug && result.id) {
957
+ console.log(` View: ${cyan(`${auth.registry}/${auth.organizationSlug}/items/${result.id}`)}`)
958
+ }
922
959
  console.log(` Install: ${cyan(`promptslide add ${slug}`)}`)
923
960
 
924
961
  // Track in lockfile
@@ -0,0 +1,228 @@
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs"
2
+ import { execFileSync } from "node:child_process"
3
+ import { join, dirname, resolve, sep } from "node:path"
4
+
5
+ import { bold, green, cyan, red, yellow, dim } from "../utils/ansi.mjs"
6
+ import { requireAuth } from "../utils/auth.mjs"
7
+ import {
8
+ fetchRegistryItem,
9
+ resolveRegistryDependencies,
10
+ detectPackageManager,
11
+ getInstallCommand,
12
+ readLockfile,
13
+ updateLockfileItem,
14
+ hashContent,
15
+ hashFile,
16
+ isFileDirty
17
+ } from "../utils/registry.mjs"
18
+ import {
19
+ toPascalCase,
20
+ addSlideToDeckConfig,
21
+ replaceDeckConfig
22
+ } from "../utils/deck-config.mjs"
23
+ import { confirm, closePrompts } from "../utils/prompts.mjs"
24
+
25
+ export async function pull(args) {
26
+ const cwd = process.cwd()
27
+
28
+ console.log()
29
+ console.log(` ${bold("promptslide")} ${dim("pull")}`)
30
+ console.log()
31
+
32
+ if (args[0] === "--help" || args[0] === "-h") {
33
+ console.log(` ${bold("Usage:")} promptslide pull ${dim("[--force]")}`)
34
+ console.log()
35
+ console.log(` Pull the latest version of your deck from the registry.`)
36
+ console.log(` Reads the deck slug from the lockfile (set during publish).`)
37
+ console.log()
38
+ console.log(` ${bold("Options:")}`)
39
+ console.log(` --force Overwrite locally modified files without prompting`)
40
+ console.log()
41
+ process.exit(0)
42
+ }
43
+
44
+ const force = args.includes("--force")
45
+ const auth = requireAuth()
46
+
47
+ // Read deckSlug from lockfile
48
+ const lock = readLockfile(cwd)
49
+ const deckSlug = lock.deckSlug
50
+
51
+ if (!deckSlug) {
52
+ console.error(` ${red("Error:")} No deck slug found in lockfile.`)
53
+ console.error(` ${dim("Run")} ${cyan("promptslide publish --type deck")} ${dim("first to set it.")}`)
54
+ console.log()
55
+ process.exit(1)
56
+ }
57
+
58
+ console.log(` Pulling deck ${bold(deckSlug)}...`)
59
+ console.log()
60
+
61
+ // Fetch the deck manifest from the registry
62
+ let deckItem
63
+ try {
64
+ deckItem = await fetchRegistryItem(deckSlug, auth)
65
+ } catch (err) {
66
+ console.error(` ${red("Error:")} ${err.message}`)
67
+ process.exit(1)
68
+ }
69
+
70
+ const versionTag = deckItem.version ? ` ${dim(`v${deckItem.version}`)}` : ""
71
+ console.log(` Found ${bold(deckItem.title || deckItem.name)} ${dim(`(${deckItem.type})`)}${versionTag}`)
72
+
73
+ // Resolve all dependencies (slides, layouts, themes, assets)
74
+ let resolved
75
+ try {
76
+ resolved = await resolveRegistryDependencies(deckItem, auth, cwd)
77
+ } catch (err) {
78
+ console.error(` ${red("Error resolving dependencies:")} ${err.message}`)
79
+ process.exit(1)
80
+ }
81
+
82
+ const itemCount = resolved.items.length
83
+ const fileCount = resolved.items.reduce((sum, i) => sum + (i.files?.length || 0), 0)
84
+ console.log(` Resolved ${bold(String(itemCount))} items with ${bold(String(fileCount))} files`)
85
+ console.log()
86
+
87
+ // Write files
88
+ const written = []
89
+ const writtenByItem = new Map()
90
+
91
+ for (const regItem of resolved.items) {
92
+ if (!regItem.files?.length) continue
93
+
94
+ const fileHashes = {}
95
+
96
+ for (const file of regItem.files) {
97
+ const targetPath = resolve(cwd, file.target, file.path)
98
+ if (!targetPath.startsWith(cwd + sep)) {
99
+ console.log(` ${red("Error:")} Invalid file path: ${file.target}${file.path}`)
100
+ continue
101
+ }
102
+ const targetDir = dirname(targetPath)
103
+ const relativePath = file.target + file.path
104
+ const newHash = hashContent(file.content)
105
+
106
+ if (existsSync(targetPath)) {
107
+ // Check if content is identical
108
+ const dataUriMatch = file.content.match(/^data:[^;]+;base64,/)
109
+ let identical
110
+ if (dataUriMatch) {
111
+ const newBuf = Buffer.from(file.content.slice(dataUriMatch[0].length), "base64")
112
+ const existingBuf = readFileSync(targetPath)
113
+ identical = newBuf.equals(existingBuf)
114
+ } else {
115
+ identical = hashFile(targetPath) === newHash
116
+ }
117
+
118
+ if (identical) {
119
+ fileHashes[relativePath] = newHash
120
+ continue
121
+ }
122
+
123
+ // Check if file has local modifications
124
+ const lockEntry = lock.items[regItem.name]
125
+ const storedHash = lockEntry?.files?.[relativePath]
126
+ if (!force) {
127
+ if (storedHash && isFileDirty(cwd, relativePath, storedHash)) {
128
+ // File was tracked and has been locally modified
129
+ const overwrite = await confirm(
130
+ ` ${yellow("!")} ${relativePath} has local changes. Overwrite?`,
131
+ false
132
+ )
133
+ if (!overwrite) {
134
+ fileHashes[relativePath] = storedHash
135
+ console.log(` ${dim("Skipped")} ${relativePath}`)
136
+ continue
137
+ }
138
+ } else if (!storedHash) {
139
+ // File exists but not in lockfile (first pull or untracked)
140
+ const overwrite = await confirm(` Overwrite ${relativePath}? ${dim("(local changes will be lost)")}`, false)
141
+ if (!overwrite) {
142
+ console.log(` ${dim("Skipped")} ${relativePath}`)
143
+ continue
144
+ }
145
+ }
146
+ }
147
+ }
148
+
149
+ // Write the file
150
+ mkdirSync(targetDir, { recursive: true })
151
+ const dataUriPrefix = file.content.match(/^data:[^;]+;base64,/)
152
+ if (dataUriPrefix) {
153
+ writeFileSync(targetPath, Buffer.from(file.content.slice(dataUriPrefix[0].length), "base64"))
154
+ } else {
155
+ writeFileSync(targetPath, file.content, "utf-8")
156
+ }
157
+ fileHashes[relativePath] = newHash
158
+ written.push({ item: regItem, file })
159
+ console.log(` ${green("+")} ${cyan(relativePath)}`)
160
+ }
161
+
162
+ if (Object.keys(fileHashes).length > 0) {
163
+ writtenByItem.set(regItem.name, { regItem, fileHashes })
164
+ }
165
+ }
166
+
167
+ // Update lockfile for all resolved items
168
+ for (const [name, { regItem, fileHashes }] of writtenByItem) {
169
+ updateLockfileItem(cwd, name, regItem.version ?? 0, fileHashes)
170
+ }
171
+
172
+ // Also update the deck manifest entry itself (has no files)
173
+ updateLockfileItem(cwd, deckSlug, deckItem.version ?? 0, {})
174
+
175
+ // Update deck-config.ts
176
+ if (deckItem.type === "deck" && deckItem.meta?.slides) {
177
+ const shouldReplace = await confirm(" Replace deck-config.ts with pulled deck config?", true)
178
+ if (shouldReplace) {
179
+ const slides = deckItem.meta.slides.map(s => ({
180
+ componentName: toPascalCase(s.slug),
181
+ importPath: `@/slides/${s.slug}`,
182
+ steps: s.steps,
183
+ section: s.section
184
+ }))
185
+ replaceDeckConfig(cwd, slides, {
186
+ transition: deckItem.meta.transition,
187
+ directionalTransition: deckItem.meta.directionalTransition
188
+ })
189
+ console.log(` ${green("+")} Replaced ${cyan("deck-config.ts")} ${dim(`(${slides.length} slides)`)}`)
190
+ } else {
191
+ // Append individual slides
192
+ for (const s of deckItem.meta.slides) {
193
+ const componentName = toPascalCase(s.slug)
194
+ const importPath = `@/slides/${s.slug}`
195
+ const updated = addSlideToDeckConfig(cwd, { componentName, importPath, steps: s.steps, section: s.section })
196
+ if (updated) {
197
+ console.log(` ${green("+")} Added ${componentName} to ${cyan("deck-config.ts")}`)
198
+ }
199
+ }
200
+ }
201
+ }
202
+
203
+ // Install npm dependencies
204
+ if (Object.keys(resolved.npmDeps).length > 0 && existsSync(join(cwd, "package.json"))) {
205
+ const pm = detectPackageManager(cwd)
206
+ const pkgList = Object.entries(resolved.npmDeps).map(([name, ver]) => `${name}@${ver}`)
207
+ const { cmd, args: installArgs, display } = getInstallCommand(pm, pkgList)
208
+ console.log()
209
+ console.log(` ${dim(`Installing dependencies: ${display}`)}`)
210
+ try {
211
+ execFileSync(cmd, installArgs, { cwd, stdio: "inherit" })
212
+ console.log(` ${green("+")} Dependencies installed`)
213
+ } catch {
214
+ console.log(` ${red("!")} Dependency installation failed. Run manually:`)
215
+ console.log(` ${display}`)
216
+ }
217
+ }
218
+
219
+ // Summary
220
+ console.log()
221
+ if (written.length === 0) {
222
+ console.log(` ${green("+")} Everything is up to date.`)
223
+ } else {
224
+ console.log(` ${green("+")} Pulled ${bold(String(written.length))} file(s) from ${bold(deckSlug)}`)
225
+ }
226
+ console.log()
227
+ closePrompts()
228
+ }
@@ -8,6 +8,9 @@ export async function studio(args) {
8
8
  const cwd = process.cwd()
9
9
  const portArg = args.find(a => a.startsWith("--port="))
10
10
  const port = portArg ? parseInt(portArg.split("=")[1], 10) : 5173
11
+ const hasHost = args.includes("--host") || args.some(a => a.startsWith("--host="))
12
+ const hostArg = args.find(a => a.startsWith("--host="))
13
+ const host = hasHost ? (hostArg ? hostArg.split("=")[1] || "0.0.0.0" : "0.0.0.0") : undefined
11
14
 
12
15
  ensureTsConfig(cwd)
13
16
 
@@ -18,7 +21,11 @@ export async function studio(args) {
18
21
  const config = createViteConfig({ cwd, mode: "development" })
19
22
  const server = await createServer({
20
23
  ...config,
21
- server: { port, strictPort: false }
24
+ server: {
25
+ port,
26
+ strictPort: false,
27
+ ...(host && { host, allowedHosts: true })
28
+ }
22
29
  })
23
30
 
24
31
  await server.listen()
package/src/core/index.ts CHANGED
@@ -63,6 +63,13 @@ export type { ThemeConfig } from "./types"
63
63
  // Shared Footer
64
64
  export { SlideFooter } from "./layouts/shared-footer"
65
65
 
66
+ // SlideRenderer
67
+ export { SlideRenderer } from "./slide-renderer"
68
+ export type { SlideRendererProps } from "./slide-renderer"
69
+
70
+ // SlideEmbed
71
+ export { SlideEmbed } from "./slide-embed"
72
+
66
73
  // SlideDeck
67
74
  export { SlideDeck } from "./slide-deck"
68
75
 
@@ -1,14 +1,14 @@
1
- import { AnimatePresence, LayoutGroup, motion } from "framer-motion"
1
+ import { LayoutGroup } from "framer-motion"
2
2
  import { ChevronLeft, ChevronRight, Download, Grid3X3, List, Maximize, Monitor } from "lucide-react"
3
3
  import { useCallback, useEffect, useRef, useState } from "react"
4
4
 
5
5
  import type { SlideTransitionType } from "./transitions"
6
6
  import type { SlideConfig } from "./types"
7
7
 
8
- import { SLIDE_DIMENSIONS, SLIDE_TRANSITION } from "./animation-config"
8
+ import { SLIDE_DIMENSIONS } from "./animation-config"
9
9
  import { AnimationProvider } from "./animation-context"
10
10
  import { SlideErrorBoundary } from "./slide-error-boundary"
11
- import { DEFAULT_SLIDE_TRANSITION, getSlideVariants } from "./transitions"
11
+ import { SlideRenderer } from "./slide-renderer"
12
12
  import { useSlideNavigation } from "./use-slide-navigation"
13
13
  import { cn } from "./utils"
14
14
 
@@ -177,18 +177,6 @@ export function SlideDeck({ slides, transition, directionalTransition }: SlideDe
177
177
  return () => window.removeEventListener("keydown", handleKeyDown)
178
178
  }, [advance, goBack, viewMode, togglePresentationMode])
179
179
 
180
- // Per-slide transition resolution
181
- const currentSlideTransition = slides[currentSlide]?.transition
182
- const transitionType = currentSlideTransition ?? transition ?? DEFAULT_SLIDE_TRANSITION
183
- const isDirectional = directionalTransition ?? false
184
-
185
- const slideVariants = getSlideVariants(
186
- { type: transitionType, directional: isDirectional },
187
- direction
188
- )
189
-
190
- const CurrentSlideComponent = slides[currentSlide]!.component
191
-
192
180
  return (
193
181
  <div className="min-h-screen w-full bg-neutral-950 text-foreground">
194
182
  <style>{`
@@ -299,73 +287,31 @@ export function SlideDeck({ slides, transition, directionalTransition }: SlideDe
299
287
  transformOrigin: "center center"
300
288
  }}
301
289
  >
302
- <AnimatePresence initial={false}>
303
- <motion.div
304
- key={currentSlide}
305
- variants={slideVariants}
306
- initial="enter"
307
- animate="center"
308
- exit="exit"
309
- transition={SLIDE_TRANSITION}
310
- onAnimationComplete={definition => {
311
- if (definition === "center") {
312
- onTransitionComplete()
313
- }
314
- }}
315
- className="absolute inset-0 h-full w-full"
316
- >
317
- <AnimationProvider
318
- currentStep={animationStep}
319
- totalSteps={totalSteps}
320
- showAllAnimations={showAllAnimations}
321
- >
322
- <SlideErrorBoundary
323
- slideIndex={currentSlide}
324
- slideTitle={slides[currentSlide]?.title}
325
- >
326
- <CurrentSlideComponent
327
- slideNumber={currentSlide + 1}
328
- totalSlides={slides.length}
329
- />
330
- </SlideErrorBoundary>
331
- </AnimationProvider>
332
- </motion.div>
333
- </AnimatePresence>
290
+ <SlideRenderer
291
+ slides={slides}
292
+ currentSlide={currentSlide}
293
+ animationStep={animationStep}
294
+ totalSteps={totalSteps}
295
+ direction={direction}
296
+ showAllAnimations={showAllAnimations}
297
+ transition={transition}
298
+ directionalTransition={directionalTransition}
299
+ onTransitionComplete={onTransitionComplete}
300
+ />
334
301
  </div>
335
302
  ) : (
336
303
  <div className="relative aspect-video w-full max-w-7xl overflow-hidden rounded-xl border border-neutral-800 bg-black shadow-2xl">
337
- <AnimatePresence initial={false}>
338
- <motion.div
339
- key={currentSlide}
340
- variants={slideVariants}
341
- initial="enter"
342
- animate="center"
343
- exit="exit"
344
- transition={SLIDE_TRANSITION}
345
- onAnimationComplete={definition => {
346
- if (definition === "center") {
347
- onTransitionComplete()
348
- }
349
- }}
350
- className="absolute inset-0 h-full w-full"
351
- >
352
- <AnimationProvider
353
- currentStep={animationStep}
354
- totalSteps={totalSteps}
355
- showAllAnimations={showAllAnimations}
356
- >
357
- <SlideErrorBoundary
358
- slideIndex={currentSlide}
359
- slideTitle={slides[currentSlide]?.title}
360
- >
361
- <CurrentSlideComponent
362
- slideNumber={currentSlide + 1}
363
- totalSlides={slides.length}
364
- />
365
- </SlideErrorBoundary>
366
- </AnimationProvider>
367
- </motion.div>
368
- </AnimatePresence>
304
+ <SlideRenderer
305
+ slides={slides}
306
+ currentSlide={currentSlide}
307
+ animationStep={animationStep}
308
+ totalSteps={totalSteps}
309
+ direction={direction}
310
+ showAllAnimations={showAllAnimations}
311
+ transition={transition}
312
+ directionalTransition={directionalTransition}
313
+ onTransitionComplete={onTransitionComplete}
314
+ />
369
315
  </div>
370
316
  )}
371
317
  </LayoutGroup>
@@ -427,9 +373,15 @@ export function SlideDeck({ slides, transition, directionalTransition }: SlideDe
427
373
  className="h-full w-full origin-top-left scale-[0.25]"
428
374
  style={{ width: "400%", height: "400%" }}
429
375
  >
430
- <SlideErrorBoundary slideIndex={index} slideTitle={slideConfig.title}>
431
- <SlideComponent slideNumber={index + 1} totalSlides={slides.length} />
432
- </SlideErrorBoundary>
376
+ <AnimationProvider
377
+ currentStep={slideConfig.steps}
378
+ totalSteps={slideConfig.steps}
379
+ showAllAnimations={true}
380
+ >
381
+ <SlideErrorBoundary slideIndex={index} slideTitle={slideConfig.title}>
382
+ <SlideComponent slideNumber={index + 1} totalSlides={slides.length} />
383
+ </SlideErrorBoundary>
384
+ </AnimationProvider>
433
385
  </div>
434
386
  <div className="absolute inset-0 bg-black/0 transition-colors group-hover:bg-black/20" />
435
387
  <div className="absolute bottom-2 left-2 rounded bg-black/70 px-2 py-1 text-xs font-medium text-white">