diff --git a/app/(app)/[appName]/[objectName]/index.tsx b/app/(app)/[appName]/[objectName]/index.tsx
index 087d78f..c040c79 100644
--- a/app/(app)/[appName]/[objectName]/index.tsx
+++ b/app/(app)/[appName]/[objectName]/index.tsx
@@ -12,6 +12,7 @@ import { ScreenHeader } from "~/components/common/ScreenHeader";
import { useToast } from "~/components/ui/Toast";
import { useConfirm } from "~/components/ui/ConfirmDialog";
import { useObjectMeta } from "~/hooks/useObjectMeta";
+import { useThemeColors } from "~/lib/theme-colors";
export default function ObjectListScreen() {
const { appName, objectName } = useLocalSearchParams<{
@@ -21,6 +22,7 @@ export default function ObjectListScreen() {
const client = useClient();
const router = useRouter();
const { t } = useTranslation();
+ const { accent } = useThemeColors();
const { toastError } = useToast();
const confirm = useConfirm();
@@ -100,7 +102,7 @@ export default function ObjectListScreen() {
accessibilityLabel={t("records.createNamed", { name: displayName })}
className="h-10 w-10 items-center justify-center rounded-full active:bg-muted"
>
-
+
}
/>
diff --git a/app/(app)/[appName]/index.tsx b/app/(app)/[appName]/index.tsx
index 927f1d1..399bce3 100644
--- a/app/(app)/[appName]/index.tsx
+++ b/app/(app)/[appName]/index.tsx
@@ -8,6 +8,7 @@ import { ListSkeleton } from "~/components/ui/ListSkeleton";
import { ScreenHeader } from "~/components/common/ScreenHeader";
import { useApp, type NavigationItem } from "~/hooks/useApps";
import { getIcon } from "~/lib/getIcon";
+import { useThemeColors } from "~/lib/theme-colors";
/**
* App home — renders the app's curated navigation tree (the same
@@ -19,6 +20,7 @@ import { getIcon } from "~/lib/getIcon";
export default function AppHomeScreen() {
const { appName } = useLocalSearchParams<{ appName: string }>();
const router = useRouter();
+ const { accent } = useThemeColors();
const { app, isLoading, error } = useApp(appName);
const displayName =
@@ -71,7 +73,7 @@ export default function AppHomeScreen() {
accessibilityLabel={item.label}
>
-
+
{item.label}
diff --git a/app/(app)/packages.tsx b/app/(app)/packages.tsx
index f96f2c1..fd92933 100644
--- a/app/(app)/packages.tsx
+++ b/app/(app)/packages.tsx
@@ -14,6 +14,7 @@ import { ScreenHeader } from "~/components/common/ScreenHeader";
import { EmptyState } from "~/components/ui/EmptyState";
import { ListSkeleton } from "~/components/ui/ListSkeleton";
import { useConfirm } from "~/components/ui/ConfirmDialog";
+import { useThemeColors } from "~/lib/theme-colors";
/**
* Package management screen – list, enable, disable, uninstall packages.
@@ -22,6 +23,7 @@ import { useConfirm } from "~/components/ui/ConfirmDialog";
*/
export default function PackagesScreen() {
const { t } = useTranslation();
+ const { accent } = useThemeColors();
const { packages, isLoading, error, refetch, enable, disable, uninstall } =
usePackageManagement();
const confirm = useConfirm();
@@ -87,7 +89,7 @@ export default function PackagesScreen() {
-
+
{pkg.label}
diff --git a/app/(tabs)/apps.tsx b/app/(tabs)/apps.tsx
index bedb71a..2fbfcbd 100644
--- a/app/(tabs)/apps.tsx
+++ b/app/(tabs)/apps.tsx
@@ -12,11 +12,13 @@ import { useApps } from "~/hooks/useApps";
import { getIcon } from "~/lib/getIcon";
import { getUserErrorMessage } from "~/lib/error-handling";
import { tCount } from "~/lib/i18n";
+import { useThemeColors } from "~/lib/theme-colors";
export default function AppsScreen() {
const { apps, isLoading, error, refetch } = useApps();
const router = useRouter();
const { t } = useTranslation();
+ const { accent } = useThemeColors();
const [isRefreshing, setIsRefreshing] = useState(false);
const handleAppPress = (appName: string) => {
@@ -85,7 +87,7 @@ export default function AppsScreen() {
accessibilityLabel={t("apps.openA11y", { name: app.label })}
>
-
+
diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx
index 0ed59e5..029d088 100644
--- a/app/(tabs)/index.tsx
+++ b/app/(tabs)/index.tsx
@@ -129,7 +129,7 @@ export default function HomeScreen() {
contentContainerStyle={webContentMaxWidth}
showsVerticalScrollIndicator={false}
refreshControl={
-
+
}
>
@@ -159,7 +159,7 @@ export default function HomeScreen() {
accessibilityLabel={t("home.assistantTitle")}
>
-
+
@@ -263,7 +263,7 @@ export default function HomeScreen() {
accessibilityLabel={`Open ${d.label} dashboard`}
>
-
+
diff --git a/app/(tabs)/search.tsx b/app/(tabs)/search.tsx
index 6745537..af0d06e 100644
--- a/app/(tabs)/search.tsx
+++ b/app/(tabs)/search.tsx
@@ -10,10 +10,12 @@ import { Input } from "~/components/ui/Input";
import { EmptyState } from "~/components/ui/EmptyState";
import { useGlobalSearch } from "~/hooks/useGlobalSearch";
import { useSearchHistoryStore } from "~/stores/search-history-store";
+import { useThemeColors } from "~/lib/theme-colors";
export default function SearchScreen() {
const router = useRouter();
const { t } = useTranslation();
+ const { accent } = useThemeColors();
const { query, setQuery, groups, isSearching, hasSearched, totalCount, objectCount } =
useGlobalSearch();
const recentQueries = useSearchHistoryStore((s) => s.queries);
@@ -162,7 +164,7 @@ export default function SearchScreen() {
accessibilityLabel={t("search.openLabel", { title: rec.title })}
>
-
+
diff --git a/components/batch/BatchActionBar.tsx b/components/batch/BatchActionBar.tsx
index ecc1174..a536dae 100644
--- a/components/batch/BatchActionBar.tsx
+++ b/components/batch/BatchActionBar.tsx
@@ -2,6 +2,7 @@ import React from "react";
import { View, Text, Pressable } from "react-native";
import { Trash2, Edit3, X } from "lucide-react-native";
import { cn } from "~/lib/utils";
+import { useThemeColors } from "~/lib/theme-colors";
export interface BatchActionBarProps {
/** Number of selected records */
@@ -25,6 +26,7 @@ export function BatchActionBar({
onClearSelection,
className,
}: BatchActionBarProps) {
+ const { accent } = useThemeColors();
if (selectedCount === 0) return null;
return (
@@ -49,7 +51,7 @@ export function BatchActionBar({
onPress={onBatchEdit}
className="flex-row items-center rounded-lg bg-primary/10 px-3 py-2"
>
-
+
Edit
)}
diff --git a/components/common/LanguageSelector.tsx b/components/common/LanguageSelector.tsx
index 53bdf12..67a53cf 100644
--- a/components/common/LanguageSelector.tsx
+++ b/components/common/LanguageSelector.tsx
@@ -5,6 +5,7 @@ import { Check } from "lucide-react-native";
import * as Haptics from "expo-haptics";
import { SUPPORTED_LANGUAGES } from "~/lib/i18n";
import { useUIStore } from "~/stores/ui-store";
+import { useThemeColors } from "~/lib/theme-colors";
import { cn } from "~/lib/utils";
/**
@@ -13,6 +14,7 @@ import { cn } from "~/lib/utils";
*/
export function LanguageSelector({ className }: { className?: string }) {
const { t } = useTranslation();
+ const { accent } = useThemeColors();
const language = useUIStore((s) => s.language);
const setLanguage = useUIStore((s) => s.setLanguage);
@@ -48,7 +50,7 @@ export function LanguageSelector({ className }: { className?: string }) {
>
{lang.label}
- {isActive && }
+ {isActive && }
);
})}
diff --git a/components/common/OfflineIndicator.tsx b/components/common/OfflineIndicator.tsx
index b5324c5..eebe084 100644
--- a/components/common/OfflineIndicator.tsx
+++ b/components/common/OfflineIndicator.tsx
@@ -2,6 +2,7 @@ import React from "react";
import { View, Text, Pressable } from "react-native";
import { WifiOff, RefreshCw } from "lucide-react-native";
import { cn } from "~/lib/utils";
+import { useThemeColors } from "~/lib/theme-colors";
export interface OfflineIndicatorProps {
isOffline: boolean;
@@ -22,6 +23,7 @@ export function OfflineIndicator({
onSyncPress,
className,
}: OfflineIndicatorProps) {
+ const { accent } = useThemeColors();
if (!isOffline && pendingCount === 0) return null;
return (
@@ -52,7 +54,7 @@ export function OfflineIndicator({
disabled={isSyncing}
className="flex-row items-center rounded-lg bg-primary/10 px-2.5 py-1"
>
-
+
{isSyncing ? "Syncing…" : "Sync"}
diff --git a/components/common/ScreenHeader.tsx b/components/common/ScreenHeader.tsx
index 4029d92..9271b55 100644
--- a/components/common/ScreenHeader.tsx
+++ b/components/common/ScreenHeader.tsx
@@ -3,6 +3,7 @@ import { View, Text, Pressable } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { ChevronLeft } from "lucide-react-native";
import { useRouter } from "expo-router";
+import { useThemeColors } from "~/lib/theme-colors";
export interface ScreenHeaderProps {
/** Primary title shown in the bar. */
@@ -44,6 +45,7 @@ export function ScreenHeader({
}: ScreenHeaderProps) {
const router = useRouter();
const insets = useSafeAreaInsets();
+ const { accent } = useThemeColors();
const handleBack = () => {
if (onBack) {
@@ -74,7 +76,7 @@ export function ScreenHeader({
accessibilityLabel="Go back"
className="h-11 w-11 items-center justify-center rounded-full active:bg-muted"
>
-
+
)}
diff --git a/components/common/ThemeSelector.tsx b/components/common/ThemeSelector.tsx
index 8ec9a51..307a064 100644
--- a/components/common/ThemeSelector.tsx
+++ b/components/common/ThemeSelector.tsx
@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
import { Check, Sun, Moon, SmartphoneNfc } from "lucide-react-native";
import * as Haptics from "expo-haptics";
import { useUIStore, type ThemeMode } from "~/stores/ui-store";
+import { useThemeColors } from "~/lib/theme-colors";
import { cn } from "~/lib/utils";
const OPTIONS: { mode: ThemeMode; icon: typeof Sun; labelKey: string }[] = [
@@ -19,6 +20,7 @@ const OPTIONS: { mode: ThemeMode; icon: typeof Sun; labelKey: string }[] = [
*/
export function ThemeSelector({ className }: { className?: string }) {
const { t } = useTranslation();
+ const { accent } = useThemeColors();
const theme = useUIStore((s) => s.theme);
const setTheme = useUIStore((s) => s.setTheme);
@@ -47,7 +49,7 @@ export function ThemeSelector({ className }: { className?: string }) {
}}
>
-
+
- {isActive && }
+ {isActive && }
);
})}
diff --git a/components/query/GlobalSearch.tsx b/components/query/GlobalSearch.tsx
index 094a5bc..0492d6c 100644
--- a/components/query/GlobalSearch.tsx
+++ b/components/query/GlobalSearch.tsx
@@ -2,6 +2,7 @@ import React from "react";
import { View, TextInput } from "react-native";
import { Search } from "lucide-react-native";
import { cn } from "~/lib/utils";
+import { useThemeColors } from "~/lib/theme-colors";
export interface GlobalSearchProps {
value: string;
@@ -20,6 +21,7 @@ export function GlobalSearch({
placeholder = "Search across all fields…",
className,
}: GlobalSearchProps) {
+ const { accent } = useThemeColors();
return (
-
+
{
const firstField = fields[0]?.name ?? "";
onAddFilter(firstField, "eq");
@@ -56,7 +58,7 @@ export function QueryBuilder({
onPress={onToggleLogic}
className="flex-row items-center rounded-lg border border-primary/30 bg-primary/10 px-3 py-1.5"
>
-
+
Match {root.logic === "AND" ? "ALL" : "ANY"}
diff --git a/components/renderers/CalendarViewRenderer.tsx b/components/renderers/CalendarViewRenderer.tsx
index 26e52de..371d1d4 100644
--- a/components/renderers/CalendarViewRenderer.tsx
+++ b/components/renderers/CalendarViewRenderer.tsx
@@ -6,6 +6,7 @@ import {
Pressable,
} from "react-native";
import { ChevronLeft, ChevronRight } from "lucide-react-native";
+import { useThemeColors } from "~/lib/theme-colors";
import { Skeleton } from "~/components/ui/Skeleton";
import { cn } from "~/lib/utils";
@@ -112,6 +113,7 @@ export function CalendarViewRenderer({
initialYear,
initialMonth,
}: CalendarViewRendererProps) {
+ const { accent } = useThemeColors();
const now = new Date();
const [year, setYear] = useState(initialYear ?? now.getFullYear());
const [month, setMonth] = useState(initialMonth ?? now.getMonth());
@@ -183,13 +185,13 @@ export function CalendarViewRenderer({
{/* Month navigation */}
-
+
{MONTH_NAMES[month]} {year}
-
+
diff --git a/components/renderers/ChartViewRenderer.tsx b/components/renderers/ChartViewRenderer.tsx
index e0bddd5..67c6319 100644
--- a/components/renderers/ChartViewRenderer.tsx
+++ b/components/renderers/ChartViewRenderer.tsx
@@ -1,6 +1,7 @@
import React, { useMemo } from "react";
import { View, Text, ScrollView } from "react-native";
import { BarChart3, TrendingUp, PieChart, Activity, AlertCircle } from "lucide-react-native";
+import { useThemeColors } from "~/lib/theme-colors";
import { Card, CardHeader, CardTitle, CardContent } from "~/components/ui/Card";
import { Skeleton } from "~/components/ui/Skeleton";
import { EmptyState } from "~/components/ui/EmptyState";
@@ -32,15 +33,16 @@ export interface ChartViewRendererProps {
/* ------------------------------------------------------------------ */
function ChartIcon({ type }: { type: ChartType }) {
+ const { accent } = useThemeColors();
switch (type) {
case "line":
- return ;
+ return ;
case "pie":
- return ;
+ return ;
case "funnel":
- return ;
+ return ;
default:
- return ;
+ return ;
}
}
diff --git a/components/renderers/DashboardViewRenderer.tsx b/components/renderers/DashboardViewRenderer.tsx
index 3da59a9..0da4dd3 100644
--- a/components/renderers/DashboardViewRenderer.tsx
+++ b/components/renderers/DashboardViewRenderer.tsx
@@ -12,6 +12,7 @@ import { Card, CardHeader, CardTitle, CardContent } from "~/components/ui/Card";
import { Skeleton } from "~/components/ui/Skeleton";
import { WidgetChart } from "./charts/WidgetChart";
import { formatByPattern, formatCurrency, formatNumber } from "~/lib/formatting";
+import { useThemeColors } from "~/lib/theme-colors";
import type { DashboardMeta, DashboardWidgetMeta } from "./types";
/** Skeleton grid shown while dashboard metadata + widget data load. */
@@ -90,6 +91,7 @@ function MetricWidget({
widget: DashboardWidgetMeta;
data?: WidgetDataPayload;
}) {
+ const { accent } = useThemeColors();
const value = data?.value ?? "—";
const trend = data?.trend;
const isPositive = trend?.startsWith("+");
@@ -107,7 +109,7 @@ function MetricWidget({
{widget.title ?? widget.name}
-
+
{data?.isLoading ? (
@@ -183,6 +185,7 @@ function ListWidget({
widget: DashboardWidgetMeta;
data?: WidgetDataPayload;
}) {
+ const { accent } = useThemeColors();
const records = data?.records ?? [];
return (
@@ -192,7 +195,7 @@ function ListWidget({
{widget.title ?? widget.name}
-
+
@@ -240,6 +243,7 @@ function ChartWidget({
widget: DashboardWidgetMeta;
data?: WidgetDataPayload;
}) {
+ const { accent } = useThemeColors();
const chartType = String(widget.chartConfig?.type ?? widget.type ?? "bar");
const colors = Array.isArray(widget.chartConfig?.colors)
? (widget.chartConfig?.colors as string[])
@@ -254,7 +258,7 @@ function ChartWidget({
{widget.title ?? widget.name}
-
+
diff --git a/components/renderers/FilterDrawer.tsx b/components/renderers/FilterDrawer.tsx
index e6ac299..172f620 100644
--- a/components/renderers/FilterDrawer.tsx
+++ b/components/renderers/FilterDrawer.tsx
@@ -2,6 +2,7 @@ import React, { useCallback } from "react";
import { View, Text, Pressable, Modal } from "react-native";
import { Filter, X } from "lucide-react-native";
import { cn } from "~/lib/utils";
+import { useThemeColors } from "~/lib/theme-colors";
import { QueryBuilder } from "~/components/query";
import { useQueryBuilder } from "~/hooks/useQueryBuilder";
import type { FieldDefinition } from "~/components/renderers/types";
@@ -46,6 +47,8 @@ export function FilterDrawer({
hasFilters: _hasFilters,
} = useQueryBuilder();
+ const { accent } = useThemeColors();
+
/* ---- Active filter count ---- */
const filterCount = root.filters.length;
@@ -73,7 +76,7 @@ export function FilterDrawer({
{/* ---- Header ---- */}
-
+
Filters
diff --git a/components/renderers/MapViewRenderer.tsx b/components/renderers/MapViewRenderer.tsx
index 7649560..d41f3cc 100644
--- a/components/renderers/MapViewRenderer.tsx
+++ b/components/renderers/MapViewRenderer.tsx
@@ -1,6 +1,7 @@
import React from "react";
import { View, Text, ScrollView, Pressable } from "react-native";
import { MapPin, Navigation } from "lucide-react-native";
+import { useThemeColors } from "~/lib/theme-colors";
import { Card, CardContent } from "~/components/ui/Card";
import { ListSkeleton } from "~/components/ui/ListSkeleton";
@@ -95,6 +96,7 @@ export function MapViewRenderer({
isLoading = false,
onMarkerPress,
}: MapViewRendererProps) {
+ const { accent } = useThemeColors();
if (isLoading) {
return (
@@ -122,7 +124,7 @@ export function MapViewRenderer({
>
{/* Summary header */}
-
+
{markers.length} location{markers.length !== 1 ? "s" : ""}
@@ -137,7 +139,7 @@ export function MapViewRenderer({
-
+
diff --git a/components/renderers/ReportRenderer.tsx b/components/renderers/ReportRenderer.tsx
index e02a353..df529b6 100644
--- a/components/renderers/ReportRenderer.tsx
+++ b/components/renderers/ReportRenderer.tsx
@@ -1,6 +1,7 @@
import React, { useMemo } from "react";
import { View, Text, ScrollView } from "react-native";
import { FileText, AlertCircle } from "lucide-react-native";
+import { useThemeColors } from "~/lib/theme-colors";
import { Card, CardHeader, CardTitle, CardContent } from "~/components/ui/Card";
import { Skeleton } from "~/components/ui/Skeleton";
import { EmptyState } from "~/components/ui/EmptyState";
@@ -307,6 +308,7 @@ export function ReportRenderer({
isLoading,
error,
}: ReportRendererProps) {
+ const { accent } = useThemeColors();
if (isLoading) {
return (
@@ -357,7 +359,7 @@ export function ReportRenderer({
-
+
{title ?? "Report"}
diff --git a/components/renderers/ViewRenderer.tsx b/components/renderers/ViewRenderer.tsx
index c5bab84..96076e6 100644
--- a/components/renderers/ViewRenderer.tsx
+++ b/components/renderers/ViewRenderer.tsx
@@ -1,5 +1,6 @@
import React, { Suspense } from "react";
import { View, Text, ActivityIndicator } from "react-native";
+import { useThemeColors } from "~/lib/theme-colors";
import { ListViewRenderer, type ListViewRendererProps } from "./ListViewRenderer";
import { FormViewRenderer, type FormViewRendererProps } from "./FormViewRenderer";
import { DetailViewRenderer, type DetailViewRendererProps } from "./DetailViewRenderer";
@@ -55,9 +56,10 @@ const LazyGantt = React.lazy(() =>
/* ------------------------------------------------------------------ */
function RendererFallback() {
+ const { accent } = useThemeColors();
return (
-
+
);
}
diff --git a/components/renderers/fields/FileField.tsx b/components/renderers/fields/FileField.tsx
index 5f8959f..39a01c4 100644
--- a/components/renderers/fields/FileField.tsx
+++ b/components/renderers/fields/FileField.tsx
@@ -12,6 +12,7 @@ import {
import type { FieldDefinition } from "../types";
import type { FileUploadResult } from "~/hooks/useFileUpload";
import { useToast } from "~/components/ui/Toast";
+import { useThemeColors } from "~/lib/theme-colors";
/* ------------------------------------------------------------------ */
/* Types */
@@ -82,6 +83,7 @@ export function FileField({
}: FileFieldProps) {
const [isUploading, setIsUploading] = useState(false);
const { toastError } = useToast();
+ const { accent } = useThemeColors();
const fileInfo = resolveFileInfo(value);
const handleUpload = useCallback(
@@ -118,12 +120,12 @@ export function FileField({
{onDownload && fileInfo.id && (
onDownload(fileInfo.id!, fileInfo.name ?? "file")}>
-
+
)}
{onShare && fileInfo.id && (
onShare(fileInfo.id!, fileInfo.name ?? "file")}>
-
+
)}
@@ -149,7 +151,7 @@ export function FileField({
{/* Upload buttons */}
{isUploading ? (
-
+
Uploading…
) : (
diff --git a/components/sync/ConflictResolutionDialog.tsx b/components/sync/ConflictResolutionDialog.tsx
index 8a68708..7738969 100644
--- a/components/sync/ConflictResolutionDialog.tsx
+++ b/components/sync/ConflictResolutionDialog.tsx
@@ -2,6 +2,7 @@ import React from "react";
import { View, Text, Pressable, ScrollView, Modal } from "react-native";
import { AlertTriangle, RefreshCw, Trash2, X } from "lucide-react-native";
import type { SyncQueueEntry } from "~/lib/sync-queue";
+import { useThemeColors } from "~/lib/theme-colors";
/* ------------------------------------------------------------------ */
/* Props */
@@ -32,6 +33,7 @@ export function ConflictResolutionDialog({
onKeepServer,
onClose,
}: ConflictResolutionDialogProps) {
+ const { accent } = useThemeColors();
return (
@@ -76,7 +78,7 @@ export function ConflictResolutionDialog({
onPress={() => onKeepLocal(entry.id)}
className="flex-1 flex-row items-center justify-center rounded-lg bg-primary/10 py-2"
>
-
+
Keep Local
diff --git a/components/ui/MarkdownText.tsx b/components/ui/MarkdownText.tsx
index d73cea1..5d4eb1b 100644
--- a/components/ui/MarkdownText.tsx
+++ b/components/ui/MarkdownText.tsx
@@ -1,5 +1,6 @@
import React, { useMemo } from "react";
import { Linking, StyleSheet } from "react-native";
+import { useColorScheme } from "nativewind";
import Markdown from "react-native-markdown-display";
/**
@@ -7,11 +8,16 @@ import Markdown from "react-native-markdown-display";
* tables) instead of plain text. LLM replies are markdown-heavy, so showing
* them raw leaks `**`, `-`, and backticks into the UI.
*
- * Styled to match the app's light surfaces (card foreground text, primary
- * links, muted code blocks). Pure-JS renderer — works on native and web.
+ * Theme-aware: the styles were hardcoded to light surfaces, which left the
+ * body text near-black (and code blocks pale) — unreadable once dark mode
+ * shipped. Pure-JS renderer — works on native and web.
*/
export function MarkdownText({ children }: { children: string }) {
- const styles = useMemo(() => markdownStyles, []);
+ const { colorScheme } = useColorScheme();
+ const styles = useMemo(
+ () => buildMarkdownStyles(colorScheme === "dark"),
+ [colorScheme],
+ );
return (
-
+
) : (
-
+
)}
);
diff --git a/lib/theme-colors.ts b/lib/theme-colors.ts
new file mode 100644
index 0000000..481dc06
--- /dev/null
+++ b/lib/theme-colors.ts
@@ -0,0 +1,37 @@
+import { useColorScheme } from "nativewind";
+
+/**
+ * Theme-aware colors for places that need a concrete hex rather than a Tailwind
+ * class — chiefly lucide icons (`color` prop) and a few inline styles. Mirrors
+ * the `global.css` tokens closely enough that icons read correctly in both
+ * light and dark mode, where a hardcoded `#1e40af` (too dark) or `#0f172a`
+ * (invisible on a dark card) would otherwise fail.
+ */
+export interface ThemeColors {
+ /** Brand/primary accent — for active icons, links, primary affordances. */
+ accent: string;
+ /** Default foreground — for neutral icons on cards/surfaces. */
+ foreground: string;
+ /** Muted foreground — secondary icons, chevrons, hints. */
+ muted: string;
+}
+
+const LIGHT: ThemeColors = {
+ accent: "#1e40af",
+ foreground: "#0f172a",
+ muted: "#94a3b8",
+};
+
+const DARK: ThemeColors = {
+ accent: "#60a5fa",
+ foreground: "#e2e8f0",
+ // Slate-400 reads acceptably on both themes; keep it stable so muted icons
+ // don't shift between light and dark.
+ muted: "#94a3b8",
+};
+
+/** Hook: theme-aware icon/inline colors for the current color scheme. */
+export function useThemeColors(): ThemeColors {
+ const { colorScheme } = useColorScheme();
+ return colorScheme === "dark" ? DARK : LIGHT;
+}