@web-my-money/tokens 1.0.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/bin/doctor.mjs ADDED
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `wmm-doctor` — consumer-setup checker for apps installing @web-my-money/*.
4
+ *
5
+ * Adapted from Astryx's `astryx doctor`: diagnose the exact setup gotchas this
6
+ * design system has actually hit in production onboarding (wmm-finance-hr,
7
+ * wmm-os), each with an actionable fix, instead of a silent broken render.
8
+ *
9
+ * Checks:
10
+ * 1. Tailwind version — v4 required (v3 needs the tailwind-preset instead)
11
+ * 2. .npmrc registry mapping — @web-my-money:registry must resolve to GitHub Packages
12
+ * 3. @source directives — one per installed @web-my-money/* package (Tailwind v4
13
+ * does not scan node_modules by default — see LAYOUT_GUIDE.md §8)
14
+ * 4. --color-danger alias — only checked for apps with their own hand-rolled
15
+ * @theme inline block (not needed if the app wholesale-
16
+ * imports @web-my-money/tokens/theme)
17
+ *
18
+ * Usage: npx wmm-doctor (run from the consuming app's npm project directory)
19
+ * Exit 0 = clean, Exit 1 = problem(s) found.
20
+ */
21
+ import { readFileSync, existsSync, readdirSync, statSync } from "node:fs";
22
+ import { join } from "node:path";
23
+
24
+ const CWD = process.cwd();
25
+ const SKIP_DIRS = new Set(["node_modules", ".git", ".next", "dist", "build"]);
26
+
27
+ function readJson(path) {
28
+ try {
29
+ return JSON.parse(readFileSync(path, "utf8"));
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
34
+
35
+ function findGlobalsCss() {
36
+ const candidates = [
37
+ "app/globals.css",
38
+ "src/app/globals.css",
39
+ "styles/globals.css",
40
+ "src/styles/globals.css",
41
+ ];
42
+ for (const c of candidates) {
43
+ const abs = join(CWD, c);
44
+ if (existsSync(abs)) return abs;
45
+ }
46
+ // Fallback: shallow search for any globals.css, bounded depth.
47
+ const found = [];
48
+ (function walk(dir, depth) {
49
+ if (depth > 4 || found.length) return;
50
+ let entries;
51
+ try {
52
+ entries = readdirSync(dir);
53
+ } catch {
54
+ return;
55
+ }
56
+ for (const entry of entries) {
57
+ if (SKIP_DIRS.has(entry)) continue;
58
+ const full = join(dir, entry);
59
+ let st;
60
+ try {
61
+ st = statSync(full);
62
+ } catch {
63
+ continue;
64
+ }
65
+ if (st.isDirectory()) walk(full, depth + 1);
66
+ else if (entry === "globals.css") found.push(full);
67
+ }
68
+ })(CWD, 0);
69
+ return found[0] ?? null;
70
+ }
71
+
72
+ const problems = [];
73
+ const oks = [];
74
+
75
+ function fail(msg, fix) {
76
+ problems.push({ msg, fix });
77
+ }
78
+ function ok(msg) {
79
+ oks.push(msg);
80
+ }
81
+
82
+ const pkgJson = readJson(join(CWD, "package.json"));
83
+ if (!pkgJson) {
84
+ console.error("✗ wmm-doctor: no package.json found in " + CWD + " — run this from your app's npm project directory.");
85
+ process.exit(1);
86
+ }
87
+
88
+ const deps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };
89
+ const wmmPackages = Object.keys(deps).filter((d) => d.startsWith("@web-my-money/"));
90
+
91
+ if (wmmPackages.length === 0) {
92
+ console.log("wmm-doctor: no @web-my-money/* packages found in package.json — nothing to check.");
93
+ process.exit(0);
94
+ }
95
+
96
+ // Packages that ship CSS/tokens/hooks only — no rendered JSX/TSX components —
97
+ // so they never need an @source directive (confirmed against the wmm-finance-hr
98
+ // migration: its globals.css @sources blocks/preset-wmm/brand/primitives but not tokens).
99
+ const NO_SOURCE_NEEDED = new Set(["@web-my-money/tokens"]);
100
+ const sourceCheckPackages = wmmPackages.filter((p) => !NO_SOURCE_NEEDED.has(p));
101
+
102
+ // 1. Tailwind version
103
+ const tailwindRange = deps.tailwindcss;
104
+ if (!tailwindRange) {
105
+ fail("tailwindcss is not a dependency.", "Install Tailwind v4: npm install tailwindcss@^4");
106
+ } else {
107
+ const major = parseInt(tailwindRange.replace(/[^\d.]/g, "").split(".")[0], 10);
108
+ if (Number.isFinite(major) && major < 4) {
109
+ fail(
110
+ `tailwindcss is on v${major} (found "${tailwindRange}").`,
111
+ "Upgrade to Tailwind v4, or use @web-my-money/tokens/tailwind-preset for v3 (see INSTALL_GUIDE.md).",
112
+ );
113
+ } else {
114
+ ok(`tailwindcss ${tailwindRange} (v4+)`);
115
+ }
116
+ }
117
+
118
+ // 2. .npmrc registry mapping
119
+ const npmrcPath = join(CWD, ".npmrc");
120
+ if (!existsSync(npmrcPath)) {
121
+ fail(
122
+ ".npmrc not found in this directory.",
123
+ 'Add .npmrc here with:\n @web-my-money:registry=https://npm.pkg.github.com\n //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}',
124
+ );
125
+ } else {
126
+ const npmrc = readFileSync(npmrcPath, "utf8");
127
+ if (!/@web-my-money:registry\s*=\s*https:\/\/npm\.pkg\.github\.com/.test(npmrc)) {
128
+ fail(
129
+ ".npmrc exists but is missing the @web-my-money registry mapping.",
130
+ "Add: @web-my-money:registry=https://npm.pkg.github.com",
131
+ );
132
+ } else {
133
+ ok(".npmrc maps @web-my-money to GitHub Packages");
134
+ }
135
+ }
136
+
137
+ // 3 & 4. globals.css checks
138
+ const globalsCssPath = findGlobalsCss();
139
+ if (!globalsCssPath) {
140
+ fail(
141
+ "Could not find a globals.css in this app.",
142
+ "If your stylesheet has a different name/path, the @source and theme checks below can't run — verify manually against LAYOUT_GUIDE.md §8.",
143
+ );
144
+ } else {
145
+ const css = readFileSync(globalsCssPath, "utf8");
146
+ const relCss = globalsCssPath.replace(CWD, ".").replace(/\\/g, "/");
147
+
148
+ const missingSource = sourceCheckPackages.filter((pkg) => !css.includes(pkg));
149
+ if (sourceCheckPackages.length === 0) {
150
+ // nothing to check
151
+ } else if (missingSource.length > 0) {
152
+ fail(
153
+ `${relCss} is missing @source directives for: ${missingSource.join(", ")}`,
154
+ `Tailwind v4 does not scan node_modules by default. Add one @source per package:\n` +
155
+ missingSource.map((p) => ` @source "../../node_modules/${p}/dist";`).join("\n") +
156
+ `\n (adjust the ../.. depth to your globals.css location — see LAYOUT_GUIDE.md §8)`,
157
+ );
158
+ } else {
159
+ ok(`${relCss} has @source directives for all installed @web-my-money packages`);
160
+ }
161
+
162
+ const wholesaleImport = /@import\s+["']@web-my-money\/tokens\/theme["']/.test(css);
163
+ const hasOwnThemeBlock = /@theme\s+inline/.test(css);
164
+ if (!wholesaleImport && hasOwnThemeBlock) {
165
+ if (!css.includes("--color-danger")) {
166
+ fail(
167
+ `${relCss} has its own @theme inline block but no --color-danger alias.`,
168
+ 'Add `--color-danger: var(--destructive);` (or your danger color) to your @theme inline block — ' +
169
+ "AppShell badges and other DS components read --color-danger.",
170
+ );
171
+ } else {
172
+ ok(`${relCss} @theme inline block has --color-danger aliased`);
173
+ }
174
+ }
175
+ }
176
+
177
+ console.log(`wmm-doctor — checked ${wmmPackages.length} @web-my-money package(s) in ${CWD}\n`);
178
+ for (const msg of oks) console.log(` ✓ ${msg}`);
179
+ if (problems.length === 0) {
180
+ console.log("\n✓ all checks passed");
181
+ process.exit(0);
182
+ }
183
+ console.log("");
184
+ for (const p of problems) {
185
+ console.error(` ✗ ${p.msg}`);
186
+ console.error(` fix: ${p.fix}\n`);
187
+ }
188
+ console.error(`✗ wmm-doctor found ${problems.length} problem(s)`);
189
+ process.exit(1);
@@ -0,0 +1,72 @@
1
+ // src/theme-runtime.ts
2
+ var THEMES = ["light", "dark", "wmm"];
3
+ var DEFAULT_THEME = "dark";
4
+ var THEME_STORAGE_KEY = "wmm-theme";
5
+ function isThemeName(v) {
6
+ return typeof v === "string" && THEMES.includes(v);
7
+ }
8
+ var themeBootScript = `(function(){try{var t=localStorage.getItem('${THEME_STORAGE_KEY}');if(t==='light'||t==='dark'||t==='wmm'){document.documentElement.setAttribute('data-theme',t);}}catch(e){}})();`;
9
+ function getStoredTheme() {
10
+ if (typeof window === "undefined") return null;
11
+ try {
12
+ const t = window.localStorage.getItem(THEME_STORAGE_KEY);
13
+ return isThemeName(t) ? t : null;
14
+ } catch {
15
+ return null;
16
+ }
17
+ }
18
+ function applyTheme(name) {
19
+ if (typeof document !== "undefined") {
20
+ document.documentElement.setAttribute("data-theme", name);
21
+ }
22
+ try {
23
+ window.localStorage.setItem(THEME_STORAGE_KEY, name);
24
+ } catch {
25
+ }
26
+ }
27
+
28
+ // src/font-size-runtime.ts
29
+ var FONT_SIZES = ["md", "lg", "xl"];
30
+ var DEFAULT_FONT_SIZE = "md";
31
+ var FONT_SIZE_STORAGE_KEY = "wmm-font-size";
32
+ function isFontSizeName(v) {
33
+ return typeof v === "string" && FONT_SIZES.includes(v);
34
+ }
35
+ var fontSizeBootScript = `(function(){try{var f=localStorage.getItem('${FONT_SIZE_STORAGE_KEY}');if(f==='md'||f==='lg'||f==='xl'){document.documentElement.classList.add('font-'+f);}}catch(e){}})();`;
36
+ function getStoredFontSize() {
37
+ if (typeof window === "undefined") return null;
38
+ try {
39
+ const f = window.localStorage.getItem(FONT_SIZE_STORAGE_KEY);
40
+ return isFontSizeName(f) ? f : null;
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+ function applyFontSize(name) {
46
+ if (typeof document !== "undefined") {
47
+ const d = document.documentElement;
48
+ for (const s of FONT_SIZES) d.classList.remove(`font-${s}`);
49
+ d.classList.add(`font-${name}`);
50
+ }
51
+ try {
52
+ window.localStorage.setItem(FONT_SIZE_STORAGE_KEY, name);
53
+ } catch {
54
+ }
55
+ }
56
+
57
+ export {
58
+ THEMES,
59
+ DEFAULT_THEME,
60
+ THEME_STORAGE_KEY,
61
+ isThemeName,
62
+ themeBootScript,
63
+ getStoredTheme,
64
+ applyTheme,
65
+ FONT_SIZES,
66
+ DEFAULT_FONT_SIZE,
67
+ FONT_SIZE_STORAGE_KEY,
68
+ isFontSizeName,
69
+ fontSizeBootScript,
70
+ getStoredFontSize,
71
+ applyFontSize
72
+ };
@@ -0,0 +1,265 @@
1
+ // src/typography.ts
2
+ var fontFamily = {
3
+ /** Body text — Inter or system sans */
4
+ sans: [
5
+ "var(--font-inter)",
6
+ "ui-sans-serif",
7
+ "system-ui",
8
+ "-apple-system",
9
+ "BlinkMacSystemFont",
10
+ "Segoe UI",
11
+ "Roboto",
12
+ "Helvetica Neue",
13
+ "Arial",
14
+ "sans-serif"
15
+ ],
16
+ /** Display / hero headings — Satoshi */
17
+ display: [
18
+ "Satoshi",
19
+ "ui-sans-serif",
20
+ "system-ui",
21
+ "-apple-system",
22
+ "sans-serif"
23
+ ],
24
+ /** Labels, badges, section headers — Space Grotesk */
25
+ label: [
26
+ "var(--font-space-grotesk)",
27
+ "ui-sans-serif",
28
+ "system-ui",
29
+ "sans-serif"
30
+ ],
31
+ /** Code blocks */
32
+ mono: [
33
+ "var(--font-geist-mono)",
34
+ "ui-monospace",
35
+ "SFMono-Regular",
36
+ "Menlo",
37
+ "Monaco",
38
+ "Consolas",
39
+ "monospace"
40
+ ]
41
+ };
42
+ var fontSize = {
43
+ xs: ["0.75rem", { lineHeight: "1rem" }],
44
+ sm: ["0.875rem", { lineHeight: "1.25rem" }],
45
+ base: ["1rem", { lineHeight: "1.5rem" }],
46
+ lg: ["1.125rem", { lineHeight: "1.75rem" }],
47
+ xl: ["1.25rem", { lineHeight: "1.75rem" }],
48
+ "2xl": ["1.5rem", { lineHeight: "2rem" }],
49
+ "3xl": ["1.875rem", { lineHeight: "2.25rem" }],
50
+ "4xl": ["2.25rem", { lineHeight: "2.5rem" }],
51
+ "5xl": ["3rem", { lineHeight: "1.15" }],
52
+ "6xl": ["3.75rem", { lineHeight: "1.1" }]
53
+ };
54
+ var letterSpacing = {
55
+ tighter: "-0.05em",
56
+ tight: "-0.025em",
57
+ normal: "0em",
58
+ wide: "0.025em",
59
+ wider: "0.05em",
60
+ label: "0.15em"
61
+ };
62
+
63
+ // src/shadows.ts
64
+ var boxShadow = {
65
+ /** Subtle teal glow for CTAs */
66
+ glow: "0 0 30px rgba(0, 196, 180, 0.15)",
67
+ /** Strong teal glow on hover */
68
+ glowStrong: "0 0 40px rgba(0, 196, 180, 0.4)",
69
+ /** Deep panel shadow with inset highlight (glassmorphism) */
70
+ panel: "0 24px 80px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255,255,255,0.08)",
71
+ /** Glass panel shadow */
72
+ glass: "0 28px 90px rgba(0, 0, 0, 0.38), 0 0 36px rgba(0, 196, 180, 0.04), inset 0 1px 0 rgba(255, 255, 255, 0.06)"
73
+ };
74
+
75
+ // src/animations.ts
76
+ var keyframes = {
77
+ marquee: {
78
+ "0%": { transform: "translate3d(0, 0, 0)" },
79
+ "100%": { transform: "translate3d(-50%, 0, 0)" }
80
+ },
81
+ "marquee-reverse": {
82
+ "0%": { transform: "translate3d(-50%, 0, 0)" },
83
+ "100%": { transform: "translate3d(0, 0, 0)" }
84
+ },
85
+ shimmer: {
86
+ "0%": { transform: "translate3d(-150%, 0, 0)" },
87
+ "100%": { transform: "translate3d(150%, 0, 0)" }
88
+ },
89
+ float: {
90
+ "0%, 100%": { transform: "translate3d(0, 0, 0)" },
91
+ "50%": { transform: "translate3d(0, -12px, 0)" }
92
+ },
93
+ pulseGlow: {
94
+ "0%, 100%": { boxShadow: "0 0 0 rgba(0, 196, 180, 0.0)" },
95
+ "50%": { boxShadow: "0 0 36px rgba(0, 196, 180, 0.18)" }
96
+ }
97
+ };
98
+ var animation = {
99
+ marquee: "marquee 28s linear infinite",
100
+ "marquee-slow": "marquee 42s linear infinite",
101
+ "marquee-reverse": "marquee-reverse 32s linear infinite",
102
+ shimmer: "shimmer 4.2s linear infinite",
103
+ float: "float 8s ease-in-out infinite",
104
+ "pulse-glow": "pulseGlow 5s ease-in-out infinite"
105
+ };
106
+ var easing = {
107
+ spring: [0.22, 1, 0.36, 1],
108
+ smooth: [0.4, 0, 0.2, 1],
109
+ snappy: [0.2, 0, 0, 1]
110
+ };
111
+
112
+ // src/gradients.ts
113
+ var backgroundImage = {
114
+ /** CTA gradient: navy → aqua (production brand) */
115
+ "cta-gradient": "linear-gradient(135deg, #1f2d56, #8fccb6)",
116
+ /** Grid pattern overlay for hero backgrounds */
117
+ "hero-grid": "linear-gradient(to right, rgba(255,255,255,0.04) 1px, transparent 1px), linear-gradient(to bottom, rgba(255,255,255,0.04) 1px, transparent 1px)"
118
+ };
119
+ var glassPanel = {
120
+ background: `radial-gradient(circle at top, rgba(255, 255, 255, 0.06), transparent 42%),
121
+ linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.03)),
122
+ rgba(15, 23, 42, 0.84)`,
123
+ border: "1px solid rgba(255, 255, 255, 0.08)",
124
+ backdropFilter: "blur(20px)"
125
+ };
126
+ var spotlight = {
127
+ background: `radial-gradient(circle at 50% 0%, rgba(0, 196, 180, 0.18), transparent 28%),
128
+ radial-gradient(circle at 100% 0%, rgba(124, 58, 237, 0.2), transparent 25%)`
129
+ };
130
+ var darkBody = {
131
+ background: `radial-gradient(circle at top, rgba(14, 165, 233, 0.12), transparent 32%),
132
+ radial-gradient(circle at 80% 10%, rgba(124, 58, 237, 0.14), transparent 28%),
133
+ linear-gradient(180deg, #070b14 0%, #080b14 45%, #09111f 100%)`
134
+ };
135
+
136
+ // src/spacing.ts
137
+ var radius = {
138
+ sm: "calc(var(--radius) * 0.6)",
139
+ md: "calc(var(--radius) * 0.8)",
140
+ lg: "var(--radius)",
141
+ xl: "calc(var(--radius) * 1.4)",
142
+ "2xl": "calc(var(--radius) * 1.8)",
143
+ "3xl": "calc(var(--radius) * 2.2)",
144
+ "4xl": "calc(var(--radius) * 2.6)",
145
+ full: "9999px"
146
+ };
147
+ var radiusBase = "0.625rem";
148
+ var container = {
149
+ center: true,
150
+ padding: {
151
+ DEFAULT: "1.25rem",
152
+ sm: "1.5rem",
153
+ lg: "2rem",
154
+ xl: "2.5rem",
155
+ "2xl": "3rem"
156
+ },
157
+ screens: {
158
+ "2xl": "1320px"
159
+ }
160
+ };
161
+
162
+ // src/tailwind-preset.ts
163
+ var wmmPreset = {
164
+ darkMode: ["class"],
165
+ theme: {
166
+ container,
167
+ extend: {
168
+ colors: {
169
+ background: "var(--background)",
170
+ foreground: "var(--foreground)",
171
+ card: {
172
+ DEFAULT: "var(--card)",
173
+ foreground: "var(--card-foreground)"
174
+ },
175
+ popover: {
176
+ DEFAULT: "var(--popover)",
177
+ foreground: "var(--popover-foreground)"
178
+ },
179
+ primary: {
180
+ DEFAULT: "var(--primary)",
181
+ foreground: "var(--primary-foreground)"
182
+ },
183
+ secondary: {
184
+ DEFAULT: "var(--secondary)",
185
+ foreground: "var(--secondary-foreground)"
186
+ },
187
+ muted: {
188
+ DEFAULT: "var(--muted)",
189
+ foreground: "var(--muted-foreground)"
190
+ },
191
+ accent: {
192
+ DEFAULT: "var(--accent)",
193
+ foreground: "var(--accent-foreground)"
194
+ },
195
+ destructive: "var(--destructive)",
196
+ border: "var(--border)",
197
+ input: "var(--input)",
198
+ ring: "var(--ring)",
199
+ // Brand accents (from theme CSS — available when WMM theme is loaded)
200
+ teal: "var(--teal, #8fccb6)",
201
+ violet: "var(--violet, #869ce2)",
202
+ // Sidebar
203
+ sidebar: {
204
+ DEFAULT: "var(--sidebar)",
205
+ foreground: "var(--sidebar-foreground)",
206
+ primary: "var(--sidebar-primary)",
207
+ "primary-foreground": "var(--sidebar-primary-foreground)",
208
+ accent: "var(--sidebar-accent)",
209
+ "accent-foreground": "var(--sidebar-accent-foreground)",
210
+ border: "var(--sidebar-border)",
211
+ ring: "var(--sidebar-ring)"
212
+ },
213
+ // Charts
214
+ chart: {
215
+ 1: "var(--chart-1)",
216
+ 2: "var(--chart-2)",
217
+ 3: "var(--chart-3)",
218
+ 4: "var(--chart-4)",
219
+ 5: "var(--chart-5)"
220
+ }
221
+ },
222
+ fontFamily: {
223
+ // spread the `as const` token tuples → mutable string[] (Tailwind's Config type)
224
+ sans: [...fontFamily.sans],
225
+ display: [...fontFamily.display],
226
+ label: [...fontFamily.label],
227
+ mono: [...fontFamily.mono]
228
+ },
229
+ backgroundImage,
230
+ boxShadow,
231
+ keyframes,
232
+ animation,
233
+ borderRadius: {
234
+ sm: "calc(var(--radius) * 0.6)",
235
+ md: "calc(var(--radius) * 0.8)",
236
+ lg: "var(--radius)",
237
+ xl: "calc(var(--radius) * 1.4)",
238
+ "2xl": "calc(var(--radius) * 1.8)",
239
+ "3xl": "calc(var(--radius) * 2.2)",
240
+ "4xl": "calc(var(--radius) * 2.6)"
241
+ },
242
+ backdropBlur: {
243
+ xs: "4px"
244
+ }
245
+ }
246
+ }
247
+ };
248
+
249
+ export {
250
+ fontFamily,
251
+ fontSize,
252
+ letterSpacing,
253
+ radius,
254
+ radiusBase,
255
+ container,
256
+ boxShadow,
257
+ keyframes,
258
+ animation,
259
+ easing,
260
+ backgroundImage,
261
+ glassPanel,
262
+ spotlight,
263
+ darkBody,
264
+ wmmPreset
265
+ };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Framework-agnostic 3-theme runtime — light / dark / wmm.
3
+ *
4
+ * Apps switch themes via the `data-theme` attribute on <html>. Drop
5
+ * `themeBootScript` into <head> (before paint) to avoid a flash, and call
6
+ * `applyTheme()` from a toggle. No React dependency — wire your own UI.
7
+ */
8
+ declare const THEMES: readonly ["light", "dark", "wmm"];
9
+ type ThemeName = (typeof THEMES)[number];
10
+ declare const DEFAULT_THEME: ThemeName;
11
+ declare const THEME_STORAGE_KEY = "wmm-theme";
12
+ declare function isThemeName(v: unknown): v is ThemeName;
13
+ /**
14
+ * Inline <script> body — run BEFORE React hydrates so the stored theme is
15
+ * applied on first paint (no dark↔light flash). Safe to dangerouslySetInnerHTML.
16
+ */
17
+ declare const themeBootScript: string;
18
+ declare function getStoredTheme(): ThemeName | null;
19
+ /** Set the active theme on <html> and persist it. */
20
+ declare function applyTheme(name: ThemeName): void;
21
+
22
+ /**
23
+ * Framework-agnostic font-size preference runtime — three-step text scale
24
+ * (md/lg/xl → 14/16/18px), applied via an `html.font-*` class.
25
+ *
26
+ * Generalizes wmm-finance-hr's real, proven `font-size-toggle.tsx` pattern
27
+ * (its own localStorage key there — this ships the DS's own canonical key,
28
+ * `wmm-font-size`, same naming convention as `theme-runtime.ts`'s `wmm-theme`).
29
+ *
30
+ * Drop `fontSizeBootScript` into <head> (before paint) to avoid a flash, and
31
+ * call `applyFontSize()` from a toggle. No React dependency — wire your own
32
+ * UI, or use `FontSizeToggle` from `@web-my-money/tokens/react`.
33
+ */
34
+ declare const FONT_SIZES: readonly ["md", "lg", "xl"];
35
+ type FontSizeName = (typeof FONT_SIZES)[number];
36
+ declare const DEFAULT_FONT_SIZE: FontSizeName;
37
+ declare const FONT_SIZE_STORAGE_KEY = "wmm-font-size";
38
+ declare function isFontSizeName(v: unknown): v is FontSizeName;
39
+ /**
40
+ * Inline <script> body — run BEFORE React hydrates so the stored size is
41
+ * applied on first paint (no layout jump). Safe to dangerouslySetInnerHTML.
42
+ */
43
+ declare const fontSizeBootScript: string;
44
+ declare function getStoredFontSize(): FontSizeName | null;
45
+ /** Set the active font size on <html> (`font-{size}` class) and persist it. */
46
+ declare function applyFontSize(name: FontSizeName): void;
47
+
48
+ export { DEFAULT_FONT_SIZE as D, FONT_SIZES as F, THEMES as T, DEFAULT_THEME as a, FONT_SIZE_STORAGE_KEY as b, type FontSizeName as c, THEME_STORAGE_KEY as d, type ThemeName as e, applyFontSize as f, applyTheme as g, fontSizeBootScript as h, getStoredFontSize as i, getStoredTheme as j, isFontSizeName as k, isThemeName as l, themeBootScript as t };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Framework-agnostic 3-theme runtime — light / dark / wmm.
3
+ *
4
+ * Apps switch themes via the `data-theme` attribute on <html>. Drop
5
+ * `themeBootScript` into <head> (before paint) to avoid a flash, and call
6
+ * `applyTheme()` from a toggle. No React dependency — wire your own UI.
7
+ */
8
+ declare const THEMES: readonly ["light", "dark", "wmm"];
9
+ type ThemeName = (typeof THEMES)[number];
10
+ declare const DEFAULT_THEME: ThemeName;
11
+ declare const THEME_STORAGE_KEY = "wmm-theme";
12
+ declare function isThemeName(v: unknown): v is ThemeName;
13
+ /**
14
+ * Inline <script> body — run BEFORE React hydrates so the stored theme is
15
+ * applied on first paint (no dark↔light flash). Safe to dangerouslySetInnerHTML.
16
+ */
17
+ declare const themeBootScript: string;
18
+ declare function getStoredTheme(): ThemeName | null;
19
+ /** Set the active theme on <html> and persist it. */
20
+ declare function applyTheme(name: ThemeName): void;
21
+
22
+ /**
23
+ * Framework-agnostic font-size preference runtime — three-step text scale
24
+ * (md/lg/xl → 14/16/18px), applied via an `html.font-*` class.
25
+ *
26
+ * Generalizes wmm-finance-hr's real, proven `font-size-toggle.tsx` pattern
27
+ * (its own localStorage key there — this ships the DS's own canonical key,
28
+ * `wmm-font-size`, same naming convention as `theme-runtime.ts`'s `wmm-theme`).
29
+ *
30
+ * Drop `fontSizeBootScript` into <head> (before paint) to avoid a flash, and
31
+ * call `applyFontSize()` from a toggle. No React dependency — wire your own
32
+ * UI, or use `FontSizeToggle` from `@web-my-money/tokens/react`.
33
+ */
34
+ declare const FONT_SIZES: readonly ["md", "lg", "xl"];
35
+ type FontSizeName = (typeof FONT_SIZES)[number];
36
+ declare const DEFAULT_FONT_SIZE: FontSizeName;
37
+ declare const FONT_SIZE_STORAGE_KEY = "wmm-font-size";
38
+ declare function isFontSizeName(v: unknown): v is FontSizeName;
39
+ /**
40
+ * Inline <script> body — run BEFORE React hydrates so the stored size is
41
+ * applied on first paint (no layout jump). Safe to dangerouslySetInnerHTML.
42
+ */
43
+ declare const fontSizeBootScript: string;
44
+ declare function getStoredFontSize(): FontSizeName | null;
45
+ /** Set the active font size on <html> (`font-{size}` class) and persist it. */
46
+ declare function applyFontSize(name: FontSizeName): void;
47
+
48
+ export { DEFAULT_FONT_SIZE as D, FONT_SIZES as F, THEMES as T, DEFAULT_THEME as a, FONT_SIZE_STORAGE_KEY as b, type FontSizeName as c, THEME_STORAGE_KEY as d, type ThemeName as e, applyFontSize as f, applyTheme as g, fontSizeBootScript as h, getStoredFontSize as i, getStoredTheme as j, isFontSizeName as k, isThemeName as l, themeBootScript as t };
Binary file
package/dist/fonts.css ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * WMM display font — Satoshi (weight 900). Import in your app's globals.css:
3
+ * @import "@web-my-money/tokens/fonts.css";
4
+ *
5
+ * Body (Inter) and label (Space Grotesk) fonts are loaded per-app via next/font,
6
+ * which set --font-inter / --font-space-grotesk. Satoshi isn't on Google Fonts,
7
+ * so it ships here as a self-hosted woff2 and binds to the "Satoshi" family that
8
+ * the typography `display` stack references.
9
+ */
10
+ @font-face {
11
+ font-family: "Satoshi";
12
+ src: url("./fonts/satoshi-black.woff2") format("woff2");
13
+ font-weight: 900;
14
+ font-style: normal;
15
+ font-display: swap;
16
+ }