diff --git a/.gitignore b/.gitignore index 1c60840..a3b52bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ node_modules +.venv-font +__pycache__ .env dist *.log diff --git a/README.md b/README.md index 97fbeec..ceef7a7 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,13 @@ Starts the dev server at `http://localhost:3002` with file watching. pnpm typecheck ``` +## Around typeface + +Around is the site's custom geometric unicase display face. Its source geometry, +build environment, generated fonts, specimen, and design notes are all kept in +this repository. See [`docs/around-typeface.md`](docs/around-typeface.md) before +changing or regenerating it. + ## Publishing blog posts Blog posts are published as `site.standard.document` records to your PDS: diff --git a/docs/around-typeface.md b/docs/around-typeface.md new file mode 100644 index 0000000..ac43959 --- /dev/null +++ b/docs/around-typeface.md @@ -0,0 +1,171 @@ +# Around typeface + +Around is Cameron's custom geometric unicase display face for +[`cameron.stream`](https://cameron.stream). It was drawn and refined as a +**cosmological instrument**: precise, austere, slightly uncanny, and more like +an artifact than a conventional brand font. + +This repository contains everything required to rebuild and revisit it. No +FontLab, Glyphs, Illustrator, or private source file exists elsewhere. + +## Source of truth + +| File | Role | +| --- | --- | +| `scripts/generate-around-font.py` | Canonical glyph geometry, metrics, mappings, kerning, metadata, and font build | +| `scripts/requirements-around-font.txt` | Pinned build-only Python dependencies | +| `public/fonts/around-regular.ttf` | Generated desktop font committed for serving and download | +| `public/fonts/around-regular.woff2` | Generated web font committed for serving | +| `src/components/type-specimen.tsx` | Public `/type` specimen and design statement | +| `public/type.css` | Specimen composition and responsive behavior | +| `public/site.css` | Site-wide `@font-face`, display-font integration, and font cache key | + +The Python generator is the editable source. The TTF and WOFF2 files are build +artifacts and should never be edited directly. + +## Build from a clean checkout + +The font toolchain is deliberately separate from the website's Node runtime. + +```bash +python3 -m venv .venv-font +.venv-font/bin/python -m pip install -r scripts/requirements-around-font.txt +.venv-font/bin/python scripts/generate-around-font.py +``` + +The generator writes both files under `public/fonts/`. Builds are reproducible: +it uses a fixed `SOURCE_DATE_EPOCH` unless that standard environment variable +is explicitly supplied by the caller. Rebuilding unchanged source should +therefore leave the committed font binaries unchanged. + +After a real font change, increment the cache query attached to both font URLs +in `public/site.css`. Browsers can otherwise retain an older WOFF2 while the +server is already serving the new one. + +## Design grammar + +- **1000 units per em** with an **800-unit cap/unicase body**. +- **70-unit monoline construction** is the default skeleton grammar. +- Letters are built from lines, sampled arcs, cubic curves, and a small number + of precision geometries converted into TrueType outlines through Shapely. +- Uppercase and lowercase Unicode codepoints map to the same unicase glyphs. +- Character widths are proportional. Around is geometric, not monospaced. +- The ring `○` is a first-class glyph and recurring visual primitive. +- Inter remains the body and navigation face. Around is for names, titles, + statements, and display-scale moments. + +The generator has two construction layers: + +1. `GLYPHS` defines centerline skeletons for the broad family. +2. `precision_geometry()` replaces forms that need optical or contour-level + control after a generic stroked skeleton proves insufficient. + +That distinction is intentional. Do not force every glyph through one clever +abstraction. The face became good when the system allowed optical exceptions. + +## Settled forms and relationships + +These are design decisions, not unfinished cleanup: + +- **S** is the characteristic form. Preserve its distributed curvature and + current silhouette unless a real-scale proof exposes a concrete problem. +- **R** is deliberately upright and left-anchored. Its diagonal leg must not + extend beyond the bowl's right edge; the generator enforces this invariant. +- **P and R share bowl dimensions** through `PR_BOWL_RX` and `PR_BOWL_RY`. +- The **M, J, and 1** retain their unusual constructions. A review proposed + conventionalizing them; proofs did not justify losing their character. +- Current word spacing is **330 units**. The original 430-unit space made + sentences fragment into isolated word objects. +- Kerning is sparse and optical. Existing pairs should be judged in words, + rather than expanded mechanically into a large generic table. + +The current R leg centerline is `[(535, 500), (810, -50)]`. Treat that as a +protected relationship to the bowl, not an arbitrary pair of coordinates. + +## How to refine it + +1. Change the Python source, never the generated binaries. +2. Regenerate both font formats. +3. Open `/type` and inspect the face at hero, sentence, alphabet, numeral, and + small-display scales. +4. Inspect real site routes too. At minimum: `/`, `/travel`, `/blog`, one post + with a long title, and `/annotations`. +5. Check desktop, ordinary mobile, and a narrow 320px viewport. The specimen + contains intentionally oversized compositions, so hidden clipping can look + like successful overflow handling. +6. Proof changes in words containing the affected pair. For example, a `P/A` + change should be judged in `SPACE`, `PAPER`, and `PAIR`, not only `PA`. +7. Run the checks below, inspect the binary diff, and bump the font cache key. + +Useful proof strings: + +```text +SPACE DISPLAY PAPER TYPEFACE +PAIR APPROACH SHAPE OPEN +AVATAR WAYWARD YOUTH ATLAS +LOVE OVER MOVE ROUND +NEVER MEMORY SIGNAL SYSTEM +LA TYPE TO TA FO PA PO PE PR +``` + +Do not assess only enlarged isolated glyphs. Several forms that look peculiar +alone become coherent in words, while spacing defects often appear only in a +sentence or on a real page. + +## Validation + +Run the site type checker: + +```bash +pnpm typecheck +git diff --check +``` + +Confirm that the generated files can be fully parsed and contain the expected +metadata: + +```bash +.venv-font/bin/python - <<'PY' +from fontTools.ttLib import TTFont + +for path in ("public/fonts/around-regular.ttf", "public/fonts/around-regular.woff2"): + font = TTFont(path) + font.ensureDecompiled() + cmap = font.getBestCmap() + required = ( + set(range(ord("A"), ord("Z") + 1)) + | set(range(ord("a"), ord("z") + 1)) + | set(range(ord("0"), ord("9") + 1)) + | {0x25CB, 0x2018, 0x2019, 0x201C, 0x201D} + ) + assert required <= set(cmap) + assert font["OS/2"].usWeightClass == 400 + assert "GPOS" in font + print(path, len(font.getGlyphOrder()), "glyphs", len(cmap), "codepoints") +PY +``` + +At the time this document was written, both formats contain **58 glyphs** and +map **88 codepoints**. A deliberate character-set expansion will change those +figures; update this document when it does. + +## Known limits + +- Around is a display font, not a general-purpose text face. +- It currently covers the Latin alphabet, decimal numerals, selected + punctuation, typographic quotes, and the ring. It is not broad multilingual + coverage. +- There is one regular weight and no variable axis. +- Browser and page-level proofing remains part of the build. Binary validity + cannot tell whether a word rhythm or silhouette is actually good. + +## Deployment + +The font and specimen deploy with the rest of `cameron.stream`: + +```bash +fly deploy +``` + +After deployment, verify `https://cameron.stream/type` and confirm the WOFF2 +request uses the new cache key from `public/site.css`. diff --git a/public/fonts/around-regular.ttf b/public/fonts/around-regular.ttf index 464ce52cc81e67e4a77733f0f20e324857a9b17a..0b8fa79b4398f937675398d5bbc64a12362d21ed 100644 GIT binary patch delta 46 wcmZ2;m}$*nrU`*ef5IjP7fC_`2_<51H-e5&ITC0@xhq|0J%sK=Kufz delta 46 xcmZ2;m}$*nrU`*ee}g9m7fC;wI6a=<<|_j;^9u$B28L&q93TozZ+viO0RX0X5a|E_ diff --git a/public/fonts/around-regular.woff2 b/public/fonts/around-regular.woff2 index 843d56c62b27d60469f0a5c8d92d30916bfbb92a..f21ad2c1317214539f1323a662502a12b361e74b 100644 GIT binary patch literal 9376 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 literal 9364 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