Прикладываете глубокое исследование ЦА или загружаете тексовый файл с ним в папку проекта
Промт:Ты — senior frontend developer и product-oriented UX/UI designer. Нужно разработать современный лендинг на React + TypeScript + Tailwind CSS + shadcn/ui.
Главная задачаСоздай лендинг из 5 основных экранов + footer:
- Hero-секция с интерактивной 3D-сценой Spline, темным премиальным стилем, spotlight-эффектом и сильным оффером.
- Секция о продукте с карточками продукта на GlowCard / spotlight-card эффекте.
- Секция преимуществ с понятной структурой выгод, иконками lucide-react и краткими объяснениями.
- Секция отзывов с draggable testimonial cards на framer-motion.
- FAQ-секция с частыми вопросами и возражениями.
- Footer с навигацией, CTA, контактами/соцсетями и юридической строкой.
Контент для лендинга нужно взять из файла, который приложен в Codex. В файле находится глубокая аналитика ЦА. Найди этот файл в проекте/контексте, изучи его и используй оттуда:
- описание продукта;
- целевую аудиторию;
- боли и желания ЦА;
- ключевые инсайты;
- главный оффер;
- преимущества;
- возражения;
- формулировки для FAQ;
- возможные отзывы или их реалистичные формулировки на основе анализа;
- tone of voice.
Если файл с аналитикой недоступен, не выдумывай факты. Создай аккуратные placeholder-тексты с пометкой TODO: заменить на данные из файла аналитики ЦА.
Технические требованияПроект должен поддерживать:
- React;
- TypeScript;
- Tailwind CSS;
- shadcn/ui project structure;
- алиас @/;
- компоненты в /components/ui;
- желательно Next.js App Router, если проект на Next.js.
Сначала проверь структуру проекта.
Если проект не настроен под shadcn/ui, Tailwind CSS или TypeScript — добавь инструкции и настрой недостающие части через shadcn CLI.
Если путь для UI-компонентов отличается от /components/ui, всё равно создай /components/ui, потому что предоставленные компоненты используют импорты вида:
import { cn } from "@/lib/utils";
import { Card } from "@/components/ui/card";
Убедись, что есть файл:
/lib/utils.ts
с функцией cn.
Если его нет — создай:
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
Установи зависимости:
npm install @splinetool/runtime @splinetool/react-spline framer-motion lucide-react clsx tailwind-merge
Если shadcn/ui не установлен:
npx shadcn@latest init
Куда внедрять лендингОпредели фреймворк:
Если Next.js App RouterОсновная страница:
/app/page.tsx
Глобальные стили:
/app/globals.css
Если Vite ReactОсновная страница:
/src/App.tsx
Глобальные стили:
/src/index.css
При необходимости адаптируй импорты, но сохрани структуру /components/ui.
UI-стильСделай лендинг в стиле premium dark SaaS / AI product:
- темный фон;
- мягкие radial gradients;
- glassmorphism;
- аккуратные border-свечения;
- большие заголовки;
- плавные hover-эффекты;
- адаптивность под desktop/tablet/mobile;
- mobile-first поведение;
- понятные CTA;
- без перегруза анимациями.
Цветовая палитра:
- base: slate, zinc, neutral;
- accent: indigo, violet, blue, можно немного cyan;
- background: почти черный #020617 или Tailwind slate-950.
Компонент 1: Spline HeroСоздай файл:
/components/ui/splite.tsx
Вставь код:
'use client'
import { Suspense, lazy } from 'react'
const Spline = lazy(() => import('@splinetool/react-spline'))
interface SplineSceneProps {
scene: string
className?: string
}
export function SplineScene({ scene, className }: SplineSceneProps) {
return (
<Suspense
fallback={
<div className="w-full h-full flex items-center justify-center">
<span className="loader"></span>
</div>
}
>
<Spline
scene={scene}
className={className}
/>
</Suspense>
)
}
Создай файл:
/components/ui/spotlight.tsx
Вставь код:
import React from "react";
import { cn } from "@/lib/utils";
type SpotlightProps = {
className?: string;
fill?: string;
};
export const Spotlight = ({ className, fill }: SpotlightProps) => {
return (
<svg
className={cn(
"animate-spotlight pointer-events-none absolute z-[1] h-[169%] w-[138%] lg:w-[84%] opacity-0",
className
)}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 3787 2842"
fill="none"
>
<g filter="url(#filter)">
<ellipse
cx="1924.71"
cy="273.501"
rx="1924.71"
ry="273.501"
transform="matrix(-0.822377 -0.568943 -0.568943 0.822377 3631.88 2291.09)"
fill={fill || "white"}
fillOpacity="0.21"
/>
</g>
<defs>
<filter
id="filter"
x="0.860352"
y="0.838989"
width="3785.16"
height="2840.26"
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feBlend
mode="normal"
in="SourceGraphic"
in2="BackgroundImageFix"
result="shape"
/>
<feGaussianBlur
stdDeviation="151"
result="effect1_foregroundBlur_1065_8"
/>
</filter>
</defs>
</svg>
);
};
Создай или проверь файл:
/components/ui/card.tsx
Если его нет — добавь:
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm",
className,
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3 ref={ref} className={cn("text-2xl font-semibold leading-none tracking-tight", className)} {...props} />
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex items-center p-6 pt-0", className)} {...props} />
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
В tailwind.config.ts добавь animation для hero spotlight, если её нет:
theme: {
extend: {
animation: {
spotlight: "spotlight 2s ease .75s 1 forwards",
},
keyframes: {
spotlight: {
"0%": {
opacity: "0",
transform: "translate(-72%, -62%) scale(0.5)",
},
"100%": {
opacity: "1",
transform: "translate(-50%, -40%) scale(1)",
},
},
},
},
}
В Hero используй:
<SplineScene
scene="https://prod.spline.design/kZDDjO5HuC9GJUM2/scene.splinecode"
className="w-full h-full"
/>
Hero должен содержать:
- бейдж над заголовком;
- H1 на основе главного инсайта из файла аналитики ЦА;
- подзаголовок с ценностью продукта;
- 2 CTA-кнопки;
- 3 коротких trust/value пункта;
- справа 3D Spline;
- на mobile 3D-блок должен уходить ниже текста или становиться компактнее.
Компонент 2: GlowCard для секции продуктаСоздай файл:
/components/ui/spotlight-card.tsx
Вставь код:
import React, { useEffect, useRef, ReactNode } from 'react';
interface GlowCardProps {
children: ReactNode;
className?: string;
glowColor?: 'blue' | 'purple' | 'green' | 'red' | 'orange';
size?: 'sm' | 'md' | 'lg';
width?: string | number;
height?: string | number;
customSize?: boolean;
}
const glowColorMap = {
blue: { base: 220, spread: 200 },
purple: { base: 280, spread: 300 },
green: { base: 120, spread: 200 },
red: { base: 0, spread: 200 },
orange: { base: 30, spread: 200 }
};
const sizeMap = {
sm: 'w-48 h-64',
md: 'w-64 h-80',
lg: 'w-80 h-96'
};
const GlowCard: React.FC<GlowCardProps> = ({
children,
className = '',
glowColor = 'blue',
size = 'md',
width,
height,
customSize = false
}) => {
const cardRef = useRef<HTMLDivElement>(null);
const innerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const syncPointer = (e: PointerEvent) => {
const { clientX: x, clientY: y } = e;
if (cardRef.current) {
cardRef.current.style.setProperty('--x', x.toFixed(2));
cardRef.current.style.setProperty('--xp', (x / window.innerWidth).toFixed(2));
cardRef.current.style.setProperty('--y', y.toFixed(2));
cardRef.current.style.setProperty('--yp', (y / window.innerHeight).toFixed(2));
}
};
document.addEventListener('pointermove', syncPointer);
return () => document.removeEventListener('pointermove', syncPointer);
}, []);
const { base, spread } = glowColorMap[glowColor];
const getSizeClasses = () => {
if (customSize) return '';
return sizeMap[size];
};
const getInlineStyles = () => {
const baseStyles: React.CSSProperties & Record<string, string | number> = {
'--base': base,
'--spread': spread,
'--radius': '14',
'--border': '3',
'--backdrop': 'hsl(0 0% 60% / 0.12)',
'--backup-border': 'var(--backdrop)',
'--size': '200',
'--outer': '1',
'--border-size': 'calc(var(--border, 2) * 1px)',
'--spotlight-size': 'calc(var(--size, 150) * 1px)',
'--hue': 'calc(var(--base) + (var(--xp, 0) * var(--spread, 0)))',
backgroundImage: `radial-gradient(
var(--spotlight-size) var(--spotlight-size) at
calc(var(--x, 0) * 1px)
calc(var(--y, 0) * 1px),
hsl(var(--hue, 210) calc(var(--saturation, 100) * 1%) calc(var(--lightness, 70) * 1%) / var(--bg-spot-opacity, 0.1)), transparent
)`,
backgroundColor: 'var(--backdrop, transparent)',
backgroundSize: 'calc(100% + (2 * var(--border-size))) calc(100% + (2 * var(--border-size)))',
backgroundPosition: '50% 50%',
backgroundAttachment: 'fixed',
border: 'var(--border-size) solid var(--backup-border)',
position: 'relative',
touchAction: 'none',
};
if (width !== undefined) {
baseStyles.width = typeof width === 'number' ? `${width}px` : width;
}
if (height !== undefined) {
baseStyles.height = typeof height === 'number' ? `${height}px` : height;
}
return baseStyles;
};
const beforeAfterStyles = `
[data-glow]::before,
[data-glow]::after {
pointer-events: none;
content: "";
position: absolute;
inset: calc(var(--border-size) * -1);
border: var(--border-size) solid transparent;
border-radius: calc(var(--radius) * 1px);
background-attachment: fixed;
background-size: calc(100% + (2 * var(--border-size))) calc(100% + (2 * var(--border-size)));
background-repeat: no-repeat;
background-position: 50% 50%;
mask: linear-gradient(transparent, transparent), linear-gradient(white, white);
mask-clip: padding-box, border-box;
mask-composite: intersect;
}
[data-glow]::before {
background-image: radial-gradient(
calc(var(--spotlight-size) * 0.75) calc(var(--spotlight-size) * 0.75) at
calc(var(--x, 0) * 1px)
calc(var(--y, 0) * 1px),
hsl(var(--hue, 210) calc(var(--saturation, 100) * 1%) calc(var(--lightness, 50) * 1%) / var(--border-spot-opacity, 1)), transparent 100%
);
filter: brightness(2);
}
[data-glow]::after {
background-image: radial-gradient(
calc(var(--spotlight-size) * 0.5) calc(var(--spotlight-size) * 0.5) at
calc(var(--x, 0) * 1px)
calc(var(--y, 0) * 1px),
hsl(0 100% 100% / var(--border-light-opacity, 1)), transparent 100%
);
}
[data-glow] [data-glow] {
position: absolute;
inset: 0;
will-change: filter;
opacity: var(--outer, 1);
border-radius: calc(var(--radius) * 1px);
border-width: calc(var(--border-size) * 20);
filter: blur(calc(var(--border-size) * 10));
background: none;
pointer-events: none;
border: none;
}
[data-glow] > [data-glow]::before {
inset: -10px;
border-width: 10px;
}
`;
return (
<>
<style dangerouslySetInnerHTML={{ __html: beforeAfterStyles }} />
<div
ref={cardRef}
data-glow
style={getInlineStyles()}
className={`
${getSizeClasses()}
${!customSize ? 'aspect-[3/4]' : ''}
rounded-2xl
relative
grid
grid-rows-[1fr_auto]
shadow-[0_1rem_2rem_-1rem_black]
p-4
gap-4
backdrop-blur-[5px]
${className}
`}
>
<div ref={innerRef} data-glow />
{children}
</div>
</>
);
};
export { GlowCard };
Используй GlowCard в секции продукта. Сделай 3–4 карточки, например:
- ключевая функция продукта;
- главный результат для клиента;
- как продукт работает;
- почему это лучше альтернатив.
Но конкретные тексты возьми из приложенного файла аналитики ЦА.
Компонент 3: TestimonialsСоздай файл:
/components/ui/testimonial-cards.tsx
Вставь и типизируй компонент:
"use client";
import * as React from "react";
import { motion } from "framer-motion";
type TestimonialPosition = "front" | "middle" | "back";
type TestimonialCardProps = {
handleShuffle: () => void;
testimonial: string;
position: TestimonialPosition;
id: number;
author: string;
};
export function TestimonialCard({
handleShuffle,
testimonial,
position,
id,
author,
}: TestimonialCardProps) {
const dragRef = React.useRef(0);
const isFront = position === "front";
return (
<motion.div
style={{
zIndex: position === "front" ? "2" : position === "middle" ? "1" : "0",
}}
animate={{
rotate: position === "front" ? "-6deg" : position === "middle" ? "0deg" : "6deg",
x: position === "front" ? "0%" : position === "middle" ? "33%" : "66%",
}}
drag={true}
dragElastic={0.35}
dragListener={isFront}
dragConstraints={{
top: 0,
left: 0,
right: 0,
bottom: 0,
}}
onDragStart={(e) => {
dragRef.current = e.clientX;
}}
onDragEnd={(e) => {
if (dragRef.current - e.clientX > 150) {
handleShuffle();
}
dragRef.current = 0;
}}
transition={{ duration: 0.35 }}
className={`absolute left-0 top-0 grid h-[450px] w-[350px] select-none place-content-center space-y-6 rounded-2xl border-2 border-slate-700 bg-slate-800/20 p-6 shadow-xl backdrop-blur-md ${
isFront ? "cursor-grab active:cursor-grabbing" : ""
}`}
>
<img
src={`https://i.pravatar.cc/128?img=${id}`}
alt={`Avatar of ${author}`}
className="pointer-events-none mx-auto h-32 w-32 rounded-full border-2 border-slate-700 bg-slate-200 object-cover"
/>
<span className="text-center text-lg italic text-slate-400">"{testimonial}"</span>
<span className="text-center text-sm font-medium text-indigo-400">{author}</span>
</motion.div>
);
}
В секции отзывов создай wrapper-компонент, например:
/components/landing/testimonials-section.tsx
Он должен:
- хранить массив отзывов;
- использовать useState;
- реализовать shuffle;
- брать тексты отзывов из аналитики ЦА или формулировать реалистично на основе болей/желаний из файла;
- не использовать реальные бренды, если они не указаны в файле;
- если данных нет — использовать нейтральные placeholder-имена.
Структура компонентов лендингаСоздай папку:
/components/landing
И разнеси секции:
/components/landing/hero-section.tsx
/components/landing/product-section.tsx
/components/landing/benefits-section.tsx
/components/landing/testimonials-section.tsx
/components/landing/faq-section.tsx
/components/landing/footer.tsx
На главной странице импортируй их и собери:
export default function Page() {
return (
<main className="min-h-screen bg-slate-950 text-white">
<HeroSection />
<ProductSection />
<BenefitsSection />
<TestimonialsSection />
<FAQSection />
<Footer />
</main>
);
}
Hero-section требованияHero должен быть сильным и conversion-focused.
Структура:
- navigation/header сверху;
- logo placeholder или название продукта из файла;
- nav links: Product, Benefits, Testimonials, FAQ;
- primary CTA;
- badge;
- H1;
- subheadline;
- CTA group;
- trust row;
- Spline 3D справа;
- spotlight background.
Используй:
import { SplineScene } from "@/components/ui/splite";
import { Card } from "@/components/ui/card";
import { Spotlight } from "@/components/ui/spotlight";
Hero должен визуально напоминать пример с SplineSceneBasic, но быть полноценной landing hero-секцией.
Product-section требованияСекция о продукте:
- заголовок;
- подзаголовок;
- 3–4 GlowCard карточки;
- внутри карточек использовать lucide-react icons;
- тексты брать из файла аналитики ЦА;
- карточки должны быть responsive:
- desktop: grid 3 или 4 колонки;
- tablet: 2 колонки;
- mobile: 1 колонка.
Используй:
import { GlowCard } from "@/components/ui/spotlight-card";
Benefits-section требованияСекция преимуществ:
- 4–6 преимуществ;
- каждое преимущество должно быть связано с болью или желанием ЦА;
- использовать иконки из lucide-react;
- оформить как clean cards / feature list;
- добавить короткий CTA в конце секции.
Не делай абстрактные преимущества типа “быстро, удобно, качественно”. Формулируй конкретно, на основе аналитики.
Testimonials-section требованияСекция отзывов:
- слева текстовый блок:
- заголовок;
- короткое объяснение;
- микроинструкция “Потяните карточку влево”;
- справа draggable cards;
- на mobile карточки должны быть ниже текста;
- отзывы должны отражать реальные боли/результаты из аналитики ЦА;
- не использовать названия известных компаний, если они не указаны в файле.
FAQ-section требованияFAQ должен закрывать возражения ЦА.
Сделай 6–8 вопросов.
Темы FAQ:
- для кого продукт;
- какой результат можно ожидать;
- что нужно для старта;
- сколько времени занимает внедрение/использование;
- чем отличается от альтернатив;
- есть ли поддержка;
- что если не подойдет;
- вопрос, связанный с главным страхом ЦА из аналитики.
Оформи FAQ как accordion. Если shadcn accordion установлен — используй его. Если нет — создай простой accessible accordion на React state.
Footer требованияFooter должен содержать:
- название продукта;
- короткий one-liner;
- ссылки на секции;
- CTA;
- контакты или placeholder;
- copyright;
- “Privacy Policy” / “Terms” placeholders.
АдаптивностьОбязательно проверь:
- 320px mobile;
- 768px tablet;
- 1024px desktop;
- 1440px desktop.
Требования:
- не должно быть горизонтального скролла;
- Spline не должен ломать mobile layout;
- testimonials cards не должны вылезать за экран;
- карточки должны перестраиваться в одну колонку на mobile;
- текст должен оставаться читаемым.
Качество кодаСделай:
- чистые, переиспользуемые компоненты;
- строгую TypeScript-типизацию;
- отсутствие any, если можно типизировать;
- корректные импорты;
- отсутствие неиспользуемых импортов;
- семантичную HTML-структуру;
- accessible labels для кнопок/ссылок;
- aria для FAQ accordion, если делаешь вручную.
КонтентПеред написанием финальных текстов:
- Найди приложенный файл с глубокой аналитикой ЦА.
- Извлеки из него:
- название продукта;
- описание продукта;
- ЦА;
- боли;
- желания;
- возражения;
- обещание результата;
- tone of voice.
- На основе этого напиши тексты для всех секций.
Если в аналитике нет названия продукта — используй временное название:
TODO_PRODUCT_NAME
Если в аналитике нет конкретного оффера — используй:
TODO: заменить на оффер из аналитики ЦА
Не добавляй неподтвержденные факты, цифры, гарантии, кейсы или бренды.
Дополнительные визуальные деталиДобавь:
- background gradients;
- subtle noise or grid overlay через Tailwind/CSS;
- hover states;
- smooth scroll;
- section anchors;
- CTA buttons;
- consistent max-width container;
- хорошие отступы между секциями.
Можно использовать Unsplash stock images только если нужны фоновые изображения, но не делай изображения главным элементом, потому что основной акцент — Spline, GlowCards и animated testimonials.
Финальный результатВ конце работы:
- Перечисли измененные/созданные файлы.
- Укажи установленные зависимости.
- Укажи, откуда был взят контент.
- Если какие-то данные из файла аналитики не найдены — явно перечисли TODO.
- Убедись, что проект запускается командой:
npm run dev
- Проверь TypeScript и исправь ошибки.
- Проверь, что лендинг визуально состоит из 5 основных экранов + footer.