diff --git a/brain-trails/components/dashboard/CharacterCard.tsx b/brain-trails/components/dashboard/CharacterCard.tsx new file mode 100644 index 0000000..3eb7559 --- /dev/null +++ b/brain-trails/components/dashboard/CharacterCard.tsx @@ -0,0 +1,129 @@ +"use client"; + +import { motion } from "framer-motion"; +import { useRouter } from "next/navigation"; +import { Flame, Star, Scroll, Timer, GraduationCap, Layers, PenTool, Coins } from "lucide-react"; +import { useAuth } from "@/context/AuthContext"; +import { useTheme } from "@/context/ThemeContext"; +import { useQuests } from "@/hooks/useQuests"; +import OwlCompanion from "@/components/ui/OwlCompanion"; + +/** + * Habitica-style "character sheet" — the dashboard hero. Avatar + three vitality + * bars (streak / XP / today's quests) + the gold purse + the do-a-thing actions. + */ +function levelTitle(level: number): string { + if (level >= 40) return "Archmage of Study"; + if (level >= 25) return "Grand Scholar"; + if (level >= 15) return "Adept"; + if (level >= 7) return "Scholar"; + if (level >= 3) return "Apprentice"; + return "Novice"; +} + +export default function CharacterCard() { + const { profile } = useAuth(); + const { theme } = useTheme(); + const router = useRouter(); + const { quests } = useQuests(); + const isSun = theme === "sun"; + + const name = profile?.display_name || profile?.username || "Traveler"; + const level = profile?.level ?? 1; + const xp = profile?.xp ?? 0; + const streak = profile?.streak_days ?? 0; + const gold = profile?.gold ?? 0; + + const xpIntoLevel = Math.max(0, xp - (level - 1) * 1000); + const xpPct = Math.min(xpIntoLevel / 1000, 1) * 100; + + // Streak "vitality" — fills toward the next milestone (7 / 30 / 100). + const tiers = [7, 30, 100]; + const nextTier = tiers.find((t) => streak < t) ?? 100; + const prevTier = [0, ...tiers].filter((t) => t <= streak).pop() ?? 0; + const streakPct = nextTier > prevTier ? ((streak - prevTier) / (nextTier - prevTier)) * 100 : 100; + + const doneToday = quests.filter((q) => q.period === "daily" && q.is_completed).length; + const totalToday = quests.filter((q) => q.period === "daily").length; + const questPct = totalToday > 0 ? (doneToday / totalToday) * 100 : 0; + + const card = isSun ? "bg-white border-slate-200" : "bg-slate-900/80 border-white/10"; + const ink = isSun ? "text-slate-900" : "text-white"; + const sub = isSun ? "text-slate-500" : "text-slate-400"; + const track = isSun ? "bg-slate-100" : "bg-white/10"; + + const Bar = ({ icon, label, value, pct, from, to }: { + icon: React.ReactNode; label: string; value: string; pct: number; from: string; to: string; + }) => ( +
+
+ {icon}{label} + {value} +
+
+ +
+
+ ); + + const action = (label: string, icon: React.ReactNode, href: string, primary?: boolean) => ( + + ); + + return ( + + {/* Identity row */} +
+
+ 0 ? "celebrating" : "idle"} showName={false} className="w-[78px] h-[78px]" /> + + Lv {level} + +
+
+

{name}

+

{levelTitle(level)}

+
+
+ + {gold.toLocaleString()} +
+
+ + {/* Vitality bars */} +
+ } label="Streak" value={`${streak}d`} pct={streakPct} from="from-rose-500" to="to-orange-500" /> + } label="Level" value={`${xpIntoLevel}/1000`} pct={xpPct} from="from-amber-400" to="to-yellow-500" /> + } label="Dailies" value={`${doneToday}/${totalToday}`} pct={questPct} from="from-violet-500" to="to-fuchsia-500" /> +
+ + {/* Do-a-thing actions */} +
+ {action("Focus", , "/focus", true)} + {action("Trial", , "/quiz")} + {action("Review", , "/flashcards")} + {action("Write", , "/notes")} +
+
+ ); +} diff --git a/brain-trails/components/dashboard/Dashboard.tsx b/brain-trails/components/dashboard/Dashboard.tsx index ca2898f..a2928a3 100644 --- a/brain-trails/components/dashboard/Dashboard.tsx +++ b/brain-trails/components/dashboard/Dashboard.tsx @@ -2,7 +2,7 @@ import { motion } from "framer-motion"; import QuestLog from "./QuestLog"; -import StudyRoom from "./StudyRoom"; +import CharacterCard from "./CharacterCard"; import TopStatsBar from "./TopStatsBar"; import LeaderboardPodium from "./LeaderboardPodium"; import ActivityFeed from "./ActivityFeed"; @@ -13,90 +13,61 @@ import AmbientPlayer from "../ui/AmbientPlayer"; import { useTheme } from "@/context/ThemeContext"; /** - * Dashboard - Glassmorphism Layout - * Centerpiece owl, left study profile, right community hub, bottom adventures + * Dashboard — an "adventurer's board" (Habitica-flavoured): character sheet on + * top, today's dailies front-and-centre, party + subjects to the side. */ export default function Dashboard() { const { theme } = useTheme(); const isSun = theme === "sun"; + const panel = isSun + ? "bg-white/70 border-white/60 backdrop-blur-xl" + : "bg-white/[0.04] border-white/[0.08] backdrop-blur-xl"; + return (
-
- {/* Minimal Top Nav */} -
+
+
- {/* MAIN CONTENT */} -
- - {/* Top Row: Left Panel | Centerpiece Owl | Right Panel */} -
- - {/* Left Side - My Study Profile */} - - - + {/* Character sheet hero */} + - {/* Centerpiece - Owl Mascot & Floating Stats */} + {/* Board: dailies (main) + party/subjects (side) */} +
+
- + - {/* Right Side - Community Hub */} - + + +
+ +
+ + + +
- - {/* Bottom Center - Adventures & Activity */} - -
-
- -
-
- -
-
-
-
+