feat(mobile): custom fonts (General Sans + Geist Mono) + logo rework
FONTY:
- Dodane assets/fonts/: GeneralSans Regular/Medium/Semibold (Fontshare, free
commercial) + GeistMono Regular (Vercel OFL). Pobrane jako .ttf.
- expo-font ~13.0.4 (matchuje SDK 52). Native module jest w APK bo `expo`
ciagnie expo-font jako bezposrednia zaleznosc -> useFonts dziala przez OTA
bez rebuildu.
- App.tsx: useFonts() gate (blokuje render do zaladowania, .ttf z bundla <100ms)
+ globalny Text.defaultProps fontFamily=GeneralSans-Regular dla body.
- theme.ts: fonts = { body, medium, display, mono }. RN nie syntezuje weightow
dla custom fontow, wiec 4 osobne rodziny per-weight (gotcha udokumentowany).
- Jawne fonty na high-traffic: SceneTile (title=display, meta+dur=mono),
MoviePosterCard (j.w.), navigation (taby display/medium, header display).
LOGO:
- GoonWordmark przepisany: zamiast krzywych recznych SVG path (o-ka jako
nachodzace elipsy, zniekształcone n) renderuje PRAWDZIWY tekst w General Sans
Semibold. Dwutonowy twist: "g[oo]n" ze srodkowym "oo" w oxblood.
- GoonMark (monogram): czysty SVG koncentryczny ring + dot (oxblood) — motyw
soczewki/oka. Dla app-icon/splash gdzie font niedostepny.
- Wpiety na AgeGate (wordmark 40), Login (mark 44 + wordmark 44), nav header.
OTA: c986c911-0868-44f7-9f4a-fc2a74e53095 live (23 assets, 4 fonty serwuja 200).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a0481060f3
commit
d87263dde9
15 changed files with 140 additions and 159 deletions
|
|
@ -6,6 +6,8 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|||
import * as Sentry from '@sentry/react-native';
|
||||
import Constants from 'expo-constants';
|
||||
import { registerRootComponent } from 'expo';
|
||||
import { useFonts } from 'expo-font';
|
||||
import { Text as RNText } from 'react-native';
|
||||
import * as ScreenCapture from 'expo-screen-capture';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import * as Updates from 'expo-updates';
|
||||
|
|
@ -75,7 +77,30 @@ const queryClient = new QueryClient({
|
|||
},
|
||||
});
|
||||
|
||||
// Globalny default fontu dla całego <Text> — General Sans Regular jako body.
|
||||
// Komponenty które chcą display/mono nadpisują fontFamily jawnie (bo RN nie
|
||||
// syntezuje weightów dla custom fontów). Bold-ale-bez-fontFamily tekst zostanie
|
||||
// Regular weightem General Sans — wciąż distinctive face, akceptowalne dla
|
||||
// nietkniętych ekranów; high-traffic komponenty mają jawny Semibold.
|
||||
let _textDefaultApplied = false;
|
||||
function applyDefaultFont() {
|
||||
if (_textDefaultApplied) return;
|
||||
_textDefaultApplied = true;
|
||||
const T = RNText as unknown as { defaultProps?: { style?: unknown } };
|
||||
T.defaultProps = T.defaultProps || {};
|
||||
const prev = T.defaultProps.style;
|
||||
T.defaultProps.style = [{ fontFamily: 'GeneralSans-Regular' }, prev].filter(Boolean);
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
const [fontsLoaded] = useFonts({
|
||||
'GeneralSans-Regular': require('./assets/fonts/GeneralSans-Regular.ttf'),
|
||||
'GeneralSans-Medium': require('./assets/fonts/GeneralSans-Medium.ttf'),
|
||||
'GeneralSans-Semibold': require('./assets/fonts/GeneralSans-Semibold.ttf'),
|
||||
'GeistMono-Regular': require('./assets/fonts/GeistMono-Regular.ttf'),
|
||||
});
|
||||
if (fontsLoaded) applyDefaultFont();
|
||||
|
||||
const [hydrated, setHydrated] = useState(false);
|
||||
const [ageAccepted, setAgeAccepted] = useState(false);
|
||||
const [client, setClient] = useState<GoonClient | null>(null);
|
||||
|
|
@ -245,7 +270,7 @@ export default function App() {
|
|||
return () => sub.remove();
|
||||
}, []);
|
||||
|
||||
if (!hydrated || !lockReady) {
|
||||
if (!fontsLoaded || !hydrated || !lockReady) {
|
||||
return (
|
||||
<View style={{ flex: 1, backgroundColor: theme.bg, justifyContent: 'center' }}>
|
||||
<ActivityIndicator color={theme.fg} />
|
||||
|
|
|
|||
|
|
@ -44,7 +44,8 @@
|
|||
}
|
||||
],
|
||||
"expo-video",
|
||||
"@sentry/react-native/expo"
|
||||
"@sentry/react-native/expo",
|
||||
"expo-font"
|
||||
],
|
||||
"extra": {
|
||||
"sentryDsn": "",
|
||||
|
|
|
|||
BIN
mobile/assets/fonts/GeistMono-Regular.ttf
Normal file
BIN
mobile/assets/fonts/GeistMono-Regular.ttf
Normal file
Binary file not shown.
BIN
mobile/assets/fonts/GeneralSans-Medium.ttf
Normal file
BIN
mobile/assets/fonts/GeneralSans-Medium.ttf
Normal file
Binary file not shown.
BIN
mobile/assets/fonts/GeneralSans-Regular.ttf
Normal file
BIN
mobile/assets/fonts/GeneralSans-Regular.ttf
Normal file
Binary file not shown.
BIN
mobile/assets/fonts/GeneralSans-Semibold.ttf
Normal file
BIN
mobile/assets/fonts/GeneralSans-Semibold.ttf
Normal file
Binary file not shown.
27
mobile/package-lock.json
generated
27
mobile/package-lock.json
generated
|
|
@ -16,6 +16,7 @@
|
|||
"expo-asset": "~11.0.5",
|
||||
"expo-build-properties": "~0.13.3",
|
||||
"expo-clipboard": "~7.0.1",
|
||||
"expo-font": "~13.0.4",
|
||||
"expo-haptics": "~14.0.1",
|
||||
"expo-image": "~2.0.7",
|
||||
"expo-intent-launcher": "~12.0.2",
|
||||
|
|
@ -6246,6 +6247,19 @@
|
|||
"integrity": "sha512-t+1F1tiDocSot8iSnrn/CjTUMvVvPV2DpafSVcticpbSzMGybEN7wcamO1t18fK7WxGXpZE9gxtd80qwv/LLqQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/expo-font": {
|
||||
"version": "13.0.4",
|
||||
"resolved": "https://registry.npmjs.org/expo-font/-/expo-font-13.0.4.tgz",
|
||||
"integrity": "sha512-eAP5hyBgC8gafFtprsz0HMaB795qZfgJWqTmU0NfbSin1wUuVySFMEPMOrTkTgmazU73v4Cb4x7p86jY1XXYUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fontfaceobserver": "^2.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"expo": "*",
|
||||
"react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/expo-haptics": {
|
||||
"version": "14.0.1",
|
||||
"resolved": "https://registry.npmjs.org/expo-haptics/-/expo-haptics-14.0.1.tgz",
|
||||
|
|
@ -6500,19 +6514,6 @@
|
|||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/expo/node_modules/expo-font": {
|
||||
"version": "13.0.4",
|
||||
"resolved": "https://registry.npmjs.org/expo-font/-/expo-font-13.0.4.tgz",
|
||||
"integrity": "sha512-eAP5hyBgC8gafFtprsz0HMaB795qZfgJWqTmU0NfbSin1wUuVySFMEPMOrTkTgmazU73v4Cb4x7p86jY1XXYUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fontfaceobserver": "^2.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"expo": "*",
|
||||
"react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/expo/node_modules/expo-keep-awake": {
|
||||
"version": "14.0.3",
|
||||
"resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-14.0.3.tgz",
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
"expo-asset": "~11.0.5",
|
||||
"expo-build-properties": "~0.13.3",
|
||||
"expo-clipboard": "~7.0.1",
|
||||
"expo-font": "~13.0.4",
|
||||
"expo-haptics": "~14.0.1",
|
||||
"expo-image": "~2.0.7",
|
||||
"expo-intent-launcher": "~12.0.2",
|
||||
|
|
|
|||
|
|
@ -1,124 +1,76 @@
|
|||
/**
|
||||
* Goon wordmark — custom letterform, NOT a typed text in a font.
|
||||
* Goon brand marks.
|
||||
*
|
||||
* 4 litery jako path geometry. Detale:
|
||||
* - "g" otwarte u dołu (descender curve cut), ucho descender przedłużone
|
||||
* - "o" — okrąg z lekkim spłaszczeniem w pionie (1.0×0.95 aspect), counter
|
||||
* z 60% wysokości (cięższe niż typical grotesk → daje "weight")
|
||||
* - "n" — minimal grotesk, stem łączy się z arc bez serif joint
|
||||
* - Kerning naturalny, ale "oo" lekko ciaśniejsze niż "go"/"on"
|
||||
* Rework 2026-05-30: poprzednia wersja rysowała litery ręcznie jako SVG path
|
||||
* geometry — wychodziło krzywo (o-ka jako nachodzące elipsy, zniekształcone n).
|
||||
* Teraz mamy General Sans Semibold jako realny font (useFonts w App.tsx), więc
|
||||
* wordmark renderuje PRAWDZIWY tekst w tej rodzinie — czysto i spójnie z resztą
|
||||
* typografii.
|
||||
*
|
||||
* Viewbox 200×56 — 4 litery × ~44px width, używamy aspect ratio scale.
|
||||
* Distinctive twist: dwutonowe "g[oo]n" — środkowe "oo" w oxblood (brand accent),
|
||||
* "g"+"n" w foreground. Czytelne nawet w małym headerze, wiąże logo z paletą.
|
||||
*
|
||||
* Użycie:
|
||||
* <GoonWordmark width={120} color={theme.fg} />
|
||||
* <GoonWordmark width={80} color={theme.accent} /> // dla splash
|
||||
* <GoonWordmark size={26} /> // header
|
||||
* <GoonWordmark size={48} mono /> // splash, jednolity kolor
|
||||
*/
|
||||
import React from 'react';
|
||||
import Svg, { Path } from 'react-native-svg';
|
||||
import { Text, View } from 'react-native';
|
||||
import Svg, { Circle } from 'react-native-svg';
|
||||
|
||||
interface Props {
|
||||
width?: number;
|
||||
import { fonts, theme } from '../theme';
|
||||
|
||||
interface WordmarkProps {
|
||||
/** fontSize wordmarku w px. */
|
||||
size?: number;
|
||||
/** Kolor liter g+n (oo zawsze accent, chyba że `mono`). */
|
||||
color?: string;
|
||||
/** Jednolity kolor (bez dwutonu) — np. na splash gdzie tło = accent. */
|
||||
mono?: boolean;
|
||||
}
|
||||
|
||||
export function GoonWordmark({ width = 120, color = '#F5EDE0' }: Props) {
|
||||
const height = width * (56 / 200);
|
||||
export function GoonWordmark({ size = 26, color = theme.fg, mono = false }: WordmarkProps) {
|
||||
const base = {
|
||||
fontFamily: fonts.display,
|
||||
fontSize: size,
|
||||
letterSpacing: -size * 0.03,
|
||||
includeFontPadding: false as const,
|
||||
};
|
||||
const accent = mono ? color : theme.accent;
|
||||
return (
|
||||
<Svg width={width} height={height} viewBox="0 0 200 56" fill="none">
|
||||
{/*
|
||||
g — descender extends below baseline. Counter = 12 radius hole.
|
||||
Path: outer ellipse, inner counter (evenodd), descender hook from
|
||||
bottom-right going down-left.
|
||||
*/}
|
||||
<Path
|
||||
d="M 28 14
|
||||
A 14 13 0 1 1 28 40
|
||||
A 14 13 0 1 1 28 14
|
||||
Z
|
||||
M 28 22
|
||||
A 6 5 0 1 0 28 32
|
||||
A 6 5 0 1 0 28 22
|
||||
Z
|
||||
M 38 28
|
||||
L 38 48
|
||||
A 12 8 0 0 1 18 50"
|
||||
fill={color}
|
||||
stroke={color}
|
||||
strokeWidth="0.5"
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
{/*
|
||||
o — slightly flat ellipse, heavy counter.
|
||||
*/}
|
||||
<Path
|
||||
d="M 76 14
|
||||
A 14 13 0 1 1 76 40
|
||||
A 14 13 0 1 1 76 14
|
||||
Z
|
||||
M 76 22
|
||||
A 6 5 0 1 0 76 32
|
||||
A 6 5 0 1 0 76 22
|
||||
Z"
|
||||
fill={color}
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
{/* o #2 — tight pair z poprzednim o (kerning -2px) */}
|
||||
<Path
|
||||
d="M 118 14
|
||||
A 14 13 0 1 1 118 40
|
||||
A 14 13 0 1 1 118 14
|
||||
Z
|
||||
M 118 22
|
||||
A 6 5 0 1 0 118 32
|
||||
A 6 5 0 1 0 118 22
|
||||
Z"
|
||||
fill={color}
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
{/*
|
||||
n — grotesk z arc bez serif. Lewa stem prosty, prawa wychodzi z arc.
|
||||
*/}
|
||||
<Path
|
||||
d="M 144 16
|
||||
L 144 40
|
||||
L 150 40
|
||||
L 150 26
|
||||
A 8 10 0 0 1 166 26
|
||||
L 166 40
|
||||
L 172 40
|
||||
L 172 24
|
||||
A 12 14 0 0 0 150 22
|
||||
L 150 16
|
||||
Z"
|
||||
fill={color}
|
||||
/>
|
||||
</Svg>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<Text style={[base, { color }]}>g</Text>
|
||||
<Text style={[base, { color: accent }]}>oo</Text>
|
||||
<Text style={[base, { color }]}>n</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Monogram — tylko "g" + ucho descender, jako standalone icon.
|
||||
* 56×56, używany jako adaptive-icon / splash mark.
|
||||
* Monogram — koncentryczny ring + wypełniona kropka (oxblood). Czyta się jako
|
||||
* "o" z wordmarku, motyw soczewki/oka (watching) bez dosłowności. Czysty SVG
|
||||
* (bez fontu) — używany do generowania app-icon / adaptive-icon / splash PNG,
|
||||
* gdzie custom font nie jest dostępny.
|
||||
*
|
||||
* <GoonMark size={48} />
|
||||
*/
|
||||
export function GoonMark({ size = 48, color = '#F5EDE0' }: { size?: number; color?: string }) {
|
||||
export function GoonMark({
|
||||
size = 48,
|
||||
ringColor = theme.fg,
|
||||
dotColor = theme.accent,
|
||||
bg,
|
||||
}: {
|
||||
size?: number;
|
||||
ringColor?: string;
|
||||
dotColor?: string;
|
||||
/** Opcjonalne tło (dla icon — np. theme.bg). Pominięte = przezroczyste. */
|
||||
bg?: string;
|
||||
}) {
|
||||
// viewBox 100×100. Ring: cx50 cy50 r36, stroke 11. Dot: r15 wypełniony.
|
||||
return (
|
||||
<Svg width={size} height={size} viewBox="0 0 56 56" fill="none">
|
||||
<Path
|
||||
d="M 28 10
|
||||
A 14 13 0 1 1 28 36
|
||||
A 14 13 0 1 1 28 10
|
||||
Z
|
||||
M 28 18
|
||||
A 6 5 0 1 0 28 28
|
||||
A 6 5 0 1 0 28 18
|
||||
Z
|
||||
M 38 24
|
||||
L 38 44
|
||||
A 12 8 0 0 1 18 46"
|
||||
fill={color}
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
<Svg width={size} height={size} viewBox="0 0 100 100" fill="none">
|
||||
{bg ? <Circle cx={50} cy={50} r={50} fill={bg} /> : null}
|
||||
<Circle cx={50} cy={50} r={36} stroke={ringColor} strokeWidth={11} fill="none" />
|
||||
<Circle cx={50} cy={50} r={15} fill={dotColor} />
|
||||
</Svg>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { Image } from 'expo-image';
|
||||
import React from 'react';
|
||||
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
||||
import { theme } from '../theme';
|
||||
import { fonts, theme } from '../theme';
|
||||
import type { MovieOut } from '../types';
|
||||
|
||||
export function MoviePosterCard({
|
||||
|
|
@ -101,7 +101,7 @@ const styles = StyleSheet.create({
|
|||
paddingHorizontal: 6,
|
||||
paddingVertical: 2,
|
||||
},
|
||||
newBadgeText: { color: theme.fg, fontSize: 10, fontWeight: '800', letterSpacing: 0.5 },
|
||||
newBadgeText: { color: theme.fg, fontSize: 9, fontFamily: fonts.mono, fontWeight: '700', letterSpacing: 0.6 },
|
||||
posterDimmed: { opacity: 0.45 },
|
||||
watchedBadge: {
|
||||
position: 'absolute',
|
||||
|
|
@ -124,7 +124,7 @@ const styles = StyleSheet.create({
|
|||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
},
|
||||
progressFg: { height: 3, backgroundColor: theme.accent },
|
||||
title: { color: theme.fg, fontSize: 13, fontWeight: '600', marginTop: 6 },
|
||||
title: { color: theme.fg, fontSize: 13, fontFamily: fonts.display, marginTop: 6, letterSpacing: -0.2 },
|
||||
titleDimmed: { color: theme.muted },
|
||||
meta: { color: theme.muted, fontSize: 11, marginTop: 2 },
|
||||
meta: { color: theme.muted, fontSize: 10, fontFamily: fonts.mono, marginTop: 2, letterSpacing: 0.5, textTransform: 'uppercase' },
|
||||
});
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import React, { useState } from 'react';
|
|||
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
import type { RootStackParamList } from '../navigation';
|
||||
import { theme } from '../theme';
|
||||
import { fonts, theme } from '../theme';
|
||||
import type { SceneOut } from '../types';
|
||||
import { Thumb } from './Thumb';
|
||||
|
||||
|
|
@ -190,22 +190,23 @@ const styles = StyleSheet.create({
|
|||
durText: {
|
||||
color: theme.fg,
|
||||
fontSize: 11,
|
||||
fontWeight: '600',
|
||||
fontFamily: fonts.mono,
|
||||
fontVariant: ['tabular-nums'],
|
||||
},
|
||||
title: {
|
||||
color: theme.fg,
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
fontFamily: fonts.display,
|
||||
marginTop: 8,
|
||||
letterSpacing: -0.2,
|
||||
},
|
||||
titleDim: { color: theme.muted },
|
||||
meta: {
|
||||
color: theme.muted,
|
||||
fontSize: 11,
|
||||
marginTop: 2,
|
||||
letterSpacing: 0.3,
|
||||
fontSize: 10,
|
||||
fontFamily: fonts.mono,
|
||||
marginTop: 3,
|
||||
letterSpacing: 0.5,
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import { SitesScreen } from './screens/SitesScreen';
|
|||
import { StudioScenesScreen } from './screens/StudioScenesScreen';
|
||||
import { TagScenesScreen } from './screens/TagScenesScreen';
|
||||
import { TagsScreen } from './screens/TagsScreen';
|
||||
import { theme } from './theme';
|
||||
import { fonts, theme } from './theme';
|
||||
|
||||
export type RootStackParamList = {
|
||||
Scenes: undefined;
|
||||
|
|
@ -90,8 +90,8 @@ function TopTabs({
|
|||
const tabs: TopTab[] = ['Scenes', 'Movies', 'Sites'];
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', gap: 16, paddingHorizontal: 12, alignItems: 'center' }}>
|
||||
{/* Wordmark — branding po lewej, dystans 14px do pierwszego tabu. */}
|
||||
<GoonWordmark width={64} color={theme.fg} />
|
||||
{/* Wordmark — branding po lewej, dystans do pierwszego tabu. */}
|
||||
<GoonWordmark size={24} />
|
||||
<View style={{ width: 1, height: 18, backgroundColor: theme.border, marginRight: 2 }} />
|
||||
{tabs.map((t) => {
|
||||
const active = t === current;
|
||||
|
|
@ -101,7 +101,7 @@ function TopTabs({
|
|||
style={{
|
||||
color: active ? theme.accent : theme.muted,
|
||||
fontSize: 13,
|
||||
fontWeight: active ? '700' : '500',
|
||||
fontFamily: active ? fonts.display : fonts.medium,
|
||||
letterSpacing: 0.3,
|
||||
}}
|
||||
>
|
||||
|
|
@ -145,7 +145,7 @@ export function AppNavigator({ onLogout, client, appVersion }: AppNavigatorProps
|
|||
<Stack.Navigator
|
||||
screenOptions={{
|
||||
headerStyle: { backgroundColor: theme.card },
|
||||
headerTitleStyle: { color: theme.fg },
|
||||
headerTitleStyle: { color: theme.fg, fontFamily: fonts.display },
|
||||
headerTintColor: theme.accent,
|
||||
contentStyle: { backgroundColor: theme.bg },
|
||||
}}
|
||||
|
|
@ -248,7 +248,7 @@ export function AppNavigator({ onLogout, client, appVersion }: AppNavigatorProps
|
|||
/>
|
||||
<Stack.Screen
|
||||
name="AppLockSettings"
|
||||
options={{ title: 'Blokada aplikacji' }}
|
||||
options={{ title: 'App lock' }}
|
||||
>
|
||||
{() => <AppLockSettingsScreen />}
|
||||
</Stack.Screen>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
View,
|
||||
} from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { GoonWordmark } from '../components/GoonWordmark';
|
||||
import { markAccepted } from '../lib/agegate';
|
||||
import { theme } from '../theme';
|
||||
|
||||
|
|
@ -65,7 +66,7 @@ export function AgeGateScreen({ onAccept }: Props) {
|
|||
contentContainerStyle={styles.scroll}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<Text style={styles.title}>goon</Text>
|
||||
<GoonWordmark size={40} />
|
||||
<Text style={styles.subtitle}>Adult content · Self-hosted only</Text>
|
||||
|
||||
<View style={styles.section}>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
View,
|
||||
} from 'react-native';
|
||||
import { GoonClient } from '../api';
|
||||
import { GoonMark, GoonWordmark } from '../components/GoonWordmark';
|
||||
import { saveCredentials } from '../storage';
|
||||
import { theme } from '../theme';
|
||||
|
||||
|
|
@ -57,8 +58,9 @@ export function LoginScreen({ onAuthenticated }: Props) {
|
|||
keyboardShouldPersistTaps="handled"
|
||||
>
|
||||
<View style={styles.brandBlock}>
|
||||
<View style={styles.brandDot} />
|
||||
<Text style={styles.title}>goon</Text>
|
||||
<GoonMark size={44} bg="transparent" />
|
||||
<View style={{ height: 14 }} />
|
||||
<GoonWordmark size={44} />
|
||||
<Text style={styles.subtitle}>self-hosted scene catalog</Text>
|
||||
</View>
|
||||
|
||||
|
|
|
|||
|
|
@ -41,29 +41,26 @@ export const theme = {
|
|||
} as const;
|
||||
|
||||
/**
|
||||
* Font family — pusty string = system default na iOS/Android (uniknięcie
|
||||
* "font not found" warningów). Po zainstalowaniu custom fontów (TODO niżej):
|
||||
* 1. `npx expo install expo-font expo-splash-screen`
|
||||
* 2. Pobierz fonty:
|
||||
* - General Sans (Fontshare, free): https://www.fontshare.com/fonts/general-sans
|
||||
* - Geist Mono (Vercel, free OFL): https://github.com/vercel/geist-font
|
||||
* 3. Skopiuj .ttf do `mobile/assets/fonts/`
|
||||
* 4. W App.tsx:
|
||||
* const [loaded] = useFonts({
|
||||
* 'GeneralSans-Semibold': require('./assets/fonts/GeneralSans-Semibold.ttf'),
|
||||
* 'GeneralSans-Regular': require('./assets/fonts/GeneralSans-Regular.ttf'),
|
||||
* 'GeistMono-Regular': require('./assets/fonts/GeistMono-Regular.ttf'),
|
||||
* });
|
||||
* if (!loaded) return null; // lub SplashScreen.preventAutoHideAsync()
|
||||
* 5. Zmień stałe poniżej na konkretne fontFamily strings.
|
||||
* Font family — General Sans (display + body, Fontshare) + Geist Mono (meta,
|
||||
* Vercel OFL). Pliki .ttf w `mobile/assets/fonts/`, ładowane przez useFonts()
|
||||
* w App.tsx (runtime load — działa przez OTA bo expo-font native jest w APK).
|
||||
*
|
||||
* Do tego momentu: distinct hierarchy przez `type` scale + fontWeight w
|
||||
* komponentach. System font (San Francisco / Roboto) jest OK temporary.
|
||||
* WAŻNE (custom-font gotcha): RN NIE syntezuje weightów dla custom fontów —
|
||||
* fontWeight:'700' na fontFamily:'GeneralSans-Regular' NIE pogrubi. Trzeba
|
||||
* jawnie wskazać rodzinę per-weight. Stąd 4 osobne sloty:
|
||||
* - body → Regular (400) — domyślny tekst
|
||||
* - medium → Medium (500) — labels, tab inactive
|
||||
* - display → Semibold (600)— headingi, tytuły, bold
|
||||
* - mono → Geist Mono — meta, duration, liczby, kategorie
|
||||
*
|
||||
* Gdy useFonts jeszcze nie ready, App.tsx blokuje render (fonty z bundla
|
||||
* ładują się <100ms), więc te stałe są zawsze valid przy pierwszym paint.
|
||||
*/
|
||||
export const fonts = {
|
||||
display: undefined as string | undefined,
|
||||
displayRegular: undefined as string | undefined,
|
||||
mono: undefined as string | undefined,
|
||||
body: 'GeneralSans-Regular',
|
||||
medium: 'GeneralSans-Medium',
|
||||
display: 'GeneralSans-Semibold',
|
||||
mono: 'GeistMono-Regular',
|
||||
} as const;
|
||||
|
||||
export function scoreColor(score: number): string {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue