
Horizontal views via position: absolute — all panels share
the same page Y so transforms are direct X, Z, and small Y offsets.
position: absolute
In the relative-flow approach (views-horizontal.html), each panel
sits lower in the page than the one before it — panel 1 might be at Y=1360px,
panel 4 at Y=3300px. To bring them to the same 3D height, each needs a
different, large negative translateY:
−1360px, −1880px, −2540px, −3300px.
Those values must be re-estimated whenever content height changes.
With position: absolute all panels share the same top
value in the page, so their base Y position is identical. The CSS 3D transform
only needs to specify intentional offsets — a consistent rotation and
X/Z translation, with small optional Y tweaks for deliberate vertical variation.
No compensation math, no large magic numbers.
/* All panels at the same DOM Y — direct transforms */ .abs-panel { position: absolute; top: 300px; /* shared baseline — same for all */ left: 0; right: 0; margin: 0 auto; max-width: 700px; } .panel-far-left.foldpage-el { transform: rotateY(42deg) translateX(-880px) translateZ(160px); /* no translateY needed — panel is already at the right height */ } .panel-near-left.foldpage-el { /* small translateY = intentional 80px lift, not compensation */ transform: translateY(-80px) rotateY(20deg) translateX(-440px) translateZ(-200px); }
All panels share top: 300px — the 3D Y difference between them is whatever you put in the transform.
Changing a panel's content height doesn't shift any other panel's Y position in the page.
To shift a panel 200px higher in 3D, write translateY(-200px) — nothing to reverse-engineer.
Absolute panels don't extend the page. A .scroll-spacer div provides the scroll room instead.
panel-far-left — transform:
rotateY(42deg) translateX(-880px) translateZ(160px).
No Y offset — it sits at exactly top: 300px.
Scroll Room
Fold derives the total scroll range from document.scrollHeight − window.innerHeight.
Absolute elements don't contribute to scrollHeight — only elements
in normal document flow do. Without intervention, a page of all absolute panels
would have barely any scroll room and the view system would stall.
The fix is a .scroll-spacer div in normal flow at the end of the
wrapper. Its height is the scroll room budget for all view transitions.
<div id="wrapper" class="foldpage" style="position:relative"> <header data-foldpage-view="0" style="min-height:115vh">...</header> <!-- position:absolute — all at the same top --> <section class="panel-left" style="position:absolute;top:300px" data-foldpage-view>...</section> <section class="panel-right" style="position:absolute;top:300px" data-foldpage-view>...</section> <!-- Normal-flow spacer: adds the scroll room absolute panels can't --> <div class="scroll-spacer"></div> </div>
The scroll range used by the view system is split into equal segments, one per view transition. Rough formula:
/* Minimum scroll spacer height */ .scroll-spacer { height: calc( (N_views − 1) × 100vh /* one viewport per transition */ + 25% buffer /* headroom for tall zones */ ); }
This demo has 5 views (0–4) so 4 transitions. At 900px viewport:
4 × 900 × 1.25 ≈ 4500px. The home section (min-height: 115vh)
already contributes ~1035px, but the spacer covers the rest as a flat guarantee —
it's simpler to over-size it than to calculate exactly.
panel-near-left:
translateY(-80px) rotateY(20deg) translateX(-440px) translateZ(-200px).
The translateY(-80px) is a deliberate 80px upward shift — not
compensation for page flow.
Side by Side
Both techniques produce identical 3D results — the difference is only in how the CSS is authored and how robust it is to content changes.
/* Each panel needs its own * large Y compensation */ .p1.foldpage-el { transform: translateY(-1360px) rotateY(42deg) translateX(-860px) translateZ(160px); } .p2.foldpage-el { transform: translateY(-1880px) rotateY(20deg) translateX(-440px) translateZ(-200px); } .p3.foldpage-el { transform: translateY(-2540px) ... }
/* All panels at top:300px. * No per-panel Y offsets. */ .p1.foldpage-el { transform: rotateY(42deg) translateX(-880px) translateZ(160px); } .p2.foldpage-el { transform: translateY(-80px) rotateY(20deg) translateX(-440px) translateZ(-200px); } .p3.foldpage-el { transform: translateY(80px) ... }
Content naturally flows vertically (articles, docs) and the Y layout is meaningful.
Panels are layout-independent cards or panels with no natural vertical relationship.
Panels overlap in the flat browser view (before Fold activates). Fine for Fold-first pages.
Changing panel content height doesn't cascade — each panel's position is independent.
The home section's center Y depends on min-height: 115vh, which
changes with viewport height. The absolute panels are at a fixed top: 300px,
so at a 900px viewport they appear slightly above the home section center (~517px).
If you want exact alignment, use a fixed home height instead of min-height:
.view-home { height: 900px; } /* fixed — viewport-independent */ .abs-panel { top: 250px; } /* 900/2 - panel_height/2 = 250px */
panel-near-right:
translateY(80px) rotateY(-20deg) translateX(440px) translateZ(-200px).
The translateY(80px) intentionally drops it 80px below the outer panels
to reinforce the horizontal arc shape.
Starter Template
A minimal absolute-positioning horizontal layout. Fixed home height for predictable panel alignment, a sized scroll spacer, and clean transforms.
<div id="wrapper" class="foldpage"> <header class="home" data-foldpage-view="0"> <h1>My Page</h1> <a href="#left" data-foldpage-view-link="#left">Left</a> <a href="#right" data-foldpage-view-link="#right">Right</a> </header> <section id="left" class="panel-left" data-foldpage-view>Left</section> <section id="right" class="panel-right" data-foldpage-view>Right</section> <div class="scroll-spacer"></div> </div> <style> #wrapper { position: relative; } .home { height: 900px; } /* fixed — easier math */ .panel-left, .panel-right { position: absolute; top: 250px; /* 900/2 − panel_h/2 ≈ home center */ left: 0; right: 0; margin: 0 auto; max-width: 700px; } /* 2 transitions × 900px × 1.25 buffer */ .scroll-spacer { height: 2250px; } .panel-left.foldpage-el { transform: rotateY(40deg) translateX(-800px) translateZ(150px); } .panel-right.foldpage-el { transform: rotateY(-40deg) translateX(800px) translateZ(150px); } </style>
See views-horizontal.html for the relative-flow version of this pattern, or all demos for the full set.
panel-far-right:
rotateY(-42deg) translateX(880px) translateZ(160px).
Symmetric with the far-left panel — same magnitude, opposite sign, no Y at all.