feat: split big file and update agents.md

This commit is contained in:
2026-06-18 23:16:57 +03:00
parent 39cfc34693
commit 00ab1ad4af
19 changed files with 327 additions and 234 deletions

View File

@@ -5,7 +5,56 @@ Northyard Residences — premium real-estate шаблон девелопера:
## Project Specifics ## Project Specifics
- Квартиры, локация, amenities и этапы покупки описаны в `src/entities/site-content.ts`. - Квартиры, локация, amenities и этапы покупки описаны в `src/entities/site-content.ts`.
- `src/app` — только маршруты; страницы собираются в `src/widgets/template-ui.tsx`. - `src/app` — только маршруты; композиция каждой страницы живёт в отдельном widget (`src/widgets/<page>-page.tsx`). См. File Map.
- Mock-калькуляторы держи в `src/features/*/ui`; не подключай реальные банки, CRM, карты или формы отправки без запроса. - Mock-калькуляторы держи в `src/features/*/ui`; не подключай реальные банки, CRM, карты или формы отправки без запроса.
- Не превращай шаблон в общий лендинг: каждая страница должна отвечать на конкретный вопрос покупателя недвижимости. - Не превращай шаблон в общий лендинг: каждая страница должна отвечать на конкретный вопрос покупателя недвижимости.
- Проверка после правок: `pnpm lint` и `pnpm build`. - Проверка после правок: `pnpm lint` и `pnpm build`.
## Design System
Источник токенов — `src/app/globals.css` (`@theme inline` + `:root`/`.dark`). Шрифт — **Noto Sans** (`--font-northyard`), один гарнитур на весь UI, с включёнными OpenType-фичами `ss01`/`cv01` на `body`. Работай через семантические классы Tailwind (`bg-primary`, `text-foreground`, `border-border`, `text-muted-foreground`), никогда не хардкодь hex/oklch.
Личность: **тёплый «каменный» premium real-estate** — палитра построена на тёплых бежево-песочных оттенках (hue ~8390), почти без насыщенности. Фон — тёплый камень/песок, primary — глубокий тёмно-коричнево-серый «графит» (НЕ синий и НЕ чёрный), secondary и accent — приглушённый шалфейно-зелёный (hue ~120128). Тон спокойный, дорогой, архитектурный: воздух, светлые карточки, тонкие линии.
| Роль | Light | Характер |
|---|---|---|
| `background` | тёплый светлый камень/песок | основной фон страниц |
| `foreground` | тёмный тёплый графит | текст |
| `primary` | глубокий графит (тёплый) | бренд: лого-плашка, CTA-кнопки, числа, акцент линий планировок |
| `secondary` | приглушённый светлый шалфей | бейджи, мягкие плашки (`bg-secondary text-foreground`) |
| `accent` / `ring` | шалфейно-зелёный | hover/focus-кольца, точечные акценты |
| `muted` / `muted-foreground` | светлый песок / тёплый серо-бежевый | вторичный текст, подписи, hover-фон строк |
| `card` | почти белый тёплый | карточки, таблицы, sticky-панели на каменном фоне |
| `border` | тёплый бежевый | тонкие границы (1px) повсюду |
Узнаваемые приёмы (держи их — это «лицо» шаблона):
- **Мягкие углы:** `--radius` = 0.5rem; карточки и панели `rounded-lg`, мелкие плашки `rounded-md`, лого-иконка `rounded-full`.
- **Тонкие тёплые границы:** `border` (1px) на каждой карточке/строке; структура держится на линиях, а не на тенях.
- **Bento-сетки:** крупные `grid` с асимметричными колонками (`lg:grid-cols-[1.1fr_0.9fr]`, `[1fr_380px]`, sticky правая колонка `lg:sticky lg:top-24`).
- **Каменные фоны секций:** утилита `.northyard-stone` (тёплый radial+linear градиент песок/шалфей) для hero и contacts.
- **Планировочная сетка:** утилита `.floor-grid` (32px графитовая сетка-миллиметровка) для floor plan и карты локации; зоны планировки — `border-2 border-primary/*`.
- **Типографика:** крупные `font-semibold` заголовки (`text-5xl``text-7xl`, плотный `leading-[0.98]`), мягкие eyebrow-бейджи `variant="outline"`.
Do / Don't:
- **Do:** держи тёплую каменную палитру и шалфейный акцент; используй bento-сетки, light-карточки, sticky-панель заявки/ипотеки; продавай ежедневный сценарий покупателя (планировка, маршруты, сервис).
- **Don't:** не вводи холодный синий/чёрный primary, насыщенные или неоновые цвета, тяжёлые тени, острые `rounded-none` углы или generic SaaS-hero с градиентом — это ломает дорогой архитектурный тон.
## File Map
| Route | Widget |
|---|---|
| `/` | `src/widgets/home-page.tsx` (`HomePage`) |
| `/apartments` | `src/widgets/apartments-page.tsx` (`ApartmentsPage`) |
| `/apartments/a-1204` | `src/widgets/apartment-detail-page.tsx` (`ApartmentDetailPage`) |
| `/location` | `src/widgets/location-page.tsx` (`LocationPage`) |
| `/amenities` | `src/widgets/amenities-page.tsx` (`AmenitiesPage`) |
| `/purchase` | `src/widgets/purchase-page.tsx` (`PurchasePage`) |
| `/contacts` | `src/widgets/contacts-page.tsx` (`ContactsPage`) |
Переиспользуемые блоки:
- `src/widgets/site-shell.tsx``SiteShell` (header + nav, обёртка всех страниц).
- `src/widgets/apartment-rows.tsx``ApartmentRows` (Home, Apartments; рендерит `apartments`).
- `src/shared/ui/section-header.tsx``SectionHeader` (заголовок секций: Home, Apartments, Location, Amenities, Purchase).
- `src/features/mortgage-calculator/ui/mortgage-calculator.tsx``MortgageCalculator` (mock-калькулятор: Home, Purchase).
Одноразовые блоки колоцированы со своей страницей: `FloorPlanCard` (план квартиры) живёт внутри `src/widgets/apartment-detail-page.tsx`.

View File

@@ -1,4 +1,4 @@
import { AmenitiesPage } from "@/widgets/template-ui"; import { AmenitiesPage } from "@/widgets/amenities-page";
export default function Page() { export default function Page() {
return <AmenitiesPage />; return <AmenitiesPage />;

View File

@@ -1,4 +1,4 @@
import { ApartmentDetailPage } from "@/widgets/template-ui"; import { ApartmentDetailPage } from "@/widgets/apartment-detail-page";
export default function Page() { export default function Page() {
return <ApartmentDetailPage />; return <ApartmentDetailPage />;

View File

@@ -1,4 +1,4 @@
import { ApartmentsPage } from "@/widgets/template-ui"; import { ApartmentsPage } from "@/widgets/apartments-page";
export default function Page() { export default function Page() {
return <ApartmentsPage />; return <ApartmentsPage />;

View File

@@ -1,4 +1,4 @@
import { ContactsPage } from "@/widgets/template-ui"; import { ContactsPage } from "@/widgets/contacts-page";
export default function Page() { export default function Page() {
return <ContactsPage />; return <ContactsPage />;

View File

@@ -1,4 +1,4 @@
import { LocationPage } from "@/widgets/template-ui"; import { LocationPage } from "@/widgets/location-page";
export default function Page() { export default function Page() {
return <LocationPage />; return <LocationPage />;

View File

@@ -1,4 +1,4 @@
import { HomePage } from "@/widgets/template-ui"; import { HomePage } from "@/widgets/home-page";
export default function Page() { export default function Page() {
return <HomePage />; return <HomePage />;

View File

@@ -1,4 +1,4 @@
import { PurchasePage } from "@/widgets/template-ui"; import { PurchasePage } from "@/widgets/purchase-page";
export default function Page() { export default function Page() {
return <PurchasePage />; return <PurchasePage />;

View File

@@ -0,0 +1,13 @@
import { Badge } from "@/shared/ui/badge";
export function SectionHeader({ label, title, text }: { label: string; title: string; text: string }) {
return (
<div className="mx-auto mb-8 max-w-7xl px-4 md:px-6">
<Badge variant="outline" className="mb-4 bg-card">{label}</Badge>
<div className="grid gap-4 md:grid-cols-[0.9fr_0.8fr] md:items-end">
<h1 className="text-4xl font-semibold tracking-normal md:text-6xl">{title}</h1>
<p className="text-lg leading-8 text-muted-foreground">{text}</p>
</div>
</div>
);
}

View File

@@ -0,0 +1,18 @@
import { TreesIcon } from "lucide-react";
import { amenities } from "@/entities/site-content";
import { SectionHeader } from "@/shared/ui/section-header";
import { SiteShell } from "@/widgets/site-shell";
export function AmenitiesPage() {
return (
<SiteShell>
<section className="py-12 md:py-18">
<SectionHeader label="Сервис" title="Инфраструктура не для буклета, а для будней" text="Блоки отвечают на бизнес-вопрос покупателя: что будет удобно каждый день после сделки." />
<div className="mx-auto grid max-w-7xl gap-5 px-4 md:grid-cols-2 md:px-6">
{amenities.map((item, index) => <article key={item.title} className="min-h-72 rounded-lg border bg-card p-6"><TreesIcon className="mb-12 size-8 text-primary" /><div className="text-sm text-muted-foreground">0{index + 1}</div><h2 className="mt-3 text-3xl font-semibold">{item.title}</h2><p className="mt-4 leading-7 text-muted-foreground">{item.text}</p></article>)}
</div>
</section>
</SiteShell>
);
}

View File

@@ -0,0 +1,49 @@
import Link from "next/link";
import { CheckIcon } from "lucide-react";
import { apartments } from "@/entities/site-content";
import { Badge } from "@/shared/ui/badge";
import { Button } from "@/shared/ui/button";
import { SiteShell } from "@/widgets/site-shell";
function FloorPlanCard() {
return (
<div className="floor-grid relative min-h-[420px] overflow-hidden rounded-lg border bg-card p-5">
<div className="absolute inset-6 grid grid-cols-6 grid-rows-5 gap-2">
<div className="col-span-4 row-span-3 rounded-md border-2 border-primary/70 bg-background/80 p-3 text-sm font-medium">Гостиная 32.4 м²</div>
<div className="col-span-2 row-span-2 rounded-md border-2 border-primary/50 bg-secondary/45 p-3 text-sm">Кухня</div>
<div className="col-span-2 row-span-2 rounded-md border-2 border-primary/50 bg-background/80 p-3 text-sm">Мастер</div>
<div className="col-span-2 row-span-2 rounded-md border-2 border-primary/50 bg-background/80 p-3 text-sm">Детская</div>
<div className="col-span-1 rounded-md border-2 border-primary/40 bg-muted p-2 text-xs">С/у</div>
<div className="col-span-1 rounded-md border-2 border-primary/40 bg-muted p-2 text-xs">Пост.</div>
</div>
<div className="absolute bottom-5 left-5 rounded-full bg-primary px-4 py-2 text-sm font-medium text-primary-foreground">A-1204 / 96.4 м²</div>
</div>
);
}
export function ApartmentDetailPage() {
const apartment = apartments[0];
return (
<SiteShell>
<section className="px-4 py-10 md:px-6 md:py-16">
<div className="mx-auto grid max-w-7xl gap-6 lg:grid-cols-[1fr_420px]">
<div>
<Badge className="mb-5">{apartment.status}</Badge>
<h1 className="text-5xl font-semibold md:text-7xl">Квартира {apartment.id}</h1>
<p className="mt-4 max-w-2xl text-lg leading-8 text-muted-foreground">{apartment.rooms}, {apartment.area}, {apartment.plan}. Вид на {apartment.view}, этаж {apartment.floor}.</p>
<div className="mt-8"><FloorPlanCard /></div>
</div>
<aside className="rounded-lg border bg-card p-5 shadow-sm lg:sticky lg:top-24 lg:self-start">
<div className="text-sm text-muted-foreground">Стоимость</div>
<div className="mt-1 text-4xl font-semibold">{apartment.price}</div>
<div className="mt-6 grid gap-3 text-sm">
{['Отделка white box', 'Окна в пол', '2 санузла', 'Семейная кухня-гостиная'].map((item) => <div key={item} className="flex items-center gap-2"><CheckIcon className="size-4 text-primary" />{item}</div>)}
</div>
<Button asChild className="mt-6 w-full"><Link href="/contacts">Забронировать просмотр</Link></Button>
</aside>
</div>
</section>
</SiteShell>
);
}

View File

@@ -0,0 +1,21 @@
import Link from "next/link";
import { apartments } from "@/entities/site-content";
import { Badge } from "@/shared/ui/badge";
export function ApartmentRows() {
return (
<div className="overflow-hidden rounded-lg border bg-card">
{apartments.map((apartment) => (
<Link key={apartment.id} href={apartment.id === "A-1204" ? "/apartments/a-1204" : "/apartments"} className="grid gap-3 border-b p-4 transition last:border-b-0 hover:bg-muted/60 md:grid-cols-[120px_1fr_120px_120px_160px_120px] md:items-center">
<span className="font-semibold">{apartment.id}</span>
<span>{apartment.rooms} / {apartment.area}</span>
<span className="text-muted-foreground">этаж {apartment.floor}</span>
<span className="text-muted-foreground">{apartment.view}</span>
<span className="font-semibold">{apartment.price}</span>
<Badge variant={apartment.status === "доступна" ? "default" : "secondary"}>{apartment.status}</Badge>
</Link>
))}
</div>
);
}

View File

@@ -0,0 +1,23 @@
import { HomeIcon } from "lucide-react";
import { SectionHeader } from "@/shared/ui/section-header";
import { ApartmentRows } from "@/widgets/apartment-rows";
import { SiteShell } from "@/widgets/site-shell";
export function ApartmentsPage() {
return (
<SiteShell>
<section className="py-12 md:py-18">
<SectionHeader label="Каталог" title="Квартиры по корпусам, видам и сценариям" text="Каталог показывает не только площадь и цену, но и планировочную пользу: хранение, приватность, рабочие зоны и ориентацию окон." />
<div className="mx-auto grid max-w-7xl gap-6 px-4 md:px-6 lg:grid-cols-[1fr_380px]">
<ApartmentRows />
<div className="rounded-lg border bg-card p-5">
<HomeIcon className="mb-5 size-7" />
<h2 className="text-2xl font-semibold">Фильтры для будущего CMS</h2>
<p className="mt-3 leading-7 text-muted-foreground">Комнаты, корпус, вид, отделка, статус брони и диапазон цены уже отражены в интерфейсе как production-ready блоки.</p>
</div>
</div>
</section>
</SiteShell>
);
}

View File

@@ -0,0 +1,25 @@
import { site } from "@/entities/site-content";
import { Badge } from "@/shared/ui/badge";
import { Button } from "@/shared/ui/button";
import { SiteShell } from "@/widgets/site-shell";
export function ContactsPage() {
return (
<SiteShell>
<section className="northyard-stone px-4 py-12 md:px-6 md:py-18">
<div className="mx-auto grid max-w-7xl gap-6 lg:grid-cols-[1fr_420px]">
<div className="rounded-lg border bg-card/90 p-6 md:p-10">
<Badge className="mb-6">Отдел продаж</Badge>
<h1 className="text-5xl font-semibold md:text-7xl">Запишитесь на приватный просмотр</h1>
<p className="mt-6 text-lg leading-8 text-muted-foreground">Менеджер подготовит подборку квартир, планировки и финансовый сценарий до визита.</p>
</div>
<div className="rounded-lg border bg-card p-6 shadow-sm">
<div className="text-sm text-muted-foreground">Телефон</div><div className="text-2xl font-semibold">{site.phone}</div>
<div className="mt-6 text-sm text-muted-foreground">Email</div><div className="text-2xl font-semibold">{site.email}</div>
<Button className="mt-8 w-full">Назначить просмотр</Button>
</div>
</div>
</section>
</SiteShell>
);
}

48
src/widgets/home-page.tsx Normal file
View File

@@ -0,0 +1,48 @@
import Image from "next/image";
import Link from "next/link";
import { ArrowRightIcon } from "lucide-react";
import { site } from "@/entities/site-content";
import { MortgageCalculator } from "@/features/mortgage-calculator/ui/mortgage-calculator";
import { Badge } from "@/shared/ui/badge";
import { Button } from "@/shared/ui/button";
import { SectionHeader } from "@/shared/ui/section-header";
import { ApartmentRows } from "@/widgets/apartment-rows";
import { SiteShell } from "@/widgets/site-shell";
export function HomePage() {
return (
<SiteShell>
<section className="northyard-stone px-4 py-8 md:px-6 md:py-12">
<div className="mx-auto grid max-w-7xl gap-5 lg:grid-cols-[1.1fr_0.9fr]">
<div className="rounded-lg border bg-card/80 p-6 shadow-sm md:p-10">
<Badge className="mb-8 bg-secondary text-foreground">Ключи с IV квартала 2026</Badge>
<h1 className="max-w-4xl text-5xl font-semibold leading-[0.98] md:text-7xl">Квартал с приватным двором и городом в пешей доступности</h1>
<p className="mt-6 max-w-2xl text-lg leading-8 text-muted-foreground">{site.name} соединяет клубный сервис, спокойные планировки и инфраструктуру района без ощущения закрытого острова.</p>
<div className="mt-8 flex flex-wrap gap-3">
<Button asChild size="lg"><Link href="/apartments">Выбрать квартиру <ArrowRightIcon className="size-4" /></Link></Button>
<Button asChild variant="outline" size="lg"><Link href="/location">Смотреть локацию</Link></Button>
</div>
</div>
<div className="grid gap-5 md:grid-cols-2 lg:grid-cols-1">
<div className="relative min-h-[320px] overflow-hidden rounded-lg border">
<Image src="https://images.unsplash.com/photo-1600607688969-a5bfcd646154?auto=format&fit=crop&w=1400&q=82" alt="Лобби жилого квартала" fill priority className="object-cover" sizes="(min-width: 1024px) 40vw, 100vw" />
</div>
<div className="grid grid-cols-3 gap-3">
{["214 квартир", "2 корпуса", "0 машин во дворе"].map((item) => <div key={item} className="rounded-lg border bg-card p-4 text-sm font-semibold shadow-sm">{item}</div>)}
</div>
</div>
</div>
</section>
<section className="px-4 py-12 md:px-6 md:py-18">
<div className="mx-auto grid max-w-7xl gap-5 lg:grid-cols-[1fr_380px]">
<div>
<SectionHeader label="Квартиры" title="Планировки, которые держат сценарий семьи" text="От студий для инвестиций до угловых квартир с мастер-блоком, постирочной и окнами на тихий двор." />
<div className="px-4 md:px-6"><ApartmentRows /></div>
</div>
<div className="lg:sticky lg:top-24 lg:self-start"><MortgageCalculator /></div>
</div>
</section>
</SiteShell>
);
}

View File

@@ -0,0 +1,22 @@
import { MapPinIcon } from "lucide-react";
import { locationPoints, site } from "@/entities/site-content";
import { SectionHeader } from "@/shared/ui/section-header";
import { SiteShell } from "@/widgets/site-shell";
export function LocationPage() {
return (
<SiteShell>
<section className="py-12 md:py-18">
<SectionHeader label="Локация" title="Город рядом, двор остается тихим" text="Страница продает не абстрактный адрес, а ежедневные маршруты: школа, парк, офис, сервисы и вечерние прогулки." />
<div className="mx-auto grid max-w-7xl gap-5 px-4 md:px-6 lg:grid-cols-[1fr_1fr]">
<div className="floor-grid min-h-[520px] rounded-lg border bg-card p-6">
<MapPinIcon className="mb-20 size-10 text-primary" />
<div className="rounded-lg bg-primary p-5 text-primary-foreground shadow-lg md:w-72">{site.address}<br />главный вход с пешеходной улицы</div>
</div>
<div className="grid gap-4">{locationPoints.map((point) => <article key={point.title} className="rounded-lg border bg-card p-5"><div className="text-3xl font-semibold">{point.time}</div><h2 className="mt-3 text-xl font-semibold">{point.title}</h2><p className="mt-2 text-muted-foreground">{point.text}</p></article>)}</div>
</div>
</section>
</SiteShell>
);
}

View File

@@ -0,0 +1,25 @@
import { construction, purchaseSteps } from "@/entities/site-content";
import { MortgageCalculator } from "@/features/mortgage-calculator/ui/mortgage-calculator";
import { Progress } from "@/shared/ui/progress";
import { SectionHeader } from "@/shared/ui/section-header";
import { SiteShell } from "@/widgets/site-shell";
export function PurchasePage() {
return (
<SiteShell>
<section className="py-12 md:py-18">
<SectionHeader label="Покупка" title="Понятный путь от выбора до ключей" text="Шаблон закрывает блоки, которые нужны девелоперу: бронь, ипотека, документы, ход строительства и контакты отдела продаж." />
<div className="mx-auto grid max-w-7xl gap-6 px-4 md:px-6 lg:grid-cols-[0.9fr_1.1fr]">
<MortgageCalculator />
<div className="space-y-4">
{purchaseSteps.map((step, index) => <div key={step} className="rounded-lg border bg-card p-5"><span className="text-sm text-muted-foreground">0{index + 1}</span><h2 className="mt-2 text-2xl font-semibold">{step}</h2></div>)}
<div className="rounded-lg border bg-card p-5">
<h2 className="mb-4 text-2xl font-semibold">Ход строительства</h2>
<div className="grid gap-4">{construction.map((item) => <div key={item.label}><div className="mb-2 flex justify-between text-sm"><span>{item.label}</span><span>{item.value}%</span></div><Progress value={item.value} /></div>)}</div>
</div>
</div>
</div>
</section>
</SiteShell>
);
}

View File

@@ -0,0 +1,26 @@
import type { ReactNode } from "react";
import Link from "next/link";
import { Building2Icon } from "lucide-react";
import { navItems, site } from "@/entities/site-content";
import { Button } from "@/shared/ui/button";
export function SiteShell({ children }: { children: ReactNode }) {
return (
<main className="min-h-screen bg-background text-foreground">
<header className="sticky top-0 z-40 border-b bg-background/90 backdrop-blur">
<div className="mx-auto flex max-w-7xl items-center justify-between px-4 py-4 md:px-6">
<Link href="/" className="flex items-center gap-2 font-semibold">
<span className="rounded-full bg-primary p-2 text-primary-foreground"><Building2Icon className="size-4" /></span>
{site.name}
</Link>
<nav className="hidden items-center gap-6 text-sm text-muted-foreground md:flex">
{navItems.map((item) => <Link key={item.href} href={item.href} className="hover:text-foreground">{item.label}</Link>)}
</nav>
<Button asChild size="sm"><Link href="/contacts">Записаться</Link></Button>
</div>
</header>
{children}
</main>
);
}

View File

@@ -1,226 +0,0 @@
import Image from "next/image";
import Link from "next/link";
import { ArrowRightIcon, Building2Icon, CheckIcon, HomeIcon, MapPinIcon, TreesIcon } from "lucide-react";
import { MortgageCalculator } from "@/features/mortgage-calculator/ui/mortgage-calculator";
import { apartments, amenities, construction, locationPoints, navItems, purchaseSteps, site } from "@/entities/site-content";
import { Badge } from "@/shared/ui/badge";
import { Button } from "@/shared/ui/button";
import { Progress } from "@/shared/ui/progress";
function Shell({ children }: { children: React.ReactNode }) {
return (
<main className="min-h-screen bg-background text-foreground">
<header className="sticky top-0 z-40 border-b bg-background/90 backdrop-blur">
<div className="mx-auto flex max-w-7xl items-center justify-between px-4 py-4 md:px-6">
<Link href="/" className="flex items-center gap-2 font-semibold">
<span className="rounded-full bg-primary p-2 text-primary-foreground"><Building2Icon className="size-4" /></span>
{site.name}
</Link>
<nav className="hidden items-center gap-6 text-sm text-muted-foreground md:flex">
{navItems.map((item) => <Link key={item.href} href={item.href} className="hover:text-foreground">{item.label}</Link>)}
</nav>
<Button asChild size="sm"><Link href="/contacts">Записаться</Link></Button>
</div>
</header>
{children}
</main>
);
}
function SectionHeader({ label, title, text }: { label: string; title: string; text: string }) {
return (
<div className="mx-auto mb-8 max-w-7xl px-4 md:px-6">
<Badge variant="outline" className="mb-4 bg-card">{label}</Badge>
<div className="grid gap-4 md:grid-cols-[0.9fr_0.8fr] md:items-end">
<h1 className="text-4xl font-semibold tracking-normal md:text-6xl">{title}</h1>
<p className="text-lg leading-8 text-muted-foreground">{text}</p>
</div>
</div>
);
}
function FloorPlanCard() {
return (
<div className="floor-grid relative min-h-[420px] overflow-hidden rounded-lg border bg-card p-5">
<div className="absolute inset-6 grid grid-cols-6 grid-rows-5 gap-2">
<div className="col-span-4 row-span-3 rounded-md border-2 border-primary/70 bg-background/80 p-3 text-sm font-medium">Гостиная 32.4 м²</div>
<div className="col-span-2 row-span-2 rounded-md border-2 border-primary/50 bg-secondary/45 p-3 text-sm">Кухня</div>
<div className="col-span-2 row-span-2 rounded-md border-2 border-primary/50 bg-background/80 p-3 text-sm">Мастер</div>
<div className="col-span-2 row-span-2 rounded-md border-2 border-primary/50 bg-background/80 p-3 text-sm">Детская</div>
<div className="col-span-1 rounded-md border-2 border-primary/40 bg-muted p-2 text-xs">С/у</div>
<div className="col-span-1 rounded-md border-2 border-primary/40 bg-muted p-2 text-xs">Пост.</div>
</div>
<div className="absolute bottom-5 left-5 rounded-full bg-primary px-4 py-2 text-sm font-medium text-primary-foreground">A-1204 / 96.4 м²</div>
</div>
);
}
function ApartmentRows() {
return (
<div className="overflow-hidden rounded-lg border bg-card">
{apartments.map((apartment) => (
<Link key={apartment.id} href={apartment.id === "A-1204" ? "/apartments/a-1204" : "/apartments"} className="grid gap-3 border-b p-4 transition last:border-b-0 hover:bg-muted/60 md:grid-cols-[120px_1fr_120px_120px_160px_120px] md:items-center">
<span className="font-semibold">{apartment.id}</span>
<span>{apartment.rooms} / {apartment.area}</span>
<span className="text-muted-foreground">этаж {apartment.floor}</span>
<span className="text-muted-foreground">{apartment.view}</span>
<span className="font-semibold">{apartment.price}</span>
<Badge variant={apartment.status === "доступна" ? "default" : "secondary"}>{apartment.status}</Badge>
</Link>
))}
</div>
);
}
export function HomePage() {
return (
<Shell>
<section className="northyard-stone px-4 py-8 md:px-6 md:py-12">
<div className="mx-auto grid max-w-7xl gap-5 lg:grid-cols-[1.1fr_0.9fr]">
<div className="rounded-lg border bg-card/80 p-6 shadow-sm md:p-10">
<Badge className="mb-8 bg-secondary text-foreground">Ключи с IV квартала 2026</Badge>
<h1 className="max-w-4xl text-5xl font-semibold leading-[0.98] md:text-7xl">Квартал с приватным двором и городом в пешей доступности</h1>
<p className="mt-6 max-w-2xl text-lg leading-8 text-muted-foreground">{site.name} соединяет клубный сервис, спокойные планировки и инфраструктуру района без ощущения закрытого острова.</p>
<div className="mt-8 flex flex-wrap gap-3">
<Button asChild size="lg"><Link href="/apartments">Выбрать квартиру <ArrowRightIcon className="size-4" /></Link></Button>
<Button asChild variant="outline" size="lg"><Link href="/location">Смотреть локацию</Link></Button>
</div>
</div>
<div className="grid gap-5 md:grid-cols-2 lg:grid-cols-1">
<div className="relative min-h-[320px] overflow-hidden rounded-lg border">
<Image src="https://images.unsplash.com/photo-1600607688969-a5bfcd646154?auto=format&fit=crop&w=1400&q=82" alt="Лобби жилого квартала" fill priority className="object-cover" sizes="(min-width: 1024px) 40vw, 100vw" />
</div>
<div className="grid grid-cols-3 gap-3">
{["214 квартир", "2 корпуса", "0 машин во дворе"].map((item) => <div key={item} className="rounded-lg border bg-card p-4 text-sm font-semibold shadow-sm">{item}</div>)}
</div>
</div>
</div>
</section>
<section className="px-4 py-12 md:px-6 md:py-18">
<div className="mx-auto grid max-w-7xl gap-5 lg:grid-cols-[1fr_380px]">
<div>
<SectionHeader label="Квартиры" title="Планировки, которые держат сценарий семьи" text="От студий для инвестиций до угловых квартир с мастер-блоком, постирочной и окнами на тихий двор." />
<div className="px-4 md:px-6"><ApartmentRows /></div>
</div>
<div className="lg:sticky lg:top-24 lg:self-start"><MortgageCalculator /></div>
</div>
</section>
</Shell>
);
}
export function ApartmentsPage() {
return (
<Shell>
<section className="py-12 md:py-18">
<SectionHeader label="Каталог" title="Квартиры по корпусам, видам и сценариям" text="Каталог показывает не только площадь и цену, но и планировочную пользу: хранение, приватность, рабочие зоны и ориентацию окон." />
<div className="mx-auto grid max-w-7xl gap-6 px-4 md:px-6 lg:grid-cols-[1fr_380px]">
<ApartmentRows />
<div className="rounded-lg border bg-card p-5">
<HomeIcon className="mb-5 size-7" />
<h2 className="text-2xl font-semibold">Фильтры для будущего CMS</h2>
<p className="mt-3 leading-7 text-muted-foreground">Комнаты, корпус, вид, отделка, статус брони и диапазон цены уже отражены в интерфейсе как production-ready блоки.</p>
</div>
</div>
</section>
</Shell>
);
}
export function ApartmentDetailPage() {
const apartment = apartments[0];
return (
<Shell>
<section className="px-4 py-10 md:px-6 md:py-16">
<div className="mx-auto grid max-w-7xl gap-6 lg:grid-cols-[1fr_420px]">
<div>
<Badge className="mb-5">{apartment.status}</Badge>
<h1 className="text-5xl font-semibold md:text-7xl">Квартира {apartment.id}</h1>
<p className="mt-4 max-w-2xl text-lg leading-8 text-muted-foreground">{apartment.rooms}, {apartment.area}, {apartment.plan}. Вид на {apartment.view}, этаж {apartment.floor}.</p>
<div className="mt-8"><FloorPlanCard /></div>
</div>
<aside className="rounded-lg border bg-card p-5 shadow-sm lg:sticky lg:top-24 lg:self-start">
<div className="text-sm text-muted-foreground">Стоимость</div>
<div className="mt-1 text-4xl font-semibold">{apartment.price}</div>
<div className="mt-6 grid gap-3 text-sm">
{['Отделка white box', 'Окна в пол', '2 санузла', 'Семейная кухня-гостиная'].map((item) => <div key={item} className="flex items-center gap-2"><CheckIcon className="size-4 text-primary" />{item}</div>)}
</div>
<Button asChild className="mt-6 w-full"><Link href="/contacts">Забронировать просмотр</Link></Button>
</aside>
</div>
</section>
</Shell>
);
}
export function LocationPage() {
return (
<Shell>
<section className="py-12 md:py-18">
<SectionHeader label="Локация" title="Город рядом, двор остается тихим" text="Страница продает не абстрактный адрес, а ежедневные маршруты: школа, парк, офис, сервисы и вечерние прогулки." />
<div className="mx-auto grid max-w-7xl gap-5 px-4 md:px-6 lg:grid-cols-[1fr_1fr]">
<div className="floor-grid min-h-[520px] rounded-lg border bg-card p-6">
<MapPinIcon className="mb-20 size-10 text-primary" />
<div className="rounded-lg bg-primary p-5 text-primary-foreground shadow-lg md:w-72">{site.address}<br />главный вход с пешеходной улицы</div>
</div>
<div className="grid gap-4">{locationPoints.map((point) => <article key={point.title} className="rounded-lg border bg-card p-5"><div className="text-3xl font-semibold">{point.time}</div><h2 className="mt-3 text-xl font-semibold">{point.title}</h2><p className="mt-2 text-muted-foreground">{point.text}</p></article>)}</div>
</div>
</section>
</Shell>
);
}
export function AmenitiesPage() {
return (
<Shell>
<section className="py-12 md:py-18">
<SectionHeader label="Сервис" title="Инфраструктура не для буклета, а для будней" text="Блоки отвечают на бизнес-вопрос покупателя: что будет удобно каждый день после сделки." />
<div className="mx-auto grid max-w-7xl gap-5 px-4 md:grid-cols-2 md:px-6">
{amenities.map((item, index) => <article key={item.title} className="min-h-72 rounded-lg border bg-card p-6"><TreesIcon className="mb-12 size-8 text-primary" /><div className="text-sm text-muted-foreground">0{index + 1}</div><h2 className="mt-3 text-3xl font-semibold">{item.title}</h2><p className="mt-4 leading-7 text-muted-foreground">{item.text}</p></article>)}
</div>
</section>
</Shell>
);
}
export function PurchasePage() {
return (
<Shell>
<section className="py-12 md:py-18">
<SectionHeader label="Покупка" title="Понятный путь от выбора до ключей" text="Шаблон закрывает блоки, которые нужны девелоперу: бронь, ипотека, документы, ход строительства и контакты отдела продаж." />
<div className="mx-auto grid max-w-7xl gap-6 px-4 md:px-6 lg:grid-cols-[0.9fr_1.1fr]">
<MortgageCalculator />
<div className="space-y-4">
{purchaseSteps.map((step, index) => <div key={step} className="rounded-lg border bg-card p-5"><span className="text-sm text-muted-foreground">0{index + 1}</span><h2 className="mt-2 text-2xl font-semibold">{step}</h2></div>)}
<div className="rounded-lg border bg-card p-5">
<h2 className="mb-4 text-2xl font-semibold">Ход строительства</h2>
<div className="grid gap-4">{construction.map((item) => <div key={item.label}><div className="mb-2 flex justify-between text-sm"><span>{item.label}</span><span>{item.value}%</span></div><Progress value={item.value} /></div>)}</div>
</div>
</div>
</div>
</section>
</Shell>
);
}
export function ContactsPage() {
return (
<Shell>
<section className="northyard-stone px-4 py-12 md:px-6 md:py-18">
<div className="mx-auto grid max-w-7xl gap-6 lg:grid-cols-[1fr_420px]">
<div className="rounded-lg border bg-card/90 p-6 md:p-10">
<Badge className="mb-6">Отдел продаж</Badge>
<h1 className="text-5xl font-semibold md:text-7xl">Запишитесь на приватный просмотр</h1>
<p className="mt-6 text-lg leading-8 text-muted-foreground">Менеджер подготовит подборку квартир, планировки и финансовый сценарий до визита.</p>
</div>
<div className="rounded-lg border bg-card p-6 shadow-sm">
<div className="text-sm text-muted-foreground">Телефон</div><div className="text-2xl font-semibold">{site.phone}</div>
<div className="mt-6 text-sm text-muted-foreground">Email</div><div className="text-2xl font-semibold">{site.email}</div>
<Button className="mt-8 w-full">Назначить просмотр</Button>
</div>
</div>
</section>
</Shell>
);
}