Files
eranos/src/main.tsx
T
Chad Curtis e1348f782e refactor: deduplicate theme dark/light detection into colorUtils
Extract getBackgroundThemeMode() and getBackgroundHex() into colorUtils.ts,
replacing duplicated CSS variable reading and luminance calculations in
EmojiPicker, main.tsx status bar, and TweetEmbed. Also fixes TweetEmbed
incorrectly treating custom themes as always light.
2026-03-28 06:27:21 -05:00

82 lines
3.3 KiB
TypeScript

import { createRoot } from 'react-dom/client';
// Import polyfills first
import './lib/polyfills.ts';
// Kick off cache hydration early so data is ready before components render.
import { hydrateNip05Cache } from '@/lib/nip05Cache';
import { hydrateProfileCache } from '@/lib/profileCache';
hydrateNip05Cache();
hydrateProfileCache();
import { ErrorBoundary } from '@/components/ErrorBoundary';
import App from './App.tsx';
import './index.css';
import '@fontsource-variable/inter';
// ─── Native status bar theming (Android APK / iOS) ───────────────────────────
// Keeps the OS top chrome in sync with the active app theme.
// Runs before React so the very first paint matches the persisted theme.
// Uses a MutationObserver so it reacts to all subsequent theme changes
// (class changes for builtin themes, style-content changes for custom themes).
import { Capacitor } from '@capacitor/core';
import { StatusBar, Style } from '@capacitor/status-bar';
import { getBackgroundThemeMode, getBackgroundHex } from '@/lib/colorUtils';
if (Capacitor.isNativePlatform()) {
/**
* Read --background from the computed style of <html>, convert the HSL
* value to a hex color, and update the native status bar to match.
*
* Style.Dark = light/white icons (use on dark backgrounds)
* Style.Light = dark/black icons (use on light backgrounds)
*/
function updateStatusBar() {
const hex = getBackgroundHex();
if (!hex) return;
const isDark = getBackgroundThemeMode() === 'dark';
StatusBar.setStyle({ style: isDark ? Style.Dark : Style.Light }).catch(() => {});
StatusBar.setBackgroundColor({ color: hex }).catch(() => {});
}
// Apply immediately (theme class is set synchronously by AppProvider useLayoutEffect
// before the first React paint, but we still try early in case it's already set).
updateStatusBar();
// Re-apply whenever the theme class changes on <html> (light / dark / custom)
const classObserver = new MutationObserver(() => updateStatusBar());
classObserver.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class'],
});
// Re-apply whenever the injected <style id="theme-vars"> content changes
// (covers custom themes that change CSS variables without changing the class).
const styleObserver = new MutationObserver(() => updateStatusBar());
const observeThemeVars = () => {
const el = document.getElementById('theme-vars');
if (el) {
styleObserver.observe(el, { characterData: true, childList: true, subtree: true });
}
};
// The style element may not exist yet — watch <head> for it to appear.
observeThemeVars();
const headObserver = new MutationObserver(() => observeThemeVars());
headObserver.observe(document.head, { childList: true });
}
// ─────────────────────────────────────────────────────────────────────────────
createRoot(document.getElementById("root")!).render(
<ErrorBoundary>
<App />
</ErrorBoundary>
);
// Remove the HTML preloader after React has painted.
requestAnimationFrame(() => {
document.getElementById('preloader')?.remove();
});