Design system · v0.3 (Phase 3)

Дизайн-система v'ball

Кернел дизайн-системы после миграции на Nuxt 4 + Nuxt UI 4 + Tailwind v4. UI-компоненты скопированы 1:1 из frontend/components/ui/ и адаптированы под новый стек. Используется Phase 4 параллельными pages-агентами, дизайнером для ревью и любым новым контрибьютором.

10 ui/* компонентов7 shared компонентов3 темы (sand · coral · marine)5 keyframes

Раздел 2

Цветовые палитры

3 бренд-палитры + dark scale + нейтральные. Источник — @theme блок в app/assets/css/main.css.

sand

Тренировки

--color-sand-500 · #F4D06F

  • 50
  • 100
  • 300
  • 500
  • 700

coral

Турниры

--color-coral-500 · #FF6B6B

  • 50
  • 100
  • 300
  • 500
  • 700

marine

Кэмпы

--color-marine-500 · #0E6BA8

  • 50
  • 100
  • 300
  • 500
  • 700

dark

Текст / поверхности

--color-dark-500 · #111827

  • 100
  • 300
  • 500
  • 700
  • 900

success

Успех

--color-success-500 · #10B981

  • 50
  • 500
  • 600

error

Ошибка

--color-error-500 · #EF4444

  • 50
  • 500
  • 600

Раздел 3

Типографика

Заголовки — Montserrat (700/800), текст — Inter (400/500/600). Размеры — clamp() от mobile к desktop.

H1 · Montserrat 800

clamp(3rem, 4vw+1.5rem, 4.5rem) · 48 → 72px

H2 · Montserrat 700

clamp(2.25rem, 3vw+1rem, 3.5rem) · 36 → 56px

H3 · Montserrat 600

clamp(1.75rem, 2vw+0.75rem, 2.5rem) · 28 → 40px

H4 · Montserrat 600

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

Скругления и spacing

Универсальное скругление 24px (radius-3xl). Все карточки, кнопки, модальные.

--radius-xs
0.375rem
--radius-sm
0.5rem
--radius-md
0.75rem
--radius-lg
1rem
--radius-xl
1.25rem
--radius-2xl
1.5rem
--radius-3xl
1.5rem
--radius-full
9999px

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

UI компоненты

components/ui/*.vue · auto-import без префикса (Breadcrumbs, LoadingButton…)

Breadcrumbs

components/ui/Breadcrumbs.vue

Preview

Usage

<Breadcrumbs :items="[
  { name: 'Главная', url: '/' },
  { name: 'Турниры', url: '/tournaments' },
  { name: 'Two Top 2026', url: '#' },
]" />

LoadingButton

components/ui/LoadingButton.vue

Preview

Usage

<LoadingButton :loading="saving" loading-text="Сохраняем…">
  Записаться
</LoadingButton>

RippleButton

components/ui/RippleButton.vue

Preview

Usage

<RippleButton @click="onSubmit">
  Записаться на тренировку
</RippleButton>

VballImage (главная обёртка)

components/ui/VballImage.vue

Preview

Beach volleyball player
Tournament action
Broken (fallback demo)

Usage

<VballImage
  :src="photoUrl"
  alt="Описание для скринридера"
  aspect-ratio="1/1"
  rounded="3xl"
  loading="lazy"
/>

ImageWithFade · ImageWithSkeleton

components/ui/ImageWithFade.vue

Preview

ImageWithFade

ImageWithSkeleton

Usage

<ImageWithFade     :src="url" aspect-ratio="1" />
<ImageWithSkeleton :src="url" aspect-ratio="16/9" rounded="2xl" />

SkeletonBox · SkeletonCard · SkeletonList

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" />

LoadingOverlay

components/ui/LoadingOverlay.vue

Preview

Usage

<LoadingOverlay
  :show="isSaving"
  message="Сохраняем…"
  variant="blur"   <!-- 'transparent' | 'blur' | 'solid' -->
  size="medium"
/>

Раздел 7

Shared компоненты

components/*.vue — отзывы, статус бэка, версия. Auto-import без префиксов.

ReviewCard

components/ReviewCard.vue

Preview

АП

Отличная школа, тренеры внимательные, прогресс пошёл со второго занятия. Очень рекомендую.

Опубликовано: 3 июня 2026 г.

Usage

<ReviewCard :review="review" />

/* ReviewWithUser shape:
   { id, rating, text, publishedAt,
     user: { displayName, description, photoURL } } */

ReviewsList (карусель)

components/ReviewsList.vue

Preview

Usage

<ReviewsList :reviews="reviews" :loading="loading" :limit="6" />

ReviewForm + ReviewModal

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"
/>

BackendStatus · VersionBadge

components/BackendStatus.vue · VersionBadge.vue

Preview

Виджеты прибиты к нижнему-правому углу экрана (position: fixed). BackendStatus уже виден на всех страницах через layouts/default.vue; VersionBadge — в admin layout.

  • BackendStatus → опрос /api/ping раз в 30s.
  • VersionBadge → читает /api/version.

Usage

<!-- в layouts/default.vue -->
<BackendStatus />
<VersionBadge />

UpdatePrompt

components/UpdatePrompt.vue

Preview

Компонент-плейсхолдер для prompt «Доступна новая версия». PWA пока не подключен (devops-agent), поэтому prompt никогда не появляется. Когда service worker будет включён — раскомментировать useRegisterSW() внутри компонента.

Usage

<!-- в layouts/default.vue -->
<UpdatePrompt />

Раздел 8

Nuxt UI 4 примитивы

UButton, UInput, USelect, UTooltip, UModal — используем их напрямую с дефолтами из app.config.ts.

UButton — варианты

@nuxt/ui

Preview

Usage

<UButton variant="solid" color="primary" size="md">
  Записаться
</UButton>

<!-- Дефолт color=primary, size=md заданы в app.config.ts -->

UInput · UTextarea · USelect · UCheckbox

@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="…" />

UTooltip (для disabled-состояний)

@nuxt/ui

Preview

Все disabled-кнопки обязательно оборачиваем в UTooltip с причиной — native title="" не работает на mobile и блокируется pointer-events:none.

Usage

<UTooltip text="Нужно сначала выбрать уровень">
  <UButton :disabled="!level">Записаться</UButton>
</UTooltip>

UModal

@nuxt/ui

Preview

Usage

<UButton @click="open = true">Open</UButton>
<UModal v-model:open="open" title="…">
  <template #body>…</template>
  <template #footer>…</template>
</UModal>

Раздел 9

Brand CSS-классы (legacy)

Глобальные классы из 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>

v'ball

Школа пляжного волейбола: тренировки, турниры и кэмпы для взрослых.

Контакты

  • +7 916 375 0517
  • info@vball.ru
  • Москва, ул. Примерная, 1

© 2024 v'ball. Все права защищены.