Skip to content

Restyle the architecture docs site and animate the sidebar - #9527

Draft
kube wants to merge 11 commits into
mainfrom
claude/petrinaut-arch-docs-styling-554323
Draft

Restyle the architecture docs site and animate the sidebar#9527
kube wants to merge 11 commits into
mainfrom
claude/petrinaut-arch-docs-styling-554323

Conversation

@kube

@kube kube commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Important

Appearance of the docs site only, bundle and content untouched.

Summary

Before this PR, the architecture docs site ran on Starlight's default theme: a blue accent on the active nav row and on every prose link, a 42px page title, side panels filled in three different greys, and square code frames. Collapsing the left panel set display: none on the pane and swapped the content column's inset for a fixed 2rem, so the layout jumped in a single frame. The resize handle filled its whole 6px width with the accent colour on hover, wrote to localStorage on every pointer move, and lost its highlight as soon as the pointer left the strip mid-drag.

This restyles the site on one neutral ramp, monochrome apart from what carries meaning, and animates the collapse. The pane hides with visibility instead of display, the content inset derives from the pane width, and every box sized from that width transitions on one curve. A drag or a held arrow key marks itself so those transitions do not apply and the pane tracks the pointer directly.

Links

Changes

Appearance

  • One neutral ramp serves both themes

    Header and sidebar share the page background and carry no divider between them.
    Shadows are flat apart from the search dialog and the mobile table-of-contents dropdown.

  • The sidebar's depth guide is the only rule drawn in the chrome

    It stays because it carries information: how deep a row sits in the tree. The borders that framed the header, the sidebar and the on-this-page column are gone.
    Element borders stay where they separate content from the page: tables, code frames, and the markdown images holding the diagrams.

  • Controls read by fill rather than by outline

    Search field and the diff-build compare chips sit one step off the page background, so neither adds a line to the header.

  • Accent tokens point at the strongest foreground

    Starlight routes prose links, the active nav row and the active table-of-contents row through the accent tokens, so this removes the blue in one place.
    Links stay distinguishable by an underline one step down from the text.

  • Type is 17px on a 1.65 leading, with a shorter heading scale

    Starlight's desktop h1 is 42px. Hierarchy comes from weight and space instead of size.
    --sl-text-body is defined upstream but never applied, so the body size is set here.

  • Blocks sit 1.5rem apart and a section heading takes 2.25em above it

    Starlight's 1rem between blocks is tight under a 1.65 leading, and its 1.5em above a heading is barely more than the gap between two paragraphs.

  • Header loses its social links, and the rule that divided them

    .social-icons::after draws that rule on the container, so it outlived having any links. Hidden while the group holds none, so configuring one brings it back.

  • Theme select's focus ring restated in the palette

    The select carries no border, so the browser's own ring is the only marker and it arrives as a thick blue rectangle. Replaced rather than removed, on :focus-visible.

  • Collapse toggle and theme select rest at 50%, the rails at 70%

    One small control in a wide header carries less than a column of text. A press dips the toggle and springs it back past its own size.

  • Rails rest at 70%, reaching full strength over 300ms

    The whole rail is the hover target, so the reveal does not depend on landing on a row. Keyboard focus reveals them too.
    prefers-reduced-transparency turns the effect off, and prefers-reduced-motion drops the toggle's bounce.

  • A band under the header blurs and tints the content passing beneath it

    backdrop-filter takes a single radius, so the ramp comes from the mask: one layer at 1px of blur, full strength for its top half and out to nothing by its bottom.
    Tint over 60px, blur over 80px: matched heights hid the blur, since the tint is opaque where the blur is strongest.
    It spans the content column only, stopping short of both rails.

  • Both bands grow out of scroll and neither exists before it

    Height, tint and opacity come off one progress number the header script writes, 0 to 1 over the first 20px, so an unscrolled page has nothing laid over it.
    The nav reads its own scrollTop, since the rail scrolls independently of the page. Nothing eases either, since the value already tracks the scroll.

  • Content band clears Starlight's mobile table-of-contents bar

    Between 50rem and 72rem the on-this-page column becomes a bar under the header, which the band blurred. It now starts below --sl-mobile-toc-height as well as the header.

  • Body text renders in Inter, requested through Astro's font support

    One variable file, subset to latin at build time, preloaded beside the existing mono face.
    Inter stands in for the grotesque the reference design uses, which is not licensed for redistribution.

  • Sidebar and table-of-contents rows are pills

    Equal sizes across levels, with a grey fill for hover and a stronger one for the active row.
    "On this page" drops to a label, having been sized as a heading competing with the page title.

  • Code frames are rounded and flat, through the --ec-* variables Expressive Code reads

    Declared on :root and on the block itself, which is what a code block carrying its own theme reads.

  • Content panels no longer rule a line between each other

    On a generated page that line fell directly under the title and read as a header border.

Sidebar motion

  • Collapsing animates the pane, the content column, the handle and both panels

    Everything that sizes off the sidebar width or --sl-content-width transitions on one curve over one duration, otherwise the column separates from the pane it follows.

  • Content inset derives from the pane width as max(sidebar-width, 2rem)

    One length with two ends, replacing a jump between the pane's width and an unrelated 2rem.

  • Pane hides with visibility rather than display: none

    A visibility transition holds visible for the whole collapse and flips at the end, so the rows travel with the pane and still leave the tab order at rest.

  • Pane clips horizontally

    A scroll container with overflow-y: auto computes overflow-x to auto, which flashed a horizontal scrollbar as the rows outgrew the narrowing pane.

  • All of the motion sits behind prefers-reduced-motion

Resize handle

  • Handle marks its gesture, and the transitions do not apply while it is set

    A drag and a held arrow key already track their input frame by frame. A transition on top of that makes the pane lag behind the pointer.
    The mark also stays lit for the whole drag, where it used to rely on :hover and drop as soon as the pointer left the 6px strip.

  • A 2px mark fades in on hover inside the unchanged 6px hit area

    Filling all 6px with the accent colour made the edge jump three pixels wider and change hue when the pointer crossed it.

  • Width applies once per frame and persists once per gesture

    A pointer reports several moves per frame, and each write invalidates layout for the pane, the content column and both panels sized from it.
    localStorage writes synchronously and used to take every pointer position.

  • Cursor and text selection hold for the length of a drag

    Pointer capture keeps the events coming while the pointer sits outside the strip, but the cursor flickered and the drag selected the text it crossed.

Known issues

  • Rendered diagrams keep the white background baked into their SVG

    Unchanged by this PR, and the most visible remaining mismatch in dark mode. The background comes from the generator's D2 render, not from the site.

Next steps

  • No automated coverage for the sidebar behaviour

    The package has no test suite to add to. Wiring one would need a test:unit task and a browser runner for a site whose content is generated.

Test coverage

  • lint:tsc and the production build:

    Cover the font registration and the two component changes. Both subset font files land in dist.

  • Scripted DOM probe over one toggle and one drag:

    Intermediate widths on the pane, the content column and the handle; visible held across the whole collapse and hidden at rest; a fully collapsed pane; the gesture flag suppressing transitions during a drag and a held arrow key; one storage write per gesture; reduced motion turning the animation off while the collapse still works.

  • Screenshots:

    Both themes, expanded and collapsed, on an authored page, a generated layer page and a page of code.

How to test

  • Open Petrinaut arch docs preview on Vercel
  • Toggle the sidebar from the control left of the logo
  • Expect pane, content column and on-this-page panel to move together, no jump
  • Hover the pane's trailing edge
  • Expect a thin mark to fade in
  • Drag that edge
  • Expect width to follow the pointer with no lag, and the mark to stay lit outside the strip
  • Focus the edge, then ArrowLeft and ArrowRight
  • Header > theme select > Light
  • Expect the same monochrome palette inverted

Demo

Video and before/after screenshots pending, handed over for drag-drop.

@kube kube self-assigned this Sep 3, 2026
@vercel

vercel Bot commented Sep 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
petrinaut-docs Ready Ready Preview Sep 4, 2026 2:28am UTC
3 Skipped Deployments
Project Deployment Actions Updated
hash Ignored Ignored Preview Sep 4, 2026 2:28am UTC
hashdotdesign-tokens Ignored Ignored Preview Sep 4, 2026 2:28am UTC
petrinaut Skipped Skipped Sep 4, 2026 2:28am UTC

Request Review

kube added 2 commits September 4, 2026 00:27
Restate Starlight's palette and type scale as one flat monochrome
system, so the chrome reads as a single surface divided by hairlines
and colour is left to the things that carry meaning.

Collapsing the left panel now animates: the pane hides with
visibility rather than display, the content inset derives from the
pane width, and every box sized from it transitions on one curve.
A drag or a held arrow key already tracks its input, so the handle
marks the gesture and the transitions stand aside.
The borders framing the header, the sidebar and the on-this-page
column are gone, leaving the sidebar's depth guide as the only rule
drawn in the chrome, where it says how deep a row sits in the tree.

The search field and the compare chips now read by fill rather than
by an outline, one step off the page background in either theme.
Both rails and the collapse toggle sit at 70% until the pointer or
keyboard focus reaches them, over 300ms, with the whole rail as the
hover target. prefers-reduced-transparency turns the effect off.

A 30px band under the header blurs and tints the content passing
beneath it. backdrop-filter cannot ramp on its own, so the ramp is
three stacked layers of increasing radius, each masked to a shorter
band, and each filtering what the one beneath it produced.

Blocks now sit 1.5rem apart with 2.25em above a section heading, and
body copy is 17px: Starlight defines --sl-text-body but never applies
it, so the size had to be set here.
The band ran both to 30px, which hid the blur: its strongest layers
sit at the top, which is exactly where the tint is opaque. The tint
keeps its 30px and the blur now runs to 60px, so below the tint there
is a stretch where content is blurred and still visible.

Radii go up with it, 4px to 16px.
The three layers now differ by height rather than by mask alone. All
are anchored under the header and run to a different depth: 60px at
1px of blur, 40px at 2px, 20px at 4px. They overlap towards the
header and thin out to one at the bottom, so the tallest and weakest
is the layer that meets unblurred content.

Each fades out over its own lower half, so a layer ending reads as a
soft edge rather than a line across the page.
The band's construction moves to a shared class so the rail and the
content column build it from one set of rules.

The rail scrolls independently of the page, so its band keys off the
pane's own scrollTop and appears only once rows have gone up behind
the header. Shown unconditionally it would sit on the first row of an
unscrolled rail and dim it for nothing.
@vercel
vercel Bot temporarily deployed to Preview – petrinaut September 4, 2026 00:08 Inactive
Height and opacity both come off `--pnd-nav-progress`, which the
header script writes from the rail's scrollTop, 0 to 1 over the first
60px. At rest the band has no height, so it stays off the first row of
an unscrolled rail. Nothing eases it: the value already tracks the
scroll frame by frame, and a transition would leave the band lagging
the rows it covers.

The ramp is now two layers rather than three, 60px at 1px of blur and
30px at 2px, in both places the band is used.
@vercel
vercel Bot temporarily deployed to Preview – petrinaut September 4, 2026 00:21 Inactive
Sixty took two rows of scrolling before the band was fully formed, so
it read as arriving late. Twenty is well under one row: the band is
there as soon as anything has gone up behind the header, and the ramp
only takes the hard edge off its arrival.
The band is now a 60px tint under an 80px blur, with a single layer at
1px. `backdrop-filter` takes a single radius, so the ramp comes from
the mask: full strength for the layer's top half, out to nothing by
its bottom.

The comment on the markup still described three layers, two rounds
after there were three.
The content band now grows out of the window's scroll the way the nav
band grows out of the rail's, so an unscrolled page has nothing laid
over it. One tracker drives both, and one progress number per band
carries height, tint and opacity.

The content band also starts below `--sl-mobile-toc-height`. Between
50rem and 72rem Starlight moves the on-this-page column into a bar
under the header, and the band was sitting on top of it.
@vercel
vercel Bot temporarily deployed to Preview – petrinaut September 4, 2026 02:03 Inactive
The collapse toggle and the theme select rest at 50% rather than the
rails' 70%: one small control in a wide header carries less than a
column of text. A press dips the toggle and springs it back past its
own size, behind `prefers-reduced-motion`.

The toggle's hover and focus rules carried two selectors' worth of
specificity against a base rule with three, so the button had never
lit up on hover at all. Both now carry the same prefix.

The GitHub link goes from the config. Starlight draws the rule beside
it from `.social-icons::after`, on the container rather than a link,
so the group is hidden while it holds none. The theme select's focus
ring is restated in the palette's greys.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/apps area/infra Relates to version control, CI, CD or IaC (area)

Development

Successfully merging this pull request may close these issues.

1 participant