diff --git a/app/globals.css b/app/globals.css index a03c732..4633996 100644 --- a/app/globals.css +++ b/app/globals.css @@ -2,9 +2,107 @@ @tailwind components; @tailwind utilities; +:root { + /* Font variables - will be overridden by Next.js Font */ + --font-orbitron: "Orbitron", sans-serif; + --font-rajdhani: "Rajdhani", sans-serif; + + /* Dark cyan theme (default) */ + --background: #001d2e; + --foreground: #ffffff; + --card: rgba(0, 29, 46, 0.6); + --card-hover: rgba(0, 29, 46, 0.8); + --card-column: rgba(0, 29, 46, 0.8); + --border: rgba(29, 254, 228, 0.3); + --input: rgba(0, 29, 46, 0.6); + --primary: #1dfee4; + --primary-foreground: #001d2e; + --muted: #9ca3af; + --muted-foreground: #9ca3af; + --gray-300: #d1d5db; + --gray-400: #9ca3af; + --gray-500: #6b7280; + --gray-600: #4b5563; + --gray-700: #374151; + --gray-800: #1f2937; + --gray-900: #111827; + --accent: #1dfee4; + --destructive: #ef4444; + --success: #10b981; + --purple: #8b5cf6; + --yellow: #eab308; + --green: #10b981; + --blue: #3b82f6; + --pixel-gold: #1dfee4; + --accent-color: #1dfee4; +} + +.dark { + /* Dark gold theme (original) */ + --background: #000000; + --foreground: #ffffff; + --card: rgba(0, 0, 0, 0.6); + --card-hover: rgba(0, 0, 0, 0.8); + --card-column: rgba(0, 0, 0, 0.8); + --border: rgba(218, 165, 32, 0.3); + --input: rgba(0, 0, 0, 0.6); + --primary: #06b6d4; + --primary-foreground: #ffffff; + --muted: #9ca3af; + --muted-foreground: #9ca3af; + --gray-300: #d1d5db; + --gray-400: #9ca3af; + --gray-500: #6b7280; + --gray-600: #4b5563; + --gray-700: #374151; + --gray-800: #1f2937; + --gray-900: #111827; + --accent: #ff8c00; + --destructive: #ef4444; + --success: #10b981; + --purple: #8b5cf6; + --yellow: #eab308; + --green: #10b981; + --blue: #3b82f6; + --pixel-gold: #daa520; + --accent-color: #daa520; +} + +.dark-cyan { + /* Dark cyan theme (new) */ + --background: #001d2e; + --foreground: #ffffff; + --card: rgba(0, 29, 46, 0.6); + --card-hover: rgba(0, 29, 46, 0.8); + --card-column: rgba(0, 29, 46, 0.8); + --border: rgba(29, 254, 228, 0.3); + --input: rgba(0, 29, 46, 0.6); + --primary: #1dfee4; + --primary-foreground: #001d2e; + --muted: #9ca3af; + --muted-foreground: #9ca3af; + --gray-300: #d1d5db; + --gray-400: #9ca3af; + --gray-500: #6b7280; + --gray-600: #4b5563; + --gray-700: #374151; + --gray-800: #1f2937; + --gray-900: #111827; + --accent: #1dfee4; + --destructive: #ef4444; + --success: #10b981; + --purple: #8b5cf6; + --yellow: #eab308; + --green: #10b981; + --blue: #3b82f6; + --pixel-gold: #1dfee4; + --accent-color: #1dfee4; +} + @layer base { body { - @apply bg-black text-white; + background-color: var(--background); + color: var(--foreground); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", @@ -44,4 +142,52 @@ .animate-shimmer { animation: shimmer 2s infinite; } + + /* Button hover states using CSS variables */ + .btn-primary { + border-color: color-mix(in srgb, var(--accent-color) 50%, transparent); + background-color: color-mix(in srgb, var(--background) 60%, transparent); + color: var(--foreground); + } + .btn-primary:hover:not(:disabled) { + border-color: var(--accent-color); + background-color: color-mix(in srgb, var(--accent-color) 10%, transparent); + } + + .btn-secondary { + border-color: rgba(107, 114, 128, 0.5); + background-color: rgba(31, 41, 55, 0.2); + color: var(--gray-400); + } + .btn-secondary:hover:not(:disabled) { + border-color: var(--gray-500); + background-color: rgba(31, 41, 55, 0.3); + } + + .btn-success { + border-color: rgba(16, 185, 129, 0.5); + background-color: rgba(16, 185, 129, 0.2); + color: var(--success); + } + .btn-success:hover:not(:disabled) { + background-color: rgba(16, 185, 129, 0.3); + } + + .btn-danger { + border-color: rgba(239, 68, 68, 0.5); + background-color: rgba(239, 68, 68, 0.2); + color: var(--destructive); + } + .btn-danger:hover:not(:disabled) { + background-color: rgba(239, 68, 68, 0.3); + } + + .btn-ghost { + border-color: transparent; + background-color: transparent; + color: var(--foreground); + } + .btn-ghost:hover:not(:disabled) { + color: var(--accent-color); + } } diff --git a/app/layout.tsx b/app/layout.tsx index d88dd2a..689447c 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -3,6 +3,7 @@ import type { ReactNode } from "react"; import { Orbitron, Rajdhani } from "next/font/google"; import "./globals.css"; import SessionProvider from "@/components/layout/SessionProvider"; +import { ThemeProvider } from "@/contexts/ThemeContext"; const orbitron = Orbitron({ subsets: ["latin"], @@ -27,9 +28,14 @@ export default function RootLayout({ children: ReactNode; }>) { return ( - + - {children} + + {children} + ); diff --git a/components/Avatar.tsx b/components/Avatar.tsx new file mode 100644 index 0000000..520f0a1 --- /dev/null +++ b/components/Avatar.tsx @@ -0,0 +1,75 @@ +"use client"; + +import { useState, useEffect, useRef } from "react"; +import { normalizeAvatarUrl } from "@/lib/avatars"; + +interface AvatarProps { + src: string | null | undefined; + username: string; + size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl"; + className?: string; + borderClassName?: string; + fallbackText?: string; +} + +const sizeClasses = { + xs: "w-6 h-6 text-[8px]", + sm: "w-8 h-8 text-[10px]", + md: "w-10 h-10 text-xs", + lg: "w-16 h-16 sm:w-20 sm:h-20 text-xl sm:text-2xl", + xl: "w-24 h-24 text-4xl", + "2xl": "w-32 h-32 text-4xl", +}; + +export default function Avatar({ + src, + username, + size = "md", + className = "", + borderClassName = "", + fallbackText, +}: AvatarProps) { + const [avatarError, setAvatarError] = useState(false); + const prevSrcRef = useRef(undefined); + + // Reset error state when src changes + useEffect(() => { + if (src !== prevSrcRef.current) { + prevSrcRef.current = src; + // Reset error when src changes to allow retry + // eslint-disable-next-line react-hooks/set-state-in-effect + setAvatarError(false); + } + }, [src]); + + const sizeClass = sizeClasses[size]; + const normalizedSrc = normalizeAvatarUrl(src); + const displaySrc = normalizedSrc && !avatarError ? normalizedSrc : null; + const initial = fallbackText || username.charAt(0).toUpperCase(); + + return ( +
+ {displaySrc ? ( + {username} setAvatarError(true)} + /> + ) : null} + + {initial} + +
+ ); +} diff --git a/components/layout/HeroSection.tsx b/components/layout/HeroSection.tsx index b400796..fd58274 100644 --- a/components/layout/HeroSection.tsx +++ b/components/layout/HeroSection.tsx @@ -15,15 +15,15 @@ export default function HeroSection({ backgroundImage }: HeroSectionProps) {

GAME.OF.TECH @@ -32,14 +32,20 @@ export default function HeroSection({ backgroundImage }: HeroSectionProps) {

{/* Subtitle */} -
+
Peaksys
{/* Description */} -

+

Transformez votre apprentissage en aventure. Participez aux ateliers, défiez-vous sur les katas, partagez vos connaissances lors des présentations et progressez dans votre parcours tech. Gagnez de diff --git a/components/navigation/Navigation.tsx b/components/navigation/Navigation.tsx index 9a8d0de..323ff0c 100644 --- a/components/navigation/Navigation.tsx +++ b/components/navigation/Navigation.tsx @@ -5,7 +5,7 @@ import { useSession, signOut } from "next-auth/react"; import { useState } from "react"; import { usePathname } from "next/navigation"; import PlayerStats from "@/components/profile/PlayerStats"; -import { Button } from "@/components/ui"; +import { Button, ThemeToggle } from "@/components/ui"; interface UserData { username: string; @@ -42,17 +42,30 @@ export default function Navigation({ const isAdmin = initialIsAdmin ?? session?.user?.role === "ADMIN"; return ( -