View Layouts
Add a layout attribute to a container and its views are arranged in 3D — no manual positioning or CSS transforms needed.
The layout runs at Fold.init(). It sets each panel's position and transform as inline styles and, for most layouts, adds a scroll spacer so there's enough scroll distance to visit every view.
Supported layout types: ring, grid, random, helix, scatter.
Quick Start
<div class="foldpage"
data-foldpage-view-layout="ring">
<header data-foldpage-view="0">Center view</header> <!-- head -->
<section data-foldpage-view>Panel 1</section>
<section data-foldpage-view>Panel 2</section>
<section data-foldpage-view>Panel 3</section>
</div>
<script>
Fold.init({ foldpage: true });
</script>No viewLayout config is needed in Fold.init — data attributes on the container drive the layout automatically.
Auto Views — let Fold discover view elements automatically:
<div class="foldpage"
data-foldpage-view-layout="ring"
data-foldpage-view-auto=".card">
<header data-foldpage-view="0">Center view</header>
<div class="card">Panel 1</div> <!-- auto-tagged as views at runtime -->
<div class="card">Panel 2</div>
<div class="card">Panel 3</div>
</div>See Auto Views for full details including heading-based discovery.
Framework components — Angular, React, Vue, and Web Component elements can be used as view panels directly. Fold discovers them by data-foldpage-view regardless of tag name:
<!-- Angular SPA example -->
<div class="foldpage"
data-foldpage-view-layout="ring"
data-foldpage-view-layout-direction="row-reverse">
<app-hero data-foldpage-view="0" /> <!-- head / center view -->
<app-features data-foldpage-view />
<app-demos data-foldpage-view />
<app-contact data-foldpage-view />
</div>Each <app-*> element gets no 3D mesh of its own; the layout positions it and its inner HTML renders normally.
Generic Attributes
These attributes apply to all layout types.
Container attributes (on the data-foldpage-view-layout element):
| Attribute | Type | Default | Description |
|---|---|---|---|
data-foldpage-view-layout | "ring" / "grid" / "random" / "helix" / "scatter" | — | Layout type. Required. |
data-foldpage-view-layout-absolute | boolean | type-specific¹ | Whether panels are absolutely positioned. See Absolute Positioning. |
data-foldpage-view-auto | CSS selector | — | Automatically discover view elements. See Auto Views. |
data-foldpage-view-layout-count | integer | type-specific² | Ring: total slots. Grid: columns per row (0 = auto). Random/Scatter: PRNG seed (0 = unseeded). |
data-foldpage-view-layout-spacing | number | 1.0 | Uniform spacing multiplier. Fallback for -x, -y, -z overrides. Scatter: also controls the per-panel Z step. |
data-foldpage-view-layout-spacing-x | number | → spacing | X-axis spacing override. |
data-foldpage-view-layout-spacing-y | number | → spacing | Y-axis spacing override. Random: direction bias — positive = scatter only below head; negative = only above; 0 = symmetric ±Y. |
data-foldpage-view-layout-spacing-z | number | → spacing | Z-axis spacing override. Negative reverses Z direction. Random: direction bias — positive = scatter only behind; negative = only in front; 0 = symmetric ±Z. Scatter: per-panel Z step override (magnitude only, no direction bias — always ordered front-to-back). |
data-foldpage-view-layout-offset-x | px | 0 | Ring/Grid/Helix: X shift per successive step. Random/Scatter: global X bias added after scatter. |
data-foldpage-view-layout-offset-y | px | 0 | Ring/Grid/Helix: Y shift per step. Positive = lower on screen. Random/Scatter: global Y bias. |
data-foldpage-view-layout-offset-z | px | 0 | Ring/Grid/Helix: Z stagger per step. Random/Scatter: global Z bias. |
data-foldpage-view-layout-rotation | degrees | 0 | Base rotation value. Fallback for -x/y/z overrides. Meaning varies per layout — see Rotation. Scatter: a boolean toggle instead (any nonzero value), not a magnitude. |
data-foldpage-view-layout-rotation-x | degrees | → rotation | X rotation (pitch) override. Not used by scatter. |
data-foldpage-view-layout-rotation-y | degrees | → rotation | Y rotation (yaw) override. Not used by scatter. |
data-foldpage-view-layout-rotation-z | degrees | → rotation | Z rotation (roll) override. Not used by scatter. |
data-foldpage-view-layout-align | "center" / "start" | "center" | Horizontal alignment of panels. Partial last rows follow this setting. |
data-foldpage-view-layout-head | boolean | "true" | See Head View. |
data-foldpage-view-layout-scroll-distance | px | 1000 | Scroll distance per view transition. Only applies when absolute=true. |
data-foldpage-view-layout-top | px | auto | Panel top offset. Auto = head view bottom + 40 px (or head view top for random/scatter). Only applies when absolute=true. |
¹ Absolute positioning defaults: ring = true, grid = true, random = true, helix = false, scatter = true.
² Ring: panel count. Grid: ceil(sqrt(panelCount)). Random/Scatter: PRNG seed.
Per-panel view navigation attributes (on data-foldpage-view elements):
These change the camera's angle when it flies to a view, not the panel's own transform.
| Attribute | Type | Default | Description |
|---|---|---|---|
data-foldpage-rotate-x | degrees | 0 | Camera X tilt when navigating to this view. |
data-foldpage-rotate-y | degrees | 0 | Camera Y rotation when navigating to this view. Use 180 to approach from inside a ring (outward-facing). |
data-foldpage-rotate-z | degrees | 0 | Camera Z roll when navigating to this view. |
Head View
The element with data-foldpage-view="0" (or the first data-foldpage-view found) is the head view.
data-foldpage-view-layout-head | Ring | Grid | Helix |
|---|---|---|---|
"true" (default) | Head sits at center, position unchanged. Panels arranged around it. | Head stays in normal document flow at Z=0. Panels start one layer back. | Head is untouched (step 0). Each panel receives step × rotation. |
"false" | Head included as slot 0 in the ring. | Head is the first grid panel at (col=0, layer=0). | Head is panel at step 1. |
View Ordering
data-foldpage-view="<n>" sets explicit slot order for panels. Panels with numeric values are sorted by that number and assigned first; unnumbered panels follow in DOM order.
<section data-foldpage-view="2">Appears third</section>
<section data-foldpage-view="1">Appears second</section>
<section data-foldpage-view>Appears last (DOM order)</section>Auto Views
data-foldpage-view-auto instructs Fold to automatically discover view elements within a layout container and assign data-foldpage-view="" to each at runtime. No markup changes are needed on individual panels.
The discovery pass runs before the layout engine, so the layout sees the same view structure as if the attributes were hand-authored.
Selector Mode
Any valid querySelectorAll string tags every matching element within the container:
<div class="foldpage"
data-foldpage-view-layout="ring"
data-foldpage-view-auto=".card">
<header data-foldpage-view="0">Head</header>
<div class="card">Card 1</div> <!-- auto-tagged as data-foldpage-view="" -->
<div class="card">Card 2</div>
<div class="card">Card 3</div>
</div>Comma-separated and compound selectors work:
data-foldpage-view-auto=".card"
data-foldpage-view-auto="section.panel"
data-foldpage-view-auto="[data-panel]"Heading Mode
When the selector is a single heading tag (h1–h6), Fold uses ancestor-walk mode: it finds each heading's highest ancestor within the container that contains exactly that one heading, and tags that ancestor as the view. This naturally maps to section-structured markup.
<div class="foldpage"
data-foldpage-view-layout="helix"
data-foldpage-view-layout-rotation="30"
data-foldpage-view-auto="h2">
<header data-foldpage-view="0">Head</header>
<!-- section contains exactly one h2 → section is auto-tagged -->
<section class="panel">
<h2>Introduction</h2>
<p>Content...</p>
</section>
<section class="panel">
<h2>Getting Started</h2>
<p>Content...</p>
</section>
</div>Ancestor walk: starting from the <h2>, Fold walks up the DOM tree. Any ancestor that contains exactly one h2 becomes the candidate. The highest such ancestor (closest to the layout container) is tagged as the view.
Flat structure / Content Splitting: when multiple headings share the same direct parent (no wrapping sections), Fold automatically injects wrapper <div>s to group each heading with the siblings that follow it:
<!-- Source: headings are direct children of the container -->
<div class="foldpage" data-foldpage-view-auto="h2">
<p>Intro paragraph — left untouched</p>
<h2>Section A</h2>
<p>Content A</p>
<h2>Section B</h2>
<p>Content B</p>
</div>
<!-- Runtime result after auto-views runs: -->
<div class="foldpage" data-foldpage-view-auto="h2">
<p>Intro paragraph — left untouched</p>
<div data-foldpage-view-auto-wrapper="" data-foldpage-view="">
<h2>Section A</h2>
<p>Content A</p>
</div>
<div data-foldpage-view-auto-wrapper="" data-foldpage-view="">
<h2>Section B</h2>
<p>Content B</p>
</div>
</div>Content before the first heading is left in place. Injected wrappers are removed and their content is unwrapped when Fold.destroy() is called.
Merge Approach
Elements that already carry data-foldpage-view are never overwritten — auto-views and hand-authored markup coexist safely. The head view (data-foldpage-view="0") should always be manually marked and is unaffected by the discovery pass.
<div data-foldpage-view-layout="ring"
data-foldpage-view-auto=".card">
<!-- Manually pinned at slot 3 — skipped by auto-views -->
<div class="card" data-foldpage-view="3">Pinned</div>
<!-- Auto-discovered — no existing attribute -->
<div class="card">Auto A</div>
<div class="card">Auto B</div>
</div>JS API
Fold.init({
viewLayout: {
type: 'ring',
viewAuto: '.card', // global default selector
}
});The data-foldpage-view-auto attribute on the container takes precedence over the viewAuto JS option when both are set.
Absolute Positioning
Layout types differ in how they position panels:
| Layout | Default | Effect when true | Effect when false |
|---|---|---|---|
| ring | true | position:absolute + scroll spacer | Flow-positioned; elements stay in document flow |
| grid | true | position:absolute + scroll spacer | Flow-positioned |
| random | true | position:absolute + scroll spacer | Flow-positioned |
| helix | false | position:absolute + scroll spacer | Only CSS transform applied; elements in flow |
| scatter | true | position:absolute + scroll spacer | Flow-positioned |
When absolute=true, panels are absolutely positioned at a shared top offset and a scroll spacer is added so there's enough scroll distance to visit every view.
When absolute=false, only CSS transforms are applied (rotation and translation). Elements stay in their natural vertical order in the document — page height is determined by content. This is the helix default.
Overriding the Default
Data attribute (per-container):
<!-- Helix with absolute positioning (opt-in) -->
<div data-foldpage-view-layout="helix"
data-foldpage-view-layout-absolute="true"
data-foldpage-view-layout-rotation="40">...</div>
<!-- Ring without absolute positioning (opt-out) -->
<div data-foldpage-view-layout="ring"
data-foldpage-view-layout-absolute="false">...</div>JS API (global override):
Fold.init({
viewLayout: {
type: 'ring',
absolute: false, // override for all containers
}
});Priority: JS absolute option > data-foldpage-view-layout-absolute attribute > layout-type default.
When to use absolute=false
Use absolute=false when you want a layout type's 3D rotation/offset effects but prefer the page scroll height to be determined by actual content (not a synthetic spacer). The helix layout is designed around this mode — panels visually spiral in 3D while staying in normal document flow.
Ring Layout
Panels are placed at evenly-spaced angles around the head view in a circle (or partial arc). Each panel is auto-rotated to face inward by default.
How It Works
- The head element's position is unchanged (when
head="true"). - All other
data-foldpage-viewdescendants become ring panels, assigned to slots in order. - Ring radius = 700 px × (spacing + panels × 0.05). The ring grows automatically as panels are added.
- Angles are measured clockwise from North (−Z axis):
0°= North,90°= East,180°= South,270°= West. - Each panel's auto-rotation faces toward the origin (inward).
Ring Attribute Reference
Container attributes (in addition to generic attributes):
| Attribute | Type | Default | Description |
|---|---|---|---|
data-foldpage-view-layout-start | degrees | 0 | Angle of the first slot. 0 = North, 90 = East, 180 = South. |
data-foldpage-view-layout-direction | "row" / "row-reverse" | "row-reverse" | Slot stepping direction viewed from above ("row" = clockwise, "row-reverse" = counter-clockwise). Shares grid's vocabulary — ring has no "layer" concept, so only these two apply. |
data-foldpage-view-layout-empty | comma-separated indices | — | Slot indices to skip, e.g. "5,6,7". Enables arc configurations. |
JS API
Fold.init({
viewLayout: {
type: 'ring',
spacing: 1.5, // ring radius multiplier
count: 6, // total slots
start: 0, // first slot angle (degrees)
direction: 'row-reverse', // 'row' | 'row-reverse'
empty: [5], // leave slot 5 empty
scrollDistance: 1000,
top: 400, // px — omit for auto
absolute: true, // default for ring
}
});
// Live update — patches transforms in place
Fold.set({ viewLayout: { spacing: 0.8 } });Facing: Inward vs Outward
Panels face inward toward the center of the ring by default.
To make the camera approach from inside the ring, add data-foldpage-rotate-y="180" to the panel:
<!-- Camera approaches from inside (outward-facing view) -->
<section data-foldpage-view data-foldpage-rotate-y="180">...</section>data-foldpage-rotate-y only changes the camera's approach; the panel's own position and facing come from the layout.
Arc Configuration
Leave slots empty to form a half-circle, quarter-circle, or any partial arc:
<div class="foldpage"
data-foldpage-view-layout="ring"
data-foldpage-view-layout-count="8"
data-foldpage-view-layout-start="90"
data-foldpage-view-layout-direction="row"
data-foldpage-view-layout-empty="5,6,7">
<!-- 5 panels fill slots 0–4: East → SE → S → SW → West -->
</div>Slot math: step = 360° / count. With count=8 (step = 45°) and start=90°, slots step East → SE → S → SW → West → NW → N → NE. Marking slots 5–7 empty leaves a front-facing arc from East to West.
Grid Layout
Panels are placed in a rectangular grid. Columns spread along X; depth layers spread along Z. An optional Y offset per layer creates a staircase effect.
How It Works
- Panels are assigned to
[column, layer]positions in order, following thedirectionfill convention. - Column gap = 800 px × (
spacing-xorspacing). Layer gap = 700 px × (spacing-zorspacing). - Default
count=ceil(sqrt(panelCount))— a roughly square grid:
| Panels | Auto columns | Shape |
|---|---|---|
| 4 | 2 | 2 × 2 |
| 6 | 3 | 3 × 2 |
| 9 | 3 | 3 × 3 |
| 12 | 4 | 4 × 3 |
- Panels face the camera by default (no auto-rotation). Use
data-foldpage-rotate-yfor per-panel camera orientation. - Partial last rows follow the
alignsetting. - When
head="true"(default): the head view stays in normal flow at Z=0; panels start at Z = −layerGap. Whenhead="false": all views are grid panels.
Direction
Controls the fill axis and which end of the Z axis is first:
| Value | Fill axis | Z starts | First panel |
|---|---|---|---|
"row" (default) | X (columns) | front (Z=0) | front-left |
"row-reverse" | X (columns) | back | back-left → toward viewer |
"layer" | Z (depth) | front (Z=0) | front-left, fills depth-first |
"layer-reverse" | Z (depth) | back | back-left, fills toward viewer |
Left/right is controlled by align.
Grid Attribute Reference
All generic attributes apply. No grid-specific additions beyond direction (which takes different values from ring).
<div class="foldpage"
data-foldpage-view-layout="grid"
data-foldpage-view-layout-count="3"
data-foldpage-view-layout-spacing="1.2"
data-foldpage-view-layout-spacing-z="0.8"
data-foldpage-view-layout-offset-y="60"
data-foldpage-view-layout-direction="row">
<header data-foldpage-view="0">Center</header>
<section data-foldpage-view>P1</section>
<!-- ... 8 more panels for a 3×3 grid -->
</div>JS API
Fold.init({
viewLayout: {
type: 'grid',
count: 3, // columns per row
spacing: 1.0, // base multiplier for both X and Z gaps
spacingX: 1.2, // column gap override (800px × 1.2 = 960px)
spacingZ: 0.8, // layer gap override (700px × 0.8 = 560px)
offsetY: 60, // staircase: each layer 60px lower on screen
align: 'center',
direction: 'row',
head: true,
scrollDistance: 1000,
absolute: true, // default for grid
}
});
Fold.set({ viewLayout: { spacingZ: 1.2 } });Staircase Effect
Use offset-y to step each depth layer down the screen:
<div class="foldpage"
data-foldpage-view-layout="grid"
data-foldpage-view-layout-count="3"
data-foldpage-view-layout-offset-y="80">
<!-- Each layer is 80px lower than the previous -->
</div>Random Layout
Panels are placed at random positions and rotations within configurable scatter bounds. A seeded PRNG produces a stable, reproducible layout on every load when count is set.
How It Works
- Each panel receives a random (X, Y, Z) position and (rotX, rotY, rotZ) rotation independently.
- Scatter is centered on the origin (head view position) by default.
spacingcontrols the overall scatter radius (700 px × spacing).spacing-x/y/zoverride per-axis scatter independently.spacing-ydefaults tospacing × 0.5.countis repurposed as the PRNG seed.count = 0(default) usesMath.random()for a different layout each load.
Random Attribute Reference
| Attribute | Type | Default | Description |
|---|---|---|---|
data-foldpage-view-layout-count | integer | 0 | PRNG seed. 0 = unseeded. Any positive integer gives a fixed layout. |
data-foldpage-view-layout-spacing | number | 1.0 | Base scatter radius (700 px × spacing). |
data-foldpage-view-layout-spacing-x | number | → spacing | X scatter override (800 px × spacing-x). |
data-foldpage-view-layout-spacing-y | number | 0 | Y scatter + direction bias. > 0 = only below head; < 0 = only above; = 0 = symmetric ±Y (uses spacing × 0.5). |
data-foldpage-view-layout-spacing-z | number | 0 | Z scatter + direction bias. > 0 = only behind; < 0 = only in front; = 0 = symmetric ±Z. |
data-foldpage-view-layout-offset-x | px | 0 | Global X bias added to every panel after scatter. |
data-foldpage-view-layout-offset-y | px | 0 | Global Y bias. Positive = lower on screen. |
data-foldpage-view-layout-offset-z | px | 0 | Global Z bias. Negative pushes all panels behind the head view. |
data-foldpage-view-layout-rotation | degrees | 0 | Max rotation magnitude on all axes. |
data-foldpage-view-layout-rotation-x/y/z | degrees | → rotation | Per-axis max rotation override. |
JS API
Fold.init({
viewLayout: {
type: 'random',
count: 42, // seed — omit for unseeded
spacing: 0.4,
spacingY: 0.6, // scatter below head only (Y > 0)
spacingZ: 0.2,
offsetZ: -700, // push all panels 700px behind head
rotation: 20,
rotationY: 45,
scrollDistance: 1000,
absolute: true, // default for random
}
});Scatter Layout
Panels are placed at random X/Y positions within a bounded area — like the random layout — but Z is deliberately not random. Each panel is pushed back one fixed step further than the last, in panel order, so depth stays predictable even though X/Y don't.
How It Works
- Each panel receives a random (X, Y) position, independently.
- Z is an ordered step, not random: panel 0 sits at the default depth (Z = 0), panel 1 is one step further back, panel 2 two steps back, and so on.
spacingcontrols both the X/Y scatter extent (800 px × spacing for X, 700 px × spacing for Y) and the per-panel Z step (300 px × spacing).spacing-x/spacing-y/spacing-zoverride their respective axis independently, same fallback pattern as the other layout types.countis repurposed as the PRNG seed for the X/Y scatter (and rotation, when enabled) —count = 0(default) usesMath.random()for a different scatter each load; any positive integer reproduces the exact same layout every time.- Rotation is off by default. Setting
data-foldpage-view-layout-rotationto any value other than"false"/"0"/""enables a small random in-place tilt on X and Y only (never Z — cards stay flat-facing, just tilted) within a fixed ±25° range. This differs from every other layout type, whererotationis a magnitude — here it's a toggle, because the tilt amount is a single shared constant, not something meant to be dialed per axis.
Scatter Attribute Reference
| Attribute | Type | Default | Description |
|---|---|---|---|
data-foldpage-view-layout-count | integer | 0 | PRNG seed. 0 = unseeded. Any positive integer gives a fixed, reproducible layout (position AND rotation). |
data-foldpage-view-layout-spacing | number | 1.0 | X/Y scatter extent (800/700 px × spacing) AND per-panel Z step (300 px × spacing). |
data-foldpage-view-layout-spacing-x | number | → spacing | X scatter override (800 px × spacing-x). |
data-foldpage-view-layout-spacing-y | number | → spacing | Y downward scatter override (700 px × spacing-y). |
data-foldpage-view-layout-spacing-z | number | → spacing | Per-panel Z step override (300 px × spacing-z). Magnitude only — Z is always ordered front-to-back, never direction-biased like random's spacing-z. |
data-foldpage-view-layout-offset-x | px | 0 | Global X bias added to every panel after scatter. |
data-foldpage-view-layout-offset-y | px | 0 | Global Y bias. Positive = lower on screen. |
data-foldpage-view-layout-offset-z | px | 0 | Global Z bias added after each panel's ordered step. Negative pushes all panels further behind the head view. |
data-foldpage-view-layout-rotation | boolean | off | Toggle, not a magnitude. Any value other than "false"/"0"/"" enables a fixed ±25° random X/Y tilt per panel. |
JS API
Fold.init({
viewLayout: {
type: 'scatter',
count: 7, // seed — omit for unseeded
spacing: 1.0, // X/Y extent + Z step multiplier
spacingX: 1.4, // wider X scatter only
rotation: 1, // any nonzero value enables the ±25° X/Y tilt
scrollDistance: 1000,
absolute: true, // default for scatter
}
});
// Live update — patches transforms in place
Fold.set({ viewLayout: { spacing: 0.6, rotation: 0 } });Helix Layout
Panels receive a cumulative CSS transform — each successive panel gets one additional step of rotation and/or translation. Elements stay in normal document flow by default (no position: absolute). The visual spiral is purely CSS transform-based; document structure and scroll height are unaffected.
How It Works
- The head view (
step = 0) is untouched — no transform applied. - Each panel at index
ireceivesstep = i + 1increments:- Panel 1 →
rotation × 1 - Panel 2 →
rotation × 2 - Panel N →
rotation × N
- Panel 1 →
- Transforms are additive on all axes independently.
- By default (
absolute=false), elements remain in normal flow and no scroll spacer is added.
Helix Attribute Reference
| Attribute | Type | Default | Description |
|---|---|---|---|
data-foldpage-view-layout-rotation | degrees | 0 | Base Y-axis rotation per step. Fallback when rotation-y is not set. |
data-foldpage-view-layout-rotation-x | degrees | 0 | Per-step X rotation (pitch). |
data-foldpage-view-layout-rotation-y | degrees | 0 | Per-step Y rotation (yaw). Overrides rotation for Y when non-zero. |
data-foldpage-view-layout-rotation-z | degrees | 0 | Per-step Z rotation (roll). |
data-foldpage-view-layout-offset-x | px | 0 | Per-step X translate. |
data-foldpage-view-layout-offset-y | px | 0 | Per-step Y translate. Positive = lower on screen. |
data-foldpage-view-layout-offset-z | px | 0 | Per-step Z translate. Negative = push deeper per step. |
data-foldpage-view-layout-absolute | boolean | false | Opt into position:absolute mode with scroll spacer. |
data-foldpage-view-layout-scroll-distance | px | 1000 | Scroll distance per view transition. Only applies when absolute=true. |
data-foldpage-view-layout-top | px | auto | Panel top offset. Only applies when absolute=true. |
JS API
Fold.init({
viewLayout: {
type: 'helix',
rotation: 40, // 40° per step on Y axis
rotationX: 0, // pitch per step
rotationZ: 0, // roll per step
offsetZ: -200, // push 200px deeper per step (true Z-helix)
absolute: false, // default for helix — elements stay in flow
}
});Full Circle
With rotation="40" and 9 panels: 9 × 40° = 360° — a complete spiral back to the starting orientation:
<div class="foldpage"
data-foldpage-view-layout="helix"
data-foldpage-view-layout-rotation="40">
<header data-foldpage-view="0">0°</header>
<section data-foldpage-view>40°</section>
<section data-foldpage-view>80°</section>
<!-- ... through 360° at panel 9 -->
</div>Helix with Absolute Positioning
Opt into absolute positioning to get scroll-spacer-driven navigation:
<div class="foldpage"
data-foldpage-view-layout="helix"
data-foldpage-view-layout-rotation="40"
data-foldpage-view-layout-absolute="true"
data-foldpage-view-layout-scroll-distance="1200">Rotation
rotation / rotation-x/y/z behave differently per layout type:
| Layout | Meaning |
|---|---|
| Ring | rotation / rotation-y spin every panel in place by the same flat amount, on top of the auto-facing angle — it does not accumulate per panel and does not move panels around the ring. rotation-x / rotation-z are the opposite: cumulative per sequential panel slot (panel 0 = 0°, panel 1 = 1×value, panel 2 = 2×value), which tilts the ring into a progressively steeper cone rather than staying a flat circle. |
| Grid | rotation / rotation-y spin every panel in place by the same flat amount — it does not accumulate per layer and does not move panels off their grid slot. rotation-x / rotation-z are cumulative per depth layer (layer 0 = 0°, layer 1 = 1×value, layer 2 = 2×value) and tilt each layer's whole (x, y, z) position — a deliberate "fan of cards" effect. |
| Random | Maximum scatter magnitude. Each panel gets a random rotation uniformly distributed within ±value degrees on the given axis. |
| Helix | Per-step additive rotation. Each panel receives step × value on the configured axes. The head view (step 0) is never rotated. |
| Scatter | On/off toggle, not a magnitude. Any nonzero value enables a fixed ±25° random tilt on X and Y (never Z) for every panel. No per-axis override — see Scatter Layout. |
rotation is the base fallback when no per-axis override is set — for ring and grid this fallback only ever reaches rotation-y (never rotation-x/rotation-z, which are explicit-only). Set rotation-x="8" or rotation-z="8" directly to opt into the cumulative tilt effect on those axes.
<!-- Ring: every panel spins 15° in place (flat, not cumulative) -->
<div data-foldpage-view-layout="ring"
data-foldpage-view-layout-rotation-y="15">
<!-- Grid: each depth layer pitches 8° more forward -->
<div data-foldpage-view-layout="grid"
data-foldpage-view-layout-rotation-x="8">
<!-- Random: panels scatter with ±30° Y rotation, no X/Z -->
<div data-foldpage-view-layout="random"
data-foldpage-view-layout-rotation-y="30"
data-foldpage-view-layout-count="7">
<!-- Helix: each panel rotates 40° more on Y, diving 10° on X -->
<div data-foldpage-view-layout="helix"
data-foldpage-view-layout-rotation="40"
data-foldpage-view-layout-rotation-x="10">
<!-- Scatter: X/Y tilt enabled (toggle, not a magnitude), reproducible via seed -->
<div data-foldpage-view-layout="scatter"
data-foldpage-view-layout-rotation="1"
data-foldpage-view-layout-count="7">Scroll Spacer
When absolute=true (the default for ring, grid, random, and scatter), a <div data-foldpage-scroll-spacer> is automatically appended to the container with height = panels × scrollDistance. This provides sufficient scroll distance to animate through all views.
If a [data-foldpage-scroll-spacer] or .scroll-spacer element already exists in the container, its height is updated in place. The spacer is removed (or its height restored) on Fold.destroy().
When absolute=false (the default for helix), no spacer is injected — page height comes from content.
View Dimming
Place data-foldpage-view-dim on any layout container to fade non-active panels:
<div data-foldpage-view-layout="ring"
data-foldpage-view-dim="0.3">
<!-- Inactive panels at 30% opacity; active panel at 100% -->
</div>The default global dim value is 0.8. See View Dimming for full details, including the JS API (viewDim) and per-container override behavior.
Content Around Your Views
You don't need to wrap every piece of content in data-foldpage-view. Ordinary elements — a nav bar before your first view, a footer after your last one, or a plain section left between two views — are handled automatically as soon as at least one data-foldpage-view element exists on the page:
- Fold positions this content to continue naturally from whichever view it's closest to, instead of leaving it in its raw, unadjusted spot on the page.
- The scroll range automatically extends to include it, so it pans into view along with everything else — nothing gets skipped or stranded.
- Several pieces of non-view content sitting next to each other keep their natural spacing relative to one another.

