Lena ParkMotion designer
Layers
Ridge studyFour layers, one card
React + Tailwind
const css = `
.card-layers {
width: 210px;
height: 140px;
perspective: 900px;
cursor: pointer;
}
.card-layers__stack {
position: relative;
width: 100%;
height: 100%;
transform-style: preserve-3d;
transition: transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
}
.card-layers:hover .card-layers__stack,
.card-layers:focus-visible .card-layers__stack { transform: rotateX(56deg) rotateZ(-38deg) scale(0.82); }
.card-layers:focus-visible { outline: none; }
.card-layers__layer {
position: absolute;
inset: 0;
border-radius: 14px;
transition: transform 0.7s cubic-bezier(0.22, 1, 0.36, 1), box-shadow 0.7s ease;
}
.card-layers:hover .card-layers__layer,
.card-layers:focus-visible .card-layers__layer {
transform: translateZ(calc(var(--i) * 26px));
box-shadow: 0 0 0 1px light-dark(#6d7179, #7d8087);
}
.card-layers__layer--base { background: light-dark(#f3f4f7, #1a1c1f); box-shadow: 0 0 0 1px light-dark(#dbdde1, #2e3035); }
.card-layers__layer--media::before {
content: '';
position: absolute;
left: 12px;
right: 12px;
top: 12px;
height: 56px;
border-radius: 9px;
background: linear-gradient(135deg, light-dark(#6d7179, #7d8087), light-dark(#dbdde1, #2e3035));
}
.card-layers__layer--text { padding: 78px 14px 0; }
.card-layers__layer--text b { display: block; color: light-dark(#373a40, #f1f2f3); font-size: 13px; font-weight: 600; }
.card-layers__layer--text span { display: block; color: light-dark(#5f626a, #a7abb2); font-size: 11px; margin-top: 2px; }
.card-layers__layer--action::before {
content: 'Open';
position: absolute;
right: 12px;
bottom: 14px;
padding: 4px 10px;
border-radius: 999px;
background: light-dark(#373a40, #f1f2f3);
color: light-dark(#fbfcfd, #121417);
font-size: 10px;
font-weight: 600;
}
/* The layer's name, readable only once it is pulled apart. */
.card-layers__layer::after {
content: attr(data-name);
position: absolute;
left: calc(100% + 8px);
top: 0;
color: light-dark(#5f626a, #a7abb2);
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 9px;
white-space: nowrap;
opacity: 0;
transition: opacity 0.3s ease;
}
.card-layers:hover .card-layers__layer::after { opacity: 1; transition-delay: 0.35s; }
.card-layers:hover .card-layers__layer--media,
.card-layers:hover .card-layers__layer--text,
.card-layers:hover .card-layers__layer--action { background: color-mix(in oklab, light-dark(#373a40, #f1f2f3) 4%, transparent); }
@media (prefers-reduced-motion: reduce) {
.card-layers__stack, .card-layers__layer { transition-duration: 0.01s; }
}
`;
const LAYERS = [
{ cls: 'base', name: 'Surface' },
{ cls: 'media', name: 'Media' },
{ cls: 'text', name: 'Text' },
{ cls: 'action', name: 'Action' },
];
export function LayersCard() {
return (
<>
<style>
{css}
</style>
<div className="card-layers" tabIndex={0} role="group" aria-label="Layers: Ridge study">
<div className="card-layers__stack">
{LAYERS.map((l, i) => (
<div
key={l.cls}
className={`card-layers__layer card-layers__layer--${l.cls}`}
data-name={l.name}
style={{ '--i': i } as React.CSSProperties}
>
{l.cls === 'text' && (
<>
<b>Ridge study</b>
<span>Four layers, one card</span>
</>
)}
</div>
))}
</div>
</div>
</>
);
}Markup where the component goes, CSS in your stylesheet. No JavaScript.
HTML
<div class="card-layers" tabindex="0" role="group" aria-label="Layers: Ridge study">
<div class="card-layers__stack">
<div class="card-layers__layer card-layers__layer--base" data-name="Surface" style="--i:0"></div>
<div class="card-layers__layer card-layers__layer--media" data-name="Media" style="--i:1"></div>
<div class="card-layers__layer card-layers__layer--text" data-name="Text" style="--i:2">
<b>Ridge study</b>
<span>Four layers, one card</span>
</div>
<div class="card-layers__layer card-layers__layer--action" data-name="Action" style="--i:3"></div>
</div>
</div>CSS
.card-layers {
width: 210px;
height: 140px;
perspective: 900px;
cursor: pointer;
}
.card-layers__stack {
position: relative;
width: 100%;
height: 100%;
transform-style: preserve-3d;
transition: transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
}
.card-layers:hover .card-layers__stack,
.card-layers:focus-visible .card-layers__stack { transform: rotateX(56deg) rotateZ(-38deg) scale(0.82); }
.card-layers:focus-visible { outline: none; }
.card-layers__layer {
position: absolute;
inset: 0;
border-radius: 14px;
transition: transform 0.7s cubic-bezier(0.22, 1, 0.36, 1), box-shadow 0.7s ease;
}
.card-layers:hover .card-layers__layer,
.card-layers:focus-visible .card-layers__layer {
transform: translateZ(calc(var(--i) * 26px));
box-shadow: 0 0 0 1px light-dark(#6d7179, #7d8087);
}
.card-layers__layer--base { background: light-dark(#f3f4f7, #1a1c1f); box-shadow: 0 0 0 1px light-dark(#dbdde1, #2e3035); }
.card-layers__layer--media::before {
content: '';
position: absolute;
left: 12px;
right: 12px;
top: 12px;
height: 56px;
border-radius: 9px;
background: linear-gradient(135deg, light-dark(#6d7179, #7d8087), light-dark(#dbdde1, #2e3035));
}
.card-layers__layer--text { padding: 78px 14px 0; }
.card-layers__layer--text b { display: block; color: light-dark(#373a40, #f1f2f3); font-size: 13px; font-weight: 600; }
.card-layers__layer--text span { display: block; color: light-dark(#5f626a, #a7abb2); font-size: 11px; margin-top: 2px; }
.card-layers__layer--action::before {
content: 'Open';
position: absolute;
right: 12px;
bottom: 14px;
padding: 4px 10px;
border-radius: 999px;
background: light-dark(#373a40, #f1f2f3);
color: light-dark(#fbfcfd, #121417);
font-size: 10px;
font-weight: 600;
}
/* The layer's name, readable only once it is pulled apart. */
.card-layers__layer::after {
content: attr(data-name);
position: absolute;
left: calc(100% + 8px);
top: 0;
color: light-dark(#5f626a, #a7abb2);
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 9px;
white-space: nowrap;
opacity: 0;
transition: opacity 0.3s ease;
}
.card-layers:hover .card-layers__layer::after { opacity: 1; transition-delay: 0.35s; }
.card-layers:hover .card-layers__layer--media,
.card-layers:hover .card-layers__layer--text,
.card-layers:hover .card-layers__layer--action { background: color-mix(in oklab, light-dark(#373a40, #f1f2f3) 4%, transparent); }
@media (prefers-reduced-motion: reduce) {
.card-layers__stack, .card-layers__layer { transition-duration: 0.01s; }
}Goal: Create a "Layers" card.
Visual behavior: Hover explodes the card into its stacked layers in isometric view, like a design file: surface, media, text and action float apart in depth, each labelled with its name.
Technique:
- Four full-size layers stack inside a preserve-3d wrapper: a base surface, a media block, the text and an action pill (drawn with ::before).
- On hover the wrapper turns to rotateX(56deg) rotateZ(-38deg) scale(0.82), an isometric view, and each layer moves translateZ(--i x 26px) so they separate.
- Each layer gets an outline, and its name appears beside it from data-name through ::after content: attr(), after a short delay.
Accessibility: The card is focusable (tabIndex 0) with role="group" and an aria-label that sums it up; keyboard focus plays the same reveal as hover. Under prefers-reduced-motion the change is instant.
My stack: {{USER_STACK}}
My styling: {{USER_STYLING}}
My constraints: {{USER_CONSTRAINTS}}
My colors: neutral only. Black, white and grays, no hues. Use the color values above as written, or my project's closest neutral tokens.
Return a single self-contained component. No dependencies.Goal: Create a "Layers" card.
Visual behavior: Hover explodes the card into its stacked layers in isometric view, like a design file: surface, media, text and action float apart in depth, each labelled with its name.
Technique:
- Four full-size layers stack inside a preserve-3d wrapper: a base surface, a media block, the text and an action pill (drawn with ::before).
- On hover the wrapper turns to rotateX(56deg) rotateZ(-38deg) scale(0.82), an isometric view, and each layer moves translateZ(--i x 26px) so they separate.
- Each layer gets an outline, and its name appears beside it from data-name through ::after content: attr(), after a short delay.
Accessibility: The card is focusable (tabIndex 0) with role="group" and an aria-label that sums it up; keyboard focus plays the same reveal as hover. Under prefers-reduced-motion the change is instant.
My stack: {{USER_STACK}}
My styling: {{USER_STYLING}}
My constraints: {{USER_CONSTRAINTS}}
My colors: use my project's existing color tokens. The color values above are grayscale placeholders. Map each one by its role (surface, text, border, accent, glow) to my theme, and keep their relative lightness so contrast and the effect still read.
Return a single self-contained component. No dependencies.Paste into Claude, ChatGPT or Cursor after filling in {{USER_STACK}}, {{USER_STYLING}} and {{USER_CONSTRAINTS}}. Neutral keeps the greys you see here; Match my project asks the model to map them onto your own colour tokens.
Free to use in any project, commercial included. MIT-0, no attribution required. Licence
About this component
Hover explodes the card into its stacked layers in isometric view, like a design file: surface, media, text and action float apart in depth, each labelled with its name.
- Dependencies
- None
- Difficulty
- Intermediate
- Triggers
- hover, 3d, card
- Techniques
- preserve 3d, 3d transform, css variables, pseudo elements
- Reduced motion
- Respected
- Theme
- Light and dark (follows your page's color-scheme)
The card is focusable (tabIndex 0) with role="group" and an aria-label that sums it up; keyboard focus plays the same reveal as hover. Under prefers-reduced-motion the change is instant.