Files
edbridge-scholars/src/pages/student/StudentLayout.tsx

270 lines
7.7 KiB
TypeScript

import { Outlet, NavLink, useLocation } from "react-router-dom";
import { Home, BookOpen, Award, User, Video, Map } from "lucide-react";
import { SidebarProvider } from "../../components/ui/sidebar";
import { AppSidebar } from "../../components/AppSidebar";
const NAV_ITEMS = [
{
to: "/student/home",
icon: Home,
label: "Home",
color: "#f97316",
bg: "rgba(249,115,22,0.12)",
},
{
to: "/student/practice",
icon: BookOpen,
label: "Practice",
color: "#a855f7",
bg: "rgba(168,85,247,0.12)",
},
{
to: "/student/quests",
icon: Map,
label: "Quests",
color: "#587ffc",
bg: "rgba(53,75,150,0.12)",
},
{
to: "/student/lessons",
icon: Video,
label: "Lessons",
color: "#0891b2",
bg: "rgba(8,145,178,0.12)",
},
{
to: "/student/rewards",
icon: Award,
label: "Rewards",
color: "#16a34a",
bg: "rgba(22,163,74,0.12)",
},
{
to: "/student/profile",
icon: User,
label: "Profile",
color: "#e11d48",
bg: "rgba(225,29,72,0.12)",
},
];
// ── Quest dock overrides: dark navy pirate theme ──────────────────────────────
// Active color on quests page gets the gold treatment instead of the tab color.
const QUEST_NAV_ITEMS = NAV_ITEMS.map((item) =>
item.to === "/student/quests"
? { ...item, color: "#fbbf24", bg: "rgba(251,191,36,0.15)" }
: { ...item, color: "#94a3b8", bg: "rgba(255,255,255,0.08)" },
);
const STYLES = `
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@700;800;900&family=Cinzel:wght@700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Sorts+Mill+Goudy:ital@0;1&display=swap');
/* ══ DEFAULT dock (cream frosted glass) ══ */
.sl-dock-wrap {
position: fixed;
bottom: calc(1.25rem + env(safe-area-inset-bottom));
left: 50%;
transform: translateX(-50%);
z-index: 20;
background: rgba(255,251,244,0.72);
backdrop-filter: blur(24px) saturate(180%);
-webkit-backdrop-filter: blur(24px) saturate(180%);
border: 1.5px solid rgba(255,255,255,0.7);
border-radius: 100px;
box-shadow:
0 8px 32px rgba(0,0,0,0.12),
0 2px 8px rgba(0,0,0,0.06),
inset 0 1px 0 rgba(255,255,255,0.8);
padding: 0.45rem 0.5rem;
display: flex;
align-items: center;
gap: 0.15rem;
transition:
background 0.4s ease,
border-color 0.4s ease,
box-shadow 0.4s ease;
}
/* ══ QUEST dock (dark navy pirate) ══ */
.sl-dock-wrap.quest-mode {
background: linear-gradient(
90deg,
transparent 0%,
rgba(251,191,36,0.05) 30%,
rgba(251,191,36,0.1) 50%,
rgba(251,191,36,0.15) 70%,
transparent 100%
);
background-size: 200% 100%;
animation: slGoldSweep 3s linear infinite;
backdrop-filter: blur(28px) saturate(160%);
-webkit-backdrop-filter: blur(28px) saturate(160%);
border: 1.5px solid rgba(251,191,36,0.28);
box-shadow:
0 8px 32px rgba(0,0,0,0.55),
0 2px 8px rgba(0,0,0,0.35),
0 0 0 1px rgba(251,191,36,0.08),
inset 0 1px 0 rgba(255,255,255,0.06);
}
@keyframes slGoldSweep {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* ── Each nav item ── */
.sl-dock-item {
display: flex;
align-items: center;
gap: 0;
border-radius: 100px;
padding: 0.5rem 0.6rem;
text-decoration: none;
border: none;
background: transparent;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
transition:
padding 0.35s cubic-bezier(0.34,1.56,0.64,1),
gap 0.35s cubic-bezier(0.34,1.56,0.64,1),
background 0.25s ease;
white-space: nowrap;
overflow: hidden;
position: relative;
}
.sl-dock-item:active { transform: scale(0.91); }
.sl-dock-item.active {
padding: 0.5rem 1rem 0.5rem 0.75rem;
gap: 0.45rem;
}
/* ── Icon circle ── */
.sl-dock-icon {
width: 32px; height: 32px; flex-shrink: 0;
border-radius: 50%;
display: flex; align-items: center; justify-content: center;
background: transparent;
transition: background 0.25s ease, transform 0.35s cubic-bezier(0.34,1.56,0.64,1);
}
.sl-dock-item.active .sl-dock-icon { transform: scale(1.1); }
/* In quest mode, active quest icon gets a gold glow */
.quest-mode .sl-dock-item.active .sl-dock-icon {
box-shadow: 0 0 12px rgba(251,191,36,0.35);
}
/* ── Label ── */
.sl-dock-label {
font-family: 'Nunito', sans-serif;
font-size: 0.8rem;
font-weight: 900;
letter-spacing: 0.01em;
max-width: 0;
opacity: 0;
overflow: hidden;
transition:
max-width 0.35s cubic-bezier(0.34,1.56,0.64,1),
opacity 0.25s ease 0.05s;
pointer-events: none;
}
.sl-dock-item.active .sl-dock-label {
max-width: 80px;
opacity: 1;
}
/* Ensure the dock is hidden on desktop (md and up) */
@media (min-width: 768px) {
.sl-dock-wrap { display: none !important; }
}
/* Quest mode: active label uses Cinzel for the pirate feel */
.quest-mode .sl-dock-item.active .sl-dock-label {
font-family: 'Sorts Mill Goudy', serif;
font-size: 0.85rem;
letter-spacing: 0.05em;
text-shadow: 0 0 12px rgba(251,191,36,0.5);
}
/* ── Quest mode: inactive icons are dimmer ── */
.quest-mode .sl-dock-item:not(.active) .sl-dock-icon {
opacity: 0.55;
transition: opacity 0.2s ease, background 0.25s ease, transform 0.35s cubic-bezier(0.34,1.56,0.64,1);
}
.quest-mode .sl-dock-item:not(.active):hover .sl-dock-icon {
opacity: 0.85;
}
/* Ensure the dock is hidden on desktop (md and up) */
@media (min-width: 768px) {
.sl-dock-wrap { display: none !important; }
}
`;
export function StudentLayout() {
const location = useLocation();
const isQuestPage = location.pathname.startsWith("/student/quests");
// Pick the right nav item config based on page
const items = isQuestPage ? QUEST_NAV_ITEMS : NAV_ITEMS;
return (
<SidebarProvider>
<style>{STYLES}</style>
<div className="flex min-h-screen w-full overflow-x-hidden">
{/* Desktop Sidebar */}
<div className="hidden md:block">
<AppSidebar />
</div>
<div className="flex flex-col flex-1 min-w-0">
{/* Extra bottom padding so content clears the floating dock */}
<main className="flex-1 pb-24 md:pb-0">
<Outlet />
</main>
</div>
{/* ── Floating dock (mobile only) ── */}
<nav
className={`sl-dock-wrap md:hidden${isQuestPage ? " quest-mode" : ""}`}
>
{items.map((item) => (
<NavLink
key={item.to}
to={item.to}
className={({ isActive }) =>
`sl-dock-item${isActive ? " active" : ""}`
}
>
{({ isActive }) => (
<>
<div
className="sl-dock-icon"
style={{ background: isActive ? item.bg : "transparent" }}
>
<item.icon
size={18}
strokeWidth={isActive ? 2.5 : 1.75}
color={
isActive
? item.color
: isQuestPage
? "rgba(255,255,255,0.4)"
: "#94a3b8"
}
/>
</div>
<span className="sl-dock-label" style={{ color: item.color }}>
{item.label}
</span>
</>
)}
</NavLink>
))}
</nav>
</div>
</SidebarProvider>
);
}