diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index b27122f..aba0a5f 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -17,8 +17,14 @@ - Lucide, Lucide contributors, ISC: https://github.com/lucide-icons/lucide - React Markdown and remark-gfm, unified contributors, MIT: https://github.com/remarkjs/react-markdown and https://github.com/remarkjs/remark-gfm -- DM Sans and IBM Plex Mono font packages, SIL Open Font License 1.1: - https://fontsource.org/fonts/dm-sans and https://fontsource.org/fonts/ibm-plex-mono +- Open Runde by Laurids Kern, a rounded Inter (The Inter Project Authors), + SIL Open Font License 1.1: https://github.com/lauridskern/open-runde. + The UI ships "AXP Runde", Open Runde with one added glyph (a hooked + lowercase l under `cv05`/`ss01`); the build script is + `scripts/fonts/build-axp-runde.py` and the license is in `ui/src/fonts` + and the built UI license directory. +- IBM Plex Mono font package, SIL Open Font License 1.1: + https://fontsource.org/fonts/ibm-plex-mono The UI build collects the full license texts of its bundled dependencies and their runtime dependency graph into `dist/ui/licenses/bundled.txt`. diff --git a/docs/design/type-specimen.html b/docs/design/type-specimen.html new file mode 100644 index 0000000..c4a0fdc --- /dev/null +++ b/docs/design/type-specimen.html @@ -0,0 +1,538 @@ + + + + + + AXP type specimen + + + + + + +

AXP type specimen

+

+ The UI face and its signature glyph, then ten open-licensed alternatives + for comparison, then the monospace and handwriting shortlist. Everything + here is SIL OFL or similar. +

+ +
+
+

AXP Runde · cv05 on

+
+ Open Runde (rounded Inter) plus a hooked lowercase l. This is what the + app ships. +
+
Illinois lily
all the little tools
+

+ The little agent left a checkpoint. Pull the parser fix locally, then + let the tool finish. + 1lI 0O — All contributions · Illegal fallback +

+
+ Checkpoint readyNew contributionNeeds your input +
+
+
+

Open Runde · as published

+
+ Same outlines with the feature off. Note how l and I collide in + “Illinois”. +
+
Illinois lily
all the little tools
+

+ The little agent left a checkpoint. Pull the parser fix locally, then + let the tool finish. + 1lI 0O — All contributions · Illegal fallback +

+
+ Checkpoint readyNew contributionNeeds your input +
+
+
+ +

Ten alternatives (sans, for UI text)

+
+
+
+ NunitoOFL · variable 200–1000 +
+
Illinois lily, all the little tools
+
+ The little agent left a checkpoint. Pull the parser fix locally, then + let the tool finish. 1lI 0O +
+
+ The Animal Crossing library’s pick. Fully rounded terminals, very + warm, slightly wide. The l has no tail. +
+
+
+
+ Zen Maru GothicOFL · 5 weights +
+
Illinois lily, all the little tools
+
+ The little agent left a checkpoint. Pull the parser fix locally, then + let the tool finish. 1lI 0O +
+
+ Japanese rounded gothic with a charming Latin. The most “kawaii” + option; covers JP if the project ever needs it. +
+
+
+
+ M PLUS Rounded 1cOFL · 7 weights +
+
Illinois lily, all the little tools
+
+ The little agent left a checkpoint. Pull the parser fix locally, then + let the tool finish. 1lI 0O +
+
+ Rounder and softer than Zen Maru; a bit toy-like at large sizes, fine + at 12–14px. +
+
+
+
+ QuicksandOFL · variable 300–700 +
+
Illinois lily, all the little tools
+
+ The little agent left a checkpoint. Pull the parser fix locally, then + let the tool finish. 1lI 0O +
+
+ Geometric rounded. Light and airy; small sizes get thin and the l/I + problem is worse. +
+
+
+
+ FredokaOFL · variable 300–700 +
+
Illinois lily, all the little tools
+
+ The little agent left a checkpoint. Pull the parser fix locally, then + let the tool finish. 1lI 0O +
+
+ Chunky and playful. Good for stamps and big labels, tiring as body + text. +
+
+
+
+ FigtreeOFL · variable 300–900 +
+
Illinois lily, all the little tools
+
+ The little agent left a checkpoint. Pull the parser fix locally, then + let the tool finish. 1lI 0O +
+
+ Friendly geometric with soft joins, not rounded terminals. Excellent + UI text; less personality. +
+
+
+
+ Plus Jakarta SansOFL · variable 200–800 +
+
Illinois lily, all the little tools
+
+ The little agent left a checkpoint. Pull the parser fix locally, then + let the tool finish. 1lI 0O +
+
+ Modern, slightly quirky grotesque. Reads “startup”, not “outdoors”. +
+
+
+
LexendOFL · variable 100–900
+
Illinois lily, all the little tools
+
+ The little agent left a checkpoint. Pull the parser fix locally, then + let the tool finish. 1lI 0O +
+
+ Readability-first, wide set. Distinctive but takes a lot of horizontal + room in dense UI. +
+
+
+
+ Recursive (Casual)OFL · variable, CASL + MONO axes +
+
Illinois lily, all the little tools
+
+ The little agent left a checkpoint. Pull the parser fix locally, then + let the tool finish. 1lI 0O +
+
+ One family that slides from sans to mono and from linear to casual. + Real personality (the a, g, l). Strongest identity candidate if we + want a single family for UI + code. +
+
+
+
+ Atkinson Hyperlegible NextOFL · variable 200–800 +
+
Illinois lily, all the little tools
+
+ The little agent left a checkpoint. Pull the parser fix locally, then + let the tool finish. 1lI 0O +
+
+ Disambiguation is the whole design: tailed l, serifed I, slashed 0. + Proof the “signature glyph” idea has legs; a bit clinical for our + mood. +
+
+
+
Varela RoundOFL · one weight
+
Illinois lily, all the little tools
+
+ The little agent left a checkpoint. Pull the parser fix locally, then + let the tool finish. 1lI 0O +
+
+ Lovely rounded shapes but a single weight rules it out for UI + hierarchy. +
+
+
+ +

Monospace shortlist (code, diffs, IDs)

+
+
+
+ JetBrains MonoOFL · Google Fonts +
+
if (!input.trim()) {
+
+ throw new Error('Add a value before parsing.'); const l = 1; // l vs 1 + vs I vs | +
+
+ Safe, tall x-height, ligatures optional. Little personality. +
+
+
+
+ Victor MonoOFL · Google Fonts +
+
if (!input.trim()) {
+
+ throw new Error('Add a value before parsing.'); + const l = 1; // cursive italics +
+
+ Narrow, with cursive italics that suit comments and hand-written + margin notes. +
+
+
+
+ Recursive Mono CasualOFL · Google Fonts +
+
if (!input.trim()) {
+
+ throw new Error('Add a value before parsing.'); const l = 1; // casual + axis at 1 +
+
+ Hand-drawn feel while staying strictly monospaced. Pairs naturally + with the sans above. +
+
+
+
+ Maple MonoOFL · github.com/subframe7536/maple-font +
+
(not loaded here)
+
+ The l with the curl is the reference you mentioned. Ligatures, + italics, and a build service to tweak features; a strong option once + we decide whether code should carry the same hook as the UI face. +
+
+ Also worth a look: Monaspace Radon (OFL, handwriting mono with texture + healing), Fantasque Sans Mono, Commit Mono, 0xProto, Iosevka + (buildable curly variants). +
+
+
+ +

Handwriting accent (margin notes, stamps, the discussion voice)

+
+
+
CaveatOFL · Google Fonts
+
+ Kept the return type intact — second pair of eyes? +
+
+ Loose, quick, legible at 18px+. Good for reviewer scribbles beside a + diff. +
+
+
+
+ Patrick HandOFL · Google Fonts +
+
+ Kept the return type intact — second pair of eyes? +
+
+ Neater print hand; works smaller than Caveat. The closest free + analogue to highlighte.rs’s “Letters Home”. +
+
+
+ + + + diff --git a/docs/design/typography.md b/docs/design/typography.md new file mode 100644 index 0000000..5f1ee6b --- /dev/null +++ b/docs/design/typography.md @@ -0,0 +1,83 @@ +# Typography + +## What changed + +The UI face is now **AXP Runde**: [Open Runde](https://github.com/lauridskern/open-runde) +(Laurids Kern's rounded cut of Inter, SIL OFL 1.1) plus one glyph of our own, a +hooked lowercase `l`, exposed as the OpenType features `cv05` and `ss01`. With the +features off the font is Open Runde, outline for outline. The app turns `cv05` +on globally in `ui/src/style.css`. + +DM Sans is gone. IBM Plex Mono stays for code until the monospace decision below. + +Files: + +- `ui/src/fonts/axp-runde/*.woff2` — the four built weights (400/500/600/700) +- `ui/src/fonts/open-runde/*.woff2` — the unmodified upstream files, kept for comparison +- `ui/src/fonts/axp-runde.css` — `@font-face` rules; imported from `ui/src/main.tsx` +- `scripts/fonts/build-axp-runde.py` — reproducible build (fontTools + brotli) +- `docs/design/type-specimen.html` — open it in a browser to compare everything below + +## Why a signature glyph + +You pointed at Maple Mono's curled `l` as the kind of detail that makes a face +recognisable, and highlighte.rs gets a similar effect from Inter's own `cv05` +(tailed `l`). Open Runde dropped Inter's alternate glyphs when it was rounded, +so there was nothing to switch on. Drawing one is a small amount of geometry: +the stem is a rounded rectangle, and the hook is a stroke about three-quarters +of the stem width that sweeps off the baseline into a round terminal sitting +half a stem-width up. It is generated from each weight's actual stem, so it +stays proportional from Regular to Bold. + +Two practical wins ride along with the identity: + +- `l`, `I` and `1` stop colliding ("Illinois", "1lI") at the 10–12px sizes this UI uses. +- Because it is a feature, any surface can opt out (`font-feature-settings: "cv05" 0`) + without loading another font. + +Known limits: the new glyph is not in the kerning classes, so pairs like `ly` +and `lt` use default spacing; Open Runde is static (no variable axes), so the +CSS weights `550`/`650` were mapped to `600`/`700`. + +## Ten alternatives, and why not + +All open-licensed, all usable at UI sizes, deliberately different from each other. +See the specimen for the rendered comparison. + +| Face | Character | Verdict | +| -------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------- | +| Nunito | Fully rounded, warm, slightly wide | The Animal Crossing library's default; friendly but generic in 2026 | +| Zen Maru Gothic | Japanese rounded gothic, charming Latin | Most "kawaii"; strong second choice, and gives us JP coverage | +| M PLUS Rounded 1c | Softer, rounder than Zen Maru | Toy-like at display sizes | +| Quicksand | Geometric rounded, light | Too thin at 11–12px; l/I worse | +| Fredoka | Chunky, playful | Great for stamps and big labels, not for body | +| Figtree | Friendly geometric, soft joins | Excellent UI text, little personality | +| Plus Jakarta Sans | Quirky modern grotesque | Reads "startup", not "outdoors" | +| Lexend | Readability-first, wide | Eats horizontal room in dense views | +| Recursive | Variable: sans↔mono, linear↔casual, one family | Strongest alternative identity; the casual axis has real hand-drawn charm | +| Atkinson Hyperlegible Next | Tailed l, serifed I, slashed 0 by design | Proves the signature-glyph idea; feels clinical for our mood | +| Varela Round | Lovely rounded shapes | One weight only, so no hierarchy | + +Recommendation: keep AXP Runde. If we ever want more character than a rounded +Inter can give, Recursive is the one to prototype next, because it would let +UI and code share one family (and its casual mono is close to what the +discussion voice wants). + +## Monospace: open decision + +Candidates, all OFL: Maple Mono (the curled `l` you referenced; ligatures; +build service), Recursive Mono Casual (pairs with the sans, hand-drawn feel), +Monaspace Radon (handwriting mono with texture healing), JetBrains Mono (safe), +Victor Mono (narrow, cursive italics for comments), Fantasque Sans Mono, +Commit Mono, 0xProto, Iosevka (buildable curly variants). + +My lean: Maple Mono for code and diffs so the `l` hook echoes across UI and +code, with ligatures off in diffs (they hide characters reviewers need to see). +It is not on Google Fonts, so it would be vendored like AXP Runde. + +## Handwriting accent + +highlighte.rs sets its quotes in a handwriting face ("Letters Home"), and the +review-tools experiment wants the same for reviewer scribbles and stamps. +Caveat (loose, 18px+) and Patrick Hand (neater, works smaller) are the two +free faces that fit; both are in the specimen. diff --git a/package-lock.json b/package-lock.json index fb6270b..20cdc49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,6 @@ "devDependencies": { "@axe-core/playwright": "4.13.0", "@eslint/js": "^10.0.0", - "@fontsource-variable/dm-sans": "5.3.0", "@fontsource/ibm-plex-mono": "5.3.0", "@pierre/diffs": "1.4.1", "@pierre/trees": "1.0.0-beta.6", @@ -676,16 +675,6 @@ "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@fontsource-variable/dm-sans": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@fontsource-variable/dm-sans/-/dm-sans-5.3.0.tgz", - "integrity": "sha512-BpUG5bqePiDFMBM4ZtLSlPdAIOM990uB1dlXzIf4aw21yQR7BmykedLXXXMqGWsR18aMLOsbWccwJNh3ztTQaA==", - "dev": true, - "license": "OFL-1.1", - "funding": { - "url": "https://github.com/sponsors/ayuhito" - } - }, "node_modules/@fontsource/ibm-plex-mono": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@fontsource/ibm-plex-mono/-/ibm-plex-mono-5.3.0.tgz", diff --git a/package.json b/package.json index 880335d..0d1faa5 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,6 @@ "devDependencies": { "@axe-core/playwright": "4.13.0", "@eslint/js": "^10.0.0", - "@fontsource-variable/dm-sans": "5.3.0", "@fontsource/ibm-plex-mono": "5.3.0", "@pierre/diffs": "1.4.1", "@pierre/trees": "1.0.0-beta.6", diff --git a/scripts/fonts/build-axp-runde.py b/scripts/fonts/build-axp-runde.py new file mode 100644 index 0000000..1b10515 --- /dev/null +++ b/scripts/fonts/build-axp-runde.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python3 +"""Build the AXP Runde web fonts from Open Runde. + +Open Runde (https://github.com/lauridskern/open-runde) is a rounded Inter. It +ships without Inter's alternate glyphs, so there is no tailed lowercase l to +switch on. This script adds one: a rounded, slightly upturned tail drawn from +the stem geometry of each weight, exposed as OpenType features `cv05` (the tag +Inter uses for the same idea) and `ss01`. Everything else is byte-for-byte the +same outline data, so with the features off the font renders as Open Runde. + +Usage: + python3 -m venv .venv && .venv/bin/pip install fonttools brotli + .venv/bin/python scripts/fonts/build-axp-runde.py /path/to/open-runde/src/desktop ui/src/fonts/axp-runde + +The output family is renamed to "AXP Runde" as the SIL OFL asks for modified +fonts. The original license travels with the files. +""" +from __future__ import annotations + +import sys +from pathlib import Path + +from fontTools.fontBuilder import FontBuilder +from fontTools.pens.boundsPen import BoundsPen +from fontTools.pens.t2CharStringPen import T2CharStringPen +from fontTools.ttLib import TTFont +from fontTools.ttLib.tables import otTables as ot + +K = 0.5523 # circular arc approximation constant for cubic Béziers + +WEIGHTS = {"Regular": 400, "Medium": 500, "Semibold": 600, "Bold": 700} + + +def stem_of_l(glyph_set): + """Return (left, right, radius) of the plain l stem.""" + bp = BoundsPen(glyph_set) + glyph_set["l"].draw(bp) + left, _, right, top = bp.bounds + # The stem's rounded corner radius is the y of the first on-curve point + # (see the outline dump in the design notes); recover it from the contour. + from fontTools.pens.recordingPen import RecordingPen + + rp = RecordingPen() + glyph_set["l"].draw(rp) + first = rp.value[0][1][0] + radius = first[1] + return left, right, radius, top + + +def draw_tailed_l(pen, left, right, radius, top): + """Draw `l.tail`: the original stem ending in a short, upturned hook. + + The hook is what makes it read as a lowercase l with a tail (like Inter's + cv05) rather than a small capital L: it is shorter than the stem is wide, + it curves the whole way, and its terminal sits well above the baseline. + Direction matches the source contour (right edge up first). + """ + w = right - left + thick = w * 0.74 # the hook is a stroke, visibly lighter than the stem + term_r = thick / 2 + cx = right + w * 0.58 # terminal centre: the hook reaches ~0.95w past the stem + cy = w * 0.50 # ...and its centre sits half a stem-width up + end_x = cx + term_r + bottom_y = cy - term_r # lowest point of the terminal, above the baseline + top_y = cy + term_r + join_y = top_y + w * 0.62 # where the hook's inner curve meets the stem + + pen.moveTo((right, join_y)) + pen.lineTo((right, top - radius)) + pen.curveTo((right, top - radius * (1 - K)), (right - radius * (1 - K), top), (right - radius, top)) + pen.lineTo((left + radius, top)) + pen.curveTo((left + radius * (1 - K), top), (left, top - radius * (1 - K)), (left, top - radius)) + pen.lineTo((left, radius)) + pen.curveTo((left, radius * (1 - K)), (left + radius * (1 - K), 0), (left + radius, 0)) + # Outer edge: along the baseline, then one continuous sweep up into the terminal. + pen.lineTo((right - w * 0.05, 0)) + pen.curveTo((right + w * 0.36, 0), (cx - term_r * 0.55, bottom_y), (cx, bottom_y)) + # Terminal: two quarter arcs, bottom → right → top. + pen.curveTo((cx + term_r * K, bottom_y), (end_x, cy - term_r * K), (end_x, cy)) + pen.curveTo((end_x, cy + term_r * K), (cx + term_r * K, top_y), (cx, top_y)) + # Inner edge: sweep from the terminal top back into the stem. + pen.curveTo((cx - term_r * 0.9, top_y), (right, top_y + w * 0.12), (right, join_y)) + pen.closePath() + return end_x + + +def add_feature(gsub, tag, lookup_index): + fr = ot.FeatureRecord() + fr.FeatureTag = tag + fr.Feature = ot.Feature() + fr.Feature.FeatureParams = None + fr.Feature.LookupListIndex = [lookup_index] + fr.Feature.LookupCount = 1 + gsub.FeatureList.FeatureRecord.append(fr) + gsub.FeatureList.FeatureCount = len(gsub.FeatureList.FeatureRecord) + return gsub.FeatureList.FeatureCount - 1 + + +def register_everywhere(gsub, feature_index): + for sr in gsub.ScriptList.ScriptRecord: + script = sr.Script + systems = [script.DefaultLangSys] if script.DefaultLangSys else [] + systems += [r.LangSys for r in script.LangSysRecord] + for ls in systems: + ls.FeatureIndex.append(feature_index) + ls.FeatureCount = len(ls.FeatureIndex) + + +def sort_features(gsub): + """FeatureList must be sorted by tag; remap LangSys indices after sorting.""" + records = gsub.FeatureList.FeatureRecord + order = sorted(range(len(records)), key=lambda i: records[i].FeatureTag) + remap = {old: new for new, old in enumerate(order)} + gsub.FeatureList.FeatureRecord = [records[i] for i in order] + for sr in gsub.ScriptList.ScriptRecord: + script = sr.Script + systems = [script.DefaultLangSys] if script.DefaultLangSys else [] + systems += [r.LangSys for r in script.LangSysRecord] + for ls in systems: + ls.FeatureIndex = sorted(remap[i] for i in ls.FeatureIndex) + + +def build(src: Path, weight: str, out_dir: Path) -> None: + source = TTFont(src / f"OpenRunde-{weight}.otf") + glyph_set = source.getGlyphSet() + order = list(source.getGlyphOrder()) + metrics = source["hmtx"].metrics + upm = source["head"].unitsPerEm + + charstrings = {} + for name in order: + pen = T2CharStringPen(metrics[name][0], glyph_set) + glyph_set[name].draw(pen) + charstrings[name] = pen.getCharString() + + left, right, radius, top = stem_of_l(glyph_set) + tail_pen = T2CharStringPen(0, glyph_set) # width set below + end_x = draw_tailed_l(tail_pen, left, right, radius, top) + advance = round(end_x + left * 0.55) # tighter right sidebearing than the plain stem + tail_pen = T2CharStringPen(advance, glyph_set) + draw_tailed_l(tail_pen, left, right, radius, top) + charstrings["l.tail"] = tail_pen.getCharString() + order.append("l.tail") + new_metrics = dict(metrics) + new_metrics["l.tail"] = (advance, left) + + family = "AXP Runde" + ps_name = f"AXPRunde-{weight}" + fb = FontBuilder(upm, isTTF=False) + fb.setupGlyphOrder(order) + fb.setupCharacterMap(source.getBestCmap()) + fb.setupCFF( + ps_name, + {"FullName": f"{family} {weight}", "FamilyName": family, "Weight": weight}, + charstrings, + {}, + ) + fb.setupHorizontalMetrics(new_metrics) + hhea = source["hhea"] + fb.setupHorizontalHeader(ascent=hhea.ascent, descent=hhea.descent) + os2 = source["OS/2"] + fb.setupNameTable( + { + "familyName": family, + "styleName": weight, + "uniqueFontIdentifier": f"{ps_name};AXP", + "fullName": f"{family} {weight}", + "psName": ps_name, + "version": "Version 1.001; AXP build from Open Runde 1.001", + "copyright": "Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter). " + "Open Runde by Laurids Kern. AXP Runde adds a tailed l (cv05/ss01).", + "licenseDescription": "This Font Software is licensed under the SIL Open Font License, Version 1.1.", + "licenseInfoURL": "https://scripts.sil.org/OFL", + } + ) + fb.setupOS2( + version=4, + usWeightClass=WEIGHTS[weight], + sTypoAscender=os2.sTypoAscender, + sTypoDescender=os2.sTypoDescender, + sTypoLineGap=os2.sTypoLineGap, + usWinAscent=os2.usWinAscent, + usWinDescent=os2.usWinDescent, + sxHeight=os2.sxHeight, + sCapHeight=os2.sCapHeight, + fsSelection=os2.fsSelection, + fsType=0, + ) + fb.setupPost() + font = fb.font + font["head"].macStyle = 1 if weight == "Bold" else 0 + # Carry the shaping tables over; glyph ids are unchanged because l.tail is appended. + for table in ("GDEF", "GPOS", "GSUB"): + if table in source: + font[table] = source[table] + + gsub = font["GSUB"].table + lookup = ot.Lookup() + lookup.LookupType = 1 + lookup.LookupFlag = 0 + subst = ot.SingleSubst() + subst.mapping = {"l": "l.tail"} + lookup.SubTable = [subst] + lookup.SubTableCount = 1 + gsub.LookupList.Lookup.append(lookup) + gsub.LookupList.LookupCount = len(gsub.LookupList.Lookup) + index = gsub.LookupList.LookupCount - 1 + for tag in ("cv05", "ss01"): + register_everywhere(gsub, add_feature(gsub, tag, index)) + sort_features(gsub) + + out_dir.mkdir(parents=True, exist_ok=True) + font.save(out_dir / f"{ps_name}.otf") + font.flavor = "woff2" + font.save(out_dir / f"{ps_name}.woff2") + print(f"{ps_name}: l stem {left}-{right} r={radius}; l.tail advance {advance} (plain l {metrics['l'][0]})") + + +def main() -> None: + if len(sys.argv) != 3: + sys.exit(__doc__) + src, out = Path(sys.argv[1]), Path(sys.argv[2]) + for weight in WEIGHTS: + build(src, weight, out) + + +if __name__ == "__main__": + main() diff --git a/scripts/ui-notices.mjs b/scripts/ui-notices.mjs index cef32e4..9c26824 100644 --- a/scripts/ui-notices.mjs +++ b/scripts/ui-notices.mjs @@ -10,7 +10,6 @@ const roots = [ "@pierre/trees", "react-markdown", "remark-gfm", - "@fontsource-variable/dm-sans", "@fontsource/ibm-plex-mono", ]; const packages = new Map(); diff --git a/ui/public/licenses/Open-Runde-OFL.txt b/ui/public/licenses/Open-Runde-OFL.txt new file mode 100644 index 0000000..9b2ca37 --- /dev/null +++ b/ui/public/licenses/Open-Runde-OFL.txt @@ -0,0 +1,92 @@ +Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION AND CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/ui/src/DiffPanel.tsx b/ui/src/DiffPanel.tsx index b94eb66..5b4b3c7 100644 --- a/ui/src/DiffPanel.tsx +++ b/ui/src/DiffPanel.tsx @@ -114,7 +114,7 @@ function Patch({ if (path && paths.includes(path)) setSelected(path); }, unsafeCSS: - ":host { --trees-font-family: 'DM Sans Variable', sans-serif; --trees-font-size: 12px; --trees-fg-override: #34433a; --trees-fg-muted-override: #4f5d4c; }", + ":host { --trees-font-family: 'AXP Runde', sans-serif; --trees-font-size: 12px; --trees-fg-override: #34433a; --trees-fg-muted-override: #4f5d4c; }", }); const file = parsed.files.find((file) => file.name === selected) ?? parsed.files[0]; diff --git a/ui/src/fonts/OFL-Open-Runde.txt b/ui/src/fonts/OFL-Open-Runde.txt new file mode 100644 index 0000000..9b2ca37 --- /dev/null +++ b/ui/src/fonts/OFL-Open-Runde.txt @@ -0,0 +1,92 @@ +Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION AND CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/ui/src/fonts/axp-runde.css b/ui/src/fonts/axp-runde.css new file mode 100644 index 0000000..e0d732b --- /dev/null +++ b/ui/src/fonts/axp-runde.css @@ -0,0 +1,31 @@ +/* AXP Runde: Open Runde (a rounded Inter, SIL OFL 1.1) plus one signature glyph, + * a hooked lowercase l behind `cv05`/`ss01`. Built by scripts/fonts/build-axp-runde.py. + * Four static weights; the browser picks the nearest for anything in between. */ +@font-face { + font-family: "AXP Runde"; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url("./axp-runde/AXPRunde-Regular.woff2") format("woff2"); +} +@font-face { + font-family: "AXP Runde"; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url("./axp-runde/AXPRunde-Medium.woff2") format("woff2"); +} +@font-face { + font-family: "AXP Runde"; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url("./axp-runde/AXPRunde-Semibold.woff2") format("woff2"); +} +@font-face { + font-family: "AXP Runde"; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url("./axp-runde/AXPRunde-Bold.woff2") format("woff2"); +} diff --git a/ui/src/fonts/axp-runde/AXPRunde-Bold.woff2 b/ui/src/fonts/axp-runde/AXPRunde-Bold.woff2 new file mode 100644 index 0000000..b67f77e Binary files /dev/null and b/ui/src/fonts/axp-runde/AXPRunde-Bold.woff2 differ diff --git a/ui/src/fonts/axp-runde/AXPRunde-Medium.woff2 b/ui/src/fonts/axp-runde/AXPRunde-Medium.woff2 new file mode 100644 index 0000000..dc4394d Binary files /dev/null and b/ui/src/fonts/axp-runde/AXPRunde-Medium.woff2 differ diff --git a/ui/src/fonts/axp-runde/AXPRunde-Regular.woff2 b/ui/src/fonts/axp-runde/AXPRunde-Regular.woff2 new file mode 100644 index 0000000..f305478 Binary files /dev/null and b/ui/src/fonts/axp-runde/AXPRunde-Regular.woff2 differ diff --git a/ui/src/fonts/axp-runde/AXPRunde-Semibold.woff2 b/ui/src/fonts/axp-runde/AXPRunde-Semibold.woff2 new file mode 100644 index 0000000..a4e7e49 Binary files /dev/null and b/ui/src/fonts/axp-runde/AXPRunde-Semibold.woff2 differ diff --git a/ui/src/fonts/open-runde/OpenRunde-Bold.woff2 b/ui/src/fonts/open-runde/OpenRunde-Bold.woff2 new file mode 100644 index 0000000..dbdabb2 Binary files /dev/null and b/ui/src/fonts/open-runde/OpenRunde-Bold.woff2 differ diff --git a/ui/src/fonts/open-runde/OpenRunde-Medium.woff2 b/ui/src/fonts/open-runde/OpenRunde-Medium.woff2 new file mode 100644 index 0000000..99de77a Binary files /dev/null and b/ui/src/fonts/open-runde/OpenRunde-Medium.woff2 differ diff --git a/ui/src/fonts/open-runde/OpenRunde-Regular.woff2 b/ui/src/fonts/open-runde/OpenRunde-Regular.woff2 new file mode 100644 index 0000000..30447d0 Binary files /dev/null and b/ui/src/fonts/open-runde/OpenRunde-Regular.woff2 differ diff --git a/ui/src/fonts/open-runde/OpenRunde-Semibold.woff2 b/ui/src/fonts/open-runde/OpenRunde-Semibold.woff2 new file mode 100644 index 0000000..ca05cba Binary files /dev/null and b/ui/src/fonts/open-runde/OpenRunde-Semibold.woff2 differ diff --git a/ui/src/main.tsx b/ui/src/main.tsx index 00ea246..18da849 100644 --- a/ui/src/main.tsx +++ b/ui/src/main.tsx @@ -1,7 +1,7 @@ import { StrictMode, Component } from "react"; import type { ReactNode } from "react"; import { createRoot } from "react-dom/client"; -import "@fontsource-variable/dm-sans"; +import "./fonts/axp-runde.css"; import "@fontsource/ibm-plex-mono/latin-400.css"; import "./vendor/huabu/tokens.css"; import "./style.css"; diff --git a/ui/src/style.css b/ui/src/style.css index 8ddbe3e..b2094c0 100644 --- a/ui/src/style.css +++ b/ui/src/style.css @@ -9,13 +9,21 @@ body { margin: 0; color: var(--fg-default); font-family: - "DM Sans Variable", + "AXP Runde", -apple-system, BlinkMacSystemFont, sans-serif; + /* Re-list the always-on features next to cv05, the hooked l that is our signature glyph. */ + font-feature-settings: + "kern" 1, + "liga" 1, + "calt" 1, + "cv05" 1; font-size: 14px; line-height: 1.5; -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; } button, input, @@ -160,7 +168,7 @@ code { font-size: 10px; letter-spacing: 1.3px; color: #596054; - font-weight: 650; + font-weight: 700; margin: 0 13px 11px; } .sidebar nav { @@ -184,9 +192,10 @@ code { .nav-item.active { color: #324a37; background: #e3e9db; - font-weight: 650; + font-weight: 700; } .nav-count { + font-variant-numeric: tabular-nums; margin-left: auto; min-width: 20px; padding: 1px 5px; @@ -216,7 +225,7 @@ code { } .bring-agent h3 { font-size: 15px; - font-weight: 550; + font-weight: 600; line-height: 1.35; letter-spacing: -0.35px; } @@ -385,7 +394,7 @@ h1 { justify-content: center; gap: 8px; font-size: 11px; - font-weight: 550; + font-weight: 600; white-space: nowrap; transition: background 0.15s, @@ -467,7 +476,7 @@ h1 { padding: 0; color: #405735; font-size: 11px; - font-weight: 550; + font-weight: 600; } .text-button:hover { text-decoration: underline; @@ -751,7 +760,7 @@ h1 { } .contribution-card h3 { font-size: 16px; - font-weight: 550; + font-weight: 600; letter-spacing: -0.4px; line-height: 1.35; margin-bottom: 8px; @@ -891,7 +900,7 @@ h1 { } .person strong { font-size: 11px; - font-weight: 550; + font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -933,7 +942,7 @@ h1 { } .online-agent strong { font-size: 10px; - font-weight: 550; + font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -950,7 +959,7 @@ h1 { } .community-note > span { font-size: 24px; - font-weight: 300; + font-weight: 400; } .community-note p { font-size: 11px; @@ -1081,9 +1090,10 @@ h1 { color: var(--fg-subtle); } .person-stats strong { + font-variant-numeric: tabular-nums; color: var(--fg-default); margin-right: 6px; - font-weight: 550; + font-weight: 600; } .fine-print { color: var(--fg-subtle); @@ -1118,7 +1128,7 @@ h1 { } .activity-title strong { color: var(--fg-default); - font-weight: 550; + font-weight: 600; } .activity-title time { float: right; @@ -1131,7 +1141,7 @@ h1 { align-items: center; padding: 5px 0; font-size: 12px; - font-weight: 550; + font-weight: 600; color: #4c653f; text-align: left; } @@ -1265,6 +1275,7 @@ h1 { font-size: 11px; } .message-author time { + font-variant-numeric: tabular-nums; color: #5b6154; font-size: 10px; margin-left: auto; @@ -1670,7 +1681,7 @@ textarea::placeholder { } .comment-form label { font-size: 11px; - font-weight: 550; + font-weight: 600; display: block; margin-bottom: 10px; } @@ -1821,7 +1832,7 @@ dialog::backdrop { } .onboarding-step h3 { font-size: 14px; - font-weight: 550; + font-weight: 600; margin: 2px 0 6px; } .onboarding-step p {