Documentation site for keeb.supply — assembly instructions, troubleshooting guides, and keyboard basics.
Live site: https://docs.keeb.supply
- Hugo (extended, v0.156.0+) — static site generator
- Thulite / Doks — documentation theme
- Cloudflare Pages — hosting & deployment
- pnpm — package manager (Node ≥ 24.13.0 required)
pnpm install # install dependencies
pnpm dev # start dev server at localhost:1313
pnpm build # production build → public/
pnpm preview # preview production build via Wrangler
pnpm format # run Prettiercontent/
basics/ # foundational guides (firmware, soldering, hardware)
instructions/ # per-keyboard assembly instructions
troubleshooting/ # hardware & firmware troubleshooting
Each keyboard lives in content/instructions/<name>/ with its own _index.md (section landing) and sub-pages (packing list, required supplies, assembly, etc.).
Page ordering in the instructions grid is controlled by the weight front matter field in each _index.md.
- Create
content/instructions/<keyboard-name>/with an_index.md - Add sub-pages as needed (
packing-list/index.md,assembly/index.md, etc.) - Drop a cover image (
cover.webprecommended) in the section folder — it appears in the instructions grid - Set
weightin_index.mdto control sort order
Pushes deploy automatically via Cloudflare Pages. To preview locally:
pnpm build
pnpm previewRedirects and headers for Cloudflare Pages are in static/_redirects and static/_headers.
Brand colours and font overrides live in assets/scss/common/:
| File | Purpose |
|---|---|
_variables-custom.scss |
Brand colours, font stack |
_fonts.scss |
@font-face declarations (Atkinson Hyperlegible) |
_custom.scss |
Component overrides (header bar, sidebar, callouts) |
Default Doks ships Jost. This site uses Atkinson Hyperlegible (@fontsource/atkinson-hyperlegible), self-hosted via a Hugo module mount. The Jost font files are excluded from the build via files = ["! fonts/vendor/jost/**"] on the doks-core static mount in config/_default/module.toml. The Atkinson woff2 files are preloaded in a layouts/_partials/head/resource-hints.html override.
Two custom colours replace the Doks defaults throughout:
| Variable | Value | Used for |
|---|---|---|
$brand-teal |
#00556a |
Buttons, nav active, light-mode accents |
$brand-pink |
#d32e9d |
Links (both modes), callout links, sidebar active, dark-mode accents |
The Starlight CSS tokens (--sl-color-accent, --sl-color-accent-high) are wired to these so sidebar active states and other theme components inherit the brand colours automatically.
The top gradient bar uses a custom teal→pink gradient in both light and dark mode. The default theme uses yellow→vermilion in light mode and a blue-green in dark mode.
The /instructions/ page uses a fully custom image grid layout (layouts/instructions/list.html) instead of the default Doks section list. Each keyboard card shows a cover image (first .webp/.jpg/.png found in the section folder), with a gradient overlay label and hover animation. Cards are sorted by weight.
The homepage layout is replaced (layouts/home.html) with a simpler hero + three-panel feature section linking to the main content areas.
Theme partials are overridden by placing files at the same path under layouts/_partials/. Current overrides:
| Partial | What it changes |
|---|---|
main/edit-page.html |
Fixes a double-slash bug in the edit URL when multilingualMode is off — the theme appended an empty $lang segment unconditionally |
main/docs-navigation.html |
Scopes prev/next page navigation to .CurrentSection.RegularPages so arrows stay within the same keyboard's pages instead of jumping across the whole instructions section |
sidebar/section-menu.html |
Custom sidebar logic for the instructions section: when inside a keyboard (e.g. /instructions/forager/) only that keyboard's pages are shown; at the /instructions/ root all keyboards are listed. Other sections (basics, troubleshooting) use default Doks behaviour |
All non-SVG, non-GIF images use a two-image LQIP (low-quality image placeholder) crossfade instead of plain loading:
- A tiny 20×px webp at q20 is generated at build time and embedded as an inline base64
srcon an absolutely-positionedimg.blur-up-lqipelement - The real full-resolution image is a sibling
img.blur-upin the samespan.blur-up-wrapwrapper, starting atopacity: 0 - When the real image loads,
blur-up.js(loaded withdefer) fades the LQIP out and the real image in viaopacitytransitions — nosrcswap, no layout shift - The LQIP has
filter: blur(8px)andtransform: scale(1.1)(to hide blur edge bleed);overflow: hiddenon the wrapper clips the overflow. After the fade the LQIP is removed from the render tree viadisplay: noneso the blur compositing layer stops affecting neighbouring elements - The theme has its own
.blur-up { filter: blur(5px) }class (for lazysizes) — overridden withfilter: noneonimg.blur-upsince we don't use lazysizes - Content images (markdown
![...]()) are handled by a render hook atlayouts/_markup/render-image.html; instruction grid cards are handled inlayouts/instructions/list.html blur-up,blur-up-lqip,blur-up-wrap, andloadedare all safelisted inconfig/postcss.config.jsso PurgeCSS doesn't strip them in production
Two extra output formats are enabled beyond the Doks defaults:
markdown— each page is also rendered as raw markdown (at<page>/index.md)llms— anllms.txtis generated at the site root for AI consumption