Expand GaugeGap into a four-experiment science arcade#88
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a new 'Science Arcade' feature (ExperimentArcade) containing four interactive simulations: the Butterfly Effect, Firefly Sync, Wave Eraser, and Living Chemistry. It adds the simulation logic, state management, and styling, while updating the landing page to integrate the new arcade. Key feedback includes addressing a potential TypeError when copying experiments via navigator.clipboard, fixing the Wave Eraser detector intensity to reflect live wave states instead of freezing on pointer stop, ensuring scrollToPlayground does not reference the removed playgroundRef, and adding -webkit-backdrop-filter prefixes in CSS for Safari compatibility.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| navigator.clipboard?.writeText(url.toString()) | ||
| .then(() => onNotice('Challenge copied. Send it to someone who thinks they can do better.')) | ||
| .catch(() => onNotice('Copy was blocked by the browser. Use Share challenge.')); |
There was a problem hiding this comment.
Using optional chaining navigator.clipboard?.writeText(...) followed by a .then() call will result in a TypeError: Cannot read properties of undefined (reading 'then') if navigator.clipboard is undefined (e.g., in non-secure HTTP contexts or older browsers). Check if navigator.clipboard?.writeText is available before calling it.
| navigator.clipboard?.writeText(url.toString()) | |
| .then(() => onNotice('Challenge copied. Send it to someone who thinks they can do better.')) | |
| .catch(() => onNotice('Copy was blocked by the browser. Use Share challenge.')); | |
| if (navigator.clipboard?.writeText) { | |
| navigator.clipboard.writeText(url.toString()) | |
| .then(() => onNotice('Challenge copied. Send it to someone who thinks they can do better.')) | |
| .catch(() => onNotice('Copy was blocked by the browser. Use Share challenge.')); | |
| } else { | |
| onNotice('Copy is not supported by your browser or in this context.'); | |
| } |
| const value = (Math.sin(k * Math.hypot(px - s1x, py - sy) - timeRef.current) | ||
| + Math.sin(k * Math.hypot(px - s2x, py - sy) - timeRef.current + active.phase)) * 0.5; |
There was a problem hiding this comment.
The detector's intensity is calculated using timeRef.current, which continuously changes in the animation loop. However, moveDetector is only triggered on pointer events. When the user stops moving the pointer, the displayed 'Detector energy' score freezes at a random instantaneous value rather than reflecting the live wave state. To fix this without expensive state updates in the render loop, we can calculate the time-independent wave amplitude (envelope) at the detector's position.
| const value = (Math.sin(k * Math.hypot(px - s1x, py - sy) - timeRef.current) | |
| + Math.sin(k * Math.hypot(px - s2x, py - sy) - timeRef.current + active.phase)) * 0.5; | |
| const r1 = Math.hypot(px - s1x, py - sy); | |
| const r2 = Math.hypot(px - s2x, py - sy); | |
| const value = Math.abs(Math.cos((k * (r1 - r2) - active.phase) * 0.5)); |
| const playgroundRef = useRef(null); | ||
| const ARCADE_IDS = new Set(['attractor', 'fireflies', 'waves', 'reaction']); | ||
|
|
||
| export function GaugeGapLanding({ onStart, onNavigate, onOpenReconstruct }) { |
There was a problem hiding this comment.
The playgroundRef has been removed from this component, but the scrollToPlayground function (which is called in multiple click handlers) likely still references it. This will cause a runtime error when those handlers are triggered. Please update scrollToPlayground to scroll to the new #playground element directly (e.g., using document.getElementById('playground')?.scrollIntoView(...)).
| border-radius: 8px; | ||
| color: rgba(220, 252, 255, 0.7); | ||
| background: rgba(2, 4, 10, 0.72); | ||
| backdrop-filter: blur(8px); |
| border-radius: 999px; | ||
| color: #edffb3; | ||
| background: rgba(4, 9, 12, 0.72); | ||
| backdrop-filter: blur(8px); |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 092cd9fedd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| setNotice('The interference map changed instantly. Hunt for a silent node.'); | ||
| } | ||
|
|
||
| const visibility = Math.round(Math.abs(detector.intensity) * 100); |
There was a problem hiding this comment.
Recompute Wave Eraser detector energy live
When the Wave Eraser opens or when its sliders/time change, detector.intensity stays at the last pointer event value (0 initially), so the score can report “Near-perfect silence” while the animated field under the crosshair has a different energy. This affects the default startup state and any stationary detector; recompute the detector intensity in the render loop or whenever params/time advance.
Useful? React with 👍 / 👎.
| } | ||
|
|
||
| const visibility = Math.round(Math.abs(detector.intensity) * 100); | ||
| const state = [params.wavelength, params.spacing, params.phase, params.speed].map((value) => round(value, 3)).join(','); |
There was a problem hiding this comment.
Include detector position in wave challenge state
When users share a Wave Eraser run after finding a quiet point, the URL only encodes the wave parameters and drops the detector coordinates, while a recipient reloads with the detector reset to the center. The copied/shared link therefore cannot reproduce the node that produced the advertised detector score/title, so include detector.x/detector.y in the state and restore them on load.
Useful? React with 👍 / 👎.
What this adds
Builds the next GaugeGap growth layer from the strongest patterns in interactive-science libraries: immediate motion, recognizable presets, one clear challenge, different interaction styles, and shareable run states.
Four playable labs
Arcade experience
Validation requested