Media & effects
Depth Corridor
CSS 3D perspective tunnel with progressive depth blur
detent-depth-corridor
Port status
- Catalog status
- implemented
- Origin
- upstream
- Phase
- 4
- Wave
- phase-4-cinematic
- Group
- Media & effects
7.5 KB gzip for the class module · inlines the shared spring
Variants
A 3D perspective corridor: cards recede along translateZ with a progressive focal blur, and the active
layer springs forward with the pinned springSmooth physics (stiffness 200, damping 28, mass 0.85). Use
the arrow keys, the arrow buttons or the indicator pills — each change dispatches one
detent-layer-change with detail { index }. Moving the pointer over a corridor adds the
parallax tilt.
no layer change yet
Six layers with the third active: only the three nearest layers on each side are in the DOM, so the window is seven cards at most. The status line below each corridor is driven by the documented event.
no layer change yet
With prefers-reduced-motion: reduce the springs are not created at all: the layer change is applied
immediately, the parallax is off and the focal blur is removed. The layer body text is always text — the component
never parses markup.
Recorded states
reduced motionrecorded state; no setter
Usage
Import
import '/components/depth-corridor/define.js';
// or register it yourself
import { DetentDepthCorridor } from '/components/depth-corridor/depth-corridor.js';
Attributes
active-layer-indexenable-parallaxperspective-depth
Properties
layersactiveLayerIndexenableParallaxperspectiveDepthonLayerChange
Slots
No slots are recorded for this component.
Events
detent-layer-change— { index: number }
Example
Runnable example: site/examples/depth-corridor.ts. The snippet below is generated from the catalogued attributes.
<detent-depth-corridor
active-layer-index="0"
enable-parallax
perspective-depth="1000"
></detent-depth-corridor>
Property-only values
const element = document.querySelector('detent-depth-corridor');
if (element !== null) {
// layers: DepthLayer[]
element.layers = [];
// onLayerChange: (index: number) => void
element.onLayerChange = () => {};
}
Source
The pinned upstream source for this row:
src/components/ui/DepthCorridor.tsxsha256 d794f0b39eda…
Native files
The native module and its artifacts:
src/components/depth-corridor/depth-corridor.tssrc/components/depth-corridor/styles.csssrc/components/depth-corridor/define.tsdocs/components/depth-corridor.mdsite/examples/depth-corridor.tstests/browser/depth-corridor.spec.tsevidence/components/depth-corridor.md
API
Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.
Attributes
| Attribute | Property | Type | Default | Notes |
|---|---|---|---|---|
active-layer-index | activeLayerIndex | number | 0 | Kebab-case attribute; camelCase property. |
enable-parallax | enableParallax | boolean | true | Presence semantics: present means true, absent means false. |
perspective-depth | perspectiveDepth | number | 1000 | Kebab-case attribute; camelCase property. |
Properties
| Property | Type | Default | Notes |
|---|---|---|---|
layers | DepthLayer[] | — | Property-only: assign from script; not accepted as an attribute. |
activeLayerIndex | number | 0 | Property-only: assign from script; not accepted as an attribute. |
enableParallax | boolean | true | Property-only: assign from script; not accepted as an attribute. |
perspectiveDepth | number | 1000 | Property-only: assign from script; not accepted as an attribute. |
onLayerChange | (index: number) => void | undefined | Property-only: assign from script; not accepted as an attribute. |
Events
| Event | Upstream prop | Payload type | Notes |
|---|---|---|---|
detent-layer-change | onLayerChange | (index: number) => void | Composed, bubbles; fires only for a user action. |
Slots
No slots are recorded for this component.
CSS customisation
- React callback props become typed composed CustomEvents; programmatic state assignment emits none.
- Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
- `lucide-react` icon components replaced with local SVG assets (ISC notice retained).
- React state/hooks replaced by element-owned state with pre-upgrade property preservation.
- Layer content is text: the row records no slots.
- blurAmount is declared upstream but never read.
- Opacity is continuous in the interpolated offset; upstream switched formula at the current layer and jumped by 0.25.
- There is no controlled/uncontrolled split: the attribute is authoritative and reflected as a clamped value.
- A clamped no-op emits nothing.
- Reduced motion is implemented even though the pinned source never read the preference.
- Pills and arrows are real buttons with aria-current and accessible names.
- The horizontal depth step shrinks below the narrowest recorded viewport so the stack never overflows.
- lucide-react chevrons become local inline SVGs (the ISC notice is retained in THIRD_PARTY_NOTICES.md).
- Framer springSmooth becomes the shared spring helper at the same 200/28/0.85 physics.
What it does
A CSS 3D perspective corridor: layered cards recede along a translateZ depth axis with a progressive focal blur and a spring transition, plus pointer parallax and arrow-key navigation.
Upstream behaviour
- True 3D perspective translation (translateZ) and multi-layered receding depth geometry
- Progressive depth-of-field focal blurring and subtle mouse tracking parallax
- Keyboard arrow navigation (Left/Right/Up/Down) with smooth spring transitions
- Tactile indicator pills and interactive layer selection
Differences from upstream
- React callback props become typed composed CustomEvents; programmatic state assignment emits none.
- Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
- `lucide-react` icon components replaced with local SVG assets (ISC notice retained).
- React state/hooks replaced by element-owned state with pre-upgrade property preservation.
- Layer content is text: the row records no slots.
- blurAmount is declared upstream but never read.
- Opacity is continuous in the interpolated offset; upstream switched formula at the current layer and jumped by 0.25.
- There is no controlled/uncontrolled split: the attribute is authoritative and reflected as a clamped value.
- A clamped no-op emits nothing.
- Reduced motion is implemented even though the pinned source never read the preference.
- Pills and arrows are real buttons with aria-current and accessible names.
- The horizontal depth step shrinks below the narrowest recorded viewport so the stack never overflows.
- lucide-react chevrons become local inline SVGs (the ISC notice is retained in THIRD_PARTY_NOTICES.md).
- Framer springSmooth becomes the shared spring helper at the same 200/28/0.85 physics.