feat: standardize author profiles & add devcontainer

This commit is contained in:
George Cushen committed 2025-12-07 01:50:48 +00:00
1 parent 75234c306b
commit f0eb4a7e9c
80 files changed
+1327 -1019

No files matched your search

+1
View File
@@ -3,6 +3,7 @@
"files": {
"includes": [
"**",
"!modules/**/*.html",
"!**/node_modules",
"!**/public",
"!**/resources",
@@ -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}} &#8729;
{{ if .date}}
{{ .awarder }} &#8729;
{{ 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 }}
+7 -15
View File
@@ -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) -}}
+35 -23
View File
@@ -9,7 +9,7 @@ Features:
- Updates special module metadata (blox-tailwind data/hugoblox.yaml version)
- Updates dependent modules' go.mod "require" versions when a dependency is released
- Tags changed modules with annotated tags and pushes tags
- Updates templates' go.mod to latest released module versions and bumps Hugo version in CI/Netlify config
- Updates templates' go.mod to latest released module versions and bumps Hugo version in template configs (hugoblox.yaml, Netlify, devcontainer)
Usage examples:
poetry run python scripts/release_modules.py --dry-run --log-level INFO
@@ -113,6 +113,36 @@ def update_go_mod_require_pseudo_version(go_mod_text: str, dep_module_path: str,
return re.sub(pattern_line, replacement_line, go_mod_text)
def update_devcontainer_hugo(starter_dir: Path, latest_hugo: Optional[str], updated_paths: List[Path]) -> None:
"""Update devcontainer Hugo feature version if present."""
if not latest_hugo:
return
devcontainer = starter_dir / ".devcontainer" / "devcontainer.json"
if not devcontainer.exists():
return
try:
data = json.loads(devcontainer.read_text(encoding="utf-8"))
except Exception as exc: # pragma: no cover - defensive
logging.warning("starter %s: failed to parse devcontainer.json (%s)", starter_dir.name, exc)
return
features = data.get("features", {})
hugo_feature = features.get("ghcr.io/devcontainers/features/hugo:1")
if not isinstance(hugo_feature, dict):
return
current = hugo_feature.get("version")
if current == latest_hugo:
return
hugo_feature["version"] = latest_hugo
features["ghcr.io/devcontainers/features/hugo:1"] = hugo_feature
data["features"] = features
devcontainer.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
updated_paths.append(devcontainer)
logging.info("starter %s: devcontainer Hugo version -> %s", starter_dir.name, latest_hugo)
@dataclass
class Module:
name: str # e.g., blox-tailwind
@@ -512,17 +542,8 @@ def update_starters(modules: Dict[str, Module], commit: bool, push: bool) -> Non
updated_paths.append(netlify)
logging.info("starter %s: netlify.toml HUGO_VERSION -> %s", starter_dir.name, latest_hugo)
# bump deploy.yml env WC_HUGO_VERSION
workflow = starter_dir / ".github" / "workflows" / "deploy.yml"
if latest_hugo and workflow.exists():
with workflow.open("r", encoding="utf-8") as f:
wf = yaml.load(f) or {}
if "env" in wf and wf["env"].get("WC_HUGO_VERSION") != latest_hugo:
wf["env"]["WC_HUGO_VERSION"] = latest_hugo
with workflow.open("w", encoding="utf-8") as f:
yaml.dump(wf, f)
updated_paths.append(workflow)
logging.info("starter %s: deploy.yml WC_HUGO_VERSION -> %s", starter_dir.name, latest_hugo)
# bump devcontainer Hugo version (new location for Hugo pin)
update_devcontainer_hugo(starter_dir, latest_hugo, updated_paths)
if updated_paths:
stage_and_maybe_commit(updated_paths, "chore(templates): bump modules and Hugo", commit, push)
@@ -591,17 +612,8 @@ def update_starters_to_commits(modules: Dict[str, Module], commit: bool, push: b
updated_paths.append(netlify)
logging.info("starter %s: netlify.toml HUGO_VERSION -> %s", starter_dir.name, latest_hugo)
# bump deploy.yml env WC_HUGO_VERSION (same as regular update_starters)
workflow = starter_dir / ".github" / "workflows" / "deploy.yml"
if latest_hugo and workflow.exists():
with workflow.open("r", encoding="utf-8") as f:
wf = yaml.load(f) or {}
if "env" in wf and wf["env"].get("WC_HUGO_VERSION") != latest_hugo:
wf["env"]["WC_HUGO_VERSION"] = latest_hugo
with workflow.open("w", encoding="utf-8") as f:
yaml.dump(wf, f)
updated_paths.append(workflow)
logging.info("starter %s: deploy.yml WC_HUGO_VERSION -> %s", starter_dir.name, latest_hugo)
# bump devcontainer Hugo version (same as regular update_starters)
update_devcontainer_hugo(starter_dir, latest_hugo, updated_paths)
if updated_paths:
stage_and_maybe_commit(updated_paths, "chore(templates): update modules to latest commits", commit, push)
+92
View File
@@ -0,0 +1,92 @@
#!/usr/bin/env python3
import argparse
import re
import json
import sys
from pathlib import Path
def update_file_regex(path, pattern, replacement):
file_path = Path(path)
if not file_path.exists():
print(f"Warning: {path} not found.")
return False
content = file_path.read_text()
if re.search(pattern, content):
new_content = re.sub(pattern, replacement, content)
file_path.write_text(new_content)
print(f"Updated {path}")
return True
else:
print(f"Warning: Pattern not found in {path}")
return False
def update_devcontainer(path, new_version):
file_path = Path(path)
if not file_path.exists():
print(f"Warning: {path} not found.")
return False
try:
content = json.loads(file_path.read_text())
# Update Hugo feature version
features = content.get("features", {})
hugo_feature = features.get("ghcr.io/devcontainers/features/hugo:1")
if hugo_feature:
hugo_feature["version"] = new_version
file_path.write_text(json.dumps(content, indent=2) + "\n") # standard json formatting
print(f"Updated {path}")
return True
else:
print(f"Warning: Hugo feature not found in {path}")
return False
except Exception as e:
print(f"Error updating {path}: {e}")
return False
def main():
parser = argparse.ArgumentParser(description="Update Hugo version in template files")
parser.add_argument("version", help="New Hugo version (e.g., 0.152.2)")
args = parser.parse_args()
version = args.version
# List of templates to update
templates = [
"academic-cv",
"blog",
"documentation",
"landing-page",
"link-in-bio",
"resume"
]
for template in templates:
print(f"\nProcessing template: {template}")
template_dir = Path(f"templates/{template}")
# 1. Update hugoblox.yaml
update_file_regex(
template_dir / "hugoblox.yaml",
r"hugo_version: .*",
f"hugo_version: '{version}'"
)
# 2. Update netlify.toml
update_file_regex(
template_dir / "netlify.toml",
r'HUGO_VERSION = ".*"',
f'HUGO_VERSION = "{version}"'
)
# 3. Update devcontainer.json
update_devcontainer(
template_dir / ".devcontainer/devcontainer.json",
version
)
print(f"\nSuccessfully updated all templates to Hugo v{version}")
print("Note: .github/workflows/deploy.yml is configured to read the version dynamically from hugoblox.yaml.")
if __name__ == "__main__":
main()
+5 -1
View File
@@ -12,7 +12,7 @@ sections:
- block: resume-biography-3
content:
# Choose a user profile to display (a folder name within `content/authors/`)
username: admin
username: me
text: ''
# Show a call-to-action button under your biography? (optional)
button:
@@ -28,6 +28,10 @@ sections:
gradient_mesh:
enable: true
# Name heading sizing to accommodate long or short names
name:
size: balanced # Options: compact (long names), balanced (default), display (short names)
# Avatar customization
avatar:
size: medium # Options: small (150px), medium (200px, default), large (320px), xl (400px), xxl (500px)
@@ -1,172 +0,0 @@
---
# Display name
title: Alex Johnson
# Name pronunciation (optional)
name_pronunciation: ''
# Full name (for SEO)
first_name: Alex
last_name: Johnson
# Pronouns (optional)
pronouns: he/him
# Status emoji
status:
icon: 🚀
# Is this the primary user of the site?
superuser: true
# Highlight the author in author lists? (true/false)
highlight_name: true
# Role/position/tagline
role: Senior AI Research Scientist
# Organizations/Affiliations to display in Biography blox
organizations:
- name: Meta AI
url: https://ai.meta.com/
# Social network links
# Need to use another icon? Simply download the SVG icon to your `assets/media/icons/` folder.
profiles:
- icon: at-symbol
url: 'mailto:alex.johnson@meta.com'
label: E-mail Me
- icon: brands/x
url: https://x.com/AlexAIResearch
- icon: brands/github
url: https://github.com/alexjohnsonai
- icon: brands/linkedin
url: https://www.linkedin.com/in/alexjohnsonai/
- icon: academicons/google-scholar
url: https://scholar.google.com/citations?user=alexjohnson
- icon: academicons/orcid
url: https://orcid.org/0000-0002-1825-0097
interests:
- Large Language Models
- Computer Vision
- Reinforcement Learning
- AI Ethics
education:
- area: PhD Computer Science (AI Focus)
institution: Stanford University
icon: ""
date_start: 2015-09-01
date_end: 2019-06-30
summary: |
Thesis on _Scaling Laws for Neural Language Models_. Supervised by Prof. Andrew Ng. Published 5 papers in NeurIPS and ICML, with 2 best paper awards.
button:
text: 'Read Thesis'
url: 'https://example.com/thesis.pdf'
- area: MS Computer Science
institution: Carnegie Mellon University
icon: ""
date_start: 2013-09-01
date_end: 2015-05-31
summary: |
GPA: 4.0/4.0
Specialized in machine learning and robotics.
- area: BS Computer Science
institution: MIT
icon: ""
date_start: 2009-09-01
date_end: 2013-05-31
summary: |
GPA: 3.9/4.0
Minored in Mathematics. President of AI Club.
work:
- position: Senior AI Research Scientist
company_name: Meta AI
company_url: 'https://ai.meta.com/'
icon: ''
date_start: 2020-01-01
date_end: ''
summary: |2-
Leading research on multimodal AI systems. Contributed to Llama 2 and other open-source models. 50+ citations in 3 years.
- position: AI Research Intern
company_name: OpenAI
company_url: 'https://openai.com/'
icon: ''
date_start: 2019-06-01
date_end: 2019-12-31
summary: |
Worked on GPT-3 scaling. Co-authored paper on prompt engineering.
# Skills
# Add your own SVG icons to `assets/media/icons/`
skills:
- name: Technical Skills
items:
- name: Python & PyTorch
description: ''
percent: 95
icon: code-bracket
- name: Machine Learning
description: ''
percent: 100
icon: chart-bar
- name: Cloud Computing (AWS/GCP)
description: ''
percent: 85
icon: cloud
- name: Hobbies
color: '#eeac02'
color_border: '#f0bf23'
items:
- name: Hiking in the Rockies
description: ''
percent: 80
icon: person-simple-walk
- name: Building Custom PCs
description: ''
percent: 90
icon: cpu-chip
- name: Sci-Fi Reading
description: ''
percent: 70
icon: book-open
languages:
- name: English
percent: 100
- name: Spanish
percent: 50
# Awards.
# Add/remove as many awards below as you like.
# Only `title`, `awarder`, and `date` are required.
# Begin multi-line `summary` with YAML's `|` or `|2-` multi-line prefix and indent 2 spaces below.
awards:
- title: Best Paper Award
url: https://neurips.cc/
date: '2022-12-01'
awarder: NeurIPS
icon: hero/trophy
summary: |
Awarded for groundbreaking work on efficient training of large models.
- title: AI Innovation Grant
url: https://www.nsf.gov/
date: '2021-06-15'
awarder: National Science Foundation
icon: hero/currency-dollar
summary: |
$500,000 grant for research in ethical AI development.
- title: Outstanding PhD Thesis
url: https://www.stanford.edu/
date: '2019-06-30'
awarder: Stanford University
icon: hero/academic-cap
summary: |
Recognized for contributions to scaling laws in deep learning.
---
Alex Johnson is a Senior AI Research Scientist at Meta AI, where he leads groundbreaking work on large language models and multimodal systems. His research has been published in top conferences like NeurIPS and ICML, with over 10,000 citations. Alex is passionate about pushing the boundaries of AI while ensuring ethical development. Outside of work, he enjoys hiking in the Rockies, building custom PCs, and mentoring the next generation of AI talent.
@@ -3,7 +3,7 @@ title: 📈 Communicate your results effectively with the best data visualizatio
summary: Use popular tools such as HuggingFace, Plotly, Mermaid, and data frames.
date: 2023-10-25
authors:
- admin
- me
tags:
- Hugo
- Hugo Blox
@@ -25,7 +25,7 @@ cover:
name: "✨"
authors:
- admin
- me
- Ted
tags:
@@ -3,7 +3,7 @@ title: ⚡️ Turn Jupyter Notebooks into Blog Posts
summary: Publish your data science and research directly from Jupyter Notebooks. No screenshots required.
date: 2024-07-15
authors:
- admin
- me
tags:
- Hugo Blox
- Jupyter
@@ -3,7 +3,7 @@ title: ✅ Manage your projects
summary: Easily manage your projects - create ideation mind maps, Gantt charts, todo lists, and more!
date: 2023-10-23
authors:
- admin
- me
tags:
- Hugo Blox
- Markdown
@@ -3,7 +3,7 @@ title: 🧠 Sharpen your thinking with a second brain
summary: Create a personal knowledge base and share your knowledge with your peers.
date: 2023-10-26
authors:
- admin
- me
tags:
- Second Brain
- Markdown
@@ -4,7 +4,7 @@ summary: Embed videos, podcasts, code, LaTeX math, and even test students!
date: 2023-10-24
math: true
authors:
- admin
- me
tags:
- Hugo
- Hugo Blox Builder
@@ -25,7 +25,7 @@ all_day: false
publishDate: '2017-01-01T00:00:00Z'
authors:
- admin
- me
tags: []
+4 -6
View File
@@ -12,7 +12,7 @@ design:
sections:
- block: resume-experience
content:
username: admin
username: me
design:
# Hugo date format
date_format: 'January 2006'
@@ -21,15 +21,13 @@ sections:
- block: resume-skills
content:
title: Skills & Hobbies
username: admin
design:
show_skill_percentage: false
username: me
- block: resume-awards
content:
title: Awards
username: admin
username: me
- block: resume-languages
content:
title: Languages
username: admin
username: me
---
@@ -2,10 +2,10 @@
title: 'An example conference paper'
# Authors
# If you created a profile for a user (e.g. the default `admin` user), write the username (folder name) here
# If you created a profile for a user (e.g. the default `me` user), write the username (folder name) here
# and it will be replaced with their full name and linked to their profile.
authors:
- admin
- me
- Robert Ford
# Author notes (optional)
@@ -1,7 +1,7 @@
---
title: "An example journal article"
authors:
- admin
- me
- Robert Ford
author_notes:
- "Equal contribution"
@@ -1,7 +1,7 @@
---
title: "An example preprint / working paper"
authors:
- admin
- me
date: "2019-04-07T00:00:00Z"
# Schedule page publish date (NOT publication's date).
+116
View File
@@ -0,0 +1,116 @@
schema: hugoblox/author/v1
slug: me
is_owner: true
name:
display: Dr. Alex Johnson
given: Alex
family: Johnson
alternate: '' # Example: 王伟
pronunciation: '' # Example: Wáng Wěi
pronouns: he/him
postnominals:
- PhD
- MIEEE
status:
icon: "☕️"
role: Senior AI Research Scientist
bio: |
Alex Johnson is a Senior AI Research Scientist at Meta AI. His research
has been published in top conferences like NeurIPS and ICML, with over
10,000 citations. Alex is passionate about pushing the boundaries of AI
while ensuring ethical development.
affiliations:
- name: Meta AI
url: https://ai.meta.com/
links:
- icon: at-symbol
url: mailto:alex.johnson@example.com
label: E-mail Me
- icon: brands/x
url: https://x.com/example
- icon: brands/github
url: https://github.com/example
- icon: brands/linkedin
url: https://www.linkedin.com/in/example/
- icon: academicons/google-scholar
url: https://scholar.google.com/citations?user=example
- icon: academicons/orcid
url: https://orcid.org/0000-0002-1825-0097
interests:
- Large Language Models
- Computer Vision
- Reinforcement Learning
- AI Ethics
education:
- degree: PhD Computer Science
institution: Stanford University
start: 2015-09-01
end: 2019-06-30
summary: |
Thesis on scaling laws for neural language models. Published 5 papers in NeurIPS and ICML.
button:
text: Download dissertation
url: /resume.pdf
icon: hero/arrow-down-tray
- degree: MS Computer Science
institution: Carnegie Mellon University
start: 2013-09-01
end: 2015-05-31
- degree: BS Computer Science
institution: MIT
start: 2009-09-01
end: 2013-05-31
experience:
- role: Senior AI Research Scientist
org: Meta AI
start: 2020-01-01
summary: |
Leading research on multimodal AI systems. Contributed to Llama 2 and other open-source models.
button:
text: View portfolio
url: /
icon: hero/arrow-down-tray
- role: AI Research Intern
org: OpenAI
start: 2019-06-01
end: 2019-12-31
summary: |
Worked on GPT-3 scaling. Co-authored paper on prompt engineering.
skills:
- name: Technical Skills
items:
- label: Pytho
level: 5
- label: Machine Learning
level: 5
- label: Cloud Computing
level: 4
- name: Research & Communication
items:
- label: Academic Writing
level: 5
- label: Conference Presentations
level: 4
- label: Grant Proposals
level: 4
languages:
- name: English
level: 5
label: Native
- name: Spanish
level: 4
label: Fluent
awards:
- title: Best Paper Award
awarder: NeurIPS
date: "2022-12-01"
summary: Awarded for groundbreaking work on efficient training of large models.
icon: hero/trophy
@@ -0,0 +1,22 @@
{
"image": "mcr.microsoft.com/devcontainers/go:1",
"features": {
"ghcr.io/devcontainers/features/hugo:1": {
"extended": true,
"version": "0.152.2"
},
"ghcr.io/devcontainers/features/node:1": {}
},
"customizations": {
"vscode": {
"extensions": [
"hugoblox.hugoblox-studio"
]
}
},
"postCreateCommand": "pnpm install --no-frozen-lockfile && hugo --logLevel debug && pnpm dlx pagefind --source 'public' --verbose",
"postAttachCommand": "(code --command workbench.view.extension.hugoblox-studio || true) && hugo server --disableFastRender -D --port 1313",
"forwardPorts": [
1313
]
}
+7 -2
View File
@@ -1,7 +1,6 @@
name: Deploy website to GitHub Pages
env:
WC_HUGO_VERSION: '0.152.2'
NODE_VERSION: '20'
on:
@@ -42,6 +41,12 @@ jobs:
if: hashFiles('package.json') != ''
uses: pnpm/action-setup@v4
- name: Get Hugo Version
id: hugo-version
run: |
VERSION=$(grep "hugo_version" hugoblox.yaml | awk '{print $2}' | tr -d "'\"")
echo "HUGO_VERSION=$VERSION" >> $GITHUB_ENV
- name: Install dependencies
run: |
# Install Tailwind CLI if package.json exists
@@ -53,7 +58,7 @@ jobs:
- name: Setup Hugo
uses: peaceiris/actions-hugo@v3
with:
hugo-version: ${{ env.WC_HUGO_VERSION }}
hugo-version: ${{ env.HUGO_VERSION }}
extended: true
# Cache dependencies (Go modules, node_modules) - stable, rarely changes
+2 -2
View File
@@ -5,8 +5,8 @@ type: landing
sections:
- block: resume-biography
content:
# The user's folder name in content/authors/
username: admin
# Author slug (data/authors/<slug>.yaml)
username: me
design:
spacing:
padding: [0, 0, 0, 0]
@@ -1,62 +0,0 @@
---
# Display name
title: 吳健雄
# Name pronunciation (optional)
name_pronunciation: Chien Shiung Wu
# Full name (for SEO)
first_name: Shiung Wu
last_name: Chien
# Pronouns (optional)
pronouns: she/her
# Status emoji
status:
icon: ☕️
# Is this the primary user of the site?
superuser: true
# Role/position/tagline
role: Professor of Artificial Intelligence
# Organizations/Affiliations to show in About widget
organizations:
- name: Stanford University
url: https://www.stanford.edu/
# Short bio (displayed in user profile at end of posts)
bio: My research interests include distributed robotics, mobile computing and programmable matter.
# Social Networking
# Need to use another icon? Simply download the SVG icon to your `assets/media/icons/` folder.
profiles:
- icon: at-symbol
url: 'mailto:your-email@example.com'
label: E-mail Me
- icon: brands/x
url: https://x.com/BuildLore
- icon: brands/instagram
url: https://www.instagram.com/
- icon: brands/linkedin
url: https://www.linkedin.com/
# Link to a PDF of your resume/CV - upload it to `static/uploads/resume.pdf`
- icon: academicons/cv
url: uploads/resume.pdf
label: Download my resume
- icon: rss
url: ./post/index.xml
label: Subscribe to my blog via RSS feed
# Highlight the author in author lists? (true/false)
highlight_name: true
# Author's website URL
website: ""
---
Chien Shiung Wu is a professor of artificial intelligence at the Stanford AI Lab. Her research interests include
distributed robotics, mobile computing and programmable matter. She leads the Robotic Neurobiology group, which develops
self-reconfiguring robots, systems of self-organizing robots, and mobile sensor networks.
@@ -3,7 +3,7 @@ title: 📈 Communicate your results effectively with the best data visualizatio
summary: Use popular tools such as Plotly, Mermaid, and data frames.
date: 2023-10-25
authors:
- admin
- me
tags:
- Hugo
- Hugo Blox
@@ -9,7 +9,7 @@ image:
caption: 'Image credit: [**Unsplash**](https://unsplash.com)'
authors:
- admin
- me
- Ted
tags:
@@ -3,7 +3,7 @@ title: ✅ Manage your projects
summary: Easily manage your projects - create ideation mind maps, Gantt charts, todo lists, and more!
date: 2023-10-23
authors:
- admin
- me
tags:
- Hugo Blox
- Markdown
@@ -3,7 +3,7 @@ title: 🧠 Sharpen your thinking with a second brain
summary: Create a personal knowledge base and share your knowledge with your peers.
date: 2023-10-26
authors:
- admin
- me
tags:
- Second Brain
- Markdown
@@ -4,7 +4,7 @@ summary: Embed videos, podcasts, code, LaTeX math, and even test students!
date: 2023-10-24
math: true
authors:
- admin
- me
tags:
- Hugo
- Hugo Blox Builder
+32
View File
@@ -0,0 +1,32 @@
schema: hugoblox/author/v1
slug: me
is_owner: true
name:
display: 吳健雄
given: Shiung Wu
family: Chien
pronunciation: Chien Shiung Wu
pronouns: she/her
status:
icon: "☕️"
role: Professor of Artificial Intelligence
bio: My research interests include distributed robotics, mobile computing and programmable matter.
affiliations:
- name: Stanford University
url: https://www.stanford.edu/
links:
- icon: at-symbol
url: mailto:your-email@example.com
label: E-mail Me
- icon: brands/x
url: https://x.com/BuildLore
- icon: brands/instagram
url: https://www.instagram.com/
- icon: brands/linkedin
url: https://www.linkedin.com/
- icon: academicons/cv
url: uploads/resume.pdf
label: Download my resume
- icon: rss
url: ./post/index.xml
label: Subscribe to my blog via RSS feed
@@ -0,0 +1,22 @@
{
"image": "mcr.microsoft.com/devcontainers/go:1",
"features": {
"ghcr.io/devcontainers/features/hugo:1": {
"extended": true,
"version": "0.152.2"
},
"ghcr.io/devcontainers/features/node:1": {}
},
"customizations": {
"vscode": {
"extensions": [
"hugoblox.hugoblox-studio"
]
}
},
"postCreateCommand": "pnpm install --no-frozen-lockfile && hugo --logLevel debug && pnpm dlx pagefind --source 'public' --verbose",
"postAttachCommand": "(code --command workbench.view.extension.hugoblox-studio || true) && hugo server --disableFastRender -D --port 1313",
"forwardPorts": [
1313
]
}
+7 -2
View File
@@ -1,7 +1,6 @@
name: Deploy website to GitHub Pages
env:
WC_HUGO_VERSION: '0.152.2'
NODE_VERSION: '20'
on:
@@ -42,6 +41,12 @@ jobs:
if: hashFiles('package.json') != ''
uses: pnpm/action-setup@v4
- name: Get Hugo Version
id: hugo-version
run: |
VERSION=$(grep "hugo_version" hugoblox.yaml | awk '{print $2}' | tr -d "'\"")
echo "HUGO_VERSION=$VERSION" >> $GITHUB_ENV
- name: Install dependencies
run: |
# Install Tailwind CLI if package.json exists
@@ -53,7 +58,7 @@ jobs:
- name: Setup Hugo
uses: peaceiris/actions-hugo@v3
with:
hugo-version: ${{ env.WC_HUGO_VERSION }}
hugo-version: ${{ env.HUGO_VERSION }}
extended: true
# Cache dependencies (Go modules, node_modules) - stable, rarely changes
@@ -1,62 +0,0 @@
---
# Display name
title: 吳健雄
# Name pronunciation (optional)
name_pronunciation: Chien Shiung Wu
# Full name (for SEO)
first_name: Shiung Wu
last_name: Chien
# Pronouns (optional)
pronouns: she/her
# Status emoji
status:
icon: ☕️
# Is this the primary user of the site?
superuser: true
# Role/position/tagline
role: Professor of Artificial Intelligence
# Organizations/Affiliations to show in About widget
organizations:
- name: Stanford University
url: https://www.stanford.edu/
# Short bio (displayed in user profile at end of posts)
bio: My research interests include distributed robotics, mobile computing and programmable matter.
# Social Networking
# Need to use another icon? Simply download the SVG icon to your `assets/media/icons/` folder.
profiles:
- icon: at-symbol
url: 'mailto:your-email@example.com'
label: E-mail Me
- icon: brands/x
url: https://x.com/BuildLore
- icon: brands/instagram
url: https://www.instagram.com/
- icon: brands/linkedin
url: https://www.linkedin.com/
# Link to a PDF of your resume/CV - upload it to `static/uploads/resume.pdf`
- icon: academicons/cv
url: uploads/resume.pdf
label: Download my resume
- icon: rss
url: ./post/index.xml
label: Subscribe to my blog via RSS feed
# Highlight the author in author lists? (true/false)
highlight_name: true
# Author's website URL
website: ""
---
Chien Shiung Wu is a professor of artificial intelligence at the Stanford AI Lab. Her research interests include
distributed robotics, mobile computing and programmable matter. She leads the Robotic Neurobiology group, which develops
self-reconfiguring robots, systems of self-organizing robots, and mobile sensor networks.
@@ -3,7 +3,7 @@ title: v1.0.0 Released!
summary: Hugo v1.0.0 released. Download now and follow the guide to get started in 5 minutes!
date: 2023-01-01
authors:
- admin
- me
tags:
- Hugo
- Hugo Blox
@@ -3,7 +3,7 @@ title: v2.0.0 Released!
summary: Hugo v2.0.0 released. Download now and follow the guide to get started in 5 minutes!
date: 2024-01-19
authors:
- admin
- me
tags:
- Hugo
- Hugo Blox
@@ -1,6 +1,8 @@
---
title: Nvidia
summary: nvidia.com
external_link: https://research.nvidia.com/labs/toronto-ai/
links:
- type: site
url: https://research.nvidia.com/labs/toronto-ai/
date: 2024-02-17
---
@@ -0,0 +1,32 @@
schema: hugoblox/author/v1
slug: me
is_owner: true
name:
display: 吳健雄
given: Shiung Wu
family: Chien
pronunciation: Chien Shiung Wu
pronouns: she/her
status:
icon: "☕️"
role: Professor of Artificial Intelligence
bio: My research interests include distributed robotics, mobile computing and programmable matter.
affiliations:
- name: Stanford University
url: https://www.stanford.edu/
links:
- icon: at-symbol
url: mailto:your-email@example.com
label: E-mail Me
- icon: brands/x
url: https://x.com/BuildLore
- icon: brands/instagram
url: https://www.instagram.com/
- icon: brands/linkedin
url: https://www.linkedin.com/
- icon: academicons/cv
url: uploads/resume.pdf
label: Download my resume
- icon: rss
url: ./post/index.xml
label: Subscribe to my blog via RSS feed
@@ -0,0 +1,22 @@
{
"image": "mcr.microsoft.com/devcontainers/go:1",
"features": {
"ghcr.io/devcontainers/features/hugo:1": {
"extended": true,
"version": "0.152.2"
},
"ghcr.io/devcontainers/features/node:1": {}
},
"customizations": {
"vscode": {
"extensions": [
"hugoblox.hugoblox-studio"
]
}
},
"postCreateCommand": "pnpm install --no-frozen-lockfile && hugo --logLevel debug && pnpm dlx pagefind --source 'public' --verbose",
"postAttachCommand": "(code --command workbench.view.extension.hugoblox-studio || true) && hugo server --disableFastRender -D --port 1313",
"forwardPorts": [
1313
]
}
+7 -2
View File
@@ -1,7 +1,6 @@
name: Deploy website to GitHub Pages
env:
WC_HUGO_VERSION: '0.152.2'
NODE_VERSION: '20'
on:
@@ -42,6 +41,12 @@ jobs:
if: hashFiles('package.json') != ''
uses: pnpm/action-setup@v4
- name: Get Hugo Version
id: hugo-version
run: |
VERSION=$(grep "hugo_version" hugoblox.yaml | awk '{print $2}' | tr -d "'\"")
echo "HUGO_VERSION=$VERSION" >> $GITHUB_ENV
- name: Install dependencies
run: |
# Install Tailwind CLI if package.json exists
@@ -53,7 +58,7 @@ jobs:
- name: Setup Hugo
uses: peaceiris/actions-hugo@v3
with:
hugo-version: ${{ env.WC_HUGO_VERSION }}
hugo-version: ${{ env.HUGO_VERSION }}
extended: true
# Cache dependencies (Go modules, node_modules) - stable, rarely changes
@@ -1,62 +0,0 @@
---
# Display name
title: 吳健雄
# Name pronunciation (optional)
name_pronunciation: Chien Shiung Wu
# Full name (for SEO)
first_name: Shiung Wu
last_name: Chien
# Pronouns (optional)
pronouns: she/her
# Status emoji
status:
icon: ☕️
# Is this the primary user of the site?
superuser: true
# Role/position/tagline
role: Professor of Artificial Intelligence
# Organizations/Affiliations to show in About widget
organizations:
- name: Stanford University
url: https://www.stanford.edu/
# Short bio (displayed in user profile at end of posts)
bio: My research interests include distributed robotics, mobile computing and programmable matter.
# Social Networking
# Need to use another icon? Simply download the SVG icon to your `assets/media/icons/` folder.
profiles:
- icon: at-symbol
url: 'mailto:your-email@example.com'
label: E-mail Me
- icon: brands/x
url: https://x.com/BuildLore
- icon: brands/instagram
url: https://www.instagram.com/
- icon: brands/linkedin
url: https://www.linkedin.com/
# Link to a PDF of your resume/CV - upload it to `static/uploads/resume.pdf`
- icon: academicons/cv
url: uploads/resume.pdf
label: Download my resume
- icon: rss
url: ./post/index.xml
label: Subscribe to my blog via RSS feed
# Highlight the author in author lists? (true/false)
highlight_name: true
# Author's website URL
website: ""
---
Chien Shiung Wu is a professor of artificial intelligence at the Stanford AI Lab. Her research interests include
distributed robotics, mobile computing and programmable matter. She leads the Robotic Neurobiology group, which develops
self-reconfiguring robots, systems of self-organizing robots, and mobile sensor networks.
@@ -3,7 +3,7 @@ title: 📈 Communicate your results effectively with the best data visualizatio
summary: Use popular tools such as Plotly, Mermaid, and data frames.
date: 2023-10-25
authors:
- admin
- me
tags:
- Hugo
- Hugo Blox
@@ -9,7 +9,7 @@ image:
caption: 'Image credit: [**Unsplash**](https://unsplash.com)'
authors:
- admin
- me
- Ted
tags:
@@ -3,7 +3,7 @@ title: ✅ Manage your projects
summary: Easily manage your projects - create ideation mind maps, Gantt charts, todo lists, and more!
date: 2023-10-23
authors:
- admin
- me
tags:
- Hugo Blox
- Markdown
@@ -3,7 +3,7 @@ title: 🧠 Sharpen your thinking with a second brain
summary: Create a personal knowledge base and share your knowledge with your peers.
date: 2023-10-26
authors:
- admin
- me
tags:
- Second Brain
- Markdown
@@ -4,7 +4,7 @@ summary: Embed videos, podcasts, code, LaTeX math, and even test students!
date: 2023-10-24
math: true
authors:
- admin
- me
tags:
- Hugo
- Hugo Blox Builder
@@ -0,0 +1,32 @@
schema: hugoblox/author/v1
slug: me
is_owner: true
name:
display: 吳健雄
given: Shiung Wu
family: Chien
pronunciation: Chien Shiung Wu
pronouns: she/her
status:
icon: "☕️"
role: Professor of Artificial Intelligence
bio: My research interests include distributed robotics, mobile computing and programmable matter.
affiliations:
- name: Stanford University
url: https://www.stanford.edu/
links:
- icon: at-symbol
url: mailto:your-email@example.com
label: E-mail Me
- icon: brands/x
url: https://x.com/BuildLore
- icon: brands/instagram
url: https://www.instagram.com/
- icon: brands/linkedin
url: https://www.linkedin.com/
- icon: academicons/cv
url: uploads/resume.pdf
label: Download my resume
- icon: rss
url: ./post/index.xml
label: Subscribe to my blog via RSS feed
@@ -0,0 +1,22 @@
{
"image": "mcr.microsoft.com/devcontainers/go:1",
"features": {
"ghcr.io/devcontainers/features/hugo:1": {
"extended": true,
"version": "0.152.2"
},
"ghcr.io/devcontainers/features/node:1": {}
},
"customizations": {
"vscode": {
"extensions": [
"hugoblox.hugoblox-studio"
]
}
},
"postCreateCommand": "pnpm install --no-frozen-lockfile && hugo --logLevel debug && pnpm dlx pagefind --source 'public' --verbose",
"postAttachCommand": "(code --command workbench.view.extension.hugoblox-studio || true) && hugo server --disableFastRender -D --port 1313",
"forwardPorts": [
1313
]
}
+7 -2
View File
@@ -1,7 +1,6 @@
name: Deploy website to GitHub Pages
env:
WC_HUGO_VERSION: '0.152.2'
NODE_VERSION: '20'
on:
@@ -42,6 +41,12 @@ jobs:
if: hashFiles('package.json') != ''
uses: pnpm/action-setup@v4
- name: Get Hugo Version
id: hugo-version
run: |
VERSION=$(grep "hugo_version" hugoblox.yaml | awk '{print $2}' | tr -d "'\"")
echo "HUGO_VERSION=$VERSION" >> $GITHUB_ENV
- name: Install dependencies
run: |
# Install Tailwind CLI if package.json exists
@@ -53,7 +58,7 @@ jobs:
- name: Setup Hugo
uses: peaceiris/actions-hugo@v3
with:
hugo-version: ${{ env.WC_HUGO_VERSION }}
hugo-version: ${{ env.HUGO_VERSION }}
extended: true
# Cache dependencies (Go modules, node_modules) - stable, rarely changes
+1 -1
View File
@@ -13,7 +13,7 @@ sections:
- block: resume-biography
content:
# The user's folder name in content/authors/
username: admin
username: me
design:
biography:
style: 'text-align: justify; font-size: 0.8em;'
@@ -1,169 +0,0 @@
---
# Display name
title: Alex Johnson
# Name pronunciation (optional)
name_pronunciation: ''
# Full name (for SEO)
first_name: Alex
last_name: Johnson
# Pronouns (optional)
pronouns: he/him
# Status emoji
status:
icon: 🚀
# Is this the primary user of the site?
superuser: true
# Highlight the author in author lists? (true/false)
highlight_name: true
# Role/position/tagline
role: Senior AI Research Scientist
# Organizations/Affiliations to display in Biography blox
organizations:
- name: Meta AI
url: https://ai.meta.com/
# Social network links
# Need to use another icon? Simply download the SVG icon to your `assets/media/icons/` folder.
profiles:
- icon: at-symbol
url: 'mailto:alex.johnson@meta.com'
label: E-mail Me
- icon: brands/x
url: https://x.com/AlexAIResearch
- icon: brands/github
url: https://github.com/alexjohnsonai
- icon: brands/linkedin
url: https://www.linkedin.com/in/alexjohnsonai/
- icon: academicons/google-scholar
url: https://scholar.google.com/citations?user=alexjohnson
- icon: academicons/orcid
url: https://orcid.org/0000-0002-1825-0097
interests:
- Large Language Models
- Computer Vision
- Reinforcement Learning
- AI Ethics
education:
- area: PhD Computer Science (AI Focus)
institution: Stanford University
date_start: 2015-09-01
date_end: 2019-06-30
summary: |
Thesis on _Scaling Laws for Neural Language Models_. Supervised by Prof. Andrew Ng. Published 5 papers in NeurIPS and ICML, with 2 best paper awards.
button:
text: 'Read Thesis'
url: 'https://example.com/thesis.pdf'
- area: MS Computer Science
institution: Carnegie Mellon University
date_start: 2013-09-01
date_end: 2015-05-31
summary: |
GPA: 4.0/4.0
Specialized in machine learning and robotics.
- area: BS Computer Science
institution: MIT
date_start: 2009-09-01
date_end: 2013-05-31
summary: |
GPA: 3.9/4.0
Minored in Mathematics. President of AI Club.
work:
- position: Senior AI Research Scientist
company_name: Meta AI
company_url: 'https://ai.meta.com/'
company_logo: ''
date_start: 2020-01-01
date_end: ''
summary: |2-
Leading research on multimodal AI systems. Contributed to Llama 2 and other open-source models. 50+ citations in 3 years.
- position: AI Research Intern
company_name: OpenAI
company_url: 'https://openai.com/'
company_logo: ''
date_start: 2019-06-01
date_end: 2019-12-31
summary: |
Worked on GPT-3 scaling. Co-authored paper on prompt engineering.
# Skills
# Add your own SVG icons to `assets/media/icons/`
skills:
- name: Technical Skills
items:
- name: Python & PyTorch
description: ''
percent: 95
icon: code-bracket
- name: Machine Learning
description: ''
percent: 100
icon: chart-bar
- name: Cloud Computing (AWS/GCP)
description: ''
percent: 85
icon: cloud
- name: Hobbies
color: '#eeac02'
color_border: '#f0bf23'
items:
- name: Hiking in the Rockies
description: ''
percent: 80
icon: person-simple-walk
- name: Building Custom PCs
description: ''
percent: 90
icon: cpu-chip
- name: Sci-Fi Reading
description: ''
percent: 70
icon: book-open
languages:
- name: English
percent: 100
- name: Spanish
percent: 50
# Awards.
# Add/remove as many awards below as you like.
# Only `title`, `awarder`, and `date` are required.
# Begin multi-line `summary` with YAML's `|` or `|2-` multi-line prefix and indent 2 spaces below.
awards:
- title: Best Paper Award
url: https://neurips.cc/
date: '2022-12-01'
awarder: NeurIPS
icon: hero/trophy
summary: |
Awarded for groundbreaking work on efficient training of large models.
- title: AI Innovation Grant
url: https://www.nsf.gov/
date: '2021-06-15'
awarder: National Science Foundation
icon: hero/currency-dollar
summary: |
$500,000 grant for research in ethical AI development.
- title: Outstanding PhD Thesis
url: https://www.stanford.edu/
date: '2019-06-30'
awarder: Stanford University
icon: hero/academic-cap
summary: |
Recognized for contributions to scaling laws in deep learning.
---
Alex Johnson is a Senior AI Research Scientist at Meta AI, where he leads groundbreaking work on large language models and multimodal systems. His research has been published in top conferences like NeurIPS and ICML, with over 10,000 citations. Alex is passionate about pushing the boundaries of AI while ensuring ethical development. Outside of work, he enjoys hiking in the Rockies, building custom PCs, and mentoring the next generation of AI talent.
+125
View File
@@ -0,0 +1,125 @@
schema: hugoblox/author/v1
slug: me
is_owner: true
name:
display: Alex Johnson
given: Alex
family: Johnson
pronouns: he/him
status:
icon: "🚀"
role: Senior AI Research Scientist
bio: >
Alex Johnson is a Senior AI Research Scientist at Meta AI, where he leads groundbreaking work on large language models
and multimodal systems. His research has been published in top conferences like NeurIPS and ICML, with over 10,000
citations. Alex is passionate about pushing the boundaries of AI while ensuring ethical development. Outside of work,
he enjoys hiking in the Rockies, building custom PCs, and mentoring the next generation of AI talent.
affiliations:
- name: Meta AI
url: https://ai.meta.com/
links:
- icon: at-symbol
url: mailto:alex.johnson@meta.com
label: E-mail Me
- icon: brands/x
url: https://x.com/AlexAIResearch
- icon: brands/github
url: https://github.com/alexjohnsonai
- icon: brands/linkedin
url: https://www.linkedin.com/in/alexjohnsonai/
- icon: academicons/google-scholar
url: https://scholar.google.com/citations?user=alexjohnson
- icon: academicons/orcid
url: https://orcid.org/0000-0002-1825-0097
interests:
- Large Language Models
- Computer Vision
- Reinforcement Learning
- AI Ethics
education:
- degree: PhD Computer Science (AI Focus)
institution: Stanford University
start: 2015-09-01
end: 2019-06-30
summary: |
Thesis on _Scaling Laws for Neural Language Models_. Supervised by Prof. Andrew Ng. Published 5 papers in NeurIPS and ICML, with 2 best paper awards.
button:
text: 'Read Thesis'
url: 'https://example.com/thesis.pdf'
- degree: MS Computer Science
institution: Carnegie Mellon University
start: 2013-09-01
end: 2015-05-31
summary: |
GPA: 4.0/4.0
Specialized in machine learning and robotics.
- degree: BS Computer Science
institution: MIT
start: 2009-09-01
end: 2013-05-31
summary: |
GPA: 3.9/4.0
Minored in Mathematics. President of AI Club.
experience:
- role: Senior AI Research Scientist
org: Meta AI
start: 2020-01-01
summary: |
Leading research on multimodal AI systems. Contributed to Llama 2 and other open-source models. 50+ citations in 3 years.
- role: AI Research Intern
org: OpenAI
start: 2019-06-01
end: 2019-12-31
summary: |
Worked on GPT-3 scaling. Co-authored paper on prompt engineering.
skills:
- name: Technical Skills
items:
- label: Python & PyTorch
level: 5
icon: code-bracket
- label: Machine Learning
level: 5
icon: chart-bar
- label: Cloud Computing (AWS/GCP)
level: 4
icon: cloud
- name: Hobbies
color: '#eeac02'
color_border: '#f0bf23'
items:
- label: Hiking in the Rockies
level: 4
icon: person-simple-walk
- label: Building Custom PCs
level: 5
icon: cpu-chip
- label: Sci-Fi Reading
level: 4
icon: book-open
languages:
- name: English
level: 5
- name: Spanish
level: 3
awards:
- title: Best Paper Award
url: https://neurips.cc/
date: '2022-12-01'
awarder: NeurIPS
icon: hero/trophy
summary: Awarded for groundbreaking work on efficient training of large models.
- title: AI Innovation Grant
url: https://www.nsf.gov/
date: '2021-06-15'
awarder: National Science Foundation
icon: hero/currency-dollar
summary: $500,000 grant for research in ethical AI development.
- title: Outstanding PhD Thesis
url: https://www.stanford.edu/
date: '2019-06-30'
awarder: Stanford University
icon: hero/academic-cap
summary: Recognized for contributions to scaling laws in deep learning.
@@ -0,0 +1,22 @@
{
"image": "mcr.microsoft.com/devcontainers/go:1",
"features": {
"ghcr.io/devcontainers/features/hugo:1": {
"extended": true,
"version": "0.152.2"
},
"ghcr.io/devcontainers/features/node:1": {}
},
"customizations": {
"vscode": {
"extensions": [
"hugoblox.hugoblox-studio"
]
}
},
"postCreateCommand": "pnpm install --no-frozen-lockfile && hugo --logLevel debug && pnpm dlx pagefind --source 'public' --verbose",
"postAttachCommand": "(code --command workbench.view.extension.hugoblox-studio || true) && hugo server --disableFastRender -D --port 1313",
"forwardPorts": [
1313
]
}
+7 -2
View File
@@ -1,7 +1,6 @@
name: Deploy website to GitHub Pages
env:
WC_HUGO_VERSION: '0.152.2'
NODE_VERSION: '20'
on:
@@ -42,6 +41,12 @@ jobs:
if: hashFiles('package.json') != ''
uses: pnpm/action-setup@v4
- name: Get Hugo Version
id: hugo-version
run: |
VERSION=$(grep "hugo_version" hugoblox.yaml | awk '{print $2}' | tr -d "'\"")
echo "HUGO_VERSION=$VERSION" >> $GITHUB_ENV
- name: Install dependencies
run: |
# Install Tailwind CLI if package.json exists
@@ -53,7 +58,7 @@ jobs:
- name: Setup Hugo
uses: peaceiris/actions-hugo@v3
with:
hugo-version: ${{ env.WC_HUGO_VERSION }}
hugo-version: ${{ env.HUGO_VERSION }}
extended: true
# Cache dependencies (Go modules, node_modules) - stable, rarely changes
+5 -5
View File
@@ -13,7 +13,7 @@ design:
sections:
- block: biography
content:
username: admin
username: me
# Show a call-to-action button under your biography? (optional)
button:
text: Download Résumé
@@ -31,7 +31,7 @@ sections:
shape: rounded # Options: circle (default), square, rounded
- block: experience
content:
username: admin
username: me
design:
# Hugo date format
date_format: 'January 2006'
@@ -40,13 +40,13 @@ sections:
- block: skills
content:
title: Skills & Hobbies
username: admin
username: me
- block: awards
content:
title: Awards
username: admin
username: me
- block: languages
content:
title: Languages
username: admin
username: me
---
@@ -1,37 +1,24 @@
---
# Display name
title: 吳健雄
# Name pronunciation (optional)
name_pronunciation: Chien Shiung Wu
# Full name (for SEO)
first_name: Shiung Wu
last_name: Chien
# Pronouns (optional)
pronouns: she/her
# Status emoji
schema: hugoblox/author/v1
slug: me
is_owner: true
name:
display: 吳健雄
given: Shiung Wu
family: Chien
pronunciation: Chien Shiung Wu
pronouns: she/her
status:
icon: ☕️
# Is this the primary user of the site?
superuser: true
# Role/position/tagline
icon: "☕️"
role: Director of Cloud Infrastructure
# Organizations/Affiliations to display in Biography blox
organizations:
bio: >
I am currently Director of Cloud Infrastructure at GenCoin where I am leading
teams on edge computing, deep sea data center architecture, and hivemind optimization.
affiliations:
- name: GenCoin
url: https://www.example.com/
# Social network links
# Need to use another icon? Simply download the SVG icon to your `assets/media/icons/` folder.
profiles:
links:
- icon: at-symbol
url: 'mailto:your-email@example.com'
url: mailto:your-email@example.com
label: E-mail Me
- icon: brands/x
url: https://x.com/BuildLore
@@ -41,21 +28,20 @@ profiles:
url: https://www.linkedin.com/
- icon: brands/instagram
url: https://www.instagram.com/
education:
- area: PhD Artificial Intelligence
- degree: PhD Artificial Intelligence
institution: Stanford University
date_start: 2016-01-01
date_end: 2020-12-31
start: 2016-01-01
end: 2020-12-31
summary: |
Thesis on _Why LLMs are awesome_. Supervised by [Prof Joe Smith](https://example.com). Presented papers at 5 IEEE conferences with the contributions being published in 2 Springer journals.
button:
text: 'Read Thesis'
url: 'https://example.com'
- area: MEng Artificial Intelligence
- degree: MEng Artificial Intelligence
institution: Massachusetts Institute of Technology
date_start: 2016-01-01
date_end: 2020-12-31
start: 2016-01-01
end: 2020-12-31
summary: |
GPA: 3.8/4.0
@@ -63,10 +49,10 @@ education:
- lorem ipsum dolor sit amet, consectetur adipiscing elit
- lorem ipsum dolor sit amet, consectetur adipiscing elit
- lorem ipsum dolor sit amet, consectetur adipiscing elit
- area: BSc Artificial Intelligence
- degree: BSc Artificial Intelligence
institution: Massachusetts Institute of Technology
date_start: 2016-01-01
date_end: 2020-12-31
start: 2016-01-01
end: 2020-12-31
summary: |
GPA: 3.4/4.0
@@ -74,76 +60,50 @@ education:
- lorem ipsum dolor sit amet, consectetur adipiscing elit
- lorem ipsum dolor sit amet, consectetur adipiscing elit
- lorem ipsum dolor sit amet, consectetur adipiscing elit
work:
- position: Director of Cloud Infrastructure
company_name: GenCoin
company_url: ''
company_logo: ''
date_start: 2021-01-01
date_end: ''
summary: |2-
experience:
- role: Director of Cloud Infrastructure
org: GenCoin
start: 2021-01-01
summary: |
Responsibilities include:
- lorem ipsum dolor sit amet, consectetur adipiscing elit
- lorem ipsum dolor sit amet, consectetur adipiscing elit
- lorem ipsum dolor sit amet, consectetur adipiscing elit
- position: Backend Software Engineer
company_name: X
company_url: ''
company_logo: ''
date_start: 2016-01-01
date_end: 2020-12-31
- role: Backend Software Engineer
org: X
start: 2016-01-01
end: 2020-12-31
summary: |
Responsibilities include:
- Migrated infrastructure to a new data center
- lorem ipsum dolor sit amet, consectetur adipiscing elit
- lorem ipsum dolor sit amet, consectetur adipiscing elit
# Skills
# Add your own SVG icons to `assets/media/icons/`
skills:
- name: Technical Skills
items:
- name: Python
description: ''
percent: 80
icon: devicon/python
- name: RStudio
description: ''
percent: 100
icon: devicon/rstudio
- name: PyTorch
description: ''
percent: 40
icon: devicon/pytorch
- label: Python
level: 4
- label: RStudio
level: 5
- label: PyTorch
level: 2
- name: Hobbies
color: '#eeac02'
color_border: '#f0bf23'
items:
- name: Hiking
description: ''
percent: 60
icon: person-simple-walk
- name: Cats
description: ''
percent: 100
icon: cat
- name: Photography
description: ''
percent: 80
icon: camera
- label: Hiking
level: 3
- label: Cats
level: 5
- label: Photography
level: 4
languages:
- name: English
percent: 100
level: 5
- name: Chinese
percent: 75
level: 4
- name: Portuguese
percent: 25
# Awards.
# Add/remove as many awards below as you like.
# Only `title`, `awarder`, and `date` are required.
# Begin multi-line `summary` with YAML's `|` or `|2-` multi-line prefix and indent 2 spaces below.
level: 1
awards:
- title: Neural Networks and Deep Learning
url: https://www.coursera.org/learn/neural-networks-deep-learning
@@ -170,6 +130,3 @@ awards:
icon: brands/datacamp
summary: |
Object-oriented programming (OOP) lets you specify relationships between functions and the objects that they can act on, helping you manage complexity in your code. This is an intermediate level course, providing an introduction to OOP, using the S3 and R6 systems. S3 is a great day-to-day R programming tool that simplifies some of the functions that you write. R6 is especially useful for industry-specific analyses, working with web APIs, and building GUIs.
---
I am currently Director of Cloud Infrastructure at GenCoin where I am leading teams on edge computing, deep sea data center architecture, and hivemind optimization.