33 / 36
All 36 in 36 Tailwind Stacked Cards
- 01Tailwind Scroll-Driven Scaling Card Stack
- 02Tailwind Swipe Card Deck Drag to Dismiss
- 03Tailwind Glassmorphism Testimonial Card Stack
- 04Tailwind Stacked Cards Expand Accordion on Click
- 05Tailwind Fan Out Photo Stack on Hover
- 06Tailwind 3D Perspective Tilt Card Stack
- 07Tailwind Multi Step Form Card Stack
- 08Tailwind Tabbed Card Stack Bring to Front
- 09Tailwind Click to Front Stacked Cards Cycle
- 10Tailwind Classic Card Deck Stack Shadow
- 11Tailwind Fan Spread Hand of Cards
- 12Tailwind Polaroid Photo Stack Scattered Rotate
- 13Tailwind Staircase Offset Stacked Cards Cascade
- 14Tailwind Cards Spread Out Horizontally on Hover
- 15Tailwind Stacked Cards Lift on Hover Sibling Push
- 16Tailwind Accordion Stacked Cards Slide Out Menu
- 17Tailwind Shuffle Cards Animation Loop Deck
- 18Tailwind File Folder Tab Stacked Cards
- 19Tailwind Scattered Photos Pinch to Centre on Hover
- 20Tailwind Mobile Swipe Card Stack Translate3d
- 21Tailwind Single Accordion Card Nested Layers
- 22Tailwind Isometric Tower Stacked Cards 3D Column
- 23Tailwind 3D Flip Card Stack Reveal Back
- 24Tailwind Card Stack Tilt on Hover Quadrant
- 25Tailwind Notification Stack Pile Dismiss
- 26Tailwind Receipt Roll Unroll Stacked Items
- 27Tailwind Scratch Card Reveal Canvas Mask
- 28Tailwind Stacked Cards Hover Reveal Spread
- 29Tailwind Scroll Activated Sticky Stacking Cards
- 30Tailwind Stacked Testimonial Slider Radio
- 31Tailwind Isometric Overlapping Stacked Cards
- 32Tailwind Bento Grid Stacked Cards Hover Fan
- 33Tailwind Perspective Card Deck Depth Tunnel
- 34Tailwind Peel Back Corner Card Reveal
- 35Tailwind Timeline Stacked Milestone Cards
- 36Tailwind Spiral Tower Rotating Stacked Cards
Tailwind Perspective Card Deck Depth Tunnel
Four panels receding into the screen on negative Z, tilting as a group on hover while the front card pushes toward you. Four :nth-child rules become one index expression — depth, vertical offset and opacity all fall out of --i — and the opacity ramp doubles as the contrast fix, because a panel that recedes should also read as background.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="stk-33 grid w-full min-h-svh place-items-center px-5 py-15 font-stk33-sans bg-stk33-bg [background-image:radial-gradient(circle_at_50%_50%,var(--color-stk33-glow),transparent_70%)]" aria-label="Sector status deck">
<div class="group h-95 w-75 [perspective:900px]">
<div class="relative size-full [transform-style:preserve-3d] transition-transform duration-800 ease-out group-hover:[transform:rotateX(18deg)_rotateY(-12deg)] motion-reduce:transition-none motion-reduce:rotate-none!">
<article class="absolute inset-0 flex flex-col justify-end rounded-[20px] border border-stk33-neon/35 bg-linear-[155deg,var(--color-stk33-c1),var(--color-stk33-c2)] p-6.5 text-stk33-ink backdrop-blur-[2px] shadow-[0_0_40px_var(--color-stk33-halo)] opacity-[calc(1-var(--i)*0.2)] [transform:translateZ(calc(var(--i)*-80px))_translateY(calc(var(--i)*-26px))] transition-[transform,opacity] duration-800 ease-stk33 group-hover:[transform:translateZ(60px)] motion-reduce:transition-none" style="--i:0">
<h3 class="m-0 font-stk33-display text-[1.3rem] tracking-[0.08em]">SECTOR 01</h3>
<p class="m-0 text-[0.95rem] tracking-[0.05em] text-stk33-ink/75">Active node // online</p>
</article>
<article class="absolute inset-0 flex flex-col justify-end rounded-[20px] border border-stk33-neon/35 bg-linear-[155deg,var(--color-stk33-c1),var(--color-stk33-c2)] p-6.5 text-stk33-ink backdrop-blur-[2px] shadow-[0_0_40px_var(--color-stk33-halo)] opacity-[calc(1-var(--i)*0.2)] [transform:translateZ(calc(var(--i)*-80px))_translateY(calc(var(--i)*-26px))] transition-[transform,opacity] duration-800 ease-stk33 motion-reduce:transition-none" style="--i:1" aria-hidden="true">
<h3 class="m-0 font-stk33-display text-[1.3rem] tracking-[0.08em]">SECTOR 02</h3>
<p class="m-0 text-[0.95rem] tracking-[0.05em] text-stk33-ink/75">Buffered // standby</p>
</article>
<article class="absolute inset-0 flex flex-col justify-end rounded-[20px] border border-stk33-neon/35 bg-linear-[155deg,var(--color-stk33-c1),var(--color-stk33-c2)] p-6.5 text-stk33-ink backdrop-blur-[2px] shadow-[0_0_40px_var(--color-stk33-halo)] opacity-[calc(1-var(--i)*0.2)] [transform:translateZ(calc(var(--i)*-80px))_translateY(calc(var(--i)*-26px))] transition-[transform,opacity] duration-800 ease-stk33 motion-reduce:transition-none" style="--i:2" aria-hidden="true">
<h3 class="m-0 font-stk33-display text-[1.3rem] tracking-[0.08em]">SECTOR 03</h3>
<p class="m-0 text-[0.95rem] tracking-[0.05em] text-stk33-ink/75">Cached // idle</p>
</article>
<article class="absolute inset-0 flex flex-col justify-end rounded-[20px] border border-stk33-neon/35 bg-linear-[155deg,var(--color-stk33-c1),var(--color-stk33-c2)] p-6.5 text-stk33-ink backdrop-blur-[2px] shadow-[0_0_40px_var(--color-stk33-halo)] opacity-[calc(1-var(--i)*0.2)] [transform:translateZ(calc(var(--i)*-80px))_translateY(calc(var(--i)*-26px))] transition-[transform,opacity] duration-800 ease-stk33 motion-reduce:transition-none" style="--i:3" aria-hidden="true">
<h3 class="m-0 font-stk33-display text-[1.3rem] tracking-[0.08em]">SECTOR 04</h3>
<p class="m-0 text-[0.95rem] tracking-[0.05em] text-stk33-ink/75">Archived // sleep</p>
</article>
</div>
</div>
</section><section class="stk-33 grid w-full min-h-svh place-items-center px-5 py-15 font-stk33-sans bg-stk33-bg [background-image:radial-gradient(circle_at_50%_50%,var(--color-stk33-glow),transparent_70%)]" aria-label="Sector status deck">
<div class="group h-95 w-75 [perspective:900px]">
<div class="relative size-full [transform-style:preserve-3d] transition-transform duration-800 ease-out group-hover:[transform:rotateX(18deg)_rotateY(-12deg)] motion-reduce:transition-none motion-reduce:rotate-none!">
<article class="absolute inset-0 flex flex-col justify-end rounded-[20px] border border-stk33-neon/35 bg-linear-[155deg,var(--color-stk33-c1),var(--color-stk33-c2)] p-6.5 text-stk33-ink backdrop-blur-[2px] shadow-[0_0_40px_var(--color-stk33-halo)] opacity-[calc(1-var(--i)*0.2)] [transform:translateZ(calc(var(--i)*-80px))_translateY(calc(var(--i)*-26px))] transition-[transform,opacity] duration-800 ease-stk33 group-hover:[transform:translateZ(60px)] motion-reduce:transition-none" style="--i:0">
<h3 class="m-0 font-stk33-display text-[1.3rem] tracking-[0.08em]">SECTOR 01</h3>
<p class="m-0 text-[0.95rem] tracking-[0.05em] text-stk33-ink/75">Active node // online</p>
</article>
<article class="absolute inset-0 flex flex-col justify-end rounded-[20px] border border-stk33-neon/35 bg-linear-[155deg,var(--color-stk33-c1),var(--color-stk33-c2)] p-6.5 text-stk33-ink backdrop-blur-[2px] shadow-[0_0_40px_var(--color-stk33-halo)] opacity-[calc(1-var(--i)*0.2)] [transform:translateZ(calc(var(--i)*-80px))_translateY(calc(var(--i)*-26px))] transition-[transform,opacity] duration-800 ease-stk33 motion-reduce:transition-none" style="--i:1" aria-hidden="true">
<h3 class="m-0 font-stk33-display text-[1.3rem] tracking-[0.08em]">SECTOR 02</h3>
<p class="m-0 text-[0.95rem] tracking-[0.05em] text-stk33-ink/75">Buffered // standby</p>
</article>
<article class="absolute inset-0 flex flex-col justify-end rounded-[20px] border border-stk33-neon/35 bg-linear-[155deg,var(--color-stk33-c1),var(--color-stk33-c2)] p-6.5 text-stk33-ink backdrop-blur-[2px] shadow-[0_0_40px_var(--color-stk33-halo)] opacity-[calc(1-var(--i)*0.2)] [transform:translateZ(calc(var(--i)*-80px))_translateY(calc(var(--i)*-26px))] transition-[transform,opacity] duration-800 ease-stk33 motion-reduce:transition-none" style="--i:2" aria-hidden="true">
<h3 class="m-0 font-stk33-display text-[1.3rem] tracking-[0.08em]">SECTOR 03</h3>
<p class="m-0 text-[0.95rem] tracking-[0.05em] text-stk33-ink/75">Cached // idle</p>
</article>
<article class="absolute inset-0 flex flex-col justify-end rounded-[20px] border border-stk33-neon/35 bg-linear-[155deg,var(--color-stk33-c1),var(--color-stk33-c2)] p-6.5 text-stk33-ink backdrop-blur-[2px] shadow-[0_0_40px_var(--color-stk33-halo)] opacity-[calc(1-var(--i)*0.2)] [transform:translateZ(calc(var(--i)*-80px))_translateY(calc(var(--i)*-26px))] transition-[transform,opacity] duration-800 ease-stk33 motion-reduce:transition-none" style="--i:3" aria-hidden="true">
<h3 class="m-0 font-stk33-display text-[1.3rem] tracking-[0.08em]">SECTOR 04</h3>
<p class="m-0 text-[0.95rem] tracking-[0.05em] text-stk33-ink/75">Archived // sleep</p>
</article>
</div>
</div>
</section>@import "tailwindcss";
@theme {
--color-stk33-bg: #05060a;
--color-stk33-bg: oklch(0.08 0.012 275);
--color-stk33-glow: #10131f;
--color-stk33-glow: oklch(0.19 0.022 275);
--color-stk33-c1: #28345a;
--color-stk33-c1: oklch(0.32 0.055 265);
--color-stk33-c2: #121628;
--color-stk33-c2: oklch(0.20 0.030 270);
--color-stk33-ink: #cfe0ff;
--color-stk33-ink: oklch(0.90 0.040 260);
--color-stk33-neon: #78aaff;
--color-stk33-neon: oklch(0.74 0.130 260);
--color-stk33-halo: rgba(70, 120, 255, 0.18);
--font-stk33-sans: 'Rajdhani', system-ui, sans-serif;
--font-stk33-display: 'Orbitron', system-ui, sans-serif;
--ease-stk33: cubic-bezier(0.2, 0.8, 0.2, 1);
}@import "tailwindcss";
@theme {
--color-stk33-bg: #05060a;
--color-stk33-bg: oklch(0.08 0.012 275);
--color-stk33-glow: #10131f;
--color-stk33-glow: oklch(0.19 0.022 275);
--color-stk33-c1: #28345a;
--color-stk33-c1: oklch(0.32 0.055 265);
--color-stk33-c2: #121628;
--color-stk33-c2: oklch(0.20 0.030 270);
--color-stk33-ink: #cfe0ff;
--color-stk33-ink: oklch(0.90 0.040 260);
--color-stk33-neon: #78aaff;
--color-stk33-neon: oklch(0.74 0.130 260);
--color-stk33-halo: rgba(70, 120, 255, 0.18);
--font-stk33-sans: 'Rajdhani', system-ui, sans-serif;
--font-stk33-display: 'Orbitron', system-ui, sans-serif;
--ease-stk33: cubic-bezier(0.2, 0.8, 0.2, 1);
}Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-stk33-bg: #05060a;
--color-stk33-bg: oklch(0.08 0.012 275);
--color-stk33-glow: #10131f;
--color-stk33-glow: oklch(0.19 0.022 275);
--color-stk33-c1: #28345a;
--color-stk33-c1: oklch(0.32 0.055 265);
--color-stk33-c2: #121628;
--color-stk33-c2: oklch(0.20 0.030 270);
--color-stk33-ink: #cfe0ff;
--color-stk33-ink: oklch(0.90 0.040 260);
--color-stk33-neon: #78aaff;
--color-stk33-neon: oklch(0.74 0.130 260);
--color-stk33-halo: rgba(70, 120, 255, 0.18);
--font-stk33-sans: 'Rajdhani', system-ui, sans-serif;
--font-stk33-display: 'Orbitron', system-ui, sans-serif;
--ease-stk33: cubic-bezier(0.2, 0.8, 0.2, 1);
}@import "tailwindcss";
@theme {
--color-stk33-bg: #05060a;
--color-stk33-bg: oklch(0.08 0.012 275);
--color-stk33-glow: #10131f;
--color-stk33-glow: oklch(0.19 0.022 275);
--color-stk33-c1: #28345a;
--color-stk33-c1: oklch(0.32 0.055 265);
--color-stk33-c2: #121628;
--color-stk33-c2: oklch(0.20 0.030 270);
--color-stk33-ink: #cfe0ff;
--color-stk33-ink: oklch(0.90 0.040 260);
--color-stk33-neon: #78aaff;
--color-stk33-neon: oklch(0.74 0.130 260);
--color-stk33-halo: rgba(70, 120, 255, 0.18);
--font-stk33-sans: 'Rajdhani', system-ui, sans-serif;
--font-stk33-display: 'Orbitron', system-ui, sans-serif;
--ease-stk33: cubic-bezier(0.2, 0.8, 0.2, 1);
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@600&family=Rajdhani:wght@500&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@600&family=Rajdhani:wght@500&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The webfonts load as
<link>tags insetup, not as an@importinside the stylesheet. Tailwind emits user@importrules after its own@layerblocks, and a late@importis invalid CSS — the browser drops it and the demo silently falls back to system fonts. - Four
:nth-childrules become one index expression covering depth, vertical rake and opacity — adding a fifth panel is one attribute. opacity-[calc(1-var(--i)*0.2)]is both the depth cue and the honest answer to contrast: foreshortened text is harder to read than its ratio suggests, so receding panels read as background.- The glass uses Tailwind's
backdrop-blur-*rather than an arbitrary property, specifically because the utility emits the-webkit-prefix older Safari needs. - Two hover scopes fire from one pointer entry: the stage tilts the deck, and the front panel has its own full transform pushing it out of the tunnel.
- The three background panels are
aria-hidden; only the front sector is real content, which keeps four near-identical status lines out of the accessibility tree. motion-reduce:rotate-none!keeps the informative tunnel and removes the tilt, rather than slowing a rotation that is itself the problem.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Perspective Card Deck Depth Tunnel
Source: https://codefronts.com/components/tailwind-stacked-cards/tailwind-perspective-card-deck-depth-tunnel/
Four panels receding into the screen on negative Z, tilting as a group on hover while the front card pushes toward you. Four :nth-child rules become one index expression — depth, vertical offset and opacity all fall out of --i — and the opacity ramp doubles as the contrast fix, because a panel that recedes should also read as background.
## HTML
```html
<section class="stk-33 grid w-full min-h-svh place-items-center px-5 py-15 font-stk33-sans bg-stk33-bg [background-image:radial-gradient(circle_at_50%_50%,var(--color-stk33-glow),transparent_70%)]" aria-label="Sector status deck">
<div class="group h-95 w-75 [perspective:900px]">
<div class="relative size-full [transform-style:preserve-3d] transition-transform duration-800 ease-out group-hover:[transform:rotateX(18deg)_rotateY(-12deg)] motion-reduce:transition-none motion-reduce:rotate-none!">
<article class="absolute inset-0 flex flex-col justify-end rounded-[20px] border border-stk33-neon/35 bg-linear-[155deg,var(--color-stk33-c1),var(--color-stk33-c2)] p-6.5 text-stk33-ink backdrop-blur-[2px] shadow-[0_0_40px_var(--color-stk33-halo)] opacity-[calc(1-var(--i)*0.2)] [transform:translateZ(calc(var(--i)*-80px))_translateY(calc(var(--i)*-26px))] transition-[transform,opacity] duration-800 ease-stk33 group-hover:[transform:translateZ(60px)] motion-reduce:transition-none" style="--i:0">
<h3 class="m-0 font-stk33-display text-[1.3rem] tracking-[0.08em]">SECTOR 01</h3>
<p class="m-0 text-[0.95rem] tracking-[0.05em] text-stk33-ink/75">Active node // online</p>
</article>
<article class="absolute inset-0 flex flex-col justify-end rounded-[20px] border border-stk33-neon/35 bg-linear-[155deg,var(--color-stk33-c1),var(--color-stk33-c2)] p-6.5 text-stk33-ink backdrop-blur-[2px] shadow-[0_0_40px_var(--color-stk33-halo)] opacity-[calc(1-var(--i)*0.2)] [transform:translateZ(calc(var(--i)*-80px))_translateY(calc(var(--i)*-26px))] transition-[transform,opacity] duration-800 ease-stk33 motion-reduce:transition-none" style="--i:1" aria-hidden="true">
<h3 class="m-0 font-stk33-display text-[1.3rem] tracking-[0.08em]">SECTOR 02</h3>
<p class="m-0 text-[0.95rem] tracking-[0.05em] text-stk33-ink/75">Buffered // standby</p>
</article>
<article class="absolute inset-0 flex flex-col justify-end rounded-[20px] border border-stk33-neon/35 bg-linear-[155deg,var(--color-stk33-c1),var(--color-stk33-c2)] p-6.5 text-stk33-ink backdrop-blur-[2px] shadow-[0_0_40px_var(--color-stk33-halo)] opacity-[calc(1-var(--i)*0.2)] [transform:translateZ(calc(var(--i)*-80px))_translateY(calc(var(--i)*-26px))] transition-[transform,opacity] duration-800 ease-stk33 motion-reduce:transition-none" style="--i:2" aria-hidden="true">
<h3 class="m-0 font-stk33-display text-[1.3rem] tracking-[0.08em]">SECTOR 03</h3>
<p class="m-0 text-[0.95rem] tracking-[0.05em] text-stk33-ink/75">Cached // idle</p>
</article>
<article class="absolute inset-0 flex flex-col justify-end rounded-[20px] border border-stk33-neon/35 bg-linear-[155deg,var(--color-stk33-c1),var(--color-stk33-c2)] p-6.5 text-stk33-ink backdrop-blur-[2px] shadow-[0_0_40px_var(--color-stk33-halo)] opacity-[calc(1-var(--i)*0.2)] [transform:translateZ(calc(var(--i)*-80px))_translateY(calc(var(--i)*-26px))] transition-[transform,opacity] duration-800 ease-stk33 motion-reduce:transition-none" style="--i:3" aria-hidden="true">
<h3 class="m-0 font-stk33-display text-[1.3rem] tracking-[0.08em]">SECTOR 04</h3>
<p class="m-0 text-[0.95rem] tracking-[0.05em] text-stk33-ink/75">Archived // sleep</p>
</article>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-stk33-bg: #05060a;
--color-stk33-bg: oklch(0.08 0.012 275);
--color-stk33-glow: #10131f;
--color-stk33-glow: oklch(0.19 0.022 275);
--color-stk33-c1: #28345a;
--color-stk33-c1: oklch(0.32 0.055 265);
--color-stk33-c2: #121628;
--color-stk33-c2: oklch(0.20 0.030 270);
--color-stk33-ink: #cfe0ff;
--color-stk33-ink: oklch(0.90 0.040 260);
--color-stk33-neon: #78aaff;
--color-stk33-neon: oklch(0.74 0.130 260);
--color-stk33-halo: rgba(70, 120, 255, 0.18);
--font-stk33-sans: 'Rajdhani', system-ui, sans-serif;
--font-stk33-display: 'Orbitron', system-ui, sans-serif;
--ease-stk33: cubic-bezier(0.2, 0.8, 0.2, 1);
}
```Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Perspective Card Deck Depth Tunnel
Source: https://codefronts.com/components/tailwind-stacked-cards/tailwind-perspective-card-deck-depth-tunnel/
Four panels receding into the screen on negative Z, tilting as a group on hover while the front card pushes toward you. Four :nth-child rules become one index expression — depth, vertical offset and opacity all fall out of --i — and the opacity ramp doubles as the contrast fix, because a panel that recedes should also read as background.
## HTML
```html
<section class="stk-33 grid w-full min-h-svh place-items-center px-5 py-15 font-stk33-sans bg-stk33-bg [background-image:radial-gradient(circle_at_50%_50%,var(--color-stk33-glow),transparent_70%)]" aria-label="Sector status deck">
<div class="group h-95 w-75 [perspective:900px]">
<div class="relative size-full [transform-style:preserve-3d] transition-transform duration-800 ease-out group-hover:[transform:rotateX(18deg)_rotateY(-12deg)] motion-reduce:transition-none motion-reduce:rotate-none!">
<article class="absolute inset-0 flex flex-col justify-end rounded-[20px] border border-stk33-neon/35 bg-linear-[155deg,var(--color-stk33-c1),var(--color-stk33-c2)] p-6.5 text-stk33-ink backdrop-blur-[2px] shadow-[0_0_40px_var(--color-stk33-halo)] opacity-[calc(1-var(--i)*0.2)] [transform:translateZ(calc(var(--i)*-80px))_translateY(calc(var(--i)*-26px))] transition-[transform,opacity] duration-800 ease-stk33 group-hover:[transform:translateZ(60px)] motion-reduce:transition-none" style="--i:0">
<h3 class="m-0 font-stk33-display text-[1.3rem] tracking-[0.08em]">SECTOR 01</h3>
<p class="m-0 text-[0.95rem] tracking-[0.05em] text-stk33-ink/75">Active node // online</p>
</article>
<article class="absolute inset-0 flex flex-col justify-end rounded-[20px] border border-stk33-neon/35 bg-linear-[155deg,var(--color-stk33-c1),var(--color-stk33-c2)] p-6.5 text-stk33-ink backdrop-blur-[2px] shadow-[0_0_40px_var(--color-stk33-halo)] opacity-[calc(1-var(--i)*0.2)] [transform:translateZ(calc(var(--i)*-80px))_translateY(calc(var(--i)*-26px))] transition-[transform,opacity] duration-800 ease-stk33 motion-reduce:transition-none" style="--i:1" aria-hidden="true">
<h3 class="m-0 font-stk33-display text-[1.3rem] tracking-[0.08em]">SECTOR 02</h3>
<p class="m-0 text-[0.95rem] tracking-[0.05em] text-stk33-ink/75">Buffered // standby</p>
</article>
<article class="absolute inset-0 flex flex-col justify-end rounded-[20px] border border-stk33-neon/35 bg-linear-[155deg,var(--color-stk33-c1),var(--color-stk33-c2)] p-6.5 text-stk33-ink backdrop-blur-[2px] shadow-[0_0_40px_var(--color-stk33-halo)] opacity-[calc(1-var(--i)*0.2)] [transform:translateZ(calc(var(--i)*-80px))_translateY(calc(var(--i)*-26px))] transition-[transform,opacity] duration-800 ease-stk33 motion-reduce:transition-none" style="--i:2" aria-hidden="true">
<h3 class="m-0 font-stk33-display text-[1.3rem] tracking-[0.08em]">SECTOR 03</h3>
<p class="m-0 text-[0.95rem] tracking-[0.05em] text-stk33-ink/75">Cached // idle</p>
</article>
<article class="absolute inset-0 flex flex-col justify-end rounded-[20px] border border-stk33-neon/35 bg-linear-[155deg,var(--color-stk33-c1),var(--color-stk33-c2)] p-6.5 text-stk33-ink backdrop-blur-[2px] shadow-[0_0_40px_var(--color-stk33-halo)] opacity-[calc(1-var(--i)*0.2)] [transform:translateZ(calc(var(--i)*-80px))_translateY(calc(var(--i)*-26px))] transition-[transform,opacity] duration-800 ease-stk33 motion-reduce:transition-none" style="--i:3" aria-hidden="true">
<h3 class="m-0 font-stk33-display text-[1.3rem] tracking-[0.08em]">SECTOR 04</h3>
<p class="m-0 text-[0.95rem] tracking-[0.05em] text-stk33-ink/75">Archived // sleep</p>
</article>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-stk33-bg: #05060a;
--color-stk33-bg: oklch(0.08 0.012 275);
--color-stk33-glow: #10131f;
--color-stk33-glow: oklch(0.19 0.022 275);
--color-stk33-c1: #28345a;
--color-stk33-c1: oklch(0.32 0.055 265);
--color-stk33-c2: #121628;
--color-stk33-c2: oklch(0.20 0.030 270);
--color-stk33-ink: #cfe0ff;
--color-stk33-ink: oklch(0.90 0.040 260);
--color-stk33-neon: #78aaff;
--color-stk33-neon: oklch(0.74 0.130 260);
--color-stk33-halo: rgba(70, 120, 255, 0.18);
--font-stk33-sans: 'Rajdhani', system-ui, sans-serif;
--font-stk33-display: 'Orbitron', system-ui, sans-serif;
--ease-stk33: cubic-bezier(0.2, 0.8, 0.2, 1);
}
```How this works
Negative Z is the whole tunnel. Positive translateZ brings a card toward the viewer; negative pushes it away. Combined with a vertical offset it reads as a corridor:
[transform:translateZ(calc(var(--i)*-80px))_translateY(calc(var(--i)*-26px))]
opacity-[calc(1-var(--i)*0.2)]Four :nth-child blocks collapse to that, and adding a fifth panel is one more --i.
The opacity ramp is not decoration. A 3D projection foreshortens text on the receding panels, which lowers effective contrast whatever the colour values say. Fading the whole panel is the honest response: it signals ‘this is background’ instead of pretending distant text is readable. The front panel, which carries the content, stays at full strength.
Two hover scopes, one gesture. The stage's group-hover: tilts the deck; the front card has its own group-hover:[transform:translateZ(60px)] that pushes it out of the tunnel. Both fire from the same pointer entry, and because the front card's transform is written in full there is no partial-override surprise.
Glass has a budget. backdrop-filter is GPU-composited and expensive; four panels is about the ceiling on mobile. The blur here is deliberately small (2px) — enough to separate the panels, cheap enough to stay smooth.
Make it yours
- Change the tunnel depth with the
-80pxmultiplier, and the vertical rake with the-26px. - Set the fade rate with the
0.2factor in the opacity calc. - Retune the hover tilt via the stage's
rotateX/rotateYpair. - Rebrand the neon through
--color-stk33-neon; the border and the glow both read it. - Add a scanline overlay as one absolutely-positioned
repeating-linear-gradientelement if you want the full sci-fi register.
Gotchas — read before shipping
backdrop-blurneeds something behind it to sample — over a flat single-tone background it produces nothing visible.- Cap the glass panels at three or four.
backdrop-filteris GPU-composited and stacking many of them stutters on mid-range phones. - Include the
-webkit-prefix for older Safari — Tailwind'sbackdrop-blur-*emits it, so use the utility rather than an arbitrary property. - Dim the receding panels rather than trusting their text contrast; foreshortened type is harder to read than its contrast ratio suggests.
perspectiveon the stage,preserve-3don the deck. Reversed, the tunnel flattens.- Under reduced motion, keep the resting tunnel and drop the tilt — the layout is informative, the rotation is not.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
backdrop-filter is Chrome 76+, Safari 9+ prefixed, Firefox 103+; 3D transforms are universal. The floor is Tailwind v4's own generated sheet.