1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
|
# ericzou.dev
Hand-written static site. No framework, no dependencies, no build step for the
site itself — the blog is the only generated part.
## Writing a post
```
npm run write
```
Opens the writing desk at <http://localhost:4000>: draft on the left, the real
post styling on the right, the list of everything you have written on the far
left. Drafts save themselves as you type. Published posts only change when you
press save.
Press **publish** and the post is written to `_writing/posts/`, its page is
built, and the blog index and RSS feed are regenerated. Then commit and push.
| Command | What it does |
| --- | --- |
| `npm run write` | Open the writing desk |
| `npm run blog` | Rebuild post pages, blog index and feed from source |
| `npm run preview` | Same, but includes drafts, into `_writing/.preview/` |
| `make` | Gzip everything for the server. Does **not** build the blog |
`make` is what the deploy hook runs on the server, and the server has no Node,
so `make` must never depend on the build. Generated blog files are committed
for the same reason. Run `npm run blog` before you commit.
`make` also needs GNU make, which isn't installed on the Windows box. The npm
scripts run anywhere Node does.
## Cover images
The **cover** field under the title attaches a 5:2 banner. It shows above the
title in the preview and on the post page, and becomes the post's `og:image`
for link previews. A 5:2 image up to 2000px wide is uploaded as-is. Anything
else is centre-cropped to 5:2 and re-encoded as webp in the browser first.
The file sits next to its source as `<slug>.cover.<ext>` and follows the post
through renames, publish, unpublish and delete. That means a draft's cover
stays in the gitignored `drafts/` too. The build copies it to
`blog/<slug>/cover.<ext>`. Covers are not part of version history.
## Deploying
**Pushing to `master` publishes the site.** A post-receive hook on
`git.skullheadx.com` picks up the push; nothing else is needed and there is no
rsync step any more.
That has one consequence worth remembering: **anything committed to `master`
is served publicly.** There is no separate allowlist of files to deploy.
Which is why `_writing/drafts/` is gitignored. An unfinished post lives only on
the machine you wrote it on, so it cannot reach the server by accident.
## Not losing a draft
Two layers, both local:
- **Autosave.** A draft writes itself to disk about a second and a half after
you stop typing. Published posts never autosave — those only change when you
press save.
- **History.** Every save that actually changed something keeps a snapshot in
`_writing/.history/<slug>/`, up to 60 per post. The **history** button lists
them newest first; opening one loads it into the editor without touching the
file, so a wrong pick costs nothing. Renaming a post carries its history with
it.
Both are gitignored, which means neither is a backup against losing the
machine. Publish or copy anything you would be upset to lose.
`_writing/` itself is committed, because it is the source the blog is built
from. It is inert on the server: the editor needs a local Node process to do
anything.
## Layout
```
index.html the whole site: six tabs, terminal docked at the bottom
style.css chrome and page styles, shared by every page
main.js tab switching, the terminal, keyboard navigation
blog.css blog.js generated — post page styles and behaviour
blog/<slug>/ generated — one directory per published post
feed.xml generated — RSS
card.html the Pokemon-style business card, standalone
_writing/ authoring: post sources, the editor, the build
posts/ published post sources, committed
drafts/ unpublished, gitignored, never deployed
.history/ local version snapshots, gitignored
```
Everything under `blog/`, plus `blog.css`, `blog.js` and `feed.xml`, is written
by `_writing/build.mjs`. Editing them by hand gets overwritten on the next
build. The sources are `_writing/lib/theme.mjs` and `_writing/lib/postjs.mjs`.
The blog index inside `index.html` and the post-list rules inside `style.css`
are injected between `BLOG:` marker comments. Leave the markers alone.
|