complete design overhaul

This commit is contained in:
lila 2026-04-19 19:25:55 +02:00
parent d033a08d87
commit 0a0bafa0ec
14 changed files with 505 additions and 160 deletions

View file

@ -35,16 +35,18 @@ const SettingGroup = ({
onSelect,
}: SettingGroupProps) => (
<div className="w-full">
<p className="text-sm font-medium text-purple-400 mb-2">{label}</p>
<p className="text-xs font-bold tracking-widest uppercase text-(--color-primary) mb-2">
{label}
</p>
<div className="flex gap-2 flex-wrap">
{options.map((option) => (
<button
key={option}
onClick={() => onSelect(option)}
className={`py-2 px-5 rounded-xl font-semibold text-sm border-b-4 transition-all duration-200 cursor-pointer ${
className={`py-2 px-5 rounded-xl font-semibold text-sm border transition-all duration-200 cursor-pointer ${
selected === option
? "bg-purple-600 text-white border-purple-800"
: "bg-white text-purple-900 border-purple-200 hover:bg-purple-50 hover:border-purple-300"
? "bg-(--color-primary) text-white border-(--color-primary-dark) shadow-sm"
: "bg-white text-(--color-primary-dark) border-(--color-primary-light) hover:bg-(--color-surface) hover:-translate-y-0.5 active:translate-y-0"
}`}
>
{LABELS[option] ?? option}
@ -91,12 +93,18 @@ export const GameSetup = ({ onStart }: GameSetupProps) => {
return (
<div className="flex flex-col items-center gap-6 w-full max-w-md mx-auto">
<div className="bg-white rounded-3xl shadow-lg p-8 w-full text-center">
<h1 className="text-3xl font-bold text-purple-900 mb-1">lila</h1>
<p className="text-sm text-gray-400">Set up your quiz</p>
<div className="relative overflow-hidden w-full rounded-3xl border border-(--color-primary-light) bg-white dark:bg-black/10 shadow-sm p-8 text-center">
<div className="absolute -top-16 -left-20 h-40 w-40 rounded-full bg-(--color-accent) opacity-[0.10] blur-3xl" />
<div className="absolute -bottom-20 -right-20 h-44 w-44 rounded-full bg-(--color-primary) opacity-[0.12] blur-3xl" />
<h1 className="relative text-3xl font-black tracking-tight text-(--color-text) mb-1">
lila
</h1>
<p className="relative text-sm text-(--color-text-muted)">
Set up your quiz
</p>
</div>
<div className="bg-white rounded-3xl shadow-lg p-6 w-full flex flex-col gap-5">
<div className="w-full rounded-3xl border border-(--color-primary-light) bg-white dark:bg-black/10 shadow-sm p-6 flex flex-col gap-5">
<SettingGroup
label="I speak"
options={SUPPORTED_LANGUAGE_CODES}
@ -131,9 +139,9 @@ export const GameSetup = ({ onStart }: GameSetupProps) => {
<button
onClick={handleStart}
className="w-full py-4 rounded-2xl text-xl font-bold bg-linear-to-r from-pink-400 to-purple-500 text-white border-b-4 border-purple-700 hover:-translate-y-0.5 active:translate-y-0 active:border-b-2 transition-all duration-200 cursor-pointer"
className="w-full py-4 rounded-2xl text-xl font-black bg-linear-to-r from-pink-400 to-purple-500 text-white shadow-sm hover:shadow-md hover:-translate-y-0.5 active:translate-y-0 transition-all duration-200 cursor-pointer"
>
Start Quiz
Start
</button>
</div>
);

View file

@ -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 (
<button
className={`${base} ${styles[state]}`}
className={`${base} ${styles[state]} ${motion}`}
onClick={onSelect}
disabled={
state === "disabled" || state === "correct" || state === "wrong"
}
>
{text}
<span className="absolute inset-0 -z-10 opacity-0 group-hover:opacity-100 transition-opacity">
<span className="absolute -top-10 -right-12 h-24 w-24 rounded-full bg-(--color-primary) opacity-[0.10] blur-2xl" />
<span className="absolute -bottom-10 -left-12 h-24 w-24 rounded-full bg-(--color-accent) opacity-[0.10] blur-2xl" />
</span>
<span className="flex items-center justify-between gap-3">
<span className="truncate">{text}</span>
{state === "correct" && <span aria-hidden></span>}
{state === "wrong" && <span aria-hidden></span>}
</span>
</button>
);
};

View file

@ -48,22 +48,31 @@ export const QuestionCard = ({
return (
<div className="flex flex-col items-center gap-6 w-full max-w-md mx-auto">
<div className="flex items-center gap-2 text-sm font-medium text-purple-400">
<span>
{questionNumber} / {totalQuestions}
</span>
<div className="w-full flex items-center justify-between">
<div className="inline-flex items-center gap-2 rounded-full bg-(--color-surface) border border-(--color-primary-light) px-4 py-1 text-xs font-bold tracking-widest uppercase text-(--color-primary)">
Round {questionNumber}/{totalQuestions}
</div>
<div className="text-xs font-semibold text-(--color-text-muted)">
{currentResult ? "Checked" : selectedOptionId !== null ? "Ready" : "Pick one"}
</div>
</div>
<div className="bg-white rounded-3xl shadow-lg p-8 w-full text-center">
<h2 className="text-3xl font-bold text-purple-900 mb-2">
<div className="relative w-full overflow-hidden rounded-3xl border border-(--color-primary-light) bg-white/40 dark:bg-black/10 backdrop-blur shadow-sm p-8 text-center">
<div className="absolute -top-16 -left-20 h-40 w-40 rounded-full bg-(--color-accent) opacity-[0.10] blur-3xl" />
<div className="absolute -bottom-20 -right-20 h-44 w-44 rounded-full bg-(--color-primary) opacity-[0.12] blur-3xl" />
<h2 className="relative text-3xl font-black tracking-tight text-(--color-text) mb-2">
{question.prompt}
</h2>
{question.gloss && (
<p className="text-sm text-gray-400 italic">{question.gloss}</p>
<p className="relative text-sm text-(--color-text-muted) italic">
{question.gloss}
</p>
)}
</div>
<div className="flex flex-col gap-3 w-full">
<div className="w-full rounded-3xl border border-(--color-primary-light) bg-white/55 dark:bg-black/10 backdrop-blur shadow-sm p-4">
<div className="flex flex-col gap-3">
{question.options.map((option) => (
<OptionButton
key={option.optionId}
@ -72,21 +81,22 @@ export const QuestionCard = ({
onSelect={() => handleSelect(option.optionId)}
/>
))}
</div>
</div>
{!currentResult && selectedOptionId !== null && (
<button
onClick={handleSubmit}
className="w-full py-3 rounded-2xl text-lg font-bold bg-linear-to-r from-pink-400 to-purple-500 text-white border-b-4 border-purple-700 hover:-translate-y-0.5 active:translate-y-0 active:border-b-2 transition-all duration-200 cursor-pointer"
className="w-full py-3 rounded-2xl text-lg font-bold bg-linear-to-r from-pink-400 to-purple-500 text-white shadow-sm hover:shadow-md hover:-translate-y-0.5 active:translate-y-0 transition-all duration-200 cursor-pointer"
>
Submit
Lock it in
</button>
)}
{currentResult && (
<button
onClick={handleNext}
className="w-full py-3 rounded-2xl text-lg font-bold bg-purple-600 text-white border-b-4 border-purple-800 hover:bg-purple-500 hover:-translate-y-0.5 active:translate-y-0 active:border-b-2 transition-all duration-200 cursor-pointer"
className="w-full py-3 rounded-2xl text-lg font-bold bg-(--color-primary) text-white shadow-sm hover:shadow-md hover:bg-(--color-primary-dark) hover:-translate-y-0.5 active:translate-y-0 transition-all duration-200 cursor-pointer"
>
{questionNumber === totalQuestions ? "See Results" : "Next"}
</button>

View file

@ -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 (
<div className="flex flex-col items-center gap-8 w-full max-w-md mx-auto">
<div className="bg-white rounded-3xl shadow-lg p-10 w-full text-center">
<p className="text-lg font-medium text-purple-400 mb-2">Your Score</p>
<h2 className="text-6xl font-bold text-purple-900 mb-1">
<div className="relative overflow-hidden w-full rounded-3xl border border-(--color-primary-light) bg-white/40 dark:bg-black/10 backdrop-blur shadow-sm p-10 text-center">
{percentage === 100 && <ConfettiBurst />}
<div className="absolute -top-20 -left-24 h-56 w-56 rounded-full bg-(--color-accent) opacity-[0.10] blur-3xl" />
<div className="absolute -bottom-24 -right-20 h-64 w-64 rounded-full bg-(--color-primary) opacity-[0.12] blur-3xl" />
<p className="relative inline-flex items-center gap-2 rounded-full bg-(--color-surface) border border-(--color-primary-light) px-4 py-1 text-xs font-bold tracking-widest uppercase text-(--color-primary) mb-3">
Results
</p>
<h2 className="relative text-6xl font-black tracking-tight text-(--color-text) mb-1">
{score}/{total}
</h2>
<p className="text-2xl mb-6">{getMessage()}</p>
<p className="relative text-2xl mb-6">{getMessage()}</p>
<div className="w-full bg-purple-100 rounded-full h-4 mb-2">
<div className="relative w-full bg-(--color-surface) border border-(--color-primary-light) rounded-full h-4 mb-2 overflow-hidden">
<div
className="bg-linear-to-r from-pink-400 to-purple-500 h-4 rounded-full transition-all duration-700"
style={{ width: `${percentage}%` }}
/>
</div>
<p className="text-sm text-gray-400">{percentage}% correct</p>
<p className="relative text-sm text-(--color-text-muted)">
{percentage}% correct
</p>
</div>
<div className="flex flex-col gap-2 w-full">
{results.map((result, index) => (
<div
key={result.questionId}
className={`flex items-center gap-3 py-2 px-4 rounded-xl text-sm ${
className={`flex items-center gap-3 py-2 px-4 rounded-xl text-sm border ${
result.isCorrect
? "bg-emerald-50 text-emerald-700"
: "bg-pink-50 text-pink-700"
? "bg-emerald-50/60 text-emerald-700 border-emerald-200"
: "bg-pink-50/60 text-pink-700 border-pink-200"
}`}
>
<span className="font-bold">{index + 1}.</span>
@ -51,9 +60,9 @@ export const ScoreScreen = ({ results, onPlayAgain }: ScoreScreenProps) => {
<button
onClick={onPlayAgain}
className="py-3 px-10 rounded-2xl text-lg font-bold bg-purple-600 text-white border-b-4 border-purple-800 hover:bg-purple-500 hover:-translate-y-0.5 active:translate-y-0 active:border-b-2 transition-all duration-200 cursor-pointer"
className="w-full py-3 px-10 rounded-2xl text-lg font-black bg-(--color-primary) text-white shadow-sm hover:shadow-md hover:bg-(--color-primary-dark) hover:-translate-y-0.5 active:translate-y-0 transition-all duration-200 cursor-pointer"
>
Play Again
Play again
</button>
</div>
);