import { useState } from "react";
const STEPS = [
{ id: "spark", question: "What moment first sparked your journey?", placeholder: "e.g. I lost my job and realized I had a gift for helping others rebuild...", hint: "Think of the turning point that set everything in motion." },
{ id: "struggle", question: "What's the biggest challenge you had to overcome?", placeholder: "e.g. I had no network, no budget, and no one believed in my vision...", hint: "The struggle is where your story gets its power." },
{ id: "superpower", question: "What unique strength did that journey reveal in you?", placeholder: "e.g. I discovered I could translate complex ideas into simple stories...", hint: "This becomes the core of your brand." },
{ id: "serve", question: "Who do you serve, and what transformation do you help them create?", placeholder: "e.g. I help women entrepreneurs go from invisible to influential...", hint: "Your mission is rooted in your story." }
];
const AVATAR_STYLES = [
{ label: "Visionary Leader", desc: "Bold, confident, forward-facing" },
{ label: "Creative Innovator", desc: "Expressive, artistic, dynamic" },
{ label: "Warm Connector", desc: "Approachable, genuine, inviting" },
{ label: "Bold Disruptor", desc: "Edgy, powerful, unapologetic" }
];
async function callClaude(systemPrompt, userMessage) {
const res = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"content-type": "application/json",
"anthropic-version": "2023-06-01",
"anthropic-dangerous-direct-browser-access": "true"
},
body: JSON.stringify({
model: "claude-haiku-4-5-20251001",
max_tokens: 1024,
system: systemPrompt,
messages: [{ role: "user", content: userMessage }]
})
});
const json = await res.json();
if (!res.ok) throw new Error(json?.error?.message || `HTTP ${res.status}`);
return json.content.map(b => b.text || "").join("").trim();
}
const C = {
bg: "#0d0b09", bgCard: "rgba(28,20,16,0.85)",
border: "rgba(107,76,54,0.35)", accent: "#a67c5b",
accentD: "#6b4c36", cream: "#d6c4ae", white: "#f5f0ea", muted: "#6b4c36"
};
const btnStyle = (extra = {}) => ({
background: `linear-gradient(135deg, ${C.accentD}, ${C.accent})`,
border: "none", borderRadius: "11px", padding: "12px 26px",
color: C.white, fontSize: "14px", fontWeight: "600",
cursor: "pointer", fontFamily: "Georgia, serif",
boxShadow: "0 4px 16px rgba(0,0,0,0.35)", ...extra
});
const cardStyle = (extra = {}) => ({
background: C.bgCard, border: `1px solid ${C.border}`,
borderRadius: "14px", padding: "18px 20px", ...extra
});
export default function App() {
const [step, setStep] = useState("intro");
const [answers, setAnswers] = useState({});
const [currentQ, setCurrentQ] = useState(0);
const [styleChoice, setStyleChoice] = useState(null);
const [result, setResult] = useState(null);
const [avatarUrl, setAvatarUrl] = useState(null);
const [busy, setBusy] = useState(false);
const [avatarBusy, setAvatarBusy] = useState(false);
const [errorMsg, setErrorMsg] = useState("");
const [debugMsg, setDebugMsg] = useState("");
const generate = async () => {
setBusy(true);
setErrorMsg("");
setDebugMsg("");
setStep("generating");
try {
const raw = await callClaude(
`Respond with a single valid JSON object only. No markdown. No backticks. No explanation. Start with { end with }.`,
`Brand story strategist task. Given these origin story answers:
- Spark: ${answers.spark}
- Struggle: ${answers.struggle}
- Superpower: ${answers.superpower}
- Who they serve: ${answers.serve}
Return this JSON:
{"headline":"1-line brand headline under 12 words","tagline":"5-7 word tagline","story":"3-sentence first-person origin story","archetype":"2-3 word brand archetype","coreMessage":"single most powerful brand message","visibilityStrategy":"2-3 sentences on visibility"}`
);
setDebugMsg(raw.slice(0, 100));
const match = raw.match(/\{[\s\S]*\}/);
if (!match) throw new Error(`Could not find JSON in response. Got: ${raw.slice(0, 150)}`);
const parsed = JSON.parse(match[0]);
setResult(parsed);
setStep("result");
} catch (e) {
setErrorMsg(e.message);
setStep("style");
} finally {
setBusy(false);
}
};
const generateAvatar = async () => {
setAvatarBusy(true);
setErrorMsg("");
try {
const imgPrompt = await callClaude(
"You write image generation prompts. Respond with plain text only, no quotes, no JSON.",
`Write a 35-word photorealistic portrait prompt for a ${styleChoice} professional whose brand archetype is "${result?.archetype}".`
);
const encoded = encodeURIComponent(imgPrompt.slice(0, 200));
setAvatarUrl(`https://image.pollinations.ai/prompt/${encoded}?width=400&height=400&nologo=true`);
} catch (e) {
setErrorMsg(e.message);
} finally {
setAvatarBusy(false);
}
};
const reset = () => {
setStep("intro"); setAnswers({}); setCurrentQ(0);
setStyleChoice(null); setResult(null); setAvatarUrl(null);
setErrorMsg(""); setDebugMsg("");
};
const ErrorBanner = () => errorMsg ? (
<div style={{ background: "rgba(160,40,20,0.15)", border: "1px solid rgba(200,80,60,0.4)",
borderRadius: "10px", padding: "11px 15px", marginBottom: "16px",
color: "#e8a090", fontSize: "12px", wordBreak: "break-word" }}>
⚠️ {errorMsg}
</div>
) : null;
return (
<div style={{
minHeight: "100vh",
background: `linear-gradient(155deg, ${C.bg} 0%, #1c1410 55%, ${C.bg} 100%)`,
fontFamily: "Georgia, serif", color: C.white,
display: "flex", flexDirection: "column",
alignItems: "center", justifyContent: "center",
padding: "24px"
}}>
<style>{`
@keyframes spin { to { transform: rotate(360deg); } }
textarea:focus { outline: none; border-color: ${C.accent} !important; box-shadow: 0 0 0 3px rgba(166,124,91,0.18) !important; }
textarea::placeholder { color: ${C.muted}; opacity: 1; }
`}</style>
<div style={{ position: "fixed", top: 0, left: 0, right: 0, height: "3px",
background: `linear-gradient(90deg, transparent, ${C.accentD}, ${C.cream}, ${C.accentD}, transparent)` }} />
{/* Header */}
<div style={{ textAlign: "center", marginBottom: "32px" }}>
<div style={{ fontSize: "10px", letterSpacing: "5px", textTransform: "uppercase", color: C.accent, marginBottom: "12px" }}>
Story to Strategy Workshop
</div>
<h1 style={{
fontSize: "clamp(28px,5vw,46px)", fontWeight: "300", margin: 0, lineHeight: 1.15,
background: `linear-gradient(135deg,#fff 0%,${C.cream} 55%,${C.accent} 100%)`,
WebkitBackgroundClip: "text", WebkitTextFillColor: "transparent"
}}>Your Brand Avatar</h1>
<p style={{ color: C.muted, fontSize: "14px", marginTop: "8px", fontStyle: "italic" }}>
From your origin story to your brand identity
</p>
</div>
{/* Main card */}
<div style={{ ...cardStyle(), maxWidth: "660px", width: "100%",
backdropFilter: "blur(14px)", boxShadow: "0 40px 80px rgba(0,0,0,0.55)" }}>
{/* INTRO */}
{step === "intro" && (
<div style={{ textAlign: "center" }}>
<div style={{ fontSize: "38px", marginBottom: "18px" }}>✦</div>
<h2 style={{ fontSize: "22px", fontWeight: "400", marginBottom: "12px" }}>Let's Build Your Brand Avatar</h2>
<p style={{ color: C.accent, lineHeight: 1.75, marginBottom: "28px", fontSize: "15px" }}>
Answer 4 questions about your origin story and we'll generate your complete brand identity.
</p>
<div style={{ display: "flex", gap: "8px", justifyContent: "center", flexWrap: "wrap", marginBottom: "28px" }}>
{["Your Spark","Your Struggle","Your Superpower","Your Mission"].map(s => (
<span key={s} style={{ padding: "6px 14px", borderRadius: "20px",
border: "1px solid rgba(166,124,91,0.3)", fontSize: "12px", color: C.cream }}>{s}</span>
))}
</div>
<button style={btnStyle()} onClick={() => setStep("questions")}>Begin My Story →</button>
</div>
)}
{/* QUESTIONS */}
{step === "questions" && (
<div>
<div style={{ display: "flex", gap: "6px", marginBottom: "26px" }}>
{STEPS.map((_, i) => (
<div key={i} style={{ height: "3px", flex: 1, borderRadius: "2px",
background: i <= currentQ ? `linear-gradient(90deg,${C.accentD},${C.cream})` : "rgba(255,255,255,0.08)",
transition: "background 0.3s" }} />
))}
</div>
<div style={{ fontSize: "10px", letterSpacing: "3px", textTransform: "uppercase", color: C.muted, marginBottom: "8px" }}>
Question {currentQ + 1} of {STEPS.length}
</div>
<h2 style={{ fontSize: "20px", fontWeight: "400", marginBottom: "6px", lineHeight: 1.45 }}>
{STEPS[currentQ].question}
</h2>
<p style={{ color: C.muted, fontSize: "13px", marginBottom: "16px", fontStyle: "italic" }}>
{STEPS[currentQ].hint}
</p>
<textarea
rows={4}
value={answers[STEPS[currentQ].id] || ""}
onChange={e => setAnswers(p => ({ ...p, [STEPS[currentQ].id]: e.target.value }))}
placeholder={STEPS[currentQ].placeholder}
style={{ width: "100%", background: "rgba(13,11,9,0.7)",
border: `1px solid ${C.border}`, borderRadius: "11px",
padding: "14px", color: C.white, fontSize: "15px",
fontFamily: "Georgia,serif", resize: "vertical",
boxSizing: "border-box", lineHeight: 1.65 }}
/>
<div style={{ display: "flex", justifyContent: "flex-end", marginTop: "18px" }}>
<button
onClick={() => currentQ < STEPS.length - 1 ? setCurrentQ(q => q + 1) : setStep("style")}
disabled={!answers[STEPS[currentQ].id]?.trim()}
style={btnStyle({ opacity: answers[STEPS[currentQ].id]?.trim() ? 1 : 0.35,
cursor: answers[STEPS[currentQ].id]?.trim() ? "pointer" : "not-allowed" })}>
{currentQ < STEPS.length - 1 ? "Next →" : "Choose My Style →"}
</button>
</div>
</div>
)}
{/* STYLE PICKER */}
{step === "style" && (
<div>
<h2 style={{ fontSize: "20px", fontWeight: "400", marginBottom: "6px" }}>Choose your brand presence</h2>
<p style={{ color: C.muted, fontSize: "14px", marginBottom: "20px" }}>Which style best reflects how you show up?</p>
<ErrorBanner />
{debugMsg && (
<div style={{ background: "rgba(0,0,0,0.3)", borderRadius: "8px", padding: "8px 12px",
marginBottom: "14px", fontSize: "11px", color: "#888", wordBreak: "break-all" }}>
Debug: {debugMsg}
</div>
)}
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "10px", marginBottom: "24px" }}>
{AVATAR_STYLES.map(s => (
<div key={s.label} onClick={() => setStyleChoice(s.label)} style={{
padding: "15px", borderRadius: "12px", cursor: "pointer",
border: styleChoice === s.label ? `2px solid ${C.accent}` : `1px solid ${C.border}`,
background: styleChoice === s.label ? "rgba(107,76,54,0.18)" : "rgba(13,11,9,0.45)",
transition: "all 0.2s"
}}>
<div style={{ fontWeight: "500", fontSize: "14px", marginBottom: "3px" }}>{s.label}</div>
<div style={{ color: C.muted, fontSize: "12px" }}>{s.desc}</div>
</div>
))}
</div>
<button onClick={generate} disabled={!styleChoice || busy}
style={btnStyle({ width: "100%", opacity: styleChoice ? 1 : 0.35,
cursor: styleChoice ? "pointer" : "not-allowed" })}>
Generate My Brand Identity ✦
</button>
</div>
)}
{/* GENERATING */}
{step === "generating" && (
<div style={{ textAlign: "center", padding: "48px 0" }}>
<div style={{ width: "52px", height: "52px", borderRadius: "50%",
border: "2px solid rgba(107,76,54,0.25)", borderTop: `2px solid ${C.accent}`,
animation: "spin 1s linear infinite", margin: "0 auto 22px" }} />
<h2 style={{ fontWeight: "300", fontSize: "20px", marginBottom: "8px" }}>Crafting your brand identity…</h2>
<p style={{ color: C.muted, fontSize: "14px", fontStyle: "italic" }}>Turning your origin story into strategy</p>
</div>
)}
{/* RESULT */}
{step === "result" && result && (
<div>
<ErrorBanner />
<div style={{ textAlign: "center", marginBottom: "22px", paddingBottom: "20px", borderBottom: `1px solid ${C.border}` }}>
<div style={{ fontSize: "10px", letterSpacing: "4px", color: C.accent, textTransform: "uppercase", marginBottom: "8px" }}>
Your Brand Identity
</div>
<h2 style={{ fontSize: "clamp(18px,4vw,26px)", fontWeight: "300", lineHeight: 1.3, marginBottom: "10px",
background: `linear-gradient(135deg,#fff,${C.cream})`,
WebkitBackgroundClip: "text", WebkitTextFillColor: "transparent" }}>
{result.headline}
</h2>
<span style={{ padding: "5px 18px", border: "1px solid rgba(166,124,91,0.4)",
borderRadius: "20px", fontSize: "13px", color: C.cream, fontStyle: "italic" }}>
"{result.tagline}"
</span>
</div>
{/* Avatar */}
<div style={{ ...cardStyle({ background: "rgba(13,11,9,0.6)", marginBottom: "18px", textAlign: "center" }) }}>
{avatarUrl ? (
<>
<img src={avatarUrl} alt="avatar" onError={() => setAvatarUrl(null)}
style={{ width: "144px", height: "144px", borderRadius: "50%", objectFit: "cover",
border: "3px solid rgba(166,124,91,0.5)", boxShadow: "0 8px 28px rgba(0,0,0,0.5)", marginBottom: "10px" }} />
<p style={{ margin: 0, color: C.muted, fontSize: "12px" }}>Your {styleChoice} avatar</p>
</>
) : (
<>
<div style={{ width: "90px", height: "90px", borderRadius: "50%", margin: "0 auto 14px",
background: "rgba(107,76,54,0.2)", border: `2px solid ${C.border}`,
display: "flex", alignItems: "center", justifyContent: "center", fontSize: "32px" }}>👤</div>
<button onClick={generateAvatar} disabled={avatarBusy}
style={btnStyle({ fontSize: "13px", padding: "9px 20px" })}>
{avatarBusy ? "Generating…" : `Generate ${styleChoice} Avatar`}
</button>
</>
)}
</div>
{[
{ label: "Brand Archetype", value: result.archetype, icon: "✦" },
{ label: "Your Origin Story", value: result.story, icon: "◈" },
{ label: "Core Message", value: result.coreMessage, icon: "◉" },
{ label: "Visibility Strategy", value: result.visibilityStrategy, icon: "▲" }
].map(item => (
<div key={item.label} style={{ ...cardStyle({ marginBottom: "12px", borderLeft: `3px solid ${C.accentD}` }) }}>
<div style={{ fontSize: "10px", letterSpacing: "2.5px", textTransform: "uppercase", color: C.accent, marginBottom: "6px" }}>
{item.icon} {item.label}
</div>
<p style={{ margin: 0, color: C.cream, lineHeight: 1.75, fontSize: "14px" }}>{item.value}</p>
</div>
))}
<button onClick={reset} style={btnStyle({
width: "100%", marginTop: "6px", background: "transparent",
border: "1px solid rgba(107,76,54,0.4)", color: C.accent, boxShadow: "none"
})}>Start Over</button>
</div>
)}
</div>
<p style={{ marginTop: "22px", color: "rgba(107,76,54,0.4)", fontSize: "11px", letterSpacing: "2px", textTransform: "uppercase" }}>
Story to Strategy Visibility Workshop
</p>
</div>
);
}