Skip to main content

Animation System

Централизованная система анимаций через Tailwind Config + Global CSS.

Цель: Единая система, без дубликатов, быстрые transitions для native app feeling.


Quick Reference

Use CaseTailwind ClassDurationNotes
Modal overlayanimate-fade-in150msСтандарт для центральных модалов
Modal overlay (медленнее)animate-fade-in-normal300msДля тяжёлого контента
Modal closeanimate-fade-out150msИсчезновение
Card appearanimate-slide-up150msЛёгкая анимация (10px translateY)
Button scaleanimate-scale-in150msFast feedback
Fade + scale comboanimate-scale-fade150msКомбинированный эффект
Attention grabberanimate-pulse-slow3s infiniteДля important elements
Loading breathinganimate-breathe2.5s infiniteМягкое "дыхание" для loading screens
Bottom sheet.animate-slide-up300msGlobal CSS (100% translateY)
Shimmer effect.skeleton-shimmer (CSS class)2s infiniteSkeleton loaders (gradient sweep)
Gradient shiftanimate-gradient-shift3s infiniteAnimated gradients

Speed Variants

Fast (default): 150ms

Для UI feedback и быстрых transitions:

  • animate-fade-in (150ms)
  • animate-slide-up (150ms)
  • animate-scale-in (150ms)
  • animate-fade-out (150ms)
  • animate-scale-fade (150ms)

Когда использовать: Модалы, overlays, tooltips, quick feedback

Normal: 300ms

Для content transitions и тяжёлых анимаций:

  • animate-fade-in-normal (300ms)
  • animate-slide-up-normal (300ms)

Когда использовать: Bottom sheets, сложные компоненты, когда нужна плавность

Long: 2.5s+

Для attention effects и loading states:

  • animate-breathe (2.5s infinite) — мягкое "дыхание" для loading screens
  • animate-pulse-slow (3s infinite)
  • animate-shimmer (3s infinite)
  • animate-gradient-shift (3s infinite)

Когда использовать: Loading screens, skeleton loaders, promotional banners


Animation Categories

🎭 Opacity

ClassKeyframeEffect
animate-fade-infadeIn0% opacity → 100% opacity (150ms)
animate-fade-in-normalfadeIn0% opacity → 100% opacity (300ms)
animate-fade-outfadeOut100% opacity → 0% opacity (150ms)
Breaking Change

До рефакторинга: fadeIn включал scale(0.95 → 1) transform

После рефакторинга: fadeIn — только opacity (БЕЗ scale)

Если нужен scale: используй animate-scale-fade

🚀 Transform

ClassKeyframeEffect
animate-slide-upslideUptranslateY(10px) + opacity (150ms)
animate-slide-up-normalslideUptranslateY(10px) + opacity (300ms)
animate-scale-inscaleInscale(0.9 → 1) + opacity (150ms)

🔥 Combined

ClassKeyframeEffect
animate-scale-fadescaleFadescale(0.95 → 1) + fade in (150ms)

Когда использовать: Для "выпрыгивающих" элементов (modals, cards)

🫁 Breathing

ClassKeyframeEffect
animate-breathebreathescale(1 → 1.05 → 1) + opacity(1 → 0.85 → 1) (2.5s infinite)

Когда использовать: Loading screens с иконками, мягкие привлекающие внимание эффекты

Пример использования:

{/* BrandedLoadingScreen - иконка с breathing эффектом */}
<img
src="/images/items/scrap.webp"
className="w-24 h-24 object-contain animate-breathe"
/>

Global CSS vs Tailwind

Важно: Два разных animate-slide-up

В системе есть ДВА класса с одинаковым именем, но разными источниками и эффектами!

Tailwind: animate-slide-up

Источник: frontend/tailwind.config.js

Keyframe: slideUp

Эффект:

0% { transform: translateY(10px); opacity: 0; }
100% { transform: translateY(0); opacity: 1; }

Duration: 150ms

Use Case: Лёгкая анимация для cards, small components

Пример:

<div className="animate-slide-up">Card content</div>

Global CSS: .animate-slide-up

Источник: frontend/src/index.css:431-444

Keyframe: slide-up

Эффект:

@keyframes slide-up {
from { transform: translateY(100%); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.animate-slide-up {
animation: slide-up 0.3s ease-out;
}

Duration: 300ms

Use Case: Bottom sheet modals (heavy animations)

Пример:

{/* Global CSS класс используется для bottom sheets */}
<div className="... animate-slide-up">
Bottom sheet content (слайдится снизу на 100% высоты)
</div>

Компоненты использующие Global CSS:

  • CasePreviewModal.tsx
  • StreakModal.tsx
  • RaffleModal.tsx
  • ReferralModal.tsx
  • SeasonModal.tsx
  • SalvageModal.tsx, BuffActivateModal.tsx, etc.

DO NOT USE ❌

Эти паттерны ломают переключение тем и создают дубликаты:

Плохой паттернПроблемаЗамени на
Inline @keyframesДублирование кодаTailwind classes
style={{ animation: '...' }}Не централизованоTailwind classes
Hardcoded durationsInconsistent timingСтандартные варианты
@keyframes в компонентахДубликатыTailwind config или Global CSS

Примеры:

// ❌ НЕПРАВИЛЬНО: inline styles
<div style={{ animation: 'fade-in 0.3s ease-out' }}>

// ❌ НЕПРАВИЛЬНО: inline @keyframes
<style>{`
@keyframes fade-in { ... }
.animate-fade-in { animation: fade-in 0.3s ease-out; }
`}</style>

// ✅ ПРАВИЛЬНО: Tailwind class
<div className="animate-fade-in">

Architecture

Слои системы

┌─────────────────────────────────────────────────┐
│ Components (используй в JSX) │
│ className="animate-fade-in" │
└─────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────┐
│ Tailwind Config (animation definitions) │
│ 'fade-in': 'fadeIn 150ms ease-out' │
└─────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────┐
│ Keyframes (CSS animations) │
│ @keyframes fadeIn { 0% {...} 100% {...} } │
└─────────────────────────────────────────────────┘

Файлы системы

ФайлНазначение
frontend/tailwind.config.js:96-143Tailwind animations + keyframes
frontend/src/index.css:431-444Global CSS для bottom sheets

Adding New Animations

1. Добавь в Tailwind Config

Файл: frontend/tailwind.config.js

animation: {
'my-animation': 'myKeyframe 200ms ease-out',
},
keyframes: {
myKeyframe: {
'0%': { /* start state */ },
'100%': { /* end state */ },
},
}

2. Используй в компоненте

<div className="animate-my-animation">
Content
</div>

3. Избегай дубликатов

Перед добавлением:

  1. Проверь существующие animations в config
  2. Проверь Global CSS (index.css)
  3. Можно ли использовать существующий вариант?

Performance

Что анимировать

Можно:

  • opacity
  • transform (translate, scale, rotate)

Избегай:

  • width, height (triggers layout)
  • left, top (triggers layout)
  • margin, padding (triggers layout)

GPU Acceleration

Tailwind animations автоматически используют GPU acceleration через transform и opacity.

Не нужно добавлять вручную:

/* ❌ НЕ НУЖНО */
.my-element {
will-change: transform;
transform: translateZ(0);
}

Accessibility

Prefers Reduced Motion

TODO

Добавить поддержку prefers-reduced-motion в frontend/src/index.css:

@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}

Когда реализовано: Пользователи с motion sickness получат мгновенные transitions.


Migration from Inline Animations

До рефакторинга

// ResultModal.tsx (старая версия)
<div className="... animate-fade-in">
<style>{`
@keyframes fade-in {
0% { opacity: 0; transform: scale(0.95); }
100% { opacity: 1; transform: scale(1); }
}
.animate-fade-in {
animation: fade-in 0.3s ease-out;
}
`}</style>
</div>

После рефакторинга

// ResultModal.tsx (новая версия)
<div className="... animate-fade-in">
{/* Inline styles удалены - используется Tailwind */}
</div>

Результат:

  • 60+ строк дублированного кода удалено
  • Все модалы используют единую систему
  • Timing консистентный (150ms для всех fade-in)

Breaking Changes

Duration Changes:

AnimationБылоСталоИзменение
animate-fade-in500ms150ms3x быстрее
animate-slide-up300ms150ms2x быстрее
animate-scale-in200ms150msНемного быстрее

🎯 Behavior Changes:

AnimationБылоСтало
fadeIn keyframeopacity + scale(0.95→1)только opacity

Если нужен старый behavior:

  • Медленнее: используй -normal варианты (animate-fade-in-normal → 300ms)
  • С scale: используй animate-scale-fade (scale 0.95→1 + fade)

Examples

// Центральный modal (быстро)
<div className="fixed inset-0 bg-black/80 animate-fade-in">
<div className="bg-white rounded-lg">
Modal content
</div>
</div>

// Bottom sheet (медленнее, полный slide)
<div className="fixed inset-0 bg-black/80 animate-fade-in">
<div className="w-full bg-white rounded-t-3xl animate-slide-up">
Sheet content (300ms, 100% translateY from Global CSS)
</div>
</div>

Card List

// Карточки появляются с лёгким slide-up
{items.map((item, i) => (
<div
key={item.id}
className="animate-slide-up"
style={{ animationDelay: `${i * 50}ms` }}
>
{item.name}
</div>
))}

Button Feedback

<button className="hover:animate-scale-in active:scale-95 transition-all">
Click me
</button>

Loading Tips System

Система подсказок на экране загрузки с fade-анимацией и ротацией.

Расположение файлов

ФайлНазначение
frontend/src/constants/loading-tips.tsКонтент подсказок — редактировать здесь
frontend/src/components/BrandedLoadingScreen.tsxКомпонент LoadingTip
frontend/src/styles/loading-animations.cssCSS для tip-fade-in/out

Категории и веса

КатегорияВесОписание
app50%Советы по приложению goLoot
funny30%Смешные/ироничные про Rust
rust_game20%Полезные советы по игре Rust

Конфигурация

// frontend/src/constants/loading-tips.ts

/** Интервал смены подсказки (мс) */
export const TIP_ROTATION_INTERVAL = 5000; // 5 секунд

/** Длительность fade анимации (мс) */
export const TIP_FADE_DURATION = 300;

Как добавить подсказку

Файл: frontend/src/constants/loading-tips.ts

export const LOADING_TIPS: LoadingTip[] = [
// APP — советы по goLoot
{ text: 'Твоя новая подсказка про приложение', category: 'app' },

// FUNNY — смешные про Rust
{ text: 'Шутка про naked с факелом', category: 'funny' },

// RUST_GAME — советы по игре
{ text: 'Всегда делай airlock', category: 'rust_game' },
];

Как изменить веса категорий

// frontend/src/constants/loading-tips.ts

export const CATEGORY_WEIGHTS: Record<TipCategory, number> = {
app: 50, // 50% — советы по приложению
funny: 30, // 30% — шутки
rust_game: 20, // 20% — советы по игре
};

Текущее количество

КатегорияКоличество
app~25 подсказок
funny~15 подсказок
rust_game~10 подсказок
Всего~50 подсказок

CSS Анимации

/* frontend/src/styles/loading-animations.css */

.tip-fade-in {
animation: tipFadeIn 0.3s ease-out forwards;
}

.tip-fade-out {
animation: tipFadeOut 0.3s ease-in forwards;
}

Skeleton Shimmer System

Skeleton компоненты используют современный shimmer sweep эффект (градиент слева направо) вместо animate-pulse.

CSS Class: .skeleton-shimmer

Источник: frontend/src/index.css

.skeleton-shimmer {
background: linear-gradient(
90deg,
rgba(var(--skeleton-base), 1) 0%,
rgba(var(--skeleton-shimmer), 0.6) 50%,
rgba(var(--skeleton-base), 1) 100%
);
background-size: 200% 100%;
animation: shimmer 2s ease-in-out infinite;
}

Визуальный эффект: Серый фон с плавным sweep-градиентом слева направо, аналогичный Instagram/YouTube.

Skeleton Primitives

КомпонентФормаКласс
SkeletonCardПрямоугольникskeleton-shimmer rounded-lg
SkeletonTextПолоска текстаskeleton-shimmer rounded
SkeletonAvatarКругskeleton-shimmer rounded-full
SkeletonButtonКнопкаskeleton-shimmer rounded

Composed Skeletons

КомпонентИмитирует
HomeSkeletonBalanceSection + QuestGrid (3 карточки)
ProfileSkeletonПрофиль пользователя
InventorySkeleton3×3 сетка карточек предметов
CasesSkeletonКарусель кейсов
AchievementsSkeleton2 карточки достижений + footer

Theme Support

Цвета skeleton адаптируются к теме через CSS variables (--skeleton-base, --skeleton-shimmer).

См. Theme Guide — Color Palette для конкретных значений.

Когда виден

Skeleton отображается только при падении bootstrap — если bootstrap успешен, данные берутся из кеша и skeleton не появляется.