Left & Right

Horizontal spatial navigation — panels spread along the X axis.
Each section cancels its page Y position and offsets along X (and Z).

foldpage-el

The CSS Class API

When Fold processes an element it adds the foldpage-el class to it. This class is the hook for all CSS-driven 3D positioning. Any rule that targets .myclass.foldpage-el is invisible to the browser's layout engine but drives the element's position, rotation, and scale in the Three.js scene.

The approach keeps 3D authoring entirely in CSS — no JavaScript, no separate coordinate system to learn. You write familiar transform rules, and Fold extracts and applies them as group transforms in world space.

/* Only active when Fold is running */
.hero.foldpage-el {
  transform: rotateY(-30deg) translateX(600px);
}

/* Children can add their own depth pops */
.hero h1.foldpage-el {
  transform: translateZ(28px);
}
.hero img.foldpage-el {
  transform: translateZ(48px);
}
Container transform

Positions the whole section in 3D — rotates and translates the group origin.

Child depth pop

Children add a local translateZ to float in front of their parent plane.

No JS required

Fold reads computed styles at render time — plain CSS, no script.

Inactive by default

Rules with .foldpage-el are ignored by the browser until Fold adds that class.

This panel is panel-far-left — far left at rotateY(42deg) translateX(-860px), with translateY(-1360px) applied first to cancel its page Y position.

Styling in 3D

Color & Theming

Fold reads each element's computed style at render time, so any CSS you write — colors, gradients, borders, opacity, box-shadow — is honored in the 3D scene. Background planes use MeshStandardMaterial; colors are converted from CSS rgba() to Three.js Color.

Background & Borders

An element's background color, gradient, or image becomes the texture of a rounded-rectangle plane mesh. Border radius is preserved. Box shadows render as separate offset planes at a slight Z depth behind the main mesh.

/* All of these render faithfully in 3D */
.card {
  background: rgba(12, 8, 30, 0.9);
  border: 1px solid rgba(160, 100, 240, 0.3);
  border-radius: 16px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
}

Text Color & Shadows

Text color, text-shadow, opacity, and letter-spacing all flow through to Troika. Multiple shadow layers render as stacked text meshes slightly behind the main text, using their CSS color and blur values.

Dark mode / light mode work naturally — Fold captures whatever getComputedStyle() returns after your media queries resolve. Switching themes just re-renders with the new computed values.

Horizontal Layout Technique

Y Compensation

HTML elements flow vertically — each section sits lower on the page than the one before it. In a standard Fold layout that natural Y offset becomes part of the element's 3D world position. For a horizontal layout, where all panels should sit at roughly the same height, you need to cancel that offset with a negative translateY.

The Technique

The key rule: put translateY(-offset) before any rotation in the transform chain. CSS transforms apply left-to-right, each one changing the local coordinate frame. If translateY comes first — before rotateY — it operates in the original world frame and cancels the element's page-scroll Y position exactly. If it comes after a rotation, the Y axis is no longer aligned with the world Y axis and the compensation will be off.

/* ✓ translateY FIRST — works in world frame */
.panel.foldpage-el {
  transform:
    translateY(-1880px)   /* cancel page Y, world frame */
    rotateY(20deg)       /* now rotate local frame */
    translateX(-440px)  /* move in rotated local X */
    translateZ(-200px); /* depth in rotated local Z */
}

/* ✗ translateY AFTER rotation — compensates wrong axis */
.panel.foldpage-el {
  transform:
    rotateY(20deg)
    translateY(-1880px)  /* now tilted — wrong! */
    translateX(-440px);
}

Estimating the Offset

The offset is the vertical center of the element in the page, measured in CSS pixels from the top of the document. A rough estimate:

Formula offset = sum of all preceding element heights + margins + (this element's height / 2)

For this demo, with a min-height: 115vh home section (~1050px at a 900px viewport) and panels averaging ~520px slot height (content + 100px margin):

Panel 1 1050 + 100 + 210 = 1360px
Panel 2 1050 + 100 + 420 + 100 + 210 = 1880px
Panel 3 1050 + 100 + 420 + 100 + 420 + 100 + 350 = 2540px (tall panel)
Panel 4 1050 + 100 + 420 + 100 + 420 + 100 + 700 + 100 + 210 = 3300px

Tuning in Practice

These estimates are calculated for a ~900px tall viewport. At different viewport heights, min-height: 115vh produces different absolute pixel values, shifting all subsequent panels. If the horizontal alignment looks off, adjust the translateY values in increments of a few hundred pixels.

An alternative approach: set a fixed pixel height on the home section (e.g. height: 900px) rather than min-height: 115vh. This makes the Y offsets viewport-independent and easier to calculate.

Combining X, Z, and Y

After the Y compensation, the remaining transforms position the panel in the horizontal plane. The outer panels in this demo use a stronger rotateY and are pushed slightly forward in Z (translateZ(160px)) so they arc toward the viewer — creating the impression of a curved horizontal stage rather than a flat row. The inner panels are pushed slightly back (translateZ(-200px)) to reinforce the curve.

This panel is panel-near-right: translateY(-2540px) rotateY(-20deg) translateX(440px) translateZ(-200px). It is the tallest view, so the camera pans through it before moving to the far right.

Template

The Horizontal Pattern

A minimal horizontal views layout needs three things: a tall home section, panels with predictable heights, and Y compensation on each panel.

<header class="home" data-foldpage-view="0">
  <h1>Page Title</h1>
  <nav>
    <a href="#left"  data-foldpage-view-link="#left">Left</a>
    <a href="#right" data-foldpage-view-link="#right">Right</a>
  </nav>
</header>

<section id="left"  class="panel-left"  data-foldpage-view>...</section>
<section id="right" class="panel-right" data-foldpage-view>...</section>

<style>
  .home { height: 900px; } /* fixed height for predictable Y offsets */

  .panel-left.foldpage-el {
    transform:
      translateY(-1100px)  /* 900 + 100 margin + 100 half-panel */
      rotateY(40deg)
      translateX(-800px)
      translateZ(150px);
  }
  .panel-right.foldpage-el {
    transform:
      translateY(-1700px)  /* 900 + 100 + 200 panel + 100 + 100 half-panel */
      rotateY(-40deg)
      translateX(800px)
      translateZ(150px);
  }
</style>

See the demos index for the full set of view layout patterns, or explore Views & Navigation for the radial layout where panels face a central origin from all directions.

This panel is panel-far-right — translateY(-3300px) rotateY(-42deg) translateX(860px) translateZ(160px). The Y compensation brought it from ~3300px down the page to the same height as the origin.