Add particle effects / atmosphere panel design spec
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
33032bcd07
commit
646a16cd0f
1 changed files with 106 additions and 0 deletions
106
docs/superpowers/specs/2026-04-10-particle-effects-design.md
Normal file
106
docs/superpowers/specs/2026-04-10-particle-effects-design.md
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# Particle Effects / Atmosphere Panel
|
||||
|
||||
**Date:** 2026-04-10
|
||||
**Status:** Approved
|
||||
|
||||
## Summary
|
||||
|
||||
Replace the single fog toggle button in the campaign header with an atmosphere panel that supports four effects: fog (CSS, existing), fire, rain, and embers (tsParticles). Effects stack independently. Each effect has an on/off toggle and an intensity slider (0–100).
|
||||
|
||||
## Library
|
||||
|
||||
`@tsparticles/react` + `@tsparticles/slim`
|
||||
|
||||
- Slim engine includes all shapes and movers needed for fire, rain, embers
|
||||
- Intensity maps directly onto particle config params (count, speed)
|
||||
- FogOverlay stays as CSS — no library change for fog
|
||||
|
||||
## Components
|
||||
|
||||
### `AtmospherePanel.tsx` (new)
|
||||
|
||||
Button trigger in the campaign header, replacing the existing fog `<button>`. When one or more effects are active the button shows their icons (e.g. `🌫🔥`). Clicking opens a popover panel anchored below the button. Clicking outside closes it.
|
||||
|
||||
Panel contains one row per effect (Fog, Fire, Rain, Embers):
|
||||
- Label with emoji
|
||||
- Toggle switch (on/off)
|
||||
- Intensity slider (0–100), dimmed when effect is off
|
||||
|
||||
On any toggle or slider change, the component calls an `onAtmosphereChange(newAtmosphere)` callback. CampaignView owns the socket; its handler calls `setAtmosphere(next)` and `socket.emit("atmosphere:update", { campaignId, ...next })`. AtmospherePanel receives only `atmosphere` state and `onAtmosphereChange` as props — no direct socket access.
|
||||
|
||||
Fog intensity slider is rendered but has no mechanical effect in v1 (CSS fog doesn't have an intensity param). It will not be wired until the fog overlay supports it.
|
||||
|
||||
### `ParticleOverlay.tsx` (new)
|
||||
|
||||
Full-screen fixed canvas, `pointer-events: none`, z-index 9997 (just below FogOverlay at 9998). Receives the full `atmosphere` state. For each of fire, rain, embers: if `active === true`, renders a tsParticles instance with a config generated from `getXxxConfig(intensity)`. Each effect is a separate tsParticles instance so they layer independently.
|
||||
|
||||
### `particleConfigs.ts` (new)
|
||||
|
||||
Pure functions — no side effects, no imports from React. Each takes `intensity: number` (0–100) and returns a tsParticles `ISourceOptions` config.
|
||||
|
||||
| Effect | Count range | Speed range | Particle style |
|
||||
|--------|-------------|-------------|----------------|
|
||||
| Fire | 20–200 | 2–8 upward | Small circles, orange/red/yellow gradient, fade as they rise, emitter along bottom edge |
|
||||
| Rain | 50–500 | 5–20 downward | Thin elongated lines, ~70° angle, slight opacity, full-width emitter at top |
|
||||
| Embers | 5–80 | 0.2–1.5 drift | Small dots, warm amber, slow random drift, gentle fade-out |
|
||||
|
||||
### `FogOverlay.tsx` (unchanged)
|
||||
|
||||
Props unchanged: `active: boolean`. CampaignView passes `atmosphere.fog.active`.
|
||||
|
||||
## State
|
||||
|
||||
In `CampaignView.tsx`, replace `fogActive: boolean` with:
|
||||
|
||||
```typescript
|
||||
const [atmosphere, setAtmosphere] = useState({
|
||||
fog: { active: false, intensity: 50 },
|
||||
fire: { active: false, intensity: 50 },
|
||||
rain: { active: false, intensity: 50 },
|
||||
embers: { active: false, intensity: 50 },
|
||||
});
|
||||
```
|
||||
|
||||
## Socket
|
||||
|
||||
### Payload change
|
||||
|
||||
`atmosphere:update` payload changes from `{ fog: boolean }` to:
|
||||
|
||||
```typescript
|
||||
{
|
||||
fog: { active: boolean; intensity: number };
|
||||
fire: { active: boolean; intensity: number };
|
||||
rain: { active: boolean; intensity: number };
|
||||
embers: { active: boolean; intensity: number };
|
||||
}
|
||||
```
|
||||
|
||||
### Server (`server/src/socket.ts`)
|
||||
|
||||
Update the TypeScript type for the `atmosphere:update` handler to accept and rebroadcast the new payload shape. No persistence — server is a pure relay.
|
||||
|
||||
### Client (`CampaignView.tsx`)
|
||||
|
||||
The `atmosphere:update` listener replaces the entire `atmosphere` state with the received payload, keeping all clients in sync including intensity values.
|
||||
|
||||
## File Checklist
|
||||
|
||||
| File | Action |
|
||||
|------|--------|
|
||||
| `client/src/components/AtmospherePanel.tsx` | Create |
|
||||
| `client/src/components/AtmospherePanel.module.css` | Create |
|
||||
| `client/src/components/ParticleOverlay.tsx` | Create |
|
||||
| `client/src/lib/particleConfigs.ts` | Create |
|
||||
| `client/src/components/FogOverlay.tsx` | No change |
|
||||
| `client/src/components/FogOverlay.module.css` | No change |
|
||||
| `client/src/pages/CampaignView.tsx` | Update state, socket listener, JSX |
|
||||
| `server/src/socket.ts` | Update TypeScript type |
|
||||
| `client/package.json` | Add @tsparticles/react, @tsparticles/slim |
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- Fog intensity wiring (slider exists, does nothing in v1)
|
||||
- Moving atmosphere control to App-level header (deferred to DM view redesign)
|
||||
- Snow or other effects beyond fog/fire/rain/embers
|
||||
- Per-effect configuration beyond intensity (e.g. wind direction for rain)
|
||||
Loading…
Add table
Reference in a new issue