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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
//! Typography — layer 1 of the house font model.
//!
//! Wiki `typography-standard`. The model is three layers: an app override, the
//! house default, then a system generic, and this is the middle one. Two needs,
//! two names, and no others in the suite:
//!
//! ```text
//! --font-mono Quasi Mono -> monospace
//! --font-sans Quasi Body -> sans-serif
//! ```
//!
//! Both are cut by `quasi-type` from the Atkinson Hyperlegible superfamily plus
//! the house glyph set. This crate does not cut them and cannot: quasi-type is
//! `publish = false` and makeover is on crates.io, so the cut lives in each
//! consumer's own build script (`quasi_type::cut`, taken as a git dependency,
//! the way `shop-font` does it). What lives here is the vocabulary, which is
//! the half that was scattered.
//!
//! Font is not a theme's business and none of this is themeable. A theme
//! declares colour by role; nothing in a theme file names a face, and the two
//! tokens below are the same in every theme. That is why they are constants
//! rather than another section of `SemanticTokens`, and why they belong in a
//! stylesheet generated once at build time rather than in the block that gets
//! re-injected on a theme switch.
//!
//! The brand/display tier is out of scope, per product and by decision: Young
//! Serif on MNW, Reglo in GoingsOn, Departure Mono on Alloy, audiofiles' logo
//! face. No renderer emits them and no described screen resolves a token to
//! one, so they keep their own `font-family` until the app-override layer
//! lands and gives them a place to be declared.
use crateFontSlot;
/// The mono slot: code, data, identifiers, cell grids, anything monospaced.
pub const FONT_MONO: &str = "\"Quasi Mono\", monospace";
/// The body / UI slot. Everything that is not the mono slot or brand tier.
pub const FONT_SANS: &str = "\"Quasi Body\", sans-serif";
/// The family name inside [`FONT_MONO`], on its own, for a consumer that needs
/// the name rather than the stack. A test asserts the two agree.
pub const HOUSE_MONO_FAMILY: &str = "Quasi Mono";
/// The family name inside [`FONT_SANS`]. See [`HOUSE_MONO_FAMILY`].
pub const HOUSE_SANS_FAMILY: &str = "Quasi Body";
/// The weight range both house faces carry.
///
/// They are variable, `wght` 200-800, and a declaration that omits the range
/// makes every weight resolve to the file's default instance — which is
/// ExtraLight, because a cut keeps its base's default.
pub const HOUSE_WEIGHT_RANGE: &str = "200 800";
/// Filename a consumer writes the cut mono face to, under its own font URL.
///
/// `quasi-type` writes `QuasiMono[wght].woff2`, naming the variable axis the
/// way a font tool expects. Those brackets have to be percent-encoded to
/// survive a URL and are a bug waiting to be written, so the web copy takes a
/// plain name and the two places that have to agree — the build script that
/// writes the file and the `@font-face` that fetches it — agree through this
/// constant rather than by both spelling it out.
pub const WEBFONT_MONO_FILE: &str = "QuasiMono.woff2";
/// Filename a consumer writes the cut body face to. See [`WEBFONT_MONO_FILE`].
pub const WEBFONT_SANS_FILE: &str = "QuasiBody.woff2";
/// The house font tokens as CSS declarations (no selector), for a caller that
/// is composing its own block.
/// The house font tokens as a `:root { … }` block.
///
/// Inlined by surfaces that cannot link a stylesheet — the MNW embeds are the
/// live case — and written to a file by everything else, through
/// `makeover_build::typography_css`.
/// The `@font-face` rules for both slots, fetching from `base_url`.
///
/// `base_url` is the directory the consumer serves its fonts from, without a
/// trailing slash: `/static/fonts` on the MNW server, `fonts` for a Tauri
/// frontend loading relative to its index.
///
/// # `font-weight: 200 800`, which is the part that bites
///
/// Both faces are variable over `wght` 200-800 in one file, and the mono
/// face's **default instance is ExtraLight** — that is upstream Atkinson's
/// default and the cut keeps the axis rather than pinning a master, so a
/// consumer that loads the file and takes what it opens at draws its whole UI
/// at 200. Declaring the range here is what makes the browser resolve `normal`
/// to 400 and `bold` to 700 instead. shop hit the same trap from the other
/// side and names `wght` 400 explicitly in its shaper; this is the web's
/// version of that fix, stated once for every consumer.
///
/// `font-display: swap` on both: the faces are 31KB and 50KB, they are cached
/// hard after the first paint, and a flash of the fallback beats invisible
/// text either way.