feat: initialize project with Vite + React + TypeScript + Tailwind CSS

- Set up Vite 7 + React 19 + TypeScript 5 + Tailwind CSS 4
- Configure path aliases (@/ -> src/)
- Create Layout with Header and Footer components
- Create HomePage with hero section and feature highlights
- Set up cursor rules for agent-driven development
- Create PLAN.md and LESSONS.md for progress tracking

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Денис Шкабатур
2026-02-11 12:21:03 +03:00
commit 673760f9da
25 changed files with 3080 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
---
description: Agent workflow rules for 100% agent-driven development
alwaysApply: true
---
# Agent Workflow
## Before Making Changes
1. Read PLAN.md to understand current phase and priorities
2. Check LESSONS.md for relevant past decisions
3. Read existing code before editing — never guess at file contents
## After Making Changes
1. Run `npm run build` to verify no errors
2. Update PLAN.md checkboxes when a feature is complete
3. Add important learnings to LESSONS.md
4. Commit with conventional message format
## When Stuck
- Check LESSONS.md for similar past issues
- Prefer simple solutions over clever ones
- If a decision has trade-offs, document the reasoning in LESSONS.md
## Code Quality
- Fix all TypeScript errors before committing
- Fix all linter warnings before committing
- No `console.log` in committed code (use only for debugging)
- Remove unused imports and variables

View File

@@ -0,0 +1,30 @@
---
description: Project overview and conventions for the Diet Reference App
alwaysApply: true
---
# Diet & Healthy Weight Reference App
## Project
Static SPA — справочник методик похудения и здорового веса. Vite 6 + React 19 + TypeScript 5 + Tailwind CSS 4 + React Router 7.
## Key Conventions
- All code in TypeScript strict mode — no `any`, no `as` casts without justification
- Functional components only, prefer named exports
- Custom hooks for reusable logic (prefix `use`)
- Data lives in `src/data/` as typed TS modules
- All user state (theme, bookmarks) via localStorage through custom hooks
- Semantic HTML + ARIA attributes for accessibility
- Russian language for UI content, English for code (variable names, comments)
## File Structure
- `src/components/` — reusable UI components (Button, Card, Badge, etc.)
- `src/pages/` — route-level page components
- `src/data/` — static content data (methods, categories)
- `src/hooks/` — custom React hooks
- `src/utils/` — pure utility functions
- `src/types/` — shared TypeScript types and interfaces
## Commit Style
- Atomic commits per feature
- Format: `feat: short description` / `fix:` / `refactor:` / `style:` / `docs:`

View File

@@ -0,0 +1,31 @@
---
description: Tailwind CSS styling conventions
globs: "**/*.tsx"
alwaysApply: false
---
# Tailwind CSS Styling
## Approach
- Utility-first: use Tailwind classes directly in JSX
- Use `clsx` for conditional classes
- Extract repeated patterns into components, not CSS classes
- Dark mode via `dark:` variant (class strategy)
## Layout
- Mobile-first: default styles for mobile, `sm:` / `md:` / `lg:` for larger
- Use `container mx-auto px-4` for page-level wrappers
- Flexbox and Grid via Tailwind utilities
## Colors
- Use semantic color names from theme (primary, secondary, accent)
- Never hardcode hex colors in className
## Example
```tsx
<article className={clsx(
"rounded-2xl border p-6 transition-shadow hover:shadow-lg",
"bg-white dark:bg-gray-800",
isBookmarked && "ring-2 ring-primary-500"
)}>
```

View File

@@ -0,0 +1,39 @@
---
description: TypeScript and React patterns for the project
globs: "**/*.{ts,tsx}"
alwaysApply: false
---
# TypeScript & React Standards
## Components
- Functional components with explicit return types
- Props as `interface` (not `type`), suffix with `Props`
- Destructure props in function signature
- Use `React.FC` is NOT allowed — use plain functions
```typescript
// GOOD
interface MethodCardProps {
method: DietMethod;
onBookmark?: (id: string) => void;
}
export function MethodCard({ method, onBookmark }: MethodCardProps) {
return <article>...</article>;
}
```
## Hooks
- Custom hooks return objects (not arrays) for >2 values
- Always handle loading/error states
- Prefix with `use`
## Types
- Prefer `interface` for object shapes, `type` for unions/intersections
- No enums — use `as const` objects + type inference
- Export shared types from `src/types/`
## Imports
- Absolute imports from `@/` (mapped to `src/`)
- Group: react → third-party → @/ imports → relative → types

19
.gitignore vendored Normal file
View File

@@ -0,0 +1,19 @@
# Dependencies
node_modules/
# Build
dist/
# IDE
.vscode/
*.swp
*.swo
.DS_Store
# Environment
.env
.env.local
# Logs
*.log
npm-debug.log*

16
LESSONS.md Normal file
View File

@@ -0,0 +1,16 @@
# Lessons Learned — Опыт разработки
Этот файл фиксирует важные уроки, решения и инсайты, полученные в ходе разработки.
---
## 2026-02-11 — Старт проекта
### Выбор стека
- **Vite + React + TS** выбран вместо Astro/Next.js: проект интерактивный (поиск, фильтры, BMI калькулятор), Astro избыточен для SPA, Next.js избыточен без SSR.
- **Tailwind CSS 4** — utility-first, быстрая стилизация, отличная поддержка dark mode.
- **React Router 7** — client-side routing для SPA, нет нужды в серверном роутинге.
### Решения по архитектуре
- Данные хранятся как TS-модули (не JSON) — это даёт типизацию на уровне данных.
- localStorage для пользовательских настроек (тема, закладки) — достаточно для статического приложения.

71
PLAN.md Normal file
View File

@@ -0,0 +1,71 @@
# Diet & Healthy Weight Reference App — План разработки
## Описание проекта
Статическое веб-приложение — справочник методик похудения и поддержания здорового веса.
Полностью serverless, деплой на любой статик-хостинг (Vercel, Netlify, GitHub Pages).
## Стек технологий
- **Runtime**: Vite 6 + React 19 + TypeScript 5
- **Стили**: Tailwind CSS 4 + clsx
- **Роутинг**: React Router 7 (client-side)
- **Данные**: Статические JSON/TS модули (без API)
- **Хранение**: localStorage для закладок / настроек
- **Сборка**: Vite → статический бандл
## Архитектура
```
src/
components/ — переиспользуемые UI-компоненты
pages/ — страницы приложения
data/ — контент: методики, категории
hooks/ — кастомные React-хуки
utils/ — утилиты (расчёт BMI и др.)
types/ — TypeScript типы
assets/ — изображения, иконки
```
## Фичи (в порядке приоритета)
### Phase 1 — Фундамент
- [x] Инициализация проекта (Vite + React + TS + Tailwind)
- [ ] Базовая структура приложения и роутинг
- [ ] Layout: header, footer, навигация
- [ ] Главная страница с обзором
### Phase 2 — Контент
- [ ] Модель данных для методик
- [ ] Данные: минимум 15 методик по категориям
- [ ] Страница списка методик с фильтрами
- [ ] Страница детальной информации о методике
### Phase 3 — Интерактивность
- [ ] Поиск по методикам (instant search)
- [ ] Фильтрация по категориям, сложности, эффективности
- [ ] BMI-калькулятор
- [ ] Закладки (favorites) через localStorage
- [ ] Сравнение методик (до 3 шт)
### Phase 4 — Полировка
- [ ] Тёмная / светлая тема
- [ ] Адаптивный дизайн (mobile-first)
- [ ] Анимации переходов
- [ ] SEO мета-теги
- [ ] Деплой на GitHub Pages / Vercel
## Прогресс
| Дата | Что сделано | Коммит |
|------|-------------|--------|
| 2026-02-11 | Создан план, cursor rules, инициализация проекта | — |
## Принципы разработки
1. **Коммит на каждую фичу** — атомарные, понятные коммиты
2. **100% агентная разработка** — cursor rules покрывают все паттерны
3. **TypeScript strict mode** — никаких `any`
4. **Переиспользуемые компоненты** — DRY
5. **Доступность (a11y)** — семантический HTML, ARIA
6. **Производительность** — lazy loading, минимум бандла

14
index.html Normal file
View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Справочник методик похудения и поддержания здорового веса" />
<title>HealthyWeight — Справочник методик</title>
</head>
<body class="min-h-screen bg-gray-50 text-gray-900 antialiased dark:bg-gray-950 dark:text-gray-100">
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

2510
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

27
package.json Normal file
View File

@@ -0,0 +1,27 @@
{
"name": "diet",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"clsx": "^2.1.1",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-router-dom": "^7.13.0"
},
"devDependencies": {
"@tailwindcss/vite": "^4.1.18",
"@types/node": "^25.2.3",
"@types/react": "^19.2.13",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.4",
"tailwindcss": "^4.1.18",
"typescript": "^5.9.3",
"vite": "^7.3.1"
}
}

1
public/vite.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y=".9em" font-size="90"><?</text></svg>

After

Width:  |  Height:  |  Size: 108 B

13
src/App.tsx Normal file
View File

@@ -0,0 +1,13 @@
import { Routes, Route } from "react-router-dom";
import { Layout } from "@/components/Layout";
import { HomePage } from "@/pages/HomePage";
export function App() {
return (
<Routes>
<Route element={<Layout />}>
<Route index element={<HomePage />} />
</Route>
</Routes>
);
}

14
src/components/Footer.tsx Normal file
View File

@@ -0,0 +1,14 @@
export function Footer() {
return (
<footer className="border-t border-gray-200 bg-white py-8 dark:border-gray-800 dark:bg-gray-950">
<div className="container mx-auto px-4 text-center text-sm text-gray-500 dark:text-gray-400">
<p>
HealthyWeight &mdash; справочник методик похудения и здорового веса.
</p>
<p className="mt-1">
Информация носит ознакомительный характер. Проконсультируйтесь с врачом.
</p>
</div>
</footer>
);
}

55
src/components/Header.tsx Normal file
View File

@@ -0,0 +1,55 @@
import { Link, NavLink } from "react-router-dom";
import { clsx } from "clsx";
const navLinks = [
{ to: "/", label: "Главная" },
{ to: "/methods", label: "Методики" },
{ to: "/calculator", label: "Калькулятор BMI" },
] as const;
export function Header() {
return (
<header className="sticky top-0 z-50 border-b border-gray-200 bg-white/80 backdrop-blur-lg dark:border-gray-800 dark:bg-gray-950/80">
<div className="container mx-auto flex items-center justify-between px-4 py-4">
<Link to="/" className="flex items-center gap-2 text-xl font-bold text-primary-600">
<span aria-hidden="true" className="text-2xl">&#x1F33F;</span>
HealthyWeight
</Link>
<nav className="hidden items-center gap-1 md:flex">
{navLinks.map(({ to, label }) => (
<NavLink
key={to}
to={to}
className={({ isActive }) =>
clsx(
"rounded-lg px-4 py-2 text-sm font-medium transition-colors",
isActive
? "bg-primary-50 text-primary-700 dark:bg-primary-900/30 dark:text-primary-300"
: "text-gray-600 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-100",
)
}
>
{label}
</NavLink>
))}
</nav>
<MobileMenuButton />
</div>
</header>
);
}
function MobileMenuButton() {
return (
<button
className="rounded-lg p-2 text-gray-600 hover:bg-gray-100 md:hidden dark:text-gray-400 dark:hover:bg-gray-800"
aria-label="Открыть меню"
>
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
</svg>
</button>
);
}

15
src/components/Layout.tsx Normal file
View File

@@ -0,0 +1,15 @@
import { Outlet } from "react-router-dom";
import { Header } from "@/components/Header";
import { Footer } from "@/components/Footer";
export function Layout() {
return (
<div className="flex min-h-screen flex-col">
<Header />
<main className="flex-1">
<Outlet />
</main>
<Footer />
</div>
);
}

31
src/index.css Normal file
View File

@@ -0,0 +1,31 @@
@import "tailwindcss";
@theme {
--color-primary-50: #f0fdf4;
--color-primary-100: #dcfce7;
--color-primary-200: #bbf7d0;
--color-primary-300: #86efac;
--color-primary-400: #4ade80;
--color-primary-500: #22c55e;
--color-primary-600: #16a34a;
--color-primary-700: #15803d;
--color-primary-800: #166534;
--color-primary-900: #14532d;
--color-accent-50: #fefce8;
--color-accent-100: #fef9c3;
--color-accent-200: #fef08a;
--color-accent-300: #fde047;
--color-accent-400: #facc15;
--color-accent-500: #eab308;
--color-accent-600: #ca8a04;
--color-accent-700: #a16207;
--color-accent-800: #854d0e;
--color-accent-900: #713f12;
}
html {
scroll-behavior: smooth;
}
/* Dark mode is managed via class on <html> */

13
src/main.tsx Normal file
View File

@@ -0,0 +1,13 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import { App } from "@/App";
import "@/index.css";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>,
);

83
src/pages/HomePage.tsx Normal file
View File

@@ -0,0 +1,83 @@
import { Link } from "react-router-dom";
const highlights = [
{
icon: "\u{1F4D6}",
title: "Подробные описания",
text: "Каждая методика с научным обоснованием, плюсами и минусами",
},
{
icon: "\u{1F50D}",
title: "Удобный поиск",
text: "Быстрый поиск и фильтрация по категориям и параметрам",
},
{
icon: "\u{1F4CA}",
title: "Калькулятор BMI",
text: "Рассчитайте индекс массы тела и получите рекомендации",
},
{
icon: "\u{2696}\u{FE0F}",
title: "Сравнение методик",
text: "Сравните до 3 методик бок о бок для выбора подходящей",
},
] as const;
export function HomePage() {
return (
<>
{/* Hero Section */}
<section className="bg-gradient-to-br from-primary-50 via-white to-accent-50 py-20 dark:from-gray-900 dark:via-gray-950 dark:to-gray-900">
<div className="container mx-auto px-4 text-center">
<h1 className="text-4xl font-extrabold tracking-tight text-gray-900 sm:text-5xl lg:text-6xl dark:text-white">
Методики похудения
<span className="block text-primary-600 dark:text-primary-400">и здорового веса</span>
</h1>
<p className="mx-auto mt-6 max-w-2xl text-lg text-gray-600 dark:text-gray-300">
Полный справочник научно обоснованных методик от диет до физической активности.
Найдите подход, который подойдёт именно вам.
</p>
<div className="mt-10 flex flex-col items-center justify-center gap-4 sm:flex-row">
<Link
to="/methods"
className="rounded-xl bg-primary-600 px-8 py-3.5 text-base font-semibold text-white shadow-lg shadow-primary-500/25 transition hover:bg-primary-700 hover:shadow-xl"
>
Смотреть методики
</Link>
<Link
to="/calculator"
className="rounded-xl border-2 border-gray-300 bg-white px-8 py-3.5 text-base font-semibold text-gray-700 transition hover:border-primary-300 hover:text-primary-700 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-200 dark:hover:border-primary-600"
>
Рассчитать BMI
</Link>
</div>
</div>
</section>
{/* Highlights */}
<section className="py-20">
<div className="container mx-auto px-4">
<h2 className="text-center text-3xl font-bold text-gray-900 dark:text-white">
Что вас ждёт
</h2>
<div className="mt-12 grid gap-8 sm:grid-cols-2 lg:grid-cols-4">
{highlights.map(({ icon, title, text }) => (
<div
key={title}
className="rounded-2xl border border-gray-200 bg-white p-6 text-center transition-shadow hover:shadow-lg dark:border-gray-800 dark:bg-gray-900"
>
<div className="text-4xl" aria-hidden="true">{icon}</div>
<h3 className="mt-4 text-lg font-semibold text-gray-900 dark:text-white">
{title}
</h3>
<p className="mt-2 text-sm text-gray-500 dark:text-gray-400">
{text}
</p>
</div>
))}
</div>
</div>
</section>
</>
);
}

1
src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

27
tsconfig.app.json Normal file
View File

@@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"]
}

1
tsconfig.app.tsbuildinfo Normal file
View File

@@ -0,0 +1 @@
{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/footer.tsx","./src/components/header.tsx","./src/components/layout.tsx","./src/pages/homepage.tsx"],"version":"5.9.3"}

7
tsconfig.json Normal file
View File

@@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}

20
tsconfig.node.json Normal file
View File

@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["vite.config.ts"]
}

View File

@@ -0,0 +1 @@
{"root":["./vite.config.ts"],"version":"5.9.3"}

13
vite.config.ts Normal file
View File

@@ -0,0 +1,13 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import { fileURLToPath, URL } from "node:url";
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
});