neighbor-sum-avoidance: render arena pre-start with interactions disabled

Show the circle arena before the user clicks Start (greyed via
opacity-60 pointer-events-none), and guard drag/drop/click handlers on
isActive. Clicking Start enables interactions and kicks off the timer
as before. Also resync slot count with the Numbers slider while idle.

Marks T1 done.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Neeldhara Misra 2026-04-17 09:22:23 +05:30
parent fcbdcfccdf
commit 1c754de4f7
2 changed files with 85 additions and 63 deletions

View file

@ -41,6 +41,14 @@ const NeighborSumAvoidance = ({ shareUrl }: NeighborSumAvoidanceProps) => {
};
}, [isActive, isComplete]);
// Keep the pre-start arena in sync with the Numbers slider.
useEffect(() => {
if (!isActive) {
setSlots(Array(numberCount).fill(null));
setAvailableNumbers(Array.from({ length: numberCount }, (_, i) => i + 1));
}
}, [numberCount, isActive]);
const isDivisible = (a: number, b: number) => {
const sum = a + b;
return sum % 3 === 0 || sum % 5 === 0 || sum % 7 === 0;
@ -110,12 +118,13 @@ const NeighborSumAvoidance = ({ shareUrl }: NeighborSumAvoidanceProps) => {
};
const handleDragStart = (num: number, fromSlot: number | null) => {
if (!isActive) return;
setDraggedNumber(num);
setDraggedFrom(fromSlot);
};
const handleDrop = (toSlot: number) => {
if (draggedNumber === null) return;
if (!isActive || draggedNumber === null) return;
if (mode === 'default') {
// Default mode: place number in slot
@ -140,6 +149,7 @@ const NeighborSumAvoidance = ({ shareUrl }: NeighborSumAvoidanceProps) => {
};
const handleSlotClick = (slotIndex: number) => {
if (!isActive) return;
if (mode === 'default') {
// Remove from slot in default mode
if (slots[slotIndex] !== null) {
@ -284,8 +294,9 @@ const NeighborSumAvoidance = ({ shareUrl }: NeighborSumAvoidanceProps) => {
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{!isActive ? (
<div className="space-y-6">
{/* Config controls — shown before start */}
{!isActive && (
<div className="space-y-4">
<div className="flex items-center justify-center gap-6">
<div className="flex items-center gap-4">
<Label htmlFor="mode-switch">Default Mode</Label>
@ -311,8 +322,7 @@ const NeighborSumAvoidance = ({ shareUrl }: NeighborSumAvoidanceProps) => {
</div>
</div>
<div className="space-y-4 text-center">
<div className="text-sm text-muted-foreground space-y-2">
<div className="text-sm text-muted-foreground space-y-2 text-center">
{mode === 'default' ? (
<>
<p>Drag numbers from below into the circle slots.</p>
@ -326,13 +336,11 @@ const NeighborSumAvoidance = ({ shareUrl }: NeighborSumAvoidanceProps) => {
</>
)}
</div>
<Button onClick={() => startGame(mode)} size="lg">
Start {mode === 'default' ? 'Default' : 'Guided'} Mode
</Button>
</div>
</div>
) : (
<div className="space-y-6">
)}
{/* Timer + controls — shown after start */}
{isActive && (
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Timer className="w-4 h-4" />
@ -348,10 +356,24 @@ const NeighborSumAvoidance = ({ shareUrl }: NeighborSumAvoidanceProps) => {
Restart
</Button>
</div>
)}
{/* Arena — always visible */}
<div className={!isActive ? 'opacity-60 pointer-events-none' : ''}>
{renderCircle()}
</div>
{mode === 'default' && availableNumbers.length > 0 && (
{/* Start button — shown before start */}
{!isActive && (
<div className="text-center">
<Button onClick={() => startGame(mode)} size="lg">
Start {mode === 'default' ? 'Default' : 'Guided'} Mode
</Button>
</div>
)}
{/* Available numbers for default mode */}
{isActive && mode === 'default' && availableNumbers.length > 0 && (
<div className="flex flex-wrap justify-center gap-4">
{availableNumbers.map(num => (
<div
@ -375,8 +397,6 @@ const NeighborSumAvoidance = ({ shareUrl }: NeighborSumAvoidanceProps) => {
<Button onClick={restart}>Play Again</Button>
</div>
)}
</div>
)}
</CardContent>
</Card>
</div>

View file

@ -333,8 +333,10 @@
{
"id": "474e8061-c7a2-49a2-ba85-a8c57814ea28",
"text": "in both default and guided mode, show the arena. to begin with all interactions should be disabled, and enabled as soon as the user clicks the start button (this should set off th etimer too, as it does now).",
"done": false,
"refId": "T1"
"done": true,
"refId": "T1",
"remark": "Arena now renders pre-start (greyed with opacity-60 pointer-events-none). Drag/drop/click handlers short-circuit on !isActive as defense-in-depth. Start button still triggers startGame (kicks off timer + populates slots). Added a useEffect to resync slots/availableNumbers when the Numbers slider changes before start, so the pre-start arena always matches the chosen count.",
"doneDate": "2026-04-17"
}
]
}