Files

297 lines
10 KiB
TypeScript

import React, { useMemo } from 'react';
interface AlbumCoverProps {
seed: string;
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
className?: string;
children?: React.ReactNode;
}
// Seeded random number generator for consistent results
class SeededRandom {
private seed: number;
constructor(seed: string) {
this.seed = this.hashString(seed);
}
private hashString(str: string): number {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return Math.abs(hash) || 1;
}
next(): number {
this.seed = (this.seed * 1103515245 + 12345) & 0x7fffffff;
return this.seed / 0x7fffffff;
}
range(min: number, max: number): number {
return min + this.next() * (max - min);
}
int(min: number, max: number): number {
return Math.floor(this.range(min, max));
}
pick<T>(arr: T[]): T {
return arr[this.int(0, arr.length)];
}
}
// Curated color palettes - music-themed combinations
const palettes = [
// Sunset Vibes
{ colors: ['#FF6B6B', '#FEC89A', '#FFD93D', '#C9184A'], bg: '#1a1a2e' },
// Ocean Depths
{ colors: ['#0077B6', '#00B4D8', '#90E0EF', '#CAF0F8'], bg: '#03045E' },
// Forest Night
{ colors: ['#2D6A4F', '#40916C', '#52B788', '#95D5B2'], bg: '#1B4332' },
// Neon Dreams
{ colors: ['#F72585', '#7209B7', '#3A0CA3', '#4CC9F0'], bg: '#10002B' },
// Golden Hour
{ colors: ['#FF9500', '#FF5400', '#FFBD00', '#FFE066'], bg: '#2D1B00' },
// Arctic Aurora
{ colors: ['#48CAE4', '#00F5D4', '#9B5DE5', '#F15BB5'], bg: '#0A0A1A' },
// Lavender Haze
{ colors: ['#E0AAFF', '#C77DFF', '#9D4EDD', '#7B2CBF'], bg: '#240046' },
// Cherry Blossom
{ colors: ['#FFCCD5', '#FFB3C1', '#FF758F', '#C9184A'], bg: '#2B0A14' },
// Cyber Punk
{ colors: ['#00FF87', '#60EFFF', '#FF00E5', '#FFE500'], bg: '#0D0D0D' },
// Deep Space
{ colors: ['#7400B8', '#5E60CE', '#4EA8DE', '#56CFE1'], bg: '#03071E' },
// Warm Ember
{ colors: ['#FFBA08', '#FAA307', '#F48C06', '#E85D04'], bg: '#370617' },
// Cool Mint
{ colors: ['#64DFDF', '#72EFDD', '#80FFDB', '#5EEAD4'], bg: '#0D3B3B' },
// Velvet Rose
{ colors: ['#9D174D', '#BE185D', '#DB2777', '#EC4899'], bg: '#1C0A14' },
// Electric Blue
{ colors: ['#0EA5E9', '#38BDF8', '#7DD3FC', '#E0F2FE'], bg: '#0C1929' },
// Jungle Fever
{ colors: ['#84CC16', '#A3E635', '#BEF264', '#ECFCCB'], bg: '#1A2E05' },
];
type PatternType = 'aurora' | 'mesh' | 'orbs' | 'rays' | 'waves' | 'geometric' | 'nebula' | 'gradient' | 'rings' | 'crystal';
const generatePattern = (rng: SeededRandom, palette: typeof palettes[0]): React.CSSProperties => {
const patterns: PatternType[] = ['aurora', 'mesh', 'orbs', 'rays', 'waves', 'geometric', 'nebula', 'gradient', 'rings', 'crystal'];
const pattern = rng.pick(patterns);
const colors = palette.colors;
const bg = palette.bg;
switch (pattern) {
case 'aurora': {
const angle1 = rng.int(0, 360);
const angle2 = rng.int(0, 360);
return {
background: `
linear-gradient(${angle1}deg, ${colors[0]}00 0%, ${colors[0]}88 25%, ${colors[1]}88 50%, ${colors[2]}88 75%, ${colors[3]}00 100%),
linear-gradient(${angle2}deg, ${colors[2]}00 0%, ${colors[3]}66 30%, ${colors[0]}66 70%, ${colors[1]}00 100%),
radial-gradient(ellipse at ${rng.int(20, 80)}% ${rng.int(60, 100)}%, ${colors[1]}44 0%, transparent 50%),
linear-gradient(180deg, ${bg} 0%, ${colors[3]}22 100%)
`,
backgroundColor: bg,
};
}
case 'mesh': {
const points = [
{ x: rng.int(0, 40), y: rng.int(0, 40) },
{ x: rng.int(60, 100), y: rng.int(0, 40) },
{ x: rng.int(0, 40), y: rng.int(60, 100) },
{ x: rng.int(60, 100), y: rng.int(60, 100) },
];
return {
background: `
radial-gradient(at ${points[0].x}% ${points[0].y}%, ${colors[0]} 0%, transparent 50%),
radial-gradient(at ${points[1].x}% ${points[1].y}%, ${colors[1]} 0%, transparent 50%),
radial-gradient(at ${points[2].x}% ${points[2].y}%, ${colors[2]} 0%, transparent 50%),
radial-gradient(at ${points[3].x}% ${points[3].y}%, ${colors[3]} 0%, transparent 50%)
`,
backgroundColor: bg,
};
}
case 'orbs': {
const orbCount = rng.int(3, 6);
const orbs = Array.from({ length: orbCount }, (_, i) => {
const size = rng.int(30, 70);
const x = rng.int(10, 90);
const y = rng.int(10, 90);
const color = colors[i % colors.length];
const blur = rng.int(20, 40);
return `radial-gradient(circle ${size}% at ${x}% ${y}%, ${color}99 0%, ${color}44 ${blur}%, transparent 70%)`;
});
return {
background: [...orbs, `linear-gradient(135deg, ${bg} 0%, ${colors[0]}11 100%)`].join(', '),
backgroundColor: bg,
};
}
case 'rays': {
const centerX = rng.int(30, 70);
const centerY = rng.int(30, 70);
const rayCount = rng.int(6, 12);
const rays = Array.from({ length: rayCount }, (_, i) => {
const angle = (360 / rayCount) * i + rng.int(-10, 10);
const color = colors[i % colors.length];
return `linear-gradient(${angle}deg, transparent 0%, transparent 45%, ${color}66 48%, ${color}66 52%, transparent 55%, transparent 100%)`;
});
return {
background: [
`radial-gradient(circle at ${centerX}% ${centerY}%, ${colors[0]} 0%, transparent 30%)`,
...rays,
].join(', '),
backgroundColor: bg,
};
}
case 'waves': {
const waveAngle = rng.int(0, 180);
const waveSize = rng.int(8, 20);
return {
background: `
repeating-linear-gradient(
${waveAngle}deg,
${colors[0]}44 0px,
${colors[1]}44 ${waveSize}px,
${colors[2]}44 ${waveSize * 2}px,
${colors[3]}44 ${waveSize * 3}px,
${colors[0]}44 ${waveSize * 4}px
),
radial-gradient(ellipse at 50% 0%, ${colors[0]}66 0%, transparent 70%),
radial-gradient(ellipse at 50% 100%, ${colors[2]}66 0%, transparent 70%)
`,
backgroundColor: bg,
};
}
case 'geometric': {
const angle = rng.int(0, 90);
return {
background: `
conic-gradient(from ${angle}deg at 50% 50%, ${colors[0]}, ${colors[1]}, ${colors[2]}, ${colors[3]}, ${colors[0]}),
repeating-conic-gradient(from 0deg at 50% 50%, ${bg}00 0deg, ${bg}88 ${90/rng.int(2,6)}deg)
`,
backgroundBlendMode: 'overlay',
backgroundColor: bg,
};
}
case 'nebula': {
const x1 = rng.int(20, 80);
const y1 = rng.int(20, 80);
const x2 = rng.int(20, 80);
const y2 = rng.int(20, 80);
return {
background: `
radial-gradient(ellipse ${rng.int(60, 100)}% ${rng.int(40, 80)}% at ${x1}% ${y1}%, ${colors[0]}88 0%, transparent 50%),
radial-gradient(ellipse ${rng.int(40, 80)}% ${rng.int(60, 100)}% at ${x2}% ${y2}%, ${colors[1]}88 0%, transparent 50%),
radial-gradient(ellipse ${rng.int(50, 90)}% ${rng.int(50, 90)}% at ${100-x1}% ${100-y1}%, ${colors[2]}66 0%, transparent 60%),
radial-gradient(ellipse ${rng.int(30, 60)}% ${rng.int(30, 60)}% at ${100-x2}% ${100-y2}%, ${colors[3]}44 0%, transparent 70%),
linear-gradient(${rng.int(0, 360)}deg, ${bg} 0%, ${colors[0]}22 50%, ${bg} 100%)
`,
backgroundColor: bg,
};
}
case 'gradient': {
const angle = rng.int(0, 360);
const type = rng.int(0, 3);
if (type === 0) {
return {
background: `linear-gradient(${angle}deg, ${colors[0]} 0%, ${colors[1]} 33%, ${colors[2]} 66%, ${colors[3]} 100%)`,
};
} else if (type === 1) {
return {
background: `
radial-gradient(circle at ${rng.int(30, 70)}% ${rng.int(30, 70)}%, ${colors[0]} 0%, ${colors[1]} 30%, ${colors[2]} 60%, ${colors[3]} 100%)
`,
};
} else {
return {
background: `
linear-gradient(${angle}deg, ${colors[0]} 0%, ${colors[0]} 25%, transparent 25%, transparent 75%, ${colors[2]} 75%),
linear-gradient(${angle + 90}deg, ${colors[1]} 0%, ${colors[1]} 25%, transparent 25%, transparent 75%, ${colors[3]} 75%),
linear-gradient(${angle}deg, ${colors[2]} 0%, ${colors[3]} 100%)
`,
backgroundBlendMode: 'multiply, screen, normal',
};
}
}
case 'rings': {
const centerX = rng.int(30, 70);
const centerY = rng.int(30, 70);
return {
background: `
repeating-radial-gradient(circle at ${centerX}% ${centerY}%,
${colors[0]}66 0px, ${colors[0]}66 2px,
transparent 2px, transparent ${rng.int(15, 25)}px,
${colors[1]}66 ${rng.int(15, 25)}px, ${colors[1]}66 ${rng.int(17, 27)}px,
transparent ${rng.int(17, 27)}px, transparent ${rng.int(35, 50)}px
),
radial-gradient(circle at ${centerX}% ${centerY}%, ${colors[2]}88 0%, transparent 60%),
linear-gradient(${rng.int(0, 180)}deg, ${colors[3]}44, ${colors[0]}44)
`,
backgroundColor: bg,
};
}
case 'crystal': {
const facets = rng.int(4, 8);
const gradients = Array.from({ length: facets }, (_, i) => {
const startAngle = (360 / facets) * i;
const color = colors[i % colors.length];
return `conic-gradient(from ${startAngle}deg at ${50 + rng.int(-20, 20)}% ${50 + rng.int(-20, 20)}%, ${color}88 0deg, transparent ${360/facets}deg)`;
});
return {
background: [
...gradients,
`radial-gradient(circle at 50% 50%, ${colors[0]}44 0%, transparent 70%)`,
].join(', '),
backgroundColor: bg,
};
}
default:
return {
background: `linear-gradient(135deg, ${colors[0]}, ${colors[1]})`,
};
}
};
export const AlbumCover: React.FC<AlbumCoverProps> = ({ seed, size = 'md', className = '', children }) => {
const coverStyle = useMemo(() => {
const rng = new SeededRandom(seed);
const palette = rng.pick(palettes);
return generatePattern(rng, palette);
}, [seed]);
const sizeClasses: Record<string, string> = {
xs: 'w-8 h-8',
sm: 'w-10 h-10',
md: 'w-12 h-12',
lg: 'w-14 h-14',
xl: 'w-48 h-48',
full: 'w-full h-full',
};
return (
<div
className={`${sizeClasses[size]} rounded-md shadow-lg flex-shrink-0 overflow-hidden relative ${className}`}
style={coverStyle}
>
{children}
</div>
);
};
export default AlbumCover;