Motiva
Мои коллекции
← каталог

Spotlight Hero

Прожектор на hero: широкий луч из угла одним радиальным градиентом — мягкость без blur. Появление one-shot, дальше только медленное дыхание трансформом.

cssautobasic0reduced-motion ✓
🎛 Настройки — крути и копируй

Пропсы

Разобрано из кода записи: обязательные — без «?», у остальных показано значение по умолчанию.

ПропТипПо умолчаниюЧто это
backdrop?string | falseBACKDROPПодложка под светом: CSS-значение `background` или `false`. `false` нужен, когда запись стоит фоном СЕКЦИИ и подложку задаёт она.
hue?number290Тон света, градусы oklch.
angle?number-26Наклон луча, градусы.
strength?number0.55Яркость луча (0.2–1).
children?ReactNodeСодержимое поверх (в витрине — заголовок, в секции обычно пусто).

Установка

npx shadcn@latest add https://motiva.pages.dev/r/spotlight-hero.json
Что нужно в проекте до установки
  1. components.json в корне — с алиасами в форме"components": "@/components". Нет файла — CLI не поймёт, куда класть.
  2. Алиас @ объявлен дважды: "paths": {"@/*": ["./src/*"]} в корневом tsconfig.json (в Vite он часто содержит только references — тогда CLI создаёт папку@) и resolve.alias в конфиге сборщика.
  3. Токены подключены один раз на приложение:import "@/styles/motiva-tokens.css" — они приезжают вместе с записью. Без них секция потеряет цвета, ритм и тему.
  4. Своя типографика не должна спорить: если у вас на обёрткеtext-align: center, он протечёт внутрь блоков.
  5. Проект без TypeScript? Всё равно нужен минимальныйtsconfig.json с pathsи "tsx": true в components.json: CLI грузит tsconfig безусловно, а с "tsx": false идёт за jsconfig.json и падает так же. Приехавший рядом.tsx можно не трогать — вам нужен.vanilla.js.

Экспорт

↓ Скачать .zip

Код

import type { CSSProperties, ReactNode } from "react";

/** Подложка по умолчанию — как в витрине. */
const BACKDROP = "oklch(0.13 0.015 275)";

export interface SpotlightHeroProps {
  /**
   * Подложка под светом: CSS-значение `background` или `false`.
   * `false` нужен, когда запись стоит фоном СЕКЦИИ и подложку задаёт она.
   */
  backdrop?: string | false;
  /** Тон света, градусы oklch. */
  hue?: number;
  /** Наклон луча, градусы. */
  angle?: number;
  /** Яркость луча (0.2–1). */
  strength?: number;
  /** Содержимое поверх (в витрине — заголовок, в секции обычно пусто). */
  children?: ReactNode;
}

/**
 * Прожектор на hero: широкий луч бьёт из верхнего левого угла. Мягкость даёт сам
 * радиальный градиент — ни одного filter: blur (полноэкранный blur — перерисовка,
 * AGENTS). Появление — одна анимация opacity+transform, дальше только медленное
 * «дыхание» трансформом: растр градиента закеширован композитором.
 * Reduced-motion / data-shot — свет стоит включённым (база = финал).
 */
export function SpotlightHero({
  backdrop = BACKDROP,
  hue = 290,
  angle = -26,
  strength = 0.55,
  children,
}: SpotlightHeroProps = {}) {
  return (
    <div
      className="sh"
      style={{
        position: "absolute",
        inset: 0,
        overflow: "hidden",
        ...(backdrop === false ? null : { background: backdrop }),
      }}
    >
      <style>{`
        .sh { container-type: size; }
        .sh-beam {
          position: absolute;
          width: 120cqw; height: 220cqh;
          border-radius: 999px;
          transform-origin: 50% 0;
          /* база = финальное состояние: при animation: none свет просто включён */
          opacity: 1;
          animation: sh-in 1.6s cubic-bezier(0.22, 1, 0.36, 1) both, sh-breathe 12s ease-in-out 1.6s infinite;
        }
        .sh-main {
          left: -34cqw; top: -120cqh;
          background: radial-gradient(50% 50% at 50% 50%, var(--sh-c1), transparent 68%);
          transform: rotate(var(--sh-angle));
        }
        .sh-side {
          right: -50cqw; top: -140cqh;
          background: radial-gradient(50% 50% at 50% 50%, var(--sh-c2), transparent 66%);
          transform: rotate(calc(var(--sh-angle) * -0.6));
          animation-delay: 0.25s, 1.85s;
        }
        @keyframes sh-in {
          from { opacity: 0; translate: 0 -12cqh; }
          to   { opacity: 1; translate: 0 0; }
        }
        @keyframes sh-breathe {
          0%, 100% { scale: 1; }
          50% { scale: 1.045; }
        }
        @media (prefers-reduced-motion: reduce) { .sh-beam { animation: none; } }
      `}</style>
      <div
        style={
          {
            position: "absolute",
            inset: 0,
            "--sh-angle": `${angle}deg`,
            "--sh-c1": `oklch(0.92 0.03 ${hue} / ${strength})`,
            "--sh-c2": `oklch(0.85 0.05 ${hue + 40} / ${strength * 0.4})`,
          } as CSSProperties
        }
      >
        <div className="sh-beam sh-main" />
        <div className="sh-beam sh-side" />
      </div>
      {children && (
        <div style={{ position: "absolute", inset: 0, display: "grid", placeItems: "center" }}>{children}</div>
      )}
    </div>
  );
}
лицензия: MITавтор: Motivaисточник: оригинал (Motiva); идея-референс — Aceternity Spotlight (только идея: terms запрещают заимствование кода)v1.0.0