Media & effects
Car Smoke Page Transition
Cinematic automotive page transition
detent-car-smoke-page-transition
Port status
- Catalog status
- implemented
- Origin
- upstream
- Phase
- 4
- Wave
- phase-4-cinematic
- Group
- Media & effects
8.4 KB gzip for the class module
Variants
The trigger is the real light-DOM button above, so Enter and Space work with native semantics
and the button keeps its own name. Activating it runs the pinned choreography: the car crosses for
car-duration (700 ms) with body pitch, roll and wheel spin, a canvas paints drift smoke, skid marks and
speed lines, the wipe fades in at car-duration × 0.65, and
detent-transition-complete fires at car-delay + page-transition-delay + 100 ms — the point
at which a consumer navigates. The overlay is fixed, hidden and inert at rest, and it owns the pointer while it
runs. With prefers-reduced-motion: reduce no car, wheel or canvas animation runs, but the same event
fires and the same final state is reached. This demo never navigates.
No transition yet.
Recorded states
dark themerecorded state; no setterreduced motionrecorded state; no setter
Usage
Import
import '/components/car-smoke-page-transition/define.js';
// or register it yourself
import { DetentCarSmokePageTransition } from '/components/car-smoke-page-transition/car-smoke-page-transition.js';
Attributes
car-delaycar-durationcar-sizedirectionpage-transition-delaypage-transition-durationreset-delayshow-speed-linessmoke-densitysmoke-lifetime
Properties
onTransitionCompleteverticalPosition
Slots
default
Events
detent-transition-complete— {}
Example
Runnable example: site/examples/car-smoke-page-transition.ts. The snippet below is generated from the catalogued attributes.
<detent-car-smoke-page-transition
car-delay="0"
car-duration="700"
car-size="md"
direction="left-to-right"
page-transition-delay="550"
page-transition-duration="350"
reset-delay="300"
show-speed-lines
smoke-density="medium"
smoke-lifetime="1200"
></detent-car-smoke-page-transition>
Property-only values
const element = document.querySelector('detent-car-smoke-page-transition');
if (element !== null) {
// onTransitionComplete: () => void
element.onTransitionComplete = () => {};
// verticalPosition: 'top' | 'center' | 'bottom'
element.verticalPosition = 'center';
}
Source
The pinned upstream source for this row:
src/components/ui/CarSmokePageTransition.tsxsha256 7c1a5d0f9863…
Native files
The native module and its artifacts:
src/components/car-smoke-page-transition/car-smoke-page-transition.tssrc/components/car-smoke-page-transition/styles.csssrc/components/car-smoke-page-transition/define.tsdocs/components/car-smoke-page-transition.mdsite/examples/car-smoke-page-transition.tstests/browser/car-smoke-page-transition.spec.tsevidence/components/car-smoke-page-transition.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 |
|---|---|---|---|---|
car-delay | carDelay | number | 0 | Kebab-case attribute; camelCase property. |
car-duration | carDuration | number | 700 | Kebab-case attribute; camelCase property. |
car-size | carSize | 'sm' | 'md' | 'lg' | 'md' | Kebab-case attribute; camelCase property. |
direction | direction | 'left-to-right' | 'right-to-left' | 'left-to-right' | Kebab-case attribute; camelCase property. |
page-transition-delay | pageTransitionDelay | number | 550 | Kebab-case attribute; camelCase property. |
page-transition-duration | pageTransitionDuration | number | 350 | Kebab-case attribute; camelCase property. |
reset-delay | resetDelay | number | 300 | Kebab-case attribute; camelCase property. |
show-speed-lines | showSpeedLines | boolean | true | Presence semantics: present means true, absent means false. |
smoke-density | smokeDensity | 'off' | 'low' | 'medium' | 'high' | 'medium' | Kebab-case attribute; camelCase property. |
smoke-lifetime | smokeLifetime | number | 1200 | Kebab-case attribute; camelCase property. |
Properties
| Property | Type | Default | Notes |
|---|---|---|---|
onTransitionComplete | () => void | undefined | Property-only: assign from script; not accepted as an attribute. |
verticalPosition | 'top' | 'center' | 'bottom' | 'center' | Property-only: assign from script; not accepted as an attribute. |
Events
| Event | Upstream prop | Payload type | Notes |
|---|---|---|---|
detent-transition-complete | onTransitionComplete | () => void | Composed, bubbles; fires only for a user action. |
Slots
| Slot | Notes |
|---|---|
default | Consumer content; rendered through a slot, never re-created. |
CSS customisation
- React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
- `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
- 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.
- The overlay owns the pointer while it runs and is hidden/inert at idle; the upstream overlay is pointer-events-none.
- The trigger keeps native semantics: upstream wrapped children in a role=button span with a hard-coded aria-label and its own key handler, while the native element makes the slotted control the trigger and calls preventDefault() without stopPropagation(), so consumer listeners still run (PC-11.6).
- The fallback trigger is the pinned Launch Transition button.
- Theme resolves through tokens and prefers-color-scheme instead of useTheme(); --detent-car-smoke-wipe is the override.
- The dir=ltr wrapper is dropped because direction drives the geometry and the mirrored artwork.
- showSpeedLines (default true) is reflected and the property is the opt-out.
What it does
A cinematic page transition: the slotted trigger launches a fixed overlay, a supercar travels across the viewport with body pitch/roll and spinning wheels, a canvas paints drift smoke, skid marks and speed lines, a wipe fades in, and detent-transition-complete fires when the choreography ends — the point at which the consumer navigates.
Upstream behaviour
- Hardware-accelerated Canvas particle drift smoke physics and realistic skid marks
- Choreographed multi-axis Ferrari drift movement, wheel spin, and speed lines
- Customizable travel direction (left-to-right or right-to-left), density, and scale
- Automatic theme awareness (light and dark mode overlay shading)
- Full accessibility support with automatic prefers-reduced-motion fallback
Differences from upstream
- React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
- `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
- 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.
- The overlay owns the pointer while it runs and is hidden/inert at idle; the upstream overlay is pointer-events-none.
- The trigger keeps native semantics: upstream wrapped children in a role=button span with a hard-coded aria-label and its own key handler, while the native element makes the slotted control the trigger and calls preventDefault() without stopPropagation(), so consumer listeners still run (PC-11.6).
- The fallback trigger is the pinned Launch Transition button.
- Theme resolves through tokens and prefers-color-scheme instead of useTheme(); --detent-car-smoke-wipe is the override.
- The dir=ltr wrapper is dropped because direction drives the geometry and the mirrored artwork.
- showSpeedLines (default true) is reflected and the property is the opt-out.