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