From b5e7cbd572f2f83c599e6a965f4b0a1f0d32a05c Mon Sep 17 00:00:00 2001 From: StanisLove Date: Thu, 18 Jun 2026 23:15:19 +0300 Subject: [PATCH] feat: split big file and update agents.md --- AGENTS.md | 49 +- src/app/admissions/page.tsx | 2 +- src/app/community/page.tsx | 2 +- src/app/outcomes/page.tsx | 2 +- src/app/page.tsx | 2 +- src/app/programs/page.tsx | 2 +- src/app/programs/product-management/page.tsx | 2 +- src/app/schedule/page.tsx | 2 +- src/shared/ui/loud-title.tsx | 13 + src/widgets/admissions-page.tsx | 29 ++ src/widgets/community-page.tsx | 54 +++ src/widgets/home-page.tsx | 136 ++++++ src/widgets/outcomes-page.tsx | 37 ++ src/widgets/program-card.tsx | 40 ++ src/widgets/program-detail-page.tsx | 32 ++ src/widgets/programs-page.tsx | 45 ++ src/widgets/schedule-page.tsx | 34 ++ src/widgets/site-shell.tsx | 43 ++ src/widgets/student-work-card.tsx | 18 + src/widgets/template-ui.tsx | 457 ------------------- 20 files changed, 536 insertions(+), 465 deletions(-) create mode 100644 src/shared/ui/loud-title.tsx create mode 100644 src/widgets/admissions-page.tsx create mode 100644 src/widgets/community-page.tsx create mode 100644 src/widgets/home-page.tsx create mode 100644 src/widgets/outcomes-page.tsx create mode 100644 src/widgets/program-card.tsx create mode 100644 src/widgets/program-detail-page.tsx create mode 100644 src/widgets/programs-page.tsx create mode 100644 src/widgets/schedule-page.tsx create mode 100644 src/widgets/site-shell.tsx create mode 100644 src/widgets/student-work-card.tsx delete mode 100644 src/widgets/template-ui.tsx diff --git a/AGENTS.md b/AGENTS.md index 09288d7..ef7b069 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,8 +5,55 @@ Orbit Academy — cohort-based education шаблон: сохраняй neo-brut ## Project Specifics - Программы, модули, расписание, outcomes, student work, mentors, admissions и community лежат в `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. - Mock-интеракции поступления держи в `src/features/*/ui`; не добавляй реальные формы, auth, LMS или платежи без запроса. - Не превращай страницы в обычный SaaS-лендинг или курс с лекциями: каждая страница должна показывать образовательный процесс, артефакт, поток, critique или результат. - Громкий brutalist стиль должен поддерживать доверие: добавляй proof, менторов, работы студентов и конкретные deliverables, а не декоративный шум. - Проверка после правок: `pnpm lint` и `pnpm build`. + +## Design System + +Источник токенов — `src/app/globals.css` (`@theme` + `:root`/`.dark`). Шрифт — Inter (`--font-orbit`), один гарнитур на sans и mono. Работай через семантические классы Tailwind (`bg-primary`, `text-foreground`, `border-foreground`), не хардкодь hex/oklch. + +Личность: **neo-brutalist «cream / ink / acid»** — тёплый кремовый фон, near-black «ink» как граница и текст, кислотно-зелёный secondary как сигнальный акцент, глубокий indigo primary, оранжевый accent для редких всплесков. + +| Роль | Light | Характер | +|---|---|---| +| `background` | тёплый кремовый | основной фон страниц | +| `foreground` | near-black ink | текст + **границы** (`border-2 border-foreground`) | +| `primary` | глубокий indigo | заголовочные плашки, активные строки | +| `secondary` | кислотный лайм | бейджи, сигнальные акценты, hover | +| `accent` | оранжевый | редкие точечные всплески | +| `card` | почти белый | карточки на кремовом фоне | + +Узнаваемые приёмы (держи их, это и есть «лицо» проекта): +- **Жёсткие углы:** `--radius` = 0.125rem; большинство элементов — `rounded-none`. +- **Толстые границы:** `border-2 border-foreground` повсюду; это структура, а не декор. +- **Hard offset shadow:** `shadow-[8px_8px_0_var(--foreground)]` (карточки, hover усиливает до `12px`). +- **Громкая типографика:** `font-black uppercase`, очень плотный `leading` (`leading-none`/`leading-[0.9]`), крупные размеры (до `text-8xl`). +- **Утилитарные классы:** `.orbit-board` — сетка-миллиметровка для «board»-секций; `.marker-highlight` — маркерное подчёркивание лаймом. + +Do / Don't: +- **Do:** расширяй существующий язык — сетки, плашки, board-эстетику; держи контент-first (артефакт, proof, метрика). +- **Don't:** мягкие тени, скруглённые карточки, пастель, generic-SaaS hero с градиентом — это ломает личность шаблона. + +## File Map + +| Route | Widget | +|---|---| +| `/` | `src/widgets/home-page.tsx` (`HomePage`) | +| `/programs` | `src/widgets/programs-page.tsx` (`ProgramsPage`) | +| `/programs/product-management` | `src/widgets/program-detail-page.tsx` (`ProgramDetailPage`) | +| `/schedule` | `src/widgets/schedule-page.tsx` (`SchedulePage`) | +| `/outcomes` | `src/widgets/outcomes-page.tsx` (`OutcomesPage`) | +| `/admissions` | `src/widgets/admissions-page.tsx` (`AdmissionsPage`) | +| `/community` | `src/widgets/community-page.tsx` (`CommunityPage`) | + +Переиспользуемые блоки: +- `src/widgets/site-shell.tsx` — `SiteShell` (header + nav + footer, обёртка всех страниц). +- `src/widgets/program-card.tsx` — `ProgramCard` (Home, Programs). +- `src/widgets/student-work-card.tsx` — `StudentWorkCard` (Home, Outcomes). +- `src/shared/ui/loud-title.tsx` — `LoudTitle` (заголовочная секция внутренних страниц). +- `src/features/admission-board/ui/admission-board.tsx` — mock admissions board. + +Одноразовые блоки колоцированы со своей страницей (напр. `HeroStudio`/`OutcomeStrip` в `home-page.tsx`, `MentorRoster` в `programs-page.tsx`). diff --git a/src/app/admissions/page.tsx b/src/app/admissions/page.tsx index deea4da..a13388e 100644 --- a/src/app/admissions/page.tsx +++ b/src/app/admissions/page.tsx @@ -1,4 +1,4 @@ -import { AdmissionsPage } from "@/widgets/template-ui"; +import { AdmissionsPage } from "@/widgets/admissions-page"; export default function Page() { return ; diff --git a/src/app/community/page.tsx b/src/app/community/page.tsx index 98be947..b2f3dd1 100644 --- a/src/app/community/page.tsx +++ b/src/app/community/page.tsx @@ -1,4 +1,4 @@ -import { CommunityPage } from "@/widgets/template-ui"; +import { CommunityPage } from "@/widgets/community-page"; export default function Page() { return ; diff --git a/src/app/outcomes/page.tsx b/src/app/outcomes/page.tsx index d95631d..e37f49e 100644 --- a/src/app/outcomes/page.tsx +++ b/src/app/outcomes/page.tsx @@ -1,4 +1,4 @@ -import { OutcomesPage } from "@/widgets/template-ui"; +import { OutcomesPage } from "@/widgets/outcomes-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/programs/page.tsx b/src/app/programs/page.tsx index 437baf1..45250ad 100644 --- a/src/app/programs/page.tsx +++ b/src/app/programs/page.tsx @@ -1,4 +1,4 @@ -import { ProgramsPage } from "@/widgets/template-ui"; +import { ProgramsPage } from "@/widgets/programs-page"; export default function Page() { return ; diff --git a/src/app/programs/product-management/page.tsx b/src/app/programs/product-management/page.tsx index e38d052..d6d7297 100644 --- a/src/app/programs/product-management/page.tsx +++ b/src/app/programs/product-management/page.tsx @@ -1,4 +1,4 @@ -import { ProgramDetailPage } from "@/widgets/template-ui"; +import { ProgramDetailPage } from "@/widgets/program-detail-page"; export default function Page() { return ; diff --git a/src/app/schedule/page.tsx b/src/app/schedule/page.tsx index ba6c3f8..8a975b8 100644 --- a/src/app/schedule/page.tsx +++ b/src/app/schedule/page.tsx @@ -1,4 +1,4 @@ -import { SchedulePage } from "@/widgets/template-ui"; +import { SchedulePage } from "@/widgets/schedule-page"; export default function Page() { return ; diff --git a/src/shared/ui/loud-title.tsx b/src/shared/ui/loud-title.tsx new file mode 100644 index 0000000..c853ae1 --- /dev/null +++ b/src/shared/ui/loud-title.tsx @@ -0,0 +1,13 @@ +import { Badge } from "@/shared/ui/badge"; + +export function LoudTitle({ label, title, text }: { label: string; title: string; text: string }) { + return ( +
+
+ {label} +

{title}

+

{text}

+
+
+ ); +} diff --git a/src/widgets/admissions-page.tsx b/src/widgets/admissions-page.tsx new file mode 100644 index 0000000..f869503 --- /dev/null +++ b/src/widgets/admissions-page.tsx @@ -0,0 +1,29 @@ +import { AdmissionBoard } from "@/features/admission-board/ui/admission-board"; +import { admissions } from "@/entities/site-content"; +import { LoudTitle } from "@/shared/ui/loud-title"; +import { SiteShell } from "@/widgets/site-shell"; + +export function AdmissionsPage() { + return ( + + +
+
+
+ {admissions.map((item, index) => ( +
+
0{index + 1}
+

{item}

+
+ ))} +
+ +
+
+
+ ); +} diff --git a/src/widgets/community-page.tsx b/src/widgets/community-page.tsx new file mode 100644 index 0000000..d144df1 --- /dev/null +++ b/src/widgets/community-page.tsx @@ -0,0 +1,54 @@ +import { MessageSquareTextIcon, PlaySquareIcon, Rows3Icon } from "lucide-react"; + +import { communityNotes } from "@/entities/site-content"; +import { LoudTitle } from "@/shared/ui/loud-title"; +import { SiteShell } from "@/widgets/site-shell"; + +export function CommunityPage() { + return ( + + +
+
+ {communityNotes.map((note) => ( +
+
+ +

{note.title}

+

{note.text}

+
+
{note.cadence}
+
+ ))} +
+
+
+
+
+ +

Demo archive

+

Записи защит, шаблоны артефактов и разборы доступны выпускникам после курса.

+
+
+ {[ + ["artifact library", "problem briefs, research boards, metric trees"], + ["hiring rooms", "портфолио-ревью и разбор офферов"], + ["peer circles", "мини-группы по роли и домену"], + ["mentor threads", "асинхронные вопросы после live-сессий"], + ].map(([title, text]) => ( +
+ +

{title}

+

{text}

+
+ ))} +
+
+
+
+ ); +} diff --git a/src/widgets/home-page.tsx b/src/widgets/home-page.tsx new file mode 100644 index 0000000..2b2d8e9 --- /dev/null +++ b/src/widgets/home-page.tsx @@ -0,0 +1,136 @@ +import Image from "next/image"; +import Link from "next/link"; +import { ArrowRightIcon } from "lucide-react"; + +import { heroStats, outcomes, programs, site, studentWork } from "@/entities/site-content"; +import { Badge } from "@/shared/ui/badge"; +import { Button } from "@/shared/ui/button"; +import { ProgramCard } from "@/widgets/program-card"; +import { SiteShell } from "@/widgets/site-shell"; +import { StudentWorkCard } from "@/widgets/student-work-card"; + +function HeroStudio() { + return ( +
+
+ Команда разбирает продуктовые артефакты на воркшопе +
+ critique room +
+
+
+
+
+
week 04 board
+

Metric architecture

+
+ {site.capacity} +
+
+ {[ + ["19:00", "live teardown", "разбор metric tree"], + ["20:10", "peer room", "критика по группам"], + ["21:00", "mentor notes", "правки до пятницы"], + ].map(([time, title, text]) => ( +
+
{time}
+
+
{title}
+
{text}
+
+
+ ))} +
+
+
+ ); +} + +function OutcomeStrip() { + return ( +
+
+ {outcomes.map((item) => ( +
+
{item.value}
+

{item.label}

+
+ ))} +
+
+ ); +} + +export function HomePage() { + return ( + +
+
+
+ + Следующий поток: {site.nextCohort} + +

+ Не лекции. Еженедельный продуктовый разбор. +

+

+ Не лекции. Работа, разбор, demo day. +

+

+ Orbit Academy запускает cohort-based программы, где студенты каждую неделю + приносят рабочий артефакт: problem brief, research board, metric tree или roadmap defense. +

+
+ + +
+
+ {heroStats.map((item) => ( +
+
{item.value}
+
{item.label}
+
+ ))} +
+
+ +
+
+ +
+
+ {programs.map((program, index) => ( + + ))} +
+
+ + + +
+
+
+
работы студентов
+

Proof вместо обещаний

+
+
+ {studentWork.map((work) => ( + + ))} +
+
+
+
+ ); +} diff --git a/src/widgets/outcomes-page.tsx b/src/widgets/outcomes-page.tsx new file mode 100644 index 0000000..f2ef0db --- /dev/null +++ b/src/widgets/outcomes-page.tsx @@ -0,0 +1,37 @@ +import { TrophyIcon } from "lucide-react"; + +import { outcomes, studentWork } from "@/entities/site-content"; +import { LoudTitle } from "@/shared/ui/loud-title"; +import { SiteShell } from "@/widgets/site-shell"; +import { StudentWorkCard } from "@/widgets/student-work-card"; + +export function OutcomesPage() { + return ( + + +
+
+ {outcomes.map((item) => ( +
+ +
{item.value}
+

{item.label}

+

{item.proof}

+
+ ))} +
+
+
+
+ {studentWork.map((work) => ( + + ))} +
+
+
+ ); +} diff --git a/src/widgets/program-card.tsx b/src/widgets/program-card.tsx new file mode 100644 index 0000000..94ba78e --- /dev/null +++ b/src/widgets/program-card.tsx @@ -0,0 +1,40 @@ +import Link from "next/link"; +import { ArrowRightIcon } from "lucide-react"; + +import { programs } from "@/entities/site-content"; + +type Program = (typeof programs)[number]; + +export function ProgramCard({ program, index }: { program: Program; index: number }) { + const href = program.slug === "product-management" ? "/programs/product-management" : "/programs"; + + return ( + +
+ 0{index + 1} + {program.duration} +
+
+

{program.title}

+

{program.signal}

+

{program.outcome}

+
+
+
+ {program.deliverables.slice(0, 3).map((item) => ( + + {item} + + ))} +
+
+ {program.level} / {program.seats} / {program.price} + +
+
+ + ); +} diff --git a/src/widgets/program-detail-page.tsx b/src/widgets/program-detail-page.tsx new file mode 100644 index 0000000..774dc85 --- /dev/null +++ b/src/widgets/program-detail-page.tsx @@ -0,0 +1,32 @@ +import { modules, programs } from "@/entities/site-content"; +import { LoudTitle } from "@/shared/ui/loud-title"; +import { SiteShell } from "@/widgets/site-shell"; + +export function ProgramDetailPage() { + const program = programs[0]; + + return ( + + +
+
+ {modules.map((module) => ( +
+
{module.week}
+
+

{module.title}

+
{module.critique}
+
+

{module.task}

+
{module.artifact}
+
+ ))} +
+
+
+ ); +} diff --git a/src/widgets/programs-page.tsx b/src/widgets/programs-page.tsx new file mode 100644 index 0000000..daf285f --- /dev/null +++ b/src/widgets/programs-page.tsx @@ -0,0 +1,45 @@ +import { UsersIcon } from "lucide-react"; + +import { mentors, programs } from "@/entities/site-content"; +import { LoudTitle } from "@/shared/ui/loud-title"; +import { ProgramCard } from "@/widgets/program-card"; +import { SiteShell } from "@/widgets/site-shell"; + +function MentorRoster() { + return ( +
+ {mentors.map((mentor) => ( +
+ +

{mentor.name}

+

{mentor.role}

+

{mentor.focus}

+
+ ))} +
+ ); +} + +export function ProgramsPage() { + return ( + + +
+
+ {programs.map((program, index) => ( + + ))} +
+
+
+
+ +
+
+
+ ); +} diff --git a/src/widgets/schedule-page.tsx b/src/widgets/schedule-page.tsx new file mode 100644 index 0000000..7b469ae --- /dev/null +++ b/src/widgets/schedule-page.tsx @@ -0,0 +1,34 @@ +import { CalendarDaysIcon } from "lucide-react"; + +import { schedule } from "@/entities/site-content"; +import { Badge } from "@/shared/ui/badge"; +import { LoudTitle } from "@/shared/ui/loud-title"; +import { SiteShell } from "@/widgets/site-shell"; + +export function SchedulePage() { + return ( + + +
+
+ {schedule.map((item) => ( +
+
+ + {item.date} +
+
{item.program}
+
{item.mode}
+
{item.intensity}
+ {item.status} +
+ ))} +
+
+
+ ); +} diff --git a/src/widgets/site-shell.tsx b/src/widgets/site-shell.tsx new file mode 100644 index 0000000..c194130 --- /dev/null +++ b/src/widgets/site-shell.tsx @@ -0,0 +1,43 @@ +import type { ReactNode } from "react"; +import Link from "next/link"; +import { RocketIcon } from "lucide-react"; + +import { navItems, site } from "@/entities/site-content"; +import { Button } from "@/shared/ui/button"; + +export function SiteShell({ children }: { children: ReactNode }) { + return ( +
+
+
+ + + + + {site.name} + + + +
+
+ {children} +
+
+
+
{site.name}
+
{site.tagline}
+
+
{site.email}
+
+
+
+ ); +} diff --git a/src/widgets/student-work-card.tsx b/src/widgets/student-work-card.tsx new file mode 100644 index 0000000..c8c4822 --- /dev/null +++ b/src/widgets/student-work-card.tsx @@ -0,0 +1,18 @@ +import Image from "next/image"; + +import { studentWork } from "@/entities/site-content"; + +export function StudentWorkCard({ work }: { work: (typeof studentWork)[number] }) { + return ( +
+
+ {work.title} +
+
+
{work.role}
+

{work.title}

+
{work.result}
+
+
+ ); +} diff --git a/src/widgets/template-ui.tsx b/src/widgets/template-ui.tsx deleted file mode 100644 index 9277b42..0000000 --- a/src/widgets/template-ui.tsx +++ /dev/null @@ -1,457 +0,0 @@ -import type { ReactNode } from "react"; -import Image from "next/image"; -import Link from "next/link"; -import { - ArrowRightIcon, - CalendarDaysIcon, - MessageSquareTextIcon, - PlaySquareIcon, - RocketIcon, - Rows3Icon, - TrophyIcon, - UsersIcon, -} from "lucide-react"; - -import { AdmissionBoard } from "@/features/admission-board/ui/admission-board"; -import { - admissions, - communityNotes, - heroStats, - mentors, - modules, - navItems, - outcomes, - programs, - schedule, - site, - studentWork, -} from "@/entities/site-content"; -import { Badge } from "@/shared/ui/badge"; -import { Button } from "@/shared/ui/button"; - -type Program = (typeof programs)[number]; - -function Shell({ children }: { children: ReactNode }) { - return ( -
-
-
- - - - - {site.name} - - - -
-
- {children} -
-
-
-
{site.name}
-
{site.tagline}
-
-
{site.email}
-
-
-
- ); -} - -function LoudTitle({ label, title, text }: { label: string; title: string; text: string }) { - return ( -
-
- {label} -

{title}

-

{text}

-
-
- ); -} - -function HeroStudio() { - return ( -
-
- Команда разбирает продуктовые артефакты на воркшопе -
- critique room -
-
-
-
-
-
week 04 board
-

Metric architecture

-
- {site.capacity} -
-
- {[ - ["19:00", "live teardown", "разбор metric tree"], - ["20:10", "peer room", "критика по группам"], - ["21:00", "mentor notes", "правки до пятницы"], - ].map(([time, title, text]) => ( -
-
{time}
-
-
{title}
-
{text}
-
-
- ))} -
-
-
- ); -} - -function ProgramCard({ program, index }: { program: Program; index: number }) { - const href = program.slug === "product-management" ? "/programs/product-management" : "/programs"; - - return ( - -
- 0{index + 1} - {program.duration} -
-
-

{program.title}

-

{program.signal}

-

{program.outcome}

-
-
-
- {program.deliverables.slice(0, 3).map((item) => ( - - {item} - - ))} -
-
- {program.level} / {program.seats} / {program.price} - -
-
- - ); -} - -function OutcomeStrip() { - return ( -
-
- {outcomes.map((item) => ( -
-
{item.value}
-

{item.label}

-
- ))} -
-
- ); -} - -function StudentWorkCard({ work }: { work: (typeof studentWork)[number] }) { - return ( -
-
- {work.title} -
-
-
{work.role}
-

{work.title}

-
{work.result}
-
-
- ); -} - -function MentorRoster() { - return ( -
- {mentors.map((mentor) => ( -
- -

{mentor.name}

-

{mentor.role}

-

{mentor.focus}

-
- ))} -
- ); -} - -export function HomePage() { - return ( - -
-
-
- - Следующий поток: {site.nextCohort} - -

- Не лекции. Еженедельный продуктовый разбор. -

-

- Не лекции. Работа, разбор, demo day. -

-

- Orbit Academy запускает cohort-based программы, где студенты каждую неделю - приносят рабочий артефакт: problem brief, research board, metric tree или roadmap defense. -

-
- - -
-
- {heroStats.map((item) => ( -
-
{item.value}
-
{item.label}
-
- ))} -
-
- -
-
- -
-
- {programs.map((program, index) => ( - - ))} -
-
- - - -
-
-
-
работы студентов
-

Proof вместо обещаний

-
-
- {studentWork.map((work) => ( - - ))} -
-
-
-
- ); -} - -export function ProgramsPage() { - return ( - - -
-
- {programs.map((program, index) => ( - - ))} -
-
-
-
- -
-
-
- ); -} - -export function ProgramDetailPage() { - const program = programs[0]; - - return ( - - -
-
- {modules.map((module) => ( -
-
{module.week}
-
-

{module.title}

-
{module.critique}
-
-

{module.task}

-
{module.artifact}
-
- ))} -
-
-
- ); -} - -export function SchedulePage() { - return ( - - -
-
- {schedule.map((item) => ( -
-
- - {item.date} -
-
{item.program}
-
{item.mode}
-
{item.intensity}
- {item.status} -
- ))} -
-
-
- ); -} - -export function OutcomesPage() { - return ( - - -
-
- {outcomes.map((item) => ( -
- -
{item.value}
-

{item.label}

-

{item.proof}

-
- ))} -
-
-
-
- {studentWork.map((work) => ( - - ))} -
-
-
- ); -} - -export function AdmissionsPage() { - return ( - - -
-
-
- {admissions.map((item, index) => ( -
-
0{index + 1}
-

{item}

-
- ))} -
- -
-
-
- ); -} - -export function CommunityPage() { - return ( - - -
-
- {communityNotes.map((note) => ( -
-
- -

{note.title}

-

{note.text}

-
-
{note.cadence}
-
- ))} -
-
-
-
-
- -

Demo archive

-

Записи защит, шаблоны артефактов и разборы доступны выпускникам после курса.

-
-
- {[ - ["artifact library", "problem briefs, research boards, metric trees"], - ["hiring rooms", "портфолио-ревью и разбор офферов"], - ["peer circles", "мини-группы по роли и домену"], - ["mentor threads", "асинхронные вопросы после live-сессий"], - ].map(([title, text]) => ( -
- -

{title}

-

{text}

-
- ))} -
-
-
-
- ); -}