feat: add a white theme
This commit is contained in:
41
components/AppLayout.tsx
Normal file
41
components/AppLayout.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useSession } from "next-auth/react";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
import { useTheme } from "@/lib/theme";
|
||||
|
||||
export default function AppLayout({ children }: { children: React.ReactNode }) {
|
||||
const { status } = useSession();
|
||||
const router = useRouter();
|
||||
const { theme } = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
if (status === "unauthenticated") {
|
||||
router.push("/");
|
||||
}
|
||||
}, [status, router]);
|
||||
|
||||
if (status === "loading") {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center" style={{ background: "var(--bg)" }}>
|
||||
<span className="font-mono text-xs tracking-widest animate-pulse" style={{ color: "var(--accent)" }}>
|
||||
CHARGEMENT...
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (status !== "authenticated") return null;
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen" style={{ background: "var(--bg)" }}>
|
||||
<Sidebar />
|
||||
<main className="ml-56 flex-1 p-8" style={{ background: "var(--bg)" }}>
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useTheme } from "@/lib/theme";
|
||||
|
||||
export default function DragonEye({ size = 60 }: { size?: number }) {
|
||||
const irisRef = useRef<HTMLDivElement>(null);
|
||||
const { theme } = useTheme();
|
||||
const isLight = theme === "light";
|
||||
|
||||
useEffect(() => {
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
@@ -26,6 +29,22 @@ export default function DragonEye({ size = 60 }: { size?: number }) {
|
||||
const h = size;
|
||||
const w = size * 1.6;
|
||||
|
||||
// Light theme: sky-blue palette
|
||||
const glowColor = isLight ? "#38bdf8" : "#4aff8c";
|
||||
const eyeBg = isLight
|
||||
? "radial-gradient(ellipse at 40% 35%, #e0f7ff, #b8eeff)"
|
||||
: "radial-gradient(ellipse at 40% 35%, #0a1a0a, #020402)";
|
||||
const eyeBorder = isLight ? "rgba(56,189,248,0.4)" : "rgba(74,255,140,0.25)";
|
||||
const eyeShadow = isLight
|
||||
? "0 0 20px rgba(56,189,248,0.25), inset 0 0 10px rgba(56,189,248,0.1)"
|
||||
: "0 0 20px rgba(74,255,140,0.1), inset 0 0 20px rgba(0,0,0,0.8)";
|
||||
const irisBg = isLight
|
||||
? "radial-gradient(ellipse at 40% 35%, #7dd3fc 0%, #0ea5e9 35%, #0369a1 70%)"
|
||||
: "radial-gradient(ellipse at 40% 35%, #1aff6a 0%, #0a8a3a 35%, #022a12 70%)";
|
||||
const irisShadow = isLight ? "0 0 12px rgba(56,189,248,0.6)" : "0 0 12px rgba(74,255,140,0.4)";
|
||||
const pupilBg = isLight ? "#001a2e" : "#010801";
|
||||
const eyelidBg = isLight ? "#dbeafe" : "#0a0d0f";
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative flex-shrink-0"
|
||||
@@ -35,7 +54,7 @@ export default function DragonEye({ size = 60 }: { size?: number }) {
|
||||
<div
|
||||
className="absolute inset-0 rounded-full blur-md opacity-30"
|
||||
style={{
|
||||
background: "radial-gradient(ellipse, #4aff8c 0%, transparent 70%)",
|
||||
background: `radial-gradient(ellipse, ${glowColor} 0%, transparent 70%)`,
|
||||
borderRadius: "50%",
|
||||
}}
|
||||
/>
|
||||
@@ -45,9 +64,9 @@ export default function DragonEye({ size = 60 }: { size?: number }) {
|
||||
className="absolute inset-0 overflow-hidden"
|
||||
style={{
|
||||
borderRadius: "50%",
|
||||
background: "radial-gradient(ellipse at 40% 35%, #0a1a0a, #020402)",
|
||||
border: "1px solid rgba(74,255,140,0.25)",
|
||||
boxShadow: "0 0 20px rgba(74,255,140,0.1), inset 0 0 20px rgba(0,0,0,0.8)",
|
||||
background: eyeBg,
|
||||
border: `1px solid ${eyeBorder}`,
|
||||
boxShadow: eyeShadow,
|
||||
animation: "blink 9s ease-in-out infinite",
|
||||
}}
|
||||
>
|
||||
@@ -60,8 +79,8 @@ export default function DragonEye({ size = 60 }: { size?: number }) {
|
||||
width: h * 0.65,
|
||||
height: h * 0.65,
|
||||
borderRadius: "50%",
|
||||
background: "radial-gradient(ellipse at 40% 35%, #1aff6a 0%, #0a8a3a 35%, #022a12 70%)",
|
||||
boxShadow: "0 0 12px rgba(74,255,140,0.4)",
|
||||
background: irisBg,
|
||||
boxShadow: irisShadow,
|
||||
transition: "transform 0.08s ease-out",
|
||||
}}
|
||||
>
|
||||
@@ -72,19 +91,20 @@ export default function DragonEye({ size = 60 }: { size?: number }) {
|
||||
transform: "translate(-50%, -50%)",
|
||||
width: h * 0.1,
|
||||
height: h * 0.55,
|
||||
background: "#010801",
|
||||
background: pupilBg,
|
||||
borderRadius: "50%",
|
||||
boxShadow: "0 0 6px rgba(0,0,0,0.9)",
|
||||
animation: "pupilDilate 7s ease-in-out infinite",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Highlight */}
|
||||
<div
|
||||
className="absolute"
|
||||
style={{
|
||||
top: "18%", left: "22%",
|
||||
width: h * 0.12, height: h * 0.08,
|
||||
background: "rgba(200,255,220,0.4)",
|
||||
background: isLight ? "rgba(255,255,255,0.7)" : "rgba(200,255,220,0.4)",
|
||||
borderRadius: "50%",
|
||||
transform: "rotate(-20deg)",
|
||||
filter: "blur(1px)",
|
||||
@@ -97,7 +117,7 @@ export default function DragonEye({ size = 60 }: { size?: number }) {
|
||||
className="absolute top-0 left-0 right-0"
|
||||
style={{
|
||||
height: "30%",
|
||||
background: "linear-gradient(to bottom, #0a0d0f, rgba(10,13,15,0.5))",
|
||||
background: `linear-gradient(to bottom, ${eyelidBg}, transparent)`,
|
||||
borderRadius: "50% 50% 0 0 / 80% 80% 0 0",
|
||||
zIndex: 2,
|
||||
}}
|
||||
@@ -107,7 +127,7 @@ export default function DragonEye({ size = 60 }: { size?: number }) {
|
||||
className="absolute bottom-0 left-0 right-0"
|
||||
style={{
|
||||
height: "22%",
|
||||
background: "linear-gradient(to top, #0a0d0f, rgba(10,13,15,0.5))",
|
||||
background: `linear-gradient(to top, ${eyelidBg}, transparent)`,
|
||||
borderRadius: "0 0 50% 50% / 0 0 80% 80%",
|
||||
zIndex: 2,
|
||||
}}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
import { signOut } from "next-auth/react";
|
||||
import DragonEye from "@/components/DragonEye";
|
||||
import ThemeToggleButton from "@/components/ThemeToggleButton";
|
||||
import { useTheme } from "@/lib/theme";
|
||||
import {
|
||||
LayoutDashboard,
|
||||
Twitch,
|
||||
@@ -25,20 +27,28 @@ const nav = [
|
||||
export default function Sidebar() {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const { theme } = useTheme();
|
||||
const isLight = theme === "light";
|
||||
|
||||
const handleLogout = () => {
|
||||
signOut({ callbackUrl: "/" });
|
||||
};
|
||||
|
||||
const bg = isLight ? "bg-wy-light-surface" : "bg-wy-dark";
|
||||
const border = isLight ? "border-wy-light-border" : "border-wy-border";
|
||||
const textSec = isLight ? "text-wy-light-text-secondary" : "text-wy-text-secondary";
|
||||
const textPri = isLight ? "text-wy-light-text-primary" : "text-wy-text-primary";
|
||||
const title = isLight ? "text-wy-light-text-primary" : "text-white";
|
||||
|
||||
return (
|
||||
<aside className="fixed left-0 top-0 h-screen w-56 bg-wy-dark border-r border-wy-border flex flex-col z-50">
|
||||
<aside className={`fixed left-0 top-0 h-screen w-56 ${bg} border-r ${border} flex flex-col z-50`}>
|
||||
|
||||
{/* Logo */}
|
||||
<div className="flex items-center gap-3 px-5 py-6 border-b border-wy-border">
|
||||
<div className={`flex items-center gap-3 px-5 py-6 border-b ${border}`}>
|
||||
<DragonEye size={32} />
|
||||
<div>
|
||||
<div className="text-[8px] font-mono tracking-[0.3em] text-acid-green/60 uppercase">Analytics</div>
|
||||
<div className="text-base font-black tracking-widest text-white">WYVIEW</div>
|
||||
<div className={`text-[8px] font-mono tracking-[0.3em] ${isLight ? "text-sky-400/80" : "text-acid-green/60"} uppercase`}>Analytics</div>
|
||||
<div className={`text-base font-black tracking-widest ${title}`}>WYVIEW</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -51,22 +61,30 @@ export default function Sidebar() {
|
||||
onClick={() => router.push(href)}
|
||||
className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-md text-left transition-all duration-150 group ${
|
||||
active
|
||||
? "bg-acid-green/10 border border-acid-green/30 text-acid-green"
|
||||
: "border border-transparent text-wy-text-secondary hover:text-wy-text-primary hover:bg-white/[0.04]"
|
||||
? isLight
|
||||
? "bg-sky-100 border border-sky-300 text-sky-600"
|
||||
: "bg-acid-green/10 border border-acid-green/30 text-acid-green"
|
||||
: `border border-transparent ${textSec} hover:${textPri} ${isLight ? "hover:bg-sky-50" : "hover:bg-white/[0.04]"}`
|
||||
}`}
|
||||
>
|
||||
<Icon size={16} className="flex-shrink-0" />
|
||||
<span className="text-sm font-medium">{label}</span>
|
||||
{active && <span className="ml-auto w-1.5 h-1.5 rounded-full bg-acid-green" />}
|
||||
{active && (
|
||||
<span className={`ml-auto w-1.5 h-1.5 rounded-full ${isLight ? "bg-sky-500" : "bg-acid-green"}`} />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
<div className="px-3 py-4 border-t border-wy-border">
|
||||
<div className={`px-3 py-4 border-t ${border} flex flex-col gap-2`}>
|
||||
{/* Theme toggle */}
|
||||
<div className="flex justify-end pr-1">
|
||||
<ThemeToggleButton />
|
||||
</div>
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-md text-wy-text-secondary hover:text-red-400 hover:bg-red-500/10 border border-transparent hover:border-red-500/25 transition-all duration-150"
|
||||
className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-md ${textSec} hover:text-red-400 hover:bg-red-500/10 border border-transparent hover:border-red-500/25 transition-all duration-150`}
|
||||
>
|
||||
<LogOut size={16} />
|
||||
<span className="text-sm font-medium">Déconnexion</span>
|
||||
|
||||
@@ -18,15 +18,20 @@ const accentColors = {
|
||||
export default function StatCard({ label, value, sub, accent = "green", delta, deltaUp }: StatCardProps) {
|
||||
const colors = accentColors[accent];
|
||||
return (
|
||||
<div className={`bg-wy-surface border border-wy-border border-t-2 ${colors.border} rounded-lg p-5 relative group hover:-translate-y-0.5 transition-transform duration-200`}>
|
||||
<div className="text-xs font-mono tracking-widest text-wy-text-secondary uppercase mb-3">
|
||||
<div
|
||||
className={`border border-t-2 ${colors.border} rounded-lg p-5 relative group hover:-translate-y-0.5 transition-transform duration-200`}
|
||||
style={{ background: "var(--surface)", borderColor: "var(--border)" }}
|
||||
>
|
||||
<div className="text-xs font-mono tracking-widest uppercase mb-3" style={{ color: "var(--text-secondary)" }}>
|
||||
{label}
|
||||
</div>
|
||||
<div className={`text-4xl font-black tracking-tight ${colors.val} leading-none mb-1`}>
|
||||
{value === "—" || value === 0 ? <span className="text-wy-text-secondary">—</span> : value}
|
||||
{value === "—" || value === 0
|
||||
? <span style={{ color: "var(--text-secondary)" }}>—</span>
|
||||
: value}
|
||||
</div>
|
||||
{sub && (
|
||||
<div className="text-xs text-wy-text-secondary font-mono mt-1">{sub}</div>
|
||||
<div className="text-xs font-mono mt-1" style={{ color: "var(--text-secondary)" }}>{sub}</div>
|
||||
)}
|
||||
{delta && (
|
||||
<div className={`absolute top-4 right-4 text-xs font-mono tracking-wider px-2 py-1 rounded-md border ${
|
||||
|
||||
19
components/ThemeToggleButton.tsx
Normal file
19
components/ThemeToggleButton.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import { Moon, Sun } from "lucide-react";
|
||||
import { useTheme } from "@/lib/theme";
|
||||
|
||||
export default function ThemeToggleButton() {
|
||||
const { theme, toggle } = useTheme();
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={toggle}
|
||||
aria-label="Changer de thème"
|
||||
className="flex items-center justify-center w-8 h-8 rounded-md border border-wy-border text-wy-text-secondary hover:text-wy-text-primary hover:bg-white/[0.06] transition-all duration-150"
|
||||
>
|
||||
{theme === "dark" ? <Sun size={15} /> : <Moon size={15} />}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user