feat: improve website styling
This commit is contained in:
@@ -11,6 +11,12 @@
|
|||||||
to { opacity: 1; transform: translateY(0); }
|
to { opacity: 1; transform: translateY(0); }
|
||||||
}
|
}
|
||||||
.animate-fade-in { animation: fadeIn 0.3s ease-out; }
|
.animate-fade-in { animation: fadeIn 0.3s ease-out; }
|
||||||
|
|
||||||
|
@keyframes slideInUp {
|
||||||
|
from { opacity: 0; transform: translateY(20px) scale(0.95); }
|
||||||
|
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||||
|
}
|
||||||
|
.animate-slide-in-up { animation: slideInUp 0.35s cubic-bezier(0.21, 1.02, 0.73, 1) forwards; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-slate-50 text-slate-800 antialiased">
|
<body class="bg-slate-50 text-slate-800 antialiased">
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ function Nav() {
|
|||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
||||||
isActive
|
isActive
|
||||||
? "bg-indigo-600 text-white"
|
? "bg-white/20 text-white shadow-sm backdrop-blur-sm"
|
||||||
: "text-slate-600 hover:bg-slate-200"
|
: "text-indigo-100 hover:bg-white/10 hover:text-white"
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -23,10 +23,15 @@ function Nav() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="bg-white border-b border-slate-200 sticky top-0 z-50">
|
<nav className="bg-gradient-to-r from-indigo-600 via-indigo-700 to-purple-700 sticky top-0 z-50 shadow-lg">
|
||||||
<div className="max-w-6xl mx-auto px-4 py-3 flex items-center gap-2">
|
<div className="max-w-6xl mx-auto px-4 py-3 flex items-center gap-2">
|
||||||
<h1 className="text-lg font-bold text-indigo-700 mr-6">
|
<h1 className="text-lg font-extrabold tracking-tight mr-6 flex items-center gap-2">
|
||||||
|
<span className="inline-flex items-center justify-center w-8 h-8 rounded-lg bg-white/20 backdrop-blur-sm text-white text-base">
|
||||||
|
🏠
|
||||||
|
</span>
|
||||||
|
<span className="bg-gradient-to-r from-white to-indigo-200 bg-clip-text text-transparent">
|
||||||
House ELO
|
House ELO
|
||||||
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
{link("/", "Compare")}
|
{link("/", "Compare")}
|
||||||
{link("/rankings", "Rankings")}
|
{link("/rankings", "Rankings")}
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { api, type Listing, type Matchup, type CompareResult } from "../api";
|
import { api, type Listing, type Matchup, type CompareResult } from "../api";
|
||||||
import ListingCard from "../components/ListingCard";
|
import ListingCard from "../components/ListingCard";
|
||||||
|
|
||||||
|
interface Toast {
|
||||||
|
id: number;
|
||||||
|
result: CompareResult;
|
||||||
|
}
|
||||||
|
|
||||||
export default function CompareView() {
|
export default function CompareView() {
|
||||||
const [matchup, setMatchup] = useState<Matchup | null>(null);
|
const [matchup, setMatchup] = useState<Matchup | null>(null);
|
||||||
const [result, setResult] = useState<CompareResult | null>(null);
|
const [result, setResult] = useState<CompareResult | null>(null);
|
||||||
|
const [toasts, setToasts] = useState<Toast[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [comparisonCount, setComparisonCount] = useState(0);
|
const [comparisonCount, setComparisonCount] = useState(0);
|
||||||
|
const toastId = useRef(0);
|
||||||
|
|
||||||
const fetchMatchup = useCallback(async () => {
|
const fetchMatchup = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -35,6 +42,11 @@ export default function CompareView() {
|
|||||||
const res = await api.submitComparison(winner.global_id, loser.global_id);
|
const res = await api.submitComparison(winner.global_id, loser.global_id);
|
||||||
setResult(res);
|
setResult(res);
|
||||||
setComparisonCount((c) => c + 1);
|
setComparisonCount((c) => c + 1);
|
||||||
|
|
||||||
|
// Add toast
|
||||||
|
const id = ++toastId.current;
|
||||||
|
setToasts((prev) => [...prev, { id, result: res }]);
|
||||||
|
setTimeout(() => setToasts((prev) => prev.filter((t) => t.id !== id)), 3000);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(e instanceof Error ? e.message : "Failed to submit comparison");
|
setError(e instanceof Error ? e.message : "Failed to submit comparison");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -78,18 +90,6 @@ export default function CompareView() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{result && (
|
|
||||||
<div className="mb-6 bg-green-50 border border-green-200 rounded-lg p-4 text-center animate-fade-in">
|
|
||||||
<p className="text-green-800 font-medium">
|
|
||||||
ELO change: +{result.elo_change.toFixed(1)} / -{result.elo_change.toFixed(1)}
|
|
||||||
</p>
|
|
||||||
<p className="text-green-600 text-sm">
|
|
||||||
Winner: {result.new_winner_elo.toFixed(0)} · Loser:{" "}
|
|
||||||
{result.new_loser_elo.toFixed(0)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="grid md:grid-cols-2 gap-6">
|
<div className="grid md:grid-cols-2 gap-6">
|
||||||
<ListingCard
|
<ListingCard
|
||||||
listing={matchup.listing_a}
|
listing={matchup.listing_a}
|
||||||
@@ -121,6 +121,36 @@ export default function CompareView() {
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* ELO change toasts */}
|
||||||
|
<div className="fixed bottom-6 right-6 flex flex-col gap-3 z-50 pointer-events-none">
|
||||||
|
{toasts.map((t) => (
|
||||||
|
<div
|
||||||
|
key={t.id}
|
||||||
|
className="pointer-events-auto bg-white border border-slate-200 rounded-xl shadow-xl px-5 py-4 min-w-[260px] animate-slide-in-up"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3 mb-1">
|
||||||
|
<span className="inline-flex items-center justify-center w-8 h-8 rounded-full bg-green-100 text-green-600 text-lg font-bold">
|
||||||
|
↑
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-semibold text-slate-800">ELO Updated</p>
|
||||||
|
<p className="text-xs text-slate-500">Comparison recorded</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between mt-2 text-sm">
|
||||||
|
<span className="text-green-600 font-bold">
|
||||||
|
Winner: {t.result.new_winner_elo.toFixed(0)}{" "}
|
||||||
|
<span className="text-green-500 text-xs">(+{t.result.elo_change.toFixed(1)})</span>
|
||||||
|
</span>
|
||||||
|
<span className="text-red-500 font-bold">
|
||||||
|
Loser: {t.result.new_loser_elo.toFixed(0)}{" "}
|
||||||
|
<span className="text-red-400 text-xs">(-{t.result.elo_change.toFixed(1)})</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user