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" && <>
}
{idx+1} / {total}
); 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) => (
{b}
))}
); 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) => (
{s.value}
{s.label}
))}
{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