@@ -4,110 +4,120 @@ import 'server-only';
44import { fetchArtists , fetchShowByUUID } from '@/app/queries' ;
55import { ImageResponse } from 'next/og' ;
66import { NextRequest } from 'next/server' ;
7- import React from 'react ' ;
7+ import { notFound } from 'next/navigation ' ;
88
99export const runtime = 'edge' ;
1010
11- const notFound = ( ) =>
12- new Response ( 'Not Found' , {
13- status : 404 ,
14- } ) ;
15-
1611// Function to generate a color based on artist UUID
1712const getArtistColor = ( uuid : string ) => {
18- // Generate a consistent color based on the UUID
13+ // Generate a consistent hash from the UUID
1914 const hash = Array . from ( uuid ) . reduce ( ( acc , char ) => char . charCodeAt ( 0 ) + ( ( acc << 5 ) - acc ) , 0 ) ;
2015
21- // Map to a curated set of Tailwind colors
22- const tailwindColors = [
23- '#3b82f6' , // blue-500
24- '#10b981' , // emerald-500
25- '#8b5cf6' , // violet-500
26- '#ef4444' , // red-500
27- '#f59e0b' , // amber-500
28- '#06b6d4' , // cyan-500
29- '#f97316' , // orange-500
30- '#8b5cf6' , // violet-500
31- '#0ea5e9' , // sky-500
32- '#22c55e' , // green-500
33- '#dc2626' , // red-600
34- '#0d9488' , // teal-600
35- ] ;
16+ // Use HSL color model for more systematic and beautiful variations
17+ // Hue: 0-360 (full color spectrum)
18+ // Saturation: 60-80% (more vibrant than before)
19+ // Lightness: 55-75% (slightly darker for more vibrant appearance)
20+ const hue = Math . abs ( hash ) % 360 ;
21+ const saturation = 60 + ( Math . abs ( hash >> 8 ) % 20 ) ; // Increased from 40-60% to 60-80%
22+ const lightness = 55 + ( Math . abs ( hash >> 16 ) % 20 ) ; // Adjusted from 65-85% to 55-75%
3623
37- return tailwindColors [ Math . abs ( hash ) % tailwindColors . length ] ;
24+ // Return the color in HSL format
25+ return `hsl(${ hue } , ${ saturation } %, ${ lightness } %)` ;
3826} ;
3927
4028// Function to generate a gradient based on artist UUID
4129const getArtistGradient = ( uuid : string ) => {
30+ // Generate a base color using HSL for better control
4231 const baseColor = getArtistColor ( uuid ) ;
4332
44- // Create complementary color pairs for beautiful gradients using Tailwind colors
45- const gradientPairs = {
46- '#3b82f6' : '#8b5cf6' , // blue-500 to violet-500
47- '#10b981' : '#06b6d4' , // emerald-500 to cyan-500
48- '#8b5cf6' : '#3b82f6' , // violet-500 to blue-500
49- '#ef4444' : '#f97316' , // red-500 to orange-500
50- '#f59e0b' : '#ef4444' , // amber-500 to red-500
51- '#06b6d4' : '#10b981' , // cyan-500 to emerald-500
52- '#f97316' : '#f59e0b' , // orange-500 to amber-500
53- // '#8b5cf6': '#a855f7', // violet-500 to purple-500
54- '#0ea5e9' : '#3b82f6' , // sky-500 to blue-500
55- '#22c55e' : '#10b981' , // green-500 to emerald-500
56- '#dc2626' : '#ef4444' , // red-600 to red-500
57- '#0d9488' : '#06b6d4' , // teal-600 to cyan-500
58- } ;
59-
60- const secondColor = gradientPairs [ baseColor ] || '#8b5cf6' ; // Default to violet-500
61-
62- return `linear-gradient(135deg, ${ baseColor } , ${ secondColor } )` ;
63- } ;
64-
65- export async function GET ( request : NextRequest ) {
66- try {
67- const { searchParams } = new URL ( request . url ) ;
33+ // Create a second hash for the complementary color
34+ const secondHash = Array . from ( uuid ) . reduce (
35+ ( acc , char , i ) => char . charCodeAt ( 0 ) + ( ( acc << ( ( i % 5 ) + 3 ) ) - acc ) ,
36+ 0
37+ ) ;
38+
39+ // Generate complementary colors using color theory
40+ // Options: analogous (±30°), complementary (180°), triadic (120°), split-complementary (±150°)
41+ const colorSchemes = [
42+ 30 , // analogous 1
43+ - 30 , // analogous 2
44+ 60 , // harmonious 1
45+ - 60 , // harmonious 2
46+ 120 , // triadic 1
47+ - 120 , // triadic 2
48+ // 150, // split-complementary 1
49+ // -150, // split-complementary 2
50+ 180 , // complementary (used less frequently for pastels)
51+ ] ;
6852
69- const showUuid = searchParams . get ( 'showUuid' ) ;
70- if ( ! showUuid ) return notFound ( ) ;
53+ // Extract HSL values from the base color
54+ const baseHSLMatch = baseColor . match ( / h s l \( ( \d + ) , \s * ( \d + ) % , \s * ( \d + ) % \) / ) ;
55+ const baseHue = baseHSLMatch ? parseInt ( baseHSLMatch [ 1 ] ) : 0 ;
56+ const baseSaturation = baseHSLMatch ? parseInt ( baseHSLMatch [ 2 ] ) : 60 ;
57+ const baseLightness = baseHSLMatch ? parseInt ( baseHSLMatch [ 3 ] ) : 55 ;
7158
72- const artists = await fetchArtists ( ) ;
73- const show = await fetchShowByUUID ( showUuid ) ;
59+ // Select a color scheme based on the hash
60+ const schemeOffset = colorSchemes [ Math . abs ( secondHash ) % colorSchemes . length ] ;
7461
75- if ( ! show || ! show . sources ?. length ) return notFound ( ) ;
62+ // Create a complementary hue and adjust saturation/lightness for vibrant effect
63+ const secondHue = ( baseHue + schemeOffset + 360 ) % 360 ;
64+ // Higher saturation but still balanced
65+ const secondSaturation = Math . min ( Math . max ( baseSaturation - ( secondHash % 10 ) , 75 ) , 75 ) ; // Increased from 35-55% to 55-75%
66+ const secondLightness = Math . min ( Math . max ( baseLightness + ( secondHash % 10 ) , 60 ) , 75 ) ; // Adjusted from 70-85% to 60-75%
7667
77- // Get params
78- const artist = artists . find ( ( artist ) => artist . uuid === show . artist_uuid ) ;
79- const artistName = artist ?. name ?? 'Unknown Artist' ;
68+ // Create second color in HSL format
69+ const secondColor = `hsl(${ secondHue } , ${ secondSaturation } %, ${ secondLightness } %)` ;
8070
81- // Generate dynamic background color and pattern based on artist UUID
82- const bgGradient = getArtistGradient ( show . artist_uuid || '' ) ;
71+ // Determine if we should use a third color (about 1/3 of the time)
8372
84- // Generate a pattern of circles for the background based on artist UUID
85- const patternSeed = parseInt ( show . artist_uuid ?. replace ( / \D / g, '' ) . slice ( 0 , 8 ) || '0' , 10 ) ;
86- const patternElements : React . ReactNode [ ] = [ ] ;
73+ const gradientString = `linear-gradient(135deg, ${ baseColor } , ${ secondColor } )` ;
8774
88- for ( let i = 0 ; i < 5 ; i ++ ) {
89- const size = 100 + ( patternSeed % 200 ) ;
90- const x = ( patternSeed * ( i + 1 ) ) % 900 ;
91- const y = ( patternSeed * ( i + 2 ) ) % 900 ;
92- const opacity = 0.1 + i * 0.05 ;
75+ return gradientString ;
76+ } ;
9377
94- patternElements . push (
95- < div
96- key = { i }
97- tw = "absolute rounded-full"
98- style = { {
99- width : size ,
100- height : size ,
101- left : x ,
102- top : y ,
103- background : 'white' ,
104- opacity : opacity ,
105- filter : 'blur(40px)' ,
106- } }
107- />
108- ) ;
78+ const generatePixelatedSVG = ( opacity = 0.4 ) => {
79+ // Create a pixelated pattern with large squares
80+ const squareSize = 45 ; // Size of each pixel square
81+ const gridSize = 10 ; // Number of squares in each row/column
82+ const borderWidth = 1 ; // Reduced width of the border between pixels
83+ const borderColor = 'rgba(0, 0, 0, 0.1)' ; // More transparent black for subtler borders
84+
85+ let squares = '' ;
86+ for ( let y = 0 ; y < gridSize ; y ++ ) {
87+ for ( let x = 0 ; x < gridSize ; x ++ ) {
88+ // TODO: get rid of the randomness here.
89+ const squareOpacity = ( Math . random ( ) * 0.3 + 0.1 ) * opacity ;
90+
91+ squares += `<rect
92+ x="${ x * squareSize } "
93+ y="${ y * squareSize } "
94+ width="${ squareSize } "
95+ height="${ squareSize } "
96+ fill="white"
97+ fill-opacity="${ squareOpacity } "
98+ stroke="${ borderColor } "
99+ stroke-width="${ borderWidth } "
100+ />` ;
109101 }
110- const [ fontReg , fontBold , fontMegaBold ] = await Promise . all ( [
102+ }
103+
104+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${ squareSize * gridSize } " height="${ squareSize * gridSize } " viewBox="0 0 ${ squareSize * gridSize } ${ squareSize * gridSize } ">
105+ ${ squares }
106+ </svg>` ;
107+
108+ return `url("data:image/svg+xml;base64,${ Buffer . from ( svg ) . toString ( 'base64' ) } ")` ;
109+ } ;
110+
111+ export async function GET ( request : NextRequest ) {
112+ try {
113+ const { searchParams } = new URL ( request . url ) ;
114+
115+ const showUuid = searchParams . get ( 'showUuid' ) ;
116+ if ( ! showUuid ) return notFound ( ) ;
117+
118+ const [ artists , show , fontReg , fontBold , fontMegaBold ] = await Promise . all ( [
119+ fetchArtists ( ) ,
120+ fetchShowByUUID ( showUuid ) ,
111121 fetch (
112122 new URL ( 'https://cdn.jsdelivr.net/fontsource/fonts/roboto@latest/latin-400-normal.ttf' ) ,
113123 { next : { revalidate : 60 * 60 * 24 * 30 } } // Cache for 30 days
@@ -122,26 +132,39 @@ export async function GET(request: NextRequest) {
122132 ) . then ( ( res ) => res . arrayBuffer ( ) ) ,
123133 ] ) ;
124134
135+ if ( ! show || ! show . sources ?. length ) return notFound ( ) ;
136+
137+ // Get params
138+ const artist = artists . find ( ( artist ) => artist . uuid === show . artist_uuid ) ;
139+ const artistName = artist ?. name ?? 'Unknown Artist' ;
140+
141+ // Generate dynamic background color and pattern based on artist UUID
142+ const bgGradient = getArtistGradient ( show . artist_uuid || '' ) ;
143+ // Generate pixelated background pattern
144+ const pixelatedSVG = generatePixelatedSVG ( 0.8 ) ;
145+
125146 return new ImageResponse (
126147 (
127148 < div
128149 tw = "flex h-full w-full flex-col items-center justify-center p-10 text-white relative overflow-hidden"
129- style = { { background : bgGradient } }
150+ style = { {
151+ backgroundImage : `${ pixelatedSVG } , ${ bgGradient } ` ,
152+ backgroundBlendMode : 'overlay' ,
153+ } }
130154 >
131- < div tw = "flex w-full max-w-[800px] flex-col items-center justify-center rounded-2xl bg-black/20 p-12 backdrop-blur-sm shadow-2xl " >
132- < div tw = "mb-4 text-center text-7xl font-extrabold tracking-tight" > { artistName } </ div >
133- < div tw = "mb-6 text-center text-6xl font-bold" > { show . display_date } </ div >
155+ < div tw = "flex w-full max-w-[800px] flex-col items-center justify-center rounded-2xl bg-black/25 p-12 relative " >
156+ < div tw = "mb-2 text-center text-7xl font-extrabold tracking-tight" > { artistName } </ div >
157+ < div tw = "mb-2 text-center text-6xl font-bold" > { show . display_date } </ div >
134158 < div tw = "flex items-center justify-center" style = { { gap : 8 } } >
135159 { show . venue ?. name && (
136- < div tw = "rounded-xl bg-white/25 px-6 py-3 text-4xl flex text-center backdrop-blur-sm " >
160+ < div tw = "rounded-xl bg-white/20 px-6 py-3 text-4xl flex text-center" >
137161 { show . venue ?. name } { show . venue ?. location ? `• ${ show . venue . location } ` : '' }
138162 </ div >
139163 ) }
140164 </ div >
141165 </ div >
142166
143167 < div tw = "absolute bottom-6 right-6 text-4xl font-bold" > Relisten.net</ div >
144- { patternElements }
145168 </ div >
146169 ) ,
147170 {
0 commit comments