feat: improve avatar quality and add customizable sizing options

Resolves #3123

## What Changed

**Avatar Quality Improvements:**
- Generate avatars at 2x resolution for crisp display on high-DPI screens
- Updated Hugo imaging config with quality: 90, lanczos resampling, and picture hint
- Improved WebP compression quality across all avatar locations

**New Customization Parameters:**
- Added design.avatar.size with 5 options: small (150px), medium (200px), large (320px), xl (400px), xxl (500px)
- Added design.avatar.shape with 3 options: circle (default), square, rounded
- Sizing aligned with 2025 social media standards (LinkedIn/X: 400px, Instagram: 320px, TikTok: 200px)

**Updated Templates:**
- Enhanced resume-biography.html and resume-biography-3.html with flexible parameter system
- Updated all starter templates (academic-cv, resume, blog, link-in-bio) with example configurations
- Improved avatar CSS for dynamic sizing control
This commit is contained in:
George Cushen committed 2025-08-18 02:43:55 +01:00
1 parent b90b1f245c
commit bec38c8759
19 files changed
+106 -34

No files matched your search

@@ -5,13 +5,11 @@
position: relative;
}
/* Custom avatar size on homepage */
/* Custom avatar size on homepage - size controlled by template params */
.avatar {
width: auto;
height: auto;
max-width: 225px;
max-height: 225px;
object-fit: cover;
}
@@ -97,8 +95,7 @@
.avatar-wrapper {
position: relative;
width: 150px; /* Match image size in about.avatar widget */
height: 150px;
/* width and height controlled by template params */
margin-left: auto;
margin-right: auto;
}
+6
View File
@@ -102,6 +102,12 @@ outputFormats:
baseName: backlinks
isPlainText: true
notAlternative: true
imaging:
# High quality defaults for crisp avatar display
quality: 90
resampleFilter: lanczos
anchor: smart
hint: picture
params:
locale:
date_format: 'Jan 2, 2006'
@@ -15,7 +15,20 @@
{{end}}
{{ $person := $person_page.Params }}
{{ $avatar := ($person_page.Resources.ByType "image").GetMatch "*avatar*" }}
{{ $avatar_shape := site.Params.features.avatar.shape | default "circle" }}
{{/* Avatar customization parameters */}}
{{ $avatar_size := $block.design.avatar.size | default "medium" }}
{{ $avatar_shape := $block.design.avatar.shape | default "circle" }}
{{/* Size mappings optimized for 2025 standards: display_size -> [display_px, generation_px] */}}
{{ $size_map := dict "small" (slice "150" "300") "medium" (slice "200" "400") "large" (slice "320" "640") "xl" (slice "400" "800") "xxl" (slice "500" "1000") }}
{{ $size_config := index $size_map $avatar_size | default (index $size_map "medium") }}
{{ $display_size := index $size_config 0 }}
{{ $generation_size := index $size_config 1 }}
{{/* Shape class mappings */}}
{{ $shape_classes := dict "circle" "rounded-full" "square" "rounded-none" "rounded" "rounded-lg" }}
{{ $shape_class := index $shape_classes $avatar_shape | default "rounded-full" }}
<div class="resume-biography px-3 flex flex-col md:flex-row justify-center gap-12">
@@ -42,10 +55,11 @@
<div id="profile" class="flex justify-center items-center flex-col">
{{ if $avatar }}
<div class="avatar-wrapper {{ if $img }}-mt-[105px]{{else}}mt-10{{end}}">
{{ $avatar_image := $avatar.Fill "150x150 Center" }}
<img class="avatar rounded-full bg-white dark:bg-gray-800 p-1" src="{{ $avatar_image.RelPermalink }}" alt="{{$person_page.Title}}"
width="{{ $avatar_image.Width }}" height="{{ $avatar_image.Height }}">
<div class="avatar-wrapper {{ if $img }}-mt-[105px]{{else}}mt-10{{end}}" style="width: {{$display_size}}px; height: {{$display_size}}px;">
{{ $avatar_image := $avatar.Fill (printf "%sx%s Center" $generation_size $generation_size) }}
<img class="avatar {{$shape_class}} bg-white dark:bg-gray-800 p-1" src="{{ $avatar_image.RelPermalink }}" alt="{{$person_page.Title}}"
width="{{$display_size}}" height="{{$display_size}}"
style="width: {{$display_size}}px; height: {{$display_size}}px; object-fit: cover;">
{{with $person.status.icon}}<span class="avatar-emoji">{{.|emojify}}</span>{{end}}
</div>
{{ end }}
@@ -15,7 +15,20 @@
{{end}}
{{ $person := $person_page.Params }}
{{ $avatar := ($person_page.Resources.ByType "image").GetMatch "*avatar*" }}
{{ $avatar_shape := site.Params.features.avatar.shape | default "circle" }}
{{/* Avatar customization parameters */}}
{{ $avatar_size := $block.design.avatar.size | default "medium" }}
{{ $avatar_shape := $block.design.avatar.shape | default "circle" }}
{{/* Size mappings optimized for 2025 standards: display_size -> [display_px, generation_px] */}}
{{ $size_map := dict "small" (slice "150" "300") "medium" (slice "200" "400") "large" (slice "320" "640") "xl" (slice "400" "800") "xxl" (slice "500" "1000") }}
{{ $size_config := index $size_map $avatar_size | default (index $size_map "medium") }}
{{ $display_size := index $size_config 0 }}
{{ $generation_size := index $size_config 1 }}
{{/* Shape class mappings */}}
{{ $shape_classes := dict "circle" "rounded-full" "square" "rounded-none" "rounded" "rounded-lg" }}
{{ $shape_class := index $shape_classes $avatar_shape | default "rounded-full" }}
{{ $img := "" }}
{{with $block.design.banner.filename}}
@@ -38,10 +51,11 @@
<div id="profile" class="resume-biography flex justify-center items-center flex-col dark:text-white">
{{ if $avatar }}
<div class="avatar-wrapper {{ if $img }}-mt-[105px]{{else}}mt-10{{end}}">
{{ $avatar_image := $avatar.Fill "150x150 Center" }}
<img class="avatar rounded-full bg-white dark:bg-gray-800 p-1" src="{{ $avatar_image.RelPermalink }}" alt="{{$person_page.Title}}"
width="{{ $avatar_image.Width }}" height="{{ $avatar_image.Height }}">
<div class="avatar-wrapper {{ if $img }}-mt-[105px]{{else}}mt-10{{end}}" style="width: {{$display_size}}px; height: {{$display_size}}px;">
{{ $avatar_image := $avatar.Fill (printf "%sx%s Center" $generation_size $generation_size) }}
<img class="avatar {{$shape_class}} bg-white dark:bg-gray-800 p-1" src="{{ $avatar_image.RelPermalink }}" alt="{{$person_page.Title}}"
width="{{$display_size}}" height="{{$display_size}}"
style="width: {{$display_size}}px; height: {{$display_size}}px; object-fit: cover;">
{{with $person.status.icon}}<span class="avatar-emoji">{{.|emojify}}</span>{{end}}
</div>
{{ end }}
@@ -15,7 +15,7 @@
<div class="flex pt-12 pb-4">
{{ if $avatar }}
{{ $authorImage := $avatar.Fill "192x192 Center" }}
{{ $authorImage := $avatar.Fill "384x384 Center" }}
<img
class="mr-4 h-24 w-24 {{if ne $avatar_shape "square"}}rounded-full{{end}}"
width="96"
@@ -149,7 +149,7 @@
{{ $twitter_card := "summary_large_image" }}
{{ if (and (eq .Kind "term") $avatar_image) }}
{{/* Match image processing in About widget to prevent generating more images than necessary. */}}
{{ $og_image = ($avatar_image.Fill "270x270 Center").Permalink }}
{{ $og_image = ($avatar_image.Fill "540x540 Center").Permalink }}
{{ $twitter_card = "summary" }}
{{ else if $featured_image }}
{{ $og_image = $featured_image.Permalink }}
@@ -70,7 +70,7 @@
<div class="relative h-5 w-5 flex-shrink-0">
{{ $avatar := (.Resources.ByType "image").GetMatch "*avatar*" }}
{{ if $avatar }}
{{ $authorImage := $avatar.Process "Fill 50x50 Center 95 webp" }}
{{ $authorImage := $avatar.Process "Fill 100x100 Center webp" }}
<img alt="avatar"
class="rounded-full object-cover"
data-nimg="fill"
+1 -1
View File
@@ -48,7 +48,7 @@
{{ end }}
<div class="group inline-flex items-center text-current gap-x-1.5 mx-1">
{{ with $avatar }}
{{ $avatar = $avatar.Process "Fill 50x50 Center webp" }}
{{ $avatar = $avatar.Process "Fill 100x100 Center webp" }}
<img src="{{ $avatar.RelPermalink }}" alt="{{ $author_page.Title }}" class="inline-block h-4 w-4 rounded-full border border-current" loading="lazy" />
{{ end }}
<div {{if $author_page.Params.website}}class="group-hover:underline"{{end}}>{{ $author_page.Title }}</div>