Above & Below

Y-axis spatial navigation — panels rise and fall around the origin.
Scroll to climb up through the sky, then descend into the depths.

CSS Position

Fixed, Sticky & Absolute

Fold respects CSS position values and maps each to its 3D equivalent, preserving the layering and attachment semantics you already know from the browser.

Fixed

Elements with position: fixed are rendered as non-scrolling overlays in the 3D scene. They remain locked to the camera view as the user scrolls — ideal for navigation bars, HUDs, or persistent controls that should always be visible regardless of scene position.

<!-- Fixed: always visible, doesn't scroll away -->
<nav style="position: fixed; top: 0;" class="my-nav">...</nav>

Sticky

Sticky elements mirror the browser's built-in sticky behavior. While the viewport scroll is within the parent's bounds, the element is rendered at a fixed position in 3D space. Once the parent scrolls out of range, it moves with the scroll group as normal. Fold tracks both states and transitions between them using a live getBoundingClientRect().top threshold check.

<!-- Sticky: sticks at top until parent scrolls away -->
<header style="position: sticky; top: 0;">Section Title</header>

Absolute

Absolutely positioned elements are placed relative to their nearest positioned ancestor in the 3D scene graph, matching browser layout semantics. Fold's findPositionedAncestor() walks the DOM tree upward to locate the correct parent group and nests the element within it at its CSS-computed offset.

<!-- Absolute: relative to nearest positioned parent -->
<div style="position: relative;">
  <span style="position: absolute; top: 10px; right: 10px;">Badge</span>
</div>
fixed

Camera-locked overlay. Does not scroll. Rendered outside the main scroll group.

sticky

Two 3D instances: one in scroll group, one fixed — toggled by scroll position threshold.

absolute

Nested under its positioned ancestor group. Offset by CSS top/left/right/bottom.

static / relative

Default flow. Nested by DOM parent–child structure in the scene graph.

This panel is view-sky — rotateX(-22deg) translateY(-680px) translateZ(260px). The camera approached from below, looking slightly upward to read it.

Text & Typography

Rich Text Rendering

Fold renders HTML text content as live 3D geometry — not bitmaps or sprites. Each paragraph, heading, link, and inline element is processed by Troika, a WebGL-native text renderer that produces signed-distance-field meshes directly in the Three.js scene.

Supported Elements

All standard text containers are handled: <p>, <h1>–<h6>, <li>, <blockquote>, <label>, and <td>. Inline elements — <strong>, <em>, <a>, <code>, <span> — are parsed within their parent text block and rendered with per-character style overrides.

Inline Style Ranges

Fold uses a fork of Troika that adds styleRanges support — the ability to override font weight, font style, and color on arbitrary character ranges within a single text mesh. This means a paragraph with a bold word, an italic phrase, and a coloured link is a single draw call, not three separate meshes.

<p>This text has <strong>bold</strong> and <em>italic</em> words
   rendered natively in 3D — no images, live geometry.</p>

CSS Properties Respected

Fold reads computed styles at render time and maps them to Troika text properties. The following CSS properties are supported on text elements:

Font

font-size, font-weight, font-style, font-family

Color & Shadow

color, text-shadow (multiple layers supported)

Layout

line-height, letter-spacing, text-align, text-transform

Decoration

text-decoration: underline — custom geometry via Troika's getSelectionRects

Font Configuration

Fold ships with two TTF font families loaded from /public/fonts/: Caladea (serif) with regular, bold, italic, and bold-italic variants, and Lato (sans-serif) with the same four variants. These cover the majority of font-weight and font-style combinations you'd use in practice.

Custom fonts can be added by pointing CSS font-family to a TTF file accessible via your server. Troika loads font files on demand and caches them for the lifetime of the page — no pre-registration required.

Text Shadows

CSS text-shadow declarations are parsed and rendered as additional Troika text meshes placed behind the primary text mesh. Each shadow layer becomes a separate draw call with the shadow's offset, blur, and color applied. Multiple comma-separated shadows are all rendered.

h1 {
  text-shadow:
    0 0 40px rgba(100, 160, 255, 0.4),
    -1px 0 rgba(240, 218, 67, .5);
}

Underlines

When text-decoration: underline is present — or when an <a> element inherits underline styling — Fold generates a custom Underlines component. It calls Troika's getSelectionRects to obtain precise glyph bounding boxes, then places thin rectangular meshes beneath each underlined character run. This approach handles word-wrapping and proportional widths correctly without any CSS approximation.

Interactive Text

Clicking or hovering text ranges is supported via raycasting against the bounding boxes returned by getSelectionRects. Links styled with data-foldpage-view-link are fully interactive in 3D — the camera flies to the target view on click, and hover states update correctly even when the text mesh is rotated in space.

This works because Fold's event manager uses clientX / clientY coordinates against the canvas, then intersects rays with per-character bounding quads rather than the text mesh as a whole. A user clicking the second word of a two-word link activates the link correctly.

This panel is the tallest view — view-apex at rotateX(-38deg) translateY(-1250px) translateX(200px) translateZ(420px). The camera tilts 38° back to read it fully.

Overflow & Scroll

Nested Scrolling Containers

Elements with overflow: scroll or overflow: auto that contain enough content to overflow are rendered as stencil-masked scroll containers in 3D. The outer bounds of the element clip via a WebGL Mask plane; an inner spring-animated group handles vertical scroll driven by the canvas wheel event.

<div style="overflow: auto; height: 300px; width: 100%;">
  <p>Long content that scrolls independently...</p>
  <p>...within its clipped 3D region.</p>
</div>

What Triggers Each Behavior

hidden / clip

Content is clipped at the element's bounds. No scrolling. Stencil mask applied, no scroll group.

scroll / auto

Clips and scrolls when scrollHeight > clientHeight. Spring-animated inner group.

How It Works

Fold assigns each overflow container a stencil ID from a shared pool (IDs 10–254, wrapping). The outer element renders an OverflowClipMask — a drei Mask plane at the container's screen bounds that writes its ID to the WebGL stencil buffer.

All child meshes within that container read the stencil context via React's OverflowStencilContext and apply useMask props so they only draw where the stencil matches. The effect is a hard rectangular clip that works correctly in 3D space — even when the container itself is rotated or translated.

Scrolling is handled by an OverflowScrollGroup — a spring-based Y-translation group that listens for wheel events on the canvas. It initialises from the DOM element's current scrollTop so that content already scrolled in the DOM appears correctly on first render. Bidirectional sync keeps the DOM's scrollTop in step with the 3D scroll position.

This panel is below the origin — view-low: rotateX(22deg) translateY(680px) translateZ(260px). The camera approached from above, angling slightly downward.

The Pipeline

DOM → 3D Architecture

Fold's core loop runs once on Fold.init() and produces a static Three.js scene from the current DOM. The pipeline has five stages:

1. Query Fold walks the DOM finding elements that match uiConfNodes selectors — configured tag names and CSS classes mapped to render types (Text, Background, Image, Video).
2. Position FoldGroupElement reads each element's getBoundingClientRect() and scroll offsets, then places a Three.js group at the element's center in world space using the pixelSizeDefault = 0.0007 scale factor.
3. Geometry Element type determines the mesh: text nodes → Troika TText meshes; block backgrounds → RoundedPlane; images → ImageMesh; video → FoldVideo texture plane.
4. Spring FoldGroup wraps all scene content in a react-spring animated group. Page scroll, window resize, and XR thumbstick/touch input all drive the spring target, which eases the group into position each frame.
5. Views Camera framing for data-foldpage-view elements uses getBoundingClientRect() combined with CSS transform extraction to compute the element's world-space normal, then positions the camera along that normal at a comfortable viewing distance.

The result is that any page already marked up with semantic HTML can become a 3D spatial experience by adding the foldpage class, a few data-foldpage-view attributes, and CSS transforms on .myelement.foldpage-el selectors.

Deepest point — view-deep: rotateX(38deg) translateY(1250px) translateX(-180px) translateZ(420px). You are now approximately 875px below the origin in CSS space, the camera tilted 38° down.

Explore the other demos at the demos index or read the Views & Navigation Docs to learn about data attributes, camera controls, WebXR, and the complete API surface.