> Idea: An interactive n-body gravity sandbox: click and drag to fling glowing planets onto a dark starfield canvas, watch them orbit, slingshot, and merge on collision, each leaving a fading light trail. Controls to add a sun, clear, pause, and adjust gravity.

Build a single-file, zero-dependency, no-build interactive **n-body gravity sandbox** that runs entirely in the browser. The artifact is a polished, self-contained static web page: the user flings glowing planets onto a dark starfield with a click-and-drag gesture, then watches them orbit, slingshot, and merge on collision under real Newtonian gravity, with each body leaving a soft, fading light trail. A control panel lets the user drop a heavy sun, clear the field, pause/resume the simulation, and tune the gravitational constant in real time. Deliver something that feels alive, tactile, and beautiful on first load — a piece anyone would want to keep playing with.

## Goal
Create an HTML5 Canvas 2D physics toy that simulates the gravitational interaction of an arbitrary number of point-mass bodies ("planets") in a 2D plane. The defining experience is the **fling-to-launch** interaction: the user presses the pointer down on empty space, drags to aim and set speed, and releases to spawn a planet with a velocity proportional to the drag vector. From that moment the body is fully under the simulation's control — it accelerates toward every other body according to Newton's law of universal gravitation, traces a glowing orbital path, and either settles into a stable orbit, gets slingshotted away, or collides and merges with another body (conserving mass and momentum). The page must be immediately legible and inviting: a dark, deep-space aesthetic; a subtle parallax starfield behind the action; smooth 60fps motion; and a compact, modern control surface that never gets in the way of the canvas.

The simulation is the product. It must be physically plausible (orbits should actually close, slingshots should actually accelerate a flyby, merges should conserve momentum so the combined body keeps moving sensibly) while remaining a *playful sandbox* rather than a rigorous scientific instrument — favor stability, beauty, and responsiveness over numerical perfection. The result should run flawlessly offline, with zero console errors, zero external network requests, and zero build step. Opening the file directly (`file://`) must work exactly as well as serving it.

Treat this as a flagship interactive showcase. The bar is "I'd pin this to my bookmarks and show my friends," not "it technically computes gravity."

## Tech stack
- **Pure static front end. No build step, no bundler, no transpile.** Author plain, modern ES2020+ JavaScript that runs natively in evergreen browsers (Chrome, Firefox, Safari, Edge).
- **HTML5 Canvas 2D** for all rendering (starfield, bodies, glows, trails, aim indicator, collision flashes). Do not use WebGL — Canvas 2D is sufficient and keeps the file dependency-free and universally compatible.
- **Vanilla JavaScript only.** No React, Vue, Svelte, jQuery, or any runtime library. No physics engine (matter.js, planck, etc.) — hand-write the integrator. No CDN scripts, no web fonts, no external images, no analytics, no service workers.
- **System font stack** for all UI text: `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif`. Never load a webfont.
- **CSS:** authored as a stylesheet (an external same-origin `./styles.css` or an inline `<style>` block are both acceptable; if you split it, keep paths relative). Use CSS custom properties for the palette, modern fl/grid layout, and `backdrop-filter` for the glassy control panel where supported (with a solid fallback).
- **Structure the deliverable as separable files** so it reads like real engineering, not a code dump: `index.html` (markup + `<style>` or a `<link>` to `./styles.css`), `./main.js` (ALL JavaScript), and optional `./styles.css`. Link the script with `<script src="./main.js" defer></script>`. **No inline `<script>` blocks. No inline event-handler attributes (`onclick=`, `onload=`, `onmousedown=`, etc.) — wire every event with `addEventListener` from `./main.js`.** No `eval`, no `new Function`, no `setTimeout("string")`. This page is served under a strict Content-Security-Policy that forbids inline and remote JavaScript; it must run cleanly under it.
- **State management:** a single plain module-scoped state object plus a `Body` class. No global leakage beyond one namespaced object if needed. Use `requestAnimationFrame` for the loop and `performance.now()` for timing.
- **Persistence:** persist user-tunable settings (gravity strength, trail length, paused state, whether the starfield is shown) to `localStorage` under a single namespaced key (e.g. `orbital.settings.v1`) and restore them on load. Wrap all `localStorage` access in try/catch so private-mode or disabled-storage browsers degrade gracefully to defaults.
- **No network:** the page must make zero `fetch`/XHR/WebSocket calls to any origin. Everything ships in the files.

Justify implicitly through the code's clarity: a small, readable simulation core (vector math, gravity accumulation, integration, collision detection/merge), a rendering layer, an input layer, and a UI/controls layer, each cleanly separated.

## File layout
Produce these files, using **relative** paths throughout so the app works whether served from a domain root, a sub-path, or a subdomain:

- `index.html` — semantic markup only. Contains: a full-bleed `<canvas id="sky">`, a control panel (`<aside>` or `<section>`) with all buttons/sliders, a collapsible "how to play" hint, a small stats readout (body count, FPS), and a footer. Links the stylesheet (or inlines `<style>`) and loads `<script src="./main.js" defer></script>` at the end of `<body>`. No inline JS anywhere.
- `./main.js` — the entire application: constants/config, the `Vec`/math helpers, the `Body` class, the `Simulation` (or equivalent state + step function), the renderer, the input/gesture handling, the controls wiring, the persistence layer, and the bootstrap that starts the loop on `DOMContentLoaded`/module load. Heavily but tastefully commented so the file is readable top-to-bottom.
- `./styles.css` — *optional*; if you split styles out of `index.html`, put them here and `<link rel="stylesheet" href="./styles.css">`. If you keep styles inline, omit this file. Either choice is fine; do not load CSS from a CDN.

Recommended internal module boundaries inside `./main.js` (sections, not separate files):
1. **Config & palette constants** — gravitational constant default, softening epsilon, merge density, trail decay, star count, color ramps, slider ranges.
2. **Math** — `Vec` operations or inline float math (add, sub, scale, len, normalize, distance squared).
3. **Body model** — position, velocity, mass, radius (derived from mass), color/hue, trail ring buffer, glow params.
4. **Physics step** — pairwise gravity accumulation with softening, semi-implicit Euler (or velocity-Verlet) integration, sub-stepping for stability at high speed, collision detection and momentum-conserving merge.
5. **Renderer** — clear/fade pass, starfield (with subtle twinkle/parallax), trails, body glows, the live aim arrow during a drag, collision flash particles, optional velocity vectors (debug toggle).
6. **Input** — pointer down/move/up (and touch) for fling-to-launch; the aim/preview; click-to-add-sun targeting; keyboard shortcuts.
7. **UI/controls** — buttons (Add Sun, Clear, Pause/Play, Reset), sliders (Gravity, optionally Trail length and Star density), live stat updates, settings persistence, the hint panel toggle.
8. **Bootstrap** — DPR-aware canvas sizing, resize handling, seed a tasteful starting scene, kick off `requestAnimationFrame`.

## Behavior
Implement the following behaviors precisely. Where a number is given, treat it as a sensible default the user can usually feel; tune for beauty and stability.

**Fling-to-launch (primary interaction).**
- On pointer-down over empty canvas (not over the control panel), begin an "aiming" gesture: record the press point as the spawn position and show a live preview of the planet to be created at that point.
- As the pointer moves, draw an aim arrow from the spawn point toward the current pointer. The launch velocity is proportional to the drag vector (`velocity = (pressPoint - currentPointer) * launchScale`) so that dragging *away* from where you want it to go and releasing "slingshots" it like a slingshot — OR, if you prefer the more intuitive "drag in the direction of travel" model, make velocity = `(currentPointer - pressPoint) * launchScale`. **Pick one, make it feel great, and show the resulting trajectory direction clearly with the arrow.** Show a faint predicted-direction ghost.
- The longer the drag, the faster the launch, up to a sensible cap so a body can't be launched at an unrenderable speed. Display the speed subtly (e.g. arrow length / color intensity).
- On pointer-up, spawn a `Body` at the spawn point with the computed velocity, a randomized pleasing hue, and a mass/radius for a "planet" (smaller than a sun). The new body immediately participates in the simulation.
- A pure click with no drag (below a few px threshold) drops a stationary small planet at that point.
- Support touch: the same gesture must work with a single finger on mobile, using Pointer Events (preferred) or touch events. Call `preventDefault` appropriately so the page doesn't scroll/zoom while flinging. Make the canvas `touch-action: none`.

**Gravity & motion (the simulation core).**
- Every body attracts every other body by Newton's law: `F = G * m1 * m2 / r²`, applied as acceleration `a = G * m_other / r²` toward the other body, summed over all others each step. Use a **softening term** (`r² + epsilon²`) to prevent the force singularity as bodies get very close — this keeps the sim stable and avoids NaN slingshots to infinity.
- Integrate with **semi-implicit (symplectic) Euler** or **velocity-Verlet** for good energy behavior so orbits don't rapidly spiral out. Use a fixed timestep with an accumulator, and **sub-step** (e.g. 2–8 substeps per frame) when bodies are moving fast or close, so fast flybys and tight orbits stay stable at 60fps and don't tunnel through each other.
- Mass determines radius via a constant-density relation (radius ∝ mass^(1/2) for a 2D disk, or mass^(1/3) for a 3D-looking sphere — pick what looks best and stay consistent). Suns are far more massive (and larger) than flung planets.
- The default gravitational constant should produce visibly curved paths and graceful orbits within a few seconds of flinging a planet near a sun — not so weak that nothing happens, not so strong that everything instantly collapses.

**Collisions & merging.**
- Detect collision when the distance between two bodies is less than the sum of their radii (or a slightly smaller fraction for a satisfying overlap).
- On collision, **merge** the two bodies into one: combined mass = m1 + m2; new velocity = momentum-conserving `(m1*v1 + m2*v2)/(m1+m2)`; new position = mass-weighted centroid; new radius from combined mass; blended color (mass-weighted hue). The larger body's identity/hue should dominate.
- Trigger a brief, pretty **collision flash**: an expanding ring and a handful of short-lived spark particles at the impact point, tinted by the merging bodies' colors. Keep it cheap (particles are decorative, not physical).
- Never produce NaN/Infinity. Clamp or skip degenerate cases (zero distance, zero mass). If a body's values become non-finite, remove it safely.

**Light trails.**
- Each body leaves a soft, fading trail of its recent path. Implement trails as either (a) a per-body ring buffer of recent positions drawn as a fading, tapering, additively-blended stroke, or (b) a global "fade the whole canvas slightly each frame instead of fully clearing" motion-blur trail, or a hybrid. Trails must fade smoothly to nothing, be tinted to each body's hue, and glow.
- Trail length/persistence should be pleasant by default and ideally adjustable via a slider. Trails must not accumulate unbounded memory — cap the ring buffer length.

**Starfield background.**
- Render a dark deep-space background (near-black with a faint vertical gradient / subtle nebula tint). Scatter a few hundred stars of varied size and brightness with a gentle twinkle (slow alpha oscillation) and optional very subtle parallax drift so the field feels deep without distracting. The starfield is purely decorative and must be cheap to draw.

**Controls (must all work).**
- **Add Sun** — spawns a large, bright, high-mass star-colored body. Default placement at canvas center (or at the last pointer location). Optionally, after pressing it, the next click places the sun — if you do that, make the targeting state obvious and cancelable with Esc. Suns glow strongly and can have a subtle corona/pulse. The user may add multiple suns to make binary systems.
- **Clear** — removes all bodies and trails and resets to an empty starfield. Confirm only if there are many bodies, or just clear instantly with a quick fade — your call, but make it feel intentional, not jarring.
- **Pause / Play** — freezes/resumes the physics while keeping the render alive (so the scene stays visible and the user can still aim/inspect). The button label/icon reflects state. Spacebar toggles it.
- **Gravity slider** — adjusts the gravitational constant `G` live across a range from "drifting/weak" to "strong/collapsing." Changing it takes effect immediately on the running simulation. Show the current value. Persist it.
- Provide a sensible **Reset** (restore the default seeded scene) distinct from Clear (empty field), or fold one into the other clearly.
- Optional but encouraged extra controls that elevate the toy: **Trail length** slider, **Star density** toggle, a **velocity-vectors / debug** toggle, and a **"Solar system" preset** button that seeds a central sun with a few planets already in stable circular orbits (compute each planet's orbital speed as `sqrt(G*M/r)` perpendicular to the radius so they actually orbit cleanly — this is a great instant "wow").

**Keyboard shortcuts.** Wire these via `addEventListener`:
- `Space` — pause/play.
- `S` — add sun.
- `C` — clear.
- `R` — reset to seeded scene.
- `H` or `?` — toggle the how-to-play hint.
- `Esc` — cancel any pending placement/aim.
Show the shortcut list in the hint panel.

**Performance & lifecycle.**
- Target a smooth 60fps with dozens of bodies. The naive O(n²) pairwise loop is acceptable for the expected body counts (tens, low hundreds) — keep it tight (cache positions, avoid allocations in the hot loop, use squared distances where possible). If body count gets large, the sim should degrade gracefully (slow down, not crash); optionally soft-cap the body count with a gentle notice.
- Handle window resize and device-pixel-ratio: size the canvas backing store to `cssWidth * devicePixelRatio` and scale the context so rendering is crisp on retina. Reposition/clamp nothing destructively on resize — just keep the sim running.
- Pause the `requestAnimationFrame` work when the tab is hidden (`document.hidden` / `visibilitychange`) to save CPU, and resume cleanly without a giant time-jump (clamp `dt`).
- Never throw. Guard against empty-scene, single-body, and all-merged-into-one states.

**Footer / attribution.**
- Include a small, tasteful footer link reading exactly **"Built with a one-shot prompt from 1ShotGen"** linking to `https://1shotgen.com` (open in a new tab, `rel="noopener noreferrer"`). It must be unobtrusive — low-contrast, small, fixed or pinned to a corner — and must not cover the canvas interaction area or steal pointer events from the sandbox.

## UX & visual design
Deliver a **deep-space, dark, luminous** aesthetic. The canvas is the hero; UI is a quiet glass overlay.

**Color palette (use these hex values as the basis; refine for harmony):**
- Background void: `#05060a` deepening to `#0a0d18` (subtle radial/vertical gradient), with an optional faint nebula tint `#10162e`.
- Panel glass: `rgba(14, 18, 32, 0.66)` with `backdrop-filter: blur(14px)` and a hairline border `rgba(255,255,255,0.08)`.
- Primary accent / interactive: an electric cyan-violet — cyan `#54e0ff`, violet `#a98bff`; use a gradient between them for sliders, focus rings, and the active state of buttons.
- Text: primary `#e8ecf6`, muted `#9aa3bd`, faint `#5b6480`.
- Suns: warm core `#fff3c4` → `#ffb347` → glow `#ff8a3d`.
- Planets: pull hues across a tasteful range (cyans, violets, teals, warm corals) at high saturation but not garish; bodies glow with a soft radial gradient from a bright core to a transparent edge.
- Collision spark: white-hot core fading to the merged bodies' tints.
- Trails: each body's own hue at additive low alpha.

**Typography & spacing.**
- System font stack only. Use a compact type scale: panel title ~14–15px semibold with slight letter-spacing and uppercase for section labels; control labels ~12–13px; the stats readout ~11–12px monospace-ish (use the system mono stack `ui-monospace, SFMono-Regular, Menlo, Consolas, monospace` for numbers so they don't jitter).
- Spacing scale based on 4px units (4/8/12/16/20/24). Generous padding inside the panel; comfortable hit targets (≥36px) for buttons and slider thumbs.

**Control panel.**
- A single floating glass panel, pinned to a corner (top-left or top-right), with a subtle drop shadow and rounded corners (~16px). It should feel like a HUD. On small screens it collapses into a compact bar or a slide-up sheet so it never eats the canvas. Include a tiny collapse/expand affordance.
- Buttons: pill or rounded-rect, glassy, with a clear hover (lift + accent glow), active/pressed, and focus-visible state (accent ring) for keyboard accessibility. Pause/Play and the toggles should clearly show their on/off state.
- Sliders: custom-styled range inputs with a gradient-filled track, a glowing thumb, and a live numeric value beside the label. They must be keyboard-operable and show focus.
- A small **stats** cluster: live body count and FPS (smoothed), updated each frame without layout thrash.
- A collapsible **"How to play"** hint: one or two lines ("Drag on the canvas to fling a planet. Add a sun and watch them orbit.") plus the keyboard shortcuts. Dismissible, and its state persists.

**Motion & feel.**
- Everything that can move should ease, not snap: panel collapse, hint reveal, button hovers (120–200ms, ease-out). Respect `prefers-reduced-motion` by toning down UI transitions and twinkle (never the core physics).
- The aim arrow during a fling should feel springy and precise, with a clear tip and a length/brightness that reads as "power."
- Bodies should have a gentle glow and a faint specular highlight so they read as luminous spheres, not flat dots. Suns pulse very subtly.
- The whole thing should feel calm and cosmic at rest, and exciting when bodies whip past each other.

**Accessibility & robustness.**
- All interactive controls reachable and operable by keyboard; visible focus states; `aria-label`s on icon-only buttons; the canvas has a descriptive `aria-label` and the page a sensible `<title>` ("Orbital — n-body gravity sandbox" or similar).
- Sufficient contrast for all readable text against the glass.
- Works at any viewport from ~320px wide phones to large desktops. No horizontal scroll. Canvas always full-bleed behind the HUD.
- Zero console errors or warnings on load and during normal play. No layout shift after load.

## Definition of done
The artifact is complete when ALL of the following are true:

- [ ] Opening `index.html` directly in a modern browser (including via `file://`) renders a dark starfield with a tasteful seeded opening scene (e.g. a central sun with one or two planets already orbiting, or a calm empty field with a clear prompt to start flinging) — **with zero console errors or warnings**.
- [ ] **No inline JavaScript exists:** no `<script>` with body content, no `on*=` inline handler attributes anywhere; all behavior is wired from `./main.js` via `addEventListener`. The page runs correctly under a strict CSP that forbids inline and remote scripts. No `eval`/`new Function`.
- [ ] **No external resources or network calls:** no CDNs, web fonts, external images, analytics, or `fetch`/XHR/WebSocket to any origin. The app is fully self-contained and works offline. All asset paths are relative (`./main.js`, `./styles.css`).
- [ ] **Fling-to-launch works** with mouse and with touch: press, drag to aim (with a live arrow + planet preview + readable power), release to spawn a planet whose velocity matches the drag. A tiny drag / click drops a stationary planet.
- [ ] **Gravity is real:** bodies attract each other via `F = G·m₁·m₂/r²` with softening; integration is symplectic/Verlet with sub-stepping; flung planets visibly curve, orbit, and slingshot; orbits are stable enough to watch for a while without exploding to infinity or collapsing instantly.
- [ ] **Collisions merge** bodies conserving mass and momentum (combined mass, momentum-weighted velocity, centroid position, blended hue, recomputed radius), each merge accompanied by a brief, pretty collision flash. No NaN/Infinity ever appears; degenerate cases are handled.
- [ ] **Light trails** render per body, tinted to its hue, glowing, and fading smoothly to nothing, with bounded memory (capped ring buffer or canvas-fade approach).
- [ ] **Add Sun** spawns a large bright high-mass star that the planets orbit; multiple suns can coexist (binary systems possible).
- [ ] **Clear** empties the field cleanly; **Pause/Play** freezes/resumes physics while keeping the scene rendered and aimable; **Reset** restores the seeded scene.
- [ ] **Gravity slider** changes `G` live with a visible value and an immediately felt effect; its value (and other settings) persist across reloads via `localStorage`, degrading to defaults if storage is unavailable.
- [ ] **Keyboard shortcuts** work: Space (pause), S (sun), C (clear), R (reset), H/? (hint), Esc (cancel placement/aim).
- [ ] **Responsive & crisp:** canvas is DPR-aware and sharp on retina; the layout works from 320px phones to large desktops with no horizontal scroll; the control panel never blocks the play area and collapses gracefully on small screens; window resize keeps the sim running.
- [ ] **Performance:** smooth ~60fps with dozens of bodies; no per-frame allocations in the hot loop causing GC stutter; work pauses when the tab is hidden and resumes without a time-jump; FPS and body count shown live.
- [ ] **Polish:** glassy HUD with hover/active/focus-visible states, gradient sliders with glowing thumbs, eased microinteractions, `prefers-reduced-motion` respected, luminous glowing bodies, subtly pulsing suns, and a twinkling parallax starfield.
- [ ] A small, tasteful footer reads exactly **"Built with a one-shot prompt from 1ShotGen"** and links to `https://1shotgen.com` (`target="_blank" rel="noopener noreferrer"`), unobtrusive and not stealing canvas pointer events.
- [ ] Code is cleanly organized into the documented sections (config, math, body, physics, render, input, controls, bootstrap), readably commented, with no dead code, no `console.log` spam, no placeholders, and no TODOs left in the shipped files.
- [ ] The experience is genuinely delightful: it invites play, looks beautiful at rest and in motion, and behaves correctly across mouse, touch, keyboard, resize, pause, clear, and reset without a single error.
