Docs Surfaces
Shared layout
Chrome that every page inherits: the layout shell, theme switch, command palette, and footer.
Scope
This document covers shared cross-page UI behavior:
- layout shell
- navbar and header actions
- page-template adaptation
- shared footer
Base Layout
Main file: src/layouts/Layout.astro
Responsibilities:
- owns the HTML shell for most routes
- sets canonical, OG, and Twitter metadata
- exposes optional RSS and oEmbed discovery links
- mounts the shared section navbar by default
- mounts the shared theme dropdown
- avoids mounting third-party analytics scripts
Theme behavior:
- runs before paint with an inline script
- reads
localStorage.theme - falls back to
prefers-color-scheme - applies
html.dark - stores the current selection in
html[data-theme-setting]
Navbar Model
Implementation lives in src/layouts/Layout.astro.
Important design choice:
- the navbar is section-anchor based, not route-aware
Behavior:
- default links target:
#projects-section#writing-section#moods-section
- nav labels are rewritten into per-character spans
- scrolling updates the active section
- smooth scrolling is handled in client code
IntersectionObserverswitches the nav between horizontal and vertical modes based on hero visibility- the active indicator is animated only in vertical mode
Header actions:
Layout.astroowns the theme dropdown- individual pages can inject extra buttons into
[data-header-actions] Layout.astroexposes a small registration surface for GSAP header-button animation
Page Template Adaptation
Main file: src/layouts/Page.astro
Purpose:
- reuse the same base layout for document-style pages such as
/privacy
How it adapts the shared nav:
- adds
body.page-template-active - keeps only the first nav item
- renames that item to
buxx.me - rewires it to
/ - removes the active indicator
- removes extra links and separators
This keeps the global chrome but changes the navigation contract from section scrolling to home navigation.
Shared Footer
File: src/features/home/ui/Footer.astro
Behavior:
- static footer
- exposes
/privacy - exposes the GitHub source repository
The privacy page is therefore linked from:
- the global footer
- the mood notify panel
Implementation Summary
- shared UI concerns are centralized in
Layout.astro - content pages reuse the same shell and mutate the nav through
Page.astro - the shared layout is optimized for the home page first, then adapted for document-style pages
Motion Vocabulary
Declared at :root in src/styles/globals.css. One
curve family, one duration scale, site-wide:
| Token | Value |
|---|---|
--ease |
cubic-bezier(0.2, 0, 0, 1) |
--ease-out |
cubic-bezier(0.23, 1, 0.32, 1) |
--ease-in-out |
cubic-bezier(0.77, 0, 0.175, 1) |
--dur-press |
110ms |
--dur-fast |
130ms |
--dur-base |
190ms |
--dur-enter |
240ms |
The scale was adopted from the portal, which was the only part of the site that
had one; src/styles/portal.css now aliases its
--portal-* names to these so its existing rules keep reading their own
vocabulary.
Rule: new motion uses a token. A literal curve needs a comment saying why it is not one.
--expo-out is deliberately outside this scale — it is a linear() easing for
the 1.5s theme wipe, a different register from UI motion.
src/styles/home-reveal.css likewise owns its
own --reveal-ease by design.
Adoption follow-ups
The adoption pass replaced literals only where the value matched a token exactly and the site was enumerated in plan 022. Left for a later pass:
Exact matches, mechanically safe — these can become tokens with no change in rendering:
src/features/components/ui/OnThisPage.astro:72—--ease-outsrc/pages/privacy.astro:334—--ease-outsrc/components/CommandPalette.astro:1554-1556—--easesrc/features/admin/ui/AnalyticsCharts.tsx:158—--ease(inline style, React island)
src/styles/code-box.css:11 and src/styles/listening.css:655 already read
var(--ease-out, …) with a literal fallback; that form is deliberate for
stylesheets that may mount outside their owning subtree.
Near-misses needing a judgement call — each is an “ease-out with a long
tail” that is not --ease-out. Collapsing them blind would change how things
feel, so each site needs its own decision (is this meant to be the standard
ease-out, or is the curve deliberate?):
| Curve | Uses | Notable homes |
|---|---|---|
cubic-bezier(0.16, 1, 0.3, 1) |
31 | globals, TimelineWheel, 404, view transitions |
cubic-bezier(0.2, 0.8, 0.2, 1) |
12 | — |
cubic-bezier(0.4, 0, 0.2, 1) |
9 | Material’s standard curve |
cubic-bezier(0.32, 0.72, 0, 1) |
8 | ProjectStack entrance |
cubic-bezier(0.22, 1, 0.36, 1) |
8 (+2 unspaced) | SiteWordmark, hero cards, GitHubContributions |
cubic-bezier(0.25, 1, 0.3, 1) |
6 | blog.css, SiteWordmark |
cubic-bezier(0.45, 0, 0.2, 1) |
4 | — |
cubic-bezier(0.2, 0.7, 0.2, 1) |
3 (+2 unspaced variant) | — |
Overshoot curves (0.25, 1.22, 0.45, 1.04, 0.25, 1.18, 0.45, 1.04,
0.34, 1.56, 0.64, 1, 0.22, 1.2, 0.4, 1) are character, not drift — they are
not candidates for the token set.
Also worth a pass: the same curve is spelled both with and without spaces
(cubic-bezier(0.22,1,0.36,1) vs cubic-bezier(0.22, 1, 0.36, 1)), which
defeats grep-based auditing.