| … | … | @@ -59,7 +59,7 @@ export default function Create ({ client, onCreated, onCancel = null }) { |
| 59 | 59 | if (made) { |
| 60 | 60 | const words = String(made.mnemonic || '').split(/\s+/).filter(Boolean) |
| 61 | 61 | return ( |
| 62 | <ScrollView contentContainerStyle={s.wrap}> | |
| 62 | <ScrollView style={s.screen} contentContainerStyle={s.wrap}> | |
| 63 | 63 | <Text style={s.h1}>Write these down</Text> |
| 64 | 64 | <View style={s.danger}> |
| 65 | 65 | <Text style={s.dangerH}>This is shown once and never again</Text> |
| … | … | @@ -107,7 +107,7 @@ export default function Create ({ client, onCreated, onCancel = null }) { |
| 107 | 107 | |
| 108 | 108 | // ── PHASE 1: what it costs, and the passphrase ─────────────────────────────────────────────────── |
| 109 | 109 | return ( |
| 110 | <ScrollView contentContainerStyle={s.wrap}> | |
| 110 | <ScrollView style={s.screen} contentContainerStyle={s.wrap}> | |
| 111 | 111 | <Text style={s.h1}>Create a new identity</Text> |
| 112 | 112 | |
| 113 | 113 | <Text style={s.body}> |
| … | … | @@ -173,8 +173,13 @@ export default function Create ({ client, onCreated, onCancel = null }) { |
| 173 | 173 | ) |
| 174 | 174 | } |
| 175 | 175 | |
| 176 | // ⛔ PAINTS ITS OWN BACKGROUND — see Pair.jsx for the full reason. AndroidManifest sets | |
| 177 | // Theme.AppCompat.DayNight.NoActionBar, so an unpainted view sits on a DARK window in dark mode and | |
| 178 | // every colour here was chosen for white. On THIS screen that is not merely ugly: the recovery words | |
| 179 | // are shown exactly once, and a user who cannot read them has lost the identity permanently. | |
| 176 | 180 | const s = StyleSheet.create({ |
| 177 | wrap: { padding: 24, gap: 10 }, | |
| 181 | screen: { flex: 1, backgroundColor: '#fff' }, | |
| 182 | wrap: { padding: 24, gap: 10, backgroundColor: '#fff' }, | |
| 178 | 183 | h1: { fontSize: 22, fontWeight: '700', marginBottom: 4 }, |
| 179 | 184 | body: { fontSize: 15, lineHeight: 21, color: '#333' }, |
| 180 | 185 | b: { fontWeight: '700' }, |
| … | … | @@ -185,11 +190,11 @@ const s = StyleSheet.create({ |
| 185 | 190 | dangerH: { fontWeight: '700', color: '#7a1f1f' }, |
| 186 | 191 | dangerB: { fontSize: 14, lineHeight: 20, color: '#7a1f1f' }, |
| 187 | 192 | words: { backgroundColor: '#f7f7f7', borderRadius: 8, padding: 14, gap: 4, marginVertical: 6 }, |
| 188 | word: { fontFamily: 'monospace', fontSize: 15, lineHeight: 22 }, | |
| 189 | wordN: { color: '#999' }, | |
| 190 | label: { fontWeight: '600', marginTop: 10 }, | |
| 191 | code: { fontFamily: 'monospace', backgroundColor: '#f2f2f2', padding: 10, borderRadius: 6, fontSize: 12 }, | |
| 192 | input: { borderWidth: 1, borderColor: '#ccc', borderRadius: 6, padding: 10, fontSize: 14, marginTop: 6 }, | |
| 193 | word: { fontFamily: 'monospace', fontSize: 15, lineHeight: 22, color: '#14171a' }, | |
| 194 | wordN: { color: '#5b6b78' }, | |
| 195 | label: { fontWeight: '600', marginTop: 10, color: '#14171a' }, | |
| 196 | code: { fontFamily: 'monospace', backgroundColor: '#f2f2f2', padding: 10, borderRadius: 6, fontSize: 12, color: '#14171a' }, | |
| 197 | input: { borderWidth: 1, borderColor: '#ccc', borderRadius: 6, padding: 10, fontSize: 14, marginTop: 6, color: '#14171a', backgroundColor: '#fff' }, | |
| 193 | 198 | btn: { backgroundColor: '#111', borderRadius: 8, padding: 14, alignItems: 'center', marginTop: 16 }, |
| 194 | 199 | btnOff: { opacity: 0.4 }, |
| 195 | 200 | btnT: { color: '#fff', fontWeight: '600' }, |
| … | … | @@ -197,11 +202,11 @@ const s = StyleSheet.create({ |
| 197 | 202 | box: { width: 24, height: 24, borderWidth: 2, borderColor: '#888', borderRadius: 4, alignItems: 'center', justifyContent: 'center' }, |
| 198 | 203 | boxOn: { borderColor: '#111', backgroundColor: '#111' }, |
| 199 | 204 | tick: { color: '#fff', fontWeight: '700' }, |
| 200 | checkT: { fontSize: 15, flexShrink: 1 }, | |
| 201 | dim: { color: '#888', fontSize: 13 }, | |
| 205 | checkT: { fontSize: 15, flexShrink: 1, color: '#14171a' }, | |
| 206 | dim: { color: '#5b6b78', fontSize: 13 }, | |
| 202 | 207 | err: { color: '#b00', marginTop: 8 }, |
| 203 | 208 | skip: { alignItems: 'center', paddingVertical: 14, marginTop: 6 }, |
| 204 | 209 | skipT: { color: '#2563eb', fontSize: 16 }, |
| 205 | 210 | skipOff: { opacity: 0.4 }, |
| 206 | foot: { color: '#888', fontSize: 13, marginTop: 20, lineHeight: 19 } | |
| 211 | foot: { color: '#5b6b78', fontSize: 13, marginTop: 20, lineHeight: 19 } | |
| 207 | 212 | }) |
| … | … | @@ -49,7 +49,7 @@ export default function Pair ({ client, onPaired, onSkip = null, onCreate = null |
| 49 | 49 | } |
| 50 | 50 | |
| 51 | 51 | return ( |
| 52 | <ScrollView contentContainerStyle={s.wrap}> | |
| 52 | <ScrollView style={s.screen} contentContainerStyle={s.wrap}> | |
| 53 | 53 | <Text style={s.h1}>Connect this phone</Text> |
| 54 | 54 | |
| 55 | 55 | <Text style={s.body}> |
| … | … | @@ -135,28 +135,42 @@ export default function Pair ({ client, onPaired, onSkip = null, onCreate = null |
| 135 | 135 | ) |
| 136 | 136 | } |
| 137 | 137 | |
| 138 | // ⛔ THIS SCREEN MUST PAINT ITS OWN BACKGROUND. AndroidManifest sets | |
| 139 | // Theme.AppCompat.DayNight.NoActionBar, so on a handset in dark mode the window behind an unpainted | |
| 140 | // view is DARK — while every colour here (and the default black of an unstyled <Text>) was chosen for | |
| 141 | // white. The result was a screen of near-invisible text, reported on a handset 2026-08-25. Every other | |
| 142 | // screen in the app already does this (Screens.jsx `list`); these two ceremonies were the ones that | |
| 143 | // never did, because they were written as overlays on top of an app that was already painted. | |
| 144 | // | |
| 145 | // Painted explicitly rather than made theme-aware: the app is a light design throughout, and a single | |
| 146 | // ceremony screen quietly following the system theme would be the only dark surface in it. | |
| 147 | // | |
| 148 | // TEXT COLOURS ARE EXPLICIT for the same reason — an unstyled <Text> inherits the platform's, which is | |
| 149 | // what flips to white and vanishes. Same for the inputs: a TextInput with no `color` renders typed | |
| 150 | // characters in the theme's colour, so on a dark phone you could not read what you had pasted. | |
| 138 | 151 | const s = StyleSheet.create({ |
| 139 | wrap: { padding: 24, gap: 10 }, | |
| 140 | h1: { fontSize: 22, fontWeight: '700', marginBottom: 4 }, | |
| 152 | screen: { flex: 1, backgroundColor: '#fff' }, | |
| 153 | wrap: { padding: 24, gap: 10, backgroundColor: '#fff' }, | |
| 154 | h1: { fontSize: 22, fontWeight: '700', marginBottom: 4, color: '#14171a' }, | |
| 141 | 155 | body: { fontSize: 15, lineHeight: 21, color: '#333' }, |
| 142 | 156 | warn: { backgroundColor: '#fff6e5', borderColor: '#e8c98a', borderWidth: 1, borderRadius: 8, padding: 14, gap: 8, marginVertical: 10 }, |
| 143 | 157 | warnH: { fontWeight: '700' }, |
| 144 | 158 | warnB: { fontSize: 14, lineHeight: 20, color: '#4a3c1a' }, |
| 145 | label: { fontWeight: '600', marginTop: 10 }, | |
| 146 | input: { borderWidth: 1, borderColor: '#ccc', borderRadius: 6, padding: 10, fontSize: 14 }, | |
| 159 | label: { fontWeight: '600', marginTop: 10, color: '#14171a' }, | |
| 160 | input: { borderWidth: 1, borderColor: '#ccc', borderRadius: 6, padding: 10, fontSize: 14, color: '#14171a', backgroundColor: '#fff' }, | |
| 147 | 161 | mono: { fontFamily: 'monospace', minHeight: 70 }, |
| 148 | code: { fontFamily: 'monospace', backgroundColor: '#f2f2f2', padding: 10, borderRadius: 6 }, | |
| 162 | code: { fontFamily: 'monospace', backgroundColor: '#f2f2f2', padding: 10, borderRadius: 6, color: '#14171a' }, | |
| 149 | 163 | btn: { backgroundColor: '#111', borderRadius: 8, padding: 14, alignItems: 'center', marginTop: 16 }, |
| 150 | 164 | btnOff: { opacity: 0.4 }, |
| 151 | 165 | btnT: { color: '#fff', fontWeight: '600' }, |
| 152 | dim: { color: '#888', fontSize: 13 }, | |
| 166 | dim: { color: '#5b6b78', fontSize: 13 }, | |
| 153 | 167 | err: { color: '#b00', marginTop: 8 }, |
| 154 | 168 | alt: { borderTopWidth: 1, borderTopColor: '#e5e5e5', marginTop: 22, paddingTop: 18, gap: 6 }, |
| 155 | altH: { fontWeight: '700', fontSize: 15 }, | |
| 169 | altH: { fontWeight: '700', fontSize: 15, color: '#14171a' }, | |
| 156 | 170 | altBtn: { borderWidth: 1, borderColor: '#111', borderRadius: 8, padding: 13, alignItems: 'center', marginTop: 8 }, |
| 157 | 171 | altBtnT: { color: '#111', fontWeight: '600' }, |
| 158 | 172 | skip: { alignItems: 'center', paddingVertical: 14, marginTop: 6 }, |
| 159 | 173 | skipT: { color: '#2563eb', fontSize: 16 }, |
| 160 | 174 | skipOff: { opacity: 0.4 }, |
| 161 | foot: { color: '#888', fontSize: 13, marginTop: 20, lineHeight: 19 } | |
| 175 | foot: { color: '#5b6b78', fontSize: 13, marginTop: 20, lineHeight: 19 } | |
| 162 | 176 | }) |
| … | … | @@ -1,6 +1,6 @@ |
| 1 | 1 | { |
| 2 | 2 | "name": "favela-android", |
| 3 | "version": "0.1.6", | |
| 3 | "version": "0.1.7", | |
| 4 | 4 | "description": "favela-android — a React Native client over a bare-kit worklet running a FULL favela node (hyper-proto-peer + appview + hyperswarm) on the phone. favela-web is reference for the logic, NOT the rendering: this is a different rendering surface. See spec/architecture.md.", |
| 5 | 5 | "private": true, |
| 6 | 6 | "type": "module", |