diff --git a/overlay/src/app/pages/client/ClientNonUIFeatures.tsx b/overlay/src/app/pages/client/ClientNonUIFeatures.tsx index 1387ffa..1bb3893 100644 --- a/overlay/src/app/pages/client/ClientNonUIFeatures.tsx +++ b/overlay/src/app/pages/client/ClientNonUIFeatures.tsx @@ -35,9 +35,11 @@ import { isTauri, isElectron, isCapacitorNative, + applyAndroidTypographyFix, sendNotification, setupNotificationTapListener, } from '../../utils/tauri'; +import '../../styles/android-typography.css'; import { setPaarrotNavigate, initPaarrotAPI } from '../../paarrot-api'; import { startBackgroundSync, @@ -84,6 +86,14 @@ function EmojiStyleFeature() { return null; } +function AndroidTypographyFeature() { + useEffect(() => { + applyAndroidTypographyFix(); + }, []); + + return null; +} + function PageZoomFeature() { const [pageZoom] = useSetting(settingsAtom, 'pageZoom'); @@ -684,6 +694,7 @@ export function ClientNonUIFeatures({ children }: ClientNonUIFeaturesProps) { return ( <> + diff --git a/overlay/src/app/styles/android-typography.css b/overlay/src/app/styles/android-typography.css new file mode 100644 index 0000000..0d3ef4f --- /dev/null +++ b/overlay/src/app/styles/android-typography.css @@ -0,0 +1,8 @@ +/* + * Android WebView renders Inter Variable with broken word/letter spacing. + * Applied when html.android-capacitor is set (see applyAndroidTypographyFix). + */ +html.android-capacitor body { + letter-spacing: normal; + word-spacing: normal; +} diff --git a/overlay/src/app/utils/tauri.ts b/overlay/src/app/utils/tauri.ts index c8f3545..9d7f05d 100644 --- a/overlay/src/app/utils/tauri.ts +++ b/overlay/src/app/utils/tauri.ts @@ -248,6 +248,41 @@ export const isAndroid = (): boolean => { return ua.includes('android'); }; +/** Returns true when running as a Capacitor app on Android. */ +export const isCapacitorAndroid = (): boolean => { + if (typeof window === 'undefined') return false; + const cap = (window as { Capacitor?: { isNativePlatform?: () => boolean; getPlatform?: () => string } }).Capacitor; + return Boolean(cap?.isNativePlatform?.() && cap?.getPlatform?.() === 'android'); +}; + +/** + * Android WebView mishandles Inter Variable — swap to static Inter / system fonts. + */ +export const applyAndroidTypographyFix = (): void => { + if (!isCapacitorAndroid()) return; + + const root = document.documentElement; + root.classList.add('android-capacitor'); + + void (async () => { + try { + await import('@fontsource/inter/400.css'); + await import('@fontsource/inter/500.css'); + await import('@fontsource/inter/600.css'); + root.style.setProperty( + '--font-secondary', + `'Inter', system-ui, Roboto, 'Noto Sans', var(--font-emoji), sans-serif` + ); + } catch (err) { + console.warn('[applyAndroidTypographyFix] Falling back to system font:', err); + root.style.setProperty( + '--font-secondary', + `system-ui, Roboto, 'Noto Sans', var(--font-emoji), sans-serif` + ); + } + })(); +}; + /** * Apply safe area insets for mobile devices * On Android, CSS env() may not work, so we apply fallback padding