From ea938e56443c05d4b4e900cf2084fda2f0ba1dd6 Mon Sep 17 00:00:00 2001 From: Davide Scaini Date: Wed, 3 Jun 2026 09:50:05 +0200 Subject: [PATCH] design: align visual style with bincio_autarchive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add src/theme.ts with centralised color palette - Backgrounds: #111 → #09090b, surfaces #1e1e1e → #18181b - All cards get 1px #27272a borders (matches autarchive cards) - Text: #fff/#888/#555 → #f4f4f5/#a1a1aa/#71717a - Accent: #3b82f6 → #60a5fa (autarchive default palette) - Tab icons: colored emoji → monochromatic Unicode (◉ ☰ ⚙) - Action buttons use muted palette (#16a34a / #d97706 / #dc2626) - Keep-awake toggle uses ◑/◌ symbols, border highlights accent on active - Connect/Scan buttons match autarchive surface+border style --- src/navigation/AppNavigator.tsx | 48 ++++++++++----------- src/screens/PostRecordingScreen.tsx | 29 +++++++------ src/screens/RecordingScreen.tsx | 58 ++++++++++++------------- src/screens/SavedRecordingsScreen.tsx | 29 +++++++------ src/screens/SensorPairingScreen.tsx | 62 +++++++++++++-------------- src/screens/SettingsScreen.tsx | 52 ++++++++++++---------- src/theme.ts | 24 +++++++++++ 7 files changed, 165 insertions(+), 137 deletions(-) create mode 100644 src/theme.ts diff --git a/src/navigation/AppNavigator.tsx b/src/navigation/AppNavigator.tsx index d0893e4..8375a48 100644 --- a/src/navigation/AppNavigator.tsx +++ b/src/navigation/AppNavigator.tsx @@ -10,36 +10,34 @@ import { SensorPairingScreen } from '../screens/SensorPairingScreen'; import { SavedRecordingsScreen } from '../screens/SavedRecordingsScreen'; import { SettingsScreen } from '../screens/SettingsScreen'; import { RootStackParamList, TabParamList } from '../types'; +import { colors } from '../theme'; const Stack = createNativeStackNavigator(); const Tab = createBottomTabNavigator(); +const TAB_ICONS: Record = { + Recording: '◉', + Saved: '☰', + Settings: '⚙', +}; + function Tabs() { return ( ({ + headerStyle: { backgroundColor: colors.bg }, + headerTintColor: colors.text, + tabBarStyle: { backgroundColor: colors.surface, borderTopColor: colors.border }, + tabBarActiveTintColor: colors.accent, + tabBarInactiveTintColor: colors.textMuted, + tabBarIcon: ({ color }) => ( + {TAB_ICONS[route.name]} + ), + })} > - }} - /> - 📋 }} - /> - ⚙️ }} - /> + + + ); } @@ -49,9 +47,9 @@ export function AppNavigator() { diff --git a/src/screens/PostRecordingScreen.tsx b/src/screens/PostRecordingScreen.tsx index 11255bc..2657556 100644 --- a/src/screens/PostRecordingScreen.tsx +++ b/src/screens/PostRecordingScreen.tsx @@ -6,6 +6,7 @@ import { saveGpx } from '../services/gpx'; import { insertRecording } from '../services/db'; import { SavedRecording } from '../types'; import { randomUUID } from 'expo-crypto'; +import { colors } from '../theme'; export function PostRecordingScreen() { const nav = useNavigation(); @@ -65,7 +66,7 @@ export function PostRecordingScreen() { {saving ? 'Saving…' : 'Save'} - Discard + Discard ); @@ -91,16 +92,16 @@ function Stat({ label, value }: { label: string; value: string }) { } const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: '#111' }, - content: { padding: 24, gap: 16 }, - heading: { color: '#fff', fontSize: 24, fontWeight: '700', marginBottom: 8 }, - statsRow: { flexDirection: 'row', flexWrap: 'wrap', gap: 12, marginBottom: 8 }, - stat: { flex: 1, minWidth: '40%', backgroundColor: '#1e1e1e', borderRadius: 12, padding: 16 }, - statLabel: { color: '#888', fontSize: 12, textTransform: 'uppercase' }, - statValue: { color: '#fff', fontSize: 22, fontWeight: '600', marginTop: 4 }, - input: { backgroundColor: '#1e1e1e', color: '#fff', borderRadius: 12, padding: 16, fontSize: 18 }, - btn: { borderRadius: 12, padding: 16, alignItems: 'center' }, - btnSave: { backgroundColor: '#22c55e' }, - btnDiscard: { backgroundColor: '#1e1e1e' }, - btnText: { fontSize: 16, fontWeight: '700', color: '#fff' }, + container: { flex: 1, backgroundColor: colors.bg }, + content: { padding: 24, gap: 12 }, + heading: { color: colors.text, fontSize: 22, fontWeight: '700', marginBottom: 4 }, + statsRow: { flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginBottom: 4 }, + stat: { flex: 1, minWidth: '40%', backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, borderRadius: 10, padding: 14 }, + statLabel: { color: colors.textMuted, fontSize: 11, textTransform: 'uppercase', letterSpacing: 0.5 }, + statValue: { color: colors.text, fontSize: 22, fontWeight: '700', marginTop: 4 }, + input: { backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, color: colors.text, borderRadius: 10, padding: 14, fontSize: 16 }, + btn: { borderRadius: 10, padding: 16, alignItems: 'center' }, + btnSave: { backgroundColor: colors.btnStart }, + btnDiscard:{ backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border }, + btnText: { fontSize: 16, fontWeight: '600', color: colors.text }, }); diff --git a/src/screens/RecordingScreen.tsx b/src/screens/RecordingScreen.tsx index 475503c..e207f83 100644 --- a/src/screens/RecordingScreen.tsx +++ b/src/screens/RecordingScreen.tsx @@ -7,12 +7,13 @@ import { Map, Camera, GeoJSONSource, Layer, UserLocation } from '@maplibre/mapli import type { LineLayerStyle } from '@maplibre/maplibre-react-native'; import { useRecordingStore } from '../store/recording'; import { startGpsRecording, stopGpsRecording, requestLocationPermissions } from '../services/gps'; -import { RootStackParamList, TrackPoint } from '../types'; +import { RootStackParamList } from '../types'; +import { colors } from '../theme'; const MAP_STYLE = 'https://tiles.openfreemap.org/styles/liberty'; const trackLineStyle: LineLayerStyle = { - lineColor: '#3b82f6', + lineColor: colors.accent, lineWidth: 3, lineJoin: 'round', lineCap: 'round', @@ -41,10 +42,7 @@ export function RecordingScreen() { const trackGeoJSON = useMemo>(() => ({ type: 'Feature', - geometry: { - type: 'LineString', - coordinates: trackPoints.map((p) => [p.lon, p.lat]), - }, + geometry: { type: 'LineString', coordinates: trackPoints.map((p) => [p.lon, p.lat]) }, properties: {}, }), [trackPoints]); @@ -84,14 +82,14 @@ export function RecordingScreen() { return ( - - - + + + - - - + + + @@ -118,7 +116,7 @@ export function RecordingScreen() { style={[styles.awakeBtn, keepAwake && styles.awakeBtnOn]} onPress={() => setKeepAwake(!keepAwake)} > - {keepAwake ? '☀️ Awake' : '💤 Sleep'} + {keepAwake ? '◑ Awake' : '◌ Sleep'} {status === 'idle' && ( @@ -161,21 +159,21 @@ function StatBox({ label, value }: { label: string; value: string }) { } const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: '#111' }, - statsGrid: { flexDirection: 'row', flexWrap: 'wrap', padding: 8 }, - statBox: { width: '25%', padding: 8, alignItems: 'center' }, - statLabel: { color: '#888', fontSize: 11, textTransform: 'uppercase' }, - statValue: { color: '#fff', fontSize: 18, fontWeight: '600', marginTop: 2 }, - mapArea: { flex: 1, overflow: 'hidden' }, - sensorBtn: { position: 'absolute', top: 10, right: 10, backgroundColor: 'rgba(17,17,17,0.85)', borderRadius: 8, paddingVertical: 6, paddingHorizontal: 12 }, - sensorBtnText: { color: '#3b82f6', fontSize: 13, fontWeight: '600' }, - controls: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 12, padding: 20 }, - awakeBtn: { backgroundColor: '#1e1e1e', borderRadius: 20, paddingVertical: 8, paddingHorizontal: 14 }, - awakeBtnOn: { backgroundColor: '#2a2a1a' }, - awakeBtnText: { color: '#aaa', fontSize: 13 }, - btn: { paddingVertical: 16, paddingHorizontal: 32, borderRadius: 50 }, - btnStart: { backgroundColor: '#22c55e' }, - btnPause: { backgroundColor: '#f59e0b' }, - btnStop: { backgroundColor: '#ef4444' }, - btnText: { color: '#fff', fontSize: 18, fontWeight: '700' }, + container: { flex: 1, backgroundColor: colors.bg }, + statsGrid: { flexDirection: 'row', flexWrap: 'wrap', padding: 8, borderBottomWidth: 1, borderBottomColor: colors.border }, + statBox: { width: '25%', padding: 8, alignItems: 'center' }, + statLabel: { color: colors.textMuted, fontSize: 10, textTransform: 'uppercase', letterSpacing: 0.5 }, + statValue: { color: colors.text, fontSize: 17, fontWeight: '600', marginTop: 2 }, + mapArea: { flex: 1, overflow: 'hidden' }, + sensorBtn: { position: 'absolute', top: 10, right: 10, backgroundColor: 'rgba(9,9,11,0.8)', borderRadius: 8, borderWidth: 1, borderColor: colors.border, paddingVertical: 6, paddingHorizontal: 12 }, + sensorBtnText: { color: colors.accent, fontSize: 13, fontWeight: '600' }, + controls: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 12, padding: 20, borderTopWidth: 1, borderTopColor: colors.border }, + awakeBtn: { backgroundColor: colors.surface, borderRadius: 20, borderWidth: 1, borderColor: colors.border, paddingVertical: 8, paddingHorizontal: 14 }, + awakeBtnOn: { borderColor: colors.accent }, + awakeBtnText: { color: colors.textSub, fontSize: 13 }, + btn: { paddingVertical: 15, paddingHorizontal: 30, borderRadius: 50 }, + btnStart: { backgroundColor: colors.btnStart }, + btnPause: { backgroundColor: colors.btnPause }, + btnStop: { backgroundColor: colors.btnStop }, + btnText: { color: '#fff', fontSize: 17, fontWeight: '700' }, }); diff --git a/src/screens/SavedRecordingsScreen.tsx b/src/screens/SavedRecordingsScreen.tsx index 04e311a..b4a8e81 100644 --- a/src/screens/SavedRecordingsScreen.tsx +++ b/src/screens/SavedRecordingsScreen.tsx @@ -6,6 +6,7 @@ import { listRecordings, deleteRecording } from '../services/db'; import { uploadGpx } from '../services/upload'; import { SavedRecording } from '../types'; import AsyncStorage from '@react-native-async-storage/async-storage'; +import { colors } from '../theme'; export function SavedRecordingsScreen() { const [recordings, setRecordings] = useState([]); @@ -24,9 +25,9 @@ export function SavedRecordingsScreen() { async function handleUpload(rec: SavedRecording) { const instanceUrl = await AsyncStorage.getItem('instanceUrl'); - const apiToken = await AsyncStorage.getItem('apiToken'); + const apiToken = await AsyncStorage.getItem('apiToken'); if (!instanceUrl || !apiToken) { - Alert.alert('Not configured', 'Set your bincio instance URL and API token in Settings.'); + Alert.alert('Not connected', 'Log in to your bincio instance in Settings first.'); return; } setUploading(rec.id); @@ -56,7 +57,7 @@ export function SavedRecordingsScreen() { return h > 0 ? `${h}h ${m}m` : `${m}m`; }; - if (loading) return ; + if (loading) return ; return ( @@ -80,7 +81,7 @@ export function SavedRecordingsScreen() { {uploading === item.id ? '…' : 'Upload'} handleDelete(item)}> - Delete + Delete @@ -92,14 +93,14 @@ export function SavedRecordingsScreen() { } const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: '#111' }, - center: { flex: 1, backgroundColor: '#111', alignItems: 'center', justifyContent: 'center' }, - list: { padding: 16, gap: 12 }, - card: { backgroundColor: '#1e1e1e', borderRadius: 12, padding: 16 }, - cardMeta: { marginBottom: 12 }, - cardTitle: { color: '#fff', fontSize: 17, fontWeight: '600' }, - cardSub: { color: '#888', fontSize: 13, marginTop: 4 }, - cardActions: { flexDirection: 'row', gap: 20 }, - action: { color: '#3b82f6', fontWeight: '600', fontSize: 15 }, - empty: { color: '#555', textAlign: 'center', marginTop: 60 }, + container: { flex: 1, backgroundColor: colors.bg }, + center: { flex: 1, backgroundColor: colors.bg, alignItems: 'center', justifyContent: 'center' }, + list: { padding: 16, gap: 10 }, + card: { backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, borderRadius: 12, padding: 16 }, + cardMeta: { marginBottom: 12 }, + cardTitle: { color: colors.text, fontSize: 15, fontWeight: '600' }, + cardSub: { color: colors.textMuted, fontSize: 12, marginTop: 4 }, + cardActions:{ flexDirection: 'row', gap: 20 }, + action: { color: colors.accent, fontWeight: '600', fontSize: 14 }, + empty: { color: colors.textMuted, textAlign: 'center', marginTop: 60 }, }); diff --git a/src/screens/SensorPairingScreen.tsx b/src/screens/SensorPairingScreen.tsx index 21c74da..db67785 100644 --- a/src/screens/SensorPairingScreen.tsx +++ b/src/screens/SensorPairingScreen.tsx @@ -8,10 +8,11 @@ import { subscribeForDevice, savePairedDevice, loadPairedDevices, removePairedDevice, } from '../services/ble'; import { BleDevice } from '../types'; +import { colors } from '../theme'; const TYPE_LABEL: Record = { - hr: 'Heart Rate', - power: 'Power Meter', + hr: 'Heart Rate', + power: 'Power Meter', cadence: 'Cadence', }; @@ -29,7 +30,6 @@ export function SensorPairingScreen() { const [connecting, setConnecting] = useState>({}); const stopScanRef = useRef<(() => void) | null>(null); - // Load saved devices and attempt reconnect on mount useEffect(() => { (async () => { const devices = await loadPairedDevices(); @@ -89,26 +89,25 @@ export function SensorPairingScreen() { setSaved((prev) => prev.filter((e) => e.device.id !== device.id)); } + const savedIds = new Set(saved.map((e) => e.device.id)); + const newFound = found.filter((d) => !savedIds.has(d.id)); + type ListItem = | { kind: 'header'; label: string } | { kind: 'saved'; entry: SavedEntry } | { kind: 'found'; device: BleDevice }; - const savedIds = new Set(saved.map((e) => e.device.id)); - const newFound = found.filter((d) => !savedIds.has(d.id)); - const listData: ListItem[] = [ - ...(saved.length > 0 ? [{ kind: 'header' as const, label: 'Saved sensors' }, ...saved.map((e) => ({ kind: 'saved' as const, entry: e }))] : []), + ...(saved.length > 0 ? [{ kind: 'header' as const, label: 'Saved sensors' }, ...saved.map((e) => ({ kind: 'saved' as const, entry: e }))] : []), ...(newFound.length > 0 ? [{ kind: 'header' as const, label: 'Found nearby' }, ...newFound.map((d) => ({ kind: 'found' as const, device: d }))] : []), ]; return ( - Sensors Pair your HR monitor, power meter, or cadence sensor. - {scanning ? : Scan for sensors} + {scanning ? : Scan for sensors} {TYPE_LABEL[entry.device.type]} - {entry.status === 'connecting' && } - {entry.status === 'connected' && Connected} + {entry.status === 'connecting' && } + {entry.status === 'connected' && Connected} {(entry.status === 'saved' || entry.status === 'error') && ( reconnect(entry.device, true)}> {entry.status === 'error' ? 'Retry' : 'Reconnect'} @@ -155,7 +154,9 @@ export function SensorPairingScreen() { onPress={() => handleConnect(device)} disabled={isConnecting} > - {isConnecting ? : Connect} + {isConnecting + ? + : Connect} ); @@ -169,23 +170,22 @@ export function SensorPairingScreen() { } const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: '#111', padding: 24 }, - heading: { color: '#fff', fontSize: 24, fontWeight: '700' }, - sub: { color: '#888', marginTop: 4, marginBottom: 20 }, - scanBtn: { backgroundColor: '#3b82f6', borderRadius: 12, padding: 14, alignItems: 'center', marginBottom: 8 }, - scanBtnText: { color: '#fff', fontSize: 16, fontWeight: '700' }, - list: { flex: 1, marginTop: 8 }, - sectionHeader: { color: '#555', fontSize: 12, textTransform: 'uppercase', letterSpacing: 0.8, marginTop: 16, marginBottom: 8 }, - deviceRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', backgroundColor: '#1e1e1e', borderRadius: 12, padding: 16, marginBottom: 8 }, - deviceInfo: { flex: 1, marginRight: 12 }, - deviceName: { color: '#fff', fontSize: 16, fontWeight: '600' }, - deviceType: { color: '#888', fontSize: 13, marginTop: 2 }, - deviceActions: { flexDirection: 'row', alignItems: 'center', gap: 12 }, - connectedLabel: { color: '#22c55e', fontWeight: '600', fontSize: 14 }, - reconnectBtn: { backgroundColor: '#1e3a5f', borderRadius: 8, paddingVertical: 7, paddingHorizontal: 12 }, - reconnectBtnText: { color: '#3b82f6', fontWeight: '600', fontSize: 14 }, - forgetLabel: { color: '#ef4444', fontSize: 14 }, - connectBtn: { backgroundColor: '#3b82f6', borderRadius: 8, paddingVertical: 8, paddingHorizontal: 16, minWidth: 80, alignItems: 'center' }, - connectBtnText: { color: '#fff', fontWeight: '600' }, - empty: { color: '#555', textAlign: 'center', marginTop: 40 }, + container: { flex: 1, backgroundColor: colors.bg, padding: 16 }, + sub: { color: colors.textMuted, fontSize: 13, marginBottom: 16 }, + scanBtn: { backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, borderRadius: 10, padding: 14, alignItems: 'center', marginBottom: 4 }, + scanBtnText: { color: colors.text, fontSize: 15, fontWeight: '600' }, + list: { flex: 1, marginTop: 4 }, + sectionHeader: { color: colors.textMuted, fontSize: 11, textTransform: 'uppercase', letterSpacing: 0.8, marginTop: 20, marginBottom: 8 }, + deviceRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, borderRadius: 10, padding: 14, marginBottom: 8 }, + deviceInfo: { flex: 1, marginRight: 12 }, + deviceName: { color: colors.text, fontSize: 15, fontWeight: '600' }, + deviceType: { color: colors.textMuted, fontSize: 12, marginTop: 2 }, + deviceActions: { flexDirection: 'row', alignItems: 'center', gap: 12 }, + connectedLabel: { color: colors.success, fontWeight: '600', fontSize: 13 }, + reconnectBtn: { backgroundColor: colors.accentDim, borderRadius: 8, paddingVertical: 6, paddingHorizontal: 12 }, + reconnectBtnText:{ color: colors.accent, fontWeight: '600', fontSize: 13 }, + forgetLabel: { color: colors.error, fontSize: 13 }, + connectBtn: { backgroundColor: colors.accentDim, borderRadius: 8, paddingVertical: 7, paddingHorizontal: 14, minWidth: 80, alignItems: 'center' }, + connectBtnText: { color: colors.accent, fontWeight: '600' }, + empty: { color: colors.textMuted, textAlign: 'center', marginTop: 40 }, }); diff --git a/src/screens/SettingsScreen.tsx b/src/screens/SettingsScreen.tsx index 433e201..e81db4f 100644 --- a/src/screens/SettingsScreen.tsx +++ b/src/screens/SettingsScreen.tsx @@ -5,6 +5,7 @@ import { } from 'react-native'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { login, logout, loadAuthState } from '../services/auth'; +import { colors } from '../theme'; export function SettingsScreen() { const [instanceUrl, setInstanceUrl] = useState(''); @@ -31,8 +32,8 @@ export function SettingsScreen() { async function handleConnect() { if (!instanceUrl.trim()) { Alert.alert('Required', 'Enter the instance URL.'); return; } - if (!handle.trim()) { Alert.alert('Required', 'Enter your handle.'); return; } - if (!password) { Alert.alert('Required', 'Enter your password.'); return; } + if (!handle.trim()) { Alert.alert('Required', 'Enter your handle.'); return; } + if (!password) { Alert.alert('Required', 'Enter your password.'); return; } setConnecting(true); const result = await login(instanceUrl.trim(), handle.trim(), password); @@ -77,7 +78,7 @@ export function SettingsScreen() { value={instanceUrl} onChangeText={setInstanceUrl} placeholder="https://bincio.example.com" - placeholderTextColor="#555" + placeholderTextColor={colors.placeholder} autoCapitalize="none" keyboardType="url" editable={!connectedAs} @@ -101,7 +102,7 @@ export function SettingsScreen() { value={handle} onChangeText={setHandle} placeholder="your-handle" - placeholderTextColor="#555" + placeholderTextColor={colors.placeholder} autoCapitalize="none" autoCorrect={false} /> @@ -112,7 +113,7 @@ export function SettingsScreen() { value={password} onChangeText={setPassword} placeholder="••••••••" - placeholderTextColor="#555" + placeholderTextColor={colors.placeholder} secureTextEntry /> @@ -126,7 +127,7 @@ export function SettingsScreen() { disabled={connecting} > {connecting - ? + ? : Connect} @@ -136,27 +137,32 @@ export function SettingsScreen() { Kilometre alerts - + ); } const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: '#111' }, - content: { padding: 24, gap: 12 }, - sectionTitle: { color: '#888', fontSize: 13, textTransform: 'uppercase', letterSpacing: 0.8, marginTop: 16 }, - label: { color: '#aaa', fontSize: 14, marginBottom: 4 }, - input: { backgroundColor: '#1e1e1e', color: '#fff', borderRadius: 10, padding: 14, fontSize: 16 }, - hint: { color: '#555', fontSize: 13, lineHeight: 18 }, - connectedBox: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', backgroundColor: '#1e1e1e', borderRadius: 10, padding: 14 }, - connectedLabel: { color: '#22c55e', fontSize: 12, textTransform: 'uppercase', letterSpacing: 0.6 }, - connectedName: { color: '#fff', fontSize: 16, fontWeight: '600', marginTop: 2 }, - disconnectBtn: { paddingVertical: 6, paddingHorizontal: 12, borderRadius: 8, borderWidth: 1, borderColor: '#ef4444' }, - disconnectBtnText: { color: '#ef4444', fontWeight: '600' }, - connectBtn: { backgroundColor: '#3b82f6', borderRadius: 12, padding: 16, alignItems: 'center', marginTop: 4 }, - connectBtnDisabled: { opacity: 0.6 }, - connectBtnText: { color: '#fff', fontSize: 16, fontWeight: '700' }, - row: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', backgroundColor: '#1e1e1e', borderRadius: 10, padding: 14 }, - rowLabel: { color: '#fff', fontSize: 16 }, + container: { flex: 1, backgroundColor: colors.bg }, + content: { padding: 16, gap: 10 }, + sectionTitle: { color: colors.textMuted, fontSize: 11, textTransform: 'uppercase', letterSpacing: 0.8, marginTop: 16 }, + label: { color: colors.textSub, fontSize: 13, marginBottom: 2 }, + input: { backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, color: colors.text, borderRadius: 8, padding: 12, fontSize: 15 }, + hint: { color: colors.textMuted, fontSize: 12, lineHeight: 18 }, + connectedBox: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, borderRadius: 10, padding: 14 }, + connectedLabel: { color: colors.success, fontSize: 11, textTransform: 'uppercase', letterSpacing: 0.6 }, + connectedName: { color: colors.text, fontSize: 15, fontWeight: '600', marginTop: 2 }, + disconnectBtn: { paddingVertical: 6, paddingHorizontal: 12, borderRadius: 8, borderWidth: 1, borderColor: colors.errorBg }, + disconnectBtnText: { color: colors.error, fontWeight: '600', fontSize: 13 }, + connectBtn: { backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, borderRadius: 10, padding: 14, alignItems: 'center', marginTop: 4 }, + connectBtnDisabled: { opacity: 0.5 }, + connectBtnText: { color: colors.text, fontSize: 15, fontWeight: '600' }, + row: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, borderRadius: 10, padding: 14 }, + rowLabel: { color: colors.text, fontSize: 15 }, }); diff --git a/src/theme.ts b/src/theme.ts new file mode 100644 index 0000000..0bbd59f --- /dev/null +++ b/src/theme.ts @@ -0,0 +1,24 @@ +export const colors = { + bg: '#09090b', + surface: '#18181b', + border: '#27272a', + borderStrong: '#3f3f46', + + text: '#f4f4f5', + textSub: '#a1a1aa', + textMuted: '#71717a', + placeholder: '#52525b', + + accent: '#60a5fa', + accentDim: 'rgba(96,165,250,0.15)', + + success: '#86efac', + successBg: '#14532d', + error: '#fca5a5', + errorBg: '#7f1d1d', + + // recording action buttons + btnStart: '#16a34a', + btnPause: '#d97706', + btnStop: '#dc2626', +} as const;