feat(shop): 9 premium animated backgrounds

Doubles the bg catalogue from 10 to 19 with richer multi-layer
animations. Every keyframe pack is CSS-only and respects the existing
prefers-reduced-motion fallback.

  sunset       550   slow hue cycle through warm palette
  rain         650   2-layer vertical streaks at different speeds
  snow         700   3-layer drifting flakes pattern
  clouds       750   drifting white blobs on day sky (only LIGHT one)
  fireflies    800   pulsing glowing dots, opposing drift
  cyber-grid   850   neon grid scrolling down with vignette
  kaleidoscope 1000  two huge conic-gradients in opposite rotation
  ocean        1100  layered blobs drift like undulating waves
  aurora-dance 1500  multi-band aurora — new premium top-tier

Tonal classification mirrored in api.js DARK_BG_SLUGS so the veil
picks the right contrast: clouds is light, the other 8 join the dark
set (alongside dark, stars, aurora, nebula, grid).

Each background also gains a matching .bg-preview.bg-<slug> rule that
reuses the same animation at the shop's 90px swatch — WYSIWYG.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-05-29 21:35:59 +03:00
parent d838c94df4
commit f6fbe922a9
3 changed files with 337 additions and 1 deletions
@@ -0,0 +1,59 @@
-- ═══════════════════════════════════════════════════════════════
-- 036: 9 premium animated backgrounds
--
-- Builds on the 10 bg seed from migration 035 with a wave of richer,
-- multi-layer animations: weather (rain, snow, clouds), atmospheric
-- (sunset, fireflies), and stylized (aurora-dance, ocean, kaleidoscope,
-- cyber-grid). All CSS-only with prefers-reduced-motion fallbacks.
--
-- Price ladder: 550 → 1500, slotting just above the existing 'aurora'
-- (900) and 'nebula' (1200) so the new top-tier 'aurora-dance' becomes
-- the most aspirational unlock at 1500 coins.
--
-- Re-runnable: each row gated by WHERE NOT EXISTS (name, type).
-- ═══════════════════════════════════════════════════════════════
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Закат', 'Медленно меняющая цвет вечерняя палитра', 'background', 'cosmetic', 550,
'{"slug":"sunset"}', 'sunset', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Закат' AND type='background');
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Дождь', 'Быстрые вертикальные капли на тёмном небе', 'background', 'cosmetic', 650,
'{"slug":"rain"}', 'cloud-rain', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Дождь' AND type='background');
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Снегопад', 'Медленно падающие снежинки в два слоя', 'background', 'cosmetic', 700,
'{"slug":"snow"}', 'snowflake', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Снегопад' AND type='background');
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Облака', 'Плывущие облака на дневном небе', 'background', 'cosmetic', 750,
'{"slug":"clouds"}', 'cloud', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Облака' AND type='background');
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Светлячки', 'Тёплые огоньки пульсируют и блуждают', 'background', 'cosmetic', 800,
'{"slug":"fireflies"}', 'lightbulb', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Светлячки' AND type='background');
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Кибер-сетка', 'Светящаяся неоновая сетка с разверткой', 'background', 'cosmetic', 850,
'{"slug":"cyber-grid"}', 'cpu', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Кибер-сетка' AND type='background');
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Калейдоскоп', 'Два встречных вращения цветовых вихрей', 'background', 'cosmetic', 1000,
'{"slug":"kaleidoscope"}', 'rotate-3d', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Калейдоскоп' AND type='background');
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Океан', 'Глубокие волны цвета у горизонта', 'background', 'cosmetic', 1100,
'{"slug":"ocean"}', 'waves', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Океан' AND type='background');
INSERT INTO shop_items (name, description, type, category, price, data, icon, is_active)
SELECT 'Танец сияния', 'Многослойное северное сияние с волнами и блюром', 'background', 'cosmetic', 1500,
'{"slug":"aurora-dance"}', 'wand-sparkles', 1
WHERE NOT EXISTS (SELECT 1 FROM shop_items WHERE name='Танец сияния' AND type='background');
+272
View File
@@ -1468,6 +1468,278 @@ body[data-bg-tone="dark"] .frame-unlock-hint {
animation: ls-bg-nebula-pan 30s ease-in-out infinite;
}
/* ══════════════════════════════════════════
PREMIUM ANIMATED BACKGROUNDS (migration 036)
══════════════════════════════════════════ */
/* ── 11. sunset (paid, animated) ────────────────────────────────
A warm three-stop gradient that slowly hue-rotates through a
sunset cycle — same palette, different ambient temperature. */
@keyframes ls-bg-sunset-cycle {
0%, 100% { filter: hue-rotate(0deg) saturate(1.05); }
50% { filter: hue-rotate(35deg) saturate(1.20); }
}
.bg-sunset,
.bg-preview.bg-sunset {
background: linear-gradient(180deg, #1a1240 0%, #ff6b6b 55%, #ffa07a 100%);
animation: ls-bg-sunset-cycle 60s linear infinite;
}
/* ── 12. rain (paid, animated) ──────────────────────────────────
Two layers of vertical streaks moving down at different rates
for parallax. The streaks are linear-gradients tiled at small
sizes so they read as drops. */
@keyframes ls-bg-rain-front {
from { background-position: 0 0; }
to { background-position: 0 80px; }
}
@keyframes ls-bg-rain-back {
from { background-position: 0 0; }
to { background-position: 0 120px; }
}
.bg-rain,
.bg-preview.bg-rain {
background-color: #0a1828;
background-image:
linear-gradient(180deg, transparent 0%, transparent 40%, rgba(255,255,255,0.55) 50%, rgba(255,255,255,0.20) 70%, transparent 80%),
linear-gradient(180deg, transparent 0%, transparent 30%, rgba(255,255,255,0.30) 45%, rgba(255,255,255,0.10) 60%, transparent 70%);
background-size: 3px 80px, 5px 120px;
background-position: 0 0, 25% 0;
background-repeat: repeat, repeat;
animation: ls-bg-rain-front 0.6s linear infinite, ls-bg-rain-back 1.1s linear infinite;
}
/* ── 13. snow (paid, animated) ──────────────────────────────────
Pattern of small white circles drifting downward; multi-size
layers create depth. */
@keyframes ls-bg-snow-fall {
from { background-position: 0 0, 0 0, 0 0; }
to { background-position: 0 200px, 0 300px, 0 250px; }
}
.bg-snow,
.bg-preview.bg-snow {
background-color: #0f1729;
background-image:
radial-gradient(circle 1.5px at 20% 30%, white 100%, transparent 100%),
radial-gradient(circle 2.5px at 60% 70%, white 100%, transparent 100%),
radial-gradient(circle 2px at 80% 20%, white 100%, transparent 100%);
background-size: 200px 200px, 300px 300px, 250px 250px;
animation: ls-bg-snow-fall 22s linear infinite;
}
/* ── 14. clouds (paid, animated, LIGHT) ─────────────────────────
Drifting white blobs on a sky gradient. Two layers cross the
screen at different speeds for parallax. */
@keyframes ls-bg-clouds-drift {
from { transform: translateX(-30%); }
to { transform: translateX(30%); }
}
.bg-clouds,
.bg-preview.bg-clouds {
background: linear-gradient(180deg, #87ceeb 0%, #e0f6ff 60%, #ffffff 100%);
position: relative;
overflow: hidden;
}
.bg-clouds::before,
.bg-clouds::after,
.bg-preview.bg-clouds::before,
.bg-preview.bg-clouds::after {
content: '';
position: absolute;
inset: -10%;
background-image:
radial-gradient(ellipse 18% 8% at 15% 25%, rgba(255,255,255,0.9), transparent 60%),
radial-gradient(ellipse 14% 7% at 45% 35%, rgba(255,255,255,0.8), transparent 60%),
radial-gradient(ellipse 22% 9% at 75% 22%, rgba(255,255,255,0.85), transparent 60%);
animation: ls-bg-clouds-drift 80s linear infinite alternate;
}
.bg-clouds::after,
.bg-preview.bg-clouds::after {
background-image:
radial-gradient(ellipse 16% 7% at 25% 60%, rgba(255,255,255,0.55), transparent 60%),
radial-gradient(ellipse 20% 8% at 65% 75%, rgba(255,255,255,0.5), transparent 60%);
animation-duration: 120s;
animation-delay: -20s;
opacity: 0.7;
}
/* ── 15. fireflies (paid, animated) ─────────────────────────────
Pulsing glowing dots that drift in opposing directions. Two
pseudo-element layers for variety; soft blur + opacity pulse. */
@keyframes ls-bg-firefly-drift {
0% { transform: translate(0, 0); }
25% { transform: translate(3%, -4%); }
50% { transform: translate(-2%, -2%); }
75% { transform: translate(2%, 3%); }
100% { transform: translate(0, 0); }
}
@keyframes ls-bg-firefly-pulse {
0%, 100% { opacity: 0.35; }
50% { opacity: 1; }
}
.bg-fireflies,
.bg-preview.bg-fireflies {
background: radial-gradient(ellipse at center, #1a2942 0%, #0a1828 70%, #050b14 100%);
position: relative;
overflow: hidden;
}
.bg-fireflies::before,
.bg-fireflies::after,
.bg-preview.bg-fireflies::before,
.bg-preview.bg-fireflies::after {
content: '';
position: absolute;
inset: 0;
background-image:
radial-gradient(circle 4px at 12% 22%, #ffd166 100%, transparent 100%),
radial-gradient(circle 3px at 38% 48%, #ffd166 100%, transparent 100%),
radial-gradient(circle 5px at 62% 18%, #ffd166 100%, transparent 100%),
radial-gradient(circle 3px at 88% 55%, #ffd166 100%, transparent 100%),
radial-gradient(circle 4px at 22% 78%, #ffd166 100%, transparent 100%),
radial-gradient(circle 3px at 68% 85%, #ffd166 100%, transparent 100%);
filter: blur(2px);
animation: ls-bg-firefly-drift 9s ease-in-out infinite, ls-bg-firefly-pulse 3.6s ease-in-out infinite;
}
.bg-fireflies::after,
.bg-preview.bg-fireflies::after {
background-image:
radial-gradient(circle 3px at 28% 38%, #ffe599 100%, transparent 100%),
radial-gradient(circle 4px at 52% 68%, #ffe599 100%, transparent 100%),
radial-gradient(circle 3px at 78% 30%, #ffe599 100%, transparent 100%);
animation-duration: 13s, 4.8s;
animation-delay: -2s, -1s;
opacity: 0.7;
}
/* ── 16. cyber-grid (paid, animated) ────────────────────────────
Neon-blue grid scrolling downward; perspective is faked via a
vertical gradient overlay that fades into darkness. */
@keyframes ls-bg-cyber-scan {
from { background-position: 0 0, 0 0, 0 0; }
to { background-position: 0 100%, 0 50px, 0 50px; }
}
.bg-cyber-grid,
.bg-preview.bg-cyber-grid {
background-color: #060a1f;
background-image:
linear-gradient(180deg, rgba(6,214,224,0.10) 0%, transparent 60%),
linear-gradient(0deg, rgba(6,214,224,0.30) 1px, transparent 1px),
linear-gradient(90deg, rgba(6,214,224,0.30) 1px, transparent 1px);
background-size: 100% 100%, 50px 50px, 50px 50px;
animation: ls-bg-cyber-scan 6s linear infinite;
}
/* ── 17. kaleidoscope (paid, animated) ──────────────────────────
Two heavily-blurred conic-gradients rotating in opposite
directions, blended via mix-blend-mode. */
@keyframes ls-bg-kal-cw { to { transform: rotate(360deg); } }
@keyframes ls-bg-kal-ccw { to { transform: rotate(-360deg); } }
.bg-kaleidoscope,
.bg-preview.bg-kaleidoscope {
background: #0a0a1f;
position: relative;
overflow: hidden;
}
.bg-kaleidoscope::before,
.bg-preview.bg-kaleidoscope::before {
content: '';
position: absolute;
inset: -50%;
background: conic-gradient(
from 0deg,
transparent 0%, rgba(155,93,229,0.45) 20%,
transparent 33%, rgba(6,214,224,0.45) 53%,
transparent 66%, rgba(255,107,53,0.45) 86%,
transparent 100%
);
filter: blur(40px);
animation: ls-bg-kal-cw 28s linear infinite;
}
.bg-kaleidoscope::after,
.bg-preview.bg-kaleidoscope::after {
content: '';
position: absolute;
inset: -50%;
background: conic-gradient(
from 90deg,
transparent 0%, rgba(6,214,160,0.35) 25%,
transparent 40%, rgba(255,209,102,0.35) 65%,
transparent 100%
);
filter: blur(55px);
mix-blend-mode: screen;
animation: ls-bg-kal-ccw 44s linear infinite;
}
/* ── 18. ocean (paid, animated) ─────────────────────────────────
Layered radial blobs slowly drift to fake undulating waves of
color near the bottom of the screen. */
@keyframes ls-bg-ocean-flow {
0% { background-position: 0% 100%, 0% 100%, 100% 100%; }
50% { background-position: 50% 60%, 50% 80%, 50% 90%; }
100% { background-position: 0% 100%, 0% 100%, 100% 100%; }
}
.bg-ocean,
.bg-preview.bg-ocean {
background-color: #0d2a4a;
background-image:
radial-gradient(ellipse 60% 40% at 50% 100%, rgba(6,214,224,0.55), transparent 60%),
radial-gradient(ellipse 50% 35% at 30% 100%, rgba(6,214,160,0.50), transparent 65%),
radial-gradient(ellipse 70% 35% at 70% 100%, rgba(155,93,229,0.40), transparent 65%);
background-size: 220% 220%, 260% 260%, 240% 240%;
background-repeat: no-repeat;
animation: ls-bg-ocean-flow 22s ease-in-out infinite;
}
/* ── 19. aurora-dance (premium top, animated) ───────────────────
Multi-layered aurora — two huge skewed bands waving in opposite
phases, plus a slow conic underlay for depth. */
@keyframes ls-bg-aurora-band-a {
0%, 100% { transform: translateX(-12%) skewY(-4deg); }
50% { transform: translateX( 12%) skewY( 4deg); }
}
@keyframes ls-bg-aurora-band-b {
0%, 100% { transform: translateX( 14%) skewY( 6deg); }
50% { transform: translateX(-14%) skewY(-6deg); }
}
.bg-aurora-dance,
.bg-preview.bg-aurora-dance {
background: radial-gradient(ellipse at top, #1a0a4a 0%, #050214 70%);
position: relative;
overflow: hidden;
}
.bg-aurora-dance::before,
.bg-preview.bg-aurora-dance::before {
content: '';
position: absolute;
inset: -20%;
background: linear-gradient(180deg,
transparent 0%,
rgba(6,214,224,0.55) 30%,
rgba(155,93,229,0.65) 50%,
rgba(6,214,160,0.55) 72%,
transparent 100%
);
filter: blur(34px);
animation: ls-bg-aurora-band-a 9s ease-in-out infinite;
}
.bg-aurora-dance::after,
.bg-preview.bg-aurora-dance::after {
content: '';
position: absolute;
inset: -20%;
background: linear-gradient(180deg,
transparent 0%,
rgba(255,107,53,0.40) 28%,
rgba(255,209,102,0.35) 48%,
rgba(155,93,229,0.50) 70%,
transparent 100%
);
filter: blur(40px);
mix-blend-mode: screen;
animation: ls-bg-aurora-band-b 13s ease-in-out infinite;
}
/* ── Reduced-motion: kill all bg animations, keep static palette ─ */
@media (prefers-reduced-motion: reduce) {
#ls-bg-fx,
+6 -1
View File
@@ -1084,7 +1084,12 @@ async function applyCosmetics() {
// ON TOP of it (#ls-bg-veil, z-index:-1 vs bg-fx -2) acting as a
// translucent veil so vibrant animations don't drown out the UI.
// The tone attr on <body> lets the veil darken for dark presets.
const DARK_BG_SLUGS = new Set(['dark', 'stars', 'aurora', 'nebula', 'grid']);
const DARK_BG_SLUGS = new Set([
'dark', 'stars', 'aurora', 'nebula', 'grid',
// Phase migration-036 additions — all dark except 'clouds'.
'sunset', 'rain', 'snow', 'fireflies', 'cyber-grid',
'kaleidoscope', 'ocean', 'aurora-dance',
]);
if (c.background && c.background.slug && c.background.slug !== 'none') {
const slug = String(c.background.slug).replace(/[^a-z0-9_-]/gi, '');
let bgEl = document.getElementById('ls-bg-fx');