particle field planning
All checks were successful
deploy / build-and-deploy (push) Successful in 31s

This commit is contained in:
Josh Bairstow 2026-07-23 23:14:04 +10:00
parent ae60a7c856
commit f4b8e1a7d8
5 changed files with 1685 additions and 0 deletions

View file

@ -0,0 +1,119 @@
# Particle Field + Greebling Restructure
**Date:** 2026-06-03
**Status:** Approved
## Context
The site currently uses fixed-position SVG accent marks (`Mark.astro`) placed at hardcoded viewport percentages. They feel scattered rather than connected to anything structural. Alongside this, a new ambient background visual — a floating particle field — is to be introduced to add depth and visual interest.
The two changes are complementary: both join the same atmospheric layer of the site. The marks become structurally grounded; the particle field adds a new living layer beneath everything.
---
## Part 1: ParticleField Component
### What it does
A full-viewport `<canvas>` rendered as a fixed background layer below the content stage. Particles drift slowly across the canvas, connected by faint lines when within reach. An underlying Perlin/fBm noise field modulates opacity and glow per-particle, producing an organic pulsing quality. The cursor brightens nearby lines.
### Layer position
```
.stage (relative, z-index: 1) ← content, unchanged
ParticleField canvas (fixed, z-index: 0, pointer-events: none) ← new
body::before grain (fixed, z-index: 0) ← unchanged
```
The canvas sits below the stage (`z:0` vs `z:1`) and carries `pointer-events: none`. No existing display or interactive component is affected.
### New file
`src/components/islands/ParticleField.tsx`
- Canvas: `position: fixed; inset: 0; width: 100vw; height: 100vh; pointer-events: none; z-index: 0`
- No background fill — canvas is transparent; `#17130E` page ground shows through
- Perlin noise engine ported directly from `reference/particle-field-demo/particleNetwork.js`
- All tunable values in a single `PARTICLE_CONFIG` object at the top of the file — nothing buried in render logic
- `prefers-reduced-motion`: animation loop pauses, canvas renders one static frame
- Lifecycle: `useEffect` mount/unmount, resize observer to keep canvas dimensions in sync
### Integration
Added to `src/layouts/BaseLayout.astro` as:
```astro
<ParticleField client:idle />
```
Placed in the DOM **before** `.stage` so stacking order is correct without needing a higher z-index on the stage.
### Colour palette
Warm neutral register, matching `--fg-on-dark-2` / `--fg-on-dark-3` range — embers, not highlights.
| Token | Value | Replaces (reference cool blue) |
|---|---|---|
| Particle fill | `rgb(200, 178, 148)` | `rgb(180, 200, 255)` |
| Connecting lines | `174, 140, 100` | `80, 160, 240` |
| Mouse lines | `210, 185, 150` | `160, 220, 255` |
| Glow inner | `200, 170, 130` | `180, 220, 255` |
| Glow mid | `155, 120, 82` | `100, 170, 255` |
Starting config values will be tuned down from the reference defaults (fewer particles, lower line alpha) for a content site context. All values live in `PARTICLE_CONFIG` for immediate visual iteration.
---
## Part 2: Mark Restructure
### What changes
`Mark.astro` switches from `position: fixed` to `position: absolute`. All existing props (`kind`, `width`, `top`, `left`, `right`, `bottom`, `rotate`) are unchanged — only the positioning context shifts from viewport to nearest `position: relative` ancestor.
The `<slot name="marks">` in `BaseLayout.astro` is cleared of its defaults. Marks migrate to live inside the specific section containers they visually belong near.
### Per-section anchoring
Each host section gets `position: relative` added. Marks use small offsets (including negative values) to sit at or just outside the section's visible edge. Absolutely-positioned elements are out of flow, so section content and layout are unaffected.
Example (home page):
| Mark | Section | Edge |
|---|---|---|
| `ringdots` 84px | `.intro` | top-left, slight outward offset |
| `dots` 52px | `.hero` | top-right edge |
| `pipes` 28px | `.hero` | bottom-left edge |
| `concentric` 42px | `.index` | bottom-right edge |
The same anchoring logic applies to `404.astro`, `blog/index.astro`, `code/index.astro`, `art/index.astro`, and `PostLayout.astro`.
### Future enhancement
The current placement is a hand-curated first pass. A future `generateMarks(sections)` utility could sample from available section anchors and mark types with seeded randomness, removing the fingerprints of manual placement and producing layouts that feel discovered rather than designed. This is intentionally deferred — visual inspection of the curated pass will inform what the generative version should optimise for.
---
## Files to modify
| File | Change |
|---|---|
| `src/components/atoms/Mark.astro` | `position: fixed``position: absolute` |
| `src/layouts/BaseLayout.astro` | Clear slot defaults; add `<ParticleField client:idle />` |
| `src/pages/index.astro` | Move marks into sections |
| `src/pages/404.astro` | Move marks into sections |
| `src/pages/blog/index.astro` | Move marks into sections |
| `src/pages/code/index.astro` | Move marks into sections |
| `src/pages/art/index.astro` | Move marks into sections |
| `src/layouts/PostLayout.astro` | Move marks into sections |
| `src/components/islands/ParticleField.tsx` | **New file** |
---
## Verification
1. Run `npm run dev` and inspect all pages — no visual regressions in nav, content, or islands
2. Confirm particle canvas renders below all content and does not intercept pointer events (click through the canvas onto links/buttons)
3. Confirm marks appear at section edges on each page, not scattered across the viewport
4. Toggle `prefers-reduced-motion: reduce` in devtools — canvas should freeze, not disappear
5. Resize viewport — canvas should fill; marks should reposition relative to their sections
6. Check dark/light theme toggle — mark filters and opacity should still apply correctly