Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion app/api/generate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }],
}),
Expand Down
25 changes: 14 additions & 11 deletions components/VirtueForgeApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand Down Expand Up @@ -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 });
Expand All @@ -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) => {
Expand Down Expand Up @@ -248,6 +249,8 @@ export default function VirtueForgeApp() {
}));
setSelChild(appData.children.length);
}}
onUpdateChild={updateChild}
onRemoveChild={removeChild}
/>
)}

Expand Down
35 changes: 32 additions & 3 deletions components/app/ChildManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string[]>([]);
const [description, setDescription] = useState("");

const atLimit = !premium && children.length >= PLANS.free.children;

Expand All @@ -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);
};

Expand Down Expand Up @@ -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</label>
<input type="number" value={age} onChange={(e) => setAge(parseInt(e.target.value) || 0)}
min={1} max={16} style={inputStyle} />
<input
type="text"
inputMode="numeric"
pattern="[0-9]*"
value={age || ""}
onChange={(e) => {
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" }}
/>
</div>
<div>
<label style={{
Expand All @@ -160,6 +171,24 @@ export default function ChildManager({ children, onAdd, onRemove, premium, onNex
</select>
</div>

<div style={{ marginBottom: 14 }}>
<label style={{
display: "block", fontFamily: T.fontSans, fontSize: 13,
fontWeight: 600, color: T.navy, marginBottom: 6,
}}>
Looks &amp; loves <span style={{ fontWeight: 400, color: T.gray500 }}>
(optional, woven into every story)
</span>
</label>
<textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="e.g., long brown hair, loves dinosaurs and her stuffed wolf Bramble"
rows={2}
style={{ ...inputStyle, resize: "vertical" }}
/>
</div>

<div style={{ marginBottom: 20 }}>
<label style={{
display: "block", fontFamily: T.fontSans, fontSize: 13,
Expand Down
Loading
Loading