mirror of
https://github.com/tiennm99/HugoBlox-kit.git
synced 2026-10-15 08:16:39 +00:00
feat: standardize author profiles & add devcontainer
This commit is contained in:
1 parent
75234c306b
commit
f0eb4a7e9c
80 files changed
+1327
-1019
No files matched your search
@@ -46,7 +46,7 @@
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
border: 1px solid #a8e9e9;
|
||||
border: 1px solid var(--color-primary-200, #cbd5f5);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
@@ -54,5 +54,5 @@
|
||||
position: absolute;
|
||||
width: 60%;
|
||||
height: 10px;
|
||||
background-color: #3cb5b5;
|
||||
background-color: var(--color-primary-600, #2563eb);
|
||||
}
|
||||
@@ -7,7 +7,7 @@ Showcase your professional accomplishments, certifications, and awards with the
|
||||
## ✨ Key Features
|
||||
|
||||
- **Professional Cards**: Clean, shadowed card design for each award or certification
|
||||
- **Icon Integration**: Brand icons for certifying organizations and institutions
|
||||
- **Icon Integration**: Add per-award icons (e.g., trophy, badge) for instant visual cues
|
||||
- **Certificate Links**: Direct links to verify credentials with proper external link handling
|
||||
- **Date Formatting**: Customizable date display (month/year format by default)
|
||||
- **Markdown Support**: Rich text formatting for award titles and descriptions
|
||||
@@ -55,4 +55,3 @@ Showcase your professional accomplishments, certifications, and awards with the
|
||||
- **Brand Integration**: Certifying organization icons add visual interest and authenticity
|
||||
|
||||
Perfect for professionals, academics, consultants, and anyone who wants to showcase their achievements in a format that builds confidence and demonstrates expertise. Your accomplishments deserve professional presentation.
|
||||
|
||||
@@ -5,14 +5,9 @@
|
||||
{{/* Initialise */}}
|
||||
{{ $page := .wcPage }}
|
||||
{{ $block := .wcBlock }}
|
||||
{{ $author := $block.content.username | default "admin" }}
|
||||
|
||||
{{ $person_page_path := (printf "/authors/%s" $author) }}
|
||||
{{ $person_page := site.GetPage $person_page_path }}
|
||||
{{ if not $person_page }}
|
||||
{{ errorf "Could not find an author page at `%s`. Please check the value of `author` in your Skill block and create an associated author page if one does not already exist. See https://docs.hugoblox.com/page-builder/#about " $person_page_path }}
|
||||
{{end}}
|
||||
{{ $awards := $person_page.Params.awards }}
|
||||
{{ $author := $block.content.username | default "me" }}
|
||||
{{ $profile := partial "functions/get_author_profile" $author }}
|
||||
{{ $awards := $profile.awards }}
|
||||
|
||||
<div class="flex flex-col items-center max-w-prose mx-auto gap-3 justify-center">
|
||||
|
||||
@@ -24,25 +19,29 @@
|
||||
|
||||
<div class="w-full flex flex-col gap-6">
|
||||
{{ if $awards }}
|
||||
{{ range $idx, $key := sort $awards ".date_start" "desc" }}
|
||||
{{ range $idx, $key := sort $awards ".date" "desc" }}
|
||||
<div class="w-full p-6 bg-white border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700">
|
||||
{{- $icon := .icon }}
|
||||
{{ with $icon }}
|
||||
<div class="w-7 h-7 text-gray-500 dark:text-gray-400 mb-3">
|
||||
{{ partial "functions/get_icon" (dict "name" $icon "attributes" "style=\"height: 100%;\"") }}
|
||||
</div>
|
||||
{{ end}}
|
||||
{{with .url}}<a href={{.}} target="_blank" rel="noopener">{{end}}
|
||||
<h5 class="mb-2 text-2xl font-semibold tracking-tight text-gray-900 dark:text-white">{{.title | markdownify | emojify}}</h5>
|
||||
<h5 class="mb-2 text-2xl font-semibold tracking-tight text-gray-900 dark:text-white flex items-center gap-2">
|
||||
{{ with $icon }}
|
||||
<span class="inline-flex items-center justify-center w-7 h-7 text-primary-600 dark:text-primary-300">
|
||||
{{ partial "functions/get_icon" (dict "name" . "attributes" "class='w-6 h-6'") }}
|
||||
</span>
|
||||
{{ end }}
|
||||
<span>{{.title | markdownify | emojify}}</span>
|
||||
</h5>
|
||||
{{with .url}}</a>{{end}}
|
||||
|
||||
<div class="block mb-3 text-sm font-normal leading-none text-gray-500 dark:text-gray-300">
|
||||
{{.awarder}} ∙
|
||||
{{ if .date}}
|
||||
{{ .awarder }} ∙
|
||||
{{ if .date }}
|
||||
{{ (time .date) | time.Format ($block.design.date_format | default "January 2006") }}
|
||||
{{else}}
|
||||
{{ else if .date_start }}
|
||||
{{ (time .date_start) | time.Format ($block.design.date_format | default "January 2006") }}
|
||||
{{ else }}
|
||||
{{ i18n "present" | default "Present" }}
|
||||
{{end}}
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
{{with .summary}}
|
||||
|
||||
@@ -6,6 +6,11 @@ Present a complete professional picture with the Resume Biography 3 block - an e
|
||||
|
||||
## 🛠️ Customization
|
||||
|
||||
- **Name sizing**: Adjust the profile heading size to accommodate long names using `design.name.size`:
|
||||
- `compact`: space-friendly for long names
|
||||
- `balanced` (default): matches the demo style
|
||||
- `display`: extra-large for short names
|
||||
|
||||
- **Section heading overrides**: Customize any of the three main section headings using `content.headings`:
|
||||
- `about`: Biography section heading (default: "Professional Summary" from i18n `about_me`)
|
||||
- `education`: Education section heading (default: from i18n `education`)
|
||||
@@ -20,6 +25,9 @@ content:
|
||||
about: Research Profile
|
||||
education: Academic Background
|
||||
interests: Research Areas
|
||||
design:
|
||||
name:
|
||||
size: compact
|
||||
```
|
||||
|
||||
## ✨ Key Features
|
||||
|
||||
@@ -6,20 +6,23 @@
|
||||
{{ $page := .wcPage }}
|
||||
{{ $block := .wcBlock }}
|
||||
|
||||
{{ $author := $block.content.username | default "admin" }}
|
||||
|
||||
{{ $person_page_path := (printf "/authors/%s" $author) }}
|
||||
{{ $person_page := site.GetPage $person_page_path }}
|
||||
{{ if not $person_page }}
|
||||
{{ errorf "Could not find an author page at `%s`. Please check the value of `author` in your About widget and create an associated author page if one does not already exist. See https://docs.hugoblox.com/page-builder/#about " $person_page_path }}
|
||||
{{end}}
|
||||
{{ $person := $person_page.Params }}
|
||||
{{ $avatar := ($person_page.Resources.ByType "image").GetMatch "*avatar*" }}
|
||||
{{ $author := $block.content.username | default "me" }}
|
||||
{{ $profile := partial "functions/get_author_profile" $author }}
|
||||
{{ $avatar := $profile.avatar }}
|
||||
|
||||
{{/* Avatar customization parameters */}}
|
||||
{{ $avatar_size := $block.design.avatar.size | default "large" }}
|
||||
{{ $avatar_shape := $block.design.avatar.shape | default "circle" }}
|
||||
|
||||
{{/* Profile name typography */}}
|
||||
{{ $name_size := $block.design.name.size | default "balanced" }}
|
||||
{{ $name_class_map := dict
|
||||
"compact" "text-3xl sm:text-4xl lg:text-5xl"
|
||||
"balanced" "text-4xl sm:text-5xl lg:text-6xl"
|
||||
"display" "text-5xl sm:text-6xl lg:text-7xl"
|
||||
}}
|
||||
{{ $name_class := index $name_class_map $name_size | default (index $name_class_map "balanced") }}
|
||||
|
||||
{{/* 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 "large") }}
|
||||
@@ -68,40 +71,33 @@
|
||||
|
||||
{{/* Avatar */}}
|
||||
{{ if $avatar }}
|
||||
<div class="avatar-wrapper {{ if $img }}-mt-24{{else}}mb-4{{end}}" style="width: {{$display_size}}px; height: {{$display_size}}px;">
|
||||
<div class="avatar-wrapper {{ if $img }}-mt-24{{else}}mb-4{{end}}">
|
||||
{{ $avatar_image := $avatar.Fill (printf "%sx%s Center" $generation_size $generation_size) }}
|
||||
<img class="avatar {{$shape_class}} border-4 border-white shadow-2xl"
|
||||
src="{{ $avatar_image.RelPermalink }}"
|
||||
alt="{{$person_page.Title}}"
|
||||
alt="{{$profile.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}}
|
||||
style="object-fit: cover;">
|
||||
{{with $profile.status.icon}}<span class="avatar-emoji">{{.|emojify}}</span>{{end}}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Name and Role - Centered */}}
|
||||
<div class="space-y-3">
|
||||
<h1 class="text-5xl lg:text-6xl font-black text-gray-900 dark:text-white leading-tight">
|
||||
{{- if $person.name_pronunciation -}}
|
||||
<ruby>
|
||||
<rb>{{ $person_page.Title }}</rb>
|
||||
<rt class="text-sm text-gray-500 dark:text-gray-400">{{ $person.name_pronunciation }}</rt>
|
||||
</ruby>
|
||||
{{- else -}}
|
||||
{{- $person_page.Title -}}
|
||||
{{- end -}}
|
||||
<h1 class="{{ $name_class }} font-black text-gray-900 dark:text-white leading-tight">
|
||||
{{ $profile.title }}
|
||||
</h1>
|
||||
|
||||
{{- with $person.pronouns -}}
|
||||
{{- with $profile.pronouns -}}
|
||||
<p class="text-lg text-gray-600 dark:text-gray-400">({{ . }})</p>
|
||||
{{- end -}}
|
||||
|
||||
{{ with $person.role }}
|
||||
{{ with $profile.role }}
|
||||
<p class="text-2xl font-semibold text-primary-600 dark:text-primary-400">{{ . | markdownify | emojify }}</p>
|
||||
{{ end }}
|
||||
|
||||
{{ range $person.organizations }}
|
||||
{{ range $profile.affiliations }}
|
||||
<p class="text-lg text-gray-700 dark:text-gray-300">
|
||||
{{ with .url }}<a href="{{ . }}" target="_blank" rel="noopener" class="hover:text-primary-600 dark:hover:text-primary-400 transition-colors">{{ end }}
|
||||
{{ .name }}
|
||||
@@ -112,12 +108,7 @@
|
||||
|
||||
{{/* Social Icons */}}
|
||||
<div class="flex flex-wrap justify-center gap-4">
|
||||
{{ range $person.profiles }}
|
||||
{{ $pack := or .icon_pack "fas" }}
|
||||
{{ $pack_prefix := $pack }}
|
||||
{{ if in (slice "fab" "fas" "far" "fal") $pack }}
|
||||
{{ $pack_prefix = "fa" }}
|
||||
{{ end }}
|
||||
{{ range $profile.links }}
|
||||
{{ $link := .url | default .link }}
|
||||
{{ $scheme := (urls.Parse $link).Scheme }}
|
||||
{{ $target := "" }}
|
||||
@@ -143,7 +134,8 @@
|
||||
<div class="md:col-span-8">
|
||||
|
||||
{{/* Bio Section */}}
|
||||
{{ with ($block.content.text | emojify | $page.RenderString) | default $person_page.Content }}
|
||||
{{ $bio_content := ($block.content.text | emojify | $page.RenderString) | default $profile.bio }}
|
||||
{{ with $bio_content }}
|
||||
<div class="mb-12">
|
||||
<div class="flex items-center gap-4 mb-8">
|
||||
<div class="flex-shrink-0 w-12 h-12 bg-primary-100 dark:bg-primary-900/50 rounded-full flex items-center justify-center">
|
||||
@@ -174,7 +166,7 @@
|
||||
{{ end }}
|
||||
|
||||
{{/* Education Section */}}
|
||||
{{ with $person.education }}
|
||||
{{ with $profile.education }}
|
||||
<div class="mb-16">
|
||||
<div class="flex items-center gap-4 mb-8">
|
||||
<div class="flex-shrink-0 w-12 h-12 bg-primary-100 dark:bg-primary-900/50 rounded-full flex items-center justify-center">
|
||||
@@ -195,9 +187,12 @@
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="flex-1 flex-grow">
|
||||
{{ $degree := .degree | default .area }}
|
||||
{{ $year := .year | default (cond (and .start .end) (printf "%s - %s" .start .end) .start) }}
|
||||
<p class="text-xl font-bold text-gray-900 dark:text-white group-hover:text-primary-600 dark:group-hover:text-primary-400 transition-colors">
|
||||
{{ .area }}{{ with .year }}, {{ . }}{{ end }}
|
||||
{{ $degree }}
|
||||
</p>
|
||||
{{ with $year }}<p class="text-gray-600 dark:text-gray-400 font-medium mt-1">{{ . }}</p>{{ end }}
|
||||
<p class="text-gray-600 dark:text-gray-400 font-medium mt-1">{{ .institution }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -208,7 +203,7 @@
|
||||
{{ end }}
|
||||
|
||||
{{/* Interests Section */}}
|
||||
{{ with $person.interests }}
|
||||
{{ with $profile.interests }}
|
||||
<div>
|
||||
<div class="flex items-center gap-4 mb-8">
|
||||
<div class="flex-shrink-0 w-12 h-12 bg-primary-100 dark:bg-primary-900/50 rounded-full flex items-center justify-center">
|
||||
@@ -232,4 +227,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -106,6 +106,18 @@
|
||||
"description": "Custom CSS styles for biography text"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"type": "object",
|
||||
"description": "Profile name typography controls",
|
||||
"properties": {
|
||||
"size": {
|
||||
"type": "string",
|
||||
"enum": ["compact", "balanced", "display"],
|
||||
"description": "Heading size for the profile name to suit different name lengths",
|
||||
"default": "balanced"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,9 @@
|
||||
{{ $page := .wcPage }}
|
||||
{{ $block := .wcBlock }}
|
||||
|
||||
{{ $author := $block.content.username | default "admin" }}
|
||||
|
||||
{{ $person_page_path := (printf "/authors/%s" $author) }}
|
||||
{{ $person_page := site.GetPage $person_page_path }}
|
||||
{{ if not $person_page }}
|
||||
{{ errorf "Could not find an author page at `%s`. Please check the value of `author` in your About widget and create an associated author page if one does not already exist. See https://docs.hugoblox.com/page-builder/#about " $person_page_path }}
|
||||
{{end}}
|
||||
{{ $person := $person_page.Params }}
|
||||
{{ $avatar := ($person_page.Resources.ByType "image").GetMatch "*avatar*" }}
|
||||
{{ $author := $block.content.username | default "me" }}
|
||||
{{ $profile := partial "functions/get_author_profile" $author }}
|
||||
{{ $avatar := $profile.avatar }}
|
||||
|
||||
{{/* Avatar customization parameters */}}
|
||||
{{ $avatar_size := $block.design.avatar.size | default "medium" }}
|
||||
@@ -76,30 +70,23 @@
|
||||
|
||||
<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}}"
|
||||
<img class="avatar {{$shape_class}} bg-white dark:bg-gray-800 p-1" src="{{ $avatar_image.RelPermalink }}" alt="{{$profile.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}}
|
||||
{{with $profile.status.icon}}<span class="avatar-emoji">{{.|emojify}}</span>{{end}}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<div class="text-3xl font-bold mb-2 mt-6">
|
||||
{{- if $person.name_pronunciation -}}
|
||||
<ruby>
|
||||
<rb>{{ $person_page.Title }}</rb>
|
||||
<rt>{{ $person.name_pronunciation }}</rt>
|
||||
</ruby>
|
||||
{{- else -}}
|
||||
{{- $person_page.Title -}}
|
||||
{{- end -}}
|
||||
{{- $profile.title -}}
|
||||
</div>
|
||||
{{- with $person.pronouns -}}
|
||||
{{- with $profile.pronouns -}}
|
||||
<div class="text-lg font-normal text-gray-600 dark:text-gray-400 -mt-1 mb-2">({{ . }})</div>
|
||||
{{- end -}}
|
||||
|
||||
{{ with $person.role }}<h3 class="font-semibold mb-1">{{ . | markdownify | emojify }}</h3>{{ end }}
|
||||
{{ with $profile.role }}<h3 class="font-semibold mb-1">{{ . | markdownify | emojify }}</h3>{{ end }}
|
||||
|
||||
{{ range $person.organizations }}
|
||||
{{ range $profile.affiliations }}
|
||||
<div class="mb-2">
|
||||
{{ with .url }}<a href="{{ . }}" target="_blank" rel="noopener">{{ end }}
|
||||
<div>{{ .name }}</div>
|
||||
@@ -107,13 +94,9 @@
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ if $profile.links }}
|
||||
<ul class="network-icon dark:text-zinc-100">
|
||||
{{ range $person.profiles }}
|
||||
{{ $pack := or .icon_pack "fas" }}
|
||||
{{ $pack_prefix := $pack }}
|
||||
{{ if in (slice "fab" "fas" "far" "fal") $pack }}
|
||||
{{ $pack_prefix = "fa" }}
|
||||
{{ end }}
|
||||
{{ range $profile.links }}
|
||||
{{ $link := .url | default .link }}
|
||||
{{ $scheme := (urls.Parse $link).Scheme }}
|
||||
{{ $target := "" }}
|
||||
@@ -131,8 +114,10 @@
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
{{ end }}
|
||||
|
||||
{{ with ($block.content.text | emojify | $page.RenderString) | default $person_page.Content }}
|
||||
{{ $bio_content := ($block.content.text | emojify | $page.RenderString) | default $profile.bio }}
|
||||
{{ with $bio_content }}
|
||||
<div class="pt-2 d-flex justify-content-center prose prose-slate lg:prose-xl dark:prose-invert">
|
||||
<div class="bio-text" {{ with $block.design.biography.style }}{{ (printf "style=\"%s\"" .) | safe.HTMLAttr }}{{end}}>
|
||||
{{ . }}
|
||||
|
||||
@@ -7,13 +7,8 @@
|
||||
{{ $block := .wcBlock }}
|
||||
{{ $text := $block.content.text | emojify | $page.RenderString }}
|
||||
|
||||
{{ $author := $block.content.username | default "admin" }}
|
||||
{{ $person_page_path := (printf "/authors/%s" $author) }}
|
||||
{{ $person_page := site.GetPage $person_page_path }}
|
||||
{{ if not $person_page }}
|
||||
{{ errorf "Could not find an author page at `%s`. Please check the value of `author` in your About widget and create an associated author page if one does not already exist. See https://docs.hugoblox.com/page-builder/#about " $person_page_path }}
|
||||
{{end}}
|
||||
{{ $person := $person_page.Params }}
|
||||
{{ $author := $block.content.username | default "me" }}
|
||||
{{ $profile := partial "functions/get_author_profile" $author }}
|
||||
{{ $is_education_first := $block.design.is_education_first | default false }}
|
||||
|
||||
<div class="flex flex-col items-center mx-auto w-full max-w-prose justify-center">
|
||||
@@ -26,11 +21,12 @@
|
||||
<!-- Need Gap >= 3 when flex-row due to negative margin on cols (-start-3) -->
|
||||
<div class="flex flex-col lg:gap-x-6 w-full px-6 sm:px-0 {{ if $is_education_first }}flex-col-reverse{{end}}">
|
||||
|
||||
{{ if $person.work }}
|
||||
{{ $experience := or $profile.experience $profile.work }}
|
||||
{{ if $experience }}
|
||||
<div class="w-full">
|
||||
<h3 class="mb-6 text-3xl font-bold text-gray-900 dark:text-white text-center">{{ i18n "experience" | default "Experience" }}</h3>
|
||||
<ol class="relative w-full border-s border-gray-200 dark:border-gray-700">
|
||||
{{ range $idx, $key := $person.work }}
|
||||
{{ range $idx, $key := $experience }}
|
||||
<li class="mb-10 ms-6">
|
||||
<span class="absolute flex items-center justify-center w-6 h-6 bg-primary-100 rounded-full -start-3 ring-8 ring-white dark:ring-gray-900 dark:bg-primary-900">
|
||||
{{ if .icon }}
|
||||
@@ -39,27 +35,31 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 256 256" class="w-5 h-5 text-primary-800 dark:text-primary-300"><path d="M216,56H176V48a24,24,0,0,0-24-24H104A24,24,0,0,0,80,48v8H40A16,16,0,0,0,24,72V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V72A16,16,0,0,0,216,56ZM96,48a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8v8H96ZM216,72v41.61A184,184,0,0,1,128,136a184.07,184.07,0,0,1-88-22.38V72Zm0,128H40V131.64A200.19,200.19,0,0,0,128,152a200.25,200.25,0,0,0,88-20.37V200ZM104,112a8,8,0,0,1,8-8h32a8,8,0,0,1,0,16H112A8,8,0,0,1,104,112Z"></path></svg>
|
||||
{{end}}
|
||||
</span>
|
||||
<h3 class="flex items-center mb-1 text-lg font-semibold text-gray-900 dark:text-white">{{.position}}
|
||||
<h3 class="flex items-center mb-1 text-lg font-semibold text-gray-900 dark:text-white">{{ .role | default .position }}
|
||||
<!-- <span class="bg-blue-100 text-blue-800 text-sm font-medium me-2 px-2.5 py-0.5 rounded dark:bg-blue-900 dark:text-blue-300 ms-3">Latest</span>-->
|
||||
</h3>
|
||||
<span class="block mb-2 text-sm font-normal leading-none text-gray-500 dark:text-gray-300">{{.company_name}}</span>
|
||||
<span class="block mb-2 text-sm font-normal leading-none text-gray-500 dark:text-gray-300">{{ .org | default .company_name }}</span>
|
||||
<time class="block mb-3 text-sm font-normal leading-none text-gray-500 dark:text-gray-300">
|
||||
{{ if .date_start}}
|
||||
{{ (time .date_start) | time.Format ($block.design.date_format | default "January 2006") }} –
|
||||
{{ $start := or .start .date_start }}
|
||||
{{ $end := or .end .date_end }}
|
||||
{{ if $start }}
|
||||
{{ (time $start) | time.Format ($block.design.date_format | default "January 2006") }} –
|
||||
{{ end }}
|
||||
{{ if .date_end}}
|
||||
{{ (time .date_end) | time.Format ($block.design.date_format | default "January 2006") }}
|
||||
{{else}}
|
||||
{{ if $end }}
|
||||
{{ (time $end) | time.Format ($block.design.date_format | default "January 2006") }}
|
||||
{{ else }}
|
||||
{{ i18n "present" | default "Present" }}
|
||||
{{end}}
|
||||
</time>
|
||||
<div class="mb-4 text-base font-normal text-gray-500 dark:text-gray-300 prose max-w-none prose-slate dark:prose-invert">{{.summary | $page.RenderString}}</div>
|
||||
|
||||
{{ with .button }}
|
||||
<a href="{{.url}}" target="_blank" rel="noopener" class="inline-flex items-center px-4 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg transition-colors hover:bg-gray-100 hover:text-gray-900 focus:z-10 focus:ring-4 focus:outline-none focus:ring-gray-200 focus:text-gray-900 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 dark:focus:text-white dark:focus:ring-gray-700"><svg class="w-3.5 h-3.5 me-2.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M14.707 7.793a1 1 0 0 0-1.414 0L11 10.086V1.5a1 1 0 0 0-2 0v8.586L6.707 7.793a1 1 0 1 0-1.414 1.414l4 4a1 1 0 0 0 1.416 0l4-4a1 1 0 0 0-.002-1.414Z"/>
|
||||
<path d="M18 12h-2.55l-2.975 2.975a3.5 3.5 0 0 1-4.95 0L4.55 12H2a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2Zm-3 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"/>
|
||||
</svg> {{.text}}</a>
|
||||
<a href="{{.url}}" target="_blank" rel="noopener" class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg transition-colors hover:bg-gray-100 hover:text-gray-900 focus:z-10 focus:ring-4 focus:outline-none focus:ring-gray-200 focus:text-gray-900 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 dark:focus:text-white dark:focus:ring-gray-700">
|
||||
{{ with .icon }}
|
||||
{{ partial "functions/get_icon" (dict "name" . "attributes" "class='w-4 h-4'") }}
|
||||
{{ end }}
|
||||
{{ .text | default (i18n "learn_more" | default "Learn more") }}
|
||||
</a>
|
||||
{{ end }}
|
||||
|
||||
</li>
|
||||
@@ -68,11 +68,11 @@
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ if $person.education }}
|
||||
{{ if $profile.education }}
|
||||
<div class="w-full">
|
||||
<h3 class="mb-6 text-3xl font-bold text-gray-900 dark:text-white text-center">{{ i18n "education" }}</h3>
|
||||
<ol class="relative w-full border-s border-gray-200 dark:border-gray-700">
|
||||
{{ range $idx, $key := $person.education }}
|
||||
{{ range $idx, $key := $profile.education }}
|
||||
<li class="mb-10 ms-6">
|
||||
<span class="absolute flex items-center justify-center w-6 h-6 bg-primary-100 rounded-full -start-3 ring-8 ring-white dark:ring-gray-900 dark:bg-primary-900">
|
||||
{{ if .icon }}
|
||||
@@ -81,27 +81,31 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 256 256" class="w-5 h-5 text-primary-800 dark:text-primary-300"><path d="M251.76,88.94l-120-64a8,8,0,0,0-7.52,0l-120,64a8,8,0,0,0,0,14.12L32,117.87v48.42a15.91,15.91,0,0,0,4.06,10.65C49.16,191.53,78.51,216,128,216a130,130,0,0,0,48-8.76V240a8,8,0,0,0,16,0V199.51a115.63,115.63,0,0,0,27.94-22.57A15.91,15.91,0,0,0,224,166.29V117.87l27.76-14.81a8,8,0,0,0,0-14.12ZM128,200c-43.27,0-68.72-21.14-80-33.71V126.4l76.24,40.66a8,8,0,0,0,7.52,0L176,143.47v46.34C163.4,195.69,147.52,200,128,200Zm80-33.75a97.83,97.83,0,0,1-16,14.25V134.93l16-8.53ZM188,118.94l-.22-.13-56-29.87a8,8,0,0,0-7.52,14.12L171,128l-43,22.93L25,96,128,41.07,231,96Z"></path></svg>
|
||||
{{end}}
|
||||
</span>
|
||||
<h3 class="flex items-center mb-1 text-lg font-semibold text-gray-900 dark:text-white">{{.area}}
|
||||
<h3 class="flex items-center mb-1 text-lg font-semibold text-gray-900 dark:text-white">{{ .degree | default .area }}
|
||||
<!-- <span class="bg-blue-100 text-blue-800 text-sm font-medium me-2 px-2.5 py-0.5 rounded dark:bg-blue-900 dark:text-blue-300 ms-3">Latest</span>-->
|
||||
</h3>
|
||||
<span class="block mb-2 text-sm font-normal leading-none text-gray-500 dark:text-gray-300">{{.institution}}</span>
|
||||
<span class="block mb-2 text-sm font-normal leading-none text-gray-500 dark:text-gray-300">{{ .institution }}</span>
|
||||
<time class="block mb-3 text-sm font-normal leading-none text-gray-500 dark:text-gray-300">
|
||||
{{ if .date_start }}
|
||||
{{ (time .date_start) | time.Format ($block.design.date_format | default "January 2006") }} –
|
||||
{{ $start := or .start .date_start }}
|
||||
{{ $end := or .end .date_end }}
|
||||
{{ if $start }}
|
||||
{{ (time $start) | time.Format ($block.design.date_format | default "January 2006") }} –
|
||||
{{ end }}
|
||||
{{ if .date_end}}
|
||||
{{ (time .date_end) | time.Format ($block.design.date_format | default "January 2006") }}
|
||||
{{else}}
|
||||
{{ if $end }}
|
||||
{{ (time $end) | time.Format ($block.design.date_format | default "January 2006") }}
|
||||
{{ else }}
|
||||
{{ i18n "present" | default "Present" }}
|
||||
{{end}}
|
||||
</time>
|
||||
<div class="mb-4 text-base font-normal text-gray-500 dark:text-gray-300 prose max-w-none prose-slate dark:prose-invert">{{.summary | $page.RenderString}}</div>
|
||||
|
||||
{{ with .button }}
|
||||
<a href="{{.url}}" target="_blank" rel="noopener" class="inline-flex items-center px-4 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg transition-colors hover:bg-gray-100 hover:text-gray-900 focus:z-10 focus:ring-4 focus:outline-none focus:ring-gray-200 focus:text-gray-900 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 dark:focus:text-white dark:focus:ring-gray-700"><svg class="w-3.5 h-3.5 me-2.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M14.707 7.793a1 1 0 0 0-1.414 0L11 10.086V1.5a1 1 0 0 0-2 0v8.586L6.707 7.793a1 1 0 1 0-1.414 1.414l4 4a1 1 0 0 0 1.416 0l4-4a1 1 0 0 0-.002-1.414Z"/>
|
||||
<path d="M18 12h-2.55l-2.975 2.975a3.5 3.5 0 0 1-4.95 0L4.55 12H2a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2Zm-3 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"/>
|
||||
</svg> {{.text}}</a>
|
||||
<a href="{{.url}}" target="_blank" rel="noopener" class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg transition-colors hover:bg-gray-100 hover:text-gray-900 focus:z-10 focus:ring-4 focus:outline-none focus:ring-gray-200 focus:text-gray-900 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 dark:focus:text-white dark:focus:ring-gray-700">
|
||||
{{ with .icon }}
|
||||
{{ partial "functions/get_icon" (dict "name" . "attributes" "class='w-4 h-4'") }}
|
||||
{{ end }}
|
||||
{{ .text | default (i18n "learn_more" | default "Learn more") }}
|
||||
</a>
|
||||
{{ end }}
|
||||
|
||||
</li>
|
||||
|
||||
@@ -5,14 +5,9 @@
|
||||
{{/* Initialise */}}
|
||||
{{ $page := .wcPage }}
|
||||
{{ $block := .wcBlock }}
|
||||
{{ $author := $block.content.username | default "admin" }}
|
||||
|
||||
{{ $person_page_path := (printf "/authors/%s" $author) }}
|
||||
{{ $person_page := site.GetPage $person_page_path }}
|
||||
{{ if not $person_page }}
|
||||
{{ errorf "Could not find an author page at `%s`. Please check the value of `author` in your Skill block and create an associated author page if one does not already exist. See https://docs.hugoblox.com/page-builder/#about " $person_page_path }}
|
||||
{{end}}
|
||||
{{ $langs := $person_page.Params.languages }}
|
||||
{{ $author := $block.content.username | default "me" }}
|
||||
{{ $profile := partial "functions/get_author_profile" $author }}
|
||||
{{ $langs := $profile.languages }}
|
||||
|
||||
<div class="flex flex-col items-center max-w-prose mx-auto gap-3 justify-center">
|
||||
|
||||
@@ -22,8 +17,24 @@
|
||||
|
||||
{{ with $block.content.text }}<p>{{ . | markdownify | emojify }}</p>{{ end }}
|
||||
|
||||
<div class="flex items-center flex-col lg:flex-row gap-x-8">
|
||||
<div class="flex items-center flex-col lg:flex-row gap-y-10 lg:gap-y-0 lg:gap-x-8">
|
||||
{{ range $langs }}
|
||||
{{ $percent := 100.0 }}
|
||||
{{ $levelText := .level }}
|
||||
{{ $label := .label }}
|
||||
|
||||
{{ $levelStr := printf "%v" ($levelText | default "") }}
|
||||
{{ $numericLevel := gt (len (findRE "^[0-9]+(?:\\.[0-9]+)?$" $levelStr)) 0 }}
|
||||
|
||||
{{ if $numericLevel }}
|
||||
{{ $percent = mul (float $levelStr) 20 }}
|
||||
{{ else if .percent }}
|
||||
{{ $percent = float .percent }}
|
||||
{{ end }}
|
||||
|
||||
{{ if gt $percent 100 }}{{ $percent = 100 }}{{ end }}
|
||||
{{ if lt $percent 0 }}{{ $percent = 0 }}{{ end }}
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="flex items-center justify-center w-28 h-28">
|
||||
<svg class="transform -rotate-90" viewBox="0 0 288 288">
|
||||
@@ -31,11 +42,16 @@
|
||||
class="text-gray-200 dark:text-gray-700" />
|
||||
<circle cx="145" cy="145" r="120" stroke="currentColor" stroke-width="30" fill="transparent"
|
||||
class="text-primary-600 dark:text-primary-300"
|
||||
style="stroke-dasharray: calc(2 * 22 / 7 * 120); stroke-dashoffset: calc((2 * 22 / 7 * 120) - ((2 * 22 / 7 * 120) * {{.percent}}/100))" />
|
||||
style="stroke-dasharray: calc(2 * 22 / 7 * 120); stroke-dashoffset: calc((2 * 22 / 7 * 120) - ((2 * 22 / 7 * 120) * {{$percent}}/100))" />
|
||||
</svg>
|
||||
<span class="absolute text-3xl">{{.percent}}%</span>
|
||||
<span class="absolute text-3xl">{{ $percent }}%</span>
|
||||
</div>
|
||||
<span class="text-1xl">{{.name}}</span>
|
||||
{{ if $label }}
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400 mt-1">{{ $label }}</span>
|
||||
{{ else if and $levelText (not $numericLevel) }}
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400 mt-1">{{ . }}</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ Present your technical and professional competencies with the Resume Skills bloc
|
||||
- **Icon Integration**: Beautiful icons for each skill that enhance visual recognition
|
||||
- **Flexible Layout**: Side-by-side categories on larger screens, stacked on mobile
|
||||
- **Custom Colors**: Configurable progress bar colors to match your brand
|
||||
- **Toggle Control**: Option to show or hide percentage indicators
|
||||
- **Automatic Bars**: Bars appear when a skill has a level (1–5)
|
||||
- **Markdown Support**: Rich text formatting for skill names and descriptions
|
||||
|
||||
## 🎯 Perfect For
|
||||
@@ -64,4 +64,3 @@ Present your technical and professional competencies with the Resume Skills bloc
|
||||
Perfect for technical professionals, creative experts, and anyone who wants to present their skills in an organized, visually appealing format that helps employers quickly understand their capabilities and expertise levels.
|
||||
|
||||
Turn your skill set into a competitive advantage with professional presentation that clearly communicates your value proposition.
|
||||
|
||||
@@ -5,15 +5,9 @@
|
||||
{{/* Initialise */}}
|
||||
{{ $page := .wcPage }}
|
||||
{{ $block := .wcBlock }}
|
||||
{{ $author := $block.content.username | default "admin" }}
|
||||
|
||||
{{ $person_page_path := (printf "/authors/%s" $author) }}
|
||||
{{ $person_page := site.GetPage $person_page_path }}
|
||||
{{ if not $person_page }}
|
||||
{{ errorf "Could not find an author page at `%s`. Please check the value of `author` in your Skill block and create an associated author page if one does not already exist. See https://docs.hugoblox.com/page-builder/#about " $person_page_path }}
|
||||
{{end}}
|
||||
{{ $skills := $person_page.Params.skills }}
|
||||
{{ $show_show_skill_percentage := $block.design.show_skill_percentage | default true }}
|
||||
{{ $author := $block.content.username | default "me" }}
|
||||
{{ $profile := partial "functions/get_author_profile" $author }}
|
||||
{{ $skills := $profile.skills | default (slice) }}
|
||||
|
||||
<div class="flex flex-col items-center max-w-prose mx-auto gap-3 justify-center">
|
||||
|
||||
@@ -35,12 +29,7 @@
|
||||
{{ .name | markdownify | emojify }}
|
||||
{{ with .description }}<p>{{ . | markdownify | emojify }}</p>{{ end }}
|
||||
</div>
|
||||
{{ range .items }}
|
||||
{{ $pack := or .icon_pack "fas" }}
|
||||
{{ $pack_prefix := $pack }}
|
||||
{{ if in (slice "fab" "fas" "far" "fal") $pack }}
|
||||
{{ $pack_prefix = "fa" }}
|
||||
{{ end }}
|
||||
{{ range (.items | default (slice)) }}
|
||||
|
||||
<div class="skills-content">
|
||||
|
||||
@@ -50,13 +39,26 @@
|
||||
</span>
|
||||
{{ end }}
|
||||
|
||||
{{ $label := .label | default .name }}
|
||||
<span class="skills-name text-gray-700 dark:text-gray-300">
|
||||
{{ .name | markdownify | emojify }}
|
||||
{{ $label | markdownify | emojify }}
|
||||
{{ with .description }}<p class="skills-description">{{ . | markdownify | emojify }}</p>{{ end }}
|
||||
</span>
|
||||
{{ if .percent | and $show_show_skill_percentage }}
|
||||
{{/* Numeric level (1-5) only. */}}
|
||||
{{ $level := 0.0 }}
|
||||
{{ if isset . "level" }}
|
||||
{{ $raw := .level }}
|
||||
{{ $type := printf "%T" $raw }}
|
||||
{{ if or (eq $type "int") (eq $type "int64") (eq $type "int32") (eq $type "int16") (eq $type "int8") (eq $type "uint") (eq $type "uint64") (eq $type "uint32") (eq $type "uint16") (eq $type "uint8") (eq $type "float64") (eq $type "float32") }}
|
||||
{{ $level = float $raw }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ if gt $level 5 }}{{ $level = 5 }}{{ end }}
|
||||
{{ if lt $level 0 }}{{ $level = 0 }}{{ end }}
|
||||
{{ if gt $level 0 }}
|
||||
{{ $levelPercent := mul $level 20 }}
|
||||
<div class="skills-wrapper" {{with $color_border}}{{ (printf "style=\"border-color: %s\"" .) | safeHTMLAttr }}{{end}}>
|
||||
<div class="skills-percent" style="width: {{ (printf "%s" (cast.ToString (.percent | default 100))) | safeCSS }}%; {{with $color}}{{ (printf "background-color: %s" .) | safeCSS }}{{end}}"></div>
|
||||
<div class="skills-percent" style="width: {{ printf "%.0f" $levelPercent }}%; {{with $color}}{{ (printf "background-color: %s" .) | safeCSS }}{{end}}"></div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
@@ -64,4 +66,3 @@
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
@@ -39,13 +39,7 @@
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"show_skill_percentage": {
|
||||
"type": "boolean",
|
||||
"description": "Display skill percentage bars",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
"properties": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -37,119 +37,180 @@
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
{{/* Team Grid */}}
|
||||
<div class="grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
|
||||
{{ range $block.content.user_groups }}
|
||||
{{ $group_name := . }}
|
||||
{{ $query := where (where site.Pages "Section" "authors") ".Params.user_groups" "intersect" (slice .) }}
|
||||
|
||||
{{/* Sort */}}
|
||||
{{ $sort_by := $block.content.sort_by | default "Params.last_name" }}
|
||||
{{ $sort_by = partial "functions/get_sort_by_parameter" $sort_by }}
|
||||
{{ $sort_ascending := $block.content.sort_ascending | default true }}
|
||||
{{ $sort_order := cond $sort_ascending "asc" "desc" }}
|
||||
{{ $query = sort $query $sort_by $sort_order }}
|
||||
|
||||
{{/* Group Header (if multiple groups) */}}
|
||||
{{ if and $query (gt (len $block.content.user_groups) 1) }}
|
||||
<div class="col-span-full">
|
||||
{{/* Build profiles from data/authors */}}
|
||||
{{ $allProfiles := slice }}
|
||||
{{ range $slug, $_ := site.Data.authors }}
|
||||
{{ $allProfiles = $allProfiles | append (partial "functions/get_author_profile" (dict "slug" $slug)) }}
|
||||
{{ end }}
|
||||
|
||||
{{ $groups := $block.content.user_groups }}
|
||||
|
||||
{{ if $groups }}
|
||||
{{ range $group := $groups }}
|
||||
{{ $groupProfiles := slice }}
|
||||
{{ range $allProfiles }}
|
||||
{{ $u := .user_groups | default (slice) }}
|
||||
{{ if gt (len (intersect $u (slice $group))) 0 }}
|
||||
{{ $groupProfiles = $groupProfiles | append . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ $groupProfiles = sort $groupProfiles "title" }}
|
||||
|
||||
{{ if and $groupProfiles (gt (len $groups) 1) }}
|
||||
<div class="col-span-full mb-4">
|
||||
<h3 class="text-2xl font-bold text-gray-900 dark:text-white mb-6 pb-2 border-b border-gray-200 dark:border-gray-700">
|
||||
{{ $group_name | markdownify }}
|
||||
{{ $group | markdownify }}
|
||||
</h3>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Team Members */}}
|
||||
{{ range $person := $query }}
|
||||
{{ $avatar := (.Resources.ByType "image").GetMatch "*avatar*" }}
|
||||
{{ $link := "" }}
|
||||
{{ with site.GetPage (printf "/authors/%s" (path.Base .File.Dir)) }}
|
||||
{{ $link = .RelPermalink }}
|
||||
|
||||
<div class="grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 w-full">
|
||||
{{ range $profile := $groupProfiles }}
|
||||
{{ $avatar := $profile.avatar }}
|
||||
{{ $link := printf "/authors/%s/" $profile.slug }}
|
||||
|
||||
<div class="group relative">
|
||||
<div class="relative overflow-hidden rounded-2xl bg-white dark:bg-gray-800 shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1">
|
||||
|
||||
{{ if $avatar }}
|
||||
<a href="{{ $link }}" class="block relative h-64 overflow-hidden">
|
||||
{{ $image := $avatar.Fill "400x400 Center webp" }}
|
||||
<img src="{{ $image.RelPermalink }}"
|
||||
alt="{{ $profile.title }}"
|
||||
width="{{ $image.Width }}"
|
||||
height="{{ $image.Height }}"
|
||||
loading="lazy"
|
||||
class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-110">
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||
</a>
|
||||
{{ end }}
|
||||
|
||||
<div class="p-6">
|
||||
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-1">
|
||||
<a href="{{ $link }}" class="hover:text-primary-600 dark:hover:text-primary-400 transition-colors">
|
||||
{{ $profile.title }}
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
{{ if $show_role }}
|
||||
<div class="text-gray-600 dark:text-gray-400 mb-3">
|
||||
{{ with $profile.role }}<p class="font-medium">{{ . }}</p>{{ end }}
|
||||
{{ if and $show_organizations $profile.affiliations }}
|
||||
{{ range first 1 $profile.affiliations }}
|
||||
<p class="text-sm mt-1">{{ .name }}</p>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ with $profile.bio }}
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4 line-clamp-2">
|
||||
{{ . }}
|
||||
</p>
|
||||
{{ end }}
|
||||
|
||||
{{ if and $show_interests $profile.interests }}
|
||||
<div class="mb-4">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{{ range first 3 $profile.interests }}
|
||||
<span class="inline-block bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded">
|
||||
{{ . }}
|
||||
</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ if and $show_social $profile.links }}
|
||||
<div class="flex gap-3 pt-2 border-t border-gray-200 dark:border-gray-700">
|
||||
{{ range $profile.links }}
|
||||
{{ $l := .url | default .link }}
|
||||
{{ $scheme := (urls.Parse $l).Scheme }}
|
||||
{{ $target := "" }}
|
||||
{{ if not $scheme }}
|
||||
{{ $l = (.url | default .link) | relLangURL }}
|
||||
{{ if eq (path.Ext $l) ".pdf" }}{{ $target = "target=\"_blank\" rel=\"noopener\"" }}{{ end }}
|
||||
{{ else if in (slice "http" "https") $scheme }}
|
||||
{{ $target = "target=\"_blank\" rel=\"noopener\"" }}
|
||||
{{ end }}
|
||||
|
||||
<a href="{{ $l | safeURL }}"
|
||||
{{ $target | safeHTMLAttr }}
|
||||
aria-label="{{ .icon }}"
|
||||
{{ with .label }} title="{{ . }}"{{ end }}
|
||||
class="text-gray-500 hover:text-primary-600 dark:text-gray-400 dark:hover:text-primary-400 transition-colors">
|
||||
{{ partial "functions/get_icon" (dict "name" .icon "attributes" "class=\"w-5 h-5\"") }}
|
||||
</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
{{ $allProfiles = sort $allProfiles "title" }}
|
||||
<div class="grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{{ range $profile := $allProfiles }}
|
||||
{{ $avatar := $profile.avatar }}
|
||||
{{ $link := printf "/authors/%s/" $profile.slug }}
|
||||
<div class="group relative">
|
||||
<div class="relative overflow-hidden rounded-2xl bg-white dark:bg-gray-800 shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1">
|
||||
|
||||
{{/* Avatar */}}
|
||||
{{ if $avatar }}
|
||||
<a href="{{ $link }}" class="block relative h-64 overflow-hidden">
|
||||
{{ $image := $avatar.Fill "400x400 Center webp" }}
|
||||
<img src="{{ $image.RelPermalink }}"
|
||||
alt="{{ .Title }}"
|
||||
alt="{{ $profile.title }}"
|
||||
width="{{ $image.Width }}"
|
||||
height="{{ $image.Height }}"
|
||||
loading="lazy"
|
||||
class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-110">
|
||||
|
||||
{{/* Gradient Overlay */}}
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||
</a>
|
||||
{{ end }}
|
||||
|
||||
{{/* Content */}}
|
||||
<div class="p-6">
|
||||
{{/* Name */}}
|
||||
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-1">
|
||||
<a href="{{ $link }}" class="hover:text-primary-600 dark:hover:text-primary-400 transition-colors">
|
||||
{{ .Title }}
|
||||
{{ $profile.title }}
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
{{/* Role & Organization */}}
|
||||
{{ if $show_role }}
|
||||
<div class="text-gray-600 dark:text-gray-400 mb-3">
|
||||
{{ with .Params.role }}
|
||||
<p class="font-medium">{{ . }}</p>
|
||||
{{ end }}
|
||||
|
||||
{{ if and $show_organizations .Params.organizations }}
|
||||
{{ range first 1 .Params.organizations }}
|
||||
{{ with $profile.role }}<p class="font-medium">{{ . }}</p>{{ end }}
|
||||
{{ if and $show_organizations $profile.affiliations }}
|
||||
{{ range first 1 $profile.affiliations }}
|
||||
<p class="text-sm mt-1">{{ .name }}</p>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Bio */}}
|
||||
{{ with .Params.bio }}
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4 line-clamp-2">
|
||||
{{ . }}
|
||||
</p>
|
||||
{{ with $profile.bio }}
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4 line-clamp-2">{{ . }}</p>
|
||||
{{ end }}
|
||||
|
||||
{{/* Interests */}}
|
||||
{{ if and $show_interests .Params.interests }}
|
||||
{{ if and $show_interests $profile.interests }}
|
||||
<div class="mb-4">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{{ range first 3 .Params.interests }}
|
||||
<span class="inline-block bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded">
|
||||
{{ . }}
|
||||
</span>
|
||||
{{ range first 3 $profile.interests }}
|
||||
<span class="inline-block bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded">{{ . }}</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Social Links */}}
|
||||
{{ if $show_social }}
|
||||
{{ if and $show_social $profile.links }}
|
||||
<div class="flex gap-3 pt-2 border-t border-gray-200 dark:border-gray-700">
|
||||
{{ range .Params.social }}
|
||||
{{ $link := .url | default .link }}
|
||||
{{ $scheme := (urls.Parse $link).Scheme }}
|
||||
{{ range $profile.links }}
|
||||
{{ $l := .url | default .link }}
|
||||
{{ $scheme := (urls.Parse $l).Scheme }}
|
||||
{{ $target := "" }}
|
||||
{{ if not $scheme }}
|
||||
{{ $link = (.url | default .link) | relLangURL }}
|
||||
{{ if eq (path.Ext $link) ".pdf" }}{{ $target = "target=\"_blank\" rel=\"noopener\"" }}{{ end }}
|
||||
{{ $l = (.url | default .link) | relLangURL }}
|
||||
{{ if eq (path.Ext $l) ".pdf" }}{{ $target = "target=\"_blank\" rel=\"noopener\"" }}{{ end }}
|
||||
{{ else if in (slice "http" "https") $scheme }}
|
||||
{{ $target = "target=\"_blank\" rel=\"noopener\"" }}
|
||||
{{ end }}
|
||||
|
||||
<a href="{{ $link | safeURL }}"
|
||||
{{ $target | safeHTMLAttr }}
|
||||
aria-label="{{ .icon }}"
|
||||
{{ with .label }} title="{{ . }}"{{ end }}
|
||||
class="text-gray-500 hover:text-primary-600 dark:text-gray-400 dark:hover:text-primary-400 transition-colors">
|
||||
<a href="{{ $l | safeURL }}" {{ $target | safeHTMLAttr }} aria-label="{{ .icon }}" {{ with .label }} title="{{ . }}"{{ end }} class="text-gray-500 hover:text-primary-600 dark:text-gray-400 dark:hover:text-primary-400 transition-colors">
|
||||
{{ partial "functions/get_icon" (dict "name" .icon "attributes" "class=\"w-5 h-5\"") }}
|
||||
</a>
|
||||
{{ end }}
|
||||
@@ -159,8 +220,8 @@
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{/* Call to Action */}}
|
||||
{{ with $block.content.cta }}
|
||||
|
||||
@@ -7,19 +7,18 @@
|
||||
{{ $publisher := $branding.organization }}
|
||||
|
||||
{{ $author := "" }}
|
||||
{{ $author_username := "" }}
|
||||
{{ $author_slug := "" }}
|
||||
|
||||
{{ if and (not $page.Params.authors) ($page.Scratch.Get "superuser_username") }}
|
||||
{{ $author_username = $page.Scratch.Get "superuser_username" }}
|
||||
{{ if and (not $page.Params.authors) ($page.Scratch.Get "superuser_slug") }}
|
||||
{{ $author_slug = $page.Scratch.Get "superuser_slug" }}
|
||||
{{ else if $page.Params.authors }}
|
||||
{{ $author = index $page.Params.authors 0 }}
|
||||
{{ $author_username = urlize $author }}
|
||||
{{ $author_slug = urlize $author }}
|
||||
{{ end }}
|
||||
|
||||
{{ $taxonomy := "authors" }}
|
||||
{{ $profile_page := site.GetPage (printf "/%s/%s" $taxonomy $author_username) }}
|
||||
{{ with $profile_page }}
|
||||
{{ $author = .Title }}
|
||||
{{ $profile := partial "functions/get_author_profile" (dict "slug" $author_slug) }}
|
||||
{{ if $profile.title }}
|
||||
{{ $author = $profile.title }}
|
||||
{{ else }}
|
||||
{{ $author = $author | default $publisher }}
|
||||
{{ end }}
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
{{/*
|
||||
Return a normalized HugoBlox author/person profile from data.
|
||||
|
||||
Input (flexible):
|
||||
- dict with "slug" key, or a raw string slug.
|
||||
|
||||
Output (dict):
|
||||
- slug: string
|
||||
- title: string (display name, fallback to humanized slug)
|
||||
- role: string (optional)
|
||||
- bio: string (optional)
|
||||
- status: map (optional)
|
||||
- affiliations: array (optional)
|
||||
- ids: map (optional)
|
||||
- links: array (optional)
|
||||
- is_owner: bool (optional)
|
||||
- has_data: bool (true if found in data/authors)
|
||||
- avatar: resource (optional) – matching assets/media/authors/<slug>.*
|
||||
*/}}
|
||||
|
||||
{{- $input := . -}}
|
||||
{{- $slug := "" -}}
|
||||
|
||||
{{- $inputType := printf "%T" $input -}}
|
||||
|
||||
{{- if (reflect.IsMap $input) -}}
|
||||
{{- $slug = index $input "slug" | default "" -}}
|
||||
{{- else if eq $inputType "string" -}}
|
||||
{{- $slug = $input -}}
|
||||
{{- else -}}
|
||||
{{- $slug = printf "%v" $input -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $slug = $slug | urlize -}}
|
||||
|
||||
{{- $data := index site.Data.authors $slug -}}
|
||||
{{- $hasData := $data -}}
|
||||
|
||||
{{/* Structured name resolution */}}
|
||||
{{- $name := $data.name -}}
|
||||
{{- $display := "" -}}
|
||||
{{- $given := "" -}}
|
||||
{{- $family := "" -}}
|
||||
{{- $middle := "" -}}
|
||||
{{- $alternate := "" -}}
|
||||
{{- $namePronunciation := "" -}}
|
||||
{{- $namePronouns := "" -}}
|
||||
|
||||
{{- if $name.display }}{{ $display = $name.display }}{{ end }}
|
||||
{{- if $name.given }}{{ $given = $name.given }}{{ end }}
|
||||
{{- if $name.family }}{{ $family = $name.family }}{{ end }}
|
||||
{{- if $name.middle }}{{ $middle = $name.middle }}{{ end }}
|
||||
{{- if $name.alternate }}{{ $alternate = $name.alternate }}{{ end }}
|
||||
{{- if $name.pronunciation }}{{ $namePronunciation = $name.pronunciation }}{{ end }}
|
||||
{{- if $name.pronouns }}{{ $namePronouns = $name.pronouns }}{{ end }}
|
||||
|
||||
{{- $title := "" -}}
|
||||
{{- if $display -}}
|
||||
{{- $title = $display -}}
|
||||
{{- else if $data.title -}}
|
||||
{{- $title = $data.title -}}
|
||||
{{- else -}}
|
||||
{{- $title = $slug | humanize -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $role := $data.role -}}
|
||||
{{- $bio := $data.bio -}}
|
||||
{{- $status := $data.status -}}
|
||||
{{- $affiliations := $data.affiliations -}}
|
||||
{{- $ids := $data.ids -}}
|
||||
{{- $links := $data.links -}}
|
||||
{{- $isOwner := or ($data.is_owner) ($data.superuser) -}}
|
||||
{{- $pronouns := $namePronouns | default $data.pronouns -}}
|
||||
{{- $userGroups := $data.user_groups -}}
|
||||
{{- $interests := $data.interests -}}
|
||||
{{- $education := $data.education -}}
|
||||
{{- $experience := or $data.experience $data.work -}}
|
||||
{{- $skills := $data.skills -}}
|
||||
{{- $languages := $data.languages -}}
|
||||
{{- $awards := $data.awards -}}
|
||||
{{- $postnominals := slice -}}
|
||||
{{- if $data.postnominals -}}
|
||||
{{- if (reflect.IsSlice $data.postnominals) -}}
|
||||
{{- $postnominals = $data.postnominals -}}
|
||||
{{- else -}}
|
||||
{{- $postnominals = slice $data.postnominals -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Avatar resolution: look in assets/media/authors/<slug>.* */ -}}
|
||||
{{- $avatar := resources.GetMatch (printf "media/authors/%s.*" $slug) -}}
|
||||
|
||||
{{- $profile := dict
|
||||
"slug" $slug
|
||||
"title" $title
|
||||
"name_display" $display
|
||||
"name_given" $given
|
||||
"name_family" $family
|
||||
"name_middle" $middle
|
||||
"name_alternate" $alternate
|
||||
"name_pronunciation" $namePronunciation
|
||||
"role" $role
|
||||
"bio" $bio
|
||||
"status" $status
|
||||
"affiliations" $affiliations
|
||||
"ids" $ids
|
||||
"links" $links
|
||||
"pronouns" $pronouns
|
||||
"user_groups" $userGroups
|
||||
"interests" $interests
|
||||
"education" $education
|
||||
"experience" $experience
|
||||
"skills" $skills
|
||||
"languages" $languages
|
||||
"awards" $awards
|
||||
"postnominals" $postnominals
|
||||
"is_owner" $isOwner
|
||||
"has_data" (not (eq $hasData nil))
|
||||
"avatar" $avatar
|
||||
-}}
|
||||
|
||||
{{- return $profile -}}
|
||||
@@ -3,16 +3,22 @@
|
||||
{{/* Don't show author box on normal pages or if author box disabled. */}}
|
||||
{{ if ne .Type "page" | and (not (or (eq site.Params.profile false) (eq .Params.profile false))) }}
|
||||
|
||||
{{/* Display superuser if superuser exists and page authors are not explicitly specified. */}}
|
||||
{{/* Otherwise, display first author if a profile for them exists. */}}
|
||||
{{ $authors := .Params.authors }}
|
||||
|
||||
{{ if and (not .Params.authors) (.Scratch.Get "superuser_username") }}
|
||||
{{ $author_page := site.GetPage (printf "/%s/%s" "authors" (.Scratch.Get "superuser_username")) }}
|
||||
{{ partial "page_author_card" (dict "author_page" $author_page) }}
|
||||
{{ else if .Params.authors }}
|
||||
{{ $taxonomy := "authors" }}
|
||||
{{ range $author_obj := (.GetTerms $taxonomy) }}
|
||||
{{ partial "page_author_card" (dict "author_page" $author_obj.Page) }}
|
||||
{{/* If authors not set, fallback to superuser/owner from Scratch */}}
|
||||
{{ if (not $authors) }}
|
||||
{{ $superuser := .Scratch.Get "superuser_slug" }}
|
||||
{{ if $superuser }}
|
||||
{{ $authors = (slice $superuser) }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ if $authors }}
|
||||
{{ range $idx, $slug := $authors }}
|
||||
{{ $profile := partial "functions/get_author_profile" $slug }}
|
||||
{{ if $profile }}
|
||||
{{ partial "page_author_card" (dict "profile" $profile "page" .) }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
{{ $profile := .profile }}
|
||||
{{ $page := .page }}
|
||||
{{ $author_page := .author_page }}
|
||||
{{ $site_type := site.Params.hugoblox.identity.type | humanize | default "Person" }}
|
||||
{{- $has_profile := not (eq nil $author_page.Params.superuser) -}}
|
||||
{{ $avatar_shape := site.Params.hugoblox.layout.avatar_shape | default "circle" }}
|
||||
|
||||
{{/* Check an author profile exists. */}}
|
||||
{{ if $has_profile }}
|
||||
{{/* If it's a personal site and primary page author is superuser, link to the homepage rather than their profile page. */}}
|
||||
{{ $profile_url := $author_page.RelPermalink }}
|
||||
{{ if and (eq $site_type "Person") (eq $author_page.Params.superuser true) }}
|
||||
{{ if $profile }}
|
||||
{{/* If it's a personal site and the profile is owner, link home */}}
|
||||
{{ $profile_url := printf "/authors/%s/" $profile.slug }}
|
||||
{{ if and (eq $site_type "Person") ($profile.is_owner) }}
|
||||
{{ $profile_url = site.BaseURL }}
|
||||
{{ end }}
|
||||
{{ $avatar := ($author_page.Resources.ByType "image").GetMatch "*avatar*" }}
|
||||
{{ $avatar_shape := site.Params.hugoblox.layout.avatar_shape | default "circle" }}
|
||||
|
||||
{{ $avatar := $profile.avatar }}
|
||||
|
||||
<div class="flex pt-12 pb-4">
|
||||
{{ if $avatar }}
|
||||
{{/* Use utility for high-res avatar processing (smart for Retina displays) */}}
|
||||
{{ $responsive := partial "functions/process_responsive_image.html" (dict
|
||||
"image" $avatar
|
||||
"mode" "fill"
|
||||
@@ -31,7 +29,7 @@
|
||||
src="{{ $responsive.fallback.RelPermalink }}"
|
||||
width="96"
|
||||
height="96"
|
||||
alt="{{$author_page.Title}}"
|
||||
alt="{{$profile.title}}"
|
||||
loading="lazy"
|
||||
/>
|
||||
{{ end }}
|
||||
@@ -42,29 +40,27 @@
|
||||
<div class="font-semibold leading-6 text-neutral-800 dark:text-neutral-300">
|
||||
<span class="inline-block">
|
||||
{{if $profile_url}}<a href="{{$profile_url}}" class="no-underline">{{end}}
|
||||
{{$author_page.Title}}
|
||||
{{$profile.title}}
|
||||
{{if $profile_url}}</a>{{end}}
|
||||
</span>
|
||||
{{- with $author_page.Params.pronouns -}}
|
||||
{{- with $profile.pronouns -}}
|
||||
<span class="text-sm font-normal text-gray-600 dark:text-gray-400 ml-1 inline-block">({{ . }})</span>
|
||||
{{- end -}}
|
||||
</div>
|
||||
|
||||
{{ with $author_page.Params.role }}
|
||||
{{ with $profile.role }}
|
||||
<div class="text-sm font-bold text-neutral-700 dark:text-neutral-300">
|
||||
{{. | markdownify | emojify}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
|
||||
{{ with $author_page.Params.bio }}
|
||||
{{ with $profile.bio }}
|
||||
<div class="text-sm text-neutral-700 dark:text-neutral-300">{{ . | markdownify | emojify }}</div>
|
||||
{{ end }}
|
||||
|
||||
<div class="text-2xl sm:text-lg pt-1">
|
||||
|
||||
{{ partial "social_links" $author_page }}
|
||||
|
||||
{{ partial "social_links" (dict "Params" (dict "profiles" $profile.links)) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
{{/* Display author list. */}}
|
||||
|
||||
{{ if .Params.authors }}
|
||||
{{ range $index, $value := (.GetTerms "authors") }}
|
||||
{{- /* Highlight the author's name? */ -}}
|
||||
{{- $highlight_name := .Page.Params.highlight_name | default false -}}
|
||||
{{ range $index, $term := (.GetTerms "authors") }}
|
||||
{{- $slug := $term.Data.Term | urlize -}}
|
||||
{{- $profile := partial "functions/get_author_profile" (dict "slug" $slug) -}}
|
||||
{{- $display := $profile.title | default $term.LinkTitle -}}
|
||||
{{- $highlight_name := false -}}
|
||||
|
||||
{{- if gt $index 0 }}, {{ end -}}
|
||||
<span {{ if $highlight_name }}class="font-bold"{{end}}>
|
||||
{{/* Effectively check the page's `build` option as `build` is not exposed in Hugo's Page object. */}}
|
||||
{{- if .RelPermalink -}}
|
||||
<a href="{{.RelPermalink}}">{{.LinkTitle}}</a>
|
||||
{{- if $term.RelPermalink -}}
|
||||
<a href="{{ $term.RelPermalink }}">{{ $display }}</a>
|
||||
{{- else -}}
|
||||
{{ .LinkTitle }}
|
||||
{{ $display }}
|
||||
{{- end -}}
|
||||
</span>
|
||||
{{- if isset $.Params "author_notes" -}}
|
||||
|
||||
@@ -12,18 +12,30 @@
|
||||
<meta name="robots" content="noindex" />
|
||||
{{- end -}}
|
||||
|
||||
{{/* Attempt to load superuser. */}}
|
||||
{{ $superuser_name := "" }}
|
||||
{{ $superuser_username := "" }}
|
||||
{{ $superuser_role := "" }}
|
||||
{{ range first 1 (where (where site.Pages "Section" "authors") "Params.superuser" true) }}
|
||||
{{ $superuser_name = .Title }}
|
||||
{{ $superuser_username = path.Base .File.Dir }}
|
||||
{{ $superuser_role = .Params.role }}
|
||||
{{ end }}
|
||||
{{ $scr.Set "superuser_username" $superuser_username }}{{/* Set superuser globally for page_author.html. */}}
|
||||
{{/* Attempt to load superuser/person from data. */}}
|
||||
{{ $superuser_name := "" }}
|
||||
{{ $superuser_slug := "" }}
|
||||
{{ $superuser_role := "" }}
|
||||
|
||||
{{ with $superuser_name }}<meta name="author" content="{{ . }}" />{{ end }}
|
||||
{{/* Prefer is_owner flag, fallback to slug 'me' */}}
|
||||
{{ range $slug, $data := site.Data.authors }}
|
||||
{{ if and (not $superuser_slug) ($data.is_owner) }}
|
||||
{{ $superuser_slug = $slug }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ if and (eq $superuser_slug "") (index site.Data.authors "me") }}
|
||||
{{ $superuser_slug = "me" }}
|
||||
{{ end }}
|
||||
|
||||
{{ if $superuser_slug }}
|
||||
{{ $profile := partial "functions/get_author_profile" (dict "slug" $superuser_slug) }}
|
||||
{{ $superuser_name = $profile.title }}
|
||||
{{ $superuser_role = $profile.role }}
|
||||
{{ end }}
|
||||
|
||||
{{ $scr.Set "superuser_slug" $superuser_slug }}{{/* Set superuser globally for author rendering. */}}
|
||||
|
||||
{{ with $superuser_name }}<meta name="author" content="{{ . }}" />{{ end }}
|
||||
|
||||
{{/* Generate page description. */}}
|
||||
{{ $desc := "" }}
|
||||
@@ -114,15 +126,22 @@
|
||||
{{/* Get page image for sharing. */}}
|
||||
{{ $sharing_image := resources.GetMatch (path.Join "media" "sharing.*") }}
|
||||
{{ $featured_image := (.Resources.ByType "image").GetMatch "*featured*" }}
|
||||
{{ $avatar_image := (.Resources.ByType "image").GetMatch "avatar*" }}
|
||||
{{ $avatar_image := (.Resources.ByType "image").GetMatch "avatar*" }}
|
||||
{{ $has_logo := fileExists "assets/media/logo.png" | or (fileExists "assets/media/logo.svg") }}
|
||||
{{ $og_image := "" }}
|
||||
{{ $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 "540x540 Center").Permalink }}
|
||||
{{ if and (eq .Kind "term") (eq .Data.Singular "author") }}
|
||||
{{/* Try data-driven avatar for author term pages */}}
|
||||
{{ $termSlug := .Data.Term | urlize }}
|
||||
{{ $profile := partial "functions/get_author_profile" (dict "slug" $termSlug) }}
|
||||
{{ with $profile.avatar }}
|
||||
{{ $og_image = (.Fill "540x540 Center").Permalink }}
|
||||
{{ $twitter_card = "summary" }}
|
||||
{{ else if $featured_image }}
|
||||
{{ end }}
|
||||
{{ else if (and (eq .Kind "term") $avatar_image) }}
|
||||
{{ $og_image = ($avatar_image.Fill "540x540 Center").Permalink }}
|
||||
{{ $twitter_card = "summary" }}
|
||||
{{ else if $featured_image }}
|
||||
{{ $og_image = $featured_image.Permalink }}
|
||||
{{ else if $sharing_image }}
|
||||
{{ $og_image = $sharing_image.Permalink }}
|
||||
|
||||
@@ -145,29 +145,20 @@
|
||||
<div class="flex items-center gap-3 text-xs text-zinc-500 dark:text-zinc-500 flex-wrap mb-3">
|
||||
{{ if $item.Params.authors }}
|
||||
<div class="flex items-center gap-2 min-w-0">
|
||||
{{ $authorTerm := index ($item.GetTerms "authors") 0 }}
|
||||
{{ if $authorTerm }}
|
||||
{{/* Author page exists - show avatar or default icon */}}
|
||||
<div class="relative h-6 w-6 flex-shrink-0">
|
||||
{{ $avatar := ($authorTerm.Resources.ByType "image").GetMatch "*avatar*" }}
|
||||
{{ if $avatar }}
|
||||
{{ $avatar_48 := $avatar.Process "Fill 48x48 Center webp" }}
|
||||
<img alt="avatar" class="rounded-full object-cover" src="{{$avatar_48.RelPermalink}}" width="24" height="24" loading="lazy" decoding="async" />
|
||||
{{ else }}
|
||||
{{/* Default avatar icon when no image */}}
|
||||
<div class="w-6 h-6 bg-gray-100 dark:bg-gray-700 rounded-full flex items-center justify-center">
|
||||
{{ partial "functions/get_icon" (dict "name" "hero/user" "attributes" "class=\"w-4 h-4 text-gray-500 dark:text-gray-400\"") }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
<span class="truncate max-w-[9rem] text-sm">{{ $authorTerm.Page.LinkTitle }}</span>
|
||||
{{ else }}
|
||||
{{/* No author page - just show team icon and first author name */}}
|
||||
<div class="w-6 h-6 bg-primary-100 dark:bg-primary-900/50 rounded-full flex items-center justify-center flex-shrink-0">
|
||||
{{ partial "functions/get_icon" (dict "name" "hero/user-group" "attributes" "class=\"w-4 h-4 text-primary-600 dark:text-primary-400\"") }}
|
||||
</div>
|
||||
<span class="truncate max-w-[9rem] text-sm">{{ index $item.Params.authors 0 }}</span>
|
||||
{{ end }}
|
||||
{{ $slug := index $item.Params.authors 0 | urlize }}
|
||||
{{ $profile := partial "functions/get_author_profile" $slug }}
|
||||
{{ $avatar := $profile.avatar }}
|
||||
<div class="relative h-6 w-6 flex-shrink-0">
|
||||
{{ if $avatar }}
|
||||
{{ $avatar_48 := $avatar.Process "Fill 48x48 Center webp" }}
|
||||
<img alt="avatar" class="rounded-full object-cover" src="{{$avatar_48.RelPermalink}}" width="24" height="24" loading="lazy" decoding="async" />
|
||||
{{ else }}
|
||||
<div class="w-6 h-6 bg-gray-100 dark:bg-gray-700 rounded-full flex items-center justify-center">
|
||||
{{ partial "functions/get_icon" (dict "name" "hero/user" "attributes" "class=\"w-4 h-4 text-gray-500 dark:text-gray-400\"") }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
<span class="truncate max-w-[9rem] text-sm">{{ $profile.title }}</span>
|
||||
</div>
|
||||
<span class="opacity-40">•</span>
|
||||
{{ end }}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
{{ define "main" }}
|
||||
|
||||
{{ $slug := .Data.Term | urlize }}
|
||||
{{ $profile := partial "functions/get_author_profile" (dict "slug" $slug) }}
|
||||
{{ $avatar := $profile.avatar }}
|
||||
{{ $avatar_shape := site.Params.hugoblox.layout.avatar_shape | default "circle" }}
|
||||
|
||||
<section class="max-w-5xl mx-auto px-6 py-10">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center gap-6">
|
||||
<div class="flex-shrink-0">
|
||||
{{ if $avatar }}
|
||||
{{ $responsive := partial "functions/process_responsive_image.html" (dict
|
||||
"image" $avatar
|
||||
"mode" "fill"
|
||||
"aspect_ratio" "1:1"
|
||||
"anchor" "Center"
|
||||
"sizes" (slice 160 240 320 480)
|
||||
) }}
|
||||
<img
|
||||
class="h-32 w-32 sm:h-40 sm:w-40 {{ if ne $avatar_shape "square" }}rounded-full{{ end }}"
|
||||
srcset="{{ $responsive.srcset }}"
|
||||
sizes="160px"
|
||||
src="{{ $responsive.fallback.RelPermalink }}"
|
||||
width="160"
|
||||
height="160"
|
||||
alt="{{ $profile.title }}"
|
||||
loading="lazy"
|
||||
/>
|
||||
{{ else }}
|
||||
<div class="h-32 w-32 sm:h-40 sm:w-40 bg-gray-100 dark:bg-gray-800 rounded-full flex items-center justify-center">
|
||||
{{ partial "functions/get_icon" (dict "name" "hero/user" "attributes" "class=\"h-12 w-12 text-gray-400\"") }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<h1 class="text-3xl sm:text-4xl font-bold">{{ $profile.title }}</h1>
|
||||
{{ with $profile.status }}
|
||||
{{ with .icon }}
|
||||
<span class="text-2xl" aria-hidden="true">{{ . }}</span>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
{{ with $profile.role }}
|
||||
<div class="text-lg font-semibold text-neutral-700 dark:text-neutral-200">{{ . | markdownify | emojify }}</div>
|
||||
{{ end }}
|
||||
|
||||
{{ with $profile.affiliations }}
|
||||
<div class="text-neutral-600 dark:text-neutral-300">
|
||||
{{ range $index, $org := . }}
|
||||
{{ if gt $index 0 }} · {{ end }}
|
||||
{{ if $org.url }}<a href="{{ $org.url }}" class="underline">{{ $org.name }}</a>{{ else }}{{ $org.name }}{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ with $profile.bio }}
|
||||
<div class="prose prose-slate dark:prose-invert max-w-none">
|
||||
{{ . | markdownify | emojify }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ if $profile.links }}
|
||||
<div class="pt-2">
|
||||
{{ partial "social_links" (dict "Params" (dict "profiles" $profile.links)) }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{ $view := "card" }}
|
||||
{{ $block := . }}
|
||||
{{ $paginator := .Paginate .Pages.ByDate.Reverse }}
|
||||
{{ $config := dict
|
||||
"columns" 2
|
||||
"len" (len $paginator.Pages)
|
||||
"fill_image" true
|
||||
"show_date" true
|
||||
"show_read_time" true
|
||||
"show_read_more" true
|
||||
}}
|
||||
|
||||
<div class="flex flex-col items-center px-6">
|
||||
{{ partial "functions/render_view" (dict "fragment" "start" "page" $block "item" . "view" $view "config" $config) }}
|
||||
|
||||
{{ range $index, $item := $paginator.Pages }}
|
||||
{{ partial "functions/render_view" (dict "page" . "item" . "view" $view "index" $index "config" $config) }}
|
||||
{{ end }}
|
||||
|
||||
{{ partial "functions/render_view" (dict "fragment" "end" "page" $block "item" . "view" $view) }}
|
||||
|
||||
{{ partial "components/paginator" . }}
|
||||
</div>
|
||||
|
||||
{{ end }}
|
||||
@@ -78,30 +78,22 @@
|
||||
<script>console.log('Author notes detected in single.html, Alpine.js should load');</script>
|
||||
{{ end }}
|
||||
|
||||
{{ $taxonomy := "authors" }}
|
||||
{{ range $i, $author_obj := (.GetTerms $taxonomy) }}
|
||||
{{ $author_page := $author_obj.Page }}
|
||||
{{ $avatar := ($author_page.Resources.ByType "image").GetMatch "*avatar*" }}
|
||||
{{- if and $i (not $avatar) }}<span class="mr-1">,</span>{{ end -}}
|
||||
{{ with $author_page.Params.website }}
|
||||
<a href="{{.}}" target="_blank" rel="noreferrer">
|
||||
{{ end }}
|
||||
{{ range $i, $slug := .Params.authors }}
|
||||
{{ $profile := partial "functions/get_author_profile" $slug }}
|
||||
{{ $avatar := $profile.avatar }}
|
||||
{{ if and $i (not $avatar) }}<span class="mr-1">,</span>{{ end }}
|
||||
<div class="group inline-flex items-center text-current gap-x-1.5 mx-1">
|
||||
{{ with $avatar }}
|
||||
{{/* Simple 2x retina image for small avatar */}}
|
||||
{{ $avatar_32 := $avatar.Process "Fill 32x32 Center webp" }}
|
||||
{{ $avatar_32 := .Process "Fill 32x32 Center webp" }}
|
||||
<img src="{{ $avatar_32.RelPermalink }}"
|
||||
width="16"
|
||||
height="16"
|
||||
alt="{{ $author_page.Title }}"
|
||||
alt="{{ $profile.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>
|
||||
<div>{{ $profile.title }}</div>
|
||||
</div>
|
||||
{{ with $author_page.Params.website }}
|
||||
</a>
|
||||
{{ end }}
|
||||
{{/* Add author note tooltip if available */}}
|
||||
{{- if isset $.Params "author_notes" -}}
|
||||
{{- with (index $.Params.author_notes $i) -}}
|
||||
|
||||
Reference in new issue
Block a user