feat: split big file and update agents.md
This commit is contained in:
49
AGENTS.md
49
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>-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`).
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AdmissionsPage } from "@/widgets/template-ui";
|
||||
import { AdmissionsPage } from "@/widgets/admissions-page";
|
||||
|
||||
export default function Page() {
|
||||
return <AdmissionsPage />;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CommunityPage } from "@/widgets/template-ui";
|
||||
import { CommunityPage } from "@/widgets/community-page";
|
||||
|
||||
export default function Page() {
|
||||
return <CommunityPage />;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OutcomesPage } from "@/widgets/template-ui";
|
||||
import { OutcomesPage } from "@/widgets/outcomes-page";
|
||||
|
||||
export default function Page() {
|
||||
return <OutcomesPage />;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { HomePage } from "@/widgets/template-ui";
|
||||
import { HomePage } from "@/widgets/home-page";
|
||||
|
||||
export default function Page() {
|
||||
return <HomePage />;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ProgramsPage } from "@/widgets/template-ui";
|
||||
import { ProgramsPage } from "@/widgets/programs-page";
|
||||
|
||||
export default function Page() {
|
||||
return <ProgramsPage />;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ProgramDetailPage } from "@/widgets/template-ui";
|
||||
import { ProgramDetailPage } from "@/widgets/program-detail-page";
|
||||
|
||||
export default function Page() {
|
||||
return <ProgramDetailPage />;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SchedulePage } from "@/widgets/template-ui";
|
||||
import { SchedulePage } from "@/widgets/schedule-page";
|
||||
|
||||
export default function Page() {
|
||||
return <SchedulePage />;
|
||||
|
||||
13
src/shared/ui/loud-title.tsx
Normal file
13
src/shared/ui/loud-title.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Badge } from "@/shared/ui/badge";
|
||||
|
||||
export function LoudTitle({ label, title, text }: { label: string; title: string; text: string }) {
|
||||
return (
|
||||
<section className="orbit-board border-b-2 border-foreground px-4 py-12 md:px-6 md:py-20">
|
||||
<div className="mx-auto max-w-7xl">
|
||||
<Badge className="mb-5 rounded-none border-2 border-foreground bg-secondary text-foreground">{label}</Badge>
|
||||
<h1 className="max-w-5xl break-words text-5xl font-black uppercase leading-[0.92] md:text-8xl">{title}</h1>
|
||||
<p className="mt-6 max-w-2xl text-lg leading-8 text-muted-foreground">{text}</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
29
src/widgets/admissions-page.tsx
Normal file
29
src/widgets/admissions-page.tsx
Normal file
@@ -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 (
|
||||
<SiteShell>
|
||||
<LoudTitle
|
||||
label="Admissions"
|
||||
title="Поступление без иллюзии элитарности"
|
||||
text="Нужна не мотивационная анкета, а проверка цели, уровня и готовности показывать работу группе каждую неделю."
|
||||
/>
|
||||
<section className="px-4 py-12 md:px-6">
|
||||
<div className="mx-auto grid max-w-7xl gap-8 lg:grid-cols-[1fr_430px]">
|
||||
<div className="grid gap-4">
|
||||
{admissions.map((item, index) => (
|
||||
<div key={item} className="grid gap-4 border-2 border-foreground bg-card p-5 md:grid-cols-[72px_1fr]">
|
||||
<div className="text-4xl font-black text-primary">0{index + 1}</div>
|
||||
<h2 className="text-3xl font-black uppercase leading-none">{item}</h2>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<AdmissionBoard />
|
||||
</div>
|
||||
</section>
|
||||
</SiteShell>
|
||||
);
|
||||
}
|
||||
54
src/widgets/community-page.tsx
Normal file
54
src/widgets/community-page.tsx
Normal file
@@ -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 (
|
||||
<SiteShell>
|
||||
<LoudTitle
|
||||
label="Community"
|
||||
title="Комьюнити работает после последней защиты"
|
||||
text="Сильная онлайн-школа продает не только программу, но и среду, где продолжают показывать работу и получать обратную связь."
|
||||
/>
|
||||
<section className="px-4 py-12 md:px-6">
|
||||
<div className="mx-auto grid max-w-7xl gap-6 md:grid-cols-3">
|
||||
{communityNotes.map((note) => (
|
||||
<article key={note.title} className="flex min-h-80 flex-col justify-between border-2 border-foreground bg-card p-6">
|
||||
<div>
|
||||
<MessageSquareTextIcon className="mb-16 size-8 text-primary" />
|
||||
<h2 className="text-3xl font-black uppercase leading-none">{note.title}</h2>
|
||||
<p className="mt-4 leading-7 text-muted-foreground">{note.text}</p>
|
||||
</div>
|
||||
<div className="mt-8 border-t-2 border-foreground pt-4 text-sm font-black uppercase">{note.cadence}</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
<section className="px-4 pb-12 md:px-6">
|
||||
<div className="mx-auto grid max-w-7xl gap-6 lg:grid-cols-[0.8fr_1.2fr]">
|
||||
<div className="border-2 border-foreground bg-secondary p-6">
|
||||
<PlaySquareIcon className="mb-10 size-8" />
|
||||
<h2 className="text-4xl font-black uppercase leading-none">Demo archive</h2>
|
||||
<p className="mt-4 leading-7">Записи защит, шаблоны артефактов и разборы доступны выпускникам после курса.</p>
|
||||
</div>
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
{[
|
||||
["artifact library", "problem briefs, research boards, metric trees"],
|
||||
["hiring rooms", "портфолио-ревью и разбор офферов"],
|
||||
["peer circles", "мини-группы по роли и домену"],
|
||||
["mentor threads", "асинхронные вопросы после live-сессий"],
|
||||
].map(([title, text]) => (
|
||||
<div key={title} className="border-2 border-foreground bg-card p-5">
|
||||
<Rows3Icon className="mb-8 size-7 text-primary" />
|
||||
<h3 className="text-2xl font-black uppercase">{title}</h3>
|
||||
<p className="mt-3 text-sm leading-6 text-muted-foreground">{text}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</SiteShell>
|
||||
);
|
||||
}
|
||||
136
src/widgets/home-page.tsx
Normal file
136
src/widgets/home-page.tsx
Normal file
@@ -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 (
|
||||
<div className="overflow-hidden border-2 border-foreground bg-card shadow-[8px_8px_0_var(--foreground)]">
|
||||
<div className="relative h-72 border-b-2 border-foreground">
|
||||
<Image
|
||||
src="https://images.unsplash.com/photo-1552664730-d307ca884978?auto=format&fit=crop&w=1400&q=82"
|
||||
alt="Команда разбирает продуктовые артефакты на воркшопе"
|
||||
fill
|
||||
priority
|
||||
className="object-cover"
|
||||
sizes="(min-width: 1024px) 38vw, 100vw"
|
||||
/>
|
||||
<div className="absolute left-4 top-4 bg-background px-3 py-2 text-xs font-black uppercase">
|
||||
critique room
|
||||
</div>
|
||||
</div>
|
||||
<div className="orbit-board p-5">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<div className="text-xs font-black uppercase text-muted-foreground">week 04 board</div>
|
||||
<h2 className="mt-1 text-3xl font-black uppercase leading-none">Metric architecture</h2>
|
||||
</div>
|
||||
<Badge className="rounded-none bg-secondary text-foreground">{site.capacity}</Badge>
|
||||
</div>
|
||||
<div className="mt-5 grid gap-3">
|
||||
{[
|
||||
["19:00", "live teardown", "разбор metric tree"],
|
||||
["20:10", "peer room", "критика по группам"],
|
||||
["21:00", "mentor notes", "правки до пятницы"],
|
||||
].map(([time, title, text]) => (
|
||||
<div key={time} className="grid grid-cols-[64px_1fr] gap-3 border-2 border-foreground bg-background p-3">
|
||||
<div className="font-black text-primary">{time}</div>
|
||||
<div>
|
||||
<div className="font-black uppercase">{title}</div>
|
||||
<div className="text-sm text-muted-foreground">{text}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function OutcomeStrip() {
|
||||
return (
|
||||
<section className="border-y-2 border-foreground bg-primary px-4 py-10 text-primary-foreground md:px-6">
|
||||
<div className="mx-auto grid max-w-7xl gap-6 md:grid-cols-4">
|
||||
{outcomes.map((item) => (
|
||||
<div key={item.value}>
|
||||
<div className="text-5xl font-black">{item.value}</div>
|
||||
<p className="mt-2 max-w-sm text-sm font-bold opacity-80">{item.label}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export function HomePage() {
|
||||
return (
|
||||
<SiteShell>
|
||||
<section className="orbit-board px-4 py-10 md:px-6 md:py-14">
|
||||
<div className="mx-auto grid max-w-7xl gap-8 lg:grid-cols-[0.95fr_1.05fr] lg:items-center">
|
||||
<div>
|
||||
<Badge className="mb-5 rounded-none border-2 border-foreground bg-secondary text-foreground">
|
||||
Следующий поток: {site.nextCohort}
|
||||
</Badge>
|
||||
<h1 className="hidden text-5xl font-black uppercase leading-[0.86] md:block md:text-8xl">
|
||||
Не лекции. Еженедельный продуктовый разбор.
|
||||
</h1>
|
||||
<h1 className="text-[2.65rem] font-black uppercase leading-[0.9] md:hidden">
|
||||
Не лекции. Работа, разбор, demo day.
|
||||
</h1>
|
||||
<p className="mt-6 max-w-2xl text-lg leading-8 text-muted-foreground">
|
||||
Orbit Academy запускает cohort-based программы, где студенты каждую неделю
|
||||
приносят рабочий артефакт: problem brief, research board, metric tree или roadmap defense.
|
||||
</p>
|
||||
<div className="mt-7 flex flex-wrap gap-3">
|
||||
<Button asChild className="rounded-none" size="lg">
|
||||
<Link href="/programs">Выбрать программу <ArrowRightIcon className="size-4" /></Link>
|
||||
</Button>
|
||||
<Button asChild variant="outline" className="rounded-none border-2" size="lg">
|
||||
<Link href="/schedule">Расписание</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-8 grid gap-3 md:grid-cols-3">
|
||||
{heroStats.map((item) => (
|
||||
<div key={item.value} className="border-2 border-foreground bg-card p-3">
|
||||
<div className="text-2xl font-black">{item.value}</div>
|
||||
<div className="mt-1 text-xs font-bold text-muted-foreground">{item.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<HeroStudio />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="px-4 py-12 md:px-6">
|
||||
<div className="mx-auto grid max-w-7xl gap-6 md:grid-cols-3">
|
||||
{programs.map((program, index) => (
|
||||
<ProgramCard key={program.title} program={program} index={index} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<OutcomeStrip />
|
||||
|
||||
<section className="px-4 py-12 md:px-6">
|
||||
<div className="mx-auto grid max-w-7xl gap-6 lg:grid-cols-[0.7fr_1.3fr]">
|
||||
<div>
|
||||
<div className="marker-highlight w-fit text-sm font-black uppercase">работы студентов</div>
|
||||
<h2 className="mt-5 text-4xl font-black uppercase leading-none md:text-6xl">Proof вместо обещаний</h2>
|
||||
</div>
|
||||
<div className="grid gap-5 md:grid-cols-3">
|
||||
{studentWork.map((work) => (
|
||||
<StudentWorkCard key={work.title} work={work} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</SiteShell>
|
||||
);
|
||||
}
|
||||
37
src/widgets/outcomes-page.tsx
Normal file
37
src/widgets/outcomes-page.tsx
Normal file
@@ -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 (
|
||||
<SiteShell>
|
||||
<LoudTitle
|
||||
label="Outcomes"
|
||||
title="Результаты показываются через работу, а не обещания"
|
||||
text="Шаблон отделяет образовательный маркетинг от реальных outcome-блоков: кейсы, метрики, демо и роли выпускников."
|
||||
/>
|
||||
<section className="px-4 py-12 md:px-6">
|
||||
<div className="mx-auto grid max-w-7xl gap-6 md:grid-cols-2 lg:grid-cols-4">
|
||||
{outcomes.map((item) => (
|
||||
<article key={item.value} className="border-2 border-foreground bg-card p-6 shadow-[8px_8px_0_var(--foreground)]">
|
||||
<TrophyIcon className="mb-10 size-8 text-primary" />
|
||||
<div className="text-5xl font-black">{item.value}</div>
|
||||
<p className="mt-4 font-bold leading-7">{item.label}</p>
|
||||
<p className="mt-3 text-sm text-muted-foreground">{item.proof}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
<section className="px-4 pb-12 md:px-6">
|
||||
<div className="mx-auto grid max-w-7xl gap-5 md:grid-cols-3">
|
||||
{studentWork.map((work) => (
|
||||
<StudentWorkCard key={work.title} work={work} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</SiteShell>
|
||||
);
|
||||
}
|
||||
40
src/widgets/program-card.tsx
Normal file
40
src/widgets/program-card.tsx
Normal file
@@ -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 (
|
||||
<Link
|
||||
href={href}
|
||||
className="group flex min-h-[360px] flex-col justify-between border-2 border-foreground bg-card p-5 shadow-[8px_8px_0_var(--foreground)] transition hover:-translate-y-1 hover:shadow-[12px_12px_0_var(--foreground)]"
|
||||
>
|
||||
<div className="flex justify-between gap-4 text-sm font-black uppercase">
|
||||
<span>0{index + 1}</span>
|
||||
<span>{program.duration}</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-4xl font-black uppercase leading-none">{program.title}</h3>
|
||||
<p className="mt-4 font-bold leading-7">{program.signal}</p>
|
||||
<p className="mt-3 leading-7 text-muted-foreground">{program.outcome}</p>
|
||||
</div>
|
||||
<div>
|
||||
<div className="mb-4 flex flex-wrap gap-2">
|
||||
{program.deliverables.slice(0, 3).map((item) => (
|
||||
<span key={item} className="border-2 border-foreground bg-secondary px-2 py-1 text-xs font-black uppercase">
|
||||
{item}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center justify-between border-t-2 border-foreground pt-4 text-sm font-bold">
|
||||
<span>{program.level} / {program.seats} / {program.price}</span>
|
||||
<ArrowRightIcon className="size-5 transition group-hover:translate-x-1" />
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
32
src/widgets/program-detail-page.tsx
Normal file
32
src/widgets/program-detail-page.tsx
Normal file
@@ -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 (
|
||||
<SiteShell>
|
||||
<LoudTitle
|
||||
label="Product Management"
|
||||
title="От проблемы к roadmap, который можно защищать"
|
||||
text={`${program.duration}, ${program.format}. Итог - ${program.outcome}.`}
|
||||
/>
|
||||
<section className="px-4 py-12 md:px-6">
|
||||
<div className="mx-auto grid max-w-7xl gap-5">
|
||||
{modules.map((module) => (
|
||||
<article key={module.week} className="grid gap-4 border-2 border-foreground bg-card p-5 md:grid-cols-[100px_0.75fr_1fr_0.8fr] md:items-start">
|
||||
<div className="text-5xl font-black">{module.week}</div>
|
||||
<div>
|
||||
<h2 className="text-3xl font-black uppercase leading-none">{module.title}</h2>
|
||||
<div className="mt-3 text-sm font-black text-primary">{module.critique}</div>
|
||||
</div>
|
||||
<p className="leading-7 text-muted-foreground">{module.task}</p>
|
||||
<div className="border-2 border-foreground bg-secondary p-3 text-sm font-black">{module.artifact}</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</SiteShell>
|
||||
);
|
||||
}
|
||||
45
src/widgets/programs-page.tsx
Normal file
45
src/widgets/programs-page.tsx
Normal file
@@ -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 (
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
{mentors.map((mentor) => (
|
||||
<article key={mentor.name} className="border-2 border-foreground bg-card p-5">
|
||||
<UsersIcon className="mb-10 size-7 text-primary" />
|
||||
<h3 className="text-2xl font-black uppercase leading-none">{mentor.name}</h3>
|
||||
<p className="mt-3 font-bold">{mentor.role}</p>
|
||||
<p className="mt-2 text-sm text-muted-foreground">{mentor.focus}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ProgramsPage() {
|
||||
return (
|
||||
<SiteShell>
|
||||
<LoudTitle
|
||||
label="Programs"
|
||||
title="Три программы для разных точек роста"
|
||||
text="Каждая программа имеет outcome, расписание, формат обратной связи, deliverables и ограниченное количество мест в группе."
|
||||
/>
|
||||
<section className="px-4 py-12 md:px-6">
|
||||
<div className="mx-auto grid max-w-7xl gap-6 md:grid-cols-3">
|
||||
{programs.map((program, index) => (
|
||||
<ProgramCard key={program.title} program={program} index={index} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
<section className="px-4 pb-12 md:px-6">
|
||||
<div className="mx-auto max-w-7xl">
|
||||
<MentorRoster />
|
||||
</div>
|
||||
</section>
|
||||
</SiteShell>
|
||||
);
|
||||
}
|
||||
34
src/widgets/schedule-page.tsx
Normal file
34
src/widgets/schedule-page.tsx
Normal file
@@ -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 (
|
||||
<SiteShell>
|
||||
<LoudTitle
|
||||
label="Schedule"
|
||||
title="Потоки видны как операционный календарь"
|
||||
text="Страница помогает выбрать момент входа: дата старта, формат, статус набора и реальная интенсивность."
|
||||
/>
|
||||
<section className="px-4 py-12 md:px-6">
|
||||
<div className="mx-auto max-w-7xl divide-y-2 divide-foreground border-2 border-foreground bg-card">
|
||||
{schedule.map((item) => (
|
||||
<div key={`${item.date}-${item.program}`} className="grid gap-3 p-5 md:grid-cols-[150px_1fr_190px_190px_150px] md:items-center">
|
||||
<div className="flex items-center gap-2 font-black">
|
||||
<CalendarDaysIcon className="size-5" />
|
||||
{item.date}
|
||||
</div>
|
||||
<div className="text-3xl font-black uppercase leading-none">{item.program}</div>
|
||||
<div className="font-bold">{item.mode}</div>
|
||||
<div className="text-sm text-muted-foreground">{item.intensity}</div>
|
||||
<Badge className="w-fit rounded-none bg-secondary text-foreground">{item.status}</Badge>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</SiteShell>
|
||||
);
|
||||
}
|
||||
43
src/widgets/site-shell.tsx
Normal file
43
src/widgets/site-shell.tsx
Normal file
@@ -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 (
|
||||
<main className="min-h-screen bg-background text-foreground">
|
||||
<header className="sticky top-0 z-30 border-b-2 border-foreground bg-background/95 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-3 text-xl font-black uppercase">
|
||||
<span className="grid size-10 place-items-center border-2 border-foreground bg-secondary">
|
||||
<RocketIcon className="size-5" />
|
||||
</span>
|
||||
{site.name}
|
||||
</Link>
|
||||
<nav className="hidden items-center gap-5 text-sm font-black md:flex">
|
||||
{navItems.map((item) => (
|
||||
<Link key={item.href} href={item.href} className="hover:underline">
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
<Button asChild className="rounded-none bg-secondary text-foreground hover:bg-secondary/80">
|
||||
<Link href="/admissions">Подать заявку</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
{children}
|
||||
<footer className="border-t-2 border-foreground px-4 py-8 md:px-6">
|
||||
<div className="mx-auto flex max-w-7xl flex-col gap-3 text-sm md:flex-row md:items-center md:justify-between">
|
||||
<div>
|
||||
<div className="font-black uppercase">{site.name}</div>
|
||||
<div className="mt-1 text-muted-foreground">{site.tagline}</div>
|
||||
</div>
|
||||
<div className="font-bold">{site.email}</div>
|
||||
</div>
|
||||
</footer>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
18
src/widgets/student-work-card.tsx
Normal file
18
src/widgets/student-work-card.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import Image from "next/image";
|
||||
|
||||
import { studentWork } from "@/entities/site-content";
|
||||
|
||||
export function StudentWorkCard({ work }: { work: (typeof studentWork)[number] }) {
|
||||
return (
|
||||
<article className="overflow-hidden border-2 border-foreground bg-card">
|
||||
<div className="relative h-56 border-b-2 border-foreground">
|
||||
<Image src={work.image} alt={work.title} fill className="object-cover" sizes="(min-width: 768px) 33vw, 100vw" />
|
||||
</div>
|
||||
<div className="p-5">
|
||||
<div className="text-sm font-black uppercase text-primary">{work.role}</div>
|
||||
<h3 className="mt-3 text-3xl font-black uppercase leading-none">{work.title}</h3>
|
||||
<div className="mt-5 border-t-2 border-foreground pt-4 text-xl font-black">{work.result}</div>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<main className="min-h-screen bg-background text-foreground">
|
||||
<header className="sticky top-0 z-30 border-b-2 border-foreground bg-background/95 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-3 text-xl font-black uppercase">
|
||||
<span className="grid size-10 place-items-center border-2 border-foreground bg-secondary">
|
||||
<RocketIcon className="size-5" />
|
||||
</span>
|
||||
{site.name}
|
||||
</Link>
|
||||
<nav className="hidden items-center gap-5 text-sm font-black md:flex">
|
||||
{navItems.map((item) => (
|
||||
<Link key={item.href} href={item.href} className="hover:underline">
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
<Button asChild className="rounded-none bg-secondary text-foreground hover:bg-secondary/80">
|
||||
<Link href="/admissions">Подать заявку</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
{children}
|
||||
<footer className="border-t-2 border-foreground px-4 py-8 md:px-6">
|
||||
<div className="mx-auto flex max-w-7xl flex-col gap-3 text-sm md:flex-row md:items-center md:justify-between">
|
||||
<div>
|
||||
<div className="font-black uppercase">{site.name}</div>
|
||||
<div className="mt-1 text-muted-foreground">{site.tagline}</div>
|
||||
</div>
|
||||
<div className="font-bold">{site.email}</div>
|
||||
</div>
|
||||
</footer>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
function LoudTitle({ label, title, text }: { label: string; title: string; text: string }) {
|
||||
return (
|
||||
<section className="orbit-board border-b-2 border-foreground px-4 py-12 md:px-6 md:py-20">
|
||||
<div className="mx-auto max-w-7xl">
|
||||
<Badge className="mb-5 rounded-none border-2 border-foreground bg-secondary text-foreground">{label}</Badge>
|
||||
<h1 className="max-w-5xl break-words text-5xl font-black uppercase leading-[0.92] md:text-8xl">{title}</h1>
|
||||
<p className="mt-6 max-w-2xl text-lg leading-8 text-muted-foreground">{text}</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function HeroStudio() {
|
||||
return (
|
||||
<div className="overflow-hidden border-2 border-foreground bg-card shadow-[8px_8px_0_var(--foreground)]">
|
||||
<div className="relative h-72 border-b-2 border-foreground">
|
||||
<Image
|
||||
src="https://images.unsplash.com/photo-1552664730-d307ca884978?auto=format&fit=crop&w=1400&q=82"
|
||||
alt="Команда разбирает продуктовые артефакты на воркшопе"
|
||||
fill
|
||||
priority
|
||||
className="object-cover"
|
||||
sizes="(min-width: 1024px) 38vw, 100vw"
|
||||
/>
|
||||
<div className="absolute left-4 top-4 bg-background px-3 py-2 text-xs font-black uppercase">
|
||||
critique room
|
||||
</div>
|
||||
</div>
|
||||
<div className="orbit-board p-5">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<div className="text-xs font-black uppercase text-muted-foreground">week 04 board</div>
|
||||
<h2 className="mt-1 text-3xl font-black uppercase leading-none">Metric architecture</h2>
|
||||
</div>
|
||||
<Badge className="rounded-none bg-secondary text-foreground">{site.capacity}</Badge>
|
||||
</div>
|
||||
<div className="mt-5 grid gap-3">
|
||||
{[
|
||||
["19:00", "live teardown", "разбор metric tree"],
|
||||
["20:10", "peer room", "критика по группам"],
|
||||
["21:00", "mentor notes", "правки до пятницы"],
|
||||
].map(([time, title, text]) => (
|
||||
<div key={time} className="grid grid-cols-[64px_1fr] gap-3 border-2 border-foreground bg-background p-3">
|
||||
<div className="font-black text-primary">{time}</div>
|
||||
<div>
|
||||
<div className="font-black uppercase">{title}</div>
|
||||
<div className="text-sm text-muted-foreground">{text}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ProgramCard({ program, index }: { program: Program; index: number }) {
|
||||
const href = program.slug === "product-management" ? "/programs/product-management" : "/programs";
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
className="group flex min-h-[360px] flex-col justify-between border-2 border-foreground bg-card p-5 shadow-[8px_8px_0_var(--foreground)] transition hover:-translate-y-1 hover:shadow-[12px_12px_0_var(--foreground)]"
|
||||
>
|
||||
<div className="flex justify-between gap-4 text-sm font-black uppercase">
|
||||
<span>0{index + 1}</span>
|
||||
<span>{program.duration}</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-4xl font-black uppercase leading-none">{program.title}</h3>
|
||||
<p className="mt-4 font-bold leading-7">{program.signal}</p>
|
||||
<p className="mt-3 leading-7 text-muted-foreground">{program.outcome}</p>
|
||||
</div>
|
||||
<div>
|
||||
<div className="mb-4 flex flex-wrap gap-2">
|
||||
{program.deliverables.slice(0, 3).map((item) => (
|
||||
<span key={item} className="border-2 border-foreground bg-secondary px-2 py-1 text-xs font-black uppercase">
|
||||
{item}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center justify-between border-t-2 border-foreground pt-4 text-sm font-bold">
|
||||
<span>{program.level} / {program.seats} / {program.price}</span>
|
||||
<ArrowRightIcon className="size-5 transition group-hover:translate-x-1" />
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
function OutcomeStrip() {
|
||||
return (
|
||||
<section className="border-y-2 border-foreground bg-primary px-4 py-10 text-primary-foreground md:px-6">
|
||||
<div className="mx-auto grid max-w-7xl gap-6 md:grid-cols-4">
|
||||
{outcomes.map((item) => (
|
||||
<div key={item.value}>
|
||||
<div className="text-5xl font-black">{item.value}</div>
|
||||
<p className="mt-2 max-w-sm text-sm font-bold opacity-80">{item.label}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function StudentWorkCard({ work }: { work: (typeof studentWork)[number] }) {
|
||||
return (
|
||||
<article className="overflow-hidden border-2 border-foreground bg-card">
|
||||
<div className="relative h-56 border-b-2 border-foreground">
|
||||
<Image src={work.image} alt={work.title} fill className="object-cover" sizes="(min-width: 768px) 33vw, 100vw" />
|
||||
</div>
|
||||
<div className="p-5">
|
||||
<div className="text-sm font-black uppercase text-primary">{work.role}</div>
|
||||
<h3 className="mt-3 text-3xl font-black uppercase leading-none">{work.title}</h3>
|
||||
<div className="mt-5 border-t-2 border-foreground pt-4 text-xl font-black">{work.result}</div>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
function MentorRoster() {
|
||||
return (
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
{mentors.map((mentor) => (
|
||||
<article key={mentor.name} className="border-2 border-foreground bg-card p-5">
|
||||
<UsersIcon className="mb-10 size-7 text-primary" />
|
||||
<h3 className="text-2xl font-black uppercase leading-none">{mentor.name}</h3>
|
||||
<p className="mt-3 font-bold">{mentor.role}</p>
|
||||
<p className="mt-2 text-sm text-muted-foreground">{mentor.focus}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function HomePage() {
|
||||
return (
|
||||
<Shell>
|
||||
<section className="orbit-board px-4 py-10 md:px-6 md:py-14">
|
||||
<div className="mx-auto grid max-w-7xl gap-8 lg:grid-cols-[0.95fr_1.05fr] lg:items-center">
|
||||
<div>
|
||||
<Badge className="mb-5 rounded-none border-2 border-foreground bg-secondary text-foreground">
|
||||
Следующий поток: {site.nextCohort}
|
||||
</Badge>
|
||||
<h1 className="hidden text-5xl font-black uppercase leading-[0.86] md:block md:text-8xl">
|
||||
Не лекции. Еженедельный продуктовый разбор.
|
||||
</h1>
|
||||
<h1 className="text-[2.65rem] font-black uppercase leading-[0.9] md:hidden">
|
||||
Не лекции. Работа, разбор, demo day.
|
||||
</h1>
|
||||
<p className="mt-6 max-w-2xl text-lg leading-8 text-muted-foreground">
|
||||
Orbit Academy запускает cohort-based программы, где студенты каждую неделю
|
||||
приносят рабочий артефакт: problem brief, research board, metric tree или roadmap defense.
|
||||
</p>
|
||||
<div className="mt-7 flex flex-wrap gap-3">
|
||||
<Button asChild className="rounded-none" size="lg">
|
||||
<Link href="/programs">Выбрать программу <ArrowRightIcon className="size-4" /></Link>
|
||||
</Button>
|
||||
<Button asChild variant="outline" className="rounded-none border-2" size="lg">
|
||||
<Link href="/schedule">Расписание</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-8 grid gap-3 md:grid-cols-3">
|
||||
{heroStats.map((item) => (
|
||||
<div key={item.value} className="border-2 border-foreground bg-card p-3">
|
||||
<div className="text-2xl font-black">{item.value}</div>
|
||||
<div className="mt-1 text-xs font-bold text-muted-foreground">{item.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<HeroStudio />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="px-4 py-12 md:px-6">
|
||||
<div className="mx-auto grid max-w-7xl gap-6 md:grid-cols-3">
|
||||
{programs.map((program, index) => (
|
||||
<ProgramCard key={program.title} program={program} index={index} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<OutcomeStrip />
|
||||
|
||||
<section className="px-4 py-12 md:px-6">
|
||||
<div className="mx-auto grid max-w-7xl gap-6 lg:grid-cols-[0.7fr_1.3fr]">
|
||||
<div>
|
||||
<div className="marker-highlight w-fit text-sm font-black uppercase">работы студентов</div>
|
||||
<h2 className="mt-5 text-4xl font-black uppercase leading-none md:text-6xl">Proof вместо обещаний</h2>
|
||||
</div>
|
||||
<div className="grid gap-5 md:grid-cols-3">
|
||||
{studentWork.map((work) => (
|
||||
<StudentWorkCard key={work.title} work={work} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Shell>
|
||||
);
|
||||
}
|
||||
|
||||
export function ProgramsPage() {
|
||||
return (
|
||||
<Shell>
|
||||
<LoudTitle
|
||||
label="Programs"
|
||||
title="Три программы для разных точек роста"
|
||||
text="Каждая программа имеет outcome, расписание, формат обратной связи, deliverables и ограниченное количество мест в группе."
|
||||
/>
|
||||
<section className="px-4 py-12 md:px-6">
|
||||
<div className="mx-auto grid max-w-7xl gap-6 md:grid-cols-3">
|
||||
{programs.map((program, index) => (
|
||||
<ProgramCard key={program.title} program={program} index={index} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
<section className="px-4 pb-12 md:px-6">
|
||||
<div className="mx-auto max-w-7xl">
|
||||
<MentorRoster />
|
||||
</div>
|
||||
</section>
|
||||
</Shell>
|
||||
);
|
||||
}
|
||||
|
||||
export function ProgramDetailPage() {
|
||||
const program = programs[0];
|
||||
|
||||
return (
|
||||
<Shell>
|
||||
<LoudTitle
|
||||
label="Product Management"
|
||||
title="От проблемы к roadmap, который можно защищать"
|
||||
text={`${program.duration}, ${program.format}. Итог - ${program.outcome}.`}
|
||||
/>
|
||||
<section className="px-4 py-12 md:px-6">
|
||||
<div className="mx-auto grid max-w-7xl gap-5">
|
||||
{modules.map((module) => (
|
||||
<article key={module.week} className="grid gap-4 border-2 border-foreground bg-card p-5 md:grid-cols-[100px_0.75fr_1fr_0.8fr] md:items-start">
|
||||
<div className="text-5xl font-black">{module.week}</div>
|
||||
<div>
|
||||
<h2 className="text-3xl font-black uppercase leading-none">{module.title}</h2>
|
||||
<div className="mt-3 text-sm font-black text-primary">{module.critique}</div>
|
||||
</div>
|
||||
<p className="leading-7 text-muted-foreground">{module.task}</p>
|
||||
<div className="border-2 border-foreground bg-secondary p-3 text-sm font-black">{module.artifact}</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</Shell>
|
||||
);
|
||||
}
|
||||
|
||||
export function SchedulePage() {
|
||||
return (
|
||||
<Shell>
|
||||
<LoudTitle
|
||||
label="Schedule"
|
||||
title="Потоки видны как операционный календарь"
|
||||
text="Страница помогает выбрать момент входа: дата старта, формат, статус набора и реальная интенсивность."
|
||||
/>
|
||||
<section className="px-4 py-12 md:px-6">
|
||||
<div className="mx-auto max-w-7xl divide-y-2 divide-foreground border-2 border-foreground bg-card">
|
||||
{schedule.map((item) => (
|
||||
<div key={`${item.date}-${item.program}`} className="grid gap-3 p-5 md:grid-cols-[150px_1fr_190px_190px_150px] md:items-center">
|
||||
<div className="flex items-center gap-2 font-black">
|
||||
<CalendarDaysIcon className="size-5" />
|
||||
{item.date}
|
||||
</div>
|
||||
<div className="text-3xl font-black uppercase leading-none">{item.program}</div>
|
||||
<div className="font-bold">{item.mode}</div>
|
||||
<div className="text-sm text-muted-foreground">{item.intensity}</div>
|
||||
<Badge className="w-fit rounded-none bg-secondary text-foreground">{item.status}</Badge>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</Shell>
|
||||
);
|
||||
}
|
||||
|
||||
export function OutcomesPage() {
|
||||
return (
|
||||
<Shell>
|
||||
<LoudTitle
|
||||
label="Outcomes"
|
||||
title="Результаты показываются через работу, а не обещания"
|
||||
text="Шаблон отделяет образовательный маркетинг от реальных outcome-блоков: кейсы, метрики, демо и роли выпускников."
|
||||
/>
|
||||
<section className="px-4 py-12 md:px-6">
|
||||
<div className="mx-auto grid max-w-7xl gap-6 md:grid-cols-2 lg:grid-cols-4">
|
||||
{outcomes.map((item) => (
|
||||
<article key={item.value} className="border-2 border-foreground bg-card p-6 shadow-[8px_8px_0_var(--foreground)]">
|
||||
<TrophyIcon className="mb-10 size-8 text-primary" />
|
||||
<div className="text-5xl font-black">{item.value}</div>
|
||||
<p className="mt-4 font-bold leading-7">{item.label}</p>
|
||||
<p className="mt-3 text-sm text-muted-foreground">{item.proof}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
<section className="px-4 pb-12 md:px-6">
|
||||
<div className="mx-auto grid max-w-7xl gap-5 md:grid-cols-3">
|
||||
{studentWork.map((work) => (
|
||||
<StudentWorkCard key={work.title} work={work} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</Shell>
|
||||
);
|
||||
}
|
||||
|
||||
export function AdmissionsPage() {
|
||||
return (
|
||||
<Shell>
|
||||
<LoudTitle
|
||||
label="Admissions"
|
||||
title="Поступление без иллюзии элитарности"
|
||||
text="Нужна не мотивационная анкета, а проверка цели, уровня и готовности показывать работу группе каждую неделю."
|
||||
/>
|
||||
<section className="px-4 py-12 md:px-6">
|
||||
<div className="mx-auto grid max-w-7xl gap-8 lg:grid-cols-[1fr_430px]">
|
||||
<div className="grid gap-4">
|
||||
{admissions.map((item, index) => (
|
||||
<div key={item} className="grid gap-4 border-2 border-foreground bg-card p-5 md:grid-cols-[72px_1fr]">
|
||||
<div className="text-4xl font-black text-primary">0{index + 1}</div>
|
||||
<h2 className="text-3xl font-black uppercase leading-none">{item}</h2>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<AdmissionBoard />
|
||||
</div>
|
||||
</section>
|
||||
</Shell>
|
||||
);
|
||||
}
|
||||
|
||||
export function CommunityPage() {
|
||||
return (
|
||||
<Shell>
|
||||
<LoudTitle
|
||||
label="Community"
|
||||
title="Комьюнити работает после последней защиты"
|
||||
text="Сильная онлайн-школа продает не только программу, но и среду, где продолжают показывать работу и получать обратную связь."
|
||||
/>
|
||||
<section className="px-4 py-12 md:px-6">
|
||||
<div className="mx-auto grid max-w-7xl gap-6 md:grid-cols-3">
|
||||
{communityNotes.map((note) => (
|
||||
<article key={note.title} className="flex min-h-80 flex-col justify-between border-2 border-foreground bg-card p-6">
|
||||
<div>
|
||||
<MessageSquareTextIcon className="mb-16 size-8 text-primary" />
|
||||
<h2 className="text-3xl font-black uppercase leading-none">{note.title}</h2>
|
||||
<p className="mt-4 leading-7 text-muted-foreground">{note.text}</p>
|
||||
</div>
|
||||
<div className="mt-8 border-t-2 border-foreground pt-4 text-sm font-black uppercase">{note.cadence}</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
<section className="px-4 pb-12 md:px-6">
|
||||
<div className="mx-auto grid max-w-7xl gap-6 lg:grid-cols-[0.8fr_1.2fr]">
|
||||
<div className="border-2 border-foreground bg-secondary p-6">
|
||||
<PlaySquareIcon className="mb-10 size-8" />
|
||||
<h2 className="text-4xl font-black uppercase leading-none">Demo archive</h2>
|
||||
<p className="mt-4 leading-7">Записи защит, шаблоны артефактов и разборы доступны выпускникам после курса.</p>
|
||||
</div>
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
{[
|
||||
["artifact library", "problem briefs, research boards, metric trees"],
|
||||
["hiring rooms", "портфолио-ревью и разбор офферов"],
|
||||
["peer circles", "мини-группы по роли и домену"],
|
||||
["mentor threads", "асинхронные вопросы после live-сессий"],
|
||||
].map(([title, text]) => (
|
||||
<div key={title} className="border-2 border-foreground bg-card p-5">
|
||||
<Rows3Icon className="mb-8 size-7 text-primary" />
|
||||
<h3 className="text-2xl font-black uppercase">{title}</h3>
|
||||
<p className="mt-3 text-sm leading-6 text-muted-foreground">{text}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Shell>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user