sand
Тренировки--color-sand-500 · #F4D06F
- 50
- 100
- 300
- 500
- 700
Design system · v0.3 (Phase 3)
Кернел дизайн-системы после миграции на Nuxt 4 + Nuxt UI 4 + Tailwind v4. UI-компоненты скопированы 1:1 из frontend/components/ui/ и адаптированы под новый стек. Используется Phase 4 параллельными pages-агентами, дизайнером для ревью и любым новым контрибьютором.
Раздел 2
3 бренд-палитры + dark scale + нейтральные. Источник — @theme блок в app/assets/css/main.css.
--color-sand-500 · #F4D06F
--color-coral-500 · #FF6B6B
--color-marine-500 · #0E6BA8
--color-dark-500 · #111827
--color-success-500 · #10B981
--color-error-500 · #EF4444
Раздел 3
Заголовки — Montserrat (700/800), текст — Inter (400/500/600). Размеры — clamp() от mobile к desktop.
clamp(3rem, 4vw+1.5rem, 4.5rem) · 48 → 72px
clamp(2.25rem, 3vw+1rem, 3.5rem) · 36 → 56px
clamp(1.75rem, 2vw+0.75rem, 2.5rem) · 28 → 40px
clamp(1.5rem, 1.5vw+0.5rem, 2rem) · 24 → 32px
Body · Inter 400 / 500. Основной текст материалов, статей и описаний. Пляжный волейбол — это динамика, ритм и доверие к команде.
clamp(1rem, 0.5vw+0.875rem, 1.125rem) · 16 → 18px
Small · 14 → 16px. Подписи, пометки, вторичные ссылки.
clamp(0.875rem, 0.25vw+0.8rem, 1rem)
<h1 class="text-h1 font-heading font-extrabold">…</h1>
<h2 class="text-h2 font-heading font-bold">…</h2>
<p class="text-body font-body">…</p>
<p class="text-small">…</p>
/* CSS-токены (main.css) */
--text-h1: clamp(3rem, 4vw + 1.5rem, 4.5rem);
--text-body: clamp(1rem, 0.5vw + 0.875rem, 1.125rem);Раздел 4
Универсальное скругление 24px (radius-3xl). Все карточки, кнопки, модальные.
Spacing scale (Tailwind default)
1 / 2 / 4 / 6 / 8 / 12 / 16 / 24 (× 4px)
/* Универсальное скругление */
.card { border-radius: var(--radius-3xl); } /* 24px */
.button { border-radius: var(--radius-3xl); } /* 24px */
/* Token scale (CSS variables) */
--radius-md: 0.75rem; /* small chip */
--radius-2xl: 1.5rem; /* card, modal, image */
--radius-3xl: 1.5rem; /* alias of 2xl in v'ball */Раздел 5
5 keyframes + page transitions. Длительность 300–500ms. Easing — ease-in-out / cubic-bezier.
/* Все 5 keyframes — в app/assets/css/main.css */
@keyframes fadeIn { 0% { opacity: 0 } 100% { opacity: 1 } }
@keyframes slideUp { from { translateY(100%); opacity: 0 } to { … } }
@keyframes fadeInUp { from { translateY(30px); opacity: 0 } to { … } }
@keyframes pulse-soft { 0%,100% { opacity: 1 } 50% { opacity: 0.6 } }
/* Utility classes */
.animate-fade-in { animation: fadeIn 0.6s ease-out backwards; }
.animate-fade-in-up { animation: fadeInUp 0.8s ease-out forwards; }
.animate-slide-up { animation: slideUp 0.5s ease-out; }Раздел 6
components/ui/*.vue · auto-import без префикса (Breadcrumbs, LoadingButton…)
components/ui/Breadcrumbs.vue
Preview
Usage
<Breadcrumbs :items="[
{ name: 'Главная', url: '/' },
{ name: 'Турниры', url: '/tournaments' },
{ name: 'Two Top 2026', url: '#' },
]" />components/ui/LoadingButton.vue
Preview
Usage
<LoadingButton :loading="saving" loading-text="Сохраняем…">
Записаться
</LoadingButton>components/ui/RippleButton.vue
Preview
Usage
<RippleButton @click="onSubmit">
Записаться на тренировку
</RippleButton>components/ui/VballImage.vue
Preview

Usage
<VballImage
:src="photoUrl"
alt="Описание для скринридера"
aspect-ratio="1/1"
rounded="3xl"
loading="lazy"
/>components/ui/ImageWithFade.vue
Preview
ImageWithFade
ImageWithSkeleton
Usage
<ImageWithFade :src="url" aspect-ratio="1" />
<ImageWithSkeleton :src="url" aspect-ratio="16/9" rounded="2xl" />components/ui/Skeleton*.vue
Preview
SkeletonBox (раздельно)
SkeletonCard
SkeletonList
Usage
<SkeletonBox height="20px" width="60%" />
<SkeletonCard :has-image="true" :has-button="true" />
<SkeletonList :count="3" :has-avatar="true" />components/ui/LoadingOverlay.vue
Preview
Usage
<LoadingOverlay
:show="isSaving"
message="Сохраняем…"
variant="blur" <!-- 'transparent' | 'blur' | 'solid' -->
size="medium"
/>Раздел 7
components/*.vue — отзывы, статус бэка, версия. Auto-import без префиксов.
components/ReviewCard.vue
Preview
Отличная школа, тренеры внимательные, прогресс пошёл со второго занятия. Очень рекомендую.
Usage
<ReviewCard :review="review" />
/* ReviewWithUser shape:
{ id, rating, text, publishedAt,
user: { displayName, description, photoURL } } */components/ReviewsList.vue
Preview
Отличная школа, тренеры внимательные, прогресс пошёл со второго занятия. Очень рекомендую.
Хорошая компания и атмосфера. Хотелось бы больше выездов на турниры.
Записался ради формы, остался ради команды. Лучшее решение за последний год.
Usage
<ReviewsList :reviews="reviews" :loading="loading" :limit="6" />components/ReviewForm.vue · ReviewModal.vue
Preview
POST /api/reviews · нужна сессия (войди как participant/admin). Без авторизации форма покажет toast «Требуется авторизация».
Usage
<button @click="open = true">Оставить отзыв</button>
<ReviewModal v-model="open" @success="refresh()" />
<!-- standalone форма без модала -->
<ReviewForm
:existing-review="myReview"
@success="close"
@cancel="close"
/>components/BackendStatus.vue · VersionBadge.vue
Preview
Виджеты прибиты к нижнему-правому углу экрана (position: fixed). BackendStatus уже виден на всех страницах через layouts/default.vue; VersionBadge — в admin layout.
/api/ping раз в 30s./api/version.Usage
<!-- в layouts/default.vue -->
<BackendStatus />
<VersionBadge />components/UpdatePrompt.vue
Preview
Компонент-плейсхолдер для prompt «Доступна новая версия». PWA пока не подключен (devops-agent), поэтому prompt никогда не появляется. Когда service worker будет включён — раскомментировать useRegisterSW() внутри компонента.
Usage
<!-- в layouts/default.vue -->
<UpdatePrompt />Раздел 8
UButton, UInput, USelect, UTooltip, UModal — используем их напрямую с дефолтами из app.config.ts.
@nuxt/ui
Preview
Usage
<UButton variant="solid" color="primary" size="md">
Записаться
</UButton>
<!-- Дефолт color=primary, size=md заданы в app.config.ts -->@nuxt/ui
Preview
Usage
<UFormField label="Email" required>
<UInput v-model="email" type="email" placeholder="…" />
</UFormField>
<USelect v-model="level" :items="levelOptions" />
<UTextarea v-model="note" :rows="3" />
<UCheckbox v-model="agree" label="…" />@nuxt/ui
Preview
Все disabled-кнопки обязательно оборачиваем в UTooltip с причиной — native title="" не работает на mobile и блокируется pointer-events:none.
Usage
<UTooltip text="Нужно сначала выбрать уровень">
<UButton :disabled="!level">Записаться</UButton>
</UTooltip>@nuxt/ui
Preview
Usage
<UButton @click="open = true">Open</UButton>
<UModal v-model:open="open" title="…">
<template #body>…</template>
<template #footer>…</template>
</UModal>Раздел 9
Глобальные классы из app/assets/css/main.css — используются Phase 4 при поэтапной миграции страниц.
.card
белая карточка с hover scale
.card-dark
для tournaments
.card-marine
для camps
.heading-1
.heading-2
.heading-3
.text-small — служебный текст
<!-- Только на pages, ещё не мигрированных на Nuxt UI 4 -->
<button class="btn-primary">Записаться</button>
<button class="btn-secondary">Отмена</button>
<button class="btn-dark">Tournament CTA</button>
<div class="card">…</div>
<div class="card-dark">…</div>
<div class="card-marine">…</div>
<h1 class="heading-1">…</h1>
<h2 class="heading-2">…</h2>