README
¶
coppice
coppice builds a small personal site with essays from markdown: one Go program, markdown in, static HTML out, no framework, no Node. A mathematical animation on the home page (ASCII shapes, Lissajous figures, cycloids, the Mandelbrot set's boundary), Tufte-style sidenotes in posts, a list of writings by year, galleries, a docs section with search, an RSS feed and a sitemap, colour themes with a dark side, highlighted code, and self-hosted fonts from Google Fonts.
It is free software under the GPL v3 (see Licence).
A site of your own
You need Go 1.25 or newer. coppice works as Jekyll does: your site is a folder, or a repository, of its own, and coppice is run in it. Its look, the templates, the stylesheet and the scripts, is built into the program.
go run github.com/spinneydev/coppice@latest -new mysite
cd mysite
go run github.com/spinneydev/coppice@latest -serve :8000
or go install github.com/spinneydev/coppice@latest once, then
coppice -new mysite, coppice and coppice -serve :8000.
-new writes a config.toml with every setting explained, a home page, a
contact page, a first post, an icon and a README. A site can change any
part of the look with files of its own: a template
in templates/, a stylesheet in styles/, anything in static/
(Customising). spinney's own sites are
built this way: spinney.dev
and canopy.spinney.dev,
whose docs are canopy's own markdown.
Or start from this example
This repository is also coppice's example site, with every feature at work: three posts, a gallery and coppice's docs.
-
Fork or clone this repository.
-
Edit
config.toml: the site's name, URL, description, the links on the home page, the animation, colours, fonts, analytics (or none), and the site's own words for a site in another language. -
Make the content yours:
content/index.mdis the home page's text, any othercontent/<name>.mda page at/<name>/, posts go incontent/posts/and docs incontent/docs/. The examples (an about page, a gallery of 118 public-domain paintings instatic/images/gallery/(34 MB), three posts, and coppice's own docs) are there to read and delete. -
Build and look:
go run . # build into public/ go run . -serve :8000 # build, serve on http://localhost:8000, # and build again whenever a file changes go run . -serve :8000 -drafts # with drafts and later posts tooThe first build downloads the fonts, so it needs the internet; they're cached in
.cache/fonts/after that.
Docs
The full documentation is coppice's own docs section, which the example
site builds at /docs/. Its sources read well here too:
- Getting started: building, the folders, what stops a build
- Settings: every setting in
config.toml - Pages, posts and docs: front matter, drafts, layouts
- Writing: markdown, sidenotes, callouts, pictures, mathematics, code
- Galleries
- Accessibility
- Deploying
- Customising: the stylesheet, templates, a theme or an animation of your own
Deploy
public/ is the whole site: upload it to any static host. On a host that
builds from git, such as Cloudflare Pages, make go run . the build
command and public the output directory, and set the environment
variable GO_VERSION to 1.25 or newer; for a site in a repository of
its own, the build command is go run github.com/spinneydev/coppice@latest.
See Deploying.
How it's put together
main.go the generator: Markdown, sidenotes, galleries, feed, sitemap, checks
fonts.go downloads the fonts from Google Fonts at build time
templates/ the theme: base.html wraps every page; one template per page type
styles/ the theme: the stylesheet in parts, joined into css/site.css
static/js/ the theme: the animations' and pages' scripts
starter/ what -new writes
config.toml the example site's settings
content/ the example site: index.md, a .md file per page, posts/, docs/
static/ the example site's pictures, copied as they are
tools/gifs/ records the animations as GIFs for this README (own module)
docs/ those GIFs; not part of the site
| url | source | template |
|---|---|---|
/ |
index.md |
index.html |
/posts/<slug>/ |
posts/<slug>.md |
post.html |
/writings/ |
all posts, grouped by year | writings.html |
/feed.xml |
all posts, full text (RSS 2.0) | (in main.go) |
/docs/<slug>/ |
docs/<slug>.md |
doc.html |
/docs/ |
docs/index.md and every doc |
docs.html |
/<name>/ |
<name>.md, e.g. contact |
page.html |
/404.html |
(none) | 404.html |
/sitemap.xml, /robots.txt |
every page but the 404 | (in main.go) |
The theme (templates/, styles/, static/js/) is built into the
program; the example site, here, has it as its own files too, so a change
to it shows at once with go run . -serve :8000. go test ./... runs the
tests, and builds this example site, a new one from -new, and one with
nothing but the built-in theme, checking every link in them.
Animations

shapes
curve
cycloid
mandelbrot
cycloid-horizontal
ford
golden
nautilus
sine
penrose
chords
newtonThe GIFs are recorded from the real scripts by tools/gifs; after
changing an animation, run go -C tools/gifs run . (or name the ones to
redo: go -C tools/gifs run . golden).
Set animation in config.toml. Each is one script in static/js/,
named after it; a page loads only the one it shows, through loop.js,
which pauses it off screen, shows a still frame to
people who ask for reduced motion, and carries the animation over from
home to contact so it doesn't restart. Without JavaScript they're hidden;
the site works without it.
shapes: a donut, cube, octahedron, Menger sponge, dodecahedron, gyroid and Hopf link in ASCII, after donut.c. Each character raymarches a signed distance field; a morph blends two fields, so shapes melt into each other.curve: a dot stretches into a line that opens into a Lissajous figure, then loops through Lissajous figures and rose curves. Every curve has the same 1200 points, so a morph moves each point to its partner.cycloid: a circle rolls round a fixed one and its pen draws a cardioid, nephroid, epicycloid, deltoid, astroid and hypocycloid.mandelbrot: the curves |z_n(c)| = 2 for n = 2 to 10 and back, closing in on the Mandelbrot set's boundary. Each is traced in the browser by following z_n(c) = 2·e^(i·2^(n-1)·θ) round in small steps; θ is the same "external angle" on every curve, so a morph moves each point a short way.cycloid-horizontal: a circle rolls along a line, end to end and back, never stopping; from edge to edge its pen slides along the spoke, drawing normal, curtate and prolate (looped) cycloids.medley:curve,cycloidandcycloid-horizontalas one, with one line throughout. The last rose becomes a cardioid as the circles fade in; later the fixed circle unrolls into a line and the curve into the trail of a circle rolling along it (a cycloid is an epicycloid round an infinitely large circle); then the trail closes back into the first Lissajous figure. Every change blends the same points, so nothing cuts.ford: Ford circles. Over every fraction p/q between 0 and 1, in lowest terms, a circle of radius 1/(2q²) resting on a line; no two overlap, and neighbours in a Farey sequence touch. The two big ones over 0 and 1 rise first, then each denominator up to 20, smaller and quicker; they hold, then sink back, smallest first.golden: a dot stretches into a line, which curls up into the golden spiral (a logarithmic spiral widening by φ every quarter turn), holds, then uncurls back to the dot. The line bends but never stretches: each stage is the same line partly rolled up.nautilus: a nautilus shell in section. The line curls as ingolden, but into a spiral widening 3 times a turn, close to a real nautilus; then the mouth closes it and the septa, the curved walls between its chambers, grow in from the innermost out, leaving the last third of a turn open as the body chamber.sine: a dot stretches into a line; then a pen at its left end bobs up and down, speeding up from rest, and its height, carried off to the right at a steady speed, draws a sine wave that runs out along the flat line until it fills it.penrose: the Penrose triangle, the impossible one: three square beams at right angles in a loop. It starts to turn, slowly, and turns for ever; every third of a turn it looks as it began. It's three faces of real beams seen isometrically, each a third of a turn from the next.chords: N points round a circle, each joined to the point k times as far round. The chords' envelope is a cardioid for k = 2, a nephroid for 3, then epicycloids; the chords are drawn in one by one, then k counts up to 6, the chords sliding from curve to curve, and back.newton: Newton's cradle. The first ball is lifted and let go; it stops dead as it hits the row and the last one swings out and back, for ever.random: one of these, picked once per visit and kept from page to page; a return visit gets a different one.custom: likerandom, but only from the ones listed incustom, e.g.custom = ["curve", "cycloid", "ford"].
Licence
GNU GPL v3, or any later version. Use it, change it, run your own site with it; if you share a changed version, share its source under the GPL too.
Your own writing and pictures are yours: set their licence in [license]
in config.toml, shown under each post and in the feed. The fonts aren't
in this repository; they're downloaded at build time and carry their own
licences (both defaults are under the SIL Open Font License).
coppice, a small program that builds a personal site from markdown.
Copyright (C) 2026 The spinney authors
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
Public License for more details.
Documentation
¶
Overview ¶
Fonts from Google Fonts, served from this site.
The build asks Google Fonts for the families named in config.toml, downloads the font files into public/fonts/ and writes public/css/fonts.css to match. Pages only ever load fonts from this site; visitors never contact Google.
Variable fonts come with their full weight range (and optical size, if they have it); static fonts with the few weights the CSS uses. Only the subsets in [fonts] subsets are kept. Everything downloaded is cached in the site's .cache/fonts/, so later builds work offline; delete it to fetch afresh.
Tiny static site generator, run in a site's folder as Jekyll is: its content/ and config.toml into public/, set in coppice's theme (its templates/, styles/ and static/js/, built into the program), with any templates, styles and static files of the site's own over it.