@@ -17,6 +17,9 @@ import {
1717 Calendar ,
1818 PenLine ,
1919 Sparkles ,
20+ Clock3 ,
21+ Database ,
22+ FileCode2 ,
2023} from "lucide-react" ;
2124import { useTranslations } from "next-intl" ;
2225import { useLanguage } from "@/hooks/useLanguage" ;
@@ -32,6 +35,8 @@ type BlogItem = {
3235 date : string ;
3336 slug : string ;
3437 source ?: "static" | "firestore" ;
38+ authorName ?: string ;
39+ authorPhotoURL ?: string ;
3540} ;
3641
3742/* ── Floating ambient orbs ── */
@@ -181,6 +186,11 @@ const BlogCard = ({
181186 year : "numeric" ,
182187 } ) ;
183188
189+ // Reading time estimate (rough: ~1min per 200 chars of description)
190+ const readTime = Math . max ( 1 , Math . ceil ( ( blog . description ?. length ?? 80 ) / 200 ) ) ;
191+
192+ const SourceIcon = blog . source === "firestore" ? Database : FileCode2 ;
193+
184194 return (
185195 < motion . div
186196 className = { cn ( isFeatured && "md:col-span-2 lg:col-span-2" ) }
@@ -207,9 +217,16 @@ const BlogCard = ({
207217 "transition-all duration-500 ease-out" ,
208218 "hover:-translate-y-1.5 hover:border-white/[0.12]" ,
209219 "hover:shadow-[0_20px_60px_-15px_rgba(167,139,250,0.12)]" ,
210- isFeatured ? "p-7 md:p-8" : "p-6"
211220 ) }
212221 >
222+ { /* Top accent bar — always visible, colored by accent */ }
223+ < div
224+ className = "h-[2px] w-full"
225+ style = { {
226+ background : `linear-gradient(90deg, ${ accentColor } 50, ${ accentColor } 20, transparent 80%)` ,
227+ } }
228+ />
229+
213230 { /* Cursor-following spotlight */ }
214231 < motion . div
215232 className = "absolute pointer-events-none opacity-0 group-hover:opacity-100 transition-opacity duration-500"
@@ -224,15 +241,7 @@ const BlogCard = ({
224241 } }
225242 />
226243
227- { /* Top accent line on hover */ }
228- < motion . div
229- className = "absolute top-0 left-0 right-0 h-[2px] opacity-0 group-hover:opacity-100 transition-opacity duration-500"
230- style = { {
231- background : `linear-gradient(90deg, transparent, ${ accentColor } , transparent)` ,
232- } }
233- />
234-
235- { /* Corner glow */ }
244+ { /* Corner glow on hover */ }
236245 < div
237246 className = "absolute -top-12 -right-12 w-32 h-32 opacity-0 group-hover:opacity-[0.06] transition-opacity duration-700 rounded-full"
238247 style = { {
@@ -241,42 +250,54 @@ const BlogCard = ({
241250 />
242251
243252 { /* Content */ }
244- < div className = "relative z-10 flex flex-col h-full" >
245- { /* Header: date + featured badge */ }
246- < div className = "flex items-center gap-3 mb-4" >
247- < div className = "flex items-center gap-1.5" >
248- < Calendar
249- className = "w-3 h-3"
250- style = { { color : accentColor } }
251- />
252- < span className = "font-mono text-[11px] text-zinc-500" >
253- { formattedDate }
254- </ span >
255- </ div >
253+ < div className = { cn (
254+ "relative z-10 flex flex-col h-full" ,
255+ isFeatured ? "p-7 md:p-8" : "p-5 md:p-6"
256+ ) } >
257+ { /* Header row: meta badges */ }
258+ < div className = "flex items-center flex-wrap gap-2.5 mb-4" >
259+ { /* Date badge */ }
260+ < span className = "inline-flex items-center gap-1.5 text-[11px] font-mono text-zinc-500" >
261+ < Calendar className = "w-3 h-3" style = { { color : accentColor } } />
262+ { formattedDate }
263+ </ span >
264+
265+ < span className = "w-0.5 h-0.5 rounded-full bg-zinc-700" />
266+
267+ { /* Read time */ }
268+ < span className = "inline-flex items-center gap-1 text-[11px] font-mono text-zinc-600" >
269+ < Clock3 className = "w-2.5 h-2.5" />
270+ { readTime } min
271+ </ span >
272+
273+ < span className = "w-0.5 h-0.5 rounded-full bg-zinc-700" />
274+
275+ { /* Source micro badge */ }
276+ < span
277+ className = "inline-flex items-center gap-1 text-[9px] font-mono uppercase tracking-wider px-1.5 py-0.5 rounded-md border"
278+ style = { {
279+ color : `${ accentColor } 90` ,
280+ background : `${ accentColor } 06` ,
281+ borderColor : `${ accentColor } 15` ,
282+ } }
283+ >
284+ < SourceIcon className = "w-2.5 h-2.5" />
285+ { blog . source === "firestore" ? "Community" : "MDX" }
286+ </ span >
287+
288+ { /* Featured badge */ }
256289 { isFeatured && (
257- < motion . span
258- className = "flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-mono uppercase tracking-wider"
290+ < span
291+ className = "inline- flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-mono uppercase tracking-wider"
259292 style = { {
260293 background : `${ accentColor } 10` ,
261294 border : `1px solid ${ accentColor } 25` ,
262295 color : accentColor ,
263296 } }
264- animate = { {
265- boxShadow : [
266- `0 0 0px ${ accentColor } 00` ,
267- `0 0 12px ${ accentColor } 20` ,
268- `0 0 0px ${ accentColor } 00` ,
269- ] ,
270- } }
271- transition = { {
272- duration : 3 ,
273- repeat : Infinity ,
274- ease : "easeInOut" ,
275- } }
276297 >
277298 < Sparkles className = "w-2.5 h-2.5" />
278299 Latest
279- </ motion . span >
300+ </ span >
280301 ) }
281302 </ div >
282303
@@ -305,32 +326,56 @@ const BlogCard = ({
305326 </ p >
306327 ) }
307328
308- { /* Read more (pushed to bottom) */ }
309- < div className = "mt-auto flex items-center gap-2 text-sm" >
310- < span
311- className = "font-mono text-xs transition-colors duration-300"
312- style = { { color : `${ accentColor } 90` } }
313- >
314- < span className = { mmFont } > { t ( "readArticle" ) } </ span >
315- </ span >
316- < motion . span
317- className = "inline-block"
318- initial = { { x : 0 } }
319- whileHover = { { x : 3 } }
320- >
329+ { /* Separator */ }
330+ < div
331+ className = "h-px w-full mb-4 mt-auto"
332+ style = { {
333+ background : `linear-gradient(90deg, ${ accentColor } 10, white/[0.03], transparent)` ,
334+ } }
335+ />
336+
337+ { /* Footer: author + read more */ }
338+ < div className = "flex items-center justify-between" >
339+ { /* Author */ }
340+ < div className = "flex items-center gap-2" >
341+ { blog . authorPhotoURL ? (
342+ < img
343+ src = { blog . authorPhotoURL }
344+ alt = { blog . authorName ?? "" }
345+ className = "w-5 h-5 rounded-full object-cover ring-1 ring-white/[0.08]"
346+ referrerPolicy = "no-referrer"
347+ />
348+ ) : (
349+ < span
350+ className = "w-5 h-5 rounded-full flex items-center justify-center text-[10px] font-bold uppercase ring-1 ring-white/[0.08]"
351+ style = { {
352+ background : `${ accentColor } 15` ,
353+ color : `${ accentColor } cc` ,
354+ } }
355+ >
356+ { ( blog . authorName ?? blog . source ?? "M" ) . charAt ( 0 ) }
357+ </ span >
358+ ) }
359+ { blog . authorName && (
360+ < span className = "text-[11px] font-mono text-zinc-500 truncate max-w-[100px]" >
361+ { blog . authorName }
362+ </ span >
363+ ) }
364+ </ div >
365+
366+ { /* Read more + index */ }
367+ < div className = "flex items-center gap-2 text-sm" >
368+ < span
369+ className = "font-mono text-xs transition-colors duration-300"
370+ style = { { color : `${ accentColor } 90` } }
371+ >
372+ < span className = { mmFont } > { t ( "readArticle" ) } </ span >
373+ </ span >
321374 < ArrowUpRight
322375 className = "w-3.5 h-3.5 transition-all duration-300 group-hover:translate-x-0.5 group-hover:-translate-y-0.5"
323376 style = { { color : accentColor } }
324377 />
325- </ motion . span >
326-
327- { /* Expanding line */ }
328- < motion . div
329- className = "h-[1px] flex-1 origin-left scale-x-0 group-hover:scale-x-100 transition-transform duration-500"
330- style = { {
331- background : `linear-gradient(90deg, ${ accentColor } 40, transparent)` ,
332- } }
333- />
378+ </ div >
334379 </ div >
335380 </ div >
336381 </ div >
@@ -442,6 +487,8 @@ const BlogPageClient = ({ blogs: staticBlogs }: { blogs: BlogItem[] }) => {
442487 date : ( p . publishedAt ?? p . createdAt ) . toISOString ( ) ,
443488 slug : `/blog/post?slug=${ p . slug } ` ,
444489 source : "firestore" as const ,
490+ authorName : p . authorName ,
491+ authorPhotoURL : p . authorPhotoURL ,
445492 } ) ) ;
446493 const blogs = [ ...staticBlogs . map ( ( b ) => ( { ...b , source : "static" as const } ) ) , ...firestoreItems ]
447494 . sort ( ( a , b ) => new Date ( b . date ) . getTime ( ) - new Date ( a . date ) . getTime ( ) ) ;
0 commit comments