Skip to main content

Content & marketing

Parallax Depth Panels code

Copy the implementation or inspect every file that belongs to this component unit.

resources/views/components/ui/parallax-depth-panels.blade.php

@props([
    'image',
    'alt' => '',
    'eyebrow' => null,
    'title' => null,
    'description' => null,
    'overlayClass' => 'bg-gradient-to-br from-gray-950/78 via-gray-950/28 to-gray-950/76',
])

@php
    /*
    | Mouse-tilt depth parallax. The background, content and floating panels move
    | at different depths to create a layered scene.
    */
    $panels = [
        [
            'class' => 'left-6 top-20',
            'title' => 'Foreground layer',
            'copy' => 'Moves faster than the image',
            'x' => 88,
            'y' => 62,
            'rotate' => 8,
            'surface' => 'bg-white/10',
        ],
        [
            'class' => 'right-8 top-28',
            'title' => 'Far ridge',
            'copy' => 'Slides with a slower offset',
            'x' => -42,
            'y' => -28,
            'rotate' => -5,
            'surface' => 'bg-gray-950/35',
        ],
        [
            'class' => 'left-16 bottom-28',
            'title' => 'Near brush',
            'copy' => 'Pulls wide for a closer plane',
            'x' => 118,
            'y' => -76,
            'rotate' => -10,
            'surface' => 'bg-brand-950/35',
        ],
        [
            'class' => 'right-12 bottom-24',
            'title' => 'Depth panel',
            'copy' => 'Opposite drift creates separation',
            'x' => -126,
            'y' => -84,
            'rotate' => 10,
            'surface' => 'bg-gray-950/45',
        ],
        [
            'class' => 'left-1/2 top-1/2 -translate-x-1/2',
            'title' => 'Mid layer',
            'copy' => 'Keeps the center subtly alive',
            'x' => 58,
            'y' => -52,
            'rotate' => 6,
            'surface' => 'bg-white/[0.08]',
        ],
    ];
@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.1] object-cover will-change-transform motion-reduce:scale-100 motion-reduce:transform-none"
        style="transform: translate3d(0, 0, 0) scale(1.1);"
        :style="reduced ? 'transform: translate3d(0, 0, 0) scale(1);' : `transform: translate3d(${px * -54}px, ${py * -34}px, 0) scale(1.14);`"
    >

    <div class="absolute inset-0 -z-10 {{ $overlayClass }}" aria-hidden="true"></div>

    @foreach ($panels as $panel)
        <div
            class="pointer-events-none absolute {{ $panel['class'] }} hidden min-w-44 rounded-2xl border border-white/15 {{ $panel['surface'] }} px-5 py-4 text-sm text-white shadow-2xl shadow-gray-950/45 backdrop-blur-md will-change-transform md:block"
            :style="reduced ? '' : 'transform: translate3d(' + (px * {{ $panel['x'] }}) + 'px, ' + (py * {{ $panel['y'] }}) + 'px, 0) rotate(' + (px * {{ $panel['rotate'] }}) + 'deg);'"
            aria-hidden="true"
        >
            <p class="font-semibold">{{ $panel['title'] }}</p>
            <p class="mt-1 text-white/70">{{ $panel['copy'] }}</p>
        </div>
    @endforeach

    <div class="mx-auto flex w-full max-w-7xl items-center px-6 py-16 sm:px-8 lg:px-10">
        <div
            class="max-w-3xl will-change-transform motion-reduce:transform-none"
            :style="reduced ? '' : `transform: perspective(900px) rotateX(${py * -12}deg) rotateY(${px * 16}deg) translate3d(${px * 34}px, ${py * 26}px, 0);`"
        >
            @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-gray-100/85 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>
</section>