Particle System
Fold includes a particle system that runs entirely on the GPU. Configure it with Fold.init(), with HTML data attributes, or both.
Index
Modes ↓
- Global —
brownian·gravity·spring·orbit·chain·boid·spiral - Element —
attract·repel·orbit·vortex·contain·brownian·gravity·boid
Fold.init() API ↓
particles— Global config object passed toFold.init()- Simulation — count, modes, friction, simSpeed, forces
- Bounds — shape, auto-doc sizing, radius, strength, constrain vs wrap
- Rendering — trails, line thickness, blend mode, depth color
- Interactivity — cursor and touch influence
- Attractors — attractor limit, particle budget, base force strength
- Lifecycle — particleLifetime, emitter respawn
- Debug — origin sphere and attractor overlays
- Reference →
Data Attribute API ↓
data-foldpage-particles— Element mode(s):attract,repel,orbit,vortex,contain,brownian,gravity,boiddata-foldpage-particles-strength— Per-element force strength (auto-scaled by element area when omitted)data-foldpage-particles-radius— Influence radius in CSS px (auto-scaled when omitted)data-foldpage-particles-emit— Seed N particles near this element; auto-computed from area when omitteddata-foldpage-particles-lifetime— Per-emitter particle lifetime in secondsdata-foldpage-particles-brownian-strength— Per-element brownian turbulence strengthdata-foldpage-particles-brownian-frequency— Per-element brownian step frequencydata-foldpage-particles-gravity— Per-element gravity vector"x,y,z"data-foldpage-particles-friction— Per-element friction (auto-scaled when omitted)- Per-mode arrays → — Supply space-separated values to tune each mode independently
- Global scope → —
target="body"sets global modes and config from HTML - Reference →
Reference ↓
Global Configuration
Pass a particles object to Fold.init() to configure the particle system. All properties are optional — unspecified values use the defaults shown below.
Fold.init({
particles: {
// count omitted → auto-computed from page area (set 0 to suppress ambient cloud)
modes: ['brownian'],
friction: 0.89,
boundsShape: 'box',
boundsAutoDoc: true,
boundsStrength: 1,
boundsMode: 'constrain',
attractorBaseStrength: 0.004,
trailLength: 7,
brownianStrength: 0.0002,
brownianFrequency: 11,
depthColorStops: [
{ depth: -0.2, color: [0.05, 0.10, 1.00] },
{ depth: 0.0, color: [0.20, 1.00, 0.40] },
{ depth: 0.2, color: [1.00, 0.10, 0.05] },
],
},
});Simulation
count
Total number of ambient GPU particles — the global background cloud that moves independently of any DOM element. Each particle is drawn as a short trail. Higher counts increase GPU load but have no CPU cost.
Particles seeded by data-foldpage-particles-emit elements are additive — they are allocated on top of this count, not subtracted from it.
Auto-compute: When count is omitted from the particles config object, Fold measures the page area (window.innerWidth × document.body.scrollHeight) and computes an appropriate count (up to 5 000 at a 1440 × 5000 px reference page). This means minimal configs like Fold.init({ particles: {} }) get a sensible ambient cloud automatically.
count: 0: Explicitly accepted — zero ambient particles. Use this to disable the global cloud entirely and let DOM element emitters be the sole particle source. Note that false, null, or an absent count key are treated as "unset" and trigger auto-compute; only the literal value 0 suppresses ambient particles.
| Default | Range |
|---|---|
| (auto-computed from page area) | 0 or any positive integer |
// Explicit count
Fold.init({ particles: { count: 1200 } });
// Suppress ambient cloud — particles only from data-attr emitters
Fold.init({ particles: { count: 0, friction: 0.89, trailLength: 7 } });
// Omit count — auto-sized to page area
Fold.init({ particles: { friction: 0.89 } });modes
Array of modes active on the particle system. Multiple modes compose additively — all active modes apply their force to every particle each frame.
| Default |
|---|
['brownian'] |
Fold.init({ particles: { modes: ['brownian', 'gravity'] } });See Modes for all available values and their behavior.
friction
Velocity damping factor applied each frame. 1.0 = no damping (particles coast forever). 0.0 = full stop every frame. Values near 0.75–0.90 give natural-feeling motion.
| Default | Range |
|---|---|
0.89 | 0.0 – 1.0 |
simSpeed
Overall simulation speed — movement and particle lifetimes all scale together. simSpeed: 0.15 runs the simulation at 15% speed with no change to the character of the motion.
| Default | Range |
|---|---|
1.0 | 0.0+ (values above 1.0 fast-forward) |
Fold.init({
particles: {
simSpeed: 0.15,
},
});brownianFrequency
How many times per second the Brownian random-walk direction changes. At high values each particle gets a new random impulse nearly every frame — rapid, jittery noise. Lowering this means each kick lasts longer, producing smooth lazy drifts instead of high-frequency chatter.
This controls the temporal character of Brownian motion, independent of its strength.
| Default | Range |
|---|---|
11 | Any positive number |
// Smooth, slow-drifting Brownian — direction holds for ~0.5 s per kick
Fold.init({
particles: {
brownianFrequency: 2,
brownianStrength: 0.001,
modes: ['brownian'],
},
});| Value | Character |
|---|---|
60+ | Rapid jitter — new direction nearly every frame |
11 (default) | Gentle ambient drift — direction shifts roughly 11× per second |
4–8 | Lazy drift — particles meander slowly between direction changes |
1–2 | Long slow glide — each random kick lasts nearly a second |
brownianStrength
Magnitude of the random impulse applied each frame when brownian mode is active. Larger values create more chaotic, energetic jitter. This is the primary "noise" knob for ambient motion.
| Default |
|---|
0.0002 |
gravity
World-space acceleration vector applied each frame when gravity mode is active. The default points slightly downward. Units are world-space per-frame² — values are typically very small.
| Default |
|---|
[0, -0.00004, 0] |
Fold.init({ particles: { modes: ['gravity'], gravity: [0, -0.0001, 0] } });springStrength
Spring coefficient pulling particles toward springTarget when spring or orbit mode is active. Used as the inward component in orbit mode too (strength × 0.5).
| Default |
|---|
0.00001 |
springTarget
World-space target point for spring and orbit modes. Defaults to the particle group's local origin [0, 0, 0].
| Default |
|---|
[0, 0, 0] |
Bounds
The bounds volume keeps particles inside the visible page area. The default shape is a box automatically sized to the document dimensions — particles stay inside the live page width and height rather than spilling outside it.
boundsShape
Selects the bounds geometry.
| Value | Behavior |
|---|---|
'box' (default) | An axis-aligned box sized to the document. Each axis is constrained or wrapped independently. |
'sphere' | A sphere centered at the particle group origin, sized by boundsRadius. |
Fold.init({ particles: { boundsShape: 'sphere', boundsRadius: 0.6 } });boundsAutoDoc
When true (the default), the box bounds are automatically sized to window.innerWidth × document.body.scrollHeight and re-applied whenever the document resizes. The box center is automatically placed at the vertical midpoint of the document in particle local space.
Set to false to use boundsBox and boundsCenter for fully manual control.
| Default |
|---|
true |
boundsBox
Explicit half-extents [x, y, z] in world units for the box bounds. Overrides boundsAutoDoc when provided with non-zero values.
The relationship to CSS pixels is: worldUnits = cssPixels × 0.0007. So a 800 px half-width ≈ 0.56.
| Default |
|---|
| (auto-computed from document) |
Fold.init({
particles: {
boundsShape: 'box',
boundsAutoDoc: false,
boundsBox: [0.56, 1.4, 0.45], // [halfWidth, halfHeight, halfDepth]
},
});boundsCenter
Explicit center offset [x, y, z] of the box in particle local space. Overrides the auto-computed document center when provided with non-zero values.
| Default |
|---|
| (auto-computed: document vertical midpoint) |
boundsRadius
For boundsShape: 'sphere': radius of the containing sphere in world units.
For boundsShape: 'box': used as the Z-axis half-extent (depth of the particle slab) when boundsAutoDoc is computing the other dimensions.
The relationship to CSS pixels is: boundsRadius / 0.0007 ≈ pixels. So 0.45 ≈ 643 px radius.
| Default |
|---|
0.45 |
boundsStrength
How hard the boundary pushes back in constrain mode. For box bounds, this applies per-axis proportional to how far a particle has overshot the face. Higher values create a harder wall; lower values allow particles to drift further outside before returning.
| Default |
|---|
1 |
boundsMode
How particles behave when they reach the bounds volume.
| Value | Behavior |
|---|---|
'constrain' (default) | A spring force pushes the particle back inward. |
'wrap' | The particle teleports to the opposite side of the volume. For box bounds each axis wraps independently; velocity is optionally damped by wrapDamping. |
Fold.init({ particles: { boundsMode: 'wrap', wrapDamping: 0.5 } });wrapDamping
Velocity multiplier applied once when a particle wraps. 0 = particles stop dead on wrap; 1 = velocity is fully preserved. Only used when boundsMode: 'wrap'.
| Default |
|---|
0 |
Rendering
trailLength
Number of frames back used to pick the trail endpoint. Higher values create longer trails. The trail fades from near-transparent at the tail to fully opaque at the head.
| Default |
|---|
7 |
lineThickness
Particle line thickness in screen-space pixels. The line expands perpendicular to its direction in clip space, so it always reads as a consistent thickness regardless of depth.
| Default |
|---|
1.0 |
blendMode
How particle colors are composited over the scene behind them.
| Value | Effect |
|---|---|
'normal' (default) | Standard alpha blending. |
'additive' | Colors add together — bright on dark backgrounds, good for glowing effects. |
'multiply' | Colors multiply — darkens the scene. |
'subtractive' | Colors subtract from the scene. |
Fold.init({ particles: { blendMode: 'additive' } });depthColorStops
An array of { depth, color } stops defining a gradient mapped to each particle's Z position in world space. The color at each particle's current depth is interpolated between the two nearest stops.
depth is a world-space Z value (negative = farther from camera). color is a normalized RGB array [r, g, b] where each channel is 0.0–1.0.
Fold.init({
particles: {
depthColorStops: [
{ depth: -0.25, color: [0.05, 0.15, 0.90] }, // deep blue
{ depth: 0.00, color: [0.30, 0.85, 1.00] }, // cyan
{ depth: 0.25, color: [0.90, 0.45, 1.00] }, // violet
],
},
});Default (5 stops):
| depth | color (RGB) | approx. |
|---|---|---|
-0.2 | [0.05, 0.10, 1.00] | deep blue |
-0.1 | [0.00, 0.80, 1.00] | sky blue |
0.0 | [0.20, 1.00, 0.40] | green |
0.1 | [1.00, 0.80, 0.00] | amber |
0.2 | [1.00, 0.10, 0.05] | red |
Interactivity
Cursor and touch position are mapped to a world-space point and used as a live attractor. The attraction is always active when interactiveStrength > 0 — no additional setup is required.
interactiveStrength
Force magnitude of the cursor influence. Set to 0 to disable cursor attraction entirely.
| Default |
|---|
0.002 |
interactiveDistance
The world-space radius around the cursor within which particles are fully influenced. Falls off as inverse-square beyond this distance.
| Default |
|---|
0.05 |
interactiveStaleDecay
When the cursor stops moving, the interactive force decays by this factor each frame. 0.99 = slow decay (force lingers for ~100 frames). 0.9 = fast decay (~10 frames).
| Default |
|---|
0.99 |
Fold.init({
particles: {
interactiveStrength: 0.003, // stronger pull
interactiveDistance: 0.08, // wider reach
interactiveStaleDecay: 0.95, // force fades quickly when pointer is still
},
});DOM Attractors Config
maxAttractors
Maximum number of DOM element attractor slots active simultaneously. Each mode token in a space/comma-separated list on one element consumes one slot — data-foldpage-particles="vortex gravity" uses two slots. The maximum is 40.
| Default | Hard cap |
|---|---|
40 | 40 |
Fold.init({
particles: {
maxAttractors: 20, // lower to reduce GPU cost
},
});maxParticles
Cap on the total particle count — count (ambient) plus all data-foldpage-particles-emit values. If the total exceeds the cap, emitters lowest on the page get fewer particles until it fits.
| Default |
|---|
10000 |
10000 is light for any WebGL-capable GPU. Raise it for denser scenes, lower it for low-power devices.
Fold.init({
particles: {
maxParticles: 10000, // total particle budget: ambient + all emitters combined
count: 500, // 500 ambient + up to 9500 emitter particles within budget
},
});attractorBaseStrength
Base world-space force magnitude for DOM element attractors at strength=1.0. Each element's data-foldpage-particles-strength value (0–1) is multiplied by this to get the final force.
| Default |
|---|
0.004 |
Lifecycle
particleLifetime
Global particle lifetime in seconds. When set to 0 (the default) particles are immortal and never respawn. When set to a positive value, each particle has a normalised age that advances by delta / lifetime per frame. When age reaches 1.0 the particle dies and immediately respawns:
- Emitter-assigned particles (those seeded by a
data-foldpage-particles-emitelement) respawn near their assigned emitter's current DOM position. If the emitter also hasdata-foldpage-particles-lifetime, that value overrides this global setting for those particles only. - Free particles (no emitter assignment) respawn at a random point inside the bounds volume, creating a continuously refreshing ambient cloud.
Initial ages are randomised at startup to prevent mass-respawn on the first frame. On respawn each particle also receives a small random phase offset (up to 50% of its lifetime), so particles respawning near the same element do so in a rolling stagger rather than a synchronised burst — this keeps emitters producing a continuous flow even after many lifetime cycles. The particle's trail is additionally suppressed for trailLength frames while the history buffer fills with the new trajectory, preventing a ghost line connecting the spawn point to the previous death position.
Fold.init({
particles: {
count: 800,
particleLifetime: 6, // each ambient particle lives for 6 seconds then respawns
modes: ['brownian'],
},
});Pair with data-foldpage-particles-emit to keep particles flowing from specific elements:
<!-- 300 particles stream continuously from this headline -->
<h1 data-foldpage-particles="attract"
data-foldpage-particles-strength="0.7"
data-foldpage-particles-radius="180"
data-foldpage-particles-emit="300">
Hero Headline
</h1>| Default | Range |
|---|---|
0 (immortal) | Any positive number (seconds) |
Debug
debug
When true, renders diagnostic overlays in the 3D scene:
- A red sphere at the particle group's local origin.
- Wireframe spheres at each active DOM attractor position, scaled to the attractor's influence radius and color-coded by mode: green = attract, red = repel, blue = orbit, yellow = vortex.
Useful for verifying that attractor elements are positioned correctly relative to 3D content.
| Default |
|---|
false |
Fold.init({ particles: { debug: true } });Modes
Modes define how forces act on particles. Apply a mode globally so it affects every particle in the scene, or attach it to a specific element so it only influences particles within that element's radius. Forces from all active modes combine each frame.
Global scope — set via Fold.init({ particles: { modes: [...] } }) or data-foldpage-particles-target="body" in HTML.
Element scope — set via data-foldpage-particles="..." on any HTML element.
| Mode | Global | Element | Description |
|---|---|---|---|
brownian | ✓ | ✓ | Random impulse each frame — ambient noise and jitter. Global: affects every particle uniformly. Element: localized turbulence, fading at the element edge. |
gravity | ✓ | ✓ | Directional acceleration. Global: constant pull everywhere. Element: directional field within the element's radius, falling off at the edge. |
orbit | ✓ | ✓ | Tangential spin force. Global: all particles orbit a fixed springTarget point. Element: nearby particles orbit that element's position. |
boid | ✓ | ✓ | Flocking — cohesion, alignment, and separation. Global: full-scene flocking. Element: localized flocking zone; intensity scales with global flocking parameters. |
attract | — | ✓ | Spring pull toward the element's center. |
repel | — | ✓ | Spring push away from the element's center. |
vortex | — | ✓ | XZ funnel spin with vertical pull and centripetal spring. |
contain | — | ✓ | Particles bounce off the element's CSS bounding box. |
spring | ✓ | — | Pulls every particle toward springTarget. |
chain | ✓ | — | Spring-links particles in sequence — ribbon or streamer effect. |
spiral | ✓ | — | Helical tangential XZ force with sinusoidal vertical oscillation. |
The global modes array controls which global-scope modes are active. Modes compose additively — ['brownian', 'orbit'] applies both forces simultaneously to every particle.
| Mode | Description |
|---|---|
brownian | Random impulse each frame — ambient noise and jitter. The brownianStrength property controls the magnitude. |
gravity | Constant acceleration in the gravity vector direction. Good for falling particles or directional drift. |
spring | Spring force pulling every particle toward springTarget. Particles cluster around the origin and oscillate. |
orbit | Tangential force in the XZ plane circling particles around springTarget's Y axis, combined with a gentle inward spring. Creates circular cloud motion. |
chain | Each particle is spring-linked to the next one in sequence. Creates flowing ribbon or streamer effects. Configure chain tension via Fold.init. |
boid | Flocking simulation — particles attract nearby neighbors (cohesion), align velocities (alignment), and avoid crowding (separation). Configure search radius, cohesion, alignment, and separation strength via Fold.init. |
spiral | Tangential XZ force combined with a sinusoidal vertical oscillation, creating helical or DNA-strand paths. Configure tangential force and vertical amplitude via Fold.init. |
Mode parameters
| Parameter | Default | Used by |
|---|---|---|
brownianStrength | 0.0002 | brownian |
brownianFrequency | 11 | brownian |
gravity | [0, -0.00004, 0] | gravity |
springStrength | 0.00001 | spring, orbit, spiral |
springTarget | [0, 0, 0] | spring, orbit, spiral |
chainStrength | 0.002 | chain |
boidRadius | 0.05 | boid (cohesion + alignment search radius) |
boidSepRadius | 0.05 | boid (separation search radius) |
boidCohesionStr | 0.008 | boid |
boidAlignmentStr | 0.005 | boid |
boidSeparationStr | 0.003 | boid |
spiralTangentStr | 0.0001 | spiral |
spiralVertStr | 0.0 | spiral (vertical oscillation amplitude) |
Element Modes
Any HTML element inside a Fold.page target wrapper can influence the particle system by carrying data-foldpage-particles attributes. Element modes only affect particles within the element's radius, with forces falling off quadratically toward the edge.
NOTE Up to
40elements can have active particle mode simultaneously; elements beyond the limit are ignored. Each mode token in a space/comma-separated list counts as one slot —"vortex gravity"on one element uses two slots.
data-foldpage-particles
Marks an element as a particle influencer and sets its mode(s). Accepts a space- or comma-separated list — each mode is registered as an independent slot on the same element, so the forces compose simultaneously.
| Value | Behavior |
|---|---|
attract | Spring force pulls nearby particles toward the element's center. |
repel | Spring force pushes nearby particles away from the element's center. |
orbit | 3D tangential force orbits particles around the element. The orbit plane adapts to each particle's angular momentum — particles can arc over the top or underneath as well as around the sides. A gentle inward spring maintains orbital distance. |
vortex | XZ tangential spin combined with a vertical pull and an inward centripetal spring — funnel effect that actively retains particles within the zone. |
contain | Particles move freely inside the element's CSS bounding box. Any particle that crosses the boundary receives a per-axis pushback force proportional to how far it has overshot, fading to zero at radius pixels outside. |
brownian | Localized turbulence field within the radius, falling off quadratically toward the edge. Configure with data-foldpage-particles-brownian-strength and data-foldpage-particles-brownian-frequency. |
gravity | Directional gravity field within the radius. Applies a constant acceleration vector, also falling off quadratically. Configure with data-foldpage-particles-gravity. |
boid | Localized flocking zone — particles near the element flock together using cohesion, alignment, and separation forces. Force fades at the element edge. Configure flocking intensity via Fold.init boid parameters. |
<!-- Pull particles toward this card's center -->
<div data-foldpage-particles="attract">
Feature Card
</div>
<!-- Push particles away — hollow region effect -->
<nav data-foldpage-particles="repel">
Navigation
</nav>
<!-- Keep emitted particles within the element's own bounds -->
<div data-foldpage-particles="contain"
data-foldpage-particles-strength="1.5"
data-foldpage-particles-radius="80"
data-foldpage-particles-emit="200">
Contained particle field
</div>
<!-- Combine modes: vortex spin with an additional orbital force -->
<div data-foldpage-particles="vortex orbit"
data-foldpage-particles-strength="0.8"
data-foldpage-particles-radius="200">
Vortex + orbit zone
</div>Each mode in the list occupies one attractor slot toward the maxAttractors limit. Unknown or misspelled mode tokens are silently ignored.
contain and radius: for the contain mode, data-foldpage-particles-radius is the falloff distance in CSS pixels — how far outside the element boundary the pushback force reaches before dropping to zero. A smaller radius (e.g. 40) creates a harder wall; a larger value (e.g. 150) produces a softer gradient that guides particles back from further away.
Per-Mode Arrays
When an element lists multiple modes you can supply space-separated values for strength, radius, friction, brownian-strength, brownian-frequency, and gravity. Values align positionally with the mode tokens in data-foldpage-particles. (emit and lifetime are element-level scalars and do not accept per-mode lists — see below.)
<div data-foldpage-particles="attract brownian"
data-foldpage-particles-strength="1.0 0.3"
data-foldpage-particles-radius="180 360"
data-foldpage-particles-emit="120"
data-foldpage-particles-brownian-strength="0 0.0006"
data-foldpage-particles-friction="0.96 0.88">
</div>Here attract gets strength=1.0, radius=180; brownian gets strength=0.3, radius=360, brownian-strength=0.0006, friction=0.88. Both share the element's single emit=120 pool.
Padding rule: if fewer values are provided than there are modes, the last value repeats for all remaining modes.
Single value: a single value applies to all modes for strength, radius, friction, brownian-strength, and brownian-frequency.
emit and lifetime are not per-mode. They describe the element's particle pool, not individual force behaviors. Particles from that pool are affected by all modes whose radius covers them. Passing a list for these attributes has no additional effect — only the single scalar is used.
Gravity: because gravity already uses commas to separate x,y,z components, per-mode gravity vectors are separated by semicolons:
data-foldpage-particles-gravity="0,-0.001,0; 0,-0.003,0"
<!-- attract → (0,-0.001,0) brownian → (0,-0.003,0) -->A single vector without a semicolon still works as before and applies to all modes.
data-foldpage-particles-strength
Per-element strength multiplier in the range 0–1. Multiplied by the global base force strength (see attractorBaseStrength in Fold.init) to produce the final force magnitude.
Auto-scaling: When this attribute is omitted, the base value of 0.8 is automatically scaled by element size (see Size-aware auto-scaling). Larger elements get proportionally stronger pull/push without any configuration. Set the attribute explicitly to pin strength independent of element size.
| Default (omitted) |
|---|
| auto (scales with element size) |
<!-- Explicit strength -->
<div data-foldpage-particles="attract"
data-foldpage-particles-strength="0.9">
Strong attractor
</div>
<!-- Omit — strength auto-scales to element area -->
<div data-foldpage-particles="attract">
Auto-scaled attractor
</div>data-foldpage-particles-radius
Influence radius in CSS pixels. Particles outside this radius are unaffected. Force falls off quadratically from full strength at the element's center to zero at the radius edge.
Auto-scaling: When this attribute is omitted, the base value of 200 px is automatically scaled by element size. A small 100 × 60 px element gets a radius of ~63 px; a large 500 × 300 px element gets ~316 px. Set the attribute explicitly to lock the radius.
| Default (omitted) |
|---|
| auto (scales with element size) |
<!-- Explicit radius -->
<div data-foldpage-particles="orbit"
data-foldpage-particles-radius="250">
Wide orbit zone
</div>
<!-- Omit — radius auto-scales to element area -->
<div data-foldpage-particles="attract">
Auto-scaled radius
</div>data-foldpage-particles-emit
Seeds N additional particles near this element's position at mount time — on top of the global count set in Fold.init(). The element also acts as an attractor (using its data-foldpage-particles mode) to keep those particles in the neighbourhood after initialization.
Each emitter element contributes its own independent particle slice. Multiple emitters each add their own count rather than splitting a shared budget.
Auto-emit: When the attribute is absent entirely, the emit count is automatically computed from the element's rendered area:
autoEmit = clamp(round(200 × sizeFactor), 1, 500)At the 300 × 200 px baseline this produces 200 particles. Smaller elements emit fewer; larger elements emit more, up to the 500-particle per-element cap.
Explicit 0: Setting emit="0" suppresses auto-emit and seeds no particles for this element. Use this when you want the element to act as a force field only, with no dedicated particle cloud.
| Value | Behaviour |
|---|---|
| (absent) | Auto-computed from element area, capped at 500 |
0 | No particles seeded — attractor-only |
> 0 | Exactly N particles seeded near this element |
<!-- Auto-emit — count derived from element size -->
<div data-foldpage-particles="attract">
200 particles at 300×200 px; scales with area
</div>
<!-- Explicit count -->
<h1 data-foldpage-particles="attract"
data-foldpage-particles-strength="0.6"
data-foldpage-particles-radius="180"
data-foldpage-particles-emit="200">
Hero Headline
</h1>
<!-- Force field only — no particles emitted -->
<nav data-foldpage-particles="repel"
data-foldpage-particles-emit="0">
Navigation
</nav>When particleLifetime > 0 (or when data-foldpage-particles-lifetime is set), emitter particles respawn near their assigned element's live DOM position rather than at a random point, creating a continuous stream from the element.
data-foldpage-particles-lifetime
Particle lifetime in seconds for this element's influence zone. Behavior depends on whether the element also carries data-foldpage-particles-emit:
With emit — per-emitter lifetime. Overrides the global particleLifetime for the particles seeded by this element only. Other emitters and free particles are unaffected.
Without emit — global lifetime override. Sets the lifetime for all free (non-emitter-assigned) particles on the page. This is the DOM-level equivalent of Fold.init({ particles: { particleLifetime: N } }) and only takes effect when no global particleLifetime is configured. Use this to make the ambient particle field cycle without needing emitters.
| Value | Behavior |
|---|---|
0 (default) | Falls back to the global particleLifetime. If that is also 0, particles are immortal. |
> 0 | Particles live for this many seconds, then respawn. Emitter particles respawn near the element; free particles respawn at a random point in the bounds volume. |
<!-- All ambient particles cycle every 2 seconds (no emit needed) -->
<div data-foldpage-particles="repel"
data-foldpage-particles-radius="180"
data-foldpage-particles-lifetime="2">
Repel zone — particles near here die and refresh
</div><!-- These 150 particles respawn every 4 seconds near this element -->
<div data-foldpage-particles="attract"
data-foldpage-particles-strength="0.6"
data-foldpage-particles-radius="120"
data-foldpage-particles-emit="150"
data-foldpage-particles-lifetime="4">
Streaming emitter
</div>Mix immortal ambient particles with short-lived emitter bursts:
<!-- All free particles cycle every 2 seconds -->
<div data-foldpage-particles="repel"
data-foldpage-particles-lifetime="2">
Repel zone
</div>
<!-- Emitter particles have their own independent 5-second cycle -->
<section data-foldpage-particles="attract"
data-foldpage-particles-emit="200"
data-foldpage-particles-lifetime="5">
Hero section
</section>data-foldpage-particles-brownian-strength
Turbulence magnitude for the brownian element mode. The per-element default (0.001) is intentionally stronger than the global ambient brownianStrength (0.0002) — localized turbulence fields are expected to be more energetic than the scene-wide drift.
| Default (omitted) |
|---|
0.001 |
<div data-foldpage-particles="brownian"
data-foldpage-particles-radius="220"
data-foldpage-particles-brownian-strength="0.0008"
data-foldpage-particles-brownian-frequency="30">
</div>data-foldpage-particles-brownian-frequency
Step rate of the per-element brownian noise. Higher values produce faster, more erratic jitter; lower values give slow, rolling turbulence. The per-element default (20) is higher than the global ambient frequency (11) for more perceptibly active localized zones.
| Default (omitted) | Value | Effect |
|---|---|---|
20 | 60+ | Rapid frame-rate jitter |
20 (default) | Brisk turbulence — direction shifts ~20× per second | |
10–15 | Smooth lazy drifts | |
4–8 | Slow rolling noise |
data-foldpage-particles-gravity
Directional gravity vector for the gravity element mode, as three comma- or space-separated floats "x,y,z". The force is applied within the influence radius and falls off quadratically toward the edge. Defaults to the global gravity from Fold.init({ particles: { gravity } }).
Values are in world units per frame²; useful values are roughly 0.0001–0.001.
<!-- Downward pull within 250px of this element -->
<div data-foldpage-particles="gravity"
data-foldpage-particles-radius="250"
data-foldpage-particles-gravity="0,-0.0003,0">
</div>
<!-- Combine with attract — pull in AND push down -->
<div data-foldpage-particles="attract gravity"
data-foldpage-particles-radius="200"
data-foldpage-particles-gravity="0,-0.0002,0">
</div>data-foldpage-particles-friction
Overrides the global friction value for particles that fall within this element's influence radius. Accepts a float in the range 0–1, where 0 stops particles instantly and 1 means no damping. The effective friction blends between the override and the global value, weighted by the quadratic falloff — particles at the centre of the element see the full override; particles at the edge of the radius blend back toward global friction.
Auto-scaling: When this attribute is omitted, friction is automatically adjusted by element size:
autoFriction = clamp(1 − (1 − globalFriction) / sizeFactor, 0.70, 0.97)At the baseline element size the result equals the global friction exactly (no change). Smaller elements get higher friction (more damping, tighter clusters); larger elements get lower friction (less damping, more momentum). Set the attribute explicitly to bypass this scaling.
Unlike brownian and gravity, this attribute works with any mode and does not require a specific value in data-foldpage-particles.
<!-- Sticky zone — particles decelerate and gather near this element -->
<div data-foldpage-particles="attract"
data-foldpage-particles-radius="200"
data-foldpage-particles-friction="0.95">
</div>
<!-- Slippery zone — particles accelerate and coast near this element -->
<div data-foldpage-particles="orbit"
data-foldpage-particles-radius="180"
data-foldpage-particles-friction="0.55">
</div>Size-aware auto-scaling
When strength, radius, emit, and friction attributes are all omitted, the particle system measures the element's rendered area at mount time and derives proportional values automatically. This means a single data-foldpage-particles="attract" attribute is all you need — Fold handles the rest.
Combining attributes
All attributes can be combined on the same element, including multiple modes:
<!-- Single mode with all attributes -->
<div
data-foldpage-particles="orbit"
data-foldpage-particles-strength="0.7"
data-foldpage-particles-radius="220"
data-foldpage-particles-emit="150"
data-foldpage-particles-lifetime="5">
Featured content — particles orbit and refresh every 5 s
</div>
<!-- Multiple modes on one element -->
<div
data-foldpage-particles="vortex orbit"
data-foldpage-particles-strength="0.75"
data-foldpage-particles-radius="200"
data-foldpage-particles-emit="150">
Vortex spin + orbital pull simultaneously
</div>Global scope via data attributes
Setting data-foldpage-particles-target="body" on any element promotes it from a local attractor to a global config source. The element does not create an attractor slot. Instead its attributes are merged into the global FoldParticlesConfig — equivalent to the values you would pass to Fold.init({ particles: { ... } }).
In this context the primary data-foldpage-particles attribute is interpreted as simulation modes (brownian, gravity, spring, orbit, chain, boid, spiral), not attractor behavior modes.
<!-- Pure-HTML global config — no Fold.init() call required -->
<div
data-foldpage-particles="brownian gravity"
data-foldpage-particles-target="body"
data-foldpage-particles-friction="0.92"
data-foldpage-particles-brownian-strength="0.0003"
data-foldpage-particles-gravity="0,-0.00006,0">
</div>This is equivalent to:
Fold.init({ particles: {
modes: ['brownian', 'gravity'],
friction: 0.92,
brownianStrength: 0.0003,
gravity: [0, -0.00006, 0],
} });Supported attributes in target="body" context:
| Attribute | Maps to |
|---|---|
data-foldpage-particles | modes — simulation mode list |
data-foldpage-particles-friction | friction |
data-foldpage-particles-brownian-strength | brownianStrength |
data-foldpage-particles-brownian-frequency | brownianFrequency |
data-foldpage-particles-gravity | gravity — single "x,y,z" vector |
Attractor-only attributes (strength, radius, emit, lifetime) are ignored on target="body" elements.
Priority: target="body" config sits below JS Fold.init() / Fold.set() — if both set the same property, the JS value wins. Multiple target="body" elements on the same page merge first-wins per property, in DOM order.
Mixed usage: target="body" elements and regular attractor elements coexist freely on the same page. The global config drives the simulation modes; the attractor elements add local force zones on top.
Full Config Reference
Fold.init() config
| Property | Default | Description |
|---|---|---|
count | (auto from page area) | Ambient GPU particles. 0 = no ambient cloud. Emitter particles are additive. |
modes | ['brownian'] | Active global mode(s) — see Modes |
friction | 0.89 | Velocity damping per frame (0 = stop, 1 = no damping) |
simSpeed | 1.0 | Simulation speed. 0.15 = 15% speed |
brownianStrength | 0.0002 | Random impulse magnitude (brownian mode) |
brownianFrequency | 11 | Direction-change rate in Hz for Brownian mode. Lower = smoother lazy drifts |
gravity | [0, 0, 0] | Global acceleration vector (gravity mode). Auto-enables the mode when non-zero. |
springStrength | 0.00001 | Spring coefficient toward springTarget |
springTarget | [0, 0, 0] | Spring / orbit / spiral anchor point |
boundsShape | 'box' | 'box' document-fitted rectangle | 'sphere' centered sphere |
boundsAutoDoc | true | Auto-size box bounds to innerWidth × scrollHeight; updates on resize |
boundsBox | (auto) | Explicit box half-extents [x, y, z] in world units; overrides boundsAutoDoc |
boundsCenter | (auto) | Explicit box center [x, y, z] in particle local space; overrides boundsAutoDoc |
boundsRadius | 0.45 | Sphere radius in world units, or Z half-extent for box mode (÷ 0.0007 → CSS px) |
boundsStrength | 1 | Boundary spring strength (constrain mode) |
boundsMode | 'constrain' | 'constrain' push back | 'wrap' teleport through |
wrapDamping | 0 | Velocity multiplier on wrap (wrap mode only) |
interactiveStrength | 0.002 | Cursor/touch force magnitude. 0 = disabled. |
interactiveDistance | 0.05 | World-space radius of cursor influence |
interactiveStaleDecay | 0.99 | Force decay rate when cursor is stationary |
chainStrength | 0.002 | Spring strength linking adjacent particles (chain mode) |
boidRadius | 0.05 | Cohesion + alignment search radius (boid mode) |
boidSepRadius | 0.05 | Separation search radius (boid mode) |
boidCohesionStr | 0.008 | Cohesion force strength (boid mode) |
boidAlignmentStr | 0.005 | Velocity alignment strength (boid mode) |
boidSeparationStr | 0.003 | Separation force strength (boid mode) |
spiralTangentStr | 0.0001 | XZ tangential force strength (spiral mode) |
spiralVertStr | 0.0 | Vertical oscillation amplitude (spiral mode) |
trailLength | 7 | Frames back for trail endpoint |
lineThickness | 1.0 | Particle line thickness in screen pixels |
blendMode | 'normal' | 'normal' | 'additive' | 'multiply' | 'subtractive' |
depthColorStops | (5-stop gradient) | { depth, color } stops mapping Z depth to particle color |
maxAttractors | 40 | Max simultaneous DOM element attractor slots (hard cap: 40). Each mode token on an element uses one slot. |
maxParticles | 10000 | Cap on total particles (count + all emit values). If exceeded, emitters lowest on the page get fewer particles. |
attractorBaseStrength | 0.004 | Base force for DOM attractors at strength=1.0 |
particleLifetime | 0 (immortal) | Global seconds before a particle dies and respawns. 0 = never. |
debug | false | Render origin axes gizmo and wireframe attractor overlays |
Data attribute reference
All attributes use the foldpage prefix.
| Attribute | Default (when omitted) | Description |
|---|---|---|
data-foldpage-particles | — | One or more space/comma-separated modes: attract | repel | orbit | vortex | contain | brownian | gravity | boid. Each token uses one attractor slot (max 40). |
data-foldpage-particles-strength | auto (scales with element size) | Force multiplier 0–1. Multiplied by global base force strength. Auto-scales by element area when omitted. |
data-foldpage-particles-radius | auto (scales with element size) | Influence radius in CSS px. Auto-scales by element area when omitted. |
data-foldpage-particles-emit | auto (scales with element size) | Particles seeded near this element. Absent = auto from area; 0 = none (attractor only). |
data-foldpage-particles-lifetime | 0 | Lifetime in seconds. With emit: per-emitter override. Without emit: sets global lifetime for all free particles. 0 = immortal. |
data-foldpage-particles-brownian-strength | 0.001 | Turbulence magnitude for brownian mode on this element. Intentionally stronger than global brownian strength. |
data-foldpage-particles-brownian-frequency | 20 | Noise step rate (Hz) for brownian mode. Higher = faster jitter. |
data-foldpage-particles-gravity | global gravity | Gravity vector "x,y,z" for gravity mode on this element. |
data-foldpage-particles-friction | auto (scales inversely with element size) | Friction override (0–1) within the element's radius. When absent, auto-scales so smaller elements damp more. Blends with global by falloff weight. |

