From 96e96730401e1bafbacb6a8b116fea950653d42e Mon Sep 17 00:00:00 2001 From: Markus Neusinger <2921697+MarkusNeusinger@users.noreply.github.com> Date: Wed, 16 Sep 2026 18:54:11 +0200 Subject: [PATCH 1/2] =?UTF-8?q?feat(fonts):=20MonoLisa=203.000=20=E2=80=94?= =?UTF-8?q?=20Code=20everywhere,=20Text=20registered=20for=20experiments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit anyplot.ai moves from MonoLisa 2.017 to 3.000. The v3 webfonts (70 Unicode-range subsets per family, variable wght + GRAD) live under gs://anyplot-static/fonts/v3/{code,text}/ and the four variable TTFs under fonts/v3/desktop/; app/src/styles/fonts.css is generated from the order page's builder CSS by automation/scripts/monolisa_fonts_css.py. Two v2 → v3 breaking changes are absorbed: the script italic is `ss01` (the old `ss02` tag now means "alt i r"), and the coding ligatures moved from `liga` to `dlig`, which tokens.css re-enables on pre/code/kbd/samp so code blocks keep their ligatures. The OG-image renderer loads the v3 Code TTFs, sets only the wght axis of the now two-axis font (a bare [weight] would raise on v3 and silently keep the default), caches under /tmp/anyplot-fonts/v3 and draws the tagline with ss01. CSS families are 'MonoLisa Code' and 'MonoLisa Text' (new --text token, unused by default so nothing is fetched). Docs, design-sync CSS and notes follow. The v2 files stay in the bucket until the cutover is verified on anyplot.ai. Co-Authored-By: Claude Fable 5.1 --- .design-sync/NOTES.md | 9 +- .design-sync/conventions.md | 4 +- .gitignore | 1 + api/main.py | 2 +- app/.designsync-fonts/monolisa.css | 31 +- app/index.html | 10 +- app/src/main.tsx | 2 +- app/src/sections/landing/HeroSection.tsx | 2 +- app/src/styles/fonts.css | 1049 +++++++++++++++++++--- app/src/styles/tokens.css | 34 +- app/src/theme/tokens.ts | 15 +- automation/scripts/monolisa_fonts_css.py | 229 +++++ changelog.d/monolisa-v3.md | 15 + core/images.py | 53 +- docs/reference/seo.md | 13 +- docs/reference/style-guide.md | 66 +- tests/unit/core/test_images.py | 76 +- 17 files changed, 1374 insertions(+), 237 deletions(-) create mode 100644 automation/scripts/monolisa_fonts_css.py create mode 100644 changelog.d/monolisa-v3.md diff --git a/.design-sync/NOTES.md b/.design-sync/NOTES.md index 6086669bc01..94d1573dc52 100644 --- a/.design-sync/NOTES.md +++ b/.design-sync/NOTES.md @@ -17,15 +17,16 @@ This is an **app-shape** sync — the *source* is the `app/` website (`anyplot-w ## MonoLisa fonts (re-fetch on a fresh clone) -MonoLisa is proprietary (© FaceType Foundry, CORS-locked GCS) and is **NOT in this public repo**. The variable ttf are fetched at sync time into `app/.designsync-fonts/` (the `.ttf`/`.woff2` binaries are gitignored; only the `@font-face` CSS there is committed) and shipped only to the private design project — never committed. `app/.designsync-fonts/monolisa.css` (the `@font-face`, committed) references them. On a fresh clone, re-fetch before building: +MonoLisa is proprietary (© FaceType Foundry, CORS-locked GCS) and is **NOT in this public repo**. The variable ttf (v3: MonoLisa Code + MonoLisa Text, upright + italic) are fetched at sync time into `app/.designsync-fonts/` (the `.ttf`/`.woff2` binaries are gitignored; only the `@font-face` CSS there is committed) and shipped only to the private design project — never committed. `app/.designsync-fonts/monolisa.css` (the `@font-face`, committed) references them. On a fresh clone, re-fetch before building: ```sh mkdir -p app/.designsync-fonts && cd app/.designsync-fonts -curl -sSfo MonoLisaVariableNormal.ttf https://storage.googleapis.com/anyplot-static/fonts/MonoLisaVariableNormal.ttf -curl -sSfo MonoLisaVariableItalic.ttf https://storage.googleapis.com/anyplot-static/fonts/MonoLisaVariableItalic.ttf +for f in MonoLisaCodeUpright MonoLisaCodeItalic MonoLisaTextUpright MonoLisaTextItalic; do + curl -sSfo "$f.ttf" "https://storage.googleapis.com/anyplot-static/fonts/v3/desktop/$f.ttf" +done ``` -The variable ttf cover the full glyph range (normal + italic, weights 100–900, the `ss02` script set intact). CORS is browser-only, so `curl` works even though browser fetches are restricted to `anyplot.ai`. +The variable ttf cover the full glyph range (upright + italic, axes `wght` 1–1000 and `GRAD` −50…50, the `ss01` script set intact in the italics). CORS is browser-only, so `curl` works even though browser fetches are restricted to `anyplot.ai`. ## Known render warns - None. (`[RENDER_THIN]` on Footer in an early pass was fixed by collapsing its preview to a single cell — its two variants differed only in a hidden link href.) diff --git a/.design-sync/conventions.md b/.design-sync/conventions.md index 92a38810c32..185710efb74 100644 --- a/.design-sync/conventions.md +++ b/.design-sync/conventions.md @@ -28,12 +28,12 @@ Style with the `var(--*)` tokens, not invented hex. The vocabulary (all defined - **Surfaces** — warm off-white, *never* pure `#fff`: `--bg-page`, `--bg-surface`, `--bg-elevated` - **Ink** (warm grayscale text) + hairlines: `--ink`, `--ink-soft`, `--ink-muted`, `--rule` - **imprint palette — 8 categorical hues, reserved for PLOTS/charts, not UI chrome:** `--imprint-green` (`#009E73`, the brand), `--imprint-lavender`, `--imprint-blue`, `--imprint-ochre`, `--imprint-red`, `--imprint-cyan`, `--imprint-rose`, `--imprint-lime`; `--imprint-amber` = warning. -- **Type stacks:** `--mono` ( = `--serif` = `--sans`, all MonoLisa); **code surface:** `--code-bg`, `--code-text`, `--code-border` (+ `--code-comment`/`-keyword`/`-string`/… syntax tokens) +- **Type stacks:** `--mono` ( = `--serif` = `--sans`, all MonoLisa Code); `--text` (MonoLisa Text, proportional — experiments only, unused by default); **code surface:** `--code-bg`, `--code-text`, `--code-border` (+ `--code-comment`/`-keyword`/`-string`/… syntax tokens) - **Layout:** `--gutter` (24px), `--max` (1240px — paper/reading width), `--max-catalog` (2200px — plot grids) ## Typography — one typeface, two voices -**MonoLisa everywhere** — body, UI, nav, buttons, logo, headlines. There is no second font. The editorial accent is **italic**, which auto-triggers MonoLisa's `ss02` script set (enabled globally via `font-feature-settings: "ss02"` on `html`). So: upright = structure/prose/labels; `font-style: italic` = emphasis/taglines/title accents (renders as flowing script). Bold (700) only at display sizes. +**MonoLisa Code everywhere** — body, UI, nav, buttons, logo, headlines. There is no second font in use (MonoLisa Text is registered but reserved for experiments). The editorial accent is **italic**, which auto-triggers MonoLisa's `ss01` script set (enabled globally via `font-feature-settings: "ss01"` on `html`; v3 tag — `ss02` was the v2 tag and now means "alt i r"). So: upright = structure/prose/labels; `font-style: italic` = emphasis/taglines/title accents (renders as flowing script). Bold (700) only at display sizes. Code surfaces (`pre`, `code`) add `dlig` for the coding ligatures. ## Color discipline (the core rule) diff --git a/.gitignore b/.gitignore index 5734dd533a7..711db4d6c35 100644 --- a/.gitignore +++ b/.gitignore @@ -274,4 +274,5 @@ ds-bundle/ # proprietary brand font (MonoLisa, © FaceType Foundry) — fetched at sync time, never committed # (the @font-face CSS at app/.designsync-fonts/monolisa.css IS committed) app/.designsync-fonts/*.ttf +app/.designsync-fonts/*.otf app/.designsync-fonts/*.woff2 diff --git a/api/main.py b/api/main.py index 451eb0da4c1..44440fee182 100644 --- a/api/main.py +++ b/api/main.py @@ -113,7 +113,7 @@ async def lifespan(app: FastAPI): logger.info("Starting anyplot API...") # Text shaping decides whether the OG cards carry MonoLisa's italic swashes - # (`ss02`) and kerning at all — log it once so a degraded image is visible + # (`ss01`) and kerning at all — log it once so a degraded image is visible # in Cloud Run logs instead of only in a pixel diff of the served PNG. if has_text_shaping(): logger.info("Text shaping (libraqm): available") diff --git a/app/.designsync-fonts/monolisa.css b/app/.designsync-fonts/monolisa.css index ddb1b0ad029..dec028f2ddb 100644 --- a/app/.designsync-fonts/monolisa.css +++ b/app/.designsync-fonts/monolisa.css @@ -1,24 +1,41 @@ /* - * MonoLisa (variable) — brand typeface for the anyplot design system. + * MonoLisa 3.000 (variable: wght + GRAD) — brand typeface for the anyplot + * design system. Two families: MonoLisa Code (monospaced, used everywhere) + * and MonoLisa Text (proportional sibling, registered for experiments). * Proprietary, © FaceType Foundry. The .ttf binaries are fetched at sync time * from the anyplot GCS bucket into this directory and gitignored (this CSS is * committed); they are shipped only into the private claude.ai/design project, * never committed to the (public) repo. * * Re-fetch (see .design-sync/NOTES.md): - * curl -sSfo MonoLisaVariableNormal.ttf https://storage.googleapis.com/anyplot-static/fonts/MonoLisaVariableNormal.ttf - * curl -sSfo MonoLisaVariableItalic.ttf https://storage.googleapis.com/anyplot-static/fonts/MonoLisaVariableItalic.ttf + * for f in MonoLisaCodeUpright MonoLisaCodeItalic MonoLisaTextUpright MonoLisaTextItalic; do + * curl -sSfo $f.ttf https://storage.googleapis.com/anyplot-static/fonts/v3/desktop/$f.ttf + * done */ @font-face { - font-family: 'MonoLisa'; - src: url('./MonoLisaVariableNormal.ttf') format('truetype'); + font-family: 'MonoLisa Code'; + src: url('./MonoLisaCodeUpright.ttf') format('truetype'); font-weight: 100 900; font-style: normal; font-display: swap; } @font-face { - font-family: 'MonoLisa'; - src: url('./MonoLisaVariableItalic.ttf') format('truetype'); + font-family: 'MonoLisa Code'; + src: url('./MonoLisaCodeItalic.ttf') format('truetype'); + font-weight: 100 900; + font-style: italic; + font-display: swap; +} +@font-face { + font-family: 'MonoLisa Text'; + src: url('./MonoLisaTextUpright.ttf') format('truetype'); + font-weight: 100 900; + font-style: normal; + font-display: swap; +} +@font-face { + font-family: 'MonoLisa Text'; + src: url('./MonoLisaTextItalic.ttf') format('truetype'); font-weight: 100 900; font-style: italic; font-display: swap; diff --git a/app/index.html b/app/index.html index de9537798f4..b85c3e53f5d 100644 --- a/app/index.html +++ b/app/index.html @@ -73,12 +73,12 @@ - - - - + + + + - +