Features
Spotlight Features
Тёмная секция возможностей с иерархией: полноширинная шапка, крупная панель с одной главной возможностью и абстрактной геометрией, рядом три второстепенные строки на волосяных линиях. Самодостаточна: не зависит от темы и стиля проекта.
- dark
- features
- saas
- editorial
- landing
- section
Превью
Использовать с AI
- 1. Скопируйте ссылку.
- 2. Напишите агенту своими словами и вставьте её в предложение.
- 3. Агент откроет ссылку и поставит компонент из registry.
Показать полную инструкцию
То, что лежит по ссылке в развёрнутом виде. Нужна, если агент не может открыть ссылку — тогда вставьте этот текст целиком.
# Установка и размещение «features-001» (Spotlight Features) из VibeUI ## 1. Сначала установка — не пропускай, не пересоздавай Команда установки недоступна: URL реестра VibeUI не сконфигурирован. Ставится в: components/vibeui/features-001.tsx (точный путь следует алиасам components.json этого проекта). npm-зависимости: нет. Registry-зависимости: нет. Ставь из реестра. Не пересоздавай по описанию, не подменяй похожим компонентом из другой библиотеки и не переписывай под стиль проекта. ## 2. Что это Тёмная секция возможностей с иерархией: полноширинная шапка, крупная панель с одной главной возможностью и абстрактной геометрией, рядом три второстепенные строки на волосяных линиях. Самодостаточна: не зависит от темы и стиля проекта. Тёмная графитовая секция возможностей. Полноширинная шапка: акцентная черта с eyebrow, h2 в две части, описание и опциональная ссылка. Ниже асимметричное тело: слева панель с одной главной возможностью — заголовок, абзац и абстрактная геометрическая иллюстрация на inline SVG; справа ровно три второстепенные возможности строками, разделёнными волосяными линиями, с акцентными индексами. Плоско, без теней и свечения. Разделители прочерчиваются, контент проявляется со стагером. Ноль зависимостей, один файл, раскладка и типографика считаются от ширины блока через container-запросы и cqi. ## 3. Как использовать Это целая секция страницы. Вставляй её одним куском — не вынимай из неё фрагменты и не собирай заново из мелких компонентов. Имя экспорта и пропсы смотри в установленном файле. ## 4. Куда поставить Это полноширинная секция страницы. Поставь её прямым потомком разметки страницы, в том порядке, который назвал пользователь. Не вкладывай внутрь карточки, сайдбара, модального окна или другого узкого контейнера: секция меряет собственную ширину и разложится неправильно. Куда поставить: ___ (Эту строку заполняет пользователь. Если она осталась пустой — спроси, а не угадывай.) ## 5. Сохранить как установлено - иерархию: одна главная возможность крупной панелью и ровно три второстепенные строками — не выравнивай их в сетку одинаковых карточек и не превращай в «три фичи с иконками» - асимметричное тело от 64rem: панель шире списка; шапка остаётся полноширинной над телом - абстрактную геометрию в панели — это иллюстрация, а не интерфейс; не превращай её в окно приложения, дашборд, график с осями или скриншот и не заменяй на <img> - второстепенные возможности как список на волосяных линиях: без карточек, без рамок вокруг каждой, без иллюстративных иконок - тёмную графитовую палитру --vibeui-features-001-* с низкой цветностью — не заменять на токены темы проекта (bg-background, text-foreground, primary, border) и не добавлять свечение, градиентные пятна или крупные тени - плоскость: теней нет, тугой радиус только у панели, всё остальное держится на волосяных линиях - h2 как заголовок секции — блок ставится под hero и не должен приносить второй h1 на страницу; заголовки возможностей остаются h3 - container-type: inline-size на корне блока и @container-правила в его <style> — блок меряет собственную ширину, а не ширину окна; не переписывай их на viewport-медиазапросы - типографику в единицах cqi, clamp и tracking - анимацию разделителей и одноразовый стагер; в этом блоке нет бесконечных анимаций - блок <style> внутри компонента — в нём палитра, container-правила, keyframes и prefers-reduced-motion - семантику: список как <ul>/<li>, диаграмма и декоративные линии aria-hidden, индексы декоративны ## 6. Можно менять - тексты eyebrow, title, titleAccent, description - подпись и href у ссылки action - заголовок и описание главной возможности - заголовки и описания трёх второстепенных возможностей - акцентный цвет — проп accent (и accentForeground) - внешние отступы через className ## 7. Правила - Не добавляй четвёртую второстепенную возможность и не убирай главную: количество зон фиксировано, это часть композиции. - Не добавляй иконки из icon-библиотек: блок собран без зависимостей. - Не добавляй motion-библиотеки: анимации уже на CSS. - Не выноси CSS-переменные в globals.css: блок должен оставаться одним файлом. - Описания второстепенных возможностей держи в одну строку смысла — блок рассчитан на короткий текст, длинные абзацы ломают ритм списка. - Если меняешь accent, проверь контраст акцентного текста на тёмном фоне: нужно не менее 4.5:1. - Сохраняй семантику ссылок. Для внутренней навигации можно использовать компонент роутера проекта, если сохраняются визуальные стили, доступность и поведение href. ## 8. Проверка - рендерится без ошибок в консоли; - выглядит так же, как превью на странице VibeUI, откуда взята ссылка; - если нет — ты изменил что-то из раздела 5, верни как было.
Для разработчика
Команда установки недоступна: переменная окруженияREGISTRY_BASE_URL
Блок самодостаточен: один файл, без зависимостей, всегда тёмный независимо от темы проекта. Цвета живут в локальных переменных --vibeui-features-001-*; акцент меняется пропом accent. Раскладка переключается container-запросами от ширины самого блока. Это секция, а не герой: заголовок — h2, поэтому блок можно ставить под hero, не создавая второй h1 на странице.
Исходник компонента
Тот же файл, который поставит агент. Нужен, если вы предпочитаете скопировать код руками.
import type { CSSProperties } from "react"
type Features001Action = {
label: string
href: string
}
type Features001Feature = {
title: string
description: string
}
export type Features001Props = {
eyebrow?: string
title?: string
titleAccent?: string
description?: string
action?: Features001Action
primaryFeature?: Features001Feature
features?: Features001Feature[]
accent?: string
accentForeground?: string
className?: string
}
// Весь CSS блока живёт здесь, а не в globals.css проекта: палитра, раскладка
// и keyframes. Переменные объявлены в :where() с нулевой специфичностью,
// поэтому пользователь переопределяет их чем угодно.
//
// Тон: плоский графит с низкой цветностью. Ни свечения, ни градиентных пятен,
// ни теней — глубина строится контрастом панели и фона плюс волосяные линии.
//
// container-type делает блок собственным query-контейнером: раскладка и размер
// шрифта считаются от ширины блока, а не от ширины окна. Поэтому асимметричное
// тело видно и в миниатюре каталога, где блок рендерится в 1280px внутри
// узкого окна. Правила раскладки — плоским CSS, а не Tailwind-вариантами, чтобы
// блок не зависел от версии Tailwind в чужом проекте; специфичность (0,2,0)
// выше утилит.
const STYLES = `
:where([data-vibeui-block="features-001"]){
--vibeui-features-001-bg:oklch(0.19 0.006 260);
--vibeui-features-001-panel:oklch(0.23 0.008 260);
--vibeui-features-001-fg:oklch(0.97 0.002 260);
--vibeui-features-001-muted:oklch(0.72 0.008 260);
--vibeui-features-001-border:oklch(1 0 0 / 12%);
--vibeui-features-001-accent:oklch(0.78 0.14 175);
--vibeui-features-001-accent-fg:oklch(0.2 0.03 175);
--vibeui-features-001-accent-soft:color-mix(in oklab, var(--vibeui-features-001-accent) 16%, transparent);
--vibeui-features-001-ring:color-mix(in oklab, var(--vibeui-features-001-accent) 70%, transparent);
--vibeui-features-001-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="features-001"]{container-type:inline-size}
@container (min-width:40rem){
[data-vibeui-block="features-001"] [data-part="frame"]{min-height:620px;padding-left:2.5rem;padding-right:2.5rem}
[data-vibeui-block="features-001"] [data-part="header"]{grid-template-columns:minmax(0,1fr) auto;align-items:end}
[data-vibeui-block="features-001"] [data-part="action"]{justify-self:end}
}
@container (min-width:64rem){
[data-vibeui-block="features-001"] [data-part="frame"]{min-height:680px;padding-top:4rem;padding-bottom:4rem}
[data-vibeui-block="features-001"] [data-part="body"]{grid-template-columns:minmax(0,1.15fr) minmax(0,1fr);gap:3rem}
}
@container (min-width:80rem){
[data-vibeui-block="features-001"] [data-part="frame"]{min-height:720px;padding-left:4rem;padding-right:4rem}
[data-vibeui-block="features-001"] [data-part="inner"]{max-width:1200px;margin-inline:auto}
[data-vibeui-block="features-001"] [data-part="body"]{gap:4rem}
}
@keyframes vibeui-features-001-fade-up{from{opacity:0;transform:translate3d(0,10px,0)}to{opacity:1;transform:none}}
@keyframes vibeui-features-001-rule{from{transform:scaleX(0)}to{transform:scaleX(1)}}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="features-001"] *{animation:none!important;transition:none!important}}
`
const ENTER =
"animate-[vibeui-features-001-fade-up_0.5s_cubic-bezier(0.16,1,0.3,1)_both]"
const RULE =
"origin-left animate-[vibeui-features-001-rule_0.5s_cubic-bezier(0.16,1,0.3,1)_both]"
// Стагер второстепенных возможностей: индекс строки задаёт задержку.
const ROW_DELAYS = [
"[animation-delay:160ms]",
"[animation-delay:220ms]",
"[animation-delay:280ms]",
]
function cx(...classes: (string | false | undefined)[]) {
return classes.filter(Boolean).join(" ")
}
// Абстрактная геометрия: слои и связь между ними. Это иллюстрация, а не
// интерфейс — здесь намеренно нет ни шапки окна, ни кнопок, ни осей графика.
function LayerDiagram() {
return (
<svg
viewBox="0 0 460 160"
preserveAspectRatio="xMinYMid meet"
className="mt-8 h-[clamp(6.5rem,12cqi,9rem)] w-full"
role="presentation"
>
<g
stroke="var(--vibeui-features-001-border)"
fill="var(--vibeui-features-001-accent-soft)"
>
<rect x="180" y="8" width="250" height="40" rx="8" opacity="0.45" />
<rect x="120" y="60" width="250" height="40" rx="8" opacity="0.7" />
<rect x="60" y="112" width="250" height="40" rx="8" />
</g>
<path
d="M164 28 H100 V132"
fill="none"
stroke="var(--vibeui-features-001-accent)"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
vectorEffect="non-scaling-stroke"
/>
<circle
cx="164"
cy="28"
r="3.5"
fill="var(--vibeui-features-001-accent)"
/>
<circle
cx="100"
cy="132"
r="3.5"
fill="var(--vibeui-features-001-accent)"
/>
</svg>
)
}
export function Features001({
eyebrow = "Capabilities",
title = "Everything after the first",
titleAccent = "deploy",
description = "The unglamorous half of shipping — observability, rollbacks, access control — handled before you need it.",
action = { label: "See the full list", href: "#" },
primaryFeature = {
title: "Layered environments",
description:
"Preview, staging and production share one config and drift apart on purpose, not by accident.",
},
features = [
{
title: "Instant rollback",
description: "Any deploy reverts to the previous build in one step.",
},
{
title: "Scoped access",
description: "Per-environment roles, no shared production credentials.",
},
{
title: "Audit trail",
description: "Every change carries an author, a reason and a timestamp.",
},
],
accent,
accentForeground,
className,
}: Features001Props) {
const style = {
...(accent ? { "--vibeui-features-001-accent": accent } : {}),
...(accentForeground
? { "--vibeui-features-001-accent-fg": accentForeground }
: {}),
} as CSSProperties
return (
<section
data-vibeui-block="features-001"
style={style}
className={cx(
"relative isolate overflow-hidden bg-[var(--vibeui-features-001-bg)] font-[family-name:var(--vibeui-features-001-font)] text-[var(--vibeui-features-001-fg)] antialiased",
className,
)}
>
<style href="vibeui-features-001" precedence="medium">
{STYLES}
</style>
{/* Отступы и высота живут на внутреннем слое: container-запросы читают
ширину секции, а сама секция своим контейнером быть не может. */}
<div
data-part="frame"
className="flex min-h-[560px] items-center px-6 py-12"
>
<div data-part="inner" className="w-full">
<header data-part="header" className="grid gap-6">
<div className={ENTER}>
{eyebrow ? (
<p className="flex items-center gap-3 text-[0.75rem] font-medium tracking-[0.09em] text-[var(--vibeui-features-001-accent)] uppercase">
<span
aria-hidden="true"
className={cx(
RULE,
"h-px w-8 bg-[var(--vibeui-features-001-accent)]",
)}
/>
{eyebrow}
</p>
) : null}
<h2 className="mt-5 max-w-[22ch] text-[clamp(1.875rem,3.4cqi,2.75rem)] leading-[1.1] font-semibold tracking-tight text-balance break-words">
{title}
{titleAccent ? (
<>
{" "}
<span className="text-[var(--vibeui-features-001-accent)]">
{titleAccent}
</span>
</>
) : null}
</h2>
{description ? (
<p className="mt-4 max-w-[38rem] text-[clamp(1rem,1.25cqi,1.0625rem)] leading-relaxed text-pretty break-words text-[var(--vibeui-features-001-muted)]">
{description}
</p>
) : null}
</div>
{action ? (
<a
data-part="action"
href={action.href}
className={cx(
ENTER,
"group inline-flex items-center gap-2 self-start text-[0.875rem] font-medium text-[var(--vibeui-features-001-accent)] transition-opacity duration-150 [animation-delay:60ms] hover:opacity-80 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-[var(--vibeui-features-001-ring)]",
)}
>
{action.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}
</header>
<div
aria-hidden="true"
className={cx(
RULE,
"mt-8 h-px w-full bg-[var(--vibeui-features-001-border)] [animation-delay:90ms]",
)}
/>
<div data-part="body" className="mt-8 grid gap-8">
{primaryFeature ? (
<article
data-part="primary"
className={cx(
ENTER,
"rounded-[10px] border border-[var(--vibeui-features-001-border)] bg-[var(--vibeui-features-001-panel)] p-6 [animation-delay:90ms]",
)}
>
<h3 className="text-[1.25rem] font-medium">
{primaryFeature.title}
</h3>
<p className="mt-3 max-w-[34rem] text-[0.9375rem] leading-relaxed text-pretty text-[var(--vibeui-features-001-muted)]">
{primaryFeature.description}
</p>
<LayerDiagram />
</article>
) : null}
{features.length > 0 ? (
<ul
data-part="secondary"
className="flex flex-col justify-center"
>
{features.slice(0, 3).map((feature, index) => (
<li
key={feature.title}
className={cx(
ENTER,
ROW_DELAYS[index],
"flex gap-4 py-5",
index > 0 &&
"border-t border-[var(--vibeui-features-001-border)]",
)}
>
<span
aria-hidden="true"
className="pt-1 text-[0.75rem] text-[var(--vibeui-features-001-accent)] tabular-nums"
>
0{index + 1}
</span>
<div className="min-w-0">
<h3 className="text-[1rem] font-medium">
{feature.title}
</h3>
<p className="mt-1.5 text-[0.875rem] leading-relaxed text-pretty break-words text-[var(--vibeui-features-001-muted)]">
{feature.description}
</p>
</div>
</li>
))}
</ul>
) : null}
</div>
</div>
</div>
</section>
)
}