Files
movieloop-frontend/src/components/game/ValidationFeedback.tsx
T
TehRiehlDeal 8d0b7f39ee feat(ui): give the app one visual identity as Movie Loop
Gold is the primary in both themes, Bricolage Grotesque carries the
wordmark, titles and the score, and Movie A / Movie B wear the blue and
magenta of three-strip Technicolor on their posters instead of colouring
whole rows. The loop closes on screen - connectors light, a gold line
draws back to Movie A, the row glows, then the result arrives - in place
of confetti, on every play mode, scaled to the chain's length. Every
page shares one header and hairline lists; copy is sentence case with
one verb per flow, and the Home hero shows a real finished loop.

Also clamps link counts at zero: an opponent who had not moved yet
showed "-1 links".
2026-09-12 22:42:55 -07:00

65 lines
1.9 KiB
TypeScript

import { useGameStore } from '@/stores/game-store';
import { Loader2, AlertCircle, CheckCircle2 } from 'lucide-react';
import { useEffect, useState } from 'react';
export default function ValidationFeedback() {
const isValidating = useGameStore((s) => s.isValidating);
const validationError = useGameStore((s) => s.validationError);
const chain = useGameStore((s) => s.chain);
const [showCheck, setShowCheck] = useState(false);
const [flashedLength, setFlashedLength] = useState(0);
// Flash a gold check when the chain grows (successful validation). Adjusting
// state during render is React's documented pattern for reacting to a changed
// value; doing it in an effect costs an extra committed render each link.
if (chain.length > 1 && chain.length !== flashedLength) {
setFlashedLength(chain.length);
setShowCheck(true);
}
useEffect(() => {
if (!showCheck) return;
const timer = setTimeout(() => setShowCheck(false), 600);
return () => clearTimeout(timer);
}, [showCheck]);
if (isValidating) {
return (
<div
role="status"
className="flex items-center gap-2 text-sm text-muted-foreground"
>
<Loader2 className="h-4 w-4 animate-spin" aria-hidden="true" />
Checking the link…
</div>
);
}
if (validationError) {
return (
<div
key={validationError}
role="alert"
className="flex animate-shake items-center gap-2 text-sm text-destructive motion-reduce:animate-none"
>
<AlertCircle className="h-4 w-4" aria-hidden="true" />
{validationError}
</div>
);
}
if (showCheck) {
return (
<div
role="status"
className="flex animate-check-flash items-center gap-2 text-sm font-medium text-primary-text motion-reduce:animate-none"
>
<CheckCircle2 className="h-4 w-4" aria-hidden="true" />
Linked
</div>
);
}
return null;
}