Hero

Aurora Hero

Тёмный SaaS hero с ambient-свечением, grid-подложкой, badge и парой CTA. Самодостаточен: не зависит от темы и стиля проекта.

  • dark
  • saas
  • gradient
  • animated
  • centered
  • landing

Превью

1440pxHost theme

Использовать с AI

  1. 1. Скопируйте ссылку.
  2. 2. Напишите агенту своими словами и вставьте её в предложение.
  3. 3. Агент откроет ссылку и поставит компонент из registry.
Показать полную инструкцию

То, что лежит по ссылке в развёрнутом виде. Нужна, если агент не может открыть ссылку — тогда вставьте этот текст целиком.

# Установка и размещение «hero-001» (Aurora Hero) из VibeUI

## 1. Сначала установка — не пропускай, не пересоздавай
Команда установки недоступна: URL реестра VibeUI не сконфигурирован.

Ставится в: components/vibeui/hero-001.tsx (точный путь следует алиасам components.json этого проекта).
npm-зависимости: нет.
Registry-зависимости: нет.

Ставь из реестра. Не пересоздавай по описанию, не подменяй похожим
компонентом из другой библиотеки и не переписывай под стиль проекта.

## 2. Что это
Тёмный SaaS hero с ambient-свечением, grid-подложкой, badge и парой CTA. Самодостаточен: не зависит от темы и стиля проекта.

Тёмный центрированный SaaS-hero: badge, крупный заголовок в две части, подзаголовок, две CTA-ссылки, строка фактов. Ambient-свечение и grid-подложка на чистом CSS, ноль зависимостей, один файл.

## 3. Как использовать
Это целая секция страницы. Вставляй её одним куском — не вынимай из
неё фрагменты и не собирай заново из мелких компонентов.
Имя экспорта и пропсы смотри в установленном файле.

## 4. Куда поставить
Это полноширинная секция страницы. Поставь её прямым потомком
разметки страницы, в том порядке, который назвал пользователь. Не
вкладывай внутрь карточки, сайдбара, модального окна или другого
узкого контейнера: секция меряет собственную ширину и разложится
неправильно.

Куда поставить: ___
(Эту строку заполняет пользователь. Если она осталась пустой — спроси,
а не угадывай.)

## 5. Сохранить как установлено
- тёмный фон блока независимо от темы проекта
- локальную палитру --vibeui-hero-* — не заменять её на токены темы проекта (bg-background, text-foreground, primary и т.п.)
- ambient glow и grid-подложку
- clamp-типографику заголовка и tracking в единицах cqi
- entrance-анимации и стагер через animation-delay
- container-type: inline-size на корне блока и @container-правила в его <style> — блок меряет собственную ширину, а не ширину окна; не переписывай их на viewport-медиазапросы
- responsive-поведение CTA: столбик на узком блоке, строка от 40rem ширины блока
- семантику: один h1, CTA как <a href>, декоративные слои aria-hidden
- блок <style> внутри компонента — в нём keyframes и prefers-reduced-motion

## 6. Можно менять
- тексты badge, title, titleAccent, description, CTA и highlights
- href у CTA
- акцентный цвет — проп accent (и accentForeground для контраста текста на кнопке)
- внешние отступы через className

## 7. Правила
- Сохраняй семантику ссылок. Для внутренней навигации можно использовать компонент роутера проекта, если сохраняются визуальные стили, доступность и поведение href.
- Не добавляй motion-библиотеки: анимации уже на CSS.
- Не выноси CSS-переменные в globals.css: блок должен оставаться одним файлом.

## 8. Проверка
- рендерится без ошибок в консоли;
- выглядит так же, как превью на странице VibeUI, откуда взята ссылка;
- если нет — ты изменил что-то из раздела 5, верни как было.

Для разработчика

Команда установки недоступна: переменная окруженияREGISTRY_BASE_URL

Блок самодостаточен: один файл, без зависимостей, всегда тёмный независимо от темы проекта. Цвета живут в локальных переменных --vibeui-hero-*; акцент меняется пропом accent. Keyframes и prefers-reduced-motion лежат внутри компонента, globals.css править не нужно.

Исходник компонента

Тот же файл, который поставит агент. Нужен, если вы предпочитаете скопировать код руками.

import type { CSSProperties } from "react"

type Hero001Action = {
  label: string
  href: string
}

export type Hero001Props = {
  badge?: string
  title?: string
  titleAccent?: string
  description?: string
  primaryAction?: Hero001Action
  secondaryAction?: Hero001Action
  highlights?: string[]
  accent?: string
  accentForeground?: string
  className?: string
}

// Блок везде выглядит одинаково, поэтому палитра, шрифт и keyframes живут здесь,
// а не в globals.css проекта. Селектор в :where() — нулевая специфичность,
// так что любой класс или inline style пользователя переопределяет значение.
//
// container-type делает блок собственным query-контейнером: раскладка и размер
// шрифта считаются от ширины блока, а не от ширины окна. Поэтому блок выглядит
// одинаково и на странице, и в масштабированной миниатюре каталога, где он
// рендерится в 1280px внутри узкого окна. Правила раскладки живут здесь, а не
// в Tailwind-вариантах, чтобы блок не зависел от версии Tailwind в чужом
// проекте; специфичность (0,2,0) — выше утилит, поэтому они переопределяются.
const STYLES = `
:where([data-vibeui-block="hero-001"]){
--vibeui-hero-bg:oklch(0.16 0.014 266);
--vibeui-hero-fg:oklch(0.98 0.003 266);
--vibeui-hero-muted:oklch(0.75 0.019 266);
--vibeui-hero-border:oklch(1 0 0 / 14%);
--vibeui-hero-accent:oklch(0.72 0.163 264);
--vibeui-hero-accent-fg:oklch(0.17 0.02 266);
--vibeui-hero-ring:color-mix(in oklab, var(--vibeui-hero-accent) 75%, transparent);
--vibeui-hero-glow:color-mix(in oklab, var(--vibeui-hero-accent) 38%, transparent);
--vibeui-hero-grid:oklch(1 0 0 / 6%);
--vibeui-hero-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="hero-001"]{container-type:inline-size}
@container (min-width:40rem){
[data-vibeui-block="hero-001"] [data-part="frame"]{padding-left:2.5rem;padding-right:2.5rem}
[data-vibeui-block="hero-001"] [data-part="actions"]{width:auto;flex-direction:row}
}
@container (min-width:48rem){
[data-vibeui-block="hero-001"] [data-part="frame"]{min-height:680px;padding-top:8rem;padding-bottom:8rem}
}
@keyframes vibeui-hero-001-fade-up{from{opacity:0;transform:translate3d(0,14px,0)}to{opacity:1;transform:none}}
@keyframes vibeui-hero-001-glow{0%,100%{opacity:.68}50%{opacity:1}}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="hero-001"] *{animation:none!important;transition:none!important}}
`

const ENTER =
  "animate-[vibeui-hero-001-fade-up_0.6s_cubic-bezier(0.16,1,0.3,1)_both]"

const ACTION_BASE =
  "group inline-flex h-12 items-center justify-center gap-2 rounded-full px-7 text-[0.95rem] font-medium focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--vibeui-hero-ring)]"

function cx(...classes: (string | false | undefined)[]) {
  return classes.filter(Boolean).join(" ")
}

export function Hero001({
  badge = "Now in public beta",
  title = "Ship your product",
  titleAccent = "twice as fast",
  description = "Everything your team needs to design, build and launch — in one workspace that stays out of the way.",
  primaryAction = { label: "Start building", href: "#" },
  secondaryAction = { label: "Book a demo", href: "#" },
  highlights = ["No credit card", "14-day trial", "SOC 2 compliant"],
  accent,
  accentForeground,
  className,
}: Hero001Props) {
  const style = {
    ...(accent ? { "--vibeui-hero-accent": accent } : {}),
    ...(accentForeground
      ? { "--vibeui-hero-accent-fg": accentForeground }
      : {}),
  } as CSSProperties

  return (
    <section
      data-vibeui-block="hero-001"
      style={style}
      className={cx(
        "relative isolate overflow-hidden bg-[var(--vibeui-hero-bg)] font-[family-name:var(--vibeui-hero-font)] text-[var(--vibeui-hero-fg)] antialiased",
        className,
      )}
    >
      <style href="vibeui-hero-001" precedence="medium">
        {STYLES}
      </style>

      <div
        aria-hidden="true"
        className="pointer-events-none absolute inset-x-0 -top-[35%] h-[85%] animate-[vibeui-hero-001-glow_12s_ease-in-out_infinite]"
        style={{
          background:
            "radial-gradient(50% 50% at 50% 55%, var(--vibeui-hero-glow), transparent 72%)",
        }}
      />
      <div
        aria-hidden="true"
        className="pointer-events-none absolute inset-0"
        style={{
          backgroundImage:
            "linear-gradient(to right, var(--vibeui-hero-grid) 1px, transparent 1px), linear-gradient(to bottom, var(--vibeui-hero-grid) 1px, transparent 1px)",
          backgroundSize: "64px 64px",
          maskImage:
            "radial-gradient(60% 55% at 50% 45%, black, transparent 100%)",
          WebkitMaskImage:
            "radial-gradient(60% 55% at 50% 45%, black, transparent 100%)",
        }}
      />

      {/* Отступы и высота живут на внутреннем слое: container-запросы читают
          ширину секции, а сама секция своим контейнером быть не может. */}
      <div
        data-part="frame"
        className="relative flex min-h-[560px] items-center px-6 py-24"
      >
        <div className="mx-auto flex w-full max-w-3xl flex-col items-center text-center">
          {badge ? (
            <span
              className={cx(
                ENTER,
                "inline-flex items-center gap-2 rounded-full border border-[var(--vibeui-hero-border)] px-3.5 py-1.5 text-xs font-medium tracking-wide text-[var(--vibeui-hero-muted)]",
              )}
            >
              <span
                aria-hidden="true"
                className="size-1.5 rounded-full bg-[var(--vibeui-hero-accent)]"
              />
              {badge}
            </span>
          ) : null}

          <h1
            className={cx(
              ENTER,
              "mt-7 text-[clamp(2.25rem,6cqi,4.75rem)] leading-[1.04] font-semibold tracking-tight text-balance break-words [animation-delay:60ms]",
            )}
          >
            {title}
            {titleAccent ? (
              <>
                {" "}
                <span className="text-[var(--vibeui-hero-muted)]">
                  {titleAccent}
                </span>
              </>
            ) : null}
          </h1>

          {description ? (
            <p
              className={cx(
                ENTER,
                "mt-6 max-w-xl text-[clamp(1rem,1.6cqi,1.15rem)] leading-relaxed text-pretty break-words text-[var(--vibeui-hero-muted)] [animation-delay:120ms]",
              )}
            >
              {description}
            </p>
          ) : null}

          <div
            data-part="actions"
            className={cx(
              ENTER,
              "mt-10 flex w-full max-w-full flex-col gap-3 [animation-delay:180ms]",
            )}
          >
            {primaryAction ? (
              <a
                href={primaryAction.href}
                className={cx(
                  ACTION_BASE,
                  "bg-[var(--vibeui-hero-accent)] text-[var(--vibeui-hero-accent-fg)] transition-[filter,transform] duration-150 hover:-translate-y-px hover:brightness-110",
                )}
                style={{
                  boxShadow:
                    "0 14px 40px -14px color-mix(in oklab, var(--vibeui-hero-accent) 70%, transparent)",
                }}
              >
                {primaryAction.label}
              </a>
            ) : null}

            {secondaryAction ? (
              <a
                href={secondaryAction.href}
                className={cx(
                  ACTION_BASE,
                  "border border-[var(--vibeui-hero-border)] text-[var(--vibeui-hero-fg)] transition-colors duration-150 hover:bg-[color-mix(in_oklab,var(--vibeui-hero-fg)_10%,transparent)]",
                )}
              >
                {secondaryAction.label}
                <svg
                  aria-hidden="true"
                  viewBox="0 0 16 16"
                  fill="none"
                  className="size-4 transition-transform duration-150 group-hover:translate-x-0.5"
                >
                  <path
                    d="M3 8h9m0 0L8.5 4.5M12 8l-3.5 3.5"
                    stroke="currentColor"
                    strokeWidth="1.5"
                    strokeLinecap="round"
                    strokeLinejoin="round"
                  />
                </svg>
              </a>
            ) : null}
          </div>

          {highlights.length > 0 ? (
            <ul
              className={cx(
                ENTER,
                "mt-10 flex flex-wrap items-center justify-center gap-x-3 gap-y-2 text-sm text-[var(--vibeui-hero-muted)] [animation-delay:240ms]",
              )}
            >
              {highlights.map((highlight, index) => (
                <li key={highlight} className="flex items-center gap-3">
                  {index > 0 ? (
                    <span
                      aria-hidden="true"
                      className="size-1 rounded-full bg-[var(--vibeui-hero-border)]"
                    />
                  ) : null}
                  {highlight}
                </li>
              ))}
            </ul>
          ) : null}
        </div>
      </div>
    </section>
  )
}