diff --git a/apps/web/src/components/game/GameSetup.tsx b/apps/web/src/components/game/GameSetup.tsx index 0266342..9315bc4 100644 --- a/apps/web/src/components/game/GameSetup.tsx +++ b/apps/web/src/components/game/GameSetup.tsx @@ -35,16 +35,18 @@ const SettingGroup = ({ onSelect, }: SettingGroupProps) => (
-

{label}

+

+ {label} +

{options.map((option) => (
); diff --git a/apps/web/src/components/game/OptionButton.tsx b/apps/web/src/components/game/OptionButton.tsx index e01e4ae..783d25d 100644 --- a/apps/web/src/components/game/OptionButton.tsx +++ b/apps/web/src/components/game/OptionButton.tsx @@ -6,26 +6,39 @@ type OptionButtonProps = { export const OptionButton = ({ text, state, onSelect }: OptionButtonProps) => { const base = - "w-full py-3 px-6 rounded-2xl text-lg font-semibold transition-all duration-200 border-b-4 cursor-pointer"; + "group relative w-full overflow-hidden py-3 px-6 rounded-2xl text-lg font-semibold transition-all duration-200 border cursor-pointer text-left"; const styles = { - idle: "bg-white text-purple-900 border-purple-200 hover:bg-purple-50 hover:border-purple-300 hover:-translate-y-0.5 active:translate-y-0 active:border-b-2", + idle: "bg-white text-(--color-primary-dark) border-(--color-primary-light) hover:bg-(--color-surface) hover:-translate-y-0.5 active:translate-y-0", selected: - "bg-purple-100 text-purple-900 border-purple-400 ring-2 ring-purple-400", - disabled: "bg-gray-100 text-gray-400 border-gray-200 cursor-default", - correct: "bg-emerald-400 text-white border-emerald-600 scale-[1.02]", - wrong: "bg-pink-400 text-white border-pink-600", + "bg-(--color-surface) text-(--color-primary-dark) border-(--color-primary) ring-2 ring-(--color-primary)", + disabled: + "bg-(--color-surface) text-(--color-primary-light) border-(--color-primary-light) cursor-default", + correct: + "bg-emerald-400/90 text-white border-emerald-600 ring-2 ring-emerald-300 scale-[1.01]", + wrong: "bg-pink-500/90 text-white border-pink-700 ring-2 ring-pink-300", }; + const motion = + state === "correct" ? "lila-pop" : state === "wrong" ? "lila-shake" : ""; + return ( ); }; diff --git a/apps/web/src/components/game/QuestionCard.tsx b/apps/web/src/components/game/QuestionCard.tsx index f81ec0d..7878a5b 100644 --- a/apps/web/src/components/game/QuestionCard.tsx +++ b/apps/web/src/components/game/QuestionCard.tsx @@ -48,22 +48,31 @@ export const QuestionCard = ({ return (
-
- - {questionNumber} / {totalQuestions} - +
+
+ Round {questionNumber}/{totalQuestions} +
+
+ {currentResult ? "Checked" : selectedOptionId !== null ? "Ready" : "Pick one"} +
-
-

+
+
+
+ +

{question.prompt}

{question.gloss && ( -

{question.gloss}

+

+ {question.gloss} +

)}
-
+
+
{question.options.map((option) => ( handleSelect(option.optionId)} /> ))} +
{!currentResult && selectedOptionId !== null && ( )} {currentResult && ( diff --git a/apps/web/src/components/game/ScoreScreen.tsx b/apps/web/src/components/game/ScoreScreen.tsx index afd3295..ac64da2 100644 --- a/apps/web/src/components/game/ScoreScreen.tsx +++ b/apps/web/src/components/game/ScoreScreen.tsx @@ -1,4 +1,5 @@ import type { AnswerResult } from "@lila/shared"; +import { ConfettiBurst } from "../ui/ConfettiBurst"; type ScoreScreenProps = { results: AnswerResult[]; onPlayAgain: () => void }; @@ -17,30 +18,38 @@ export const ScoreScreen = ({ results, onPlayAgain }: ScoreScreenProps) => { return (
-
-

Your Score

-

+
+ {percentage === 100 && } +
+
+ +

+ Results +

+

{score}/{total}

-

{getMessage()}

+

{getMessage()}

-
+
-

{percentage}% correct

+

+ {percentage}% correct +

{results.map((result, index) => (
{index + 1}. @@ -51,9 +60,9 @@ export const ScoreScreen = ({ results, onPlayAgain }: ScoreScreenProps) => {
); diff --git a/apps/web/src/components/landing/FeatureCards.tsx b/apps/web/src/components/landing/FeatureCards.tsx index 849afd0..e8085c2 100644 --- a/apps/web/src/components/landing/FeatureCards.tsx +++ b/apps/web/src/components/landing/FeatureCards.tsx @@ -12,29 +12,52 @@ const features = [ }, { emoji: "⚔️", - title: "Multiplayer coming", - description: "Challenge friends and see who has the bigger vocabulary.", + title: "Real-time multiplayer", + description: "Create a room, share the code, and race to the best score.", }, ]; const FeatureCards = () => { return ( -
-

- Why lila -

+
+
+
+ Tiny rounds · big dopamine +
+

+ Why lila +

+

+ Built to be fast to start, satisfying to finish, and fun to repeat. +

+
-
+
{features.map(({ emoji, title, description }) => (
- {emoji} -

{title}

-

+

+
+
+ {emoji} +
+

{title}

+
+

{description}

+
+ + + Instant feedback + + + + Type-safe API + +
))}
diff --git a/apps/web/src/components/landing/Hero.tsx b/apps/web/src/components/landing/Hero.tsx index 0297e53..6a6de87 100644 --- a/apps/web/src/components/landing/Hero.tsx +++ b/apps/web/src/components/landing/Hero.tsx @@ -5,57 +5,132 @@ const Hero = () => { const { data: session } = useSession(); return ( -
-
- - Vocabulary trainer - +
+
+
+
-

- Meet{" "} - - lila - -

+
+
+
+ + Duolingo-style drills · real-time multiplayer + +
-

- Learn words.{" "} - Beat friends. -

+

+ Learn vocabulary fast,{" "} + + together + + . +

-
- {["🇬🇧", "🇮🇹", "🇩🇪", "🇫🇷", "🇪🇸"].map((flag) => ( - - {flag} - - ))} -
+

+ A word appears. You pick the translation. You score points. + Then you queue up a room and{" "} + beat friends{" "} + in real time. +

-
- {session ? ( - - Start playing → - - ) : ( - <> - - Get started → - - - Login - - - )} +
+ {["🇬🇧", "🇮🇹", "🇩🇪", "🇫🇷", "🇪🇸"].map((flag) => ( + + {flag} + + ))} + + Supported languages: English, Italian, German, French, Spanish + +
+ +
+ {session ? ( + <> + + Play solo + + + Play with friends + + + ) : ( + <> + + Get started + + + Log in + + + )} +
+
+ +
+
+
+
+
+
+
+
+
+ + Live preview + +
+ +
+

+ Translate +

+
+
+ finestra +
+
+ (noun) · A2 +
+
+ +
+ {["window", "forest", "river", "kitchen"].map((opt) => ( +
+ {opt} +
+ ))} +
+ +
+
+ Round 2/10 +
+
+ + Multiplayer room +
+
+
+
+
+
); diff --git a/apps/web/src/components/landing/HowItWorks.tsx b/apps/web/src/components/landing/HowItWorks.tsx index b9791a8..8255493 100644 --- a/apps/web/src/components/landing/HowItWorks.tsx +++ b/apps/web/src/components/landing/HowItWorks.tsx @@ -20,26 +20,55 @@ const steps = [ const HowItWorks = () => { return ( -
-

- How it works -

- -
- {steps.map(({ number, title, description }) => ( -
- - {number} - -

{title}

-

- {description} -

+
+
+
+
+
+
+ Quick · satisfying · replayable
- ))} +

+ How it works +

+

+ Short rounds, instant feedback, and just enough pressure to make the + words stick. +

+
+ +
    + {steps.map(({ number, title, description }) => ( +
  1. +
    +
    +
    +
    +
    + + {number} + +
    +
    +
    +

    + {title} +

    +

    + {description} +

    +
    + + Under 30 seconds +
    +
    +
    +
  2. + ))} +
); diff --git a/apps/web/src/components/multiplayer/MultiplayerScoreScreen.tsx b/apps/web/src/components/multiplayer/MultiplayerScoreScreen.tsx index 5e95588..f530db8 100644 --- a/apps/web/src/components/multiplayer/MultiplayerScoreScreen.tsx +++ b/apps/web/src/components/multiplayer/MultiplayerScoreScreen.tsx @@ -1,5 +1,6 @@ import { useNavigate } from "@tanstack/react-router"; import type { LobbyPlayer } from "@lila/shared"; +import { ConfettiBurst } from "../ui/ConfettiBurst"; type MultiplayerScoreScreenProps = { players: LobbyPlayer[]; @@ -26,19 +27,27 @@ export const MultiplayerScoreScreen = ({ .join(" and "); return ( -
-
+
+
+
+
+ +
+ {isWinner && !isTie && } {/* Result header */}
-

+
+ Multiplayer +
+

{isTie ? "It's a tie!" : isWinner ? "You win! 🎉" : "Game over"}

-

+

{isTie ? `${winnerNames} tied` : `${winnerNames} wins!`}

-
+
{/* Score list */}
@@ -48,35 +57,35 @@ export const MultiplayerScoreScreen = ({ return (
- + {index + 1}. {player.user.name} {isCurrentUser && ( - + (you) )} {isPlayerWinner && ( - + 👑 )}
- + {player.score} pts
@@ -84,12 +93,12 @@ export const MultiplayerScoreScreen = ({ })}
-
+
{/* Actions */}
-
+
{/* Join lobby */}
-

Join a lobby

-

+

Join a lobby

+

Enter the code shared by your host.

setJoinCode(e.target.value)} @@ -128,7 +128,7 @@ function MultiplayerPage() { disabled={isCreating || isJoining} /> -

Click to copy

+

Click to copy

-
+
{/* Player list */}
-

+

Players ({lobby.players.length})

    {lobby.players.map((player) => (
  • {player.user.name} @@ -135,7 +137,7 @@ function LobbyPage() { {/* Start button — host only */} {isHost && (