06 / 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 3D Perspective Tilt Card Stack
The holographic deck: five parallax planes, an iridescent conic sweep, a cursor-tracked specular glare and a mask-composite border, tilting toward the pointer in real time. This is the demo where the port costs the most — @property, @keyframes and the mask trick all stay in CSS — and the payoff is that every translateZ plane is now readable on the element it belongs to instead of five rules away.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="stk-06 w-full min-h-svh flex items-center justify-center px-5 py-15 font-stk06-sans bg-stk06-bg [background-image:radial-gradient(circle_at_20%_30%,color-mix(in_oklch,var(--color-stk06-a)_22%,transparent),transparent_60%),radial-gradient(circle_at_80%_70%,color-mix(in_oklch,var(--color-stk06-b)_22%,transparent),transparent_60%)]" aria-label="Holographic 3D perspective tilt card stack">
<div class="relative w-[min(340px,100%)] h-115 [perspective:1200px] [perspective-origin:center_40%] has-focus-visible:outline-3 has-focus-visible:outline-stk06-a has-focus-visible:outline-offset-[12px] has-focus-visible:rounded-[26px]">
<div class="group relative size-full cursor-grab [transform-style:preserve-3d] [transform:rotateX(var(--stk-06-tilt-x))_rotateY(var(--stk-06-tilt-y))] transition-[--stk-06-tilt-x,--stk-06-tilt-y] duration-400 ease-stk06 focus-visible:outline-none focus-visible:[--stk-06-tilt-x:10deg] focus-visible:[--stk-06-tilt-y:-12deg] motion-reduce:transition-none" id="stk-06-deck" tabindex="0" role="button" aria-label="Interactive holographic card deck. Move the pointer to tilt it.">
<div class="absolute inset-0 rounded-[22px] bg-linear-[160deg,var(--color-stk06-card),var(--color-stk06-deep)] border border-stk06-edge shadow-[0_30px_60px_-30px_rgba(0,0,0,0.9)] brightness-50 [transform:translateZ(-100px)_translateY(20px)_scale(0.92)]" aria-hidden="true">
<span class="absolute top-5 right-[22px] font-stk06-mono text-sm font-bold tracking-[0.05em] text-stk06-num">03</span>
</div>
<div class="absolute inset-0 rounded-[22px] bg-linear-[160deg,var(--color-stk06-card),var(--color-stk06-deep)] border border-stk06-edge shadow-[0_30px_60px_-30px_rgba(0,0,0,0.9)] brightness-75 [transform:translateZ(-50px)_translateY(10px)_scale(0.96)]" aria-hidden="true">
<span class="absolute top-5 right-[22px] font-stk06-mono text-sm font-bold tracking-[0.05em] text-stk06-num">02</span>
</div>
<div class="absolute inset-0 overflow-hidden rounded-[22px] border border-stk06-edge shadow-[0_30px_60px_-30px_rgba(0,0,0,0.9)] [transform:translateZ(0)] [background-image:linear-gradient(155deg,color-mix(in_oklch,var(--color-stk06-a)_12%,var(--color-stk06-card))_0%,var(--color-stk06-deep)_100%)]">
<div class="absolute inset-0 rounded-[inherit] pointer-events-none opacity-85 blur-[20px] [transform:translateZ(4px)] [background-image:radial-gradient(circle_at_30%_30%,color-mix(in_oklch,var(--color-stk06-a)_35%,transparent),transparent_50%),radial-gradient(circle_at_70%_70%,color-mix(in_oklch,var(--color-stk06-b)_35%,transparent),transparent_50%)]" aria-hidden="true"></div>
<div class="absolute inset-0 rounded-[inherit] pointer-events-none opacity-[0.32] mix-blend-color-dodge animate-stk06-spin motion-reduce:animate-none [transform:translateZ(6px)] [background-image:conic-gradient(from_var(--stk-06-holo)_at_var(--stk-06-mx)_var(--stk-06-my),color-mix(in_oklch,var(--color-stk06-a)_70%,transparent),color-mix(in_oklch,var(--color-stk06-b)_70%,transparent),color-mix(in_oklch,var(--color-stk06-c)_70%,transparent),color-mix(in_oklch,var(--color-stk06-a)_70%,transparent))]" aria-hidden="true"></div>
<div class="absolute inset-0 flex h-full flex-col justify-end gap-2 px-[30px] py-8 text-white [transform:translateZ(30px)]">
<p class="m-0 font-stk06-mono text-[11px] font-bold tracking-[0.22em] uppercase text-stk06-a">Holographic · 01</p>
<h3 class="m-0 text-[32px] font-extrabold leading-[1.05] tracking-[-0.03em] text-white">Depth Pass</h3>
<p class="m-0 max-w-[26ch] text-sm leading-snug text-stk06-body">Move your pointer to tilt the deck. Layered parallax, iridescent sweep, real specular glare.</p>
</div>
<div class="absolute inset-0 rounded-[inherit] pointer-events-none opacity-0 mix-blend-overlay transition-opacity duration-300 group-hover:opacity-100 group-focus-visible:opacity-100 [transform:translateZ(35px)] [background-image:radial-gradient(200px_circle_at_var(--stk-06-mx)_var(--stk-06-my),rgba(255,255,255,0.35),transparent_55%)]" aria-hidden="true"></div>
<div class="stk-06__rim absolute inset-0 rounded-[inherit] pointer-events-none p-0.5 opacity-70 transition-opacity duration-300 group-hover:opacity-100 group-focus-visible:opacity-100 [transform:translateZ(38px)] [background-image:conic-gradient(from_var(--stk-06-holo),var(--color-stk06-a),var(--color-stk06-b),var(--color-stk06-c),var(--color-stk06-a))] [mask:linear-gradient(#000_0_0)_content-box,linear-gradient(#000_0_0)] [mask-composite:exclude]" aria-hidden="true"></div>
</div>
</div>
</div>
</section><section class="stk-06 w-full min-h-svh flex items-center justify-center px-5 py-15 font-stk06-sans bg-stk06-bg [background-image:radial-gradient(circle_at_20%_30%,color-mix(in_oklch,var(--color-stk06-a)_22%,transparent),transparent_60%),radial-gradient(circle_at_80%_70%,color-mix(in_oklch,var(--color-stk06-b)_22%,transparent),transparent_60%)]" aria-label="Holographic 3D perspective tilt card stack">
<div class="relative w-[min(340px,100%)] h-115 [perspective:1200px] [perspective-origin:center_40%] has-focus-visible:outline-3 has-focus-visible:outline-stk06-a has-focus-visible:outline-offset-[12px] has-focus-visible:rounded-[26px]">
<div class="group relative size-full cursor-grab [transform-style:preserve-3d] [transform:rotateX(var(--stk-06-tilt-x))_rotateY(var(--stk-06-tilt-y))] transition-[--stk-06-tilt-x,--stk-06-tilt-y] duration-400 ease-stk06 focus-visible:outline-none focus-visible:[--stk-06-tilt-x:10deg] focus-visible:[--stk-06-tilt-y:-12deg] motion-reduce:transition-none" id="stk-06-deck" tabindex="0" role="button" aria-label="Interactive holographic card deck. Move the pointer to tilt it.">
<div class="absolute inset-0 rounded-[22px] bg-linear-[160deg,var(--color-stk06-card),var(--color-stk06-deep)] border border-stk06-edge shadow-[0_30px_60px_-30px_rgba(0,0,0,0.9)] brightness-50 [transform:translateZ(-100px)_translateY(20px)_scale(0.92)]" aria-hidden="true">
<span class="absolute top-5 right-[22px] font-stk06-mono text-sm font-bold tracking-[0.05em] text-stk06-num">03</span>
</div>
<div class="absolute inset-0 rounded-[22px] bg-linear-[160deg,var(--color-stk06-card),var(--color-stk06-deep)] border border-stk06-edge shadow-[0_30px_60px_-30px_rgba(0,0,0,0.9)] brightness-75 [transform:translateZ(-50px)_translateY(10px)_scale(0.96)]" aria-hidden="true">
<span class="absolute top-5 right-[22px] font-stk06-mono text-sm font-bold tracking-[0.05em] text-stk06-num">02</span>
</div>
<div class="absolute inset-0 overflow-hidden rounded-[22px] border border-stk06-edge shadow-[0_30px_60px_-30px_rgba(0,0,0,0.9)] [transform:translateZ(0)] [background-image:linear-gradient(155deg,color-mix(in_oklch,var(--color-stk06-a)_12%,var(--color-stk06-card))_0%,var(--color-stk06-deep)_100%)]">
<div class="absolute inset-0 rounded-[inherit] pointer-events-none opacity-85 blur-[20px] [transform:translateZ(4px)] [background-image:radial-gradient(circle_at_30%_30%,color-mix(in_oklch,var(--color-stk06-a)_35%,transparent),transparent_50%),radial-gradient(circle_at_70%_70%,color-mix(in_oklch,var(--color-stk06-b)_35%,transparent),transparent_50%)]" aria-hidden="true"></div>
<div class="absolute inset-0 rounded-[inherit] pointer-events-none opacity-[0.32] mix-blend-color-dodge animate-stk06-spin motion-reduce:animate-none [transform:translateZ(6px)] [background-image:conic-gradient(from_var(--stk-06-holo)_at_var(--stk-06-mx)_var(--stk-06-my),color-mix(in_oklch,var(--color-stk06-a)_70%,transparent),color-mix(in_oklch,var(--color-stk06-b)_70%,transparent),color-mix(in_oklch,var(--color-stk06-c)_70%,transparent),color-mix(in_oklch,var(--color-stk06-a)_70%,transparent))]" aria-hidden="true"></div>
<div class="absolute inset-0 flex h-full flex-col justify-end gap-2 px-[30px] py-8 text-white [transform:translateZ(30px)]">
<p class="m-0 font-stk06-mono text-[11px] font-bold tracking-[0.22em] uppercase text-stk06-a">Holographic · 01</p>
<h3 class="m-0 text-[32px] font-extrabold leading-[1.05] tracking-[-0.03em] text-white">Depth Pass</h3>
<p class="m-0 max-w-[26ch] text-sm leading-snug text-stk06-body">Move your pointer to tilt the deck. Layered parallax, iridescent sweep, real specular glare.</p>
</div>
<div class="absolute inset-0 rounded-[inherit] pointer-events-none opacity-0 mix-blend-overlay transition-opacity duration-300 group-hover:opacity-100 group-focus-visible:opacity-100 [transform:translateZ(35px)] [background-image:radial-gradient(200px_circle_at_var(--stk-06-mx)_var(--stk-06-my),rgba(255,255,255,0.35),transparent_55%)]" aria-hidden="true"></div>
<div class="stk-06__rim absolute inset-0 rounded-[inherit] pointer-events-none p-0.5 opacity-70 transition-opacity duration-300 group-hover:opacity-100 group-focus-visible:opacity-100 [transform:translateZ(38px)] [background-image:conic-gradient(from_var(--stk-06-holo),var(--color-stk06-a),var(--color-stk06-b),var(--color-stk06-c),var(--color-stk06-a))] [mask:linear-gradient(#000_0_0)_content-box,linear-gradient(#000_0_0)] [mask-composite:exclude]" aria-hidden="true"></div>
</div>
</div>
</div>
</section>@import "tailwindcss";
/* Registered so they can INTERPOLATE. An unregistered custom property is an
untyped token: the tilt would snap back instead of easing, and the holo
angle could not be animated by keyframes at all. No utility form exists. */
@property --stk-06-tilt-x {
syntax: '<angle>';
initial-value: 0deg;
inherits: true;
}
@property --stk-06-tilt-y {
syntax: '<angle>';
initial-value: 0deg;
inherits: true;
}
@property --stk-06-mx {
syntax: '<percentage>';
initial-value: 50%;
inherits: true;
}
@property --stk-06-my {
syntax: '<percentage>';
initial-value: 50%;
inherits: true;
}
@property --stk-06-holo {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
@theme {
--color-stk06-bg: #04050a;
--color-stk06-bg: oklch(0.10 0.015 275);
--color-stk06-card: #1a1c2e;
--color-stk06-card: oklch(0.24 0.03 285);
--color-stk06-deep: #0f1120;
--color-stk06-deep: oklch(0.18 0.028 285);
--color-stk06-edge: #2a2d48;
--color-stk06-edge: oklch(0.32 0.035 285);
--color-stk06-num: #6a72a8;
--color-stk06-num: oklch(0.55 0.06 285);
--color-stk06-body: #c3c7de;
--color-stk06-body: oklch(0.83 0.02 285);
--color-stk06-a: #c77ce8;
--color-stk06-a: oklch(0.72 0.18 320);
--color-stk06-b: #3fa6e0;
--color-stk06-b: oklch(0.68 0.20 200);
--color-stk06-c: #e8ab48;
--color-stk06-c: oklch(0.75 0.19 60);
--font-stk06-sans: 'Archivo', system-ui, sans-serif;
--font-stk06-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-stk06: cubic-bezier(0.22, 1, 0.36, 1);
--animate-stk06-spin: stk-06-spin 8s linear infinite;
}
@keyframes stk-06-spin {
from {
--stk-06-holo: 0deg;
}
to {
--stk-06-holo: 360deg;
}
}
/* Older Safari spells mask-composite differently, and one class list cannot
carry two declarations of the same property. */
.stk-06__rim {
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
}@import "tailwindcss";
/* Registered so they can INTERPOLATE. An unregistered custom property is an
untyped token: the tilt would snap back instead of easing, and the holo
angle could not be animated by keyframes at all. No utility form exists. */
@property --stk-06-tilt-x {
syntax: '<angle>';
initial-value: 0deg;
inherits: true;
}
@property --stk-06-tilt-y {
syntax: '<angle>';
initial-value: 0deg;
inherits: true;
}
@property --stk-06-mx {
syntax: '<percentage>';
initial-value: 50%;
inherits: true;
}
@property --stk-06-my {
syntax: '<percentage>';
initial-value: 50%;
inherits: true;
}
@property --stk-06-holo {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
@theme {
--color-stk06-bg: #04050a;
--color-stk06-bg: oklch(0.10 0.015 275);
--color-stk06-card: #1a1c2e;
--color-stk06-card: oklch(0.24 0.03 285);
--color-stk06-deep: #0f1120;
--color-stk06-deep: oklch(0.18 0.028 285);
--color-stk06-edge: #2a2d48;
--color-stk06-edge: oklch(0.32 0.035 285);
--color-stk06-num: #6a72a8;
--color-stk06-num: oklch(0.55 0.06 285);
--color-stk06-body: #c3c7de;
--color-stk06-body: oklch(0.83 0.02 285);
--color-stk06-a: #c77ce8;
--color-stk06-a: oklch(0.72 0.18 320);
--color-stk06-b: #3fa6e0;
--color-stk06-b: oklch(0.68 0.20 200);
--color-stk06-c: #e8ab48;
--color-stk06-c: oklch(0.75 0.19 60);
--font-stk06-sans: 'Archivo', system-ui, sans-serif;
--font-stk06-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-stk06: cubic-bezier(0.22, 1, 0.36, 1);
--animate-stk06-spin: stk-06-spin 8s linear infinite;
}
@keyframes stk-06-spin {
from {
--stk-06-holo: 0deg;
}
to {
--stk-06-holo: 360deg;
}
}
/* Older Safari spells mask-composite differently, and one class list cannot
carry two declarations of the same property. */
.stk-06__rim {
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
}(() => {
const root = document.querySelector('.stk-06');
if (!root) return;
const deck = root.querySelector('#stk-06-deck');
const MAX_DEG = 14;
/* Touch devices fire synthetic mousemove events; without this guard the card
jitters under a phantom pointer. Reduced motion opts out entirely. */
if (!matchMedia('(hover: hover) and (pointer: fine)').matches) return;
if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
let raf = 0;
const set = (rx, ry, mx, my) => {
deck.style.setProperty('--stk-06-tilt-x', rx + 'deg');
deck.style.setProperty('--stk-06-tilt-y', ry + 'deg');
deck.style.setProperty('--stk-06-mx', mx + '%');
deck.style.setProperty('--stk-06-my', my + '%');
};
deck.addEventListener('pointermove', (e) => {
if (raf) cancelAnimationFrame(raf);
raf = requestAnimationFrame(() => {
const r = deck.getBoundingClientRect();
const px = (e.clientX - r.left) / r.width;
const py = (e.clientY - r.top) / r.height;
set(((0.5 - py) * 2 * MAX_DEG).toFixed(2), ((px - 0.5) * 2 * MAX_DEG).toFixed(2), (px * 100).toFixed(1), (py * 100).toFixed(1));
});
});
deck.addEventListener('pointerleave', () => {
if (raf) cancelAnimationFrame(raf);
set(0, 0, 50, 50);
});
})();(() => {
const root = document.querySelector('.stk-06');
if (!root) return;
const deck = root.querySelector('#stk-06-deck');
const MAX_DEG = 14;
/* Touch devices fire synthetic mousemove events; without this guard the card
jitters under a phantom pointer. Reduced motion opts out entirely. */
if (!matchMedia('(hover: hover) and (pointer: fine)').matches) return;
if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
let raf = 0;
const set = (rx, ry, mx, my) => {
deck.style.setProperty('--stk-06-tilt-x', rx + 'deg');
deck.style.setProperty('--stk-06-tilt-y', ry + 'deg');
deck.style.setProperty('--stk-06-mx', mx + '%');
deck.style.setProperty('--stk-06-my', my + '%');
};
deck.addEventListener('pointermove', (e) => {
if (raf) cancelAnimationFrame(raf);
raf = requestAnimationFrame(() => {
const r = deck.getBoundingClientRect();
const px = (e.clientX - r.left) / r.width;
const py = (e.clientY - r.top) / r.height;
set(((0.5 - py) * 2 * MAX_DEG).toFixed(2), ((px - 0.5) * 2 * MAX_DEG).toFixed(2), (px * 100).toFixed(1), (py * 100).toFixed(1));
});
});
deck.addEventListener('pointerleave', () => {
if (raf) cancelAnimationFrame(raf);
set(0, 0, 50, 50);
});
})();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";
/* Registered so they can INTERPOLATE. An unregistered custom property is an
untyped token: the tilt would snap back instead of easing, and the holo
angle could not be animated by keyframes at all. No utility form exists. */
@property --stk-06-tilt-x {
syntax: '<angle>';
initial-value: 0deg;
inherits: true;
}
@property --stk-06-tilt-y {
syntax: '<angle>';
initial-value: 0deg;
inherits: true;
}
@property --stk-06-mx {
syntax: '<percentage>';
initial-value: 50%;
inherits: true;
}
@property --stk-06-my {
syntax: '<percentage>';
initial-value: 50%;
inherits: true;
}
@property --stk-06-holo {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
@theme {
--color-stk06-bg: #04050a;
--color-stk06-bg: oklch(0.10 0.015 275);
--color-stk06-card: #1a1c2e;
--color-stk06-card: oklch(0.24 0.03 285);
--color-stk06-deep: #0f1120;
--color-stk06-deep: oklch(0.18 0.028 285);
--color-stk06-edge: #2a2d48;
--color-stk06-edge: oklch(0.32 0.035 285);
--color-stk06-num: #6a72a8;
--color-stk06-num: oklch(0.55 0.06 285);
--color-stk06-body: #c3c7de;
--color-stk06-body: oklch(0.83 0.02 285);
--color-stk06-a: #c77ce8;
--color-stk06-a: oklch(0.72 0.18 320);
--color-stk06-b: #3fa6e0;
--color-stk06-b: oklch(0.68 0.20 200);
--color-stk06-c: #e8ab48;
--color-stk06-c: oklch(0.75 0.19 60);
--font-stk06-sans: 'Archivo', system-ui, sans-serif;
--font-stk06-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-stk06: cubic-bezier(0.22, 1, 0.36, 1);
--animate-stk06-spin: stk-06-spin 8s linear infinite;
}
@keyframes stk-06-spin {
from {
--stk-06-holo: 0deg;
}
to {
--stk-06-holo: 360deg;
}
}
/* Older Safari spells mask-composite differently, and one class list cannot
carry two declarations of the same property. */
.stk-06__rim {
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
}@import "tailwindcss";
/* Registered so they can INTERPOLATE. An unregistered custom property is an
untyped token: the tilt would snap back instead of easing, and the holo
angle could not be animated by keyframes at all. No utility form exists. */
@property --stk-06-tilt-x {
syntax: '<angle>';
initial-value: 0deg;
inherits: true;
}
@property --stk-06-tilt-y {
syntax: '<angle>';
initial-value: 0deg;
inherits: true;
}
@property --stk-06-mx {
syntax: '<percentage>';
initial-value: 50%;
inherits: true;
}
@property --stk-06-my {
syntax: '<percentage>';
initial-value: 50%;
inherits: true;
}
@property --stk-06-holo {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
@theme {
--color-stk06-bg: #04050a;
--color-stk06-bg: oklch(0.10 0.015 275);
--color-stk06-card: #1a1c2e;
--color-stk06-card: oklch(0.24 0.03 285);
--color-stk06-deep: #0f1120;
--color-stk06-deep: oklch(0.18 0.028 285);
--color-stk06-edge: #2a2d48;
--color-stk06-edge: oklch(0.32 0.035 285);
--color-stk06-num: #6a72a8;
--color-stk06-num: oklch(0.55 0.06 285);
--color-stk06-body: #c3c7de;
--color-stk06-body: oklch(0.83 0.02 285);
--color-stk06-a: #c77ce8;
--color-stk06-a: oklch(0.72 0.18 320);
--color-stk06-b: #3fa6e0;
--color-stk06-b: oklch(0.68 0.20 200);
--color-stk06-c: #e8ab48;
--color-stk06-c: oklch(0.75 0.19 60);
--font-stk06-sans: 'Archivo', system-ui, sans-serif;
--font-stk06-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-stk06: cubic-bezier(0.22, 1, 0.36, 1);
--animate-stk06-spin: stk-06-spin 8s linear infinite;
}
@keyframes stk-06-spin {
from {
--stk-06-holo: 0deg;
}
to {
--stk-06-holo: 360deg;
}
}
/* Older Safari spells mask-composite differently, and one class list cannot
carry two declarations of the same property. */
.stk-06__rim {
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
}<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=Archivo:wdth,wght@75..125,400;75..125,700;75..125,800&family=JetBrains+Mono:wght@400;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=Archivo:wdth,wght@75..125,400;75..125,700;75..125,800&family=JetBrains+Mono:wght@400;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. - This is the most expensive port in the collection: five
@propertyregistrations, one@keyframesblock and one-webkit-mask-compositefallback all have to stay in CSS. Everything else moves to the markup. - The five parallax planes now declare their own depth —
[transform:translateZ(30px)]sits on the content layer instead of in a rule named after it. mix-blend-color-dodgeandmix-blend-overlayare real utilities; only theconic-gradientandradial-gradientthey blend need arbitrary values.color-mix(in_oklch,…)works inside arbitrary values as long as every space becomes an underscore — and it reads the same@themetokens the plain utilities do, so a rebrand is still one block.transition-[--stk-06-tilt-x,--stk-06-tilt-y]transitions custom properties by name, which is legal only because they are registered.- The keyboard path is a variant, not a rule:
focus-visible:[--stk-06-tilt-x:10deg]parks the deck at a flattering angle, andhas-focus-visible:on the scene draws the ring outside the 3D context where it will not be foreshortened.
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 3D Perspective Tilt Card Stack
Source: https://codefronts.com/components/tailwind-stacked-cards/tailwind-3d-perspective-tilt-card-stack/
The holographic deck: five parallax planes, an iridescent conic sweep, a cursor-tracked specular glare and a mask-composite border, tilting toward the pointer in real time. This is the demo where the port costs the most — @property, @keyframes and the mask trick all stay in CSS — and the payoff is that every translateZ plane is now readable on the element it belongs to instead of five rules away.
## HTML
```html
<section class="stk-06 w-full min-h-svh flex items-center justify-center px-5 py-15 font-stk06-sans bg-stk06-bg [background-image:radial-gradient(circle_at_20%_30%,color-mix(in_oklch,var(--color-stk06-a)_22%,transparent),transparent_60%),radial-gradient(circle_at_80%_70%,color-mix(in_oklch,var(--color-stk06-b)_22%,transparent),transparent_60%)]" aria-label="Holographic 3D perspective tilt card stack">
<div class="relative w-[min(340px,100%)] h-115 [perspective:1200px] [perspective-origin:center_40%] has-focus-visible:outline-3 has-focus-visible:outline-stk06-a has-focus-visible:outline-offset-[12px] has-focus-visible:rounded-[26px]">
<div class="group relative size-full cursor-grab [transform-style:preserve-3d] [transform:rotateX(var(--stk-06-tilt-x))_rotateY(var(--stk-06-tilt-y))] transition-[--stk-06-tilt-x,--stk-06-tilt-y] duration-400 ease-stk06 focus-visible:outline-none focus-visible:[--stk-06-tilt-x:10deg] focus-visible:[--stk-06-tilt-y:-12deg] motion-reduce:transition-none" id="stk-06-deck" tabindex="0" role="button" aria-label="Interactive holographic card deck. Move the pointer to tilt it.">
<div class="absolute inset-0 rounded-[22px] bg-linear-[160deg,var(--color-stk06-card),var(--color-stk06-deep)] border border-stk06-edge shadow-[0_30px_60px_-30px_rgba(0,0,0,0.9)] brightness-50 [transform:translateZ(-100px)_translateY(20px)_scale(0.92)]" aria-hidden="true">
<span class="absolute top-5 right-[22px] font-stk06-mono text-sm font-bold tracking-[0.05em] text-stk06-num">03</span>
</div>
<div class="absolute inset-0 rounded-[22px] bg-linear-[160deg,var(--color-stk06-card),var(--color-stk06-deep)] border border-stk06-edge shadow-[0_30px_60px_-30px_rgba(0,0,0,0.9)] brightness-75 [transform:translateZ(-50px)_translateY(10px)_scale(0.96)]" aria-hidden="true">
<span class="absolute top-5 right-[22px] font-stk06-mono text-sm font-bold tracking-[0.05em] text-stk06-num">02</span>
</div>
<div class="absolute inset-0 overflow-hidden rounded-[22px] border border-stk06-edge shadow-[0_30px_60px_-30px_rgba(0,0,0,0.9)] [transform:translateZ(0)] [background-image:linear-gradient(155deg,color-mix(in_oklch,var(--color-stk06-a)_12%,var(--color-stk06-card))_0%,var(--color-stk06-deep)_100%)]">
<div class="absolute inset-0 rounded-[inherit] pointer-events-none opacity-85 blur-[20px] [transform:translateZ(4px)] [background-image:radial-gradient(circle_at_30%_30%,color-mix(in_oklch,var(--color-stk06-a)_35%,transparent),transparent_50%),radial-gradient(circle_at_70%_70%,color-mix(in_oklch,var(--color-stk06-b)_35%,transparent),transparent_50%)]" aria-hidden="true"></div>
<div class="absolute inset-0 rounded-[inherit] pointer-events-none opacity-[0.32] mix-blend-color-dodge animate-stk06-spin motion-reduce:animate-none [transform:translateZ(6px)] [background-image:conic-gradient(from_var(--stk-06-holo)_at_var(--stk-06-mx)_var(--stk-06-my),color-mix(in_oklch,var(--color-stk06-a)_70%,transparent),color-mix(in_oklch,var(--color-stk06-b)_70%,transparent),color-mix(in_oklch,var(--color-stk06-c)_70%,transparent),color-mix(in_oklch,var(--color-stk06-a)_70%,transparent))]" aria-hidden="true"></div>
<div class="absolute inset-0 flex h-full flex-col justify-end gap-2 px-[30px] py-8 text-white [transform:translateZ(30px)]">
<p class="m-0 font-stk06-mono text-[11px] font-bold tracking-[0.22em] uppercase text-stk06-a">Holographic · 01</p>
<h3 class="m-0 text-[32px] font-extrabold leading-[1.05] tracking-[-0.03em] text-white">Depth Pass</h3>
<p class="m-0 max-w-[26ch] text-sm leading-snug text-stk06-body">Move your pointer to tilt the deck. Layered parallax, iridescent sweep, real specular glare.</p>
</div>
<div class="absolute inset-0 rounded-[inherit] pointer-events-none opacity-0 mix-blend-overlay transition-opacity duration-300 group-hover:opacity-100 group-focus-visible:opacity-100 [transform:translateZ(35px)] [background-image:radial-gradient(200px_circle_at_var(--stk-06-mx)_var(--stk-06-my),rgba(255,255,255,0.35),transparent_55%)]" aria-hidden="true"></div>
<div class="stk-06__rim absolute inset-0 rounded-[inherit] pointer-events-none p-0.5 opacity-70 transition-opacity duration-300 group-hover:opacity-100 group-focus-visible:opacity-100 [transform:translateZ(38px)] [background-image:conic-gradient(from_var(--stk-06-holo),var(--color-stk06-a),var(--color-stk06-b),var(--color-stk06-c),var(--color-stk06-a))] [mask:linear-gradient(#000_0_0)_content-box,linear-gradient(#000_0_0)] [mask-composite:exclude]" aria-hidden="true"></div>
</div>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
/* Registered so they can INTERPOLATE. An unregistered custom property is an
untyped token: the tilt would snap back instead of easing, and the holo
angle could not be animated by keyframes at all. No utility form exists. */
@property --stk-06-tilt-x {
syntax: '<angle>';
initial-value: 0deg;
inherits: true;
}
@property --stk-06-tilt-y {
syntax: '<angle>';
initial-value: 0deg;
inherits: true;
}
@property --stk-06-mx {
syntax: '<percentage>';
initial-value: 50%;
inherits: true;
}
@property --stk-06-my {
syntax: '<percentage>';
initial-value: 50%;
inherits: true;
}
@property --stk-06-holo {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
@theme {
--color-stk06-bg: #04050a;
--color-stk06-bg: oklch(0.10 0.015 275);
--color-stk06-card: #1a1c2e;
--color-stk06-card: oklch(0.24 0.03 285);
--color-stk06-deep: #0f1120;
--color-stk06-deep: oklch(0.18 0.028 285);
--color-stk06-edge: #2a2d48;
--color-stk06-edge: oklch(0.32 0.035 285);
--color-stk06-num: #6a72a8;
--color-stk06-num: oklch(0.55 0.06 285);
--color-stk06-body: #c3c7de;
--color-stk06-body: oklch(0.83 0.02 285);
--color-stk06-a: #c77ce8;
--color-stk06-a: oklch(0.72 0.18 320);
--color-stk06-b: #3fa6e0;
--color-stk06-b: oklch(0.68 0.20 200);
--color-stk06-c: #e8ab48;
--color-stk06-c: oklch(0.75 0.19 60);
--font-stk06-sans: 'Archivo', system-ui, sans-serif;
--font-stk06-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-stk06: cubic-bezier(0.22, 1, 0.36, 1);
--animate-stk06-spin: stk-06-spin 8s linear infinite;
}
@keyframes stk-06-spin {
from {
--stk-06-holo: 0deg;
}
to {
--stk-06-holo: 360deg;
}
}
/* Older Safari spells mask-composite differently, and one class list cannot
carry two declarations of the same property. */
.stk-06__rim {
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
}
```
## JavaScript
```js
(() => {
const root = document.querySelector('.stk-06');
if (!root) return;
const deck = root.querySelector('#stk-06-deck');
const MAX_DEG = 14;
/* Touch devices fire synthetic mousemove events; without this guard the card
jitters under a phantom pointer. Reduced motion opts out entirely. */
if (!matchMedia('(hover: hover) and (pointer: fine)').matches) return;
if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
let raf = 0;
const set = (rx, ry, mx, my) => {
deck.style.setProperty('--stk-06-tilt-x', rx + 'deg');
deck.style.setProperty('--stk-06-tilt-y', ry + 'deg');
deck.style.setProperty('--stk-06-mx', mx + '%');
deck.style.setProperty('--stk-06-my', my + '%');
};
deck.addEventListener('pointermove', (e) => {
if (raf) cancelAnimationFrame(raf);
raf = requestAnimationFrame(() => {
const r = deck.getBoundingClientRect();
const px = (e.clientX - r.left) / r.width;
const py = (e.clientY - r.top) / r.height;
set(((0.5 - py) * 2 * MAX_DEG).toFixed(2), ((px - 0.5) * 2 * MAX_DEG).toFixed(2), (px * 100).toFixed(1), (py * 100).toFixed(1));
});
});
deck.addEventListener('pointerleave', () => {
if (raf) cancelAnimationFrame(raf);
set(0, 0, 50, 50);
});
})();
```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 3D Perspective Tilt Card Stack
Source: https://codefronts.com/components/tailwind-stacked-cards/tailwind-3d-perspective-tilt-card-stack/
The holographic deck: five parallax planes, an iridescent conic sweep, a cursor-tracked specular glare and a mask-composite border, tilting toward the pointer in real time. This is the demo where the port costs the most — @property, @keyframes and the mask trick all stay in CSS — and the payoff is that every translateZ plane is now readable on the element it belongs to instead of five rules away.
## HTML
```html
<section class="stk-06 w-full min-h-svh flex items-center justify-center px-5 py-15 font-stk06-sans bg-stk06-bg [background-image:radial-gradient(circle_at_20%_30%,color-mix(in_oklch,var(--color-stk06-a)_22%,transparent),transparent_60%),radial-gradient(circle_at_80%_70%,color-mix(in_oklch,var(--color-stk06-b)_22%,transparent),transparent_60%)]" aria-label="Holographic 3D perspective tilt card stack">
<div class="relative w-[min(340px,100%)] h-115 [perspective:1200px] [perspective-origin:center_40%] has-focus-visible:outline-3 has-focus-visible:outline-stk06-a has-focus-visible:outline-offset-[12px] has-focus-visible:rounded-[26px]">
<div class="group relative size-full cursor-grab [transform-style:preserve-3d] [transform:rotateX(var(--stk-06-tilt-x))_rotateY(var(--stk-06-tilt-y))] transition-[--stk-06-tilt-x,--stk-06-tilt-y] duration-400 ease-stk06 focus-visible:outline-none focus-visible:[--stk-06-tilt-x:10deg] focus-visible:[--stk-06-tilt-y:-12deg] motion-reduce:transition-none" id="stk-06-deck" tabindex="0" role="button" aria-label="Interactive holographic card deck. Move the pointer to tilt it.">
<div class="absolute inset-0 rounded-[22px] bg-linear-[160deg,var(--color-stk06-card),var(--color-stk06-deep)] border border-stk06-edge shadow-[0_30px_60px_-30px_rgba(0,0,0,0.9)] brightness-50 [transform:translateZ(-100px)_translateY(20px)_scale(0.92)]" aria-hidden="true">
<span class="absolute top-5 right-[22px] font-stk06-mono text-sm font-bold tracking-[0.05em] text-stk06-num">03</span>
</div>
<div class="absolute inset-0 rounded-[22px] bg-linear-[160deg,var(--color-stk06-card),var(--color-stk06-deep)] border border-stk06-edge shadow-[0_30px_60px_-30px_rgba(0,0,0,0.9)] brightness-75 [transform:translateZ(-50px)_translateY(10px)_scale(0.96)]" aria-hidden="true">
<span class="absolute top-5 right-[22px] font-stk06-mono text-sm font-bold tracking-[0.05em] text-stk06-num">02</span>
</div>
<div class="absolute inset-0 overflow-hidden rounded-[22px] border border-stk06-edge shadow-[0_30px_60px_-30px_rgba(0,0,0,0.9)] [transform:translateZ(0)] [background-image:linear-gradient(155deg,color-mix(in_oklch,var(--color-stk06-a)_12%,var(--color-stk06-card))_0%,var(--color-stk06-deep)_100%)]">
<div class="absolute inset-0 rounded-[inherit] pointer-events-none opacity-85 blur-[20px] [transform:translateZ(4px)] [background-image:radial-gradient(circle_at_30%_30%,color-mix(in_oklch,var(--color-stk06-a)_35%,transparent),transparent_50%),radial-gradient(circle_at_70%_70%,color-mix(in_oklch,var(--color-stk06-b)_35%,transparent),transparent_50%)]" aria-hidden="true"></div>
<div class="absolute inset-0 rounded-[inherit] pointer-events-none opacity-[0.32] mix-blend-color-dodge animate-stk06-spin motion-reduce:animate-none [transform:translateZ(6px)] [background-image:conic-gradient(from_var(--stk-06-holo)_at_var(--stk-06-mx)_var(--stk-06-my),color-mix(in_oklch,var(--color-stk06-a)_70%,transparent),color-mix(in_oklch,var(--color-stk06-b)_70%,transparent),color-mix(in_oklch,var(--color-stk06-c)_70%,transparent),color-mix(in_oklch,var(--color-stk06-a)_70%,transparent))]" aria-hidden="true"></div>
<div class="absolute inset-0 flex h-full flex-col justify-end gap-2 px-[30px] py-8 text-white [transform:translateZ(30px)]">
<p class="m-0 font-stk06-mono text-[11px] font-bold tracking-[0.22em] uppercase text-stk06-a">Holographic · 01</p>
<h3 class="m-0 text-[32px] font-extrabold leading-[1.05] tracking-[-0.03em] text-white">Depth Pass</h3>
<p class="m-0 max-w-[26ch] text-sm leading-snug text-stk06-body">Move your pointer to tilt the deck. Layered parallax, iridescent sweep, real specular glare.</p>
</div>
<div class="absolute inset-0 rounded-[inherit] pointer-events-none opacity-0 mix-blend-overlay transition-opacity duration-300 group-hover:opacity-100 group-focus-visible:opacity-100 [transform:translateZ(35px)] [background-image:radial-gradient(200px_circle_at_var(--stk-06-mx)_var(--stk-06-my),rgba(255,255,255,0.35),transparent_55%)]" aria-hidden="true"></div>
<div class="stk-06__rim absolute inset-0 rounded-[inherit] pointer-events-none p-0.5 opacity-70 transition-opacity duration-300 group-hover:opacity-100 group-focus-visible:opacity-100 [transform:translateZ(38px)] [background-image:conic-gradient(from_var(--stk-06-holo),var(--color-stk06-a),var(--color-stk06-b),var(--color-stk06-c),var(--color-stk06-a))] [mask:linear-gradient(#000_0_0)_content-box,linear-gradient(#000_0_0)] [mask-composite:exclude]" aria-hidden="true"></div>
</div>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
/* Registered so they can INTERPOLATE. An unregistered custom property is an
untyped token: the tilt would snap back instead of easing, and the holo
angle could not be animated by keyframes at all. No utility form exists. */
@property --stk-06-tilt-x {
syntax: '<angle>';
initial-value: 0deg;
inherits: true;
}
@property --stk-06-tilt-y {
syntax: '<angle>';
initial-value: 0deg;
inherits: true;
}
@property --stk-06-mx {
syntax: '<percentage>';
initial-value: 50%;
inherits: true;
}
@property --stk-06-my {
syntax: '<percentage>';
initial-value: 50%;
inherits: true;
}
@property --stk-06-holo {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
@theme {
--color-stk06-bg: #04050a;
--color-stk06-bg: oklch(0.10 0.015 275);
--color-stk06-card: #1a1c2e;
--color-stk06-card: oklch(0.24 0.03 285);
--color-stk06-deep: #0f1120;
--color-stk06-deep: oklch(0.18 0.028 285);
--color-stk06-edge: #2a2d48;
--color-stk06-edge: oklch(0.32 0.035 285);
--color-stk06-num: #6a72a8;
--color-stk06-num: oklch(0.55 0.06 285);
--color-stk06-body: #c3c7de;
--color-stk06-body: oklch(0.83 0.02 285);
--color-stk06-a: #c77ce8;
--color-stk06-a: oklch(0.72 0.18 320);
--color-stk06-b: #3fa6e0;
--color-stk06-b: oklch(0.68 0.20 200);
--color-stk06-c: #e8ab48;
--color-stk06-c: oklch(0.75 0.19 60);
--font-stk06-sans: 'Archivo', system-ui, sans-serif;
--font-stk06-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-stk06: cubic-bezier(0.22, 1, 0.36, 1);
--animate-stk06-spin: stk-06-spin 8s linear infinite;
}
@keyframes stk-06-spin {
from {
--stk-06-holo: 0deg;
}
to {
--stk-06-holo: 360deg;
}
}
/* Older Safari spells mask-composite differently, and one class list cannot
carry two declarations of the same property. */
.stk-06__rim {
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
}
```
## JavaScript
```js
(() => {
const root = document.querySelector('.stk-06');
if (!root) return;
const deck = root.querySelector('#stk-06-deck');
const MAX_DEG = 14;
/* Touch devices fire synthetic mousemove events; without this guard the card
jitters under a phantom pointer. Reduced motion opts out entirely. */
if (!matchMedia('(hover: hover) and (pointer: fine)').matches) return;
if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
let raf = 0;
const set = (rx, ry, mx, my) => {
deck.style.setProperty('--stk-06-tilt-x', rx + 'deg');
deck.style.setProperty('--stk-06-tilt-y', ry + 'deg');
deck.style.setProperty('--stk-06-mx', mx + '%');
deck.style.setProperty('--stk-06-my', my + '%');
};
deck.addEventListener('pointermove', (e) => {
if (raf) cancelAnimationFrame(raf);
raf = requestAnimationFrame(() => {
const r = deck.getBoundingClientRect();
const px = (e.clientX - r.left) / r.width;
const py = (e.clientY - r.top) / r.height;
set(((0.5 - py) * 2 * MAX_DEG).toFixed(2), ((px - 0.5) * 2 * MAX_DEG).toFixed(2), (px * 100).toFixed(1), (py * 100).toFixed(1));
});
});
deck.addEventListener('pointerleave', () => {
if (raf) cancelAnimationFrame(raf);
set(0, 0, 50, 50);
});
})();
```How this works
Perspective belongs to the parent, and utilities make that mistake easy to spot. [perspective:1200px] goes on the scene; [transform-style:preserve-3d] goes on the deck that rotates. Put perspective on the rotating element and 3D collapses into a 2D skew — the single most common failure in this pattern, and now it is visible in the markup which element carries which.
The typed properties cannot move. @property --stk-06-tilt-x { syntax: '<angle>' } is what makes the return-to-rest a smooth interpolation rather than a snap, and there is no variant, utility or arbitrary form for an at-rule. Same for the @keyframes that spins the holo angle. Everything else — the tilt transform itself, the five depths, both blend modes, the glare gradient — is markup.
class="[transform-style:preserve-3d]
[transform:rotateX(var(--stk-06-tilt-x))_rotateY(var(--stk-06-tilt-y))]
transition-[--stk-06-tilt-x,--stk-06-tilt-y] duration-400"Note the transition names custom properties. That only works because they are registered — an unregistered custom property is an untyped token and cannot be interpolated at all.
The iridescent border stays a mask sandwich. [mask:linear-gradient(#000_0_0)_content-box,linear-gradient(#000_0_0)] [mask-composite:exclude] with the -webkit-mask-composite:xor fallback in the stylesheet, because two values of the same property in one class list cannot both survive.
The script writes four numbers and nothing else. No classes, no styles beyond the four registered custom properties, throttled through requestAnimationFrame, and it returns early unless (hover:hover) and (pointer:fine) matches and reduced motion is off — so touch devices never get the phantom-pointer jitter.
Make it yours
- Rebrand the three iridescent hues via
--color-stk06-a/-b/-cin@theme;color-mix(in oklch)handles the interpolation between them. - Change tilt intensity with the
MAX_DEGconstant — 8deg is subtle, 14deg is the default, 20deg is a toy. - Adjust the parallax by editing each plane's
[transform:translateZ(…)]; wider Z gaps read as more pronounced depth. - Slow the sweep by editing the
--animate-stk06-spinduration token — 3s energetic, 8s default, 15s calm. - Swap
mix-blend-mode-color-dodgeformix-blend-mode-overlay(subtler) ormix-blend-mode-screen(lighter).
Gotchas — read before shipping
@propertycannot become a utility. Without the registration the tilt steps abruptly on return and the holo sweep does not animate at all.[perspective:…]on the tilting element flattens the 3D. It must sit on the parent.- Throttle pointer writes with
requestAnimationFrame. Rawpointermovefires 100+ times a second and writing four custom properties per event burns real CPU. - Guard with
(hover:hover) and (pointer:fine). Touch devices fire synthetic mousemove events and the card jitters under a pointer that is not there. mix-blend-mode-*needs an explicit background on the parent. Over a transparent parent the layers composite against nothing and the blend does nothing.- Two declarations of the same property cannot both live in one class list — the
-webkit-mask-compositefallback has to stay in the stylesheet. - Under reduced motion, skip attaching the handler and stop the spin. A slower hologram is not an accommodation.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 120+ | 16.4+ | 128+ | 120+ |
Needs @property (typed angles), color-mix(in oklch), mask-composite:exclude (Chrome 120+, Safari 15.4+, Firefox 120+), mix-blend-mode and 3D transforms. All shipped by mid-2024; older engines still read the layered 3D structure, just without smooth tilt or a moving sweep.