99
1010{{ $resource := partial "functions/get_featured_image.html" $item }}
1111{{ $anchor := $item.Params.image.focal_point | default "Center" }}
12+ {{ $fill_image := .config.fill_image | default true }}
13+ {{ $showDate := .config.show_date | default true }}
14+ {{ $showReadTime := .config.show_read_time | default true }}
15+ {{ $showReadMore := .config.show_read_more | default true }}
16+ {{ $hasMeta := or $showDate $showReadTime $showReadMore }}
17+ {{ $index := .index }}
1218
13- < a href ="{{ $link }} " {{ $target | safeHTMLAttr }} class ="mb-5 ">
14- < div class ="md:flex ">
15- < div class ="md:flex-shrink-0 overflow-hidden ">
19+ < div class ="group bg-white/90 dark:bg-zinc-900/90 backdrop-blur-sm rounded-2xl ring-1 ring-zinc-900/5 dark:ring-white/10 shadow-lg overflow-hidden transition-all duration-300 ease-out hover:shadow-xl hover:shadow-blue-500/10 hover:-translate-y-2 focus-within:ring-2 focus-within:ring-blue-500/50 " role ="article " aria-labelledby ="card-title-{{ $item.File.UniqueID }} ">
20+ <!-- Image Section -->
21+ < div class ="relative overflow-hidden aspect-[16/9] bg-gradient-to-br from-zinc-100 to-zinc-200 dark:from-zinc-800 dark:to-zinc-900 ">
22+ {{ with $item.Params.content_meta }}
23+ {{ if .trending }}
24+ < div class ="absolute top-3 right-3 z-10 inline-flex items-center gap-1 px-2.5 py-1 rounded-full text-[11px] font-semibold bg-black/55 backdrop-blur-sm text-white shadow-sm ">
25+ < span > {{ i18n "trending" | default "Trending" }}</ span >
26+ < span aria-hidden ="true "> 🔥</ span >
27+ </ div >
28+ {{ end }}
29+ {{ end }}
1630 {{ with $resource }}
17- {{ $original_image := .Fill (printf "655x655 %s" $anchor) }}
18- {{ $responsive := partial "functions/process_responsive_image.html" (dict
19- "image" $original_image
20- "mode" "responsive"
21- "sizes" (slice 192 384 480 655)
22- ) }}
23-
24- < img class ="h-48 w-full object-cover md:w-48 hover:scale-125 transition duration-500 cursor-pointer object-cover "
25- srcset ="{{ $responsive.srcset }} "
26- sizes ="(max-width: 768px) 100vw, 192px "
27- src ="{{ $responsive.fallback.RelPermalink }} "
28- width ="{{ $original_image.Width }} "
29- height ="{{ $original_image.Height }} "
30- loading ="lazy "
31- alt ="{{ $item.Title | plainify }} ">
31+ {{ if ne .MediaType.SubType "gif" }}
32+ {{ $original_image := "" }}
33+ {{ if $fill_image }}
34+ {{ $original_image = .Fill (printf "800x450 %s" $anchor) }}
35+ {{ else }}
36+ {{ $original_image = .Fit (printf "800x450 %s" $anchor) }}
37+ {{ end }}
38+ {{ $responsive := partial "functions/process_responsive_image.html" (dict
39+ "image" $original_image
40+ "mode" "responsive"
41+ "sizes" (slice 400 600 800)
42+ "formats" (slice "avif" "webp" "jpg")
43+ ) }}
44+ < a href ="{{ $link }} " {{ $target | safeHTMLAttr }} class ="block ">
45+ < img class ="w-full h-full transition-transform duration-500 ease-out group-hover:scale-105 {{ if $fill_image }}object-fill{{ else }}object-contain{{ end }} "
46+ srcset ="{{ $responsive.srcset }} "
47+ sizes ="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw "
48+ src ="{{ $responsive.fallback.RelPermalink }} "
49+ width ="{{ $original_image.Width }} "
50+ height ="{{ $original_image.Height }} "
51+ loading ="lazy "
52+ decoding ="async "
53+ {{ if eq $index 0 }}fetchpriority ="high "{{ end }}
54+ style ="position: absolute; height: 100%; width: 100%; inset: 0px; color: transparent; "
55+ alt ="{{ $item.Title | plainify }} featured image ">
56+ </ a >
57+ {{ else }}
58+ < a href ="{{ $link }} " {{ $target | safeHTMLAttr }} class ="block ">
59+ < img class ="w-full h-full transition-transform duration-500 ease-out group-hover:scale-105 {{ if $fill_image }}object-fill{{ else }}object-contain{{ end }} "
60+ src ="{{ .RelPermalink }} "
61+ width ="{{ .Width }} "
62+ height ="{{ .Height }} "
63+ loading ="lazy "
64+ decoding ="async "
65+ style ="position: absolute; height: 100%; width: 100%; inset: 0px; color: transparent; "
66+ alt ="{{ $item.Title | plainify }} featured image ">
67+ </ a >
68+ {{ end }}
69+ {{ else }}
70+ <!-- Fallback gradient with subtle pattern -->
71+ < div class ="w-full h-full bg-gradient-to-br from-blue-500/20 to-purple-500/20 flex items-center justify-center ">
72+ < div class ="w-16 h-16 rounded-full bg-white/20 backdrop-blur-sm flex items-center justify-center ">
73+ < svg class ="w-8 h-8 text-white/60 " fill ="currentColor " viewBox ="0 0 20 20 ">
74+ < path fill-rule ="evenodd " d ="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z " clip-rule ="evenodd "/>
75+ </ svg >
76+ </ div >
77+ </ div >
3278 {{end}}
79+
80+ <!-- Subtle overlay for better text contrast (allow clicks to pass through) -->
81+ < div class ="absolute inset-0 pointer-events-none bg-gradient-to-t from-black/10 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 "> </ div >
3382 </ div >
34- < div class ="p-8 ">
35- < div class ="uppercase tracking-wide text-md text-primary-700 dark:text-primary-200 font-semibold "> {{ $item.Title }}</ div >
36- < p class ="block mt-1 text-sm leading-tight font-medium text-black dark:text-white ">
37- {{ ($item.Params.summary | default $item.Summary) | plainify | htmlUnescape | chomp -}}
38- </ p >
39- < p class ="mt-2 text-gray-500 dark:text-gray-400 text-sm ">
40- {{- $item.Date | time.Format (site.Params.locale.date_format | default ":date_long") -}}
83+
84+ <!-- Content Section -->
85+ < div class ="p-8 space-y-4 ">
86+ {{ with $item.Params.content_meta }}
87+ {{ if or .content_type .difficulty }}
88+ < div class ="flex items-center gap-x-4 ">
89+ {{ with .content_type }}
90+ < span class ="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300 ">
91+ {{ . }}
92+ </ span >
93+ {{ end }}
94+ {{ with .difficulty }}
95+ < span class ="text-sm font-medium text-zinc-600 dark:text-zinc-400 "> {{ i18n "difficulty" }}: {{ . }}</ span >
96+ {{ end }}
97+ </ div >
98+ {{ end }}
99+ {{ end }}
100+
101+ {{ if and $item.Params.tags (gt (len $item.Params.tags) 0) }}
102+ < div class ="flex items-center gap-2 ">
103+ {{ with index ($item.GetTerms "tags") 0 }}
104+ < a href ="{{ .RelPermalink }} ">
105+ < span class ="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold bg-primary-100 text-primary-800 dark:bg-primary-900/40 dark:text-primary-300 ">
106+ {{ .Page.LinkTitle }}
107+ </ span >
108+ </ a >
109+ {{ end }}
110+ </ div >
111+ {{ end }}
112+
113+ < h3 id ="card-title-{{ $item.File.UniqueID }} " class ="text-xl font-bold tracking-tight text-zinc-900 dark:text-zinc-100 group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors duration-200 leading-tight ">
114+ < a href ="{{ $link }} " {{ $target | safeHTMLAttr }} class ="hover:underline ">
115+ {{ $item.Title }}
116+ {{ if $item.Params.external_link }}
117+ {{ partial "functions/get_icon" (dict "name" "arrow-top-right-on-square" "attributes" "style=\"height: 1em;\" class=\"inline-flex h-4 w-4 ml-1 align-text-top\"") }}
118+ {{ end }}
119+ </ a >
120+ </ h3 >
121+
122+ < p class ="text-zinc-600 dark:text-zinc-400 text-base leading-relaxed line-clamp-3 ">
123+ {{ ($item.Params.summary | default $item.Summary) | plainify | htmlUnescape | truncate 180 }}
41124 </ p >
125+
126+ {{ with $item.Params.content_meta.prerequisites }}
127+ {{ $max := 2 }}
128+ {{ $count := len . }}
129+ < div class ="flex items-center gap-2 pt-1 text-sm text-zinc-600 dark:text-zinc-400 ">
130+ < span class ="opacity-80 "> {{ i18n "prerequisites" }}:</ span >
131+ {{ range (first $max .) }}
132+ < span class ="inline-flex items-center px-2 py-0.5 rounded-md text-[11px] font-medium bg-zinc-100 text-zinc-800 dark:bg-zinc-800 dark:text-zinc-300 "> {{ . }}</ span >
133+ {{ end }}
134+ {{ if gt $count $max }}
135+ < span class ="text-[11px] opacity-70 "> +{{ sub $count $max }}</ span >
136+ {{ end }}
137+ </ div >
138+ {{ end }}
139+
140+ <!-- Metadata section -->
141+ < div class ="flex flex-col gap-2 sm:flex-row sm:items-center justify-between pt-3 {{ if $hasMeta }}border-t border-zinc-100 dark:border-zinc-800{{ end }} ">
142+ < div class ="flex items-center gap-3 text-xs text-zinc-500 dark:text-zinc-500 flex-wrap ">
143+ {{ if $item.Params.authors }}
144+ < div class ="flex items-center gap-2 min-w-0 ">
145+ {{ with index ($item.GetTerms "authors") 0 }}
146+ < div class ="relative h-6 w-6 flex-shrink-0 ">
147+ {{ $avatar := (.Resources.ByType "image").GetMatch "*avatar*" }}
148+ {{ if $avatar }}
149+ {{ $avatar_48 := $avatar.Process "Fill 48x48 Center webp" }}
150+ < img alt ="avatar " class ="rounded-full object-cover " src ="{{$avatar_48.RelPermalink}} " width ="24 " height ="24 " loading ="lazy " decoding ="async " />
151+ {{ end }}
152+ </ div >
153+ < span class ="truncate max-w-[9rem] text-sm "> {{ .Page.LinkTitle }}</ span >
154+ {{ end }}
155+ </ div >
156+ < span class ="opacity-40 "> •</ span >
157+ {{ end }}
158+ {{ if $showDate }}
159+ < time class ="hidden sm:inline whitespace-nowrap " datetime ="{{ $item.Date.Format "2006-01-02 " }}">
160+ {{ $item.Date | time.Format (site.Params.locale.date_format | default "Jan 2, 2006") }}
161+ </ time >
162+ {{ end }}
163+ {{ if and $showDate $showReadTime }}
164+ < span class ="hidden sm:inline opacity-40 "> •</ span >
165+ {{ end }}
166+ {{ if $showReadTime }}
167+ {{ $content := $item.Content | plainify }}
168+ {{ $words := len (split $content " ") }}
169+ {{ $readTime := div $words 200 }}
170+ {{ if lt $readTime 1 }}{{ $readTime = 1 }}{{ end }}
171+ < span class ="hidden sm:inline whitespace-nowrap "> {{ $readTime }} {{ i18n "minute_read" }}</ span >
172+ {{ end }}
173+ </ div >
174+
175+ <!-- Read More with arrow - always visible for accessibility -->
176+ {{ if $showReadMore }}
177+ < a href ="{{ $link }} " {{ $target | safeHTMLAttr }} class ="flex items-center gap-2 text-blue-600 dark:text-blue-400 font-medium text-sm opacity-70 group-hover:opacity-100 transform group-hover:translate-x-1 transition-all duration-300 self-start sm:self-auto sm:ml-0 ">
178+ < span > {{ i18n "read_more" }}</ span >
179+ < svg class ="w-4 h-4 transition-transform group-hover:translate-x-1 " fill ="none " stroke ="currentColor " viewBox ="0 0 24 24 " aria-hidden ="true ">
180+ < path stroke-linecap ="round " stroke-linejoin ="round " stroke-width ="2 " d ="M17 8l4 4m0 0l-4 4m4-4H3 "/>
181+ </ svg >
182+ </ a >
183+ {{ end }}
184+ </ div >
185+
186+ <!-- Optional AI metadata -->
187+ {{ if $item.Params.ai_insights }}
188+ < div class ="flex items-center gap-2 pt-2 text-xs text-blue-500 bg-blue-50 dark:bg-blue-950/30 rounded-lg px-3 py-2 ">
189+ < svg class ="w-4 h-4 flex-shrink-0 " fill ="currentColor " viewBox ="0 0 20 20 ">
190+ < path d ="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z "/>
191+ </ svg >
192+ < span class ="font-medium "> {{ i18n "ai_insight" }}:</ span >
193+ < span class ="text-zinc-600 dark:text-zinc-400 "> {{ $item.Params.ai_insights }}</ span >
194+ </ div >
195+ {{ end }}
42196 </ div >
43- </ div >
44- </ a >
197+ </ div >
0 commit comments