# Interactive Divs with Physics — floating grayscale cards that scatter from the cursor

## Goal

Build a full-screen, black, zero-gravity playground where **twelve grayscale polaroid-style image cards drift over the whole viewport**. Each card is a real HTML `<div>` whose position and rotation are driven, every frame, by a Matter.js rigid body living in a **gravity-free** physics world. A big centered word sits behind them. The star effect: **moving the mouse near a card fires a random impulse into it**, so nearby cards shoot away, slowly coast to a stop under air friction, gently bounce off the invisible viewport walls, and keep floating. It reads like a slow-motion swarm of photo prints you can bat around with the cursor.

## Tech

Vanilla HTML/CSS/JS. Two libraries with **two different loading mechanisms** (match this exactly — the sketch depends on p5 running in "global mode"):

1. **p5.js v1.4.0 as a classic CDN `<script>` in `<head>`** (NOT an npm import). This runs p5 in global mode: p5 auto-initializes on window load and attaches globals like `createCanvas`, `random`, `dist`, `background`, `width`, `height`, `mouseX`, `mouseY`, and calls your global `setup()` / `draw()` / `mouseMoved()` functions.
   ```html
   <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
   ```
2. **matter-js as an npm ESM import** in your module script:
   ```js
   import Matter from "matter-js";
   const { Engine, World, Bodies, Body } = Matter;
   ```

No GSAP, no ScrollTrigger, no Lenis, no framework. The page does not scroll.

**Global-mode bridge (critical):** because your code is an ES module, `setup`/`draw`/`mouseMoved` are module-scoped and p5 can't see them. At the very bottom of the module, expose them on `window`:
```js
window.setup = setup;
window.draw = draw;
window.mouseMoved = mouseMoved;
```

## Layout / HTML

Minimal. p5 injects its own `<canvas>` and the JS creates the card divs at runtime.

```html
<body>
  <div class="header"><h1>Motionprompts</h1></div>
  <script type="module" src="./script.js"></script>
</body>
```

- One `.header` with an `<h1>` (fictional brand text `Motionprompts`), absolutely centered — it sits *behind* the floating cards (the p5 canvas and the `.item` divs are appended to `<body>` after it and paint on top).
- No card markup in HTML: the twelve `<div class="item">` elements (each containing one `<img>`) are created in JS and appended to `document.body`.

## Styling

Reset and full-bleed stage:
- `* { margin:0; padding:0; box-sizing:border-box; }`
- `html, body { width:100vw; height:100vh; overflow:hidden; background:#000; }` — the page is exactly one viewport, no scroll, pure black behind everything.

Header (centered background word):
- `.header { position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); }`
- `.header h1 { font-family:"Circular Std", sans-serif; font-size:12vw; font-weight:500; letter-spacing:-0.05em; line-height:175%; color:#fff; text-align:center; }` — a very large, tight, geometric-sans word in white. Any clean geometric sans works as fallback.

The cards (`.item` — a white polaroid frame):
- `.item { position:absolute; width:200px; height:225px; padding:0.5em 0.5em 4em 0.5em; overflow:hidden; background:#fff; }` — a **200×225px white card** with an asymmetric pad (thin on top/sides, `4em` thick at the **bottom**) so the photo sits in the upper area and leaves a polaroid-style white chin.
- `.item img { width:100%; height:100%; object-fit:cover; filter:grayscale(100%); }` — the photo fills the padded content box, cropped, and rendered **fully grayscale**.

JS sets each card's inline `left`, `top`, and `transform: rotate(<rad>)` every frame (see below).

## Physics + render effect (be exhaustive — this is the whole component)

### Engine & world

```js
let engine = Engine.create();
engine.world.gravity.y = 0;   // ZERO gravity — cards float, they never fall
```

Keep module-level state: `engine`, `items = []`, `lastMouseX = -1`, `lastMouseY = -1`.

### `setup()` (runs once)

1. `createCanvas(window.innerWidth, window.innerHeight);` — a p5 canvas the full size of the window.
2. `engine = Engine.create();` then `engine.world.gravity.y = 0;`.
3. `addBoundaries();` (below).
4. Spawn **12 cards** in a loop `for (let i = 0; i < 12; i++)`:
   - `x = random(100, width - 100)`, `y = random(100, height - 100)` — random start inside a 100px inset.
   - `items.push(new Item(x, y, "img" + (i + 1) + ".jpg"));` — image sources `img1.jpg … img12.jpg`.

### `addBoundaries()` — four invisible static walls

Four static rectangles of `thickness = 50`, positioned so their inner edge lines up with the viewport edges (bodies sit just *outside* the visible area). Add all four with `World.add(engine.world, [...])`:
- **Top:** `Bodies.rectangle(width/2, -thickness/2, width, thickness, { isStatic:true })`
- **Bottom:** `Bodies.rectangle(width/2, height + thickness/2, width, thickness, { isStatic:true })`
- **Left:** `Bodies.rectangle(-thickness/2, height/2, thickness, height, { isStatic:true })`
- **Right:** `Bodies.rectangle(width + thickness/2, height/2, thickness, height, { isStatic:true })`

These keep the cards contained; cards bounce off them (see restitution).

### `Item` class

**Constructor `(x, y, imagePath)`:**
- Physics body options: `{ frictionAir: 0.075, restitution: 0.25, density: 0.002, angle: Math.random() * Math.PI * 2 }`.
  - `frictionAir: 0.075` — heavy air drag, so impulses decay to a stop within ~1s (the slow, floaty coast).
  - `restitution: 0.25` — soft, low-energy bounces off walls and each other.
  - `density: 0.002` — light bodies (mass = density × area = 0.002 × 100 × 200 = 40) so the impulse below produces a big, visible kick.
  - `angle` — each card starts at a **random rotation** (0–2π).
- Body: `this.body = Bodies.rectangle(x, y, 100, 200, options);` — a **100×200 rectangle body** (note: smaller than the 200×225 visual card; this is intentional/original). `World.add(engine.world, this.body);`.
- DOM: create `<div class="item">`, position it (see offset math), create an `<img>` with `src = imagePath`, append the img to the div, append the div to `document.body`.

**Position offset (used in constructor and every frame in `update()`):**
```js
this.div.style.left = (this.body.position.x - 50) + "px";
this.div.style.top  = (this.body.position.y - 100) + "px";
```
The div's top-left is offset **−50px x / −100px y** from the body center (half of the 100×200 *body* size, not the card size — keep these exact numbers).

**`update()` (called every frame):**
```js
this.div.style.left = (this.body.position.x - 50) + "px";
this.div.style.top  = (this.body.position.y - 100) + "px";
this.div.style.transform = "rotate(" + this.body.angle + "rad)";
```
So each card's screen position and rotation mirror its physics body exactly, in radians.

### `draw()` — the p5 render loop (~60fps)

```js
function draw() {
  background("black");        // repaint canvas black each frame
  Engine.update(engine);      // step the physics simulation one tick
  items.forEach((item) => item.update());  // sync every card div to its body
}
```
The canvas itself only ever shows solid black; all the visible motion is the DOM `.item` divs being repositioned on top of it. There is no `noLoop()` — the loop runs continuously so bodies keep integrating (drifting, decaying, settling) even without input.

### `mouseMoved()` — the interaction (the star moment)

Throttle by how far the mouse has moved, then push nearby cards:
```js
function mouseMoved() {
  if (dist(mouseX, mouseY, lastMouseX, lastMouseY) > 10) {
    lastMouseX = mouseX;
    items.forEach((item) => {
      if (dist(mouseX, mouseY, item.body.position.x, item.body.position.y) < 150) {
        const forceMagnitude = 3;
        Body.applyForce(
          item.body,
          { x: item.body.position.x, y: item.body.position.y },   // apply at the body's own center → pure linear push, no torque
          { x: random(-forceMagnitude, forceMagnitude),
            y: random(-forceMagnitude, forceMagnitude) }           // random 2D impulse in [-3, 3] on each axis
        );
      }
    });
  }
}
```
Behavior to reproduce:
- Only reacts once the cursor has traveled **>10px** since the last sampled point (`lastMouseX/lastMouseY` seeded at `-1`, so it fires as soon as the mouse enters the page).
- For **every card whose body center is within 150px of the cursor**, apply a **random force vector**, each component uniformly in `[-3, 3]`, at the card's own center of mass (so it's a straight shove, no spin from the force — any rotation you see is incidental collision response).
- With `frictionAir 0.075` the shoved cards fan out fast, then glide to rest; `restitution 0.25` gives them a gentle bounce if they reach a wall or clip a neighbor.

## Assets / images

**12 editorial photographs**, `img1.jpg … img12.jpg`, one per card. Each is displayed cropped (`object-fit:cover`) into the polaroid's ~184×153px content window and forced to **grayscale** by CSS, so any source palette is fine. Aim for a cohesive, moody, cinematic/editorial set — e.g. atmospheric portraits, sci-fi/astronaut scenes, minimalist product shots, monochrome cityscapes — anything with strong tonal contrast reads well once desaturated. No logos or real brand marks. (If fewer than 12 images are available, repeat them in order.)

## Behavior notes

- **Desktop / pointer-driven, page-level component.** The whole viewport is the stage; nothing scrolls (`overflow:hidden`, page is exactly `100vw × 100vh`). Not mobile-safe (relies on `mousemove`).
- **Continuous simulation, no autoplay animation:** cards start at random positions and rotations and simply sit still (zero gravity, no initial velocity) until the cursor disturbs them; then they scatter and re-settle. Cards can overlap and gently jostle via collisions.
- Canvas is sized to the window at load; there is no resize handler in the original (bodies/walls keep their initial dimensions if the window is resized).
- No reduced-motion handling and no console errors expected. Heavy-ish (12 DOM nodes synced to a 60fps physics loop) but smooth on desktop.
