@motir/design-system 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/theme/fontSets.d.ts +222 -0
- package/dist/theme/fontSets.js +155 -0
- package/dist/theme/fontSets.js.map +1 -0
- package/package.json +2 -2
- package/theme.css +200 -27
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { DEFAULT_STYLE_ID, STYLE_DEFAULT_TYPE, STYLE_DIMENSIONS, STYLE_IDS, STYLE_REGISTRY, StyleDefinition, StyleDimensionKey, StyleId, defaultTypeForStyle, isStyleId, resolveStyle } from './theme/styles.js';
|
|
2
2
|
export { BASE_PALETTE_ID, DEFAULT_PALETTE_ID, DEFAULT_PROJECT_PALETTE_ID, PALETTE_IDS, PALETTE_REGISTRY, PaletteDefinition, PaletteId, isPaletteId, resolvePalette } from './theme/palettes.js';
|
|
3
3
|
export { DEFAULT_TYPE_ID, TYPE_IDS, TYPE_REGISTRY, TypeId, TypographyDefinition, isTypeId, resolveType } from './theme/typography.js';
|
|
4
|
+
export { DEFAULT_FONT_SET_ID, FONT_SET_IDS, FONT_SET_LOCALES, FONT_SET_REGISTRY, FONT_SET_ROLES, FontSet, FontSetId, FontSetLocale, FontSetMember, FontSetMemberSource, FontSetRole, FontSetRoleMembers, LOCALE_FONT_SET, fontSetMemberVar, isFontSetLocale, resolveFontSet, resolveFontSetMember } from './theme/fontSets.js';
|
|
4
5
|
export { PALETTE_IDS_VERSION, PALETTE_ID_MIGRATION, ResolvedThemePattern, THEME_DEFAULTS, THEME_STORAGE_KEYS, ThemePattern, isThemePattern, migrateStoredPaletteId, resolvePattern } from './theme/types.js';
|
|
5
6
|
export { buildThemeInitScript, themeInitScript } from './theme/init-script.js';
|
|
6
7
|
export { AppearanceAxesInput, LocalAppearanceSnapshot, resolveAppliedAppearance, resolveAxesToApplied } from './theme/appearance-resolution.js';
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
export * from './theme/styles.js';
|
|
21
21
|
export * from './theme/palettes.js';
|
|
22
22
|
export * from './theme/typography.js';
|
|
23
|
+
export * from './theme/fontSets.js';
|
|
23
24
|
|
|
24
25
|
// ── Pattern axis + storage/defaults (owned by ./theme/types) ─────────────────
|
|
25
26
|
export { THEME_STORAGE_KEYS, THEME_DEFAULTS, isThemePattern, resolvePattern } from './theme/types.js';
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Motir FONT-SET REGISTRY: which faces each locale's script is drawn in
|
|
3
|
+
* (Story MOTIR-7733 · Subtask MOTIR-7843).
|
|
4
|
+
*
|
|
5
|
+
* A Type pairing (`./typography.ts`) decides the LATIN faces of the three roles
|
|
6
|
+
* (sans / serif / mono). A font set decides the SCRIPT face that sits BEHIND
|
|
7
|
+
* them, so a ja page draws its Latin in the pairing's face and its kana and
|
|
8
|
+
* kanji in the ja set's face, under every pairing. The two axes are
|
|
9
|
+
* independent: a set never changes which pairing is active, and a pairing never
|
|
10
|
+
* changes which set a locale uses.
|
|
11
|
+
*
|
|
12
|
+
* ── Where the rows come from ────────────────────────────────────────────
|
|
13
|
+
* Every set, member and default below is taken from
|
|
14
|
+
* `docs/typography/font-sets.md` (MOTIR-7841), which read dooooWeb's mapping at
|
|
15
|
+
* `225af77` and filled the roles dooooWeb leaves empty from cited references.
|
|
16
|
+
* `inDooooWeb` carries that document's `in dooooWeb` / `added (not in dooooWeb)`
|
|
17
|
+
* tag. Change a row here only together with that document; the package test
|
|
18
|
+
* holds the two equal.
|
|
19
|
+
*
|
|
20
|
+
* ── What this module is NOT ─────────────────────────────────────────────
|
|
21
|
+
* Data and pure resolvers only. It names CSS custom properties (the contract
|
|
22
|
+
* between the app's `next/font` loader and `theme.css`) but sets none, loads no
|
|
23
|
+
* face, and writes no CSS. Loading stays in the consuming app.
|
|
24
|
+
*/
|
|
25
|
+
/** The eleven locales app.motir.co ships in. A closed product decision. */
|
|
26
|
+
declare const FONT_SET_LOCALES: readonly ["en", "zh", "ja", "ko", "de", "fr", "es", "it", "nl", "pl", "pt"];
|
|
27
|
+
/** A locale the font-set registry knows. */
|
|
28
|
+
type FontSetLocale = (typeof FONT_SET_LOCALES)[number];
|
|
29
|
+
/** The three type roles every set covers: the same roles `--font-*` carries. */
|
|
30
|
+
declare const FONT_SET_ROLES: readonly ["sans", "serif", "mono"];
|
|
31
|
+
/** One of the three type roles. */
|
|
32
|
+
type FontSetRole = (typeof FONT_SET_ROLES)[number];
|
|
33
|
+
/**
|
|
34
|
+
* Where a member's face comes from.
|
|
35
|
+
*
|
|
36
|
+
* - `type-pairing`: the active Type pairing's own face for the role. The Latin
|
|
37
|
+
* set's only member; it loads nothing and has no CSS variable.
|
|
38
|
+
* - `next/font/google`: a Google Fonts face the app loads with `next/font`;
|
|
39
|
+
* `googleFamily` is the loader's export name (`Noto_Sans_JP`).
|
|
40
|
+
*/
|
|
41
|
+
type FontSetMemberSource = {
|
|
42
|
+
readonly kind: 'type-pairing';
|
|
43
|
+
} | {
|
|
44
|
+
readonly kind: 'next/font/google';
|
|
45
|
+
readonly googleFamily: string;
|
|
46
|
+
};
|
|
47
|
+
/** One selectable face in one role of one set. */
|
|
48
|
+
interface FontSetMember {
|
|
49
|
+
/** Stable kebab-case slug, unique within its role. The value a pick stores. */
|
|
50
|
+
readonly id: string;
|
|
51
|
+
/** The display name, or `null` for the `type-pairing` member. */
|
|
52
|
+
readonly family: string | null;
|
|
53
|
+
readonly source: FontSetMemberSource;
|
|
54
|
+
/** `docs/typography/font-sets.md`'s tag: `in dooooWeb` (true) or `added` (false). */
|
|
55
|
+
readonly inDooooWeb: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* The role whose member of the SAME id this member re-uses the face of. A
|
|
58
|
+
* CJK set's mono role draws its Han glyphs from the set's sans face (the Noto
|
|
59
|
+
* Sans Mono CJK practice, see the doc), so the face is loaded once and both
|
|
60
|
+
* roles read one variable.
|
|
61
|
+
*/
|
|
62
|
+
readonly sameFaceAs?: FontSetRole;
|
|
63
|
+
}
|
|
64
|
+
/** One role of one set: its members, and which one a page uses by default. */
|
|
65
|
+
interface FontSetRoleMembers {
|
|
66
|
+
/** The id of the member used when nobody has picked one. */
|
|
67
|
+
readonly default: string;
|
|
68
|
+
readonly members: readonly FontSetMember[];
|
|
69
|
+
}
|
|
70
|
+
/** A locale group's font set. */
|
|
71
|
+
interface FontSet {
|
|
72
|
+
readonly id: string;
|
|
73
|
+
/** Display name for `/tokens` and the picker. */
|
|
74
|
+
readonly label: string;
|
|
75
|
+
/**
|
|
76
|
+
* The BCP 47 tag the set's `:lang()` rule matches. It is the tag the app
|
|
77
|
+
* renders on `<html lang>` (`zh`, not `zh-Hans`), because `:lang(zh)`
|
|
78
|
+
* matches `zh` and `zh-Hans` while `:lang(zh-Hans)` does not match `zh`.
|
|
79
|
+
* `null` for the Latin set, which is matched by its locales instead.
|
|
80
|
+
*/
|
|
81
|
+
readonly lang: string | null;
|
|
82
|
+
/** True for a CJK set: its faces are unicode-range sliced and never preloaded. */
|
|
83
|
+
readonly cjk: boolean;
|
|
84
|
+
readonly roles: Readonly<Record<FontSetRole, FontSetRoleMembers>>;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The registry, one entry per set. Ids are the doc's registry ids; dooooWeb's
|
|
88
|
+
* own group names are noted beside each.
|
|
89
|
+
*/
|
|
90
|
+
declare const FONT_SET_REGISTRY: {
|
|
91
|
+
readonly latin: {
|
|
92
|
+
readonly id: "latin";
|
|
93
|
+
readonly label: "Latin";
|
|
94
|
+
readonly lang: null;
|
|
95
|
+
readonly cjk: false;
|
|
96
|
+
readonly roles: {
|
|
97
|
+
readonly sans: {
|
|
98
|
+
readonly default: "type-pairing";
|
|
99
|
+
readonly members: readonly [FontSetMember];
|
|
100
|
+
};
|
|
101
|
+
readonly serif: {
|
|
102
|
+
readonly default: "type-pairing";
|
|
103
|
+
readonly members: readonly [FontSetMember];
|
|
104
|
+
};
|
|
105
|
+
readonly mono: {
|
|
106
|
+
readonly default: "type-pairing";
|
|
107
|
+
readonly members: readonly [FontSetMember];
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
readonly 'zh-Hans': {
|
|
112
|
+
readonly id: "zh-Hans";
|
|
113
|
+
readonly label: "Simplified Chinese";
|
|
114
|
+
readonly lang: "zh";
|
|
115
|
+
readonly cjk: true;
|
|
116
|
+
readonly roles: {
|
|
117
|
+
readonly sans: {
|
|
118
|
+
readonly default: "noto-sans-sc";
|
|
119
|
+
readonly members: readonly [FontSetMember];
|
|
120
|
+
};
|
|
121
|
+
readonly serif: {
|
|
122
|
+
readonly default: "noto-serif-sc";
|
|
123
|
+
readonly members: readonly [FontSetMember, FontSetMember];
|
|
124
|
+
};
|
|
125
|
+
readonly mono: {
|
|
126
|
+
readonly default: "noto-sans-sc";
|
|
127
|
+
readonly members: readonly [FontSetMember];
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
readonly ja: {
|
|
132
|
+
readonly id: "ja";
|
|
133
|
+
readonly label: "Japanese";
|
|
134
|
+
readonly lang: "ja";
|
|
135
|
+
readonly cjk: true;
|
|
136
|
+
readonly roles: {
|
|
137
|
+
readonly sans: {
|
|
138
|
+
readonly default: "noto-sans-jp";
|
|
139
|
+
readonly members: readonly [FontSetMember, FontSetMember];
|
|
140
|
+
};
|
|
141
|
+
readonly serif: {
|
|
142
|
+
readonly default: "noto-serif-jp";
|
|
143
|
+
readonly members: readonly [FontSetMember];
|
|
144
|
+
};
|
|
145
|
+
readonly mono: {
|
|
146
|
+
readonly default: "noto-sans-jp";
|
|
147
|
+
readonly members: readonly [FontSetMember];
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
readonly ko: {
|
|
152
|
+
readonly id: "ko";
|
|
153
|
+
readonly label: "Korean";
|
|
154
|
+
readonly lang: "ko";
|
|
155
|
+
readonly cjk: true;
|
|
156
|
+
readonly roles: {
|
|
157
|
+
readonly sans: {
|
|
158
|
+
readonly default: "noto-sans-kr";
|
|
159
|
+
readonly members: readonly [FontSetMember, FontSetMember];
|
|
160
|
+
};
|
|
161
|
+
readonly serif: {
|
|
162
|
+
readonly default: "noto-serif-kr";
|
|
163
|
+
readonly members: readonly [FontSetMember];
|
|
164
|
+
};
|
|
165
|
+
readonly mono: {
|
|
166
|
+
readonly default: "noto-sans-kr";
|
|
167
|
+
readonly members: readonly [FontSetMember];
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
/** The id of every registered set. */
|
|
173
|
+
type FontSetId = keyof typeof FONT_SET_REGISTRY;
|
|
174
|
+
/** All set ids, in registry order. */
|
|
175
|
+
declare const FONT_SET_IDS: FontSetId[];
|
|
176
|
+
/** The set an unknown or empty page language falls back to: the app renders it in English. */
|
|
177
|
+
declare const DEFAULT_FONT_SET_ID: FontSetId;
|
|
178
|
+
/** Which set each locale is drawn in (font-sets.md § Locale → set). */
|
|
179
|
+
declare const LOCALE_FONT_SET: {
|
|
180
|
+
readonly en: "latin";
|
|
181
|
+
readonly zh: "zh-Hans";
|
|
182
|
+
readonly ja: "ja";
|
|
183
|
+
readonly ko: "ko";
|
|
184
|
+
readonly de: "latin";
|
|
185
|
+
readonly fr: "latin";
|
|
186
|
+
readonly es: "latin";
|
|
187
|
+
readonly it: "latin";
|
|
188
|
+
readonly nl: "latin";
|
|
189
|
+
readonly pl: "latin";
|
|
190
|
+
readonly pt: "latin";
|
|
191
|
+
};
|
|
192
|
+
/** Narrowing guard: is an arbitrary value one of the eleven locales? */
|
|
193
|
+
declare function isFontSetLocale(value: unknown): value is FontSetLocale;
|
|
194
|
+
/**
|
|
195
|
+
* The set for an `<html lang>` value. Matches the primary subtag
|
|
196
|
+
* case-insensitively (`ja-JP` → ja, `PT-br` → pt, `zh-Hans` → zh).
|
|
197
|
+
*
|
|
198
|
+
* The fallback is explicit: an empty or unknown tag returns the Latin set,
|
|
199
|
+
* because the app renders an unknown language in English. It never falls back
|
|
200
|
+
* to "the nearest CJK set". Use `isFontSetLocale` on the primary subtag to tell
|
|
201
|
+
* a fallback from a match.
|
|
202
|
+
*/
|
|
203
|
+
declare function resolveFontSet(lang: string): FontSet;
|
|
204
|
+
/**
|
|
205
|
+
* A member of one role of one set, by id. A missing or unknown id (a stored
|
|
206
|
+
* pick naming a member that has since been removed) returns that role's
|
|
207
|
+
* default, never another set's face.
|
|
208
|
+
*/
|
|
209
|
+
declare function resolveFontSetMember(setId: FontSetId, role: FontSetRole, memberId?: string): FontSetMember;
|
|
210
|
+
/**
|
|
211
|
+
* The CSS custom property the app's `next/font` loader assigns to a member's
|
|
212
|
+
* face and `theme.css` reads: `--font-set-<set>-<role>-<member>`. This name is
|
|
213
|
+
* the contract between the registry, the loader and the stylesheet, so it is
|
|
214
|
+
* built here and nowhere else.
|
|
215
|
+
*
|
|
216
|
+
* A member that re-uses another role's face (`sameFaceAs`) returns THAT role's
|
|
217
|
+
* variable, so the face is declared once. The `type-pairing` member, and an id
|
|
218
|
+
* the role does not have, return `null`.
|
|
219
|
+
*/
|
|
220
|
+
declare function fontSetMemberVar(setId: FontSetId, role: FontSetRole, memberId: string): string | null;
|
|
221
|
+
|
|
222
|
+
export { DEFAULT_FONT_SET_ID, FONT_SET_IDS, FONT_SET_LOCALES, FONT_SET_REGISTRY, FONT_SET_ROLES, type FontSet, type FontSetId, type FontSetLocale, type FontSetMember, type FontSetMemberSource, type FontSetRole, type FontSetRoleMembers, LOCALE_FONT_SET, fontSetMemberVar, isFontSetLocale, resolveFontSet, resolveFontSetMember };
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// src/theme/fontSets.ts
|
|
2
|
+
var FONT_SET_LOCALES = [
|
|
3
|
+
"en",
|
|
4
|
+
"zh",
|
|
5
|
+
"ja",
|
|
6
|
+
"ko",
|
|
7
|
+
"de",
|
|
8
|
+
"fr",
|
|
9
|
+
"es",
|
|
10
|
+
"it",
|
|
11
|
+
"nl",
|
|
12
|
+
"pl",
|
|
13
|
+
"pt"
|
|
14
|
+
];
|
|
15
|
+
var FONT_SET_ROLES = ["sans", "serif", "mono"];
|
|
16
|
+
var TYPE_PAIRING = {
|
|
17
|
+
id: "type-pairing",
|
|
18
|
+
family: null,
|
|
19
|
+
source: { kind: "type-pairing" },
|
|
20
|
+
inDooooWeb: false
|
|
21
|
+
};
|
|
22
|
+
function google(id, family, inDooooWeb, sameFaceAs) {
|
|
23
|
+
return {
|
|
24
|
+
id,
|
|
25
|
+
family,
|
|
26
|
+
source: { kind: "next/font/google", googleFamily: family.replace(/ /g, "_") },
|
|
27
|
+
inDooooWeb,
|
|
28
|
+
...sameFaceAs ? { sameFaceAs } : {}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
var FONT_SET_REGISTRY = {
|
|
32
|
+
// dooooWeb: `latin`. Its Latin picker faces are deliberately not members:
|
|
33
|
+
// behind the pairing's face they would never draw a glyph (font-sets.md § latin).
|
|
34
|
+
latin: {
|
|
35
|
+
id: "latin",
|
|
36
|
+
label: "Latin",
|
|
37
|
+
lang: null,
|
|
38
|
+
cjk: false,
|
|
39
|
+
roles: {
|
|
40
|
+
sans: { default: "type-pairing", members: [TYPE_PAIRING] },
|
|
41
|
+
serif: { default: "type-pairing", members: [TYPE_PAIRING] },
|
|
42
|
+
mono: { default: "type-pairing", members: [TYPE_PAIRING] }
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
// dooooWeb: `chinese-simplified`.
|
|
46
|
+
"zh-Hans": {
|
|
47
|
+
id: "zh-Hans",
|
|
48
|
+
label: "Simplified Chinese",
|
|
49
|
+
lang: "zh",
|
|
50
|
+
cjk: true,
|
|
51
|
+
roles: {
|
|
52
|
+
sans: { default: "noto-sans-sc", members: [google("noto-sans-sc", "Noto Sans SC", false)] },
|
|
53
|
+
serif: {
|
|
54
|
+
default: "noto-serif-sc",
|
|
55
|
+
members: [
|
|
56
|
+
google("noto-serif-sc", "Noto Serif SC", true),
|
|
57
|
+
// Draws Traditional / inherited forms, so never the default (font-sets.md § Region forms).
|
|
58
|
+
google("lxgw-wenkai-tc", "LXGW WenKai TC", true)
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
mono: {
|
|
62
|
+
default: "noto-sans-sc",
|
|
63
|
+
members: [google("noto-sans-sc", "Noto Sans SC", false, "sans")]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
// dooooWeb: `japanese`.
|
|
68
|
+
ja: {
|
|
69
|
+
id: "ja",
|
|
70
|
+
label: "Japanese",
|
|
71
|
+
lang: "ja",
|
|
72
|
+
cjk: true,
|
|
73
|
+
roles: {
|
|
74
|
+
sans: {
|
|
75
|
+
default: "noto-sans-jp",
|
|
76
|
+
members: [
|
|
77
|
+
google("noto-sans-jp", "Noto Sans JP", true),
|
|
78
|
+
google("m-plus-rounded-1c", "M PLUS Rounded 1c", true)
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
serif: {
|
|
82
|
+
default: "noto-serif-jp",
|
|
83
|
+
members: [google("noto-serif-jp", "Noto Serif JP", false)]
|
|
84
|
+
},
|
|
85
|
+
mono: {
|
|
86
|
+
default: "noto-sans-jp",
|
|
87
|
+
members: [google("noto-sans-jp", "Noto Sans JP", false, "sans")]
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
// dooooWeb: `korean`.
|
|
92
|
+
ko: {
|
|
93
|
+
id: "ko",
|
|
94
|
+
label: "Korean",
|
|
95
|
+
lang: "ko",
|
|
96
|
+
cjk: true,
|
|
97
|
+
roles: {
|
|
98
|
+
sans: {
|
|
99
|
+
default: "noto-sans-kr",
|
|
100
|
+
members: [
|
|
101
|
+
google("noto-sans-kr", "Noto Sans KR", true),
|
|
102
|
+
google("nanum-gothic", "Nanum Gothic", true)
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
serif: {
|
|
106
|
+
default: "noto-serif-kr",
|
|
107
|
+
members: [google("noto-serif-kr", "Noto Serif KR", false)]
|
|
108
|
+
},
|
|
109
|
+
mono: {
|
|
110
|
+
default: "noto-sans-kr",
|
|
111
|
+
members: [google("noto-sans-kr", "Noto Sans KR", false, "sans")]
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
var FONT_SET_IDS = Object.keys(FONT_SET_REGISTRY);
|
|
117
|
+
var DEFAULT_FONT_SET_ID = "latin";
|
|
118
|
+
var LOCALE_FONT_SET = {
|
|
119
|
+
en: "latin",
|
|
120
|
+
zh: "zh-Hans",
|
|
121
|
+
ja: "ja",
|
|
122
|
+
ko: "ko",
|
|
123
|
+
de: "latin",
|
|
124
|
+
fr: "latin",
|
|
125
|
+
es: "latin",
|
|
126
|
+
it: "latin",
|
|
127
|
+
nl: "latin",
|
|
128
|
+
pl: "latin",
|
|
129
|
+
pt: "latin"
|
|
130
|
+
};
|
|
131
|
+
function isFontSetLocale(value) {
|
|
132
|
+
return typeof value === "string" && FONT_SET_LOCALES.includes(value);
|
|
133
|
+
}
|
|
134
|
+
function resolveFontSet(lang) {
|
|
135
|
+
const primary = lang.trim().split(/[-_]/)[0]?.toLowerCase() ?? "";
|
|
136
|
+
const id = isFontSetLocale(primary) ? LOCALE_FONT_SET[primary] : DEFAULT_FONT_SET_ID;
|
|
137
|
+
return FONT_SET_REGISTRY[id];
|
|
138
|
+
}
|
|
139
|
+
function resolveFontSetMember(setId, role, memberId) {
|
|
140
|
+
const r = FONT_SET_REGISTRY[setId].roles[role];
|
|
141
|
+
const named = memberId === void 0 ? void 0 : r.members.find((m) => m.id === memberId);
|
|
142
|
+
return named ?? r.members.find((m) => m.id === r.default);
|
|
143
|
+
}
|
|
144
|
+
function fontSetMemberVar(setId, role, memberId) {
|
|
145
|
+
const member = FONT_SET_REGISTRY[setId].roles[role].members.find(
|
|
146
|
+
(m) => m.id === memberId
|
|
147
|
+
);
|
|
148
|
+
if (!member || member.source.kind === "type-pairing") return null;
|
|
149
|
+
const faceRole = member.sameFaceAs ?? role;
|
|
150
|
+
return `--font-set-${setId}-${faceRole}-${member.id}`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export { DEFAULT_FONT_SET_ID, FONT_SET_IDS, FONT_SET_LOCALES, FONT_SET_REGISTRY, FONT_SET_ROLES, LOCALE_FONT_SET, fontSetMemberVar, isFontSetLocale, resolveFontSet, resolveFontSetMember };
|
|
154
|
+
//# sourceMappingURL=fontSets.js.map
|
|
155
|
+
//# sourceMappingURL=fontSets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/theme/fontSets.ts"],"names":[],"mappings":"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","file":"fontSets.js","sourcesContent":["/**\n * The Motir FONT-SET REGISTRY: which faces each locale's script is drawn in\n * (Story MOTIR-7733 · Subtask MOTIR-7843).\n *\n * A Type pairing (`./typography.ts`) decides the LATIN faces of the three roles\n * (sans / serif / mono). A font set decides the SCRIPT face that sits BEHIND\n * them, so a ja page draws its Latin in the pairing's face and its kana and\n * kanji in the ja set's face, under every pairing. The two axes are\n * independent: a set never changes which pairing is active, and a pairing never\n * changes which set a locale uses.\n *\n * ── Where the rows come from ────────────────────────────────────────────\n * Every set, member and default below is taken from\n * `docs/typography/font-sets.md` (MOTIR-7841), which read dooooWeb's mapping at\n * `225af77` and filled the roles dooooWeb leaves empty from cited references.\n * `inDooooWeb` carries that document's `in dooooWeb` / `added (not in dooooWeb)`\n * tag. Change a row here only together with that document; the package test\n * holds the two equal.\n *\n * ── What this module is NOT ─────────────────────────────────────────────\n * Data and pure resolvers only. It names CSS custom properties (the contract\n * between the app's `next/font` loader and `theme.css`) but sets none, loads no\n * face, and writes no CSS. Loading stays in the consuming app.\n */\n\n/** The eleven locales app.motir.co ships in. A closed product decision. */\nexport const FONT_SET_LOCALES = [\n 'en',\n 'zh',\n 'ja',\n 'ko',\n 'de',\n 'fr',\n 'es',\n 'it',\n 'nl',\n 'pl',\n 'pt',\n] as const;\n\n/** A locale the font-set registry knows. */\nexport type FontSetLocale = (typeof FONT_SET_LOCALES)[number];\n\n/** The three type roles every set covers: the same roles `--font-*` carries. */\nexport const FONT_SET_ROLES = ['sans', 'serif', 'mono'] as const;\n\n/** One of the three type roles. */\nexport type FontSetRole = (typeof FONT_SET_ROLES)[number];\n\n/**\n * Where a member's face comes from.\n *\n * - `type-pairing`: the active Type pairing's own face for the role. The Latin\n * set's only member; it loads nothing and has no CSS variable.\n * - `next/font/google`: a Google Fonts face the app loads with `next/font`;\n * `googleFamily` is the loader's export name (`Noto_Sans_JP`).\n */\nexport type FontSetMemberSource =\n | { readonly kind: 'type-pairing' }\n | { readonly kind: 'next/font/google'; readonly googleFamily: string };\n\n/** One selectable face in one role of one set. */\nexport interface FontSetMember {\n /** Stable kebab-case slug, unique within its role. The value a pick stores. */\n readonly id: string;\n /** The display name, or `null` for the `type-pairing` member. */\n readonly family: string | null;\n readonly source: FontSetMemberSource;\n /** `docs/typography/font-sets.md`'s tag: `in dooooWeb` (true) or `added` (false). */\n readonly inDooooWeb: boolean;\n /**\n * The role whose member of the SAME id this member re-uses the face of. A\n * CJK set's mono role draws its Han glyphs from the set's sans face (the Noto\n * Sans Mono CJK practice, see the doc), so the face is loaded once and both\n * roles read one variable.\n */\n readonly sameFaceAs?: FontSetRole;\n}\n\n/** One role of one set: its members, and which one a page uses by default. */\nexport interface FontSetRoleMembers {\n /** The id of the member used when nobody has picked one. */\n readonly default: string;\n readonly members: readonly FontSetMember[];\n}\n\n/** A locale group's font set. */\nexport interface FontSet {\n readonly id: string;\n /** Display name for `/tokens` and the picker. */\n readonly label: string;\n /**\n * The BCP 47 tag the set's `:lang()` rule matches. It is the tag the app\n * renders on `<html lang>` (`zh`, not `zh-Hans`), because `:lang(zh)`\n * matches `zh` and `zh-Hans` while `:lang(zh-Hans)` does not match `zh`.\n * `null` for the Latin set, which is matched by its locales instead.\n */\n readonly lang: string | null;\n /** True for a CJK set: its faces are unicode-range sliced and never preloaded. */\n readonly cjk: boolean;\n readonly roles: Readonly<Record<FontSetRole, FontSetRoleMembers>>;\n}\n\nconst TYPE_PAIRING: FontSetMember = {\n id: 'type-pairing',\n family: null,\n source: { kind: 'type-pairing' },\n inDooooWeb: false,\n};\n\nfunction google(\n id: string,\n family: string,\n inDooooWeb: boolean,\n sameFaceAs?: FontSetRole,\n): FontSetMember {\n return {\n id,\n family,\n source: { kind: 'next/font/google', googleFamily: family.replace(/ /g, '_') },\n inDooooWeb,\n ...(sameFaceAs ? { sameFaceAs } : {}),\n };\n}\n\n/**\n * The registry, one entry per set. Ids are the doc's registry ids; dooooWeb's\n * own group names are noted beside each.\n */\nexport const FONT_SET_REGISTRY = {\n // dooooWeb: `latin`. Its Latin picker faces are deliberately not members:\n // behind the pairing's face they would never draw a glyph (font-sets.md § latin).\n latin: {\n id: 'latin',\n label: 'Latin',\n lang: null,\n cjk: false,\n roles: {\n sans: { default: 'type-pairing', members: [TYPE_PAIRING] },\n serif: { default: 'type-pairing', members: [TYPE_PAIRING] },\n mono: { default: 'type-pairing', members: [TYPE_PAIRING] },\n },\n },\n // dooooWeb: `chinese-simplified`.\n 'zh-Hans': {\n id: 'zh-Hans',\n label: 'Simplified Chinese',\n lang: 'zh',\n cjk: true,\n roles: {\n sans: { default: 'noto-sans-sc', members: [google('noto-sans-sc', 'Noto Sans SC', false)] },\n serif: {\n default: 'noto-serif-sc',\n members: [\n google('noto-serif-sc', 'Noto Serif SC', true),\n // Draws Traditional / inherited forms, so never the default (font-sets.md § Region forms).\n google('lxgw-wenkai-tc', 'LXGW WenKai TC', true),\n ],\n },\n mono: {\n default: 'noto-sans-sc',\n members: [google('noto-sans-sc', 'Noto Sans SC', false, 'sans')],\n },\n },\n },\n // dooooWeb: `japanese`.\n ja: {\n id: 'ja',\n label: 'Japanese',\n lang: 'ja',\n cjk: true,\n roles: {\n sans: {\n default: 'noto-sans-jp',\n members: [\n google('noto-sans-jp', 'Noto Sans JP', true),\n google('m-plus-rounded-1c', 'M PLUS Rounded 1c', true),\n ],\n },\n serif: {\n default: 'noto-serif-jp',\n members: [google('noto-serif-jp', 'Noto Serif JP', false)],\n },\n mono: {\n default: 'noto-sans-jp',\n members: [google('noto-sans-jp', 'Noto Sans JP', false, 'sans')],\n },\n },\n },\n // dooooWeb: `korean`.\n ko: {\n id: 'ko',\n label: 'Korean',\n lang: 'ko',\n cjk: true,\n roles: {\n sans: {\n default: 'noto-sans-kr',\n members: [\n google('noto-sans-kr', 'Noto Sans KR', true),\n google('nanum-gothic', 'Nanum Gothic', true),\n ],\n },\n serif: {\n default: 'noto-serif-kr',\n members: [google('noto-serif-kr', 'Noto Serif KR', false)],\n },\n mono: {\n default: 'noto-sans-kr',\n members: [google('noto-sans-kr', 'Noto Sans KR', false, 'sans')],\n },\n },\n },\n} as const satisfies Record<string, FontSet>;\n\n/** The id of every registered set. */\nexport type FontSetId = keyof typeof FONT_SET_REGISTRY;\n\n/** All set ids, in registry order. */\nexport const FONT_SET_IDS = Object.keys(FONT_SET_REGISTRY) as FontSetId[];\n\n/** The set an unknown or empty page language falls back to: the app renders it in English. */\nexport const DEFAULT_FONT_SET_ID: FontSetId = 'latin';\n\n/** Which set each locale is drawn in (font-sets.md § Locale → set). */\nexport const LOCALE_FONT_SET = {\n en: 'latin',\n zh: 'zh-Hans',\n ja: 'ja',\n ko: 'ko',\n de: 'latin',\n fr: 'latin',\n es: 'latin',\n it: 'latin',\n nl: 'latin',\n pl: 'latin',\n pt: 'latin',\n} as const satisfies Record<FontSetLocale, FontSetId>;\n\n/** Narrowing guard: is an arbitrary value one of the eleven locales? */\nexport function isFontSetLocale(value: unknown): value is FontSetLocale {\n return typeof value === 'string' && (FONT_SET_LOCALES as readonly string[]).includes(value);\n}\n\n/**\n * The set for an `<html lang>` value. Matches the primary subtag\n * case-insensitively (`ja-JP` → ja, `PT-br` → pt, `zh-Hans` → zh).\n *\n * The fallback is explicit: an empty or unknown tag returns the Latin set,\n * because the app renders an unknown language in English. It never falls back\n * to \"the nearest CJK set\". Use `isFontSetLocale` on the primary subtag to tell\n * a fallback from a match.\n */\nexport function resolveFontSet(lang: string): FontSet {\n const primary = lang.trim().split(/[-_]/)[0]?.toLowerCase() ?? '';\n const id = isFontSetLocale(primary) ? LOCALE_FONT_SET[primary] : DEFAULT_FONT_SET_ID;\n return FONT_SET_REGISTRY[id];\n}\n\n/**\n * A member of one role of one set, by id. A missing or unknown id (a stored\n * pick naming a member that has since been removed) returns that role's\n * default, never another set's face.\n */\nexport function resolveFontSetMember(\n setId: FontSetId,\n role: FontSetRole,\n memberId?: string,\n): FontSetMember {\n const r: FontSetRoleMembers = FONT_SET_REGISTRY[setId].roles[role];\n const named = memberId === undefined ? undefined : r.members.find((m) => m.id === memberId);\n return named ?? r.members.find((m) => m.id === r.default)!;\n}\n\n/**\n * The CSS custom property the app's `next/font` loader assigns to a member's\n * face and `theme.css` reads: `--font-set-<set>-<role>-<member>`. This name is\n * the contract between the registry, the loader and the stylesheet, so it is\n * built here and nowhere else.\n *\n * A member that re-uses another role's face (`sameFaceAs`) returns THAT role's\n * variable, so the face is declared once. The `type-pairing` member, and an id\n * the role does not have, return `null`.\n */\nexport function fontSetMemberVar(\n setId: FontSetId,\n role: FontSetRole,\n memberId: string,\n): string | null {\n const member = FONT_SET_REGISTRY[setId].roles[role].members.find(\n (m: FontSetMember) => m.id === memberId,\n );\n if (!member || member.source.kind === 'type-pairing') return null;\n const faceRole = member.sameFaceAs ?? role;\n return `--font-set-${setId}-${faceRole}-${member.id}`;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@motir/design-system",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Motir's 3-axis design system (colour · style · type): distributable token CSS, the axis registries, the theme-apply API, and framework-agnostic React primitives. Shared by motir-core and planner-scaffolded products.",
|
|
5
5
|
"license": "GPL-3.0-only",
|
|
6
6
|
"repository": {
|
|
@@ -66,6 +66,6 @@
|
|
|
66
66
|
"happy-dom": "^20.9.0",
|
|
67
67
|
"tsup": "^8.5.0",
|
|
68
68
|
"typescript": "^5",
|
|
69
|
-
"vitest": "^4.1.
|
|
69
|
+
"vitest": "^4.1.11"
|
|
70
70
|
}
|
|
71
71
|
}
|
package/theme.css
CHANGED
|
@@ -135,11 +135,20 @@
|
|
|
135
135
|
--color-link-pressed: #005bab;
|
|
136
136
|
|
|
137
137
|
/* ── Typography ─────────────────────────────────────────────────── */
|
|
138
|
-
/*
|
|
138
|
+
/* The three role tokens, composed from a pairing chain, a script face and a
|
|
139
|
+
generic tail (MOTIR-7845). Declared here so Tailwind registers the
|
|
140
|
+
font-sans / font-serif / font-mono utilities; the same composition is
|
|
141
|
+
re-declared, unlayered, in the FONT SETS section, which is where it is
|
|
142
|
+
explained. The -source variables are set by next/font in app/fonts.ts. */
|
|
139
143
|
--font-sans:
|
|
140
|
-
var(--font-sans-
|
|
141
|
-
|
|
142
|
-
--font-
|
|
144
|
+
var(--font-sans-pairing, 'Segoe UI'),
|
|
145
|
+
var(--font-script-sans, var(--font-sans-tail, sans-serif)), var(--font-sans-tail, sans-serif);
|
|
146
|
+
--font-serif:
|
|
147
|
+
var(--font-serif-pairing, Georgia), var(--font-script-serif, var(--font-serif-tail, serif)),
|
|
148
|
+
var(--font-serif-tail, serif);
|
|
149
|
+
--font-mono:
|
|
150
|
+
var(--font-mono-pairing, 'SF Mono'), var(--font-script-mono, var(--font-mono-tail, monospace)),
|
|
151
|
+
var(--font-mono-tail, monospace);
|
|
143
152
|
|
|
144
153
|
/* Type scale — Notion-ish hierarchy.
|
|
145
154
|
Headings (xl and up) default to serif; body defaults to sans. */
|
|
@@ -262,6 +271,22 @@
|
|
|
262
271
|
--focus-ring-color: var(--color-primary);
|
|
263
272
|
}
|
|
264
273
|
|
|
274
|
+
/* The BASE (motir) pairing's chains for the three roles (MOTIR-7845). Kept
|
|
275
|
+
outside @theme so Tailwind does not mint font-sans-pairing utilities, and
|
|
276
|
+
placed before every [data-type] block so each pairing overrides it. The
|
|
277
|
+
pairing chain names faces only; its generic keywords live in the tail, and
|
|
278
|
+
so do -apple-system and BlinkMacSystemFont, which name the platform's UI font
|
|
279
|
+
with its own CJK cascade (PingFang, Hiragino) and would draw the commonest
|
|
280
|
+
Han glyphs ahead of the font set's face (MOTIR-7880). */
|
|
281
|
+
:root {
|
|
282
|
+
--font-sans-pairing: var(--font-sans-source, 'Segoe UI'), 'Segoe UI', Roboto;
|
|
283
|
+
--font-sans-tail: -apple-system, BlinkMacSystemFont, sans-serif;
|
|
284
|
+
--font-serif-pairing: var(--font-serif-source, Georgia);
|
|
285
|
+
--font-serif-tail: serif;
|
|
286
|
+
--font-mono-pairing: var(--font-mono-source, 'SF Mono'), 'SF Mono', Menlo;
|
|
287
|
+
--font-mono-tail: monospace;
|
|
288
|
+
}
|
|
289
|
+
|
|
265
290
|
/* ═══════════════════════════════════════════════════════════════════════
|
|
266
291
|
TIER 1 — Dark mode color overrides
|
|
267
292
|
Activated when [data-theme="dark"] is set on <html> (by the init
|
|
@@ -4006,6 +4031,18 @@
|
|
|
4006
4031
|
--el-showcase-wave-1: var(--color-primary);
|
|
4007
4032
|
--el-showcase-wave-2: var(--color-accent-teal);
|
|
4008
4033
|
--el-showcase-wave-3: var(--color-accent);
|
|
4034
|
+
/* motir.co's live link ("Motir builds itself" in the header): its square
|
|
4035
|
+
and its words walk the wave hues. The mark is each hue as is; the text
|
|
4036
|
+
is each hue mixed 60/40 with the primary ink, because the raw teal and
|
|
4037
|
+
warm hues fall below AA as text in most light palettes (down to 2.4:1),
|
|
4038
|
+
and at 60% every hue clears 4.5:1 on the page and the surface in every
|
|
4039
|
+
palette and theme. */
|
|
4040
|
+
--el-live-mark-1: var(--color-primary);
|
|
4041
|
+
--el-live-mark-2: var(--color-accent-teal);
|
|
4042
|
+
--el-live-mark-3: var(--color-accent);
|
|
4043
|
+
--el-live-text-1: color-mix(in srgb, var(--color-primary) 60%, var(--color-foreground));
|
|
4044
|
+
--el-live-text-2: color-mix(in srgb, var(--color-accent-teal) 60%, var(--color-foreground));
|
|
4045
|
+
--el-live-text-3: color-mix(in srgb, var(--color-accent) 60%, var(--color-foreground));
|
|
4009
4046
|
}
|
|
4010
4047
|
|
|
4011
4048
|
/* Dark-mode lift for the sidebar hover token. The other three --el-sidebar-*
|
|
@@ -4331,12 +4368,14 @@
|
|
|
4331
4368
|
|
|
4332
4369
|
/* The `motir` BASE type pairing, re-asserted (MOTIR-3933) — same reason as
|
|
4333
4370
|
the base palette above. The type axis carries no descendant rules, so these
|
|
4334
|
-
three
|
|
4371
|
+
three roles' pairing chains and tails are the whole of it. */
|
|
4335
4372
|
[data-appearance-scope][data-type='motir'] {
|
|
4336
|
-
--font-mono: var(--font-mono-source,
|
|
4337
|
-
--font-
|
|
4338
|
-
|
|
4339
|
-
--font-
|
|
4373
|
+
--font-mono-pairing: var(--font-mono-source, 'SF Mono'), 'SF Mono', Menlo;
|
|
4374
|
+
--font-mono-tail: monospace;
|
|
4375
|
+
--font-sans-pairing: var(--font-sans-source, 'Segoe UI'), 'Segoe UI', Roboto;
|
|
4376
|
+
--font-sans-tail: -apple-system, BlinkMacSystemFont, sans-serif;
|
|
4377
|
+
--font-serif-pairing: var(--font-serif-source, Georgia);
|
|
4378
|
+
--font-serif-tail: serif;
|
|
4340
4379
|
}
|
|
4341
4380
|
|
|
4342
4381
|
/* ── Palette: Cobalt (data-palette="cobalt") — Subtask 7.3.49 ───────────────
|
|
@@ -4968,17 +5007,18 @@
|
|
|
4968
5007
|
|
|
4969
5008
|
[data-type='motir-sans'] {
|
|
4970
5009
|
/* Sans headlines — re-point the editorial serif role at the sans stack. */
|
|
4971
|
-
--font-serif:
|
|
4972
|
-
|
|
5010
|
+
--font-serif-pairing: var(--font-sans-source, 'Segoe UI'), 'Segoe UI', Roboto;
|
|
5011
|
+
--font-serif-tail: -apple-system, BlinkMacSystemFont, sans-serif;
|
|
4973
5012
|
/* Re-asserted at the BASE value, not omitted (MOTIR-3933). An omitted role
|
|
4974
5013
|
inherits the ANCESTOR's, which is invisible on `<html>` and wrong in a
|
|
4975
5014
|
nested scope: this pairing under a mono ancestor rendered mono. Unlike the
|
|
4976
5015
|
colour axis there is no `[data-appearance-scope]` layer to fall back
|
|
4977
5016
|
through — `--font-*` are role tokens, not `--el-*` — so every
|
|
4978
5017
|
`[data-type]` block must be TOTAL over the three roles. */
|
|
4979
|
-
--font-sans:
|
|
4980
|
-
|
|
4981
|
-
--font-mono: var(--font-mono-source,
|
|
5018
|
+
--font-sans-pairing: var(--font-sans-source, 'Segoe UI'), 'Segoe UI', Roboto;
|
|
5019
|
+
--font-sans-tail: -apple-system, BlinkMacSystemFont, sans-serif;
|
|
5020
|
+
--font-mono-pairing: var(--font-mono-source, 'SF Mono'), 'SF Mono', Menlo;
|
|
5021
|
+
--font-mono-tail: monospace;
|
|
4982
5022
|
}
|
|
4983
5023
|
|
|
4984
5024
|
[data-type='motir-mono'] {
|
|
@@ -4987,15 +5027,18 @@
|
|
|
4987
5027
|
content) re-types to a code-native monospace voice, not just headlines (Yue,
|
|
4988
5028
|
2026-06-19: the type axis must re-type the whole UI, chrome included). Meta
|
|
4989
5029
|
already is mono. ONLY --font-* tokens (the disjointness guard). */
|
|
4990
|
-
--font-serif: var(--font-mono-source,
|
|
4991
|
-
--font-
|
|
5030
|
+
--font-serif-pairing: var(--font-mono-source, 'SF Mono'), 'SF Mono', Menlo;
|
|
5031
|
+
--font-serif-tail: monospace;
|
|
5032
|
+
--font-sans-pairing: var(--font-mono-source, 'SF Mono'), 'SF Mono', Menlo;
|
|
5033
|
+
--font-sans-tail: monospace;
|
|
4992
5034
|
/* Re-asserted at the BASE value, not omitted (MOTIR-3933). An omitted role
|
|
4993
5035
|
inherits the ANCESTOR's, which is invisible on `<html>` and wrong in a
|
|
4994
5036
|
nested scope: this pairing under a mono ancestor rendered mono. Unlike the
|
|
4995
5037
|
colour axis there is no `[data-appearance-scope]` layer to fall back
|
|
4996
5038
|
through — `--font-*` are role tokens, not `--el-*` — so every
|
|
4997
5039
|
`[data-type]` block must be TOTAL over the three roles. */
|
|
4998
|
-
--font-mono: var(--font-mono-source,
|
|
5040
|
+
--font-mono-pairing: var(--font-mono-source, 'SF Mono'), 'SF Mono', Menlo;
|
|
5041
|
+
--font-mono-tail: monospace;
|
|
4999
5042
|
}
|
|
5000
5043
|
|
|
5001
5044
|
/* ── Palette: Spectrum (data-palette="spectrum") — Subtask 7.3.52 ───────────
|
|
@@ -6011,15 +6054,18 @@
|
|
|
6011
6054
|
(Yue, 2026-06-19: the type axis must re-type the whole UI, chrome included).
|
|
6012
6055
|
Meta stays JetBrains Mono (--font-mono). ONLY font tokens here (the
|
|
6013
6056
|
disjointness guard): no colour (--el / --color) or shape token. */
|
|
6014
|
-
--font-serif: var(--font-grotesk-source)
|
|
6015
|
-
--font-
|
|
6057
|
+
--font-serif-pairing: var(--font-grotesk-source);
|
|
6058
|
+
--font-serif-tail: ui-sans-serif, system-ui, sans-serif;
|
|
6059
|
+
--font-sans-pairing: var(--font-grotesk-source);
|
|
6060
|
+
--font-sans-tail: ui-sans-serif, system-ui, sans-serif;
|
|
6016
6061
|
/* Re-asserted at the BASE value, not omitted (MOTIR-3933). An omitted role
|
|
6017
6062
|
inherits the ANCESTOR's, which is invisible on `<html>` and wrong in a
|
|
6018
6063
|
nested scope: this pairing under a mono ancestor rendered mono. Unlike the
|
|
6019
6064
|
colour axis there is no `[data-appearance-scope]` layer to fall back
|
|
6020
6065
|
through — `--font-*` are role tokens, not `--el-*` — so every
|
|
6021
6066
|
`[data-type]` block must be TOTAL over the three roles. */
|
|
6022
|
-
--font-mono: var(--font-mono-source,
|
|
6067
|
+
--font-mono-pairing: var(--font-mono-source, 'SF Mono'), 'SF Mono', Menlo;
|
|
6068
|
+
--font-mono-tail: monospace;
|
|
6023
6069
|
}
|
|
6024
6070
|
|
|
6025
6071
|
[data-type='editorial'] {
|
|
@@ -6030,16 +6076,18 @@
|
|
|
6030
6076
|
so the change is a real, visible FACE swap — magazine, considered — not a
|
|
6031
6077
|
size tweak. ONLY --font-* role tokens here (the disjointness guard): no
|
|
6032
6078
|
colour (--el/--color) or shape (--radius/--spacing/--shadow) token. */
|
|
6033
|
-
--font-serif: var(--font-editorial-source), 'Fraunces', Georgia
|
|
6079
|
+
--font-serif-pairing: var(--font-editorial-source), 'Fraunces', Georgia;
|
|
6080
|
+
--font-serif-tail: serif;
|
|
6034
6081
|
/* Re-asserted at the BASE value, not omitted (MOTIR-3933). An omitted role
|
|
6035
6082
|
inherits the ANCESTOR's, which is invisible on `<html>` and wrong in a
|
|
6036
6083
|
nested scope: this pairing under a mono ancestor rendered mono. Unlike the
|
|
6037
6084
|
colour axis there is no `[data-appearance-scope]` layer to fall back
|
|
6038
6085
|
through — `--font-*` are role tokens, not `--el-*` — so every
|
|
6039
6086
|
`[data-type]` block must be TOTAL over the three roles. */
|
|
6040
|
-
--font-sans:
|
|
6041
|
-
|
|
6042
|
-
--font-mono: var(--font-mono-source,
|
|
6087
|
+
--font-sans-pairing: var(--font-sans-source, 'Segoe UI'), 'Segoe UI', Roboto;
|
|
6088
|
+
--font-sans-tail: -apple-system, BlinkMacSystemFont, sans-serif;
|
|
6089
|
+
--font-mono-pairing: var(--font-mono-source, 'SF Mono'), 'SF Mono', Menlo;
|
|
6090
|
+
--font-mono-tail: monospace;
|
|
6043
6091
|
}
|
|
6044
6092
|
|
|
6045
6093
|
[data-type='mono-technical'] {
|
|
@@ -6052,7 +6100,132 @@
|
|
|
6052
6100
|
JetBrains mono stack). Only --font-* role tokens are touched — colour
|
|
6053
6101
|
(--el/--color) and shape (--radius/--spacing) are the other two, independent
|
|
6054
6102
|
axes (the disjointness guard). */
|
|
6055
|
-
--font-serif: var(--font-mono-technical-source,
|
|
6056
|
-
--font-
|
|
6057
|
-
--font-
|
|
6103
|
+
--font-serif-pairing: var(--font-mono-technical-source, 'SF Mono'), 'SF Mono', Menlo;
|
|
6104
|
+
--font-serif-tail: monospace;
|
|
6105
|
+
--font-mono-pairing: var(--font-mono-technical-source, 'SF Mono'), 'SF Mono', Menlo;
|
|
6106
|
+
--font-mono-tail: monospace;
|
|
6107
|
+
--font-sans-pairing: var(--font-mono-technical-source, 'SF Mono'), 'SF Mono', Menlo;
|
|
6108
|
+
--font-sans-tail: monospace;
|
|
6109
|
+
}
|
|
6110
|
+
|
|
6111
|
+
/* ═══════════════════════════════════════════════════════════════════════
|
|
6112
|
+
FONT SETS — the script face behind the pairing (Story MOTIR-7733 · MOTIR-7845)
|
|
6113
|
+
A Type pairing (the [data-type] blocks above) decides each role's LATIN
|
|
6114
|
+
faces. A font set decides the SCRIPT face that sits BEHIND them, chosen by
|
|
6115
|
+
the language of the element: on <html lang="ja" data-type="editorial">
|
|
6116
|
+
--font-serif resolves to Fraunces (Latin glyphs), then the ja set's serif
|
|
6117
|
+
face (kana and kanji), then the generic tail.
|
|
6118
|
+
|
|
6119
|
+
The two axes are composed ONCE, in the rule below, from three tokens per
|
|
6120
|
+
role, so neither axis multiplies the other:
|
|
6121
|
+
--font-<role>-pairing the pairing's named faces, with NO generic keyword.
|
|
6122
|
+
A generic (serif, sans-serif, system-ui, ui-*) maps
|
|
6123
|
+
to one system font, which on a CJK system covers
|
|
6124
|
+
CJK and would pre-empt the set's face.
|
|
6125
|
+
--font-script-<role> the set's face for the language (the blocks below).
|
|
6126
|
+
--font-<role>-tail the pairing's generic tail, as it was before.
|
|
6127
|
+
|
|
6128
|
+
Every name in this section comes from packages/design-system/src/theme/
|
|
6129
|
+
fontSets.ts (FONT_SET_REGISTRY, LOCALE_FONT_SET, fontSetMemberVar). The
|
|
6130
|
+
consuming app's next/font loader assigns the --font-set-* variables; this
|
|
6131
|
+
file assigns none of them. motir.co imports this file and loads no CJK
|
|
6132
|
+
face, so every var() here carries a fallback: an undefined variable without
|
|
6133
|
+
one would make the whole font-family invalid at computed time, and the
|
|
6134
|
+
pairing's Latin face would be lost with it.
|
|
6135
|
+
|
|
6136
|
+
Only --font-* custom properties are set here: no @font-face, no
|
|
6137
|
+
unicode-range, no colour or shape token.
|
|
6138
|
+
═══════════════════════════════════════════════════════════════════════ */
|
|
6139
|
+
|
|
6140
|
+
/* The composition. A custom property's var() is resolved on the element that
|
|
6141
|
+
DECLARES it, so every element that can change one of the inputs (a pairing,
|
|
6142
|
+
a language, a picked member) re-composes the three roles itself. */
|
|
6143
|
+
:root,
|
|
6144
|
+
[data-type],
|
|
6145
|
+
[lang],
|
|
6146
|
+
[data-font-set-sans],
|
|
6147
|
+
[data-font-set-serif],
|
|
6148
|
+
[data-font-set-mono] {
|
|
6149
|
+
--font-sans:
|
|
6150
|
+
var(--font-sans-pairing, 'Segoe UI'),
|
|
6151
|
+
var(--font-script-sans, var(--font-sans-tail, sans-serif)), var(--font-sans-tail, sans-serif);
|
|
6152
|
+
--font-serif:
|
|
6153
|
+
var(--font-serif-pairing, Georgia), var(--font-script-serif, var(--font-serif-tail, serif)),
|
|
6154
|
+
var(--font-serif-tail, serif);
|
|
6155
|
+
--font-mono:
|
|
6156
|
+
var(--font-mono-pairing, 'SF Mono'), var(--font-script-mono, var(--font-mono-tail, monospace)),
|
|
6157
|
+
var(--font-mono-tail, monospace);
|
|
6158
|
+
}
|
|
6159
|
+
|
|
6160
|
+
/* The script token is declared only on an element that carries a lang
|
|
6161
|
+
attribute, and every element below it INHERITS the value. `:lang()` alone
|
|
6162
|
+
would match every descendant and re-declare the set's default on each one,
|
|
6163
|
+
so a nested [data-type] preview inside a page with a picked member would
|
|
6164
|
+
re-compose with the default and lose the pick.
|
|
6165
|
+
|
|
6166
|
+
Latin: no script face. `initial` makes the token the guaranteed-invalid
|
|
6167
|
+
value, so the composition's fallback (the pairing's own tail) takes the slot
|
|
6168
|
+
and the stack equals the pairing's stack exactly. Listed per locale so a
|
|
6169
|
+
Latin-language element nested in a CJK page resets. A page whose language no
|
|
6170
|
+
set lists matches no block and also gets Latin, as resolveFontSet falls
|
|
6171
|
+
back. */
|
|
6172
|
+
[lang]:lang(en),
|
|
6173
|
+
[lang]:lang(de),
|
|
6174
|
+
[lang]:lang(fr),
|
|
6175
|
+
[lang]:lang(es),
|
|
6176
|
+
[lang]:lang(it),
|
|
6177
|
+
[lang]:lang(nl),
|
|
6178
|
+
[lang]:lang(pl),
|
|
6179
|
+
[lang]:lang(pt) {
|
|
6180
|
+
--font-script-sans: initial;
|
|
6181
|
+
--font-script-serif: initial;
|
|
6182
|
+
--font-script-mono: initial;
|
|
6183
|
+
}
|
|
6184
|
+
|
|
6185
|
+
/* Simplified Chinese (zh-Hans): each role's default member. A CJK mono role
|
|
6186
|
+
draws its Han glyphs from the set's sans face (fontSets.ts sameFaceAs), so
|
|
6187
|
+
the block stays total over the three roles while loading no extra face. */
|
|
6188
|
+
[lang]:lang(zh) {
|
|
6189
|
+
--font-script-sans: var(--font-set-zh-Hans-sans-noto-sans-sc, sans-serif);
|
|
6190
|
+
--font-script-serif: var(--font-set-zh-Hans-serif-noto-serif-sc, serif);
|
|
6191
|
+
--font-script-mono: var(--font-set-zh-Hans-sans-noto-sans-sc, monospace);
|
|
6192
|
+
}
|
|
6193
|
+
|
|
6194
|
+
/* A member picked by name. Comes after the set's block, so on an element
|
|
6195
|
+
carrying both a lang and the attribute this one wins the specificity tie.
|
|
6196
|
+
An id the registry does not list matches nothing and keeps the default. */
|
|
6197
|
+
:lang(zh)[data-font-set-serif='lxgw-wenkai-tc'] {
|
|
6198
|
+
--font-script-serif: var(--font-set-zh-Hans-serif-lxgw-wenkai-tc, serif);
|
|
6199
|
+
}
|
|
6200
|
+
|
|
6201
|
+
/* Japanese (ja): each role's default member. A CJK mono role
|
|
6202
|
+
draws its Han glyphs from the set's sans face (fontSets.ts sameFaceAs), so
|
|
6203
|
+
the block stays total over the three roles while loading no extra face. */
|
|
6204
|
+
[lang]:lang(ja) {
|
|
6205
|
+
--font-script-sans: var(--font-set-ja-sans-noto-sans-jp, sans-serif);
|
|
6206
|
+
--font-script-serif: var(--font-set-ja-serif-noto-serif-jp, serif);
|
|
6207
|
+
--font-script-mono: var(--font-set-ja-sans-noto-sans-jp, monospace);
|
|
6208
|
+
}
|
|
6209
|
+
|
|
6210
|
+
/* A member picked by name. Comes after the set's block, so on an element
|
|
6211
|
+
carrying both a lang and the attribute this one wins the specificity tie.
|
|
6212
|
+
An id the registry does not list matches nothing and keeps the default. */
|
|
6213
|
+
:lang(ja)[data-font-set-sans='m-plus-rounded-1c'] {
|
|
6214
|
+
--font-script-sans: var(--font-set-ja-sans-m-plus-rounded-1c, sans-serif);
|
|
6215
|
+
}
|
|
6216
|
+
|
|
6217
|
+
/* Korean (ko): each role's default member. A CJK mono role
|
|
6218
|
+
draws its Han glyphs from the set's sans face (fontSets.ts sameFaceAs), so
|
|
6219
|
+
the block stays total over the three roles while loading no extra face. */
|
|
6220
|
+
[lang]:lang(ko) {
|
|
6221
|
+
--font-script-sans: var(--font-set-ko-sans-noto-sans-kr, sans-serif);
|
|
6222
|
+
--font-script-serif: var(--font-set-ko-serif-noto-serif-kr, serif);
|
|
6223
|
+
--font-script-mono: var(--font-set-ko-sans-noto-sans-kr, monospace);
|
|
6224
|
+
}
|
|
6225
|
+
|
|
6226
|
+
/* A member picked by name. Comes after the set's block, so on an element
|
|
6227
|
+
carrying both a lang and the attribute this one wins the specificity tie.
|
|
6228
|
+
An id the registry does not list matches nothing and keeps the default. */
|
|
6229
|
+
:lang(ko)[data-font-set-sans='nanum-gothic'] {
|
|
6230
|
+
--font-script-sans: var(--font-set-ko-sans-nanum-gothic, sans-serif);
|
|
6058
6231
|
}
|