Content & marketing
Parallax Card Stack code
Copy the implementation or inspect every file that belongs to this component unit.
resources/views/components/ui/parallax-card-stack.blade.php
@props([
'image',
'alt' => '',
'eyebrow' => null,
'title' => null,
'description' => null,
])
@php
/*
| Floating card-stack parallax. Cards are arranged as a layered stack and
| separate on pointer movement for a premium product-story treatment.
*/
$cards = [
['label' => 'Scout', 'copy' => 'Foreground card tracks widest', 'class' => 'left-4 top-8', 'x' => 112, 'y' => 70, 'rotate' => -8],
['label' => 'Route', 'copy' => 'Middle plane stays readable', 'class' => 'right-10 top-28', 'x' => -72, 'y' => 54, 'rotate' => 6],
['label' => 'Hold', 'copy' => 'Back card anchors the stack', 'class' => 'left-20 bottom-12', 'x' => 48, 'y' => -42, 'rotate' => 4],
['label' => 'Signal', 'copy' => 'Opposite drift adds contrast', 'class' => 'right-4 bottom-20', 'x' => -124, 'y' => -86, 'rotate' => 10],
];
@endphp
<section
x-data="{
px: 0, py: 0,
reduced: window.matchMedia('(prefers-reduced-motion: reduce)').matches,
move($event) {
if (this.reduced) {
return;
}
const rect = $el.getBoundingClientRect();
this.px = (($event.clientX - rect.left) / rect.width) - 0.5;
this.py = (($event.clientY - rect.top) / rect.height) - 0.5;
},
reset() {
this.px = 0;
this.py = 0;
},
}"
@mousemove="move($event)"
@mouseleave="reset()"
{{ $attributes->merge(['class' => 'relative isolate flex min-h-screen overflow-hidden bg-gray-950']) }}
>
<img
src="{{ $image }}"
alt="{{ $alt }}"
@if ($alt === '') aria-hidden="true" @endif
class="pointer-events-none absolute inset-0 -z-20 h-full w-full scale-[1.12] object-cover will-change-transform motion-reduce:scale-100 motion-reduce:transform-none"
style="transform: translate3d(0, 0, 0) scale(1.12);"
:style="reduced ? 'transform: translate3d(0, 0, 0) scale(1);' : `transform: translate3d(${px * -48}px, ${py * -34}px, 0) scale(1.12);`"
>
<div class="absolute inset-0 -z-10 bg-gradient-to-br from-gray-950/86 via-gray-950/35 to-gray-950/78" aria-hidden="true"></div>
<div class="mx-auto grid w-full max-w-7xl items-center gap-12 px-6 py-16 sm:px-8 lg:grid-cols-[0.88fr_1.12fr] lg:px-10">
<div>
@if ($eyebrow)
<x-ui.badge variant="brand" icon="stack" class="bg-white/12 text-white ring-1 ring-white/20 backdrop-blur dark:bg-white/12 dark:text-white">
{{ $eyebrow }}
</x-ui.badge>
@endif
@if ($title)
<h2 class="mt-5 text-4xl font-semibold tracking-tight text-white text-balance sm:text-6xl">{{ $title }}</h2>
@endif
@if ($description)
<p class="mt-5 max-w-2xl text-base leading-7 text-white/78 text-pretty sm:text-lg">{{ $description }}</p>
@endif
@isset($actions)
<div class="mt-8 flex flex-col gap-3 sm:flex-row">{{ $actions }}</div>
@endisset
</div>
<div class="relative min-h-[36rem]">
<div
class="absolute inset-10 overflow-hidden rounded-[2rem] border border-white/15 bg-white/10 shadow-2xl shadow-gray-950/60 will-change-transform"
:style="reduced ? '' : `transform: perspective(900px) rotateX(${py * -8}deg) rotateY(${px * 12}deg) translate3d(${px * 20}px, ${py * 14}px, 0);`"
>
<img src="{{ $image }}" alt="" class="h-full w-full scale-110 object-cover">
<div class="absolute inset-0 bg-gradient-to-t from-gray-950/80 via-transparent to-white/10" aria-hidden="true"></div>
</div>
@foreach ($cards as $card)
<div
class="absolute {{ $card['class'] }} w-52 rounded-2xl border border-white/15 bg-gray-950/46 p-5 text-white shadow-2xl shadow-gray-950/50 backdrop-blur-md will-change-transform"
:style="reduced ? '' : 'transform: translate3d(' + (px * {{ $card['x'] }}) + 'px, ' + (py * {{ $card['y'] }}) + 'px, 0) rotate(' + (px * {{ $card['rotate'] }}) + 'deg);'"
aria-hidden="true"
>
<p class="text-sm font-semibold">{{ $card['label'] }}</p>
<p class="mt-2 text-sm leading-6 text-white/68">{{ $card['copy'] }}</p>
</div>
@endforeach
</div>
</div>
</section>