From 520cb0e1847561f2cb4b1daf20491c753f9fc5cc Mon Sep 17 00:00:00 2001 From: StanisLove Date: Thu, 18 Jun 2026 23:14:26 +0300 Subject: [PATCH] feat: split big file and update agents.md --- AGENTS.md | 55 ++++++- src/app/cart/page.tsx | 13 +- src/app/catalog/page.tsx | 15 +- src/app/layout.tsx | 2 +- src/app/lookbook/page.tsx | 14 +- src/app/page.tsx | 27 +--- src/app/product/classic-overshirt/page.tsx | 15 +- src/app/shipping/page.tsx | 14 +- src/shared/ui/icon-cards.tsx | 8 + src/shared/ui/info-columns.tsx | 3 + src/shared/ui/inner-hero.tsx | 5 + src/shared/ui/split-story.tsx | 6 + src/widgets/cart-page.tsx | 24 +++ src/widgets/catalog-page.tsx | 31 ++++ src/widgets/collection-board.tsx | 13 ++ src/widgets/featured-grid.tsx | 11 ++ src/widgets/home-page.tsx | 71 +++++++++ src/widgets/lookbook-page.tsx | 26 ++++ src/widgets/product-assurance.tsx | 4 + src/widgets/product-detail-page.tsx | 37 +++++ src/widgets/shipping-page.tsx | 16 ++ src/widgets/site-shell.tsx | 39 +++++ src/widgets/template-ui.tsx | 167 --------------------- 23 files changed, 361 insertions(+), 255 deletions(-) create mode 100644 src/shared/ui/icon-cards.tsx create mode 100644 src/shared/ui/info-columns.tsx create mode 100644 src/shared/ui/inner-hero.tsx create mode 100644 src/shared/ui/split-story.tsx create mode 100644 src/widgets/cart-page.tsx create mode 100644 src/widgets/catalog-page.tsx create mode 100644 src/widgets/collection-board.tsx create mode 100644 src/widgets/featured-grid.tsx create mode 100644 src/widgets/home-page.tsx create mode 100644 src/widgets/lookbook-page.tsx create mode 100644 src/widgets/product-assurance.tsx create mode 100644 src/widgets/product-detail-page.tsx create mode 100644 src/widgets/shipping-page.tsx create mode 100644 src/widgets/site-shell.tsx delete mode 100644 src/widgets/template-ui.tsx diff --git a/AGENTS.md b/AGENTS.md index cc56559..b4c825e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,7 +5,7 @@ ## Project Specifics - Source content lives in `src/entities/site-content.ts`; keep visible copy there or directly in route JSX. -- `src/app` is routing only; page composition belongs to `src/widgets/template-ui.tsx`. +- `src/app` — только route wrappers; композиция каждой страницы живёт в отдельном widget (`src/widgets/-page.tsx`). Header/footer — в `src/widgets/site-shell.tsx`. См. File Map. - Keep cards at 8px radius or less and preserve the selected palette in `src/app/globals.css`. - This is a static frontend template: do not add real payments, auth, persistence, external API calls, or backend contracts without an explicit product request. - After changes run `pnpm lint` and `pnpm build`. @@ -14,3 +14,56 @@ - Кириллица обязательна для видимого текста и выбранных Google Fonts. - При AI-правках сохраняйте доменные блоки этого шаблона: они отличают проект от generic landing. + +## Design System + +Источник токенов — `src/app/globals.css` (`@theme inline` + `:root`/`.dark`). Шрифты: заголовки и `.font-display` — **Montserrat** (`--font-display`), body — **Inter** (`--font-sans`); mono замаплен на тот же display. Работай через семантические классы Tailwind (`bg-foreground`, `text-muted-foreground`, `border-border`), не хардкодь hex/oklch. + +Личность: **premium monochrome fashion** — почти бесцветная ахроматическая палитра (всё в нейтральных серых, `chroma ≈ 0`), near-black «ink» как контраст, и единственный тёплый янтарный `accent` (oklch 0.72 0.16 71) для редких акцентов. Фотографии сознательно `grayscale` и расцвечиваются (`group-hover:grayscale-0`) только при наведении — это ключевой приём бренда. + +| Роль | Light | Характер | +|---|---|---| +| `background` | почти белый neutral (0.985) | основной фон страниц | +| `foreground` | near-black ink (0.13) | текст + инверсный CTA/footer (`bg-foreground text-background`) | +| `primary` | near-black ink | логотип-плашка, primary-кнопки | +| `secondary` | светло-серый (0.94) | бейджи, теги товаров | +| `accent` | тёплый янтарь | редкий точечный акцент (по умолчанию почти не используется) | +| `muted` | серый (0.955 / fg 0.48) | фон image-плейсхолдеров, вторичный текст | +| `card` | чистый белый (1.0) | карточки на фоне 0.985 | +| `border` | мягкий серый (0.86) | тонкие границы (`border border-border`, НЕ толстые) | + +Узнаваемые приёмы (держи их, это и есть «лицо» проекта): +- **Мягкие углы:** `--radius` = 0.375rem; почти всё — `rounded-md`. Никаких острых или сильно скруглённых форм. +- **Тонкие границы:** `border border-border` (1px), не толстые brutalist-рамки. +- **Grayscale → color на hover:** фото идут `object-cover grayscale`, при наведении карточки — `transition group-hover:grayscale-0` (+ лёгкий `group-hover:scale-[1.03]`). +- **Hover-lift с мягкой тенью:** `hover:-translate-y-1 hover:shadow-[0_24px_60px_rgba(0,0,0,0.09)]` — приподнятая карточка вместо жёсткого offset-shadow. +- **Типографика:** `font-semibold` (не black), очень крупные плотные заголовки (`text-7xl`+, `leading-[0.94]`), eyebrow — `uppercase tracking-[0.16em] text-muted-foreground`. +- **Инверсные блоки:** акцентные секции (footer, CTA, средняя карточка lookbook) — `bg-foreground text-background` с `text-background/70` для вторичного текста. +- **Фон-фактура:** `.fashion-fabric` — тонкая тканевая сетка для hero; фиксированная max-ширина контента `max-w-[1320px]`. + +Do / Don't: +- **Do:** держи ахроматическую палитру, тонкие границы, grayscale-фото, product-first сетки, крупную сдержанную типографику. +- **Don't:** яркие цвета/градиенты, толстые рамки, жёсткие offset-тени, `rounded-none` или `rounded-full` на карточках, `font-black` — это ломает premium-минимализм. + +## File Map + +| Route | Widget | +|---|---| +| `/` | `src/widgets/home-page.tsx` (`HomePage`) | +| `/catalog` | `src/widgets/catalog-page.tsx` (`CatalogPage`) | +| `/product/classic-overshirt` | `src/widgets/product-detail-page.tsx` (`ProductDetailPage`) | +| `/lookbook` | `src/widgets/lookbook-page.tsx` (`LookbookPage`) | +| `/cart` | `src/widgets/cart-page.tsx` (`CartPage`) | +| `/shipping` | `src/widgets/shipping-page.tsx` (`ShippingPage`) | + +Переиспользуемые блоки: +- `src/widgets/site-shell.tsx` — `SiteHeader` + `SiteFooter` (обёртка всех страниц через `app/layout.tsx`). +- `src/widgets/featured-grid.tsx` — `FeaturedGrid` (Home, Catalog). +- `src/widgets/collection-board.tsx` — `CollectionBoard` (Home, Lookbook). +- `src/widgets/product-assurance.tsx` — `ProductAssurance` (Home, Product). +- `src/shared/ui/inner-hero.tsx` — `InnerHero` (заголовочная секция внутренних страниц). +- `src/shared/ui/icon-cards.tsx` — `IconCards` (Shipping, Product). +- `src/shared/ui/info-columns.tsx` — `InfoColumns` (Shipping, Cart). +- `src/shared/ui/split-story.tsx` — `SplitStory` (Home, Lookbook). + +Одноразовые блоки колоцированы со своей страницей: `PageHero`/`MetricStrip`/`TestimonialBand` в `home-page.tsx`, `CatalogToolbar`/`PricingTiles` в `catalog-page.tsx`, `LookbookGrid` в `lookbook-page.tsx`, `CartPreview` в `cart-page.tsx`, `ProductDetail`/`OptionRow`/`CtaPanel` в `product-detail-page.tsx`. diff --git a/src/app/cart/page.tsx b/src/app/cart/page.tsx index 6e17890..47d6250 100644 --- a/src/app/cart/page.tsx +++ b/src/app/cart/page.tsx @@ -1,14 +1,5 @@ -"use client"; - -import { CartPreview, InfoColumns, InnerHero } from "@/widgets/template-ui"; - +import { CartPage } from "@/widgets/cart-page"; export default function Page() { - return ( - <> - - - - - ); + return ; } diff --git a/src/app/catalog/page.tsx b/src/app/catalog/page.tsx index 8c621b7..41d702d 100644 --- a/src/app/catalog/page.tsx +++ b/src/app/catalog/page.tsx @@ -1,16 +1,5 @@ -"use client"; - -import { CatalogToolbar, FeaturedGrid, InnerHero, PricingTiles } from "@/widgets/template-ui"; -import { products, tastingSets } from "@/entities/site-content"; - +import { CatalogPage } from "@/widgets/catalog-page"; export default function Page() { - return ( - <> - - - - - - ); + return ; } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 99b5d6d..6b59ea8 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,7 +3,7 @@ import { Inter, Montserrat } from "next/font/google"; import "./globals.css"; import { ThemeProvider } from "@/shared/hooks/theme-provider"; import { ThemeMessageListener } from "@/shared/hooks/theme-message-listener"; -import { SiteHeader, SiteFooter } from "@/widgets/template-ui"; +import { SiteHeader, SiteFooter } from "@/widgets/site-shell"; const display = Montserrat({ variable: "--font-display", diff --git a/src/app/lookbook/page.tsx b/src/app/lookbook/page.tsx index f2916ff..48fdd06 100644 --- a/src/app/lookbook/page.tsx +++ b/src/app/lookbook/page.tsx @@ -1,15 +1,5 @@ -"use client"; - -import { CollectionBoard, InnerHero, LookbookGrid, SplitStory } from "@/widgets/template-ui"; - +import { LookbookPage } from "@/widgets/lookbook-page"; export default function Page() { - return ( - <> - - - - - - ); + return ; } diff --git a/src/app/page.tsx b/src/app/page.tsx index e2aa520..5b1660a 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,28 +1,5 @@ -"use client"; - -import { CollectionBoard, FeaturedGrid, MetricStrip, PageHero, ProductAssurance, SplitStory, TestimonialBand } from "@/widgets/template-ui"; -import { highlights, products, site, testimonials } from "@/entities/site-content"; - +import { HomePage } from "@/widgets/home-page"; export default function Page() { - return ( - <> - - - - - - - - - ); + return ; } diff --git a/src/app/product/classic-overshirt/page.tsx b/src/app/product/classic-overshirt/page.tsx index 4ce77a9..0d8dbb3 100644 --- a/src/app/product/classic-overshirt/page.tsx +++ b/src/app/product/classic-overshirt/page.tsx @@ -1,16 +1,5 @@ -"use client"; - -import { CtaPanel, IconCards, ProductAssurance, ProductDetail } from "@/widgets/template-ui"; -import { eventTypes } from "@/entities/site-content"; - +import { ProductDetailPage } from "@/widgets/product-detail-page"; export default function Page() { - return ( - <> - - - - - - ); + return ; } diff --git a/src/app/shipping/page.tsx b/src/app/shipping/page.tsx index 0192f6e..e40cbc0 100644 --- a/src/app/shipping/page.tsx +++ b/src/app/shipping/page.tsx @@ -1,15 +1,5 @@ -"use client"; - -import { IconCards, InfoColumns, InnerHero } from "@/widgets/template-ui"; -import { contactCards } from "@/entities/site-content"; - +import { ShippingPage } from "@/widgets/shipping-page"; export default function Page() { - return ( - <> - - - - - ); + return ; } diff --git a/src/shared/ui/icon-cards.tsx b/src/shared/ui/icon-cards.tsx new file mode 100644 index 0000000..49f8ea9 --- /dev/null +++ b/src/shared/ui/icon-cards.tsx @@ -0,0 +1,8 @@ +import { eventTypes } from "@/entities/site-content"; + +type IconComponent = React.ComponentType<{ className?: string }>; +type IconItem = { title: string; text: string; icon: IconComponent }; + +export function IconCards({ items = eventTypes }: { items?: readonly IconItem[] }) { + return
{items.map((item) => { const Icon = item.icon; return

{item.title}

{item.text}

; })}
; +} diff --git a/src/shared/ui/info-columns.tsx b/src/shared/ui/info-columns.tsx new file mode 100644 index 0000000..15ee9ab --- /dev/null +++ b/src/shared/ui/info-columns.tsx @@ -0,0 +1,3 @@ +export function InfoColumns({ title, items }: { title: string; items: readonly { title: string; text: string }[] }) { + return

{title}

{items.map((item) =>

{item.title}

{item.text}

)}
; +} diff --git a/src/shared/ui/inner-hero.tsx b/src/shared/ui/inner-hero.tsx new file mode 100644 index 0000000..7400768 --- /dev/null +++ b/src/shared/ui/inner-hero.tsx @@ -0,0 +1,5 @@ +import { Badge } from "@/shared/ui/badge"; + +export function InnerHero({ eyebrow, title, text }: { eyebrow: string; title: string; text: string }) { + return
{eyebrow}

{title}

{text}

; +} diff --git a/src/shared/ui/split-story.tsx b/src/shared/ui/split-story.tsx new file mode 100644 index 0000000..63be5c2 --- /dev/null +++ b/src/shared/ui/split-story.tsx @@ -0,0 +1,6 @@ +import Image from "next/image"; +import { CheckIcon } from "lucide-react"; + +export function SplitStory({ image, eyebrow, title, text, points }: { image: string; eyebrow: string; title: string; text: string; points: readonly string[] }) { + return
{title}
{eyebrow}

{title}

{text}

{points.map((point) =>
{point}
)}
; +} diff --git a/src/widgets/cart-page.tsx b/src/widgets/cart-page.tsx new file mode 100644 index 0000000..2907963 --- /dev/null +++ b/src/widgets/cart-page.tsx @@ -0,0 +1,24 @@ +"use client"; + +import Image from "next/image"; +import { MinusIcon, PlusIcon } from "lucide-react"; + +import { products } from "@/entities/site-content"; +import { Button } from "@/shared/ui/button"; +import { Separator } from "@/shared/ui/separator"; +import { InfoColumns } from "@/shared/ui/info-columns"; +import { InnerHero } from "@/shared/ui/inner-hero"; + +function CartPreview() { + return
{products.slice(0, 3).map((item, index) =>
{item.name}

{item.name}

{index === 0 ? "black / L" : "black / M"}

1
)}
; +} + +export function CartPage() { + return ( + <> + + + + + ); +} diff --git a/src/widgets/catalog-page.tsx b/src/widgets/catalog-page.tsx new file mode 100644 index 0000000..904fa15 --- /dev/null +++ b/src/widgets/catalog-page.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { CheckIcon, FilterIcon, SearchIcon, SlidersHorizontalIcon } from "lucide-react"; + +import { products, tastingSets } from "@/entities/site-content"; +import { Button } from "@/shared/ui/button"; +import { Input } from "@/shared/ui/input"; +import { Separator } from "@/shared/ui/separator"; +import { FeaturedGrid } from "@/widgets/featured-grid"; +import { InnerHero } from "@/shared/ui/inner-hero"; + +type TileItem = { title: string; price: string; items: readonly string[] }; + +function CatalogToolbar() { + return
; +} + +function PricingTiles({ title, items }: { title: string; items: readonly TileItem[] }) { + return

{title}

{items.map((item) =>

{item.title}

{item.price}
{item.items.map((line) =>
{line}
)}
)}
; +} + +export function CatalogPage() { + return ( + <> + + + + + + ); +} diff --git a/src/widgets/collection-board.tsx b/src/widgets/collection-board.tsx new file mode 100644 index 0000000..412488f --- /dev/null +++ b/src/widgets/collection-board.tsx @@ -0,0 +1,13 @@ +import Image from "next/image"; +import Link from "next/link"; +import { ArrowRightIcon } from "lucide-react"; + +import { products, site } from "@/entities/site-content"; +import { Button } from "@/shared/ui/button"; + +const productImages = products.map((item) => item.image); + +export function CollectionBoard() { + const looks = [{ title: "01 / Layered city", image: site.heroImage, text: "overshirt поверх плотного jersey" }, { title: "02 / Evening black", image: site.accentImage, text: "широкие брюки и структурный верх" }, { title: "03 / Travel shell", image: productImages[2], text: "сумка utility и легкий слой" }] as const; + return

Lookbook говорит о посадке, ткани и комплекте

{looks.map((look, index) =>
{look.title}

{look.title}

{look.text}

)}
; +} diff --git a/src/widgets/featured-grid.tsx b/src/widgets/featured-grid.tsx new file mode 100644 index 0000000..c7ab997 --- /dev/null +++ b/src/widgets/featured-grid.tsx @@ -0,0 +1,11 @@ +import Image from "next/image"; +import Link from "next/link"; + +import { site } from "@/entities/site-content"; +import { Badge } from "@/shared/ui/badge"; + +type TextItem = { name: string; price: string; tag: string; text: string; image?: string }; + +export function FeaturedGrid({ eyebrow, title, text, items }: { eyebrow: string; title: string; text: string; items: readonly TextItem[] }) { + return
{eyebrow}

{title}

{text}

{items.map((item) =>
{item.name}
{item.tag}{item.price}

{item.name}

{item.text}

)}
; +} diff --git a/src/widgets/home-page.tsx b/src/widgets/home-page.tsx new file mode 100644 index 0000000..80fee35 --- /dev/null +++ b/src/widgets/home-page.tsx @@ -0,0 +1,71 @@ +"use client"; + +import Image from "next/image"; +import Link from "next/link"; +import { ArrowRightIcon } from "lucide-react"; + +import { highlights, products, site, testimonials } from "@/entities/site-content"; +import { Badge } from "@/shared/ui/badge"; +import { Button } from "@/shared/ui/button"; +import { SplitStory } from "@/shared/ui/split-story"; +import { CollectionBoard } from "@/widgets/collection-board"; +import { FeaturedGrid } from "@/widgets/featured-grid"; +import { ProductAssurance } from "@/widgets/product-assurance"; + +function PageHero() { + return ( +
+
+
+
+ Core collection 2026 +

Гардероб, который держит форму дня

+

{site.tagline}

+
+ + +
+
+
+
+ Модель в черной коллекции Monochrome Supply +
+
Классическая overshirt
cotton twill / black / прямой крой
+
17 900 ₽
+
+
+
+ {products.slice(1, 4).map((item) => ( + +
{item.name}
+
{item.tag}
{item.name}
{item.price}
+ + ))} +
+
+
+
+ ); +} + +function MetricStrip() { + return
{highlights.map((item) => { const Icon = item.icon; return
{item.value}

{item.title}

{item.text}

; })}
; +} + +function TestimonialBand() { + return
{testimonials.map((item) =>
{item.rating}

“{item.text}”

{item.name}
)}
; +} + +export function HomePage() { + return ( + <> + + + + + + + + + ); +} diff --git a/src/widgets/lookbook-page.tsx b/src/widgets/lookbook-page.tsx new file mode 100644 index 0000000..f748029 --- /dev/null +++ b/src/widgets/lookbook-page.tsx @@ -0,0 +1,26 @@ +"use client"; + +import Image from "next/image"; + +import { products, site } from "@/entities/site-content"; +import { SplitStory } from "@/shared/ui/split-story"; +import { CollectionBoard } from "@/widgets/collection-board"; +import { InnerHero } from "@/shared/ui/inner-hero"; + +const productImages = products.map((item) => item.image); + +function LookbookGrid() { + const images = [site.heroImage, site.accentImage, productImages[0], productImages[1], productImages[2], productImages[3]]; + return
{images.map((image, index) =>
Lookbook Monochrome Supply
)}
; +} + +export function LookbookPage() { + return ( + <> + + + + + + ); +} diff --git a/src/widgets/product-assurance.tsx b/src/widgets/product-assurance.tsx new file mode 100644 index 0000000..ea0cdc6 --- /dev/null +++ b/src/widgets/product-assurance.tsx @@ -0,0 +1,4 @@ +export function ProductAssurance({ compact = false }: { compact?: boolean }) { + const rows = [["Материал", "Плотный хлопковый twill держит форму и не выглядит спортивно."], ["Посадка", "Прямой крой оставляет место под слой и не утяжеляет силуэт."], ["После покупки", "30 дней на возврат, примерочный интервал и подсказки по уходу."]] as const; + return
{rows.map(([title, text]) =>

{title}

{text}

)}
; +} diff --git a/src/widgets/product-detail-page.tsx b/src/widgets/product-detail-page.tsx new file mode 100644 index 0000000..7c95a06 --- /dev/null +++ b/src/widgets/product-detail-page.tsx @@ -0,0 +1,37 @@ +"use client"; + +import Image from "next/image"; +import Link from "next/link"; +import { ArrowRightIcon, ChevronRightIcon, HeartIcon, ShoppingCartIcon } from "lucide-react"; + +import { eventTypes, products, site } from "@/entities/site-content"; +import { Badge } from "@/shared/ui/badge"; +import { Button } from "@/shared/ui/button"; +import { Separator } from "@/shared/ui/separator"; +import { IconCards } from "@/shared/ui/icon-cards"; +import { ProductAssurance } from "@/widgets/product-assurance"; + +const productImages = products.map((item) => item.image); + +function OptionRow({ label, options, active }: { label: string; options: readonly string[]; active?: string }) { + return
{label}
{options.map((option) => )}
; +} + +function ProductDetail() { + return
Классическая overshirt
{[site.heroImage, site.accentImage, productImages[1], productImages[2]].map((image) =>
Деталь товара
)}
Каталог Верхний слой Overshirt

Классическая overshirt

4.8 ★210 отзывов
17 900 ₽
22 900 ₽
Скидка 20%

Плотный cotton twill, прямой крой, скрытая планка и аккуратный вес для межсезонья.

; +} + +function CtaPanel({ title, text, href, label }: { title: string; text: string; href: string; label: string }) { + return

{title}

{text}

; +} + +export function ProductDetailPage() { + return ( + <> + + + + + + ); +} diff --git a/src/widgets/shipping-page.tsx b/src/widgets/shipping-page.tsx new file mode 100644 index 0000000..1077e7a --- /dev/null +++ b/src/widgets/shipping-page.tsx @@ -0,0 +1,16 @@ +"use client"; + +import { contactCards } from "@/entities/site-content"; +import { IconCards } from "@/shared/ui/icon-cards"; +import { InfoColumns } from "@/shared/ui/info-columns"; +import { InnerHero } from "@/shared/ui/inner-hero"; + +export function ShippingPage() { + return ( + <> + + + + + ); +} diff --git a/src/widgets/site-shell.tsx b/src/widgets/site-shell.tsx new file mode 100644 index 0000000..83a9f98 --- /dev/null +++ b/src/widgets/site-shell.tsx @@ -0,0 +1,39 @@ +import Link from "next/link"; +import { MenuIcon } from "lucide-react"; + +import { site } from "@/entities/site-content"; +import { Button } from "@/shared/ui/button"; + +export function SiteHeader() { + return ( +
+
+ + Mo + {site.name} + + +
+ + +
+
+
+ ); +} + +export function SiteFooter() { + return ( +
+
+
{site.name}

{site.tagline}

+
Коллекция
{site.nav.slice(0, 4).map((item) => {item.label})}
+
Шаблон
Product-first ecommerce с каталогом, PDP, lookbook, корзиной и сервисной страницей.
+
+
+ ); +} diff --git a/src/widgets/template-ui.tsx b/src/widgets/template-ui.tsx deleted file mode 100644 index 181dde3..0000000 --- a/src/widgets/template-ui.tsx +++ /dev/null @@ -1,167 +0,0 @@ -"use client"; - -import Image from "next/image"; -import Link from "next/link"; -import { - ArrowRightIcon, - CheckIcon, - ChevronRightIcon, - FilterIcon, - HeartIcon, - MenuIcon, - MinusIcon, - PlusIcon, - SearchIcon, - ShoppingCartIcon, - SlidersHorizontalIcon, -} from "lucide-react"; - -import { eventTypes, highlights, products, site, testimonials } from "@/entities/site-content"; -import { Badge } from "@/shared/ui/badge"; -import { Button } from "@/shared/ui/button"; -import { Input } from "@/shared/ui/input"; -import { Separator } from "@/shared/ui/separator"; - -type IconComponent = React.ComponentType<{ className?: string }>; -type TextItem = { name: string; price: string; tag: string; text: string; image?: string }; -type TileItem = { title: string; price: string; items: readonly string[] }; -type IconItem = { title: string; text: string; icon: IconComponent }; - -const productImages = products.map((item) => item.image); - -export function SiteHeader() { - return ( -
-
- - Mo - {site.name} - - -
- - -
-
-
- ); -} - -export function SiteFooter() { - return ( -
-
-
{site.name}

{site.tagline}

-
Коллекция
{site.nav.slice(0, 4).map((item) => {item.label})}
-
Шаблон
Product-first ecommerce с каталогом, PDP, lookbook, корзиной и сервисной страницей.
-
-
- ); -} - -export function PageHero(_props: Record = {}) { - return ( -
-
-
-
- Core collection 2026 -

Гардероб, который держит форму дня

-

{site.tagline}

-
- - -
-
-
-
- Модель в черной коллекции Monochrome Supply -
-
Классическая overshirt
cotton twill / black / прямой крой
-
17 900 ₽
-
-
-
- {products.slice(1, 4).map((item) => ( - -
{item.name}
-
{item.tag}
{item.name}
{item.price}
- - ))} -
-
-
-
- ); -} - -export function InnerHero({ eyebrow, title, text }: { eyebrow: string; title: string; text: string }) { - return
{eyebrow}

{title}

{text}

; -} - -export function CatalogToolbar() { - return
; -} - -export function FeaturedGrid({ eyebrow, title, text, items }: { eyebrow: string; title: string; text: string; items: readonly TextItem[] }) { - return
{eyebrow}

{title}

{text}

{items.map((item) =>
{item.name}
{item.tag}{item.price}

{item.name}

{item.text}

)}
; -} - -export function MetricStrip(_props: { items?: unknown } = {}) { - return
{highlights.map((item) => { const Icon = item.icon; return
{item.value}

{item.title}

{item.text}

; })}
; -} - -export function CollectionBoard() { - const looks = [{ title: "01 / Layered city", image: site.heroImage, text: "overshirt поверх плотного jersey" }, { title: "02 / Evening black", image: site.accentImage, text: "широкие брюки и структурный верх" }, { title: "03 / Travel shell", image: productImages[2], text: "сумка utility и легкий слой" }] as const; - return

Lookbook говорит о посадке, ткани и комплекте

{looks.map((look, index) =>
{look.title}

{look.title}

{look.text}

)}
; -} - -export function ProductDetail() { - return
Классическая overshirt
{[site.heroImage, site.accentImage, productImages[1], productImages[2]].map((image) =>
Деталь товара
)}
Каталог Верхний слой Overshirt

Классическая overshirt

4.8 ★210 отзывов
17 900 ₽
22 900 ₽
Скидка 20%

Плотный cotton twill, прямой крой, скрытая планка и аккуратный вес для межсезонья.

; -} - -function OptionRow({ label, options, active }: { label: string; options: readonly string[]; active?: string }) { - return
{label}
{options.map((option) => )}
; -} - -export function ProductAssurance({ compact = false }: { compact?: boolean }) { - const rows = [["Материал", "Плотный хлопковый twill держит форму и не выглядит спортивно."], ["Посадка", "Прямой крой оставляет место под слой и не утяжеляет силуэт."], ["После покупки", "30 дней на возврат, примерочный интервал и подсказки по уходу."]] as const; - return
{rows.map(([title, text]) =>

{title}

{text}

)}
; -} - -export function PricingTiles({ title, items }: { title: string; items: readonly TileItem[] }) { - return

{title}

{items.map((item) =>

{item.title}

{item.price}
{item.items.map((line) =>
{line}
)}
)}
; -} - -export function LookbookGrid() { - const images = [site.heroImage, site.accentImage, productImages[0], productImages[1], productImages[2], productImages[3]]; - return
{images.map((image, index) =>
Lookbook Monochrome Supply
)}
; -} - -export function CartPreview() { - return
{products.slice(0, 3).map((item, index) =>
{item.name}

{item.name}

{index === 0 ? "black / L" : "black / M"}

1
)}
; -} - -export function IconCards({ items = eventTypes }: { items?: readonly IconItem[] }) { - return
{items.map((item) => { const Icon = item.icon; return

{item.title}

{item.text}

; })}
; -} - -export function InfoColumns({ title, items }: { title: string; items: readonly { title: string; text: string }[] }) { - return

{title}

{items.map((item) =>

{item.title}

{item.text}

)}
; -} - -export function SplitStory({ image, eyebrow, title, text, points }: { image: string; eyebrow: string; title: string; text: string; points: readonly string[] }) { - return
{title}
{eyebrow}

{title}

{text}

{points.map((point) =>
{point}
)}
; -} - -export function TestimonialBand(_props: { items?: unknown } = {}) { - return
{testimonials.map((item) =>
{item.rating}

“{item.text}”

{item.name}
)}
; -} - -export function CtaPanel({ title, text, href, label }: { title: string; text: string; href: string; label: string }) { - return

{title}

{text}

; -}