Building the Section Map

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:

  1. Where am I in the whole branch? — especially in deep Nucleate docs with numbered sections and many siblings.
  2. What is the shape of this documentation? — overview vs. setup vs. legal, at a glance.
  3. 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:

LayerLocationRole
WayfindingAbove article titleText breadcrumb: Observatory → … → current section
Sidebar treeLeft columnHierarchical links, collapsible
Section MapBottom of sidebarRadial 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

LayerResponsibility
section-map.htmlGate on type: docs and sectionMap param; seed root node; invoke walk
section-map-walk.htmlRecursive descent of section + pages; append nodes/edges to scratch
sidebar.htmlIncludes partial after sidebar tree on docs pages
section-map.jsParse JSON, compute layout, render SVG, apply path/current classes
custom.css.section-map* styles in the engagement block
scripts.htmlLoads 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: from parent RelPermalink → to child

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:

  1. Root sits at (centerX, centerY).
  2. Each parent’s children divide the parent’s angular sweep evenly.
  3. Ring radius grows by ringBase + (depth - 1) * ringStep.
  4. 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:

  1. resolveCurrentId() — match window.location.pathname to a node id; fall back to Hugo’s currentId from the template.
  2. buildPathNodeIds() — walk parent pointers from current to root.
  3. buildPathEdgeKeys() — same walk for edge keys.

CSS classes applied:

  • section-map-node--path — on path
  • section-map-node--ancestor — on path but not current
  • section-map-node--current — active page
  • section-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 bundle

Design Decisions

DecisionRationale
Static render, no RAF loopSidebar utility should not compete with reading; battery and prefers-reduced-motion friendly by default
Full branch, not just ancestorsShows sibling pages you might want; tree shape matters for orientation
Radial layout, not force-directedPredictable, reproducible, matches observatory geometry
Beacon rootVisual continuity from hub to docs
Threshold > 2 nodesAvoid empty ornament on tiny sections
Below sidebar treeTree remains primary interaction; map is orienteering
Same graph source as sidebarWeight order and section structure stay in sync with content

How to Recreate Something Like This

  1. Pick scope — one root section per map (not the whole site). The hub map and the doc map serve different scales.

  2. Walk your content tree at build time — identical nodes/edges the sidebar would use. Single source of truth.

  3. Serialize into the pagedata-graph JSON on a container element.

  4. Layout with deterministic geometry — angular subdivision is easier to debug than force simulation and never “jitters.”

  5. Highlight the active path — users care where they are, not an abstract graph. Parent walk from current node is O(depth).

  6. Fit the viewBox — sidebar columns are narrow; auto-fit prevents clipping on large branches.

  7. Link every node — the map is navigation, not decoration.

  8. 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.md and 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 pagesectionMap: false in front matter.
  • New pages — appear automatically after build; weight controls order and angular placement.
  • Exclude from sidebar treesidebar.exclude affects the tree only; the walk does not currently filter on it. If you need parity, extend section-map-walk.html to respect the same flag.

Observatory vs Section Map

Observatory (hub)Section Map (docs)
ScopeAll top-level sectionsOne docs branch
Depth2 levels (category + expand)Full hierarchy
MotionRotation, trails, dust, cometsStatic
InteractionExpand, zoom, hover brakeClick to navigate
LayoutFixed ring + dynamic childrenRecursive sunburst
Primary jobStudio identity + entryIn-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.exclude parity 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.