From f6fbe922a9984de0a189aaaf45a502ae4a134969 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Fri, 29 May 2026 21:35:59 +0300 Subject: [PATCH] feat(shop): 9 premium animated backgrounds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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- rule that reuses the same animation at the shop's 90px swatch — WYSIWYG. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../db/migrations/036_backgrounds_premium.sql | 59 ++++ frontend/css/ls.css | 272 ++++++++++++++++++ js/api.js | 7 +- 3 files changed, 337 insertions(+), 1 deletion(-) create mode 100644 backend/src/db/migrations/036_backgrounds_premium.sql diff --git a/backend/src/db/migrations/036_backgrounds_premium.sql b/backend/src/db/migrations/036_backgrounds_premium.sql new file mode 100644 index 0000000..5a5777e --- /dev/null +++ b/backend/src/db/migrations/036_backgrounds_premium.sql @@ -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'); diff --git a/frontend/css/ls.css b/frontend/css/ls.css index d27cb0b..b11c651 100644 --- a/frontend/css/ls.css +++ b/frontend/css/ls.css @@ -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, diff --git a/js/api.js b/js/api.js index a4bf2e4..67ca924 100644 --- a/js/api.js +++ b/js/api.js @@ -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 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');