aboutsummaryrefslogtreecommitdiffstats
path: root/AGENTS.md
diff options
context:
space:
mode:
authorgrm <grm@eyesin.space>2026-09-17 21:29:44 +0300
committergrm <grm@eyesin.space>2026-09-17 21:29:44 +0300
commite96bbec551c0388e972894b349413fa6ef607352 (patch)
treec63b50d515fc0a0f754f970b8d316dd37f73ac80 /AGENTS.md
parent69912c4ac04ada3286c09400ce523d5d3ab4cfb4 (diff)
downloadblogspace-e96bbec551c0388e972894b349413fa6ef607352.tar.gz
blogspace-e96bbec551c0388e972894b349413fa6ef607352.tar.bz2
blogspace-e96bbec551c0388e972894b349413fa6ef607352.zip
Add custom fonts: upload WOFF2/WOFF/TTF/OTF files and pick them on the Design tab
Fonts become a file kind of their own (sniffed from the bytes like images, served inline from /media), and the theme gets custom_font and custom_heading_font: a chosen file is declared as @font-face and put first in the stack, the built-in Font / Heading font choice behind it as the fallback, so there is no "custom" enum value and no invalid state. The pickers are plain selects with an upload input, no script. Fonts uploaded before this are reclassified by the migration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
Diffstat (limited to 'AGENTS.md')
-rw-r--r--AGENTS.md26
1 files changed, 20 insertions, 6 deletions
diff --git a/AGENTS.md b/AGENTS.md
index 84b6a7b..d4a9284 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -185,13 +185,23 @@ internal/web/ server.go (host router, middleware, render helpers)
header text colour; hidden on phones so a long title can wrap. `NavStyle`
`plain` is regular weight + underline for the menu links (blog.css makes
them bold).
+ **Custom fonts** (`CustomFont`, `CustomHeadingFont`: font file ids or ""):
+ a chosen file is declared as `@font-face` (`"BlogFont"` / `"BlogHeadingFont"`,
+ `src:url(/media/<id>)`, same-origin so no CORS) and put first in the stack,
+ the built-in `Font`/`HeadingFont` choice behind it as the fallback — so
+ there is no "custom" enum value and no invalid state. `HeadingFontFamily`
+ builds on the text's stack when `HeadingFont` is `same`, and the `h1…h6`
+ rule is emitted whenever `HeadingFontSet` (a heading choice or a heading
+ file). Picked with the `fontpick` partial: a plain `<select>` of the newest
+ `recentImages` library fonts (`kind = font`) plus the chosen older one, and
+ an upload input — no library panel, no script.
Optional colours (`LinkHover`, `NavHover`) are `""` = inherit and come
from a colour input paired with a `<name>_custom` checkbox, shown/hidden
with CSS only (`.hoverpick > input:not(:checked) ~ …`). Image fields use the
`imagepick` partial: a `<select>` of the `recentImages` newest library
images (`kind = image`) plus the chosen one (`imageNames` looks up an older
- one's filename), so the page never grows with the library; `none` clears, a
- uuid selects, no value keeps (`pickImage`); an upload in `<name>_file` wins.
+ one's filename, fonts included), so the page never grows with the library; `none` clears, a
+ uuid selects, no value keeps (`pickFile`); an upload in `<name>_file` wins.
The `imagelib` partial (one per form; the design page and the post form
both include it) is the panel behind "Choose from library…", with its own
script: it marks every `.imagepick` with `.js` (hiding the select, showing
@@ -245,15 +255,18 @@ internal/web/ server.go (host router, middleware, render helpers)
- **Files** (`files` table, `store/files.go`, `handlers_files.go`,
`filetype.go`, `/b/{sub}/files…`): the upload library, any type. A row is
`id, filename, content_type, kind, size, data`; `kind` (image, document,
- audio, video, archive, other) is decided at upload by `fileType`, which
+ audio, video, font, archive, other) is decided at upload by `fileType`, which
trusts `http.DetectContentType` first and lets the extension refine only a
generic sniff (text/plain, octet-stream) to a type on the allowlists in
`filetype.go`; anything unknown is stored as `application/octet-stream`.
+ Fonts (`fontTypes`: WOFF2, WOFF, TTF, OTF) are known by their bytes alone,
+ like images; TTC and EOT stay `other`.
`/media/{uuid}` is served on every host with immutable cache headers, from
the host's blog database only (the root domain serves the root blog's
files); dashboard previews for another blog on the root host use
`GET /b/{sub}/media/{id}` (`withBlog`). **The root domain carries the
- session cookie, so `servedAs` renders inline only images, PDF, plain text,
+ session cookie, so `servedAs` renders inline only images, fonts (a browser
+ parses one only inside `@font-face`), PDF, plain text,
audio and video; everything else (HTML, SVG, XML, JS, archives, binaries)
goes out as `application/octet-stream` + `Content-Disposition: attachment`.**
`?download` forces attachment. SVG is therefore never an image (download
@@ -293,7 +306,8 @@ internal/web/ server.go (host router, middleware, render helpers)
`outro_file`) + `appendFile(body, f, format)`
(the no-JS path: `fileMarkdown` or `fileHTML`, i.e. `<img>`/`<a>`), and
store `renderBody(format, src)` (`markdown.Render`, or the source untouched
- for HTML) in the `*_html` column — the public templates only ever print
+ for HTML) in the `*_html` column (`readUpload`'s last argument is the kind
+ the field must be — `"image"`, `"font"` — or `""` for anything) — the public templates only ever print
that column.
**HTML mode** (`.editor.html`, toggled by the radios): the Markdown toolbar
groups (`.ed-md`), link box, shortcuts, list continuation and cheat-sheet
@@ -444,7 +458,7 @@ superadmin password to `admin`. Production refuses both.
`-- +goose Up/Down` sections; they run automatically at startup. Never
edit an applied migration. Blog chain so far: `00001_init`, `00002_language`,
`00003_files`, `00004_tags`, `00005_search`, `00006_format`, `00007_post_image`,
- `00008_page_outro`, `00009_gizmo_style`; control: `00001_init`, `00002_upload_limit`. Both chains were re-baselined at 00001 after
+ `00008_page_outro`, `00009_gizmo_style`, `00010_font_kind`; control: `00001_init`, `00002_upload_limit`. Both chains were re-baselined at 00001 after
the move to per-blog databases; deployments from before it have
`goose_db_version` rows 2–7 in the control DB that must be deleted once
(README "Upgrading from a single database") or the next control migration