From c7ee4a691aa7a01805d01323734c393f08eaa4bc Mon Sep 17 00:00:00 2001 From: StanisLove Date: Thu, 18 Jun 2026 23:14:57 +0300 Subject: [PATCH] feat: split big file and update agents.md --- AGENTS.md | 50 ++++- src/app/contact/page.tsx | 2 +- src/app/page.tsx | 2 +- src/app/process/page.tsx | 2 +- src/app/services/page.tsx | 2 +- src/app/studio/page.tsx | 2 +- src/app/work/courtyard-house/page.tsx | 2 +- src/app/work/page.tsx | 2 +- src/shared/ui/page-title.tsx | 13 ++ src/widgets/contact-page.tsx | 31 ++++ src/widgets/home-page.tsx | 64 +++++++ src/widgets/process-page.tsx | 22 +++ src/widgets/project-detail-page.tsx | 35 ++++ src/widgets/project-image.tsx | 12 ++ src/widgets/services-page.tsx | 24 +++ src/widgets/site-shell.tsx | 30 +++ src/widgets/studio-page.tsx | 35 ++++ src/widgets/template-ui.tsx | 257 -------------------------- src/widgets/work-page.tsx | 25 +++ 19 files changed, 347 insertions(+), 265 deletions(-) create mode 100644 src/shared/ui/page-title.tsx create mode 100644 src/widgets/contact-page.tsx create mode 100644 src/widgets/home-page.tsx create mode 100644 src/widgets/process-page.tsx create mode 100644 src/widgets/project-detail-page.tsx create mode 100644 src/widgets/project-image.tsx create mode 100644 src/widgets/services-page.tsx create mode 100644 src/widgets/site-shell.tsx create mode 100644 src/widgets/studio-page.tsx delete mode 100644 src/widgets/template-ui.tsx create mode 100644 src/widgets/work-page.tsx diff --git a/AGENTS.md b/AGENTS.md index 2e32e5f..8e4081d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,7 +5,55 @@ Lineform Studio — архитектурный шаблон с жесткой Sw ## Project Specifics - Доменный контент лежит в `src/entities/site-content.ts`; не размазывай списки проектов и услуг по компонентам. -- `src/app` содержит только route wrappers; композиция страниц находится в `src/widgets/template-ui.tsx`. +- `src/app` содержит только route wrappers; композиция каждой страницы живёт в отдельном widget (`src/widgets/-page.tsx`). См. File Map. - Feature-формы и узкие mock-интеракции размещай в `src/features/*/ui`. - Не добавляй CMS, реальные заявки, карты, платежи или backend API без отдельного запроса. - Проверка после правок: `pnpm lint` и `pnpm build`. + +## Design System + +Источник токенов — `src/app/globals.css` (`@theme` + `:root`/`.dark`). Шрифт — Roboto Flex (`--font-lineform`), один гарнитур на sans и mono, с включёнными `ss01`/`cv01`. Работай через семантические классы Tailwind (`bg-background`, `text-foreground`, `border-foreground`), не хардкодь hex/oklch. + +Личность: **Swiss/brutalist «бумага и тушь»** — почти белый бумажный фон, near-black «ink» как граница и текст, и единственный тёплый янтарный accent как редкий сигнал. Цвета намеренно нейтральные (нулевая хрома у фона/текста/secondary) — драматизм создаёт не палитра, а сетка, масштаб типографики и толстые границы. + +| Роль | Light | Характер | +|---|---|---| +| `background` | почти белый (oklch 0.985) | бумажный фон страниц | +| `foreground` | near-black ink (oklch 0.13) | текст + **границы** (`border-2 border-foreground`) | +| `primary` | near-black ink | заливка кнопок/инверсия | +| `secondary` | светло-серый | нейтральные плашки | +| `muted` / `muted-foreground` | серый | вторичный текст, фон фото | +| `accent` | тёплый янтарь (oklch 0.78 0.16 75) | единственный цветной сигнал (бейдж города) | +| `card` | чистый белый | поверхности на бумажном фоне | + +Узнаваемые приёмы (держи их, это и есть «лицо» проекта): +- **Жёсткие углы:** `--radius` = 0.125rem; всё `rounded-none` (кнопки, бейджи, карточки). +- **Толстые границы и divider-ы:** `border-2 border-foreground`, `divide-y-2 divide-foreground`, `border-y-2` — структура страницы строится границами, а не тенями. +- **Никаких теней:** объём даёт инверсия `hover:bg-foreground hover:text-background`, а не drop-shadow. +- **Чертёжная типографика:** `font-black uppercase`, очень плотный `leading` (`leading-none`/`leading-[0.78]`/`leading-[0.88]`), гигантские viewport-размеры (`text-[18vw]`/`md:text-[10vw]`, до `text-9xl`), индексы вида `Index / 01`. +- **Утилитарные классы:** `.lineform-grid` — миллиметровая сетка-подложка для hero/секций; `.arch-photo` — `grayscale(1) contrast(1.08)` на всех фото (ч/б архив). +- **Широкий каркас:** контейнер `max-w-[1500px]`, многоколоночные `md:grid-cols-[...]` раскладки с фиксированными колонками-индексами. + +Do / Don't: +- **Do:** держи ч/б палитру, толстые границы, индексную нумерацию, grayscale-фото и крупную uppercase-типографику; accent — только точечно. +- **Don't:** мягкие тени, скруглённые углы, цветные градиенты, цветные фото, generic-SaaS hero — это ломает «чертёжную» личность шаблона. + +## File Map + +| Route | Widget | +|---|---| +| `/` | `src/widgets/home-page.tsx` (`HomePage`) | +| `/work` | `src/widgets/work-page.tsx` (`WorkPage`) | +| `/work/courtyard-house` | `src/widgets/project-detail-page.tsx` (`ProjectDetailPage`) | +| `/services` | `src/widgets/services-page.tsx` (`ServicesPage`) | +| `/process` | `src/widgets/process-page.tsx` (`ProcessPage`) | +| `/studio` | `src/widgets/studio-page.tsx` (`StudioPage`) | +| `/contact` | `src/widgets/contact-page.tsx` (`ContactPage`) | + +Переиспользуемые блоки: +- `src/widgets/site-shell.tsx` — `SiteShell` (sticky header + nav, обёртка всех страниц). +- `src/widgets/project-image.tsx` — `ProjectImage` (grayscale-фото проекта с индексом; Home + Project Detail). +- `src/shared/ui/page-title.tsx` — `PageTitle` (заголовочная секция `code / title / text` внутренних страниц: Work, Services, Process, Studio, Contact). +- `src/features/project-brief/ui/project-brief-form.tsx` — mock-форма брифа. + +Одноразовые блоки колоцированы со своей страницей (напр. featured-секция и сетка проектов в `home-page.tsx`, список фактов студии в `studio-page.tsx`). diff --git a/src/app/contact/page.tsx b/src/app/contact/page.tsx index b4be4b0..2e99867 100644 --- a/src/app/contact/page.tsx +++ b/src/app/contact/page.tsx @@ -1,4 +1,4 @@ -import { ContactPage } from "@/widgets/template-ui"; +import { ContactPage } from "@/widgets/contact-page"; export default function Page() { return ; diff --git a/src/app/page.tsx b/src/app/page.tsx index 2794f3c..5b1660a 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,4 +1,4 @@ -import { HomePage } from "@/widgets/template-ui"; +import { HomePage } from "@/widgets/home-page"; export default function Page() { return ; diff --git a/src/app/process/page.tsx b/src/app/process/page.tsx index fc3705d..d9cbeb1 100644 --- a/src/app/process/page.tsx +++ b/src/app/process/page.tsx @@ -1,4 +1,4 @@ -import { ProcessPage } from "@/widgets/template-ui"; +import { ProcessPage } from "@/widgets/process-page"; export default function Page() { return ; diff --git a/src/app/services/page.tsx b/src/app/services/page.tsx index c724185..9414a07 100644 --- a/src/app/services/page.tsx +++ b/src/app/services/page.tsx @@ -1,4 +1,4 @@ -import { ServicesPage } from "@/widgets/template-ui"; +import { ServicesPage } from "@/widgets/services-page"; export default function Page() { return ; diff --git a/src/app/studio/page.tsx b/src/app/studio/page.tsx index 957c539..b1ad8b6 100644 --- a/src/app/studio/page.tsx +++ b/src/app/studio/page.tsx @@ -1,4 +1,4 @@ -import { StudioPage } from "@/widgets/template-ui"; +import { StudioPage } from "@/widgets/studio-page"; export default function Page() { return ; diff --git a/src/app/work/courtyard-house/page.tsx b/src/app/work/courtyard-house/page.tsx index 6d54131..588cc06 100644 --- a/src/app/work/courtyard-house/page.tsx +++ b/src/app/work/courtyard-house/page.tsx @@ -1,4 +1,4 @@ -import { ProjectDetailPage } from "@/widgets/template-ui"; +import { ProjectDetailPage } from "@/widgets/project-detail-page"; export default function Page() { return ; diff --git a/src/app/work/page.tsx b/src/app/work/page.tsx index 610e089..837f049 100644 --- a/src/app/work/page.tsx +++ b/src/app/work/page.tsx @@ -1,4 +1,4 @@ -import { WorkPage } from "@/widgets/template-ui"; +import { WorkPage } from "@/widgets/work-page"; export default function Page() { return ; diff --git a/src/shared/ui/page-title.tsx b/src/shared/ui/page-title.tsx new file mode 100644 index 0000000..c872604 --- /dev/null +++ b/src/shared/ui/page-title.tsx @@ -0,0 +1,13 @@ +export function PageTitle({ code, title, text }: { code: string; title: string; text: string }) { + return ( +
+
+
{code}
+
+

{title}

+

{text}

+
+
+
+ ); +} diff --git a/src/widgets/contact-page.tsx b/src/widgets/contact-page.tsx new file mode 100644 index 0000000..323906d --- /dev/null +++ b/src/widgets/contact-page.tsx @@ -0,0 +1,31 @@ +import { MailIcon, RulerIcon } from "lucide-react"; + +import { ProjectBriefForm } from "@/features/project-brief/ui/project-brief-form"; +import { site } from "@/entities/site-content"; +import { PageTitle } from "@/shared/ui/page-title"; +import { SiteShell } from "@/widgets/site-shell"; + +export function ContactPage() { + return ( + + +
+
+
+
+ +
{site.email}
+

Ответим с вопросами по объекту и предложим формат первой консультации.

+
+
+ +
Что приложить
+

План БТИ, фото объекта, референсы, примерный бюджет и желаемый срок запуска стройки.

+
+
+ +
+
+
+ ); +} diff --git a/src/widgets/home-page.tsx b/src/widgets/home-page.tsx new file mode 100644 index 0000000..7e4288e --- /dev/null +++ b/src/widgets/home-page.tsx @@ -0,0 +1,64 @@ +import Link from "next/link"; +import { ArrowUpRightIcon } from "lucide-react"; + +import { projects, site } from "@/entities/site-content"; +import { Badge } from "@/shared/ui/badge"; +import { SiteShell } from "@/widgets/site-shell"; +import { ProjectImage } from "@/widgets/project-image"; + +export function HomePage() { + const featured = projects[0]; + return ( + +
+
+
+
+
+ {site.descriptor} + {site.city} +
+

+ Жесткая форма для живой среды +

+
+

+ Проектируем дома, интерьеры и рабочие пространства без декора ради декора: сценарии, свет, материалы и контроль стройки. +

+
+
+
+
+
+ +
+
+
+
Featured / {featured.year}
+

{featured.title}

+

{featured.summary}

+
+
+
{featured.type}
+
{featured.location}
+ Смотреть кейс +
+
+
+
+
+ {projects.map((project) => ( + +
+ {project.index} + +
+

{project.title}

+

{project.type} / {project.location}

+ + ))} +
+
+
+ ); +} diff --git a/src/widgets/process-page.tsx b/src/widgets/process-page.tsx new file mode 100644 index 0000000..ddac11d --- /dev/null +++ b/src/widgets/process-page.tsx @@ -0,0 +1,22 @@ +import { processSteps } from "@/entities/site-content"; +import { PageTitle } from "@/shared/ui/page-title"; +import { SiteShell } from "@/widgets/site-shell"; + +export function ProcessPage() { + return ( + + +
+
+ {processSteps.map((item) => ( +
+
{item.step}
+

{item.title}

+

{item.text}

+
+ ))} +
+
+
+ ); +} diff --git a/src/widgets/project-detail-page.tsx b/src/widgets/project-detail-page.tsx new file mode 100644 index 0000000..4579a62 --- /dev/null +++ b/src/widgets/project-detail-page.tsx @@ -0,0 +1,35 @@ +import { projects } from "@/entities/site-content"; +import { SiteShell } from "@/widgets/site-shell"; +import { ProjectImage } from "@/widgets/project-image"; + +export function ProjectDetailPage() { + const project = projects[0]; + return ( + +
+
+
+
{project.index} / {project.location}
+

{project.title}

+

{project.summary}

+
+
+ {project.type}{project.status}{project.year} +
+
+
+
+
+
+ {["Двор как приватная комната", "Галерея вместо коридора", "Материалы стареют достойно"].map((item, index) => ( +
+
0{index + 1}
+

{item}

+

Решение зафиксировано в планировке, узлах и сценариях света, чтобы стройка не превращала проект в набор компромиссов.

+
+ ))} +
+
+
+ ); +} diff --git a/src/widgets/project-image.tsx b/src/widgets/project-image.tsx new file mode 100644 index 0000000..c29d5f5 --- /dev/null +++ b/src/widgets/project-image.tsx @@ -0,0 +1,12 @@ +import Image from "next/image"; + +import { projects } from "@/entities/site-content"; + +export function ProjectImage({ project, priority = false }: { project: (typeof projects)[number]; priority?: boolean }) { + return ( +
+ {project.title} +
{project.index}
+
+ ); +} diff --git a/src/widgets/services-page.tsx b/src/widgets/services-page.tsx new file mode 100644 index 0000000..33dab7e --- /dev/null +++ b/src/widgets/services-page.tsx @@ -0,0 +1,24 @@ +import { DraftingCompassIcon } from "lucide-react"; + +import { services } from "@/entities/site-content"; +import { PageTitle } from "@/shared/ui/page-title"; +import { SiteShell } from "@/widgets/site-shell"; + +export function ServicesPage() { + return ( + + +
+
+ {services.map((service) => ( +
+ +

{service.title}

+

{service.text}

+
+ ))} +
+
+
+ ); +} diff --git a/src/widgets/site-shell.tsx b/src/widgets/site-shell.tsx new file mode 100644 index 0000000..95542ab --- /dev/null +++ b/src/widgets/site-shell.tsx @@ -0,0 +1,30 @@ +import type { ReactNode } from "react"; +import Link from "next/link"; + +import { navItems, site } from "@/entities/site-content"; +import { Button } from "@/shared/ui/button"; + +export function SiteShell({ children }: { children: ReactNode }) { + return ( +
+
+
+ + {site.name} + + + +
+
+ {children} +
+ ); +} diff --git a/src/widgets/studio-page.tsx b/src/widgets/studio-page.tsx new file mode 100644 index 0000000..4eec3ca --- /dev/null +++ b/src/widgets/studio-page.tsx @@ -0,0 +1,35 @@ +import Image from "next/image"; + +import { press, studioFacts } from "@/entities/site-content"; +import { Badge } from "@/shared/ui/badge"; +import { PageTitle } from "@/shared/ui/page-title"; +import { SiteShell } from "@/widgets/site-shell"; + +export function StudioPage() { + return ( + + +
+
+
+ Студия Lineform +
+
+
+
+ {studioFacts.map((fact) => ( +
+
{fact.value}
+

{fact.label}

+
+ ))} +
+
+

Публикации и премии

+
{press.map((item) => {item})}
+
+
+
+
+ ); +} diff --git a/src/widgets/template-ui.tsx b/src/widgets/template-ui.tsx deleted file mode 100644 index 7990dcd..0000000 --- a/src/widgets/template-ui.tsx +++ /dev/null @@ -1,257 +0,0 @@ -import Image from "next/image"; -import Link from "next/link"; -import { ArrowUpRightIcon, DraftingCompassIcon, MailIcon, RulerIcon } from "lucide-react"; - -import { ProjectBriefForm } from "@/features/project-brief/ui/project-brief-form"; -import { Badge } from "@/shared/ui/badge"; -import { Button } from "@/shared/ui/button"; -import { navItems, press, processSteps, projects, services, site, studioFacts } from "@/entities/site-content"; - -function Shell({ children }: { children: React.ReactNode }) { - return ( -
-
-
- - {site.name} - - - -
-
- {children} -
- ); -} - -function PageTitle({ code, title, text }: { code: string; title: string; text: string }) { - return ( -
-
-
{code}
-
-

{title}

-

{text}

-
-
-
- ); -} - -function ProjectImage({ project, priority = false }: { project: (typeof projects)[number]; priority?: boolean }) { - return ( -
- {project.title} -
{project.index}
-
- ); -} - -export function HomePage() { - const featured = projects[0]; - return ( - -
-
-
-
-
- {site.descriptor} - {site.city} -
-

- Жесткая форма для живой среды -

-
-

- Проектируем дома, интерьеры и рабочие пространства без декора ради декора: сценарии, свет, материалы и контроль стройки. -

-
-
-
-
-
- -
-
-
-
Featured / {featured.year}
-

{featured.title}

-

{featured.summary}

-
-
-
{featured.type}
-
{featured.location}
- Смотреть кейс -
-
-
-
-
- {projects.map((project) => ( - -
- {project.index} - -
-

{project.title}

-

{project.type} / {project.location}

- - ))} -
-
-
- ); -} - -export function WorkPage() { - return ( - - -
-
- {projects.map((project) => ( - - {project.index} - {project.title} - {project.type} - {project.year} - - ))} -
-
-
- ); -} - -export function ProjectDetailPage() { - const project = projects[0]; - return ( - -
-
-
-
{project.index} / {project.location}
-

{project.title}

-

{project.summary}

-
-
- {project.type}{project.status}{project.year} -
-
-
-
-
-
- {["Двор как приватная комната", "Галерея вместо коридора", "Материалы стареют достойно"].map((item, index) => ( -
-
0{index + 1}
-

{item}

-

Решение зафиксировано в планировке, узлах и сценариях света, чтобы стройка не превращала проект в набор компромиссов.

-
- ))} -
-
-
- ); -} - -export function ServicesPage() { - return ( - - -
-
- {services.map((service) => ( -
- -

{service.title}

-

{service.text}

-
- ))} -
-
-
- ); -} - -export function ProcessPage() { - return ( - - -
-
- {processSteps.map((item) => ( -
-
{item.step}
-

{item.title}

-

{item.text}

-
- ))} -
-
-
- ); -} - -export function StudioPage() { - return ( - - -
-
-
- Студия Lineform -
-
-
-
- {studioFacts.map((fact) => ( -
-
{fact.value}
-

{fact.label}

-
- ))} -
-
-

Публикации и премии

-
{press.map((item) => {item})}
-
-
-
-
- ); -} - -export function ContactPage() { - return ( - - -
-
-
-
- -
{site.email}
-

Ответим с вопросами по объекту и предложим формат первой консультации.

-
-
- -
Что приложить
-

План БТИ, фото объекта, референсы, примерный бюджет и желаемый срок запуска стройки.

-
-
- -
-
-
- ); -} diff --git a/src/widgets/work-page.tsx b/src/widgets/work-page.tsx new file mode 100644 index 0000000..6ad0a2a --- /dev/null +++ b/src/widgets/work-page.tsx @@ -0,0 +1,25 @@ +import Link from "next/link"; + +import { projects } from "@/entities/site-content"; +import { PageTitle } from "@/shared/ui/page-title"; +import { SiteShell } from "@/widgets/site-shell"; + +export function WorkPage() { + return ( + + +
+
+ {projects.map((project) => ( + + {project.index} + {project.title} + {project.type} + {project.year} + + ))} +
+
+
+ ); +}