From 1c754de4f7a136c52b20625ff29768f9c8772b86 Mon Sep 17 00:00:00 2001 From: Neeldhara Misra Date: Fri, 17 Apr 2026 09:22:23 +0530 Subject: [PATCH] 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) --- .../interactives/NeighborSumAvoidance.tsx | 142 ++++++++++-------- src/data/admin-data.json | 6 +- 2 files changed, 85 insertions(+), 63 deletions(-) diff --git a/src/components/interactives/NeighborSumAvoidance.tsx b/src/components/interactives/NeighborSumAvoidance.tsx index 3689487..5e75c7a 100644 --- a/src/components/interactives/NeighborSumAvoidance.tsx +++ b/src/components/interactives/NeighborSumAvoidance.tsx @@ -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) => { - {!isActive ? ( -
+ {/* Config controls — shown before start */} + {!isActive && ( +
@@ -296,7 +307,7 @@ const NeighborSumAvoidance = ({ shareUrl }: NeighborSumAvoidanceProps) => { />
- +
{
-
-
- {mode === 'default' ? ( - <> -

Drag numbers from below into the circle slots.

-

Adjacent numbers that sum to a multiple of 3, 5, or 7 will show a red arc.

-

Click a filled slot to remove the number.

- - ) : ( - <> -

Numbers are connected by edges if their sum is NOT divisible by 3, 5, or 7.

-

Click two slots to swap their numbers and form a cycle on the circle's edge.

- - )} -
- +
+ {mode === 'default' ? ( + <> +

Drag numbers from below into the circle slots.

+

Adjacent numbers that sum to a multiple of 3, 5, or 7 will show a red arc.

+

Click a filled slot to remove the number.

+ + ) : ( + <> +

Numbers are connected by edges if their sum is NOT divisible by 3, 5, or 7.

+

Click two slots to swap their numbers and form a cycle on the circle's edge.

+ + )}
- ) : ( -
-
-
- - {formatTime(time)} -
- {mode === 'guided' && ( -
- Swaps: {swaps} -
- )} - + )} + + {/* Timer + controls — shown after start */} + {isActive && ( +
+
+ + {formatTime(time)}
- - {renderCircle()} - - {mode === 'default' && availableNumbers.length > 0 && ( -
- {availableNumbers.map(num => ( -
handleDragStart(num, null)} - className="w-12 h-12 rounded-full border-2 border-primary bg-background flex items-center justify-center text-lg font-bold cursor-move hover:scale-110 transition-transform" - > - {num} -
- ))} + {mode === 'guided' && ( +
+ Swaps: {swaps}
)} + +
+ )} - {isComplete && ( -
-

- 🎉 Congratulations! You solved it in {formatTime(time)} - {mode === 'guided' && ` with ${swaps} swaps`}! -

- + {/* Arena — always visible */} +
+ {renderCircle()} +
+ + {/* Start button — shown before start */} + {!isActive && ( +
+ +
+ )} + + {/* Available numbers for default mode */} + {isActive && mode === 'default' && availableNumbers.length > 0 && ( +
+ {availableNumbers.map(num => ( +
handleDragStart(num, null)} + className="w-12 h-12 rounded-full border-2 border-primary bg-background flex items-center justify-center text-lg font-bold cursor-move hover:scale-110 transition-transform" + > + {num}
- )} + ))} +
+ )} + + {isComplete && ( +
+

+ 🎉 Congratulations! You solved it in {formatTime(time)} + {mode === 'guided' && ` with ${swaps} swaps`}! +

+
)} diff --git a/src/data/admin-data.json b/src/data/admin-data.json index 4875a14..6c50267 100644 --- a/src/data/admin-data.json +++ b/src/data/admin-data.json @@ -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" } ] }