diff --git a/app/api/generate/route.ts b/app/api/generate/route.ts index 29b7af8..b67c19b 100644 --- a/app/api/generate/route.ts +++ b/app/api/generate/route.ts @@ -143,7 +143,9 @@ export async function POST(req: NextRequest) { }, body: JSON.stringify({ model: "claude-sonnet-4-20250514", - max_tokens: 3000, + // Headroom for ~2500-word custom stories (long bedtime sagas). + // Sonnet bills on actual output, not the cap, so raising this is free. + max_tokens: 8000, system: SYSTEM_PROMPT, messages: [{ role: "user", content: cleanPrompt }], }), diff --git a/components/VirtueForgeApp.tsx b/components/VirtueForgeApp.tsx index a8d7864..e90a47c 100644 --- a/components/VirtueForgeApp.tsx +++ b/components/VirtueForgeApp.tsx @@ -35,10 +35,8 @@ export default function VirtueForgeApp() { setAppData(data); setPremiumState(isPremium()); setLoaded(true); - // If returning user with setup done, go to dashboard - if (data.setupComplete && data.children.length > 0) { - setPage("dashboard"); - } + // Always start at the landing page — even returning visitors should see + // the marketing surface (and the "Continue with [child]" CTA there). }, []); useEffect(() => { @@ -83,6 +81,12 @@ export default function VirtueForgeApp() { upd({ children: [...appData.children, child], setupComplete: true }); }; + const updateChild = (i: number, child: ChildProfile) => { + const next = [...appData.children]; + next[i] = child; + upd({ children: next }); + }; + const resetAll = () => { trackEvent("data_reset"); setAppData({ children: [], familyVirtues: [], setupComplete: false }); @@ -91,13 +95,10 @@ export default function VirtueForgeApp() { }; const startJourney = () => { - // Go straight to Story Studio — the fastest path to value. - // StoryForge has inline setup, so no funnel is required. - if (appData.setupComplete && appData.children.length > 0) { - setPage("dashboard"); - } else { - setPage("stories"); - } + // Always land in Story Studio — the fastest path to value. StoryForge + // shows saved characters at the top if any exist, so returning users + // can jump straight to picking one and generating. + setPage("stories"); }; const handleDemo = (scenario: DemoScenario) => { @@ -248,6 +249,8 @@ export default function VirtueForgeApp() { })); setSelChild(appData.children.length); }} + onUpdateChild={updateChild} + onRemoveChild={removeChild} /> )} diff --git a/components/app/ChildManager.tsx b/components/app/ChildManager.tsx index fdf53b5..ceeb065 100644 --- a/components/app/ChildManager.tsx +++ b/components/app/ChildManager.tsx @@ -19,6 +19,7 @@ export default function ChildManager({ children, onAdd, onRemove, premium, onNex const [sex, setSex] = useState("boy"); const [readingLevel, setReadingLevel] = useState(""); const [struggles, setStruggles] = useState([]); + const [description, setDescription] = useState(""); const atLimit = !premium && children.length >= PLANS.free.children; @@ -28,8 +29,9 @@ export default function ChildManager({ children, onAdd, onRemove, premium, onNex name: name.trim(), age, sex, readingLevel: readingLevel || getDefaultReadingLevel(age), struggles, readBooks: [], virtueProgress: {}, + description: description.trim() || undefined, }); - setName(""); setAge(5); setSex("boy"); setReadingLevel(""); setStruggles([]); + setName(""); setAge(5); setSex("boy"); setReadingLevel(""); setStruggles([]); setDescription(""); setShowForm(false); }; @@ -132,8 +134,17 @@ export default function ChildManager({ children, onAdd, onRemove, premium, onNex display: "block", fontFamily: T.fontSans, fontSize: 13, fontWeight: 600, color: T.navy, marginBottom: 6, }}>Age - setAge(parseInt(e.target.value) || 0)} - min={1} max={16} style={inputStyle} /> + { + const v = e.target.value.replace(/[^0-9]/g, ""); + setAge(v ? parseInt(v, 10) : 0); + }} + style={{ ...inputStyle, appearance: "textfield" as React.CSSProperties["appearance"], MozAppearance: "textfield" }} + />
+
+ +