7c50fa9a90
Agora's colors and fonts are now hardcoded in the bundle. There is no
runtime CSS-variable injection, no remote-loaded theme, no font-family
override from event data, no background image, no recolored favicon.
Switching themes only toggles the .dark class on <html>.
Removed:
- src/themes.ts: ThemeConfig, ThemesConfig, CoreThemeColors, ThemeFont,
ThemeBackground, themePresets (22 presets), buildThemeCssFromCore,
deriveTokensFromCore, the 'custom' Theme variant. Now exports only
resolveTheme(theme) -> 'light' | 'dark'.
- src/lib/fontLoader.ts: deleted. Mounted arbitrary @font-face rules with
event-sourced URLs and font-family overrides — the primary CSS-injection
vector. sanitizeCssString moved to src/lib/cssSanitize.ts (still used by
the Letter feature).
- AppProvider hooks useApplyFonts / useApplyBackground / useApplyFavicon.
- useTheme.applyCustomTheme — the entrypoint that wrote external palettes
to global theme state.
- ColorMomentEyeButton + its callers in NoteCard and PostDetailPage. Color
Moments (kind 3367) still render as palette art, but the 'Set as theme'
button is gone.
- LetterAttachment in LetterDetailSheet — the gift-box UI that applied an
attached color moment as the recipient's theme.
- paletteToTheme() from colorMomentUtils — only getColors() remains.
- customTheme / themes fields from AppConfig, AppConfigSchema,
EncryptedSettings, EncryptedSettingsSchema. Existing customTheme values
in localStorage and encrypted NIP-78 settings are now ignored.
- 14 unused @fontsource packages. Only the 10 letter fonts and the 2 base
UI fonts (Inter Variable, Bebas Neue) remain. noto-sans-nushu is kept
for the encrypted-letter obfuscation indicator.
Added:
- Static :root {} and .dark {} blocks in src/index.css with the 19 shadcn
tokens hardcoded. These were previously injected at runtime by
AppProvider; without them the app would lose all colors when the runtime
injector was removed.
- src/lib/cssSanitize.ts holding the sanitizeCssString helper for the
Letter feature's font-family interpolation.
Simplified:
- public/theme.js: no longer reads customTheme or themes from localStorage,
just resolves system/light/dark with hardcoded built-ins. Must stay in
sync with src/index.css colors.
- src/lib/fonts.ts: trimmed to only the 10 letter fonts. findBundledFont
and resolveCssFamily removed (they were only used by fontLoader).
107 lines
3.1 KiB
TypeScript
107 lines
3.1 KiB
TypeScript
/**
|
|
* Bundled font registry for the Letter feature (kind 8211).
|
|
*
|
|
* Letters let senders pick a font for the body text; the recipient's client
|
|
* lazy-loads the corresponding fontsource package when the letter is opened.
|
|
* Only the fonts in `FONT_OPTIONS` (see `src/lib/letterTypes.ts`) are
|
|
* ever requested, and the family name is matched against this allowlist —
|
|
* arbitrary font URLs are NOT loaded.
|
|
*
|
|
* The Agora app's UI uses `Inter Variable` and `Bebas Neue` (loaded eagerly
|
|
* in `src/main.tsx`), neither of which is configurable.
|
|
*/
|
|
|
|
interface BundledFont {
|
|
/** Canonical font-family name used in letter metadata. */
|
|
family: string;
|
|
/** Dynamic import that loads the font CSS into the page. */
|
|
load: () => Promise<void>;
|
|
}
|
|
|
|
const bundledFonts: BundledFont[] = [
|
|
{
|
|
family: 'Fredoka',
|
|
load: () => import('@fontsource-variable/fredoka').then(() => {}),
|
|
},
|
|
{
|
|
family: 'Fredoka Variable',
|
|
load: () => import('@fontsource-variable/fredoka').then(() => {}),
|
|
},
|
|
{
|
|
family: 'Nunito',
|
|
load: () => import('@fontsource-variable/nunito').then(() => {}),
|
|
},
|
|
{
|
|
family: 'Nunito Variable',
|
|
load: () => import('@fontsource-variable/nunito').then(() => {}),
|
|
},
|
|
{
|
|
family: 'Playfair Display',
|
|
load: () => import('@fontsource-variable/playfair-display').then(() => {}),
|
|
},
|
|
{
|
|
family: 'Playfair Display Variable',
|
|
load: () => import('@fontsource-variable/playfair-display').then(() => {}),
|
|
},
|
|
{
|
|
family: 'Caveat',
|
|
load: () => import('@fontsource/caveat/400.css').then(() => {}),
|
|
},
|
|
{
|
|
family: 'Pacifico',
|
|
load: () => import('@fontsource/pacifico/400.css').then(() => {}),
|
|
},
|
|
{
|
|
family: 'Pirata One',
|
|
load: () => import('@fontsource/pirata-one/400.css').then(() => {}),
|
|
},
|
|
{
|
|
family: 'Permanent Marker',
|
|
load: () => import('@fontsource/permanent-marker/400.css').then(() => {}),
|
|
},
|
|
{
|
|
family: 'Special Elite',
|
|
load: () => import('@fontsource/special-elite/400.css').then(() => {}),
|
|
},
|
|
{
|
|
family: 'Creepster',
|
|
load: () => import('@fontsource/creepster/400.css').then(() => {}),
|
|
},
|
|
{
|
|
family: 'Silkscreen',
|
|
load: () => import('@fontsource/silkscreen/400.css').then(() => {}),
|
|
},
|
|
];
|
|
|
|
/** Map from lowercase family name to BundledFont for quick lookup. */
|
|
const bundledFontMap = new Map(
|
|
bundledFonts.map((f) => [f.family.toLowerCase(), f]),
|
|
);
|
|
|
|
/** Tracks which fonts have already been loaded. */
|
|
const loadedFonts = new Set<string>();
|
|
|
|
/**
|
|
* Ensure a bundled font is loaded (idempotent).
|
|
*
|
|
* Returns `true` if the font was found in the allowlist and the CSS was
|
|
* loaded (or had already been loaded). Returns `false` for any unknown
|
|
* family — the caller's text just falls back to the default font stack.
|
|
*/
|
|
export async function loadBundledFont(family: string): Promise<boolean> {
|
|
const key = family.toLowerCase();
|
|
if (loadedFonts.has(key)) return true;
|
|
|
|
const font = bundledFontMap.get(key);
|
|
if (!font) return false;
|
|
|
|
try {
|
|
await font.load();
|
|
loadedFonts.add(key);
|
|
return true;
|
|
} catch (error) {
|
|
console.error(`Failed to load bundled font "${family}":`, error);
|
|
return false;
|
|
}
|
|
}
|