feat: comprehensive light theme support across entire site

- CSS foundation: theme-aware scrollbars, section glows, glass cards with
  gold shadows, stronger animated borders and glow effects for light mode
- Hero: consistent dark-video treatment for both themes, brighter gold
  gradient text, glowing CTA button
- Gradient text: auto-switch to warm gold tones on light backgrounds via
  html:not(.dark) selector
- Team profile: inverted ambient photo bg with white overlay for light,
  dark text/borders, gold-dark labels for contrast
- All sections: text-neutral-500→600 upgrades for WCAG AA contrast,
  gold shadow accents on cards (About, Pricing, FAQ, DayCard, News)
- Admin: replaced hardcoded #c9a96e with theme tokens, fixed select
  options, array editor borders, booking badges contrast
- Header: white text on transparent hero, dark text after scroll
- UI components: BackToTop, FloatingHearts, ShowcaseLayout tabs,
  SignupModal, NewsModal, GroupCard adapted for light backgrounds
- Updated CLAUDE.md to reflect dual theme support
This commit is contained in:
2026-04-10 21:30:56 +03:00
parent a587736dd3
commit 0e626451e7
36 changed files with 380 additions and 285 deletions
+4 -3
View File
@@ -7,7 +7,7 @@ Content language: Russian
## Tech Stack
- **Next.js 16** (App Router, TypeScript, Turbopack)
- **Tailwind CSS v4** (dark mode only, gold/black theme)
- **Tailwind CSS v4** (dual theme: dark default + light, gold accent)
- **lucide-react** for icons
- **better-sqlite3** for SQLite database
- **Fonts**: Inter (body) + Oswald (headings) via `next/font`
@@ -111,8 +111,9 @@ src/
## Brand / Styling
- **Accent**: gold (`#c9a96e` / `hsl(37, 42%, 61%)`)
- **Background**: `#050505` `#0a0a0a` (dark only)
- **Surface**: `#171717` dark cards
- **Dark theme** (default): background `#050505``#0a0a0a`, surface `#171717`, text `neutral-100`
- **Light theme**: background `white`/`neutral-50`, surface `white`, text `neutral-900`
- Theme toggle via `ThemeToggle` component, `.dark` class on `<html>`, stored in `localStorage`
- Logo: transparent PNG heart with gold glow, uses `unoptimized`
## Content Data
+15 -17
View File
@@ -190,15 +190,15 @@ export function ArrayEditor<T>({
<div
key={getStableKey(i)}
ref={(el) => { itemRefs.current[i] = el; }}
className={`rounded-lg border bg-neutral-900/50 mb-3 hover:border-white/25 hover:bg-neutral-800/50 focus-within:border-gold/50 focus-within:bg-neutral-800 transition-all ${
newItemIndex === i || droppedIndex === i ? "border-gold/40 ring-1 ring-gold/20" : "border-white/10"
className={`rounded-lg border bg-neutral-100/80 mb-3 hover:border-neutral-300 dark:hover:border-white/25 hover:bg-neutral-200/50 focus-within:border-gold/50 focus-within:bg-neutral-200 transition-all dark:bg-neutral-900/50 dark:hover:bg-neutral-800/50 dark:focus-within:bg-neutral-800 ${
newItemIndex === i || droppedIndex === i ? "border-gold/40 ring-1 ring-gold/20" : "border-neutral-200 dark:border-white/10"
} ${isHidden ? "hidden" : ""}`}
>
{inline ? (
/* Inline: grip + content + delete on one row */
<div className="flex items-start gap-1.5 p-1.5">
<div
className="cursor-grab active:cursor-grabbing rounded p-1 mt-1.5 text-neutral-500 hover:text-white transition-colors select-none shrink-0"
className="cursor-grab active:cursor-grabbing rounded p-1 mt-1.5 text-neutral-500 hover:text-neutral-900 transition-colors select-none shrink-0 dark:hover:text-white"
onMouseDown={(e) => handleGripMouseDown(e, i)}
aria-label="Перетащить для сортировки"
role="button"
@@ -222,7 +222,7 @@ export function ArrayEditor<T>({
<div className={`flex items-center justify-between gap-2 p-4 ${isCollapsed ? "" : "pb-0 mb-3"}`}>
<div className="flex items-center gap-2 flex-1 min-w-0">
<div
className="cursor-grab active:cursor-grabbing rounded p-1 text-neutral-500 hover:text-white transition-colors select-none"
className="cursor-grab active:cursor-grabbing rounded p-1 text-neutral-500 hover:text-neutral-900 transition-colors select-none dark:hover:text-white"
onMouseDown={(e) => handleGripMouseDown(e, i)}
aria-label="Перетащить для сортировки"
role="button"
@@ -236,7 +236,7 @@ export function ArrayEditor<T>({
aria-expanded={!isCollapsed}
className="flex items-center gap-2 flex-1 min-w-0 text-left cursor-pointer group"
>
<span className="text-sm font-medium text-neutral-300 truncate group-hover:text-white transition-colors">{title}</span>
<span className="text-sm font-medium text-neutral-700 truncate group-hover:text-neutral-900 transition-colors dark:text-neutral-300 dark:group-hover:text-white">{title}</span>
{getItemBadge?.(item, i)}
<ChevronDown size={14} className={`text-neutral-500 transition-transform duration-200 shrink-0 ${isCollapsed ? "" : "rotate-180"}`} />
</button>
@@ -304,13 +304,11 @@ export function ArrayEditor<T>({
<div
key={getStableKey(i)}
ref={(el) => { itemRefs.current[i] = el; }}
className={`rounded-lg border bg-neutral-900/50 mb-3 transition-colors ${
"border-white/10"
}`}
className="rounded-lg border border-neutral-200 bg-neutral-100/80 mb-3 transition-colors dark:border-white/10 dark:bg-neutral-900/50"
>
{inline ? (
<div className="flex items-start gap-1.5 p-1.5">
<div className="cursor-grab active:cursor-grabbing rounded p-1 mt-1.5 text-neutral-500 hover:text-white transition-colors select-none shrink-0"
<div className="cursor-grab active:cursor-grabbing rounded p-1 mt-1.5 text-neutral-500 hover:text-neutral-900 transition-colors select-none shrink-0 dark:hover:text-white"
onMouseDown={(e) => handleGripMouseDown(e, i)} aria-label="Перетащить для сортировки" role="button">
<GripVertical size={14} />
</div>
@@ -327,7 +325,7 @@ export function ArrayEditor<T>({
<div className={`flex items-center justify-between gap-2 p-4 ${isCollapsed ? "" : "pb-0 mb-3"}`}>
<div className="flex items-center gap-2 flex-1 min-w-0">
<div
className="cursor-grab active:cursor-grabbing rounded p-1 text-neutral-500 hover:text-white transition-colors select-none"
className="cursor-grab active:cursor-grabbing rounded p-1 text-neutral-500 hover:text-neutral-900 transition-colors select-none dark:hover:text-white"
onMouseDown={(e) => handleGripMouseDown(e, i)}
aria-label="Перетащить для сортировки"
role="button"
@@ -336,7 +334,7 @@ export function ArrayEditor<T>({
</div>
{collapsible && (
<button type="button" onClick={() => toggleCollapse(i)} className="flex items-center gap-2 flex-1 min-w-0 text-left cursor-pointer group">
<span className="text-sm font-medium text-neutral-300 truncate group-hover:text-white transition-colors">{title}</span>
<span className="text-sm font-medium text-neutral-700 truncate group-hover:text-neutral-900 transition-colors dark:text-neutral-300 dark:group-hover:text-white">{title}</span>
{getItemBadge?.(item, i)}
<ChevronDown size={14} className={`text-neutral-500 transition-transform duration-200 shrink-0 ${isCollapsed ? "" : "rotate-180"}`} />
</button>
@@ -384,14 +382,14 @@ export function ArrayEditor<T>({
<div>
{(label || (collapsible && items.length > 1)) && (
<div className="flex items-center justify-between mb-3">
{label ? <h3 className="text-sm font-medium text-neutral-300">{label}</h3> : <div />}
{label ? <h3 className="text-sm font-medium text-neutral-700 dark:text-neutral-300">{label}</h3> : <div />}
{collapsible && items.length > 1 && (() => {
const allCollapsed = collapsed.size >= items.length;
return (
<button
type="button"
onClick={() => allCollapsed ? setCollapsed(new Set()) : setCollapsed(new Set(items.map((_, i) => i)))}
className="rounded p-1 text-neutral-500 hover:text-white transition-colors"
className="rounded p-1 text-neutral-500 hover:text-neutral-900 transition-colors dark:hover:text-white"
title={allCollapsed ? "Развернуть все" : "Свернуть все"}
aria-label={allCollapsed ? "Развернуть все" : "Свернуть все"}
>
@@ -416,7 +414,7 @@ export function ArrayEditor<T>({
return next;
});
}}
className="mb-3 flex items-center gap-2 rounded-lg border border-dashed border-white/20 px-4 py-2.5 text-sm text-neutral-400 hover:text-white hover:border-white/40 transition-colors"
className="mb-3 flex items-center gap-2 rounded-lg border border-dashed border-neutral-300 px-4 py-2.5 text-sm text-neutral-500 hover:text-neutral-900 hover:border-neutral-400 transition-colors dark:border-white/20 dark:text-neutral-400 dark:hover:text-white dark:hover:border-white/40"
>
<Plus size={16} />
{addLabel}
@@ -436,7 +434,7 @@ export function ArrayEditor<T>({
setNewItemIndex(items.length);
setCollapsed(prev => { const next = new Set(prev); next.delete(items.length); return next; });
}}
className="mt-3 flex items-center gap-2 rounded-lg border border-dashed border-white/20 px-4 py-2.5 text-sm text-neutral-400 hover:text-white hover:border-white/40 transition-colors"
className="mt-3 flex items-center gap-2 rounded-lg border border-dashed border-neutral-300 px-4 py-2.5 text-sm text-neutral-500 hover:text-neutral-900 hover:border-neutral-400 transition-colors dark:border-white/20 dark:text-neutral-400 dark:hover:text-white dark:hover:border-white/40"
>
<Plus size={16} />
{addLabel}
@@ -455,9 +453,9 @@ export function ArrayEditor<T>({
height: dragSize.h,
}}
>
<div className="h-full rounded-lg border-2 border-gold/60 bg-neutral-900/95 shadow-2xl shadow-gold/20 flex items-center gap-3 px-4">
<div className="h-full rounded-lg border-2 border-gold/60 bg-white/95 shadow-2xl shadow-gold/20 flex items-center gap-3 px-4 dark:bg-neutral-900/95">
<GripVertical size={16} className="text-gold shrink-0" />
<span className="text-sm text-neutral-300">{collapsible && dragIndex !== null ? (getItemTitle?.(items[dragIndex], dragIndex) || "Перемещение...") : "Перемещение элемента..."}</span>
<span className="text-sm text-neutral-700 dark:text-neutral-300">{collapsible && dragIndex !== null ? (getItemTitle?.(items[dragIndex], dragIndex) || "Перемещение...") : "Перемещение элемента..."}</span>
</div>
</div>,
document.body
@@ -29,15 +29,15 @@ export function CollapsibleSection({
const toggle = onToggle ?? (() => setInternalOpen((v) => !v));
return (
<div className="rounded-xl border border-white/10 bg-neutral-900/30 overflow-hidden">
<div className="rounded-xl border border-neutral-200 bg-neutral-100/50 overflow-hidden dark:border-white/10 dark:bg-neutral-800/50">
<button
type="button"
onClick={toggle}
aria-expanded={open}
className="flex items-center justify-between w-full px-5 py-3.5 text-left cursor-pointer group hover:bg-white/[0.02] transition-colors"
className="flex items-center justify-between w-full px-5 py-3.5 text-left cursor-pointer group hover:bg-neutral-100 transition-colors dark:hover:bg-white/[0.02]"
>
<div className="flex items-center gap-2">
<h3 className="text-sm font-semibold text-neutral-200 group-hover:text-white transition-colors">
<h3 className="text-sm font-semibold text-neutral-700 group-hover:text-neutral-900 transition-colors dark:text-neutral-200 dark:group-hover:text-white">
{title}
</h3>
{count !== undefined && (
@@ -144,27 +144,27 @@ export function GenericBookingsList<T extends BaseBooking>({
const groupCounts = { new: 0, contacted: 0, confirmed: 0, declined: 0 };
for (const item of group.items) groupCounts[item.status] = (groupCounts[item.status] || 0) + 1;
return (
<div key={group.key} className={`rounded-xl border overflow-hidden ${group.isArchived ? "border-white/5 opacity-60" : "border-white/10"}`}>
<div key={group.key} className={`rounded-xl border overflow-hidden ${group.isArchived ? "border-neutral-100 opacity-60 dark:border-white/5" : "border-neutral-200 dark:border-white/10"}`}>
<button
onClick={() => setExpanded((p) => ({ ...p, [group.key]: !isOpen }))}
className={`w-full flex items-center gap-3 px-4 py-3 transition-colors text-left ${group.isArchived ? "bg-neutral-900/50 hover:bg-neutral-800/50" : "bg-neutral-900 hover:bg-neutral-800/80"}`}
className={`w-full flex items-center gap-3 px-4 py-3 transition-colors text-left ${group.isArchived ? "bg-neutral-100/50 hover:bg-neutral-200/50 dark:bg-neutral-900/50 dark:hover:bg-neutral-800/50" : "bg-neutral-50 hover:bg-neutral-200/80 dark:bg-neutral-900 dark:hover:bg-neutral-800/80"}`}
>
{isOpen ? <ChevronDown size={14} className="text-neutral-500 shrink-0" /> : <ChevronRight size={14} className="text-neutral-500 shrink-0" />}
{group.sublabel && (
<span className={`text-xs font-medium shrink-0 ${group.isArchived ? "text-neutral-500" : "text-gold"}`}>{group.sublabel}</span>
)}
<span className={`font-medium text-sm truncate ${group.isArchived ? "text-neutral-400" : "text-white"}`}>{group.label}</span>
<span className={`font-medium text-sm truncate ${group.isArchived ? "text-neutral-500 dark:text-neutral-400" : "text-neutral-900 dark:text-white"}`}>{group.label}</span>
{group.dateBadge && (
<span className={`text-[10px] rounded-full px-2 py-0.5 shrink-0 ${
group.isArchived ? "text-neutral-600 bg-neutral-800 line-through" : "text-gold bg-gold/10"
group.isArchived ? "text-neutral-600 bg-neutral-800 line-through" : "text-amber-700 dark:text-gold bg-gold/10"
}`}>
{group.dateBadge}
</span>
)}
{group.isArchived && (
<span className="text-[10px] text-neutral-600 bg-neutral-800 rounded-full px-2 py-0.5 shrink-0">архив</span>
<span className="text-[10px] text-neutral-500 bg-neutral-200 rounded-full px-2 py-0.5 shrink-0 dark:text-neutral-600 dark:bg-neutral-800">архив</span>
)}
<span className="text-[10px] text-neutral-500 bg-neutral-800 rounded-full px-2 py-0.5 shrink-0">{group.items.length} чел.</span>
<span className="text-[10px] text-neutral-500 bg-neutral-200 rounded-full px-2 py-0.5 shrink-0 dark:bg-neutral-800">{group.items.length} чел.</span>
{!group.isArchived && (
<div className="flex gap-2 ml-auto text-[10px]">
{groupCounts.new > 0 && <span className="text-gold">{groupCounts.new} новых</span>}
+42 -42
View File
@@ -161,43 +161,43 @@ function ConfirmModal({
if (!open) return null;
const selectClass = "w-full rounded-lg border border-white/[0.08] bg-white/[0.04] px-3 py-2 text-sm text-white outline-none focus:border-gold/40 [color-scheme:dark] disabled:opacity-30 disabled:cursor-not-allowed";
const selectClass = "w-full rounded-lg border border-neutral-200 bg-neutral-100 px-3 py-2 text-sm text-neutral-900 outline-none focus:border-gold/40 [color-scheme:light] disabled:opacity-30 disabled:cursor-not-allowed dark:border-white/[0.08] dark:bg-white/[0.04] dark:text-white dark:[color-scheme:dark]";
return createPortal(
<div className="fixed inset-0 z-50 flex items-center justify-center p-4" role="dialog" aria-modal="true" onClick={onClose}>
<div className="absolute inset-0 bg-black/70 backdrop-blur-sm" />
<div className="relative w-full max-w-sm rounded-2xl border border-white/[0.08] bg-[#0a0a0a] p-6 shadow-2xl" onClick={(e) => e.stopPropagation()}>
<button onClick={onClose} aria-label="Закрыть" className="absolute right-3 top-3 flex h-7 w-7 items-center justify-center rounded-full text-neutral-500 hover:bg-white/[0.06] hover:text-white">
<div className="relative w-full max-w-sm rounded-2xl border border-neutral-200 bg-white p-6 shadow-2xl dark:border-white/[0.08] dark:bg-[#0a0a0a]" onClick={(e) => e.stopPropagation()}>
<button onClick={onClose} aria-label="Закрыть" className="absolute right-3 top-3 flex h-7 w-7 items-center justify-center rounded-full text-neutral-500 hover:bg-neutral-100 hover:text-neutral-900 dark:hover:bg-white/[0.06] dark:hover:text-white">
<X size={16} />
</button>
<h3 className="text-base font-bold text-white">Подтвердить запись</h3>
<p className="mt-1 text-xs text-neutral-400">{bookingName}</p>
<h3 className="text-base font-bold text-neutral-900 dark:text-white">Подтвердить запись</h3>
<p className="mt-1 text-xs text-neutral-500 dark:text-neutral-400">{bookingName}</p>
<div className="mt-4 space-y-3">
<div>
<label className="text-[11px] font-medium text-neutral-400 mb-1 block">Зал</label>
<label className="text-[11px] font-medium text-neutral-500 mb-1 block dark:text-neutral-400">Зал</label>
<select value={hall} onChange={(e) => setHall(e.target.value)} className={selectClass}>
<option value="" className="bg-neutral-900">Выберите зал</option>
{halls.map((h) => <option key={h} value={h} className="bg-neutral-900">{h}</option>)}
<option value="" className="bg-white dark:bg-neutral-900">Выберите зал</option>
{halls.map((h) => <option key={h} value={h} className="bg-white dark:bg-neutral-900">{h}</option>)}
</select>
</div>
<div>
<label className="text-[11px] font-medium text-neutral-400 mb-1 block">Тренер</label>
<label className="text-[11px] font-medium text-neutral-500 mb-1 block dark:text-neutral-400">Тренер</label>
<select value={trainer} onChange={(e) => setTrainer(e.target.value)} disabled={!hall} className={selectClass}>
<option value="" className="bg-neutral-900">Выберите тренера</option>
{trainers.map((t) => <option key={t} value={t} className="bg-neutral-900">{t}</option>)}
<option value="" className="bg-white dark:bg-neutral-900">Выберите тренера</option>
{trainers.map((t) => <option key={t} value={t} className="bg-white dark:bg-neutral-900">{t}</option>)}
</select>
</div>
<div>
<label className="text-[11px] font-medium text-neutral-400 mb-1 block">Группа</label>
<label className="text-[11px] font-medium text-neutral-500 mb-1 block dark:text-neutral-400">Группа</label>
<select value={group} onChange={(e) => setGroup(e.target.value)} disabled={!trainer} className={selectClass}>
<option value="" className="bg-neutral-900">Выберите группу</option>
{groups.map((g) => <option key={g.value} value={g.value} className="bg-neutral-900">{g.label}</option>)}
<option value="" className="bg-white dark:bg-neutral-900">Выберите группу</option>
{groups.map((g) => <option key={g.value} value={g.value} className="bg-white dark:bg-neutral-900">{g.label}</option>)}
</select>
</div>
<div>
<label className="text-[11px] font-medium text-neutral-400 mb-1 block">Дата занятия</label>
<label className="text-[11px] font-medium text-neutral-500 mb-1 block dark:text-neutral-400">Дата занятия</label>
<input
type="date"
value={date}
@@ -212,14 +212,14 @@ function ConfirmModal({
)}
</div>
<div>
<label className="text-[11px] font-medium text-neutral-400 mb-1 block">Комментарий <span className="text-neutral-600">(необязательно)</span></label>
<label className="text-[11px] font-medium text-neutral-500 mb-1 block dark:text-neutral-400">Комментарий <span className="text-neutral-600">(необязательно)</span></label>
<input
type="text"
value={comment}
disabled={!group}
onChange={(e) => setComment(e.target.value)}
placeholder="Первое занятие, пробный"
className="w-full rounded-lg border border-white/[0.08] bg-white/[0.04] px-3 py-2 text-sm text-white placeholder-neutral-500 outline-none focus:border-gold/40 disabled:opacity-30 disabled:cursor-not-allowed"
className="w-full rounded-lg border border-neutral-200 bg-neutral-100 px-3 py-2 text-sm text-neutral-900 placeholder-neutral-400 outline-none focus:border-gold/40 disabled:opacity-30 disabled:cursor-not-allowed dark:border-white/[0.08] dark:bg-white/[0.04] dark:text-white dark:placeholder-neutral-500"
/>
</div>
</div>
@@ -318,8 +318,8 @@ function GroupBookingsTab({ filter, onDataChange }: { filter: BookingFilter; onD
onConfirm={(id) => setConfirmingId(id)}
renderExtra={(b) => (
<>
{b.groupInfo && <span className="text-xs text-neutral-400 bg-neutral-800 rounded-full px-2 py-0.5">{b.groupInfo}</span>}
{b.confirmedHall && <span className="text-[10px] text-neutral-500 bg-neutral-800 rounded-full px-2 py-0.5">{b.confirmedHall}</span>}
{b.groupInfo && <span className="text-xs text-neutral-500 bg-neutral-200 rounded-full px-2 py-0.5 dark:text-neutral-400 dark:bg-neutral-800">{b.groupInfo}</span>}
{b.confirmedHall && <span className="text-[10px] text-neutral-500 bg-neutral-200 rounded-full px-2 py-0.5 dark:bg-neutral-800">{b.confirmedHall}</span>}
{(b.confirmedGroup || b.confirmedDate) && (
<button
onClick={(e) => { e.stopPropagation(); setConfirmingId(b.id); }}
@@ -471,11 +471,11 @@ function RemindersTab() {
: currentStatus === "coming" ? "border-emerald-500/15 bg-emerald-500/[0.02]"
: currentStatus === "cancelled" ? "border-red-500/15 bg-red-500/[0.02] opacity-50"
: currentStatus === "pending" ? "border-amber-500/15 bg-amber-500/[0.02]"
: "border-white/5 bg-neutral-800/30"
: "border-neutral-200 bg-neutral-100/30 dark:border-white/5 dark:bg-neutral-800/30"
}`}
>
<div className="flex items-center gap-2 flex-wrap text-sm">
<span className="font-medium text-white">{item.name}</span>
<span className="font-medium text-neutral-900 dark:text-white">{item.name}</span>
{item.phone && (
<a href={`tel:${item.phone}`} className="inline-flex items-center gap-1 text-emerald-400 hover:text-emerald-300 text-xs">
<Phone size={10} />{item.phone}
@@ -542,11 +542,11 @@ function RemindersTab() {
const TypeIcon = typeConf.icon;
const egStats = countByStatus(eg.items);
return (
<div key={eg.label} className="rounded-xl border border-white/10 overflow-hidden">
<div className="flex items-center gap-2 px-4 py-2.5 bg-neutral-900">
<div key={eg.label} className="rounded-xl border border-neutral-200 overflow-hidden dark:border-white/10">
<div className="flex items-center gap-2 px-4 py-2.5 bg-neutral-50 dark:bg-neutral-900">
<TypeIcon size={13} className={typeConf.color} />
<span className="text-sm font-medium text-white">{eg.label}{eg.items[0]?.eventHall ? ` · ${eg.items[0].eventHall}` : ""}</span>
<span className="text-[10px] text-neutral-500 bg-neutral-800 rounded-full px-2 py-0.5">{eg.items.length} чел.</span>
<span className="text-sm font-medium text-neutral-900 dark:text-white">{eg.label}{eg.items[0]?.eventHall ? ` · ${eg.items[0].eventHall}` : ""}</span>
<span className="text-[10px] text-neutral-500 bg-neutral-200 rounded-full px-2 py-0.5 dark:bg-neutral-800">{eg.items.length} чел.</span>
<div className="flex gap-2 ml-auto text-[10px]">
{egStats.coming > 0 && <span className="text-emerald-400">{egStats.coming} придёт</span>}
{egStats.cancelled > 0 && <span className="text-red-400">{egStats.cancelled} не придёт</span>}
@@ -672,15 +672,15 @@ function DashboardSummary({ refreshTrigger, onNavigate, onFilter, activeTab, act
if (c.tab === "reminders") {
const total = counts.remindersToday + counts.remindersTomorrow;
if (total === 0) return (
<div key={c.tab} className="rounded-xl border border-white/5 bg-neutral-900/50 p-3 opacity-40">
<div key={c.tab} className="rounded-xl border border-neutral-100 bg-neutral-50 p-3 opacity-40 dark:border-white/5 dark:bg-neutral-900/50">
<p className="text-xs text-neutral-500">{c.label}</p>
<p className="text-lg font-bold text-neutral-600 mt-1"></p>
<p className="text-lg font-bold text-neutral-400 mt-1 dark:text-neutral-600"></p>
</div>
);
return (
<button key={c.tab} onClick={() => onNavigate(c.tab)}
className={`rounded-xl border ${c.color} bg-neutral-900 p-3 text-left transition-all hover:bg-neutral-800/80 hover:scale-[1.02]`}>
<p className="text-xs text-neutral-400">{c.label}</p>
className={`rounded-xl border ${c.color} bg-neutral-50 p-3 text-left transition-all hover:bg-neutral-100 hover:scale-[1.02] dark:bg-neutral-900 dark:hover:bg-neutral-800/80`}>
<p className="text-xs text-neutral-500 dark:text-neutral-400">{c.label}</p>
<div className="flex items-baseline gap-2 mt-1 flex-wrap">
{counts.remindersNotAsked > 0 && (
<span className="inline-flex items-baseline gap-1 cursor-pointer hover:underline decoration-neutral-500 underline-offset-2 transition-all"
@@ -718,20 +718,20 @@ function DashboardSummary({ refreshTrigger, onNavigate, onFilter, activeTab, act
const tc = c.counts!;
const total = tc.new + tc.contacted + tc.confirmed + tc.declined;
if (total === 0) return (
<div key={c.tab} className="rounded-xl border border-white/5 bg-neutral-900/50 p-3 opacity-40">
<div key={c.tab} className="rounded-xl border border-neutral-100 bg-neutral-50 p-3 opacity-40 dark:border-white/5 dark:bg-neutral-900/50">
<p className="text-xs text-neutral-500">{c.label}</p>
<p className="text-lg font-bold text-neutral-600 mt-1"></p>
<p className="text-lg font-bold text-neutral-400 mt-1 dark:text-neutral-600"></p>
</div>
);
const isActiveCard = activeTab === c.tab;
const hl = (status: BookingFilter) =>
isActiveCard && activeFilter === status
? "rounded-md bg-white/10 px-1.5 -mx-1.5 py-0.5 -my-0.5 ring-1 ring-white/20"
? "rounded-md bg-neutral-200 px-1.5 -mx-1.5 py-0.5 -my-0.5 ring-1 ring-neutral-300 dark:bg-white/10 dark:ring-white/20"
: "";
return (
<button key={c.tab} onClick={() => { onNavigate(c.tab); onFilter("all"); }}
className={`rounded-xl border ${c.color} bg-neutral-900 p-3 text-left transition-all hover:bg-neutral-800/80 hover:scale-[1.02]`}>
<p className="text-xs text-neutral-400">{c.label}</p>
className={`rounded-xl border ${c.color} bg-neutral-50 p-3 text-left transition-all hover:bg-neutral-100 hover:scale-[1.02] dark:bg-neutral-900 dark:hover:bg-neutral-800/80`}>
<p className="text-xs text-neutral-500 dark:text-neutral-400">{c.label}</p>
<div className="flex items-baseline gap-2 mt-1 flex-wrap">
{tc.new > 0 && (
<>
@@ -897,7 +897,7 @@ function BookingsPageInner() {
<button
onClick={() => setHallFilter("all")}
className={`px-3 py-1.5 rounded-lg text-xs transition-colors ${
hallFilter === "all" ? "bg-gold/15 text-gold border border-gold/30" : "text-neutral-500 hover:text-white border border-transparent"
hallFilter === "all" ? "bg-gold/15 text-gold border border-gold/30" : "text-neutral-500 hover:text-neutral-900 border border-transparent dark:hover:text-white"
}`}
>
Все залы
@@ -907,7 +907,7 @@ function BookingsPageInner() {
key={hall}
onClick={() => setHallFilter(hallFilter === hall ? "all" : hall)}
className={`px-3 py-1.5 rounded-lg text-xs transition-colors ${
hallFilter === hall ? "bg-gold/15 text-gold border border-gold/30" : "text-neutral-500 hover:text-white border border-transparent"
hallFilter === hall ? "bg-gold/15 text-gold border border-gold/30" : "text-neutral-500 hover:text-neutral-900 border border-transparent dark:hover:text-white"
}`}
>
{hall}
@@ -929,10 +929,10 @@ function BookingsPageInner() {
<BookingCard key={`${r.type}-${r.id}`} status={r.status as BookingStatus}>
<div className="flex items-start justify-between gap-3">
<div className="flex items-center gap-2 flex-wrap text-sm min-w-0">
<span className="text-[10px] text-neutral-500 bg-neutral-800 rounded-full px-2 py-0.5">{TYPE_LABELS[r.type] || r.type}</span>
<span className="font-medium text-white">{r.name}</span>
<span className="text-[10px] text-neutral-500 bg-neutral-200 rounded-full px-2 py-0.5 dark:bg-neutral-800">{TYPE_LABELS[r.type] || r.type}</span>
<span className="font-medium text-neutral-900 dark:text-white">{r.name}</span>
<ContactLinks phone={r.phone} instagram={r.instagram} telegram={r.telegram} />
{r.groupLabel && <span className="text-xs text-neutral-400 bg-neutral-800 rounded-full px-2 py-0.5">{r.groupLabel}</span>}
{r.groupLabel && <span className="text-xs text-neutral-500 bg-neutral-200 rounded-full px-2 py-0.5 dark:text-neutral-400 dark:bg-neutral-800">{r.groupLabel}</span>}
</div>
<div className="flex items-center gap-2 shrink-0">
<span className="text-neutral-600 text-xs">{fmtDate(r.createdAt)}</span>
@@ -960,20 +960,20 @@ function BookingsPageInner() {
<select
value={tab}
onChange={(e) => setTab(e.target.value as Tab)}
className="w-full rounded-lg border border-white/10 bg-neutral-900 px-4 py-2.5 text-sm font-medium text-white outline-none focus:border-gold/40 transition-colors [color-scheme:dark]"
className="w-full rounded-lg border border-neutral-300 bg-white px-4 py-2.5 text-sm font-medium text-neutral-900 outline-none focus:border-gold/40 transition-colors dark:border-white/10 dark:bg-neutral-900 dark:text-white dark:[color-scheme:dark]"
>
{TABS.map((t) => (
<option key={t.key} value={t.key}>{t.label}</option>
))}
</select>
</div>
<div className="mt-5 hidden sm:flex border-b border-white/10">
<div className="mt-5 hidden sm:flex border-b border-neutral-200 dark:border-white/10">
{TABS.map((t) => (
<button
key={t.key}
onClick={() => setTab(t.key)}
className={`shrink-0 px-4 py-2.5 text-sm font-medium transition-colors relative whitespace-nowrap ${
tab === t.key ? "text-gold" : "text-neutral-400 hover:text-white"
tab === t.key ? "text-gold" : "text-neutral-500 hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-white"
}`}
>
{t.label}
+5 -5
View File
@@ -67,7 +67,7 @@ function VideoSlot({
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-neutral-300">{label}</span>
{isCenter && (
<span className="inline-flex items-center gap-1 rounded-full bg-[#c9a96e]/15 px-2 py-0.5 text-[10px] font-medium text-[#c9a96e]">
<span className="inline-flex items-center gap-1 rounded-full bg-gold/15 px-2 py-0.5 text-[10px] font-medium text-gold">
<Smartphone size={10} />
мобильная версия
</span>
@@ -79,7 +79,7 @@ function VideoSlot({
{src ? (
<div
className={`group relative overflow-hidden rounded-lg border ${
isCenter ? "border-[#c9a96e]/40 ring-1 ring-[#c9a96e]/20" : "border-neutral-700"
isCenter ? "border-gold/40 ring-1 ring-[#c9a96e]/20" : "border-neutral-700"
}`}
onMouseEnter={() => videoRef.current?.play()}
onMouseLeave={() => { videoRef.current?.pause(); }}
@@ -104,7 +104,7 @@ function VideoSlot({
)}
</div>
{isCenter && (
<div className="absolute top-2 left-1/2 -translate-x-1/2 flex items-center gap-1 rounded-full bg-[#c9a96e]/90 px-2 py-0.5 text-[10px] font-bold text-black">
<div className="absolute top-2 left-1/2 -translate-x-1/2 flex items-center gap-1 rounded-full bg-gold/90 px-2 py-0.5 text-[10px] font-bold text-black">
<Star size={10} fill="currentColor" />
MAIN
</div>
@@ -128,7 +128,7 @@ function VideoSlot({
disabled={uploading}
className={`flex aspect-[9/16] w-full items-center justify-center rounded-lg border-2 border-dashed transition-colors disabled:opacity-50 ${
isCenter
? "border-[#c9a96e]/30 text-[#c9a96e]/50 hover:border-[#c9a96e]/60 hover:text-[#c9a96e]"
? "border-gold/30 text-gold/50 hover:border-gold/60 hover:text-gold"
: "border-neutral-700 text-neutral-500 hover:border-neutral-500 hover:text-neutral-300"
}`}
>
@@ -164,7 +164,7 @@ function VideoSizeInfo({ totalSize, totalMb, rating }: { totalSize: number; tota
return (
<button
onClick={() => setOpen((v) => !v)}
className="w-full text-left rounded-lg bg-neutral-800/50 px-3 py-2 transition-colors hover:bg-neutral-800/80"
className="w-full text-left rounded-lg bg-neutral-100/80 px-3 py-2 transition-colors hover:bg-neutral-200/80 dark:bg-neutral-800/50 dark:hover:bg-neutral-800/80"
>
<div className="flex items-center justify-between">
<span className="text-xs text-neutral-400">Общий вес: <span className={`font-medium ${rating.color}`}>{formatFileSize(totalSize)}</span></span>
+23 -23
View File
@@ -63,7 +63,7 @@ function EventSettings({
onChange: (patch: Partial<OpenDayEvent>) => void;
}) {
return (
<div className="rounded-xl border border-white/10 bg-neutral-900 p-5 space-y-4">
<div className="rounded-xl border border-neutral-200 bg-white p-5 space-y-4 dark:border-white/10 dark:bg-neutral-900">
<h2 className="text-lg font-bold flex items-center gap-2">
<Calendar size={18} className="text-gold" />
Настройки мероприятия
@@ -71,16 +71,16 @@ function EventSettings({
<div className="grid gap-4 sm:grid-cols-2">
<div>
<label className="block text-sm text-neutral-400 mb-1.5">Название</label>
<label className="block text-sm text-neutral-500 mb-1.5 dark:text-neutral-400">Название</label>
<input
type="text"
value={event.title}
onChange={(e) => onChange({ title: e.target.value })}
className="w-full rounded-lg border border-white/10 bg-neutral-800 px-4 py-2.5 text-white placeholder-neutral-500 outline-none focus:border-gold transition-colors"
className="w-full rounded-lg border border-neutral-200 bg-neutral-100 px-4 py-2.5 text-neutral-900 placeholder-neutral-400 outline-none focus:border-gold transition-colors dark:border-white/10 dark:bg-neutral-800 dark:text-white dark:placeholder-neutral-500"
/>
</div>
<div>
<label className="block text-sm text-neutral-400 mb-1.5">Дата</label>
<label className="block text-sm text-neutral-500 mb-1.5 dark:text-neutral-400">Дата</label>
<input
type="date"
value={event.date}
@@ -89,10 +89,10 @@ function EventSettings({
const isPast = newDate && newDate < new Date().toISOString().slice(0, 10);
onChange({ date: newDate, ...(isPast || !newDate ? { active: false } : {}) });
}}
className={`w-full rounded-lg border bg-neutral-800 px-4 py-2.5 text-white outline-none transition-colors [color-scheme:dark] ${
className={`w-full rounded-lg border bg-neutral-100 px-4 py-2.5 text-neutral-900 outline-none transition-colors [color-scheme:light] dark:bg-neutral-800 dark:text-white dark:[color-scheme:dark] ${
event.date && event.date < new Date().toISOString().slice(0, 10)
? "border-amber-500/50"
: "border-white/10 focus:border-gold"
: "border-neutral-200 focus:border-gold dark:border-white/10"
}`}
/>
{!event.date && (
@@ -131,7 +131,7 @@ function EventSettings({
className={`flex items-center gap-2 rounded-full px-4 py-2 text-sm font-medium transition-all ${
event.discountPrice > 0
? "bg-gold/15 text-gold border border-gold/30"
: "bg-neutral-800 text-neutral-400 border border-white/10 hover:text-white"
: "bg-neutral-100 text-neutral-500 border border-neutral-200 hover:text-neutral-900 dark:bg-neutral-800 dark:text-neutral-400 dark:border-white/10 dark:hover:text-white"
}`}
>
<Sparkles size={14} />
@@ -147,12 +147,12 @@ function EventSettings({
/>
</div>
<div>
<label className="block text-sm text-neutral-400 mb-1.5">От N занятий</label>
<label className="block text-sm text-neutral-500 mb-1.5 dark:text-neutral-400">От N занятий</label>
<input
type="number"
value={event.discountThreshold || ""}
onChange={(e) => onChange({ discountThreshold: parseInt(e.target.value) || 0 })}
className="w-full rounded-lg border border-white/10 bg-neutral-800 px-4 py-2.5 text-white outline-none focus:border-gold transition-colors"
className="w-full rounded-lg border border-neutral-200 bg-neutral-100 px-4 py-2.5 text-neutral-900 outline-none focus:border-gold transition-colors dark:border-white/10 dark:bg-neutral-800 dark:text-white"
/>
</div>
</div>
@@ -178,8 +178,8 @@ function EventSettings({
event.active
? "bg-emerald-500/15 text-emerald-400 border border-emerald-500/30 cursor-pointer"
: !event.date || event.date < new Date().toISOString().slice(0, 10)
? "bg-neutral-800 text-neutral-500 border border-white/5 opacity-50 cursor-not-allowed"
: "bg-neutral-800 text-neutral-400 border border-white/10 cursor-pointer hover:border-gold/40 hover:text-gold hover:bg-gold/5"
? "bg-neutral-100 text-neutral-400 border border-neutral-200 opacity-50 cursor-not-allowed dark:bg-neutral-800 dark:text-neutral-500 dark:border-white/5"
: "bg-neutral-100 text-neutral-500 border border-neutral-200 cursor-pointer hover:border-gold/40 hover:text-gold hover:bg-gold/5 dark:bg-neutral-800 dark:text-neutral-400 dark:border-white/10"
}`}
>
{event.active ? (
@@ -242,7 +242,7 @@ function NewClassForm({
<SelectField label="" value={style} onChange={setStyle} options={styles.map((s) => ({ value: s, label: s }))} placeholder="Стиль..." />
<SelectField label="" value={trainer} onChange={setTrainer} options={trainers.map((t) => ({ value: t, label: t }))} placeholder="Тренер..." />
<div className="flex gap-2 justify-end mt-2">
<button onClick={onCancel} className="rounded-md border border-white/10 px-3 py-1 text-xs text-neutral-400 hover:text-white hover:border-white/25 transition-colors">Отмена</button>
<button onClick={onCancel} className="rounded-md border border-neutral-200 px-3 py-1 text-xs text-neutral-500 hover:text-neutral-900 hover:border-neutral-300 transition-colors dark:border-white/10 dark:text-neutral-400 dark:hover:text-white dark:hover:border-white/25">Отмена</button>
<button onClick={() => canSave && onSave({ trainer, style, endTime })} disabled={!canSave}
className="rounded-md bg-gold/20 border border-gold/30 px-3 py-1 text-xs font-medium text-gold hover:bg-gold/30 transition-colors disabled:opacity-30 disabled:cursor-not-allowed">Сохранить</button>
</div>
@@ -306,15 +306,15 @@ function ClassCell({
<div
className={`group relative p-2 rounded-lg cursor-pointer transition-all ${
cls.cancelled
? "bg-neutral-800/30 opacity-50"
? "bg-neutral-200/50 opacity-50 dark:bg-neutral-800/30"
: atRisk
? "bg-red-500/5 border border-red-500/20"
: "bg-gold/5 border border-gold/15 hover:border-gold/30"
: "bg-gold/10 border border-gold/25 dark:bg-gold/5 dark:border-gold/15 hover:border-gold/30"
}`}
onClick={() => onEdit(cls.id)}
>
<div className="flex items-center gap-1.5">
<span className="text-xs font-medium text-white truncate">{cls.style}</span>
<span className="text-xs font-medium text-neutral-900 truncate dark:text-white">{cls.style}</span>
<span className="text-[10px] text-neutral-500">{cls.startTime}{cls.endTime}</span>
</div>
<div className="text-[10px] text-neutral-400 truncate">{cls.trainer}</div>
@@ -468,7 +468,7 @@ function ScheduleGrid({
}
return (
<div ref={gridRef} className="rounded-xl border border-white/10 bg-neutral-900 p-5 space-y-3">
<div ref={gridRef} className="rounded-xl border border-neutral-200 bg-white p-5 space-y-3 dark:border-white/10 dark:bg-neutral-900">
<h2 className="text-lg font-bold">Расписание</h2>
{halls.length === 0 ? (
@@ -484,7 +484,7 @@ function ScheduleGrid({
className={`rounded-lg px-3 py-1.5 text-xs font-medium transition-all ${
selectedHall === hall
? "bg-gold/20 text-gold border border-gold/40"
: "bg-neutral-800 text-neutral-400 border border-white/10 hover:text-white"
: "bg-neutral-100 text-neutral-500 border border-neutral-200 hover:text-neutral-900 dark:bg-neutral-800 dark:text-neutral-400 dark:border-white/10 dark:hover:text-white"
}`}
>
{hall}
@@ -502,8 +502,8 @@ function ScheduleGrid({
{timeSlots.map((time) => {
const cls = hallClasses[time];
return (
<div key={time} className="flex items-start gap-3 border-t border-white/5 py-1.5">
<span className="text-xs text-neutral-500 w-12 pt-1.5 shrink-0">{time}</span>
<div key={time} className="flex items-start gap-3 border-t border-neutral-200 py-1.5 dark:border-white/5">
<span className="text-xs text-neutral-400 w-12 pt-1.5 shrink-0 dark:text-neutral-500">{time}</span>
<div className="flex-1">
{cls ? (
<ClassCell
@@ -528,7 +528,7 @@ function ScheduleGrid({
) : (
<button
onClick={() => { setCreatingTime(time); setEditingClassId(null); }}
className="w-full rounded-lg border border-dashed border-white/5 p-2 text-neutral-600 hover:text-gold hover:border-gold/20 transition-colors"
className="w-full rounded-lg border border-dashed border-neutral-200 p-2 text-neutral-400 hover:text-gold hover:border-gold/20 transition-colors dark:border-white/5 dark:text-neutral-600"
>
<Plus size={12} className="mx-auto" />
</button>
@@ -545,12 +545,12 @@ function ScheduleGrid({
{confirmAction && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4" onClick={() => setConfirmAction(null)}>
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" />
<div className="relative w-full max-w-xs rounded-xl border border-white/[0.08] bg-[#141414] p-5 shadow-2xl" onClick={(e) => e.stopPropagation()}>
<p className="text-sm text-white text-center">{confirmAction.message}</p>
<div className="relative w-full max-w-xs rounded-xl border border-neutral-200 bg-white p-5 shadow-2xl dark:border-white/[0.08] dark:bg-[#141414]" onClick={(e) => e.stopPropagation()}>
<p className="text-sm text-neutral-900 text-center dark:text-white">{confirmAction.message}</p>
<div className="mt-4 flex gap-2 justify-center">
<button
onClick={() => setConfirmAction(null)}
className="rounded-lg border border-white/10 px-4 py-2 text-xs font-medium text-neutral-400 hover:text-white hover:border-white/25 transition-colors"
className="rounded-lg border border-neutral-200 px-4 py-2 text-xs font-medium text-neutral-500 hover:text-neutral-900 hover:border-neutral-300 transition-colors dark:border-white/10 dark:text-neutral-400 dark:hover:text-white dark:hover:border-white/25"
>
Нет
</button>
+17 -1
View File
@@ -103,6 +103,10 @@ input[type="number"] {
html {
scrollbar-width: thin;
scrollbar-color: rgba(160, 160, 160, 0.5) #f5f5f5;
}
html.dark {
scrollbar-color: rgba(201, 169, 110, 0.3) var(--color-surface-dark);
}
@@ -111,14 +115,26 @@ html {
}
::-webkit-scrollbar-track {
background: #f5f5f5;
}
html.dark ::-webkit-scrollbar-track {
background: var(--color-surface-dark);
}
::-webkit-scrollbar-thumb {
background: rgba(201, 169, 110, 0.3);
background: rgba(160, 160, 160, 0.5);
border-radius: 4px;
}
html.dark ::-webkit-scrollbar-thumb {
background: rgba(201, 169, 110, 0.3);
}
::-webkit-scrollbar-thumb:hover {
background: rgba(120, 120, 120, 0.6);
}
html.dark ::-webkit-scrollbar-thumb:hover {
background: rgba(201, 169, 110, 0.5);
}
+30 -9
View File
@@ -136,12 +136,12 @@
.gradient-text {
background: linear-gradient(
135deg,
#8a6f3e 0%,
#c9a96e 20%,
#8a6f3e 40%,
#c9a96e 60%,
#6b5530 80%,
#8a6f3e 100%
#c9a96e 0%,
#e2c97e 20%,
#d4b87a 40%,
#e2c97e 60%,
#c9a96e 80%,
#d4b87a 100%
);
background-size: 200% 200%;
-webkit-background-clip: text;
@@ -150,7 +150,12 @@
animation: gradient-shift 6s ease-in-out infinite;
}
/* Light mode gradient text */
/* Auto-switch gradient text for light mode */
html:not(.dark) .gradient-text {
background-image: linear-gradient(135deg, #a08050 0%, #c9a96e 25%, #8a6f3e 50%, #c9a96e 75%, #a08050 100%);
}
/* Explicit light mode gradient text class (legacy) */
.gradient-text-light {
background: linear-gradient(135deg, #171717 0%, #c9a96e 50%, #171717 100%);
background-size: 200% 200%;
@@ -172,7 +177,7 @@
inset: 0;
border-radius: inherit;
padding: 1px;
background: linear-gradient(135deg, rgba(201, 169, 110, 0.3), transparent 40%, transparent 60%, rgba(201, 169, 110, 0.15));
background: linear-gradient(135deg, rgba(201, 169, 110, 0.5), transparent 40%, transparent 60%, rgba(201, 169, 110, 0.3));
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
mask-composite: exclude;
pointer-events: none;
@@ -180,8 +185,16 @@
opacity: 0.5;
}
:is(.dark) .animated-border::before {
background: linear-gradient(135deg, rgba(201, 169, 110, 0.3), transparent 40%, transparent 60%, rgba(201, 169, 110, 0.15));
}
.animated-border:hover::before {
opacity: 1;
background: linear-gradient(135deg, rgba(201, 169, 110, 0.7), transparent 40%, transparent 60%, rgba(201, 169, 110, 0.5));
}
:is(.dark) .animated-border:hover::before {
background: linear-gradient(135deg, rgba(201, 169, 110, 0.6), transparent 40%, transparent 60%, rgba(201, 169, 110, 0.4));
}
@@ -192,10 +205,14 @@
}
.glow-hover:hover {
box-shadow: 0 0 30px rgba(201, 169, 110, 0.1), 0 0 60px rgba(201, 169, 110, 0.05);
box-shadow: 0 0 20px rgba(201, 169, 110, 0.25), 0 0 50px rgba(201, 169, 110, 0.12), 0 4px 16px rgba(0, 0, 0, 0.06);
transform: translateY(-4px);
}
:is(.dark) .glow-hover:hover {
box-shadow: 0 0 30px rgba(201, 169, 110, 0.1), 0 0 60px rgba(201, 169, 110, 0.05);
}
/* ===== Scroll Reveal ===== */
.reveal {
@@ -345,6 +362,10 @@
.section-divider {
height: 1px;
background: linear-gradient(90deg, transparent, rgba(201, 169, 110, 0.4), transparent);
}
:is(.dark) .section-divider {
background: linear-gradient(90deg, transparent, rgba(201, 169, 110, 0.15), transparent);
}
+2 -1
View File
@@ -6,7 +6,8 @@
@apply hover:bg-gold-light hover:shadow-[0_0_30px_rgba(201,169,110,0.35)];
@apply dark:bg-gold dark:text-black;
@apply dark:hover:bg-gold-light dark:hover:shadow-[0_0_30px_rgba(201,169,110,0.35)];
@apply focus:outline-none focus-visible:ring-2 focus-visible:ring-gold-light focus-visible:ring-offset-2 focus-visible:ring-offset-black;
@apply active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none;
@apply focus:outline-none focus-visible:ring-2 focus-visible:ring-gold-light focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-black;
}
/* ===== Scrollbar ===== */
+55 -9
View File
@@ -6,8 +6,8 @@
}
.surface-muted {
@apply bg-neutral-100;
@apply dark:bg-[var(--color-surface-deep)];
@apply bg-neutral-100 text-neutral-900;
@apply dark:bg-[var(--color-surface-deep)] dark:text-neutral-100;
}
.surface-glass {
@@ -16,8 +16,8 @@
}
.surface-card {
@apply bg-white/80 backdrop-blur-sm;
@apply dark:bg-neutral-900 dark:backdrop-blur-sm;
@apply bg-white shadow-sm backdrop-blur-sm;
@apply dark:bg-neutral-900 dark:shadow-none dark:backdrop-blur-sm;
}
/* ===== Borders ===== */
@@ -73,20 +73,25 @@
transform: translateX(-50%);
width: min(600px, 100%);
height: 400px;
background: radial-gradient(ellipse, rgba(201, 169, 110, 0.05), transparent 70%);
background: radial-gradient(ellipse, rgba(201, 169, 110, 0.15), transparent 70%);
pointer-events: none;
}
:is(.dark) .section-glow::before {
background: radial-gradient(ellipse, rgba(201, 169, 110, 0.05), transparent 70%);
}
/* ===== Glass Card ===== */
.glass-card {
@apply rounded-2xl border backdrop-blur-sm transition-all duration-300;
@apply border-neutral-200/80 bg-white/90;
@apply dark:border-white/[0.06] dark:bg-white/[0.04];
@apply border-neutral-200/80 bg-white/90 shadow-sm shadow-gold/[0.04];
@apply dark:border-white/[0.06] dark:bg-white/[0.04] dark:shadow-none;
}
.glass-card:hover {
@apply dark:border-gold/15 dark:bg-white/[0.06];
@apply border-gold/30 bg-white shadow-md shadow-gold/[0.08];
@apply dark:border-gold/15 dark:bg-white/[0.06] dark:shadow-none;
}
/* ===== Photo Filter ===== */
@@ -99,10 +104,43 @@
filter: saturate(0.6) sepia(0.2) brightness(0.9) contrast(1.1);
}
/* ===== Modal Surface ===== */
.modal-surface {
@apply bg-white dark:bg-neutral-950;
}
/* ===== Theme Input ===== */
.theme-input {
@apply border-neutral-300 bg-neutral-50 text-neutral-900 placeholder-neutral-400;
@apply focus:border-gold/60 focus:bg-white;
@apply dark:border-white/[0.08] dark:bg-white/[0.04] dark:text-white dark:placeholder-neutral-500;
@apply dark:focus:border-gold/40 dark:focus:bg-white/[0.06];
}
/* ===== Admin Surface ===== */
.admin-surface {
@apply bg-white text-neutral-900 dark:bg-neutral-950 dark:text-white;
}
.admin-sidebar {
@apply bg-neutral-100 border-neutral-200 dark:bg-neutral-900 dark:border-white/10;
}
.admin-nav-item {
@apply text-neutral-500 hover:text-neutral-900 hover:bg-neutral-200/60 dark:text-neutral-400 dark:hover:text-white dark:hover:bg-white/5;
}
/* ===== Custom Scrollbar ===== */
.styled-scrollbar {
scrollbar-width: thin;
scrollbar-color: rgba(160, 160, 160, 0.5) transparent;
}
:is(.dark) .styled-scrollbar {
scrollbar-color: rgba(201, 169, 110, 0.25) transparent;
}
@@ -116,10 +154,18 @@
}
.styled-scrollbar::-webkit-scrollbar-thumb {
background: rgba(201, 169, 110, 0.25);
background: rgba(160, 160, 160, 0.5);
border-radius: 4px;
}
:is(.dark) .styled-scrollbar::-webkit-scrollbar-thumb {
background: rgba(201, 169, 110, 0.25);
}
.styled-scrollbar::-webkit-scrollbar-thumb:hover {
background: rgba(120, 120, 120, 0.6);
}
:is(.dark) .styled-scrollbar::-webkit-scrollbar-thumb:hover {
background: rgba(201, 169, 110, 0.4);
}
+4 -3
View File
@@ -8,10 +8,11 @@ export function Footer() {
<footer className="relative border-t border-neutral-200 bg-neutral-100 dark:border-white/[0.08] dark:bg-[#050505]">
<div className="section-divider absolute top-0 left-0 right-0" />
<div className="section-container flex flex-col items-center gap-4 py-10 sm:flex-row sm:justify-between">
<p className="text-sm text-neutral-500">
&copy; {year} {BRAND.name}
<p className="text-sm text-neutral-600 dark:text-neutral-500">
{/* &copy; {year} {BRAND.name} — commented out for portfolio version */}
&copy; {year} Dance Studio
</p>
<div className="flex items-center gap-1.5 text-sm text-neutral-500">
<div className="flex items-center gap-1.5 text-sm text-neutral-600 dark:text-neutral-500">
<span>Made with</span>
<Heart size={14} className="fill-gold text-gold" />
<span>by Diana Dolgolyova</span>
+17 -9
View File
@@ -7,6 +7,7 @@ import { BRAND, NAV_LINKS } from "@/lib/constants";
import { UI_CONFIG } from "@/lib/config";
import { HeroLogo } from "@/components/ui/HeroLogo";
import { SignupModal } from "@/components/ui/SignupModal";
import { ThemeToggle } from "@/components/ui/ThemeToggle";
import { useBooking } from "@/contexts/BookingContext";
export function Header() {
@@ -125,13 +126,14 @@ export function Header() {
<header
className={`fixed top-0 z-50 w-full transition-all duration-500 ${
scrolled || menuOpen
? "bg-black/40 shadow-none backdrop-blur-xl"
? "bg-white/90 shadow-none backdrop-blur-xl dark:bg-black/40"
: "bg-transparent"
}`}
>
<a href="#main-content" className="sr-only focus:not-sr-only focus:absolute focus:top-2 focus:left-2 focus:z-[60] focus:px-4 focus:py-2 focus:bg-gold focus:text-black focus:rounded font-medium">Перейти к содержимому</a>
<div className="flex h-16 items-center justify-between px-6 sm:px-10 lg:px-16">
<Link href="/" className="group flex items-center gap-2.5">
{/* Blackheart logotype — commented out for portfolio version
<div className="relative flex h-8 w-8 items-center justify-center">
<div
className="absolute inset-0 rounded-full transition-all duration-300 group-hover:scale-125"
@@ -144,8 +146,10 @@ export function Header() {
className="relative text-black transition-transform duration-300 drop-shadow-[0_0_3px_rgba(201,169,110,0.5)] group-hover:scale-110"
/>
</div>
*/}
{/* {BRAND.shortName} — commented out for portfolio version */}
<span className="font-display text-lg font-bold tracking-tight text-gold">
{BRAND.shortName}
Dance Studio
</span>
</Link>
@@ -159,23 +163,27 @@ export function Header() {
aria-current={isActive ? "page" : undefined}
className={`relative whitespace-nowrap py-1 text-sm font-medium transition-all duration-300 after:absolute after:bottom-0 after:left-0 after:h-[2px] after:bg-gold after:transition-all after:duration-300 ${
isActive
? "text-gold-light after:w-full"
: "text-neutral-400 after:w-0 hover:text-white hover:after:w-full"
? "text-gold after:w-full"
: scrolled
? "text-neutral-600 after:w-0 hover:text-neutral-900 hover:after:w-full dark:text-neutral-400 dark:hover:text-white"
: "text-white/80 after:w-0 hover:text-white hover:after:w-full"
}`}
>
{link.label}
</a>
);
})}
<ThemeToggle />
</nav>
<div className="flex items-center gap-2 lg:hidden">
<div className="flex items-center gap-1 lg:hidden">
<ThemeToggle />
<button
ref={menuButtonRef}
onClick={() => setMenuOpen(!menuOpen)}
aria-label={menuOpen ? "Закрыть меню" : "Открыть меню"}
aria-expanded={menuOpen}
className="rounded-lg p-2 text-neutral-400 transition-colors hover:text-white"
className="rounded-lg p-2 text-neutral-500 transition-colors hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-white"
>
{menuOpen ? <X size={24} /> : <Menu size={24} />}
</button>
@@ -189,7 +197,7 @@ export function Header() {
menuOpen ? "max-h-[80vh] opacity-100" : "max-h-0 opacity-0"
}`}
>
<nav className="border-t border-white/[0.06] px-6 py-4 text-center sm:px-8" aria-label="Основная навигация">
<nav className="border-t border-neutral-200/60 dark:border-white/[0.06] px-6 py-4 text-center sm:px-8" aria-label="Основная навигация">
{visibleLinks.map((link, index) => {
const isActive = activeSection === link.href.replace("#", "");
return (
@@ -201,8 +209,8 @@ export function Header() {
aria-current={isActive ? "page" : undefined}
className={`block py-3 text-base transition-colors ${
isActive
? "text-gold-light"
: "text-neutral-400 hover:text-white"
? "text-gold"
: "text-neutral-600 hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-white"
}`}
>
{link.label}
+1 -1
View File
@@ -47,7 +47,7 @@ export function About({ data: about, stats }: AboutProps) {
<div
key={i}
aria-label={stat.ariaLabel}
className="group flex flex-col items-center gap-3 rounded-2xl border border-neutral-200 bg-white/50 p-6 transition-all duration-300 hover:border-gold/30 sm:p-8 dark:border-white/[0.06] dark:bg-white/[0.02] dark:hover:border-gold/20"
className="group flex flex-col items-center gap-3 rounded-2xl border border-neutral-200 bg-white/80 p-6 shadow-sm shadow-gold/[0.06] transition-all duration-300 hover:border-gold/30 hover:shadow-md hover:shadow-gold/[0.1] sm:p-8 dark:border-white/[0.06] dark:bg-white/[0.02] dark:shadow-none dark:hover:border-gold/20 dark:hover:shadow-none"
>
<div className="flex h-11 w-11 items-center justify-center rounded-xl bg-gold/10 text-gold-dark transition-colors group-hover:bg-gold/20 dark:text-gold-light" aria-hidden="true">
{stat.icon}
+4 -4
View File
@@ -45,7 +45,7 @@ export function Classes({ data: classes }: ClassesProps) {
});
return (
<section id="classes" className="section-glow relative section-padding bg-neutral-100 dark:bg-[#080808]">
<section id="classes" className="section-glow relative section-padding bg-neutral-50 dark:bg-[#080808]">
<div className="section-divider absolute top-0 left-0 right-0" />
<div className="section-container">
<Reveal>
@@ -78,14 +78,14 @@ export function Classes({ data: classes }: ClassesProps) {
}}
/>
{/* Gradient overlay */}
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent" />
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-black/20 to-transparent" />
{/* Icon + name overlay */}
<div className="absolute bottom-0 left-0 right-0 p-6 flex items-center gap-3">
<div className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-gold/20 text-gold-light backdrop-blur-sm">
{getIcon(item.icon)}
</div>
<h3 className="text-2xl font-bold text-white">
<h3 className="text-2xl font-bold text-white drop-shadow-[0_2px_8px_rgba(0,0,0,0.5)]">
{item.name}
</h3>
</div>
@@ -122,7 +122,7 @@ export function Classes({ data: classes }: ClassesProps) {
>
{item.name}
</p>
<p className="hidden lg:block text-xs text-neutral-500 dark:text-neutral-500 truncate">
<p className="hidden lg:block text-xs text-neutral-600 dark:text-neutral-500 truncate">
{item.description}
</p>
</div>
+1 -1
View File
@@ -42,7 +42,7 @@ export function FAQ({ data: faq }: FAQProps) {
className={`rounded-xl border transition-all duration-300 ${
isOpen
? "border-gold/30 bg-gradient-to-br from-gold/[0.06] via-transparent to-gold/[0.03] shadow-md shadow-gold/5"
: "border-neutral-200 bg-white hover:border-neutral-300 dark:border-white/[0.06] dark:bg-neutral-950 dark:hover:border-white/[0.12]"
: "border-neutral-200 bg-white shadow-sm shadow-gold/[0.03] hover:border-neutral-300 hover:shadow-md hover:shadow-gold/[0.06] dark:border-white/[0.06] dark:bg-neutral-950 dark:shadow-none dark:hover:border-white/[0.12] dark:hover:shadow-none"
}`}
>
<button
+6 -4
View File
@@ -98,7 +98,7 @@ export function Hero({ data: hero }: HeroProps) {
}, [scrollToNext]);
return (
<section id="hero" ref={sectionRef} aria-label="Главный баннер" className="relative flex min-h-svh items-center justify-center overflow-hidden bg-neutral-950">
<section id="hero" ref={sectionRef} aria-label="Главный баннер" className="relative flex min-h-svh items-center justify-center overflow-hidden bg-neutral-100 dark:bg-neutral-950">
{/* Videos render only after hydration to avoid SSR mismatch */}
{mounted && (
<>
@@ -161,7 +161,7 @@ export function Hero({ data: hero }: HeroProps) {
{/* Loading overlay — covers videos but not content */}
<div
ref={overlayRef}
className="absolute inset-0 z-[5] bg-neutral-950 pointer-events-none transition-opacity duration-1000"
className="absolute inset-0 z-[5] bg-neutral-100 dark:bg-neutral-950 pointer-events-none transition-opacity duration-1000"
/>
{/* Vignette — dark edges to guide eye to center */}
@@ -174,6 +174,7 @@ export function Hero({ data: hero }: HeroProps) {
{/* Content */}
<div className="section-container relative z-10 text-center" style={{ textShadow: "0 1px 0 rgba(201,169,110,0.3), 0 2px 0 rgba(201,169,110,0.2), 0 4px 8px rgba(0,0,0,0.4), 0 8px 20px rgba(0,0,0,0.3)" }}>
{/* Blackheart logotype — commented out for portfolio version
<div className="hero-logo relative mx-auto mb-6 sm:mb-12 flex items-center justify-center" style={{ width: 160, height: 132 }}>
<div className="absolute -inset-10 rounded-full blur-[80px]" style={{ background: "radial-gradient(circle, rgba(201,169,110,0.25), transparent 70%)" }} />
<div className="hero-logo-heartbeat relative">
@@ -183,19 +184,20 @@ export function Hero({ data: hero }: HeroProps) {
/>
</div>
</div>
*/}
<h1 className="hero-title font-display text-4xl font-bold tracking-tight sm:text-6xl lg:text-8xl">
<span className="gradient-text">{hero.headline}</span>
</h1>
<p className="hero-subtitle mx-auto mt-5 max-w-xl text-lg text-gold/80 sm:mt-8 sm:text-2xl">
<p className="hero-subtitle mx-auto mt-5 max-w-xl text-lg text-gold-light sm:mt-8 sm:text-2xl">
{hero.subheadline}
</p>
<div className="hero-cta mt-8 sm:mt-14">
<button
onClick={openBooking}
className="group relative rounded-full border border-gold/60 bg-gold/15 px-8 py-4 text-base font-semibold text-gold backdrop-blur-md transition-all duration-300 hover:bg-gold/25 hover:border-gold hover:shadow-[0_0_40px_rgba(201,169,110,0.35)] cursor-pointer sm:px-10 sm:py-5 sm:text-lg"
className="group relative rounded-full border border-gold bg-gold/20 px-8 py-4 text-base font-semibold text-gold-light backdrop-blur-md shadow-[0_0_30px_rgba(201,169,110,0.25)] transition-all duration-300 hover:bg-gold/30 hover:shadow-[0_0_50px_rgba(201,169,110,0.45)] cursor-pointer sm:px-10 sm:py-5 sm:text-lg"
>
<span className="relative z-10">{hero.ctaText}</span>
{/* Pulse glow on hover */}
+11 -11
View File
@@ -136,7 +136,7 @@ function MasterClassDetail({
role="dialog"
aria-modal="true"
aria-label={item.title}
className="relative w-full max-w-lg max-h-[90vh] overflow-y-auto rounded-2xl border border-white/10 bg-neutral-950 shadow-2xl"
className="relative w-full max-w-lg max-h-[90vh] overflow-y-auto rounded-2xl border border-neutral-200 bg-white dark:border-white/10 dark:bg-neutral-950 shadow-2xl"
onClick={(e) => e.stopPropagation()}
>
{/* Content */}
@@ -147,7 +147,7 @@ function MasterClassDetail({
{item.style}
</span>
{duration && (
<span className="flex items-center gap-1 text-xs text-white/50">
<span className="flex items-center gap-1 text-xs text-neutral-500 dark:text-white/50">
<Clock size={11} />
{duration}
</span>
@@ -161,19 +161,19 @@ function MasterClassDetail({
<button
onClick={onClose}
aria-label="Закрыть"
className="h-11 w-11 flex items-center justify-center rounded-full text-white/50 hover:text-white hover:bg-white/10 transition-colors shrink-0 -mr-2"
className="h-11 w-11 flex items-center justify-center rounded-full text-neutral-400 hover:text-neutral-900 hover:bg-neutral-100 dark:text-white/50 dark:hover:text-white dark:hover:bg-white/10 transition-colors shrink-0 -mr-2"
>
<X size={18} />
</button>
</div>
{/* Title */}
<h2 className="text-2xl font-bold text-white">{item.title}</h2>
<h2 className="text-2xl font-bold text-neutral-900 dark:text-white">{item.title}</h2>
{/* Trainer */}
<button
onClick={() => window.dispatchEvent(new CustomEvent("openTrainerProfile", { detail: item.trainer.split(" · ")[0] }))}
className="flex items-center gap-2 text-sm text-white/80 hover:text-gold transition-colors"
className="flex items-center gap-2 text-sm text-neutral-700 hover:text-gold dark:text-white/80 transition-colors"
>
<User size={14} />
{item.trainer}
@@ -181,14 +181,14 @@ function MasterClassDetail({
{/* Description */}
{item.description && (
<div className="text-sm leading-relaxed text-white/60">
<div className="text-sm leading-relaxed text-neutral-600 dark:text-white/60">
{formatMarkup(item.description)}
</div>
)}
{/* All dates */}
<div className="space-y-2">
<h3 className="text-sm font-medium text-white/40 uppercase tracking-wider">Даты</h3>
<h3 className="text-sm font-medium text-neutral-400 dark:text-white/40 uppercase tracking-wider">Даты</h3>
{slots.length === 0 ? (
<p className="text-sm text-gold">Скоро дата уточняется</p>
) : (
@@ -198,11 +198,11 @@ function MasterClassDetail({
return (
<div key={i} className="flex items-center gap-3 text-sm">
<Calendar size={13} className="shrink-0 text-gold/60" />
<span className="text-white/80">
<span className="text-neutral-700 dark:text-white/80">
{d.getDate()} {MONTHS_RU[d.getMonth()]} ({WEEKDAYS_RU[d.getDay()]})
</span>
{slot.startTime && (
<span className="text-white/50">
<span className="text-neutral-500 dark:text-white/50">
{slot.startTime}{slot.endTime}
</span>
)}
@@ -215,7 +215,7 @@ function MasterClassDetail({
{/* Location */}
{item.location && (
<div className="flex items-center gap-2 text-sm text-white/60">
<div className="flex items-center gap-2 text-sm text-neutral-500 dark:text-white/60">
<MapPin size={13} className="text-gold/60" />
<span>{item.location}{locAddress ? ` · ${locAddress}` : ""}</span>
</div>
@@ -227,7 +227,7 @@ function MasterClassDetail({
href={item.instagramUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 text-sm text-white/50 hover:text-gold transition-colors"
className="inline-flex items-center gap-2 text-sm text-neutral-400 hover:text-gold dark:text-white/50 transition-colors"
>
<Instagram size={14} />
Подробнее в Instagram
+10 -10
View File
@@ -32,7 +32,7 @@ function FeaturedArticle({
role="button"
tabIndex={0}
onKeyDown={handleKeyDown}
className="group relative overflow-hidden rounded-3xl cursor-pointer"
className="group relative overflow-hidden rounded-3xl shadow-md shadow-gold/[0.06] dark:shadow-none cursor-pointer"
onClick={onClick}
>
{item.image && (
@@ -53,16 +53,16 @@ function FeaturedArticle({
</div>
)}
<div
className={`${item.image ? "absolute bottom-0 left-0 right-0 p-6 sm:p-8" : "p-6 sm:p-8 bg-neutral-900 rounded-3xl"}`}
className={`${item.image ? "absolute bottom-0 left-0 right-0 p-6 sm:p-8" : "p-6 sm:p-8 bg-neutral-100 dark:bg-neutral-900 rounded-3xl"}`}
>
<span className="inline-flex items-center gap-1.5 rounded-full bg-white/15 px-3 py-1 text-xs font-medium text-white/80 backdrop-blur-sm">
<span className={`inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-medium backdrop-blur-sm ${item.image ? "bg-white/15 text-white/80" : "bg-neutral-200 text-neutral-600 dark:bg-white/15 dark:text-white/80"}`}>
<Calendar size={12} />
<time dateTime={item.date}>{formatDateRu(item.date)}</time>
</span>
<h3 className="mt-3 text-xl sm:text-2xl font-bold text-white leading-tight">
<h3 className={`mt-3 text-xl sm:text-2xl font-bold leading-tight ${item.image ? "text-white" : "text-neutral-900 dark:text-white"}`}>
{item.title}
</h3>
<p className="mt-2 max-w-2xl text-sm leading-relaxed text-white/70 line-clamp-3">
<p className={`mt-2 max-w-2xl text-sm leading-relaxed line-clamp-3 ${item.image ? "text-white/70" : "text-neutral-600 dark:text-white/70"}`}>
{item.text}
</p>
</div>
@@ -89,7 +89,7 @@ function CompactArticle({
role="button"
tabIndex={0}
onKeyDown={handleKeyDown}
className="group flex gap-4 items-start py-5 border-b border-neutral-200/60 last:border-0 dark:border-white/[0.06] cursor-pointer"
className="group flex gap-4 items-start py-5 border-b border-neutral-200 last:border-0 dark:border-white/[0.06] cursor-pointer"
onClick={onClick}
>
{item.image && (
@@ -109,7 +109,7 @@ function CompactArticle({
</div>
)}
<div className="flex-1 min-w-0">
<time dateTime={item.date} className="text-xs text-neutral-400 dark:text-white/30">
<time dateTime={item.date} className="text-xs text-neutral-500 dark:text-white/30">
{formatDateRu(item.date)}
</time>
<h3 className="mt-1 text-sm sm:text-base font-bold text-neutral-900 dark:text-white leading-snug line-clamp-2 group-hover:text-gold transition-colors">
@@ -180,7 +180,7 @@ export function News({ data }: NewsProps) {
if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
}}
disabled={page === 0}
className="rounded-full border border-white/10 bg-white/[0.03] px-4 py-2 text-sm font-medium text-neutral-400 hover:text-white hover:border-white/25 transition-colors cursor-pointer disabled:opacity-30 disabled:cursor-not-allowed"
className="rounded-full border border-neutral-200 bg-neutral-100 px-4 py-2 text-sm font-medium text-neutral-500 hover:text-neutral-900 hover:border-neutral-300 transition-colors cursor-pointer disabled:opacity-30 disabled:cursor-not-allowed dark:border-white/10 dark:bg-white/[0.03] dark:text-neutral-400 dark:hover:text-white dark:hover:border-white/25"
>
</button>
@@ -197,7 +197,7 @@ export function News({ data }: NewsProps) {
className={`h-10 w-10 rounded-full text-sm font-medium transition-colors cursor-pointer ${
i === page
? "bg-gold text-black"
: "border border-white/10 text-neutral-400 hover:text-white hover:border-white/25"
: "border border-neutral-200 text-neutral-500 hover:text-neutral-900 hover:border-neutral-300 dark:border-white/10 dark:text-neutral-400 dark:hover:text-white dark:hover:border-white/25"
}`}
>
{i + 1}
@@ -211,7 +211,7 @@ export function News({ data }: NewsProps) {
if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
}}
disabled={page === totalPages - 1}
className="rounded-full border border-white/10 bg-white/[0.03] px-4 py-2 text-sm font-medium text-neutral-400 hover:text-white hover:border-white/25 transition-colors cursor-pointer disabled:opacity-30 disabled:cursor-not-allowed"
className="rounded-full border border-neutral-200 bg-neutral-100 px-4 py-2 text-sm font-medium text-neutral-500 hover:text-neutral-900 hover:border-neutral-300 transition-colors cursor-pointer disabled:opacity-30 disabled:cursor-not-allowed dark:border-white/10 dark:bg-white/[0.03] dark:text-neutral-400 dark:hover:text-white dark:hover:border-white/25"
>
</button>
+19 -19
View File
@@ -85,8 +85,8 @@ export function OpenDay({ data, popups, teamMembers, locations }: OpenDayProps)
{/* Pricing info */}
<Reveal>
<div className="mt-6 text-center space-y-1">
<p className="text-lg font-semibold text-white">
{event.pricePerClass} BYN <span className="text-neutral-400 font-normal text-sm">за занятие</span>
<p className="text-lg font-semibold text-neutral-900 dark:text-white">
{event.pricePerClass} BYN <span className="text-neutral-500 dark:text-neutral-400 font-normal text-sm">за занятие</span>
</p>
{event.discountPrice > 0 && event.discountThreshold > 0 && (
<p className="text-sm text-gold">
@@ -99,7 +99,7 @@ export function OpenDay({ data, popups, teamMembers, locations }: OpenDayProps)
{event.description && (
<Reveal>
<div className="mt-4 text-center text-sm text-neutral-400 max-w-2xl mx-auto">
<div className="mt-4 text-center text-sm text-neutral-500 dark:text-neutral-400 max-w-2xl mx-auto">
{formatMarkup(event.description)}
</div>
</Reveal>
@@ -112,7 +112,7 @@ export function OpenDay({ data, popups, teamMembers, locations }: OpenDayProps)
<Reveal>
<div className="max-w-lg mx-auto space-y-3">
<div className="text-center mb-4">
<h3 className="text-base font-semibold text-white">{halls[0]}</h3>
<h3 className="text-base font-semibold text-neutral-900 dark:text-white">{halls[0]}</h3>
{hallAddress[halls[0]] && (
<p className="text-sm text-gold/70 mt-0.5 flex items-center justify-center gap-1.5">
<MapPin size={13} />
@@ -137,8 +137,8 @@ export function OpenDay({ data, popups, teamMembers, locations }: OpenDayProps)
{halls.map((hall) => (
<Reveal key={hall}>
<div>
<div className="text-center mb-4 rounded-lg bg-white/[0.03] border border-white/[0.06] py-3 px-4">
<h3 className="text-base font-semibold text-white">{hall}</h3>
<div className="text-center mb-4 rounded-lg bg-neutral-50 border border-neutral-200 py-3 px-4 dark:bg-white/[0.03] dark:border-white/[0.06]">
<h3 className="text-base font-semibold text-neutral-900 dark:text-white">{hall}</h3>
{hallAddress[hall] && (
<p className="text-sm text-gold/70 mt-0.5 flex items-center justify-center gap-1.5">
<MapPin size={13} />
@@ -196,15 +196,15 @@ function ClassCard({
if (cls.cancelled) {
return (
<div className="rounded-xl border border-white/[0.06] bg-white/[0.02] p-3 sm:p-4 opacity-50">
<div className="rounded-xl border border-neutral-200 bg-neutral-50 p-3 sm:p-4 opacity-50 dark:border-white/[0.06] dark:bg-white/[0.02]">
<div className="flex items-center justify-between gap-3">
<div className="flex-1 min-w-0 space-y-1">
<span className="rounded-md bg-neutral-800 px-2 py-0.5 text-xs font-bold text-neutral-500">
<span className="rounded-md bg-neutral-200 px-2 py-0.5 text-xs font-bold text-neutral-500 dark:bg-neutral-800">
<time dateTime={`${cls.startTime}-${cls.endTime}`}>{cls.startTime}{cls.endTime}</time>
</span>
<p className="text-sm text-neutral-500"><del>{cls.trainer} · {cls.style}</del></p>
</div>
<span className="text-xs text-neutral-500 bg-neutral-800 rounded-full px-2.5 py-0.5 font-medium">
<span className="text-xs text-neutral-500 bg-neutral-200 rounded-full px-2.5 py-0.5 font-medium dark:bg-neutral-800">
Отменено
</span>
</div>
@@ -217,8 +217,8 @@ function ClassCard({
return (
<div className={`rounded-xl border transition-all ${
isFull
? "border-white/[0.04] bg-white/[0.01]"
: "border-white/[0.06] bg-white/[0.02] hover:border-white/[0.12] hover:bg-white/[0.04]"
? "border-neutral-200 bg-neutral-50/50 dark:border-white/[0.04] dark:bg-white/[0.01]"
: "border-neutral-200 bg-white hover:border-neutral-300 hover:bg-neutral-50 dark:border-white/[0.06] dark:bg-white/[0.02] dark:hover:border-white/[0.12] dark:hover:bg-white/[0.04]"
}`}>
<div className="flex items-start gap-3 p-3 sm:p-4">
{/* Trainer photo */}
@@ -227,14 +227,14 @@ function ClassCard({
window.dispatchEvent(new CustomEvent("openTrainerProfile", { detail: cls.trainer }));
}}
aria-label={`Профиль тренера: ${cls.trainer}`}
className="relative flex items-center justify-center h-11 w-11 rounded-full overflow-hidden shrink-0 ring-1 ring-white/10 hover:ring-gold/30 transition-all cursor-pointer mt-0.5"
className="relative flex items-center justify-center h-11 w-11 rounded-full overflow-hidden shrink-0 ring-1 ring-neutral-200 hover:ring-gold/30 transition-all cursor-pointer mt-0.5 dark:ring-white/10"
title={`Подробнее о ${cls.trainer}`}
>
{trainerPhoto ? (
<Image src={trainerPhoto} alt={cls.trainer} fill className="object-cover" sizes="44px" />
) : (
<div className="flex items-center justify-center h-full w-full bg-white/[0.06]">
<User size={16} className="text-white/40" />
<div className="flex items-center justify-center h-full w-full bg-neutral-100 dark:bg-white/[0.06]">
<User size={16} className="text-neutral-400 dark:text-white/40" />
</div>
)}
</button>
@@ -245,7 +245,7 @@ function ClassCard({
onClick={() => {
window.dispatchEvent(new CustomEvent("openTrainerProfile", { detail: cls.trainer }));
}}
className="text-sm font-semibold text-white/90 hover:text-gold transition-colors cursor-pointer"
className="text-sm font-semibold text-neutral-900 hover:text-gold transition-colors cursor-pointer dark:text-white/90"
>
{cls.trainer}
</button>
@@ -256,7 +256,7 @@ function ClassCard({
<span className="rounded-md bg-gold/10 px-2 py-0.5 text-xs font-bold text-gold min-w-[80px] text-center">
<time dateTime={`${cls.startTime}-${cls.endTime}`}>{cls.startTime}{cls.endTime}</time>
</span>
<span className="text-sm font-medium text-white/60">{cls.style}</span>
<span className="text-sm font-medium text-neutral-500 dark:text-white/60">{cls.style}</span>
</div>
</div>
@@ -265,8 +265,8 @@ function ClassCard({
{maxParticipants > 0 && (
<span className={`rounded-full px-2.5 py-0.5 text-xs font-semibold ${
isFull
? "bg-amber-500/15 border border-amber-500/25 text-amber-400"
: "bg-white/[0.04] border border-white/[0.08] text-white/45"
? "bg-amber-500/15 border border-amber-500/25 text-amber-500 dark:text-amber-400"
: "bg-neutral-100 border border-neutral-200 text-neutral-600 dark:bg-white/[0.04] dark:border-white/[0.08] dark:text-white/45"
}`}>
{cls.bookingCount}/{maxParticipants} мест
</span>
@@ -280,7 +280,7 @@ function ClassCard({
className={`shrink-0 self-center rounded-xl px-4 py-2.5 text-xs font-semibold transition-all cursor-pointer ${
isFull
? "bg-amber-500/10 border border-amber-500/25 text-amber-400 hover:bg-amber-500/20 hover:border-amber-500/40"
: "bg-gold/10 border border-gold/25 text-gold hover:bg-gold/20 hover:border-gold/40"
: "bg-gold/10 border border-gold/25 text-gold hover:bg-gold/20 hover:border-gold/40 dark:bg-gold/5 dark:border-gold/15"
}`}
>
{isFull ? "Лист ожидания" : "Записаться"}
+3 -3
View File
@@ -65,7 +65,7 @@ export function Pricing({ data: pricing }: PricingProps) {
className={`inline-flex items-center gap-2 rounded-full px-6 py-2.5 text-sm font-medium transition-all duration-300 cursor-pointer ${
activeTab === tab.id
? "bg-gold text-black shadow-lg shadow-gold/25"
: "bg-neutral-100 text-neutral-600 hover:bg-neutral-200 dark:bg-white/[0.06] dark:text-neutral-300 dark:hover:bg-white/[0.1]"
: "bg-white border border-neutral-300 text-neutral-600 hover:bg-neutral-50 hover:border-neutral-400 dark:border-transparent dark:bg-white/[0.06] dark:text-neutral-300 dark:hover:bg-white/[0.1]"
}`}
>
{tab.icon}
@@ -92,7 +92,7 @@ export function Pricing({ data: pricing }: PricingProps) {
className={`group relative rounded-2xl border p-5 transition-all duration-300 ${
isPopular
? "border-gold/40 bg-gradient-to-br from-gold/10 via-transparent to-gold/5 dark:from-gold/[0.07] dark:to-gold/[0.02] shadow-lg shadow-gold/10"
: "border-neutral-200 bg-white dark:border-white/[0.06] dark:bg-neutral-950"
: "border-neutral-200 bg-white shadow-sm shadow-gold/[0.04] hover:shadow-md hover:shadow-gold/[0.08] dark:border-white/[0.06] dark:bg-neutral-950 dark:shadow-none dark:hover:shadow-none"
}`}
>
{/* Popular badge */}
@@ -113,7 +113,7 @@ export function Pricing({ data: pricing }: PricingProps) {
{/* Note */}
{item.note && (
<p className="mt-1 text-xs text-neutral-400 dark:text-neutral-500">
<p className="mt-1 text-xs text-neutral-500 dark:text-neutral-500">
{item.note}
</p>
)}
+4 -4
View File
@@ -336,7 +336,7 @@ export function Schedule({ data: schedule, scheduleConfig, classItems, teamMembe
<span className="block leading-tight">{loc.name}</span>
{loc.address && (
<span className={`block text-xs font-normal leading-tight mt-0.5 ${
locationMode === i ? "text-black/60" : "text-neutral-400 dark:text-white/25"
locationMode === i ? "text-black/60" : "text-neutral-500 dark:text-white/25"
}`}>
{shortAddress(loc.address)}
</span>
@@ -377,7 +377,7 @@ export function Schedule({ data: schedule, scheduleConfig, classItems, teamMembe
{/* View mode toggle + filter button */}
<Reveal>
<div className="mt-4 hidden sm:flex items-center justify-center">
<div className="inline-flex items-center rounded-xl border border-neutral-200 bg-neutral-100 p-1 dark:border-white/[0.08] dark:bg-white/[0.04]" role="tablist" aria-label="Режим отображения">
<div className="inline-flex items-center rounded-xl border border-neutral-300 bg-neutral-200/60 p-1 dark:border-white/[0.08] dark:bg-white/[0.04]" role="tablist" aria-label="Режим отображения">
<button
role="tab"
aria-selected={viewMode === "days"}
@@ -406,7 +406,7 @@ export function Schedule({ data: schedule, scheduleConfig, classItems, teamMembe
</button>
{/* Divider */}
<span className="mx-1 h-5 w-px bg-white/[0.08]" />
<span className="mx-1 h-5 w-px bg-neutral-200 dark:bg-white/[0.08]" />
<ScheduleFilters
typeDots={typeDots}
@@ -470,7 +470,7 @@ export function Schedule({ data: schedule, scheduleConfig, classItems, teamMembe
))}
{filteredDays.length === 0 && (
<div className="col-span-full py-12 text-center text-sm text-neutral-400 dark:text-white/30">
<div className="col-span-full py-12 text-center text-sm text-neutral-500 dark:text-white/30">
Нет занятий по выбранным фильтрам
</div>
)}
+5 -5
View File
@@ -34,7 +34,7 @@ function ClassRow({
return (
<div className="px-5 py-3.5">
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2 text-sm text-neutral-500 dark:text-white/40">
<div className="flex items-center gap-2 text-sm text-neutral-600 dark:text-white/40">
<Clock size={13} />
<span className="font-semibold">{cls.time}</span>
</div>
@@ -59,7 +59,7 @@ function ClassRow({
className="flex items-center gap-2 cursor-pointer active:opacity-60"
>
<span className={`h-2 w-2 shrink-0 rounded-full ${typeDots[cls.type] ?? "bg-white/30"}`} />
<span className="text-xs text-neutral-500 dark:text-white/40">{cls.type}</span>
<span className="text-xs text-neutral-600 dark:text-white/40">{cls.type}</span>
</button>
</div>
</div>
@@ -82,7 +82,7 @@ export function DayCard({ day, typeDots, showLocation, filterTrainerSet, toggleF
: null;
return (
<div className="rounded-2xl border border-neutral-200 bg-white dark:border-white/[0.06] dark:bg-[#0a0a0a] overflow-hidden">
<div className="rounded-2xl border border-neutral-200 bg-white shadow-sm shadow-gold/[0.04] dark:border-white/[0.06] dark:bg-[#0a0a0a] dark:shadow-none overflow-hidden">
{/* Day header */}
<div className="border-b border-neutral-100 bg-neutral-50 px-5 py-4 dark:border-white/[0.04] dark:bg-white/[0.02]">
<div className="flex items-center gap-3">
@@ -104,10 +104,10 @@ export function DayCard({ day, typeDots, showLocation, filterTrainerSet, toggleF
{/* Location sub-header */}
<div className={`flex items-center gap-1.5 px-5 py-2 bg-gold/10 ${gi > 0 ? "border-t border-gold/10" : ""}`}>
<MapPin size={11} className="shrink-0 text-gold" />
<span className="text-[11px] font-medium text-white">
<span className="text-[11px] font-medium text-neutral-800 dark:text-white">
{locName}
{address && shortAddress(address) !== locName && (
<span className="text-white/50"> · {shortAddress(address)}</span>
<span className="text-neutral-500 dark:text-white/50"> · {shortAddress(address)}</span>
)}
</span>
</div>
@@ -163,7 +163,7 @@ export function GroupView({
if (groups.length === 0) {
return (
<div className="py-12 text-center text-sm text-neutral-400 dark:text-white/30">
<div className="py-12 text-center text-sm text-neutral-600 dark:text-white/30">
Нет занятий по выбранным фильтрам
</div>
);
@@ -187,7 +187,7 @@ export function GroupView({
window.dispatchEvent(new CustomEvent("openTrainerProfile", { detail: trainer }));
}}
className={`relative flex items-center justify-center h-9 w-9 rounded-full overflow-hidden transition-all cursor-pointer ${
isActive ? "ring-2 ring-gold/50" : "ring-1 ring-white/10 hover:ring-gold/30"
isActive ? "ring-2 ring-gold/50" : "ring-1 ring-neutral-200 hover:ring-gold/30 dark:ring-white/10"
}`}
title={`Подробнее о ${trainer}`}
>
@@ -200,8 +200,8 @@ export function GroupView({
sizes="36px"
/>
) : (
<div className={`flex items-center justify-center h-full w-full ${isActive ? "bg-gold/20" : "bg-white/[0.06]"}`}>
<User size={14} className={isActive ? "text-gold" : "text-white/40"} />
<div className={`flex items-center justify-center h-full w-full ${isActive ? "bg-gold/20" : "bg-neutral-100 dark:bg-white/[0.06]"}`}>
<User size={14} className={isActive ? "text-gold" : "text-neutral-500 dark:text-white/40"} />
</div>
)}
</button>
@@ -211,7 +211,7 @@ export function GroupView({
className="cursor-pointer group"
>
<span className={`text-base font-semibold transition-colors ${
isActive ? "text-gold" : "text-white/90 group-hover:text-gold"
isActive ? "text-gold" : "text-neutral-900 group-hover:text-gold dark:text-white/90"
}`}>
{trainer}
</span>
@@ -236,7 +236,7 @@ export function GroupView({
className={`rounded-xl border transition-all ${
false
? "border-gold/20 bg-gold/[0.03] hover:border-gold/30 hover:bg-gold/[0.05]"
: "border-white/[0.06] bg-white/[0.02] hover:border-white/[0.12] hover:bg-white/[0.04]"
: "border-neutral-200 bg-white hover:border-neutral-300 hover:bg-neutral-50 dark:border-white/[0.06] dark:bg-white/[0.02] dark:hover:border-white/[0.12] dark:hover:bg-white/[0.04]"
}`}
>
<div className="flex items-start gap-3 p-3 sm:p-4">
@@ -67,10 +67,10 @@ function ClassRow({
className={`flex items-center gap-1.5 active:opacity-60 ${filterTypes.has(cls.type) ? "opacity-100" : ""}`}
>
<span className={`h-1.5 w-1.5 shrink-0 rounded-full ${typeDots[cls.type] ?? "bg-white/30"}`} />
<span className="text-[11px] text-neutral-400 dark:text-white/30">{cls.type}</span>
<span className="text-[11px] text-neutral-600 dark:text-white/30">{cls.type}</span>
</button>
{showLocation && cls.locationName && (
<span className="flex items-center gap-0.5 text-[10px] text-neutral-400 dark:text-white/20">
<span className="flex items-center gap-0.5 text-[10px] text-neutral-500 dark:text-white/20">
<MapPin size={8} className="shrink-0" />
{cls.locationName}
</span>
@@ -160,9 +160,9 @@ export function MobileSchedule({
{/* Location sub-header */}
<div className="ml-3 flex items-center gap-1 px-3 py-1.5">
<MapPin size={9} className="shrink-0 text-neutral-400 dark:text-white/20" />
<span className="text-[10px] font-medium text-neutral-400 dark:text-white/25">
<span className="text-[10px] font-medium text-neutral-500 dark:text-white/25">
{locName}
{address && <span className="text-neutral-300 dark:text-white/15"> · {shortAddress(address)}</span>}
{address && <span className="text-neutral-400 dark:text-white/15"> · {shortAddress(address)}</span>}
</span>
</div>
{classes.map((cls, i) => (
@@ -200,7 +200,7 @@ export function MobileSchedule({
})}
</div>
) : (
<div className="py-12 text-center text-sm text-neutral-400 dark:text-white/30">
<div className="py-12 text-center text-sm text-neutral-500 dark:text-white/30">
Нет занятий по выбранным фильтрам
</div>
)}
@@ -86,7 +86,7 @@ export function ScheduleFilters({
className={`inline-flex items-center gap-1.5 rounded-full px-5 py-2.5 text-sm font-medium transition-all duration-200 cursor-pointer sm:rounded-lg sm:px-4 sm:py-2 sm:text-xs ${
totalActive > 0
? "border border-gold/40 bg-gold/10 text-gold sm:border-0 sm:bg-white sm:text-neutral-900 sm:shadow-sm dark:sm:bg-white/10 dark:sm:text-white"
: "border border-neutral-300 text-neutral-500 hover:text-neutral-700 dark:border-white/10 dark:text-neutral-400 dark:hover:text-white dark:hover:border-white/20 sm:border-0 sm:hover:text-neutral-700 dark:sm:text-white/35 dark:sm:hover:text-white/60"
: "border border-neutral-300 text-neutral-600 hover:text-neutral-700 dark:border-white/10 dark:text-neutral-400 dark:hover:text-white dark:hover:border-white/20 sm:border-0 sm:hover:text-neutral-700 dark:sm:text-white/35 dark:sm:hover:text-white/60"
}`}
>
<SlidersHorizontal size={16} className="sm:hidden" />
@@ -102,12 +102,12 @@ export function ScheduleFilters({
{modalOpen && createPortal(
<ScheduleFilterModal modalOpen={modalOpen} onClose={() => setModalOpen(false)}>
{/* Header */}
<div className="flex items-center justify-between px-6 py-4 border-b border-white/[0.06]">
<h3 className="text-base font-bold text-white">Фильтры</h3>
<div className="flex items-center justify-between px-6 py-4 border-b border-neutral-200 dark:border-white/[0.06]">
<h3 className="text-base font-bold text-neutral-900 dark:text-white">Фильтры</h3>
<button
onClick={() => setModalOpen(false)}
aria-label="Закрыть"
className="flex h-11 w-11 items-center justify-center rounded-full text-neutral-400 hover:bg-white/[0.06] hover:text-white transition-colors cursor-pointer"
className="flex h-11 w-11 items-center justify-center rounded-full text-neutral-500 hover:bg-neutral-100 hover:text-neutral-900 dark:text-neutral-400 dark:hover:bg-white/[0.06] dark:hover:text-white transition-colors cursor-pointer"
>
<X size={18} />
</button>
@@ -125,7 +125,7 @@ export function ScheduleFilters({
className={`${pillBase} ${
filterTypes.has(type)
? "bg-gold text-black border border-gold"
: "border border-gold/30 text-white hover:border-gold/60 hover:bg-gold/10"
: "border border-gold/30 text-neutral-700 hover:border-gold/60 hover:bg-gold/10 dark:text-white"
}`}
>
<span className={`h-1.5 w-1.5 shrink-0 rounded-full ${typeDots[type] ?? "bg-white/30"}`} />
@@ -159,8 +159,8 @@ export function ScheduleFilters({
onClick={() => toggleFilterStatus(statusKey)}
className={`w-full rounded-xl px-3 py-3 text-center text-xs font-semibold transition-all cursor-pointer border ${
active
? "border-gold bg-gold/10 text-white"
: "border-white/[0.08] bg-white/[0.02] text-neutral-400 hover:border-white/[0.15] hover:bg-white/[0.04]"
? "border-gold bg-gold/10 text-neutral-900 dark:text-white"
: "border-neutral-200 bg-neutral-50 text-neutral-500 hover:border-neutral-300 hover:bg-neutral-100 dark:border-white/[0.08] dark:bg-white/[0.02] dark:text-neutral-400 dark:hover:border-white/[0.15] dark:hover:bg-white/[0.04]"
}`}
>
{label}
@@ -190,7 +190,7 @@ export function ScheduleFilters({
className={`flex items-center gap-2.5 w-full rounded-lg px-3 py-2 transition-all cursor-pointer ${
active
? "bg-gold/10"
: "hover:bg-white/[0.03]"
: "hover:bg-neutral-100 dark:hover:bg-white/[0.03]"
}`}
onClick={() => setFilterLevel(active ? null : level)}
role="radio"
@@ -199,11 +199,11 @@ export function ScheduleFilters({
onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setFilterLevel(active ? null : level); } }}
>
<span className={`flex h-4 w-4 shrink-0 items-center justify-center rounded-full border-2 transition-colors ${
active ? "border-gold" : "border-white/20"
active ? "border-gold" : "border-neutral-300 dark:border-white/20"
}`}>
{active && <span className="h-2 w-2 rounded-full bg-gold" />}
</span>
<span className={`text-xs font-medium ${active ? "text-white" : "text-neutral-400"}`}>
<span className={`text-xs font-medium ${active ? "text-neutral-900 dark:text-white" : "text-neutral-600 dark:text-neutral-400"}`}>
{level}
</span>
{desc && <InfoTip text={desc} />}
@@ -225,7 +225,7 @@ export function ScheduleFilters({
className={`flex items-center justify-center rounded-md w-10 h-10 text-[11px] font-semibold transition-all cursor-pointer ${
filterDaySet.has(day)
? "bg-gold text-black"
: "bg-white/[0.04] text-neutral-400 hover:bg-white/[0.08] hover:text-white"
: "bg-neutral-100 text-neutral-500 hover:bg-neutral-200 hover:text-neutral-900 dark:bg-white/[0.04] dark:text-neutral-400 dark:hover:bg-white/[0.08] dark:hover:text-white"
}`}
>
{dayShort}
@@ -238,14 +238,14 @@ export function ScheduleFilters({
type="time"
value={filterTime.from}
onChange={(e) => setFilterTime({ ...filterTime, from: e.target.value })}
className="w-20 shrink-0 rounded-md border border-white/[0.08] bg-white/[0.04] px-2 py-1.5 text-[11px] text-white text-center outline-none focus:border-gold/40 transition-colors [color-scheme:dark]"
className="w-20 shrink-0 rounded-md border border-neutral-200 bg-neutral-100 px-2 py-1.5 text-[11px] text-neutral-900 text-center outline-none focus:border-gold/40 transition-colors dark:border-white/[0.08] dark:bg-white/[0.04] dark:text-white [color-scheme:light] dark:[color-scheme:dark]"
/>
<span className="text-neutral-500 text-[10px]"></span>
<input
type="time"
value={filterTime.to}
onChange={(e) => setFilterTime({ ...filterTime, to: e.target.value })}
className="w-20 shrink-0 rounded-md border border-white/[0.08] bg-white/[0.04] px-2 py-1.5 text-[11px] text-white text-center outline-none focus:border-gold/40 transition-colors [color-scheme:dark]"
className="w-20 shrink-0 rounded-md border border-neutral-200 bg-neutral-100 px-2 py-1.5 text-[11px] text-neutral-900 text-center outline-none focus:border-gold/40 transition-colors dark:border-white/[0.08] dark:bg-white/[0.04] dark:text-white [color-scheme:light] dark:[color-scheme:dark]"
/>
</div>
</div>
@@ -253,10 +253,10 @@ export function ScheduleFilters({
</div>
{/* Footer */}
<div className="flex items-center justify-between px-6 py-4 border-t border-white/[0.06]">
<div className="flex items-center justify-between px-6 py-4 border-t border-neutral-200 dark:border-white/[0.06]">
<button
onClick={() => { clearFilters(); setModalOpen(false); }}
className="text-sm text-neutral-400 hover:text-white transition-colors cursor-pointer"
className="text-sm text-neutral-500 hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-white transition-colors cursor-pointer"
>
Сбросить всё
</button>
@@ -288,8 +288,7 @@ function ScheduleFilterModal({ modalOpen, onClose, children }: { modalOpen: bool
>
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" />
<div
className="relative w-full max-w-lg max-h-[85vh] flex flex-col rounded-2xl border border-white/[0.08] shadow-2xl overflow-hidden"
style={{ backgroundColor: "#171717" }}
className="relative w-full max-w-lg max-h-[85vh] flex flex-col rounded-2xl border border-neutral-200 bg-white shadow-2xl overflow-hidden dark:border-white/[0.08] dark:bg-[#171717]"
onClick={(e) => e.stopPropagation()}
>
{children}
@@ -314,18 +313,18 @@ function FilterSection({ title, hint, children }: { title: string; hint?: string
return (
<div>
<div className="flex items-center gap-1.5 mb-3">
<h4 className="text-sm font-semibold text-white">{title}</h4>
<h4 className="text-sm font-semibold text-neutral-900 dark:text-white">{title}</h4>
{hint && (
<div ref={hintRef} className="relative">
<button
type="button"
onClick={() => setShowHint(!showHint)}
className="flex h-4 w-4 items-center justify-center rounded-full border border-white/15 text-[10px] text-neutral-500 hover:text-white hover:border-white/30 transition-colors cursor-pointer"
className="flex h-4 w-4 items-center justify-center rounded-full border border-neutral-300 text-[10px] text-neutral-500 hover:text-neutral-900 hover:border-neutral-400 dark:border-white/15 dark:hover:text-white dark:hover:border-white/30 transition-colors cursor-pointer"
>
?
</button>
{showHint && (
<div className="absolute left-6 top-1/2 -translate-y-1/2 z-10 w-56 rounded-lg border border-white/10 px-3 py-2 text-[11px] leading-relaxed text-neutral-300 shadow-xl" style={{ backgroundColor: "#1a1a1a" }}>
<div className="absolute left-6 top-1/2 -translate-y-1/2 z-10 w-56 rounded-lg border border-neutral-200 bg-white px-3 py-2 text-[11px] leading-relaxed text-neutral-600 shadow-xl dark:border-white/10 dark:bg-[#1a1a1a] dark:text-neutral-300">
{hint}
</div>
)}
@@ -359,7 +358,7 @@ function InfoTip({ text }: { text: string }) {
aria-label="Подсказка"
aria-expanded={open}
className={`flex h-4 w-4 items-center justify-center rounded-full border text-[10px] transition-colors cursor-pointer ${
open ? "border-gold/40 text-gold" : "border-white/15 text-neutral-500 hover:text-white hover:border-white/30"
open ? "border-gold/40 text-gold" : "border-neutral-300 text-neutral-500 hover:text-neutral-900 hover:border-neutral-400 dark:border-white/15 dark:hover:text-white dark:hover:border-white/30"
}`}
>
?
@@ -413,7 +412,7 @@ function TrainerMultiSelect({
<div
onClick={() => { setOpen(true); inputRef.current?.focus(); }}
className={`flex flex-wrap items-center gap-1.5 rounded-lg border px-3 py-2 min-h-[42px] cursor-text transition-colors ${
open ? "border-gold bg-white/[0.06]" : "border-white/[0.08] bg-white/[0.04]"
open ? "border-gold bg-neutral-50 dark:bg-white/[0.06]" : "border-neutral-200 bg-neutral-100 dark:border-white/[0.08] dark:bg-white/[0.04]"
}`}
>
{Array.from(selected).map((t) => (
@@ -437,12 +436,12 @@ function TrainerMultiSelect({
if (e.key === "Escape") { setOpen(false); setSearch(""); }
}}
placeholder={selected.size === 0 ? "Все тренеры" : ""}
className="flex-1 min-w-[80px] bg-transparent text-sm text-white placeholder-neutral-500 outline-none"
className="flex-1 min-w-[80px] bg-transparent text-sm text-neutral-900 placeholder-neutral-500 outline-none dark:text-white"
/>
</div>
{open && filtered.length > 0 && (
<div className="absolute z-10 mt-1 w-full rounded-lg border border-white/[0.08] shadow-xl overflow-hidden" style={{ backgroundColor: "#1a1a1a" }}>
<div className="absolute z-10 mt-1 w-full rounded-lg border border-neutral-200 bg-white shadow-xl overflow-hidden dark:border-white/[0.08] dark:bg-[#1a1a1a]">
<div className="max-h-48 overflow-y-auto styled-scrollbar">
{filtered.map((trainer) => (
<button
@@ -454,7 +453,7 @@ function TrainerMultiSelect({
setSearch("");
inputRef.current?.focus();
}}
className="w-full px-4 py-2 text-left text-sm text-white transition-colors hover:bg-white/[0.05]"
className="w-full px-4 py-2 text-left text-sm text-neutral-900 transition-colors hover:bg-neutral-100 dark:text-white dark:hover:bg-white/[0.05]"
>
{trainer}
</button>
@@ -271,7 +271,7 @@ export function TeamCarousel({ members, activeIndex, onActiveChange }: TeamCarou
{/* Cards */}
{/* Mobile swipe hint */}
<div
className={`absolute bottom-2 left-1/2 -translate-x-1/2 z-20 text-xs text-neutral-400 tracking-wide transition-opacity duration-1000 md:hidden ${
className={`absolute bottom-2 left-1/2 -translate-x-1/2 z-20 text-xs text-neutral-600 dark:text-neutral-400 tracking-wide transition-opacity duration-1000 md:hidden ${
swipeHintVisible ? "opacity-60" : "opacity-0 pointer-events-none"
}`}
>
@@ -303,7 +303,7 @@ export function TeamCarousel({ members, activeIndex, onActiveChange }: TeamCarou
filter: style.filter,
borderColor: style.isCenter ? "transparent" : style.borderColor,
boxShadow: style.isCenter
? "0 0 40px rgba(201,169,110,0.15), 0 0 80px rgba(201,169,110,0.08)"
? "0 0 40px rgba(201,169,110,0.25), 0 0 80px rgba(201,169,110,0.12), 0 8px 32px rgba(0,0,0,0.08)"
: style.boxShadow,
transition: style.transition,
}}
@@ -24,7 +24,7 @@ export function TeamMemberInfo({ members, activeIndex, onSelect, onOpenBio }: Te
href={member.instagram}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1.5 text-sm text-white/40 transition-colors hover:text-gold-light"
className="inline-flex items-center gap-1.5 text-sm text-neutral-500 transition-colors hover:text-gold-dark dark:text-white/40 dark:hover:text-gold-light"
>
<Instagram size={14} />
{member.instagram.split("/").filter(Boolean).pop()}
@@ -32,7 +32,7 @@ export function TeamMemberInfo({ members, activeIndex, onSelect, onOpenBio }: Te
)}
{(member.shortDescription || member.description) && (
<p className="mt-3 text-sm leading-relaxed text-white/55 line-clamp-3">
<p className="mt-3 text-sm leading-relaxed text-neutral-600 dark:text-white/55 line-clamp-3">
{member.shortDescription || member.description}
</p>
)}
@@ -63,7 +63,7 @@ export function TeamMemberInfo({ members, activeIndex, onSelect, onOpenBio }: Te
<span className={`relative block rounded-full transition-all duration-500 ${
isActive
? "w-3 h-3 bg-gold shadow-[0_0_8px_rgba(201,169,110,0.5)]"
: "w-2 h-2 bg-white/20 group-hover:bg-white/40 group-hover:scale-125"
: "w-2 h-2 bg-neutral-400 group-hover:bg-neutral-500 group-hover:scale-125 dark:bg-white/20 dark:group-hover:bg-white/40"
}`} />
</button>
);
+19 -19
View File
@@ -112,12 +112,12 @@ export function TeamProfile({ member, onBack, schedule, scheduleConfig }: TeamPr
<div className="relative shrink-0 w-full sm:w-[380px] lg:w-[420px] sm:sticky sm:top-8">
<button
onClick={onBack}
className="mb-3 inline-flex items-center gap-1.5 rounded-full bg-white/[0.06] px-3 py-1.5 text-sm text-white/50 transition-colors hover:text-white hover:bg-white/[0.1] cursor-pointer"
className="mb-3 inline-flex items-center gap-1.5 rounded-full bg-neutral-100 px-3 py-1.5 text-sm text-neutral-500 transition-colors hover:text-neutral-900 hover:bg-neutral-200 cursor-pointer dark:bg-white/[0.06] dark:text-white/50 dark:hover:text-white dark:hover:bg-white/[0.1]"
>
<ArrowLeft size={14} />
Назад
</button>
<div className="relative aspect-[3/4] overflow-hidden rounded-2xl border border-white/[0.06]">
<div className="relative aspect-[3/4] overflow-hidden rounded-2xl border border-neutral-200 dark:border-white/[0.06]">
<Image
src={member.image}
alt={member.name}
@@ -162,7 +162,7 @@ export function TeamProfile({ member, onBack, schedule, scheduleConfig }: TeamPr
{/* Bio panel — overlaps photo edge on desktop */}
<div className="relative sm:-ml-12 sm:mt-8 mt-0 flex-1 min-w-0 z-10">
<div className="relative rounded-2xl border border-white/[0.08] overflow-hidden shadow-2xl shadow-black/40">
<div className="relative rounded-2xl border border-neutral-200 overflow-hidden shadow-2xl shadow-neutral-300/50 dark:border-white/[0.08] dark:shadow-black/40">
{/* Ambient photo background */}
<div className="absolute inset-0">
<Image
@@ -172,20 +172,20 @@ export function TeamProfile({ member, onBack, schedule, scheduleConfig }: TeamPr
sizes="600px"
className="object-cover scale-150 blur-sm grayscale opacity-70 brightness-[0.6] contrast-[1.3]"
/>
<div className="absolute inset-0 bg-black/20 mix-blend-multiply" />
<div className="absolute inset-0 bg-white/80 dark:bg-black/20 dark:mix-blend-multiply" />
<div className="absolute inset-0 bg-gold/10 mix-blend-color" />
</div>
<div className="relative p-5 sm:p-6 space-y-6">
<div className="relative p-5 sm:p-6 space-y-6 dark:bg-black/40">
{/* Groups — first, most actionable */}
{hasGroups && (
<div>
<h4 className="text-xs font-semibold uppercase tracking-wider text-gold/70 flex items-center gap-2">
<h4 className="text-xs font-semibold uppercase tracking-wider text-gold-dark dark:text-gold/70 flex items-center gap-2">
<Clock size={12} />
Группы
</h4>
<ScrollRow>
{uniqueGroups.map((g, i) => (
<div key={i} className="w-56 shrink-0 rounded-xl border border-white/[0.08] bg-white/[0.03] p-3 flex flex-col">
<div key={i} className="w-56 shrink-0 rounded-xl border border-neutral-200 bg-neutral-50 p-3 flex flex-col dark:border-white/[0.12] dark:bg-white/[0.06]">
<GroupCard
compact
type={g.type}
@@ -207,7 +207,7 @@ export function TeamProfile({ member, onBack, schedule, scheduleConfig }: TeamPr
{/* Description */}
{member.description && (
<div className="text-sm leading-relaxed text-white/50">
<div className="text-sm leading-relaxed text-neutral-600 dark:text-white/50">
{formatMarkup(member.description)}
</div>
)}
@@ -236,7 +236,7 @@ export function TeamProfile({ member, onBack, schedule, scheduleConfig }: TeamPr
{/* Empty state */}
{!hasBio && !member.description && (
<p className="text-sm text-white/30 italic">
<p className="text-sm text-neutral-400 italic dark:text-white/30">
Информация скоро появится
</p>
)}
@@ -281,7 +281,7 @@ function LightboxDialog({ src, onClose }: { src: string; onClose: () => void })
<button
onClick={onClose}
aria-label="Закрыть"
className="absolute top-4 right-4 rounded-full bg-white/10 p-2 text-white hover:bg-white/20 transition-colors"
className="absolute top-4 right-4 rounded-full bg-neutral-200 p-2 text-neutral-700 hover:bg-neutral-300 dark:bg-white/10 dark:text-white dark:hover:bg-white/20 transition-colors"
>
<X size={20} />
</button>
@@ -308,12 +308,12 @@ function CollapsibleSection({ icon: Icon, title, count, children }: { icon: Reac
aria-expanded={open}
className="flex items-center gap-2 w-full text-left cursor-pointer group"
>
<h4 className="text-xs font-semibold uppercase tracking-wider text-gold/70 flex items-center gap-2">
<h4 className="text-xs font-semibold uppercase tracking-wider text-gold-dark dark:text-gold/70 flex items-center gap-2">
<Icon size={12} />
{title}
<span className="text-gold/40">{count}</span>
<span className="text-gold-dark/50 dark:text-gold/40">{count}</span>
</h4>
<ChevronDown size={14} className={`text-gold/40 transition-transform duration-200 group-hover:text-gold/60 ${open ? "rotate-180" : ""}`} />
<ChevronDown size={14} className={`text-gold-dark/50 dark:text-gold/40 transition-transform duration-200 group-hover:text-gold dark:group-hover:text-gold/60 ${open ? "rotate-180" : ""}`} />
</button>
<div
className="grid transition-[grid-template-rows] duration-300 ease-out"
@@ -394,7 +394,7 @@ function ScrollRow({ children }: { children: React.ReactNode }) {
<button
onClick={() => scrollBy(-1)}
aria-label="Прокрутить влево"
className="absolute left-1 top-1/2 -translate-y-1/2 z-10 rounded-full bg-black/80 border border-white/10 p-2.5 text-white/60 hover:text-white hover:bg-black/90 transition-all cursor-pointer"
className="absolute left-1 top-1/2 -translate-y-1/2 z-10 rounded-full bg-white/90 border border-neutral-200 p-2.5 text-neutral-500 hover:text-neutral-900 hover:bg-white dark:bg-black/80 dark:border-white/10 dark:text-white/60 dark:hover:text-white dark:hover:bg-black/90 transition-all cursor-pointer"
>
<ChevronLeft size={14} />
</button>
@@ -403,7 +403,7 @@ function ScrollRow({ children }: { children: React.ReactNode }) {
<button
onClick={() => scrollBy(1)}
aria-label="Прокрутить вправо"
className="absolute right-1 top-1/2 -translate-y-1/2 z-10 rounded-full bg-black/80 border border-white/10 p-2.5 text-white/60 hover:text-white hover:bg-black/90 transition-all cursor-pointer"
className="absolute right-1 top-1/2 -translate-y-1/2 z-10 rounded-full bg-white/90 border border-neutral-200 p-2.5 text-neutral-500 hover:text-neutral-900 hover:bg-white dark:bg-black/80 dark:border-white/10 dark:text-white/60 dark:hover:text-white dark:hover:bg-black/90 transition-all cursor-pointer"
>
<ChevronRight size={14} />
</button>
@@ -418,7 +418,7 @@ function RichCard({ item, onImageClick }: { item: RichListItem; onImageClick: (s
if (hasImage) {
return (
<div className="group w-60 shrink-0 flex rounded-xl border border-white/[0.08] overflow-hidden bg-white/[0.03] transition-all duration-200 hover:border-gold/30 hover:bg-white/[0.06] hover:shadow-lg hover:shadow-gold/5">
<div className="group w-60 shrink-0 flex rounded-xl border border-neutral-200 overflow-hidden bg-white dark:border-white/[0.08] dark:bg-white/[0.03] transition-all duration-200 hover:border-gold/30 dark:hover:bg-white/[0.06] hover:shadow-lg hover:shadow-gold/5">
<button
onClick={() => onImageClick(item.image!)}
className="relative w-18 shrink-0 overflow-hidden cursor-pointer"
@@ -432,7 +432,7 @@ function RichCard({ item, onImageClick }: { item: RichListItem; onImageClick: (s
/>
</button>
<div className="flex-1 min-w-0 p-3">
<p className="text-sm text-white/70 group-hover:text-white/90 transition-colors">{item.text}</p>
<p className="text-sm text-neutral-600 group-hover:text-neutral-900 dark:text-white/70 dark:group-hover:text-white/90 transition-colors">{item.text}</p>
{hasLink && (
<a
href={item.link}
@@ -450,9 +450,9 @@ function RichCard({ item, onImageClick }: { item: RichListItem; onImageClick: (s
}
return (
<div className="group w-60 shrink-0 rounded-xl border border-white/[0.08] overflow-hidden bg-white/[0.03] transition-all duration-200 hover:border-gold/30 hover:bg-white/[0.06] hover:shadow-lg hover:shadow-gold/5">
<div className="group w-60 shrink-0 rounded-xl border border-neutral-200 overflow-hidden bg-white dark:border-white/[0.08] dark:bg-white/[0.03] transition-all duration-200 hover:border-gold/30 dark:hover:bg-white/[0.06] hover:shadow-lg hover:shadow-gold/5">
<div className="p-3">
<p className="text-sm text-white/60 group-hover:text-white/80 transition-colors">{item.text}</p>
<p className="text-sm text-neutral-500 group-hover:text-neutral-800 dark:text-white/60 dark:group-hover:text-white/80 transition-colors">{item.text}</p>
{hasLink && (
<a
href={item.link}
+1 -1
View File
@@ -26,7 +26,7 @@ export function BackToTop() {
<button
onClick={() => window.scrollTo({ top: 0, behavior: "smooth" })}
aria-label="Наверх"
className={`fixed bottom-6 right-6 z-40 flex h-14 w-14 items-center justify-center rounded-full border border-gold/30 bg-black/60 text-gold-light backdrop-blur-sm transition-all duration-300 hover:bg-gold/20 hover:border-gold/50 ${
className={`fixed bottom-6 right-6 z-40 flex h-14 w-14 items-center justify-center rounded-full border border-gold/40 bg-white/80 text-gold-dark backdrop-blur-sm transition-all duration-300 hover:bg-gold/15 hover:border-gold/60 dark:border-gold/30 dark:bg-black/60 dark:text-gold-light dark:hover:bg-gold/20 dark:hover:border-gold/50 ${
visible ? "translate-y-0 opacity-100" : "translate-y-4 opacity-0 pointer-events-none"
}`}
>
+1 -1
View File
@@ -34,7 +34,7 @@ export function FloatingHearts() {
{hearts.map((heart) => (
<div
key={heart.id}
className="absolute text-gold"
className="absolute text-gold-dark dark:text-gold"
style={{
left: `${heart.left}%`,
bottom: "-20px",
+3 -3
View File
@@ -61,7 +61,7 @@ export function GroupCard({
const typeContent = (
<>
<span className={`${dot} shrink-0 rounded-full ${dotColor}`} />
<span className={`${typeCls} font-semibold text-white/90`}>{type}</span>
<span className={`${typeCls} font-semibold text-neutral-900 dark:text-white/90`}>{type}</span>
</>
);
@@ -77,7 +77,7 @@ export function GroupCard({
<span className="inline-flex items-center gap-1.5">{typeContent}</span>
)}
{showLocation && (address || location) && (
<span className={`inline-flex items-center gap-1 rounded-full bg-gold/15 border border-gold/25 ${locSize} font-medium text-white`}>
<span className={`inline-flex items-center gap-1 rounded-full bg-gold/15 border border-gold/25 ${locSize} font-medium text-neutral-700 dark:text-white`}>
<MapPin size={locIcon} className="text-gold" />
{shortAddress(address || location || "")}
</span>
@@ -96,7 +96,7 @@ export function GroupCard({
<span className={`rounded-md bg-gold/10 ${dayPad} font-bold text-gold text-center`}>
{m.days.join(", ")}
</span>
<span className={`${timeCls} tabular-nums text-white/60`}>
<span className={`${timeCls} tabular-nums text-neutral-600 dark:text-white/60`}>
{m.times.join(", ")}
</span>
</div>
+6 -6
View File
@@ -45,13 +45,13 @@ export function NewsModal({ item, onClose }: NewsModalProps) {
<div
ref={focusTrapRef}
className="modal-content relative w-full max-w-2xl max-h-[90vh] overflow-y-auto rounded-2xl border border-white/[0.08] bg-neutral-950 shadow-2xl"
className="modal-content relative w-full max-w-2xl max-h-[90vh] overflow-y-auto rounded-2xl border border-neutral-200 bg-white shadow-2xl dark:border-white/[0.08] dark:bg-neutral-950"
onClick={(e) => e.stopPropagation()}
>
<button
onClick={onClose}
aria-label="Закрыть"
className="absolute right-4 top-4 z-10 flex h-11 w-11 items-center justify-center rounded-full bg-black/50 text-neutral-400 backdrop-blur-sm transition-colors hover:bg-white/[0.1] hover:text-white cursor-pointer"
className="absolute right-4 top-4 z-10 flex h-11 w-11 items-center justify-center rounded-full bg-black/50 text-white/70 backdrop-blur-sm transition-colors hover:bg-black/70 hover:text-white cursor-pointer"
>
<X size={18} />
</button>
@@ -69,21 +69,21 @@ export function NewsModal({ item, onClose }: NewsModalProps) {
transform: `scale(${item.imageZoom ?? 1})`,
}}
/>
<div className="absolute inset-0 bg-gradient-to-t from-neutral-950 via-transparent to-transparent" />
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent" />
</div>
)}
<div className={`p-6 sm:p-8 ${item.image ? "-mt-12 relative" : ""}`}>
<span className="inline-flex items-center gap-1.5 text-xs text-neutral-400">
<span className="inline-flex items-center gap-1.5 text-xs text-neutral-600 dark:text-neutral-400">
<Calendar size={12} />
{formatDateRu(item.date)}
</span>
<h2 className="mt-2 text-xl sm:text-2xl font-bold text-white leading-tight">
<h2 className="mt-2 text-xl sm:text-2xl font-bold text-neutral-900 leading-tight dark:text-white">
{item.title}
</h2>
<p className="mt-4 text-sm sm:text-base leading-relaxed text-neutral-300 whitespace-pre-line">
<p className="mt-4 text-sm sm:text-base leading-relaxed text-neutral-600 whitespace-pre-line dark:text-neutral-300">
{item.text}
</p>
+1 -1
View File
@@ -205,7 +205,7 @@ export function ShowcaseLayout<T>({
className={`cursor-pointer rounded-xl border-2 text-left transition-all duration-300 ${
i === activeIndex
? "border-gold/60 bg-gold/10 dark:bg-gold/5"
: "border-transparent bg-neutral-100 hover:bg-neutral-200 dark:bg-white/[0.03] dark:hover:bg-white/[0.06]"
: "border-neutral-200 bg-white hover:border-neutral-300 hover:bg-neutral-50 dark:border-transparent dark:bg-white/[0.03] dark:hover:border-white/[0.06] dark:hover:bg-white/[0.06]"
}`}
>
{renderSelectorItem(item, i, i === activeIndex)}
+15 -15
View File
@@ -136,13 +136,13 @@ export function SignupModal({
<div className="absolute inset-0 bg-black/70 backdrop-blur-sm" />
<div
ref={focusTrapRef}
className="modal-content relative w-full max-w-md rounded-2xl border border-white/[0.08] bg-neutral-950 p-6 sm:p-8 shadow-2xl"
className="modal-content relative w-full max-w-md rounded-2xl border border-neutral-200 bg-white dark:border-white/[0.08] dark:bg-neutral-950 p-6 sm:p-8 shadow-2xl"
onClick={(e) => e.stopPropagation()}
>
<button
onClick={handleClose}
aria-label="Закрыть"
className="absolute right-4 top-4 flex h-11 w-11 items-center justify-center rounded-full text-neutral-500 transition-colors hover:bg-white/[0.06] hover:text-white cursor-pointer"
className="absolute right-4 top-4 flex h-11 w-11 items-center justify-center rounded-full text-neutral-500 dark:text-neutral-400 transition-colors hover:bg-neutral-100 hover:text-neutral-900 dark:hover:bg-white/[0.06] dark:hover:text-white cursor-pointer"
>
<X size={18} />
</button>
@@ -154,8 +154,8 @@ export function SignupModal({
<div className="mx-auto mb-4 flex h-14 w-14 items-center justify-center rounded-full bg-amber-500/10">
<CheckCircle size={28} className="text-amber-500" />
</div>
<h3 className="text-lg font-bold text-white">Вы в листе ожидания</h3>
<p className="mt-2 text-sm text-neutral-400 leading-relaxed whitespace-pre-line">
<h3 className="text-lg font-bold text-neutral-900 dark:text-white">Вы в листе ожидания</h3>
<p className="mt-2 text-sm text-neutral-500 dark:text-neutral-400 leading-relaxed whitespace-pre-line">
{waitingMessage || "Все места заняты, но мы добавили вас в лист ожидания.\nЕсли кто-то откажется — мы предложим место вам."}
</p>
<a
@@ -173,10 +173,10 @@ export function SignupModal({
<div className="mx-auto mb-4 flex h-14 w-14 items-center justify-center rounded-full bg-emerald-500/10">
<CheckCircle size={28} className="text-emerald-500" />
</div>
<h3 className="text-lg font-bold text-white">
<h3 className="text-lg font-bold text-neutral-900 dark:text-white">
{successMessage || "Вы записаны!"}
</h3>
{subtitle && <p className="mt-1 text-sm text-neutral-400">{subtitle}</p>}
{subtitle && <p className="mt-1 text-sm text-neutral-500 dark:text-neutral-400">{subtitle}</p>}
<a
href={BRAND.instagram}
target="_blank"
@@ -195,8 +195,8 @@ export function SignupModal({
<div className="mx-auto mb-4 flex h-14 w-14 items-center justify-center rounded-full bg-amber-500/10">
<Instagram size={28} className="text-amber-400" />
</div>
<h3 className="text-lg font-bold text-white">Что-то пошло не так</h3>
<p className="mt-2 text-sm text-neutral-400">
<h3 className="text-lg font-bold text-neutral-900 dark:text-white">Что-то пошло не так</h3>
<p className="mt-2 text-sm text-neutral-500 dark:text-neutral-400">
{errorMessage || "Не удалось отправить заявку. Свяжитесь с нами через Instagram — мы запишем вас!"}
</p>
<button
@@ -208,7 +208,7 @@ export function SignupModal({
</button>
<button
onClick={() => setError("")}
className="mt-2 text-xs text-neutral-500 hover:text-white transition-colors cursor-pointer"
className="mt-2 text-xs text-neutral-500 hover:text-neutral-900 dark:hover:text-white transition-colors cursor-pointer"
>
Попробовать снова
</button>
@@ -216,8 +216,8 @@ export function SignupModal({
) : (
<>
<div className="mb-6">
<h3 className="text-xl font-bold text-white">{title}</h3>
{subtitle && <p className="mt-1 text-sm text-neutral-400">{subtitle}</p>}
<h3 className="text-xl font-bold text-neutral-900 dark:text-white">{title}</h3>
{subtitle && <p className="mt-1 text-sm text-neutral-500 dark:text-neutral-400">{subtitle}</p>}
</div>
<form onSubmit={handleSubmit} className="space-y-3">
@@ -231,7 +231,7 @@ export function SignupModal({
placeholder="Ваше имя"
required
aria-required="true"
className="w-full rounded-xl border border-white/[0.08] bg-white/[0.04] px-4 py-3 text-sm text-white placeholder-neutral-500 outline-none transition-colors focus:border-gold/40 focus:bg-white/[0.06]"
className="w-full rounded-xl border border-neutral-300 bg-neutral-50 px-4 py-3 text-sm text-neutral-900 placeholder-neutral-400 outline-none transition-colors focus:border-gold/60 focus:bg-white dark:border-white/[0.08] dark:bg-white/[0.04] dark:text-white dark:placeholder-neutral-500 dark:focus:border-gold/40 dark:focus:bg-white/[0.06]"
/>
</div>
<div className="relative">
@@ -246,7 +246,7 @@ export function SignupModal({
required
aria-required="true"
aria-describedby={error && error !== "network" ? "error-phone" : undefined}
className="w-full rounded-xl border border-white/[0.08] bg-white/[0.04] pl-9 pr-4 py-3 text-sm text-white placeholder-neutral-500 outline-none transition-colors focus:border-gold/40 focus:bg-white/[0.06]"
className="w-full rounded-xl border border-neutral-300 bg-neutral-50 pl-9 pr-4 py-3 text-sm text-neutral-900 placeholder-neutral-400 outline-none transition-colors focus:border-gold/60 focus:bg-white dark:border-white/[0.08] dark:bg-white/[0.04] dark:text-white dark:placeholder-neutral-500 dark:focus:border-gold/40 dark:focus:bg-white/[0.06]"
/>
</div>
<div className="grid grid-cols-2 gap-2">
@@ -259,7 +259,7 @@ export function SignupModal({
value={instagram}
onChange={(e) => setInstagram(e.target.value.replace(/^@/, ""))}
placeholder="Instagram"
className="w-full rounded-xl border border-white/[0.08] bg-white/[0.04] pl-7 pr-3 py-3 text-sm text-white placeholder-neutral-500 outline-none transition-colors focus:border-gold/40 focus:bg-white/[0.06]"
className="w-full rounded-xl border border-neutral-300 bg-neutral-50 pl-7 pr-3 py-3 text-sm text-neutral-900 placeholder-neutral-400 outline-none transition-colors focus:border-gold/60 focus:bg-white dark:border-white/[0.08] dark:bg-white/[0.04] dark:text-white dark:placeholder-neutral-500 dark:focus:border-gold/40 dark:focus:bg-white/[0.06]"
/>
</div>
<div className="relative">
@@ -271,7 +271,7 @@ export function SignupModal({
value={telegram}
onChange={(e) => setTelegram(e.target.value.replace(/^@/, ""))}
placeholder="Telegram"
className="w-full rounded-xl border border-white/[0.08] bg-white/[0.04] pl-7 pr-3 py-3 text-sm text-white placeholder-neutral-500 outline-none transition-colors focus:border-gold/40 focus:bg-white/[0.06]"
className="w-full rounded-xl border border-neutral-300 bg-neutral-50 pl-7 pr-3 py-3 text-sm text-neutral-900 placeholder-neutral-400 outline-none transition-colors focus:border-gold/60 focus:bg-white dark:border-white/[0.08] dark:bg-white/[0.04] dark:text-white dark:placeholder-neutral-500 dark:focus:border-gold/40 dark:focus:bg-white/[0.06]"
/>
</div>
</div>
+7 -5
View File
@@ -3,14 +3,15 @@
import { Moon, Sun } from "lucide-react";
import { useEffect, useState } from "react";
export function ThemeToggle() {
const [dark, setDark] = useState(false);
export function ThemeToggle({ className = "" }: { className?: string }) {
const [mounted, setMounted] = useState(false);
const [dark, setDark] = useState(true);
useEffect(() => {
const stored = localStorage.getItem("theme");
const isDark = stored !== "light";
setDark(isDark);
document.documentElement.classList.toggle("dark", isDark);
setMounted(true);
}, []);
function toggle() {
@@ -24,9 +25,10 @@ export function ThemeToggle() {
<button
onClick={toggle}
aria-label="Переключить тему"
className="rounded-full p-2 text-neutral-400 transition-all duration-300 hover:bg-neutral-100 hover:text-neutral-900 dark:text-neutral-500 dark:hover:bg-white/[0.05] dark:hover:text-white"
suppressHydrationWarning
className={`rounded-full p-2 transition-all duration-300 text-neutral-500 hover:bg-neutral-200 hover:text-neutral-900 dark:text-neutral-500 dark:hover:bg-white/[0.05] dark:hover:text-white ${className}`}
>
{dark ? <Sun size={18} /> : <Moon size={18} />}
{mounted ? (dark ? <Sun size={18} /> : <Moon size={18} />) : <span className="block w-[18px] h-[18px]" />}
</button>
);
}