diff --git a/src/App.tsx b/src/App.tsx index 7257411..216ac75 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,12 +1,18 @@ import { Routes, Route } from "react-router-dom"; import { Layout } from "@/components/Layout"; import { HomePage } from "@/pages/HomePage"; +import { MethodsPage } from "@/pages/MethodsPage"; +import { MethodDetailPage } from "@/pages/MethodDetailPage"; +import { BmiCalculatorPage } from "@/pages/BmiCalculatorPage"; export function App() { return ( }> } /> + } /> + } /> + } /> ); diff --git a/src/components/Badge.tsx b/src/components/Badge.tsx new file mode 100644 index 0000000..3329635 --- /dev/null +++ b/src/components/Badge.tsx @@ -0,0 +1,30 @@ +import { clsx } from "clsx"; + +interface BadgeProps { + children: React.ReactNode; + variant?: "default" | "emerald" | "blue" | "violet" | "amber" | "rose"; + size?: "sm" | "md"; +} + +const variantClasses = { + default: "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300", + emerald: "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-300", + blue: "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300", + violet: "bg-violet-100 text-violet-700 dark:bg-violet-900/30 dark:text-violet-300", + amber: "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300", + rose: "bg-rose-100 text-rose-700 dark:bg-rose-900/30 dark:text-rose-300", +} as const; + +export function Badge({ children, variant = "default", size = "sm" }: BadgeProps) { + return ( + + {children} + + ); +} diff --git a/src/components/BookmarkButton.tsx b/src/components/BookmarkButton.tsx new file mode 100644 index 0000000..2c584a1 --- /dev/null +++ b/src/components/BookmarkButton.tsx @@ -0,0 +1,41 @@ +import { clsx } from "clsx"; + +interface BookmarkButtonProps { + isBookmarked: boolean; + onClick: () => void; + size?: "sm" | "md"; +} + +export function BookmarkButton({ isBookmarked, onClick, size = "sm" }: BookmarkButtonProps) { + return ( + + ); +} diff --git a/src/components/DifficultyBadge.tsx b/src/components/DifficultyBadge.tsx new file mode 100644 index 0000000..5bbf883 --- /dev/null +++ b/src/components/DifficultyBadge.tsx @@ -0,0 +1,21 @@ +import { clsx } from "clsx"; +import type { Difficulty } from "@/types/method"; + +interface DifficultyBadgeProps { + difficulty: Difficulty; +} + +const config = { + easy: { label: "Легко", class: "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300" }, + medium: { label: "Средне", class: "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-300" }, + hard: { label: "Сложно", class: "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300" }, +} as const; + +export function DifficultyBadge({ difficulty }: DifficultyBadgeProps) { + const { label, class: className } = config[difficulty]; + return ( + + {label} + + ); +} diff --git a/src/components/EffectivenessIndicator.tsx b/src/components/EffectivenessIndicator.tsx new file mode 100644 index 0000000..1bca333 --- /dev/null +++ b/src/components/EffectivenessIndicator.tsx @@ -0,0 +1,35 @@ +import { clsx } from "clsx"; +import type { Effectiveness } from "@/types/method"; + +interface EffectivenessIndicatorProps { + effectiveness: Effectiveness; +} + +const levels = { + low: { label: "Низкая", filled: 1 }, + medium: { label: "Средняя", filled: 2 }, + high: { label: "Высокая", filled: 3 }, +} as const; + +export function EffectivenessIndicator({ effectiveness }: EffectivenessIndicatorProps) { + const { label, filled } = levels[effectiveness]; + return ( +
+ Эффективность: +
+ {[1, 2, 3].map((level) => ( +
+ ))} +
+ {label} +
+ ); +} diff --git a/src/components/FilterPanel.tsx b/src/components/FilterPanel.tsx new file mode 100644 index 0000000..f50619e --- /dev/null +++ b/src/components/FilterPanel.tsx @@ -0,0 +1,107 @@ +import { clsx } from "clsx"; +import { CATEGORIES, DIFFICULTIES, EFFECTIVENESS_LEVELS } from "@/types/method"; +import type { CategoryId, Difficulty, Effectiveness } from "@/types/method"; + +interface FilterPanelProps { + selectedCategories: CategoryId[]; + selectedDifficulties: Difficulty[]; + selectedEffectiveness: Effectiveness[]; + onToggleCategory: (id: CategoryId) => void; + onToggleDifficulty: (id: Difficulty) => void; + onToggleEffectiveness: (id: Effectiveness) => void; + onReset: () => void; +} + +export function FilterPanel({ + selectedCategories, + selectedDifficulties, + selectedEffectiveness, + onToggleCategory, + onToggleDifficulty, + onToggleEffectiveness, + onReset, +}: FilterPanelProps) { + const hasFilters = + selectedCategories.length > 0 || + selectedDifficulties.length > 0 || + selectedEffectiveness.length > 0; + + return ( +
+
+

Фильтры

+ {hasFilters && ( + + )} +
+ + {/* Categories */} +
+

Категория

+
+ {Object.values(CATEGORIES).map((cat) => ( + + ))} +
+
+ + {/* Difficulty */} +
+

Сложность

+
+ {Object.values(DIFFICULTIES).map((diff) => ( + + ))} +
+
+ + {/* Effectiveness */} +
+

Эффективность

+
+ {Object.values(EFFECTIVENESS_LEVELS).map((eff) => ( + + ))} +
+
+
+ ); +} diff --git a/src/components/MethodCard.tsx b/src/components/MethodCard.tsx new file mode 100644 index 0000000..14a3b34 --- /dev/null +++ b/src/components/MethodCard.tsx @@ -0,0 +1,58 @@ +import { Link } from "react-router-dom"; +import { clsx } from "clsx"; +import type { DietMethod } from "@/types/method"; +import { CATEGORIES } from "@/types/method"; +import { Badge } from "@/components/Badge"; +import { DifficultyBadge } from "@/components/DifficultyBadge"; +import { EffectivenessIndicator } from "@/components/EffectivenessIndicator"; +import { BookmarkButton } from "@/components/BookmarkButton"; + +interface MethodCardProps { + method: DietMethod; + isBookmarked: boolean; + onToggleBookmark: (id: string) => void; +} + +export function MethodCard({ method, isBookmarked, onToggleBookmark }: MethodCardProps) { + const category = CATEGORIES[method.category]; + + return ( + +
+ onToggleBookmark(method.id)} + /> +
+ +
+ + {category.label} + +
+ +

+ {method.title} +

+ +

+ {method.shortDescription} +

+ +
+ +
+ +
+ {method.timeframe} +
+ + ); +} diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar.tsx new file mode 100644 index 0000000..25fc980 --- /dev/null +++ b/src/components/SearchBar.tsx @@ -0,0 +1,32 @@ +interface SearchBarProps { + value: string; + onChange: (value: string) => void; + placeholder?: string; +} + +export function SearchBar({ value, onChange, placeholder = "Поиск методик..." }: SearchBarProps) { + return ( +
+ + + + onChange(e.target.value)} + placeholder={placeholder} + className="w-full rounded-xl border border-gray-300 bg-white py-3 pl-11 pr-4 text-sm text-gray-900 outline-none transition-colors placeholder:text-gray-400 focus:border-primary-500 focus:ring-2 focus:ring-primary-500/20 dark:border-gray-700 dark:bg-gray-900 dark:text-white dark:placeholder:text-gray-500 dark:focus:border-primary-400" + /> +
+ ); +} diff --git a/src/data/methods.ts b/src/data/methods.ts new file mode 100644 index 0000000..845d966 --- /dev/null +++ b/src/data/methods.ts @@ -0,0 +1,528 @@ +import type { DietMethod } from "@/types/method"; + +export const methods: DietMethod[] = [ + // === ДИЕТЫ === + { + id: "1", + slug: "intermittent-fasting", + title: "Интервальное голодание", + shortDescription: "Чередование периодов приёма пищи и голодания (16/8, 5:2 и др.)", + fullDescription: + "Интервальное голодание — это режим питания, при котором вы чередуете окна приёма пищи с периодами голодания. Самая популярная схема — 16/8: вы едите в течение 8 часов и голодаете 16 часов. Другой вариант — 5:2, при котором 5 дней вы питаетесь нормально, а 2 дня ограничиваете калории до 500–600. Метод не предписывает конкретные продукты, а фокусируется на времени приёма пищи. Исследования показывают положительное влияние на метаболизм, чувствительность к инсулину и аутофагию.", + category: "diet", + difficulty: "medium", + effectiveness: "high", + timeframe: "Результаты через 2–4 недели", + pros: [ + "Не требует подсчёта калорий", + "Гибкость в выборе продуктов", + "Улучшает чувствительность к инсулину", + "Стимулирует аутофагию (клеточное обновление)", + "Бесплатно — не нужны добавки или спецпродукты", + ], + cons: [ + "Первые дни могут быть тяжёлыми из-за голода", + "Не подходит людям с расстройствами пищевого поведения", + "Может вызвать переедание в окно питания", + "Не рекомендуется беременным и кормящим", + ], + scientificBasis: + "Мета-анализ 2020 г. (New England Journal of Medicine) подтвердил эффективность для снижения веса и улучшения метаболических маркеров. Исследования на людях показывают снижение веса на 3–8% за 3–24 недели.", + contraindications: [ + "Сахарный диабет 1 типа", + "Расстройства пищевого поведения", + "Беременность и кормление грудью", + "Дефицит массы тела", + ], + samplePlan: + "Схема 16/8: завтрак в 12:00, обед в 15:00, ужин до 20:00. Между приёмами пищи — вода, чай, кофе без сахара.", + tags: ["голодание", "метаболизм", "инсулин", "аутофагия"], + }, + { + id: "2", + slug: "mediterranean-diet", + title: "Средиземноморская диета", + shortDescription: "Питание на основе овощей, фруктов, оливкового масла и рыбы", + fullDescription: + "Средиземноморская диета основана на традиционном питании жителей Греции, Италии и Испании. Основу составляют овощи, фрукты, цельные злаки, бобовые, орехи, оливковое масло и рыба. Красное мясо и сладости ограничены. Это не столько диета для похудения, сколько долгосрочный здоровый стиль питания, который снижает риск сердечно-сосудистых заболеваний, диабета 2 типа и некоторых видов рака.", + category: "diet", + difficulty: "easy", + effectiveness: "high", + timeframe: "Постепенное снижение веса, устойчивый результат за 3–6 месяцев", + pros: [ + "Научно доказанная польза для сердца", + "Разнообразное и вкусное питание", + "Не требует строгих ограничений", + "Снижает риск хронических заболеваний", + "Подходит для длительного применения", + ], + cons: [ + "Относительно дорогие продукты (рыба, оливковое масло, орехи)", + "Медленное снижение веса", + "Требует навыков приготовления пищи", + ], + scientificBasis: + "Исследование PREDIMED (2013, NEJM) с 7447 участниками показало снижение сердечно-сосудистых рисков на 30%. Признана лучшей диетой по рейтингу U.S. News & World Report несколько лет подряд.", + contraindications: [ + "Аллергия на морепродукты или орехи (можно адаптировать)", + ], + samplePlan: + "Завтрак: овсянка с ягодами и орехами. Обед: салат с тунцом и оливковым маслом. Ужин: запечённая рыба с овощами гриль.", + tags: ["средиземноморье", "сердце", "оливковое масло", "рыба"], + }, + { + id: "3", + slug: "keto-diet", + title: "Кетогенная диета (кето)", + shortDescription: "Высокожировая, низкоуглеводная диета для введения организма в кетоз", + fullDescription: + "Кетогенная диета предполагает резкое сокращение углеводов (до 20–50 г в день) и увеличение потребления жиров. Когда организм исчерпывает запасы гликогена, он переходит в состояние кетоза — начинает использовать жиры как основной источник энергии, расщепляя их до кетоновых тел. Типичное соотношение макронутриентов: 70–80% жиров, 15–20% белков, 5–10% углеводов.", + category: "diet", + difficulty: "hard", + effectiveness: "high", + timeframe: "Быстрое снижение веса в первые 2 недели, устойчивое — за 1–3 месяца", + pros: [ + "Быстрое снижение веса на начальном этапе", + "Снижение аппетита за счёт кетоза", + "Стабильный уровень сахара в крови", + "Эффективна при эпилепсии и некоторых неврологических заболеваниях", + ], + cons: [ + "Трудно соблюдать длительное время", + "«Кето-грипп» в первые дни адаптации", + "Ограничение фруктов и многих овощей", + "Потенциальные проблемы с холестерином", + "Социальные трудности (ограниченный выбор в ресторанах)", + ], + scientificBasis: + "Мета-анализ 13 РКИ (British Journal of Nutrition, 2013) показал, что кетогенная диета приводит к большему снижению веса, чем низкожировая диета в долгосрочной перспективе.", + contraindications: [ + "Заболевания печени и поджелудочной железы", + "Дефицит карнитина", + "Порфирия", + "Беременность", + ], + samplePlan: + "Завтрак: яичница с авокадо и беконом. Обед: салат с курицей и оливковым маслом. Ужин: стейк из лосося с брокколи в сливочном соусе.", + tags: ["кетоз", "низкоуглеводная", "жиры", "LCHF"], + }, + { + id: "4", + slug: "calorie-deficit", + title: "Дефицит калорий (CICO)", + shortDescription: "Контроль калорийности: потреблять меньше, чем расходуешь", + fullDescription: + "CICO (Calories In, Calories Out) — это фундаментальный принцип снижения веса. Вы создаёте дефицит калорий, потребляя меньше энергии, чем тратите. Рекомендуемый дефицит — 300–500 ккал в день для безопасного снижения веса 0.5–1 кг в неделю. Метод не ограничивает продукты, но требует подсчёта калорий и понимания энергетической ценности пищи.", + category: "diet", + difficulty: "medium", + effectiveness: "high", + timeframe: "0.5–1 кг в неделю при дефиците 500 ккал/день", + pros: [ + "Научно доказанный принцип — работает всегда", + "Гибкость в выборе продуктов", + "Подходит для любого бюджета", + "Обучает пониманию калорийности продуктов", + ], + cons: [ + "Требует постоянного подсчёта калорий", + "Может игнорировать качество питания", + "Утомительно в долгосрочной перспективе", + "Не учитывает гормональный фон", + ], + scientificBasis: + "Закон термодинамики подтверждён тысячами исследований. Мета-анализ в American Journal of Clinical Nutrition показывает, что дефицит калорий — единственный обязательный фактор для снижения веса.", + contraindications: [ + "Расстройства пищевого поведения", + "Дефицит массы тела", + ], + samplePlan: + "Рассчитайте свою норму калорий (TDEE), вычтите 400–500 ккал. Используйте приложение для отслеживания (MyFitnessPal, FatSecret).", + tags: ["калории", "подсчёт", "CICO", "дефицит"], + }, + { + id: "5", + slug: "plant-based", + title: "Растительное питание", + shortDescription: "Диета на основе растительной пищи с минимумом продуктов животного происхождения", + fullDescription: + "Растительное питание (plant-based diet) делает акцент на овощах, фруктах, бобовых, цельных злаках, орехах и семенах. В отличие от строгого веганства, допускает небольшое количество продуктов животного происхождения. Такой подход естественным образом снижает калорийность за счёт высокого содержания клетчатки и воды в растительных продуктах.", + category: "diet", + difficulty: "medium", + effectiveness: "medium", + timeframe: "Устойчивое снижение за 2–6 месяцев", + pros: [ + "Высокое содержание клетчатки и витаминов", + "Снижение риска сердечно-сосудистых заболеваний", + "Экологически более устойчивый подход", + "Естественное снижение калорийности", + ], + cons: [ + "Риск дефицита B12, железа, цинка", + "Требует планирования для получения полного белка", + "Может быть непривычным для любителей мяса", + ], + scientificBasis: + "Исследование в Journal of the American Heart Association (2019) показало снижение риска сердечно-сосудистых заболеваний на 16% у приверженцев растительного питания.", + contraindications: [ + "Тяжёлая анемия (без контроля врача)", + "Детский возраст (без контроля педиатра)", + ], + tags: ["растительная", "вегетарианская", "клетчатка", "овощи"], + }, + + // === ФИЗИЧЕСКАЯ АКТИВНОСТЬ === + { + id: "6", + slug: "hiit-training", + title: "HIIT (Высокоинтенсивные интервальные тренировки)", + shortDescription: "Короткие взрывные упражнения с периодами отдыха", + fullDescription: + "HIIT (High-Intensity Interval Training) — метод тренировок, чередующий короткие периоды максимальной нагрузки (20–60 секунд) с периодами отдыха или низкой интенсивности. Тренировка обычно длится 15–30 минут, но по эффективности сжигания калорий сопоставима с 45–60 минутами кардио. Ключевой эффект — EPOC (избыточное потребление кислорода после тренировки), которое повышает метаболизм на несколько часов после занятия.", + category: "activity", + difficulty: "hard", + effectiveness: "high", + timeframe: "Видимые результаты через 3–4 недели при 3 тренировках в неделю", + pros: [ + "Эффективное сжигание калорий за короткое время", + "Повышение метаболизма на часы после тренировки (EPOC)", + "Не требует оборудования", + "Сохраняет мышечную массу при похудении", + ], + cons: [ + "Высокий риск травм при неправильной технике", + "Не подходит начинающим без подготовки", + "Тяжело психологически", + "Требует полноценного восстановления между тренировками", + ], + scientificBasis: + "Мета-анализ в British Journal of Sports Medicine (2019) показал, что HIIT на 28.5% эффективнее традиционного кардио для снижения жировой массы.", + contraindications: [ + "Сердечно-сосудистые заболевания", + "Проблемы с суставами", + "Нулевая физическая подготовка (сначала базовые тренировки)", + ], + samplePlan: + "Табата 4 мин: 20 сек — берпи, 10 сек — отдых, 8 раундов. Повторить 4 раза с отдыхом 1 мин между блоками.", + tags: ["интервальные", "кардио", "жиросжигание", "EPOC"], + }, + { + id: "7", + slug: "walking-10k", + title: "Ходьба 10 000 шагов", + shortDescription: "Ежедневная ходьба как простой и безопасный способ увеличить расход калорий", + fullDescription: + "Ходьба — самая доступная и безопасная форма физической активности. 10 000 шагов в день (примерно 7–8 км) позволяют сжигать 300–500 дополнительных калорий. Ходьба не создаёт нагрузки на суставы, не требует специальной подготовки и оборудования, и легко вписывается в повседневную жизнь.", + category: "activity", + difficulty: "easy", + effectiveness: "medium", + timeframe: "Постепенное снижение веса за 2–3 месяца", + pros: [ + "Подходит всем возрастам и уровням подготовки", + "Минимальный риск травм", + "Улучшает настроение и снижает стресс", + "Не требует спортзала и оборудования", + "Легко сделать привычкой", + ], + cons: [ + "Медленное снижение веса", + "Может быть скучно", + "Зависимость от погоды (для прогулок на улице)", + "Без диеты эффект ограничен", + ], + scientificBasis: + "Исследование в JAMA Internal Medicine (2019) с 16 741 участниками показало, что увеличение количества шагов до 7 500 в день снижает смертность на 40%.", + contraindications: [ + "Тяжёлые заболевания опорно-двигательного аппарата (консультация врача)", + ], + samplePlan: + "Начните с 5 000 шагов и увеличивайте на 500 шагов каждую неделю. Используйте шагомер или фитнес-браслет.", + tags: ["ходьба", "шаги", "кардио", "повседневная"], + }, + { + id: "8", + slug: "strength-training", + title: "Силовые тренировки", + shortDescription: "Упражнения с отягощениями для роста мышц и ускорения метаболизма", + fullDescription: + "Силовые тренировки (с гантелями, штангой, тренажёрами или собственным весом) увеличивают мышечную массу, что повышает базовый метаболизм. Каждый килограмм мышц сжигает примерно 13 ккал в сутки в покое (против 4.5 ккал для жира). Помимо похудения, силовые тренировки укрепляют кости, суставы, улучшают осанку и функциональную силу.", + category: "activity", + difficulty: "medium", + effectiveness: "high", + timeframe: "Изменение состава тела заметно через 6–8 недель", + pros: [ + "Ускоряет базовый метаболизм", + "Формирует подтянутую фигуру", + "Укрепляет кости (профилактика остеопороза)", + "Эффект сохраняется даже в покое", + "Улучшает осанку и функциональность", + ], + cons: [ + "Требует обучения технике", + "Нужен доступ к оборудованию или спортзалу", + "Вес на весах может не снижаться (рост мышц)", + "Риск травм при неправильной технике", + ], + scientificBasis: + "Мета-анализ в Obesity Reviews (2021) показал, что силовые тренировки значимо снижают процент жира в теле, даже без изменения диеты.", + contraindications: [ + "Острые травмы опорно-двигательного аппарата", + "Нестабильная гипертония", + "Недавно перенесённые операции", + ], + samplePlan: + "3 тренировки в неделю: понедельник — верх тела, среда — низ тела, пятница — всё тело. 3–4 подхода по 8–12 повторений.", + tags: ["мышцы", "тренажёрный зал", "метаболизм", "сила"], + }, + + // === ОБРАЗ ЖИЗНИ === + { + id: "9", + slug: "sleep-optimization", + title: "Оптимизация сна", + shortDescription: "Качественный сон 7–9 часов как основа здорового метаболизма", + fullDescription: + "Недосыпание (менее 7 часов) повышает уровень грелина (гормон голода) и снижает лептин (гормон сытости), что приводит к перееданию. Кроме того, недостаток сна повышает кортизол и снижает чувствительность к инсулину. Оптимизация сна — это один из самых недооценённых инструментов для контроля веса.", + category: "lifestyle", + difficulty: "easy", + effectiveness: "medium", + timeframe: "Улучшение метаболизма за 1–2 недели нормализации сна", + pros: [ + "Бесплатно", + "Улучшает не только вес, но и общее здоровье", + "Снижает тягу к сладкому и жирному", + "Повышает энергию для тренировок", + ], + cons: [ + "Может быть сложно при загруженном графике", + "Требует дисциплины и изменения привычек", + "Результат не мгновенный", + ], + scientificBasis: + "Исследование в Annals of Internal Medicine (2010) показало, что при недосыпании 55% потери веса приходится на мышцы вместо жира.", + contraindications: [ + "Расстройства сна требуют консультации врача (апноэ, бессонница)", + ], + samplePlan: + "Ложитесь и вставайте в одно время. За 1 час до сна: нет экранов, нет кофеина после 14:00, прохладная комната (18–20°C).", + tags: ["сон", "восстановление", "гормоны", "грелин"], + }, + { + id: "10", + slug: "mindful-eating", + title: "Осознанное питание", + shortDescription: "Практика внимательного отношения к еде: есть медленно, чувствовать сигналы тела", + fullDescription: + "Осознанное питание (mindful eating) — это практика полного присутствия во время еды. Вы едите медленно, обращаете внимание на вкус, текстуру, запах пищи, и учитесь различать физический голод от эмоционального. Метод помогает естественным образом уменьшить порции без ощущения ограничения.", + category: "lifestyle", + difficulty: "easy", + effectiveness: "medium", + timeframe: "Формирование привычки за 3–4 недели, снижение веса за 2–3 месяца", + pros: [ + "Не требует ограничений в продуктах", + "Улучшает отношения с едой", + "Снижает эмоциональное переедание", + "Помогает наслаждаться едой больше", + ], + cons: [ + "Требует постоянной практики", + "Медленный эффект на вес", + "Трудно практиковать в спешке", + ], + scientificBasis: + "Систематический обзор в Obesity Reviews (2014) показал, что практики осознанного питания снижают частоту переедания и эмоционального еды.", + contraindications: [], + samplePlan: + "Ешьте без телефона и ТВ. Жуйте каждый кусочек 20–30 раз. Делайте паузу в середине приёма пищи, чтобы оценить голод.", + tags: ["осознанность", "медитация", "порции", "привычки"], + }, + { + id: "11", + slug: "water-intake", + title: "Водный режим", + shortDescription: "Достаточное потребление воды для поддержания метаболизма и контроля аппетита", + fullDescription: + "Вода участвует во всех метаболических процессах. Стакан воды перед едой снижает количество потребляемых калорий на 75–90 ккал за приём пищи. Часто жажда маскируется под голод, и достаточное потребление воды (30 мл на кг веса) помогает избежать лишних перекусов. Холодная вода дополнительно сжигает калории на нагрев.", + category: "lifestyle", + difficulty: "easy", + effectiveness: "low", + timeframe: "Вспомогательный метод, усиливает другие подходы", + pros: [ + "Максимально просто и доступно", + "Помогает отличить голод от жажды", + "Улучшает состояние кожи и пищеварение", + "Усиливает эффект других методов", + ], + cons: [ + "Сам по себе не приведёт к значительному похудению", + "Избыток воды может быть вреден", + "Частые походы в туалет", + ], + scientificBasis: + "Исследование в Obesity (2010): 500 мл воды перед каждым приёмом пищи помогло участникам похудеть на 44% больше за 12 недель.", + contraindications: [ + "Заболевания почек (консультация врача о нормах)", + "Сердечная недостаточность", + ], + samplePlan: + "Выпивайте стакан воды сразу после пробуждения. Пейте 500 мл за 30 мин до каждого приёма пищи. Норма: 30 мл × вес тела в кг.", + tags: ["вода", "гидратация", "метаболизм", "аппетит"], + }, + + // === ПСИХОЛОГИЯ === + { + id: "12", + slug: "cognitive-behavioral-therapy", + title: "Когнитивно-поведенческая терапия (КПТ)", + shortDescription: "Работа с пищевыми привычками и мышлением через психотерапию", + fullDescription: + "КПТ для управления весом фокусируется на выявлении и изменении деструктивных мыслей и поведенческих паттернов, связанных с едой. Терапевт помогает осознать триггеры переедания, изменить отношение к еде и телу, сформировать устойчивые здоровые привычки. Это один из самых эффективных подходов для долгосрочного поддержания результата.", + category: "psychology", + difficulty: "medium", + effectiveness: "high", + timeframe: "12–20 сеансов, устойчивый эффект до нескольких лет", + pros: [ + "Работает с корневыми причинами переедания", + "Долгосрочный устойчивый результат", + "Улучшает общее психическое здоровье", + "Научно доказанная эффективность", + ], + cons: [ + "Требует работы со специалистом", + "Стоимость терапии", + "Требует времени и усилий", + "Не даёт быстрого результата на весах", + ], + scientificBasis: + "Кокрановский обзор (2005, обновлён 2018) подтверждает эффективность КПТ для лечения переедания и поддержания здорового веса в долгосрочной перспективе.", + contraindications: [], + samplePlan: + "Найдите психолога, специализирующегося на КПТ и пищевом поведении. Курс обычно 12–20 еженедельных сеансов.", + tags: ["психология", "терапия", "привычки", "переедание"], + }, + { + id: "13", + slug: "habit-stacking", + title: "Наслаивание привычек (Habit Stacking)", + shortDescription: "Привязка новых здоровых привычек к уже существующим", + fullDescription: + "Метод наслаивания привычек основан на нейробиологии формирования привычек. Вы привязываете новое желаемое действие к уже существующей привычке: «После того как я [существующая привычка], я сделаю [новая привычка]». Например: «После того как я налью утренний кофе, я сделаю 10 приседаний». Этот метод использует уже сформированные нейронные пути, что значительно облегчает формирование новых привычек.", + category: "psychology", + difficulty: "easy", + effectiveness: "medium", + timeframe: "Формирование устойчивой привычки за 21–66 дней", + pros: [ + "Очень просто начать", + "Использует уже существующие привычки", + "Постепенное наращивание без стресса", + "Научное обоснование (нейропластичность)", + ], + cons: [ + "Медленное накопление эффекта", + "Требует последовательности", + "Не даёт быстрого результата", + ], + scientificBasis: + "Основан на исследованиях нейропластичности и работах BJ Fogg (Stanford). Исследование в European Journal of Social Psychology (2009) показало, что среднее время формирования привычки — 66 дней.", + contraindications: [], + samplePlan: + "Составьте список из 3 привычек: 1) После пробуждения — стакан воды. 2) После обеда — 10-минутная прогулка. 3) После ужина — 5 минут растяжки.", + tags: ["привычки", "мотивация", "психология", "постепенность"], + }, + + // === МЕДИЦИНСКИЕ === + { + id: "14", + slug: "medical-supervision", + title: "Похудение под наблюдением врача", + shortDescription: "Программа снижения веса с медицинским контролем и анализами", + fullDescription: + "Медицинская программа снижения веса включает: обследование (анализы крови, оценка гормонов, УЗИ), составление индивидуального плана питания врачом-диетологом, мониторинг прогресса. Может включать фармакотерапию (например, препараты GLP-1: семаглутид, лираглутид). Подход рекомендуется при ожирении (ИМТ > 30) или при наличии сопутствующих заболеваний.", + category: "medical", + difficulty: "medium", + effectiveness: "high", + timeframe: "Индивидуально, обычно программа на 3–12 месяцев", + pros: [ + "Индивидуальный подход", + "Контроль здоровья на каждом этапе", + "Выявление скрытых причин лишнего веса", + "Безопасность при наличии хронических заболеваний", + "Возможность медикаментозной поддержки", + ], + cons: [ + "Высокая стоимость", + "Требует регулярных визитов к врачу", + "Зависимость от препаратов (при фармакотерапии)", + ], + scientificBasis: + "Рандомизированные исследования STEP (2021) показали снижение веса до 15% на семаглутиде за 68 недель. Медицинский контроль снижает риски в 3–5 раз по сравнению с самостоятельным похудением при ожирении.", + contraindications: [ + "Зависит от выбранных препаратов и методов — определяет врач", + ], + samplePlan: + "Запишитесь к эндокринологу. Сдайте: общий анализ крови, ТТГ, инсулин, HbA1c, липидный профиль. Обсудите индивидуальный план.", + tags: ["врач", "эндокринолог", "анализы", "семаглутид"], + }, + { + id: "15", + slug: "elimination-diet", + title: "Элиминационная диета", + shortDescription: "Исключение потенциальных пищевых аллергенов для выявления проблемных продуктов", + fullDescription: + "Элиминационная диета — это диагностический инструмент, при котором из рациона на 2–4 недели исключаются потенциально проблемные продукты (глютен, молочные продукты, яйца, соя, орехи, сахар). Затем продукты по одному возвращаются, с отслеживанием реакции организма. Хроническое воспаление от пищевой непереносимости может замедлять метаболизм и способствовать задержке жидкости.", + category: "medical", + difficulty: "hard", + effectiveness: "medium", + timeframe: "Диагностический период 6–8 недель, далее индивидуально", + pros: [ + "Помогает выявить скрытые пищевые непереносимости", + "Уменьшает воспаление и отёки", + "Улучшает пищеварение и самочувствие", + "Персонализированный подход к питанию", + ], + cons: [ + "Очень ограничительная на начальном этапе", + "Требует дисциплины и ведения дневника", + "Социально затруднительна", + "Риск дефицита нутриентов без контроля", + ], + scientificBasis: + "NICE guidelines рекомендуют элиминационные диеты для диагностики пищевой непереносимости. Исследования показывают, что до 20% населения имеют ту или иную пищевую непереносимость.", + contraindications: [ + "Расстройства пищевого поведения", + "Дефицит массы тела", + "Дети без контроля педиатра", + ], + samplePlan: + "Фаза 1 (2–4 недели): исключите глютен, молочные, сахар, яйца, сою. Фаза 2: возвращайте по одному продукту каждые 3 дня, записывая реакции.", + tags: ["аллергия", "воспаление", "глютен", "непереносимость"], + }, + { + id: "16", + slug: "stress-management", + title: "Управление стрессом", + shortDescription: "Техники снижения стресса для нормализации кортизола и пищевого поведения", + fullDescription: + "Хронический стресс повышает уровень кортизола — гормона, который стимулирует отложение жира в области живота и усиливает тягу к калорийной пище. Методы управления стрессом (медитация, дыхательные упражнения, йога, прогулки на природе) помогают нормализовать гормональный фон и снизить эмоциональное переедание.", + category: "psychology", + difficulty: "easy", + effectiveness: "medium", + timeframe: "Снижение кортизола за 2–4 недели регулярной практики", + pros: [ + "Улучшает общее качество жизни", + "Снижает кортизол и висцеральный жир", + "Множество бесплатных методов", + "Помогает от эмоционального переедания", + ], + cons: [ + "Не работает как единственный метод похудения", + "Требует регулярной практики", + "Эффект на вес косвенный", + ], + scientificBasis: + "Исследование в Obesity (2017) показало, что программа снижения стресса с медитацией привела к значимому уменьшению висцерального жира без изменения диеты.", + contraindications: [], + samplePlan: + "Утро: 10 минут медитации (приложение Headspace/Calm). Обед: 5 минут дыхания 4-7-8. Вечер: 15 минут прогулки.", + tags: ["стресс", "кортизол", "медитация", "йога"], + }, +]; diff --git a/src/hooks/useBookmarks.ts b/src/hooks/useBookmarks.ts new file mode 100644 index 0000000..e255c49 --- /dev/null +++ b/src/hooks/useBookmarks.ts @@ -0,0 +1,22 @@ +import { useCallback } from "react"; +import { useLocalStorage } from "@/hooks/useLocalStorage"; + +export function useBookmarks() { + const [bookmarks, setBookmarks] = useLocalStorage("bookmarks", []); + + const toggleBookmark = useCallback( + (id: string) => { + setBookmarks((prev) => + prev.includes(id) ? prev.filter((b) => b !== id) : [...prev, id], + ); + }, + [setBookmarks], + ); + + const isBookmarked = useCallback( + (id: string) => bookmarks.includes(id), + [bookmarks], + ); + + return { bookmarks, toggleBookmark, isBookmarked }; +} diff --git a/src/hooks/useLocalStorage.ts b/src/hooks/useLocalStorage.ts new file mode 100644 index 0000000..47382a9 --- /dev/null +++ b/src/hooks/useLocalStorage.ts @@ -0,0 +1,25 @@ +import { useState, useCallback } from "react"; + +export function useLocalStorage(key: string, initialValue: T) { + const [storedValue, setStoredValue] = useState(() => { + try { + const item = window.localStorage.getItem(key); + return item ? (JSON.parse(item) as T) : initialValue; + } catch { + return initialValue; + } + }); + + const setValue = useCallback( + (value: T | ((val: T) => T)) => { + setStoredValue((prev) => { + const valueToStore = value instanceof Function ? value(prev) : value; + window.localStorage.setItem(key, JSON.stringify(valueToStore)); + return valueToStore; + }); + }, + [key], + ); + + return [storedValue, setValue] as const; +} diff --git a/src/pages/BmiCalculatorPage.tsx b/src/pages/BmiCalculatorPage.tsx new file mode 100644 index 0000000..40ec1ee --- /dev/null +++ b/src/pages/BmiCalculatorPage.tsx @@ -0,0 +1,149 @@ +import { useState } from "react"; +import { clsx } from "clsx"; +import { calculateBmi, type BmiResult } from "@/utils/bmi"; + +export function BmiCalculatorPage() { + const [weight, setWeight] = useState(""); + const [height, setHeight] = useState(""); + const [result, setResult] = useState(null); + + const handleCalculate = (e: React.FormEvent) => { + e.preventDefault(); + const w = parseFloat(weight); + const h = parseFloat(height); + if (w > 0 && h > 0) { + setResult(calculateBmi(w, h)); + } + }; + + const handleReset = () => { + setWeight(""); + setHeight(""); + setResult(null); + }; + + return ( +
+
+

+ Калькулятор BMI +

+

+ Индекс массы тела (BMI) — показатель соотношения веса и роста. Помогает + оценить, находится ли ваш вес в здоровом диапазоне. +

+ +
+
+
+ + setWeight(e.target.value)} + placeholder="70" + required + className="w-full rounded-xl border border-gray-300 bg-white px-4 py-3 text-gray-900 outline-none transition-colors focus:border-primary-500 focus:ring-2 focus:ring-primary-500/20 dark:border-gray-700 dark:bg-gray-900 dark:text-white" + /> +
+
+ + setHeight(e.target.value)} + placeholder="175" + required + className="w-full rounded-xl border border-gray-300 bg-white px-4 py-3 text-gray-900 outline-none transition-colors focus:border-primary-500 focus:ring-2 focus:ring-primary-500/20 dark:border-gray-700 dark:bg-gray-900 dark:text-white" + /> +
+
+ +
+ + {result && ( + + )} +
+
+ + {/* Result */} + {result && ( +
+
+

Ваш BMI

+

+ {result.value} +

+

+ {result.category} +

+
+ +
+ {result.recommendation} +
+ + {/* BMI Scale */} +
+

+ Шкала BMI +

+
+
+
+
+
+
+
+
+ 16 + 18.5 + 25 + 30 + 35 + 40+ +
+
+
+ )} + + {/* Info */} +
+ Важно: BMI — приблизительный показатель. Он не учитывает + мышечную массу, возраст, пол и распределение жира. Для полной оценки + здоровья обратитесь к врачу. +
+
+
+ ); +} diff --git a/src/pages/MethodDetailPage.tsx b/src/pages/MethodDetailPage.tsx new file mode 100644 index 0000000..e7fd21d --- /dev/null +++ b/src/pages/MethodDetailPage.tsx @@ -0,0 +1,161 @@ +import { useParams, Link } from "react-router-dom"; +import { methods } from "@/data/methods"; +import { CATEGORIES } from "@/types/method"; +import { useBookmarks } from "@/hooks/useBookmarks"; +import { Badge } from "@/components/Badge"; +import { DifficultyBadge } from "@/components/DifficultyBadge"; +import { EffectivenessIndicator } from "@/components/EffectivenessIndicator"; +import { BookmarkButton } from "@/components/BookmarkButton"; + +export function MethodDetailPage() { + const { slug } = useParams<{ slug: string }>(); + const { isBookmarked, toggleBookmark } = useBookmarks(); + + const method = methods.find((m) => m.slug === slug); + + if (!method) { + return ( +
+

Методика не найдена

+ + Вернуться к списку + +
+ ); + } + + const category = CATEGORIES[method.category]; + + return ( +
+ {/* Breadcrumb */} + + +
+ {/* Header */} +
+
+ + {category.label} + + toggleBookmark(method.id)} + size="md" + /> +
+

+ {method.title} +

+

+ {method.shortDescription} +

+
+ + {method.timeframe} +
+
+ + {/* Description */} +
+

{method.fullDescription}

+
+ + {/* Pros & Cons */} +
+
+
    + {method.pros.map((pro) => ( +
  • + + {pro} +
  • + ))} +
+
+ +
+
    + {method.cons.map((con) => ( +
  • + + {con} +
  • + ))} +
+
+
+ + {/* Scientific Basis */} +
+
+ {method.scientificBasis} +
+
+ + {/* Contraindications */} + {method.contraindications.length > 0 && ( +
+
+
    + {method.contraindications.map((c) => ( +
  • + + {c} +
  • + ))} +
+
+
+ )} + + {/* Sample Plan */} + {method.samplePlan && ( +
+
+ {method.samplePlan} +
+
+ )} + + {/* Tags */} +
+ {method.tags.map((tag) => ( + + #{tag} + + ))} +
+ + {/* Back link */} +
+ + + Все методики + +
+
+
+ ); +} + +function Section({ title, children }: { title: string; children: React.ReactNode }) { + return ( +
+

{title}

+ {children} +
+ ); +} diff --git a/src/pages/MethodsPage.tsx b/src/pages/MethodsPage.tsx new file mode 100644 index 0000000..59cdcb2 --- /dev/null +++ b/src/pages/MethodsPage.tsx @@ -0,0 +1,109 @@ +import { useState, useMemo } from "react"; +import { methods } from "@/data/methods"; +import { useBookmarks } from "@/hooks/useBookmarks"; +import { MethodCard } from "@/components/MethodCard"; +import { SearchBar } from "@/components/SearchBar"; +import { FilterPanel } from "@/components/FilterPanel"; +import type { CategoryId, Difficulty, Effectiveness } from "@/types/method"; + +function toggleInArray(arr: T[], item: T): T[] { + return arr.includes(item) ? arr.filter((i) => i !== item) : [...arr, item]; +} + +export function MethodsPage() { + const [search, setSearch] = useState(""); + const [selectedCategories, setSelectedCategories] = useState([]); + const [selectedDifficulties, setSelectedDifficulties] = useState([]); + const [selectedEffectiveness, setSelectedEffectiveness] = useState([]); + const [showBookmarkedOnly, setShowBookmarkedOnly] = useState(false); + const { isBookmarked, toggleBookmark } = useBookmarks(); + + const filtered = useMemo(() => { + const query = search.toLowerCase().trim(); + return methods.filter((m) => { + if (query) { + const searchable = [m.title, m.shortDescription, ...m.tags].join(" ").toLowerCase(); + if (!searchable.includes(query)) return false; + } + if (selectedCategories.length > 0 && !selectedCategories.includes(m.category)) return false; + if (selectedDifficulties.length > 0 && !selectedDifficulties.includes(m.difficulty)) return false; + if (selectedEffectiveness.length > 0 && !selectedEffectiveness.includes(m.effectiveness)) return false; + if (showBookmarkedOnly && !isBookmarked(m.id)) return false; + return true; + }); + }, [search, selectedCategories, selectedDifficulties, selectedEffectiveness, showBookmarkedOnly, isBookmarked]); + + const resetFilters = () => { + setSelectedCategories([]); + setSelectedDifficulties([]); + setSelectedEffectiveness([]); + setShowBookmarkedOnly(false); + }; + + return ( +
+
+

Методики

+

+ {methods.length} методик похудения и поддержания здорового веса +

+
+ +
+ {/* Sidebar */} + + + {/* Grid */} +
+ {filtered.length === 0 ? ( +
+

Ничего не найдено

+ +
+ ) : ( +
+ {filtered.map((method) => ( + + ))} +
+ )} +

+ Показано {filtered.length} из {methods.length} +

+
+
+
+ ); +} diff --git a/src/types/method.ts b/src/types/method.ts new file mode 100644 index 0000000..f1c04b4 --- /dev/null +++ b/src/types/method.ts @@ -0,0 +1,43 @@ +export interface DietMethod { + id: string; + slug: string; + title: string; + shortDescription: string; + fullDescription: string; + category: CategoryId; + difficulty: Difficulty; + effectiveness: Effectiveness; + timeframe: string; + pros: string[]; + cons: string[]; + scientificBasis: string; + contraindications: string[]; + samplePlan?: string; + tags: string[]; +} + +export const CATEGORIES = { + diet: { id: "diet", label: "Диеты", icon: "\u{1F957}", color: "emerald" }, + activity: { id: "activity", label: "Физическая активность", icon: "\u{1F3CB}\u{FE0F}", color: "blue" }, + lifestyle: { id: "lifestyle", label: "Образ жизни", icon: "\u{1F331}", color: "violet" }, + psychology: { id: "psychology", label: "Психология", icon: "\u{1F9E0}", color: "amber" }, + medical: { id: "medical", label: "Медицинские", icon: "\u{2695}\u{FE0F}", color: "rose" }, +} as const; + +export type CategoryId = keyof typeof CATEGORIES; + +export const DIFFICULTIES = { + easy: { id: "easy", label: "Легко", order: 1 }, + medium: { id: "medium", label: "Средне", order: 2 }, + hard: { id: "hard", label: "Сложно", order: 3 }, +} as const; + +export type Difficulty = keyof typeof DIFFICULTIES; + +export const EFFECTIVENESS_LEVELS = { + low: { id: "low", label: "Низкая", order: 1 }, + medium: { id: "medium", label: "Средняя", order: 2 }, + high: { id: "high", label: "Высокая", order: 3 }, +} as const; + +export type Effectiveness = keyof typeof EFFECTIVENESS_LEVELS; diff --git a/src/utils/bmi.ts b/src/utils/bmi.ts new file mode 100644 index 0000000..9c00a04 --- /dev/null +++ b/src/utils/bmi.ts @@ -0,0 +1,67 @@ +export interface BmiResult { + value: number; + category: string; + color: string; + recommendation: string; +} + +export function calculateBmi(weightKg: number, heightCm: number): BmiResult { + const heightM = heightCm / 100; + const bmi = weightKg / (heightM * heightM); + const value = Math.round(bmi * 10) / 10; + + if (value < 16) { + return { + value, + category: "Выраженный дефицит массы тела", + color: "text-red-600", + recommendation: "Необходимо срочно обратиться к врачу. Недостаточная масса тела опасна для здоровья.", + }; + } + if (value < 18.5) { + return { + value, + category: "Дефицит массы тела", + color: "text-orange-500", + recommendation: "Рекомендуется увеличить калорийность рациона и обратиться к диетологу.", + }; + } + if (value < 25) { + return { + value, + category: "Нормальная масса тела", + color: "text-green-600", + recommendation: "Отличный показатель! Поддерживайте текущий вес с помощью сбалансированного питания и активности.", + }; + } + if (value < 30) { + return { + value, + category: "Избыточная масса тела (предожирение)", + color: "text-yellow-600", + recommendation: "Рекомендуется скорректировать питание и увеличить физическую активность. Ознакомьтесь с методиками в нашем справочнике.", + }; + } + if (value < 35) { + return { + value, + category: "Ожирение I степени", + color: "text-orange-600", + recommendation: "Рекомендуется обратиться к врачу и составить план снижения веса. Изучите методики под медицинским контролем.", + }; + } + if (value < 40) { + return { + value, + category: "Ожирение II степени", + color: "text-red-500", + recommendation: "Необходимо обратиться к эндокринологу. Снижение веса под медицинским наблюдением.", + }; + } + return { + value, + category: "Ожирение III степени (морбидное)", + color: "text-red-700", + recommendation: "Необходима медицинская помощь. Обратитесь к специалисту для составления программы лечения.", + }; +} diff --git a/tsconfig.app.tsbuildinfo b/tsconfig.app.tsbuildinfo index ecb1d6d..2786e98 100644 --- a/tsconfig.app.tsbuildinfo +++ b/tsconfig.app.tsbuildinfo @@ -1 +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"} \ No newline at end of file +{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/badge.tsx","./src/components/bookmarkbutton.tsx","./src/components/difficultybadge.tsx","./src/components/effectivenessindicator.tsx","./src/components/filterpanel.tsx","./src/components/footer.tsx","./src/components/header.tsx","./src/components/layout.tsx","./src/components/methodcard.tsx","./src/components/searchbar.tsx","./src/data/methods.ts","./src/hooks/usebookmarks.ts","./src/hooks/uselocalstorage.ts","./src/pages/bmicalculatorpage.tsx","./src/pages/homepage.tsx","./src/pages/methoddetailpage.tsx","./src/pages/methodspage.tsx","./src/types/method.ts","./src/utils/bmi.ts"],"version":"5.9.3"} \ No newline at end of file