Files
party-mix-app/apps/web/src/lib/overlayPalettes.ts
Kirko 428548a620 feat: nginx reverse proxy, Spotify import, overlay system, UI overhaul
- Add nginx as single entry point: /api/* → backend, /* → web
- NEXT_PUBLIC_API_URL="" so all API calls are relative (go through nginx)
- Add Spotify playlist import (Client Credentials OAuth, up to 500 tracks)
- Add Yandex/Spotify tabbed import UI on /playlists
- Add stream overlay system (SSE + polling fallback, 9 styles)
- Reorganize pages into (main) route group
- Add QueuePanel, VersionsPanel, Toaster components
- Add overlay settings tab in /settings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 00:45:53 +03:00

369 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { OverlayStyle } from '@/store/overlayStore'
export interface OverlayPalette {
id: string
name: string
swatches: string[] // preview dots (2-3 hex/rgba)
bg?: string // panel background (color or gradient)
border?: string // full CSS border declaration
shadow?: string // box-shadow
text?: string // primary text color
text2?: string // secondary text color
chroma?: string // characteristic color: retro border hex, neon/matrix phosphor hex, glam metal rgb-string
chroma2?: string // for RGB string "r,g,b" when chroma is hex (used for --accent-rgb override)
titleBg?: string // Y2K titlebar gradient
bodyBg?: string // Y2K body gradient
}
const PALETTES: Record<OverlayStyle, OverlayPalette[]> = {
classic: [
{
id: 'default', name: 'Тёмный', swatches: ['#0a0a10', '#ffffff', '#888888'],
bg: 'rgba(10,10,16,0.82)', border: '1px solid rgba(255,255,255,0.09)',
shadow: '0 8px 32px rgba(0,0,0,0.55)',
text: '#ffffff', text2: 'rgba(255,255,255,0.45)', chroma: 'rgba(255,255,255,0.06)',
},
{
id: 'warm', name: 'Тёплый', swatches: ['#160c06', '#ffeedd', '#dca850'],
bg: 'rgba(22,12,6,0.86)', border: '1px solid rgba(220,168,80,0.20)',
shadow: '0 8px 32px rgba(20,8,0,0.6)',
text: '#ffeedd', text2: 'rgba(255,200,120,0.50)', chroma: 'rgba(220,168,80,0.10)',
},
{
id: 'ocean', name: 'Морской', swatches: ['#040a1a', '#ddeeff', '#3c82ff'],
bg: 'rgba(4,10,26,0.88)', border: '1px solid rgba(60,130,255,0.22)',
shadow: '0 8px 32px rgba(0,10,40,0.6)',
text: '#ddeeff', text2: 'rgba(100,170,255,0.50)', chroma: 'rgba(60,130,255,0.08)',
},
{
id: 'frost', name: 'Белый', swatches: ['#ffffff', '#111111', '#bbbbbb'],
bg: 'rgba(255,255,255,0.90)', border: '1px solid rgba(0,0,0,0.09)',
shadow: '0 8px 32px rgba(0,0,0,0.15)',
text: '#111111', text2: 'rgba(0,0,0,0.42)', chroma: 'rgba(0,0,0,0.05)',
},
],
aero: [
{
id: 'default', name: 'Небо', swatches: ['#bee6ff', '#78b9ff', '#003a6e'],
bg: 'linear-gradient(160deg,rgba(190,230,255,0.55) 0%,rgba(120,185,255,0.42) 100%)',
border: '1.5px solid rgba(255,255,255,0.72)',
shadow: '0 4px 20px rgba(80,160,255,0.30),inset 0 1.5px 0 rgba(255,255,255,0.65)',
text: '#003a6e', text2: 'rgba(0,60,130,0.60)',
},
{
id: 'rose', name: 'Роза', swatches: ['#ffc8d7', '#ff87af', '#6e0030'],
bg: 'linear-gradient(160deg,rgba(255,200,215,0.55) 0%,rgba(255,135,175,0.42) 100%)',
border: '1.5px solid rgba(255,255,255,0.72)',
shadow: '0 4px 20px rgba(255,100,160,0.30),inset 0 1.5px 0 rgba(255,255,255,0.65)',
text: '#6e0030', text2: 'rgba(130,0,60,0.60)',
},
{
id: 'mint', name: 'Мята', swatches: ['#b4ffd2', '#50dc9b', '#003a20'],
bg: 'linear-gradient(160deg,rgba(180,255,210,0.55) 0%,rgba(80,220,155,0.42) 100%)',
border: '1.5px solid rgba(255,255,255,0.72)',
shadow: '0 4px 20px rgba(60,200,130,0.30),inset 0 1.5px 0 rgba(255,255,255,0.65)',
text: '#003a20', text2: 'rgba(0,80,50,0.60)',
},
{
id: 'lavender', name: 'Лаванда', swatches: ['#dcc8ff', '#9b7dff', '#3a006e'],
bg: 'linear-gradient(160deg,rgba(220,200,255,0.55) 0%,rgba(155,125,255,0.42) 100%)',
border: '1.5px solid rgba(255,255,255,0.72)',
shadow: '0 4px 20px rgba(130,80,255,0.30),inset 0 1.5px 0 rgba(255,255,255,0.65)',
text: '#3a006e', text2: 'rgba(70,0,140,0.55)',
},
],
retro: [
{
id: 'default', name: 'Янтарь', swatches: ['#0c0500', '#c07030', '#f8d090'],
bg: 'rgba(12,5,0,0.94)', border: '2px solid #c07030',
shadow: '0 0 28px rgba(180,90,20,0.45),4px 4px 0 rgba(0,0,0,0.6)',
text: '#f8d090', text2: '#9a6020', chroma: 'rgba(180,100,20,0.30)',
},
{
id: 'phosphor', name: 'Фосфор', swatches: ['#000c04', '#30c060', '#90f8b0'],
bg: 'rgba(0,12,4,0.94)', border: '2px solid #30c060',
shadow: '0 0 28px rgba(20,160,60,0.45),4px 4px 0 rgba(0,0,0,0.6)',
text: '#90f8b0', text2: '#207840', chroma: 'rgba(20,160,60,0.28)',
},
{
id: 'crt', name: 'CRT синий', swatches: ['#000412', '#3060c0', '#90b8f8'],
bg: 'rgba(0,4,18,0.94)', border: '2px solid #3060c0',
shadow: '0 0 28px rgba(30,80,200,0.45),4px 4px 0 rgba(0,0,0,0.6)',
text: '#90b8f8', text2: '#204880', chroma: 'rgba(30,80,200,0.30)',
},
{
id: 'blood', name: 'Красный', swatches: ['#0e0202', '#c02828', '#f89090'],
bg: 'rgba(14,2,2,0.94)', border: '2px solid #c02828',
shadow: '0 0 28px rgba(180,28,20,0.45),4px 4px 0 rgba(0,0,0,0.6)',
text: '#f89090', text2: '#802020', chroma: 'rgba(180,28,20,0.28)',
},
],
neon: [
{
id: 'default', name: 'Акцент', swatches: ['#00000a', 'var(--accent)', 'var(--accent)'],
bg: 'rgba(0,0,10,0.90)',
},
{
id: 'cyan', name: 'Голубой', swatches: ['#00040c', '#00e5ff', '#00e5ff'],
bg: 'rgba(0,4,12,0.92)', chroma: '#00e5ff', chroma2: '0,229,255',
},
{
id: 'magenta', name: 'Пурпур', swatches: ['#0a0008', '#ff00cc', '#ff00cc'],
bg: 'rgba(10,0,8,0.92)', chroma: '#ff00cc', chroma2: '255,0,204',
},
{
id: 'lime', name: 'Лайм', swatches: ['#000a04', '#00ff88', '#00ff88'],
bg: 'rgba(0,10,4,0.92)', chroma: '#00ff88', chroma2: '0,255,136',
},
],
clean: [
{
id: 'default', name: 'Стандарт', swatches: ['transparent', '#ffffff', 'var(--accent)'],
},
{
id: 'shadow', name: 'С тенью', swatches: ['transparent', '#ffffff', '#666666'],
text: '#ffffff',
},
{
id: 'dark-text', name: 'Тёмный', swatches: ['transparent', '#111111', '#333333'],
text: '#111111', text2: 'rgba(0,0,0,0.50)',
},
{
id: 'warm-text', name: 'Тёплый', swatches: ['transparent', '#ffeedd', '#ddaa66'],
text: '#ffeedd', text2: 'rgba(255,200,120,0.60)',
},
],
y2k: [
{
id: 'default', name: 'Лунный', swatches: ['#c8d0e0', '#1a4090', '#000060'],
titleBg: 'linear-gradient(90deg,#1a4090 0%,#4a80d0 40%,#1a4090 100%)',
bodyBg: 'linear-gradient(160deg,#dce4f0,#c0c8e0)',
text: '#000060', text2: '#404880',
},
{
id: 'rose', name: 'Розовый', swatches: ['#f0d4dc', '#901440', '#600020'],
titleBg: 'linear-gradient(90deg,#901440 0%,#d04080 40%,#901440 100%)',
bodyBg: 'linear-gradient(160deg,#f0d4dc,#d0b0c0)',
text: '#600020', text2: '#804060',
},
{
id: 'dark', name: 'Тёмный', swatches: ['#c0c0c8', '#484848', '#000020'],
titleBg: 'linear-gradient(90deg,#282828 0%,#484848 40%,#282828 100%)',
bodyBg: 'linear-gradient(160deg,#c0c0c8,#a0a0b0)',
text: '#000020', text2: '#303040',
},
{
id: 'forest', name: 'Лесной', swatches: ['#d4ecd8', '#106020', '#004010'],
titleBg: 'linear-gradient(90deg,#106020 0%,#308040 40%,#106020 100%)',
bodyBg: 'linear-gradient(160deg,#d4ecd8,#b8d8c0)',
text: '#004010', text2: '#306040',
},
],
lofi: [
{
id: 'default', name: 'Коричневый', swatches: ['#2a1a0e', '#fde8c0', '#e8a060'],
bg: 'rgba(42,26,14,0.92)',
text: '#fde8c0', text2: 'rgba(var(--accent-rgb),0.60)',
},
{
id: 'night', name: 'Ночной', swatches: ['#0c1020', '#c0d4f8', '#7090d8'],
bg: 'rgba(12,16,32,0.92)',
text: '#c0d4f8', text2: 'rgba(var(--accent-rgb),0.60)',
},
{
id: 'forest', name: 'Лесной', swatches: ['#0c1c10', '#c8f0c8', '#60c870'],
bg: 'rgba(12,28,16,0.92)',
text: '#c8f0c8', text2: 'rgba(var(--accent-rgb),0.60)',
},
{
id: 'plum', name: 'Сливовый', swatches: ['#1c0c20', '#e8c0f8', '#c060d8'],
bg: 'rgba(28,12,32,0.92)',
text: '#e8c0f8', text2: 'rgba(var(--accent-rgb),0.60)',
},
],
glam: [
{
id: 'default', name: 'Золото', swatches: ['#1c1204', '#d4af37', '#f0c840'],
bg: 'linear-gradient(145deg,rgba(28,18,4,0.95),rgba(16,10,2,0.95))',
chroma: '212,175,55', text: '#f0c840', text2: 'rgba(200,150,30,0.65)',
},
{
id: 'silver', name: 'Серебро', swatches: ['#101214', '#c0c8d2', '#e0e8f0'],
bg: 'linear-gradient(145deg,rgba(16,18,20,0.96),rgba(10,12,14,0.96))',
chroma: '192,200,210', text: '#e0e8f0', text2: 'rgba(160,170,180,0.65)',
},
{
id: 'rose-gold', name: 'Розовое золото', swatches: ['#160e0e', '#dc9e8e', '#f0c8b8'],
bg: 'linear-gradient(145deg,rgba(22,14,14,0.95),rgba(14,8,8,0.95))',
chroma: '220,158,142', text: '#f0c8b8', text2: 'rgba(200,140,120,0.65)',
},
{
id: 'emerald', name: 'Изумруд', swatches: ['#08140e', '#32b478', '#80f8c0'],
bg: 'linear-gradient(145deg,rgba(8,20,14,0.95),rgba(4,12,8,0.95))',
chroma: '50,180,120', text: '#80f8c0', text2: 'rgba(40,160,100,0.65)',
},
],
matrix: [
{
id: 'default', name: 'Зелёный', swatches: ['#000800', '#00ff32', '#00ff32'],
bg: 'rgba(0,8,0,0.93)', chroma: '#00ff32', chroma2: '0,255,50',
border: '1px solid rgba(0,255,50,0.35)',
},
{
id: 'cyan', name: 'Голубой', swatches: ['#00040c', '#00e5ff', '#00e5ff'],
bg: 'rgba(0,4,12,0.93)', chroma: '#00e5ff', chroma2: '0,229,255',
border: '1px solid rgba(0,229,255,0.35)',
},
{
id: 'amber', name: 'Янтарь', swatches: ['#0c0800', '#ffb400', '#ffb400'],
bg: 'rgba(12,8,0,0.93)', chroma: '#ffb400', chroma2: '255,180,0',
border: '1px solid rgba(255,180,0,0.35)',
},
{
id: 'violet', name: 'Пурпур', swatches: ['#08000c', '#cc00ff', '#cc00ff'],
bg: 'rgba(8,0,12,0.93)', chroma: '#cc00ff', chroma2: '204,0,255',
border: '1px solid rgba(204,0,255,0.35)',
},
],
}
export function getPalettes(style: OverlayStyle): OverlayPalette[] {
return PALETTES[style] ?? PALETTES.classic
}
export function getPalette(style: OverlayStyle, paletteId: string): OverlayPalette {
const list = getPalettes(style)
return list.find(p => p.id === paletteId) ?? list[0]
}
// ── Custom palette ────────────────────────────────────────────────────────────
export interface ColorFieldDef {
key: string
label: string
default: string // hex default for the color picker
}
export const STYLE_CUSTOM_FIELDS: Record<OverlayStyle, ColorFieldDef[]> = {
classic: [
{ key: 'bg', label: 'Фон', default: '#0a0a10' },
{ key: 'chroma', label: 'Рамка', default: '#ffffff' },
{ key: 'text', label: 'Текст', default: '#ffffff' },
{ key: 'text2', label: 'Текст 2', default: '#888888' },
],
aero: [
{ key: 'bg', label: 'Цвет', default: '#bee6ff' },
{ key: 'text', label: 'Текст', default: '#003a6e' },
{ key: 'text2', label: 'Текст 2', default: '#004488' },
],
retro: [
{ key: 'bg', label: 'Фон', default: '#0c0500' },
{ key: 'chroma', label: 'Хром', default: '#c07030' },
{ key: 'text', label: 'Текст', default: '#f8d090' },
{ key: 'text2', label: 'Текст 2', default: '#9a6020' },
],
neon: [
{ key: 'bg', label: 'Фон', default: '#00000a' },
{ key: 'chroma', label: 'Свечение', default: '#de9cfe' },
],
clean: [
{ key: 'text', label: 'Текст', default: '#ffffff' },
{ key: 'text2', label: 'Текст 2', default: '#888888' },
],
y2k: [
{ key: 'titleBg', label: 'Заголовок', default: '#1a4090' },
{ key: 'bodyBg', label: 'Тело', default: '#c0c8e0' },
{ key: 'text', label: 'Текст', default: '#000060' },
{ key: 'text2', label: 'Текст 2', default: '#404880' },
],
lofi: [
{ key: 'bg', label: 'Фон', default: '#2a1a0e' },
{ key: 'text', label: 'Текст', default: '#fde8c0' },
{ key: 'text2', label: 'Текст 2', default: '#e8a060' },
],
glam: [
{ key: 'bg', label: 'Фон', default: '#1c1204' },
{ key: 'chroma', label: 'Металл', default: '#d4af37' },
{ key: 'text', label: 'Текст', default: '#f0c840' },
],
matrix: [
{ key: 'bg', label: 'Фон', default: '#000800' },
{ key: 'chroma', label: 'Фосфор', default: '#00ff32' },
],
}
function hexToRgba(hex: string, a: number): string {
if (!/^#[0-9a-fA-F]{6}$/.test(hex)) return hex
const r = parseInt(hex.slice(1, 3), 16)
const g = parseInt(hex.slice(3, 5), 16)
const b = parseInt(hex.slice(5, 7), 16)
return `rgba(${r},${g},${b},${a})`
}
function hexToRgb(hex: string): string {
if (!/^#[0-9a-fA-F]{6}$/.test(hex)) return '128,128,128'
const r = parseInt(hex.slice(1, 3), 16)
const g = parseInt(hex.slice(3, 5), 16)
const b = parseInt(hex.slice(5, 7), 16)
return `${r},${g},${b}`
}
export function buildCustomPalette(
style: OverlayStyle,
data: Record<string, string>,
): OverlayPalette {
const pal: OverlayPalette = { id: 'custom', name: 'Свой', swatches: [] }
if (data.text) pal.text = data.text
if (data.text2) pal.text2 = data.text2
if (data.bg) {
if (style === 'aero') {
pal.bg = `linear-gradient(160deg,${hexToRgba(data.bg, 0.55)} 0%,${hexToRgba(data.bg, 0.38)} 100%)`
} else {
const alpha = style === 'classic' ? 0.85 : 0.92
pal.bg = hexToRgba(data.bg, alpha)
}
}
if (data.chroma) {
switch (style) {
case 'glam':
pal.chroma = hexToRgb(data.chroma)
break
case 'neon':
case 'matrix':
pal.chroma = data.chroma
pal.chroma2 = hexToRgb(data.chroma)
if (style === 'matrix') {
pal.border = `1px solid ${hexToRgba(data.chroma, 0.38)}`
}
break
case 'retro':
pal.chroma = hexToRgba(data.chroma, 0.30)
pal.border = `2px solid ${data.chroma}`
pal.shadow = `0 0 28px ${hexToRgba(data.chroma, 0.45)},4px 4px 0 rgba(0,0,0,0.6)`
break
case 'classic':
pal.border = `1px solid ${hexToRgba(data.chroma, 0.28)}`
pal.chroma = hexToRgba(data.chroma, 0.10)
break
default:
pal.chroma = data.chroma
}
}
if (data.titleBg) pal.titleBg = data.titleBg
if (data.bodyBg) pal.bodyBg = data.bodyBg
return pal
}