Restructure `publication` frontmatter from a flat Markdown string into a
structured object (name, short_name, volume, issue, pages, publisher),
resolved through a new `functions/resolve_publication.html` partial that
also emits a per-page warning pointing legacy sites at `hugoblox migrate
publications`. APA/MLA citation rendering and the publication metadata
row both consume the resolver - no parsing of conventional Markdown
strings at render time.
Add work-level metadata fields surfaced via a new reusable
`components/award_badge.html` partial (variants: pill, card-corner,
inline, row; level-driven icon + colour tokens; pagefind filter):
- `awards[]` - name, level (winner/runner_up/honorable_mention/nominee/
selected/featured), issuer, date, note, url; skim-row pills above the
featured image, full row in the metadata grid, corner badges on card
and slides-gallery views, inline marker on citation views, pill on
date-title-summary
- `funding[]` - funder + grant + url, rendered as a metadata grid row
- `license` - SPDX-friendly string, metadata grid row
- `peer_reviewed`, `open_access` - status pills, decoupled from
the publication block so preprints can surface them
Demo content updated in academic-cv (and the same shape in research-lab).
Route all .Params.image access through a single normalizer partial that
returns the params as a map and emits a warnf with the expected shape
when frontmatter is set but not an object.
Adds the metadata pipeline that makes the gallery block self-curating.
Per-image metadata now resolves through a four-tier waterfall, first
non-empty source wins:
1. Frontmatter (items[].caption etc.)
2. Sidecar file next to the image - tries six patterns:
<name>.yaml / .yml / .json and <basename>.yaml / .yml / .json,
looked up in both site assets and the page bundle (covers album
mode and page-resource mode alike).
3. Embedded IPTC/EXIF via Hugo's .Exif.Tags - caption from
Caption-Abstract / ImageDescription / XPSubject / Description,
credit from By-line / Artist / Creator. Wrapped in `try` so
non-EXIF formats and bad metadata never break the build.
4. Humanised filename - alt fallback only; captions stay empty
rather than fabricated from filenames.
Also adds a third source mode - content.page_resources: true -
auto-discovers image resources attached to the current page bundle.
Drop-in for blog posts and project pages where photos live next to
index.md; sidecar lookup works inside the bundle too.
Fixes a leading-slash quirk: Resource.Name returns "/media/..." for
site assets but a bare path for page-bundle resources; the path is
now normalised so sidecar lookups land in either layout.
Test site adds two sidecar YAML files (04-cabin.jpg.yaml and
06-mountain.jpg.yaml) - the masonry album demo now picks up titles,
markdown captions, and links from those sidecars without listing any
items[].
First-class image gallery block. Two layouts ship today - grid (uniform
tiles, configurable columns 1-6, aspect-ratio presets) and masonry
(CSS-columns Pinterest packing). Click-to-zoom lightbox built in Preact,
portalled to <body> so it escapes the section's `isolation: isolate`
stacking context; keyboard navigation (← → Esc), touch swipe, and body
scroll-lock included.
Two source modes: explicit items[] for curated galleries with per-image
captions/credits/links, or album: <folder> for drop-in folder loading
sorted by filename. Remote URLs (Unsplash/Pexels/CDNs) are passed
through unprocessed; local images get the full responsive variants +
inline base64 LQIP placeholder for instant first-paint over the eventual
sharp image.
Captions, titles, and credits are rendered through Hugo's RenderString
at build time (display: inline) - users get full Markdown (bold/italic/
code/links) in any of those fields with zero JS markdown parser. Alt
text falls back to plainified caption → humanised filename so screen
readers always have something useful.
Caption positions: below (default - accessibility-first), overlay
(magazine gradient), hover (clean grid reveal), none (lightbox-only).
Block-scoped CSS in the co-located style.css is picked up by the
existing block-CSS auto-discovery.
Test site: two demos under home.yaml (curated grid + auto-loaded
masonry) with 9 sample Picsum images stored under
test/assets/media/albums/gallery/ at varied aspect ratios.
Single-location + multi-marker, side-by-side and map-only layouts.
Zero-config — no API key. MapLibre is vendored via vite and loaded
conditionally when a page contains a map block. Provider resolution
is a single switchable function so other tile providers can be added
later without changing the block contract.
The previous section_break implementation used CSS mask-image, which
incidentally established a stacking context on the section. Replacing
it with a gradient overlay div removed that side-effect — and broke
the visual depth on every section with a gradient mesh or radial
background.
Without a stacking context, the section's negative-z-index descendants
(home-section-bg at z:-1 carrying the radial gradient, and the
gradient_mesh wrapper at z:-10 carrying the orbs) escaped the section
and painted behind the page root, where they were occluded by the
page body — flat dark hero, no glow, no orbs.
Add `isolate` (isolation: isolate) to the section element. This is
the no-visual-side-effect way to force a stacking context, restoring
containment of the negative-z layers without affecting siblings or
content.
The mask-image approach for section_break was wrong: making pixels
transparent reveals the body background, not the next sibling section
(which sits below in document flow, not behind in z-order). When the
body bg didn't match the adjacent section, this produced a visible
white/light strip between sections — exactly what the fade was
supposed to prevent.
Replace mask-image with absolutely-positioned gradient overlay divs at
the section's top/bottom edges. The overlay fades from transparent to
a user-specified colour matching the adjacent sibling's background.
Explicit colour declaration is required because Hugo can't reliably
auto-detect the next sibling's bg at parse time.
API change (no backward-compat — feature was added this session, no
sites depend on the legacy string form):
# before
section_break: fade-bottom
# after
section_break:
fade_bottom: "#101828" # CSS colour matching next section's bg
fade_top: "#0a0e27" # optional, independent
size: "6rem" # optional, default 6rem
Update the landing page hero (fade-bottom into dark logos band) and
cta-card (fade-top from white FAQ section above) to the new form.
- New `content.media` field accepts image (Hugo-pipeline-processed for
responsive srcset) or video (passes through as <video>). Optional
`dark_src` provides a dark-mode image variant.
- New `design.layout` enum: centered (default) | split-left |
split-right | stacked. Split layouts auto-set alignment to 'left'
unless explicitly overridden.
- Bump trust-strip star size from w-4 to w-5 for visual presence.
- Two new shared partials in layouts/_partials/functions/:
- resolve_image_resource: path → {resource | fallback_url}
- process_image_for_props: path → {src, srcset, width, height}
These factor out the resource-resolution + responsive-processing
pipeline so media-bearing blocks don't have to inline it.
- Capabilities registry: media-image, media-video, split-layouts.
- Landing page _index.md: keeps the centered hero (no behavioural
change) but documents the opt-in pattern for split layouts inline.
Replaces block.html with a Preact component featuring smooth CSS grid
0fr→1fr height animation, icon rotation, and multi-paragraph answer
support via renderRich(). Single-open accordion with question/title and
answer/text field aliases for backward compatibility.
Steps block:
- Three layouts: vertical (stacked with connector line), horizontal
(side-by-side, connector above), timeline (alternating cards on centre
line)
- Three marker styles: number (decimal/padded/roman/alpha), icon, dot
- Layout-aware marker sizing - bare 40px icon for horizontal, tinted
circle for vertical/timeline
- Staggered IntersectionObserver entrance animation
- Code span rendering in step body text
- Added to startup-landing-page template as a horizontal how-it-works
section
Wire .hbb-section to var(--hb-spacing-section) instead of hardcoded 6rem,
enabling hugoblox.layout.spacing (compact/comfortable/spacious) to take effect.
- Default to "spacious" (6rem) to preserve existing visual layouts
- Per-section override via design.spacing.padding still works (inline > class)
- Remove dead design.spacing frontmatter field from all templates
- Set correct layout.spacing token in each template's params.yaml
Closes#3314
site_head.html, jsonld/article.html, jsonld/qapage.html, and
jsonld/event.html hardcode `(.Resources.ByType "image").GetMatch
"*featured*"` to find featured images. This ignores custom filenames
set via `.Params.image.filename` in front matter, causing og:image
and JSON-LD image to fall back to the site icon.
Replace the hardcoded pattern with the `get_featured_image` partial
(already used by jsonld/webpage.html), which properly searches for:
1. Files matching `*featured*` in the page directory
2. Custom filename from `.Params.image.filename` in the page directory
3. Custom filename in `assets/media/` directory
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Replace manual format detection (printf "%T", MediaType.SubType whitelists,
duck-typing) with Hugo's new reflect.IsResource, reflect.IsImageResource, and
reflect.IsImageResourceProcessable functions across 19 template files.
Key changes:
- process_responsive_image: collapse 15-line format whitelist into single
reflect.IsImageResourceProcessable call
- Unify three-way SVG/processable/GIF branches into two-way $canProcess guard
- Remove duplicate GIF passthrough branches in single.html, events/page.html
- Add missing SVG guard in research-areas block (was calling .Fill unprotected)
- Replace nil checks with reflect.IsResource in get_cover_image/get_featured_image
Design decision: GIFs are intentionally excluded from processing despite Hugo
supporting it — processing strips animation frames, so we preserve user-optimized
animated GIFs by checking (IsImageResourceProcessable AND not GIF).
Move block-specific CSS from centralized assets/css/blox/ to co-located
style.css files within each block directory. The css.html partial now
auto-discovers style.css files from bundled and community blocks and
concatenates them into the Tailwind entry via resources.Concat, enabling
full @apply support while avoiding VFS path resolution issues.
Shared biography styles (avatar, ruby) are extracted to blox/shared/css/
and auto-included before per-block styles. Navbar CSS moves to
components/ as it's a framework-level concern. The separate community
CSS pipeline in site_head.html is removed — all block CSS now flows
through a single Tailwind entry point.
Replace site.LanguageCode → site.Language.Locale and
.Language.LanguageCode → .Language.Locale in module layouts,
and languageCode → locale in all template language configs.
Enabled by new Hugo v0.153 release.
Replace the brittle `urlquery` + blanket `+` → `%20` workaround with
Hugo's `urls.PathEscape` for mailto URIs, which natively percent-encodes
spaces as `%20`. The old approach could corrupt titles containing
literal `+` characters.
All other display toggles use positive naming (show_*, toc, share, etc.).
Flipping to show_date (default: true) aligns the API and reduces cognitive load.
BREAKING CHANGE
Add a second resolution tier to the typography system: before falling
back to Google Fonts, check assets/dist/font/ for a matching local
file. When found, emit @font-face with local() fallback instead of a
CDN <link>. Variable fonts detected from ".var." filename convention.
Bundle JetBrains Mono alongside Inter so the default "modern" pack
loads entirely from local files — zero external font requests.
SVG resources don't support .Width, .Height, or .Process in Hugo,
causing crashes in process_responsive_image.html and all callers
that pass SVGs without guards.
Add SVG checks to process_responsive_image.html, preact-wrapper.html,
resume-biography, resume-biography-3, authors/term, and
page_author_card.
Fixes#3305
Hugo doesn't natively merge language-specific data directories with the default data. Add a `get_authors_data` partial that manually
looks up `data/<lang>/authors/` and deep-merges translated fields
on top of the default data/authors/ profiles.
Centralizes all `site.Data.authors` access through this new partial
so author names, bios, roles, and other fields can be translated
per language while inheriting non-translated fields from the default.
Refactor the sidebar component logic to correctly handle the `no_sidebar` parameter.
Previously, `default true` treated an explicit `false` value as empty, causing the sidebar
to be hidden unintentionally. Replaced with an explicit inequality check.
- keep raw front-matter author strings as preferred display when no profile exists
- fall back to title-cased humanized slug only as last resort to avoid lowercase initials
- render authors directly from Params order and resolve links via taxonomy pages when present
Fix https://discord.com/channels/722225264733716590/1457522006462435541/1458539152160067784
- default to last-name sort with numeric-friendly keys for weight/graduation_year
- support per-group sorting, optional empty-state, and role fallback to affiliation
- add grid/interest caps, alignment controls, and avatar initial placeholders
- expose graduation_year/weight/name_family_pref in profiles; update schema/README
- retain raw front-matter author strings as display names when no profile data is present
- title-case humanized slug only as last-resort fallback to avoid lowercase surnames
- reduce need for author profile files while keeping names correctly capitalized
Fixes https://discord.com/channels/722225264733716590/1457522006462435541