Content & marketing
Parallax Masked Title code
Copy the implementation or inspect every file that belongs to this component unit.
resources/views/components/ui/parallax-masked-title.blade.php
@props([
'image',
'alt' => '',
'eyebrow' => null,
'title' => null,
'description' => null,
'overlayClass' => 'bg-gradient-to-br from-gray-950/82 via-gray-950/38 to-gray-950/78',
])
@php
/*
| Premium masked-title parallax. The scene drifts behind the section while
| the headline uses the same image as a moving text fill.
*/
@endphp
<section
x-data="{
x: 0, y: 0, scroll: 0,
reduced: window.matchMedia('(prefers-reduced-motion: reduce)').matches,
move($event) {
if (this.reduced) {
return;
}
const rect = $el.getBoundingClientRect();
this.x = ((($event.clientX - rect.left) / rect.width) - 0.5) * 80;
this.y = ((($event.clientY - rect.top) / rect.height) - 0.5) * 46;
},
reset() {
this.x = 0;
this.y = 0;
},
update() {
if (this.reduced) {
this.scroll = 0;
return;
}
this.scroll = $el.getBoundingClientRect().top * -0.12;
},
}"
x-init="update()"
@scroll.window.passive="update()"
@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.14] object-cover will-change-transform motion-reduce:scale-100 motion-reduce:transform-none"
style="transform: translate3d(0, 0, 0) scale(1.14);"
:style="reduced ? 'transform: translate3d(0, 0, 0) scale(1);' : `transform: translate3d(${x * -0.55}px, calc(${scroll}px + ${y * -0.35}px), 0) scale(1.14);`"
>
<div class="absolute inset-0 -z-10 {{ $overlayClass }}" aria-hidden="true"></div>
<div class="absolute inset-x-0 bottom-0 -z-10 h-44 bg-gradient-to-t from-gray-950 to-transparent" aria-hidden="true"></div>
<div class="mx-auto grid w-full max-w-7xl items-center gap-10 px-6 py-16 sm:px-8 lg:grid-cols-[1fr_0.72fr] lg:px-10">
<div>
@if ($eyebrow)
<x-ui.badge variant="brand" icon="text-aa" 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-6 max-w-5xl bg-clip-text text-6xl font-semibold tracking-tight text-transparent text-balance sm:text-8xl lg:text-9xl"
style="background-image: url('{{ $image }}'); background-size: 150% auto; background-position: center;"
:style="reduced ? `background-image: url('{{ $image }}'); background-size: 150% auto; background-position: center;` : `background-image: url('{{ $image }}'); background-size: 150% auto; background-position: calc(50% + ${x}px) calc(50% + ${scroll * 0.18}px);`"
>
{{ $title }}
</h2>
@endif
@if ($description)
<p class="mt-6 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 hidden min-h-96 lg:block" aria-hidden="true">
<div
class="absolute left-4 top-8 h-64 w-52 overflow-hidden rounded-2xl border border-white/15 bg-white/10 shadow-2xl shadow-gray-950/50 backdrop-blur will-change-transform"
:style="reduced ? '' : `transform: translate3d(${x * 0.32}px, ${y * 0.5}px, 0) rotate(${x * 0.04}deg);`"
>
<img src="{{ $image }}" alt="" class="h-full w-full object-cover">
</div>
<div
class="absolute bottom-6 right-2 w-64 rounded-2xl border border-white/15 bg-gray-950/45 p-5 text-sm text-white shadow-2xl shadow-gray-950/50 backdrop-blur will-change-transform"
:style="reduced ? '' : `transform: translate3d(${x * -0.4}px, ${y * -0.56}px, 0) rotate(${x * -0.035}deg);`"
>
<p class="font-semibold">Image-filled type</p>
<p class="mt-2 leading-6 text-white/68">The headline is clipped to the moving landscape, so the type has its own parallax layer.</p>
</div>
</div>
</div>
</section>