99 parseAuthors ,
1010} from ' @/lib/data-utils'
1111import { formatDate } from ' @/lib/utils'
12+ import { getThumbnailColors } from ' @/lib/thumbnail-colors'
1213import { Icon } from ' astro-icon/components'
1314import { Image } from ' astro:assets'
1415import type { CollectionEntry } from ' astro:content'
@@ -24,165 +25,98 @@ const readTime = await getCombinedReadingTime(entry.id)
2425const authors = await parseAuthors (entry .data .authors ?? [])
2526const subpostCount = ! isSubpost (entry .id ) ? await getSubpostCount (entry .id ) : 0
2627
27- // Helper function to get thumbnail colors with automatic pairing
28- function getThumbnailColors(data : any ) {
29- const theme: ' dark-on-light' | ' light-on-dark' = data .thumbnailTheme === ' light-on-dark' ? ' light-on-dark' : ' dark-on-light' // default theme
30-
31- // Default color pairs
32- const colorPairs = {
33- ' dark-on-light' : {
34- iconDefault: ' #374151' , // gray-700
35- bgDefault: ' #f3f4f6' , // gray-100
36- },
37- ' light-on-dark' : {
38- iconDefault: ' #f9fafb' , // gray-50
39- bgDefault: ' #374151' , // gray-700
40- }
28+ /* ---------- visual theming ---------- */
29+ const thumb = getThumbnailColors (entry .data ) // ← NEW
30+ const card = (() => {
31+ if (! entry .data .cardBgColor ) return { hasBg:false }
32+ return {
33+ hasBg:true ,
34+ light: entry .data .cardBgColor ,
35+ dark: entry .data .cardBgColorDark ?? entry .data .cardBgColor ,
4136 }
42-
43- const defaultColors = colorPairs [theme ]
44-
45- let iconColor = data .thumbnailIconColor || defaultColors .iconDefault
46- let bgColor = data .thumbnailBgColor || defaultColors .bgDefault
47-
48- // If only one color is provided, auto-generate the other based on theme
49- if (data .thumbnailIconColor && ! data .thumbnailBgColor ) {
50- // Icon color provided, generate background
51- if (theme === ' dark-on-light' ) {
52- bgColor = lightenColor (data .thumbnailIconColor , 0.85 ) // Much lighter version
53- } else {
54- bgColor = darkenColor (data .thumbnailIconColor , 0.7 ) // Darker version
55- }
56- } else if (data .thumbnailBgColor && ! data .thumbnailIconColor ) {
57- // Background color provided, generate icon
58- if (theme === ' dark-on-light' ) {
59- iconColor = darkenColor (data .thumbnailBgColor , 0.7 ) // Much darker version
60- } else {
61- iconColor = lightenColor (data .thumbnailBgColor , 0.85 ) // Lighter version
62- }
63- }
64-
65- return { iconColor , bgColor }
66- }
67-
68- // Helper functions to lighten/darken colors
69- function lightenColor(color : string , factor : number ): string {
70- // Convert hex to RGB
71- const hex = color .replace (' #' , ' ' )
72- const r = parseInt (hex .substr (0 , 2 ), 16 )
73- const g = parseInt (hex .substr (2 , 2 ), 16 )
74- const b = parseInt (hex .substr (4 , 2 ), 16 )
75-
76- // Lighten by interpolating towards white
77- const newR = Math .round (r + (255 - r ) * factor )
78- const newG = Math .round (g + (255 - g ) * factor )
79- const newB = Math .round (b + (255 - b ) * factor )
80-
81- return ` #${newR .toString (16 ).padStart (2 , ' 0' )}${newG .toString (16 ).padStart (2 , ' 0' )}${newB .toString (16 ).padStart (2 , ' 0' )} `
82- }
37+ })()
8338
84- function darkenColor(color : string , factor : number ): string {
85- // Convert hex to RGB
86- const hex = color .replace (' #' , ' ' )
87- const r = parseInt (hex .substr (0 , 2 ), 16 )
88- const g = parseInt (hex .substr (2 , 2 ), 16 )
89- const b = parseInt (hex .substr (4 , 2 ), 16 )
90-
91- // Darken by multiplying by factor
92- const newR = Math .round (r * factor )
93- const newG = Math .round (g * factor )
94- const newB = Math .round (b * factor )
95-
96- return ` #${newR .toString (16 ).padStart (2 , ' 0' )}${newG .toString (16 ).padStart (2 , ' 0' )}${newB .toString (16 ).padStart (2 , ' 0' )} `
39+ const cssVars = {
40+ bgLight: card .light ,
41+ bgDark: card .dark ,
42+ thumbIconLight: thumb .iconLight ,
43+ thumbIconDark: thumb .iconDark ,
44+ thumbBgLight: thumb .bgLight ,
45+ thumbBgDark: thumb .bgDark ,
9746}
47+ ---
9848
99- const thumbnailColors = getThumbnailColors (entry .data )
100-
101- // Helper function to get card styles
102- function getCardStyles(data : any ) {
103- let cardStyle = ' '
104- let hoverStyle = ' '
105-
106- // Set background color if provided
107- if (data .cardBgColor ) {
108- cardStyle += ` background-color: ${data .cardBgColor }; `
109- }
110-
111- // Set outline color on hover if provided
112- if (data .cardOutlineColor ) {
113- hoverStyle += ` outline: 3px solid ${data .cardOutlineColor }; outline-offset: -3px; `
114- }
115-
116- return { cardStyle , hoverStyle }
117- }
11849
119- const cardStyles = getCardStyles (entry .data )
120- ---
50+ <style define:vars ={ cssVars } >
51+ /* card background */
52+ .blog-card-bg { background-color: var(--bgLight); }
53+ :root[data-theme='dark']
54+ .blog-card-bg { background-color: var(--bgDark); }
12155
122- <style >
123- /* No image loading styles needed anymore */
56+ /* thumbnail */
57+ .thumbnail { background: var(--thumbBgLight); }
58+ .thumbnail-icon { color: var(--thumbIconLight); }
59+ :root[data-theme='dark']
60+ .thumbnail { background: var(--thumbBgDark); }
61+ :root[data-theme='dark']
62+ .thumbnail-icon { color: var(--thumbIconDark); }
12463</style >
12564
12665<div
127- class =" hover:bg-secondary/50 hover-card rounded-xl border p-3 transition-colors duration-300 ease-in-out"
66+ class ={ ` hover:bg-secondary/50 hover-card rounded-xl border p-3 transition-colors duration-300 ease-in-out ${ card . hasBg ? ' blog-card-bg ' : ' ' } ` }
12867>
12968 <Link
13069 href ={ ` /${entry .collection }/${entry .id } ` }
13170 class =" flex flex-col gap-4 sm:flex-row"
13271 >
133- {
134- entry .data .image && (
135- <div class = " relative max-w-[200px] sm:flex-shrink-0 relative" >
136- <div
137- class = " h-[55px] w-[80px] rounded-xl flex items-center justify-center"
138- style = { ` background-color: ${thumbnailColors .bgColor } ` }
139- >
140- <Icon
141- name = { entry .data .thumbnailIcon || ' lucide:file-text' }
142- class = { ` ${entry .data .thumbnailIconSize || ' h-10 w-10' } ` }
143- style = { ` color: ${thumbnailColors .iconColor } ` }
144- />
145- </div >
72+ { (entry .data .thumbnailIcon || entry .data .image ) && (
73+ <div class = " relative max-w-[200px] sm:flex-shrink-0" >
74+ <div class = " h-[55px] w-[80px] rounded-xl flex items-center justify-center thumbnail" >
75+ <Icon
76+ name = { entry .data .thumbnailIcon || ' lucide:file-text' }
77+ class = { ` thumbnail-icon ${entry .data .thumbnailIconSize || ' h-10 w-10' } ` }
78+ style = " fill:currentColor;"
79+ />
14680 </div >
147- )
148- }
81+ </ div >
82+ ) }
14983
15084 <div class =" grow" >
151- <h3 class =" text-xl font-extrabold leading-none mb-1" >{ entry .data .title } </h3 >
152- <p class =" text-muted-foreground mb-1 text-sm font-medium leading-tight" >{ entry .data .description } </p >
85+ <h3 class =" text-wrap text-xl font-extrabold leading-none mb-1" >
86+ { entry .data .title }
87+ </h3 >
88+ <p class =" text-muted-foreground mb-1 text-sm font-medium leading-tight" >
89+ { entry .data .description }
90+ </p >
15391
154- <div
155- class =" text-muted-foreground mt-2 flex flex-wrap items-center gap-x-2 text-xs"
156- >
92+ <div class =" text-muted-foreground mt-2 flex flex-wrap items-center gap-x-2 text-xs" >
15793 <span >{ formattedDate } </span >
15894 <Separator orientation =" vertical" className =" h-4!" />
15995 <span >{ readTime } </span >
160- {
161- subpostCount > 0 && (
162- <>
163- <Separator orientation = " vertical" className = " h-4!" />
164- <span class = " flex items-center gap-1" >
165- <Icon name = " lucide:file-text" class = " size-3" />
166- { subpostCount } subpost{ subpostCount === 1 ? ' ' : ' s' }
167- </span >
168- </>
169- )
170- }
171- {
172- entry .data .tags && entry .data .tags .length > 0 && (
173- <>
174- <Separator orientation = " vertical" className = " h-4!" />
175- <div class = " flex flex-wrap gap-1" >
176- { entry .data .tags .map ((tag ) => (
177- <Badge variant = " muted" className = " flex items-center gap-x-1 text-xs" >
178- { tag }
179- </Badge >
180- ))}
181- </div >
182- </>
183- )
184- }
96+
97+ { subpostCount > 0 && (
98+ <>
99+ <Separator orientation = " vertical" className = " h-4!" />
100+ <span class = " flex items-center gap-1" >
101+ <Icon name = " lucide:file-text" class = " size-3" />
102+ { subpostCount } subpost{ subpostCount === 1 ? ' ' : ' s' }
103+ </span >
104+ </>
105+ )}
106+
107+ { entry .data .tags ?.length && (
108+ <>
109+ <Separator orientation = " vertical" className = " h-4!" />
110+ <div class = " flex flex-wrap gap-1" >
111+ { entry .data .tags .map ((tag ) => (
112+ <Badge variant = " muted" className = " flex items-center gap-x-1 text-xs" >
113+ { tag }
114+ </Badge >
115+ ))}
116+ </div >
117+ </>
118+ )}
185119 </div >
186120 </div >
187121 </Link >
188- </div >
122+ </div >
0 commit comments