diff --git a/src/app/globals.css b/src/app/globals.css index e3f3b88..4b74c44 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -33,6 +33,8 @@ } * { + margin: 0; + padding: 0; box-sizing: border-box; } @@ -46,121 +48,259 @@ body { background-attachment: fixed; color: var(--ctp-text); min-height: 100vh; + font-size: 16px; + line-height: 1.6; } -/* Gradient overlays */ -.gradient-overlay { - position: relative; - overflow: hidden; +/* Text colors */ +.text-mauve { + color: var(--ctp-mauve); +} +.text-blue { + color: var(--ctp-blue); +} +.text-red { + color: var(--ctp-red); +} +.text-green { + color: var(--ctp-green); +} +.text-yellow { + color: var(--ctp-yellow); +} +.text-sky { + color: var(--ctp-sky); +} +.text-teal { + color: var(--ctp-teal); +} +.text-text { + color: var(--ctp-text); +} +.text-subtext0 { + color: var(--ctp-subtext0); +} +.text-subtext1 { + color: var(--ctp-subtext1); +} +.text-overlay0 { + color: var(--ctp-overlay0); +} +.text-overlay1 { + color: var(--ctp-overlay1); +} +.text-base { + color: var(--ctp-base); } -.gradient-overlay::before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: linear-gradient( - 135deg, - rgba(203, 166, 247, 0.1) 0%, - rgba(137, 180, 250, 0.1) 100% - ); - pointer-events: none; - animation: gradient-shift 8s ease infinite; +/* Background colors */ +.bg-surface0 { + background-color: var(--ctp-surface0); +} +.bg-surface1 { + background-color: var(--ctp-surface1); +} +.bg-surface2 { + background-color: var(--ctp-surface2); +} +.bg-base { + background-color: var(--ctp-base); +} +.bg-mantle { + background-color: var(--ctp-mantle); } -@keyframes gradient-shift { - 0%, - 100% { - opacity: 0.5; - } - 50% { - opacity: 0.8; - } +.bg-blue { + background-color: var(--ctp-blue); +} +.bg-sapphire { + background-color: var(--ctp-sapphire); +} +.bg-red { + background-color: var(--ctp-red); +} +.bg-maroon { + background-color: var(--ctp-maroon); +} +.bg-green { + background-color: var(--ctp-green); +} +.bg-teal { + background-color: var(--ctp-teal); +} +.bg-yellow { + background-color: var(--ctp-yellow); +} +.bg-peach { + background-color: var(--ctp-peach); +} +.bg-mauve { + background-color: var(--ctp-mauve); } -/* Card styles */ +/* Border colors */ +.border-surface2 { + border-color: var(--ctp-surface2); +} +.border-blue { + border-color: var(--ctp-blue); +} +.border-red { + border-color: var(--ctp-red); +} +.border-mauve { + border-color: var(--ctp-mauve); +} + +/* Card component */ .card { background: var(--ctp-surface0); - border: 2px solid var(--ctp-surface2); - border-radius: 1rem; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - position: relative; - overflow: hidden; -} - -.card::before { - content: ""; - position: absolute; - top: 0; - left: -100%; - width: 100%; - height: 100%; - background: linear-gradient( - 90deg, - transparent, - rgba(255, 255, 255, 0.1), - transparent - ); - transition: left 0.5s; -} - -.card:hover::before { - left: 100%; + border: 3px solid var(--ctp-surface2); + border-radius: 24px; + transition: all 0.3s ease; } .card:hover { border-color: var(--ctp-mauve); - transform: translateY(-2px); - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4); + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5); } -/* Button styles */ +/* Button component */ .btn { - position: relative; - overflow: hidden; - transition: all 0.3s ease; + display: inline-block; font-weight: 700; - border-radius: 0.75rem; + border-radius: 16px; + transition: all 0.2s ease; cursor: pointer; border: none; -} - -.btn::before { - content: ""; - position: absolute; - top: 50%; - left: 50%; - width: 0; - height: 0; - border-radius: 50%; - background: rgba(255, 255, 255, 0.2); - transform: translate(-50%, -50%); - transition: - width 0.6s, - height 0.6s; -} - -.btn:active::before { - width: 300px; - height: 300px; + text-align: center; + font-size: 1.5rem; } .btn:hover { transform: translateY(-2px); - box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); + box-shadow: 0 12px 30px rgba(0, 0, 0, 0.4); } .btn:active { transform: translateY(0); } +.btn:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none !important; +} + +.btn-primary { + background: linear-gradient(135deg, var(--ctp-mauve), var(--ctp-pink)); +} + +.btn-primary:hover { + background: linear-gradient(135deg, var(--ctp-pink), var(--ctp-mauve)); +} + +.btn-success { + background: linear-gradient(135deg, var(--ctp-green), var(--ctp-teal)); +} + +.btn-success:hover { + background: linear-gradient(135deg, var(--ctp-teal), var(--ctp-green)); +} + +.btn-danger { + background: linear-gradient(135deg, var(--ctp-red), var(--ctp-maroon)); +} + +.btn-danger:hover { + background: linear-gradient(135deg, var(--ctp-maroon), var(--ctp-red)); +} + +.btn-warning { + background: linear-gradient(135deg, var(--ctp-yellow), var(--ctp-peach)); +} + +.btn-warning:hover { + background: linear-gradient(135deg, var(--ctp-peach), var(--ctp-yellow)); +} + +.btn-secondary { + background: linear-gradient(135deg, var(--ctp-overlay0), var(--ctp-overlay1)); +} + +.btn-secondary:hover { + background: linear-gradient(135deg, var(--ctp-overlay1), var(--ctp-overlay2)); +} + +.btn-blue { + background: linear-gradient(135deg, var(--ctp-blue), var(--ctp-sapphire)); +} + +.btn-blue:hover { + background: linear-gradient(135deg, var(--ctp-sapphire), var(--ctp-blue)); +} + +/* Input component */ +.input { + background: var(--ctp-surface1); + border: 3px solid var(--ctp-surface2); + border-radius: 16px; + color: var(--ctp-text); + transition: all 0.3s ease; + font-size: 1.25rem; +} + +.input:focus { + outline: none; + border-color: var(--ctp-mauve); + box-shadow: 0 0 20px rgba(203, 166, 247, 0.3); +} + +.input::placeholder { + color: var(--ctp-overlay0); +} + +/* Radio option */ +.radio-option { + background: var(--ctp-surface1); + border: 3px solid transparent; + border-radius: 20px; + cursor: pointer; + transition: all 0.3s ease; +} + +.radio-option:hover { + background: var(--ctp-surface2); +} + +.radio-option.selected { + border-color: var(--ctp-mauve); + background: var(--ctp-surface2); +} + +/* Score card variants */ +.score-card-blue { + background: rgba(137, 180, 250, 0.1); + border: 4px solid var(--ctp-blue); + border-radius: 24px; +} + +.score-card-red { + background: rgba(243, 139, 168, 0.1); + border: 4px solid var(--ctp-red); + border-radius: 24px; +} + +.player-item { + background: var(--ctp-surface0); + border-radius: 16px; +} + /* Animations */ @keyframes fadeIn { from { opacity: 0; - transform: translateY(20px); + transform: translateY(30px); } to { opacity: 1; @@ -168,56 +308,7 @@ body { } } -@keyframes slideInLeft { - from { - transform: translateX(-100%); - opacity: 0; - } - to { - transform: translateX(0); - opacity: 1; - } -} - -@keyframes slideInRight { - from { - transform: translateX(100%); - opacity: 0; - } - to { - transform: translateX(0); - opacity: 1; - } -} - -@keyframes scaleIn { - from { - transform: scale(0.8); - opacity: 0; - } - to { - transform: scale(1); - opacity: 1; - } -} - -@keyframes pulse-glow { - 0%, - 100% { - box-shadow: - 0 0 20px var(--ctp-red), - 0 0 40px var(--ctp-red), - 0 0 60px var(--ctp-red); - } - 50% { - box-shadow: - 0 0 30px var(--ctp-red), - 0 0 60px var(--ctp-red), - 0 0 90px var(--ctp-red); - } -} - -@keyframes pulse-scale { +@keyframes pulse { 0%, 100% { transform: scale(1); @@ -227,240 +318,107 @@ body { } } -@keyframes timer-pulse { - 0%, - 100% { - transform: scale(1); - } - 50% { - transform: scale(1.1); - } -} - -@keyframes float { - 0%, - 100% { - transform: translateY(0px); - } - 50% { - transform: translateY(-10px); - } -} - -@keyframes shimmer { - 0% { - background-position: -1000px 0; - } - 100% { - background-position: 1000px 0; - } -} - -@keyframes spin-slow { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -/* Utility classes */ .fade-in { animation: fadeIn 0.5s ease-out; } -.slide-in-left { - animation: slideInLeft 0.5s ease-out; +.pulse { + animation: pulse 2s ease-in-out infinite; } -.slide-in-right { - animation: slideInRight 0.5s ease-out; +/* Timer styles */ +.timer-container { + min-width: 300px; } -.scale-in { - animation: scaleIn 0.4s ease-out; +.timer-bar-container { + width: 100%; + height: 16px; + background: var(--ctp-surface0); + border-radius: 999px; + overflow: hidden; + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3); } -.float { - animation: float 3s ease-in-out infinite; +.timer-bar { + height: 100%; + transition: width 0.3s ease-out; + border-radius: 999px; +} + +.timer-bar-green { + background: linear-gradient(90deg, var(--ctp-green), var(--ctp-teal)); + box-shadow: 0 0 20px rgba(166, 227, 161, 0.5); +} + +.timer-bar-yellow { + background: linear-gradient(90deg, var(--ctp-yellow), var(--ctp-peach)); + box-shadow: 0 0 20px rgba(249, 226, 175, 0.5); +} + +.timer-bar-red { + background: linear-gradient(90deg, var(--ctp-red), var(--ctp-maroon)); + box-shadow: 0 0 20px rgba(243, 139, 168, 0.5); + animation: pulse 0.5s ease-in-out infinite; +} + +/* Status dot */ +.status-dot { + width: 16px; + height: 16px; + border-radius: 50%; + display: inline-block; + animation: pulse 2s ease-in-out infinite; +} + +.status-active { + background: var(--ctp-green); + box-shadow: 0 0 12px var(--ctp-green); +} + +.status-waiting { + background: var(--ctp-yellow); + box-shadow: 0 0 12px var(--ctp-yellow); +} + +.status-error { + background: var(--ctp-red); + box-shadow: 0 0 12px var(--ctp-red); } /* Buzz button */ .buzz-button { - position: relative; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - background: linear-gradient( - 135deg, - var(--ctp-red) 0%, - var(--ctp-maroon) 100% - ); - box-shadow: 0 10px 40px rgba(243, 139, 168, 0.5); + width: 280px; + height: 280px; + border-radius: 50%; + background: linear-gradient(135deg, var(--ctp-red), var(--ctp-maroon)); + border: none; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 10px 40px rgba(243, 139, 168, 0.6); + font-size: 2.5rem; + font-weight: 700; + color: white; } -.buzz-button::after { - content: ""; - position: absolute; - top: -50%; - left: -50%; - width: 200%; - height: 200%; - background: linear-gradient( - 45deg, - transparent 30%, - rgba(255, 255, 255, 0.3) 50%, - transparent 70% - ); - transform: rotate(45deg); - animation: shimmer 3s infinite; -} - -.buzz-button:hover { +.buzz-button:hover:not(:disabled) { transform: scale(1.1); - box-shadow: 0 15px 50px rgba(243, 139, 168, 0.7); + box-shadow: 0 15px 50px rgba(243, 139, 168, 0.8); } -.buzz-button:active { +.buzz-button:active:not(:disabled) { transform: scale(0.95); } -.buzz-active { - animation: - pulse-glow 1s infinite, - pulse-scale 1s infinite; -} - -.buzz-disabled { - opacity: 0.5; +.buzz-button:disabled { + opacity: 0.3; cursor: not-allowed; filter: grayscale(1); } -/* Timer */ -.timer-warning { - animation: timer-pulse 0.5s ease-in-out infinite; -} - -.timer-bar { - transition: - width 0.3s ease-out, - background-color 0.3s ease; - border-radius: 9999px; - position: relative; - overflow: hidden; -} - -.timer-bar::after { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: linear-gradient( - 90deg, - transparent, - rgba(255, 255, 255, 0.3), - transparent - ); - animation: shimmer 2s infinite; -} - -/* Score displays */ -.score-card { - background: linear-gradient( - 135deg, - var(--ctp-surface0) 0%, - var(--ctp-surface1) 100% - ); - border: 2px solid; - transition: all 0.3s ease; - position: relative; - overflow: hidden; -} - -.score-card::before { - content: ""; - position: absolute; - top: -50%; - left: -50%; - width: 200%; - height: 200%; - background: radial-gradient( - circle, - rgba(255, 255, 255, 0.1) 0%, - transparent 70% - ); - animation: spin-slow 20s linear infinite; -} - -.team1-card { - border-color: var(--ctp-blue); - box-shadow: 0 0 30px rgba(137, 180, 250, 0.2); -} - -.team2-card { - border-color: var(--ctp-red); - box-shadow: 0 0 30px rgba(243, 139, 168, 0.2); -} - -.individual-card { - border-color: var(--ctp-mauve); - box-shadow: 0 0 30px rgba(203, 166, 247, 0.2); -} - -/* Question cards */ -.question-card { - background: linear-gradient( - 135deg, - var(--ctp-surface0) 0%, - var(--ctp-surface1) 100% - ); - border: 2px solid var(--ctp-surface2); - border-radius: 1.5rem; - padding: 2rem; - position: relative; - overflow: hidden; -} - -.question-card::before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 4px; - background: linear-gradient( - 90deg, - var(--ctp-red), - var(--ctp-yellow), - var(--ctp-green), - var(--ctp-blue), - var(--ctp-mauve) - ); - background-size: 200% 100%; - animation: shimmer 3s linear infinite; -} - -/* Glow effects */ -.glow-green { - box-shadow: 0 0 20px rgba(166, 227, 161, 0.5); -} - -.glow-red { - box-shadow: 0 0 20px rgba(243, 139, 168, 0.5); -} - -.glow-blue { - box-shadow: 0 0 20px rgba(137, 180, 250, 0.5); -} - -.glow-yellow { - box-shadow: 0 0 20px rgba(249, 226, 175, 0.5); -} - -.glow-mauve { - box-shadow: 0 0 20px rgba(203, 166, 247, 0.5); +.buzz-button.active { + animation: pulse 1s ease-in-out infinite; + box-shadow: 0 0 60px rgba(243, 139, 168, 0.9); } /* Scrollbar */ @@ -470,158 +428,14 @@ body { ::-webkit-scrollbar-track { background: var(--ctp-mantle); - border-radius: 10px; } ::-webkit-scrollbar-thumb { - background: linear-gradient( - 180deg, - var(--ctp-surface1) 0%, - var(--ctp-surface2) 100% - ); - border-radius: 10px; + background: var(--ctp-surface1); + border-radius: 7px; border: 2px solid var(--ctp-mantle); } ::-webkit-scrollbar-thumb:hover { - background: linear-gradient( - 180deg, - var(--ctp-surface2) 0%, - var(--ctp-overlay0) 100% - ); -} - -/* Text gradients */ -.text-gradient-mauve { - background: linear-gradient( - 135deg, - var(--ctp-mauve) 0%, - var(--ctp-pink) 100% - ); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -.text-gradient-blue { - background: linear-gradient( - 135deg, - var(--ctp-blue) 0%, - var(--ctp-sapphire) 100% - ); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -.text-gradient-green { - background: linear-gradient( - 135deg, - var(--ctp-green) 0%, - var(--ctp-teal) 100% - ); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -/* Loading states */ -.skeleton { - background: linear-gradient( - 90deg, - var(--ctp-surface0) 25%, - var(--ctp-surface1) 50%, - var(--ctp-surface0) 75% - ); - background-size: 200% 100%; - animation: shimmer 2s infinite; - border-radius: 0.5rem; -} - -/* Badges */ -.badge { - display: inline-block; - padding: 0.5rem 1rem; - border-radius: 9999px; - font-weight: 700; - font-size: 0.875rem; - text-transform: uppercase; - letter-spacing: 0.05em; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); -} - -/* Status indicators */ -.status-dot { - width: 12px; - height: 12px; - border-radius: 50%; - display: inline-block; - margin-right: 0.5rem; - animation: pulse-scale 2s ease-in-out infinite; -} - -.status-active { - background: var(--ctp-green); - box-shadow: 0 0 10px var(--ctp-green); -} - -.status-waiting { - background: var(--ctp-yellow); - box-shadow: 0 0 10px var(--ctp-yellow); -} - -.status-error { - background: var(--ctp-red); - box-shadow: 0 0 10px var(--ctp-red); -} - -/* Glass morphism */ -.glass { - background: rgba(49, 50, 68, 0.7); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - border: 1px solid rgba(205, 214, 244, 0.1); -} - -/* Neon text */ -.neon-text { - text-shadow: - 0 0 10px currentColor, - 0 0 20px currentColor, - 0 0 30px currentColor, - 0 0 40px currentColor; -} - -/* Particle background effect */ -@keyframes particle-float { - 0%, - 100% { - transform: translate(0, 0) rotate(0deg); - } - 33% { - transform: translate(30px, -30px) rotate(120deg); - } - 66% { - transform: translate(-20px, 20px) rotate(240deg); - } -} - -.particles { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - pointer-events: none; - z-index: 0; -} - -.particle { - position: absolute; - width: 4px; - height: 4px; - background: var(--ctp-mauve); - border-radius: 50%; - opacity: 0.3; - animation: particle-float 20s infinite; + background: var(--ctp-surface2); } diff --git a/src/app/host/page.tsx b/src/app/host/page.tsx index b6cc278..f0b329d 100644 --- a/src/app/host/page.tsx +++ b/src/app/host/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useCallback } from "react"; import { Player } from "@/types"; import Cookies from "js-cookie"; import Timer from "@/components/Timer"; @@ -23,6 +23,17 @@ export default function HostPage() { } }, []); + const gameAction = useCallback( + async (action: string) => { + await fetch(`/api/game/${roomCode}`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ action, moderatorId }), + }); + }, + [roomCode, moderatorId], + ); + async function createRoom() { const res = await fetch("/api/room/create", { method: "POST", @@ -60,14 +71,6 @@ export default function HostPage() { return () => clearInterval(interval); }, [roomCode]); - async function gameAction(action: string) { - await fetch(`/api/game/${roomCode}`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ action, moderatorId }), - }); - } - async function closeRoom() { await fetch(`/api/room/${roomCode}`, { method: "POST", @@ -100,7 +103,6 @@ export default function HostPage() { } } - // C for correct if (e.code === "KeyC") { const phase = gameState?.phase; if (phase === "tossup_buzzing" && gameState?.buzzedPlayer) { @@ -110,7 +112,6 @@ export default function HostPage() { } } - // W for wrong if (e.code === "KeyW") { const phase = gameState?.phase; if (phase === "tossup_buzzing" && gameState?.buzzedPlayer) { @@ -123,18 +124,37 @@ export default function HostPage() { window.addEventListener("keydown", handleKeyPress); return () => window.removeEventListener("keydown", handleKeyPress); - }, [roomCode, gameState, players]); + }, [roomCode, gameState, players, gameAction]); if (roomClosed) { return ( -
-
-

+
+
+

Room Closed

Go Home @@ -145,44 +165,102 @@ export default function HostPage() { if (!roomCode) { return ( -
-
-

+
+
+

Create Room

-
-