# One-Shot Prompt

**Simulation**: Receipt Paper (preferred default)
**Theme**: Clean Minimal
**Name**: Tender
**Generated**: 2026-04-24

## Prompt

Generate a complete, self-contained single-file interactive 3D receipt paper physics simulation named "Tender". The simulation features a dense particle grid forming a receipt-shaped sheet that can be grabbed, folded, crumpled, and tossed — with printed receipt text that deforms with the surface.

### Technical Architecture

**Single HTML file** with inline CSS and JS. Import Three.js via CDN import map:

```html
<script type="importmap">
{
  "imports": {
    "three": "https://cdn.jsdelivr.net/npm/three@0.184.0/build/three.module.js"
  }
}
</script>
```

No other external dependencies. No external images, fonts, or models.

### Physics System

**Particle grid**: 40 columns × 60 rows (2400 particles), forming a receipt-proportioned sheet (roughly 4.8 × 7.2 world units wide). Particles stored as a flat array, each with `pos` (current position), `old` (previous position for Verlet), `pinned` flag, and `mass`.

**Pin constraints**: Pin the top two corners (indices 0 and 39) to fixed world-space positions, suspending the receipt from those points. The receipt hangs vertically under gravity with top edge near y=3 and bottom edge near y=-4.

**Constraint topology** (all using the reference distance-constraint solver from the one-shot physics techniques):

1. **Structural (horizontal)**: Connect every particle (i,j) to (i+1,j) at rest length = spacing (0.12 units). This maintains the grid shape along rows.
2. **Structural (vertical)**: Connect every particle (i,j) to (i,j+1) at rest length = spacing. This maintains the grid shape along columns.
3. **Shear (diagonals)**: Connect (i,j) to (i+1,j+1) and (i+1,j) to (i,j+1) at rest length = spacing × √2. This prevents the grid from collapsing into parallelograms.
4. **Bend (skip-one horizontal)**: Connect (i,j) to (i+2,j) at rest length = spacing × 2 for all i, j. This resists folding along the horizontal axis. Stiffness multiplier of 2.5× (halve the correction per particle pair so the effective stiffness is higher).
5. **Bend (skip-one vertical)**: Connect (i,j) to (i,j+2) at rest length = spacing × 2 for all i, j. Stiffness multiplier of 2.5×.

Total constraints: approximately 2400 (structural) + 4600 (shear) + 3500 (bend) ≈ 10,500 constraints.

**Verlet integration**:
- Fixed physics timestep: 1/120s
- 8 substeps per physics tick (resulting substep dt = 1/960s)
- 4 constraint-solving iterations per substep (iterating all constraints in order each pass)
- Damping factor: 0.985 per step (slight air resistance)
- Delta-time cap at 33ms (prevents explosion after tab-switch)
- Accumulator pattern: render loop accumulates wall-clock dt, runs physics substeps while accumulator >= 1/120s

**Forces**:
- Gravity: [0, -9.8, 0] m/s²
- Wind: toggleable with W key. Wind force vector computed per-particle via sine-wave superposition: `wx = sin(t×1.3 + x×0.5)×0.5 + sin(t×2.7 + z×0.3)×0.3`, `wy = sin(t×0.7 + y×0.4)×0.1`, `wz = cos(t×1.1 + x×0.4)×0.4 + cos(t×3.1 + y×0.2)×0.2`, multiplied by a wind strength of 3.0. Each particle gets wind force applied as acceleration before integration.
- Self-collision repulsion: spatial hash grid with cell size 0.25 units. Check only non-adjacent particles (Manhattan grid distance > 2). Apply repulsion force when two particles are closer than 0.15 units — push apart with force proportional to penetration depth, capped at 0.01 units per pair per substep.

**Floor collision**: If any particle's y position falls below -6.0, snap to floor level and apply friction (damp horizontal velocity by 0.8) and bounce (reflect vertical velocity with 0.3 coefficient).

**Constraint tearing**: When a constraint stretches beyond 3.0× its rest length, remove it from the constraint array (the receipt can tear under extreme stress).

### Rendering

**Three.js scene setup**:
- WebGLRenderer with antialias: true, alpha: false, setPixelRatio clamped to max 2
- PerspectiveCamera: fov 60, near 0.1, far 100, initial position at spherical coords (theta=π/4, phi=π/3, distance=12)
- Scene background: CSS gradient applied to body (linear-gradient from #e8e8e8 to #d4d4d4), transparent renderer, or a scene.background Color set to #dcdcdc. Use CSS for background and set renderer alpha: true for the gradient to show through.
- AmbientLight: color #ffffff, intensity 0.4
- DirectionalLight: color #ffffff, intensity 0.8, position [8, 12, 4], castShadow: true
- Shadow map: 2048×2048, shadow camera bounds sufficient for the scene

**Paper mesh**:
- BufferGeometry built from the particle grid: indexed faces forming quads (2 triangles per grid cell). Updated per frame by copying particle positions into `geometry.attributes.position.array`, then calling `computeVertexNormals()`.
- Material: MeshStandardMaterial with color #f5f0e8 (warm off-white), roughness 0.7, metalness 0.0, side: DoubleSide
- Receipt text rendered as a CanvasTexture mapped to the mesh via UV coordinates. The canvas is 1024×1536 pixels. Background: #f5f0e8. Text rendered in dark gray (#1a1a1a) using 14px monospace font. UV coordinates on the mesh map the full sheet 0→1 in both U (columns) and V (rows).

**Receipt text content** (rendered on the canvas texture, positioned with y-offsets starting at y=40):
```
PAPER BIRD CAFE
Since 1987  ·  No. 4
────────────────────
1  Flat White      4.50
1  Cortado         4.00
2  Croissant       7.00
1  Almond Cake     5.50
────────────────────
Subtotal          21.00
GST (10%)          2.10
Total             23.10
────────────────────
Paid with Card
···· ···· ···· 4217
────────────────────
Thank you!
Enjoy your coffee.
```

Also render a subtle coffee cup icon (simple geometric cup shape) at the top-right of the canvas, and a small "TENDER" watermark in light gray.

**Ground plane**: PlaneGeometry at y=-6, slightly larger than the sheet shadow falloff area. MeshStandardMaterial with color #c8c8c8, roughness 0.9, metalness 0.0. Set to receiveShadow.

**Grab indicator**: A small sphere (radius 0.08) rendered at the currently grabbed particle position during interaction. Material: MeshStandardMaterial with color #ff6b35 (orange), emissive #ff4400, emissiveIntensity 0.5.

### Camera Controls

Custom orbit camera (no OrbitControls import):
- Left-button drag on background (not on particles): no rotation — left button is reserved for grab interaction
- Right-button drag: orbit. dx rotates theta, dy changes phi. Phi clamped to [0.1, π-0.1]. 
- Scroll wheel: zoom (change camera distance). Distance clamped to [3, 40].
- Camera position computed from spherical coordinates around a target point that defaults to center of the receipt sheet.

Actually, since left-click is for grabbing particles, orbit uses right-click drag and scroll:
- Right-click + drag: orbit camera around target
- Scroll: zoom in/out
- No middle-click pan (keep it simple)

### Interaction

**Raycaster-based grab**:
- On pointerdown (left button): cast a ray from camera through screen coordinates. Find the nearest particle within 0.5 world-units of the ray. If found, set that particle as `grabbed`.
- On pointerdown, also compute a grab plane: perpendicular to the camera's forward direction, passing through the grabbed particle's position.
- On pointermove (while grabbing): intersect the ray with the grab plane to get the target 3D position. Move the grabbed particle to that position and set old=pos (zero velocity, particle follows cursor).
- On pointerup: release the grabbed particle. It regains normal physics behavior with its current position and old position as-set (will gain velocity from subsequent frame differences).

**Touch support**:
- touchstart: same as pointerdown (single touch = grab attempt)
- touchmove: same as pointermove
- touchend: release grab
- touch-action: none on canvas element to prevent scroll/zoom
- Pinch-to-zoom: detect two-finger gesture, adjust camera distance proportionally

**Grab visual feedback**: Cursor changes to `grab` when hovering near a grabbable particle, `grabbing` while actively grabbing. The grab indicator sphere follows the grabbed particle. A subtle ring is drawn at the grab point (could use a TorusGeometry of radius 0.15).

### UI Overlay

Minimal DOM overlay positioned above the canvas:

**Top-left HUD**:
- Simulation name "Tender" in 24px serif font (Georgia), color #333, semi-transparent background
- Below it: "Receipt paper physics" in 12px sans-serif, color #888

**Bottom-left instruction**:
- "Click & drag to crumple · Right-drag to orbit · Scroll to zoom" in 13px monospace, color #555
- Fades out after 4 seconds (CSS transition opacity from 1 to 0 over 1s, delayed 3s)

**Top-right controls**:
- FPS counter (hidden by default, toggle with I key), 11px monospace, color #555
- Wind indicator: "Wind: OFF" or "Wind: ON" based on toggle state

**Center overlay (paused state)**:
- Semi-transparent dark overlay (rgba(0,0,0,0.3))
- "PAUSED — press Space" text in white, 18px sans-serif
- Shown when simulation is paused

**Bottom-right**:
- Small text "R: reset  ·  Space: pause  ·  W: wind  ·  I: info" in 11px monospace, color #999

### States

| State | Behavior |
|---|---|
| **Loading** | Brief "Initializing..." overlay with simulation name |
| **Running** | Physics active, interaction enabled, instruction text fades |
| **Paused** | Physics frozen (skip substep loop), semi-transparent overlay with "PAUSED — press Space" |
| **Reset** | All particles return to initial grid positions, velocities zeroed (old=pos), all torn constraints restored. Brief smooth transition or immediate snap. |

### Keyboard Controls

- **R**: Reset simulation (restore all particles and constraints to initial state)
- **Space**: Toggle pause
- **W**: Toggle wind
- **I**: Toggle info overlay (FPS + particle count)

### Mobile Adaptations

- Detect mobile via `navigator.maxTouchPoints > 0` or screen width < 768px
- Reduce particle grid to 30×45 (1350 particles) on mobile for stable 60fps
- Reduce constraint iterations to 3 (from 4)
- Reduce substeps to 6 (from 8)
- Larger grab radius (0.6 units instead of 0.5)
- touch-action: none on canvas
- Responsive canvas fills viewport
- Handle orientation changes

### Accessibility

- `prefers-reduced-motion`: If the user has reduced motion preference, disable wind and reduce particle count by 40%. Wind toggle still works if user manually enables it.
- Alt text and aria labels on UI elements

### Page Metadata

```html
<title>Tender — Receipt Paper Physics</title>
<meta name="description" content="Interactive 3D receipt paper physics simulation. Grab, crumple, fold, and toss a printed receipt with realistic deformation, shadows, and satisfying tactile feedback.">
```

Include OG and Twitter card metadata. Include an inline SVG favicon (a simple folded-paper icon in the page's head).

### Performance Targets

- 60fps on mid-range hardware with the full 2400-particle grid
- 60fps on mobile with reduced 1350-particle grid
- Use Float32Array for particle positions (no object-per-coordinate)
- Reuse BufferGeometry, only update position attribute per frame
- Spatial hash for self-collision neighbor lookup
- Constraint solving uses raw array access (no method calls in hot loops)

### Code Structure

The HTML file should be structured as:
1. `<!DOCTYPE html>`, `<head>` with meta, styles, import map
2. `<body>` with:
   - UI overlay divs
   - Canvas (created by Three.js)
   - Main `<script type="module">` containing:
     a. Particle class definition
     b. Constraint class definition
     c. Grid builder function (createReceipt)
     d. Constraint solver (solveConstraints with tearing)
     e. Self-collision handler with spatial hash
     f. Floor collision handler
     g. Texture generator (canvas receipt text)
     h. Three.js scene setup
     i. Mesh geometry builder with indexed faces
     j. Camera orbit logic
     k. Interaction handlers (mouse, touch, keyboard)
     l. UI state management
     m. Main animation loop with fixed timestep
     n. Initialization and event binding

### Hosting

Drop `index.html` into any static host (Cloudflare Pages, Vercel, CodePen, or local `python3 -m http.server`). No build step required.

## Notes

- Verlet integration chosen for its natural constraint-handling properties — position-based dynamics is the right fit for cloth/paper simulation
- 40×60 aspect ratio matches a real receipt (roughly 2:3 proportions)
- Bending stiffness is intentionally higher than standard cloth to capture paper's resistance to creasing
- The canvas texture approach means the receipt text physically deforms with the mesh — a key visual win
- Spatial hashing enables O(n) self-collision for 2400 particles
- Right-click orbit preserves left-click for the primary interaction (grabbing)
- The grab plane perpendicular to camera ensures natural-feeling 3D dragging
- Performance-critical loops use direct typed-array access and avoid allocations
- Mobile reduction keeps the experience smooth without sacrificing the core physics feel
