Advanced Doodle Training

import { useEffect, useMemo, useState } from “react”;
import { Card, CardContent } from “@/components/ui/card”;
import { Button } from “@/components/ui/button”;
import { Badge } from “@/components/ui/badge”;
import { Progress } from “@/components/ui/progress”;
import { motion, AnimatePresence } from “framer-motion”;

/**
* Life Through a Doodle’s Eyes – Interactive Story Page
* – Choose-your-own-adventure with breed-specific flavor text
* – Progress bar + breadcrumbs
* – Replayable endings with share button
* – Local storage save/restore
* – Keyboard & accessible buttons
* – Clean, modern UI using Tailwind + shadcn/ui
*/

const BREEDS = [
{ key: “golden”, name: “Goldendoodle”, emoji: “🥇” },
{ key: “berne”, name: “Bernedoodle”, emoji: “🏔️” },
{ key: “labra”, name: “Labradoodle”, emoji: “🛋️” },
{ key: “aussie”, name: “Aussiedoodle”, emoji: “🎯” },
];

// Helper to inject breed-specific flavor into text templates
function renderText(tpl: string, breedName: string) {
return tpl
.replaceAll(“{{breed}}”, breedName)
.replaceAll(“{{nickname}}”, nick(breedName));
}

function nick(breedName: string) {
switch (breedName) {
case “Goldendoodle”:
return “golden goofball”;
case “Bernedoodle”:
return “mountain snuggler”;
case “Labradoodle”:
return “chill cuddle pro”;
case “Aussiedoodle”:
return “zoomie strategist”;
default:
return “doodle”;
}
}

// Story graph
// Each node: { id, title, text, options: [{label, next}], end?: { badge, summary } }
const NODES = {
start: {
id: “start”,
title: “Good Morning, Doodle!”,
text:
“You stretch, shake the floof, and greet the sun. Do you…”,
options: [
{ label: “Jump on the bed and wake your human”, next: “bed_jump” },
{ label: “Wait like an angel by the food bowl”, next: “food_wait” },
],
},
bed_jump: {
id: “bed_jump”,
title: “Bed Launch Sequence 🚀”,
text:
“Your human groans, then laughs—classic {{nickname}} move. Belly rubs achieved! What’s next?”,
options: [
{ label: “Activate indoor zoomies”, next: “zoomies” },
{ label: “Melt into the couch like warm butter”, next: “couch_melt” },
],
},
food_wait: {
id: “food_wait”,
title: “Breakfast Diplomacy 🍳”,
text:
“Patience pays! Kibble served with a side of praise for the polite {{breed}}. Now…”,
options: [
{ label: “Deploy big eyes for a bacon bonus”, next: “bacon_heist” },
{ label: “Investigate fallen crumbs like a detective”, next: “crumb_case” },
],
},
zoomies: {
id: “zoomies”,
title: “Living Room Grand Prix 🏁”,
text:
“You tear around the house like a fuzzy comet. Humans cheer. Lap record shattered.”,
options: [
{ label: “Victory lap outside on a walk”, next: “walk” },
{ label: “Hydrate and plan next mission”, next: “water_plan” },
],
},
couch_melt: {
id: “couch_melt”,
title: “Couch Potato Supreme 🛋️”,
text:
“You become one with the furniture. A masterclass in relaxation.”,
options: [
{ label: “Nap hard; dream of tennis balls”, next: “dream_balls” },
{ label: “Hear leash jingle—WALK?!”, next: “walk” },
],
},
bacon_heist: {
id: “bacon_heist”,
title: “The Bacon Caper 🥓”,
text:
“Operation: Bacon. Success! (We won’t tell the vet.)”,
options: [
{ label: “Celebrate with a polite sit”, next: “walk” },
{ label: “Zoom in triumphant circles”, next: “zoomies” },
],
},
crumb_case: {
id: “crumb_case”,
title: “Crumb Detective 🔍”,
text:
“A toast fragment! Case closed, detective {{nickname}}.”,
options: [
{ label: “Request outside patrol”, next: “walk” },
{ label: “Retire to the couch”, next: “couch_melt” },
],
},
walk: {
id: “walk”,
title: “Neighborhood Patrol 🚶‍♀️”,
text:
“Squirrels are notified. You strut like the majestic {{breed}} you are. A friendly kid asks to pet you—of course!”,
options: [
{ label: “Splash through a mysterious puddle”, next: “puddle” },
{ label: “Heel like a champion for treats”, next: “heel_hero” },
],
},
water_plan: {
id: “water_plan”,
title: “Strategic Hydration 💧”,
text:
“Slurp. Slurp. You ponder life’s big questions: ball now or nap now?”,
options: [
{ label: “Ball now!”, next: “ball_quest” },
{ label: “Nap now…”, next: “dream_balls” },
],
},
puddle: {
id: “puddle”,
title: “Puddle Physics 🌊”,
text:
“You enter clean. You exit abstract art. Human: concerned. You: thrilled.”,
options: [],
end: {
badge: “The Splash Artist”,
summary:
“You embraced chaos and moisture. Bath time is inevitable, pride remains untouchable.”,
},
},
heel_hero: {
id: “heel_hero”,
title: “Heel of Fortune ⭐”,
text:
“You heel perfectly past distractions like a pro. Treat jackpot unlocked!”,
options: [],
end: {
badge: “The Polite Pro”,
summary: “Manners on point. Neighborhood reputation: impeccable.”,
},
},
ball_quest: {
id: “ball_quest”,
title: “The Eternal Ball Quest 🎾”,
text:
“You locate, retrieve, and parade the sacred tennis ball. Humans clap. Legends speak of your fetch.”,
options: [],
end: {
badge: “Fetch Champion”,
summary: “Boundless energy, laser focus, tail like a metronome of joy.”,
},
},
dream_balls: {
id: “dream_balls”,
title: “REM: Really Excellent Memories 💤”,
text:
“Feet twitch. Quiet barks. You’re racing dream-squirrels across cotton-candy fields.”,
options: [],
end: {
badge: “Cozy Cloud Rider”,
summary: “Rest mastered. Cuteness levels dangerously high.”,
},
},
} as const;

type NodeKey = keyof typeof NODES;

export default function DoodleInteractiveStory() {
const [breed, setBreed] = useState(null);
const [node, setNode] = useState(“start”);
const [trail, setTrail] = useState([“start”]);

// restore session
useEffect(() => {
const cached = localStorage.getItem(“doodle_story_state”);
if (cached) {
try {
const s = JSON.parse(cached);
if (s?.breed && s?.node && s?.trail) {
setBreed(s.breed);
setNode(s.node);
setTrail(s.trail);
}
} catch {}
}
}, []);

// persist session
useEffect(() => {
const payload = JSON.stringify({ breed, node, trail });
localStorage.setItem(“doodle_story_state”, payload);
}, [breed, node, trail]);

const progress = useMemo(() => {
// naive progress: start=0, endings ~ 100; mid nodes ~ 33/66
const depth = Math.max(0, trail.length – 1);
const capped = Math.min(100, Math.round((depth / 4) * 100));
return NODES[node].end ? 100 : Math.min(95, capped);
}, [node, trail]);

const isEnd = Boolean(NODES[node].end);

function go(next: NodeKey) {
setNode(next);
setTrail((t) => […t, next]);
}

function resetAll() {
setBreed(null);
setNode(“start”);
setTrail([“start”]);
}

async function share() {
const end = NODES[node].end;
const title = `I played Life Through a Doodle’s Eyes – I am: ${end?.badge}!`;
const text = `${end?.summary}`;
const url = typeof window !== “undefined” ? window.location.href : “”;
if (navigator.share) {
try {
await navigator.share({ title, text, url });
return;
} catch {}
}
// Fallback: copy link
try {
await navigator.clipboard.writeText(url);
alert(“Link copied! Share it with your doodle friends 🐾”);
} catch {
alert(“Couldn’t share automatically. You can copy the URL from the address bar.”);
}
}

return (


Life Through a Doodle’s Eyes 🐾

An interactive, choose-your-own-adventure for doodle lovers

{trail.map((t, i) => (

{NODES[t].title.replace(/ .*/, “”)}

))}

{!breed ? (

) : (



{BREEDS.find((b) => b.name === breed)?.emoji} {breed}

{NODES[node].title}

{renderText(NODES[node].text, breed)}

{!isEnd ? (

{NODES[node].options.map((opt, i) => (

))}

) : (

Your Ending

{NODES[node].end!.badge}

{NODES[node].end!.summary}



)}




)}

Tip: Use arrow keys to navigate choices (focus a button and press ←/→) or tap on mobile.

);
}

function BreedPicker({ onPick }: { onPick: (name: string) => void }) {
return (

Pick your doodle narrator

{BREEDS.map((b) => (
onPick(b.name)}
className=”rounded-2xl border p-4 text-center hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-ring”
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>

{b.emoji}
{b.name}
Start story


))}



);
}import { useEffect, useMemo, useState } from “react”;
import { Card, CardContent } from “@/components/ui/card”;
import { Button } from “@/components/ui/button”;
import { Badge } from “@/components/ui/badge”;
import { Progress } from “@/components/ui/progress”;
import { motion, AnimatePresence } from “framer-motion”;

/**
* Life Through a Doodle’s Eyes – Interactive Story Page
* – Choose-your-own-adventure with breed-specific flavor text
* – Progress bar + breadcrumbs
* – Replayable endings with share button
* – Local storage save/restore
* – Keyboard & accessible buttons
* – Clean, modern UI using Tailwind + shadcn/ui
*/

const BREEDS = [
{ key: “golden”, name: “Goldendoodle”, emoji: “🥇” },
{ key: “berne”, name: “Bernedoodle”, emoji: “🏔️” },
{ key: “labra”, name: “Labradoodle”, emoji: “🛋️” },
{ key: “aussie”, name: “Aussiedoodle”, emoji: “🎯” },
];

// Helper to inject breed-specific flavor into text templates
function renderText(tpl: string, breedName: string) {
return tpl
.replaceAll(“{{breed}}”, breedName)
.replaceAll(“{{nickname}}”, nick(breedName));
}

function nick(breedName: string) {
switch (breedName) {
case “Goldendoodle”:
return “golden goofball”;
case “Bernedoodle”:
return “mountain snuggler”;
case “Labradoodle”:
return “chill cuddle pro”;
case “Aussiedoodle”:
return “zoomie strategist”;
default:
return “doodle”;
}
}

// Story graph
// Each node: { id, title, text, options: [{label, next}], end?: { badge, summary } }
const NODES = {
start: {
id: “start”,
title: “Good Morning, Doodle!”,
text:
“You stretch, shake the floof, and greet the sun. Do you…”,
options: [
{ label: “Jump on the bed and wake your human”, next: “bed_jump” },
{ label: “Wait like an angel by the food bowl”, next: “food_wait” },
],
},
bed_jump: {
id: “bed_jump”,
title: “Bed Launch Sequence 🚀”,
text:
“Your human groans, then laughs—classic {{nickname}} move. Belly rubs achieved! What’s next?”,
options: [
{ label: “Activate indoor zoomies”, next: “zoomies” },
{ label: “Melt into the couch like warm butter”, next: “couch_melt” },
],
},
food_wait: {
id: “food_wait”,
title: “Breakfast Diplomacy 🍳”,
text:
“Patience pays! Kibble served with a side of praise for the polite {{breed}}. Now…”,
options: [
{ label: “Deploy big eyes for a bacon bonus”, next: “bacon_heist” },
{ label: “Investigate fallen crumbs like a detective”, next: “crumb_case” },
],
},
zoomies: {
id: “zoomies”,
title: “Living Room Grand Prix 🏁”,
text:
“You tear around the house like a fuzzy comet. Humans cheer. Lap record shattered.”,
options: [
{ label: “Victory lap outside on a walk”, next: “walk” },
{ label: “Hydrate and plan next mission”, next: “water_plan” },
],
},
couch_melt: {
id: “couch_melt”,
title: “Couch Potato Supreme 🛋️”,
text:
“You become one with the furniture. A masterclass in relaxation.”,
options: [
{ label: “Nap hard; dream of tennis balls”, next: “dream_balls” },
{ label: “Hear leash jingle—WALK?!”, next: “walk” },
],
},
bacon_heist: {
id: “bacon_heist”,
title: “The Bacon Caper 🥓”,
text:
“Operation: Bacon. Success! (We won’t tell the vet.)”,
options: [
{ label: “Celebrate with a polite sit”, next: “walk” },
{ label: “Zoom in triumphant circles”, next: “zoomies” },
],
},
crumb_case: {
id: “crumb_case”,
title: “Crumb Detective 🔍”,
text:
“A toast fragment! Case closed, detective {{nickname}}.”,
options: [
{ label: “Request outside patrol”, next: “walk” },
{ label: “Retire to the couch”, next: “couch_melt” },
],
},
walk: {
id: “walk”,
title: “Neighborhood Patrol 🚶‍♀️”,
text:
“Squirrels are notified. You strut like the majestic {{breed}} you are. A friendly kid asks to pet you—of course!”,
options: [
{ label: “Splash through a mysterious puddle”, next: “puddle” },
{ label: “Heel like a champion for treats”, next: “heel_hero” },
],
},
water_plan: {
id: “water_plan”,
title: “Strategic Hydration 💧”,
text:
“Slurp. Slurp. You ponder life’s big questions: ball now or nap now?”,
options: [
{ label: “Ball now!”, next: “ball_quest” },
{ label: “Nap now…”, next: “dream_balls” },
],
},
puddle: {
id: “puddle”,
title: “Puddle Physics 🌊”,
text:
“You enter clean. You exit abstract art. Human: concerned. You: thrilled.”,
options: [],
end: {
badge: “The Splash Artist”,
summary:
“You embraced chaos and moisture. Bath time is inevitable, pride remains untouchable.”,
},
},
heel_hero: {
id: “heel_hero”,
title: “Heel of Fortune ⭐”,
text:
“You heel perfectly past distractions like a pro. Treat jackpot unlocked!”,
options: [],
end: {
badge: “The Polite Pro”,
summary: “Manners on point. Neighborhood reputation: impeccable.”,
},
},
ball_quest: {
id: “ball_quest”,
title: “The Eternal Ball Quest 🎾”,
text:
“You locate, retrieve, and parade the sacred tennis ball. Humans clap. Legends speak of your fetch.”,
options: [],
end: {
badge: “Fetch Champion”,
summary: “Boundless energy, laser focus, tail like a metronome of joy.”,
},
},
dream_balls: {
id: “dream_balls”,
title: “REM: Really Excellent Memories 💤”,
text:
“Feet twitch. Quiet barks. You’re racing dream-squirrels across cotton-candy fields.”,
options: [],
end: {
badge: “Cozy Cloud Rider”,
summary: “Rest mastered. Cuteness levels dangerously high.”,
},
},
} as const;

type NodeKey = keyof typeof NODES;

export default function DoodleInteractiveStory() {
const [breed, setBreed] = useState(null);
const [node, setNode] = useState(“start”);
const [trail, setTrail] = useState([“start”]);

// restore session
useEffect(() => {
const cached = localStorage.getItem(“doodle_story_state”);
if (cached) {
try {
const s = JSON.parse(cached);
if (s?.breed && s?.node && s?.trail) {
setBreed(s.breed);
setNode(s.node);
setTrail(s.trail);
}
} catch {}
}
}, []);

// persist session
useEffect(() => {
const payload = JSON.stringify({ breed, node, trail });
localStorage.setItem(“doodle_story_state”, payload);
}, [breed, node, trail]);

const progress = useMemo(() => {
// naive progress: start=0, endings ~ 100; mid nodes ~ 33/66
const depth = Math.max(0, trail.length – 1);
const capped = Math.min(100, Math.round((depth / 4) * 100));
return NODES[node].end ? 100 : Math.min(95, capped);
}, [node, trail]);

const isEnd = Boolean(NODES[node].end);

function go(next: NodeKey) {
setNode(next);
setTrail((t) => […t, next]);
}

function resetAll() {
setBreed(null);
setNode(“start”);
setTrail([“start”]);
}

async function share() {
const end = NODES[node].end;
const title = `I played Life Through a Doodle’s Eyes – I am: ${end?.badge}!`;
const text = `${end?.summary}`;
const url = typeof window !== “undefined” ? window.location.href : “”;
if (navigator.share) {
try {
await navigator.share({ title, text, url });
return;
} catch {}
}
// Fallback: copy link
try {
await navigator.clipboard.writeText(url);
alert(“Link copied! Share it with your doodle friends 🐾”);
} catch {
alert(“Couldn’t share automatically. You can copy the URL from the address bar.”);
}
}

return (


Life Through a Doodle’s Eyes 🐾

An interactive, choose-your-own-adventure for doodle lovers

{trail.map((t, i) => (

{NODES[t].title.replace(/ .*/, “”)}

))}

{!breed ? (

) : (



{BREEDS.find((b) => b.name === breed)?.emoji} {breed}

{NODES[node].title}

{renderText(NODES[node].text, breed)}

{!isEnd ? (

{NODES[node].options.map((opt, i) => (

))}

) : (

Your Ending

{NODES[node].end!.badge}

{NODES[node].end!.summary}



)}




)}

Tip: Use arrow keys to navigate choices (focus a button and press ←/→) or tap on mobile.

);
}

function BreedPicker({ onPick }: { onPick: (name: string) => void }) {
return (

Pick your doodle narrator

{BREEDS.map((b) => (
onPick(b.name)}
className=”rounded-2xl border p-4 text-center hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-ring”
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>

{b.emoji}
{b.name}
Start story


))}



);
}

import { useEffect, useMemo, useState } from "react"; import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Progress } from "@/components/ui/progress"; import { motion, AnimatePresence } from "framer-motion";/** * Life Through a Doodle's Eyes – Interactive Story Page * - Choose-your-own-adventure with breed-specific flavor text * - Progress bar + breadcrumbs * - Replayable endings with share button * - Local storage save/restore * - Keyboard & accessible buttons * - Clean, modern UI using Tailwind + shadcn/ui */const BREEDS = [ { key: "golden", name: "Goldendoodle", emoji: "🥇" }, { key: "berne", name: "Bernedoodle", emoji: "🏔️" }, { key: "labra", name: "Labradoodle", emoji: "🛋️" }, { key: "aussie", name: "Aussiedoodle", emoji: "🎯" }, ];// Helper to inject breed-specific flavor into text templates function renderText(tpl: string, breedName: string) { return tpl .replaceAll("{{breed}}", breedName) .replaceAll("{{nickname}}", nick(breedName)); }function nick(breedName: string) { switch (breedName) { case "Goldendoodle": return "golden goofball"; case "Bernedoodle": return "mountain snuggler"; case "Labradoodle": return "chill cuddle pro"; case "Aussiedoodle": return "zoomie strategist"; default: return "doodle"; } }// Story graph // Each node: { id, title, text, options: [{label, next}], end?: { badge, summary } } const NODES = { start: { id: "start", title: "Good Morning, Doodle!", text: "You stretch, shake the floof, and greet the sun. Do you…", options: [ { label: "Jump on the bed and wake your human", next: "bed_jump" }, { label: "Wait like an angel by the food bowl", next: "food_wait" }, ], }, bed_jump: { id: "bed_jump", title: "Bed Launch Sequence 🚀", text: "Your human groans, then laughs—classic {{nickname}} move. Belly rubs achieved! What's next?", options: [ { label: "Activate indoor zoomies", next: "zoomies" }, { label: "Melt into the couch like warm butter", next: "couch_melt" }, ], }, food_wait: { id: "food_wait", title: "Breakfast Diplomacy 🍳", text: "Patience pays! Kibble served with a side of praise for the polite {{breed}}. Now…", options: [ { label: "Deploy big eyes for a bacon bonus", next: "bacon_heist" }, { label: "Investigate fallen crumbs like a detective", next: "crumb_case" }, ], }, zoomies: { id: "zoomies", title: "Living Room Grand Prix 🏁", text: "You tear around the house like a fuzzy comet. Humans cheer. Lap record shattered.", options: [ { label: "Victory lap outside on a walk", next: "walk" }, { label: "Hydrate and plan next mission", next: "water_plan" }, ], }, couch_melt: { id: "couch_melt", title: "Couch Potato Supreme 🛋️", text: "You become one with the furniture. A masterclass in relaxation.", options: [ { label: "Nap hard; dream of tennis balls", next: "dream_balls" }, { label: "Hear leash jingle—WALK?!", next: "walk" }, ], }, bacon_heist: { id: "bacon_heist", title: "The Bacon Caper 🥓", text: "Operation: Bacon. Success! (We won't tell the vet.)", options: [ { label: "Celebrate with a polite sit", next: "walk" }, { label: "Zoom in triumphant circles", next: "zoomies" }, ], }, crumb_case: { id: "crumb_case", title: "Crumb Detective 🔍", text: "A toast fragment! Case closed, detective {{nickname}}.", options: [ { label: "Request outside patrol", next: "walk" }, { label: "Retire to the couch", next: "couch_melt" }, ], }, walk: { id: "walk", title: "Neighborhood Patrol 🚶‍♀️", text: "Squirrels are notified. You strut like the majestic {{breed}} you are. A friendly kid asks to pet you—of course!", options: [ { label: "Splash through a mysterious puddle", next: "puddle" }, { label: "Heel like a champion for treats", next: "heel_hero" }, ], }, water_plan: { id: "water_plan", title: "Strategic Hydration 💧", text: "Slurp. Slurp. You ponder life's big questions: ball now or nap now?", options: [ { label: "Ball now!", next: "ball_quest" }, { label: "Nap now…", next: "dream_balls" }, ], }, puddle: { id: "puddle", title: "Puddle Physics 🌊", text: "You enter clean. You exit abstract art. Human: concerned. You: thrilled.", options: [], end: { badge: "The Splash Artist", summary: "You embraced chaos and moisture. Bath time is inevitable, pride remains untouchable.", }, }, heel_hero: { id: "heel_hero", title: "Heel of Fortune ⭐", text: "You heel perfectly past distractions like a pro. Treat jackpot unlocked!", options: [], end: { badge: "The Polite Pro", summary: "Manners on point. Neighborhood reputation: impeccable.", }, }, ball_quest: { id: "ball_quest", title: "The Eternal Ball Quest 🎾", text: "You locate, retrieve, and parade the sacred tennis ball. Humans clap. Legends speak of your fetch.", options: [], end: { badge: "Fetch Champion", summary: "Boundless energy, laser focus, tail like a metronome of joy.", }, }, dream_balls: { id: "dream_balls", title: "REM: Really Excellent Memories 💤", text: "Feet twitch. Quiet barks. You're racing dream-squirrels across cotton-candy fields.", options: [], end: { badge: "Cozy Cloud Rider", summary: "Rest mastered. Cuteness levels dangerously high.", }, }, } as const;type NodeKey = keyof typeof NODES;export default function DoodleInteractiveStory() { const [breed, setBreed] = useState(null); const [node, setNode] = useState("start"); const [trail, setTrail] = useState(["start"]);// restore session useEffect(() => { const cached = localStorage.getItem("doodle_story_state"); if (cached) { try { const s = JSON.parse(cached); if (s?.breed && s?.node && s?.trail) { setBreed(s.breed); setNode(s.node); setTrail(s.trail); } } catch {} } }, []);// persist session useEffect(() => { const payload = JSON.stringify({ breed, node, trail }); localStorage.setItem("doodle_story_state", payload); }, [breed, node, trail]);const progress = useMemo(() => { // naive progress: start=0, endings ~ 100; mid nodes ~ 33/66 const depth = Math.max(0, trail.length - 1); const capped = Math.min(100, Math.round((depth / 4) * 100)); return NODES[node].end ? 100 : Math.min(95, capped); }, [node, trail]);const isEnd = Boolean(NODES[node].end);function go(next: NodeKey) { setNode(next); setTrail((t) => [...t, next]); }function resetAll() { setBreed(null); setNode("start"); setTrail(["start"]); }async function share() { const end = NODES[node].end; const title = `I played Life Through a Doodle's Eyes – I am: ${end?.badge}!`; const text = `${end?.summary}`; const url = typeof window !== "undefined" ? window.location.href : ""; if (navigator.share) { try { await navigator.share({ title, text, url }); return; } catch {} } // Fallback: copy link try { await navigator.clipboard.writeText(url); alert("Link copied! Share it with your doodle friends 🐾"); } catch { alert("Couldn't share automatically. You can copy the URL from the address bar."); } }return (
Life Through a Doodle’s Eyes 🐾

An interactive, choose-your-own-adventure for doodle lovers

{trail.map((t, i) => ( {NODES[t].title.replace(/ .*/, "")} ))}
{!breed ? ( ) : (
{BREEDS.find((b) => b.name === breed)?.emoji} {breed}

{NODES[node].title}

{renderText(NODES[node].text, breed)}

{!isEnd ? (
{NODES[node].options.map((opt, i) => ( ))}
) : (
Your Ending

{NODES[node].end!.badge}

{NODES[node].end!.summary}

)}
)}
Tip: Use arrow keys to navigate choices (focus a button and press ←/→) or tap on mobile.
); }function BreedPicker({ onPick }: { onPick: (name: string) => void }) { return (

Pick your doodle narrator

{BREEDS.map((b) => ( onPick(b.name)} className="rounded-2xl border p-4 text-center hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-ring" whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }} >
{b.emoji}
{b.name}
Start story
))}
); }import { useEffect, useMemo, useState } from "react"; import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Progress } from "@/components/ui/progress"; import { motion, AnimatePresence } from "framer-motion";/** * Life Through a Doodle's Eyes – Interactive Story Page * - Choose-your-own-adventure with breed-specific flavor text * - Progress bar + breadcrumbs * - Replayable endings with share button * - Local storage save/restore * - Keyboard & accessible buttons * - Clean, modern UI using Tailwind + shadcn/ui */const BREEDS = [ { key: "golden", name: "Goldendoodle", emoji: "🥇" }, { key: "berne", name: "Bernedoodle", emoji: "🏔️" }, { key: "labra", name: "Labradoodle", emoji: "🛋️" }, { key: "aussie", name: "Aussiedoodle", emoji: "🎯" }, ];// Helper to inject breed-specific flavor into text templates function renderText(tpl: string, breedName: string) { return tpl .replaceAll("{{breed}}", breedName) .replaceAll("{{nickname}}", nick(breedName)); }function nick(breedName: string) { switch (breedName) { case "Goldendoodle": return "golden goofball"; case "Bernedoodle": return "mountain snuggler"; case "Labradoodle": return "chill cuddle pro"; case "Aussiedoodle": return "zoomie strategist"; default: return "doodle"; } }// Story graph // Each node: { id, title, text, options: [{label, next}], end?: { badge, summary } } const NODES = { start: { id: "start", title: "Good Morning, Doodle!", text: "You stretch, shake the floof, and greet the sun. Do you…", options: [ { label: "Jump on the bed and wake your human", next: "bed_jump" }, { label: "Wait like an angel by the food bowl", next: "food_wait" }, ], }, bed_jump: { id: "bed_jump", title: "Bed Launch Sequence 🚀", text: "Your human groans, then laughs—classic {{nickname}} move. Belly rubs achieved! What's next?", options: [ { label: "Activate indoor zoomies", next: "zoomies" }, { label: "Melt into the couch like warm butter", next: "couch_melt" }, ], }, food_wait: { id: "food_wait", title: "Breakfast Diplomacy 🍳", text: "Patience pays! Kibble served with a side of praise for the polite {{breed}}. Now…", options: [ { label: "Deploy big eyes for a bacon bonus", next: "bacon_heist" }, { label: "Investigate fallen crumbs like a detective", next: "crumb_case" }, ], }, zoomies: { id: "zoomies", title: "Living Room Grand Prix 🏁", text: "You tear around the house like a fuzzy comet. Humans cheer. Lap record shattered.", options: [ { label: "Victory lap outside on a walk", next: "walk" }, { label: "Hydrate and plan next mission", next: "water_plan" }, ], }, couch_melt: { id: "couch_melt", title: "Couch Potato Supreme 🛋️", text: "You become one with the furniture. A masterclass in relaxation.", options: [ { label: "Nap hard; dream of tennis balls", next: "dream_balls" }, { label: "Hear leash jingle—WALK?!", next: "walk" }, ], }, bacon_heist: { id: "bacon_heist", title: "The Bacon Caper 🥓", text: "Operation: Bacon. Success! (We won't tell the vet.)", options: [ { label: "Celebrate with a polite sit", next: "walk" }, { label: "Zoom in triumphant circles", next: "zoomies" }, ], }, crumb_case: { id: "crumb_case", title: "Crumb Detective 🔍", text: "A toast fragment! Case closed, detective {{nickname}}.", options: [ { label: "Request outside patrol", next: "walk" }, { label: "Retire to the couch", next: "couch_melt" }, ], }, walk: { id: "walk", title: "Neighborhood Patrol 🚶‍♀️", text: "Squirrels are notified. You strut like the majestic {{breed}} you are. A friendly kid asks to pet you—of course!", options: [ { label: "Splash through a mysterious puddle", next: "puddle" }, { label: "Heel like a champion for treats", next: "heel_hero" }, ], }, water_plan: { id: "water_plan", title: "Strategic Hydration 💧", text: "Slurp. Slurp. You ponder life's big questions: ball now or nap now?", options: [ { label: "Ball now!", next: "ball_quest" }, { label: "Nap now…", next: "dream_balls" }, ], }, puddle: { id: "puddle", title: "Puddle Physics 🌊", text: "You enter clean. You exit abstract art. Human: concerned. You: thrilled.", options: [], end: { badge: "The Splash Artist", summary: "You embraced chaos and moisture. Bath time is inevitable, pride remains untouchable.", }, }, heel_hero: { id: "heel_hero", title: "Heel of Fortune ⭐", text: "You heel perfectly past distractions like a pro. Treat jackpot unlocked!", options: [], end: { badge: "The Polite Pro", summary: "Manners on point. Neighborhood reputation: impeccable.", }, }, ball_quest: { id: "ball_quest", title: "The Eternal Ball Quest 🎾", text: "You locate, retrieve, and parade the sacred tennis ball. Humans clap. Legends speak of your fetch.", options: [], end: { badge: "Fetch Champion", summary: "Boundless energy, laser focus, tail like a metronome of joy.", }, }, dream_balls: { id: "dream_balls", title: "REM: Really Excellent Memories 💤", text: "Feet twitch. Quiet barks. You're racing dream-squirrels across cotton-candy fields.", options: [], end: { badge: "Cozy Cloud Rider", summary: "Rest mastered. Cuteness levels dangerously high.", }, }, } as const;type NodeKey = keyof typeof NODES;export default function DoodleInteractiveStory() { const [breed, setBreed] = useState(null); const [node, setNode] = useState("start"); const [trail, setTrail] = useState(["start"]);// restore session useEffect(() => { const cached = localStorage.getItem("doodle_story_state"); if (cached) { try { const s = JSON.parse(cached); if (s?.breed && s?.node && s?.trail) { setBreed(s.breed); setNode(s.node); setTrail(s.trail); } } catch {} } }, []);// persist session useEffect(() => { const payload = JSON.stringify({ breed, node, trail }); localStorage.setItem("doodle_story_state", payload); }, [breed, node, trail]);const progress = useMemo(() => { // naive progress: start=0, endings ~ 100; mid nodes ~ 33/66 const depth = Math.max(0, trail.length - 1); const capped = Math.min(100, Math.round((depth / 4) * 100)); return NODES[node].end ? 100 : Math.min(95, capped); }, [node, trail]);const isEnd = Boolean(NODES[node].end);function go(next: NodeKey) { setNode(next); setTrail((t) => [...t, next]); }function resetAll() { setBreed(null); setNode("start"); setTrail(["start"]); }async function share() { const end = NODES[node].end; const title = `I played Life Through a Doodle's Eyes – I am: ${end?.badge}!`; const text = `${end?.summary}`; const url = typeof window !== "undefined" ? window.location.href : ""; if (navigator.share) { try { await navigator.share({ title, text, url }); return; } catch {} } // Fallback: copy link try { await navigator.clipboard.writeText(url); alert("Link copied! Share it with your doodle friends 🐾"); } catch { alert("Couldn't share automatically. You can copy the URL from the address bar."); } }return (
Life Through a Doodle’s Eyes 🐾

An interactive, choose-your-own-adventure for doodle lovers

{trail.map((t, i) => ( {NODES[t].title.replace(/ .*/, "")} ))}
{!breed ? ( ) : (
{BREEDS.find((b) => b.name === breed)?.emoji} {breed}

{NODES[node].title}

{renderText(NODES[node].text, breed)}

{!isEnd ? (
{NODES[node].options.map((opt, i) => ( ))}
) : (
Your Ending

{NODES[node].end!.badge}

{NODES[node].end!.summary}

)}
)}
Tip: Use arrow keys to navigate choices (focus a button and press ←/→) or tap on mobile.
); }function BreedPicker({ onPick }: { onPick: (name: string) => void }) { return (

Pick your doodle narrator

{BREEDS.map((b) => ( onPick(b.name)} className="rounded-2xl border p-4 text-center hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-ring" whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }} >
{b.emoji}
{b.name}
Start story
))}
); }

What Are Some Popular Advanced Tricks For Doodles And How Can They Be Taught?

As highly intelligent dogs, Doodles are great candidates for advanced tricks that go beyond basic commands. Using positive reinforcement, such as high-value treats and praise, you can teach your Doodle impressive new tricks. Patience, short training sessions, and consistency are key to success. 

Here are some popular advanced tricks and how to teach them:

Play dead

This is a fun, multistep trick that requires your dog to lie on their side and stay still on command. 

  • What you’ll need: High-value treats, a quiet room, and a dog that knows “down” and “stay”.
  • Method:
    1. Ask your dog to lie down.
    2. Hold a treat near your dog’s nose and slowly lure their head backward toward their shoulder.
    3. As your dog follows the treat, they will naturally roll onto their side. Mark and reward the desired behavior.
    4. Once your dog consistently rolls onto their side with the treat lure, introduce a verbal cue like “bang” just before they start the motion.
    5. Fade the hand lure, rewarding only when they respond to the verbal cue alone. 

Weave through legs

This trick involves your dog moving in a figure-eight pattern between your legs as you walk. 

  • What you’ll need: High-value treats and a confident Doodle.
  • Method:
    1. With your dog in front of you, lure them through your legs with a treat in your hand.
    2. As your dog finishes going through, mark and reward them.
    3. Step forward with the opposite foot and lure them back through your legs from the other side.
    4. Once your dog is comfortable following the treat, start using an empty hand to guide them.
    5. Add a verbal cue like “weave” as your dog starts the motion. With practice, you can remove the hand cues entirely. 

Fetch a named object

This trick expands on basic fetch by teaching your dog to differentiate between objects by name and retrieve the correct one on command. 

  • What you’ll need: Several distinct toys (e.g., a ball, a rope, a frisbee) and high-value treats.
  • Method:
    1. Start with one unique toy. Name it (e.g., “Ball”) and practice fetching it with your dog.
    2. Once your dog reliably fetches the single toy, introduce a second object. If they get confused, remove the second object and practice with the first one again.
    3. Gradually add more named objects, always rewarding your dog when they choose the correct one.
    4. If your dog makes a mistake, simply try again. Avoid getting frustrated, as some intelligent dogs may get confused when new objects are introduced. 

Close the door

An impressive and useful trick for Doodles, this involves them pushing a door closed with their nose or paw on command. 

  • What you’ll need: A post-it note or small target, high-value treats, and a door that swings easily.
  • Method:
    1. Target training: Teach your Doodle to touch a sticky note with their nose. Hold the note in your hand, and when they touch it, mark and reward them.
    2. Move to the door: Place the sticky note on the door at your dog’s nose height. Encourage them to touch it. Reward any interaction, and give an extra large “jackpot” reward when they touch it hard enough to make the door move.
    3. Add a cue: When your dog consistently touches the target, add a verbal cue like “close.”
    4. Fade the target: When your dog reliably closes the door with the verbal command, you can gradually make the target smaller or remove it entirely. 

Tips for success with advanced tricks:

  • Break down the steps: Any complex trick is just a series of smaller, simpler behaviors strung together. Train each part separately before connecting them.
  • Keep it fun: If your Doodle gets bored or distracted, take a break. End every training session on a positive note so they look forward to the next one.
  • Vary the rewards: In addition to treats, use toys, praise, or affection to keep your dog motivated and engaged.
  • Practice in different environments: As your Doodle masters a trick, practice it with increasing distractions to make the behavior more reliable.