+
{ACCENT_PRESETS.map((preset, i) => (
))}
+ {/* Live background */}
Живой фон
{BG_PRESETS.map((preset) => {
diff --git a/apps/web/src/components/AudioBackground.tsx b/apps/web/src/components/AudioBackground.tsx
index d3ce089..cd1392e 100644
--- a/apps/web/src/components/AudioBackground.tsx
+++ b/apps/web/src/components/AudioBackground.tsx
@@ -4,7 +4,10 @@ import { useEffect, useRef } from 'react'
import { audioState } from '@/lib/audioState'
import { useBgStore } from '@/store/bgStore'
-type Pt = { x: number; y: number; vx: number; vy: number; r: number; a: number }
+type Pt = { x: number; y: number; vx: number; vy: number; r: number; a: number }
+type Ring = { r: number; alpha: number; speed: number }
+type Star = { x: number; y: number; r: number; ba: number; ph: number; sp: number }
+type Drop = { x: number; y: number; speed: number; len: number; alpha: number }
export default function AudioBackground() {
const canvasRef = useRef
(null)
@@ -20,13 +23,10 @@ export default function AudioBackground() {
let rafId: number
let smoothBass = 0
- let smoothMid = 0
+ let smoothMid = 0
let dataBuf: Uint8Array | null = null
- const resize = () => {
- canvas.width = window.innerWidth
- canvas.height = window.innerHeight
- }
+ const resize = () => { canvas.width = window.innerWidth; canvas.height = window.innerHeight }
resize()
window.addEventListener('resize', resize)
@@ -47,69 +47,66 @@ export default function AudioBackground() {
smoothMid += (rM - smoothMid) * 0.07
}
- const acRgb = () =>
- document.documentElement.style.getPropertyValue('--accent-rgb') || '200,255,0'
+ const ar = () => document.documentElement.style.getPropertyValue('--accent-rgb') || '200,255,0'
- // ── ORBS ─────────────────────────────────────────────────────────────────
+ // ── ORBS (vivid) ──────────────────────────────────────────────────────────
const drawOrbs = () => {
- const W = canvas.width, H = canvas.height
- const ar = acRgb()
+ const W = canvas.width, H = canvas.height, a = ar()
const t = Date.now() / 4500
- const b = Math.sin(t) * 0.03 + Math.cos(t * 0.7) * 0.015
- const diag = Math.hypot(W, H), base = diag * 0.55
+ const br = Math.sin(t) * 0.05 + Math.cos(t * 0.7) * 0.025
+ const diag = Math.hypot(W, H), base = diag * 0.68
ctx.clearRect(0, 0, W, H)
ctx.fillStyle = '#0a0a0f'
ctx.fillRect(0, 0, W, H)
- const r1 = base * (0.75 + smoothBass * 0.55 + b)
- const a1 = 0.055 + smoothBass * 0.07
- const g1 = ctx.createRadialGradient(W * 0.18, H * 0.08, 0, W * 0.18, H * 0.08, r1)
- g1.addColorStop(0, `rgba(${ar},${a1})`); g1.addColorStop(0.45, `rgba(${ar},${a1 * 0.28})`); g1.addColorStop(1, `rgba(${ar},0)`)
+ // Top-left — accent, bass-driven
+ const r1 = base * (0.82 + smoothBass * 0.7 + br)
+ const a1 = 0.14 + smoothBass * 0.18
+ const g1 = ctx.createRadialGradient(W * 0.10, H * 0.04, 0, W * 0.10, H * 0.04, r1)
+ g1.addColorStop(0, `rgba(${a},${a1})`); g1.addColorStop(0.38, `rgba(${a},${a1 * 0.4})`); g1.addColorStop(1, `rgba(${a},0)`)
ctx.fillStyle = g1; ctx.fillRect(0, 0, W, H)
- const r2 = base * (0.65 + smoothMid * 0.45 - b * 0.6)
- const a2 = 0.045 + smoothMid * 0.055
- const g2 = ctx.createRadialGradient(W * 0.82, H * 0.92, 0, W * 0.82, H * 0.92, r2)
- g2.addColorStop(0, `rgba(255,60,172,${a2})`); g2.addColorStop(0.45, `rgba(255,60,172,${a2 * 0.28})`); g2.addColorStop(1, 'rgba(255,60,172,0)')
+ // Bottom-right — pink, mid-driven
+ const r2 = base * (0.75 + smoothMid * 0.60 - br * 0.6)
+ const a2 = 0.13 + smoothMid * 0.16
+ const g2 = ctx.createRadialGradient(W * 0.90, H * 0.96, 0, W * 0.90, H * 0.96, r2)
+ g2.addColorStop(0, `rgba(255,60,172,${a2})`); g2.addColorStop(0.38, `rgba(255,60,172,${a2 * 0.4})`); g2.addColorStop(1, 'rgba(255,60,172,0)')
ctx.fillStyle = g2; ctx.fillRect(0, 0, W, H)
+ // Center — purple
const c = smoothBass * 0.55 + smoothMid * 0.45
- if (c > 0.005) {
- const r3 = base * (0.38 + c * 0.32)
- const g3 = ctx.createRadialGradient(W * 0.5, H * 0.52, 0, W * 0.5, H * 0.52, r3)
- g3.addColorStop(0, `rgba(140,100,255,${c * 0.035})`); g3.addColorStop(1, 'rgba(140,100,255,0)')
- ctx.fillStyle = g3; ctx.fillRect(0, 0, W, H)
- }
+ const r3 = base * (0.48 + c * 0.45)
+ const a3 = 0.06 + c * 0.10
+ const g3 = ctx.createRadialGradient(W * 0.5, H * 0.5, 0, W * 0.5, H * 0.5, r3)
+ g3.addColorStop(0, `rgba(140,100,255,${a3})`); g3.addColorStop(1, 'rgba(140,100,255,0)')
+ ctx.fillStyle = g3; ctx.fillRect(0, 0, W, H)
+
+ // Bottom-left — accent, extra depth
+ const r4 = base * (0.58 + smoothBass * 0.5 + br * 0.5)
+ const a4 = 0.08 + smoothBass * 0.10
+ const g4 = ctx.createRadialGradient(W * 0.04, H * 0.94, 0, W * 0.04, H * 0.94, r4)
+ g4.addColorStop(0, `rgba(${a},${a4})`); g4.addColorStop(0.5, `rgba(${a},${a4 * 0.2})`); g4.addColorStop(1, `rgba(${a},0)`)
+ ctx.fillStyle = g4; ctx.fillRect(0, 0, W, H)
}
// ── WAVES ─────────────────────────────────────────────────────────────────
const drawWaves = () => {
- const W = canvas.width, H = canvas.height
- const ar = acRgb()
+ const W = canvas.width, H = canvas.height, a = ar()
const t = Date.now() / 1000
-
- ctx.clearRect(0, 0, W, H)
- ctx.fillStyle = '#0a0a0f'
- ctx.fillRect(0, 0, W, H)
-
+ ctx.clearRect(0, 0, W, H); ctx.fillStyle = '#0a0a0f'; ctx.fillRect(0, 0, W, H)
const layers = [
- { y: 0.70, amp: 20 + smoothBass * 60, freq: 0.007, ph: t * 0.35, al: 0.12, c: `rgba(${ar},` },
+ { y: 0.70, amp: 20 + smoothBass * 60, freq: 0.007, ph: t * 0.35, al: 0.12, c: `rgba(${a},` },
{ y: 0.76, amp: 16 + smoothMid * 45, freq: 0.011, ph: -t * 0.5, al: 0.09, c: 'rgba(255,60,172,' },
- { y: 0.81, amp: 24 + smoothBass * 75, freq: 0.005, ph: t * 0.28, al: 0.14, c: `rgba(${ar},` },
+ { y: 0.81, amp: 24 + smoothBass * 75, freq: 0.005, ph: t * 0.28, al: 0.14, c: `rgba(${a},` },
{ y: 0.87, amp: 12 + smoothMid * 35, freq: 0.013, ph: -t * 0.62, al: 0.08, c: 'rgba(140,100,255,' },
- { y: 0.93, amp: 30 + smoothBass * 90, freq: 0.004, ph: t * 0.18, al: 0.18, c: `rgba(${ar},` },
+ { y: 0.93, amp: 30 + smoothBass * 90, freq: 0.004, ph: t * 0.18, al: 0.18, c: `rgba(${a},` },
]
-
for (const l of layers) {
const baseY = H * l.y
- ctx.beginPath()
- ctx.moveTo(-2, baseY)
- for (let x = 0; x <= W + 3; x += 3) {
- ctx.lineTo(x, baseY
- + Math.sin(x * l.freq + l.ph) * l.amp
- + Math.sin(x * l.freq * 2.4 + l.ph * 1.6) * l.amp * 0.25)
- }
+ ctx.beginPath(); ctx.moveTo(-2, baseY)
+ for (let x = 0; x <= W + 3; x += 3)
+ ctx.lineTo(x, baseY + Math.sin(x * l.freq + l.ph) * l.amp + Math.sin(x * l.freq * 2.4 + l.ph * 1.6) * l.amp * 0.25)
ctx.lineTo(W + 2, H + 2); ctx.lineTo(-2, H + 2); ctx.closePath()
const g = ctx.createLinearGradient(0, baseY - l.amp * 1.5, 0, H)
g.addColorStop(0, `${l.c}${l.al})`); g.addColorStop(1, `${l.c}0)`)
@@ -120,111 +117,210 @@ export default function AudioBackground() {
// ── PARTICLES ─────────────────────────────────────────────────────────────
const PTS: Pt[] = Array.from({ length: 55 }, () => ({
x: Math.random(), y: Math.random(),
- vx: (Math.random() - 0.5) * 0.00025,
- vy: (Math.random() - 0.5) * 0.00025,
- r: Math.random() * 1.5 + 0.6,
- a: Math.random() * 0.35 + 0.1,
+ vx: (Math.random() - 0.5) * 0.00025, vy: (Math.random() - 0.5) * 0.00025,
+ r: Math.random() * 1.5 + 0.6, a: Math.random() * 0.35 + 0.1,
}))
const drawParticles = () => {
- const W = canvas.width, H = canvas.height
- const ar = acRgb()
+ const W = canvas.width, H = canvas.height, a = ar()
+ ctx.fillStyle = 'rgba(10,10,15,0.2)'; ctx.fillRect(0, 0, W, H)
const spd = 1 + smoothBass * 3
-
- ctx.fillStyle = 'rgba(10,10,15,0.2)'
- ctx.fillRect(0, 0, W, H)
-
for (const p of PTS) {
p.x += p.vx * spd; p.y += p.vy * spd
if (p.x < 0) p.x += 1; if (p.x > 1) p.x -= 1
if (p.y < 0) p.y += 1; if (p.y > 1) p.y -= 1
if (smoothBass > 0.35 && Math.random() < 0.07) {
- p.vx += (Math.random() - 0.5) * 0.0006
- p.vy += (Math.random() - 0.5) * 0.0006
+ p.vx += (Math.random() - 0.5) * 0.0006; p.vy += (Math.random() - 0.5) * 0.0006
const mv = 0.0012
- p.vx = Math.max(-mv, Math.min(mv, p.vx))
- p.vy = Math.max(-mv, Math.min(mv, p.vy))
+ p.vx = Math.max(-mv, Math.min(mv, p.vx)); p.vy = Math.max(-mv, Math.min(mv, p.vy))
}
- ctx.beginPath()
- ctx.arc(p.x * W, p.y * H, p.r * (1 + smoothBass * 1.2), 0, Math.PI * 2)
- ctx.fillStyle = `rgba(${ar},${p.a * (0.7 + smoothMid * 0.3)})`
- ctx.fill()
+ ctx.beginPath(); ctx.arc(p.x * W, p.y * H, p.r * (1 + smoothBass * 1.2), 0, Math.PI * 2)
+ ctx.fillStyle = `rgba(${a},${p.a * (0.7 + smoothMid * 0.3)})`; ctx.fill()
}
-
const maxD = Math.min(W, H) * 0.12
ctx.lineWidth = 0.5
- for (let i = 0; i < PTS.length; i++) {
+ for (let i = 0; i < PTS.length; i++)
for (let j = i + 1; j < PTS.length; j++) {
- const dx = (PTS[i].x - PTS[j].x) * W
- const dy = (PTS[i].y - PTS[j].y) * H
+ const dx = (PTS[i].x - PTS[j].x) * W, dy = (PTS[i].y - PTS[j].y) * H
const d = Math.sqrt(dx * dx + dy * dy)
if (d < maxD) {
- ctx.beginPath()
- ctx.moveTo(PTS[i].x * W, PTS[i].y * H)
- ctx.lineTo(PTS[j].x * W, PTS[j].y * H)
- ctx.strokeStyle = `rgba(${ar},${(1 - d / maxD) * 0.07 * (1 + smoothMid)})`
- ctx.stroke()
+ ctx.beginPath(); ctx.moveTo(PTS[i].x * W, PTS[i].y * H); ctx.lineTo(PTS[j].x * W, PTS[j].y * H)
+ ctx.strokeStyle = `rgba(${a},${(1 - d / maxD) * 0.07 * (1 + smoothMid)})`; ctx.stroke()
}
}
- }
}
- // ── AURORA ────────────────────────────────────────────────────────────────
+ // ── AURORA (vivid) ────────────────────────────────────────────────────────
const drawAurora = () => {
- const W = canvas.width, H = canvas.height
- const ar = acRgb()
+ const W = canvas.width, H = canvas.height, a = ar()
const t = Date.now() / 3000
-
- ctx.clearRect(0, 0, W, H)
- ctx.fillStyle = '#0a0a0f'
- ctx.fillRect(0, 0, W, H)
+ ctx.clearRect(0, 0, W, H); ctx.fillStyle = '#0a0a0f'; ctx.fillRect(0, 0, W, H)
const bands = [
- { cx: 0.15, cy: 0.25, w: 0.55, h: 0.30, phase: t * 0.6, al: 0.08 + smoothBass * 0.06, c: ar },
- { cx: 0.50, cy: 0.15, w: 0.70, h: 0.22, phase: -t * 0.4, al: 0.06 + smoothMid * 0.05, c: '140,100,255' },
- { cx: 0.80, cy: 0.35, w: 0.50, h: 0.28, phase: t * 0.5, al: 0.07 + smoothBass * 0.04, c: '255,60,172' },
- { cx: 0.35, cy: 0.55, w: 0.60, h: 0.20, phase: -t * 0.7, al: 0.05 + smoothMid * 0.04, c: ar },
+ { cx: 0.08, cy: 0.15, w: 0.75, h: 0.30, ph: t * 0.8, al: 0.22 + smoothBass * 0.18, c: a },
+ { cx: 0.55, cy: 0.08, w: 0.90, h: 0.22, ph: -t * 0.6, al: 0.18 + smoothMid * 0.15, c: '140,100,255' },
+ { cx: 0.88, cy: 0.28, w: 0.65, h: 0.28, ph: t * 0.7, al: 0.20 + smoothBass * 0.14, c: '255,60,172' },
+ { cx: 0.28, cy: 0.48, w: 0.80, h: 0.20, ph: -t * 0.9, al: 0.14 + smoothMid * 0.12, c: a },
+ { cx: 0.72, cy: 0.62, w: 0.60, h: 0.24, ph: t * 0.55, al: 0.12 + smoothBass * 0.10, c: '140,100,255' },
+ { cx: 0.18, cy: 0.78, w: 0.70, h: 0.22, ph: -t * 0.7, al: 0.10 + smoothMid * 0.08, c: '255,60,172' },
]
for (const band of bands) {
const cx = band.cx * W
- const cy = (band.cy + Math.sin(band.phase) * 0.06) * H
+ const cy = (band.cy + Math.sin(band.ph) * 0.10) * H
const rw = band.w * W
- const rh = band.h * H * (1 + smoothBass * 0.3)
-
- ctx.save()
- ctx.translate(cx, cy)
- ctx.scale(1, rh / rw)
-
+ const rh = band.h * H * (1 + smoothBass * 0.45)
+ ctx.save(); ctx.translate(cx, cy); ctx.scale(1, rh / rw)
const g = ctx.createRadialGradient(0, 0, 0, 0, 0, rw)
- g.addColorStop(0, `rgba(${band.c},${band.al})`)
- g.addColorStop(0.35, `rgba(${band.c},${band.al * 0.5})`)
- g.addColorStop(1, `rgba(${band.c},0)`)
- ctx.fillStyle = g
- ctx.beginPath()
- ctx.arc(0, 0, rw, 0, Math.PI * 2)
- ctx.fill()
- ctx.restore()
+ g.addColorStop(0, `rgba(${band.c},${band.al})`); g.addColorStop(0.4, `rgba(${band.c},${band.al * 0.5})`); g.addColorStop(1, `rgba(${band.c},0)`)
+ ctx.fillStyle = g; ctx.beginPath(); ctx.arc(0, 0, rw, 0, Math.PI * 2); ctx.fill(); ctx.restore()
}
}
- // ── LOOP ──────────────────────────────────────────────────────────────────
- const drawFn = { orbs: drawOrbs, waves: drawWaves, particles: drawParticles, aurora: drawAurora }[bgMode] ?? drawOrbs
+ // ── PULSE ─────────────────────────────────────────────────────────────────
+ const RINGS: Ring[] = []
+ let prevBass = 0
+
+ const drawPulse = () => {
+ const W = canvas.width, H = canvas.height, a = ar()
+ const cx = W * 0.5, cy = H * 0.5
+ const maxR = Math.hypot(W, H) * 0.8
+
+ ctx.fillStyle = 'rgba(10,10,15,0.14)'; ctx.fillRect(0, 0, W, H)
+
+ if (smoothBass > prevBass + 0.07 && smoothBass > 0.18)
+ RINGS.push({ r: 12, alpha: 0.6 + smoothBass * 0.35, speed: 3 + smoothBass * 8 })
+ prevBass = smoothBass
+
+ if (Math.random() < 0.014)
+ RINGS.push({ r: 5, alpha: 0.16, speed: 1.4 })
+
+ for (let i = RINGS.length - 1; i >= 0; i--) {
+ const ring = RINGS[i]
+ ring.r += ring.speed; ring.alpha -= 0.006
+ if (ring.alpha <= 0 || ring.r > maxR) { RINGS.splice(i, 1); continue }
+ ctx.beginPath(); ctx.arc(cx, cy, ring.r, 0, Math.PI * 2)
+ ctx.strokeStyle = `rgba(${a},${ring.alpha})`
+ ctx.lineWidth = 1.5 + ring.alpha * 3; ctx.stroke()
+ if (ring.r > 30 && ring.alpha > 0.12) {
+ ctx.beginPath(); ctx.arc(cx, cy, ring.r * 0.82, 0, Math.PI * 2)
+ ctx.strokeStyle = `rgba(255,60,172,${ring.alpha * 0.45})`
+ ctx.lineWidth = 0.8; ctx.stroke()
+ }
+ }
+
+ const g = ctx.createRadialGradient(cx, cy, 0, cx, cy, 90 + smoothBass * 130)
+ g.addColorStop(0, `rgba(${a},${0.06 + smoothBass * 0.12})`); g.addColorStop(1, `rgba(${a},0)`)
+ ctx.fillStyle = g; ctx.fillRect(0, 0, W, H)
+ }
+
+ // ── STARS ─────────────────────────────────────────────────────────────────
+ const STARS: Star[] = Array.from({ length: 180 }, () => ({
+ x: Math.random(), y: Math.random(),
+ r: Math.random() * 1.4 + 0.3,
+ ba: Math.random() * 0.55 + 0.15,
+ ph: Math.random() * Math.PI * 2,
+ sp: Math.random() * 1.5 + 0.4,
+ }))
+
+ const drawStars = () => {
+ const W = canvas.width, H = canvas.height, a = ar()
+ const t = Date.now() / 1000
+ ctx.clearRect(0, 0, W, H); ctx.fillStyle = '#0a0a0f'; ctx.fillRect(0, 0, W, H)
+
+ const n1 = ctx.createRadialGradient(W * 0.30, H * 0.35, 0, W * 0.30, H * 0.35, W * 0.55)
+ n1.addColorStop(0, `rgba(${a},${0.05 + smoothMid * 0.05})`); n1.addColorStop(1, `rgba(${a},0)`)
+ ctx.fillStyle = n1; ctx.fillRect(0, 0, W, H)
+ const n2 = ctx.createRadialGradient(W * 0.72, H * 0.62, 0, W * 0.72, H * 0.62, W * 0.45)
+ n2.addColorStop(0, `rgba(140,100,255,${0.04 + smoothBass * 0.05})`); n2.addColorStop(1, 'rgba(140,100,255,0)')
+ ctx.fillStyle = n2; ctx.fillRect(0, 0, W, H)
+
+ for (const s of STARS) {
+ const tw = (Math.sin(t * s.sp + s.ph) + 1) * 0.5
+ const alpha = s.ba * (0.35 + tw * 0.65) * (1 + smoothMid * 0.45)
+ const r = s.r * (1 + smoothBass * tw * 1.0)
+ if (s.r > 1.1) {
+ ctx.beginPath(); ctx.arc(s.x * W, s.y * H, r * 2.8, 0, Math.PI * 2)
+ ctx.fillStyle = `rgba(${a},${alpha * 0.18})`; ctx.fill()
+ }
+ ctx.beginPath(); ctx.arc(s.x * W, s.y * H, r, 0, Math.PI * 2)
+ ctx.fillStyle = `rgba(${a},${alpha})`; ctx.fill()
+ }
+ }
+
+ // ── RAIN ──────────────────────────────────────────────────────────────────
+ const DROPS: Drop[] = Array.from({ length: 65 }, () => ({
+ x: Math.random(), y: Math.random(),
+ speed: Math.random() * 0.003 + 0.0015,
+ len: Math.random() * 0.07 + 0.04,
+ alpha: Math.random() * 0.5 + 0.2,
+ }))
+
+ const drawRain = () => {
+ const W = canvas.width, H = canvas.height, a = ar()
+ ctx.fillStyle = 'rgba(10,10,15,0.1)'; ctx.fillRect(0, 0, W, H)
+ const spd = 1 + smoothBass * 4.5
+ for (const d of DROPS) {
+ d.y += d.speed * spd
+ if (d.y > 1.1) { d.y = -d.len; d.x = Math.random() }
+ const x = d.x * W, y = d.y * H
+ const len = d.len * H * (1 + smoothBass * 0.6)
+ const g = ctx.createLinearGradient(x, y - len, x, y)
+ g.addColorStop(0, `rgba(${a},0)`); g.addColorStop(0.6, `rgba(${a},${d.alpha * 0.5})`); g.addColorStop(1, `rgba(${a},${d.alpha})`)
+ ctx.fillStyle = g; ctx.fillRect(x - 1, y - len, 2, len)
+ ctx.beginPath(); ctx.arc(x, y, 2, 0, Math.PI * 2)
+ ctx.fillStyle = `rgba(${a},${d.alpha})`; ctx.fill()
+ }
+ }
+
+ // ── RAYS ──────────────────────────────────────────────────────────────────
+ const NUM_RAYS = 9
+ const RAY_OFF = Array.from({ length: NUM_RAYS }, (_, i) => (i / NUM_RAYS) * Math.PI * 2)
+
+ const drawRays = () => {
+ const W = canvas.width, H = canvas.height, a = ar()
+ const t = Date.now() / 10000
+ const cx = W * 0.5, cy = H * 0.58
+ const maxR = Math.hypot(W, H)
+
+ ctx.clearRect(0, 0, W, H); ctx.fillStyle = '#0a0a0f'; ctx.fillRect(0, 0, W, H)
+
+ for (let i = 0; i < NUM_RAYS; i++) {
+ const angle = RAY_OFF[i] + t
+ const hw = 0.065 + smoothBass * 0.055
+ const al = (i % 2 === 0 ? 0.08 : 0.05) + smoothBass * 0.09 + smoothMid * 0.02
+ ctx.beginPath(); ctx.moveTo(cx, cy); ctx.arc(cx, cy, maxR, angle - hw, angle + hw); ctx.closePath()
+ const g = ctx.createRadialGradient(cx, cy, 0, cx, cy, maxR * 0.65)
+ g.addColorStop(0, `rgba(${a},${al * 2.8})`); g.addColorStop(0.25, `rgba(${a},${al})`); g.addColorStop(1, `rgba(${a},0)`)
+ ctx.fillStyle = g; ctx.fill()
+ }
+ for (let i = 0; i < 4; i++) {
+ const angle = RAY_OFF[i * 2] + t * 1.4 + Math.PI * 0.28
+ const hw = 0.04 + smoothMid * 0.035
+ const al = 0.04 + smoothMid * 0.05
+ ctx.beginPath(); ctx.moveTo(cx, cy); ctx.arc(cx, cy, maxR, angle - hw, angle + hw); ctx.closePath()
+ const g = ctx.createRadialGradient(cx, cy, 0, cx, cy, maxR * 0.5)
+ g.addColorStop(0, `rgba(255,60,172,${al * 2.5})`); g.addColorStop(1, 'rgba(255,60,172,0)')
+ ctx.fillStyle = g; ctx.fill()
+ }
+ const cg = ctx.createRadialGradient(cx, cy, 0, cx, cy, 120 + smoothBass * 160)
+ cg.addColorStop(0, `rgba(${a},${0.12 + smoothBass * 0.18})`); cg.addColorStop(1, `rgba(${a},0)`)
+ ctx.fillStyle = cg; ctx.fillRect(0, 0, W, H)
+ }
+
+ // ── LOOP ──────────────────────────────────────────────────────────────────
+ const drawFn = ({
+ orbs: drawOrbs, waves: drawWaves, particles: drawParticles,
+ aurora: drawAurora, pulse: drawPulse, stars: drawStars,
+ rain: drawRain, rays: drawRays,
+ } as Record void>)[bgMode] ?? drawOrbs
- // Clear once to avoid bleed from previous mode
ctx.clearRect(0, 0, canvas.width, canvas.height)
- const loop = () => {
- tick()
- drawFn()
- rafId = requestAnimationFrame(loop)
- }
+ const loop = () => { tick(); drawFn(); rafId = requestAnimationFrame(loop) }
loop()
- return () => {
- cancelAnimationFrame(rafId)
- window.removeEventListener('resize', resize)
- }
+ return () => { cancelAnimationFrame(rafId); window.removeEventListener('resize', resize) }
}, [bgMode])
if (bgMode === 'none') return null
diff --git a/apps/web/src/store/bgStore.ts b/apps/web/src/store/bgStore.ts
index 0e03a8e..f304fa7 100644
--- a/apps/web/src/store/bgStore.ts
+++ b/apps/web/src/store/bgStore.ts
@@ -2,7 +2,7 @@
import { create } from 'zustand'
-export type BgMode = 'orbs' | 'waves' | 'particles' | 'aurora' | 'none'
+export type BgMode = 'orbs' | 'waves' | 'particles' | 'aurora' | 'pulse' | 'stars' | 'rain' | 'rays' | 'none'
export interface BgPreset {
id: BgMode
@@ -11,10 +11,14 @@ export interface BgPreset {
}
export const BG_PRESETS: BgPreset[] = [
- { id: 'orbs', name: 'Орбы', desc: 'Мягкие цветовые пятна' },
- { id: 'waves', name: 'Волны', desc: 'Звуковые волны снизу' },
- { id: 'particles', name: 'Частицы', desc: 'Плавающая сеть точек' },
+ { id: 'orbs', name: 'Орбы', desc: 'Цветовые пятна' },
+ { id: 'waves', name: 'Волны', desc: 'Звуковые волны' },
+ { id: 'particles', name: 'Частицы', desc: 'Сеть точек' },
{ id: 'aurora', name: 'Аврора', desc: 'Северное сияние' },
+ { id: 'pulse', name: 'Пульс', desc: 'Расходящиеся кольца' },
+ { id: 'stars', name: 'Звёзды', desc: 'Звёздное небо' },
+ { id: 'rain', name: 'Дождь', desc: 'Световые капли' },
+ { id: 'rays', name: 'Лучи', desc: 'Лучи из центра' },
{ id: 'none', name: 'Нет', desc: 'Чистый фон' },
]