diff --git a/themes/bonsai/layouts/index.rss.xml b/themes/bonsai/layouts/index.rss.xml
index 31bcc6f..78729dd 100644
--- a/themes/bonsai/layouts/index.rss.xml
+++ b/themes/bonsai/layouts/index.rss.xml
@@ -16,7 +16,16 @@
{{ site.Language.LanguageCode | default site.LanguageCode }}
{{ $now.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}
- {{- range site.Params.links }}
+ {{- /* Flatten params.links + params.sections.*.links into a single list. */ -}}
+ {{- $allLinks := slice -}}
+ {{- if site.Params.sections -}}
+ {{- range site.Params.sections -}}
+ {{- range .links -}}{{- $allLinks = $allLinks | append . -}}{{- end -}}
+ {{- end -}}
+ {{- else -}}
+ {{- $allLinks = site.Params.links -}}
+ {{- end -}}
+ {{- range $allLinks }}
-
{{ .title | transform.XMLEscape }}
{{ .url }}
diff --git a/themes/bonsai/layouts/partials/bio-card.html b/themes/bonsai/layouts/partials/bio-card.html
index 732afa2..bfaad3c 100644
--- a/themes/bonsai/layouts/partials/bio-card.html
+++ b/themes/bonsai/layouts/partials/bio-card.html
@@ -2,11 +2,11 @@
{{- $tagline := site.Params.tagline -}}
{{- $bio := site.Params.bio -}}
{{- $links := site.Params.links -}}
+{{- $sections := site.Params.sections -}}
{{- $layout := site.Params.layout | default "stack" -}}
-{{- $validLayouts := slice "stack" "grid" "inline" -}}
-{{- if not (in $validLayouts $layout) -}}
- {{- warnf "bonsai: unknown params.layout %q (expected stack|grid|inline), falling back to 'stack'" $layout -}}
- {{- $layout = "stack" -}}
+{{- $defaultLabel := i18n "nav_links_label" | default "Links" -}}
+{{- if and $sections $links -}}
+ {{- warnf "bonsai: both params.sections and params.links set; sections win, flat links ignored" -}}
{{- end -}}
@@ -22,11 +22,37 @@
{{ . | markdownify }}
{{- end }}
- {{- if $links }}
-
+ {{- if $sections -}}
+ {{- range $i, $section := $sections -}}
+ {{- $secLinks := $section.links -}}
+ {{- if $secLinks -}}
+ {{- $secLayout := $section.layout | default $layout -}}
+ {{- $secTitle := $section.title -}}
+ {{- $slug := "" -}}
+ {{- with $secTitle }}{{ $slug = . | urlize }}{{ end -}}
+ {{- if not $slug }}{{ $slug = printf "section-%d" (add $i 1) }}{{ end -}}
+
+ {{- with $secTitle }}
+ {{ . }}
+ {{- end }}
+ {{- with $section.description }}
+ {{ . | markdownify }}
+ {{- end }}
+ {{ partial "link-group.html" (dict "links" $secLinks "layout" $secLayout "label" (or $secTitle $defaultLabel)) }}
+
+ {{- end -}}
+ {{- end -}}
+ {{- else if $links -}}
+ {{ partial "link-group.html" (dict "links" $links "layout" $layout "label" $defaultLabel) }}
+ {{- end -}}
+
+ {{- if site.Params.share }}
+ {{ partial "share-button.html" . }}
{{- end }}
+
+ {{- if site.Params.vcard }}
+ {{ i18n "save_contact" | default "Save contact" }}
+ {{- end }}
+
+ {{ partial "qr-block.html" . }}
diff --git a/themes/bonsai/layouts/partials/link-button.html b/themes/bonsai/layouts/partials/link-button.html
index f553508..b1405cd 100644
--- a/themes/bonsai/layouts/partials/link-button.html
+++ b/themes/bonsai/layouts/partials/link-button.html
@@ -1,12 +1,56 @@
-{{- $title := .title -}}
-{{- $url := .url -}}
-{{- $icon := .icon -}}
+{{- /* Render one link button.
+ Context dict: { "link": {title,url,icon,image,featured,startDate,endDate,rel,note,...}, "analytics": params.analytics }
+ Reads ONLY from its dict argument (per docs/code-standards.md partial composition rule). */ -}}
+{{- $l := .link -}}
+{{- $a := .analytics -}}
+{{- /* Per-link visibility window: skip rendering when now < startDate or now > endDate.
+ Build-time evaluation only — re-evaluated at next site rebuild. */ -}}
+{{- $now := now -}}
+{{- $startOk := true -}}
+{{- $endOk := true -}}
+{{- with $l.startDate }}{{ $startOk = ge $now (time .) }}{{ end -}}
+{{- with $l.endDate }}{{ $endOk = le $now (time .) }}{{ end -}}
+{{- if and $startOk $endOk -}}
+{{- $title := $l.title -}}
+{{- $url := $l.url -}}
+{{- $icon := $l.icon -}}
+{{- $image := $l.image -}}
+{{- $note := $l.note -}}
{{- $external := strings.HasPrefix $url "http" -}}
-
- {{- with $icon }}
- {{ partial "icon.html" . }}
+ {{- if $external }} target="_blank" rel="{{ $rel }}"{{ end -}}
+ {{- if $trackClicks }} data-analytics-event="link:{{ $l.title | urlize }}"{{ end }}>
+ {{- if $image -}}
+
+ {{- else if $icon -}}
+ {{ partial "icon.html" $icon }}
{{- end }}
- {{ $title }}
+
+ {{- $title -}}
+ {{- with $note }}{{ . }}{{- end -}}
+
+{{- end -}}
diff --git a/themes/bonsai/layouts/partials/link-group.html b/themes/bonsai/layouts/partials/link-group.html
new file mode 100644
index 0000000..f615979
--- /dev/null
+++ b/themes/bonsai/layouts/partials/link-group.html
@@ -0,0 +1,21 @@
+{{- /* Render a navigation block of links.
+ Context dict: { "links": [...], "layout": "stack|grid|inline", "label": "..." }
+ Used both by the flat-list rendering and per-section rendering. */ -}}
+{{- $links := .links -}}
+{{- $layout := .layout | default "stack" -}}
+{{- $label := .label | default (i18n "nav_links_label" | default "Links") -}}
+{{- $validLayouts := slice "stack" "grid" "inline" -}}
+{{- if not (in $validLayouts $layout) -}}
+ {{- warnf "bonsai: unknown layout %q (expected stack|grid|inline), falling back to 'stack'" $layout -}}
+ {{- $layout = "stack" -}}
+{{- end -}}
+{{- /* Resolve analytics once and pass into each link-button so the partial reads
+ only its dict argument (per docs/code-standards.md partial composition rule). */ -}}
+{{- $analytics := site.Params.analytics -}}
+{{- if $links -}}
+
+{{- end -}}
diff --git a/themes/bonsai/layouts/partials/schema-person.html b/themes/bonsai/layouts/partials/schema-person.html
index c9dd5da..ce36624 100644
--- a/themes/bonsai/layouts/partials/schema-person.html
+++ b/themes/bonsai/layouts/partials/schema-person.html
@@ -2,8 +2,17 @@
Suppressed when site.Params.schema = false. */ -}}
{{- if ne (site.Params.schema | default true) false -}}
{{- $name := site.Params.name | default site.Title -}}
+{{- /* Flatten params.links + params.sections.*.links into a single list for sameAs. */ -}}
+{{- $allLinks := slice -}}
+{{- if site.Params.sections -}}
+ {{- range site.Params.sections -}}
+ {{- range .links -}}{{- $allLinks = $allLinks | append . -}}{{- end -}}
+ {{- end -}}
+{{- else -}}
+ {{- $allLinks = site.Params.links -}}
+{{- end -}}
{{- $sameAs := slice -}}
-{{- range site.Params.links -}}
+{{- range $allLinks -}}
{{- with .url -}}
{{- if not (or (strings.HasPrefix . "mailto:") (strings.HasPrefix . "tel:")) -}}
{{- $sameAs = $sameAs | append . -}}
diff --git a/themes/bonsai/layouts/variants/single.html b/themes/bonsai/layouts/variants/single.html
index b73ba65..9daad66 100644
--- a/themes/bonsai/layouts/variants/single.html
+++ b/themes/bonsai/layouts/variants/single.html
@@ -1,5 +1,6 @@
{{ define "head_extra" }}
-
+{{- $g := resources.Get "css/gallery.css" | resources.Minify | resources.Fingerprint "sha384" -}}
+
{{ end }}
{{ define "main" }}
@@ -31,7 +32,7 @@
layout = "{{ .name }}"