Skip to main content

Layout primitives

Spotlight Card code

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

resources/views/components/ui/spotlight-card.blade.php

@props([
    'icon' => null,
    'iconWeight' => 'duotone',
    'title' => null,
    'href' => null,
    'meta' => null,
])

@php
    /*
    | Animated sibling of <x-ui.glow-card>. Keeps the cursor-following spotlight,
    | and adds a subtle 3D tilt that tracks the pointer plus a lift on hover.
    | Renders as an <a> when `href` is set. Tilt is pointer-driven only, so it
    | stays still for keyboard/touch users and under reduced motion.
    |
    | Usage:
    |   <x-ui.spotlight-card icon="lightning" title="Realtime" href="#">
    |       Stream events to any endpoint.
    |   </x-ui.spotlight-card>
    */
    $tag = $href ? 'a' : 'div';
@endphp

<{{ $tag }}
    @if ($href) href="{{ $href }}" @endif
    x-data="{
        gx: '50%', gy: '50%', rx: 0, ry: 0,
        reduced: window.matchMedia('(prefers-reduced-motion: reduce)').matches,
        move($event) {
            const r = $el.getBoundingClientRect();
            const px = ($event.clientX - r.left) / r.width;
            const py = ($event.clientY - r.top) / r.height;
            this.gx = (px * 100) + '%';
            this.gy = (py * 100) + '%';
            if (! this.reduced) {
                this.rx = (0.5 - py) * 8;
                this.ry = (px - 0.5) * 8;
            }
        },
        reset() { this.rx = 0; this.ry = 0; this.gx = '50%'; this.gy = '50%'; },
    }"
    @mousemove="move($event)"
    @mouseleave="reset()"
    style="--glow-x: 50%; --glow-y: 50%"
    :style="`--glow-x: ${gx}; --glow-y: ${gy}; transform: perspective(900px) rotateX(${rx}deg) rotateY(${ry}deg)`"
    {{ $attributes->merge(['class' => 'group/spot relative block overflow-hidden rounded-2xl border border-gray-200 bg-white p-6 transition-[border-color,box-shadow,transform] duration-300 hover:-translate-y-1 hover:border-brand-300 hover:shadow-xl hover:shadow-brand-500/10 dark:border-gray-800 dark:bg-gray-900 dark:hover:border-brand-700']) }}
>
    {{-- Cursor-following spotlight --}}
    <div
        class="pointer-events-none absolute inset-0 -z-0 opacity-0 transition-opacity duration-300 group-hover/spot:opacity-100"
        style="background: radial-gradient(360px circle at var(--glow-x) var(--glow-y), var(--color-brand-500), transparent 40%); mask-image: radial-gradient(360px circle at var(--glow-x) var(--glow-y), black, transparent 40%);"
        aria-hidden="true"
    ></div>
    <div class="pointer-events-none absolute inset-px -z-0 rounded-[15px] bg-white dark:bg-gray-900" aria-hidden="true"></div>

    <div class="relative z-10" style="transform: translateZ(40px)">
        <div class="flex items-start justify-between gap-3">
            @if ($icon)
                <span class="flex h-11 w-11 items-center justify-center rounded-xl bg-brand-50 text-brand-600 ring-1 ring-brand-100 transition group-hover/spot:scale-110 group-hover/spot:bg-brand-100 dark:bg-brand-950/60 dark:text-brand-400 dark:ring-brand-900/60">
                    <x-ui.icon :name="$icon" :weight="$iconWeight" size="xl" />
                </span>
            @endif

            @if ($meta)
                <span class="text-xs font-medium uppercase tracking-wider text-gray-400 dark:text-gray-500">{{ $meta }}</span>
            @endif

            @if ($href)
                <x-ui.icon name="arrow-up-right" weight="bold" size="base" class="ml-auto text-gray-300 transition group-hover/spot:translate-x-0.5 group-hover/spot:-translate-y-0.5 group-hover/spot:text-brand-500 dark:text-gray-600" />
            @endif
        </div>

        @if ($title)
            <h3 class="mt-4 text-base font-semibold tracking-tight text-gray-900 dark:text-white">{{ $title }}</h3>
        @endif

        <div class="mt-1.5 text-sm leading-relaxed text-gray-600 dark:text-gray-400">
            {{ $slot }}
        </div>
    </div>
</{{ $tag }}>