Overview
Every docs page on this site carries a Section Map at the bottom of the sidebar: the entire branch you are in—Nucleate, Devlog, About—laid out as nodes and edges, with your current page lit along the path from the root.
The hub page has the Observatory: a living chart of the whole studio. Docs pages need a local map of one branch—same visual language, same amber beacon at the root, no idle spin or particle fields. Section Map is the name we use in UI copy, code, and docs.
This devlog documents what the Section Map does, why it exists, and how it is built.
Why Not the Sidebar Alone?
Hextra’s docs sidebar is a collapsible tree. It works for clicking the next page. It does not answer:
- Where am I in the whole branch? — especially in deep Nucleate docs with numbered sections and many siblings.
- What is the shape of this documentation? — overview vs. setup vs. legal, at a glance.
- How does this branch connect back to the studio? — the map’s root is the section home; breadcrumbs link back to the Observatory.
The Section Map is complementary, not a replacement. Tree for sequential reading; map for spatial orientation. Same content, two views—like a table of contents and a map in a field guide.
What Visitors Experience
On any type: docs page (desktop sidebar visible):
- Below the collapsible nav tree, a “Section Map” heading introduces the widget.
- An SVG radial layout shows every page and subsection in the current top-level section (e.g. all of
/nucleate/). - Root node — amber beacon gradient at the center (section
_index). - Section nodes — larger circles for nested sections.
- Page nodes — smaller circles for leaf pages.
- Dim default — most nodes sit at low opacity; the map stays quiet until you look at the path.
- Path highlight — edges and nodes on the route from root → … → current page brighten; ancestors glow; current page gets the strongest stroke and drop shadow.
- Hover — any node brightens; all nodes are links.
- Click — navigate to that page (full page load; map re-renders on the new page).
- Hidden when trivial — if the branch has only two nodes total, the map is omitted.
- Hidden on mobile — sidebar map lives in the desktop sidebar column; mobile keeps the tree only.
There is no animation loop. Unlike the observatory, this is a static instrument reading: you are here, on this spoke, in this branch.
Relationship to Other Wayfinding
Three layers stack on docs pages:
| Layer | Location | Role |
|---|---|---|
| Wayfinding | Above article title | Text breadcrumb: Observatory → … → current section |
| Sidebar tree | Left column | Hierarchical links, collapsible |
| Section Map | Bottom of sidebar | Radial map of entire branch with path highlight |
A fourth related piece—docs dust—lives in the right-hand “On this page” TOC: rising star particles behind the heading list. Same amber palette as the observatory; independent motion (vertical drift, not orbit). The Section Map shares the visual vocabulary (beacon, dim amber, glow on focus) without sharing the motion budget.
Together they say: you left the rotating observatory and entered a focused workspace—but the workshop aesthetic followed you in.
Architecture: Build Time vs Runtime
| Layer | Responsibility |
|---|---|
section-map.html | Gate on type: docs and sectionMap param; seed root node; invoke walk |
section-map-walk.html | Recursive descent of section + pages; append nodes/edges to scratch |
sidebar.html | Includes partial after sidebar tree on docs pages |
section-map.js | Parse JSON, compute layout, render SVG, apply path/current classes |
custom.css | .section-map* styles in the engagement block |
scripts.html | Loads section-map.js on all docs pages (with docs-dust, scroll-top) |
Graph JSON is embedded once per page in data-graph, including rootId, currentId, nodes, and edges. No fetch, no client-side CMS.
Graph Generation (Hugo)
The walk starts at site.GetPage "section" .Section—the top-level branch for the current page (e.g. nucleate, devlog).
For each page and subsection, recursively:
- Append a node:
id(RelPermalink),label,href,type(root|section|page),depth,weight - Append an edge:
fromparent RelPermalink →tochild
Children are ordered by Hugo’s ByWeight, matching sidebar order.
Visibility gate: render only if len(nodes) > 2. A section with just an index and one child does not need a map.
Opt-out: front matter sectionMap: false on any docs page disables the partial for that page’s sidebar context.
Layout Algorithm (JavaScript)
layoutHierarchy() performs a recursive angular sector layout:
- Root sits at
(centerX, centerY). - Each parent’s children divide the parent’s angular sweep evenly.
- Ring radius grows by
ringBase + (depth - 1) * ringStep. - Child order follows
weight, then label locale sort.
This produces a sunburst-like tree without crossing edges—important for readable docs maps with many siblings (Nucleate’s numbered sections fan out cleanly).
After layout, fitViewBox() computes bounding box of all nodes (including radii) and sets the SVG viewBox with padding. Sidebar mode uses tighter constants and a slight zoom (viewBoxZoom: 1.124) so the map fills the narrow column.
Path Resolution and Highlighting
On render:
resolveCurrentId()— matchwindow.location.pathnameto a node id; fall back to Hugo’scurrentIdfrom the template.buildPathNodeIds()— walk parent pointers from current to root.buildPathEdgeKeys()— same walk for edge keys.
CSS classes applied:
section-map-node--path— on pathsection-map-node--ancestor— on path but not currentsection-map-node--current— active pagesection-map-edge--path— edges along the route
Off-path nodes stay at 0.3 opacity; path nodes and edges brighten to amber. The current node gets a stronger glow—same design language as selecting a node on the observatory, without motion.
Re-init on pageshow — handles bfcache back-navigation so the path highlight stays correct when returning via browser back.
File Map
layouts/partials/
section-map.html # Build graph payload; emit sidebar widget
section-map-walk.html # Recursive content walk
sidebar.html # Includes Section Map on docs pages
assets/
js/section-map.js # ~295 lines: layout + SVG render
css/custom.css # .section-map* styles (~150 lines)
layouts/partials/scripts.html # Docs-only script bundleDesign Decisions
| Decision | Rationale |
|---|---|
| Static render, no RAF loop | Sidebar utility should not compete with reading; battery and prefers-reduced-motion friendly by default |
| Full branch, not just ancestors | Shows sibling pages you might want; tree shape matters for orientation |
| Radial layout, not force-directed | Predictable, reproducible, matches observatory geometry |
| Beacon root | Visual continuity from hub to docs |
| Threshold > 2 nodes | Avoid empty ornament on tiny sections |
| Below sidebar tree | Tree remains primary interaction; map is orienteering |
| Same graph source as sidebar | Weight order and section structure stay in sync with content |
How to Recreate Something Like This
Pick scope — one root section per map (not the whole site). The hub map and the doc map serve different scales.
Walk your content tree at build time — identical nodes/edges the sidebar would use. Single source of truth.
Serialize into the page —
data-graphJSON on a container element.Layout with deterministic geometry — angular subdivision is easier to debug than force simulation and never “jitters.”
Highlight the active path — users care where they are, not an abstract graph. Parent walk from current node is O(depth).
Fit the viewBox — sidebar columns are narrow; auto-fit prevents clipping on large branches.
Link every node — the map is navigation, not decoration.
Gate on complexity — skip when the map would be a dot and a line.
You do not need D3. A few hundred lines of vanilla SVG DOM suffice.
Technical Prerequisites
- Hugo sections with weighted
_index.mdand nested pages - Custom sidebar partial hook (or docs layout override)
- SVG basics:
createElementNS,<a>wrapped circles,viewBox - Tree traversal for layout and path highlighting
- CSS for state classes (path, current, hover)
Optional: Hugo Pipes minify/fingerprint in scripts.html; pageshow listener for bfcache correctness.
Rough size: ~295 lines JS, ~150 lines CSS, ~45 lines Hugo templates—an order of magnitude smaller than the observatory.
Extending or Disabling
- Disable on a page —
sectionMap: falsein front matter. - New pages — appear automatically after build; weight controls order and angular placement.
- Exclude from sidebar tree —
sidebar.excludeaffects the tree only; the walk does not currently filter on it. If you need parity, extendsection-map-walk.htmlto respect the same flag.
Observatory vs Section Map
| Observatory (hub) | Section Map (docs) | |
|---|---|---|
| Scope | All top-level sections | One docs branch |
| Depth | 2 levels (category + expand) | Full hierarchy |
| Motion | Rotation, trails, dust, comets | Static |
| Interaction | Expand, zoom, hover brake | Click to navigate |
| Layout | Fixed ring + dynamic children | Recursive sunburst |
| Primary job | Studio identity + entry | In-branch orientation |
Same family of instruments. Different scale and temperament.
Reflections
The Section Map answers a reader question the sidebar tree asks you to infer: where am I in this branch, and what else lives here?
The feature stays restrained on purpose: no spin, no particles in the sidebar, low opacity until you need the path. Documentation pages should feel focused. The map is a compass in the drawer, not another showpiece.
Next Steps
- Consider
sidebar.excludeparity in the graph walk. - Additional technical devlogs for docs dust, wayfinding, and product-page engagement widgets as those systems mature.
Written for the Technical devlog series.