feat(treasure): add treasure quest, quest modal, island node, quest widget
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
import { Outlet, NavLink } from "react-router-dom";
|
||||
import { Home, BookOpen, Award, User, Video } from "lucide-react";
|
||||
import { Outlet, NavLink, useLocation } from "react-router-dom";
|
||||
import { Home, BookOpen, Award, User, Video, Map } from "lucide-react";
|
||||
import { SidebarProvider, SidebarTrigger } from "../../components/ui/sidebar";
|
||||
import { AppSidebar } from "../../components/AppSidebar";
|
||||
|
||||
@ -18,6 +18,13 @@ const NAV_ITEMS = [
|
||||
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,
|
||||
@ -41,19 +48,26 @@ const NAV_ITEMS = [
|
||||
},
|
||||
];
|
||||
|
||||
const STYLES = `
|
||||
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@700;800;900&display=swap');
|
||||
// ── 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)" },
|
||||
);
|
||||
|
||||
/* ── The floating island dock ── */
|
||||
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;
|
||||
|
||||
/* Frosted pill */
|
||||
background: rgba(255, 251, 244, 0.72);
|
||||
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);
|
||||
@ -62,11 +76,42 @@ const STYLES = `
|
||||
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 ── */
|
||||
@ -83,10 +128,11 @@ const STYLES = `
|
||||
-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),
|
||||
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 {
|
||||
@ -102,11 +148,14 @@ const STYLES = `
|
||||
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);
|
||||
.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 (only visible when active) ── */
|
||||
/* ── Label ── */
|
||||
.sl-dock-label {
|
||||
font-family: 'Nunito', sans-serif;
|
||||
font-size: 0.8rem;
|
||||
@ -124,9 +173,32 @@ const STYLES = `
|
||||
max-width: 80px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
`;
|
||||
|
||||
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>
|
||||
@ -136,15 +208,16 @@ export function StudentLayout() {
|
||||
|
||||
<div className="flex flex-col flex-1 min-w-0">
|
||||
<SidebarTrigger className="hidden md:block" />
|
||||
{/* Extra bottom padding so content clears the floating dock */}
|
||||
<main className="flex-1 md:pb-0">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
|
||||
{/* ── Floating island dock (mobile only) ── */}
|
||||
<nav className="sl-dock-wrap md:hidden">
|
||||
{NAV_ITEMS.map((item) => (
|
||||
{/* ── Floating dock (mobile only) ── */}
|
||||
<nav
|
||||
className={`sl-dock-wrap md:hidden${isQuestPage ? " quest-mode" : ""}`}
|
||||
>
|
||||
{items.map((item) => (
|
||||
<NavLink
|
||||
key={item.to}
|
||||
to={item.to}
|
||||
@ -161,7 +234,13 @@ export function StudentLayout() {
|
||||
<item.icon
|
||||
size={18}
|
||||
strokeWidth={isActive ? 2.5 : 1.75}
|
||||
color={isActive ? item.color : "#94a3b8"}
|
||||
color={
|
||||
isActive
|
||||
? item.color
|
||||
: isQuestPage
|
||||
? "rgba(255,255,255,0.4)"
|
||||
: "#94a3b8"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<span className="sl-dock-label" style={{ color: item.color }}>
|
||||
|
||||
Reference in New Issue
Block a user