import { useState, useCallback, useRef, useEffect } from "react";
const THEMES = {
minimal: {
name: "Minimal", icon: "◻",
bg: "#ffffff", text: "#111111", accent: "#111111",
sub: "#777777", cardBg: "#f9f9f9", border: "#e0e0e0",
font: "'Playfair Display', Georgia, serif", bodyFont: "Georgia, serif",
gradient: "linear-gradient(135deg,#f5f5f5,#ffffff)",
},
corporate: {
name: "Corporate", icon: "🏛",
bg: "#0f2744", text: "#ffffff", accent: "#f0a832",
sub: "#a8c4e0", cardBg: "#162f52", border: "#1e3d6a",
font: "'Cormorant Garamond', 'Times New Roman', serif", bodyFont: "'Times New Roman', serif",
gradient: "linear-gradient(135deg,#0f2744,#1a3a5c)",
},
creative: {
name: "Creative", icon: "🎨",
bg: "#1a0533", text: "#ffffff", accent: "#ff6ec7",
sub: "#c084fc", cardBg: "#2d0f52", border: "#4a1a7a",
font: "'Bebas Neue', Impact, sans-serif", bodyFont: "Arial, sans-serif",
gradient: "linear-gradient(135deg,#1a0533,#3d0f6b)",
},
educational: {
name: "Educational", icon: "📚",
bg: "#fef9ee", text: "#1a1a1a", accent: "#d97706",
sub: "#6b7280", cardBg: "#fff8e1", border: "#fde68a",
font: "'Merriweather', Georgia, serif", bodyFont: "Georgia, serif",
gradient: "linear-gradient(135deg,#fef9ee,#fff8e1)",
},
tech: {
name: "Tech", icon: "⬡",
bg: "#0d1117", text: "#e6edf3", accent: "#58a6ff",
sub: "#3fb950", cardBg: "#161b22", border: "#30363d",
font: "'Share Tech Mono', 'Courier New', monospace", bodyFont: "'Courier New', monospace",
gradient: "linear-gradient(135deg,#0d1117,#161b22)",
},
startup: {
name: "Startup", icon: "🚀",
bg: "#ff4d4d", text: "#ffffff", accent: "#ffcd3c",
sub: "#ffa0a0", cardBg: "#e03d3d", border: "#cc2c2c",
font: "'Righteous', Arial Black, sans-serif", bodyFont: "Arial, sans-serif",
gradient: "linear-gradient(135deg,#ff4d4d,#ff7043)",
},
};
const LAYOUT_TYPES = ["title","bullets","two-col","quote","stats","visual","section"];
function SlideRenderer({ slide, theme, idx, total }) {
const t = THEMES[theme];
const base = {
width:"100%", height:"100%", background: t.gradient,
fontFamily: t.bodyFont, color: t.text,
padding: "0", display:"flex", flexDirection:"column",
position:"relative", overflow:"hidden", boxSizing:"border-box",
};
const Deco = () => (
{theme==="tech" && <>
>}
{theme==="creative" && <>
>}
{theme==="startup" && <>
>}
{theme==="minimal" &&
}
{theme==="corporate" && <>
);
if (slide.type === "title") return (
{slide.icon &&
{slide.icon}
}
{slide.title}
{slide.subtitle &&
{slide.subtitle}
}
{slide.badge &&
{slide.badge}
}
);
if (slide.type === "section") return (
{slide.title}
{slide.subtitle &&
{slide.subtitle}
}
);
if (slide.type === "bullets") return (
{slide.title}
{(slide.bullets||[]).map((b,i) => (
);
if (slide.type === "two-col") return (
{slide.title}
{[slide.col1,slide.col2].map((col,ci) => col && (
{col.heading}
{(col.points||[]).map((p,i) =>
• {p}
)}
))}
);
if (slide.type === "quote") return (
"
{slide.quote}
{slide.author &&
— {slide.author}
}
);
if (slide.type === "stats") return (
{slide.title}
{(slide.stats||[]).map((s,i) => (
))}
{slide.note &&
{slide.note}
}
);
if (slide.type === "visual") return (
{slide.title}
{slide.body}
{slide.cta &&
{slide.cta}
}
{slide.icon||"🖼"}
{slide.caption &&
{slide.caption}
}
);
return (
{slide.title}
);
}
const SLIDE_COUNT_OPTIONS = [8,10,12,15,18,20];
export default function App() {
const [dark, setDark] = useState(false);
const [topic, setTopic] = useState("");
const [keywords, setKeywords] = useState("");
const [theme, setTheme] = useState("corporate");
const [slideCount, setSlideCount] = useState(12);
const [slides, setSlides] = useState([]);
const [current, setCurrent] = useState(0);
const [loading, setLoading] = useState(false);
const [status, setStatus] = useState("");
const [error, setError] = useState("");
const [editingSlide, setEditingSlide] = useState(null);
const [tab, setTab] = useState("create"); // create | preview | edit
const [exportMsg, setExportMsg] = useState("");
const previewRef = useRef(null);
const nm = dark
? { bg:"#1a1e2e", surface:"#222840", shadow1:"rgba(0,0,0,0.5)", shadow2:"rgba(255,255,255,0.03)", text:"#e2e8f0", sub:"#94a3b8", border:"#2d3560", accent:"#6ee7b7", inputBg:"#1a1e2e" }
: { bg:"#dde3f0", surface:"#e8eef8", shadow1:"rgba(175,185,210,0.8)", shadow2:"rgba(255,255,255,0.9)", text:"#1e2a3b", sub:"#5a6a80", border:"#cdd5e8", accent:"#3b82f6", inputBg:"#dde3f0" };
const neumBox = (inset=false) => ({
background: nm.surface,
borderRadius: 16,
boxShadow: inset
? `inset 4px 4px 10px ${nm.shadow1}, inset -4px -4px 10px ${nm.shadow2}`
: `8px 8px 20px ${nm.shadow1}, -6px -6px 16px ${nm.shadow2}`,
});
const neumBtn = (active=false) => ({
...neumBox(active),
border: "none", cursor:"pointer", outline:"none",
color: active ? nm.accent : nm.text,
transition:"all 0.2s ease", fontWeight:600,
});
async function generate() {
if (!topic.trim()) { setError("Please enter a topic first!"); return; }
setError(""); setSlides([]); setLoading(true); setStatus("Thinking about your presentation…"); setCurrent(0);
const systemPrompt = `You are an expert presentation designer. Generate a complete, compelling slide deck as JSON. Return ONLY valid JSON, no markdown, no explanation.
The JSON must be: { "slides": [...] }
Each slide must have: { "id": number, "type": string, "title": string, ...type-specific fields }
Slide types and their fields:
- "title": title, subtitle, badge (optional), icon (emoji)
- "section": title, subtitle
- "bullets": title, bullets (array of strings, 3-5 items)
- "two-col": title, col1: {heading, points[]}, col2: {heading, points[]}
- "quote": quote, author
- "stats": title, stats: [{value, label}] (3-4 stats), note (optional)
- "visual": title, body, icon (emoji), caption, cta (optional)
Rules:
- Start with a "title" slide
- End with a "section" slide as conclusion/call-to-action
- Include at least 1 "stats" slide, 1 "quote" slide, 1 "two-col" slide
- Make content specific, insightful, and professional for the ${theme} style
- Vary slide types, avoid repetition
- Total slides: exactly ${slideCount}`;
const userMsg = `Create a ${slideCount}-slide presentation about: "${topic}".
Keywords/focus: ${keywords || "general overview"}.
Style: ${THEMES[theme].name}.
Make it engaging, specific, and data-driven where possible.`;
try {
setStatus("Calling AI to generate your slides…");
const res = await fetch("https://api.anthropic.com/v1/messages", {
method:"POST",
headers:{ "Content-Type":"application/json" },
body: JSON.stringify({
model:"claude-sonnet-4-20250514",
max_tokens:4000,
system: systemPrompt,
messages:[{role:"user",content:userMsg}],
}),
});
if (!res.ok) throw new Error(`API error ${res.status}`);
const data = await res.json();
const raw = data.content?.[0]?.text || "";
let parsed;
try {
const clean = raw.replace(/```json|```/g,"").trim();
parsed = JSON.parse(clean);
} catch(e) {
const match = raw.match(/\{[\s\S]*\}/);
if (match) parsed = JSON.parse(match[0]);
else throw new Error("Could not parse response as JSON");
}
setSlides(parsed.slides || []);
setStatus("");
setTab("preview");
} catch(e) {
setError("Generation failed: " + e.message);
setStatus("");
} finally {
setLoading(false);
}
}
function doExport(type) {
setExportMsg(`${type} export initiated! In a full deployment this would generate a real ${type} file.`);
setTimeout(() => setExportMsg(""), 3500);
}
function shuffleTheme() {
const keys = Object.keys(THEMES);
const next = keys[(keys.indexOf(theme)+1) % keys.length];
setTheme(next);
}
const s = nm;
return (
{/* Header */}
⬡
SlideForge
AI Presentation Generator
{["create","preview","edit"].map(t2 => (
))}
{/* Left Panel */}
{/* Input Card */}
✦ Presentation Topic
Keywords (optional)
setKeywords(e.target.value)}
placeholder="innovation, data, growth, 2025…"
style={{width:"100%",background:"transparent",border:"none",outline:"none",padding:"12px 14px",color:s.text,fontSize:13,fontFamily:"inherit",boxSizing:"border-box"}}/>
{/* Theme Selector */}
⬡ Slide Style
{Object.entries(THEMES).map(([key,th]) => (
))}
{/* Slide Count */}
⊞ Number of Slides
{SLIDE_COUNT_OPTIONS.map(n => (
))}
{/* Generate Button */}
{error &&
{error}
}
{/* Export */}
{slides.length > 0 && (
↓ Export
{[["PPTX","📊"],["PDF","📄"],["Link","🔗"]].map(([t3,ic]) => (
))}
{exportMsg &&
{exportMsg}
}
)}
{/* Right Panel: Preview */}
{slides.length === 0 && !loading && (
⬡
Your slides will appear here
Enter a topic, choose a style, and hit Generate to create your AI-powered deck
)}
{loading && (
⟳
{status}
)}
{slides.length > 0 && (
<>
{/* Main Slide Preview */}
{slides[current] && }
{/* Navigation */}
{current+1} of
{slides.length}
{slides[current]?.type} slide
{/* Thumbnail Strip */}
All Slides
{slides.map((sl,i) => (
))}
{/* Quick Edit Panel (when edit tab) */}
{tab === "edit" && slides[current] && (
✎ Edit Slide {current+1}
Title
{
const updated = [...slides];
updated[current] = {...updated[current], title: e.target.value};
setSlides(updated);
}} style={{width:"100%",background:"transparent",border:"none",outline:"none",padding:"10px 14px",color:s.text,fontSize:14,fontFamily:"inherit",boxSizing:"border-box"}}/>
{slides[current].subtitle !== undefined && <>
Subtitle
{
const updated = [...slides];
updated[current] = {...updated[current], subtitle: e.target.value};
setSlides(updated);
}} style={{width:"100%",background:"transparent",border:"none",outline:"none",padding:"10px 14px",color:s.text,fontSize:14,fontFamily:"inherit",boxSizing:"border-box"}}/>
>}
{slides[current].type === "bullets" && (
<>
Bullet Points
{(slides[current].bullets||[]).map((b,bi) => (
•
{
const updated = [...slides];
const bullets = [...(updated[current].bullets||[])];
bullets[bi] = e.target.value;
updated[current] = {...updated[current], bullets};
setSlides(updated);
}} style={{flex:1,background:"transparent",border:"none",outline:"none",padding:"10px 4px 10px 0",color:s.text,fontSize:13,fontFamily:"inherit"}}/>
))}
>
)}
{slides[current].type === "stats" && (
<>
Statistics
{(slides[current].stats||[]).map((st,si) => (
{
const updated = [...slides];
const stats = [...(updated[current].stats||[])];
stats[si] = {...stats[si], value: e.target.value};
updated[current] = {...updated[current], stats};
setSlides(updated);
}} style={{width:"100%",background:"transparent",border:"none",outline:"none",padding:"8px 12px",color:s.accent,fontSize:16,fontWeight:700,fontFamily:"inherit",boxSizing:"border-box"}}/>
{
const updated = [...slides];
const stats = [...(updated[current].stats||[])];
stats[si] = {...stats[si], label: e.target.value};
updated[current] = {...updated[current], stats};
setSlides(updated);
}} style={{width:"100%",background:"transparent",border:"none",outline:"none",padding:"8px 12px",color:s.sub,fontSize:12,fontFamily:"inherit",boxSizing:"border-box"}}/>
))}
>
)}
)}
>
)}
);
}